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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # Previews for the PostMailer
5
+ class PostMailerPreview < BaseMailerPreview
6
+ def post_notification
7
+ PostMailer.post_notification(
8
+ mock_post(content: mock_content(mention_users: %w[glebm joel])),
9
+ %w[glebm@test.com joel@test.com]
10
+ )
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # Previews for the PrivateTopicMailer
5
+ class PrivateTopicMailerPreview < BaseMailerPreview
6
+ def message_notification
7
+ PrivateTopicMailer.message_notification(
8
+ mock_private_post(content: mock_content(mention_users: ['glebm'])),
9
+ %w[glebm@test.com joel@test.com]
10
+ )
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class BaseMailer < Thredded.parent_mailer.constantize
5
+ helper ::Thredded::UrlsHelper
6
+
7
+ protected
8
+
9
+ # Find a record by ID, or return the passed record.
10
+ # @param [Class<ActiveRecord::Base>] klass
11
+ # @param [Integer, String, klass] id_or_record
12
+ # @return [klass]
13
+ def find_record(klass, id_or_record)
14
+ # Check by name because in development the Class might have been reloaded after id was initialized
15
+ id_or_record.class.name == klass.name ? id_or_record : klass.find(id_or_record)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class PostMailer < Thredded::BaseMailer
5
+ def post_notification(post_id, emails)
6
+ @post = find_record Thredded::Post, post_id
7
+ email_details = Thredded::TopicEmailView.new(@post.postable)
8
+ headers['X-SMTPAPI'] = email_details.smtp_api_tag('post_notification')
9
+
10
+ mail from: email_details.no_reply,
11
+ to: email_details.no_reply,
12
+ bcc: emails,
13
+ subject: [
14
+ Thredded.email_outgoing_prefix,
15
+ t('thredded.emails.post_notification.subject',
16
+ user: @post.user.thredded_display_name,
17
+ topic_title: @post.postable.title)
18
+ ].compact.join
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class PrivateTopicMailer < Thredded::BaseMailer
5
+ def message_notification(post_id, emails)
6
+ @post = find_record Thredded::PrivatePost, post_id
7
+ email_details = Thredded::TopicEmailView.new(@post.postable)
8
+ headers['X-SMTPAPI'] = email_details.smtp_api_tag('private_topic_mailer')
9
+
10
+ mail from: email_details.no_reply,
11
+ to: email_details.no_reply,
12
+ bcc: emails,
13
+ subject: [
14
+ Thredded.email_outgoing_prefix,
15
+ t('thredded.emails.message_notification.subject',
16
+ user: @post.user.thredded_display_name,
17
+ topic_title: @post.postable.title)
18
+ ].compact.join
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # Moderation state of a piece of content, such as a Thredded::Topic or a Thredded::Post.
5
+ # Requires an integer moderation_state column, a user_id column, and a user_detail association on the including class.
6
+ # @api private
7
+ module ContentModerationState
8
+ extend ActiveSupport::Concern
9
+ include Thredded::ModerationState
10
+
11
+ included do
12
+ before_validation :set_default_moderation_state, on: :create
13
+
14
+ scope :moderation_state_visible_to_all, -> { where(visible_to_all_arel_node) }
15
+
16
+ scope :moderation_state_visible_to_user, (lambda do |user|
17
+ visible = visible_to_all_arel_node
18
+ # @type [Arel::Table]
19
+ table = arel_table
20
+ if user && !user.thredded_anonymous?
21
+ # Own content
22
+ visible = visible.or(table[:user_id].eq(user.id))
23
+ # Content that one can moderate
24
+ moderatable_messageboard_ids = user.thredded_can_moderate_messageboards.map(&:id)
25
+ if moderatable_messageboard_ids.present?
26
+ visible = visible.or(table[:messageboard_id].in(moderatable_messageboard_ids))
27
+ end
28
+ end
29
+ where(visible)
30
+ end)
31
+
32
+ # @return [Arel::Nodes::Node]
33
+ # @api private
34
+ def self.visible_to_all_arel_node
35
+ if Thredded.content_visible_while_pending_moderation
36
+ # All non-blocked content
37
+ arel_table[:moderation_state].not_eq(moderation_states[:blocked])
38
+ else
39
+ # Only approved content
40
+ arel_table[:moderation_state].eq(moderation_states[:approved])
41
+ end
42
+ end
43
+ end
44
+
45
+ # Whether this is visible to anyone based on the moderation state.
46
+ def moderation_state_visible_to_all?
47
+ if Thredded.content_visible_while_pending_moderation
48
+ !blocked?
49
+ else
50
+ approved?
51
+ end
52
+ end
53
+
54
+ # Whether this is visible to the given user based on the moderation state.
55
+ def moderation_state_visible_to_user?(user)
56
+ moderation_state_visible_to_all? ||
57
+ (!user.thredded_anonymous? &&
58
+ (user_id == user.id || user.thredded_can_moderate_messageboards.map(&:id).include?(messageboard_id)))
59
+ end
60
+
61
+ private
62
+
63
+ def set_default_moderation_state
64
+ self.moderation_state ||= user_detail.moderation_state
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ module Thredded
5
+ # Excludes pagination routes in addition to the given list of reserved words.
6
+ class FriendlyIdReservedWordsAndPagination
7
+ PAGINATION_PATTERN = /\Apage-\d+\z/i
8
+
9
+ def initialize(words = [])
10
+ @words = Set.new(words)
11
+ end
12
+
13
+ def include?(slug)
14
+ @words.include?(slug) || slug =~ PAGINATION_PATTERN
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # Defines a moderation_state enum
5
+ # Requires an integer moderation_state column on the including class.
6
+ module ModerationState
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ enum moderation_state: %i[pending_moderation approved blocked]
11
+ validates :moderation_state, presence: true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module NotifierPreference
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ delegate :human_name, to: :notifier, prefix: true
9
+
10
+ def self.detect_or_default(prefs, notifier)
11
+ (prefs && prefs.find { |pref| pref.notifier_key == notifier.key }) || default(notifier)
12
+ end
13
+ end
14
+
15
+ def notifier
16
+ @notifier ||= Thredded.notifiers.find { |notifier| notifier.key == notifier_key }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ # @abstract Classes that include this module are expected to implement {#readers}.
5
+ # @!method readers
6
+ # @abstract
7
+ # @return [ActiveRecord::Relation<Thredded.user_class>] users from that can read this post.
8
+ module PostCommon
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ paginates_per 50
13
+
14
+ delegate :email, to: :user, prefix: true, allow_nil: true
15
+
16
+ validates :content, presence: true
17
+
18
+ scope :order_oldest_first, -> { order(created_at: :asc, id: :asc) }
19
+ scope :order_newest_first, -> { order(created_at: :desc, id: :desc) }
20
+
21
+ before_validation :ensure_user_detail, on: :create
22
+ end
23
+
24
+ def avatar_url
25
+ Thredded.avatar_url.call(user)
26
+ end
27
+
28
+ def calculate_page(postable_posts, per_page)
29
+ 1 + postable_posts.where(postable_posts.arel_table[:created_at].lt(created_at)).count / per_page
30
+ end
31
+
32
+ # @param view_context [Object] the context of the rendering view.
33
+ # @return [String] formatted and sanitized html-safe post content.
34
+ def filtered_content(view_context, users_provider: ->(names) { readers_from_user_names(names) }, **options)
35
+ Thredded::ContentFormatter.new(view_context, users_provider: users_provider, **options).format_content(content)
36
+ end
37
+
38
+ def first_post_in_topic?
39
+ postable.first_post == self
40
+ end
41
+
42
+ # @return [ActiveRecord::Relation<Thredded.user_class>] users from the list of user names that can read this post.
43
+ # @api private
44
+ def readers_from_user_names(user_names)
45
+ DbTextSearch::CaseInsensitive
46
+ .new(readers, Thredded.user_name_column)
47
+ .in(user_names)
48
+ end
49
+
50
+ # Marks all the posts from the given one as unread for the given user
51
+ # @param user [Thredded.user_class]
52
+ # @param page [Integer]
53
+ def mark_as_unread(user, page)
54
+ if previous_post.nil?
55
+ read_state = postable.user_read_states.find_by(user_id: user.id)
56
+ read_state.destroy if read_state
57
+ else
58
+ read_state = postable.user_read_states.create_with(
59
+ read_at: previous_post.created_at,
60
+ page: page
61
+ ).find_or_create_by(user_id: user.id)
62
+ read_state.update_columns(read_at: previous_post.created_at, page: page)
63
+ end
64
+ end
65
+
66
+ def previous_post
67
+ @previous_post ||= postable.posts.order_newest_first.find_by('created_at < ?', created_at)
68
+ end
69
+
70
+ private
71
+
72
+ def ensure_user_detail
73
+ build_user_detail if user && !user_detail
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class SearchParser
5
+ def initialize(query)
6
+ @query = query
7
+ @keywords = %w[in by order]
8
+ end
9
+
10
+ def parse
11
+ parsed_input = parse_keywords
12
+ parsed_input['text'] = parse_text
13
+ parsed_input
14
+ end
15
+
16
+ def parse_keywords
17
+ found_terms_hash = {}
18
+
19
+ @keywords.each do |keyword|
20
+ regex = Regexp.new(keyword + '\s*:\s*\w+', Regexp::IGNORECASE)
21
+ keyword_scan = @query.scan(regex)
22
+ @query = @query.gsub(regex, '')
23
+
24
+ next if keyword_scan.blank?
25
+ keyword_scan.each do |term|
26
+ found_terms_hash[keyword] ||= []
27
+ found_terms_hash[keyword] << term.delete(' ').split(':')[1]
28
+ end
29
+ end
30
+
31
+ found_terms_hash
32
+ end
33
+
34
+ def parse_text
35
+ regex = Regexp.new('\"[^"]*\"')
36
+ found_terms = @query.scan(regex)
37
+ @query = @query.sub(regex, '')
38
+ found_terms.concat(@query.split(/\s+/))
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ module TopicCommon
5
+ extend ActiveSupport::Concern
6
+ included do
7
+ paginates_per 50 if respond_to?(:paginates_per)
8
+
9
+ belongs_to :last_user,
10
+ class_name: Thredded.user_class_name,
11
+ foreign_key: 'last_user_id',
12
+ **(Thredded.rails_gte_51? ? { optional: true } : {})
13
+
14
+ scope :order_recently_posted_first, -> { order(last_post_at: :desc, id: :desc) }
15
+ scope :on_page, ->(page_num) { page(page_num) }
16
+
17
+ validates :hash_id, presence: true, uniqueness: true
18
+ validates :posts_count, numericality: true
19
+
20
+ before_validation do
21
+ self.hash_id = SecureRandom.hex(10) if hash_id.nil?
22
+ end
23
+
24
+ delegate :name, :name=, :email, :email=, to: :user, prefix: true
25
+
26
+ before_validation :ensure_user_detail, on: :create
27
+ end
28
+
29
+ def user
30
+ super || Thredded::NullUser.new
31
+ end
32
+
33
+ def last_user
34
+ super || Thredded::NullUser.new
35
+ end
36
+
37
+ def private?
38
+ !public?
39
+ end
40
+
41
+ private
42
+
43
+ def ensure_user_detail
44
+ build_user_detail if user && !user_detail
45
+ end
46
+
47
+ module ClassMethods
48
+ # @param user [Thredded.user_class]
49
+ # @return [ActiveRecord::Relation]
50
+ def unread(user)
51
+ topics = arel_table
52
+ reads_class = reflect_on_association(:user_read_states).klass
53
+ reads = reads_class.arel_table
54
+ joins(topics.join(reads, Arel::Nodes::OuterJoin)
55
+ .on(topics[:id].eq(reads[:postable_id]).and(reads[:user_id].eq(user.id))).join_sources)
56
+ .merge(reads_class.where(reads[:id].eq(nil).or(reads[:read_at].lt(topics[:last_post_at]))))
57
+ end
58
+
59
+ private
60
+
61
+ # @param user [Thredded.user_class]
62
+ # @return [ByPostableLookup]
63
+ def read_states_by_postable_hash(user)
64
+ read_states = reflect_on_association(:user_read_states).klass
65
+ .where(user_id: user.id, postable_id: current_scope.map(&:id))
66
+ Thredded::TopicCommon::CachingHash.from_relation(read_states)
67
+ end
68
+
69
+ public
70
+
71
+ # @param user [Thredded.user_class]
72
+ # @return [Array<[TopicCommon, UserTopicReadStateCommon]>]
73
+ def with_read_states(user)
74
+ null_read_state = Thredded::NullUserTopicReadState.new
75
+ return current_scope.zip([null_read_state]) if user.thredded_anonymous?
76
+ read_states_by_postable = read_states_by_postable_hash(user)
77
+ current_scope.map do |postable|
78
+ [postable, read_states_by_postable[postable] || null_read_state]
79
+ end
80
+ end
81
+ end
82
+
83
+ class CachingHash < Hash
84
+ def self.from_relation(postable_relation)
85
+ self[postable_relation.map { |related| [related.postable_id, related] }]
86
+ end
87
+
88
+ # lookup related item by postable and set the inverse lookup
89
+ def [](postable)
90
+ super(postable.id).tap { |related| related.postable = postable if related }
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Thredded
4
+ class TopicsSearch
5
+ def initialize(query, scope)
6
+ @terms = Thredded::SearchParser.new(query).parse
7
+ @scope = scope
8
+
9
+ @search_categories = @search_users = @search_text = nil
10
+ end
11
+
12
+ # @return [ActiveRecord::Relation<Thredded::Topic>]
13
+ def search
14
+ if categories.present?
15
+ @scope = @scope.joins(:topic_categories).merge(Thredded::TopicCategory.where(category_id: categories))
16
+ end
17
+ if text.present? || users.present?
18
+ [search_topics, search_posts].compact.reduce(:union)
19
+ else
20
+ @scope
21
+ end
22
+ end
23
+
24
+ protected
25
+
26
+ def search_topics
27
+ scope = @scope
28
+ scope = @scope.where(user_id: users) if users.present?
29
+ scope = DbTextSearch::FullText.new(scope, :title).search(text) if text.present?
30
+ scope
31
+ end
32
+
33
+ def search_posts
34
+ posts_scope = Thredded::Post
35
+ posts_scope = posts_scope.where(user_id: users) if users.present?
36
+ posts_scope = DbTextSearch::FullText.new(posts_scope, :content).search(text) if text.present?
37
+ @scope.joins(:posts).merge(posts_scope)
38
+ end
39
+
40
+ def categories
41
+ @search_categories ||=
42
+ if @terms['in'].present?
43
+ DbTextSearch::CaseInsensitive
44
+ .new(Category, :name)
45
+ .in(@terms['in']).pluck(:id)
46
+ else
47
+ []
48
+ end
49
+ end
50
+
51
+ def users
52
+ @search_users ||=
53
+ if @terms['by']
54
+ DbTextSearch::CaseInsensitive
55
+ .new(Thredded.user_class, Thredded.user_name_column)
56
+ .in(@terms['by']).pluck(:id)
57
+ else
58
+ []
59
+ end
60
+ end
61
+
62
+ # @return [Array<String>]
63
+ def text
64
+ @terms['text']
65
+ end
66
+ end
67
+ end