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
|
@old_username = args[:old_username].unicode_normalize
|
||||||
@new_username = args[:new_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 =
|
@raw_mention_regex =
|
||||||
/
|
/
|
||||||
|
@ -26,13 +29,10 @@ module Jobs
|
||||||
)
|
)
|
||||||
/ix
|
/ix
|
||||||
|
|
||||||
@raw_quote_regex = /(\[quote\s*=\s*["'']?)#{@old_username}(\,?[^\]]*\])/i
|
|
||||||
|
|
||||||
cooked_username = PrettyText::Helpers.format_username(@old_username)
|
cooked_username = PrettyText::Helpers.format_username(@old_username)
|
||||||
@cooked_mention_username_regex = /\A@#{cooked_username}\z/i
|
@cooked_mention_username_regex = /\A@#{cooked_username}\z/i
|
||||||
@cooked_mention_user_path_regex =
|
@cooked_mention_user_path_regex =
|
||||||
%r{\A/u(?:sers)?/#{UrlHelper.encode_component(cooked_username)}\z}i
|
%r{\A/u(?:sers)?/#{UrlHelper.encode_component(cooked_username)}\z}i
|
||||||
@cooked_quote_username_regex = /(?<=\s)#{cooked_username}(?=:)/i
|
|
||||||
|
|
||||||
update_posts
|
update_posts
|
||||||
update_revisions
|
update_revisions
|
||||||
|
@ -160,10 +160,7 @@ module Jobs
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_raw(raw)
|
def update_raw(raw)
|
||||||
raw.gsub(@raw_mention_regex, "@#{@new_username}").gsub(
|
@quote_rewriter.rewrite_raw(raw.gsub(@raw_mention_regex, "@#{@new_username}"))
|
||||||
@raw_quote_regex,
|
|
||||||
"\\1#{@new_username}\\2",
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Uses Nokogiri instead of rebake, because it works for posts and revisions
|
# Uses Nokogiri instead of rebake, because it works for posts and revisions
|
||||||
|
@ -182,28 +179,7 @@ module Jobs
|
||||||
) if a["href"]
|
) if a["href"]
|
||||||
end
|
end
|
||||||
|
|
||||||
doc
|
@quote_rewriter.rewrite_cooked(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
|
|
||||||
|
|
||||||
doc.to_html
|
doc.to_html
|
||||||
end
|
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) }
|
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
|
it "do off topic quoting with emoji unescape" do
|
||||||
topic = Fabricate(:topic, title: "this is a test topic :slight_smile:")
|
topic = Fabricate(:topic, title: "this is a test topic :slight_smile:")
|
||||||
expected = <<~HTML
|
expected = <<~HTML
|
||||||
|
|
|
@ -494,6 +494,41 @@ RSpec.describe UsernameChanger do
|
||||||
HTML
|
HTML
|
||||||
end
|
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
|
context "when there is a simple quote" do
|
||||||
let(:raw) { <<~RAW }
|
let(:raw) { <<~RAW }
|
||||||
Lorem ipsum
|
Lorem ipsum
|
||||||
|
@ -599,7 +634,6 @@ RSpec.describe UsernameChanger do
|
||||||
quoted post
|
quoted post
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<aside class="quote" data-post="#{another_quoted_post.post_number}" data-topic="#{another_quoted_post.topic.id}">
|
<aside class="quote" data-post="#{another_quoted_post.post_number}" data-topic="#{another_quoted_post.topic.id}">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="quote-controls"></div>
|
<div class="quote-controls"></div>
|
||||||
|
|
Loading…
Reference in New Issue