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
|
@@ -28,6 +28,50 @@ import { CLOSE, validRoomId } from '../crdt/providers/room.js';
|
|
|
28
28
|
import { COMMENTS_HEADER, HISTORY_HEADER, HISTORY_KEY_HEADER, IDENTITY_HEADERS, PROBE_HEADER, closedSocket } from './room/shared.js';
|
|
29
29
|
import { parseReplica } from './room/access.js';
|
|
30
30
|
import { MAX_COMMENT_REQUEST_BYTES } from './room/comments.js';
|
|
31
|
+
/** The CORS headers `options.cors` gives an answer to `request` (none: `null`). */
|
|
32
|
+
const corsHeaders = (request, options) => {
|
|
33
|
+
const cors = options?.cors;
|
|
34
|
+
const origin = request.headers.get('Origin');
|
|
35
|
+
if (!cors || origin === null || !originAllowed(request, options))
|
|
36
|
+
return null;
|
|
37
|
+
const headers = new Headers();
|
|
38
|
+
if (options?.allowedOrigins === undefined) {
|
|
39
|
+
headers.set('Access-Control-Allow-Origin', '*');
|
|
40
|
+
return headers;
|
|
41
|
+
}
|
|
42
|
+
headers.set('Access-Control-Allow-Origin', origin);
|
|
43
|
+
headers.set('Vary', 'Origin');
|
|
44
|
+
if (typeof cors === 'object' && cors.credentials === true)
|
|
45
|
+
headers.set('Access-Control-Allow-Credentials', 'true');
|
|
46
|
+
return headers;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* One HTTP route with `options.cors`: a preflight answered here, every other
|
|
50
|
+
* answer given its CORS headers (a `101` upgrade passes as it is).
|
|
51
|
+
*/
|
|
52
|
+
const withCors = async (request, options, route) => {
|
|
53
|
+
const cors = options?.cors;
|
|
54
|
+
if (cors && request.method === 'OPTIONS' && request.headers.has('Origin')) {
|
|
55
|
+
const headers = corsHeaders(request, options);
|
|
56
|
+
if (headers === null)
|
|
57
|
+
return new Response('origin not allowed', { status: 403 });
|
|
58
|
+
headers.set('Access-Control-Allow-Methods', 'GET, POST');
|
|
59
|
+
headers.set('Access-Control-Allow-Headers', request.headers.get('Access-Control-Request-Headers') ?? 'Content-Type');
|
|
60
|
+
headers.set('Access-Control-Max-Age', String((typeof cors === 'object' ? cors.maxAge : undefined) ?? 600));
|
|
61
|
+
return new Response(null, { status: 204, headers });
|
|
62
|
+
}
|
|
63
|
+
const response = await route();
|
|
64
|
+
const headers = corsHeaders(request, options);
|
|
65
|
+
if (headers === null || response.status === 101)
|
|
66
|
+
return response;
|
|
67
|
+
const merged = new Headers(response.headers);
|
|
68
|
+
headers.forEach((value, name) => merged.set(name, value));
|
|
69
|
+
return new Response(response.body, {
|
|
70
|
+
status: response.status,
|
|
71
|
+
statusText: response.statusText,
|
|
72
|
+
headers: merged
|
|
73
|
+
});
|
|
74
|
+
};
|
|
31
75
|
/** Is `request`'s `Origin` one `options` allows (no `Origin`, or no list: yes)? */
|
|
32
76
|
const originAllowed = (request, options) => {
|
|
33
77
|
const allowed = options?.allowedOrigins;
|
|
@@ -109,6 +153,9 @@ const identityHeaders = (identity) => {
|
|
|
109
153
|
* `404` (the room keeps no history), `405` (another method).
|
|
110
154
|
*/
|
|
111
155
|
export async function routeDocumentHistory(request, rooms, documentId, authorize, options) {
|
|
156
|
+
return withCors(request, options, () => routeHistory(request, rooms, documentId, authorize, options));
|
|
157
|
+
}
|
|
158
|
+
async function routeHistory(request, rooms, documentId, authorize, options) {
|
|
112
159
|
const query = new URL(request.url).searchParams;
|
|
113
160
|
const op = request.method === 'GET'
|
|
114
161
|
? query.has('key')
|
|
@@ -191,6 +238,9 @@ const bodyWithin = async (request, max) => {
|
|
|
191
238
|
* comment bytes full), `429` (the user past the room's comment rate).
|
|
192
239
|
*/
|
|
193
240
|
export async function routeDocumentComments(request, rooms, documentId, authorize, options) {
|
|
241
|
+
return withCors(request, options, () => routeComments(request, rooms, documentId, authorize, options));
|
|
242
|
+
}
|
|
243
|
+
async function routeComments(request, rooms, documentId, authorize, options) {
|
|
194
244
|
const op = request.method === 'GET' ? 'list' : request.method === 'POST' ? 'post' : null;
|
|
195
245
|
if (op === null)
|
|
196
246
|
return new Response('method not allowed', { status: 405 });
|
|
@@ -215,6 +265,9 @@ export async function routeDocumentComments(request, rooms, documentId, authoriz
|
|
|
215
265
|
.fetch(new Request(request.url, { method: op === 'post' ? 'POST' : 'GET', headers, body }));
|
|
216
266
|
}
|
|
217
267
|
export async function routeDocumentSocket(request, rooms, documentId, authorize, options) {
|
|
268
|
+
return withCors(request, options, () => routeSocket(request, rooms, documentId, authorize, options));
|
|
269
|
+
}
|
|
270
|
+
async function routeSocket(request, rooms, documentId, authorize, options) {
|
|
218
271
|
// The probes: a plain GET, authorized like a dial, answered with JSON or
|
|
219
272
|
// an HTTP status — `lastUpdated` and `snapshot` (the document).
|
|
220
273
|
const query = new URL(request.url).searchParams;
|
|
@@ -56,6 +56,20 @@ export type AwarenessLike = {
|
|
|
56
56
|
getLocalState: () => Record<string, unknown> | null;
|
|
57
57
|
setLocalState: (state: Record<string, unknown> | null) => void;
|
|
58
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* The awareness a view's presence writer reads and writes: its own state,
|
|
61
|
+
* every client's state, and the `change` event (the real `Awareness`).
|
|
62
|
+
*/
|
|
63
|
+
export type PresenceSource = AwarenessLike & {
|
|
64
|
+
clientID: number;
|
|
65
|
+
getStates: () => Map<number, Record<string, unknown>>;
|
|
66
|
+
on: (name: 'change', listener: () => void) => void;
|
|
67
|
+
off: (name: 'change', listener: () => void) => void;
|
|
68
|
+
};
|
|
69
|
+
/** Another person online: their client id and the fields of their `user` state (`name`, `color`, …), the document's actor name and colour when `user` has none. */
|
|
70
|
+
export type PresencePeer = EdytorAwarenessUser & Record<string, unknown> & {
|
|
71
|
+
clientId: number;
|
|
72
|
+
};
|
|
59
73
|
/** A client-local presence key, minted once by the view that owns the entry. */
|
|
60
74
|
export declare const mintPresenceKey: () => string;
|
|
61
75
|
/**
|
|
@@ -90,9 +104,16 @@ export declare class PresenceWriter {
|
|
|
90
104
|
private readonly awareness;
|
|
91
105
|
private readonly key;
|
|
92
106
|
throttle: number;
|
|
93
|
-
constructor(awareness:
|
|
107
|
+
constructor(awareness: PresenceSource, key: string, { share, throttle }?: PresenceOptions);
|
|
94
108
|
get share(): PresenceShare;
|
|
95
109
|
set share(share: PresenceShare);
|
|
110
|
+
/**
|
|
111
|
+
* The other people online: every client but this one, by client id,
|
|
112
|
+
* each `{ clientId, name?, color?, …user }`. Reactive: read in markup or
|
|
113
|
+
* an effect, it updates when someone joins, leaves or changes their
|
|
114
|
+
* state; the awareness listener exists only while something reads it.
|
|
115
|
+
*/
|
|
116
|
+
get peers(): PresencePeer[];
|
|
96
117
|
/** Publish `payload` (the full selection; `focus`: the block holding its focus) as this view shares it, within the throttle. */
|
|
97
118
|
write(payload: PresenceSelection | null, focus?: string | null): void;
|
|
98
119
|
/** Remove this view's entry and drop a held write (the view's teardown). */
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { isRecord, jsonEquals } from '../utils/json.js';
|
|
2
|
+
import { createSubscriber } from 'svelte/reactivity';
|
|
3
|
+
/** Every client but this one, by client id. */
|
|
4
|
+
const readPeers = (awareness) => [...awareness.getStates()]
|
|
5
|
+
.filter(([clientId]) => clientId !== awareness.clientID)
|
|
6
|
+
.sort(([a], [b]) => a - b)
|
|
7
|
+
.map(([clientId, state]) => {
|
|
8
|
+
const actor = isRecord(state.actor) ? state.actor : {};
|
|
9
|
+
const user = isRecord(state.user) ? state.user : {};
|
|
10
|
+
const peer = { ...user, clientId };
|
|
11
|
+
if (peer.name === undefined && typeof actor.name === 'string')
|
|
12
|
+
peer.name = actor.name;
|
|
13
|
+
if (peer.color === undefined && typeof actor.color === 'string')
|
|
14
|
+
peer.color = actor.color;
|
|
15
|
+
return peer;
|
|
16
|
+
});
|
|
2
17
|
let presenceKeys = 0;
|
|
3
18
|
/** A client-local presence key, minted once by the view that owns the entry. */
|
|
4
19
|
export const mintPresenceKey = () => `view-${++presenceKeys}`;
|
|
@@ -50,6 +65,21 @@ export class PresenceWriter {
|
|
|
50
65
|
#held;
|
|
51
66
|
/** The last payload and focused block handed in, kept so a share change republishes at once. */
|
|
52
67
|
#latest = [null, null];
|
|
68
|
+
/** The peers while someone reads them reactively (`undefined`: recompute). */
|
|
69
|
+
#peers;
|
|
70
|
+
#watched = false;
|
|
71
|
+
#subscribe = createSubscriber((update) => {
|
|
72
|
+
const changed = () => {
|
|
73
|
+
this.#peers = undefined;
|
|
74
|
+
update();
|
|
75
|
+
};
|
|
76
|
+
this.#watched = true;
|
|
77
|
+
this.awareness.on('change', changed);
|
|
78
|
+
return () => {
|
|
79
|
+
this.awareness.off('change', changed);
|
|
80
|
+
[this.#watched, this.#peers] = [false, undefined];
|
|
81
|
+
};
|
|
82
|
+
});
|
|
53
83
|
constructor(awareness, key, { share = 'caret', throttle = DEFAULT_PRESENCE_THROTTLE } = {}) {
|
|
54
84
|
this.awareness = awareness;
|
|
55
85
|
this.key = key;
|
|
@@ -65,6 +95,18 @@ export class PresenceWriter {
|
|
|
65
95
|
this.#share = share;
|
|
66
96
|
this.write(...this.#latest);
|
|
67
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* The other people online: every client but this one, by client id,
|
|
100
|
+
* each `{ clientId, name?, color?, …user }`. Reactive: read in markup or
|
|
101
|
+
* an effect, it updates when someone joins, leaves or changes their
|
|
102
|
+
* state; the awareness listener exists only while something reads it.
|
|
103
|
+
*/
|
|
104
|
+
get peers() {
|
|
105
|
+
this.#subscribe();
|
|
106
|
+
if (!this.#watched)
|
|
107
|
+
return readPeers(this.awareness);
|
|
108
|
+
return (this.#peers ??= readPeers(this.awareness));
|
|
109
|
+
}
|
|
68
110
|
/** Publish `payload` (the full selection; `focus`: the block holding its focus) as this view shares it, within the throttle. */
|
|
69
111
|
write(payload, focus = null) {
|
|
70
112
|
this.#latest = [payload, focus];
|
|
@@ -48,7 +48,7 @@ export type CommentsClientOptions = {
|
|
|
48
48
|
/**
|
|
49
49
|
* The websocket sync's server URL (the `server` of `<Edytor>` or
|
|
50
50
|
* `createWebsocketSync`), whose socket hears the room's changes. Default:
|
|
51
|
-
*
|
|
51
|
+
* every websocket sync of the same room on the page, whatever its URL.
|
|
52
52
|
*/
|
|
53
53
|
socket?: string;
|
|
54
54
|
/** Query parameters sent with each request (a token): an object, or a function read at every request. */
|
|
@@ -34,7 +34,7 @@ export const createCommentsClient = (options) => {
|
|
|
34
34
|
assertRoomId(options.room);
|
|
35
35
|
const server = options.server.replace(/\/+$/, '');
|
|
36
36
|
const base = `${server.replace(/^ws/, 'http')}/${encodeURIComponent(options.room)}`;
|
|
37
|
-
const socket =
|
|
37
|
+
const socket = options.socket?.replace(/\/+$/, '') ?? null;
|
|
38
38
|
const request = async (method, body) => {
|
|
39
39
|
const url = new URL(base);
|
|
40
40
|
const params = typeof options.params === 'function' ? options.params() : options.params;
|
|
@@ -42,8 +42,8 @@ const getCaretRect = (point, origin, editor) => {
|
|
|
42
42
|
const range = (point.node.ownerDocument ?? editor.ownerDocument).createRange();
|
|
43
43
|
range.setStart(point.node, point.offset);
|
|
44
44
|
range.collapse(true);
|
|
45
|
-
const rect =
|
|
46
|
-
if (rect
|
|
45
|
+
const rect = range.getBoundingClientRect();
|
|
46
|
+
if (rect.width || rect.height) {
|
|
47
47
|
return toRemoteRect(rect, origin);
|
|
48
48
|
}
|
|
49
49
|
return getFallbackRect(point, origin, editor);
|
|
@@ -71,7 +71,7 @@ const getSelectionRects = (start, end, origin, editor) => {
|
|
|
71
71
|
(end.text.node && renderSkipped(end.text.node))) {
|
|
72
72
|
return [getFallbackRect(start, origin, editor)];
|
|
73
73
|
}
|
|
74
|
-
const clientRects =
|
|
74
|
+
const clientRects = Array.from(range.getClientRects());
|
|
75
75
|
const seen = new Set();
|
|
76
76
|
const rects = clientRects
|
|
77
77
|
.filter((rect) => rect.width || rect.height)
|
|
@@ -5,11 +5,9 @@
|
|
|
5
5
|
import { Edytor as EdytorClass, useEdytor } from '../edytor.svelte.js';
|
|
6
6
|
import {
|
|
7
7
|
SyncRefusedError,
|
|
8
|
-
type Awareness,
|
|
9
8
|
type DocChange,
|
|
10
9
|
type DocumentActor,
|
|
11
|
-
type EdytorDocument
|
|
12
|
-
type YDoc
|
|
10
|
+
type EdytorDocument
|
|
13
11
|
} from '../crdt/index.js';
|
|
14
12
|
import { CLOSE, validRoomId } from '../crdt/providers/room.js';
|
|
15
13
|
import type { EdytorSync, WebsocketSyncOptions } from '../collaboration/index.js';
|
|
@@ -23,21 +21,15 @@
|
|
|
23
21
|
import { brandOf } from '../plugins/brand.js';
|
|
24
22
|
import type { PartialLabels } from '../labels.js';
|
|
25
23
|
import { createRichTextPlugin } from '../plugins/richtext/RichTextPlugin.svelte';
|
|
26
|
-
import { createArrowMovePlugin } from '../plugins/arrowMove/arrowMove.js';
|
|
27
|
-
import { createImagePlugin } from '../plugins/image/ImagePlugin.svelte';
|
|
28
24
|
import { createSuggestionsPlugin } from '../plugins/suggestions/suggestionsPlugin.js';
|
|
29
25
|
|
|
30
26
|
/**
|
|
31
27
|
* The plugins every view has unless it lists one of their kind (or sets
|
|
32
28
|
* `defaultPlugins={false}`), after the app's so its kinds and keys win:
|
|
33
|
-
*
|
|
29
|
+
* the suggestion UI, then rich text (an app lists `createArrowMovePlugin()`
|
|
30
|
+
* for block moves and `createImagePlugin({ upload })` for images itself).
|
|
34
31
|
*/
|
|
35
|
-
const DEFAULTS = [
|
|
36
|
-
createArrowMovePlugin(),
|
|
37
|
-
createSuggestionsPlugin(),
|
|
38
|
-
createImagePlugin(),
|
|
39
|
-
createRichTextPlugin()
|
|
40
|
-
];
|
|
32
|
+
const DEFAULTS = [createSuggestionsPlugin(), createRichTextPlugin()];
|
|
41
33
|
const withDefaults = (plugins: Plugin[] = []): Plugin[] => {
|
|
42
34
|
const listed = new Set(plugins.map(brandOf));
|
|
43
35
|
return [...plugins, ...DEFAULTS.filter((plugin) => !listed.has(brandOf(plugin)))];
|
|
@@ -67,7 +59,7 @@
|
|
|
67
59
|
|
|
68
60
|
export type EdytorProps = {
|
|
69
61
|
plugins?: Plugin[];
|
|
70
|
-
/** Add rich text
|
|
62
|
+
/** Add rich text and the suggestion UI unless `plugins` lists them (default `true`). */
|
|
71
63
|
defaultPlugins?: boolean;
|
|
72
64
|
class?: string;
|
|
73
65
|
/** The root textbox's accessible name (or name it with `aria-labelledby`). */
|
|
@@ -81,8 +73,6 @@
|
|
|
81
73
|
edytor?: EdytorClass;
|
|
82
74
|
/** The assembled document this view renders (see `crdt/document.ts`). */
|
|
83
75
|
document?: EdytorDocument;
|
|
84
|
-
doc?: YDoc;
|
|
85
|
-
awareness?: Awareness;
|
|
86
76
|
/** The local author (id, name, color) of the document this view owns: attribution and presence. */
|
|
87
77
|
actor?: DocumentActor;
|
|
88
78
|
/**
|
|
@@ -96,6 +86,13 @@
|
|
|
96
86
|
* the connection.
|
|
97
87
|
*/
|
|
98
88
|
readonly?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Let users nest blocks (default `false`): Tab and Shift+Tab nest and
|
|
91
|
+
* unnest, drops and handle keys may place a block inside another. Off,
|
|
92
|
+
* Tab is left to the browser and no gesture nests; containers keep their
|
|
93
|
+
* bodies. Read once.
|
|
94
|
+
*/
|
|
95
|
+
allowNesting?: boolean;
|
|
99
96
|
/** Chords (`mod+s`, `shift+alt+enter`) the view binds before plugins and built-ins. Read once. */
|
|
100
97
|
hotkeys?: Partial<Record<HotKeyCombination, HotKey>>;
|
|
101
98
|
/** After every commit that changed the visible document: a `JSONDoc`, the type `value` takes. */
|
|
@@ -186,8 +183,8 @@
|
|
|
186
183
|
edytor = $bindable(),
|
|
187
184
|
// Aliased so the DOM `document` global keeps working in this scope.
|
|
188
185
|
document: edytorDocument,
|
|
189
|
-
doc,
|
|
190
186
|
readonly = false,
|
|
187
|
+
allowNesting = false,
|
|
191
188
|
value = defaultValue,
|
|
192
189
|
hotkeys,
|
|
193
190
|
sync,
|
|
@@ -198,7 +195,6 @@
|
|
|
198
195
|
onSyncRefused,
|
|
199
196
|
requireHydration,
|
|
200
197
|
snapshot,
|
|
201
|
-
awareness,
|
|
202
198
|
actor,
|
|
203
199
|
presence,
|
|
204
200
|
onChange,
|
|
@@ -216,10 +212,9 @@
|
|
|
216
212
|
|
|
217
213
|
const initialEdytorOptions = untrack(() => ({
|
|
218
214
|
readonly,
|
|
215
|
+
allowNesting,
|
|
219
216
|
plugins: defaultPlugins ? withDefaults(userPlugins) : userPlugins,
|
|
220
217
|
document: edytorDocument,
|
|
221
|
-
doc,
|
|
222
|
-
awareness,
|
|
223
218
|
actor,
|
|
224
219
|
requireHydration,
|
|
225
220
|
hotkeys,
|
|
@@ -291,7 +286,7 @@
|
|
|
291
286
|
}
|
|
292
287
|
});
|
|
293
288
|
|
|
294
|
-
// The component owns the Edytor — release its
|
|
289
|
+
// The component owns the Edytor — release its facade/
|
|
295
290
|
// undo-manager listeners so a shared doc doesn't retain dead mounts.
|
|
296
291
|
// `onDestroy` also runs after a server render: a view-owned document
|
|
297
292
|
// (and its awareness timer) must not outlive the request.
|
|
@@ -408,6 +403,29 @@
|
|
|
408
403
|
{/if}
|
|
409
404
|
|
|
410
405
|
<style>
|
|
406
|
+
/* Where a drag lands, a block handle's or files' from outside (`DropIndicator`):
|
|
407
|
+
* the bar (4px) and the nest backdrop, placed in the overlay and shown
|
|
408
|
+
* (`data-shown`) by the drag; their look is a theme's (`edytor/themes/notion.css`). */
|
|
409
|
+
:global([data-edytor-drop-indicator]),
|
|
410
|
+
:global([data-edytor-drop-backdrop]) {
|
|
411
|
+
position: absolute;
|
|
412
|
+
pointer-events: none;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
:global([data-edytor-drop-indicator]) {
|
|
416
|
+
z-index: 100;
|
|
417
|
+
box-sizing: border-box;
|
|
418
|
+
height: 4px;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
:global([data-edytor-drop-backdrop]) {
|
|
422
|
+
z-index: 99;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
:global([data-edytor-drop-backdrop]:not([data-shown='true'])) {
|
|
426
|
+
visibility: hidden;
|
|
427
|
+
}
|
|
428
|
+
|
|
411
429
|
/*
|
|
412
430
|
* Room below the last block, where a press puts the caret in a trailing
|
|
413
431
|
* paragraph (`nav.trailing.press`): `--edytor-trailing-space`. No
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Edytor as EdytorClass, useEdytor } from '../edytor.svelte.js';
|
|
2
|
-
import { SyncRefusedError, type
|
|
2
|
+
import { SyncRefusedError, type DocChange, type DocumentActor, type EdytorDocument } from '../crdt/index.js';
|
|
3
3
|
import type { EdytorSync, WebsocketSyncOptions } from '../collaboration/index.js';
|
|
4
4
|
import { type PresenceOptions } from '../collaboration/awarenessSelection.js';
|
|
5
5
|
export { EdytorClass as EdytorContext, useEdytor };
|
|
@@ -7,7 +7,7 @@ import type { Plugin } from '../plugins.js';
|
|
|
7
7
|
import type { PartialLabels } from '../labels.js';
|
|
8
8
|
export type EdytorProps = {
|
|
9
9
|
plugins?: Plugin[];
|
|
10
|
-
/** Add rich text
|
|
10
|
+
/** Add rich text and the suggestion UI unless `plugins` lists them (default `true`). */
|
|
11
11
|
defaultPlugins?: boolean;
|
|
12
12
|
class?: string;
|
|
13
13
|
/** The root textbox's accessible name (or name it with `aria-labelledby`). */
|
|
@@ -21,8 +21,6 @@ export type EdytorProps = {
|
|
|
21
21
|
edytor?: EdytorClass;
|
|
22
22
|
/** The assembled document this view renders (see `crdt/document.ts`). */
|
|
23
23
|
document?: EdytorDocument;
|
|
24
|
-
doc?: YDoc;
|
|
25
|
-
awareness?: Awareness;
|
|
26
24
|
/** The local author (id, name, color) of the document this view owns: attribution and presence. */
|
|
27
25
|
actor?: DocumentActor;
|
|
28
26
|
/**
|
|
@@ -36,6 +34,13 @@ export type EdytorProps = {
|
|
|
36
34
|
* the connection.
|
|
37
35
|
*/
|
|
38
36
|
readonly?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Let users nest blocks (default `false`): Tab and Shift+Tab nest and
|
|
39
|
+
* unnest, drops and handle keys may place a block inside another. Off,
|
|
40
|
+
* Tab is left to the browser and no gesture nests; containers keep their
|
|
41
|
+
* bodies. Read once.
|
|
42
|
+
*/
|
|
43
|
+
allowNesting?: boolean;
|
|
39
44
|
/** Chords (`mod+s`, `shift+alt+enter`) the view binds before plugins and built-ins. Read once. */
|
|
40
45
|
hotkeys?: Partial<Record<HotKeyCombination, HotKey>>;
|
|
41
46
|
/** After every commit that changed the visible document: a `JSONDoc`, the type `value` takes. */
|
package/dist/crdt/admission.d.ts
CHANGED
|
@@ -4,14 +4,12 @@
|
|
|
4
4
|
* Every way content enters an {@link EdytorDocument} crosses the same
|
|
5
5
|
* gate here, in the same order, with the same refusal semantics:
|
|
6
6
|
*
|
|
7
|
-
* 1. **`
|
|
8
|
-
* ({@link UnsupportedDocError} `'foreign'`)?
|
|
9
|
-
* 2. **`assertSchema`** — does the committed schema manifest allow
|
|
7
|
+
* 1. **`assertSchema`** — does the committed schema manifest allow
|
|
10
8
|
* this build to own the doc ({@link SchemaMismatchError}:
|
|
11
9
|
* `'unversioned'` content with no version stamp, `'unsupported'`
|
|
12
10
|
* version this build cannot speak, `'foreign'` manifest naming
|
|
13
11
|
* another schema)?
|
|
14
|
-
*
|
|
12
|
+
* 2. **Verdict** — `'fresh'` (registry empty — seeding is legal) or
|
|
15
13
|
* `'initialized'` (supported schema content present — assert and
|
|
16
14
|
* adopt, never seed).
|
|
17
15
|
*
|
|
@@ -59,10 +57,10 @@
|
|
|
59
57
|
* readiness transition, and a pending document never writes or
|
|
60
58
|
* broadcasts a bootstrap block.
|
|
61
59
|
*/
|
|
62
|
-
import { assertSchema,
|
|
60
|
+
import { assertSchema, checkSchema, isInitialized, registryEmpty, schemaVersion, SchemaMismatchError } from './edytor-doc.js';
|
|
63
61
|
import type { SchemaProblem } from './edytor-doc.js';
|
|
64
62
|
import type { EngineApi, EngineDoc, YDoc } from './engine-api.js';
|
|
65
|
-
export { assertSchema,
|
|
63
|
+
export { assertSchema, checkSchema, isInitialized, registryEmpty, schemaVersion, SchemaMismatchError };
|
|
66
64
|
export type { SchemaProblem };
|
|
67
65
|
/**
|
|
68
66
|
* What an admitted doc carries — the `sync()` readiness decision:
|
|
@@ -83,7 +81,7 @@ export type AdmissionResult = {
|
|
|
83
81
|
verdict: AdmissionVerdict;
|
|
84
82
|
} | {
|
|
85
83
|
admitted: false;
|
|
86
|
-
error:
|
|
84
|
+
error: SchemaMismatchError;
|
|
87
85
|
};
|
|
88
86
|
/**
|
|
89
87
|
* Raised when a candidate update cannot even be decoded or integrated —
|
|
@@ -102,9 +100,6 @@ export declare class UndecodableUpdateError extends Error {
|
|
|
102
100
|
* The document-level admission gate — runs the ordered checks and throws
|
|
103
101
|
* the typed refusal:
|
|
104
102
|
*
|
|
105
|
-
* - {@link UnsupportedDocError} `'foreign'` — not a v14 engine doc
|
|
106
|
-
* (foreign roots/engines may still attach *content* later; this is
|
|
107
|
-
* about doc shape);
|
|
108
103
|
* - {@link SchemaMismatchError} `'unversioned' | 'unsupported' |
|
|
109
104
|
* 'foreign'` — a schema claim this build cannot own.
|
|
110
105
|
*
|
package/dist/crdt/admission.js
CHANGED
|
@@ -4,14 +4,12 @@
|
|
|
4
4
|
* Every way content enters an {@link EdytorDocument} crosses the same
|
|
5
5
|
* gate here, in the same order, with the same refusal semantics:
|
|
6
6
|
*
|
|
7
|
-
* 1. **`
|
|
8
|
-
* ({@link UnsupportedDocError} `'foreign'`)?
|
|
9
|
-
* 2. **`assertSchema`** — does the committed schema manifest allow
|
|
7
|
+
* 1. **`assertSchema`** — does the committed schema manifest allow
|
|
10
8
|
* this build to own the doc ({@link SchemaMismatchError}:
|
|
11
9
|
* `'unversioned'` content with no version stamp, `'unsupported'`
|
|
12
10
|
* version this build cannot speak, `'foreign'` manifest naming
|
|
13
11
|
* another schema)?
|
|
14
|
-
*
|
|
12
|
+
* 2. **Verdict** — `'fresh'` (registry empty — seeding is legal) or
|
|
15
13
|
* `'initialized'` (supported schema content present — assert and
|
|
16
14
|
* adopt, never seed).
|
|
17
15
|
*
|
|
@@ -59,11 +57,11 @@
|
|
|
59
57
|
* readiness transition, and a pending document never writes or
|
|
60
58
|
* broadcasts a bootstrap block.
|
|
61
59
|
*/
|
|
62
|
-
import { assertSchema,
|
|
60
|
+
import { assertSchema, checkSchema, isInitialized, registryEmpty, schemaVersion, SchemaMismatchError } from './edytor-doc.js';
|
|
63
61
|
import { asEngineDoc } from './structs.js';
|
|
64
62
|
import { keepingReplaced } from './incarnations.js';
|
|
65
63
|
// ── shared gate vocabulary (one doorway for document + transport layers) ──
|
|
66
|
-
export { assertSchema,
|
|
64
|
+
export { assertSchema, checkSchema, isInitialized, registryEmpty, schemaVersion, SchemaMismatchError };
|
|
67
65
|
/**
|
|
68
66
|
* Raised when a candidate update cannot even be decoded or integrated —
|
|
69
67
|
* refused before the schema question is ever asked. Distinct from the
|
|
@@ -86,9 +84,6 @@ export class UndecodableUpdateError extends Error {
|
|
|
86
84
|
* The document-level admission gate — runs the ordered checks and throws
|
|
87
85
|
* the typed refusal:
|
|
88
86
|
*
|
|
89
|
-
* - {@link UnsupportedDocError} `'foreign'` — not a v14 engine doc
|
|
90
|
-
* (foreign roots/engines may still attach *content* later; this is
|
|
91
|
-
* about doc shape);
|
|
92
87
|
* - {@link SchemaMismatchError} `'unversioned' | 'unsupported' |
|
|
93
88
|
* 'foreign'` — a schema claim this build cannot own.
|
|
94
89
|
*
|
|
@@ -98,7 +93,6 @@ export class UndecodableUpdateError extends Error {
|
|
|
98
93
|
* readiness decision.
|
|
99
94
|
*/
|
|
100
95
|
export const assertAdmission = (doc, docName = 'document') => {
|
|
101
|
-
assertUsableDoc(doc);
|
|
102
96
|
assertSchema(doc, docName);
|
|
103
97
|
return isInitialized(doc) ? 'initialized' : 'fresh';
|
|
104
98
|
};
|
|
@@ -112,7 +106,7 @@ export const inspectAdmission = (doc, docName = 'document') => {
|
|
|
112
106
|
return { admitted: true, verdict: assertAdmission(doc, docName) };
|
|
113
107
|
}
|
|
114
108
|
catch (error) {
|
|
115
|
-
if (error instanceof
|
|
109
|
+
if (error instanceof SchemaMismatchError) {
|
|
116
110
|
return { admitted: false, error };
|
|
117
111
|
}
|
|
118
112
|
throw error;
|
package/dist/crdt/data.d.ts
CHANGED
|
@@ -73,6 +73,16 @@ export declare const readData: (node: EngineNode) => JsonObj | undefined;
|
|
|
73
73
|
* in `node`'s data, in order; `[]` where no array is.
|
|
74
74
|
*/
|
|
75
75
|
export declare const itemIds: (node: EngineNode, path: readonly string[]) => string[];
|
|
76
|
+
/**
|
|
77
|
+
* `node`'s data as it reads once `patches` are applied (nothing written),
|
|
78
|
+
* with the ids of the array items at a path (`itemIds`): an item a patch
|
|
79
|
+
* makes has no id yet (`''`), so a later patch names it by index.
|
|
80
|
+
* `null` when a patch fits no value.
|
|
81
|
+
*/
|
|
82
|
+
export declare const previewData: (node: EngineNode | undefined, patches: readonly DataPatch[]) => {
|
|
83
|
+
data: JsonObj;
|
|
84
|
+
ids: (path: readonly string[]) => string[];
|
|
85
|
+
} | null;
|
|
76
86
|
/** `data` with `patches` applied in order, as a fresh value (a root patch replaces it); `null` when one fits no value. */
|
|
77
87
|
export declare const applyPatch: (data: JsonObj, patches: readonly DataPatch[]) => JsonObj | null;
|
|
78
88
|
/**
|
package/dist/crdt/data.js
CHANGED
|
@@ -354,8 +354,9 @@ export const readData = (node) => valueOf(treeOf(leavesOf(node)));
|
|
|
354
354
|
* The ids of the items of the array at `path` (keys, item indexes or ids)
|
|
355
355
|
* in `node`'s data, in order; `[]` where no array is.
|
|
356
356
|
*/
|
|
357
|
-
export const itemIds = (node, path) =>
|
|
358
|
-
|
|
357
|
+
export const itemIds = (node, path) => idsAt(treeOf(leavesOf(node)), path);
|
|
358
|
+
const idsAt = (root, path) => {
|
|
359
|
+
let t = root;
|
|
359
360
|
for (const k of path)
|
|
360
361
|
if (t && isArr(open(t))) {
|
|
361
362
|
const item = itemAt(itemsOf(t), k);
|
|
@@ -365,6 +366,23 @@ export const itemIds = (node, path) => {
|
|
|
365
366
|
t = itemish(k) ? undefined : t?.k.get(enc(segmentKey(k)));
|
|
366
367
|
return t ? itemsOf(open(t)).map(({ id }) => id) : [];
|
|
367
368
|
};
|
|
369
|
+
/**
|
|
370
|
+
* `node`'s data as it reads once `patches` are applied (nothing written),
|
|
371
|
+
* with the ids of the array items at a path (`itemIds`): an item a patch
|
|
372
|
+
* makes has no id yet (`''`), so a later patch names it by index.
|
|
373
|
+
* `null` when a patch fits no value.
|
|
374
|
+
*/
|
|
375
|
+
export const previewData = (node, patches) => {
|
|
376
|
+
const base = node ? leavesOf(node) : new Map();
|
|
377
|
+
const root = treeOf(base);
|
|
378
|
+
if (!patches.every((p) => edits().apply(root, p)))
|
|
379
|
+
return null;
|
|
380
|
+
const known = new Set([...base.keys()].flatMap((key) => key.split('/')));
|
|
381
|
+
return {
|
|
382
|
+
data: valueOf(root) ?? {},
|
|
383
|
+
ids: (path) => idsAt(root, path).map((id) => (known.has(`${id}#`) ? id : ''))
|
|
384
|
+
};
|
|
385
|
+
};
|
|
368
386
|
/** `data` with `patches` applied in order, as a fresh value (a root patch replaces it); `null` when one fits no value. */
|
|
369
387
|
export const applyPatch = (data, patches) => {
|
|
370
388
|
const root = treeOf(dataLeaves(data));
|
|
@@ -2,13 +2,14 @@ import type { BlockAttributionApi } from '../attribution/block.js';
|
|
|
2
2
|
import type { EdytorDocConfig, OpResult, Prepared } from './types.js';
|
|
3
3
|
import type { DocBase, DocReads } from './reads.js';
|
|
4
4
|
/**
|
|
5
|
-
* Raised when
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* bare `bindEdytorDoc` facade has no document
|
|
5
|
+
* Raised when an operation runs against a torn-down document: every attach
|
|
6
|
+
* reference released, or its facade disposed (terminal for writes; reads
|
|
7
|
+
* stay dead-safe: a stale handle may still be inspected). Distinct from
|
|
8
|
+
* `DocumentNotReadyError`: destroyed is terminal, pending may still resolve.
|
|
9
|
+
* Kept in this module because a bare `bindEdytorDoc` facade has no document
|
|
10
|
+
* layer above it.
|
|
10
11
|
*/
|
|
11
|
-
export declare class
|
|
12
|
+
export declare class DocumentDestroyedError extends Error {
|
|
12
13
|
constructor(service?: string);
|
|
13
14
|
}
|
|
14
15
|
/** What the write funnel writes with: the reads, the attribution writer and the facade's configuration. */
|
package/dist/crdt/doc/funnel.js
CHANGED
|
@@ -11,16 +11,17 @@ import { writeLeaves } from '../data.js';
|
|
|
11
11
|
import { NOOP } from './plan.js';
|
|
12
12
|
import { claimInto, deleteRange, formatRangeIn, insertIntoText } from '../text/model.js';
|
|
13
13
|
/**
|
|
14
|
-
* Raised when
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* bare `bindEdytorDoc` facade has no document
|
|
14
|
+
* Raised when an operation runs against a torn-down document: every attach
|
|
15
|
+
* reference released, or its facade disposed (terminal for writes; reads
|
|
16
|
+
* stay dead-safe: a stale handle may still be inspected). Distinct from
|
|
17
|
+
* `DocumentNotReadyError`: destroyed is terminal, pending may still resolve.
|
|
18
|
+
* Kept in this module because a bare `bindEdytorDoc` facade has no document
|
|
19
|
+
* layer above it.
|
|
19
20
|
*/
|
|
20
|
-
export class
|
|
21
|
+
export class DocumentDestroyedError extends Error {
|
|
21
22
|
constructor(service) {
|
|
22
|
-
super(
|
|
23
|
-
this.name = '
|
|
23
|
+
super(`${service ?? 'document'}: the document is destroyed`);
|
|
24
|
+
this.name = 'DocumentDestroyedError';
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
/** The write funnel of one facade. */
|
|
@@ -37,7 +38,7 @@ export const writeFunnel = (c) => {
|
|
|
37
38
|
/** Mutation boundary — every facade write funnels through `write`. */
|
|
38
39
|
const write = (fn) => {
|
|
39
40
|
if (disposed) {
|
|
40
|
-
throw new
|
|
41
|
+
throw new DocumentDestroyedError();
|
|
41
42
|
}
|
|
42
43
|
config.assertWritable?.();
|
|
43
44
|
return fn();
|