@atom-forge/brickee 0.0.0-stage → 0.0.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README-AI.md +283 -0
- package/README.md +41 -2
- package/dist/Editor.svelte +1968 -0
- package/dist/Editor.svelte.d.ts +22 -0
- package/dist/ImagePicker.svelte +131 -0
- package/dist/ImagePicker.svelte.d.ts +13 -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 +186 -0
- package/dist/Preview.svelte.d.ts +35 -0
- package/dist/PropsBar.svelte +395 -0
- package/dist/PropsBar.svelte.d.ts +20 -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 +147 -0
- package/dist/markdown.d.ts +25 -0
- package/dist/markdown.js +219 -0
- package/dist/media.d.ts +17 -0
- package/dist/media.js +12 -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 +563 -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 +94 -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 +133 -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 +115 -0
- package/docs/en/editor-and-preview.md +141 -0
- package/docs/en/getting-started.md +81 -0
- package/docs/en/host-contexts.md +275 -0
- package/docs/en/index.md +38 -0
- package/docs/en/recipes-and-pitfalls.md +122 -0
- package/docs/en/server-side.md +95 -0
- package/docs/en/upgrading.md +55 -0
- package/docs/en/writing-plugins.md +305 -0
- package/docs/hu/bundled-plugins.md +99 -0
- package/docs/hu/documents-and-markdown.md +115 -0
- package/docs/hu/editor-and-preview.md +141 -0
- package/docs/hu/getting-started.md +81 -0
- package/docs/hu/host-contexts.md +275 -0
- package/docs/hu/index.md +38 -0
- package/docs/hu/recipes-and-pitfalls.md +122 -0
- package/docs/hu/server-side.md +95 -0
- package/docs/hu/upgrading.md +55 -0
- package/docs/hu/writing-plugins.md +305 -0
- package/package.json +110 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Configure releases with Ship.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.0.1] - 2026-11-10
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- The block editor, moved here from the gamer365 project (where it was the `block-editor` workspace package), as `@atom-forge/brickee`: the markdown core with the editor, the preview, the JSON stored form and the markdown conversions; the kit for writing plugins (`Frame`, `Empty`, block fields, media / view / text contexts, helpers); the bundled plugins in `@atom-forge/brickee/plugins` (general blocks only: YouTube, map, table, gap — the app-specific ones, such as boxes, callouts, images and the supporters-only divider, stay in the app that gave them their meaning); the zod schema in `@atom-forge/brickee/schema`; the Hungarian UI text in `@atom-forge/brickee/locales/hu`.
|
|
19
|
+
- `BlockPlugin.preview` is optional: an app with its own renderer leaves it out, and `<Preview>` skips such blocks. The bundled plugins keep a default preview, which an app may use or replace.
|
|
20
|
+
- An optional markdown form for plugins (`BlockPlugin.markdown`), used by `toMarkdown(blocks, { plugins, media })` and by copying in the editor; the bundled plugins have one where it makes sense (YouTube and map as a link, table as a GFM table).
|
|
21
|
+
- Scroll sync: `scrollSync()` keeps a preview box scrolled to the same fraction as the editor's box (one way, also when the preview's content changes size; `use:sync.source` / `use:sync.target`, `align()`), with `scrollFraction` / `setScrollFraction` for keeping a position across a layout change. Proportional only: the two show roughly the same place, not the same block.
|
|
22
|
+
- Repository frame following the AtomForge packages: agent instructions, book, English and Hungarian docs, `README-AI.md`, `TODO.md`, `notes/`, and a tag-triggered publish workflow with `@atom-forge/ship` 0.2.0 (installed from npm).
|
|
23
|
+
|
|
24
|
+
---
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gergely Laborci
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README-AI.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# @atom-forge/brickee — AI guide
|
|
2
|
+
|
|
3
|
+
Compact reference for coding agents using `@atom-forge/brickee` in a Svelte 5 project. It is derived from [`docs/en/`](docs/en/index.md); where the two disagree, `docs/en/` is authoritative.
|
|
4
|
+
|
|
5
|
+
Brickee is a block editor. Its core is a markdown block editor (`md:p`, `md:h1`…`md:h6`, `md:ul`, `md:ol`, `md:todo`, `md:quote`, `md:code`, `md:hr`) plus the kit plugins are written with; every other block is a plugin with an editor view, a site preview, fields and optionally a markdown form. Documents are stored as JSON (`StoredBlock[]`). The bundled plugins (`@atom-forge/brickee/plugins`) are plugins like any other, opt-in.
|
|
6
|
+
|
|
7
|
+
## Rules
|
|
8
|
+
|
|
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
|
+
- 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
|
+
- 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
|
+
- No app-specific editor props: app data reaches plugins through contexts (`media` / `provideMedia`, `provideView`, `messages` / `provideText`) or plugin fields.
|
|
13
|
+
- Plugin props are untrusted user input. Previews sanitize: `safeColor` for colours, check links, clamp numbers, look images up by id in the media list, never `{@html}` with props.
|
|
14
|
+
- A plugin view declares both `props` and `blocks` with `$bindable()`, even if unused.
|
|
15
|
+
- Containers (`content: 'blocks'`) nest one level only; nested editors never offer containers or unique plugins. `accepts` narrows their content (`'md:*'` = every built-in).
|
|
16
|
+
- `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.
|
|
17
|
+
- `toMarkdown` and `toStored` use the DOM: browser only. `fromMarkdown`, `fromStored`, `inline`, the schema and `./model` run anywhere.
|
|
18
|
+
- Validate saved documents on the server with `blocksSchemaFor(plugins)`; it checks shape and `accepts`, not known types, uniqueness or prop contents.
|
|
19
|
+
- Scroll sync is proportional (same scroll fraction), one way, editor → preview; it needs no markup and shows roughly the same place, not the same block.
|
|
20
|
+
- `<Preview>` is optional: the editor doesn't depend on it. An app with its own renderer can render `StoredBlock[]` itself and omit plugin `preview`s; bundled plugins ship a default preview you may use, replace (`{ ...youtube, preview: Mine }`) or ignore.
|
|
21
|
+
- UI text: English source text is the key; pass `messages` (e.g. `hu` from `@atom-forge/brickee/locales/hu`, extended with your own keys) to the top editor/preview.
|
|
22
|
+
|
|
23
|
+
## Public API
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// @atom-forge/brickee — components
|
|
27
|
+
// <Editor bind:value={StoredBlock[]} plugins?={BlockPlugin[]} palette?={string[]} media?={MediaItem[]}
|
|
28
|
+
// messages?={Messages} readonly?={boolean} placeholder?={string} /> // `nested` is for Frame only
|
|
29
|
+
// <Preview blocks={StoredBlock[]} plugins={BlockPlugin[]} media?={MediaItem[]} messages?={Messages}
|
|
30
|
+
// markGated?={boolean /* default true */} nested?={boolean} />
|
|
31
|
+
// <Frame bind:blocks {readonly} {palette} {plugins} heading?={string} empty?={string} image?={string} placeholder?={string} />
|
|
32
|
+
// <Empty icon={Icon} text={string} />
|
|
33
|
+
|
|
34
|
+
interface StoredBlock { type: string; indent: number; text?: string; props?: Record<string, unknown>; blocks?: StoredBlock[] }
|
|
35
|
+
const MD = 'md:'; const MAX_INDENT = 6; const MAX_DEPTH = 2;
|
|
36
|
+
function isMd(b: StoredBlock): boolean;
|
|
37
|
+
function acceptsId(accepts: readonly string[] | undefined, id: string): boolean;
|
|
38
|
+
function inlineToPlainText(md: string): string;
|
|
39
|
+
function blocksToPlainText(blocks: StoredBlock[]): string;
|
|
40
|
+
|
|
41
|
+
// markdown (Block = the editor's working form: { id, type, html, indent, checked?, props?, plugin?, blocks? })
|
|
42
|
+
function inline(md: string): string; // inline markdown -> safe inline HTML
|
|
43
|
+
function fromStored(s: StoredBlock): Block;
|
|
44
|
+
function toStored(b: Block): StoredBlock; // browser only
|
|
45
|
+
function fromMarkdown(src: string): Block[]; // one block per non-empty line; built-in blocks only
|
|
46
|
+
function toMarkdown(blocks: Block[], opts?: { plugins?: BlockPlugin[]; media?: MediaItem[] }): string; // browser only
|
|
47
|
+
|
|
48
|
+
// 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
|
+
function provideView(get: () => string | undefined): void;
|
|
54
|
+
function useView(): () => string | undefined;
|
|
55
|
+
type Messages = Record<string, string>;
|
|
56
|
+
type Translate = (text: string, vars?: Record<string, string | number>) => string; // fills {name}
|
|
57
|
+
function provideText(own: () => Messages | undefined): void;
|
|
58
|
+
function useText(): Translate;
|
|
59
|
+
function translator(messages?: Messages): Translate;
|
|
60
|
+
|
|
61
|
+
// scroll sync (editor -> preview, by scroll fraction)
|
|
62
|
+
interface ScrollSync {
|
|
63
|
+
source: Action<HTMLElement>; // use:sync.source on the editor's scrolling box
|
|
64
|
+
target: Action<HTMLElement, boolean | undefined>; // use:sync.target={on} on the preview's (default on)
|
|
65
|
+
align(): void; // re-sync now (e.g. after the preview width changed)
|
|
66
|
+
}
|
|
67
|
+
function scrollSync(): ScrollSync;
|
|
68
|
+
function scrollFraction(box: HTMLElement): number; // 0…1 (0 if it does not scroll)
|
|
69
|
+
function setScrollFraction(box: HTMLElement, f: number): void;
|
|
70
|
+
|
|
71
|
+
// palette
|
|
72
|
+
interface PaletteItem { id: string; label: string; hint: string; keys: string; icon: Icon }
|
|
73
|
+
const PALETTE_ITEMS: PaletteItem[]; const BUILT_IN_IDS: string[];
|
|
74
|
+
function narrowPalette(palette: string[], accepts: readonly string[] | undefined): string[];
|
|
75
|
+
|
|
76
|
+
// kit helpers
|
|
77
|
+
function safeColor(c: unknown, fallback: string): string; // #hex (3–8 digits) or fallback
|
|
78
|
+
function lines(s: string): string[]; // trimmed non-empty lines
|
|
79
|
+
function baseValue(v: unknown): unknown; // base of a per-view prop
|
|
80
|
+
function viewValues(v: unknown, keys: string[]): unknown[]; // per-view prop, inheritance applied
|
|
81
|
+
function mdText(s: unknown): string; // plain text safe in markdown
|
|
82
|
+
function mdJoin(...parts: (string | false | null | undefined)[]): string; // blank-line join, empties dropped
|
|
83
|
+
function mdQuote(md: string): string; // '> ' every line
|
|
84
|
+
|
|
85
|
+
// plugins
|
|
86
|
+
interface BlockPlugin<P extends Props = Props> {
|
|
87
|
+
type: string; // not 'md:'; schema: /^[a-z][a-z0-9:-]{0,39}$/
|
|
88
|
+
label: string; hint: string; keys: string; icon: Icon;
|
|
89
|
+
props: () => P; // a new block's props
|
|
90
|
+
fields?: PropField[];
|
|
91
|
+
tag(props: P): string; // mini label; ' · ' parts translated separately
|
|
92
|
+
content: 'none' | 'text' | 'plain' | 'blocks'; // plugins use 'none' or 'blocks'
|
|
93
|
+
view: Component<PluginViewProps<any>, {}, 'props' | 'blocks'>;
|
|
94
|
+
preview?: Component<PluginPreviewProps<any>>; // optional: without it <Preview> renders nothing for the block
|
|
95
|
+
accepts?: string[]; // container content as palette ids; 'md:*'
|
|
96
|
+
markdown?(props: P, ctx: MarkdownContext): string; // '' = left out
|
|
97
|
+
unique?: boolean;
|
|
98
|
+
gate?: boolean;
|
|
99
|
+
}
|
|
100
|
+
interface PluginViewProps<P> { props: P; blocks: StoredBlock[]; readonly: boolean; palette: string[]; plugins: BlockPlugin[] }
|
|
101
|
+
interface PluginPreviewProps<P> { props: P; blocks: StoredBlock[]; plugins: BlockPlugin[] }
|
|
102
|
+
interface MarkdownContext { inner: string; media: MediaItem[] }
|
|
103
|
+
interface PropField {
|
|
104
|
+
key: string; label: string;
|
|
105
|
+
kind: 'text' | 'textarea' | 'color' | 'select' | 'slider' | 'image' | 'images';
|
|
106
|
+
placeholder?: string;
|
|
107
|
+
options?: { value: string; label: string }[]; // select; stepped slider; label may use {n}
|
|
108
|
+
min?: number; max?: number; step?: number; unit?: string; // number slider (stored as string)
|
|
109
|
+
none?: string; // unset label (number slider notch, optional colour)
|
|
110
|
+
views?: { key: string; label: string }[]; // per-view slider values
|
|
111
|
+
format?: (value: unknown) => string;
|
|
112
|
+
parse?: (text: string) => unknown; // '' / null removes the key
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// @atom-forge/brickee/plugins
|
|
116
|
+
// table, youtube, map, gap — the only bundled plugins (none holds nested blocks or uses media); PLUGINS = [table, youtube, map, gap]
|
|
117
|
+
// types: TableProps, YouTubeProps, MapProps, GapProps, Head
|
|
118
|
+
// 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, host-contexts.md).
|
|
120
|
+
|
|
121
|
+
// @atom-forge/brickee/schema (zod)
|
|
122
|
+
const blocksSchema: ZodType<StoredBlock[]>;
|
|
123
|
+
function blocksSchemaFor(plugins: readonly { type: string; accepts?: readonly string[] }[]): ZodType<StoredBlock[]>;
|
|
124
|
+
|
|
125
|
+
// @atom-forge/brickee/model — StoredBlock, MD, MAX_INDENT, MAX_DEPTH, isMd, acceptsId, inlineToPlainText, blocksToPlainText
|
|
126
|
+
// @atom-forge/brickee/locales/hu — hu: Messages
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Minimal example
|
|
130
|
+
|
|
131
|
+
```svelte
|
|
132
|
+
<script lang="ts">
|
|
133
|
+
import { Editor, Preview, type MediaItem, type StoredBlock } from '@atom-forge/brickee';
|
|
134
|
+
import { PLUGINS } from '@atom-forge/brickee/plugins';
|
|
135
|
+
import { hu } from '@atom-forge/brickee/locales/hu';
|
|
136
|
+
|
|
137
|
+
let { media = [] }: { media?: MediaItem[] } = $props();
|
|
138
|
+
let value = $state<StoredBlock[]>([]);
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<Editor bind:value plugins={PLUGINS} {media} messages={hu} />
|
|
142
|
+
<Preview blocks={value} plugins={PLUGINS} {media} messages={hu} />
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Common extensions
|
|
146
|
+
|
|
147
|
+
**Writing a plugin** — a props interface, a view, a preview, the object:
|
|
148
|
+
|
|
149
|
+
```svelte
|
|
150
|
+
<!-- ButtonView.svelte -->
|
|
151
|
+
<script lang="ts" module>
|
|
152
|
+
export interface ButtonProps extends Record<string, unknown> { label: string; url: string; color: string }
|
|
153
|
+
</script>
|
|
154
|
+
<script lang="ts">
|
|
155
|
+
import { MousePointerClick } from '@lucide/svelte';
|
|
156
|
+
import { Empty, type PluginViewProps } from '@atom-forge/brickee';
|
|
157
|
+
let { props = $bindable(), blocks = $bindable() }: PluginViewProps<ButtonProps> = $props();
|
|
158
|
+
</script>
|
|
159
|
+
{#if props.label}<span>{props.label}</span>{:else}<Empty icon={MousePointerClick} text="Set the label and the link above" />{/if}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```svelte
|
|
163
|
+
<!-- ButtonPreview.svelte -->
|
|
164
|
+
<script lang="ts">
|
|
165
|
+
import { safeColor, type PluginPreviewProps } from '@atom-forge/brickee';
|
|
166
|
+
import type { ButtonProps } from './ButtonView.svelte';
|
|
167
|
+
let { props }: PluginPreviewProps<ButtonProps> = $props();
|
|
168
|
+
const href = $derived(/^(https?:|mailto:|\/(?!\/))/.test(String(props.url ?? '')) ? String(props.url) : '');
|
|
169
|
+
</script>
|
|
170
|
+
{#if href && props.label}<a {href} style:background={safeColor(props.color, '#2563eb')}>{props.label}</a>{/if}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// button.ts
|
|
175
|
+
import { MousePointerClick } from '@lucide/svelte';
|
|
176
|
+
import { mdText, type BlockPlugin } from '@atom-forge/brickee';
|
|
177
|
+
import ButtonView, { type ButtonProps } from './ButtonView.svelte';
|
|
178
|
+
import ButtonPreview from './ButtonPreview.svelte';
|
|
179
|
+
|
|
180
|
+
export const button: BlockPlugin<ButtonProps> = {
|
|
181
|
+
type: 'button', label: 'Button', hint: 'A link as a button', keys: 'button link cta', icon: MousePointerClick,
|
|
182
|
+
props: () => ({ label: '', url: '', color: '#2563eb' }),
|
|
183
|
+
fields: [
|
|
184
|
+
{ key: 'label', label: 'Label', kind: 'text' },
|
|
185
|
+
{ key: 'url', label: 'Link', kind: 'text', placeholder: 'https://…' },
|
|
186
|
+
{ key: 'color', label: 'Colour', kind: 'color' }
|
|
187
|
+
],
|
|
188
|
+
tag: () => 'button',
|
|
189
|
+
content: 'none',
|
|
190
|
+
markdown: (p) => (p.url ? `[${mdText(p.label)}](${p.url})` : ''),
|
|
191
|
+
view: ButtonView,
|
|
192
|
+
preview: ButtonPreview
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
A **container** uses `content: 'blocks'`, `accepts`, `<Frame bind:blocks {readonly} {palette} {plugins} />` in its view and `<Preview {blocks} {plugins} nested />` in its preview; its `markdown` gets the inner blocks as `ctx.inner` (`mdQuote(mdJoin(title, inner))`).
|
|
197
|
+
|
|
198
|
+
**Overriding a preview / adding fields** — spread the plugin, keep `type`:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import type { BlockPlugin } from '@atom-forge/brickee';
|
|
202
|
+
import { map, youtube } from '@atom-forge/brickee/plugins';
|
|
203
|
+
import SiteYouTube from './SiteYouTube.svelte'; // receives PluginPreviewProps<YouTubeProps>
|
|
204
|
+
import SiteMap from './SiteMap.svelte';
|
|
205
|
+
|
|
206
|
+
export const plugins: BlockPlugin[] = [
|
|
207
|
+
{ ...youtube, preview: SiteYouTube },
|
|
208
|
+
{
|
|
209
|
+
...map,
|
|
210
|
+
props: () => ({ ...map.props(), caption: '' }),
|
|
211
|
+
fields: [...(map.fields ?? []), { key: 'caption', label: 'Caption', kind: 'text' }],
|
|
212
|
+
preview: SiteMap
|
|
213
|
+
}
|
|
214
|
+
];
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Markdown form and conversion** — `markdown(props, { inner, media })` returns markdown or `''`; use `mdText`, `mdJoin`, `mdQuote`, and `findMedia(media, id)?.url` for images. Whole document: `toMarkdown(doc.map(fromStored), { plugins, media })`; import: `fromMarkdown(text).map(toStored)` (both in the browser).
|
|
218
|
+
|
|
219
|
+
**Per-view fields** — add `views` to a slider field and provide the current view:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import type { BlockPlugin } from '@atom-forge/brickee';
|
|
223
|
+
|
|
224
|
+
const VIEWS = [{ key: 'desktop', label: 'Desktop' }, { key: 'phone', label: 'Phone' }];
|
|
225
|
+
const perView = (p: BlockPlugin, key: string): BlockPlugin => ({ ...p, fields: p.fields?.map((f) => (f.key === key ? { ...f, views: VIEWS } : f)) });
|
|
226
|
+
// host component around <Editor>: provideView(() => currentViewKey);
|
|
227
|
+
// preview: const [desktop, phone] = viewValues(props.ratio, ['desktop', 'phone']);
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Stored as the base value alone (`"56"`) or an object by view key (`{ "desktop": "56", "phone": "100" }`); a view without its own value inherits from the view before it.
|
|
231
|
+
|
|
232
|
+
**Scroll sync (split view)** — one way, editor → preview, same scroll fraction; re-syncs on source scroll and target resize:
|
|
233
|
+
|
|
234
|
+
```svelte
|
|
235
|
+
<script lang="ts">
|
|
236
|
+
import { Editor, Preview, scrollSync, type StoredBlock } from '@atom-forge/brickee';
|
|
237
|
+
import { PLUGINS } from '@atom-forge/brickee/plugins';
|
|
238
|
+
let value = $state<StoredBlock[]>([]);
|
|
239
|
+
const sync = scrollSync();
|
|
240
|
+
</script>
|
|
241
|
+
<div style="overflow-y: auto; height: 100vh" use:sync.source><Editor bind:value plugins={PLUGINS} /></div>
|
|
242
|
+
<div style="overflow-y: auto; height: 100vh" use:sync.target><Preview blocks={value} plugins={PLUGINS} /></div>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Keep the place across a layout change: `const f = scrollFraction(box); change(); await tick(); setScrollFraction(box, f);`.
|
|
246
|
+
|
|
247
|
+
**Server validation**:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { blocksSchemaFor } from '@atom-forge/brickee/schema';
|
|
251
|
+
const result = blocksSchemaFor(plugins).safeParse(input); // or pass [{ type, accepts }] only
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Pitfalls
|
|
255
|
+
|
|
256
|
+
| Symptom | Cause | Fix |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| Fields and popups unstyled | `@atom-forge/ui` theme CSS / `@source` missing | Set up `@atom-forge/ui`; render beneath its `Root`. |
|
|
259
|
+
| Context errors with a linked local copy | Duplicate `svelte` / `@atom-forge/ui` | Vite `resolve.dedupe: ['svelte', '@atom-forge/ui', '@lucide/svelte', 'zod']`. |
|
|
260
|
+
| 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`, or image id not in `media` | Same plugins and media for editor and preview. |
|
|
262
|
+
| Box not offered inside a box | One nesting level | By design. |
|
|
263
|
+
| Block not offered inside a container | `accepts` or host `palette` excludes it | Add its palette id (or `'md:*'`). |
|
|
264
|
+
| Gated content visible to all | `gate` only marks | Cut the document on the server. |
|
|
265
|
+
| `document is not defined` | `toMarkdown` / `toStored` during SSR | Call in the browser. |
|
|
266
|
+
| Injected colour / link | Props rendered unchecked | `safeColor`, link checks, no `{@html}`. |
|
|
267
|
+
| Override loses its blocks | `type` changed | Keep `type`. |
|
|
268
|
+
| Bind error in a custom view | `props` / `blocks` not `$bindable()` | Declare both bindable. |
|
|
269
|
+
| Value is never `[]` after mount | An empty document is one empty paragraph | Test emptiness with `blocksToPlainText(doc).trim() === ''`. |
|
|
270
|
+
| Preview does not follow the editor | Actions not on the scrolling boxes | Put them on the `overflow` boxes. |
|
|
271
|
+
| `image`, `callout`, `locked`… not exported from `/plugins` | Only `table`, `youtube`, `map`, `gap` are bundled | Keep app-owned copies with the same `type` (see `docs/en/upgrading.md`). |
|
|
272
|
+
| `safeColor` import fails from `/plugins` | Kit helpers live in the core | Import from `@atom-forge/brickee`. |
|
|
273
|
+
|
|
274
|
+
## Documentation map
|
|
275
|
+
|
|
276
|
+
In the installed package (`node_modules/@atom-forge/brickee/`):
|
|
277
|
+
|
|
278
|
+
- [`docs/en/index.md`](docs/en/index.md) — full English documentation, one page per chapter: [getting started](docs/en/getting-started.md), [editor and preview](docs/en/editor-and-preview.md), [documents and markdown](docs/en/documents-and-markdown.md), [bundled plugins](docs/en/bundled-plugins.md), [writing plugins](docs/en/writing-plugins.md), [host contexts](docs/en/host-contexts.md), [on the server](docs/en/server-side.md), [recipes and pitfalls](docs/en/recipes-and-pitfalls.md), [upgrading](docs/en/upgrading.md). Authoritative.
|
|
279
|
+
- [`docs/hu/index.md`](docs/hu/index.md) — Hungarian translation.
|
|
280
|
+
- [`CHANGELOG.md`](CHANGELOG.md) — release history.
|
|
281
|
+
- `dist/index.d.ts`, `dist/plugins/index.d.ts` — the exported types of the installed version; `dist/plugins/` — the bundled plugins' source, useful as plugin examples.
|
|
282
|
+
|
|
283
|
+
The book at https://books.atom-forge.eu is the source of these docs and adds the AI Notes chapter: instructions to copy into your project's agent instruction file. To develop the package itself, follow `AGENTS.md` in the repository.
|
package/README.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Brickee
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@atom-forge/brickee` is a block editor for Svelte 5. Its core is a markdown block editor — paragraphs, headings, lists, todos, quotes, code, rules — and everything else is a plugin: a block with an editor view, a site preview, its fields, and optionally a markdown form. Documents are stored as JSON.
|
|
4
|
+
|
|
5
|
+
- **Core** — the markdown blocks and the kit plugins are written with. Without plugins it is a plain markdown block editor.
|
|
6
|
+
- **Bundled plugins** (`@atom-forge/brickee/plugins`) — a few general blocks: YouTube, map, table, gap. Plugins like any other: pick the ones you need.
|
|
7
|
+
- **Your plugins** — the same `BlockPlugin` shape, in your app.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun add @atom-forge/brickee @atom-forge/ui @lucide/svelte
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`svelte` 5, `@atom-forge/ui` (popups and form controls; your app needs its theme CSS) and `@lucide/svelte` are peer dependencies; `zod` is an optional one for the server-side schema.
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
```svelte
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
import { Editor, Preview, type StoredBlock } from '@atom-forge/brickee';
|
|
22
|
+
import { PLUGINS } from '@atom-forge/brickee/plugins';
|
|
23
|
+
import { hu } from '@atom-forge/brickee/locales/hu';
|
|
24
|
+
|
|
25
|
+
let value = $state<StoredBlock[]>([]);
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<Editor bind:value plugins={PLUGINS} messages={hu} />
|
|
29
|
+
<Preview blocks={value} plugins={PLUGINS} />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Documentation
|
|
33
|
+
|
|
34
|
+
- [The Brickee book](https://books.atom-forge.eu) — concepts, plugins, recipes, and pitfalls.
|
|
35
|
+
- [English docs](docs/en/index.md) · [Magyar dokumentáció](docs/hu/index.md)
|
|
36
|
+
- [README-AI.md](README-AI.md) — compact reference for coding agents.
|
|
37
|
+
|
|
38
|
+
Developing Brickee itself starts in [AGENTS.md](AGENTS.md).
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|