Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions lib/discourse_modifications/topic_slug.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

require 'any_ascii'

module ::DiscourseModifications
class TopicSlug

# Converts a topic title into a slug, removing any emoji strings and normalizing the text.
# This is primarily adds the unicode normalization step to the existing slug generation process
# for ASCII encoding.
def self.slug_for_topic(topic, slug, title)
return slug unless (SiteSetting.slug_generation_method || :ascii).to_sym == :ascii

string = title.gsub(/:([\w\-+]+(?::t\d)?):/, "")
string = AnyAscii.transliterate(string)
string = Slug.ascii_generator(string)
string = Slug.prettify_slug(string, max_length: Slug::MAX_LENGTH)

string.blank? || Slug.slug_is_only_numbers?(string) ? "topic-#{topic.id}" : string
end
end
end
7 changes: 7 additions & 0 deletions plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
# url: TODO
# required_version: 2.7.0

gem 'any_ascii', '0.3.2'

enabled_site_setting :discourse_modifications_enabled

module ::DiscourseModifications
Expand All @@ -18,4 +20,9 @@ module ::DiscourseModifications

after_initialize do
# Code which should run after Rails has finished booting

# Note: if this file gets too large, consider moving code into separate files in the lib directory
# and applying a Initializer pattern to load them.

Topic.slug_computed_callbacks << ::DiscourseModifications::TopicSlug.method(:slug_for_topic)
end
62 changes: 62 additions & 0 deletions spec/lib/discourse_modifications/topic_slug_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

RSpec.configure { |c| c.filter_run_when_matching :focus }

RSpec.describe DiscourseModifications::TopicSlug do
let(:topic) { Fabricate(:topic, id: 42) }

describe ".slug_for_topic" do
before do
# Default to ascii slug generation
allow(SiteSetting).to receive(:slug_generation_method).and_return("ascii")
end

it "removes emoji codes from the title" do
title = "Hello :smile: World"
slug = "hello-world"
expect(described_class.slug_for_topic(topic, slug, title)).to eq("hello-world")
end

it "unicode normalizes the title" do
title = "Café"
slug = "cafe"
expect(described_class.slug_for_topic(topic, slug, title)).to eq("cafe")
end

it "returns the original slug if slug_generation_method is not ascii" do
allow(SiteSetting).to receive(:slug_generation_method).and_return("encoded")
expect(described_class.slug_for_topic(topic, "original-slug", "Some Title")).to eq("original-slug")
end

it "returns a fallback slug if the result is blank" do
title = ":smile:"
slug = ""
expect(described_class.slug_for_topic(topic, slug, title)).to eq("topic-42")
end

it "returns a fallback slug if the result is only numbers" do
title = "123456"
slug = "123456"
expect(described_class.slug_for_topic(topic, slug, title)).to eq("topic-42")
end

it "truncates the slug to the max length" do
long_title = "a" * (Slug::MAX_LENGTH + 10)
slug = "a" * (Slug::MAX_LENGTH + 10)
result = described_class.slug_for_topic(topic, slug, long_title)
expect(result.length).to eq(Slug::MAX_LENGTH)
end

it "handles titles with multiple emoji codes" do
title = "Hello :smile: World :rocket:"
slug = "hello-world"
expect(described_class.slug_for_topic(topic, slug, title)).to eq("hello-world")
end

it "handles titles with emoji codes with t modifier" do
title = "Hello :wave:t2: World"
slug = "hello-world"
expect(described_class.slug_for_topic(topic, slug, title)).to eq("hello-world")
end
end
end
Loading