edytor 0.1.0-next.21 → 0.1.0-next.23
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 +1 -1
- package/dist/cloudflare/DocumentRoom.d.ts +302 -10
- package/dist/cloudflare/DocumentRoom.js +965 -88
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/routeDocumentSocket.js +30 -6
- package/dist/collaboration/awarenessSelection.d.ts +7 -1
- package/dist/collaboration/awarenessSelection.js +7 -1
- package/dist/collaboration/index.d.ts +2 -2
- package/dist/collaboration/index.js +2 -2
- package/dist/collaboration/providers.d.ts +4 -0
- package/dist/collaboration/providers.js +4 -0
- package/dist/components/Edytor.svelte +14 -2
- package/dist/components/Edytor.svelte.d.ts +8 -1
- package/dist/crdt/admission.d.ts +6 -1
- package/dist/crdt/admission.js +9 -2
- package/dist/crdt/data.d.ts +3 -1
- package/dist/crdt/data.js +42 -32
- package/dist/crdt/document.d.ts +31 -8
- package/dist/crdt/document.js +53 -18
- package/dist/crdt/edytor-doc.d.ts +31 -3
- package/dist/crdt/edytor-doc.js +131 -14
- package/dist/crdt/engine.d.ts +6 -0
- package/dist/crdt/engine.js +7 -0
- package/dist/crdt/flow.d.ts +2 -0
- package/dist/crdt/flow.js +2 -2
- package/dist/crdt/index.d.ts +20 -9
- package/dist/crdt/index.js +3 -3
- package/dist/crdt/migration/migrate.js +9 -3
- package/dist/crdt/placement/model.d.ts +9 -1
- package/dist/crdt/placement/model.js +29 -10
- package/dist/crdt/placement/rank.d.ts +20 -0
- package/dist/crdt/placement/rank.js +79 -0
- package/dist/crdt/protocols/envelope.d.ts +20 -1
- package/dist/crdt/protocols/envelope.js +14 -2
- package/dist/crdt/providers/container.d.ts +15 -0
- package/dist/crdt/providers/container.js +17 -3
- package/dist/crdt/providers/index.d.ts +21 -1
- package/dist/crdt/providers/index.js +86 -3
- package/dist/crdt/providers/indexeddb.js +60 -40
- package/dist/crdt/providers/room.d.ts +18 -2
- package/dist/crdt/providers/room.js +29 -5
- package/dist/crdt/providers/websocket.d.ts +9 -1
- package/dist/crdt/providers/websocket.js +21 -6
- package/dist/crdt/storage.d.ts +21 -0
- package/dist/crdt/storage.js +37 -0
- package/dist/crdt/structs.d.ts +17 -0
- package/dist/crdt/structs.js +41 -0
- package/dist/crdt/text/model.d.ts +9 -1
- package/dist/crdt/text/model.js +24 -14
- package/dist/crdt/text/rows.d.ts +94 -0
- package/dist/crdt/text/rows.js +193 -0
- package/dist/crdt/text/runs.d.ts +30 -10
- package/dist/crdt/text/runs.js +1235 -264
- package/dist/edytor.svelte.d.ts +3 -1
- package/dist/edytor.svelte.js +9 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/plugins/image/ImageEmpty.svelte +19 -6
- package/dist/plugins/image/ImagePlugin.svelte +9 -3
- package/dist/plugins/image/ImagePlugin.svelte.d.ts +2 -2
- package/dist/plugins/image/image.d.ts +16 -0
- package/dist/plugins/image/image.js +19 -0
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/plugins.d.ts +11 -0
- package/dist/session/commands.d.ts +1 -0
- package/dist/session/commands.js +4 -1
- package/package.json +1 -1
package/dist/edytor.svelte.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export type EdytorOptions = {
|
|
|
43
43
|
awareness?: Awareness;
|
|
44
44
|
/** The local author of a view-owned document (with `document`, set it there). */
|
|
45
45
|
actor?: DocumentActor;
|
|
46
|
+
/** A view-owned document's `requireHydration` (with `document`, set it there). */
|
|
47
|
+
requireHydration?: boolean;
|
|
46
48
|
/** What this view shares of its selection with peers, and how often (`edytor.presence`). */
|
|
47
49
|
presence?: PresenceOptions;
|
|
48
50
|
sync?: boolean;
|
|
@@ -193,7 +195,7 @@ export declare class Edytor {
|
|
|
193
195
|
*/
|
|
194
196
|
historyUndo: () => void;
|
|
195
197
|
historyRedo: () => void;
|
|
196
|
-
constructor({ snippets, readonly, hotKeys, plugins, document, doc, awareness, actor, presence, sync, value, onSelectionChange, placeholder, onChange }: EdytorOptions);
|
|
198
|
+
constructor({ snippets, readonly, hotKeys, plugins, document, doc, awareness, actor, requireHydration, presence, sync, value, onSelectionChange, placeholder, onChange }: EdytorOptions);
|
|
197
199
|
/** Kinds already reported missing (DEV, once each). */
|
|
198
200
|
private unknownKinds;
|
|
199
201
|
/**
|
package/dist/edytor.svelte.js
CHANGED
|
@@ -247,10 +247,13 @@ export class Edytor {
|
|
|
247
247
|
: 'noop';
|
|
248
248
|
this.dispatcher.last = { operation: command, status };
|
|
249
249
|
};
|
|
250
|
-
constructor({ snippets, readonly, hotKeys, plugins, document, doc, awareness, actor, presence, sync, value, onSelectionChange, placeholder, onChange }) {
|
|
250
|
+
constructor({ snippets, readonly, hotKeys, plugins, document, doc, awareness, actor, requireHydration, presence, sync, value, onSelectionChange, placeholder, onChange }) {
|
|
251
251
|
if (document !== undefined) {
|
|
252
|
-
if (doc !== undefined ||
|
|
253
|
-
|
|
252
|
+
if (doc !== undefined ||
|
|
253
|
+
awareness !== undefined ||
|
|
254
|
+
actor !== undefined ||
|
|
255
|
+
requireHydration !== undefined) {
|
|
256
|
+
throw new Error('EdytorOptions: `document` cannot be combined with `doc`/`awareness`/`actor`/`requireHydration` — ' +
|
|
254
257
|
'the document owns them (compose them via attachDocument first).');
|
|
255
258
|
}
|
|
256
259
|
this.document = document;
|
|
@@ -258,7 +261,7 @@ export class Edytor {
|
|
|
258
261
|
else {
|
|
259
262
|
// Legacy path — the view internally owns a document composed
|
|
260
263
|
// around the injected (or a fresh) doc/awareness.
|
|
261
|
-
this.document = attachDocument(doc ?? new Y.Doc(), { awareness, actor });
|
|
264
|
+
this.document = attachDocument(doc ?? new Y.Doc(), { awareness, actor, requireHydration });
|
|
262
265
|
this.ownsDocument = true;
|
|
263
266
|
}
|
|
264
267
|
this.readonly = readonly || false;
|
|
@@ -323,9 +326,9 @@ export class Edytor {
|
|
|
323
326
|
// ANYTHING is applied, so a failed view cannot half-seed semantics.
|
|
324
327
|
const blocks = Array.from(this.blocks);
|
|
325
328
|
this.document.adoptSemantics({
|
|
326
|
-
roles: Object.fromEntries(blocks.map(([type, { void: v, island, lines, layout }]) => [
|
|
329
|
+
roles: Object.fromEntries(blocks.map(([type, { void: v, island, lines, layout, atomic }]) => [
|
|
327
330
|
type,
|
|
328
|
-
{ void: v, island, lines, layout }
|
|
331
|
+
{ void: v, island, lines, layout, atomic }
|
|
329
332
|
])),
|
|
330
333
|
rendersContent: Object.fromEntries(blocks.map(([type, definition]) => [type, definition.rendersContent !== false])),
|
|
331
334
|
defaultChild
|
package/dist/index.d.ts
CHANGED
|
@@ -41,4 +41,4 @@ export type { Suggestion, Suggestions, SuggestionAt, SuggestionContent, Suggesti
|
|
|
41
41
|
export { textToBlocks, type TextToBlocksOptions } from './clipboard/textBlocks.js';
|
|
42
42
|
export * from './plugins/index.js';
|
|
43
43
|
export * from './crdt/index.js';
|
|
44
|
-
export { createIndexeddbSync, createWebsocketSync, clearDocument, storeState, IndexeddbPersistence, WebsocketProvider, PresenceWriter, type PresenceOptions, type PresenceShare } from './collaboration/index.js';
|
|
44
|
+
export { createIndexeddbSync, createWebsocketSync, clearDocument, storeState, IndexeddbPersistence, WebsocketProvider, prefetch, lastUpdated, PresenceWriter, DEFAULT_PRESENCE_THROTTLE, type PresenceOptions, type PresenceShare } from './collaboration/index.js';
|
package/dist/index.js
CHANGED
|
@@ -39,4 +39,4 @@ export * from './crdt/index.js';
|
|
|
39
39
|
// needs no `edytor/crdt` import for the common IndexedDB/websocket cases.
|
|
40
40
|
// The `EdytorSync*`/`IndexeddbSyncOptions`/`WebsocketSyncOptions` types are
|
|
41
41
|
// already re-exported through `./crdt/index.js` above.
|
|
42
|
-
export { createIndexeddbSync, createWebsocketSync, clearDocument, storeState, IndexeddbPersistence, WebsocketProvider, PresenceWriter } from './collaboration/index.js';
|
|
42
|
+
export { createIndexeddbSync, createWebsocketSync, clearDocument, storeState, IndexeddbPersistence, WebsocketProvider, prefetch, lastUpdated, PresenceWriter, DEFAULT_PRESENCE_THROTTLE } from './collaboration/index.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Block } from '../../block/block.svelte.js';
|
|
3
|
-
import { safeImageSrc } from './image.js';
|
|
3
|
+
import { MAX_INLINE_IMAGE_BYTES, oversizedInlineImage, safeImageSrc } from './image.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Notion's empty image: "Add an image", then a link field (and Upload with
|
|
@@ -12,12 +12,15 @@
|
|
|
12
12
|
$props();
|
|
13
13
|
let draft = $state('');
|
|
14
14
|
let open = $state(false);
|
|
15
|
-
|
|
15
|
+
/** Why the last link or upload was not embedded (`null`: it was). */
|
|
16
|
+
let failed = $state<'invalid' | 'inline' | null>(null);
|
|
17
|
+
const inlineLimit = `${MAX_INLINE_IMAGE_BYTES / (1024 * 1024)} MB`;
|
|
16
18
|
|
|
17
19
|
const embed = (value: string) => {
|
|
18
20
|
const src = safeImageSrc(value);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
// An inline image over the cap is never stored (H6): it would weigh on every sync.
|
|
22
|
+
failed = !src ? 'invalid' : oversizedInlineImage(src) ? 'inline' : null;
|
|
23
|
+
if (src && failed === null && block) block.data.src = src;
|
|
21
24
|
};
|
|
22
25
|
</script>
|
|
23
26
|
|
|
@@ -57,13 +60,23 @@
|
|
|
57
60
|
try {
|
|
58
61
|
embed(await upload(file));
|
|
59
62
|
} catch {
|
|
60
|
-
failed =
|
|
63
|
+
failed = 'invalid';
|
|
61
64
|
}
|
|
62
65
|
}}
|
|
63
66
|
/>
|
|
64
67
|
</label>
|
|
65
68
|
{/if}
|
|
66
|
-
{#if failed
|
|
69
|
+
{#if failed === 'inline'}
|
|
70
|
+
<small data-edytor-image-error="inline"
|
|
71
|
+
>Inline images are limited to {inlineLimit}: {upload
|
|
72
|
+
? 'upload the file instead'
|
|
73
|
+
: 'host the image and paste its link'}.</small
|
|
74
|
+
>
|
|
75
|
+
{:else if failed}
|
|
76
|
+
<small data-edytor-image-error="invalid"
|
|
77
|
+
>That doesn't look like an image link or upload.</small
|
|
78
|
+
>
|
|
79
|
+
{/if}
|
|
67
80
|
</div>
|
|
68
81
|
{/if}
|
|
69
82
|
{/if}
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
import type { Block } from '../../block/block.svelte.js';
|
|
4
4
|
import ImageEmpty from './ImageEmpty.svelte';
|
|
5
5
|
import { imageKinds } from '../../crdt/semantics.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
MAX_INLINE_IMAGE_BYTES,
|
|
8
|
+
oversizedInlineImage,
|
|
9
|
+
safeImageSrc,
|
|
10
|
+
storableImageSrc
|
|
11
|
+
} from './image.js';
|
|
7
12
|
|
|
8
|
-
export { safeImageSrc };
|
|
13
|
+
export { MAX_INLINE_IMAGE_BYTES, oversizedInlineImage, safeImageSrc, storableImageSrc };
|
|
9
14
|
|
|
10
15
|
export type ImagePluginOptions = {
|
|
11
16
|
/** Upload a picked file and answer its URL; without it only links are embedded. */
|
|
@@ -48,10 +53,11 @@
|
|
|
48
53
|
const src = safeImageSrc(block.data?.src);
|
|
49
54
|
return `<figure>${src ? `<img src="${escape(src)}" alt="">` : ''}<figcaption>${caption}</figcaption></figure>`;
|
|
50
55
|
},
|
|
56
|
+
// A pasted inline image over the cap is not imported (H6).
|
|
51
57
|
parse: (el) => {
|
|
52
58
|
const src =
|
|
53
59
|
el.localName === 'figure' &&
|
|
54
|
-
|
|
60
|
+
storableImageSrc(el.querySelector('img')?.getAttribute('src'));
|
|
55
61
|
return src ? { src } : undefined;
|
|
56
62
|
}
|
|
57
63
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Plugin } from '../../plugins.js';
|
|
2
|
-
import { safeImageSrc } from './image.js';
|
|
3
|
-
export { safeImageSrc };
|
|
2
|
+
import { MAX_INLINE_IMAGE_BYTES, oversizedInlineImage, safeImageSrc, storableImageSrc } from './image.js';
|
|
3
|
+
export { MAX_INLINE_IMAGE_BYTES, oversizedInlineImage, safeImageSrc, storableImageSrc };
|
|
4
4
|
export type ImagePluginOptions = {
|
|
5
5
|
/** Upload a picked file and answer its URL; without it only links are embedded. */
|
|
6
6
|
upload?: (file: File) => Promise<string>;
|
|
@@ -1,2 +1,18 @@
|
|
|
1
1
|
/** An image source the block may render: http(s), blob, or an inline image. */
|
|
2
2
|
export declare const safeImageSrc: (value: unknown) => string | null;
|
|
3
|
+
/**
|
|
4
|
+
* The largest inline image (`data:` URL) an image block takes: 1 MiB of its
|
|
5
|
+
* URL (H6). An inline image is stored in the document and sent in every
|
|
6
|
+
* update and catch-up that carries the block, so a larger one would bloat
|
|
7
|
+
* the room's frames and storage: host the file instead (the plugin's
|
|
8
|
+
* `upload` option) and embed its URL.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MAX_INLINE_IMAGE_BYTES: number;
|
|
11
|
+
/** Is `src` an inline (`data:`) image larger than {@link MAX_INLINE_IMAGE_BYTES}? */
|
|
12
|
+
export declare const oversizedInlineImage: (src: string) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* An image source a block may store: {@link safeImageSrc}'s, but no
|
|
15
|
+
* inline image over {@link MAX_INLINE_IMAGE_BYTES}. Rendering still takes
|
|
16
|
+
* any safe source, so a document that holds a larger one shows it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const storableImageSrc: (value: unknown) => string | null;
|
|
@@ -5,3 +5,22 @@ export const safeImageSrc = (value) => {
|
|
|
5
5
|
const src = value.trim();
|
|
6
6
|
return /^(https?:|blob:)/i.test(src) || /^data:image\/[a-z0-9.+-]+[;,]/i.test(src) ? src : null;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* The largest inline image (`data:` URL) an image block takes: 1 MiB of its
|
|
10
|
+
* URL (H6). An inline image is stored in the document and sent in every
|
|
11
|
+
* update and catch-up that carries the block, so a larger one would bloat
|
|
12
|
+
* the room's frames and storage: host the file instead (the plugin's
|
|
13
|
+
* `upload` option) and embed its URL.
|
|
14
|
+
*/
|
|
15
|
+
export const MAX_INLINE_IMAGE_BYTES = 1024 * 1024;
|
|
16
|
+
/** Is `src` an inline (`data:`) image larger than {@link MAX_INLINE_IMAGE_BYTES}? */
|
|
17
|
+
export const oversizedInlineImage = (src) => /^data:/i.test(src) && src.length > MAX_INLINE_IMAGE_BYTES;
|
|
18
|
+
/**
|
|
19
|
+
* An image source a block may store: {@link safeImageSrc}'s, but no
|
|
20
|
+
* inline image over {@link MAX_INLINE_IMAGE_BYTES}. Rendering still takes
|
|
21
|
+
* any safe source, so a document that holds a larger one shows it.
|
|
22
|
+
*/
|
|
23
|
+
export const storableImageSrc = (value) => {
|
|
24
|
+
const src = safeImageSrc(value);
|
|
25
|
+
return src !== null && !oversizedInlineImage(src) ? src : null;
|
|
26
|
+
};
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -12,6 +12,6 @@ export type { ToolbarController } from './toolbar/ToolbarController.svelte.js';
|
|
|
12
12
|
export { blockMenuPlugin, createBlockMenuPlugin } from './blockMenu/blockMenuPlugin.js';
|
|
13
13
|
export { suggestionsPlugin, createSuggestionsPlugin, isSuggestionsPlugin, type SuggestionsOptions, type SuggestionBarPayload } from './suggestions/suggestionsPlugin.js';
|
|
14
14
|
export type { BlockMenuOptions, BlockMenuController, BlockMenuAction } from './blockMenu/BlockMenuController.svelte.js';
|
|
15
|
-
export { imagePlugin, createImagePlugin, isImagePlugin, safeImageSrc, type ImagePluginOptions } from './image/ImagePlugin.svelte';
|
|
15
|
+
export { imagePlugin, createImagePlugin, isImagePlugin, safeImageSrc, storableImageSrc, oversizedInlineImage, MAX_INLINE_IMAGE_BYTES, type ImagePluginOptions } from './image/ImagePlugin.svelte';
|
|
16
16
|
export type { BlockHandleController } from './blockHandles/BlockHandleController.svelte.js';
|
|
17
17
|
export { columnsPlugin, createColumnsPlugin, isColumnsPlugin, type ColumnsPluginOptions } from './columns/ColumnsPlugin.svelte';
|
package/dist/plugins/index.js
CHANGED
|
@@ -9,5 +9,5 @@ export { slashMenuPlugin, createSlashMenuPlugin } from './slashMenu/slashMenuPlu
|
|
|
9
9
|
export { toolbarPlugin, createToolbarPlugin } from './toolbar/toolbarPlugin.js';
|
|
10
10
|
export { blockMenuPlugin, createBlockMenuPlugin } from './blockMenu/blockMenuPlugin.js';
|
|
11
11
|
export { suggestionsPlugin, createSuggestionsPlugin, isSuggestionsPlugin } from './suggestions/suggestionsPlugin.js';
|
|
12
|
-
export { imagePlugin, createImagePlugin, isImagePlugin, safeImageSrc } from './image/ImagePlugin.svelte';
|
|
12
|
+
export { imagePlugin, createImagePlugin, isImagePlugin, safeImageSrc, storableImageSrc, oversizedInlineImage, MAX_INLINE_IMAGE_BYTES } from './image/ImagePlugin.svelte';
|
|
13
13
|
export { columnsPlugin, createColumnsPlugin, isColumnsPlugin } from './columns/ColumnsPlugin.svelte';
|
package/dist/plugins.d.ts
CHANGED
|
@@ -246,6 +246,17 @@ export type BlockDefinition = {
|
|
|
246
246
|
* in the delete contract). Adopted by the document as a role.
|
|
247
247
|
*/
|
|
248
248
|
layout?: boolean;
|
|
249
|
+
/**
|
|
250
|
+
* Data paths written as one value (`data.atomic`): a top-level key, or an
|
|
251
|
+
* array of keys for a nested one. Setting `block.data.link`, or a key
|
|
252
|
+
* inside it, writes the whole `link` as one property, so two people
|
|
253
|
+
* setting it at once never merge into a value neither wrote (one wins
|
|
254
|
+
* whole); elsewhere each key merges on its own. Adopted by the document as
|
|
255
|
+
* part of the kind's role: every view of a document, a headless one and
|
|
256
|
+
* the room must declare the same paths (`semanticsOf({ embed: { atomic:
|
|
257
|
+
* ['link'] } })`).
|
|
258
|
+
*/
|
|
259
|
+
atomic?: readonly (string | readonly string[])[];
|
|
249
260
|
/**
|
|
250
261
|
* Whether the snippet renders the block's own content (`content()`).
|
|
251
262
|
* Defaults to `true`; containers that render only their children
|
|
@@ -57,6 +57,7 @@ export declare class Dispatcher {
|
|
|
57
57
|
/** The plan `lead` holds for the next dispatched operation (a slash trigger's removal), if any. */
|
|
58
58
|
get pendingLead(): Plan | null;
|
|
59
59
|
/** Admission: a readonly view or a read-only document refuses every mutating command. */
|
|
60
|
+
/** Admission: not readonly, a writable document, and one decided when it requires hydration (H12). */
|
|
60
61
|
permits: () => boolean;
|
|
61
62
|
/** Apply the undo policy's cut before a `kind` command writes. */
|
|
62
63
|
cut: (kind: string, phase?: "before" | "after") => void;
|
package/dist/session/commands.js
CHANGED
|
@@ -163,7 +163,10 @@ export class Dispatcher {
|
|
|
163
163
|
return this.leading;
|
|
164
164
|
}
|
|
165
165
|
/** Admission: a readonly view or a read-only document refuses every mutating command. */
|
|
166
|
-
|
|
166
|
+
/** Admission: not readonly, a writable document, and one decided when it requires hydration (H12). */
|
|
167
|
+
permits = () => !this.edytor.readonly &&
|
|
168
|
+
this.edytor.document.writable &&
|
|
169
|
+
(this.edytor.document.ready || !this.edytor.document.requireHydration);
|
|
167
170
|
/** Apply the undo policy's cut before a `kind` command writes. */
|
|
168
171
|
cut = (kind, phase = 'before') => {
|
|
169
172
|
const policy = this.decide(policyOf(kind));
|