thredded 0.2.2 → 0.3.0

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 (220) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.mkdn +63 -0
  3. data/Procfile +1 -0
  4. data/README.mkdn +42 -20
  5. data/app/assets/images/thredded/private-messages.svg +4 -0
  6. data/app/assets/images/thredded/settings.svg +4 -0
  7. data/app/assets/javascripts/thredded.es6 +2 -10
  8. data/app/assets/javascripts/thredded/{currently_online.es6 → components/currently_online.es6} +0 -0
  9. data/app/assets/javascripts/thredded/{post_form.es6 → components/post_form.es6} +0 -0
  10. data/app/assets/javascripts/thredded/{time_stamps.es6 → components/time_stamps.es6} +0 -0
  11. data/app/assets/javascripts/thredded/{topic_form.es6 → components/topic_form.es6} +1 -1
  12. data/app/assets/javascripts/thredded/components/topics.es6 +37 -0
  13. data/app/assets/javascripts/thredded/components/user_preferences_form.es6 +45 -0
  14. data/app/assets/javascripts/thredded/components/users_select.es6 +56 -0
  15. data/app/assets/javascripts/thredded/dependencies.js +9 -0
  16. data/app/assets/javascripts/thredded/thredded.es6 +1 -0
  17. data/app/assets/stylesheets/thredded/_base.scss +3 -2
  18. data/app/assets/stylesheets/thredded/_thredded.scss +4 -1
  19. data/app/assets/stylesheets/thredded/base/_buttons.scss +2 -1
  20. data/app/assets/stylesheets/thredded/base/_forms.scss +23 -18
  21. data/app/assets/stylesheets/thredded/base/_grid.scss +1 -1
  22. data/app/assets/stylesheets/thredded/base/_nav.scss +21 -0
  23. data/app/assets/stylesheets/thredded/base/_tables.scss +5 -14
  24. data/app/assets/stylesheets/thredded/base/_typography.scss +9 -4
  25. data/app/assets/stylesheets/thredded/base/_variables.scss +28 -9
  26. data/app/assets/stylesheets/thredded/components/_alerts.scss +19 -0
  27. data/app/assets/stylesheets/thredded/components/_currently-online.scss +1 -1
  28. data/app/assets/stylesheets/thredded/components/_form-list.scss +2 -4
  29. data/app/assets/stylesheets/thredded/components/_icons.scss +3 -0
  30. data/app/assets/stylesheets/thredded/components/_messageboard.scss +4 -4
  31. data/app/assets/stylesheets/thredded/components/_pagination.scss +2 -2
  32. data/app/assets/stylesheets/thredded/components/_post-form.scss +3 -0
  33. data/app/assets/stylesheets/thredded/components/_post.scss +14 -4
  34. data/app/assets/stylesheets/thredded/components/_select2.scss +79 -9
  35. data/app/assets/stylesheets/thredded/components/_topic-header.scss +11 -1
  36. data/app/assets/stylesheets/thredded/components/_topics.scss +13 -11
  37. data/app/assets/stylesheets/thredded/layout/_main-container.scss +3 -3
  38. data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +11 -17
  39. data/app/assets/stylesheets/thredded/layout/_navigation.scss +72 -0
  40. data/app/assets/stylesheets/thredded/layout/_search-navigation.scss +66 -0
  41. data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +35 -61
  42. data/app/commands/thredded/at_notification_extractor.rb +1 -0
  43. data/app/commands/thredded/members_marked_notified.rb +1 -0
  44. data/app/commands/thredded/messageboard_destroyer.rb +7 -2
  45. data/app/commands/thredded/notify_mentioned_users.rb +8 -21
  46. data/app/commands/thredded/notify_private_topic_users.rb +3 -5
  47. data/app/controllers/thredded/application_controller.rb +76 -41
  48. data/app/controllers/thredded/autocomplete_users_controller.rb +46 -0
  49. data/app/controllers/thredded/messageboards_controller.rb +8 -5
  50. data/app/controllers/thredded/posts_controller.rb +20 -22
  51. data/app/controllers/thredded/preferences_controller.rb +19 -14
  52. data/app/controllers/thredded/private_topics_controller.rb +58 -23
  53. data/app/controllers/thredded/setups_controller.rb +1 -0
  54. data/app/controllers/thredded/theme_previews_controller.rb +24 -53
  55. data/app/controllers/thredded/topics_controller.rb +48 -77
  56. data/app/forms/thredded/private_topic_form.rb +1 -21
  57. data/app/forms/thredded/topic_form.rb +3 -7
  58. data/app/forms/thredded/user_preferences_form.rb +62 -0
  59. data/app/helpers/thredded/application_helper.rb +11 -12
  60. data/app/helpers/thredded/urls_helper.rb +103 -0
  61. data/app/jobs/thredded/activity_updater_job.rb +4 -3
  62. data/app/jobs/thredded/at_notifier_job.rb +1 -0
  63. data/app/jobs/thredded/notify_private_topic_users_job.rb +1 -0
  64. data/app/mailer_previews/thredded/base_mailer_preview.rb +101 -0
  65. data/app/mailer_previews/thredded/post_mailer_preview.rb +11 -0
  66. data/app/mailer_previews/thredded/private_post_mailer_preview.rb +11 -0
  67. data/app/mailer_previews/thredded/private_topic_mailer_preview.rb +15 -0
  68. data/app/mailers/thredded/base_mailer.rb +13 -0
  69. data/app/mailers/thredded/post_mailer.rb +4 -2
  70. data/app/mailers/thredded/private_post_mailer.rb +4 -2
  71. data/app/mailers/thredded/private_topic_mailer.rb +4 -2
  72. data/app/models/concerns/thredded/friendly_id_reserved_words_and_pagination.rb +16 -0
  73. data/app/models/concerns/thredded/post_common.rb +68 -63
  74. data/app/models/concerns/thredded/topic_common.rb +31 -8
  75. data/app/models/concerns/thredded/user_topic_read_state_common.rb +31 -0
  76. data/app/models/thredded/category.rb +1 -0
  77. data/app/models/thredded/messageboard.rb +24 -25
  78. data/app/models/thredded/messageboard_user.rb +1 -0
  79. data/app/models/thredded/null_preference.rb +1 -0
  80. data/app/models/thredded/null_user.rb +1 -6
  81. data/app/models/thredded/null_user_topic_read_state.rb +12 -0
  82. data/app/models/thredded/post.rb +6 -9
  83. data/app/models/thredded/post_notification.rb +1 -0
  84. data/app/models/thredded/private_post.rb +6 -2
  85. data/app/models/thredded/private_topic.rb +46 -32
  86. data/app/models/thredded/private_user.rb +3 -2
  87. data/app/models/thredded/stats.rb +1 -0
  88. data/app/models/thredded/topic.rb +40 -64
  89. data/app/models/thredded/topic_category.rb +1 -0
  90. data/app/models/thredded/user_detail.rb +2 -15
  91. data/app/models/thredded/user_extender.rb +29 -14
  92. data/app/models/thredded/user_messageboard_preference.rb +20 -0
  93. data/app/models/thredded/user_permissions/admin/if_admin_column_true.rb +1 -0
  94. data/app/models/thredded/user_permissions/admin/none.rb +1 -0
  95. data/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb +1 -0
  96. data/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb +1 -0
  97. data/app/models/thredded/user_permissions/moderate/none.rb +1 -0
  98. data/app/models/thredded/user_permissions/read/all.rb +1 -0
  99. data/app/models/thredded/user_permissions/write/all.rb +1 -0
  100. data/app/models/thredded/user_permissions/write/none.rb +1 -0
  101. data/app/models/thredded/user_preference.rb +7 -1
  102. data/app/models/thredded/user_private_topic_read_state.rb +12 -0
  103. data/app/models/thredded/user_topic_read_state.rb +12 -0
  104. data/app/policies/thredded/messageboard_policy.rb +27 -0
  105. data/app/policies/thredded/post_policy.rb +33 -0
  106. data/app/policies/thredded/private_post_policy.rb +29 -0
  107. data/app/policies/thredded/private_topic_policy.rb +23 -0
  108. data/app/policies/thredded/topic_policy.rb +32 -0
  109. data/app/view_models/thredded/base_topic_view.rb +56 -0
  110. data/app/view_models/thredded/post_view.rb +44 -0
  111. data/app/view_models/thredded/posts_page_view.rb +27 -0
  112. data/app/view_models/thredded/private_topic_view.rb +9 -0
  113. data/app/{decorators/thredded/topic_email_decorator.rb → view_models/thredded/topic_email_view.rb} +2 -1
  114. data/app/view_models/thredded/topic_view.rb +23 -0
  115. data/app/view_models/thredded/topics_page_view.rb +26 -0
  116. data/app/views/thredded/error_pages/forbidden.html.erb +6 -0
  117. data/app/views/thredded/error_pages/not_found.html.erb +6 -0
  118. data/app/views/thredded/messageboards/_messageboard.html.erb +13 -6
  119. data/app/views/thredded/messageboards/index.html.erb +2 -8
  120. data/app/views/thredded/messageboards/new.html.erb +8 -2
  121. data/app/views/thredded/post_mailer/at_notification.html.erb +3 -3
  122. data/app/views/thredded/post_mailer/at_notification.text.erb +1 -1
  123. data/app/views/thredded/posts/_content_field.html.erb +1 -1
  124. data/app/views/thredded/posts/_form.html.erb +5 -1
  125. data/app/views/thredded/posts/_post.html.erb +3 -1
  126. data/app/views/thredded/posts/edit.html.erb +7 -3
  127. data/app/views/thredded/posts_common/_form.html.erb +1 -1
  128. data/app/views/thredded/posts_common/_post.html.erb +14 -8
  129. data/app/views/thredded/preferences/_form.html.erb +37 -15
  130. data/app/views/thredded/preferences/_header.html.erb +1 -1
  131. data/app/views/thredded/preferences/edit.html.erb +4 -6
  132. data/app/views/thredded/private_post_mailer/at_notification.html.erb +6 -4
  133. data/app/views/thredded/private_posts/_form.html.erb +5 -1
  134. data/app/views/thredded/private_posts/_private_post.html.erb +3 -1
  135. data/app/views/thredded/private_topic_mailer/message_notification.html.erb +3 -7
  136. data/app/views/thredded/private_topic_mailer/message_notification.text.erb +1 -3
  137. data/app/views/thredded/private_topics/_breadcrumbs.html.erb +2 -2
  138. data/app/views/thredded/private_topics/_form.html.erb +15 -10
  139. data/app/views/thredded/private_topics/_header.html.erb +12 -0
  140. data/app/views/thredded/private_topics/_no_private_topics.html.erb +2 -2
  141. data/app/views/thredded/private_topics/_private_topic.html.erb +4 -6
  142. data/app/views/thredded/private_topics/edit.html.erb +32 -0
  143. data/app/views/thredded/private_topics/index.html.erb +5 -5
  144. data/app/views/thredded/private_topics/new.html.erb +1 -2
  145. data/app/views/thredded/private_topics/show.html.erb +12 -7
  146. data/app/views/thredded/search/_form.html.erb +9 -6
  147. data/app/views/thredded/shared/{_messageboard_topics_breadcrumbs.html.erb → _breadcrumbs.html.erb} +2 -2
  148. data/app/views/thredded/shared/_header.html.erb +2 -3
  149. data/app/views/thredded/shared/_nav.html.erb +20 -0
  150. data/app/views/thredded/shared/nav/_notification_preferences.html.erb +6 -0
  151. data/app/views/thredded/shared/nav/_private_topics.html.erb +11 -0
  152. data/app/views/thredded/shared/nav/_standalone.html.erb +12 -0
  153. data/app/views/thredded/theme_previews/_section_title.html.erb +2 -2
  154. data/app/views/thredded/theme_previews/show.html.erb +13 -17
  155. data/app/views/thredded/topics/_form.html.erb +8 -6
  156. data/app/views/thredded/topics/_header.html.erb +12 -0
  157. data/app/views/thredded/topics/_topic.html.erb +4 -8
  158. data/app/views/thredded/topics/_topic_form_admin_options.html.erb +1 -1
  159. data/app/views/thredded/topics/edit.html.erb +22 -18
  160. data/app/views/thredded/topics/index.html.erb +3 -3
  161. data/app/views/thredded/topics/new.html.erb +1 -1
  162. data/app/views/thredded/topics/search.html.erb +17 -5
  163. data/app/views/thredded/topics/show.html.erb +14 -11
  164. data/bin/rails +5 -0
  165. data/config.ru +3 -0
  166. data/config/i18n-tasks.yml +16 -0
  167. data/config/locales/en.yml +90 -0
  168. data/config/routes.rb +29 -15
  169. data/db/migrate/20160329231848_create_thredded.rb +29 -33
  170. data/db/seeds.rb +115 -0
  171. data/db/upgrade_migrations/20160410111522_upgrade_v0_2_to_v0_3.rb +59 -0
  172. data/heroku.gemfile +26 -0
  173. data/heroku.gemfile.lock +282 -0
  174. data/lib/generators/thredded/install/install_generator.rb +1 -0
  175. data/lib/generators/thredded/install/templates/initializer.rb +17 -0
  176. data/lib/html/pipeline/at_mention_filter.rb +2 -1
  177. data/lib/html/pipeline/bbcode_filter.rb +13 -4
  178. data/lib/tasks/thredded_tasks.rake +1 -0
  179. data/lib/thredded.rb +19 -17
  180. data/lib/thredded/at_users.rb +1 -0
  181. data/lib/thredded/engine.rb +14 -5
  182. data/lib/thredded/errors.rb +11 -11
  183. data/lib/thredded/main_app_route_delegator.rb +1 -0
  184. data/lib/thredded/search_parser.rb +2 -1
  185. data/lib/thredded/topics_search.rb +67 -0
  186. data/lib/thredded/version.rb +2 -1
  187. data/thredded.gemspec +12 -8
  188. metadata +146 -82
  189. data/app/assets/javascripts/thredded/users_select.es6 +0 -5
  190. data/app/assets/stylesheets/thredded/layout/_topic-navigation.scss +0 -53
  191. data/app/commands/thredded/user_reads_private_topic.rb +0 -22
  192. data/app/commands/thredded/user_resets_private_topic_to_unread.rb +0 -23
  193. data/app/decorators/thredded/base_topic_decorator.rb +0 -14
  194. data/app/decorators/thredded/base_user_topic_decorator.rb +0 -63
  195. data/app/decorators/thredded/messageboard_decorator.rb +0 -41
  196. data/app/decorators/thredded/post_decorator.rb +0 -40
  197. data/app/decorators/thredded/private_topic_decorator.rb +0 -23
  198. data/app/decorators/thredded/topic_decorator.rb +0 -25
  199. data/app/decorators/thredded/user_private_topic_decorator.rb +0 -13
  200. data/app/decorators/thredded/user_topic_decorator.rb +0 -37
  201. data/app/models/thredded/ability.rb +0 -60
  202. data/app/models/thredded/notification_preference.rb +0 -17
  203. data/app/models/thredded/null_topic.rb +0 -15
  204. data/app/models/thredded/null_topic_read.rb +0 -19
  205. data/app/models/thredded/user_topic_read.rb +0 -10
  206. data/app/views/thredded/shared/_notification_preferences.html.erb +0 -7
  207. data/app/views/thredded/shared/_top_nav.html.erb +0 -36
  208. data/app/views/thredded/shared/_topic_nav.html.erb +0 -22
  209. data/app/views/thredded/topics/_recent_topics_by_user.html.erb +0 -8
  210. data/app/views/thredded/topics/by_category.html.erb +0 -56
  211. data/app/views/thredded/topics_common/_header.html.erb +0 -6
  212. data/lib/thredded/messageboard_user_permissions.rb +0 -22
  213. data/lib/thredded/post_sql_builder.rb +0 -12
  214. data/lib/thredded/post_user_permissions.rb +0 -32
  215. data/lib/thredded/private_topic_user_permissions.rb +0 -26
  216. data/lib/thredded/search_sql_builder.rb +0 -21
  217. data/lib/thredded/seed_database.rb +0 -76
  218. data/lib/thredded/table_sql_builder.rb +0 -41
  219. data/lib/thredded/topic_sql_builder.rb +0 -11
  220. data/lib/thredded/topic_user_permissions.rb +0 -32
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class UserPreferencesForm
4
+ include ActiveModel::Model
5
+
6
+ # @return [Thredded::Messageboard, nil]
7
+ attr_reader :messageboard
8
+
9
+ validate :validate_children
10
+
11
+ delegate :notify_on_mention, :notify_on_mention=,
12
+ :notify_on_message, :notify_on_message=,
13
+ to: :user_preference
14
+
15
+ delegate :notify_on_mention, :notify_on_mention=,
16
+ to: :user_messageboard_preference,
17
+ prefix: :messageboard
18
+
19
+ # @param user [Thredded.user_class]
20
+ # @param messageboard [Thredded::Messageboard, nil]
21
+ def initialize(user:, messageboard: nil, params: {})
22
+ @user = user
23
+ @messageboard = messageboard
24
+ super(params)
25
+ end
26
+
27
+ # @return [Boolean]
28
+ def save
29
+ return false unless valid?
30
+ Thredded::UserPreference.transaction do
31
+ user_preference.save!
32
+ user_messageboard_preference.save! if messageboard
33
+ end
34
+ true
35
+ end
36
+
37
+ private
38
+
39
+ # @return [Thredded::UserPreference]
40
+ def user_preference
41
+ @user_preference ||= @user.thredded_user_preference
42
+ end
43
+
44
+ # @return [Thredded::UserMessageboardPreference, nil]
45
+ def user_messageboard_preference
46
+ return nil unless @messageboard
47
+ @user_messageboard_preference ||=
48
+ user_preference.messageboard_preferences.where(messageboard_id: @messageboard.id).first_or_initialize
49
+ end
50
+
51
+ def validate_children
52
+ promote_errors(user_preference.errors) if user_preference.invalid?
53
+ promote_errors(user_messageboard_preference.errors, :messageboard) if messageboard && user_messageboard_preference.invalid?
54
+ end
55
+
56
+ def promote_errors(child_errors, prefix = nil)
57
+ child_errors.each do |attribute, message|
58
+ errors.add([prefix, attribute].compact.join('_'), message)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  module Thredded
2
3
  module ApplicationHelper
4
+ include ::Thredded::UrlsHelper
5
+
3
6
  # Render the page container with the supplied block as content.
4
7
  def thredded_page(&block)
5
8
  # enable the host app to easily check whether a thredded view is being rendered:
@@ -8,12 +11,6 @@ module Thredded
8
11
  render partial: 'thredded/shared/page'
9
12
  end
10
13
 
11
- # @param user [Thredded.user_class, Thredded::NullUser]
12
- # @return [String] path to the user as specified by {Thredded.user_path}
13
- def user_path(user)
14
- Thredded.user_path(self, user)
15
- end
16
-
17
14
  # @param user [Thredded.user_class, Thredded::NullUser]
18
15
  # @return [String] html_safe link to the user
19
16
  def user_link(user)
@@ -30,12 +27,14 @@ module Thredded
30
27
  super(collection, args.reverse_merge(views_prefix: 'thredded'))
31
28
  end
32
29
 
33
- def edit_post_path(post)
34
- if post.private_topic_post?
35
- edit_private_topic_private_post_path(post.postable, post)
36
- else
37
- edit_messageboard_topic_post_path(messageboard, post.postable, post)
38
- end
30
+ # @param topic [BaseTopicView]
31
+ # @return [Array<String>]
32
+ def topic_css_classes(topic)
33
+ [
34
+ *topic.states.map { |s| "thredded--topic-#{s}" },
35
+ *(topic.categories.map { |c| "thredded--topic-category-#{c.name}" } if topic.respond_to?(:categories)),
36
+ *('thredded--private-topic' if topic.is_a?(Thredded::PrivateTopicView))
37
+ ]
39
38
  end
40
39
  end
41
40
  end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ module UrlsHelper
4
+ class << self
5
+ include Thredded::Engine.routes.url_helpers
6
+ include Thredded::UrlsHelper
7
+ end
8
+
9
+ # @param user [Thredded.user_class, Thredded::NullUser]
10
+ # @return [String] path to the user as specified by {Thredded.user_path}
11
+ def user_path(user)
12
+ Thredded.user_path(self, user)
13
+ end
14
+
15
+ # @param topic [Topic, PrivateTopic, UserTopicDecorator, UserPrivateTopicDecorator]
16
+ # @return [String]
17
+ def topic_url(topic, params = {})
18
+ if params[:page] == 1
19
+ params = params.dup
20
+ params.delete(:page)
21
+ end
22
+ if topic.private?
23
+ private_topic_url(
24
+ topic.slug,
25
+ params)
26
+ else
27
+ messageboard_topic_url(
28
+ topic.messageboard.slug,
29
+ topic.slug,
30
+ params)
31
+ end
32
+ end
33
+
34
+ # @param topic [Topic, PrivateTopic, UserTopicDecorator, UserPrivateTopicDecorator]
35
+ # @return [String] path to the latest unread page of the given topic.
36
+ def topic_path(topic, params = {})
37
+ topic_url(topic, params.merge(only_path: true))
38
+ end
39
+
40
+ # @param post [Post, PrivatePost]
41
+ # @return [String] URL of the topic page with the post anchor.
42
+ def post_url(post, params = {})
43
+ params = params.dup
44
+ params[:anchor] ||= dom_id(post)
45
+ params[:page] ||= post.page
46
+ topic_url(post.postable, params)
47
+ end
48
+
49
+ # @param post [Post, PrivatePost]
50
+ # @return [String] path to the topic page with the post anchor.
51
+ def post_path(post, params = {})
52
+ post_url(post, params.merge(only_path: true))
53
+ end
54
+
55
+ # @param post [Post, PrivatePost]
56
+ # @return [String] path to the Edit Post page.
57
+ def edit_post_path(post)
58
+ if post.private_topic_post?
59
+ edit_private_topic_private_post_path(post.postable, post)
60
+ else
61
+ edit_messageboard_topic_post_path(post.messageboard, post.postable, post)
62
+ end
63
+ end
64
+
65
+ # @param post [Post, PrivatePost]
66
+ # @return [String] path to the DELETE PATCH PUT POST endpoint.
67
+ def delete_post_path(post)
68
+ if post.private_topic_post?
69
+ private_topic_private_post_path(post.postable, post)
70
+ else
71
+ messageboard_topic_post_path(post.messageboard, post.postable, post)
72
+ end
73
+ end
74
+
75
+ # @param messageboard [Thredded::Messageboard, nil]
76
+ # @param params [Hash] additional params
77
+ # @return [String] the URL to the global or messageboard edit preferences page.
78
+ def edit_preferences_url(messageboard = nil, params = {})
79
+ if messageboard.try(:persisted?)
80
+ edit_messageboard_preferences_url(messageboard, params)
81
+ else
82
+ super(params)
83
+ end
84
+ end
85
+
86
+ # @param messageboard [Thredded::Messageboard, nil]
87
+ # @param params [Hash] additional params
88
+ # @return [String] the path to the global or messageboard edit preferences page.
89
+ def edit_preferences_path(messageboard = nil, params = {})
90
+ edit_preferences_url(messageboard, params.merge(only_path: true))
91
+ end
92
+
93
+ # @param messageboard [Thredded::Messageboard, nil]
94
+ # @return [String] the path to the global or messageboard search.
95
+ def search_path(messageboard = nil)
96
+ if messageboard.try(:persisted?)
97
+ messageboard_search_path(messageboard)
98
+ else
99
+ messageboards_search_path
100
+ end
101
+ end
102
+ end
103
+ end
@@ -1,12 +1,13 @@
1
+ # frozen_string_literal: true
1
2
  module Thredded
2
3
  class ActivityUpdaterJob < ::ActiveJob::Base
3
4
  queue_as :default
4
5
 
5
6
  def perform(user_id, messageboard_id)
6
- now = Time.zone.now
7
+ now = Time.current
7
8
 
8
- user_detail = Thredded::UserDetail.for_user_id(user_id)
9
- user_detail.update_column(:last_seen_at, now)
9
+ user_detail = Thredded::UserDetail.where(user_id: user_id).first_or_initialize
10
+ user_detail.update!(last_seen_at: now)
10
11
 
11
12
  Thredded::MessageboardUser
12
13
  .where(
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Thredded
2
3
  class AtNotifierJob < ::ActiveJob::Base
3
4
  queue_as :default
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Thredded
2
3
  class NotifyPrivateTopicUsersJob < ::ActiveJob::Base
3
4
  queue_as :default
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ # A base class for Thredded mailer previews.
4
+ # @abstract
5
+ class BaseMailerPreview
6
+ def self.preview_classes
7
+ RailsEmailPreview.find_preview_classes File.expand_path('..', File.dirname(__FILE__))
8
+ end
9
+
10
+ protected
11
+
12
+ def mock_content(mention_users: [])
13
+ <<-MARKDOWN
14
+ #{mention_users.map { |u| "@#{u}" } * ', '}, if we synthesize the driver, we can get to the HDD panel through the `1080p EXE` bus!
15
+ I'll program the **redundant** SMTP array, that should monitor the SMS microchip!
16
+ MARKDOWN
17
+ end
18
+
19
+ def mock_topic(attr = {})
20
+ Topic.new(
21
+ attr.reverse_merge(
22
+ title: 'A test topic',
23
+ slug: 'a-test-topic',
24
+ created_at: 3.days.ago,
25
+ id: 1 + rand(1334),
26
+ last_user: mock_user,
27
+ locked: [false, true].sample,
28
+ messageboard: mock_messageboard,
29
+ posts_count: 1 + rand(42),
30
+ sticky: [false, true].sample,
31
+ updated_at: Time.zone.now,
32
+ user: mock_user,
33
+ ))
34
+ end
35
+
36
+ def mock_post(attr = {})
37
+ topic = attr[:postable] || mock_topic
38
+ Post.new(
39
+ attr.reverse_merge(
40
+ content: 'A test post',
41
+ created_at: Time.zone.now,
42
+ id: 1 + rand(1334),
43
+ messageboard: topic.messageboard,
44
+ postable: topic,
45
+ updated_at: Time.zone.now,
46
+ user: topic.last_user,
47
+ ))
48
+ end
49
+
50
+ def mock_private_topic(attr = {})
51
+ PrivateTopic.new(
52
+ attr.reverse_merge(
53
+ title: 'A test private topic',
54
+ slug: 'a-test-private-topic',
55
+ created_at: 3.days.ago,
56
+ id: 1 + rand(1334),
57
+ last_user: mock_user,
58
+ posts_count: 1 + rand(42),
59
+ updated_at: Time.zone.now,
60
+ user: mock_user,
61
+ ))
62
+ end
63
+
64
+ def mock_private_post(attr = {})
65
+ private_topic = attr[:postable] || mock_private_topic
66
+ PrivatePost.new(
67
+ attr.reverse_merge(
68
+ content: 'A test private post',
69
+ created_at: Time.zone.now,
70
+ id: 1 + rand(1334),
71
+ postable: private_topic,
72
+ updated_at: Time.zone.now,
73
+ user: private_topic.last_user,
74
+ ))
75
+ end
76
+
77
+ def mock_messageboard(attr = {})
78
+ Messageboard.new(
79
+ attr.reverse_merge(
80
+ name: 'A test messageboard',
81
+ slug: 'a-test-messageboard',
82
+ description: 'Test messageboard description',
83
+ closed: false,
84
+ created_at: 1.month.ago,
85
+ id: 1 + rand(1334),
86
+ posts_count: rand(1337),
87
+ topics_count: rand(42),
88
+ updated_at: Time.zone.now,
89
+ ))
90
+ end
91
+
92
+ def mock_user(attr = {})
93
+ name = %w(Alice Bob).sample
94
+ Thredded.user_class.new(
95
+ attr.reverse_merge(
96
+ Thredded.user_name_column => name,
97
+ email: "#{name.downcase}@test.com",
98
+ ))
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ # Previews for the PostMailer
4
+ class PostMailerPreview < BaseMailerPreview
5
+ def at_notification
6
+ PostMailer.at_notification(
7
+ mock_post(content: mock_content(mention_users: %w(glebm joel))),
8
+ %w(glebm@test.com joel@test.com))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ # Previews for the PrivatePostMailer
4
+ class PrivatePostMailerPreview < BaseMailerPreview
5
+ def at_notification
6
+ PrivatePostMailer.at_notification(
7
+ mock_private_post(content: mock_content(mention_users: %w(glebm joel))),
8
+ %w(glebm@test.com joel@test.com))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ # Previews for the PrivateTopicMailer
4
+ class PrivateTopicMailerPreview < BaseMailerPreview
5
+ def message_notification
6
+ PrivateTopicMailer.message_notification(
7
+ mock_private_topic.tap do |private_topic|
8
+ private_topic.posts = [
9
+ mock_private_post(content: mock_content(mention_users: ['glebm']), postable: private_topic)
10
+ ]
11
+ end,
12
+ %w(glebm@test.com joel@test.com))
13
+ end
14
+ end
15
+ end
@@ -1,4 +1,17 @@
1
+ # frozen_string_literal: true
1
2
  module Thredded
2
3
  class BaseMailer < ActionMailer::Base
4
+ helper ::Thredded::UrlsHelper
5
+
6
+ protected
7
+
8
+ # Find a record by ID, or return the passed record.
9
+ # @param [Class<ActiveRecord::Base>] klass
10
+ # @param [Fixnum, String, klass] id_or_record
11
+ # @return [klass]
12
+ def find_record(klass, id_or_record)
13
+ # Check by name because in development the Class might have been reloaded after id was initialized
14
+ id_or_record.class.name == klass.name ? id_or_record : klass.find(id_or_record)
15
+ end
3
16
  end
4
17
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require_dependency 'thredded/topic_email_view'
1
3
  module Thredded
2
4
  class PostMailer < Thredded::BaseMailer
3
5
  def at_notification(post_id, emails)
4
- @post = Post.find(post_id)
5
- email_details = TopicEmailDecorator.new(@post.postable)
6
+ @post = find_record Post, post_id
7
+ email_details = TopicEmailView.new(@post.postable)
6
8
  headers['X-SMTPAPI'] = email_details.smtp_api_tag('at_notification')
7
9
 
8
10
  mail from: email_details.no_reply,
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require_dependency 'thredded/topic_email_view'
1
3
  module Thredded
2
4
  class PrivatePostMailer < Thredded::BaseMailer
3
5
  def at_notification(post_id, emails)
4
- @post = PrivatePost.find(post_id)
5
- email_details = TopicEmailDecorator.new(@post.postable)
6
+ @post = find_record PrivatePost, post_id
7
+ email_details = TopicEmailView.new(@post.postable)
6
8
  headers['X-SMTPAPI'] = email_details.smtp_api_tag('at_notification')
7
9
 
8
10
  mail from: email_details.no_reply,
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require_dependency 'thredded/topic_email_view'
1
3
  module Thredded
2
4
  class PrivateTopicMailer < Thredded::BaseMailer
3
5
  def message_notification(private_topic_id, emails)
4
- @topic = PrivateTopic.find(private_topic_id)
5
- email_details = TopicEmailDecorator.new(@topic)
6
+ @topic = find_record PrivateTopic, private_topic_id
7
+ email_details = TopicEmailView.new(@topic)
6
8
  headers['X-SMTPAPI'] = email_details.smtp_api_tag('private_topic_mailer')
7
9
 
8
10
  mail from: email_details.no_reply,
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+ module Thredded
4
+ # Excludes pagination routes in addition to the given list of reserved words.
5
+ class FriendlyIdReservedWordsAndPagination
6
+ PAGINATION_PATTERN = /\Apage-\d+\z/i
7
+
8
+ def initialize(words = [])
9
+ @words = Set.new(words)
10
+ end
11
+
12
+ def include?(slug)
13
+ @words.include?(slug) || slug =~ PAGINATION_PATTERN
14
+ end
15
+ end
16
+ end