thredded 0.1.0 → 0.2.2

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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.mkdn +202 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.mkdn +391 -0
  5. data/app/assets/images/thredded/breadcrumb-chevron.svg +1 -0
  6. data/app/assets/javascripts/thredded/currently_online.es6 +25 -0
  7. data/app/assets/javascripts/thredded/post_form.es6 +20 -0
  8. data/app/assets/javascripts/thredded/time_stamps.es6 +8 -0
  9. data/app/assets/javascripts/thredded/topic_form.es6 +55 -0
  10. data/app/assets/javascripts/thredded/users_select.es6 +5 -0
  11. data/app/assets/javascripts/thredded.es6 +10 -0
  12. data/app/assets/stylesheets/thredded/_base.scss +11 -0
  13. data/app/assets/stylesheets/thredded/_dependencies.scss +1 -0
  14. data/app/assets/stylesheets/thredded/_thredded.scss +27 -0
  15. data/app/assets/stylesheets/thredded/base/_alerts.scss +40 -0
  16. data/app/assets/stylesheets/thredded/base/_buttons.scss +39 -0
  17. data/app/assets/stylesheets/thredded/base/_forms.scss +79 -0
  18. data/app/assets/stylesheets/thredded/base/_grid.scss +19 -0
  19. data/app/assets/stylesheets/thredded/base/_lists.scss +31 -0
  20. data/app/assets/stylesheets/thredded/base/_tables.scss +25 -0
  21. data/app/assets/stylesheets/thredded/base/_typography.scss +36 -0
  22. data/app/assets/stylesheets/thredded/base/_variables.scss +54 -0
  23. data/app/assets/stylesheets/thredded/components/_base.scss +13 -0
  24. data/app/assets/stylesheets/thredded/components/_currently-online.scss +48 -0
  25. data/app/assets/stylesheets/thredded/components/_empty.scss +11 -0
  26. data/app/assets/stylesheets/thredded/components/_flash-message.scss +19 -0
  27. data/app/assets/stylesheets/thredded/components/_form-list.scss +35 -0
  28. data/app/assets/stylesheets/thredded/components/_main-section.scss +3 -0
  29. data/app/assets/stylesheets/thredded/components/_messageboard.scss +55 -0
  30. data/app/assets/stylesheets/thredded/components/_pagination.scss +26 -0
  31. data/app/assets/stylesheets/thredded/components/_post-form.scss +21 -0
  32. data/app/assets/stylesheets/thredded/components/_post.scss +66 -0
  33. data/app/assets/stylesheets/thredded/components/_preferences.scss +6 -0
  34. data/app/assets/stylesheets/thredded/components/_select2.scss +42 -0
  35. data/app/assets/stylesheets/thredded/components/_topic-delete.scss +9 -0
  36. data/app/assets/stylesheets/thredded/components/_topic-header.scss +20 -0
  37. data/app/assets/stylesheets/thredded/components/_topics.scss +113 -0
  38. data/app/assets/stylesheets/thredded/layout/_main-container.scss +15 -0
  39. data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +45 -0
  40. data/app/assets/stylesheets/thredded/layout/_topic-navigation.scss +53 -0
  41. data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +87 -0
  42. data/app/assets/stylesheets/thredded/utilities/_is-compact.scss +26 -0
  43. data/app/assets/stylesheets/thredded/utilities/_is-expanded.scss +16 -0
  44. data/app/assets/stylesheets/thredded.scss +3 -0
  45. data/app/commands/thredded/at_notification_extractor.rb +15 -0
  46. data/app/commands/thredded/members_marked_notified.rb +18 -0
  47. data/app/commands/thredded/messageboard_destroyer.rb +60 -0
  48. data/app/commands/thredded/notify_mentioned_users.rb +68 -0
  49. data/app/commands/thredded/notify_private_topic_users.rb +51 -0
  50. data/app/commands/thredded/user_reads_private_topic.rb +22 -0
  51. data/app/commands/thredded/user_resets_private_topic_to_unread.rb +23 -0
  52. data/app/controllers/thredded/application_controller.rb +105 -0
  53. data/app/controllers/thredded/messageboards_controller.rb +58 -0
  54. data/app/controllers/thredded/posts_controller.rb +78 -0
  55. data/app/controllers/thredded/preferences_controller.rb +28 -0
  56. data/app/controllers/thredded/private_topics_controller.rb +65 -0
  57. data/app/controllers/thredded/setups_controller.rb +53 -0
  58. data/app/controllers/thredded/theme_previews_controller.rb +59 -0
  59. data/app/controllers/thredded/topics_controller.rb +153 -0
  60. data/app/decorators/thredded/base_topic_decorator.rb +14 -0
  61. data/app/decorators/thredded/base_user_topic_decorator.rb +63 -0
  62. data/app/decorators/thredded/messageboard_decorator.rb +41 -0
  63. data/app/decorators/thredded/post_decorator.rb +40 -0
  64. data/app/decorators/thredded/private_topic_decorator.rb +23 -0
  65. data/app/decorators/thredded/topic_decorator.rb +25 -0
  66. data/app/decorators/thredded/topic_email_decorator.rb +24 -0
  67. data/app/decorators/thredded/user_private_topic_decorator.rb +13 -0
  68. data/app/decorators/thredded/user_topic_decorator.rb +37 -0
  69. data/app/forms/thredded/private_topic_form.rb +126 -0
  70. data/app/forms/thredded/topic_form.rb +94 -0
  71. data/app/helpers/thredded/application_helper.rb +41 -0
  72. data/app/jobs/thredded/activity_updater_job.rb +20 -0
  73. data/app/jobs/thredded/at_notifier_job.rb +11 -0
  74. data/app/jobs/thredded/notify_private_topic_users_job.rb +11 -0
  75. data/app/mailers/thredded/base_mailer.rb +4 -0
  76. data/app/mailers/thredded/post_mailer.rb +15 -0
  77. data/app/mailers/thredded/private_post_mailer.rb +15 -0
  78. data/app/mailers/thredded/private_topic_mailer.rb +15 -0
  79. data/app/models/concerns/thredded/post_common.rb +105 -0
  80. data/app/models/concerns/thredded/topic_common.rb +48 -0
  81. data/app/models/thredded/ability.rb +60 -0
  82. data/app/models/thredded/category.rb +12 -0
  83. data/app/models/thredded/messageboard.rb +50 -0
  84. data/app/models/thredded/messageboard_user.rb +12 -0
  85. data/app/models/thredded/notification_preference.rb +17 -0
  86. data/app/models/thredded/null_preference.rb +11 -0
  87. data/app/models/thredded/null_topic.rb +15 -0
  88. data/app/models/thredded/null_topic_read.rb +19 -0
  89. data/app/models/thredded/null_user.rb +51 -0
  90. data/app/models/thredded/post.rb +37 -0
  91. data/app/models/thredded/post_notification.rb +17 -0
  92. data/app/models/thredded/private_post.rb +25 -0
  93. data/app/models/thredded/private_topic.rb +60 -0
  94. data/app/models/thredded/private_user.rb +6 -0
  95. data/app/models/thredded/stats.rb +37 -0
  96. data/app/models/thredded/topic.rb +104 -0
  97. data/app/models/thredded/topic_category.rb +6 -0
  98. data/app/models/thredded/user_detail.rb +26 -0
  99. data/app/models/thredded/user_extender.rb +33 -0
  100. data/app/models/thredded/user_permissions/admin/if_admin_column_true.rb +12 -0
  101. data/app/models/thredded/user_permissions/admin/none.rb +12 -0
  102. data/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb +13 -0
  103. data/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb +25 -0
  104. data/app/models/thredded/user_permissions/moderate/none.rb +25 -0
  105. data/app/models/thredded/user_permissions/read/all.rb +25 -0
  106. data/app/models/thredded/user_permissions/write/all.rb +25 -0
  107. data/app/models/thredded/user_permissions/write/none.rb +25 -0
  108. data/app/models/thredded/user_preference.rb +6 -0
  109. data/app/models/thredded/user_topic_read.rb +10 -0
  110. data/app/views/layouts/thredded/application.html.erb +15 -0
  111. data/app/views/thredded/categories/_category.html.erb +1 -0
  112. data/app/views/thredded/kaminari/_first_page.html.erb +11 -0
  113. data/app/views/thredded/kaminari/_gap.html.erb +8 -0
  114. data/app/views/thredded/kaminari/_last_page.html.erb +11 -0
  115. data/app/views/thredded/kaminari/_next_page.html.erb +11 -0
  116. data/app/views/thredded/kaminari/_page.html.erb +12 -0
  117. data/app/views/thredded/kaminari/_paginator.html.erb +23 -0
  118. data/app/views/thredded/kaminari/_prev_page.html.erb +11 -0
  119. data/app/views/thredded/messageboards/_messageboard.html.erb +15 -0
  120. data/app/views/thredded/messageboards/index.html.erb +20 -0
  121. data/app/views/thredded/messageboards/new.html.erb +18 -0
  122. data/app/views/thredded/post_mailer/at_notification.html.erb +14 -0
  123. data/app/views/thredded/post_mailer/at_notification.text.erb +10 -0
  124. data/app/views/thredded/posts/_content_field.html.erb +4 -0
  125. data/app/views/thredded/posts/_form.html.erb +1 -0
  126. data/app/views/thredded/posts/_post.html.erb +1 -0
  127. data/app/views/thredded/posts/_user.html.erb +3 -0
  128. data/app/views/thredded/posts/edit.html.erb +13 -0
  129. data/app/views/thredded/posts_common/_form.html.erb +10 -0
  130. data/app/views/thredded/posts_common/_post.html.erb +20 -0
  131. data/app/views/thredded/preferences/_form.html.erb +28 -0
  132. data/app/views/thredded/preferences/_header.html.erb +1 -0
  133. data/app/views/thredded/preferences/edit.html.erb +12 -0
  134. data/app/views/thredded/private_post_mailer/at_notification.html.erb +11 -0
  135. data/app/views/thredded/private_posts/_form.html.erb +1 -0
  136. data/app/views/thredded/private_posts/_private_post.html.erb +1 -0
  137. data/app/views/thredded/private_topic_mailer/message_notification.html.erb +17 -0
  138. data/app/views/thredded/private_topic_mailer/message_notification.text.erb +13 -0
  139. data/app/views/thredded/private_topics/_breadcrumbs.html.erb +4 -0
  140. data/app/views/thredded/private_topics/_form.html.erb +24 -0
  141. data/app/views/thredded/private_topics/_no_private_topics.html.erb +6 -0
  142. data/app/views/thredded/private_topics/_private_topic.html.erb +22 -0
  143. data/app/views/thredded/private_topics/index.html.erb +23 -0
  144. data/app/views/thredded/private_topics/new.html.erb +13 -0
  145. data/app/views/thredded/private_topics/show.html.erb +14 -0
  146. data/app/views/thredded/search/_form.html.erb +7 -0
  147. data/app/views/thredded/shared/_currently_online.html.erb +16 -0
  148. data/app/views/thredded/shared/_flash_messages.html.erb +7 -0
  149. data/app/views/thredded/shared/_header.html.erb +4 -0
  150. data/app/views/thredded/shared/_messageboard_topics_breadcrumbs.html.erb +6 -0
  151. data/app/views/thredded/shared/_notification_preferences.html.erb +7 -0
  152. data/app/views/thredded/shared/_page.html.erb +6 -0
  153. data/app/views/thredded/shared/_time_ago.html.erb +7 -0
  154. data/app/views/thredded/shared/_top_nav.html.erb +36 -0
  155. data/app/views/thredded/shared/_topic_nav.html.erb +22 -0
  156. data/app/views/thredded/theme_previews/_section_title.html.erb +3 -0
  157. data/app/views/thredded/theme_previews/show.html.erb +108 -0
  158. data/app/views/thredded/topics/_form.html.erb +23 -0
  159. data/app/views/thredded/topics/_recent_topics_by_user.html.erb +8 -0
  160. data/app/views/thredded/topics/_topic.html.erb +29 -0
  161. data/app/views/thredded/topics/_topic_form_admin_options.html.erb +12 -0
  162. data/app/views/thredded/topics/by_category.html.erb +56 -0
  163. data/app/views/thredded/topics/edit.html.erb +38 -0
  164. data/app/views/thredded/topics/index.html.erb +18 -0
  165. data/app/views/thredded/topics/menu/_new_topic.html.erb +3 -0
  166. data/app/views/thredded/topics/new.html.erb +11 -0
  167. data/app/views/thredded/topics/search.html.erb +9 -0
  168. data/app/views/thredded/topics/show.html.erb +31 -0
  169. data/app/views/thredded/topics_common/_header.html.erb +6 -0
  170. data/app/views/thredded/users/_link.html.erb +9 -0
  171. data/config/routes.rb +28 -0
  172. data/db/migrate/20160329231848_create_thredded.rb +173 -0
  173. data/lib/generators/thredded/install/USAGE +13 -0
  174. data/lib/generators/thredded/install/install_generator.rb +23 -0
  175. data/lib/generators/thredded/install/templates/initializer.rb +51 -0
  176. data/lib/html/pipeline/at_mention_filter.rb +20 -0
  177. data/lib/html/pipeline/bbcode_filter.rb +19 -0
  178. data/lib/tasks/thredded_tasks.rake +18 -0
  179. data/lib/thredded/at_users.rb +19 -0
  180. data/lib/thredded/engine.rb +35 -0
  181. data/lib/thredded/errors.rb +67 -0
  182. data/lib/thredded/main_app_route_delegator.rb +24 -0
  183. data/lib/thredded/messageboard_user_permissions.rb +22 -0
  184. data/lib/thredded/post_sql_builder.rb +12 -0
  185. data/lib/thredded/post_user_permissions.rb +32 -0
  186. data/lib/thredded/private_topic_user_permissions.rb +26 -0
  187. data/lib/thredded/search_parser.rb +45 -0
  188. data/lib/thredded/search_sql_builder.rb +21 -0
  189. data/lib/thredded/seed_database.rb +76 -0
  190. data/lib/thredded/table_sql_builder.rb +41 -0
  191. data/lib/thredded/topic_sql_builder.rb +11 -0
  192. data/lib/thredded/topic_user_permissions.rb +32 -0
  193. data/lib/thredded/version.rb +3 -0
  194. data/lib/thredded.rb +85 -0
  195. data/thredded.gemspec +68 -0
  196. metadata +248 -122
@@ -0,0 +1,18 @@
1
+ <% content_for :thredded_page_title, 'Topics' %>
2
+ <% content_for :thredded_page_id, 'thredded--topics-index' %>
3
+ <% content_for :thredded_breadcrumbs, render('thredded/shared/messageboard_topics_breadcrumbs') %>
4
+
5
+ <%= thredded_page do %>
6
+ <%= content_tag :section, class: 'thredded--main-section thredded--topics' do %>
7
+ <%= render 'thredded/topics/form',
8
+ messageboard: messageboard,
9
+ topic: @new_topic,
10
+ css_class: 'thredded--is-compact',
11
+ placeholder: 'Start a New Topic' if @new_topic %>
12
+ <%= render @decorated_topics %>
13
+ <% end %>
14
+
15
+ <footer>
16
+ <%= paginate @topics %>
17
+ </footer>
18
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <li>
2
+ <%= link_to 'New Topic', new_messageboard_topic_path %>
3
+ </li>
@@ -0,0 +1,11 @@
1
+ <% content_for :thredded_page_title, 'Create a New Topic' %>
2
+ <% content_for :thredded_page_id, 'thredded--new-topic' %>
3
+ <% content_for :thredded_breadcrumbs, render('thredded/shared/messageboard_topics_breadcrumbs') %>
4
+
5
+ <%= thredded_page do %>
6
+ <%= render 'thredded/topics/form',
7
+ messageboard: messageboard,
8
+ topic: @new_topic,
9
+ css_class: 'thredded--is-expanded',
10
+ placeholder: 'Start a New Topic' %>
11
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% content_for :thredded_page_title, 'Topics Search Results' %>
2
+ <% content_for :thredded_page_id, 'thredded--topic-search-results' %>
3
+ <% content_for :thredded_breadcrumbs, render('thredded/shared/messageboard_topics_breadcrumbs') %>
4
+
5
+ <%= thredded_page do %>
6
+ <section class="thredded--main-section thredded--topics">
7
+ <%= render @decorated_topics %>
8
+ </section>
9
+ <% end %>
@@ -0,0 +1,31 @@
1
+ <% content_for :thredded_page_title, topic.title %>
2
+ <% content_for :thredded_page_id, 'thredded--posts' %>
3
+ <% content_for :thredded_breadcrumbs, render('thredded/shared/messageboard_topics_breadcrumbs') %>
4
+
5
+ <%= thredded_page do %>
6
+ <%= content_tag_for :section, topic, class: "thredded--main-section thredded--topic #{user_topic.css_class}" do %>
7
+ <%= render 'thredded/topics_common/header', topic: topic %>
8
+ <%= render @posts %>
9
+ <% if can?(:create, @new_post) %>
10
+ <div class="thredded--post-form--wrapper">
11
+ <h3 class="thredded--post-form--title">Add a post</h3>
12
+ <%= render 'thredded/posts/form',
13
+ topic: topic,
14
+ post: @new_post,
15
+ button_text: 'Submit Reply' %>
16
+ </div>
17
+ <% end %>
18
+
19
+ <% if current_ability.can? :destroy, topic %>
20
+ <div class="thredded--topic-delete--wrapper">
21
+ <%= form_tag messageboard_topic_path(messageboard, topic), method: :delete, class: 'thredded--topic-delete-form' do %>
22
+ <%= submit_tag 'Delete Topic', class: 'thredded--form--submit' %>
23
+ <% end %>
24
+ </div>
25
+ <% end %>
26
+ <% end %>
27
+
28
+ <footer>
29
+ <%= paginate @posts %>
30
+ </footer>
31
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <header class="thredded--topic-header">
2
+ <h1 class="thredded--topic-header--title"><%= topic.title %></h1>
3
+ <cite class="thredded--topic-header--started-by">
4
+ Started <%= time_ago topic.created_at %> by <%= user_link topic.user %>
5
+ </cite>
6
+ </header>
@@ -0,0 +1,9 @@
1
+ <% if user %>
2
+ <% if !user.thredded_anonymous? %>
3
+ <a href="<%= user_path(user) %>"><%= user.to_s %></a>
4
+ <% else %>
5
+ <%= user.to_s %>
6
+ <% end %>
7
+ <% else %>
8
+ <em>Deleted user</em>
9
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,28 @@
1
+ Thredded::Engine.routes.draw do
2
+ constraints(->(req) { req.env['QUERY_STRING'].include? 'q=' }) do
3
+ get '/:messageboard_id(.:format)' => 'topics#search', as: :messageboard_search
4
+ end
5
+
6
+ resource :theme_preview, only: [:show] if %w(development test).include? Rails.env
7
+
8
+ resources :private_topics, path: 'private-topics' do
9
+ resources :private_posts, path: '', except: [:index], controller: 'posts'
10
+ end
11
+
12
+ get '/messageboards/new' => 'messageboards#new', as: :new_messageboard
13
+ get '/:messageboard_id/preferences/edit' => 'preferences#edit'
14
+ get '/:messageboard_id/new(.:format)' => 'topics#new', as: :new_messageboard_topic
15
+ get '/:messageboard_id/:id/edit(.:format)' => 'topics#edit', as: :edit_messageboard_topic
16
+ get '/:messageboard_id/:id/page-:page(.:format)' => 'topics#show', as: :paged_messageboard_topic_posts, constraints: { page: /\d+/ }
17
+ get '/:messageboard_id/category/:category_id' => 'topics#category', as: :messageboard_topics_categories
18
+
19
+ resources :messageboards, only: [:index, :create], path: '' do
20
+ resource :preferences, only: [:edit, :update]
21
+
22
+ resources :topics, path: '' do
23
+ resources :posts, path: '', except: [:index]
24
+ end
25
+ end
26
+
27
+ root to: 'messageboards#index'
28
+ end
@@ -0,0 +1,173 @@
1
+ class CreateThredded < ActiveRecord::Migration
2
+ def change
3
+
4
+ unless table_exists?(:friendly_id_slugs)
5
+ # The user might have installed FriendlyId separately already.
6
+ create_table :friendly_id_slugs do |t|
7
+ t.string :slug, limit: 255, null: false
8
+ t.integer :sluggable_id, null: false
9
+ t.string :sluggable_type, limit: 50
10
+ t.string :scope, limit: 191
11
+ t.datetime :created_at, null: false
12
+ end
13
+ add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], name: :index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope, unique: true
14
+ add_index :friendly_id_slugs, [:slug, :sluggable_type], name: :index_friendly_id_slugs_on_slug_and_sluggable_type
15
+ add_index :friendly_id_slugs, [:sluggable_id], name: :index_friendly_id_slugs_on_sluggable_id
16
+ add_index :friendly_id_slugs, [:sluggable_type], name: :index_friendly_id_slugs_on_sluggable_type
17
+ end
18
+
19
+ create_table :thredded_categories do |t|
20
+ t.integer :messageboard_id, null: false
21
+ t.string :name, limit: 255, null: false
22
+ t.string :description, limit: 255
23
+ t.timestamps null: false
24
+ t.string :slug, limit: 191, null: false
25
+ end
26
+ add_index :thredded_categories, [:messageboard_id, :slug], name: :index_thredded_categories_on_messageboard_id_and_slug, unique: true
27
+ add_index :thredded_categories, [:messageboard_id], name: :index_thredded_categories_on_messageboard_id
28
+ DbTextSearch::CaseInsensitiveEq.add_index connection, :thredded_categories, :name, name: :thredded_categories_name_ci
29
+
30
+ create_table :thredded_messageboards do |t|
31
+ t.string :name, limit: 255, null: false
32
+ t.string :slug, limit: 191
33
+ t.text :description
34
+ t.integer :topics_count, default: 0
35
+ t.integer :posts_count, default: 0
36
+ t.boolean :closed, default: false, null: false
37
+ t.timestamps null: false
38
+ t.string :filter, limit: 255, default: 'markdown', null: false
39
+ end
40
+ add_index :thredded_messageboards, [:closed], name: :index_thredded_messageboards_on_closed
41
+ add_index :thredded_messageboards, [:slug], name: :index_thredded_messageboards_on_slug
42
+
43
+ create_table :thredded_notification_preferences do |t|
44
+ t.boolean :notify_on_mention, default: true
45
+ t.boolean :notify_on_message, default: true
46
+ t.integer :user_id, null: false
47
+ t.integer :messageboard_id, null: false
48
+ t.timestamps null: false
49
+ end
50
+ add_index :thredded_notification_preferences, [:messageboard_id], name: :index_thredded_notification_preferences_on_messageboard_id
51
+ add_index :thredded_notification_preferences, [:user_id], name: :index_thredded_notification_preferences_on_user_id
52
+
53
+ create_table :thredded_post_notifications do |t|
54
+ t.string :email, limit: 255, null: false
55
+ t.integer :post_id, null: false
56
+ t.timestamps null: false
57
+ t.string :post_type, limit: 255
58
+ end
59
+ add_index :thredded_post_notifications, [:post_id, :post_type], name: :index_thredded_post_notifications_on_post
60
+
61
+ create_table :thredded_posts do |t|
62
+ t.integer :user_id, limit: 4
63
+ t.text :content, limit: 65535
64
+ t.string :ip, limit: 255
65
+ t.string :filter, limit: 255, default: 'markdown'
66
+ t.string :source, limit: 255, default: 'web'
67
+ t.integer :postable_id, limit: 4
68
+ t.integer :messageboard_id, null: false
69
+ t.timestamps null: false
70
+ end
71
+ add_index :thredded_posts, [:messageboard_id], name: :index_thredded_posts_on_messageboard_id
72
+ add_index :thredded_posts, [:postable_id], name: :index_thredded_posts_on_postable_id
73
+ add_index :thredded_posts, [:postable_id], name: :index_thredded_posts_on_postable_id_and_postable_type
74
+ add_index :thredded_posts, [:user_id], name: :index_thredded_posts_on_user_id
75
+ DbTextSearch::FullTextSearch.add_index connection, :thredded_posts, :content, name: :thredded_posts_content_fts
76
+
77
+ create_table :thredded_private_posts do |t|
78
+ t.integer :user_id, limit: 4
79
+ t.text :content, limit: 65535
80
+ t.string :ip, limit: 255
81
+ t.string :filter, limit: 255, default: 'markdown'
82
+ t.integer :postable_id, null: false
83
+ t.timestamps null: false
84
+ end
85
+
86
+ create_table :thredded_private_topics do |t|
87
+ t.integer :user_id, null: false
88
+ t.integer :last_user_id, null: false
89
+ t.string :title, limit: 255, null: false
90
+ t.string :slug, limit: 191, null: false
91
+ t.integer :posts_count, default: 0
92
+ t.string :hash_id, limit: 191, null: false
93
+ t.timestamps null: false
94
+ end
95
+ add_index :thredded_private_topics, [:hash_id], name: :index_thredded_private_topics_on_hash_id
96
+ add_index :thredded_private_topics, [:slug], name: :index_thredded_private_topics_on_slug
97
+
98
+ create_table :thredded_private_users do |t|
99
+ t.integer :private_topic_id, limit: 4
100
+ t.integer :user_id, limit: 4
101
+ t.timestamps null: false
102
+ t.boolean :read, default: false
103
+ end
104
+ add_index :thredded_private_users, [:private_topic_id], name: :index_thredded_private_users_on_private_topic_id
105
+ add_index :thredded_private_users, [:read], name: :index_thredded_private_users_on_read
106
+ add_index :thredded_private_users, [:user_id], name: :index_thredded_private_users_on_user_id
107
+
108
+ create_table :thredded_topic_categories do |t|
109
+ t.integer :topic_id, null: false
110
+ t.integer :category_id, null: false
111
+ end
112
+ add_index :thredded_topic_categories, [:category_id], name: :index_thredded_topic_categories_on_category_id
113
+ add_index :thredded_topic_categories, [:topic_id], name: :index_thredded_topic_categories_on_topic_id
114
+
115
+ create_table :thredded_topics do |t|
116
+ t.integer :user_id, null: false
117
+ t.integer :last_user_id, null: false
118
+ t.string :title, limit: 255, null: false
119
+ t.string :slug, limit: 191, null: false
120
+ t.integer :messageboard_id, null: false
121
+ t.integer :posts_count, default: 0, null: false
122
+ t.boolean :sticky, default: false, null: false
123
+ t.boolean :locked, default: false, null: false
124
+ t.string :hash_id, limit: 191, null: false
125
+ t.string :type, limit: 191
126
+ t.timestamps null: false
127
+ end
128
+ add_index :thredded_topics, [:hash_id], name: :index_thredded_topics_on_hash_id
129
+ add_index :thredded_topics, [:messageboard_id, :slug], name: :index_thredded_topics_on_messageboard_id_and_slug, unique: true
130
+ add_index :thredded_topics, [:messageboard_id], name: :index_thredded_topics_on_messageboard_id
131
+ add_index :thredded_topics, [:user_id], name: :index_thredded_topics_on_user_id
132
+ DbTextSearch::FullTextSearch.add_index connection, :thredded_topics, :title, name: :thredded_topics_title_fts
133
+
134
+ create_table :thredded_user_details do |t|
135
+ t.integer :user_id, null: false
136
+ t.datetime :latest_activity_at
137
+ t.integer :posts_count, default: 0
138
+ t.integer :topics_count, default: 0
139
+ t.timestamps null: false
140
+ t.datetime :last_seen_at
141
+ end
142
+ add_index :thredded_user_details, [:latest_activity_at], name: :index_thredded_user_details_on_latest_activity_at
143
+ add_index :thredded_user_details, [:user_id], name: :index_thredded_user_details_on_user_id
144
+
145
+ create_table :thredded_messageboard_users do |t|
146
+ t.references :thredded_user_detail, foreign_key: true, null: false
147
+ t.references :thredded_messageboard, foreign_key: true, null: false
148
+ t.datetime :last_seen_at, null: false
149
+ end
150
+ add_index :thredded_messageboard_users, [:thredded_messageboard_id, :thredded_user_detail_id],
151
+ name: :index_thredded_messageboard_users_primary
152
+ add_index :thredded_messageboard_users, [:thredded_messageboard_id, :last_seen_at],
153
+ name: :index_thredded_messageboard_users_for_recently_active
154
+
155
+ create_table :thredded_user_preferences do |t|
156
+ t.integer :user_id, null: false
157
+ t.string :time_zone, limit: 191, default: 'Eastern Time (US & Canada)'
158
+ t.timestamps null: false
159
+ end
160
+ add_index :thredded_user_preferences, [:user_id], name: :index_thredded_user_preferences_on_user_id
161
+
162
+ create_table :thredded_user_topic_reads do |t|
163
+ t.integer :user_id, null: false
164
+ t.integer :topic_id, null: false
165
+ t.integer :post_id, null: false
166
+ t.integer :posts_count, default: 0, null: false
167
+ t.integer :page, default: 1, null: false
168
+ t.timestamps null: false
169
+ end
170
+ add_index :thredded_user_topic_reads, [:topic_id], name: :index_thredded_user_topic_reads_on_topic_id
171
+ add_index :thredded_user_topic_reads, [:user_id, :topic_id], name: :index_thredded_user_topic_reads_on_user_id_and_topic_id, unique: true
172
+ end
173
+ end
@@ -0,0 +1,13 @@
1
+ Description:
2
+ Create an initializer with description of configuration and defaults
3
+
4
+ Example:
5
+ rails generate thredded:install
6
+
7
+ This will create:
8
+ config/initializers/thredded.rb
9
+
10
+ Options:
11
+
12
+ [--theme] # Will copy thredded layout, views, and assets to the parent app
13
+
@@ -0,0 +1,23 @@
1
+ module Thredded
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ class_option :theme,
5
+ type: :boolean,
6
+ default: false,
7
+ desc: 'Copy all thredded layout, views, and assets to parent application.'
8
+
9
+ def set_source_paths
10
+ @source_paths = [
11
+ File.expand_path('../templates', __FILE__),
12
+ File.expand_path('../../../../..', __FILE__),
13
+ ]
14
+ end
15
+
16
+ def copy_initializer_file
17
+ copy_file \
18
+ 'initializer.rb',
19
+ 'config/initializers/thredded.rb'
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ # Thredded configuration
2
+
3
+ # ==> User Configuration
4
+ # The name of the class your app uses for your users.
5
+ # By default the engine will use 'User' but if you have another name
6
+ # for your user class - change it here.
7
+ Thredded.user_class = 'User'
8
+
9
+ # User name column, used in @mention syntax and should be unique.
10
+ # This is the column used to search for users' names if/when someone is @ mentioned.
11
+ Thredded.user_name_column = :name
12
+
13
+ # The path (or URL) you will use to link to your users' profiles.
14
+ # When linking to a user, Thredded will use this lambda to spit out
15
+ # the path or url to your user. This lambda is evaluated in the view context.
16
+ Thredded.user_path = lambda do |user|
17
+ user_path = :"#{Thredded.user_class.name.underscore}_path"
18
+ main_app.respond_to?(user_path) ? main_app.send(user_path, user) : "/users/#{user.to_param}"
19
+ end
20
+
21
+ # User avatar URL. rb-gravatar gem is used by default:
22
+ Thredded.avatar_url = ->(user) { Gravatar.src(user.email, 128, 'mm') }
23
+
24
+ # ==> Permissions Configuration
25
+ # By default, thredded uses a simple permission model, where all the users can post to all message boards,
26
+ # and admins and moderators are determined by a flag on the users table.
27
+
28
+ # The name of the moderator flag column on the users table.
29
+ Thredded.moderator_column = :admin
30
+ # The name of the admin flag column on the users table.
31
+ Thredded.admin_column = :admin
32
+
33
+ # This model can be customized further by overriding a handful of methods on the User model.
34
+ # For more information, see app/models/thredded/user_extender.rb.
35
+
36
+ # ==> Email Configuration
37
+ # Email "From:" field will use the following
38
+ # Thredded.email_from = 'no-reply@example.com'
39
+
40
+ # Incoming email will be directed to this host
41
+ # Thredded.email_incoming_host = 'example.com'
42
+
43
+ # Emails going out will prefix the "Subject:" with the following string
44
+ # Thredded.email_outgoing_prefix = '[My Forum] '
45
+
46
+ # Reply to field for email notifications
47
+ # Thredded.email_reply_to = -> postable { "#{postable.hash_id}@#{Thredded.email_incoming_host}" }
48
+
49
+ # ==> View Configuration
50
+ # Set the layout for rendering the thredded views.
51
+ Thredded.layout = 'thredded/application'
@@ -0,0 +1,20 @@
1
+ require 'thredded/at_users'
2
+
3
+ module HTML
4
+ class Pipeline
5
+ class AtMentionFilter < Filter
6
+ def initialize(text, context = nil, result = nil)
7
+ super text, context, result
8
+ @text = text.to_s.gsub "\r", ''
9
+ @post = context[:post]
10
+ @view_context = context[:view_context]
11
+ end
12
+
13
+ def call
14
+ html = Thredded::AtUsers.render(@text, @post, @view_context)
15
+ html.rstrip!
16
+ html
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ require 'bbcoder'
2
+
3
+ module HTML
4
+ class Pipeline
5
+ class BbcodeFilter < TextFilter
6
+ def initialize(text, context = {}, result = nil)
7
+ super text, context, result
8
+ @context = context
9
+ @text = @text.gsub "\r", ''
10
+ end
11
+
12
+ def call
13
+ html = BBCoder.new(@text).to_html.gsub(/\n|\r\n/, '<br />')
14
+ html.rstrip!
15
+ "<p>#{html}</p>"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ namespace :thredded do
2
+ desc 'Destroy messageboard and all related data'
3
+ task :destroy, [:slug] => :environment do |_, args|
4
+ Thredded::MessageboardDestroyer.new(args.slug).run
5
+ end
6
+
7
+ task :nuke, [:slug] => :destroy
8
+
9
+ namespace :install do
10
+ desc 'Copy emoji to the Rails `public/emoji` directory'
11
+ task :emoji do
12
+ require 'emoji'
13
+
14
+ target = "#{Rake.original_dir}/public"
15
+ `mkdir -p '#{target}' && cp -Rp '#{Emoji.images_path}/emoji' '#{target}'`
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module Thredded
2
+ class AtUsers
3
+ def self.render(content, post, view_context)
4
+ at_names = AtNotificationExtractor.new(content).run
5
+
6
+ if at_names.any?
7
+ members = post.readers_from_user_names(at_names)
8
+
9
+ members.each do |member|
10
+ member_path = Thredded.user_path(view_context, member)
11
+ content.gsub!(/(@#{member.to_s})\b/i,
12
+ %(<a href="#{ERB::Util.html_escape member_path}">\\1</a>))
13
+ end
14
+ end
15
+
16
+ content
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ module Thredded
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Thredded
4
+
5
+ config.autoload_paths << File.expand_path('../../../app/decorators', __FILE__)
6
+ config.autoload_paths << File.expand_path('../../../app/forms', __FILE__)
7
+ config.autoload_paths << File.expand_path('../../../app/commands', __FILE__)
8
+ config.autoload_paths << File.expand_path('../../../app/jobs', __FILE__)
9
+
10
+ config.generators do |g|
11
+ g.test_framework :rspec, fixture: true
12
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
13
+ g.helper false
14
+ end
15
+
16
+ config.to_prepare do
17
+ if Thredded.user_class
18
+ Thredded.user_class.send(:include, Thredded::UserExtender)
19
+ end
20
+
21
+ unless Thredded.standalone_layout?
22
+ # Delegate all main_app routes to allow calling them directly.
23
+ ::Thredded::ApplicationController.helper ::Thredded::MainAppRouteDelegator
24
+ end
25
+ end
26
+
27
+ initializer 'thredded.setup_assets' do
28
+ Thredded::Engine.config.assets.precompile += %w(
29
+ thredded.js
30
+ thredded.css
31
+ thredded/breadcrumb-chevron.svg
32
+ )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,67 @@
1
+ module Thredded
2
+ class Error < StandardError
3
+ end
4
+
5
+ module Errors
6
+ class DatabaseEmpty < Thredded::Error
7
+ def message
8
+ 'Seed the database with "rake thredded:dev:seed".'
9
+ end
10
+ end
11
+
12
+ class UserNotFound < Thredded::Error
13
+ def message
14
+ 'This user could not be found. Is their name misspelled?'
15
+ end
16
+ end
17
+
18
+ class PrivateTopicNotFound < Thredded::Error
19
+ def message
20
+ 'This private topic does not exist.'
21
+ end
22
+ end
23
+
24
+ class TopicNotFound < Thredded::Error
25
+ def message
26
+ 'This topic does not exist.'
27
+ end
28
+ end
29
+
30
+ class MessageboardNotFound < Thredded::Error
31
+ def message
32
+ 'This messageboard does not exist.'
33
+ end
34
+ end
35
+
36
+ class MessageboardReadDenied < Thredded::Error
37
+ def message
38
+ 'You are not authorized access to this messageboard.'
39
+ end
40
+ end
41
+
42
+ class MessageboardCreateDenied < Thredded::Error
43
+ def message
44
+ 'You are not authorized to create a new messageboard.'
45
+ end
46
+ end
47
+
48
+ class TopicCreateDenied < Thredded::Error
49
+ def message
50
+ 'You are not authorized to post in this messageboard.'
51
+ end
52
+ end
53
+
54
+ class PrivateTopicCreateDenied < TopicCreateDenied
55
+ end
56
+
57
+ class EmptySearchResults < Thredded::Error
58
+ def initialize(query)
59
+ @query = query
60
+ end
61
+
62
+ def message
63
+ "There are no results for your search - '#{@query}'"
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ module Thredded
2
+ # If thredded is rendered within an application layout, this module allows
3
+ # referring to the routes in the layout directly, without having to use `main_app.`.
4
+ module MainAppRouteDelegator
5
+ # delegate url helpers to main_app
6
+ def method_missing(method, *args, &block)
7
+ if main_app_route_method?(method)
8
+ main_app.send(method, *args)
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ def respond_to?(method, *args)
15
+ super || main_app_route_method?(method)
16
+ end
17
+
18
+ private
19
+
20
+ def main_app_route_method?(method)
21
+ method.to_s =~ /_(?:path|url)$/ && main_app.respond_to?(method)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module Thredded
2
+ class MessageboardUserPermissions
3
+ attr_reader :messageboard, :user
4
+
5
+ def initialize(messageboard, user)
6
+ @messageboard = messageboard
7
+ @user = user
8
+ end
9
+
10
+ def readable?
11
+ user.thredded_can_read_messageboards.include?(messageboard)
12
+ end
13
+
14
+ def postable?
15
+ user.thredded_can_write_messageboards.include?(messageboard)
16
+ end
17
+
18
+ def moderatable?
19
+ user.thredded_can_moderate_messageboards.include?(messageboard)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ module Thredded
2
+ class PostSqlBuilder < TableSqlBuilder
3
+ def apply_filters
4
+ @scope = @scope.joins(:topic_categories).where(category_id: categories) if categories.present?
5
+ return unless users.present? || text.present?
6
+ posts_scope = Thredded::Post
7
+ posts_scope = posts_scope.where(user_id: users) if users.present?
8
+ posts_scope = DbTextSearch::FullTextSearch.new(posts_scope, :content).find(text) if text.present?
9
+ @scope = @scope.joins(:posts).merge(posts_scope)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ module Thredded
2
+ class PostUserPermissions
3
+ def initialize(post, user, _user_details)
4
+ @post = post
5
+ @topic = post.postable
6
+ @messageboard_user_permission = MessageboardUserPermissions.new(post.messageboard, user)
7
+ @user = user
8
+ end
9
+
10
+ def editable?
11
+ created_post? || @messageboard_user_permission.moderatable?
12
+ end
13
+
14
+ def manageable?
15
+ created_post? || @messageboard_user_permission.moderatable?
16
+ end
17
+
18
+ def creatable?
19
+ thread_is_not_locked? && @messageboard_user_permission.postable?
20
+ end
21
+
22
+ private
23
+
24
+ def created_post?
25
+ @user.id == @post.user_id
26
+ end
27
+
28
+ def thread_is_not_locked?
29
+ @topic.private? || !@topic.locked?
30
+ end
31
+ end
32
+ end