thredded 0.9.4 → 0.10.0

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