@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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Writing plugins
|
|
2
|
+
|
|
3
|
+
[← Previous](bundled-plugins.md) · [Contents](index.md) · [Next →](host-contexts.md)
|
|
4
|
+
|
|
5
|
+
A plugin is a `BlockPlugin` object plus two Svelte components: a **view** for the editor and a **preview** for the site. Everything a plugin needs is exported from `@atom-forge/brickee` — the bundled plugins use nothing else.
|
|
6
|
+
|
|
7
|
+
## Anatomy
|
|
8
|
+
|
|
9
|
+
| Member | Type | Meaning |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `type` | `string` | The stored `type`. Must not start with `md:`; to pass the schema it must match `^[a-z][a-z0-9:-]{0,39}$`. |
|
|
12
|
+
| `label`, `hint` | `string` | Name and short description in the slash and Turn-into menus. |
|
|
13
|
+
| `keys` | `string` | Extra search words for the slash menu. |
|
|
14
|
+
| `icon` | `Icon` | A `@lucide/svelte` icon component. |
|
|
15
|
+
| `props` | `() => P` | The props of a new block. |
|
|
16
|
+
| `fields` | `PropField[]` | The settings on the top of the block (optional). |
|
|
17
|
+
| `tag` | `(props: P) => string` | The small label on the top of the block, before its fields. Parts separated by ` · ` are translated one by one. |
|
|
18
|
+
| `content` | `'none' \| 'text' \| 'plain' \| 'blocks'` | What the block holds. Plugins use `'none'` (props only) or `'blocks'` (a nested document); it decides what Turn-into offers. |
|
|
19
|
+
| `view` | `Component<PluginViewProps>` | The block in the editor. |
|
|
20
|
+
| `preview` | `Component<PluginPreviewProps>` | The block on the site (optional). Without it, `Preview` renders nothing for the block — for apps that render stored blocks with their own renderer. |
|
|
21
|
+
| `accepts` | `string[]` | A container's allowed nested blocks, as palette ids; `'md:*'` is every built-in block. Default: everything the host offers. |
|
|
22
|
+
| `markdown` | `(props: P, ctx: MarkdownContext) => string` | Its markdown form (optional). |
|
|
23
|
+
| `unique` | `boolean` | At most one per document, top level only. |
|
|
24
|
+
| `gate` | `boolean` | The blocks after it are gated; the preview marks them. |
|
|
25
|
+
|
|
26
|
+
`P` is the props type of the plugin; declare it as an interface extending `Record<string, unknown>`.
|
|
27
|
+
|
|
28
|
+
## A plugin without content
|
|
29
|
+
|
|
30
|
+
A link button: a label, a link, a size and a colour — all set in fields, nothing typed into the block itself.
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
// safe-url.ts — only links the site should render
|
|
34
|
+
export const safeUrl = (u: unknown) => {
|
|
35
|
+
const s = String(u ?? '').trim();
|
|
36
|
+
return /^(https?:|mailto:|\/(?!\/))/.test(s) ? s : '';
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```svelte
|
|
41
|
+
<!-- ButtonView.svelte: the block in the editor -->
|
|
42
|
+
<script lang="ts" module>
|
|
43
|
+
export interface ButtonProps extends Record<string, unknown> {
|
|
44
|
+
label: string;
|
|
45
|
+
url: string;
|
|
46
|
+
size: 'normal' | 'large';
|
|
47
|
+
color: string;
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<script lang="ts">
|
|
52
|
+
import { MousePointerClick } from '@lucide/svelte';
|
|
53
|
+
import { Empty, safeColor, type PluginViewProps } from '@atom-forge/brickee';
|
|
54
|
+
|
|
55
|
+
// Both bindable, even when the view does not use them: the editor binds them on every plugin view.
|
|
56
|
+
let { props = $bindable(), blocks = $bindable() }: PluginViewProps<ButtonProps> = $props();
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
{#if props.label}
|
|
60
|
+
<span class="button" style:background={safeColor(props.color, '#2563eb')}>{props.label}</span>
|
|
61
|
+
{:else}
|
|
62
|
+
<Empty icon={MousePointerClick} text="Set the label and the link above" />
|
|
63
|
+
{/if}
|
|
64
|
+
|
|
65
|
+
<style>
|
|
66
|
+
.button {
|
|
67
|
+
display: inline-block;
|
|
68
|
+
padding: 0.5em 1.2em;
|
|
69
|
+
border-radius: 8px;
|
|
70
|
+
color: #fff;
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```svelte
|
|
76
|
+
<!-- ButtonPreview.svelte: the block on the site -->
|
|
77
|
+
<script lang="ts">
|
|
78
|
+
import { safeColor, type PluginPreviewProps } from '@atom-forge/brickee';
|
|
79
|
+
import type { ButtonProps } from './ButtonView.svelte';
|
|
80
|
+
import { safeUrl } from './safe-url';
|
|
81
|
+
|
|
82
|
+
let { props }: PluginPreviewProps<ButtonProps> = $props();
|
|
83
|
+
const href = $derived(safeUrl(props.url));
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
{#if href && props.label}
|
|
87
|
+
<p>
|
|
88
|
+
<a class="button" class:large={props.size === 'large'} {href} style:background={safeColor(props.color, '#2563eb')}>
|
|
89
|
+
{props.label}
|
|
90
|
+
</a>
|
|
91
|
+
</p>
|
|
92
|
+
{/if}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// button.ts: the plugin
|
|
97
|
+
import { MousePointerClick } from '@lucide/svelte';
|
|
98
|
+
import { mdText, type BlockPlugin } from '@atom-forge/brickee';
|
|
99
|
+
import ButtonView, { type ButtonProps } from './ButtonView.svelte';
|
|
100
|
+
import ButtonPreview from './ButtonPreview.svelte';
|
|
101
|
+
import { safeUrl } from './safe-url';
|
|
102
|
+
|
|
103
|
+
export const button: BlockPlugin<ButtonProps> = {
|
|
104
|
+
type: 'button',
|
|
105
|
+
label: 'Button',
|
|
106
|
+
hint: 'A link as a button',
|
|
107
|
+
keys: 'button link cta',
|
|
108
|
+
icon: MousePointerClick,
|
|
109
|
+
props: () => ({ label: '', url: '', size: 'normal', color: '#2563eb' }),
|
|
110
|
+
fields: [
|
|
111
|
+
{ key: 'label', label: 'Label', kind: 'text' },
|
|
112
|
+
{ key: 'url', label: 'Link', kind: 'text', placeholder: 'https://…', parse: (s) => s.trim() },
|
|
113
|
+
{
|
|
114
|
+
key: 'size',
|
|
115
|
+
label: 'Size',
|
|
116
|
+
kind: 'select',
|
|
117
|
+
options: [
|
|
118
|
+
{ value: 'normal', label: 'Normal' },
|
|
119
|
+
{ value: 'large', label: 'Large' }
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{ key: 'color', label: 'Colour', kind: 'color' }
|
|
123
|
+
],
|
|
124
|
+
tag: (p) => (p.size === 'large' ? 'button · large' : 'button'),
|
|
125
|
+
content: 'none',
|
|
126
|
+
markdown: (p) => {
|
|
127
|
+
const href = safeUrl(p.url);
|
|
128
|
+
return href ? `[${mdText(p.label) || href}](${href})` : '';
|
|
129
|
+
},
|
|
130
|
+
view: ButtonView,
|
|
131
|
+
preview: ButtonPreview
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Register it beside the others:
|
|
136
|
+
|
|
137
|
+
```svelte
|
|
138
|
+
<Editor bind:value plugins={[...PLUGINS, button]} />
|
|
139
|
+
<Preview blocks={value} plugins={[...PLUGINS, button]} />
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
> **Props come from users**
|
|
143
|
+
>
|
|
144
|
+
> A plugin's props are whatever was stored — typed by an editor, pasted, or sent to your API by anyone who can save a document. The schema only checks the shape. Every preview must treat props as untrusted: take colours through `safeColor`, check links, clamp numbers, and look images up in the media list instead of using stored URLs. Svelte escapes text in `{...}`; never put a prop into `{@html}`.
|
|
145
|
+
|
|
146
|
+
## Fields
|
|
147
|
+
|
|
148
|
+
Each `PropField` is one setting on the top of the block. The editor draws them and stores their values in `props`.
|
|
149
|
+
|
|
150
|
+
| Member | Meaning |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `key` | The prop it edits. |
|
|
153
|
+
| `label` | Its label (translated through `messages`). |
|
|
154
|
+
| `kind` | `text`, `textarea`, `color`, `select`, `slider`, `image` or `images`. |
|
|
155
|
+
| `placeholder` | Hint of an empty text field; for an optional colour, the colour a click on "none" starts from. |
|
|
156
|
+
| `options` | `{ value, label }[]` for `select`, and for a `slider` that steps through values. An option label may use `{n}`, filled with its value. |
|
|
157
|
+
| `min`, `max`, `step`, `unit` | A number `slider`: its range, step (default 1) and the unit shown after the value. |
|
|
158
|
+
| `none` | The label of the unset state: a number slider's notch left of `min`, or an optional colour's "none" button. |
|
|
159
|
+
| `views` | Per-view values for a `slider` (see [Per-view values](host-contexts.md#per-view-values)). |
|
|
160
|
+
| `format` | The field's text from the stored value (default: the value as a string). |
|
|
161
|
+
| `parse` | The stored value from the field's text (default: the text). |
|
|
162
|
+
|
|
163
|
+
How each kind stores its value:
|
|
164
|
+
|
|
165
|
+
| Kind | Stored value |
|
|
166
|
+
|---|---|
|
|
167
|
+
| `text`, `textarea` | The text, or what `parse` returns (`parse: lines` stores a list of lines). |
|
|
168
|
+
| `select` | The chosen option's `value`. |
|
|
169
|
+
| `slider` with `options` | The value of the option at the slider's position. |
|
|
170
|
+
| `slider` with `min` and `max` | The number as a string (for example `'56'`); the `none` notch removes the key. |
|
|
171
|
+
| `color` | A `#rrggbb` string; with `none`, clearing it removes the key. |
|
|
172
|
+
| `image` | The picked media item's `id`; picking it again removes it. |
|
|
173
|
+
| `images` | An array of ids, in picking order; picking a picked one takes it out. |
|
|
174
|
+
|
|
175
|
+
An empty value (`''`, `null` or `undefined`, also when returned by `parse`) removes the key from `props`, so a preview must always handle a missing prop. A field's `parse` is also a good place to normalize input: the bundled YouTube field accepts any YouTube link and stores only the video id.
|
|
176
|
+
|
|
177
|
+
## The view
|
|
178
|
+
|
|
179
|
+
The view receives `PluginViewProps`:
|
|
180
|
+
|
|
181
|
+
| Prop | Meaning |
|
|
182
|
+
|---|---|
|
|
183
|
+
| `props` | The block's props (bindable): a view may also edit them in place, as the table does. |
|
|
184
|
+
| `blocks` | The nested document (bindable); always `[]` for a plugin without content. |
|
|
185
|
+
| `readonly` | The editor is read-only. |
|
|
186
|
+
| `palette`, `plugins` | The host's palette (already narrowed to `accepts`) and plugins, for a nested editor. |
|
|
187
|
+
|
|
188
|
+
Declare `props` and `blocks` with `$bindable()` even when the view does not use them. The fields are drawn above the view automatically; a view only has to show the block. `Empty` (props `icon` and `text`) is the dashed placeholder the bundled plugins show until their props are set; its text is translated.
|
|
189
|
+
|
|
190
|
+
## The preview
|
|
191
|
+
|
|
192
|
+
The preview receives `PluginPreviewProps`: `props`, `blocks` and `plugins`. It is a plain Svelte component that renders the block for the site. Media, messages and the view are available through the contexts (`useMedia`, `useText`, `useView`; see [Host contexts](host-contexts.md)).
|
|
193
|
+
|
|
194
|
+
## Containers
|
|
195
|
+
|
|
196
|
+
A plugin with `content: 'blocks'` holds a nested document in `blocks`. Its view uses `Frame`, a thin box that draws the block's fields on its top and a nested editor below; its preview renders the inner blocks with a nested `Preview`.
|
|
197
|
+
|
|
198
|
+
```svelte
|
|
199
|
+
<!-- NoteView.svelte -->
|
|
200
|
+
<script lang="ts" module>
|
|
201
|
+
export interface NoteProps extends Record<string, unknown> {
|
|
202
|
+
title: string;
|
|
203
|
+
}
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<script lang="ts">
|
|
207
|
+
import { Frame, useText, type PluginViewProps } from '@atom-forge/brickee';
|
|
208
|
+
|
|
209
|
+
let { props = $bindable(), blocks = $bindable(), readonly, palette, plugins }: PluginViewProps<NoteProps> = $props();
|
|
210
|
+
const tr = useText();
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<Frame bind:blocks {readonly} {palette} {plugins} heading={props.title} empty={tr('Note')} placeholder={tr('Write the note…')} />
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```svelte
|
|
217
|
+
<!-- NotePreview.svelte -->
|
|
218
|
+
<script lang="ts">
|
|
219
|
+
import { Preview, type PluginPreviewProps } from '@atom-forge/brickee';
|
|
220
|
+
import type { NoteProps } from './NoteView.svelte';
|
|
221
|
+
|
|
222
|
+
let { props, blocks, plugins }: PluginPreviewProps<NoteProps> = $props();
|
|
223
|
+
</script>
|
|
224
|
+
|
|
225
|
+
<aside class="note">
|
|
226
|
+
{#if props.title}<p class="note-title">{props.title}</p>{/if}
|
|
227
|
+
<Preview {blocks} {plugins} nested />
|
|
228
|
+
</aside>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
// note.ts
|
|
233
|
+
import { StickyNote } from '@lucide/svelte';
|
|
234
|
+
import { mdJoin, mdQuote, mdText, type BlockPlugin } from '@atom-forge/brickee';
|
|
235
|
+
import NoteView, { type NoteProps } from './NoteView.svelte';
|
|
236
|
+
import NotePreview from './NotePreview.svelte';
|
|
237
|
+
|
|
238
|
+
export const note: BlockPlugin<NoteProps> = {
|
|
239
|
+
type: 'note',
|
|
240
|
+
label: 'Note',
|
|
241
|
+
hint: 'Boxed text with a title',
|
|
242
|
+
keys: 'note aside box',
|
|
243
|
+
icon: StickyNote,
|
|
244
|
+
props: () => ({ title: '' }),
|
|
245
|
+
fields: [{ key: 'title', label: 'Title', kind: 'text' }],
|
|
246
|
+
tag: () => 'note',
|
|
247
|
+
content: 'blocks',
|
|
248
|
+
accepts: ['md:p', 'md:ul', 'md:ol', 'youtube'],
|
|
249
|
+
markdown: (p, { inner }) => mdQuote(mdJoin(p.title && `**${mdText(p.title)}**`, inner)),
|
|
250
|
+
view: NoteView,
|
|
251
|
+
preview: NotePreview
|
|
252
|
+
};
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`Frame` props: `blocks` (bindable), `readonly`, `palette`, `plugins`, and optionally `placeholder` for the nested editor. `heading`, `empty` and `image` (a URL) make a heading line, which is shown only when the fields are not (read-only mode or no fields).
|
|
256
|
+
|
|
257
|
+
How nesting works:
|
|
258
|
+
|
|
259
|
+
- **One level.** A nested editor never offers blocks that hold blocks themselves, nor unique blocks. The schema rejects deeper documents.
|
|
260
|
+
- **`accepts`** narrows what the nested editor offers to the listed palette ids, in the host's order. `'md:*'` stands for every built-in block, so `['md:*', 'youtube']` is "text and videos". Without `accepts`, the nested editor offers everything the host does (minus containers and unique blocks).
|
|
261
|
+
- The host's `palette` still applies: `accepts` can only narrow it, never add to it.
|
|
262
|
+
- Wrapping blocks into a container from the Turn-into menu, and turning a container into another, is offered only when the container accepts every block going in.
|
|
263
|
+
- `blocksSchemaFor(plugins)` checks `accepts` on the server (see [On the server](server-side.md)).
|
|
264
|
+
|
|
265
|
+
## Unique and gate blocks
|
|
266
|
+
|
|
267
|
+
- **`unique: true`** — at most one per document, at the top level. Once the document has one, the plugin leaves the slash and Turn-into menus; nested editors never offer it.
|
|
268
|
+
- **`gate: true`** — the blocks after the first top-level gate block are gated. The `Preview` renders them inside a section marked "For supporters only" with a dashed line; `markGated={false}` turns the marking off. Nested previews never split.
|
|
269
|
+
|
|
270
|
+
A supporters-only divider is a typical example of both: one per document, with the gated content behind it.
|
|
271
|
+
|
|
272
|
+
> **Gating only marks**
|
|
273
|
+
>
|
|
274
|
+
> `gate` does not hide anything: the preview still renders every block, and the stored document still holds them. Deciding who may read the gated part — and leaving it out of the page for everyone else — is the host's job, on the server (see [Gated content](server-side.md#gated-content)). `unique` is not enforced by the schema either.
|
|
275
|
+
|
|
276
|
+
## Markdown form
|
|
277
|
+
|
|
278
|
+
`markdown(props, { inner, media })` returns the block as markdown, used when blocks are copied in the editor and by `toMarkdown`:
|
|
279
|
+
|
|
280
|
+
- `inner` — the nested blocks already as markdown (`''` without any);
|
|
281
|
+
- `media` — the document's media, to turn an image id into its URL with `findMedia(media, id)?.url`.
|
|
282
|
+
|
|
283
|
+
Return `''` to leave the block out. Plugins whose output has no meaning as text (an ad slot, the bundled `gap`) simply have no markdown form. Three helpers keep the output well-formed:
|
|
284
|
+
|
|
285
|
+
| Helper | Does |
|
|
286
|
+
|---|---|
|
|
287
|
+
| `mdText(value)` | Plain text made safe inside markdown: escapes inline marks and `#`, joins lines. |
|
|
288
|
+
| `mdJoin(...parts)` | Joins parts as separate blocks (blank line between), leaving out empty and falsy ones. |
|
|
289
|
+
| `mdQuote(md)` | Prefixes every line with `> `, for a box rendered as a blockquote. |
|
|
290
|
+
|
|
291
|
+
## The kit
|
|
292
|
+
|
|
293
|
+
| Export | Kind | Use |
|
|
294
|
+
|---|---|---|
|
|
295
|
+
| `Frame` | component | The editor look of a container: fields on top, nested editor below. |
|
|
296
|
+
| `Empty` | component | The placeholder of a block whose props are not set yet. |
|
|
297
|
+
| `safeColor(value, fallback)` | function | A `#hex` colour (3–8 digits), or the fallback. |
|
|
298
|
+
| `lines(text)` | function | Trimmed, non-empty lines: a field's `parse` for "one per line" lists. |
|
|
299
|
+
| `baseValue(value)` | function | The base value of a per-view prop. |
|
|
300
|
+
| `viewValues(value, keys)` | function | A per-view prop's value for each view. |
|
|
301
|
+
| `mdText`, `mdJoin`, `mdQuote` | functions | Markdown forms (above). |
|
|
302
|
+
| `useMedia`, `findMedia` | functions | The document's images (see [Host contexts](host-contexts.md)). |
|
|
303
|
+
| `useText` | function | Translates the plugin's own UI text. |
|
|
304
|
+
| `useView` | function | The view the host is showing. |
|
|
305
|
+
| `Preview` | component | Renders a container's nested blocks (with `nested`). |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Mellékelt pluginok
|
|
2
|
+
|
|
3
|
+
[← Előző](documents-and-markdown.md) · [Tartalom](index.md) · [Következő →](writing-plugins.md)
|
|
4
|
+
|
|
5
|
+
Az `@atom-forge/brickee/plugins` négy általános célú plugint tartalmaz. Kizárólag azzal készültek, amit a mag bármely plugin-szerzőnek exportál, így példaként is szolgálnak, és csak azokat veszed át, amelyekre szükséged van.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { PLUGINS } from '@atom-forge/brickee/plugins'; // mind a négy
|
|
9
|
+
import { table, youtube } from '@atom-forge/brickee/plugins'; // vagy néhány
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## A pluginok
|
|
13
|
+
|
|
14
|
+
| Export | `type` | Mezők | Markdown alak | Mi ez |
|
|
15
|
+
|---|---|---|---|---|
|
|
16
|
+
| `table` | `table` | — (helyben szerkesztve) | GFM-táblázat | Inline formázott cellák sorai és oszlopai; a sorokat és az oszlopokat a fogantyújuknál fogva húzhatod. Az első sor lehet fejléc, az utolsó lábléc. |
|
|
17
|
+
| `youtube` | `youtube` | videóhivatkozás vagy -azonosító, cím | hivatkozás | YouTube-videó, kattintásra betöltve; bármely YouTube-hivatkozásból csak a videó azonosítója marad meg. |
|
|
18
|
+
| `map` | `map` | hely vagy cím, nagyításcsúszka | hivatkozás | Egy hely vagy cím beágyazott Google Maps-térképe. |
|
|
19
|
+
| `gap` | `gap` | magasságcsúszka | nincs | Üres hely a blokkok között, 1–8 sor magas. |
|
|
20
|
+
|
|
21
|
+
Egyikük sem tartalmaz beágyazott blokkokat. A képblokkok, a dobozok és a többi tároló megjelenése és viselkedése alkalmazásonként más, ezért a csomag ezeket rád hagyja: a [Pluginok írása](writing-plugins.md) egy tárolót mutat be, [Az alkalmazás kontextusai](host-contexts.md) pedig egy képblokkot.
|
|
22
|
+
|
|
23
|
+
A pluginok mellett a belépési pont exportálja a props-típusaikat (`TableProps`, `YouTubeProps`, `MapProps`, `GapProps`) és az előnézeteik által használt segédfüggvényeket, amelyek saját előnézetek írásakor jól jönnek: `youtubeId` (videóazonosító bármely YouTube-hivatkozásból), `mapSrc` (egy hely beágyazási címe), `gapLines`, `GAP_LINES` és `GAP_MAX`, valamint a táblázatokhoz `tableHeads`, `rowHead`, `HEADS` és `square`.
|
|
24
|
+
|
|
25
|
+
## Előnézet felülírása
|
|
26
|
+
|
|
27
|
+
A plugin egyszerű objektum, így az átalakítása egy spread. A leggyakoribb változtatás az előnézet: a szerkesztőnézet a csomagé marad, az oldal pedig a saját komponenseddel rendereli a blokkot – így a szerkesztő előnézete és a közzétett oldal pontosan ugyanaz.
|
|
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 helyettesítő előnézet ugyanazokat a propsokat kapja, mint az eredeti (`PluginPreviewProps`: `props`, `blocks`, `plugins`). Ez lejátszó beágyazása helyett egy bélyegképet mutat, amely a videóra hivatkozik:
|
|
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
|
+
// a props felhasználói bemenet: csak a jól formált videóazonosítót tartsd meg
|
|
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
|
+
## Mezők hozzáadása
|
|
61
|
+
|
|
62
|
+
Az új beállítások új mezőket és új kezdő propsokat jelentenek. A plugin nézetén nem kell változtatni: a szerkesztő minden mezőt kirajzol a blokk tetején, az előnézeted pedig beolvassa az új propsokat.
|
|
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
|
+
Ha a szerkesztő nem angol nyelvű, add hozzá az új mezőcímkéket a `messages`-hez (lásd [Felületi szövegek](host-contexts.md#felületi-szövegek)). Ha egy meglévő csúszkát nézetenkéntivé akarsz tenni, képezd le a `fields` tömböt, és adj hozzá `views`-t (lásd [Nézetenkénti értékek](host-contexts.md#nézetenkénti-értékek)).
|
|
96
|
+
|
|
97
|
+
> **Tartsd meg a típust**
|
|
98
|
+
>
|
|
99
|
+
> Felülíráskor tartsd meg a plugin `type` értékét: a tárolt dokumentumok típus szerint hivatkoznak a blokkokra. Egy új `type` új blokkot jelent, és a meglévő dokumentumok nem fogják megtalálni a blokkjaikat.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Dokumentumok és markdown
|
|
2
|
+
|
|
3
|
+
[← Előző](editor-and-preview.md) · [Tartalom](index.md) · [Következő →](bundled-plugins.md)
|
|
4
|
+
|
|
5
|
+
## A tárolt alak
|
|
6
|
+
|
|
7
|
+
A dokumentum `StoredBlock` objektumok tömbje: egyszerű JSON, amelyet úgy, ahogy van, elmenthetsz egy adatbázis-oszlopba:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
interface StoredBlock {
|
|
11
|
+
type: string; // beépített blokkoknál 'md:p', 'md:h2'…; plugin-blokkoknál a plugin típusa
|
|
12
|
+
indent: number; // 0–6
|
|
13
|
+
text?: string; // beépített blokkok: inline markdown (md:code esetén nyers szöveg)
|
|
14
|
+
props?: Record<string, unknown>; // plugin-beállítások; md:todo { checked }, md:code { lang }
|
|
15
|
+
blocks?: StoredBlock[]; // beágyazott blokkokat tartalmazó pluginok: a belső dokumentumuk
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Egy rövid dokumentum egy bekezdéssel, egy listával, egy videóval és egy dobozzal (a [Pluginok írása](writing-plugins.md#tárolók) `note` tárolójával):
|
|
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
|
+
- A beépített típusok `md:` előtagot kapnak (`MD` konstans, `isMd(block)` vizsgálat); minden más plugin-típus.
|
|
38
|
+
- A plugin-blokkok csak a `type`, az `indent`, a `props` és – tárolóknál – a `blocks` mezőt őrzik meg. Hogy a props mit jelent, az a pluginon múlik.
|
|
39
|
+
- A beágyazás egy szint mély: egy dobozon belüli blokk maga már nem tartalmazhat blokkokat (a `MAX_DEPTH` értéke 2).
|
|
40
|
+
- A szerkesztő saját munkaalakja (`Block`, `id`-vel és inline HTML-lel) soha nem kerül tárolásra; a kettő között a `toStored` és a `fromStored` alakít át.
|
|
41
|
+
|
|
42
|
+
## Beépített blokkok
|
|
43
|
+
|
|
44
|
+
| Típus | Tárolt szöveg | Props | Markdown |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| `md:p` | inline markdown | — | maga a szöveg |
|
|
47
|
+
| `md:h1` … `md:h6` | inline markdown | — | `#` … `######` |
|
|
48
|
+
| `md:ul` | inline markdown | — | `- item` |
|
|
49
|
+
| `md:ol` | inline markdown | — | `1. item` (sorozatonként és behúzási szintenként számozva) |
|
|
50
|
+
| `md:todo` | inline markdown | `{ checked: boolean }` | `- [x] item` |
|
|
51
|
+
| `md:quote` | inline markdown | — | `> quote` |
|
|
52
|
+
| `md:code` | nyers szöveg | `{ lang?: string }` | kerített blokk a nyelvvel |
|
|
53
|
+
| `md:hr` | — | — | `---` |
|
|
54
|
+
|
|
55
|
+
Az `indent` egymásba ágyazza a listaelemeket (az egymást követő lista-blokkokból az előnézetben helyesen egymásba ágyazott `ul`/`ol` elemek lesznek), a bekezdéseket és az idézeteket pedig behúzza. A címsorok, a kódblokkok és az elválasztók figyelmen kívül hagyják.
|
|
56
|
+
|
|
57
|
+
### Inline markdown
|
|
58
|
+
|
|
59
|
+
A beépített blokkok szövege a markdown egy kicsi, biztonságos részhalmaza:
|
|
60
|
+
|
|
61
|
+
| Jelölés | Tárolt alak |
|
|
62
|
+
|---|---|
|
|
63
|
+
| félkövér | `**text**` |
|
|
64
|
+
| dőlt | `*text*` |
|
|
65
|
+
| aláhúzott, alsó index, felső index | `<u>text</u>`, `<sub>text</sub>`, `<sup>text</sup>` |
|
|
66
|
+
| áthúzott | `~~text~~` |
|
|
67
|
+
| kód | `` `text` `` |
|
|
68
|
+
| hivatkozás | `[text](url)` – csak a `http:`, `https:`, `mailto:`, `tel:` és a webhelyen belüli `/…` célok lesznek hivatkozások |
|
|
69
|
+
| sortörés | egy újsor karakter |
|
|
70
|
+
|
|
71
|
+
Az egyszerű szövegben a speciális karakterek visszaperjeles escape-elést kapnak, így soha nem válnak jelöléssé. Az `inline(md)` az inline markdownt HTML-lé alakítja: előbb escape-eli a szöveget, és csak utána teszi hozzá a fenti tageket, így a kimenete biztonságosan megjeleníthető `{@html}`-lel – az előnézet is így tesz.
|
|
72
|
+
|
|
73
|
+
## Markdown be és ki
|
|
74
|
+
|
|
75
|
+
A markdown teljes értékű formátum: a vágólap ezt hordozza, és a befogadó alkalmazás importálhatja és exportálhatja.
|
|
76
|
+
|
|
77
|
+
| Függvény | Miből → mibe | Hol fut |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `fromStored(block)` | `StoredBlock` → `Block` | bárhol |
|
|
80
|
+
| `toStored(block)` | `Block` → `StoredBlock` | csak böngészőben |
|
|
81
|
+
| `fromMarkdown(text)` | markdown → `Block[]` | bárhol |
|
|
82
|
+
| `toMarkdown(blocks, { plugins, media })` | `Block[]` → markdown | csak böngészőben |
|
|
83
|
+
| `inline(md)` | inline markdown → inline HTML | bárhol |
|
|
84
|
+
|
|
85
|
+
A markdown-függvények a szerkesztő alakján (`Block[]`) dolgoznak, ezért a tárolt dokumentumok a `fromStored` és a `toStored` függvényen mennek át:
|
|
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
|
+
// Tárolt dokumentum -> markdown (csak böngészőben).
|
|
92
|
+
export const exportMarkdown = (doc: StoredBlock[], media: MediaItem[]) =>
|
|
93
|
+
toMarkdown(doc.map(fromStored), { plugins: PLUGINS, media });
|
|
94
|
+
|
|
95
|
+
// Markdown -> tárolt dokumentum (csak böngészőben).
|
|
96
|
+
export const importMarkdown = (text: string): StoredBlock[] => fromMarkdown(text).map(toStored);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> **Csak böngészőben**
|
|
100
|
+
>
|
|
101
|
+
> A `toMarkdown` és a `toStored` a DOM-on keresztül olvassa a szerkesztő inline HTML-jét, ezért szüksége van a `document`-re. A böngészőben hívd őket (eseménykezelőben, effectben), ne szerveroldali renderelés közben. Szerveroldali szöveghez a `blocksToPlainText`-et használd (lásd [A szerveren](server-side.md)).
|
|
102
|
+
|
|
103
|
+
Amit a `fromMarkdown` beolvas:
|
|
104
|
+
|
|
105
|
+
- címsorok, felsorolás-, számozott és teendő-elemek, idézetek, kerített kódblokkok a nyelvükkel, elválasztók és a fenti inline jelölések;
|
|
106
|
+
- a sor eleji szóközök behúzásként (szintenként két szóköz vagy egy tabulátor);
|
|
107
|
+
- minden nem üres sor külön blokként – a tördelt sorokat nem vonja össze egy bekezdéssé, és csak beépített blokkokat hoz létre (táblázatot, képet vagy plugin-blokkot nem).
|
|
108
|
+
|
|
109
|
+
Amit a `toMarkdown` kiír:
|
|
110
|
+
|
|
111
|
+
- az egymást követő listaelemeket egymást követő sorokba, minden más blokkot üres sorral elválasztva;
|
|
112
|
+
- a listaelemeket és a bekezdéseket szintenként két szóközzel behúzva;
|
|
113
|
+
- a plugin-blokkot a pluginja `markdown(props, { inner, media })` függvényével, ahol az `inner` a már átalakított beágyazott blokkjai; a markdown alak nélküli plugin, illetve az, amelyik `''`-t ad vissza, kimarad.
|
|
114
|
+
|
|
115
|
+
Saját pluginod markdown alakjának megírásáról a [Pluginok írása](writing-plugins.md#markdown-alak) fejezet szól.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# A szerkesztő és az előnézet
|
|
2
|
+
|
|
3
|
+
[← Előző](getting-started.md) · [Tartalom](index.md) · [Következő →](documents-and-markdown.md)
|
|
4
|
+
|
|
5
|
+
## Az Editor propjai
|
|
6
|
+
|
|
7
|
+
| Prop | Típus | Alapérték | Jelentés |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `value` | `StoredBlock[]` (bindable) | `[]` | A dokumentum. Minden változáskor visszaíródik; a kívülről lecserélt dokumentum újra betöltődik. |
|
|
10
|
+
| `plugins` | `BlockPlugin[]` | `[]` | A pluginblokkok, amelyeket ez a szerkesztő megjeleníthet és felkínálhat. |
|
|
11
|
+
| `palette` | `string[]` | minden beépített blokk, utána minden plugin | A perjeles menü által felkínált blokktípusok, ebben a sorrendben (lásd lent). |
|
|
12
|
+
| `media` | `MediaItem[]` | — | A képek, amelyek közül a blokkok választhatnak (lásd [Az alkalmazás kontextusai](host-contexts.md)). |
|
|
13
|
+
| `messages` | `Messages` | — | Felületi szövegek az alkalmazás nyelvén: angol szöveg → fordítás. |
|
|
14
|
+
| `readonly` | `boolean` | `false` | Szerkesztés nélkül mutatja a dokumentumot; a pluginmezők rejtve vannak. |
|
|
15
|
+
| `placeholder` | `string` | `Write something, or press '/' for blocks…` | Az üres blokk tippszövege. |
|
|
16
|
+
| `nested` | `boolean` | `false` | A beágyazott blokkokat tartalmazó pluginok állítják be a belső szerkesztőjükön; a befogadó alkalmazás soha nem állítja be. |
|
|
17
|
+
|
|
18
|
+
Az üres dokumentum egyetlen üres bekezdésként jelenik meg, így a szerkesztő által visszaírt érték a csatolás után soha nem üres tömb: legalább `[{ type: 'md:p', indent: 0, text: '' }]`. Az előnézet kihagyja az üres bekezdéseket.
|
|
19
|
+
|
|
20
|
+
## A Preview propjai
|
|
21
|
+
|
|
22
|
+
| Prop | Típus | Alapérték | Jelentés |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| `blocks` | `StoredBlock[]` | — | A megjelenítendő dokumentum. |
|
|
25
|
+
| `plugins` | `BlockPlugin[]` | — | A pluginok, amelyek előnézetei a pluginblokkokat renderelik. |
|
|
26
|
+
| `media` | `MediaItem[]` | — | A képek, amelyekre a blokkok azonosítóval hivatkoznak. |
|
|
27
|
+
| `messages` | `Messages` | — | Felületi szövegek az alkalmazás nyelvén. |
|
|
28
|
+
| `markGated` | `boolean` | `true` | Megjelöli a kapus blokk utáni blokkokat (lásd [Egyedi és kapus blokkok](writing-plugins.md#egyedi-és-kapus-blokkok)). |
|
|
29
|
+
| `nested` | `boolean` | `false` | A beágyazott blokkjaikat renderelő plugin-előnézetek állítják be. |
|
|
30
|
+
|
|
31
|
+
Az előnézet a beépített blokkokat sima HTML-ként rendereli (`p`, `h1`…`h6`, `ul`/`ol` beágyazott elemekkel, `blockquote`, `pre > code` `language-<lang>` osztállyal, `hr`), a pluginblokkokat pedig a pluginjuk `preview` komponensével. Az a blokk, amelynek típusához nincs plugin a `plugins` listában, vagy amelynek pluginjának nincs `preview`-ja, nem renderelődik.
|
|
32
|
+
|
|
33
|
+
Hogy használod-e egyáltalán a `Preview`-t, rajtad múlik: a szerkesztő nem függ tőle. Egy saját megjelenítővel dolgozó alkalmazás maga is renderelheti a tárolt blokkokat, és a pluginjai elhagyhatják a `preview`-t.
|
|
34
|
+
|
|
35
|
+
A legfelső szintű szerkesztőnek vagy előnézetnek átadott `media` és `messages` öröklődik minden alatta lévő beágyazott szerkesztőre, beágyazott előnézetre és pluginkomponensre.
|
|
36
|
+
|
|
37
|
+
## A paletta
|
|
38
|
+
|
|
39
|
+
A `palette` **palettaazonosítók** listája: `md:p`, `md:h1`…`md:h6`, `md:ul`, `md:ol`, `md:todo`, `md:quote`, `md:code`, `md:hr` a beépített blokkokhoz, pluginoknál pedig a plugin `type` értéke. Ez dönti el:
|
|
40
|
+
|
|
41
|
+
- mely blokkokat kínálja fel a perjeles menü és az „Átalakítás” menü, és milyen sorrendben;
|
|
42
|
+
- mely markdown-gyorsbillentyűk működnek (a `## ` begépelése csak akkor alakít egy bekezdést címsorrá, ha az `md:h2` szerepel a palettán);
|
|
43
|
+
- mivé válhat a beillesztett markdown: a palettán kívüli típusú beillesztett blokk bekezdés lesz, az elválasztó pedig elvész.
|
|
44
|
+
|
|
45
|
+
A sima bekezdés (`md:p`) tartalékként mindig engedélyezett.
|
|
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
|
+
// Nincs h1 (az oldal címe az), és nincs kódblokk.
|
|
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
|
+
A `PALETTE_ITEMS` a beépített blokkokat sorolja fel a címkéikkel, tippjeikkel és ikonjaikkal; a `BUILT_IN_IDS` csak az azonosítóik az alapértelmezett sorrendben.
|
|
63
|
+
|
|
64
|
+
> **A paletta a felkínálást korlátozza, nem a betöltést**
|
|
65
|
+
>
|
|
66
|
+
> Ha egy dokumentumban már van palettán kívüli blokk, az továbbra is megjelenik. Az a pluginblokk, amelynek pluginja hiányzik a `plugins` listából, változatlanul megmarad, és ismeretlen blokkként jelenik meg, amelyet csak törölni lehet. Ha az ilyen dokumentumokat el akarod utasítani, validáld őket a szerveren (lásd [A szerveren](server-side.md)).
|
|
67
|
+
|
|
68
|
+
## Szerkesztés
|
|
69
|
+
|
|
70
|
+
| Művelet | Hogyan |
|
|
71
|
+
|---|---|
|
|
72
|
+
| Blokk beszúrása | A `/` megnyitja a perjeles menüt (címke, tipp, kulcsszavak és ezek fordításai alapján kereshető); a `+` gomb egy blokkot ad hozzá alul. |
|
|
73
|
+
| Markdown-gyorsbillentyűk | Bekezdés elején: `# `…`###### `, `- ` / `* ` / `+ `, `1. `, `[] `, `> `. |
|
|
74
|
+
| Szövegformázás | Jelölj ki szöveget az eszköztárhoz, vagy `Ctrl/Cmd` + `B` félkövér, `I` dőlt, `U` aláhúzott, `Shift+X` áthúzott, `E` kód, `K` link, `\` formázás eltávolítása. Az alsó és a felső index az eszköztáron érhető el. |
|
|
75
|
+
| Behúzás | `Tab` / `Shift+Tab` (legfeljebb 6 szint). |
|
|
76
|
+
| Sortörés | `Shift+Enter`; kódblokkban az `Enter` töri a sort, a `Shift+Enter` vagy a `Ctrl/Cmd+Enter` pedig új blokkot kezd. |
|
|
77
|
+
| Blokkok kijelölése | Kattints egy blokk fogantyújára, nyomj `Esc`-et egy blokkban, `Shift+↑/↓` a blokk szélén túl, vagy húzz szövegkijelölést több blokkon át. Üres blokkban a `Ctrl/Cmd+A` minden blokkot kijelöl. |
|
|
78
|
+
| Blokkok mozgatása | Húzd a fogantyút (beágyazott szerkesztőkbe be és onnan ki is), vagy `Ctrl/Cmd+Shift+↑/↓`. |
|
|
79
|
+
| Átalakítás | Kattints egy blokk vagy egy kijelölés fogantyújára az „Átalakítás” menühöz. |
|
|
80
|
+
| Másolás, kivágás | A kijelölt blokkok másolásakor a markdownjuk kerül a vágólapra (a pluginblokkok a markdown alakjukon keresztül). |
|
|
81
|
+
| Beillesztés | A többsoros szöveg markdownként értelmeződik, és blokkokká válik. |
|
|
82
|
+
| Visszavonás, újra | `Ctrl/Cmd+Z`, `Ctrl/Cmd+Shift+Z` vagy `Ctrl/Cmd+Y`. |
|
|
83
|
+
|
|
84
|
+
Hogy egy blokk mivé alakítható, az attól függ, mit tartalmaz (a `content` értékétől):
|
|
85
|
+
|
|
86
|
+
- a szöveges blokkok egymásba alakíthatók, és megtartják a formázásukat; ha szöveget kóddá alakítasz, csak a betűk maradnak meg;
|
|
87
|
+
- a tartalom nélküli blokk (elválasztó, videó, térkép) csak üres blokkokat vagy más tartalom nélküli blokkokat válthat fel;
|
|
88
|
+
- a beágyazott blokkokat tartalmazó blokk (egy doboz) körbefoghatja a kijelölt blokkokat, ha mindegyiket elfogadja; a **„Kicsomagolás”** kimozgatja a doboz blokkjait; egy doboz átalakítható másik dobozszerű típussá, és az azonos nevű props értékek átkerülnek.
|
|
89
|
+
|
|
90
|
+
## Görgetés-szinkron
|
|
91
|
+
|
|
92
|
+
Ha az alkalmazás egymás mellett mutatja a szerkesztőt és az előnézetet, az előnézet követheti a szerkesztő görgetését. A `scrollSync()` két Svelte actiont és egy függvényt ad vissza:
|
|
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
|
+
- A `use:sync.source` a szerkesztő görgethető dobozára kerül, a `use:sync.target` az előnézetére. A cél a görgetési tartományának ugyanakkora hányadáig (0…1) görög, mint a forrás. A szinkron egyirányú: az előnézet görgetése semmi mást nem mozgat.
|
|
125
|
+
- Animációs képkockánként egyszer igazodik, amikor a forrás görög, és akkor is, amikor a céldoboz vagy a gyerekei mérete megváltozik. A `sync.align()` kérésre is igazít, például miután megváltozott az előnézet szélessége.
|
|
126
|
+
- A `use:sync.target={on}` egy logikai értéket kap (alapértéke `true`), ha az alkalmazás két doboz között váltogatja a célt, például egy asztali panel és egy telefonkeret között: a láthatót kapcsold be.
|
|
127
|
+
- A szinkron csak arányos: a két doboz nagyjából ugyanazt a helyet mutatja, nem pontosan ugyanazt a blokkot. Ez szándékos — nem kell hozzá semmilyen jelölés, és bármilyen előnézettel működik.
|
|
128
|
+
|
|
129
|
+
A `scrollFraction(box)` megmondja, mennyire van legörgetve egy doboz (0…1; 0, ha nem görgethető), a `setScrollFraction(box, f)` pedig odagörgeti — például hogy megmaradjon a hely, amikor az önmagában látható előnézet szélessége változik:
|
|
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(); // pl. az előnézet szélességének váltása
|
|
138
|
+
await tick();
|
|
139
|
+
setScrollFraction(box, f);
|
|
140
|
+
}
|
|
141
|
+
```
|