@atom-forge/brickee 0.0.1 → 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 +19 -1
- package/README-AI.md +24 -21
- package/dist/Editor.svelte +52 -64
- package/dist/Editor.svelte.d.ts +0 -3
- package/dist/LinkPrompt.svelte +3 -3
- package/dist/Preview.svelte +1 -5
- package/dist/Preview.svelte.d.ts +0 -3
- package/dist/PropsBar.svelte +120 -110
- package/dist/PropsBar.svelte.d.ts +0 -3
- package/dist/code.d.ts +12 -0
- package/dist/code.js +15 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kit/Empty.svelte +2 -2
- package/dist/kit/Frame.svelte +3 -3
- package/dist/locales/hu.js +2 -7
- package/dist/markdown.d.ts +0 -2
- package/dist/markdown.js +1 -1
- package/dist/plugins/Gap.svelte +1 -1
- package/dist/plugins/Map.svelte +1 -1
- package/dist/plugins/Table.svelte +36 -62
- package/dist/plugins/TablePreview.svelte +2 -2
- package/dist/plugins/YouTubePreview.svelte +1 -1
- package/dist/plugins/index.js +12 -1
- package/dist/schema.js +1 -1
- package/dist/types.d.ts +6 -7
- package/docs/en/bundled-plugins.md +1 -1
- package/docs/en/documents-and-markdown.md +4 -5
- package/docs/en/editor-and-preview.md +1 -3
- package/docs/en/getting-started.md +6 -2
- package/docs/en/host-contexts.md +26 -132
- package/docs/en/index.md +4 -4
- package/docs/en/recipes-and-pitfalls.md +10 -9
- package/docs/en/upgrading.md +20 -4
- package/docs/en/writing-plugins.md +111 -11
- package/docs/hu/bundled-plugins.md +1 -1
- package/docs/hu/documents-and-markdown.md +4 -5
- package/docs/hu/editor-and-preview.md +1 -3
- package/docs/hu/getting-started.md +6 -2
- package/docs/hu/host-contexts.md +26 -132
- package/docs/hu/index.md +4 -4
- package/docs/hu/recipes-and-pitfalls.md +10 -9
- package/docs/hu/upgrading.md +20 -4
- package/docs/hu/writing-plugins.md +111 -11
- package/package.json +6 -2
- package/dist/ImagePicker.svelte +0 -131
- package/dist/ImagePicker.svelte.d.ts +0 -13
- package/dist/media.d.ts +0 -17
- package/dist/media.js +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -7,9 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.0.2] - 2026-11-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Showcase site in `src/routes` (`bun run dev`), styled like the `@atom-forge/ui` site: a playground (editor beside its preview, stored JSON and markdown), the guide (editor and preview, split view, markdown, stored form and schema), a page per bundled plugin, writing, nesting and overriding plugins, image blocks, and the host contexts (per-view values, code languages, localisation), each with live examples and their code. It resolves `@atom-forge/brickee` to the local source and prerenders to `build/` (`@sveltejs/adapter-static`); it is not part of the npm package.
|
|
15
|
+
- A `toggles` field kind (`PropField`): a row of icon buttons, each switching one of `options` on or off (`options[].icon` is the button, `label` its tooltip), stored as the list of the ones on.
|
|
16
|
+
- `provideCodeLanguages(() => list)` (with `useCodeLanguages` and the `CodeLanguage` type): a host context that turns a code block's language field into a dropdown of the given languages, with *None* first; without it the language stays free text. Nested editors inherit the list, and a stored language missing from it stays an option.
|
|
17
|
+
|
|
10
18
|
### Changed
|
|
11
|
-
- Configure releases with Ship.
|
|
12
19
|
|
|
20
|
+
- The fields bar is more compact: smaller controls and type, text fields show their label as the placeholder (with the field's own placeholder after it), the other fields a short label beside the control.
|
|
21
|
+
- The table's header and footer rows are two icon toggles in its fields bar (a `toggles` field), like any plugin field; the toolbar above the table holds only the row and column tools of the cell being edited, and says to click a cell while none is. The info icon with the keyboard hint is gone.
|
|
22
|
+
- The editor follows the host's light and dark mode: its colours (block fields, popups, frames, the table editor, the block actions) come from the `@atom-forge/ui` theme tokens with the former colours as fallbacks, and the props bar and the link prompt no longer force the light theme. The previews' table borders, video captions and the gap's stripes are drawn from the text colour.
|
|
23
|
+
- A code block's language is edited in the same fields bar as a plugin's (its header shows `code · <language>`), instead of a small input in its corner; read-only, the corner still shows the language.
|
|
24
|
+
- The block handles keep 1px free above and below, so the handles of neighbouring blocks do not touch.
|
|
25
|
+
- The docs note that the block handles are drawn about 22 px left of the editor's box, so the element around it needs that much left padding.
|
|
26
|
+
- Releases are configured with `ship-it init`: the publish workflow comes from Ship's template, `package.json` gains `pub-validate` and `publishConfig`, and npm Trusted Publishing lets CI publish the next versions.
|
|
27
|
+
|
|
28
|
+
### Removed
|
|
29
|
+
|
|
30
|
+
- **Breaking:** images and media are no longer the library's concept — whether a block stores a URL or an id, how the user picks an image and how it is resolved is the application's decision. Removed: the `image` and `images` field kinds with their picker, the `media` prop of `Editor` and `Preview`, `provideMedia`, `useMedia`, `findMedia`, the `MediaItem` type, `toMarkdown`'s `media` option and `MarkdownContext.media`. An image block is an app plugin that picks in its own view or dialog and resolves with the app's own data; stored documents are unaffected. See the upgrading chapter.
|
|
13
31
|
|
|
14
32
|
## [0.0.1] - 2026-11-10
|
|
15
33
|
|
package/README-AI.md
CHANGED
|
@@ -9,8 +9,10 @@ Brickee is a block editor. Its core is a markdown block editor (`md:p`, `md:h1`
|
|
|
9
9
|
- Peers: `svelte` 5, `@atom-forge/ui` (popups and field controls), `@lucide/svelte`; `zod` only for `@atom-forge/brickee/schema`. The app must set up `@atom-forge/ui` (Tailwind 4, its theme CSS and `@source`, the app beneath its `Root`).
|
|
10
10
|
- Store the editor's `value` (`StoredBlock[]`) as it is. Built-in types start with `md:`; any other `type` is a plugin's. Never rename a plugin's `type`: stored documents refer to it.
|
|
11
11
|
- Pass the same `plugins` to the `Editor` and the `Preview` of a document type. Restrict what an editor offers with `palette` (palette ids: `md:*` kinds and plugin types), not by dropping plugins.
|
|
12
|
-
-
|
|
13
|
-
-
|
|
12
|
+
- The editor's colours come from the `@atom-forge/ui` theme tokens (light/dark follow the host). Its block handles sit ~22 px left of its box: leave that much left padding around it.
|
|
13
|
+
- No app-specific editor props: app data reaches plugins through contexts (`provideView`, `provideCodeLanguages`, `messages` / `provideText`), plugin fields, or the app's own modules and contexts.
|
|
14
|
+
- No image concept: no image field, picker or media list. An image block is an app plugin that stores what the app chooses (a URL, or an id of its own media library), picks it in its view or the app's own dialog, and resolves ids with the app's own data.
|
|
15
|
+
- Plugin props are untrusted user input. Previews sanitize: `safeColor` for colours, check links and image addresses (`http(s)` or site-relative), clamp numbers, never `{@html}` with props.
|
|
14
16
|
- A plugin view declares both `props` and `blocks` with `$bindable()`, even if unused.
|
|
15
17
|
- Containers (`content: 'blocks'`) nest one level only; nested editors never offer containers or unique plugins. `accepts` narrows their content (`'md:*'` = every built-in).
|
|
16
18
|
- `unique`: one per document, top level; not enforced by the schema. `gate`: the preview only *marks* blocks after it; hide gated content on the server.
|
|
@@ -24,9 +26,9 @@ Brickee is a block editor. Its core is a markdown block editor (`md:p`, `md:h1`
|
|
|
24
26
|
|
|
25
27
|
```ts
|
|
26
28
|
// @atom-forge/brickee — components
|
|
27
|
-
// <Editor bind:value={StoredBlock[]} plugins?={BlockPlugin[]} palette?={string[]}
|
|
28
|
-
//
|
|
29
|
-
// <Preview blocks={StoredBlock[]} plugins={BlockPlugin[]}
|
|
29
|
+
// <Editor bind:value={StoredBlock[]} plugins?={BlockPlugin[]} palette?={string[]} messages?={Messages}
|
|
30
|
+
// readonly?={boolean} placeholder?={string} /> // `nested` is for Frame only
|
|
31
|
+
// <Preview blocks={StoredBlock[]} plugins={BlockPlugin[]} messages?={Messages}
|
|
30
32
|
// markGated?={boolean /* default true */} nested?={boolean} />
|
|
31
33
|
// <Frame bind:blocks {readonly} {palette} {plugins} heading?={string} empty?={string} image?={string} placeholder?={string} />
|
|
32
34
|
// <Empty icon={Icon} text={string} />
|
|
@@ -43,15 +45,14 @@ function inline(md: string): string; // inline markdown -> safe
|
|
|
43
45
|
function fromStored(s: StoredBlock): Block;
|
|
44
46
|
function toStored(b: Block): StoredBlock; // browser only
|
|
45
47
|
function fromMarkdown(src: string): Block[]; // one block per non-empty line; built-in blocks only
|
|
46
|
-
function toMarkdown(blocks: Block[], opts?: { plugins?: BlockPlugin[]
|
|
48
|
+
function toMarkdown(blocks: Block[], opts?: { plugins?: BlockPlugin[] }): string; // browser only
|
|
47
49
|
|
|
48
50
|
// contexts (call provide* in component init, above the editor/preview; use* in plugin components)
|
|
49
|
-
interface MediaItem { id: string; name: string; url: string; group?: string }
|
|
50
|
-
function provideMedia(own: () => MediaItem[] | undefined): void;
|
|
51
|
-
function useMedia(): () => MediaItem[];
|
|
52
|
-
function findMedia(list: MediaItem[], id: unknown): MediaItem | undefined;
|
|
53
51
|
function provideView(get: () => string | undefined): void;
|
|
54
52
|
function useView(): () => string | undefined;
|
|
53
|
+
interface CodeLanguage { value: string; label?: string } // value: stored as the code block's props.lang
|
|
54
|
+
function provideCodeLanguages(own: () => (string | CodeLanguage)[] | undefined): void; // list: the language field is a dropdown
|
|
55
|
+
function useCodeLanguages(): () => CodeLanguage[]; // empty: free text
|
|
55
56
|
type Messages = Record<string, string>;
|
|
56
57
|
type Translate = (text: string, vars?: Record<string, string | number>) => string; // fills {name}
|
|
57
58
|
function provideText(own: () => Messages | undefined): void;
|
|
@@ -99,12 +100,12 @@ interface BlockPlugin<P extends Props = Props> {
|
|
|
99
100
|
}
|
|
100
101
|
interface PluginViewProps<P> { props: P; blocks: StoredBlock[]; readonly: boolean; palette: string[]; plugins: BlockPlugin[] }
|
|
101
102
|
interface PluginPreviewProps<P> { props: P; blocks: StoredBlock[]; plugins: BlockPlugin[] }
|
|
102
|
-
interface MarkdownContext { inner: string
|
|
103
|
+
interface MarkdownContext { inner: string }
|
|
103
104
|
interface PropField {
|
|
104
105
|
key: string; label: string;
|
|
105
|
-
kind: 'text' | 'textarea' | 'color' | 'select' | 'slider' | '
|
|
106
|
+
kind: 'text' | 'textarea' | 'color' | 'select' | 'slider' | 'toggles'; // toggles: icon buttons, stores the list of the ones on
|
|
106
107
|
placeholder?: string;
|
|
107
|
-
options?: { value: string; label: string }[]; // select; stepped slider
|
|
108
|
+
options?: { value: string; label: string; icon?: Icon }[]; // select; stepped slider (label may use {n}); toggles (icon + tooltip)
|
|
108
109
|
min?: number; max?: number; step?: number; unit?: string; // number slider (stored as string)
|
|
109
110
|
none?: string; // unset label (number slider notch, optional colour)
|
|
110
111
|
views?: { key: string; label: string }[]; // per-view slider values
|
|
@@ -113,10 +114,10 @@ interface PropField {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
// @atom-forge/brickee/plugins
|
|
116
|
-
// table, youtube, map, gap — the only bundled plugins (none holds nested blocks or
|
|
117
|
+
// table, youtube, map, gap — the only bundled plugins (none holds nested blocks or images); PLUGINS = [table, youtube, map, gap]
|
|
117
118
|
// types: TableProps, YouTubeProps, MapProps, GapProps, Head
|
|
118
119
|
// helpers: youtubeId, mapSrc, gapLines, GAP_LINES, GAP_MAX, tableHeads, rowHead, HEADS, square
|
|
119
|
-
// Image blocks, boxes and other containers are app plugins you write yourself (see docs/en/writing-plugins.md
|
|
120
|
+
// Image blocks, boxes and other containers are app plugins you write yourself (see docs/en/writing-plugins.md).
|
|
120
121
|
|
|
121
122
|
// @atom-forge/brickee/schema (zod)
|
|
122
123
|
const blocksSchema: ZodType<StoredBlock[]>;
|
|
@@ -130,16 +131,15 @@ function blocksSchemaFor(plugins: readonly { type: string; accepts?: readonly st
|
|
|
130
131
|
|
|
131
132
|
```svelte
|
|
132
133
|
<script lang="ts">
|
|
133
|
-
import { Editor, Preview, type
|
|
134
|
+
import { Editor, Preview, type StoredBlock } from '@atom-forge/brickee';
|
|
134
135
|
import { PLUGINS } from '@atom-forge/brickee/plugins';
|
|
135
136
|
import { hu } from '@atom-forge/brickee/locales/hu';
|
|
136
137
|
|
|
137
|
-
let { media = [] }: { media?: MediaItem[] } = $props();
|
|
138
138
|
let value = $state<StoredBlock[]>([]);
|
|
139
139
|
</script>
|
|
140
140
|
|
|
141
|
-
<Editor bind:value plugins={PLUGINS}
|
|
142
|
-
<Preview blocks={value} plugins={PLUGINS}
|
|
141
|
+
<Editor bind:value plugins={PLUGINS} messages={hu} />
|
|
142
|
+
<Preview blocks={value} plugins={PLUGINS} messages={hu} />
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
## Common extensions
|
|
@@ -214,7 +214,7 @@ export const plugins: BlockPlugin[] = [
|
|
|
214
214
|
];
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
-
**Markdown form and conversion** — `markdown(props, { inner
|
|
217
|
+
**Markdown form and conversion** — `markdown(props, { inner })` returns markdown or `''`; use `mdText`, `mdJoin`, `mdQuote`. Whole document: `toMarkdown(doc.map(fromStored), { plugins })`; import: `fromMarkdown(text).map(toStored)` (both in the browser).
|
|
218
218
|
|
|
219
219
|
**Per-view fields** — add `views` to a slider field and provide the current view:
|
|
220
220
|
|
|
@@ -256,9 +256,12 @@ const result = blocksSchemaFor(plugins).safeParse(input); // or pass [{ type, ac
|
|
|
256
256
|
| Symptom | Cause | Fix |
|
|
257
257
|
|---|---|---|
|
|
258
258
|
| Fields and popups unstyled | `@atom-forge/ui` theme CSS / `@source` missing | Set up `@atom-forge/ui`; render beneath its `Root`. |
|
|
259
|
+
| Block handles stick out on the left | Handles are drawn ~22 px left of the editor's box | Give the editor's container ~22 px left padding. |
|
|
259
260
|
| Context errors with a linked local copy | Duplicate `svelte` / `@atom-forge/ui` | Vite `resolve.dedupe: ['svelte', '@atom-forge/ui', '@lucide/svelte', 'zod']`. |
|
|
260
261
|
| A block is missing from `<Preview>` | Its plugin has no `preview` | Add one (bundled plugins have a default) or render it yourself. |
|
|
261
|
-
| "Unknown block" in the editor, block missing on the site | Plugin missing from `plugins
|
|
262
|
+
| "Unknown block" in the editor, block missing on the site | Plugin missing from `plugins` | Same plugins for editor and preview. |
|
|
263
|
+
| Image block renders nothing | Preview rejects the stored address, or the app's lookup lacks the id | Store `http(s)` / site-relative addresses; resolve ids from the same app data everywhere. |
|
|
264
|
+
| `media` prop, `useMedia`, `findMedia`, `kind: 'image'` missing | Removed: images are the app's business | Own picking and lookup in the app's image plugin (see `docs/en/upgrading.md`). |
|
|
262
265
|
| Box not offered inside a box | One nesting level | By design. |
|
|
263
266
|
| Block not offered inside a container | `accepts` or host `palette` excludes it | Add its palette id (or `'md:*'`). |
|
|
264
267
|
| Gated content visible to all | `gate` only marks | Cut the document on the server. |
|
package/dist/Editor.svelte
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
plainText
|
|
20
20
|
} from './dom';
|
|
21
21
|
import { fromMarkdown, fromStored, plainHtml, toMarkdown, toStored } from './markdown';
|
|
22
|
-
import {
|
|
22
|
+
import { useCodeLanguages } from './code';
|
|
23
23
|
import { provideText, useText, type Messages } from './i18n';
|
|
24
24
|
import { PopupContainer, createPopupManager, getPopupManager } from '@atom-forge/ui';
|
|
25
25
|
import LinkPrompt from './LinkPrompt.svelte';
|
|
@@ -41,8 +41,6 @@
|
|
|
41
41
|
plugins?: BlockPlugin[];
|
|
42
42
|
/** Set by plugins with nested blocks: a compact editor inside another block. */
|
|
43
43
|
nested?: boolean;
|
|
44
|
-
/** Images the blocks can pick from (the page's attachments); nested editors share their parent's. */
|
|
45
|
-
media?: MediaItem[];
|
|
46
44
|
/** UI text in the host's language (English text -> translation); nested editors share their parent's. */
|
|
47
45
|
messages?: Messages;
|
|
48
46
|
}
|
|
@@ -53,18 +51,15 @@
|
|
|
53
51
|
palette: paletteProp,
|
|
54
52
|
plugins = [],
|
|
55
53
|
nested = false,
|
|
56
|
-
media,
|
|
57
54
|
messages
|
|
58
55
|
}: Props = $props();
|
|
59
56
|
|
|
60
|
-
provideMedia(() => media);
|
|
61
57
|
provideText(() => messages);
|
|
62
58
|
// Popups (the link field) are atom-forge popups; the top editor has their container, nested editors share it.
|
|
63
59
|
// svelte-ignore state_referenced_locally
|
|
64
60
|
const popups = nested ? getPopupManager() : createPopupManager(getPopupManager());
|
|
65
61
|
const tr = useText();
|
|
66
62
|
const placeholder = $derived(placeholderProp ?? tr("Write something, or press '/' for blocks…"));
|
|
67
|
-
const getMedia = useMedia();
|
|
68
63
|
|
|
69
64
|
// One level of nesting: an editor inside a block offers no block types that hold blocks themselves, nor unique
|
|
70
65
|
// ones (those belong to the top level).
|
|
@@ -818,7 +813,7 @@
|
|
|
818
813
|
function onCopy(e: ClipboardEvent, cut = false) {
|
|
819
814
|
if (!selSet.size) return;
|
|
820
815
|
e.preventDefault();
|
|
821
|
-
e.clipboardData?.setData('text/plain', toMarkdown(value.filter((b) => selSet.has(b.id)), { plugins
|
|
816
|
+
e.clipboardData?.setData('text/plain', toMarkdown(value.filter((b) => selSet.has(b.id)), { plugins }));
|
|
822
817
|
if (cut) deleteSelected();
|
|
823
818
|
}
|
|
824
819
|
|
|
@@ -843,11 +838,22 @@
|
|
|
843
838
|
|
|
844
839
|
/* ------------------------------------------------------ properties panel */
|
|
845
840
|
|
|
846
|
-
// Plugin blocks show their tag and fields on their top (PropsBar); a code block
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
841
|
+
// Plugin blocks and code blocks show their tag and fields on their top (PropsBar); read-only, a code block shows its
|
|
842
|
+
// language in its corner.
|
|
843
|
+
// Free text, or a dropdown when the host gives a list (provideCodeLanguages). A stored language missing from the
|
|
844
|
+
// list stays an option, so opening an older document loses nothing.
|
|
845
|
+
const codeLanguages = useCodeLanguages();
|
|
846
|
+
const codeFields = (b: Block): PropField[] => {
|
|
847
|
+
const list = codeLanguages();
|
|
848
|
+
if (!list.length)
|
|
849
|
+
// Only characters an opening fence can carry (```c++, ```objective-c).
|
|
850
|
+
return [{ key: 'lang', label: 'Language', kind: 'text', placeholder: 'e.g. ts, py, css', parse: (s) => s.replace(/[^\w+#.-]/g, '') }];
|
|
851
|
+
const lang = typeof b.props?.lang === 'string' ? b.props.lang : '';
|
|
852
|
+
const options = [{ value: '', label: 'None' }, ...list.map((l) => ({ value: l.value, label: l.label ?? l.value }))];
|
|
853
|
+
if (lang && !list.some((l) => l.value === lang)) options.push({ value: lang, label: lang });
|
|
854
|
+
return [{ key: 'lang', label: 'Language', kind: 'select', options }];
|
|
855
|
+
};
|
|
856
|
+
const codeTag = (b: Block) => [tr('code'), b.props?.lang].filter(Boolean).join(' · ');
|
|
851
857
|
function tagOf(b: Block) {
|
|
852
858
|
const p = pluginOf(b.plugin);
|
|
853
859
|
return p ? p.tag(b.props ?? {}).split(' · ').map((s) => tr(s)).join(' · ') : (b.plugin ?? '');
|
|
@@ -858,19 +864,6 @@
|
|
|
858
864
|
return f.format ? f.format(v) : v == null ? '' : String(v);
|
|
859
865
|
};
|
|
860
866
|
|
|
861
|
-
/** Image fields: picking a picked image again takes it out. */
|
|
862
|
-
function pickMedia(b: Block, f: PropField, id: string) {
|
|
863
|
-
typed();
|
|
864
|
-
const next = { ...(b.props ?? {}) };
|
|
865
|
-
if (f.kind === 'image') {
|
|
866
|
-
if (next[f.key] === id) delete next[f.key];
|
|
867
|
-
else next[f.key] = id;
|
|
868
|
-
} else {
|
|
869
|
-
const ids = (next[f.key] as string[] | undefined) ?? [];
|
|
870
|
-
next[f.key] = ids.includes(id) ? ids.filter((x) => x !== id) : [...ids, id];
|
|
871
|
-
}
|
|
872
|
-
b.props = next;
|
|
873
|
-
}
|
|
874
867
|
/** Sets one property to a value as it is ('' or undefined removes it). */
|
|
875
868
|
function setValue(b: Block, key: string, v: unknown) {
|
|
876
869
|
typed();
|
|
@@ -1274,7 +1267,7 @@
|
|
|
1274
1267
|
{#if plugin}
|
|
1275
1268
|
<div class="plugin">
|
|
1276
1269
|
{#snippet propsBar()}
|
|
1277
|
-
<PropsBar block={b} tag={tagOf(b)} fields={plugin.fields ?? []}
|
|
1270
|
+
<PropsBar block={b} tag={tagOf(b)} fields={plugin.fields ?? []} {tr} {setProp} {setValue} open={fresh.has(b.id)} />
|
|
1278
1271
|
{/snippet}
|
|
1279
1272
|
<PluginHost
|
|
1280
1273
|
{plugin}
|
|
@@ -1307,6 +1300,10 @@
|
|
|
1307
1300
|
onclick={() => toggleTodo(b)}
|
|
1308
1301
|
>{#if b.checked}<Check size={12} strokeWidth={3} />{/if}</button
|
|
1309
1302
|
>
|
|
1303
|
+
{:else if b.type === 'code' && !readonly}
|
|
1304
|
+
<div class="code-fields">
|
|
1305
|
+
<PropsBar block={b} tag={codeTag(b)} fields={codeFields(b)} {tr} {setProp} {setValue} />
|
|
1306
|
+
</div>
|
|
1310
1307
|
{/if}
|
|
1311
1308
|
<div
|
|
1312
1309
|
class="ce"
|
|
@@ -1334,20 +1331,12 @@
|
|
|
1334
1331
|
}}
|
|
1335
1332
|
></div>
|
|
1336
1333
|
{/if}
|
|
1337
|
-
{#if b.type === 'code'}
|
|
1338
|
-
<!-- the language of a code block, in its corner -->
|
|
1339
|
-
<
|
|
1334
|
+
{#if b.type === 'code' && readonly}
|
|
1335
|
+
<!-- read-only: the language of a code block, in its corner -->
|
|
1336
|
+
<div class="badge">
|
|
1340
1337
|
<span class="tag">{tr('code')}</span>
|
|
1341
|
-
{#if
|
|
1342
|
-
|
|
1343
|
-
class="lang"
|
|
1344
|
-
placeholder={tr('language')}
|
|
1345
|
-
aria-label={tr('Language')}
|
|
1346
|
-
size="6"
|
|
1347
|
-
bind:value={() => fieldText(b, CODE_FIELDS[0]), (v: string) => setProp(b, CODE_FIELDS[0], v)}
|
|
1348
|
-
/>
|
|
1349
|
-
{:else if b.props?.lang}<span class="tag">{b.props.lang}</span>{/if}
|
|
1350
|
-
</label>
|
|
1338
|
+
{#if b.props?.lang}<span class="tag">{b.props.lang}</span>{/if}
|
|
1339
|
+
</div>
|
|
1351
1340
|
{:else if b.type === 'plugin' && (readonly || !pluginOf(b.plugin)?.fields?.length)}
|
|
1352
1341
|
<div class="badge"><span class="tag">{tagOf(b)}</span></div>
|
|
1353
1342
|
{/if}
|
|
@@ -1448,12 +1437,12 @@
|
|
|
1448
1437
|
|
|
1449
1438
|
<style>
|
|
1450
1439
|
.editor {
|
|
1451
|
-
--fg: #1d1d1f;
|
|
1452
|
-
--muted: #a0a0a8;
|
|
1440
|
+
--fg: var(--color-surface-contrast, #1d1d1f);
|
|
1441
|
+
--muted: var(--color-muted-contrast, #a0a0a8);
|
|
1453
1442
|
--accent: #3b82f6;
|
|
1454
1443
|
--sel: rgba(59, 130, 246, 0.14);
|
|
1455
|
-
--surface: #fff;
|
|
1456
|
-
--border: #e5e5ea;
|
|
1444
|
+
--surface: var(--color-surface, #fff);
|
|
1445
|
+
--border: var(--color-frame, #e5e5ea);
|
|
1457
1446
|
--code-bg: rgba(120, 120, 140, 0.14);
|
|
1458
1447
|
position: relative;
|
|
1459
1448
|
outline: none;
|
|
@@ -1518,11 +1507,12 @@
|
|
|
1518
1507
|
bottom: -2px;
|
|
1519
1508
|
}
|
|
1520
1509
|
|
|
1510
|
+
/* 1px short at both ends, so the bars of neighbouring blocks do not touch. */
|
|
1521
1511
|
.gutter {
|
|
1522
1512
|
position: absolute;
|
|
1523
1513
|
left: -22px;
|
|
1524
|
-
top:
|
|
1525
|
-
bottom:
|
|
1514
|
+
top: 1px;
|
|
1515
|
+
bottom: 1px;
|
|
1526
1516
|
width: 20px;
|
|
1527
1517
|
}
|
|
1528
1518
|
.handle {
|
|
@@ -1610,7 +1600,7 @@
|
|
|
1610
1600
|
top: 50%;
|
|
1611
1601
|
translate: 0 -50%;
|
|
1612
1602
|
border: 4px solid transparent;
|
|
1613
|
-
border-right-color: #1d1d1f;
|
|
1603
|
+
border-right-color: var(--color-surface-contrast, #1d1d1f);
|
|
1614
1604
|
border-left: 0;
|
|
1615
1605
|
}
|
|
1616
1606
|
.grab:has(.grip:hover) .bubble,
|
|
@@ -1635,8 +1625,9 @@
|
|
|
1635
1625
|
height: 26px;
|
|
1636
1626
|
padding: 0;
|
|
1637
1627
|
border: 0;
|
|
1638
|
-
|
|
1639
|
-
|
|
1628
|
+
/* inverted: the theme's text colour as the background */
|
|
1629
|
+
color: var(--color-surface, #fff);
|
|
1630
|
+
background: var(--color-surface-contrast, #1d1d1f);
|
|
1640
1631
|
cursor: pointer;
|
|
1641
1632
|
box-shadow: 0 4px 12px rgb(0 0 0 / 0.2);
|
|
1642
1633
|
}
|
|
@@ -1649,7 +1640,7 @@
|
|
|
1649
1640
|
padding-right: 2px;
|
|
1650
1641
|
}
|
|
1651
1642
|
.bubble button:hover {
|
|
1652
|
-
background: #
|
|
1643
|
+
background: color-mix(in srgb, var(--color-surface-contrast, #1d1d1f) 82%, var(--color-surface, #fff));
|
|
1653
1644
|
}
|
|
1654
1645
|
.bubble button.del:hover {
|
|
1655
1646
|
background: #c62828;
|
|
@@ -1768,7 +1759,7 @@
|
|
|
1768
1759
|
.t-plugin > .badge {
|
|
1769
1760
|
top: calc(0.35em + 5px);
|
|
1770
1761
|
/* Readable over a map or an image. */
|
|
1771
|
-
background:
|
|
1762
|
+
background: color-mix(in srgb, var(--color-surface, #fff) 92%, transparent);
|
|
1772
1763
|
border-radius: 6px;
|
|
1773
1764
|
}
|
|
1774
1765
|
.tag {
|
|
@@ -1782,25 +1773,22 @@
|
|
|
1782
1773
|
font: 500 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1783
1774
|
user-select: none;
|
|
1784
1775
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
outline: none;
|
|
1776
|
+
/* the fields bar on its own row above the code */
|
|
1777
|
+
.t-code {
|
|
1778
|
+
flex-wrap: wrap;
|
|
1779
|
+
row-gap: 0;
|
|
1780
|
+
}
|
|
1781
|
+
.code-fields,
|
|
1782
|
+
.t-code .ce {
|
|
1783
|
+
flex-basis: 100%;
|
|
1794
1784
|
}
|
|
1795
|
-
.
|
|
1796
|
-
|
|
1797
|
-
border-color: var(--border);
|
|
1798
|
-
background: var(--bg, #fff);
|
|
1785
|
+
.t-code:has(> .badge) .ce {
|
|
1786
|
+
padding-right: 7.5em;
|
|
1799
1787
|
}
|
|
1800
1788
|
.t-code .ce {
|
|
1801
1789
|
background: var(--code-bg);
|
|
1802
1790
|
border-radius: 8px;
|
|
1803
|
-
padding: 0.7em
|
|
1791
|
+
padding: 0.7em 0.9em;
|
|
1804
1792
|
font: 0.88em/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1805
1793
|
white-space: pre-wrap;
|
|
1806
1794
|
}
|
package/dist/Editor.svelte.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type MediaItem } from './media';
|
|
2
1
|
import { type Messages } from './i18n';
|
|
3
2
|
import { type BlockPlugin, type StoredBlock } from './types';
|
|
4
3
|
interface Props {
|
|
@@ -12,8 +11,6 @@ interface Props {
|
|
|
12
11
|
plugins?: BlockPlugin[];
|
|
13
12
|
/** Set by plugins with nested blocks: a compact editor inside another block. */
|
|
14
13
|
nested?: boolean;
|
|
15
|
-
/** Images the blocks can pick from (the page's attachments); nested editors share their parent's. */
|
|
16
|
-
media?: MediaItem[];
|
|
17
14
|
/** UI text in the host's language (English text -> translation); nested editors share their parent's. */
|
|
18
15
|
messages?: Messages;
|
|
19
16
|
}
|
package/dist/LinkPrompt.svelte
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</script>
|
|
23
23
|
|
|
24
24
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
25
|
-
<div class="
|
|
25
|
+
<div class="link" {onkeydown}>
|
|
26
26
|
<Input bind:this={input} compact monospace bind:value={url} placeholder="https://…" />
|
|
27
27
|
<Button compact accent label={tr('OK')} onclick={() => popups.resolve(url.trim())} />
|
|
28
28
|
{#if href}<Button compact ghost label={tr('Remove')} onclick={() => popups.resolve('')} />{/if}
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
gap: 6px;
|
|
35
35
|
width: 340px;
|
|
36
36
|
padding: 8px;
|
|
37
|
-
background: #fff;
|
|
38
|
-
border: 1px solid #e5e5ea;
|
|
37
|
+
background: var(--color-surface, #fff);
|
|
38
|
+
border: 1px solid var(--color-frame, #e5e5ea);
|
|
39
39
|
border-radius: 12px;
|
|
40
40
|
box-shadow: 0 10px 36px rgba(0, 0, 0, 0.2);
|
|
41
41
|
}
|
package/dist/Preview.svelte
CHANGED
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
<script lang="ts">
|
|
50
50
|
import { useText } from './i18n';
|
|
51
51
|
import { inline } from './markdown';
|
|
52
|
-
import { provideMedia, type MediaItem } from './media';
|
|
53
52
|
import { provideText, type Messages } from './i18n';
|
|
54
53
|
import type { BlockPlugin } from './types';
|
|
55
54
|
|
|
@@ -58,16 +57,13 @@
|
|
|
58
57
|
plugins: BlockPlugin[];
|
|
59
58
|
/** Set by plugins rendering their nested blocks; only the top level splits at the supporters-only divider. */
|
|
60
59
|
nested?: boolean;
|
|
61
|
-
/** The images blocks refer to by id; nested previews share their parent's. */
|
|
62
|
-
media?: MediaItem[];
|
|
63
60
|
/** UI text in the host's language; nested previews share their parent's. */
|
|
64
61
|
messages?: Messages;
|
|
65
62
|
/** Mark the blocks after a gate (the supporters-only divider) with a label and a dashed line; default: yes. */
|
|
66
63
|
markGated?: boolean;
|
|
67
64
|
}
|
|
68
|
-
let { blocks, plugins, nested = false,
|
|
65
|
+
let { blocks, plugins, nested = false, messages, markGated = true }: Props = $props();
|
|
69
66
|
|
|
70
|
-
provideMedia(() => media);
|
|
71
67
|
provideText(() => messages);
|
|
72
68
|
const tr = useText();
|
|
73
69
|
|
package/dist/Preview.svelte.d.ts
CHANGED
|
@@ -15,7 +15,6 @@ export type Part = {
|
|
|
15
15
|
lists: ListNode[];
|
|
16
16
|
};
|
|
17
17
|
export declare function parts(blocks: StoredBlock[]): Part[];
|
|
18
|
-
import { type MediaItem } from './media';
|
|
19
18
|
import { type Messages } from './i18n';
|
|
20
19
|
import type { BlockPlugin } from './types';
|
|
21
20
|
interface Props {
|
|
@@ -23,8 +22,6 @@ interface Props {
|
|
|
23
22
|
plugins: BlockPlugin[];
|
|
24
23
|
/** Set by plugins rendering their nested blocks; only the top level splits at the supporters-only divider. */
|
|
25
24
|
nested?: boolean;
|
|
26
|
-
/** The images blocks refer to by id; nested previews share their parent's. */
|
|
27
|
-
media?: MediaItem[];
|
|
28
25
|
/** UI text in the host's language; nested previews share their parent's. */
|
|
29
26
|
messages?: Messages;
|
|
30
27
|
/** Mark the blocks after a gate (the supporters-only divider) with a label and a dashed line; default: yes. */
|