thredded 0.15.3 → 0.15.4

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