railswiki 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 %>