feat: label feature nodes and marks in the history dropdowns

Replace the hardcoded node if/else chain in describeTransaction with a
NODE_LABELS map covering image, text box, formula, page break, TOC,
fields, cross-reference, index/bibliography entries, ruby and columns;
foot-/endnotes labelled via their kind attr. MARK_LABELS gains link,
comment, bookmark, character style and tracked change.
This commit is contained in:
Steffen Becker committed 2026-08-15 23:31:22 +02:00
1 parent bb15572b57
commit 3cec6990ef
2 files changed
+47 -4

No files matched your search

+33 -4
View File
@@ -88,6 +88,35 @@ const MARK_LABELS: Record<string, string> = {
highlight: 'Highlight',
subscript: 'Subscript',
superscript: 'Superscript',
link: 'Link',
comment: 'Comment',
bookmark: 'Bookmark',
charStyle: 'Character style',
trackChanges: 'Tracked change',
};
// First inserted node with a label wins; nodesBetween visits parents before children,
// so the outermost structure (a table over its paragraphs) names the entry.
const NODE_LABELS: Record<string, string> = {
table: 'Insert table',
bulletList: 'Bullet list',
orderedList: 'Numbered list',
image: 'Insert image',
textBox: 'Text box',
formula: 'Formula',
pageBreak: 'Page break',
horizontalRule: 'Horizontal rule',
tableOfContents: 'Table of contents',
dateTimeField: 'Date field',
pageNumber: 'Page number',
pageCount: 'Page count',
chapterField: 'Chapter field',
sequenceField: 'Caption field',
crossRef: 'Cross-reference',
indexEntry: 'Index entry',
bibliographyEntry: 'Bibliography entry',
ruby: 'Ruby text',
columns: 'Columns',
};
function describeMark(name: string, attrs: Record<string, unknown> | undefined): string {
@@ -120,10 +149,10 @@ function describeTransaction(tr: Transaction): string {
content.nodesBetween(0, content.size, (node) => {
const name = node.type.name;
if (!structural) {
if (name === 'table') structural = 'Insert table';
else if (name === 'bulletList') structural = 'Bullet list';
else if (name === 'orderedList') structural = 'Numbered list';
else if (name === 'heading') structural = `Heading ${node.attrs.level ?? ''}`.trim();
if (name === 'heading') structural = `Heading ${node.attrs.level ?? ''}`.trim();
else if (name === 'noteRef' || name === 'note')
structural = node.attrs.kind === 'endnote' ? 'Endnote' : 'Footnote';
else if (NODE_LABELS[name]) structural = NODE_LABELS[name];
else if (step instanceof ReplaceAroundStep && name === 'paragraph') structural = 'Paragraph';
}
if (node.isText) {
+14
View File
@@ -45,6 +45,20 @@ describe('history log', () => {
editor.destroy();
});
it('labels feature nodes and marks', () => {
const editor = makeEditor();
editor.commands.insertContent('Hello');
endGroup(editor);
editor.commands.insertContentAt(editor.state.doc.content.size, { type: 'image', attrs: { src: 'data:,' } });
endGroup(editor);
editor.commands.insertNote('footnote');
endGroup(editor);
editor.chain().setTextSelection({ from: 1, to: 6 }).setLink({ href: 'https://example.com' }).run();
expect(undoLabels()).toEqual(['Typing: “Hello”', 'Insert image', 'Footnote', 'Link']);
editor.destroy();
});
it('moves entries across on undo/redo and keeps counts at the plugin depths', () => {
const editor = makeEditor();
editor.commands.insertContent('one');