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.
- checksums.yaml +4 -4
- data/CHANGELOG.mkdn +202 -0
- data/MIT-LICENSE +20 -0
- data/README.mkdn +391 -0
- data/app/assets/images/thredded/breadcrumb-chevron.svg +1 -0
- data/app/assets/javascripts/thredded/currently_online.es6 +25 -0
- data/app/assets/javascripts/thredded/post_form.es6 +20 -0
- data/app/assets/javascripts/thredded/time_stamps.es6 +8 -0
- data/app/assets/javascripts/thredded/topic_form.es6 +55 -0
- data/app/assets/javascripts/thredded/users_select.es6 +5 -0
- data/app/assets/javascripts/thredded.es6 +10 -0
- data/app/assets/stylesheets/thredded/_base.scss +11 -0
- data/app/assets/stylesheets/thredded/_dependencies.scss +1 -0
- data/app/assets/stylesheets/thredded/_thredded.scss +27 -0
- data/app/assets/stylesheets/thredded/base/_alerts.scss +40 -0
- data/app/assets/stylesheets/thredded/base/_buttons.scss +39 -0
- data/app/assets/stylesheets/thredded/base/_forms.scss +79 -0
- data/app/assets/stylesheets/thredded/base/_grid.scss +19 -0
- data/app/assets/stylesheets/thredded/base/_lists.scss +31 -0
- data/app/assets/stylesheets/thredded/base/_tables.scss +25 -0
- data/app/assets/stylesheets/thredded/base/_typography.scss +36 -0
- data/app/assets/stylesheets/thredded/base/_variables.scss +54 -0
- data/app/assets/stylesheets/thredded/components/_base.scss +13 -0
- data/app/assets/stylesheets/thredded/components/_currently-online.scss +48 -0
- data/app/assets/stylesheets/thredded/components/_empty.scss +11 -0
- data/app/assets/stylesheets/thredded/components/_flash-message.scss +19 -0
- data/app/assets/stylesheets/thredded/components/_form-list.scss +35 -0
- data/app/assets/stylesheets/thredded/components/_main-section.scss +3 -0
- data/app/assets/stylesheets/thredded/components/_messageboard.scss +55 -0
- data/app/assets/stylesheets/thredded/components/_pagination.scss +26 -0
- data/app/assets/stylesheets/thredded/components/_post-form.scss +21 -0
- data/app/assets/stylesheets/thredded/components/_post.scss +66 -0
- data/app/assets/stylesheets/thredded/components/_preferences.scss +6 -0
- data/app/assets/stylesheets/thredded/components/_select2.scss +42 -0
- data/app/assets/stylesheets/thredded/components/_topic-delete.scss +9 -0
- data/app/assets/stylesheets/thredded/components/_topic-header.scss +20 -0
- data/app/assets/stylesheets/thredded/components/_topics.scss +113 -0
- data/app/assets/stylesheets/thredded/layout/_main-container.scss +15 -0
- data/app/assets/stylesheets/thredded/layout/_main-navigation.scss +45 -0
- data/app/assets/stylesheets/thredded/layout/_topic-navigation.scss +53 -0
- data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +87 -0
- data/app/assets/stylesheets/thredded/utilities/_is-compact.scss +26 -0
- data/app/assets/stylesheets/thredded/utilities/_is-expanded.scss +16 -0
- data/app/assets/stylesheets/thredded.scss +3 -0
- data/app/commands/thredded/at_notification_extractor.rb +15 -0
- data/app/commands/thredded/members_marked_notified.rb +18 -0
- data/app/commands/thredded/messageboard_destroyer.rb +60 -0
- data/app/commands/thredded/notify_mentioned_users.rb +68 -0
- data/app/commands/thredded/notify_private_topic_users.rb +51 -0
- data/app/commands/thredded/user_reads_private_topic.rb +22 -0
- data/app/commands/thredded/user_resets_private_topic_to_unread.rb +23 -0
- data/app/controllers/thredded/application_controller.rb +105 -0
- data/app/controllers/thredded/messageboards_controller.rb +58 -0
- data/app/controllers/thredded/posts_controller.rb +78 -0
- data/app/controllers/thredded/preferences_controller.rb +28 -0
- data/app/controllers/thredded/private_topics_controller.rb +65 -0
- data/app/controllers/thredded/setups_controller.rb +53 -0
- data/app/controllers/thredded/theme_previews_controller.rb +59 -0
- data/app/controllers/thredded/topics_controller.rb +153 -0
- data/app/decorators/thredded/base_topic_decorator.rb +14 -0
- data/app/decorators/thredded/base_user_topic_decorator.rb +63 -0
- data/app/decorators/thredded/messageboard_decorator.rb +41 -0
- data/app/decorators/thredded/post_decorator.rb +40 -0
- data/app/decorators/thredded/private_topic_decorator.rb +23 -0
- data/app/decorators/thredded/topic_decorator.rb +25 -0
- data/app/decorators/thredded/topic_email_decorator.rb +24 -0
- data/app/decorators/thredded/user_private_topic_decorator.rb +13 -0
- data/app/decorators/thredded/user_topic_decorator.rb +37 -0
- data/app/forms/thredded/private_topic_form.rb +126 -0
- data/app/forms/thredded/topic_form.rb +94 -0
- data/app/helpers/thredded/application_helper.rb +41 -0
- data/app/jobs/thredded/activity_updater_job.rb +20 -0
- data/app/jobs/thredded/at_notifier_job.rb +11 -0
- data/app/jobs/thredded/notify_private_topic_users_job.rb +11 -0
- data/app/mailers/thredded/base_mailer.rb +4 -0
- data/app/mailers/thredded/post_mailer.rb +15 -0
- data/app/mailers/thredded/private_post_mailer.rb +15 -0
- data/app/mailers/thredded/private_topic_mailer.rb +15 -0
- data/app/models/concerns/thredded/post_common.rb +105 -0
- data/app/models/concerns/thredded/topic_common.rb +48 -0
- data/app/models/thredded/ability.rb +60 -0
- data/app/models/thredded/category.rb +12 -0
- data/app/models/thredded/messageboard.rb +50 -0
- data/app/models/thredded/messageboard_user.rb +12 -0
- data/app/models/thredded/notification_preference.rb +17 -0
- data/app/models/thredded/null_preference.rb +11 -0
- data/app/models/thredded/null_topic.rb +15 -0
- data/app/models/thredded/null_topic_read.rb +19 -0
- data/app/models/thredded/null_user.rb +51 -0
- data/app/models/thredded/post.rb +37 -0
- data/app/models/thredded/post_notification.rb +17 -0
- data/app/models/thredded/private_post.rb +25 -0
- data/app/models/thredded/private_topic.rb +60 -0
- data/app/models/thredded/private_user.rb +6 -0
- data/app/models/thredded/stats.rb +37 -0
- data/app/models/thredded/topic.rb +104 -0
- data/app/models/thredded/topic_category.rb +6 -0
- data/app/models/thredded/user_detail.rb +26 -0
- data/app/models/thredded/user_extender.rb +33 -0
- data/app/models/thredded/user_permissions/admin/if_admin_column_true.rb +12 -0
- data/app/models/thredded/user_permissions/admin/none.rb +12 -0
- data/app/models/thredded/user_permissions/message/readers_of_writeable_boards.rb +13 -0
- data/app/models/thredded/user_permissions/moderate/if_moderator_column_true.rb +25 -0
- data/app/models/thredded/user_permissions/moderate/none.rb +25 -0
- data/app/models/thredded/user_permissions/read/all.rb +25 -0
- data/app/models/thredded/user_permissions/write/all.rb +25 -0
- data/app/models/thredded/user_permissions/write/none.rb +25 -0
- data/app/models/thredded/user_preference.rb +6 -0
- data/app/models/thredded/user_topic_read.rb +10 -0
- data/app/views/layouts/thredded/application.html.erb +15 -0
- data/app/views/thredded/categories/_category.html.erb +1 -0
- data/app/views/thredded/kaminari/_first_page.html.erb +11 -0
- data/app/views/thredded/kaminari/_gap.html.erb +8 -0
- data/app/views/thredded/kaminari/_last_page.html.erb +11 -0
- data/app/views/thredded/kaminari/_next_page.html.erb +11 -0
- data/app/views/thredded/kaminari/_page.html.erb +12 -0
- data/app/views/thredded/kaminari/_paginator.html.erb +23 -0
- data/app/views/thredded/kaminari/_prev_page.html.erb +11 -0
- data/app/views/thredded/messageboards/_messageboard.html.erb +15 -0
- data/app/views/thredded/messageboards/index.html.erb +20 -0
- data/app/views/thredded/messageboards/new.html.erb +18 -0
- data/app/views/thredded/post_mailer/at_notification.html.erb +14 -0
- data/app/views/thredded/post_mailer/at_notification.text.erb +10 -0
- data/app/views/thredded/posts/_content_field.html.erb +4 -0
- data/app/views/thredded/posts/_form.html.erb +1 -0
- data/app/views/thredded/posts/_post.html.erb +1 -0
- data/app/views/thredded/posts/_user.html.erb +3 -0
- data/app/views/thredded/posts/edit.html.erb +13 -0
- data/app/views/thredded/posts_common/_form.html.erb +10 -0
- data/app/views/thredded/posts_common/_post.html.erb +20 -0
- data/app/views/thredded/preferences/_form.html.erb +28 -0
- data/app/views/thredded/preferences/_header.html.erb +1 -0
- data/app/views/thredded/preferences/edit.html.erb +12 -0
- data/app/views/thredded/private_post_mailer/at_notification.html.erb +11 -0
- data/app/views/thredded/private_posts/_form.html.erb +1 -0
- data/app/views/thredded/private_posts/_private_post.html.erb +1 -0
- data/app/views/thredded/private_topic_mailer/message_notification.html.erb +17 -0
- data/app/views/thredded/private_topic_mailer/message_notification.text.erb +13 -0
- data/app/views/thredded/private_topics/_breadcrumbs.html.erb +4 -0
- data/app/views/thredded/private_topics/_form.html.erb +24 -0
- data/app/views/thredded/private_topics/_no_private_topics.html.erb +6 -0
- data/app/views/thredded/private_topics/_private_topic.html.erb +22 -0
- data/app/views/thredded/private_topics/index.html.erb +23 -0
- data/app/views/thredded/private_topics/new.html.erb +13 -0
- data/app/views/thredded/private_topics/show.html.erb +14 -0
- data/app/views/thredded/search/_form.html.erb +7 -0
- data/app/views/thredded/shared/_currently_online.html.erb +16 -0
- data/app/views/thredded/shared/_flash_messages.html.erb +7 -0
- data/app/views/thredded/shared/_header.html.erb +4 -0
- data/app/views/thredded/shared/_messageboard_topics_breadcrumbs.html.erb +6 -0
- data/app/views/thredded/shared/_notification_preferences.html.erb +7 -0
- data/app/views/thredded/shared/_page.html.erb +6 -0
- data/app/views/thredded/shared/_time_ago.html.erb +7 -0
- data/app/views/thredded/shared/_top_nav.html.erb +36 -0
- data/app/views/thredded/shared/_topic_nav.html.erb +22 -0
- data/app/views/thredded/theme_previews/_section_title.html.erb +3 -0
- data/app/views/thredded/theme_previews/show.html.erb +108 -0
- data/app/views/thredded/topics/_form.html.erb +23 -0
- data/app/views/thredded/topics/_recent_topics_by_user.html.erb +8 -0
- data/app/views/thredded/topics/_topic.html.erb +29 -0
- data/app/views/thredded/topics/_topic_form_admin_options.html.erb +12 -0
- data/app/views/thredded/topics/by_category.html.erb +56 -0
- data/app/views/thredded/topics/edit.html.erb +38 -0
- data/app/views/thredded/topics/index.html.erb +18 -0
- data/app/views/thredded/topics/menu/_new_topic.html.erb +3 -0
- data/app/views/thredded/topics/new.html.erb +11 -0
- data/app/views/thredded/topics/search.html.erb +9 -0
- data/app/views/thredded/topics/show.html.erb +31 -0
- data/app/views/thredded/topics_common/_header.html.erb +6 -0
- data/app/views/thredded/users/_link.html.erb +9 -0
- data/config/routes.rb +28 -0
- data/db/migrate/20160329231848_create_thredded.rb +173 -0
- data/lib/generators/thredded/install/USAGE +13 -0
- data/lib/generators/thredded/install/install_generator.rb +23 -0
- data/lib/generators/thredded/install/templates/initializer.rb +51 -0
- data/lib/html/pipeline/at_mention_filter.rb +20 -0
- data/lib/html/pipeline/bbcode_filter.rb +19 -0
- data/lib/tasks/thredded_tasks.rake +18 -0
- data/lib/thredded/at_users.rb +19 -0
- data/lib/thredded/engine.rb +35 -0
- data/lib/thredded/errors.rb +67 -0
- data/lib/thredded/main_app_route_delegator.rb +24 -0
- data/lib/thredded/messageboard_user_permissions.rb +22 -0
- data/lib/thredded/post_sql_builder.rb +12 -0
- data/lib/thredded/post_user_permissions.rb +32 -0
- data/lib/thredded/private_topic_user_permissions.rb +26 -0
- data/lib/thredded/search_parser.rb +45 -0
- data/lib/thredded/search_sql_builder.rb +21 -0
- data/lib/thredded/seed_database.rb +76 -0
- data/lib/thredded/table_sql_builder.rb +41 -0
- data/lib/thredded/topic_sql_builder.rb +11 -0
- data/lib/thredded/topic_user_permissions.rb +32 -0
- data/lib/thredded/version.rb +3 -0
- data/lib/thredded.rb +85 -0
- data/thredded.gemspec +68 -0
- metadata +248 -122
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class BaseTopicDecorator < SimpleDelegator
|
|
3
|
+
include Rails.application.routes.url_helpers
|
|
4
|
+
include ActionView::Helpers::UrlHelper
|
|
5
|
+
|
|
6
|
+
def slug
|
|
7
|
+
__getobj__.slug.nil? ? id : __getobj__.slug
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def original
|
|
11
|
+
__getobj__
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class BaseUserTopicDecorator < SimpleDelegator
|
|
3
|
+
extend ActiveModel::Naming
|
|
4
|
+
include ActiveModel::Conversion
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
# @return [Class<ActiveRecord::Base>]
|
|
8
|
+
def topic_class
|
|
9
|
+
fail 'Implement in subclass'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def decorator_class
|
|
13
|
+
"#{topic_class.name}Decorator".constantize
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def decorate_all(user, topics)
|
|
17
|
+
topics.map { |topic| new(user, topic) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def model_name
|
|
21
|
+
ActiveModel::Name.new(self, nil, topic_class.name.demodulize)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(user, topic)
|
|
26
|
+
@user = user || Thredded::NullUser.new
|
|
27
|
+
@topic = self.class.decorator_class.new(topic)
|
|
28
|
+
super(@topic)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_model
|
|
32
|
+
topic
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def persisted?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def css_class
|
|
40
|
+
[read_status_class, topic.css_class].map(&:presence).compact.join(' ')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def read_status_class
|
|
44
|
+
if read?
|
|
45
|
+
'thredded--topic--read'
|
|
46
|
+
else
|
|
47
|
+
'thredded--topic--unread'
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def read?
|
|
52
|
+
fail 'Subclass responsibility'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_ary
|
|
56
|
+
[self]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
attr_reader :topic, :user
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class MessageboardDecorator < SimpleDelegator
|
|
3
|
+
include ActionView::Helpers::NumberHelper
|
|
4
|
+
include ActionView::Helpers::TagHelper
|
|
5
|
+
|
|
6
|
+
def initialize(messageboard)
|
|
7
|
+
super
|
|
8
|
+
@messageboard = messageboard
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def original
|
|
12
|
+
messageboard
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def meta
|
|
16
|
+
topics_count = number_to_human(messageboard.topics_count)
|
|
17
|
+
posts_count = number_to_human(messageboard.posts_count)
|
|
18
|
+
|
|
19
|
+
"#{topics_count} topics / #{posts_count} posts".downcase
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def latest_topic
|
|
23
|
+
@latest_topic ||= begin
|
|
24
|
+
messageboard.topics.order_latest_first.first ||
|
|
25
|
+
Thredded::NullTopic.new
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def latest_user
|
|
30
|
+
latest_topic.last_user
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def category_options
|
|
34
|
+
messageboard.categories.map { |cat| [cat.name, cat.id] }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
attr_reader :messageboard
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class PostDecorator < SimpleDelegator
|
|
3
|
+
attr_reader :post
|
|
4
|
+
|
|
5
|
+
def initialize(post)
|
|
6
|
+
super
|
|
7
|
+
@post = post
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def user_name
|
|
11
|
+
if user
|
|
12
|
+
user.to_s
|
|
13
|
+
else
|
|
14
|
+
'Anonymous'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def original
|
|
19
|
+
post
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def avatar_url
|
|
23
|
+
super.sub(/\Ahttp:/, '')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_ary
|
|
27
|
+
[self]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def created_at_str
|
|
33
|
+
created_at.getutc.to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def created_at_utc
|
|
37
|
+
created_at.getutc.iso8601
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'thredded/base_topic_decorator'
|
|
2
|
+
|
|
3
|
+
module Thredded
|
|
4
|
+
class PrivateTopicDecorator < SimpleDelegator
|
|
5
|
+
def initialize(private_topic)
|
|
6
|
+
super Thredded::BaseTopicDecorator.new(private_topic)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.model_name
|
|
10
|
+
ActiveModel::Name.new(self, nil, 'PrivateTopic')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_model
|
|
14
|
+
__getobj__
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def css_class
|
|
18
|
+
classes = []
|
|
19
|
+
classes << 'thredded--private-topic'
|
|
20
|
+
classes.join(' ')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'thredded/base_topic_decorator'
|
|
2
|
+
|
|
3
|
+
module Thredded
|
|
4
|
+
class TopicDecorator < SimpleDelegator
|
|
5
|
+
def initialize(private_topic)
|
|
6
|
+
super(Thredded::BaseTopicDecorator.new(private_topic))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.model_name
|
|
10
|
+
ActiveModel::Name.new(self, nil, 'Topic')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def css_class
|
|
14
|
+
classes = []
|
|
15
|
+
classes << 'thredded--topic--locked' if locked?
|
|
16
|
+
classes << 'thredded--topic--sticky' if sticky?
|
|
17
|
+
classes += ['thredded--topic--category'] + categories.map { |c| "thredded--topic--category--#{c.name}" } if categories.present?
|
|
18
|
+
classes.join(' ')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def category_options
|
|
22
|
+
messageboard.decorate.category_options
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class TopicEmailDecorator
|
|
3
|
+
# @param [Thredded::TopicCommon] topic
|
|
4
|
+
def initialize(topic)
|
|
5
|
+
@topic = topic
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def smtp_api_tag(tag)
|
|
9
|
+
%({"category": ["thredded_#{@topic.private? ? 'private_topic' : @topic.messageboard.name}","#{tag}"]})
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def subject
|
|
13
|
+
"#{Thredded.email_outgoing_prefix} #{@topic.title}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def reply_to
|
|
17
|
+
Thredded.email_reply_to.call(@topic)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def no_reply
|
|
21
|
+
Thredded.email_from
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'thredded/base_user_topic_decorator'
|
|
2
|
+
|
|
3
|
+
module Thredded
|
|
4
|
+
class UserPrivateTopicDecorator < BaseUserTopicDecorator
|
|
5
|
+
def self.topic_class
|
|
6
|
+
PrivateTopic
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def read?
|
|
10
|
+
topic.private_users.find_by(user: user).try(:read?)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'thredded/base_user_topic_decorator'
|
|
2
|
+
|
|
3
|
+
module Thredded
|
|
4
|
+
class UserTopicDecorator < BaseUserTopicDecorator
|
|
5
|
+
def self.topic_class
|
|
6
|
+
Topic
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def farthest_page
|
|
10
|
+
read_status.page
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def farthest_post
|
|
14
|
+
read_status.farthest_post
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def read?
|
|
18
|
+
topic.posts_count == read_status.posts_count
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def read_status
|
|
24
|
+
if user.id > 0
|
|
25
|
+
@read_status ||= topic.user_topic_reads.select do |reads|
|
|
26
|
+
reads.user_id == user.id
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if @read_status.blank?
|
|
31
|
+
Thredded::NullTopicRead.new
|
|
32
|
+
else
|
|
33
|
+
@read_status.first
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class PrivateTopicForm
|
|
3
|
+
include ActiveModel::Model
|
|
4
|
+
|
|
5
|
+
attr_accessor \
|
|
6
|
+
:title,
|
|
7
|
+
:category_ids,
|
|
8
|
+
:user_ids,
|
|
9
|
+
:locked,
|
|
10
|
+
:sticky,
|
|
11
|
+
:content,
|
|
12
|
+
:private_topic
|
|
13
|
+
|
|
14
|
+
attr_reader :user, :params
|
|
15
|
+
|
|
16
|
+
validate :validate_children
|
|
17
|
+
|
|
18
|
+
def initialize(params = {})
|
|
19
|
+
@params = params
|
|
20
|
+
@title = params[:title]
|
|
21
|
+
@category_ids = params[:category_ids] || []
|
|
22
|
+
@user_ids = params[:user_ids] || []
|
|
23
|
+
@user = params[:user] || fail('user is required')
|
|
24
|
+
@locked = params[:locked]
|
|
25
|
+
@sticky = params[:sticky]
|
|
26
|
+
@content = params[:content]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.model_name
|
|
30
|
+
Thredded::PrivateTopic.model_name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def users
|
|
34
|
+
@user.thredded_can_message_users
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def users_for_select
|
|
38
|
+
(users - [@user]).map { |user| [user.to_s, user.id] }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def id
|
|
42
|
+
private_topic.id
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def save
|
|
46
|
+
return false unless valid?
|
|
47
|
+
|
|
48
|
+
ActiveRecord::Base.transaction do
|
|
49
|
+
private_topic.save!
|
|
50
|
+
post.save!
|
|
51
|
+
end
|
|
52
|
+
true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def private_topic
|
|
56
|
+
@private_topic ||= Thredded::PrivateTopic.new(
|
|
57
|
+
title: title,
|
|
58
|
+
users: private_users,
|
|
59
|
+
user: non_null_user,
|
|
60
|
+
last_user: non_null_user)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def post
|
|
64
|
+
@post ||= private_topic.posts.build(
|
|
65
|
+
content: content,
|
|
66
|
+
user: non_null_user)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def users_selected_options
|
|
70
|
+
{ selected: private_user_ids }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def users_select_html_options
|
|
74
|
+
{
|
|
75
|
+
multiple: true,
|
|
76
|
+
required: true,
|
|
77
|
+
'data-placeholder' => 'select users to participate in this topic',
|
|
78
|
+
'data-thredded-users-select' => true
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def topic_categories
|
|
85
|
+
if category_ids
|
|
86
|
+
ids = category_ids.reject(&:empty?).map(&:to_i)
|
|
87
|
+
Category.where(id: ids)
|
|
88
|
+
else
|
|
89
|
+
[]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def private_users
|
|
94
|
+
Thredded.user_class.where(id: normalized_user_ids)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def private_user_ids
|
|
98
|
+
private_users.map(&:id)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def normalized_user_ids
|
|
102
|
+
user_ids
|
|
103
|
+
.reject(&:empty?)
|
|
104
|
+
.map(&:to_i)
|
|
105
|
+
.push(user.id)
|
|
106
|
+
.uniq
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @return [Thredded.user_class, nil] return a user or nil if the user is a NullUser
|
|
110
|
+
# This is necessary because assigning a NullUser to an ActiveRecord association results in an exception.
|
|
111
|
+
def non_null_user
|
|
112
|
+
@user unless @user.thredded_anonymous?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def validate_children
|
|
116
|
+
promote_errors(private_topic.errors) if private_topic.invalid?
|
|
117
|
+
promote_errors(post.errors) if post.invalid?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def promote_errors(child_errors)
|
|
121
|
+
child_errors.each do |attribute, message|
|
|
122
|
+
errors.add(attribute, message)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class TopicForm
|
|
3
|
+
include ActiveModel::Model
|
|
4
|
+
|
|
5
|
+
attr_accessor :title, :category_ids, :locked, :sticky, :content, :topic
|
|
6
|
+
attr_reader :user, :messageboard
|
|
7
|
+
|
|
8
|
+
validate :validate_children
|
|
9
|
+
|
|
10
|
+
def initialize(params = {})
|
|
11
|
+
@title = params[:title]
|
|
12
|
+
@category_ids = params[:category_ids]
|
|
13
|
+
@locked = params[:locked] || false
|
|
14
|
+
@sticky = params[:sticky] || false
|
|
15
|
+
@content = params[:content]
|
|
16
|
+
@user = params[:user] || fail('user is required')
|
|
17
|
+
@messageboard = params[:messageboard]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.model_name
|
|
21
|
+
Thredded::Topic.model_name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def categories
|
|
25
|
+
topic.messageboard.categories
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def category_options
|
|
29
|
+
topic.messageboard.decorate.category_options
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def filter
|
|
33
|
+
topic.messageboard.filter
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def save
|
|
37
|
+
return false unless valid?
|
|
38
|
+
|
|
39
|
+
ActiveRecord::Base.transaction do
|
|
40
|
+
topic.save!
|
|
41
|
+
post.save!
|
|
42
|
+
end
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def topic
|
|
47
|
+
@topic ||= messageboard.topics.build(
|
|
48
|
+
title: title,
|
|
49
|
+
locked: locked,
|
|
50
|
+
sticky: sticky,
|
|
51
|
+
user: non_null_user,
|
|
52
|
+
last_user: non_null_user,
|
|
53
|
+
categories: topic_categories,
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def post
|
|
58
|
+
@post ||= topic.posts.build(
|
|
59
|
+
content: content,
|
|
60
|
+
user: non_null_user,
|
|
61
|
+
messageboard: messageboard,
|
|
62
|
+
filter: messageboard.filter
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# @return [Thredded.user_class, nil] return a user or nil if the user is a NullUser
|
|
69
|
+
# This is necessary because assigning a NullUser to an ActiveRecord association results in an exception.
|
|
70
|
+
def non_null_user
|
|
71
|
+
@user unless @user.thredded_anonymous?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def topic_categories
|
|
75
|
+
if category_ids
|
|
76
|
+
ids = category_ids.reject(&:empty?).map(&:to_i)
|
|
77
|
+
Category.where(id: ids)
|
|
78
|
+
else
|
|
79
|
+
[]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def validate_children
|
|
84
|
+
promote_errors(topic.errors) if topic.invalid?
|
|
85
|
+
promote_errors(post.errors) if post.invalid?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def promote_errors(child_errors)
|
|
89
|
+
child_errors.each do |attribute, message|
|
|
90
|
+
errors.add(attribute, message)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
module ApplicationHelper
|
|
3
|
+
# Render the page container with the supplied block as content.
|
|
4
|
+
def thredded_page(&block)
|
|
5
|
+
# enable the host app to easily check whether a thredded view is being rendered:
|
|
6
|
+
content_for :thredded, true
|
|
7
|
+
content_for :thredded_page_content, &block
|
|
8
|
+
render partial: 'thredded/shared/page'
|
|
9
|
+
end
|
|
10
|
+
|
|
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
|
+
# @param user [Thredded.user_class, Thredded::NullUser]
|
|
18
|
+
# @return [String] html_safe link to the user
|
|
19
|
+
def user_link(user)
|
|
20
|
+
render partial: 'thredded/users/link', locals: { user: user }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param datetime [DateTime]
|
|
24
|
+
# @return [String] html_safe datetime presentation
|
|
25
|
+
def time_ago(datetime)
|
|
26
|
+
render partial: 'thredded/shared/time_ago', locals: { datetime: datetime }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def paginate(collection, args = {})
|
|
30
|
+
super(collection, args.reverse_merge(views_prefix: 'thredded'))
|
|
31
|
+
end
|
|
32
|
+
|
|
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
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class ActivityUpdaterJob < ::ActiveJob::Base
|
|
3
|
+
queue_as :default
|
|
4
|
+
|
|
5
|
+
def perform(user_id, messageboard_id)
|
|
6
|
+
now = Time.zone.now
|
|
7
|
+
|
|
8
|
+
user_detail = Thredded::UserDetail.for_user_id(user_id)
|
|
9
|
+
user_detail.update_column(:last_seen_at, now)
|
|
10
|
+
|
|
11
|
+
Thredded::MessageboardUser
|
|
12
|
+
.where(
|
|
13
|
+
thredded_messageboard_id: messageboard_id,
|
|
14
|
+
thredded_user_detail_id: user_detail.id
|
|
15
|
+
)
|
|
16
|
+
.first_or_initialize
|
|
17
|
+
.update!(last_seen_at: now)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class NotifyPrivateTopicUsersJob < ::ActiveJob::Base
|
|
3
|
+
queue_as :default
|
|
4
|
+
|
|
5
|
+
def perform(private_topic_id)
|
|
6
|
+
private_topic = Thredded::PrivateTopic.find(private_topic_id)
|
|
7
|
+
|
|
8
|
+
NotifyPrivateTopicUsers.new(private_topic).run
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class PostMailer < Thredded::BaseMailer
|
|
3
|
+
def at_notification(post_id, emails)
|
|
4
|
+
@post = Post.find(post_id)
|
|
5
|
+
email_details = TopicEmailDecorator.new(@post.postable)
|
|
6
|
+
headers['X-SMTPAPI'] = email_details.smtp_api_tag('at_notification')
|
|
7
|
+
|
|
8
|
+
mail from: email_details.no_reply,
|
|
9
|
+
to: email_details.no_reply,
|
|
10
|
+
bcc: emails,
|
|
11
|
+
reply_to: email_details.reply_to,
|
|
12
|
+
subject: email_details.subject
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class PrivatePostMailer < Thredded::BaseMailer
|
|
3
|
+
def at_notification(post_id, emails)
|
|
4
|
+
@post = PrivatePost.find(post_id)
|
|
5
|
+
email_details = TopicEmailDecorator.new(@post.postable)
|
|
6
|
+
headers['X-SMTPAPI'] = email_details.smtp_api_tag('at_notification')
|
|
7
|
+
|
|
8
|
+
mail from: email_details.no_reply,
|
|
9
|
+
to: email_details.no_reply,
|
|
10
|
+
bcc: emails,
|
|
11
|
+
reply_to: email_details.reply_to,
|
|
12
|
+
subject: email_details.subject
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Thredded
|
|
2
|
+
class PrivateTopicMailer < Thredded::BaseMailer
|
|
3
|
+
def message_notification(private_topic_id, emails)
|
|
4
|
+
@topic = PrivateTopic.find(private_topic_id)
|
|
5
|
+
email_details = TopicEmailDecorator.new(@topic)
|
|
6
|
+
headers['X-SMTPAPI'] = email_details.smtp_api_tag('private_topic_mailer')
|
|
7
|
+
|
|
8
|
+
mail from: email_details.no_reply,
|
|
9
|
+
to: email_details.no_reply,
|
|
10
|
+
bcc: emails,
|
|
11
|
+
reply_to: email_details.reply_to,
|
|
12
|
+
subject: email_details.subject
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|