FIX: Allow for nested chat transcripts (#19572)

The way our markdown raw_html hoisting worked, we only
supported one level of hoisting the HTML content. However
when nesting [chat] transcript BBCode we need to allow
for multiple levels of it. This commit changes opts.discourse.hoisted
to be more constant, and the GUID keys that have the hoisted
content are only deleted by unhoistForCooked rather than
the cook function itself, which prematurely deletes them
when they are needed further down the line.
This commit is contained in:
Martin Brennan 2022-12-23 09:56:30 +10:00 committed by GitHub
parent 788bcb7736
commit 641a1a6b44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 5 deletions

View File

@ -423,6 +423,7 @@ function unhoistForCooked(hoisted, cooked) {
found = true;
return hoisted[key];
});
delete hoisted[key];
};
while (found) {
@ -578,14 +579,16 @@ export function setup(opts, siteSettings, state) {
export function cook(raw, opts) {
// we still have to hoist html_raw nodes so they bypass the allowlister
// this is the case for oneboxes
let hoisted = {};
opts.discourse.hoisted = hoisted;
// this is the case for oneboxes and also certain plugins that require
// raw HTML rendering within markdown bbcode rules
opts.discourse.hoisted ??= {};
const rendered = opts.engine.render(raw);
let cooked = opts.discourse.sanitizer(rendered).trim();
cooked = unhoistForCooked(hoisted, cooked);
delete opts.discourse.hoisted;
// opts.discourse.hoisted guid keys will be deleted within here to
// keep the object empty
cooked = unhoistForCooked(opts.discourse.hoisted, cooked);
return cooked;
}

View File

@ -211,4 +211,52 @@ martin</div>
ensure
InlineOneboxer.invalidate("https://en.wikipedia.org/wiki/Hyperlink")
end
it "handles nested chat transcripts in posts" do
SiteSetting.external_system_avatars_enabled = false
freeze_time
channel = Fabricate(:chat_channel)
message1 = Fabricate(:chat_message, chat_channel: channel, user: post.user)
message2 = Fabricate(:chat_message, chat_channel: channel, user: post.user)
md = ChatTranscriptService.new(channel, message2.user, messages_or_ids: [message2.id]).generate_markdown
message1.update!(message: md)
md_for_post = ChatTranscriptService.new(channel, message1.user, messages_or_ids: [message1.id]).generate_markdown
post.update!(raw: md_for_post)
expect(post.cooked.chomp).to eq(<<~COOKED.chomp)
<div class="chat-transcript" data-message-id="#{message1.id}" data-username="#{message1.user.username}" data-datetime="#{message1.created_at.iso8601}" data-channel-name="#{channel.name}" data-channel-id="#{channel.id}">
<div class="chat-transcript-user">
<div class="chat-transcript-user-avatar">
<img loading="lazy" alt="" width="20" height="20" src="//test.localhost#{post.user.avatar_template.gsub("{size}", "40")}" class="avatar">
</div>
<div class="chat-transcript-username">
#{message1.user.username}</div>
<div class="chat-transcript-datetime">
<a href="/chat/channel/#{channel.id}/-?messageId=#{message1.id}" title="#{message1.created_at.iso8601}"></a>
</div>
<a class="chat-transcript-channel" href="/chat/channel/#{channel.id}/-">
##{channel.name}</a>
</div>
<div class="chat-transcript-messages">
<div class="chat-transcript" data-message-id="#{message2.id}" data-username="#{message2.user.username}" data-datetime="#{message2.created_at.iso8601}" data-channel-name="#{channel.name}" data-channel-id="#{channel.id}">
<div class="chat-transcript-user">
<div class="chat-transcript-user-avatar">
<img loading="lazy" alt="" width="20" height="20" src="//test.localhost#{post.user.avatar_template.gsub("{size}", "40")}" class="avatar">
</div>
<div class="chat-transcript-username">
#{message2.user.username}</div>
<div class="chat-transcript-datetime">
<a href="/chat/channel/#{channel.id}/-?messageId=#{message2.id}" title="#{message1.created_at.iso8601}"></a>
</div>
<a class="chat-transcript-channel" href="/chat/channel/#{channel.id}/-">
##{channel.name}</a>
</div>
<div class="chat-transcript-messages">
<p>#{message2.message}</p>
</div>
</div>
</div>
</div>
COOKED
end
end