@liminis/editor 0.1.0-rc.1
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/LICENSE +34 -0
- package/README.md +376 -0
- package/dist/annotations/anchor-align.d.ts +50 -0
- package/dist/annotations/anchor-align.js +74 -0
- package/dist/annotations/anchor-model.d.ts +81 -0
- package/dist/annotations/anchor-model.js +80 -0
- package/dist/annotations/anchor-resolver.d.ts +92 -0
- package/dist/annotations/anchor-resolver.js +231 -0
- package/dist/annotations/block-structure.d.ts +65 -0
- package/dist/annotations/block-structure.js +113 -0
- package/dist/annotations/types.d.ts +159 -0
- package/dist/annotations/types.js +50 -0
- package/dist/annotations.d.ts +25 -0
- package/dist/annotations.js +27 -0
- package/dist/app/App.d.ts +64 -0
- package/dist/app/App.js +180 -0
- package/dist/app/editor/AmbientCorrectionPlugin.d.ts +18 -0
- package/dist/app/editor/AmbientCorrectionPlugin.js +185 -0
- package/dist/app/editor/AnchorScrollPlugin.d.ts +16 -0
- package/dist/app/editor/AnchorScrollPlugin.js +134 -0
- package/dist/app/editor/AnnotationMarkerPlugin.d.ts +24 -0
- package/dist/app/editor/AnnotationMarkerPlugin.js +224 -0
- package/dist/app/editor/AnnotationPlugin.d.ts +42 -0
- package/dist/app/editor/AnnotationPlugin.js +135 -0
- package/dist/app/editor/AnnotationSurface.d.ts +34 -0
- package/dist/app/editor/AnnotationSurface.js +115 -0
- package/dist/app/editor/AssetContext.d.ts +20 -0
- package/dist/app/editor/AssetContext.js +81 -0
- package/dist/app/editor/BlockClickPlugin.d.ts +8 -0
- package/dist/app/editor/BlockClickPlugin.js +126 -0
- package/dist/app/editor/CodeBlockPlugin.d.ts +1 -0
- package/dist/app/editor/CodeBlockPlugin.js +219 -0
- package/dist/app/editor/CorrectionPanelPlugin.d.ts +14 -0
- package/dist/app/editor/CorrectionPanelPlugin.js +333 -0
- package/dist/app/editor/DragHandlePlugin.d.ts +1 -0
- package/dist/app/editor/DragHandlePlugin.js +300 -0
- package/dist/app/editor/Editor.d.ts +84 -0
- package/dist/app/editor/Editor.js +539 -0
- package/dist/app/editor/FrontmatterPlugin.d.ts +19 -0
- package/dist/app/editor/FrontmatterPlugin.js +301 -0
- package/dist/app/editor/ImageModal.d.ts +13 -0
- package/dist/app/editor/ImageModal.js +267 -0
- package/dist/app/editor/ImagePlugin.d.ts +3 -0
- package/dist/app/editor/ImagePlugin.js +187 -0
- package/dist/app/editor/LinkClickPlugin.d.ts +23 -0
- package/dist/app/editor/LinkClickPlugin.js +90 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.d.ts +49 -0
- package/dist/app/editor/MarkdownShortcutsPlugin.js +373 -0
- package/dist/app/editor/OrderedTaskListPlugin.d.ts +21 -0
- package/dist/app/editor/OrderedTaskListPlugin.js +78 -0
- package/dist/app/editor/SearchPlugin.d.ts +1 -0
- package/dist/app/editor/SearchPlugin.js +379 -0
- package/dist/app/editor/SelectionContextMenuPlugin.d.ts +29 -0
- package/dist/app/editor/SelectionContextMenuPlugin.js +160 -0
- package/dist/app/editor/SlashMenu.d.ts +11 -0
- package/dist/app/editor/SlashMenu.js +494 -0
- package/dist/app/editor/SlashMenuPlugin.d.ts +1 -0
- package/dist/app/editor/SlashMenuPlugin.js +168 -0
- package/dist/app/editor/TableActionsPlugin.d.ts +1 -0
- package/dist/app/editor/TableActionsPlugin.js +398 -0
- package/dist/app/editor/TogglePlugin.d.ts +1 -0
- package/dist/app/editor/TogglePlugin.js +159 -0
- package/dist/app/editor/Toolbar.d.ts +10 -0
- package/dist/app/editor/Toolbar.js +263 -0
- package/dist/app/editor/WikiLinkExistencePlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkExistencePlugin.js +104 -0
- package/dist/app/editor/WikiLinkFormatPlugin.d.ts +17 -0
- package/dist/app/editor/WikiLinkFormatPlugin.js +203 -0
- package/dist/app/editor/annotation-marks.d.ts +177 -0
- package/dist/app/editor/annotation-marks.js +750 -0
- package/dist/app/editor/annotationCommands.d.ts +13 -0
- package/dist/app/editor/annotationCommands.js +11 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.d.ts +35 -0
- package/dist/app/editor/c4/C4InteractiveRenderer.js +299 -0
- package/dist/app/editor/c4/edge-clipping.d.ts +24 -0
- package/dist/app/editor/c4/edge-clipping.js +139 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.d.ts +38 -0
- package/dist/app/editor/c4/hooks/useC4DiagramDrag.js +112 -0
- package/dist/app/editor/c4/layout.d.ts +25 -0
- package/dist/app/editor/c4/layout.js +839 -0
- package/dist/app/editor/c4/parser.d.ts +19 -0
- package/dist/app/editor/c4/parser.js +410 -0
- package/dist/app/editor/c4/render-to-string.d.ts +24 -0
- package/dist/app/editor/c4/render-to-string.js +34 -0
- package/dist/app/editor/c4/renderer.d.ts +64 -0
- package/dist/app/editor/c4/renderer.js +569 -0
- package/dist/app/editor/c4/types.d.ts +203 -0
- package/dist/app/editor/c4/types.js +43 -0
- package/dist/app/editor/correction-yaml.d.ts +10 -0
- package/dist/app/editor/correction-yaml.js +40 -0
- package/dist/app/editor/editorNodes.d.ts +9 -0
- package/dist/app/editor/editorNodes.js +36 -0
- package/dist/app/editor/index.d.ts +1 -0
- package/dist/app/editor/index.js +1 -0
- package/dist/app/editor/mark-overlap-resolver.d.ts +6 -0
- package/dist/app/editor/mark-overlap-resolver.js +45 -0
- package/dist/app/editor/nodes/C4Component.d.ts +14 -0
- package/dist/app/editor/nodes/C4Component.js +284 -0
- package/dist/app/editor/nodes/C4Node.d.ts +32 -0
- package/dist/app/editor/nodes/C4Node.js +112 -0
- package/dist/app/editor/nodes/CalloutNode.d.ts +23 -0
- package/dist/app/editor/nodes/CalloutNode.js +94 -0
- package/dist/app/editor/nodes/CustomLinkNode.d.ts +57 -0
- package/dist/app/editor/nodes/CustomLinkNode.js +209 -0
- package/dist/app/editor/nodes/CustomListItemNode.d.ts +46 -0
- package/dist/app/editor/nodes/CustomListItemNode.js +120 -0
- package/dist/app/editor/nodes/CustomListNode.d.ts +27 -0
- package/dist/app/editor/nodes/CustomListNode.js +54 -0
- package/dist/app/editor/nodes/DefinitionListNode.d.ts +44 -0
- package/dist/app/editor/nodes/DefinitionListNode.js +151 -0
- package/dist/app/editor/nodes/DiagramContextMenu.d.ts +45 -0
- package/dist/app/editor/nodes/DiagramContextMenu.js +116 -0
- package/dist/app/editor/nodes/EquationComponent.d.ts +17 -0
- package/dist/app/editor/nodes/EquationComponent.js +130 -0
- package/dist/app/editor/nodes/EquationNode.d.ts +43 -0
- package/dist/app/editor/nodes/EquationNode.js +179 -0
- package/dist/app/editor/nodes/FootnoteNode.d.ts +40 -0
- package/dist/app/editor/nodes/FootnoteNode.js +151 -0
- package/dist/app/editor/nodes/FrontmatterNode.d.ts +24 -0
- package/dist/app/editor/nodes/FrontmatterNode.js +88 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.d.ts +17 -0
- package/dist/app/editor/nodes/HorizontalRuleNode.js +57 -0
- package/dist/app/editor/nodes/HtmlNode.d.ts +32 -0
- package/dist/app/editor/nodes/HtmlNode.js +121 -0
- package/dist/app/editor/nodes/ImageComponent.d.ts +11 -0
- package/dist/app/editor/nodes/ImageComponent.js +228 -0
- package/dist/app/editor/nodes/ImageNode.d.ts +53 -0
- package/dist/app/editor/nodes/ImageNode.js +183 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.d.ts +42 -0
- package/dist/app/editor/nodes/ListItemParagraphBreakNode.js +95 -0
- package/dist/app/editor/nodes/MermaidComponent.d.ts +11 -0
- package/dist/app/editor/nodes/MermaidComponent.js +216 -0
- package/dist/app/editor/nodes/MermaidNode.d.ts +27 -0
- package/dist/app/editor/nodes/MermaidNode.js +96 -0
- package/dist/app/editor/nodes/ToggleNode.d.ts +56 -0
- package/dist/app/editor/nodes/ToggleNode.js +237 -0
- package/dist/app/editor/nodes/diagram-context-menu.d.ts +11 -0
- package/dist/app/editor/nodes/diagram-context-menu.js +70 -0
- package/dist/app/editor/nodes/diagram-utils.d.ts +13 -0
- package/dist/app/editor/nodes/diagram-utils.js +29 -0
- package/dist/app/editor/nodes/index.d.ts +30 -0
- package/dist/app/editor/nodes/index.js +17 -0
- package/dist/app/editor/utils.d.ts +2 -0
- package/dist/app/editor/utils.js +17 -0
- package/dist/app/mapper/index.d.ts +3 -0
- package/dist/app/mapper/index.js +2 -0
- package/dist/app/mapper/lexicalToMdast.d.ts +62 -0
- package/dist/app/mapper/lexicalToMdast.js +1937 -0
- package/dist/app/mapper/mdastToLexical.d.ts +31 -0
- package/dist/app/mapper/mdastToLexical.js +1075 -0
- package/dist/contract.d.ts +8 -0
- package/dist/contract.js +8 -0
- package/dist/headless.d.ts +40 -0
- package/dist/headless.js +45 -0
- package/dist/host/context.d.ts +20 -0
- package/dist/host/context.js +24 -0
- package/dist/host/defaults.d.ts +21 -0
- package/dist/host/defaults.js +47 -0
- package/dist/host/messages.d.ts +26 -0
- package/dist/host/messages.js +47 -0
- package/dist/host/types.d.ts +75 -0
- package/dist/host/types.js +13 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +51 -0
- package/dist/markdown/annotate-sentinels.d.ts +42 -0
- package/dist/markdown/annotate-sentinels.js +86 -0
- package/dist/markdown/parse.d.ts +56 -0
- package/dist/markdown/parse.js +516 -0
- package/dist/markdown/stringify.d.ts +7 -0
- package/dist/markdown/stringify.js +439 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/LICENSE +21 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/README.md +41 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.d.ts +72 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/from-markdown.js +127 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.d.ts +8 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/index.js +6 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.d.ts +17 -0
- package/dist/markdown/vendor/mdast-util-wiki-link/to-markdown.js +33 -0
- package/dist/markdown.d.ts +28 -0
- package/dist/markdown.js +44 -0
- package/dist/mathjax-config.d.ts +89 -0
- package/dist/mathjax-config.js +133 -0
- package/dist/nodes.d.ts +34 -0
- package/dist/nodes.js +34 -0
- package/dist/stores/correctionStore.d.ts +15 -0
- package/dist/stores/correctionStore.js +12 -0
- package/dist/stores/editorStore.d.ts +16 -0
- package/dist/stores/editorStore.js +27 -0
- package/dist/styles.css +2477 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.js +91 -0
- package/dist/utils/file-types.d.ts +10 -0
- package/dist/utils/file-types.js +46 -0
- package/docs/annotations.md +134 -0
- package/docs/decisions/README.md +72 -0
- package/docs/decisions/adr-002.md +27 -0
- package/docs/decisions/adr-007.md +27 -0
- package/docs/decisions/adr-010.md +26 -0
- package/docs/decisions/adr-024.md +124 -0
- package/docs/decisions/adr-024b.md +0 -0
- package/docs/decisions/adr-025.md +103 -0
- package/docs/decisions/adr-027.md +66 -0
- package/docs/decisions/adr-057.md +67 -0
- package/docs/decisions/adr-070.md +131 -0
- package/docs/decisions/adr-075.md +410 -0
- package/docs/decisions/adr-076.md +183 -0
- package/docs/decisions/adr-077.md +410 -0
- package/docs/decisions/adr-078.md +243 -0
- package/docs/decisions/adr-079.md +167 -0
- package/docs/decisions/adr-080.md +325 -0
- package/docs/decisions/adr-081.md +108 -0
- package/docs/decisions/adr-082.md +123 -0
- package/docs/decisions/adr-083.md +204 -0
- package/docs/decisions/adr-084.md +179 -0
- package/docs/editor-api.md +106 -0
- package/docs/markdown-pipeline.md +282 -0
- package/docs/provenance.md +106 -0
- package/docs/zusammen-editor-capability-map.md +232 -0
- package/package.json +175 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState, useRef } from 'react';
|
|
3
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
4
|
+
import { $getRoot, $getNodeByKey, $isElementNode } from 'lexical';
|
|
5
|
+
import { $isCodeNode } from '@lexical/code';
|
|
6
|
+
import { $isFrontmatterNode } from './nodes/index.js';
|
|
7
|
+
// Shared helper to recursively find all code nodes in a tree
|
|
8
|
+
// Excludes FrontmatterNode since it shouldn't show the language selector
|
|
9
|
+
function findCodeNodesInTree(node) {
|
|
10
|
+
const codeNodes = [];
|
|
11
|
+
if ($isCodeNode(node) && !$isFrontmatterNode(node)) {
|
|
12
|
+
codeNodes.push(node);
|
|
13
|
+
}
|
|
14
|
+
if ($isElementNode(node)) {
|
|
15
|
+
for (const child of node.getChildren()) {
|
|
16
|
+
codeNodes.push(...findCodeNodesInTree(child));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return codeNodes;
|
|
20
|
+
}
|
|
21
|
+
// Popular languages for the dropdown
|
|
22
|
+
// Note: 'plain' is a valid Prism language that provides no syntax highlighting
|
|
23
|
+
const LANGUAGES = [
|
|
24
|
+
{ value: 'plain', label: 'Plain Text' },
|
|
25
|
+
{ value: 'javascript', label: 'JavaScript' },
|
|
26
|
+
{ value: 'typescript', label: 'TypeScript' },
|
|
27
|
+
{ value: 'jsx', label: 'JSX' },
|
|
28
|
+
{ value: 'tsx', label: 'TSX' },
|
|
29
|
+
{ value: 'python', label: 'Python' },
|
|
30
|
+
{ value: 'java', label: 'Java' },
|
|
31
|
+
{ value: 'c', label: 'C' },
|
|
32
|
+
{ value: 'cpp', label: 'C++' },
|
|
33
|
+
{ value: 'csharp', label: 'C#' },
|
|
34
|
+
{ value: 'go', label: 'Go' },
|
|
35
|
+
{ value: 'rust', label: 'Rust' },
|
|
36
|
+
{ value: 'ruby', label: 'Ruby' },
|
|
37
|
+
{ value: 'php', label: 'PHP' },
|
|
38
|
+
{ value: 'swift', label: 'Swift' },
|
|
39
|
+
{ value: 'kotlin', label: 'Kotlin' },
|
|
40
|
+
{ value: 'markup', label: 'HTML' },
|
|
41
|
+
{ value: 'css', label: 'CSS' },
|
|
42
|
+
{ value: 'scss', label: 'SCSS' },
|
|
43
|
+
{ value: 'json', label: 'JSON' },
|
|
44
|
+
{ value: 'yaml', label: 'YAML' },
|
|
45
|
+
{ value: 'markdown', label: 'Markdown' },
|
|
46
|
+
{ value: 'sql', label: 'SQL' },
|
|
47
|
+
{ value: 'bash', label: 'Bash' },
|
|
48
|
+
{ value: 'powershell', label: 'PowerShell' },
|
|
49
|
+
{ value: 'docker', label: 'Dockerfile' },
|
|
50
|
+
{ value: 'graphql', label: 'GraphQL' },
|
|
51
|
+
];
|
|
52
|
+
function LanguageSelector({ nodeKey, currentLanguage, position, onClose }) {
|
|
53
|
+
const [editor] = useLexicalComposerContext();
|
|
54
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
55
|
+
const menuRef = useRef(null);
|
|
56
|
+
const inputRef = useRef(null);
|
|
57
|
+
const filteredLanguages = LANGUAGES.filter((lang) => lang.label.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
58
|
+
lang.value.toLowerCase().includes(searchQuery.toLowerCase()));
|
|
59
|
+
// Focus input on mount
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
inputRef.current?.focus();
|
|
62
|
+
}, []);
|
|
63
|
+
// Close on click outside
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const handleClickOutside = (e) => {
|
|
66
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
67
|
+
onClose();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
71
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
72
|
+
}, [onClose]);
|
|
73
|
+
const selectLanguage = useCallback((language) => {
|
|
74
|
+
// Set the language and wait for the update to complete before closing
|
|
75
|
+
editor.update(() => {
|
|
76
|
+
const node = $getNodeByKey(nodeKey);
|
|
77
|
+
if (node && $isCodeNode(node)) {
|
|
78
|
+
// Use 'plain' for Plain Text - it's a valid Prism language with no highlighting
|
|
79
|
+
// This avoids Lexical's transform that resets undefined/null languages to 'javascript'
|
|
80
|
+
node.setLanguage(language);
|
|
81
|
+
}
|
|
82
|
+
}, {
|
|
83
|
+
onUpdate: () => {
|
|
84
|
+
// Close the selector after the update is committed
|
|
85
|
+
onClose();
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}, [editor, nodeKey, onClose]);
|
|
89
|
+
const handleKeyDown = useCallback((e) => {
|
|
90
|
+
if (e.key === 'Escape') {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
onClose();
|
|
93
|
+
}
|
|
94
|
+
}, [onClose]);
|
|
95
|
+
const handleItemClick = useCallback((e, language) => {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
selectLanguage(language);
|
|
99
|
+
}, [selectLanguage]);
|
|
100
|
+
// Normalize for comparison
|
|
101
|
+
const normalizedCurrent = currentLanguage || 'plain';
|
|
102
|
+
return (_jsxs("div", { ref: menuRef, className: "code-language-menu", style: {
|
|
103
|
+
position: 'fixed',
|
|
104
|
+
top: position.top,
|
|
105
|
+
right: position.right,
|
|
106
|
+
}, children: [_jsx("input", { ref: inputRef, type: "text", className: "code-language-search", placeholder: "Search languages...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onKeyDown: handleKeyDown }), _jsx("div", { className: "code-language-list", children: filteredLanguages.length === 0 ? (_jsx("div", { className: "code-language-empty", children: "No languages found" })) : (filteredLanguages.map((lang) => {
|
|
107
|
+
const isCurrentLang = lang.value === normalizedCurrent;
|
|
108
|
+
return (_jsxs("div", { className: `code-language-item ${isCurrentLang ? 'current' : ''}`, onMouseDown: (e) => handleItemClick(e, lang.value), children: [lang.label, isCurrentLang && (_jsx("span", { className: "code-language-check", children: "\u2713" }))] }, lang.value || 'plain-text'));
|
|
109
|
+
})) })] }));
|
|
110
|
+
}
|
|
111
|
+
export function CodeBlockPlugin() {
|
|
112
|
+
const [editor] = useLexicalComposerContext();
|
|
113
|
+
const [codeBlocks, setCodeBlocks] = useState([]);
|
|
114
|
+
const [selectorState, setSelectorState] = useState(null);
|
|
115
|
+
const updateScheduledRef = useRef(false);
|
|
116
|
+
// Track code blocks and their positions
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
// Batched update function that reads all DOM positions in a single frame
|
|
119
|
+
const updateCodeBlocks = () => {
|
|
120
|
+
updateScheduledRef.current = false;
|
|
121
|
+
const editorElement = editor.getRootElement();
|
|
122
|
+
if (!editorElement)
|
|
123
|
+
return;
|
|
124
|
+
editor.getEditorState().read(() => {
|
|
125
|
+
const root = $getRoot();
|
|
126
|
+
const codeNodes = findCodeNodesInTree(root);
|
|
127
|
+
// Collect node keys, languages, and text content first (no DOM reads)
|
|
128
|
+
const nodeData = [];
|
|
129
|
+
for (const node of codeNodes) {
|
|
130
|
+
nodeData.push({
|
|
131
|
+
nodeKey: node.getKey(),
|
|
132
|
+
language: node.getLanguage() || '',
|
|
133
|
+
textContent: node.getTextContent(),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Batch DOM reads in a single pass after the read()
|
|
137
|
+
requestAnimationFrame(() => {
|
|
138
|
+
const blocks = [];
|
|
139
|
+
for (const { nodeKey, language, textContent } of nodeData) {
|
|
140
|
+
const element = editor.getElementByKey(nodeKey);
|
|
141
|
+
if (element) {
|
|
142
|
+
blocks.push({
|
|
143
|
+
nodeKey,
|
|
144
|
+
language,
|
|
145
|
+
textContent,
|
|
146
|
+
rect: element.getBoundingClientRect(),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
setCodeBlocks(blocks);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
// Debounced update scheduler
|
|
155
|
+
const scheduleUpdate = () => {
|
|
156
|
+
if (!updateScheduledRef.current) {
|
|
157
|
+
updateScheduledRef.current = true;
|
|
158
|
+
requestAnimationFrame(updateCodeBlocks);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const timeoutId = setTimeout(updateCodeBlocks, 200);
|
|
162
|
+
const unsubscribe = editor.registerUpdateListener(() => {
|
|
163
|
+
scheduleUpdate();
|
|
164
|
+
});
|
|
165
|
+
// Also update on scroll/resize with debouncing
|
|
166
|
+
const handleScrollResize = () => {
|
|
167
|
+
scheduleUpdate();
|
|
168
|
+
};
|
|
169
|
+
window.addEventListener('scroll', handleScrollResize, true);
|
|
170
|
+
window.addEventListener('resize', handleScrollResize);
|
|
171
|
+
return () => {
|
|
172
|
+
clearTimeout(timeoutId);
|
|
173
|
+
unsubscribe();
|
|
174
|
+
window.removeEventListener('scroll', handleScrollResize, true);
|
|
175
|
+
window.removeEventListener('resize', handleScrollResize);
|
|
176
|
+
};
|
|
177
|
+
}, [editor]);
|
|
178
|
+
const handleOpenSelector = useCallback((nodeKey, language, buttonRect) => {
|
|
179
|
+
setSelectorState({
|
|
180
|
+
nodeKey,
|
|
181
|
+
language,
|
|
182
|
+
position: {
|
|
183
|
+
top: buttonRect.bottom + 4,
|
|
184
|
+
right: window.innerWidth - buttonRect.right,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}, []);
|
|
188
|
+
const handleCloseSelector = useCallback(() => {
|
|
189
|
+
setSelectorState(null);
|
|
190
|
+
}, []);
|
|
191
|
+
// Track which code blocks show "Copied" feedback
|
|
192
|
+
const [copiedNodeKey, setCopiedNodeKey] = useState(null);
|
|
193
|
+
const handleCopyCode = useCallback((nodeKey, textContent) => {
|
|
194
|
+
void navigator.clipboard.writeText(textContent).then(() => {
|
|
195
|
+
setCopiedNodeKey(nodeKey);
|
|
196
|
+
setTimeout(() => setCopiedNodeKey(null), 1500);
|
|
197
|
+
});
|
|
198
|
+
}, []);
|
|
199
|
+
return (_jsxs(_Fragment, { children: [codeBlocks.map((block) => {
|
|
200
|
+
// Normalize empty/null/undefined to 'plain' for comparison
|
|
201
|
+
const normalizedLang = block.language || 'plain';
|
|
202
|
+
const langLabel = LANGUAGES.find((l) => l.value === normalizedLang)?.label || normalizedLang;
|
|
203
|
+
const isCopied = copiedNodeKey === block.nodeKey;
|
|
204
|
+
return (_jsxs("div", { className: "code-block-toolbar", style: {
|
|
205
|
+
position: 'fixed',
|
|
206
|
+
top: block.rect.top + 6,
|
|
207
|
+
right: window.innerWidth - block.rect.right + 8,
|
|
208
|
+
}, children: [_jsx("button", { className: "code-language-button", onClick: (e) => {
|
|
209
|
+
e.preventDefault();
|
|
210
|
+
e.stopPropagation();
|
|
211
|
+
const rect = e.target.getBoundingClientRect();
|
|
212
|
+
handleOpenSelector(block.nodeKey, block.language, rect);
|
|
213
|
+
}, children: langLabel }), _jsx("button", { className: `code-copy-button ${isCopied ? 'copied' : ''}`, onClick: (e) => {
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
e.stopPropagation();
|
|
216
|
+
handleCopyCode(block.nodeKey, block.textContent);
|
|
217
|
+
}, title: "Copy code", children: isCopied ? (_jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) })) : (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), _jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] })) })] }, block.nodeKey));
|
|
218
|
+
}), selectorState && (_jsx(LanguageSelector, { nodeKey: selectorState.nodeKey, currentLanguage: selectorState.language, position: selectorState.position, onClose: handleCloseSelector }))] }));
|
|
219
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CorrectionPanelPlugin — floating correction panel inside the Lexical editor.
|
|
3
|
+
*
|
|
4
|
+
* Opens when useCorrectionStore.isOpen is true. Loads suggestions from three
|
|
5
|
+
* sources progressively: the host's corrections store (immediate), then entity
|
|
6
|
+
* and passage suggestions from the host's knowledge services (async).
|
|
7
|
+
*
|
|
8
|
+
* On confirm, merges a same_as entry into the corrections document and asks the
|
|
9
|
+
* host to apply it. All persistence and knowledge-graph access crosses the
|
|
10
|
+
* package seam through `CorrectionHostServices` (see ADR-075); when the host
|
|
11
|
+
* supplies no correction services the panel still opens, shows no suggestions,
|
|
12
|
+
* and confirming performs only the in-document replacement.
|
|
13
|
+
*/
|
|
14
|
+
export declare function CorrectionPanelPlugin(): JSX.Element | null;
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* CorrectionPanelPlugin — floating correction panel inside the Lexical editor.
|
|
4
|
+
*
|
|
5
|
+
* Opens when useCorrectionStore.isOpen is true. Loads suggestions from three
|
|
6
|
+
* sources progressively: the host's corrections store (immediate), then entity
|
|
7
|
+
* and passage suggestions from the host's knowledge services (async).
|
|
8
|
+
*
|
|
9
|
+
* On confirm, merges a same_as entry into the corrections document and asks the
|
|
10
|
+
* host to apply it. All persistence and knowledge-graph access crosses the
|
|
11
|
+
* package seam through `CorrectionHostServices` (see ADR-075); when the host
|
|
12
|
+
* supplies no correction services the panel still opens, shows no suggestions,
|
|
13
|
+
* and confirming performs only the in-document replacement.
|
|
14
|
+
*/
|
|
15
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
16
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
17
|
+
import { $getRoot, $isTextNode } from 'lexical';
|
|
18
|
+
import { useCorrectionStore } from '../../stores/correctionStore.js';
|
|
19
|
+
import { useEditorHost } from '../../host/context.js';
|
|
20
|
+
import { parseCorrectionsYaml, serializeCorrectionsYaml, mergeCorrection, isExistingCanonical, } from './correction-yaml.js';
|
|
21
|
+
// --- Style helpers ---
|
|
22
|
+
const isDark = () => typeof document !== 'undefined' && document.documentElement.classList.contains('dark');
|
|
23
|
+
const panelStyle = () => {
|
|
24
|
+
const dark = isDark();
|
|
25
|
+
return {
|
|
26
|
+
position: 'fixed',
|
|
27
|
+
zIndex: 10001,
|
|
28
|
+
borderRadius: '8px',
|
|
29
|
+
padding: '12px',
|
|
30
|
+
minWidth: '280px',
|
|
31
|
+
maxWidth: '360px',
|
|
32
|
+
background: `var(--vscode-menu-background, ${dark ? '#252526' : '#ffffff'})`,
|
|
33
|
+
border: `1px solid var(--vscode-menu-border, ${dark ? '#454545' : '#d4d4d4'})`,
|
|
34
|
+
boxShadow: dark ? '0 4px 16px rgba(0,0,0,0.4)' : '0 4px 16px rgba(0,0,0,0.15)',
|
|
35
|
+
color: `var(--vscode-menu-foreground, ${dark ? '#cccccc' : '#333333'})`,
|
|
36
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
37
|
+
fontSize: '13px',
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
// --- Main component ---
|
|
41
|
+
export function CorrectionPanelPlugin() {
|
|
42
|
+
const [editor] = useLexicalComposerContext();
|
|
43
|
+
const { corrections, notifyError } = useEditorHost();
|
|
44
|
+
const { isOpen, position, selectedText } = useCorrectionStore();
|
|
45
|
+
const panelRef = useRef(null);
|
|
46
|
+
const yamlEntriesRef = useRef([]);
|
|
47
|
+
const [yamlSuggestions, setYamlSuggestions] = useState([]);
|
|
48
|
+
const [entitySuggestions, setEntitySuggestions] = useState([]);
|
|
49
|
+
const [passageSuggestions, setPassageSuggestions] = useState([]);
|
|
50
|
+
const [loadingEntities, setLoadingEntities] = useState(false);
|
|
51
|
+
const [loadingPassages, setLoadingPassages] = useState(false);
|
|
52
|
+
const [canonicalInput, setCanonicalInput] = useState('');
|
|
53
|
+
const [replaceAll, setReplaceAll] = useState(false);
|
|
54
|
+
const [confirmPhase, setConfirmPhase] = useState('idle');
|
|
55
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
56
|
+
const close = useCallback(() => {
|
|
57
|
+
useCorrectionStore.getState().close();
|
|
58
|
+
}, []);
|
|
59
|
+
// Reset state when panel opens
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!isOpen)
|
|
62
|
+
return;
|
|
63
|
+
setYamlSuggestions([]);
|
|
64
|
+
setEntitySuggestions([]);
|
|
65
|
+
setPassageSuggestions([]);
|
|
66
|
+
setCanonicalInput('');
|
|
67
|
+
setReplaceAll(false);
|
|
68
|
+
setConfirmPhase('idle');
|
|
69
|
+
setIsSubmitting(false);
|
|
70
|
+
yamlEntriesRef.current = [];
|
|
71
|
+
}, [isOpen]);
|
|
72
|
+
// Outside-click and Escape dismissal
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!isOpen)
|
|
75
|
+
return;
|
|
76
|
+
const handleMousedown = (e) => {
|
|
77
|
+
if (panelRef.current && !panelRef.current.contains(e.target)) {
|
|
78
|
+
close();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const handleKey = (e) => {
|
|
82
|
+
if (e.key === 'Escape')
|
|
83
|
+
close();
|
|
84
|
+
};
|
|
85
|
+
document.addEventListener('mousedown', handleMousedown);
|
|
86
|
+
document.addEventListener('keydown', handleKey);
|
|
87
|
+
return () => {
|
|
88
|
+
document.removeEventListener('mousedown', handleMousedown);
|
|
89
|
+
document.removeEventListener('keydown', handleKey);
|
|
90
|
+
};
|
|
91
|
+
}, [isOpen, close]);
|
|
92
|
+
// Load suggestions when panel opens
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!isOpen || !selectedText || !corrections)
|
|
95
|
+
return;
|
|
96
|
+
let cancelled = false;
|
|
97
|
+
// Load YAML suggestions synchronously after file read
|
|
98
|
+
const loadYaml = async () => {
|
|
99
|
+
try {
|
|
100
|
+
const raw = await corrections.readCorrections();
|
|
101
|
+
if (cancelled)
|
|
102
|
+
return;
|
|
103
|
+
const entries = parseCorrectionsYaml(raw);
|
|
104
|
+
yamlEntriesRef.current = entries;
|
|
105
|
+
const matches = entries
|
|
106
|
+
.filter((e) => e.type === 'same_as' && e.aliases.includes(selectedText))
|
|
107
|
+
.map((e) => e.canonical);
|
|
108
|
+
setYamlSuggestions(matches);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// File may not exist yet — that's fine
|
|
112
|
+
yamlEntriesRef.current = [];
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
// Load entity suggestions via MCP
|
|
116
|
+
const loadEntities = async () => {
|
|
117
|
+
setLoadingEntities(true);
|
|
118
|
+
try {
|
|
119
|
+
const names = await corrections.suggestEntities(selectedText, 5);
|
|
120
|
+
if (cancelled)
|
|
121
|
+
return;
|
|
122
|
+
setEntitySuggestions(names);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Knowledge graph unavailable — silent, not required for panel function
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
if (!cancelled)
|
|
129
|
+
setLoadingEntities(false);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
// Load passage suggestions via MCP
|
|
133
|
+
const loadPassages = async () => {
|
|
134
|
+
setLoadingPassages(true);
|
|
135
|
+
try {
|
|
136
|
+
const names = await corrections.suggestPassages(selectedText, 5, 0.3);
|
|
137
|
+
if (cancelled)
|
|
138
|
+
return;
|
|
139
|
+
setPassageSuggestions(names);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Knowledge graph unavailable — silent
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
if (!cancelled)
|
|
146
|
+
setLoadingPassages(false);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
// Hosts back these services with an MCP stdio transport, which multiplexes
|
|
150
|
+
// nothing: a second concurrent call to the same server is rejected rather
|
|
151
|
+
// than queued. So serialize the two knowledge suggestion calls, while
|
|
152
|
+
// keeping the corrections read concurrent (it is a different service).
|
|
153
|
+
// The host relies on this ordering — do not parallelise.
|
|
154
|
+
const loadKnowledgeSuggestions = async () => {
|
|
155
|
+
await loadEntities();
|
|
156
|
+
if (cancelled)
|
|
157
|
+
return;
|
|
158
|
+
await loadPassages();
|
|
159
|
+
};
|
|
160
|
+
void loadYaml();
|
|
161
|
+
void loadKnowledgeSuggestions();
|
|
162
|
+
return () => {
|
|
163
|
+
cancelled = true;
|
|
164
|
+
};
|
|
165
|
+
}, [isOpen, selectedText, corrections]);
|
|
166
|
+
// Deduplicate suggestions across sources (case-insensitive), preserving order
|
|
167
|
+
const allSuggestions = useMemo(() => {
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
const out = [];
|
|
170
|
+
for (const name of [...yamlSuggestions, ...entitySuggestions, ...passageSuggestions]) {
|
|
171
|
+
const key = name.toLowerCase();
|
|
172
|
+
if (!seen.has(key)) {
|
|
173
|
+
seen.add(key);
|
|
174
|
+
out.push(name);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}, [yamlSuggestions, entitySuggestions, passageSuggestions]);
|
|
179
|
+
const handleConfirm = useCallback(async () => {
|
|
180
|
+
const canonical = canonicalInput.trim();
|
|
181
|
+
if (!canonical)
|
|
182
|
+
return;
|
|
183
|
+
// R13: warn if selectedText is an existing canonical name
|
|
184
|
+
if (confirmPhase === 'idle' && isExistingCanonical(yamlEntriesRef.current, selectedText)) {
|
|
185
|
+
setConfirmPhase('needs-canonical-warning');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
setIsSubmitting(true);
|
|
189
|
+
try {
|
|
190
|
+
// (a) Replace all occurrences in the document if requested
|
|
191
|
+
if (replaceAll) {
|
|
192
|
+
editor.update(() => {
|
|
193
|
+
// Escape selectedText for use in a regex, then add word boundaries so
|
|
194
|
+
// "Tito" matches "Tito" and "Tito's" but not "Otito" or "titanium".
|
|
195
|
+
const escaped = selectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
196
|
+
const regex = new RegExp('\\b' + escaped + '\\b', 'g');
|
|
197
|
+
const walk = (node) => {
|
|
198
|
+
if ($isTextNode(node)) {
|
|
199
|
+
// Skip text inside code blocks — don't replace identifiers
|
|
200
|
+
const parentType = node.getParent()?.getType() ?? '';
|
|
201
|
+
if (parentType === 'code' || parentType === 'code-highlight')
|
|
202
|
+
return;
|
|
203
|
+
const content = node.getTextContent();
|
|
204
|
+
const replaced = content.replace(regex, canonical);
|
|
205
|
+
if (replaced !== content)
|
|
206
|
+
node.setTextContent(replaced);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if ('getChildren' in node && typeof node.getChildren === 'function') {
|
|
210
|
+
for (const child of node.getChildren()) {
|
|
211
|
+
walk(child);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
walk($getRoot());
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// (b) No host persistence available — the in-document replacement above is
|
|
219
|
+
// all this package can do on its own.
|
|
220
|
+
if (!corrections) {
|
|
221
|
+
setIsSubmitting(false);
|
|
222
|
+
close();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// (c) Re-read, merge, and write atomically — avoids overwriting corrections
|
|
226
|
+
// added by other sources while the panel was open. The host owns mkdir and
|
|
227
|
+
// atomic-write semantics.
|
|
228
|
+
let latestEntries = yamlEntriesRef.current;
|
|
229
|
+
try {
|
|
230
|
+
const fresh = await corrections.readCorrections();
|
|
231
|
+
latestEntries = parseCorrectionsYaml(fresh);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// File missing or unreadable — start from empty (yamlEntriesRef is still the best fallback)
|
|
235
|
+
}
|
|
236
|
+
const updated = mergeCorrection(latestEntries, selectedText, canonical);
|
|
237
|
+
const yaml = serializeCorrectionsYaml(updated);
|
|
238
|
+
await corrections.writeCorrections(yaml);
|
|
239
|
+
yamlEntriesRef.current = updated;
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
notifyError('Failed to save correction', err instanceof Error ? err.message : String(err));
|
|
243
|
+
setIsSubmitting(false);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// (d) Apply corrections — isolated so a KG failure never blocks panel close
|
|
247
|
+
try {
|
|
248
|
+
const applied = await corrections.applyCorrections();
|
|
249
|
+
if (!applied) {
|
|
250
|
+
notifyError('Knowledge graph sync failed', 'The correction was saved. It will be applied on the next ingestion pass.');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
notifyError('Knowledge graph sync failed', 'The correction was saved. It will be applied on the next ingestion pass.');
|
|
255
|
+
}
|
|
256
|
+
// (f) Close panel — reached only after successful YAML write
|
|
257
|
+
setIsSubmitting(false);
|
|
258
|
+
close();
|
|
259
|
+
}, [canonicalInput, confirmPhase, replaceAll, selectedText, editor, close, corrections, notifyError]);
|
|
260
|
+
if (!isOpen)
|
|
261
|
+
return null;
|
|
262
|
+
const dark = isDark();
|
|
263
|
+
const borderColor = `var(--vscode-menu-border, ${dark ? '#454545' : '#d4d4d4'})`;
|
|
264
|
+
const hoverBg = `var(--vscode-menu-selectionBackground, ${dark ? '#094771' : '#e8e8e8'})`;
|
|
265
|
+
const mutedColor = dark ? '#888888' : '#999999';
|
|
266
|
+
const inputBg = dark ? '#1e1e1e' : '#f5f5f5';
|
|
267
|
+
const warningBg = dark ? '#4a3500' : '#fff8e1';
|
|
268
|
+
const warningBorder = dark ? '#8a6800' : '#f0c000';
|
|
269
|
+
const btnBase = {
|
|
270
|
+
padding: '4px 12px',
|
|
271
|
+
borderRadius: '4px',
|
|
272
|
+
fontSize: '12px',
|
|
273
|
+
cursor: 'pointer',
|
|
274
|
+
border: `1px solid ${borderColor}`,
|
|
275
|
+
background: 'transparent',
|
|
276
|
+
color: `var(--vscode-menu-foreground, ${dark ? '#cccccc' : '#333333'})`,
|
|
277
|
+
};
|
|
278
|
+
const btnPrimary = {
|
|
279
|
+
...btnBase,
|
|
280
|
+
background: `var(--vscode-button-background, ${dark ? '#0e639c' : '#007acc'})`,
|
|
281
|
+
color: `var(--vscode-button-foreground, #ffffff)`,
|
|
282
|
+
border: 'none',
|
|
283
|
+
opacity: isSubmitting ? 0.6 : 1,
|
|
284
|
+
};
|
|
285
|
+
return (_jsxs("div", { ref: panelRef, style: {
|
|
286
|
+
...panelStyle(),
|
|
287
|
+
left: Math.min(position.x, window.innerWidth - 380),
|
|
288
|
+
top: Math.min(position.y, window.innerHeight - 450),
|
|
289
|
+
}, children: [_jsxs("div", { style: { marginBottom: '8px' }, children: [_jsx("span", { style: { color: mutedColor, fontSize: '11px', textTransform: 'uppercase', letterSpacing: '0.05em' }, children: "Correcting" }), _jsx("div", { style: { fontWeight: 600, marginTop: '2px', wordBreak: 'break-word' }, children: selectedText })] }), _jsx("hr", { style: { margin: '8px 0', border: 'none', borderTop: `1px solid ${borderColor}` } }), (allSuggestions.length > 0 || loadingEntities || loadingPassages) && (_jsxs("div", { style: { marginBottom: '8px' }, children: [_jsx("div", { style: { color: mutedColor, fontSize: '11px', marginBottom: '4px' }, children: "Suggestions" }), yamlSuggestions.length > 0 && (_jsx("div", { children: yamlSuggestions.map((name) => (_jsx(SuggestionItem, { name: name, hoverBg: hoverBg, onClick: () => setCanonicalInput(name) }, `yaml-${name}`))) })), loadingEntities ? (_jsx("div", { style: { color: mutedColor, fontSize: '12px', padding: '4px 0' }, children: "Loading entity matches\u2026" })) : entitySuggestions.length > 0 ? (_jsx("div", { children: entitySuggestions
|
|
290
|
+
.filter((n) => !yamlSuggestions.some((y) => y.toLowerCase() === n.toLowerCase()))
|
|
291
|
+
.map((name) => (_jsx(SuggestionItem, { name: name, hoverBg: hoverBg, onClick: () => setCanonicalInput(name) }, `entity-${name}`))) })) : null, loadingPassages ? (_jsx("div", { style: { color: mutedColor, fontSize: '12px', padding: '4px 0' }, children: "Loading passage matches\u2026" })) : passageSuggestions.length > 0 ? (_jsx("div", { children: passageSuggestions
|
|
292
|
+
.filter((n) => !yamlSuggestions.some((y) => y.toLowerCase() === n.toLowerCase()) &&
|
|
293
|
+
!entitySuggestions.some((e) => e.toLowerCase() === n.toLowerCase()))
|
|
294
|
+
.map((name) => (_jsx(SuggestionItem, { name: name, hoverBg: hoverBg, onClick: () => setCanonicalInput(name) }, `passage-${name}`))) })) : null] })), _jsxs("div", { style: { marginBottom: '10px' }, children: [_jsx("label", { style: { display: 'block', color: mutedColor, fontSize: '11px', marginBottom: '4px' }, children: "Canonical name" }), _jsx("input", { type: "text", value: canonicalInput, onChange: (e) => setCanonicalInput(e.target.value), placeholder: "Enter correct name\u2026", style: {
|
|
295
|
+
width: '100%',
|
|
296
|
+
padding: '5px 8px',
|
|
297
|
+
borderRadius: '4px',
|
|
298
|
+
border: `1px solid ${borderColor}`,
|
|
299
|
+
background: inputBg,
|
|
300
|
+
color: `var(--vscode-menu-foreground, ${dark ? '#cccccc' : '#333333'})`,
|
|
301
|
+
fontSize: '13px',
|
|
302
|
+
boxSizing: 'border-box',
|
|
303
|
+
}, onKeyDown: (e) => {
|
|
304
|
+
if (e.key === 'Enter')
|
|
305
|
+
void handleConfirm();
|
|
306
|
+
} })] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '10px', cursor: 'pointer' }, children: [_jsx("input", { type: "checkbox", checked: replaceAll, onChange: (e) => setReplaceAll(e.target.checked) }), _jsx("span", { style: { fontSize: '12px' }, children: "Replace all occurrences in this document" })] }), confirmPhase === 'needs-canonical-warning' && (_jsx("div", { style: {
|
|
307
|
+
marginBottom: '10px',
|
|
308
|
+
padding: '8px',
|
|
309
|
+
borderRadius: '4px',
|
|
310
|
+
background: warningBg,
|
|
311
|
+
border: `1px solid ${warningBorder}`,
|
|
312
|
+
fontSize: '12px',
|
|
313
|
+
}, children: "This term is already a canonical name for other aliases. Proceeding will re-canonicalize it." })), _jsxs("div", { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' }, children: [_jsx("button", { style: btnBase, onClick: close, children: "Cancel" }), confirmPhase === 'needs-canonical-warning' ? (_jsx("button", { style: btnPrimary, onClick: handleConfirm, disabled: isSubmitting || !canonicalInput.trim(), children: "Proceed anyway" })) : (_jsx("button", { style: btnPrimary, onClick: handleConfirm, disabled: isSubmitting || !canonicalInput.trim(), children: isSubmitting ? 'Saving…' : 'Confirm' }))] })] }));
|
|
314
|
+
}
|
|
315
|
+
function SuggestionItem({ name, hoverBg, onClick, }) {
|
|
316
|
+
const dark = isDark();
|
|
317
|
+
return (_jsx("button", { onClick: onClick, style: {
|
|
318
|
+
display: 'block',
|
|
319
|
+
width: '100%',
|
|
320
|
+
padding: '4px 8px',
|
|
321
|
+
background: 'transparent',
|
|
322
|
+
border: 'none',
|
|
323
|
+
textAlign: 'left',
|
|
324
|
+
cursor: 'pointer',
|
|
325
|
+
fontSize: '13px',
|
|
326
|
+
borderRadius: '3px',
|
|
327
|
+
color: `var(--vscode-menu-foreground, ${dark ? '#cccccc' : '#333333'})`,
|
|
328
|
+
}, onMouseEnter: (e) => {
|
|
329
|
+
e.currentTarget.style.background = hoverBg;
|
|
330
|
+
}, onMouseLeave: (e) => {
|
|
331
|
+
e.currentTarget.style.background = 'transparent';
|
|
332
|
+
}, children: name }));
|
|
333
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function DragHandlePlugin(): import("react").JSX.Element | null;
|