edytor 1.0.0 → 3.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/README.md +26 -69
- package/dist/block/block.svelte.d.ts +6 -4
- package/dist/block/block.svelte.js +12 -8
- package/dist/block/inlineBlock.svelte.d.ts +6 -2
- package/dist/block/inlineBlock.svelte.js +7 -2
- package/dist/clipboard/htmlFlow.d.ts +20 -7
- package/dist/clipboard/htmlFlow.js +23 -15
- package/dist/clipboard/serializeClipboardFragment.d.ts +37 -4
- package/dist/clipboard/serializeClipboardFragment.js +6 -4
- package/dist/clipboard/textBlocks.d.ts +8 -0
- package/dist/clipboard/textBlocks.js +6 -2
- package/dist/cloudflare/room/admission.js +3 -3
- package/dist/cloudflare/room/context.d.ts +7 -1
- package/dist/cloudflare/routeDocumentSocket.d.ts +17 -0
- package/dist/cloudflare/routeDocumentSocket.js +53 -0
- package/dist/collaboration/awarenessSelection.d.ts +22 -1
- package/dist/collaboration/awarenessSelection.js +42 -0
- package/dist/collaboration/comments/client.d.ts +1 -1
- package/dist/collaboration/comments/client.js +1 -1
- package/dist/collaboration/remoteSelection.js +3 -3
- package/dist/components/Block.svelte +3 -1
- package/dist/components/Edytor.svelte +38 -20
- package/dist/components/Edytor.svelte.d.ts +9 -4
- package/dist/components/InlineBlock.svelte +1 -1
- package/dist/crdt/admission.d.ts +5 -10
- package/dist/crdt/admission.js +5 -11
- package/dist/crdt/data.d.ts +10 -0
- package/dist/crdt/data.js +20 -2
- package/dist/crdt/doc/funnel.d.ts +7 -6
- package/dist/crdt/doc/funnel.js +10 -9
- package/dist/crdt/doc/gate.d.ts +0 -17
- package/dist/crdt/doc/gate.js +2 -41
- package/dist/crdt/doc/history.js +2 -2
- package/dist/crdt/doc/reads.d.ts +7 -0
- package/dist/crdt/doc/reads.js +11 -1
- package/dist/crdt/document.d.ts +13 -14
- package/dist/crdt/document.js +17 -19
- package/dist/crdt/edytor-doc.d.ts +22 -4
- package/dist/crdt/edytor-doc.js +5 -8
- package/dist/crdt/index.d.ts +9 -3
- package/dist/crdt/index.js +2 -2
- package/dist/crdt/operations.d.ts +1 -1
- package/dist/crdt/operations.js +1 -0
- package/dist/crdt/props.d.ts +32 -0
- package/dist/crdt/props.js +172 -0
- package/dist/crdt/protocol.d.ts +2 -2
- package/dist/crdt/protocol.js +2 -2
- package/dist/crdt/providers/index.d.ts +1 -1
- package/dist/crdt/providers/room.d.ts +10 -9
- package/dist/crdt/providers/room.js +13 -10
- package/dist/crdt/providers/websocket.d.ts +1 -1
- package/dist/crdt/providers/websocket.js +28 -29
- package/dist/crdt/semantics.js +3 -1
- package/dist/dnd/autoScroll.d.ts +15 -7
- package/dist/dnd/autoScroll.js +10 -15
- package/dist/{plugins/blockHandles → dnd}/dropIndicator.d.ts +2 -2
- package/dist/{plugins/blockHandles → dnd}/dropIndicator.js +2 -5
- package/dist/dnd/external.d.ts +6 -0
- package/dist/dnd/external.js +17 -0
- package/dist/{plugins/blockHandles → dnd}/geometry.d.ts +4 -2
- package/dist/{plugins/blockHandles → dnd}/geometry.js +3 -4
- package/dist/dnd/placement.d.ts +200 -0
- package/dist/dnd/placement.js +426 -0
- package/dist/dnd/pragmatic.d.ts +3 -1
- package/dist/dnd/pragmatic.js +12 -2
- package/dist/dnd/vendor/pragmatic/UPSTREAM.md +51 -18
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.d.ts +0 -2
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.js +4 -22
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter-types.d.ts +26 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.d.ts +30 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.js +203 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-items.js +13 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-types.js +9 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/is-an-available-type.js +19 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/honey-pot-fix/make-honey-pot-fix.js +4 -16
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/internal-types.d.ts +1 -8
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-drop-target.js +5 -6
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-monitor.js +2 -3
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.d.ts +1 -1
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.js +6 -24
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.d.ts +2 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.js +5 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.d.ts +3 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.js +12 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/native-types.d.ts +12 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window-in-safari.js +10 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window.js +55 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.js +8 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/internal-types.d.ts +0 -4
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/get-scroll-by.js +16 -72
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/try-scroll.js +1 -9
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/configuration.js +4 -29
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/get-scroll-change.js +2 -3
- package/dist/edytor.svelte.d.ts +19 -9
- package/dist/edytor.svelte.js +28 -33
- package/dist/events/beforeInputCommands.d.ts +2 -2
- package/dist/events/beforeInputCommands.js +8 -4
- package/dist/events/onBeforeInput.js +1 -1
- package/dist/events/onDrop.js +25 -10
- package/dist/events/onInput.d.ts +0 -12
- package/dist/events/onInput.js +1 -67
- package/dist/events/onPaste.d.ts +2 -1
- package/dist/events/onPaste.js +8 -3
- package/dist/export/forms.d.ts +627 -0
- package/dist/export/forms.js +749 -0
- package/dist/export/index.d.ts +170 -0
- package/dist/export/index.js +161 -0
- package/dist/export/markdown.d.ts +41 -0
- package/dist/export/markdown.js +585 -0
- package/dist/export/markdownWriter.d.ts +9 -0
- package/dist/export/markdownWriter.js +133 -0
- package/dist/export/mdx.d.ts +15 -0
- package/dist/export/mdx.js +33 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/kinds.js +1 -1
- package/dist/labels.d.ts +2 -0
- package/dist/labels.js +2 -1
- package/dist/plugins/anchored.svelte.d.ts +21 -25
- package/dist/plugins/anchored.svelte.js +23 -39
- package/dist/plugins/arrowMove/arrowMove.d.ts +1 -1
- package/dist/plugins/arrowMove/arrowMove.js +1 -1
- package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +2 -122
- package/dist/plugins/blockHandles/BlockHandleController.svelte.js +48 -414
- package/dist/plugins/blockHandles/BlockHandles.svelte +14 -32
- package/dist/plugins/blockHandles/blockHandlesPlugin.js +2 -1
- package/dist/plugins/blockHandles/dragPreview.d.ts +26 -7
- package/dist/plugins/blockHandles/dragPreview.js +33 -0
- package/dist/plugins/blockMenu/BlockMenuController.svelte.d.ts +5 -0
- package/dist/plugins/blockMenu/BlockMenuController.svelte.js +7 -2
- package/dist/plugins/blockMenu/blockMenuPlugin.js +5 -50
- package/dist/plugins/chrome.d.ts +27 -5
- package/dist/plugins/chrome.js +24 -9
- package/dist/plugins/code/CodeHeader.svelte +5 -3
- package/dist/plugins/code/CodePlugin.svelte +15 -12
- package/dist/plugins/code/header.svelte.js +7 -11
- package/dist/plugins/code/languageMenu.svelte.d.ts +5 -2
- package/dist/plugins/code/languageMenu.svelte.js +13 -20
- package/dist/plugins/code/languages.d.ts +0 -3
- package/dist/plugins/code/languages.js +0 -2
- package/dist/plugins/columns/ColumnResize.svelte +3 -2
- package/dist/plugins/columns/ColumnsPlugin.svelte +8 -21
- package/dist/plugins/columns/gaps.d.ts +7 -5
- package/dist/plugins/columns/gaps.js +11 -6
- package/dist/plugins/columns/resize.svelte.d.ts +5 -6
- package/dist/plugins/columns/resize.svelte.js +12 -14
- package/dist/plugins/comments/CommentsController.svelte.d.ts +6 -0
- package/dist/plugins/comments/CommentsController.svelte.js +20 -0
- package/dist/plugins/equation/EquationPlugin.svelte +13 -30
- package/dist/plugins/equation/EquationPlugin.svelte.d.ts +1 -0
- package/dist/plugins/equation/equation.css +7 -0
- package/dist/plugins/equation/equation.svelte.d.ts +7 -15
- package/dist/plugins/equation/equation.svelte.js +13 -50
- package/dist/plugins/equation/tex.d.ts +17 -0
- package/dist/plugins/equation/tex.js +42 -0
- package/dist/plugins/fileDrop.d.ts +43 -0
- package/dist/plugins/fileDrop.js +122 -0
- package/dist/plugins/find/FindController.svelte.d.ts +5 -0
- package/dist/plugins/find/FindController.svelte.js +14 -14
- package/dist/plugins/find/findPlugin.js +1 -15
- package/dist/plugins/image/ImageChrome.svelte +8 -2
- package/dist/plugins/image/ImagePlugin.svelte +56 -84
- package/dist/plugins/image/ImagePlugin.svelte.d.ts +13 -1
- package/dist/plugins/image/controls.svelte.js +16 -2
- package/dist/plugins/image/image.css +17 -0
- package/dist/plugins/image/toolbar.svelte.d.ts +5 -4
- package/dist/plugins/image/toolbar.svelte.js +21 -31
- package/dist/plugins/index.d.ts +4 -5
- package/dist/plugins/index.js +0 -1
- package/dist/plugins/list.svelte.d.ts +9 -0
- package/dist/plugins/list.svelte.js +18 -0
- package/dist/plugins/markdownShortcuts.js +5 -5
- package/dist/plugins/marquee/marqueePlugin.js +0 -4
- package/dist/plugins/media/AudioPlugin.svelte +13 -1
- package/dist/plugins/media/AudioPlugin.svelte.d.ts +11 -1
- package/dist/plugins/media/BookmarkPlugin.svelte +6 -97
- package/dist/plugins/media/EmbedPlugin.svelte +5 -90
- package/dist/plugins/media/EmptySlot.svelte +36 -20
- package/dist/plugins/media/EmptySlot.svelte.d.ts +6 -15
- package/dist/plugins/media/FilePlugin.svelte +12 -20
- package/dist/plugins/media/FilePlugin.svelte.d.ts +9 -0
- package/dist/plugins/media/MediaEmpty.svelte +2 -55
- package/dist/plugins/media/MediaEmpty.svelte.d.ts +18 -14
- package/dist/plugins/media/SourceBlock.svelte +178 -52
- package/dist/plugins/media/SourceBlock.svelte.d.ts +13 -6
- package/dist/plugins/media/UploadPreview.svelte +32 -0
- package/dist/plugins/media/UploadPreview.svelte.d.ts +11 -0
- package/dist/plugins/media/VideoPlugin.svelte +13 -1
- package/dist/plugins/media/VideoPlugin.svelte.d.ts +11 -1
- package/dist/plugins/media/empty.svelte.d.ts +13 -4
- package/dist/plugins/media/empty.svelte.js +32 -26
- package/dist/plugins/media/media.d.ts +9 -63
- package/dist/plugins/media/media.js +1 -194
- package/dist/plugins/media/sources.d.ts +60 -0
- package/dist/plugins/media/sources.js +165 -0
- package/dist/plugins/media/upload.css +23 -0
- package/dist/plugins/media/urlPaste.svelte.d.ts +2 -1
- package/dist/plugins/media/urlPaste.svelte.js +12 -21
- package/dist/plugins/mention/MentionPlugin.svelte +50 -38
- package/dist/plugins/mention/MentionPlugin.svelte.d.ts +11 -2
- package/dist/plugins/mention/mention.css +6 -0
- package/dist/plugins/page/PagePlugin.svelte +2 -9
- package/dist/plugins/page/page.js +1 -1
- package/dist/plugins/pageLink/PageLinkPlugin.svelte +126 -28
- package/dist/plugins/pageLink/PageLinkPlugin.svelte.d.ts +13 -1
- package/dist/plugins/pageLink/pageLink.css +6 -0
- package/dist/plugins/richtext/RichTextPlugin.svelte +99 -204
- package/dist/plugins/richtext/RichTextPlugin.svelte.d.ts +1 -0
- package/dist/plugins/richtext/body.js +1 -1
- package/dist/plugins/richtext/calloutIcons.svelte.d.ts +2 -2
- package/dist/plugins/richtext/calloutIcons.svelte.js +5 -7
- package/dist/plugins/richtext/richTextOperations.d.ts +1 -19
- package/dist/plugins/richtext/richTextOperations.js +3 -49
- package/dist/plugins/richtext/richtext.css +6 -0
- package/dist/plugins/slashMenu/SlashMenuController.svelte.d.ts +2 -0
- package/dist/plugins/slashMenu/SlashMenuController.svelte.js +7 -0
- package/dist/plugins/slashMenu/slashMenuPlugin.js +5 -14
- package/dist/plugins/table/TableChrome.svelte +2 -3
- package/dist/plugins/table/TablePlugin.svelte +20 -54
- package/dist/plugins/table/chrome.svelte.d.ts +5 -2
- package/dist/plugins/table/chrome.svelte.js +33 -56
- package/dist/plugins/table/table.js +3 -3
- package/dist/plugins/toc/TocPlugin.svelte +2 -4
- package/dist/plugins/toolbar/ToolbarController.svelte.d.ts +7 -0
- package/dist/plugins/toolbar/ToolbarController.svelte.js +20 -13
- package/dist/plugins/toolbar/toolbarPlugin.js +19 -53
- package/dist/plugins/triggers/TriggerController.svelte.d.ts +7 -2
- package/dist/plugins/triggers/TriggerController.svelte.js +11 -4
- package/dist/plugins/triggers/place.js +1 -2
- package/dist/plugins/triggers/triggers.d.ts +6 -1
- package/dist/plugins/triggers/triggers.js +12 -13
- package/dist/plugins/uploads.svelte.d.ts +48 -32
- package/dist/plugins/uploads.svelte.js +119 -43
- package/dist/plugins.d.ts +66 -14
- package/dist/plugins.js +0 -24
- package/dist/selection/replaceSelection.js +2 -2
- package/dist/selection/selection.svelte.d.ts +4 -10
- package/dist/selection/selection.svelte.js +7 -11
- package/dist/session/attempt.d.ts +16 -6
- package/dist/session/attempt.js +1 -0
- package/dist/session/bindings.js +35 -32
- package/dist/session/commands.d.ts +7 -11
- package/dist/session/commands.js +15 -19
- package/dist/session/history.js +1 -0
- package/dist/session/inputRules.d.ts +6 -3
- package/dist/session/inputRules.js +10 -6
- package/dist/session/moves.d.ts +33 -0
- package/dist/session/moves.js +71 -1
- package/dist/session/navigation.d.ts +7 -0
- package/dist/session/navigation.js +20 -11
- package/dist/session/props.d.ts +28 -0
- package/dist/session/props.js +65 -0
- package/dist/session/suggestions.svelte.d.ts +0 -11
- package/dist/session/suggestions.svelte.js +15 -3
- package/dist/session/uploads.d.ts +29 -0
- package/dist/session/uploads.js +10 -0
- package/dist/surface/lines.js +1 -3
- package/dist/surface/observer.svelte.d.ts +17 -1
- package/dist/surface/observer.svelte.js +72 -17
- package/dist/surface/overlay.d.ts +0 -14
- package/dist/surface/overlay.js +0 -25
- package/dist/surface/projector.svelte.js +1 -1
- package/dist/text/text.svelte.js +3 -13
- package/dist/themes/notion.css +9 -0
- package/dist/utils/sanitize.d.ts +38 -0
- package/dist/utils/sanitize.js +82 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +5 -0
- package/package.json +18 -1
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/max-z-index.js +0 -3
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/supports-popover.js +0 -16
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/is-axis-allowed.js +0 -3
- package/dist/plugins/code/LanguageMenu.svelte +0 -25
- package/dist/plugins/code/LanguageMenu.svelte.d.ts +0 -9
- package/dist/plugins/equation/EquationEditor.svelte +0 -27
- package/dist/plugins/equation/EquationEditor.svelte.d.ts +0 -9
- package/dist/plugins/image/ImageToolbarHost.svelte +0 -37
- package/dist/plugins/image/ImageToolbarHost.svelte.d.ts +0 -9
- package/dist/plugins/richtext/CalloutIconPicker.svelte +0 -26
- package/dist/plugins/richtext/CalloutIconPicker.svelte.d.ts +0 -9
- /package/dist/{plugins/columns → dnd}/stacking.d.ts +0 -0
- /package/dist/{plugins/columns → dnd}/stacking.js +0 -0
package/README.md
CHANGED
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
|
|
18
18
|
Edytor aims to be for Svelte what Slate.js is for React: a heavily customizable editor with an API to build any kind of collaborative rich text editor.
|
|
19
19
|
|
|
20
|
-
> **
|
|
20
|
+
> **3.0.** Edytor `3.0.0` is on npm as `edytor`: its API follows semantic versioning, and every change is listed in the changelog. It has no production track record yet: read the [limitations](https://edytor.dev/docs/reference/limitations) before you build on it. Issues and PRs are welcome: when you report a bug, include the document's JSON value.
|
|
21
21
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
|
-
- **Notion-style editing out of the box.** `<Edytor />` alone is a rich text editor
|
|
24
|
+
- **Notion-style editing out of the box.** `<Edytor />` alone is a rich text editor: headings, toggle headings, lists, to-dos, toggles, callouts, quotes, dividers, ten marks and Notion's hotkeys. Add the Notion theme and the plugins you want: block handles and a block menu (block colours; drag one block, or every block the selection covers), a slash menu, a selection toolbar, markdown shortcuts (`createMarkdownShortcutsPlugin()`), code blocks and images (`createImagePlugin({ upload })`). The package draws no chrome: each handle and menu is your snippet, or the ready-made registry item.
|
|
25
25
|
- **Pages and a table of contents, opt-in.** `createPagePlugin` links a block to another document (a page per room, opened and named by your app), and `createTocPlugin()` lists the headings live ([Page](https://edytor.dev/docs/plugins/page), [Table of contents](https://edytor.dev/docs/plugins/table-of-contents)).
|
|
26
26
|
- **Columns, opt-in.** List `createColumnsPlugin()` for Notion's multi-column layouts: drag a block to the edge of another to put them side by side, or pick "2 columns" to "5 columns"; drag the gap to resize. Layouts converge across collaborators and the room ([Columns](https://edytor.dev/docs/plugins/columns)).
|
|
27
27
|
- **Tables, opt-in.** List `createTablePlugin()` for Notion's simple table: cells of text, <kbd>Tab</kbd> between them, rows and columns added, moved and deleted from the table's edges and grips, resizable columns, header row and column, HTML tables pasted and copied. Concurrent row and column edits converge on every replica ([Table](https://edytor.dev/docs/plugins/table)).
|
|
@@ -29,17 +29,18 @@ Edytor aims to be for Svelte what Slate.js is for React: a heavily customizable
|
|
|
29
29
|
- **Comments, opt-in.** `createCommentsPlugin` adds Notion's comment threads: select text, press Comment, and the thread shows in a sidebar beside it with replies, Resolve and Re-open. The room keeps the threads beside the document and sends each change live; the anchor follows the text through edits and is not copied ([Comments](https://edytor.dev/docs/plugins/comments)).
|
|
30
30
|
- **Your markup.** Blocks, marks and inline atoms render through your Svelte snippets; the slash menu, toolbar, block menu and handles take a snippet and keep their behavior.
|
|
31
31
|
- **Your language.** Every word the package shows or says, slash keywords included, comes from one labels dictionary the view takes (`<Edytor labels>`); your chrome's words are your markup's; English by default ([Localization](https://edytor.dev/docs/customization/localization)).
|
|
32
|
-
- **Synced properties.** `block.data`, `atom.data` and `edytor.data`
|
|
32
|
+
- **Synced properties.** `block.data`, `atom.data` and `edytor.data` are plain Svelte state at any depth (`block.data.meta.author = 'Ada'`, `block.data.tags.push('x')`, `delete block.data.draft`), each write an undoable operation; each property and each array item syncs on its own, so concurrent edits of different properties, or of different items of one array, merge.
|
|
33
33
|
- **AI suggestions.** `edytor.suggestions.add(position, content)` proposes text, paragraphs, lists, to-dos or images after, before or inside a block, at the end of its text, or in place of the selection; it streams in, shows only on your screen, and becomes one undo step when accepted ([Suggestions](https://edytor.dev/docs/editor/suggestions)).
|
|
34
34
|
- **Plugins that can veto anything.** Every command is prepared before it writes; plugins see the command and each planned step and can refuse or replace it.
|
|
35
35
|
- **Real-time collaboration.** One `EdytorDocument` shared by any number of views, or none (headless). Presence cursors, identity-preserving moves, splits and merges, and undo that only takes back your own edits.
|
|
36
36
|
- **Offline first.** A local IndexedDB copy and cross-tab sync; offline edits survive reloads and reach the server on reconnect.
|
|
37
37
|
- **A Cloudflare Durable Object room.** `edytor/cloudflare` stores in SQLite, acknowledges after the write, binds identity to the socket and hibernates.
|
|
38
|
+
- **Import and export.** `edytor/export` turns a document into HTML or Markdown and back (`toHTML`, `toMarkdown`, `fromHTML`, `textToBlocks`), by each kind's own forms, in the browser, Node or a Worker; HTML import is sanitized ([Import and export](https://edytor.dev/docs/editor/import-export)).
|
|
38
39
|
- **Headless and Worker-safe.** `edytor/crdt/edytor` runs in Node and Workers without Svelte.
|
|
39
40
|
|
|
40
41
|
## Install
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
Edytor `3.0.0` is on npm under `latest`:
|
|
43
44
|
|
|
44
45
|
```bash
|
|
45
46
|
pnpm add edytor
|
|
@@ -49,83 +50,39 @@ pnpm add edytor
|
|
|
49
50
|
|
|
50
51
|
## Quick start
|
|
51
52
|
|
|
52
|
-
The package draws no menu, toolbar or block handle: each is a snippet you pass its plugin. Without them the editor works but shows none. The demo's ready-made, Notion-style ones are one copy away: copy [`src/routes/chrome`](https://github.com/beynar/edytor/tree/master/src/routes/chrome) into your app, make its imports name the package, and pass its plugins ([quick start](https://edytor.dev/docs/getting-started/quick-start#the-ready-made-menus-and-handles)):
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npx degit beynar/edytor/src/routes/chrome src/lib/chrome
|
|
56
|
-
sed -i.bak 's#\$lib/index\.js#edytor#' src/lib/chrome/*.ts src/lib/chrome/*.svelte && rm src/lib/chrome/*.bak
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Take the folder of the version you install, by its tag (`beynar/edytor/src/routes/chrome#v1.0.0`): without one you get `master`'s, which follows the next release.
|
|
60
|
-
|
|
61
53
|
```svelte
|
|
62
54
|
<script lang="ts">
|
|
63
|
-
import { Edytor } from 'edytor';
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
blockHandlesPlugin,
|
|
69
|
-
blockMenuPlugin,
|
|
70
|
-
codePlugin
|
|
71
|
-
} from '$lib/chrome/plugins';
|
|
55
|
+
import { Edytor, createCodePlugin, type JSONDoc } from 'edytor';
|
|
56
|
+
|
|
57
|
+
let value = $state<JSONDoc>({
|
|
58
|
+
children: [{ type: 'paragraph', content: [{ text: 'Hello, World!' }] }]
|
|
59
|
+
});
|
|
72
60
|
</script>
|
|
73
61
|
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/>
|
|
78
|
-
</div>
|
|
62
|
+
<Edytor {value} plugins={[createCodePlugin()]} onChange={(next) => (value = next)} />
|
|
63
|
+
|
|
64
|
+
<p>{value.children?.length ?? 0} blocks</p>
|
|
79
65
|
```
|
|
80
66
|
|
|
81
|
-
|
|
67
|
+
`<Edytor>` renders on the server and hydrates, so it works in any route. It includes rich text; each other block kind, block moves from the keyboard (`createArrowMovePlugin()`), and file drops (a media plugin given an `upload`) is a plugin you add, here code blocks; nesting blocks (<kbd>Tab</kbd>, drops inside a block) is the `allowNesting` option, off by default. `onChange` hands you the document after every change ([quick start](https://edytor.dev/docs/getting-started/quick-start)).
|
|
68
|
+
|
|
69
|
+
The package draws no menu, toolbar or block handle: each is a snippet you pass its plugin ([menus and handles](https://edytor.dev/docs/customization/menus)). Notion-style ones, in shadcn-svelte, are a registry item you add to your app (`npx shadcn-svelte@latest add https://edytor.dev/r/edytor-chrome.json`), as plugins for your `<Edytor>` ([ready-made menus](https://edytor.dev/docs/customization/ready-made-menus)).
|
|
70
|
+
|
|
71
|
+
The fastest way to a full editor is the starter, a [shadcn-svelte](https://www.shadcn-svelte.com) registry item: every plugin, the ready-made menus and handles, comments, and a page with a cover, an icon, a title and typed properties, in one component copied into your app (Tailwind v4 and `shadcn-svelte init` first; [starter editor](https://edytor.dev/docs/starter)):
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
npx shadcn-svelte@latest add https://edytor.dev/r/starter.json
|
|
75
|
+
```
|
|
82
76
|
|
|
83
77
|
```svelte
|
|
84
|
-
<script
|
|
85
|
-
import {
|
|
86
|
-
Edytor,
|
|
87
|
-
createCodePlugin,
|
|
88
|
-
createSlashMenuPlugin,
|
|
89
|
-
createMarkdownShortcutsPlugin,
|
|
90
|
-
richTextPlaceholder,
|
|
91
|
-
type JSONDoc,
|
|
92
|
-
type SlashMenuController
|
|
93
|
-
} from 'edytor';
|
|
94
|
-
import 'edytor/themes/notion.css';
|
|
95
|
-
|
|
96
|
-
const value: JSONDoc = {
|
|
97
|
-
children: [{ type: 'paragraph', content: [{ text: 'Hello, World!' }] }]
|
|
98
|
-
};
|
|
99
|
-
const plugins = [
|
|
100
|
-
// Notion's hints in empty blocks.
|
|
101
|
-
() => ({ placeholder: richTextPlaceholder }),
|
|
102
|
-
createCodePlugin(),
|
|
103
|
-
createMarkdownShortcutsPlugin(),
|
|
104
|
-
createSlashMenuPlugin({ menu })
|
|
105
|
-
];
|
|
78
|
+
<script>
|
|
79
|
+
import { StarterEditor } from '$lib/components/edytor/starter/index.js';
|
|
106
80
|
</script>
|
|
107
81
|
|
|
108
|
-
|
|
109
|
-
{#snippet menu(slash: SlashMenuController)}
|
|
110
|
-
<ul {@attach slash.popup}>
|
|
111
|
-
{#each slash.commands as command, index (command.id)}
|
|
112
|
-
<li
|
|
113
|
-
data-selected={slash.index === index}
|
|
114
|
-
{@attach slash.option(index)}
|
|
115
|
-
onclick={() => void slash.run(command)}
|
|
116
|
-
>
|
|
117
|
-
{command.label}
|
|
118
|
-
</li>
|
|
119
|
-
{/each}
|
|
120
|
-
</ul>
|
|
121
|
-
{/snippet}
|
|
122
|
-
|
|
123
|
-
<div class="edytor-notion">
|
|
124
|
-
<Edytor {value} {plugins} onChange={(root) => console.log(root)} />
|
|
125
|
-
</div>
|
|
82
|
+
<StarterEditor />
|
|
126
83
|
```
|
|
127
84
|
|
|
128
|
-
|
|
85
|
+
To collaborate, name a room and a server running the [`edytor/cloudflare` room](https://edytor.dev/docs/server/quick-start):
|
|
129
86
|
|
|
130
87
|
```svelte
|
|
131
88
|
<Edytor
|
|
@@ -31,11 +31,13 @@ export declare class Block {
|
|
|
31
31
|
get content(): (Text | InlineBlock)[];
|
|
32
32
|
get type(): string;
|
|
33
33
|
/**
|
|
34
|
-
* The block's `data
|
|
35
|
-
* block's cell
|
|
36
|
-
*
|
|
34
|
+
* The block's `data` as plain state: reactive at any depth (through the
|
|
35
|
+
* block's cell), and writable (`block.data.color = 'red'`,
|
|
36
|
+
* `block.data.tags.push('x')`, `delete block.data.color`). Each write is
|
|
37
|
+
* the `patchData` operation, dispatched (`session/props`). The root's is
|
|
38
|
+
* the document's data (`edytor.data`).
|
|
37
39
|
*/
|
|
38
|
-
get data():
|
|
40
|
+
get data(): Record<string, any>;
|
|
39
41
|
get definition(): BlockDefinition;
|
|
40
42
|
get selected(): boolean;
|
|
41
43
|
get focused(): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cleanups } from '../utils.js';
|
|
1
2
|
import { Text } from '../text/text.svelte.js';
|
|
2
3
|
/** Mark an element inside a block's markup as non-editable chrome (`block.void`, a preview's too). */
|
|
3
4
|
export const voidChrome = (node) => {
|
|
@@ -76,14 +77,20 @@ export class Block {
|
|
|
76
77
|
return this.edytor.facade.blockTypeOf(this.id) ?? '';
|
|
77
78
|
}
|
|
78
79
|
#data;
|
|
80
|
+
#props;
|
|
79
81
|
/**
|
|
80
|
-
* The block's `data
|
|
81
|
-
* block's cell
|
|
82
|
-
*
|
|
82
|
+
* The block's `data` as plain state: reactive at any depth (through the
|
|
83
|
+
* block's cell), and writable (`block.data.color = 'red'`,
|
|
84
|
+
* `block.data.tags.push('x')`, `delete block.data.color`). Each write is
|
|
85
|
+
* the `patchData` operation, dispatched (`session/props`). The root's is
|
|
86
|
+
* the document's data (`edytor.data`).
|
|
83
87
|
*/
|
|
84
88
|
get data() {
|
|
85
89
|
if (this.isRoot)
|
|
86
|
-
return this.edytor.
|
|
90
|
+
return this.edytor.data;
|
|
91
|
+
return (this.#props ??= this.edytor.dataWrites.proxy(this.id, () => this.#current()));
|
|
92
|
+
}
|
|
93
|
+
#current() {
|
|
87
94
|
const { facade, cells } = this.edytor;
|
|
88
95
|
cells?.get(this.id);
|
|
89
96
|
const version = facade.version;
|
|
@@ -330,10 +337,7 @@ export class Block {
|
|
|
330
337
|
this.edytor.selection.keep(this);
|
|
331
338
|
// A block laid out again with no commit moves the chrome beside it (the overlay measures).
|
|
332
339
|
const unobserve = this.edytor.overlay.observe(node);
|
|
333
|
-
const onDestroy = this.edytor.plugins
|
|
334
|
-
const action = plugin.onBlockAttached?.({ node, block: this });
|
|
335
|
-
return typeof action === 'function' ? [action] : [];
|
|
336
|
-
});
|
|
340
|
+
const onDestroy = cleanups(this.edytor.plugins, (plugin) => plugin.onBlockAttached?.({ node, block: this }));
|
|
337
341
|
return {
|
|
338
342
|
destroy: () => {
|
|
339
343
|
this.#viewState = read(node);
|
|
@@ -22,8 +22,12 @@ export declare class InlineBlock {
|
|
|
22
22
|
get parent(): Block;
|
|
23
23
|
get selected(): boolean;
|
|
24
24
|
get type(): string;
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The atom's `data` as plain state: reactive at any depth (through its
|
|
27
|
+
* block's cell), and writable; each write is its `patchData` operation
|
|
28
|
+
* (`session/props`).
|
|
29
|
+
*/
|
|
30
|
+
get data(): Record<string, any>;
|
|
27
31
|
get definition(): InlineBlockDefinition;
|
|
28
32
|
/** Position among the block's content parts. */
|
|
29
33
|
get index(): number;
|
|
@@ -37,9 +37,14 @@ export class InlineBlock {
|
|
|
37
37
|
get type() {
|
|
38
38
|
return this.#item?.type ?? '';
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
#props;
|
|
41
|
+
/**
|
|
42
|
+
* The atom's `data` as plain state: reactive at any depth (through its
|
|
43
|
+
* block's cell), and writable; each write is its `patchData` operation
|
|
44
|
+
* (`session/props`).
|
|
45
|
+
*/
|
|
41
46
|
get data() {
|
|
42
|
-
return this.#item?.data ?? {};
|
|
47
|
+
return (this.#props ??= this.edytor.dataWrites.proxy({ block: this.blockId, atom: this.id }, () => this.#item?.data ?? {}));
|
|
43
48
|
}
|
|
44
49
|
get definition() {
|
|
45
50
|
return this.edytor.inlineBlocks.get(this.type) ?? {};
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import type { Edytor } from '../edytor.svelte.js';
|
|
2
1
|
import type { Flow } from '../crdt/flow.js';
|
|
3
|
-
import type
|
|
4
|
-
import {
|
|
2
|
+
import { type JSONBlock, type SerializableContent } from '../utils/json.js';
|
|
3
|
+
import type { AtomForms, BlockForms, MarkForms } from './serializeClipboardFragment.js';
|
|
5
4
|
/** What HTML import reads: the kind, mark and atom records, and the document's default child. */
|
|
6
|
-
export type ImportKinds =
|
|
7
|
-
|
|
5
|
+
export type ImportKinds = {
|
|
6
|
+
readonly blocks: ReadonlyMap<string, BlockForms>;
|
|
7
|
+
readonly marks: ReadonlyMap<string, MarkForms>;
|
|
8
|
+
readonly inlineBlocks?: ReadonlyMap<string, AtomForms>;
|
|
9
|
+
readonly document: {
|
|
10
|
+
defaultChild: (parent: string | null) => string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/** A `DOMParser` constructor: the browser's, or a server's (`linkedom`, `jsdom`, `happy-dom`). */
|
|
14
|
+
export type HtmlParser = new () => {
|
|
15
|
+
parseFromString(html: string, type: 'text/html'): {
|
|
16
|
+
body: Node | null;
|
|
17
|
+
};
|
|
8
18
|
};
|
|
9
19
|
type Values = Record<string, SerializableContent>;
|
|
10
20
|
/**
|
|
@@ -13,9 +23,10 @@ type Values = Record<string, SerializableContent>;
|
|
|
13
23
|
* none: an export form written as one (a callout's `<div data-edytor-callout>`)
|
|
14
24
|
* is found by its `parse` hook only.
|
|
15
25
|
*/
|
|
16
|
-
export declare const tagOf: (type: string, kind:
|
|
26
|
+
export declare const tagOf: (type: string, kind: BlockForms, data: Values) => string | undefined;
|
|
17
27
|
/**
|
|
18
|
-
* External HTML as a
|
|
28
|
+
* External HTML as lines (JSON blocks; a line without a `type` is its
|
|
29
|
+
* parent's default child): `parser` parses (inert: no script
|
|
19
30
|
* runs, nothing loads), and the tag tables are the records inverted — a
|
|
20
31
|
* catalogue kind's tag per preset (a content-less container takes its
|
|
21
32
|
* default child's tag, one child per text line), a value-less mark's
|
|
@@ -27,5 +38,7 @@ export declare const tagOf: (type: string, kind: BlockDefinition, data: Values)
|
|
|
27
38
|
* atom, never its text. Unknown
|
|
28
39
|
* elements degrade to text runs; whitespace collapses as HTML renders it. `null` when the HTML carries nothing (`flow.shape`).
|
|
29
40
|
*/
|
|
41
|
+
export declare const linesOfHtml: (kinds: ImportKinds, html: string | undefined, parser?: HtmlParser | undefined) => JSONBlock[] | null;
|
|
42
|
+
/** External HTML as a flow (the paste's): {@link linesOfHtml}, with the browser's parser. */
|
|
30
43
|
export declare const flowOfHtml: (kinds: ImportKinds, html: string | undefined) => Flow | null;
|
|
31
44
|
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { colorsOfClasses } from '../block/colors.js';
|
|
2
2
|
import { isTextPart } from '../block/contentRange.js';
|
|
3
3
|
import { jsonBlockToSpec } from '../utils/json.js';
|
|
4
|
+
/** Element and text nodes by their type, not their realm: a server parser's nodes are not this global's. */
|
|
5
|
+
const isElement = (node) => node?.nodeType === 1;
|
|
4
6
|
/** Never content: their text is code, metadata or form state. */
|
|
5
7
|
const SKIP = /^(script|style|template|noscript|svg|math|iframe|object|embed|canvas|video|audio|img|input|button|select|textarea|head|title|meta|link|base)$/;
|
|
6
8
|
/** HTML's block-level elements: they start a line (a kind's tag does too). */
|
|
@@ -21,7 +23,7 @@ const sizeOf = (img, side) => {
|
|
|
21
23
|
* email's tracking pixel). It is text, its `alt`, never a block.
|
|
22
24
|
*/
|
|
23
25
|
const isGlyph = (node) => {
|
|
24
|
-
if (!(node
|
|
26
|
+
if (!isElement(node) || node.localName !== 'img')
|
|
25
27
|
return false;
|
|
26
28
|
const alt = node.getAttribute('alt')?.trim() ?? '';
|
|
27
29
|
if (alt && EMOJI_ALT.test(alt) && /[\p{Extended_Pictographic}\p{Regional_Indicator}]/u.test(alt))
|
|
@@ -61,7 +63,8 @@ export const tagOf = (type, kind, data) => {
|
|
|
61
63
|
return undefined;
|
|
62
64
|
};
|
|
63
65
|
/**
|
|
64
|
-
* External HTML as a
|
|
66
|
+
* External HTML as lines (JSON blocks; a line without a `type` is its
|
|
67
|
+
* parent's default child): `parser` parses (inert: no script
|
|
65
68
|
* runs, nothing loads), and the tag tables are the records inverted — a
|
|
66
69
|
* catalogue kind's tag per preset (a content-less container takes its
|
|
67
70
|
* default child's tag, one child per text line), a value-less mark's
|
|
@@ -73,8 +76,8 @@ export const tagOf = (type, kind, data) => {
|
|
|
73
76
|
* atom, never its text. Unknown
|
|
74
77
|
* elements degrade to text runs; whitespace collapses as HTML renders it. `null` when the HTML carries nothing (`flow.shape`).
|
|
75
78
|
*/
|
|
76
|
-
export const
|
|
77
|
-
if (!html || html.length > 8 * 1024 * 1024 ||
|
|
79
|
+
export const linesOfHtml = (kinds, html, parser = globalThis.DOMParser) => {
|
|
80
|
+
if (!html || html.length > 8 * 1024 * 1024 || !parser)
|
|
78
81
|
return null;
|
|
79
82
|
const { blocks, marks } = kinds;
|
|
80
83
|
const atoms = kinds.inlineBlocks ?? new Map();
|
|
@@ -99,8 +102,9 @@ export const flowOfHtml = (kinds, html) => {
|
|
|
99
102
|
return undefined;
|
|
100
103
|
for (const [type, kind] of blocks) {
|
|
101
104
|
const data = kind.parse?.(element);
|
|
105
|
+
// A kind of lines (a code block) takes its element's text, one child per line.
|
|
102
106
|
if (data)
|
|
103
|
-
return { type, kind, data };
|
|
107
|
+
return { type, kind, data, lines: kind.lines ? kind.defaultChild : undefined };
|
|
104
108
|
}
|
|
105
109
|
return byTag.get(element.localName);
|
|
106
110
|
};
|
|
@@ -124,11 +128,10 @@ export const flowOfHtml = (kinds, html) => {
|
|
|
124
128
|
return found;
|
|
125
129
|
};
|
|
126
130
|
// A kind's hook may claim an element of another namespace (an equation's MathML `<math>`).
|
|
127
|
-
const isBlock = (node) => (node
|
|
128
|
-
(node instanceof Element && kindOf(node) !== undefined);
|
|
131
|
+
const isBlock = (node) => isElement(node) && (BLOCK.test(node.localName) || kindOf(node) !== undefined);
|
|
129
132
|
/** A void kind's element (an image): it shows no text, so a line it is met in ends there. */
|
|
130
133
|
const isVoid = (node) => {
|
|
131
|
-
const claim = node
|
|
134
|
+
const claim = isElement(node) ? kindOf(node) : undefined;
|
|
132
135
|
return claim?.kind.void === true;
|
|
133
136
|
};
|
|
134
137
|
const text = (line, value, marks, pre) => {
|
|
@@ -148,16 +151,16 @@ export const flowOfHtml = (kinds, html) => {
|
|
|
148
151
|
line.content.push(Object.keys(marks).length ? { text: value, marks } : { text: value });
|
|
149
152
|
};
|
|
150
153
|
const inline = (node, at, marks, pre) => {
|
|
151
|
-
if (node.nodeType ===
|
|
154
|
+
if (node.nodeType === 3)
|
|
152
155
|
return text(at.line(), node.textContent ?? '', marks, pre);
|
|
153
156
|
if (isGlyph(node))
|
|
154
157
|
return text(at.line(), node.getAttribute('alt') ?? '', marks, pre);
|
|
155
158
|
if (isVoid(node))
|
|
156
159
|
return at.apart(node);
|
|
157
|
-
const atom = node
|
|
160
|
+
const atom = isElement(node) ? atomOf(node) : undefined;
|
|
158
161
|
if (atom)
|
|
159
162
|
return void at.line().content.push(atom);
|
|
160
|
-
if (!(node
|
|
163
|
+
if (!isElement(node) || SKIP.test(node.localName))
|
|
161
164
|
return;
|
|
162
165
|
if (node.localName === 'br')
|
|
163
166
|
return text(at.line(), '\n', marks, true);
|
|
@@ -286,12 +289,17 @@ export const flowOfHtml = (kinds, html) => {
|
|
|
286
289
|
// Split: a text line left empty around a void element is not kept.
|
|
287
290
|
return parts.filter((part) => !texts.has(part) || end(part).content.length > 0 || part.children !== undefined);
|
|
288
291
|
};
|
|
289
|
-
const body = new
|
|
292
|
+
const body = new parser().parseFromString(html, 'text/html').body;
|
|
293
|
+
if (!body)
|
|
294
|
+
return null;
|
|
290
295
|
const lines = linesOf(body, undefined);
|
|
291
296
|
const carries = (line) => line.content.length > 0 ||
|
|
292
297
|
Boolean(line.children?.some(carries)) ||
|
|
293
298
|
Boolean(line.type && blocks.get(line.type)?.void);
|
|
294
|
-
return lines.some(carries)
|
|
295
|
-
|
|
296
|
-
|
|
299
|
+
return lines.some(carries) ? lines : null;
|
|
300
|
+
};
|
|
301
|
+
/** External HTML as a flow (the paste's): {@link linesOfHtml}, with the browser's parser. */
|
|
302
|
+
export const flowOfHtml = (kinds, html) => {
|
|
303
|
+
const lines = linesOfHtml(kinds, html);
|
|
304
|
+
return lines ? { lines: lines.map((line) => jsonBlockToSpec(line, true)) } : null;
|
|
297
305
|
};
|
|
@@ -1,9 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The document's export forms, one reading for the clipboard and for
|
|
3
|
+
* `toHTML` / `toMarkdown` (`edytor/export`): each kind, mark and atom
|
|
4
|
+
* declares its own forms (`html`, `plain`, `markdown`; a mark's `tag` and
|
|
5
|
+
* `attributes`), and this module only walks the blocks. Pure: no view, no
|
|
6
|
+
* DOM, no Svelte.
|
|
7
|
+
*/
|
|
8
|
+
import type { JSONBlock, SerializableContent } from '../utils/json.js';
|
|
9
|
+
import type { BlockDefinition, InlineBlockDefinition, MarkDefinition } from '../plugins.js';
|
|
10
|
+
import type { EdytorClipboardFragment, JSONContentPart } from './types.js';
|
|
11
|
+
/** A kind's export and import forms: the fields of its record the export and HTML import read. */
|
|
12
|
+
export type BlockForms = Pick<BlockDefinition, 'html' | 'plain' | 'markdown' | 'parse' | 'element' | 'contentElement' | 'void' | 'island' | 'lines' | 'rendersContent' | 'defaultChild' | 'itemKind'> & {
|
|
13
|
+
/** The data of each way to create the kind: HTML import finds a preset's tag. */
|
|
14
|
+
presets?: {
|
|
15
|
+
data?: Record<string, SerializableContent>;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
/** A mark's export and import forms. */
|
|
19
|
+
export type MarkForms = Pick<MarkDefinition, 'tag' | 'attributes' | 'parse' | 'markdown'>;
|
|
20
|
+
/** An atom's export and import forms. */
|
|
21
|
+
export type AtomForms = Pick<InlineBlockDefinition, 'html' | 'plain' | 'markdown' | 'parse'>;
|
|
22
|
+
/** The records the export reads: each kind, mark and atom declares its own forms (a view is one). */
|
|
23
|
+
export type ExportKinds = {
|
|
24
|
+
readonly blocks: ReadonlyMap<string, BlockForms>;
|
|
25
|
+
readonly marks: ReadonlyMap<string, MarkForms>;
|
|
26
|
+
readonly inlineBlocks: ReadonlyMap<string, AtomForms>;
|
|
27
|
+
};
|
|
5
28
|
export declare const encodeClipboardJson: (value: unknown) => string;
|
|
6
29
|
/** `value` escaped for an HTML text or a double-quoted attribute: every kind's export reads it. */
|
|
7
30
|
export declare const escapeHtml: (value: string) => string;
|
|
31
|
+
/** The content as HTML, or as plain text: an atom by its kind's form (`html`, `plain`). */
|
|
32
|
+
export declare const contentOf: (content: JSONContentPart[] | undefined, kinds: ExportKinds, html: boolean) => string;
|
|
33
|
+
/**
|
|
34
|
+
* How sibling blocks' HTML joins (`edytor/export` wraps a run of list items
|
|
35
|
+
* in its list): the blocks, their HTML, and their parent's `html` form.
|
|
36
|
+
* Without one, one after another (the clipboard's).
|
|
37
|
+
*/
|
|
38
|
+
export type SiblingsHtml = (blocks: JSONBlock[], html: string[], parent?: BlockForms['html']) => string;
|
|
39
|
+
/** Blocks as HTML: the clipboard's and `toHTML`'s one reading of the kinds' forms. */
|
|
40
|
+
export declare const blocksToHtml: (blocks: JSONBlock[], kinds: ExportKinds, wrap: SiblingsHtml) => string;
|
|
8
41
|
export declare const serializeClipboardFragmentToPlainText: (fragment: EdytorClipboardFragment, kinds: ExportKinds) => string;
|
|
9
42
|
export declare const serializeClipboardFragmentToHtml: (fragment: EdytorClipboardFragment, kinds: ExportKinds) => string;
|
|
@@ -19,7 +19,7 @@ const textHtml = (part, kinds) => {
|
|
|
19
19
|
return html;
|
|
20
20
|
};
|
|
21
21
|
/** The content as HTML, or as plain text: an atom by its kind's form (`html`, `plain`). */
|
|
22
|
-
const contentOf = (content = [], kinds, html) => content
|
|
22
|
+
export const contentOf = (content = [], kinds, html) => content
|
|
23
23
|
.map((part) => {
|
|
24
24
|
if (isTextPart(part))
|
|
25
25
|
return html ? textHtml(part, kinds) : part.text;
|
|
@@ -41,9 +41,9 @@ const withClasses = (html, classes) => classes
|
|
|
41
41
|
})
|
|
42
42
|
: html;
|
|
43
43
|
/** A block and its subtree as HTML (its colours as Notion's classes), or as plain text. */
|
|
44
|
-
const blockOf = (block, kinds, html) => {
|
|
44
|
+
const blockOf = (block, kinds, html, wrap) => {
|
|
45
45
|
const content = contentOf(block.content, kinds, html);
|
|
46
|
-
const kids = block.children?.map((child) => blockOf(child, kinds, html)) ?? [];
|
|
46
|
+
const kids = block.children?.map((child) => blockOf(child, kinds, html, wrap)) ?? [];
|
|
47
47
|
const kind = kinds.blocks.get(block.type);
|
|
48
48
|
if (!html) {
|
|
49
49
|
const children = kids.filter(Boolean).join('\n');
|
|
@@ -51,8 +51,8 @@ const blockOf = (block, kinds, html) => {
|
|
|
51
51
|
? kind.plain(block, content, children)
|
|
52
52
|
: [content, children].filter(Boolean).join('\n');
|
|
53
53
|
}
|
|
54
|
-
const children = kids.join('');
|
|
55
54
|
const form = kind?.html;
|
|
55
|
+
const children = wrap ? wrap(block.children ?? [], kids, form) : kids.join('');
|
|
56
56
|
const out = !form
|
|
57
57
|
? `<p>${content}</p>${children}`
|
|
58
58
|
: typeof form === 'string'
|
|
@@ -61,6 +61,8 @@ const blockOf = (block, kinds, html) => {
|
|
|
61
61
|
// A block's colours as Notion's export names them (`block-color-red`).
|
|
62
62
|
return withClasses(out, colorClasses(block.data));
|
|
63
63
|
};
|
|
64
|
+
/** Blocks as HTML: the clipboard's and `toHTML`'s one reading of the kinds' forms. */
|
|
65
|
+
export const blocksToHtml = (blocks, kinds, wrap) => wrap(blocks, blocks.map((block) => blockOf(block, kinds, true, wrap)));
|
|
64
66
|
export const serializeClipboardFragmentToPlainText = (fragment, kinds) => fragment.kind === 'blocks'
|
|
65
67
|
? fragment.blocks.map((block) => blockOf(block, kinds, false)).join('\n')
|
|
66
68
|
: contentOf(fragment.content, kinds, false);
|
|
@@ -21,5 +21,13 @@ export type TextToBlocksOptions = {
|
|
|
21
21
|
};
|
|
22
22
|
/** Whether `text` holds block markdown (one line of it is enough), as a paste reads it. */
|
|
23
23
|
export declare const hasBlockMarkdown: (text: string) => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* One line's kind, data and text, or `null` for a paragraph line: the one
|
|
26
|
+
* reading of a markdown line's block marker (`edytor/export`'s full reader
|
|
27
|
+
* reads its lines with it too).
|
|
28
|
+
*/
|
|
29
|
+
export declare const lineKind: (line: string) => (Omit<JSONBlock, "content"> & {
|
|
30
|
+
text: string;
|
|
31
|
+
}) | null;
|
|
24
32
|
/** `text` as blocks (see the module comment). */
|
|
25
33
|
export declare const textToBlocks: (text: string, { markdown, type }?: TextToBlocksOptions) => JSONBlock[];
|
|
@@ -25,8 +25,12 @@ const runs = (text) => {
|
|
|
25
25
|
const BLOCK_LINE = /^\s*(?:#{1,6}\s|[-*+]\s|\d+[.)]\s|>|```|(?:-{3,}|\*{3,}|_{3,})\s*$)/;
|
|
26
26
|
/** Whether `text` holds block markdown (one line of it is enough), as a paste reads it. */
|
|
27
27
|
export const hasBlockMarkdown = (text) => text.split(/\r?\n/).some((line) => BLOCK_LINE.test(line));
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* One line's kind, data and text, or `null` for a paragraph line: the one
|
|
30
|
+
* reading of a markdown line's block marker (`edytor/export`'s full reader
|
|
31
|
+
* reads its lines with it too).
|
|
32
|
+
*/
|
|
33
|
+
export const lineKind = (line) => {
|
|
30
34
|
let m = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
31
35
|
if (m)
|
|
32
36
|
return { type: 'heading', data: { level: `h${Math.min(3, m[1].length)}` }, text: m[2] };
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { Y } from '../../crdt/engine.js';
|
|
13
13
|
import * as E from '../../crdt/protocol.js';
|
|
14
|
-
import {
|
|
14
|
+
import { ChunkError, CLOSE } from '../../crdt/providers/room.js';
|
|
15
15
|
import { MAX_WAITING_DELETES, noTimers } from './shared.js';
|
|
16
16
|
import { STORAGE_FAILURE, now, sync, tally } from './context.js';
|
|
17
17
|
import { attributionWrites, BINDING_PREFIX, forgedAttributionDeletes, forgedDeletes, forgedWriters } from './forged.js';
|
|
@@ -168,7 +168,7 @@ export class Admission {
|
|
|
168
168
|
}
|
|
169
169
|
catch (error) {
|
|
170
170
|
this.#chunkReaders.delete(ws);
|
|
171
|
-
if (error instanceof
|
|
171
|
+
if (error instanceof ChunkError && error.limit !== undefined) {
|
|
172
172
|
return this.#overQuota(ws, attachment.user, 'frame', {
|
|
173
173
|
bytes: error.total,
|
|
174
174
|
limit: error.limit
|
|
@@ -187,7 +187,7 @@ export class Admission {
|
|
|
187
187
|
room.presence.depart(ws);
|
|
188
188
|
return room.close(ws, CLOSE.fault, 'room busy');
|
|
189
189
|
}
|
|
190
|
-
if (error instanceof
|
|
190
|
+
if (error instanceof ChunkError) {
|
|
191
191
|
room.note({ reason: 'internal', detail: `chunks: ${error.message}` });
|
|
192
192
|
room.presence.depart(ws);
|
|
193
193
|
return room.close(ws, CLOSE.fault, 'chunk sequence lost');
|
|
@@ -233,6 +233,12 @@ export declare const crdt: {
|
|
|
233
233
|
blockDataOf: (id: string) => Record<string, unknown> | undefined;
|
|
234
234
|
docData: () => import("../../crdt/doc/types.js").JsonObj;
|
|
235
235
|
dataItemIds: (target: import("../../index.js").DataTarget, path: readonly string[]) => string[];
|
|
236
|
+
dataPreview: (target: import("../../index.js").DataTarget, pending: readonly import("../../index.js").DataPatch[]) => {
|
|
237
|
+
data: {
|
|
238
|
+
[x: string]: unknown;
|
|
239
|
+
};
|
|
240
|
+
ids: (path: readonly string[]) => string[];
|
|
241
|
+
} | null;
|
|
236
242
|
blockAttribution: (id: string) => import("../../index.js").BlockAttribution | undefined;
|
|
237
243
|
crdtId: (id: string) => string | null;
|
|
238
244
|
resolveBlock: (id: string) => E.EngineNode | null;
|
|
@@ -530,7 +536,7 @@ export declare const crdt: {
|
|
|
530
536
|
off<NAME extends "refused" | "status" | "failed" | "schema-mismatch" | "message-error" | "protocol-mismatch" | "synced" | "connection-close" | "connection-error" | "saved" | "permission-denied" | "expired" | "unreachable">(name: NAME, f: E.WebsocketProviderEvents[NAME]): void;
|
|
531
537
|
};
|
|
532
538
|
};
|
|
533
|
-
watchComments: (server: string, roomName: string, listener: (message: E.CommentMessage) => void) => (() => void);
|
|
539
|
+
watchComments: (server: string | null, roomName: string, listener: (message: E.CommentMessage) => void) => (() => void);
|
|
534
540
|
storeState: (idbPersistence: {
|
|
535
541
|
db: IDBDatabase | null;
|
|
536
542
|
name: string;
|
|
@@ -50,6 +50,23 @@ export type RouteDocumentOptions = {
|
|
|
50
50
|
* your page from another site's. Omitted: every origin passes.
|
|
51
51
|
*/
|
|
52
52
|
allowedOrigins?: readonly string[] | ((origin: string) => boolean);
|
|
53
|
+
/**
|
|
54
|
+
* Answer a page on another origin (the app on `app.example.com`, the
|
|
55
|
+
* room on `rooms.example.com`): `OPTIONS` preflights are answered `204`
|
|
56
|
+
* (`GET, POST`, the headers the page asked for), and every HTTP answer
|
|
57
|
+
* (comments, history, the probes) carries `Access-Control-Allow-Origin`.
|
|
58
|
+
* The origins are `allowedOrigins`: with a list or a predicate, the
|
|
59
|
+
* request's `Origin` is echoed (`Vary: Origin`) and `credentials: true`
|
|
60
|
+
* lets the page send its cookies (`fetch(…, { credentials: 'include' })`);
|
|
61
|
+
* without one, every origin gets `*` and no credentials. A preflight from
|
|
62
|
+
* an origin `allowedOrigins` refuses is answered `403`. `maxAge`: how long
|
|
63
|
+
* a browser keeps a preflight, in seconds (default 600). A WebSocket dial
|
|
64
|
+
* needs none of it. Omitted: no CORS headers, and `OPTIONS` answers `405`.
|
|
65
|
+
*/
|
|
66
|
+
cors?: boolean | {
|
|
67
|
+
credentials?: boolean;
|
|
68
|
+
maxAge?: number;
|
|
69
|
+
};
|
|
53
70
|
};
|
|
54
71
|
/** The replica a client asked for in `?replica=<doc.clientID>` (or `param`), or `null`. */
|
|
55
72
|
export declare const requestedReplica: (request: Request, param?: string) => number | null;
|