railswiki 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +185 -0
  4. data/Rakefile +24 -0
  5. data/app/assets/config/railswiki_manifest.js +2 -0
  6. data/app/assets/javascripts/railswiki/application.js +13 -0
  7. data/app/assets/javascripts/railswiki/histories.js +2 -0
  8. data/app/assets/javascripts/railswiki/invites.js +2 -0
  9. data/app/assets/javascripts/railswiki/pages.js +2 -0
  10. data/app/assets/javascripts/railswiki/sessions.js +2 -0
  11. data/app/assets/javascripts/railswiki/uploaded_files.js +2 -0
  12. data/app/assets/javascripts/railswiki/users.js +2 -0
  13. data/app/assets/stylesheets/railswiki/application.css +15 -0
  14. data/app/assets/stylesheets/railswiki/histories.css +4 -0
  15. data/app/assets/stylesheets/railswiki/invites.css +4 -0
  16. data/app/assets/stylesheets/railswiki/pages.scss +55 -0
  17. data/app/assets/stylesheets/railswiki/sessions.css +4 -0
  18. data/app/assets/stylesheets/railswiki/uploaded_files.scss +54 -0
  19. data/app/assets/stylesheets/railswiki/users.css +4 -0
  20. data/app/controllers/railswiki/application_controller.rb +126 -0
  21. data/app/controllers/railswiki/histories_controller.rb +48 -0
  22. data/app/controllers/railswiki/invites_controller.rb +61 -0
  23. data/app/controllers/railswiki/pages_controller.rb +141 -0
  24. data/app/controllers/railswiki/sessions_controller.rb +75 -0
  25. data/app/controllers/railswiki/uploaded_files_controller.rb +100 -0
  26. data/app/controllers/railswiki/users_controller.rb +55 -0
  27. data/app/helpers/railswiki/application_helper.rb +26 -0
  28. data/app/helpers/railswiki/histories_helper.rb +4 -0
  29. data/app/helpers/railswiki/invites_helper.rb +4 -0
  30. data/app/helpers/railswiki/pages_helper.rb +76 -0
  31. data/app/helpers/railswiki/sessions_helper.rb +4 -0
  32. data/app/helpers/railswiki/title_helper.rb +7 -0
  33. data/app/helpers/railswiki/uploaded_files_helper.rb +4 -0
  34. data/app/helpers/railswiki/users_helper.rb +4 -0
  35. data/app/helpers/railswiki/wiki_helper.rb +189 -0
  36. data/app/jobs/railswiki/application_job.rb +4 -0
  37. data/app/mailers/railswiki/application_mailer.rb +6 -0
  38. data/app/models/railswiki/application_record.rb +5 -0
  39. data/app/models/railswiki/history.rb +14 -0
  40. data/app/models/railswiki/invite.rb +20 -0
  41. data/app/models/railswiki/page.rb +56 -0
  42. data/app/models/railswiki/uploaded_file.rb +32 -0
  43. data/app/models/railswiki/user.rb +27 -0
  44. data/app/uploaders/railswiki/file_uploader.rb +56 -0
  45. data/app/views/layouts/railswiki/application.html.erb +25 -0
  46. data/app/views/railswiki/histories/index.html.erb +33 -0
  47. data/app/views/railswiki/histories/show.html.erb +17 -0
  48. data/app/views/railswiki/invites/_form.html.erb +38 -0
  49. data/app/views/railswiki/invites/index.html.erb +39 -0
  50. data/app/views/railswiki/invites/new.html.erb +7 -0
  51. data/app/views/railswiki/invites/show.html.erb +34 -0
  52. data/app/views/railswiki/pages/_form.html.erb +144 -0
  53. data/app/views/railswiki/pages/edit.html.erb +8 -0
  54. data/app/views/railswiki/pages/history.html.erb +11 -0
  55. data/app/views/railswiki/pages/index.html.erb +72 -0
  56. data/app/views/railswiki/pages/new.html.erb +7 -0
  57. data/app/views/railswiki/pages/show.html.erb +36 -0
  58. data/app/views/railswiki/sessions/no_invite.erb +7 -0
  59. data/app/views/railswiki/sessions/not_authorized.html.erb +12 -0
  60. data/app/views/railswiki/uploaded_files/_form.html.erb +31 -0
  61. data/app/views/railswiki/uploaded_files/_inline.html.erb +5 -0
  62. data/app/views/railswiki/uploaded_files/edit.html.erb +8 -0
  63. data/app/views/railswiki/uploaded_files/file_dialog.html.erb +11 -0
  64. data/app/views/railswiki/uploaded_files/image_dialog.html.erb +11 -0
  65. data/app/views/railswiki/uploaded_files/index.html.erb +36 -0
  66. data/app/views/railswiki/uploaded_files/new.html.erb +7 -0
  67. data/app/views/railswiki/uploaded_files/show.html.erb +29 -0
  68. data/app/views/railswiki/users/_form.html.erb +29 -0
  69. data/app/views/railswiki/users/edit.html.erb +8 -0
  70. data/app/views/railswiki/users/index.html.erb +37 -0
  71. data/app/views/railswiki/users/show.html.erb +59 -0
  72. data/app/views/shared/_formatting.md.erb +29 -0
  73. data/app/views/shared/_histories.html.erb +21 -0
  74. data/app/views/shared/_layout.html.erb +17 -0
  75. data/app/views/shared/_menu.html.erb +15 -0
  76. data/app/views/shared/_meta.html.erb +1 -0
  77. data/app/views/shared/_notices.html.erb +3 -0
  78. data/app/views/shared/_roles.html.erb +12 -0
  79. data/app/views/shared/_search.md.erb +5 -0
  80. data/config/initializers/carrierwave.rb +6 -0
  81. data/config/initializers/omniauth.rb +16 -0
  82. data/config/initializers/session_store.rb +1 -0
  83. data/config/routes.rb +25 -0
  84. data/db/migrate/20170420000841_create_railswiki_pages.rb +10 -0
  85. data/db/migrate/20170420010111_add_sessions_table.rb +12 -0
  86. data/db/migrate/20170420010147_create_railswiki_users.rb +14 -0
  87. data/db/migrate/20170420021039_add_lowercase_title_to_page.rb +5 -0
  88. data/db/migrate/20170420021840_create_railswiki_histories.rb +11 -0
  89. data/db/migrate/20170420235420_add_email_and_image_to_user.rb +8 -0
  90. data/db/migrate/20170421000333_add_last_login_to_user.rb +5 -0
  91. data/db/migrate/20170421010945_add_role_to_user.rb +7 -0
  92. data/db/migrate/20170421020932_create_railswiki_uploaded_files.rb +10 -0
  93. data/db/migrate/20170421030140_add_title_to_uploaded_file.rb +5 -0
  94. data/db/migrate/20170517224700_create_railswiki_invites.rb +12 -0
  95. data/db/migrate/20170517234452_add_role_to_invite.rb +5 -0
  96. data/db/migrate/20170622033540_set_all_mysql_tables_to_utf8.rb +54 -0
  97. data/lib/railswiki.rb +5 -0
  98. data/lib/railswiki/engine.rb +14 -0
  99. data/lib/railswiki/version.rb +3 -0
  100. data/lib/tasks/railswiki_tasks.rake +4 -0
  101. metadata +255 -0
@@ -0,0 +1,27 @@
1
+ require "email_validator"
2
+
3
+ module Railswiki
4
+ class User < ApplicationRecord
5
+ ROLE_ADMIN = "admin"
6
+ ROLE_EDITOR = "editor"
7
+ ROLE_DEFAULT = "user"
8
+
9
+ AVAILABLE_ROLES = [ROLE_ADMIN, ROLE_EDITOR, ROLE_DEFAULT]
10
+
11
+ has_many :histories, dependent: :nullify, foreign_key: "author_id"
12
+ has_many :uploaded_files, dependent: :nullify
13
+ has_many :invites, dependent: :nullify, foreign_key: "inviting_user_id"
14
+ has_one :invite, dependent: :destroy, foreign_key: "invited_user_id"
15
+
16
+ validates :provider, presence: true
17
+ validates :email, presence: true, uniqueness: true, email: true
18
+ validates :role, presence: true, inclusion: { in: AVAILABLE_ROLES }
19
+
20
+ def expose_json
21
+ {
22
+ id: id,
23
+ name: name,
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,56 @@
1
+ require_dependency "carrierwave"
2
+
3
+ module Railswiki
4
+ class FileUploader < CarrierWave::Uploader::Base
5
+
6
+ # Include RMagick or MiniMagick support:
7
+ # include CarrierWave::RMagick
8
+ # include CarrierWave::MiniMagick
9
+
10
+ # Choose what kind of storage to use for this uploader:
11
+ storage :file
12
+ # storage :fog
13
+
14
+ # Override the directory where uploaded files will be stored.
15
+ # This is a sensible default for uploaders that are meant to be mounted:
16
+ def store_dir
17
+ "public/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
18
+ end
19
+
20
+ def cache_dir
21
+ Rails.root.join 'tmp/uploads/cache'
22
+ end
23
+
24
+ # Provide a default URL as a default if there hasn't been a file uploaded:
25
+ # def default_url
26
+ # # For Rails 3.1+ asset pipeline compatibility:
27
+ # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
28
+ #
29
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
30
+ # end
31
+
32
+ # Process files as they are uploaded:
33
+ # process scale: [200, 300]
34
+ #
35
+ # def scale(width, height)
36
+ # # do something
37
+ # end
38
+
39
+ # Create different versions of your uploaded files:
40
+ # version :thumb do
41
+ # process resize_to_fit: [50, 50]
42
+ # end
43
+
44
+ # Add a white list of extensions which are allowed to be uploaded.
45
+ # For images you might use something like this:
46
+ # def extension_whitelist
47
+ # %w(jpg jpeg gif png)
48
+ # end
49
+
50
+ # Override the filename of the uploaded files:
51
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
52
+ # def filename
53
+ # "file#{File.extname(super)}" if super
54
+ # end
55
+ end
56
+ end
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= content_for?(:title) ? yield(:title) : 'Railswiki' %></title>
5
+ <%= stylesheet_link_tag "railswiki/application", media: "all" %>
6
+ <%= javascript_include_tag "railswiki/application" %>
7
+
8
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
9
+ <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
10
+
11
+ <%= javascript_pack_tag "application" %>
12
+ <%= stylesheet_pack_tag "application" %>
13
+
14
+ <meta name="viewport" content="width=device-width, initial-scale=1">
15
+
16
+ <%= csrf_meta_tags %>
17
+
18
+ <%= render partial: "shared/meta" %>
19
+ </head>
20
+ <body>
21
+
22
+ <%= render partial: "shared/layout" %>
23
+
24
+ </body>
25
+ </html>
@@ -0,0 +1,33 @@
1
+ <% title ["Recent changes"] %>
2
+
3
+ <div class="meta-content">
4
+ <h1>Histories</h1>
5
+
6
+ <table class="list-table">
7
+ <thead>
8
+ <tr>
9
+ <th>Title</th>
10
+ <th>Latest version</th>
11
+ <% if user_can?(:see_page_author) %>
12
+ <th>Author</th>
13
+ <% end %>
14
+ <th colspan="3"></th>
15
+ </tr>
16
+ </thead>
17
+
18
+ <tbody>
19
+ <% @histories.order(created_at: :desc).limit(100).each do |history| %>
20
+ <tr>
21
+ <td><%= link_to history.page.title, wiki_path(history.page) %></td>
22
+ <td><%= time_ago_in_words(history.created_at) %> ago</td>
23
+ <% if user_can?(:see_page_author) %>
24
+ <td><%= user_link history.author %></td>
25
+ <% end %>
26
+ <td><%= link_to "json", history_path(history, format: :json) %></td>
27
+ <td><%= link_to 'Destroy', history, method: :delete, data: { confirm: 'Are you sure?' } if user_can?(:delete_history) %></td>
28
+ </tr>
29
+ <% end %>
30
+ </tbody>
31
+ </table>
32
+
33
+ </div>
@@ -0,0 +1,17 @@
1
+ <% title ["#{@history.page.title} (as at #{@history.created_at})"] %>
2
+
3
+ <h1>
4
+ <%= link_to @history.page.title, wiki_path(@history.page) %>
5
+ <small><%= link_to ".json", history_path(@history, format: :json) %></small>
6
+ as at <%= time_ago @history.created_at %></h1>
7
+
8
+ <div class="wiki-content">
9
+ <%= raw markdown.render @history.body %>
10
+ </div>
11
+
12
+ <p>
13
+ <strong>Edited by</strong>
14
+ <%= link_to @history.author.name, @history.author %>
15
+ </p>
16
+
17
+ <%= link_to 'Back', histories_path %>
@@ -0,0 +1,38 @@
1
+ <%= form_for(invite) do |f| %>
2
+ <% if invite.errors.any? %>
3
+ <div id="error_explanation">
4
+ <h2><%= pluralize(invite.errors.count, "error") %> prohibited this invite from being saved:</h2>
5
+
6
+ <ul>
7
+ <% invite.errors.full_messages.each do |message| %>
8
+ <li><%= message %></li>
9
+ <% end %>
10
+ </ul>
11
+ </div>
12
+ <% end %>
13
+
14
+ <div class="field">
15
+ <%= f.label :email %>
16
+ <%= f.text_field :email %>
17
+
18
+ <small class="tip">
19
+ Invited users need to sign in using the same Google Account
20
+ registered to this e-mail address.
21
+ </small>
22
+ </div>
23
+
24
+ <div class="field">
25
+ <%= f.label :inviting_user %>
26
+ <%= user_link invite.inviting_user %>
27
+
28
+ <small class="tip">
29
+ That's you!
30
+ </small>
31
+ </div>
32
+
33
+ <%= render("shared/roles", form: f) %>
34
+
35
+ <div class="actions">
36
+ <%= f.submit %>
37
+ </div>
38
+ <% end %>
@@ -0,0 +1,39 @@
1
+ <% title ["Invites"] %>
2
+
3
+ <div class="meta-content">
4
+ <h1>Invites</h1>
5
+
6
+ <table class="list-table">
7
+ <thead>
8
+ <tr>
9
+ <th>Email</th>
10
+ <th>Invited By</th>
11
+ <th>Invited User</th>
12
+ <th>Invited At</th>
13
+ <th>Accepted At</th>
14
+ <th colspan="2"></th>
15
+ </tr>
16
+ </thead>
17
+
18
+ <tbody>
19
+ <% @invites.each do |invite| %>
20
+ <tr>
21
+ <td><%= link_to invite.email, invite %></td>
22
+ <td><%= user_link invite.inviting_user %></td>
23
+ <td><%= user_link invite.invited_user %></td>
24
+ <td><%= time_ago invite.created_at %></td>
25
+ <td><%= time_ago invite.accepted_at %></td>
26
+ <td><%= link_to 'Show', invite %></td>
27
+ <td><%= link_to 'Destroy', invite, method: :delete, data: { confirm: 'Are you sure?' } if user_can?(:delete_invite) %></td>
28
+ </tr>
29
+ <% end %>
30
+ </tbody>
31
+ </table>
32
+
33
+ <br>
34
+
35
+ <%= link_to "Invited users", invites_path if user_can?(:list_invites) %>
36
+
37
+ <%= link_to "Invite new user", new_invite_path if user_can?(:create_invite) %>
38
+
39
+ </div>
@@ -0,0 +1,7 @@
1
+ <% title ["New invite"] %>
2
+
3
+ <h1>New Invite</h1>
4
+
5
+ <%= render 'form', invite: @invite %>
6
+
7
+ <%= link_to 'Back', invites_path %>
@@ -0,0 +1,34 @@
1
+ <% title ["Invite for #{@invite.email}"] %>
2
+
3
+ <p>
4
+ <strong>Email:</strong>
5
+ <%= @invite.email %>
6
+ </p>
7
+
8
+ <p>
9
+ <strong>Invited by:</strong>
10
+ <%= user_link @invite.inviting_user %>
11
+ </p>
12
+
13
+ <p>
14
+ <strong>Invited user:</strong>
15
+ <%= user_link @invite.invited_user %>
16
+ </p>
17
+
18
+ <p>
19
+ <strong>Created at:</strong>
20
+ <%= time_ago @invite.created_at %>
21
+ </p>
22
+
23
+ <p>
24
+ <strong>Accepted at:</strong>
25
+ <%= time_ago @invite.accepted_at %>
26
+ </p>
27
+
28
+ <p>
29
+ <strong>Invite this user to sign up at:</strong>
30
+ <%= link_to main_app.login_url %>
31
+ </p>
32
+
33
+ <%= link_to 'Destroy', @invite, method: :delete, data: { confirm: 'Are you sure?' } if user_can?(:delete_invite) %>
34
+ <%= link_to 'Back', invites_path %>
@@ -0,0 +1,144 @@
1
+ <%= form_for(page) do |f| %>
2
+ <% if page.errors.any? %>
3
+ <div id="error_explanation">
4
+ <h2><%= pluralize(page.errors.count, "error") %> prohibited this page from being saved:</h2>
5
+
6
+ <ul>
7
+ <% page.errors.full_messages.each do |message| %>
8
+ <li><%= message %></li>
9
+ <% end %>
10
+ </ul>
11
+ </div>
12
+ <% end %>
13
+
14
+ <div class="field">
15
+ <%= f.label :title %>
16
+ <%= f.text_field :title %>
17
+ </div>
18
+
19
+ <div class="field">
20
+ <%= f.label :content %>
21
+ <%= f.text_area :content %>
22
+ </div>
23
+
24
+ <div class="actions">
25
+ <%= f.submit data: { disable_with: false } %>
26
+ </div>
27
+ <% end %>
28
+
29
+ <script type="text/javascript">
30
+ var select_image = {
31
+ name: "select-image",
32
+ action: function create_select_image_popup(editor) {
33
+ var refresh_links = function refresh_links() {
34
+ $(".insert-image li img").on('click', function(e) {
35
+ pos = simplemde.codemirror.getCursor();
36
+ simplemde.codemirror.setSelection(pos, pos);
37
+ var text = "![" + $(e.target).parent("li").attr("data-title") + "](" + $(e.target).parent("li").attr("data-url") + ")";
38
+ simplemde.codemirror.replaceSelection(text);
39
+ modal.close();
40
+ });
41
+ };
42
+
43
+ var request_content = function request_content() {
44
+ $.ajax({
45
+ url: "<%= j image_dialog_uploaded_files_path %>",
46
+ success: function success(data, textStatus, jqXHR) {
47
+ modal.setContent(data);
48
+ refresh_links();
49
+ }
50
+ });
51
+ };
52
+
53
+ var modal = new tingle.modal({
54
+ cssClass: ['insert-image'],
55
+ onOpen: function() {
56
+ request_content();
57
+ }
58
+ });
59
+ modal.setContent("<span class=\"loading\">loading...</span>");
60
+ modal.open();
61
+ },
62
+ className: "fa fa-picture-o",
63
+ title: "Insert Image"
64
+ };
65
+
66
+ var select_file = {
67
+ name: "select-file",
68
+ action: function create_select_file_popup(editor) {
69
+ var refresh_links = function refresh_links() {
70
+ $(".insert-file li span").on('click', function(e) {
71
+ pos = simplemde.codemirror.getCursor();
72
+ simplemde.codemirror.setSelection(pos, pos);
73
+ var text = "[" + $(e.target).parent("li").attr("data-title") + "](" + $(e.target).parent("li").attr("data-url") + ")";
74
+ simplemde.codemirror.replaceSelection(text);
75
+ modal.close();
76
+ });
77
+ };
78
+
79
+ var request_content = function request_content() {
80
+ $.ajax({
81
+ url: "<%= j file_dialog_uploaded_files_path %>",
82
+ success: function success(data, textStatus, jqXHR) {
83
+ modal.setContent(data);
84
+ refresh_links();
85
+ }
86
+ });
87
+ };
88
+
89
+ var modal = new tingle.modal({
90
+ cssClass: ['insert-file'],
91
+ onOpen: function() {
92
+ request_content();
93
+ }
94
+ });
95
+ modal.setContent("<span class=\"loading\">loading...</span>");
96
+ modal.open();
97
+ },
98
+ className: "fa fa-file-o",
99
+ title: "Insert File"
100
+ };
101
+
102
+ var targetElement = document.getElementById("page_content");
103
+
104
+ var simplemde = new SimpleMDE({
105
+ element: targetElement,
106
+ toolbar: ["bold", "italic", "strikethrough", "heading", "|",
107
+ "code", "quote", "table", "horizontal-rule", "|",
108
+ "link", select_image, select_file, "unordered-list", "ordered-list", "|",
109
+ "preview", "side-by-side", "fullscreen"],
110
+
111
+ // a JS alert window appears asking for the link or image URL
112
+ promptURLs: true,
113
+
114
+ // force the textarea to stay up to date
115
+ forceSync: true,
116
+ });
117
+
118
+ simplemde.originalContent = $(targetElement).val();
119
+
120
+ var thereAreUnsavedChanges = function() {
121
+ return !window.confirmedNavigatingAway && $(targetElement).val() != simplemde.originalContent;
122
+ }
123
+
124
+ $(window).bind("beforeunload", function(event) {
125
+ if (thereAreUnsavedChanges()) {
126
+ event.returnValue = "You have unsaved changes that may be lost.";
127
+ return event.returnValue;
128
+ }
129
+ });
130
+
131
+ $("input[type=submit]").on("click", function() {
132
+ // if we're clicking Submit, we always want to navigate away
133
+ window.confirmedNavigatingAway = true;
134
+ });
135
+
136
+ // Turbolinks 4 & 5
137
+ $(document).on("page:before-change turbolinks:before-visit", function() {
138
+ if (thereAreUnsavedChanges()) {
139
+ window.confirmedNavigatingAway = confirm("You have unsaved changes that may be lost.");
140
+ return window.confirmedNavigatingAway;
141
+ }
142
+ });
143
+ </script>
144
+
@@ -0,0 +1,8 @@
1
+ <% title [@page.title, "Edit"] %>
2
+
3
+ <h1>Editing <%= @page.title %></h1>
4
+
5
+ <%= render 'form', page: @page %>
6
+
7
+ <%= link_to 'Show', @page %> |
8
+ <%= link_to 'Back', pages_path %>
@@ -0,0 +1,11 @@
1
+ <% title [@page.title, "History"] %>
2
+
3
+ <h1>
4
+ History of
5
+ <%= link_to @page.title, wiki_path(@page) %>
6
+ <small><%= link_to ".json", page_history_path(@page, format: :json) %></small>
7
+ </h1>
8
+
9
+ <%= render "shared/histories", histories: @page.histories %>
10
+
11
+ <%= link_to 'Back', users_path %>