FEATURE: adds support for event preview in rich editor (#708)
Adds support to parsing/serializing/displaying an event preview representation for the rich editor view. Co-authored-by: Renato Atilio <renato@discourse.org>
This commit is contained in:
parent
7674040f4f
commit
cbebb8da6f
|
@ -1,3 +1,4 @@
|
|||
< 3.5.0.beta2-dev: c1031224a552ea01958d153350c2d1254b323579
|
||||
< 3.5.0.beta1-dev: fdf3ad927744a9dbb826cc46e489cca8ad469044
|
||||
< 3.4.0.beta2-dev: 5f6942c1f3616d43eec18a71d76baa0e55e1340b
|
||||
< 3.4.0.beta1-dev: 908ad614bc412f831f929ca726a4bda0b9ccaab6
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { isTesting } from "discourse/lib/environment";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import I18n, { i18n } from "discourse-i18n";
|
||||
import DiscoursePostEvent from "discourse/plugins/discourse-calendar/discourse/components/discourse-post-event";
|
||||
import DiscoursePostEventEvent from "discourse/plugins/discourse-calendar/discourse/models/discourse-post-event-event";
|
||||
import guessDateFormat from "../lib/guess-best-date-format";
|
||||
|
||||
function _validEventPreview(eventContainer) {
|
||||
export function buildEventPreview(eventContainer) {
|
||||
eventContainer.innerHTML = "";
|
||||
eventContainer.classList.add("discourse-post-event-preview");
|
||||
|
||||
|
@ -34,13 +35,14 @@ function _validEventPreview(eventContainer) {
|
|||
);
|
||||
|
||||
const format = guessDateFormat(startsAt, endsAt);
|
||||
const guessedTz = isTesting() ? "UTC" : moment.tz.guess();
|
||||
|
||||
let datesString = `<span class='start'>${startsAt
|
||||
.tz(moment.tz.guess())
|
||||
.tz(guessedTz)
|
||||
.format(format)}</span>`;
|
||||
if (endsAt) {
|
||||
datesString += ` → <span class='end'>${endsAt
|
||||
.tz(moment.tz.guess())
|
||||
.tz(guessedTz)
|
||||
.format(format)}</span>`;
|
||||
}
|
||||
datesContainer.innerHTML = datesString;
|
||||
|
@ -67,7 +69,7 @@ function _decorateEventPreview(api, cooked) {
|
|||
if (index > 0) {
|
||||
_invalidEventPreview(eventContainer);
|
||||
} else {
|
||||
_validEventPreview(eventContainer);
|
||||
buildEventPreview(eventContainer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
import { camelize } from "@ember/string";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { buildEventPreview } from "../initializers/discourse-post-event-decorator";
|
||||
|
||||
const EVENT_ATTRIBUTES = {
|
||||
name: { default: null },
|
||||
start: { default: null },
|
||||
end: { default: null },
|
||||
reminders: { default: null },
|
||||
minimal: { default: null },
|
||||
closed: { default: null },
|
||||
status: { default: "public" },
|
||||
timezone: { default: "UTC" },
|
||||
allowedGroups: { default: null },
|
||||
};
|
||||
|
||||
/** @type {RichEditorExtension} */
|
||||
const extension = {
|
||||
nodeSpec: {
|
||||
event: {
|
||||
attrs: EVENT_ATTRIBUTES,
|
||||
group: "block",
|
||||
defining: true,
|
||||
isolating: true,
|
||||
draggable: true,
|
||||
parseDOM: [
|
||||
{
|
||||
tag: "div.discourse-post-event",
|
||||
getAttrs(dom) {
|
||||
return { ...dom.dataset };
|
||||
},
|
||||
},
|
||||
],
|
||||
toDOM(node) {
|
||||
const element = document.createElement("div");
|
||||
element.classList.add("discourse-post-event");
|
||||
for (const [key, value] of Object.entries(node.attrs)) {
|
||||
if (value !== null) {
|
||||
element.dataset[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
buildEventPreview(element);
|
||||
|
||||
return element;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
parse: {
|
||||
wrap_bbcode(state, token) {
|
||||
if (token.tag === "div") {
|
||||
if (token.nesting === -1 && state.top().type.name === "event") {
|
||||
state.closeNode();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
token.nesting === 1 &&
|
||||
token.attrGet("class") === "discourse-post-event"
|
||||
) {
|
||||
const attrs = Object.fromEntries(
|
||||
token.attrs
|
||||
.filter(([key]) => key.startsWith("data-"))
|
||||
.map(([key, value]) => [camelize(key.slice(5)), value])
|
||||
);
|
||||
|
||||
state.openNode(state.schema.nodes.event, attrs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
|
||||
serializeNode: {
|
||||
event(state, node) {
|
||||
let bbcode = "[event";
|
||||
|
||||
Object.entries(node.attrs).forEach(([key, value]) => {
|
||||
if (value !== null) {
|
||||
bbcode += ` ${key}="${value}"`;
|
||||
}
|
||||
});
|
||||
|
||||
bbcode += "]\n[/event]\n";
|
||||
|
||||
state.write(bbcode);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
initialize() {
|
||||
withPluginApi("2.1.1", (api) => {
|
||||
api.registerRichEditorExtension(extension);
|
||||
});
|
||||
},
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { testMarkdown } from "discourse/tests/helpers/rich-editor-helper";
|
||||
|
||||
module("Integration | Component | rich-editor-extension", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
const testCases = {
|
||||
"event alone": [
|
||||
[
|
||||
`[event start="2025-03-21 15:41" status="public" timezone="Europe/Paris" allowedGroups="trust_level_0"]\n[/event]\n`,
|
||||
`<div class="discourse-post-event discourse-post-event-preview ProseMirror-selectednode" data-start="2025-03-21 15:41" data-status="public" data-timezone="Europe/Paris" data-allowed-groups="trust_level_0" contenteditable="false" draggable="true"><div class="event-preview-status">Public</div><div class="event-preview-dates"><span class="start">March 21, 2025 2:41 PM</span></div></div>`,
|
||||
`[event start="2025-03-21 15:41" status="public" timezone="Europe/Paris" allowedGroups="trust_level_0"]\n[/event]\n`,
|
||||
],
|
||||
],
|
||||
"event with content around": [
|
||||
[
|
||||
`Hello world\n\n[event start="2025-03-21 15:41" status="public" timezone="Europe/Paris" allowedGroups="trust_level_0"]\n[/event]\nGoodbye world`,
|
||||
`<p>Hello world</p><div class="discourse-post-event discourse-post-event-preview" data-start="2025-03-21 15:41" data-status="public" data-timezone="Europe/Paris" data-allowed-groups="trust_level_0" contenteditable="false" draggable="true"><div class="event-preview-status">Public</div><div class="event-preview-dates"><span class="start">March 21, 2025 2:41 PM</span></div></div><p>Goodbye world</p>`,
|
||||
`Hello world\n\n[event start="2025-03-21 15:41" status="public" timezone="Europe/Paris" allowedGroups="trust_level_0"]\n[/event]\nGoodbye world`,
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
Object.entries(testCases).forEach(([name, tests]) => {
|
||||
tests.forEach(([markdown, expectedHtml, expectedMarkdown]) => {
|
||||
test(name, async function (assert) {
|
||||
this.siteSettings.rich_editor = true;
|
||||
|
||||
await testMarkdown(assert, markdown, expectedHtml, expectedMarkdown);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue