@bygga.dev/editor 1.8.4 → 1.9.4
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/README.md +185 -33
- package/{TextEditor-rArVp_I4.js → TextEditor-CGebw3iB.js} +722 -618
- package/bygga-editor.js +3233 -2494
- package/element.d.ts +112 -3
- package/package.json +1 -1
- package/{providerKeys-D5U1FqVd.js → providerKeys-ZibeU1i6.js} +2 -2
- package/{urls-CZzpTatM.js → urls-CVNpNWrl.js} +3447 -3264
package/README.md
CHANGED
|
@@ -105,6 +105,15 @@ assistive technology. Settable as the attribute or the `.locale` property. An
|
|
|
105
105
|
unsupported value warns and falls back to the default. The supported set is exported
|
|
106
106
|
as `SUPPORTED_LOCALES` / `DEFAULT_LOCALE`.
|
|
107
107
|
|
|
108
|
+
### `permission` attribute
|
|
109
|
+
What this user may do with [locked blocks](#locked-blocks): `admin` (may lock, unlock,
|
|
110
|
+
and edit a locked block like any other) or `creator` (a content creator: may edit the
|
|
111
|
+
text inside a locked block and nothing else about it). Reactive; settable as the
|
|
112
|
+
attribute or the `.permission` property. **Absent — or anything unrecognised — is
|
|
113
|
+
`creator`**, the permission that can do least, so an embed that never mentions it offers
|
|
114
|
+
no lock controls at all, and a typo'd `admn` warns rather than handing every user the
|
|
115
|
+
keys.
|
|
116
|
+
|
|
108
117
|
### `config` property *(required)*
|
|
109
118
|
Host callbacks. Assign it **before** connecting the element (the editor throws at
|
|
110
119
|
mount if `uploadImage` is missing). It's a plain object, re-read on assignment, so
|
|
@@ -176,17 +185,31 @@ backend must later resolve every id it sees; see
|
|
|
176
185
|
[Sending and publishing](#sending-and-publishing).
|
|
177
186
|
|
|
178
187
|
### `brand` property
|
|
179
|
-
The colours and fonts your embed permits
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
188
|
+
The colours and fonts your embed permits, the look new blocks start with, values for
|
|
189
|
+
your block plugins, and which block tiles to offer — a **menu, never a rewrite of a
|
|
190
|
+
document**. It bounds and seeds what is added next; it never rejects or edits a
|
|
191
|
+
document, and a value already in a document that the Brand doesn't contain keeps
|
|
192
|
+
rendering. All halves are optional; an absent one is unrestricted.
|
|
183
193
|
|
|
184
194
|
```js
|
|
185
195
|
editor.brand = {
|
|
186
|
-
//
|
|
187
|
-
//
|
|
196
|
+
// Your colours, as swatches everywhere a colour is picked. Hex with or without '#',
|
|
197
|
+
// shorthand expanded, case-insensitive. By default they REPLACE the free spectrum;
|
|
198
|
+
// `anyColor: true` keeps it underneath them.
|
|
188
199
|
colors: ['#2f2a72', '#a28c67'],
|
|
189
200
|
|
|
201
|
+
// A second, local palette under the first — a department's colours inside your
|
|
202
|
+
// brand, say — and the headings the two sections show. Unnamed, two palettes read
|
|
203
|
+
// "Brand colors" and "Local colors"; a single one has no heading at all. A colour in
|
|
204
|
+
// both lists is shown in the first only.
|
|
205
|
+
localColors: ['#8a2be2', '#2e8b57'],
|
|
206
|
+
colorsLabel: 'Acme',
|
|
207
|
+
localColorsLabel: 'Acme Studio',
|
|
208
|
+
|
|
209
|
+
// Keep the free picker (colour field, hue, hex) under the palettes, so authors may
|
|
210
|
+
// still pick any colour. Absent or false: only palette colours can be picked.
|
|
211
|
+
anyColor: false,
|
|
212
|
+
|
|
190
213
|
// Narrows the email-safe faces the font pickers offer, in your order. The FIRST is
|
|
191
214
|
// the font a new blank document starts on.
|
|
192
215
|
fonts: ['georgia', 'arial'],
|
|
@@ -224,7 +247,56 @@ never appears. Set `type: 'file'` on those:
|
|
|
224
247
|
The email-safe faces are `arial`, `tahoma`, `trebuchet`, `verdana`, `georgia`,
|
|
225
248
|
`times`, `courier`. Invalid entries (an unparseable colour, a non-http(s) font href)
|
|
226
249
|
are dropped with a `[bygga-editor]` console warning rather than throwing — one typo
|
|
227
|
-
must not take down your editor.
|
|
250
|
+
must not take down your editor.
|
|
251
|
+
|
|
252
|
+
#### Block styles, plugin values and hidden tiles
|
|
253
|
+
|
|
254
|
+
```js
|
|
255
|
+
editor.brand = {
|
|
256
|
+
...brand,
|
|
257
|
+
// The look a newly placed Header, Text or Button block starts with. Every field is
|
|
258
|
+
// optional. `font` is an email-safe face or the `name` of one of your customFonts;
|
|
259
|
+
// `fontSize` is one of 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48; colours are hex.
|
|
260
|
+
styles: {
|
|
261
|
+
header: { font: 'georgia', fontSize: 32, color: '#2f2a72', bold: true,
|
|
262
|
+
align: 'center', level: 2, padding: 8, background: '#ffffff',
|
|
263
|
+
margin: { top: 0, right: 0, bottom: 16, left: 0 } },
|
|
264
|
+
text: { font: 'arial', fontSize: 16, color: '#333333', align: 'left',
|
|
265
|
+
paragraphSpacing: 8 },
|
|
266
|
+
button: { background: '#2f2a72', textColor: '#ffffff', font: 'arial',
|
|
267
|
+
fontSize: 16, bold: true, align: 'center',
|
|
268
|
+
padding: { top: 12, right: 24, bottom: 12, left: 24 },
|
|
269
|
+
// One number rounds every corner; name the corners for a shape.
|
|
270
|
+
borderRadius: { topLeft: 0, topRight: 12, bottomRight: 0, bottomLeft: 0 },
|
|
271
|
+
border: { width: 2, color: '#064c70', style: 'solid' } },
|
|
272
|
+
},
|
|
273
|
+
// Anything your block plugins read to style their own blocks (see Block plugins).
|
|
274
|
+
custom: { cardRadius: 8, accent: '#a28c67' },
|
|
275
|
+
// Block tiles not to offer: built-in types and/or plugin names.
|
|
276
|
+
hiddenBlocks: ['html', 'acme.legacy-banner'],
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Each style can also set the block's box — `margin` and `padding` (a number or
|
|
281
|
+
`{ top, right, bottom, left }`), `borderRadius` (a number, or
|
|
282
|
+
`{ topLeft, topRight, bottomRight, bottomLeft }`), and `border`
|
|
283
|
+
(`{ width, color?, style? }`, `style` one of `solid`, `dashed`, `dotted`) — plus
|
|
284
|
+
`underline` for header and text, and `paragraphSpacing` (`0`, `4`, `8`, `12`, `16`,
|
|
285
|
+
`20`, `24` or `32` px) for text.
|
|
286
|
+
|
|
287
|
+
A style is a **starting value, written into the block when it is placed** — exactly as
|
|
288
|
+
if the author had chosen each setting by hand. Revising your Brand changes the blocks
|
|
289
|
+
placed next, never the ones already in your documents, and the compiled email knows
|
|
290
|
+
nothing about Brands. A header's title and a button carry their whole style from the
|
|
291
|
+
moment they appear; a text block is placed with its alignment, background and padding,
|
|
292
|
+
and the face, size, colour and weight are applied to what the author types into an
|
|
293
|
+
empty paragraph (a mark needs text to sit on). The author can change any of it
|
|
294
|
+
afterwards, as with any block.
|
|
295
|
+
|
|
296
|
+
A hidden tile only narrows the menu: a hidden block type already in a document still
|
|
297
|
+
renders and edits. An entry naming neither a built-in type nor a registered plugin is
|
|
298
|
+
warned about, as is any style field the editor can't use — that field is dropped and
|
|
299
|
+
the rest of the style kept. The Transparent option stays available beside a
|
|
228
300
|
restricted palette, since "no fill" is a structural choice rather than a brand colour.
|
|
229
301
|
|
|
230
302
|
A custom face renders in the editor and wherever the compiled HTML is shown in a real
|
|
@@ -235,7 +307,7 @@ carries what it uses, so it keeps rendering even if you later change the registr
|
|
|
235
307
|
### `savedBlocks` property
|
|
236
308
|
The Saved block library this embed offers — see [Saved blocks](#saved-blocks). An
|
|
237
309
|
array property (arrays can't arrive through an attribute). This is the **only** thing
|
|
238
|
-
the Saved
|
|
310
|
+
the Saved blocks section renders; the editor keeps no library of its own.
|
|
239
311
|
|
|
240
312
|
### Events
|
|
241
313
|
Events do not bubble — listen on the element itself.
|
|
@@ -246,6 +318,34 @@ Events do not bubble — listen on the element itself.
|
|
|
246
318
|
editor dirty.
|
|
247
319
|
- **`dirtychange`** — fired when the unsaved-changes state flips; `event.detail[0]` is
|
|
248
320
|
a boolean. Drives, e.g., a Save button's enabled state.
|
|
321
|
+
- **`autosave`** — fired when the author pauses after editing (about five seconds of
|
|
322
|
+
quiet), or after about thirty seconds of editing without a pause; `event.detail[0]`
|
|
323
|
+
is `{ document, digest }`. Never per keystroke (that is `change`), never for a
|
|
324
|
+
document with nothing unsaved, and never twice for the same content. The editor saves
|
|
325
|
+
nothing itself: persist the payload and call `markSaved()`, or call `save()` to store
|
|
326
|
+
it with its compiled HTML — though each `save()` spends a compile from your licence's
|
|
327
|
+
quota. Edits made within the last pause have not autosaved yet, so guard page exits
|
|
328
|
+
with `isDirty()` (a `beforeunload` handler) rather than relying on the event alone.
|
|
329
|
+
|
|
330
|
+
`digest` fingerprints the document's *content* — SHA-256, as lowercase hex, over its
|
|
331
|
+
[RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) canonical JSON — so you can skip a
|
|
332
|
+
write when you already hold that content (another tab saved it, or your last save
|
|
333
|
+
did). Equal content always has an equal digest, whatever order your storage keeps the
|
|
334
|
+
keys in. Keep the digest of what you last stored, or compute it for any document with
|
|
335
|
+
the exported `documentDigest(document)`; a backend can compute the same value with any
|
|
336
|
+
JCS library.
|
|
337
|
+
|
|
338
|
+
```js
|
|
339
|
+
import { documentDigest } from '@bygga.dev/editor'
|
|
340
|
+
|
|
341
|
+
let storedDigest = await documentDigest(storedDocument)
|
|
342
|
+
editor.addEventListener('autosave', async (event) => {
|
|
343
|
+
const [{ document, digest }] = event.detail
|
|
344
|
+
if (digest === storedDigest) return // already saved — nothing changed
|
|
345
|
+
await api.saveDraft(document)
|
|
346
|
+
storedDigest = digest
|
|
347
|
+
})
|
|
348
|
+
```
|
|
249
349
|
- **`licenseerror`** — fired once when the editor refuses to open; `event.detail[0]` is
|
|
250
350
|
`{ reason }`, one of `key`, `domain`, `origin`, `malformed`, `unreachable`,
|
|
251
351
|
`invalid`, `insecure-context`. The panel and a fuller `console.error` diagnostic are
|
|
@@ -321,11 +421,15 @@ editor.savedBlocks = rows.map(parseSavedBlock)
|
|
|
321
421
|
|
|
322
422
|
`id` and `name` are ordinary strings you can key rows by and show in your own admin;
|
|
323
423
|
`content` is an opaque blob to write verbatim and hand back untouched. Resolving
|
|
324
|
-
`saveBlock` does **not** put the block in the
|
|
424
|
+
`saveBlock` does **not** put the block in the library — `savedBlocks` is the only
|
|
325
425
|
truth the editor renders, so reassign it. A malformed or duplicate-id entry is dropped
|
|
326
426
|
with a warning rather than taking down the editor. Entries are offered only in
|
|
327
427
|
documents of the kind they were saved from.
|
|
328
428
|
|
|
429
|
+
The library appears under the block tiles, in view whenever they are. A Saved block
|
|
430
|
+
keeps any [lock](#locked-blocks) it was saved with, so an admin can curate locked
|
|
431
|
+
sections — a masthead, a legal footer — for content creators to drop in.
|
|
432
|
+
|
|
329
433
|
## Block plugins
|
|
330
434
|
|
|
331
435
|
Custom block types — a video embed, a product card — registered per embed through
|
|
@@ -345,6 +449,46 @@ deployment a static-file update that rebuilds nothing. The contract is `BlockPlu
|
|
|
345
449
|
as permanent and namespace it (`acme.product-card`). `email()` must be deterministic
|
|
346
450
|
and `data` must be plain JSON — the editor re-bakes on load and on every change.
|
|
347
451
|
|
|
452
|
+
**The Brand reaches a plugin where it chooses values.** `create(context)` receives
|
|
453
|
+
`context.brand` — your Brand after validation: `colors`, `localColors`, `colorsLabel`,
|
|
454
|
+
`localColorsLabel`, `anyColor`, `fonts`, `customFonts`, `styles`, `custom` (whatever
|
|
455
|
+
you put in `brand.custom`), and `css`, the block styles
|
|
456
|
+
as ready-to-use CSS — so a new block can start on your look; the settings contexts
|
|
457
|
+
carry the same `brand`, to offer your palette or a "reset to brand". `css.header`
|
|
458
|
+
is how a custom header of your own takes the look the Brand gives the built-in one:
|
|
459
|
+
`css.header.cssText` is a string like `font-family:Georgia, 'Times New Roman', serif;font-size:32px`
|
|
460
|
+
to write into your heading's `style` (escape it like any attribute value), with each
|
|
461
|
+
property also available on its own. A custom font written there is carried into the
|
|
462
|
+
compiled email automatically, as long as your baked HTML names it. Copy what you use into the block's `data`: `email()` and `preview()`
|
|
463
|
+
deliberately do not receive the Brand, because a block's HTML must follow from its
|
|
464
|
+
stored data alone — otherwise the same document would bake differently in two embeds,
|
|
465
|
+
and revising your Brand would silently rewrite every document it touched.
|
|
466
|
+
|
|
467
|
+
```js
|
|
468
|
+
create(context) {
|
|
469
|
+
return { title: '', accent: context.brand.custom.accent ?? context.brand.colors[0] }
|
|
470
|
+
},
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Locked blocks
|
|
474
|
+
|
|
475
|
+
An admin can **lock** a columns block from its settings. For a content creator (the
|
|
476
|
+
`creator` [permission](#permission-attribute)) a locked block is fixed: it can't be
|
|
477
|
+
moved, duplicated, removed, restyled or relaid out, nothing can be added to or taken
|
|
478
|
+
out of it, and its images, buttons and other blocks can't be changed — but **the text
|
|
479
|
+
inside it stays editable** — including a button's label, and whether it is bold,
|
|
480
|
+
through the button's own cut-down settings. A block that *holds* a locked block can't be moved,
|
|
481
|
+
duplicated, removed or hidden (per device or per recipient) by a creator either, since
|
|
482
|
+
that would take the lock with it, and a layout change that would drop a column holding
|
|
483
|
+
one isn't offered. An admin edits
|
|
484
|
+
locked blocks like any other and sees the same "Locked" marker.
|
|
485
|
+
|
|
486
|
+
The lock is stored on the block (`locked: true`) and travels with the document and
|
|
487
|
+
with Saved blocks. It is **guidance for the people editing, not a security boundary**:
|
|
488
|
+
the editor enforces it in its own UI, but you receive the whole document on every
|
|
489
|
+
save, so anything that must hold against a determined user has to be checked where
|
|
490
|
+
you store documents.
|
|
491
|
+
|
|
348
492
|
## AI assistance
|
|
349
493
|
|
|
350
494
|
The editor can offer authors AI help on the text they're writing — but it ships **the UI
|
|
@@ -363,7 +507,7 @@ editor.config = {
|
|
|
363
507
|
// one off means removing it here — there is no separate flag to keep in step.
|
|
364
508
|
actions: ['proofread', 'rewrite', 'shorten'],
|
|
365
509
|
|
|
366
|
-
// Called
|
|
510
|
+
// Called once per paragraph of the author's selection. Answer with its replacement text.
|
|
367
511
|
async run({ action, text, locale }) {
|
|
368
512
|
const res = await fetch('/api/ai', { // YOUR endpoint, your model, your key
|
|
369
513
|
method: 'POST',
|
|
@@ -383,8 +527,11 @@ editor doesn't know is dropped with a console warning, and an empty `actions` ar
|
|
|
383
527
|
means no controls — same as no `ai` at all.
|
|
384
528
|
|
|
385
529
|
**What the author sees:** an AI button in the text toolbar listing exactly your declared
|
|
386
|
-
actions. It runs on their selection, or on the whole block when nothing is selected
|
|
387
|
-
|
|
530
|
+
actions. It runs on their selection, or on the whole block when nothing is selected, one
|
|
531
|
+
paragraph at a time: each paragraph or heading is its own `run` call, all sent at once, so
|
|
532
|
+
a block keeps its paragraphs, heading level, line breaks and styling. The answers land
|
|
533
|
+
together as one undo step, around anything the author typed while waiting. The button is
|
|
534
|
+
disabled while requests are in flight.
|
|
388
535
|
|
|
389
536
|
### Implementing the endpoint
|
|
390
537
|
|
|
@@ -432,29 +579,28 @@ export async function handleAi(req, res) {
|
|
|
432
579
|
if (text.length > 5000) return res.status(413).json({ error: 'selection too long' })
|
|
433
580
|
|
|
434
581
|
const message = await client.messages.parse({
|
|
435
|
-
model: 'claude-opus-5',
|
|
582
|
+
model: 'claude-opus-5-5',
|
|
436
583
|
// These are short transforms on an email selection; a low cap bounds both latency
|
|
437
584
|
// and a runaway bill. Raise it if you allow long selections.
|
|
438
585
|
max_tokens: 4000,
|
|
439
586
|
system: [
|
|
440
587
|
`You edit marketing email copy. ${instruction}`,
|
|
441
|
-
`Reply in the same language as the input (the author
|
|
588
|
+
`Reply in the same language as the input (the author's interface language is "${locale}").`,
|
|
442
589
|
'Return ONLY the edited text — no commentary, no explanation, no quotes around it.',
|
|
443
590
|
'Preserve any {{merge_tag}} placeholders EXACTLY as they appear, including spelling and position.',
|
|
444
|
-
'Return plain text: no Markdown, no HTML.',
|
|
591
|
+
'Return plain text: no Markdown, no HTML. Keep line breaks where they are; add none.',
|
|
445
592
|
].join(' '),
|
|
446
593
|
messages: [{ role: 'user', content: text }],
|
|
447
594
|
output_config: { format: zodOutputFormat(Result) },
|
|
448
595
|
})
|
|
449
596
|
|
|
450
|
-
res.json({ text: message.parsed_output?.text ??
|
|
597
|
+
res.json({ text: message.parsed_output?.text ?? '' })
|
|
451
598
|
}
|
|
452
599
|
```
|
|
453
600
|
|
|
454
601
|
**Clean what you return anyway.** Even with structured output, trim whitespace and strip
|
|
455
|
-
matched surrounding quotes — models add them.
|
|
456
|
-
|
|
457
|
-
returning the input makes that explicit.
|
|
602
|
+
matched surrounding quotes — models add them. An empty answer, or the text unchanged, is
|
|
603
|
+
nothing to do: the editor leaves that paragraph exactly as it was, formatting included.
|
|
458
604
|
|
|
459
605
|
### Security, cost and abuse
|
|
460
606
|
|
|
@@ -463,18 +609,19 @@ returning the input makes that explicit.
|
|
|
463
609
|
- **Authenticate and authorise the endpoint** with the same session your app already
|
|
464
610
|
uses, and check the user may edit that document. Without it you've published an open
|
|
465
611
|
proxy to your model account.
|
|
466
|
-
- **Rate limit per user**, not per IP — this is a button an author can hold down
|
|
467
|
-
per
|
|
468
|
-
-
|
|
469
|
-
|
|
470
|
-
|
|
612
|
+
- **Rate limit per user**, not per IP — this is a button an author can hold down, and one
|
|
613
|
+
click sends one request per paragraph, all at once. Allow a burst (say ten) inside a
|
|
614
|
+
short per-user quota, plus a max input length.
|
|
615
|
+
- **Cap the input**: reject a paragraph beyond a size you're willing to pay for — one long
|
|
616
|
+
paragraph is still one request.
|
|
471
617
|
- **Log for support, not surveillance**: the action, size and outcome are enough to debug
|
|
472
618
|
"the AI button doesn't work"; the copy itself is your customer's content.
|
|
473
619
|
|
|
474
620
|
### Failure and timeouts
|
|
475
621
|
|
|
476
622
|
**Rejecting is a first-class outcome, not a bug.** The editor shows the author an error,
|
|
477
|
-
logs your reason to the console, and leaves their text exactly as it was
|
|
623
|
+
logs your reason to the console, and leaves their text exactly as it was — every paragraph
|
|
624
|
+
of it, since an action's answers land together or not at all. Throw with a
|
|
478
625
|
message worth reading — a quota refusal and a network blip shouldn't look the same to
|
|
479
626
|
whoever supports the author:
|
|
480
627
|
|
|
@@ -512,9 +659,9 @@ ai: {
|
|
|
512
659
|
|
|
513
660
|
| | |
|
|
514
661
|
| --- | --- |
|
|
515
|
-
| `run` receives | `{ action, text, locale }
|
|
516
|
-
| `run` returns | `Promise<string>` — the replacement text |
|
|
517
|
-
| Applied as | plain text
|
|
662
|
+
| `run` receives | `{ action, text, locale }`, once per paragraph in range, all at once — `text` is that paragraph's plain text with merge tags as `{{tagId}}` and line breaks as `\n`; `locale` is the editor's interface language |
|
|
663
|
+
| `run` returns | `Promise<string>` — the paragraph's replacement text |
|
|
664
|
+
| Applied as | plain text in the paragraph's main formatting, with merge-tag chips and line breaks rebuilt; every paragraph in one undo step. **Formatting within a paragraph is flattened**; merge tags survive; an empty or unchanged answer changes nothing |
|
|
518
665
|
| On rejection | author sees an error, console gets your reason, text is unchanged |
|
|
519
666
|
| TypeScript | `AiAssistant`, `AiAction`, `AiRequest` are exported |
|
|
520
667
|
|
|
@@ -684,17 +831,22 @@ and forced-colors modes are handled behind the tokens and keep working under a r
|
|
|
684
831
|
The package augments `HTMLElementTagNameMap`, so
|
|
685
832
|
`document.querySelector('bygga-editor')` is typed as `ByggaEditorElement` — its
|
|
686
833
|
properties, methods, and a typed `addEventListener` for `change` / `dirtychange` /
|
|
687
|
-
`licenseerror` included.
|
|
834
|
+
`autosave` / `licenseerror` included.
|
|
688
835
|
|
|
689
836
|
- **Types:** `Document`, `EditorConfig`, `ByggaEditorElement`, `ByggaEditorEventMap`,
|
|
690
|
-
`
|
|
691
|
-
`
|
|
837
|
+
`AutosaveDetail`,
|
|
838
|
+
`Brand`, `BrandFont`, `BrandCustomFont`, `BrandStyles`, `BrandTextStyle`,
|
|
839
|
+
`BrandHeaderStyle`, `BrandButtonStyle`, `BrandBox`, `BrandCorners`, `BrandBorder`,
|
|
840
|
+
`BrandBlockType`, `Permission`,
|
|
841
|
+
`MergeTags`, `MergeTagEntry`, `MergeTagLabels`, `MergeTagOption`, `SavedBlock`,
|
|
842
|
+
`SavedBlockContent`, `BlockPlugin`, `BlockPluginCreateContext`, `BlockPluginBrand`,
|
|
843
|
+
`BlockPluginStyleCss`,
|
|
692
844
|
`BlockPluginSettingsContext`, `BlockPluginGlobalsContext`,
|
|
693
845
|
`BlockPluginGlobalSettings`, `AiAssistant`, `AiAction`, `AiRequest`, `Locale`,
|
|
694
846
|
`LicenseError`, `LicenseErrorReason`.
|
|
695
847
|
- **Values:** `createEmptyDocument`, `parseDocument`, `parseSavedBlock`,
|
|
696
|
-
`SUPPORTED_LOCALES`, `DEFAULT_LOCALE`, `EDITOR_TAG`, and
|
|
697
|
-
constructor, already registered on import).
|
|
848
|
+
`documentDigest`, `SUPPORTED_LOCALES`, `DEFAULT_LOCALE`, `EDITOR_TAG`, and
|
|
849
|
+
`ByggaEditor` (the element constructor, already registered on import).
|
|
698
850
|
|
|
699
851
|
## Framework integration
|
|
700
852
|
|