@refrakt-md/editor 0.8.1 → 0.8.3

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 (42) hide show
  1. package/app/dist/assets/{index-D3TQo8gu.js → index-3MvwKRVQ.js} +1 -1
  2. package/app/dist/assets/{index-CeU_s7BB.js → index-B7e694w6.js} +1 -1
  3. package/app/dist/assets/{index-DzHt8ZRh.js → index-BBljOYQu.js} +1 -1
  4. package/app/dist/assets/{index-C72UC2ga.js → index-BEGy_i8o.js} +1 -1
  5. package/app/dist/assets/{index-CqHjo2YT.js → index-BGy7ixjW.js} +1 -1
  6. package/app/dist/assets/{index-DVM3uoxc.js → index-BaLgiiKk.js} +1 -1
  7. package/app/dist/assets/{index-CW02bulk.js → index-BjlNcvOf.js} +1 -1
  8. package/app/dist/assets/{index-DmY6uqAw.js → index-CKfKYVw7.js} +1 -1
  9. package/app/dist/assets/{index-BLuaHLN3.js → index-COFbngzR.js} +1 -1
  10. package/app/dist/assets/{index-BBinZAiy.js → index-CPEo_rvd.js} +1 -1
  11. package/app/dist/assets/{index-D_Y6J00B.js → index-CQDCT-XT.js} +1 -1
  12. package/app/dist/assets/{index-COIPZ34u.js → index-CUmEjEeR.js} +1 -1
  13. package/app/dist/assets/{index-BgCNqcSo.js → index-CeV-Af4N.js} +1 -1
  14. package/app/dist/assets/{index-DW2zI-Ss.js → index-ChbH55h5.js} +1 -1
  15. package/app/dist/assets/index-CzvG5PZT.css +1 -0
  16. package/app/dist/assets/{index-ZLvRNfLb.js → index-D9-aYc3I.js} +1 -1
  17. package/app/dist/assets/{index-BwFn9q4x.js → index-DezxtfNV.js} +1 -1
  18. package/app/dist/assets/{index-CXFMPmtf.js → index-DrI4IfXE.js} +1 -1
  19. package/app/dist/assets/{index-DgIg-QAA.js → index-DwfxgjnU.js} +2 -2
  20. package/app/dist/assets/index-ogrpJNou.js +555 -0
  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 +534 -48
  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/InlineEditor.svelte +15 -5
  33. package/app/src/lib/components/ProseBlockCard.svelte +446 -0
  34. package/app/src/lib/components/ProseEditPanel.svelte +470 -0
  35. package/app/src/lib/components/RuneAttributes.svelte +51 -0
  36. package/app/src/lib/editor/block-parser.ts +211 -9
  37. package/dist/server.d.ts.map +1 -1
  38. package/dist/server.js +129 -1
  39. package/dist/server.js.map +1 -1
  40. package/package.json +6 -6
  41. package/app/dist/assets/index-BD2EBUrQ.css +0 -1
  42. package/app/dist/assets/index-BlAOhWAQ.js +0 -453
@@ -0,0 +1,519 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { fetchAssets, uploadAsset, type ImageAsset } from '../api/client.js';
4
+
5
+ interface Props {
6
+ /** Current image path (e.g. "/hero.png") or empty for new */
7
+ currentSrc: string;
8
+ /** Current alt text */
9
+ currentAlt: string;
10
+ onchange: (src: string, alt: string) => void;
11
+ onremove: () => void;
12
+ onclose: () => void;
13
+ }
14
+
15
+ let { currentSrc, currentAlt, onchange, onremove, onclose }: Props = $props();
16
+
17
+ let modalEl: HTMLDivElement;
18
+ let fileInput: HTMLInputElement;
19
+ let altInput: HTMLInputElement;
20
+
21
+ let images = $state<ImageAsset[]>([]);
22
+ let loading = $state(true);
23
+ let uploading = $state(false);
24
+ let search = $state('');
25
+ let editSrc = $state(currentSrc);
26
+ let editAlt = $state(currentAlt);
27
+ let dragOver = $state(false);
28
+
29
+ const filtered = $derived(
30
+ search
31
+ ? images.filter(img => img.name.toLowerCase().includes(search.toLowerCase()))
32
+ : images
33
+ );
34
+
35
+ onMount(() => {
36
+ loadImages();
37
+
38
+ function handleKeydown(e: KeyboardEvent) {
39
+ if (e.key === 'Escape') onclose();
40
+ }
41
+
42
+ document.addEventListener('keydown', handleKeydown);
43
+ return () => document.removeEventListener('keydown', handleKeydown);
44
+ });
45
+
46
+ async function loadImages() {
47
+ loading = true;
48
+ try {
49
+ images = await fetchAssets();
50
+ } catch {
51
+ images = [];
52
+ }
53
+ loading = false;
54
+ }
55
+
56
+ function handleBackdropClick(e: MouseEvent) {
57
+ if (e.target === e.currentTarget) {
58
+ onclose();
59
+ }
60
+ }
61
+
62
+ function selectImage(img: ImageAsset) {
63
+ editSrc = img.path;
64
+ // If alt text is empty, suggest the filename without extension
65
+ if (!editAlt) {
66
+ const name = img.name.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ');
67
+ editAlt = name;
68
+ }
69
+ }
70
+
71
+ function handleApply() {
72
+ if (editSrc) {
73
+ onchange(editSrc, editAlt);
74
+ }
75
+ }
76
+
77
+ function handleUploadClick() {
78
+ fileInput?.click();
79
+ }
80
+
81
+ async function handleFileSelected(e: Event) {
82
+ const input = e.target as HTMLInputElement;
83
+ const file = input.files?.[0];
84
+ if (!file) return;
85
+ await doUpload(file);
86
+ input.value = '';
87
+ }
88
+
89
+ async function doUpload(file: File) {
90
+ if (!file.type.startsWith('image/')) return;
91
+ uploading = true;
92
+ try {
93
+ const result = await uploadAsset(file);
94
+ // Refresh list and select the new image
95
+ await loadImages();
96
+ editSrc = result.path;
97
+ if (!editAlt) {
98
+ const name = result.name.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ');
99
+ editAlt = name;
100
+ }
101
+ } catch (err) {
102
+ console.error('Upload failed:', err);
103
+ }
104
+ uploading = false;
105
+ }
106
+
107
+ function handleDragOver(e: DragEvent) {
108
+ e.preventDefault();
109
+ dragOver = true;
110
+ }
111
+
112
+ function handleDragLeave() {
113
+ dragOver = false;
114
+ }
115
+
116
+ async function handleDrop(e: DragEvent) {
117
+ e.preventDefault();
118
+ dragOver = false;
119
+ const file = e.dataTransfer?.files[0];
120
+ if (file) await doUpload(file);
121
+ }
122
+
123
+ function handleAltKeydown(e: KeyboardEvent) {
124
+ if (e.key === 'Enter') {
125
+ e.preventDefault();
126
+ handleApply();
127
+ }
128
+ }
129
+ </script>
130
+
131
+ <div
132
+ class="image-edit-backdrop"
133
+ onclick={handleBackdropClick}
134
+ onkeydown={() => {}}
135
+ role="dialog"
136
+ aria-modal="true"
137
+ aria-label="Select image"
138
+ >
139
+ <div
140
+ class="image-edit-modal"
141
+ class:drag-over={dragOver}
142
+ bind:this={modalEl}
143
+ ondragover={handleDragOver}
144
+ ondragleave={handleDragLeave}
145
+ ondrop={handleDrop}
146
+ >
147
+ <!-- Header -->
148
+ <div class="image-edit-modal__header">
149
+ <span class="image-edit-modal__label">image</span>
150
+ <button class="image-edit-modal__close" onclick={onclose} aria-label="Close">&times;</button>
151
+ </div>
152
+
153
+ <!-- Search -->
154
+ <div class="image-edit-modal__search">
155
+ <input
156
+ class="image-edit-modal__search-input"
157
+ type="text"
158
+ placeholder="Search images..."
159
+ bind:value={search}
160
+ />
161
+ </div>
162
+
163
+ <!-- Grid -->
164
+ <div class="image-edit-modal__grid-area">
165
+ {#if loading}
166
+ <div class="image-edit-modal__empty">Loading images...</div>
167
+ {:else if filtered.length === 0 && !search}
168
+ <div class="image-edit-modal__empty">No images found. Upload one to get started.</div>
169
+ {:else if filtered.length === 0}
170
+ <div class="image-edit-modal__empty">No images match "{search}"</div>
171
+ {:else}
172
+ <div class="image-edit-modal__grid">
173
+ {#each filtered as img (img.path)}
174
+ <button
175
+ class="image-edit-modal__thumb"
176
+ class:selected={editSrc === img.path}
177
+ onclick={() => selectImage(img)}
178
+ title={img.name}
179
+ >
180
+ <img src={img.path} alt={img.name} loading="lazy" />
181
+ </button>
182
+ {/each}
183
+ </div>
184
+ {/if}
185
+ </div>
186
+
187
+ <!-- Upload bar -->
188
+ <div class="image-edit-modal__upload-bar">
189
+ <button
190
+ class="image-edit-modal__upload-btn"
191
+ onclick={handleUploadClick}
192
+ disabled={uploading}
193
+ >
194
+ {uploading ? 'Uploading...' : 'Upload image'}
195
+ </button>
196
+ <span class="image-edit-modal__upload-hint">or drag & drop</span>
197
+ <input
198
+ type="file"
199
+ accept="image/*"
200
+ bind:this={fileInput}
201
+ onchange={handleFileSelected}
202
+ style="display: none"
203
+ />
204
+ </div>
205
+
206
+ <!-- Footer: alt text + actions -->
207
+ <div class="image-edit-modal__footer">
208
+ <div class="image-edit-modal__row">
209
+ <label class="image-edit-modal__field-label" for="image-alt">Alt</label>
210
+ <input
211
+ id="image-alt"
212
+ class="image-edit-modal__input"
213
+ type="text"
214
+ bind:value={editAlt}
215
+ bind:this={altInput}
216
+ onkeydown={handleAltKeydown}
217
+ placeholder="Describe this image..."
218
+ />
219
+ </div>
220
+ <div class="image-edit-modal__actions">
221
+ <button
222
+ class="image-edit-modal__btn image-edit-modal__btn--apply"
223
+ onclick={handleApply}
224
+ disabled={!editSrc}
225
+ >Apply</button>
226
+ <button
227
+ class="image-edit-modal__btn image-edit-modal__btn--remove"
228
+ onclick={onremove}
229
+ >Remove</button>
230
+ </div>
231
+ </div>
232
+ </div>
233
+ </div>
234
+
235
+ <style>
236
+ /* ── Backdrop ─────────────────────────────────────────── */
237
+
238
+ .image-edit-backdrop {
239
+ position: fixed;
240
+ inset: 0;
241
+ z-index: 1100;
242
+ display: flex;
243
+ align-items: center;
244
+ justify-content: center;
245
+ background: rgba(0, 0, 0, 0.3);
246
+ animation: image-edit-backdrop-in 120ms ease-out;
247
+ }
248
+
249
+ @keyframes image-edit-backdrop-in {
250
+ from { opacity: 0; }
251
+ to { opacity: 1; }
252
+ }
253
+
254
+ /* ── Modal ────────────────────────────────────────────── */
255
+
256
+ .image-edit-modal {
257
+ z-index: 1101;
258
+ display: flex;
259
+ flex-direction: column;
260
+ width: 90vw;
261
+ max-width: 580px;
262
+ max-height: 80vh;
263
+ background: var(--ed-surface-0, #fff);
264
+ border: 1px solid var(--ed-border-default, #e2e8f0);
265
+ border-radius: var(--ed-radius-lg, 8px);
266
+ box-shadow: var(--ed-shadow-xl, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1));
267
+ animation: image-edit-enter 120ms ease-out;
268
+ overflow: hidden;
269
+ }
270
+
271
+ .image-edit-modal.drag-over {
272
+ outline: 2px dashed var(--ed-accent, #3b82f6);
273
+ outline-offset: -2px;
274
+ }
275
+
276
+ @keyframes image-edit-enter {
277
+ from { opacity: 0; transform: scale(0.97) translateY(8px); }
278
+ to { opacity: 1; transform: scale(1) translateY(0); }
279
+ }
280
+
281
+ /* ── Header ──────────────────────────────────────────── */
282
+
283
+ .image-edit-modal__header {
284
+ display: flex;
285
+ align-items: center;
286
+ justify-content: space-between;
287
+ padding: var(--ed-space-2, 0.5rem) var(--ed-space-3, 0.75rem);
288
+ border-bottom: 1px solid var(--ed-border-default, #e2e8f0);
289
+ }
290
+
291
+ .image-edit-modal__label {
292
+ font-size: 10px;
293
+ font-weight: 700;
294
+ color: var(--ed-text-muted, #94a3b8);
295
+ text-transform: uppercase;
296
+ letter-spacing: 0.04em;
297
+ }
298
+
299
+ .image-edit-modal__close {
300
+ background: none;
301
+ border: none;
302
+ font-size: 18px;
303
+ line-height: 1;
304
+ color: var(--ed-text-muted, #94a3b8);
305
+ cursor: pointer;
306
+ padding: 0 4px;
307
+ }
308
+
309
+ .image-edit-modal__close:hover {
310
+ color: var(--ed-text-primary, #1a1a2e);
311
+ }
312
+
313
+ /* ── Search ──────────────────────────────────────────── */
314
+
315
+ .image-edit-modal__search {
316
+ padding: var(--ed-space-2, 0.5rem) var(--ed-space-3, 0.75rem);
317
+ }
318
+
319
+ .image-edit-modal__search-input {
320
+ width: 100%;
321
+ padding: var(--ed-space-1, 0.25rem) var(--ed-space-2, 0.5rem);
322
+ border: 1px solid var(--ed-border-default, #e2e8f0);
323
+ border-radius: var(--ed-radius-sm, 4px);
324
+ font-size: var(--ed-text-sm, 13px);
325
+ color: var(--ed-text-primary, #1a1a2e);
326
+ background: var(--ed-surface-0, #fff);
327
+ outline: none;
328
+ font-family: inherit;
329
+ box-sizing: border-box;
330
+ }
331
+
332
+ .image-edit-modal__search-input:focus {
333
+ border-color: var(--ed-accent, #3b82f6);
334
+ box-shadow: 0 0 0 2px var(--ed-accent-ring, rgba(59, 130, 246, 0.2));
335
+ }
336
+
337
+ /* ── Grid area ───────────────────────────────────────── */
338
+
339
+ .image-edit-modal__grid-area {
340
+ flex: 1;
341
+ overflow-y: auto;
342
+ padding: 0 var(--ed-space-3, 0.75rem);
343
+ min-height: 120px;
344
+ max-height: 320px;
345
+ }
346
+
347
+ .image-edit-modal__grid {
348
+ display: grid;
349
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
350
+ gap: 8px;
351
+ padding-bottom: var(--ed-space-2, 0.5rem);
352
+ }
353
+
354
+ .image-edit-modal__empty {
355
+ display: flex;
356
+ align-items: center;
357
+ justify-content: center;
358
+ height: 120px;
359
+ color: var(--ed-text-muted, #94a3b8);
360
+ font-size: var(--ed-text-sm, 13px);
361
+ }
362
+
363
+ /* ── Thumbnail ───────────────────────────────────────── */
364
+
365
+ .image-edit-modal__thumb {
366
+ position: relative;
367
+ aspect-ratio: 1;
368
+ border: 2px solid var(--ed-border-default, #e2e8f0);
369
+ border-radius: var(--ed-radius-sm, 4px);
370
+ overflow: hidden;
371
+ cursor: pointer;
372
+ padding: 0;
373
+ background: var(--ed-surface-1, #f8fafc);
374
+ transition: border-color 100ms;
375
+ }
376
+
377
+ .image-edit-modal__thumb:hover {
378
+ border-color: var(--ed-text-muted, #94a3b8);
379
+ }
380
+
381
+ .image-edit-modal__thumb.selected {
382
+ border-color: var(--ed-accent, #3b82f6);
383
+ box-shadow: 0 0 0 2px var(--ed-accent-ring, rgba(59, 130, 246, 0.3));
384
+ }
385
+
386
+ .image-edit-modal__thumb img {
387
+ width: 100%;
388
+ height: 100%;
389
+ object-fit: cover;
390
+ display: block;
391
+ }
392
+
393
+ /* ── Upload bar ──────────────────────────────────────── */
394
+
395
+ .image-edit-modal__upload-bar {
396
+ display: flex;
397
+ align-items: center;
398
+ gap: var(--ed-space-2, 0.5rem);
399
+ padding: var(--ed-space-2, 0.5rem) var(--ed-space-3, 0.75rem);
400
+ border-top: 1px solid var(--ed-border-default, #e2e8f0);
401
+ }
402
+
403
+ .image-edit-modal__upload-btn {
404
+ padding: var(--ed-space-1, 0.25rem) var(--ed-space-3, 0.75rem);
405
+ border: 1px solid var(--ed-border-default, #e2e8f0);
406
+ border-radius: var(--ed-radius-sm, 4px);
407
+ font-size: 12px;
408
+ font-weight: 500;
409
+ cursor: pointer;
410
+ background: var(--ed-surface-0, #fff);
411
+ color: var(--ed-text-secondary, #475569);
412
+ transition: background 100ms;
413
+ }
414
+
415
+ .image-edit-modal__upload-btn:hover:not(:disabled) {
416
+ background: var(--ed-surface-2, #f1f5f9);
417
+ }
418
+
419
+ .image-edit-modal__upload-btn:disabled {
420
+ opacity: 0.6;
421
+ cursor: not-allowed;
422
+ }
423
+
424
+ .image-edit-modal__upload-hint {
425
+ font-size: 11px;
426
+ color: var(--ed-text-muted, #94a3b8);
427
+ }
428
+
429
+ /* ── Footer ──────────────────────────────────────────── */
430
+
431
+ .image-edit-modal__footer {
432
+ display: flex;
433
+ flex-direction: column;
434
+ gap: var(--ed-space-2, 0.5rem);
435
+ padding: var(--ed-space-2, 0.5rem) var(--ed-space-3, 0.75rem);
436
+ border-top: 1px solid var(--ed-border-default, #e2e8f0);
437
+ }
438
+
439
+ .image-edit-modal__row {
440
+ display: flex;
441
+ align-items: center;
442
+ gap: var(--ed-space-2, 0.5rem);
443
+ }
444
+
445
+ .image-edit-modal__field-label {
446
+ font-size: 10px;
447
+ font-weight: 600;
448
+ color: var(--ed-text-muted, #94a3b8);
449
+ text-transform: uppercase;
450
+ letter-spacing: 0.03em;
451
+ width: 24px;
452
+ flex-shrink: 0;
453
+ }
454
+
455
+ .image-edit-modal__input {
456
+ flex: 1;
457
+ min-width: 0;
458
+ padding: var(--ed-space-1, 0.25rem) var(--ed-space-2, 0.5rem);
459
+ border: 1px solid var(--ed-border-default, #e2e8f0);
460
+ border-radius: var(--ed-radius-sm, 4px);
461
+ font-size: var(--ed-text-sm, 13px);
462
+ color: var(--ed-text-primary, #1a1a2e);
463
+ background: var(--ed-surface-0, #fff);
464
+ outline: none;
465
+ font-family: inherit;
466
+ line-height: 1.4;
467
+ }
468
+
469
+ .image-edit-modal__input:focus {
470
+ border-color: var(--ed-accent, #3b82f6);
471
+ box-shadow: 0 0 0 2px var(--ed-accent-ring, rgba(59, 130, 246, 0.2));
472
+ }
473
+
474
+ /* ── Actions ─────────────────────────────────────────── */
475
+
476
+ .image-edit-modal__actions {
477
+ display: flex;
478
+ gap: var(--ed-space-2, 0.5rem);
479
+ }
480
+
481
+ .image-edit-modal__btn {
482
+ display: flex;
483
+ align-items: center;
484
+ justify-content: center;
485
+ gap: var(--ed-space-2);
486
+ height: 28px;
487
+ padding: 0 var(--ed-space-3);
488
+ border: 1px solid var(--ed-text-secondary);
489
+ border-radius: var(--ed-radius-sm);
490
+ background: transparent;
491
+ color: var(--ed-text-secondary);
492
+ font-size: var(--ed-text-sm);
493
+ font-weight: 500;
494
+ cursor: pointer;
495
+ transition: background var(--ed-transition-fast), color var(--ed-transition-fast), border-color var(--ed-transition-fast);
496
+ white-space: nowrap;
497
+ }
498
+
499
+ .image-edit-modal__btn:hover:not(.image-edit-modal__btn--apply):not(:disabled) {
500
+ border-color: var(--ed-text-primary);
501
+ color: var(--ed-text-primary);
502
+ }
503
+
504
+ .image-edit-modal__btn--apply {
505
+ background: var(--ed-text-primary);
506
+ color: #ffffff;
507
+ border-color: var(--ed-text-primary);
508
+ }
509
+
510
+ .image-edit-modal__btn--apply:hover:not(:disabled) {
511
+ background: var(--ed-text-secondary);
512
+ border-color: var(--ed-text-secondary);
513
+ }
514
+
515
+ .image-edit-modal__btn--apply:disabled {
516
+ opacity: 0.4;
517
+ cursor: not-allowed;
518
+ }
519
+ </style>
@@ -289,39 +289,42 @@
289
289
  bind:this={popoverEl}
290
290
  style="left: {left}px; top: {top}px"
291
291
  >
292
- <!-- Toolbar row -->
293
- <div class="inline-edit-popover__toolbar">
292
+ <!-- Titlebar -->
293
+ <div class="inline-edit-popover__header">
294
294
  <span class="inline-edit-popover__label">{dataName}</span>
295
- <div class="inline-edit-popover__toolbar-buttons">
296
- <button
297
- class="inline-edit-popover__tool-btn"
298
- class:active={isBold}
299
- onclick={() => toggleFormat('bold')}
300
- title="Bold (Ctrl+B)"
301
- ><strong>B</strong></button>
302
- <button
303
- class="inline-edit-popover__tool-btn"
304
- class:active={isItalic}
305
- onclick={() => toggleFormat('italic')}
306
- title="Italic (Ctrl+I)"
307
- ><em>I</em></button>
308
- <button
309
- class="inline-edit-popover__tool-btn inline-edit-popover__tool-btn--code"
310
- class:active={isCode}
311
- onclick={() => toggleFormat('code')}
312
- title="Inline code"
313
- >&lt;/&gt;</button>
314
- <button
315
- class="inline-edit-popover__tool-btn"
316
- onclick={openLinkEditor}
317
- title="Link (Ctrl+K)"
318
- >
319
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
320
- <path d="M6.5 9.5a3 3 0 0 0 4.2.3l2-2a3 3 0 0 0-4.2-4.3l-1.1 1.1" />
321
- <path d="M9.5 6.5a3 3 0 0 0-4.2-.3l-2 2a3 3 0 0 0 4.2 4.3l1.1-1.1" />
322
- </svg>
323
- </button>
324
- </div>
295
+ <button class="inline-edit-popover__close" onclick={onclose} aria-label="Close">&times;</button>
296
+ </div>
297
+
298
+ <!-- Toolbar -->
299
+ <div class="inline-edit-popover__toolbar">
300
+ <button
301
+ class="inline-edit-popover__tool-btn"
302
+ class:active={isBold}
303
+ onclick={() => toggleFormat('bold')}
304
+ title="Bold (Ctrl+B)"
305
+ ><strong>B</strong></button>
306
+ <button
307
+ class="inline-edit-popover__tool-btn"
308
+ class:active={isItalic}
309
+ onclick={() => toggleFormat('italic')}
310
+ title="Italic (Ctrl+I)"
311
+ ><em>I</em></button>
312
+ <button
313
+ class="inline-edit-popover__tool-btn inline-edit-popover__tool-btn--code"
314
+ class:active={isCode}
315
+ onclick={() => toggleFormat('code')}
316
+ title="Inline code"
317
+ >&lt;/&gt;</button>
318
+ <button
319
+ class="inline-edit-popover__tool-btn"
320
+ onclick={openLinkEditor}
321
+ title="Link (Ctrl+K)"
322
+ >
323
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
324
+ <path d="M6.5 9.5a3 3 0 0 0 4.2.3l2-2a3 3 0 0 0-4.2-4.3l-1.1 1.1" />
325
+ <path d="M9.5 6.5a3 3 0 0 0-4.2-.3l-2 2a3 3 0 0 0 4.2 4.3l1.1-1.1" />
326
+ </svg>
327
+ </button>
325
328
  </div>
326
329
 
327
330
  <!-- Editable content area -->
@@ -399,12 +402,12 @@
399
402
  to { opacity: 1; transform: scale(1) translateY(0); }
400
403
  }
401
404
 
402
- /* ── Toolbar ──────────────────────────────────────────── */
405
+ /* ── Header ──────────────────────────────────────────── */
403
406
 
404
- .inline-edit-popover__toolbar {
407
+ .inline-edit-popover__header {
405
408
  display: flex;
406
409
  align-items: center;
407
- gap: var(--ed-space-2, 0.5rem);
410
+ justify-content: space-between;
408
411
  padding: 0 var(--ed-space-1, 0.25rem);
409
412
  }
410
413
 
@@ -418,11 +421,27 @@
418
421
  flex-shrink: 0;
419
422
  }
420
423
 
421
- .inline-edit-popover__toolbar-buttons {
424
+ .inline-edit-popover__close {
425
+ background: none;
426
+ border: none;
427
+ font-size: 18px;
428
+ line-height: 1;
429
+ color: var(--ed-text-muted, #94a3b8);
430
+ cursor: pointer;
431
+ padding: 0 4px;
432
+ }
433
+
434
+ .inline-edit-popover__close:hover {
435
+ color: var(--ed-text-primary, #1a1a2e);
436
+ }
437
+
438
+ /* ── Toolbar ──────────────────────────────────────────── */
439
+
440
+ .inline-edit-popover__toolbar {
422
441
  display: flex;
423
442
  align-items: center;
424
443
  gap: 2px;
425
- margin-left: auto;
444
+ padding: 0 var(--ed-space-1, 0.25rem);
426
445
  }
427
446
 
428
447
  .inline-edit-popover__tool-btn {
@@ -562,32 +581,36 @@
562
581
  }
563
582
 
564
583
  .inline-edit-popover__link-btn {
565
- padding: var(--ed-space-1, 0.25rem) var(--ed-space-2, 0.5rem);
566
- border: 1px solid var(--ed-border-default, #e2e8f0);
567
- border-radius: var(--ed-radius-sm, 4px);
568
- font-size: 11px;
584
+ display: flex;
585
+ align-items: center;
586
+ justify-content: center;
587
+ gap: var(--ed-space-2);
588
+ height: 28px;
589
+ padding: 0 var(--ed-space-3);
590
+ border: 1px solid var(--ed-text-secondary);
591
+ border-radius: var(--ed-radius-sm);
592
+ background: transparent;
593
+ color: var(--ed-text-secondary);
594
+ font-size: var(--ed-text-sm);
569
595
  font-weight: 500;
570
596
  cursor: pointer;
571
- transition: background 100ms, color 100ms;
572
- }
573
-
574
- .inline-edit-popover__link-btn--apply {
575
- background: var(--ed-accent, #3b82f6);
576
- border-color: var(--ed-accent, #3b82f6);
577
- color: white;
597
+ transition: background var(--ed-transition-fast), color var(--ed-transition-fast), border-color var(--ed-transition-fast);
598
+ white-space: nowrap;
578
599
  }
579
600
 
580
- .inline-edit-popover__link-btn--apply:hover {
581
- opacity: 0.9;
601
+ .inline-edit-popover__link-btn:hover:not(.inline-edit-popover__link-btn--apply):not(:disabled) {
602
+ border-color: var(--ed-text-primary);
603
+ color: var(--ed-text-primary);
582
604
  }
583
605
 
584
- .inline-edit-popover__link-btn--remove {
585
- background: transparent;
586
- color: var(--ed-text-muted, #94a3b8);
606
+ .inline-edit-popover__link-btn--apply {
607
+ background: var(--ed-text-primary);
608
+ color: #ffffff;
609
+ border-color: var(--ed-text-primary);
587
610
  }
588
611
 
589
- .inline-edit-popover__link-btn--remove:hover {
590
- color: var(--ed-text-secondary, #475569);
591
- background: var(--ed-surface-2, #f1f5f9);
612
+ .inline-edit-popover__link-btn--apply:hover:not(:disabled) {
613
+ background: var(--ed-text-secondary);
614
+ border-color: var(--ed-text-secondary);
592
615
  }
593
616
  </style>