@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.
Files changed (87) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README-AI.md +283 -0
  4. package/README.md +41 -2
  5. package/dist/Editor.svelte +1968 -0
  6. package/dist/Editor.svelte.d.ts +22 -0
  7. package/dist/ImagePicker.svelte +131 -0
  8. package/dist/ImagePicker.svelte.d.ts +13 -0
  9. package/dist/LinkPrompt.svelte +42 -0
  10. package/dist/LinkPrompt.svelte.d.ts +8 -0
  11. package/dist/PluginHost.svelte +41 -0
  12. package/dist/PluginHost.svelte.d.ts +13 -0
  13. package/dist/Preview.svelte +186 -0
  14. package/dist/Preview.svelte.d.ts +35 -0
  15. package/dist/PropsBar.svelte +395 -0
  16. package/dist/PropsBar.svelte.d.ts +20 -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 +147 -0
  31. package/dist/markdown.d.ts +25 -0
  32. package/dist/markdown.js +219 -0
  33. package/dist/media.d.ts +17 -0
  34. package/dist/media.js +12 -0
  35. package/dist/model.d.ts +21 -0
  36. package/dist/model.js +45 -0
  37. package/dist/palette.d.ts +19 -0
  38. package/dist/palette.js +23 -0
  39. package/dist/plugins/Gap.svelte +28 -0
  40. package/dist/plugins/Gap.svelte.d.ts +15 -0
  41. package/dist/plugins/GapPreview.svelte +17 -0
  42. package/dist/plugins/GapPreview.svelte.d.ts +5 -0
  43. package/dist/plugins/Map.svelte +47 -0
  44. package/dist/plugins/Map.svelte.d.ts +12 -0
  45. package/dist/plugins/MapPreview.svelte +26 -0
  46. package/dist/plugins/MapPreview.svelte.d.ts +5 -0
  47. package/dist/plugins/Table.svelte +563 -0
  48. package/dist/plugins/Table.svelte.d.ts +28 -0
  49. package/dist/plugins/TablePreview.svelte +62 -0
  50. package/dist/plugins/TablePreview.svelte.d.ts +5 -0
  51. package/dist/plugins/YouTube.svelte +52 -0
  52. package/dist/plugins/YouTube.svelte.d.ts +8 -0
  53. package/dist/plugins/YouTubePreview.svelte +76 -0
  54. package/dist/plugins/YouTubePreview.svelte.d.ts +5 -0
  55. package/dist/plugins/index.d.ts +20 -0
  56. package/dist/plugins/index.js +94 -0
  57. package/dist/plugins/util.d.ts +3 -0
  58. package/dist/plugins/util.js +8 -0
  59. package/dist/schema.d.ts +11 -0
  60. package/dist/schema.js +25 -0
  61. package/dist/scroll.d.ts +18 -0
  62. package/dist/scroll.js +57 -0
  63. package/dist/types.d.ts +133 -0
  64. package/dist/types.js +43 -0
  65. package/dist/view.d.ts +6 -0
  66. package/dist/view.js +11 -0
  67. package/docs/en/bundled-plugins.md +99 -0
  68. package/docs/en/documents-and-markdown.md +115 -0
  69. package/docs/en/editor-and-preview.md +141 -0
  70. package/docs/en/getting-started.md +81 -0
  71. package/docs/en/host-contexts.md +275 -0
  72. package/docs/en/index.md +38 -0
  73. package/docs/en/recipes-and-pitfalls.md +122 -0
  74. package/docs/en/server-side.md +95 -0
  75. package/docs/en/upgrading.md +55 -0
  76. package/docs/en/writing-plugins.md +305 -0
  77. package/docs/hu/bundled-plugins.md +99 -0
  78. package/docs/hu/documents-and-markdown.md +115 -0
  79. package/docs/hu/editor-and-preview.md +141 -0
  80. package/docs/hu/getting-started.md +81 -0
  81. package/docs/hu/host-contexts.md +275 -0
  82. package/docs/hu/index.md +38 -0
  83. package/docs/hu/recipes-and-pitfalls.md +122 -0
  84. package/docs/hu/server-side.md +95 -0
  85. package/docs/hu/upgrading.md +55 -0
  86. package/docs/hu/writing-plugins.md +305 -0
  87. package/package.json +110 -5
@@ -0,0 +1,99 @@
1
+ # Bundled plugins
2
+
3
+ [← Previous](documents-and-markdown.md) · [Contents](index.md) · [Next →](writing-plugins.md)
4
+
5
+ `@atom-forge/brickee/plugins` holds four general-purpose plugins. They are written only with what the core exports to any plugin author, so they double as examples, and you take only the ones you need.
6
+
7
+ ```ts
8
+ import { PLUGINS } from '@atom-forge/brickee/plugins'; // all four
9
+ import { table, youtube } from '@atom-forge/brickee/plugins'; // or some
10
+ ```
11
+
12
+ ## The plugins
13
+
14
+ | Export | `type` | Fields | Markdown form | What it is |
15
+ |---|---|---|---|---|
16
+ | `table` | `table` | — (edited in place) | a GFM table | Rows and columns of inline-formatted cells; rows and columns are dragged by their grips. The first row can be a header, the last a footer. |
17
+ | `youtube` | `youtube` | video link or id, title | a link | A YouTube video, loaded on click; any YouTube link is reduced to the video id. |
18
+ | `map` | `map` | place or address, zoom slider | a link | A Google Maps embed of a place or an address. |
19
+ | `gap` | `gap` | height slider | none | Empty space between blocks, 1–8 lines high. |
20
+
21
+ None of them holds nested blocks. Image blocks, boxes and other containers are application-specific in look and behaviour, so the package leaves them to you: [Writing plugins](writing-plugins.md) shows a container, and [Host contexts](host-contexts.md) an image block.
22
+
23
+ Besides the plugins, the entry point exports their props types (`TableProps`, `YouTubeProps`, `MapProps`, `GapProps`) and the helpers their previews use, which are useful when you write your own previews: `youtubeId` (a video id from any YouTube link), `mapSrc` (the embed address of a place), `gapLines`, `GAP_LINES` and `GAP_MAX`, and `tableHeads`, `rowHead`, `HEADS` and `square` for tables.
24
+
25
+ ## Overriding a preview
26
+
27
+ A plugin is a plain object, so adapting one is a spread. The most common change is the preview: the editor view stays the package's, and the site renders the block with your own component — so the editor's preview and the published page are exactly the same.
28
+
29
+ ```ts
30
+ // blocks.ts
31
+ import type { BlockPlugin } from '@atom-forge/brickee';
32
+ import { gap, table, youtube } from '@atom-forge/brickee/plugins';
33
+ import SiteYouTube from './SiteYouTube.svelte';
34
+
35
+ const withPreview = (plugin: BlockPlugin, preview: BlockPlugin['preview']): BlockPlugin => ({ ...plugin, preview });
36
+
37
+ export const plugins: BlockPlugin[] = [withPreview(youtube, SiteYouTube), table, gap];
38
+ ```
39
+
40
+ A replacement preview receives the same props as the original (`PluginPreviewProps`: `props`, `blocks`, `plugins`). This one shows a thumbnail that links to the video instead of embedding a player:
41
+
42
+ ```svelte
43
+ <!-- SiteYouTube.svelte -->
44
+ <script lang="ts">
45
+ import type { PluginPreviewProps } from '@atom-forge/brickee';
46
+ import { youtubeId, type YouTubeProps } from '@atom-forge/brickee/plugins';
47
+
48
+ let { props }: PluginPreviewProps<YouTubeProps> = $props();
49
+ // props are user input: keep only a well-formed video id
50
+ const id = $derived(youtubeId(String(props.id ?? '')));
51
+ </script>
52
+
53
+ {#if id.length === 11}
54
+ <a class="video" href="https://www.youtube.com/watch?v={id}" target="_blank" rel="noopener noreferrer">
55
+ <img src="https://img.youtube.com/vi/{id}/hqdefault.jpg" alt={props.title || 'YouTube video'} />
56
+ </a>
57
+ {/if}
58
+ ```
59
+
60
+ ## Adding fields
61
+
62
+ New settings are new fields and new initial props. The plugin's view does not need to change: the editor draws every field on the top of the block, and your preview reads the new props.
63
+
64
+ ```ts
65
+ import type { BlockPlugin } from '@atom-forge/brickee';
66
+ import { map } from '@atom-forge/brickee/plugins';
67
+ import SiteMap from './SiteMap.svelte';
68
+
69
+ export const siteMap: BlockPlugin = {
70
+ ...map,
71
+ props: () => ({ ...map.props(), caption: '' }),
72
+ fields: [...(map.fields ?? []), { key: 'caption', label: 'Caption', kind: 'text' }],
73
+ preview: SiteMap
74
+ };
75
+ ```
76
+
77
+ ```svelte
78
+ <!-- SiteMap.svelte -->
79
+ <script lang="ts">
80
+ import type { PluginPreviewProps } from '@atom-forge/brickee';
81
+ import { mapSrc, type MapProps } from '@atom-forge/brickee/plugins';
82
+
83
+ let { props }: PluginPreviewProps<MapProps> = $props();
84
+ const src = $derived(mapSrc(props));
85
+ </script>
86
+
87
+ {#if src}
88
+ <figure>
89
+ <iframe title={props.q} {src} loading="lazy"></iframe>
90
+ {#if props.caption}<figcaption>{props.caption}</figcaption>{/if}
91
+ </figure>
92
+ {/if}
93
+ ```
94
+
95
+ Add the new field labels to your `messages` if the editor is not in English (see [UI text](host-contexts.md#ui-text)). To make an existing slider per-view, map over `fields` and add `views` to it (see [Per-view values](host-contexts.md#per-view-values)).
96
+
97
+ > **Keep the type**
98
+ >
99
+ > Keep the plugin's `type` when you override it: stored documents refer to blocks by type. A new `type` makes a new block, and existing documents will not find their blocks.
@@ -0,0 +1,115 @@
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, media })` | `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 MediaItem, 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[], media: MediaItem[]) =>
93
+ toMarkdown(doc.map(fromStored), { plugins: PLUGINS, media });
94
+
95
+ // Markdown -> stored document (browser only).
96
+ export const importMarkdown = (text: string): StoredBlock[] => fromMarkdown(text).map(toStored);
97
+ ```
98
+
99
+ > **Browser only**
100
+ >
101
+ > `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)).
102
+
103
+ What `fromMarkdown` reads:
104
+
105
+ - headings, bulleted, numbered and to-do items, quotes, fenced code blocks with their language, dividers, and the inline marks above;
106
+ - leading spaces as indentation (two spaces or one tab per level);
107
+ - 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).
108
+
109
+ What `toMarkdown` writes:
110
+
111
+ - consecutive list items on consecutive lines, every other block separated by a blank line;
112
+ - list items and paragraphs indented by two spaces per level;
113
+ - a plugin block through its plugin's `markdown(props, { inner, media })`, where `inner` is its nested blocks already converted; a plugin without a markdown form, or one returning `''`, is left out.
114
+
115
+ Writing a markdown form for your own plugin is covered in [Writing plugins](writing-plugins.md#markdown-form).
@@ -0,0 +1,141 @@
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
+ | `media` | `MediaItem[]` | — | The images blocks can pick from (see [Host contexts](host-contexts.md)). |
13
+ | `messages` | `Messages` | — | UI text in the host's language: English text → translation. |
14
+ | `readonly` | `boolean` | `false` | Shows the document without editing; plugin fields are hidden. |
15
+ | `placeholder` | `string` | `Write something, or press '/' for blocks…` | The hint of an empty block. |
16
+ | `nested` | `boolean` | `false` | Set by plugins with nested blocks for their inner editor; a host never sets it. |
17
+
18
+ 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.
19
+
20
+ ## Preview props
21
+
22
+ | Prop | Type | Default | Meaning |
23
+ |---|---|---|---|
24
+ | `blocks` | `StoredBlock[]` | — | The document to render. |
25
+ | `plugins` | `BlockPlugin[]` | — | The plugins whose previews render the plugin blocks. |
26
+ | `media` | `MediaItem[]` | — | The images the blocks refer to by id. |
27
+ | `messages` | `Messages` | — | UI text in the host's language. |
28
+ | `markGated` | `boolean` | `true` | Marks the blocks after a gate block (see [Unique and gate blocks](writing-plugins.md#unique-and-gate-blocks)). |
29
+ | `nested` | `boolean` | `false` | Set by plugin previews that render their nested blocks. |
30
+
31
+ 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.
32
+
33
+ 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.
34
+
35
+ `media` and `messages` given to the top-level editor or preview are inherited by every nested editor, nested preview and plugin component below it.
36
+
37
+ ## The palette
38
+
39
+ 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:
40
+
41
+ - which blocks the slash menu and the Turn-into menu offer, and in which order;
42
+ - which markdown shortcuts work (typing `## ` turns a paragraph into a heading only if `md:h2` is in the palette);
43
+ - what pasted markdown may become: a pasted block of a type outside the palette becomes a paragraph, and a divider is dropped.
44
+
45
+ A plain paragraph (`md:p`) is always allowed as the fallback.
46
+
47
+ ```svelte
48
+ <script lang="ts">
49
+ import { Editor, type StoredBlock } from '@atom-forge/brickee';
50
+ import { map, table, youtube } from '@atom-forge/brickee/plugins';
51
+
52
+ const plugins = [table, youtube, map];
53
+ // No h1 (the page title is one) and no code blocks.
54
+ const palette = ['md:p', 'md:h2', 'md:h3', 'md:ul', 'md:ol', 'md:quote', 'md:hr', ...plugins.map((p) => p.type)];
55
+
56
+ let value = $state<StoredBlock[]>([]);
57
+ </script>
58
+
59
+ <Editor bind:value {plugins} {palette} />
60
+ ```
61
+
62
+ `PALETTE_ITEMS` lists the built-in blocks with their labels, hints and icons; `BUILT_IN_IDS` is just their ids in the default order.
63
+
64
+ > **The palette limits offering, not loading**
65
+ >
66
+ > 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)).
67
+
68
+ ## Editing
69
+
70
+ | Action | How |
71
+ |---|---|
72
+ | Insert a block | `/` opens the slash menu (searchable by label, hint, keywords and their translations); the `+` button adds a block below. |
73
+ | Markdown shortcuts | At the start of a paragraph: `# `…`###### `, `- ` / `* ` / `+ `, `1. `, `[] `, `> `. |
74
+ | 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. |
75
+ | Indent | `Tab` / `Shift+Tab` (at most 6 levels). |
76
+ | Line break | `Shift+Enter`; in a code block `Enter` breaks the line and `Shift+Enter` or `Ctrl/Cmd+Enter` starts a new block. |
77
+ | 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. |
78
+ | Move blocks | Drag the handle (also into and out of nested editors), or `Ctrl/Cmd+Shift+↑/↓`. |
79
+ | Turn into | Click the handle of a block or a selection for the Turn-into menu. |
80
+ | Copy, cut | Copying selected blocks puts their markdown on the clipboard (plugin blocks through their markdown form). |
81
+ | Paste | Multi-line text is read as markdown and becomes blocks. |
82
+ | Undo, redo | `Ctrl/Cmd+Z`, `Ctrl/Cmd+Shift+Z` or `Ctrl/Cmd+Y`. |
83
+
84
+ What a block can turn into depends on what it holds (its `content`):
85
+
86
+ - text blocks turn into each other and keep their formatting; turning text into code keeps only the letters;
87
+ - a block without content (divider, video, map) only replaces empty blocks or other blocks without content;
88
+ - 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.
89
+
90
+ ## Scroll sync
91
+
92
+ 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:
93
+
94
+ ```svelte
95
+ <script lang="ts">
96
+ import { Editor, Preview, scrollSync, type StoredBlock } from '@atom-forge/brickee';
97
+ import { PLUGINS } from '@atom-forge/brickee/plugins';
98
+
99
+ let value = $state<StoredBlock[]>([]);
100
+ const sync = scrollSync();
101
+ </script>
102
+
103
+ <div class="split">
104
+ <div class="pane" use:sync.source>
105
+ <Editor bind:value plugins={PLUGINS} />
106
+ </div>
107
+ <div class="pane" use:sync.target>
108
+ <Preview blocks={value} plugins={PLUGINS} />
109
+ </div>
110
+ </div>
111
+
112
+ <style>
113
+ .split {
114
+ display: grid;
115
+ grid-template-columns: 1fr 1fr;
116
+ height: 100vh;
117
+ }
118
+ .pane {
119
+ overflow-y: auto;
120
+ }
121
+ </style>
122
+ ```
123
+
124
+ - `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.
125
+ - 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.
126
+ - `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.
127
+ - 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.
128
+
129
+ `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:
130
+
131
+ ```ts
132
+ import { scrollFraction, setScrollFraction } from '@atom-forge/brickee';
133
+ import { tick } from 'svelte';
134
+
135
+ export async function keepPlace(box: HTMLElement, change: () => void) {
136
+ const f = scrollFraction(box);
137
+ change(); // e.g. switch the preview width
138
+ await tick();
139
+ setScrollFraction(box, f);
140
+ }
141
+ ```
@@ -0,0 +1,81 @@
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, image…);
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, image picker) 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
+ > **Missing theme**
41
+ >
42
+ > 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.
43
+
44
+ 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.
45
+
46
+ ## A first editor
47
+
48
+ ```svelte
49
+ <script lang="ts">
50
+ import { Editor, Preview, type StoredBlock } from '@atom-forge/brickee';
51
+ import { PLUGINS } from '@atom-forge/brickee/plugins';
52
+
53
+ let value = $state<StoredBlock[]>([]);
54
+ </script>
55
+
56
+ <Editor bind:value plugins={PLUGINS} />
57
+
58
+ <article>
59
+ <Preview blocks={value} plugins={PLUGINS} />
60
+ </article>
61
+ ```
62
+
63
+ - `value` is the document as JSON (`StoredBlock[]`). The editor writes it back on every change; save it as it is.
64
+ - `plugins` lists the plugin blocks the editor can show and offer. Leave it out and you have a plain markdown editor.
65
+ - `Preview` renders the same document with the same plugins, the way the site shows it.
66
+
67
+ Type `/` in an empty line to open the block menu.
68
+
69
+ ## Entry points
70
+
71
+ | Import | Contents |
72
+ |---|---|
73
+ | `@atom-forge/brickee` | `Editor`, `Preview`, the kit (`Frame`, `Empty`, helpers), the stored form, the markdown conversions, the contexts, the palette, the types. |
74
+ | `@atom-forge/brickee/plugins` | The bundled plugins (`table`, `youtube`, `map`, `gap`), `PLUGINS` with all four, and their helpers. |
75
+ | `@atom-forge/brickee/schema` | `blocksSchema` and `blocksSchemaFor` (zod), for server-side validation. |
76
+ | `@atom-forge/brickee/model` | The stored form without Svelte: `StoredBlock`, `MD`, `isMd`, `acceptsId`, `blocksToPlainText`… |
77
+ | `@atom-forge/brickee/locales/hu` | `hu`: the Hungarian UI text. |
78
+
79
+ > **Only what you import is bundled**
80
+ >
81
+ > 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.