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,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module UserTopicReadStateCommon
5
+ extend ActiveSupport::Concern
6
+ included do
7
+ extend ClassMethods
8
+ validates :user_id, uniqueness: { scope: :postable_id }
9
+ end
10
+
11
+ # @return [Boolean]
12
+ def read?
13
+ postable.last_post_at <= read_at
14
+ end
15
+
16
+ # @param post [Post or PrivatePost]
17
+ # @return [Boolean]
18
+ def post_read?(post)
19
+ post.created_at <= read_at
20
+ end
21
+
22
+ module ClassMethods
23
+ # @param user_id [Integer]
24
+ # @param topic_id [Integer]
25
+ # @param post [Thredded::PostCommon]
26
+ # @param post_page [Integer]
27
+ def touch!(user_id, topic_id, post, post_page)
28
+ # TODO: Switch to upsert once Travis supports PostgreSQL 9.5.
29
+ # Travis issue: https://github.com/travis-ci/travis-ci/issues/4264
30
+ # Upsert gem: https://github.com/seamusabshere/upsert
31
+ state = find_or_initialize_by(user_id: user_id, postable_id: topic_id)
32
+ fail ArgumentError, "expected post_page >= 1, given #{post_page.inspect}" if post_page < 1
33
+ return unless !state.read_at? || state.read_at < post.created_at
34
+ state.update!(read_at: post.created_at, page: post_page)
35
+ end
36
+
37
+ def read_on_first_post!(user, topic)
38
+ create!(user: user, postable: topic, read_at: Time.zone.now, page: 1)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class Category < ActiveRecord::Base
5
+ extend FriendlyId
6
+ belongs_to :messageboard
7
+ has_many :topic_categories
8
+ has_many :topics, through: :topic_categories
9
+ friendly_id :name, use: %i[history scoped], scope: :messageboard
10
+
11
+ validates :name, presence: true
12
+ validates :messageboard_id, presence: true
13
+
14
+ def normalize_friendly_id(input)
15
+ Thredded.slugifier.call(input.to_s)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class Messageboard < ActiveRecord::Base
5
+ extend FriendlyId
6
+ friendly_id :slug_candidates,
7
+ use: %i[slugged reserved],
8
+ # Avoid route conflicts
9
+ reserved_words: ::Thredded::FriendlyIdReservedWordsAndPagination.new(
10
+ %w[
11
+ admin
12
+ autocomplete-users
13
+ messageboards
14
+ posts
15
+ preferences
16
+ private-posts
17
+ private-topics
18
+ theme-preview
19
+ ]
20
+ )
21
+
22
+ validates :name, uniqueness: true, length: { maximum: 60 }, presence: true
23
+ validates :topics_count, numericality: true
24
+ validates :position, presence: true, on: :update
25
+ before_save :ensure_position, on: :create
26
+
27
+ def ensure_position
28
+ self.position ||= (created_at || Time.zone.now).to_i
29
+ end
30
+
31
+ has_many :categories, dependent: :destroy
32
+ has_many :user_messageboard_preferences, dependent: :destroy
33
+ has_many :posts, dependent: :destroy
34
+ has_many :topics, dependent: :destroy, inverse_of: :messageboard
35
+
36
+ belongs_to :last_topic, class_name: 'Thredded::Topic', optional: true
37
+
38
+ has_many :user_details, through: :posts
39
+ has_many :messageboard_users,
40
+ inverse_of: :messageboard,
41
+ foreign_key: :thredded_messageboard_id,
42
+ dependent: :destroy
43
+ has_many :recently_active_user_details,
44
+ -> { merge(Thredded::MessageboardUser.recently_active) },
45
+ class_name: 'Thredded::UserDetail',
46
+ through: :messageboard_users,
47
+ source: :user_detail
48
+ has_many :recently_active_users,
49
+ class_name: Thredded.user_class_name,
50
+ through: :recently_active_user_details,
51
+ source: :user
52
+
53
+ belongs_to :group,
54
+ inverse_of: :messageboards,
55
+ foreign_key: :messageboard_group_id,
56
+ class_name: 'Thredded::MessageboardGroup',
57
+ **(Thredded.rails_gte_51? ? { optional: true } : {})
58
+
59
+ has_many :post_moderation_records, inverse_of: :messageboard, dependent: :delete_all
60
+
61
+ # rubocop:disable Style/Lambda
62
+ scope :top_level_messageboards, -> { where(group: nil) }
63
+ scope :by_messageboard_group, ->(group) { where(group: group.id) }
64
+ scope :ordered, ->(order = Thredded.messageboards_order) {
65
+ case order
66
+ when :position
67
+ self
68
+ when :created_at_asc
69
+ ordered_by_created_at_asc
70
+ when :last_post_at_desc
71
+ ordered_by_last_post_at_desc
72
+ when :topics_count_desc
73
+ ordered_by_topics_count_desc
74
+ end.ordered_by_position.order(id: :asc)
75
+ }
76
+ scope :ordered_by_position, ->() { order(position: :asc) }
77
+ scope :ordered_by_created_at_asc, ->() { order(created_at: :asc) }
78
+ scope :ordered_by_last_post_at_desc, ->() {
79
+ joins('LEFT JOIN thredded_topics AS last_topics ON thredded_messageboards.last_topic_id = last_topics.id')
80
+ .order(Arel.sql('COALESCE(last_topics.last_post_at, thredded_messageboards.created_at) DESC'))
81
+ }
82
+ scope :ordered_by_topics_count_desc, ->() {
83
+ order(topics_count: :desc)
84
+ }
85
+ # rubocop:enable Style/Lambda
86
+
87
+ # Finds the messageboard by its slug or ID, or raises Thredded::Errors::MessageboardNotFound.
88
+ # @param slug_or_id [String]
89
+ # @return [Thredded::Messageboard]
90
+ # @raise [Thredded::Errors::MessageboardNotFound] if the messageboard with the given slug does not exist.
91
+ def self.friendly_find!(slug_or_id)
92
+ friendly.find(slug_or_id)
93
+ rescue ActiveRecord::RecordNotFound
94
+ raise Thredded::Errors::MessageboardNotFound
95
+ end
96
+
97
+ def last_user
98
+ last_topic.try(:last_user)
99
+ end
100
+
101
+ def update_last_topic!
102
+ return if destroyed?
103
+ self.last_topic = topics.order_recently_posted_first.moderation_state_visible_to_all.first
104
+ save! if last_topic_id_changed?
105
+ end
106
+
107
+ def normalize_friendly_id(input)
108
+ Thredded.slugifier.call(input.to_s)
109
+ end
110
+
111
+ private
112
+
113
+ def slug_candidates
114
+ [
115
+ :name,
116
+ [:name, '-board']
117
+ ]
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class MessageboardGroup < ActiveRecord::Base
5
+ has_many :messageboards,
6
+ inverse_of: :group,
7
+ foreign_key: :messageboard_group_id,
8
+ dependent: :nullify
9
+
10
+ scope :ordered, -> { order(position: :asc, id: :asc) }
11
+ validates :name, presence: true, uniqueness: true
12
+ validates :position, presence: true, on: :update
13
+ before_save :ensure_position, on: :create
14
+
15
+ def ensure_position
16
+ self.position ||= Time.zone.now.to_i
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class MessageboardNotificationsForFollowedTopics < ActiveRecord::Base
5
+ belongs_to :user_preference,
6
+ primary_key: :user_id,
7
+ foreign_key: :user_id,
8
+ inverse_of: :messageboard_notifications_for_followed_topics
9
+ belongs_to :user,
10
+ class_name: Thredded.user_class_name,
11
+ inverse_of: :thredded_user_messageboard_preferences
12
+ belongs_to :messageboard
13
+ scope :for_messageboard, ->(messageboard) { where(messageboard_id: messageboard.id) }
14
+
15
+ validates :user_id, presence: true
16
+ validates :messageboard_id, presence: true
17
+
18
+ def self.in(messageboard)
19
+ where(messageboard_id: messageboard.id)
20
+ end
21
+
22
+ include Thredded::NotifierPreference
23
+
24
+ def self.default(_notifier)
25
+ # could be moved to `notifier.defaults(:notifications_for_followed_topics)` or
26
+ # `notifier.defaults(:messageboard_notifications_for_followed_topics)`
27
+ Thredded::BaseNotifier::NotificationsDefault.new(true)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # The state of a user with regards to a messageboard, such as the last time the user was active (visited)
5
+ # the messageboard.
6
+ class MessageboardUser < ActiveRecord::Base
7
+ belongs_to :messageboard,
8
+ class_name: 'Thredded::Messageboard',
9
+ foreign_key: :thredded_messageboard_id,
10
+ inverse_of: :messageboard_users
11
+ belongs_to :user_detail, class_name: 'Thredded::UserDetail', foreign_key: :thredded_user_detail_id
12
+ scope :recently_active, -> { where(arel_table[:last_seen_at].gt(Thredded.active_user_threshold.ago)) }
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class NotificationsForFollowedTopics < ActiveRecord::Base
5
+ belongs_to :user,
6
+ class_name: Thredded.user_class_name,
7
+ inverse_of: :thredded_notifications_for_followed_topics
8
+ belongs_to :messageboard,
9
+ # If messageboard is `nil`, these are the global preferences.
10
+ **(Thredded.rails_gte_51? ? { optional: true } : {})
11
+ belongs_to :user_preference,
12
+ primary_key: :user_id,
13
+ foreign_key: :user_id,
14
+ inverse_of: :notifications_for_followed_topics
15
+
16
+ validates :user_id, presence: true
17
+
18
+ include Thredded::NotifierPreference
19
+
20
+ def self.default(_notifier)
21
+ # could be moved to `notifier.defaults(:notifications_for_followed_topics)`
22
+ Thredded::BaseNotifier::NotificationsDefault.new(true)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class NotificationsForPrivateTopics < ActiveRecord::Base
5
+ belongs_to :user,
6
+ class_name: Thredded.user_class_name,
7
+ inverse_of: :thredded_notifications_for_private_topics
8
+ belongs_to :user_preference,
9
+ primary_key: :user_id,
10
+ foreign_key: :user_id,
11
+ inverse_of: :notifications_for_private_topics
12
+
13
+ validates :user_id, presence: true
14
+
15
+ include Thredded::NotifierPreference
16
+
17
+ def self.default(_notifier)
18
+ # could be moved to `notifier.defaults(:notifications_for_private_topics)`
19
+ Thredded::BaseNotifier::NotificationsDefault.new(true)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class NullUser
5
+ include ::Thredded::UserPermissions::Read::All
6
+ include ::Thredded::UserPermissions::Write::None
7
+ include ::Thredded::UserPermissions::Message::ReadersOfWriteableBoards
8
+ include ::Thredded::UserPermissions::Moderate::None
9
+ include ::Thredded::UserPermissions::Admin::None
10
+
11
+ def thredded_private_topics
12
+ Thredded::PrivateTopic.none
13
+ end
14
+
15
+ def id
16
+ nil
17
+ end
18
+
19
+ def name
20
+ I18n.t('thredded.null_user_name')
21
+ end
22
+
23
+ def to_s
24
+ name
25
+ end
26
+
27
+ def valid?
28
+ false
29
+ end
30
+
31
+ def thredded_anonymous?
32
+ true
33
+ end
34
+
35
+ def thredded_user_detail
36
+ Thredded::UserDetail.new
37
+ end
38
+
39
+ def thredded_user_preference
40
+ Thredded::UserPreference.new
41
+ end
42
+
43
+ def thredded_display_name
44
+ to_s
45
+ end
46
+
47
+ def marked_for_destruction?
48
+ return false
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class NullUserTopicReadState
5
+ def page
6
+ 1
7
+ end
8
+
9
+ def read?
10
+ false
11
+ end
12
+
13
+ def post_read?(_post)
14
+ false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class Post < ActiveRecord::Base
5
+ include Thredded::PostCommon
6
+ include Thredded::ContentModerationState
7
+
8
+ belongs_to :user,
9
+ class_name: Thredded.user_class_name,
10
+ inverse_of: :thredded_posts,
11
+ **(Thredded.rails_gte_51? ? { optional: true } : {})
12
+ belongs_to :messageboard,
13
+ counter_cache: true
14
+ belongs_to :postable,
15
+ class_name: 'Thredded::Topic',
16
+ inverse_of: :posts,
17
+ counter_cache: true
18
+ belongs_to :user_detail,
19
+ inverse_of: :posts,
20
+ primary_key: :user_id,
21
+ foreign_key: :user_id,
22
+ counter_cache: true,
23
+ **(Thredded.rails_gte_51? ? { optional: true } : {})
24
+ has_many :moderation_records,
25
+ class_name: 'Thredded::PostModerationRecord',
26
+ dependent: :nullify
27
+ has_many :user_notifications,
28
+ class_name: 'Thredded::UserPostNotification',
29
+ dependent: :destroy
30
+ has_one :last_moderation_record, -> { order_newest_first },
31
+ class_name: 'Thredded::PostModerationRecord'
32
+
33
+ validates :messageboard_id, presence: true
34
+
35
+ after_commit :update_parent_last_user_and_time_from_last_post, on: %i[create destroy]
36
+ after_commit :update_parent_last_user_and_time_from_last_post_if_moderation_state_changed, on: :update
37
+
38
+ after_commit :auto_follow_and_notify, on: %i[create update]
39
+
40
+ # @param [Integer] per_page
41
+ # @param [Thredded.user_class] user
42
+ def page(per_page: self.class.default_per_page, user:)
43
+ readable_posts = PostPolicy::Scope.new(user, postable.posts).resolve
44
+ calculate_page(readable_posts, per_page)
45
+ end
46
+
47
+ def private_topic_post?
48
+ false
49
+ end
50
+
51
+ # @return [ActiveRecord::Relation<Thredded.user_class>] users that can read this post.
52
+ def readers
53
+ Thredded.user_class.thredded_messageboards_readers([messageboard])
54
+ end
55
+
56
+ private
57
+
58
+ def auto_follow_and_notify
59
+ return unless user
60
+ # need to do this in-process so that it appears to them immediately
61
+ if first_post_in_topic? ? Thredded.auto_follow_when_creating_topic : Thredded.auto_follow_when_posting_in_topic
62
+ UserTopicFollow.create_unless_exists(user.id, postable_id, :posted)
63
+ end
64
+ # everything else can happen later
65
+ AutoFollowAndNotifyJob.perform_later(id)
66
+ end
67
+
68
+ def update_parent_last_user_and_time_from_last_post
69
+ postable.update_last_user_and_time_from_last_post!
70
+ messageboard.update_last_topic!
71
+ end
72
+
73
+ def update_parent_last_user_and_time_from_last_post_if_moderation_state_changed
74
+ update_parent_last_user_and_time_from_last_post if previous_changes.include?('moderation_state')
75
+ end
76
+ end
77
+ end