thredded 0.1.0 → 0.2.2

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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.mkdn +202 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.mkdn +391 -0
  5. data/app/assets/images/thredded/breadcrumb-chevron.svg +1 -0
  6. data/app/assets/javascripts/thredded/currently_online.es6 +25 -0
  7. data/app/assets/javascripts/thredded/post_form.es6 +20 -0
  8. data/app/assets/javascripts/thredded/time_stamps.es6 +8 -0
  9. data/app/assets/javascripts/thredded/topic_form.es6 +55 -0
  10. data/app/assets/javascripts/thredded/users_select.es6 +5 -0
  11. data/app/assets/javascripts/thredded.es6 +10 -0
  12. data/app/assets/stylesheets/thredded/_base.scss +11 -0
  13. data/app/assets/stylesheets/thredded/_dependencies.scss +1 -0
  14. data/app/assets/stylesheets/thredded/_thredded.scss +27 -0
  15. data/app/assets/stylesheets/thredded/base/_alerts.scss +40 -0
  16. data/app/assets/stylesheets/thredded/base/_buttons.scss +39 -0
  17. data/app/assets/stylesheets/thredded/base/_forms.scss +79 -0
  18. data/app/assets/stylesheets/thredded/base/_grid.scss +19 -0
  19. data/app/assets/stylesheets/thredded/base/_lists.scss +31 -0
  20. data/app/assets/stylesheets/thredded/base/_tables.scss +25 -0
  21. data/app/assets/stylesheets/thredded/base/_typography.scss +36 -0
  22. data/app/assets/stylesheets/thredded/base/_variables.scss +54 -0
  23. data/app/assets/stylesheets/thredded/components/_base.scss +13 -0
  24. data/app/assets/stylesheets/thredded/components/_currently-online.scss +48 -0
  25. data/app/assets/stylesheets/thredded/components/_empty.scss +11 -0
  26. data/app/assets/stylesheets/thredded/components/_flash-message.scss +19 -0
  27. data/app/assets/stylesheets/thredded/components/_form-list.scss +35 -0
  28. data/app/assets/stylesheets/thredded/components/_main-section.scss +3 -0
  29. data/app/assets/stylesheets/thredded/components/_messageboard.scss +55 -0
  30. data/app/assets/stylesheets/thredded/components/_pagination.scss +26 -0
  31. data/app/assets/stylesheets/thredded/components/_post-form.scss +21 -0
  32. data/app/assets/stylesheets/thredded/components/_post.scss +66 -0
  33. data/app/assets/stylesheets/thredded/components/_preferences.scss +6 -0
  34. data/app/assets/stylesheets/thredded/components/_select2.scss +42 -0
  35. data/app/assets/stylesheets/thredded/components/_topic-delete.scss +9 -0
  36. data/app/assets/stylesheets/thredded/components/_topic-header.scss +20 -0
  37. data/app/assets/stylesheets/thredded/components/_topics.scss +113 -0
  38. data/app/assets/stylesheets/thredded/layout/_main-container.scss +15 -0
  39. data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +45 -0
  40. data/app/assets/stylesheets/thredded/layout/_topic-navigation.scss +53 -0
  41. data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +87 -0
  42. data/app/assets/stylesheets/thredded/utilities/_is-compact.scss +26 -0
  43. data/app/assets/stylesheets/thredded/utilities/_is-expanded.scss +16 -0
  44. data/app/assets/stylesheets/thredded.scss +3 -0
  45. data/app/commands/thredded/at_notification_extractor.rb +15 -0
  46. data/app/commands/thredded/members_marked_notified.rb +18 -0
  47. data/app/commands/thredded/messageboard_destroyer.rb +60 -0
  48. data/app/commands/thredded/notify_mentioned_users.rb +68 -0
  49. data/app/commands/thredded/notify_private_topic_users.rb +51 -0
  50. data/app/commands/thredded/user_reads_private_topic.rb +22 -0
  51. data/app/commands/thredded/user_resets_private_topic_to_unread.rb +23 -0
  52. data/app/controllers/thredded/application_controller.rb +105 -0
  53. data/app/controllers/thredded/messageboards_controller.rb +58 -0
  54. data/app/controllers/thredded/posts_controller.rb +78 -0
  55. data/app/controllers/thredded/preferences_controller.rb +28 -0
  56. data/app/controllers/thredded/private_topics_controller.rb +65 -0
  57. data/app/controllers/thredded/setups_controller.rb +53 -0
  58. data/app/controllers/thredded/theme_previews_controller.rb +59 -0
  59. data/app/controllers/thredded/topics_controller.rb +153 -0
  60. data/app/decorators/thredded/base_topic_decorator.rb +14 -0
  61. data/app/decorators/thredded/base_user_topic_decorator.rb +63 -0
  62. data/app/decorators/thredded/messageboard_decorator.rb +41 -0
  63. data/app/decorators/thredded/post_decorator.rb +40 -0
  64. data/app/decorators/thredded/private_topic_decorator.rb +23 -0
  65. data/app/decorators/thredded/topic_decorator.rb +25 -0
  66. data/app/decorators/thredded/topic_email_decorator.rb +24 -0
  67. data/app/decorators/thredded/user_private_topic_decorator.rb +13 -0
  68. data/app/decorators/thredded/user_topic_decorator.rb +37 -0
  69. data/app/forms/thredded/private_topic_form.rb +126 -0
  70. data/app/forms/thredded/topic_form.rb +94 -0
  71. data/app/helpers/thredded/application_helper.rb +41 -0
  72. data/app/jobs/thredded/activity_updater_job.rb +20 -0
  73. data/app/jobs/thredded/at_notifier_job.rb +11 -0
  74. data/app/jobs/thredded/notify_private_topic_users_job.rb +11 -0
  75. data/app/mailers/thredded/base_mailer.rb +4 -0
  76. data/app/mailers/thredded/post_mailer.rb +15 -0
  77. data/app/mailers/thredded/private_post_mailer.rb +15 -0
  78. data/app/mailers/thredded/private_topic_mailer.rb +15 -0
  79. data/app/models/concerns/thredded/post_common.rb +105 -0
  80. data/app/models/concerns/thredded/topic_common.rb +48 -0
  81. data/app/models/thredded/ability.rb +60 -0
  82. data/app/models/thredded/category.rb +12 -0
  83. data/app/models/thredded/messageboard.rb +50 -0
  84. data/app/models/thredded/messageboard_user.rb +12 -0
  85. data/app/models/thredded/notification_preference.rb +17 -0
  86. data/app/models/thredded/null_preference.rb +11 -0
  87. data/app/models/thredded/null_topic.rb +15 -0
  88. data/app/models/thredded/null_topic_read.rb +19 -0
  89. data/app/models/thredded/null_user.rb +51 -0
  90. data/app/models/thredded/post.rb +37 -0
  91. data/app/models/thredded/post_notification.rb +17 -0
  92. data/app/models/thredded/private_post.rb +25 -0
  93. data/app/models/thredded/private_topic.rb +60 -0
  94. data/app/models/thredded/private_user.rb +6 -0
  95. data/app/models/thredded/stats.rb +37 -0
  96. data/app/models/thredded/topic.rb +104 -0
  97. data/app/models/thredded/topic_category.rb +6 -0
  98. data/app/models/thredded/user_detail.rb +26 -0
  99. data/app/models/thredded/user_extender.rb +33 -0
  100. data/app/models/thredded/user_permissions/admin/if_admin_column_true.rb +12 -0
  101. data/app/models/thredded/user_permissions/admin/none.rb +12 -0
  102. data/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb +13 -0
  103. data/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb +25 -0
  104. data/app/models/thredded/user_permissions/moderate/none.rb +25 -0
  105. data/app/models/thredded/user_permissions/read/all.rb +25 -0
  106. data/app/models/thredded/user_permissions/write/all.rb +25 -0
  107. data/app/models/thredded/user_permissions/write/none.rb +25 -0
  108. data/app/models/thredded/user_preference.rb +6 -0
  109. data/app/models/thredded/user_topic_read.rb +10 -0
  110. data/app/views/layouts/thredded/application.html.erb +15 -0
  111. data/app/views/thredded/categories/_category.html.erb +1 -0
  112. data/app/views/thredded/kaminari/_first_page.html.erb +11 -0
  113. data/app/views/thredded/kaminari/_gap.html.erb +8 -0
  114. data/app/views/thredded/kaminari/_last_page.html.erb +11 -0
  115. data/app/views/thredded/kaminari/_next_page.html.erb +11 -0
  116. data/app/views/thredded/kaminari/_page.html.erb +12 -0
  117. data/app/views/thredded/kaminari/_paginator.html.erb +23 -0
  118. data/app/views/thredded/kaminari/_prev_page.html.erb +11 -0
  119. data/app/views/thredded/messageboards/_messageboard.html.erb +15 -0
  120. data/app/views/thredded/messageboards/index.html.erb +20 -0
  121. data/app/views/thredded/messageboards/new.html.erb +18 -0
  122. data/app/views/thredded/post_mailer/at_notification.html.erb +14 -0
  123. data/app/views/thredded/post_mailer/at_notification.text.erb +10 -0
  124. data/app/views/thredded/posts/_content_field.html.erb +4 -0
  125. data/app/views/thredded/posts/_form.html.erb +1 -0
  126. data/app/views/thredded/posts/_post.html.erb +1 -0
  127. data/app/views/thredded/posts/_user.html.erb +3 -0
  128. data/app/views/thredded/posts/edit.html.erb +13 -0
  129. data/app/views/thredded/posts_common/_form.html.erb +10 -0
  130. data/app/views/thredded/posts_common/_post.html.erb +20 -0
  131. data/app/views/thredded/preferences/_form.html.erb +28 -0
  132. data/app/views/thredded/preferences/_header.html.erb +1 -0
  133. data/app/views/thredded/preferences/edit.html.erb +12 -0
  134. data/app/views/thredded/private_post_mailer/at_notification.html.erb +11 -0
  135. data/app/views/thredded/private_posts/_form.html.erb +1 -0
  136. data/app/views/thredded/private_posts/_private_post.html.erb +1 -0
  137. data/app/views/thredded/private_topic_mailer/message_notification.html.erb +17 -0
  138. data/app/views/thredded/private_topic_mailer/message_notification.text.erb +13 -0
  139. data/app/views/thredded/private_topics/_breadcrumbs.html.erb +4 -0
  140. data/app/views/thredded/private_topics/_form.html.erb +24 -0
  141. data/app/views/thredded/private_topics/_no_private_topics.html.erb +6 -0
  142. data/app/views/thredded/private_topics/_private_topic.html.erb +22 -0
  143. data/app/views/thredded/private_topics/index.html.erb +23 -0
  144. data/app/views/thredded/private_topics/new.html.erb +13 -0
  145. data/app/views/thredded/private_topics/show.html.erb +14 -0
  146. data/app/views/thredded/search/_form.html.erb +7 -0
  147. data/app/views/thredded/shared/_currently_online.html.erb +16 -0
  148. data/app/views/thredded/shared/_flash_messages.html.erb +7 -0
  149. data/app/views/thredded/shared/_header.html.erb +4 -0
  150. data/app/views/thredded/shared/_messageboard_topics_breadcrumbs.html.erb +6 -0
  151. data/app/views/thredded/shared/_notification_preferences.html.erb +7 -0
  152. data/app/views/thredded/shared/_page.html.erb +6 -0
  153. data/app/views/thredded/shared/_time_ago.html.erb +7 -0
  154. data/app/views/thredded/shared/_top_nav.html.erb +36 -0
  155. data/app/views/thredded/shared/_topic_nav.html.erb +22 -0
  156. data/app/views/thredded/theme_previews/_section_title.html.erb +3 -0
  157. data/app/views/thredded/theme_previews/show.html.erb +108 -0
  158. data/app/views/thredded/topics/_form.html.erb +23 -0
  159. data/app/views/thredded/topics/_recent_topics_by_user.html.erb +8 -0
  160. data/app/views/thredded/topics/_topic.html.erb +29 -0
  161. data/app/views/thredded/topics/_topic_form_admin_options.html.erb +12 -0
  162. data/app/views/thredded/topics/by_category.html.erb +56 -0
  163. data/app/views/thredded/topics/edit.html.erb +38 -0
  164. data/app/views/thredded/topics/index.html.erb +18 -0
  165. data/app/views/thredded/topics/menu/_new_topic.html.erb +3 -0
  166. data/app/views/thredded/topics/new.html.erb +11 -0
  167. data/app/views/thredded/topics/search.html.erb +9 -0
  168. data/app/views/thredded/topics/show.html.erb +31 -0
  169. data/app/views/thredded/topics_common/_header.html.erb +6 -0
  170. data/app/views/thredded/users/_link.html.erb +9 -0
  171. data/config/routes.rb +28 -0
  172. data/db/migrate/20160329231848_create_thredded.rb +173 -0
  173. data/lib/generators/thredded/install/USAGE +13 -0
  174. data/lib/generators/thredded/install/install_generator.rb +23 -0
  175. data/lib/generators/thredded/install/templates/initializer.rb +51 -0
  176. data/lib/html/pipeline/at_mention_filter.rb +20 -0
  177. data/lib/html/pipeline/bbcode_filter.rb +19 -0
  178. data/lib/tasks/thredded_tasks.rake +18 -0
  179. data/lib/thredded/at_users.rb +19 -0
  180. data/lib/thredded/engine.rb +35 -0
  181. data/lib/thredded/errors.rb +67 -0
  182. data/lib/thredded/main_app_route_delegator.rb +24 -0
  183. data/lib/thredded/messageboard_user_permissions.rb +22 -0
  184. data/lib/thredded/post_sql_builder.rb +12 -0
  185. data/lib/thredded/post_user_permissions.rb +32 -0
  186. data/lib/thredded/private_topic_user_permissions.rb +26 -0
  187. data/lib/thredded/search_parser.rb +45 -0
  188. data/lib/thredded/search_sql_builder.rb +21 -0
  189. data/lib/thredded/seed_database.rb +76 -0
  190. data/lib/thredded/table_sql_builder.rb +41 -0
  191. data/lib/thredded/topic_sql_builder.rb +11 -0
  192. data/lib/thredded/topic_user_permissions.rb +32 -0
  193. data/lib/thredded/version.rb +3 -0
  194. data/lib/thredded.rb +85 -0
  195. data/thredded.gemspec +68 -0
  196. metadata +248 -122
@@ -0,0 +1,105 @@
1
+ module Thredded
2
+ module PostCommon
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ paginates_per 50
6
+
7
+ belongs_to :user, class_name: Thredded.user_class
8
+ delegate :email, to: :user, prefix: true, allow_nil: true
9
+
10
+ has_many :post_notifications, as: :post, dependent: :destroy
11
+
12
+ validates :content, presence: true
13
+
14
+ after_create :update_parent_last_user_and_timestamp
15
+ after_commit :notify_at_users, on: [:create, :update]
16
+
17
+ extend ClassMethods
18
+ end
19
+
20
+ def avatar_url
21
+ Thredded.avatar_url.call(user)
22
+ end
23
+
24
+ def user_anonymous?
25
+ user.try(:thredded_anonymous?)
26
+ end
27
+
28
+ # @param view_context [Object] the context of the rendering view.
29
+ def filtered_content(view_context)
30
+ pipeline = HTML::Pipeline.new(
31
+ [
32
+ html_filter_for_pipeline,
33
+ HTML::Pipeline::SanitizationFilter,
34
+ HTML::Pipeline::AtMentionFilter,
35
+ HTML::Pipeline::EmojiFilter,
36
+ HTML::Pipeline::AutolinkFilter,
37
+ ], context_options)
38
+
39
+ result = pipeline.call(content, view_context: view_context)
40
+ result[:output].to_s.html_safe
41
+ end
42
+
43
+ private
44
+
45
+ def context_options
46
+ {
47
+ asset_root: Rails.application.config.action_controller.asset_host || '',
48
+ post: self,
49
+ whitelist: sanitize_whitelist
50
+ }
51
+ end
52
+
53
+ def sanitize_whitelist
54
+ HTML::Pipeline::SanitizationFilter::WHITELIST.deep_merge(
55
+ attributes: {
56
+ 'code' => ['class'],
57
+ 'img' => %w(src class width height),
58
+ 'blockquote' => ['class'],
59
+ },
60
+ transformers: [(lambda do |env|
61
+ node = env[:node]
62
+ node_name = env[:node_name]
63
+
64
+ return if env[:is_whitelisted] || !node.element?
65
+ return if node_name != 'iframe'
66
+ return if (node['src'] =~ %r{\A(https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/}).nil?
67
+
68
+ Sanitize.node!(
69
+ node,
70
+ elements: %w(iframe),
71
+ attributes: {
72
+ 'iframe' => %w(allowfullscreen frameborder height src width)
73
+ }
74
+ )
75
+
76
+ { node_whitelist: [node] }
77
+ end)]
78
+ )
79
+ end
80
+
81
+ def html_filter_for_pipeline
82
+ if filter == 'bbcode'
83
+ HTML::Pipeline::BbcodeFilter
84
+ else
85
+ HTML::Pipeline::MarkdownFilter
86
+ end
87
+ end
88
+
89
+ def update_parent_last_user_and_timestamp
90
+ return unless postable && user
91
+
92
+ postable.update!(last_user: user, updated_at: Time.zone.now)
93
+ end
94
+
95
+ def notify_at_users
96
+ AtNotifierJob.perform_later(self.class.name, id)
97
+ end
98
+
99
+ module ClassMethods
100
+ def filters
101
+ %w(bbcode markdown)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,48 @@
1
+ module Thredded
2
+ module TopicCommon
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ paginates_per 50 if self.respond_to?(:paginates_per)
6
+
7
+ belongs_to :user,
8
+ class_name: Thredded.user_class
9
+ belongs_to :last_user,
10
+ class_name: Thredded.user_class,
11
+ foreign_key: 'last_user_id'
12
+
13
+ scope :order_latest_first, -> { order(updated_at: :desc, id: :desc) }
14
+ scope :on_page, -> page_num { page(page_num).per(30) }
15
+
16
+ validates_presence_of :hash_id
17
+ validates_presence_of :last_user_id
18
+ validates_numericality_of :posts_count
19
+ validates_uniqueness_of :hash_id
20
+
21
+ before_validation do
22
+ self.hash_id = SecureRandom.hex(10) if hash_id.nil?
23
+ end
24
+
25
+ delegate :name, :name=, :email, :email=, to: :user, prefix: true
26
+ end
27
+
28
+ def user
29
+ super || NullUser.new
30
+ end
31
+
32
+ def last_user
33
+ super || NullUser.new
34
+ end
35
+
36
+ def private?
37
+ !public?
38
+ end
39
+
40
+ module ClassMethods
41
+ def decorate
42
+ all.map do |topic|
43
+ TopicDecorator.new(topic)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,60 @@
1
+ module Thredded
2
+ class Ability
3
+ include ::CanCan::Ability
4
+
5
+ def initialize(user)
6
+ user ||= Thredded::NullUser.new
7
+ user_details = user.thredded_user_detail
8
+
9
+ can :manage, :all if user.thredded_admin?
10
+
11
+ can :read, Thredded::Messageboard do |messageboard|
12
+ Thredded::MessageboardUserPermissions.new(messageboard, user).readable?
13
+ end
14
+
15
+ can :admin, Thredded::Topic do |topic|
16
+ Thredded::TopicUserPermissions.new(topic, user, user_details).adminable?
17
+ end
18
+
19
+ can :edit, Thredded::Topic do |topic|
20
+ Thredded::TopicUserPermissions.new(topic, user, user_details).editable?
21
+ end
22
+
23
+ can :update, Thredded::Topic do |topic|
24
+ Thredded::TopicUserPermissions.new(topic, user, user_details).editable?
25
+ end
26
+
27
+ can :read, Thredded::Topic do |topic|
28
+ Thredded::TopicUserPermissions.new(topic, user, user_details).readable?
29
+ end
30
+
31
+ can :create, Thredded::Topic do |topic|
32
+ Thredded::TopicUserPermissions.new(topic, user, user_details).creatable?
33
+ end
34
+
35
+ can :manage, Thredded::PrivateTopic do |private_topic|
36
+ Thredded::PrivateTopicUserPermissions.new(private_topic, user, user_details).manageable?
37
+ end
38
+
39
+ can :create, Thredded::PrivateTopic do |private_topic|
40
+ Thredded::PrivateTopicUserPermissions.new(private_topic, user, user_details).creatable?
41
+ end
42
+
43
+ can :read, Thredded::PrivateTopic do |private_topic|
44
+ Thredded::PrivateTopicUserPermissions.new(private_topic, user, user_details).readable?
45
+ end
46
+
47
+ can :edit, Thredded::Post do |post|
48
+ Thredded::PostUserPermissions.new(post, user, user_details).editable?
49
+ end
50
+
51
+ can :manage, Thredded::Post do |post|
52
+ Thredded::PostUserPermissions.new(post, user, user_details).manageable?
53
+ end
54
+
55
+ can :create, Thredded::Post do |post|
56
+ Thredded::PostUserPermissions.new(post, user, user_details).creatable?
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,12 @@
1
+ module Thredded
2
+ class Category < ActiveRecord::Base
3
+ extend FriendlyId
4
+ belongs_to :messageboard
5
+ has_many :topic_categories
6
+ has_many :topics, through: :topic_categories
7
+ friendly_id :name, use: [:history, :scoped], scope: :messageboard
8
+
9
+ validates :name, presence: true
10
+ validates :messageboard_id, presence: true
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ module Thredded
2
+ class Messageboard < ActiveRecord::Base
3
+ FILTERS = %w(markdown bbcode)
4
+
5
+ extend FriendlyId
6
+ friendly_id :name, use: :slugged
7
+
8
+ validates :filter, inclusion: { in: FILTERS }, presence: true
9
+ validates :name, uniqueness: true, length: { maximum: 60 }, presence: true
10
+ validates :slug, exclusion: { in: %w(private-topics) }
11
+ validates :topics_count, numericality: true
12
+
13
+ has_many :categories, dependent: :destroy
14
+ has_many :notification_preferences, dependent: :destroy
15
+ has_many :posts, dependent: :destroy
16
+ has_many :topics, dependent: :destroy
17
+ has_many :user_details, through: :posts
18
+
19
+ has_many :messageboard_users,
20
+ class_name: 'Thredded::MessageboardUser',
21
+ inverse_of: :messageboard,
22
+ foreign_key: :thredded_messageboard_id
23
+ has_many :recently_active_user_details,
24
+ -> { merge(Thredded::MessageboardUser.recently_active) },
25
+ class_name: 'Thredded::UserDetail',
26
+ through: :messageboard_users,
27
+ source: :user_detail
28
+ has_many :recently_active_users,
29
+ class_name: Thredded.user_class,
30
+ through: :recently_active_user_details,
31
+ source: :user
32
+
33
+ default_scope { where(closed: false).order(topics_count: :desc) }
34
+
35
+ def self.decorate
36
+ all.map do |messageboard|
37
+ MessageboardDecorator.new(messageboard)
38
+ end
39
+ end
40
+
41
+ def preferences_for(user)
42
+ @preferences_for ||=
43
+ notification_preferences.where(user_id: user).first_or_create
44
+ end
45
+
46
+ def decorate
47
+ MessageboardDecorator.new(self)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,12 @@
1
+ module Thredded
2
+ # The state of a user with regards to a messageboard, such as the last time the user was active (visited)
3
+ # the messageboard.
4
+ class MessageboardUser < ActiveRecord::Base
5
+ belongs_to :messageboard,
6
+ class_name: 'Thredded::Messageboard',
7
+ foreign_key: :thredded_messageboard_id,
8
+ inverse_of: :messageboard_users
9
+ belongs_to :user_detail, class_name: 'Thredded::UserDetail', foreign_key: :thredded_user_detail_id
10
+ scope :recently_active, -> { where(arel_table[:last_seen_at].gt(Thredded.active_user_threshold.ago)) }
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module Thredded
2
+ class NotificationPreference < ActiveRecord::Base
3
+ belongs_to :user
4
+ belongs_to :messageboard
5
+
6
+ validates :user_id, presence: true
7
+ validates :messageboard_id, presence: true
8
+
9
+ def self.for(user)
10
+ where(user_id: user.id)
11
+ end
12
+
13
+ def self.in(messageboard)
14
+ where(messageboard_id: messageboard.id)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Thredded
2
+ class NullPreference
3
+ def notify_on_mention
4
+ true
5
+ end
6
+
7
+ def notify_on_message
8
+ true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Thredded
2
+ class NullTopic
3
+ def updated_at
4
+ nil
5
+ end
6
+
7
+ def user
8
+ Thredded::NullUser.new
9
+ end
10
+
11
+ def last_user
12
+ Thredded::NullUser.new
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module Thredded
2
+ class NullTopicRead
3
+ def page
4
+ 1
5
+ end
6
+
7
+ def post_id
8
+ 0
9
+ end
10
+
11
+ def posts_count
12
+ 0
13
+ end
14
+
15
+ def farthest_post
16
+ Post.new
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ require_relative './user_permissions/read/all'
2
+ require_relative './user_permissions/write/none'
3
+ require_relative './user_permissions/message/readers_of_writeable_boards'
4
+ require_relative './user_permissions/moderate/none'
5
+ require_relative './user_permissions/admin/none'
6
+
7
+ module Thredded
8
+ class NullUser
9
+ include ::Thredded::UserPermissions::Read::All
10
+ include ::Thredded::UserPermissions::Write::None
11
+ include ::Thredded::UserPermissions::Message::ReadersOfWriteableBoards
12
+ include ::Thredded::UserPermissions::Moderate::None
13
+ include ::Thredded::UserPermissions::Admin::None
14
+
15
+ def thredded_private_topics
16
+ Thredded::PrivateTopic.none
17
+ end
18
+
19
+ def id
20
+ 0
21
+ end
22
+
23
+ def member_of?(_)
24
+ false
25
+ end
26
+
27
+ def name
28
+ 'Anonymous User'
29
+ end
30
+
31
+ def to_s
32
+ name
33
+ end
34
+
35
+ def valid?
36
+ false
37
+ end
38
+
39
+ def thredded_anonymous?
40
+ true
41
+ end
42
+
43
+ def thredded_user_detail
44
+ Thredded::UserDetail.new
45
+ end
46
+
47
+ def thredded_user_preference
48
+ Thredded::UserPreference.new
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,37 @@
1
+ module Thredded
2
+ class Post < ActiveRecord::Base
3
+ include PostCommon
4
+
5
+ belongs_to :messageboard,
6
+ counter_cache: true
7
+ belongs_to :postable,
8
+ class_name: 'Thredded::Topic',
9
+ inverse_of: :posts,
10
+ counter_cache: true
11
+ belongs_to :user_detail,
12
+ inverse_of: :posts,
13
+ primary_key: :user_id,
14
+ foreign_key: :user_id,
15
+ counter_cache: true
16
+
17
+ validates :messageboard_id, presence: true
18
+ before_validation :set_filter_from_messageboard
19
+
20
+ def private_topic_post?
21
+ false
22
+ end
23
+
24
+ # @return [ActiveRecord::Relation<Thredded.user_class>] users from the list of user names that can read this post.
25
+ def readers_from_user_names(user_names)
26
+ DbTextSearch::CaseInsensitiveEq
27
+ .new(Thredded.user_class.thredded_messageboards_readers([messageboard]), Thredded.user_name_column)
28
+ .find(user_names)
29
+ end
30
+
31
+ private
32
+
33
+ def set_filter_from_messageboard
34
+ self.filter = messageboard.filter if messageboard
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ module Thredded
2
+ # Keeps track of post notifications that have been sent already.
3
+ class PostNotification < ActiveRecord::Base
4
+ belongs_to :post, polymorphic: true
5
+ # Specific post type associations for joins
6
+ belongs_to :non_private_post,
7
+ foreign_key: 'post_id',
8
+ foreign_type: 'Thredded::Post',
9
+ class_name: 'Thredded::Post'
10
+ belongs_to :private_post,
11
+ foreign_key: 'post_id',
12
+ foreign_type: 'Thredded::PrivatePost',
13
+ class_name: 'Thredded::PrivatePost'
14
+ validates :email, presence: true
15
+ validates :post, presence: true
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module Thredded
2
+ class PrivatePost < ActiveRecord::Base
3
+ include PostCommon
4
+
5
+ belongs_to :postable,
6
+ class_name: 'Thredded::PrivateTopic',
7
+ inverse_of: :posts,
8
+ counter_cache: :posts_count
9
+ belongs_to :user_detail,
10
+ inverse_of: :private_posts,
11
+ primary_key: :user_id,
12
+ foreign_key: :user_id
13
+
14
+ def private_topic_post?
15
+ true
16
+ end
17
+
18
+ # @return [ActiveRecord::Relation<Thredded.user_class>] users from the list of user names that can read this post.
19
+ def readers_from_user_names(user_names)
20
+ DbTextSearch::CaseInsensitiveEq
21
+ .new(postable.users, Thredded.user_name_column)
22
+ .find(user_names)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,60 @@
1
+ module Thredded
2
+ class PrivateTopic < ActiveRecord::Base
3
+ include TopicCommon
4
+ extend FriendlyId
5
+ friendly_id :title, use: :history
6
+
7
+ has_many :posts,
8
+ class_name: 'Thredded::PrivatePost',
9
+ foreign_key: :postable_id,
10
+ inverse_of: :postable,
11
+ dependent: :destroy
12
+ has_many :private_users
13
+ has_many :users, through: :private_users
14
+
15
+ def self.find_by_slug(slug)
16
+ friendly.find(slug)
17
+ rescue ActiveRecord::RecordNotFound
18
+ raise Thredded::Errors::PrivateTopicNotFound
19
+ end
20
+
21
+ def decorate
22
+ TopicDecorator.new(self)
23
+ end
24
+
25
+ def user_topic_reads
26
+ []
27
+ end
28
+
29
+ def categories
30
+ []
31
+ end
32
+
33
+ def self.for_user(user)
34
+ joins(:private_users)
35
+ .where(thredded_private_users: { user_id: user.id })
36
+ end
37
+
38
+ def add_user(user)
39
+ if String == user.class
40
+ user = Thredded.user_class.find_by_name(user)
41
+ end
42
+
43
+ users << user
44
+ end
45
+
46
+ def public?
47
+ false
48
+ end
49
+
50
+ def user_id=(ids)
51
+ return unless ids.size > 0
52
+
53
+ self.users = Thredded.user_class.where(id: ids.uniq)
54
+ end
55
+
56
+ def users_to_sentence
57
+ users.map { |user| user.to_s.capitalize }.to_sentence
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,6 @@
1
+ module Thredded
2
+ class PrivateUser < ActiveRecord::Base
3
+ belongs_to :private_topic
4
+ belongs_to :user, class_name: Thredded.user_class
5
+ end
6
+ end
@@ -0,0 +1,37 @@
1
+ module Thredded
2
+ class Stats
3
+ include ActionView::Helpers::NumberHelper
4
+
5
+ class << self
6
+ def messageboards_count
7
+ new.messageboards_count
8
+ end
9
+
10
+ def topics_count
11
+ new.topics_count
12
+ end
13
+
14
+ def posts_count
15
+ new.posts_count
16
+ end
17
+ end
18
+
19
+ def messageboards_count
20
+ messageboards.count
21
+ end
22
+
23
+ def topics_count
24
+ number_to_human(messageboards.map(&:topics_count).sum, precision: 4)
25
+ end
26
+
27
+ def posts_count
28
+ number_to_human(messageboards.map(&:posts_count).sum, precision: 5)
29
+ end
30
+
31
+ private
32
+
33
+ def messageboards
34
+ @messageboards ||= Messageboard.all
35
+ end
36
+ end
37
+ end