thredded 0.10.0 → 0.10.1

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -17
  3. data/app/assets/images/thredded/three-dot-menu.svg +3 -0
  4. data/app/assets/stylesheets/thredded/base/_tables.scss +1 -0
  5. data/app/assets/stylesheets/thredded/base/_variables.scss +24 -1
  6. data/app/assets/stylesheets/thredded/components/_currently-online.scss +1 -0
  7. data/app/assets/stylesheets/thredded/components/_messageboard.scss +18 -10
  8. data/app/assets/stylesheets/thredded/components/_post.scss +84 -13
  9. data/app/assets/stylesheets/thredded/components/_topics.scss +7 -1
  10. data/app/assets/stylesheets/thredded/layout/_main-container.scss +1 -0
  11. data/app/assets/stylesheets/thredded/layout/_search-navigation.scss +15 -6
  12. data/app/controllers/thredded/application_controller.rb +6 -3
  13. data/app/controllers/thredded/moderation_controller.rb +1 -1
  14. data/app/controllers/thredded/posts_controller.rb +19 -22
  15. data/app/controllers/thredded/preferences_controller.rb +1 -2
  16. data/app/controllers/thredded/private_posts_controller.rb +77 -0
  17. data/app/controllers/thredded/private_topics_controller.rb +1 -1
  18. data/app/controllers/thredded/read_states_controller.rb +1 -1
  19. data/app/controllers/thredded/topics_controller.rb +1 -1
  20. data/app/forms/thredded/private_topic_form.rb +3 -3
  21. data/app/forms/thredded/topic_form.rb +1 -1
  22. data/app/helpers/thredded/application_helper.rb +12 -1
  23. data/app/helpers/thredded/render_helper.rb +14 -0
  24. data/app/helpers/thredded/urls_helper.rb +8 -0
  25. data/app/models/concerns/thredded/post_common.rb +20 -0
  26. data/app/models/concerns/thredded/user_topic_read_state_common.rb +6 -0
  27. data/app/models/thredded/null_user_topic_read_state.rb +4 -0
  28. data/app/policies/thredded/post_policy.rb +4 -0
  29. data/app/policies/thredded/private_post_policy.rb +4 -0
  30. data/app/view_hooks/thredded/all_view_hooks.rb +15 -0
  31. data/app/view_models/thredded/base_topic_view.rb +1 -1
  32. data/app/view_models/thredded/post_view.rb +23 -21
  33. data/app/view_models/thredded/posts_page_view.rb +4 -2
  34. data/app/view_models/thredded/topic_posts_page_view.rb +1 -1
  35. data/app/view_models/thredded/topic_view.rb +1 -1
  36. data/app/view_models/thredded/topics_page_view.rb +1 -0
  37. data/app/views/thredded/moderation/_post.html.erb +2 -2
  38. data/app/views/thredded/moderation/_user_post.html.erb +2 -2
  39. data/app/views/thredded/moderation/activity.html.erb +3 -1
  40. data/app/views/thredded/moderation/pending.html.erb +3 -1
  41. data/app/views/thredded/moderation/user.html.erb +3 -1
  42. data/app/views/thredded/posts/_content.html.erb +1 -0
  43. data/app/views/thredded/posts/_post.html.erb +11 -12
  44. data/app/views/thredded/posts/edit.html.erb +3 -4
  45. data/app/views/thredded/posts_common/_actions.html.erb +21 -8
  46. data/app/views/thredded/posts_common/actions/_delete.html.erb +4 -0
  47. data/app/views/thredded/posts_common/actions/_edit.html.erb +2 -0
  48. data/app/views/thredded/posts_common/actions/_mark_as_unread.html.erb +2 -0
  49. data/app/views/thredded/private_posts/_content.html.erb +1 -0
  50. data/app/views/thredded/private_posts/_private_post.html.erb +5 -6
  51. data/app/views/thredded/private_posts/edit.html.erb +18 -0
  52. data/app/views/thredded/private_topics/show.html.erb +3 -1
  53. data/app/views/thredded/shared/_nav.html.erb +1 -1
  54. data/app/views/thredded/shared/nav/_standalone.html.erb +1 -1
  55. data/app/views/thredded/topics/_sticky_topics_divider.html.erb +1 -0
  56. data/app/views/thredded/topics/_topic.html.erb +4 -0
  57. data/app/views/thredded/topics/index.html.erb +1 -1
  58. data/app/views/thredded/topics/show.html.erb +1 -1
  59. data/app/views/thredded/users/_post.html.erb +2 -2
  60. data/app/views/thredded/users/_posts.html.erb +1 -1
  61. data/config/locales/en.yml +1 -0
  62. data/config/locales/es.yml +1 -0
  63. data/config/locales/pl.yml +1 -0
  64. data/config/locales/pt-BR.yml +1 -0
  65. data/config/routes.rb +9 -4
  66. data/lib/generators/thredded/install/templates/initializer.rb +7 -0
  67. data/lib/thredded.rb +4 -0
  68. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +62 -0
  69. data/lib/thredded/version.rb +1 -1
  70. metadata +15 -4
@@ -4,8 +4,7 @@ module Thredded
4
4
  before_action :thredded_require_login!,
5
5
  :init_preferences
6
6
 
7
- def edit
8
- end
7
+ def edit; end
9
8
 
10
9
  def update
11
10
  if @preferences.save
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ # A controller for managing {PrivatePost}s.
4
+ class PrivatePostsController < Thredded::ApplicationController
5
+ include ActionView::RecordIdentifier
6
+
7
+ helper_method :topic
8
+ after_action :update_user_activity
9
+
10
+ after_action :verify_authorized
11
+
12
+ def create
13
+ post = parent_topic.posts.build(post_params)
14
+ authorize_creating post
15
+ post.save!
16
+
17
+ redirect_to post_path(post, user: thredded_current_user)
18
+ end
19
+
20
+ def edit
21
+ authorize post, :update?
22
+ end
23
+
24
+ def update
25
+ authorize post, :update?
26
+ post.update_attributes(post_params.except(:user, :ip))
27
+
28
+ redirect_to post_path(post, user: thredded_current_user)
29
+ end
30
+
31
+ def destroy
32
+ authorize post, :destroy?
33
+ post.destroy!
34
+
35
+ redirect_back fallback_location: topic_url(topic),
36
+ notice: I18n.t('thredded.posts.deleted_notice')
37
+ end
38
+
39
+ def mark_as_unread
40
+ authorize post, :read?
41
+ page = post.page
42
+ post.mark_as_unread(thredded_current_user, page)
43
+ after_mark_as_unread # customization hook
44
+ end
45
+
46
+ private
47
+
48
+ def after_mark_as_unread
49
+ redirect_to private_topics_path
50
+ end
51
+
52
+ def topic
53
+ post.postable
54
+ end
55
+
56
+ def post_params
57
+ params.require(:post)
58
+ .permit(:content)
59
+ .merge(user: thredded_current_user, ip: request.remote_ip)
60
+ end
61
+
62
+ def parent_topic
63
+ PrivateTopic
64
+ .includes(:private_users)
65
+ .friendly
66
+ .find(params[:private_topic_id])
67
+ end
68
+
69
+ def post
70
+ @post ||= Thredded::PrivatePost.find(params[:id])
71
+ end
72
+
73
+ def current_page
74
+ params[:page].nil? ? 1 : params[:page].to_i
75
+ end
76
+ end
77
+ end
@@ -30,7 +30,7 @@ module Thredded
30
30
  .page(current_page)
31
31
  @posts = Thredded::TopicPostsPageView.new(thredded_current_user, private_topic, page_scope)
32
32
 
33
- if signed_in?
33
+ if thredded_signed_in?
34
34
  Thredded::UserPrivateTopicReadState.touch!(
35
35
  thredded_current_user.id, private_topic.id, page_scope.last, current_page
36
36
  )
@@ -4,7 +4,7 @@ module Thredded
4
4
  before_action :thredded_require_login!
5
5
 
6
6
  def update
7
- MarkAllRead.run(thredded_current_user) if signed_in?
7
+ MarkAllRead.run(thredded_current_user) if thredded_signed_in?
8
8
 
9
9
  redirect_to request.referer
10
10
  end
@@ -32,7 +32,7 @@ module Thredded
32
32
  .page(current_page)
33
33
  @posts = Thredded::TopicPostsPageView.new(thredded_current_user, topic, page_scope)
34
34
 
35
- if signed_in?
35
+ if thredded_signed_in?
36
36
  Thredded::UserTopicReadState.touch!(
37
37
  thredded_current_user.id, topic.id, page_scope.last, current_page
38
38
  )
@@ -64,7 +64,7 @@ module Thredded
64
64
 
65
65
  def topic_categories
66
66
  if category_ids
67
- ids = category_ids.reject(&:empty?).map(&:to_i)
67
+ ids = category_ids.reject(&:empty?)
68
68
  Category.where(id: ids)
69
69
  else
70
70
  []
@@ -82,8 +82,8 @@ module Thredded
82
82
  def normalized_user_ids
83
83
  user_ids
84
84
  .reject(&:empty?)
85
- .map(&:to_i)
86
- .push(user.id)
85
+ .map(&:to_s)
86
+ .push(user.id.to_s)
87
87
  .uniq
88
88
  end
89
89
 
@@ -65,7 +65,7 @@ module Thredded
65
65
 
66
66
  def topic_categories
67
67
  if category_ids
68
- ids = category_ids.reject(&:empty?).map(&:to_i)
68
+ ids = category_ids.reject(&:empty?)
69
69
  Category.where(id: ids)
70
70
  else
71
71
  []
@@ -3,6 +3,7 @@ module Thredded
3
3
  module ApplicationHelper
4
4
  include ::Thredded::UrlsHelper
5
5
  include ::Thredded::NavHelper
6
+ include ::Thredded::RenderHelper
6
7
 
7
8
  # @return [AllViewHooks] View hooks configuration.
8
9
  def view_hooks
@@ -68,6 +69,16 @@ module Thredded
68
69
  end
69
70
  end
70
71
 
72
+ # @param posts [Thredded::PostsPageView, Array<Thredded::PostView>]
73
+ # @param partial [String]
74
+ # @param content_partial [String]
75
+ def render_posts(posts, partial: 'thredded/posts/post', content_partial: 'thredded/posts/content')
76
+ posts_with_contents = render_collection_to_strings_with_cache(
77
+ partial: content_partial, collection: posts, as: :post, expires_in: 1.week
78
+ )
79
+ render partial: partial, collection: posts_with_contents, as: :post_and_content
80
+ end
81
+
71
82
  def paginate(collection, args = {})
72
83
  super(collection, args.reverse_merge(views_prefix: 'thredded'))
73
84
  end
@@ -96,7 +107,7 @@ module Thredded
96
107
 
97
108
  def unread_private_topics_count
98
109
  @unread_private_topics_count ||=
99
- if signed_in?
110
+ if thredded_signed_in?
100
111
  Thredded::PrivateTopic
101
112
  .for_user(thredded_current_user)
102
113
  .unread(thredded_current_user)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ module RenderHelper
4
+ # @param collection [Array<T>]
5
+ # @param partial [String]
6
+ # @param expires_in [ActiveSupport::Duration]
7
+ # @return Array<[T, String]>
8
+ def render_collection_to_strings_with_cache(collection:, partial:, expires_in:, **opts)
9
+ CollectionToStringsWithCacheRenderer.new(lookup_context).render_collection_to_strings_with_cache(
10
+ self, collection: collection, partial: partial, expires_in: expires_in, **opts
11
+ )
12
+ end
13
+ end
14
+ end
@@ -103,5 +103,13 @@ module Thredded
103
103
  messageboards_search_path
104
104
  end
105
105
  end
106
+
107
+ def mark_unread_path(post, _params = {})
108
+ if post.private_topic_post?
109
+ mark_as_unread_private_topic_private_post_path(post.postable, post)
110
+ else
111
+ mark_as_unread_messageboard_topic_post_path(post.messageboard, post.postable, post)
112
+ end
113
+ end
106
114
  end
107
115
  end
@@ -48,6 +48,26 @@ module Thredded
48
48
  .in(user_names)
49
49
  end
50
50
 
51
+ # Marks all the posts from the given one as unread for the given user
52
+ # @param user [Thredded.user_class]
53
+ # @param page [Integer]
54
+ def mark_as_unread(user, page)
55
+ if previous_post.nil?
56
+ read_state = postable.user_read_states.find_by(user_id: user.id)
57
+ read_state.destroy if read_state
58
+ else
59
+ read_state = postable.user_read_states.create_with(
60
+ read_at: previous_post.created_at,
61
+ page: page
62
+ ).find_or_create_by(user_id: user.id)
63
+ read_state.update_columns(read_at: previous_post.created_at, page: page)
64
+ end
65
+ end
66
+
67
+ def previous_post
68
+ @previous_post ||= postable.posts.order_newest_first.find_by('created_at < ?', created_at)
69
+ end
70
+
51
71
  private
52
72
 
53
73
  def ensure_user_detail
@@ -12,6 +12,12 @@ module Thredded
12
12
  postable.last_post_at <= read_at
13
13
  end
14
14
 
15
+ # @param post [Post or PrivatePost]
16
+ # @return [Boolean]
17
+ def post_read?(post)
18
+ post.created_at <= read_at
19
+ end
20
+
15
21
  module ClassMethods
16
22
  # @param user_id [Integer]
17
23
  # @param topic_id [Integer]
@@ -8,5 +8,9 @@ module Thredded
8
8
  def read?
9
9
  false
10
10
  end
11
+
12
+ def post_read?(_post)
13
+ false
14
+ end
11
15
  end
12
16
  end
@@ -40,6 +40,10 @@ module Thredded
40
40
  !@post.first_post_in_topic? && update?
41
41
  end
42
42
 
43
+ def anonymous?
44
+ @user.thredded_anonymous?
45
+ end
46
+
43
47
  delegate :moderate?, to: :messageboard_policy
44
48
 
45
49
  private
@@ -24,6 +24,10 @@ module Thredded
24
24
  !@post.first_post_in_topic? && update?
25
25
  end
26
26
 
27
+ def anonymous?
28
+ @user.thredded_anonymous?
29
+ end
30
+
27
31
  private
28
32
 
29
33
  def own_post?
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
3
  class AllViewHooks
4
+ # @return [PostCommon]
5
+ attr_reader :post_common
4
6
  # @return [PostsCommon]
5
7
  attr_reader :posts_common
6
8
  # @return [PostForm]
@@ -23,6 +25,7 @@ module Thredded
23
25
  end
24
26
 
25
27
  def initialize
28
+ @post_common = PostCommon.new
26
29
  @posts_common = PostsCommon.new
27
30
  @post_form = PostForm.new
28
31
  @moderation_user_page = ModerationUserPage.new
@@ -42,6 +45,18 @@ module Thredded
42
45
  end
43
46
  end
44
47
 
48
+ class PostCommon
49
+ # @return [Thredded::AllViewHooks::ViewHook]
50
+ attr_reader :actions
51
+ # @return [Thredded::AllViewHooks::ViewHook]
52
+ attr_reader :mark_as_unread
53
+
54
+ def initialize
55
+ @actions = ViewHook.new
56
+ @mark_as_unread = ViewHook.new
57
+ end
58
+ end
59
+
45
60
  class PostForm
46
61
  # @return [Thredded::AllViewHooks::ViewHook]
47
62
  attr_reader :content_text_area
@@ -11,7 +11,7 @@ module Thredded
11
11
  :to_model,
12
12
  to: :@topic
13
13
 
14
- delegate :read?,
14
+ delegate :read?, :post_read?,
15
15
  to: :@read_state
16
16
 
17
17
  # @param topic [TopicCommon]
@@ -15,9 +15,11 @@ module Thredded
15
15
 
16
16
  # @param post [Thredded::PostCommon]
17
17
  # @param policy [#update? #destroy?]
18
- def initialize(post, policy)
18
+ # @param policy [Thredded::TopicView]
19
+ def initialize(post, policy, topic_view: nil)
19
20
  @post = post
20
21
  @policy = policy
22
+ @topic_view = topic_view
21
23
  end
22
24
 
23
25
  def can_update?
@@ -36,6 +38,10 @@ module Thredded
36
38
  Thredded::UrlsHelper.edit_post_path(@post)
37
39
  end
38
40
 
41
+ def mark_unread_path
42
+ Thredded::UrlsHelper.mark_unread_path(@post)
43
+ end
44
+
39
45
  def destroy_path
40
46
  Thredded::UrlsHelper.delete_post_path(@post)
41
47
  end
@@ -44,27 +50,23 @@ module Thredded
44
50
  Thredded::UrlsHelper.post_permalink_path(@post.id)
45
51
  end
46
52
 
47
- # rubocop:disable Metrics/CyclomaticComplexity
53
+ # This cache key is used only for caching the content.
48
54
  def cache_key
49
- moderation_state = unless @post.private_topic_post?
50
- if @post.pending_moderation? && !Thredded.content_visible_while_pending_moderation
51
- 'p'
52
- elsif @post.blocked?
53
- '-'
54
- end
55
- end
56
- [
57
- I18n.locale,
58
- @post.cache_key,
59
- (@post.messageboard_id unless @post.private_topic_post?),
60
- @post.user ? @post.user.cache_key : 'users/nil',
61
- moderation_state || '+',
62
- [
63
- can_update?,
64
- can_destroy?
65
- ].map { |p| p ? '+' : '-' } * ''
66
- ].compact.join('/')
55
+ @post.cache_key
56
+ end
57
+
58
+ POST_IS_READ = :read
59
+ POST_IS_UNREAD = :unread
60
+
61
+ # returns nil if read state is not appropriate to the view (i.e. viewing posts outside a topic)
62
+ def read_state
63
+ if @topic_view.nil? || @policy.anonymous?
64
+ nil
65
+ elsif @topic_view.post_read?(@post)
66
+ POST_IS_READ
67
+ else
68
+ POST_IS_UNREAD
69
+ end
67
70
  end
68
- # rubocop:enable Metrics/CyclomaticComplexity
69
71
  end
70
72
  end
@@ -16,9 +16,11 @@ module Thredded
16
16
 
17
17
  # @param user [Thredded.user_class] the user who is viewing the posts page
18
18
  # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>]
19
- def initialize(user, paginated_scope)
19
+ def initialize(user, paginated_scope, topic_view: nil)
20
20
  @paginated_scope = paginated_scope
21
- @post_views = paginated_scope.map { |post| Thredded::PostView.new(post, Pundit.policy!(user, post)) }
21
+ @post_views = paginated_scope.map do |post|
22
+ Thredded::PostView.new(post, Pundit.policy!(user, post), topic_view: topic_view)
23
+ end
22
24
  end
23
25
  end
24
26
  end
@@ -9,8 +9,8 @@ module Thredded
9
9
  # @param topic [Thredded::TopicCommon]
10
10
  # @param paginated_scope [ActiveRecord::Relation<Thredded::PostCommon>]
11
11
  def initialize(user, topic, paginated_scope)
12
- super(user, paginated_scope)
13
12
  @topic = "#{paginated_scope.reflect_on_association(:postable).klass}View".constantize.from_user(topic, user)
13
+ super(user, paginated_scope, topic_view: @topic)
14
14
  end
15
15
  end
16
16
  end
@@ -2,7 +2,7 @@
2
2
  module Thredded
3
3
  # A view model for Topic.
4
4
  class TopicView < Thredded::BaseTopicView
5
- delegate :categories, :id, :blocked?, :last_moderation_record, :followers,
5
+ delegate :sticky?, :locked?, :categories, :id, :blocked?, :last_moderation_record, :followers,
6
6
  :last_post, :messageboard_id, :messageboard_name,
7
7
  to: :@topic
8
8
 
@@ -6,6 +6,7 @@ module Thredded
6
6
  :to_ary,
7
7
  :blank?,
8
8
  :empty?,
9
+ :[],
9
10
  to: :@topic_views
10
11
  delegate :total_pages,
11
12
  :current_page,