DEV: Update username in new quote format - Part 1 (#22032)
When we introduced the new quote format with full-name display name: ``` [quote="Ted Johansson, post:1, topic:2, username:ted"] we overlooked the code responsible for rewriting quotes when a user's name is changed. ``` The functional part of this change adds support for the new quote format in the code that updates quotes when a user's username changes. See the test case in `spec/services/username_changer_spec.rb` for the details. In addition, this change adds a regression test for PrettyText to cover the new quote format, and extracts the code responsible for rewriting raw and cooked quotes into its own `QuoteRewriter` class. The functionality of the latter is tested through the tests in `spec/services/username_changer_spec.rb`.
This commit is contained in:
parent
cb87ef52de
commit
a674c6c4c2
|
@ -11,7 +11,10 @@ module Jobs
|
|||
|
||||
@old_username = args[:old_username].unicode_normalize
|
||||
@new_username = args[:new_username].unicode_normalize
|
||||
@avatar_img = PrettyText.avatar_img(args[:avatar_template], "tiny")
|
||||
|
||||
avatar_img = PrettyText.avatar_img(args[:avatar_template], "tiny")
|
||||
|
||||
@quote_rewriter = QuoteRewriter.new(@user_id, @old_username, @new_username, avatar_img)
|
||||
|
||||
@raw_mention_regex =
|
||||
/
|
||||
|
@ -26,13 +29,10 @@ module Jobs
|
|||
)
|
||||
/ix
|
||||
|
||||
@raw_quote_regex = /(\[quote\s*=\s*["'']?)#{@old_username}(\,?[^\]]*\])/i
|
||||
|
||||
cooked_username = PrettyText::Helpers.format_username(@old_username)
|
||||
@cooked_mention_username_regex = /\A@#{cooked_username}\z/i
|
||||
@cooked_mention_user_path_regex =
|
||||
%r{\A/u(?:sers)?/#{UrlHelper.encode_component(cooked_username)}\z}i
|
||||
@cooked_quote_username_regex = /(?<=\s)#{cooked_username}(?=:)/i
|
||||
|
||||
update_posts
|
||||
update_revisions
|
||||
|
@ -160,10 +160,7 @@ module Jobs
|
|||
end
|
||||
|
||||
def update_raw(raw)
|
||||
raw.gsub(@raw_mention_regex, "@#{@new_username}").gsub(
|
||||
@raw_quote_regex,
|
||||
"\\1#{@new_username}\\2",
|
||||
)
|
||||
@quote_rewriter.rewrite_raw(raw.gsub(@raw_mention_regex, "@#{@new_username}"))
|
||||
end
|
||||
|
||||
# Uses Nokogiri instead of rebake, because it works for posts and revisions
|
||||
|
@ -182,28 +179,7 @@ module Jobs
|
|||
) if a["href"]
|
||||
end
|
||||
|
||||
doc
|
||||
.css("aside.quote")
|
||||
.each do |aside|
|
||||
next unless div = aside.at_css("div.title")
|
||||
|
||||
username_replaced = false
|
||||
|
||||
aside["data-username"] = @new_username if aside["data-username"] == @old_username
|
||||
|
||||
div.children.each do |child|
|
||||
if child.text?
|
||||
content = child.content
|
||||
username_replaced =
|
||||
content.gsub!(@cooked_quote_username_regex, @new_username).present?
|
||||
child.content = content if username_replaced
|
||||
end
|
||||
end
|
||||
|
||||
if username_replaced || quotes_correct_user?(aside)
|
||||
div.at_css("img.avatar")&.replace(@avatar_img)
|
||||
end
|
||||
end
|
||||
@quote_rewriter.rewrite_cooked(doc)
|
||||
|
||||
doc.to_html
|
||||
end
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QuoteRewriter
|
||||
def initialize(user_id, old_username, new_username, avatar_img)
|
||||
@user_id = user_id
|
||||
@old_username = old_username
|
||||
@new_username = new_username
|
||||
@avatar_img = avatar_img
|
||||
end
|
||||
|
||||
def rewrite_raw(raw)
|
||||
pattern =
|
||||
Regexp.union(
|
||||
/(?<pre>\[quote\s*=\s*["'']?.*username:)#{old_username}(?<post>\,?[^\]]*\])/i,
|
||||
/(?<pre>\[quote\s*=\s*["'']?)#{old_username}(?<post>\,?[^\]]*\])/i,
|
||||
)
|
||||
|
||||
raw.gsub(pattern, "\\k<pre>#{new_username}\\k<post>")
|
||||
end
|
||||
|
||||
def rewrite_cooked(cooked)
|
||||
pattern = /(?<=\s)#{PrettyText::Helpers.format_username(old_username)}(?=:)/i
|
||||
|
||||
cooked
|
||||
.css("aside.quote")
|
||||
.each do |aside|
|
||||
next unless div = aside.at_css("div.title")
|
||||
|
||||
username_replaced = false
|
||||
|
||||
aside["data-username"] = new_username if aside["data-username"] == old_username
|
||||
|
||||
div.children.each do |child|
|
||||
if child.text?
|
||||
content = child.content
|
||||
username_replaced = content.gsub!(pattern, new_username).present?
|
||||
child.content = content if username_replaced
|
||||
end
|
||||
end
|
||||
|
||||
if username_replaced || quotes_correct_user?(aside)
|
||||
div.at_css("img.avatar")&.replace(avatar_img)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :user_id, :old_username, :new_username, :avatar_img
|
||||
|
||||
def quotes_correct_user?(aside)
|
||||
Post.exists?(topic_id: aside["data-topic"], post_number: aside["data-post"], user_id: user_id)
|
||||
end
|
||||
end
|
|
@ -28,6 +28,28 @@ RSpec.describe PrettyText do
|
|||
|
||||
before { User.stubs(:default_template).returns(default_avatar) }
|
||||
|
||||
it "correctly extracts usernames from the new quote format" do
|
||||
topic = Fabricate(:topic, title: "this is a test topic :slight_smile:")
|
||||
expected = <<~HTML
|
||||
<aside class="quote no-group" data-username="codinghorror" data-post="2" data-topic="#{topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<a href="http://test.localhost/t/this-is-a-test-topic/#{topic.id}/2">This is a test topic <img width="20" height="20" src="/images/emoji/twitter/slight_smile.png?v=#{Emoji::EMOJI_VERSION}" title="slight_smile" loading="lazy" alt="slight_smile" class="emoji"></a>
|
||||
</div>
|
||||
<blockquote>
|
||||
<p>ddd</p>
|
||||
</blockquote>
|
||||
</aside>
|
||||
HTML
|
||||
|
||||
expect(
|
||||
cook(
|
||||
"[quote=\"Jeff, post:2, topic:#{topic.id}, username:codinghorror\"]\nddd\n[/quote]",
|
||||
topic_id: 1,
|
||||
),
|
||||
).to eq(n(expected))
|
||||
end
|
||||
|
||||
it "do off topic quoting with emoji unescape" do
|
||||
topic = Fabricate(:topic, title: "this is a test topic :slight_smile:")
|
||||
expected = <<~HTML
|
||||
|
|
|
@ -494,6 +494,41 @@ RSpec.describe UsernameChanger do
|
|||
HTML
|
||||
end
|
||||
|
||||
it "replaces the username in new quote format" do
|
||||
post = create_post_and_change_username(raw: <<~RAW)
|
||||
Lorem ipsum
|
||||
|
||||
[quote="Foo Bar, post:1, topic:#{quoted_post.topic.id}, username:foo"]
|
||||
quoted post
|
||||
[/quote]
|
||||
|
||||
dolor sit amet
|
||||
RAW
|
||||
|
||||
expect(post.raw).to eq(<<~RAW.strip)
|
||||
Lorem ipsum
|
||||
|
||||
[quote="Foo Bar, post:1, topic:#{quoted_post.topic.id}, username:bar"]
|
||||
quoted post
|
||||
[/quote]
|
||||
|
||||
dolor sit amet
|
||||
RAW
|
||||
|
||||
expect(post.cooked).to match_html(<<~HTML)
|
||||
<p>Lorem ipsum</p>
|
||||
<aside class="quote no-group" data-username="bar" data-post="1" data-topic="#{quoted_post.topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
<img loading="lazy" alt="" width="24" height="24" src="//test.localhost/letter_avatar_proxy/v4/letter/b/b77776/48.png" class="avatar"> Foo Bar:</div>
|
||||
<blockquote>
|
||||
<p>quoted post</p>
|
||||
</blockquote>
|
||||
</aside>
|
||||
<p>dolor sit amet</p>
|
||||
HTML
|
||||
end
|
||||
|
||||
context "when there is a simple quote" do
|
||||
let(:raw) { <<~RAW }
|
||||
Lorem ipsum
|
||||
|
@ -599,7 +634,6 @@ RSpec.describe UsernameChanger do
|
|||
quoted post
|
||||
</blockquote>
|
||||
</aside>
|
||||
|
||||
<aside class="quote" data-post="#{another_quoted_post.post_number}" data-topic="#{another_quoted_post.topic.id}">
|
||||
<div class="title">
|
||||
<div class="quote-controls"></div>
|
||||
|
|
Loading…
Reference in New Issue