quoin-editor 1.8.0 → 2.0.0
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 +10 -1
- package/MIGRATION.md +137 -23
- package/README.md +8 -2
- package/dist/ai.cjs +34 -34
- package/dist/ai.js +3 -3
- package/dist/catalogue.cjs +21 -21
- package/dist/catalogue.js +12 -12
- package/dist/{chunk-VWYPO6SX.js → chunk-2UKI26JA.js} +5 -5
- package/dist/chunk-2UKI26JA.js.map +1 -0
- package/dist/{chunk-B3OBM6I4.cjs → chunk-4PIJC4FD.cjs} +16 -16
- package/dist/{chunk-5OZX2LV4.js → chunk-5X52BAZB.js} +5 -5
- package/dist/{chunk-5OZX2LV4.js.map → chunk-5X52BAZB.js.map} +1 -1
- package/dist/{chunk-PVFSJXYQ.cjs → chunk-5YAJZSTI.cjs} +18 -18
- package/dist/{chunk-53CNYRVQ.cjs → chunk-66OPHDFA.cjs} +50 -50
- package/dist/{chunk-EZX37R5D.js → chunk-6BVE46K7.js} +4 -4
- package/dist/{chunk-EZX37R5D.js.map → chunk-6BVE46K7.js.map} +1 -1
- package/dist/{chunk-MMS4D4MK.cjs → chunk-77CNOLHY.cjs} +3 -3
- package/dist/{chunk-DXUDBPG5.js → chunk-B4VCDJSA.js} +5 -5
- package/dist/{chunk-DXUDBPG5.js.map → chunk-B4VCDJSA.js.map} +1 -1
- package/dist/{chunk-4MCYQHFL.cjs → chunk-BYYCD54G.cjs} +2 -2
- package/dist/{chunk-BA5WXBGE.cjs → chunk-CJVJNMOB.cjs} +107 -101
- package/dist/{chunk-TQHFGSTT.cjs → chunk-D6CIIWCU.cjs} +30 -30
- package/dist/{chunk-NCDMQCZR.js → chunk-EU5IGKMP.js} +3 -3
- package/dist/{chunk-NCDMQCZR.js.map → chunk-EU5IGKMP.js.map} +1 -1
- package/dist/{chunk-7X3T7VXV.cjs → chunk-HOSDMHK6.cjs} +25 -25
- package/dist/{chunk-XKYNINUX.cjs → chunk-IKZSCTRF.cjs} +3 -3
- package/dist/{chunk-D4L5FINK.js → chunk-IW4TMECB.js} +3 -3
- package/dist/{chunk-D4L5FINK.js.map → chunk-IW4TMECB.js.map} +1 -1
- package/dist/{chunk-MRHDPXQ7.js → chunk-J2SVYBTC.js} +6 -6
- package/dist/chunk-J2SVYBTC.js.map +1 -0
- package/dist/{chunk-PHQMHXNG.js → chunk-KQPZTSW3.js} +4 -4
- package/dist/{chunk-PHQMHXNG.js.map → chunk-KQPZTSW3.js.map} +1 -1
- package/dist/{chunk-YVPZDTVZ.cjs → chunk-LEICOZYK.cjs} +2 -2
- package/dist/{chunk-SP4F5F6V.js → chunk-M77A3EGD.js} +3 -3
- package/dist/{chunk-SP4F5F6V.js.map → chunk-M77A3EGD.js.map} +1 -1
- package/dist/{chunk-VM567D6O.js → chunk-MAS4AEF4.js} +4 -4
- package/dist/{chunk-VM567D6O.js.map → chunk-MAS4AEF4.js.map} +1 -1
- package/dist/{chunk-EZWJRBQT.cjs → chunk-NFO3W644.cjs} +19 -19
- package/dist/{chunk-MSWA5ZZM.js → chunk-NL5J77VD.js} +202 -202
- package/dist/chunk-NL5J77VD.js.map +1 -0
- package/dist/{chunk-53FJFYBR.cjs → chunk-V4WQG7FR.cjs} +64 -64
- package/dist/{chunk-WOGCD7JN.js → chunk-YABGWOAV.js} +3 -3
- package/dist/{chunk-WOGCD7JN.js.map → chunk-YABGWOAV.js.map} +1 -1
- package/dist/{chunk-P6HC7RUN.cjs → chunk-Z6LAARPB.cjs} +199 -204
- package/dist/{chunk-Y4EJ6UBV.js → chunk-ZN5DHNSL.js} +15 -9
- package/dist/chunk-ZN5DHNSL.js.map +1 -0
- package/dist/comments.cjs +30 -30
- package/dist/comments.js +3 -3
- package/dist/diagram.cjs +6 -6
- package/dist/diagram.js +3 -3
- package/dist/embed.cjs +5 -5
- package/dist/embed.js +3 -3
- package/dist/emoji.cjs +6 -6
- package/dist/emoji.js +3 -3
- package/dist/find.cjs +7 -7
- package/dist/find.js +3 -3
- package/dist/headless.cjs +11 -31
- package/dist/headless.d.cts +4 -41
- package/dist/headless.d.ts +4 -41
- package/dist/headless.js +1 -1
- package/dist/highlight.cjs +3 -3
- package/dist/highlight.js +2 -2
- package/dist/index.cjs +23 -43
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/math.cjs +6 -6
- package/dist/math.js +4 -4
- package/dist/mention.cjs +6 -6
- package/dist/mention.js +4 -4
- package/dist/presence.cjs +12 -12
- package/dist/presence.js +3 -3
- package/dist/source.cjs +2 -2
- package/dist/source.js +2 -2
- package/dist/stats.cjs +6 -6
- package/dist/stats.js +3 -3
- package/dist/styles.css +2 -2
- package/dist/table.cjs +22 -22
- package/dist/table.js +3 -3
- package/dist/toc.cjs +6 -6
- package/dist/toc.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-MRHDPXQ7.js.map +0 -1
- package/dist/chunk-MSWA5ZZM.js.map +0 -1
- package/dist/chunk-VWYPO6SX.js.map +0 -1
- package/dist/chunk-Y4EJ6UBV.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
Every release of `quoin-editor` is listed here, newest first, in the spirit of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). From 1.0.0 the package follows [semantic versioning](https://semver.org/): a breaking change waits for the next major version. Until then, Quoin was 0.x and a minor version could contain breaking changes; those known at release were marked "Breaking". The 0.x sections are kept as they were released, "Docs" headings included. Moving from 0.4 to 1.0 is covered step by step in [MIGRATION.md](MIGRATION.md). Review finding ids such as F-17 are those of the 0.4.0 review in [#73](https://github.com/AmineSeboui/quoin-editor/issues/73).
|
|
3
|
+
Every release of `quoin-editor` is listed here, newest first, in the spirit of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). From 1.0.0 the package follows [semantic versioning](https://semver.org/): a breaking change waits for the next major version. Until then, Quoin was 0.x and a minor version could contain breaking changes; those known at release were marked "Breaking". The 0.x sections are kept as they were released, "Docs" headings included. Moving from 0.4 to 1.0, and from 1.x to 2.0, is covered step by step in [MIGRATION.md](MIGRATION.md). Review finding ids such as F-17 are those of the 0.4.0 review in [#73](https://github.com/AmineSeboui/quoin-editor/issues/73).
|
|
4
|
+
|
|
5
|
+
## 2.0.0 (2026-11-06)
|
|
6
|
+
|
|
7
|
+
### Removed
|
|
8
|
+
- Seven names the view layer only ever calls itself leave `quoin-editor/view`, and with it `quoin-editor/headless` and `quoin-editor`. `BlockShell` and `SelectionSink`, with the `BlockShellProps` and `SelectionSinkProps` that typed them, are drawn by `EditorView` itself and register themselves with the controller, so a second copy of either takes the registration from the first. `VoidBlockFallback` is what a block shell already falls back to when no renderer draws a block type, so a renderer table that names it changes nothing. `defaultRenderers` held the three renderers a canvas already reaches for on its own; a renderer of your own draws its text with `EditableRegion`, which is what those three do. `isCrossSelection` answers whether a text selection spans blocks or regions, which is what painting and the arrow keys ask; a host that wants the answer reads `anchor` and `head` of the public `Selection`, and were it to earn a place it belongs beside `textSelection` in `quoin-editor/engine` rather than in the React entry. `BlockRenderProps` and `BlockRenderer` stay, so a renderer table still types. The five values among the seven were marked deprecated in 1.8.0. Removing a published name is breaking, so this lands in 2.0.0; [MIGRATION.md](MIGRATION.md) covers the move.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- A menu no longer throws where `Element.prototype.scrollIntoView` is absent, which is jsdom with no stub and any webview that omits it. Ten calls guarded the element they had queried but not the method on it, so an element that existed without the method was called anyway and threw a `TypeError` that left Quoin for the host's tree, replacing the page wherever an error boundary caught it. The calls are in the slash and language menus, the code block's caret reveal, a block link's target, a table cell, a heading jumped to from the table of contents and vertical caret movement. Keeping the highlighted entry in view is the whole of what a host without the method now loses. Two `scrollTo` calls that restore a scroll position after a change carried the same shape and are guarded with them.
|
|
12
|
+
- An editable canvas warns in development when a definition of `CODE`, `IMAGE` or `FILE` replaces the preview that edits it and brings no editor of its own. Quoin edits those three in their preview rather than through an `editor`, so such a definition left a block the canvas drew and nobody could write in: no editable node, no language picker, and for an image or a file no toolbar and no way to fill the empty state. Nothing was logged, and the warning that would have caught it covered `MARKDOWN`, `CALLOUT` and `TOGGLE` only, whose consequence is milder since the canvas keeps drawing and typing into those. The migration guide said as much by naming only that first set, and now names both. Nothing about what the canvas draws changes.
|
|
4
13
|
|
|
5
14
|
## 1.8.0 (2026-11-05)
|
|
6
15
|
|
package/MIGRATION.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
# Migrating
|
|
1
|
+
# Migrating Quoin
|
|
2
|
+
|
|
3
|
+
Two upgrades are written up here. [0.4 to 1.0](#migrating-from-04-to-10) is the rewrite; [1.x to 2.0](#migrating-from-1x-to-20) takes seven names the view layer only ever called itself out of the public entries.
|
|
4
|
+
|
|
5
|
+
## Migrating from 0.4 to 1.0
|
|
2
6
|
|
|
3
7
|
Quoin 1.0 keeps the 0.4 document format and the `BlockCanvas` contract, and replaces everything under them: the card and textarea of each block give way to an editor of Quoin's own that types into the page in place. Most hosts upgrade by changing the version and reading the list below; the [checklist](#checklist) at the end sums it up. Every change is listed in the [changelog](CHANGELOG.md).
|
|
4
8
|
|
|
@@ -8,13 +12,13 @@ npm install quoin-editor@^1
|
|
|
8
12
|
|
|
9
13
|
React 18.2 or 19 and TypeScript 5.4 or later, as in 0.4.
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
### Data
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
#### No migration is needed
|
|
14
18
|
|
|
15
19
|
A document is still a `QuoinBlock[]` of `{ id, type, data }`, and every key of the five 0.4 block types keeps its name and meaning (`MarkdownBlockData`, `CalloutBlockData`, `CodeBlockData`, `ImageBlockData` and `FileBlockData`). Store and load the same arrays. 1.0 adds optional keys, listed [below](#new-optional-keys). Blocks nobody edits are handed back by `onChange` as the very objects you supplied, so a stored 0.4 document is never rewritten by being opened. The one exception is a block whose id repeats another's: it gets a fresh id.
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
#### Multi-paragraph markdown blocks split on their first edit
|
|
18
22
|
|
|
19
23
|
A 0.4 `MARKDOWN` block could hold a whole page: several paragraphs, a list, a table. A 1.0 text block holds one line: a paragraph, a heading, a list item, a task or a quote. A 0.4 block of several lines is drawn line by line and kept as it is until someone edits it. On that first edit it is split: the first line keeps the block's id, the others take ids derived from it (`<id>-2`, `<id>-3` and on, skipping any id the document already has), a table becomes a `TABLE` block and an image alone on its line an `IMAGE` block. Each piece is written back as the canonical markdown of its line, so a host that diffs stored strings sees those blocks change once.
|
|
20
24
|
|
|
@@ -33,11 +37,11 @@ export const edited: QuoinBlock[] = [
|
|
|
33
37
|
];
|
|
34
38
|
```
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
#### The `indent` field
|
|
37
41
|
|
|
38
42
|
`QuoinBlock` gains an optional `indent`, the nesting depth shared by every block type: a nested list item, or a block inside a toggle. It is written only when it is above 0. Code that copies, validates or stores blocks should keep it.
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
#### New optional keys
|
|
41
45
|
|
|
42
46
|
1.0 writes keys that 0.4 never wrote, each only once a writer uses what it holds:
|
|
43
47
|
|
|
@@ -50,21 +54,21 @@ export const edited: QuoinBlock[] = [
|
|
|
50
54
|
|
|
51
55
|
The block itself may carry `indent`, and documents may hold the new block types `DIVIDER`, `TOGGLE`, `TABLE`, `EMBED`, `BOOKMARK`, `MATH` and `TOC`. A host whose schema rejects unknown keys or types would reject a paragraph that was aligned, or an image that was resized. Add the keys above as optional, or let unknown keys through: in zod, `.passthrough()` (zod 3) or `.loose()` (zod 4) on the `data` schema; in JSON Schema, `"additionalProperties": true`.
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
#### Captions and callout bodies are inline markdown
|
|
54
58
|
|
|
55
59
|
An image's `caption` and a callout's `body` were plain text in 0.4. In 1.0 they are typed in place and stored as inline markdown, like a paragraph. A 0.4 caption such as `A **sleepy** cat` now shows "sleepy" in bold, and `&` shows as `&`. To keep a character literal in stored data, escape it with a backslash (`\*\*`); text typed in the canvas is escaped for you.
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
#### Named HTML entities
|
|
58
62
|
|
|
59
63
|
Numeric character references (`é`, `é`) are always decoded. Of the named entities, only the 23 most common are: `&`, `<`, `>`, `"`, `'`, ` `, `©`, `®`, `™`, `…`, `—`, `–`, `‘`, `’`, `“`, `”`, `«`, `»`, `×`, `÷`, `°`, `·` and `•`. Any other, such as `é`, stays literal text, and is written back escaped (`\é`) once its block is edited. The full HTML5 table would add about 10 KB gzip to the engine. Workaround: replace other named entities with the character itself or its numeric reference before you import the markdown.
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
#### Inline images in imported markdown
|
|
62
66
|
|
|
63
67
|
An image alone on its line becomes an `IMAGE` block. An image inside a paragraph, such as `see  below`, stays literal text, since 1.0 has no inline image. Workaround: put each image on a line of its own before you import the markdown, or write an extension that adds an inline image atom.
|
|
64
68
|
|
|
65
|
-
|
|
69
|
+
### API
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
#### `BlockCanvas`
|
|
68
72
|
|
|
69
73
|
Every 0.4 prop keeps its name, and its meaning except as the changes below say: `blocks`, `onChange`, `onDelete`, `blockTypes`, `upload`, `resolveAssetUrl`, `onError`, `readOnly`, `className`, `id` and `aria-label`. New ones are optional:
|
|
70
74
|
|
|
@@ -83,11 +87,11 @@ Two behaviours changed:
|
|
|
83
87
|
- **`onDelete` reports more.** Besides the block menu's Delete, it reports whole blocks removed by typing over a selected block or by pasting over selected blocks. A line that Backspace or Delete merges into its neighbour is still not reported. To restore a block, splice it back into `blocks` once they no longer hold it, as on a later click of an Undo toast, not inside the callback.
|
|
84
88
|
- **A collaborative canvas is uncontrolled.** A canvas given `collaborationExtension` from `quoin-editor/yjs` takes its content from the shared document. Pass the starting blocks once and keep echoing `onChange`; while the shared document is connected, any other array handed to `blocks` is ignored, with a warning in development.
|
|
85
89
|
|
|
86
|
-
|
|
90
|
+
#### `defineBlock`
|
|
87
91
|
|
|
88
92
|
`defineBlock` takes the same definition. `icon` now accepts any component that takes a `className` and `aria-hidden`, not only a lucide one. A custom block shows its `preview` at rest, and its `editor` opens on a double click, its Edit button or Enter on the selected block; Escape closes it.
|
|
89
93
|
|
|
90
|
-
A definition of a type the canvas types into in place, `MARKDOWN`, `CALLOUT` or `TOGGLE`, now draws only on a read-only canvas, and an editable canvas warns in development. To change how one of them looks while it is edited, give an extension a renderer for the type in `ui.renderers`:
|
|
94
|
+
A definition of a type the canvas types into in place, `MARKDOWN`, `CALLOUT` or `TOGGLE`, now draws only on a read-only canvas, and an editable canvas warns in development. `CODE`, `IMAGE` and `FILE` are edited in their `preview` rather than through an `editor`, so a definition that replaces the preview and brings no editor of its own leaves a block an editable canvas draws and nobody can write in; that warns in development too. To draw one of those three your own way on a read-only canvas and keep Quoin's editing where the canvas is editable, register your definition only while `readOnly` is set. To change how one of them looks while it is edited, give an extension a renderer for the type in `ui.renderers`:
|
|
91
95
|
|
|
92
96
|
```tsx
|
|
93
97
|
import { BlockCanvas, EditableRegion, type BlockRenderer, type ExtensionUI, type QuoinBlock } from 'quoin-editor';
|
|
@@ -107,17 +111,17 @@ export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (
|
|
|
107
111
|
}
|
|
108
112
|
```
|
|
109
113
|
|
|
110
|
-
|
|
114
|
+
#### `useQuoin`
|
|
111
115
|
|
|
112
116
|
`QuoinConfig`, which `useQuoin()` returns to a custom block, gains `highlight` and the required `controller` and `labels`. `QuoinProvider` fills them in, so only a `QuoinConfig` built by hand, such as a test double for a custom block, needs `controller: null` and `labels: englishLabels` added.
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
#### Entries
|
|
115
119
|
|
|
116
120
|
- `quoin-editor/headless` now exports the view and the engine (`useQuoinEditor`, `EditorView`, `createEditor` and the rest), and none of the 0.4 helpers. The main entry re-exports all of it.
|
|
117
121
|
- `formatShortcut` is exported from `quoin-editor` only. Import it from there instead of `quoin-editor/headless`.
|
|
118
122
|
- `quoin-editor/engine` is the engine alone, with no React and no `'use client'` banner: import `toMarkdown`, `toHTML` and the other pure functions from it in a server component.
|
|
119
123
|
|
|
120
|
-
|
|
124
|
+
#### Removed helpers
|
|
121
125
|
|
|
122
126
|
The 0.4 helpers worked on the text of a textarea. In 1.0 each is an engine command, run through the canvas's ref (`handle.current?.editor.commands.<name>(...)`) or a headless editor, or is no longer needed.
|
|
123
127
|
|
|
@@ -163,11 +167,11 @@ export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (
|
|
|
163
167
|
}
|
|
164
168
|
```
|
|
165
169
|
|
|
166
|
-
|
|
170
|
+
#### Command names of the design spec
|
|
167
171
|
|
|
168
172
|
The 1.0 design spec named `mergeBackward`, `mergeForward` and `insertBlocks` as commands. They are not commands: `deleteBackward` at the start of a block joins it to the one before, `deleteForward` at its end joins the next one to it, and `insertBlockAfter` and `insertSlice` insert blocks. Use those names.
|
|
169
173
|
|
|
170
|
-
|
|
174
|
+
#### Labels
|
|
171
175
|
|
|
172
176
|
Strings come from `labels`. Two keys are functions rather than strings: `blockDeleted(n)` and `undoHint(shortcut)`. English callout tone names are written in lower case in the tone chip and its menu; give `calloutTones` to write them otherwise:
|
|
173
177
|
|
|
@@ -185,7 +189,7 @@ export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (
|
|
|
185
189
|
}
|
|
186
190
|
```
|
|
187
191
|
|
|
188
|
-
|
|
192
|
+
### Features that now need an import
|
|
189
193
|
|
|
190
194
|
In 0.4 the slash palette's Table command wrote a markdown table into the block. In 1.0 a table is a `TABLE` block, drawn and edited by `quoin-editor/table`, which a page loads only when it imports it. Without it, a table, including one split out of a 0.4 block, is drawn as an unsupported block and its data is kept as it was. The one-line change is to pass the extension:
|
|
191
195
|
|
|
@@ -202,22 +206,22 @@ export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (
|
|
|
202
206
|
|
|
203
207
|
`catalogue()` from `quoin-editor/catalogue` adds every opt-in feature at once: tables, embeds, math, a table of contents, mentions, emoji, find and replace and stats.
|
|
204
208
|
|
|
205
|
-
|
|
209
|
+
### Styling
|
|
206
210
|
|
|
207
211
|
- Every 0.4 `--quoin-` token keeps its name, and the stylesheet still sits in the one `quoin` cascade layer.
|
|
208
212
|
- New tokens: `--quoin-selection`, the writer's text and background colours `--quoin-color-*` and `--quoin-bg-*`, `--quoin-comment`, `--quoin-comment-active` and `--quoin-comment-edge`, `--quoin-diff-inserted` and `--quoin-diff-deleted`, and `--quoin-link`. Each has a light and a dark default; a host that maps its own tokens onto Quoin's can map these too.
|
|
209
213
|
- `color-scheme` is declared for light and dark.
|
|
210
214
|
- The DOM and the class names inside the canvas are new. CSS that targeted 0.4 internals, such as a block card or its textarea, no longer matches; style through the tokens.
|
|
211
215
|
|
|
212
|
-
|
|
216
|
+
### React 18
|
|
213
217
|
|
|
214
218
|
React 18.2 and 19 are both supported, as 0.4.1 made them. CI typechecks the packed package against both majors and renders the server entries in plain Node on each, and `BlockCanvas` forwards its ref, so a `QuoinEditorHandle` reaches you on React 18 too.
|
|
215
219
|
|
|
216
|
-
|
|
220
|
+
### The package
|
|
217
221
|
|
|
218
222
|
The tarball ships ESM and CommonJS builds of every entry, with their sourcemaps. The sourcemaps leave out `sourcesContent`: a debugger maps to the original file names and lines, but shows their text only where it can read the sources, such as from the repository at the release tag.
|
|
219
223
|
|
|
220
|
-
|
|
224
|
+
### Checklist
|
|
221
225
|
|
|
222
226
|
- [ ] Install `quoin-editor@^1`.
|
|
223
227
|
- [ ] Keep `indent` and the new optional keys when you copy, validate or store blocks, and let a strict schema accept them.
|
|
@@ -231,3 +235,113 @@ The tarball ships ESM and CommonJS builds of every entry, with their sourcemaps.
|
|
|
231
235
|
- [ ] Pass `tableExtension()` (or `catalogue()`) if your documents hold tables.
|
|
232
236
|
- [ ] Map the new tokens if you map Quoin's tokens to your own, and drop CSS that targeted 0.4 internals.
|
|
233
237
|
- [ ] Treat a canvas with a collaboration extension as uncontrolled.
|
|
238
|
+
|
|
239
|
+
## Migrating from 1.x to 2.0
|
|
240
|
+
|
|
241
|
+
Quoin 2.0 keeps the document format, the props of every component and every command of 1.x. What changes is the published surface: seven names that only the view layer ever called itself stop being exported from `quoin-editor/view`, and so from `quoin-editor/headless` and `quoin-editor` with it. Five of them have carried a deprecation notice since 1.8.0, shown by an editor on hover and on the [headless reference](https://amineseboui.github.io/quoin-editor/docs/headless-reference/); the other two are the prop types of the two components, and go with them. Nothing else is removed and nothing behaves differently, so a host that imports none of the seven upgrades by changing the version.
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
npm install quoin-editor@^2
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
React 18.2 or 19 and TypeScript 5.4 or later, as in 1.x.
|
|
248
|
+
|
|
249
|
+
### Exports that are gone
|
|
250
|
+
|
|
251
|
+
| 1.x | 2.0 | Notes |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| `defaultRenderers` | `EditableRegion` | The one removal with a cost, [below](#defaultrenderers). |
|
|
254
|
+
| `BlockShell`, `BlockShellProps` | Nothing | [`EditorView` draws a shell for every block already.](#blockshell-and-selectionsink) |
|
|
255
|
+
| `SelectionSink`, `SelectionSinkProps` | Nothing | [`EditorView` draws one already.](#blockshell-and-selectionsink) |
|
|
256
|
+
| `VoidBlockFallback` | Nothing | [A block shell falls back to it on its own.](#voidblockfallback) |
|
|
257
|
+
| `isCrossSelection` | `Selection` | [Four lines over `anchor` and `head`.](#iscrossselection) |
|
|
258
|
+
|
|
259
|
+
`BlockRenderProps` and `BlockRenderer` stay public, so a renderer table still types.
|
|
260
|
+
|
|
261
|
+
#### `defaultRenderers`
|
|
262
|
+
|
|
263
|
+
A paragraph, a heading, a bullet, a numbered item, a task and a quote are all `MARKDOWN` blocks drawn by one renderer. A host that wanted its own quote therefore had to name `MARKDOWN` in its table, and could hand every other kind back to the default:
|
|
264
|
+
|
|
265
|
+
```jsx
|
|
266
|
+
import { defaultRenderers, EditableRegion } from 'quoin-editor';
|
|
267
|
+
|
|
268
|
+
const Markdown = defaultRenderers.MARKDOWN;
|
|
269
|
+
|
|
270
|
+
export const renderers = {
|
|
271
|
+
MARKDOWN: (props) =>
|
|
272
|
+
props.block.attrs.kind === 'quote' ? (
|
|
273
|
+
<figure className="pull-quote">
|
|
274
|
+
<EditableRegion block={props.block.id} region="text" label="Quote" />
|
|
275
|
+
</figure>
|
|
276
|
+
) : (
|
|
277
|
+
<Markdown {...props} />
|
|
278
|
+
),
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
In 2.0 that renderer owns every kind, and draws each one's text with `EditableRegion`, which is what the default renderer does inside:
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
import { EditableRegion, type BlockRenderer } from 'quoin-editor';
|
|
286
|
+
|
|
287
|
+
const LABELS: Record<string, string> = {
|
|
288
|
+
heading: 'Heading',
|
|
289
|
+
bullet: 'Bullet list item',
|
|
290
|
+
ordered: 'Numbered list item',
|
|
291
|
+
task: 'Task',
|
|
292
|
+
quote: 'Quote',
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export const renderers: Record<string, BlockRenderer> = {
|
|
296
|
+
MARKDOWN: ({ block }) => {
|
|
297
|
+
const kind = typeof block.attrs.kind === 'string' ? block.attrs.kind : '';
|
|
298
|
+
const text = <EditableRegion block={block.id} region="text" label={LABELS[kind] ?? 'Paragraph'} />;
|
|
299
|
+
if (kind === 'quote') return <figure className="pull-quote">{text}</figure>;
|
|
300
|
+
if (kind === 'heading') {
|
|
301
|
+
const level = typeof block.attrs.level === 'number' ? block.attrs.level : 1;
|
|
302
|
+
return <div role="heading" aria-level={level} className="quoin-heading">{text}</div>;
|
|
303
|
+
}
|
|
304
|
+
if (kind === 'bullet') {
|
|
305
|
+
return (
|
|
306
|
+
<div className="quoin-list-item" data-list="bullet">
|
|
307
|
+
<span className="quoin-marker" aria-hidden="true">{'•'}</span>
|
|
308
|
+
{text}
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return text;
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`EditableRegion` takes the block's id, the name of the region to draw (`text` for every markdown kind) and an accessible name, and is the same editable host the default renderer mounts, so the caret, the selection and the decorations keep working.
|
|
318
|
+
|
|
319
|
+
Two kinds ask for more than markup, and the sample above leaves both drawn as plain text. A numbered item shows its running number, which counts the ordered blocks above it at the same indent; a task shows a checkbox that runs the `toggleTask` command against its own block. Neither the counter nor the checkbox is public, so a host that overrides `MARKDOWN` and still wants those two drawn the way Quoin draws them writes them itself. A host that only wants to restyle a kind has a cheaper way out: leave `MARKDOWN` out of the renderer table and style the markup the default renderer already writes, which carries `data-kind` on the block and the `quoin-heading`, `quoin-list-item`, `quoin-task` and `quoin-quote` class names.
|
|
320
|
+
|
|
321
|
+
#### `BlockShell` and `SelectionSink`
|
|
322
|
+
|
|
323
|
+
Neither was ever something to render. `EditorView` draws one `SelectionSink` and one `BlockShell` per block, and each registers itself with the `ViewController` on mount (`controller.registerSink`, `controller.registerBlockElement`). A second copy takes that registration from the first, so a host that rendered either lost the selection or the block it meant to help. Drop the import; the editor already draws both. `BlockShellProps` and `SelectionSinkProps` named their props and go with them.
|
|
324
|
+
|
|
325
|
+
#### `VoidBlockFallback`
|
|
326
|
+
|
|
327
|
+
A block shell falls back to it already when no renderer in the table draws a block's type, so a table that named it only restated the default. Drop the entry and the import.
|
|
328
|
+
|
|
329
|
+
#### `isCrossSelection`
|
|
330
|
+
|
|
331
|
+
It asked whether a text selection spans blocks or regions, which is what painting and the arrow keys ask. It reads `anchor` and `head` of the public `Selection`, so a host that wants the answer writes it:
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
import type { Selection } from 'quoin-editor/engine';
|
|
335
|
+
|
|
336
|
+
export const spansBlocks = (selection: Selection | null): boolean =>
|
|
337
|
+
selection?.kind === 'text' &&
|
|
338
|
+
(selection.anchor.block !== selection.head.block || selection.anchor.region !== selection.head.region);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### 2.0 checklist
|
|
342
|
+
|
|
343
|
+
- [ ] Install `quoin-editor@^2`.
|
|
344
|
+
- [ ] Drop any `BlockShell` or `SelectionSink` you render, and the `BlockShellProps` and `SelectionSinkProps` that typed them.
|
|
345
|
+
- [ ] Drop any `VoidBlockFallback` entry from a renderer table.
|
|
346
|
+
- [ ] Replace `isCrossSelection` with the predicate above.
|
|
347
|
+
- [ ] Draw every `MARKDOWN` kind yourself if your table names `MARKDOWN`, or leave `MARKDOWN` out and style the default markup instead.
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ What `onChange` hands you, and what you store:
|
|
|
76
76
|
|
|
77
77
|
Each competitor cell links to the page that shows it; the Tiptap UI cell also draws on the review in [#73](https://github.com/AmineSeboui/quoin-editor/issues/73) (2026-10-12).
|
|
78
78
|
|
|
79
|
-
| Feature | Quoin
|
|
79
|
+
| Feature | Quoin 2.0 | Tiptap 3 | BlockNote | Editor.js | Lexical | Plate |
|
|
80
80
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
81
81
|
| Stored document | Block array, markdown per text block | [JSON, or HTML][tt-store] | [Block JSON][bn-formats] | [JSON][ej-repo] | [Editor state, as JSON][lx-intro] | [Slate JSON][pl-docs] |
|
|
82
82
|
| Markdown | The storage format | [`@tiptap/markdown`, an early release][tt-md] | [Export and import, documented as lossy][bn-formats] | [Community plugin][ej-awesome] | [`@lexical/markdown`, in and out][lx-md] | [`@platejs/markdown`, in and out][pl-md] |
|
|
@@ -131,7 +131,13 @@ Sources, checked on 2026-11-05: Tiptap [persistence][tt-store], [markdown][tt-md
|
|
|
131
131
|
- [Collaboration](https://amineseboui.github.io/quoin-editor/docs/collaboration/), [comments](https://amineseboui.github.io/quoin-editor/docs/comments/) and [AI](https://amineseboui.github.io/quoin-editor/docs/ai/)
|
|
132
132
|
- [Server rendering](https://amineseboui.github.io/quoin-editor/docs/server-rendering/): read-only, `QuoinView`, `QuoinDiff`, `toDocx`
|
|
133
133
|
- [Accessibility](https://amineseboui.github.io/quoin-editor/docs/accessibility/): what each test suite checks, and what it does not yet
|
|
134
|
-
- [FAQ](https://amineseboui.github.io/quoin-editor/docs/faq/): the package name, stability, React versions, and [upgrading
|
|
134
|
+
- [FAQ](https://amineseboui.github.io/quoin-editor/docs/faq/): the package name, stability, React versions, and [upgrading to 2.0](MIGRATION.md#migrating-from-1x-to-20)
|
|
135
|
+
|
|
136
|
+
## Sponsors
|
|
137
|
+
|
|
138
|
+
Quoin is MIT, with no paid tier and nothing held back behind a licence. It is built and maintained by one person. [Sponsoring it](https://github.com/sponsors/AmineSeboui) pays for the time that keeps issues answered and releases going out.
|
|
139
|
+
|
|
140
|
+
No sponsors yet. Yours would be the first name here.
|
|
135
141
|
|
|
136
142
|
## The name
|
|
137
143
|
|
package/dist/ai.cjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var chunkLE5XMIFJ_cjs = require('./chunk-LE5XMIFJ.cjs');
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
5
|
+
var chunkBYYCD54G_cjs = require('./chunk-BYYCD54G.cjs');
|
|
6
|
+
var chunkCJVJNMOB_cjs = require('./chunk-CJVJNMOB.cjs');
|
|
7
|
+
var chunkZ6LAARPB_cjs = require('./chunk-Z6LAARPB.cjs');
|
|
8
8
|
var chunkZWTIRGY5_cjs = require('./chunk-ZWTIRGY5.cjs');
|
|
9
9
|
require('./chunk-A6PGQVTR.cjs');
|
|
10
10
|
var chunkFK7EBZPX_cjs = require('./chunk-FK7EBZPX.cjs');
|
|
@@ -377,7 +377,7 @@ async function read(editor, link, run, stream, request) {
|
|
|
377
377
|
} catch (error) {
|
|
378
378
|
if (!live()) return;
|
|
379
379
|
end("error");
|
|
380
|
-
link.report(
|
|
380
|
+
link.report(chunkCJVJNMOB_cjs.toError(error));
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
function startStream(editor, link, stream, request, replace) {
|
|
@@ -474,7 +474,7 @@ function aiSlashItems(labels, stream) {
|
|
|
474
474
|
title: labels.askAi,
|
|
475
475
|
description: labels.askAiDescription,
|
|
476
476
|
group: "ai",
|
|
477
|
-
icon:
|
|
477
|
+
icon: chunkCJVJNMOB_cjs.Sparkles,
|
|
478
478
|
keywords: KEYWORDS,
|
|
479
479
|
run: ({ controller }) => void openFor(controller)
|
|
480
480
|
},
|
|
@@ -482,7 +482,7 @@ function aiSlashItems(labels, stream) {
|
|
|
482
482
|
id: "ai-continue",
|
|
483
483
|
title: labels.actions.continue,
|
|
484
484
|
group: "ai",
|
|
485
|
-
icon:
|
|
485
|
+
icon: chunkCJVJNMOB_cjs.PenLine,
|
|
486
486
|
keywords: [...KEYWORDS, "continue"],
|
|
487
487
|
run: ({ controller }) => {
|
|
488
488
|
if (openFor(controller)) askFrom({ controller, stream}, "continue");
|
|
@@ -493,14 +493,14 @@ function aiSlashItems(labels, stream) {
|
|
|
493
493
|
var aiBubbleItem = (labels) => ({
|
|
494
494
|
id: "ai",
|
|
495
495
|
title: labels.askAi,
|
|
496
|
-
icon:
|
|
496
|
+
icon: chunkCJVJNMOB_cjs.Sparkles,
|
|
497
497
|
available: (state) => aiState(state).ghost === null,
|
|
498
498
|
run: (controller) => void openFor(controller)
|
|
499
499
|
});
|
|
500
500
|
var aiBlockMenuItem = (labels) => ({
|
|
501
501
|
id: "ai",
|
|
502
502
|
label: labels.askAi,
|
|
503
|
-
icon:
|
|
503
|
+
icon: chunkCJVJNMOB_cjs.Sparkles,
|
|
504
504
|
available: (state, blockIds) => targetOfBlocks(state, blockIds) !== null,
|
|
505
505
|
run: (controller, blockIds) => {
|
|
506
506
|
const target = targetOfBlocks(controller.editor.state, blockIds);
|
|
@@ -605,12 +605,12 @@ function AiBar({ env, ghost, gone, barId }) {
|
|
|
605
605
|
const [next, setNext] = react.useState("");
|
|
606
606
|
const { phase } = ghost;
|
|
607
607
|
const focusPrimary = () => content.current?.querySelector(`[${PRIMARY}]`)?.focus();
|
|
608
|
-
|
|
608
|
+
chunkZ6LAARPB_cjs.useIsomorphicLayoutEffect(() => {
|
|
609
609
|
const active = content.current?.ownerDocument.activeElement;
|
|
610
610
|
inside.current = active == null || active === active.ownerDocument.body;
|
|
611
611
|
if (inside.current) focusPrimary();
|
|
612
612
|
}, []);
|
|
613
|
-
|
|
613
|
+
chunkZ6LAARPB_cjs.useIsomorphicLayoutEffect(() => {
|
|
614
614
|
const active = content.current?.ownerDocument.activeElement;
|
|
615
615
|
if (inside.current && !content.current?.contains(active ?? null)) focusPrimary();
|
|
616
616
|
}, [phase, ghost.key]);
|
|
@@ -658,21 +658,21 @@ function AiBar({ env, ghost, gone, barId }) {
|
|
|
658
658
|
children: [
|
|
659
659
|
phase === "streaming" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
660
660
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: labels.writing }),
|
|
661
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
661
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkCJVJNMOB_cjs.Button, { ...primary, size: "sm", variant: "outline", onClick: () => stop(editor), children: labels.stop })
|
|
662
662
|
] }),
|
|
663
663
|
phase === "error" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
664
664
|
/* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-sm text-destructive", children: labels.failed }),
|
|
665
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
666
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
665
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkCJVJNMOB_cjs.Button, { ...primary, size: "sm", onClick: () => retry(editor, stream), children: labels.retry }),
|
|
666
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkCJVJNMOB_cjs.Button, { size: "sm", variant: "ghost", onClick: onDiscard, children: labels.discard })
|
|
667
667
|
] }),
|
|
668
668
|
(phase === "done" || phase === "stopped") && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
669
669
|
phase === "stopped" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-muted-foreground", children: labels.stopped }),
|
|
670
670
|
gone && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "w-full text-xs text-muted-foreground", children: labels.targetGone }),
|
|
671
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
672
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
673
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
671
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkCJVJNMOB_cjs.Button, { ...primary, size: "sm", onClick: onAccept, children: labels.accept }),
|
|
672
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkCJVJNMOB_cjs.Button, { size: "sm", variant: "ghost", onClick: onDiscard, children: labels.discard }),
|
|
673
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkCJVJNMOB_cjs.Button, { size: "sm", variant: "ghost", onClick: () => retry(editor, stream), children: labels.retry }),
|
|
674
674
|
/* @__PURE__ */ jsxRuntime.jsx("form", { className: "w-full", onSubmit: onFollowUp, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
675
|
-
|
|
675
|
+
chunkCJVJNMOB_cjs.Input,
|
|
676
676
|
{
|
|
677
677
|
"aria-label": labels.followUp,
|
|
678
678
|
placeholder: labels.followUp,
|
|
@@ -711,7 +711,7 @@ function fillGhost(ghost, markdown) {
|
|
|
711
711
|
if (piece.indent > 0) element.style.setProperty("--quoin-ai-indent", String(piece.indent));
|
|
712
712
|
const region = chunkFK7EBZPX_cjs.primaryRegion(schema, piece);
|
|
713
713
|
if (region === null) element.textContent = chunkFK7EBZPX_cjs.toPlainText([codec.toQuoin(piece)], { schema });
|
|
714
|
-
else
|
|
714
|
+
else chunkZ6LAARPB_cjs.renderInline(element, piece.regions[region], []);
|
|
715
715
|
drawn2.push(element);
|
|
716
716
|
}
|
|
717
717
|
}
|
|
@@ -788,7 +788,7 @@ function clipAside(layer, text, areas, scale) {
|
|
|
788
788
|
function useGhostPlace(controller, layer, block, region) {
|
|
789
789
|
const placeNow = react.useRef(() => {
|
|
790
790
|
});
|
|
791
|
-
|
|
791
|
+
chunkZ6LAARPB_cjs.useIsomorphicLayoutEffect(() => {
|
|
792
792
|
const root = controller.root;
|
|
793
793
|
const element = layer.current;
|
|
794
794
|
const view = root?.ownerDocument.defaultView;
|
|
@@ -827,7 +827,7 @@ function AiGhost({ env, ghost, target, gone, barId }) {
|
|
|
827
827
|
const layer = react.useRef(null);
|
|
828
828
|
const drawn2 = react.useRef(null);
|
|
829
829
|
const place2 = useGhostPlace(env.controller, layer, target.to.block, target.to.region);
|
|
830
|
-
|
|
830
|
+
chunkZ6LAARPB_cjs.useIsomorphicLayoutEffect(() => {
|
|
831
831
|
const element = drawn2.current;
|
|
832
832
|
if (element === null) return;
|
|
833
833
|
element.setAttribute("inert", "");
|
|
@@ -841,7 +841,7 @@ function AiGhost({ env, ghost, target, gone, barId }) {
|
|
|
841
841
|
}
|
|
842
842
|
function useTargetAnchor(controller, target) {
|
|
843
843
|
const latest = react.useRef(target);
|
|
844
|
-
|
|
844
|
+
chunkZ6LAARPB_cjs.useIsomorphicLayoutEffect(() => {
|
|
845
845
|
latest.current = target;
|
|
846
846
|
});
|
|
847
847
|
return react.useMemo(() => {
|
|
@@ -850,8 +850,8 @@ function useTargetAnchor(controller, target) {
|
|
|
850
850
|
return { block: to.block, region: to.region, from: to.offset, to: to.offset };
|
|
851
851
|
};
|
|
852
852
|
return {
|
|
853
|
-
current:
|
|
854
|
-
() =>
|
|
853
|
+
current: chunkCJVJNMOB_cjs.virtualAnchor(
|
|
854
|
+
() => chunkCJVJNMOB_cjs.rangeRect(controller, end()),
|
|
855
855
|
() => controller.registry.get(end().block, end().region)
|
|
856
856
|
)
|
|
857
857
|
};
|
|
@@ -905,7 +905,7 @@ function AiMenu({ env, target, fromSpace }) {
|
|
|
905
905
|
};
|
|
906
906
|
const fieldLabel = asking === "tone" ? labels.tonePrompt : asking === "translate" ? labels.translatePrompt : labels.prompt;
|
|
907
907
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
908
|
-
|
|
908
|
+
chunkCJVJNMOB_cjs.Popover,
|
|
909
909
|
{
|
|
910
910
|
open: true,
|
|
911
911
|
modal: false,
|
|
@@ -913,9 +913,9 @@ function AiMenu({ env, target, fromSpace }) {
|
|
|
913
913
|
if (!next) closeMenu(controller.editor);
|
|
914
914
|
},
|
|
915
915
|
children: [
|
|
916
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
916
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkCJVJNMOB_cjs.PopoverAnchor, { virtualRef: anchor }),
|
|
917
917
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
918
|
-
|
|
918
|
+
chunkCJVJNMOB_cjs.PopoverContent,
|
|
919
919
|
{
|
|
920
920
|
"aria-label": labels.askAi,
|
|
921
921
|
side: "bottom",
|
|
@@ -932,7 +932,7 @@ function AiMenu({ env, target, fromSpace }) {
|
|
|
932
932
|
},
|
|
933
933
|
children: [
|
|
934
934
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
935
|
-
|
|
935
|
+
chunkCJVJNMOB_cjs.Input,
|
|
936
936
|
{
|
|
937
937
|
ref: field,
|
|
938
938
|
role: "combobox",
|
|
@@ -956,7 +956,7 @@ function AiMenu({ env, target, fromSpace }) {
|
|
|
956
956
|
id: optionId(index),
|
|
957
957
|
role: "option",
|
|
958
958
|
"aria-selected": index === current,
|
|
959
|
-
className:
|
|
959
|
+
className: chunkCJVJNMOB_cjs.cn(chunkCJVJNMOB_cjs.MENU_ITEM, index === current && "bg-accent text-accent-foreground"),
|
|
960
960
|
onMouseDown: (event) => event.preventDefault(),
|
|
961
961
|
onClick: () => choose(choice),
|
|
962
962
|
children: choice.label
|
|
@@ -976,15 +976,15 @@ var goneOf = (state) => {
|
|
|
976
976
|
};
|
|
977
977
|
var plainText = (markdown) => chunkFK7EBZPX_cjs.toPlainText(chunkI7X7B3WD_cjs.fromMarkdown(markdown));
|
|
978
978
|
function AiOverlay({ controller, stream, labels }) {
|
|
979
|
-
const { onError } =
|
|
979
|
+
const { onError } = chunkCJVJNMOB_cjs.useQuoin();
|
|
980
980
|
const link = linkOf(controller.editor);
|
|
981
|
-
|
|
981
|
+
chunkZ6LAARPB_cjs.useIsomorphicLayoutEffect(() => {
|
|
982
982
|
link.report = onError;
|
|
983
983
|
link.ended = (phase, output) => controller.announce((phase === "done" ? labels.written : labels.halted)(plainText(output)));
|
|
984
984
|
}, [link, onError, controller, labels]);
|
|
985
|
-
const value =
|
|
986
|
-
const gone =
|
|
987
|
-
const readOnly =
|
|
985
|
+
const value = chunkZ6LAARPB_cjs.useEditorState(controller, aiState);
|
|
986
|
+
const gone = chunkZ6LAARPB_cjs.useEditorState(controller, goneOf);
|
|
987
|
+
const readOnly = chunkZ6LAARPB_cjs.useReadOnly(controller);
|
|
988
988
|
const env = react.useMemo(() => ({ controller, stream, labels }), [controller, stream, labels]);
|
|
989
989
|
const barId = react.useId();
|
|
990
990
|
const showing = value.ghost !== null;
|
|
@@ -1003,7 +1003,7 @@ function AiOverlay({ controller, stream, labels }) {
|
|
|
1003
1003
|
const root = controller.root;
|
|
1004
1004
|
if (!root) return;
|
|
1005
1005
|
let away = true;
|
|
1006
|
-
return
|
|
1006
|
+
return chunkBYYCD54G_cjs.watchCanvasFocus(controller, root.ownerDocument, (inside, node) => {
|
|
1007
1007
|
if (!inside) away = true;
|
|
1008
1008
|
else if (root.contains(node)) {
|
|
1009
1009
|
if (away) noteFocus(controller.editor);
|
package/dist/ai.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { clipBoxes } from './chunk-MQUCCSWG.js';
|
|
3
|
-
import { watchCanvasFocus } from './chunk-
|
|
4
|
-
import { Sparkles, PenLine, useQuoin, Popover, PopoverAnchor, PopoverContent, Input, cn, MENU_ITEM, virtualAnchor, rangeRect, Button, toError } from './chunk-
|
|
5
|
-
import { useIsomorphicLayoutEffect, useEditorState, useReadOnly, renderInline } from './chunk-
|
|
3
|
+
import { watchCanvasFocus } from './chunk-IW4TMECB.js';
|
|
4
|
+
import { Sparkles, PenLine, useQuoin, Popover, PopoverAnchor, PopoverContent, Input, cn, MENU_ITEM, virtualAnchor, rangeRect, Button, toError } from './chunk-ZN5DHNSL.js';
|
|
5
|
+
import { useIsomorphicLayoutEffect, useEditorState, useReadOnly, renderInline } from './chunk-NL5J77VD.js';
|
|
6
6
|
import { mergeLabels } from './chunk-DLQCTGC3.js';
|
|
7
7
|
import './chunk-HDJ653LM.js';
|
|
8
8
|
import { createPluginKey, defineExtension, isCollapsed, regionLength, primaryRegion, getPluginState, Transaction, toPlainText, selectedBlockIds, orderedRange, comparePos, sliceFromSelection, textSelection, deleteRange, pasteAtCaret, toMarkdown, descendantsEnd, freshBlocks, insertBlocksStep, capIndents, endOf } from './chunk-5RBMZGKV.js';
|
package/dist/catalogue.cjs
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
4
|
+
var chunk5YAJZSTI_cjs = require('./chunk-5YAJZSTI.cjs');
|
|
5
|
+
var chunkHOSDMHK6_cjs = require('./chunk-HOSDMHK6.cjs');
|
|
6
|
+
var chunkNFO3W644_cjs = require('./chunk-NFO3W644.cjs');
|
|
7
|
+
var chunkLEICOZYK_cjs = require('./chunk-LEICOZYK.cjs');
|
|
8
8
|
require('./chunk-2SWLD6R5.cjs');
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
require('./chunk-
|
|
9
|
+
var chunkV4WQG7FR_cjs = require('./chunk-V4WQG7FR.cjs');
|
|
10
|
+
var chunk66OPHDFA_cjs = require('./chunk-66OPHDFA.cjs');
|
|
11
|
+
var chunkD6CIIWCU_cjs = require('./chunk-D6CIIWCU.cjs');
|
|
12
|
+
require('./chunk-IKZSCTRF.cjs');
|
|
13
13
|
require('./chunk-YEDPBYO5.cjs');
|
|
14
|
-
var
|
|
15
|
-
var
|
|
14
|
+
var chunk77CNOLHY_cjs = require('./chunk-77CNOLHY.cjs');
|
|
15
|
+
var chunk4PIJC4FD_cjs = require('./chunk-4PIJC4FD.cjs');
|
|
16
16
|
require('./chunk-LM3DHROX.cjs');
|
|
17
|
-
require('./chunk-
|
|
18
|
-
require('./chunk-
|
|
17
|
+
require('./chunk-CJVJNMOB.cjs');
|
|
18
|
+
require('./chunk-Z6LAARPB.cjs');
|
|
19
19
|
require('./chunk-ZWTIRGY5.cjs');
|
|
20
20
|
require('./chunk-A6PGQVTR.cjs');
|
|
21
21
|
require('./chunk-FK7EBZPX.cjs');
|
|
@@ -32,15 +32,15 @@ function createCatalogue(features2) {
|
|
|
32
32
|
|
|
33
33
|
// src/catalogue.ts
|
|
34
34
|
var features = {
|
|
35
|
-
table:
|
|
36
|
-
embed:
|
|
37
|
-
math:
|
|
38
|
-
diagram:
|
|
39
|
-
toc:
|
|
40
|
-
mention:
|
|
41
|
-
emoji:
|
|
42
|
-
find:
|
|
43
|
-
stats:
|
|
35
|
+
table: chunkV4WQG7FR_cjs.tableExtension,
|
|
36
|
+
embed: chunk66OPHDFA_cjs.embedExtension,
|
|
37
|
+
math: chunkD6CIIWCU_cjs.mathExtension,
|
|
38
|
+
diagram: chunk77CNOLHY_cjs.diagramExtension,
|
|
39
|
+
toc: chunk4PIJC4FD_cjs.tocExtension,
|
|
40
|
+
mention: chunk5YAJZSTI_cjs.mentionExtension,
|
|
41
|
+
emoji: chunkHOSDMHK6_cjs.emojiExtension,
|
|
42
|
+
find: chunkNFO3W644_cjs.findExtension,
|
|
43
|
+
stats: chunkLEICOZYK_cjs.statsExtension
|
|
44
44
|
};
|
|
45
45
|
var catalogue = createCatalogue(features);
|
|
46
46
|
|
package/dist/catalogue.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { mentionExtension } from './chunk-
|
|
3
|
-
import { emojiExtension } from './chunk-
|
|
4
|
-
import { findExtension } from './chunk-
|
|
5
|
-
import { statsExtension } from './chunk-
|
|
2
|
+
import { mentionExtension } from './chunk-B4VCDJSA.js';
|
|
3
|
+
import { emojiExtension } from './chunk-MAS4AEF4.js';
|
|
4
|
+
import { findExtension } from './chunk-6BVE46K7.js';
|
|
5
|
+
import { statsExtension } from './chunk-YABGWOAV.js';
|
|
6
6
|
import './chunk-KTUOPDVO.js';
|
|
7
|
-
import { tableExtension } from './chunk-
|
|
8
|
-
import { embedExtension } from './chunk-
|
|
9
|
-
import { mathExtension } from './chunk-
|
|
10
|
-
import './chunk-
|
|
7
|
+
import { tableExtension } from './chunk-2UKI26JA.js';
|
|
8
|
+
import { embedExtension } from './chunk-KQPZTSW3.js';
|
|
9
|
+
import { mathExtension } from './chunk-5X52BAZB.js';
|
|
10
|
+
import './chunk-M77A3EGD.js';
|
|
11
11
|
import './chunk-LSI4RMTY.js';
|
|
12
|
-
import { diagramExtension } from './chunk-
|
|
13
|
-
import { tocExtension } from './chunk-
|
|
12
|
+
import { diagramExtension } from './chunk-EU5IGKMP.js';
|
|
13
|
+
import { tocExtension } from './chunk-J2SVYBTC.js';
|
|
14
14
|
import './chunk-DBOZFNWU.js';
|
|
15
|
-
import './chunk-
|
|
16
|
-
import './chunk-
|
|
15
|
+
import './chunk-ZN5DHNSL.js';
|
|
16
|
+
import './chunk-NL5J77VD.js';
|
|
17
17
|
import './chunk-DLQCTGC3.js';
|
|
18
18
|
import './chunk-HDJ653LM.js';
|
|
19
19
|
import './chunk-5RBMZGKV.js';
|