@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README-AI.md +286 -0
  4. package/README.md +41 -2
  5. package/dist/Editor.svelte +1956 -0
  6. package/dist/Editor.svelte.d.ts +19 -0
  7. package/dist/LinkPrompt.svelte +42 -0
  8. package/dist/LinkPrompt.svelte.d.ts +8 -0
  9. package/dist/PluginHost.svelte +41 -0
  10. package/dist/PluginHost.svelte.d.ts +13 -0
  11. package/dist/Preview.svelte +182 -0
  12. package/dist/Preview.svelte.d.ts +32 -0
  13. package/dist/PropsBar.svelte +405 -0
  14. package/dist/PropsBar.svelte.d.ts +17 -0
  15. package/dist/code.d.ts +12 -0
  16. package/dist/code.js +15 -0
  17. package/dist/dom.d.ts +29 -0
  18. package/dist/dom.js +224 -0
  19. package/dist/i18n.d.ts +7 -0
  20. package/dist/i18n.js +18 -0
  21. package/dist/index.d.ts +13 -0
  22. package/dist/index.js +16 -0
  23. package/dist/kit/Empty.svelte +24 -0
  24. package/dist/kit/Empty.svelte.d.ts +8 -0
  25. package/dist/kit/Frame.svelte +76 -0
  26. package/dist/kit/Frame.svelte.d.ts +15 -0
  27. package/dist/kit/util.d.ts +13 -0
  28. package/dist/kit/util.js +28 -0
  29. package/dist/locales/hu.d.ts +2 -0
  30. package/dist/locales/hu.js +142 -0
  31. package/dist/markdown.d.ts +23 -0
  32. package/dist/markdown.js +219 -0
  33. package/dist/model.d.ts +21 -0
  34. package/dist/model.js +45 -0
  35. package/dist/palette.d.ts +19 -0
  36. package/dist/palette.js +23 -0
  37. package/dist/plugins/Gap.svelte +28 -0
  38. package/dist/plugins/Gap.svelte.d.ts +15 -0
  39. package/dist/plugins/GapPreview.svelte +17 -0
  40. package/dist/plugins/GapPreview.svelte.d.ts +5 -0
  41. package/dist/plugins/Map.svelte +47 -0
  42. package/dist/plugins/Map.svelte.d.ts +12 -0
  43. package/dist/plugins/MapPreview.svelte +26 -0
  44. package/dist/plugins/MapPreview.svelte.d.ts +5 -0
  45. package/dist/plugins/Table.svelte +537 -0
  46. package/dist/plugins/Table.svelte.d.ts +28 -0
  47. package/dist/plugins/TablePreview.svelte +62 -0
  48. package/dist/plugins/TablePreview.svelte.d.ts +5 -0
  49. package/dist/plugins/YouTube.svelte +52 -0
  50. package/dist/plugins/YouTube.svelte.d.ts +8 -0
  51. package/dist/plugins/YouTubePreview.svelte +76 -0
  52. package/dist/plugins/YouTubePreview.svelte.d.ts +5 -0
  53. package/dist/plugins/index.d.ts +20 -0
  54. package/dist/plugins/index.js +105 -0
  55. package/dist/plugins/util.d.ts +3 -0
  56. package/dist/plugins/util.js +8 -0
  57. package/dist/schema.d.ts +11 -0
  58. package/dist/schema.js +25 -0
  59. package/dist/scroll.d.ts +18 -0
  60. package/dist/scroll.js +57 -0
  61. package/dist/types.d.ts +132 -0
  62. package/dist/types.js +43 -0
  63. package/dist/view.d.ts +6 -0
  64. package/dist/view.js +11 -0
  65. package/docs/en/bundled-plugins.md +99 -0
  66. package/docs/en/documents-and-markdown.md +114 -0
  67. package/docs/en/editor-and-preview.md +139 -0
  68. package/docs/en/getting-started.md +85 -0
  69. package/docs/en/host-contexts.md +169 -0
  70. package/docs/en/index.md +38 -0
  71. package/docs/en/recipes-and-pitfalls.md +123 -0
  72. package/docs/en/server-side.md +95 -0
  73. package/docs/en/upgrading.md +71 -0
  74. package/docs/en/writing-plugins.md +405 -0
  75. package/docs/hu/bundled-plugins.md +99 -0
  76. package/docs/hu/documents-and-markdown.md +114 -0
  77. package/docs/hu/editor-and-preview.md +139 -0
  78. package/docs/hu/getting-started.md +85 -0
  79. package/docs/hu/host-contexts.md +169 -0
  80. package/docs/hu/index.md +38 -0
  81. package/docs/hu/recipes-and-pitfalls.md +123 -0
  82. package/docs/hu/server-side.md +95 -0
  83. package/docs/hu/upgrading.md +71 -0
  84. package/docs/hu/writing-plugins.md +405 -0
  85. package/package.json +114 -5
@@ -0,0 +1,114 @@
1
+ # Documents and markdown
2
+
3
+ [← Previous](editor-and-preview.md) · [Contents](index.md) · [Next →](bundled-plugins.md)
4
+
5
+ ## The stored form
6
+
7
+ A document is an array of `StoredBlock` objects — plain JSON you can save in a database column as it is:
8
+
9
+ ```ts
10
+ interface StoredBlock {
11
+ type: string; // 'md:p', 'md:h2'… for built-in blocks; the plugin's type for plugin blocks
12
+ indent: number; // 0–6
13
+ text?: string; // built-in blocks: inline markdown (raw text for md:code)
14
+ props?: Record<string, unknown>; // plugin settings; md:todo { checked }, md:code { lang }
15
+ blocks?: StoredBlock[]; // plugins with nested blocks: their inner document
16
+ }
17
+ ```
18
+
19
+ A short document with a paragraph, a list, a video and a box (the `note` container from [Writing plugins](writing-plugins.md#containers)):
20
+
21
+ ```json
22
+ [
23
+ { "type": "md:h2", "indent": 0, "text": "Getting there" },
24
+ { "type": "md:p", "indent": 0, "text": "Take the **night train** or see the [timetable](https://example.com/trains)." },
25
+ { "type": "md:ul", "indent": 0, "text": "Tickets online" },
26
+ { "type": "md:ul", "indent": 1, "text": "Students get *20%* off" },
27
+ { "type": "youtube", "indent": 0, "props": { "id": "aqz-KE-bpKQ", "title": "The route by night" } },
28
+ {
29
+ "type": "note",
30
+ "indent": 0,
31
+ "props": { "title": "Good to know" },
32
+ "blocks": [{ "type": "md:p", "indent": 0, "text": "Trains are cancelled on public holidays." }]
33
+ }
34
+ ]
35
+ ```
36
+
37
+ - Built-in types carry the `md:` prefix (`MD` constant, `isMd(block)` test); everything else is a plugin type.
38
+ - Plugin blocks keep only `type`, `indent`, `props` and, for containers, `blocks`. What the props mean is up to the plugin.
39
+ - Nested blocks go one level deep: a block inside a box cannot hold blocks itself (`MAX_DEPTH` is 2).
40
+ - The editor's own working form (`Block`, with an `id` and inline HTML) is never stored; `toStored` and `fromStored` convert between the two.
41
+
42
+ ## Built-in blocks
43
+
44
+ | Type | Stored text | Props | Markdown |
45
+ |---|---|---|---|
46
+ | `md:p` | inline markdown | — | the text |
47
+ | `md:h1` … `md:h6` | inline markdown | — | `#` … `######` |
48
+ | `md:ul` | inline markdown | — | `- item` |
49
+ | `md:ol` | inline markdown | — | `1. item` (numbered per run and indent level) |
50
+ | `md:todo` | inline markdown | `{ checked: boolean }` | `- [x] item` |
51
+ | `md:quote` | inline markdown | — | `> quote` |
52
+ | `md:code` | raw text | `{ lang?: string }` | fenced block with the language |
53
+ | `md:hr` | — | — | `---` |
54
+
55
+ `indent` nests list items (consecutive list blocks become properly nested `ul`/`ol` elements in the preview) and indents paragraphs and quotes. Headings, code and dividers ignore it.
56
+
57
+ ### Inline markdown
58
+
59
+ The text of a built-in block is a small, safe subset of markdown:
60
+
61
+ | Mark | Stored as |
62
+ |---|---|
63
+ | bold | `**text**` |
64
+ | italic | `*text*` |
65
+ | underline, subscript, superscript | `<u>text</u>`, `<sub>text</sub>`, `<sup>text</sup>` |
66
+ | strikethrough | `~~text~~` |
67
+ | code | `` `text` `` |
68
+ | link | `[text](url)` — only `http:`, `https:`, `mailto:`, `tel:` and site-relative `/…` targets become links |
69
+ | line break | a newline |
70
+
71
+ Special characters in plain text are backslash-escaped, so they never turn into markup. `inline(md)` turns inline markdown into HTML: it escapes the text first and only then adds the tags above, so its output is safe to render with `{@html}` — this is what the preview does.
72
+
73
+ ## Markdown in and out
74
+
75
+ Markdown is a first-class format: the clipboard carries it, and a host can import and export it.
76
+
77
+ | Function | From → to | Runs |
78
+ |---|---|---|
79
+ | `fromStored(block)` | `StoredBlock` → `Block` | anywhere |
80
+ | `toStored(block)` | `Block` → `StoredBlock` | browser only |
81
+ | `fromMarkdown(text)` | markdown → `Block[]` | anywhere |
82
+ | `toMarkdown(blocks, { plugins })` | `Block[]` → markdown | browser only |
83
+ | `inline(md)` | inline markdown → inline HTML | anywhere |
84
+
85
+ The markdown functions work on the editor form (`Block[]`), so stored documents go through `fromStored` and `toStored`:
86
+
87
+ ```ts
88
+ import { fromMarkdown, fromStored, toMarkdown, toStored, type StoredBlock } from '@atom-forge/brickee';
89
+ import { PLUGINS } from '@atom-forge/brickee/plugins';
90
+
91
+ // Stored document -> markdown (browser only).
92
+ export const exportMarkdown = (doc: StoredBlock[]) => toMarkdown(doc.map(fromStored), { plugins: PLUGINS });
93
+
94
+ // Markdown -> stored document (browser only).
95
+ export const importMarkdown = (text: string): StoredBlock[] => fromMarkdown(text).map(toStored);
96
+ ```
97
+
98
+ > **Browser only**
99
+ >
100
+ > `toMarkdown` and `toStored` read the editor's inline HTML through the DOM, so they need `document`. Call them in the browser (an event handler, an effect), not during server-side rendering. For server-side text, use `blocksToPlainText` (see [On the server](server-side.md)).
101
+
102
+ What `fromMarkdown` reads:
103
+
104
+ - headings, bulleted, numbered and to-do items, quotes, fenced code blocks with their language, dividers, and the inline marks above;
105
+ - leading spaces as indentation (two spaces or one tab per level);
106
+ - every non-empty line as its own block — it does not join wrapped lines into one paragraph, and it creates built-in blocks only (no tables, images or plugin blocks).
107
+
108
+ What `toMarkdown` writes:
109
+
110
+ - consecutive list items on consecutive lines, every other block separated by a blank line;
111
+ - list items and paragraphs indented by two spaces per level;
112
+ - a plugin block through its plugin's `markdown(props, { inner })`, where `inner` is its nested blocks already converted; a plugin without a markdown form, or one returning `''`, is left out.
113
+
114
+ Writing a markdown form for your own plugin is covered in [Writing plugins](writing-plugins.md#markdown-form).
@@ -0,0 +1,139 @@
1
+ # The editor and the preview
2
+
3
+ [← Previous](getting-started.md) · [Contents](index.md) · [Next →](documents-and-markdown.md)
4
+
5
+ ## Editor props
6
+
7
+ | Prop | Type | Default | Meaning |
8
+ |---|---|---|---|
9
+ | `value` | `StoredBlock[]` (bindable) | `[]` | The document. Written back on every change; a document replaced from outside is loaded again. |
10
+ | `plugins` | `BlockPlugin[]` | `[]` | The plugin blocks this editor can show and offer. |
11
+ | `palette` | `string[]` | every built-in block, then every plugin | The block types the slash menu offers, in this order (see below). |
12
+ | `messages` | `Messages` | — | UI text in the host's language: English text → translation. |
13
+ | `readonly` | `boolean` | `false` | Shows the document without editing; plugin fields are hidden. |
14
+ | `placeholder` | `string` | `Write something, or press '/' for blocks…` | The hint of an empty block. |
15
+ | `nested` | `boolean` | `false` | Set by plugins with nested blocks for their inner editor; a host never sets it. |
16
+
17
+ An empty document is shown as one empty paragraph, so the value the editor writes back is never an empty array once it has been mounted: it is at least `[{ type: 'md:p', indent: 0, text: '' }]`. The preview skips empty paragraphs.
18
+
19
+ ## Preview props
20
+
21
+ | Prop | Type | Default | Meaning |
22
+ |---|---|---|---|
23
+ | `blocks` | `StoredBlock[]` | — | The document to render. |
24
+ | `plugins` | `BlockPlugin[]` | — | The plugins whose previews render the plugin blocks. |
25
+ | `messages` | `Messages` | — | UI text in the host's language. |
26
+ | `markGated` | `boolean` | `true` | Marks the blocks after a gate block (see [Unique and gate blocks](writing-plugins.md#unique-and-gate-blocks)). |
27
+ | `nested` | `boolean` | `false` | Set by plugin previews that render their nested blocks. |
28
+
29
+ The preview renders built-in blocks as plain HTML (`p`, `h1`…`h6`, `ul`/`ol` with nested items, `blockquote`, `pre > code` with a `language-<lang>` class, `hr`) and each plugin block with its plugin's `preview`. A block whose type has no plugin in `plugins`, or whose plugin has no `preview`, is not rendered.
30
+
31
+ Using `Preview` at all is your choice: the editor does not depend on it. An application with its own renderer can render the stored blocks itself, and its plugins can leave `preview` out.
32
+
33
+ `messages` given to the top-level editor or preview are inherited by every nested editor, nested preview and plugin component below it.
34
+
35
+ ## The palette
36
+
37
+ The `palette` is a list of **palette ids**: `md:p`, `md:h1`…`md:h6`, `md:ul`, `md:ol`, `md:todo`, `md:quote`, `md:code`, `md:hr` for the built-in blocks, and a plugin's `type` for a plugin. It decides:
38
+
39
+ - which blocks the slash menu and the Turn-into menu offer, and in which order;
40
+ - which markdown shortcuts work (typing `## ` turns a paragraph into a heading only if `md:h2` is in the palette);
41
+ - what pasted markdown may become: a pasted block of a type outside the palette becomes a paragraph, and a divider is dropped.
42
+
43
+ A plain paragraph (`md:p`) is always allowed as the fallback.
44
+
45
+ ```svelte
46
+ <script lang="ts">
47
+ import { Editor, type StoredBlock } from '@atom-forge/brickee';
48
+ import { map, table, youtube } from '@atom-forge/brickee/plugins';
49
+
50
+ const plugins = [table, youtube, map];
51
+ // No h1 (the page title is one) and no code blocks.
52
+ const palette = ['md:p', 'md:h2', 'md:h3', 'md:ul', 'md:ol', 'md:quote', 'md:hr', ...plugins.map((p) => p.type)];
53
+
54
+ let value = $state<StoredBlock[]>([]);
55
+ </script>
56
+
57
+ <Editor bind:value {plugins} {palette} />
58
+ ```
59
+
60
+ `PALETTE_ITEMS` lists the built-in blocks with their labels, hints and icons; `BUILT_IN_IDS` is just their ids in the default order.
61
+
62
+ > **The palette limits offering, not loading**
63
+ >
64
+ > A document that already holds a block outside the palette still shows it. A plugin block whose plugin is missing from `plugins` is kept as it is and shown as an unknown block that can only be deleted. To reject such documents, validate them on the server (see [On the server](server-side.md)).
65
+
66
+ ## Editing
67
+
68
+ | Action | How |
69
+ |---|---|
70
+ | Insert a block | `/` opens the slash menu (searchable by label, hint, keywords and their translations); the `+` button adds a block below. |
71
+ | Markdown shortcuts | At the start of a paragraph: `# `…`###### `, `- ` / `* ` / `+ `, `1. `, `[] `, `> `. |
72
+ | Inline formatting | Select text for the toolbar, or `Ctrl/Cmd` + `B` bold, `I` italic, `U` underline, `Shift+X` strikethrough, `E` code, `K` link, `\` remove formatting. Subscript and superscript are on the toolbar. |
73
+ | Indent | `Tab` / `Shift+Tab` (at most 6 levels). |
74
+ | Line break | `Shift+Enter`; in a code block `Enter` breaks the line and `Shift+Enter` or `Ctrl/Cmd+Enter` starts a new block. |
75
+ | Select blocks | Click a block's handle, `Esc` in a block, `Shift+↑/↓` past the edge of a block, or drag a text selection across blocks. `Ctrl/Cmd+A` in an empty block selects every block. |
76
+ | Move blocks | Drag the handle (also into and out of nested editors), or `Ctrl/Cmd+Shift+↑/↓`. |
77
+ | Turn into | Click the handle of a block or a selection for the Turn-into menu. |
78
+ | Copy, cut | Copying selected blocks puts their markdown on the clipboard (plugin blocks through their markdown form). |
79
+ | Paste | Multi-line text is read as markdown and becomes blocks. |
80
+ | Undo, redo | `Ctrl/Cmd+Z`, `Ctrl/Cmd+Shift+Z` or `Ctrl/Cmd+Y`. |
81
+
82
+ What a block can turn into depends on what it holds (its `content`):
83
+
84
+ - text blocks turn into each other and keep their formatting; turning text into code keeps only the letters;
85
+ - a block without content (divider, video, map) only replaces empty blocks or other blocks without content;
86
+ - a block with nested blocks (a box) can wrap the selected blocks, if it accepts all of them; **Unwrap** moves a box's blocks out; a box can turn into another box-like type, and the props with the same names carry over.
87
+
88
+ ## Scroll sync
89
+
90
+ A host that shows the editor and the preview side by side can make the preview follow the editor's scrolling. `scrollSync()` returns two Svelte actions and a function:
91
+
92
+ ```svelte
93
+ <script lang="ts">
94
+ import { Editor, Preview, scrollSync, type StoredBlock } from '@atom-forge/brickee';
95
+ import { PLUGINS } from '@atom-forge/brickee/plugins';
96
+
97
+ let value = $state<StoredBlock[]>([]);
98
+ const sync = scrollSync();
99
+ </script>
100
+
101
+ <div class="split">
102
+ <div class="pane" use:sync.source>
103
+ <Editor bind:value plugins={PLUGINS} />
104
+ </div>
105
+ <div class="pane" use:sync.target>
106
+ <Preview blocks={value} plugins={PLUGINS} />
107
+ </div>
108
+ </div>
109
+
110
+ <style>
111
+ .split {
112
+ display: grid;
113
+ grid-template-columns: 1fr 1fr;
114
+ height: 100vh;
115
+ }
116
+ .pane {
117
+ overflow-y: auto;
118
+ }
119
+ </style>
120
+ ```
121
+
122
+ - `use:sync.source` goes on the editor's scrolling box, `use:sync.target` on the preview's. The target is scrolled to the same fraction (0…1) of its scroll range as the source. The sync is one way: scrolling the preview moves nothing else.
123
+ - It re-syncs once per animation frame when the source scrolls, and when the target box or its children change size. `sync.align()` re-syncs on demand, for example after the preview's width changed.
124
+ - `use:sync.target={on}` takes a boolean (default `true`), for a host that switches the target between two boxes, such as a desktop pane and a phone frame: turn on the visible one.
125
+ - The sync is proportional only: both boxes show roughly the same place, not exactly the same block. This is deliberate — it needs no markup and works with any preview.
126
+
127
+ `scrollFraction(box)` reads how far a box is scrolled (0…1; 0 when it does not scroll), and `setScrollFraction(box, f)` scrolls it there — for example to keep the place when the preview changes width while it is shown alone:
128
+
129
+ ```ts
130
+ import { scrollFraction, setScrollFraction } from '@atom-forge/brickee';
131
+ import { tick } from 'svelte';
132
+
133
+ export async function keepPlace(box: HTMLElement, change: () => void) {
134
+ const f = scrollFraction(box);
135
+ change(); // e.g. switch the preview width
136
+ await tick();
137
+ setScrollFraction(box, f);
138
+ }
139
+ ```
@@ -0,0 +1,85 @@
1
+ # Getting started
2
+
3
+ [Contents](index.md) · [Next →](editor-and-preview.md)
4
+
5
+ ## What Brickee is
6
+
7
+ Brickee is a block editor: a document is a list of blocks, each edited on its own row, reordered by dragging, and turned into another type from its handle. The core blocks are markdown — paragraphs, six heading levels, bulleted, numbered and to-do lists, quotes, code and dividers — so a document without plugins reads and copies as plain markdown.
8
+
9
+ Everything else is a plugin. A plugin is a plain object (`BlockPlugin`) that names a block type and gives it:
10
+
11
+ - a **view** — a Svelte component shown in the editor;
12
+ - a **preview** — a Svelte component that renders the block on the site;
13
+ - **fields** — the settings shown on the top of the block (text, colour, select, slider…);
14
+ - optionally a **markdown form**, used when the document is copied or exported as markdown.
15
+
16
+ The package ships four general-purpose plugins — table, YouTube video, map and gap — but they are plugins like any other: nothing in the core depends on them, and you pass only the ones you want. Image blocks, boxes and other site-specific blocks are plugins you write yourself.
17
+
18
+ ## Installation
19
+
20
+ ```sh
21
+ bun add @atom-forge/brickee @atom-forge/ui @lucide/svelte
22
+ ```
23
+
24
+ | Peer dependency | Why |
25
+ |---|---|
26
+ | `svelte` `^5.0.0` | The editor, the preview and the plugins are Svelte 5 components. |
27
+ | `@atom-forge/ui` `>=0.0.37` | The popups (link prompt) and the form controls of the block fields. |
28
+ | `@lucide/svelte` `>=1.0.0` | The block icons. |
29
+ | `zod` `^4.0.0` (optional) | Only for the server-side schema in `@atom-forge/brickee/schema`. |
30
+
31
+ ## Host setup
32
+
33
+ The block fields and popups are `@atom-forge/ui` components, so the application must be set up the way `@atom-forge/ui` requires:
34
+
35
+ - Tailwind CSS 4 with the `@atom-forge/ui` theme CSS imported and its `dist` folder added as a Tailwind `@source`;
36
+ - the application rendered beneath the `@atom-forge/ui` `Root` component, which provides the theme and the overlay contexts.
37
+
38
+ The top-level `Editor` creates its own popup manager and renders its own popup container; editors nested in plugin blocks share it.
39
+
40
+ The editor takes its colours from the `@atom-forge/ui` theme tokens (`--color-surface`, `--color-frame`, `--color-muted-contrast`…), so it follows the host's light and dark mode. The preview's borders and captions are drawn from the text colour of the page.
41
+
42
+ The block handles are drawn in a gutter **left of the editor's box**, about 22 px wide: give the element around the editor that much padding on the left, or the handles stick out of it.
43
+
44
+ > **Missing theme**
45
+ >
46
+ > Without the `@atom-forge/ui` theme CSS the editor still works, but the block fields, the colour picker, the slider and the popups appear unstyled.
47
+
48
+ The preview adds only the little styling it needs itself (to-do checkboxes, image widths, floats). The typography of headings, paragraphs and lists comes from the page that shows the preview.
49
+
50
+ ## A first editor
51
+
52
+ ```svelte
53
+ <script lang="ts">
54
+ import { Editor, Preview, type StoredBlock } from '@atom-forge/brickee';
55
+ import { PLUGINS } from '@atom-forge/brickee/plugins';
56
+
57
+ let value = $state<StoredBlock[]>([]);
58
+ </script>
59
+
60
+ <Editor bind:value plugins={PLUGINS} />
61
+
62
+ <article>
63
+ <Preview blocks={value} plugins={PLUGINS} />
64
+ </article>
65
+ ```
66
+
67
+ - `value` is the document as JSON (`StoredBlock[]`). The editor writes it back on every change; save it as it is.
68
+ - `plugins` lists the plugin blocks the editor can show and offer. Leave it out and you have a plain markdown editor.
69
+ - `Preview` renders the same document with the same plugins, the way the site shows it.
70
+
71
+ Type `/` in an empty line to open the block menu.
72
+
73
+ ## Entry points
74
+
75
+ | Import | Contents |
76
+ |---|---|
77
+ | `@atom-forge/brickee` | `Editor`, `Preview`, the kit (`Frame`, `Empty`, helpers), the stored form, the markdown conversions, the contexts, the palette, the types. |
78
+ | `@atom-forge/brickee/plugins` | The bundled plugins (`table`, `youtube`, `map`, `gap`), `PLUGINS` with all four, and their helpers. |
79
+ | `@atom-forge/brickee/schema` | `blocksSchema` and `blocksSchemaFor` (zod), for server-side validation. |
80
+ | `@atom-forge/brickee/model` | The stored form without Svelte: `StoredBlock`, `MD`, `isMd`, `acceptsId`, `blocksToPlainText`… |
81
+ | `@atom-forge/brickee/locales/hu` | `hu`: the Hungarian UI text. |
82
+
83
+ > **Only what you import is bundled**
84
+ >
85
+ > The bundled plugins live in their own entry point. An application that imports only `@atom-forge/brickee` gets the markdown editor without any plugin code.
@@ -0,0 +1,169 @@
1
+ # Host contexts
2
+
3
+ [← Previous](writing-plugins.md) · [Contents](index.md) · [Next →](server-side.md)
4
+
5
+ The editor has no props for application concepts. What a host knows — the page width it is showing, the code languages it highlights, its language — reaches the plugins through three Svelte contexts. Each has a `provide…` function, called in a component's initialization above the editor or preview, and a `use…` function for plugin components.
6
+
7
+ | Context | Provide | Read | Carries |
8
+ |---|---|---|---|
9
+ | View | `provideView(() => key)` | `useView()` | The view per-view fields edit. |
10
+ | Code languages | `provideCodeLanguages(() => list)` | `useCodeLanguages()` | The languages a code block offers. |
11
+ | Text | `messages` prop, or `provideText(() => messages)` | `useText()` | UI text in the host's language. |
12
+
13
+ Nested editors and previews inherit all three from their parent.
14
+
15
+ Images are not a context: what an image block stores and where its pictures come from is decided by the app's own plugin (see [Images](writing-plugins.md#images)).
16
+
17
+ ## Per-view values
18
+
19
+ Some settings depend on how the page is shown: an image may need a different aspect ratio on a phone than on a wide screen. A `slider` field with `views` stores a value per view. Here it is the height of the `photo` plugin from [Writing plugins](writing-plugins.md#images) and the bundled `gap`:
20
+
21
+ ```ts
22
+ import type { BlockPlugin } from '@atom-forge/brickee';
23
+ import { gap } from '@atom-forge/brickee/plugins';
24
+ import { photo } from './photo';
25
+
26
+ export const VIEWS = [
27
+ { key: 'desktop', label: 'Desktop' },
28
+ { key: 'tablet', label: 'Tablet' },
29
+ { key: 'phone', label: 'Phone' }
30
+ ];
31
+
32
+ /** The same plugin, with one of its fields set per view. */
33
+ const perView = (plugin: BlockPlugin, key: string): BlockPlugin => ({
34
+ ...plugin,
35
+ fields: plugin.fields?.map((f) => (f.key === key ? { ...f, views: VIEWS } : f))
36
+ });
37
+
38
+ export const plugins = [perView(photo, 'ratio'), perView(gap, 'lines')];
39
+ ```
40
+
41
+ Which views exist and which one is shown is the host's business. The host tells the editor the current view with `provideView`, typically from a width switch beside the editor:
42
+
43
+ ```svelte
44
+ <script lang="ts">
45
+ import { Editor, provideView, type StoredBlock } from '@atom-forge/brickee';
46
+ import { plugins } from './blocks';
47
+
48
+ let { value = $bindable(), view }: { value: StoredBlock[]; view: string } = $props();
49
+ provideView(() => view);
50
+ </script>
51
+
52
+ <Editor bind:value {plugins} />
53
+ ```
54
+
55
+ How the values behave:
56
+
57
+ - The first view is the **base**. Each other view, while it has no value of its own, takes the value of the view before it; the field then shows the value marked as inherited.
58
+ - The field edits the value of the current view and shows that view's label beside its own. Without `provideView`, or with a key that is not in `views`, it edits the base value.
59
+ - Stored: only a base value → stored alone, as a plain value (`"ratio": "56"`); own values for other views → an object keyed by view (`"ratio": { "desktop": "56", "phone": "100" }`). Documents written before a field became per-view stay valid.
60
+
61
+ A preview reads them with `viewValues(value, keys)` — the value for each view, inheritance applied — or just the base with `baseValue(value)`. This version of the photo preview gives the browser one aspect ratio per width through CSS variables:
62
+
63
+ ```svelte
64
+ <script lang="ts">
65
+ import { viewValues, type PluginPreviewProps } from '@atom-forge/brickee';
66
+ import { aspect, imageSrc, type PhotoProps } from './PhotoView.svelte';
67
+
68
+ let { props }: PluginPreviewProps<PhotoProps> = $props();
69
+ const src = $derived(imageSrc(props.url));
70
+ // desktop, tablet, phone: a CSS aspect-ratio each ('auto': the image's own shape)
71
+ const r = $derived(viewValues(props.ratio, ['desktop', 'tablet', 'phone']).map((v) => aspect(v) ?? 'auto'));
72
+ </script>
73
+
74
+ {#if src}
75
+ <img {src} alt={props.alt ?? ''} style:--r-desktop={r[0]} style:--r-tablet={r[1]} style:--r-phone={r[2]} />
76
+ {/if}
77
+
78
+ <style>
79
+ img {
80
+ width: 100%;
81
+ object-fit: cover;
82
+ aspect-ratio: var(--r-phone);
83
+ }
84
+ @media (min-width: 640px) {
85
+ img {
86
+ aspect-ratio: var(--r-tablet);
87
+ }
88
+ }
89
+ @media (min-width: 1024px) {
90
+ img {
91
+ aspect-ratio: var(--r-desktop);
92
+ }
93
+ }
94
+ </style>
95
+ ```
96
+
97
+ > **Sliders only**
98
+ >
99
+ > `views` is supported on `slider` fields. Other field kinds ignore it.
100
+
101
+ ## Code languages
102
+
103
+ A code block's language (`props.lang`, the info string of its markdown fence) is free text by default. When the host knows which languages its site can highlight, it gives the list, and the field in the code block's fields bar becomes a dropdown of them, with *None* first:
104
+
105
+ ```svelte
106
+ <script lang="ts">
107
+ import { Editor, provideCodeLanguages, type StoredBlock } from '@atom-forge/brickee';
108
+
109
+ let { value = $bindable() }: { value: StoredBlock[] } = $props();
110
+ provideCodeLanguages(() => [{ value: 'ts', label: 'TypeScript' }, { value: 'svelte', label: 'Svelte' }, 'css', 'bash']);
111
+ </script>
112
+
113
+ <Editor bind:value />
114
+ ```
115
+
116
+ An item is a string or `{ value, label? }`; the value is what is stored. Nested editors inherit the list. A stored language missing from the list stays an option, so opening an older document loses nothing.
117
+
118
+ ## UI text
119
+
120
+ The editor and the plugins are written in English, and the English text is the key of its translation. Pass `messages` — an object from English text to your language — to the top-level editor and preview:
121
+
122
+ ```svelte
123
+ <script lang="ts">
124
+ import { Editor, type StoredBlock } from '@atom-forge/brickee';
125
+ import { PLUGINS } from '@atom-forge/brickee/plugins';
126
+ import { hu } from '@atom-forge/brickee/locales/hu';
127
+
128
+ let value = $state<StoredBlock[]>([]);
129
+ </script>
130
+
131
+ <Editor bind:value plugins={PLUGINS} messages={hu} />
132
+ ```
133
+
134
+ `@atom-forge/brickee/locales/hu` exports `hu`, the Hungarian text of the editor and of every bundled plugin. Extend it with the text of your own plugins and fields:
135
+
136
+ ```ts
137
+ import type { Messages } from '@atom-forge/brickee';
138
+ import { hu } from '@atom-forge/brickee/locales/hu';
139
+
140
+ export const messages: Messages = {
141
+ ...hu,
142
+ Button: 'Gomb',
143
+ 'A link as a button': 'Egy link gombként',
144
+ Desktop: 'Asztali',
145
+ Phone: 'Telefon'
146
+ };
147
+ ```
148
+
149
+ What the editor translates by itself:
150
+
151
+ - the built-in and plugin `label`, `hint` and `keys` (the slash menu searches the English and the translated words);
152
+ - the parts of a plugin's `tag`, split at ` · `;
153
+ - field labels, placeholders, option labels, `none` labels and view labels;
154
+ - the text of `Empty`, and the editor's own interface.
155
+
156
+ Text a plugin renders itself — in its view or preview — goes through `useText()`:
157
+
158
+ ```svelte
159
+ <script lang="ts">
160
+ import { useText, type PluginPreviewProps } from '@atom-forge/brickee';
161
+
162
+ let { props }: PluginPreviewProps = $props();
163
+ const tr = useText();
164
+ </script>
165
+
166
+ <p>{tr('Map: {place}', { place: String(props.q ?? '') })}</p>
167
+ ```
168
+
169
+ `{name}` placeholders are filled from the second argument. Outside components, `translator(messages)` returns the same kind of function. A text missing from `messages` stays English.
@@ -0,0 +1,38 @@
1
+ # Brickee
2
+
3
+ `@atom-forge/brickee` is a block editor for Svelte 5. Its core is a markdown block editor — paragraphs, headings, lists, to-dos, quotes, code, dividers — and every other block 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
+ These pages are derived from the [Brickee book](https://books.atom-forge.eu), which is the source of truth. A Hungarian translation is in [`../hu/index.md`](../hu/index.md); a compact reference for coding agents is in [`README-AI.md`](../../README-AI.md).
6
+
7
+ The package is built in three layers:
8
+
9
+ ```mermaid
10
+ flowchart TB
11
+ core["Core: markdown blocks, Editor, Preview, stored form, kit"]
12
+ bundled["Bundled plugins: table, YouTube, map, gap"]
13
+ own["Your plugins: the same BlockPlugin shape"]
14
+ core --> bundled
15
+ core --> own
16
+ ```
17
+
18
+ - **Core** (`@atom-forge/brickee`) — the markdown blocks, the `Editor` and the `Preview`, the stored form and its markdown conversions, and the **kit** plugins are written with. Without plugins it is a plain markdown block editor.
19
+ - **Bundled plugins** (`@atom-forge/brickee/plugins`) — plugins like any other, written only with what the core exports. You opt in to the ones you need.
20
+ - **Your plugins** — the same `BlockPlugin` shape, living in your application.
21
+
22
+ The editor itself knows nothing about your application. Whatever an app needs — its page widths, its code languages, its language — reaches the plugins through **contexts** (view, code languages, text) or through plugin **fields**, never through special editor props. Images are no exception: an image block is a plugin of your own, which stores, picks and resolves its images the way your application does.
23
+
24
+ ## Contents
25
+
26
+ 1. [Getting started](getting-started.md) — the three layers, installation, host setup, and a first editor with its preview.
27
+ 2. [The editor and the preview](editor-and-preview.md) — the `Editor` and `Preview` props, the palette, the editing features, and scroll sync.
28
+ 3. [Documents and markdown](documents-and-markdown.md) — the stored form (`StoredBlock`), the built-in blocks, and converting from and to markdown.
29
+ 4. [Bundled plugins](bundled-plugins.md) — what `@atom-forge/brickee/plugins` contains, and how to override a preview or add fields.
30
+ 5. [Writing plugins](writing-plugins.md) — the `BlockPlugin` anatomy, fields, views and previews, containers, markdown forms, image blocks, and the kit.
31
+ 6. [Host contexts](host-contexts.md) — per-view values, code languages, and UI text.
32
+ 7. [On the server](server-side.md) — schema validation, plain text, and gated content.
33
+ 8. [Recipes and pitfalls](recipes-and-pitfalls.md) — complete patterns and the mistakes to avoid.
34
+ 9. [Upgrading](upgrading.md) — what to change in your code when you move to a newer version.
35
+
36
+ The book also has an AI Notes chapter: instructions to copy into the agent instruction file of a project that uses the package.
37
+
38
+ Svelte examples are complete components unless they say otherwise. Application values such as the current page width stand for whatever your application provides.