edytor 1.0.0-rc.2 → 1.0.0-rc.3
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/block/block.svelte.js +20 -4
- package/dist/clipboard/htmlFlow.d.ts +6 -1
- package/dist/clipboard/htmlFlow.js +19 -6
- package/dist/cloudflare/DocumentRoom.d.ts +18 -0
- package/dist/cloudflare/DocumentRoom.js +7 -0
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/room/context.d.ts +1 -0
- package/dist/cloudflare/room/storage.d.ts +8 -0
- package/dist/cloudflare/room/storage.js +24 -1
- package/dist/collaboration/history/HistoryPanel.svelte +49 -20
- package/dist/collaboration/history/index.d.ts +1 -1
- package/dist/collaboration/history/panel.d.ts +18 -0
- package/dist/components/Block.svelte +155 -98
- package/dist/components/Block.svelte.d.ts +4 -1
- package/dist/components/Content.svelte +90 -39
- package/dist/components/CustomMark.svelte +46 -0
- package/dist/components/CustomMark.svelte.d.ts +11 -0
- package/dist/components/Edytor.svelte +29 -1
- package/dist/components/InlineBlock.svelte +4 -9
- package/dist/components/Mark.svelte +46 -35
- package/dist/components/Mark.svelte.d.ts +7 -0
- package/dist/components/Text.svelte +50 -13
- package/dist/{plugins/blockHandles → dnd}/autoScroll.d.ts +2 -1
- package/dist/{plugins/blockHandles → dnd}/autoScroll.js +3 -2
- package/dist/edytor.svelte.js +3 -1
- package/dist/events/beforeInputCommands.js +4 -3
- package/dist/events/onDrop.d.ts +6 -0
- package/dist/events/onDrop.js +6 -3
- package/dist/events/onFocus.js +48 -6
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/labels.d.ts +18 -2
- package/dist/labels.js +13 -1
- package/dist/plugins/blockHandles/BlockHandle.svelte +5 -2
- package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +41 -20
- package/dist/plugins/blockHandles/BlockHandleController.svelte.js +83 -47
- package/dist/plugins/blockHandles/BlockHandles.svelte +19 -12
- package/dist/plugins/blockHandles/blockHandlesPlugin.d.ts +23 -3
- package/dist/plugins/blockHandles/blockHandlesPlugin.js +33 -9
- package/dist/plugins/blockHandles/dragPreview.js +2 -1
- package/dist/plugins/blockHandles/geometry.d.ts +44 -0
- package/dist/plugins/blockHandles/geometry.js +47 -0
- package/dist/plugins/blockMenu/BlockMenu.svelte +16 -128
- package/dist/plugins/blockMenu/BlockMenuController.svelte.d.ts +43 -2
- package/dist/plugins/blockMenu/BlockMenuController.svelte.js +142 -0
- package/dist/plugins/chrome.d.ts +72 -0
- package/dist/plugins/chrome.js +64 -0
- package/dist/plugins/code/CodeHeader.svelte +40 -54
- package/dist/plugins/code/CodeHeader.svelte.d.ts +1 -0
- package/dist/plugins/code/CodePlugin.svelte +42 -20
- package/dist/plugins/code/LanguageMenu.svelte +134 -0
- package/dist/plugins/code/LanguageMenu.svelte.d.ts +8 -0
- package/dist/plugins/code/code.css +37 -20
- package/dist/plugins/code/header.svelte.d.ts +56 -0
- package/dist/plugins/code/header.svelte.js +168 -0
- package/dist/plugins/code/languageMenu.svelte.d.ts +104 -0
- package/dist/plugins/code/languageMenu.svelte.js +329 -0
- package/dist/plugins/code/languages.d.ts +21 -0
- package/dist/plugins/comments/CommentsSidebar.svelte +3 -2
- package/dist/plugins/comments/CommentsSidebar.svelte.d.ts +3 -6
- package/dist/plugins/comments/commentsPlugin.d.ts +10 -5
- package/dist/plugins/equation/EquationEditor.svelte +31 -28
- package/dist/plugins/equation/EquationEditor.svelte.d.ts +2 -0
- package/dist/plugins/equation/EquationPlugin.svelte +15 -4
- package/dist/plugins/equation/EquationPlugin.svelte.d.ts +5 -3
- package/dist/plugins/equation/equation.svelte.d.ts +43 -18
- package/dist/plugins/equation/equation.svelte.js +90 -8
- package/dist/plugins/equation/importUrl.d.ts +6 -0
- package/dist/plugins/equation/importUrl.js +6 -0
- package/dist/plugins/equation/katex.d.ts +8 -0
- package/dist/plugins/equation/katex.js +80 -0
- package/dist/plugins/image/ImageChrome.svelte +27 -12
- package/dist/plugins/image/ImageChrome.svelte.d.ts +2 -0
- package/dist/plugins/image/ImageEmpty.svelte +25 -48
- package/dist/plugins/image/ImageEmpty.svelte.d.ts +3 -0
- package/dist/plugins/image/ImagePlugin.svelte +27 -6
- package/dist/plugins/image/ImagePlugin.svelte.d.ts +18 -0
- package/dist/plugins/image/controls.svelte.d.ts +14 -35
- package/dist/plugins/image/controls.svelte.js +56 -10
- package/dist/plugins/image/empty.svelte.d.ts +56 -0
- package/dist/plugins/image/empty.svelte.js +83 -0
- package/dist/plugins/index.d.ts +14 -4
- package/dist/plugins/index.js +3 -2
- package/dist/plugins/marquee/MarqueeBox.svelte +32 -0
- package/dist/plugins/marquee/MarqueeBox.svelte.d.ts +9 -0
- package/dist/plugins/marquee/MarqueeController.svelte.d.ts +48 -0
- package/dist/plugins/marquee/MarqueeController.svelte.js +246 -0
- package/dist/plugins/marquee/marquee.d.ts +33 -0
- package/dist/plugins/marquee/marquee.js +106 -0
- package/dist/plugins/marquee/marqueePlugin.d.ts +31 -0
- package/dist/plugins/marquee/marqueePlugin.js +67 -0
- package/dist/plugins/media/AudioPlugin.svelte +2 -0
- package/dist/plugins/media/BookmarkPlugin.svelte +22 -3
- package/dist/plugins/media/BookmarkPlugin.svelte.d.ts +18 -0
- package/dist/plugins/media/EmbedPlugin.svelte +22 -2
- package/dist/plugins/media/EmbedPlugin.svelte.d.ts +18 -0
- package/dist/plugins/media/FilePlugin.svelte +2 -0
- package/dist/plugins/media/MediaEmpty.svelte +35 -53
- package/dist/plugins/media/UrlPasteMenu.svelte +16 -7
- package/dist/plugins/media/UrlPasteMenu.svelte.d.ts +2 -0
- package/dist/plugins/media/VideoPlugin.svelte +2 -0
- package/dist/plugins/media/empty.svelte.d.ts +77 -0
- package/dist/plugins/media/empty.svelte.js +115 -0
- package/dist/plugins/media/media.d.ts +17 -0
- package/dist/plugins/media/media.js +15 -0
- package/dist/plugins/media/urlPaste.svelte.d.ts +22 -14
- package/dist/plugins/media/urlPaste.svelte.js +48 -7
- package/dist/plugins/mention/MentionPlugin.svelte +11 -7
- package/dist/plugins/mention/MentionPlugin.svelte.d.ts +7 -0
- package/dist/plugins/pageLink/PageLinkPlugin.svelte +11 -7
- package/dist/plugins/pageLink/PageLinkPlugin.svelte.d.ts +7 -0
- package/dist/plugins/richtext/CalloutIcon.svelte +38 -0
- package/dist/plugins/richtext/CalloutIcon.svelte.d.ts +9 -0
- package/dist/plugins/richtext/CalloutIconMenu.svelte +98 -0
- package/dist/plugins/richtext/CalloutIconMenu.svelte.d.ts +9 -0
- package/dist/plugins/richtext/EmptyBody.svelte +32 -0
- package/dist/plugins/richtext/EmptyBody.svelte.d.ts +8 -0
- package/dist/plugins/richtext/RichTextPlugin.svelte +201 -61
- package/dist/plugins/richtext/RichTextPlugin.svelte.d.ts +21 -1
- package/dist/plugins/richtext/TodoCheckbox.svelte +2 -2
- package/dist/plugins/richtext/body.d.ts +9 -0
- package/dist/plugins/richtext/body.js +16 -0
- package/dist/plugins/richtext/calloutIcons.svelte.d.ts +102 -0
- package/dist/plugins/richtext/calloutIcons.svelte.js +302 -0
- package/dist/plugins/richtext/labels.d.ts +6 -2
- package/dist/plugins/richtext/labels.js +6 -2
- package/dist/plugins/slashMenu/SlashMenu.svelte +25 -109
- package/dist/plugins/slashMenu/SlashMenuController.svelte.d.ts +19 -0
- package/dist/plugins/slashMenu/SlashMenuController.svelte.js +95 -0
- package/dist/plugins/slashMenu/slashMenuPlugin.d.ts +15 -11
- package/dist/plugins/suggestions/SuggestionBars.svelte +6 -1
- package/dist/plugins/suggestions/suggestionsPlugin.d.ts +3 -1
- package/dist/plugins/table/TableChrome.svelte +174 -121
- package/dist/plugins/table/TableChrome.svelte.d.ts +5 -1
- package/dist/plugins/table/TablePlugin.svelte +16 -9
- package/dist/plugins/table/chrome.svelte.d.ts +190 -94
- package/dist/plugins/table/chrome.svelte.js +658 -54
- package/dist/plugins/table/dragPreview.d.ts +29 -0
- package/dist/plugins/table/dragPreview.js +103 -0
- package/dist/plugins/table/table.css +59 -2
- package/dist/plugins/table/table.d.ts +18 -0
- package/dist/plugins/table/table.js +1 -33
- package/dist/plugins/toolbar/LinkCard.svelte +6 -2
- package/dist/plugins/toolbar/LinkCard.svelte.d.ts +2 -0
- package/dist/plugins/toolbar/Toolbar.svelte +10 -61
- package/dist/plugins/toolbar/ToolbarController.svelte.d.ts +27 -0
- package/dist/plugins/toolbar/ToolbarController.svelte.js +101 -0
- package/dist/plugins/toolbar/toolbarPlugin.d.ts +14 -3
- package/dist/plugins/toolbar/toolbarPlugin.js +1 -1
- package/dist/plugins/triggers/TriggerController.svelte.d.ts +25 -0
- package/dist/plugins/triggers/TriggerController.svelte.js +36 -0
- package/dist/plugins/triggers/TriggerMenu.svelte +27 -36
- package/dist/plugins.d.ts +25 -13
- package/dist/selection/pointer.svelte.d.ts +4 -0
- package/dist/selection/pointer.svelte.js +124 -0
- package/dist/selection/selection.svelte.js +39 -0
- package/dist/selection/visibility.d.ts +8 -0
- package/dist/selection/visibility.js +16 -3
- package/dist/session/attempt.js +4 -2
- package/dist/session/navigation.d.ts +15 -1
- package/dist/session/navigation.js +83 -1
- package/dist/session/ports.d.ts +12 -0
- package/dist/session/selection.js +3 -0
- package/dist/session/suggestions.svelte.js +22 -0
- package/dist/surface/lines.d.ts +32 -0
- package/dist/surface/lines.js +43 -0
- package/dist/surface/observer.svelte.js +20 -4
- package/dist/surface/overlay.d.ts +8 -4
- package/dist/surface/overlay.js +64 -9
- package/dist/themes/notion.css +53 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
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
|
-
> **Release candidate.** Edytor `1.0.0-rc.
|
|
20
|
+
> **Release candidate.** Edytor `1.0.0-rc.3` is the 1.0 release candidate (`edytor` on npm): its API is frozen, and a change to it before 1.0 is a listed breaking change. 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
|
|
|
@@ -117,10 +117,10 @@ export class Block {
|
|
|
117
117
|
this.patchData({ ops: [{ path: [], value: data }] });
|
|
118
118
|
};
|
|
119
119
|
get selected() {
|
|
120
|
-
return this.edytor.selection.
|
|
120
|
+
return this.edytor.selection.isSelected(this);
|
|
121
121
|
}
|
|
122
122
|
get focused() {
|
|
123
|
-
return this.edytor.selection.
|
|
123
|
+
return this.edytor.selection.isFocused(this) && !this.selected;
|
|
124
124
|
}
|
|
125
125
|
/** May this block move at all: `canPlace` without a destination (drag handles). */
|
|
126
126
|
get movable() {
|
|
@@ -195,10 +195,24 @@ export class Block {
|
|
|
195
195
|
return this.edytor.facade.isVisibleBlock(this.id);
|
|
196
196
|
}
|
|
197
197
|
get nextBlock() {
|
|
198
|
-
return this
|
|
198
|
+
return this.#sibling(1);
|
|
199
199
|
}
|
|
200
200
|
get previousBlock() {
|
|
201
|
-
return this
|
|
201
|
+
return this.#sibling(-1);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The sibling `step` places away (none past either end): one id read, no
|
|
205
|
+
* handle for every sibling (a top-level block's are the whole page's).
|
|
206
|
+
* Tracks what `parent` and `index` track: this block's cell and its
|
|
207
|
+
* parent's child list.
|
|
208
|
+
*/
|
|
209
|
+
#sibling(step) {
|
|
210
|
+
const parent = this.parent;
|
|
211
|
+
if (!parent)
|
|
212
|
+
return null;
|
|
213
|
+
const ids = this.edytor.facade.childrenIds(parent.isRoot ? null : parent.id);
|
|
214
|
+
const id = ids[this.index + step];
|
|
215
|
+
return id === undefined ? null : this.edytor.idToBlock.block(id);
|
|
202
216
|
}
|
|
203
217
|
get closestPreviousBlock() {
|
|
204
218
|
return this.edytor.blockBefore(this);
|
|
@@ -422,6 +436,8 @@ export class Block {
|
|
|
422
436
|
node.setAttribute(name, value);
|
|
423
437
|
this.node = node;
|
|
424
438
|
const release = this.edytor.surface.register(node, 'block', this.id);
|
|
439
|
+
// Its attributes follow its own selected and focused membership.
|
|
440
|
+
this.edytor.selection.keep(this);
|
|
425
441
|
// A block laid out again with no commit moves the chrome beside it (the overlay measures).
|
|
426
442
|
const unobserve = this.edytor.overlay.observe(node);
|
|
427
443
|
const onDestroy = this.edytor.plugins.flatMap((plugin) => {
|
|
@@ -7,7 +7,12 @@ export type ImportKinds = Pick<Edytor, 'blocks' | 'marks'> & Partial<Pick<Edytor
|
|
|
7
7
|
document: Pick<Edytor['document'], 'defaultChild'>;
|
|
8
8
|
};
|
|
9
9
|
type Values = Record<string, SerializableContent>;
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* The tag a kind writes for `data`: its export form's first tag, else its
|
|
12
|
+
* element's, else its content element's. A generic one (`div`, `span`) is
|
|
13
|
+
* none: an export form written as one (a callout's `<div data-edytor-callout>`)
|
|
14
|
+
* is found by its `parse` hook only.
|
|
15
|
+
*/
|
|
11
16
|
export declare const tagOf: (type: string, kind: BlockDefinition, data: Values) => string | undefined;
|
|
12
17
|
/**
|
|
13
18
|
* External HTML as a flow: the browser parses (inert: no script
|
|
@@ -33,14 +33,21 @@ const isGlyph = (node) => {
|
|
|
33
33
|
};
|
|
34
34
|
/** Tags that carry no meaning of their own: a kind rendering one is not found by it. */
|
|
35
35
|
const GENERIC = /^(div|span)$/;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The tag a kind writes for `data`: its export form's first tag, else its
|
|
38
|
+
* element's, else its content element's. A generic one (`div`, `span`) is
|
|
39
|
+
* none: an export form written as one (a callout's `<div data-edytor-callout>`)
|
|
40
|
+
* is found by its `parse` hook only.
|
|
41
|
+
*/
|
|
37
42
|
export const tagOf = (type, kind, data) => {
|
|
38
43
|
const { html, element, contentElement } = kind;
|
|
39
44
|
if (typeof html === 'string')
|
|
40
45
|
return html;
|
|
41
46
|
try {
|
|
42
|
-
if (html)
|
|
43
|
-
|
|
47
|
+
if (html) {
|
|
48
|
+
const tag = /^<([a-z][\w-]*)/i.exec(html({ type, data }, '', ''))?.[1]?.toLowerCase();
|
|
49
|
+
return tag && !GENERIC.test(tag) ? tag : undefined;
|
|
50
|
+
}
|
|
44
51
|
}
|
|
45
52
|
catch {
|
|
46
53
|
return undefined;
|
|
@@ -251,18 +258,24 @@ export const flowOfHtml = (kinds, html) => {
|
|
|
251
258
|
texts.add(current);
|
|
252
259
|
}
|
|
253
260
|
};
|
|
261
|
+
/** Whether a leading paragraph gave the element its text: one only, empty or not. */
|
|
262
|
+
let led = false;
|
|
254
263
|
for (const node of element.childNodes)
|
|
255
264
|
if (isVoid(node))
|
|
256
265
|
at.apart(node);
|
|
257
266
|
else if (!isBlock(node))
|
|
258
267
|
inline(node, at, {}, pre);
|
|
259
|
-
// A leading plain paragraph is the element's own text (`<li><p>Item</p></li
|
|
260
|
-
|
|
268
|
+
// A leading plain paragraph is the element's own text (`<li><p>Item</p></li>`,
|
|
269
|
+
// a callout's title): an empty one too, so the paragraph after it is a child.
|
|
270
|
+
else if (!led &&
|
|
271
|
+
!current.content.length &&
|
|
261
272
|
!current.children?.length &&
|
|
262
273
|
!kindOf(node) &&
|
|
263
|
-
!wraps(node))
|
|
274
|
+
!wraps(node)) {
|
|
275
|
+
led = true;
|
|
264
276
|
for (const child of node.childNodes)
|
|
265
277
|
inline(child, at, {}, pre);
|
|
278
|
+
}
|
|
266
279
|
else
|
|
267
280
|
(current.children ??= []).push(...blockOf(node, childType));
|
|
268
281
|
for (const part of texts)
|
|
@@ -128,6 +128,8 @@ export declare const DEFAULT_SAVE_AFTER = 2000;
|
|
|
128
128
|
* about 100 MB at worst. Raise it for documents of long text.
|
|
129
129
|
*/
|
|
130
130
|
export declare const DEFAULT_MAX_DOCUMENT_BYTES: number;
|
|
131
|
+
/** The share of the document quota past which the room logs its size, by default. */
|
|
132
|
+
export declare const DEFAULT_DOCUMENT_WARNING = 0.8;
|
|
131
133
|
/**
|
|
132
134
|
* The largest frame a socket may send, reassembled from chunks: room for a
|
|
133
135
|
* whole document at the default quota (a client seeding an empty room).
|
|
@@ -211,6 +213,8 @@ export type DocumentRoomEnv = {
|
|
|
211
213
|
EDYTOR_COMPACT_AFTER?: string | number;
|
|
212
214
|
EDYTOR_SAVE_AFTER?: string | number;
|
|
213
215
|
EDYTOR_MAX_DOCUMENT_BYTES?: string | number;
|
|
216
|
+
/** The share of the document quota past which the room logs its size (`documentWarning`). */
|
|
217
|
+
EDYTOR_DOCUMENT_WARNING?: string | number;
|
|
214
218
|
EDYTOR_MAX_INBOUND_FRAME_BYTES?: string | number;
|
|
215
219
|
EDYTOR_MAX_BUFFERED_BYTES?: string | number;
|
|
216
220
|
EDYTOR_MAX_UPDATES_PER_SECOND?: string | number;
|
|
@@ -290,6 +294,8 @@ export type Timing = {
|
|
|
290
294
|
export type RoomMetrics = {
|
|
291
295
|
/** The document's size as the document quota measures it: its records, uncompressed. */
|
|
292
296
|
documentBytes: number;
|
|
297
|
+
/** The document quota (`maxDocumentBytes`): writes past it are refused (`4413`). */
|
|
298
|
+
documentLimit: number;
|
|
293
299
|
/** What its rows take in storage (a snapshot compressed). */
|
|
294
300
|
storedBytes: number;
|
|
295
301
|
/** Logical records stored (the generation record included), and their rows. */
|
|
@@ -366,6 +372,11 @@ export type RoomLogEntry = {
|
|
|
366
372
|
edytor: 'quota';
|
|
367
373
|
user: string;
|
|
368
374
|
quota: string;
|
|
375
|
+
} | {
|
|
376
|
+
edytor: 'size';
|
|
377
|
+
bytes: number;
|
|
378
|
+
limit: number;
|
|
379
|
+
share: number;
|
|
369
380
|
} | {
|
|
370
381
|
edytor: 'denied';
|
|
371
382
|
user: string;
|
|
@@ -495,6 +506,12 @@ export type AttachRoomOptions = {
|
|
|
495
506
|
tablePrefix?: string;
|
|
496
507
|
/** Largest document, in stored bytes (default {@link DEFAULT_MAX_DOCUMENT_BYTES}). */
|
|
497
508
|
maxDocumentBytes?: number;
|
|
509
|
+
/**
|
|
510
|
+
* The share of `maxDocumentBytes` past which the room logs the
|
|
511
|
+
* document's size (`size`, once per crossing), above 0 and at most 1
|
|
512
|
+
* (default {@link DEFAULT_DOCUMENT_WARNING}, 0.8).
|
|
513
|
+
*/
|
|
514
|
+
documentWarning?: number;
|
|
498
515
|
/** Largest frame a socket may send, reassembled (default {@link DEFAULT_MAX_INBOUND_FRAME_BYTES}). */
|
|
499
516
|
maxInboundFrameBytes?: number;
|
|
500
517
|
/**
|
|
@@ -588,6 +605,7 @@ export declare class AttachedDocument {
|
|
|
588
605
|
readonly compactAfter: number;
|
|
589
606
|
readonly saveAfter: number;
|
|
590
607
|
readonly maxDocumentBytes: number;
|
|
608
|
+
readonly documentWarning: number;
|
|
591
609
|
readonly maxInboundFrameBytes: number;
|
|
592
610
|
readonly maxBufferedBytes: number;
|
|
593
611
|
readonly maxUpdatesPerSecond: number;
|
|
@@ -137,6 +137,8 @@ export const DEFAULT_SAVE_AFTER = 2000;
|
|
|
137
137
|
* about 100 MB at worst. Raise it for documents of long text.
|
|
138
138
|
*/
|
|
139
139
|
export const DEFAULT_MAX_DOCUMENT_BYTES = 2 * 1024 * 1024;
|
|
140
|
+
/** The share of the document quota past which the room logs its size, by default. */
|
|
141
|
+
export const DEFAULT_DOCUMENT_WARNING = 0.8;
|
|
140
142
|
/**
|
|
141
143
|
* The largest frame a socket may send, reassembled from chunks: room for a
|
|
142
144
|
* whole document at the default quota (a client seeding an empty room).
|
|
@@ -200,6 +202,7 @@ export class AttachedDocument {
|
|
|
200
202
|
compactAfter;
|
|
201
203
|
saveAfter;
|
|
202
204
|
maxDocumentBytes;
|
|
205
|
+
documentWarning;
|
|
203
206
|
maxInboundFrameBytes;
|
|
204
207
|
maxBufferedBytes;
|
|
205
208
|
maxUpdatesPerSecond;
|
|
@@ -225,6 +228,8 @@ export class AttachedDocument {
|
|
|
225
228
|
this.compactAfter = knob(options.compactAfter, DEFAULT_COMPACT_AFTER, 1e9);
|
|
226
229
|
this.saveAfter = knob(options.saveAfter, DEFAULT_SAVE_AFTER, 1e9);
|
|
227
230
|
this.maxDocumentBytes = knob(options.maxDocumentBytes, DEFAULT_MAX_DOCUMENT_BYTES, QUOTA_CEILING);
|
|
231
|
+
const warning = Number(options.documentWarning);
|
|
232
|
+
this.documentWarning = warning > 0 && warning <= 1 ? warning : DEFAULT_DOCUMENT_WARNING;
|
|
228
233
|
this.maxInboundFrameBytes = knob(options.maxInboundFrameBytes, DEFAULT_MAX_INBOUND_FRAME_BYTES, QUOTA_CEILING);
|
|
229
234
|
this.maxBufferedBytes = Math.max(this.maxInboundFrameBytes, knob(options.maxBufferedBytes, 2 * this.maxInboundFrameBytes, QUOTA_CEILING));
|
|
230
235
|
this.maxUpdatesPerSecond = knob(options.maxUpdatesPerSecond, DEFAULT_MAX_UPDATES_PER_SECOND, QUOTA_CEILING);
|
|
@@ -492,6 +497,7 @@ export class AttachedDocument {
|
|
|
492
497
|
const { counters } = room;
|
|
493
498
|
return {
|
|
494
499
|
documentBytes: room.storage.documentBytes,
|
|
500
|
+
documentLimit: room.limits.maxDocumentBytes,
|
|
495
501
|
storedBytes: stored?.bytes ?? 0,
|
|
496
502
|
records: stored?.records ?? 0,
|
|
497
503
|
rows: stored?.rows ?? 0,
|
|
@@ -649,6 +655,7 @@ export class DocumentRoom extends DurableObject {
|
|
|
649
655
|
compactAfter: Number(knobs.EDYTOR_COMPACT_AFTER),
|
|
650
656
|
saveAfter: Number(knobs.EDYTOR_SAVE_AFTER),
|
|
651
657
|
maxDocumentBytes: Number(knobs.EDYTOR_MAX_DOCUMENT_BYTES),
|
|
658
|
+
documentWarning: Number(knobs.EDYTOR_DOCUMENT_WARNING),
|
|
652
659
|
maxInboundFrameBytes: Number(knobs.EDYTOR_MAX_INBOUND_FRAME_BYTES),
|
|
653
660
|
maxBufferedBytes: Number(knobs.EDYTOR_MAX_BUFFERED_BYTES),
|
|
654
661
|
maxUpdatesPerSecond: Number(knobs.EDYTOR_MAX_UPDATES_PER_SECOND),
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `cloudflare:workers`); built on the Worker-safe CRDT entry. Documented in
|
|
5
5
|
* the site's `server/` section (room, authorization, extending, protocol).
|
|
6
6
|
*/
|
|
7
|
-
export { DocumentRoom, AttachedDocument, attachRoom, SOCKET_TAG, closedSocket, type AttachRoomOptions, type CommentOptions, DEFAULT_COMPACT_AFTER, DEFAULT_MAX_ROW_BYTES, DEFAULT_SAVE_AFTER, DEFAULT_MAX_DOCUMENT_BYTES, DEFAULT_MAX_INBOUND_FRAME_BYTES, DEFAULT_MAX_BUFFERED_BYTES, DEFAULT_MAX_UPDATES_PER_SECOND, DEFAULT_MAX_PRESENCE_BYTES, DEFAULT_MAX_PRESENCE_PER_SECOND, DEFAULT_MAX_PRESENCE_FANOUT, DEFAULT_MAX_COMMENT_BYTES, DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND, MAX_REFUSALS, MAX_WAITING_DELETES, ROOM_ORIGIN, RESTORE_ORIGIN, PURGE_ORIGIN, IDENTITY_HEADERS, PROBE_HEADER, noTimers, type Attachment, type DocumentRoomEnv, type LoadedDocument, type Refusal, type ReplicaOwner, type SavedDocument, type SocketIdentity, type StoredRecord, type FrameValidation, type ValidatedBlock, type RoomMetrics, type RoomLogEntry, type RestoreResult, type Timing } from './DocumentRoom.js';
|
|
7
|
+
export { DocumentRoom, AttachedDocument, attachRoom, SOCKET_TAG, closedSocket, type AttachRoomOptions, type CommentOptions, DEFAULT_COMPACT_AFTER, DEFAULT_MAX_ROW_BYTES, DEFAULT_SAVE_AFTER, DEFAULT_MAX_DOCUMENT_BYTES, DEFAULT_DOCUMENT_WARNING, DEFAULT_MAX_INBOUND_FRAME_BYTES, DEFAULT_MAX_BUFFERED_BYTES, DEFAULT_MAX_UPDATES_PER_SECOND, DEFAULT_MAX_PRESENCE_BYTES, DEFAULT_MAX_PRESENCE_PER_SECOND, DEFAULT_MAX_PRESENCE_FANOUT, DEFAULT_MAX_COMMENT_BYTES, DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND, MAX_REFUSALS, MAX_WAITING_DELETES, ROOM_ORIGIN, RESTORE_ORIGIN, PURGE_ORIGIN, IDENTITY_HEADERS, PROBE_HEADER, noTimers, type Attachment, type DocumentRoomEnv, type LoadedDocument, type Refusal, type ReplicaOwner, type SavedDocument, type SocketIdentity, type StoredRecord, type FrameValidation, type ValidatedBlock, type RoomMetrics, type RoomLogEntry, type RestoreResult, type Timing } from './DocumentRoom.js';
|
|
8
8
|
export { DEFAULT_RETENTION_DAYS, HISTORY_MAX_VALUE_BYTES, R2_HISTORY_MAX_VALUE_BYTES, ROOM_HISTORY_MAX_BYTES, ROOM_HISTORY_MAX_VALUE_BYTES, kvHistory, r2History, roomHistory, type HistoryEntry, type HistoryMetadata, type HistoryOptions, type HistoryRoomStorage, type HistoryStore, type HistoryStoreFactory, type HistoryStoreRecord, type KVLike, type R2BucketLike } from './history.js';
|
|
9
9
|
export { type PurgeReport } from '../crdt/purge.js';
|
|
10
10
|
export { COMMENT_ROW_BYTES, COMMENT_STATUS, MAX_COMMENT_LENGTH, MAX_COMMENTS_PER_THREAD, MAX_THREADS, commentAnchors, commentBytes, decideComment, type ThreadComment, type CommentActor, type CommentChange, type CommentOutcome, type CommentRefusal, type CommentRequest, type CommentRun, type CommentSnapshot, type CommentThread } from '../crdt/protocols/comments.js';
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `cloudflare:workers`); built on the Worker-safe CRDT entry. Documented in
|
|
5
5
|
* the site's `server/` section (room, authorization, extending, protocol).
|
|
6
6
|
*/
|
|
7
|
-
export { DocumentRoom, AttachedDocument, attachRoom, SOCKET_TAG, closedSocket, DEFAULT_COMPACT_AFTER, DEFAULT_MAX_ROW_BYTES, DEFAULT_SAVE_AFTER, DEFAULT_MAX_DOCUMENT_BYTES, DEFAULT_MAX_INBOUND_FRAME_BYTES, DEFAULT_MAX_BUFFERED_BYTES, DEFAULT_MAX_UPDATES_PER_SECOND, DEFAULT_MAX_PRESENCE_BYTES, DEFAULT_MAX_PRESENCE_PER_SECOND, DEFAULT_MAX_PRESENCE_FANOUT, DEFAULT_MAX_COMMENT_BYTES, DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND, MAX_REFUSALS, MAX_WAITING_DELETES, ROOM_ORIGIN, RESTORE_ORIGIN, PURGE_ORIGIN, IDENTITY_HEADERS, PROBE_HEADER, noTimers } from './DocumentRoom.js';
|
|
7
|
+
export { DocumentRoom, AttachedDocument, attachRoom, SOCKET_TAG, closedSocket, DEFAULT_COMPACT_AFTER, DEFAULT_MAX_ROW_BYTES, DEFAULT_SAVE_AFTER, DEFAULT_MAX_DOCUMENT_BYTES, DEFAULT_DOCUMENT_WARNING, DEFAULT_MAX_INBOUND_FRAME_BYTES, DEFAULT_MAX_BUFFERED_BYTES, DEFAULT_MAX_UPDATES_PER_SECOND, DEFAULT_MAX_PRESENCE_BYTES, DEFAULT_MAX_PRESENCE_PER_SECOND, DEFAULT_MAX_PRESENCE_FANOUT, DEFAULT_MAX_COMMENT_BYTES, DEFAULT_MAX_COMMENT_REQUESTS_PER_SECOND, MAX_REFUSALS, MAX_WAITING_DELETES, ROOM_ORIGIN, RESTORE_ORIGIN, PURGE_ORIGIN, IDENTITY_HEADERS, PROBE_HEADER, noTimers } from './DocumentRoom.js';
|
|
8
8
|
export { DEFAULT_RETENTION_DAYS, HISTORY_MAX_VALUE_BYTES, R2_HISTORY_MAX_VALUE_BYTES, ROOM_HISTORY_MAX_BYTES, ROOM_HISTORY_MAX_VALUE_BYTES, kvHistory, r2History, roomHistory } from './history.js';
|
|
9
9
|
export {} from '../crdt/purge.js';
|
|
10
10
|
export { COMMENT_ROW_BYTES, COMMENT_STATUS, MAX_COMMENT_LENGTH, MAX_COMMENTS_PER_THREAD, MAX_THREADS, commentAnchors, commentBytes, decideComment } from '../crdt/protocols/comments.js';
|
|
@@ -10,6 +10,14 @@ export declare class RoomStorage {
|
|
|
10
10
|
updates: number;
|
|
11
11
|
/** What the records hold, uncompressed (the generation record aside): the document quota's measure. */
|
|
12
12
|
documentBytes: number;
|
|
13
|
+
/** The size passed `documentWarning` of the quota and was logged (memory: a wake logs it again). */
|
|
14
|
+
private warned;
|
|
15
|
+
/**
|
|
16
|
+
* Log the document's size when it passes `documentWarning` of its quota
|
|
17
|
+
* (`room.quota.warning`): once per crossing; under it again, the next
|
|
18
|
+
* crossing logs again.
|
|
19
|
+
*/
|
|
20
|
+
private watch;
|
|
13
21
|
/** The waiting deletes stored as `pending` records (the engine may hold more, in memory). */
|
|
14
22
|
storedWaiting: Decoded['ds'];
|
|
15
23
|
/**
|
|
@@ -93,6 +93,25 @@ export class RoomStorage {
|
|
|
93
93
|
updates = 0;
|
|
94
94
|
/** What the records hold, uncompressed (the generation record aside): the document quota's measure. */
|
|
95
95
|
documentBytes = 0;
|
|
96
|
+
/** The size passed `documentWarning` of the quota and was logged (memory: a wake logs it again). */
|
|
97
|
+
warned = false;
|
|
98
|
+
/**
|
|
99
|
+
* Log the document's size when it passes `documentWarning` of its quota
|
|
100
|
+
* (`room.quota.warning`): once per crossing; under it again, the next
|
|
101
|
+
* crossing logs again.
|
|
102
|
+
*/
|
|
103
|
+
watch() {
|
|
104
|
+
const room = this.room;
|
|
105
|
+
const { maxDocumentBytes: limit, documentWarning: share } = room.limits;
|
|
106
|
+
if (this.documentBytes < limit * share) {
|
|
107
|
+
this.warned = false;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (this.warned)
|
|
111
|
+
return;
|
|
112
|
+
this.warned = true;
|
|
113
|
+
room.log({ edytor: 'size', bytes: this.documentBytes, limit, share });
|
|
114
|
+
}
|
|
96
115
|
/** The waiting deletes stored as `pending` records (the engine may hold more, in memory). */
|
|
97
116
|
storedWaiting = Y.createIdSet();
|
|
98
117
|
/**
|
|
@@ -338,6 +357,7 @@ export class RoomStorage {
|
|
|
338
357
|
// records keeps every keystroke's struct and the deleted content.
|
|
339
358
|
const doc = admit(rest.map((record) => (record.v2 ? { v2: record.bytes } : record.bytes)), `room ${room.ctx.id}`, () => room.history.restoreKeep);
|
|
340
359
|
this.documentBytes = rest.reduce((n, record) => n + record.bytes.length, 0);
|
|
360
|
+
this.watch();
|
|
341
361
|
this.adopt(doc);
|
|
342
362
|
// Every delete the engine holds waiting came from the rows.
|
|
343
363
|
this.storedWaiting = pendingDeletes(doc);
|
|
@@ -388,6 +408,7 @@ export class RoomStorage {
|
|
|
388
408
|
});
|
|
389
409
|
room.history.clearRestore();
|
|
390
410
|
this.documentBytes = snapshot.length;
|
|
411
|
+
this.watch();
|
|
391
412
|
this.updates = 0;
|
|
392
413
|
this.storedWaiting = Y.createIdSet();
|
|
393
414
|
this.adopt(doc);
|
|
@@ -450,8 +471,10 @@ export class RoomStorage {
|
|
|
450
471
|
}
|
|
451
472
|
const record = this.nextRecord++;
|
|
452
473
|
this.writeRecord(kind, record, bytes);
|
|
453
|
-
if (kind !== 'generation')
|
|
474
|
+
if (kind !== 'generation') {
|
|
454
475
|
this.documentBytes += bytes.length;
|
|
476
|
+
this.watch();
|
|
477
|
+
}
|
|
455
478
|
return record;
|
|
456
479
|
}
|
|
457
480
|
/** Record `record`'s rows (at the row positions `seqs`, when given). */
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { versionDiff } from './diff.js';
|
|
16
16
|
import { versionHighlightsPlugin } from './versionHighlights.js';
|
|
17
17
|
import { labelsWith } from '../../labels.js';
|
|
18
|
+
import { optionAttributes } from '../../plugins/chrome.js';
|
|
18
19
|
|
|
19
20
|
let {
|
|
20
21
|
client,
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
class: className,
|
|
29
30
|
previewClass,
|
|
30
31
|
version: versionRow,
|
|
32
|
+
item: itemRow,
|
|
31
33
|
onRestore,
|
|
32
34
|
onUndo,
|
|
33
35
|
onError
|
|
@@ -186,6 +188,20 @@
|
|
|
186
188
|
|
|
187
189
|
const selectedEntry = $derived(versions?.find((entry) => entry.key === selected) ?? null);
|
|
188
190
|
|
|
191
|
+
/** The panel's own prefix: its rows' ids are page-unique. */
|
|
192
|
+
const uid = $props.id();
|
|
193
|
+
/** The row of `entry` at `index`: an option, the list's one tab stop when it is selected (else the first). */
|
|
194
|
+
const optionOf = (entry: HistoryVersion, index: number) => {
|
|
195
|
+
const isSelected = entry.key === selected;
|
|
196
|
+
return optionAttributes(
|
|
197
|
+
`${uid}-version-${index}`,
|
|
198
|
+
'option',
|
|
199
|
+
isSelected,
|
|
200
|
+
{ tabindex: isSelected || (selected === null && index === 0) ? 0 : -1 },
|
|
201
|
+
false
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
|
|
189
205
|
/** The listbox's keys: the arrows, Home and End move the selection and the focus. */
|
|
190
206
|
const onListKey = (event: KeyboardEvent) => {
|
|
191
207
|
if (!versions?.length) return;
|
|
@@ -272,28 +288,41 @@
|
|
|
272
288
|
aria-label={labels.versions}
|
|
273
289
|
onkeydown={onListKey}
|
|
274
290
|
>
|
|
275
|
-
{#each versions as entry (entry.key)}
|
|
291
|
+
{#each versions as entry, index (entry.key)}
|
|
276
292
|
{@const isSelected = entry.key === selected}
|
|
293
|
+
{@const option = optionOf(entry, index)}
|
|
294
|
+
{@const preview = () => void select(entry.key)}
|
|
277
295
|
<li role="presentation">
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
296
|
+
{#if itemRow}
|
|
297
|
+
{@render itemRow({
|
|
298
|
+
item: entry,
|
|
299
|
+
version: entry,
|
|
300
|
+
id: option.id,
|
|
301
|
+
label: titleOf(entry),
|
|
302
|
+
selected: isSelected,
|
|
303
|
+
run: preview,
|
|
304
|
+
select: preview,
|
|
305
|
+
option
|
|
306
|
+
})}
|
|
307
|
+
{:else}
|
|
308
|
+
<button
|
|
309
|
+
type="button"
|
|
310
|
+
{...option}
|
|
311
|
+
data-edytor-history-version={entry.key}
|
|
312
|
+
onclick={preview}
|
|
313
|
+
>
|
|
314
|
+
{#if versionRow}
|
|
315
|
+
{@render versionRow(entry, { selected: isSelected })}
|
|
316
|
+
{:else}
|
|
317
|
+
<span data-edytor-history-version-title>{titleOf(entry)}</span>
|
|
318
|
+
<span data-edytor-history-version-editors>{editorsOf(entry)}</span>
|
|
319
|
+
<span data-edytor-history-version-time>
|
|
320
|
+
{labels.saved}
|
|
321
|
+
<time datetime={new Date(entry.at).toISOString()}>{timeOf(entry)}</time>
|
|
322
|
+
</span>
|
|
323
|
+
{/if}
|
|
324
|
+
</button>
|
|
325
|
+
{/if}
|
|
297
326
|
</li>
|
|
298
327
|
{/each}
|
|
299
328
|
</ul>
|
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
export { default as HistoryPanel } from './HistoryPanel.svelte';
|
|
7
7
|
export { createHistoryClient, HistoryRequestError, type HistoryClient, type HistoryClientOptions, type HistoryRestoreResult, type HistoryUndoResult, type HistoryVersion } from './client.js';
|
|
8
8
|
export { versionDiff, type VersionChange, type VersionDiff } from './diff.js';
|
|
9
|
-
export { defaultHistoryLabels, type HistoryPanelLabels, type HistoryPanelProps } from './panel.js';
|
|
9
|
+
export { defaultHistoryLabels, type HistoryPanelLabels, type HistoryPanelProps, type HistoryVersionItem } from './panel.js';
|
|
@@ -6,9 +6,21 @@ import type { EdytorDocument } from '../../crdt/document.js';
|
|
|
6
6
|
import type { Plugin } from '../../plugins.js';
|
|
7
7
|
import type { HistoryClient, HistoryRestoreResult, HistoryUndoResult, HistoryVersion } from './client.js';
|
|
8
8
|
import { type HistoryPanelLabels, type PartialLabels } from '../../labels.js';
|
|
9
|
+
import type { MenuItemPayload } from '../../plugins/chrome.js';
|
|
9
10
|
export type { HistoryPanelLabels };
|
|
10
11
|
/** The panel's English words: the label dictionary's `history` section (`englishLabels.history`). */
|
|
11
12
|
export declare const defaultHistoryLabels: HistoryPanelLabels;
|
|
13
|
+
/**
|
|
14
|
+
* One row of the versions list, for an `item` snippet: the row shape every
|
|
15
|
+
* menu shares (`item` is the version, `label` its date and slot, `run` and
|
|
16
|
+
* `select` preview it), with the version again. Spread `option` on the
|
|
17
|
+
* row's focusable element (its `role="option"`, id, `aria-selected` and the
|
|
18
|
+
* list's one tab stop): the list's arrows, Home and End walk those rows.
|
|
19
|
+
*/
|
|
20
|
+
export type HistoryVersionItem = MenuItemPayload<HistoryVersion> & {
|
|
21
|
+
/** The version (the same as `item`). */
|
|
22
|
+
version: HistoryVersion;
|
|
23
|
+
};
|
|
12
24
|
export type HistoryPanelProps = {
|
|
13
25
|
/** Where the versions come from: `createHistoryClient(…)`, or any object with `list`, `read`, `restore` and `undo`. */
|
|
14
26
|
client: HistoryClient;
|
|
@@ -34,6 +46,12 @@ export type HistoryPanelProps = {
|
|
|
34
46
|
version?: Snippet<[HistoryVersion, {
|
|
35
47
|
selected: boolean;
|
|
36
48
|
}]>;
|
|
49
|
+
/**
|
|
50
|
+
* Replace a version's whole row (the list item's content): one object
|
|
51
|
+
* argument, the shape every menu's rows share. Spread its `option` on
|
|
52
|
+
* the row's button. Takes precedence over `version`.
|
|
53
|
+
*/
|
|
54
|
+
item?: Snippet<[HistoryVersionItem]>;
|
|
37
55
|
/** After a restore the room answered. */
|
|
38
56
|
onRestore?: (result: HistoryRestoreResult) => void;
|
|
39
57
|
/** After an undo the room answered. */
|