diff --git a/app/models/category.rb b/app/models/category.rb index 0efbb5f3ed8..7e7bd947b02 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -374,7 +374,7 @@ class Category < ActiveRecord::Base elsif SiteSetting.slug_generation_method == 'ascii' && !CGI.unescape(self.slug).ascii_only? errors.add(:slug, I18n.t("category.errors.slug_contains_non_ascii_chars")) elsif duplicate_slug? - errors.add(:slug, 'is already in use') + errors.add(:slug, I18n.t("category.errors.is_already_in_use")) end else # auto slug diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 73b0d3fe785..07b182b805e 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -697,6 +697,7 @@ en: disallowed_topic_tags: "This topic has tags not allowed by this category: '%{tags}'" disallowed_tags_generic: "This topic has disallowed tags." slug_contains_non_ascii_chars: "contains non-ascii characters" + is_already_in_use: "is already in use" cannot_delete: uncategorized: "This category is special. It is intended as a holding area for topics that have no category; it cannot be deleted." has_subcategories: "Can't delete this category because it has sub-categories." diff --git a/plugins/chat/app/controllers/chat_controller.rb b/plugins/chat/app/controllers/chat_controller.rb index e272188e720..3f78f15b8ef 100644 --- a/plugins/chat/app/controllers/chat_controller.rb +++ b/plugins/chat/app/controllers/chat_controller.rb @@ -352,6 +352,7 @@ class Chat::ChatController < Chat::ChatBaseController message: "chat.invitation_notification", chat_channel_id: @chat_channel.id, chat_channel_title: @chat_channel.title(user), + chat_channel_slug: @chat_channel.slug, invited_by_username: current_user.username, } data[:chat_message_id] = params[:chat_message_id] if params[:chat_message_id] diff --git a/plugins/chat/app/jobs/regular/chat_notify_mentioned.rb b/plugins/chat/app/jobs/regular/chat_notify_mentioned.rb index 523d3259f2f..d6fa48e3320 100644 --- a/plugins/chat/app/jobs/regular/chat_notify_mentioned.rb +++ b/plugins/chat/app/jobs/regular/chat_notify_mentioned.rb @@ -39,9 +39,10 @@ module Jobs is_direct_message_channel: @chat_channel.direct_message_channel?, } - data[:chat_channel_title] = @chat_channel.title( - membership.user, - ) unless @is_direct_message_channel + if !@is_direct_message_channel + data[:chat_channel_title] = @chat_channel.title(membership.user) + data[:chat_channel_slug] = @chat_channel.slug + end return data if identifier_type == :direct_mentions @@ -64,8 +65,7 @@ module Jobs username: @creator.username, tag: Chat::ChatNotifier.push_notification_tag(:mention, @chat_channel.id), excerpt: @chat_message.push_notification_excerpt, - post_url: - "/chat/channel/#{@chat_channel.id}/#{@chat_channel.title(membership.user)}?messageId=#{@chat_message.id}", + post_url: "#{@chat_channel.relative_url}?messageId=#{@chat_message.id}", } translation_prefix = diff --git a/plugins/chat/app/jobs/regular/chat_notify_watching.rb b/plugins/chat/app/jobs/regular/chat_notify_watching.rb index c2c2cd8681e..e9b8805e88d 100644 --- a/plugins/chat/app/jobs/regular/chat_notify_watching.rb +++ b/plugins/chat/app/jobs/regular/chat_notify_watching.rb @@ -62,7 +62,7 @@ module Jobs payload = { username: @creator.username, notification_type: Notification.types[:chat_message], - post_url: "/chat/channel/#{@chat_channel.id}/#{@chat_channel.title(user)}", + post_url: @chat_channel.relative_url, translated_title: I18n.t(translation_key, translation_args), tag: Chat::ChatNotifier.push_notification_tag(:message, @chat_channel.id), excerpt: @chat_message.push_notification_excerpt, diff --git a/plugins/chat/app/models/category_channel.rb b/plugins/chat/app/models/category_channel.rb index 2a2a32246b4..e95c3d5cff8 100644 --- a/plugins/chat/app/models/category_channel.rb +++ b/plugins/chat/app/models/category_channel.rb @@ -21,7 +21,23 @@ class CategoryChannel < ChatChannel name.presence || category.name end - def slug - title.truncate(100).parameterize + def generate_auto_slug + return if self.slug.present? + self.slug = Slug.for(self.title.strip, "") + self.slug = "" if duplicate_slug? + end + + def ensure_slug_ok + # if we don't unescape it first we strip the % from the encoded version + slug = SiteSetting.slug_generation_method == "encoded" ? CGI.unescape(self.slug) : self.slug + self.slug = Slug.for(slug, "", method: :encoded) + + if self.slug.blank? + errors.add(:slug, :invalid) + elsif SiteSetting.slug_generation_method == "ascii" && !CGI.unescape(self.slug).ascii_only? + errors.add(:slug, I18n.t("chat.category_channel.errors.slug_contains_non_ascii_chars")) + elsif duplicate_slug? + errors.add(:slug, I18n.t("chat.category_channel.errors.is_already_in_use")) + end end end diff --git a/plugins/chat/app/models/chat_channel.rb b/plugins/chat/app/models/chat_channel.rb index bbcb53e0ae7..560ae2d322b 100644 --- a/plugins/chat/app/models/chat_channel.rb +++ b/plugins/chat/app/models/chat_channel.rb @@ -21,6 +21,8 @@ class ChatChannel < ActiveRecord::Base }, presence: true, allow_nil: true + validate :ensure_slug_ok + before_validation :generate_auto_slug scope :public_channels, -> { @@ -74,7 +76,11 @@ class ChatChannel < ActiveRecord::Base end def url - "#{Discourse.base_url}/chat/channel/#{self.id}/-" + "#{Discourse.base_url}#{relative_url}" + end + + def relative_url + "/chat/channel/#{self.id}/#{self.slug || "-"}" end def public_channel_title @@ -109,6 +115,10 @@ class ChatChannel < ActiveRecord::Base ChatPublisher.publish_channel_status(self) end + + def duplicate_slug? + ChatChannel.where(slug: self.slug).where.not(id: self.id).any? + end end # == Schema Information @@ -132,6 +142,7 @@ end # auto_join_users :boolean default(FALSE), not null # user_count_stale :boolean default(FALSE), not null # slug :string +# type :string # # Indexes # diff --git a/plugins/chat/app/models/direct_message_channel.rb b/plugins/chat/app/models/direct_message_channel.rb index 69be58827a6..9d116643d7e 100644 --- a/plugins/chat/app/models/direct_message_channel.rb +++ b/plugins/chat/app/models/direct_message_channel.rb @@ -18,4 +18,12 @@ class DirectMessageChannel < ChatChannel def title(user) direct_message.chat_channel_title_for_user(self, user) end + + def ensure_slug_ok + true + end + + def generate_auto_slug + self.slug = nil + end end diff --git a/plugins/chat/app/serializers/chat_channel_serializer.rb b/plugins/chat/app/serializers/chat_channel_serializer.rb index 8bb24577697..6a194047f98 100644 --- a/plugins/chat/app/serializers/chat_channel_serializer.rb +++ b/plugins/chat/app/serializers/chat_channel_serializer.rb @@ -9,6 +9,7 @@ class ChatChannelSerializer < ApplicationSerializer :chatable_url, :description, :title, + :slug, :last_message_sent_at, :status, :archive_failed, diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js index a523b2cea6b..a190e99a4b9 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js @@ -58,7 +58,7 @@ export default { } get name() { - return dasherize(slugifyChannel(this.title)); + return dasherize(slugifyChannel(this.channel)); } get classNames() { @@ -72,7 +72,7 @@ export default { } get models() { - return [this.channel.id, slugifyChannel(this.title)]; + return [this.channel.id, slugifyChannel(this.channel)]; } get text() { @@ -240,7 +240,7 @@ export default { } get name() { - return slugifyChannel(this.title); + return slugifyChannel(this.channel); } get classNames() { @@ -254,7 +254,7 @@ export default { } get models() { - return [this.channel.id, slugifyChannel(this.title)]; + return [this.channel.id, slugifyChannel(this.channel)]; } get title() { diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-user-menu.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-user-menu.js index 192203a506a..62758b9b0c5 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-user-menu.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-user-menu.js @@ -20,11 +20,15 @@ export default { (NotificationItemBase) => { return class extends NotificationItemBase { get linkHref() { - const title = this.notification.data.chat_channel_title - ? slugifyChannel(this.notification.data.chat_channel_title) - : "-"; - - return `/chat/channel/${this.notification.data.chat_channel_id}/${title}?messageId=${this.notification.data.chat_message_id}`; + const slug = slugifyChannel({ + title: this.notification.data.chat_channel_title, + slug: this.notification.data.chat_channel_slug, + }); + return `/chat/channel/${ + this.notification.data.chat_channel_id + }/${slug || "-"}?messageId=${ + this.notification.data.chat_message_id + }`; } get linkTitle() { @@ -53,11 +57,15 @@ export default { (NotificationItemBase) => { return class extends NotificationItemBase { get linkHref() { - const title = this.notification.data.chat_channel_title - ? slugifyChannel(this.notification.data.chat_channel_title) - : "-"; - - return `/chat/channel/${this.notification.data.chat_channel_id}/${title}?messageId=${this.notification.data.chat_message_id}`; + const slug = slugifyChannel({ + title: this.notification.data.chat_channel_title, + slug: this.notification.data.chat_channel_slug, + }); + return `/chat/channel/${ + this.notification.data.chat_channel_id + }/${slug || "-"}?messageId=${ + this.notification.data.chat_message_id + }`; } get linkTitle() { diff --git a/plugins/chat/assets/javascripts/discourse/lib/slugify-channel.js b/plugins/chat/assets/javascripts/discourse/lib/slugify-channel.js index 885f9d11dea..f7a2734879e 100644 --- a/plugins/chat/assets/javascripts/discourse/lib/slugify-channel.js +++ b/plugins/chat/assets/javascripts/discourse/lib/slugify-channel.js @@ -1,8 +1,19 @@ import { slugify } from "discourse/lib/utilities"; -export default function slugifyChannel(title) { - const slug = slugify(title); - return ( - slug.length ? slug : title.trim().toLowerCase().replace(/\s|_+/g, "-") +export default function slugifyChannel(channel) { + if (channel.slug) { + return channel.slug; + } + const slug = slugify(channel.escapedTitle || channel.title); + const resolvedSlug = ( + slug.length + ? slug + : channel.title.trim().toLowerCase().replace(/\s|_+/g, "-") ).slice(0, 100); + + if (!resolvedSlug) { + return "-"; + } + + return resolvedSlug; } diff --git a/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js b/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js index 97e42858d19..999870ac8a5 100644 --- a/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js +++ b/plugins/chat/assets/javascripts/discourse/routes/chat-channel.js @@ -42,7 +42,7 @@ export default class ChatChannelRoute extends DiscourseRoute { this.chat.setActiveChannel(model?.chatChannel); const queryParams = this.paramsFor(this.routeName); - const slug = slugifyChannel(model.chatChannel.title); + const slug = slugifyChannel(model.chatChannel); if (queryParams?.channelTitle !== slug) { this.replaceWith("chat.channel.index", model.chatChannel.id, slug); } diff --git a/plugins/chat/assets/javascripts/discourse/services/chat.js b/plugins/chat/assets/javascripts/discourse/services/chat.js index af4925489b7..237e8328cf4 100644 --- a/plugins/chat/assets/javascripts/discourse/services/chat.js +++ b/plugins/chat/assets/javascripts/discourse/services/chat.js @@ -503,7 +503,7 @@ export default class Chat extends Service { return this.router.transitionTo( "chat.channel", response.id, - slugifyChannel(response.title), + slugifyChannel(response), { queryParams } ); }); @@ -535,7 +535,7 @@ export default class Chat extends Service { return this.router.transitionTo( "chat.channel", channel.id, - slugifyChannel(channel.title), + slugifyChannel(channel), { queryParams } ); } else { diff --git a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info.hbs b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info.hbs index 01f6cc80854..fc488396a5f 100644 --- a/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info.hbs +++ b/plugins/chat/assets/javascripts/discourse/templates/chat-channel-info.hbs @@ -12,7 +12,7 @@ @route="chat.channel" @models={{array this.model.chatChannel.id - (slugify-channel this.model.chatChannel.title) + (slugify-channel this.model.chatChannel) }} class="channel-info__back-btn" > @@ -40,7 +40,7 @@ @route={{concat "chat.channel.info." tab}} @models={{array this.model.chatChannel.id - (slugify-channel this.model.chatChannel.title) + (slugify-channel this.model.chatChannel) }} class="chat-tabs-list__link" > diff --git a/plugins/chat/assets/javascripts/discourse/templates/components/chat-channel-card.hbs b/plugins/chat/assets/javascripts/discourse/templates/components/chat-channel-card.hbs index 04be19f906b..411dc94ea38 100644 --- a/plugins/chat/assets/javascripts/discourse/templates/components/chat-channel-card.hbs +++ b/plugins/chat/assets/javascripts/discourse/templates/components/chat-channel-card.hbs @@ -10,7 +10,7 @@