thredded 0.15.3 → 0.15.4

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/javascripts/thredded/components/submit_hotkey.es6 +18 -0
  4. data/app/assets/stylesheets/thredded/components/_pagination.scss +1 -1
  5. data/app/assets/stylesheets/thredded/components/_post-form.scss +0 -1
  6. data/app/assets/stylesheets/thredded/components/_topics.scss +2 -28
  7. data/app/controllers/thredded/application_controller.rb +2 -0
  8. data/app/controllers/thredded/post_permalinks_controller.rb +1 -1
  9. data/app/controllers/thredded/post_previews_controller.rb +1 -1
  10. data/app/controllers/thredded/posts_controller.rb +2 -3
  11. data/app/controllers/thredded/private_post_permalinks_controller.rb +1 -1
  12. data/app/controllers/thredded/private_post_previews_controller.rb +1 -1
  13. data/app/controllers/thredded/private_posts_controller.rb +2 -3
  14. data/app/helpers/thredded/urls_helper.rb +28 -1
  15. data/app/models/concerns/thredded/content_moderation_state.rb +2 -2
  16. data/app/models/concerns/thredded/post_common.rb +1 -1
  17. data/app/models/concerns/thredded/topic_common.rb +1 -1
  18. data/app/models/thredded/messageboard.rb +0 -3
  19. data/app/models/thredded/post.rb +8 -0
  20. data/app/models/thredded/post_moderation_record.rb +2 -0
  21. data/app/models/thredded/private_post.rb +8 -0
  22. data/app/models/thredded/private_topic.rb +14 -0
  23. data/app/models/thredded/topic.rb +2 -2
  24. data/app/models/thredded/user_extender.rb +2 -2
  25. data/app/views/thredded/posts_common/_form.html.erb +1 -0
  26. data/app/views/thredded/posts_common/form/_content_field.html.erb +1 -1
  27. data/app/views/thredded/private_topics/_form.html.erb +2 -0
  28. data/app/views/thredded/private_topics/_private_topic.html.erb +0 -5
  29. data/app/views/thredded/topics/_form.html.erb +1 -0
  30. data/app/views/thredded/topics/_topic.html.erb +6 -4
  31. data/app/views/thredded/topics/unread.html.erb +4 -1
  32. data/config/i18n-tasks.yml +1 -0
  33. data/config/locales/de.yml +5 -0
  34. data/config/locales/en.yml +5 -0
  35. data/config/locales/es.yml +5 -0
  36. data/config/locales/fr.yml +5 -0
  37. data/config/locales/it.yml +5 -0
  38. data/config/locales/pl.yml +5 -0
  39. data/config/locales/pt-BR.yml +5 -0
  40. data/config/locales/ru.yml +6 -1
  41. data/config/locales/zh-CN.yml +5 -0
  42. data/lib/generators/thredded/install/templates/initializer.rb +17 -11
  43. data/lib/thredded.rb +111 -48
  44. data/lib/thredded/content_formatter.rb +2 -2
  45. data/lib/thredded/database_seeder.rb +6 -6
  46. data/lib/thredded/db_tools.rb +2 -2
  47. data/lib/thredded/errors.rb +13 -1
  48. data/lib/thredded/html_pipeline/onebox_filter.rb +2 -2
  49. data/lib/thredded/version.rb +1 -1
  50. data/vendor/assets/javascripts/autosize.min.js +2 -6
  51. data/vendor/assets/javascripts/textcomplete.min.js +2 -2
  52. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b352700d9047e3e225b310dfc168d67b67ae3159da12baa88893b1185f89b46
4
- data.tar.gz: 8301bfe7de537c66eac09594ec89371490b5770b7dbaae223f268c41db2bcb98
3
+ metadata.gz: 4fb30f1cf973b0e6d8e26feb9006cbf20b1405cddfcaa2deb4e06ccd1d3a6524
4
+ data.tar.gz: cd3fa99b4797609c4e840ed02a3117ccf7288d1c706ef879ac6ee3d32546404a
5
5
  SHA512:
6
- metadata.gz: 95cf96447f824d72a3260f9a27ce35aa3827d1d6e64419a184bf8c76e50382a6ed759d46914f72c3680e7553fd25cd6235246b4864bc8dc85ac6e84c3c6bba79
7
- data.tar.gz: db8e6876bab1e834633aff07da90cde4b0a6e90b3469615bb21c62f00c8c57ce9b7e47dc148c5e319cf0ce2371c30d33f7e51f95d2e55ba0265f37956ac76c3b
6
+ metadata.gz: ef7c3bd8c922fc954a55e00625ffe267b0a164be7e4f36eb434de72a70f974571ae72cde41d799643ce893ef3f55aff7b5daebc70119cfe7462027d150ddf897
7
+ data.tar.gz: e005eb547cbbb181d9f118ff54e9d48427a28c6b3b66dd2e19289e8b5468ceeb059b35e80d9d62b896af964ea416184ac3ea4c4fc8d189675a9b2d5dd9d23616
data/README.md CHANGED
@@ -95,7 +95,7 @@ Then, see the rest of this Readme for more information about using and customizi
95
95
  Add the gem to your Gemfile:
96
96
 
97
97
  ```ruby
98
- gem 'thredded', '~> 0.15.3'
98
+ gem 'thredded', '~> 0.15.4'
99
99
  ```
100
100
 
101
101
  Add the Thredded [initializer] to your parent app by running the install generator.
@@ -0,0 +1,18 @@
1
+ (function() {
2
+ const Thredded = window.Thredded;
3
+ Thredded.isSubmitHotkey = (evt) => {
4
+ // Ctrl+Enter.
5
+ return evt.ctrlKey && (evt.keyCode === 13 || evt.keyCode === 10 /* http://crbug.com/79407 */);
6
+ };
7
+
8
+ document.addEventListener('keypress', (evt) => {
9
+ if (Thredded.isSubmitHotkey(evt)) {
10
+ const submitButton = document.querySelector('[data-thredded-submit-hotkey] [type="submit"]');
11
+ if (!submitButton) return;
12
+ evt.preventDefault();
13
+ // Focus first for better visual feedback.
14
+ submitButton.focus();
15
+ submitButton.click();
16
+ }
17
+ });
18
+ })();
@@ -1,6 +1,7 @@
1
1
  .thredded--pagination {
2
2
  border-top: $thredded-base-border;
3
3
  padding-top: $thredded-base-spacing;
4
+ padding-bottom: $thredded-base-spacing;
4
5
  text-align: center;
5
6
 
6
7
  > span {
@@ -27,7 +28,6 @@
27
28
  .thredded--pagination-top > .thredded--pagination {
28
29
  border-bottom: $thredded-base-border;
29
30
  margin-bottom: $thredded-base-spacing;
30
- padding-bottom: $thredded-base-spacing;
31
31
  }
32
32
 
33
33
  .thredded--pagination-bottom > .thredded--pagination {
@@ -5,7 +5,6 @@
5
5
 
6
6
  &--wrapper {
7
7
  border-top: $thredded-base-border;
8
- margin-top: $thredded-base-spacing;
9
8
  padding-top: $thredded-base-spacing;
10
9
  }
11
10
 
@@ -55,8 +55,8 @@
55
55
  }
56
56
  }
57
57
 
58
- .thredded--topics--started-by,
59
- .thredded--topics--updated-by {
58
+ .thredded--topics--updated-by,
59
+ .thredded--topics--messageboard {
60
60
  color: $thredded-secondary-text-color;
61
61
  font-size: $thredded-font-size-small;
62
62
  font-style: normal;
@@ -69,22 +69,6 @@
69
69
  color: $thredded-action-color;
70
70
  }
71
71
  }
72
-
73
- abbr {
74
- &::after {
75
- content: ' by ';
76
- }
77
- }
78
- }
79
-
80
- .thredded--topics--updated-by {
81
- margin-right: $thredded-small-spacing;
82
-
83
- abbr {
84
- &::before {
85
- content: 'updated ';
86
- }
87
- }
88
72
  }
89
73
 
90
74
  .thredded--topics--participants--participant {
@@ -93,16 +77,6 @@
93
77
  }
94
78
  }
95
79
 
96
- .thredded--topics--started-by {
97
- display: none;
98
-
99
- abbr {
100
- &::before {
101
- content: 'started ';
102
- }
103
- }
104
- }
105
-
106
80
  .thredded--topics--moderation-state {
107
81
  padding: 0.3em 0.5em;
108
82
  font-size: $thredded-font-size-small;
@@ -20,7 +20,9 @@ module Thredded
20
20
 
21
21
  rescue_from Thredded::Errors::MessageboardNotFound,
22
22
  Thredded::Errors::PrivateTopicNotFound,
23
+ Thredded::Errors::PrivatePostNotFound,
23
24
  Thredded::Errors::TopicNotFound,
25
+ Thredded::Errors::PostNotFound,
24
26
  Thredded::Errors::UserNotFound do |exception|
25
27
  @error = exception
26
28
  @message = exception.message
@@ -3,7 +3,7 @@
3
3
  module Thredded
4
4
  class PostPermalinksController < Thredded::ApplicationController
5
5
  def show
6
- post = Thredded::Post.find(params[:id])
6
+ post = Thredded::Post.find!(params[:id])
7
7
  authorize post, :read?
8
8
  redirect_to post_url(post, user: thredded_current_user), status: :found
9
9
  end
@@ -13,7 +13,7 @@ module Thredded
13
13
 
14
14
  # Preview an update to an existing post
15
15
  def update
16
- @post = Thredded::Post.find(params[:post_id])
16
+ @post = Thredded::Post.find!(params[:post_id])
17
17
  @post.assign_attributes(post_params)
18
18
  render_preview
19
19
  end
@@ -81,12 +81,11 @@ module Thredded
81
81
  def parent_topic
82
82
  Thredded::Topic
83
83
  .where(messageboard: messageboard)
84
- .friendly
85
- .find(params[:topic_id])
84
+ .friendly_find!(params[:topic_id])
86
85
  end
87
86
 
88
87
  def post
89
- @post ||= Thredded::Post.find(params[:id])
88
+ @post ||= Thredded::Post.find!(params[:id])
90
89
  end
91
90
 
92
91
  def current_page
@@ -4,7 +4,7 @@ module Thredded
4
4
  class PrivatePostPermalinksController < Thredded::ApplicationController
5
5
  before_action :thredded_require_login!
6
6
  def show
7
- private_post = Thredded::PrivatePost.find(params[:id])
7
+ private_post = Thredded::PrivatePost.find!(params[:id])
8
8
  authorize private_post, :read?
9
9
  redirect_to post_url(private_post, user: thredded_current_user), status: :found
10
10
  end
@@ -13,7 +13,7 @@ module Thredded
13
13
 
14
14
  # Preview an update to an existing post
15
15
  def update
16
- @private_post = Thredded::PrivatePost.find(params[:private_post_id])
16
+ @private_post = Thredded::PrivatePost.find!(params[:private_post_id])
17
17
  @private_post.assign_attributes(private_post_params)
18
18
  render_preview
19
19
  end
@@ -80,12 +80,11 @@ module Thredded
80
80
  def parent_topic
81
81
  Thredded::PrivateTopic
82
82
  .includes(:private_users)
83
- .friendly
84
- .find(params[:private_topic_id])
83
+ .friendly_find!(params[:private_topic_id])
85
84
  end
86
85
 
87
86
  def post
88
- @post ||= Thredded::PrivatePost.find(params[:id])
87
+ @post ||= Thredded::PrivatePost.find!(params[:id])
89
88
  end
90
89
 
91
90
  def current_page
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Thredded
4
- module UrlsHelper
4
+ module UrlsHelper # rubocop:disable Metrics/ModuleLength
5
5
  class << self
6
6
  include Thredded::Engine.routes.url_helpers
7
7
  include Thredded::UrlsHelper
@@ -141,5 +141,32 @@ module Thredded
141
141
  post_permalink_path(post)
142
142
  end
143
143
  end
144
+
145
+ # @param [Thredded.user_class] current_user
146
+ # @param [Thredded.user_class] to
147
+ # @param [Boolean] use_existing Whether to use the existing thread if any.
148
+ # @return [String] a path to a new or existing private message thread for the given users.
149
+ def send_private_message_path(current_user:, to:, use_existing: true)
150
+ existing_topic = use_existing &&
151
+ Thredded::PrivateTopic.has_exact_participants([current_user, to])
152
+ .order_recently_posted_first.first
153
+ if existing_topic
154
+ page = 1 + (existing_topic.posts_count - 1) / Thredded::PrivatePost.default_per_page
155
+ Thredded::UrlsHelper.private_topic_path(
156
+ existing_topic,
157
+ page: (page if page > 1),
158
+ autofocus_new_post_content: true,
159
+ anchor: 'post_content'
160
+ )
161
+ else
162
+ Thredded::UrlsHelper.new_private_topic_path(
163
+ private_topic: {
164
+ user_names: to.send(Thredded.user_name_column),
165
+ title: [current_user, to].map(&Thredded.user_display_name_method).join(' • ')
166
+ },
167
+ autofocus_new_post_content: true,
168
+ )
169
+ end
170
+ end
144
171
  end
145
172
  end
@@ -13,7 +13,7 @@ module Thredded
13
13
 
14
14
  scope :moderation_state_visible_to_all, -> { where(visible_to_all_arel_node) }
15
15
 
16
- scope :moderation_state_visible_to_user, (lambda do |user|
16
+ scope :moderation_state_visible_to_user, ->(user) {
17
17
  visible = visible_to_all_arel_node
18
18
  # @type [Arel::Table]
19
19
  table = arel_table
@@ -27,7 +27,7 @@ module Thredded
27
27
  end
28
28
  end
29
29
  where(visible)
30
- end)
30
+ }
31
31
 
32
32
  # @return [Arel::Nodes::Node]
33
33
  # @api private
@@ -9,7 +9,7 @@ module Thredded
9
9
  extend ActiveSupport::Concern
10
10
 
11
11
  included do
12
- paginates_per 50
12
+ paginates_per Thredded.posts_per_page
13
13
 
14
14
  delegate :email, to: :user, prefix: true, allow_nil: true
15
15
 
@@ -4,7 +4,7 @@ module Thredded
4
4
  module TopicCommon
5
5
  extend ActiveSupport::Concern
6
6
  included do
7
- paginates_per 50 if respond_to?(:paginates_per)
7
+ paginates_per Thredded.topics_per_page if respond_to?(:paginates_per)
8
8
 
9
9
  belongs_to :last_user,
10
10
  class_name: Thredded.user_class_name,
@@ -58,8 +58,6 @@ module Thredded
58
58
  **(Thredded.rails_gte_51? ? { optional: true } : {})
59
59
 
60
60
  has_many :post_moderation_records, inverse_of: :messageboard, dependent: :delete_all
61
-
62
- # rubocop:disable Style/Lambda
63
61
  scope :top_level_messageboards, -> { where(group: nil) }
64
62
  scope :by_messageboard_group, ->(group) { where(group: group.id) }
65
63
  scope :ordered, ->(order = Thredded.messageboards_order) {
@@ -83,7 +81,6 @@ module Thredded
83
81
  scope :ordered_by_topics_count_desc, ->() {
84
82
  order(topics_count: :desc)
85
83
  }
86
- # rubocop:enable Style/Lambda
87
84
 
88
85
  # Finds the messageboard by its slug or ID, or raises Thredded::Errors::MessageboardNotFound.
89
86
  # @param slug_or_id [String]
@@ -37,6 +37,14 @@ module Thredded
37
37
 
38
38
  after_commit :auto_follow_and_notify, on: %i[create update]
39
39
 
40
+ # Finds the post by its ID, or raises {Thredded::Errors::PostNotFound}.
41
+ # @param id [String, Number]
42
+ # @return [Thredded::Post]
43
+ # @raise [Thredded::Errors::PostNotFound] if the post with the given ID does not exist.
44
+ def self.find!(id)
45
+ find_by(id: id) || fail(Thredded::Errors::PostNotFound)
46
+ end
47
+
40
48
  # @param [Integer] per_page
41
49
  # @param [Thredded.user_class] user
42
50
  def page(per_page: self.class.default_per_page, user:)
@@ -31,6 +31,8 @@ module Thredded
31
31
  end
32
32
  end
33
33
 
34
+ paginates_per Thredded.posts_per_page
35
+
34
36
  # @param [Thredded.user_class] moderator
35
37
  # @param [Thredded::Post] post
36
38
  # @param [Symbol, String] previous_moderation_state
@@ -21,6 +21,14 @@ module Thredded
21
21
  after_commit :update_parent_last_user_and_timestamp, on: %i[create destroy]
22
22
  after_commit :notify_users, on: [:create]
23
23
 
24
+ # Finds the post by its ID, or raises {Thredded::Errors::PrivatePostNotFound}.
25
+ # @param id [String, Number]
26
+ # @return [Thredded::PrivatePost]
27
+ # @raise [Thredded::Errors::PrivatePostNotFound] if the post with the given ID does not exist.
28
+ def self.find!(id)
29
+ find_by(id: id) || fail(Thredded::Errors::PrivatePostNotFound)
30
+ end
31
+
24
32
  # @param [Integer] per_page
25
33
  def page(per_page: self.class.default_per_page)
26
34
  calculate_page(postable.posts, per_page)
@@ -38,6 +38,20 @@ module Thredded
38
38
  inverse_of: :postable,
39
39
  dependent: :destroy
40
40
 
41
+ # Private topics with that have exactly the given participants.
42
+ scope :has_exact_participants, ->(users) {
43
+ private_users = Thredded::PrivateUser.arel_table
44
+ joins(:private_users)
45
+ .group(arel_table[:id])
46
+ .having(
47
+ Arel::Nodes::And.new(
48
+ users.map do |user|
49
+ Arel::Nodes::Count.new([private_users[:user_id].eq(user.id).or(Arel.sql('NULL'))]).eq(1)
50
+ end
51
+ ).and(Arel::Nodes::Count.new([private_users[:user_id].not_in(users.map(&:id)).or(Arel.sql('NULL'))]).eq(0))
52
+ )
53
+ }
54
+
41
55
  validates_each :users do |model, _attr, users|
42
56
  # Must include the creator + at least one other user
43
57
  if users.length < 2
@@ -14,7 +14,7 @@ module Thredded
14
14
  scope :search_query, ->(query) { ::Thredded::TopicsSearch.new(query, self).search }
15
15
 
16
16
  scope :order_sticky_first, -> { order(sticky: :desc) }
17
- scope :order_followed_first, lambda { |user|
17
+ scope :order_followed_first, ->(user) {
18
18
  user_follows = UserTopicFollow.arel_table
19
19
  joins(arel_table.join(user_follows, Arel::Nodes::OuterJoin)
20
20
  .on(user_follows[:topic_id].eq(arel_table[:id])
@@ -22,7 +22,7 @@ module Thredded
22
22
  .order(Arel::Nodes::Ascending.new(user_follows[:id].eq(nil)))
23
23
  }
24
24
 
25
- scope :followed_by, lambda { |user|
25
+ scope :followed_by, ->(user) {
26
26
  joins(:user_follows)
27
27
  .where(thredded_user_topic_follows: { user_id: user.id })
28
28
  }
@@ -46,12 +46,12 @@ module Thredded
46
46
  opt.has_many :thredded_post_moderated_records, foreign_key: 'moderator_id', inverse_of: :moderator
47
47
  end
48
48
 
49
- scope :left_join_thredded_user_details, (lambda do
49
+ scope :left_join_thredded_user_details, -> {
50
50
  users = arel_table
51
51
  user_details = Thredded::UserDetail.arel_table
52
52
  joins(users.join(user_details, Arel::Nodes::OuterJoin)
53
53
  .on(users[:id].eq(user_details[:user_id])).join_sources)
54
- end)
54
+ }
55
55
  end
56
56
 
57
57
  def thredded_user_preference
@@ -7,6 +7,7 @@
7
7
  'data-thredded-post-form' => true,
8
8
  'data-autocomplete-url' => autocomplete_users_path,
9
9
  'data-autocomplete-min-length' => Thredded.autocomplete_min_length,
10
+ 'data-thredded-submit-hotkey' => true,
10
11
  } do |form| %>
11
12
  <ul class="thredded--form-list">
12
13
  <%= render 'thredded/posts_common/form/content',
@@ -2,7 +2,7 @@
2
2
  <%= form.label :content, content_label %>
3
3
  <%= view_hooks.post_form.content_text_area.render self, form: form, content_label: content_label do %>
4
4
  <%= render 'thredded/posts_common/form/before_content', form: form %>
5
- <%= form.text_area :content, {rows: 5, required: true} %>
5
+ <%= form.text_area :content, {rows: 5, required: true, autofocus: !!params[:autofocus_new_post_content]} %>
6
6
  <%= render 'thredded/posts_common/form/after_content', form: form %>
7
7
  <% end %>
8
8
  </li>
@@ -6,6 +6,7 @@
6
6
  # TODO: only autocomplete users in this private topic
7
7
  'data-autocomplete-url' => autocomplete_users_path,
8
8
  'data-autocomplete-min-length' => Thredded.autocomplete_min_length,
9
+ 'data-thredded-submit-hotkey' => true,
9
10
  } do |form| %>
10
11
 
11
12
  <ul class="thredded--form-list on-top">
@@ -17,6 +18,7 @@
17
18
  <li>
18
19
  <%= form.label :user_names, t('thredded.private_topics.form.users_label') %>
19
20
  <%= form.text_area :user_names,
21
+ required: true,
20
22
  placeholder: t('thredded.private_topics.form.users_placeholder'),
21
23
  'data-thredded-users-select' => true,
22
24
  'data-autocomplete-url' => autocomplete_users_path,
@@ -14,10 +14,5 @@
14
14
  collection: [private_topic.last_user, *(private_topic.users - [private_topic.last_user])] %>
15
15
  </span>
16
16
  </cite>
17
-
18
- <cite class="thredded--topics--started-by">
19
- <%= time_ago private_topic.created_at %>
20
- <%= user_link private_topic.user %>
21
- </cite>
22
17
  <% end %>
23
18
 
@@ -5,6 +5,7 @@
5
5
  'data-thredded-topic-form' => true,
6
6
  'data-autocomplete-url' => autocomplete_users_path,
7
7
  'data-autocomplete-min-length' => Thredded.autocomplete_min_length,
8
+ 'data-thredded-submit-hotkey' => true,
8
9
  } do |form| %>
9
10
  <ul class="thredded--form-list on-top">
10
11
  <li class="title">