@atom-forge/brickee 0.0.0-stage → 0.0.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/CHANGELOG.md +42 -0
- package/LICENSE +21 -0
- package/README-AI.md +286 -0
- package/README.md +41 -2
- package/dist/Editor.svelte +1956 -0
- package/dist/Editor.svelte.d.ts +19 -0
- package/dist/LinkPrompt.svelte +42 -0
- package/dist/LinkPrompt.svelte.d.ts +8 -0
- package/dist/PluginHost.svelte +41 -0
- package/dist/PluginHost.svelte.d.ts +13 -0
- package/dist/Preview.svelte +182 -0
- package/dist/Preview.svelte.d.ts +32 -0
- package/dist/PropsBar.svelte +405 -0
- package/dist/PropsBar.svelte.d.ts +17 -0
- package/dist/code.d.ts +12 -0
- package/dist/code.js +15 -0
- package/dist/dom.d.ts +29 -0
- package/dist/dom.js +224 -0
- package/dist/i18n.d.ts +7 -0
- package/dist/i18n.js +18 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +16 -0
- package/dist/kit/Empty.svelte +24 -0
- package/dist/kit/Empty.svelte.d.ts +8 -0
- package/dist/kit/Frame.svelte +76 -0
- package/dist/kit/Frame.svelte.d.ts +15 -0
- package/dist/kit/util.d.ts +13 -0
- package/dist/kit/util.js +28 -0
- package/dist/locales/hu.d.ts +2 -0
- package/dist/locales/hu.js +142 -0
- package/dist/markdown.d.ts +23 -0
- package/dist/markdown.js +219 -0
- package/dist/model.d.ts +21 -0
- package/dist/model.js +45 -0
- package/dist/palette.d.ts +19 -0
- package/dist/palette.js +23 -0
- package/dist/plugins/Gap.svelte +28 -0
- package/dist/plugins/Gap.svelte.d.ts +15 -0
- package/dist/plugins/GapPreview.svelte +17 -0
- package/dist/plugins/GapPreview.svelte.d.ts +5 -0
- package/dist/plugins/Map.svelte +47 -0
- package/dist/plugins/Map.svelte.d.ts +12 -0
- package/dist/plugins/MapPreview.svelte +26 -0
- package/dist/plugins/MapPreview.svelte.d.ts +5 -0
- package/dist/plugins/Table.svelte +537 -0
- package/dist/plugins/Table.svelte.d.ts +28 -0
- package/dist/plugins/TablePreview.svelte +62 -0
- package/dist/plugins/TablePreview.svelte.d.ts +5 -0
- package/dist/plugins/YouTube.svelte +52 -0
- package/dist/plugins/YouTube.svelte.d.ts +8 -0
- package/dist/plugins/YouTubePreview.svelte +76 -0
- package/dist/plugins/YouTubePreview.svelte.d.ts +5 -0
- package/dist/plugins/index.d.ts +20 -0
- package/dist/plugins/index.js +105 -0
- package/dist/plugins/util.d.ts +3 -0
- package/dist/plugins/util.js +8 -0
- package/dist/schema.d.ts +11 -0
- package/dist/schema.js +25 -0
- package/dist/scroll.d.ts +18 -0
- package/dist/scroll.js +57 -0
- package/dist/types.d.ts +132 -0
- package/dist/types.js +43 -0
- package/dist/view.d.ts +6 -0
- package/dist/view.js +11 -0
- package/docs/en/bundled-plugins.md +99 -0
- package/docs/en/documents-and-markdown.md +114 -0
- package/docs/en/editor-and-preview.md +139 -0
- package/docs/en/getting-started.md +85 -0
- package/docs/en/host-contexts.md +169 -0
- package/docs/en/index.md +38 -0
- package/docs/en/recipes-and-pitfalls.md +123 -0
- package/docs/en/server-side.md +95 -0
- package/docs/en/upgrading.md +71 -0
- package/docs/en/writing-plugins.md +405 -0
- package/docs/hu/bundled-plugins.md +99 -0
- package/docs/hu/documents-and-markdown.md +114 -0
- package/docs/hu/editor-and-preview.md +139 -0
- package/docs/hu/getting-started.md +85 -0
- package/docs/hu/host-contexts.md +169 -0
- package/docs/hu/index.md +38 -0
- package/docs/hu/recipes-and-pitfalls.md +123 -0
- package/docs/hu/server-side.md +95 -0
- package/docs/hu/upgrading.md +71 -0
- package/docs/hu/writing-plugins.md +405 -0
- package/package.json +114 -5
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
<!-- The fields of a plugin block (or a code block), on the top of its box. Closed: only a small header (the block's
|
|
2
|
+
tag); a click on it opens the fields in place: a wrapping row (label and control side by side), text areas on their
|
|
3
|
+
own full-width rows. -->
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
import { ColorPicker, Input, NativeSelect, Slider, Textarea } from '@atom-forge/ui';
|
|
6
|
+
import ChevronDown from '@lucide/svelte/icons/chevron-down';
|
|
7
|
+
import { slide } from 'svelte/transition';
|
|
8
|
+
import { useView } from './view';
|
|
9
|
+
import type { Translate } from './i18n';
|
|
10
|
+
import type { Block, PropField } from './types';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
block: Block;
|
|
14
|
+
tag: string;
|
|
15
|
+
fields: PropField[];
|
|
16
|
+
tr: Translate;
|
|
17
|
+
/** Sets one property from its field's text and returns the text as it is now kept. */
|
|
18
|
+
setProp: (b: Block, f: PropField, raw: string) => string;
|
|
19
|
+
/** Sets one property to a value as it is ('' or undefined removes it). */
|
|
20
|
+
setValue: (b: Block, key: string, v: unknown) => void;
|
|
21
|
+
/** Start with the fields open (a block just made). */
|
|
22
|
+
open?: boolean;
|
|
23
|
+
}
|
|
24
|
+
let { block, tag, fields, tr, setProp, setValue, open: startOpen = false }: Props = $props();
|
|
25
|
+
// svelte-ignore state_referenced_locally
|
|
26
|
+
let open = $state(startOpen);
|
|
27
|
+
|
|
28
|
+
const text = (f: PropField) => {
|
|
29
|
+
const v = block.props?.[f.key];
|
|
30
|
+
return f.format ? f.format(v) : v == null ? '' : String(v);
|
|
31
|
+
};
|
|
32
|
+
/** A text field's placeholder: its label, and its own placeholder (an example) after it. */
|
|
33
|
+
const hint = (f: PropField) => [tr(f.label), f.placeholder && tr(f.placeholder)].filter(Boolean).join(' · ');
|
|
34
|
+
/** Toggles: the values switched on, kept in the order of the options. */
|
|
35
|
+
const toggled = (f: PropField): string[] => {
|
|
36
|
+
const v = block.props?.[f.key];
|
|
37
|
+
return Array.isArray(v) ? v.map(String) : [];
|
|
38
|
+
};
|
|
39
|
+
function toggle(f: PropField, value: string) {
|
|
40
|
+
const on = new Set(toggled(f));
|
|
41
|
+
if (!on.delete(value)) on.add(value);
|
|
42
|
+
setValue(block, f.key, (f.options ?? []).map((o) => o.value).filter((x) => on.has(x)));
|
|
43
|
+
}
|
|
44
|
+
const optionLabel = (f: PropField, i: number) => {
|
|
45
|
+
const opt = f.options?.[i];
|
|
46
|
+
return opt ? tr(opt.label, { n: opt.value }) : '';
|
|
47
|
+
};
|
|
48
|
+
// Sliders: with `min`/`max` a free number (left of `min` a "none" notch that removes the value), otherwise the
|
|
49
|
+
// index of one of the `options` (an unknown value: the first). With `views`, the slider edits the value of the view
|
|
50
|
+
// the host gives (useView; none given: the base): a view without its own shows the one it takes (from the views
|
|
51
|
+
// before it), and its notch means "take it".
|
|
52
|
+
const free = (f: PropField) => f.min != null && f.max != null;
|
|
53
|
+
const low = (f: PropField) => (f.none ? f.min! - Math.round((f.max! - f.min!) / 12) : f.min!);
|
|
54
|
+
const view = useView();
|
|
55
|
+
const viewOf = (f: PropField) => {
|
|
56
|
+
const v = view();
|
|
57
|
+
return f.views?.some((x) => x.key === v) ? v! : (f.views?.[0]?.key ?? '');
|
|
58
|
+
};
|
|
59
|
+
/** The view a per-view field edits, for its label (only when the host gives one). */
|
|
60
|
+
const viewLabel = (f: PropField) => {
|
|
61
|
+
const v = f.views && view() ? f.views.find((x) => x.key === viewOf(f)) : undefined;
|
|
62
|
+
return v ? tr(v.label) : '';
|
|
63
|
+
};
|
|
64
|
+
const byView = (v: unknown): v is Record<string, unknown> => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
65
|
+
/** A view's own value (as text), '' when it has none. */
|
|
66
|
+
const own = (f: PropField, view: string) => {
|
|
67
|
+
const v = block.props?.[f.key];
|
|
68
|
+
const raw = byView(v) ? v[view] : view === f.views?.[0]?.key ? v : undefined;
|
|
69
|
+
return f.format ? f.format(raw) : raw == null ? '' : String(raw);
|
|
70
|
+
};
|
|
71
|
+
/** The text the picked view shows: its own, or the one it takes; and whether it is its own. */
|
|
72
|
+
const current = (f: PropField): { text: string; mine: boolean } => {
|
|
73
|
+
if (!f.views) return { text: text(f), mine: true };
|
|
74
|
+
const keys = f.views.map((v) => v.key);
|
|
75
|
+
const at = keys.indexOf(viewOf(f));
|
|
76
|
+
for (let i = at; i >= 0; i--) {
|
|
77
|
+
const t = own(f, keys[i]);
|
|
78
|
+
if (t) return { text: t, mine: i === at };
|
|
79
|
+
}
|
|
80
|
+
return { text: '', mine: at === 0 };
|
|
81
|
+
};
|
|
82
|
+
const pos = (f: PropField) => {
|
|
83
|
+
const t = current(f).text;
|
|
84
|
+
if (!free(f)) return Math.max(0, (f.options ?? []).findIndex((o) => o.value === t));
|
|
85
|
+
const n = Number(t);
|
|
86
|
+
return t && Number.isFinite(n) ? Math.min(f.max!, Math.max(f.min!, n)) : low(f);
|
|
87
|
+
};
|
|
88
|
+
const setPos = (f: PropField, v: number) => {
|
|
89
|
+
const t = free(f) ? (v < f.min! ? '' : String(Math.round(v))) : (f.options?.[v]?.value ?? '');
|
|
90
|
+
if (!f.views) return setProp(block, f, t);
|
|
91
|
+
// the views' own values, the picked one changed; only the base: stored alone
|
|
92
|
+
const keys = f.views.map((x) => x.key);
|
|
93
|
+
const next: Record<string, string> = {};
|
|
94
|
+
for (const k of keys) {
|
|
95
|
+
const value = k === viewOf(f) ? t : own(f, k);
|
|
96
|
+
if (value) next[k] = value;
|
|
97
|
+
}
|
|
98
|
+
const set = Object.keys(next);
|
|
99
|
+
setValue(block, f.key, !set.length ? '' : set.length === 1 && set[0] === keys[0] ? next[keys[0]] : next);
|
|
100
|
+
};
|
|
101
|
+
const posLabel = (f: PropField) => {
|
|
102
|
+
const { text: t, mine } = current(f);
|
|
103
|
+
const n = Number(t);
|
|
104
|
+
const shown = !free(f) ? optionLabel(f, pos(f)) : t && Number.isFinite(n) ? `${Math.round(n)}${f.unit ?? ''}` : tr(f.none ?? '');
|
|
105
|
+
return mine ? shown : `${shown} (${tr('inherited')})`;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// A text area keeps its text as typed while it has the focus (a list keeps its empty lines while typing); the block
|
|
109
|
+
// gets the parsed value. Out of focus it shows the value as kept (so undo shows through).
|
|
110
|
+
let editing = $state<{ key: string; text: string } | null>(null);
|
|
111
|
+
|
|
112
|
+
const short = $derived(fields.filter((f) => f.kind !== 'textarea'));
|
|
113
|
+
const long = $derived(fields.filter((f) => f.kind === 'textarea'));
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<div class="bar" class:open>
|
|
117
|
+
<button type="button" class="head" aria-expanded={open} title={tr(open ? 'Hide settings' : 'Settings')} onmousedown={(e) => e.preventDefault()} onclick={() => (open = !open)}>
|
|
118
|
+
<span class="tag">{tag}</span>
|
|
119
|
+
<ChevronDown size={13} class="chev" />
|
|
120
|
+
</button>
|
|
121
|
+
{#if open}
|
|
122
|
+
<div class="body" transition:slide={{ duration: 180 }}>
|
|
123
|
+
<div class="grid">
|
|
124
|
+
{#each short as f (f.key)}
|
|
125
|
+
{#if f.kind === 'text'}
|
|
126
|
+
<!-- the field shows the value as kept (a YouTube link becomes its id); its label is the placeholder -->
|
|
127
|
+
<label class="field" title={tr(f.label)}>
|
|
128
|
+
<span class="sr">{tr(f.label)}</span>
|
|
129
|
+
<Input small monospace placeholder={hint(f)} bind:value={() => text(f), (v: string) => setProp(block, f, v)} />
|
|
130
|
+
</label>
|
|
131
|
+
{:else}
|
|
132
|
+
<!-- not a label around buttons (a slider's view buttons, a colour's clear, toggles): a click on a label goes to its first button -->
|
|
133
|
+
<svelte:element this={f.kind === 'select' ? 'label' : 'div'} class="field inline" role={f.kind === 'toggles' ? 'group' : undefined} aria-label={f.kind === 'toggles' ? tr(f.label) : undefined}>
|
|
134
|
+
<span class="name"
|
|
135
|
+
>{tr(f.label)}{#if viewLabel(f)}<span class="view" class:own={current(f).mine}>{viewLabel(f)}</span>{/if}</span
|
|
136
|
+
>
|
|
137
|
+
{#if f.kind === 'select'}
|
|
138
|
+
<NativeSelect
|
|
139
|
+
small
|
|
140
|
+
options={(f.options ?? []).map((opt) => ({ value: opt.value, label: tr(opt.label, { n: opt.value }) }))}
|
|
141
|
+
bind:value={() => text(f), (v: string) => setProp(block, f, v)}
|
|
142
|
+
/>
|
|
143
|
+
{:else if f.kind === 'toggles'}
|
|
144
|
+
{@const on = toggled(f)}
|
|
145
|
+
<span class="toggles">
|
|
146
|
+
{#each f.options ?? [] as opt (opt.value)}
|
|
147
|
+
<button
|
|
148
|
+
type="button"
|
|
149
|
+
class="toggle"
|
|
150
|
+
class:on={on.includes(opt.value)}
|
|
151
|
+
aria-pressed={on.includes(opt.value)}
|
|
152
|
+
title={tr(opt.label)}
|
|
153
|
+
aria-label={tr(opt.label)}
|
|
154
|
+
onmousedown={(e) => e.preventDefault()}
|
|
155
|
+
onclick={() => toggle(f, opt.value)}
|
|
156
|
+
>{#if opt.icon}<opt.icon size={15} />{:else}{tr(opt.label)}{/if}</button
|
|
157
|
+
>
|
|
158
|
+
{/each}
|
|
159
|
+
</span>
|
|
160
|
+
{:else if f.kind === 'slider'}
|
|
161
|
+
<span class="line">
|
|
162
|
+
<span class="slider">
|
|
163
|
+
<Slider
|
|
164
|
+
small
|
|
165
|
+
drawStops={!free(f) && (f.options?.length ?? 0) <= 10}
|
|
166
|
+
min={free(f) ? low(f) : 0}
|
|
167
|
+
max={free(f) ? f.max : (f.options?.length ?? 1) - 1}
|
|
168
|
+
step={free(f) ? (f.step ?? 1) : 1}
|
|
169
|
+
bind:value={() => pos(f), (v: number) => setPos(f, v)}
|
|
170
|
+
/>
|
|
171
|
+
</span>
|
|
172
|
+
<span class="value">{posLabel(f)}</span>
|
|
173
|
+
</span>
|
|
174
|
+
{:else if f.kind === 'color'}
|
|
175
|
+
<!-- with `none`: an unset colour shows as "none" (a click sets one); a set one can be cleared -->
|
|
176
|
+
<span class="line">
|
|
177
|
+
{#if f.none && !text(f)}
|
|
178
|
+
<button type="button" class="nocolor" onclick={() => setProp(block, f, f.placeholder || '#000000')}>
|
|
179
|
+
<span class="swatch" aria-hidden="true"></span>{tr(f.none)}
|
|
180
|
+
</button>
|
|
181
|
+
{:else}
|
|
182
|
+
<ColorPicker small bind:value={() => text(f) || '#000000', (v: string) => setProp(block, f, v)} />
|
|
183
|
+
{#if f.none}
|
|
184
|
+
<button type="button" class="clear" title={tr(f.none)} aria-label={tr(f.none)} onclick={() => setProp(block, f, '')}>×</button>
|
|
185
|
+
{/if}
|
|
186
|
+
{/if}
|
|
187
|
+
</span>
|
|
188
|
+
{/if}
|
|
189
|
+
</svelte:element>
|
|
190
|
+
{/if}
|
|
191
|
+
{/each}
|
|
192
|
+
{#each long as f (f.key)}
|
|
193
|
+
<label class="field long" title={tr(f.label)} onfocusin={() => (editing = { key: f.key, text: text(f) })} onfocusout={() => (editing = null)}>
|
|
194
|
+
<span class="sr">{tr(f.label)}</span>
|
|
195
|
+
<Textarea
|
|
196
|
+
small
|
|
197
|
+
rows={2}
|
|
198
|
+
placeholder={hint(f)}
|
|
199
|
+
bind:value={() => (editing?.key === f.key ? editing.text : text(f)), (v: string) => {
|
|
200
|
+
if (editing?.key === f.key) editing.text = v;
|
|
201
|
+
setProp(block, f, v);
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
</label>
|
|
205
|
+
{/each}
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
{/if}
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<style>
|
|
212
|
+
.bar {
|
|
213
|
+
display: grid;
|
|
214
|
+
justify-items: start;
|
|
215
|
+
gap: 6px;
|
|
216
|
+
margin-bottom: 4px;
|
|
217
|
+
border-radius: 8px;
|
|
218
|
+
font: 12px/1.4 system-ui, sans-serif;
|
|
219
|
+
color: var(--color-surface-contrast, #1d1d1f);
|
|
220
|
+
}
|
|
221
|
+
.bar.open {
|
|
222
|
+
justify-items: stretch;
|
|
223
|
+
margin-bottom: 8px;
|
|
224
|
+
padding: 6px 8px 8px;
|
|
225
|
+
background: rgba(120, 120, 140, 0.07);
|
|
226
|
+
}
|
|
227
|
+
.bar {
|
|
228
|
+
transition:
|
|
229
|
+
padding 0.18s,
|
|
230
|
+
background 0.18s;
|
|
231
|
+
}
|
|
232
|
+
/* the fields in columns as wide as the box allows; text fields carry their label as placeholder, the others a short
|
|
233
|
+
label beside the control */
|
|
234
|
+
.grid {
|
|
235
|
+
display: grid;
|
|
236
|
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
237
|
+
gap: 6px 10px;
|
|
238
|
+
padding-top: 6px;
|
|
239
|
+
}
|
|
240
|
+
.head {
|
|
241
|
+
display: flex;
|
|
242
|
+
align-items: center;
|
|
243
|
+
gap: 2px;
|
|
244
|
+
padding: 0;
|
|
245
|
+
border: 0;
|
|
246
|
+
background: none;
|
|
247
|
+
color: var(--color-muted-contrast, #8e8e96);
|
|
248
|
+
cursor: pointer;
|
|
249
|
+
justify-self: start;
|
|
250
|
+
}
|
|
251
|
+
.head:hover .tag {
|
|
252
|
+
background: rgba(120, 120, 140, 0.2);
|
|
253
|
+
color: var(--color-surface-contrast, #1d1d1f);
|
|
254
|
+
}
|
|
255
|
+
.head :global(.chev) {
|
|
256
|
+
transition: rotate 0.15s;
|
|
257
|
+
}
|
|
258
|
+
.open .head :global(.chev) {
|
|
259
|
+
rotate: 180deg;
|
|
260
|
+
}
|
|
261
|
+
.tag {
|
|
262
|
+
padding: 1px 6px;
|
|
263
|
+
border-radius: 5px;
|
|
264
|
+
background: rgba(120, 120, 140, 0.12);
|
|
265
|
+
color: var(--color-muted-contrast, #6e6e76);
|
|
266
|
+
font: 500 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
267
|
+
white-space: nowrap;
|
|
268
|
+
user-select: none;
|
|
269
|
+
}
|
|
270
|
+
.field {
|
|
271
|
+
display: grid;
|
|
272
|
+
align-content: start;
|
|
273
|
+
min-width: 0;
|
|
274
|
+
}
|
|
275
|
+
.field.inline {
|
|
276
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 8px;
|
|
279
|
+
}
|
|
280
|
+
/* compact controls: the bar's small type in the inputs too */
|
|
281
|
+
.field :global(input:not([type='range'])),
|
|
282
|
+
.field :global(textarea),
|
|
283
|
+
.field :global(select) {
|
|
284
|
+
font-size: 12px;
|
|
285
|
+
}
|
|
286
|
+
/* the label of a field whose placeholder shows it */
|
|
287
|
+
.sr {
|
|
288
|
+
position: absolute;
|
|
289
|
+
width: 1px;
|
|
290
|
+
height: 1px;
|
|
291
|
+
overflow: hidden;
|
|
292
|
+
clip-path: inset(50%);
|
|
293
|
+
white-space: nowrap;
|
|
294
|
+
}
|
|
295
|
+
.toggles {
|
|
296
|
+
display: inline-flex;
|
|
297
|
+
justify-self: start;
|
|
298
|
+
gap: 1px;
|
|
299
|
+
padding: 2px;
|
|
300
|
+
border-radius: 7px;
|
|
301
|
+
background: var(--color-muted, #f4f4f6);
|
|
302
|
+
}
|
|
303
|
+
.toggle {
|
|
304
|
+
display: inline-grid;
|
|
305
|
+
place-items: center;
|
|
306
|
+
min-width: 26px;
|
|
307
|
+
height: 24px;
|
|
308
|
+
padding: 0 4px;
|
|
309
|
+
border: 0;
|
|
310
|
+
border-radius: 5px;
|
|
311
|
+
background: none;
|
|
312
|
+
color: var(--color-muted-contrast, #6e6e76);
|
|
313
|
+
cursor: pointer;
|
|
314
|
+
}
|
|
315
|
+
.toggle:hover {
|
|
316
|
+
color: var(--color-surface-contrast, #1d1d1f);
|
|
317
|
+
}
|
|
318
|
+
.toggle.on {
|
|
319
|
+
background: var(--color-surface, #fff);
|
|
320
|
+
color: #3b82f6;
|
|
321
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
|
322
|
+
}
|
|
323
|
+
/* text areas take a whole row */
|
|
324
|
+
.field.long {
|
|
325
|
+
grid-column: 1 / -1;
|
|
326
|
+
}
|
|
327
|
+
.line {
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
gap: 8px;
|
|
331
|
+
min-width: 0;
|
|
332
|
+
}
|
|
333
|
+
.name {
|
|
334
|
+
font-size: 11.5px;
|
|
335
|
+
font-weight: 600;
|
|
336
|
+
color: var(--color-muted-contrast, #8e8e96);
|
|
337
|
+
white-space: nowrap;
|
|
338
|
+
user-select: none;
|
|
339
|
+
}
|
|
340
|
+
/* the view a per-view field edits (the host's current one); dashed while it only takes a wider view's value */
|
|
341
|
+
.view {
|
|
342
|
+
margin-left: 6px;
|
|
343
|
+
padding: 0 5px;
|
|
344
|
+
border: 1px dashed var(--color-frame, #c8c8d0);
|
|
345
|
+
border-radius: 4px;
|
|
346
|
+
color: var(--color-muted-contrast, #6e6e76);
|
|
347
|
+
font-weight: 500;
|
|
348
|
+
}
|
|
349
|
+
.view.own {
|
|
350
|
+
border-style: solid;
|
|
351
|
+
border-color: var(--color-surface-contrast, #1d1d1f);
|
|
352
|
+
color: var(--color-surface-contrast, #1d1d1f);
|
|
353
|
+
}
|
|
354
|
+
.slider {
|
|
355
|
+
display: block;
|
|
356
|
+
flex: 1;
|
|
357
|
+
min-width: 80px;
|
|
358
|
+
}
|
|
359
|
+
.value {
|
|
360
|
+
min-width: 4.5em;
|
|
361
|
+
color: var(--color-surface-contrast, #3a3a40);
|
|
362
|
+
font-variant-numeric: tabular-nums;
|
|
363
|
+
white-space: nowrap;
|
|
364
|
+
}
|
|
365
|
+
.nocolor {
|
|
366
|
+
display: flex;
|
|
367
|
+
align-items: center;
|
|
368
|
+
gap: 6px;
|
|
369
|
+
height: 30px;
|
|
370
|
+
padding: 0 10px 0 6px;
|
|
371
|
+
border: 1px dashed var(--color-frame, #c8c8d0);
|
|
372
|
+
border-radius: 7px;
|
|
373
|
+
background: var(--color-surface, #fff);
|
|
374
|
+
color: var(--color-muted-contrast, #8e8e96);
|
|
375
|
+
cursor: pointer;
|
|
376
|
+
}
|
|
377
|
+
.nocolor:hover {
|
|
378
|
+
border-color: var(--color-accent, #ea580c);
|
|
379
|
+
color: var(--color-surface-contrast, #3a3a40);
|
|
380
|
+
}
|
|
381
|
+
/* an empty swatch, struck through */
|
|
382
|
+
.swatch {
|
|
383
|
+
width: 16px;
|
|
384
|
+
height: 16px;
|
|
385
|
+
border: 1px solid var(--color-frame, #c8c8d0);
|
|
386
|
+
border-radius: 4px;
|
|
387
|
+
background: linear-gradient(to top right, transparent calc(50% - 1px), #e5484d calc(50% - 1px), #e5484d calc(50% + 1px), transparent calc(50% + 1px));
|
|
388
|
+
}
|
|
389
|
+
.clear {
|
|
390
|
+
width: 22px;
|
|
391
|
+
height: 22px;
|
|
392
|
+
padding: 0;
|
|
393
|
+
border: 0;
|
|
394
|
+
border-radius: 6px;
|
|
395
|
+
background: none;
|
|
396
|
+
color: var(--color-muted-contrast, #8e8e96);
|
|
397
|
+
font-size: 16px;
|
|
398
|
+
line-height: 1;
|
|
399
|
+
cursor: pointer;
|
|
400
|
+
}
|
|
401
|
+
.clear:hover {
|
|
402
|
+
background: rgba(120, 120, 140, 0.14);
|
|
403
|
+
color: var(--color-surface-contrast, #1d1d1f);
|
|
404
|
+
}
|
|
405
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Translate } from './i18n';
|
|
2
|
+
import type { Block, PropField } from './types';
|
|
3
|
+
interface Props {
|
|
4
|
+
block: Block;
|
|
5
|
+
tag: string;
|
|
6
|
+
fields: PropField[];
|
|
7
|
+
tr: Translate;
|
|
8
|
+
/** Sets one property from its field's text and returns the text as it is now kept. */
|
|
9
|
+
setProp: (b: Block, f: PropField, raw: string) => string;
|
|
10
|
+
/** Sets one property to a value as it is ('' or undefined removes it). */
|
|
11
|
+
setValue: (b: Block, key: string, v: unknown) => void;
|
|
12
|
+
/** Start with the fields open (a block just made). */
|
|
13
|
+
open?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare const PropsBar: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type PropsBar = ReturnType<typeof PropsBar>;
|
|
17
|
+
export default PropsBar;
|
package/dist/code.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A language: its value is stored in the block (`props.lang`, the opening fence's info string). */
|
|
2
|
+
export interface CodeLanguage {
|
|
3
|
+
value: string;
|
|
4
|
+
/** Shown in the dropdown; defaults to the value. */
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
type Get = () => CodeLanguage[];
|
|
8
|
+
/** Call in a component's init (around the editor). Without its own list, a nested editor keeps its parent's. */
|
|
9
|
+
export declare function provideCodeLanguages(own: () => (string | CodeLanguage)[] | undefined): void;
|
|
10
|
+
/** Call in a component's init; the returned function reads the current list (empty: free text). */
|
|
11
|
+
export declare const useCodeLanguages: () => Get;
|
|
12
|
+
export {};
|
package/dist/code.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The languages a code block offers. Without a list (the default) its language is free text; with one, the field is a
|
|
2
|
+
// dropdown of these. Which languages make sense is the host's business (what its site can highlight).
|
|
3
|
+
import { getContext, setContext } from 'svelte';
|
|
4
|
+
const KEY = 'block-editor:code-languages';
|
|
5
|
+
const normalize = (list) => list.map((l) => (typeof l === 'string' ? { value: l } : l));
|
|
6
|
+
/** Call in a component's init (around the editor). Without its own list, a nested editor keeps its parent's. */
|
|
7
|
+
export function provideCodeLanguages(own) {
|
|
8
|
+
const parent = getContext(KEY);
|
|
9
|
+
setContext(KEY, () => {
|
|
10
|
+
const list = own();
|
|
11
|
+
return list ? normalize(list) : (parent?.() ?? []);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** Call in a component's init; the returned function reads the current list (empty: free text). */
|
|
15
|
+
export const useCodeLanguages = () => getContext(KEY) ?? (() => []);
|
package/dist/dom.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Svelte action: sets innerHTML once and only re-syncs when the model differs from the DOM (keeps the caret). */
|
|
2
|
+
export declare function rich(node: HTMLElement, value: string): {
|
|
3
|
+
update(v: string): void;
|
|
4
|
+
};
|
|
5
|
+
export declare function serialize(frag: DocumentFragment): string;
|
|
6
|
+
/** Returns '' for html that has no visible text (e.g. `<b></b>`, `<br>`). */
|
|
7
|
+
export declare function clean(h: string): string;
|
|
8
|
+
export declare function textLen(h: string): number;
|
|
9
|
+
export declare function splitAtCaret(el: HTMLElement): [string, string] | null;
|
|
10
|
+
export declare function atStart(el: HTMLElement): boolean;
|
|
11
|
+
export declare function atEnd(el: HTMLElement): boolean;
|
|
12
|
+
export declare function caretOffset(el: HTMLElement): number;
|
|
13
|
+
export declare function setCaret(el: HTMLElement, where?: 'start' | 'end' | number): void;
|
|
14
|
+
/** Viewport rect of the caret, or null when the browser can't tell (empty block). */
|
|
15
|
+
export declare function caretRect(): DOMRect | null;
|
|
16
|
+
/** Places the caret in `el` at the viewport point (x, y). Keeps the horizontal position while moving between blocks. */
|
|
17
|
+
export declare function caretAt(el: HTMLElement, x: number, y: number): boolean;
|
|
18
|
+
/** Deletes `n` characters directly before the caret (inside the caret's text node). */
|
|
19
|
+
export declare function removeBeforeCaret(n: number): boolean;
|
|
20
|
+
/** Is the caret on the first (up) or last (down) visual line of the block? */
|
|
21
|
+
export declare function onEdgeLine(el: HTMLElement, up: boolean): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Plain text: a selection loses every mark and link; a caret steps out of the marks around it, so what is typed next
|
|
24
|
+
* is plain (a zero-width space holds it there; the markdown conversion drops it).
|
|
25
|
+
*/
|
|
26
|
+
export declare function plainText(el: HTMLElement): void;
|
|
27
|
+
export declare function toggleCode(el: HTMLElement): void;
|
|
28
|
+
/** Is the selection's moving end (focus) at the very start (up) or end (down) of the block's text? */
|
|
29
|
+
export declare function focusAtEdge(el: HTMLElement, up: boolean): boolean;
|