mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-18 10:04:00 +08:00
When editing posts in the rich text editor, custom date formats and
advanced options (recurring, timezones, countdown, displayedTimezone)
were silently dropped. Users who carefully configured dates via the
Advanced Mode dialog would lose those settings as soon as they touched
the post in rich text mode.
This happened because the rich-editor-extension only tracked the basic
date/time/timezone attributes, ignoring everything else. Similarly,
copy-pasting date ranges would crash with "No value supplied for
attribute fromDate" because the parseDOM logic was reading attributes
from the wrapper span instead of the inner date spans.
Changes:
- Extend `formatLocalDate` to accept all LocalDateBuilder options so
dates render correctly with custom formats in the editor
- Update rich-editor-extension to parse, store, and serialize all
date attributes for both single dates and date ranges
- Fix date range copy/paste by querying the inner spans via
`dom.querySelector('[data-range="from"]')` instead of reading from
the wrapper element
- Add shared `serializeBBCodeAttr` and `buildBBCodeAttrs` helpers to
`discourse/lib/text.js` that only quote values when necessary
(whitespace or `]` characters)
- Refactor local-dates, poll, calendar, and chat plugins to use the
shared BBCode serialization helpers, eliminating duplicated logic
Internal ref - t/165719
**MARKDOWN (reference)**
<img width="1477" height="548" alt="2025-12-18 @ 18 20 25"
src="https://github.com/user-attachments/assets/f7a20835-ec09-4b41-aff7-6894451fae72"
/>
**RTE (before)**
<img width="759" height="549" alt="2025-12-18 @ 18 22 36"
src="https://github.com/user-attachments/assets/ea914a84-d087-43b6-9007-5807523a6c7c"
/>
**RTE (after)**
<img width="762" height="547" alt="2025-12-18 @ 18 20 35"
src="https://github.com/user-attachments/assets/79c6c70f-80b2-44ff-a1a5-f2564c53b3da"
/>
176 lines
6.7 KiB
Text
176 lines
6.7 KiB
Text
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);
|
|
});
|
|
}
|
|
);
|