mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
Make the ISO date the single source of truth for date mentions
Date mentions no longer persist a human-readable label in the ProseMirror data. Instead the displayed text, plaintext, DOM text and markdown link text are all derived from the ISO modelId, so the saved data can never drift or go stale. parseDOM/parseMarkdown no longer capture rendered text as a label for dates, and the mention menu/picker stop writing one.
This commit is contained in:
@@ -47,7 +47,9 @@ interface MentionItem extends MenuItem {
|
||||
id: string;
|
||||
type: MentionType;
|
||||
modelId: string;
|
||||
label: string;
|
||||
// Date mentions intentionally omit a label — their text is derived from
|
||||
// the ISO `modelId` so nothing human-readable is persisted.
|
||||
label?: string;
|
||||
actorId?: string;
|
||||
};
|
||||
}
|
||||
@@ -108,7 +110,6 @@ function MentionMenu({ search, isActive, ...rest }: Props) {
|
||||
type: MentionType.Date,
|
||||
modelId: parsedISODate,
|
||||
actorId,
|
||||
label: dateToReadable(parsedISODate, userLocale),
|
||||
},
|
||||
} as MentionItem,
|
||||
]
|
||||
|
||||
@@ -16,7 +16,6 @@ import styled from "styled-components";
|
||||
import { depths, s } from "../../styles";
|
||||
import {
|
||||
dateLocale,
|
||||
dateToReadable,
|
||||
dateToRelativeReadable,
|
||||
parseISODate,
|
||||
toISODate,
|
||||
@@ -523,7 +522,7 @@ export const MentionPullRequest = observer((props: IssuePrProps) => {
|
||||
});
|
||||
|
||||
type DateProps = ComponentProps & {
|
||||
onChangeDate: (modelId: string, label: string) => void;
|
||||
onChangeDate: (modelId: string) => void;
|
||||
};
|
||||
|
||||
export const MentionDate = observer(function MentionDate_(props: DateProps) {
|
||||
@@ -541,10 +540,9 @@ export const MentionDate = observer(function MentionDate_(props: DateProps) {
|
||||
const handleSelect = React.useCallback(
|
||||
(date: Date) => {
|
||||
setOpen(false);
|
||||
const newIso = toISODate(date);
|
||||
onChangeDate(newIso, dateToReadable(newIso, language));
|
||||
onChangeDate(toISODate(date));
|
||||
},
|
||||
[onChangeDate, language]
|
||||
[onChangeDate]
|
||||
);
|
||||
|
||||
const trigger = (
|
||||
|
||||
@@ -14,6 +14,7 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import env from "../../env";
|
||||
import type { UnfurlResponse } from "../../types";
|
||||
import { MentionType, UnfurlResourceType } from "../../types";
|
||||
import { dateToReadable } from "../../utils/date";
|
||||
import {
|
||||
MentionCollection,
|
||||
MentionDocument,
|
||||
@@ -40,17 +41,25 @@ export default class Mention extends Node {
|
||||
}
|
||||
|
||||
get schema(): NodeSpec {
|
||||
const toPlainText = (node: ProsemirrorNode) =>
|
||||
node.attrs.type === MentionType.User
|
||||
// Date mentions derive their text from the ISO `modelId`, which is the
|
||||
// single source of truth — no human-readable label is persisted for them.
|
||||
const toPlainText = (node: ProsemirrorNode) => {
|
||||
if (node.attrs.type === MentionType.Date) {
|
||||
return dateToReadable(node.attrs.modelId);
|
||||
}
|
||||
return node.attrs.type === MentionType.User
|
||||
? `@${node.attrs.label}`
|
||||
: node.attrs.label;
|
||||
};
|
||||
|
||||
return {
|
||||
attrs: {
|
||||
type: {
|
||||
default: MentionType.User,
|
||||
},
|
||||
label: {},
|
||||
label: {
|
||||
default: undefined,
|
||||
},
|
||||
modelId: {},
|
||||
actorId: {
|
||||
default: undefined,
|
||||
@@ -85,7 +94,9 @@ export default class Mention extends Node {
|
||||
type,
|
||||
modelId,
|
||||
actorId: dom.dataset.actorid,
|
||||
label: dom.innerText,
|
||||
// Date mentions derive their text from `modelId`; never capture
|
||||
// the rendered text as a persisted label.
|
||||
label: type === MentionType.Date ? undefined : dom.innerText,
|
||||
id: dom.id,
|
||||
href: dom.getAttribute("href"),
|
||||
unfurl: dom.dataset.unfurl
|
||||
@@ -329,7 +340,10 @@ export default class Mention extends Node {
|
||||
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
|
||||
const mType = node.attrs.type;
|
||||
const mId = node.attrs.modelId;
|
||||
const label = node.attrs.label;
|
||||
// Date mentions have no stored label; the readable text is derived from
|
||||
// the ISO `modelId` so it can never drift from the source of truth.
|
||||
const label =
|
||||
mType === MentionType.Date ? dateToReadable(mId) : node.attrs.label;
|
||||
const id = node.attrs.id;
|
||||
|
||||
// Use regular links for document and collection mentions
|
||||
@@ -350,26 +364,28 @@ export default class Mention extends Node {
|
||||
id: tok.attrGet("id"),
|
||||
type: tok.attrGet("type"),
|
||||
modelId: tok.attrGet("modelId"),
|
||||
label: tok.content,
|
||||
// Date mentions derive their text from `modelId`; the link text is not
|
||||
// persisted as a label.
|
||||
label:
|
||||
tok.attrGet("type") === MentionType.Date ? undefined : tok.content,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
handleChangeDate =
|
||||
({ node, getPos }: { node: ProsemirrorNode; getPos: () => number }) =>
|
||||
(modelId: string, label: string) => {
|
||||
(modelId: string) => {
|
||||
const { view } = this.editor;
|
||||
const { tr } = view.state;
|
||||
const pos = getPos();
|
||||
|
||||
if (node.attrs.modelId === modelId && node.attrs.label === label) {
|
||||
if (node.attrs.modelId === modelId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const transaction = tr.setNodeMarkup(pos, undefined, {
|
||||
...node.attrs,
|
||||
modelId,
|
||||
label,
|
||||
});
|
||||
view.dispatch(transaction);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user