@eightyfourthousand/lib-editing 2026.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +12 -0
- package/.eslintrc.json +18 -0
- package/README.md +7 -0
- package/jest.config.ts +10 -0
- package/package.json +35 -0
- package/postcss.config.mjs +9 -0
- package/project.json +29 -0
- package/src/fixtures/basic/json.ts +396 -0
- package/src/fixtures/toh251/json.ts +4913 -0
- package/src/fixtures/toh251/passages.ts +2814 -0
- package/src/fixtures/types.ts +8 -0
- package/src/index.ts +4 -0
- package/src/lib/block.ts +226 -0
- package/src/lib/components/editor/BlockEditor.tsx +38 -0
- package/src/lib/components/editor/EditorBackMatterPage.tsx +95 -0
- package/src/lib/components/editor/EditorBodyPage.tsx +87 -0
- package/src/lib/components/editor/EditorHeader.tsx +22 -0
- package/src/lib/components/editor/EditorLayout.tsx +62 -0
- package/src/lib/components/editor/EditorLeftPanelPage.tsx +27 -0
- package/src/lib/components/editor/EditorProvider.tsx +399 -0
- package/src/lib/components/editor/PaginationProvider.tsx +472 -0
- package/src/lib/components/editor/TitlesBuilder.tsx +40 -0
- package/src/lib/components/editor/TranslationBuilder.tsx +171 -0
- package/src/lib/components/editor/TranslationEditor.tsx +32 -0
- package/src/lib/components/editor/extensions/Abbreviation/Abbreviation.ts +133 -0
- package/src/lib/components/editor/extensions/Audio/Audio.ts +69 -0
- package/src/lib/components/editor/extensions/Bold.ts +43 -0
- package/src/lib/components/editor/extensions/Document.ts +8 -0
- package/src/lib/components/editor/extensions/DragHandle/DragHandle.ts +429 -0
- package/src/lib/components/editor/extensions/EndNoteLink/EndNoteLink.tsx +39 -0
- package/src/lib/components/editor/extensions/EndNoteLink/EndNoteLinkHoverContent.tsx +139 -0
- package/src/lib/components/editor/extensions/EndNoteLink/EndNoteLinkMark.ts +236 -0
- package/src/lib/components/editor/extensions/EndNoteLink/endnote-utils.ts +412 -0
- package/src/lib/components/editor/extensions/GlobalConfig.ts +52 -0
- package/src/lib/components/editor/extensions/GlossaryInstance/GlossaryInput.tsx +54 -0
- package/src/lib/components/editor/extensions/GlossaryInstance/GlossaryInstance.tsx +129 -0
- package/src/lib/components/editor/extensions/GlossaryInstance/GlossaryInstanceNode.ts +148 -0
- package/src/lib/components/editor/extensions/Heading/Heading.ts +71 -0
- package/src/lib/components/editor/extensions/HoverInputField.tsx +54 -0
- package/src/lib/components/editor/extensions/Image.ts +18 -0
- package/src/lib/components/editor/extensions/Indent.ts +103 -0
- package/src/lib/components/editor/extensions/InternalLink/InternalLink.ts +173 -0
- package/src/lib/components/editor/extensions/InternalLink/InternalLinkHoverContent.tsx +137 -0
- package/src/lib/components/editor/extensions/InternalLink/InternalLinkInput.tsx +71 -0
- package/src/lib/components/editor/extensions/InternalLink/index.ts +1 -0
- package/src/lib/components/editor/extensions/Italic.ts +50 -0
- package/src/lib/components/editor/extensions/LeadingSpace.ts +106 -0
- package/src/lib/components/editor/extensions/Line/LineNode.ts +41 -0
- package/src/lib/components/editor/extensions/LineGroup/LineGroupNode.ts +124 -0
- package/src/lib/components/editor/extensions/Link/Link.ts +65 -0
- package/src/lib/components/editor/extensions/Link/LinkHoverContent.tsx +124 -0
- package/src/lib/components/editor/extensions/Link/index.ts +1 -0
- package/src/lib/components/editor/extensions/List.ts +74 -0
- package/src/lib/components/editor/extensions/Mantra/Mantra.ts +88 -0
- package/src/lib/components/editor/extensions/Mention/Mention.ts +184 -0
- package/src/lib/components/editor/extensions/Mention/MentionHoverContent.tsx +158 -0
- package/src/lib/components/editor/extensions/NodeWrapper.tsx +57 -0
- package/src/lib/components/editor/extensions/Paragraph/Paragraph.ts +25 -0
- package/src/lib/components/editor/extensions/ParagraphIndent.ts +87 -0
- package/src/lib/components/editor/extensions/Passage/EditLabel.tsx +57 -0
- package/src/lib/components/editor/extensions/Passage/EditorOptions.tsx +29 -0
- package/src/lib/components/editor/extensions/Passage/Passage.tsx +238 -0
- package/src/lib/components/editor/extensions/Passage/PassageNode.ts +223 -0
- package/src/lib/components/editor/extensions/Passage/ReaderOptions.tsx +55 -0
- package/src/lib/components/editor/extensions/Passage/ShowAnnotations.tsx +92 -0
- package/src/lib/components/editor/extensions/Passage/index.ts +1 -0
- package/src/lib/components/editor/extensions/Passage/label.spec.ts +118 -0
- package/src/lib/components/editor/extensions/Passage/label.ts +39 -0
- package/src/lib/components/editor/extensions/Placeholder.ts +9 -0
- package/src/lib/components/editor/extensions/SlashCommand/SlashCommand.ts +65 -0
- package/src/lib/components/editor/extensions/SlashCommand/SuggestionList.tsx +109 -0
- package/src/lib/components/editor/extensions/SlashCommand/Suggestions.ts +185 -0
- package/src/lib/components/editor/extensions/SmallCaps.ts +110 -0
- package/src/lib/components/editor/extensions/StarterKit.ts +36 -0
- package/src/lib/components/editor/extensions/Subscript.ts +43 -0
- package/src/lib/components/editor/extensions/Superscript.ts +43 -0
- package/src/lib/components/editor/extensions/Table.ts +32 -0
- package/src/lib/components/editor/extensions/TextAlign.ts +5 -0
- package/src/lib/components/editor/extensions/TitleMetadata.ts +40 -0
- package/src/lib/components/editor/extensions/TitleNode.ts +133 -0
- package/src/lib/components/editor/extensions/TitlesNode.ts +102 -0
- package/src/lib/components/editor/extensions/Trailer.ts +57 -0
- package/src/lib/components/editor/extensions/TranslationDocument.ts +7 -0
- package/src/lib/components/editor/extensions/TranslationMetadata.ts +58 -0
- package/src/lib/components/editor/extensions/Underline.ts +43 -0
- package/src/lib/components/editor/hooks/index.ts +4 -0
- package/src/lib/components/editor/hooks/useBlockEditor.ts +53 -0
- package/src/lib/components/editor/hooks/useDefaultExtensions.ts +39 -0
- package/src/lib/components/editor/hooks/useDirtyStore.ts +33 -0
- package/src/lib/components/editor/hooks/useTranslationExtensions.ts +148 -0
- package/src/lib/components/editor/index.ts +10 -0
- package/src/lib/components/editor/menus/EmptyBubbleMenu.tsx +42 -0
- package/src/lib/components/editor/menus/MainBubbleMenu.tsx +51 -0
- package/src/lib/components/editor/menus/TranslationBubbleMenu.tsx +57 -0
- package/src/lib/components/editor/menus/index.ts +3 -0
- package/src/lib/components/editor/menus/selectors/EndNoteSelector.tsx +388 -0
- package/src/lib/components/editor/menus/selectors/GlossarySelector.tsx +63 -0
- package/src/lib/components/editor/menus/selectors/LinkSelector.tsx +68 -0
- package/src/lib/components/editor/menus/selectors/MantraSelector.tsx +119 -0
- package/src/lib/components/editor/menus/selectors/NodeSelector.tsx +144 -0
- package/src/lib/components/editor/menus/selectors/ParagraphButtons.tsx +68 -0
- package/src/lib/components/editor/menus/selectors/SelectorInputField.tsx +68 -0
- package/src/lib/components/editor/menus/selectors/TextAlignSelector.tsx +89 -0
- package/src/lib/components/editor/menus/selectors/TextButtons.tsx +89 -0
- package/src/lib/components/editor/menus/selectors/TranslationNodeSelector.tsx +143 -0
- package/src/lib/components/editor/menus/selectors/TranslationTextButtons.tsx +125 -0
- package/src/lib/components/editor/menus/selectors/index.ts +5 -0
- package/src/lib/components/editor/save-filter.spec.ts +94 -0
- package/src/lib/components/editor/save-filter.ts +27 -0
- package/src/lib/components/editor/util.ts +304 -0
- package/src/lib/components/index.ts +3 -0
- package/src/lib/components/reader/ReaderBackMatterPage.tsx +62 -0
- package/src/lib/components/reader/ReaderBackMatterPanel.tsx +53 -0
- package/src/lib/components/reader/ReaderBodyPage.tsx +46 -0
- package/src/lib/components/reader/ReaderBodyPanel.tsx +68 -0
- package/src/lib/components/reader/ReaderLayout.tsx +39 -0
- package/src/lib/components/reader/ReaderLeftPanel.tsx +8 -0
- package/src/lib/components/reader/ReaderLeftPanelPage.tsx +31 -0
- package/src/lib/components/reader/TranslationReader.tsx +28 -0
- package/src/lib/components/reader/index.ts +2 -0
- package/src/lib/components/reader/ssr.ts +3 -0
- package/src/lib/components/shared/AiSummarizerPage.tsx +12 -0
- package/src/lib/components/shared/BackMatterPanel.tsx +143 -0
- package/src/lib/components/shared/BodyPanel.tsx +214 -0
- package/src/lib/components/shared/HoverCardProvider.tsx +407 -0
- package/src/lib/components/shared/Imprint.tsx +24 -0
- package/src/lib/components/shared/LabeledElement.tsx +133 -0
- package/src/lib/components/shared/LeftPanel.tsx +65 -0
- package/src/lib/components/shared/NavigationContext.ts +64 -0
- package/src/lib/components/shared/NavigationProvider.tsx +368 -0
- package/src/lib/components/shared/OpenGraphImage.tsx +75 -0
- package/src/lib/components/shared/PassageSkeleton.tsx +10 -0
- package/src/lib/components/shared/RestrictionWarning.tsx +177 -0
- package/src/lib/components/shared/SourceReader.tsx +83 -0
- package/src/lib/components/shared/SuggestRevisionForm.tsx +99 -0
- package/src/lib/components/shared/TableOfContents.tsx +280 -0
- package/src/lib/components/shared/ThreeColumnRenderer.tsx +54 -0
- package/src/lib/components/shared/TranslationHeader.tsx +86 -0
- package/src/lib/components/shared/TranslationHoverCard.tsx +84 -0
- package/src/lib/components/shared/TranslationSkeleton.tsx +16 -0
- package/src/lib/components/shared/TranslationTable.tsx +155 -0
- package/src/lib/components/shared/bibliography/BibliographyBody.tsx +28 -0
- package/src/lib/components/shared/bibliography/BibliographyList.tsx +63 -0
- package/src/lib/components/shared/bibliography/index.ts +1 -0
- package/src/lib/components/shared/generate-metadata.ts +44 -0
- package/src/lib/components/shared/glossary/GlossaryInstanceBody.tsx +144 -0
- package/src/lib/components/shared/glossary/GlossaryPaginationProvider.tsx +317 -0
- package/src/lib/components/shared/glossary/GlossarySkeleton.tsx +19 -0
- package/src/lib/components/shared/glossary/GlossaryTermList.tsx +58 -0
- package/src/lib/components/shared/glossary/index.ts +3 -0
- package/src/lib/components/shared/hooks/useGlossaryInstanceListener.tsx +42 -0
- package/src/lib/components/shared/hooks/useScrollInTab.tsx +43 -0
- package/src/lib/components/shared/hooks/useScrollPositionRestore.ts +274 -0
- package/src/lib/components/shared/hooks/useTohToggle.tsx +52 -0
- package/src/lib/components/shared/index.ts +11 -0
- package/src/lib/components/shared/ssr.ts +2 -0
- package/src/lib/components/shared/titles/FramedCard.tsx +132 -0
- package/src/lib/components/shared/titles/LongTitle.tsx +20 -0
- package/src/lib/components/shared/titles/LongTitles.tsx +28 -0
- package/src/lib/components/shared/titles/Title.tsx +54 -0
- package/src/lib/components/shared/titles/TitleDetails.tsx +47 -0
- package/src/lib/components/shared/titles/TitleForm.tsx +37 -0
- package/src/lib/components/shared/titles/Titles.tsx +114 -0
- package/src/lib/components/shared/titles/TitlesCard.tsx +113 -0
- package/src/lib/components/shared/titles/index.ts +8 -0
- package/src/lib/components/shared/types.ts +79 -0
- package/src/lib/components/ssr.ts +2 -0
- package/src/lib/exporters/abbreviation.spec.ts +31 -0
- package/src/lib/exporters/abbreviation.ts +22 -0
- package/src/lib/exporters/annotation.ts +193 -0
- package/src/lib/exporters/audio.spec.ts +77 -0
- package/src/lib/exporters/audio.ts +27 -0
- package/src/lib/exporters/blockquote.spec.ts +48 -0
- package/src/lib/exporters/blockquote.ts +24 -0
- package/src/lib/exporters/code.spec.ts +93 -0
- package/src/lib/exporters/code.ts +26 -0
- package/src/lib/exporters/end-note-link.spec.ts +104 -0
- package/src/lib/exporters/end-note-link.ts +35 -0
- package/src/lib/exporters/export.ts +12 -0
- package/src/lib/exporters/glossary-instance.spec.ts +85 -0
- package/src/lib/exporters/glossary-instance.ts +31 -0
- package/src/lib/exporters/has-abbreviation.spec.ts +31 -0
- package/src/lib/exporters/has-abbreviation.ts +21 -0
- package/src/lib/exporters/heading.spec.ts +80 -0
- package/src/lib/exporters/heading.ts +28 -0
- package/src/lib/exporters/image.spec.ts +48 -0
- package/src/lib/exporters/image.ts +25 -0
- package/src/lib/exporters/indent.spec.ts +58 -0
- package/src/lib/exporters/indent.ts +18 -0
- package/src/lib/exporters/index.ts +1 -0
- package/src/lib/exporters/internal-link.spec.ts +90 -0
- package/src/lib/exporters/internal-link.ts +35 -0
- package/src/lib/exporters/italic.spec.ts +84 -0
- package/src/lib/exporters/italic.ts +55 -0
- package/src/lib/exporters/leading-space.spec.ts +28 -0
- package/src/lib/exporters/leading-space.ts +16 -0
- package/src/lib/exporters/line-group.spec.ts +48 -0
- package/src/lib/exporters/line-group.ts +24 -0
- package/src/lib/exporters/line.spec.ts +48 -0
- package/src/lib/exporters/line.ts +24 -0
- package/src/lib/exporters/link.spec.ts +123 -0
- package/src/lib/exporters/link.ts +67 -0
- package/src/lib/exporters/list-item.spec.ts +48 -0
- package/src/lib/exporters/list-item.ts +24 -0
- package/src/lib/exporters/list.spec.ts +82 -0
- package/src/lib/exporters/list.ts +31 -0
- package/src/lib/exporters/mantra.spec.ts +51 -0
- package/src/lib/exporters/mantra.ts +25 -0
- package/src/lib/exporters/mention.ts +41 -0
- package/src/lib/exporters/paragraph.spec.ts +173 -0
- package/src/lib/exporters/paragraph.ts +32 -0
- package/src/lib/exporters/quote.spec.ts +56 -0
- package/src/lib/exporters/quote.ts +25 -0
- package/src/lib/exporters/span.spec.ts +118 -0
- package/src/lib/exporters/span.ts +44 -0
- package/src/lib/exporters/table-body-data.spec.ts +48 -0
- package/src/lib/exporters/table-body-data.ts +24 -0
- package/src/lib/exporters/table-body-header.spec.ts +48 -0
- package/src/lib/exporters/table-body-header.ts +24 -0
- package/src/lib/exporters/table-body-row.spec.ts +48 -0
- package/src/lib/exporters/table-body-row.ts +24 -0
- package/src/lib/exporters/table.spec.ts +48 -0
- package/src/lib/exporters/table.ts +24 -0
- package/src/lib/exporters/trailer.spec.ts +48 -0
- package/src/lib/exporters/trailer.ts +24 -0
- package/src/lib/exporters/util.ts +62 -0
- package/src/lib/passage.ts +182 -0
- package/src/lib/titles.ts +80 -0
- package/src/lib/transformers/abbreviation.spec.ts +87 -0
- package/src/lib/transformers/abbreviation.ts +30 -0
- package/src/lib/transformers/annotate.ts +146 -0
- package/src/lib/transformers/audio.spec.ts +55 -0
- package/src/lib/transformers/audio.ts +29 -0
- package/src/lib/transformers/blockquote.spec.ts +48 -0
- package/src/lib/transformers/blockquote.ts +41 -0
- package/src/lib/transformers/code.spec.ts +52 -0
- package/src/lib/transformers/code.ts +22 -0
- package/src/lib/transformers/deprecated.ts +7 -0
- package/src/lib/transformers/end-note-link.spec.ts +56 -0
- package/src/lib/transformers/end-note-link.ts +76 -0
- package/src/lib/transformers/glossary-instance.spec.ts +55 -0
- package/src/lib/transformers/glossary-instance.ts +40 -0
- package/src/lib/transformers/has-abbreviation.spec.ts +50 -0
- package/src/lib/transformers/has-abbreviation.ts +30 -0
- package/src/lib/transformers/heading.spec.ts +62 -0
- package/src/lib/transformers/heading.ts +30 -0
- package/src/lib/transformers/image.spec.ts +51 -0
- package/src/lib/transformers/image.ts +28 -0
- package/src/lib/transformers/indent.spec.ts +53 -0
- package/src/lib/transformers/indent.ts +17 -0
- package/src/lib/transformers/index.ts +33 -0
- package/src/lib/transformers/inline-title.spec.ts +59 -0
- package/src/lib/transformers/inline-title.ts +34 -0
- package/src/lib/transformers/internal-link.spec.ts +67 -0
- package/src/lib/transformers/internal-link.ts +65 -0
- package/src/lib/transformers/italic.ts +22 -0
- package/src/lib/transformers/leading-space.spec.ts +55 -0
- package/src/lib/transformers/leading-space.ts +17 -0
- package/src/lib/transformers/line-group.spec.ts +48 -0
- package/src/lib/transformers/line-group.ts +37 -0
- package/src/lib/transformers/line.spec.ts +54 -0
- package/src/lib/transformers/line.ts +27 -0
- package/src/lib/transformers/link.spec.ts +61 -0
- package/src/lib/transformers/link.ts +27 -0
- package/src/lib/transformers/list-item.spec.ts +48 -0
- package/src/lib/transformers/list-item.ts +37 -0
- package/src/lib/transformers/list.spec.ts +58 -0
- package/src/lib/transformers/list.ts +42 -0
- package/src/lib/transformers/mantra.spec.ts +58 -0
- package/src/lib/transformers/mantra.ts +28 -0
- package/src/lib/transformers/mention.ts +70 -0
- package/src/lib/transformers/paragraph.spec.ts +46 -0
- package/src/lib/transformers/paragraph.ts +26 -0
- package/src/lib/transformers/quote.spec.ts +42 -0
- package/src/lib/transformers/quote.ts +3 -0
- package/src/lib/transformers/quoted.ts +3 -0
- package/src/lib/transformers/recurse.ts +76 -0
- package/src/lib/transformers/reference.ts +3 -0
- package/src/lib/transformers/span.spec.ts +68 -0
- package/src/lib/transformers/span.ts +78 -0
- package/src/lib/transformers/split-at.ts +58 -0
- package/src/lib/transformers/split-block.ts +110 -0
- package/src/lib/transformers/split-content.ts +67 -0
- package/src/lib/transformers/split-insert.ts +76 -0
- package/src/lib/transformers/split-marks.ts +42 -0
- package/src/lib/transformers/split-node.ts +138 -0
- package/src/lib/transformers/table-body-data.spec.ts +44 -0
- package/src/lib/transformers/table-body-data.ts +29 -0
- package/src/lib/transformers/table-body-header.spec.ts +44 -0
- package/src/lib/transformers/table-body-header.ts +29 -0
- package/src/lib/transformers/table-body-row.spec.ts +44 -0
- package/src/lib/transformers/table-body-row.ts +29 -0
- package/src/lib/transformers/table.spec.ts +47 -0
- package/src/lib/transformers/table.ts +29 -0
- package/src/lib/transformers/trailer.spec.ts +43 -0
- package/src/lib/transformers/trailer.ts +26 -0
- package/src/lib/transformers/transformer.ts +25 -0
- package/src/lib/transformers/unknown.ts +8 -0
- package/src/lib/transformers/util.ts +20 -0
- package/src/lib/types.ts +10 -0
- package/src/ssr.ts +1 -0
- package/tsconfig.json +20 -0
- package/tsconfig.lib.json +29 -0
- package/tsconfig.spec.json +22 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core';
|
|
2
|
+
import { incrementLabel } from '../Passage/label';
|
|
3
|
+
import { findPassageNode } from '../../util';
|
|
4
|
+
|
|
5
|
+
interface EndNoteLinkNote {
|
|
6
|
+
uuid: string;
|
|
7
|
+
endNote: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
location?: string;
|
|
10
|
+
toh?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface MarkRange {
|
|
14
|
+
from: number;
|
|
15
|
+
to: number;
|
|
16
|
+
mark: ReturnType<Editor['state']['doc']['resolve']> extends never
|
|
17
|
+
? never
|
|
18
|
+
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
any;
|
|
20
|
+
note: EndNoteLinkNote;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Scan main editor from start to `cursorPos`, collecting `endNoteLink` marks.
|
|
25
|
+
* Return the last one's `endNote` UUID and its position info.
|
|
26
|
+
*/
|
|
27
|
+
export function findLastEndNoteLinkBefore(
|
|
28
|
+
editor: Editor,
|
|
29
|
+
cursorPos: number,
|
|
30
|
+
): { endNote: string; from: number; to: number } | undefined {
|
|
31
|
+
const { doc } = editor.state;
|
|
32
|
+
let last: { endNote: string; from: number; to: number } | undefined;
|
|
33
|
+
|
|
34
|
+
doc.descendants((node, pos) => {
|
|
35
|
+
if (pos >= cursorPos) return false;
|
|
36
|
+
|
|
37
|
+
for (const mark of node.marks) {
|
|
38
|
+
if (mark.type.name === 'endNoteLink') {
|
|
39
|
+
const notes: EndNoteLinkNote[] = mark.attrs.notes || [];
|
|
40
|
+
for (const note of notes) {
|
|
41
|
+
if (note.endNote) {
|
|
42
|
+
last = { endNote: note.endNote, from: pos, to: pos + node.nodeSize };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return last;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Traverse the editor doc, return all mark ranges where
|
|
55
|
+
* notes[].endNote === endNotePassageUuid.
|
|
56
|
+
*/
|
|
57
|
+
export function findAllEndnoteLinksForPassage(
|
|
58
|
+
editor: Editor,
|
|
59
|
+
endNotePassageUuid: string,
|
|
60
|
+
): MarkRange[] {
|
|
61
|
+
const { doc } = editor.state;
|
|
62
|
+
const results: MarkRange[] = [];
|
|
63
|
+
|
|
64
|
+
doc.descendants((node, pos) => {
|
|
65
|
+
for (const mark of node.marks) {
|
|
66
|
+
if (mark.type.name === 'endNoteLink') {
|
|
67
|
+
const notes: EndNoteLinkNote[] = mark.attrs.notes || [];
|
|
68
|
+
for (const note of notes) {
|
|
69
|
+
if (note.endNote === endNotePassageUuid) {
|
|
70
|
+
results.push({
|
|
71
|
+
from: pos,
|
|
72
|
+
to: pos + node.nodeSize,
|
|
73
|
+
mark,
|
|
74
|
+
note,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Batch-remove all `endNoteLink` marks pointing to a given passage UUID
|
|
88
|
+
* using a single transaction.
|
|
89
|
+
*/
|
|
90
|
+
export function removeAllEndnoteLinksForPassage(
|
|
91
|
+
editor: Editor,
|
|
92
|
+
endNotePassageUuid: string,
|
|
93
|
+
): void {
|
|
94
|
+
const ranges = findAllEndnoteLinksForPassage(editor, endNotePassageUuid);
|
|
95
|
+
if (ranges.length === 0) return;
|
|
96
|
+
|
|
97
|
+
const { tr } = editor.state;
|
|
98
|
+
|
|
99
|
+
for (const { from, to, mark, note } of ranges) {
|
|
100
|
+
tr.removeMark(from, to, mark.type);
|
|
101
|
+
// If the mark has other notes besides the one we're removing, re-add it
|
|
102
|
+
const remainingNotes = (mark.attrs.notes || []).filter(
|
|
103
|
+
(n: EndNoteLinkNote) => n.uuid !== note.uuid,
|
|
104
|
+
);
|
|
105
|
+
if (remainingNotes.length > 0) {
|
|
106
|
+
tr.addMark(from, to, mark.type.create({ ...mark.attrs, notes: remainingNotes }));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
editor.view.dispatch(tr);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Re-export for convenience — canonical definition is in ../../util.ts
|
|
114
|
+
export { findPassageNode } from '../../util';
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get the last passage node in the endnotes editor.
|
|
118
|
+
* Returns { label, sort, uuid } or undefined if no passages exist.
|
|
119
|
+
*/
|
|
120
|
+
export function getLastEndnoteInEditor(
|
|
121
|
+
editor: Editor,
|
|
122
|
+
): { label: string; sort: number; uuid: string } | undefined {
|
|
123
|
+
const { doc } = editor.state;
|
|
124
|
+
let last: { label: string; sort: number; uuid: string; pos: number } | undefined;
|
|
125
|
+
|
|
126
|
+
doc.descendants((node, pos) => {
|
|
127
|
+
if (node.type.name === 'passage') {
|
|
128
|
+
last = {
|
|
129
|
+
label: node.attrs.label || '',
|
|
130
|
+
sort: node.attrs.sort ?? 0,
|
|
131
|
+
uuid: node.attrs.uuid,
|
|
132
|
+
pos,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return last
|
|
139
|
+
? { label: last.label, sort: last.sort, uuid: last.uuid }
|
|
140
|
+
: undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get the first endnotes-type passage node in the endnotes editor.
|
|
145
|
+
* Skips endnotesHeader passages.
|
|
146
|
+
* Returns { label, sort, uuid } or undefined if no passages exist.
|
|
147
|
+
*/
|
|
148
|
+
export function getFirstEndnoteInEditor(
|
|
149
|
+
editor: Editor,
|
|
150
|
+
): { label: string; sort: number; uuid: string } | undefined {
|
|
151
|
+
const { doc } = editor.state;
|
|
152
|
+
let first: { label: string; sort: number; uuid: string } | undefined;
|
|
153
|
+
|
|
154
|
+
doc.descendants((node) => {
|
|
155
|
+
if (!first && node.type.name === 'passage' && node.attrs.type === 'endnotes') {
|
|
156
|
+
first = {
|
|
157
|
+
label: node.attrs.label || '',
|
|
158
|
+
sort: node.attrs.sort ?? 0,
|
|
159
|
+
uuid: node.attrs.uuid,
|
|
160
|
+
};
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return first;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Insert a new empty endnote passage into the endnotes editor at the correct
|
|
171
|
+
* position. Use `afterPassageUuid` to insert after a passage, or
|
|
172
|
+
* `beforePassageUuid` to insert before one. Falls back to end of doc.
|
|
173
|
+
* Increments labels and sort values of all subsequent passages.
|
|
174
|
+
*/
|
|
175
|
+
export function insertEndnotePassage(
|
|
176
|
+
editor: Editor,
|
|
177
|
+
{
|
|
178
|
+
label,
|
|
179
|
+
sort,
|
|
180
|
+
uuid,
|
|
181
|
+
afterPassageUuid,
|
|
182
|
+
beforePassageUuid,
|
|
183
|
+
}: {
|
|
184
|
+
label: string;
|
|
185
|
+
sort: number;
|
|
186
|
+
uuid: string;
|
|
187
|
+
afterPassageUuid?: string;
|
|
188
|
+
beforePassageUuid?: string;
|
|
189
|
+
},
|
|
190
|
+
): void {
|
|
191
|
+
const { state } = editor;
|
|
192
|
+
const { tr, schema } = state;
|
|
193
|
+
const passageType = schema.nodes.passage;
|
|
194
|
+
const paragraphType = schema.nodes.paragraph;
|
|
195
|
+
|
|
196
|
+
if (!passageType || !paragraphType) {
|
|
197
|
+
console.warn('Required node types not found in schema');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const newPassage = passageType.create(
|
|
202
|
+
{ label, sort, uuid, type: 'endnotes' },
|
|
203
|
+
paragraphType.create(),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Find insertion position
|
|
207
|
+
let insertPos = state.doc.content.size;
|
|
208
|
+
if (beforePassageUuid) {
|
|
209
|
+
state.doc.descendants((node, pos) => {
|
|
210
|
+
if (
|
|
211
|
+
node.type.name === 'passage' &&
|
|
212
|
+
node.attrs.uuid === beforePassageUuid
|
|
213
|
+
) {
|
|
214
|
+
insertPos = pos;
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
});
|
|
219
|
+
} else if (afterPassageUuid) {
|
|
220
|
+
state.doc.descendants((node, pos) => {
|
|
221
|
+
if (
|
|
222
|
+
node.type.name === 'passage' &&
|
|
223
|
+
node.attrs.uuid === afterPassageUuid
|
|
224
|
+
) {
|
|
225
|
+
insertPos = pos + node.nodeSize;
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
tr.insert(insertPos, newPassage);
|
|
233
|
+
|
|
234
|
+
// Increment labels and sorts of passages after the insertion point.
|
|
235
|
+
// After the insert, the new passage occupies [insertPos, insertPos + newPassage.nodeSize).
|
|
236
|
+
// Passages that were at insertPos in the original doc are now shifted by newPassage.nodeSize.
|
|
237
|
+
const afterNewPos = insertPos + newPassage.nodeSize;
|
|
238
|
+
const parts = label.split('.');
|
|
239
|
+
const prefix = parts.slice(0, -1).join('.');
|
|
240
|
+
const prefixWithDot = prefix ? prefix + '.' : '';
|
|
241
|
+
const depth = parts.length;
|
|
242
|
+
let expectedNext = incrementLabel(label);
|
|
243
|
+
|
|
244
|
+
tr.doc.descendants((child, childPos) => {
|
|
245
|
+
if (childPos < afterNewPos) return true;
|
|
246
|
+
if (child.type.name !== 'passage') return true;
|
|
247
|
+
const childLabel = child.attrs.label as string;
|
|
248
|
+
if (!childLabel) return true;
|
|
249
|
+
if (prefixWithDot && !childLabel.startsWith(prefixWithDot)) return true;
|
|
250
|
+
if (childLabel.split('.').length !== depth) return true;
|
|
251
|
+
|
|
252
|
+
if (child.attrs.type === 'endnotesHeader') {
|
|
253
|
+
// Only update sort, never change header labels
|
|
254
|
+
tr.setNodeMarkup(childPos, null, {
|
|
255
|
+
...child.attrs,
|
|
256
|
+
sort: (child.attrs.sort ?? 0) + 1,
|
|
257
|
+
});
|
|
258
|
+
} else {
|
|
259
|
+
tr.setNodeMarkup(childPos, null, {
|
|
260
|
+
...child.attrs,
|
|
261
|
+
label: expectedNext,
|
|
262
|
+
sort: (child.attrs.sort ?? 0) + 1,
|
|
263
|
+
});
|
|
264
|
+
expectedNext = incrementLabel(expectedNext);
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
editor.view.dispatch(tr);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Delete a passage node from the endnotes editor by UUID,
|
|
274
|
+
* then normalize labels of subsequent passages.
|
|
275
|
+
*/
|
|
276
|
+
export function deleteEndnotePassageNode(
|
|
277
|
+
editor: Editor,
|
|
278
|
+
passageUuid: string,
|
|
279
|
+
): void {
|
|
280
|
+
const found = findPassageNode(editor, passageUuid);
|
|
281
|
+
if (!found) return;
|
|
282
|
+
|
|
283
|
+
const { pos, node } = found;
|
|
284
|
+
const { tr } = editor.state;
|
|
285
|
+
|
|
286
|
+
// Capture the deleted passage's label before removing it — subsequent
|
|
287
|
+
// passages should be renumbered starting from this label.
|
|
288
|
+
const deletedLabel = node.attrs.label as string | undefined;
|
|
289
|
+
|
|
290
|
+
// Delete the passage node
|
|
291
|
+
tr.delete(pos, pos + node.nodeSize);
|
|
292
|
+
|
|
293
|
+
// Normalize labels after deletion: passages that were after the deleted
|
|
294
|
+
// node (now starting at `pos` in the updated doc) get renumbered.
|
|
295
|
+
if (deletedLabel) {
|
|
296
|
+
let expectedNext = deletedLabel;
|
|
297
|
+
const parts = deletedLabel.split('.');
|
|
298
|
+
const prefix = parts.slice(0, -1).join('.');
|
|
299
|
+
const prefixWithDot = prefix ? prefix + '.' : '';
|
|
300
|
+
const depth = parts.length;
|
|
301
|
+
|
|
302
|
+
tr.doc.descendants((child, childPos) => {
|
|
303
|
+
// Only process passages at or after the deletion point
|
|
304
|
+
if (childPos < pos) return true;
|
|
305
|
+
if (child.type.name !== 'passage') return true;
|
|
306
|
+
const childLabel = child.attrs.label as string;
|
|
307
|
+
if (!childLabel) return true;
|
|
308
|
+
|
|
309
|
+
// Only normalize passages with same prefix and depth
|
|
310
|
+
if (prefixWithDot && !childLabel.startsWith(prefixWithDot)) return true;
|
|
311
|
+
if (childLabel.split('.').length !== depth) return true;
|
|
312
|
+
|
|
313
|
+
if (child.attrs.type === 'endnotesHeader') {
|
|
314
|
+
// Never change header labels
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
if (childLabel !== expectedNext) {
|
|
318
|
+
tr.setNodeMarkup(childPos, null, {
|
|
319
|
+
...child.attrs,
|
|
320
|
+
label: expectedNext,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
expectedNext = incrementLabel(expectedNext);
|
|
324
|
+
return true;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
editor.view.dispatch(tr);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Build a map of endnote passage UUID → label from the endnotes editor.
|
|
333
|
+
*/
|
|
334
|
+
export function buildEndnoteLabelMap(
|
|
335
|
+
endnotesEditor: Editor,
|
|
336
|
+
): Map<string, string> {
|
|
337
|
+
const map = new Map<string, string>();
|
|
338
|
+
endnotesEditor.state.doc.descendants((node) => {
|
|
339
|
+
if (node.type.name === 'passage' && node.attrs.uuid && node.attrs.label) {
|
|
340
|
+
map.set(node.attrs.uuid, node.attrs.label);
|
|
341
|
+
}
|
|
342
|
+
return true;
|
|
343
|
+
});
|
|
344
|
+
return map;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Update the `label` field inside `endNoteLink` marks in an editor to match
|
|
349
|
+
* the current labels in the endnotes editor. Call this after deleting/renumbering
|
|
350
|
+
* endnote passages so the superscript numbers in the UI stay in sync.
|
|
351
|
+
*/
|
|
352
|
+
export function syncEndnoteLinkLabels(
|
|
353
|
+
editor: Editor,
|
|
354
|
+
labelMap: Map<string, string>,
|
|
355
|
+
): void {
|
|
356
|
+
const { tr } = editor.state;
|
|
357
|
+
let changed = false;
|
|
358
|
+
|
|
359
|
+
editor.state.doc.descendants((node, pos) => {
|
|
360
|
+
for (const mark of node.marks) {
|
|
361
|
+
if (mark.type.name !== 'endNoteLink') continue;
|
|
362
|
+
const notes: EndNoteLinkNote[] = mark.attrs.notes || [];
|
|
363
|
+
let notesChanged = false;
|
|
364
|
+
|
|
365
|
+
const updatedNotes = notes.map((note) => {
|
|
366
|
+
const newLabel = labelMap.get(note.endNote);
|
|
367
|
+
if (newLabel !== undefined && newLabel !== note.label) {
|
|
368
|
+
notesChanged = true;
|
|
369
|
+
return { ...note, label: newLabel };
|
|
370
|
+
}
|
|
371
|
+
return note;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (notesChanged) {
|
|
375
|
+
const from = pos;
|
|
376
|
+
const to = pos + node.nodeSize;
|
|
377
|
+
tr.removeMark(from, to, mark.type);
|
|
378
|
+
tr.addMark(
|
|
379
|
+
from,
|
|
380
|
+
to,
|
|
381
|
+
mark.type.create({ ...mark.attrs, notes: updatedNotes }),
|
|
382
|
+
);
|
|
383
|
+
changed = true;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
if (changed) {
|
|
390
|
+
editor.view.dispatch(tr);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Editor keys that can contain endNoteLink marks. */
|
|
395
|
+
const ENDNOTE_LINK_EDITOR_KEYS = ['front', 'translation'] as const;
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* After deleting/renumbering endnote passages, sync the updated labels into
|
|
399
|
+
* endNoteLink marks across all editors that may contain them (front + translation).
|
|
400
|
+
*/
|
|
401
|
+
export function syncEndnoteLinkLabelsAcrossEditors(
|
|
402
|
+
endnotesEditor: Editor,
|
|
403
|
+
getEditor: (key: string) => Editor | undefined,
|
|
404
|
+
): void {
|
|
405
|
+
const labelMap = buildEndnoteLabelMap(endnotesEditor);
|
|
406
|
+
for (const key of ENDNOTE_LINK_EDITOR_KEYS) {
|
|
407
|
+
const ed = getEditor(key);
|
|
408
|
+
if (ed) {
|
|
409
|
+
syncEndnoteLinkLabels(ed, labelMap);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
|
|
3
|
+
declare module '@tiptap/core' {
|
|
4
|
+
interface Commands<ReturnType> {
|
|
5
|
+
globalConfig: {
|
|
6
|
+
/**
|
|
7
|
+
* Enable or disable debug mode
|
|
8
|
+
* @example editor.commands.setDebugMode(true)
|
|
9
|
+
*/
|
|
10
|
+
setDebug: (enabled: boolean) => ReturnType;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if debug mode is enabled
|
|
14
|
+
* @example editor.isDebug()
|
|
15
|
+
*/
|
|
16
|
+
isDebug: () => ReturnType;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Storage {
|
|
21
|
+
globalConfig: {
|
|
22
|
+
debug: boolean;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const GlobalConfig = Extension.create({
|
|
28
|
+
name: 'globalConfig',
|
|
29
|
+
|
|
30
|
+
addStorage() {
|
|
31
|
+
return {
|
|
32
|
+
debug: false,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
addCommands() {
|
|
37
|
+
return {
|
|
38
|
+
setDebug:
|
|
39
|
+
(enabled) =>
|
|
40
|
+
({ editor }) => {
|
|
41
|
+
this.storage.debug = enabled;
|
|
42
|
+
// Force re-render if needed
|
|
43
|
+
editor.view.updateState(editor.state);
|
|
44
|
+
return true;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
isDebug: () => () => {
|
|
48
|
+
return this.storage.debug;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Button, Input } from '@eightyfourthousand/design-system';
|
|
2
|
+
import { CheckIcon, Trash2Icon } from 'lucide-react';
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
export const GlossaryInput = ({
|
|
6
|
+
uuid,
|
|
7
|
+
onSubmit,
|
|
8
|
+
}: {
|
|
9
|
+
uuid: string;
|
|
10
|
+
onSubmit: (entity?: string) => void;
|
|
11
|
+
}) => {
|
|
12
|
+
const uuidRef = useRef<HTMLInputElement>(null);
|
|
13
|
+
|
|
14
|
+
const submit = () => {
|
|
15
|
+
onSubmit(uuidRef.current?.value);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex space-x-1 items-center">
|
|
20
|
+
<Input
|
|
21
|
+
ref={uuidRef}
|
|
22
|
+
placeholder="Glossary uuid..."
|
|
23
|
+
value={uuid}
|
|
24
|
+
onKeyDown={(e) => {
|
|
25
|
+
if (e.key === 'Enter') {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
submit();
|
|
28
|
+
}
|
|
29
|
+
}}
|
|
30
|
+
/>
|
|
31
|
+
<Button
|
|
32
|
+
size="icon"
|
|
33
|
+
variant="ghost"
|
|
34
|
+
disabled={!!uuidRef.current}
|
|
35
|
+
onClick={submit}
|
|
36
|
+
>
|
|
37
|
+
<CheckIcon />
|
|
38
|
+
</Button>
|
|
39
|
+
<Button
|
|
40
|
+
variant="ghost"
|
|
41
|
+
size="icon"
|
|
42
|
+
type="button"
|
|
43
|
+
onClick={() => {
|
|
44
|
+
if (uuidRef.current) {
|
|
45
|
+
uuidRef.current.value = '';
|
|
46
|
+
}
|
|
47
|
+
onSubmit();
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<Trash2Icon className="text-destructive" />
|
|
51
|
+
</Button>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Button } from '@eightyfourthousand/design-system';
|
|
2
|
+
import { Editor } from '@tiptap/core';
|
|
3
|
+
import { BookOpenIcon, PencilIcon, Trash2Icon } from 'lucide-react';
|
|
4
|
+
import { useCallback, useState } from 'react';
|
|
5
|
+
import { GlossaryInput } from './GlossaryInput';
|
|
6
|
+
import { findMarkByUuid } from '../../util';
|
|
7
|
+
import { useHoverCard } from '../../../shared/HoverCardProvider';
|
|
8
|
+
|
|
9
|
+
const EDITOR_UPDATE_DELAY_MS = 100;
|
|
10
|
+
|
|
11
|
+
export const GlossaryInstance = ({
|
|
12
|
+
uuid,
|
|
13
|
+
glossary,
|
|
14
|
+
editor,
|
|
15
|
+
anchor,
|
|
16
|
+
}: {
|
|
17
|
+
uuid: string;
|
|
18
|
+
glossary: string;
|
|
19
|
+
editor: Editor;
|
|
20
|
+
anchor: HTMLElement;
|
|
21
|
+
}) => {
|
|
22
|
+
const [isEditing, setIsEditingLocal] = useState(false);
|
|
23
|
+
const { close, setIsEditing: setIsEditingContext } = useHoverCard();
|
|
24
|
+
|
|
25
|
+
const setIsEditing = useCallback(
|
|
26
|
+
(editing: boolean) => {
|
|
27
|
+
setIsEditingLocal(editing);
|
|
28
|
+
setIsEditingContext(editing);
|
|
29
|
+
},
|
|
30
|
+
[setIsEditingContext],
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const deleteLink = useCallback(() => {
|
|
34
|
+
setIsEditing(false);
|
|
35
|
+
close();
|
|
36
|
+
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
const range = findMarkByUuid({
|
|
39
|
+
editor,
|
|
40
|
+
uuid,
|
|
41
|
+
markType: 'glossaryInstance',
|
|
42
|
+
});
|
|
43
|
+
if (!range) {
|
|
44
|
+
console.warn('GlossaryInstance mark not found in the document.');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { from, to, mark } = range;
|
|
49
|
+
const { tr } = editor.state;
|
|
50
|
+
tr.removeMark(from, to, mark.type);
|
|
51
|
+
editor.view.dispatch(tr);
|
|
52
|
+
}, EDITOR_UPDATE_DELAY_MS);
|
|
53
|
+
}, [editor, uuid, close, setIsEditing]);
|
|
54
|
+
|
|
55
|
+
const updateGlossary = useCallback(
|
|
56
|
+
(newGlossary: string) => {
|
|
57
|
+
setIsEditing(false);
|
|
58
|
+
close();
|
|
59
|
+
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
const range = findMarkByUuid({
|
|
62
|
+
editor,
|
|
63
|
+
uuid,
|
|
64
|
+
markType: 'glossaryInstance',
|
|
65
|
+
});
|
|
66
|
+
if (!range) {
|
|
67
|
+
console.warn('GlossaryInstance mark not found in the document.');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const { from, to, mark } = range;
|
|
72
|
+
const { tr } = editor.state;
|
|
73
|
+
tr.removeMark(from, to, mark.type);
|
|
74
|
+
tr.addMark(
|
|
75
|
+
from,
|
|
76
|
+
to,
|
|
77
|
+
mark.type.create({ ...mark.attrs, glossary: newGlossary }),
|
|
78
|
+
);
|
|
79
|
+
editor.view.dispatch(tr);
|
|
80
|
+
|
|
81
|
+
// Update the DOM attribute directly for immediate feedback
|
|
82
|
+
anchor.setAttribute('glossary', newGlossary);
|
|
83
|
+
}, EDITOR_UPDATE_DELAY_MS);
|
|
84
|
+
},
|
|
85
|
+
[editor, uuid, anchor, close, setIsEditing],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="flex justify-between gap-2 p-2 w-fit max-w-80">
|
|
90
|
+
{isEditing ? (
|
|
91
|
+
<GlossaryInput
|
|
92
|
+
uuid={glossary}
|
|
93
|
+
onSubmit={(newGlossary) => {
|
|
94
|
+
if (newGlossary) {
|
|
95
|
+
updateGlossary(newGlossary);
|
|
96
|
+
} else {
|
|
97
|
+
deleteLink();
|
|
98
|
+
}
|
|
99
|
+
setIsEditing(false);
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
) : (
|
|
103
|
+
<>
|
|
104
|
+
<BookOpenIcon className="text-primary my-auto size-6 [&_svg]:size-4" />
|
|
105
|
+
<span className="truncate text-muted-foreground text-sm my-auto">
|
|
106
|
+
{glossary}
|
|
107
|
+
</span>
|
|
108
|
+
<span className="flex-grow" />
|
|
109
|
+
<Button
|
|
110
|
+
variant="ghost"
|
|
111
|
+
size="icon"
|
|
112
|
+
className="size-6 [&_svg]:size-4"
|
|
113
|
+
onClick={() => setIsEditing(true)}
|
|
114
|
+
>
|
|
115
|
+
<PencilIcon className="text-primary my-auto" />
|
|
116
|
+
</Button>
|
|
117
|
+
<Button
|
|
118
|
+
variant="ghost"
|
|
119
|
+
size="icon"
|
|
120
|
+
className="size-6 [&_svg]:size-4"
|
|
121
|
+
onClick={deleteLink}
|
|
122
|
+
>
|
|
123
|
+
<Trash2Icon className="text-destructive my-auto" />
|
|
124
|
+
</Button>
|
|
125
|
+
</>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
};
|