threddedDANIEL 0.14.5

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 (367) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +744 -0
  4. data/app/assets/images/favicons/README.md +3 -0
  5. data/app/assets/images/favicons/amazon.png +0 -0
  6. data/app/assets/images/favicons/github.png +0 -0
  7. data/app/assets/images/favicons/google_branding/logo_calendar_128px.png +0 -0
  8. data/app/assets/images/favicons/google_branding/logo_docs_48px.png +0 -0
  9. data/app/assets/images/favicons/google_branding/logo_drive_48px.png +0 -0
  10. data/app/assets/images/favicons/google_branding/logo_forms_48px.png +0 -0
  11. data/app/assets/images/favicons/google_branding/logo_sheets_48px.png +0 -0
  12. data/app/assets/images/favicons/google_branding/logo_slides_48px.png +0 -0
  13. data/app/assets/images/favicons/stackexchange.png +0 -0
  14. data/app/assets/images/favicons/twitter.png +0 -0
  15. data/app/assets/images/favicons/wikipedia.png +0 -0
  16. data/app/assets/images/thredded/breadcrumb-chevron.svg +1 -0
  17. data/app/assets/images/thredded/follow.svg +1 -0
  18. data/app/assets/images/thredded/lock.svg +1 -0
  19. data/app/assets/images/thredded/moderation.svg +1 -0
  20. data/app/assets/images/thredded/private-messages.svg +1 -0
  21. data/app/assets/images/thredded/settings.svg +1 -0
  22. data/app/assets/images/thredded/three-dot-menu.svg +3 -0
  23. data/app/assets/images/thredded/unfollow.svg +1 -0
  24. data/app/assets/javascripts/thredded.es6 +2 -0
  25. data/app/assets/javascripts/thredded/components/currently_online.es6 +32 -0
  26. data/app/assets/javascripts/thredded/components/flash_messages.es6 +9 -0
  27. data/app/assets/javascripts/thredded/components/mention_autocompletion.es6 +40 -0
  28. data/app/assets/javascripts/thredded/components/post_form.es6 +37 -0
  29. data/app/assets/javascripts/thredded/components/preview_area.es6 +56 -0
  30. data/app/assets/javascripts/thredded/components/quote_post.es6 +49 -0
  31. data/app/assets/javascripts/thredded/components/time_stamps.es6 +25 -0
  32. data/app/assets/javascripts/thredded/components/topic_form.es6 +94 -0
  33. data/app/assets/javascripts/thredded/components/topics.es6 +46 -0
  34. data/app/assets/javascripts/thredded/components/turboforms.es6 +25 -0
  35. data/app/assets/javascripts/thredded/components/user_preferences_form.es6 +66 -0
  36. data/app/assets/javascripts/thredded/components/user_textcomplete.es6 +50 -0
  37. data/app/assets/javascripts/thredded/components/users_select.es6 +122 -0
  38. data/app/assets/javascripts/thredded/core/csrf_tokens.es6 +9 -0
  39. data/app/assets/javascripts/thredded/core/debounce.es6 +32 -0
  40. data/app/assets/javascripts/thredded/core/escape_html.es6 +7 -0
  41. data/app/assets/javascripts/thredded/core/hide_soft_keyboard.es6 +7 -0
  42. data/app/assets/javascripts/thredded/core/on_page_load.es6 +54 -0
  43. data/app/assets/javascripts/thredded/core/serialize_form.es6 +9 -0
  44. data/app/assets/javascripts/thredded/core/thredded.es6 +1 -0
  45. data/app/assets/javascripts/thredded/dependencies.js +8 -0
  46. data/app/assets/javascripts/thredded/dependencies/autosize.js +1 -0
  47. data/app/assets/javascripts/thredded/dependencies/textcomplete.js +1 -0
  48. data/app/assets/javascripts/thredded/dependencies/timeago.js +1 -0
  49. data/app/assets/javascripts/thredded/dependencies/ujs.js +1 -0
  50. data/app/assets/javascripts/thredded/thredded.es6 +2 -0
  51. data/app/assets/stylesheets/thredded.scss +3 -0
  52. data/app/assets/stylesheets/thredded/_base.scss +13 -0
  53. data/app/assets/stylesheets/thredded/_dependencies.scss +0 -0
  54. data/app/assets/stylesheets/thredded/_email.scss +51 -0
  55. data/app/assets/stylesheets/thredded/_thredded.scss +33 -0
  56. data/app/assets/stylesheets/thredded/base/_alerts.scss +44 -0
  57. data/app/assets/stylesheets/thredded/base/_buttons.scss +73 -0
  58. data/app/assets/stylesheets/thredded/base/_dropdown-menu.scss +83 -0
  59. data/app/assets/stylesheets/thredded/base/_forms.scss +84 -0
  60. data/app/assets/stylesheets/thredded/base/_grid.scss +58 -0
  61. data/app/assets/stylesheets/thredded/base/_lists.scss +31 -0
  62. data/app/assets/stylesheets/thredded/base/_nav.scss +67 -0
  63. data/app/assets/stylesheets/thredded/base/_tables.scss +17 -0
  64. data/app/assets/stylesheets/thredded/base/_typography.scss +72 -0
  65. data/app/assets/stylesheets/thredded/base/_variables.scss +141 -0
  66. data/app/assets/stylesheets/thredded/components/_alerts.scss +19 -0
  67. data/app/assets/stylesheets/thredded/components/_base.scss +25 -0
  68. data/app/assets/stylesheets/thredded/components/_currently-online.scss +54 -0
  69. data/app/assets/stylesheets/thredded/components/_empty.scss +11 -0
  70. data/app/assets/stylesheets/thredded/components/_flash-message.scss +19 -0
  71. data/app/assets/stylesheets/thredded/components/_following.scss +14 -0
  72. data/app/assets/stylesheets/thredded/components/_form-list.scss +39 -0
  73. data/app/assets/stylesheets/thredded/components/_icons.scss +3 -0
  74. data/app/assets/stylesheets/thredded/components/_main-section.scss +9 -0
  75. data/app/assets/stylesheets/thredded/components/_mention-autocomplete.scss +57 -0
  76. data/app/assets/stylesheets/thredded/components/_messageboard.scss +155 -0
  77. data/app/assets/stylesheets/thredded/components/_onebox.scss +275 -0
  78. data/app/assets/stylesheets/thredded/components/_pagination.scss +35 -0
  79. data/app/assets/stylesheets/thredded/components/_post-form.scss +27 -0
  80. data/app/assets/stylesheets/thredded/components/_post.scss +138 -0
  81. data/app/assets/stylesheets/thredded/components/_preferences.scss +19 -0
  82. data/app/assets/stylesheets/thredded/components/_preview_area.scss +11 -0
  83. data/app/assets/stylesheets/thredded/components/_topic-delete.scss +8 -0
  84. data/app/assets/stylesheets/thredded/components/_topic-header.scss +111 -0
  85. data/app/assets/stylesheets/thredded/components/_topics.scss +186 -0
  86. data/app/assets/stylesheets/thredded/layout/_main-container.scss +16 -0
  87. data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +79 -0
  88. data/app/assets/stylesheets/thredded/layout/_moderation.scss +97 -0
  89. data/app/assets/stylesheets/thredded/layout/_navigation.scss +87 -0
  90. data/app/assets/stylesheets/thredded/layout/_search-navigation.scss +126 -0
  91. data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +20 -0
  92. data/app/assets/stylesheets/thredded/layout/_user.scss +10 -0
  93. data/app/assets/stylesheets/thredded/utilities/_is-compact.scss +26 -0
  94. data/app/assets/stylesheets/thredded/utilities/_is-expanded.scss +16 -0
  95. data/app/commands/thredded/at_notification_extractor.rb +23 -0
  96. data/app/commands/thredded/autofollow_users.rb +62 -0
  97. data/app/commands/thredded/create_messageboard.rb +42 -0
  98. data/app/commands/thredded/mark_all_read.rb +22 -0
  99. data/app/commands/thredded/moderate_post.rb +48 -0
  100. data/app/commands/thredded/notify_following_users.rb +78 -0
  101. data/app/commands/thredded/notify_private_topic_users.rb +34 -0
  102. data/app/controllers/concerns/thredded/new_post_params.rb +21 -0
  103. data/app/controllers/concerns/thredded/new_private_post_params.rb +21 -0
  104. data/app/controllers/concerns/thredded/new_private_topic_params.rb +25 -0
  105. data/app/controllers/concerns/thredded/new_topic_params.rb +19 -0
  106. data/app/controllers/concerns/thredded/render_preview.rb +16 -0
  107. data/app/controllers/thredded/application_controller.rb +145 -0
  108. data/app/controllers/thredded/autocomplete_users_controller.rb +52 -0
  109. data/app/controllers/thredded/messageboard_groups_controller.rb +31 -0
  110. data/app/controllers/thredded/messageboards_controller.rb +53 -0
  111. data/app/controllers/thredded/moderation_controller.rb +103 -0
  112. data/app/controllers/thredded/post_permalinks_controller.rb +11 -0
  113. data/app/controllers/thredded/post_previews_controller.rb +29 -0
  114. data/app/controllers/thredded/posts_controller.rb +97 -0
  115. data/app/controllers/thredded/preferences_controller.rb +42 -0
  116. data/app/controllers/thredded/private_post_permalinks_controller.rb +12 -0
  117. data/app/controllers/thredded/private_post_previews_controller.rb +29 -0
  118. data/app/controllers/thredded/private_posts_controller.rb +96 -0
  119. data/app/controllers/thredded/private_topic_previews_controller.rb +15 -0
  120. data/app/controllers/thredded/private_topics_controller.rb +102 -0
  121. data/app/controllers/thredded/read_states_controller.rb +13 -0
  122. data/app/controllers/thredded/theme_previews_controller.rb +34 -0
  123. data/app/controllers/thredded/topic_previews_controller.rb +15 -0
  124. data/app/controllers/thredded/topics_controller.rb +205 -0
  125. data/app/forms/thredded/edit_topic_form.rb +51 -0
  126. data/app/forms/thredded/post_form.rb +54 -0
  127. data/app/forms/thredded/private_post_form.rb +50 -0
  128. data/app/forms/thredded/private_topic_form.rb +160 -0
  129. data/app/forms/thredded/topic_form.rb +95 -0
  130. data/app/forms/thredded/user_preferences_form.rb +110 -0
  131. data/app/helpers/thredded/application_helper.rb +129 -0
  132. data/app/helpers/thredded/nav_helper.rb +42 -0
  133. data/app/helpers/thredded/render_helper.rb +15 -0
  134. data/app/helpers/thredded/urls_helper.rb +134 -0
  135. data/app/jobs/thredded/activity_updater_job.rb +21 -0
  136. data/app/jobs/thredded/auto_follow_and_notify_job.rb +14 -0
  137. data/app/jobs/thredded/notify_private_topic_users_job.rb +12 -0
  138. data/app/mailer_previews/thredded/base_mailer_preview.rb +118 -0
  139. data/app/mailer_previews/thredded/post_mailer_preview.rb +13 -0
  140. data/app/mailer_previews/thredded/private_topic_mailer_preview.rb +13 -0
  141. data/app/mailers/thredded/base_mailer.rb +18 -0
  142. data/app/mailers/thredded/post_mailer.rb +21 -0
  143. data/app/mailers/thredded/private_topic_mailer.rb +21 -0
  144. data/app/models/concerns/thredded/content_moderation_state.rb +67 -0
  145. data/app/models/concerns/thredded/friendly_id_reserved_words_and_pagination.rb +17 -0
  146. data/app/models/concerns/thredded/moderation_state.rb +14 -0
  147. data/app/models/concerns/thredded/notifier_preference.rb +19 -0
  148. data/app/models/concerns/thredded/post_common.rb +76 -0
  149. data/app/models/concerns/thredded/search_parser.rb +41 -0
  150. data/app/models/concerns/thredded/topic_common.rb +94 -0
  151. data/app/models/concerns/thredded/topics_search.rb +67 -0
  152. data/app/models/concerns/thredded/user_topic_read_state_common.rb +42 -0
  153. data/app/models/thredded/category.rb +18 -0
  154. data/app/models/thredded/messageboard.rb +120 -0
  155. data/app/models/thredded/messageboard_group.rb +19 -0
  156. data/app/models/thredded/messageboard_notifications_for_followed_topics.rb +30 -0
  157. data/app/models/thredded/messageboard_user.rb +14 -0
  158. data/app/models/thredded/notifications_for_followed_topics.rb +25 -0
  159. data/app/models/thredded/notifications_for_private_topics.rb +22 -0
  160. data/app/models/thredded/null_user.rb +51 -0
  161. data/app/models/thredded/null_user_topic_read_state.rb +17 -0
  162. data/app/models/thredded/post.rb +77 -0
  163. data/app/models/thredded/post_moderation_record.rb +56 -0
  164. data/app/models/thredded/private_post.rb +63 -0
  165. data/app/models/thredded/private_topic.rb +89 -0
  166. data/app/models/thredded/private_user.rb +8 -0
  167. data/app/models/thredded/stats.rb +25 -0
  168. data/app/models/thredded/topic.rb +179 -0
  169. data/app/models/thredded/topic_category.rb +8 -0
  170. data/app/models/thredded/user_detail.rb +27 -0
  171. data/app/models/thredded/user_extender.rb +75 -0
  172. data/app/models/thredded/user_messageboard_preference.rb +45 -0
  173. data/app/models/thredded/user_permissions/admin/if_admin_column_true.rb +14 -0
  174. data/app/models/thredded/user_permissions/admin/none.rb +14 -0
  175. data/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb +15 -0
  176. data/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb +16 -0
  177. data/app/models/thredded/user_permissions/moderate/none.rb +16 -0
  178. data/app/models/thredded/user_permissions/read/all.rb +27 -0
  179. data/app/models/thredded/user_permissions/write/all.rb +16 -0
  180. data/app/models/thredded/user_permissions/write/none.rb +16 -0
  181. data/app/models/thredded/user_post_notification.rb +29 -0
  182. data/app/models/thredded/user_preference.rb +26 -0
  183. data/app/models/thredded/user_private_topic_read_state.rb +13 -0
  184. data/app/models/thredded/user_topic_follow.rb +34 -0
  185. data/app/models/thredded/user_topic_read_state.rb +13 -0
  186. data/app/notifiers/thredded/email_notifier.rb +25 -0
  187. data/app/policies/thredded/messageboard_group_policy.rb +16 -0
  188. data/app/policies/thredded/messageboard_policy.rb +49 -0
  189. data/app/policies/thredded/post_policy.rb +64 -0
  190. data/app/policies/thredded/private_post_policy.rb +38 -0
  191. data/app/policies/thredded/private_topic_policy.rb +24 -0
  192. data/app/policies/thredded/topic_policy.rb +49 -0
  193. data/app/view_hooks/thredded/all_view_hooks.rb +125 -0
  194. data/app/view_models/thredded/base_topic_view.rb +43 -0
  195. data/app/view_models/thredded/messageboard_group_view.rb +27 -0
  196. data/app/view_models/thredded/post_view.rb +89 -0
  197. data/app/view_models/thredded/posts_page_view.rb +27 -0
  198. data/app/view_models/thredded/private_topic_view.rb +20 -0
  199. data/app/view_models/thredded/private_topics_page_view.rb +31 -0
  200. data/app/view_models/thredded/topic_email_view.rb +18 -0
  201. data/app/view_models/thredded/topic_posts_page_view.rb +17 -0
  202. data/app/view_models/thredded/topic_view.rb +68 -0
  203. data/app/view_models/thredded/topics_page_view.rb +38 -0
  204. data/app/views/layouts/thredded/application.html.erb +18 -0
  205. data/app/views/thredded/categories/_category.html.erb +1 -0
  206. data/app/views/thredded/error_pages/forbidden.html.erb +6 -0
  207. data/app/views/thredded/error_pages/not_found.html.erb +6 -0
  208. data/app/views/thredded/kaminari/_first_page.html.erb +11 -0
  209. data/app/views/thredded/kaminari/_gap.html.erb +8 -0
  210. data/app/views/thredded/kaminari/_last_page.html.erb +11 -0
  211. data/app/views/thredded/kaminari/_next_page.html.erb +11 -0
  212. data/app/views/thredded/kaminari/_page.html.erb +12 -0
  213. data/app/views/thredded/kaminari/_paginator.html.erb +23 -0
  214. data/app/views/thredded/kaminari/_prev_page.html.erb +11 -0
  215. data/app/views/thredded/messageboard_groups/new.html.erb +28 -0
  216. data/app/views/thredded/messageboards/_form.html.erb +30 -0
  217. data/app/views/thredded/messageboards/_messageboard.html.erb +20 -0
  218. data/app/views/thredded/messageboards/_messageboard_meta.html.erb +13 -0
  219. data/app/views/thredded/messageboards/edit.html.erb +15 -0
  220. data/app/views/thredded/messageboards/index.html.erb +34 -0
  221. data/app/views/thredded/messageboards/new.html.erb +15 -0
  222. data/app/views/thredded/moderation/_nav.html.erb +19 -0
  223. data/app/views/thredded/moderation/_post.html.erb +19 -0
  224. data/app/views/thredded/moderation/_post_moderation_actions.html.erb +12 -0
  225. data/app/views/thredded/moderation/_post_moderation_record.html.erb +46 -0
  226. data/app/views/thredded/moderation/_user_moderation_state.html.erb +3 -0
  227. data/app/views/thredded/moderation/_user_post.html.erb +12 -0
  228. data/app/views/thredded/moderation/_users_search_form.html.erb +13 -0
  229. data/app/views/thredded/moderation/activity.html.erb +20 -0
  230. data/app/views/thredded/moderation/history.html.erb +13 -0
  231. data/app/views/thredded/moderation/pending.html.erb +24 -0
  232. data/app/views/thredded/moderation/user.html.erb +54 -0
  233. data/app/views/thredded/moderation/users.html.erb +41 -0
  234. data/app/views/thredded/post_mailer/post_notification.html.erb +26 -0
  235. data/app/views/thredded/post_mailer/post_notification.text.erb +14 -0
  236. data/app/views/thredded/post_previews/preview.html.erb +1 -0
  237. data/app/views/thredded/post_previews/update.html.erb +1 -0
  238. data/app/views/thredded/posts/_content.html.erb +1 -0
  239. data/app/views/thredded/posts/_form.html.erb +5 -0
  240. data/app/views/thredded/posts/_post.html.erb +13 -0
  241. data/app/views/thredded/posts/_user.html.erb +3 -0
  242. data/app/views/thredded/posts/edit.html.erb +16 -0
  243. data/app/views/thredded/posts/new.html.erb +15 -0
  244. data/app/views/thredded/posts_common/_actions.html.erb +28 -0
  245. data/app/views/thredded/posts_common/_content.html.erb +3 -0
  246. data/app/views/thredded/posts_common/_form.html.erb +22 -0
  247. data/app/views/thredded/posts_common/_header.html.erb +8 -0
  248. data/app/views/thredded/posts_common/_header_with_topic.html.erb +15 -0
  249. data/app/views/thredded/posts_common/_header_with_user_and_topic.html.erb +18 -0
  250. data/app/views/thredded/posts_common/actions/_delete.html.erb +4 -0
  251. data/app/views/thredded/posts_common/actions/_edit.html.erb +3 -0
  252. data/app/views/thredded/posts_common/actions/_mark_as_unread.html.erb +2 -0
  253. data/app/views/thredded/posts_common/actions/_quote.html.erb +4 -0
  254. data/app/views/thredded/posts_common/form/_after_content.html.erb +8 -0
  255. data/app/views/thredded/posts_common/form/_before_content.html.erb +8 -0
  256. data/app/views/thredded/posts_common/form/_content.html.erb +7 -0
  257. data/app/views/thredded/posts_common/form/_content_field.html.erb +8 -0
  258. data/app/views/thredded/posts_common/form/_preview_area.html.erb +16 -0
  259. data/app/views/thredded/preferences/_form.html.erb +95 -0
  260. data/app/views/thredded/preferences/_messageboards_nav.html.erb +8 -0
  261. data/app/views/thredded/preferences/_messageboards_nav_item.html.erb +2 -0
  262. data/app/views/thredded/preferences/edit.html.erb +20 -0
  263. data/app/views/thredded/private_post_previews/preview.html.erb +1 -0
  264. data/app/views/thredded/private_post_previews/update.html.erb +1 -0
  265. data/app/views/thredded/private_posts/_content.html.erb +1 -0
  266. data/app/views/thredded/private_posts/_form.html.erb +6 -0
  267. data/app/views/thredded/private_posts/_private_post.html.erb +6 -0
  268. data/app/views/thredded/private_posts/edit.html.erb +16 -0
  269. data/app/views/thredded/private_posts/new.html.erb +11 -0
  270. data/app/views/thredded/private_topic_mailer/message_notification.html.erb +26 -0
  271. data/app/views/thredded/private_topic_mailer/message_notification.text.erb +15 -0
  272. data/app/views/thredded/private_topic_previews/preview.html.erb +1 -0
  273. data/app/views/thredded/private_topics/_breadcrumbs.html.erb +4 -0
  274. data/app/views/thredded/private_topics/_form.html.erb +39 -0
  275. data/app/views/thredded/private_topics/_header.html.erb +17 -0
  276. data/app/views/thredded/private_topics/_no_private_topics.html.erb +6 -0
  277. data/app/views/thredded/private_topics/_private_topic.html.erb +23 -0
  278. data/app/views/thredded/private_topics/edit.html.erb +35 -0
  279. data/app/views/thredded/private_topics/header/_participant.html.erb +1 -0
  280. data/app/views/thredded/private_topics/index.html.erb +32 -0
  281. data/app/views/thredded/private_topics/new.html.erb +11 -0
  282. data/app/views/thredded/private_topics/private_topic/_participant.html.erb +1 -0
  283. data/app/views/thredded/private_topics/show.html.erb +28 -0
  284. data/app/views/thredded/search/_form.html.erb +13 -0
  285. data/app/views/thredded/shared/_breadcrumbs.html.erb +6 -0
  286. data/app/views/thredded/shared/_content_moderation_blocked_state.html.erb +8 -0
  287. data/app/views/thredded/shared/_currently_online.html.erb +6 -0
  288. data/app/views/thredded/shared/_flash_messages.html.erb +7 -0
  289. data/app/views/thredded/shared/_header.html.erb +3 -0
  290. data/app/views/thredded/shared/_nav.html.erb +19 -0
  291. data/app/views/thredded/shared/_page.html.erb +15 -0
  292. data/app/views/thredded/shared/currently_online/_header.html.erb +5 -0
  293. data/app/views/thredded/shared/currently_online/_user_list.html.erb +3 -0
  294. data/app/views/thredded/shared/currently_online/_user_list_item.html.erb +6 -0
  295. data/app/views/thredded/shared/nav/_moderation.html.erb +14 -0
  296. data/app/views/thredded/shared/nav/_notification_preferences.html.erb +8 -0
  297. data/app/views/thredded/shared/nav/_private_topics.html.erb +12 -0
  298. data/app/views/thredded/shared/nav/_standalone.html.erb +12 -0
  299. data/app/views/thredded/shared/nav/_standalone_profile.html.erb +3 -0
  300. data/app/views/thredded/shared/preview.html.erb +10 -0
  301. data/app/views/thredded/theme_previews/_section_title.html.erb +3 -0
  302. data/app/views/thredded/theme_previews/show.html.erb +99 -0
  303. data/app/views/thredded/topic_previews/preview.html.erb +1 -0
  304. data/app/views/thredded/topics/_followers.html.erb +12 -0
  305. data/app/views/thredded/topics/_form.html.erb +32 -0
  306. data/app/views/thredded/topics/_header.html.erb +32 -0
  307. data/app/views/thredded/topics/_sticky_topics_divider.html.erb +1 -0
  308. data/app/views/thredded/topics/_topic.html.erb +47 -0
  309. data/app/views/thredded/topics/_topic_form_admin_options.html.erb +12 -0
  310. data/app/views/thredded/topics/edit.html.erb +50 -0
  311. data/app/views/thredded/topics/index.html.erb +35 -0
  312. data/app/views/thredded/topics/new.html.erb +12 -0
  313. data/app/views/thredded/topics/search.html.erb +39 -0
  314. data/app/views/thredded/topics/show.html.erb +46 -0
  315. data/app/views/thredded/users/_link.html.erb +14 -0
  316. data/app/views/thredded/users/_post.html.erb +6 -0
  317. data/app/views/thredded/users/_posts.html.erb +7 -0
  318. data/bin/rails +6 -0
  319. data/config/i18n-tasks.yml +16 -0
  320. data/config/locales/de.yml +257 -0
  321. data/config/locales/en.yml +253 -0
  322. data/config/locales/es.yml +257 -0
  323. data/config/locales/fr.yml +255 -0
  324. data/config/locales/it.yml +257 -0
  325. data/config/locales/pl.yml +257 -0
  326. data/config/locales/pt-BR.yml +258 -0
  327. data/config/locales/ru.yml +255 -0
  328. data/config/locales/zh-CN.yml +246 -0
  329. data/config/routes.rb +87 -0
  330. data/db/migrate/20160329231848_create_thredded.rb +257 -0
  331. data/db/seeds.rb +4 -0
  332. data/db/upgrade_migrations/20160611094616_upgrade_v0_5_to_v0_6.rb +28 -0
  333. data/db/upgrade_migrations/20160723012349_upgrade_v0_6_to_v0_7.rb +46 -0
  334. data/db/upgrade_migrations/20161019150201_upgrade_v0_7_to_v0_8.rb +34 -0
  335. data/db/upgrade_migrations/20161113161801_upgrade_v0_8_to_v0_9.rb +60 -0
  336. data/db/upgrade_migrations/20170125033319_upgrade_v0_9_to_v0_10.rb +36 -0
  337. data/db/upgrade_migrations/20170312131417_upgrade_thredded_v0_10_to_v0_11.rb +23 -0
  338. data/db/upgrade_migrations/20170420163138_upgrade_thredded_v0_11_to_v0_12.rb +28 -0
  339. data/db/upgrade_migrations/20170811090735_upgrade_thredded_v0_13_to_v_014.rb +21 -0
  340. data/lib/generators/thredded/install/USAGE +8 -0
  341. data/lib/generators/thredded/install/install_generator.rb +20 -0
  342. data/lib/generators/thredded/install/templates/initializer.rb +160 -0
  343. data/lib/tasks/thredded_tasks.rake +14 -0
  344. data/lib/thredded.rb +190 -0
  345. data/lib/thredded/base_migration.rb +14 -0
  346. data/lib/thredded/base_notifier.rb +28 -0
  347. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +86 -0
  348. data/lib/thredded/content_formatter.rb +129 -0
  349. data/lib/thredded/database_seeder.rb +290 -0
  350. data/lib/thredded/db_tools.rb +103 -0
  351. data/lib/thredded/email_transformer.rb +22 -0
  352. data/lib/thredded/email_transformer/base.rb +47 -0
  353. data/lib/thredded/email_transformer/onebox.rb +21 -0
  354. data/lib/thredded/engine.rb +28 -0
  355. data/lib/thredded/errors.rb +68 -0
  356. data/lib/thredded/formatting_demo_content.rb +30 -0
  357. data/lib/thredded/html_pipeline/at_mention_filter.rb +78 -0
  358. data/lib/thredded/html_pipeline/autolink_filter.rb +15 -0
  359. data/lib/thredded/html_pipeline/kramdown_filter.rb +39 -0
  360. data/lib/thredded/html_pipeline/onebox_filter.rb +143 -0
  361. data/lib/thredded/html_pipeline/wrap_iframes_filter.rb +13 -0
  362. data/lib/thredded/version.rb +5 -0
  363. data/lib/thredded/view_hooks/config.rb +37 -0
  364. data/lib/thredded/view_hooks/renderer.rb +30 -0
  365. data/vendor/assets/javascripts/autosize.min.js +6 -0
  366. data/vendor/assets/javascripts/textcomplete.min.js +2 -0
  367. metadata +1035 -0
@@ -0,0 +1,16 @@
1
+ .thredded--is-expanded {
2
+ label {
3
+ height: auto;
4
+ margin-bottom: $thredded-small-spacing / 2;
5
+ opacity: 1;
6
+ visibility: visible;
7
+ }
8
+
9
+ .private_users,
10
+ .content,
11
+ .submit {
12
+ height: auto;
13
+ opacity: 1;
14
+ visibility: visible;
15
+ }
16
+ }
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class AtNotificationExtractor
5
+ def initialize(post)
6
+ @post = post
7
+ end
8
+
9
+ # @return [Array<Thredded.user_class>]
10
+ def run
11
+ view_context = Thredded::ApplicationController.new.view_context
12
+ # Do not highlight @-mentions at first, because:
13
+ # * When parsing, @-mentions within <a> tags will not be considered.
14
+ # * We can't always generate the user URL here because request.host is not available.
15
+ html = @post.filtered_content(view_context, users_provider: nil)
16
+ Thredded::HtmlPipeline::AtMentionFilter.new(
17
+ html,
18
+ view_context: view_context,
19
+ users_provider: ->(user_names) { @post.readers_from_user_names(user_names).to_a }
20
+ ).mentioned_users
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class AutofollowUsers
5
+ def initialize(post)
6
+ @post = post
7
+ end
8
+
9
+ def run
10
+ new_followers.each do |user, reason|
11
+ Thredded::UserTopicFollow.create_unless_exists(user.id, post.postable_id, reason)
12
+ end
13
+ end
14
+
15
+ # @return [Array<Thredded.user_class>]
16
+ def mentioned_users
17
+ @mentioned_users ||= Thredded::AtNotificationExtractor.new(post).run
18
+ end
19
+
20
+ # @return [Hash<Thredded.user_class, Symbol]>] a map of users that should get subscribed to their the follow reason.
21
+ def new_followers
22
+ result = {}
23
+ auto_followers.each { |user| result[user] = :auto }
24
+ exclude_follow_on_mention_opt_outs(mentioned_users).each { |user| result[user] = :mentioned }
25
+ result.delete(post.user)
26
+ result
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :post
32
+
33
+ # Returns the users that have:
34
+ # UserMessageboardPreference#auto_follow_topics? && UserPreference#auto_follow_topics?
35
+ # If the `user_preference` for a given does not exist, its default value is used.
36
+ # @return [Enumerable<Thredded.user_class>]
37
+ def auto_followers
38
+ user_board_prefs = post.messageboard.user_messageboard_preferences.each_with_object({}) do |ump, h|
39
+ h[ump.user_id] = ump
40
+ end
41
+ Thredded.user_class.includes(:thredded_user_preference)
42
+ .select(Thredded.user_class.primary_key)
43
+ .find_each(batch_size: 50_000).select do |user|
44
+
45
+ result = user_board_prefs[user.id]
46
+ result ||= Thredded::UserMessageboardPreference.new(
47
+ messageboard: post.messageboard,
48
+ user_preference: user.thredded_user_preference
49
+ )
50
+ result.auto_follow_topics?
51
+ end
52
+ end
53
+
54
+ # @return [Enumerable<Thredded.user_class>]
55
+ def exclude_follow_on_mention_opt_outs(users)
56
+ users.select do |user|
57
+ user.thredded_user_preference.follow_topics_on_mention? &&
58
+ user.thredded_user_messageboard_preferences.in(post.messageboard).follow_topics_on_mention?
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # Creates a new messageboard and seeds it with a topic.
5
+ class CreateMessageboard
6
+ # @param messageboard [Thredded::Messageboard]
7
+ # @param user [Thredded.user_class]
8
+ def initialize(messageboard, user)
9
+ @messageboard = messageboard
10
+ @user = user
11
+ end
12
+
13
+ # @return [boolean] true if the messageboard was created and seeded with a topic successfully.
14
+ def run
15
+ Thredded::Messageboard.transaction do
16
+ fail ActiveRecord::Rollback unless @messageboard.save
17
+ topic = Thredded::Topic.create!(
18
+ messageboard: @messageboard,
19
+ user: @user,
20
+ title: first_topic_title
21
+ )
22
+ Thredded::Post.create!(
23
+ messageboard: @messageboard,
24
+ user: @user,
25
+ postable: topic,
26
+ content: first_topic_content
27
+ )
28
+ true
29
+ end
30
+ end
31
+
32
+ def first_topic_title
33
+ I18n.t('thredded.messageboard_first_topic.title')
34
+ end
35
+
36
+ def first_topic_content
37
+ <<-MARKDOWN
38
+ #{I18n.t('thredded.messageboard_first_topic.content', thredded_version: Thredded::VERSION)}
39
+ MARKDOWN
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class MarkAllRead
5
+ def self.run(user)
6
+ unread_topics = Thredded::PrivateTopic.unread(user)
7
+ return if unread_topics.empty?
8
+
9
+ unread_topics.each do |topic|
10
+ last_post = topic.posts.order_oldest_first.last
11
+ total_pages = topic.posts.page(1).total_pages
12
+
13
+ Thredded::UserPrivateTopicReadState.touch!(
14
+ user.id,
15
+ topic.id,
16
+ last_post,
17
+ total_pages
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module ModeratePost
5
+ module_function
6
+
7
+ # @param [Post] post
8
+ # @param [Symbol] moderation_state
9
+ # @param [Thredded.user_class] moderator
10
+ # @return [Thredded::PostModerationRecord]
11
+ def run!(post:, moderation_state:, moderator:)
12
+ Thredded::Post.transaction do
13
+ post_moderation_record = Thredded::PostModerationRecord.record!(
14
+ moderator: moderator,
15
+ post: post,
16
+ previous_moderation_state: post.moderation_state,
17
+ moderation_state: moderation_state,
18
+ )
19
+ if post.user_id && post.user_detail.pending_moderation?
20
+ update_without_timestamping!(post.user_detail, moderation_state: moderation_state)
21
+ end
22
+ if post.postable.first_post == post
23
+ update_without_timestamping!(post.postable, moderation_state: moderation_state)
24
+ if moderation_state == :blocked
25
+ # When blocking the first post of a topic, also block all the other posts in the topic by this user.
26
+ post.postable.posts.where(user_id: post.user.id).where.not(id: post.id).each do |a_post|
27
+ update_without_timestamping!(a_post, moderation_state: moderation_state)
28
+ end
29
+ end
30
+ end
31
+ update_without_timestamping!(post, moderation_state: moderation_state)
32
+ post_moderation_record
33
+ end
34
+ end
35
+
36
+ # @param record [ActiveRecord]
37
+ # @api private
38
+ def update_without_timestamping!(record, *attr)
39
+ record_timestamps_was = record.record_timestamps
40
+ begin
41
+ record.record_timestamps = false
42
+ record.update!(*attr)
43
+ ensure
44
+ record.record_timestamps = record_timestamps_was
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Thredded
6
+ class NotifyFollowingUsers
7
+ def initialize(post)
8
+ @post = post
9
+ end
10
+
11
+ def run
12
+ subscribed_users.select! do |user|
13
+ # Record idempotently that the notification happened.
14
+ # If a notification was already created (e.g. from another thread/process),
15
+ # this will return false due to the unique constraint on the table
16
+ # and the user will be excluded.
17
+ subscribed_via_any_notifier?(user) && record_as_notified_successful?(user)
18
+ end
19
+ Thredded.notifiers.each do |notifier|
20
+ notifiable_users = targeted_users(notifier)
21
+ next if notifiable_users.empty?
22
+ notifier.new_post(@post, notifiable_users)
23
+ end
24
+ end
25
+
26
+ def targeted_users(notifier)
27
+ subscribed_users.select do |user|
28
+ user_subscribed_via?(user, notifier)
29
+ end
30
+ end
31
+
32
+ def user_subscribed_via?(user, notifier)
33
+ Thredded::NotificationsForFollowedTopics
34
+ .detect_or_default(user.thredded_notifications_for_followed_topics, notifier).enabled? &&
35
+ Thredded::MessageboardNotificationsForFollowedTopics
36
+ .detect_or_default(messageboard_notifier_prefs_by_user_id[user.id], notifier).enabled?
37
+ end
38
+
39
+ # @return [Array<User>]
40
+ def subscribed_users
41
+ @subscribed_users ||=
42
+ @post.postable.followers.includes(:thredded_notifications_for_followed_topics).select do |user|
43
+ !already_notified?(user) && !originator?(user) && can_read_post?(user)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def originator?(user)
50
+ user == @post.user
51
+ end
52
+
53
+ def can_read_post?(user)
54
+ Thredded::PostPolicy.new(user, @post).read?
55
+ end
56
+
57
+ def already_notified?(user)
58
+ # We memoize the set so that records created during this task do not affect the result,
59
+ # so that a user can receive notifications via multiple notifiers.
60
+ @already_notified_user_ids ||= Set.new Thredded::UserPostNotification.notified_user_ids(@post)
61
+ @already_notified_user_ids.include?(user.id)
62
+ end
63
+
64
+ def subscribed_via_any_notifier?(user)
65
+ Thredded.notifiers.any? { |notifier| user_subscribed_via?(user, notifier) }
66
+ end
67
+
68
+ def record_as_notified_successful?(user)
69
+ Thredded::UserPostNotification.create_from_post_and_user(@post, user)
70
+ end
71
+
72
+ def messageboard_notifier_prefs_by_user_id
73
+ @messageboard_notifier_prefs_by_user_id ||= Thredded::MessageboardNotificationsForFollowedTopics
74
+ .where(user_id: subscribed_users.map(&:id))
75
+ .for_messageboard(@post.messageboard).group_by(&:user_id)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class NotifyPrivateTopicUsers
5
+ def initialize(private_post)
6
+ @post = private_post
7
+ @private_topic = private_post.postable
8
+ end
9
+
10
+ def run
11
+ Thredded.notifiers.each do |notifier|
12
+ notifiable_users = targeted_users(notifier)
13
+ notifier.new_private_post(@post, notifiable_users) if notifiable_users.present?
14
+ end
15
+ end
16
+
17
+ def targeted_users(notifier)
18
+ users = private_topic.users.includes(:thredded_notifications_for_private_topics) - [post.user]
19
+ users = only_those_with_this_notifier_enabled(users, notifier)
20
+ users
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :post, :private_topic
26
+
27
+ def only_those_with_this_notifier_enabled(users, notifier)
28
+ users.select do |user|
29
+ Thredded::NotificationsForPrivateTopics
30
+ .detect_or_default(user.thredded_notifications_for_private_topics, notifier).enabled?
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # @api private
5
+ module NewPostParams
6
+ protected
7
+
8
+ def new_post_params
9
+ params.fetch(:post, {})
10
+ .permit(:content, :quote_post_id)
11
+ .merge(ip: request.remote_ip).tap do |p|
12
+ quote_id = p.delete(:quote_post_id)
13
+ if quote_id
14
+ post = Thredded::Post.find(quote_id)
15
+ authorize_reading post
16
+ p[:quote_post] = post
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # @api private
5
+ module NewPrivatePostParams
6
+ protected
7
+
8
+ def new_private_post_params
9
+ params.fetch(:post, {})
10
+ .permit(:content, :quote_private_post_id)
11
+ .merge(ip: request.remote_ip).tap do |p|
12
+ quote_id = p.delete(:quote_private_post_id)
13
+ if quote_id
14
+ post = Thredded::PrivatePost.find(quote_id)
15
+ authorize_reading post
16
+ p[:quote_post] = post
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # @api private
5
+ module NewPrivateTopicParams
6
+ protected
7
+
8
+ def new_private_topic_params
9
+ params
10
+ .fetch(:private_topic, {})
11
+ .permit(:title, :content, :user_names, user_ids: [])
12
+ .merge(
13
+ user: thredded_current_user,
14
+ ip: request.remote_ip
15
+ ).tap { |p| adapt_user_ids! p }
16
+ end
17
+
18
+ private
19
+
20
+ # Allow a string of IDs joined with commas.
21
+ def adapt_user_ids!(p)
22
+ p[:user_ids] = p[:user_ids].split(',') if p[:user_ids].is_a?(String)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # @api private
5
+ module NewTopicParams
6
+ protected
7
+
8
+ def new_topic_params
9
+ params
10
+ .fetch(:topic, {})
11
+ .permit(:title, :locked, :sticky, :content, category_ids: [])
12
+ .merge(
13
+ messageboard: messageboard,
14
+ user: thredded_current_user,
15
+ ip: request.remote_ip,
16
+ )
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module RenderPreview
5
+ protected
6
+
7
+ def render_preview
8
+ if request.xhr?
9
+ render layout: false
10
+ else
11
+ @preview_content = render_to_string(layout: false)
12
+ render template: 'thredded/shared/preview'
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class ApplicationController < ::ApplicationController
5
+ layout :thredded_layout
6
+ include ::Thredded::UrlsHelper
7
+ include Pundit
8
+
9
+ helper Thredded::Engine.helpers
10
+ helper_method \
11
+ :active_users,
12
+ :thredded_current_user,
13
+ :messageboard,
14
+ :messageboard_or_nil,
15
+ :preferences,
16
+ :thredded_signed_in?
17
+
18
+ rescue_from Thredded::Errors::MessageboardNotFound,
19
+ Thredded::Errors::PrivateTopicNotFound,
20
+ Thredded::Errors::TopicNotFound,
21
+ Thredded::Errors::UserNotFound do |exception|
22
+ @error = exception
23
+ @message = exception.message
24
+ render template: 'thredded/error_pages/not_found', status: :not_found
25
+ end
26
+
27
+ rescue_from Pundit::NotAuthorizedError,
28
+ Thredded::Errors::LoginRequired,
29
+ Thredded::Errors::TopicCreateDenied,
30
+ Thredded::Errors::MessageboardCreateDenied,
31
+ Thredded::Errors::PrivateTopicCreateDenied,
32
+ Thredded::Errors::MessageboardReadDenied do |exception|
33
+ @error = exception
34
+ @message = if @error.is_a?(Pundit::NotAuthorizedError)
35
+ t('thredded.errors.not_authorized')
36
+ else
37
+ exception.message
38
+ end
39
+ render template: 'thredded/error_pages/forbidden', status: :forbidden
40
+ end
41
+
42
+ protected
43
+
44
+ # The `current_user` and `signed_in?` methods are prefixed with `thredded_`
45
+ # to avoid conflicts with methods from the parent controller.
46
+
47
+ def thredded_current_user
48
+ send(Thredded.current_user_method) || NullUser.new
49
+ end
50
+
51
+ def thredded_signed_in?
52
+ !thredded_current_user.thredded_anonymous?
53
+ end
54
+
55
+ if Rails::VERSION::MAJOR < 5
56
+ # redirect_back polyfill
57
+ def redirect_back(fallback_location:, **args)
58
+ redirect_to :back, args
59
+ rescue ActionController::RedirectBackError
60
+ redirect_to fallback_location, args
61
+ end
62
+ end
63
+
64
+ # @param given [Hash]
65
+ # @return [Boolean] whether the given params are a subset of the controller's {#params}.
66
+ def params_match?(given = {})
67
+ given.all? { |k, v| v == params[k] }
68
+ end
69
+
70
+ # Returns true if the current page is beyond the end of the collection
71
+ def page_beyond_last?(page_scope)
72
+ page_scope.to_a.empty? && page_scope.current_page != 1
73
+ end
74
+
75
+ # Returns URL parameters for the last page of the given page scope.
76
+ def last_page_params(page_scope)
77
+ { page: page_scope.total_pages }
78
+ end
79
+
80
+ private
81
+
82
+ def thredded_layout
83
+ Thredded.layout
84
+ end
85
+
86
+ def authorize_reading(obj)
87
+ authorize obj, :read?
88
+ rescue Pundit::NotAuthorizedError
89
+ raise "#{obj.class.to_s.sub(/Thredded::/, 'Thredded::Errors::')}ReadDenied".constantize
90
+ end
91
+
92
+ def authorize_creating(obj)
93
+ authorize obj, :create?
94
+ rescue Pundit::NotAuthorizedError
95
+ raise "#{obj.class.to_s.sub(/Thredded::/, 'Thredded::Errors::')}CreateDenied".constantize
96
+ end
97
+
98
+ def update_user_activity
99
+ return if !messageboard_or_nil || !thredded_signed_in?
100
+
101
+ Thredded::ActivityUpdaterJob.perform_later(
102
+ thredded_current_user.id,
103
+ messageboard.id
104
+ )
105
+ end
106
+
107
+ def pundit_user
108
+ thredded_current_user
109
+ end
110
+
111
+ # Returns the `@messageboard` instance variable.
112
+ # If `@messageboard` is not set, it first sets it to the messageboard with the slug or ID given by
113
+ # `params[:messageboard_id]`.
114
+ #
115
+ # @return [Thredded::Messageboard]
116
+ # @raise [Thredded::Errors::MessageboardNotFound] if the messageboard with the given slug does not exist.
117
+ def messageboard
118
+ @messageboard ||= Thredded::Messageboard.friendly_find!(params[:messageboard_id])
119
+ end
120
+
121
+ def messageboard_or_nil
122
+ messageboard
123
+ rescue Thredded::Errors::MessageboardNotFound
124
+ nil
125
+ end
126
+
127
+ def preferences
128
+ @preferences ||= thredded_current_user.thredded_user_preference
129
+ end
130
+
131
+ def active_users
132
+ users = if messageboard_or_nil
133
+ messageboard.recently_active_users
134
+ else
135
+ Thredded.user_class.joins(:thredded_user_detail).merge(Thredded::UserDetail.recently_active).to_a
136
+ end.to_a
137
+ users.push(thredded_current_user) unless thredded_current_user.is_a?(NullUser)
138
+ users.uniq
139
+ end
140
+
141
+ def thredded_require_login!
142
+ fail Thredded::Errors::LoginRequired if thredded_current_user.thredded_anonymous?
143
+ end
144
+ end
145
+ end