thredded 0.14.4 → 0.15.1

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/app/assets/javascripts/thredded/components/preview_area.es6 +3 -0
  4. data/app/assets/javascripts/thredded/components/spoilers.es6 +37 -0
  5. data/app/assets/stylesheets/thredded/_email.scss +25 -0
  6. data/app/assets/stylesheets/thredded/_thredded.scss +1 -0
  7. data/app/assets/stylesheets/thredded/base/_typography.scss +1 -1
  8. data/app/assets/stylesheets/thredded/base/_variables.scss +2 -3
  9. data/app/assets/stylesheets/thredded/components/_post.scss +0 -11
  10. data/app/assets/stylesheets/thredded/components/_spoiler.scss +41 -0
  11. data/app/commands/thredded/mark_all_read.rb +1 -7
  12. data/app/controllers/concerns/thredded/new_post_params.rb +1 -1
  13. data/app/controllers/concerns/thredded/new_private_post_params.rb +1 -1
  14. data/app/controllers/concerns/thredded/new_private_topic_params.rb +1 -2
  15. data/app/controllers/concerns/thredded/new_topic_params.rb +0 -1
  16. data/app/controllers/thredded/application_controller.rb +10 -0
  17. data/app/controllers/thredded/posts_controller.rb +1 -2
  18. data/app/controllers/thredded/private_posts_controller.rb +1 -2
  19. data/app/controllers/thredded/private_topics_controller.rb +10 -12
  20. data/app/controllers/thredded/topics_controller.rb +14 -17
  21. data/app/forms/thredded/edit_topic_form.rb +2 -1
  22. data/app/forms/thredded/post_form.rb +3 -1
  23. data/app/forms/thredded/private_post_form.rb +3 -1
  24. data/app/forms/thredded/private_topic_form.rb +1 -0
  25. data/app/jobs/thredded/activity_updater_job.rb +18 -8
  26. data/app/jobs/thredded/auto_follow_and_notify_job.rb +2 -1
  27. data/app/models/concerns/thredded/post_common.rb +3 -4
  28. data/app/models/concerns/thredded/topic_common.rb +7 -0
  29. data/app/models/concerns/thredded/user_topic_read_state_common.rb +62 -5
  30. data/app/models/thredded/null_user_topic_read_state.rb +8 -0
  31. data/app/policies/thredded/private_post_policy.rb +16 -0
  32. data/app/view_hooks/thredded/all_view_hooks.rb +3 -0
  33. data/app/view_models/thredded/base_topic_view.rb +5 -1
  34. data/app/view_models/thredded/post_view.rb +13 -1
  35. data/app/view_models/thredded/posts_page_view.rb +1 -1
  36. data/app/view_models/thredded/topic_posts_page_view.rb +13 -1
  37. data/app/views/thredded/posts/_post.html.erb +1 -0
  38. data/app/views/thredded/posts/edit.html.erb +1 -0
  39. data/app/views/thredded/posts_common/_before_first_unread_post.html.erb +7 -0
  40. data/app/views/thredded/posts_common/form/_after_content.html.erb +8 -0
  41. data/app/views/thredded/posts_common/form/_before_content.html.erb +8 -0
  42. data/app/views/thredded/private_posts/_private_post.html.erb +2 -1
  43. data/app/views/thredded/private_posts/edit.html.erb +1 -0
  44. data/app/views/thredded/private_topics/_form.html.erb +1 -0
  45. data/app/views/thredded/private_topics/edit.html.erb +2 -1
  46. data/app/views/thredded/shared/_field_errors.html.erb +3 -0
  47. data/app/views/thredded/shared/_nav.html.erb +1 -1
  48. data/app/views/thredded/shared/_page.html.erb +1 -1
  49. data/app/views/thredded/topics/_form.html.erb +1 -0
  50. data/app/views/thredded/topics/edit.html.erb +2 -1
  51. data/config/locales/de.yml +2 -0
  52. data/config/locales/en.yml +2 -0
  53. data/config/locales/es.yml +2 -0
  54. data/config/locales/fr.yml +2 -0
  55. data/config/locales/it.yml +2 -0
  56. data/config/locales/pl.yml +2 -0
  57. data/config/locales/pt-BR.yml +2 -0
  58. data/config/locales/ru.yml +2 -0
  59. data/config/locales/zh-CN.yml +2 -0
  60. data/db/migrate/20160329231848_create_thredded.rb +37 -23
  61. data/db/upgrade_migrations/{20170811090735_upgrade_thredded_v0_13_to_v_014.rb → 20170811090735_upgrade_thredded_v0_13_to_v0_14.rb} +0 -0
  62. data/db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb +91 -0
  63. data/lib/generators/thredded/install/templates/initializer.rb +16 -7
  64. data/lib/thredded.rb +143 -125
  65. data/lib/thredded/arel_compat.rb +57 -0
  66. data/lib/thredded/base_migration.rb +10 -0
  67. data/lib/thredded/collection_to_strings_with_cache_renderer.rb +35 -9
  68. data/lib/thredded/content_formatter.rb +27 -18
  69. data/lib/thredded/database_seeder.rb +218 -64
  70. data/lib/thredded/email_transformer.rb +5 -2
  71. data/lib/thredded/email_transformer/spoiler.rb +25 -0
  72. data/lib/thredded/formatting_demo_content.rb +12 -0
  73. data/lib/thredded/html_pipeline/onebox_filter.rb +3 -38
  74. data/lib/thredded/html_pipeline/spoiler_tag_filter.rb +128 -0
  75. data/lib/thredded/html_pipeline/utils.rb +47 -0
  76. data/lib/thredded/rails_lt_5_2_arel_case_node.rb +119 -0
  77. data/lib/thredded/version.rb +1 -1
  78. metadata +17 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b6ed8f98f2eb287346630ed8c6df83ce81edfd0f3ce918dcdb02841efd46590
4
- data.tar.gz: 27a77b5767823d6c790cdc344efa10839241edd698714d1483620b5e317ed3ab
3
+ metadata.gz: a8f7ee3eae771c92edf97664c74ebbcb42e403521cc09b9c05e7379dc98956f1
4
+ data.tar.gz: 66ac525459c09f1efd65c947a8df0d41dde083166d857664f7163d0ed0b131b0
5
5
  SHA512:
6
- metadata.gz: 24f2999aabccaa1a87faa5e4b2fefa611b86a8189e199d79e6e086df3d648244194c9c49c083dcd9f55dafff2eae8367bbedf87fc2d54a756170b441c0f1b65d
7
- data.tar.gz: cf738f7f1f6354f97bb0fc54b0846b46a99d2047747e306049d03090761047b8cc17ff9a791c897cc5d0361b54ecdcdb45ffdecf0bd810b949d5298d4df861be
6
+ metadata.gz: 0516e987e201972234778107cff4be78997dd15580ec0b721f21654510049598216d946966dd6a87d11553a47f7d55f7cf78f41e9fc7d07d1e0ecafd57470370
7
+ data.tar.gz: 70d7f20e62b6187f1017ceced464e4e0cba2ca2988dd4adc9d1f8e5776f9c0b9b5d75d29ed3d12e7125361e203b549ea44fcfe3fb884a1c9ef25496c03223a5b
data/README.md CHANGED
@@ -4,7 +4,7 @@ _Thredded_ is a Rails 4.2+ forum/messageboard engine. Its goal is to be as simpl
4
4
 
5
5
  Some of the features currently in Thredded:
6
6
 
7
- * Markdown (default) or BBCode post formatting.
7
+ * Markdown (default) and / or BBCode post formatting, with [onebox] and `<spoiler>` / `[spoiler]` tag support.
8
8
  * (Un)read posts tracking.
9
9
  * Email notifications, topic subscriptions, @-mentions, per-messageboard notification settings.
10
10
  * Private group messaging.
@@ -28,6 +28,7 @@ If you're looking for variations on a theme - see [Discourse]. However, It is a
28
28
  application and not an engine like Thredded.
29
29
 
30
30
  [Discourse]: http://www.discourse.org/
31
+ [onebox]: https://github.com/discourse/onebox
31
32
 
32
33
  Table of Contents
33
34
  =================
@@ -94,7 +95,7 @@ Then, see the rest of this Readme for more information about using and customizi
94
95
  Add the gem to your Gemfile:
95
96
 
96
97
  ```ruby
97
- gem 'thredded', '~> 0.14.4'
98
+ gem 'thredded', '~> 0.15.1'
98
99
  ```
99
100
 
100
101
  Add the Thredded [initializer] to your parent app by running the install generator.
@@ -1,6 +1,8 @@
1
1
  //= require thredded/core/serialize_form
2
+ //= require thredded/components/spoilers
2
3
 
3
4
  (() => {
5
+ const Thredded = window.Thredded;
4
6
  const PREVIEW_AREA_SELECTOR = '[data-thredded-preview-area]';
5
7
  const PREVIEW_AREA_POST_SELECTOR = '[data-thredded-preview-area-post]';
6
8
 
@@ -49,6 +51,7 @@
49
51
  onPreviewResponse(data) {
50
52
  this.preview.style.display = 'block';
51
53
  this.previewPost.innerHTML = data;
54
+ Thredded.spoilers.init(this.previewPost);
52
55
  }
53
56
  }
54
57
 
@@ -0,0 +1,37 @@
1
+ //= require thredded/core/on_page_load
2
+
3
+ (() => {
4
+ const Thredded = window.Thredded;
5
+ const COMPONENT_SELECTOR = '.thredded--post--content--spoiler';
6
+ const OPEN_CLASS = 'thredded--post--content--spoiler--is-open';
7
+
8
+ Thredded.spoilers = {
9
+ init(root) {
10
+ Array.prototype.forEach.call(root.querySelectorAll(COMPONENT_SELECTOR), (node) => {
11
+ node.addEventListener('mousedown', (evt) => {
12
+ evt.stopPropagation();
13
+ this.toggle(evt.currentTarget);
14
+ });
15
+ node.addEventListener('keypress', (evt) => {
16
+ if (event.key === ' ' || event.key === 'Enter') {
17
+ evt.preventDefault();
18
+ evt.stopPropagation();
19
+ this.toggle(evt.currentTarget);
20
+ }
21
+ });
22
+ });
23
+ },
24
+
25
+ toggle(node) {
26
+ const isOpen = node.classList.contains(OPEN_CLASS);
27
+ node.classList.toggle(OPEN_CLASS);
28
+ node.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
29
+ node.firstElementChild.setAttribute('aria-hidden', isOpen ? 'false' : 'true');
30
+ node.lastElementChild.setAttribute('aria-hidden', isOpen ? 'true' : 'false');
31
+ }
32
+ };
33
+
34
+ Thredded.onPageLoad(() => {
35
+ Thredded.spoilers.init(document);
36
+ });
37
+ })();
@@ -49,3 +49,28 @@
49
49
  }
50
50
  }
51
51
  }
52
+
53
+ .thredded--post--content--spoiler {
54
+ background-color: $thredded-spoiler-background-color;
55
+ padding: 16px;
56
+
57
+ &--summary {
58
+ border-bottom: 1px solid $thredded-dark-gray;
59
+ }
60
+
61
+ &--contents {
62
+ color: transparent !important;
63
+
64
+ * {
65
+ color: transparent !important;
66
+ }
67
+
68
+ a {
69
+ text-decoration: underline;
70
+ }
71
+
72
+ > *:last-child {
73
+ margin-bottom: 0;
74
+ }
75
+ }
76
+ }
@@ -24,6 +24,7 @@
24
24
  @import "components/messageboard";
25
25
  @import "components/pagination";
26
26
  @import "components/post";
27
+ @import "components/spoiler";
27
28
  @import "components/post-form";
28
29
  @import "components/preferences";
29
30
  @import "components/topic-delete";
@@ -44,7 +44,7 @@
44
44
  border-left: solid 5px $thredded-blockquote-border-color;
45
45
  padding: 1rem;
46
46
 
47
- p:last-of-type {
47
+ > *:last-child {
48
48
  margin-bottom: 0;
49
49
  }
50
50
 
@@ -22,7 +22,7 @@ $thredded-inline-spacing: 0.4375em !default;
22
22
 
23
23
  // Named colors
24
24
  $thredded-brand: #4a90e2 !default;
25
- $thredded-dark-gray: #333 !default;
25
+ $thredded-dark-gray: rgba(#424242, 0.25) !default;
26
26
  $thredded-light-gray: #eee !default;
27
27
 
28
28
  // Colors of text, background, actions (links), and navigation
@@ -38,8 +38,7 @@ $thredded-nav-current-color: $thredded-action-color !default;
38
38
  $thredded-overlay-background-color: opacify($thredded-background-color, 1) !default;
39
39
  $thredded-overlay-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0, 0, 0, .2), 0 1px 5px 0 rgba(0, 0, 0, .12) !default;
40
40
  $thredded-secondary-nav-color: $thredded-secondary-text-color !default;
41
- $thredded-spoiler-hidden-color: $thredded-light-gray !default;
42
- $thredded-spoiler-shown-color: black !default;
41
+ $thredded-spoiler-background-color: rgba(0, 0, 0, .06) !default;
43
42
  $thredded-code-selected-line-background: #f8eec7 !default;
44
43
 
45
44
 
@@ -124,15 +124,4 @@
124
124
  pre {
125
125
  overflow-x: auto;
126
126
  }
127
-
128
- &--spoiler {
129
- color: $thredded-spoiler-hidden-color;
130
- background-color: $thredded-spoiler-hidden-color;
131
- cursor: pointer;
132
-
133
- &:hover,
134
- &:focus {
135
- color: $thredded-spoiler-shown-color;
136
- }
137
- }
138
127
  }
@@ -0,0 +1,41 @@
1
+ .thredded--post--content--spoiler {
2
+ background-color: $thredded-spoiler-background-color;
3
+ cursor: pointer;
4
+ margin: 0 0 $thredded-small-spacing;
5
+ padding: $thredded-small-spacing;
6
+ position: relative;
7
+
8
+ &--contents {
9
+ visibility: hidden;
10
+
11
+ > *:last-child {
12
+ margin-bottom: 0;
13
+ }
14
+ }
15
+
16
+ &--summary {
17
+ @extend %thredded--link;
18
+ position: absolute;
19
+ visibility: visible;
20
+ }
21
+
22
+ .thredded--post--content--spoiler {
23
+ visibility: visible;
24
+ }
25
+
26
+ table {
27
+ td, th {
28
+ border-color: $thredded-dark-gray;
29
+ }
30
+ }
31
+
32
+ &.thredded--post--content--spoiler--is-open {
33
+ > .thredded--post--content--spoiler--contents {
34
+ visibility: visible;
35
+ }
36
+
37
+ > .thredded--post--content--spoiler--summary {
38
+ display: none;
39
+ }
40
+ }
41
+ }
@@ -8,14 +8,8 @@ module Thredded
8
8
 
9
9
  unread_topics.each do |topic|
10
10
  last_post = topic.posts.order_oldest_first.last
11
- total_pages = topic.posts.page(1).total_pages
12
11
 
13
- Thredded::UserPrivateTopicReadState.touch!(
14
- user.id,
15
- topic.id,
16
- last_post,
17
- total_pages
18
- )
12
+ Thredded::UserPrivateTopicReadState.touch!(user.id, topic.id, last_post)
19
13
  end
20
14
  end
21
15
  end
@@ -8,7 +8,7 @@ module Thredded
8
8
  def new_post_params
9
9
  params.fetch(:post, {})
10
10
  .permit(:content, :quote_post_id)
11
- .merge(ip: request.remote_ip).tap do |p|
11
+ .tap do |p|
12
12
  quote_id = p.delete(:quote_post_id)
13
13
  if quote_id
14
14
  post = Thredded::Post.find(quote_id)
@@ -8,7 +8,7 @@ module Thredded
8
8
  def new_private_post_params
9
9
  params.fetch(:post, {})
10
10
  .permit(:content, :quote_private_post_id)
11
- .merge(ip: request.remote_ip).tap do |p|
11
+ .tap do |p|
12
12
  quote_id = p.delete(:quote_private_post_id)
13
13
  if quote_id
14
14
  post = Thredded::PrivatePost.find(quote_id)
@@ -7,11 +7,10 @@ module Thredded
7
7
 
8
8
  def new_private_topic_params
9
9
  params
10
- .require(:private_topic)
10
+ .fetch(:private_topic, {})
11
11
  .permit(:title, :content, :user_names, user_ids: [])
12
12
  .merge(
13
13
  user: thredded_current_user,
14
- ip: request.remote_ip
15
14
  ).tap { |p| adapt_user_ids! p }
16
15
  end
17
16
 
@@ -12,7 +12,6 @@ module Thredded
12
12
  .merge(
13
13
  messageboard: messageboard,
14
14
  user: thredded_current_user,
15
- ip: request.remote_ip,
16
15
  )
17
16
  end
18
17
  end
@@ -67,6 +67,16 @@ module Thredded
67
67
  given.all? { |k, v| v == params[k] }
68
68
  end
69
69
 
70
+ # Returns true if the current page is beyond the end of the collection
71
+ def page_beyond_last?(page_scope)
72
+ page_scope.to_a.empty? && page_scope.current_page != 1
73
+ end
74
+
75
+ # Returns URL parameters for the last page of the given page scope.
76
+ def last_page_params(page_scope)
77
+ { page: page_scope.total_pages }
78
+ end
79
+
70
80
  private
71
81
 
72
82
  def thredded_layout
@@ -55,8 +55,7 @@ module Thredded
55
55
 
56
56
  def mark_as_unread
57
57
  authorize post, :read?
58
- page = post.page(user: thredded_current_user)
59
- post.mark_as_unread(thredded_current_user, page)
58
+ post.mark_as_unread(thredded_current_user)
60
59
  after_mark_as_unread # customization hook
61
60
  end
62
61
 
@@ -54,8 +54,7 @@ module Thredded
54
54
 
55
55
  def mark_as_unread
56
56
  authorize post, :read?
57
- page = post.page
58
- post.mark_as_unread(thredded_current_user, page)
57
+ post.mark_as_unread(thredded_current_user)
59
58
  after_mark_as_unread # customization hook
60
59
  end
61
60
 
@@ -8,14 +8,13 @@ module Thredded
8
8
  before_action :thredded_require_login!
9
9
 
10
10
  def index
11
- @private_topics = Thredded::PrivateTopicsPageView.new(
12
- thredded_current_user,
13
- Thredded::PrivateTopic
14
- .distinct
15
- .for_user(thredded_current_user)
16
- .order_recently_posted_first
17
- .page(params[:page])
18
- )
11
+ page_scope = Thredded::PrivateTopic
12
+ .distinct
13
+ .for_user(thredded_current_user)
14
+ .order_recently_posted_first
15
+ .page(params[:page])
16
+ return redirect_to(last_page_params(page_scope)) if page_beyond_last?(page_scope)
17
+ @private_topics = Thredded::PrivateTopicsPageView.new(thredded_current_user, page_scope)
19
18
 
20
19
  Thredded::PrivateTopicForm.new(user: thredded_current_user).tap do |form|
21
20
  @new_private_topic = form if policy(form.private_topic).create?
@@ -31,12 +30,11 @@ module Thredded
31
30
  .includes(:user)
32
31
  .order_oldest_first
33
32
  .page(current_page)
33
+ return redirect_to(last_page_params(page_scope)) if page_beyond_last?(page_scope)
34
34
  @posts = Thredded::TopicPostsPageView.new(thredded_current_user, private_topic, page_scope)
35
35
 
36
36
  if thredded_signed_in?
37
- Thredded::UserPrivateTopicReadState.touch!(
38
- thredded_current_user.id, private_topic.id, page_scope.last, current_page
39
- )
37
+ Thredded::UserPrivateTopicReadState.touch!(thredded_current_user.id, private_topic.id, page_scope.last)
40
38
  end
41
39
 
42
40
  @new_post = Thredded::PrivatePostForm.new(
@@ -45,7 +43,7 @@ module Thredded
45
43
  end
46
44
 
47
45
  def new
48
- @private_topic = Thredded::PrivateTopicForm.new(user: thredded_current_user)
46
+ @private_topic = Thredded::PrivateTopicForm.new(new_private_topic_params)
49
47
  authorize_creating @private_topic.private_topic
50
48
  end
51
49
 
@@ -23,12 +23,11 @@ module Thredded
23
23
  return redirect_to(canonical_messageboard_params)
24
24
  end
25
25
 
26
- @topics = Thredded::TopicsPageView.new(
27
- thredded_current_user,
28
- policy_scope(messageboard.topics)
29
- .order_sticky_first.order_recently_posted_first
30
- .page(current_page)
31
- )
26
+ page_scope = policy_scope(messageboard.topics)
27
+ .order_sticky_first.order_recently_posted_first
28
+ .page(current_page)
29
+ return redirect_to(last_page_params(page_scope)) if page_beyond_last?(page_scope)
30
+ @topics = Thredded::TopicsPageView.new(thredded_current_user, page_scope)
32
31
  Thredded::TopicForm.new(messageboard: messageboard, user: thredded_current_user).tap do |form|
33
32
  @new_topic = form if policy(form.topic).create?
34
33
  end
@@ -41,12 +40,11 @@ module Thredded
41
40
  .order_oldest_first
42
41
  .includes(:user, :messageboard, :postable)
43
42
  .page(current_page)
43
+ return redirect_to(last_page_params(page_scope)) if page_beyond_last?(page_scope)
44
44
  @posts = Thredded::TopicPostsPageView.new(thredded_current_user, topic, page_scope)
45
45
 
46
46
  if thredded_signed_in?
47
- Thredded::UserTopicReadState.touch!(
48
- thredded_current_user.id, topic.id, page_scope.last, current_page
49
- )
47
+ Thredded::UserTopicReadState.touch!(thredded_current_user.id, topic.id, page_scope.last)
50
48
  end
51
49
 
52
50
  @new_post = Thredded::PostForm.new(user: thredded_current_user, topic: topic, post_params: new_post_params)
@@ -69,14 +67,13 @@ module Thredded
69
67
  Thredded::Topic.where(messageboard_id: policy_scope(Thredded::Messageboard.all).pluck(:id))
70
68
  end
71
69
  )
72
- @topics = Thredded::TopicsPageView.new(
73
- thredded_current_user,
74
- topics_scope
75
- .search_query(@query)
76
- .order_recently_posted_first
77
- .includes(:categories, :last_user, :user)
78
- .page(current_page)
79
- )
70
+ page_scope = topics_scope
71
+ .search_query(@query)
72
+ .order_recently_posted_first
73
+ .includes(:categories, :last_user, :user)
74
+ .page(current_page)
75
+ return redirect_to(last_page_params(page_scope)) if page_beyond_last?(page_scope)
76
+ @topics = Thredded::TopicsPageView.new(thredded_current_user, page_scope)
80
77
  end
81
78
 
82
79
  def new
@@ -4,7 +4,8 @@ module Thredded
4
4
  class EditTopicForm
5
5
  include ActiveModel::Model
6
6
 
7
- delegate :id, :title, :category_ids, :locked, :sticky, :messageboard, :messageboard_id, :valid?,
7
+ delegate :id, :title, :title_was, :category_ids, :locked, :sticky, :messageboard, :messageboard_id, :valid?,
8
+ :errors,
8
9
  to: :@topic
9
10
 
10
11
  # @param user [Thredded.user_class]
@@ -11,7 +11,7 @@ module Thredded
11
11
 
12
12
  # @param user [Thredded.user_class]
13
13
  # @param topic [Topic]
14
- # @param post [PrivatePost]
14
+ # @param post [Post]
15
15
  # @param post_params [Hash]
16
16
  def initialize(user:, topic:, post: nil, post_params: {})
17
17
  @messageboard = topic.messageboard
@@ -47,7 +47,9 @@ module Thredded
47
47
 
48
48
  def save
49
49
  return false unless @post.valid?
50
+ was_persisted = @post.persisted?
50
51
  @post.save!
52
+ Thredded::UserTopicReadState.touch!(@post.user.id, @topic.id, @post) unless was_persisted
51
53
  true
52
54
  end
53
55
  end