@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
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { RuneInfo } from '../api/client.js';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
runes: RuneInfo[];
|
|
6
|
+
runesByCategory: Map<string, RuneInfo[]>;
|
|
7
|
+
oninsert: (type: 'heading' | 'paragraph' | 'fence' | 'hr' | 'rune', runeName?: string) => void;
|
|
8
|
+
onclose: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { runes, runesByCategory, oninsert, onclose }: Props = $props();
|
|
12
|
+
|
|
13
|
+
// Preferred tab order
|
|
14
|
+
const TAB_ORDER = ['Content', 'Section', 'Layout', 'Code & Data', 'Semantic', 'Design', 'Site'];
|
|
15
|
+
|
|
16
|
+
let activeTab = $state('Content');
|
|
17
|
+
let search = $state('');
|
|
18
|
+
let dialogEl: HTMLDialogElement;
|
|
19
|
+
let searchEl: HTMLInputElement;
|
|
20
|
+
|
|
21
|
+
// Sorted category names following TAB_ORDER
|
|
22
|
+
let categories = $derived.by(() => {
|
|
23
|
+
const present = new Set(runesByCategory.keys());
|
|
24
|
+
const ordered = TAB_ORDER.filter(c => present.has(c));
|
|
25
|
+
// Append any categories not in TAB_ORDER
|
|
26
|
+
for (const c of present) {
|
|
27
|
+
if (!ordered.includes(c)) ordered.push(c);
|
|
28
|
+
}
|
|
29
|
+
return ordered;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Filtered runes when searching
|
|
33
|
+
let searchResults = $derived.by(() => {
|
|
34
|
+
if (!search.trim()) return null;
|
|
35
|
+
const q = search.toLowerCase();
|
|
36
|
+
return runes.filter(r =>
|
|
37
|
+
r.name.toLowerCase().includes(q) ||
|
|
38
|
+
(r.description && r.description.toLowerCase().includes(q))
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Standard content blocks (shown in Content tab)
|
|
43
|
+
const standardBlocks = [
|
|
44
|
+
{ type: 'heading' as const, name: 'Heading', description: 'Section heading', icon: 'M3 3v10M13 3v10M3 8h10' },
|
|
45
|
+
{ type: 'paragraph' as const, name: 'Paragraph', description: 'Body text', icon: 'M2 4h12M2 8h12M2 12h8' },
|
|
46
|
+
{ type: 'fence' as const, name: 'Code Block', description: 'Fenced code', icon: 'M5 4L2 8l3 4M11 4l3 4-3 4' },
|
|
47
|
+
{ type: 'hr' as const, name: 'Divider', description: 'Horizontal rule', icon: 'M2 8h12' },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
$effect(() => {
|
|
51
|
+
dialogEl?.showModal();
|
|
52
|
+
// Focus search on open
|
|
53
|
+
searchEl?.focus();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function handleBackdropClick(e: MouseEvent) {
|
|
57
|
+
if (e.target === dialogEl) onclose();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function handleInsertStandard(type: 'heading' | 'paragraph' | 'fence' | 'hr') {
|
|
61
|
+
oninsert(type);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleInsertRune(name: string) {
|
|
65
|
+
oninsert('rune', name);
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
70
|
+
<dialog
|
|
71
|
+
bind:this={dialogEl}
|
|
72
|
+
class="insert-dialog"
|
|
73
|
+
onclose={onclose}
|
|
74
|
+
onclick={handleBackdropClick}
|
|
75
|
+
onkeydown={(e) => { if (e.key === 'Escape') { e.preventDefault(); onclose(); } }}
|
|
76
|
+
>
|
|
77
|
+
<div class="insert-dialog__inner">
|
|
78
|
+
<!-- Header -->
|
|
79
|
+
<div class="insert-dialog__header">
|
|
80
|
+
<h2 class="insert-dialog__title">Insert Block</h2>
|
|
81
|
+
<div class="insert-dialog__search-wrap">
|
|
82
|
+
<svg class="insert-dialog__search-icon" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">
|
|
83
|
+
<circle cx="7" cy="7" r="4.5" />
|
|
84
|
+
<path d="M10.5 10.5L14 14" />
|
|
85
|
+
</svg>
|
|
86
|
+
<input
|
|
87
|
+
bind:this={searchEl}
|
|
88
|
+
class="insert-dialog__search"
|
|
89
|
+
type="text"
|
|
90
|
+
placeholder="Search blocks..."
|
|
91
|
+
bind:value={search}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
<button class="insert-dialog__close" onclick={onclose}>
|
|
95
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">
|
|
96
|
+
<path d="M4 4l8 8M12 4l-8 8" />
|
|
97
|
+
</svg>
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<!-- Tabs -->
|
|
102
|
+
{#if !searchResults}
|
|
103
|
+
<div class="insert-dialog__tabs">
|
|
104
|
+
{#each categories as category}
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
class="insert-dialog__tab"
|
|
108
|
+
class:active={activeTab === category}
|
|
109
|
+
onclick={() => activeTab = category}
|
|
110
|
+
>
|
|
111
|
+
{category}
|
|
112
|
+
</button>
|
|
113
|
+
{/each}
|
|
114
|
+
</div>
|
|
115
|
+
{/if}
|
|
116
|
+
|
|
117
|
+
<!-- Body -->
|
|
118
|
+
<div class="insert-dialog__body">
|
|
119
|
+
{#if searchResults}
|
|
120
|
+
<!-- Search results -->
|
|
121
|
+
{#if searchResults.length === 0}
|
|
122
|
+
<div class="insert-dialog__empty">No blocks match "{search}"</div>
|
|
123
|
+
{:else}
|
|
124
|
+
<div class="insert-dialog__grid">
|
|
125
|
+
{#each searchResults as rune}
|
|
126
|
+
<button
|
|
127
|
+
class="insert-dialog__btn insert-dialog__btn--rune"
|
|
128
|
+
onclick={() => handleInsertRune(rune.name)}
|
|
129
|
+
>
|
|
130
|
+
<span class="insert-dialog__rune-dot"></span>
|
|
131
|
+
<span class="insert-dialog__rune-info">
|
|
132
|
+
<span class="insert-dialog__rune-name">{rune.name}</span>
|
|
133
|
+
{#if rune.description}
|
|
134
|
+
<span class="insert-dialog__rune-desc">{rune.description}</span>
|
|
135
|
+
{/if}
|
|
136
|
+
</span>
|
|
137
|
+
</button>
|
|
138
|
+
{/each}
|
|
139
|
+
</div>
|
|
140
|
+
{/if}
|
|
141
|
+
{:else}
|
|
142
|
+
<!-- Tab content -->
|
|
143
|
+
<div class="insert-dialog__grid">
|
|
144
|
+
{#if activeTab === 'Content'}
|
|
145
|
+
<!-- Standard blocks first -->
|
|
146
|
+
{#each standardBlocks as block}
|
|
147
|
+
<button
|
|
148
|
+
class="insert-dialog__btn"
|
|
149
|
+
onclick={() => handleInsertStandard(block.type)}
|
|
150
|
+
>
|
|
151
|
+
<svg class="insert-dialog__btn-icon" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
152
|
+
<path d={block.icon} />
|
|
153
|
+
</svg>
|
|
154
|
+
<span class="insert-dialog__btn-info">
|
|
155
|
+
<span class="insert-dialog__btn-name">{block.name}</span>
|
|
156
|
+
<span class="insert-dialog__btn-desc">{block.description}</span>
|
|
157
|
+
</span>
|
|
158
|
+
</button>
|
|
159
|
+
{/each}
|
|
160
|
+
{/if}
|
|
161
|
+
<!-- Runes for active category -->
|
|
162
|
+
{#each runesByCategory.get(activeTab) ?? [] as rune}
|
|
163
|
+
<button
|
|
164
|
+
class="insert-dialog__btn insert-dialog__btn--rune"
|
|
165
|
+
onclick={() => handleInsertRune(rune.name)}
|
|
166
|
+
>
|
|
167
|
+
<span class="insert-dialog__rune-dot"></span>
|
|
168
|
+
<span class="insert-dialog__rune-info">
|
|
169
|
+
<span class="insert-dialog__rune-name">{rune.name}</span>
|
|
170
|
+
{#if rune.description}
|
|
171
|
+
<span class="insert-dialog__rune-desc">{rune.description}</span>
|
|
172
|
+
{/if}
|
|
173
|
+
</span>
|
|
174
|
+
</button>
|
|
175
|
+
{/each}
|
|
176
|
+
</div>
|
|
177
|
+
{/if}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</dialog>
|
|
181
|
+
|
|
182
|
+
<style>
|
|
183
|
+
.insert-dialog {
|
|
184
|
+
border: none;
|
|
185
|
+
border-radius: var(--ed-radius-lg, 12px);
|
|
186
|
+
background: var(--ed-surface-0);
|
|
187
|
+
box-shadow: var(--ed-shadow-lg);
|
|
188
|
+
padding: 0;
|
|
189
|
+
margin: auto;
|
|
190
|
+
width: 960px;
|
|
191
|
+
max-width: calc(100vw - 2rem);
|
|
192
|
+
height: min(600px, calc(100vh - 4rem));
|
|
193
|
+
display: flex;
|
|
194
|
+
flex-direction: column;
|
|
195
|
+
overflow: hidden;
|
|
196
|
+
animation: dialog-enter 0.15s ease-out;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.insert-dialog::backdrop {
|
|
200
|
+
background: rgba(0, 0, 0, 0.3);
|
|
201
|
+
backdrop-filter: blur(2px);
|
|
202
|
+
animation: backdrop-fade 0.15s ease-out;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@keyframes dialog-enter {
|
|
206
|
+
from { opacity: 0; transform: translateY(8px) scale(0.98); }
|
|
207
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@keyframes backdrop-fade {
|
|
211
|
+
from { opacity: 0; }
|
|
212
|
+
to { opacity: 1; }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.insert-dialog__inner {
|
|
216
|
+
display: flex;
|
|
217
|
+
flex-direction: column;
|
|
218
|
+
min-height: 0;
|
|
219
|
+
height: 100%;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* Header */
|
|
223
|
+
.insert-dialog__header {
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
gap: var(--ed-space-3);
|
|
227
|
+
padding: var(--ed-space-3) var(--ed-space-4);
|
|
228
|
+
border-bottom: 1px solid var(--ed-border-subtle);
|
|
229
|
+
flex-shrink: 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.insert-dialog__title {
|
|
233
|
+
font-size: var(--ed-text-md);
|
|
234
|
+
font-weight: 600;
|
|
235
|
+
color: var(--ed-text-primary);
|
|
236
|
+
margin: 0;
|
|
237
|
+
white-space: nowrap;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.insert-dialog__search-wrap {
|
|
241
|
+
flex: 1;
|
|
242
|
+
position: relative;
|
|
243
|
+
max-width: 240px;
|
|
244
|
+
margin-left: auto;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.insert-dialog__search-icon {
|
|
248
|
+
position: absolute;
|
|
249
|
+
left: 0.5rem;
|
|
250
|
+
top: 50%;
|
|
251
|
+
transform: translateY(-50%);
|
|
252
|
+
color: var(--ed-text-muted);
|
|
253
|
+
pointer-events: none;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.insert-dialog__search {
|
|
257
|
+
width: 100%;
|
|
258
|
+
padding: var(--ed-space-1) var(--ed-space-2) var(--ed-space-1) 1.8rem;
|
|
259
|
+
border: 1px solid var(--ed-border-default);
|
|
260
|
+
border-radius: var(--ed-radius-sm);
|
|
261
|
+
font-size: var(--ed-text-sm);
|
|
262
|
+
color: var(--ed-text-primary);
|
|
263
|
+
background: var(--ed-surface-1);
|
|
264
|
+
outline: none;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.insert-dialog__search:focus {
|
|
268
|
+
border-color: var(--ed-accent);
|
|
269
|
+
box-shadow: 0 0 0 2px var(--ed-accent-ring);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.insert-dialog__close {
|
|
273
|
+
flex-shrink: 0;
|
|
274
|
+
display: flex;
|
|
275
|
+
align-items: center;
|
|
276
|
+
justify-content: center;
|
|
277
|
+
width: 28px;
|
|
278
|
+
height: 28px;
|
|
279
|
+
border: none;
|
|
280
|
+
border-radius: var(--ed-radius-sm);
|
|
281
|
+
background: transparent;
|
|
282
|
+
color: var(--ed-text-muted);
|
|
283
|
+
cursor: pointer;
|
|
284
|
+
transition: background var(--ed-transition-fast), color var(--ed-transition-fast);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.insert-dialog__close:hover {
|
|
288
|
+
background: var(--ed-surface-2);
|
|
289
|
+
color: var(--ed-text-secondary);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Tabs */
|
|
293
|
+
.insert-dialog__tabs {
|
|
294
|
+
display: flex;
|
|
295
|
+
gap: 2px;
|
|
296
|
+
padding: var(--ed-space-2) var(--ed-space-4);
|
|
297
|
+
border-bottom: 1px solid var(--ed-border-subtle);
|
|
298
|
+
background: var(--ed-surface-1);
|
|
299
|
+
flex-shrink: 0;
|
|
300
|
+
overflow-x: auto;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.insert-dialog__tab {
|
|
304
|
+
padding: 0.3rem 0.7rem;
|
|
305
|
+
border: none;
|
|
306
|
+
background: transparent;
|
|
307
|
+
color: var(--ed-text-muted);
|
|
308
|
+
font-size: var(--ed-text-sm);
|
|
309
|
+
font-weight: 500;
|
|
310
|
+
cursor: pointer;
|
|
311
|
+
border-radius: var(--ed-radius-sm);
|
|
312
|
+
white-space: nowrap;
|
|
313
|
+
transition: background var(--ed-transition-fast), color var(--ed-transition-fast);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.insert-dialog__tab:hover {
|
|
317
|
+
color: var(--ed-text-secondary);
|
|
318
|
+
background: var(--ed-surface-2);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.insert-dialog__tab.active {
|
|
322
|
+
background: var(--ed-surface-0);
|
|
323
|
+
color: var(--ed-text-primary);
|
|
324
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* Body */
|
|
328
|
+
.insert-dialog__body {
|
|
329
|
+
flex: 1;
|
|
330
|
+
min-height: 0;
|
|
331
|
+
overflow-y: auto;
|
|
332
|
+
padding: var(--ed-space-4);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.insert-dialog__empty {
|
|
336
|
+
text-align: center;
|
|
337
|
+
padding: 2rem;
|
|
338
|
+
color: var(--ed-text-muted);
|
|
339
|
+
font-size: var(--ed-text-sm);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* Grid */
|
|
343
|
+
.insert-dialog__grid {
|
|
344
|
+
display: grid;
|
|
345
|
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
346
|
+
gap: 0.4rem;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* Block buttons */
|
|
350
|
+
.insert-dialog__btn {
|
|
351
|
+
display: flex;
|
|
352
|
+
align-items: flex-start;
|
|
353
|
+
gap: 0.5rem;
|
|
354
|
+
padding: var(--ed-space-2) var(--ed-space-3);
|
|
355
|
+
border: 1px solid var(--ed-border-default);
|
|
356
|
+
border-radius: var(--ed-radius-sm);
|
|
357
|
+
background: var(--ed-surface-0);
|
|
358
|
+
color: var(--ed-text-secondary);
|
|
359
|
+
font-size: var(--ed-text-sm);
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
text-align: left;
|
|
362
|
+
transition: background var(--ed-transition-fast), border-color var(--ed-transition-fast);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.insert-dialog__btn:hover {
|
|
366
|
+
background: var(--ed-accent-muted);
|
|
367
|
+
border-color: var(--ed-accent);
|
|
368
|
+
color: var(--ed-heading);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.insert-dialog__btn-icon {
|
|
372
|
+
flex-shrink: 0;
|
|
373
|
+
opacity: 0.6;
|
|
374
|
+
margin-top: 0.15rem;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.insert-dialog__btn-info {
|
|
378
|
+
display: flex;
|
|
379
|
+
flex-direction: column;
|
|
380
|
+
gap: 0.1rem;
|
|
381
|
+
min-width: 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.insert-dialog__btn-name {
|
|
385
|
+
font-weight: 500;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.insert-dialog__btn-desc {
|
|
389
|
+
font-size: 10px;
|
|
390
|
+
color: var(--ed-text-muted);
|
|
391
|
+
overflow: hidden;
|
|
392
|
+
text-overflow: ellipsis;
|
|
393
|
+
white-space: nowrap;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* Rune buttons */
|
|
397
|
+
.insert-dialog__rune-dot {
|
|
398
|
+
width: 6px;
|
|
399
|
+
height: 6px;
|
|
400
|
+
border-radius: 50%;
|
|
401
|
+
background: var(--ed-warning);
|
|
402
|
+
flex-shrink: 0;
|
|
403
|
+
margin-top: 0.35rem;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.insert-dialog__rune-info {
|
|
407
|
+
display: flex;
|
|
408
|
+
flex-direction: column;
|
|
409
|
+
gap: 0.1rem;
|
|
410
|
+
min-width: 0;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.insert-dialog__rune-name {
|
|
414
|
+
font-weight: 500;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.insert-dialog__rune-desc {
|
|
418
|
+
font-size: 10px;
|
|
419
|
+
color: var(--ed-text-muted);
|
|
420
|
+
overflow: hidden;
|
|
421
|
+
text-overflow: ellipsis;
|
|
422
|
+
white-space: nowrap;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.insert-dialog__btn--rune:hover {
|
|
426
|
+
background: var(--ed-warning-subtle);
|
|
427
|
+
border-color: var(--ed-warning);
|
|
428
|
+
}
|
|
429
|
+
</style>
|
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
|
|
11
11
|
// Viewport presets for preview mode card width
|
|
12
12
|
const VIEWPORT_WIDTHS: Record<string, number> = {
|
|
13
|
-
desktop:
|
|
13
|
+
desktop: 1440,
|
|
14
14
|
tablet: 768,
|
|
15
15
|
mobile: 375,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const cardMaxWidth = $derived(
|
|
19
19
|
editorState.editorMode === 'preview'
|
|
20
|
-
? VIEWPORT_WIDTHS[editorState.viewport] ??
|
|
21
|
-
:
|
|
20
|
+
? VIEWPORT_WIDTHS[editorState.viewport] ?? 1440
|
|
21
|
+
: 1440
|
|
22
22
|
);
|
|
23
23
|
</script>
|
|
24
24
|
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
max-width: var(--page-card-width, 960px);
|
|
37
37
|
margin: var(--ed-space-4) auto;
|
|
38
38
|
background: var(--ed-surface-0);
|
|
39
|
-
border: 1px solid var(--ed-border-default);
|
|
40
39
|
border-radius: var(--ed-radius-lg);
|
|
41
40
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
42
41
|
overflow: hidden;
|
|
@@ -6,10 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
let previewHtml = $state('');
|
|
8
8
|
let previewIframe: HTMLIFrameElement | undefined = $state();
|
|
9
|
+
let fallbackIframe: HTMLIFrameElement | undefined = $state();
|
|
9
10
|
let debounceTimer: ReturnType<typeof setTimeout>;
|
|
10
11
|
let pageMap: Map<string, string> = new Map(); // url → file path
|
|
11
12
|
let loading = $state(false);
|
|
12
13
|
|
|
14
|
+
// Inject data-theme into fallback srcdoc HTML
|
|
15
|
+
const themedPreviewHtml = $derived(
|
|
16
|
+
previewHtml.replace('<html', `<html data-theme="${editorState.previewTheme}"`)
|
|
17
|
+
);
|
|
18
|
+
|
|
13
19
|
// Listen for messages from the preview runtime iframe
|
|
14
20
|
$effect(() => {
|
|
15
21
|
function onMessage(e: MessageEvent) {
|
|
@@ -41,6 +47,17 @@
|
|
|
41
47
|
}
|
|
42
48
|
});
|
|
43
49
|
|
|
50
|
+
// Forward theme changes to the preview iframe
|
|
51
|
+
$effect(() => {
|
|
52
|
+
const theme = editorState.previewTheme;
|
|
53
|
+
if (useRuntime && previewIframe?.contentWindow) {
|
|
54
|
+
previewIframe.contentWindow.postMessage(
|
|
55
|
+
{ type: 'theme-update', theme },
|
|
56
|
+
'*',
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
44
61
|
// Main preview effect — debounces content changes
|
|
45
62
|
$effect(() => {
|
|
46
63
|
const path = editorState.currentPath;
|
|
@@ -109,8 +126,9 @@
|
|
|
109
126
|
></iframe>
|
|
110
127
|
{:else}
|
|
111
128
|
<iframe
|
|
129
|
+
bind:this={fallbackIframe}
|
|
112
130
|
title="Preview"
|
|
113
|
-
srcdoc={
|
|
131
|
+
srcdoc={themedPreviewHtml}
|
|
114
132
|
class="preview__iframe"
|
|
115
133
|
></iframe>
|
|
116
134
|
{/if}
|