FIX: Automatically generate category channel slugs (#18879)

This commit automatically ensures that category channels
have slugs when they are created or updated based on the
channel name, category name, or existing slug. The behaviour
has been copied from the Category model.

We also include a backfill here with a simplified version
of Slug.for with deduplication to fill the slugs for already
created Category chat channels.

The channel slug is also now used for chat notifications,
and for the UI and navigation for chat. `slugifyChannel`
is still used, but now does the following fallback:

* Uses channel.slug if it is present
* Uses channel.escapedTitle if it is present
* Uses channel.title if it is present

In future we may want to remove this altogether
and always rely on the slug being present, but this
is currently not possible because we are not generating
slugs for DM channels at this point.
This commit is contained in:
Martin Brennan
2022-11-09 10:28:31 +10:00
committed by GitHub
parent 3bab7a44d4
commit c6764d8c74
34 changed files with 367 additions and 75 deletions

View File

@ -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]

View File

@ -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 =

View File

@ -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,

View File

@ -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

View File

@ -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
#

View File

@ -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

View File

@ -9,6 +9,7 @@ class ChatChannelSerializer < ApplicationSerializer
:chatable_url,
:description,
:title,
:slug,
:last_message_sent_at,
:status,
:archive_failed,