@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.
- package/app/dist/assets/{index-4SP4_AaD.js → index-BBinZAiy.js} +1 -1
- package/app/dist/assets/index-BD2EBUrQ.css +1 -0
- package/app/dist/assets/{index-D77rckeh.js → index-BLuaHLN3.js} +1 -1
- package/app/dist/assets/{index-30gAspk8.js → index-BgCNqcSo.js} +1 -1
- package/app/dist/assets/index-BlAOhWAQ.js +453 -0
- package/app/dist/assets/{index-BZ4adnS0.js → index-BwFn9q4x.js} +1 -1
- package/app/dist/assets/{index-DFkteo0w.js → index-C72UC2ga.js} +1 -1
- package/app/dist/assets/{index-x67KGOIr.js → index-COIPZ34u.js} +1 -1
- package/app/dist/assets/{index-BEFUVB_B.js → index-CW02bulk.js} +1 -1
- package/app/dist/assets/{index-CI5PewQM.js → index-CXFMPmtf.js} +1 -1
- package/app/dist/assets/{index-ByHhigzw.js → index-CeU_s7BB.js} +1 -1
- package/app/dist/assets/{index-DvgOtlru.js → index-CqHjo2YT.js} +1 -1
- package/app/dist/assets/{index-DKnhR16N.js → index-D3TQo8gu.js} +1 -1
- package/app/dist/assets/{index-Baf7ZSct.js → index-DVM3uoxc.js} +1 -1
- package/app/dist/assets/{index-C9w1RpYY.js → index-DW2zI-Ss.js} +1 -1
- package/app/dist/assets/{index--rGC9bba.js → index-D_Y6J00B.js} +1 -1
- package/app/dist/assets/{index-kPhFxtn-.js → index-DgIg-QAA.js} +2 -2
- package/app/dist/assets/{index-DIuFNfTc.js → index-DmY6uqAw.js} +1 -1
- package/app/dist/assets/{index-D1WOi3EN.js → index-DzHt8ZRh.js} +1 -1
- package/app/dist/assets/{index-BwWzfQVn.js → index-ZLvRNfLb.js} +1 -1
- package/app/dist/index.html +2 -2
- package/app/src/lib/api/client.ts +49 -0
- package/app/src/lib/components/ActionEditPopover.svelte +245 -0
- package/app/src/lib/components/BlockCard.svelte +255 -1
- package/app/src/lib/components/BlockEditPanel.svelte +697 -138
- package/app/src/lib/components/BlockEditor.svelte +467 -389
- package/app/src/lib/components/CodeEditPopover.svelte +226 -0
- package/app/src/lib/components/ContentModelTree.svelte +562 -0
- package/app/src/lib/components/ContentTree.svelte +181 -0
- package/app/src/lib/components/EditorLayout.svelte +1 -6
- package/app/src/lib/components/FrontmatterEditPanel.svelte +0 -1
- package/app/src/lib/components/HeaderBar.svelte +38 -0
- package/app/src/lib/components/InlineEditPopover.svelte +593 -0
- package/app/src/lib/components/InsertBlockDialog.svelte +429 -0
- package/app/src/lib/components/PageCard.svelte +3 -4
- package/app/src/lib/components/PreviewPane.svelte +19 -1
- package/app/src/lib/components/RuneAttributes.svelte +249 -100
- package/app/src/lib/editor/block-parser.ts +463 -0
- package/app/src/lib/preview/block-renderer.ts +30 -14
- package/dist/community-tags-builder.d.ts.map +1 -1
- package/dist/community-tags-builder.js +5 -1
- package/dist/community-tags-builder.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +92 -6
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
- package/preview-runtime/App.svelte +2 -0
- package/app/dist/assets/index-DlrXwdpb.css +0 -1
- 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
|
-
<
|
|
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
|
-
<
|
|
33
|
-
<span class="rune-
|
|
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-
|
|
104
|
+
<span class="rune-attrs__required">*</span>
|
|
37
105
|
{/if}
|
|
38
106
|
</span>
|
|
39
107
|
|
|
40
108
|
{#if info.type === 'Boolean'}
|
|
41
|
-
<
|
|
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
|
-
|
|
127
|
+
{value || 'default'}
|
|
49
128
|
</button>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
</select>
|
|
65
|
-
{:else if info.type === 'Number'}
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
{:else if editingAttr === name}
|
|
66
165
|
<input
|
|
67
|
-
class="rune-
|
|
68
|
-
type=
|
|
69
|
-
value={
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
</
|
|
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-
|
|
198
|
+
.rune-attrs__row {
|
|
98
199
|
display: flex;
|
|
99
|
-
|
|
100
|
-
|
|
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-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
215
|
+
white-space: nowrap;
|
|
216
|
+
flex-shrink: 0;
|
|
109
217
|
}
|
|
110
218
|
|
|
111
|
-
.rune-
|
|
219
|
+
.rune-attrs__required {
|
|
112
220
|
color: var(--ed-danger);
|
|
113
221
|
}
|
|
114
222
|
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
font-family: inherit;
|
|
248
|
+
font-style: normal;
|
|
249
|
+
font-weight: 500;
|
|
124
250
|
}
|
|
125
251
|
|
|
126
|
-
.rune-
|
|
127
|
-
|
|
128
|
-
|
|
252
|
+
.rune-attrs__value.open {
|
|
253
|
+
background: var(--ed-surface-2);
|
|
254
|
+
color: var(--ed-text-primary);
|
|
129
255
|
}
|
|
130
256
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
282
|
+
@keyframes dropdown-enter {
|
|
283
|
+
from { opacity: 0; transform: translateY(-4px); }
|
|
284
|
+
to { opacity: 1; transform: translateY(0); }
|
|
146
285
|
}
|
|
147
286
|
|
|
148
|
-
|
|
149
|
-
.rune-attr__toggle-row {
|
|
287
|
+
.rune-attrs__option {
|
|
150
288
|
display: flex;
|
|
151
289
|
align-items: center;
|
|
152
|
-
gap: 0.
|
|
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-
|
|
156
|
-
|
|
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-
|
|
168
|
-
|
|
169
|
-
|
|
306
|
+
.rune-attrs__option.selected {
|
|
307
|
+
color: var(--ed-accent);
|
|
308
|
+
font-weight: 500;
|
|
170
309
|
}
|
|
171
310
|
|
|
172
|
-
.rune-
|
|
173
|
-
|
|
174
|
-
|
|
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-
|
|
184
|
-
|
|
316
|
+
.rune-attrs__option-label--default {
|
|
317
|
+
color: var(--ed-text-muted);
|
|
318
|
+
font-style: italic;
|
|
185
319
|
}
|
|
186
320
|
|
|
187
|
-
.rune-
|
|
188
|
-
|
|
189
|
-
color: var(--ed-
|
|
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-
|
|
342
|
+
font-size: var(--ed-text-base);
|
|
194
343
|
color: var(--ed-text-muted);
|
|
195
344
|
font-style: italic;
|
|
196
345
|
}
|