thredded 0.14.4 → 0.15.1

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