thredded 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
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,