@elabs-ai/components-editor 4.0.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/LICENSE +21 -0
- package/README.md +149 -0
- package/dist/chunk-LBC5VJBD.js +3748 -0
- package/dist/chunk-LBC5VJBD.js.map +1 -0
- package/dist/index.css +233 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +199 -0
- package/dist/index.js +362 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/monaco-environment.d.ts +2 -0
- package/dist/lib/monaco-environment.js +44 -0
- package/dist/lib/monaco-environment.js.map +1 -0
- package/dist/markdown/frontmatter.d.ts +11 -0
- package/dist/markdown/frontmatter.js +33 -0
- package/dist/markdown/frontmatter.js.map +1 -0
- package/dist/markdown/index.css +233 -0
- package/dist/markdown/index.css.map +1 -0
- package/dist/markdown/index.d.ts +1486 -0
- package/dist/markdown/index.js +4882 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/markdown/parse.d.ts +25 -0
- package/dist/markdown/parse.js +14 -0
- package/dist/markdown/parse.js.map +1 -0
- package/dist/markdown-editor-DfBZibAn.d.ts +702 -0
- package/package.json +101 -0
- package/src/ai-objects/ai-objects-integration.test.tsx +128 -0
- package/src/ai-objects/ai-objects.stories.tsx +233 -0
- package/src/ai-objects/ai-objects.test.tsx +205 -0
- package/src/ai-objects/decision-card.tsx +178 -0
- package/src/ai-objects/directives.ts +136 -0
- package/src/ai-objects/entity.tsx +181 -0
- package/src/ai-objects/index.ts +34 -0
- package/src/ai-objects/knowledge-card.tsx +143 -0
- package/src/calc-block/calc-block.stories.tsx +297 -0
- package/src/calc-block/calc-block.test.tsx +164 -0
- package/src/calc-block/calc-block.tsx +398 -0
- package/src/calc-block/calc-editor-monaco.ts +214 -0
- package/src/calc-block/calc-editor-prose.ts +128 -0
- package/src/calc-block/calc-editor.css +80 -0
- package/src/calc-block/calc-editor.stories.tsx +272 -0
- package/src/calc-block/calc-editor.test.ts +228 -0
- package/src/calc-block/calc-editor.ts +270 -0
- package/src/calc-block/calc-inline.stories.tsx +44 -0
- package/src/calc-block/calc-inline.test.tsx +44 -0
- package/src/calc-block/calc-inline.tsx +75 -0
- package/src/calc-block/index.ts +20 -0
- package/src/calc-block/types.ts +164 -0
- package/src/code-editor/code-editor.stories.tsx +218 -0
- package/src/code-editor/code-editor.test.tsx +134 -0
- package/src/code-editor/code-editor.tsx +250 -0
- package/src/code-editor/index.ts +6 -0
- package/src/code-workspace/code-workspace.stories.tsx +51 -0
- package/src/code-workspace/code-workspace.test.tsx +146 -0
- package/src/code-workspace/code-workspace.tsx +253 -0
- package/src/code-workspace/index.ts +6 -0
- package/src/copy-button/copy-button.tsx +51 -0
- package/src/copy-button/index.ts +1 -0
- package/src/css.d.ts +3 -0
- package/src/diff-editor/diff-editor.stories.tsx +44 -0
- package/src/diff-editor/diff-editor.test.tsx +68 -0
- package/src/diff-editor/diff-editor.tsx +151 -0
- package/src/diff-editor/index.ts +1 -0
- package/src/editor-context-menu/editor-context-menu.tsx +120 -0
- package/src/editor-context-menu/index.ts +1 -0
- package/src/editor-toolbar/editor-toolbar.tsx +83 -0
- package/src/editor-toolbar/index.ts +1 -0
- package/src/index.ts +48 -0
- package/src/lib/editor-completions-monaco.test.ts +206 -0
- package/src/lib/editor-completions-monaco.ts +127 -0
- package/src/lib/editor-completions.test.ts +156 -0
- package/src/lib/editor-completions.ts +153 -0
- package/src/lib/editor-content-access-prose.test.ts +411 -0
- package/src/lib/editor-content-access-prose.ts +172 -0
- package/src/lib/editor-content-access.test.ts +182 -0
- package/src/lib/editor-content-access.ts +109 -0
- package/src/lib/languages.ts +32 -0
- package/src/lib/markdown/diff.test.ts +80 -0
- package/src/lib/markdown/diff.ts +194 -0
- package/src/lib/markdown/directives.ts +342 -0
- package/src/lib/markdown/frontmatter.ts +50 -0
- package/src/lib/markdown/markdown-scale.test.ts +65 -0
- package/src/lib/markdown/markdown-scale.ts +62 -0
- package/src/lib/markdown/merge.test.ts +64 -0
- package/src/lib/markdown/merge.ts +158 -0
- package/src/lib/markdown/slugify.test.ts +156 -0
- package/src/lib/markdown/slugify.ts +47 -0
- package/src/lib/monaco-environment.ts +103 -0
- package/src/lib/monaco-theme-bridge.ts +293 -0
- package/src/lib/use-data-theme.ts +61 -0
- package/src/markdown/frontmatter.ts +14 -0
- package/src/markdown/index.ts +323 -0
- package/src/markdown/parse.test.ts +60 -0
- package/src/markdown/parse.ts +38 -0
- package/src/markdown-academic/citations.test.ts +77 -0
- package/src/markdown-academic/citations.tsx +442 -0
- package/src/markdown-academic/footnotes.tsx +235 -0
- package/src/markdown-academic/index.ts +32 -0
- package/src/markdown-academic/math.tsx +163 -0
- package/src/markdown-academic/toc.tsx +88 -0
- package/src/markdown-editor/completions/completions-menu.tsx +96 -0
- package/src/markdown-editor/completions/completions-prose.test.ts +356 -0
- package/src/markdown-editor/completions/completions-prose.ts +313 -0
- package/src/markdown-editor/completions/completions-widget.tsx +75 -0
- package/src/markdown-editor/completions/index.ts +37 -0
- package/src/markdown-editor/directive-nodes.ts +151 -0
- package/src/markdown-editor/directive-views.test.tsx +189 -0
- package/src/markdown-editor/directive-views.tsx +795 -0
- package/src/markdown-editor/exit-keymap.test.ts +88 -0
- package/src/markdown-editor/exit-keymap.ts +87 -0
- package/src/markdown-editor/index.ts +6 -0
- package/src/markdown-editor/markdown-editor.css +250 -0
- package/src/markdown-editor/markdown-editor.directives.test.tsx +105 -0
- package/src/markdown-editor/markdown-editor.fill.test.ts +34 -0
- package/src/markdown-editor/markdown-editor.paste-embed.test.tsx +297 -0
- package/src/markdown-editor/markdown-editor.stories.tsx +560 -0
- package/src/markdown-editor/markdown-editor.strictmode.test.tsx +38 -0
- package/src/markdown-editor/markdown-editor.table.test.tsx +287 -0
- package/src/markdown-editor/markdown-editor.tsx +560 -0
- package/src/markdown-editor/milkdown-react/editor.tsx +33 -0
- package/src/markdown-editor/milkdown-react/index.ts +8 -0
- package/src/markdown-editor/milkdown-react/types.ts +27 -0
- package/src/markdown-editor/milkdown-react/use-editor.ts +27 -0
- package/src/markdown-editor/milkdown-react/use-get-editor.ts +56 -0
- package/src/markdown-editor/milkdown-react/use-instance.ts +23 -0
- package/src/markdown-editor/paste-embed.ts +355 -0
- package/src/markdown-editor/slash/brand-slash-commands.test.ts +194 -0
- package/src/markdown-editor/slash/brand-slash-commands.ts +324 -0
- package/src/markdown-editor/slash/brand-slash-plugin.test.ts +254 -0
- package/src/markdown-editor/slash/brand-slash-plugin.ts +385 -0
- package/src/markdown-editor/slash/index.ts +107 -0
- package/src/markdown-editor/slash/insert-directive.test.ts +263 -0
- package/src/markdown-editor/slash/insert-directive.ts +345 -0
- package/src/markdown-editor/slash/monaco-slash-menu.test.tsx +198 -0
- package/src/markdown-editor/slash/monaco-slash-menu.tsx +356 -0
- package/src/markdown-editor/slash/shortcut-monaco.ts +69 -0
- package/src/markdown-editor/slash/shortcut.test.ts +157 -0
- package/src/markdown-editor/slash/shortcut.ts +57 -0
- package/src/markdown-editor/slash/slash-menu.stories.tsx +139 -0
- package/src/markdown-editor/slash/slash-menu.tsx +115 -0
- package/src/markdown-editor/slash/slash-scroll.test.tsx +52 -0
- package/src/markdown-editor/slash/slash-widget.tsx +97 -0
- package/src/markdown-editor/slash/source-slash-trigger.test.ts +42 -0
- package/src/markdown-editor/slash/source-slash-trigger.ts +46 -0
- package/src/markdown-editor/table-view.tsx +249 -0
- package/src/markdown-iteration/directive.tsx +92 -0
- package/src/markdown-iteration/edit-context.ts +56 -0
- package/src/markdown-iteration/index.ts +24 -0
- package/src/markdown-iteration/iteration-block.test.tsx +138 -0
- package/src/markdown-iteration/iteration-builder-dialog.stories.tsx +293 -0
- package/src/markdown-iteration/iteration-builder-dialog.tsx +312 -0
- package/src/markdown-iteration/iteration-builder.test.ts +252 -0
- package/src/markdown-iteration/iteration-builder.ts +303 -0
- package/src/markdown-iteration/iteration.test.ts +56 -0
- package/src/markdown-iteration/iteration.tsx +333 -0
- package/src/markdown-iteration/template-dialog.stories.tsx +63 -0
- package/src/markdown-iteration/template-dialog.test.tsx +104 -0
- package/src/markdown-iteration/template-dialog.tsx +116 -0
- package/src/markdown-outline/document-outline.stories.tsx +76 -0
- package/src/markdown-outline/document-outline.tsx +83 -0
- package/src/markdown-outline/index.ts +2 -0
- package/src/markdown-outline/markdown-outline.test.ts +49 -0
- package/src/markdown-outline/markdown-outline.ts +60 -0
- package/src/markdown-preview/code-fence.tsx +193 -0
- package/src/markdown-preview/index.ts +8 -0
- package/src/markdown-preview/markdown-preview-academic.stories.tsx +203 -0
- package/src/markdown-preview/markdown-preview-academic.test.tsx +257 -0
- package/src/markdown-preview/markdown-preview-iteration-calc.test.tsx +71 -0
- package/src/markdown-preview/markdown-preview-iteration-nested.test.tsx +58 -0
- package/src/markdown-preview/markdown-preview-iteration.stories.tsx +187 -0
- package/src/markdown-preview/markdown-preview-iteration.test.tsx +90 -0
- package/src/markdown-preview/markdown-preview-linking.test.tsx +61 -0
- package/src/markdown-preview/markdown-preview-transclusion.test.tsx +33 -0
- package/src/markdown-preview/markdown-preview.stories.tsx +108 -0
- package/src/markdown-preview/markdown-preview.test.tsx +411 -0
- package/src/markdown-preview/markdown-preview.tsx +1541 -0
- package/src/markdown-toolbar/index.ts +8 -0
- package/src/markdown-toolbar/markdown-commands.test.ts +84 -0
- package/src/markdown-toolbar/markdown-commands.ts +104 -0
- package/src/markdown-toolbar/markdown-toolbar.stories.tsx +56 -0
- package/src/markdown-toolbar/markdown-toolbar.tsx +263 -0
- package/src/markdown-workspace/focus-writing.test.ts +52 -0
- package/src/markdown-workspace/focus-writing.ts +38 -0
- package/src/markdown-workspace/index.ts +6 -0
- package/src/markdown-workspace/markdown-workspace.stories.tsx +688 -0
- package/src/markdown-workspace/markdown-workspace.test.tsx +326 -0
- package/src/markdown-workspace/markdown-workspace.tsx +787 -0
- package/src/mermaid-diagram/index.ts +1 -0
- package/src/mermaid-diagram/mermaid-diagram.stories.tsx +42 -0
- package/src/mermaid-diagram/mermaid-diagram.test.tsx +86 -0
- package/src/mermaid-diagram/mermaid-diagram.tsx +318 -0
- package/src/mermaid-diagram/mermaid-viewer.test.tsx +89 -0
- package/src/mermaid-diagram/mermaid-viewer.tsx +367 -0
- package/src/mermaid-diagram/real-parse.test.ts +42 -0
- package/src/mermaid-diagram/remediate.test.ts +60 -0
- package/src/mermaid-diagram/remediate.ts +124 -0
- package/src/mermaid-workspace/index.ts +1 -0
- package/src/mermaid-workspace/mermaid-workspace.stories.tsx +28 -0
- package/src/mermaid-workspace/mermaid-workspace.tsx +71 -0
- package/src/metric-block/index.ts +1 -0
- package/src/metric-block/metric-block.stories.tsx +40 -0
- package/src/metric-block/metric-block.tsx +14 -0
- package/src/monaco-workers.d.ts +10 -0
- package/src/prose/index.ts +14 -0
- package/src/prose/prose.stories.tsx +44 -0
- package/src/prose/prose.test.tsx +44 -0
- package/src/prose/prose.tsx +21 -0
- package/src/timeline/index.ts +18 -0
|
@@ -0,0 +1,4882 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./index.css";
|
|
3
|
+
import {
|
|
4
|
+
BRAND_SLASH_COMMANDS,
|
|
5
|
+
CALC_FENCE_SEED,
|
|
6
|
+
CodeEditor,
|
|
7
|
+
CopyButton,
|
|
8
|
+
DEFAULT_SLASH_SHORTCUT,
|
|
9
|
+
DEFAULT_TEMPLATE,
|
|
10
|
+
DocumentOutline,
|
|
11
|
+
ITERATION_LAYOUTS,
|
|
12
|
+
IterationBlock,
|
|
13
|
+
IterationEditContext,
|
|
14
|
+
MARKDOWN_HEADING_REM,
|
|
15
|
+
MARKDOWN_HEADING_TRACKING,
|
|
16
|
+
MARKDOWN_HEADING_WEIGHT,
|
|
17
|
+
MARKDOWN_MEASURE,
|
|
18
|
+
MarkdownEditor,
|
|
19
|
+
MetricBlock,
|
|
20
|
+
SlashMenu,
|
|
21
|
+
brandSlashViewPlugins,
|
|
22
|
+
builderValueFromParts,
|
|
23
|
+
calcDecorationSpecs,
|
|
24
|
+
calcInlaySpecs,
|
|
25
|
+
calcProsePlugins,
|
|
26
|
+
calcTokenClassName,
|
|
27
|
+
collectCompletions,
|
|
28
|
+
defaultInterpolate,
|
|
29
|
+
directivePartsFromValue,
|
|
30
|
+
emptyBuilderValue,
|
|
31
|
+
evaluateEmbedded,
|
|
32
|
+
filterSlashCommands,
|
|
33
|
+
findCalcFences,
|
|
34
|
+
groupSlashCommands,
|
|
35
|
+
identifierPrefix,
|
|
36
|
+
insertBasicBlock,
|
|
37
|
+
insertBrandDirective,
|
|
38
|
+
insertCalcFence,
|
|
39
|
+
markdownScaleVars,
|
|
40
|
+
monacoContentAccess,
|
|
41
|
+
parseAttributes,
|
|
42
|
+
parseFrontmatter,
|
|
43
|
+
parseIterationDirective,
|
|
44
|
+
parseMarkdownOutline,
|
|
45
|
+
proseMirrorContentAccess,
|
|
46
|
+
resolveCalcInsert,
|
|
47
|
+
resolveReplaceRange,
|
|
48
|
+
selectionWatchPlugin,
|
|
49
|
+
serializeFrontmatter,
|
|
50
|
+
serializeIterationDirective,
|
|
51
|
+
slashOptionId,
|
|
52
|
+
splitList,
|
|
53
|
+
staticMarkdownFromValue,
|
|
54
|
+
transposeIterationValue,
|
|
55
|
+
triggerQueryStart,
|
|
56
|
+
useMarkdownOutline
|
|
57
|
+
} from "../chunk-LBC5VJBD.js";
|
|
58
|
+
|
|
59
|
+
// src/lib/editor-completions-monaco.ts
|
|
60
|
+
import * as monaco from "monaco-editor";
|
|
61
|
+
var REGISTRY = /* @__PURE__ */ new Map();
|
|
62
|
+
var refCount = 0;
|
|
63
|
+
var registration = null;
|
|
64
|
+
function ensureRegistered() {
|
|
65
|
+
if (registration) return;
|
|
66
|
+
registration = monaco.languages.registerCompletionItemProvider("markdown", {
|
|
67
|
+
provideCompletionItems(model, position) {
|
|
68
|
+
const getProviders = REGISTRY.get(model);
|
|
69
|
+
if (!getProviders) return { suggestions: [] };
|
|
70
|
+
const providers = getProviders();
|
|
71
|
+
if (!providers || providers.length === 0) return { suggestions: [] };
|
|
72
|
+
const lineText = model.getLineContent(position.lineNumber);
|
|
73
|
+
const ctx = {
|
|
74
|
+
source: model.getValue(),
|
|
75
|
+
line: position.lineNumber,
|
|
76
|
+
column: position.column,
|
|
77
|
+
lineText
|
|
78
|
+
};
|
|
79
|
+
return collectCompletions(providers, ctx).then((matches) => ({
|
|
80
|
+
suggestions: matches.map(({ provider, item }) => ({
|
|
81
|
+
label: item.label,
|
|
82
|
+
kind: monaco.languages.CompletionItemKind.Text,
|
|
83
|
+
insertText: item.insertText,
|
|
84
|
+
detail: item.detail,
|
|
85
|
+
range: resolveReplaceRange(item, position, lineText, provider.triggerCharacters)
|
|
86
|
+
}))
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function attachCompletionsMonaco(editor2, getProviders) {
|
|
92
|
+
ensureRegistered();
|
|
93
|
+
refCount++;
|
|
94
|
+
let model = editor2.getModel();
|
|
95
|
+
if (model) REGISTRY.set(model, getProviders);
|
|
96
|
+
const contentSub = editor2.onDidChangeModelContent((e) => {
|
|
97
|
+
const providers = getProviders();
|
|
98
|
+
if (!providers || providers.length === 0) return;
|
|
99
|
+
for (const change of e.changes) {
|
|
100
|
+
if (change.text.length !== 1) continue;
|
|
101
|
+
if (providers.some((p) => p.triggerCharacters?.includes(change.text))) {
|
|
102
|
+
editor2.trigger("brand-completions", "editor.action.triggerSuggest", {});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const modelSub = editor2.onDidChangeModel(() => {
|
|
108
|
+
if (model) REGISTRY.delete(model);
|
|
109
|
+
model = editor2.getModel();
|
|
110
|
+
if (model) REGISTRY.set(model, getProviders);
|
|
111
|
+
});
|
|
112
|
+
return () => {
|
|
113
|
+
contentSub.dispose();
|
|
114
|
+
modelSub.dispose();
|
|
115
|
+
if (model) REGISTRY.delete(model);
|
|
116
|
+
refCount = Math.max(0, refCount - 1);
|
|
117
|
+
if (refCount === 0) {
|
|
118
|
+
registration?.dispose();
|
|
119
|
+
registration = null;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/markdown-editor/slash/monaco-slash-menu.tsx
|
|
125
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
126
|
+
import { useEffect, useRef, useState } from "react";
|
|
127
|
+
|
|
128
|
+
// src/markdown-toolbar/markdown-commands.ts
|
|
129
|
+
import * as monaco2 from "monaco-editor";
|
|
130
|
+
function wrapSelection(editor2, before, after = before, placeholder = "text") {
|
|
131
|
+
const model = editor2.getModel();
|
|
132
|
+
const selection = editor2.getSelection();
|
|
133
|
+
if (!model || !selection) return;
|
|
134
|
+
const selected = model.getValueInRange(selection) || placeholder;
|
|
135
|
+
editor2.executeEdits("markdown-toolbar", [
|
|
136
|
+
{ range: selection, text: `${before}${selected}${after}`, forceMoveMarkers: true }
|
|
137
|
+
]);
|
|
138
|
+
const startCol = selection.startColumn + before.length;
|
|
139
|
+
editor2.setSelection(
|
|
140
|
+
new monaco2.Selection(
|
|
141
|
+
selection.startLineNumber,
|
|
142
|
+
startCol,
|
|
143
|
+
selection.startLineNumber,
|
|
144
|
+
startCol + selected.length
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
editor2.focus();
|
|
148
|
+
}
|
|
149
|
+
function toggleLinePrefix(editor2, prefix) {
|
|
150
|
+
const model = editor2.getModel();
|
|
151
|
+
const selection = editor2.getSelection();
|
|
152
|
+
if (!model || !selection) return;
|
|
153
|
+
const edits = [];
|
|
154
|
+
const allPrefixed = (() => {
|
|
155
|
+
for (let line = selection.startLineNumber; line <= selection.endLineNumber; line++) {
|
|
156
|
+
if (!model.getLineContent(line).startsWith(prefix)) return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
})();
|
|
160
|
+
for (let line = selection.startLineNumber; line <= selection.endLineNumber; line++) {
|
|
161
|
+
const content = model.getLineContent(line);
|
|
162
|
+
if (allPrefixed) {
|
|
163
|
+
edits.push({
|
|
164
|
+
range: new monaco2.Range(line, 1, line, prefix.length + 1),
|
|
165
|
+
text: ""
|
|
166
|
+
});
|
|
167
|
+
} else if (!content.startsWith(prefix)) {
|
|
168
|
+
edits.push({ range: new monaco2.Range(line, 1, line, 1), text: prefix });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
editor2.executeEdits("markdown-toolbar", edits);
|
|
172
|
+
editor2.focus();
|
|
173
|
+
}
|
|
174
|
+
function insertLink(editor2) {
|
|
175
|
+
const model = editor2.getModel();
|
|
176
|
+
const selection = editor2.getSelection();
|
|
177
|
+
if (!model || !selection) return;
|
|
178
|
+
const label = model.getValueInRange(selection) || "label";
|
|
179
|
+
editor2.executeEdits("markdown-toolbar", [
|
|
180
|
+
{ range: selection, text: `[${label}](https://)`, forceMoveMarkers: true }
|
|
181
|
+
]);
|
|
182
|
+
editor2.focus();
|
|
183
|
+
}
|
|
184
|
+
function insertHorizontalRule(editor2) {
|
|
185
|
+
const selection = editor2.getSelection();
|
|
186
|
+
if (!selection) return;
|
|
187
|
+
const line = selection.endLineNumber;
|
|
188
|
+
const col = editor2.getModel()?.getLineMaxColumn(line) ?? 1;
|
|
189
|
+
editor2.executeEdits("markdown-toolbar", [
|
|
190
|
+
{ range: new monaco2.Range(line, col, line, col), text: `
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
`, forceMoveMarkers: true }
|
|
194
|
+
]);
|
|
195
|
+
editor2.focus();
|
|
196
|
+
}
|
|
197
|
+
function insertDirective(editor2, snippet) {
|
|
198
|
+
const selection = editor2.getSelection();
|
|
199
|
+
if (!selection) return;
|
|
200
|
+
const line = selection.endLineNumber;
|
|
201
|
+
const col = editor2.getModel()?.getLineMaxColumn(line) ?? 1;
|
|
202
|
+
editor2.executeEdits("markdown-toolbar", [
|
|
203
|
+
{
|
|
204
|
+
range: new monaco2.Range(line, col, line, col),
|
|
205
|
+
text: `
|
|
206
|
+
|
|
207
|
+
${snippet}
|
|
208
|
+
`,
|
|
209
|
+
forceMoveMarkers: true
|
|
210
|
+
}
|
|
211
|
+
]);
|
|
212
|
+
editor2.focus();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/markdown-editor/slash/monaco-slash-menu.tsx
|
|
216
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
217
|
+
var ID_PREFIX = "brand-monaco-slash";
|
|
218
|
+
var LISTBOX_ID = `${ID_PREFIX}-listbox`;
|
|
219
|
+
function getCaretCoords(editor2) {
|
|
220
|
+
const pos = editor2.getPosition();
|
|
221
|
+
if (!pos) return null;
|
|
222
|
+
const scrolled = editor2.getScrolledVisiblePosition(pos);
|
|
223
|
+
if (!scrolled) return null;
|
|
224
|
+
const domNode = editor2.getDomNode();
|
|
225
|
+
if (!domNode) return null;
|
|
226
|
+
const rect = domNode.getBoundingClientRect();
|
|
227
|
+
return {
|
|
228
|
+
top: rect.top + scrolled.top + (scrolled.height ?? 20),
|
|
229
|
+
left: rect.left + scrolled.left
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function MonacoSlashMenu({
|
|
233
|
+
editor: editor2,
|
|
234
|
+
commands,
|
|
235
|
+
open,
|
|
236
|
+
onOpenChange,
|
|
237
|
+
onInsert,
|
|
238
|
+
triggerRange,
|
|
239
|
+
className
|
|
240
|
+
}) {
|
|
241
|
+
const [query, setQuery] = useState("");
|
|
242
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
243
|
+
const [coords, setCoords] = useState(null);
|
|
244
|
+
const menuRef = useRef(null);
|
|
245
|
+
const commitSnippet = (snippet) => {
|
|
246
|
+
if (triggerRange) {
|
|
247
|
+
editor2.executeEdits("brand-slash-typed", [
|
|
248
|
+
{ range: triggerRange, text: snippet, forceMoveMarkers: true }
|
|
249
|
+
]);
|
|
250
|
+
} else {
|
|
251
|
+
(onInsert ?? insertDirective)(editor2, snippet);
|
|
252
|
+
}
|
|
253
|
+
onOpenChange(false);
|
|
254
|
+
editor2.focus();
|
|
255
|
+
};
|
|
256
|
+
const commitRunInSource = (command) => {
|
|
257
|
+
if (triggerRange) {
|
|
258
|
+
editor2.executeEdits("brand-slash-typed", [{ range: triggerRange, text: "" }]);
|
|
259
|
+
}
|
|
260
|
+
command.runInSource?.({
|
|
261
|
+
editor: editor2,
|
|
262
|
+
range: triggerRange ?? null,
|
|
263
|
+
content: monacoContentAccess(editor2)
|
|
264
|
+
});
|
|
265
|
+
onOpenChange(false);
|
|
266
|
+
editor2.focus();
|
|
267
|
+
};
|
|
268
|
+
const selectCommand = (command) => {
|
|
269
|
+
if (typeof command.runInSource === "function") {
|
|
270
|
+
commitRunInSource(command);
|
|
271
|
+
} else if (command.snippet) {
|
|
272
|
+
commitSnippet(command.snippet);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const cancel = () => {
|
|
276
|
+
if (triggerRange) {
|
|
277
|
+
editor2.executeEdits("brand-slash-cancel", [{ range: triggerRange, text: "" }]);
|
|
278
|
+
}
|
|
279
|
+
onOpenChange(false);
|
|
280
|
+
editor2.focus();
|
|
281
|
+
};
|
|
282
|
+
const selectRef = useRef(selectCommand);
|
|
283
|
+
selectRef.current = selectCommand;
|
|
284
|
+
const cancelRef = useRef(cancel);
|
|
285
|
+
cancelRef.current = cancel;
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
if (open) {
|
|
288
|
+
setQuery("");
|
|
289
|
+
setActiveIndex(0);
|
|
290
|
+
}
|
|
291
|
+
}, [open]);
|
|
292
|
+
useEffect(() => {
|
|
293
|
+
if (!open) return;
|
|
294
|
+
const update = () => {
|
|
295
|
+
setCoords(getCaretCoords(editor2));
|
|
296
|
+
};
|
|
297
|
+
update();
|
|
298
|
+
const scrollSub = editor2.onDidScrollChange(update);
|
|
299
|
+
const cursorSub = editor2.onDidChangeCursorPosition(update);
|
|
300
|
+
const onResize = () => update();
|
|
301
|
+
window.addEventListener("resize", onResize);
|
|
302
|
+
return () => {
|
|
303
|
+
scrollSub.dispose();
|
|
304
|
+
cursorSub.dispose();
|
|
305
|
+
window.removeEventListener("resize", onResize);
|
|
306
|
+
};
|
|
307
|
+
}, [open, editor2]);
|
|
308
|
+
useEffect(() => {
|
|
309
|
+
if (!open) return;
|
|
310
|
+
const blurSub = editor2.onDidBlurEditorText(() => {
|
|
311
|
+
setTimeout(() => {
|
|
312
|
+
if (!menuRef.current?.contains(document.activeElement)) {
|
|
313
|
+
onOpenChange(false);
|
|
314
|
+
}
|
|
315
|
+
}, 100);
|
|
316
|
+
});
|
|
317
|
+
return () => blurSub.dispose();
|
|
318
|
+
}, [open, editor2, onOpenChange]);
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
if (!open) return;
|
|
321
|
+
const keydown = (e) => {
|
|
322
|
+
const filtered2 = filterSlashCommands(commands, query);
|
|
323
|
+
if (e.key === "Escape") {
|
|
324
|
+
e.preventDefault();
|
|
325
|
+
e.stopPropagation();
|
|
326
|
+
cancelRef.current();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (e.key === "ArrowDown") {
|
|
330
|
+
e.preventDefault();
|
|
331
|
+
e.stopPropagation();
|
|
332
|
+
setActiveIndex((i) => (i + 1) % Math.max(filtered2.length, 1));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (e.key === "ArrowUp") {
|
|
336
|
+
e.preventDefault();
|
|
337
|
+
e.stopPropagation();
|
|
338
|
+
setActiveIndex(
|
|
339
|
+
(i) => (i - 1 + Math.max(filtered2.length, 1)) % Math.max(filtered2.length, 1)
|
|
340
|
+
);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (e.key === "Enter" || e.key === "Tab") {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
e.stopPropagation();
|
|
346
|
+
const command = filtered2[Math.min(activeIndex, filtered2.length - 1)];
|
|
347
|
+
if (command) selectRef.current(command);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
351
|
+
e.preventDefault();
|
|
352
|
+
e.stopPropagation();
|
|
353
|
+
setQuery((q) => q + e.key);
|
|
354
|
+
setActiveIndex(0);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (e.key === "Backspace") {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
e.stopPropagation();
|
|
360
|
+
if (query.length === 0) {
|
|
361
|
+
cancelRef.current();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
setQuery((q) => q.slice(0, -1));
|
|
365
|
+
setActiveIndex(0);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
const domNode = editor2.getDomNode();
|
|
369
|
+
domNode?.addEventListener("keydown", keydown, true);
|
|
370
|
+
return () => domNode?.removeEventListener("keydown", keydown, true);
|
|
371
|
+
}, [open, editor2, commands, query, activeIndex]);
|
|
372
|
+
useEffect(() => {
|
|
373
|
+
if (!open) return;
|
|
374
|
+
const filtered2 = filterSlashCommands(commands, query);
|
|
375
|
+
const active = filtered2[Math.min(activeIndex, Math.max(filtered2.length - 1, 0))];
|
|
376
|
+
if (!active) return;
|
|
377
|
+
const el = menuRef.current?.querySelector(
|
|
378
|
+
`#${CSS.escape(slashOptionId(ID_PREFIX, active.id))}`
|
|
379
|
+
);
|
|
380
|
+
el?.scrollIntoView({ block: "nearest" });
|
|
381
|
+
}, [open, commands, query, activeIndex]);
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
if (!open) return;
|
|
384
|
+
const filtered2 = filterSlashCommands(commands, query);
|
|
385
|
+
const activeCommand2 = filtered2[Math.min(activeIndex, Math.max(filtered2.length - 1, 0))];
|
|
386
|
+
const textarea = editor2.getDomNode()?.querySelector("textarea");
|
|
387
|
+
if (!textarea) return;
|
|
388
|
+
textarea.setAttribute("aria-expanded", "true");
|
|
389
|
+
textarea.setAttribute("aria-controls", LISTBOX_ID);
|
|
390
|
+
if (activeCommand2) {
|
|
391
|
+
textarea.setAttribute("aria-activedescendant", slashOptionId(ID_PREFIX, activeCommand2.id));
|
|
392
|
+
} else {
|
|
393
|
+
textarea.removeAttribute("aria-activedescendant");
|
|
394
|
+
}
|
|
395
|
+
return () => {
|
|
396
|
+
textarea.removeAttribute("aria-expanded");
|
|
397
|
+
textarea.removeAttribute("aria-controls");
|
|
398
|
+
textarea.removeAttribute("aria-activedescendant");
|
|
399
|
+
};
|
|
400
|
+
}, [open, editor2, commands, query, activeIndex]);
|
|
401
|
+
if (!open || !coords) return null;
|
|
402
|
+
const filtered = filterSlashCommands(commands, query);
|
|
403
|
+
const activeCommand = filtered[Math.min(activeIndex, Math.max(filtered.length - 1, 0))];
|
|
404
|
+
const handleSelect = (command) => {
|
|
405
|
+
selectCommand(command);
|
|
406
|
+
};
|
|
407
|
+
const resultCount = filtered.length;
|
|
408
|
+
const statusText = resultCount === 0 ? "No matching blocks" : query ? `${resultCount} result${resultCount === 1 ? "" : "s"} for \u201C${query}\u201D` : `${resultCount} block${resultCount === 1 ? "" : "s"}`;
|
|
409
|
+
return /* @__PURE__ */ jsxs(
|
|
410
|
+
"div",
|
|
411
|
+
{
|
|
412
|
+
ref: menuRef,
|
|
413
|
+
className: cn(className),
|
|
414
|
+
style: { position: "fixed", top: coords.top, left: coords.left, zIndex: 50 },
|
|
415
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
416
|
+
children: [
|
|
417
|
+
/* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", className: "sr-only", children: statusText }),
|
|
418
|
+
query && /* @__PURE__ */ jsxs(
|
|
419
|
+
"div",
|
|
420
|
+
{
|
|
421
|
+
"aria-hidden": "true",
|
|
422
|
+
className: "mb-0.5 rounded-sm border border-border bg-popover px-2 py-1 text-caption text-muted-foreground",
|
|
423
|
+
children: [
|
|
424
|
+
"Filter: ",
|
|
425
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: query })
|
|
426
|
+
]
|
|
427
|
+
}
|
|
428
|
+
),
|
|
429
|
+
/* @__PURE__ */ jsx(
|
|
430
|
+
SlashMenu,
|
|
431
|
+
{
|
|
432
|
+
id: LISTBOX_ID,
|
|
433
|
+
commands: filtered,
|
|
434
|
+
activeId: activeCommand?.id,
|
|
435
|
+
onSelect: handleSelect,
|
|
436
|
+
idPrefix: ID_PREFIX
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
]
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/markdown-workspace/markdown-workspace.tsx
|
|
445
|
+
import {
|
|
446
|
+
ResizableHandle,
|
|
447
|
+
ResizablePanel,
|
|
448
|
+
ResizablePanelGroup,
|
|
449
|
+
Toggle,
|
|
450
|
+
ToggleGroup,
|
|
451
|
+
ToggleGroupItem,
|
|
452
|
+
Tooltip as Tooltip2,
|
|
453
|
+
TooltipContent as TooltipContent2,
|
|
454
|
+
TooltipProvider as TooltipProvider2,
|
|
455
|
+
TooltipTrigger as TooltipTrigger2
|
|
456
|
+
} from "@elabs-ai/components-ui";
|
|
457
|
+
import { cn as cn13 } from "@elabs-ai/components-ui/lib/cn";
|
|
458
|
+
import { Columns2, Eye, Focus, SquareCode } from "lucide-react";
|
|
459
|
+
import {
|
|
460
|
+
forwardRef as forwardRef9,
|
|
461
|
+
useEffect as useEffect5,
|
|
462
|
+
useImperativeHandle,
|
|
463
|
+
useMemo as useMemo6,
|
|
464
|
+
useRef as useRef5,
|
|
465
|
+
useState as useState5
|
|
466
|
+
} from "react";
|
|
467
|
+
|
|
468
|
+
// src/calc-block/calc-editor-monaco.ts
|
|
469
|
+
import * as monaco3 from "monaco-editor";
|
|
470
|
+
var REGISTRY2 = /* @__PURE__ */ new Map();
|
|
471
|
+
var providersRegistered = false;
|
|
472
|
+
var inlayEmitter = null;
|
|
473
|
+
function bodyLineTexts(model, fence) {
|
|
474
|
+
const out = [];
|
|
475
|
+
for (let ln = fence.bodyStartLine; ln <= fence.bodyEndLine; ln++) {
|
|
476
|
+
out.push(model.getLineContent(ln));
|
|
477
|
+
}
|
|
478
|
+
return out;
|
|
479
|
+
}
|
|
480
|
+
function modelFences(model) {
|
|
481
|
+
return findCalcFences(model.getValue(monaco3.editor.EndOfLinePreference.LF));
|
|
482
|
+
}
|
|
483
|
+
function buildDecorations(model, hooks) {
|
|
484
|
+
const decorations = [];
|
|
485
|
+
for (const fence of modelFences(model)) {
|
|
486
|
+
if (fence.bodyEndLine < fence.bodyStartLine) continue;
|
|
487
|
+
const specs = calcDecorationSpecs(hooks, fence.bodyStartLine, bodyLineTexts(model, fence));
|
|
488
|
+
for (const s of specs) {
|
|
489
|
+
decorations.push({
|
|
490
|
+
range: new monaco3.Range(s.lineNumber, s.startColumn, s.lineNumber, s.endColumn),
|
|
491
|
+
options: { inlineClassName: s.className }
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return decorations;
|
|
496
|
+
}
|
|
497
|
+
var COMPLETION_KIND = {
|
|
498
|
+
variable: () => monaco3.languages.CompletionItemKind.Variable,
|
|
499
|
+
function: () => monaco3.languages.CompletionItemKind.Function,
|
|
500
|
+
unit: () => monaco3.languages.CompletionItemKind.Unit,
|
|
501
|
+
currency: () => monaco3.languages.CompletionItemKind.Unit,
|
|
502
|
+
constant: () => monaco3.languages.CompletionItemKind.Constant,
|
|
503
|
+
reference: () => monaco3.languages.CompletionItemKind.Reference,
|
|
504
|
+
keyword: () => monaco3.languages.CompletionItemKind.Keyword,
|
|
505
|
+
snippet: () => monaco3.languages.CompletionItemKind.Snippet
|
|
506
|
+
};
|
|
507
|
+
function mapCompletionKind(kind) {
|
|
508
|
+
return (COMPLETION_KIND[kind ?? "variable"] ?? COMPLETION_KIND.variable)();
|
|
509
|
+
}
|
|
510
|
+
function ensureProviders() {
|
|
511
|
+
if (providersRegistered) return;
|
|
512
|
+
providersRegistered = true;
|
|
513
|
+
inlayEmitter = new monaco3.Emitter();
|
|
514
|
+
monaco3.languages.registerInlayHintsProvider("markdown", {
|
|
515
|
+
onDidChangeInlayHints: inlayEmitter.event,
|
|
516
|
+
provideInlayHints(model, range) {
|
|
517
|
+
const empty = { hints: [], dispose() {
|
|
518
|
+
} };
|
|
519
|
+
const hooks = REGISTRY2.get(model)?.();
|
|
520
|
+
if (!hooks?.evaluate) return empty;
|
|
521
|
+
const hints = [];
|
|
522
|
+
for (const fence of modelFences(model)) {
|
|
523
|
+
if (fence.bodyEndLine < fence.bodyStartLine) continue;
|
|
524
|
+
if (fence.bodyEndLine < range.startLineNumber || fence.bodyStartLine > range.endLineNumber) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
for (const inlay of calcInlaySpecs(
|
|
528
|
+
hooks,
|
|
529
|
+
fence.bodyStartLine,
|
|
530
|
+
bodyLineTexts(model, fence)
|
|
531
|
+
)) {
|
|
532
|
+
hints.push({
|
|
533
|
+
position: { lineNumber: inlay.lineNumber, column: inlay.column },
|
|
534
|
+
label: inlay.text,
|
|
535
|
+
kind: monaco3.languages.InlayHintKind.Type,
|
|
536
|
+
paddingLeft: true
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return { hints, dispose() {
|
|
541
|
+
} };
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
monaco3.languages.registerCompletionItemProvider("markdown", {
|
|
545
|
+
provideCompletionItems(model, position) {
|
|
546
|
+
const hooks = REGISTRY2.get(model)?.();
|
|
547
|
+
if (!hooks?.complete) return { suggestions: [] };
|
|
548
|
+
const fence = modelFences(model).find(
|
|
549
|
+
(f) => position.lineNumber >= f.bodyStartLine && position.lineNumber <= f.bodyEndLine
|
|
550
|
+
);
|
|
551
|
+
if (!fence) return { suggestions: [] };
|
|
552
|
+
const lines = bodyLineTexts(model, fence);
|
|
553
|
+
const line = lines[position.lineNumber - fence.bodyStartLine] ?? "";
|
|
554
|
+
const column = position.column - 1;
|
|
555
|
+
const prefix = identifierPrefix(line, column);
|
|
556
|
+
let completions;
|
|
557
|
+
try {
|
|
558
|
+
completions = hooks.complete({
|
|
559
|
+
source: lines.join("\n"),
|
|
560
|
+
line,
|
|
561
|
+
lineNumber: position.lineNumber - fence.bodyStartLine + 1,
|
|
562
|
+
column,
|
|
563
|
+
prefix
|
|
564
|
+
});
|
|
565
|
+
} catch {
|
|
566
|
+
return { suggestions: [] };
|
|
567
|
+
}
|
|
568
|
+
const replace = new monaco3.Range(
|
|
569
|
+
position.lineNumber,
|
|
570
|
+
position.column - prefix.length,
|
|
571
|
+
position.lineNumber,
|
|
572
|
+
position.column
|
|
573
|
+
);
|
|
574
|
+
return {
|
|
575
|
+
suggestions: completions.map((c) => ({
|
|
576
|
+
label: c.label,
|
|
577
|
+
insertText: c.insert,
|
|
578
|
+
detail: c.detail,
|
|
579
|
+
kind: mapCompletionKind(c.kind),
|
|
580
|
+
range: replace
|
|
581
|
+
}))
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
function attachCalcMonaco(editor2, getHooks) {
|
|
587
|
+
ensureProviders();
|
|
588
|
+
const collection = editor2.createDecorationsCollection();
|
|
589
|
+
const subs = [];
|
|
590
|
+
let model = editor2.getModel();
|
|
591
|
+
if (model) REGISTRY2.set(model, getHooks);
|
|
592
|
+
const refresh = () => {
|
|
593
|
+
const current = editor2.getModel();
|
|
594
|
+
const hooks = getHooks();
|
|
595
|
+
if (!current || !hooks) {
|
|
596
|
+
collection.clear();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
REGISTRY2.set(current, getHooks);
|
|
600
|
+
collection.set(buildDecorations(current, hooks));
|
|
601
|
+
inlayEmitter?.fire();
|
|
602
|
+
};
|
|
603
|
+
refresh();
|
|
604
|
+
subs.push(editor2.onDidChangeModelContent(refresh));
|
|
605
|
+
subs.push(
|
|
606
|
+
editor2.onDidChangeModel(() => {
|
|
607
|
+
if (model) REGISTRY2.delete(model);
|
|
608
|
+
model = editor2.getModel();
|
|
609
|
+
refresh();
|
|
610
|
+
})
|
|
611
|
+
);
|
|
612
|
+
return () => {
|
|
613
|
+
for (const s of subs) s.dispose();
|
|
614
|
+
collection.clear();
|
|
615
|
+
if (model) REGISTRY2.delete(model);
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/lib/markdown/diff.ts
|
|
620
|
+
var MAX_DIFF_LINES = 5e3;
|
|
621
|
+
var splitLines = (s) => s.split("\n");
|
|
622
|
+
function lcsPairs(a, b) {
|
|
623
|
+
const n = a.length;
|
|
624
|
+
const m = b.length;
|
|
625
|
+
const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
|
|
626
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
627
|
+
const row = dp[i2];
|
|
628
|
+
const next = dp[i2 + 1];
|
|
629
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
630
|
+
row[j2] = a[i2] === b[j2] ? next[j2 + 1] + 1 : Math.max(next[j2], row[j2 + 1]);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const pairs = [];
|
|
634
|
+
let i = 0;
|
|
635
|
+
let j = 0;
|
|
636
|
+
while (i < n && j < m) {
|
|
637
|
+
if (a[i] === b[j]) {
|
|
638
|
+
pairs.push([i, j]);
|
|
639
|
+
i++;
|
|
640
|
+
j++;
|
|
641
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
642
|
+
i++;
|
|
643
|
+
} else {
|
|
644
|
+
j++;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return pairs;
|
|
648
|
+
}
|
|
649
|
+
function computeMarkdownAnnotations(before, after) {
|
|
650
|
+
if (before === after) return [];
|
|
651
|
+
const a = splitLines(before);
|
|
652
|
+
const b = splitLines(after);
|
|
653
|
+
if (a.length > MAX_DIFF_LINES || b.length > MAX_DIFF_LINES) return [];
|
|
654
|
+
const pairs = lcsPairs(a, b);
|
|
655
|
+
const annotations = [];
|
|
656
|
+
let prevA = -1;
|
|
657
|
+
let prevB = -1;
|
|
658
|
+
const walk = [...pairs, [a.length, b.length]];
|
|
659
|
+
for (const [ai, bi] of walk) {
|
|
660
|
+
const removed = ai - prevA - 1;
|
|
661
|
+
const added = bi - prevB - 1;
|
|
662
|
+
if (added > 0 && removed > 0) {
|
|
663
|
+
annotations.push({ kind: "modified", startLine: prevB + 2, endLine: bi });
|
|
664
|
+
} else if (added > 0) {
|
|
665
|
+
annotations.push({ kind: "added", startLine: prevB + 2, endLine: bi });
|
|
666
|
+
} else if (removed > 0) {
|
|
667
|
+
const anchor = Math.min(bi + 1, b.length);
|
|
668
|
+
annotations.push({
|
|
669
|
+
kind: "removed-before",
|
|
670
|
+
startLine: anchor,
|
|
671
|
+
endLine: anchor,
|
|
672
|
+
removedCount: removed
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
prevA = ai;
|
|
676
|
+
prevB = bi;
|
|
677
|
+
}
|
|
678
|
+
return mergeAdjacent(annotations);
|
|
679
|
+
}
|
|
680
|
+
function summarizeAnnotations(annotations) {
|
|
681
|
+
let added = 0;
|
|
682
|
+
let removed = 0;
|
|
683
|
+
for (const a of annotations) {
|
|
684
|
+
const span = a.endLine - a.startLine + 1;
|
|
685
|
+
if (a.kind === "added") added += span;
|
|
686
|
+
else if (a.kind === "modified") {
|
|
687
|
+
added += span;
|
|
688
|
+
removed += span;
|
|
689
|
+
} else if (a.kind === "removed-before") removed += a.removedCount ?? 1;
|
|
690
|
+
}
|
|
691
|
+
return { added, removed };
|
|
692
|
+
}
|
|
693
|
+
function mergeAdjacent(annotations) {
|
|
694
|
+
const out = [];
|
|
695
|
+
for (const ann of annotations) {
|
|
696
|
+
const last = out[out.length - 1];
|
|
697
|
+
if (last && last.kind !== "removed-before" && last.kind === ann.kind && ann.startLine <= last.endLine + 1) {
|
|
698
|
+
last.endLine = Math.max(last.endLine, ann.endLine);
|
|
699
|
+
} else {
|
|
700
|
+
out.push({ ...ann });
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return out;
|
|
704
|
+
}
|
|
705
|
+
function shiftAnnotations(annotations, offset) {
|
|
706
|
+
if (offset === 0) return annotations;
|
|
707
|
+
const out = [];
|
|
708
|
+
for (const ann of annotations) {
|
|
709
|
+
const startLine = ann.startLine - offset;
|
|
710
|
+
const endLine = ann.endLine - offset;
|
|
711
|
+
if (endLine < 1) continue;
|
|
712
|
+
out.push({ ...ann, startLine: Math.max(1, startLine), endLine });
|
|
713
|
+
}
|
|
714
|
+
return out;
|
|
715
|
+
}
|
|
716
|
+
function annotationForRange(annotations, startLine, endLine) {
|
|
717
|
+
let hit;
|
|
718
|
+
for (const ann of annotations) {
|
|
719
|
+
if (ann.kind === "removed-before") continue;
|
|
720
|
+
if (ann.startLine <= endLine && ann.endLine >= startLine) {
|
|
721
|
+
if (!hit || ann.endLine - ann.startLine < hit.endLine - hit.startLine) hit = ann;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return hit;
|
|
725
|
+
}
|
|
726
|
+
function removedMarkerAt(annotations, startLine) {
|
|
727
|
+
return annotations.find((a) => a.kind === "removed-before" && a.startLine === startLine);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/lib/markdown/merge.ts
|
|
731
|
+
function alignRegions(o, b) {
|
|
732
|
+
const pairs = lcsPairs(o, b);
|
|
733
|
+
const regions = [];
|
|
734
|
+
let prevO = -1;
|
|
735
|
+
let prevB = -1;
|
|
736
|
+
const walk = [...pairs, [o.length, b.length]];
|
|
737
|
+
for (const [oi, bi] of walk) {
|
|
738
|
+
if (oi - prevO > 1 || bi - prevB > 1) {
|
|
739
|
+
regions.push({
|
|
740
|
+
bStart: prevB + 1,
|
|
741
|
+
bEnd: bi,
|
|
742
|
+
oLines: o.slice(prevO + 1, oi),
|
|
743
|
+
exact: false
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (oi < o.length && bi < b.length) {
|
|
747
|
+
regions.push({ bStart: bi, bEnd: bi + 1, oLines: [o[oi]], exact: true });
|
|
748
|
+
}
|
|
749
|
+
prevO = oi;
|
|
750
|
+
prevB = bi;
|
|
751
|
+
}
|
|
752
|
+
return regions;
|
|
753
|
+
}
|
|
754
|
+
function mergeNormalizedEdit(original, baseline, edited) {
|
|
755
|
+
if (baseline === edited) return original;
|
|
756
|
+
if (original === baseline) return edited;
|
|
757
|
+
const o = original.split("\n");
|
|
758
|
+
const b = baseline.split("\n");
|
|
759
|
+
const n = edited.split("\n");
|
|
760
|
+
if (o.length > MAX_DIFF_LINES || b.length > MAX_DIFF_LINES || n.length > MAX_DIFF_LINES) {
|
|
761
|
+
return edited;
|
|
762
|
+
}
|
|
763
|
+
const keptB = new Uint8Array(b.length);
|
|
764
|
+
const inserts = /* @__PURE__ */ new Map();
|
|
765
|
+
{
|
|
766
|
+
const pairs = lcsPairs(b, n);
|
|
767
|
+
let prevB = -1;
|
|
768
|
+
let prevN = -1;
|
|
769
|
+
const walk = [...pairs, [b.length, n.length]];
|
|
770
|
+
for (const [bi, ni] of walk) {
|
|
771
|
+
if (ni - prevN > 1) inserts.set(bi, n.slice(prevN + 1, ni));
|
|
772
|
+
if (bi < b.length) keptB[bi] = 1;
|
|
773
|
+
prevB = bi;
|
|
774
|
+
prevN = ni;
|
|
775
|
+
}
|
|
776
|
+
void prevB;
|
|
777
|
+
}
|
|
778
|
+
const regions = alignRegions(o, b);
|
|
779
|
+
const out = [];
|
|
780
|
+
const flushInsertsBefore = (bIndex, pendingFrom) => {
|
|
781
|
+
for (let at = pendingFrom; at <= bIndex; at++) {
|
|
782
|
+
const ins = inserts.get(at);
|
|
783
|
+
if (ins) out.push(...ins);
|
|
784
|
+
}
|
|
785
|
+
return bIndex + 1;
|
|
786
|
+
};
|
|
787
|
+
let insertCursor = 0;
|
|
788
|
+
for (const region of regions) {
|
|
789
|
+
if (region.bStart === region.bEnd) {
|
|
790
|
+
const before = region.bStart - 1;
|
|
791
|
+
const contextKept = (before < 0 || keptB[before] === 1) && (region.bStart >= b.length || keptB[region.bStart] === 1);
|
|
792
|
+
insertCursor = flushInsertsBefore(region.bStart - 1, insertCursor);
|
|
793
|
+
if (contextKept) out.push(...region.oLines);
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
let allKept = true;
|
|
797
|
+
for (let bi = region.bStart; bi < region.bEnd; bi++) {
|
|
798
|
+
if (keptB[bi] !== 1) {
|
|
799
|
+
allKept = false;
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (region.exact || allKept) {
|
|
804
|
+
for (let bi = region.bStart; bi < region.bEnd; bi++) {
|
|
805
|
+
insertCursor = flushInsertsBefore(bi, insertCursor);
|
|
806
|
+
if (keptB[bi] === 1) {
|
|
807
|
+
if (region.exact) out.push(...region.oLines);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (!region.exact) out.push(...region.oLines);
|
|
811
|
+
} else {
|
|
812
|
+
for (let bi = region.bStart; bi < region.bEnd; bi++) {
|
|
813
|
+
insertCursor = flushInsertsBefore(bi, insertCursor);
|
|
814
|
+
if (keptB[bi] === 1) out.push(b[bi]);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
flushInsertsBefore(b.length, insertCursor);
|
|
819
|
+
return out.join("\n");
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/markdown-editor/slash/source-slash-trigger.ts
|
|
823
|
+
function slashTriggerRange(line, lineContent, slashColumn) {
|
|
824
|
+
if (lineContent.charAt(slashColumn - 1) !== "/") return null;
|
|
825
|
+
if (slashColumn > 1) {
|
|
826
|
+
const before = lineContent.charAt(slashColumn - 2);
|
|
827
|
+
if (!/\s/.test(before)) return null;
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
startLineNumber: line,
|
|
831
|
+
startColumn: slashColumn,
|
|
832
|
+
endLineNumber: line,
|
|
833
|
+
endColumn: slashColumn + 1
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// src/markdown-editor/slash/shortcut-monaco.ts
|
|
838
|
+
import * as monaco4 from "monaco-editor";
|
|
839
|
+
var LETTER_KEY_MAP = (() => {
|
|
840
|
+
const map = {};
|
|
841
|
+
const kc = monaco4.KeyCode;
|
|
842
|
+
for (let i = 0; i < 26; i++) {
|
|
843
|
+
const letter = String.fromCharCode(65 + i);
|
|
844
|
+
const code = kc[`Key${letter}`];
|
|
845
|
+
if (code !== void 0) map[letter.toLowerCase()] = code;
|
|
846
|
+
}
|
|
847
|
+
return map;
|
|
848
|
+
})();
|
|
849
|
+
var NAMED_KEY_MAP = {
|
|
850
|
+
"/": monaco4.KeyCode.Slash,
|
|
851
|
+
backspace: monaco4.KeyCode.Backspace,
|
|
852
|
+
delete: monaco4.KeyCode.Delete,
|
|
853
|
+
escape: monaco4.KeyCode.Escape,
|
|
854
|
+
enter: monaco4.KeyCode.Enter,
|
|
855
|
+
tab: monaco4.KeyCode.Tab,
|
|
856
|
+
arrowup: monaco4.KeyCode.UpArrow,
|
|
857
|
+
arrowdown: monaco4.KeyCode.DownArrow,
|
|
858
|
+
arrowleft: monaco4.KeyCode.LeftArrow,
|
|
859
|
+
arrowright: monaco4.KeyCode.RightArrow
|
|
860
|
+
};
|
|
861
|
+
function parseShortcut(shortcut) {
|
|
862
|
+
const parts = shortcut.split("-");
|
|
863
|
+
let binding = 0;
|
|
864
|
+
const keyPart = parts[parts.length - 1] ?? "";
|
|
865
|
+
const modifiers = parts.slice(0, -1).map((m) => m.toLowerCase());
|
|
866
|
+
for (const mod of modifiers) {
|
|
867
|
+
if (mod === "mod") binding |= monaco4.KeyMod.CtrlCmd;
|
|
868
|
+
else if (mod === "shift") binding |= monaco4.KeyMod.Shift;
|
|
869
|
+
else if (mod === "alt") binding |= monaco4.KeyMod.Alt;
|
|
870
|
+
else if (mod === "ctrl") binding |= monaco4.KeyMod.WinCtrl;
|
|
871
|
+
}
|
|
872
|
+
const key = keyPart.toLowerCase();
|
|
873
|
+
const keyCode = NAMED_KEY_MAP[key] ?? LETTER_KEY_MAP[key] ?? (() => {
|
|
874
|
+
throw new Error(`[@elabs-ai/components-editor] parseShortcut: unrecognized key "${keyPart}"`);
|
|
875
|
+
})();
|
|
876
|
+
return binding | keyCode;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/markdown-preview/markdown-preview.tsx
|
|
880
|
+
import {
|
|
881
|
+
Alert,
|
|
882
|
+
AlertDescription,
|
|
883
|
+
AlertTitle,
|
|
884
|
+
Card,
|
|
885
|
+
CardContent,
|
|
886
|
+
CardHeader,
|
|
887
|
+
CardTitle,
|
|
888
|
+
Separator as Separator3,
|
|
889
|
+
Table,
|
|
890
|
+
TableBody,
|
|
891
|
+
TableCell,
|
|
892
|
+
TableHead,
|
|
893
|
+
TableHeader,
|
|
894
|
+
TableRow
|
|
895
|
+
} from "@elabs-ai/components-ui";
|
|
896
|
+
import { cn as cn11 } from "@elabs-ai/components-ui/lib/cn";
|
|
897
|
+
import {
|
|
898
|
+
createContext as createContext4,
|
|
899
|
+
forwardRef as forwardRef7,
|
|
900
|
+
isValidElement,
|
|
901
|
+
useContext as useContext4,
|
|
902
|
+
useMemo as useMemo5
|
|
903
|
+
} from "react";
|
|
904
|
+
import {
|
|
905
|
+
Streamdown,
|
|
906
|
+
defaultRehypePlugins,
|
|
907
|
+
defaultRemarkPlugins
|
|
908
|
+
} from "streamdown";
|
|
909
|
+
import { visit as visit5 } from "unist-util-visit";
|
|
910
|
+
|
|
911
|
+
// src/lib/markdown/directives.ts
|
|
912
|
+
import remarkDirective from "remark-directive";
|
|
913
|
+
import { visit } from "unist-util-visit";
|
|
914
|
+
var BRAND_DIRECTIVES = ["card", "callout", "metric", "timeline"];
|
|
915
|
+
var BRAND_DIRECTIVE_TAG = "brand-directive";
|
|
916
|
+
var BRAND_DIRECTIVE_INLINE_TAG = "brand-directive-inline";
|
|
917
|
+
var BRAND_DIRECTIVE_ATTR = "data-brand";
|
|
918
|
+
var BRAND_DIRECTIVE_PROP = "dataBrand";
|
|
919
|
+
var DIRECTIVE_TYPES = /* @__PURE__ */ new Set(["containerDirective", "leafDirective", "textDirective"]);
|
|
920
|
+
function mdastText(node) {
|
|
921
|
+
if (typeof node.value === "string") return node.value;
|
|
922
|
+
if (node.children) return node.children.map(mdastText).join("");
|
|
923
|
+
return "";
|
|
924
|
+
}
|
|
925
|
+
function extractTimelineItems(node) {
|
|
926
|
+
const list = node.children?.find((c) => c.type === "list");
|
|
927
|
+
if (!list?.children) return [];
|
|
928
|
+
const MARKER = /^\((done|complete|completed|active|current|pending|todo)\)\s*/i;
|
|
929
|
+
return list.children.filter((c) => c.type === "listItem").map((li) => {
|
|
930
|
+
let title = mdastText(li).trim();
|
|
931
|
+
let status = "pending";
|
|
932
|
+
const marker = title.match(MARKER);
|
|
933
|
+
if (marker) {
|
|
934
|
+
status = marker[1].toLowerCase();
|
|
935
|
+
title = title.slice(marker[0].length).trim();
|
|
936
|
+
}
|
|
937
|
+
return { title, status };
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
function cleanAttributes(attrs) {
|
|
941
|
+
const out = {};
|
|
942
|
+
for (const [k, v] of Object.entries(attrs ?? {})) {
|
|
943
|
+
if (typeof v === "string") out[k] = v;
|
|
944
|
+
}
|
|
945
|
+
return out;
|
|
946
|
+
}
|
|
947
|
+
function originalText(node, source) {
|
|
948
|
+
const start = node.position?.start?.offset;
|
|
949
|
+
const end = node.position?.end?.offset;
|
|
950
|
+
if (source != null && typeof start === "number" && typeof end === "number") {
|
|
951
|
+
return source.slice(start, end);
|
|
952
|
+
}
|
|
953
|
+
const colons = node.type === "leafDirective" ? "::" : ":";
|
|
954
|
+
const label = node.children?.length ? `[${mdastText(node)}]` : "";
|
|
955
|
+
return `${colons}${node.name ?? ""}${label}`;
|
|
956
|
+
}
|
|
957
|
+
function directiveKind(type) {
|
|
958
|
+
if (type === "containerDirective") return "container";
|
|
959
|
+
if (type === "leafDirective") return "leaf";
|
|
960
|
+
return "inline";
|
|
961
|
+
}
|
|
962
|
+
function rawLabel(node, source) {
|
|
963
|
+
const kids = node.children;
|
|
964
|
+
if (!source || !kids || kids.length === 0) return void 0;
|
|
965
|
+
const start = kids[0]?.position?.start?.offset;
|
|
966
|
+
const end = kids[kids.length - 1]?.position?.end?.offset;
|
|
967
|
+
if (typeof start === "number" && typeof end === "number") return source.slice(start, end);
|
|
968
|
+
return void 0;
|
|
969
|
+
}
|
|
970
|
+
function rawContainerBody(node, source) {
|
|
971
|
+
const kids = node.children;
|
|
972
|
+
if (!source || !kids || kids.length === 0) return void 0;
|
|
973
|
+
const body = kids.filter(
|
|
974
|
+
(c) => !c.data?.directiveLabel
|
|
975
|
+
);
|
|
976
|
+
if (body.length === 0) return void 0;
|
|
977
|
+
const start = body[0]?.position?.start?.offset;
|
|
978
|
+
const end = body[body.length - 1]?.position?.end?.offset;
|
|
979
|
+
if (typeof start === "number" && typeof end === "number") return source.slice(start, end).trim();
|
|
980
|
+
return void 0;
|
|
981
|
+
}
|
|
982
|
+
function remarkBrandDirectives(knownNames = BRAND_DIRECTIVES, rawBodyNames = []) {
|
|
983
|
+
return (tree, file) => {
|
|
984
|
+
const source = typeof file?.value === "string" ? file.value : void 0;
|
|
985
|
+
visit(tree, (node, index, parent) => {
|
|
986
|
+
if (!DIRECTIVE_TYPES.has(node.type) || !node.name) return void 0;
|
|
987
|
+
const name = node.name;
|
|
988
|
+
const known = knownNames.includes(name);
|
|
989
|
+
const kind = directiveKind(node.type);
|
|
990
|
+
if (!known && node.type !== "containerDirective" && parent?.children && index != null) {
|
|
991
|
+
const literal = { type: "text", value: originalText(node, source) };
|
|
992
|
+
parent.children.splice(
|
|
993
|
+
index,
|
|
994
|
+
1,
|
|
995
|
+
node.type === "leafDirective" ? { type: "paragraph", children: [literal] } : literal
|
|
996
|
+
);
|
|
997
|
+
return index + 1;
|
|
998
|
+
}
|
|
999
|
+
const payload = {
|
|
1000
|
+
name,
|
|
1001
|
+
known,
|
|
1002
|
+
kind,
|
|
1003
|
+
attributes: cleanAttributes(node.attributes)
|
|
1004
|
+
};
|
|
1005
|
+
if (kind !== "container") {
|
|
1006
|
+
const label = rawLabel(node, source);
|
|
1007
|
+
if (label != null) payload.label = label;
|
|
1008
|
+
}
|
|
1009
|
+
if (kind === "container" && rawBodyNames.includes(name)) {
|
|
1010
|
+
const body = rawContainerBody(node, source);
|
|
1011
|
+
if (body != null) payload.body = body;
|
|
1012
|
+
node.children = [];
|
|
1013
|
+
}
|
|
1014
|
+
if (name === "timeline") {
|
|
1015
|
+
payload.items = extractTimelineItems(node);
|
|
1016
|
+
node.children = [];
|
|
1017
|
+
}
|
|
1018
|
+
const data = node.data ?? (node.data = {});
|
|
1019
|
+
data.hName = kind === "inline" ? BRAND_DIRECTIVE_INLINE_TAG : BRAND_DIRECTIVE_TAG;
|
|
1020
|
+
data.hProperties = { [BRAND_DIRECTIVE_PROP]: JSON.stringify(payload) };
|
|
1021
|
+
});
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
function buildMarkdownPlugins(options = {}) {
|
|
1025
|
+
const known = options.directiveNames && options.directiveNames.length > 0 ? [...BRAND_DIRECTIVES, ...options.directiveNames] : BRAND_DIRECTIVES;
|
|
1026
|
+
const rawBodyNames = options.rawBodyNames ?? [];
|
|
1027
|
+
return [remarkDirective, [remarkBrandDirectives, known, rawBodyNames]];
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/calc-block/calc-block.tsx
|
|
1031
|
+
import { cn as cn2 } from "@elabs-ai/components-ui/lib/cn";
|
|
1032
|
+
import { TriangleAlert } from "lucide-react";
|
|
1033
|
+
import { forwardRef, useId, useMemo } from "react";
|
|
1034
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1035
|
+
var TINT_MARKERS = {
|
|
1036
|
+
primary: "primary",
|
|
1037
|
+
success: "success",
|
|
1038
|
+
warning: "warning",
|
|
1039
|
+
danger: "destructive",
|
|
1040
|
+
destructive: "destructive",
|
|
1041
|
+
info: "info",
|
|
1042
|
+
muted: "muted",
|
|
1043
|
+
note: "muted"
|
|
1044
|
+
};
|
|
1045
|
+
var RULE_MARKERS = {
|
|
1046
|
+
line: "single",
|
|
1047
|
+
rule: "single",
|
|
1048
|
+
line2: "double",
|
|
1049
|
+
double: "double",
|
|
1050
|
+
dotted: "dotted"
|
|
1051
|
+
};
|
|
1052
|
+
function parseMarkers(line) {
|
|
1053
|
+
let text = line;
|
|
1054
|
+
let tint;
|
|
1055
|
+
let rule;
|
|
1056
|
+
for (; ; ) {
|
|
1057
|
+
const m = /\s+@([A-Za-z][A-Za-z0-9]*)\s*$/.exec(text);
|
|
1058
|
+
if (!m) break;
|
|
1059
|
+
const key = (m[1] ?? "").toLowerCase();
|
|
1060
|
+
if (key in TINT_MARKERS) {
|
|
1061
|
+
tint ??= TINT_MARKERS[key];
|
|
1062
|
+
} else if (key in RULE_MARKERS) {
|
|
1063
|
+
rule ??= RULE_MARKERS[key];
|
|
1064
|
+
} else {
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
text = text.slice(0, m.index);
|
|
1068
|
+
}
|
|
1069
|
+
return { text, tint, rule };
|
|
1070
|
+
}
|
|
1071
|
+
var RULE_CLASS = {
|
|
1072
|
+
dotted: "border-b border-dotted border-border-strong pb-1",
|
|
1073
|
+
single: "border-b border-border-strong pb-1",
|
|
1074
|
+
double: "border-b-4 border-double border-border-strong pb-1"
|
|
1075
|
+
};
|
|
1076
|
+
var TINT_CLASS = {
|
|
1077
|
+
primary: "bg-primary/10",
|
|
1078
|
+
success: "bg-success/10",
|
|
1079
|
+
warning: "bg-warning/10",
|
|
1080
|
+
destructive: "bg-destructive/10",
|
|
1081
|
+
info: "bg-info/10",
|
|
1082
|
+
muted: "bg-muted"
|
|
1083
|
+
};
|
|
1084
|
+
function isCalcValue(x) {
|
|
1085
|
+
return typeof x === "object" && x !== null && !("$$typeof" in x) && "kind" in x && "display" in x;
|
|
1086
|
+
}
|
|
1087
|
+
function tokenClass(kind, resolved) {
|
|
1088
|
+
let base;
|
|
1089
|
+
let role;
|
|
1090
|
+
switch (kind) {
|
|
1091
|
+
case "comment":
|
|
1092
|
+
base = "text-calc-comment italic";
|
|
1093
|
+
role = "brand-calc-tok--comment";
|
|
1094
|
+
break;
|
|
1095
|
+
case "operator":
|
|
1096
|
+
base = "text-calc-operator";
|
|
1097
|
+
role = "brand-calc-tok--operator";
|
|
1098
|
+
break;
|
|
1099
|
+
case "unit":
|
|
1100
|
+
base = "text-calc-unit";
|
|
1101
|
+
role = "brand-calc-tok--unit";
|
|
1102
|
+
break;
|
|
1103
|
+
case "currency":
|
|
1104
|
+
base = "text-calc-currency";
|
|
1105
|
+
role = "brand-calc-tok--currency";
|
|
1106
|
+
break;
|
|
1107
|
+
case "function":
|
|
1108
|
+
base = "text-calc-function";
|
|
1109
|
+
role = "brand-calc-tok--function";
|
|
1110
|
+
break;
|
|
1111
|
+
case "var-ref":
|
|
1112
|
+
base = "text-calc-variable";
|
|
1113
|
+
role = "brand-calc-tok--var-ref";
|
|
1114
|
+
break;
|
|
1115
|
+
case "line-ref":
|
|
1116
|
+
base = "text-calc-reference tabular-nums";
|
|
1117
|
+
role = "brand-calc-tok--line-ref";
|
|
1118
|
+
break;
|
|
1119
|
+
case "var-def":
|
|
1120
|
+
base = "text-calc-variable font-medium";
|
|
1121
|
+
role = "brand-calc-tok--var-def";
|
|
1122
|
+
break;
|
|
1123
|
+
case "unknown":
|
|
1124
|
+
base = "text-calc-warning";
|
|
1125
|
+
role = "brand-calc-tok--unknown";
|
|
1126
|
+
break;
|
|
1127
|
+
default:
|
|
1128
|
+
base = "text-calc-number";
|
|
1129
|
+
role = "brand-calc-tok--number";
|
|
1130
|
+
}
|
|
1131
|
+
return cn2(
|
|
1132
|
+
"brand-calc-tok",
|
|
1133
|
+
role,
|
|
1134
|
+
base,
|
|
1135
|
+
!resolved && "underline decoration-dotted decoration-1 underline-offset-2"
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
function renderSource(text, tokens) {
|
|
1139
|
+
const out = [];
|
|
1140
|
+
let cursor = 0;
|
|
1141
|
+
for (const [i, t] of tokens.entries()) {
|
|
1142
|
+
if (t.start > cursor) {
|
|
1143
|
+
out.push(/* @__PURE__ */ jsx2("span", { children: text.slice(cursor, t.start) }, `gap-${String(i)}`));
|
|
1144
|
+
}
|
|
1145
|
+
out.push(
|
|
1146
|
+
/* @__PURE__ */ jsx2("span", { className: tokenClass(t.kind, t.resolved), children: text.slice(t.start, t.end) }, `tok-${String(i)}`)
|
|
1147
|
+
);
|
|
1148
|
+
cursor = t.end;
|
|
1149
|
+
}
|
|
1150
|
+
if (cursor < text.length) out.push(/* @__PURE__ */ jsx2("span", { children: text.slice(cursor) }, "tail"));
|
|
1151
|
+
if (out.length === 0) out.push(/* @__PURE__ */ jsx2("span", { children: text || " " }, "empty"));
|
|
1152
|
+
return out;
|
|
1153
|
+
}
|
|
1154
|
+
function ResultCell({ result }) {
|
|
1155
|
+
if (result.error) {
|
|
1156
|
+
return /* @__PURE__ */ jsx2(
|
|
1157
|
+
"span",
|
|
1158
|
+
{
|
|
1159
|
+
className: "inline-flex shrink-0 text-calc-warning",
|
|
1160
|
+
title: result.error.message,
|
|
1161
|
+
"aria-label": `Error: ${result.error.message}`,
|
|
1162
|
+
children: /* @__PURE__ */ jsx2(TriangleAlert, { className: "size-3.5", "aria-hidden": "true" })
|
|
1163
|
+
}
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
if (result.value) {
|
|
1167
|
+
return /* @__PURE__ */ jsxs2("div", { className: "brand-calc-tok--result shrink-0 tabular-nums text-calc-result", children: [
|
|
1168
|
+
/* @__PURE__ */ jsx2("span", { className: "sr-only", children: "equals " }),
|
|
1169
|
+
/* @__PURE__ */ jsx2("span", { children: result.value.display })
|
|
1170
|
+
] });
|
|
1171
|
+
}
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
var CalcBlock = forwardRef(function CalcBlock2({
|
|
1175
|
+
source,
|
|
1176
|
+
evaluate,
|
|
1177
|
+
title,
|
|
1178
|
+
showTotal = true,
|
|
1179
|
+
total,
|
|
1180
|
+
totalLabel = "Total",
|
|
1181
|
+
markers = true,
|
|
1182
|
+
readOnly: _readOnly,
|
|
1183
|
+
className,
|
|
1184
|
+
...props
|
|
1185
|
+
}, ref) {
|
|
1186
|
+
const { lines, evalSource, hints } = useMemo(() => {
|
|
1187
|
+
const raw = source.split("\n");
|
|
1188
|
+
if (!markers) return { lines: raw, evalSource: source, hints: [] };
|
|
1189
|
+
const parsed = raw.map(parseMarkers);
|
|
1190
|
+
return {
|
|
1191
|
+
lines: parsed.map((p) => p.text),
|
|
1192
|
+
evalSource: parsed.map((p) => p.text).join("\n"),
|
|
1193
|
+
hints: parsed
|
|
1194
|
+
};
|
|
1195
|
+
}, [source, markers]);
|
|
1196
|
+
const sheet = useMemo(() => evaluate(evalSource), [evaluate, evalSource]);
|
|
1197
|
+
const empty = evalSource.trim() === "";
|
|
1198
|
+
const titleId = useId();
|
|
1199
|
+
const { titleLine, derivedTitle } = useMemo(() => {
|
|
1200
|
+
const idx = lines.findIndex((l) => l.trim() !== "");
|
|
1201
|
+
const first = (lines[idx] ?? "").trim();
|
|
1202
|
+
return /^#+\s+/.test(first) ? { titleLine: idx + 1, derivedTitle: first.replace(/^#+\s*/, "") } : { titleLine: 0, derivedTitle: void 0 };
|
|
1203
|
+
}, [lines]);
|
|
1204
|
+
const hasTitle = title != null || derivedTitle != null;
|
|
1205
|
+
const resolvedTitle = title ?? derivedTitle ?? "calc";
|
|
1206
|
+
const resolvedTotal = total ?? sheet.total;
|
|
1207
|
+
const totalDisplay = isCalcValue(resolvedTotal) ? resolvedTotal.display : resolvedTotal;
|
|
1208
|
+
const rows = [];
|
|
1209
|
+
for (const result of sheet.results) {
|
|
1210
|
+
if (result.line === titleLine) continue;
|
|
1211
|
+
const text = lines[result.line - 1] ?? "";
|
|
1212
|
+
const key = `row-${String(result.line)}`;
|
|
1213
|
+
if (text.trim() === "") {
|
|
1214
|
+
rows.push(/* @__PURE__ */ jsx2("div", { className: "h-1.5", "aria-hidden": "true" }, key));
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
if (text.trim().startsWith("#")) {
|
|
1218
|
+
rows.push(
|
|
1219
|
+
/* @__PURE__ */ jsx2("div", { className: "pt-1 font-semibold text-foreground first:pt-0", children: text.replace(/^#+\s*/, "") }, key)
|
|
1220
|
+
);
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
const hint = hints[result.line - 1];
|
|
1224
|
+
const rule = result.rule ?? hint?.rule;
|
|
1225
|
+
const tint = result.tint ?? hint?.tint;
|
|
1226
|
+
rows.push(
|
|
1227
|
+
/* @__PURE__ */ jsxs2(
|
|
1228
|
+
"div",
|
|
1229
|
+
{
|
|
1230
|
+
"data-rule": rule,
|
|
1231
|
+
"data-tint": tint,
|
|
1232
|
+
className: cn2(
|
|
1233
|
+
"flex items-baseline justify-between gap-x-6",
|
|
1234
|
+
rule && RULE_CLASS[rule],
|
|
1235
|
+
tint != null && cn2("-mx-2 rounded-sm px-2", TINT_CLASS[tint])
|
|
1236
|
+
),
|
|
1237
|
+
children: [
|
|
1238
|
+
/* @__PURE__ */ jsx2("div", { className: "min-w-0 whitespace-pre-wrap break-words text-foreground", children: renderSource(text, result.tokens) }),
|
|
1239
|
+
/* @__PURE__ */ jsx2(ResultCell, { result })
|
|
1240
|
+
]
|
|
1241
|
+
},
|
|
1242
|
+
key
|
|
1243
|
+
)
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
return /* @__PURE__ */ jsxs2(
|
|
1247
|
+
"div",
|
|
1248
|
+
{
|
|
1249
|
+
ref,
|
|
1250
|
+
"data-testid": "calc-block",
|
|
1251
|
+
role: "group",
|
|
1252
|
+
"aria-labelledby": titleId,
|
|
1253
|
+
className: cn2("my-4 overflow-hidden rounded-md border border-border bg-card", className),
|
|
1254
|
+
...props,
|
|
1255
|
+
children: [
|
|
1256
|
+
/* @__PURE__ */ jsx2("div", { className: "border-b border-border px-4 py-1.5", children: /* @__PURE__ */ jsx2(
|
|
1257
|
+
"span",
|
|
1258
|
+
{
|
|
1259
|
+
id: titleId,
|
|
1260
|
+
className: cn2(
|
|
1261
|
+
"text-meta",
|
|
1262
|
+
hasTitle ? "font-medium text-foreground" : "font-mono uppercase tracking-wide text-muted-foreground"
|
|
1263
|
+
),
|
|
1264
|
+
children: resolvedTitle
|
|
1265
|
+
}
|
|
1266
|
+
) }),
|
|
1267
|
+
empty ? /* @__PURE__ */ jsx2("p", { className: "px-4 py-6 text-body text-muted-foreground", children: "Empty calc block." }) : /* @__PURE__ */ jsx2("div", { className: "flex flex-col gap-y-1 px-4 py-3 font-mono text-code leading-relaxed", children: rows }),
|
|
1268
|
+
showTotal && resolvedTotal != null && !empty ? /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between border-t border-border px-4 py-2", children: [
|
|
1269
|
+
/* @__PURE__ */ jsx2("span", { className: "text-meta font-medium uppercase tracking-wide text-muted-foreground", children: totalLabel }),
|
|
1270
|
+
/* @__PURE__ */ jsx2("span", { className: "font-mono text-code font-medium tabular-nums text-foreground", children: totalDisplay })
|
|
1271
|
+
] }) : null
|
|
1272
|
+
]
|
|
1273
|
+
}
|
|
1274
|
+
);
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
// src/calc-block/calc-inline.tsx
|
|
1278
|
+
import { cn as cn3 } from "@elabs-ai/components-ui/lib/cn";
|
|
1279
|
+
import { TriangleAlert as TriangleAlert2 } from "lucide-react";
|
|
1280
|
+
import { forwardRef as forwardRef2, useMemo as useMemo2 } from "react";
|
|
1281
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1282
|
+
var CalcInline = forwardRef2(function CalcInline2({ source, evaluate, className, ...props }, ref) {
|
|
1283
|
+
const sheet = useMemo2(() => evaluate(source), [evaluate, source]);
|
|
1284
|
+
const first = sheet.results[0];
|
|
1285
|
+
const display = first?.value?.display;
|
|
1286
|
+
const error = first?.error ?? (display == null ? { message: "No result" } : void 0);
|
|
1287
|
+
if (error) {
|
|
1288
|
+
return /* @__PURE__ */ jsxs3(
|
|
1289
|
+
"span",
|
|
1290
|
+
{
|
|
1291
|
+
ref,
|
|
1292
|
+
"data-testid": "calc-inline",
|
|
1293
|
+
"data-calc-error": "",
|
|
1294
|
+
title: error.message,
|
|
1295
|
+
"aria-label": `${source}: ${error.message}`,
|
|
1296
|
+
className: cn3(
|
|
1297
|
+
"inline-flex items-center gap-0.5 align-baseline font-mono text-calc-warning underline decoration-dotted decoration-1 underline-offset-2",
|
|
1298
|
+
className
|
|
1299
|
+
),
|
|
1300
|
+
...props,
|
|
1301
|
+
children: [
|
|
1302
|
+
/* @__PURE__ */ jsx3(TriangleAlert2, { className: "size-3", "aria-hidden": "true" }),
|
|
1303
|
+
/* @__PURE__ */ jsx3("span", { children: source })
|
|
1304
|
+
]
|
|
1305
|
+
}
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
return /* @__PURE__ */ jsx3(
|
|
1309
|
+
"span",
|
|
1310
|
+
{
|
|
1311
|
+
ref,
|
|
1312
|
+
"data-testid": "calc-inline",
|
|
1313
|
+
title: source,
|
|
1314
|
+
"aria-label": `${source} = ${display}`,
|
|
1315
|
+
className: cn3(
|
|
1316
|
+
"inline-flex items-center rounded-sm bg-calc-result/10 px-1 align-baseline font-mono tabular-nums text-calc-result",
|
|
1317
|
+
className
|
|
1318
|
+
),
|
|
1319
|
+
...props,
|
|
1320
|
+
children: display
|
|
1321
|
+
}
|
|
1322
|
+
);
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
// src/mermaid-diagram/mermaid-diagram.tsx
|
|
1326
|
+
import { Button as Button2, Dialog, DialogContent, DialogTitle } from "@elabs-ai/components-ui";
|
|
1327
|
+
import { cn as cn5 } from "@elabs-ai/components-ui/lib/cn";
|
|
1328
|
+
import { Download, Maximize2 } from "lucide-react";
|
|
1329
|
+
import { forwardRef as forwardRef3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
1330
|
+
import { oklchToHex } from "@elabs-ai/components-tokens";
|
|
1331
|
+
|
|
1332
|
+
// src/mermaid-diagram/mermaid-viewer.tsx
|
|
1333
|
+
import { Button, Input } from "@elabs-ai/components-ui";
|
|
1334
|
+
import { cn as cn4 } from "@elabs-ai/components-ui/lib/cn";
|
|
1335
|
+
import { Maximize, Minus, Plus } from "lucide-react";
|
|
1336
|
+
import {
|
|
1337
|
+
useCallback,
|
|
1338
|
+
useEffect as useEffect2,
|
|
1339
|
+
useMemo as useMemo3,
|
|
1340
|
+
useRef as useRef2,
|
|
1341
|
+
useState as useState2
|
|
1342
|
+
} from "react";
|
|
1343
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1344
|
+
var MIN_SCALE = 0.2;
|
|
1345
|
+
var MAX_SCALE = 6;
|
|
1346
|
+
var HIT_CLASS = "wb-dg-hit";
|
|
1347
|
+
var HIT_ACTIVE_CLASS = "wb-dg-hit-active";
|
|
1348
|
+
var clampScale = (s) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, s));
|
|
1349
|
+
function fitTransform(container, natural, pad = 24) {
|
|
1350
|
+
if (container.width <= pad || container.height <= pad) return null;
|
|
1351
|
+
if (!natural.width || !natural.height) return null;
|
|
1352
|
+
const scale = clampScale(
|
|
1353
|
+
Math.min(1, (container.width - pad) / natural.width, (container.height - pad) / natural.height)
|
|
1354
|
+
);
|
|
1355
|
+
return {
|
|
1356
|
+
scale,
|
|
1357
|
+
tx: (container.width - natural.width * scale) / 2,
|
|
1358
|
+
ty: (container.height - natural.height * scale) / 2
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
function diagramNodeLabel(node) {
|
|
1362
|
+
const candidates = Array.from(node.querySelectorAll("tspan, p, span"));
|
|
1363
|
+
const leaves = candidates.filter((el) => !el.querySelector("tspan, p, span"));
|
|
1364
|
+
const parts = leaves.map((el) => (el.textContent ?? "").trim().replace(/\s+/g, " ")).filter(Boolean);
|
|
1365
|
+
if (parts.length === 0) return (node.textContent ?? "").trim().replace(/\s+/g, " ");
|
|
1366
|
+
return parts.join(" \xB7 ");
|
|
1367
|
+
}
|
|
1368
|
+
function MermaidViewer({ svg, label }) {
|
|
1369
|
+
const containerRef = useRef2(null);
|
|
1370
|
+
const stageRef = useRef2(null);
|
|
1371
|
+
const [transform, setTransform] = useState2({ scale: 1, tx: 0, ty: 0 });
|
|
1372
|
+
const [hits, setHits] = useState2([]);
|
|
1373
|
+
const [query, setQuery] = useState2("");
|
|
1374
|
+
const [activeHit, setActiveHit] = useState2(null);
|
|
1375
|
+
const dragRef = useRef2(null);
|
|
1376
|
+
const userDrivenRef = useRef2(false);
|
|
1377
|
+
const naturalSize = useRef2({ w: 0, h: 0 });
|
|
1378
|
+
const fit = useCallback(() => {
|
|
1379
|
+
const container = containerRef.current;
|
|
1380
|
+
const { w, h } = naturalSize.current;
|
|
1381
|
+
if (!container) return false;
|
|
1382
|
+
const next = fitTransform(
|
|
1383
|
+
{ width: container.clientWidth, height: container.clientHeight },
|
|
1384
|
+
{ width: w, height: h }
|
|
1385
|
+
);
|
|
1386
|
+
if (next) setTransform(next);
|
|
1387
|
+
return next !== null;
|
|
1388
|
+
}, []);
|
|
1389
|
+
useEffect2(() => {
|
|
1390
|
+
const stage = stageRef.current;
|
|
1391
|
+
const svgEl = stage?.querySelector("svg");
|
|
1392
|
+
if (!stage || !svgEl) return;
|
|
1393
|
+
const viewBox = svgEl.viewBox?.baseVal;
|
|
1394
|
+
const w = viewBox?.width || svgEl.getBoundingClientRect().width || 800;
|
|
1395
|
+
const h = viewBox?.height || svgEl.getBoundingClientRect().height || 600;
|
|
1396
|
+
svgEl.setAttribute("style", `max-width:none;width:${w}px;height:${h}px;`);
|
|
1397
|
+
svgEl.setAttribute("width", String(w));
|
|
1398
|
+
svgEl.setAttribute("height", String(h));
|
|
1399
|
+
const rect = svgEl.getBoundingClientRect();
|
|
1400
|
+
naturalSize.current = {
|
|
1401
|
+
w: rect.width > 0 ? rect.width : w,
|
|
1402
|
+
h: rect.height > 0 ? rect.height : h
|
|
1403
|
+
};
|
|
1404
|
+
const found = [];
|
|
1405
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1406
|
+
for (const node of svgEl.querySelectorAll("g.node, g.edgeLabel")) {
|
|
1407
|
+
const text = diagramNodeLabel(node);
|
|
1408
|
+
if (!text || !node.id) continue;
|
|
1409
|
+
if (seen.has(node.id)) continue;
|
|
1410
|
+
seen.add(node.id);
|
|
1411
|
+
found.push({ id: node.id, label: text });
|
|
1412
|
+
}
|
|
1413
|
+
setHits(found);
|
|
1414
|
+
if (!fit()) {
|
|
1415
|
+
let tries = 0;
|
|
1416
|
+
let raf = 0;
|
|
1417
|
+
const attempt = () => {
|
|
1418
|
+
if (userDrivenRef.current) return;
|
|
1419
|
+
if (!fit() && ++tries < 30) raf = requestAnimationFrame(attempt);
|
|
1420
|
+
};
|
|
1421
|
+
raf = requestAnimationFrame(attempt);
|
|
1422
|
+
return () => cancelAnimationFrame(raf);
|
|
1423
|
+
}
|
|
1424
|
+
}, [svg, fit]);
|
|
1425
|
+
useEffect2(() => {
|
|
1426
|
+
const container = containerRef.current;
|
|
1427
|
+
if (!container || typeof ResizeObserver === "undefined") return;
|
|
1428
|
+
const observer = new ResizeObserver(() => {
|
|
1429
|
+
if (!userDrivenRef.current) fit();
|
|
1430
|
+
});
|
|
1431
|
+
observer.observe(container);
|
|
1432
|
+
return () => observer.disconnect();
|
|
1433
|
+
}, [fit]);
|
|
1434
|
+
const q = query.trim().toLowerCase();
|
|
1435
|
+
const matches = useMemo3(
|
|
1436
|
+
() => q.length >= 2 ? hits.filter((hit) => hit.label.toLowerCase().includes(q)) : [],
|
|
1437
|
+
[hits, q]
|
|
1438
|
+
);
|
|
1439
|
+
useEffect2(() => {
|
|
1440
|
+
const svgEl = stageRef.current?.querySelector("svg");
|
|
1441
|
+
if (!svgEl) return;
|
|
1442
|
+
const matchIds = new Set(matches.map((m) => m.id));
|
|
1443
|
+
for (const node of svgEl.querySelectorAll("g.node, g.edgeLabel")) {
|
|
1444
|
+
node.classList.toggle(HIT_CLASS, matchIds.has(node.id));
|
|
1445
|
+
node.classList.toggle(HIT_ACTIVE_CLASS, node.id === activeHit);
|
|
1446
|
+
}
|
|
1447
|
+
}, [matches, activeHit]);
|
|
1448
|
+
const zoomAt = useCallback((clientX, clientY, factor) => {
|
|
1449
|
+
const container = containerRef.current;
|
|
1450
|
+
if (!container) return;
|
|
1451
|
+
userDrivenRef.current = true;
|
|
1452
|
+
const rect = container.getBoundingClientRect();
|
|
1453
|
+
setTransform((t) => {
|
|
1454
|
+
const scale = clampScale(t.scale * factor);
|
|
1455
|
+
const px = (clientX - rect.left - t.tx) / t.scale;
|
|
1456
|
+
const py = (clientY - rect.top - t.ty) / t.scale;
|
|
1457
|
+
return { scale, tx: clientX - rect.left - px * scale, ty: clientY - rect.top - py * scale };
|
|
1458
|
+
});
|
|
1459
|
+
}, []);
|
|
1460
|
+
const zoomCenter = (factor) => {
|
|
1461
|
+
const container = containerRef.current;
|
|
1462
|
+
if (!container) return;
|
|
1463
|
+
const rect = container.getBoundingClientRect();
|
|
1464
|
+
zoomAt(rect.left + rect.width / 2, rect.top + rect.height / 2, factor);
|
|
1465
|
+
};
|
|
1466
|
+
const zoomToHit = useCallback((id) => {
|
|
1467
|
+
const container = containerRef.current;
|
|
1468
|
+
const svgEl = stageRef.current?.querySelector("svg");
|
|
1469
|
+
const node = svgEl?.querySelector(`[id="${CSS.escape(id)}"]`);
|
|
1470
|
+
if (!container || !svgEl || !node) return;
|
|
1471
|
+
userDrivenRef.current = true;
|
|
1472
|
+
setActiveHit(id);
|
|
1473
|
+
setTransform((t) => {
|
|
1474
|
+
const nodeRect = node.getBoundingClientRect();
|
|
1475
|
+
const containerRect = container.getBoundingClientRect();
|
|
1476
|
+
const cx = (nodeRect.left + nodeRect.width / 2 - containerRect.left - t.tx) / t.scale;
|
|
1477
|
+
const cy = (nodeRect.top + nodeRect.height / 2 - containerRect.top - t.ty) / t.scale;
|
|
1478
|
+
const scale = clampScale(Math.max(t.scale, 1.25));
|
|
1479
|
+
return {
|
|
1480
|
+
scale,
|
|
1481
|
+
tx: containerRect.width / 2 - cx * scale,
|
|
1482
|
+
ty: containerRect.height / 2 - cy * scale
|
|
1483
|
+
};
|
|
1484
|
+
});
|
|
1485
|
+
}, []);
|
|
1486
|
+
const onWheel = (e) => {
|
|
1487
|
+
e.preventDefault();
|
|
1488
|
+
zoomAt(e.clientX, e.clientY, e.deltaY < 0 ? 1.12 : 1 / 1.12);
|
|
1489
|
+
};
|
|
1490
|
+
const onPointerDown = (e) => {
|
|
1491
|
+
if (e.button !== 0) return;
|
|
1492
|
+
userDrivenRef.current = true;
|
|
1493
|
+
dragRef.current = { x: e.clientX, y: e.clientY, tx: transform.tx, ty: transform.ty };
|
|
1494
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1495
|
+
};
|
|
1496
|
+
const onPointerMove = (e) => {
|
|
1497
|
+
const drag = dragRef.current;
|
|
1498
|
+
if (!drag) return;
|
|
1499
|
+
setTransform((t) => ({
|
|
1500
|
+
...t,
|
|
1501
|
+
tx: drag.tx + (e.clientX - drag.x),
|
|
1502
|
+
ty: drag.ty + (e.clientY - drag.y)
|
|
1503
|
+
}));
|
|
1504
|
+
};
|
|
1505
|
+
const onPointerUp = () => {
|
|
1506
|
+
dragRef.current = null;
|
|
1507
|
+
};
|
|
1508
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 flex-1 gap-3", children: [
|
|
1509
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex w-60 shrink-0 flex-col border-e border-border pe-3", children: [
|
|
1510
|
+
/* @__PURE__ */ jsx4(
|
|
1511
|
+
Input,
|
|
1512
|
+
{
|
|
1513
|
+
autoFocus: true,
|
|
1514
|
+
type: "search",
|
|
1515
|
+
placeholder: "Find in diagram\u2026",
|
|
1516
|
+
"aria-label": "Find in diagram",
|
|
1517
|
+
value: query,
|
|
1518
|
+
onChange: (e) => setQuery(e.target.value),
|
|
1519
|
+
spellCheck: false,
|
|
1520
|
+
className: "h-8 text-body"
|
|
1521
|
+
}
|
|
1522
|
+
),
|
|
1523
|
+
/* @__PURE__ */ jsx4("p", { "aria-live": "polite", className: "px-1 pt-1.5 text-meta text-muted-foreground tabular-nums", children: q.length >= 2 ? `${matches.length} ${matches.length === 1 ? "node" : "nodes"}` : `${hits.length} nodes \xB7 type to filter` }),
|
|
1524
|
+
/* @__PURE__ */ jsx4("ul", { className: "m-0 mt-1 min-h-0 flex-1 list-none overflow-auto p-0", children: (q.length >= 2 ? matches : hits).map((hit) => /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(
|
|
1525
|
+
"button",
|
|
1526
|
+
{
|
|
1527
|
+
type: "button",
|
|
1528
|
+
onClick: () => zoomToHit(hit.id),
|
|
1529
|
+
"aria-pressed": activeHit === hit.id,
|
|
1530
|
+
className: cn4(
|
|
1531
|
+
"w-full truncate rounded-md px-2 py-1.5 text-start text-caption transition-colors duration-fast ease-standard motion-reduce:transition-none",
|
|
1532
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
1533
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
1534
|
+
activeHit === hit.id ? "bg-accent font-medium text-foreground" : "text-muted-foreground"
|
|
1535
|
+
),
|
|
1536
|
+
children: hit.label
|
|
1537
|
+
}
|
|
1538
|
+
) }, hit.id)) })
|
|
1539
|
+
] }),
|
|
1540
|
+
/* @__PURE__ */ jsxs4("div", { className: "relative min-h-0 min-w-0 flex-1", children: [
|
|
1541
|
+
/* @__PURE__ */ jsxs4("div", { className: "absolute end-2 top-2 z-10 flex gap-1", children: [
|
|
1542
|
+
/* @__PURE__ */ jsx4(
|
|
1543
|
+
Button,
|
|
1544
|
+
{
|
|
1545
|
+
variant: "outline",
|
|
1546
|
+
size: "icon-sm",
|
|
1547
|
+
"aria-label": "Zoom out",
|
|
1548
|
+
onClick: () => zoomCenter(1 / 1.25),
|
|
1549
|
+
children: /* @__PURE__ */ jsx4(Minus, { className: "size-3.5" })
|
|
1550
|
+
}
|
|
1551
|
+
),
|
|
1552
|
+
/* @__PURE__ */ jsx4(
|
|
1553
|
+
Button,
|
|
1554
|
+
{
|
|
1555
|
+
variant: "outline",
|
|
1556
|
+
size: "icon-sm",
|
|
1557
|
+
"aria-label": "Zoom in",
|
|
1558
|
+
onClick: () => zoomCenter(1.25),
|
|
1559
|
+
children: /* @__PURE__ */ jsx4(Plus, { className: "size-3.5" })
|
|
1560
|
+
}
|
|
1561
|
+
),
|
|
1562
|
+
/* @__PURE__ */ jsxs4(
|
|
1563
|
+
Button,
|
|
1564
|
+
{
|
|
1565
|
+
variant: "outline",
|
|
1566
|
+
size: "sm",
|
|
1567
|
+
className: "h-7 px-2 font-mono text-meta tabular-nums",
|
|
1568
|
+
"aria-label": "Reset zoom to 100%",
|
|
1569
|
+
onClick: () => {
|
|
1570
|
+
userDrivenRef.current = true;
|
|
1571
|
+
setTransform((t) => ({ ...t, scale: 1 }));
|
|
1572
|
+
},
|
|
1573
|
+
children: [
|
|
1574
|
+
Math.round(transform.scale * 100),
|
|
1575
|
+
"%"
|
|
1576
|
+
]
|
|
1577
|
+
}
|
|
1578
|
+
),
|
|
1579
|
+
/* @__PURE__ */ jsx4(
|
|
1580
|
+
Button,
|
|
1581
|
+
{
|
|
1582
|
+
variant: "outline",
|
|
1583
|
+
size: "icon-sm",
|
|
1584
|
+
"aria-label": "Fit diagram",
|
|
1585
|
+
onClick: () => {
|
|
1586
|
+
userDrivenRef.current = false;
|
|
1587
|
+
fit();
|
|
1588
|
+
},
|
|
1589
|
+
children: /* @__PURE__ */ jsx4(Maximize, { className: "size-3.5" })
|
|
1590
|
+
}
|
|
1591
|
+
)
|
|
1592
|
+
] }),
|
|
1593
|
+
/* @__PURE__ */ jsx4(
|
|
1594
|
+
"div",
|
|
1595
|
+
{
|
|
1596
|
+
ref: containerRef,
|
|
1597
|
+
role: "img",
|
|
1598
|
+
"aria-label": label,
|
|
1599
|
+
className: cn4(
|
|
1600
|
+
"h-full w-full touch-none select-none overflow-hidden rounded-md bg-surface-muted/50",
|
|
1601
|
+
dragRef.current ? "cursor-grabbing" : "cursor-grab"
|
|
1602
|
+
),
|
|
1603
|
+
onWheel,
|
|
1604
|
+
onPointerDown,
|
|
1605
|
+
onPointerMove,
|
|
1606
|
+
onPointerUp,
|
|
1607
|
+
onPointerCancel: onPointerUp,
|
|
1608
|
+
onDoubleClick: (e) => zoomAt(e.clientX, e.clientY, 1.5),
|
|
1609
|
+
children: /* @__PURE__ */ jsx4(
|
|
1610
|
+
"div",
|
|
1611
|
+
{
|
|
1612
|
+
ref: stageRef,
|
|
1613
|
+
style: {
|
|
1614
|
+
transform: `translate(${transform.tx}px, ${transform.ty}px) scale(${transform.scale})`,
|
|
1615
|
+
transformOrigin: "0 0"
|
|
1616
|
+
},
|
|
1617
|
+
dangerouslySetInnerHTML: { __html: svg }
|
|
1618
|
+
}
|
|
1619
|
+
)
|
|
1620
|
+
}
|
|
1621
|
+
)
|
|
1622
|
+
] })
|
|
1623
|
+
] });
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// src/mermaid-diagram/remediate.ts
|
|
1627
|
+
var FLOWCHART_RESERVED = /* @__PURE__ */ new Set([
|
|
1628
|
+
"graph",
|
|
1629
|
+
"flowchart",
|
|
1630
|
+
"subgraph",
|
|
1631
|
+
"end",
|
|
1632
|
+
"style",
|
|
1633
|
+
"linkstyle",
|
|
1634
|
+
"classdef",
|
|
1635
|
+
"class",
|
|
1636
|
+
"click",
|
|
1637
|
+
"direction",
|
|
1638
|
+
"default",
|
|
1639
|
+
"state"
|
|
1640
|
+
]);
|
|
1641
|
+
function offendingToken(errorMessage) {
|
|
1642
|
+
const m = /got '([A-Za-z_]+)'/.exec(errorMessage);
|
|
1643
|
+
return m ? m[1].toLowerCase() : null;
|
|
1644
|
+
}
|
|
1645
|
+
var DECLARATION_RE = /^(graph|flowchart)\s+(tb|td|bt|rl|lr)\s*;?\s*$/;
|
|
1646
|
+
function replaceOutsideLabels(line, re, repl) {
|
|
1647
|
+
let out = "";
|
|
1648
|
+
let buf = "";
|
|
1649
|
+
let depth = 0;
|
|
1650
|
+
let inQuote = false;
|
|
1651
|
+
let inPipe = false;
|
|
1652
|
+
const flush = () => {
|
|
1653
|
+
out += buf.replace(re, repl);
|
|
1654
|
+
buf = "";
|
|
1655
|
+
};
|
|
1656
|
+
for (const ch of line) {
|
|
1657
|
+
if (inQuote) {
|
|
1658
|
+
out += ch;
|
|
1659
|
+
if (ch === '"') inQuote = false;
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
if (depth === 0 && ch === "|") {
|
|
1663
|
+
if (!inPipe) flush();
|
|
1664
|
+
inPipe = !inPipe;
|
|
1665
|
+
out += ch;
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
if (inPipe) {
|
|
1669
|
+
out += ch;
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
if (ch === '"') {
|
|
1673
|
+
flush();
|
|
1674
|
+
out += ch;
|
|
1675
|
+
inQuote = true;
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
if (ch === "[" || ch === "(" || ch === "{") {
|
|
1679
|
+
if (depth === 0) flush();
|
|
1680
|
+
depth++;
|
|
1681
|
+
out += ch;
|
|
1682
|
+
continue;
|
|
1683
|
+
}
|
|
1684
|
+
if (ch === "]" || ch === ")" || ch === "}") {
|
|
1685
|
+
if (depth > 0) depth--;
|
|
1686
|
+
if (depth === 0) {
|
|
1687
|
+
out += ch;
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
out += ch;
|
|
1691
|
+
continue;
|
|
1692
|
+
}
|
|
1693
|
+
if (depth === 0) buf += ch;
|
|
1694
|
+
else out += ch;
|
|
1695
|
+
}
|
|
1696
|
+
flush();
|
|
1697
|
+
return out;
|
|
1698
|
+
}
|
|
1699
|
+
function remediateReservedIds(chart, token) {
|
|
1700
|
+
const t = token.toLowerCase();
|
|
1701
|
+
if (!FLOWCHART_RESERVED.has(t)) return null;
|
|
1702
|
+
const re = new RegExp(`\\b${t}\\b`, "gi");
|
|
1703
|
+
let changed = false;
|
|
1704
|
+
const repl = `${t}_`;
|
|
1705
|
+
const out = chart.split("\n").map((line) => {
|
|
1706
|
+
const trimmed = line.trim().toLowerCase();
|
|
1707
|
+
if (trimmed === t) return line;
|
|
1708
|
+
if (DECLARATION_RE.test(trimmed)) return line;
|
|
1709
|
+
if (t === "subgraph" && trimmed.startsWith("subgraph")) return line;
|
|
1710
|
+
const next = replaceOutsideLabels(line, re, repl);
|
|
1711
|
+
if (next !== line) changed = true;
|
|
1712
|
+
return next;
|
|
1713
|
+
}).join("\n");
|
|
1714
|
+
return changed ? out : null;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/mermaid-diagram/mermaid-diagram.tsx
|
|
1718
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1719
|
+
var TOKEN_VARS = {
|
|
1720
|
+
background: "--background",
|
|
1721
|
+
mainBkg: "--card",
|
|
1722
|
+
primaryColor: "--muted",
|
|
1723
|
+
primaryTextColor: "--foreground",
|
|
1724
|
+
primaryBorderColor: "--border-strong",
|
|
1725
|
+
secondaryColor: "--secondary",
|
|
1726
|
+
tertiaryColor: "--muted",
|
|
1727
|
+
lineColor: "--muted-foreground",
|
|
1728
|
+
textColor: "--foreground",
|
|
1729
|
+
nodeBorder: "--border-strong",
|
|
1730
|
+
clusterBkg: "--surface-muted",
|
|
1731
|
+
clusterBorder: "--border",
|
|
1732
|
+
titleColor: "--foreground",
|
|
1733
|
+
edgeLabelBackground: "--background",
|
|
1734
|
+
errorBkgColor: "--destructive",
|
|
1735
|
+
errorTextColor: "--destructive-foreground"
|
|
1736
|
+
};
|
|
1737
|
+
var renderSeq = 0;
|
|
1738
|
+
var HIT_CSS = `
|
|
1739
|
+
.wb-dg-hit :is(rect, polygon, circle, ellipse, path.basic) { stroke: var(--warning) !important; stroke-width: 2.5px !important; }
|
|
1740
|
+
.wb-dg-hit-active :is(rect, polygon, circle, ellipse, path.basic) { stroke: var(--primary) !important; stroke-width: 3px !important; }
|
|
1741
|
+
.wb-dg-hit.edgeLabel { outline: 2px solid var(--warning); border-radius: 2px; }
|
|
1742
|
+
.wb-dg-hit-active.edgeLabel { outline: 2px solid var(--primary); border-radius: 2px; }
|
|
1743
|
+
`;
|
|
1744
|
+
var KHROMA_SAFE_RE = /^(#|rgba?\(|hsla?\()/i;
|
|
1745
|
+
function normalizeColor(value, ctx) {
|
|
1746
|
+
const v = value.trim();
|
|
1747
|
+
if (!v || KHROMA_SAFE_RE.test(v)) return v;
|
|
1748
|
+
const fromOklch = oklchToHex(v);
|
|
1749
|
+
if (fromOklch) return fromOklch;
|
|
1750
|
+
if (ctx) {
|
|
1751
|
+
try {
|
|
1752
|
+
ctx.fillStyle = "#000";
|
|
1753
|
+
ctx.fillStyle = v;
|
|
1754
|
+
if (KHROMA_SAFE_RE.test(ctx.fillStyle)) return ctx.fillStyle;
|
|
1755
|
+
} catch {
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return v;
|
|
1759
|
+
}
|
|
1760
|
+
function resolveThemeVariables(el) {
|
|
1761
|
+
const styles = getComputedStyle(el);
|
|
1762
|
+
let ctx = null;
|
|
1763
|
+
try {
|
|
1764
|
+
ctx = document.createElement("canvas").getContext("2d");
|
|
1765
|
+
} catch {
|
|
1766
|
+
ctx = null;
|
|
1767
|
+
}
|
|
1768
|
+
const vars = {
|
|
1769
|
+
fontFamily: styles.getPropertyValue("--font-sans").trim() || "inherit"
|
|
1770
|
+
};
|
|
1771
|
+
for (const [mermaidVar, token] of Object.entries(TOKEN_VARS)) {
|
|
1772
|
+
const raw = styles.getPropertyValue(token).trim();
|
|
1773
|
+
if (raw) vars[mermaidVar] = normalizeColor(raw, ctx);
|
|
1774
|
+
}
|
|
1775
|
+
return vars;
|
|
1776
|
+
}
|
|
1777
|
+
var MermaidDiagram = forwardRef3(
|
|
1778
|
+
function MermaidDiagram2({
|
|
1779
|
+
chart,
|
|
1780
|
+
label = "Diagram",
|
|
1781
|
+
copyable = true,
|
|
1782
|
+
expandable = true,
|
|
1783
|
+
highlightTerm,
|
|
1784
|
+
activeText,
|
|
1785
|
+
className,
|
|
1786
|
+
...props
|
|
1787
|
+
}, ref) {
|
|
1788
|
+
const hostRef = useRef3(null);
|
|
1789
|
+
const svgHostRef = useRef3(null);
|
|
1790
|
+
const [svg, setSvg] = useState3(null);
|
|
1791
|
+
const [error, setError] = useState3(null);
|
|
1792
|
+
const [expanded, setExpanded] = useState3(false);
|
|
1793
|
+
const downloadSvg = () => {
|
|
1794
|
+
if (!svg) return;
|
|
1795
|
+
const blob = new Blob([svg], { type: "image/svg+xml" });
|
|
1796
|
+
const url = URL.createObjectURL(blob);
|
|
1797
|
+
const a = document.createElement("a");
|
|
1798
|
+
a.href = url;
|
|
1799
|
+
a.download = `${label.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "diagram"}.svg`;
|
|
1800
|
+
a.click();
|
|
1801
|
+
URL.revokeObjectURL(url);
|
|
1802
|
+
};
|
|
1803
|
+
const [themeVersion, setThemeVersion] = useState3(0);
|
|
1804
|
+
useEffect3(() => {
|
|
1805
|
+
const host = hostRef.current;
|
|
1806
|
+
if (!host) return;
|
|
1807
|
+
const scope = host.closest("[data-theme]") ?? document.documentElement;
|
|
1808
|
+
const observer = new MutationObserver(() => setThemeVersion((v) => v + 1));
|
|
1809
|
+
observer.observe(scope, { attributes: true, attributeFilter: ["data-theme"] });
|
|
1810
|
+
return () => observer.disconnect();
|
|
1811
|
+
}, []);
|
|
1812
|
+
useEffect3(() => {
|
|
1813
|
+
let cancelled = false;
|
|
1814
|
+
const host = hostRef.current;
|
|
1815
|
+
if (!host || !chart.trim()) {
|
|
1816
|
+
setSvg(null);
|
|
1817
|
+
setError(null);
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
(async () => {
|
|
1821
|
+
try {
|
|
1822
|
+
const mermaid = (await import("mermaid")).default;
|
|
1823
|
+
if (cancelled) return;
|
|
1824
|
+
mermaid.initialize({
|
|
1825
|
+
startOnLoad: false,
|
|
1826
|
+
securityLevel: "strict",
|
|
1827
|
+
suppressErrorRendering: true,
|
|
1828
|
+
theme: "base",
|
|
1829
|
+
themeVariables: resolveThemeVariables(host)
|
|
1830
|
+
});
|
|
1831
|
+
const render = (source) => mermaid.render(`brand-mermaid-${++renderSeq}`, source);
|
|
1832
|
+
let out;
|
|
1833
|
+
try {
|
|
1834
|
+
out = await render(chart);
|
|
1835
|
+
} catch (firstErr) {
|
|
1836
|
+
const token = offendingToken(
|
|
1837
|
+
firstErr instanceof Error ? firstErr.message : String(firstErr)
|
|
1838
|
+
);
|
|
1839
|
+
const fixed = token ? remediateReservedIds(chart, token) : null;
|
|
1840
|
+
if (!fixed) throw firstErr;
|
|
1841
|
+
out = await render(fixed);
|
|
1842
|
+
}
|
|
1843
|
+
if (cancelled) return;
|
|
1844
|
+
setSvg(out.svg);
|
|
1845
|
+
setError(null);
|
|
1846
|
+
} catch (err) {
|
|
1847
|
+
if (cancelled) return;
|
|
1848
|
+
setSvg(null);
|
|
1849
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1850
|
+
}
|
|
1851
|
+
})();
|
|
1852
|
+
return () => {
|
|
1853
|
+
cancelled = true;
|
|
1854
|
+
};
|
|
1855
|
+
}, [chart, themeVersion]);
|
|
1856
|
+
useEffect3(() => {
|
|
1857
|
+
const root = svgHostRef.current;
|
|
1858
|
+
if (!root) return;
|
|
1859
|
+
const term = (highlightTerm ?? "").trim().toLowerCase();
|
|
1860
|
+
const active = (activeText ?? "").trim().toLowerCase();
|
|
1861
|
+
for (const node of root.querySelectorAll("g.node, g.edgeLabel")) {
|
|
1862
|
+
const text = (node.textContent ?? "").trim().toLowerCase();
|
|
1863
|
+
const hit = term.length >= 2 && text.length > 0 && text.includes(term);
|
|
1864
|
+
node.classList.toggle("wb-dg-hit", hit);
|
|
1865
|
+
node.classList.toggle(
|
|
1866
|
+
"wb-dg-hit-active",
|
|
1867
|
+
hit && active.length > 0 && (active.includes(text) || text.includes(active))
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
}, [svg, highlightTerm, activeText]);
|
|
1871
|
+
return /* @__PURE__ */ jsxs5(
|
|
1872
|
+
"div",
|
|
1873
|
+
{
|
|
1874
|
+
ref: (el) => {
|
|
1875
|
+
hostRef.current = el;
|
|
1876
|
+
if (typeof ref === "function") ref(el);
|
|
1877
|
+
else if (ref) ref.current = el;
|
|
1878
|
+
},
|
|
1879
|
+
"data-testid": "mermaid-diagram",
|
|
1880
|
+
className: cn5("group/mermaid relative", className),
|
|
1881
|
+
...props,
|
|
1882
|
+
children: [
|
|
1883
|
+
svg && (copyable || expandable) ? /* @__PURE__ */ jsxs5("div", { className: "absolute end-2 top-2 z-10 flex gap-1 opacity-0 transition-opacity duration-fast ease-standard focus-within:opacity-100 group-hover/mermaid:opacity-100 motion-reduce:transition-none", children: [
|
|
1884
|
+
expandable ? /* @__PURE__ */ jsx5(
|
|
1885
|
+
Button2,
|
|
1886
|
+
{
|
|
1887
|
+
variant: "outline",
|
|
1888
|
+
size: "icon-sm",
|
|
1889
|
+
"aria-label": "Expand diagram",
|
|
1890
|
+
onClick: () => setExpanded(true),
|
|
1891
|
+
children: /* @__PURE__ */ jsx5(Maximize2, { className: "size-3.5" })
|
|
1892
|
+
}
|
|
1893
|
+
) : null,
|
|
1894
|
+
/* @__PURE__ */ jsx5(
|
|
1895
|
+
Button2,
|
|
1896
|
+
{
|
|
1897
|
+
variant: "outline",
|
|
1898
|
+
size: "icon-sm",
|
|
1899
|
+
"aria-label": "Download diagram as SVG",
|
|
1900
|
+
onClick: downloadSvg,
|
|
1901
|
+
children: /* @__PURE__ */ jsx5(Download, { className: "size-3.5" })
|
|
1902
|
+
}
|
|
1903
|
+
),
|
|
1904
|
+
copyable ? /* @__PURE__ */ jsx5(
|
|
1905
|
+
CopyButton,
|
|
1906
|
+
{
|
|
1907
|
+
value: chart,
|
|
1908
|
+
label: false,
|
|
1909
|
+
"aria-label": "Copy diagram source",
|
|
1910
|
+
size: "icon-sm"
|
|
1911
|
+
}
|
|
1912
|
+
) : null
|
|
1913
|
+
] }) : null,
|
|
1914
|
+
error ? /* @__PURE__ */ jsxs5(
|
|
1915
|
+
"div",
|
|
1916
|
+
{
|
|
1917
|
+
role: "alert",
|
|
1918
|
+
className: "space-y-2 border-s-2 border-s-destructive bg-destructive/10 p-3 text-body",
|
|
1919
|
+
children: [
|
|
1920
|
+
/* @__PURE__ */ jsx5("p", { className: "font-medium text-destructive-text", children: "Diagram failed to render" }),
|
|
1921
|
+
/* @__PURE__ */ jsx5("p", { className: "text-muted-foreground", children: error }),
|
|
1922
|
+
/* @__PURE__ */ jsx5("pre", { className: "overflow-x-auto rounded bg-muted p-2 font-mono text-code text-foreground", children: chart })
|
|
1923
|
+
]
|
|
1924
|
+
}
|
|
1925
|
+
) : svg ? /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1926
|
+
/* @__PURE__ */ jsx5("style", { children: HIT_CSS }),
|
|
1927
|
+
/* @__PURE__ */ jsx5(
|
|
1928
|
+
"div",
|
|
1929
|
+
{
|
|
1930
|
+
ref: svgHostRef,
|
|
1931
|
+
role: "img",
|
|
1932
|
+
"aria-label": label,
|
|
1933
|
+
className: "overflow-x-auto rounded-md bg-card p-3 [&_svg]:mx-auto [&_svg]:h-auto [&_svg]:max-w-full",
|
|
1934
|
+
dangerouslySetInnerHTML: { __html: svg }
|
|
1935
|
+
}
|
|
1936
|
+
)
|
|
1937
|
+
] }) : /* @__PURE__ */ jsx5(
|
|
1938
|
+
"div",
|
|
1939
|
+
{
|
|
1940
|
+
role: "status",
|
|
1941
|
+
"aria-label": "Rendering diagram\u2026",
|
|
1942
|
+
className: "h-24 animate-pulse rounded-md bg-surface-muted motion-reduce:animate-none"
|
|
1943
|
+
}
|
|
1944
|
+
),
|
|
1945
|
+
expandable ? /* @__PURE__ */ jsx5(Dialog, { open: expanded, onOpenChange: setExpanded, children: /* @__PURE__ */ jsxs5(DialogContent, { className: "flex h-[88dvh] w-[92vw] max-w-[92vw] flex-col p-4 sm:max-w-[92vw]", children: [
|
|
1946
|
+
/* @__PURE__ */ jsx5(DialogTitle, { className: "sr-only", children: label }),
|
|
1947
|
+
svg ? /* @__PURE__ */ jsx5(MermaidViewer, { svg, label }) : null
|
|
1948
|
+
] }) }) : null
|
|
1949
|
+
]
|
|
1950
|
+
}
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
);
|
|
1954
|
+
|
|
1955
|
+
// src/prose/prose.tsx
|
|
1956
|
+
import {
|
|
1957
|
+
ProseHeading,
|
|
1958
|
+
ProseText,
|
|
1959
|
+
ProseLink,
|
|
1960
|
+
ProseList,
|
|
1961
|
+
ProseListItem,
|
|
1962
|
+
ProseBlockquote,
|
|
1963
|
+
ProseInlineCode
|
|
1964
|
+
} from "@elabs-ai/components-ui";
|
|
1965
|
+
|
|
1966
|
+
// src/timeline/index.ts
|
|
1967
|
+
import {
|
|
1968
|
+
Timeline
|
|
1969
|
+
} from "@elabs-ai/components-ui";
|
|
1970
|
+
|
|
1971
|
+
// src/markdown-preview/code-fence.tsx
|
|
1972
|
+
import { cn as cn6 } from "@elabs-ai/components-ui/lib/cn";
|
|
1973
|
+
import { code as codeHighlighter } from "@streamdown/code";
|
|
1974
|
+
import {
|
|
1975
|
+
useEffect as useEffect4,
|
|
1976
|
+
useRef as useRef4,
|
|
1977
|
+
useState as useState4
|
|
1978
|
+
} from "react";
|
|
1979
|
+
import { createCssVariablesTheme } from "shiki";
|
|
1980
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1981
|
+
function fenceLanguage(className) {
|
|
1982
|
+
return /\blanguage-([\w+#.-]+)\b/.exec(className ?? "")?.[1];
|
|
1983
|
+
}
|
|
1984
|
+
var cssVariablesTheme = createCssVariablesTheme({
|
|
1985
|
+
name: "brand-tokens",
|
|
1986
|
+
variablePrefix: "--md-code-",
|
|
1987
|
+
fontStyle: true
|
|
1988
|
+
});
|
|
1989
|
+
var SHIKI_THEMES = [
|
|
1990
|
+
cssVariablesTheme,
|
|
1991
|
+
cssVariablesTheme
|
|
1992
|
+
];
|
|
1993
|
+
var SHIKI_TOKEN_VARS = cn6(
|
|
1994
|
+
"[--md-code-foreground:var(--foreground)]",
|
|
1995
|
+
"[--md-code-background:transparent]",
|
|
1996
|
+
"[--md-code-token-comment:var(--muted-foreground)]",
|
|
1997
|
+
"[--md-code-token-constant:var(--chart-1)]",
|
|
1998
|
+
"[--md-code-token-function:var(--chart-3)]",
|
|
1999
|
+
"[--md-code-token-keyword:var(--chart-4)]",
|
|
2000
|
+
"[--md-code-token-link:var(--primary)]",
|
|
2001
|
+
"[--md-code-token-parameter:var(--chart-5)]",
|
|
2002
|
+
"[--md-code-token-punctuation:var(--muted-foreground)]",
|
|
2003
|
+
"[--md-code-token-string-expression:var(--chart-2)]",
|
|
2004
|
+
"[--md-code-token-string:var(--chart-2)]"
|
|
2005
|
+
);
|
|
2006
|
+
var hasFontFlag = (fontStyle, flag) => ((fontStyle ?? 0) & flag) === flag;
|
|
2007
|
+
function tokenStyle(token) {
|
|
2008
|
+
return {
|
|
2009
|
+
// htmlStyle.color carries the theme var; token.color is the fallback path.
|
|
2010
|
+
color: token.htmlStyle?.color ?? token.color,
|
|
2011
|
+
fontStyle: hasFontFlag(token.fontStyle, 1) ? "italic" : void 0,
|
|
2012
|
+
fontWeight: hasFontFlag(token.fontStyle, 2) ? "bold" : void 0,
|
|
2013
|
+
textDecoration: hasFontFlag(token.fontStyle, 4) ? "underline" : void 0
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
function useHighlightedTokens(codeText, language) {
|
|
2017
|
+
const [result, setResult] = useState4(null);
|
|
2018
|
+
const keyRef = useRef4({ codeText, language });
|
|
2019
|
+
if (keyRef.current.codeText !== codeText || keyRef.current.language !== language) {
|
|
2020
|
+
keyRef.current = { codeText, language };
|
|
2021
|
+
setResult(null);
|
|
2022
|
+
}
|
|
2023
|
+
useEffect4(() => {
|
|
2024
|
+
if (!language) return void 0;
|
|
2025
|
+
let cancelled = false;
|
|
2026
|
+
const sync = codeHighlighter.highlight(
|
|
2027
|
+
// Unknown languages fall back to "text" inside the plugin.
|
|
2028
|
+
{ code: codeText, language, themes: SHIKI_THEMES },
|
|
2029
|
+
(r) => {
|
|
2030
|
+
if (!cancelled) setResult(r);
|
|
2031
|
+
}
|
|
2032
|
+
);
|
|
2033
|
+
if (sync && !cancelled) setResult(sync);
|
|
2034
|
+
return () => {
|
|
2035
|
+
cancelled = true;
|
|
2036
|
+
};
|
|
2037
|
+
}, [codeText, language]);
|
|
2038
|
+
return result;
|
|
2039
|
+
}
|
|
2040
|
+
function CodeFence({
|
|
2041
|
+
codeText,
|
|
2042
|
+
language,
|
|
2043
|
+
searchActive,
|
|
2044
|
+
className,
|
|
2045
|
+
children,
|
|
2046
|
+
...props
|
|
2047
|
+
}) {
|
|
2048
|
+
const tokens = useHighlightedTokens(codeText, language)?.tokens ?? null;
|
|
2049
|
+
return /* @__PURE__ */ jsxs6(
|
|
2050
|
+
"div",
|
|
2051
|
+
{
|
|
2052
|
+
"data-code-fence": language ?? "",
|
|
2053
|
+
className: cn6("group/code-fence relative my-3", className),
|
|
2054
|
+
...props,
|
|
2055
|
+
children: [
|
|
2056
|
+
/* @__PURE__ */ jsx6(
|
|
2057
|
+
"pre",
|
|
2058
|
+
{
|
|
2059
|
+
"data-search-active": searchActive ? "" : void 0,
|
|
2060
|
+
className: cn6(
|
|
2061
|
+
"!my-0 overflow-x-auto rounded-md p-3 font-mono text-code",
|
|
2062
|
+
SHIKI_TOKEN_VARS,
|
|
2063
|
+
searchActive ? "bg-primary/10" : "bg-surface-muted"
|
|
2064
|
+
),
|
|
2065
|
+
children: tokens ? /* @__PURE__ */ jsx6("code", { children: tokens.map((line, lineIdx) => (
|
|
2066
|
+
// Lines are positionally stable for a given source string (the
|
|
2067
|
+
// whole list is rebuilt when `codeText` changes).
|
|
2068
|
+
/* @__PURE__ */ jsx6("span", { className: "block", children: line.length === 0 ? "\n" : line.map((token, tokenIdx) => /* @__PURE__ */ jsx6("span", { style: tokenStyle(token), children: token.content }, `token-${lineIdx}-${tokenIdx}`)) }, `line-${lineIdx}`)
|
|
2069
|
+
)) }) : children
|
|
2070
|
+
}
|
|
2071
|
+
),
|
|
2072
|
+
/* @__PURE__ */ jsxs6("div", { className: "absolute end-2 top-2 flex items-center gap-1", children: [
|
|
2073
|
+
/* @__PURE__ */ jsx6(
|
|
2074
|
+
CopyButton,
|
|
2075
|
+
{
|
|
2076
|
+
value: codeText,
|
|
2077
|
+
label: false,
|
|
2078
|
+
size: "icon-sm",
|
|
2079
|
+
className: "opacity-0 transition-opacity duration-fast ease-standard focus-visible:opacity-100 group-hover/code-fence:opacity-100 motion-reduce:transition-none"
|
|
2080
|
+
}
|
|
2081
|
+
),
|
|
2082
|
+
language ? /* @__PURE__ */ jsx6(
|
|
2083
|
+
"span",
|
|
2084
|
+
{
|
|
2085
|
+
"aria-hidden": "true",
|
|
2086
|
+
className: "pointer-events-none select-none rounded-sm bg-surface-muted px-1.5 py-0.5 font-mono text-meta text-muted-foreground",
|
|
2087
|
+
children: language
|
|
2088
|
+
}
|
|
2089
|
+
) : null
|
|
2090
|
+
] })
|
|
2091
|
+
]
|
|
2092
|
+
}
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// src/markdown-preview/markdown-preview.tsx
|
|
2097
|
+
import remarkMath from "remark-math";
|
|
2098
|
+
|
|
2099
|
+
// src/markdown-academic/citations.tsx
|
|
2100
|
+
import { cn as cn7 } from "@elabs-ai/components-ui/lib/cn";
|
|
2101
|
+
import { Separator } from "@elabs-ai/components-ui";
|
|
2102
|
+
import {
|
|
2103
|
+
createContext,
|
|
2104
|
+
forwardRef as forwardRef4,
|
|
2105
|
+
useContext,
|
|
2106
|
+
useId as useId2
|
|
2107
|
+
} from "react";
|
|
2108
|
+
import { visit as visit2 } from "unist-util-visit";
|
|
2109
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2110
|
+
var ITEM_RE = /^\s*([^@]*?)\s*(-)?@([\p{L}\d][\w:.#$%&+?<>~/-]*)\s*(.*)$/u;
|
|
2111
|
+
function parseItem(raw) {
|
|
2112
|
+
const m = ITEM_RE.exec(raw);
|
|
2113
|
+
if (!m) return null;
|
|
2114
|
+
const [, prefix, suppress, key, rest] = m;
|
|
2115
|
+
if (!key) return null;
|
|
2116
|
+
const locator = (rest ?? "").replace(/^\s*,\s*/, "").trim();
|
|
2117
|
+
const item = { key };
|
|
2118
|
+
if (suppress) item.suppressAuthor = true;
|
|
2119
|
+
if (prefix?.trim()) item.prefix = prefix.trim();
|
|
2120
|
+
if (locator) item.locator = locator;
|
|
2121
|
+
return item;
|
|
2122
|
+
}
|
|
2123
|
+
function parseCitationBracket(inner) {
|
|
2124
|
+
if (!inner.includes("@")) return null;
|
|
2125
|
+
const items = [];
|
|
2126
|
+
for (const part of inner.split(";")) {
|
|
2127
|
+
const item = parseItem(part);
|
|
2128
|
+
if (!item) return null;
|
|
2129
|
+
items.push(item);
|
|
2130
|
+
}
|
|
2131
|
+
return items.length > 0 ? items : null;
|
|
2132
|
+
}
|
|
2133
|
+
var BRACKET_RE = /(?<![\]!])\[([^[\]]+)\](?![([])/g;
|
|
2134
|
+
function collectCitations(markdown, resolve) {
|
|
2135
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2136
|
+
const order = [];
|
|
2137
|
+
let m;
|
|
2138
|
+
BRACKET_RE.lastIndex = 0;
|
|
2139
|
+
while ((m = BRACKET_RE.exec(markdown)) !== null) {
|
|
2140
|
+
const items = parseCitationBracket(m[1]);
|
|
2141
|
+
if (!items) continue;
|
|
2142
|
+
for (const { key } of items) {
|
|
2143
|
+
if (byKey.has(key)) continue;
|
|
2144
|
+
const data = resolve(key);
|
|
2145
|
+
const entry = { key, data };
|
|
2146
|
+
if (data) {
|
|
2147
|
+
entry.n = order.length + 1;
|
|
2148
|
+
order.push(entry);
|
|
2149
|
+
}
|
|
2150
|
+
byKey.set(key, entry);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
return { order, byKey };
|
|
2154
|
+
}
|
|
2155
|
+
var CITE_TAG = "brand-cite";
|
|
2156
|
+
var CITE_PROP = "dataCite";
|
|
2157
|
+
var CITE_ATTR = "data-cite";
|
|
2158
|
+
function remarkBrandCitations() {
|
|
2159
|
+
return (tree) => {
|
|
2160
|
+
visit2(tree, "text", (node, index, parent) => {
|
|
2161
|
+
const p = parent;
|
|
2162
|
+
const text = node.value;
|
|
2163
|
+
if (!p?.children || index == null || typeof text !== "string" || !text.includes("@")) return;
|
|
2164
|
+
const next = [];
|
|
2165
|
+
let last = 0;
|
|
2166
|
+
BRACKET_RE.lastIndex = 0;
|
|
2167
|
+
let m;
|
|
2168
|
+
while ((m = BRACKET_RE.exec(text)) !== null) {
|
|
2169
|
+
const items = parseCitationBracket(m[1]);
|
|
2170
|
+
if (!items) continue;
|
|
2171
|
+
if (m.index > last) next.push({ type: "text", value: text.slice(last, m.index) });
|
|
2172
|
+
const payload = { items, original: m[0] };
|
|
2173
|
+
next.push({
|
|
2174
|
+
type: "brandCite",
|
|
2175
|
+
data: { hName: CITE_TAG, hProperties: { [CITE_PROP]: JSON.stringify(payload) } }
|
|
2176
|
+
});
|
|
2177
|
+
last = m.index + m[0].length;
|
|
2178
|
+
}
|
|
2179
|
+
if (next.length === 0) return;
|
|
2180
|
+
if (last < text.length) next.push({ type: "text", value: text.slice(last) });
|
|
2181
|
+
p.children.splice(index, 1, ...next);
|
|
2182
|
+
return index + next.length;
|
|
2183
|
+
});
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
var CitationContext = createContext(null);
|
|
2187
|
+
function CitationProvider({ byKey, order, style, children }) {
|
|
2188
|
+
return /* @__PURE__ */ jsx7(CitationContext.Provider, { value: { byKey, order, style }, children });
|
|
2189
|
+
}
|
|
2190
|
+
function hoverTitle(data) {
|
|
2191
|
+
if (data.formatted) return data.formatted;
|
|
2192
|
+
const parts = [
|
|
2193
|
+
data.author,
|
|
2194
|
+
data.year != null ? `(${data.year})` : void 0,
|
|
2195
|
+
data.title,
|
|
2196
|
+
data.container
|
|
2197
|
+
].filter(Boolean);
|
|
2198
|
+
return parts.join(". ");
|
|
2199
|
+
}
|
|
2200
|
+
function CiteLink({ entry, label }) {
|
|
2201
|
+
const name = entry.data ? hoverTitle(entry.data) : entry.key;
|
|
2202
|
+
return /* @__PURE__ */ jsx7(
|
|
2203
|
+
"a",
|
|
2204
|
+
{
|
|
2205
|
+
href: `#ref-${cssId(entry.key)}`,
|
|
2206
|
+
title: entry.data ? hoverTitle(entry.data) : void 0,
|
|
2207
|
+
"aria-label": `Citation: ${name}`,
|
|
2208
|
+
className: "text-primary-text underline hover:underline focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2209
|
+
children: label
|
|
2210
|
+
}
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
function cssId(key) {
|
|
2214
|
+
return key.replace(/[^\w-]/g, "-");
|
|
2215
|
+
}
|
|
2216
|
+
function readCite(rest) {
|
|
2217
|
+
const raw = rest[CITE_ATTR] ?? rest[CITE_PROP];
|
|
2218
|
+
if (!raw) return null;
|
|
2219
|
+
try {
|
|
2220
|
+
return JSON.parse(raw);
|
|
2221
|
+
} catch {
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
function InlineCite({ node: _n, children: _c, ...rest }) {
|
|
2226
|
+
const ctx = useContext(CitationContext);
|
|
2227
|
+
const payload = readCite(rest);
|
|
2228
|
+
if (!payload) return null;
|
|
2229
|
+
if (!ctx) return /* @__PURE__ */ jsx7("span", { children: payload.original });
|
|
2230
|
+
const resolved = payload.items.map((it) => ({ it, entry: ctx.byKey.get(it.key) }));
|
|
2231
|
+
const anyResolved = resolved.some((r) => r.entry?.data);
|
|
2232
|
+
if (!anyResolved) {
|
|
2233
|
+
return /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", title: "Unresolved citation", children: payload.original });
|
|
2234
|
+
}
|
|
2235
|
+
const numeric = ctx.style === "numeric";
|
|
2236
|
+
const open = numeric ? "[" : "(";
|
|
2237
|
+
const close = numeric ? "]" : ")";
|
|
2238
|
+
const sep = numeric ? ", " : "; ";
|
|
2239
|
+
return /* @__PURE__ */ jsxs7("span", { className: "whitespace-nowrap text-meta tabular-nums", children: [
|
|
2240
|
+
open,
|
|
2241
|
+
resolved.map(({ it, entry }, i) => {
|
|
2242
|
+
const label = numeric ? numericLabel(it, entry) : authorYearLabel(it, entry);
|
|
2243
|
+
return /* @__PURE__ */ jsxs7("span", { children: [
|
|
2244
|
+
i > 0 ? sep : null,
|
|
2245
|
+
entry?.data ? /* @__PURE__ */ jsx7(CiteLink, { entry, label }) : /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", title: `Unresolved: @${it.key}`, children: label })
|
|
2246
|
+
] }, `${it.key}-${i}`);
|
|
2247
|
+
}),
|
|
2248
|
+
close
|
|
2249
|
+
] });
|
|
2250
|
+
}
|
|
2251
|
+
function numericLabel(it, entry) {
|
|
2252
|
+
if (!entry?.data || entry.n == null) return "?";
|
|
2253
|
+
return it.locator ? `${entry.n}, ${it.locator}` : String(entry.n);
|
|
2254
|
+
}
|
|
2255
|
+
function authorYearLabel(it, entry) {
|
|
2256
|
+
if (!entry?.data) return `@${it.key}?`;
|
|
2257
|
+
const d = entry.data;
|
|
2258
|
+
const head = it.suppressAuthor ? "" : d.author ? `${d.author} ` : "";
|
|
2259
|
+
const year = d.year != null ? String(d.year) : "";
|
|
2260
|
+
const core = `${head}${year}`.trim() || d.title || it.key;
|
|
2261
|
+
const prefixed = it.prefix ? `${it.prefix} ${core}` : core;
|
|
2262
|
+
return it.locator ? `${prefixed}, ${it.locator}` : prefixed;
|
|
2263
|
+
}
|
|
2264
|
+
function assembledReference(data) {
|
|
2265
|
+
if (data.formatted) return data.formatted;
|
|
2266
|
+
const parts = [
|
|
2267
|
+
data.author,
|
|
2268
|
+
data.year != null ? `(${data.year}).` : void 0,
|
|
2269
|
+
data.title ? `${data.title}.` : void 0,
|
|
2270
|
+
data.container ? `${data.container}.` : void 0
|
|
2271
|
+
].filter(Boolean);
|
|
2272
|
+
return parts.join(" ");
|
|
2273
|
+
}
|
|
2274
|
+
function referenceHref(data) {
|
|
2275
|
+
if (data.url) return data.url;
|
|
2276
|
+
if (data.doi) return `https://doi.org/${data.doi}`;
|
|
2277
|
+
return void 0;
|
|
2278
|
+
}
|
|
2279
|
+
var Bibliography = forwardRef4(function Bibliography2({ entries, style, title = "References", className, ...props }, ref) {
|
|
2280
|
+
const ctx = useContext(CitationContext);
|
|
2281
|
+
const labelId = useId2();
|
|
2282
|
+
const list = entries ?? ctx?.order ?? [];
|
|
2283
|
+
const resolvedStyle = style ?? ctx?.style ?? "numeric";
|
|
2284
|
+
const numeric = resolvedStyle === "numeric";
|
|
2285
|
+
if (list.length === 0) return null;
|
|
2286
|
+
return /* @__PURE__ */ jsxs7("section", { ref, "aria-labelledby": labelId, className: cn7("mt-8", className), ...props, children: [
|
|
2287
|
+
/* @__PURE__ */ jsx7(Separator, { className: "mb-3" }),
|
|
2288
|
+
/* @__PURE__ */ jsx7("p", { id: labelId, className: "mb-2 text-meta font-medium text-muted-foreground", children: title }),
|
|
2289
|
+
/* @__PURE__ */ jsx7("ol", { className: "space-y-2", children: list.map((entry) => {
|
|
2290
|
+
const data = entry.data;
|
|
2291
|
+
if (!data) return null;
|
|
2292
|
+
const href = referenceHref(data);
|
|
2293
|
+
return /* @__PURE__ */ jsxs7(
|
|
2294
|
+
"li",
|
|
2295
|
+
{
|
|
2296
|
+
id: `ref-${cssId(entry.key)}`,
|
|
2297
|
+
className: "flex gap-2 text-caption text-foreground scroll-mt-4",
|
|
2298
|
+
children: [
|
|
2299
|
+
numeric && entry.n != null ? /* @__PURE__ */ jsxs7("span", { className: "shrink-0 tabular-nums text-muted-foreground", children: [
|
|
2300
|
+
"[",
|
|
2301
|
+
entry.n,
|
|
2302
|
+
"]"
|
|
2303
|
+
] }) : null,
|
|
2304
|
+
/* @__PURE__ */ jsxs7("span", { className: "min-w-0", children: [
|
|
2305
|
+
assembledReference(data),
|
|
2306
|
+
" ",
|
|
2307
|
+
href ? /* @__PURE__ */ jsx7(
|
|
2308
|
+
"a",
|
|
2309
|
+
{
|
|
2310
|
+
href,
|
|
2311
|
+
target: "_blank",
|
|
2312
|
+
rel: "noopener noreferrer",
|
|
2313
|
+
className: "break-words text-primary-text underline underline-offset-2 hover:underline focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2314
|
+
children: data.url ?? `doi:${data.doi}`
|
|
2315
|
+
}
|
|
2316
|
+
) : null
|
|
2317
|
+
] })
|
|
2318
|
+
]
|
|
2319
|
+
},
|
|
2320
|
+
entry.key
|
|
2321
|
+
);
|
|
2322
|
+
}) })
|
|
2323
|
+
] });
|
|
2324
|
+
});
|
|
2325
|
+
|
|
2326
|
+
// src/markdown-academic/footnotes.tsx
|
|
2327
|
+
import { Separator as Separator2 } from "@elabs-ai/components-ui";
|
|
2328
|
+
import { cn as cn8 } from "@elabs-ai/components-ui/lib/cn";
|
|
2329
|
+
import { forwardRef as forwardRef5, useId as useId3 } from "react";
|
|
2330
|
+
import { visit as visit3 } from "unist-util-visit";
|
|
2331
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2332
|
+
var FOOTNOTE_REF_TAG = "brand-footnote-ref";
|
|
2333
|
+
var FOOTNOTE_LIST_TAG = "brand-footnote-list";
|
|
2334
|
+
var FOOTNOTE_ITEM_TAG = "brand-footnote-item";
|
|
2335
|
+
var FOOTNOTE_PROP = "dataFn";
|
|
2336
|
+
var FOOTNOTE_ATTR = "data-fn";
|
|
2337
|
+
function payloadProps(payload) {
|
|
2338
|
+
return { [FOOTNOTE_PROP]: JSON.stringify(payload) };
|
|
2339
|
+
}
|
|
2340
|
+
function remarkBrandFootnotes() {
|
|
2341
|
+
return (tree) => {
|
|
2342
|
+
const root = tree;
|
|
2343
|
+
const defs = /* @__PURE__ */ new Map();
|
|
2344
|
+
visit3(
|
|
2345
|
+
root,
|
|
2346
|
+
"footnoteDefinition",
|
|
2347
|
+
(node, index, parent) => {
|
|
2348
|
+
if (!node.identifier || !parent?.children || index == null) return;
|
|
2349
|
+
defs.set(node.identifier, node);
|
|
2350
|
+
parent.children.splice(index, 1);
|
|
2351
|
+
return index;
|
|
2352
|
+
}
|
|
2353
|
+
);
|
|
2354
|
+
const numberOf = /* @__PURE__ */ new Map();
|
|
2355
|
+
const refsOf = /* @__PURE__ */ new Map();
|
|
2356
|
+
const order = [];
|
|
2357
|
+
visit3(root, "footnoteReference", (node, index, parent) => {
|
|
2358
|
+
if (!node.identifier || !parent?.children || index == null) return;
|
|
2359
|
+
const id = node.identifier;
|
|
2360
|
+
let n = numberOf.get(id);
|
|
2361
|
+
if (n == null) {
|
|
2362
|
+
n = order.length + 1;
|
|
2363
|
+
numberOf.set(id, n);
|
|
2364
|
+
order.push(id);
|
|
2365
|
+
}
|
|
2366
|
+
const refs = refsOf.get(id) ?? [];
|
|
2367
|
+
const refId = refs.length === 0 ? `fnref-${id}` : `fnref-${id}-${refs.length + 1}`;
|
|
2368
|
+
refs.push(refId);
|
|
2369
|
+
refsOf.set(id, refs);
|
|
2370
|
+
parent.children[index] = {
|
|
2371
|
+
type: "brandFootnoteRef",
|
|
2372
|
+
data: { hName: FOOTNOTE_REF_TAG, hProperties: payloadProps({ id, n, refId }) }
|
|
2373
|
+
};
|
|
2374
|
+
});
|
|
2375
|
+
if (order.length === 0) return;
|
|
2376
|
+
const items = order.map((id, i) => {
|
|
2377
|
+
const def = defs.get(id);
|
|
2378
|
+
const body = def?.children ? def.children.map((c) => structuredClone(c)) : [{ type: "paragraph", children: [{ type: "text", value: "Missing footnote." }] }];
|
|
2379
|
+
return {
|
|
2380
|
+
type: "brandFootnoteItem",
|
|
2381
|
+
data: {
|
|
2382
|
+
hName: FOOTNOTE_ITEM_TAG,
|
|
2383
|
+
hProperties: payloadProps({ id, n: i + 1, refs: refsOf.get(id) ?? [`fnref-${id}`] })
|
|
2384
|
+
},
|
|
2385
|
+
children: body
|
|
2386
|
+
};
|
|
2387
|
+
});
|
|
2388
|
+
root.children = root.children ?? [];
|
|
2389
|
+
root.children.push({
|
|
2390
|
+
type: "brandFootnoteList",
|
|
2391
|
+
data: { hName: FOOTNOTE_LIST_TAG },
|
|
2392
|
+
children: items
|
|
2393
|
+
});
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
function readPayload(rest) {
|
|
2397
|
+
const raw = rest[FOOTNOTE_ATTR] ?? rest[FOOTNOTE_PROP];
|
|
2398
|
+
if (!raw) return null;
|
|
2399
|
+
try {
|
|
2400
|
+
return JSON.parse(raw);
|
|
2401
|
+
} catch {
|
|
2402
|
+
return null;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
function FootnoteRef({ node: _n, children: _c, ...rest }) {
|
|
2406
|
+
const payload = readPayload(rest);
|
|
2407
|
+
if (!payload) return null;
|
|
2408
|
+
const { id, n, refId } = payload;
|
|
2409
|
+
return /* @__PURE__ */ jsx8("sup", { className: "leading-none", children: /* @__PURE__ */ jsx8(
|
|
2410
|
+
"a",
|
|
2411
|
+
{
|
|
2412
|
+
id: refId ?? `fnref-${id}`,
|
|
2413
|
+
href: `#fn-${id}`,
|
|
2414
|
+
"data-footnote-ref": "",
|
|
2415
|
+
"aria-label": `Footnote ${n}`,
|
|
2416
|
+
className: "px-0.5 font-medium text-primary-text underline tabular-nums hover:underline focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2417
|
+
children: n
|
|
2418
|
+
}
|
|
2419
|
+
) });
|
|
2420
|
+
}
|
|
2421
|
+
function FootnoteItem({ node: _n, children, ...rest }) {
|
|
2422
|
+
const payload = readPayload(rest);
|
|
2423
|
+
if (!payload) return null;
|
|
2424
|
+
const { id, n, refs } = payload;
|
|
2425
|
+
const refList = refs && refs.length > 0 ? refs : [`fnref-${id}`];
|
|
2426
|
+
return /* @__PURE__ */ jsxs8(
|
|
2427
|
+
"li",
|
|
2428
|
+
{
|
|
2429
|
+
id: `fn-${id}`,
|
|
2430
|
+
className: "scroll-mt-4 ps-1 [&>p]:m-0 [&>p]:inline [&>p]:text-caption [&>p]:text-muted-foreground",
|
|
2431
|
+
children: [
|
|
2432
|
+
children,
|
|
2433
|
+
" ",
|
|
2434
|
+
refList.map((refId, i) => /* @__PURE__ */ jsxs8(
|
|
2435
|
+
"a",
|
|
2436
|
+
{
|
|
2437
|
+
href: `#${refId}`,
|
|
2438
|
+
"data-footnote-backref": "",
|
|
2439
|
+
"aria-label": refList.length > 1 ? `Back to reference ${n}, mention ${i + 1}` : `Back to reference ${n}`,
|
|
2440
|
+
className: "ms-0.5 inline-flex items-center text-muted-foreground no-underline hover:text-foreground focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2441
|
+
children: [
|
|
2442
|
+
/* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u21A9" }),
|
|
2443
|
+
refList.length > 1 ? /* @__PURE__ */ jsx8("sub", { className: "ms-0.5 leading-none tabular-nums", children: i + 1 }) : null
|
|
2444
|
+
]
|
|
2445
|
+
},
|
|
2446
|
+
refId
|
|
2447
|
+
))
|
|
2448
|
+
]
|
|
2449
|
+
}
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
var FootnoteList = forwardRef5(function FootnoteList2({ className, children, ...props }, ref) {
|
|
2453
|
+
const labelId = useId3();
|
|
2454
|
+
return /* @__PURE__ */ jsxs8("section", { ref, "aria-labelledby": labelId, className: cn8("mt-8", className), ...props, children: [
|
|
2455
|
+
/* @__PURE__ */ jsx8(Separator2, { className: "mb-3" }),
|
|
2456
|
+
/* @__PURE__ */ jsx8("p", { id: labelId, className: "mb-2 text-meta font-medium text-muted-foreground", children: "Footnotes" }),
|
|
2457
|
+
/* @__PURE__ */ jsx8("ol", { className: "list-decimal space-y-1.5 ps-6 text-caption text-muted-foreground marker:text-muted-foreground", children })
|
|
2458
|
+
] });
|
|
2459
|
+
});
|
|
2460
|
+
|
|
2461
|
+
// src/markdown-academic/math.tsx
|
|
2462
|
+
import { cn as cn9 } from "@elabs-ai/components-ui/lib/cn";
|
|
2463
|
+
import katex from "katex";
|
|
2464
|
+
import { useMemo as useMemo4 } from "react";
|
|
2465
|
+
import { visit as visit4 } from "unist-util-visit";
|
|
2466
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2467
|
+
var MATH_BLOCK_TAG = "brand-math";
|
|
2468
|
+
var MATH_INLINE_TAG = "brand-math-inline";
|
|
2469
|
+
var MATH_PROP = "dataTex";
|
|
2470
|
+
var MATH_ATTR = "data-tex";
|
|
2471
|
+
var MAX_EXPAND = 1e3;
|
|
2472
|
+
function remarkBrandMath() {
|
|
2473
|
+
return (tree) => {
|
|
2474
|
+
visit4(tree, (node, index, parent) => {
|
|
2475
|
+
if (node.type !== "inlineMath" && node.type !== "math") return;
|
|
2476
|
+
if (!parent?.children || index == null) return;
|
|
2477
|
+
const display = node.type === "math";
|
|
2478
|
+
const tex = typeof node.value === "string" ? node.value : "";
|
|
2479
|
+
parent.children[index] = {
|
|
2480
|
+
type: display ? "brandMathBlock" : "brandMathInline",
|
|
2481
|
+
data: {
|
|
2482
|
+
hName: display ? MATH_BLOCK_TAG : MATH_INLINE_TAG,
|
|
2483
|
+
hProperties: { [MATH_PROP]: tex }
|
|
2484
|
+
}
|
|
2485
|
+
};
|
|
2486
|
+
});
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
function readTex(rest) {
|
|
2490
|
+
return rest[MATH_ATTR] ?? rest[MATH_PROP] ?? "";
|
|
2491
|
+
}
|
|
2492
|
+
function renderKatex(tex, displayMode) {
|
|
2493
|
+
try {
|
|
2494
|
+
return {
|
|
2495
|
+
html: katex.renderToString(tex, {
|
|
2496
|
+
displayMode,
|
|
2497
|
+
throwOnError: false,
|
|
2498
|
+
errorColor: "var(--destructive)",
|
|
2499
|
+
trust: false,
|
|
2500
|
+
maxExpand: MAX_EXPAND,
|
|
2501
|
+
strict: "ignore",
|
|
2502
|
+
output: "htmlAndMathml"
|
|
2503
|
+
}),
|
|
2504
|
+
error: false
|
|
2505
|
+
};
|
|
2506
|
+
} catch {
|
|
2507
|
+
return { html: "", error: true };
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
function MathInline({ tex, className, ...props }) {
|
|
2511
|
+
const { html, error } = useMemo4(() => renderKatex(tex, false), [tex]);
|
|
2512
|
+
if (error) {
|
|
2513
|
+
return /* @__PURE__ */ jsx9(
|
|
2514
|
+
"code",
|
|
2515
|
+
{
|
|
2516
|
+
className: cn9("text-destructive-text", className),
|
|
2517
|
+
"aria-label": `Math (could not render): ${tex}`,
|
|
2518
|
+
title: "Could not render math",
|
|
2519
|
+
...props,
|
|
2520
|
+
children: tex
|
|
2521
|
+
}
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
return /* @__PURE__ */ jsx9(
|
|
2525
|
+
"span",
|
|
2526
|
+
{
|
|
2527
|
+
role: "math",
|
|
2528
|
+
"aria-label": tex,
|
|
2529
|
+
className: cn9("inline-block align-middle", className),
|
|
2530
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
2531
|
+
...props
|
|
2532
|
+
}
|
|
2533
|
+
);
|
|
2534
|
+
}
|
|
2535
|
+
function MathBlock({ tex, className, ...props }) {
|
|
2536
|
+
const { html, error } = useMemo4(() => renderKatex(tex, true), [tex]);
|
|
2537
|
+
if (error) {
|
|
2538
|
+
return /* @__PURE__ */ jsx9(
|
|
2539
|
+
"pre",
|
|
2540
|
+
{
|
|
2541
|
+
className: cn9(
|
|
2542
|
+
"overflow-x-auto rounded-md bg-surface-muted p-3 text-destructive-text",
|
|
2543
|
+
className
|
|
2544
|
+
),
|
|
2545
|
+
"aria-label": `Math (could not render): ${tex}`,
|
|
2546
|
+
title: "Could not render math",
|
|
2547
|
+
...props,
|
|
2548
|
+
children: /* @__PURE__ */ jsx9("code", { children: tex })
|
|
2549
|
+
}
|
|
2550
|
+
);
|
|
2551
|
+
}
|
|
2552
|
+
return /* @__PURE__ */ jsx9(
|
|
2553
|
+
"div",
|
|
2554
|
+
{
|
|
2555
|
+
role: "math",
|
|
2556
|
+
"aria-label": tex,
|
|
2557
|
+
className: cn9("my-3 overflow-x-auto text-center", className),
|
|
2558
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
2559
|
+
...props
|
|
2560
|
+
}
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
function MathInlineTag({ node: _n, children: _c, ...rest }) {
|
|
2564
|
+
return /* @__PURE__ */ jsx9(MathInline, { tex: readTex(rest) });
|
|
2565
|
+
}
|
|
2566
|
+
function MathBlockTag({ node: _n, children: _c, ...rest }) {
|
|
2567
|
+
return /* @__PURE__ */ jsx9(MathBlock, { tex: readTex(rest) });
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
// src/markdown-academic/toc.tsx
|
|
2571
|
+
import { cn as cn10 } from "@elabs-ai/components-ui/lib/cn";
|
|
2572
|
+
import { createContext as createContext2, forwardRef as forwardRef6, useContext as useContext2 } from "react";
|
|
2573
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2574
|
+
var INDENT = ["ps-0", "ps-3", "ps-6", "ps-9", "ps-12", "ps-12"];
|
|
2575
|
+
var TocContext = createContext2(null);
|
|
2576
|
+
function TocProvider({ items, children }) {
|
|
2577
|
+
const idByLine = /* @__PURE__ */ new Map();
|
|
2578
|
+
for (const it of items) idByLine.set(it.line, it.id);
|
|
2579
|
+
return /* @__PURE__ */ jsx10(TocContext.Provider, { value: { items, idByLine }, children });
|
|
2580
|
+
}
|
|
2581
|
+
function useHeadingId(line) {
|
|
2582
|
+
const ctx = useContext2(TocContext);
|
|
2583
|
+
if (line == null || !ctx) return void 0;
|
|
2584
|
+
return ctx.idByLine.get(line);
|
|
2585
|
+
}
|
|
2586
|
+
var TableOfContents = forwardRef6(
|
|
2587
|
+
function TableOfContents2({ items, title = "Contents", maxLevel = 3, className, ...props }, ref) {
|
|
2588
|
+
const ctx = useContext2(TocContext);
|
|
2589
|
+
const source = items ?? ctx?.items ?? [];
|
|
2590
|
+
const list = source.filter((it) => it.level <= maxLevel);
|
|
2591
|
+
if (list.length === 0) return null;
|
|
2592
|
+
const minLevel = Math.min(...list.map((it) => it.level));
|
|
2593
|
+
return /* @__PURE__ */ jsxs9("nav", { ref, "aria-label": title, className: cn10("my-4 text-meta", className), ...props, children: [
|
|
2594
|
+
/* @__PURE__ */ jsx10("p", { className: "mb-2 font-medium text-muted-foreground", children: title }),
|
|
2595
|
+
/* @__PURE__ */ jsx10("ol", { className: "space-y-1", children: list.map((it) => /* @__PURE__ */ jsx10("li", { className: INDENT[Math.min(it.level - minLevel, INDENT.length - 1)], children: /* @__PURE__ */ jsx10(
|
|
2596
|
+
"a",
|
|
2597
|
+
{
|
|
2598
|
+
href: `#${it.id}`,
|
|
2599
|
+
className: "text-muted-foreground underline hover:text-foreground hover:underline focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2600
|
+
children: it.text
|
|
2601
|
+
}
|
|
2602
|
+
) }, it.id)) })
|
|
2603
|
+
] });
|
|
2604
|
+
}
|
|
2605
|
+
);
|
|
2606
|
+
|
|
2607
|
+
// src/markdown-iteration/directive.tsx
|
|
2608
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
2609
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2610
|
+
var MAX_ITERATION_DEPTH = 3;
|
|
2611
|
+
var IterationDepthContext = createContext3(0);
|
|
2612
|
+
var DEFAULT_LAYOUT = {
|
|
2613
|
+
iterate: "stacked",
|
|
2614
|
+
pivot: "matrix"
|
|
2615
|
+
};
|
|
2616
|
+
var LAYOUTS = /* @__PURE__ */ new Set(["stacked", "grid", "matrix", "bento"]);
|
|
2617
|
+
function specFromDirective(name, attributes, rawBody) {
|
|
2618
|
+
const kind = name;
|
|
2619
|
+
const layoutAttr = attributes.layout;
|
|
2620
|
+
const layout = layoutAttr && LAYOUTS.has(layoutAttr) ? layoutAttr : DEFAULT_LAYOUT[kind];
|
|
2621
|
+
const columns = attributes.columns ? Number(attributes.columns) || void 0 : void 0;
|
|
2622
|
+
return {
|
|
2623
|
+
kind,
|
|
2624
|
+
layout,
|
|
2625
|
+
template: rawBody ?? "",
|
|
2626
|
+
as: attributes.as?.trim() || "item",
|
|
2627
|
+
source: attributes.source,
|
|
2628
|
+
rows: attributes.rows,
|
|
2629
|
+
cols: attributes.cols,
|
|
2630
|
+
columns,
|
|
2631
|
+
attributes
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
function IterationDirective({
|
|
2635
|
+
spec,
|
|
2636
|
+
evaluate,
|
|
2637
|
+
interpolate,
|
|
2638
|
+
renderCell
|
|
2639
|
+
}) {
|
|
2640
|
+
const depth = useContext3(IterationDepthContext);
|
|
2641
|
+
if (depth >= MAX_ITERATION_DEPTH) {
|
|
2642
|
+
return /* @__PURE__ */ jsx11("div", { className: "my-4 text-meta text-muted-foreground italic", "data-iteration-too-deep": "", children: "Iteration nested too deep \u2014 skipped." });
|
|
2643
|
+
}
|
|
2644
|
+
return /* @__PURE__ */ jsx11(IterationDepthContext.Provider, { value: depth + 1, children: /* @__PURE__ */ jsx11(
|
|
2645
|
+
IterationBlock,
|
|
2646
|
+
{
|
|
2647
|
+
spec,
|
|
2648
|
+
evaluate,
|
|
2649
|
+
interpolate,
|
|
2650
|
+
render: renderCell
|
|
2651
|
+
}
|
|
2652
|
+
) });
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
// src/markdown-preview/markdown-preview.tsx
|
|
2656
|
+
import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2657
|
+
var baseRemarkPlugins = Object.values(defaultRemarkPlugins);
|
|
2658
|
+
var BRAND_TRANSCLUSION_TAG = "brand-transclusion";
|
|
2659
|
+
var BRAND_TRANSCLUSION_ATTR = "data-transclusion";
|
|
2660
|
+
var BRAND_TRANSCLUSION_PROP = "dataTransclusion";
|
|
2661
|
+
var allowedTags = {
|
|
2662
|
+
[BRAND_DIRECTIVE_TAG]: [BRAND_DIRECTIVE_PROP],
|
|
2663
|
+
[BRAND_DIRECTIVE_INLINE_TAG]: [BRAND_DIRECTIVE_PROP],
|
|
2664
|
+
[BRAND_TRANSCLUSION_TAG]: [BRAND_TRANSCLUSION_PROP],
|
|
2665
|
+
// Academic layer (footnotes / math / citations) — opt-in via props, but the
|
|
2666
|
+
// tags are always allow-listed (harmless when the feature is off).
|
|
2667
|
+
[FOOTNOTE_REF_TAG]: [FOOTNOTE_PROP],
|
|
2668
|
+
[FOOTNOTE_ITEM_TAG]: [FOOTNOTE_PROP],
|
|
2669
|
+
[FOOTNOTE_LIST_TAG]: [],
|
|
2670
|
+
[MATH_BLOCK_TAG]: [MATH_PROP],
|
|
2671
|
+
[MATH_INLINE_TAG]: [MATH_PROP],
|
|
2672
|
+
[CITE_TAG]: [CITE_PROP]
|
|
2673
|
+
};
|
|
2674
|
+
var ACADEMIC_TAGS = [
|
|
2675
|
+
FOOTNOTE_REF_TAG,
|
|
2676
|
+
FOOTNOTE_ITEM_TAG,
|
|
2677
|
+
FOOTNOTE_LIST_TAG,
|
|
2678
|
+
MATH_BLOCK_TAG,
|
|
2679
|
+
MATH_INLINE_TAG,
|
|
2680
|
+
CITE_TAG
|
|
2681
|
+
];
|
|
2682
|
+
var ACADEMIC_TAG_ATTRS = {
|
|
2683
|
+
[FOOTNOTE_REF_TAG]: [FOOTNOTE_PROP],
|
|
2684
|
+
[FOOTNOTE_ITEM_TAG]: [FOOTNOTE_PROP],
|
|
2685
|
+
[FOOTNOTE_LIST_TAG]: [],
|
|
2686
|
+
[MATH_BLOCK_TAG]: [MATH_PROP],
|
|
2687
|
+
[MATH_INLINE_TAG]: [MATH_PROP],
|
|
2688
|
+
[CITE_TAG]: [CITE_PROP]
|
|
2689
|
+
};
|
|
2690
|
+
var rehypePlugins = (() => {
|
|
2691
|
+
const defaults = defaultRehypePlugins;
|
|
2692
|
+
const sanitize = defaults.sanitize;
|
|
2693
|
+
const schema = sanitize[1] ?? {};
|
|
2694
|
+
const protocols = schema.protocols ?? {};
|
|
2695
|
+
const extendedSanitize = [
|
|
2696
|
+
sanitize[0],
|
|
2697
|
+
{
|
|
2698
|
+
...schema,
|
|
2699
|
+
protocols: { ...protocols, src: [...protocols.src ?? ["http", "https"], "data", "blob"] },
|
|
2700
|
+
// Custom rehypePlugins bypass Streamdown's `allowedTags` merge — so the brand-directive
|
|
2701
|
+
// tags, the transclusion tag, and their JSON payload properties all go into the schema here.
|
|
2702
|
+
tagNames: [
|
|
2703
|
+
...schema.tagNames ?? [],
|
|
2704
|
+
BRAND_DIRECTIVE_TAG,
|
|
2705
|
+
BRAND_DIRECTIVE_INLINE_TAG,
|
|
2706
|
+
BRAND_TRANSCLUSION_TAG,
|
|
2707
|
+
...ACADEMIC_TAGS
|
|
2708
|
+
],
|
|
2709
|
+
attributes: {
|
|
2710
|
+
...schema.attributes ?? {},
|
|
2711
|
+
[BRAND_DIRECTIVE_TAG]: [BRAND_DIRECTIVE_PROP],
|
|
2712
|
+
[BRAND_DIRECTIVE_INLINE_TAG]: [BRAND_DIRECTIVE_PROP],
|
|
2713
|
+
[BRAND_TRANSCLUSION_TAG]: [BRAND_TRANSCLUSION_PROP],
|
|
2714
|
+
...ACADEMIC_TAG_ATTRS
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
];
|
|
2718
|
+
return [defaults.raw, extendedSanitize, defaults.harden];
|
|
2719
|
+
})();
|
|
2720
|
+
var singleBlock = (md) => [md];
|
|
2721
|
+
var AnnotationsContext = createContext4([]);
|
|
2722
|
+
var SearchContext = createContext4({ lines: [] });
|
|
2723
|
+
var HeadingActionsContext = createContext4(
|
|
2724
|
+
null
|
|
2725
|
+
);
|
|
2726
|
+
var EMPTY_REGISTRY = { directives: /* @__PURE__ */ new Map(), fences: /* @__PURE__ */ new Map() };
|
|
2727
|
+
var RegistryContext = createContext4(EMPTY_REGISTRY);
|
|
2728
|
+
var LinkPreviewContext = createContext4(
|
|
2729
|
+
null
|
|
2730
|
+
);
|
|
2731
|
+
var TransclusionResolverContext = createContext4(null);
|
|
2732
|
+
var TRANSCLUSION_MAX_DEPTH = 3;
|
|
2733
|
+
var TransclusionDepthContext = createContext4(0);
|
|
2734
|
+
function flattenNodeText(node) {
|
|
2735
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
2736
|
+
if (Array.isArray(node)) return node.map((n) => flattenNodeText(n)).join("");
|
|
2737
|
+
if (isValidElement(node)) {
|
|
2738
|
+
return flattenNodeText(node.props.children);
|
|
2739
|
+
}
|
|
2740
|
+
return "";
|
|
2741
|
+
}
|
|
2742
|
+
function remarkResolveUrls(resolve) {
|
|
2743
|
+
return function attacher() {
|
|
2744
|
+
return (tree) => {
|
|
2745
|
+
visit5(tree, (node) => {
|
|
2746
|
+
const n = node;
|
|
2747
|
+
if (n.type === "image" || n.type === "imageReference") {
|
|
2748
|
+
if (typeof n.url === "string") n.url = resolve(n.url, "image");
|
|
2749
|
+
} else if (n.type === "link" || n.type === "definition") {
|
|
2750
|
+
if (typeof n.url === "string") n.url = resolve(n.url, "link");
|
|
2751
|
+
}
|
|
2752
|
+
});
|
|
2753
|
+
};
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
function remarkResolveWikilinks(resolve) {
|
|
2757
|
+
const WIKILINK_RE = /(?<!!)\[\[([^\]]+)\]\]/g;
|
|
2758
|
+
return function attacher() {
|
|
2759
|
+
return (tree) => {
|
|
2760
|
+
visit5(tree, "text", (node, index, parent) => {
|
|
2761
|
+
const n = node;
|
|
2762
|
+
const p = parent;
|
|
2763
|
+
if (!p?.children || index == null || typeof n.value !== "string") return;
|
|
2764
|
+
const text = n.value;
|
|
2765
|
+
if (!text.includes("[[")) return;
|
|
2766
|
+
const newChildren = [];
|
|
2767
|
+
let lastIndex = 0;
|
|
2768
|
+
WIKILINK_RE.lastIndex = 0;
|
|
2769
|
+
let match;
|
|
2770
|
+
while ((match = WIKILINK_RE.exec(text)) !== null) {
|
|
2771
|
+
if (match.index > lastIndex) {
|
|
2772
|
+
newChildren.push({ type: "text", value: text.slice(lastIndex, match.index) });
|
|
2773
|
+
}
|
|
2774
|
+
const inner = match[1];
|
|
2775
|
+
const pipeIdx = inner.indexOf("|");
|
|
2776
|
+
const targetPart = pipeIdx !== -1 ? inner.slice(0, pipeIdx) : inner;
|
|
2777
|
+
const alias = pipeIdx !== -1 ? inner.slice(pipeIdx + 1) : void 0;
|
|
2778
|
+
const hashIdx = targetPart.indexOf("#");
|
|
2779
|
+
const target = hashIdx !== -1 ? targetPart.slice(0, hashIdx) : targetPart;
|
|
2780
|
+
const anchor = hashIdx !== -1 ? targetPart.slice(hashIdx + 1) : void 0;
|
|
2781
|
+
const opts = anchor ? { anchor } : {};
|
|
2782
|
+
const href = resolve(target.trim(), opts);
|
|
2783
|
+
const linkText = alias?.trim() || target.trim();
|
|
2784
|
+
if (href === null) {
|
|
2785
|
+
newChildren.push({ type: "text", value: match[0] });
|
|
2786
|
+
} else {
|
|
2787
|
+
newChildren.push({
|
|
2788
|
+
type: "link",
|
|
2789
|
+
url: href,
|
|
2790
|
+
title: null,
|
|
2791
|
+
children: [{ type: "text", value: linkText }]
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
lastIndex = match.index + match[0].length;
|
|
2795
|
+
}
|
|
2796
|
+
if (lastIndex < text.length) {
|
|
2797
|
+
newChildren.push({ type: "text", value: text.slice(lastIndex) });
|
|
2798
|
+
}
|
|
2799
|
+
if (newChildren.length > 0) {
|
|
2800
|
+
p.children.splice(index, 1, ...newChildren);
|
|
2801
|
+
return index + newChildren.length;
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2804
|
+
};
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
function remarkResolveTransclusions() {
|
|
2808
|
+
const STANDALONE_RE = /^!\[\[([^\]]+)\]\]$/;
|
|
2809
|
+
return function attacher() {
|
|
2810
|
+
return (tree) => {
|
|
2811
|
+
visit5(tree, "paragraph", (node) => {
|
|
2812
|
+
const n = node;
|
|
2813
|
+
if (!n.children || n.children.length !== 1) return;
|
|
2814
|
+
const child = n.children[0];
|
|
2815
|
+
if (child.type !== "text" || typeof child.value !== "string") return;
|
|
2816
|
+
const match = child.value.trim().match(STANDALONE_RE);
|
|
2817
|
+
if (!match) return;
|
|
2818
|
+
const inner = match[1];
|
|
2819
|
+
const hashIdx = inner.indexOf("#");
|
|
2820
|
+
const target = (hashIdx !== -1 ? inner.slice(0, hashIdx) : inner).trim();
|
|
2821
|
+
const section = hashIdx !== -1 ? inner.slice(hashIdx + 1).trim() : void 0;
|
|
2822
|
+
const payload = section ? { target, section } : { target };
|
|
2823
|
+
const data = n.data ?? (n.data = {});
|
|
2824
|
+
data.hName = BRAND_TRANSCLUSION_TAG;
|
|
2825
|
+
data.hProperties = { [BRAND_TRANSCLUSION_PROP]: JSON.stringify(payload) };
|
|
2826
|
+
n.children = [];
|
|
2827
|
+
});
|
|
2828
|
+
};
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
function nestedItemContains(node, line) {
|
|
2832
|
+
const kids = node?.children ?? [];
|
|
2833
|
+
for (const kid of kids) {
|
|
2834
|
+
const el = kid;
|
|
2835
|
+
if (el.tagName === "li") {
|
|
2836
|
+
const pos = getSourcePos(kid);
|
|
2837
|
+
if (pos && line >= pos.start && line <= pos.end) return true;
|
|
2838
|
+
}
|
|
2839
|
+
if (nestedItemContains(kid, line)) return true;
|
|
2840
|
+
}
|
|
2841
|
+
return false;
|
|
2842
|
+
}
|
|
2843
|
+
function getSourcePos(node) {
|
|
2844
|
+
const pos = node?.position;
|
|
2845
|
+
if (typeof pos?.start?.line !== "number") return void 0;
|
|
2846
|
+
return { start: pos.start.line, end: pos.end?.line ?? pos.start.line };
|
|
2847
|
+
}
|
|
2848
|
+
function RemovedMarker({ count }) {
|
|
2849
|
+
return /* @__PURE__ */ jsxs10(
|
|
2850
|
+
"div",
|
|
2851
|
+
{
|
|
2852
|
+
role: "note",
|
|
2853
|
+
"aria-label": `${count} ${count === 1 ? "line" : "lines"} removed here`,
|
|
2854
|
+
className: "flex items-center gap-2 text-meta text-destructive-text",
|
|
2855
|
+
children: [
|
|
2856
|
+
/* @__PURE__ */ jsx12("span", { "aria-hidden": "true", className: "font-mono", children: "\u2212" }),
|
|
2857
|
+
/* @__PURE__ */ jsx12("span", { "aria-hidden": "true", className: "flex-1 border-t border-dashed border-destructive/40" }),
|
|
2858
|
+
/* @__PURE__ */ jsxs10("span", { children: [
|
|
2859
|
+
count,
|
|
2860
|
+
" ",
|
|
2861
|
+
count === 1 ? "line" : "lines",
|
|
2862
|
+
" removed"
|
|
2863
|
+
] }),
|
|
2864
|
+
/* @__PURE__ */ jsx12("span", { "aria-hidden": "true", className: "flex-1 border-t border-dashed border-destructive/40" })
|
|
2865
|
+
]
|
|
2866
|
+
}
|
|
2867
|
+
);
|
|
2868
|
+
}
|
|
2869
|
+
function annotated(render, searchWash = true) {
|
|
2870
|
+
return function AnnotatedBlock(props) {
|
|
2871
|
+
const annotations = useContext4(AnnotationsContext);
|
|
2872
|
+
const search = useContext4(SearchContext);
|
|
2873
|
+
const pos = getSourcePos(props.node);
|
|
2874
|
+
const enriched = pos ? { ...props, "data-sourcepos": `${pos.start}:${pos.end}` } : props;
|
|
2875
|
+
const wash = pos && annotations.length > 0 ? annotationForRange(annotations, pos.start, pos.end) : void 0;
|
|
2876
|
+
const removed = pos && annotations.length > 0 ? removedMarkerAt(annotations, pos.start) : void 0;
|
|
2877
|
+
const activeSearch = searchWash && pos != null && search.activeLine != null && search.activeLine >= pos.start && search.activeLine <= pos.end;
|
|
2878
|
+
let content = render(enriched);
|
|
2879
|
+
if (!wash && !removed && !activeSearch) return content;
|
|
2880
|
+
if (wash) {
|
|
2881
|
+
content = /* @__PURE__ */ jsx12(
|
|
2882
|
+
"div",
|
|
2883
|
+
{
|
|
2884
|
+
"data-annotation": wash.kind,
|
|
2885
|
+
className: "border-s-2 border-s-success bg-success/10 py-1.5 pe-2 ps-3",
|
|
2886
|
+
children: content
|
|
2887
|
+
}
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2890
|
+
if (activeSearch) {
|
|
2891
|
+
content = /* @__PURE__ */ jsx12("div", { "data-search-active": "", className: "-mx-2 rounded-md bg-primary/10 px-2 py-1", children: content });
|
|
2892
|
+
}
|
|
2893
|
+
return /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
2894
|
+
removed ? /* @__PURE__ */ jsx12(RemovedMarker, { count: removed.removedCount ?? 1 }) : null,
|
|
2895
|
+
content
|
|
2896
|
+
] });
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
function heading(level) {
|
|
2900
|
+
return function HeadingMd({ node: _n, children, ...rest }) {
|
|
2901
|
+
const headingActions = useContext4(HeadingActionsContext);
|
|
2902
|
+
const start = rest["data-sourcepos"]?.split(":")[0];
|
|
2903
|
+
const line = start ? Number(start) : void 0;
|
|
2904
|
+
const headingId = useHeadingId(line);
|
|
2905
|
+
const slot = headingActions?.({
|
|
2906
|
+
level,
|
|
2907
|
+
text: flattenNodeText(children),
|
|
2908
|
+
line
|
|
2909
|
+
});
|
|
2910
|
+
return /* @__PURE__ */ jsxs10(
|
|
2911
|
+
ProseHeading,
|
|
2912
|
+
{
|
|
2913
|
+
level,
|
|
2914
|
+
id: headingId,
|
|
2915
|
+
...rest,
|
|
2916
|
+
className: cn11(
|
|
2917
|
+
slot ? "group/heading" : void 0,
|
|
2918
|
+
headingId ? "scroll-mt-4" : void 0,
|
|
2919
|
+
rest.className
|
|
2920
|
+
),
|
|
2921
|
+
children: [
|
|
2922
|
+
children,
|
|
2923
|
+
slot ? /* @__PURE__ */ jsx12(
|
|
2924
|
+
"span",
|
|
2925
|
+
{
|
|
2926
|
+
className: "ms-1.5 inline-flex align-middle opacity-0 transition-opacity duration-fast ease-standard focus-within:opacity-100 group-hover/heading:opacity-100 has-[[aria-pressed=true]]:opacity-100 motion-reduce:transition-none",
|
|
2927
|
+
children: slot
|
|
2928
|
+
}
|
|
2929
|
+
) : null
|
|
2930
|
+
]
|
|
2931
|
+
}
|
|
2932
|
+
);
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
var CALLOUT_VARIANT = {
|
|
2936
|
+
info: "info",
|
|
2937
|
+
note: "info",
|
|
2938
|
+
tip: "success",
|
|
2939
|
+
success: "success",
|
|
2940
|
+
warning: "warning",
|
|
2941
|
+
caution: "warning",
|
|
2942
|
+
danger: "destructive",
|
|
2943
|
+
error: "destructive",
|
|
2944
|
+
destructive: "destructive"
|
|
2945
|
+
};
|
|
2946
|
+
var TIMELINE_STATUS = {
|
|
2947
|
+
done: "done",
|
|
2948
|
+
complete: "done",
|
|
2949
|
+
completed: "done",
|
|
2950
|
+
active: "active",
|
|
2951
|
+
current: "active",
|
|
2952
|
+
pending: "pending",
|
|
2953
|
+
todo: "pending"
|
|
2954
|
+
};
|
|
2955
|
+
function UnknownBlock({ name }) {
|
|
2956
|
+
return /* @__PURE__ */ jsxs10(Alert, { variant: "destructive", children: [
|
|
2957
|
+
/* @__PURE__ */ jsxs10(AlertTitle, { children: [
|
|
2958
|
+
"Unknown block: ",
|
|
2959
|
+
name
|
|
2960
|
+
] }),
|
|
2961
|
+
/* @__PURE__ */ jsxs10(AlertDescription, { children: [
|
|
2962
|
+
"No renderer is mapped for ",
|
|
2963
|
+
/* @__PURE__ */ jsxs10("code", { children: [
|
|
2964
|
+
":::",
|
|
2965
|
+
name
|
|
2966
|
+
] }),
|
|
2967
|
+
". Add it to the brand directive registry, or fix the directive name."
|
|
2968
|
+
] })
|
|
2969
|
+
] });
|
|
2970
|
+
}
|
|
2971
|
+
function readDirectivePayload(rest) {
|
|
2972
|
+
const raw = rest[BRAND_DIRECTIVE_ATTR] ?? rest.dataBrand;
|
|
2973
|
+
if (!raw) return null;
|
|
2974
|
+
try {
|
|
2975
|
+
return JSON.parse(raw);
|
|
2976
|
+
} catch {
|
|
2977
|
+
return "malformed";
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
function BrandDirective({ node: _n, children, ...rest }) {
|
|
2981
|
+
const registry = useContext4(RegistryContext);
|
|
2982
|
+
const payload = readDirectivePayload(rest);
|
|
2983
|
+
if (payload === null) return null;
|
|
2984
|
+
if (payload === "malformed") return /* @__PURE__ */ jsx12(UnknownBlock, { name: "malformed" });
|
|
2985
|
+
if (!payload.known) return /* @__PURE__ */ jsx12(UnknownBlock, { name: payload.name });
|
|
2986
|
+
const attrs = payload.attributes ?? {};
|
|
2987
|
+
switch (payload.name) {
|
|
2988
|
+
case "card":
|
|
2989
|
+
return /* @__PURE__ */ jsxs10(Card, { children: [
|
|
2990
|
+
attrs.title ? /* @__PURE__ */ jsx12(CardHeader, { children: /* @__PURE__ */ jsx12(CardTitle, { children: attrs.title }) }) : null,
|
|
2991
|
+
/* @__PURE__ */ jsx12(CardContent, { className: cn11(!attrs.title && "pt-6"), children })
|
|
2992
|
+
] });
|
|
2993
|
+
case "callout":
|
|
2994
|
+
return /* @__PURE__ */ jsxs10(Alert, { variant: CALLOUT_VARIANT[attrs.type ?? ""] ?? "default", children: [
|
|
2995
|
+
attrs.title ? /* @__PURE__ */ jsx12("div", { className: "mb-1 font-medium leading-none tracking-tight", children: attrs.title }) : null,
|
|
2996
|
+
/* @__PURE__ */ jsx12(AlertDescription, { children })
|
|
2997
|
+
] });
|
|
2998
|
+
case "metric":
|
|
2999
|
+
return /* @__PURE__ */ jsx12(
|
|
3000
|
+
MetricBlock,
|
|
3001
|
+
{
|
|
3002
|
+
label: attrs.label ?? "",
|
|
3003
|
+
value: attrs.value ?? "",
|
|
3004
|
+
description: attrs.description,
|
|
3005
|
+
delta: attrs.delta,
|
|
3006
|
+
deltaDirection: attrs.delta?.startsWith("+") ? "up" : attrs.delta?.startsWith("-") ? "down" : "neutral"
|
|
3007
|
+
}
|
|
3008
|
+
);
|
|
3009
|
+
case "timeline":
|
|
3010
|
+
return /* @__PURE__ */ jsx12(
|
|
3011
|
+
Timeline,
|
|
3012
|
+
{
|
|
3013
|
+
items: (payload.items ?? []).map((it) => ({
|
|
3014
|
+
title: it.title,
|
|
3015
|
+
status: TIMELINE_STATUS[it.status] ?? "pending"
|
|
3016
|
+
}))
|
|
3017
|
+
}
|
|
3018
|
+
);
|
|
3019
|
+
default: {
|
|
3020
|
+
const renderer = registry.directives.get(payload.name);
|
|
3021
|
+
if (renderer && (!renderer.kinds || renderer.kinds.includes(payload.kind))) {
|
|
3022
|
+
return /* @__PURE__ */ jsx12(Fragment2, { children: renderer.render({
|
|
3023
|
+
name: payload.name,
|
|
3024
|
+
kind: payload.kind,
|
|
3025
|
+
attributes: attrs,
|
|
3026
|
+
children,
|
|
3027
|
+
textValue: payload.label,
|
|
3028
|
+
rawBody: payload.body
|
|
3029
|
+
}) });
|
|
3030
|
+
}
|
|
3031
|
+
return /* @__PURE__ */ jsx12(UnknownBlock, { name: payload.name });
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
function BrandInlineDirective({ node: _n, children, ...rest }) {
|
|
3036
|
+
const registry = useContext4(RegistryContext);
|
|
3037
|
+
const payload = readDirectivePayload(rest);
|
|
3038
|
+
if (payload === null || payload === "malformed") return /* @__PURE__ */ jsx12(Fragment2, { children });
|
|
3039
|
+
const renderer = registry.directives.get(payload.name);
|
|
3040
|
+
if (!renderer || renderer.kinds && !renderer.kinds.includes("inline")) {
|
|
3041
|
+
return /* @__PURE__ */ jsx12(Fragment2, { children });
|
|
3042
|
+
}
|
|
3043
|
+
return /* @__PURE__ */ jsx12(Fragment2, { children: renderer.render({
|
|
3044
|
+
name: payload.name,
|
|
3045
|
+
kind: "inline",
|
|
3046
|
+
attributes: payload.attributes ?? {},
|
|
3047
|
+
children,
|
|
3048
|
+
textValue: payload.label
|
|
3049
|
+
}) });
|
|
3050
|
+
}
|
|
3051
|
+
function fenceText(children) {
|
|
3052
|
+
if (typeof children === "string") return children;
|
|
3053
|
+
if (Array.isArray(children)) return children.map((c) => fenceText(c)).join("");
|
|
3054
|
+
return "";
|
|
3055
|
+
}
|
|
3056
|
+
function isMermaidCodeElement(child) {
|
|
3057
|
+
return isValidElement(child) && /\blanguage-mermaid\b/.test(child.props?.className ?? "");
|
|
3058
|
+
}
|
|
3059
|
+
function PreBlock({ node, children, ...rest }) {
|
|
3060
|
+
const search = useContext4(SearchContext);
|
|
3061
|
+
const registry = useContext4(RegistryContext);
|
|
3062
|
+
const pos = getSourcePos(node);
|
|
3063
|
+
const activeInBlock = pos != null && search.activeLine != null && search.activeLine >= pos.start && search.activeLine <= pos.end;
|
|
3064
|
+
const list = Array.isArray(children) ? children : [children];
|
|
3065
|
+
const mermaidChild = list.find(isMermaidCodeElement);
|
|
3066
|
+
if (mermaidChild) {
|
|
3067
|
+
const chart = fenceText(mermaidChild.props.children).replace(
|
|
3068
|
+
/\n$/,
|
|
3069
|
+
""
|
|
3070
|
+
);
|
|
3071
|
+
return /* @__PURE__ */ jsx12(
|
|
3072
|
+
MermaidDiagram,
|
|
3073
|
+
{
|
|
3074
|
+
chart,
|
|
3075
|
+
"data-sourcepos": pos ? `${pos.start}:${pos.end}` : void 0,
|
|
3076
|
+
highlightTerm: search.term,
|
|
3077
|
+
activeText: activeInBlock && search.activeLine != null ? search.lines[search.activeLine - 1] : void 0
|
|
3078
|
+
}
|
|
3079
|
+
);
|
|
3080
|
+
}
|
|
3081
|
+
const codeEl = list.find(isValidElement);
|
|
3082
|
+
const fenceLang = fenceLanguage(codeEl?.props.className);
|
|
3083
|
+
const fenceRenderer = fenceLang ? registry.fences.get(fenceLang) : void 0;
|
|
3084
|
+
if (fenceLang && fenceRenderer && codeEl) {
|
|
3085
|
+
const source = fenceText(codeEl.props.children).replace(/\n$/, "");
|
|
3086
|
+
const rendered = fenceRenderer.render({ source, lang: fenceLang });
|
|
3087
|
+
return pos ? /* @__PURE__ */ jsx12("div", { "data-sourcepos": `${pos.start}:${pos.end}`, children: rendered }) : /* @__PURE__ */ jsx12(Fragment2, { children: rendered });
|
|
3088
|
+
}
|
|
3089
|
+
const codeText = fenceText(codeEl ? codeEl.props.children : children).replace(
|
|
3090
|
+
/\n$/,
|
|
3091
|
+
""
|
|
3092
|
+
);
|
|
3093
|
+
return /* @__PURE__ */ jsx12(
|
|
3094
|
+
CodeFence,
|
|
3095
|
+
{
|
|
3096
|
+
...rest,
|
|
3097
|
+
codeText,
|
|
3098
|
+
language: fenceLang,
|
|
3099
|
+
searchActive: activeInBlock,
|
|
3100
|
+
children
|
|
3101
|
+
}
|
|
3102
|
+
);
|
|
3103
|
+
}
|
|
3104
|
+
function ImageMd({ node: _n, src, alt, ...rest }) {
|
|
3105
|
+
return /* @__PURE__ */ jsx12(
|
|
3106
|
+
"img",
|
|
3107
|
+
{
|
|
3108
|
+
src,
|
|
3109
|
+
alt: alt ?? "",
|
|
3110
|
+
loading: "lazy",
|
|
3111
|
+
className: "max-w-full rounded-md border border-border",
|
|
3112
|
+
...rest
|
|
3113
|
+
}
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
function LinkMd({ node: _n, href, children, ...rest }) {
|
|
3117
|
+
const renderLinkPreview = useContext4(LinkPreviewContext);
|
|
3118
|
+
const anchor = /* @__PURE__ */ jsx12(ProseLink, { href, ...rest, children });
|
|
3119
|
+
if (renderLinkPreview && typeof href === "string") {
|
|
3120
|
+
return /* @__PURE__ */ jsx12(Fragment2, { children: renderLinkPreview(href, anchor) });
|
|
3121
|
+
}
|
|
3122
|
+
return anchor;
|
|
3123
|
+
}
|
|
3124
|
+
function TransclusionBlock({ node: _n, ...rest }) {
|
|
3125
|
+
const resolveTransclusion = useContext4(TransclusionResolverContext);
|
|
3126
|
+
const depth = useContext4(TransclusionDepthContext);
|
|
3127
|
+
const linkPreview = useContext4(LinkPreviewContext);
|
|
3128
|
+
const rawAttr = rest[BRAND_TRANSCLUSION_ATTR] ?? rest.dataTransclusion;
|
|
3129
|
+
if (!rawAttr || !resolveTransclusion) {
|
|
3130
|
+
return /* @__PURE__ */ jsx12("span", { children: rawAttr ? `![[${JSON.parse(rawAttr).target}]]` : null });
|
|
3131
|
+
}
|
|
3132
|
+
let payload;
|
|
3133
|
+
try {
|
|
3134
|
+
payload = JSON.parse(rawAttr);
|
|
3135
|
+
} catch {
|
|
3136
|
+
return null;
|
|
3137
|
+
}
|
|
3138
|
+
const { target, section } = payload;
|
|
3139
|
+
const label = section ? `${target}#${section}` : target;
|
|
3140
|
+
if (depth >= TRANSCLUSION_MAX_DEPTH) {
|
|
3141
|
+
return /* @__PURE__ */ jsxs10(
|
|
3142
|
+
"figure",
|
|
3143
|
+
{
|
|
3144
|
+
"aria-label": `Embedded: ${label}`,
|
|
3145
|
+
className: "my-3 rounded-md border-s-2 border-s-muted bg-muted/40 px-4 py-3",
|
|
3146
|
+
"data-testid": "transclusion-block",
|
|
3147
|
+
"data-transclusion-depth": depth,
|
|
3148
|
+
children: [
|
|
3149
|
+
/* @__PURE__ */ jsx12("figcaption", { className: "mb-1 text-meta text-muted-foreground", children: label }),
|
|
3150
|
+
/* @__PURE__ */ jsx12("p", { className: "text-meta text-muted-foreground italic", children: "Transclusion too deep \u2014 embed skipped." })
|
|
3151
|
+
]
|
|
3152
|
+
}
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
const content = resolveTransclusion(target, section ? { section } : {});
|
|
3156
|
+
if (content === null) {
|
|
3157
|
+
return /* @__PURE__ */ jsx12("span", { children: `![[${label}]]` });
|
|
3158
|
+
}
|
|
3159
|
+
return /* @__PURE__ */ jsx12(TransclusionDepthContext.Provider, { value: depth + 1, children: /* @__PURE__ */ jsx12(LinkPreviewContext.Provider, { value: linkPreview, children: /* @__PURE__ */ jsx12(RecursiveTransclusionContent, { target, label, content }) }) });
|
|
3160
|
+
}
|
|
3161
|
+
function RecursiveTransclusionContent({
|
|
3162
|
+
target: _target,
|
|
3163
|
+
label,
|
|
3164
|
+
content
|
|
3165
|
+
}) {
|
|
3166
|
+
const plugins = useMemo5(() => {
|
|
3167
|
+
return [...baseRemarkPlugins, ...buildMarkdownPlugins()];
|
|
3168
|
+
}, []);
|
|
3169
|
+
return /* @__PURE__ */ jsxs10(
|
|
3170
|
+
"figure",
|
|
3171
|
+
{
|
|
3172
|
+
"aria-label": `Embedded: ${label}`,
|
|
3173
|
+
className: "my-3 rounded-md border-s-2 border-s-muted bg-muted/40 px-4 py-2",
|
|
3174
|
+
"data-testid": "transclusion-block",
|
|
3175
|
+
children: [
|
|
3176
|
+
/* @__PURE__ */ jsx12("figcaption", { className: "mb-1.5 text-meta text-muted-foreground", children: label }),
|
|
3177
|
+
/* @__PURE__ */ jsx12("div", { className: "text-body text-foreground", children: /* @__PURE__ */ jsx12(
|
|
3178
|
+
Streamdown,
|
|
3179
|
+
{
|
|
3180
|
+
parseMarkdownIntoBlocksFn: singleBlock,
|
|
3181
|
+
remarkPlugins: plugins,
|
|
3182
|
+
rehypePlugins,
|
|
3183
|
+
allowedTags,
|
|
3184
|
+
components,
|
|
3185
|
+
children: content
|
|
3186
|
+
}
|
|
3187
|
+
) })
|
|
3188
|
+
]
|
|
3189
|
+
}
|
|
3190
|
+
);
|
|
3191
|
+
}
|
|
3192
|
+
var components = {
|
|
3193
|
+
h1: annotated(heading(1)),
|
|
3194
|
+
h2: annotated(heading(2)),
|
|
3195
|
+
h3: annotated(heading(3)),
|
|
3196
|
+
h4: annotated(heading(4)),
|
|
3197
|
+
h5: annotated(heading(5)),
|
|
3198
|
+
h6: annotated(heading(6)),
|
|
3199
|
+
p: annotated(({ node: _n, ...p }) => /* @__PURE__ */ jsx12(ProseText, { ...p })),
|
|
3200
|
+
a: LinkMd,
|
|
3201
|
+
img: ImageMd,
|
|
3202
|
+
// Lists wash at ITEM granularity (a whole-list wash drowns the page), so the
|
|
3203
|
+
// ul/ol wrappers opt out of the search wash and the li carries it inline
|
|
3204
|
+
// (no wrapper div — that would break list semantics).
|
|
3205
|
+
ul: annotated(
|
|
3206
|
+
({ node: _n, ...p }) => /* @__PURE__ */ jsx12(ProseList, { ...p }),
|
|
3207
|
+
false
|
|
3208
|
+
),
|
|
3209
|
+
ol: annotated(
|
|
3210
|
+
({ node: _n, ...p }) => /* @__PURE__ */ jsx12(ProseList, { ordered: true, ...p }),
|
|
3211
|
+
false
|
|
3212
|
+
),
|
|
3213
|
+
li: function ListItemMd({ node, ...p }) {
|
|
3214
|
+
const search = useContext4(SearchContext);
|
|
3215
|
+
const pos = getSourcePos(node);
|
|
3216
|
+
const active = pos != null && search.activeLine != null && search.activeLine >= pos.start && search.activeLine <= pos.end && !nestedItemContains(node, search.activeLine);
|
|
3217
|
+
return /* @__PURE__ */ jsx12(
|
|
3218
|
+
ProseListItem,
|
|
3219
|
+
{
|
|
3220
|
+
"data-sourcepos": pos ? `${pos.start}:${pos.end}` : void 0,
|
|
3221
|
+
"data-search-active": active ? "" : void 0,
|
|
3222
|
+
...p,
|
|
3223
|
+
className: cn11(active && "-mx-1 rounded-sm bg-primary/10 px-1", p.className)
|
|
3224
|
+
}
|
|
3225
|
+
);
|
|
3226
|
+
},
|
|
3227
|
+
blockquote: annotated(({ node: _n, ...p }) => /* @__PURE__ */ jsx12(ProseBlockquote, { ...p })),
|
|
3228
|
+
hr: annotated(() => /* @__PURE__ */ jsx12(Separator3, { className: "my-4" })),
|
|
3229
|
+
pre: annotated(PreBlock, false),
|
|
3230
|
+
table: annotated(({ node: _n, ...p }) => /* @__PURE__ */ jsx12(Table, { ...p })),
|
|
3231
|
+
thead: ({ node: _n, ...p }) => /* @__PURE__ */ jsx12(TableHeader, { ...p }),
|
|
3232
|
+
tbody: ({ node: _n, ...p }) => /* @__PURE__ */ jsx12(TableBody, { ...p }),
|
|
3233
|
+
tr: ({ node: _n, ...p }) => /* @__PURE__ */ jsx12(TableRow, { ...p }),
|
|
3234
|
+
th: ({ node: _n, ...p }) => /* @__PURE__ */ jsx12(TableHead, { ...p }),
|
|
3235
|
+
td: ({ node: _n, ...p }) => /* @__PURE__ */ jsx12(TableCell, { ...p }),
|
|
3236
|
+
[BRAND_DIRECTIVE_TAG]: annotated(BrandDirective),
|
|
3237
|
+
// Inline directives render un-`annotated` (no block wrapper) to stay in the text flow.
|
|
3238
|
+
[BRAND_DIRECTIVE_INLINE_TAG]: BrandInlineDirective,
|
|
3239
|
+
// Transclusion embeds (`![[target]]`) — resolved + recursively rendered by TransclusionBlock.
|
|
3240
|
+
[BRAND_TRANSCLUSION_TAG]: TransclusionBlock,
|
|
3241
|
+
// Academic layer — footnotes, math, citations (inline tags stay in the text flow;
|
|
3242
|
+
// the footnote section is a generated block).
|
|
3243
|
+
[FOOTNOTE_REF_TAG]: FootnoteRef,
|
|
3244
|
+
[FOOTNOTE_ITEM_TAG]: FootnoteItem,
|
|
3245
|
+
[FOOTNOTE_LIST_TAG]: ({ node: _n, children, ...rest }) => /* @__PURE__ */ jsx12(FootnoteList, { ...rest, children }),
|
|
3246
|
+
[MATH_INLINE_TAG]: MathInlineTag,
|
|
3247
|
+
[MATH_BLOCK_TAG]: MathBlockTag,
|
|
3248
|
+
[CITE_TAG]: InlineCite
|
|
3249
|
+
};
|
|
3250
|
+
var MarkdownPreview = forwardRef7(
|
|
3251
|
+
function MarkdownPreview2({
|
|
3252
|
+
children,
|
|
3253
|
+
stripFrontmatter = true,
|
|
3254
|
+
annotations,
|
|
3255
|
+
resolveUrl,
|
|
3256
|
+
searchTerm,
|
|
3257
|
+
activeSearchLine,
|
|
3258
|
+
headingActions,
|
|
3259
|
+
evaluate,
|
|
3260
|
+
extensions,
|
|
3261
|
+
resolveWikilink,
|
|
3262
|
+
resolveTransclusion,
|
|
3263
|
+
renderLinkPreview,
|
|
3264
|
+
footnotes,
|
|
3265
|
+
math,
|
|
3266
|
+
resolveCitation,
|
|
3267
|
+
citationStyle = "numeric",
|
|
3268
|
+
toc,
|
|
3269
|
+
evaluateIteration,
|
|
3270
|
+
interpolate,
|
|
3271
|
+
className,
|
|
3272
|
+
...props
|
|
3273
|
+
}, ref) {
|
|
3274
|
+
const markdown = stripFrontmatter ? parseFrontmatter(children).content : children;
|
|
3275
|
+
const fmOffset = stripFrontmatter ? children.split("\n").length - markdown.split("\n").length : 0;
|
|
3276
|
+
const shifted = useMemo5(() => {
|
|
3277
|
+
if (!annotations?.length) return [];
|
|
3278
|
+
return fmOffset ? shiftAnnotations(annotations, fmOffset) : annotations;
|
|
3279
|
+
}, [annotations, fmOffset]);
|
|
3280
|
+
const search = useMemo5(() => {
|
|
3281
|
+
const term = searchTerm?.trim();
|
|
3282
|
+
const activeLine = activeSearchLine != null && activeSearchLine - fmOffset >= 1 ? activeSearchLine - fmOffset : void 0;
|
|
3283
|
+
return {
|
|
3284
|
+
term: term && term.length >= 2 ? term : void 0,
|
|
3285
|
+
activeLine,
|
|
3286
|
+
lines: markdown.split("\n")
|
|
3287
|
+
};
|
|
3288
|
+
}, [searchTerm, activeSearchLine, fmOffset, markdown]);
|
|
3289
|
+
const citations = useMemo5(() => {
|
|
3290
|
+
if (!resolveCitation) return null;
|
|
3291
|
+
return collectCitations(markdown, resolveCitation);
|
|
3292
|
+
}, [markdown, resolveCitation]);
|
|
3293
|
+
const outline = useMemo5(() => toc ? parseMarkdownOutline(markdown) : null, [toc, markdown]);
|
|
3294
|
+
const registry = useMemo5(() => {
|
|
3295
|
+
const directives = /* @__PURE__ */ new Map();
|
|
3296
|
+
for (const d of extensions?.directives ?? []) directives.set(d.name, d);
|
|
3297
|
+
const fences = /* @__PURE__ */ new Map();
|
|
3298
|
+
for (const f of extensions?.fences ?? []) fences.set(f.lang, f);
|
|
3299
|
+
if (toc) {
|
|
3300
|
+
directives.set("toc", {
|
|
3301
|
+
name: "toc",
|
|
3302
|
+
kinds: ["leaf", "container"],
|
|
3303
|
+
render: ({ attributes }) => /* @__PURE__ */ jsx12(TableOfContents, { title: attributes.title || void 0 })
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3306
|
+
if (resolveCitation) {
|
|
3307
|
+
const renderBibliography = ({ attributes }) => /* @__PURE__ */ jsx12(Bibliography, { title: attributes.title || void 0 });
|
|
3308
|
+
directives.set("bibliography", {
|
|
3309
|
+
name: "bibliography",
|
|
3310
|
+
kinds: ["leaf", "container"],
|
|
3311
|
+
render: renderBibliography
|
|
3312
|
+
});
|
|
3313
|
+
directives.set("references", {
|
|
3314
|
+
name: "references",
|
|
3315
|
+
kinds: ["leaf", "container"],
|
|
3316
|
+
render: renderBibliography
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3319
|
+
if (evaluateIteration) {
|
|
3320
|
+
const iterationDirective = (name) => ({
|
|
3321
|
+
name,
|
|
3322
|
+
kinds: ["container"],
|
|
3323
|
+
render: ({ attributes, rawBody }) => /* @__PURE__ */ jsx12(
|
|
3324
|
+
IterationDirective,
|
|
3325
|
+
{
|
|
3326
|
+
spec: specFromDirective(name, attributes, rawBody),
|
|
3327
|
+
evaluate: evaluateIteration,
|
|
3328
|
+
interpolate,
|
|
3329
|
+
renderCell: (md) => /* @__PURE__ */ jsx12(
|
|
3330
|
+
IterationCell,
|
|
3331
|
+
{
|
|
3332
|
+
markdown: md,
|
|
3333
|
+
config: {
|
|
3334
|
+
evaluateIteration,
|
|
3335
|
+
interpolate,
|
|
3336
|
+
evaluate,
|
|
3337
|
+
extensions,
|
|
3338
|
+
footnotes,
|
|
3339
|
+
math,
|
|
3340
|
+
resolveCitation,
|
|
3341
|
+
citationStyle
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
)
|
|
3345
|
+
}
|
|
3346
|
+
)
|
|
3347
|
+
});
|
|
3348
|
+
directives.set("iterate", iterationDirective("iterate"));
|
|
3349
|
+
directives.set("pivot", iterationDirective("pivot"));
|
|
3350
|
+
}
|
|
3351
|
+
if (evaluate && !fences.has("calc")) {
|
|
3352
|
+
fences.set("calc", {
|
|
3353
|
+
lang: "calc",
|
|
3354
|
+
render: ({ source }) => /* @__PURE__ */ jsx12(CalcBlock, { source, evaluate })
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
if (evaluate && !directives.has("calc")) {
|
|
3358
|
+
directives.set("calc", {
|
|
3359
|
+
name: "calc",
|
|
3360
|
+
kinds: ["inline"],
|
|
3361
|
+
// `textValue` is the verbatim expression (markdown chars preserved);
|
|
3362
|
+
// fall back to the rendered label only if positions were unavailable.
|
|
3363
|
+
render: ({ textValue, children: children2 }) => /* @__PURE__ */ jsx12(CalcInline, { source: textValue ?? flattenNodeText(children2), evaluate })
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
return { directives, fences };
|
|
3367
|
+
}, [
|
|
3368
|
+
extensions,
|
|
3369
|
+
evaluate,
|
|
3370
|
+
toc,
|
|
3371
|
+
resolveCitation,
|
|
3372
|
+
citationStyle,
|
|
3373
|
+
evaluateIteration,
|
|
3374
|
+
interpolate,
|
|
3375
|
+
footnotes,
|
|
3376
|
+
math
|
|
3377
|
+
]);
|
|
3378
|
+
const directiveNamesKey = [
|
|
3379
|
+
...(extensions?.directives ?? []).map((d) => d.name),
|
|
3380
|
+
...evaluate ? ["calc"] : [],
|
|
3381
|
+
...toc ? ["toc"] : [],
|
|
3382
|
+
...resolveCitation ? ["bibliography", "references"] : [],
|
|
3383
|
+
...evaluateIteration ? ["iterate", "pivot"] : []
|
|
3384
|
+
].join(" ");
|
|
3385
|
+
const plugins = useMemo5(() => {
|
|
3386
|
+
const directiveNames = directiveNamesKey ? directiveNamesKey.split(" ") : [];
|
|
3387
|
+
const rawBodyNames = evaluateIteration ? ["iterate", "pivot"] : [];
|
|
3388
|
+
let list = [
|
|
3389
|
+
...baseRemarkPlugins,
|
|
3390
|
+
...buildMarkdownPlugins({ directiveNames, rawBodyNames })
|
|
3391
|
+
];
|
|
3392
|
+
if (math) list = [...list, remarkMath, remarkBrandMath];
|
|
3393
|
+
if (footnotes) list = [...list, remarkBrandFootnotes];
|
|
3394
|
+
if (resolveCitation) list = [...list, remarkBrandCitations];
|
|
3395
|
+
if (resolveWikilink) list = [...list, remarkResolveWikilinks(resolveWikilink)];
|
|
3396
|
+
if (resolveTransclusion) list = [...list, remarkResolveTransclusions()];
|
|
3397
|
+
if (resolveUrl) list = [...list, remarkResolveUrls(resolveUrl)];
|
|
3398
|
+
return list;
|
|
3399
|
+
}, [
|
|
3400
|
+
resolveUrl,
|
|
3401
|
+
resolveWikilink,
|
|
3402
|
+
resolveTransclusion,
|
|
3403
|
+
directiveNamesKey,
|
|
3404
|
+
math,
|
|
3405
|
+
footnotes,
|
|
3406
|
+
resolveCitation,
|
|
3407
|
+
evaluateIteration
|
|
3408
|
+
]);
|
|
3409
|
+
const streamdown = /* @__PURE__ */ jsx12(
|
|
3410
|
+
Streamdown,
|
|
3411
|
+
{
|
|
3412
|
+
parseMarkdownIntoBlocksFn: singleBlock,
|
|
3413
|
+
remarkPlugins: plugins,
|
|
3414
|
+
rehypePlugins,
|
|
3415
|
+
allowedTags,
|
|
3416
|
+
components,
|
|
3417
|
+
children: markdown
|
|
3418
|
+
}
|
|
3419
|
+
);
|
|
3420
|
+
const withCitations = citations ? /* @__PURE__ */ jsx12(CitationProvider, { order: citations.order, byKey: citations.byKey, style: citationStyle, children: streamdown }) : streamdown;
|
|
3421
|
+
const body = outline ? /* @__PURE__ */ jsx12(TocProvider, { items: outline, children: withCitations }) : withCitations;
|
|
3422
|
+
return /* @__PURE__ */ jsx12(
|
|
3423
|
+
"div",
|
|
3424
|
+
{
|
|
3425
|
+
ref,
|
|
3426
|
+
"data-testid": "markdown-preview",
|
|
3427
|
+
className: cn11(
|
|
3428
|
+
"text-body text-foreground [&_pre]:my-3",
|
|
3429
|
+
"[&_h1]:!mt-10 [&_h2]:!mt-9 [&_h3]:!mt-7 [&_h4]:!mt-6",
|
|
3430
|
+
"[&_:is(h1,h2,h3,h4)+*]:!mt-3 [&_:is(h1,h2,h3,h4):first-child]:!mt-0",
|
|
3431
|
+
className
|
|
3432
|
+
),
|
|
3433
|
+
...props,
|
|
3434
|
+
children: /* @__PURE__ */ jsx12(AnnotationsContext.Provider, { value: shifted, children: /* @__PURE__ */ jsx12(SearchContext.Provider, { value: search, children: /* @__PURE__ */ jsx12(HeadingActionsContext.Provider, { value: headingActions ?? null, children: /* @__PURE__ */ jsx12(RegistryContext.Provider, { value: registry, children: /* @__PURE__ */ jsx12(LinkPreviewContext.Provider, { value: renderLinkPreview ?? null, children: /* @__PURE__ */ jsx12(TransclusionResolverContext.Provider, { value: resolveTransclusion ?? null, children: /* @__PURE__ */ jsx12(TransclusionDepthContext.Provider, { value: 0, children: body }) }) }) }) }) }) })
|
|
3435
|
+
}
|
|
3436
|
+
);
|
|
3437
|
+
}
|
|
3438
|
+
);
|
|
3439
|
+
function IterationCell({ markdown, config }) {
|
|
3440
|
+
return /* @__PURE__ */ jsx12(MarkdownPreview, { stripFrontmatter: false, ...config, children: markdown });
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
// src/markdown-toolbar/markdown-toolbar.tsx
|
|
3444
|
+
import {
|
|
3445
|
+
Button as Button3,
|
|
3446
|
+
DropdownMenu,
|
|
3447
|
+
DropdownMenuContent,
|
|
3448
|
+
DropdownMenuItem,
|
|
3449
|
+
DropdownMenuLabel,
|
|
3450
|
+
DropdownMenuSeparator,
|
|
3451
|
+
DropdownMenuTrigger,
|
|
3452
|
+
Separator as Separator4,
|
|
3453
|
+
Tooltip,
|
|
3454
|
+
TooltipContent,
|
|
3455
|
+
TooltipProvider,
|
|
3456
|
+
TooltipTrigger
|
|
3457
|
+
} from "@elabs-ai/components-ui";
|
|
3458
|
+
import { cn as cn12 } from "@elabs-ai/components-ui/lib/cn";
|
|
3459
|
+
import {
|
|
3460
|
+
Bold,
|
|
3461
|
+
ChevronDown,
|
|
3462
|
+
Code2,
|
|
3463
|
+
Heading,
|
|
3464
|
+
Italic,
|
|
3465
|
+
Link2,
|
|
3466
|
+
List,
|
|
3467
|
+
ListOrdered,
|
|
3468
|
+
Minus as Minus2,
|
|
3469
|
+
Quote,
|
|
3470
|
+
SquarePlus
|
|
3471
|
+
} from "lucide-react";
|
|
3472
|
+
import { forwardRef as forwardRef8, Fragment as Fragment3 } from "react";
|
|
3473
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3474
|
+
var DIRECTIVE_SNIPPETS = [
|
|
3475
|
+
{ label: "Card", snippet: `:::card{title="Title"}
|
|
3476
|
+
Content
|
|
3477
|
+
:::` },
|
|
3478
|
+
{ label: "Callout", snippet: `:::callout{type="info" title="Note"}
|
|
3479
|
+
Message
|
|
3480
|
+
:::` },
|
|
3481
|
+
{ label: "Metric", snippet: `::metric{label="Label" value="0" description="detail"}` },
|
|
3482
|
+
{
|
|
3483
|
+
label: "Timeline",
|
|
3484
|
+
snippet: `:::timeline
|
|
3485
|
+
- (done) Step one
|
|
3486
|
+
- (active) Step two
|
|
3487
|
+
- (pending) Step three
|
|
3488
|
+
:::`
|
|
3489
|
+
}
|
|
3490
|
+
];
|
|
3491
|
+
var MarkdownToolbar = forwardRef8(
|
|
3492
|
+
function MarkdownToolbar2({ editor: editor2, actions, insertCommands, className, ...props }, ref) {
|
|
3493
|
+
const disabled = !editor2;
|
|
3494
|
+
const run = (fn) => () => {
|
|
3495
|
+
if (editor2) fn(editor2);
|
|
3496
|
+
};
|
|
3497
|
+
const insertGroups = insertCommands ? groupSlashCommands(
|
|
3498
|
+
insertCommands.filter((c) => typeof c.snippet === "string")
|
|
3499
|
+
) : null;
|
|
3500
|
+
const IconButton = ({
|
|
3501
|
+
label,
|
|
3502
|
+
icon,
|
|
3503
|
+
onClick
|
|
3504
|
+
}) => /* @__PURE__ */ jsxs11(Tooltip, { children: [
|
|
3505
|
+
/* @__PURE__ */ jsx13(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx13(
|
|
3506
|
+
Button3,
|
|
3507
|
+
{
|
|
3508
|
+
type: "button",
|
|
3509
|
+
variant: "ghost",
|
|
3510
|
+
size: "icon-sm",
|
|
3511
|
+
disabled,
|
|
3512
|
+
onClick,
|
|
3513
|
+
"aria-label": label,
|
|
3514
|
+
children: icon
|
|
3515
|
+
}
|
|
3516
|
+
) }),
|
|
3517
|
+
/* @__PURE__ */ jsx13(TooltipContent, { children: label })
|
|
3518
|
+
] });
|
|
3519
|
+
return /* @__PURE__ */ jsx13(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs11(
|
|
3520
|
+
"div",
|
|
3521
|
+
{
|
|
3522
|
+
ref,
|
|
3523
|
+
role: "toolbar",
|
|
3524
|
+
"aria-label": "Markdown formatting",
|
|
3525
|
+
className: cn12(
|
|
3526
|
+
"flex h-10 shrink-0 items-center gap-0.5 border-b border-border bg-surface px-2",
|
|
3527
|
+
className
|
|
3528
|
+
),
|
|
3529
|
+
...props,
|
|
3530
|
+
children: [
|
|
3531
|
+
/* @__PURE__ */ jsx13(
|
|
3532
|
+
IconButton,
|
|
3533
|
+
{
|
|
3534
|
+
label: "Bold",
|
|
3535
|
+
icon: /* @__PURE__ */ jsx13(Bold, { className: "size-4" }),
|
|
3536
|
+
onClick: run((e) => wrapSelection(e, "**"))
|
|
3537
|
+
}
|
|
3538
|
+
),
|
|
3539
|
+
/* @__PURE__ */ jsx13(
|
|
3540
|
+
IconButton,
|
|
3541
|
+
{
|
|
3542
|
+
label: "Italic",
|
|
3543
|
+
icon: /* @__PURE__ */ jsx13(Italic, { className: "size-4" }),
|
|
3544
|
+
onClick: run((e) => wrapSelection(e, "*"))
|
|
3545
|
+
}
|
|
3546
|
+
),
|
|
3547
|
+
/* @__PURE__ */ jsx13(
|
|
3548
|
+
IconButton,
|
|
3549
|
+
{
|
|
3550
|
+
label: "Inline code",
|
|
3551
|
+
icon: /* @__PURE__ */ jsx13(Code2, { className: "size-4" }),
|
|
3552
|
+
onClick: run((e) => wrapSelection(e, "`"))
|
|
3553
|
+
}
|
|
3554
|
+
),
|
|
3555
|
+
/* @__PURE__ */ jsx13(IconButton, { label: "Link", icon: /* @__PURE__ */ jsx13(Link2, { className: "size-4" }), onClick: run(insertLink) }),
|
|
3556
|
+
/* @__PURE__ */ jsx13(Separator4, { orientation: "vertical", className: "mx-1 h-5" }),
|
|
3557
|
+
/* @__PURE__ */ jsxs11(DropdownMenu, { children: [
|
|
3558
|
+
/* @__PURE__ */ jsxs11(Tooltip, { children: [
|
|
3559
|
+
/* @__PURE__ */ jsx13(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx13(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs11(
|
|
3560
|
+
Button3,
|
|
3561
|
+
{
|
|
3562
|
+
type: "button",
|
|
3563
|
+
variant: "ghost",
|
|
3564
|
+
size: "sm",
|
|
3565
|
+
disabled,
|
|
3566
|
+
className: "gap-1",
|
|
3567
|
+
"aria-label": "Heading level",
|
|
3568
|
+
children: [
|
|
3569
|
+
/* @__PURE__ */ jsx13(Heading, { className: "size-4" }),
|
|
3570
|
+
/* @__PURE__ */ jsx13(ChevronDown, { className: "size-3" })
|
|
3571
|
+
]
|
|
3572
|
+
}
|
|
3573
|
+
) }) }),
|
|
3574
|
+
/* @__PURE__ */ jsx13(TooltipContent, { children: "Heading" })
|
|
3575
|
+
] }),
|
|
3576
|
+
/* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", children: [1, 2, 3].map((level) => /* @__PURE__ */ jsxs11(
|
|
3577
|
+
DropdownMenuItem,
|
|
3578
|
+
{
|
|
3579
|
+
onSelect: run((e) => toggleLinePrefix(e, `${"#".repeat(level)} `)),
|
|
3580
|
+
children: [
|
|
3581
|
+
"Heading ",
|
|
3582
|
+
level
|
|
3583
|
+
]
|
|
3584
|
+
},
|
|
3585
|
+
level
|
|
3586
|
+
)) })
|
|
3587
|
+
] }),
|
|
3588
|
+
/* @__PURE__ */ jsx13(
|
|
3589
|
+
IconButton,
|
|
3590
|
+
{
|
|
3591
|
+
label: "Quote",
|
|
3592
|
+
icon: /* @__PURE__ */ jsx13(Quote, { className: "size-4" }),
|
|
3593
|
+
onClick: run((e) => toggleLinePrefix(e, "> "))
|
|
3594
|
+
}
|
|
3595
|
+
),
|
|
3596
|
+
/* @__PURE__ */ jsx13(
|
|
3597
|
+
IconButton,
|
|
3598
|
+
{
|
|
3599
|
+
label: "Bullet list",
|
|
3600
|
+
icon: /* @__PURE__ */ jsx13(List, { className: "size-4" }),
|
|
3601
|
+
onClick: run((e) => toggleLinePrefix(e, "- "))
|
|
3602
|
+
}
|
|
3603
|
+
),
|
|
3604
|
+
/* @__PURE__ */ jsx13(
|
|
3605
|
+
IconButton,
|
|
3606
|
+
{
|
|
3607
|
+
label: "Numbered list",
|
|
3608
|
+
icon: /* @__PURE__ */ jsx13(ListOrdered, { className: "size-4" }),
|
|
3609
|
+
onClick: run((e) => toggleLinePrefix(e, "1. "))
|
|
3610
|
+
}
|
|
3611
|
+
),
|
|
3612
|
+
/* @__PURE__ */ jsx13(
|
|
3613
|
+
IconButton,
|
|
3614
|
+
{
|
|
3615
|
+
label: "Divider",
|
|
3616
|
+
icon: /* @__PURE__ */ jsx13(Minus2, { className: "size-4" }),
|
|
3617
|
+
onClick: run(insertHorizontalRule)
|
|
3618
|
+
}
|
|
3619
|
+
),
|
|
3620
|
+
/* @__PURE__ */ jsx13(Separator4, { orientation: "vertical", className: "mx-1 h-5" }),
|
|
3621
|
+
/* @__PURE__ */ jsxs11(DropdownMenu, { children: [
|
|
3622
|
+
/* @__PURE__ */ jsxs11(Tooltip, { children: [
|
|
3623
|
+
/* @__PURE__ */ jsx13(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx13(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs11(
|
|
3624
|
+
Button3,
|
|
3625
|
+
{
|
|
3626
|
+
type: "button",
|
|
3627
|
+
variant: "ghost",
|
|
3628
|
+
size: "sm",
|
|
3629
|
+
disabled,
|
|
3630
|
+
className: "gap-1",
|
|
3631
|
+
"aria-label": "Insert block",
|
|
3632
|
+
children: [
|
|
3633
|
+
/* @__PURE__ */ jsx13(SquarePlus, { className: "size-4" }),
|
|
3634
|
+
/* @__PURE__ */ jsx13("span", { className: "text-xs", children: "Insert" })
|
|
3635
|
+
]
|
|
3636
|
+
}
|
|
3637
|
+
) }) }),
|
|
3638
|
+
/* @__PURE__ */ jsx13(TooltipContent, { children: "Insert brand block" })
|
|
3639
|
+
] }),
|
|
3640
|
+
/* @__PURE__ */ jsx13(DropdownMenuContent, { align: "start", children: insertGroups && insertGroups.length > 0 ? insertGroups.map(({ group, commands }, gi) => /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
3641
|
+
gi > 0 ? /* @__PURE__ */ jsx13(DropdownMenuSeparator, {}) : null,
|
|
3642
|
+
/* @__PURE__ */ jsx13(DropdownMenuLabel, { className: "text-meta font-medium text-muted-foreground", children: group }),
|
|
3643
|
+
commands.map((cmd) => /* @__PURE__ */ jsxs11(
|
|
3644
|
+
DropdownMenuItem,
|
|
3645
|
+
{
|
|
3646
|
+
className: "gap-2",
|
|
3647
|
+
onSelect: run((e) => insertDirective(e, cmd.snippet)),
|
|
3648
|
+
children: [
|
|
3649
|
+
cmd.icon ? /* @__PURE__ */ jsx13("span", { className: "flex size-4 shrink-0 items-center justify-center text-muted-foreground [&_svg]:size-4", children: cmd.icon }) : null,
|
|
3650
|
+
cmd.label
|
|
3651
|
+
]
|
|
3652
|
+
},
|
|
3653
|
+
cmd.id
|
|
3654
|
+
))
|
|
3655
|
+
] }, group)) : DIRECTIVE_SNIPPETS.map(({ label, snippet }) => /* @__PURE__ */ jsx13(
|
|
3656
|
+
DropdownMenuItem,
|
|
3657
|
+
{
|
|
3658
|
+
onSelect: run((e) => insertDirective(e, snippet)),
|
|
3659
|
+
children: label
|
|
3660
|
+
},
|
|
3661
|
+
label
|
|
3662
|
+
)) })
|
|
3663
|
+
] }),
|
|
3664
|
+
actions ? /* @__PURE__ */ jsx13("div", { className: "ml-auto flex items-center gap-1.5", children: actions }) : null
|
|
3665
|
+
]
|
|
3666
|
+
}
|
|
3667
|
+
) });
|
|
3668
|
+
}
|
|
3669
|
+
);
|
|
3670
|
+
|
|
3671
|
+
// src/markdown-workspace/focus-writing.ts
|
|
3672
|
+
function topLevelBlockOf(editorRoot, node) {
|
|
3673
|
+
let current = node;
|
|
3674
|
+
while (current && current.parentNode !== editorRoot) {
|
|
3675
|
+
current = current.parentNode;
|
|
3676
|
+
}
|
|
3677
|
+
return current instanceof Element ? current : null;
|
|
3678
|
+
}
|
|
3679
|
+
function typewriterDelta(caretTop, caretHeight, hostTop, hostHeight, band = 0.22) {
|
|
3680
|
+
if (hostHeight <= 0) return 0;
|
|
3681
|
+
const center = hostTop + hostHeight / 2;
|
|
3682
|
+
const caretMid = caretTop + caretHeight / 2;
|
|
3683
|
+
const tolerance = hostHeight * band / 2;
|
|
3684
|
+
const off = caretMid - center;
|
|
3685
|
+
return Math.abs(off) <= tolerance ? 0 : off;
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
// src/markdown-workspace/markdown-workspace.tsx
|
|
3689
|
+
import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3690
|
+
var MODES = [
|
|
3691
|
+
{ value: "source", label: "Source", icon: SquareCode },
|
|
3692
|
+
{ value: "split", label: "Split", icon: Columns2 },
|
|
3693
|
+
{ value: "wysiwyg", label: "Preview-edit", icon: Eye }
|
|
3694
|
+
];
|
|
3695
|
+
var MarkdownWorkspace = forwardRef9(
|
|
3696
|
+
function MarkdownWorkspace2({
|
|
3697
|
+
value,
|
|
3698
|
+
defaultValue,
|
|
3699
|
+
onChange,
|
|
3700
|
+
mode,
|
|
3701
|
+
defaultMode = "split",
|
|
3702
|
+
onModeChange,
|
|
3703
|
+
defaultFocusWriting = false,
|
|
3704
|
+
focusWriting,
|
|
3705
|
+
modeSwitch = true,
|
|
3706
|
+
toolbarActions,
|
|
3707
|
+
slashMenu = true,
|
|
3708
|
+
insertCommands,
|
|
3709
|
+
calc,
|
|
3710
|
+
completions,
|
|
3711
|
+
onEmbedAsset,
|
|
3712
|
+
className,
|
|
3713
|
+
...props
|
|
3714
|
+
}, ref) {
|
|
3715
|
+
const slashCommandList = typeof slashMenu === "object" && slashMenu.commands ? slashMenu.commands : BRAND_SLASH_COMMANDS;
|
|
3716
|
+
const toolbarInsertCommands = insertCommands ?? slashCommandList;
|
|
3717
|
+
const isControlled = value !== void 0;
|
|
3718
|
+
const [internalValue, setInternalValue] = useState5(value ?? defaultValue ?? "");
|
|
3719
|
+
const markdown = isControlled ? value : internalValue;
|
|
3720
|
+
const [internalMode, setInternalMode] = useState5(defaultMode);
|
|
3721
|
+
const activeMode = mode ?? internalMode;
|
|
3722
|
+
const [monaco5, setMonaco] = useState5(null);
|
|
3723
|
+
const [selectionListeners] = useState5(() => /* @__PURE__ */ new Set());
|
|
3724
|
+
const calcRef = useRef5(calc);
|
|
3725
|
+
calcRef.current = calc;
|
|
3726
|
+
const calcEnabled = calc != null;
|
|
3727
|
+
useEffect5(() => {
|
|
3728
|
+
if (!monaco5 || !calcEnabled) return;
|
|
3729
|
+
monaco5.updateOptions({
|
|
3730
|
+
quickSuggestions: { other: true, comments: false, strings: false }
|
|
3731
|
+
});
|
|
3732
|
+
return attachCalcMonaco(monaco5, () => calcRef.current);
|
|
3733
|
+
}, [monaco5, calcEnabled]);
|
|
3734
|
+
const completionsRef = useRef5(completions);
|
|
3735
|
+
completionsRef.current = completions;
|
|
3736
|
+
const completionsEnabled = completions != null;
|
|
3737
|
+
useEffect5(() => {
|
|
3738
|
+
if (!monaco5 || !completionsEnabled) return;
|
|
3739
|
+
return attachCompletionsMonaco(monaco5, () => completionsRef.current);
|
|
3740
|
+
}, [monaco5, completionsEnabled]);
|
|
3741
|
+
useEffect5(() => {
|
|
3742
|
+
if (activeMode === "wysiwyg") setMonaco(null);
|
|
3743
|
+
}, [activeMode]);
|
|
3744
|
+
const slashEnabled = slashMenu !== false;
|
|
3745
|
+
const shortcut = typeof slashMenu === "object" && "shortcut" in slashMenu ? slashMenu.shortcut : DEFAULT_SLASH_SHORTCUT;
|
|
3746
|
+
const [sourceSlashOpen, setSourceSlashOpen] = useState5(false);
|
|
3747
|
+
const [typedTrigger, setTypedTrigger] = useState5(null);
|
|
3748
|
+
const handleSourceSlashOpenChange = (next) => {
|
|
3749
|
+
setSourceSlashOpen(next);
|
|
3750
|
+
if (!next) setTypedTrigger(null);
|
|
3751
|
+
};
|
|
3752
|
+
useEffect5(() => {
|
|
3753
|
+
if (!monaco5 || !slashEnabled) return;
|
|
3754
|
+
const sub = monaco5.onDidChangeModelContent((e) => {
|
|
3755
|
+
if (sourceSlashOpen || e.changes.length !== 1) return;
|
|
3756
|
+
const change = e.changes[0];
|
|
3757
|
+
if (!change || change.text !== "/") return;
|
|
3758
|
+
const model = monaco5.getModel();
|
|
3759
|
+
if (!model) return;
|
|
3760
|
+
const line = change.range.startLineNumber;
|
|
3761
|
+
const range = slashTriggerRange(line, model.getLineContent(line), change.range.startColumn);
|
|
3762
|
+
if (!range) return;
|
|
3763
|
+
setTypedTrigger(range);
|
|
3764
|
+
setSourceSlashOpen(true);
|
|
3765
|
+
});
|
|
3766
|
+
return () => sub.dispose();
|
|
3767
|
+
}, [monaco5, slashEnabled, sourceSlashOpen]);
|
|
3768
|
+
const sourceCommands = useMemo6(
|
|
3769
|
+
() => slashCommandList.filter((c) => c.snippet != null || typeof c.runInSource === "function"),
|
|
3770
|
+
[slashCommandList]
|
|
3771
|
+
);
|
|
3772
|
+
const sourceActions = useMemo6(
|
|
3773
|
+
() => slashEnabled && shortcut ? [
|
|
3774
|
+
{
|
|
3775
|
+
id: "brand.openSlashMenu",
|
|
3776
|
+
label: "Insert block\u2026",
|
|
3777
|
+
keybindings: [parseShortcut(shortcut)],
|
|
3778
|
+
// Hotkey open inserts at the caret (no typed `/` to replace).
|
|
3779
|
+
run: () => {
|
|
3780
|
+
setTypedTrigger(null);
|
|
3781
|
+
setSourceSlashOpen(true);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
] : [],
|
|
3785
|
+
[slashEnabled, shortcut]
|
|
3786
|
+
);
|
|
3787
|
+
const previewPaneRef = useRef5(null);
|
|
3788
|
+
const scrollLock = useRef5(null);
|
|
3789
|
+
const lockTimer = useRef5(null);
|
|
3790
|
+
const fmOffset = useMemo6(() => {
|
|
3791
|
+
try {
|
|
3792
|
+
const body = parseFrontmatter(markdown).content;
|
|
3793
|
+
return markdown.split("\n").length - body.split("\n").length;
|
|
3794
|
+
} catch {
|
|
3795
|
+
return 0;
|
|
3796
|
+
}
|
|
3797
|
+
}, [markdown]);
|
|
3798
|
+
const lock = (owner) => {
|
|
3799
|
+
scrollLock.current = owner;
|
|
3800
|
+
if (lockTimer.current) clearTimeout(lockTimer.current);
|
|
3801
|
+
lockTimer.current = setTimeout(() => {
|
|
3802
|
+
scrollLock.current = null;
|
|
3803
|
+
}, 150);
|
|
3804
|
+
};
|
|
3805
|
+
useEffect5(() => {
|
|
3806
|
+
if (!monaco5 || activeMode !== "split") return;
|
|
3807
|
+
const disposable = monaco5.onDidScrollChange(() => {
|
|
3808
|
+
if (scrollLock.current === "preview") return;
|
|
3809
|
+
const range = monaco5.getVisibleRanges()[0];
|
|
3810
|
+
const host = previewPaneRef.current;
|
|
3811
|
+
if (!range || !host) return;
|
|
3812
|
+
const line = range.startLineNumber - fmOffset;
|
|
3813
|
+
let target = null;
|
|
3814
|
+
for (const el of host.querySelectorAll("[data-sourcepos]")) {
|
|
3815
|
+
const end = Number(el.dataset.sourcepos?.split(":")[1]);
|
|
3816
|
+
if (end >= line) {
|
|
3817
|
+
target = el;
|
|
3818
|
+
break;
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
if (!target) return;
|
|
3822
|
+
lock("editor");
|
|
3823
|
+
host.scrollTop = target.getBoundingClientRect().top - host.getBoundingClientRect().top + host.scrollTop - 12;
|
|
3824
|
+
});
|
|
3825
|
+
return () => disposable.dispose();
|
|
3826
|
+
}, [monaco5, activeMode, fmOffset]);
|
|
3827
|
+
const onPreviewScroll = () => {
|
|
3828
|
+
if (scrollLock.current === "editor" || !monaco5 || activeMode !== "split") return;
|
|
3829
|
+
const host = previewPaneRef.current;
|
|
3830
|
+
if (!host) return;
|
|
3831
|
+
const hostTop = host.getBoundingClientRect().top;
|
|
3832
|
+
for (const el of host.querySelectorAll("[data-sourcepos]")) {
|
|
3833
|
+
if (el.getBoundingClientRect().bottom >= hostTop) {
|
|
3834
|
+
const start = Number(el.dataset.sourcepos?.split(":")[0]);
|
|
3835
|
+
if (!Number.isNaN(start)) {
|
|
3836
|
+
lock("preview");
|
|
3837
|
+
monaco5.setScrollTop(monaco5.getTopForLineNumber(Math.max(1, start + fmOffset)));
|
|
3838
|
+
}
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
};
|
|
3843
|
+
const setMarkdown = (next) => {
|
|
3844
|
+
if (!isControlled) setInternalValue(next);
|
|
3845
|
+
onChange?.(next);
|
|
3846
|
+
};
|
|
3847
|
+
const wysiwygRef = useRef5(null);
|
|
3848
|
+
const wysiwygBase = useRef5(null);
|
|
3849
|
+
useEffect5(() => {
|
|
3850
|
+
if (activeMode !== "wysiwyg") {
|
|
3851
|
+
wysiwygBase.current = null;
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
const original = markdown;
|
|
3855
|
+
wysiwygBase.current = { original, baseline: null };
|
|
3856
|
+
const poll = setInterval(() => {
|
|
3857
|
+
const base = wysiwygBase.current;
|
|
3858
|
+
if (!base || base.baseline !== null) {
|
|
3859
|
+
clearInterval(poll);
|
|
3860
|
+
return;
|
|
3861
|
+
}
|
|
3862
|
+
const s = wysiwygRef.current?.serialized();
|
|
3863
|
+
if (s != null) {
|
|
3864
|
+
base.baseline = s;
|
|
3865
|
+
clearInterval(poll);
|
|
3866
|
+
}
|
|
3867
|
+
}, 50);
|
|
3868
|
+
const stop = setTimeout(() => clearInterval(poll), 5e3);
|
|
3869
|
+
return () => {
|
|
3870
|
+
clearInterval(poll);
|
|
3871
|
+
clearTimeout(stop);
|
|
3872
|
+
};
|
|
3873
|
+
}, [activeMode]);
|
|
3874
|
+
const onWysiwygChange = (emitted) => {
|
|
3875
|
+
const base = wysiwygBase.current;
|
|
3876
|
+
const next = base && base.baseline !== null ? mergeNormalizedEdit(base.original, base.baseline, emitted) : emitted;
|
|
3877
|
+
setMarkdown(next);
|
|
3878
|
+
};
|
|
3879
|
+
const focusWritingEnabled = focusWriting !== false;
|
|
3880
|
+
const [focusWritingOn, setFocusWritingOn] = useState5(
|
|
3881
|
+
focusWritingEnabled ? defaultFocusWriting : false
|
|
3882
|
+
);
|
|
3883
|
+
const wysiwygPaneRef = useRef5(null);
|
|
3884
|
+
const lastInputAt = useRef5(0);
|
|
3885
|
+
useEffect5(() => {
|
|
3886
|
+
if (!(focusWritingEnabled && focusWritingOn && activeMode === "wysiwyg")) return;
|
|
3887
|
+
const pane = wysiwygPaneRef.current;
|
|
3888
|
+
if (!pane) return;
|
|
3889
|
+
let active = null;
|
|
3890
|
+
const onSelectionChange = () => {
|
|
3891
|
+
const root = pane.querySelector(".ProseMirror");
|
|
3892
|
+
if (!root) return;
|
|
3893
|
+
const sel = document.getSelection();
|
|
3894
|
+
const node = sel?.anchorNode ?? null;
|
|
3895
|
+
if (!node || !root.contains(node)) return;
|
|
3896
|
+
const block = topLevelBlockOf(root, node);
|
|
3897
|
+
if (block !== active) {
|
|
3898
|
+
active?.classList.remove("wb-fw-active");
|
|
3899
|
+
block?.classList.add("wb-fw-active");
|
|
3900
|
+
active = block;
|
|
3901
|
+
}
|
|
3902
|
+
if (Date.now() - lastInputAt.current < 200 && sel && sel.rangeCount > 0) {
|
|
3903
|
+
const range = sel.getRangeAt(0).getBoundingClientRect();
|
|
3904
|
+
const caret = range.height > 0 ? range : active?.getBoundingClientRect() ?? range;
|
|
3905
|
+
const host = pane.getBoundingClientRect();
|
|
3906
|
+
const delta = typewriterDelta(caret.top, caret.height, host.top, host.height);
|
|
3907
|
+
if (delta !== 0) pane.scrollTop += delta;
|
|
3908
|
+
}
|
|
3909
|
+
};
|
|
3910
|
+
const onInput = () => {
|
|
3911
|
+
lastInputAt.current = Date.now();
|
|
3912
|
+
};
|
|
3913
|
+
document.addEventListener("selectionchange", onSelectionChange);
|
|
3914
|
+
pane.addEventListener("input", onInput, true);
|
|
3915
|
+
onSelectionChange();
|
|
3916
|
+
return () => {
|
|
3917
|
+
document.removeEventListener("selectionchange", onSelectionChange);
|
|
3918
|
+
pane.removeEventListener("input", onInput, true);
|
|
3919
|
+
active?.classList.remove("wb-fw-active");
|
|
3920
|
+
};
|
|
3921
|
+
}, [focusWritingEnabled, focusWritingOn, activeMode]);
|
|
3922
|
+
const rootRef = useRef5(null);
|
|
3923
|
+
useImperativeHandle(
|
|
3924
|
+
ref,
|
|
3925
|
+
() => {
|
|
3926
|
+
const revealLine = (n, opts) => {
|
|
3927
|
+
if (monaco5) {
|
|
3928
|
+
const max = monaco5.getModel()?.getLineCount() ?? 0;
|
|
3929
|
+
if (n < 1 || n > max) return;
|
|
3930
|
+
if (opts?.center === false) {
|
|
3931
|
+
monaco5.revealLine(n);
|
|
3932
|
+
} else {
|
|
3933
|
+
monaco5.revealLineInCenter(n);
|
|
3934
|
+
}
|
|
3935
|
+
} else {
|
|
3936
|
+
const items = parseMarkdownOutline(markdown);
|
|
3937
|
+
const preceding = items.filter((item) => item.line + fmOffset <= n).at(-1);
|
|
3938
|
+
if (!preceding) return;
|
|
3939
|
+
wysiwygRef.current?.scrollToHeading(preceding.id);
|
|
3940
|
+
}
|
|
3941
|
+
};
|
|
3942
|
+
const scrollToHeading = (slug) => {
|
|
3943
|
+
if (monaco5) {
|
|
3944
|
+
const item = parseMarkdownOutline(markdown).find((i) => i.id === slug);
|
|
3945
|
+
if (!item) return;
|
|
3946
|
+
revealLine(item.line + fmOffset, { center: true });
|
|
3947
|
+
} else {
|
|
3948
|
+
wysiwygRef.current?.scrollToHeading(slug);
|
|
3949
|
+
}
|
|
3950
|
+
};
|
|
3951
|
+
const getAccess = () => {
|
|
3952
|
+
if (monaco5) return monacoContentAccess(monaco5);
|
|
3953
|
+
if (wysiwygRef.current) return wysiwygRef.current;
|
|
3954
|
+
return null;
|
|
3955
|
+
};
|
|
3956
|
+
return {
|
|
3957
|
+
revealLine,
|
|
3958
|
+
scrollToHeading,
|
|
3959
|
+
getEditor: () => monaco5,
|
|
3960
|
+
getElement: () => rootRef.current,
|
|
3961
|
+
// EditorContentAccess — read/write delegate to the active engine at call
|
|
3962
|
+
// time (the AI acts after mount, so a call-time snapshot is correct here).
|
|
3963
|
+
getText: () => getAccess()?.getText() ?? "",
|
|
3964
|
+
getSelection: () => getAccess()?.getSelection() ?? { text: "", empty: true },
|
|
3965
|
+
replaceSelection: (text) => getAccess()?.replaceSelection(text),
|
|
3966
|
+
insertAtCursor: (text) => getAccess()?.insertAtCursor(text),
|
|
3967
|
+
focus: () => getAccess()?.focus(),
|
|
3968
|
+
// onSelectionChange uses the STABLE listener set (not getAccess()) so a
|
|
3969
|
+
// subscribe-in-mount-effect survives the editor's async mount + mode
|
|
3970
|
+
// switches; the binding effect below forwards the active engine's events.
|
|
3971
|
+
onSelectionChange: (listener) => {
|
|
3972
|
+
selectionListeners.add(listener);
|
|
3973
|
+
return () => selectionListeners.delete(listener);
|
|
3974
|
+
}
|
|
3975
|
+
};
|
|
3976
|
+
},
|
|
3977
|
+
// Re-create when the things the methods close over change.
|
|
3978
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3979
|
+
[monaco5, markdown, fmOffset, activeMode, selectionListeners]
|
|
3980
|
+
);
|
|
3981
|
+
useEffect5(() => {
|
|
3982
|
+
let unsub;
|
|
3983
|
+
if (monaco5) {
|
|
3984
|
+
unsub = monacoContentAccess(monaco5).onSelectionChange(
|
|
3985
|
+
(sel) => selectionListeners.forEach((l) => l(sel))
|
|
3986
|
+
);
|
|
3987
|
+
} else if (activeMode === "wysiwyg" && wysiwygRef.current) {
|
|
3988
|
+
unsub = wysiwygRef.current.onSelectionChange(
|
|
3989
|
+
(sel) => selectionListeners.forEach((l) => l(sel))
|
|
3990
|
+
);
|
|
3991
|
+
}
|
|
3992
|
+
return () => unsub?.();
|
|
3993
|
+
}, [monaco5, activeMode, selectionListeners]);
|
|
3994
|
+
const setMode = (next) => {
|
|
3995
|
+
if (next !== "source" && next !== "split" && next !== "wysiwyg") return;
|
|
3996
|
+
if (!mode) setInternalMode(next);
|
|
3997
|
+
onModeChange?.(next);
|
|
3998
|
+
};
|
|
3999
|
+
const modeToggle = /* @__PURE__ */ jsx14(TooltipProvider2, { delayDuration: 300, children: /* @__PURE__ */ jsx14(
|
|
4000
|
+
ToggleGroup,
|
|
4001
|
+
{
|
|
4002
|
+
type: "single",
|
|
4003
|
+
value: activeMode,
|
|
4004
|
+
onValueChange: setMode,
|
|
4005
|
+
variant: "segmented",
|
|
4006
|
+
size: "sm",
|
|
4007
|
+
className: "rounded-md p-0.5",
|
|
4008
|
+
children: MODES.map(({ value: m, label, icon: Icon }) => /* @__PURE__ */ jsxs12(Tooltip2, { children: [
|
|
4009
|
+
/* @__PURE__ */ jsx14(TooltipTrigger2, { asChild: true, children: /* @__PURE__ */ jsx14(
|
|
4010
|
+
ToggleGroupItem,
|
|
4011
|
+
{
|
|
4012
|
+
value: m,
|
|
4013
|
+
"aria-label": label,
|
|
4014
|
+
className: "h-6 min-w-7 rounded-[5px] px-2",
|
|
4015
|
+
children: /* @__PURE__ */ jsx14(Icon, { className: "size-4" })
|
|
4016
|
+
}
|
|
4017
|
+
) }),
|
|
4018
|
+
/* @__PURE__ */ jsx14(TooltipContent2, { children: label })
|
|
4019
|
+
] }, m))
|
|
4020
|
+
}
|
|
4021
|
+
) });
|
|
4022
|
+
const trailing = /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
4023
|
+
modeSwitch ? modeToggle : null,
|
|
4024
|
+
toolbarActions
|
|
4025
|
+
] });
|
|
4026
|
+
const sourcePane = /* @__PURE__ */ jsx14(
|
|
4027
|
+
CodeEditor,
|
|
4028
|
+
{
|
|
4029
|
+
language: "markdown",
|
|
4030
|
+
value: markdown,
|
|
4031
|
+
onChange: setMarkdown,
|
|
4032
|
+
actions: sourceActions,
|
|
4033
|
+
onMount: (editor2) => {
|
|
4034
|
+
editor2.updateOptions({ wordWrap: "on" });
|
|
4035
|
+
setMonaco(editor2);
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
);
|
|
4039
|
+
return /* @__PURE__ */ jsxs12(
|
|
4040
|
+
"div",
|
|
4041
|
+
{
|
|
4042
|
+
ref: rootRef,
|
|
4043
|
+
"data-testid": "markdown-workspace",
|
|
4044
|
+
className: cn13("flex h-full min-h-0 flex-col overflow-hidden", className),
|
|
4045
|
+
...props,
|
|
4046
|
+
children: [
|
|
4047
|
+
activeMode === "wysiwyg" ? /* @__PURE__ */ jsxs12("div", { className: "flex h-10 shrink-0 items-center justify-end gap-2 border-b border-border bg-surface px-2", children: [
|
|
4048
|
+
focusWritingEnabled ? /* @__PURE__ */ jsx14(TooltipProvider2, { delayDuration: 300, children: /* @__PURE__ */ jsxs12(Tooltip2, { children: [
|
|
4049
|
+
/* @__PURE__ */ jsx14(TooltipTrigger2, { asChild: true, children: /* @__PURE__ */ jsxs12(
|
|
4050
|
+
Toggle,
|
|
4051
|
+
{
|
|
4052
|
+
size: "sm",
|
|
4053
|
+
pressed: focusWritingOn,
|
|
4054
|
+
onPressedChange: setFocusWritingOn,
|
|
4055
|
+
"aria-label": "Focus writing",
|
|
4056
|
+
className: "h-6 gap-1.5 px-2 text-caption",
|
|
4057
|
+
children: [
|
|
4058
|
+
/* @__PURE__ */ jsx14(Focus, { className: "size-3.5", "aria-hidden": "true" }),
|
|
4059
|
+
" Focus"
|
|
4060
|
+
]
|
|
4061
|
+
}
|
|
4062
|
+
) }),
|
|
4063
|
+
/* @__PURE__ */ jsx14(TooltipContent2, { children: "Typewriter scrolling \xB7 inactive paragraphs dim" })
|
|
4064
|
+
] }) }) : null,
|
|
4065
|
+
trailing
|
|
4066
|
+
] }) : /* @__PURE__ */ jsx14(
|
|
4067
|
+
MarkdownToolbar,
|
|
4068
|
+
{
|
|
4069
|
+
editor: monaco5,
|
|
4070
|
+
actions: trailing,
|
|
4071
|
+
insertCommands: toolbarInsertCommands
|
|
4072
|
+
}
|
|
4073
|
+
),
|
|
4074
|
+
/* @__PURE__ */ jsxs12("div", { className: "min-h-0 flex-1", children: [
|
|
4075
|
+
activeMode === "source" ? sourcePane : null,
|
|
4076
|
+
activeMode === "wysiwyg" ? /* @__PURE__ */ jsx14(
|
|
4077
|
+
"div",
|
|
4078
|
+
{
|
|
4079
|
+
ref: wysiwygPaneRef,
|
|
4080
|
+
"data-focus-writing": focusWritingEnabled && focusWritingOn ? "" : void 0,
|
|
4081
|
+
className: "h-full overflow-auto p-4",
|
|
4082
|
+
children: /* @__PURE__ */ jsx14(
|
|
4083
|
+
MarkdownEditor,
|
|
4084
|
+
{
|
|
4085
|
+
ref: wysiwygRef,
|
|
4086
|
+
defaultValue: markdown,
|
|
4087
|
+
onChange: onWysiwygChange,
|
|
4088
|
+
slashMenu,
|
|
4089
|
+
calc,
|
|
4090
|
+
completions,
|
|
4091
|
+
onEmbedAsset,
|
|
4092
|
+
className: "border-0"
|
|
4093
|
+
}
|
|
4094
|
+
)
|
|
4095
|
+
}
|
|
4096
|
+
) : null,
|
|
4097
|
+
activeMode === "split" ? /* @__PURE__ */ jsxs12(ResizablePanelGroup, { direction: "horizontal", children: [
|
|
4098
|
+
/* @__PURE__ */ jsx14(ResizablePanel, { defaultSize: 50, minSize: 25, children: sourcePane }),
|
|
4099
|
+
/* @__PURE__ */ jsx14(ResizableHandle, { withHandle: true }),
|
|
4100
|
+
/* @__PURE__ */ jsx14(ResizablePanel, { defaultSize: 50, minSize: 25, children: /* @__PURE__ */ jsx14(
|
|
4101
|
+
"div",
|
|
4102
|
+
{
|
|
4103
|
+
ref: previewPaneRef,
|
|
4104
|
+
onScroll: onPreviewScroll,
|
|
4105
|
+
className: "h-full overflow-auto p-5",
|
|
4106
|
+
children: /* @__PURE__ */ jsx14(MarkdownPreview, { children: markdown })
|
|
4107
|
+
}
|
|
4108
|
+
) })
|
|
4109
|
+
] }) : null
|
|
4110
|
+
] }),
|
|
4111
|
+
monaco5 && (activeMode === "source" || activeMode === "split") && slashEnabled ? /* @__PURE__ */ jsx14(
|
|
4112
|
+
MonacoSlashMenu,
|
|
4113
|
+
{
|
|
4114
|
+
editor: monaco5,
|
|
4115
|
+
commands: sourceCommands,
|
|
4116
|
+
open: sourceSlashOpen,
|
|
4117
|
+
onOpenChange: handleSourceSlashOpenChange,
|
|
4118
|
+
triggerRange: typedTrigger
|
|
4119
|
+
}
|
|
4120
|
+
) : null
|
|
4121
|
+
]
|
|
4122
|
+
}
|
|
4123
|
+
);
|
|
4124
|
+
}
|
|
4125
|
+
);
|
|
4126
|
+
|
|
4127
|
+
// src/markdown/parse.ts
|
|
4128
|
+
import remarkDirective2 from "remark-directive";
|
|
4129
|
+
import remarkFrontmatter from "remark-frontmatter";
|
|
4130
|
+
import remarkGfm from "remark-gfm";
|
|
4131
|
+
import remarkParse from "remark-parse";
|
|
4132
|
+
import { unified } from "unified";
|
|
4133
|
+
var processor = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter, ["yaml"]).use(remarkDirective2).freeze();
|
|
4134
|
+
function parseMarkdown(md) {
|
|
4135
|
+
return processor.parse(md);
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
// src/mermaid-workspace/mermaid-workspace.tsx
|
|
4139
|
+
import { ResizableHandle as ResizableHandle2, ResizablePanel as ResizablePanel2, ResizablePanelGroup as ResizablePanelGroup2 } from "@elabs-ai/components-ui";
|
|
4140
|
+
import { cn as cn14 } from "@elabs-ai/components-ui/lib/cn";
|
|
4141
|
+
import { forwardRef as forwardRef10, useEffect as useEffect6, useState as useState6 } from "react";
|
|
4142
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4143
|
+
var MermaidWorkspace = forwardRef10(
|
|
4144
|
+
function MermaidWorkspace2({ value, defaultValue, onChange, debounceMs = 350, className, ...props }, ref) {
|
|
4145
|
+
const isControlled = value !== void 0;
|
|
4146
|
+
const [internal, setInternal] = useState6(value ?? defaultValue ?? "");
|
|
4147
|
+
const source = isControlled ? value : internal;
|
|
4148
|
+
const [debounced, setDebounced] = useState6(source);
|
|
4149
|
+
useEffect6(() => {
|
|
4150
|
+
const t = setTimeout(() => setDebounced(source), debounceMs);
|
|
4151
|
+
return () => clearTimeout(t);
|
|
4152
|
+
}, [source, debounceMs]);
|
|
4153
|
+
const setSource = (next) => {
|
|
4154
|
+
if (!isControlled) setInternal(next);
|
|
4155
|
+
onChange?.(next);
|
|
4156
|
+
};
|
|
4157
|
+
return /* @__PURE__ */ jsx15(
|
|
4158
|
+
"div",
|
|
4159
|
+
{
|
|
4160
|
+
ref,
|
|
4161
|
+
"data-testid": "mermaid-workspace",
|
|
4162
|
+
className: cn14("h-full min-h-0 overflow-hidden", className),
|
|
4163
|
+
...props,
|
|
4164
|
+
children: /* @__PURE__ */ jsxs13(ResizablePanelGroup2, { direction: "horizontal", children: [
|
|
4165
|
+
/* @__PURE__ */ jsx15(ResizablePanel2, { defaultSize: 45, minSize: 25, children: /* @__PURE__ */ jsx15(CodeEditor, { language: "plaintext", value: source, onChange: setSource }) }),
|
|
4166
|
+
/* @__PURE__ */ jsx15(ResizableHandle2, { withHandle: true }),
|
|
4167
|
+
/* @__PURE__ */ jsx15(ResizablePanel2, { defaultSize: 55, minSize: 25, children: /* @__PURE__ */ jsx15("div", { className: "h-full overflow-auto p-4", children: /* @__PURE__ */ jsx15(MermaidDiagram, { chart: debounced, label: "Diagram preview" }) }) })
|
|
4168
|
+
] })
|
|
4169
|
+
}
|
|
4170
|
+
);
|
|
4171
|
+
}
|
|
4172
|
+
);
|
|
4173
|
+
|
|
4174
|
+
// src/ai-objects/decision-card.tsx
|
|
4175
|
+
import {
|
|
4176
|
+
Badge,
|
|
4177
|
+
Card as Card2,
|
|
4178
|
+
CardContent as CardContent2,
|
|
4179
|
+
CardHeader as CardHeader2,
|
|
4180
|
+
Separator as Separator5
|
|
4181
|
+
} from "@elabs-ai/components-ui";
|
|
4182
|
+
import { cn as cn15 } from "@elabs-ai/components-ui/lib/cn";
|
|
4183
|
+
import { cva } from "class-variance-authority";
|
|
4184
|
+
import { CheckCircle2, CircleDashed, Clock, RefreshCw } from "lucide-react";
|
|
4185
|
+
import {
|
|
4186
|
+
forwardRef as forwardRef11
|
|
4187
|
+
} from "react";
|
|
4188
|
+
import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4189
|
+
var DECISION_STATUSES = ["accepted", "rejected", "proposed", "superseded"];
|
|
4190
|
+
var STATUS_BADGE_VARIANT = {
|
|
4191
|
+
accepted: "success",
|
|
4192
|
+
rejected: "destructive",
|
|
4193
|
+
proposed: "info",
|
|
4194
|
+
superseded: "secondary"
|
|
4195
|
+
};
|
|
4196
|
+
var STATUS_LABELS = {
|
|
4197
|
+
accepted: "Accepted",
|
|
4198
|
+
rejected: "Rejected",
|
|
4199
|
+
proposed: "Proposed",
|
|
4200
|
+
superseded: "Superseded"
|
|
4201
|
+
};
|
|
4202
|
+
var STATUS_ICONS = {
|
|
4203
|
+
accepted: CheckCircle2,
|
|
4204
|
+
rejected: CircleDashed,
|
|
4205
|
+
proposed: Clock,
|
|
4206
|
+
superseded: RefreshCw
|
|
4207
|
+
};
|
|
4208
|
+
function isDecisionStatus(s) {
|
|
4209
|
+
return DECISION_STATUSES.includes(s);
|
|
4210
|
+
}
|
|
4211
|
+
var decisionCardVariants = cva("border-s-4", {
|
|
4212
|
+
variants: {
|
|
4213
|
+
status: {
|
|
4214
|
+
accepted: "border-s-success",
|
|
4215
|
+
rejected: "border-s-destructive",
|
|
4216
|
+
proposed: "border-s-info",
|
|
4217
|
+
superseded: "border-s-border"
|
|
4218
|
+
}
|
|
4219
|
+
},
|
|
4220
|
+
defaultVariants: { status: "proposed" }
|
|
4221
|
+
});
|
|
4222
|
+
var DecisionCard = forwardRef11(function DecisionCard2({ status: rawStatus, date, alternatives, children, className, ...props }, ref) {
|
|
4223
|
+
const status = isDecisionStatus(rawStatus ?? "") ? rawStatus : "proposed";
|
|
4224
|
+
const badgeVariant = STATUS_BADGE_VARIANT[status];
|
|
4225
|
+
const label = STATUS_LABELS[status];
|
|
4226
|
+
const Icon = STATUS_ICONS[status];
|
|
4227
|
+
const altItems = alternatives ? alternatives.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
4228
|
+
return /* @__PURE__ */ jsx16(
|
|
4229
|
+
"section",
|
|
4230
|
+
{
|
|
4231
|
+
ref,
|
|
4232
|
+
"aria-label": `Decision: ${label}`,
|
|
4233
|
+
className: cn15("not-prose", className),
|
|
4234
|
+
...props,
|
|
4235
|
+
children: /* @__PURE__ */ jsxs14(Card2, { className: cn15(decisionCardVariants({ status })), children: [
|
|
4236
|
+
/* @__PURE__ */ jsx16(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs14("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
4237
|
+
/* @__PURE__ */ jsxs14(Badge, { variant: badgeVariant, className: "gap-1.5", children: [
|
|
4238
|
+
/* @__PURE__ */ jsx16(Icon, { className: "size-3", "aria-hidden": "true" }),
|
|
4239
|
+
label
|
|
4240
|
+
] }),
|
|
4241
|
+
date ? /* @__PURE__ */ jsx16("time", { dateTime: date, className: "text-meta text-muted-foreground tabular-nums", children: date }) : null
|
|
4242
|
+
] }) }),
|
|
4243
|
+
children ? /* @__PURE__ */ jsx16(CardContent2, { className: "text-body text-foreground", children }) : null,
|
|
4244
|
+
altItems.length > 0 ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
4245
|
+
/* @__PURE__ */ jsx16(Separator5, {}),
|
|
4246
|
+
/* @__PURE__ */ jsxs14("div", { className: "px-6 py-4", children: [
|
|
4247
|
+
/* @__PURE__ */ jsx16("p", { className: "mb-2 text-meta font-medium text-muted-foreground", children: "Alternatives considered" }),
|
|
4248
|
+
/* @__PURE__ */ jsx16("ul", { className: "flex flex-wrap gap-1.5", "aria-label": "Alternatives considered", children: altItems.map((alt) => /* @__PURE__ */ jsx16("li", { children: /* @__PURE__ */ jsx16(Badge, { variant: "outline", className: "text-meta", children: alt }) }, alt)) })
|
|
4249
|
+
] })
|
|
4250
|
+
] }) : null
|
|
4251
|
+
] })
|
|
4252
|
+
}
|
|
4253
|
+
);
|
|
4254
|
+
});
|
|
4255
|
+
|
|
4256
|
+
// src/ai-objects/entity.tsx
|
|
4257
|
+
import { Card as Card3, CardContent as CardContent3, CardHeader as CardHeader3, CardTitle as CardTitle2 } from "@elabs-ai/components-ui";
|
|
4258
|
+
import { cn as cn16 } from "@elabs-ai/components-ui/lib/cn";
|
|
4259
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
4260
|
+
import { Box, Building2, Lightbulb, MapPin, Tag, User } from "lucide-react";
|
|
4261
|
+
import {
|
|
4262
|
+
forwardRef as forwardRef12
|
|
4263
|
+
} from "react";
|
|
4264
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4265
|
+
var ENTITY_KINDS = ["org", "person", "place", "product", "concept"];
|
|
4266
|
+
var KIND_META = {
|
|
4267
|
+
org: { Icon: Building2, label: "Organisation" },
|
|
4268
|
+
person: { Icon: User, label: "Person" },
|
|
4269
|
+
place: { Icon: MapPin, label: "Place" },
|
|
4270
|
+
product: { Icon: Box, label: "Product" },
|
|
4271
|
+
concept: { Icon: Lightbulb, label: "Concept" }
|
|
4272
|
+
};
|
|
4273
|
+
var DEFAULT_KIND_META = { Icon: Tag, label: "Entity" };
|
|
4274
|
+
function kindMeta(kind) {
|
|
4275
|
+
return KIND_META[kind ?? ""] ?? DEFAULT_KIND_META;
|
|
4276
|
+
}
|
|
4277
|
+
var entityChipVariants = cva2(
|
|
4278
|
+
// inline-flex + align-middle keeps the chip in the text baseline;
|
|
4279
|
+
// no `block` wrapper so it is valid inside <p>.
|
|
4280
|
+
"inline-flex items-center gap-1 rounded-sm border px-1.5 py-0.5 text-meta font-medium align-middle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
4281
|
+
{
|
|
4282
|
+
variants: {
|
|
4283
|
+
kind: {
|
|
4284
|
+
org: "border-border bg-secondary/60 text-secondary-foreground",
|
|
4285
|
+
// #399 — same reasoning as `concept` below: a 10% WASH is not a plate
|
|
4286
|
+
// and not a mark, so the LABEL takes the on-surface `-text` rung. This
|
|
4287
|
+
// is the row that had no `-text` rung to reach for until #399 minted it.
|
|
4288
|
+
person: "border-primary/30 bg-primary/10 text-primary-text",
|
|
4289
|
+
place: "border-info/30 bg-info/10 text-info-text",
|
|
4290
|
+
product: "border-success/30 bg-success/10 text-success-text",
|
|
4291
|
+
// `-text`, not `-foreground`: the chip is a 10% WASH on the page surface,
|
|
4292
|
+
// not a solid `--warning` plate, so it needs the on-surface rung its
|
|
4293
|
+
// place/product siblings use (#381 flipped `--warning-foreground` to
|
|
4294
|
+
// light ink for the now-deep fill).
|
|
4295
|
+
concept: "border-warning/30 bg-warning/10 text-warning-text",
|
|
4296
|
+
default: "border-border text-foreground"
|
|
4297
|
+
}
|
|
4298
|
+
},
|
|
4299
|
+
defaultVariants: { kind: "default" }
|
|
4300
|
+
}
|
|
4301
|
+
);
|
|
4302
|
+
function chipKind(kind) {
|
|
4303
|
+
if (!kind) return "default";
|
|
4304
|
+
const known = [
|
|
4305
|
+
"org",
|
|
4306
|
+
"person",
|
|
4307
|
+
"place",
|
|
4308
|
+
"product",
|
|
4309
|
+
"concept",
|
|
4310
|
+
"default"
|
|
4311
|
+
];
|
|
4312
|
+
return known.includes(kind) ? kind : "default";
|
|
4313
|
+
}
|
|
4314
|
+
var EntityChip = forwardRef12(function EntityChip2({ kind: rawKind, children, className, ...props }, ref) {
|
|
4315
|
+
const { Icon, label } = kindMeta(rawKind);
|
|
4316
|
+
const resolvedKind = chipKind(rawKind);
|
|
4317
|
+
return /* @__PURE__ */ jsxs15(
|
|
4318
|
+
"span",
|
|
4319
|
+
{
|
|
4320
|
+
ref,
|
|
4321
|
+
role: "mark",
|
|
4322
|
+
"aria-label": children ? `${String(children)} (${label})` : label,
|
|
4323
|
+
className: cn16(entityChipVariants({ kind: resolvedKind }), className),
|
|
4324
|
+
...props,
|
|
4325
|
+
children: [
|
|
4326
|
+
/* @__PURE__ */ jsx17(Icon, { className: "size-3 shrink-0", "aria-hidden": "true" }),
|
|
4327
|
+
/* @__PURE__ */ jsx17("span", { children })
|
|
4328
|
+
]
|
|
4329
|
+
}
|
|
4330
|
+
);
|
|
4331
|
+
});
|
|
4332
|
+
var EntityCard = forwardRef12(function EntityCard2({ kind: rawKind, name, children, className, ...props }, ref) {
|
|
4333
|
+
const { Icon, label } = kindMeta(rawKind);
|
|
4334
|
+
return /* @__PURE__ */ jsx17(
|
|
4335
|
+
"section",
|
|
4336
|
+
{
|
|
4337
|
+
ref,
|
|
4338
|
+
"aria-label": name ? `${label}: ${name}` : label,
|
|
4339
|
+
className: cn16("not-prose", className),
|
|
4340
|
+
...props,
|
|
4341
|
+
children: /* @__PURE__ */ jsxs15(Card3, { children: [
|
|
4342
|
+
/* @__PURE__ */ jsx17(CardHeader3, { className: "pb-3", children: /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", children: [
|
|
4343
|
+
/* @__PURE__ */ jsx17(
|
|
4344
|
+
"span",
|
|
4345
|
+
{
|
|
4346
|
+
className: "flex size-7 shrink-0 items-center justify-center rounded-md bg-muted",
|
|
4347
|
+
"aria-hidden": "true",
|
|
4348
|
+
children: /* @__PURE__ */ jsx17(Icon, { className: "size-4 text-muted-foreground" })
|
|
4349
|
+
}
|
|
4350
|
+
),
|
|
4351
|
+
/* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
|
|
4352
|
+
name ? /* @__PURE__ */ jsx17(CardTitle2, { className: "truncate", children: name }) : null,
|
|
4353
|
+
/* @__PURE__ */ jsx17("p", { className: "text-meta text-muted-foreground", children: label })
|
|
4354
|
+
] })
|
|
4355
|
+
] }) }),
|
|
4356
|
+
children ? /* @__PURE__ */ jsx17(CardContent3, { className: "text-body text-foreground", children }) : null
|
|
4357
|
+
] })
|
|
4358
|
+
}
|
|
4359
|
+
);
|
|
4360
|
+
});
|
|
4361
|
+
|
|
4362
|
+
// src/ai-objects/knowledge-card.tsx
|
|
4363
|
+
import { Card as Card4, CardContent as CardContent4, CardFooter } from "@elabs-ai/components-ui";
|
|
4364
|
+
import { cn as cn17 } from "@elabs-ai/components-ui/lib/cn";
|
|
4365
|
+
import { BookOpen, FileText } from "lucide-react";
|
|
4366
|
+
import { forwardRef as forwardRef13 } from "react";
|
|
4367
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
4368
|
+
function SourceRow({ path, resolve }) {
|
|
4369
|
+
const resolved = resolve ? resolve(path) : null;
|
|
4370
|
+
const display = resolved?.title ?? path;
|
|
4371
|
+
if (resolved) {
|
|
4372
|
+
return /* @__PURE__ */ jsxs16(
|
|
4373
|
+
"a",
|
|
4374
|
+
{
|
|
4375
|
+
href: resolved.href,
|
|
4376
|
+
rel: "noopener noreferrer",
|
|
4377
|
+
target: "_blank",
|
|
4378
|
+
className: "flex min-w-0 items-center gap-1.5 text-meta text-primary-text underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
4379
|
+
"aria-label": `Source: ${display}`,
|
|
4380
|
+
children: [
|
|
4381
|
+
/* @__PURE__ */ jsx18(FileText, { className: "size-3 shrink-0", "aria-hidden": "true" }),
|
|
4382
|
+
/* @__PURE__ */ jsx18("span", { className: "truncate", children: display })
|
|
4383
|
+
]
|
|
4384
|
+
}
|
|
4385
|
+
);
|
|
4386
|
+
}
|
|
4387
|
+
return /* @__PURE__ */ jsxs16(
|
|
4388
|
+
"span",
|
|
4389
|
+
{
|
|
4390
|
+
className: "flex min-w-0 items-center gap-1.5 text-meta text-muted-foreground",
|
|
4391
|
+
"aria-label": `Source (unresolved): ${display}`,
|
|
4392
|
+
children: [
|
|
4393
|
+
/* @__PURE__ */ jsx18(FileText, { className: "size-3 shrink-0", "aria-hidden": "true" }),
|
|
4394
|
+
/* @__PURE__ */ jsx18("span", { className: "truncate", children: display })
|
|
4395
|
+
]
|
|
4396
|
+
}
|
|
4397
|
+
);
|
|
4398
|
+
}
|
|
4399
|
+
var KnowledgeCard = forwardRef13(function KnowledgeCard2({ sources, resolve, children, className, ...props }, ref) {
|
|
4400
|
+
const sourcePaths = sources ? sources.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
4401
|
+
return /* @__PURE__ */ jsx18(
|
|
4402
|
+
"section",
|
|
4403
|
+
{
|
|
4404
|
+
ref,
|
|
4405
|
+
"aria-label": "Knowledge fact",
|
|
4406
|
+
className: cn17("not-prose", className),
|
|
4407
|
+
...props,
|
|
4408
|
+
children: /* @__PURE__ */ jsxs16(Card4, { className: "border-s-4 border-s-info", children: [
|
|
4409
|
+
/* @__PURE__ */ jsxs16(CardContent4, { className: "pt-4", children: [
|
|
4410
|
+
/* @__PURE__ */ jsxs16("div", { className: "mb-2 flex items-center gap-1.5", children: [
|
|
4411
|
+
/* @__PURE__ */ jsx18(BookOpen, { className: "size-3.5 shrink-0 text-info-text", "aria-hidden": "true" }),
|
|
4412
|
+
/* @__PURE__ */ jsx18("span", { className: "text-meta font-medium text-info-text", children: "Knowledge" })
|
|
4413
|
+
] }),
|
|
4414
|
+
/* @__PURE__ */ jsx18("div", { className: "text-body text-foreground", children })
|
|
4415
|
+
] }),
|
|
4416
|
+
sourcePaths.length > 0 ? /* @__PURE__ */ jsxs16(CardFooter, { className: "flex-col items-start gap-1 border-t border-border pt-3", children: [
|
|
4417
|
+
/* @__PURE__ */ jsx18("p", { className: "text-meta font-medium text-muted-foreground", children: "Sources" }),
|
|
4418
|
+
/* @__PURE__ */ jsx18("ul", { className: "flex w-full flex-col gap-1", "aria-label": "Sources", children: sourcePaths.map((path) => /* @__PURE__ */ jsx18("li", { className: "min-w-0", children: /* @__PURE__ */ jsx18(SourceRow, { path, resolve }) }, path)) })
|
|
4419
|
+
] }) : null
|
|
4420
|
+
] })
|
|
4421
|
+
}
|
|
4422
|
+
);
|
|
4423
|
+
});
|
|
4424
|
+
|
|
4425
|
+
// src/ai-objects/directives.ts
|
|
4426
|
+
import { createElement } from "react";
|
|
4427
|
+
function decisionDirective() {
|
|
4428
|
+
return {
|
|
4429
|
+
name: "decision",
|
|
4430
|
+
kinds: ["container"],
|
|
4431
|
+
render({ attributes, children }) {
|
|
4432
|
+
return createElement(DecisionCard, {
|
|
4433
|
+
status: attributes.status,
|
|
4434
|
+
date: attributes.date,
|
|
4435
|
+
alternatives: attributes.alternatives,
|
|
4436
|
+
children
|
|
4437
|
+
});
|
|
4438
|
+
}
|
|
4439
|
+
};
|
|
4440
|
+
}
|
|
4441
|
+
function entityDirective() {
|
|
4442
|
+
return {
|
|
4443
|
+
name: "entity",
|
|
4444
|
+
kinds: ["container", "inline"],
|
|
4445
|
+
render({ kind, attributes, children, textValue }) {
|
|
4446
|
+
if (kind === "inline") {
|
|
4447
|
+
const label = textValue ?? (typeof children === "string" ? children : void 0);
|
|
4448
|
+
return createElement(EntityChip, { kind: attributes.kind, children: label ?? children });
|
|
4449
|
+
}
|
|
4450
|
+
return createElement(EntityCard, {
|
|
4451
|
+
kind: attributes.kind,
|
|
4452
|
+
name: attributes.name,
|
|
4453
|
+
children
|
|
4454
|
+
});
|
|
4455
|
+
}
|
|
4456
|
+
};
|
|
4457
|
+
}
|
|
4458
|
+
function knowledgeDirective(options) {
|
|
4459
|
+
return {
|
|
4460
|
+
name: "knowledge",
|
|
4461
|
+
kinds: ["container"],
|
|
4462
|
+
render({ attributes, children }) {
|
|
4463
|
+
return createElement(KnowledgeCard, {
|
|
4464
|
+
sources: attributes.sources,
|
|
4465
|
+
resolve: options?.resolve,
|
|
4466
|
+
children
|
|
4467
|
+
});
|
|
4468
|
+
}
|
|
4469
|
+
};
|
|
4470
|
+
}
|
|
4471
|
+
function aiObjectDirectives(options = {}) {
|
|
4472
|
+
return [
|
|
4473
|
+
decisionDirective(),
|
|
4474
|
+
entityDirective(),
|
|
4475
|
+
knowledgeDirective({ resolve: options.resolveKnowledge })
|
|
4476
|
+
];
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4479
|
+
// src/markdown-iteration/template-dialog.tsx
|
|
4480
|
+
import {
|
|
4481
|
+
Button as Button4,
|
|
4482
|
+
Dialog as Dialog2,
|
|
4483
|
+
DialogContent as DialogContent2,
|
|
4484
|
+
DialogDescription,
|
|
4485
|
+
DialogFooter,
|
|
4486
|
+
DialogHeader,
|
|
4487
|
+
DialogTitle as DialogTitle2
|
|
4488
|
+
} from "@elabs-ai/components-ui";
|
|
4489
|
+
import { useEffect as useEffect7, useState as useState7 } from "react";
|
|
4490
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
4491
|
+
function IterationTemplateDialog({
|
|
4492
|
+
open,
|
|
4493
|
+
onOpenChange,
|
|
4494
|
+
template,
|
|
4495
|
+
onSave,
|
|
4496
|
+
kind = "iterate",
|
|
4497
|
+
mode = "split"
|
|
4498
|
+
}) {
|
|
4499
|
+
const [draft, setDraft] = useState7(template);
|
|
4500
|
+
useEffect7(() => {
|
|
4501
|
+
if (open) setDraft(template);
|
|
4502
|
+
}, [open, template]);
|
|
4503
|
+
const unit = kind === "pivot" ? "cell" : "row";
|
|
4504
|
+
const save = () => {
|
|
4505
|
+
onSave(draft);
|
|
4506
|
+
onOpenChange(false);
|
|
4507
|
+
};
|
|
4508
|
+
return /* @__PURE__ */ jsx19(Dialog2, { open, onOpenChange, children: /* @__PURE__ */ jsxs17(DialogContent2, { className: "flex max-h-[85vh] w-[min(48rem,92vw)] max-w-none flex-col", children: [
|
|
4509
|
+
/* @__PURE__ */ jsxs17(DialogHeader, { children: [
|
|
4510
|
+
/* @__PURE__ */ jsxs17(DialogTitle2, { children: [
|
|
4511
|
+
"Edit ",
|
|
4512
|
+
kind === "pivot" ? "pivot" : "iteration",
|
|
4513
|
+
" template"
|
|
4514
|
+
] }),
|
|
4515
|
+
/* @__PURE__ */ jsxs17(DialogDescription, { children: [
|
|
4516
|
+
"The per-",
|
|
4517
|
+
unit,
|
|
4518
|
+
" template. Use ",
|
|
4519
|
+
/* @__PURE__ */ jsx19("code", { children: "{{token}}" }),
|
|
4520
|
+
" placeholders (e.g.",
|
|
4521
|
+
" ",
|
|
4522
|
+
/* @__PURE__ */ jsx19("code", { children: "{{item.name}}" }),
|
|
4523
|
+
") \u2014 each is filled per ",
|
|
4524
|
+
unit,
|
|
4525
|
+
" when the block renders."
|
|
4526
|
+
] })
|
|
4527
|
+
] }),
|
|
4528
|
+
/* @__PURE__ */ jsx19("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx19(
|
|
4529
|
+
MarkdownWorkspace,
|
|
4530
|
+
{
|
|
4531
|
+
value: draft,
|
|
4532
|
+
onChange: setDraft,
|
|
4533
|
+
defaultMode: mode,
|
|
4534
|
+
className: "h-full",
|
|
4535
|
+
"aria-label": "Iteration template editor"
|
|
4536
|
+
}
|
|
4537
|
+
) }),
|
|
4538
|
+
/* @__PURE__ */ jsxs17(DialogFooter, { children: [
|
|
4539
|
+
/* @__PURE__ */ jsx19(Button4, { variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
|
|
4540
|
+
/* @__PURE__ */ jsx19(Button4, { onClick: save, children: "Save template" })
|
|
4541
|
+
] })
|
|
4542
|
+
] }) });
|
|
4543
|
+
}
|
|
4544
|
+
function IterationTemplateProvider({ children }) {
|
|
4545
|
+
const [request, setRequest] = useState7(null);
|
|
4546
|
+
return /* @__PURE__ */ jsxs17(IterationEditContext.Provider, { value: setRequest, children: [
|
|
4547
|
+
children,
|
|
4548
|
+
/* @__PURE__ */ jsx19(
|
|
4549
|
+
IterationTemplateDialog,
|
|
4550
|
+
{
|
|
4551
|
+
open: request != null,
|
|
4552
|
+
onOpenChange: (next) => {
|
|
4553
|
+
if (!next) setRequest(null);
|
|
4554
|
+
},
|
|
4555
|
+
template: request?.template ?? "",
|
|
4556
|
+
kind: request?.kind ?? "iterate",
|
|
4557
|
+
onSave: (template) => request?.onSave(template)
|
|
4558
|
+
}
|
|
4559
|
+
)
|
|
4560
|
+
] });
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
// src/markdown-iteration/iteration-builder-dialog.tsx
|
|
4564
|
+
import {
|
|
4565
|
+
Button as Button5,
|
|
4566
|
+
Dialog as Dialog3,
|
|
4567
|
+
DialogBody,
|
|
4568
|
+
DialogContent as DialogContent3,
|
|
4569
|
+
DialogDescription as DialogDescription2,
|
|
4570
|
+
DialogFooter as DialogFooter2,
|
|
4571
|
+
DialogHeader as DialogHeader2,
|
|
4572
|
+
DialogTitle as DialogTitle3,
|
|
4573
|
+
Input as Input2,
|
|
4574
|
+
Label,
|
|
4575
|
+
TagInput,
|
|
4576
|
+
ToggleGroup as ToggleGroup2,
|
|
4577
|
+
ToggleGroupItem as ToggleGroupItem2
|
|
4578
|
+
} from "@elabs-ai/components-ui";
|
|
4579
|
+
import { useEffect as useEffect8, useId as useId4, useMemo as useMemo7, useState as useState8 } from "react";
|
|
4580
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
4581
|
+
function IterationBuilderDialog({
|
|
4582
|
+
open,
|
|
4583
|
+
onOpenChange,
|
|
4584
|
+
kind: kindProp = "iterate",
|
|
4585
|
+
value,
|
|
4586
|
+
initialValues,
|
|
4587
|
+
onSave,
|
|
4588
|
+
evaluate,
|
|
4589
|
+
interpolate
|
|
4590
|
+
}) {
|
|
4591
|
+
const kind = value?.kind ?? kindProp;
|
|
4592
|
+
const isPivot = kind === "pivot";
|
|
4593
|
+
const ids = useId4();
|
|
4594
|
+
const [asName, setAsName] = useState8("item");
|
|
4595
|
+
const [layout, setLayout] = useState8(ITERATION_LAYOUTS[kind][0] ?? "stacked");
|
|
4596
|
+
const [values, setValues] = useState8([]);
|
|
4597
|
+
const [cols, setCols] = useState8([]);
|
|
4598
|
+
const [template, setTemplate] = useState8("");
|
|
4599
|
+
useEffect8(() => {
|
|
4600
|
+
if (!open) return;
|
|
4601
|
+
const seed = value ?? {
|
|
4602
|
+
...emptyBuilderValue(kind),
|
|
4603
|
+
values: initialValues ?? []
|
|
4604
|
+
};
|
|
4605
|
+
setAsName(seed.as);
|
|
4606
|
+
setLayout(seed.layout);
|
|
4607
|
+
setValues(seed.values);
|
|
4608
|
+
setCols(seed.cols ?? []);
|
|
4609
|
+
setTemplate(seed.template);
|
|
4610
|
+
}, [open]);
|
|
4611
|
+
const draft = useMemo7(
|
|
4612
|
+
() => ({
|
|
4613
|
+
kind,
|
|
4614
|
+
as: asName.trim() || "item",
|
|
4615
|
+
layout,
|
|
4616
|
+
values,
|
|
4617
|
+
cols: isPivot ? cols : void 0,
|
|
4618
|
+
template
|
|
4619
|
+
}),
|
|
4620
|
+
[kind, asName, layout, values, cols, template, isPivot]
|
|
4621
|
+
);
|
|
4622
|
+
const previewMarkdown = useMemo7(() => serializeIterationDirective(draft), [draft]);
|
|
4623
|
+
const save = () => {
|
|
4624
|
+
onSave(serializeIterationDirective(draft));
|
|
4625
|
+
onOpenChange(false);
|
|
4626
|
+
};
|
|
4627
|
+
const noun = isPivot ? "pivot" : "iteration";
|
|
4628
|
+
return /* @__PURE__ */ jsx20(Dialog3, { open, onOpenChange, children: /* @__PURE__ */ jsxs18(DialogContent3, { className: "flex max-h-[88vh] w-[min(56rem,94vw)] max-w-none flex-col", children: [
|
|
4629
|
+
/* @__PURE__ */ jsxs18(DialogHeader2, { children: [
|
|
4630
|
+
/* @__PURE__ */ jsxs18(DialogTitle3, { children: [
|
|
4631
|
+
value ? "Edit" : "Insert",
|
|
4632
|
+
" ",
|
|
4633
|
+
noun
|
|
4634
|
+
] }),
|
|
4635
|
+
/* @__PURE__ */ jsx20(DialogDescription2, { children: isPivot ? "Pick the row and column values, then write the per-cell template. The matrix below fills in live." : "Add the list values, then write the per-row template. The result below fills in live." })
|
|
4636
|
+
] }),
|
|
4637
|
+
/* @__PURE__ */ jsxs18(DialogBody, { className: "grid gap-4 md:grid-cols-2", children: [
|
|
4638
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex min-w-0 flex-col gap-4", children: [
|
|
4639
|
+
!isPivot ? /* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-1.5", children: [
|
|
4640
|
+
/* @__PURE__ */ jsx20(Label, { htmlFor: `${ids}-as`, children: "Bind name" }),
|
|
4641
|
+
/* @__PURE__ */ jsx20(
|
|
4642
|
+
Input2,
|
|
4643
|
+
{
|
|
4644
|
+
id: `${ids}-as`,
|
|
4645
|
+
value: asName,
|
|
4646
|
+
spellCheck: false,
|
|
4647
|
+
autoComplete: "off",
|
|
4648
|
+
placeholder: "item",
|
|
4649
|
+
onChange: (e) => setAsName(e.target.value)
|
|
4650
|
+
}
|
|
4651
|
+
),
|
|
4652
|
+
/* @__PURE__ */ jsxs18("p", { className: "text-meta text-muted-foreground", children: [
|
|
4653
|
+
"Use ",
|
|
4654
|
+
/* @__PURE__ */ jsx20("code", { children: `{{${asName.trim() || "item"}.name}}` }),
|
|
4655
|
+
" in the template."
|
|
4656
|
+
] })
|
|
4657
|
+
] }) : null,
|
|
4658
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-1.5", children: [
|
|
4659
|
+
/* @__PURE__ */ jsx20(Label, { htmlFor: `${ids}-values`, children: isPivot ? "Row values" : "Values" }),
|
|
4660
|
+
/* @__PURE__ */ jsx20(
|
|
4661
|
+
TagInput,
|
|
4662
|
+
{
|
|
4663
|
+
id: `${ids}-values`,
|
|
4664
|
+
value: values,
|
|
4665
|
+
onValueChange: setValues,
|
|
4666
|
+
delimiter: [",", "\n"],
|
|
4667
|
+
placeholder: "Type a value, press Enter\u2026"
|
|
4668
|
+
}
|
|
4669
|
+
)
|
|
4670
|
+
] }),
|
|
4671
|
+
isPivot ? /* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-1.5", children: [
|
|
4672
|
+
/* @__PURE__ */ jsx20(Label, { htmlFor: `${ids}-cols`, children: "Column values" }),
|
|
4673
|
+
/* @__PURE__ */ jsx20(
|
|
4674
|
+
TagInput,
|
|
4675
|
+
{
|
|
4676
|
+
id: `${ids}-cols`,
|
|
4677
|
+
value: cols,
|
|
4678
|
+
onValueChange: setCols,
|
|
4679
|
+
delimiter: [",", "\n"],
|
|
4680
|
+
placeholder: "Type a value, press Enter\u2026"
|
|
4681
|
+
}
|
|
4682
|
+
)
|
|
4683
|
+
] }) : null,
|
|
4684
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-1.5", children: [
|
|
4685
|
+
/* @__PURE__ */ jsx20(Label, { id: `${ids}-layout`, children: "Layout" }),
|
|
4686
|
+
/* @__PURE__ */ jsx20(
|
|
4687
|
+
ToggleGroup2,
|
|
4688
|
+
{
|
|
4689
|
+
type: "single",
|
|
4690
|
+
variant: "segmented",
|
|
4691
|
+
value: layout,
|
|
4692
|
+
onValueChange: (next) => {
|
|
4693
|
+
if (next) setLayout(next);
|
|
4694
|
+
},
|
|
4695
|
+
"aria-labelledby": `${ids}-layout`,
|
|
4696
|
+
className: "w-fit",
|
|
4697
|
+
children: ITERATION_LAYOUTS[kind].map((l) => /* @__PURE__ */ jsx20(ToggleGroupItem2, { value: l, className: "capitalize", children: l }, l))
|
|
4698
|
+
}
|
|
4699
|
+
)
|
|
4700
|
+
] })
|
|
4701
|
+
] }),
|
|
4702
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex min-h-0 min-w-0 flex-col gap-4", children: [
|
|
4703
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex min-h-0 flex-col gap-1.5", children: [
|
|
4704
|
+
/* @__PURE__ */ jsxs18(Label, { children: [
|
|
4705
|
+
"Per-",
|
|
4706
|
+
isPivot ? "cell" : "row",
|
|
4707
|
+
" template"
|
|
4708
|
+
] }),
|
|
4709
|
+
/* @__PURE__ */ jsx20("div", { className: "h-44 min-h-0 overflow-hidden rounded-md border border-border", children: /* @__PURE__ */ jsx20(
|
|
4710
|
+
MarkdownWorkspace,
|
|
4711
|
+
{
|
|
4712
|
+
value: template,
|
|
4713
|
+
onChange: setTemplate,
|
|
4714
|
+
defaultMode: "source",
|
|
4715
|
+
className: "h-full",
|
|
4716
|
+
"aria-label": "Per-cell template"
|
|
4717
|
+
}
|
|
4718
|
+
) })
|
|
4719
|
+
] }),
|
|
4720
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex min-h-0 flex-col gap-1.5", children: [
|
|
4721
|
+
/* @__PURE__ */ jsx20(Label, { children: "Live preview" }),
|
|
4722
|
+
/* @__PURE__ */ jsx20(
|
|
4723
|
+
"div",
|
|
4724
|
+
{
|
|
4725
|
+
role: "region",
|
|
4726
|
+
"aria-label": "Live preview",
|
|
4727
|
+
className: "min-h-0 flex-1 overflow-auto rounded-md border border-border bg-card p-3",
|
|
4728
|
+
children: /* @__PURE__ */ jsx20(
|
|
4729
|
+
MarkdownPreview,
|
|
4730
|
+
{
|
|
4731
|
+
evaluateIteration: evaluateEmbedded,
|
|
4732
|
+
evaluate,
|
|
4733
|
+
interpolate,
|
|
4734
|
+
children: previewMarkdown
|
|
4735
|
+
}
|
|
4736
|
+
)
|
|
4737
|
+
}
|
|
4738
|
+
)
|
|
4739
|
+
] })
|
|
4740
|
+
] })
|
|
4741
|
+
] }),
|
|
4742
|
+
/* @__PURE__ */ jsxs18(DialogFooter2, { children: [
|
|
4743
|
+
/* @__PURE__ */ jsx20(Button5, { variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
|
|
4744
|
+
/* @__PURE__ */ jsx20(Button5, { onClick: save, children: value ? "Save" : "Insert" })
|
|
4745
|
+
] })
|
|
4746
|
+
] }) });
|
|
4747
|
+
}
|
|
4748
|
+
function IterationBuilderProvider({
|
|
4749
|
+
children,
|
|
4750
|
+
evaluate,
|
|
4751
|
+
interpolate
|
|
4752
|
+
}) {
|
|
4753
|
+
const [request, setRequest] = useState8(null);
|
|
4754
|
+
const seed = request ? builderValueFromParts(request.kind, request.attributes ?? {}, request.template) : void 0;
|
|
4755
|
+
return /* @__PURE__ */ jsxs18(IterationEditContext.Provider, { value: setRequest, children: [
|
|
4756
|
+
children,
|
|
4757
|
+
/* @__PURE__ */ jsx20(
|
|
4758
|
+
IterationBuilderDialog,
|
|
4759
|
+
{
|
|
4760
|
+
open: request != null,
|
|
4761
|
+
onOpenChange: (next) => {
|
|
4762
|
+
if (!next) setRequest(null);
|
|
4763
|
+
},
|
|
4764
|
+
kind: request?.kind ?? "iterate",
|
|
4765
|
+
value: seed,
|
|
4766
|
+
evaluate,
|
|
4767
|
+
interpolate,
|
|
4768
|
+
onSave: (directiveMarkdown) => {
|
|
4769
|
+
const parsed = parseIterationDirective(directiveMarkdown);
|
|
4770
|
+
if (!parsed) return;
|
|
4771
|
+
const { attributes, template } = directivePartsFromValue(parsed);
|
|
4772
|
+
if (request?.onSaveData) request.onSaveData({ attributes, template });
|
|
4773
|
+
else request?.onSave(template);
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
)
|
|
4777
|
+
] });
|
|
4778
|
+
}
|
|
4779
|
+
export {
|
|
4780
|
+
BRAND_DIRECTIVES,
|
|
4781
|
+
BRAND_SLASH_COMMANDS,
|
|
4782
|
+
Bibliography,
|
|
4783
|
+
ProseBlockquote as Blockquote,
|
|
4784
|
+
CALC_FENCE_SEED,
|
|
4785
|
+
CalcBlock,
|
|
4786
|
+
CalcInline,
|
|
4787
|
+
DECISION_STATUSES,
|
|
4788
|
+
DEFAULT_TEMPLATE,
|
|
4789
|
+
DecisionCard,
|
|
4790
|
+
DocumentOutline,
|
|
4791
|
+
ENTITY_KINDS,
|
|
4792
|
+
EntityCard,
|
|
4793
|
+
EntityChip,
|
|
4794
|
+
FootnoteList,
|
|
4795
|
+
ProseHeading as Heading,
|
|
4796
|
+
ProseInlineCode as InlineCode,
|
|
4797
|
+
IterationBlock,
|
|
4798
|
+
IterationBuilderDialog,
|
|
4799
|
+
IterationBuilderProvider,
|
|
4800
|
+
IterationEditContext,
|
|
4801
|
+
IterationTemplateDialog,
|
|
4802
|
+
IterationTemplateProvider,
|
|
4803
|
+
KnowledgeCard,
|
|
4804
|
+
ProseLink as Link,
|
|
4805
|
+
ProseList as List,
|
|
4806
|
+
ProseListItem as ListItem,
|
|
4807
|
+
MARKDOWN_HEADING_REM,
|
|
4808
|
+
MARKDOWN_HEADING_TRACKING,
|
|
4809
|
+
MARKDOWN_HEADING_WEIGHT,
|
|
4810
|
+
MARKDOWN_MEASURE,
|
|
4811
|
+
MarkdownEditor,
|
|
4812
|
+
MarkdownPreview,
|
|
4813
|
+
MarkdownToolbar,
|
|
4814
|
+
MarkdownWorkspace,
|
|
4815
|
+
MathBlock,
|
|
4816
|
+
MathInline,
|
|
4817
|
+
MermaidDiagram,
|
|
4818
|
+
MermaidWorkspace,
|
|
4819
|
+
MetricBlock,
|
|
4820
|
+
MonacoSlashMenu,
|
|
4821
|
+
SlashMenu,
|
|
4822
|
+
TableOfContents,
|
|
4823
|
+
ProseText as Text,
|
|
4824
|
+
Timeline,
|
|
4825
|
+
aiObjectDirectives,
|
|
4826
|
+
annotationForRange,
|
|
4827
|
+
attachCalcMonaco,
|
|
4828
|
+
attachCompletionsMonaco,
|
|
4829
|
+
brandSlashViewPlugins,
|
|
4830
|
+
buildMarkdownPlugins,
|
|
4831
|
+
builderValueFromParts,
|
|
4832
|
+
calcProsePlugins,
|
|
4833
|
+
calcTokenClassName,
|
|
4834
|
+
collectCitations,
|
|
4835
|
+
collectCompletions,
|
|
4836
|
+
computeMarkdownAnnotations,
|
|
4837
|
+
decisionCardVariants,
|
|
4838
|
+
decisionDirective,
|
|
4839
|
+
defaultInterpolate,
|
|
4840
|
+
directivePartsFromValue,
|
|
4841
|
+
emptyBuilderValue,
|
|
4842
|
+
entityChipVariants,
|
|
4843
|
+
entityDirective,
|
|
4844
|
+
evaluateEmbedded,
|
|
4845
|
+
filterSlashCommands,
|
|
4846
|
+
findCalcFences,
|
|
4847
|
+
groupSlashCommands,
|
|
4848
|
+
insertBasicBlock,
|
|
4849
|
+
insertBrandDirective,
|
|
4850
|
+
insertCalcFence,
|
|
4851
|
+
insertDirective,
|
|
4852
|
+
insertHorizontalRule,
|
|
4853
|
+
insertLink,
|
|
4854
|
+
knowledgeDirective,
|
|
4855
|
+
markdownScaleVars,
|
|
4856
|
+
mergeNormalizedEdit,
|
|
4857
|
+
monacoContentAccess,
|
|
4858
|
+
parseAttributes,
|
|
4859
|
+
parseFrontmatter,
|
|
4860
|
+
parseIterationDirective,
|
|
4861
|
+
parseMarkdown,
|
|
4862
|
+
parseMarkdownOutline,
|
|
4863
|
+
proseMirrorContentAccess,
|
|
4864
|
+
remarkBrandDirectives,
|
|
4865
|
+
removedMarkerAt,
|
|
4866
|
+
resolveCalcInsert,
|
|
4867
|
+
resolveReplaceRange,
|
|
4868
|
+
selectionWatchPlugin,
|
|
4869
|
+
serializeFrontmatter,
|
|
4870
|
+
serializeIterationDirective,
|
|
4871
|
+
shiftAnnotations,
|
|
4872
|
+
specFromDirective,
|
|
4873
|
+
splitList,
|
|
4874
|
+
staticMarkdownFromValue,
|
|
4875
|
+
summarizeAnnotations,
|
|
4876
|
+
toggleLinePrefix,
|
|
4877
|
+
transposeIterationValue,
|
|
4878
|
+
triggerQueryStart,
|
|
4879
|
+
useMarkdownOutline,
|
|
4880
|
+
wrapSelection
|
|
4881
|
+
};
|
|
4882
|
+
//# sourceMappingURL=index.js.map
|