@refrakt-md/editor 0.8.0 → 0.8.2

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.
Files changed (42) hide show
  1. package/app/dist/assets/{index-Ca-wW6uw.js → index-80NtMar1.js} +1 -1
  2. package/app/dist/assets/index-B6H6LF1M.css +1 -0
  3. package/app/dist/assets/{index-Dg4A5Pez.js → index-BDj1XPol.js} +1 -1
  4. package/app/dist/assets/{index-BfYWp0QC.js → index-BXe1fKaT.js} +1 -1
  5. package/app/dist/assets/{index-Cq0Maciq.js → index-BfxTGrHB.js} +1 -1
  6. package/app/dist/assets/{index-BsSUa0GD.js → index-Bn8ajfVl.js} +1 -1
  7. package/app/dist/assets/{index-D6vnTt4b.js → index-CCkzIGTi.js} +2 -2
  8. package/app/dist/assets/{index-BehCztSl.js → index-CXeK-dZx.js} +1 -1
  9. package/app/dist/assets/{index-iGDqoXj_.js → index-CaRBCHaX.js} +1 -1
  10. package/app/dist/assets/index-Cd12jZId.js +479 -0
  11. package/app/dist/assets/{index-D5pMhPrg.js → index-Cgbvx23V.js} +1 -1
  12. package/app/dist/assets/{index-IU6QYZAa.js → index-D5ucdUTo.js} +1 -1
  13. package/app/dist/assets/{index-CdpS6tGk.js → index-DGYxLhpR.js} +1 -1
  14. package/app/dist/assets/{index-RKEq45V5.js → index-DNJBunzP.js} +1 -1
  15. package/app/dist/assets/{index-Cgaw2jCE.js → index-DNtuldOx.js} +1 -1
  16. package/app/dist/assets/{index-BEPqnnsd.js → index-DQUOY-pF.js} +1 -1
  17. package/app/dist/assets/{index-2hOoPFOR.js → index-DskvyNKT.js} +1 -1
  18. package/app/dist/assets/{index-CLZfwYyS.js → index-aPeHMqUX.js} +1 -1
  19. package/app/dist/assets/{index-BobjskUl.js → index-dGztG-54.js} +1 -1
  20. package/app/dist/assets/{index-DHALjxX5.js → index-xo7v6nRB.js} +1 -1
  21. package/app/dist/index.html +2 -2
  22. package/app/src/lib/api/client.ts +81 -0
  23. package/app/src/lib/components/ActionEditPopover.svelte +267 -0
  24. package/app/src/lib/components/BlockCard.svelte +285 -0
  25. package/app/src/lib/components/BlockEditPanel.svelte +640 -260
  26. package/app/src/lib/components/BlockEditor.svelte +513 -52
  27. package/app/src/lib/components/CodeEditPopover.svelte +444 -0
  28. package/app/src/lib/components/ContentModelTree.svelte +835 -0
  29. package/app/src/lib/components/EditorLayout.svelte +1 -6
  30. package/app/src/lib/components/FrontmatterEditPanel.svelte +0 -1
  31. package/app/src/lib/components/IconPickerPopover.svelte +389 -0
  32. package/app/src/lib/components/ImageEditPopover.svelte +519 -0
  33. package/app/src/lib/components/InlineEditPopover.svelte +616 -0
  34. package/app/src/lib/components/RuneAttributes.svelte +51 -0
  35. package/app/src/lib/editor/block-parser.ts +424 -6
  36. package/dist/server.d.ts +1 -0
  37. package/dist/server.d.ts.map +1 -1
  38. package/dist/server.js +189 -2
  39. package/dist/server.js.map +1 -1
  40. package/package.json +6 -6
  41. package/app/dist/assets/index-98ylvoBO.css +0 -1
  42. package/app/dist/assets/index-CVzOx0nV.js +0 -372
@@ -0,0 +1,444 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { untrack } from 'svelte';
4
+ import { EditorState, Compartment } from '@codemirror/state';
5
+ import { EditorView, keymap } from '@codemirror/view';
6
+ import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
7
+ import { HighlightStyle, syntaxHighlighting, LanguageDescription, defaultHighlightStyle } from '@codemirror/language';
8
+ import { languages as languageData } from '@codemirror/language-data';
9
+ import { markdown } from '@codemirror/lang-markdown';
10
+ import { tags } from '@lezer/highlight';
11
+ import { markdocHighlight } from '../editor/markdoc-highlight.js';
12
+
13
+ const LANGUAGES = [
14
+ 'bash', 'sh', 'shell', 'javascript', 'typescript',
15
+ 'python', 'html', 'css', 'json', 'yaml', 'markdown', 'markdoc',
16
+ 'go', 'rust', 'java', 'c', 'cpp', 'ruby', 'php', 'sql', 'swift',
17
+ 'kotlin', 'dart', 'lua', 'r', 'perl', 'scala', 'haskell', 'elixir',
18
+ 'toml', 'xml', 'graphql', 'dockerfile', 'diff', 'plaintext',
19
+ ];
20
+
21
+ interface Props {
22
+ anchorRect: DOMRect;
23
+ code: string;
24
+ language: string;
25
+ onchange: (newCode: string) => void;
26
+ onlanguagechange: (newLanguage: string) => void;
27
+ onremove: () => void;
28
+ onclose: () => void;
29
+ }
30
+
31
+ let { anchorRect, code, language, onchange, onlanguagechange, onremove, onclose }: Props = $props();
32
+
33
+ let popoverEl: HTMLDivElement;
34
+ let editorContainer: HTMLDivElement;
35
+ let view = $state<EditorView | undefined>(undefined);
36
+ let left = $state(0);
37
+ let top = $state(0);
38
+ let showAbove = $state(true);
39
+
40
+ let editCode = $state(code);
41
+ let editLanguage = $state(language);
42
+
43
+ const langCompartment = new Compartment();
44
+ const markdocCompartment = new Compartment();
45
+
46
+ const codeHighlightStyle = HighlightStyle.define([
47
+ { tag: tags.keyword, color: '#8839ef' },
48
+ { tag: [tags.atom, tags.bool], color: '#fe640b' },
49
+ { tag: tags.number, color: '#fe640b' },
50
+ { tag: [tags.string, tags.special(tags.string)], color: '#40a02b' },
51
+ { tag: [tags.regexp, tags.escape], color: '#ea76cb' },
52
+ { tag: tags.definition(tags.variableName), color: '#1e66f5' },
53
+ { tag: tags.local(tags.variableName), color: '#1e66f5' },
54
+ { tag: tags.variableName, color: '#4c4f69' },
55
+ { tag: tags.definition(tags.propertyName), color: '#1e66f5' },
56
+ { tag: tags.propertyName, color: '#1e66f5' },
57
+ { tag: [tags.typeName, tags.className, tags.namespace], color: '#df8e1d' },
58
+ { tag: [tags.macroName, tags.labelName], color: '#d20f39' },
59
+ { tag: tags.function(tags.variableName), color: '#1e66f5' },
60
+ { tag: tags.operator, color: '#04a5e5' },
61
+ { tag: tags.punctuation, color: '#7c7f93' },
62
+ { tag: tags.comment, color: '#9ca0b0', fontStyle: 'italic' },
63
+ { tag: tags.lineComment, color: '#9ca0b0', fontStyle: 'italic' },
64
+ { tag: tags.blockComment, color: '#9ca0b0', fontStyle: 'italic' },
65
+ { tag: tags.meta, color: '#7c7f93' },
66
+ { tag: tags.link, color: '#1e66f5', textDecoration: 'underline' },
67
+ { tag: tags.url, color: '#1e66f5' },
68
+ { tag: tags.invalid, color: '#d20f39' },
69
+ ]);
70
+
71
+ const popoverEditorTheme = EditorView.theme(
72
+ {
73
+ '&': {
74
+ fontSize: '13px',
75
+ height: 'auto',
76
+ backgroundColor: 'var(--ed-surface-1, #f8fafc)',
77
+ border: '1px solid var(--ed-border-default, #e2e8f0)',
78
+ borderRadius: 'var(--ed-radius-sm, 4px)',
79
+ },
80
+ '&.cm-focused': {
81
+ outline: 'none',
82
+ borderColor: 'var(--ed-accent, #3b82f6)',
83
+ boxShadow: '0 0 0 2px var(--ed-accent-ring, rgba(59, 130, 246, 0.2))',
84
+ },
85
+ '.cm-scroller': {
86
+ overflow: 'auto',
87
+ maxHeight: '12rem',
88
+ fontFamily: "var(--ed-font-mono, 'SF Mono', 'Fira Code', monospace)",
89
+ lineHeight: '1.5',
90
+ },
91
+ '.cm-content': {
92
+ padding: 'var(--ed-space-2, 0.5rem)',
93
+ caretColor: '#1e293b',
94
+ minHeight: '3rem',
95
+ },
96
+ '.cm-cursor': {
97
+ borderLeftColor: '#1e293b',
98
+ },
99
+ '&.cm-focused .cm-selectionBackground, ::selection': {
100
+ backgroundColor: 'rgba(14, 165, 233, 0.2)',
101
+ },
102
+ '.cm-selectionBackground': {
103
+ backgroundColor: 'rgba(14, 165, 233, 0.12)',
104
+ },
105
+ '.cm-activeLine': {
106
+ backgroundColor: 'transparent',
107
+ },
108
+ '.cm-markdoc-tag': {
109
+ backgroundColor: 'rgba(217, 119, 6, 0.06)',
110
+ borderRadius: '2px',
111
+ },
112
+ '.cm-markdoc-bracket': {
113
+ color: '#94a3b8',
114
+ },
115
+ '.cm-markdoc-name': {
116
+ color: '#d97706',
117
+ fontWeight: '600',
118
+ },
119
+ },
120
+ { dark: false },
121
+ );
122
+
123
+ const languageOptions = $derived(
124
+ LANGUAGES.includes(editLanguage) || !editLanguage ? LANGUAGES : [editLanguage, ...LANGUAGES]
125
+ );
126
+
127
+ function recalculatePosition() {
128
+ if (!popoverEl) return;
129
+ const popoverRect = popoverEl.getBoundingClientRect();
130
+ const popoverWidth = popoverRect.width;
131
+ const popoverHeight = popoverRect.height;
132
+ const gap = 8;
133
+
134
+ let x = anchorRect.left + anchorRect.width / 2 - popoverWidth / 2;
135
+ if (x < gap) x = gap;
136
+ if (x + popoverWidth > window.innerWidth - gap) x = window.innerWidth - popoverWidth - gap;
137
+ left = x;
138
+
139
+ const aboveY = anchorRect.top - popoverHeight - gap;
140
+ if (aboveY >= gap) {
141
+ top = aboveY;
142
+ showAbove = true;
143
+ } else {
144
+ top = anchorRect.bottom + gap;
145
+ showAbove = false;
146
+ }
147
+ }
148
+
149
+ onMount(() => {
150
+ recalculatePosition();
151
+
152
+ function handleClickOutside(e: MouseEvent) {
153
+ if (popoverEl && !popoverEl.contains(e.target as Node)) {
154
+ applyAndClose();
155
+ }
156
+ }
157
+
158
+ function handleKeydown(e: KeyboardEvent) {
159
+ if (e.key === 'Escape') {
160
+ onclose();
161
+ }
162
+ }
163
+
164
+ requestAnimationFrame(() => {
165
+ document.addEventListener('mousedown', handleClickOutside);
166
+ });
167
+ document.addEventListener('keydown', handleKeydown);
168
+
169
+ return () => {
170
+ document.removeEventListener('mousedown', handleClickOutside);
171
+ document.removeEventListener('keydown', handleKeydown);
172
+ };
173
+ });
174
+
175
+ // Create CodeMirror instance
176
+ $effect(() => {
177
+ if (!editorContainer) return;
178
+
179
+ const state = EditorState.create({
180
+ doc: untrack(() => editCode),
181
+ extensions: [
182
+ langCompartment.of(markdown()),
183
+ markdocCompartment.of(untrack(() => editLanguage) === 'markdoc' ? markdocHighlight() : []),
184
+ history(),
185
+ popoverEditorTheme,
186
+ syntaxHighlighting(codeHighlightStyle),
187
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
188
+ keymap.of([
189
+ ...defaultKeymap,
190
+ ...historyKeymap,
191
+ indentWithTab,
192
+ {
193
+ key: 'Mod-Enter',
194
+ run: () => {
195
+ applyAndClose();
196
+ return true;
197
+ },
198
+ },
199
+ {
200
+ key: 'Escape',
201
+ run: () => {
202
+ onclose();
203
+ return true;
204
+ },
205
+ },
206
+ ]),
207
+ EditorView.updateListener.of((update) => {
208
+ if (update.docChanged) {
209
+ editCode = update.state.doc.toString();
210
+ }
211
+ }),
212
+ EditorView.lineWrapping,
213
+ ],
214
+ });
215
+
216
+ const editor = new EditorView({ state, parent: editorContainer });
217
+ view = editor;
218
+
219
+ editor.focus();
220
+ loadLanguage(editor, untrack(() => editLanguage));
221
+
222
+ // Recalculate position after CodeMirror renders
223
+ requestAnimationFrame(() => recalculatePosition());
224
+
225
+ return () => {
226
+ view = undefined;
227
+ editor.destroy();
228
+ };
229
+ });
230
+
231
+ function loadLanguage(editor: EditorView, lang: string | undefined) {
232
+ const name = lang?.trim();
233
+ if (!name) return;
234
+
235
+ if (name === 'markdoc') {
236
+ // Markdoc = markdown parser + markdoc tag decorations
237
+ editor.dispatch({
238
+ effects: [
239
+ langCompartment.reconfigure(markdown()),
240
+ markdocCompartment.reconfigure(markdocHighlight()),
241
+ ],
242
+ });
243
+ return;
244
+ }
245
+
246
+ // Disable markdoc decorations for other languages
247
+ editor.dispatch({
248
+ effects: markdocCompartment.reconfigure([]),
249
+ });
250
+
251
+ const desc = LanguageDescription.matchLanguageName(languageData, name, true);
252
+ if (desc) {
253
+ desc.load().then((langSupport) => {
254
+ editor.dispatch({ effects: langCompartment.reconfigure(langSupport) });
255
+ }).catch(console.error);
256
+ }
257
+ }
258
+
259
+ function applyAndClose() {
260
+ if (editCode !== code) {
261
+ onchange(editCode);
262
+ }
263
+ onclose();
264
+ }
265
+
266
+ function handleLanguageChange(e: Event) {
267
+ const select = e.target as HTMLSelectElement;
268
+ editLanguage = select.value;
269
+ onlanguagechange(editLanguage);
270
+ if (view) loadLanguage(view, editLanguage);
271
+ }
272
+ </script>
273
+
274
+ <div
275
+ class="code-edit-popover"
276
+ class:above={showAbove}
277
+ class:below={!showAbove}
278
+ bind:this={popoverEl}
279
+ style="left: {left}px; top: {top}px"
280
+ >
281
+ <!-- Titlebar -->
282
+ <div class="code-edit-popover__header">
283
+ <span class="code-edit-popover__label">code</span>
284
+ <button class="code-edit-popover__close" onclick={onclose} aria-label="Close">&times;</button>
285
+ </div>
286
+
287
+ <!-- Toolbar -->
288
+ <div class="code-edit-popover__toolbar">
289
+ <select
290
+ class="code-edit-popover__lang-select"
291
+ value={editLanguage}
292
+ onchange={handleLanguageChange}
293
+ >
294
+ <option value="">No language</option>
295
+ {#each languageOptions as lang}
296
+ <option value={lang}>{lang}</option>
297
+ {/each}
298
+ </select>
299
+ </div>
300
+
301
+ <div class="code-edit-popover__editor" bind:this={editorContainer}></div>
302
+
303
+ <div class="code-edit-popover__actions">
304
+ <button class="code-edit-popover__btn code-edit-popover__btn--apply" onclick={applyAndClose}>Apply</button>
305
+ <button class="code-edit-popover__btn code-edit-popover__btn--remove" onclick={onremove}>Remove</button>
306
+ </div>
307
+ </div>
308
+
309
+ <style>
310
+ .code-edit-popover {
311
+ position: fixed;
312
+ z-index: 1100;
313
+ display: flex;
314
+ flex-direction: column;
315
+ gap: var(--ed-space-1, 0.25rem);
316
+ background: var(--ed-surface-0, #fff);
317
+ border: 1px solid var(--ed-border-default, #e2e8f0);
318
+ border-radius: var(--ed-radius-lg, 8px);
319
+ box-shadow: var(--ed-shadow-xl, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1));
320
+ padding: var(--ed-space-2, 0.5rem);
321
+ min-width: 320px;
322
+ max-width: 480px;
323
+ animation: code-edit-enter 120ms ease-out;
324
+ }
325
+
326
+ .code-edit-popover.above {
327
+ transform-origin: bottom center;
328
+ }
329
+
330
+ .code-edit-popover.below {
331
+ transform-origin: top center;
332
+ }
333
+
334
+ @keyframes code-edit-enter {
335
+ from { opacity: 0; transform: scale(0.97) translateY(4px); }
336
+ to { opacity: 1; transform: scale(1) translateY(0); }
337
+ }
338
+
339
+ /* ── Header ──────────────────────────────────────────── */
340
+
341
+ .code-edit-popover__header {
342
+ display: flex;
343
+ align-items: center;
344
+ justify-content: space-between;
345
+ padding: 0 var(--ed-space-1, 0.25rem);
346
+ }
347
+
348
+ .code-edit-popover__label {
349
+ font-size: 10px;
350
+ font-weight: 700;
351
+ color: var(--ed-text-muted, #94a3b8);
352
+ text-transform: uppercase;
353
+ letter-spacing: 0.04em;
354
+ }
355
+
356
+ .code-edit-popover__close {
357
+ background: none;
358
+ border: none;
359
+ font-size: 18px;
360
+ line-height: 1;
361
+ color: var(--ed-text-muted, #94a3b8);
362
+ cursor: pointer;
363
+ padding: 0 4px;
364
+ }
365
+
366
+ .code-edit-popover__close:hover {
367
+ color: var(--ed-text-primary, #1a1a2e);
368
+ }
369
+
370
+ /* ── Toolbar ─────────────────────────────────────────── */
371
+
372
+ .code-edit-popover__toolbar {
373
+ padding: 0 var(--ed-space-1, 0.25rem);
374
+ }
375
+
376
+ .code-edit-popover__lang-select {
377
+ padding: var(--ed-space-1, 0.25rem) var(--ed-space-2, 0.5rem);
378
+ border: 1px solid var(--ed-border-default, #e2e8f0);
379
+ border-radius: var(--ed-radius-sm, 4px);
380
+ font-size: var(--ed-text-sm, 13px);
381
+ font-family: var(--ed-font-mono, 'SF Mono', 'Fira Code', monospace);
382
+ color: var(--ed-text-secondary, #475569);
383
+ background: var(--ed-surface-0, #fff);
384
+ outline: none;
385
+ cursor: pointer;
386
+ }
387
+
388
+ .code-edit-popover__lang-select:focus {
389
+ border-color: var(--ed-accent, #3b82f6);
390
+ box-shadow: 0 0 0 2px var(--ed-accent-ring, rgba(59, 130, 246, 0.2));
391
+ }
392
+
393
+ /* ── Editor ──────────────────────────────────────────── */
394
+
395
+ .code-edit-popover__editor {
396
+ min-height: 3rem;
397
+ }
398
+
399
+ .code-edit-popover__editor :global(.cm-gutters) {
400
+ display: none;
401
+ }
402
+
403
+ /* ── Actions ─────────────────────────────────────────── */
404
+
405
+ .code-edit-popover__actions {
406
+ display: flex;
407
+ gap: var(--ed-space-2, 0.5rem);
408
+ margin-top: var(--ed-space-1, 0.25rem);
409
+ }
410
+
411
+ .code-edit-popover__btn {
412
+ display: flex;
413
+ align-items: center;
414
+ justify-content: center;
415
+ gap: var(--ed-space-2);
416
+ height: 28px;
417
+ padding: 0 var(--ed-space-3);
418
+ border: 1px solid var(--ed-text-secondary);
419
+ border-radius: var(--ed-radius-sm);
420
+ background: transparent;
421
+ color: var(--ed-text-secondary);
422
+ font-size: var(--ed-text-sm);
423
+ font-weight: 500;
424
+ cursor: pointer;
425
+ transition: background var(--ed-transition-fast), color var(--ed-transition-fast), border-color var(--ed-transition-fast);
426
+ white-space: nowrap;
427
+ }
428
+
429
+ .code-edit-popover__btn:hover:not(.code-edit-popover__btn--apply):not(:disabled) {
430
+ border-color: var(--ed-text-primary);
431
+ color: var(--ed-text-primary);
432
+ }
433
+
434
+ .code-edit-popover__btn--apply {
435
+ background: var(--ed-text-primary);
436
+ color: #ffffff;
437
+ border-color: var(--ed-text-primary);
438
+ }
439
+
440
+ .code-edit-popover__btn--apply:hover:not(:disabled) {
441
+ background: var(--ed-text-secondary);
442
+ border-color: var(--ed-text-secondary);
443
+ }
444
+ </style>