@refrakt-md/editor 0.8.1 → 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 (39) hide show
  1. package/app/dist/assets/{index-BgCNqcSo.js → index-80NtMar1.js} +1 -1
  2. package/app/dist/assets/index-B6H6LF1M.css +1 -0
  3. package/app/dist/assets/{index-BLuaHLN3.js → index-BDj1XPol.js} +1 -1
  4. package/app/dist/assets/{index-D_Y6J00B.js → index-BXe1fKaT.js} +1 -1
  5. package/app/dist/assets/{index-ZLvRNfLb.js → index-BfxTGrHB.js} +1 -1
  6. package/app/dist/assets/{index-D3TQo8gu.js → index-Bn8ajfVl.js} +1 -1
  7. package/app/dist/assets/{index-DgIg-QAA.js → index-CCkzIGTi.js} +2 -2
  8. package/app/dist/assets/{index-COIPZ34u.js → index-CXeK-dZx.js} +1 -1
  9. package/app/dist/assets/{index-DW2zI-Ss.js → index-CaRBCHaX.js} +1 -1
  10. package/app/dist/assets/index-Cd12jZId.js +479 -0
  11. package/app/dist/assets/{index-DmY6uqAw.js → index-Cgbvx23V.js} +1 -1
  12. package/app/dist/assets/{index-CW02bulk.js → index-D5ucdUTo.js} +1 -1
  13. package/app/dist/assets/{index-BwFn9q4x.js → index-DGYxLhpR.js} +1 -1
  14. package/app/dist/assets/{index-CqHjo2YT.js → index-DNJBunzP.js} +1 -1
  15. package/app/dist/assets/{index-CeU_s7BB.js → index-DNtuldOx.js} +1 -1
  16. package/app/dist/assets/{index-C72UC2ga.js → index-DQUOY-pF.js} +1 -1
  17. package/app/dist/assets/{index-CXFMPmtf.js → index-DskvyNKT.js} +1 -1
  18. package/app/dist/assets/{index-BBinZAiy.js → index-aPeHMqUX.js} +1 -1
  19. package/app/dist/assets/{index-DVM3uoxc.js → index-dGztG-54.js} +1 -1
  20. package/app/dist/assets/{index-DzHt8ZRh.js → index-xo7v6nRB.js} +1 -1
  21. package/app/dist/index.html +2 -2
  22. package/app/src/lib/api/client.ts +32 -0
  23. package/app/src/lib/components/ActionEditPopover.svelte +41 -19
  24. package/app/src/lib/components/BlockCard.svelte +74 -17
  25. package/app/src/lib/components/BlockEditPanel.svelte +142 -9
  26. package/app/src/lib/components/BlockEditor.svelte +154 -2
  27. package/app/src/lib/components/CodeEditPopover.svelte +281 -63
  28. package/app/src/lib/components/ContentModelTree.svelte +340 -67
  29. package/app/src/lib/components/IconPickerPopover.svelte +389 -0
  30. package/app/src/lib/components/ImageEditPopover.svelte +519 -0
  31. package/app/src/lib/components/InlineEditPopover.svelte +79 -56
  32. package/app/src/lib/components/RuneAttributes.svelte +51 -0
  33. package/app/src/lib/editor/block-parser.ts +152 -7
  34. package/dist/server.d.ts.map +1 -1
  35. package/dist/server.js +129 -1
  36. package/dist/server.js.map +1 -1
  37. package/package.json +6 -6
  38. package/app/dist/assets/index-BD2EBUrQ.css +0 -1
  39. package/app/dist/assets/index-BlAOhWAQ.js +0 -453
@@ -1,38 +1,141 @@
1
1
  <script lang="ts">
2
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
+ ];
3
20
 
4
21
  interface Props {
5
22
  anchorRect: DOMRect;
6
23
  code: string;
7
24
  language: string;
8
25
  onchange: (newCode: string) => void;
26
+ onlanguagechange: (newLanguage: string) => void;
9
27
  onremove: () => void;
10
28
  onclose: () => void;
11
29
  }
12
30
 
13
- let { anchorRect, code, language, onchange, onremove, onclose }: Props = $props();
31
+ let { anchorRect, code, language, onchange, onlanguagechange, onremove, onclose }: Props = $props();
14
32
 
15
33
  let popoverEl: HTMLDivElement;
16
- let textareaEl: HTMLTextAreaElement;
34
+ let editorContainer: HTMLDivElement;
35
+ let view = $state<EditorView | undefined>(undefined);
17
36
  let left = $state(0);
18
37
  let top = $state(0);
19
38
  let showAbove = $state(true);
20
39
 
21
40
  let editCode = $state(code);
22
-
23
- onMount(() => {
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;
24
129
  const popoverRect = popoverEl.getBoundingClientRect();
25
130
  const popoverWidth = popoverRect.width;
26
131
  const popoverHeight = popoverRect.height;
27
132
  const gap = 8;
28
133
 
29
- // Horizontal: center on anchor
30
134
  let x = anchorRect.left + anchorRect.width / 2 - popoverWidth / 2;
31
135
  if (x < gap) x = gap;
32
136
  if (x + popoverWidth > window.innerWidth - gap) x = window.innerWidth - popoverWidth - gap;
33
137
  left = x;
34
138
 
35
- // Vertical: above anchor if room, else below
36
139
  const aboveY = anchorRect.top - popoverHeight - gap;
37
140
  if (aboveY >= gap) {
38
141
  top = aboveY;
@@ -41,9 +144,10 @@
41
144
  top = anchorRect.bottom + gap;
42
145
  showAbove = false;
43
146
  }
147
+ }
44
148
 
45
- textareaEl?.focus();
46
- textareaEl?.select();
149
+ onMount(() => {
150
+ recalculatePosition();
47
151
 
48
152
  function handleClickOutside(e: MouseEvent) {
49
153
  if (popoverEl && !popoverEl.contains(e.target as Node)) {
@@ -68,6 +172,90 @@
68
172
  };
69
173
  });
70
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
+
71
259
  function applyAndClose() {
72
260
  if (editCode !== code) {
73
261
  onchange(editCode);
@@ -75,12 +263,11 @@
75
263
  onclose();
76
264
  }
77
265
 
78
- function handleKeydown(e: KeyboardEvent) {
79
- // Cmd/Ctrl+Enter to apply (Enter inserts newlines in textarea)
80
- if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
81
- e.preventDefault();
82
- applyAndClose();
83
- }
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);
84
271
  }
85
272
  </script>
86
273
 
@@ -91,21 +278,27 @@
91
278
  bind:this={popoverEl}
92
279
  style="left: {left}px; top: {top}px"
93
280
  >
281
+ <!-- Titlebar -->
94
282
  <div class="code-edit-popover__header">
95
- <span class="code-edit-popover__label">command</span>
96
- {#if language}
97
- <span class="code-edit-popover__lang">{language}</span>
98
- {/if}
283
+ <span class="code-edit-popover__label">code</span>
284
+ <button class="code-edit-popover__close" onclick={onclose} aria-label="Close">&times;</button>
99
285
  </div>
100
286
 
101
- <textarea
102
- class="code-edit-popover__textarea"
103
- bind:value={editCode}
104
- bind:this={textareaEl}
105
- onkeydown={handleKeydown}
106
- rows={Math.max(2, editCode.split('\n').length)}
107
- spellcheck="false"
108
- ></textarea>
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>
109
302
 
110
303
  <div class="code-edit-popover__actions">
111
304
  <button class="code-edit-popover__btn code-edit-popover__btn--apply" onclick={applyAndClose}>Apply</button>
@@ -143,10 +336,12 @@
143
336
  to { opacity: 1; transform: scale(1) translateY(0); }
144
337
  }
145
338
 
339
+ /* ── Header ──────────────────────────────────────────── */
340
+
146
341
  .code-edit-popover__header {
147
342
  display: flex;
148
343
  align-items: center;
149
- gap: var(--ed-space-2, 0.5rem);
344
+ justify-content: space-between;
150
345
  padding: 0 var(--ed-space-1, 0.25rem);
151
346
  }
152
347
 
@@ -158,36 +353,55 @@
158
353
  letter-spacing: 0.04em;
159
354
  }
160
355
 
161
- .code-edit-popover__lang {
162
- font-size: 10px;
163
- font-weight: 500;
164
- color: var(--ed-text-tertiary, #cbd5e1);
165
- font-family: var(--ed-font-mono, 'SF Mono', 'Fira Code', monospace);
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;
166
364
  }
167
365
 
168
- .code-edit-popover__textarea {
169
- width: 100%;
170
- min-height: 3rem;
171
- max-height: 12rem;
172
- padding: var(--ed-space-2, 0.5rem);
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);
173
378
  border: 1px solid var(--ed-border-default, #e2e8f0);
174
379
  border-radius: var(--ed-radius-sm, 4px);
175
380
  font-size: var(--ed-text-sm, 13px);
176
381
  font-family: var(--ed-font-mono, 'SF Mono', 'Fira Code', monospace);
177
- color: var(--ed-text-primary, #1a1a2e);
178
- background: var(--ed-surface-1, #f8fafc);
382
+ color: var(--ed-text-secondary, #475569);
383
+ background: var(--ed-surface-0, #fff);
179
384
  outline: none;
180
- line-height: 1.5;
181
- resize: vertical;
182
- tab-size: 2;
183
- box-sizing: border-box;
385
+ cursor: pointer;
184
386
  }
185
387
 
186
- .code-edit-popover__textarea:focus {
388
+ .code-edit-popover__lang-select:focus {
187
389
  border-color: var(--ed-accent, #3b82f6);
188
390
  box-shadow: 0 0 0 2px var(--ed-accent-ring, rgba(59, 130, 246, 0.2));
189
391
  }
190
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
+
191
405
  .code-edit-popover__actions {
192
406
  display: flex;
193
407
  gap: var(--ed-space-2, 0.5rem);
@@ -195,32 +409,36 @@
195
409
  }
196
410
 
197
411
  .code-edit-popover__btn {
198
- padding: var(--ed-space-1, 0.25rem) var(--ed-space-2, 0.5rem);
199
- border: 1px solid var(--ed-border-default, #e2e8f0);
200
- border-radius: var(--ed-radius-sm, 4px);
201
- font-size: 11px;
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);
202
423
  font-weight: 500;
203
424
  cursor: pointer;
204
- transition: background 100ms, color 100ms;
425
+ transition: background var(--ed-transition-fast), color var(--ed-transition-fast), border-color var(--ed-transition-fast);
426
+ white-space: nowrap;
205
427
  }
206
428
 
207
- .code-edit-popover__btn--apply {
208
- background: var(--ed-accent, #3b82f6);
209
- border-color: var(--ed-accent, #3b82f6);
210
- color: white;
211
- }
212
-
213
- .code-edit-popover__btn--apply:hover {
214
- opacity: 0.9;
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);
215
432
  }
216
433
 
217
- .code-edit-popover__btn--remove {
218
- background: transparent;
219
- color: var(--ed-text-muted, #94a3b8);
434
+ .code-edit-popover__btn--apply {
435
+ background: var(--ed-text-primary);
436
+ color: #ffffff;
437
+ border-color: var(--ed-text-primary);
220
438
  }
221
439
 
222
- .code-edit-popover__btn--remove:hover {
223
- color: var(--ed-text-secondary, #475569);
224
- background: var(--ed-surface-2, #f1f5f9);
440
+ .code-edit-popover__btn--apply:hover:not(:disabled) {
441
+ background: var(--ed-text-secondary);
442
+ border-color: var(--ed-text-secondary);
225
443
  }
226
444
  </style>