mirror of
https://github.com/discourse/discourse.git
synced 2025-05-26 04:11:26 +08:00
FEATURE: add local-dates (plugin) rich editor extension (#31714)
Continues the work done on https://github.com/discourse/discourse/pull/30815. Adds `local_date` and `local_date_range` nodes, parsers, and serializers.
This commit is contained in:
@ -13,6 +13,7 @@ import { i18n } from "discourse-i18n";
|
||||
import generateDateMarkup from "discourse/plugins/discourse-local-dates/lib/local-date-markup-generator";
|
||||
import LocalDatesCreateModal from "../discourse/components/modal/local-dates-create";
|
||||
import LocalDateBuilder from "../lib/local-date-builder";
|
||||
import richEditorExtension from "../lib/rich-editor-extension";
|
||||
|
||||
// Import applyLocalDates from discourse/lib/local-dates instead
|
||||
export function applyLocalDates(dates, siteSettings) {
|
||||
@ -142,6 +143,8 @@ function _partitionedRanges(element) {
|
||||
}
|
||||
|
||||
function initializeDiscourseLocalDates(api) {
|
||||
api.registerRichEditorExtension(richEditorExtension);
|
||||
|
||||
const modal = api.container.lookup("service:modal");
|
||||
const siteSettings = api.container.lookup("service:site-settings");
|
||||
const defaultTitle = i18n("discourse_local_dates.default_title", {
|
||||
|
@ -0,0 +1,195 @@
|
||||
/** @type {RichEditorExtension} */
|
||||
const extension = {
|
||||
// TODO(renato): the rendered date needs to be localized to better match the cooked content
|
||||
nodeSpec: {
|
||||
local_date: {
|
||||
attrs: { date: {}, time: {}, timezone: { default: null } },
|
||||
group: "inline",
|
||||
atom: true,
|
||||
inline: true,
|
||||
parseDOM: [
|
||||
{
|
||||
tag: "span.discourse-local-date[data-date]",
|
||||
getAttrs: (dom) => {
|
||||
return {
|
||||
date: dom.getAttribute("data-date"),
|
||||
time: dom.getAttribute("data-time"),
|
||||
timezone: dom.getAttribute("data-timezone"),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
toDOM: (node) => {
|
||||
const optionalTime = node.attrs.time ? ` ${node.attrs.time}` : "";
|
||||
return [
|
||||
"span",
|
||||
{
|
||||
class: "discourse-local-date cooked-date",
|
||||
"data-date": node.attrs.date,
|
||||
"data-time": node.attrs.time,
|
||||
"data-timezone": node.attrs.timezone,
|
||||
},
|
||||
`${node.attrs.date}${optionalTime}`,
|
||||
];
|
||||
},
|
||||
},
|
||||
local_date_range: {
|
||||
attrs: {
|
||||
fromDate: {},
|
||||
toDate: { default: null },
|
||||
fromTime: {},
|
||||
toTime: {},
|
||||
timezone: { default: null },
|
||||
},
|
||||
group: "inline",
|
||||
atom: true,
|
||||
inline: true,
|
||||
parseDOM: [
|
||||
{
|
||||
tag: "span.discourse-local-date-range",
|
||||
getAttrs: (dom) => {
|
||||
return {
|
||||
fromDate: dom.dataset.fromDate,
|
||||
toDate: dom.dataset.toDate,
|
||||
fromTime: dom.dataset.fromTime,
|
||||
toTime: dom.dataset.toTime,
|
||||
timezone: dom.dataset.timezone,
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
toDOM: (node) => {
|
||||
const fromTimeStr = node.attrs.fromTime
|
||||
? ` ${node.attrs.fromTime}`
|
||||
: "";
|
||||
const toTimeStr = node.attrs.toTime ? ` ${node.attrs.toTime}` : "";
|
||||
return [
|
||||
"span",
|
||||
{ class: "discourse-local-date-range" },
|
||||
[
|
||||
"span",
|
||||
{
|
||||
class: "discourse-local-date cooked-date",
|
||||
"data-range": "from",
|
||||
"data-date": node.attrs.fromDate,
|
||||
"data-time": node.attrs.fromTime,
|
||||
"data-timezone": node.attrs.timezone,
|
||||
},
|
||||
`${node.attrs.fromDate}${fromTimeStr}`,
|
||||
],
|
||||
" → ",
|
||||
[
|
||||
"span",
|
||||
{
|
||||
class: "discourse-local-date cooked-date",
|
||||
"data-range": "to",
|
||||
"data-date": node.attrs.toDate,
|
||||
"data-time": node.attrs.toTime,
|
||||
"data-timezone": node.attrs.timezone,
|
||||
},
|
||||
`${node.attrs.toDate}${toTimeStr}`,
|
||||
],
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
parse: {
|
||||
span_open(state, token, tokens, i) {
|
||||
if (token.attrGet("class") !== "discourse-local-date") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.attrGet("data-range") === "from") {
|
||||
state.openNode(state.schema.nodes.local_date_range, {
|
||||
fromDate: token.attrGet("data-date"),
|
||||
fromTime: token.attrGet("data-time"),
|
||||
timezone: token.attrGet("data-timezone"),
|
||||
});
|
||||
state.__localDateRange = true;
|
||||
// we depend on the token data being strictly:
|
||||
// [span_open, text, span_close, text, span_open, text, span_close]
|
||||
// removing the text occurrences
|
||||
tokens.splice(i + 1, 1);
|
||||
tokens.splice(i + 2, 1);
|
||||
tokens.splice(i + 3, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token.attrGet("data-range") === "to") {
|
||||
// In our markdown-it tokens, a range is a series of span_open/span_close/span_open/span_close
|
||||
// We skip opening a node for `to` and set it on the top node
|
||||
state.top().attrs.toDate = token.attrGet("data-date");
|
||||
state.top().attrs.toTime = token.attrGet("data-time");
|
||||
delete state.__localDateRange;
|
||||
return true;
|
||||
}
|
||||
|
||||
state.openNode(state.schema.nodes.local_date, {
|
||||
date: token.attrGet("data-date"),
|
||||
time: token.attrGet("data-time"),
|
||||
timezone: token.attrGet("data-timezone"),
|
||||
});
|
||||
// removing the text occurrence
|
||||
tokens.splice(i + 1, 1);
|
||||
return true;
|
||||
},
|
||||
span_close(state) {
|
||||
if (["local_date", "local_date_range"].includes(state.top().type.name)) {
|
||||
if (!state.__localDateRange) {
|
||||
state.closeNode();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
},
|
||||
serializeNode({ utils: { isBoundary } }) {
|
||||
return {
|
||||
local_date(state, node, parent, index) {
|
||||
if (!isBoundary(state.out, state.out.length - 1)) {
|
||||
state.write(" ");
|
||||
}
|
||||
|
||||
const optionalTime = node.attrs.time ? ` time=${node.attrs.time}` : "";
|
||||
const optionalTimezone = node.attrs.timezone
|
||||
? ` timezone="${node.attrs.timezone}"`
|
||||
: "";
|
||||
|
||||
state.write(
|
||||
`[date=${node.attrs.date}${optionalTime}${optionalTimezone}]`
|
||||
);
|
||||
|
||||
const nextSibling =
|
||||
parent.childCount > index + 1 ? parent.child(index + 1) : null;
|
||||
if (nextSibling?.isText && !isBoundary(nextSibling.text, 0)) {
|
||||
state.write(" ");
|
||||
}
|
||||
},
|
||||
local_date_range(state, node, parent, index) {
|
||||
if (!isBoundary(state.out, state.out.length - 1)) {
|
||||
state.write(" ");
|
||||
}
|
||||
|
||||
const optionalTimezone = node.attrs.timezone
|
||||
? ` timezone="${node.attrs.timezone}"`
|
||||
: "";
|
||||
|
||||
const from =
|
||||
node.attrs.fromDate +
|
||||
(node.attrs.fromTime ? `T${node.attrs.fromTime}` : "");
|
||||
const to =
|
||||
node.attrs.toDate +
|
||||
(node.attrs.toTime ? `T${node.attrs.toTime}` : "");
|
||||
state.write(`[date-range from=${from} to=${to}${optionalTimezone}]`);
|
||||
|
||||
const nextSibling =
|
||||
parent.childCount > index + 1 ? parent.child(index + 1) : null;
|
||||
if (nextSibling?.isText && !isBoundary(nextSibling.text, 0)) {
|
||||
state.write(" ");
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default extension;
|
Reference in New Issue
Block a user