@dou-ui/editor 0.0.0-stage → 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/catalog.js +18 -0
- package/dist/catalog.js.map +1 -0
- package/dist/code/code-editor-block.js +6 -0
- package/dist/code/code-editor-block.js.map +1 -0
- package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js +116 -0
- package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/index.js +69 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/languages.js +51 -0
- package/dist/code/languages.js.map +1 -0
- package/dist/code/view.js +255 -0
- package/dist/code/view.js.map +1 -0
- package/dist/collaboration/binding.js +381 -0
- package/dist/collaboration/binding.js.map +1 -0
- package/dist/collaboration/cursors.js +150 -0
- package/dist/collaboration/cursors.js.map +1 -0
- package/dist/collaboration/index.js +108 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/order.js +88 -0
- package/dist/collaboration/order.js.map +1 -0
- package/dist/collaboration/table.js +234 -0
- package/dist/collaboration/table.js.map +1 -0
- package/dist/collaboration/text.js +91 -0
- package/dist/collaboration/text.js.map +1 -0
- package/dist/components/block-handle.js +6 -0
- package/dist/components/block-handle.js.map +1 -0
- package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js +474 -0
- package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/bubble-toolbar.js +6 -0
- package/dist/components/bubble-toolbar.js.map +1 -0
- package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js +381 -0
- package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/comment-button.js +6 -0
- package/dist/components/comment-button.js.map +1 -0
- package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js +83 -0
- package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/comment-card.js +6 -0
- package/dist/components/comment-card.js.map +1 -0
- package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js +104 -0
- package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/menu-escape.js +25 -0
- package/dist/components/menu-escape.js.map +1 -0
- package/dist/components/search-bar.js +6 -0
- package/dist/components/search-bar.js.map +1 -0
- package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js +192 -0
- package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/slash-menu.js +6 -0
- package/dist/components/slash-menu.js.map +1 -0
- package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js +164 -0
- package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/core/align.js +94 -0
- package/dist/core/align.js.map +1 -0
- package/dist/core/block-selection.js +155 -0
- package/dist/core/block-selection.js.map +1 -0
- package/dist/core/blocks.js +132 -0
- package/dist/core/blocks.js.map +1 -0
- package/dist/core/changes.js +85 -0
- package/dist/core/changes.js.map +1 -0
- package/dist/core/colors.js +16 -0
- package/dist/core/colors.js.map +1 -0
- package/dist/core/commands.js +379 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/comments.js +186 -0
- package/dist/core/comments.js.map +1 -0
- package/dist/core/compare.js +106 -0
- package/dist/core/compare.js.map +1 -0
- package/dist/core/convert.js +28 -0
- package/dist/core/convert.js.map +1 -0
- package/dist/core/diff.js +141 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/editor.js +138 -0
- package/dist/core/editor.js.map +1 -0
- package/dist/core/extension.js +99 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/file-drop.js +56 -0
- package/dist/core/file-drop.js.map +1 -0
- package/dist/core/highlight.js +120 -0
- package/dist/core/highlight.js.map +1 -0
- package/dist/core/input-rules.js +61 -0
- package/dist/core/input-rules.js.map +1 -0
- package/dist/core/keymap.js +38 -0
- package/dist/core/keymap.js.map +1 -0
- package/dist/core/model.js +196 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/output.js +263 -0
- package/dist/core/output.js.map +1 -0
- package/dist/core/plugins.js +102 -0
- package/dist/core/plugins.js.map +1 -0
- package/dist/core/prepare.js +102 -0
- package/dist/core/prepare.js.map +1 -0
- package/dist/core/schema.js +158 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/search.js +197 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/selection.js +16 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/slash.js +83 -0
- package/dist/core/slash.js.map +1 -0
- package/dist/core/transaction.js +311 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/define-block.js +177 -0
- package/dist/define-block.js.map +1 -0
- package/dist/document.js +157 -0
- package/dist/document.js.map +1 -0
- package/dist/dom.js +48 -0
- package/dist/dom.js.map +1 -0
- package/dist/editor.js +33 -0
- package/dist/editor.js.map +1 -0
- package/dist/editor.vue_vue_type_script_setup_true_lang.js +564 -0
- package/dist/editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/editor2.js +6 -0
- package/dist/editor2.js.map +1 -0
- package/dist/extensions/align/align-menu.js +6 -0
- package/dist/extensions/align/align-menu.js.map +1 -0
- package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js +82 -0
- package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/align/align-sub.js +6 -0
- package/dist/extensions/align/align-sub.js.map +1 -0
- package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js +57 -0
- package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/align/commands.js +40 -0
- package/dist/extensions/align/commands.js.map +1 -0
- package/dist/extensions/align/index.js +27 -0
- package/dist/extensions/align/index.js.map +1 -0
- package/dist/extensions/attachment/attachment-block.js +6 -0
- package/dist/extensions/attachment/attachment-block.js.map +1 -0
- package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js +119 -0
- package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/attachment/files.js +66 -0
- package/dist/extensions/attachment/files.js.map +1 -0
- package/dist/extensions/attachment/index.js +132 -0
- package/dist/extensions/attachment/index.js.map +1 -0
- package/dist/extensions/blocks.js +170 -0
- package/dist/extensions/blocks.js.map +1 -0
- package/dist/extensions/callout/callout-block.js +6 -0
- package/dist/extensions/callout/callout-block.js.map +1 -0
- package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js +71 -0
- package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/callout/html.js +61 -0
- package/dist/extensions/callout/html.js.map +1 -0
- package/dist/extensions/callout/index.js +52 -0
- package/dist/extensions/callout/index.js.map +1 -0
- package/dist/extensions/callout/tones.js +12 -0
- package/dist/extensions/callout/tones.js.map +1 -0
- package/dist/extensions/code-block/code-block-view.js +6 -0
- package/dist/extensions/code-block/code-block-view.js.map +1 -0
- package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js +87 -0
- package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/code-block/index.js +98 -0
- package/dist/extensions/code-block/index.js.map +1 -0
- package/dist/extensions/code-block/languages.js +116 -0
- package/dist/extensions/code-block/languages.js.map +1 -0
- package/dist/extensions/colors/color-menu.js +6 -0
- package/dist/extensions/colors/color-menu.js.map +1 -0
- package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js +113 -0
- package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/colors/index.js +49 -0
- package/dist/extensions/colors/index.js.map +1 -0
- package/dist/extensions/colors/palette.js +26 -0
- package/dist/extensions/colors/palette.js.map +1 -0
- package/dist/extensions/editor-kit.js +35 -0
- package/dist/extensions/editor-kit.js.map +1 -0
- package/dist/extensions/embed/embed-block.js +6 -0
- package/dist/extensions/embed/embed-block.js.map +1 -0
- package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js +210 -0
- package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/embed/index.js +118 -0
- package/dist/extensions/embed/index.js.map +1 -0
- package/dist/extensions/embed/targets.js +79 -0
- package/dist/extensions/embed/targets.js.map +1 -0
- package/dist/extensions/formatting.js +153 -0
- package/dist/extensions/formatting.js.map +1 -0
- package/dist/extensions/image/image-block.js +6 -0
- package/dist/extensions/image/image-block.js.map +1 -0
- package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js +206 -0
- package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/image/index.js +125 -0
- package/dist/extensions/image/index.js.map +1 -0
- package/dist/extensions/image/uploads.js +96 -0
- package/dist/extensions/image/uploads.js.map +1 -0
- package/dist/extensions/list-html.js +58 -0
- package/dist/extensions/list-html.js.map +1 -0
- package/dist/extensions/lists.js +182 -0
- package/dist/extensions/lists.js.map +1 -0
- package/dist/extensions/mention/index.js +56 -0
- package/dist/extensions/mention/index.js.map +1 -0
- package/dist/extensions/mention/mention-menu.js +6 -0
- package/dist/extensions/mention/mention-menu.js.map +1 -0
- package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js +132 -0
- package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/mention/state.js +113 -0
- package/dist/extensions/mention/state.js.map +1 -0
- package/dist/extensions/table/commands.js +356 -0
- package/dist/extensions/table/commands.js.map +1 -0
- package/dist/extensions/table/html.js +134 -0
- package/dist/extensions/table/html.js.map +1 -0
- package/dist/extensions/table/index.js +56 -0
- package/dist/extensions/table/index.js.map +1 -0
- package/dist/extensions/table/plugin.js +81 -0
- package/dist/extensions/table/plugin.js.map +1 -0
- package/dist/extensions/table/schema.js +258 -0
- package/dist/extensions/table/schema.js.map +1 -0
- package/dist/extensions/table/table-controls.js +6 -0
- package/dist/extensions/table/table-controls.js.map +1 -0
- package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js +698 -0
- package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/table/view.js +19 -0
- package/dist/extensions/table/view.js.map +1 -0
- package/dist/extensions/toggle/index.js +87 -0
- package/dist/extensions/toggle/index.js.map +1 -0
- package/dist/extensions/toggle/state.js +120 -0
- package/dist/extensions/toggle/state.js.map +1 -0
- package/dist/extensions/toggle/toggle-block.js +6 -0
- package/dist/extensions/toggle/toggle-block.js.map +1 -0
- package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js +42 -0
- package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/locale.js +522 -0
- package/dist/locale.js.map +1 -0
- package/dist/markdown/index.js +24 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +8 -0
- package/dist/types/catalog.d.ts +8 -0
- package/dist/types/code/code-editor-block.vue.d.ts +17 -0
- package/dist/types/code/index.d.ts +18 -0
- package/dist/types/code/languages.d.ts +7 -0
- package/dist/types/code/types.d.ts +37 -0
- package/dist/types/code/view.d.ts +12 -0
- package/dist/types/collaboration/binding.d.ts +32 -0
- package/dist/types/collaboration/cursors.d.ts +26 -0
- package/dist/types/collaboration/index.d.ts +47 -0
- package/dist/types/collaboration/order.d.ts +20 -0
- package/dist/types/collaboration/table.d.ts +25 -0
- package/dist/types/collaboration/text.d.ts +9 -0
- package/dist/types/components/block-handle.vue.d.ts +3 -0
- package/dist/types/components/bubble-toolbar.vue.d.ts +7 -0
- package/dist/types/components/comment-button.vue.d.ts +3 -0
- package/dist/types/components/comment-card.vue.d.ts +3 -0
- package/dist/types/components/menu-escape.d.ts +7 -0
- package/dist/types/components/search-bar.vue.d.ts +22 -0
- package/dist/types/components/slash-menu.vue.d.ts +11 -0
- package/dist/types/core/align.d.ts +16 -0
- package/dist/types/core/block-selection.d.ts +22 -0
- package/dist/types/core/blocks.d.ts +52 -0
- package/dist/types/core/changes.d.ts +14 -0
- package/dist/types/core/colors.d.ts +4 -0
- package/dist/types/core/commands.d.ts +61 -0
- package/dist/types/core/comments.d.ts +44 -0
- package/dist/types/core/compare.d.ts +10 -0
- package/dist/types/core/convert.d.ts +12 -0
- package/dist/types/core/diff.d.ts +49 -0
- package/dist/types/core/editor.d.ts +64 -0
- package/dist/types/core/extension.d.ts +167 -0
- package/dist/types/core/file-drop.d.ts +19 -0
- package/dist/types/core/highlight.d.ts +32 -0
- package/dist/types/core/input-rules.d.ts +21 -0
- package/dist/types/core/keymap.d.ts +8 -0
- package/dist/types/core/model.d.ts +158 -0
- package/dist/types/core/output.d.ts +64 -0
- package/dist/types/core/plugins.d.ts +24 -0
- package/dist/types/core/prepare.d.ts +23 -0
- package/dist/types/core/schema.d.ts +38 -0
- package/dist/types/core/search.d.ts +83 -0
- package/dist/types/core/selection.d.ts +8 -0
- package/dist/types/core/slash.d.ts +21 -0
- package/dist/types/core/transaction.d.ts +97 -0
- package/dist/types/define-block.d.ts +66 -0
- package/dist/types/document.d.ts +46 -0
- package/dist/types/dom.d.ts +17 -0
- package/dist/types/editor.d.ts +252 -0
- package/dist/types/editor.vue.d.ts +210 -0
- package/dist/types/extensions/align/align-menu.vue.d.ts +3 -0
- package/dist/types/extensions/align/align-sub.vue.d.ts +7 -0
- package/dist/types/extensions/align/commands.d.ts +13 -0
- package/dist/types/extensions/align/index.d.ts +7 -0
- package/dist/types/extensions/attachment/attachment-block.vue.d.ts +17 -0
- package/dist/types/extensions/attachment/files.d.ts +6 -0
- package/dist/types/extensions/attachment/index.d.ts +20 -0
- package/dist/types/extensions/blocks.d.ts +8 -0
- package/dist/types/extensions/callout/callout-block.vue.d.ts +14 -0
- package/dist/types/extensions/callout/html.d.ts +4 -0
- package/dist/types/extensions/callout/index.d.ts +3 -0
- package/dist/types/extensions/callout/tones.d.ts +8 -0
- package/dist/types/extensions/code-block/code-block-view.vue.d.ts +16 -0
- package/dist/types/extensions/code-block/index.d.ts +19 -0
- package/dist/types/extensions/code-block/languages.d.ts +9 -0
- package/dist/types/extensions/colors/color-menu.vue.d.ts +3 -0
- package/dist/types/extensions/colors/index.d.ts +7 -0
- package/dist/types/extensions/colors/palette.d.ts +7 -0
- package/dist/types/extensions/editor-kit.d.ts +37 -0
- package/dist/types/extensions/embed/embed-block.vue.d.ts +16 -0
- package/dist/types/extensions/embed/index.d.ts +18 -0
- package/dist/types/extensions/embed/targets.d.ts +32 -0
- package/dist/types/extensions/formatting.d.ts +8 -0
- package/dist/types/extensions/image/image-block.vue.d.ts +16 -0
- package/dist/types/extensions/image/index.d.ts +16 -0
- package/dist/types/extensions/image/uploads.d.ts +35 -0
- package/dist/types/extensions/list-html.d.ts +11 -0
- package/dist/types/extensions/lists.d.ts +6 -0
- package/dist/types/extensions/mention/index.d.ts +11 -0
- package/dist/types/extensions/mention/mention-menu.vue.d.ts +7 -0
- package/dist/types/extensions/mention/state.d.ts +28 -0
- package/dist/types/extensions/table/commands.d.ts +69 -0
- package/dist/types/extensions/table/html.d.ts +4 -0
- package/dist/types/extensions/table/index.d.ts +8 -0
- package/dist/types/extensions/table/plugin.d.ts +4 -0
- package/dist/types/extensions/table/schema.d.ts +23 -0
- package/dist/types/extensions/table/table-controls.vue.d.ts +3 -0
- package/dist/types/extensions/table/view.d.ts +13 -0
- package/dist/types/extensions/toggle/index.d.ts +7 -0
- package/dist/types/extensions/toggle/state.d.ts +17 -0
- package/dist/types/extensions/toggle/toggle-block.vue.d.ts +14 -0
- package/dist/types/index.d.ts +266 -0
- package/dist/types/locale.d.ts +195 -0
- package/dist/types/markdown/index.d.ts +14 -0
- package/dist/types/use-comments.d.ts +17 -0
- package/dist/use-comments.js +141 -0
- package/dist/use-comments.js.map +1 -0
- package/package.json +98 -4
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { blocksToDoc, docToBlocks } from "../core/model.js";
|
|
2
|
+
import { defineEditorExtension, resolveEditorExtensions } from "../core/extension.js";
|
|
3
|
+
import { editorKit } from "../extensions/editor-kit.js";
|
|
4
|
+
import { cursorsPlugin, positionOf } from "./cursors.js";
|
|
5
|
+
import { createBinding, readDoc, syncPlugin, writeDoc } from "./binding.js";
|
|
6
|
+
import { applySharedCommentAction, collaborationColors, readSharedComments } from "dou-ui";
|
|
7
|
+
import { shallowRef } from "vue";
|
|
8
|
+
import { Plugin, TextSelection } from "prosemirror-state";
|
|
9
|
+
import * as Y from "yjs";
|
|
10
|
+
// src/collaboration/index.ts
|
|
11
|
+
/**
|
|
12
|
+
* Edit together through a Yjs document. The document lives in `map`: the
|
|
13
|
+
* editor's `v-model` only mirrors it and changes to `v-model` are not
|
|
14
|
+
* written back. Undo only takes back this user's own changes.
|
|
15
|
+
*/
|
|
16
|
+
function collaboration(options) {
|
|
17
|
+
const map = options.map;
|
|
18
|
+
const { awareness, user } = options;
|
|
19
|
+
if (awareness && user) {
|
|
20
|
+
const color = user.color ?? collaborationColors[map.doc.clientID % collaborationColors.length];
|
|
21
|
+
awareness.setLocalStateField("user", {
|
|
22
|
+
name: user.name,
|
|
23
|
+
color
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const binding = createBinding(map);
|
|
27
|
+
const undoManager = new Y.UndoManager(map, {
|
|
28
|
+
trackedOrigins: /* @__PURE__ */ new Set([binding.origin]),
|
|
29
|
+
captureTimeout: 500
|
|
30
|
+
});
|
|
31
|
+
binding.stopCapturing = () => undoManager.stopCapturing();
|
|
32
|
+
undoManager.on("stack-item-added", (event) => {
|
|
33
|
+
if (!event.stackItem.meta.has("selection")) event.stackItem.meta.set("selection", binding.selectionBefore ?? null);
|
|
34
|
+
});
|
|
35
|
+
undoManager.on("stack-item-popped", (event) => {
|
|
36
|
+
const view = binding.view;
|
|
37
|
+
const place = event.stackItem.meta.get("selection");
|
|
38
|
+
if (!view || !place) return;
|
|
39
|
+
const pos = positionOf(binding, view.state.doc, place);
|
|
40
|
+
if (pos === null) return;
|
|
41
|
+
view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(pos))));
|
|
42
|
+
});
|
|
43
|
+
const run = (action) => (_state, dispatch) => {
|
|
44
|
+
if (!(action === "undo" ? undoManager.undoStack : undoManager.redoStack).length) return false;
|
|
45
|
+
if (dispatch) {
|
|
46
|
+
binding.undoing = action;
|
|
47
|
+
try {
|
|
48
|
+
undoManager[action]();
|
|
49
|
+
} finally {
|
|
50
|
+
binding.undoing = void 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
const shared = options.comments;
|
|
56
|
+
const threads = shallowRef([]);
|
|
57
|
+
const readThreads = () => threads.value = readSharedComments(shared);
|
|
58
|
+
if (shared) readThreads();
|
|
59
|
+
const commentsPlugin = new Plugin({ view: () => {
|
|
60
|
+
shared.observe(readThreads);
|
|
61
|
+
readThreads();
|
|
62
|
+
return { destroy: () => shared.unobserve(readThreads) };
|
|
63
|
+
} });
|
|
64
|
+
return defineEditorExtension({
|
|
65
|
+
name: "collaboration",
|
|
66
|
+
externalDocument: true,
|
|
67
|
+
comments: shared ? () => ({
|
|
68
|
+
threads,
|
|
69
|
+
apply: (action) => applySharedCommentAction(shared, action, binding.untracked)
|
|
70
|
+
}) : void 0,
|
|
71
|
+
plugins: () => [
|
|
72
|
+
syncPlugin(binding),
|
|
73
|
+
...awareness ? [cursorsPlugin(binding, awareness)] : [],
|
|
74
|
+
...shared ? [commentsPlugin] : []
|
|
75
|
+
],
|
|
76
|
+
history: () => ({
|
|
77
|
+
plugins: [],
|
|
78
|
+
undo: run("undo"),
|
|
79
|
+
redo: run("redo")
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Write a document into an empty map, e.g. when a document that so far was
|
|
85
|
+
* saved as JSON starts being edited together. Do it once, where the
|
|
86
|
+
* document is created (often on the server): two clients filling the same
|
|
87
|
+
* empty map at the same time would both add their copy.
|
|
88
|
+
*/
|
|
89
|
+
function fillMap(map, doc, extensions = [editorKit()]) {
|
|
90
|
+
const schema = resolveEditorExtensions(extensions).schema;
|
|
91
|
+
writeDoc(map, blocksToDoc(schema, doc.blocks));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The document a shared map holds, read without an editor, e.g. to save a
|
|
95
|
+
* version on the server. Pass the extensions the editors use; an empty map
|
|
96
|
+
* reads as no blocks.
|
|
97
|
+
*/
|
|
98
|
+
function readMap(map, extensions = [editorKit()]) {
|
|
99
|
+
const schema = resolveEditorExtensions(extensions).schema;
|
|
100
|
+
const doc = readDoc(map, schema);
|
|
101
|
+
return {
|
|
102
|
+
version: 1,
|
|
103
|
+
blocks: doc ? docToBlocks(doc) : []
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export { collaboration, collaborationColors, fillMap, readMap };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/collaboration/index.ts"],"sourcesContent":["import {\n applySharedCommentAction,\n collaborationColors,\n readSharedComments,\n type CommentThread,\n type SharedCommentMap,\n} from 'dou-ui'\nimport { shallowRef } from 'vue'\nimport { Plugin, TextSelection, type Command } from 'prosemirror-state'\nimport * as Y from 'yjs'\nimport {\n defineEditorExtension,\n resolveEditorExtensions,\n type EditorExtension,\n} from '../core/extension'\nimport { blocksToDoc, docToBlocks, type EditorDocument, type PartialBlock } from '../core/model'\nimport { editorKit } from '../extensions/editor-kit'\nimport { createBinding, readDoc, syncPlugin, writeDoc, type YBlock } from './binding'\nimport { cursorsPlugin, positionOf, type AwarenessLike } from './cursors'\n\n/** How the people editing together see each other. */\nexport interface CollaborationUser {\n name: string\n /** The cursor color, any CSS color. Picked from the client id when left out. */\n color?: string\n}\n\nexport interface CollaborationOptions {\n /** Where the document lives, e.g. `ydoc.getMap('document')`. */\n map: Y.Map<unknown>\n /** Shares cursors and selections (`y-protocols/awareness`). Without it, others' cursors are not shown. */\n awareness?: AwarenessLike\n /** This user, shown at their cursor to the others. Needs `awareness`. */\n user?: CollaborationUser\n /**\n * Where the comment threads live, e.g. `ydoc.getMap('comments')`, so\n * they reach everyone with the document. `v-model:comments` then only\n * mirrors them. Without it, the text's anchors still travel with the\n * document and the threads are the app's to share.\n */\n comments?: Y.Map<unknown>\n}\n\nexport { collaborationColors }\n\n/**\n * Edit together through a Yjs document. The document lives in `map`: the\n * editor's `v-model` only mirrors it and changes to `v-model` are not\n * written back. Undo only takes back this user's own changes.\n */\nexport function collaboration(options: CollaborationOptions): EditorExtension {\n const map = options.map as Y.Map<YBlock>\n const { awareness, user } = options\n if (awareness && user) {\n const color = user.color ?? collaborationColors[map.doc!.clientID % collaborationColors.length]\n awareness.setLocalStateField('user', { name: user.name, color })\n }\n const binding = createBinding(map)\n const undoManager = new Y.UndoManager(map, {\n trackedOrigins: new Set([binding.origin]),\n captureTimeout: 500,\n })\n binding.stopCapturing = () => undoManager.stopCapturing()\n // put the cursor back where it was when the change was made\n undoManager.on('stack-item-added', (event: { stackItem: { meta: Map<string, unknown> } }) => {\n if (!event.stackItem.meta.has('selection'))\n event.stackItem.meta.set('selection', binding.selectionBefore ?? null)\n })\n undoManager.on('stack-item-popped', (event: { stackItem: { meta: Map<string, unknown> } }) => {\n const view = binding.view\n const place = event.stackItem.meta.get('selection') as Parameters<typeof positionOf>[2] | null\n if (!view || !place) return\n const pos = positionOf(binding, view.state.doc, place)\n if (pos === null) return\n view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(pos))))\n })\n const run =\n (action: 'undo' | 'redo'): Command =>\n (_state, dispatch) => {\n const stack = action === 'undo' ? undoManager.undoStack : undoManager.redoStack\n if (!stack.length) return false\n if (dispatch) {\n binding.undoing = action\n try {\n undoManager[action]()\n } finally {\n binding.undoing = undefined\n }\n }\n return true\n }\n\n const shared = options.comments\n const threads = shallowRef<readonly CommentThread[]>([])\n const readThreads = () => (threads.value = readSharedComments(shared as SharedCommentMap))\n if (shared) readThreads()\n // follows the shared threads while the editor is there\n const commentsPlugin = new Plugin({\n view: () => {\n shared!.observe(readThreads)\n readThreads()\n return { destroy: () => shared!.unobserve(readThreads) }\n },\n })\n\n return defineEditorExtension({\n name: 'collaboration',\n externalDocument: true,\n comments: shared\n ? () => ({\n threads,\n apply: (action) =>\n applySharedCommentAction(shared as SharedCommentMap, action, binding.untracked),\n })\n : undefined,\n plugins: () => [\n syncPlugin(binding),\n ...(awareness ? [cursorsPlugin(binding, awareness)] : []),\n ...(shared ? [commentsPlugin] : []),\n ],\n history: () => ({ plugins: [], undo: run('undo'), redo: run('redo') }),\n })\n}\n\n/**\n * Write a document into an empty map, e.g. when a document that so far was\n * saved as JSON starts being edited together. Do it once, where the\n * document is created (often on the server): two clients filling the same\n * empty map at the same time would both add their copy.\n */\nexport function fillMap(\n map: Y.Map<unknown>,\n doc: EditorDocument<PartialBlock>,\n extensions: (EditorExtension | EditorExtension[])[] = [editorKit()],\n) {\n const schema = resolveEditorExtensions(extensions).schema\n writeDoc(map as Y.Map<YBlock>, blocksToDoc(schema, doc.blocks))\n}\n\n/**\n * The document a shared map holds, read without an editor, e.g. to save a\n * version on the server. Pass the extensions the editors use; an empty map\n * reads as no blocks.\n */\nexport function readMap(\n map: Y.Map<unknown>,\n extensions: (EditorExtension | EditorExtension[])[] = [editorKit()],\n): EditorDocument {\n const schema = resolveEditorExtensions(extensions).schema\n const doc = readDoc(map as Y.Map<YBlock>, schema)\n return { version: 1, blocks: doc ? docToBlocks(doc) : [] }\n}\n\nexport type { AwarenessLike }\n"],"mappings":";;;;;;;;;;;;;;;AAkDA,SAAgB,cAAc,SAAgD;CAC5E,MAAM,MAAM,QAAQ;CACpB,MAAM,EAAE,WAAW,SAAS;CAC5B,IAAI,aAAa,MAAM;EACrB,MAAM,QAAQ,KAAK,SAAS,oBAAoB,IAAI,IAAK,WAAW,oBAAoB;EACxF,UAAU,mBAAmB,QAAQ;GAAE,MAAM,KAAK;GAAM;EAAM,CAAC;CACjE;CACA,MAAM,UAAU,cAAc,GAAG;CACjC,MAAM,cAAc,IAAI,EAAE,YAAY,KAAK;EACzC,gCAAgB,IAAI,IAAI,CAAC,QAAQ,MAAM,CAAC;EACxC,gBAAgB;CAClB,CAAC;CACD,QAAQ,sBAAsB,YAAY,cAAc;CAExD,YAAY,GAAG,qBAAqB,UAAyD;EAC3F,IAAI,CAAC,MAAM,UAAU,KAAK,IAAI,WAAW,GACvC,MAAM,UAAU,KAAK,IAAI,aAAa,QAAQ,mBAAmB,IAAI;CACzE,CAAC;CACD,YAAY,GAAG,sBAAsB,UAAyD;EAC5F,MAAM,OAAO,QAAQ;EACrB,MAAM,QAAQ,MAAM,UAAU,KAAK,IAAI,WAAW;EAClD,IAAI,CAAC,QAAQ,CAAC,OAAO;EACrB,MAAM,MAAM,WAAW,SAAS,KAAK,MAAM,KAAK,KAAK;EACrD,IAAI,QAAQ,MAAM;EAClB,KAAK,SAAS,KAAK,MAAM,GAAG,aAAa,cAAc,KAAK,KAAK,MAAM,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;CAC3F,CAAC;CACD,MAAM,OACH,YACA,QAAQ,aAAa;EAEpB,IAAI,EADU,WAAW,SAAS,YAAY,YAAY,YAAY,UAAA,CAC3D,QAAQ,OAAO;EAC1B,IAAI,UAAU;GACZ,QAAQ,UAAU;GAClB,IAAI;IACF,YAAY,OAAO,CAAC;GACtB,UAAU;IACR,QAAQ,UAAU,KAAA;GACpB;EACF;EACA,OAAO;CACT;CAEF,MAAM,SAAS,QAAQ;CACvB,MAAM,UAAU,WAAqC,CAAC,CAAC;CACvD,MAAM,oBAAqB,QAAQ,QAAQ,mBAAmB,MAA0B;CACxF,IAAI,QAAQ,YAAY;CAExB,MAAM,iBAAiB,IAAI,OAAO,EAChC,YAAY;EACV,OAAQ,QAAQ,WAAW;EAC3B,YAAY;EACZ,OAAO,EAAE,eAAe,OAAQ,UAAU,WAAW,EAAE;CACzD,EACF,CAAC;CAED,OAAO,sBAAsB;EAC3B,MAAM;EACN,kBAAkB;EAClB,UAAU,gBACC;GACL;GACA,QAAQ,WACN,yBAAyB,QAA4B,QAAQ,QAAQ,SAAS;EAClF,KACA,KAAA;EACJ,eAAe;GACb,WAAW,OAAO;GAClB,GAAI,YAAY,CAAC,cAAc,SAAS,SAAS,CAAC,IAAI,CAAC;GACvD,GAAI,SAAS,CAAC,cAAc,IAAI,CAAC;EACnC;EACA,gBAAgB;GAAE,SAAS,CAAC;GAAG,MAAM,IAAI,MAAM;GAAG,MAAM,IAAI,MAAM;EAAE;CACtE,CAAC;AACH;;;;;;;AAQA,SAAgB,QACd,KACA,KACA,aAAsD,CAAC,UAAU,CAAC,GAClE;CACA,MAAM,SAAS,wBAAwB,UAAU,CAAC,CAAC;CACnD,SAAS,KAAsB,YAAY,QAAQ,IAAI,MAAM,CAAC;AAChE;;;;;;AAOA,SAAgB,QACd,KACA,aAAsD,CAAC,UAAU,CAAC,GAClD;CAChB,MAAM,SAAS,wBAAwB,UAAU,CAAC,CAAC;CACnD,MAAM,MAAM,QAAQ,KAAsB,MAAM;CAChD,OAAO;EAAE,SAAS;EAAG,QAAQ,MAAM,YAAY,GAAG,IAAI,CAAC;CAAE;AAC3D"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/collaboration/order.ts
|
|
2
|
+
/**
|
|
3
|
+
* Order keys: strings that sort like the blocks they belong to. A key can
|
|
4
|
+
* always be made between two others, so moving or inserting a block only
|
|
5
|
+
* writes that block's key, never its neighbours'.
|
|
6
|
+
*/
|
|
7
|
+
var DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
8
|
+
/** A key between `a` and `b` (`b === null` means no upper bound). Keys never end in `0`. */
|
|
9
|
+
function midpoint(a, b) {
|
|
10
|
+
if (b !== null) {
|
|
11
|
+
let n = 0;
|
|
12
|
+
while ((a[n] ?? "0") === b[n]) n++;
|
|
13
|
+
if (n > 0) return b.slice(0, n) + midpoint(a.slice(n), b.slice(n));
|
|
14
|
+
}
|
|
15
|
+
const low = a ? DIGITS.indexOf(a[0]) : 0;
|
|
16
|
+
const high = b !== null ? DIGITS.indexOf(b[0]) : 62;
|
|
17
|
+
if (high - low > 1) return DIGITS[Math.round((low + high) / 2)];
|
|
18
|
+
if (b !== null && b.length > 1) return b.slice(0, 1);
|
|
19
|
+
return DIGITS[low] + midpoint(a.slice(1), null);
|
|
20
|
+
}
|
|
21
|
+
/** A key after `a` and before `b`; either may be missing. Needs `a < b`. */
|
|
22
|
+
function keyBetween(a, b) {
|
|
23
|
+
if (a !== null && b !== null && a >= b) throw new Error(`[@dou-ui/editor] bad order keys ${a} ${b}`);
|
|
24
|
+
return midpoint(a ?? "", b);
|
|
25
|
+
}
|
|
26
|
+
/** `n` keys in order between `a` and `b`, kept short by splitting the range in halves. */
|
|
27
|
+
function keysBetween(a, b, n) {
|
|
28
|
+
if (n <= 0) return [];
|
|
29
|
+
const middle = keyBetween(a, b);
|
|
30
|
+
const left = Math.floor((n - 1) / 2);
|
|
31
|
+
return [
|
|
32
|
+
...keysBetween(a, middle, left),
|
|
33
|
+
middle,
|
|
34
|
+
...keysBetween(middle, b, n - 1 - left)
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Which of `keys` (in their wanted order) can stay: the longest strictly
|
|
39
|
+
* increasing run. Missing keys (`null`) never stay. Returns the indexes.
|
|
40
|
+
*/
|
|
41
|
+
function keptIndexes(keys) {
|
|
42
|
+
const tails = [];
|
|
43
|
+
const previous = [];
|
|
44
|
+
keys.forEach((key, index) => {
|
|
45
|
+
if (key === null) return;
|
|
46
|
+
let low = 0;
|
|
47
|
+
let high = tails.length;
|
|
48
|
+
while (low < high) {
|
|
49
|
+
const mid = low + high >> 1;
|
|
50
|
+
if (keys[tails[mid]] < key) low = mid + 1;
|
|
51
|
+
else high = mid;
|
|
52
|
+
}
|
|
53
|
+
previous[index] = low > 0 ? tails[low - 1] : -1;
|
|
54
|
+
tails[low] = index;
|
|
55
|
+
});
|
|
56
|
+
const kept = /* @__PURE__ */ new Set();
|
|
57
|
+
let index = tails.length ? tails[tails.length - 1] : -1;
|
|
58
|
+
while (index >= 0) {
|
|
59
|
+
kept.add(index);
|
|
60
|
+
index = previous[index];
|
|
61
|
+
}
|
|
62
|
+
return kept;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Order keys for items in their wanted order, given each one's current key
|
|
66
|
+
* (`null` when it has none). The longest run already in order keeps its
|
|
67
|
+
* keys; the others get fresh keys between their kept neighbours.
|
|
68
|
+
*/
|
|
69
|
+
function orderKeys(keys) {
|
|
70
|
+
const kept = keptIndexes(keys);
|
|
71
|
+
const result = keys.slice();
|
|
72
|
+
let i = 0;
|
|
73
|
+
while (i < result.length) {
|
|
74
|
+
if (kept.has(i)) {
|
|
75
|
+
i++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
let j = i;
|
|
79
|
+
while (j < result.length && !kept.has(j)) j++;
|
|
80
|
+
const fresh = keysBetween(i > 0 ? result[i - 1] : null, j < result.length ? result[j] : null, j - i);
|
|
81
|
+
for (let k = i; k < j; k++) result[k] = fresh[k - i];
|
|
82
|
+
i = j;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
export { keptIndexes, keyBetween, keysBetween, orderKeys };
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.js","names":[],"sources":["../../src/collaboration/order.ts"],"sourcesContent":["/**\n * Order keys: strings that sort like the blocks they belong to. A key can\n * always be made between two others, so moving or inserting a block only\n * writes that block's key, never its neighbours'.\n */\n\n// ascending in code-unit order, so plain `<` compares keys\nconst DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\n\n/** A key between `a` and `b` (`b === null` means no upper bound). Keys never end in `0`. */\nfunction midpoint(a: string, b: string | null): string {\n if (b !== null) {\n let n = 0\n while ((a[n] ?? '0') === b[n]) n++\n if (n > 0) return b.slice(0, n) + midpoint(a.slice(n), b.slice(n))\n }\n const low = a ? DIGITS.indexOf(a[0]!) : 0\n const high = b !== null ? DIGITS.indexOf(b[0]!) : DIGITS.length\n if (high - low > 1) return DIGITS[Math.round((low + high) / 2)]!\n if (b !== null && b.length > 1) return b.slice(0, 1)\n return DIGITS[low]! + midpoint(a.slice(1), null)\n}\n\n/** A key after `a` and before `b`; either may be missing. Needs `a < b`. */\nexport function keyBetween(a: string | null, b: string | null): string {\n if (a !== null && b !== null && a >= b)\n throw new Error(`[@dou-ui/editor] bad order keys ${a} ${b}`)\n return midpoint(a ?? '', b)\n}\n\n/** `n` keys in order between `a` and `b`, kept short by splitting the range in halves. */\nexport function keysBetween(a: string | null, b: string | null, n: number): string[] {\n if (n <= 0) return []\n const middle = keyBetween(a, b)\n const left = Math.floor((n - 1) / 2)\n return [...keysBetween(a, middle, left), middle, ...keysBetween(middle, b, n - 1 - left)]\n}\n\n/**\n * Which of `keys` (in their wanted order) can stay: the longest strictly\n * increasing run. Missing keys (`null`) never stay. Returns the indexes.\n */\nexport function keptIndexes(keys: (string | null)[]): Set<number> {\n // patience sorting with back links\n const tails: number[] = []\n const previous: number[] = []\n keys.forEach((key, index) => {\n if (key === null) return\n let low = 0\n let high = tails.length\n while (low < high) {\n const mid = (low + high) >> 1\n if (keys[tails[mid]!]! < key) low = mid + 1\n else high = mid\n }\n previous[index] = low > 0 ? tails[low - 1]! : -1\n tails[low] = index\n })\n const kept = new Set<number>()\n let index = tails.length ? tails[tails.length - 1]! : -1\n while (index >= 0) {\n kept.add(index)\n index = previous[index]!\n }\n return kept\n}\n\n/**\n * Order keys for items in their wanted order, given each one's current key\n * (`null` when it has none). The longest run already in order keeps its\n * keys; the others get fresh keys between their kept neighbours.\n */\nexport function orderKeys(keys: (string | null)[]): string[] {\n const kept = keptIndexes(keys)\n const result = keys.slice()\n let i = 0\n while (i < result.length) {\n if (kept.has(i)) {\n i++\n continue\n }\n let j = i\n while (j < result.length && !kept.has(j)) j++\n const fresh = keysBetween(\n i > 0 ? result[i - 1]! : null,\n j < result.length ? result[j]! : null,\n j - i,\n )\n for (let k = i; k < j; k++) result[k] = fresh[k - i]!\n i = j\n }\n return result as string[]\n}\n"],"mappings":";;;;;;AAOA,IAAM,SAAS;;AAGf,SAAS,SAAS,GAAW,GAA0B;CACrD,IAAI,MAAM,MAAM;EACd,IAAI,IAAI;EACR,QAAQ,EAAE,MAAM,SAAS,EAAE,IAAI;EAC/B,IAAI,IAAI,GAAG,OAAO,EAAE,MAAM,GAAG,CAAC,IAAI,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;CACnE;CACA,MAAM,MAAM,IAAI,OAAO,QAAQ,EAAE,EAAG,IAAI;CACxC,MAAM,OAAO,MAAM,OAAO,OAAO,QAAQ,EAAE,EAAG,IAAI;CAClD,IAAI,OAAO,MAAM,GAAG,OAAO,OAAO,KAAK,OAAO,MAAM,QAAQ,CAAC;CAC7D,IAAI,MAAM,QAAQ,EAAE,SAAS,GAAG,OAAO,EAAE,MAAM,GAAG,CAAC;CACnD,OAAO,OAAO,OAAQ,SAAS,EAAE,MAAM,CAAC,GAAG,IAAI;AACjD;;AAGA,SAAgB,WAAW,GAAkB,GAA0B;CACrE,IAAI,MAAM,QAAQ,MAAM,QAAQ,KAAK,GACnC,MAAM,IAAI,MAAM,mCAAmC,EAAE,GAAG,GAAG;CAC7D,OAAO,SAAS,KAAK,IAAI,CAAC;AAC5B;;AAGA,SAAgB,YAAY,GAAkB,GAAkB,GAAqB;CACnF,IAAI,KAAK,GAAG,OAAO,CAAC;CACpB,MAAM,SAAS,WAAW,GAAG,CAAC;CAC9B,MAAM,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC;CACnC,OAAO;EAAC,GAAG,YAAY,GAAG,QAAQ,IAAI;EAAG;EAAQ,GAAG,YAAY,QAAQ,GAAG,IAAI,IAAI,IAAI;CAAC;AAC1F;;;;;AAMA,SAAgB,YAAY,MAAsC;CAEhE,MAAM,QAAkB,CAAC;CACzB,MAAM,WAAqB,CAAC;CAC5B,KAAK,SAAS,KAAK,UAAU;EAC3B,IAAI,QAAQ,MAAM;EAClB,IAAI,MAAM;EACV,IAAI,OAAO,MAAM;EACjB,OAAO,MAAM,MAAM;GACjB,MAAM,MAAO,MAAM,QAAS;GAC5B,IAAI,KAAK,MAAM,QAAU,KAAK,MAAM,MAAM;QACrC,OAAO;EACd;EACA,SAAS,SAAS,MAAM,IAAI,MAAM,MAAM,KAAM;EAC9C,MAAM,OAAO;CACf,CAAC;CACD,MAAM,uBAAO,IAAI,IAAY;CAC7B,IAAI,QAAQ,MAAM,SAAS,MAAM,MAAM,SAAS,KAAM;CACtD,OAAO,SAAS,GAAG;EACjB,KAAK,IAAI,KAAK;EACd,QAAQ,SAAS;CACnB;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,UAAU,MAAmC;CAC3D,MAAM,OAAO,YAAY,IAAI;CAC7B,MAAM,SAAS,KAAK,MAAM;CAC1B,IAAI,IAAI;CACR,OAAO,IAAI,OAAO,QAAQ;EACxB,IAAI,KAAK,IAAI,CAAC,GAAG;GACf;GACA;EACF;EACA,IAAI,IAAI;EACR,OAAO,IAAI,OAAO,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG;EAC1C,MAAM,QAAQ,YACZ,IAAI,IAAI,OAAO,IAAI,KAAM,MACzB,IAAI,OAAO,SAAS,OAAO,KAAM,MACjC,IAAI,CACN;EACA,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,OAAO,KAAK,MAAM,IAAI;EAClD,IAAI;CACN;CACA,OAAO;AACT"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { createId, inlineOf } from "../core/model.js";
|
|
2
|
+
import { orderKeys } from "./order.js";
|
|
3
|
+
import { inlineFrom, insertInline, updateText } from "./text.js";
|
|
4
|
+
import * as Y from "yjs";
|
|
5
|
+
// src/collaboration/table.ts
|
|
6
|
+
var PARTS = [
|
|
7
|
+
"rows",
|
|
8
|
+
"columns",
|
|
9
|
+
"cells",
|
|
10
|
+
"merges"
|
|
11
|
+
];
|
|
12
|
+
var cellKey = (row, column) => `${row}:${column}`;
|
|
13
|
+
var rectKey = (rect) => `${rect.top},${rect.left},${rect.bottom},${rect.right}`;
|
|
14
|
+
function part(yblock, name) {
|
|
15
|
+
let map = yblock.get(name);
|
|
16
|
+
if (!map) {
|
|
17
|
+
map = new Y.Map();
|
|
18
|
+
yblock.set(name, map);
|
|
19
|
+
}
|
|
20
|
+
return map;
|
|
21
|
+
}
|
|
22
|
+
/** Ids by their order key; ties by id. */
|
|
23
|
+
function ordered(map) {
|
|
24
|
+
if (!map) return [];
|
|
25
|
+
const orderOf = (id) => map.get(id).get("order") ?? "";
|
|
26
|
+
return Array.from(map.keys()).sort((a, b) => {
|
|
27
|
+
const x = orderOf(a);
|
|
28
|
+
const y = orderOf(b);
|
|
29
|
+
return x < y ? -1 : x > y ? 1 : a < b ? -1 : a > b ? 1 : 0;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** The first and past-the-last index of the ids that still exist. */
|
|
33
|
+
function span(ids, index) {
|
|
34
|
+
let low = Infinity;
|
|
35
|
+
let high = -1;
|
|
36
|
+
for (const id of Array.isArray(ids) ? ids : []) {
|
|
37
|
+
const at = index.get(id);
|
|
38
|
+
if (at === void 0) continue;
|
|
39
|
+
low = Math.min(low, at);
|
|
40
|
+
high = Math.max(high, at);
|
|
41
|
+
}
|
|
42
|
+
return high < 0 ? null : [low, high + 1];
|
|
43
|
+
}
|
|
44
|
+
/** Where the merged cells are in the current order; overlapping ones lose by id. */
|
|
45
|
+
function mergeRects(merges, rows, columns) {
|
|
46
|
+
const rects = [];
|
|
47
|
+
if (!merges) return rects;
|
|
48
|
+
const rowIndex = new Map(rows.map((id, index) => [id, index]));
|
|
49
|
+
const colIndex = new Map(columns.map((id, index) => [id, index]));
|
|
50
|
+
for (const id of Array.from(merges.keys()).sort()) {
|
|
51
|
+
const merge = merges.get(id);
|
|
52
|
+
const vertical = span(merge.rows, rowIndex);
|
|
53
|
+
const horizontal = span(merge.columns, colIndex);
|
|
54
|
+
if (!vertical || !horizontal) continue;
|
|
55
|
+
const rect = {
|
|
56
|
+
id,
|
|
57
|
+
top: vertical[0],
|
|
58
|
+
bottom: vertical[1],
|
|
59
|
+
left: horizontal[0],
|
|
60
|
+
right: horizontal[1]
|
|
61
|
+
};
|
|
62
|
+
if ((rect.bottom - rect.top) * (rect.right - rect.left) < 2) continue;
|
|
63
|
+
if (!rects.some((other) => rect.top < other.bottom && other.top < rect.bottom && rect.left < other.right && other.left < rect.right)) rects.push(rect);
|
|
64
|
+
}
|
|
65
|
+
return rects;
|
|
66
|
+
}
|
|
67
|
+
var textToInline = (text, schema) => text?.length ? inlineOf(schema.nodes.paragraph.create(null, inlineFrom(text, schema, false))) : [];
|
|
68
|
+
/** The table node a stored table stands for. */
|
|
69
|
+
function readTable(yblock, type, props) {
|
|
70
|
+
const spec = type.spec.rows;
|
|
71
|
+
const rowsMap = yblock.get("rows");
|
|
72
|
+
const colsMap = yblock.get("columns");
|
|
73
|
+
const cells = yblock.get("cells");
|
|
74
|
+
const rowIds = ordered(rowsMap);
|
|
75
|
+
const colIds = ordered(colsMap);
|
|
76
|
+
if (!rowIds.length || !colIds.length) return spec.write(type, props, []);
|
|
77
|
+
const columns = colIds.map((id) => {
|
|
78
|
+
const { order: _order, ...fields } = colsMap.get(id).toJSON();
|
|
79
|
+
return {
|
|
80
|
+
...fields,
|
|
81
|
+
id
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
const spans = /* @__PURE__ */ new Map();
|
|
85
|
+
for (const rect of mergeRects(yblock.get("merges"), rowIds, colIds)) spans.set(`${rect.top},${rect.left}`, rect);
|
|
86
|
+
const rows = rowIds.map((row, r) => ({
|
|
87
|
+
id: row,
|
|
88
|
+
cells: colIds.map((col, c) => {
|
|
89
|
+
const rect = spans.get(`${r},${c}`);
|
|
90
|
+
return {
|
|
91
|
+
content: textToInline(cells?.get(cellKey(row, col)), type.schema),
|
|
92
|
+
colspan: rect ? rect.right - rect.left : 1,
|
|
93
|
+
rowspan: rect ? rect.bottom - rect.top : 1
|
|
94
|
+
};
|
|
95
|
+
})
|
|
96
|
+
}));
|
|
97
|
+
return spec.write(type, {
|
|
98
|
+
...props,
|
|
99
|
+
columns
|
|
100
|
+
}, rows);
|
|
101
|
+
}
|
|
102
|
+
/** Bring a list of ids (rows or columns) in the map to `ids`, in that order. */
|
|
103
|
+
function writeOrder(map, before, ids) {
|
|
104
|
+
const kept = new Set(ids);
|
|
105
|
+
for (const id of before) if (!kept.has(id)) map.delete(id);
|
|
106
|
+
const keys = ids.map((id) => map.get(id)?.get("order") ?? null);
|
|
107
|
+
const next = orderKeys(keys);
|
|
108
|
+
ids.forEach((id, index) => {
|
|
109
|
+
let entry = map.get(id);
|
|
110
|
+
if (!entry) {
|
|
111
|
+
entry = new Y.Map();
|
|
112
|
+
map.set(id, entry);
|
|
113
|
+
}
|
|
114
|
+
if (keys[index] !== next[index]) entry.set("order", next[index]);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Write the table `after` into a stored table that holds `before` (`null`
|
|
119
|
+
* for a new one). Returns whether cells that became visible hold text the
|
|
120
|
+
* editor doesn't show yet, typed by someone else while they were merged away.
|
|
121
|
+
*/
|
|
122
|
+
function writeTable(yblock, before, after) {
|
|
123
|
+
const spec = after.type.spec.rows;
|
|
124
|
+
const grid = spec.grid(after);
|
|
125
|
+
const old = before ? spec.grid(before) : null;
|
|
126
|
+
let props = yblock.get("props");
|
|
127
|
+
if (!props) {
|
|
128
|
+
props = new Y.Map();
|
|
129
|
+
yblock.set("props", props);
|
|
130
|
+
}
|
|
131
|
+
for (const [name, value] of Object.entries(grid.props)) if (JSON.stringify(props.get(name)) !== JSON.stringify(value)) props.set(name, value);
|
|
132
|
+
for (const name of Array.from(props.keys())) if (!(name in grid.props)) props.delete(name);
|
|
133
|
+
const rowsMap = part(yblock, "rows");
|
|
134
|
+
const colsMap = part(yblock, "columns");
|
|
135
|
+
const cells = part(yblock, "cells");
|
|
136
|
+
const merges = part(yblock, "merges");
|
|
137
|
+
const colIds = grid.columns.map((column) => column.id);
|
|
138
|
+
const oldCols = old ? old.columns.map((column) => column.id) : [];
|
|
139
|
+
const oldRows = old?.rows ?? [];
|
|
140
|
+
writeOrder(rowsMap, oldRows, grid.rows);
|
|
141
|
+
writeOrder(colsMap, oldCols, colIds);
|
|
142
|
+
for (const column of grid.columns) {
|
|
143
|
+
const entry = colsMap.get(column.id);
|
|
144
|
+
for (const [name, value] of Object.entries(column)) {
|
|
145
|
+
if (name === "id") continue;
|
|
146
|
+
if (JSON.stringify(entry.get(name)) !== JSON.stringify(value)) entry.set(name, value);
|
|
147
|
+
}
|
|
148
|
+
for (const name of Array.from(entry.keys())) if (name !== "order" && !(name in column)) entry.delete(name);
|
|
149
|
+
}
|
|
150
|
+
const allRows = /* @__PURE__ */ new Set([...oldRows, ...rowsMap.keys()]);
|
|
151
|
+
const allCols = /* @__PURE__ */ new Set([...oldCols, ...colsMap.keys()]);
|
|
152
|
+
for (const row of oldRows) if (!rowsMap.has(row)) for (const col of allCols) cells.delete(cellKey(row, col));
|
|
153
|
+
for (const col of oldCols) if (!colsMap.has(col)) for (const row of allRows) cells.delete(cellKey(row, col));
|
|
154
|
+
const stored = mergeRects(merges, grid.rows, colIds);
|
|
155
|
+
const shown = new Set(stored.map(rectKey));
|
|
156
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
157
|
+
const created = [];
|
|
158
|
+
for (const cell of grid.cells) {
|
|
159
|
+
if (cell.rowspan * cell.colspan < 2) continue;
|
|
160
|
+
const rect = {
|
|
161
|
+
top: cell.row,
|
|
162
|
+
left: cell.col,
|
|
163
|
+
bottom: cell.row + cell.rowspan,
|
|
164
|
+
right: cell.col + cell.colspan
|
|
165
|
+
};
|
|
166
|
+
const key = rectKey(rect);
|
|
167
|
+
wanted.add(key);
|
|
168
|
+
if (shown.has(key)) continue;
|
|
169
|
+
const id = createId();
|
|
170
|
+
created.push(id);
|
|
171
|
+
merges.set(id, {
|
|
172
|
+
rows: grid.rows.slice(rect.top, rect.bottom),
|
|
173
|
+
columns: colIds.slice(rect.left, rect.right)
|
|
174
|
+
});
|
|
175
|
+
for (let r = rect.top; r < rect.bottom; r++) for (let c = rect.left; c < rect.right; c++) {
|
|
176
|
+
if (r === rect.top && c === rect.left) continue;
|
|
177
|
+
const text = cells.get(cellKey(grid.rows[r], colIds[c]));
|
|
178
|
+
if (text?.length) text.delete(0, text.length);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const kept = new Set(created);
|
|
182
|
+
for (const rect of stored) if (wanted.has(rectKey(rect))) kept.add(rect.id);
|
|
183
|
+
for (const id of Array.from(merges.keys())) if (!kept.has(id)) merges.delete(id);
|
|
184
|
+
const oldCells = /* @__PURE__ */ new Map();
|
|
185
|
+
for (const cell of old?.cells ?? []) oldCells.set(cellKey(old.rows[cell.row], oldCols[cell.col]), cell.node);
|
|
186
|
+
let revealed = false;
|
|
187
|
+
for (const cell of grid.cells) {
|
|
188
|
+
const key = cellKey(grid.rows[cell.row], colIds[cell.col]);
|
|
189
|
+
const node = cell.node;
|
|
190
|
+
let text = cells.get(key);
|
|
191
|
+
if (!text) {
|
|
192
|
+
text = new Y.Text();
|
|
193
|
+
cells.set(key, text);
|
|
194
|
+
insertInline(text, 0, node, 0, node.content.size);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const previous = oldCells.get(key);
|
|
198
|
+
if (previous) {
|
|
199
|
+
if (previous !== node && !previous.content.eq(node.content)) updateText(text, previous, node);
|
|
200
|
+
} else if (node.content.size) {
|
|
201
|
+
text.delete(0, text.length);
|
|
202
|
+
insertInline(text, 0, node, 0, node.content.size);
|
|
203
|
+
} else if (text.length) revealed = true;
|
|
204
|
+
}
|
|
205
|
+
return revealed;
|
|
206
|
+
}
|
|
207
|
+
/** Forget a stored table's rows, e.g. when the block stops being a table. */
|
|
208
|
+
function clearTable(yblock) {
|
|
209
|
+
for (const name of PARTS) if (yblock.has(name)) yblock.delete(name);
|
|
210
|
+
}
|
|
211
|
+
/** The cell a position in a table falls in, and the offset in its text. */
|
|
212
|
+
function cellAt(table, offset) {
|
|
213
|
+
const grid = table.type.spec.rows.grid(table);
|
|
214
|
+
for (const cell of grid.cells) if (offset > cell.offset && offset < cell.offset + cell.node.nodeSize) {
|
|
215
|
+
const column = grid.columns[cell.col].id;
|
|
216
|
+
return {
|
|
217
|
+
key: cellKey(grid.rows[cell.row], column),
|
|
218
|
+
index: offset - cell.offset - 1
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
/** Where a cell's text starts in a table, by its key; `null` when it isn't shown. */
|
|
224
|
+
function cellStart(table, key) {
|
|
225
|
+
const grid = table.type.spec.rows.grid(table);
|
|
226
|
+
for (const cell of grid.cells) if (cellKey(grid.rows[cell.row], grid.columns[cell.col].id) === key) return {
|
|
227
|
+
offset: cell.offset + 1,
|
|
228
|
+
size: cell.node.content.size
|
|
229
|
+
};
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
export { cellAt, cellStart, clearTable, readTable, writeTable };
|
|
233
|
+
|
|
234
|
+
//# sourceMappingURL=table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table.js","names":[],"sources":["../../src/collaboration/table.ts"],"sourcesContent":["import type { Node as PMNode, NodeType, Schema } from 'prosemirror-model'\nimport * as Y from 'yjs'\nimport {\n createId,\n inlineOf,\n type BlockProps,\n type PartialTableRow,\n type RowsSpec,\n type TableGrid,\n} from '../core/model'\nimport { orderKeys } from './order'\nimport { inlineFrom, insertInline, updateText } from './text'\n\n/*\n * How a table is stored: rows, columns, cells and merged cells apart, so\n * that edits made at the same time merge instead of overwriting each other.\n *\n * rows: Y.Map<row id, Y.Map { order }>\n * columns: Y.Map<column id, Y.Map { order, width?, align? }>\n * cells: Y.Map<\"row id:column id\", Y.Text>\n * merges: Y.Map<merge id, { rows: string[], columns: string[] }>\n *\n * Moving a row only changes its order key, so text typed into its cells\n * meanwhile stays with it. A merged cell lists the rows and columns it\n * covered when it was made, and spans from the first to the last of them\n * that still exist in the current order: a row inserted inside it falls\n * inside, and deleting its edge row shrinks it. Merged cells that overlap\n * are settled by id, the same way on every client.\n */\n\ntype YEntry = Y.Map<unknown>\n\nconst PARTS = ['rows', 'columns', 'cells', 'merges'] as const\n\nconst cellKey = (row: string, column: string) => `${row}:${column}`\n\ninterface StoredMerge {\n rows: string[]\n columns: string[]\n}\n\ninterface Rect {\n id: string\n top: number\n left: number\n bottom: number\n right: number\n}\n\nconst rectKey = (rect: Omit<Rect, 'id'>) => `${rect.top},${rect.left},${rect.bottom},${rect.right}`\n\nfunction part<T>(yblock: YEntry, name: (typeof PARTS)[number]): Y.Map<T> {\n let map = yblock.get(name) as Y.Map<T> | undefined\n if (!map) {\n map = new Y.Map<T>()\n yblock.set(name, map)\n }\n return map\n}\n\n/** Ids by their order key; ties by id. */\nfunction ordered(map: Y.Map<YEntry> | undefined): string[] {\n if (!map) return []\n const orderOf = (id: string) => (map.get(id)!.get('order') as string | undefined) ?? ''\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh array\n return Array.from(map.keys()).sort((a, b) => {\n const x = orderOf(a)\n const y = orderOf(b)\n return x < y ? -1 : x > y ? 1 : a < b ? -1 : a > b ? 1 : 0\n })\n}\n\n/** The first and past-the-last index of the ids that still exist. */\nfunction span(ids: unknown, index: Map<string, number>): [number, number] | null {\n let low = Infinity\n let high = -1\n for (const id of Array.isArray(ids) ? ids : []) {\n const at = index.get(id as string)\n if (at === undefined) continue\n low = Math.min(low, at)\n high = Math.max(high, at)\n }\n return high < 0 ? null : [low, high + 1]\n}\n\n/** Where the merged cells are in the current order; overlapping ones lose by id. */\nfunction mergeRects(merges: Y.Map<StoredMerge> | undefined, rows: string[], columns: string[]) {\n const rects: Rect[] = []\n if (!merges) return rects\n const rowIndex = new Map(rows.map((id, index) => [id, index]))\n const colIndex = new Map(columns.map((id, index) => [id, index]))\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh array\n for (const id of Array.from(merges.keys()).sort()) {\n const merge = merges.get(id)!\n const vertical = span(merge.rows, rowIndex)\n const horizontal = span(merge.columns, colIndex)\n if (!vertical || !horizontal) continue\n const rect = {\n id,\n top: vertical[0]!,\n bottom: vertical[1]!,\n left: horizontal[0]!,\n right: horizontal[1]!,\n }\n if ((rect.bottom - rect.top) * (rect.right - rect.left) < 2) continue\n const overlaps = rects.some(\n (other) =>\n rect.top < other.bottom &&\n other.top < rect.bottom &&\n rect.left < other.right &&\n other.left < rect.right,\n )\n if (!overlaps) rects.push(rect)\n }\n return rects\n}\n\nconst textToInline = (text: Y.Text | undefined, schema: Schema) =>\n text?.length\n ? inlineOf(schema.nodes.paragraph!.create(null, inlineFrom(text, schema, false)))\n : []\n\n/** The table node a stored table stands for. */\nexport function readTable(yblock: YEntry, type: NodeType, props: BlockProps): PMNode {\n const spec = type.spec.rows as RowsSpec\n const rowsMap = yblock.get('rows') as Y.Map<YEntry> | undefined\n const colsMap = yblock.get('columns') as Y.Map<YEntry> | undefined\n const cells = yblock.get('cells') as Y.Map<Y.Text> | undefined\n const rowIds = ordered(rowsMap)\n const colIds = ordered(colsMap)\n if (!rowIds.length || !colIds.length) return spec.write(type, props, [])\n const columns = colIds.map((id) => {\n const { order: _order, ...fields } = colsMap!.get(id)!.toJSON()\n return { ...fields, id }\n })\n const spans = new Map<string, Rect>()\n for (const rect of mergeRects(yblock.get('merges') as Y.Map<StoredMerge>, rowIds, colIds))\n spans.set(`${rect.top},${rect.left}`, rect)\n const rows: PartialTableRow[] = rowIds.map((row, r) => ({\n id: row,\n cells: colIds.map((col, c) => {\n const rect = spans.get(`${r},${c}`)\n return {\n content: textToInline(cells?.get(cellKey(row, col)), type.schema),\n colspan: rect ? rect.right - rect.left : 1,\n rowspan: rect ? rect.bottom - rect.top : 1,\n }\n }),\n }))\n return spec.write(type, { ...props, columns }, rows)\n}\n\n/** Bring a list of ids (rows or columns) in the map to `ids`, in that order. */\nfunction writeOrder(map: Y.Map<YEntry>, before: string[], ids: string[]) {\n const kept = new Set(ids)\n for (const id of before) if (!kept.has(id)) map.delete(id)\n const keys = ids.map((id) => (map.get(id)?.get('order') as string | undefined) ?? null)\n const next = orderKeys(keys)\n ids.forEach((id, index) => {\n let entry = map.get(id)\n if (!entry) {\n entry = new Y.Map<unknown>()\n map.set(id, entry)\n }\n if (keys[index] !== next[index]) entry.set('order', next[index])\n })\n}\n\n/**\n * Write the table `after` into a stored table that holds `before` (`null`\n * for a new one). Returns whether cells that became visible hold text the\n * editor doesn't show yet, typed by someone else while they were merged away.\n */\nexport function writeTable(yblock: YEntry, before: PMNode | null, after: PMNode): boolean {\n const spec = after.type.spec.rows as RowsSpec\n const grid = spec.grid(after)\n const old: TableGrid | null = before ? spec.grid(before) : null\n\n let props = yblock.get('props') as Y.Map<unknown> | undefined\n if (!props) {\n props = new Y.Map<unknown>()\n yblock.set('props', props)\n }\n for (const [name, value] of Object.entries(grid.props))\n if (JSON.stringify(props.get(name)) !== JSON.stringify(value)) props.set(name, value)\n for (const name of Array.from(props.keys())) if (!(name in grid.props)) props.delete(name)\n\n const rowsMap = part<YEntry>(yblock, 'rows')\n const colsMap = part<YEntry>(yblock, 'columns')\n const cells = part<Y.Text>(yblock, 'cells')\n const merges = part<StoredMerge>(yblock, 'merges')\n const colIds = grid.columns.map((column) => column.id)\n const oldCols = old ? old.columns.map((column) => column.id) : []\n const oldRows = old?.rows ?? []\n\n writeOrder(rowsMap, oldRows, grid.rows)\n writeOrder(colsMap, oldCols, colIds)\n for (const column of grid.columns) {\n const entry = colsMap.get(column.id)!\n for (const [name, value] of Object.entries(column) as [string, unknown][]) {\n if (name === 'id') continue\n if (JSON.stringify(entry.get(name)) !== JSON.stringify(value)) entry.set(name, value)\n }\n for (const name of Array.from(entry.keys()))\n if (name !== 'order' && !(name in column)) entry.delete(name)\n }\n\n // the cells of deleted rows and columns go with them\n const allRows = new Set([...oldRows, ...rowsMap.keys()])\n const allCols = new Set([...oldCols, ...colsMap.keys()])\n for (const row of oldRows)\n if (!rowsMap.has(row)) for (const col of allCols) cells.delete(cellKey(row, col))\n for (const col of oldCols)\n if (!colsMap.has(col)) for (const row of allRows) cells.delete(cellKey(row, col))\n\n // merged cells: keep the stored ones still shown, add the new ones\n const stored = mergeRects(merges, grid.rows, colIds)\n const shown = new Set(stored.map(rectKey))\n const wanted = new Set<string>()\n const created: string[] = []\n for (const cell of grid.cells) {\n if (cell.rowspan * cell.colspan < 2) continue\n const rect = {\n top: cell.row,\n left: cell.col,\n bottom: cell.row + cell.rowspan,\n right: cell.col + cell.colspan,\n }\n const key = rectKey(rect)\n wanted.add(key)\n if (shown.has(key)) continue\n const id = createId()\n created.push(id)\n merges.set(id, {\n rows: grid.rows.slice(rect.top, rect.bottom),\n columns: colIds.slice(rect.left, rect.right),\n })\n // merging moved the covered cells' text into this one\n for (let r = rect.top; r < rect.bottom; r++) {\n for (let c = rect.left; c < rect.right; c++) {\n if (r === rect.top && c === rect.left) continue\n const text = cells.get(cellKey(grid.rows[r]!, colIds[c]!))\n if (text?.length) text.delete(0, text.length)\n }\n }\n }\n const kept = new Set(created)\n for (const rect of stored) if (wanted.has(rectKey(rect))) kept.add(rect.id)\n for (const id of Array.from(merges.keys())) if (!kept.has(id)) merges.delete(id)\n\n // cell text\n const oldCells = new Map<string, PMNode>()\n for (const cell of old?.cells ?? [])\n oldCells.set(cellKey(old!.rows[cell.row]!, oldCols[cell.col]!), cell.node)\n let revealed = false\n for (const cell of grid.cells) {\n const key = cellKey(grid.rows[cell.row]!, colIds[cell.col]!)\n const node = cell.node\n let text = cells.get(key)\n if (!text) {\n text = new Y.Text()\n cells.set(key, text)\n insertInline(text, 0, node, 0, node.content.size)\n continue\n }\n const previous = oldCells.get(key)\n if (previous) {\n if (previous !== node && !previous.content.eq(node.content)) updateText(text, previous, node)\n } else if (node.content.size) {\n // shown again or reused: the editor's content wins\n text.delete(0, text.length)\n insertInline(text, 0, node, 0, node.content.size)\n } else if (text.length) {\n revealed = true\n }\n }\n return revealed\n}\n\n/** Forget a stored table's rows, e.g. when the block stops being a table. */\nexport function clearTable(yblock: YEntry) {\n for (const name of PARTS) if (yblock.has(name)) yblock.delete(name)\n}\n\n/** The cell a position in a table falls in, and the offset in its text. */\nexport function cellAt(table: PMNode, offset: number): { key: string; index: number } | null {\n const grid = (table.type.spec.rows as RowsSpec).grid(table)\n for (const cell of grid.cells) {\n if (offset > cell.offset && offset < cell.offset + cell.node.nodeSize) {\n const column = grid.columns[cell.col]!.id\n return { key: cellKey(grid.rows[cell.row]!, column), index: offset - cell.offset - 1 }\n }\n }\n return null\n}\n\n/** Where a cell's text starts in a table, by its key; `null` when it isn't shown. */\nexport function cellStart(table: PMNode, key: string): { offset: number; size: number } | null {\n const grid = (table.type.spec.rows as RowsSpec).grid(table)\n for (const cell of grid.cells) {\n if (cellKey(grid.rows[cell.row]!, grid.columns[cell.col]!.id) === key)\n return { offset: cell.offset + 1, size: cell.node.content.size }\n }\n return null\n}\n"],"mappings":";;;;;AAgCA,IAAM,QAAQ;CAAC;CAAQ;CAAW;CAAS;AAAQ;AAEnD,IAAM,WAAW,KAAa,WAAmB,GAAG,IAAI,GAAG;AAe3D,IAAM,WAAW,SAA2B,GAAG,KAAK,IAAI,GAAG,KAAK,KAAK,GAAG,KAAK,OAAO,GAAG,KAAK;AAE5F,SAAS,KAAQ,QAAgB,MAAwC;CACvE,IAAI,MAAM,OAAO,IAAI,IAAI;CACzB,IAAI,CAAC,KAAK;EACR,MAAM,IAAI,EAAE,IAAO;EACnB,OAAO,IAAI,MAAM,GAAG;CACtB;CACA,OAAO;AACT;;AAGA,SAAS,QAAQ,KAA0C;CACzD,IAAI,CAAC,KAAK,OAAO,CAAC;CAClB,MAAM,WAAW,OAAgB,IAAI,IAAI,EAAE,CAAC,CAAE,IAAI,OAAO,KAA4B;CAErF,OAAO,MAAM,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM;EAC3C,MAAM,IAAI,QAAQ,CAAC;EACnB,MAAM,IAAI,QAAQ,CAAC;EACnB,OAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;CAC3D,CAAC;AACH;;AAGA,SAAS,KAAK,KAAc,OAAqD;CAC/E,IAAI,MAAM;CACV,IAAI,OAAO;CACX,KAAK,MAAM,MAAM,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,GAAG;EAC9C,MAAM,KAAK,MAAM,IAAI,EAAY;EACjC,IAAI,OAAO,KAAA,GAAW;EACtB,MAAM,KAAK,IAAI,KAAK,EAAE;EACtB,OAAO,KAAK,IAAI,MAAM,EAAE;CAC1B;CACA,OAAO,OAAO,IAAI,OAAO,CAAC,KAAK,OAAO,CAAC;AACzC;;AAGA,SAAS,WAAW,QAAwC,MAAgB,SAAmB;CAC7F,MAAM,QAAgB,CAAC;CACvB,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,WAAW,IAAI,IAAI,KAAK,KAAK,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,CAAC;CAC7D,MAAM,WAAW,IAAI,IAAI,QAAQ,KAAK,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,CAAC;CAEhE,KAAK,MAAM,MAAM,MAAM,KAAK,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG;EACjD,MAAM,QAAQ,OAAO,IAAI,EAAE;EAC3B,MAAM,WAAW,KAAK,MAAM,MAAM,QAAQ;EAC1C,MAAM,aAAa,KAAK,MAAM,SAAS,QAAQ;EAC/C,IAAI,CAAC,YAAY,CAAC,YAAY;EAC9B,MAAM,OAAO;GACX;GACA,KAAK,SAAS;GACd,QAAQ,SAAS;GACjB,MAAM,WAAW;GACjB,OAAO,WAAW;EACpB;EACA,KAAK,KAAK,SAAS,KAAK,QAAQ,KAAK,QAAQ,KAAK,QAAQ,GAAG;EAQ7D,IAAI,CAPa,MAAM,MACpB,UACC,KAAK,MAAM,MAAM,UACjB,MAAM,MAAM,KAAK,UACjB,KAAK,OAAO,MAAM,SAClB,MAAM,OAAO,KAAK,KAEjB,GAAU,MAAM,KAAK,IAAI;CAChC;CACA,OAAO;AACT;AAEA,IAAM,gBAAgB,MAA0B,WAC9C,MAAM,SACF,SAAS,OAAO,MAAM,UAAW,OAAO,MAAM,WAAW,MAAM,QAAQ,KAAK,CAAC,CAAC,IAC9E,CAAC;;AAGP,SAAgB,UAAU,QAAgB,MAAgB,OAA2B;CACnF,MAAM,OAAO,KAAK,KAAK;CACvB,MAAM,UAAU,OAAO,IAAI,MAAM;CACjC,MAAM,UAAU,OAAO,IAAI,SAAS;CACpC,MAAM,QAAQ,OAAO,IAAI,OAAO;CAChC,MAAM,SAAS,QAAQ,OAAO;CAC9B,MAAM,SAAS,QAAQ,OAAO;CAC9B,IAAI,CAAC,OAAO,UAAU,CAAC,OAAO,QAAQ,OAAO,KAAK,MAAM,MAAM,OAAO,CAAC,CAAC;CACvE,MAAM,UAAU,OAAO,KAAK,OAAO;EACjC,MAAM,EAAE,OAAO,QAAQ,GAAG,WAAW,QAAS,IAAI,EAAE,CAAC,CAAE,OAAO;EAC9D,OAAO;GAAE,GAAG;GAAQ;EAAG;CACzB,CAAC;CACD,MAAM,wBAAQ,IAAI,IAAkB;CACpC,KAAK,MAAM,QAAQ,WAAW,OAAO,IAAI,QAAQ,GAAyB,QAAQ,MAAM,GACtF,MAAM,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,QAAQ,IAAI;CAC5C,MAAM,OAA0B,OAAO,KAAK,KAAK,OAAO;EACtD,IAAI;EACJ,OAAO,OAAO,KAAK,KAAK,MAAM;GAC5B,MAAM,OAAO,MAAM,IAAI,GAAG,EAAE,GAAG,GAAG;GAClC,OAAO;IACL,SAAS,aAAa,OAAO,IAAI,QAAQ,KAAK,GAAG,CAAC,GAAG,KAAK,MAAM;IAChE,SAAS,OAAO,KAAK,QAAQ,KAAK,OAAO;IACzC,SAAS,OAAO,KAAK,SAAS,KAAK,MAAM;GAC3C;EACF,CAAC;CACH,EAAE;CACF,OAAO,KAAK,MAAM,MAAM;EAAE,GAAG;EAAO;CAAQ,GAAG,IAAI;AACrD;;AAGA,SAAS,WAAW,KAAoB,QAAkB,KAAe;CACvE,MAAM,OAAO,IAAI,IAAI,GAAG;CACxB,KAAK,MAAM,MAAM,QAAQ,IAAI,CAAC,KAAK,IAAI,EAAE,GAAG,IAAI,OAAO,EAAE;CACzD,MAAM,OAAO,IAAI,KAAK,OAAQ,IAAI,IAAI,EAAE,CAAC,EAAE,IAAI,OAAO,KAA4B,IAAI;CACtF,MAAM,OAAO,UAAU,IAAI;CAC3B,IAAI,SAAS,IAAI,UAAU;EACzB,IAAI,QAAQ,IAAI,IAAI,EAAE;EACtB,IAAI,CAAC,OAAO;GACV,QAAQ,IAAI,EAAE,IAAa;GAC3B,IAAI,IAAI,IAAI,KAAK;EACnB;EACA,IAAI,KAAK,WAAW,KAAK,QAAQ,MAAM,IAAI,SAAS,KAAK,MAAM;CACjE,CAAC;AACH;;;;;;AAOA,SAAgB,WAAW,QAAgB,QAAuB,OAAwB;CACxF,MAAM,OAAO,MAAM,KAAK,KAAK;CAC7B,MAAM,OAAO,KAAK,KAAK,KAAK;CAC5B,MAAM,MAAwB,SAAS,KAAK,KAAK,MAAM,IAAI;CAE3D,IAAI,QAAQ,OAAO,IAAI,OAAO;CAC9B,IAAI,CAAC,OAAO;EACV,QAAQ,IAAI,EAAE,IAAa;EAC3B,OAAO,IAAI,SAAS,KAAK;CAC3B;CACA,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,KAAK,GACnD,IAAI,KAAK,UAAU,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,KAAK,GAAG,MAAM,IAAI,MAAM,KAAK;CACtF,KAAK,MAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,CAAC,GAAG,IAAI,EAAE,QAAQ,KAAK,QAAQ,MAAM,OAAO,IAAI;CAEzF,MAAM,UAAU,KAAa,QAAQ,MAAM;CAC3C,MAAM,UAAU,KAAa,QAAQ,SAAS;CAC9C,MAAM,QAAQ,KAAa,QAAQ,OAAO;CAC1C,MAAM,SAAS,KAAkB,QAAQ,QAAQ;CACjD,MAAM,SAAS,KAAK,QAAQ,KAAK,WAAW,OAAO,EAAE;CACrD,MAAM,UAAU,MAAM,IAAI,QAAQ,KAAK,WAAW,OAAO,EAAE,IAAI,CAAC;CAChE,MAAM,UAAU,KAAK,QAAQ,CAAC;CAE9B,WAAW,SAAS,SAAS,KAAK,IAAI;CACtC,WAAW,SAAS,SAAS,MAAM;CACnC,KAAK,MAAM,UAAU,KAAK,SAAS;EACjC,MAAM,QAAQ,QAAQ,IAAI,OAAO,EAAE;EACnC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,GAA0B;GACzE,IAAI,SAAS,MAAM;GACnB,IAAI,KAAK,UAAU,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,KAAK,GAAG,MAAM,IAAI,MAAM,KAAK;EACtF;EACA,KAAK,MAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,CAAC,GACxC,IAAI,SAAS,WAAW,EAAE,QAAQ,SAAS,MAAM,OAAO,IAAI;CAChE;CAGA,MAAM,0BAAU,IAAI,IAAI,CAAC,GAAG,SAAS,GAAG,QAAQ,KAAK,CAAC,CAAC;CACvD,MAAM,0BAAU,IAAI,IAAI,CAAC,GAAG,SAAS,GAAG,QAAQ,KAAK,CAAC,CAAC;CACvD,KAAK,MAAM,OAAO,SAChB,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG,KAAK,MAAM,OAAO,SAAS,MAAM,OAAO,QAAQ,KAAK,GAAG,CAAC;CAClF,KAAK,MAAM,OAAO,SAChB,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG,KAAK,MAAM,OAAO,SAAS,MAAM,OAAO,QAAQ,KAAK,GAAG,CAAC;CAGlF,MAAM,SAAS,WAAW,QAAQ,KAAK,MAAM,MAAM;CACnD,MAAM,QAAQ,IAAI,IAAI,OAAO,IAAI,OAAO,CAAC;CACzC,MAAM,yBAAS,IAAI,IAAY;CAC/B,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,QAAQ,KAAK,OAAO;EAC7B,IAAI,KAAK,UAAU,KAAK,UAAU,GAAG;EACrC,MAAM,OAAO;GACX,KAAK,KAAK;GACV,MAAM,KAAK;GACX,QAAQ,KAAK,MAAM,KAAK;GACxB,OAAO,KAAK,MAAM,KAAK;EACzB;EACA,MAAM,MAAM,QAAQ,IAAI;EACxB,OAAO,IAAI,GAAG;EACd,IAAI,MAAM,IAAI,GAAG,GAAG;EACpB,MAAM,KAAK,SAAS;EACpB,QAAQ,KAAK,EAAE;EACf,OAAO,IAAI,IAAI;GACb,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK,KAAK,MAAM;GAC3C,SAAS,OAAO,MAAM,KAAK,MAAM,KAAK,KAAK;EAC7C,CAAC;EAED,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,KAAK,QAAQ,KACtC,KAAK,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,OAAO,KAAK;GAC3C,IAAI,MAAM,KAAK,OAAO,MAAM,KAAK,MAAM;GACvC,MAAM,OAAO,MAAM,IAAI,QAAQ,KAAK,KAAK,IAAK,OAAO,EAAG,CAAC;GACzD,IAAI,MAAM,QAAQ,KAAK,OAAO,GAAG,KAAK,MAAM;EAC9C;CAEJ;CACA,MAAM,OAAO,IAAI,IAAI,OAAO;CAC5B,KAAK,MAAM,QAAQ,QAAQ,IAAI,OAAO,IAAI,QAAQ,IAAI,CAAC,GAAG,KAAK,IAAI,KAAK,EAAE;CAC1E,KAAK,MAAM,MAAM,MAAM,KAAK,OAAO,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,GAAG,OAAO,OAAO,EAAE;CAG/E,MAAM,2BAAW,IAAI,IAAoB;CACzC,KAAK,MAAM,QAAQ,KAAK,SAAS,CAAC,GAChC,SAAS,IAAI,QAAQ,IAAK,KAAK,KAAK,MAAO,QAAQ,KAAK,IAAK,GAAG,KAAK,IAAI;CAC3E,IAAI,WAAW;CACf,KAAK,MAAM,QAAQ,KAAK,OAAO;EAC7B,MAAM,MAAM,QAAQ,KAAK,KAAK,KAAK,MAAO,OAAO,KAAK,IAAK;EAC3D,MAAM,OAAO,KAAK;EAClB,IAAI,OAAO,MAAM,IAAI,GAAG;EACxB,IAAI,CAAC,MAAM;GACT,OAAO,IAAI,EAAE,KAAK;GAClB,MAAM,IAAI,KAAK,IAAI;GACnB,aAAa,MAAM,GAAG,MAAM,GAAG,KAAK,QAAQ,IAAI;GAChD;EACF;EACA,MAAM,WAAW,SAAS,IAAI,GAAG;EACjC,IAAI,UACE;OAAA,aAAa,QAAQ,CAAC,SAAS,QAAQ,GAAG,KAAK,OAAO,GAAG,WAAW,MAAM,UAAU,IAAI;EAAA,OACvF,IAAI,KAAK,QAAQ,MAAM;GAE5B,KAAK,OAAO,GAAG,KAAK,MAAM;GAC1B,aAAa,MAAM,GAAG,MAAM,GAAG,KAAK,QAAQ,IAAI;EAClD,OAAO,IAAI,KAAK,QACd,WAAW;CAEf;CACA,OAAO;AACT;;AAGA,SAAgB,WAAW,QAAgB;CACzC,KAAK,MAAM,QAAQ,OAAO,IAAI,OAAO,IAAI,IAAI,GAAG,OAAO,OAAO,IAAI;AACpE;;AAGA,SAAgB,OAAO,OAAe,QAAuD;CAC3F,MAAM,OAAQ,MAAM,KAAK,KAAK,KAAkB,KAAK,KAAK;CAC1D,KAAK,MAAM,QAAQ,KAAK,OACtB,IAAI,SAAS,KAAK,UAAU,SAAS,KAAK,SAAS,KAAK,KAAK,UAAU;EACrE,MAAM,SAAS,KAAK,QAAQ,KAAK,IAAI,CAAE;EACvC,OAAO;GAAE,KAAK,QAAQ,KAAK,KAAK,KAAK,MAAO,MAAM;GAAG,OAAO,SAAS,KAAK,SAAS;EAAE;CACvF;CAEF,OAAO;AACT;;AAGA,SAAgB,UAAU,OAAe,KAAsD;CAC7F,MAAM,OAAQ,MAAM,KAAK,KAAK,KAAkB,KAAK,KAAK;CAC1D,KAAK,MAAM,QAAQ,KAAK,OACtB,IAAI,QAAQ,KAAK,KAAK,KAAK,MAAO,KAAK,QAAQ,KAAK,IAAI,CAAE,EAAE,MAAM,KAChE,OAAO;EAAE,QAAQ,KAAK,SAAS;EAAG,MAAM,KAAK,KAAK,QAAQ;CAAK;CAEnE,OAAO;AACT"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Mark } from "prosemirror-model";
|
|
2
|
+
// src/collaboration/text.ts
|
|
3
|
+
var COMMENT = "comment";
|
|
4
|
+
var COMMENT_PREFIX = "comment:";
|
|
5
|
+
function markAttributes(marks, schema) {
|
|
6
|
+
const attributes = {};
|
|
7
|
+
for (const name of Object.keys(schema.marks)) if (name !== COMMENT) attributes[name] = null;
|
|
8
|
+
for (const mark of marks) if (mark.type.name === COMMENT) attributes[COMMENT_PREFIX + mark.attrs.id] = true;
|
|
9
|
+
else attributes[mark.type.name] = Object.keys(mark.attrs).length ? { ...mark.attrs } : true;
|
|
10
|
+
return attributes;
|
|
11
|
+
}
|
|
12
|
+
/** The comment attributes of the inline content from `from` to `to`. */
|
|
13
|
+
function commentAttributes(node, from, to) {
|
|
14
|
+
const keys = /* @__PURE__ */ new Set();
|
|
15
|
+
node.nodesBetween(from, to, (child) => {
|
|
16
|
+
for (const mark of child.marks) if (mark.type.name === COMMENT) keys.add(COMMENT_PREFIX + mark.attrs.id);
|
|
17
|
+
});
|
|
18
|
+
return keys;
|
|
19
|
+
}
|
|
20
|
+
/** Write `content`'s inline nodes from `from` to `to` into `text` at `at`. */
|
|
21
|
+
function insertInline(text, at, content, from, to) {
|
|
22
|
+
const schema = content.type.schema;
|
|
23
|
+
let offset = at;
|
|
24
|
+
content.forEach((child, pos) => {
|
|
25
|
+
const start = Math.max(pos, from);
|
|
26
|
+
const end = Math.min(pos + child.nodeSize, to);
|
|
27
|
+
if (start >= end) return;
|
|
28
|
+
if (child.isText) {
|
|
29
|
+
const piece = child.text.slice(start - pos, end - pos);
|
|
30
|
+
text.insert(offset, piece, markAttributes(child.marks, schema));
|
|
31
|
+
offset += piece.length;
|
|
32
|
+
} else {
|
|
33
|
+
const embed = { node: child.type.name };
|
|
34
|
+
if (Object.keys(child.attrs).length) embed.attrs = { ...child.attrs };
|
|
35
|
+
text.insertEmbed(offset, embed, markAttributes(child.marks, schema));
|
|
36
|
+
offset += 1;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function marksFrom(attributes, schema) {
|
|
41
|
+
const marks = [];
|
|
42
|
+
for (const [name, value] of Object.entries(attributes ?? {})) {
|
|
43
|
+
if (name.startsWith(COMMENT_PREFIX)) {
|
|
44
|
+
if (value === true && schema.marks[COMMENT]) marks.push(schema.marks[COMMENT].create({ id: name.slice(8) }));
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const type = schema.marks[name];
|
|
48
|
+
if (!type || value === null || value === void 0) continue;
|
|
49
|
+
marks.push(type.create(value === true ? null : value));
|
|
50
|
+
}
|
|
51
|
+
return Mark.setFrom(marks);
|
|
52
|
+
}
|
|
53
|
+
function inlineFrom(text, schema, code) {
|
|
54
|
+
const nodes = [];
|
|
55
|
+
for (const op of text.toDelta()) if (typeof op.insert === "string") {
|
|
56
|
+
if (op.insert) nodes.push(schema.text(op.insert, code ? [] : marksFrom(op.attributes, schema)));
|
|
57
|
+
} else {
|
|
58
|
+
const type = schema.nodes[op.insert.node];
|
|
59
|
+
if (type?.isInline) nodes.push(type.create(op.insert.attrs ?? null, null, marksFrom(op.attributes, schema)));
|
|
60
|
+
}
|
|
61
|
+
return nodes;
|
|
62
|
+
}
|
|
63
|
+
/** Bring `text` from `before`'s inline content to `after`'s, changing only what differs. */
|
|
64
|
+
function updateText(text, before, after) {
|
|
65
|
+
const start = before.content.findDiffStart(after.content);
|
|
66
|
+
if (start === null) return;
|
|
67
|
+
const end = before.content.findDiffEnd(after.content);
|
|
68
|
+
const overlap = start - Math.min(end.a, end.b);
|
|
69
|
+
if (overlap > 0) {
|
|
70
|
+
end.a += overlap;
|
|
71
|
+
end.b += overlap;
|
|
72
|
+
}
|
|
73
|
+
if (end.a - start === end.b - start && before.textBetween(start, end.a, "\n", "") === after.textBetween(start, end.b, "\n", "")) {
|
|
74
|
+
const schema = after.type.schema;
|
|
75
|
+
const comments = commentAttributes(before, start, end.a);
|
|
76
|
+
after.content.forEach((child, pos) => {
|
|
77
|
+
const from = Math.max(pos, start);
|
|
78
|
+
const to = Math.min(pos + child.nodeSize, end.b);
|
|
79
|
+
if (from >= to) return;
|
|
80
|
+
const attributes = markAttributes(child.marks, schema);
|
|
81
|
+
for (const key of comments) if (!(key in attributes)) attributes[key] = null;
|
|
82
|
+
text.format(from, to - from, attributes);
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (end.a > start) text.delete(start, end.a - start);
|
|
87
|
+
if (end.b > start) insertInline(text, start, after, start, end.b);
|
|
88
|
+
}
|
|
89
|
+
export { inlineFrom, insertInline, markAttributes, updateText };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","names":[],"sources":["../../src/collaboration/text.ts"],"sourcesContent":["import { Mark, type Node as PMNode, type Schema } from 'prosemirror-model'\nimport type * as Y from 'yjs'\n\n/* inline content ↔ Y.Text */\n\nexport type Attributes = Record<string, unknown>\n\n/*\n * A text can be in several comment threads at once, each added and\n * removed on its own, also by two people at the same time: every thread\n * is its own attribute, `comment:<thread id>`.\n */\nconst COMMENT = 'comment'\nconst COMMENT_PREFIX = 'comment:'\n\nexport function markAttributes(marks: readonly Mark[], schema: Schema): Attributes {\n // every mark is named, so `insert` doesn't inherit formatting from the left\n // (comment attributes the text doesn't have are cleared by `insert` itself)\n const attributes: Attributes = {}\n for (const name of Object.keys(schema.marks)) if (name !== COMMENT) attributes[name] = null\n for (const mark of marks) {\n if (mark.type.name === COMMENT) attributes[COMMENT_PREFIX + mark.attrs.id] = true\n else attributes[mark.type.name] = Object.keys(mark.attrs).length ? { ...mark.attrs } : true\n }\n return attributes\n}\n\n/** The comment attributes of the inline content from `from` to `to`. */\nfunction commentAttributes(node: PMNode, from: number, to: number): Set<string> {\n const keys = new Set<string>()\n node.nodesBetween(from, to, (child) => {\n for (const mark of child.marks)\n if (mark.type.name === COMMENT) keys.add(COMMENT_PREFIX + mark.attrs.id)\n })\n return keys\n}\n\ninterface Embed {\n node: string\n attrs?: Attributes\n}\n\n/** Write `content`'s inline nodes from `from` to `to` into `text` at `at`. */\nexport function insertInline(text: Y.Text, at: number, content: PMNode, from: number, to: number) {\n const schema = content.type.schema\n let offset = at\n content.forEach((child, pos) => {\n const start = Math.max(pos, from)\n const end = Math.min(pos + child.nodeSize, to)\n if (start >= end) return\n if (child.isText) {\n const piece = child.text!.slice(start - pos, end - pos)\n text.insert(offset, piece, markAttributes(child.marks, schema))\n offset += piece.length\n } else {\n const embed: Embed = { node: child.type.name }\n if (Object.keys(child.attrs).length) embed.attrs = { ...child.attrs }\n text.insertEmbed(offset, embed, markAttributes(child.marks, schema))\n offset += 1\n }\n })\n}\n\nfunction marksFrom(attributes: Attributes | undefined, schema: Schema): Mark[] {\n const marks: Mark[] = []\n for (const [name, value] of Object.entries(attributes ?? {})) {\n if (name.startsWith(COMMENT_PREFIX)) {\n if (value === true && schema.marks[COMMENT])\n marks.push(schema.marks[COMMENT].create({ id: name.slice(COMMENT_PREFIX.length) }))\n continue\n }\n const type = schema.marks[name]\n if (!type || value === null || value === undefined) continue\n marks.push(type.create(value === true ? null : (value as Attributes)))\n }\n return Mark.setFrom(marks) as Mark[]\n}\n\nexport function inlineFrom(text: Y.Text, schema: Schema, code: boolean): PMNode[] {\n const nodes: PMNode[] = []\n for (const op of text.toDelta() as { insert: string | Embed; attributes?: Attributes }[]) {\n if (typeof op.insert === 'string') {\n if (op.insert)\n nodes.push(schema.text(op.insert, code ? [] : marksFrom(op.attributes, schema)))\n } else {\n const type = schema.nodes[op.insert.node]\n if (type?.isInline)\n nodes.push(type.create(op.insert.attrs ?? null, null, marksFrom(op.attributes, schema)))\n }\n }\n return nodes\n}\n\n/** Bring `text` from `before`'s inline content to `after`'s, changing only what differs. */\nexport function updateText(text: Y.Text, before: PMNode, after: PMNode) {\n const start = before.content.findDiffStart(after.content)\n if (start === null) return\n const end = before.content.findDiffEnd(after.content)!\n // the ends can overlap the start when the change repeats nearby text\n const overlap = start - Math.min(end.a, end.b)\n if (overlap > 0) {\n end.a += overlap\n end.b += overlap\n }\n const sameText =\n end.a - start === end.b - start &&\n before.textBetween(start, end.a, '\\n', '') === after.textBetween(start, end.b, '\\n', '')\n if (sameText) {\n // only the formatting changed: keep the characters, so edits on them merge\n const schema = after.type.schema\n const comments = commentAttributes(before, start, end.a)\n after.content.forEach((child, pos) => {\n const from = Math.max(pos, start)\n const to = Math.min(pos + child.nodeSize, end.b)\n if (from >= to) return\n const attributes = markAttributes(child.marks, schema)\n // threads taken off this text\n for (const key of comments) if (!(key in attributes)) attributes[key] = null\n text.format(from, to - from, attributes)\n })\n return\n }\n if (end.a > start) text.delete(start, end.a - start)\n if (end.b > start) insertInline(text, start, after, start, end.b)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import block_handle_vue_vue_type_script_setup_true_lang_default from "./block-handle.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
// src/components/block-handle.vue
|
|
3
|
+
var block_handle_default = block_handle_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
export { block_handle_default as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=block-handle.js.map
|