quoin-editor 0.4.2 → 1.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 +205 -0
- package/MIGRATION.md +233 -0
- package/README.md +78 -716
- package/dist/ai.cjs +1107 -0
- package/dist/ai.cjs.map +1 -0
- package/dist/ai.d.cts +68 -0
- package/dist/ai.d.ts +68 -0
- package/dist/ai.js +1107 -0
- package/dist/ai.js.map +1 -0
- package/dist/catalogue.cjs +59 -0
- package/dist/catalogue.cjs.map +1 -0
- package/dist/catalogue.d.cts +39 -0
- package/dist/catalogue.d.ts +39 -0
- package/dist/catalogue.js +59 -0
- package/dist/catalogue.js.map +1 -0
- package/dist/chunk-2QCRZ7OL.cjs +319 -0
- package/dist/chunk-2QCRZ7OL.cjs.map +1 -0
- package/dist/chunk-2VDK27IF.cjs +6666 -0
- package/dist/chunk-2VDK27IF.cjs.map +1 -0
- package/dist/chunk-47MLKSI2.js +326 -0
- package/dist/chunk-47MLKSI2.js.map +1 -0
- package/dist/chunk-5DNCCN6X.cjs +326 -0
- package/dist/chunk-5DNCCN6X.cjs.map +1 -0
- package/dist/chunk-AEJXZBVR.cjs +1378 -0
- package/dist/chunk-AEJXZBVR.cjs.map +1 -0
- package/dist/chunk-CDELOHDY.cjs +239 -0
- package/dist/chunk-CDELOHDY.cjs.map +1 -0
- package/dist/chunk-E75EHRPA.cjs +175 -0
- package/dist/chunk-E75EHRPA.cjs.map +1 -0
- package/dist/chunk-EL2OEJQC.cjs +562 -0
- package/dist/chunk-EL2OEJQC.cjs.map +1 -0
- package/dist/chunk-GLLXHMH2.cjs +25 -0
- package/dist/chunk-GLLXHMH2.cjs.map +1 -0
- package/dist/chunk-GTTPVJSD.cjs +3286 -0
- package/dist/chunk-GTTPVJSD.cjs.map +1 -0
- package/dist/chunk-HELJTLNC.cjs +100 -0
- package/dist/chunk-HELJTLNC.cjs.map +1 -0
- package/dist/chunk-HJIQT3S5.js +4047 -0
- package/dist/chunk-HJIQT3S5.js.map +1 -0
- package/dist/chunk-HZSSEYJS.cjs +504 -0
- package/dist/chunk-HZSSEYJS.cjs.map +1 -0
- package/dist/chunk-JDPTZUBW.js +184 -0
- package/dist/chunk-JDPTZUBW.js.map +1 -0
- package/dist/chunk-JEIFJHEI.js +14 -0
- package/dist/chunk-JEIFJHEI.js.map +1 -0
- package/dist/chunk-K574XHMM.js +25 -0
- package/dist/chunk-K574XHMM.js.map +1 -0
- package/dist/chunk-KAVU6T6J.js +249 -0
- package/dist/chunk-KAVU6T6J.js.map +1 -0
- package/dist/chunk-KXOBZGQO.js +1378 -0
- package/dist/chunk-KXOBZGQO.js.map +1 -0
- package/dist/chunk-LCQSG4M6.js +3335 -0
- package/dist/chunk-LCQSG4M6.js.map +1 -0
- package/dist/chunk-MQ2XQ4MX.js +100 -0
- package/dist/chunk-MQ2XQ4MX.js.map +1 -0
- package/dist/chunk-ND5XJVK2.cjs +3335 -0
- package/dist/chunk-ND5XJVK2.cjs.map +1 -0
- package/dist/chunk-NNDYYEW5.js +504 -0
- package/dist/chunk-NNDYYEW5.js.map +1 -0
- package/dist/chunk-NOKWNK2O.js +319 -0
- package/dist/chunk-NOKWNK2O.js.map +1 -0
- package/dist/chunk-OMOBMXD5.cjs +57 -0
- package/dist/chunk-OMOBMXD5.cjs.map +1 -0
- package/dist/chunk-PMWCQSQX.js +239 -0
- package/dist/chunk-PMWCQSQX.js.map +1 -0
- package/dist/chunk-POBM7MGR.js +30 -0
- package/dist/chunk-POBM7MGR.js.map +1 -0
- package/dist/chunk-PWUEPDA3.cjs +598 -0
- package/dist/chunk-PWUEPDA3.cjs.map +1 -0
- package/dist/chunk-PZ4GT5JO.cjs +4047 -0
- package/dist/chunk-PZ4GT5JO.cjs.map +1 -0
- package/dist/chunk-QAW5NH6S.cjs +14 -0
- package/dist/chunk-QAW5NH6S.cjs.map +1 -0
- package/dist/chunk-RDG4VI6E.cjs +706 -0
- package/dist/chunk-RDG4VI6E.cjs.map +1 -0
- package/dist/chunk-RUDWMKYX.js +562 -0
- package/dist/chunk-RUDWMKYX.js.map +1 -0
- package/dist/chunk-SG37UBDO.js +706 -0
- package/dist/chunk-SG37UBDO.js.map +1 -0
- package/dist/chunk-SLVDAHHK.cjs +30 -0
- package/dist/chunk-SLVDAHHK.cjs.map +1 -0
- package/dist/chunk-T7SW6OWV.js +598 -0
- package/dist/chunk-T7SW6OWV.js.map +1 -0
- package/dist/chunk-TC4DARU2.js +175 -0
- package/dist/chunk-TC4DARU2.js.map +1 -0
- package/dist/chunk-WQHV3MRE.cjs +184 -0
- package/dist/chunk-WQHV3MRE.cjs.map +1 -0
- package/dist/chunk-WXMYDXPN.cjs +33 -0
- package/dist/chunk-WXMYDXPN.cjs.map +1 -0
- package/dist/chunk-XEBWC7ZL.js +57 -0
- package/dist/chunk-XEBWC7ZL.js.map +1 -0
- package/dist/chunk-ZC24WB7Y.cjs +249 -0
- package/dist/chunk-ZC24WB7Y.cjs.map +1 -0
- package/dist/chunk-ZGZEJCRV.js +6666 -0
- package/dist/chunk-ZGZEJCRV.js.map +1 -0
- package/dist/chunk-ZHOSSWUM.js +33 -0
- package/dist/chunk-ZHOSSWUM.js.map +1 -0
- package/dist/chunk-ZS2WA357.js +3286 -0
- package/dist/chunk-ZS2WA357.js.map +1 -0
- package/dist/comments.cjs +993 -0
- package/dist/comments.cjs.map +1 -0
- package/dist/comments.d.cts +88 -0
- package/dist/comments.d.ts +88 -0
- package/dist/comments.js +993 -0
- package/dist/comments.js.map +1 -0
- package/dist/diff.cjs +343 -0
- package/dist/diff.cjs.map +1 -0
- package/dist/diff.d.cts +67 -0
- package/dist/diff.d.ts +67 -0
- package/dist/diff.js +343 -0
- package/dist/diff.js.map +1 -0
- package/dist/docx.cjs +854 -0
- package/dist/docx.cjs.map +1 -0
- package/dist/docx.d.cts +27 -0
- package/dist/docx.d.ts +27 -0
- package/dist/docx.js +854 -0
- package/dist/docx.js.map +1 -0
- package/dist/embed-provider-jXwlRmnp.d.cts +19 -0
- package/dist/embed-provider-jXwlRmnp.d.ts +19 -0
- package/dist/embed.cjs +17 -0
- package/dist/embed.cjs.map +1 -0
- package/dist/embed.d.cts +56 -0
- package/dist/embed.d.ts +56 -0
- package/dist/embed.js +17 -0
- package/dist/embed.js.map +1 -0
- package/dist/emoji-data.cjs +1968 -0
- package/dist/emoji-data.cjs.map +1 -0
- package/dist/emoji-data.js +1968 -0
- package/dist/emoji-data.js.map +1 -0
- package/dist/emoji-extension-C12gDPm6.d.cts +46 -0
- package/dist/emoji-extension-DpQ43HjE.d.ts +46 -0
- package/dist/emoji.cjs +18 -0
- package/dist/emoji.cjs.map +1 -0
- package/dist/emoji.d.cts +31 -0
- package/dist/emoji.d.ts +31 -0
- package/dist/emoji.js +18 -0
- package/dist/emoji.js.map +1 -0
- package/dist/engine.cjs +119 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.cts +144 -0
- package/dist/engine.d.ts +144 -0
- package/dist/engine.js +119 -0
- package/dist/engine.js.map +1 -0
- package/dist/find-extension-DKqLLSuP.d.ts +41 -0
- package/dist/find-extension-_sbZEt3R.d.cts +41 -0
- package/dist/find.cjs +20 -0
- package/dist/find.cjs.map +1 -0
- package/dist/find.d.cts +44 -0
- package/dist/find.d.ts +44 -0
- package/dist/find.js +20 -0
- package/dist/find.js.map +1 -0
- package/dist/headless.cjs +151 -518
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +102 -69
- package/dist/headless.d.ts +102 -69
- package/dist/headless.js +149 -467
- package/dist/headless.js.map +1 -1
- package/dist/index-CgD72kHB.d.cts +65 -0
- package/dist/index-DNYbTK3w.d.ts +65 -0
- package/dist/index.cjs +183 -2753
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +345 -253
- package/dist/index.d.ts +345 -253
- package/dist/index.js +168 -2696
- package/dist/index.js.map +1 -1
- package/dist/math.cjs +18 -0
- package/dist/math.cjs.map +1 -0
- package/dist/math.d.cts +49 -0
- package/dist/math.d.ts +49 -0
- package/dist/math.js +18 -0
- package/dist/math.js.map +1 -0
- package/dist/mention.cjs +18 -0
- package/dist/mention.cjs.map +1 -0
- package/dist/mention.d.cts +50 -0
- package/dist/mention.d.ts +50 -0
- package/dist/mention.js +18 -0
- package/dist/mention.js.map +1 -0
- package/dist/presence.cjs +565 -0
- package/dist/presence.cjs.map +1 -0
- package/dist/presence.d.cts +30 -0
- package/dist/presence.d.ts +30 -0
- package/dist/presence.js +565 -0
- package/dist/presence.js.map +1 -0
- package/dist/registry-CWBOSAHm.d.cts +121 -0
- package/dist/registry-CWBOSAHm.d.ts +121 -0
- package/dist/stats-extension-Bly88uio.d.ts +40 -0
- package/dist/stats-extension-CJPijvUb.d.cts +40 -0
- package/dist/stats.cjs +19 -0
- package/dist/stats.cjs.map +1 -0
- package/dist/stats.d.cts +19 -0
- package/dist/stats.d.ts +19 -0
- package/dist/stats.js +19 -0
- package/dist/stats.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/table-extension-BTLxFy2S.d.cts +57 -0
- package/dist/table-extension-Dzf4k6hI.d.ts +57 -0
- package/dist/table.cjs +50 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +57 -0
- package/dist/table.d.ts +57 -0
- package/dist/table.js +50 -0
- package/dist/table.js.map +1 -0
- package/dist/to-html-C5t7xyaM.d.ts +21 -0
- package/dist/to-html-CNCITCln.d.cts +21 -0
- package/dist/toc-extension-B3-s6h2x.d.cts +25 -0
- package/dist/toc-extension-DWm5alVj.d.ts +25 -0
- package/dist/toc.cjs +18 -0
- package/dist/toc.cjs.map +1 -0
- package/dist/toc.d.cts +24 -0
- package/dist/toc.d.ts +24 -0
- package/dist/toc.js +18 -0
- package/dist/toc.js.map +1 -0
- package/dist/types-Cz7XJLaK.d.cts +366 -0
- package/dist/types-DULc8wwg.d.cts +120 -0
- package/dist/types-DULc8wwg.d.ts +120 -0
- package/dist/types-a1CRBlEh.d.ts +366 -0
- package/dist/view-controller-DSHw0ltQ.d.cts +118 -0
- package/dist/view-controller-UbpzblGl.d.ts +118 -0
- package/dist/view.cjs +23 -0
- package/dist/view.cjs.map +1 -0
- package/dist/view.d.cts +40 -0
- package/dist/view.d.ts +40 -0
- package/dist/view.js +23 -0
- package/dist/view.js.map +1 -0
- package/dist/yjs.cjs +1026 -0
- package/dist/yjs.cjs.map +1 -0
- package/dist/yjs.d.cts +22 -0
- package/dist/yjs.d.ts +22 -0
- package/dist/yjs.js +1026 -0
- package/dist/yjs.js.map +1 -0
- package/package.json +217 -18
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Changelog
|
|
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).
|
|
4
|
+
|
|
5
|
+
## 1.0.0 (2026-10-23)
|
|
6
|
+
|
|
7
|
+
Quoin 1.0 replaces the 0.4 canvas, a card per block with a textarea opened to edit it, by an editor of its own: an engine with no dependency, a view that types into the page in place, and a canvas UI on top. A 0.4 document loads as it is, and the 0.4 `BlockCanvas` props keep their names. [MIGRATION.md](MIGRATION.md) lists what a 0.4 host changes.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `quoin-editor/engine`: the editing engine alone, with no React, no DOM and no `'use client'` banner. A document model of blocks with inline regions, steps, transactions and history, commands (`editor.commands`, `editor.chain()`), input and paste rules, and a markdown codec that reads a 0.4 document as it is and writes back only the blocks that were edited. Also `toMarkdown`, `fromMarkdown`, `toHTML`, `toPlainText`, `fromHTML`, `diffBlocks` and `countDoc`.
|
|
11
|
+
- `quoin-editor/headless`: the engine and its editable view, with no canvas UI: `useQuoinEditor`, `EditorView`, `EditableRegion` and `ViewController`. Each text region is typed into in place, with IME composition, native caret movement, selections across blocks and the clipboard handled by the view.
|
|
12
|
+
- `quoin-editor`: `BlockCanvas` rebuilt on the engine. Paragraphs, headings, bullet, numbered and task lists, quotes, callouts, toggles and dividers are typed in place, with live formatting from markdown shortcuts (`**bold**`, `# `, `- `, `> ` and the rest). Code, image and file blocks are drawn and edited inside the canvas. The canvas adds a slash menu, a bubble toolbar for formatting and links, a link popover, a gutter with a drag handle and a "+" that adds a block below, a block menu (Turn into, Color, Duplicate, Copy, Copy link to block, Move up, Move down, Delete) reached from the handle, a right click on the gutter or Mod+/, drag and drop with a drop line, text and background colours, text alignment, a toolbar for touch screens and a keyboard shortcuts dialog (Mod+Shift+/).
|
|
13
|
+
- `BlockCanvas` props `extensions`, `highlight`, `autoFocus` (off by default), `placeholder`, `labels` and `blockLink`, and a ref whose `QuoinEditorHandle` gives the `editor`, the `controller`, `focus()` and `toMarkdown()`.
|
|
14
|
+
- `defineExtension` for extensions of your own: blocks, marks, atoms, commands, keymaps, input and paste rules, plugins, decorations, transaction hooks, and a `ui` field (`ExtensionUI`) that adds slash items, bubble items, block menu items, block renderers and overlays to the canvas.
|
|
15
|
+
- `QuoinLabels`, `englishLabels`, `resolveLabels` and `useLabels`: every string the canvas shows or speaks, English by default and replaced key by key through `labels`.
|
|
16
|
+
- `CodeHighlighter` and `CodeToken`, for the `highlight` prop that colours code blocks.
|
|
17
|
+
- The optional `indent` field of `QuoinBlock`, the nesting depth of list items and of the blocks inside a toggle.
|
|
18
|
+
- The engine reads and writes the `DIVIDER`, `TOGGLE`, `TABLE`, `EMBED`, `BOOKMARK`, `MATH` and `TOC` block types, beside the five 0.4 types.
|
|
19
|
+
- Opt-in features, each an entry of its own that a page loads only when it imports it:
|
|
20
|
+
- `quoin-editor/table`: tables typed cell by cell, with row and column menus, column widths and cell ranges.
|
|
21
|
+
- `quoin-editor/embed`: embeds framed from YouTube, Vimeo, Loom, Figma, CodePen, Spotify and SoundCloud, and bookmark cards, with an `unfurl` option of your own.
|
|
22
|
+
- `quoin-editor/math`: math blocks and formulas in a line, drawn by your `renderMath`, such as KaTeX.
|
|
23
|
+
- `quoin-editor/toc`: a table of contents block that follows the headings.
|
|
24
|
+
- `quoin-editor/mention`: `@` mentions, found by your own `search`.
|
|
25
|
+
- `quoin-editor/emoji`: a `:` menu and an emoji picker, whose data loads the first time one opens.
|
|
26
|
+
- `quoin-editor/find`: find and replace.
|
|
27
|
+
- `quoin-editor/stats`: word and character counts, a limit, focus mode and typography rules.
|
|
28
|
+
- `quoin-editor/catalogue`: `catalogue()`, every feature above at once.
|
|
29
|
+
- Platform entries:
|
|
30
|
+
- `quoin-editor/yjs`: real-time co-editing through a Yjs document and your own provider, with an undo of your own changes only. `yjs` is an optional peer dependency.
|
|
31
|
+
- `quoin-editor/presence`: collaborators' selections, carets and avatars.
|
|
32
|
+
- `quoin-editor/comments`: comment threads anchored to text, kept in your store.
|
|
33
|
+
- `quoin-editor/ai`: answers of your model streamed into the document; Quoin never calls a model itself.
|
|
34
|
+
- `quoin-editor/view`: `QuoinView`, read-only output with no hooks, for a server component.
|
|
35
|
+
- `quoin-editor/diff`: `QuoinDiff`, the changes between two versions of a document, block by block and word by word.
|
|
36
|
+
- `quoin-editor/docx`: `toDocx`, a Word document written in a browser, a worker or Node.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
- Text blocks are typed in place instead of in a textarea that a click opens. Undo and redo work across the whole document, and the caret moves from block to block with the arrows.
|
|
40
|
+
- A new text block holds one line: a paragraph, a heading, a list item, a task or a quote. A 0.4 `MARKDOWN` block holding several paragraphs is kept as it is until it is first edited; it is then split into one block per line, the first keeping its id and the others taking ids derived from it (`<id>-2`, `<id>-3` and on), each written back as the canonical markdown of its line. A table in it becomes a `TABLE` block and an image alone on its line an `IMAGE` block.
|
|
41
|
+
- Nested list items keep their depth in `indent` rather than in leading spaces of their markdown.
|
|
42
|
+
- An image's `caption` and a callout's `body` are inline markdown, typed in place like a paragraph.
|
|
43
|
+
- The core blocks write new optional keys into `data` once a writer uses what they hold: `align`, `color` and `background` on `MARKDOWN`, `icon` on `CALLOUT`, `url`, `align` and `widthPercent` on `IMAGE`, and `mime` on `FILE`, beside `indent` on the block itself. Every 0.4 key keeps its name and meaning.
|
|
44
|
+
- `QuoinConfig`, which `useQuoin()` returns, gains `highlight` and the required `controller` and `labels`. `QuoinProvider` fills them in, so only a `QuoinConfig` built by hand, such as a test double, needs them.
|
|
45
|
+
- A structural copy of the document coming back is recognised as an echo within the last 256 changes the canvas reported, instead of 64.
|
|
46
|
+
- `onDelete` also reports whole blocks removed by typing over a selected block or pasting over selected blocks. A line that Backspace or Delete merges into its neighbour is still not reported.
|
|
47
|
+
- `defineBlock` takes any icon component that accepts a class name and `aria-hidden`, not only a lucide one. A custom block shows its `preview` at rest; its `editor` opens on a double click, its Edit button or Enter on the selected block, and Escape closes it. A definition for `DIVIDER` draws everywhere, and one for `MARKDOWN`, `CALLOUT` or `TOGGLE` only on a read-only canvas, which warns in development when the canvas is editable.
|
|
48
|
+
- `quoin-editor/headless` exports the view and the engine, and `formatShortcut` is exported from the main entry only. The main entry re-exports everything in `quoin-editor/headless`.
|
|
49
|
+
- A canvas given a collaboration extension is uncontrolled while the shared document is connected: it ignores any `blocks` array it did not emit, and warns in development.
|
|
50
|
+
- The stylesheet declares `color-scheme` in light and dark, and reads new tokens: `--quoin-selection`, the 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`. Every 0.4 token keeps its name.
|
|
51
|
+
- English callout tone names are written in lower case in the tone chip and its menu.
|
|
52
|
+
- One build with code splitting serves every entry, in ESM and CommonJS, and each entry is held to a gzip budget in CI.
|
|
53
|
+
- The tarball is about 1 MB packed, under 5 MB unpacked: the sourcemaps leave out `sourcesContent` and name their sources relative to the map. They still map each line to its file and line under `src/`. Every entry is exported, and the package publishes `dist`, the README, the license, this changelog and MIGRATION.md.
|
|
54
|
+
- `@radix-ui/react-context-menu` is a new dependency, for the block menu on a right click.
|
|
55
|
+
|
|
56
|
+
### Removed
|
|
57
|
+
- The 0.4 textarea helpers, from both `quoin-editor` and `quoin-editor/headless`: `applyEdit` and `Edit`, `lineAt`, `toggleLinePrefix` and `LinePrefix`, `continueList`, `indentList`, `wrapSelection`, `linkPaste` and `WrapKind`, `insertCommand` and `InsertableCommand`, `shouldOpenPalette`, `caretRect` and `CaretRect`, and `useMarkdownKeymap`. Engine commands do their work; MIGRATION.md maps each one.
|
|
58
|
+
- `TEXT_COMMANDS` and `TextCommand`: the slash menu's items come from the canvas and from extensions.
|
|
59
|
+
- `useBlockList`, with `BlockList` and `UndoToken`: the editor's own commands and history cover it.
|
|
60
|
+
- `useAutoGrow`: nothing is drawn in a textarea any more.
|
|
61
|
+
- `formatShortcut` from `quoin-editor/headless` (it stays in `quoin-editor`).
|
|
62
|
+
- The dependencies `@dnd-kit/core`, `@dnd-kit/modifiers`, `@dnd-kit/sortable` and `@dnd-kit/utilities` (the canvas drags blocks itself), `cmdk` (the slash menu is the canvas's own), `tailwind-merge`, `class-variance-authority`, `@radix-ui/react-label` and `@radix-ui/react-select`.
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
0.4.1 fixed F-01, F-03, F-04, F-05, F-06, F-07, F-08, F-10, F-16, F-18, F-19, F-24, F-25, F-27, F-39 and F-40, listed under 0.4.1 below. 1.0 closes these; findings it only partly addresses are under Known gaps:
|
|
66
|
+
- F-02 [ARCH-1, CQ-12]: the card and textarea model is gone. Text is typed in place on one engine, with one history for the whole document and the caret moving across blocks.
|
|
67
|
+
- F-09 [CQ-4, PERF-1]: a keystroke re-renders the one block it changes, since each block shell is memoised on its block (`src/view/react/BlockShell.tsx`). `e2e/tests/headless/performance.spec.ts` holds typing in a 1,000 block document to a p95 of 16 ms on a development machine, and to a ratio against a small document in CI.
|
|
68
|
+
- F-13 [PKG-4]: the README no longer calls the headless entry safe for a server component; it carries a `'use client'` banner, as the main entry does. `quoin-editor/engine`, `/view`, `/diff`, `/docx` and `/yjs` carry none. `npm run check:directives` checks the banner of each entry, and the consumer check renders the view, the diff and the Word export in plain Node.
|
|
69
|
+
- F-15 [TEST-1]: CI runs Playwright on Chromium, Firefox and WebKit, typechecks the packed package and resolves each of its entries on React 18 and 19, renders the server entries in plain Node, and the release workflow runs the consumer check before publishing.
|
|
70
|
+
- F-17 [A11Y-1]: Mod+Shift+Arrow and the block menu move a block one place, and every move, by keyboard or by drag, is announced with its new position (`e2e/tests/canvas/shortcuts.spec.ts`, `drag.spec.ts`).
|
|
71
|
+
- F-21 [A11Y-8]: the slash menu is a combobox with `aria-activedescendant` from the moment it opens, and its result count is announced.
|
|
72
|
+
- F-22 [A11Y-9]: the slash menu opens from the text typed, not from a key press, so a virtual keyboard or dictation opens it too.
|
|
73
|
+
- F-23 [A11Y-10]: an upload in flight no longer disables the focused file input, and one status region, mounted from the start, announces its progress.
|
|
74
|
+
- F-26 [CQ-11]: `BlockCanvas.tsx` and `BlockCard.tsx` are gone; the canvas file wires the engine, the view and the UI in 159 lines.
|
|
75
|
+
- F-28 [CQ-14]: one suggestion menu serves the slash menu and every other menu typed in the text, and the shortcuts dialog reads one table, which `scripts/keyboard-docs.test.ts` checks against the keymaps: the dialog lists only keys the code binds, and every key the canvas's own keymaps bind.
|
|
76
|
+
- F-29 [CQ-15, PKG-5]: the `badge`, `label` and `select` primitives are gone, with `@radix-ui/react-label` and `@radix-ui/react-select`.
|
|
77
|
+
- F-31 [CQ-17]: `toggleLinePrefix` is gone with the textarea helpers.
|
|
78
|
+
- F-32 [CQ-18, PKG-3]: one build with code splitting, so `quoin-editor` and `quoin-editor/headless` share each export, which `src/index.test.ts` checks against the built files.
|
|
79
|
+
- F-34 [A11Y-7]: the editor is one tab stop, held by the block the selection is in. The gutter's controls are left out of the tab order, Mod+/ opens the block menu from the text, and the arrows move between blocks.
|
|
80
|
+
- F-35 [A11Y-12]: every block menu item is named by the text it shows (`src/gutter/BlockMenu.test.tsx`).
|
|
81
|
+
- F-36 [A11Y-14]: the canvas no longer takes focus on mount unless `autoFocus` is set.
|
|
82
|
+
- F-37 [A11Y-16]: nothing animates or scrolls smoothly when the reader asks for reduced motion (`e2e/tests/canvas/reader-settings.spec.ts`).
|
|
83
|
+
- F-38 [A11Y-17]: on a touch screen the gutter shows one 44 px handle beside the block being edited, and a toolbar above the keyboard carries the editing tools.
|
|
84
|
+
- F-41 [A11Y-21]: the shortcuts dialog lists the canvas's keyboard model, the keyboard docs page adds the keys of the opt-in features, and the Accessibility docs page names what each test suite checks and what it does not.
|
|
85
|
+
|
|
86
|
+
### Security
|
|
87
|
+
- `toDocx` links a hyperlink only when its address is an absolute `http`, `https`, `mailto` or `tel` one; any other address, relative ones included, is written as plain text, since Word reads a relative address against the file's folder and a `\\host\share` address as a network share.
|
|
88
|
+
- `QuoinView` and `QuoinDiff` write the colour of a `highlight` token into a `style` attribute only when it is one colour value (a hex colour, a keyword, a colour function or a custom property), so a token cannot add a declaration of its own.
|
|
89
|
+
|
|
90
|
+
### Known gaps
|
|
91
|
+
Findings of #73 that 1.0 addresses only in part. Each part still open stays open in [#73](https://github.com/AmineSeboui/quoin-editor/issues/73); none has a follow-up issue yet.
|
|
92
|
+
- F-11 [API-1]. Fixed: extensions add commands, keymaps, input and paste rules, plugins and canvas UI through `defineExtension`, and `BlockCanvas` takes a ref. Remains: a block's `preview` still cannot write its data, and `BlockEditorProps` gained no `blockId`, `index` or `onRemove`.
|
|
93
|
+
- F-12 [PKG-2]. Fixed: dnd-kit, cmdk, tailwind-merge and class-variance-authority are gone, features beyond the core blocks are entries of their own, and `npm run check:size` holds every entry to a budget. Remains: the build turns tree shaking off, since Rollup would strip the `'use client'` banners, and leaves it to the host's bundler.
|
|
94
|
+
- F-14 [API-4, A11Y-15]. Fixed: `QuoinView` renders a document on a server, with real headings, lists and links, the canvas draws text formatted instead of raw markdown, and `toHTML` and `QuoinView` draw an image as a `figure` with `width`, `height`, `loading="lazy"` and its caption, and a file as a link. Remains: an empty alt still marks an image decorative silently, with no choice to make it so on purpose.
|
|
95
|
+
- F-20 [A11Y-6]. Fixed: a callout's tone is in its accessible name ("Callout, warning"), and `toHTML` writes a callout as `<aside role="note" data-tone>`, so colour is no longer the only cue. Remains: the light `--quoin-warning` keeps its 0.4 value, so the warning callout's rule keeps the contrast the review measured, 2.84:1 against its wash.
|
|
96
|
+
- F-30 [CQ-16]. Fixed: ESLint with typescript-eslint and react-hooks runs in CI, with a coverage gate of 90 percent of lines on the engine. Remains: no coverage threshold on the canvas or the hooks.
|
|
97
|
+
- F-33 [API-3]. Fixed: `icon` accepts any component. Remains: `initialData` stays optional when the data has required fields, and a definition has no `parse` or `migrate`.
|
|
98
|
+
- F-42 [A11Y-22]. Fixed: `color-scheme` is declared in light and dark. Remains: `--quoin-input` keeps its 0.4 values, which the review measured at 1.26:1 in light and 1.47:1 in dark for field borders.
|
|
99
|
+
|
|
100
|
+
## 0.4.2 (2026-10-15)
|
|
101
|
+
|
|
102
|
+
A metadata-only patch. The code is the same as 0.4.1 and nothing in the public API changes.
|
|
103
|
+
|
|
104
|
+
### Changed
|
|
105
|
+
- The repository moved to `AmineSeboui/quoin-editor`, so the `repository`, `homepage` and `bugs` fields of the package, and the README badges and links, point at the new repository and its documentation site, `https://amineseboui.github.io/quoin-editor`.
|
|
106
|
+
- This version is published with npm provenance from the new repository. The provenance of earlier versions still names the former repository, which no longer exists.
|
|
107
|
+
|
|
108
|
+
## 0.4.1 (2026-10-12)
|
|
109
|
+
|
|
110
|
+
A patch release that fixes the data-loss, accessibility and React 18 problems found in a review of 0.4.0. Nothing is removed from the public API.
|
|
111
|
+
|
|
112
|
+
### Fixed
|
|
113
|
+
- `useAutosave` saves the newest edit when its component unmounts, and when the page is hidden or closed (`pagehide`, or the tab turning hidden), instead of dropping up to one debounce interval of typing (F-01).
|
|
114
|
+
- `useAutosave` keeps one save in flight. Edits made while it runs are sent in a single follow-up save with the newest payload, so an older payload can no longer land last (F-05).
|
|
115
|
+
- A save that fails after `useAutosave` has unmounted no longer schedules retries nobody can stop (F-06).
|
|
116
|
+
- A save that fails for good no longer retries forever: retries stop after `maxRetries` (default 8) or when `isRetryable` says no, `status` becomes `failed` and `error` holds the reason. A success clears the error and any pending backoff (F-05).
|
|
117
|
+
- An image block records its `storageKey` as soon as the upload finishes, before the picture is measured. With overlapping uploads the last pick wins, and a late measurement never overwrites a newer image. Replacing an image drops the previous image's width and height, so a picture that cannot be measured is never stored with another picture's size (F-07).
|
|
118
|
+
- `UploadField` waits for an async `onUploaded` and reports its rejection through `onError`, once. The "Image attached" text no longer carries a check mark that screen readers read out (F-25).
|
|
119
|
+
- The markdown surface and the empty-document starter ignore keys that belong to an IME composition. Committing a candidate with Enter no longer continues a list, and Backspace, Tab and `/` inside a composition no longer exit a list, indent or open the palette (F-08).
|
|
120
|
+
- On macOS the shortcuts use Command, so Ctrl+B, Ctrl+E and Ctrl+K keep their system text meanings. Elsewhere they use Ctrl, as before (F-27).
|
|
121
|
+
- A lagging host that echoes the canvas back no longer rewinds it. An array the canvas emitted is recognised however late it comes back, so the limit of 64 unacknowledged emissions is gone, and a structural copy of one of the last 64 counts as its echo. Stores that clone the document on the way through, such as Redux, Zustand or a server round trip, keep every keystroke while they are within 64 edits (F-10).
|
|
122
|
+
- Keyboard focus is visible on buttons, inputs, textareas, selects, the dialog close button, the markdown surface and the starter: a solid 2 px outline in `--quoin-ring`, which reaches 3:1 against the page and popover in light and dark, and which Windows forced colours mode keeps (F-04, F-18).
|
|
123
|
+
- A highlighted row in a menu, the slash palette or a select carries an inset bar as well as a background, and the Delete label and shortcut hints reach 4.5:1 on it (F-19, F-40).
|
|
124
|
+
- React 18 works: `Button`, `Input` and `Textarea` forward refs, so the refs that focus return, the drag handle and the menu triggers rely on are no longer dropped, and layout effects no longer run during server rendering. CI now checks the packed package on React 18 and 19 (F-03).
|
|
125
|
+
- Inserting a block focuses and scrolls to it inside its own canvas, so two canvases on one page no longer interfere (F-16).
|
|
126
|
+
- The last inserted block no longer reopens, and takes focus, when `readOnly` is toggled or the host undoes a change (F-24).
|
|
127
|
+
- Inserting a block from the slash palette closes the source block's editor, so one editor is open at a time (F-39).
|
|
128
|
+
|
|
129
|
+
### Changed
|
|
130
|
+
- `useAutosave` gains the `'failed'` status, the `maxRetries`, `isRetryable` and `flushOnHide` options, and `error` and `flush()` on its result. When `save` resolves `{ ok: false }`, that value is the `error`.
|
|
131
|
+
- `UploadFieldProps.onUploaded` may return a promise.
|
|
132
|
+
- `useAutoGrow` is typed to return `React.RefObject<T>`, so the ref it returns is accepted by React 18 and 19 types.
|
|
133
|
+
- Default tokens: light `--quoin-ring` is darker, dark `--quoin-ring` is lighter, and light `--quoin-destructive` and `--quoin-muted-foreground` are slightly darker. A host that maps `--quoin-ring` to a pale colour should give it one that reaches 3:1.
|
|
134
|
+
- A host that does not echo `onChange` and restores a copy of a state from its last 64 edits now sees it ignored as a late echo. Remount the canvas with a React `key` to force the reset.
|
|
135
|
+
- The package description, keywords and author are updated.
|
|
136
|
+
|
|
137
|
+
### Docs
|
|
138
|
+
- The README no longer warns that a store which clones the document loses text.
|
|
139
|
+
- New Accessibility and Stability sections, which name the gaps that remain in 0.4.
|
|
140
|
+
- `--quoin-ring` is documented.
|
|
141
|
+
|
|
142
|
+
## 0.4.0 (2026-10-12)
|
|
143
|
+
|
|
144
|
+
### Breaking
|
|
145
|
+
- Each callout tone draws in a colour of its own. New tokens: `--quoin-info`, `--quoin-success` and `--quoin-warning`.
|
|
146
|
+
|
|
147
|
+
## 0.3.0 (2026-10-11)
|
|
148
|
+
|
|
149
|
+
Answers an integrator's report from 0.1.1.
|
|
150
|
+
|
|
151
|
+
### Added
|
|
152
|
+
- The core block data shapes are exported (`MarkdownBlockData`, `CalloutBlockData`, `CodeBlockData`, `ImageBlockData`, `FileBlockData`, `CoreBlockData`) and documented, with a test that reads the keys back from each editor.
|
|
153
|
+
- A block definition may omit `editor` for content that is never edited in the canvas; omitting both `editor` and `preview` is a type error.
|
|
154
|
+
- A read-only canvas warns once per type, in the developer console, when a definition has no `preview`.
|
|
155
|
+
|
|
156
|
+
### Changed
|
|
157
|
+
- `useAutosave`'s `save` may return anything; only an explicit `{ ok: false }` (or a rejection) counts as a failure.
|
|
158
|
+
- `defineBlock` defaults its data type to `Record<string, unknown>`.
|
|
159
|
+
- A block whose type nobody registered no longer offers an Edit button.
|
|
160
|
+
|
|
161
|
+
### Docs
|
|
162
|
+
- Tailwind v3, v4 and no-Tailwind setups, and the install footprint.
|
|
163
|
+
|
|
164
|
+
## 0.2.0 (2026-10-10)
|
|
165
|
+
|
|
166
|
+
### Breaking
|
|
167
|
+
- Every CSS custom property is prefixed `--quoin-`.
|
|
168
|
+
- All of the package's CSS ships under one top-level `quoin` cascade layer.
|
|
169
|
+
- Tailwind's theme is imported by reference, so its tokens no longer land on the host's `:root`.
|
|
170
|
+
- Utilities are built from the package's components only, not from the README prose.
|
|
171
|
+
|
|
172
|
+
### Added
|
|
173
|
+
- `onDelete` tells the host which block a deletion removed and the index it held.
|
|
174
|
+
- `UploadField` and `FilePickerButton` are exported for custom blocks.
|
|
175
|
+
|
|
176
|
+
### Fixed
|
|
177
|
+
- Builds under Tailwind v3 hosts.
|
|
178
|
+
|
|
179
|
+
## 0.1.1 (2026-10-09)
|
|
180
|
+
|
|
181
|
+
### Fixed
|
|
182
|
+
- `package.json` is exposed through the `exports` map, so tooling can read the manifest.
|
|
183
|
+
- The headless surface is re-exported from the main entry.
|
|
184
|
+
- The caret lands where a click fell instead of at the top of the block.
|
|
185
|
+
- Every emission a lagging host has not echoed yet is recognised, so delayed `onChange` handling no longer loses typing.
|
|
186
|
+
- A block type nothing registered is drawn and reported through `onError`.
|
|
187
|
+
- `useBlockList` caps the removals it keeps for undo.
|
|
188
|
+
|
|
189
|
+
### Changed
|
|
190
|
+
- The markdown commit the keymap is keyed on is memoised.
|
|
191
|
+
|
|
192
|
+
### Docs
|
|
193
|
+
- A custom block reaching `upload` through `useQuoin`, and a host token collision with its escape hatch.
|
|
194
|
+
|
|
195
|
+
## 0.1.0 (2026-10-09)
|
|
196
|
+
|
|
197
|
+
First release of Quoin, published on npm as `quoin-editor` (npm rejects the bare name `quoin` as too similar to an existing package).
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
- `BlockCanvas`: a controlled React block editor (`blocks` and `onChange`) with drag to reorder, block inserters, a block actions menu and a read-only mode.
|
|
201
|
+
- Five core block types: markdown, callout, code, image and file, plus an open registry through `defineBlock` with plain React `editor` and `preview` components.
|
|
202
|
+
- A markdown surface with keyboard shortcuts, list continuation, indentation and a slash palette.
|
|
203
|
+
- Host-owned uploads through an injected `upload` function and `resolveAssetUrl`.
|
|
204
|
+
- `quoin-editor/headless`: the markdown text transforms with no UI dependency.
|
|
205
|
+
- Scoped styles with a class-based dark mode.
|
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Migrating from 0.4 to 1.0
|
|
2
|
+
|
|
3
|
+
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
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install quoin-editor@^1
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
React 18.2 or 19 and TypeScript 5.4 or later, as in 0.4.
|
|
10
|
+
|
|
11
|
+
## Data
|
|
12
|
+
|
|
13
|
+
### No migration is needed
|
|
14
|
+
|
|
15
|
+
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
|
+
|
|
17
|
+
### Multi-paragraph markdown blocks split on their first edit
|
|
18
|
+
|
|
19
|
+
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
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import type { QuoinBlock } from 'quoin-editor';
|
|
23
|
+
|
|
24
|
+
// Before the first edit, exactly as stored in 0.4.
|
|
25
|
+
export const stored: QuoinBlock[] = [{ id: 'a', type: 'MARKDOWN', data: { markdown: '# Notes\n\nFirst point\n\n- one\n - nested' } }];
|
|
26
|
+
|
|
27
|
+
// What onChange reports once the heading has been edited.
|
|
28
|
+
export const edited: QuoinBlock[] = [
|
|
29
|
+
{ id: 'a', type: 'MARKDOWN', data: { markdown: '# Notes!' } },
|
|
30
|
+
{ id: 'a-2', type: 'MARKDOWN', data: { markdown: 'First point' } },
|
|
31
|
+
{ id: 'a-3', type: 'MARKDOWN', data: { markdown: '- one' } },
|
|
32
|
+
{ id: 'a-4', type: 'MARKDOWN', data: { markdown: '- nested' }, indent: 1 },
|
|
33
|
+
];
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### The `indent` field
|
|
37
|
+
|
|
38
|
+
`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
|
+
|
|
40
|
+
### New optional keys
|
|
41
|
+
|
|
42
|
+
1.0 writes keys that 0.4 never wrote, each only once a writer uses what it holds:
|
|
43
|
+
|
|
44
|
+
| Type | New keys in `data` |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `MARKDOWN` | `align` (`'left'`, `'center'`, `'right'` or `'justify'`), `color` and `background` (palette colour names) |
|
|
47
|
+
| `CALLOUT` | `icon`, the emoji the writer picked |
|
|
48
|
+
| `IMAGE` | `url` (an image embedded by address), `align` and `widthPercent` |
|
|
49
|
+
| `FILE` | `mime`, the type the uploaded file declares |
|
|
50
|
+
|
|
51
|
+
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
|
+
|
|
53
|
+
### Captions and callout bodies are inline markdown
|
|
54
|
+
|
|
55
|
+
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
|
+
|
|
57
|
+
### Named HTML entities
|
|
58
|
+
|
|
59
|
+
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
|
+
|
|
61
|
+
### Inline images in imported markdown
|
|
62
|
+
|
|
63
|
+
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
|
+
|
|
65
|
+
## API
|
|
66
|
+
|
|
67
|
+
### `BlockCanvas`
|
|
68
|
+
|
|
69
|
+
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
|
+
|
|
71
|
+
| Prop | What it does |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `extensions` | Opt-in features and extensions of your own, read when the canvas mounts. Remount with a new `key` to change them. |
|
|
74
|
+
| `highlight` | Colours code blocks with your highlighter. Keep it stable. |
|
|
75
|
+
| `autoFocus` | Focuses the canvas on mount. Off by default. |
|
|
76
|
+
| `placeholder` | The text of an empty paragraph that holds the caret. |
|
|
77
|
+
| `labels` | Every string the canvas shows or speaks, English by default. Keep the object stable. |
|
|
78
|
+
| `blockLink` | The address "Copy link to block" copies, by default the page's address with `#block-<id>`. |
|
|
79
|
+
| `ref` | A `QuoinEditorHandle` with the `editor`, the `controller`, `focus()` and `toMarkdown()`. |
|
|
80
|
+
|
|
81
|
+
Two behaviours changed:
|
|
82
|
+
|
|
83
|
+
- **`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
|
+
- **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
|
+
|
|
86
|
+
### `defineBlock`
|
|
87
|
+
|
|
88
|
+
`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
|
+
|
|
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`:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { BlockCanvas, EditableRegion, type BlockRenderer, type ExtensionUI, type QuoinBlock } from 'quoin-editor';
|
|
94
|
+
import { defineExtension } from 'quoin-editor/engine';
|
|
95
|
+
|
|
96
|
+
const Callout: BlockRenderer = ({ block }) => (
|
|
97
|
+
<div className="my-callout" data-tone={String(block.attrs.tone)}>
|
|
98
|
+
<EditableRegion block={block.id} region="body" label="Callout" />
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const ui: ExtensionUI = { renderers: { CALLOUT: Callout } };
|
|
103
|
+
const extensions = [defineExtension({ name: 'my-callout', ui })];
|
|
104
|
+
|
|
105
|
+
export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (next: QuoinBlock[]) => void }) {
|
|
106
|
+
return <BlockCanvas blocks={blocks} onChange={onChange} extensions={extensions} />;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `useQuoin`
|
|
111
|
+
|
|
112
|
+
`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
|
+
|
|
114
|
+
### Entries
|
|
115
|
+
|
|
116
|
+
- `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
|
+
- `formatShortcut` is exported from `quoin-editor` only. Import it from there instead of `quoin-editor/headless`.
|
|
118
|
+
- `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
|
+
|
|
120
|
+
### Removed helpers
|
|
121
|
+
|
|
122
|
+
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
|
+
|
|
124
|
+
| 0.4 | 1.0 | Notes |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| `applyEdit`, `Edit` | `insertText` | The editor applies every edit itself; insert text at the selection with the command. |
|
|
127
|
+
| `lineAt` | Nothing | Each text block holds one line. |
|
|
128
|
+
| `toggleLinePrefix`, `LinePrefix` | `setBlockType` | `setBlockType('MARKDOWN', { kind: 'heading', level: 2 })`, `{ kind: 'bullet' }`, `{ kind: 'ordered' }` and so on. |
|
|
129
|
+
| `continueList` | `splitBlock` | Enter continues a list on its own. |
|
|
130
|
+
| `indentList` | `indent`, `outdent` | Tab and Shift+Tab call them. |
|
|
131
|
+
| `wrapSelection`, `WrapKind` | `toggleMark`, `setLink` | `toggleMark('bold')`, `'italic'` or `'code'`; `setLink` for `'link'`. |
|
|
132
|
+
| `linkPaste` | `setLink`, `pasteLinkOverSelection` | Pasting an address over a selection links it on its own. |
|
|
133
|
+
| `insertCommand`, `InsertableCommand` | `insertBlockAfter`, `setBlockType` | `setBlockType` for a heading or a list, `insertBlockAfter` for a code block or a divider; a table needs `quoin-editor/table`. |
|
|
134
|
+
| `shouldOpenPalette` | Nothing | The slash menu opens from the text typed, virtual keyboards included. |
|
|
135
|
+
| `caretRect`, `CaretRect` | Nothing | The caret is the browser's own: read `window.getSelection()`. |
|
|
136
|
+
| `useMarkdownKeymap` | `defineExtension` | Give an extension a `keymap` of commands. |
|
|
137
|
+
| `TEXT_COMMANDS`, `TextCommand` | `defineExtension` | Add slash items through an extension's `ui.slashItems`. |
|
|
138
|
+
| `useBlockList`, `BlockList`, `UndoToken` | Nothing | The host holds the array itself and hands it to `blocks`; the rows below map each member. |
|
|
139
|
+
| `BlockList.insertAt` | `insertBlockAfter` | Inserts after the selected block. |
|
|
140
|
+
| `BlockList.update` | `setAttrs` | Patches the selected block; to replace whole blocks, hand `blocks` a new array. |
|
|
141
|
+
| `BlockList.remove` | `deleteBlocks` | Deletes the selected blocks. |
|
|
142
|
+
| `BlockList.undo` | `undo` | The editor's history undoes a deletion, with no token. |
|
|
143
|
+
| `BlockList.move` | `moveBlocksUp`, `moveBlocksDown` | |
|
|
144
|
+
| `BlockList.reorder` | `moveBlocksTo` | `moveBlocksTo(ids, toIndex, indent)`. |
|
|
145
|
+
| `BlockList.reset` | Nothing | Hand `blocks` a new array, or call `editor.setBlocks(blocks)`. |
|
|
146
|
+
| `useAutoGrow` | Nothing | Nothing is drawn in a textarea any more. |
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { useRef } from 'react';
|
|
150
|
+
import { BlockCanvas, type QuoinBlock, type QuoinEditorHandle } from 'quoin-editor';
|
|
151
|
+
|
|
152
|
+
export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (next: QuoinBlock[]) => void }) {
|
|
153
|
+
const handle = useRef<QuoinEditorHandle>(null);
|
|
154
|
+
const heading = () => handle.current?.editor.commands.setBlockType('MARKDOWN', { kind: 'heading', level: 2 });
|
|
155
|
+
return (
|
|
156
|
+
<>
|
|
157
|
+
<button type="button" onClick={heading}>
|
|
158
|
+
Heading
|
|
159
|
+
</button>
|
|
160
|
+
<BlockCanvas ref={handle} blocks={blocks} onChange={onChange} />
|
|
161
|
+
</>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Command names of the design spec
|
|
167
|
+
|
|
168
|
+
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
|
+
|
|
170
|
+
### Labels
|
|
171
|
+
|
|
172
|
+
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
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { BlockCanvas, type QuoinBlock, type QuoinLabels } from 'quoin-editor';
|
|
176
|
+
|
|
177
|
+
const labels: Partial<QuoinLabels> = {
|
|
178
|
+
calloutTones: { info: 'Info', tip: 'Tip', success: 'Success', warning: 'Warning', danger: 'Danger' },
|
|
179
|
+
blockDeleted: (n) => (n === 1 ? 'Block removed' : `${n} blocks removed`),
|
|
180
|
+
undoHint: (shortcut) => `${shortcut} brings it back`,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (next: QuoinBlock[]) => void }) {
|
|
184
|
+
return <BlockCanvas blocks={blocks} onChange={onChange} labels={labels} />;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Features that now need an import
|
|
189
|
+
|
|
190
|
+
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
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { BlockCanvas, type QuoinBlock } from 'quoin-editor';
|
|
194
|
+
import { tableExtension } from 'quoin-editor/table';
|
|
195
|
+
|
|
196
|
+
const extensions = [tableExtension()];
|
|
197
|
+
|
|
198
|
+
export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (next: QuoinBlock[]) => void }) {
|
|
199
|
+
return <BlockCanvas blocks={blocks} onChange={onChange} extensions={extensions} />;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`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
|
+
|
|
205
|
+
## Styling
|
|
206
|
+
|
|
207
|
+
- Every 0.4 `--quoin-` token keeps its name, and the stylesheet still sits in the one `quoin` cascade layer.
|
|
208
|
+
- 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
|
+
- `color-scheme` is declared for light and dark.
|
|
210
|
+
- 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
|
+
|
|
212
|
+
## React 18
|
|
213
|
+
|
|
214
|
+
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
|
+
|
|
216
|
+
## The package
|
|
217
|
+
|
|
218
|
+
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
|
+
|
|
220
|
+
## Checklist
|
|
221
|
+
|
|
222
|
+
- [ ] Install `quoin-editor@^1`.
|
|
223
|
+
- [ ] Keep `indent` and the new optional keys when you copy, validate or store blocks, and let a strict schema accept them.
|
|
224
|
+
- [ ] Expect a 0.4 block of several lines to be split into one block per line, with derived ids, on its first edit.
|
|
225
|
+
- [ ] Check captions and callout bodies that hold `*`, `_`, `` ` ``, `[` or `&`, which now read as inline markdown.
|
|
226
|
+
- [ ] Replace named entities other than the 23 listed, and move inline images onto lines of their own, in markdown you import.
|
|
227
|
+
- [ ] Replace each removed helper with the command in the table above.
|
|
228
|
+
- [ ] Import `formatShortcut` from `quoin-editor`.
|
|
229
|
+
- [ ] Move a `MARKDOWN`, `CALLOUT` or `TOGGLE` definition used on an editable canvas to an extension's `ui.renderers`.
|
|
230
|
+
- [ ] Wait until `blocks` no longer hold a block before restoring it from `onDelete`.
|
|
231
|
+
- [ ] Pass `tableExtension()` (or `catalogue()`) if your documents hold tables.
|
|
232
|
+
- [ ] Map the new tokens if you map Quoin's tokens to your own, and drop CSS that targeted 0.4 internals.
|
|
233
|
+
- [ ] Treat a canvas with a collaboration extension as uncontrolled.
|