@refrakt-md/editor 0.7.2 → 0.8.1

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 (50) hide show
  1. package/app/dist/assets/{index-4SP4_AaD.js → index-BBinZAiy.js} +1 -1
  2. package/app/dist/assets/index-BD2EBUrQ.css +1 -0
  3. package/app/dist/assets/{index-D77rckeh.js → index-BLuaHLN3.js} +1 -1
  4. package/app/dist/assets/{index-30gAspk8.js → index-BgCNqcSo.js} +1 -1
  5. package/app/dist/assets/index-BlAOhWAQ.js +453 -0
  6. package/app/dist/assets/{index-BZ4adnS0.js → index-BwFn9q4x.js} +1 -1
  7. package/app/dist/assets/{index-DFkteo0w.js → index-C72UC2ga.js} +1 -1
  8. package/app/dist/assets/{index-x67KGOIr.js → index-COIPZ34u.js} +1 -1
  9. package/app/dist/assets/{index-BEFUVB_B.js → index-CW02bulk.js} +1 -1
  10. package/app/dist/assets/{index-CI5PewQM.js → index-CXFMPmtf.js} +1 -1
  11. package/app/dist/assets/{index-ByHhigzw.js → index-CeU_s7BB.js} +1 -1
  12. package/app/dist/assets/{index-DvgOtlru.js → index-CqHjo2YT.js} +1 -1
  13. package/app/dist/assets/{index-DKnhR16N.js → index-D3TQo8gu.js} +1 -1
  14. package/app/dist/assets/{index-Baf7ZSct.js → index-DVM3uoxc.js} +1 -1
  15. package/app/dist/assets/{index-C9w1RpYY.js → index-DW2zI-Ss.js} +1 -1
  16. package/app/dist/assets/{index--rGC9bba.js → index-D_Y6J00B.js} +1 -1
  17. package/app/dist/assets/{index-kPhFxtn-.js → index-DgIg-QAA.js} +2 -2
  18. package/app/dist/assets/{index-DIuFNfTc.js → index-DmY6uqAw.js} +1 -1
  19. package/app/dist/assets/{index-D1WOi3EN.js → index-DzHt8ZRh.js} +1 -1
  20. package/app/dist/assets/{index-BwWzfQVn.js → index-ZLvRNfLb.js} +1 -1
  21. package/app/dist/index.html +2 -2
  22. package/app/src/lib/api/client.ts +49 -0
  23. package/app/src/lib/components/ActionEditPopover.svelte +245 -0
  24. package/app/src/lib/components/BlockCard.svelte +255 -1
  25. package/app/src/lib/components/BlockEditPanel.svelte +697 -138
  26. package/app/src/lib/components/BlockEditor.svelte +467 -389
  27. package/app/src/lib/components/CodeEditPopover.svelte +226 -0
  28. package/app/src/lib/components/ContentModelTree.svelte +562 -0
  29. package/app/src/lib/components/ContentTree.svelte +181 -0
  30. package/app/src/lib/components/EditorLayout.svelte +1 -6
  31. package/app/src/lib/components/FrontmatterEditPanel.svelte +0 -1
  32. package/app/src/lib/components/HeaderBar.svelte +38 -0
  33. package/app/src/lib/components/InlineEditPopover.svelte +593 -0
  34. package/app/src/lib/components/InsertBlockDialog.svelte +429 -0
  35. package/app/src/lib/components/PageCard.svelte +3 -4
  36. package/app/src/lib/components/PreviewPane.svelte +19 -1
  37. package/app/src/lib/components/RuneAttributes.svelte +249 -100
  38. package/app/src/lib/editor/block-parser.ts +463 -0
  39. package/app/src/lib/preview/block-renderer.ts +30 -14
  40. package/dist/community-tags-builder.d.ts.map +1 -1
  41. package/dist/community-tags-builder.js +5 -1
  42. package/dist/community-tags-builder.js.map +1 -1
  43. package/dist/server.d.ts +1 -0
  44. package/dist/server.d.ts.map +1 -1
  45. package/dist/server.js +92 -6
  46. package/dist/server.js.map +1 -1
  47. package/package.json +6 -6
  48. package/preview-runtime/App.svelte +2 -0
  49. package/app/dist/assets/index-DlrXwdpb.css +0 -1
  50. package/app/dist/assets/index-GlUHQ_jL.js +0 -324
@@ -9,6 +9,9 @@
9
9
 
10
10
  let { runeInfo, attributes, onchange }: Props = $props();
11
11
 
12
+ /** Attributes that only apply when layout is split or split-reverse */
13
+ const SPLIT_ONLY_ATTRS = new Set(['ratio', 'align', 'gap', 'collapse']);
14
+
12
15
  function updateAttr(name: string, value: string) {
13
16
  const next = { ...attributes };
14
17
  if (value === '' || value === undefined) {
@@ -19,67 +22,166 @@
19
22
  onchange(next);
20
23
  }
21
24
 
25
+ /** Format kebab-case attribute name for display */
26
+ function formatLabel(name: string): string {
27
+ return name.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
28
+ }
29
+
30
+ /** Check if split-only attrs should be hidden */
31
+ function isSplitOnly(name: string): boolean {
32
+ if (!SPLIT_ONLY_ATTRS.has(name)) return false;
33
+ if (!('layout' in runeInfo.attributes)) return false;
34
+ const layout = attributes['layout'] ?? '';
35
+ return layout !== 'split' && layout !== 'split-reverse';
36
+ }
37
+
22
38
  /** All schema attributes, sorted with required first */
23
39
  let sortedAttrs = $derived(
24
40
  Object.entries(runeInfo.attributes)
41
+ .filter(([name]) => !isSplitOnly(name))
25
42
  .sort(([, a], [, b]) => (a.required === b.required ? 0 : a.required ? -1 : 1))
26
43
  );
44
+
45
+ // ── Dropdown state ──────────────────────────────────────────
46
+ let openDropdown: string | null = $state(null);
47
+ let attrsEl: HTMLDivElement;
48
+
49
+ function toggleDropdown(name: string) {
50
+ openDropdown = openDropdown === name ? null : name;
51
+ }
52
+
53
+ function selectValue(name: string, value: string) {
54
+ updateAttr(name, value);
55
+ openDropdown = null;
56
+ }
57
+
58
+ function handleClickOutside(e: MouseEvent) {
59
+ if (openDropdown && attrsEl && !attrsEl.contains(e.target as Node)) {
60
+ openDropdown = null;
61
+ }
62
+ }
63
+
64
+ function handleKeydown(e: KeyboardEvent) {
65
+ if (e.key === 'Escape' && openDropdown) {
66
+ openDropdown = null;
67
+ e.stopPropagation();
68
+ }
69
+ }
70
+
71
+ // ── Inline text editing state ────────────────────────────────
72
+ let editingAttr: string | null = $state(null);
73
+ let editValue: string = $state('');
74
+
75
+ function startEditing(name: string, currentValue: string) {
76
+ editingAttr = name;
77
+ editValue = currentValue;
78
+ }
79
+
80
+ function commitEdit(name: string) {
81
+ updateAttr(name, editValue);
82
+ editingAttr = null;
83
+ }
84
+
85
+ function handleEditKeydown(e: KeyboardEvent, name: string) {
86
+ if (e.key === 'Enter') {
87
+ commitEdit(name);
88
+ } else if (e.key === 'Escape') {
89
+ editingAttr = null;
90
+ e.stopPropagation();
91
+ }
92
+ }
27
93
  </script>
28
94
 
29
- <div class="rune-attrs">
95
+ <svelte:window onmousedown={handleClickOutside} onkeydown={handleKeydown} />
96
+
97
+ <div class="rune-attrs" bind:this={attrsEl}>
30
98
  {#each sortedAttrs as [name, info] (name)}
31
99
  {@const value = attributes[name] ?? ''}
32
- <label class="rune-attr">
33
- <span class="rune-attr__label">
34
- {name}
100
+ <div class="rune-attrs__row">
101
+ <span class="rune-attrs__label">
102
+ {formatLabel(name)}
35
103
  {#if info.required}
36
- <span class="rune-attr__required">*</span>
104
+ <span class="rune-attrs__required">*</span>
37
105
  {/if}
38
106
  </span>
39
107
 
40
108
  {#if info.type === 'Boolean'}
41
- <div class="rune-attr__toggle-row">
109
+ <button
110
+ type="button"
111
+ class="rune-attrs__value"
112
+ class:active={value === 'true'}
113
+ onclick={() => updateAttr(name, value === 'true' ? '' : 'true')}
114
+ >
115
+ {value === 'true' ? 'Yes' : 'No'}
116
+ </button>
117
+
118
+ {:else if info.values && info.values.length > 0}
119
+ <div class="rune-attrs__enum">
42
120
  <button
43
- class="rune-attr__toggle"
44
- class:active={value === 'true'}
45
- onclick={() => updateAttr(name, value === 'true' ? '' : 'true')}
46
121
  type="button"
122
+ class="rune-attrs__value"
123
+ class:active={!!value}
124
+ class:open={openDropdown === name}
125
+ onclick={() => toggleDropdown(name)}
47
126
  >
48
- <span class="rune-attr__toggle-knob"></span>
127
+ {value || 'default'}
49
128
  </button>
50
- <span class="rune-attr__toggle-label">{value === 'true' ? 'Yes' : 'No'}</span>
51
- </div>
52
- {:else if info.values && info.values.length > 0}
53
- <select
54
- class="rune-attr__select"
55
- value={value}
56
- onchange={(e) => updateAttr(name, (e.target as HTMLSelectElement).value)}
57
- >
58
- {#if !info.required}
59
- <option value="">-- none --</option>
129
+
130
+ {#if openDropdown === name}
131
+ <div class="rune-attrs__dropdown">
132
+ <button
133
+ type="button"
134
+ class="rune-attrs__option"
135
+ class:selected={!value}
136
+ onclick={() => selectValue(name, '')}
137
+ >
138
+ <span class="rune-attrs__option-label rune-attrs__option-label--default">default</span>
139
+ {#if !value}
140
+ <svg class="rune-attrs__check" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
141
+ <path d="M3 8l3.5 3.5L13 5" />
142
+ </svg>
143
+ {/if}
144
+ </button>
145
+ {#each info.values as opt}
146
+ <button
147
+ type="button"
148
+ class="rune-attrs__option"
149
+ class:selected={value === opt}
150
+ onclick={() => selectValue(name, opt)}
151
+ >
152
+ <span class="rune-attrs__option-label">{opt}</span>
153
+ {#if value === opt}
154
+ <svg class="rune-attrs__check" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
155
+ <path d="M3 8l3.5 3.5L13 5" />
156
+ </svg>
157
+ {/if}
158
+ </button>
159
+ {/each}
160
+ </div>
60
161
  {/if}
61
- {#each info.values as opt}
62
- <option value={opt}>{opt}</option>
63
- {/each}
64
- </select>
65
- {:else if info.type === 'Number'}
162
+ </div>
163
+
164
+ {:else if editingAttr === name}
66
165
  <input
67
- class="rune-attr__input"
68
- type="number"
69
- value={value}
70
- oninput={(e) => updateAttr(name, (e.target as HTMLInputElement).value)}
71
- placeholder={info.required ? 'required' : ''}
166
+ class="rune-attrs__inline-input"
167
+ type={info.type === 'Number' ? 'number' : 'text'}
168
+ bind:value={editValue}
169
+ onblur={() => commitEdit(name)}
170
+ onkeydown={(e) => handleEditKeydown(e, name)}
171
+ autofocus
72
172
  />
173
+
73
174
  {:else}
74
- <input
75
- class="rune-attr__input"
76
- type="text"
77
- value={value}
78
- oninput={(e) => updateAttr(name, (e.target as HTMLInputElement).value)}
79
- placeholder={info.required ? 'required' : ''}
80
- />
175
+ <button
176
+ type="button"
177
+ class="rune-attrs__value"
178
+ class:active={!!value}
179
+ onclick={() => startEditing(name, value)}
180
+ >
181
+ {value || (info.required ? 'required' : 'default')}
182
+ </button>
81
183
  {/if}
82
- </label>
184
+ </div>
83
185
  {/each}
84
186
 
85
187
  {#if sortedAttrs.length === 0}
@@ -91,106 +193,153 @@
91
193
  .rune-attrs {
92
194
  display: flex;
93
195
  flex-direction: column;
94
- gap: 0.6rem;
95
196
  }
96
197
 
97
- .rune-attr {
198
+ .rune-attrs__row {
98
199
  display: flex;
99
- flex-direction: column;
100
- gap: var(--ed-space-1);
200
+ align-items: center;
201
+ justify-content: space-between;
202
+ gap: 0.75rem;
203
+ padding: 0.4rem 0;
204
+ border-bottom: 1px solid var(--ed-border-subtle);
205
+ position: relative;
101
206
  }
102
207
 
103
- .rune-attr__label {
104
- font-size: var(--ed-text-xs);
105
- font-weight: 600;
208
+ .rune-attrs__row:last-child {
209
+ border-bottom: none;
210
+ }
211
+
212
+ .rune-attrs__label {
213
+ font-size: var(--ed-text-base);
106
214
  color: var(--ed-text-tertiary);
107
- text-transform: uppercase;
108
- letter-spacing: 0.03em;
215
+ white-space: nowrap;
216
+ flex-shrink: 0;
109
217
  }
110
218
 
111
- .rune-attr__required {
219
+ .rune-attrs__required {
112
220
  color: var(--ed-danger);
113
221
  }
114
222
 
115
- .rune-attr__input {
116
- padding: var(--ed-space-2) var(--ed-space-3);
117
- border: 1px solid var(--ed-border-default);
118
- border-radius: var(--ed-radius-sm);
223
+ /* Clickable value button */
224
+ .rune-attrs__value {
119
225
  font-size: var(--ed-text-base);
226
+ color: var(--ed-text-muted);
227
+ font-style: italic;
228
+ background: none;
229
+ border: none;
230
+ padding: 0.15rem 0.4rem;
231
+ border-radius: var(--ed-radius-sm);
232
+ cursor: pointer;
233
+ text-align: right;
234
+ transition: background var(--ed-transition-fast), color var(--ed-transition-fast);
235
+ min-width: 0;
236
+ overflow: hidden;
237
+ text-overflow: ellipsis;
238
+ white-space: nowrap;
239
+ }
240
+
241
+ .rune-attrs__value:hover {
242
+ background: var(--ed-surface-2);
243
+ color: var(--ed-text-secondary);
244
+ }
245
+
246
+ .rune-attrs__value.active {
120
247
  color: var(--ed-text-primary);
121
- background: var(--ed-surface-0);
122
- outline: none;
123
- font-family: inherit;
248
+ font-style: normal;
249
+ font-weight: 500;
124
250
  }
125
251
 
126
- .rune-attr__input:focus {
127
- border-color: var(--ed-accent);
128
- box-shadow: 0 0 0 2px var(--ed-accent-ring);
252
+ .rune-attrs__value.open {
253
+ background: var(--ed-surface-2);
254
+ color: var(--ed-text-primary);
129
255
  }
130
256
 
131
- .rune-attr__select {
132
- padding: var(--ed-space-2) var(--ed-space-3);
257
+ /* Enum dropdown container */
258
+ .rune-attrs__enum {
259
+ position: relative;
260
+ display: flex;
261
+ justify-content: flex-end;
262
+ min-width: 0;
263
+ }
264
+
265
+ .rune-attrs__dropdown {
266
+ position: absolute;
267
+ top: calc(100% + 4px);
268
+ right: 0;
269
+ z-index: 10;
270
+ min-width: 130px;
271
+ background: var(--ed-surface-0);
133
272
  border: 1px solid var(--ed-border-default);
134
273
  border-radius: var(--ed-radius-sm);
135
- font-size: var(--ed-text-base);
136
- color: var(--ed-text-primary);
137
- background: var(--ed-surface-0);
138
- outline: none;
139
- font-family: inherit;
140
- cursor: pointer;
274
+ box-shadow: var(--ed-shadow-lg);
275
+ padding: 4px;
276
+ display: flex;
277
+ flex-direction: column;
278
+ gap: 1px;
279
+ animation: dropdown-enter 0.1s ease-out;
141
280
  }
142
281
 
143
- .rune-attr__select:focus {
144
- border-color: var(--ed-accent);
145
- box-shadow: 0 0 0 2px var(--ed-accent-ring);
282
+ @keyframes dropdown-enter {
283
+ from { opacity: 0; transform: translateY(-4px); }
284
+ to { opacity: 1; transform: translateY(0); }
146
285
  }
147
286
 
148
- /* Toggle switch */
149
- .rune-attr__toggle-row {
287
+ .rune-attrs__option {
150
288
  display: flex;
151
289
  align-items: center;
152
- gap: 0.6rem;
290
+ gap: 0.5rem;
291
+ padding: 0.3rem 0.5rem;
292
+ border: none;
293
+ border-radius: calc(var(--ed-radius-sm) - 2px);
294
+ background: transparent;
295
+ color: var(--ed-text-secondary);
296
+ font-size: var(--ed-text-base);
297
+ cursor: pointer;
298
+ white-space: nowrap;
299
+ transition: background var(--ed-transition-fast);
153
300
  }
154
301
 
155
- .rune-attr__toggle {
156
- position: relative;
157
- width: 32px;
158
- height: 18px;
159
- border-radius: 9px;
160
- border: 1px solid var(--ed-border-strong);
161
- background: var(--ed-surface-3);
162
- cursor: pointer;
163
- transition: background var(--ed-transition-fast), border-color var(--ed-transition-fast);
164
- padding: 0;
302
+ .rune-attrs__option:hover {
303
+ background: var(--ed-surface-2);
165
304
  }
166
305
 
167
- .rune-attr__toggle.active {
168
- background: var(--ed-accent);
169
- border-color: var(--ed-accent);
306
+ .rune-attrs__option.selected {
307
+ color: var(--ed-accent);
308
+ font-weight: 500;
170
309
  }
171
310
 
172
- .rune-attr__toggle-knob {
173
- position: absolute;
174
- top: 2px;
175
- left: 2px;
176
- width: 12px;
177
- height: 12px;
178
- border-radius: 50%;
179
- background: var(--ed-surface-0);
180
- transition: transform var(--ed-transition-fast);
311
+ .rune-attrs__option-label {
312
+ flex: 1;
313
+ text-align: left;
181
314
  }
182
315
 
183
- .rune-attr__toggle.active .rune-attr__toggle-knob {
184
- transform: translateX(14px);
316
+ .rune-attrs__option-label--default {
317
+ color: var(--ed-text-muted);
318
+ font-style: italic;
185
319
  }
186
320
 
187
- .rune-attr__toggle-label {
188
- font-size: var(--ed-text-sm);
189
- color: var(--ed-text-tertiary);
321
+ .rune-attrs__check {
322
+ flex-shrink: 0;
323
+ color: var(--ed-accent);
324
+ }
325
+
326
+ /* Inline text input */
327
+ .rune-attrs__inline-input {
328
+ font-size: var(--ed-text-base);
329
+ padding: 0.1rem 0.35rem;
330
+ border: 1px solid var(--ed-accent);
331
+ border-radius: var(--ed-radius-sm);
332
+ background: var(--ed-surface-0);
333
+ color: var(--ed-text-primary);
334
+ outline: none;
335
+ box-shadow: 0 0 0 2px var(--ed-accent-ring);
336
+ text-align: right;
337
+ width: 8rem;
338
+ font-family: inherit;
190
339
  }
191
340
 
192
341
  .rune-attrs__empty {
193
- font-size: var(--ed-text-sm);
342
+ font-size: var(--ed-text-base);
194
343
  color: var(--ed-text-muted);
195
344
  font-style: italic;
196
345
  }