@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.
- package/app/dist/assets/{index-Ca-wW6uw.js → index-80NtMar1.js} +1 -1
- package/app/dist/assets/index-B6H6LF1M.css +1 -0
- package/app/dist/assets/{index-Dg4A5Pez.js → index-BDj1XPol.js} +1 -1
- package/app/dist/assets/{index-BfYWp0QC.js → index-BXe1fKaT.js} +1 -1
- package/app/dist/assets/{index-Cq0Maciq.js → index-BfxTGrHB.js} +1 -1
- package/app/dist/assets/{index-BsSUa0GD.js → index-Bn8ajfVl.js} +1 -1
- package/app/dist/assets/{index-D6vnTt4b.js → index-CCkzIGTi.js} +2 -2
- package/app/dist/assets/{index-BehCztSl.js → index-CXeK-dZx.js} +1 -1
- package/app/dist/assets/{index-iGDqoXj_.js → index-CaRBCHaX.js} +1 -1
- package/app/dist/assets/index-Cd12jZId.js +479 -0
- package/app/dist/assets/{index-D5pMhPrg.js → index-Cgbvx23V.js} +1 -1
- package/app/dist/assets/{index-IU6QYZAa.js → index-D5ucdUTo.js} +1 -1
- package/app/dist/assets/{index-CdpS6tGk.js → index-DGYxLhpR.js} +1 -1
- package/app/dist/assets/{index-RKEq45V5.js → index-DNJBunzP.js} +1 -1
- package/app/dist/assets/{index-Cgaw2jCE.js → index-DNtuldOx.js} +1 -1
- package/app/dist/assets/{index-BEPqnnsd.js → index-DQUOY-pF.js} +1 -1
- package/app/dist/assets/{index-2hOoPFOR.js → index-DskvyNKT.js} +1 -1
- package/app/dist/assets/{index-CLZfwYyS.js → index-aPeHMqUX.js} +1 -1
- package/app/dist/assets/{index-BobjskUl.js → index-dGztG-54.js} +1 -1
- package/app/dist/assets/{index-DHALjxX5.js → index-xo7v6nRB.js} +1 -1
- package/app/dist/index.html +2 -2
- package/app/src/lib/api/client.ts +81 -0
- package/app/src/lib/components/ActionEditPopover.svelte +267 -0
- package/app/src/lib/components/BlockCard.svelte +285 -0
- package/app/src/lib/components/BlockEditPanel.svelte +640 -260
- package/app/src/lib/components/BlockEditor.svelte +513 -52
- package/app/src/lib/components/CodeEditPopover.svelte +444 -0
- package/app/src/lib/components/ContentModelTree.svelte +835 -0
- package/app/src/lib/components/EditorLayout.svelte +1 -6
- package/app/src/lib/components/FrontmatterEditPanel.svelte +0 -1
- 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 +616 -0
- package/app/src/lib/components/RuneAttributes.svelte +51 -0
- package/app/src/lib/editor/block-parser.ts +424 -6
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +189 -2
- package/dist/server.js.map +1 -1
- package/package.json +6 -6
- package/app/dist/assets/index-98ylvoBO.css +0 -1
- package/app/dist/assets/index-CVzOx0nV.js +0 -372
|
@@ -8,13 +8,23 @@
|
|
|
8
8
|
buildRuneMap,
|
|
9
9
|
blockLabel,
|
|
10
10
|
extractRuneInner,
|
|
11
|
+
rebuildRuneSource,
|
|
11
12
|
type ParsedBlock,
|
|
13
|
+
type RuneBlock,
|
|
12
14
|
} from '../editor/block-parser.js';
|
|
15
|
+
import { findSectionMapping, applySectionEdit, findActionMapping, applyActionEdit, findCommandMapping, applyCommandEdit, applyLanguageEdit, findImageMapping, applyImageEdit, findIconMapping, applyIconEdit, type SectionMapping, type ActionMapping, type CommandMapping, type ImageMapping, type IconMapping } from '../editor/section-mapper.js';
|
|
16
|
+
import { stripInlineMarkdown } from '../editor/inline-markdown.js';
|
|
13
17
|
import { editorState } from '../state/editor.svelte.js';
|
|
14
18
|
import BlockCard from './BlockCard.svelte';
|
|
19
|
+
import type { SectionClickInfo } from './BlockCard.svelte';
|
|
15
20
|
import BlockEditPanel from './BlockEditPanel.svelte';
|
|
16
21
|
import FrontmatterEditPanel from './FrontmatterEditPanel.svelte';
|
|
17
22
|
import InsertBlockDialog from './InsertBlockDialog.svelte';
|
|
23
|
+
import InlineEditPopover from './InlineEditPopover.svelte';
|
|
24
|
+
import ActionEditPopover from './ActionEditPopover.svelte';
|
|
25
|
+
import CodeEditPopover from './CodeEditPopover.svelte';
|
|
26
|
+
import ImageEditPopover from './ImageEditPopover.svelte';
|
|
27
|
+
import IconPickerPopover from './IconPickerPopover.svelte';
|
|
18
28
|
|
|
19
29
|
interface Props {
|
|
20
30
|
bodyContent: string;
|
|
@@ -99,17 +109,14 @@
|
|
|
99
109
|
reconcileIds(blocks, newBlocks);
|
|
100
110
|
blocks = newBlocks;
|
|
101
111
|
lastParsedSource = body;
|
|
102
|
-
// Close edit panel when switching files
|
|
112
|
+
// Close edit panel and inline popover when switching files
|
|
103
113
|
activeIndex = null;
|
|
114
|
+
editingFrontmatter = false;
|
|
115
|
+
anchorPoint = null;
|
|
116
|
+
inlineEdit = null;
|
|
104
117
|
}
|
|
105
118
|
});
|
|
106
119
|
|
|
107
|
-
// Sync edit panel open state to global state (for layout adjustments)
|
|
108
|
-
$effect(() => {
|
|
109
|
-
editorState.editPanelOpen = !readOnly && (activeIndex !== null || editingFrontmatter);
|
|
110
|
-
return () => { editorState.editPanelOpen = false; };
|
|
111
|
-
});
|
|
112
|
-
|
|
113
120
|
/** Sync blocks back to source text */
|
|
114
121
|
function syncToSource() {
|
|
115
122
|
const newSource = serializeBlocks(blocks);
|
|
@@ -130,15 +137,71 @@
|
|
|
130
137
|
|
|
131
138
|
let activeIndex: number | null = $state(null);
|
|
132
139
|
let hoveredIndex: number | null = $state(null);
|
|
140
|
+
let anchorPoint: { x: number; y: number } | null = $state(null);
|
|
141
|
+
let pendingRuneIndex: number | null = $state(null);
|
|
142
|
+
let editSessionId: number = $state(0);
|
|
143
|
+
|
|
144
|
+
// ── Popover positioning ─────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
const POPOVER_WIDTH = 420;
|
|
147
|
+
const POPOVER_GAP = 12;
|
|
148
|
+
|
|
149
|
+
let popoverStyle = $derived.by(() => {
|
|
150
|
+
if (!anchorPoint) return '';
|
|
151
|
+
|
|
152
|
+
const vw = window.innerWidth;
|
|
153
|
+
const vh = window.innerHeight;
|
|
154
|
+
|
|
155
|
+
// Prefer placing to the right of the click point
|
|
156
|
+
let left = anchorPoint.x + POPOVER_GAP;
|
|
157
|
+
if (left + POPOVER_WIDTH > vw - 16) {
|
|
158
|
+
left = anchorPoint.x - POPOVER_WIDTH - POPOVER_GAP;
|
|
159
|
+
}
|
|
160
|
+
if (left < 16) {
|
|
161
|
+
left = vw - POPOVER_WIDTH - 16;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Vertical: start at click Y, clamped to viewport
|
|
165
|
+
let top = anchorPoint.y;
|
|
166
|
+
const maxH = vh - 120;
|
|
167
|
+
const maxTop = vh - Math.min(600, maxH) - 16;
|
|
168
|
+
if (top > maxTop) top = maxTop;
|
|
169
|
+
if (top < 60) top = 60;
|
|
170
|
+
|
|
171
|
+
return `left: ${left}px; top: ${top}px; max-height: min(600px, ${maxH}px);`;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
function toggleBlock(index: number, x: number, y: number) {
|
|
175
|
+
editingFrontmatter = false;
|
|
176
|
+
if (activeIndex === index) {
|
|
177
|
+
activeIndex = null;
|
|
178
|
+
anchorPoint = null;
|
|
179
|
+
pendingRuneIndex = null;
|
|
180
|
+
} else {
|
|
181
|
+
editSessionId++;
|
|
182
|
+
activeIndex = index;
|
|
183
|
+
anchorPoint = { x, y };
|
|
184
|
+
pendingRuneIndex = null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
133
187
|
|
|
134
|
-
function
|
|
188
|
+
function handleRuneClick(index: number, x: number, y: number, nestedRuneIndex?: number) {
|
|
135
189
|
editingFrontmatter = false;
|
|
136
|
-
|
|
190
|
+
editSessionId++;
|
|
191
|
+
activeIndex = index;
|
|
192
|
+
anchorPoint = { x, y };
|
|
193
|
+
pendingRuneIndex = nestedRuneIndex ?? null;
|
|
137
194
|
}
|
|
138
195
|
|
|
139
|
-
function toggleFrontmatter() {
|
|
196
|
+
function toggleFrontmatter(e: MouseEvent) {
|
|
140
197
|
activeIndex = null;
|
|
141
|
-
editingFrontmatter
|
|
198
|
+
if (editingFrontmatter) {
|
|
199
|
+
editingFrontmatter = false;
|
|
200
|
+
anchorPoint = null;
|
|
201
|
+
} else {
|
|
202
|
+
editingFrontmatter = true;
|
|
203
|
+
anchorPoint = { x: e.clientX, y: e.clientY };
|
|
204
|
+
}
|
|
142
205
|
}
|
|
143
206
|
|
|
144
207
|
function handleKeydown(e: KeyboardEvent) {
|
|
@@ -147,10 +210,25 @@
|
|
|
147
210
|
if (activeIndex !== null || editingFrontmatter) {
|
|
148
211
|
activeIndex = null;
|
|
149
212
|
editingFrontmatter = false;
|
|
213
|
+
anchorPoint = null;
|
|
214
|
+
pendingRuneIndex = null;
|
|
150
215
|
}
|
|
151
216
|
}
|
|
152
217
|
}
|
|
153
218
|
|
|
219
|
+
function handleListScroll() {
|
|
220
|
+
if (activeIndex !== null || editingFrontmatter) {
|
|
221
|
+
activeIndex = null;
|
|
222
|
+
editingFrontmatter = false;
|
|
223
|
+
anchorPoint = null;
|
|
224
|
+
pendingRuneIndex = null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function handleResize() {
|
|
229
|
+
if (anchorPoint) anchorPoint = { ...anchorPoint };
|
|
230
|
+
}
|
|
231
|
+
|
|
154
232
|
// ── Block operations ─────────────────────────────────────────
|
|
155
233
|
|
|
156
234
|
function handleUpdateBlock(index: number, updated: ParsedBlock) {
|
|
@@ -178,6 +256,9 @@
|
|
|
178
256
|
|
|
179
257
|
function handleDragStart(e: DragEvent, index: number) {
|
|
180
258
|
dragIndex = index;
|
|
259
|
+
activeIndex = null;
|
|
260
|
+
editingFrontmatter = false;
|
|
261
|
+
anchorPoint = null;
|
|
181
262
|
if (e.dataTransfer) {
|
|
182
263
|
e.dataTransfer.effectAllowed = 'move';
|
|
183
264
|
e.dataTransfer.setData('text/plain', String(index));
|
|
@@ -338,6 +419,309 @@
|
|
|
338
419
|
syncToSource();
|
|
339
420
|
}
|
|
340
421
|
|
|
422
|
+
// ── Inline section editing ──────────────────────────────────
|
|
423
|
+
|
|
424
|
+
let inlineEdit: {
|
|
425
|
+
blockIndex: number;
|
|
426
|
+
dataName: string;
|
|
427
|
+
inlineSource: string;
|
|
428
|
+
rect: DOMRect;
|
|
429
|
+
mapping: SectionMapping;
|
|
430
|
+
} | null = $state(null);
|
|
431
|
+
|
|
432
|
+
function handleSectionClick(index: number, info: SectionClickInfo) {
|
|
433
|
+
const block = blocks[index];
|
|
434
|
+
if (block.type !== 'rune') return;
|
|
435
|
+
const rb = block as RuneBlock;
|
|
436
|
+
|
|
437
|
+
if (info.editType === 'link') {
|
|
438
|
+
const mapping = findActionMapping(rb.innerContent, info.text, info.href ?? '');
|
|
439
|
+
if (!mapping) return;
|
|
440
|
+
|
|
441
|
+
actionEdit = {
|
|
442
|
+
blockIndex: index,
|
|
443
|
+
rect: info.rect,
|
|
444
|
+
mapping,
|
|
445
|
+
};
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (info.editType === 'code') {
|
|
450
|
+
const mapping = findCommandMapping(rb.innerContent, info.text);
|
|
451
|
+
if (!mapping) return;
|
|
452
|
+
|
|
453
|
+
commandEdit = {
|
|
454
|
+
blockIndex: index,
|
|
455
|
+
rect: info.rect,
|
|
456
|
+
mapping,
|
|
457
|
+
};
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (info.editType === 'image') {
|
|
462
|
+
const imgSrc = info.href ?? '';
|
|
463
|
+
const mapping = findImageMapping(rb.innerContent, imgSrc);
|
|
464
|
+
if (!mapping) return;
|
|
465
|
+
|
|
466
|
+
imageEdit = {
|
|
467
|
+
blockIndex: index,
|
|
468
|
+
mapping,
|
|
469
|
+
};
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (info.editType === 'icon') {
|
|
474
|
+
const iconName = info.iconName ?? '';
|
|
475
|
+
const mapping = findIconMapping(rb.innerContent, iconName);
|
|
476
|
+
if (!mapping) return;
|
|
477
|
+
|
|
478
|
+
iconEdit = {
|
|
479
|
+
blockIndex: index,
|
|
480
|
+
mapping,
|
|
481
|
+
};
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Default: inline text editing
|
|
486
|
+
const mapping = findSectionMapping(rb.innerContent, info.dataName, info.text);
|
|
487
|
+
if (!mapping) return;
|
|
488
|
+
|
|
489
|
+
inlineEdit = {
|
|
490
|
+
blockIndex: index,
|
|
491
|
+
dataName: info.dataName,
|
|
492
|
+
inlineSource: mapping.inlineSource,
|
|
493
|
+
rect: info.rect,
|
|
494
|
+
mapping,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function handleInlineEditChange(newInlineSource: string) {
|
|
499
|
+
if (!inlineEdit) return;
|
|
500
|
+
const block = blocks[inlineEdit.blockIndex];
|
|
501
|
+
if (block.type !== 'rune') return;
|
|
502
|
+
const rb = block as RuneBlock;
|
|
503
|
+
|
|
504
|
+
const newInner = applySectionEdit(rb.innerContent, inlineEdit.mapping, newInlineSource);
|
|
505
|
+
const updated: RuneBlock = { ...rb, innerContent: newInner, source: '' };
|
|
506
|
+
updated.source = rebuildRuneSource(updated);
|
|
507
|
+
|
|
508
|
+
// Update the mapping to reflect the new source so subsequent edits work
|
|
509
|
+
inlineEdit = {
|
|
510
|
+
...inlineEdit,
|
|
511
|
+
inlineSource: newInlineSource,
|
|
512
|
+
mapping: {
|
|
513
|
+
...inlineEdit.mapping,
|
|
514
|
+
text: stripInlineMarkdown(newInlineSource),
|
|
515
|
+
source: inlineEdit.mapping.sourcePrefix + newInlineSource,
|
|
516
|
+
inlineSource: newInlineSource,
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
handleUpdateBlock(inlineEdit.blockIndex, updated);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function closeInlineEdit() {
|
|
524
|
+
inlineEdit = null;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ── Action item editing ────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
let actionEdit: {
|
|
530
|
+
blockIndex: number;
|
|
531
|
+
rect: DOMRect;
|
|
532
|
+
mapping: ActionMapping;
|
|
533
|
+
} | null = $state(null);
|
|
534
|
+
|
|
535
|
+
function handleActionEditChange(newText: string, newHref: string) {
|
|
536
|
+
if (!actionEdit) return;
|
|
537
|
+
const block = blocks[actionEdit.blockIndex];
|
|
538
|
+
if (block.type !== 'rune') return;
|
|
539
|
+
const rb = block as RuneBlock;
|
|
540
|
+
|
|
541
|
+
const newInner = applyActionEdit(rb.innerContent, actionEdit.mapping, newText, newHref);
|
|
542
|
+
const updated: RuneBlock = { ...rb, innerContent: newInner, source: '' };
|
|
543
|
+
updated.source = rebuildRuneSource(updated);
|
|
544
|
+
|
|
545
|
+
handleUpdateBlock(actionEdit.blockIndex, updated);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function handleActionRemove() {
|
|
549
|
+
if (!actionEdit) return;
|
|
550
|
+
const blockIndex = actionEdit.blockIndex;
|
|
551
|
+
const block = blocks[blockIndex];
|
|
552
|
+
if (block.type !== 'rune') return;
|
|
553
|
+
const rb = block as RuneBlock;
|
|
554
|
+
|
|
555
|
+
// Remove the entire list item line from the inner content
|
|
556
|
+
const newInner = rb.innerContent.replace(actionEdit.mapping.source + '\n', '').replace(actionEdit.mapping.source, '');
|
|
557
|
+
const updated: RuneBlock = { ...rb, innerContent: newInner, source: '' };
|
|
558
|
+
updated.source = rebuildRuneSource(updated);
|
|
559
|
+
|
|
560
|
+
actionEdit = null;
|
|
561
|
+
handleUpdateBlock(blockIndex, updated);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function closeActionEdit() {
|
|
565
|
+
actionEdit = null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ── Command (code block) editing ──────────────────────────
|
|
569
|
+
|
|
570
|
+
let commandEdit: {
|
|
571
|
+
blockIndex: number;
|
|
572
|
+
rect: DOMRect;
|
|
573
|
+
mapping: CommandMapping;
|
|
574
|
+
} | null = $state(null);
|
|
575
|
+
|
|
576
|
+
function handleCommandEditChange(newCode: string) {
|
|
577
|
+
if (!commandEdit) return;
|
|
578
|
+
const block = blocks[commandEdit.blockIndex];
|
|
579
|
+
if (block.type !== 'rune') return;
|
|
580
|
+
const rb = block as RuneBlock;
|
|
581
|
+
|
|
582
|
+
const newInner = applyCommandEdit(rb.innerContent, commandEdit.mapping, newCode);
|
|
583
|
+
const updated: RuneBlock = { ...rb, innerContent: newInner, source: '' };
|
|
584
|
+
updated.source = rebuildRuneSource(updated);
|
|
585
|
+
|
|
586
|
+
handleUpdateBlock(commandEdit.blockIndex, updated);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function handleCommandRemove() {
|
|
590
|
+
if (!commandEdit) return;
|
|
591
|
+
const blockIndex = commandEdit.blockIndex;
|
|
592
|
+
const block = blocks[blockIndex];
|
|
593
|
+
if (block.type !== 'rune') return;
|
|
594
|
+
const rb = block as RuneBlock;
|
|
595
|
+
|
|
596
|
+
// Remove the entire fenced code block from the inner content
|
|
597
|
+
const newInner = rb.innerContent.replace(commandEdit.mapping.source + '\n', '').replace(commandEdit.mapping.source, '');
|
|
598
|
+
const updated: RuneBlock = { ...rb, innerContent: newInner, source: '' };
|
|
599
|
+
updated.source = rebuildRuneSource(updated);
|
|
600
|
+
|
|
601
|
+
commandEdit = null;
|
|
602
|
+
handleUpdateBlock(blockIndex, updated);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function closeCommandEdit() {
|
|
606
|
+
commandEdit = null;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function handleLanguageChange(newLanguage: string) {
|
|
610
|
+
if (!commandEdit) return;
|
|
611
|
+
const block = blocks[commandEdit.blockIndex];
|
|
612
|
+
if (block.type !== 'rune') return;
|
|
613
|
+
const rb = block as RuneBlock;
|
|
614
|
+
|
|
615
|
+
const newInner = applyLanguageEdit(rb.innerContent, commandEdit.mapping, newLanguage);
|
|
616
|
+
const updated: RuneBlock = { ...rb, innerContent: newInner, source: '' };
|
|
617
|
+
updated.source = rebuildRuneSource(updated);
|
|
618
|
+
|
|
619
|
+
// Update the mapping to reflect the new language and opener
|
|
620
|
+
const afterDelimiter = commandEdit.mapping.opener.slice(commandEdit.mapping.delimiter.length);
|
|
621
|
+
const infoString = afterDelimiter.replace(/^\w*/, '').trim();
|
|
622
|
+
const newOpener = commandEdit.mapping.delimiter + newLanguage + (infoString ? ' ' + infoString : '');
|
|
623
|
+
const newSource = newOpener + '\n' + commandEdit.mapping.code + '\n' + commandEdit.mapping.delimiter;
|
|
624
|
+
|
|
625
|
+
commandEdit = {
|
|
626
|
+
...commandEdit,
|
|
627
|
+
mapping: {
|
|
628
|
+
...commandEdit.mapping,
|
|
629
|
+
language: newLanguage,
|
|
630
|
+
opener: newOpener,
|
|
631
|
+
source: newSource,
|
|
632
|
+
},
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
handleUpdateBlock(commandEdit.blockIndex, updated);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// ── Image editing ────────────────────────────────────────────
|
|
639
|
+
|
|
640
|
+
let imageEdit: {
|
|
641
|
+
blockIndex: number;
|
|
642
|
+
mapping: ImageMapping;
|
|
643
|
+
} | null = $state(null);
|
|
644
|
+
|
|
645
|
+
function handleImageEditChange(newSrc: string, newAlt: string) {
|
|
646
|
+
if (!imageEdit) return;
|
|
647
|
+
const block = blocks[imageEdit.blockIndex];
|
|
648
|
+
if (block.type !== 'rune') return;
|
|
649
|
+
const rb = block as RuneBlock;
|
|
650
|
+
|
|
651
|
+
const newInner = applyImageEdit(rb.innerContent, imageEdit.mapping, newAlt, newSrc);
|
|
652
|
+
const updated: RuneBlock = { ...rb, innerContent: newInner, source: '' };
|
|
653
|
+
updated.source = rebuildRuneSource(updated);
|
|
654
|
+
|
|
655
|
+
handleUpdateBlock(imageEdit.blockIndex, updated);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function handleImageRemove() {
|
|
659
|
+
if (!imageEdit) return;
|
|
660
|
+
const blockIndex = imageEdit.blockIndex;
|
|
661
|
+
const block = blocks[blockIndex];
|
|
662
|
+
if (block.type !== 'rune') return;
|
|
663
|
+
const rb = block as RuneBlock;
|
|
664
|
+
|
|
665
|
+
const newInner = rb.innerContent.replace(imageEdit.mapping.source + '\n', '').replace(imageEdit.mapping.source, '');
|
|
666
|
+
const updated: RuneBlock = { ...rb, innerContent: newInner, source: '' };
|
|
667
|
+
updated.source = rebuildRuneSource(updated);
|
|
668
|
+
|
|
669
|
+
imageEdit = null;
|
|
670
|
+
handleUpdateBlock(blockIndex, updated);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function closeImageEdit() {
|
|
674
|
+
imageEdit = null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// ── Icon editing ────────────────────────────────────────────
|
|
678
|
+
|
|
679
|
+
let iconEdit: {
|
|
680
|
+
blockIndex: number;
|
|
681
|
+
mapping: IconMapping;
|
|
682
|
+
} | null = $state(null);
|
|
683
|
+
|
|
684
|
+
function handleIconEditChange(newIconName: string) {
|
|
685
|
+
if (!iconEdit) return;
|
|
686
|
+
const block = blocks[iconEdit.blockIndex];
|
|
687
|
+
if (block.type !== 'rune') return;
|
|
688
|
+
const rb = block as RuneBlock;
|
|
689
|
+
|
|
690
|
+
const newInner = applyIconEdit(rb.innerContent, iconEdit.mapping, newIconName);
|
|
691
|
+
const updated: RuneBlock = { ...rb, innerContent: newInner, source: '' };
|
|
692
|
+
updated.source = rebuildRuneSource(updated);
|
|
693
|
+
|
|
694
|
+
handleUpdateBlock(iconEdit.blockIndex, updated);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function closeIconEdit() {
|
|
698
|
+
iconEdit = null;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ── Field edit from Structure tab ──────────────────────────────
|
|
702
|
+
|
|
703
|
+
function handleFieldEdit(dataName: string, inlineSource: string, rect: DOMRect, mapping: SectionMapping) {
|
|
704
|
+
if (activeIndex === null) return;
|
|
705
|
+
commandEdit = null;
|
|
706
|
+
inlineEdit = {
|
|
707
|
+
blockIndex: activeIndex,
|
|
708
|
+
dataName,
|
|
709
|
+
inlineSource,
|
|
710
|
+
rect,
|
|
711
|
+
mapping,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function handleFieldCodeEdit(code: string, language: string, rect: DOMRect, mapping: CommandMapping) {
|
|
716
|
+
if (activeIndex === null) return;
|
|
717
|
+
inlineEdit = null;
|
|
718
|
+
commandEdit = {
|
|
719
|
+
blockIndex: activeIndex,
|
|
720
|
+
rect,
|
|
721
|
+
mapping,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
341
725
|
// Group runes by category for the insert menu
|
|
342
726
|
let runesByCategory = $derived.by(() => {
|
|
343
727
|
const map = new Map<string, RuneInfo[]>();
|
|
@@ -350,7 +734,7 @@
|
|
|
350
734
|
});
|
|
351
735
|
</script>
|
|
352
736
|
|
|
353
|
-
<svelte:window onkeydown={handleKeydown} />
|
|
737
|
+
<svelte:window onkeydown={handleKeydown} onresize={handleResize} />
|
|
354
738
|
|
|
355
739
|
<div class="block-editor">
|
|
356
740
|
{#if blocks.length === 0 && !readOnly}
|
|
@@ -366,10 +750,10 @@
|
|
|
366
750
|
</div>
|
|
367
751
|
{/if}
|
|
368
752
|
|
|
369
|
-
<div class="block-editor__stage"
|
|
753
|
+
<div class="block-editor__stage">
|
|
370
754
|
<!-- Scrollable block list -->
|
|
371
755
|
<div class="block-editor__scroll">
|
|
372
|
-
<div class="block-editor__list-wrap">
|
|
756
|
+
<div class="block-editor__list-wrap" onscroll={handleListScroll}>
|
|
373
757
|
<!-- Frontmatter summary header (blocks mode only) -->
|
|
374
758
|
{#if !readOnly}
|
|
375
759
|
<div class="block-editor__fm-header">
|
|
@@ -382,7 +766,7 @@
|
|
|
382
766
|
<button
|
|
383
767
|
class="block-editor__fm-edit"
|
|
384
768
|
class:active={editingFrontmatter}
|
|
385
|
-
onclick={toggleFrontmatter}
|
|
769
|
+
onclick={(e) => toggleFrontmatter(e)}
|
|
386
770
|
title="Edit frontmatter"
|
|
387
771
|
>
|
|
388
772
|
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -414,6 +798,9 @@
|
|
|
414
798
|
{communityPostTransforms}
|
|
415
799
|
{communityStyles}
|
|
416
800
|
{aggregated}
|
|
801
|
+
{readOnly}
|
|
802
|
+
onsectionclick={readOnly ? undefined : (info) => handleSectionClick(i, info)}
|
|
803
|
+
onruneclick={readOnly ? undefined : (info) => handleRuneClick(i, info.x, info.y, info.nestedRuneIndex)}
|
|
417
804
|
ondragstart={readOnly ? undefined : (e) => handleDragStart(e, i)}
|
|
418
805
|
ondragover={readOnly ? undefined : (e) => handleDragOver(e, i)}
|
|
419
806
|
ondrop={readOnly ? undefined : (e) => handleDrop(e, i)}
|
|
@@ -446,7 +833,7 @@
|
|
|
446
833
|
<!-- Block label — slides in from right on hover, pinned when active -->
|
|
447
834
|
<button
|
|
448
835
|
class="block-editor__hover-label"
|
|
449
|
-
onclick={() => toggleBlock(i)}
|
|
836
|
+
onclick={(e) => toggleBlock(i, e.clientX, e.clientY)}
|
|
450
837
|
aria-pressed={activeIndex === i}
|
|
451
838
|
>
|
|
452
839
|
{blockLabel(block)}
|
|
@@ -457,30 +844,39 @@
|
|
|
457
844
|
</div>
|
|
458
845
|
</div>
|
|
459
846
|
|
|
460
|
-
<!-- Edit panel — slides in from the right (blocks mode only) -->
|
|
461
|
-
{#if !readOnly}
|
|
462
|
-
<div class="block-editor__edit-panel">
|
|
463
|
-
{#if editingFrontmatter}
|
|
464
|
-
<FrontmatterEditPanel
|
|
465
|
-
onclose={() => { editingFrontmatter = false; }}
|
|
466
|
-
/>
|
|
467
|
-
{:else if activeIndex !== null && blocks[activeIndex]}
|
|
468
|
-
{#key activeIndex}
|
|
469
|
-
<BlockEditPanel
|
|
470
|
-
block={blocks[activeIndex]}
|
|
471
|
-
{runeMap}
|
|
472
|
-
runes={() => runes}
|
|
473
|
-
{aggregated}
|
|
474
|
-
onupdate={(updated) => handleUpdateBlock(activeIndex!, updated)}
|
|
475
|
-
onremove={() => { const idx = activeIndex!; activeIndex = null; handleRemoveBlock(idx); }}
|
|
476
|
-
onclose={() => { activeIndex = null; }}
|
|
477
|
-
/>
|
|
478
|
-
{/key}
|
|
479
|
-
{/if}
|
|
480
|
-
</div>
|
|
481
|
-
{/if}
|
|
482
847
|
</div>
|
|
483
848
|
|
|
849
|
+
<!-- Popover edit panel — anchored to click position -->
|
|
850
|
+
{#if !readOnly && (activeIndex !== null || editingFrontmatter)}
|
|
851
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
852
|
+
<div
|
|
853
|
+
class="block-editor__popover-backdrop"
|
|
854
|
+
onmousedown={() => { activeIndex = null; editingFrontmatter = false; anchorPoint = null; pendingRuneIndex = null; }}
|
|
855
|
+
></div>
|
|
856
|
+
<div class="block-editor__popover" style={popoverStyle}>
|
|
857
|
+
{#if editingFrontmatter}
|
|
858
|
+
<FrontmatterEditPanel
|
|
859
|
+
onclose={() => { editingFrontmatter = false; anchorPoint = null; }}
|
|
860
|
+
/>
|
|
861
|
+
{:else if activeIndex !== null && blocks[activeIndex]}
|
|
862
|
+
{#key editSessionId}
|
|
863
|
+
<BlockEditPanel
|
|
864
|
+
block={blocks[activeIndex]}
|
|
865
|
+
{runeMap}
|
|
866
|
+
runes={() => runes}
|
|
867
|
+
{aggregated}
|
|
868
|
+
initialRuneIndex={pendingRuneIndex}
|
|
869
|
+
onupdate={(updated) => handleUpdateBlock(activeIndex!, updated)}
|
|
870
|
+
onremove={() => { const idx = activeIndex!; activeIndex = null; anchorPoint = null; pendingRuneIndex = null; handleRemoveBlock(idx); }}
|
|
871
|
+
onclose={() => { activeIndex = null; anchorPoint = null; pendingRuneIndex = null; }}
|
|
872
|
+
oneditfield={handleFieldEdit}
|
|
873
|
+
oneditcode={handleFieldCodeEdit}
|
|
874
|
+
/>
|
|
875
|
+
{/key}
|
|
876
|
+
{/if}
|
|
877
|
+
</div>
|
|
878
|
+
{/if}
|
|
879
|
+
|
|
484
880
|
{#if showInsertMenu}
|
|
485
881
|
<InsertBlockDialog
|
|
486
882
|
{runes}
|
|
@@ -489,6 +885,58 @@
|
|
|
489
885
|
onclose={closeInsertMenu}
|
|
490
886
|
/>
|
|
491
887
|
{/if}
|
|
888
|
+
|
|
889
|
+
{#if inlineEdit}
|
|
890
|
+
<InlineEditPopover
|
|
891
|
+
anchorRect={inlineEdit.rect}
|
|
892
|
+
dataName={inlineEdit.dataName}
|
|
893
|
+
inlineSource={inlineEdit.inlineSource}
|
|
894
|
+
onchange={handleInlineEditChange}
|
|
895
|
+
onclose={closeInlineEdit}
|
|
896
|
+
/>
|
|
897
|
+
{/if}
|
|
898
|
+
|
|
899
|
+
{#if actionEdit}
|
|
900
|
+
<ActionEditPopover
|
|
901
|
+
anchorRect={actionEdit.rect}
|
|
902
|
+
text={actionEdit.mapping.text}
|
|
903
|
+
href={actionEdit.mapping.href}
|
|
904
|
+
onchange={handleActionEditChange}
|
|
905
|
+
onremove={handleActionRemove}
|
|
906
|
+
onclose={closeActionEdit}
|
|
907
|
+
/>
|
|
908
|
+
{/if}
|
|
909
|
+
|
|
910
|
+
{#if commandEdit}
|
|
911
|
+
<CodeEditPopover
|
|
912
|
+
anchorRect={commandEdit.rect}
|
|
913
|
+
code={commandEdit.mapping.code}
|
|
914
|
+
language={commandEdit.mapping.language}
|
|
915
|
+
onchange={handleCommandEditChange}
|
|
916
|
+
onlanguagechange={handleLanguageChange}
|
|
917
|
+
onremove={handleCommandRemove}
|
|
918
|
+
onclose={closeCommandEdit}
|
|
919
|
+
/>
|
|
920
|
+
{/if}
|
|
921
|
+
|
|
922
|
+
{#if imageEdit}
|
|
923
|
+
<ImageEditPopover
|
|
924
|
+
currentSrc={imageEdit.mapping.src}
|
|
925
|
+
currentAlt={imageEdit.mapping.alt}
|
|
926
|
+
onchange={(src, alt) => { handleImageEditChange(src, alt); closeImageEdit(); }}
|
|
927
|
+
onremove={handleImageRemove}
|
|
928
|
+
onclose={closeImageEdit}
|
|
929
|
+
/>
|
|
930
|
+
{/if}
|
|
931
|
+
|
|
932
|
+
{#if iconEdit}
|
|
933
|
+
<IconPickerPopover
|
|
934
|
+
icons={themeConfig?.icons ?? {}}
|
|
935
|
+
currentIcon={iconEdit.mapping.name}
|
|
936
|
+
onchange={(name) => { handleIconEditChange(name); closeIconEdit(); }}
|
|
937
|
+
onclose={closeIconEdit}
|
|
938
|
+
/>
|
|
939
|
+
{/if}
|
|
492
940
|
</div>
|
|
493
941
|
|
|
494
942
|
|
|
@@ -515,7 +963,6 @@
|
|
|
515
963
|
min-height: 0;
|
|
516
964
|
display: flex;
|
|
517
965
|
flex-direction: column;
|
|
518
|
-
transition: margin-right var(--ed-transition-slow);
|
|
519
966
|
}
|
|
520
967
|
|
|
521
968
|
.block-editor__list-wrap {
|
|
@@ -697,24 +1144,38 @@
|
|
|
697
1144
|
color: white;
|
|
698
1145
|
}
|
|
699
1146
|
|
|
700
|
-
/*
|
|
701
|
-
.block-
|
|
1147
|
+
/* Popover backdrop — transparent click target */
|
|
1148
|
+
.block-editor__popover-backdrop {
|
|
702
1149
|
position: fixed;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1150
|
+
inset: 0;
|
|
1151
|
+
z-index: 9;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/* Popover container — anchored to block card */
|
|
1155
|
+
.block-editor__popover {
|
|
1156
|
+
position: fixed;
|
|
1157
|
+
width: 420px;
|
|
1158
|
+
overflow: hidden;
|
|
1159
|
+
display: flex;
|
|
1160
|
+
flex-direction: column;
|
|
708
1161
|
background: var(--ed-surface-0);
|
|
709
|
-
border-radius: var(--ed-radius-lg)
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1162
|
+
border-radius: var(--ed-radius-lg);
|
|
1163
|
+
border: 1px solid var(--ed-border-default);
|
|
1164
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
|
1165
|
+
0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
713
1166
|
z-index: 10;
|
|
1167
|
+
animation: popover-enter 0.15s ease-out;
|
|
714
1168
|
}
|
|
715
1169
|
|
|
716
|
-
|
|
717
|
-
|
|
1170
|
+
@keyframes popover-enter {
|
|
1171
|
+
from {
|
|
1172
|
+
opacity: 0;
|
|
1173
|
+
transform: translateY(4px) scale(0.98);
|
|
1174
|
+
}
|
|
1175
|
+
to {
|
|
1176
|
+
opacity: 1;
|
|
1177
|
+
transform: translateY(0) scale(1);
|
|
1178
|
+
}
|
|
718
1179
|
}
|
|
719
1180
|
|
|
720
1181
|
/* Empty state */
|