@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.
- package/app/dist/assets/{index-BgCNqcSo.js → index-80NtMar1.js} +1 -1
- package/app/dist/assets/index-B6H6LF1M.css +1 -0
- package/app/dist/assets/{index-BLuaHLN3.js → index-BDj1XPol.js} +1 -1
- package/app/dist/assets/{index-D_Y6J00B.js → index-BXe1fKaT.js} +1 -1
- package/app/dist/assets/{index-ZLvRNfLb.js → index-BfxTGrHB.js} +1 -1
- package/app/dist/assets/{index-D3TQo8gu.js → index-Bn8ajfVl.js} +1 -1
- package/app/dist/assets/{index-DgIg-QAA.js → index-CCkzIGTi.js} +2 -2
- package/app/dist/assets/{index-COIPZ34u.js → index-CXeK-dZx.js} +1 -1
- package/app/dist/assets/{index-DW2zI-Ss.js → index-CaRBCHaX.js} +1 -1
- package/app/dist/assets/index-Cd12jZId.js +479 -0
- package/app/dist/assets/{index-DmY6uqAw.js → index-Cgbvx23V.js} +1 -1
- package/app/dist/assets/{index-CW02bulk.js → index-D5ucdUTo.js} +1 -1
- package/app/dist/assets/{index-BwFn9q4x.js → index-DGYxLhpR.js} +1 -1
- package/app/dist/assets/{index-CqHjo2YT.js → index-DNJBunzP.js} +1 -1
- package/app/dist/assets/{index-CeU_s7BB.js → index-DNtuldOx.js} +1 -1
- package/app/dist/assets/{index-C72UC2ga.js → index-DQUOY-pF.js} +1 -1
- package/app/dist/assets/{index-CXFMPmtf.js → index-DskvyNKT.js} +1 -1
- package/app/dist/assets/{index-BBinZAiy.js → index-aPeHMqUX.js} +1 -1
- package/app/dist/assets/{index-DVM3uoxc.js → index-dGztG-54.js} +1 -1
- package/app/dist/assets/{index-DzHt8ZRh.js → index-xo7v6nRB.js} +1 -1
- package/app/dist/index.html +2 -2
- package/app/src/lib/api/client.ts +32 -0
- package/app/src/lib/components/ActionEditPopover.svelte +41 -19
- package/app/src/lib/components/BlockCard.svelte +74 -17
- package/app/src/lib/components/BlockEditPanel.svelte +142 -9
- package/app/src/lib/components/BlockEditor.svelte +154 -2
- package/app/src/lib/components/CodeEditPopover.svelte +281 -63
- package/app/src/lib/components/ContentModelTree.svelte +340 -67
- package/app/src/lib/components/IconPickerPopover.svelte +389 -0
- package/app/src/lib/components/ImageEditPopover.svelte +519 -0
- package/app/src/lib/components/InlineEditPopover.svelte +79 -56
- package/app/src/lib/components/RuneAttributes.svelte +51 -0
- package/app/src/lib/editor/block-parser.ts +152 -7
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +129 -1
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
- package/app/dist/assets/index-BD2EBUrQ.css +0 -1
- package/app/dist/assets/index-BlAOhWAQ.js +0 -453
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
import type { ContentNode } from '../editor/block-parser.js';
|
|
4
4
|
import { splitListItems } from '../editor/block-parser.js';
|
|
5
5
|
|
|
6
|
+
interface PreviewItem {
|
|
7
|
+
text: string;
|
|
8
|
+
type: 'text' | 'rune';
|
|
9
|
+
/** Index within field.nodes — used for rune navigation and greedy field editing */
|
|
10
|
+
nodeIndex?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
interface Props {
|
|
7
14
|
structure: ResolvedStructure;
|
|
8
15
|
rootLabel: string;
|
|
@@ -10,7 +17,10 @@
|
|
|
10
17
|
onremovefield: (fieldName: string, zoneName?: string) => void;
|
|
11
18
|
onappenditem: (fieldName: string, zoneName?: string) => void;
|
|
12
19
|
onremovelistitem: (fieldName: string, itemIndex: number, zoneName?: string) => void;
|
|
13
|
-
|
|
20
|
+
onreorderlistitem: (fieldName: string, fromIndex: number, toIndex: number, zoneName?: string) => void;
|
|
21
|
+
oneditfield: (fieldName: string, rect: DOMRect, zoneName?: string, nodeIndex?: number) => void;
|
|
22
|
+
oneditlistitem: (fieldName: string, itemIndex: number, rect: DOMRect, zoneName?: string) => void;
|
|
23
|
+
onnavigaterune: (fieldName: string, nodeIndex: number, zoneName?: string) => void;
|
|
14
24
|
onfieldselect: (fieldName: string, zoneName?: string) => void;
|
|
15
25
|
selectedField?: string | null;
|
|
16
26
|
}
|
|
@@ -22,15 +32,84 @@
|
|
|
22
32
|
onremovefield,
|
|
23
33
|
onappenditem,
|
|
24
34
|
onremovelistitem,
|
|
35
|
+
onreorderlistitem,
|
|
25
36
|
oneditfield,
|
|
37
|
+
oneditlistitem,
|
|
38
|
+
onnavigaterune,
|
|
26
39
|
onfieldselect,
|
|
27
40
|
selectedField = null,
|
|
28
41
|
}: Props = $props();
|
|
29
42
|
|
|
43
|
+
// ── Drag and drop state ──────────────────────────────────────
|
|
44
|
+
let dragFieldName: string | null = $state(null);
|
|
45
|
+
let dragZoneName: string | undefined = $state(undefined);
|
|
46
|
+
let dragFromIndex: number | null = $state(null);
|
|
47
|
+
let dragOverIndex: number | null = $state(null);
|
|
48
|
+
|
|
49
|
+
function handleItemDragStart(e: DragEvent, fieldName: string, index: number, zoneName?: string) {
|
|
50
|
+
dragFieldName = fieldName;
|
|
51
|
+
dragZoneName = zoneName;
|
|
52
|
+
dragFromIndex = index;
|
|
53
|
+
dragOverIndex = index;
|
|
54
|
+
if (e.dataTransfer) {
|
|
55
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
56
|
+
e.dataTransfer.setData('text/plain', String(index));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function handleItemDragOver(e: DragEvent, index: number) {
|
|
61
|
+
if (dragFromIndex === null) return;
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
|
|
64
|
+
dragOverIndex = index;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function handleItemDrop(e: DragEvent, index: number) {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
if (dragFromIndex !== null && dragFromIndex !== index && dragFieldName !== null) {
|
|
70
|
+
onreorderlistitem(dragFieldName, dragFromIndex, index, dragZoneName);
|
|
71
|
+
}
|
|
72
|
+
dragFieldName = null;
|
|
73
|
+
dragZoneName = undefined;
|
|
74
|
+
dragFromIndex = null;
|
|
75
|
+
dragOverIndex = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function handleItemDragEnd() {
|
|
79
|
+
dragFieldName = null;
|
|
80
|
+
dragZoneName = undefined;
|
|
81
|
+
dragFromIndex = null;
|
|
82
|
+
dragOverIndex = null;
|
|
83
|
+
}
|
|
84
|
+
|
|
30
85
|
function isListField(match: string): boolean {
|
|
31
86
|
return match === 'list' || match.startsWith('list:');
|
|
32
87
|
}
|
|
33
88
|
|
|
89
|
+
function isGreedyItemField(field: ResolvedField): boolean {
|
|
90
|
+
return field.greedy && field.filled && field.match !== 'any';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Extract individual items from a greedy field for per-item rendering */
|
|
94
|
+
function greedyItemPreviews(field: ResolvedField): { text: string; index: number }[] {
|
|
95
|
+
return field.nodes.map((node, i) => {
|
|
96
|
+
let text: string;
|
|
97
|
+
if (node.type === 'fence') {
|
|
98
|
+
text = node.fenceLanguage ? `[${node.fenceLanguage}]` : '[code]';
|
|
99
|
+
} else if (node.type === 'heading' && node.headingText) {
|
|
100
|
+
text = node.headingText;
|
|
101
|
+
} else if (node.type === 'rune' && node.runeName) {
|
|
102
|
+
text = node.runeName;
|
|
103
|
+
} else if (node.type === 'image') {
|
|
104
|
+
text = '[image]';
|
|
105
|
+
} else {
|
|
106
|
+
const raw = node.source.replace(/\n/g, ' ').trim();
|
|
107
|
+
text = raw.length > 50 ? raw.slice(0, 47) + '...' : raw;
|
|
108
|
+
}
|
|
109
|
+
return { text: text.length > 50 ? text.slice(0, 47) + '...' : text, index: i };
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
34
113
|
/** Icon SVG path for a match type */
|
|
35
114
|
function matchIcon(match: string): string {
|
|
36
115
|
// Take first alternative for pipe-separated matches
|
|
@@ -74,20 +153,21 @@
|
|
|
74
153
|
return result;
|
|
75
154
|
}
|
|
76
155
|
|
|
77
|
-
/** Generate
|
|
78
|
-
function contentPreview(nodes: ContentNode[]):
|
|
79
|
-
const previews:
|
|
80
|
-
for (
|
|
156
|
+
/** Generate preview items for matched content */
|
|
157
|
+
function contentPreview(nodes: ContentNode[]): PreviewItem[] {
|
|
158
|
+
const previews: PreviewItem[] = [];
|
|
159
|
+
for (let idx = 0; idx < nodes.length; idx++) {
|
|
160
|
+
const node = nodes[idx];
|
|
81
161
|
if (node.type === 'heading' && node.headingText) {
|
|
82
|
-
previews.push(node.headingText);
|
|
162
|
+
previews.push({ text: node.headingText, type: 'text', nodeIndex: idx });
|
|
83
163
|
} else if (node.type === 'paragraph') {
|
|
84
164
|
const text = node.source.replace(/\n/g, ' ').trim();
|
|
85
|
-
previews.push(text.length > 60 ? text.slice(0, 57) + '...' : text);
|
|
165
|
+
previews.push({ text: text.length > 60 ? text.slice(0, 57) + '...' : text, type: 'text', nodeIndex: idx });
|
|
86
166
|
} else if (node.type === 'rune' && node.runeName) {
|
|
87
|
-
previews.push(node.runeName);
|
|
167
|
+
previews.push({ text: node.runeName, type: 'rune', nodeIndex: idx });
|
|
88
168
|
} else if (node.type === 'fence') {
|
|
89
169
|
const lang = node.fenceLanguage ? `[${node.fenceLanguage}]` : '[code]';
|
|
90
|
-
previews.push(lang);
|
|
170
|
+
previews.push({ text: lang, type: 'text', nodeIndex: idx });
|
|
91
171
|
} else if (node.type === 'list') {
|
|
92
172
|
// Show abbreviated list items from source
|
|
93
173
|
const items = node.source.split('\n')
|
|
@@ -95,20 +175,145 @@
|
|
|
95
175
|
.map(l => l.replace(/^[-*\d.]+\s+/, '').trim())
|
|
96
176
|
.filter(Boolean);
|
|
97
177
|
for (const item of items.slice(0, 3)) {
|
|
98
|
-
previews.push(item.length > 50 ? item.slice(0, 47) + '...' : item);
|
|
178
|
+
previews.push({ text: item.length > 50 ? item.slice(0, 47) + '...' : item, type: 'text' });
|
|
99
179
|
}
|
|
100
|
-
if (items.length > 3) previews.push(`... +${items.length - 3} more
|
|
180
|
+
if (items.length > 3) previews.push({ text: `... +${items.length - 3} more`, type: 'text' });
|
|
101
181
|
} else if (node.type === 'image') {
|
|
102
|
-
previews.push('[image]');
|
|
182
|
+
previews.push({ text: '[image]', type: 'text', nodeIndex: idx });
|
|
103
183
|
} else {
|
|
104
184
|
const text = node.source.replace(/\n/g, ' ').trim();
|
|
105
|
-
if (text) previews.push(text.length > 50 ? text.slice(0, 47) + '...' : text);
|
|
185
|
+
if (text) previews.push({ text: text.length > 50 ? text.slice(0, 47) + '...' : text, type: 'text', nodeIndex: idx });
|
|
106
186
|
}
|
|
107
187
|
}
|
|
108
188
|
return previews;
|
|
109
189
|
}
|
|
110
190
|
</script>
|
|
111
191
|
|
|
192
|
+
{#snippet previewItems(previews: PreviewItem[], fieldName: string, zoneName?: string)}
|
|
193
|
+
{#each previews as preview}
|
|
194
|
+
{#if preview.type === 'rune' && preview.nodeIndex !== undefined}
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
class="cm-tree__preview-line cm-tree__preview-line--rune"
|
|
198
|
+
onclick={() => onnavigaterune(fieldName, preview.nodeIndex!, zoneName)}
|
|
199
|
+
>
|
|
200
|
+
<span class="cm-tree__rune-dot"></span>
|
|
201
|
+
{preview.text}
|
|
202
|
+
</button>
|
|
203
|
+
{:else}
|
|
204
|
+
<button
|
|
205
|
+
type="button"
|
|
206
|
+
class="cm-tree__preview-line cm-tree__preview-line--clickable"
|
|
207
|
+
onclick={(e) => oneditfield(fieldName, (e.currentTarget as HTMLElement).getBoundingClientRect(), zoneName, preview.nodeIndex)}
|
|
208
|
+
>
|
|
209
|
+
{preview.text}
|
|
210
|
+
</button>
|
|
211
|
+
{/if}
|
|
212
|
+
{/each}
|
|
213
|
+
{/snippet}
|
|
214
|
+
|
|
215
|
+
{#snippet listItemRows(listItems: { text: string; index: number }[], fieldName: string, zoneName?: string)}
|
|
216
|
+
{#each listItems as item}
|
|
217
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
218
|
+
<div
|
|
219
|
+
class="cm-tree__preview-item"
|
|
220
|
+
class:cm-tree__preview-item--drag-over={dragFieldName === fieldName && dragZoneName === zoneName && dragOverIndex === item.index && dragFromIndex !== item.index}
|
|
221
|
+
class:cm-tree__preview-item--dragging={dragFieldName === fieldName && dragZoneName === zoneName && dragFromIndex === item.index}
|
|
222
|
+
draggable="true"
|
|
223
|
+
ondragstart={(e) => handleItemDragStart(e, fieldName, item.index, zoneName)}
|
|
224
|
+
ondragover={(e) => handleItemDragOver(e, item.index)}
|
|
225
|
+
ondrop={(e) => handleItemDrop(e, item.index)}
|
|
226
|
+
ondragend={handleItemDragEnd}
|
|
227
|
+
>
|
|
228
|
+
<span
|
|
229
|
+
class="cm-tree__preview-grip"
|
|
230
|
+
title="Drag to reorder"
|
|
231
|
+
>
|
|
232
|
+
<svg width="6" height="10" viewBox="0 0 6 10" fill="none">
|
|
233
|
+
<circle cx="1.5" cy="1.5" r="1" fill="currentColor"/>
|
|
234
|
+
<circle cx="4.5" cy="1.5" r="1" fill="currentColor"/>
|
|
235
|
+
<circle cx="1.5" cy="5" r="1" fill="currentColor"/>
|
|
236
|
+
<circle cx="4.5" cy="5" r="1" fill="currentColor"/>
|
|
237
|
+
<circle cx="1.5" cy="8.5" r="1" fill="currentColor"/>
|
|
238
|
+
<circle cx="4.5" cy="8.5" r="1" fill="currentColor"/>
|
|
239
|
+
</svg>
|
|
240
|
+
</span>
|
|
241
|
+
<button
|
|
242
|
+
type="button"
|
|
243
|
+
class="cm-tree__preview-text cm-tree__preview-text--clickable"
|
|
244
|
+
onclick={(e) => {
|
|
245
|
+
e.stopPropagation();
|
|
246
|
+
oneditlistitem(fieldName, item.index, (e.currentTarget as HTMLElement).getBoundingClientRect(), zoneName);
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
{item.text}
|
|
250
|
+
</button>
|
|
251
|
+
<button
|
|
252
|
+
type="button"
|
|
253
|
+
class="cm-tree__preview-remove"
|
|
254
|
+
title="Remove item"
|
|
255
|
+
onclick={() => onremovelistitem(fieldName, item.index, zoneName)}
|
|
256
|
+
>
|
|
257
|
+
<svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
258
|
+
<line x1="4" y1="4" x2="12" y2="12" />
|
|
259
|
+
<line x1="12" y1="4" x2="4" y2="12" />
|
|
260
|
+
</svg>
|
|
261
|
+
</button>
|
|
262
|
+
</div>
|
|
263
|
+
{/each}
|
|
264
|
+
{/snippet}
|
|
265
|
+
|
|
266
|
+
{#snippet greedyItemRows(items: { text: string; index: number }[], fieldName: string, zoneName?: string)}
|
|
267
|
+
{#each items as item}
|
|
268
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
269
|
+
<div
|
|
270
|
+
class="cm-tree__preview-item"
|
|
271
|
+
class:cm-tree__preview-item--drag-over={dragFieldName === fieldName && dragZoneName === zoneName && dragOverIndex === item.index && dragFromIndex !== item.index}
|
|
272
|
+
class:cm-tree__preview-item--dragging={dragFieldName === fieldName && dragZoneName === zoneName && dragFromIndex === item.index}
|
|
273
|
+
draggable="true"
|
|
274
|
+
ondragstart={(e) => handleItemDragStart(e, fieldName, item.index, zoneName)}
|
|
275
|
+
ondragover={(e) => handleItemDragOver(e, item.index)}
|
|
276
|
+
ondrop={(e) => handleItemDrop(e, item.index)}
|
|
277
|
+
ondragend={handleItemDragEnd}
|
|
278
|
+
>
|
|
279
|
+
<span
|
|
280
|
+
class="cm-tree__preview-grip"
|
|
281
|
+
title="Drag to reorder"
|
|
282
|
+
>
|
|
283
|
+
<svg width="6" height="10" viewBox="0 0 6 10" fill="none">
|
|
284
|
+
<circle cx="1.5" cy="1.5" r="1" fill="currentColor"/>
|
|
285
|
+
<circle cx="4.5" cy="1.5" r="1" fill="currentColor"/>
|
|
286
|
+
<circle cx="1.5" cy="5" r="1" fill="currentColor"/>
|
|
287
|
+
<circle cx="4.5" cy="5" r="1" fill="currentColor"/>
|
|
288
|
+
<circle cx="1.5" cy="8.5" r="1" fill="currentColor"/>
|
|
289
|
+
<circle cx="4.5" cy="8.5" r="1" fill="currentColor"/>
|
|
290
|
+
</svg>
|
|
291
|
+
</span>
|
|
292
|
+
<button
|
|
293
|
+
type="button"
|
|
294
|
+
class="cm-tree__preview-text cm-tree__preview-text--clickable"
|
|
295
|
+
onclick={(e) => {
|
|
296
|
+
e.stopPropagation();
|
|
297
|
+
oneditfield(fieldName, (e.currentTarget as HTMLElement).getBoundingClientRect(), zoneName, item.index);
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
{item.text}
|
|
301
|
+
</button>
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
class="cm-tree__preview-remove"
|
|
305
|
+
title="Remove item"
|
|
306
|
+
onclick={() => onremovelistitem(fieldName, item.index, zoneName)}
|
|
307
|
+
>
|
|
308
|
+
<svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
309
|
+
<line x1="4" y1="4" x2="12" y2="12" />
|
|
310
|
+
<line x1="12" y1="4" x2="4" y2="12" />
|
|
311
|
+
</svg>
|
|
312
|
+
</button>
|
|
313
|
+
</div>
|
|
314
|
+
{/each}
|
|
315
|
+
{/snippet}
|
|
316
|
+
|
|
112
317
|
<div class="cm-tree">
|
|
113
318
|
<!-- Root node -->
|
|
114
319
|
<div class="cm-tree__root">
|
|
@@ -121,6 +326,7 @@
|
|
|
121
326
|
{#each structure.fields as field}
|
|
122
327
|
{@const previews = contentPreview(field.nodes)}
|
|
123
328
|
{@const listItems = isListField(field.match) ? listItemPreviews(field.nodes) : []}
|
|
329
|
+
{@const greedyItems = isGreedyItemField(field) ? greedyItemPreviews(field) : []}
|
|
124
330
|
<button
|
|
125
331
|
type="button"
|
|
126
332
|
class="cm-tree__field"
|
|
@@ -128,13 +334,7 @@
|
|
|
128
334
|
class:cm-tree__field--empty={!field.filled}
|
|
129
335
|
class:cm-tree__field--required={!field.optional && !field.filled}
|
|
130
336
|
class:cm-tree__field--selected={selectedField === field.name}
|
|
131
|
-
onclick={(
|
|
132
|
-
if (field.filled && !isListField(field.match)) {
|
|
133
|
-
oneditfield(field.name, (e.currentTarget as HTMLElement).getBoundingClientRect());
|
|
134
|
-
} else {
|
|
135
|
-
onfieldselect(field.name);
|
|
136
|
-
}
|
|
137
|
-
}}
|
|
337
|
+
onclick={() => onfieldselect(field.name)}
|
|
138
338
|
>
|
|
139
339
|
<svg class="cm-tree__field-icon" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
140
340
|
<path d={matchIcon(field.match)} />
|
|
@@ -145,7 +345,7 @@
|
|
|
145
345
|
{/if}
|
|
146
346
|
<span class="cm-tree__field-spacer"></span>
|
|
147
347
|
{#if field.filled}
|
|
148
|
-
{#if isListField(field.match)}
|
|
348
|
+
{#if isListField(field.match) || isGreedyItemField(field)}
|
|
149
349
|
<button
|
|
150
350
|
type="button"
|
|
151
351
|
class="cm-tree__action cm-tree__action--add"
|
|
@@ -187,28 +387,15 @@
|
|
|
187
387
|
</button>
|
|
188
388
|
{#if field.filled && isListField(field.match) && listItems.length > 0}
|
|
189
389
|
<div class="cm-tree__previews">
|
|
190
|
-
{
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
class="cm-tree__preview-remove"
|
|
196
|
-
title="Remove item"
|
|
197
|
-
onclick={() => onremovelistitem(field.name, item.index)}
|
|
198
|
-
>
|
|
199
|
-
<svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
200
|
-
<line x1="4" y1="4" x2="12" y2="12" />
|
|
201
|
-
<line x1="12" y1="4" x2="4" y2="12" />
|
|
202
|
-
</svg>
|
|
203
|
-
</button>
|
|
204
|
-
</div>
|
|
205
|
-
{/each}
|
|
390
|
+
{@render listItemRows(listItems, field.name)}
|
|
391
|
+
</div>
|
|
392
|
+
{:else if field.filled && greedyItems.length > 0}
|
|
393
|
+
<div class="cm-tree__previews">
|
|
394
|
+
{@render greedyItemRows(greedyItems, field.name)}
|
|
206
395
|
</div>
|
|
207
396
|
{:else if field.filled && previews.length > 0}
|
|
208
397
|
<div class="cm-tree__previews">
|
|
209
|
-
{
|
|
210
|
-
<div class="cm-tree__preview-line">{preview}</div>
|
|
211
|
-
{/each}
|
|
398
|
+
{@render previewItems(previews, field.name)}
|
|
212
399
|
</div>
|
|
213
400
|
{/if}
|
|
214
401
|
{/each}
|
|
@@ -227,6 +414,7 @@
|
|
|
227
414
|
{#each zone.fields as field}
|
|
228
415
|
{@const previews = contentPreview(field.nodes)}
|
|
229
416
|
{@const listItems = isListField(field.match) ? listItemPreviews(field.nodes) : []}
|
|
417
|
+
{@const greedyItems = isGreedyItemField(field) ? greedyItemPreviews(field) : []}
|
|
230
418
|
<button
|
|
231
419
|
type="button"
|
|
232
420
|
class="cm-tree__field"
|
|
@@ -234,13 +422,7 @@
|
|
|
234
422
|
class:cm-tree__field--empty={!field.filled}
|
|
235
423
|
class:cm-tree__field--required={!field.optional && !field.filled}
|
|
236
424
|
class:cm-tree__field--selected={selectedField === `${zone.name}.${field.name}`}
|
|
237
|
-
onclick={(
|
|
238
|
-
if (field.filled && !isListField(field.match)) {
|
|
239
|
-
oneditfield(field.name, (e.currentTarget as HTMLElement).getBoundingClientRect(), zone.name);
|
|
240
|
-
} else {
|
|
241
|
-
onfieldselect(field.name, zone.name);
|
|
242
|
-
}
|
|
243
|
-
}}
|
|
425
|
+
onclick={() => onfieldselect(field.name, zone.name)}
|
|
244
426
|
>
|
|
245
427
|
<svg class="cm-tree__field-icon" width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
246
428
|
<path d={matchIcon(field.match)} />
|
|
@@ -251,7 +433,7 @@
|
|
|
251
433
|
{/if}
|
|
252
434
|
<span class="cm-tree__field-spacer"></span>
|
|
253
435
|
{#if field.filled}
|
|
254
|
-
{#if isListField(field.match)}
|
|
436
|
+
{#if isListField(field.match) || isGreedyItemField(field)}
|
|
255
437
|
<button
|
|
256
438
|
type="button"
|
|
257
439
|
class="cm-tree__action cm-tree__action--add"
|
|
@@ -293,28 +475,15 @@
|
|
|
293
475
|
</button>
|
|
294
476
|
{#if field.filled && isListField(field.match) && listItems.length > 0}
|
|
295
477
|
<div class="cm-tree__previews">
|
|
296
|
-
{
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
class="cm-tree__preview-remove"
|
|
302
|
-
title="Remove item"
|
|
303
|
-
onclick={() => onremovelistitem(field.name, item.index, zone.name)}
|
|
304
|
-
>
|
|
305
|
-
<svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
306
|
-
<line x1="4" y1="4" x2="12" y2="12" />
|
|
307
|
-
<line x1="12" y1="4" x2="4" y2="12" />
|
|
308
|
-
</svg>
|
|
309
|
-
</button>
|
|
310
|
-
</div>
|
|
311
|
-
{/each}
|
|
478
|
+
{@render listItemRows(listItems, field.name, zone.name)}
|
|
479
|
+
</div>
|
|
480
|
+
{:else if field.filled && greedyItems.length > 0}
|
|
481
|
+
<div class="cm-tree__previews">
|
|
482
|
+
{@render greedyItemRows(greedyItems, field.name, zone.name)}
|
|
312
483
|
</div>
|
|
313
484
|
{:else if field.filled && previews.length > 0}
|
|
314
485
|
<div class="cm-tree__previews">
|
|
315
|
-
{
|
|
316
|
-
<div class="cm-tree__preview-line">{preview}</div>
|
|
317
|
-
{/each}
|
|
486
|
+
{@render previewItems(previews, field.name, zone.name)}
|
|
318
487
|
</div>
|
|
319
488
|
{/if}
|
|
320
489
|
{/each}
|
|
@@ -509,11 +678,68 @@
|
|
|
509
678
|
line-height: 1.4;
|
|
510
679
|
}
|
|
511
680
|
|
|
681
|
+
.cm-tree__preview-line--clickable {
|
|
682
|
+
display: block;
|
|
683
|
+
width: 100%;
|
|
684
|
+
background: none;
|
|
685
|
+
border: none;
|
|
686
|
+
padding: 0.05rem 0.25rem;
|
|
687
|
+
cursor: pointer;
|
|
688
|
+
text-align: left;
|
|
689
|
+
font: inherit;
|
|
690
|
+
font-size: var(--ed-text-xs);
|
|
691
|
+
color: var(--ed-text-muted);
|
|
692
|
+
border-radius: 2px;
|
|
693
|
+
overflow: hidden;
|
|
694
|
+
text-overflow: ellipsis;
|
|
695
|
+
white-space: nowrap;
|
|
696
|
+
line-height: 1.4;
|
|
697
|
+
transition: background var(--ed-transition-fast), color var(--ed-transition-fast);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
.cm-tree__preview-line--clickable:hover {
|
|
701
|
+
background: var(--ed-surface-2);
|
|
702
|
+
color: var(--ed-text-secondary);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.cm-tree__preview-line--rune {
|
|
706
|
+
display: flex;
|
|
707
|
+
align-items: center;
|
|
708
|
+
gap: 0.3rem;
|
|
709
|
+
width: 100%;
|
|
710
|
+
background: none;
|
|
711
|
+
border: none;
|
|
712
|
+
padding: 0.05rem 0.25rem;
|
|
713
|
+
cursor: pointer;
|
|
714
|
+
text-align: left;
|
|
715
|
+
font: inherit;
|
|
716
|
+
font-size: var(--ed-text-xs);
|
|
717
|
+
color: var(--ed-warning-text);
|
|
718
|
+
font-weight: 500;
|
|
719
|
+
border-radius: 2px;
|
|
720
|
+
line-height: 1.4;
|
|
721
|
+
transition: background var(--ed-transition-fast);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.cm-tree__preview-line--rune:hover {
|
|
725
|
+
background: var(--ed-warning-subtle);
|
|
726
|
+
}
|
|
727
|
+
|
|
512
728
|
.cm-tree__preview-item {
|
|
513
729
|
display: flex;
|
|
514
730
|
align-items: center;
|
|
515
731
|
gap: 0.25rem;
|
|
516
732
|
line-height: 1.4;
|
|
733
|
+
border-top: 2px solid transparent;
|
|
734
|
+
transition: border-color var(--ed-transition-fast);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.cm-tree__preview-item--drag-over {
|
|
738
|
+
border-top-color: var(--ed-accent);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.cm-tree__preview-item--dragging {
|
|
742
|
+
opacity: 0.4;
|
|
517
743
|
}
|
|
518
744
|
|
|
519
745
|
.cm-tree__preview-text {
|
|
@@ -526,6 +752,53 @@
|
|
|
526
752
|
min-width: 0;
|
|
527
753
|
}
|
|
528
754
|
|
|
755
|
+
.cm-tree__preview-text--clickable {
|
|
756
|
+
background: none;
|
|
757
|
+
border: none;
|
|
758
|
+
padding: 0.05rem 0.15rem;
|
|
759
|
+
cursor: pointer;
|
|
760
|
+
font: inherit;
|
|
761
|
+
font-size: var(--ed-text-xs);
|
|
762
|
+
color: var(--ed-text-muted);
|
|
763
|
+
text-align: left;
|
|
764
|
+
border-radius: 2px;
|
|
765
|
+
overflow: hidden;
|
|
766
|
+
text-overflow: ellipsis;
|
|
767
|
+
white-space: nowrap;
|
|
768
|
+
flex: 1;
|
|
769
|
+
min-width: 0;
|
|
770
|
+
transition: color var(--ed-transition-fast), background var(--ed-transition-fast);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.cm-tree__preview-text--clickable:hover {
|
|
774
|
+
color: var(--ed-text-secondary);
|
|
775
|
+
background: var(--ed-surface-2);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/* Drag grip handle */
|
|
779
|
+
.cm-tree__preview-grip {
|
|
780
|
+
display: flex;
|
|
781
|
+
align-items: center;
|
|
782
|
+
justify-content: center;
|
|
783
|
+
width: 12px;
|
|
784
|
+
height: 16px;
|
|
785
|
+
padding: 0;
|
|
786
|
+
color: var(--ed-text-muted);
|
|
787
|
+
cursor: grab;
|
|
788
|
+
flex-shrink: 0;
|
|
789
|
+
opacity: 0;
|
|
790
|
+
transition: opacity var(--ed-transition-fast), color var(--ed-transition-fast);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.cm-tree__preview-item:hover .cm-tree__preview-grip {
|
|
794
|
+
opacity: 0.6;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.cm-tree__preview-grip:hover {
|
|
798
|
+
opacity: 1 !important;
|
|
799
|
+
color: var(--ed-text-secondary);
|
|
800
|
+
}
|
|
801
|
+
|
|
529
802
|
.cm-tree__preview-remove {
|
|
530
803
|
display: flex;
|
|
531
804
|
align-items: center;
|