mirror of
https://github.com/discourse/discourse.git
synced 2026-08-07 03:35:32 -05:00
FEATURE: Implement chat transcripts in rich editor (#31819)
This commit allows the ProseMirror rich editor to display chat transcripts copied from chat using the "Copy" button. The BBCode usually looks something like this: ``` [chat quote="hunter;29856;2025-03-20T07:13:04Z" channel="design gems 🎉" channelId="95"] haha **ok** _cool_ [/chat] ``` But there are several variations that must be accounted for: * Single message from single user * Multiple messages from a single and multiple users * Messages inside chat threads The rich transcript extension has to ignore many of the chat transcript markdown tokens because they simply aren't necessary -- none of the ProseMirror nodes need to be editable. So, we basically recreate the same HTML that the chat transcript markdown rule does in the `toDOM()` function. Maybe in future we want to make the markdown rule do less and have this HTML creation in one place, but for now we need to mirror in both files. --------- Co-authored-by: Renato Atilio <renato@discourse.org>
This commit is contained in:
co-authored by
Renato Atilio
parent
b4cdc39e51
commit
d3c2bd015d
@@ -0,0 +1,176 @@
|
||||
import { getOwner } from "@ember/owner";
|
||||
import { module, test } from "qunit";
|
||||
import {
|
||||
registerRichEditorExtension,
|
||||
resetRichEditorExtensions,
|
||||
} from "discourse/lib/composer/rich-editor-extensions";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { setupRichEditor } from "discourse/tests/helpers/rich-editor-helper";
|
||||
import richEditorExtension from "discourse/plugins/chat/lib/rich-editor-extension";
|
||||
|
||||
module(
|
||||
"Integration | Component | prosemirror-editor - chat transcript extension",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.siteSettings.rich_editor = true;
|
||||
this.siteSettings.chat_enabled = true;
|
||||
|
||||
// This is necessary for the chat transcripts to work in JS, because this
|
||||
// is necessary for the markdown-it rule to run, this tells chat what
|
||||
// pretty text features and markdown rules are allowed in chat transcripts.
|
||||
const site = getOwner(this).lookup("service:site");
|
||||
site.set(
|
||||
"markdown_additional_options",
|
||||
JSON.parse(
|
||||
'{"chat":{"limited_pretty_text_features":["anchor","bbcode-block","bbcode-inline","code","category-hashtag","censored","chat-transcript","discourse-local-dates","emoji","emojiShortcuts","inlineEmoji","html-img","hashtag-autocomplete","mentions","unicodeUsernames","onebox","quotes","spoiler-alert","table","text-post-process","upload-protocol","watched-words","chat-html-inline"],"limited_pretty_text_markdown_rules":["autolink","list","backticks","newline","code","fence","image","table","linkify","link","strikethrough","blockquote","emphasis","replacements","escape"],"hashtag_configurations":{"topic-composer":["category","tag","channel"],"chat-composer":["channel","category","tag"]}}}'
|
||||
)
|
||||
);
|
||||
|
||||
resetRichEditorExtensions().then(() => {
|
||||
registerRichEditorExtension(richEditorExtension);
|
||||
});
|
||||
});
|
||||
|
||||
test("single message from single user transcript", async function (assert) {
|
||||
const singleMessageSingleUserMarkdown = `[chat quote="hunter;29856;2025-03-20T07:13:04Z" channel="design gems :tada:" channelId="95"]
|
||||
haha **ok** _cool_
|
||||
[/chat]
|
||||
`;
|
||||
const [{ value }] = await setupRichEditor(
|
||||
assert,
|
||||
singleMessageSingleUserMarkdown
|
||||
);
|
||||
const rootElement = document.querySelector(
|
||||
".ProseMirror .chat-transcript"
|
||||
);
|
||||
assert
|
||||
.dom(".chat-transcript-messages", rootElement)
|
||||
.hasHtml("<p>haha <strong>ok</strong> <em>cool</em></p>");
|
||||
assert
|
||||
.dom(".chat-transcript-user .chat-transcript-username", rootElement)
|
||||
.hasText("hunter");
|
||||
assert
|
||||
.dom(".chat-transcript-user .chat-transcript-datetime", rootElement)
|
||||
.exists();
|
||||
assert
|
||||
.dom(".chat-transcript-channel", rootElement)
|
||||
.hasText("#design gems");
|
||||
assert
|
||||
.dom(".chat-transcript-channel", rootElement)
|
||||
.hasAttribute("href", "/chat/c/-/95");
|
||||
assert.dom(".chat-transcript-channel img[title='tada']").exists();
|
||||
|
||||
assert.strictEqual(value, singleMessageSingleUserMarkdown);
|
||||
});
|
||||
|
||||
test("multiple messages from multiple different users", async function (assert) {
|
||||
const multiMessagesMultiUserMarkdown = `[chat quote="martin;29853;2025-03-20T07:12:55Z" channel="design gems :tada:" channelId="95" multiQuote="true" chained="true"]
|
||||
test
|
||||
[/chat]
|
||||
[chat quote="hunter;29856;2025-03-20T07:13:04Z" chained="true"]
|
||||
haha **ok** _cool_
|
||||
[/chat]
|
||||
`;
|
||||
const [{ value }] = await setupRichEditor(
|
||||
assert,
|
||||
multiMessagesMultiUserMarkdown
|
||||
);
|
||||
|
||||
assert.dom(".chat-transcript").exists({ count: 2 });
|
||||
assert.dom(".chat-transcript-messages").exists({ count: 2 });
|
||||
assert.dom(".chat-transcript-user").exists({ count: 2 });
|
||||
assert
|
||||
.dom(".chat-transcript:nth-of-type(1)")
|
||||
.hasClass("chat-transcript-chained");
|
||||
assert
|
||||
.dom(".chat-transcript-meta")
|
||||
.hasText("Originally sent in design gems");
|
||||
assert.dom(".chat-transcript-meta img[title='tada']").exists();
|
||||
|
||||
let rootElement = document.querySelector(
|
||||
".ProseMirror .chat-transcript:nth-of-type(1)"
|
||||
);
|
||||
assert
|
||||
.dom(".chat-transcript-messages", rootElement)
|
||||
.hasHtml("<p>test</p>");
|
||||
assert
|
||||
.dom(".chat-transcript-user .chat-transcript-username", rootElement)
|
||||
.hasText("martin");
|
||||
|
||||
rootElement = document.querySelector(
|
||||
".ProseMirror .chat-transcript:nth-of-type(2)"
|
||||
);
|
||||
assert
|
||||
.dom(".chat-transcript-messages", rootElement)
|
||||
.hasHtml("<p>haha <strong>ok</strong> <em>cool</em></p>");
|
||||
assert
|
||||
.dom(".chat-transcript-user .chat-transcript-username", rootElement)
|
||||
.hasText("hunter");
|
||||
|
||||
assert.strictEqual(value, multiMessagesMultiUserMarkdown);
|
||||
});
|
||||
|
||||
test("messages in a thread", async function (assert) {
|
||||
const threadMessagesMarkdown = `[chat quote="martin;29854;2025-03-20T07:12:57Z" channel="design gems :tada:" channelId="95" multiQuote="true" threadId="124" threadTitle="Some cool thread title"]
|
||||
thread op message
|
||||
|
||||
[chat quote="martin;29857;2025-03-24T07:08:01Z"]
|
||||
thread other message
|
||||
[/chat]
|
||||
|
||||
[/chat]
|
||||
`;
|
||||
const [{ value }] = await setupRichEditor(assert, threadMessagesMarkdown);
|
||||
assert
|
||||
.dom(".chat-transcript-meta")
|
||||
.hasText("Originally sent in design gems");
|
||||
assert.dom(".chat-transcript-meta img[title='tada']").exists();
|
||||
assert
|
||||
.dom(".chat-transcript details summary .chat-transcript-thread")
|
||||
.exists();
|
||||
|
||||
let rootElement = document.querySelector(
|
||||
".ProseMirror .chat-transcript details summary .chat-transcript-thread"
|
||||
);
|
||||
assert
|
||||
.dom(
|
||||
".chat-transcript-thread-header svg.d-icon-discourse-threads",
|
||||
rootElement
|
||||
)
|
||||
.exists();
|
||||
assert
|
||||
.dom(
|
||||
".chat-transcript-thread-header .chat-transcript-thread-header__title",
|
||||
rootElement
|
||||
)
|
||||
.hasText("Some cool thread title");
|
||||
|
||||
assert
|
||||
.dom(".chat-transcript-messages", rootElement)
|
||||
.hasHtml(
|
||||
"<p>thread op message</p>",
|
||||
"the thread op message is inside the summary element"
|
||||
);
|
||||
assert
|
||||
.dom(".chat-transcript-user .chat-transcript-username", rootElement)
|
||||
.hasText("martin");
|
||||
|
||||
rootElement = document.querySelector(
|
||||
".ProseMirror .chat-transcript details .chat-transcript"
|
||||
);
|
||||
assert
|
||||
.dom(".chat-transcript-messages", rootElement)
|
||||
.hasHtml(
|
||||
"<p>thread other message</p>",
|
||||
"the other thread messages are inside the details element"
|
||||
);
|
||||
assert
|
||||
.dom(".chat-transcript-user .chat-transcript-username", rootElement)
|
||||
.hasText("martin");
|
||||
|
||||
assert.strictEqual(value, threadMessagesMarkdown);
|
||||
});
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user