@d34dman/flowdrop 0.0.57 → 0.0.59
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/README.md +9 -8
- package/dist/adapters/WorkflowAdapter.d.ts +2 -1
- package/dist/adapters/agentspec/AgentSpecAdapter.d.ts +4 -0
- package/dist/adapters/agentspec/AgentSpecAdapter.js +27 -22
- package/dist/adapters/agentspec/componentTypeDefaults.d.ts +73 -0
- package/dist/adapters/agentspec/componentTypeDefaults.js +238 -0
- package/dist/adapters/agentspec/{nodeTypeRegistry.d.ts → defaultNodeTypes.d.ts} +21 -30
- package/dist/adapters/agentspec/{nodeTypeRegistry.js → defaultNodeTypes.js} +31 -59
- package/dist/adapters/agentspec/index.d.ts +3 -1
- package/dist/adapters/agentspec/index.js +4 -2
- package/dist/components/App.svelte +57 -13
- package/dist/components/NodeSidebar.svelte +20 -8
- package/dist/components/NodeSidebar.svelte.d.ts +2 -1
- package/dist/components/WorkflowEditor.svelte +14 -13
- package/dist/components/form/FormMarkdownEditor.svelte +546 -422
- package/dist/components/form/FormMarkdownEditor.svelte.d.ts +2 -0
- package/dist/components/form/FormUISchemaRenderer.svelte +4 -8
- package/dist/components/form/types.d.ts +1 -1
- package/dist/components/nodes/WorkflowNode.svelte +1 -2
- package/dist/core/index.d.ts +13 -3
- package/dist/core/index.js +16 -3
- package/dist/form/code.js +6 -1
- package/dist/form/fieldRegistry.d.ts +79 -15
- package/dist/form/fieldRegistry.js +104 -49
- package/dist/form/full.d.ts +2 -2
- package/dist/form/full.js +2 -2
- package/dist/form/index.d.ts +3 -3
- package/dist/form/index.js +6 -2
- package/dist/form/markdown.d.ts +3 -3
- package/dist/form/markdown.js +8 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/registry/BaseRegistry.d.ts +92 -0
- package/dist/registry/BaseRegistry.js +124 -0
- package/dist/registry/builtinFormats.d.ts +23 -0
- package/dist/registry/builtinFormats.js +70 -0
- package/dist/registry/builtinNodes.js +4 -0
- package/dist/registry/index.d.ts +2 -1
- package/dist/registry/index.js +2 -0
- package/dist/registry/nodeComponentRegistry.d.ts +26 -57
- package/dist/registry/nodeComponentRegistry.js +29 -82
- package/dist/registry/workflowFormatRegistry.d.ts +122 -0
- package/dist/registry/workflowFormatRegistry.js +96 -0
- package/dist/schema/index.d.ts +23 -0
- package/dist/schema/index.js +23 -0
- package/dist/schemas/v1/workflow.schema.json +1078 -0
- package/dist/stores/portCoordinateStore.js +1 -4
- package/dist/stores/workflowStore.d.ts +3 -0
- package/dist/stores/workflowStore.js +3 -0
- package/dist/svelte-app.d.ts +4 -0
- package/dist/svelte-app.js +9 -1
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.js +4 -0
- package/package.json +20 -13
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
FormMarkdownEditor Component
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
CodeMirror 6-based Markdown editor for rich text content
|
|
4
|
+
|
|
5
5
|
Features:
|
|
6
|
-
- Full Markdown editing with
|
|
7
|
-
-
|
|
8
|
-
- Toolbar with common formatting options
|
|
9
|
-
- Autosave support (optional)
|
|
10
|
-
-
|
|
6
|
+
- Full Markdown editing with CodeMirror 6
|
|
7
|
+
- Markdown syntax highlighting via @codemirror/lang-markdown
|
|
8
|
+
- Toolbar with common formatting options + keyboard shortcuts
|
|
9
|
+
- Autosave support (optional, localStorage)
|
|
10
|
+
- Status bar with word/line/character count
|
|
11
11
|
- Consistent styling with other form components
|
|
12
12
|
- Proper ARIA attributes for accessibility
|
|
13
|
-
-
|
|
14
|
-
|
|
13
|
+
- Dark/light theme support
|
|
14
|
+
|
|
15
15
|
Usage:
|
|
16
16
|
Use with schema format: "markdown" to render this editor
|
|
17
17
|
-->
|
|
18
18
|
|
|
19
19
|
<script lang="ts">
|
|
20
20
|
import { onMount, onDestroy } from 'svelte';
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
import { EditorView, lineNumbers, drawSelection, keymap } from '@codemirror/view';
|
|
22
|
+
import { EditorState, Compartment } from '@codemirror/state';
|
|
23
|
+
import { history, historyKeymap, defaultKeymap, indentWithTab } from '@codemirror/commands';
|
|
24
|
+
import { highlightSpecialChars, highlightActiveLine } from '@codemirror/view';
|
|
25
|
+
import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language';
|
|
26
|
+
import { markdown } from '@codemirror/lang-markdown';
|
|
27
|
+
import { oneDark } from '@codemirror/theme-one-dark';
|
|
26
28
|
|
|
27
29
|
interface Props {
|
|
28
30
|
/** Field identifier */
|
|
@@ -47,6 +49,8 @@
|
|
|
47
49
|
autosaveDelay?: number;
|
|
48
50
|
/** Whether the field is disabled (read-only) */
|
|
49
51
|
disabled?: boolean;
|
|
52
|
+
/** Whether to use dark theme */
|
|
53
|
+
darkTheme?: boolean;
|
|
50
54
|
/** ARIA description ID */
|
|
51
55
|
ariaDescribedBy?: string;
|
|
52
56
|
/** Callback when value changes */
|
|
@@ -65,162 +69,406 @@
|
|
|
65
69
|
autosave = false,
|
|
66
70
|
autosaveDelay = 10000,
|
|
67
71
|
disabled = false,
|
|
72
|
+
darkTheme = false,
|
|
68
73
|
ariaDescribedBy,
|
|
69
74
|
onChange
|
|
70
75
|
}: Props = $props();
|
|
71
76
|
|
|
72
|
-
/** Reference to the
|
|
73
|
-
let
|
|
74
|
-
|
|
75
|
-
/** EasyMDE editor instance */
|
|
76
|
-
let easymde: EasyMDEInstance | undefined = $state(undefined);
|
|
77
|
+
/** Reference to the editor container element */
|
|
78
|
+
let containerRef: HTMLDivElement | undefined = $state(undefined);
|
|
77
79
|
|
|
78
|
-
/**
|
|
79
|
-
let
|
|
80
|
+
/** CodeMirror editor instance */
|
|
81
|
+
let editorView: EditorView | undefined = $state(undefined);
|
|
80
82
|
|
|
81
83
|
/** Flag to prevent update loops */
|
|
82
84
|
let isInternalUpdate = false;
|
|
83
85
|
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
86
|
+
/** Flag to skip $effect when change originated from the editor */
|
|
87
|
+
let isEditorUpdate = false;
|
|
88
|
+
|
|
89
|
+
/** Status bar stats */
|
|
90
|
+
let wordCount = $state(0);
|
|
91
|
+
let lineCount = $state(0);
|
|
92
|
+
let charCount = $state(0);
|
|
93
|
+
|
|
94
|
+
/** Autosave timer */
|
|
95
|
+
let autosaveTimer: ReturnType<typeof setTimeout> | undefined;
|
|
96
|
+
|
|
97
|
+
/** Theme compartment for dynamic theme switching */
|
|
98
|
+
const themeCompartment = new Compartment();
|
|
99
|
+
|
|
100
|
+
// ── Toolbar actions ──────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
type ToolbarAction = {
|
|
103
|
+
id: string;
|
|
104
|
+
label: string;
|
|
105
|
+
icon: string;
|
|
106
|
+
/** If true, icon is an SVG string rendered with {@html} */
|
|
107
|
+
isSvg?: boolean;
|
|
108
|
+
shortcut?: string;
|
|
109
|
+
action: () => void;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Inline SVG icons (heroicons outline, 16x16)
|
|
113
|
+
const icons = {
|
|
114
|
+
link: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path d="M12.232 4.232a2.5 2.5 0 0 1 3.536 3.536l-1.225 1.224a.75.75 0 0 0 1.061 1.06l1.224-1.224a4 4 0 0 0-5.656-5.656l-3 3a4 4 0 0 0 .225 5.865.75.75 0 0 0 .977-1.138 2.5 2.5 0 0 1-.142-3.667l3-3Z"/><path d="M11.603 7.963a.75.75 0 0 0-.977 1.138 2.5 2.5 0 0 1 .142 3.667l-3 3a2.5 2.5 0 0 1-3.536-3.536l1.225-1.224a.75.75 0 0 0-1.061-1.06l-1.224 1.224a4 4 0 1 0 5.656 5.656l3-3a4 4 0 0 0-.225-5.865Z"/></svg>',
|
|
115
|
+
image:
|
|
116
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0L2.5 11.06Zm6.5-3.31a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z" clip-rule="evenodd"/></svg>',
|
|
117
|
+
table:
|
|
118
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M.99 5.24A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm8.26 4.51v3.75h1.5v-3.75h-1.5Zm1.5-1.5v-3.75h-1.5v3.75h1.5Zm-3-3.75H3.25a.75.75 0 0 0-.75.75v3h5.25v-3.75Zm-5.25 5.25v3.75c0 .414.336.75.75.75h4.5v-4.5H2.5Zm14.5 0h-5.25v4.5h4.5a.75.75 0 0 0 .75-.75v-3.75Zm0-1.5v-3a.75.75 0 0 0-.75-.75h-4.5v3.75H17Z" clip-rule="evenodd"/></svg>'
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
function wrapSelection(before: string, after: string) {
|
|
122
|
+
if (!editorView) return;
|
|
123
|
+
const { from, to } = editorView.state.selection.main;
|
|
124
|
+
const selected = editorView.state.sliceDoc(from, to);
|
|
125
|
+
const replacement = `${before}${selected || 'text'}${after}`;
|
|
126
|
+
editorView.dispatch({
|
|
127
|
+
changes: { from, to, insert: replacement },
|
|
128
|
+
selection: {
|
|
129
|
+
anchor: selected ? from + before.length : from + before.length,
|
|
130
|
+
head: selected ? from + before.length + selected.length : from + before.length + 4
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
editorView.focus();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function prefixLine(prefix: string) {
|
|
137
|
+
if (!editorView) return;
|
|
138
|
+
const { from } = editorView.state.selection.main;
|
|
139
|
+
const line = editorView.state.doc.lineAt(from);
|
|
140
|
+
const currentText = line.text;
|
|
141
|
+
|
|
142
|
+
// If already has this prefix, remove it (toggle)
|
|
143
|
+
if (currentText.startsWith(prefix)) {
|
|
144
|
+
editorView.dispatch({
|
|
145
|
+
changes: { from: line.from, to: line.from + prefix.length, insert: '' }
|
|
146
|
+
});
|
|
147
|
+
} else {
|
|
148
|
+
// Remove any existing heading prefix before adding new one
|
|
149
|
+
const headingMatch = currentText.match(/^#{1,6}\s/);
|
|
150
|
+
const removeLen = headingMatch ? headingMatch[0].length : 0;
|
|
151
|
+
editorView.dispatch({
|
|
152
|
+
changes: { from: line.from, to: line.from + removeLen, insert: prefix }
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
editorView.focus();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function insertAtCursor(text: string) {
|
|
159
|
+
if (!editorView) return;
|
|
160
|
+
const { from, to } = editorView.state.selection.main;
|
|
161
|
+
editorView.dispatch({
|
|
162
|
+
changes: { from, to, insert: text },
|
|
163
|
+
selection: { anchor: from + text.length }
|
|
164
|
+
});
|
|
165
|
+
editorView.focus();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const toolbarActions: (ToolbarAction | '|')[] = [
|
|
169
|
+
{
|
|
170
|
+
id: 'bold',
|
|
171
|
+
label: 'Bold',
|
|
172
|
+
icon: 'B',
|
|
173
|
+
shortcut: 'Mod-b',
|
|
174
|
+
action: () => wrapSelection('**', '**')
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'italic',
|
|
178
|
+
label: 'Italic',
|
|
179
|
+
icon: 'I',
|
|
180
|
+
shortcut: 'Mod-i',
|
|
181
|
+
action: () => wrapSelection('_', '_')
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 'strikethrough',
|
|
185
|
+
label: 'Strikethrough',
|
|
186
|
+
icon: 'S',
|
|
187
|
+
action: () => wrapSelection('~~', '~~')
|
|
188
|
+
},
|
|
100
189
|
'|',
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
190
|
+
{
|
|
191
|
+
id: 'heading-1',
|
|
192
|
+
label: 'Heading 1',
|
|
193
|
+
icon: 'H1',
|
|
194
|
+
action: () => prefixLine('# ')
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'heading-2',
|
|
198
|
+
label: 'Heading 2',
|
|
199
|
+
icon: 'H2',
|
|
200
|
+
action: () => prefixLine('## ')
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'heading-3',
|
|
204
|
+
label: 'Heading 3',
|
|
205
|
+
icon: 'H3',
|
|
206
|
+
action: () => prefixLine('### ')
|
|
207
|
+
},
|
|
104
208
|
'|',
|
|
105
|
-
|
|
209
|
+
{
|
|
210
|
+
id: 'quote',
|
|
211
|
+
label: 'Quote',
|
|
212
|
+
icon: '"',
|
|
213
|
+
action: () => prefixLine('> ')
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 'unordered-list',
|
|
217
|
+
label: 'Unordered List',
|
|
218
|
+
icon: '•',
|
|
219
|
+
action: () => prefixLine('- ')
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'ordered-list',
|
|
223
|
+
label: 'Ordered List',
|
|
224
|
+
icon: '1.',
|
|
225
|
+
action: () => prefixLine('1. ')
|
|
226
|
+
},
|
|
106
227
|
'|',
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
228
|
+
{
|
|
229
|
+
id: 'link',
|
|
230
|
+
label: 'Link',
|
|
231
|
+
icon: icons.link,
|
|
232
|
+
isSvg: true,
|
|
233
|
+
shortcut: 'Mod-k',
|
|
234
|
+
action: () => {
|
|
235
|
+
if (!editorView) return;
|
|
236
|
+
const { from, to } = editorView.state.selection.main;
|
|
237
|
+
const selected = editorView.state.sliceDoc(from, to);
|
|
238
|
+
const text = selected || 'link text';
|
|
239
|
+
const replacement = `[${text}](url)`;
|
|
240
|
+
editorView.dispatch({
|
|
241
|
+
changes: { from, to, insert: replacement },
|
|
242
|
+
selection: {
|
|
243
|
+
anchor: from + text.length + 3,
|
|
244
|
+
head: from + text.length + 6
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
editorView.focus();
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: 'image',
|
|
252
|
+
label: 'Image',
|
|
253
|
+
icon: icons.image,
|
|
254
|
+
isSvg: true,
|
|
255
|
+
action: () => insertAtCursor('')
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: 'table',
|
|
259
|
+
label: 'Table',
|
|
260
|
+
icon: icons.table,
|
|
261
|
+
isSvg: true,
|
|
262
|
+
action: () =>
|
|
263
|
+
insertAtCursor('\n| Header | Header |\n| ------ | ------ |\n| Cell | Cell |\n')
|
|
118
264
|
}
|
|
265
|
+
];
|
|
119
266
|
|
|
120
|
-
|
|
121
|
-
// Dynamically import EasyMDE and its styles (client-side only)
|
|
122
|
-
const [EasyMDE] = await Promise.all([
|
|
123
|
-
import('easymde').then((m) => m.default),
|
|
124
|
-
import('easymde/dist/easymde.min.css')
|
|
125
|
-
]);
|
|
126
|
-
|
|
127
|
-
// Build autosave config if enabled
|
|
128
|
-
const autosaveConfig = autosave
|
|
129
|
-
? {
|
|
130
|
-
enabled: true,
|
|
131
|
-
uniqueId: `flowdrop-markdown-${id}`,
|
|
132
|
-
delay: autosaveDelay
|
|
133
|
-
}
|
|
134
|
-
: undefined;
|
|
135
|
-
|
|
136
|
-
// Create EasyMDE instance
|
|
137
|
-
easymde = new EasyMDE({
|
|
138
|
-
element: textareaRef,
|
|
139
|
-
initialValue: value,
|
|
140
|
-
placeholder: placeholder,
|
|
141
|
-
spellChecker: spellChecker,
|
|
142
|
-
autosave: autosaveConfig,
|
|
143
|
-
toolbar: disabled ? false : showToolbar ? [...toolbarConfig] : false,
|
|
144
|
-
status: showStatusBar,
|
|
145
|
-
forceSync: true,
|
|
146
|
-
minHeight: height,
|
|
147
|
-
renderingConfig: {
|
|
148
|
-
singleLineBreaks: false,
|
|
149
|
-
codeSyntaxHighlighting: true
|
|
150
|
-
},
|
|
151
|
-
shortcuts: disabled
|
|
152
|
-
? {}
|
|
153
|
-
: {
|
|
154
|
-
toggleBold: 'Cmd-B',
|
|
155
|
-
toggleItalic: 'Cmd-I',
|
|
156
|
-
toggleStrikethrough: 'Cmd-Alt-S',
|
|
157
|
-
toggleHeadingSmaller: 'Cmd-H',
|
|
158
|
-
toggleHeadingBigger: 'Shift-Cmd-H',
|
|
159
|
-
toggleCodeBlock: 'Cmd-Alt-C',
|
|
160
|
-
toggleBlockquote: "Cmd-'",
|
|
161
|
-
toggleOrderedList: 'Cmd-Alt-L',
|
|
162
|
-
toggleUnorderedList: 'Cmd-L',
|
|
163
|
-
cleanBlock: 'Cmd-E',
|
|
164
|
-
drawLink: 'Cmd-K',
|
|
165
|
-
drawImage: 'Cmd-Alt-I',
|
|
166
|
-
drawTable: 'Cmd-Alt-T',
|
|
167
|
-
togglePreview: 'Cmd-P',
|
|
168
|
-
toggleSideBySide: 'F9',
|
|
169
|
-
toggleFullScreen: 'F11'
|
|
170
|
-
}
|
|
171
|
-
});
|
|
267
|
+
// ── CM6 Keyboard shortcuts for toolbar actions ───────────
|
|
172
268
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
269
|
+
function createToolbarKeymap() {
|
|
270
|
+
return keymap.of([
|
|
271
|
+
{
|
|
272
|
+
key: 'Mod-b',
|
|
273
|
+
run: () => {
|
|
274
|
+
wrapSelection('**', '**');
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
key: 'Mod-i',
|
|
280
|
+
run: () => {
|
|
281
|
+
wrapSelection('_', '_');
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
key: 'Mod-k',
|
|
287
|
+
run: () => {
|
|
288
|
+
const action = toolbarActions.find((a) => a !== '|' && a.id === 'link');
|
|
289
|
+
if (action && action !== '|') action.action();
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
key: 'Mod-h',
|
|
295
|
+
run: () => {
|
|
296
|
+
prefixLine('## ');
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
key: "Mod-'",
|
|
302
|
+
run: () => {
|
|
303
|
+
prefixLine('> ');
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
key: 'Mod-l',
|
|
309
|
+
run: () => {
|
|
310
|
+
prefixLine('- ');
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
176
313
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
314
|
+
]);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ── Stats computation ────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
function updateStats(doc: { toString: () => string; lines: number }) {
|
|
320
|
+
const text = doc.toString();
|
|
321
|
+
charCount = text.length;
|
|
322
|
+
lineCount = doc.lines;
|
|
323
|
+
const trimmed = text.trim();
|
|
324
|
+
wordCount = trimmed ? trimmed.split(/\s+/).length : 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── Editor setup ─────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
function createExtensions() {
|
|
330
|
+
const extensions = [
|
|
331
|
+
lineNumbers(),
|
|
332
|
+
highlightSpecialChars(),
|
|
333
|
+
highlightActiveLine(),
|
|
334
|
+
drawSelection(),
|
|
335
|
+
|
|
336
|
+
// Editing features (skip when read-only)
|
|
337
|
+
...(disabled
|
|
338
|
+
? [EditorState.readOnly.of(true), EditorView.editable.of(false)]
|
|
339
|
+
: [
|
|
340
|
+
history(),
|
|
341
|
+
keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
|
|
342
|
+
createToolbarKeymap()
|
|
343
|
+
]),
|
|
344
|
+
|
|
345
|
+
// Theme
|
|
346
|
+
themeCompartment.of(
|
|
347
|
+
darkTheme ? oneDark : syntaxHighlighting(defaultHighlightStyle, { fallback: true })
|
|
348
|
+
),
|
|
349
|
+
|
|
350
|
+
// Markdown language support
|
|
351
|
+
markdown(),
|
|
352
|
+
|
|
353
|
+
// Update listener
|
|
354
|
+
EditorView.updateListener.of((update) => {
|
|
355
|
+
if (!update.docChanged || isInternalUpdate) return;
|
|
356
|
+
|
|
357
|
+
const content = update.state.doc.toString();
|
|
358
|
+
isEditorUpdate = true;
|
|
359
|
+
onChange(content);
|
|
360
|
+
|
|
361
|
+
updateStats(update.state.doc);
|
|
362
|
+
|
|
363
|
+
// Autosave
|
|
364
|
+
if (autosave) {
|
|
365
|
+
clearTimeout(autosaveTimer);
|
|
366
|
+
autosaveTimer = setTimeout(() => {
|
|
367
|
+
try {
|
|
368
|
+
localStorage.setItem(`flowdrop-markdown-${id}`, content);
|
|
369
|
+
} catch {
|
|
370
|
+
// localStorage may be full or unavailable
|
|
371
|
+
}
|
|
372
|
+
}, autosaveDelay);
|
|
182
373
|
}
|
|
374
|
+
}),
|
|
375
|
+
|
|
376
|
+
// Custom theme
|
|
377
|
+
EditorView.theme({
|
|
378
|
+
'&': {
|
|
379
|
+
height: height,
|
|
380
|
+
fontSize: 'var(--fd-text-sm, 0.8125rem)',
|
|
381
|
+
fontFamily: "'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace"
|
|
382
|
+
},
|
|
383
|
+
'.cm-scroller': {
|
|
384
|
+
overflow: 'auto'
|
|
385
|
+
},
|
|
386
|
+
'.cm-content': {
|
|
387
|
+
minHeight: '100px',
|
|
388
|
+
padding: '0.5rem 0'
|
|
389
|
+
},
|
|
390
|
+
'&.cm-focused': {
|
|
391
|
+
outline: 'none'
|
|
392
|
+
}
|
|
393
|
+
}),
|
|
394
|
+
EditorView.lineWrapping,
|
|
395
|
+
|
|
396
|
+
// Accessibility
|
|
397
|
+
EditorView.contentAttributes.of({
|
|
398
|
+
'aria-label': 'Markdown editor',
|
|
399
|
+
'aria-multiline': 'true'
|
|
400
|
+
})
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
return extensions;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
onMount(() => {
|
|
407
|
+
if (!containerRef) return;
|
|
408
|
+
|
|
409
|
+
// Load autosaved content if available
|
|
410
|
+
let initialContent = value;
|
|
411
|
+
if (autosave) {
|
|
412
|
+
try {
|
|
413
|
+
const saved = localStorage.getItem(`flowdrop-markdown-${id}`);
|
|
414
|
+
if (saved !== null) {
|
|
415
|
+
initialContent = saved;
|
|
416
|
+
onChange(saved);
|
|
417
|
+
}
|
|
418
|
+
} catch {
|
|
419
|
+
// localStorage unavailable
|
|
420
|
+
}
|
|
421
|
+
}
|
|
183
422
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
423
|
+
editorView = new EditorView({
|
|
424
|
+
state: EditorState.create({
|
|
425
|
+
doc: initialContent,
|
|
426
|
+
extensions: createExtensions()
|
|
427
|
+
}),
|
|
428
|
+
parent: containerRef
|
|
429
|
+
});
|
|
187
430
|
|
|
188
|
-
|
|
189
|
-
} catch (error) {
|
|
190
|
-
console.error('Failed to load EasyMDE:', error);
|
|
191
|
-
isLoading = false;
|
|
192
|
-
}
|
|
431
|
+
updateStats(editorView.state.doc);
|
|
193
432
|
});
|
|
194
433
|
|
|
195
|
-
/**
|
|
196
|
-
* Clean up editor on destroy
|
|
197
|
-
*/
|
|
198
434
|
onDestroy(() => {
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
}
|
|
435
|
+
if (autosaveTimer) clearTimeout(autosaveTimer);
|
|
436
|
+
if (editorView) editorView.destroy();
|
|
202
437
|
});
|
|
203
438
|
|
|
204
439
|
/**
|
|
205
440
|
* Update editor content when value prop changes externally
|
|
206
441
|
*/
|
|
207
442
|
$effect(() => {
|
|
208
|
-
if (!
|
|
443
|
+
if (!editorView) return;
|
|
444
|
+
|
|
445
|
+
// Skip if the change originated from the editor itself
|
|
446
|
+
if (isEditorUpdate) {
|
|
447
|
+
isEditorUpdate = false;
|
|
209
448
|
return;
|
|
210
449
|
}
|
|
211
450
|
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
// Only update if content actually changed and wasn't from internal edit
|
|
215
|
-
if (value !== currentValue && !isInternalUpdate) {
|
|
451
|
+
const currentContent = editorView.state.doc.toString();
|
|
452
|
+
if (value !== currentContent && !isInternalUpdate) {
|
|
216
453
|
isInternalUpdate = true;
|
|
217
|
-
|
|
454
|
+
editorView.dispatch({
|
|
455
|
+
changes: {
|
|
456
|
+
from: 0,
|
|
457
|
+
to: editorView.state.doc.length,
|
|
458
|
+
insert: value
|
|
459
|
+
}
|
|
460
|
+
});
|
|
218
461
|
isInternalUpdate = false;
|
|
462
|
+
updateStats(editorView.state.doc);
|
|
219
463
|
}
|
|
220
464
|
});
|
|
221
465
|
</script>
|
|
222
466
|
|
|
223
|
-
<div
|
|
467
|
+
<div
|
|
468
|
+
class="form-markdown-editor"
|
|
469
|
+
class:form-markdown-editor--dark={darkTheme}
|
|
470
|
+
style="--editor-height: {height}"
|
|
471
|
+
>
|
|
224
472
|
<!-- Hidden input for form submission compatibility -->
|
|
225
473
|
<input
|
|
226
474
|
type="hidden"
|
|
@@ -231,20 +479,47 @@
|
|
|
231
479
|
aria-required={required}
|
|
232
480
|
/>
|
|
233
481
|
|
|
234
|
-
<!--
|
|
235
|
-
{#if
|
|
236
|
-
<div class="form-markdown-
|
|
237
|
-
|
|
238
|
-
|
|
482
|
+
<!-- Toolbar -->
|
|
483
|
+
{#if showToolbar && !disabled}
|
|
484
|
+
<div class="form-markdown-editor__toolbar" role="toolbar" aria-label="Markdown formatting">
|
|
485
|
+
{#each toolbarActions as item}
|
|
486
|
+
{#if item === '|'}
|
|
487
|
+
<span class="form-markdown-editor__separator"></span>
|
|
488
|
+
{:else}
|
|
489
|
+
<button
|
|
490
|
+
type="button"
|
|
491
|
+
class="form-markdown-editor__btn"
|
|
492
|
+
title="{item.label}{item.shortcut ? ` (${item.shortcut.replace('Mod', '⌘')})` : ''}"
|
|
493
|
+
onclick={item.action}
|
|
494
|
+
>
|
|
495
|
+
{#if item.isSvg}
|
|
496
|
+
<span class="form-markdown-editor__btn-svg">{@html item.icon}</span>
|
|
497
|
+
{:else}
|
|
498
|
+
<span
|
|
499
|
+
class="form-markdown-editor__btn-icon"
|
|
500
|
+
class:form-markdown-editor__btn-icon--bold={item.id === 'bold'}
|
|
501
|
+
class:form-markdown-editor__btn-icon--italic={item.id === 'italic'}
|
|
502
|
+
class:form-markdown-editor__btn-icon--strike={item.id === 'strikethrough'}
|
|
503
|
+
>{item.icon}</span
|
|
504
|
+
>
|
|
505
|
+
{/if}
|
|
506
|
+
</button>
|
|
507
|
+
{/if}
|
|
508
|
+
{/each}
|
|
239
509
|
</div>
|
|
240
510
|
{/if}
|
|
241
511
|
|
|
242
|
-
<!--
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
512
|
+
<!-- CodeMirror container -->
|
|
513
|
+
<div bind:this={containerRef} class="form-markdown-editor__body"></div>
|
|
514
|
+
|
|
515
|
+
<!-- Status bar -->
|
|
516
|
+
{#if showStatusBar}
|
|
517
|
+
<div class="form-markdown-editor__status">
|
|
518
|
+
<span>words: {wordCount}</span>
|
|
519
|
+
<span>lines: {lineCount}</span>
|
|
520
|
+
<span>characters: {charCount}</span>
|
|
521
|
+
</div>
|
|
522
|
+
{/if}
|
|
248
523
|
</div>
|
|
249
524
|
|
|
250
525
|
<style>
|
|
@@ -253,376 +528,225 @@
|
|
|
253
528
|
width: 100%;
|
|
254
529
|
}
|
|
255
530
|
|
|
256
|
-
/*
|
|
257
|
-
|
|
531
|
+
/* ── Toolbar ───────────────────────────────────── */
|
|
532
|
+
|
|
533
|
+
.form-markdown-editor__toolbar {
|
|
258
534
|
display: flex;
|
|
259
535
|
align-items: center;
|
|
260
|
-
|
|
261
|
-
gap: 0.75rem;
|
|
262
|
-
padding: 2rem;
|
|
263
|
-
background-color: var(--fd-muted);
|
|
536
|
+
gap: 0.125rem;
|
|
264
537
|
border: 1px solid var(--fd-border);
|
|
265
|
-
border-
|
|
266
|
-
|
|
267
|
-
font-size: var(--fd-text-sm);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
.form-markdown-editor__spinner {
|
|
271
|
-
width: 1.25rem;
|
|
272
|
-
height: 1.25rem;
|
|
273
|
-
border: 2px solid var(--fd-border);
|
|
274
|
-
border-top-color: var(--fd-primary);
|
|
275
|
-
border-radius: 50%;
|
|
276
|
-
animation: spin 0.8s linear infinite;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
@keyframes spin {
|
|
280
|
-
to {
|
|
281
|
-
transform: rotate(360deg);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/* Hide the raw textarea when editor is loaded */
|
|
286
|
-
.form-markdown-editor__textarea--hidden {
|
|
287
|
-
display: none;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/* Fallback textarea styling (shown during loading or if editor fails) */
|
|
291
|
-
.form-markdown-editor textarea:not(.form-markdown-editor__textarea--hidden) {
|
|
292
|
-
width: 100%;
|
|
293
|
-
min-height: var(--editor-height, 300px);
|
|
294
|
-
padding: 0.75rem;
|
|
295
|
-
border: 1px solid var(--fd-border);
|
|
296
|
-
border-radius: var(--fd-radius-lg);
|
|
297
|
-
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace;
|
|
298
|
-
font-size: var(--fd-text-sm);
|
|
299
|
-
line-height: 1.5;
|
|
300
|
-
resize: vertical;
|
|
301
|
-
background-color: var(--fd-muted);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/* EasyMDE container styling */
|
|
305
|
-
.form-markdown-editor :global(.CodeMirror) {
|
|
306
|
-
border: 1px solid var(--fd-border);
|
|
307
|
-
border-top: none;
|
|
308
|
-
border-radius: 0;
|
|
309
|
-
background-color: var(--fd-muted);
|
|
310
|
-
color: var(--fd-foreground);
|
|
311
|
-
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace;
|
|
312
|
-
font-size: var(--fd-text-sm);
|
|
313
|
-
min-height: var(--editor-height, 300px);
|
|
314
|
-
transition: border-color var(--fd-transition-normal);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/* CodeMirror cursor styling for visibility in dark mode */
|
|
318
|
-
.form-markdown-editor :global(.CodeMirror-cursor) {
|
|
319
|
-
border-left-color: var(--fd-foreground);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/* CodeMirror selection styling */
|
|
323
|
-
.form-markdown-editor :global(.CodeMirror-selected) {
|
|
324
|
-
background-color: var(--fd-primary-muted) !important;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.form-markdown-editor :global(.CodeMirror-focused .CodeMirror-selected) {
|
|
328
|
-
background-color: var(--fd-primary-muted) !important;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/* CodeMirror line number gutter */
|
|
332
|
-
.form-markdown-editor :global(.CodeMirror-gutters) {
|
|
538
|
+
border-bottom: none;
|
|
539
|
+
border-radius: var(--fd-radius-lg) var(--fd-radius-lg) 0 0;
|
|
333
540
|
background-color: var(--fd-subtle);
|
|
334
|
-
|
|
541
|
+
padding: 0.375rem 0.5rem;
|
|
335
542
|
}
|
|
336
543
|
|
|
337
|
-
.form-markdown-
|
|
544
|
+
.form-markdown-editor__btn {
|
|
545
|
+
display: flex;
|
|
546
|
+
align-items: center;
|
|
547
|
+
justify-content: center;
|
|
548
|
+
width: 2rem;
|
|
549
|
+
height: 2rem;
|
|
550
|
+
border: none;
|
|
551
|
+
border-radius: var(--fd-radius-md);
|
|
552
|
+
background: none;
|
|
338
553
|
color: var(--fd-muted-foreground);
|
|
554
|
+
cursor: pointer;
|
|
555
|
+
font-size: 0.8125rem;
|
|
556
|
+
transition: all var(--fd-transition-fast);
|
|
339
557
|
}
|
|
340
558
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
font-size: 1.25rem;
|
|
344
|
-
line-height: 1.4;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
.form-markdown-editor :global(.cm-header-2) {
|
|
348
|
-
font-size: 1.125rem;
|
|
349
|
-
line-height: 1.4;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.form-markdown-editor :global(.cm-header-3) {
|
|
353
|
-
font-size: 1rem;
|
|
354
|
-
line-height: 1.4;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
.form-markdown-editor :global(.cm-header-4),
|
|
358
|
-
.form-markdown-editor :global(.cm-header-5),
|
|
359
|
-
.form-markdown-editor :global(.cm-header-6) {
|
|
360
|
-
font-size: 0.9375rem;
|
|
361
|
-
line-height: 1.4;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/* Keep all headers in monospace and reasonable weight */
|
|
365
|
-
.form-markdown-editor :global(.cm-header) {
|
|
366
|
-
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace;
|
|
367
|
-
font-weight: 600;
|
|
559
|
+
.form-markdown-editor__btn:hover {
|
|
560
|
+
background-color: var(--fd-border);
|
|
368
561
|
color: var(--fd-foreground);
|
|
369
562
|
}
|
|
370
563
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
color: var(--fd-muted-foreground);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
.form-markdown-editor :global(.cm-s-easymde .cm-link) {
|
|
377
|
-
color: var(--fd-primary);
|
|
564
|
+
.form-markdown-editor__btn-icon--bold {
|
|
565
|
+
font-weight: 700;
|
|
378
566
|
}
|
|
379
567
|
|
|
380
|
-
.form-markdown-
|
|
381
|
-
|
|
568
|
+
.form-markdown-editor__btn-icon--italic {
|
|
569
|
+
font-style: italic;
|
|
382
570
|
}
|
|
383
571
|
|
|
384
|
-
.form-markdown-
|
|
385
|
-
|
|
572
|
+
.form-markdown-editor__btn-icon--strike {
|
|
573
|
+
text-decoration: line-through;
|
|
386
574
|
}
|
|
387
575
|
|
|
388
|
-
.form-markdown-
|
|
389
|
-
|
|
576
|
+
.form-markdown-editor__btn-svg {
|
|
577
|
+
display: flex;
|
|
578
|
+
align-items: center;
|
|
579
|
+
justify-content: center;
|
|
390
580
|
}
|
|
391
581
|
|
|
392
|
-
.form-markdown-
|
|
393
|
-
|
|
394
|
-
|
|
582
|
+
.form-markdown-editor__separator {
|
|
583
|
+
width: 1px;
|
|
584
|
+
height: 1.25rem;
|
|
585
|
+
background-color: var(--fd-border-strong);
|
|
586
|
+
margin: 0 0.25rem;
|
|
395
587
|
}
|
|
396
588
|
|
|
397
|
-
|
|
398
|
-
color: var(--fd-foreground);
|
|
399
|
-
font-weight: 700;
|
|
400
|
-
}
|
|
589
|
+
/* ── Editor body ───────────────────────────────── */
|
|
401
590
|
|
|
402
|
-
.form-markdown-
|
|
403
|
-
|
|
404
|
-
|
|
591
|
+
.form-markdown-editor__body {
|
|
592
|
+
border: 1px solid var(--fd-border);
|
|
593
|
+
border-radius: var(--fd-radius-lg);
|
|
594
|
+
overflow: hidden;
|
|
595
|
+
background-color: var(--fd-muted);
|
|
596
|
+
transition: border-color var(--fd-transition-normal);
|
|
405
597
|
}
|
|
406
598
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
599
|
+
/* When toolbar is present, remove top radius */
|
|
600
|
+
.form-markdown-editor__toolbar + .form-markdown-editor__body {
|
|
601
|
+
border-top: none;
|
|
602
|
+
border-radius: 0;
|
|
410
603
|
}
|
|
411
604
|
|
|
412
|
-
.form-markdown-
|
|
605
|
+
.form-markdown-editor__body:hover {
|
|
413
606
|
border-color: var(--fd-border-strong);
|
|
414
607
|
}
|
|
415
608
|
|
|
416
|
-
.form-markdown-
|
|
609
|
+
.form-markdown-editor__body:focus-within {
|
|
417
610
|
border-color: var(--fd-primary);
|
|
418
611
|
background-color: var(--fd-background);
|
|
419
|
-
color: var(--fd-foreground);
|
|
420
612
|
box-shadow:
|
|
421
|
-
0 0 0 3px
|
|
613
|
+
0 0 0 3px var(--fd-primary-muted),
|
|
422
614
|
var(--fd-shadow-sm);
|
|
423
615
|
}
|
|
424
616
|
|
|
425
|
-
/*
|
|
426
|
-
.form-markdown-editor :global(.editor-toolbar) {
|
|
427
|
-
border: 1px solid var(--fd-border);
|
|
428
|
-
border-bottom: none;
|
|
429
|
-
border-radius: var(--fd-radius-lg) var(--fd-radius-lg) 0 0;
|
|
430
|
-
background-color: var(--fd-subtle);
|
|
431
|
-
padding: 0.5rem;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
.form-markdown-editor :global(.editor-toolbar::before),
|
|
435
|
-
.form-markdown-editor :global(.editor-toolbar::after) {
|
|
436
|
-
display: none;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/* Toolbar buttons */
|
|
440
|
-
.form-markdown-editor :global(.editor-toolbar button) {
|
|
441
|
-
color: var(--fd-muted-foreground);
|
|
442
|
-
border: none;
|
|
443
|
-
border-radius: var(--fd-radius-md);
|
|
444
|
-
width: 2rem;
|
|
445
|
-
height: 2rem;
|
|
446
|
-
transition: all var(--fd-transition-fast);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
.form-markdown-editor :global(.editor-toolbar button:hover) {
|
|
450
|
-
background-color: var(--fd-border);
|
|
451
|
-
color: var(--fd-foreground);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
.form-markdown-editor :global(.editor-toolbar button.active) {
|
|
455
|
-
background-color: var(--fd-primary-muted);
|
|
456
|
-
color: var(--fd-primary-hover);
|
|
457
|
-
}
|
|
617
|
+
/* ── Status bar ────────────────────────────────── */
|
|
458
618
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/* Status bar */
|
|
466
|
-
.form-markdown-editor :global(.editor-statusbar) {
|
|
619
|
+
.form-markdown-editor__status {
|
|
620
|
+
display: flex;
|
|
621
|
+
gap: 1rem;
|
|
622
|
+
justify-content: flex-end;
|
|
467
623
|
border: 1px solid var(--fd-border);
|
|
468
624
|
border-top: none;
|
|
469
625
|
border-radius: 0 0 var(--fd-radius-lg) var(--fd-radius-lg);
|
|
470
626
|
background-color: var(--fd-muted);
|
|
471
|
-
padding: 0.
|
|
627
|
+
padding: 0.375rem 0.75rem;
|
|
472
628
|
font-size: var(--fd-text-xs);
|
|
473
629
|
color: var(--fd-muted-foreground);
|
|
474
630
|
}
|
|
475
631
|
|
|
476
|
-
/*
|
|
477
|
-
.form-markdown-editor
|
|
478
|
-
|
|
479
|
-
padding: 1rem;
|
|
480
|
-
font-family: inherit;
|
|
481
|
-
font-size: var(--fd-text-sm);
|
|
482
|
-
line-height: 1.6;
|
|
483
|
-
color: var(--fd-foreground);
|
|
632
|
+
/* When no toolbar, body gets top radius */
|
|
633
|
+
.form-markdown-editor:not(:has(.form-markdown-editor__toolbar)) .form-markdown-editor__body {
|
|
634
|
+
border-radius: var(--fd-radius-lg) var(--fd-radius-lg) 0 0;
|
|
484
635
|
}
|
|
485
636
|
|
|
486
|
-
|
|
487
|
-
.form-markdown-editor
|
|
488
|
-
|
|
489
|
-
.form-markdown-editor :global(.editor-preview h4),
|
|
490
|
-
.form-markdown-editor :global(.editor-preview h5),
|
|
491
|
-
.form-markdown-editor :global(.editor-preview h6) {
|
|
492
|
-
margin-top: 1.5em;
|
|
493
|
-
margin-bottom: 0.5em;
|
|
494
|
-
font-weight: 600;
|
|
495
|
-
color: var(--fd-foreground);
|
|
637
|
+
/* When no status bar, body gets bottom radius */
|
|
638
|
+
.form-markdown-editor:not(:has(.form-markdown-editor__status)) .form-markdown-editor__body {
|
|
639
|
+
border-radius: 0 0 var(--fd-radius-lg) var(--fd-radius-lg);
|
|
496
640
|
}
|
|
497
641
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
642
|
+
/* When no toolbar AND no status bar, body gets full radius */
|
|
643
|
+
.form-markdown-editor:not(:has(.form-markdown-editor__toolbar)):not(
|
|
644
|
+
:has(.form-markdown-editor__status)
|
|
645
|
+
)
|
|
646
|
+
.form-markdown-editor__body {
|
|
647
|
+
border-radius: var(--fd-radius-lg);
|
|
502
648
|
}
|
|
503
649
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
650
|
+
/* ── CM6 overrides ─────────────────────────────── */
|
|
651
|
+
/* Design tokens (--fd-*) auto-resolve for dark mode via [data-theme='dark'] */
|
|
652
|
+
/* !important needed to override oneDark's JS-injected styles */
|
|
507
653
|
|
|
508
|
-
.form-markdown-
|
|
509
|
-
|
|
654
|
+
.form-markdown-editor__body :global(.cm-editor) {
|
|
655
|
+
height: var(--editor-height, 300px);
|
|
656
|
+
background-color: var(--fd-muted) !important;
|
|
657
|
+
color: var(--fd-foreground) !important;
|
|
510
658
|
}
|
|
511
659
|
|
|
512
|
-
.form-markdown-
|
|
513
|
-
|
|
660
|
+
.form-markdown-editor__body :global(.cm-scroller) {
|
|
661
|
+
overflow: auto;
|
|
514
662
|
}
|
|
515
663
|
|
|
516
|
-
.form-markdown-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
border-radius: var(--fd-radius-sm);
|
|
520
|
-
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', monospace;
|
|
521
|
-
font-size: 0.8125rem;
|
|
664
|
+
.form-markdown-editor__body :global(.cm-content) {
|
|
665
|
+
color: var(--fd-foreground) !important;
|
|
666
|
+
caret-color: var(--fd-foreground) !important;
|
|
522
667
|
}
|
|
523
668
|
|
|
524
|
-
.form-markdown-
|
|
525
|
-
|
|
526
|
-
background-color: var(--fd-foreground);
|
|
527
|
-
border-radius: var(--fd-radius-lg);
|
|
528
|
-
overflow-x: auto;
|
|
669
|
+
.form-markdown-editor__body :global(.cm-line) {
|
|
670
|
+
color: var(--fd-foreground) !important;
|
|
529
671
|
}
|
|
530
672
|
|
|
531
|
-
.form-markdown-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
color: var(--fd-subtle);
|
|
673
|
+
.form-markdown-editor__body :global(.cm-gutters) {
|
|
674
|
+
background-color: var(--fd-subtle) !important;
|
|
675
|
+
border-right: 1px solid var(--fd-border);
|
|
535
676
|
}
|
|
536
677
|
|
|
537
|
-
.form-markdown-
|
|
538
|
-
|
|
539
|
-
padding: 0.5rem 1rem;
|
|
540
|
-
border-left: 4px solid var(--fd-primary);
|
|
541
|
-
background-color: var(--fd-primary-muted);
|
|
542
|
-
color: var(--fd-foreground);
|
|
678
|
+
.form-markdown-editor__body :global(.cm-linenumber) {
|
|
679
|
+
color: var(--fd-muted-foreground) !important;
|
|
543
680
|
}
|
|
544
681
|
|
|
545
|
-
.form-markdown-
|
|
546
|
-
|
|
547
|
-
margin: 0.75em 0;
|
|
548
|
-
padding-left: 1.5rem;
|
|
682
|
+
.form-markdown-editor__body :global(.cm-cursor) {
|
|
683
|
+
border-left-color: var(--fd-muted-foreground) !important;
|
|
549
684
|
}
|
|
550
685
|
|
|
551
|
-
.form-markdown-
|
|
552
|
-
|
|
686
|
+
.form-markdown-editor__body :global(.cm-activeLine) {
|
|
687
|
+
background-color: var(--fd-subtle) !important;
|
|
553
688
|
}
|
|
554
689
|
|
|
555
|
-
.form-markdown-
|
|
556
|
-
color: var(--fd-
|
|
557
|
-
text-decoration: underline;
|
|
690
|
+
.form-markdown-editor__body :global(.cm-activeLineGutter) {
|
|
691
|
+
background-color: var(--fd-subtle) !important;
|
|
558
692
|
}
|
|
559
693
|
|
|
560
|
-
|
|
561
|
-
color: var(--fd-primary);
|
|
562
|
-
}
|
|
694
|
+
/* ── Markdown syntax styling ───────────────────── */
|
|
563
695
|
|
|
564
|
-
.form-markdown-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
margin: 1rem 0;
|
|
696
|
+
.form-markdown-editor__body :global(.cm-header-1) {
|
|
697
|
+
font-size: 1.25rem;
|
|
698
|
+
line-height: 1.4;
|
|
568
699
|
}
|
|
569
700
|
|
|
570
|
-
.form-markdown-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
padding: 0.5rem 0.75rem;
|
|
574
|
-
text-align: left;
|
|
701
|
+
.form-markdown-editor__body :global(.cm-header-2) {
|
|
702
|
+
font-size: 1.125rem;
|
|
703
|
+
line-height: 1.4;
|
|
575
704
|
}
|
|
576
705
|
|
|
577
|
-
.form-markdown-
|
|
578
|
-
|
|
579
|
-
|
|
706
|
+
.form-markdown-editor__body :global(.cm-header-3) {
|
|
707
|
+
font-size: 1rem;
|
|
708
|
+
line-height: 1.4;
|
|
580
709
|
}
|
|
581
710
|
|
|
582
|
-
.form-markdown-
|
|
583
|
-
|
|
584
|
-
|
|
711
|
+
.form-markdown-editor__body :global(.cm-header) {
|
|
712
|
+
font-weight: 600;
|
|
713
|
+
color: var(--fd-foreground) !important;
|
|
585
714
|
}
|
|
586
715
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
width: 50% !important;
|
|
716
|
+
.form-markdown-editor__body :global(.cm-processingInstruction) {
|
|
717
|
+
color: var(--fd-success) !important;
|
|
590
718
|
}
|
|
591
719
|
|
|
592
|
-
.form-markdown-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
border-left: none;
|
|
596
|
-
border-radius: 0 0 var(--fd-radius-lg) 0;
|
|
720
|
+
.form-markdown-editor__body :global(.cm-emphasis) {
|
|
721
|
+
color: var(--fd-foreground) !important;
|
|
722
|
+
font-style: italic;
|
|
597
723
|
}
|
|
598
724
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
725
|
+
.form-markdown-editor__body :global(.cm-strong) {
|
|
726
|
+
color: var(--fd-foreground) !important;
|
|
727
|
+
font-weight: 700;
|
|
602
728
|
}
|
|
603
729
|
|
|
604
|
-
.form-markdown-
|
|
605
|
-
|
|
730
|
+
.form-markdown-editor__body :global(.cm-strikethrough) {
|
|
731
|
+
color: var(--fd-muted-foreground) !important;
|
|
732
|
+
text-decoration: line-through;
|
|
606
733
|
}
|
|
607
734
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
color: var(--fd-muted-foreground);
|
|
611
|
-
font-style: italic;
|
|
735
|
+
.form-markdown-editor__body :global(.cm-url) {
|
|
736
|
+
color: var(--fd-primary) !important;
|
|
612
737
|
}
|
|
613
738
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
739
|
+
.form-markdown-editor__body :global(.cm-link) {
|
|
740
|
+
color: var(--fd-primary) !important;
|
|
741
|
+
text-decoration: underline;
|
|
617
742
|
}
|
|
618
743
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
border-radius: 0 0 0.5rem 0.5rem;
|
|
744
|
+
.form-markdown-editor__body :global(.cm-meta) {
|
|
745
|
+
color: var(--fd-muted-foreground) !important;
|
|
622
746
|
}
|
|
623
747
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
748
|
+
.form-markdown-editor__body :global(.cm-quote) {
|
|
749
|
+
color: var(--fd-success) !important;
|
|
750
|
+
font-style: italic;
|
|
627
751
|
}
|
|
628
752
|
</style>
|