thredded 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.mkdn +21 -0
  3. data/README.mkdn +17 -1
  4. data/app/assets/stylesheets/thredded/_thredded.scss +1 -0
  5. data/app/assets/stylesheets/thredded/base/_grid.scss +6 -0
  6. data/app/assets/stylesheets/thredded/base/_nav.scss +51 -0
  7. data/app/assets/stylesheets/thredded/base/_variables.scss +1 -0
  8. data/app/assets/stylesheets/thredded/components/_base.scss +4 -0
  9. data/app/assets/stylesheets/thredded/components/_post.scss +19 -6
  10. data/app/assets/stylesheets/thredded/components/_topics.scss +6 -0
  11. data/app/assets/stylesheets/thredded/layout/_moderation.scss +53 -8
  12. data/app/assets/stylesheets/thredded/layout/_navigation.scss +1 -1
  13. data/app/assets/stylesheets/thredded/layout/_user-navigation.scss +7 -47
  14. data/app/assets/stylesheets/thredded/layout/_user.scss +10 -0
  15. data/app/commands/thredded/moderate_post.rb +1 -1
  16. data/app/controllers/thredded/messageboards_controller.rb +1 -1
  17. data/app/controllers/thredded/moderation_controller.rb +39 -5
  18. data/app/helpers/thredded/application_helper.rb +4 -2
  19. data/app/models/concerns/thredded/content_moderation_state.rb +15 -8
  20. data/app/models/concerns/thredded/post_common.rb +1 -0
  21. data/app/models/concerns/thredded/topic_common.rb +0 -1
  22. data/app/models/thredded/messageboard.rb +4 -4
  23. data/app/models/thredded/post.rb +2 -0
  24. data/app/models/thredded/post_moderation_record.rb +3 -1
  25. data/app/models/thredded/topic.rb +17 -0
  26. data/app/models/thredded/user_extender.rb +7 -0
  27. data/app/policies/thredded/post_policy.rb +2 -0
  28. data/app/policies/thredded/topic_policy.rb +6 -6
  29. data/app/view_models/thredded/post_view.rb +19 -1
  30. data/app/view_models/thredded/posts_page_view.rb +1 -0
  31. data/app/view_models/thredded/topic_view.rb +5 -1
  32. data/app/views/thredded/messageboards/_messageboard.html.erb +3 -3
  33. data/app/views/thredded/moderation/_nav.html.erb +16 -0
  34. data/app/views/thredded/moderation/_post.html.erb +9 -2
  35. data/app/views/thredded/moderation/_post_moderation_record.html.erb +22 -19
  36. data/app/views/thredded/moderation/_user_moderation_state.html.erb +3 -0
  37. data/app/views/thredded/moderation/_user_post.html.erb +7 -0
  38. data/app/views/thredded/moderation/_users_search_form.html.erb +10 -0
  39. data/app/views/thredded/moderation/history.html.erb +2 -8
  40. data/app/views/thredded/moderation/pending.html.erb +3 -10
  41. data/app/views/thredded/moderation/user.html.erb +42 -0
  42. data/app/views/thredded/moderation/users.html.erb +38 -0
  43. data/app/views/thredded/posts/_post.html.erb +4 -0
  44. data/app/views/thredded/posts_common/_header.html.erb +1 -0
  45. data/app/views/thredded/posts_common/_header_with_topic.html.erb +15 -0
  46. data/app/views/thredded/posts_common/_header_with_user_and_topic.html.erb +18 -0
  47. data/app/views/thredded/search/_form.html.erb +3 -1
  48. data/app/views/thredded/shared/_content_moderation_blocked_state.html.erb +8 -0
  49. data/app/views/thredded/shared/_nav.html.erb +8 -4
  50. data/app/views/thredded/topics/_topic.html.erb +6 -0
  51. data/app/views/thredded/users/_post.html.erb +6 -0
  52. data/app/views/thredded/users/_posts.html.erb +7 -0
  53. data/config/locales/en.yml +29 -3
  54. data/config/locales/pt-BR.yml +34 -9
  55. data/config/routes.rb +7 -2
  56. data/db/migrate/20160329231848_create_thredded.rb +22 -21
  57. data/db/upgrade_migrations/20160611094616_upgrade_v0_5_to_v0_6.rb +25 -0
  58. data/heroku.gemfile.lock +2 -2
  59. data/lib/thredded.rb +17 -5
  60. data/lib/thredded/version.rb +1 -1
  61. metadata +15 -2
@@ -18,12 +18,14 @@ module Thredded
18
18
  end
19
19
 
20
20
  # @param datetime [DateTime]
21
+ # @param default [String] a string to return if time is nil.
21
22
  # @return [String] html_safe datetime presentation
22
- def time_ago(datetime)
23
+ def time_ago(datetime, default: '-')
23
24
  timeago_tag datetime,
24
25
  lang: I18n.locale.to_s.downcase,
25
26
  format: -> (t, _opts) { t.year == Time.current.year ? :short : :long },
26
- nojs: true
27
+ nojs: true,
28
+ default: default
27
29
  end
28
30
 
29
31
  def paginate(collection, args = {})
@@ -14,19 +14,24 @@ module Thredded
14
14
  # @type [Arel::Table]
15
15
  table = arel_table
16
16
  # @type [Arel::Nodes::Node]
17
- visible_to_all =
17
+ visible =
18
18
  if Thredded.content_visible_while_pending_moderation
19
+ # All non-blocked content
19
20
  table[:moderation_state].not_eq(moderation_states[:blocked])
20
21
  else
22
+ # Only approved content
21
23
  table[:moderation_state].eq(moderation_states[:approved])
22
24
  end
23
- where(
24
- if user && !user.thredded_anonymous?
25
- visible_to_all.or(table[:user_id].eq(user.id))
26
- else
27
- visible_to_all
25
+ if user && !user.thredded_anonymous?
26
+ # Own content
27
+ visible = visible.or(table[:user_id].eq(user.id))
28
+ # Content that one can moderate
29
+ moderatable_messageboard_ids = user.thredded_can_moderate_messageboards.map(&:id)
30
+ if moderatable_messageboard_ids.present?
31
+ visible = visible.or(table[:messageboard_id].in(moderatable_messageboard_ids))
28
32
  end
29
- )
33
+ end
34
+ where(visible)
30
35
  end)
31
36
  end
32
37
 
@@ -41,7 +46,9 @@ module Thredded
41
46
 
42
47
  # Whether this is visible to the given user based on the moderation state.
43
48
  def moderation_state_visible_to_user?(user)
44
- moderation_state_visible_to_all? || (!user.thredded_anonymous? && user_id == user.id)
49
+ moderation_state_visible_to_all? ||
50
+ (!user.thredded_anonymous? &&
51
+ (user_id == user.id || user.thredded_can_moderate_messageboards.map(&:id).include?(messageboard_id)))
45
52
  end
46
53
 
47
54
  private
@@ -14,6 +14,7 @@ module Thredded
14
14
  validates :content, presence: true
15
15
 
16
16
  scope :order_oldest_first, -> { order(id: :asc) }
17
+ scope :order_newest_first, -> { order(id: :desc) }
17
18
 
18
19
  after_commit :update_parent_last_user_and_timestamp, on: [:create, :destroy]
19
20
  end
@@ -13,7 +13,6 @@ module Thredded
13
13
  scope :on_page, -> (page_num) { page(page_num).per(30) }
14
14
 
15
15
  validates :hash_id, presence: true, uniqueness: true
16
- validates :last_user_id, presence: true
17
16
  validates :posts_count, numericality: true
18
17
 
19
18
  before_validation do
@@ -25,8 +25,8 @@ module Thredded
25
25
  has_many :user_messageboard_preferences, dependent: :destroy
26
26
  has_many :posts, dependent: :destroy
27
27
  has_many :topics, dependent: :destroy, inverse_of: :messageboard
28
- has_one :latest_topic, -> { order_recently_updated_first },
29
- class_name: 'Thredded::Topic'
28
+
29
+ belongs_to :last_topic, class_name: 'Thredded::Topic'
30
30
 
31
31
  has_many :user_details, through: :posts
32
32
  has_many :messageboard_users,
@@ -54,8 +54,8 @@ module Thredded
54
54
  scope :top_level_messageboards, -> { where(group: nil) }
55
55
  scope :by_messageboard_group, ->(group) { where(group: group.id) }
56
56
 
57
- def latest_user
58
- latest_topic.last_user
57
+ def last_user
58
+ last_topic.try(:last_user)
59
59
  end
60
60
 
61
61
  def slug_candidates
@@ -21,6 +21,8 @@ module Thredded
21
21
  has_many :moderation_records,
22
22
  class_name: 'Thredded::PostModerationRecord',
23
23
  dependent: :nullify
24
+ has_one :last_moderation_record, -> { order_newest_first },
25
+ class_name: 'Thredded::PostModerationRecord'
24
26
 
25
27
  validates :messageboard_id, presence: true
26
28
 
@@ -8,6 +8,8 @@ module Thredded
8
8
  end
9
9
  validates :previous_moderation_state, presence: true
10
10
 
11
+ scope :order_newest_first, -> { order(created_at: :desc, id: :desc) }
12
+
11
13
  belongs_to :messageboard, inverse_of: :post_moderation_records
12
14
  validates :messageboard_id, presence: true
13
15
  belongs_to :post, inverse_of: :moderation_records
@@ -37,7 +39,7 @@ module Thredded
37
39
  post: post,
38
40
  post_content: post.content,
39
41
  post_user: post.user,
40
- post_user_name: post.user.send(Thredded.user_name_column),
42
+ post_user_name: post.user.try(:send, Thredded.user_name_column),
41
43
  messageboard_id: post.messageboard_id,
42
44
  )
43
45
  end
@@ -63,6 +63,8 @@ module Thredded
63
63
  source: :user,
64
64
  through: :user_follows
65
65
 
66
+ after_commit :update_messageboard_last_topic, on: [:create, :destroy]
67
+
66
68
  def self.find_by_slug!(slug)
67
69
  friendly.find(slug)
68
70
  rescue ActiveRecord::RecordNotFound
@@ -107,6 +109,11 @@ module Thredded
107
109
  title_changed?
108
110
  end
109
111
 
112
+ # @return [Thredded::PostModerationRecord, nil]
113
+ def last_moderation_record
114
+ first_post.try(:last_moderation_record)
115
+ end
116
+
110
117
  private
111
118
 
112
119
  def slug_candidates
@@ -115,5 +122,15 @@ module Thredded
115
122
  [:title, '-topic'],
116
123
  ]
117
124
  end
125
+
126
+ def update_messageboard_last_topic
127
+ return if messageboard.destroyed?
128
+ last_topic = if destroyed?
129
+ messageboard.topics.order_recently_updated_first.select(:id).first
130
+ else
131
+ self
132
+ end
133
+ messageboard.update!(last_topic_id: last_topic.try(:id))
134
+ end
118
135
  end
119
136
  end
@@ -41,6 +41,13 @@ module Thredded
41
41
  opt.has_many :thredded_post_moderation_records, foreign_key: 'post_user_id', inverse_of: :post_user
42
42
  opt.has_many :thredded_post_moderated_records, foreign_key: 'moderator_id', inverse_of: :moderator
43
43
  end
44
+
45
+ scope :left_join_thredded_user_details, (lambda do
46
+ users = arel_table
47
+ user_details = Thredded::UserDetail.arel_table
48
+ joins(users.join(user_details, Arel::Nodes::OuterJoin)
49
+ .on(users[:id].eq(user_details[:user_id])).join_sources)
50
+ end)
44
51
  end
45
52
 
46
53
  def thredded_user_preference
@@ -41,6 +41,8 @@ module Thredded
41
41
  @post.postable.first_post != @post && update?
42
42
  end
43
43
 
44
+ delegate :moderate?, to: :messageboard_policy
45
+
44
46
  private
45
47
 
46
48
  def messageboard_policy
@@ -21,17 +21,17 @@ module Thredded
21
21
  # @param user [Thredded.user_class]
22
22
  # @param topic [Thredded::Topic]
23
23
  def initialize(user, topic)
24
- @user = user
25
- @topic = topic
26
- @messageboard_user_permission = MessageboardPolicy.new(user, topic.messageboard)
24
+ @user = user
25
+ @topic = topic
26
+ @messageboard_policy = MessageboardPolicy.new(user, topic.messageboard)
27
27
  end
28
28
 
29
29
  def create?
30
- @messageboard_user_permission.post?
30
+ @messageboard_policy.post?
31
31
  end
32
32
 
33
33
  def read?
34
- @messageboard_user_permission.read? && @topic.moderation_state_visible_to_user?(@user)
34
+ @messageboard_policy.read? && @topic.moderation_state_visible_to_user?(@user)
35
35
  end
36
36
 
37
37
  def update?
@@ -43,7 +43,7 @@ module Thredded
43
43
  end
44
44
 
45
45
  def moderate?
46
- @messageboard_user_permission.moderate?
46
+ @messageboard_policy.moderate?
47
47
  end
48
48
  end
49
49
  end
@@ -11,6 +11,7 @@ module Thredded
11
11
  :pending_moderation?,
12
12
  :approved?,
13
13
  :blocked?,
14
+ :last_moderation_record,
14
15
  to: :@post
15
16
 
16
17
  # @param post [Thredded::PostCommon]
@@ -28,6 +29,10 @@ module Thredded
28
29
  @can_destroy ||= @policy.destroy?
29
30
  end
30
31
 
32
+ def can_moderate?
33
+ @can_moderate ||= @policy.moderate?
34
+ end
35
+
31
36
  def edit_path
32
37
  Thredded::UrlsHelper.edit_post_path(@post)
33
38
  end
@@ -36,17 +41,30 @@ module Thredded
36
41
  Thredded::UrlsHelper.delete_post_path(@post)
37
42
  end
38
43
 
44
+ def permalink_path
45
+ Thredded::UrlsHelper.post_permalink_path(@post.id)
46
+ end
47
+
48
+ # rubocop:disable Metrics/CyclomaticComplexity
39
49
  def cache_key
50
+ moderation_state = unless @post.private_topic_post?
51
+ if @post.pending_moderation? && !Thredded.content_visible_while_pending_moderation
52
+ 'p'
53
+ elsif @post.blocked?
54
+ '-'
55
+ end
56
+ end
40
57
  [
41
58
  I18n.locale,
42
59
  @post,
43
60
  @post.user,
61
+ moderation_state || '+',
44
62
  [
45
- !@post.private_topic_post? && @post.pending_moderation? && !Thredded.content_visible_while_pending_moderation,
46
63
  can_update?,
47
64
  can_destroy?
48
65
  ].map { |p| p ? '+' : '-' } * ''
49
66
  ]
50
67
  end
68
+ # rubocop:enable Metrics/CyclomaticComplexity
51
69
  end
52
70
  end
@@ -6,6 +6,7 @@ module Thredded
6
6
  # A view model for a page of PostViews.
7
7
  class PostsPageView
8
8
  delegate :to_ary,
9
+ :present?,
9
10
  to: :@post_views
10
11
  delegate :total_pages,
11
12
  :current_page,
@@ -2,7 +2,7 @@
2
2
  module Thredded
3
3
  # A view model for Topic.
4
4
  class TopicView < BaseTopicView
5
- delegate :categories, :id,
5
+ delegate :categories, :id, :blocked?, :last_moderation_record,
6
6
  to: :@topic
7
7
 
8
8
  # @param topic [TopicCommon]
@@ -30,6 +30,10 @@ module Thredded
30
30
  ].compact
31
31
  end
32
32
 
33
+ def can_moderate?
34
+ @policy.moderate?
35
+ end
36
+
33
37
  def edit_path
34
38
  Thredded::UrlsHelper.edit_messageboard_topic_path(@topic.messageboard, @topic)
35
39
  end
@@ -11,11 +11,11 @@
11
11
 
12
12
  <p class="thredded--messageboard--description"><%= messageboard.description %></p>
13
13
 
14
- <% if messageboard.latest_topic %>
14
+ <% if messageboard.last_topic %>
15
15
  <p class="thredded--messageboard--byline">
16
16
  <%= t 'thredded.messageboard.last_updated_by_html',
17
- time_ago: time_ago(messageboard.latest_topic.updated_at),
18
- user: messageboard.latest_user %>
17
+ time_ago: time_ago(messageboard.last_topic.updated_at),
18
+ user: messageboard.last_user %>
19
19
  </p>
20
20
  <% end %>
21
21
  <% end %>
@@ -0,0 +1,16 @@
1
+ <% content_for :thredded_main_navigation do %>
2
+ <nav class="thredded--moderation-navigation">
3
+ <ul class="thredded--moderation-navigation--items">
4
+ <li class="thredded--moderation-navigation--item thredded--moderation-navigation--pending">
5
+ <%= link_to t('thredded.nav.moderation_pending'), pending_moderation_path %>
6
+ </li>
7
+ <li class="thredded--moderation-navigation--item thredded--moderation-navigation--history">
8
+ <%= link_to t('thredded.nav.moderation_history'), moderation_history_path %>
9
+ </li>
10
+ <li class="thredded--moderation-navigation--item thredded--moderation-navigation--users">
11
+ <%= link_to t('thredded.nav.moderation_users'), users_moderation_path %>
12
+ </li>
13
+ </ul>
14
+ <%= render 'users_search_form' %>
15
+ </nav>
16
+ <% end %>
@@ -1,6 +1,13 @@
1
1
  <%# @param post [Thredded::PostView] %>
2
- <%= content_tag :article, id: dom_id(post), class: 'thredded--post' do %>
3
- <%= render 'thredded/posts_common/header', post: post %>
2
+ <%= content_tag :article, id: dom_id(post), class: 'thredded--post thredded--post-moderation' do %>
3
+ <%= render 'thredded/posts_common/header_with_user_and_topic',
4
+ post: post,
5
+ post_user_link: if post.user
6
+ link_to(post.user, user_moderation_path(post.user.id))
7
+ else
8
+ content_tag :em, t('thredded.null_user_name')
9
+ end
10
+ %>
4
11
  <%= render 'thredded/posts_common/content', post: post %>
5
12
  <%= render 'thredded/posts_common/actions', post: post %>
6
13
  <%= render 'post_moderation_actions', post: post %>
@@ -1,20 +1,24 @@
1
1
  <%
2
- post = post_moderation_record.post
2
+ record = post_moderation_record
3
+ post = record.post
4
+ if post
5
+ post_view = Thredded::PostView.new(post, policy(post))
6
+ end
3
7
  moderation_state_notice_args = {
4
- moderator: user_link(post_moderation_record.moderator),
5
- time_ago: time_ago(post_moderation_record.created_at)
8
+ moderator: user_link(record.moderator),
9
+ time_ago: time_ago(record.created_at)
6
10
  }
7
11
  %>
8
- <article class="thredded--post-moderation-record thredded--post-moderation-record-<%= post_moderation_record.moderation_state %>">
12
+ <article class="thredded--post-moderation-record thredded--post-moderation-record-<%= record.moderation_state %>">
9
13
  <header class="thredded--post-moderation-record--header">
10
14
  <p class="thredded--post-moderation-record--moderation-state-notice">
11
- <% if post_moderation_record.approved? %>
15
+ <% if record.approved? %>
12
16
  <%= t('thredded.moderation.post_approved_html', moderation_state_notice_args) %>
13
- <% elsif post_moderation_record.blocked? %>
17
+ <% elsif record.blocked? %>
14
18
  <%= t('thredded.moderation.post_blocked_html', moderation_state_notice_args) %>
15
19
  <% end %>
16
20
  </p>
17
- <% if post && post.content != post_moderation_record.post_content %>
21
+ <% if post && post.content != record.post_content %>
18
22
  <p class="thredded--post-moderation-record--content-changed-notice">
19
23
  <%= t('thredded.moderation.posts_content_changed_since_moderation_html', post_url: post_permalink_path(post)) %>
20
24
  </p>
@@ -22,21 +26,20 @@
22
26
  <%= t 'thredded.moderation.post_deleted_notice' unless post %>
23
27
  </header>
24
28
  <article class="thredded--post">
25
- <header>
26
- <% if post_moderation_record.post_user %>
27
- <%= image_tag Thredded.avatar_url.call(post_moderation_record.post_user), class: 'thredded--post--avatar' %>
28
- <h2 class="thredded--post--user"><%= user_link post_moderation_record.post_user %></h2>
29
+ <% post_user_link = capture do %>
30
+ <% if post.user %>
31
+ <%= link_to post.user, user_moderation_path(post.user.id) %>
29
32
  <% else %>
30
- <h2 class="thredded--post--user">
31
- <%= post_moderation_record.post_user_name %>, <em><%= t 'thredded.null_user_name' %></em>
32
- </h2>
33
+ <%= safe_join [record.post_user_name, content_tag(:em, t('thredded.null_user_name'))].compact, ', ' %>
33
34
  <% end %>
34
- <% if post %>
35
- <p class="thredded--post--created-at"><%= time_ago post.created_at %></p>
36
- <% end %>
37
- </header>
35
+ <% end %>
36
+ <% if post %>
37
+ <%= render 'thredded/posts_common/header_with_user_and_topic', post: post_view, post_user_link: post_user_link %>
38
+ <% else %>
39
+ <header><h2 class="thredded--post--user"><%= post_user_link %></h2></header>
40
+ <% end %>
38
41
  <div class="thredded--post--content">
39
- <%= Thredded::ContentFormatter.new(self).format_content(post_moderation_record.post_content) %>
42
+ <%= Thredded::ContentFormatter.new(self).format_content(record.post_content) %>
40
43
  </div>
41
44
  </article>
42
45
  <%= render 'post_moderation_actions', post: post if post %>
@@ -0,0 +1,3 @@
1
+ <% user_detail = user.thredded_user_detail %>
2
+ <%= user_detail.moderation_state.to_s.humanize %>
3
+ <%= time_ago user_detail.moderation_state_changed_at, default: '' %>
@@ -0,0 +1,7 @@
1
+ <%# @param post [Thredded::PostView] %>
2
+ <%= content_tag :article, id: dom_id(post), class: 'thredded--post thredded--post-moderation' do %>
3
+ <%= render 'thredded/posts_common/header_with_topic', post: post %>
4
+ <%= render 'thredded/posts_common/content', post: post %>
5
+ <%= render 'thredded/posts_common/actions', post: post %>
6
+ <%= render 'post_moderation_actions', post: post %>
7
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <%= form_tag users_moderation_path, class: 'thredded--form thredded--navigation--search', method: 'get' do %>
2
+ <%= label_tag :q, t('thredded.moderation.search_users.form_label') %>
3
+ <%= text_field_tag :q, @query,
4
+ type: 'search',
5
+ required: true,
6
+ # If there are no results the user will likely want to change the query, so auto-focus.
7
+ autofocus: @query.presence && !@users.presence,
8
+ placeholder: t('thredded.moderation.search_users.form_placeholder') %>
9
+ <button type="submit"><%= t 'thredded.search.form.btn_submit' %></button>
10
+ <% end %>
@@ -1,13 +1,7 @@
1
1
  <% content_for :thredded_page_title,
2
2
  safe_join([t('thredded.nav.moderation'), t('thredded.nav.moderation_history')], ': ') %>
3
- <% content_for :thredded_page_id, 'thredded--pending-moderation' %>
4
- <% content_for :thredded_breadcrumbs do %>
5
- <ul class="thredded--navigation-breadcrumbs">
6
- <li><%= link_to t('thredded.nav.all_messageboards'), messageboards_path %></li>
7
- <li><%= link_to t('thredded.nav.moderation'), pending_moderation_path %></li>
8
- <li><%= link_to t('thredded.nav.moderation_history'), moderation_history_path %></li>
9
- </ul>
10
- <% end %>
3
+ <% content_for :thredded_page_id, 'thredded--moderation-history' %>
4
+ <%= render 'nav' %>
11
5
 
12
6
  <%= thredded_page do %>
13
7
  <%= content_tag :section, class: 'thredded--main-section' do %>