@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./index.css";
|
|
3
|
+
import {
|
|
4
|
+
CodeEditor,
|
|
5
|
+
CopyButton,
|
|
6
|
+
EditorContextMenu,
|
|
7
|
+
MarkdownEditor,
|
|
8
|
+
applyBrandTheme,
|
|
9
|
+
brandThemeId,
|
|
10
|
+
buildBrandThemeData,
|
|
11
|
+
monacoContentAccess,
|
|
12
|
+
useDataTheme
|
|
13
|
+
} from "./chunk-LBC5VJBD.js";
|
|
14
|
+
|
|
15
|
+
// src/diff-editor/diff-editor.tsx
|
|
16
|
+
import * as monaco from "monaco-editor";
|
|
17
|
+
import { cn } from "@elabs-ai/components-ui/lib/cn";
|
|
18
|
+
import {
|
|
19
|
+
forwardRef,
|
|
20
|
+
useEffect,
|
|
21
|
+
useImperativeHandle,
|
|
22
|
+
useRef,
|
|
23
|
+
useState
|
|
24
|
+
} from "react";
|
|
25
|
+
import { jsx } from "react/jsx-runtime";
|
|
26
|
+
var BASE_OPTIONS = {
|
|
27
|
+
automaticLayout: true,
|
|
28
|
+
minimap: { enabled: false },
|
|
29
|
+
scrollBeyondLastLine: false,
|
|
30
|
+
fontSize: 13,
|
|
31
|
+
ignoreTrimWhitespace: false,
|
|
32
|
+
padding: { top: 12, bottom: 12 },
|
|
33
|
+
scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 }
|
|
34
|
+
};
|
|
35
|
+
var DiffEditor = forwardRef(function DiffEditor2({
|
|
36
|
+
original,
|
|
37
|
+
modified,
|
|
38
|
+
language = "typescript",
|
|
39
|
+
readOnly = true,
|
|
40
|
+
renderSideBySide = true,
|
|
41
|
+
height = "100%",
|
|
42
|
+
options,
|
|
43
|
+
onMount,
|
|
44
|
+
className,
|
|
45
|
+
style,
|
|
46
|
+
...props
|
|
47
|
+
}, ref) {
|
|
48
|
+
const containerRef = useRef(null);
|
|
49
|
+
const [editor2, setEditor] = useState(null);
|
|
50
|
+
const { theme, revision } = useDataTheme();
|
|
51
|
+
const onMountRef = useRef(onMount);
|
|
52
|
+
onMountRef.current = onMount;
|
|
53
|
+
useImperativeHandle(ref, () => editor2, [
|
|
54
|
+
editor2
|
|
55
|
+
]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const container = containerRef.current;
|
|
58
|
+
if (!container) return;
|
|
59
|
+
const instance = monaco.editor.createDiffEditor(container, {
|
|
60
|
+
...BASE_OPTIONS,
|
|
61
|
+
readOnly,
|
|
62
|
+
renderSideBySide,
|
|
63
|
+
...options
|
|
64
|
+
});
|
|
65
|
+
const originalModel = monaco.editor.createModel(original, language);
|
|
66
|
+
const modifiedModel = monaco.editor.createModel(modified, language);
|
|
67
|
+
instance.setModel({ original: originalModel, modified: modifiedModel });
|
|
68
|
+
setEditor(instance);
|
|
69
|
+
onMountRef.current?.(instance, monaco);
|
|
70
|
+
return () => {
|
|
71
|
+
instance.dispose();
|
|
72
|
+
originalModel.dispose();
|
|
73
|
+
modifiedModel.dispose();
|
|
74
|
+
setEditor(null);
|
|
75
|
+
};
|
|
76
|
+
}, []);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const original_ = editor2?.getModel()?.original;
|
|
79
|
+
if (original_ && original_.getValue() !== original) original_.setValue(original);
|
|
80
|
+
}, [editor2, original]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const modified_ = editor2?.getModel()?.modified;
|
|
83
|
+
if (modified_ && modified_.getValue() !== modified) modified_.setValue(modified);
|
|
84
|
+
}, [editor2, modified]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const models = editor2?.getModel();
|
|
87
|
+
if (!models) return;
|
|
88
|
+
monaco.editor.setModelLanguage(models.original, language);
|
|
89
|
+
monaco.editor.setModelLanguage(models.modified, language);
|
|
90
|
+
}, [editor2, language]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
editor2?.updateOptions({ readOnly, renderSideBySide });
|
|
93
|
+
}, [editor2, readOnly, renderSideBySide]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!editor2) return;
|
|
96
|
+
try {
|
|
97
|
+
applyBrandTheme(monaco, theme);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error("[@elabs-ai/components-editor] failed to apply brand theme", err);
|
|
100
|
+
}
|
|
101
|
+
}, [editor2, theme, revision]);
|
|
102
|
+
const resolvedStyle = {
|
|
103
|
+
height: typeof height === "number" ? `${height}px` : height,
|
|
104
|
+
...style
|
|
105
|
+
};
|
|
106
|
+
return /* @__PURE__ */ jsx(
|
|
107
|
+
"div",
|
|
108
|
+
{
|
|
109
|
+
ref: containerRef,
|
|
110
|
+
"data-testid": "diff-editor",
|
|
111
|
+
className: cn("h-full w-full overflow-hidden bg-background text-foreground", className),
|
|
112
|
+
style: resolvedStyle,
|
|
113
|
+
...props
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// src/code-workspace/code-workspace.tsx
|
|
119
|
+
import { Tabs, TabsList, TabsTrigger } from "@elabs-ai/components-ui";
|
|
120
|
+
import { cn as cn2 } from "@elabs-ai/components-ui/lib/cn";
|
|
121
|
+
import {
|
|
122
|
+
forwardRef as forwardRef2,
|
|
123
|
+
useCallback,
|
|
124
|
+
useEffect as useEffect2,
|
|
125
|
+
useImperativeHandle as useImperativeHandle2,
|
|
126
|
+
useRef as useRef2,
|
|
127
|
+
useState as useState2
|
|
128
|
+
} from "react";
|
|
129
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
130
|
+
var EXT_LANGUAGE = {
|
|
131
|
+
ts: "typescript",
|
|
132
|
+
tsx: "typescript",
|
|
133
|
+
mts: "typescript",
|
|
134
|
+
cts: "typescript",
|
|
135
|
+
js: "javascript",
|
|
136
|
+
jsx: "javascript",
|
|
137
|
+
mjs: "javascript",
|
|
138
|
+
cjs: "javascript",
|
|
139
|
+
json: "json",
|
|
140
|
+
css: "css",
|
|
141
|
+
scss: "scss",
|
|
142
|
+
less: "less",
|
|
143
|
+
html: "html",
|
|
144
|
+
md: "markdown",
|
|
145
|
+
py: "python",
|
|
146
|
+
sql: "sql",
|
|
147
|
+
yml: "yaml",
|
|
148
|
+
yaml: "yaml",
|
|
149
|
+
xml: "xml",
|
|
150
|
+
sh: "shell"
|
|
151
|
+
};
|
|
152
|
+
var basename = (path) => path.split("/").pop() || path;
|
|
153
|
+
function inferLanguage(file) {
|
|
154
|
+
if (file.language) return file.language;
|
|
155
|
+
const ext = file.path.split(".").pop()?.toLowerCase() ?? "";
|
|
156
|
+
return EXT_LANGUAGE[ext] ?? "plaintext";
|
|
157
|
+
}
|
|
158
|
+
var NULL_ACCESS = {
|
|
159
|
+
getText: () => "",
|
|
160
|
+
getSelection: () => ({ text: "", empty: true }),
|
|
161
|
+
replaceSelection: () => void 0,
|
|
162
|
+
insertAtCursor: () => void 0,
|
|
163
|
+
focus: () => void 0,
|
|
164
|
+
onSelectionChange: () => () => void 0
|
|
165
|
+
};
|
|
166
|
+
var CodeWorkspace = forwardRef2(
|
|
167
|
+
function CodeWorkspace2({
|
|
168
|
+
files,
|
|
169
|
+
activePath,
|
|
170
|
+
defaultActivePath,
|
|
171
|
+
onActivePathChange,
|
|
172
|
+
onFileChange,
|
|
173
|
+
readOnly = false,
|
|
174
|
+
height = "100%",
|
|
175
|
+
className,
|
|
176
|
+
style,
|
|
177
|
+
...props
|
|
178
|
+
}, ref) {
|
|
179
|
+
const [internalActive, setInternalActive] = useState2(defaultActivePath ?? files[0]?.path ?? "");
|
|
180
|
+
const isControlled = activePath !== void 0;
|
|
181
|
+
const requested = isControlled ? activePath : internalActive;
|
|
182
|
+
const activeFile = files.find((f) => f.path === requested) ?? files[0];
|
|
183
|
+
const active = activeFile?.path ?? "";
|
|
184
|
+
const rootRef = useRef2(null);
|
|
185
|
+
const [activeEditor, setActiveEditor] = useState2(null);
|
|
186
|
+
useEffect2(() => {
|
|
187
|
+
if (!activeFile) setActiveEditor(null);
|
|
188
|
+
}, [activeFile]);
|
|
189
|
+
const [selectionListeners] = useState2(() => /* @__PURE__ */ new Set());
|
|
190
|
+
useEffect2(() => {
|
|
191
|
+
if (!activeEditor) return;
|
|
192
|
+
const unsub = monacoContentAccess(activeEditor).onSelectionChange(
|
|
193
|
+
(sel) => selectionListeners.forEach((l) => l(sel))
|
|
194
|
+
);
|
|
195
|
+
return unsub;
|
|
196
|
+
}, [activeEditor, selectionListeners]);
|
|
197
|
+
const selectTab = useCallback(
|
|
198
|
+
(path) => {
|
|
199
|
+
if (!isControlled) setInternalActive(path);
|
|
200
|
+
onActivePathChange?.(path);
|
|
201
|
+
},
|
|
202
|
+
[isControlled, onActivePathChange]
|
|
203
|
+
);
|
|
204
|
+
useImperativeHandle2(ref, () => {
|
|
205
|
+
const access = activeEditor ? monacoContentAccess(activeEditor) : NULL_ACCESS;
|
|
206
|
+
return {
|
|
207
|
+
getActiveEditor: () => activeEditor,
|
|
208
|
+
getElement: () => rootRef.current,
|
|
209
|
+
getText: () => access.getText(),
|
|
210
|
+
getSelection: () => access.getSelection(),
|
|
211
|
+
replaceSelection: (text) => access.replaceSelection(text),
|
|
212
|
+
insertAtCursor: (text) => access.insertAtCursor(text),
|
|
213
|
+
focus: () => access.focus(),
|
|
214
|
+
// Stable set (not the call-time `access`) so a subscription survives the
|
|
215
|
+
// editor's async mount + tab swaps; the binding effect forwards events.
|
|
216
|
+
onSelectionChange: (listener) => {
|
|
217
|
+
selectionListeners.add(listener);
|
|
218
|
+
return () => selectionListeners.delete(listener);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}, [activeEditor, selectionListeners]);
|
|
222
|
+
const resolvedStyle = {
|
|
223
|
+
height: typeof height === "number" ? `${height}px` : height,
|
|
224
|
+
...style
|
|
225
|
+
};
|
|
226
|
+
return /* @__PURE__ */ jsx2(
|
|
227
|
+
"div",
|
|
228
|
+
{
|
|
229
|
+
ref: rootRef,
|
|
230
|
+
className: cn2(
|
|
231
|
+
"flex flex-col overflow-hidden rounded-lg border border-border bg-surface",
|
|
232
|
+
className
|
|
233
|
+
),
|
|
234
|
+
style: resolvedStyle,
|
|
235
|
+
...props,
|
|
236
|
+
children: /* @__PURE__ */ jsxs(
|
|
237
|
+
Tabs,
|
|
238
|
+
{
|
|
239
|
+
value: active,
|
|
240
|
+
onValueChange: selectTab,
|
|
241
|
+
className: "flex min-h-0 flex-1 flex-col gap-0",
|
|
242
|
+
children: [
|
|
243
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center border-b border-border bg-surface", children: [
|
|
244
|
+
/* @__PURE__ */ jsx2(TabsList, { className: "h-9 flex-1 justify-start gap-0 rounded-none bg-transparent p-0", children: files.map((file) => /* @__PURE__ */ jsx2(
|
|
245
|
+
TabsTrigger,
|
|
246
|
+
{
|
|
247
|
+
value: file.path,
|
|
248
|
+
className: "h-9 rounded-none border-e border-border px-3 text-xs data-[state=active]:bg-background data-[state=active]:shadow-none",
|
|
249
|
+
children: basename(file.path)
|
|
250
|
+
},
|
|
251
|
+
file.path
|
|
252
|
+
)) }),
|
|
253
|
+
activeFile ? /* @__PURE__ */ jsx2(CopyButton, { value: activeFile.value, className: "me-1.5" }) : null
|
|
254
|
+
] }),
|
|
255
|
+
/* @__PURE__ */ jsx2("div", { className: "min-h-0 flex-1", children: activeFile ? /* @__PURE__ */ jsx2(
|
|
256
|
+
CodeEditor,
|
|
257
|
+
{
|
|
258
|
+
path: activeFile.path,
|
|
259
|
+
language: inferLanguage(activeFile),
|
|
260
|
+
value: activeFile.value,
|
|
261
|
+
readOnly,
|
|
262
|
+
onChange: (next) => onFileChange?.(activeFile.path, next),
|
|
263
|
+
onMount: (editor2) => setActiveEditor(editor2),
|
|
264
|
+
height: "100%"
|
|
265
|
+
},
|
|
266
|
+
activeFile.path
|
|
267
|
+
) : null })
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// src/editor-toolbar/editor-toolbar.tsx
|
|
277
|
+
import {
|
|
278
|
+
Select,
|
|
279
|
+
SelectContent,
|
|
280
|
+
SelectItem,
|
|
281
|
+
SelectTrigger,
|
|
282
|
+
SelectValue
|
|
283
|
+
} from "@elabs-ai/components-ui";
|
|
284
|
+
import { cn as cn3 } from "@elabs-ai/components-ui/lib/cn";
|
|
285
|
+
import { forwardRef as forwardRef3 } from "react";
|
|
286
|
+
|
|
287
|
+
// src/lib/languages.ts
|
|
288
|
+
var EDITOR_LANGUAGES = [
|
|
289
|
+
{ id: "typescript", label: "TypeScript" },
|
|
290
|
+
{ id: "javascript", label: "JavaScript" },
|
|
291
|
+
{ id: "json", label: "JSON" },
|
|
292
|
+
{ id: "html", label: "HTML" },
|
|
293
|
+
{ id: "css", label: "CSS" },
|
|
294
|
+
{ id: "scss", label: "SCSS" },
|
|
295
|
+
{ id: "markdown", label: "Markdown" },
|
|
296
|
+
{ id: "python", label: "Python" },
|
|
297
|
+
{ id: "sql", label: "SQL" },
|
|
298
|
+
{ id: "yaml", label: "YAML" },
|
|
299
|
+
{ id: "xml", label: "XML" },
|
|
300
|
+
{ id: "shell", label: "Shell" },
|
|
301
|
+
{ id: "plaintext", label: "Plain text" }
|
|
302
|
+
];
|
|
303
|
+
function languageLabel(id) {
|
|
304
|
+
return EDITOR_LANGUAGES.find((l) => l.id === id)?.label ?? id;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/editor-toolbar/editor-toolbar.tsx
|
|
308
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
309
|
+
var EditorToolbar = forwardRef3(function EditorToolbar2({
|
|
310
|
+
filename,
|
|
311
|
+
language,
|
|
312
|
+
onLanguageChange,
|
|
313
|
+
languages = EDITOR_LANGUAGES,
|
|
314
|
+
copyValue,
|
|
315
|
+
actions,
|
|
316
|
+
className,
|
|
317
|
+
...props
|
|
318
|
+
}, ref) {
|
|
319
|
+
return /* @__PURE__ */ jsxs2(
|
|
320
|
+
"div",
|
|
321
|
+
{
|
|
322
|
+
ref,
|
|
323
|
+
className: cn3(
|
|
324
|
+
"flex h-10 shrink-0 items-center gap-2 border-b border-border bg-surface px-3",
|
|
325
|
+
className
|
|
326
|
+
),
|
|
327
|
+
...props,
|
|
328
|
+
children: [
|
|
329
|
+
filename != null ? /* @__PURE__ */ jsx3("span", { className: "truncate text-sm font-medium text-foreground", children: filename }) : null,
|
|
330
|
+
/* @__PURE__ */ jsxs2("div", { className: "ml-auto flex items-center gap-1.5", children: [
|
|
331
|
+
actions,
|
|
332
|
+
onLanguageChange ? /* @__PURE__ */ jsxs2(Select, { value: language, onValueChange: onLanguageChange, children: [
|
|
333
|
+
/* @__PURE__ */ jsx3(SelectTrigger, { size: "sm", className: "h-7 w-[140px]", "aria-label": "Language", children: /* @__PURE__ */ jsx3(SelectValue, { placeholder: "Language" }) }),
|
|
334
|
+
/* @__PURE__ */ jsx3(SelectContent, { children: languages.map((l) => /* @__PURE__ */ jsx3(SelectItem, { value: l.id, children: l.label }, l.id)) })
|
|
335
|
+
] }) : null,
|
|
336
|
+
copyValue != null ? /* @__PURE__ */ jsx3(CopyButton, { value: copyValue }) : null
|
|
337
|
+
] })
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// src/index.ts
|
|
344
|
+
import * as monaco2 from "monaco-editor";
|
|
345
|
+
export {
|
|
346
|
+
CodeEditor,
|
|
347
|
+
CodeWorkspace,
|
|
348
|
+
CopyButton,
|
|
349
|
+
DiffEditor,
|
|
350
|
+
EDITOR_LANGUAGES,
|
|
351
|
+
EditorContextMenu,
|
|
352
|
+
EditorToolbar,
|
|
353
|
+
MarkdownEditor,
|
|
354
|
+
applyBrandTheme,
|
|
355
|
+
brandThemeId,
|
|
356
|
+
buildBrandThemeData,
|
|
357
|
+
languageLabel,
|
|
358
|
+
monaco2 as monaco,
|
|
359
|
+
monacoContentAccess,
|
|
360
|
+
useDataTheme
|
|
361
|
+
};
|
|
362
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/diff-editor/diff-editor.tsx","../src/code-workspace/code-workspace.tsx","../src/editor-toolbar/editor-toolbar.tsx","../src/lib/languages.ts","../src/index.ts"],"sourcesContent":["\"use client\";\n\nimport * as monaco from \"monaco-editor\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n} from \"react\";\nimport { applyBrandTheme } from \"../lib/monaco-theme-bridge\";\nimport { useDataTheme } from \"../lib/use-data-theme\";\n\nexport type MonacoDiffEditor = monaco.editor.IStandaloneDiffEditor;\n\nexport interface DiffEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, \"defaultValue\"> {\n /** Left/original document. */\n original: string;\n /** Right/modified document. */\n modified: string;\n /** Monaco language id applied to both sides. Defaults to \"typescript\". */\n language?: string;\n /** Read-only (diffs are read-only by default). */\n readOnly?: boolean;\n /** Side-by-side (true) vs inline (false) diff. Defaults to side-by-side. */\n renderSideBySide?: boolean;\n /** Editor height. Number → px. Defaults to \"100%\". */\n height?: number | string;\n /** Passthrough Monaco diff options (merged over the defaults). */\n options?: monaco.editor.IStandaloneDiffEditorConstructionOptions;\n /** Called once the diff editor + monaco namespace are ready. */\n onMount?: (editor: MonacoDiffEditor, monacoApi: typeof monaco) => void;\n}\n\nconst BASE_OPTIONS: monaco.editor.IStandaloneDiffEditorConstructionOptions = {\n automaticLayout: true,\n minimap: { enabled: false },\n scrollBeyondLastLine: false,\n fontSize: 13,\n ignoreTrimWhitespace: false,\n padding: { top: 12, bottom: 12 },\n scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 },\n};\n\n/**\n * A token-themed Monaco diff editor (original ↔ modified). Same theming bridge\n * and chrome story as {@link CodeEditor}; reads as a side-by-side or inline diff.\n */\nexport const DiffEditor = forwardRef<MonacoDiffEditor | null, DiffEditorProps>(function DiffEditor(\n {\n original,\n modified,\n language = \"typescript\",\n readOnly = true,\n renderSideBySide = true,\n height = \"100%\",\n options,\n onMount,\n className,\n style,\n ...props\n },\n ref,\n) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [editor, setEditor] = useState<MonacoDiffEditor | null>(null);\n const { theme, revision } = useDataTheme();\n\n const onMountRef = useRef(onMount);\n onMountRef.current = onMount;\n\n useImperativeHandle<MonacoDiffEditor | null, MonacoDiffEditor | null>(ref, () => editor, [\n editor,\n ]);\n\n // Mount once.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const instance = monaco.editor.createDiffEditor(container, {\n ...BASE_OPTIONS,\n readOnly,\n renderSideBySide,\n ...options,\n });\n const originalModel = monaco.editor.createModel(original, language);\n const modifiedModel = monaco.editor.createModel(modified, language);\n instance.setModel({ original: originalModel, modified: modifiedModel });\n // Theme is applied by the effect below once `setEditor` runs.\n setEditor(instance);\n onMountRef.current?.(instance, monaco);\n\n return () => {\n instance.dispose();\n originalModel.dispose();\n modifiedModel.dispose();\n setEditor(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const original_ = editor?.getModel()?.original;\n if (original_ && original_.getValue() !== original) original_.setValue(original);\n }, [editor, original]);\n\n useEffect(() => {\n const modified_ = editor?.getModel()?.modified;\n if (modified_ && modified_.getValue() !== modified) modified_.setValue(modified);\n }, [editor, modified]);\n\n useEffect(() => {\n const models = editor?.getModel();\n if (!models) return;\n monaco.editor.setModelLanguage(models.original, language);\n monaco.editor.setModelLanguage(models.modified, language);\n }, [editor, language]);\n\n useEffect(() => {\n editor?.updateOptions({ readOnly, renderSideBySide });\n }, [editor, readOnly, renderSideBySide]);\n\n useEffect(() => {\n if (!editor) return;\n try {\n applyBrandTheme(monaco, theme);\n } catch (err) {\n console.error(\"[@elabs-ai/components-editor] failed to apply brand theme\", err);\n }\n // `revision` re-applies once the data-theme attribute settles. See CodeEditor.\n }, [editor, theme, revision]);\n\n const resolvedStyle: CSSProperties = {\n height: typeof height === \"number\" ? `${height}px` : height,\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n data-testid=\"diff-editor\"\n className={cn(\"h-full w-full overflow-hidden bg-background text-foreground\", className)}\n style={resolvedStyle}\n {...props}\n />\n );\n});\n","\"use client\";\n\nimport { Tabs, TabsList, TabsTrigger } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n} from \"react\";\nimport { CodeEditor, type MonacoCodeEditor } from \"../code-editor\";\nimport { CopyButton } from \"../copy-button\";\nimport {\n monacoContentAccess,\n type EditorContentAccess,\n type EditorSelection,\n} from \"../lib/editor-content-access\";\n\nexport interface EditorFile {\n /** Unique path/name; shown (basename) on the tab and used as the model path. */\n path: string;\n /** Monaco language id. Inferred from the extension when omitted. */\n language?: string;\n /** File content. */\n value: string;\n}\n\nexport interface CodeWorkspaceProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Open files, one tab each. */\n files: EditorFile[];\n /** Controlled active file path. */\n activePath?: string;\n /** Initial active path for uncontrolled use. Defaults to the first file. */\n defaultActivePath?: string;\n /** Called when the active tab changes. */\n onActivePathChange?: (path: string) => void;\n /** Called on every edit with the file path + new content. */\n onFileChange?: (path: string, value: string) => void;\n /** Render all files read-only. */\n readOnly?: boolean;\n /** Workspace height. Number → px. Defaults to \"100%\". */\n height?: number | string;\n}\n\nconst EXT_LANGUAGE: Record<string, string> = {\n ts: \"typescript\",\n tsx: \"typescript\",\n mts: \"typescript\",\n cts: \"typescript\",\n js: \"javascript\",\n jsx: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n json: \"json\",\n css: \"css\",\n scss: \"scss\",\n less: \"less\",\n html: \"html\",\n md: \"markdown\",\n py: \"python\",\n sql: \"sql\",\n yml: \"yaml\",\n yaml: \"yaml\",\n xml: \"xml\",\n sh: \"shell\",\n};\n\nconst basename = (path: string) => path.split(\"/\").pop() || path;\n\nfunction inferLanguage(file: EditorFile): string {\n if (file.language) return file.language;\n const ext = file.path.split(\".\").pop()?.toLowerCase() ?? \"\";\n return EXT_LANGUAGE[ext] ?? \"plaintext\";\n}\n\n/**\n * Imperative handle exposed via `CodeWorkspace`'s `ref`.\n *\n * **Migration note:** the forwarded `ref` type changed from `HTMLDivElement` to\n * `CodeWorkspaceHandle` (mirrors the #273 `MarkdownWorkspaceHandle` precedent).\n * Replace any `ref.current` / `ref.current.scrollIntoView()` DOM access with\n * `ref.current?.getElement()`. This is the only breaking change in this work.\n */\nexport interface CodeWorkspaceHandle extends EditorContentAccess {\n /**\n * The live Monaco editor for the currently active tab, or `null` while booting\n * or when no files are open. Consumers needing the full Monaco API can use this\n * and pass it to `monacoContentAccess(handle.getActiveEditor()!)`.\n */\n getActiveEditor(): MonacoCodeEditor | null;\n /**\n * The workspace root DOM element. Preserves the old `HTMLDivElement` ref access\n * that existed before the ref-type change (#273 `MarkdownWorkspace` playbook).\n */\n getElement(): HTMLDivElement | null;\n}\n\n/** No-op EditorContentAccess returned when no Monaco editor is mounted. */\nconst NULL_ACCESS: EditorContentAccess = {\n getText: () => \"\",\n getSelection: () => ({ text: \"\", empty: true }),\n replaceSelection: () => undefined,\n insertAtCursor: () => undefined,\n focus: () => undefined,\n onSelectionChange: () => () => undefined,\n};\n\n/**\n * Multi-file editor shell: brand-ui `Tabs` for the file bar above a single\n * {@link CodeEditor} that swaps to the active file. This is the \"reuse our\n * components for the chrome\" surface — Tabs/Button are ours; the editing surface\n * is Monaco. Edits bubble through `onFileChange`.\n *\n * The forwarded `ref` exposes a {@link CodeWorkspaceHandle} (not `HTMLDivElement`).\n * Use `ref.current?.getElement()` to access the root DOM node.\n */\nexport const CodeWorkspace = forwardRef<CodeWorkspaceHandle, CodeWorkspaceProps>(\n function CodeWorkspace(\n {\n files,\n activePath,\n defaultActivePath,\n onActivePathChange,\n onFileChange,\n readOnly = false,\n height = \"100%\",\n className,\n style,\n ...props\n },\n ref,\n ) {\n const [internalActive, setInternalActive] = useState(defaultActivePath ?? files[0]?.path ?? \"\");\n const isControlled = activePath !== undefined;\n const requested = isControlled ? activePath : internalActive;\n const activeFile = files.find((f) => f.path === requested) ?? files[0];\n const active = activeFile?.path ?? \"\";\n\n // The root div ref (getElement() preserves old HTMLDivElement access).\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n // The live Monaco editor for the active tab. Nulled when there is no active file.\n const [activeEditor, setActiveEditor] = useState<MonacoCodeEditor | null>(null);\n\n // Defensive: if the active file is removed (no `activeFile`), null the held instance\n // so getActiveEditor() and content-access methods don't act on a disposed editor.\n useEffect(() => {\n if (!activeFile) setActiveEditor(null);\n }, [activeFile]);\n\n // A STABLE listener set for onSelectionChange. The handle adds/removes here;\n // the binding effect below forwards the ACTIVE editor's selection changes into\n // it. This is what makes a subscribe-in-mount-effect survive the editor's async\n // mount (and tab swaps): the consumer subscribes once; binding re-attaches as\n // `activeEditor` changes. (Delegating onSelectionChange to a call-time snapshot\n // would bind to the not-yet-mounted editor and silently never fire.)\n const [selectionListeners] = useState(() => new Set<(sel: EditorSelection) => void>());\n useEffect(() => {\n if (!activeEditor) return;\n const unsub = monacoContentAccess(activeEditor).onSelectionChange((sel) =>\n selectionListeners.forEach((l) => l(sel)),\n );\n return unsub;\n }, [activeEditor, selectionListeners]);\n\n const selectTab = useCallback(\n (path: string) => {\n if (!isControlled) setInternalActive(path);\n onActivePathChange?.(path);\n },\n [isControlled, onActivePathChange],\n );\n\n useImperativeHandle(ref, () => {\n const access = activeEditor ? monacoContentAccess(activeEditor) : NULL_ACCESS;\n return {\n getActiveEditor: () => activeEditor,\n getElement: () => rootRef.current,\n getText: () => access.getText(),\n getSelection: () => access.getSelection(),\n replaceSelection: (text: string) => access.replaceSelection(text),\n insertAtCursor: (text: string) => access.insertAtCursor(text),\n focus: () => access.focus(),\n // Stable set (not the call-time `access`) so a subscription survives the\n // editor's async mount + tab swaps; the binding effect forwards events.\n onSelectionChange: (listener) => {\n selectionListeners.add(listener);\n return () => selectionListeners.delete(listener);\n },\n };\n }, [activeEditor, selectionListeners]);\n\n const resolvedStyle: CSSProperties = {\n height: typeof height === \"number\" ? `${height}px` : height,\n ...style,\n };\n\n return (\n <div\n ref={rootRef}\n className={cn(\n \"flex flex-col overflow-hidden rounded-lg border border-border bg-surface\",\n className,\n )}\n style={resolvedStyle}\n {...props}\n >\n <Tabs\n value={active}\n onValueChange={selectTab}\n className=\"flex min-h-0 flex-1 flex-col gap-0\"\n >\n <div className=\"flex items-center border-b border-border bg-surface\">\n <TabsList className=\"h-9 flex-1 justify-start gap-0 rounded-none bg-transparent p-0\">\n {files.map((file) => (\n <TabsTrigger\n key={file.path}\n value={file.path}\n className=\"h-9 rounded-none border-e border-border px-3 text-xs data-[state=active]:bg-background data-[state=active]:shadow-none\"\n >\n {basename(file.path)}\n </TabsTrigger>\n ))}\n </TabsList>\n {activeFile ? <CopyButton value={activeFile.value} className=\"me-1.5\" /> : null}\n </div>\n\n <div className=\"min-h-0 flex-1\">\n {activeFile ? (\n <CodeEditor\n key={activeFile.path}\n path={activeFile.path}\n language={inferLanguage(activeFile)}\n value={activeFile.value}\n readOnly={readOnly}\n onChange={(next) => onFileChange?.(activeFile.path, next)}\n onMount={(editor) => setActiveEditor(editor)}\n height=\"100%\"\n />\n ) : null}\n </div>\n </Tabs>\n </div>\n );\n },\n);\n","\"use client\";\n\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { CopyButton } from \"../copy-button\";\nimport { EDITOR_LANGUAGES, type EditorLanguage } from \"../lib/languages\";\n\nexport interface EditorToolbarProps extends HTMLAttributes<HTMLDivElement> {\n /** Filename shown on the left (e.g. \"index.ts\"). */\n filename?: ReactNode;\n /** Active language id; shows a language picker when `onLanguageChange` is set. */\n language?: string;\n /** Called when the user picks a language. Omit to hide the picker. */\n onLanguageChange?: (language: string) => void;\n /** Languages offered in the picker. Defaults to {@link EDITOR_LANGUAGES}. */\n languages?: EditorLanguage[];\n /** Text copied by the copy button. Omit to hide the button. */\n copyValue?: string;\n /** Extra actions rendered before the copy button (brand-ui buttons, etc.). */\n actions?: ReactNode;\n}\n\n/**\n * Brand-ui chrome for the editors — the part that genuinely reuses our\n * components. Renders a filename label, an optional language `Select` and a copy\n * `Button`. The editing surface itself is Monaco; this is the frame around it.\n */\nexport const EditorToolbar = forwardRef<HTMLDivElement, EditorToolbarProps>(function EditorToolbar(\n {\n filename,\n language,\n onLanguageChange,\n languages = EDITOR_LANGUAGES,\n copyValue,\n actions,\n className,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(\n \"flex h-10 shrink-0 items-center gap-2 border-b border-border bg-surface px-3\",\n className,\n )}\n {...props}\n >\n {filename != null ? (\n <span className=\"truncate text-sm font-medium text-foreground\">{filename}</span>\n ) : null}\n\n <div className=\"ml-auto flex items-center gap-1.5\">\n {actions}\n\n {onLanguageChange ? (\n <Select value={language} onValueChange={onLanguageChange}>\n <SelectTrigger size=\"sm\" className=\"h-7 w-[140px]\" aria-label=\"Language\">\n <SelectValue placeholder=\"Language\" />\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l.id} value={l.id}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : null}\n\n {copyValue != null ? <CopyButton value={copyValue} /> : null}\n </div>\n </div>\n );\n});\n","/**\n * Curated language list for the editor toolbar's language picker. Ids are\n * Monaco's built-in language ids (`monaco.languages.getLanguages()`), so they\n * map straight onto `setModelLanguage` / model creation.\n */\nexport interface EditorLanguage {\n /** Monaco language id (e.g. \"typescript\"). */\n id: string;\n /** Human-readable label for the picker. */\n label: string;\n}\n\nexport const EDITOR_LANGUAGES: EditorLanguage[] = [\n { id: \"typescript\", label: \"TypeScript\" },\n { id: \"javascript\", label: \"JavaScript\" },\n { id: \"json\", label: \"JSON\" },\n { id: \"html\", label: \"HTML\" },\n { id: \"css\", label: \"CSS\" },\n { id: \"scss\", label: \"SCSS\" },\n { id: \"markdown\", label: \"Markdown\" },\n { id: \"python\", label: \"Python\" },\n { id: \"sql\", label: \"SQL\" },\n { id: \"yaml\", label: \"YAML\" },\n { id: \"xml\", label: \"XML\" },\n { id: \"shell\", label: \"Shell\" },\n { id: \"plaintext\", label: \"Plain text\" },\n];\n\n/** Look up a language's label, falling back to the raw id. */\nexport function languageLabel(id: string): string {\n return EDITOR_LANGUAGES.find((l) => l.id === id)?.label ?? id;\n}\n","/**\n * @elabs-ai/components-editor — a token-themed Monaco (VS Code) editor wrapped as brand-ui\n * components.\n *\n * Monaco is a self-rendering editor engine; this package wraps it the way\n * @elabs-ai/components-flow wraps React Flow and @elabs-ai/components-data wraps TanStack. The editing\n * surface + widgets are Monaco's, recolored from the active brand theme via the\n * theming bridge; the chrome (toolbar, file tabs, copy button) is brand-ui.\n *\n * Worker setup (for completions/diagnostics) — import ONCE at the app entry:\n * import \"@elabs-ai/components-editor/monaco-environment\"; // Vite apps\n */\nexport {\n CodeEditor,\n type CodeEditorProps,\n type MonacoCodeEditor,\n type EditorAction,\n} from \"./code-editor\";\nexport { DiffEditor, type DiffEditorProps, type MonacoDiffEditor } from \"./diff-editor\";\nexport {\n MarkdownEditor,\n type MarkdownEditorProps,\n type MarkdownEditorHandle,\n type EmbedAssetFn,\n} from \"./markdown-editor\";\nexport {\n CodeWorkspace,\n type CodeWorkspaceHandle,\n type CodeWorkspaceProps,\n type EditorFile,\n} from \"./code-workspace\";\nexport {\n monacoContentAccess,\n type EditorContentAccess,\n type EditorSelection,\n} from \"./lib/editor-content-access\";\nexport { EditorToolbar, type EditorToolbarProps } from \"./editor-toolbar\";\nexport { EditorContextMenu, type EditorContextMenuProps } from \"./editor-context-menu\";\nexport { CopyButton, type CopyButtonProps } from \"./copy-button\";\n\n// Theming bridge + language helpers (advanced consumers / custom chrome).\nexport { applyBrandTheme, buildBrandThemeData, brandThemeId } from \"./lib/monaco-theme-bridge\";\nexport { useDataTheme, type DataThemeState } from \"./lib/use-data-theme\";\nexport { EDITOR_LANGUAGES, languageLabel, type EditorLanguage } from \"./lib/languages\";\n\n// Re-export the monaco namespace so consumers can build custom editors / wire\n// commands without adding a direct dependency.\nexport * as monaco from \"monaco-editor\";\n"],"mappings":"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","names":["DiffEditor","editor","cn","forwardRef","useEffect","useImperativeHandle","useRef","useState","jsx","forwardRef","CodeWorkspace","useState","useRef","useEffect","useImperativeHandle","cn","editor","cn","forwardRef","jsx","jsxs","forwardRef","EditorToolbar","cn","monaco"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/lib/monaco-environment.ts
|
|
2
|
+
import EditorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
|
|
3
|
+
import JsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
|
|
4
|
+
import CssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
|
|
5
|
+
import HtmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
|
|
6
|
+
import TsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
|
|
7
|
+
var policyCache = /* @__PURE__ */ new Map();
|
|
8
|
+
var monacoEnvironment = {
|
|
9
|
+
createTrustedTypesPolicy(policyName, policyOptions) {
|
|
10
|
+
if (policyCache.has(policyName)) return policyCache.get(policyName);
|
|
11
|
+
let policy;
|
|
12
|
+
try {
|
|
13
|
+
policy = globalThis.trustedTypes?.createPolicy(
|
|
14
|
+
policyName,
|
|
15
|
+
policyOptions
|
|
16
|
+
);
|
|
17
|
+
} catch {
|
|
18
|
+
policy = void 0;
|
|
19
|
+
}
|
|
20
|
+
policyCache.set(policyName, policy);
|
|
21
|
+
return policy;
|
|
22
|
+
},
|
|
23
|
+
getWorker(_workerId, label) {
|
|
24
|
+
switch (label) {
|
|
25
|
+
case "json":
|
|
26
|
+
return new JsonWorker();
|
|
27
|
+
case "css":
|
|
28
|
+
case "scss":
|
|
29
|
+
case "less":
|
|
30
|
+
return new CssWorker();
|
|
31
|
+
case "html":
|
|
32
|
+
case "handlebars":
|
|
33
|
+
case "razor":
|
|
34
|
+
return new HtmlWorker();
|
|
35
|
+
case "typescript":
|
|
36
|
+
case "javascript":
|
|
37
|
+
return new TsWorker();
|
|
38
|
+
default:
|
|
39
|
+
return new EditorWorker();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
globalThis.MonacoEnvironment = monacoEnvironment;
|
|
44
|
+
//# sourceMappingURL=monaco-environment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/monaco-environment.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/triple-slash-reference -- carry the `*?worker` ambient type into any program that imports this module (apps' tsc).\n/// <reference path=\"../monaco-workers.d.ts\" />\n/**\n * Monaco web-worker wiring for Vite apps.\n *\n * Monaco runs language intelligence (TS/JS diagnostics, JSON schema, CSS/HTML\n * services) in web workers. This module registers a `MonacoEnvironment.getWorker`\n * factory using Vite's `?worker` import suffix, which makes Vite bundle each\n * worker entry. Import it ONCE at the app entry, before any editor mounts:\n *\n * import \"@elabs-ai/components-editor/monaco-environment\";\n *\n * Without this, the editor still renders + highlights, but completions,\n * diagnostics and hovers are unavailable.\n *\n * NOTE: `?worker` is a Vite-only transform. Non-Vite consumers must provide\n * their own `self.MonacoEnvironment.getWorker(Url)` — see the package README.\n */\nimport type { Environment } from \"monaco-editor\";\nimport EditorWorker from \"monaco-editor/esm/vs/editor/editor.worker?worker\";\nimport JsonWorker from \"monaco-editor/esm/vs/language/json/json.worker?worker\";\nimport CssWorker from \"monaco-editor/esm/vs/language/css/css.worker?worker\";\nimport HtmlWorker from \"monaco-editor/esm/vs/language/html/html.worker?worker\";\nimport TsWorker from \"monaco-editor/esm/vs/language/typescript/ts.worker?worker\";\n\n/**\n * Monaco mints its Trusted-Types policies through this hook when it is present\n * (`vs/base/browser/trustedTypes.js`, and the pre-bundled copy in\n * `vs/common/workers.js`), falling back to `trustedTypes.createPolicy` when it is\n * not. We supply it to **de-duplicate by name**.\n *\n * Why: `monaco-editor@0.55` ships the \"mint `defaultWorkerFactory` at module\n * init\" block TWICE, in two different source files —\n * `vs/base/browser/webWorkerFactory.js` (reached from `editorWorkerService`) and\n * `vs/common/workers.js` (the pre-bundled `createWebWorker` API surface reached\n * from `editor.api`). Both are in the main-thread graph, so both run. No bundler\n * can dedupe them: they are distinct source files, not one module resolved twice.\n *\n * Under `require-trusted-types-for 'script'` the second call is refused —\n * \"a TrustedTypePolicy with that name already exists and the directive does not\n * contain 'allow-duplicates'\" — and Monaco reports it through\n * `onUnexpectedError`. Memoizing here fixes it at the source instead of making\n * every consumer widen their CSP with `'allow-duplicates'`, which would let ANY\n * script re-register a policy name.\n *\n * A no-op where Trusted Types is unsupported (Firefox, Safari): returns\n * `undefined`, exactly as Monaco's own fallback does, and Monaco then uses the\n * raw string. A refused name also returns `undefined` (the browser still logs the\n * CSP violation, so a missing allowlist entry stays visible) rather than throwing.\n *\n * See `docs/CSP-AND-NETWORK.md` §2.6.\n */\ntype TrustedTypesHook = NonNullable<Environment[\"createTrustedTypesPolicy\"]>;\ntype MonacoPolicy = ReturnType<TrustedTypesHook>;\ntype PolicyOptions = Parameters<TrustedTypesHook>[1];\n\n/** `lib.dom` carries no Trusted-Types types, so the factory is typed structurally. */\ninterface TrustedTypesGlobal {\n trustedTypes?: { createPolicy(name: string, options?: PolicyOptions): MonacoPolicy };\n}\n\nconst policyCache = new Map<string, MonacoPolicy>();\n\nconst monacoEnvironment: Environment = {\n createTrustedTypesPolicy(policyName, policyOptions) {\n if (policyCache.has(policyName)) return policyCache.get(policyName);\n\n let policy: MonacoPolicy;\n try {\n policy = (globalThis as unknown as TrustedTypesGlobal).trustedTypes?.createPolicy(\n policyName,\n policyOptions,\n );\n } catch {\n policy = undefined;\n }\n\n policyCache.set(policyName, policy);\n return policy;\n },\n\n getWorker(_workerId: string, label: string): Worker {\n switch (label) {\n case \"json\":\n return new JsonWorker();\n case \"css\":\n case \"scss\":\n case \"less\":\n return new CssWorker();\n case \"html\":\n case \"handlebars\":\n case \"razor\":\n return new HtmlWorker();\n case \"typescript\":\n case \"javascript\":\n return new TsWorker();\n default:\n return new EditorWorker();\n }\n },\n};\n\n(globalThis as unknown as { MonacoEnvironment: Environment }).MonacoEnvironment = monacoEnvironment;\n"],"mappings":";AAmBA,OAAO,kBAAkB;AACzB,OAAO,gBAAgB;AACvB,OAAO,eAAe;AACtB,OAAO,gBAAgB;AACvB,OAAO,cAAc;AAsCrB,IAAM,cAAc,oBAAI,IAA0B;AAElD,IAAM,oBAAiC;AAAA,EACrC,yBAAyB,YAAY,eAAe;AAClD,QAAI,YAAY,IAAI,UAAU,EAAG,QAAO,YAAY,IAAI,UAAU;AAElE,QAAI;AACJ,QAAI;AACF,eAAU,WAA6C,cAAc;AAAA,QACnE;AAAA,QACA;AAAA,MACF;AAAA,IACF,QAAQ;AACN,eAAS;AAAA,IACX;AAEA,gBAAY,IAAI,YAAY,MAAM;AAClC,WAAO;AAAA,EACT;AAAA,EAEA,UAAU,WAAmB,OAAuB;AAClD,YAAQ,OAAO;AAAA,MACb,KAAK;AACH,eAAO,IAAI,WAAW;AAAA,MACxB,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,eAAO,IAAI,UAAU;AAAA,MACvB,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,eAAO,IAAI,WAAW;AAAA,MACxB,KAAK;AAAA,MACL,KAAK;AACH,eAAO,IAAI,SAAS;AAAA,MACtB;AACE,eAAO,IAAI,aAAa;AAAA,IAC5B;AAAA,EACF;AACF;AAEC,WAA6D,oBAAoB;","names":[]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface ParsedDocument {
|
|
2
|
+
/** Parsed frontmatter object ({} when there is no frontmatter block). */
|
|
3
|
+
frontmatter: Record<string, unknown>;
|
|
4
|
+
/** Markdown body with the frontmatter block removed. */
|
|
5
|
+
content: string;
|
|
6
|
+
hasFrontmatter: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare function parseFrontmatter(source: string): ParsedDocument;
|
|
9
|
+
declare function serializeFrontmatter(frontmatter: Record<string, unknown>, content: string): string;
|
|
10
|
+
|
|
11
|
+
export { type ParsedDocument, parseFrontmatter, serializeFrontmatter };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/lib/markdown/frontmatter.ts
|
|
2
|
+
import { dump, load } from "js-yaml";
|
|
3
|
+
var FRONTMATTER_RE = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
|
|
4
|
+
function parseFrontmatter(source) {
|
|
5
|
+
const match = source.match(FRONTMATTER_RE);
|
|
6
|
+
if (!match) {
|
|
7
|
+
return { frontmatter: {}, content: source, hasFrontmatter: false };
|
|
8
|
+
}
|
|
9
|
+
const parsed = load(match[1] ?? "");
|
|
10
|
+
const frontmatter = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
11
|
+
return {
|
|
12
|
+
frontmatter,
|
|
13
|
+
content: source.slice(match[0].length),
|
|
14
|
+
hasFrontmatter: true
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function serializeFrontmatter(frontmatter, content) {
|
|
18
|
+
const body = content.replace(/^\s+/, "");
|
|
19
|
+
if (!frontmatter || Object.keys(frontmatter).length === 0) {
|
|
20
|
+
return body;
|
|
21
|
+
}
|
|
22
|
+
const yaml = dump(frontmatter, { lineWidth: -1, noRefs: true }).trimEnd();
|
|
23
|
+
return `---
|
|
24
|
+
${yaml}
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
${body}`;
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
parseFrontmatter,
|
|
31
|
+
serializeFrontmatter
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=frontmatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/markdown/frontmatter.ts"],"sourcesContent":["/**\n * YAML frontmatter parse/serialize. Splits the leading `---` block off a markdown\n * document so the body can be rendered/edited and the metadata can drive a form.\n *\n * `parseFrontmatter` throws on malformed YAML — callers (the app) catch and map it\n * to a typed error for the metadata panel.\n */\nimport { dump, load } from \"js-yaml\";\n\nexport interface ParsedDocument {\n /** Parsed frontmatter object ({} when there is no frontmatter block). */\n frontmatter: Record<string, unknown>;\n /** Markdown body with the frontmatter block removed. */\n content: string;\n hasFrontmatter: boolean;\n}\n\n// Optional leading BOM (\\uFEFF), then a `---` … `---` block at the very top.\nconst FRONTMATTER_RE = /^\\uFEFF?---[ \\t]*\\r?\\n([\\s\\S]*?)\\r?\\n---[ \\t]*(?:\\r?\\n|$)/;\n\nexport function parseFrontmatter(source: string): ParsedDocument {\n const match = source.match(FRONTMATTER_RE);\n if (!match) {\n return { frontmatter: {}, content: source, hasFrontmatter: false };\n }\n\n const parsed = load(match[1] ?? \"\");\n const frontmatter =\n parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n\n return {\n frontmatter,\n content: source.slice(match[0].length),\n hasFrontmatter: true,\n };\n}\n\nexport function serializeFrontmatter(\n frontmatter: Record<string, unknown>,\n content: string,\n): string {\n const body = content.replace(/^\\s+/, \"\");\n if (!frontmatter || Object.keys(frontmatter).length === 0) {\n return body;\n }\n const yaml = dump(frontmatter, { lineWidth: -1, noRefs: true }).trimEnd();\n return `---\\n${yaml}\\n---\\n\\n${body}`;\n}\n"],"mappings":";AAOA,SAAS,MAAM,YAAY;AAW3B,IAAM,iBAAiB;AAEhB,SAAS,iBAAiB,QAAgC;AAC/D,QAAM,QAAQ,OAAO,MAAM,cAAc;AACzC,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,aAAa,CAAC,GAAG,SAAS,QAAQ,gBAAgB,MAAM;AAAA,EACnE;AAEA,QAAM,SAAS,KAAK,MAAM,CAAC,KAAK,EAAE;AAClC,QAAM,cACJ,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,IACxD,SACD,CAAC;AAEP,SAAO;AAAA,IACL;AAAA,IACA,SAAS,OAAO,MAAM,MAAM,CAAC,EAAE,MAAM;AAAA,IACrC,gBAAgB;AAAA,EAClB;AACF;AAEO,SAAS,qBACd,aACA,SACQ;AACR,QAAM,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AACvC,MAAI,CAAC,eAAe,OAAO,KAAK,WAAW,EAAE,WAAW,GAAG;AACzD,WAAO;AAAA,EACT;AACA,QAAM,OAAO,KAAK,aAAa,EAAE,WAAW,IAAI,QAAQ,KAAK,CAAC,EAAE,QAAQ;AACxE,SAAO;AAAA,EAAQ,IAAI;AAAA;AAAA;AAAA,EAAY,IAAI;AACrC;","names":[]}
|