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,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module ApplicationHelper
5
+ include ::Thredded::UrlsHelper
6
+ include ::Thredded::NavHelper
7
+ include ::Thredded::RenderHelper
8
+
9
+ # @return [AllViewHooks] View hooks configuration.
10
+ def view_hooks
11
+ @view_hooks ||= Thredded.view_hooks
12
+ end
13
+
14
+ def thredded_container_data
15
+ {
16
+ 'thredded-locale' => I18n.locale,
17
+ 'thredded-page-id' => content_for(:thredded_page_id),
18
+ 'thredded-root-url' => thredded.root_path
19
+ }
20
+ end
21
+
22
+ def thredded_container_classes
23
+ ['thredded--main-container', content_for(:thredded_page_id)].tap do |classes|
24
+ classes << 'thredded--is-moderator' unless moderatable_messageboards_ids.empty?
25
+ end
26
+ end
27
+
28
+ # Render the page container with the supplied block as content.
29
+ def thredded_page(&block)
30
+ # enable the host app to easily check whether a thredded view is being rendered:
31
+ content_for :thredded, true
32
+ content_for :thredded_page_content, &block
33
+ render partial: 'thredded/shared/page'
34
+ end
35
+
36
+ # @param user [Thredded.user_class, Thredded::NullUser]
37
+ # @return [String] html_safe link to the user
38
+ def user_link(user)
39
+ render partial: 'thredded/users/link', locals: { user: user }
40
+ end
41
+
42
+ # @param user [Thredded.user_class]
43
+ # @return [String] wrapped @mention string
44
+ def user_mention(user)
45
+ username = user.send(Thredded.user_name_column)
46
+ if username.include?(' ')
47
+ %(@"#{username}")
48
+ else
49
+ "@#{username}"
50
+ end
51
+ end
52
+
53
+ # @param datetime [DateTime]
54
+ # @param default [String] a string to return if time is nil.
55
+ # @return [String] html_safe datetime presentation
56
+ def time_ago(datetime, default: '-', html_options: {})
57
+ return content_tag :time, default if datetime.nil?
58
+ html_options = html_options.dup
59
+ is_current_year = datetime.year == Time.current.year
60
+ if datetime > 4.days.ago
61
+ content = t 'thredded.time_ago', time: time_ago_in_words(datetime)
62
+ html_options['data-time-ago'] = true unless html_options.key?('data-time-ago')
63
+ else
64
+ content = I18n.l(datetime.to_date,
65
+ format: (is_current_year ? :short : :long))
66
+ end
67
+ html_options[:title] = I18n.l(datetime) unless html_options.key?(:title)
68
+ time_tag datetime, content, html_options
69
+ end
70
+
71
+ # @param posts [Thredded::PostsPageView, Array<Thredded::PostView>]
72
+ # @param partial [String]
73
+ # @param content_partial [String]
74
+ def render_posts(posts, partial: 'thredded/posts/post', content_partial: 'thredded/posts/content', locals: {})
75
+ posts_with_contents = render_collection_to_strings_with_cache(
76
+ partial: content_partial, collection: posts, as: :post, expires_in: 1.week
77
+ )
78
+ render partial: partial, collection: posts_with_contents, as: :post_and_content, locals: locals
79
+ end
80
+
81
+ def paginate(collection, args = {})
82
+ super(collection, args.reverse_merge(views_prefix: 'thredded'))
83
+ end
84
+
85
+ # @param topic [BaseTopicView]
86
+ # @return [Array<String>]
87
+ def topic_css_classes(topic)
88
+ [
89
+ *topic.states.map { |s| "thredded--topic-#{s}" },
90
+ *(topic.categories.map { |c| "thredded--topic-category-#{c.name}" } if topic.respond_to?(:categories)),
91
+ *('thredded--private-topic' if topic.is_a?(Thredded::PrivateTopicView))
92
+ ]
93
+ end
94
+
95
+ # @param follow_reason ['manual', 'posted', 'mentioned', 'auto', nil]
96
+ def topic_follow_reason_text(follow_reason)
97
+ if follow_reason
98
+ # rubocop:disable Metrics/LineLength
99
+ # i18n-tasks-use t('thredded.topics.following.manual') t('thredded.topics.following.posted') t('thredded.topics.following.mentioned') t('thredded.topics.following.auto')
100
+ # rubocop:enable Metrics/LineLength
101
+ t("thredded.topics.following.#{follow_reason}")
102
+ else
103
+ t('thredded.topics.not_following')
104
+ end
105
+ end
106
+
107
+ def unread_private_topics_count
108
+ @unread_private_topics_count ||=
109
+ if thredded_signed_in?
110
+ Thredded::PrivateTopic
111
+ .for_user(thredded_current_user)
112
+ .unread(thredded_current_user)
113
+ .count
114
+ else
115
+ 0
116
+ end
117
+ end
118
+
119
+ def moderatable_messageboards_ids
120
+ @moderatable_messageboards_ids ||=
121
+ thredded_current_user.thredded_can_moderate_messageboards.pluck(:id)
122
+ end
123
+
124
+ def posts_pending_moderation_count
125
+ @posts_pending_moderation_count ||=
126
+ Thredded::Post.where(messageboard_id: moderatable_messageboards_ids).pending_moderation.count
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ module Thredded
5
+ module NavHelper
6
+ USER_NAV_MODERATION_PAGES = Set.new(
7
+ %w[
8
+ thredded--pending-moderation
9
+ thredded--moderation-activity
10
+ thredded--moderation-history
11
+ thredded--moderation-users
12
+ thredded--moderation-user
13
+ ]
14
+ )
15
+
16
+ USER_NAV_PREFERENCES_PAGES = Set.new(
17
+ %w[
18
+ thredded--preferences
19
+ ]
20
+ )
21
+
22
+ USER_NAV_PRIVATE_TOPICS_PAGES = Set.new(
23
+ %w[
24
+ thredded--new-private-topic
25
+ thredded--private-topics-index
26
+ thredded--private-topic-show
27
+ ]
28
+ )
29
+
30
+ def current_page_preferences?
31
+ USER_NAV_PREFERENCES_PAGES.include?(content_for(:thredded_page_id))
32
+ end
33
+
34
+ def current_page_moderation?
35
+ USER_NAV_MODERATION_PAGES.include?(content_for(:thredded_page_id))
36
+ end
37
+
38
+ def current_page_private_topics?
39
+ USER_NAV_PRIVATE_TOPICS_PAGES.include?(content_for(:thredded_page_id))
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module RenderHelper
5
+ # @param collection [Array<T>]
6
+ # @param partial [String]
7
+ # @param expires_in [ActiveSupport::Duration]
8
+ # @return Array<[T, String]>
9
+ def render_collection_to_strings_with_cache(collection:, partial:, expires_in:, **opts)
10
+ Thredded::CollectionToStringsWithCacheRenderer.new(lookup_context).render_collection_to_strings_with_cache(
11
+ self, collection: collection, partial: partial, expires_in: expires_in, **opts
12
+ )
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module UrlsHelper
5
+ class << self
6
+ include Thredded::Engine.routes.url_helpers
7
+ include Thredded::UrlsHelper
8
+ end
9
+
10
+ # @param user [Thredded.user_class, Thredded::NullUser]
11
+ # @return [String] path to the user as specified by {Thredded.user_path}
12
+ def user_path(user)
13
+ Thredded.user_path(self, user)
14
+ end
15
+
16
+ # @param topic [Topic, PrivateTopic, UserTopicDecorator, UserPrivateTopicDecorator]
17
+ # @return [String]
18
+ def topic_url(topic, params = {})
19
+ if params[:page] == 1
20
+ params = params.dup
21
+ params.delete(:page)
22
+ end
23
+ if topic.private?
24
+ private_topic_url(
25
+ topic.slug,
26
+ params
27
+ )
28
+ else
29
+ messageboard_topic_url(
30
+ topic.messageboard.slug,
31
+ topic.slug,
32
+ params
33
+ )
34
+ end
35
+ end
36
+
37
+ # @param topic [Topic, PrivateTopic, UserTopicDecorator, UserPrivateTopicDecorator]
38
+ # @return [String] path to the latest unread page of the given topic.
39
+ def topic_path(topic, params = {})
40
+ topic_url(topic, params.merge(only_path: true))
41
+ end
42
+
43
+ # @param post [Post, PrivatePost]
44
+ # @param user [Thredded.user_class] the current user
45
+ # @return [String] URL of the topic page with the post anchor.
46
+ def post_url(post, user:, **params)
47
+ params = params.dup
48
+ params[:anchor] ||= ActionView::RecordIdentifier.dom_id(post)
49
+ params[:page] ||= post.private_topic_post? ? post.page : post.page(user: user)
50
+ topic_url(post.postable, params)
51
+ end
52
+
53
+ # @param post [Post, PrivatePost]
54
+ # @param user [Thredded.user_class] the current user
55
+ # @return [String] path to the topic page with the post anchor.
56
+ def post_path(post, user:, **params)
57
+ post_url(post, params.merge(user: user, only_path: true))
58
+ end
59
+
60
+ # @param post [Post, PrivatePost]
61
+ # @return [String] path to the Edit Post page.
62
+ def edit_post_path(post)
63
+ if post.private_topic_post?
64
+ edit_private_topic_private_post_path(post.postable, post)
65
+ else
66
+ edit_messageboard_topic_post_path(post.messageboard, post.postable, post)
67
+ end
68
+ end
69
+
70
+ # @param post [Post, PrivatePost]
71
+ # @return [String] path to the DELETE endpoint.
72
+ def delete_post_path(post)
73
+ if post.private_topic_post?
74
+ private_topic_private_post_path(post.postable, post)
75
+ else
76
+ messageboard_topic_post_path(post.messageboard, post.postable, post)
77
+ end
78
+ end
79
+
80
+ # @param messageboard [Thredded::Messageboard, nil]
81
+ # @param params [Hash] additional params
82
+ # @return [String] the URL to the global or messageboard edit preferences page.
83
+ def edit_preferences_url(messageboard = nil, params = {})
84
+ if messageboard.try(:persisted?)
85
+ edit_messageboard_preferences_url(messageboard, params)
86
+ else
87
+ edit_global_preferences_url(params)
88
+ end
89
+ end
90
+
91
+ # @param messageboard [Thredded::Messageboard, nil]
92
+ # @param params [Hash] additional params
93
+ # @return [String] the path to the global or messageboard edit preferences page.
94
+ def edit_preferences_path(messageboard = nil, params = {})
95
+ edit_preferences_url(messageboard, params.merge(only_path: true))
96
+ end
97
+
98
+ # @param messageboard [Thredded::Messageboard, nil]
99
+ # @return [String] the path to the global or messageboard search.
100
+ def search_path(messageboard = nil)
101
+ if messageboard.try(:persisted?)
102
+ messageboard_search_path(messageboard)
103
+ else
104
+ messageboards_search_path
105
+ end
106
+ end
107
+
108
+ def quote_post_path(post)
109
+ if post.private_topic_post?
110
+ quote_private_topic_private_post_path(post.postable, post)
111
+ else
112
+ quote_messageboard_topic_post_path(post.messageboard, post.postable, post)
113
+ end
114
+ end
115
+
116
+ def mark_unread_path(post, _params = {})
117
+ if post.private_topic_post?
118
+ mark_as_unread_private_topic_private_post_path(post.postable, post)
119
+ else
120
+ mark_as_unread_messageboard_topic_post_path(post.messageboard, post.postable, post)
121
+ end
122
+ end
123
+
124
+ # @param post [Post, PrivatePost]
125
+ # @return [String] post permalink path
126
+ def permalink_path(post)
127
+ if post.private_topic_post?
128
+ private_post_permalink_path(post)
129
+ else
130
+ post_permalink_path(post)
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class ActivityUpdaterJob < ::ActiveJob::Base
5
+ queue_as :default
6
+
7
+ def perform(user_id, messageboard_id)
8
+ now = Time.current
9
+
10
+ user_detail = Thredded::UserDetail.find_or_initialize_by(user_id: user_id)
11
+ user_detail.update!(last_seen_at: now)
12
+
13
+ Thredded::MessageboardUser
14
+ .find_or_initialize_by(
15
+ thredded_messageboard_id: messageboard_id,
16
+ thredded_user_detail_id: user_detail.id
17
+ )
18
+ .update!(last_seen_at: now)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class AutoFollowAndNotifyJob < ::ActiveJob::Base
5
+ queue_as :default
6
+
7
+ def perform(post_id)
8
+ post = Thredded::Post.find(post_id)
9
+
10
+ Thredded::AutofollowUsers.new(post).run
11
+ Thredded::NotifyFollowingUsers.new(post).run
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class NotifyPrivateTopicUsersJob < ::ActiveJob::Base
5
+ queue_as :default
6
+
7
+ def perform(private_post_id)
8
+ private_post = Thredded::PrivatePost.find(private_post_id)
9
+ NotifyPrivateTopicUsers.new(private_post).run
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # A base class for Thredded mailer previews.
5
+ # @abstract
6
+ class BaseMailerPreview
7
+ def self.preview_classes
8
+ RailsEmailPreview.find_preview_classes File.expand_path('..', File.dirname(__FILE__))
9
+ end
10
+
11
+ protected
12
+
13
+ def mock_content(mention_users: [])
14
+ <<-MARKDOWN
15
+ Hey #{mention_users.map { |u| "@#{u}" } * ', '}!
16
+ All of the basic [Markdown](https://kramdown.gettalong.org/quickref.html) formatting is supported (powered by [Kramdown](https://kramdown.gettalong.org)).
17
+
18
+ Additionally, Markdown is extended to support the following:
19
+
20
+ #{Thredded::FormattingDemoContent.parts.join("\n")}
21
+ MARKDOWN
22
+ end
23
+
24
+ def mock_topic(attr = {})
25
+ fail 'Do not assign ID here or a has_many association might get updated' if attr.key?(:id)
26
+ Thredded::Topic.new(
27
+ attr.reverse_merge(
28
+ title: 'A test topic',
29
+ slug: 'a-test-topic',
30
+ created_at: 3.days.ago,
31
+ last_user: mock_user,
32
+ locked: [false, true].sample,
33
+ messageboard: mock_messageboard,
34
+ posts_count: 1 + rand(42),
35
+ sticky: [false, true].sample,
36
+ updated_at: Time.zone.now,
37
+ user: mock_user,
38
+ )
39
+ )
40
+ end
41
+
42
+ def mock_post(attr = {})
43
+ topic = attr[:postable] || mock_topic
44
+ Thredded::Post.new(
45
+ attr.reverse_merge(
46
+ content: 'A test post',
47
+ created_at: Time.zone.now,
48
+ id: 1 + rand(1334),
49
+ messageboard: topic.messageboard,
50
+ postable: topic,
51
+ updated_at: Time.zone.now,
52
+ user: topic.last_user,
53
+ )
54
+ ).tap { |m| mock_post_cache_key! m }
55
+ end
56
+
57
+ def mock_private_topic(attr = {})
58
+ fail 'Do not assign ID here or a has_many association might get updated' if attr.key?(:id)
59
+ Thredded::PrivateTopic.new(
60
+ attr.reverse_merge(
61
+ title: 'A test private topic',
62
+ slug: 'a-test-private-topic',
63
+ created_at: 3.days.ago,
64
+ last_user: mock_user,
65
+ posts_count: 1 + rand(42),
66
+ updated_at: Time.zone.now,
67
+ user: mock_user,
68
+ )
69
+ )
70
+ end
71
+
72
+ def mock_private_post(attr = {})
73
+ private_topic = attr[:postable] || mock_private_topic
74
+ Thredded::PrivatePost.new(
75
+ attr.reverse_merge(
76
+ content: 'A test private post',
77
+ created_at: Time.zone.now,
78
+ id: 1 + rand(1334),
79
+ postable: private_topic,
80
+ updated_at: Time.zone.now,
81
+ user: private_topic.last_user,
82
+ )
83
+ ).tap { |m| mock_post_cache_key! m }
84
+ end
85
+
86
+ def mock_messageboard(attr = {})
87
+ fail 'Do not assign ID here or a has_many association might get updated' if attr.key?(:id)
88
+ Thredded::Messageboard.new(
89
+ attr.reverse_merge(
90
+ name: 'A test messageboard',
91
+ slug: 'a-test-messageboard',
92
+ description: 'Test messageboard description',
93
+ created_at: 1.month.ago,
94
+ posts_count: rand(1337),
95
+ topics_count: rand(42),
96
+ updated_at: Time.zone.now,
97
+ )
98
+ )
99
+ end
100
+
101
+ def mock_user(attr = {})
102
+ name = %w[Alice Bob].sample
103
+ Thredded.user_class.new(
104
+ attr.reverse_merge(
105
+ Thredded.user_name_column => name,
106
+ email: "#{name.downcase}@test.com",
107
+ )
108
+ )
109
+ end
110
+
111
+ def mock_post_cache_key!(post)
112
+ orig_key = post.cache_key
113
+ post.define_singleton_method :cache_key do
114
+ orig_key.sub(/new$/, "preview-#{Digest.hexencode(Digest::SHA2.new.digest(content))}")
115
+ end
116
+ end
117
+ end
118
+ end