thredded 0.9.4 → 0.10.0

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/javascripts/thredded/core/mention_autocompletion.es6 +2 -2
  4. data/app/assets/stylesheets/thredded/base/_variables.scss +8 -2
  5. data/app/assets/stylesheets/thredded/components/_currently-online.scss +1 -0
  6. data/app/assets/stylesheets/thredded/components/_messageboard.scss +85 -8
  7. data/app/assets/stylesheets/thredded/components/_post.scss +3 -0
  8. data/app/commands/thredded/notify_following_users.rb +28 -6
  9. data/app/controllers/thredded/topics_controller.rb +20 -3
  10. data/app/forms/thredded/edit_topic_form.rb +50 -0
  11. data/app/forms/thredded/topic_form.rb +1 -5
  12. data/app/helpers/thredded/application_helper.rb +1 -0
  13. data/app/models/thredded/post.rb +3 -0
  14. data/app/models/thredded/topic.rb +17 -0
  15. data/app/models/thredded/user_extender.rb +1 -0
  16. data/app/models/thredded/user_permissions/read/all.rb +1 -1
  17. data/app/models/thredded/user_post_notification.rb +28 -0
  18. data/app/view_hooks/thredded/all_view_hooks.rb +21 -0
  19. data/app/view_models/thredded/post_view.rb +2 -1
  20. data/app/views/thredded/messageboards/_messageboard.html.erb +18 -16
  21. data/app/views/thredded/messageboards/index.html.erb +27 -15
  22. data/app/views/thredded/topics/_form.html.erb +1 -1
  23. data/app/views/thredded/topics/edit.html.erb +12 -6
  24. data/config/locales/en.yml +1 -0
  25. data/config/locales/es.yml +1 -0
  26. data/config/locales/pl.yml +1 -0
  27. data/config/locales/pt-BR.yml +1 -0
  28. data/db/migrate/20160329231848_create_thredded.rb +12 -2
  29. data/db/upgrade_migrations/20170125033319_upgrade_v0_9_to_v0_10.rb +28 -0
  30. data/lib/thredded/database_seeder.rb +1 -0
  31. data/lib/thredded/html_pipeline/at_mention_filter.rb +2 -2
  32. data/lib/thredded/version.rb +1 -1
  33. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ff5d29c06fad4e64cf4fa4b1577d77785a29453
4
- data.tar.gz: 2a6196cbbcff86edd7e418e9b0bdb6bd2bb7ce23
3
+ metadata.gz: 0e08209cc4846d358cb9cf977e394ab14e553571
4
+ data.tar.gz: dc0b3eb3302a5fd5b9a3d7d01abfcb25af8635a9
5
5
  SHA512:
6
- metadata.gz: 944f979a29481faaf886de134b5d2e1ee7dee38d26597292d630c10406a9910e6b064a9b23ba1a26b31a65548a23a7a4cb3e3ed539e84830d8fdc150492e5e86
7
- data.tar.gz: d87c3e1584e457bbd771114d548fdf143c90d8adb12f1c4393c7b41f1cb594b68bf6ed3cb05e48827ce3df04db7d5a9f9463f853e4896f51032576c9f989d702
6
+ metadata.gz: 7f14c1568bc33b5264b04c97e1ac8500d2215c6dd5f9e0002fd052baff8e722b7f691960db5ac47f85f45a828988762ba8f4eb09b2eb9a82ec8fa5c78a5c7f18
7
+ data.tar.gz: 1e41c3ff6c705a77a5a9856a5c2cb1216d0ccb291796562fd0ca2be87a5a45877e08c7972f39f6c2d2a56ff4c3ffa05f944119d3bf1a78b086a6e400af2d0383
data/README.md CHANGED
@@ -56,7 +56,7 @@ Then, see the rest of this Readme for more information about using and customizi
56
56
  Add the gem to your Gemfile:
57
57
 
58
58
  ```ruby
59
- gem 'thredded', '~> 0.9.4'
59
+ gem 'thredded', '~> 0.10.0'
60
60
  ```
61
61
 
62
62
  Add the Thredded [initializer] to your parent app by running the install generator.
@@ -41,7 +41,7 @@ class ThreddedMentionAutocompletion {
41
41
  },
42
42
  replace ({name, match}) {
43
43
  let prefix = match[1];
44
- if (/[. ]/.test(name)) {
44
+ if (/[., ()]/.test(name)) {
45
45
  return `${prefix}"${name}" `
46
46
  } else {
47
47
  return `${prefix}${name} `
@@ -51,4 +51,4 @@ class ThreddedMentionAutocompletion {
51
51
  }
52
52
  }
53
53
 
54
- ThreddedMentionAutocompletion.MATCH_RE = /(^@|\s@)"?([\w. ]+)$/;
54
+ ThreddedMentionAutocompletion.MATCH_RE = /(^@|\s@)"?([\w.,\- ()]+)$/;
@@ -22,8 +22,8 @@ $thredded-dark-gray: #333 !default;
22
22
  $thredded-light-gray: #eee !default;
23
23
 
24
24
  // Colors of text, background, actions (links), and navigation
25
- $thredded-text-color: #575d6b !default;
26
- $thredded-secondary-text-color: lighten($thredded-text-color, 30%) !default;
25
+ $thredded-text-color: rgba(black, 0.87) !default;
26
+ $thredded-secondary-text-color: rgba(opacify($thredded-text-color, 1), 0.54) !default;
27
27
  $thredded-background-color: #fff !default;
28
28
  $thredded-action-color: $thredded-brand !default;
29
29
  $thredded-action-hover-color: darken($thredded-action-color, 15%) !default;
@@ -83,3 +83,9 @@ $thredded-badge-active-color: $thredded-button-color !default;
83
83
  $thredded-badge-active-background: $thredded-action-color !default;
84
84
  $thredded-badge-inactive-color: $thredded-button-color !default;
85
85
  $thredded-badge-inactive-background: rgba($thredded-text-color, 0.3) !default;
86
+
87
+ // Layout features
88
+ // Whether to display messageboards as a grid on the desktop screen sizes.
89
+ $thredded-messageboards-grid: true !default;
90
+ // Minimum width of a grid item
91
+ $thredded-messageboards-grid-item-flex-basis: map-get($thredded-grid-breakpoint-max-widths, tablet) / 3 !default;
@@ -5,6 +5,7 @@
5
5
  position: fixed;
6
6
  right: 0;
7
7
  width: 16.25rem; // 260px
8
+ z-index: 10;
8
9
 
9
10
  @include thredded-media-mobile {
10
11
  display: none;
@@ -1,18 +1,25 @@
1
+ .thredded--messageboards-group {
2
+ margin-bottom: $thredded-base-spacing;
3
+ }
4
+
1
5
  .thredded--messageboard {
2
6
  @extend %thredded--link;
3
- border: $thredded-base-border;
4
7
  display: block;
5
- margin-bottom: $thredded-base-spacing;
8
+ margin-bottom: 1px;
6
9
  padding: $thredded-base-spacing;
7
10
  position: relative;
8
11
 
9
- header {
10
- margin-bottom: $thredded-small-spacing;
12
+ &, &:focus, &:hover, &:active {
13
+ outline: 1px solid $thredded-base-border-color;
11
14
  }
12
15
 
13
16
  &:hover {
14
17
  background-color: rgba($thredded-brand, 0.035);
15
18
  }
19
+
20
+ &:active {
21
+ box-shadow: $thredded-form-box-shadow;
22
+ }
16
23
  }
17
24
 
18
25
  .thredded--messageboard--title {
@@ -21,31 +28,42 @@
21
28
  float: left;
22
29
  font-size: 1.125rem; // 18px
23
30
  line-height: 1.2;
24
- margin-bottom: 0;
25
31
  margin-right: $thredded-small-spacing;
26
32
  vertical-align: baseline;
27
33
  }
28
34
 
35
+ .thredded--messageboard--title,
36
+ .thredded--messageboard--meta,
37
+ .thredded--messageboard--description {
38
+ margin-bottom: $thredded-small-spacing / 2;
39
+ }
40
+
41
+ .thredded--messageboard--description,
42
+ .thredded--messageboard--meta,
43
+ .thredded--messageboard--byline {
44
+ font-size: 0.875em;
45
+ }
46
+
29
47
  .thredded--messageboard--meta {
30
48
  @extend %thredded--heading;
31
49
  color: $thredded-secondary-text-color;
32
50
  display: inline-block;
33
51
  font-weight: normal;
34
- margin-bottom: 0;
35
52
  vertical-align: baseline;
36
53
  }
37
54
 
38
55
  .thredded--messageboard--description {
39
56
  @extend %thredded--paragraph;
40
57
  clear: both;
41
- margin-bottom: $thredded-small-spacing / 2;
42
58
  color: $thredded-text-color;
59
+ &:empty {
60
+ margin: 0;
61
+ }
43
62
  }
44
63
 
45
64
  .thredded--messageboard--byline {
46
65
  @extend %thredded--paragraph;
47
66
  color: $thredded-secondary-text-color;
48
- font-size: 0.875em;
49
67
  font-weight: normal;
50
68
  margin-bottom: 0;
51
69
  }
@@ -56,3 +74,62 @@
56
74
  display: none;
57
75
  }
58
76
  }
77
+
78
+ @supports (flex-wrap: wrap) {
79
+ .thredded--messageboard {
80
+ display: flex;
81
+ flex-direction: column;
82
+ &--header {
83
+ display: flex;
84
+ flex-wrap: wrap;
85
+ justify-content: space-between;
86
+ }
87
+ &--meta {
88
+ text-align: right;
89
+ }
90
+ &--byline,
91
+ &--description {
92
+ margin-top: auto;
93
+ }
94
+ }
95
+
96
+ @if $thredded-messageboards-grid {
97
+ @include thredded-media-desktop-and-up {
98
+ $item-border-width: 1px;
99
+ $item-padding-x: ($thredded-base-spacing * 0.8);
100
+ $item-padding-y: $thredded-base-spacing;
101
+
102
+ %thredded--messageboards-cell-flex {
103
+ flex-basis: $thredded-messageboards-grid-item-flex-basis;
104
+ flex-grow: 1;
105
+ }
106
+
107
+ .thredded--messageboards-group {
108
+ display: flex;
109
+ flex-direction: row;
110
+ flex-wrap: wrap;
111
+ justify-content: space-between;
112
+ margin-left: $item-border-width;
113
+
114
+ // Size incomplete last rows with up to two missing items.
115
+ &::after, &::before {
116
+ @extend %thredded--messageboards-cell-flex;
117
+ content: "";
118
+ margin-right: $item-border-width;
119
+ padding: 0 $item-padding-x;
120
+ }
121
+
122
+ &::before {
123
+ order: 1
124
+ }
125
+ }
126
+
127
+ .thredded--messageboard {
128
+ @extend %thredded--messageboards-cell-flex;
129
+ margin-left: 1px;
130
+ padding: $item-padding-y $item-padding-x;
131
+ }
132
+
133
+ }
134
+ }
135
+ }
@@ -95,6 +95,9 @@
95
95
  blockquote {
96
96
  @extend %thredded--blockquote;
97
97
  }
98
+ pre {
99
+ overflow-x: auto;
100
+ }
98
101
 
99
102
  &--spoiler {
100
103
  color: $thredded-spoiler-hidden-color;
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require 'set'
3
+
2
4
  module Thredded
3
5
  class NotifyFollowingUsers
4
6
  def initialize(post)
@@ -8,12 +10,26 @@ module Thredded
8
10
  def run
9
11
  Thredded.notifiers.each do |notifier|
10
12
  notifiable_users = targeted_users(notifier)
11
- notifier.new_post(@post, notifiable_users) if notifiable_users.present?
13
+ notifiable_users = notifiable_users.select do |user|
14
+ # Create a notification for the user.
15
+ # If a notification was already created (from another thread/process),
16
+ # this will return false due to the unique constraint on the table
17
+ # and the user will be excluded.
18
+ Thredded::UserPostNotification.create_from_post_and_user(@post, user)
19
+ end
20
+ next if notifiable_users.empty?
21
+ notifier.new_post(@post, notifiable_users)
12
22
  end
13
23
  end
14
24
 
15
25
  def targeted_users(notifier)
16
- possible_targeted_users.select do |user|
26
+ users_subscribed_via(notifier).reject do |user|
27
+ already_notified_user_ids.include?(user.id)
28
+ end
29
+ end
30
+
31
+ def users_subscribed_via(notifier)
32
+ subscribed_users.select do |user|
17
33
  NotificationsForFollowedTopics
18
34
  .detect_or_default(user.thredded_notifications_for_followed_topics, notifier).enabled? &&
19
35
  MessageboardNotificationsForFollowedTopics
@@ -21,16 +37,22 @@ module Thredded
21
37
  end
22
38
  end
23
39
 
24
- def possible_targeted_users
25
- @possible_targeted_users ||=
26
- @post.postable.followers.includes(:thredded_notifications_for_followed_topics).reject { |u| u == @post.user }
40
+ def subscribed_users
41
+ @subscribed_users ||=
42
+ @post.postable.followers.includes(:thredded_notifications_for_followed_topics).reject do |user|
43
+ user == @post.user || !Thredded::PostPolicy.new(user, @post).read?
44
+ end
45
+ end
46
+
47
+ def already_notified_user_ids
48
+ @notified_user_ids ||= Set.new Thredded::UserPostNotification.notified_user_ids(@post)
27
49
  end
28
50
 
29
51
  private
30
52
 
31
53
  def messageboard_notifier_prefs_by_user_id
32
54
  @messageboard_notifier_prefs_by_user_id ||= MessageboardNotificationsForFollowedTopics
33
- .where(user_id: possible_targeted_users.map(&:id))
55
+ .where(user_id: subscribed_users.map(&:id))
34
56
  .for_messageboard(@post.messageboard).group_by(&:user_id)
35
57
  end
36
58
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
- class TopicsController < Thredded::ApplicationController
3
+ class TopicsController < Thredded::ApplicationController # rubocop:disable Metrics/ClassLength
4
4
  include Thredded::NewTopicParams
5
5
 
6
6
  before_action :thredded_require_login!,
@@ -92,12 +92,23 @@ module Thredded
92
92
 
93
93
  def edit
94
94
  authorize topic, :update?
95
+ @edit_topic = Thredded::EditTopicForm.new(user: thredded_current_user, topic: topic)
95
96
  end
96
97
 
97
98
  def update
99
+ topic.assign_attributes(topic_params_for_update)
98
100
  authorize topic, :update?
99
- if topic.update(topic_params.merge(last_user_id: thredded_current_user.id))
100
- redirect_to messageboard_topic_url(messageboard, topic),
101
+ if topic.messageboard_id_changed?
102
+ # Work around the association not being reset.
103
+ # TODO: report issue to Rails. Looks like a regression of:
104
+ # https://rails.lighthouseapp.com/projects/8994/tickets/2989
105
+ topic.messageboard = Thredded::Messageboard.find(topic.messageboard_id)
106
+
107
+ authorize topic.messageboard, :post?
108
+ end
109
+ @edit_topic = Thredded::EditTopicForm.new(user: thredded_current_user, topic: topic)
110
+ if @edit_topic.save
111
+ redirect_to messageboard_topic_url(topic.messageboard, topic),
101
112
  notice: t('thredded.topics.updated_notice')
102
113
  else
103
114
  render :edit
@@ -148,6 +159,12 @@ module Thredded
148
159
  .permit(:title, :locked, :sticky, category_ids: [])
149
160
  end
150
161
 
162
+ def topic_params_for_update
163
+ params
164
+ .require(:topic)
165
+ .permit(:title, :locked, :sticky, :messageboard_id, category_ids: [])
166
+ end
167
+
151
168
  def current_page
152
169
  (params[:page] || 1).to_i
153
170
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ class EditTopicForm
4
+ include ActiveModel::Model
5
+
6
+ delegate :id, :title, :category_ids, :locked, :sticky, :messageboard, :messageboard_id, :valid?,
7
+ to: :@topic
8
+
9
+ # @param user [Thredded.user_class]
10
+ # @param topic [Thredded::Topic]
11
+ def initialize(user:, topic:)
12
+ @user = user
13
+ @topic = topic
14
+ end
15
+
16
+ def self.model_name
17
+ Thredded::Topic.model_name
18
+ end
19
+
20
+ def category_options
21
+ @topic.messageboard.categories.map { |cat| [cat.name, cat.id] }
22
+ end
23
+
24
+ def messageboard_options
25
+ @user.thredded_can_write_messageboards.map { |messageboard| [messageboard.name, messageboard.id] }
26
+ end
27
+
28
+ def save
29
+ return false unless valid?
30
+ @topic.save!
31
+ true
32
+ end
33
+
34
+ def persisted?
35
+ true
36
+ end
37
+
38
+ def path
39
+ Thredded::UrlsHelper.messageboard_topic_path(@topic.messageboard, @topic)
40
+ end
41
+
42
+ def edit_path
43
+ Thredded::UrlsHelper.edit_messageboard_topic_path(@topic.messageboard, @topic)
44
+ end
45
+
46
+ def messageboard_path
47
+ Thredded::UrlsHelper.messageboard_topics_path(@topic.messageboard)
48
+ end
49
+ end
50
+ end
@@ -22,12 +22,8 @@ module Thredded
22
22
  Thredded::Topic.model_name
23
23
  end
24
24
 
25
- def categories
26
- topic.messageboard.categories
27
- end
28
-
29
25
  def category_options
30
- categories.map { |cat| [cat.name, cat.id] }
26
+ messageboard.categories.map { |cat| [cat.name, cat.id] }
31
27
  end
32
28
 
33
29
  def save
@@ -4,6 +4,7 @@ module Thredded
4
4
  include ::Thredded::UrlsHelper
5
5
  include ::Thredded::NavHelper
6
6
 
7
+ # @return [AllViewHooks] View hooks configuration.
7
8
  def view_hooks
8
9
  @view_hooks ||= Thredded.view_hooks
9
10
  end
@@ -21,6 +21,9 @@ module Thredded
21
21
  has_many :moderation_records,
22
22
  class_name: 'Thredded::PostModerationRecord',
23
23
  dependent: :nullify
24
+ has_many :user_notifications,
25
+ class_name: 'Thredded::UserPostNotification',
26
+ dependent: :destroy
24
27
  has_one :last_moderation_record, -> { order_newest_first },
25
28
  class_name: 'Thredded::PostModerationRecord'
26
29
 
@@ -44,6 +44,7 @@ module Thredded
44
44
  counter_cache: :topics_count
45
45
 
46
46
  has_many :posts,
47
+ autosave: true,
47
48
  class_name: 'Thredded::Post',
48
49
  foreign_key: :postable_id,
49
50
  inverse_of: :postable,
@@ -76,6 +77,10 @@ module Thredded
76
77
  after_commit :update_messageboard_last_topic, on: :update, if: -> { previous_changes.include?('moderation_state') }
77
78
  after_update :update_last_user_and_time_from_last_post!, if: -> { previous_changes.include?('moderation_state') }
78
79
 
80
+ after_commit :handle_messageboard_change_after_commit,
81
+ on: :update,
82
+ if: -> { previous_changes.include?('messageboard_id') }
83
+
79
84
  # Finds the topic by its slug or ID, or raises Thredded::Errors::TopicNotFound.
80
85
  # @param slug_or_id [String]
81
86
  # @return [Thredded::Topic]
@@ -151,5 +156,17 @@ module Thredded
151
156
  def update_messageboard_last_topic
152
157
  messageboard.update_last_topic!
153
158
  end
159
+
160
+ def handle_messageboard_change_after_commit
161
+ # Update the `posts.messageboard_id` column. The column is just a performance optimization,
162
+ # so use update_all to avoid validitaing, triggering callbacks, and updating the timestamps:
163
+ posts.update_all(messageboard_id: messageboard_id)
164
+
165
+ # Update the associated messageboard metadata that Rails does not update them automatically.
166
+ previous_changes['messageboard_id'].each do |messageboard_id|
167
+ Thredded::Messageboard.reset_counters(messageboard_id, :topics, :posts)
168
+ Messageboard.find(messageboard_id).update_last_topic!
169
+ end
170
+ end
154
171
  end
155
172
  end
@@ -26,6 +26,7 @@ module Thredded
26
26
  opt.has_many :thredded_user_messageboard_preferences, class_name: 'Thredded::UserMessageboardPreference'
27
27
  opt.has_many :thredded_notifications_for_followed_topics, class_name: 'Thredded::NotificationsForFollowedTopics'
28
28
  opt.has_many :thredded_notifications_for_private_topics, class_name: 'Thredded::NotificationsForPrivateTopics'
29
+ opt.has_many :thredded_post_notifications, class_name: 'Thredded::UserPostNotification'
29
30
  opt.has_many :thredded_private_users, class_name: 'Thredded::PrivateUser'
30
31
  opt.has_many :thredded_topic_read_states, class_name: 'Thredded::UserTopicReadState'
31
32
  opt.has_many :thredded_private_topic_read_states, class_name: 'Thredded::UserPrivateTopicReadState'
@@ -12,7 +12,7 @@ module Thredded
12
12
  end
13
13
 
14
14
  module ClassMethods
15
- # Users that can read the given messageboards.
15
+ # Users that can read some of the given messageboards.
16
16
  #
17
17
  # @param _messageboards [Array<Thredded::Messageboard>]
18
18
  # @return [ActiveRecord::Relation<Thredded.user_class>] users that can read the given messageboards
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ module Thredded
3
+ # Delivery records for Thredded::Post notifications.
4
+ class UserPostNotification < ActiveRecord::Base
5
+ belongs_to :user, class_name: Thredded.user_class, inverse_of: :thredded_post_notifications
6
+ belongs_to :post, class_name: 'Thredded::Post', inverse_of: :user_notifications
7
+
8
+ # @param post [Thredded::Post]
9
+ # @return [Array<Integer>] The IDs of users who were already notified about the given post.
10
+ def self.notified_user_ids(post)
11
+ where(post_id: post.id).pluck(:user_id)
12
+ end
13
+
14
+ # Create a user-post notification record for a given post and a user.
15
+ # @param post [Thredded::Post]
16
+ # @param user [Thredded.user_class]
17
+ # @return [Boolean] true if a new record was created, false otherwise (e.g. if a record had already existed).
18
+ def self.create_from_post_and_user(post, user)
19
+ create(
20
+ post_id: post.id,
21
+ user_id: user.id,
22
+ notified_at: Time.zone.now
23
+ )
24
+ rescue ActiveRecord::RecordNotUnique
25
+ false
26
+ end
27
+ end
28
+ end
@@ -5,6 +5,8 @@ module Thredded
5
5
  attr_reader :posts_common
6
6
  # @return [PostForm]
7
7
  attr_reader :post_form
8
+ # @return [MessageboardsIndex]
9
+ attr_reader :messageboards_index
8
10
  # @return [ModerationUserPage]
9
11
  attr_reader :moderation_user_page
10
12
 
@@ -24,6 +26,7 @@ module Thredded
24
26
  @posts_common = PostsCommon.new
25
27
  @post_form = PostForm.new
26
28
  @moderation_user_page = ModerationUserPage.new
29
+ @messageboards_index = MessageboardsIndex.new
27
30
  end
28
31
 
29
32
  # View hooks for collections of public or private posts.
@@ -52,6 +55,24 @@ module Thredded
52
55
  end
53
56
  end
54
57
 
58
+ class MessageboardsIndex
59
+ # @return [Thredded::AllViewHooks::ViewHook]
60
+ attr_reader :container
61
+ # @return [Thredded::AllViewHooks::ViewHook]
62
+ attr_reader :list
63
+ # @return [Thredded::AllViewHooks::ViewHook]
64
+ attr_reader :group
65
+ # @return [Thredded::AllViewHooks::ViewHook]
66
+ attr_reader :messageboard
67
+
68
+ def initialize
69
+ @container = ViewHook.new
70
+ @list = ViewHook.new
71
+ @group = ViewHook.new
72
+ @messageboard = ViewHook.new
73
+ end
74
+ end
75
+
55
76
  class ModerationUserPage
56
77
  # @return [Thredded::AllViewHooks::ViewHook]
57
78
  attr_reader :user_title
@@ -56,13 +56,14 @@ module Thredded
56
56
  [
57
57
  I18n.locale,
58
58
  @post.cache_key,
59
+ (@post.messageboard_id unless @post.private_topic_post?),
59
60
  @post.user ? @post.user.cache_key : 'users/nil',
60
61
  moderation_state || '+',
61
62
  [
62
63
  can_update?,
63
64
  can_destroy?
64
65
  ].map { |p| p ? '+' : '-' } * ''
65
- ].join('/')
66
+ ].compact.join('/')
66
67
  end
67
68
  # rubocop:enable Metrics/CyclomaticComplexity
68
69
  end
@@ -1,22 +1,24 @@
1
1
  <% if policy(messageboard).read? %>
2
- <%= link_to messageboard_topics_path(messageboard), class: 'thredded--messageboard' do %>
3
- <header>
4
- <h2 class="thredded--messageboard--title"><%= messageboard.name %></h2>
5
- <h3 class="thredded--messageboard--meta">
6
- <%= t 'thredded.messageboard.topics_and_posts_counts',
7
- topics_count: number_to_human(messageboard.topics_count).downcase,
8
- posts_count: number_to_human(messageboard.posts_count).downcase %>
9
- </h3>
10
- </header>
2
+ <%= view_hooks.messageboards_index.messageboard.render self, messageboard: messageboard do %>
3
+ <%= link_to messageboard_topics_path(messageboard), class: 'thredded--messageboard' do %>
4
+ <header class="thredded--messageboard--header">
5
+ <h2 class="thredded--messageboard--title"><%= messageboard.name %></h2>
6
+ <h3 class="thredded--messageboard--meta">
7
+ <%= t 'thredded.messageboard.topics_and_posts_counts',
8
+ topics_count: number_with_delimiter(messageboard.topics_count),
9
+ posts_count: number_with_delimiter(messageboard.posts_count) %>
10
+ </h3>
11
+ </header>
11
12
 
12
- <p class="thredded--messageboard--description"><%= messageboard.description %></p>
13
+ <p class="thredded--messageboard--description"><%= messageboard.description %></p>
13
14
 
14
- <% if messageboard.last_topic %>
15
- <p class="thredded--messageboard--byline">
16
- <%= t 'thredded.messageboard.last_updated_by_html',
17
- time_ago: time_ago(messageboard.last_topic.last_post_at),
18
- user: messageboard.last_user.thredded_display_name %>
19
- </p>
15
+ <% if messageboard.last_topic %>
16
+ <p class="thredded--messageboard--byline">
17
+ <%= t 'thredded.messageboard.last_updated_by_html',
18
+ time_ago: time_ago(messageboard.last_topic.last_post_at),
19
+ user: messageboard.last_user.thredded_display_name %>
20
+ </p>
21
+ <% end %>
20
22
  <% end %>
21
23
  <% end %>
22
24
  <% end %>
@@ -2,21 +2,33 @@
2
2
  <% content_for :thredded_page_id, 'thredded--messageboards-index' %>
3
3
  <% content_for :thredded_breadcrumbs, render('thredded/shared/breadcrumbs') %>
4
4
  <%= thredded_page do %>
5
- <section class="thredded--main-section thredded--messageboards">
6
- <% @groups.each do |group| %>
7
- <% if group.name.present? %>
8
- <h3><%= group.name %></h3>
5
+ <%= view_hooks.messageboards_index.container.render self, groups: @groups do %>
6
+ <section class="thredded--main-section thredded--messageboards">
7
+ <%= view_hooks.messageboards_index.list.render self, groups: @groups do %>
8
+ <% @groups.each do |group| %>
9
+ <% if group.name.present? %>
10
+ <h3 class="thredded--messageboards-group--title"><%= group.name %></h3>
11
+ <% end %>
12
+ <div class="thredded--messageboards-group">
13
+ <%= view_hooks.messageboards_index.group.render self, group: group do %>
14
+ <%= render group.messageboards %>
15
+ <% end %>
16
+ </div>
17
+ <% end %>
9
18
  <% end %>
10
- <%= render group.messageboards %>
11
- <% end %>
12
19
 
13
- <div class="thredded--messageboards--actions">
14
- <% if policy(Thredded::Messageboard.new).create? %>
15
- <a class="thredded--button" href="<%= new_messageboard_path %>"><%= t('thredded.messageboard.create') %></a>
16
- <% end %>
17
- <% if policy(Thredded::MessageboardGroup.new).create? %>
18
- <a class="thredded--button" href="<%= new_messageboard_group_path %>"><%= t('thredded.messageboard_group.create') %></a>
19
- <% end %>
20
- </div>
21
- </section>
20
+ <div class="thredded--messageboards--actions">
21
+ <% if policy(Thredded::Messageboard.new).create? %>
22
+ <a class="thredded--button" href="<%= new_messageboard_path %>">
23
+ <%= t('thredded.messageboard.create') %>
24
+ </a>
25
+ <% end %>
26
+ <% if policy(Thredded::MessageboardGroup.new).create? %>
27
+ <a class="thredded--button" href="<%= new_messageboard_group_path %>">
28
+ <%= t('thredded.messageboard_group.create') %>
29
+ </a>
30
+ <% end %>
31
+ </div>
32
+ </section>
33
+ <% end %>
22
34
  <% end %>
@@ -11,7 +11,7 @@
11
11
  <%= form.text_field :title, placeholder: placeholder, required: true %>
12
12
  </li>
13
13
 
14
- <% if form.object.categories.any? %>
14
+ <% if form.object.category_options.any? %>
15
15
  <li class="category">
16
16
  <%= form.select :category_ids,
17
17
  form.object.category_options,
@@ -1,16 +1,17 @@
1
- <% content_for :thredded_page_title, t('thredded.nav.edit_private_topic') %>
1
+ <% content_for :thredded_page_title, t('thredded.nav.edit_topic') %>
2
2
  <% content_for :thredded_page_id, 'thredded--edit-topic' %>
3
3
  <% content_for :thredded_breadcrumbs do %>
4
4
  <ul class="thredded--navigation-breadcrumbs">
5
5
  <li><%= link_to t('thredded.nav.all_messageboards'), messageboards_path %></li>
6
- <li><%= link_to messageboard.name, messageboard_topics_path(@topic.messageboard) %></li>
7
- <li><%= link_to @topic.title, topic_path(@topic) %></li>
8
- <li><%= link_to t('thredded.nav.edit_topic'), edit_messageboard_topic_path(@topic.messageboard, @topic) %></li>
6
+ <li><%= link_to messageboard.name, @edit_topic.messageboard_path %></li>
7
+ <li><%= link_to @edit_topic.title, @edit_topic.path %></li>
8
+ <li><%= link_to t('thredded.nav.edit_topic'), @edit_topic.edit_path %></li>
9
9
  </ul>
10
10
  <% end %>
11
11
 
12
12
  <%= thredded_page do %>
13
- <%= form_for [@topic.messageboard, @topic],
13
+ <%= form_for @edit_topic,
14
+ url: @edit_topic.path,
14
15
  html: { class: 'thredded--form thredded--is-expanded', 'data-thredded-topic-form' => true } do |form| %>
15
16
  <ul class="thredded--form-list on-top">
16
17
 
@@ -22,7 +23,7 @@
22
23
  required: true %>
23
24
  </li>
24
25
 
25
- <% if form.object.categories.any? %>
26
+ <% if form.object.category_options.any? %>
26
27
  <li class="category">
27
28
  <%= form.select :category_ids, form.object.category_options, {},
28
29
  multiple: true,
@@ -30,6 +31,11 @@
30
31
  </li>
31
32
  <% end %>
32
33
 
34
+ <li>
35
+ <%= form.label :messageboard_id, t('thredded.topics.form.messageboard_label') %>
36
+ <%= form.select :messageboard_id, form.object.messageboard_options %>
37
+ </li>
38
+
33
39
  <%= render 'thredded/topics/topic_form_admin_options', form: form %>
34
40
 
35
41
  <li>
@@ -150,6 +150,7 @@ en:
150
150
  categories_placeholder: Categories
151
151
  content_label: :thredded.posts.form.content_label
152
152
  create_btn: Create New Topic
153
+ messageboard_label: Messageboard
153
154
  title_label: Title
154
155
  title_placeholder: :thredded.topics.form.title_label
155
156
  title_placeholder_start: Start a New Topic
@@ -150,6 +150,7 @@ es:
150
150
  categories_placeholder: Categorías
151
151
  content_label: :thredded.posts.form.content_label
152
152
  create_btn: Crear Nuevo Tema
153
+ messageboard_label: Foro
153
154
  title_label: Título
154
155
  title_placeholder: :thredded.topics.form.title_label
155
156
  title_placeholder_start: Crear un Nuevo Tema
@@ -149,6 +149,7 @@ pl:
149
149
  categories_placeholder: Kategorie
150
150
  content_label: :thredded.posts.form.content_label
151
151
  create_btn: Stwórz nowy temat
152
+ messageboard_label: Tablica
152
153
  title_label: Tytuł
153
154
  title_placeholder: :thredded.topics.form.title_label
154
155
  title_placeholder_start: Rozpocznij nowy temat
@@ -153,6 +153,7 @@ pt-BR:
153
153
  categories_placeholder: Categorias
154
154
  content_label: :thredded.posts.form.content_label
155
155
  create_btn: Criar Novo Tópico
156
+ messageboard_label: Fórum
156
157
  title_label: Título
157
158
  title_placeholder: :thredded.topics.form.title_label
158
159
  title_placeholder_start: Iniciar um novo tópico
@@ -149,8 +149,8 @@ class CreateThredded < ActiveRecord::Migration
149
149
  end
150
150
 
151
151
  create_table :thredded_messageboard_users do |t|
152
- t.references :thredded_user_detail, foreign_key: true, null: false
153
- t.references :thredded_messageboard, foreign_key: true, null: false
152
+ t.references :thredded_user_detail, foreign_key: { on_delete: :cascade }, null: false
153
+ t.references :thredded_messageboard, foreign_key: { on_delete: :cascade }, null: false
154
154
  t.datetime :last_seen_at, null: false
155
155
  t.index [:thredded_messageboard_id, :thredded_user_detail_id],
156
156
  name: :index_thredded_messageboard_users_primary
@@ -237,6 +237,16 @@ class CreateThredded < ActiveRecord::Migration
237
237
  t.index [:user_id, :messageboard_id, :notifier_key],
238
238
  name: 'thredded_messageboard_notifications_for_followed_topics_unique', unique: true
239
239
  end
240
+
241
+ create_table :thredded_user_post_notifications do |t|
242
+ t.references :user, null: false
243
+ t.foreign_key Thredded.user_class.table_name, column: :user_id, on_delete: :cascade
244
+ t.references :post, null: false
245
+ t.foreign_key :thredded_posts, column: :post_id, on_delete: :cascade
246
+ t.datetime :notified_at, null: false
247
+ t.index :post_id, name: :index_thredded_user_post_notifications_on_post_id
248
+ t.index [:user_id, :post_id], name: :index_thredded_user_post_notifications_on_user_id_and_post_id, unique: true
249
+ end
240
250
  end
241
251
  end
242
252
  # rubocop:enable Metrics/MethodLength
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ class UpgradeV09ToV010 < ActiveRecord::Migration
3
+ def up
4
+ remove_foreign_key :thredded_messageboard_users, :thredded_messageboards
5
+ add_foreign_key :thredded_messageboard_users, :thredded_messageboards, on_delete: :cascade
6
+ remove_foreign_key :thredded_messageboard_users, :thredded_user_details
7
+ add_foreign_key :thredded_messageboard_users, :thredded_user_details, on_delete: :cascade
8
+
9
+ create_table :thredded_user_post_notifications do |t|
10
+ t.references :user, null: false
11
+ t.foreign_key Thredded.user_class.table_name, column: :user_id, on_delete: :cascade
12
+ t.references :post, null: false
13
+ t.foreign_key :thredded_posts, column: :post_id, on_delete: :cascade
14
+ t.datetime :notified_at, null: false
15
+ t.index :post_id, name: :index_thredded_user_post_notifications_on_post_id
16
+ t.index [:user_id, :post_id], name: :index_thredded_user_post_notifications_on_user_id_and_post_id, unique: true
17
+ end
18
+ end
19
+
20
+ def down
21
+ drop_table :thredded_user_post_notifications
22
+
23
+ remove_foreign_key :thredded_messageboard_users, :thredded_user_details
24
+ add_foreign_key :thredded_messageboard_users, :thredded_user_details
25
+ remove_foreign_key :thredded_messageboard_users, :thredded_messageboards
26
+ add_foreign_key :thredded_messageboard_users, :thredded_messageboards
27
+ end
28
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'factory_girl_rails'
3
+ require_relative '../../spec/support/features/fake_content'
3
4
 
4
5
  # rubocop:disable HandleExceptions
5
6
  begin
@@ -34,7 +34,7 @@ module Thredded
34
34
 
35
35
  private
36
36
 
37
- MATCH_NAME_RE = /(?:^|[\s>])@(\w+|"[\w. ]+")(?=\W|$)/
37
+ MATCH_NAME_RE = /(?:^|[\s>])@([\w\-]+|"[\w.,\- ()]+")(?=\W|$)/
38
38
 
39
39
  def mentioned_names(text_node_html)
40
40
  text_node_html.scan(MATCH_NAME_RE).map(&:first).map { |m| m.start_with?('"') ? m[1..-2] : m }
@@ -45,7 +45,7 @@ module Thredded
45
45
  return unless names.present?
46
46
  @users_provider.call(names).each do |user|
47
47
  name = user.thredded_display_name
48
- maybe_quoted_name = name =~ /[. ]/ ? %("#{name}") : name
48
+ maybe_quoted_name = name =~ /[., ()]/ ? %("#{name}") : name
49
49
  url = Thredded.user_path(@view_context, user)
50
50
  text_node_html.gsub!(
51
51
  /(^|[\s>])(@#{Regexp.escape maybe_quoted_name})([^a-z\d]|$)/i,
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Thredded
3
- VERSION = '0.9.4'
3
+ VERSION = '0.10.0'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thredded
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Oliveira
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-01-30 00:00:00.000000000 Z
12
+ date: 2017-02-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pundit
@@ -762,6 +762,7 @@ files:
762
762
  - app/controllers/thredded/theme_previews_controller.rb
763
763
  - app/controllers/thredded/topic_previews_controller.rb
764
764
  - app/controllers/thredded/topics_controller.rb
765
+ - app/forms/thredded/edit_topic_form.rb
765
766
  - app/forms/thredded/private_topic_form.rb
766
767
  - app/forms/thredded/topic_form.rb
767
768
  - app/forms/thredded/user_preferences_form.rb
@@ -815,6 +816,7 @@ files:
815
816
  - app/models/thredded/user_permissions/read/all.rb
816
817
  - app/models/thredded/user_permissions/write/all.rb
817
818
  - app/models/thredded/user_permissions/write/none.rb
819
+ - app/models/thredded/user_post_notification.rb
818
820
  - app/models/thredded/user_preference.rb
819
821
  - app/models/thredded/user_private_topic_read_state.rb
820
822
  - app/models/thredded/user_topic_follow.rb
@@ -952,6 +954,7 @@ files:
952
954
  - db/upgrade_migrations/20160723012349_upgrade_v0_6_to_v0_7.rb
953
955
  - db/upgrade_migrations/20161019150201_upgrade_v0_7_to_v0_8.rb
954
956
  - db/upgrade_migrations/20161113161801_upgrade_v0_8_to_v0_9.rb
957
+ - db/upgrade_migrations/20170125033319_upgrade_v0_9_to_v0_10.rb
955
958
  - lib/generators/thredded/install/USAGE
956
959
  - lib/generators/thredded/install/install_generator.rb
957
960
  - lib/generators/thredded/install/templates/initializer.rb