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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/javascripts/thredded/components/submit_hotkey.es6 +18 -0
- data/app/assets/stylesheets/thredded/components/_pagination.scss +1 -1
- data/app/assets/stylesheets/thredded/components/_post-form.scss +0 -1
- data/app/assets/stylesheets/thredded/components/_topics.scss +2 -28
- data/app/controllers/thredded/application_controller.rb +2 -0
- data/app/controllers/thredded/post_permalinks_controller.rb +1 -1
- data/app/controllers/thredded/post_previews_controller.rb +1 -1
- data/app/controllers/thredded/posts_controller.rb +2 -3
- data/app/controllers/thredded/private_post_permalinks_controller.rb +1 -1
- data/app/controllers/thredded/private_post_previews_controller.rb +1 -1
- data/app/controllers/thredded/private_posts_controller.rb +2 -3
- data/app/helpers/thredded/urls_helper.rb +28 -1
- data/app/models/concerns/thredded/content_moderation_state.rb +2 -2
- data/app/models/concerns/thredded/post_common.rb +1 -1
- data/app/models/concerns/thredded/topic_common.rb +1 -1
- data/app/models/thredded/messageboard.rb +0 -3
- data/app/models/thredded/post.rb +8 -0
- data/app/models/thredded/post_moderation_record.rb +2 -0
- data/app/models/thredded/private_post.rb +8 -0
- data/app/models/thredded/private_topic.rb +14 -0
- data/app/models/thredded/topic.rb +2 -2
- data/app/models/thredded/user_extender.rb +2 -2
- data/app/views/thredded/posts_common/_form.html.erb +1 -0
- data/app/views/thredded/posts_common/form/_content_field.html.erb +1 -1
- data/app/views/thredded/private_topics/_form.html.erb +2 -0
- data/app/views/thredded/private_topics/_private_topic.html.erb +0 -5
- data/app/views/thredded/topics/_form.html.erb +1 -0
- data/app/views/thredded/topics/_topic.html.erb +6 -4
- data/app/views/thredded/topics/unread.html.erb +4 -1
- data/config/i18n-tasks.yml +1 -0
- data/config/locales/de.yml +5 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/es.yml +5 -0
- data/config/locales/fr.yml +5 -0
- data/config/locales/it.yml +5 -0
- data/config/locales/pl.yml +5 -0
- data/config/locales/pt-BR.yml +5 -0
- data/config/locales/ru.yml +6 -1
- data/config/locales/zh-CN.yml +5 -0
- data/lib/generators/thredded/install/templates/initializer.rb +17 -11
- data/lib/thredded.rb +111 -48
- data/lib/thredded/content_formatter.rb +2 -2
- data/lib/thredded/database_seeder.rb +6 -6
- data/lib/thredded/db_tools.rb +2 -2
- data/lib/thredded/errors.rb +13 -1
- data/lib/thredded/html_pipeline/onebox_filter.rb +2 -2
- data/lib/thredded/version.rb +1 -1
- data/vendor/assets/javascripts/autosize.min.js +2 -6
- data/vendor/assets/javascripts/textcomplete.min.js +2 -2
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fb30f1cf973b0e6d8e26feb9006cbf20b1405cddfcaa2deb4e06ccd1d3a6524
|
4
|
+
data.tar.gz: cd3fa99b4797609c4e840ed02a3117ccf7288d1c706ef879ac6ee3d32546404a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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 {
|
@@ -55,8 +55,8 @@
|
|
55
55
|
}
|
56
56
|
}
|
57
57
|
|
58
|
-
.thredded--topics--
|
59
|
-
.thredded--topics--
|
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
|
@@ -81,12 +81,11 @@ module Thredded
|
|
81
81
|
def parent_topic
|
82
82
|
Thredded::Topic
|
83
83
|
.where(messageboard: messageboard)
|
84
|
-
.
|
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
|
-
.
|
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, (
|
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
|
-
|
30
|
+
}
|
31
31
|
|
32
32
|
# @return [Arel::Nodes::Node]
|
33
33
|
# @api private
|
@@ -4,7 +4,7 @@ module Thredded
|
|
4
4
|
module TopicCommon
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
included do
|
7
|
-
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]
|
data/app/models/thredded/post.rb
CHANGED
@@ -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:)
|
@@ -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,
|
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,
|
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,
|
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
|
-
|
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">
|