@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,56 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
// src/core/file-drop.ts
|
|
3
|
+
var fileDropKey = "douFileDrop";
|
|
4
|
+
/**
|
|
5
|
+
* A plugin that hands pasted and dropped files to the targets. However many
|
|
6
|
+
* extensions take files, the first such plugin sorts all of them out, so
|
|
7
|
+
* an image and a PDF pasted together both arrive.
|
|
8
|
+
*/
|
|
9
|
+
function fileDropPlugin(target) {
|
|
10
|
+
const handle = (view, list, pos, event) => {
|
|
11
|
+
const files = Array.from(list ?? []);
|
|
12
|
+
if (!files.length) return false;
|
|
13
|
+
const targets = view.state.plugins.map((plugin) => plugin.spec[fileDropKey]).filter((item) => !!item);
|
|
14
|
+
if (targets[0] !== target) return false;
|
|
15
|
+
const ordered = [...targets.filter((item) => !item.fallback), ...targets.filter((item) => item.fallback)];
|
|
16
|
+
const runs = [];
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
const owner = ordered.find((item) => item.accepts(file));
|
|
19
|
+
if (!owner) continue;
|
|
20
|
+
const last = runs.at(-1);
|
|
21
|
+
if (last?.target === owner) last.files.push(file);
|
|
22
|
+
else runs.push({
|
|
23
|
+
target: owner,
|
|
24
|
+
files: [file]
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!runs.length) return false;
|
|
28
|
+
if (event && runs.length === 1 && runs[0].target.skipPaste?.(event, runs[0].files)) return false;
|
|
29
|
+
let at = pos;
|
|
30
|
+
for (const run of runs) at = run.target.insert(view, run.files, at);
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
return new Plugin({
|
|
34
|
+
key: new PluginKey(),
|
|
35
|
+
[fileDropKey]: target,
|
|
36
|
+
props: {
|
|
37
|
+
handlePaste(view, event) {
|
|
38
|
+
return handle(view, event.clipboardData?.files, view.state.selection.from, event);
|
|
39
|
+
},
|
|
40
|
+
handleDrop(view, event) {
|
|
41
|
+
const dragEvent = event;
|
|
42
|
+
if (!dragEvent.dataTransfer?.files.length) return false;
|
|
43
|
+
const at = view.posAtCoords({
|
|
44
|
+
left: dragEvent.clientX,
|
|
45
|
+
top: dragEvent.clientY
|
|
46
|
+
});
|
|
47
|
+
const handled = handle(view, dragEvent.dataTransfer.files, at?.pos ?? view.state.selection.from);
|
|
48
|
+
if (handled) event.preventDefault();
|
|
49
|
+
return handled;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export { fileDropPlugin };
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=file-drop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-drop.js","names":[],"sources":["../../src/core/file-drop.ts"],"sourcesContent":["import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\n\n/** A block type that takes pasted and dropped files, e.g. images or attachments. */\nexport interface FileDropTarget {\n /** Whether the target takes this file. */\n accepts: (file: File) => boolean\n /** Takes files no other target accepts (attachments): asked last. */\n fallback?: boolean\n /** Insert blocks for the files after the block at `pos`; returns a position in the last one. */\n insert: (view: EditorView, files: File[], pos: number) => number\n /** Leave a paste to the browser, e.g. an image copied from a page comes with its address. */\n skipPaste?: (event: ClipboardEvent, files: File[]) => boolean\n}\n\nconst fileDropKey = 'douFileDrop'\n\n/**\n * A plugin that hands pasted and dropped files to the targets. However many\n * extensions take files, the first such plugin sorts all of them out, so\n * an image and a PDF pasted together both arrive.\n */\nexport function fileDropPlugin(target: FileDropTarget): Plugin {\n const handle = (\n view: EditorView,\n list: FileList | undefined | null,\n pos: number,\n event?: ClipboardEvent,\n ) => {\n const files = Array.from(list ?? [])\n if (!files.length) return false\n const targets = view.state.plugins\n .map(\n (plugin) =>\n (plugin.spec as Record<string, unknown>)[fileDropKey] as FileDropTarget | undefined,\n )\n .filter((item): item is FileDropTarget => !!item)\n // only the first file plugin does the work\n if (targets[0] !== target) return false\n const ordered = [\n ...targets.filter((item) => !item.fallback),\n ...targets.filter((item) => item.fallback),\n ]\n const runs: { target: FileDropTarget; files: File[] }[] = []\n for (const file of files) {\n const owner = ordered.find((item) => item.accepts(file))\n if (!owner) continue\n const last = runs.at(-1)\n if (last?.target === owner) last.files.push(file)\n else runs.push({ target: owner, files: [file] })\n }\n if (!runs.length) return false\n if (event && runs.length === 1 && runs[0]!.target.skipPaste?.(event, runs[0]!.files))\n return false\n // in the order the files came\n let at = pos\n for (const run of runs) at = run.target.insert(view, run.files, at)\n return true\n }\n return new Plugin({\n key: new PluginKey(),\n [fileDropKey]: target,\n props: {\n handlePaste(view: EditorView, event: ClipboardEvent) {\n return handle(view, event.clipboardData?.files, view.state.selection.from, event)\n },\n handleDrop(view: EditorView, event: DragEvent) {\n const dragEvent = event\n if (!dragEvent.dataTransfer?.files.length) return false\n const at = view.posAtCoords({ left: dragEvent.clientX, top: dragEvent.clientY })\n const handled = handle(\n view,\n dragEvent.dataTransfer.files,\n at?.pos ?? view.state.selection.from,\n )\n if (handled) event.preventDefault()\n return handled\n },\n },\n } as never)\n}\n"],"mappings":";;AAeA,IAAM,cAAc;;;;;;AAOpB,SAAgB,eAAe,QAAgC;CAC7D,MAAM,UACJ,MACA,MACA,KACA,UACG;EACH,MAAM,QAAQ,MAAM,KAAK,QAAQ,CAAC,CAAC;EACnC,IAAI,CAAC,MAAM,QAAQ,OAAO;EAC1B,MAAM,UAAU,KAAK,MAAM,QACxB,KACE,WACE,OAAO,KAAiC,YAC7C,CAAC,CACA,QAAQ,SAAiC,CAAC,CAAC,IAAI;EAElD,IAAI,QAAQ,OAAO,QAAQ,OAAO;EAClC,MAAM,UAAU,CACd,GAAG,QAAQ,QAAQ,SAAS,CAAC,KAAK,QAAQ,GAC1C,GAAG,QAAQ,QAAQ,SAAS,KAAK,QAAQ,CAC3C;EACA,MAAM,OAAoD,CAAC;EAC3D,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,QAAQ,QAAQ,MAAM,SAAS,KAAK,QAAQ,IAAI,CAAC;GACvD,IAAI,CAAC,OAAO;GACZ,MAAM,OAAO,KAAK,GAAG,EAAE;GACvB,IAAI,MAAM,WAAW,OAAO,KAAK,MAAM,KAAK,IAAI;QAC3C,KAAK,KAAK;IAAE,QAAQ;IAAO,OAAO,CAAC,IAAI;GAAE,CAAC;EACjD;EACA,IAAI,CAAC,KAAK,QAAQ,OAAO;EACzB,IAAI,SAAS,KAAK,WAAW,KAAK,KAAK,EAAE,CAAE,OAAO,YAAY,OAAO,KAAK,EAAE,CAAE,KAAK,GACjF,OAAO;EAET,IAAI,KAAK;EACT,KAAK,MAAM,OAAO,MAAM,KAAK,IAAI,OAAO,OAAO,MAAM,IAAI,OAAO,EAAE;EAClE,OAAO;CACT;CACA,OAAO,IAAI,OAAO;EAChB,KAAK,IAAI,UAAU;GAClB,cAAc;EACf,OAAO;GACL,YAAY,MAAkB,OAAuB;IACnD,OAAO,OAAO,MAAM,MAAM,eAAe,OAAO,KAAK,MAAM,UAAU,MAAM,KAAK;GAClF;GACA,WAAW,MAAkB,OAAkB;IAC7C,MAAM,YAAY;IAClB,IAAI,CAAC,UAAU,cAAc,MAAM,QAAQ,OAAO;IAClD,MAAM,KAAK,KAAK,YAAY;KAAE,MAAM,UAAU;KAAS,KAAK,UAAU;IAAQ,CAAC;IAC/E,MAAM,UAAU,OACd,MACA,UAAU,aAAa,OACvB,IAAI,OAAO,KAAK,MAAM,UAAU,IAClC;IACA,IAAI,SAAS,MAAM,eAAe;IAClC,OAAO;GACT;EACF;CACF,CAAU;AACZ"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
3
|
+
// src/core/highlight.ts
|
|
4
|
+
var highlightKey = new PluginKey("dou-editor-highlight");
|
|
5
|
+
/** Remembered results, by language and code: undo and redo find them again. */
|
|
6
|
+
var CACHE_SIZE = 200;
|
|
7
|
+
/** Read the tokens from highlighted HTML: the text of each `<span>` with its style and class. */
|
|
8
|
+
function readTokens(html, code) {
|
|
9
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
10
|
+
const root = doc.querySelector("code") ?? doc.body;
|
|
11
|
+
const tokens = [];
|
|
12
|
+
let offset = 0;
|
|
13
|
+
const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
14
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
15
|
+
const text = node.textContent ?? "";
|
|
16
|
+
const span = node.parentElement;
|
|
17
|
+
const style = span?.getAttribute("style") ?? "";
|
|
18
|
+
const className = span && !span.classList.contains("line") ? span.className : "";
|
|
19
|
+
if (span && span !== root && (style || className) && text) tokens.push({
|
|
20
|
+
from: offset,
|
|
21
|
+
to: offset + text.length,
|
|
22
|
+
style,
|
|
23
|
+
className
|
|
24
|
+
});
|
|
25
|
+
offset += text.length;
|
|
26
|
+
}
|
|
27
|
+
const text = root.textContent ?? "";
|
|
28
|
+
if (text !== code && text !== `${code}\n`) return null;
|
|
29
|
+
return tokens;
|
|
30
|
+
}
|
|
31
|
+
var keyOf = (lang, code) => `${lang}\n${code}`;
|
|
32
|
+
var isCode = (node) => !!node.type.spec.code;
|
|
33
|
+
function decorate(doc, cache, previous) {
|
|
34
|
+
const decorations = [];
|
|
35
|
+
doc.descendants((node, pos) => {
|
|
36
|
+
if (!node.isTextblock) return true;
|
|
37
|
+
if (!isCode(node)) return false;
|
|
38
|
+
const start = pos + 1;
|
|
39
|
+
const tokens = cache.get(keyOf(node.attrs.language || "text", node.textContent));
|
|
40
|
+
if (tokens) for (const token of tokens) decorations.push(Decoration.inline(start + token.from, start + token.to, {
|
|
41
|
+
class: `dou-editor__token ${token.className}`.trim(),
|
|
42
|
+
style: token.style
|
|
43
|
+
}));
|
|
44
|
+
else decorations.push(...previous.find(start, start + node.content.size));
|
|
45
|
+
return false;
|
|
46
|
+
});
|
|
47
|
+
return DecorationSet.create(doc, decorations);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Highlights code blocks with a highlighter that returns HTML. It runs
|
|
51
|
+
* after each change (asynchronous highlighters are fine) and the colors
|
|
52
|
+
* are laid over the text as decorations, so the document keeps plain text.
|
|
53
|
+
*/
|
|
54
|
+
function highlightPlugin(highlight) {
|
|
55
|
+
const cache = /* @__PURE__ */ new Map();
|
|
56
|
+
const pending = /* @__PURE__ */ new Set();
|
|
57
|
+
const highlighterOf = (state) => highlight ?? highlightKey.getState(state)?.fallback ?? null;
|
|
58
|
+
function remember(key, tokens) {
|
|
59
|
+
cache.delete(key);
|
|
60
|
+
cache.set(key, tokens);
|
|
61
|
+
if (cache.size > CACHE_SIZE) cache.delete(cache.keys().next().value);
|
|
62
|
+
}
|
|
63
|
+
/** Start highlighting the code blocks that have no result yet. */
|
|
64
|
+
function run(view) {
|
|
65
|
+
const highlighter = highlighterOf(view.state);
|
|
66
|
+
if (!highlighter) return;
|
|
67
|
+
view.state.doc.descendants((node) => {
|
|
68
|
+
if (!node.isTextblock) return true;
|
|
69
|
+
if (!isCode(node)) return false;
|
|
70
|
+
const lang = node.attrs.language || "text";
|
|
71
|
+
const code = node.textContent;
|
|
72
|
+
const key = keyOf(lang, code);
|
|
73
|
+
if (cache.has(key) || pending.has(key)) return false;
|
|
74
|
+
pending.add(key);
|
|
75
|
+
Promise.resolve().then(() => highlighter(code, lang)).then((html) => remember(key, readTokens(html, code) ?? []), () => remember(key, [])).then(() => {
|
|
76
|
+
pending.delete(key);
|
|
77
|
+
if (view.isDestroyed) return;
|
|
78
|
+
const tr = view.state.tr.setMeta(highlightKey, { refresh: true });
|
|
79
|
+
view.dispatch(tr.setMeta("addToHistory", false));
|
|
80
|
+
});
|
|
81
|
+
return false;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return new Plugin({
|
|
85
|
+
key: highlightKey,
|
|
86
|
+
state: {
|
|
87
|
+
init: (_config, state) => ({
|
|
88
|
+
decorations: decorate(state.doc, cache, DecorationSet.empty),
|
|
89
|
+
fallback: null
|
|
90
|
+
}),
|
|
91
|
+
apply(tr, value, _old, state) {
|
|
92
|
+
const meta = tr.getMeta(highlightKey);
|
|
93
|
+
const fallback = meta && "fallback" in meta ? meta.fallback ?? null : value.fallback;
|
|
94
|
+
if (!tr.docChanged && !meta?.refresh && fallback === value.fallback) return value;
|
|
95
|
+
const previous = value.decorations.map(tr.mapping, tr.doc);
|
|
96
|
+
return {
|
|
97
|
+
decorations: decorate(state.doc, cache, previous),
|
|
98
|
+
fallback
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
view(view) {
|
|
103
|
+
run(view);
|
|
104
|
+
return { update(current, before) {
|
|
105
|
+
if (current.state.doc !== before.doc || highlighterOf(current.state) !== highlighterOf(before)) run(current);
|
|
106
|
+
} };
|
|
107
|
+
},
|
|
108
|
+
props: { decorations: (state) => highlightKey.getState(state)?.decorations }
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/** Set the highlighter used when the code block was not given one. */
|
|
112
|
+
function setFallbackHighlighter(view, highlight) {
|
|
113
|
+
if (!highlightKey.get(view.state)) return;
|
|
114
|
+
if (highlightKey.getState(view.state)?.fallback === highlight) return;
|
|
115
|
+
const tr = view.state.tr.setMeta(highlightKey, { fallback: highlight });
|
|
116
|
+
view.dispatch(tr.setMeta("addToHistory", false));
|
|
117
|
+
}
|
|
118
|
+
export { highlightKey, highlightPlugin, readTokens, setFallbackHighlighter };
|
|
119
|
+
|
|
120
|
+
//# sourceMappingURL=highlight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"highlight.js","names":[],"sources":["../../src/core/highlight.ts"],"sourcesContent":["import type { Node as PMNode } from 'prosemirror-model'\nimport { Plugin, PluginKey, type EditorState, type Transaction } from 'prosemirror-state'\nimport { Decoration, DecorationSet, type EditorView } from 'prosemirror-view'\n\n/**\n * Turns code into highlighted HTML, the same function `DouCodeBlock` takes:\n * `(code, lang) => codeToHtml(code, { lang, themes, defaultColor: false })`\n * with shiki works as is.\n */\nexport type CodeHighlighter = (code: string, lang: string) => string | Promise<string>\n\n/** A highlighted stretch of a code block's text. */\ninterface Token {\n from: number\n to: number\n style: string\n className: string\n}\n\ninterface HighlightState {\n decorations: DecorationSet\n /** The highlighter the editor falls back on, e.g. one set up in ConfigProvider. */\n fallback: CodeHighlighter | null\n}\n\ninterface HighlightMeta {\n fallback?: CodeHighlighter | null\n /** Highlighting finished somewhere: rebuild the decorations. */\n refresh?: true\n}\n\nexport const highlightKey = new PluginKey<HighlightState>('dou-editor-highlight')\n\n/** Remembered results, by language and code: undo and redo find them again. */\nconst CACHE_SIZE = 200\n\n/** Read the tokens from highlighted HTML: the text of each `<span>` with its style and class. */\nexport function readTokens(html: string, code: string): Token[] | null {\n const doc = new DOMParser().parseFromString(html, 'text/html')\n const root = doc.querySelector('code') ?? doc.body\n const tokens: Token[] = []\n let offset = 0\n const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT)\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n const text = node.textContent ?? ''\n const span = node.parentElement\n const style = span?.getAttribute('style') ?? ''\n const className = span && !span.classList.contains('line') ? span.className : ''\n if (span && span !== root && (style || className) && text)\n tokens.push({ from: offset, to: offset + text.length, style, className })\n offset += text.length\n }\n // a highlighter may end with a newline; anything else that does not\n // match the code would put the colors in the wrong places\n const text = root.textContent ?? ''\n if (text !== code && text !== `${code}\\n`) return null\n return tokens\n}\n\nconst keyOf = (lang: string, code: string) => `${lang}\\n${code}`\nconst isCode = (node: PMNode) => !!node.type.spec.code\n\nfunction decorate(\n doc: PMNode,\n cache: Map<string, Token[]>,\n previous: DecorationSet,\n): DecorationSet {\n const decorations: Decoration[] = []\n doc.descendants((node, pos) => {\n if (!node.isTextblock) return true\n if (!isCode(node)) return false\n const start = pos + 1\n const tokens = cache.get(keyOf(node.attrs.language || 'text', node.textContent))\n if (tokens) {\n for (const token of tokens)\n decorations.push(\n Decoration.inline(start + token.from, start + token.to, {\n class: `dou-editor__token ${token.className}`.trim(),\n style: token.style,\n }),\n )\n } else {\n // keep the old colors (moved along with the edit) until the new ones arrive\n decorations.push(...previous.find(start, start + node.content.size))\n }\n return false\n })\n return DecorationSet.create(doc, decorations)\n}\n\n/**\n * Highlights code blocks with a highlighter that returns HTML. It runs\n * after each change (asynchronous highlighters are fine) and the colors\n * are laid over the text as decorations, so the document keeps plain text.\n */\nexport function highlightPlugin(highlight?: CodeHighlighter): Plugin<HighlightState> {\n const cache = new Map<string, Token[]>()\n const pending = new Set<string>()\n const highlighterOf = (state: EditorState) =>\n highlight ?? highlightKey.getState(state)?.fallback ?? null\n\n function remember(key: string, tokens: Token[]) {\n cache.delete(key)\n cache.set(key, tokens)\n if (cache.size > CACHE_SIZE) cache.delete(cache.keys().next().value!)\n }\n\n /** Start highlighting the code blocks that have no result yet. */\n function run(view: EditorView) {\n const highlighter = highlighterOf(view.state)\n if (!highlighter) return\n view.state.doc.descendants((node) => {\n if (!node.isTextblock) return true\n if (!isCode(node)) return false\n const lang = node.attrs.language || 'text'\n const code = node.textContent\n const key = keyOf(lang, code)\n if (cache.has(key) || pending.has(key)) return false\n pending.add(key)\n Promise.resolve()\n .then(() => highlighter(code, lang))\n .then(\n (html) => remember(key, readTokens(html, code) ?? []),\n // a language the highlighter does not know stays plain\n () => remember(key, []),\n )\n .then(() => {\n pending.delete(key)\n if (view.isDestroyed) return\n const tr = view.state.tr.setMeta(highlightKey, { refresh: true } satisfies HighlightMeta)\n view.dispatch(tr.setMeta('addToHistory', false))\n })\n return false\n })\n }\n\n return new Plugin<HighlightState>({\n key: highlightKey,\n state: {\n init: (_config, state) => ({\n decorations: decorate(state.doc, cache, DecorationSet.empty),\n fallback: null,\n }),\n apply(tr: Transaction, value, _old, state) {\n const meta = tr.getMeta(highlightKey) as HighlightMeta | undefined\n const fallback = meta && 'fallback' in meta ? (meta.fallback ?? null) : value.fallback\n if (!tr.docChanged && !meta?.refresh && fallback === value.fallback) return value\n const previous = value.decorations.map(tr.mapping, tr.doc)\n return { decorations: decorate(state.doc, cache, previous), fallback }\n },\n },\n view(view) {\n run(view)\n return {\n update(current, before) {\n if (\n current.state.doc !== before.doc ||\n highlighterOf(current.state) !== highlighterOf(before)\n )\n run(current)\n },\n }\n },\n props: {\n decorations: (state) => highlightKey.getState(state)?.decorations,\n },\n })\n}\n\n/** Set the highlighter used when the code block was not given one. */\nexport function setFallbackHighlighter(view: EditorView, highlight: CodeHighlighter | null) {\n if (!highlightKey.get(view.state)) return\n if (highlightKey.getState(view.state)?.fallback === highlight) return\n const tr = view.state.tr.setMeta(highlightKey, { fallback: highlight } satisfies HighlightMeta)\n view.dispatch(tr.setMeta('addToHistory', false))\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { blockAround } from "./blocks.js";
|
|
2
|
+
import { TextSelection } from "prosemirror-state";
|
|
3
|
+
import { InputRule, inputRules } from "prosemirror-inputrules";
|
|
4
|
+
// src/core/input-rules.ts
|
|
5
|
+
/**
|
|
6
|
+
* A Markdown-style shortcut typed at the start of a block, e.g. `# ` for a
|
|
7
|
+
* heading. It only applies to paragraphs, so typing `# ` inside a heading
|
|
8
|
+
* or code stays literal. Input rules never run during IME composition.
|
|
9
|
+
*/
|
|
10
|
+
function blockInputRule(pattern, type, attrs) {
|
|
11
|
+
return new InputRule(pattern, (state, match, start, end) => {
|
|
12
|
+
const nodeType = state.schema.nodes[type];
|
|
13
|
+
const $start = state.doc.resolve(start);
|
|
14
|
+
if (!nodeType || $start.parent.type.name !== "paragraph") return null;
|
|
15
|
+
const tr = state.tr.delete(start, end);
|
|
16
|
+
tr.setBlockType(start, start, nodeType, attrs?.(match) ?? null);
|
|
17
|
+
return tr;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A shortcut that fills an empty paragraph with a block without text (e.g.
|
|
22
|
+
* `---` for a divider) and opens a paragraph below. The pattern matches the
|
|
23
|
+
* whole paragraph, the character just typed included.
|
|
24
|
+
*/
|
|
25
|
+
function atomInputRule(pattern, type) {
|
|
26
|
+
return new InputRule(pattern, (state, match, start) => {
|
|
27
|
+
const nodeType = state.schema.nodes[type];
|
|
28
|
+
const $start = state.doc.resolve(start);
|
|
29
|
+
if (!nodeType || $start.parent.type.name !== "paragraph") return null;
|
|
30
|
+
if ($start.parent.content.size !== match[0].length - 1) return null;
|
|
31
|
+
const info = blockAround($start);
|
|
32
|
+
if (!info || info.group) return null;
|
|
33
|
+
const tr = state.tr.replaceWith(info.contentPos, info.contentPos + info.content.nodeSize, nodeType.create());
|
|
34
|
+
const at = info.pos + tr.doc.nodeAt(info.pos).nodeSize;
|
|
35
|
+
tr.insert(at, state.schema.nodes.block.create({ id: null }, state.schema.nodes.paragraph.create()));
|
|
36
|
+
tr.setSelection(TextSelection.create(tr.doc, at + 2));
|
|
37
|
+
return tr;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Formatting typed inline, e.g. `**bold**`. The pattern's first group is
|
|
42
|
+
* the marked text with its markers, the second the text alone. The
|
|
43
|
+
* character just typed (the last marker) is not in the document yet.
|
|
44
|
+
*/
|
|
45
|
+
function markInputRule(pattern, mark) {
|
|
46
|
+
return new InputRule(pattern, (state, match, start, end) => {
|
|
47
|
+
const type = state.schema.marks[mark];
|
|
48
|
+
const [all, marked, text] = match;
|
|
49
|
+
if (!type || !all || !marked || !text) return null;
|
|
50
|
+
if (state.doc.resolve(start).parent.type.spec.code) return null;
|
|
51
|
+
const from = start + all.length - marked.length;
|
|
52
|
+
const marker = (marked.length - text.length) / 2;
|
|
53
|
+
return state.tr.delete(from + marker + text.length, end).delete(from, from + marker).addMark(from, from + text.length, type.create()).removeStoredMark(type);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function editorInputRules(rules) {
|
|
57
|
+
return inputRules({ rules });
|
|
58
|
+
}
|
|
59
|
+
export { atomInputRule, blockInputRule, editorInputRules, markInputRule };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=input-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-rules.js","names":[],"sources":["../../src/core/input-rules.ts"],"sourcesContent":["import { InputRule, inputRules } from 'prosemirror-inputrules'\nimport { TextSelection, type Plugin } from 'prosemirror-state'\nimport { blockAround } from './blocks'\n\n/**\n * A Markdown-style shortcut typed at the start of a block, e.g. `# ` for a\n * heading. It only applies to paragraphs, so typing `# ` inside a heading\n * or code stays literal. Input rules never run during IME composition.\n */\nexport function blockInputRule(\n pattern: RegExp,\n type: string,\n attrs?: (match: RegExpMatchArray) => Record<string, unknown>,\n) {\n return new InputRule(pattern, (state, match, start, end) => {\n const nodeType = state.schema.nodes[type]\n const $start = state.doc.resolve(start)\n if (!nodeType || $start.parent.type.name !== 'paragraph') return null\n const tr = state.tr.delete(start, end)\n tr.setBlockType(start, start, nodeType, attrs?.(match) ?? null)\n return tr\n })\n}\n\n/**\n * A shortcut that fills an empty paragraph with a block without text (e.g.\n * `---` for a divider) and opens a paragraph below. The pattern matches the\n * whole paragraph, the character just typed included.\n */\nexport function atomInputRule(pattern: RegExp, type: string) {\n return new InputRule(pattern, (state, match, start) => {\n const nodeType = state.schema.nodes[type]\n const $start = state.doc.resolve(start)\n // the last character is not in the document yet\n if (!nodeType || $start.parent.type.name !== 'paragraph') return null\n if ($start.parent.content.size !== match[0]!.length - 1) return null\n const info = blockAround($start)\n if (!info || info.group) return null\n const tr = state.tr.replaceWith(\n info.contentPos,\n info.contentPos + info.content.nodeSize,\n nodeType.create(),\n )\n const at = info.pos + tr.doc.nodeAt(info.pos)!.nodeSize\n tr.insert(\n at,\n state.schema.nodes.block!.create({ id: null }, state.schema.nodes.paragraph!.create()),\n )\n tr.setSelection(TextSelection.create(tr.doc, at + 2))\n return tr\n })\n}\n\n/**\n * Formatting typed inline, e.g. `**bold**`. The pattern's first group is\n * the marked text with its markers, the second the text alone. The\n * character just typed (the last marker) is not in the document yet.\n */\nexport function markInputRule(pattern: RegExp, mark: string) {\n return new InputRule(pattern, (state, match, start, end) => {\n const type = state.schema.marks[mark]\n const [all, marked, text] = match\n if (!type || !all || !marked || !text) return null\n if (state.doc.resolve(start).parent.type.spec.code) return null\n const from = start + all.length - marked.length\n const marker = (marked.length - text.length) / 2\n const tr = state.tr\n .delete(from + marker + text.length, end)\n .delete(from, from + marker)\n .addMark(from, from + text.length, type.create())\n .removeStoredMark(type)\n return tr\n })\n}\n\nexport function editorInputRules(rules: InputRule[]): Plugin {\n return inputRules({ rules })\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,eACd,SACA,MACA,OACA;CACA,OAAO,IAAI,UAAU,UAAU,OAAO,OAAO,OAAO,QAAQ;EAC1D,MAAM,WAAW,MAAM,OAAO,MAAM;EACpC,MAAM,SAAS,MAAM,IAAI,QAAQ,KAAK;EACtC,IAAI,CAAC,YAAY,OAAO,OAAO,KAAK,SAAS,aAAa,OAAO;EACjE,MAAM,KAAK,MAAM,GAAG,OAAO,OAAO,GAAG;EACrC,GAAG,aAAa,OAAO,OAAO,UAAU,QAAQ,KAAK,KAAK,IAAI;EAC9D,OAAO;CACT,CAAC;AACH;;;;;;AAOA,SAAgB,cAAc,SAAiB,MAAc;CAC3D,OAAO,IAAI,UAAU,UAAU,OAAO,OAAO,UAAU;EACrD,MAAM,WAAW,MAAM,OAAO,MAAM;EACpC,MAAM,SAAS,MAAM,IAAI,QAAQ,KAAK;EAEtC,IAAI,CAAC,YAAY,OAAO,OAAO,KAAK,SAAS,aAAa,OAAO;EACjE,IAAI,OAAO,OAAO,QAAQ,SAAS,MAAM,EAAE,CAAE,SAAS,GAAG,OAAO;EAChE,MAAM,OAAO,YAAY,MAAM;EAC/B,IAAI,CAAC,QAAQ,KAAK,OAAO,OAAO;EAChC,MAAM,KAAK,MAAM,GAAG,YAClB,KAAK,YACL,KAAK,aAAa,KAAK,QAAQ,UAC/B,SAAS,OAAO,CAClB;EACA,MAAM,KAAK,KAAK,MAAM,GAAG,IAAI,OAAO,KAAK,GAAG,CAAC,CAAE;EAC/C,GAAG,OACD,IACA,MAAM,OAAO,MAAM,MAAO,OAAO,EAAE,IAAI,KAAK,GAAG,MAAM,OAAO,MAAM,UAAW,OAAO,CAAC,CACvF;EACA,GAAG,aAAa,cAAc,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC;EACpD,OAAO;CACT,CAAC;AACH;;;;;;AAOA,SAAgB,cAAc,SAAiB,MAAc;CAC3D,OAAO,IAAI,UAAU,UAAU,OAAO,OAAO,OAAO,QAAQ;EAC1D,MAAM,OAAO,MAAM,OAAO,MAAM;EAChC,MAAM,CAAC,KAAK,QAAQ,QAAQ;EAC5B,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,OAAO;EAC9C,IAAI,MAAM,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,KAAK,KAAK,MAAM,OAAO;EAC3D,MAAM,OAAO,QAAQ,IAAI,SAAS,OAAO;EACzC,MAAM,UAAU,OAAO,SAAS,KAAK,UAAU;EAM/C,OALW,MAAM,GACd,OAAO,OAAO,SAAS,KAAK,QAAQ,GAAG,CAAC,CACxC,OAAO,MAAM,OAAO,MAAM,CAAC,CAC3B,QAAQ,MAAM,OAAO,KAAK,QAAQ,KAAK,OAAO,CAAC,CAAC,CAChD,iBAAiB,IACb;CACT,CAAC;AACH;AAEA,SAAgB,iBAAiB,OAA4B;CAC3D,OAAO,WAAW,EAAE,MAAM,CAAC;AAC7B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { deleteSelectedAtom, duplicateBlocks, exitCode, insertBreak, joinBackward, joinForward, liftBlocks, newlineInCode, setBlockType, sinkBlocks, splitBlock, swapBlocks } from "./commands.js";
|
|
2
|
+
import { selectCurrentBlocks } from "./block-selection.js";
|
|
3
|
+
import { keymap } from "prosemirror-keymap";
|
|
4
|
+
import { chainCommands, deleteSelection, selectAll } from "prosemirror-commands";
|
|
5
|
+
import { redo, undo } from "prosemirror-history";
|
|
6
|
+
// src/core/keymap.ts
|
|
7
|
+
/** Two spaces in code; elsewhere `Tab` indents the block. */
|
|
8
|
+
var indentCode = (state, dispatch) => {
|
|
9
|
+
if (!state.selection.$from.parent.type.spec.code) return false;
|
|
10
|
+
if (dispatch) dispatch(state.tr.insertText(" "));
|
|
11
|
+
return true;
|
|
12
|
+
};
|
|
13
|
+
/** The keys every editor has; marks and block types bring their own. */
|
|
14
|
+
function editorKeymap(options = {}) {
|
|
15
|
+
const undoCommand = options.undo ?? undo;
|
|
16
|
+
const redoCommand = options.redo ?? redo;
|
|
17
|
+
return keymap({
|
|
18
|
+
Enter: chainCommands(newlineInCode, splitBlock),
|
|
19
|
+
"Shift-Enter": insertBreak,
|
|
20
|
+
"Mod-Enter": exitCode,
|
|
21
|
+
Backspace: chainCommands(deleteSelectedAtom, deleteSelection, joinBackward),
|
|
22
|
+
Delete: chainCommands(deleteSelectedAtom, deleteSelection, joinForward),
|
|
23
|
+
Tab: chainCommands(indentCode, sinkBlocks),
|
|
24
|
+
"Shift-Tab": liftBlocks,
|
|
25
|
+
Escape: selectCurrentBlocks,
|
|
26
|
+
"Mod-a": selectAll,
|
|
27
|
+
"Mod-z": undoCommand,
|
|
28
|
+
"Mod-Shift-z": redoCommand,
|
|
29
|
+
"Mod-y": redoCommand,
|
|
30
|
+
"Mod-Alt-0": setBlockType("paragraph"),
|
|
31
|
+
"Mod-d": duplicateBlocks,
|
|
32
|
+
"Mod-Shift-ArrowUp": swapBlocks(-1),
|
|
33
|
+
"Mod-Shift-ArrowDown": swapBlocks(1)
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export { editorKeymap };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=keymap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keymap.js","names":[],"sources":["../../src/core/keymap.ts"],"sourcesContent":["import { chainCommands, deleteSelection, selectAll } from 'prosemirror-commands'\nimport { redo, undo } from 'prosemirror-history'\nimport { keymap } from 'prosemirror-keymap'\nimport type { Command, Plugin } from 'prosemirror-state'\nimport { selectCurrentBlocks } from './block-selection'\nimport {\n deleteSelectedAtom,\n duplicateBlocks,\n exitCode,\n insertBreak,\n joinBackward,\n joinForward,\n liftBlocks,\n swapBlocks,\n newlineInCode,\n setBlockType,\n sinkBlocks,\n splitBlock,\n} from './commands'\n\nexport interface KeymapOptions {\n /** Undo and redo; the collaboration extension swaps in its own. */\n undo?: Command\n redo?: Command\n}\n\n/** Two spaces in code; elsewhere `Tab` indents the block. */\nconst indentCode: Command = (state, dispatch) => {\n if (!state.selection.$from.parent.type.spec.code) return false\n if (dispatch) dispatch(state.tr.insertText(' '))\n return true\n}\n\n/** The keys every editor has; marks and block types bring their own. */\nexport function editorKeymap(options: KeymapOptions = {}): Plugin {\n const undoCommand = options.undo ?? undo\n const redoCommand = options.redo ?? redo\n return keymap({\n Enter: chainCommands(newlineInCode, splitBlock),\n 'Shift-Enter': insertBreak,\n 'Mod-Enter': exitCode,\n Backspace: chainCommands(deleteSelectedAtom, deleteSelection, joinBackward),\n Delete: chainCommands(deleteSelectedAtom, deleteSelection, joinForward),\n // when nothing can be indented, Tab moves focus on as usual\n Tab: chainCommands(indentCode, sinkBlocks),\n 'Shift-Tab': liftBlocks,\n Escape: selectCurrentBlocks,\n 'Mod-a': selectAll,\n\n 'Mod-z': undoCommand,\n 'Mod-Shift-z': redoCommand,\n 'Mod-y': redoCommand,\n\n 'Mod-Alt-0': setBlockType('paragraph'),\n\n 'Mod-d': duplicateBlocks,\n 'Mod-Shift-ArrowUp': swapBlocks(-1),\n 'Mod-Shift-ArrowDown': swapBlocks(1),\n })\n}\n"],"mappings":";;;;;;;AA2BA,IAAM,cAAuB,OAAO,aAAa;CAC/C,IAAI,CAAC,MAAM,UAAU,MAAM,OAAO,KAAK,KAAK,MAAM,OAAO;CACzD,IAAI,UAAU,SAAS,MAAM,GAAG,WAAW,IAAI,CAAC;CAChD,OAAO;AACT;;AAGA,SAAgB,aAAa,UAAyB,CAAC,GAAW;CAChE,MAAM,cAAc,QAAQ,QAAQ;CACpC,MAAM,cAAc,QAAQ,QAAQ;CACpC,OAAO,OAAO;EACZ,OAAO,cAAc,eAAe,UAAU;EAC9C,eAAe;EACf,aAAa;EACb,WAAW,cAAc,oBAAoB,iBAAiB,YAAY;EAC1E,QAAQ,cAAc,oBAAoB,iBAAiB,WAAW;EAEtE,KAAK,cAAc,YAAY,UAAU;EACzC,aAAa;EACb,QAAQ;EACR,SAAS;EAET,SAAS;EACT,eAAe;EACf,SAAS;EAET,aAAa,aAAa,WAAW;EAErC,SAAS;EACT,qBAAqB,WAAW,EAAE;EAClC,uBAAuB,WAAW,CAAC;CACrC,CAAC;AACH"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { isColor } from "./colors.js";
|
|
2
|
+
import { Mark } from "prosemirror-model";
|
|
3
|
+
// src/core/model.ts
|
|
4
|
+
var MARK_ORDER = [
|
|
5
|
+
"bold",
|
|
6
|
+
"italic",
|
|
7
|
+
"underline",
|
|
8
|
+
"strike",
|
|
9
|
+
"code"
|
|
10
|
+
];
|
|
11
|
+
/** Built-in marks in a fixed order, then the others by name. */
|
|
12
|
+
var markRank = (name) => {
|
|
13
|
+
const index = MARK_ORDER.indexOf(name);
|
|
14
|
+
return index < 0 ? MARK_ORDER.length : index;
|
|
15
|
+
};
|
|
16
|
+
/** A random id; UUIDs keep blocks created at the same time on two devices apart. */
|
|
17
|
+
function createId() {
|
|
18
|
+
const crypto = globalThis.crypto;
|
|
19
|
+
if (crypto?.randomUUID) return crypto.randomUUID();
|
|
20
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
21
|
+
if (crypto?.getRandomValues) crypto.getRandomValues(bytes);
|
|
22
|
+
else for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
23
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
24
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
25
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
26
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
27
|
+
}
|
|
28
|
+
function inlineOf(node) {
|
|
29
|
+
const result = [];
|
|
30
|
+
node.forEach((child) => {
|
|
31
|
+
if (child.type.name === "hardBreak") {
|
|
32
|
+
result.push({ type: "break" });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!child.isText) {
|
|
36
|
+
result.push({
|
|
37
|
+
type: "inline",
|
|
38
|
+
name: child.type.name,
|
|
39
|
+
props: { ...child.attrs }
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const marks = child.marks.filter((mark) => !mark.type.spec.field).map((mark) => mark.type.name).filter((name) => name !== "link" && name !== "comment").sort((a, b) => markRank(a) - markRank(b) || a.localeCompare(b));
|
|
44
|
+
const link = child.marks.find((mark) => mark.type.name === "link")?.attrs.href;
|
|
45
|
+
const item = {
|
|
46
|
+
type: "text",
|
|
47
|
+
text: child.text ?? ""
|
|
48
|
+
};
|
|
49
|
+
if (marks.length) item.marks = marks;
|
|
50
|
+
if (link) item.link = link;
|
|
51
|
+
for (const mark of child.marks) {
|
|
52
|
+
const field = mark.type.spec.field;
|
|
53
|
+
if (field) item[field] = mark.attrs.value;
|
|
54
|
+
}
|
|
55
|
+
const comments = child.marks.filter((mark) => mark.type.name === "comment").map((mark) => mark.attrs.id);
|
|
56
|
+
if (comments.length) item.comments = comments;
|
|
57
|
+
const previous = result.at(-1);
|
|
58
|
+
if (previous?.type === "text" && previous.link === item.link && previous.color === item.color && previous.highlight === item.highlight && String(previous.marks ?? "") === String(item.marks ?? "") && String(previous.comments ?? "") === String(item.comments ?? "")) previous.text += item.text;
|
|
59
|
+
else result.push(item);
|
|
60
|
+
});
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
/** A content node's attributes; those its spec lists as `optionalProps` only when set. */
|
|
64
|
+
function propsOf(content) {
|
|
65
|
+
const props = { ...content.attrs };
|
|
66
|
+
for (const name of content.type.spec.optionalProps ?? []) if (props[name] === null || props[name] === void 0) delete props[name];
|
|
67
|
+
return props;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Blocks already read, by node. Nodes never change, so a block that wasn't
|
|
71
|
+
* edited is the same object as last time: reading a large document after
|
|
72
|
+
* typing one key only builds the blocks on the path to that key.
|
|
73
|
+
*/
|
|
74
|
+
var blockCache = /* @__PURE__ */ new WeakMap();
|
|
75
|
+
var freeze = (value) => {
|
|
76
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
77
|
+
Object.freeze(value);
|
|
78
|
+
for (const item of Object.values(value)) freeze(item);
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
};
|
|
82
|
+
/** A block of the document; shared between reads, so frozen. */
|
|
83
|
+
function blockFromNode(node) {
|
|
84
|
+
let block = blockCache.get(node);
|
|
85
|
+
if (!block) {
|
|
86
|
+
block = readBlock(node);
|
|
87
|
+
freeze(block);
|
|
88
|
+
blockCache.set(node, block);
|
|
89
|
+
}
|
|
90
|
+
return block;
|
|
91
|
+
}
|
|
92
|
+
function readBlock(node) {
|
|
93
|
+
const content = node.firstChild;
|
|
94
|
+
const group = node.childCount > 1 ? node.child(1) : null;
|
|
95
|
+
const children = [];
|
|
96
|
+
group?.forEach((child) => children.push(blockFromNode(child)));
|
|
97
|
+
const rows = content.type.spec.rows;
|
|
98
|
+
if (rows) {
|
|
99
|
+
const read = rows.read(content);
|
|
100
|
+
return {
|
|
101
|
+
id: node.attrs.id,
|
|
102
|
+
type: content.type.name,
|
|
103
|
+
props: read.props,
|
|
104
|
+
content: [],
|
|
105
|
+
rows: read.rows,
|
|
106
|
+
children
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
id: node.attrs.id,
|
|
111
|
+
type: content.type.name,
|
|
112
|
+
props: propsOf(content),
|
|
113
|
+
content: content.type.spec.code ? codeText(content) : inlineOf(content),
|
|
114
|
+
children
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function codeText(node) {
|
|
118
|
+
return node.textContent ? [{
|
|
119
|
+
type: "text",
|
|
120
|
+
text: node.textContent
|
|
121
|
+
}] : [];
|
|
122
|
+
}
|
|
123
|
+
function docToBlocks(doc) {
|
|
124
|
+
const blocks = [];
|
|
125
|
+
doc.forEach((node) => blocks.push(blockFromNode(node)));
|
|
126
|
+
return blocks;
|
|
127
|
+
}
|
|
128
|
+
function marksFor(schema, item) {
|
|
129
|
+
const marks = [];
|
|
130
|
+
for (const name of item.marks ?? []) {
|
|
131
|
+
const type = schema.marks[name];
|
|
132
|
+
if (type) marks.push(type.create());
|
|
133
|
+
}
|
|
134
|
+
if (item.link && schema.marks.link) marks.push(schema.marks.link.create({ href: item.link }));
|
|
135
|
+
for (const type of Object.values(schema.marks)) {
|
|
136
|
+
const field = type.spec.field;
|
|
137
|
+
const value = field && item[field];
|
|
138
|
+
if (!value) continue;
|
|
139
|
+
if (!isColor(value)) warnOnce(`"${value}" is not a palette color; dropped`);
|
|
140
|
+
else marks.push(type.create({ value }));
|
|
141
|
+
}
|
|
142
|
+
for (const id of item.comments ?? []) marks.push(schema.marks.comment.create({ id }));
|
|
143
|
+
return Mark.setFrom(marks);
|
|
144
|
+
}
|
|
145
|
+
function inlineNodes(schema, content, code) {
|
|
146
|
+
if (content === void 0) return [];
|
|
147
|
+
const items = typeof content === "string" ? [{
|
|
148
|
+
type: "text",
|
|
149
|
+
text: content
|
|
150
|
+
}] : content;
|
|
151
|
+
const nodes = [];
|
|
152
|
+
for (const item of items) if (item.type === "break") nodes.push(code ? schema.text("\n") : schema.nodes.hardBreak.create());
|
|
153
|
+
else if (item.type === "inline") {
|
|
154
|
+
const type = schema.nodes[item.name];
|
|
155
|
+
if (type?.isInline && !code) nodes.push(type.create(item.props));
|
|
156
|
+
} else if (item.text) nodes.push(schema.text(item.text, code ? [] : marksFor(schema, item)));
|
|
157
|
+
return nodes;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The attributes a content type declares, taken from `props` where given.
|
|
161
|
+
* A spec's `normalizeProps` can clamp them, e.g. a heading's level.
|
|
162
|
+
*/
|
|
163
|
+
function contentAttrs(type, props = {}) {
|
|
164
|
+
const attrs = {};
|
|
165
|
+
for (const name of Object.keys(type.spec.attrs ?? {})) if (props[name] !== void 0) attrs[name] = props[name];
|
|
166
|
+
const normalize = type.spec.normalizeProps;
|
|
167
|
+
return normalize ? normalize(attrs) : attrs;
|
|
168
|
+
}
|
|
169
|
+
function createContentNode(schema, type, props, content, rows) {
|
|
170
|
+
const nodeType = schema.nodes[type]?.isInGroup("blockContent") ? schema.nodes[type] : schema.nodes.paragraph;
|
|
171
|
+
const rowsSpec = nodeType.spec.rows;
|
|
172
|
+
if (rowsSpec) return rowsSpec.write(nodeType, props ?? {}, rows ?? []);
|
|
173
|
+
const attrs = contentAttrs(nodeType, props);
|
|
174
|
+
if (nodeType.isAtom || !nodeType.inlineContent) return nodeType.create(attrs);
|
|
175
|
+
return nodeType.create(attrs, inlineNodes(schema, content, !!nodeType.spec.code));
|
|
176
|
+
}
|
|
177
|
+
function nodeFromBlock(schema, block) {
|
|
178
|
+
const content = createContentNode(schema, block.type ?? "paragraph", block.props, block.content, block.rows);
|
|
179
|
+
const children = (block.children ?? []).map((child) => nodeFromBlock(schema, child));
|
|
180
|
+
const parts = children.length ? [content, schema.nodes.blockGroup.create(null, children)] : [content];
|
|
181
|
+
return schema.nodes.block.create({ id: block.id ?? createId() }, parts);
|
|
182
|
+
}
|
|
183
|
+
function blocksToDoc(schema, blocks) {
|
|
184
|
+
const list = blocks.length ? blocks : [{ type: "paragraph" }];
|
|
185
|
+
return schema.nodes.doc.create(null, list.map((block) => nodeFromBlock(schema, block)));
|
|
186
|
+
}
|
|
187
|
+
var warned = /* @__PURE__ */ new Set();
|
|
188
|
+
/** A development warning, once per message. */
|
|
189
|
+
function warnOnce(message) {
|
|
190
|
+
if (process.env.NODE_ENV === "production" || warned.has(message)) return;
|
|
191
|
+
warned.add(message);
|
|
192
|
+
console.warn(`[@dou-ui/editor] ${message}`);
|
|
193
|
+
}
|
|
194
|
+
export { blockFromNode, blocksToDoc, contentAttrs, createContentNode, createId, docToBlocks, inlineNodes, inlineOf, nodeFromBlock, warnOnce };
|
|
195
|
+
|
|
196
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","names":[],"sources":["../../src/core/model.ts"],"sourcesContent":["import { Mark, type NodeType, type Node as PMNode, type Schema } from 'prosemirror-model'\nimport { isColor, type TextColor } from './colors'\nimport type { ContentType, MarkType } from './schema'\n\n/** A run of text with its formatting. */\nexport interface TextContent {\n type: 'text'\n text: string\n /** Formatting, in a fixed order: bold, italic, underline, strike, code. */\n marks?: MarkType[]\n /** The link target, when the text is a link. */\n link?: string\n /** The text color, by palette name (e.g. `red`), with the `colors` extension. */\n color?: TextColor\n /** The background color, by palette name (e.g. `yellow`), with the `colors` extension. */\n highlight?: TextColor\n /** The ids of the comment threads about this text. */\n comments?: string[]\n}\n\n/**\n * For the `colors` extension's marks (internal): a mark that carries one\n * color is stored on the text run under this name, e.g.\n * `{ text, color: 'red' }`, and keeps it in its `value` attribute.\n */\nexport type MarkField = 'color' | 'highlight'\n\n/** A line break inside a block (`Shift+Enter`). */\nexport interface BreakContent {\n type: 'break'\n}\n\n/** An inline node an extension adds, e.g. a mention. */\nexport interface InlineNodeContent {\n type: 'inline'\n /** The node's name, e.g. `mention`. */\n name: string\n props: Record<string, unknown>\n}\n\nexport type InlineContent = TextContent | BreakContent | InlineNodeContent\n\n/** A block's attributes: those of the built-in types, and any an extension declares. */\nexport interface BlockProps {\n /** Heading level, `1` to `3`. */\n level?: number\n /** Whether a to-do item is done. */\n checked?: boolean\n /** The language of a code block. */\n language?: string\n /** The tone of a callout: `info`, `tip`, `warning` or `danger`. */\n tone?: string\n /** How a paragraph, heading or quote lines up; left out when it follows the reading direction. */\n align?: 'left' | 'center' | 'right'\n [name: string]: unknown\n}\n\n/** A table cell: formatted text, and how far it reaches when merged. */\nexport interface TableCell {\n content: InlineContent[]\n /** Columns the cell spans, when more than one. */\n colspan?: number\n /** Rows the cell spans, when more than one. */\n rowspan?: number\n}\n\n/** A table row: one cell for every column, including those a merged cell covers. */\nexport interface TableRow {\n /** Stable across edits, like a block's id. */\n id: string\n cells: TableCell[]\n}\n\nexport interface PartialTableCell {\n content?: InlineContent[] | string\n colspan?: number\n rowspan?: number\n}\n\nexport interface PartialTableRow {\n id?: string\n cells: (PartialTableCell | string)[]\n}\n\n/** One block of the document, as `v-model` reads and writes it. */\nexport interface Block {\n /** Stable across edits; a random UUID unless you provide your own. */\n id: string\n type: ContentType\n props: BlockProps\n content: InlineContent[]\n /** The rows of a table; other blocks have none. */\n rows?: TableRow[]\n children: Block[]\n}\n\n/**\n * The document the editor edits, bound with `v-model`: plain JSON to store\n * and send as it is. The editor writes full blocks; it reads partial ones.\n */\nexport interface EditorDocument<B extends PartialBlock = Block> {\n /** The version of the format; see `migrateDocument`. */\n version: 1\n blocks: B[]\n}\n\n/** A block to insert: everything but the type is optional. */\nexport interface PartialBlock {\n id?: string\n type?: ContentType\n props?: BlockProps\n /** Inline content, or plain text. */\n content?: InlineContent[] | string\n rows?: PartialTableRow[]\n children?: PartialBlock[]\n}\n\n/**\n * A content node spec field for blocks whose content is rows rather than\n * text (a table): how to read them into a block and build them back.\n */\nexport interface RowsSpec {\n read: (node: PMNode) => { props: BlockProps; rows: TableRow[] }\n write: (type: NodeType, props: BlockProps, rows: PartialTableRow[]) => PMNode\n /** The node as a grid of ids and cell nodes, for collaboration. */\n grid: (node: PMNode) => TableGrid\n}\n\n/** A table node laid out by ids. */\nexport interface TableGrid {\n /** The props other than `columns`. */\n props: BlockProps\n /** Each column's id and its other fields (width, alignment). */\n columns: { id: string; width?: number; align?: string }[]\n /** Row ids, top to bottom. */\n rows: string[]\n /** The cells that are shown, merged ones by their top-left place. */\n cells: GridCell[]\n}\n\nexport interface GridCell {\n row: number\n col: number\n rowspan: number\n colspan: number\n /** The cell node; its content is inline. */\n node: PMNode\n /** Where the cell starts, counted from the start of the table's content. */\n offset: number\n}\n\nconst MARK_ORDER: string[] = ['bold', 'italic', 'underline', 'strike', 'code']\n\n/** Built-in marks in a fixed order, then the others by name. */\nconst markRank = (name: string) => {\n const index = MARK_ORDER.indexOf(name)\n return index < 0 ? MARK_ORDER.length : index\n}\n\n/** A random id; UUIDs keep blocks created at the same time on two devices apart. */\nexport function createId(): string {\n const crypto = globalThis.crypto\n if (crypto?.randomUUID) return crypto.randomUUID()\n // a v4 UUID from `getRandomValues`, or `Math.random` as a last resort\n const bytes = new Uint8Array(16)\n if (crypto?.getRandomValues) crypto.getRandomValues(bytes)\n else for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256)\n bytes[6] = (bytes[6]! & 0x0f) | 0x40\n bytes[8] = (bytes[8]! & 0x3f) | 0x80\n const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n}\n\n/* document → blocks */\n\nexport function inlineOf(node: PMNode): InlineContent[] {\n const result: InlineContent[] = []\n node.forEach((child) => {\n if (child.type.name === 'hardBreak') {\n result.push({ type: 'break' })\n return\n }\n if (!child.isText) {\n result.push({ type: 'inline', name: child.type.name, props: { ...child.attrs } })\n return\n }\n const marks = child.marks\n .filter((mark) => !mark.type.spec.field)\n .map((mark) => mark.type.name)\n .filter((name): name is MarkType => name !== 'link' && name !== 'comment')\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts the filtered copy\n .sort((a, b) => markRank(a) - markRank(b) || a.localeCompare(b))\n const link = child.marks.find((mark) => mark.type.name === 'link')?.attrs.href as\n string | undefined\n const item: TextContent = { type: 'text', text: child.text ?? '' }\n if (marks.length) item.marks = marks\n if (link) item.link = link\n for (const mark of child.marks) {\n const field = mark.type.spec.field as MarkField | undefined\n if (field) item[field] = mark.attrs.value as TextColor\n }\n const comments = child.marks\n .filter((mark) => mark.type.name === 'comment')\n .map((mark) => mark.attrs.id as string)\n if (comments.length) item.comments = comments\n // merge with the previous run when the formatting is the same\n const previous = result.at(-1)\n if (\n previous?.type === 'text' &&\n previous.link === item.link &&\n previous.color === item.color &&\n previous.highlight === item.highlight &&\n String(previous.marks ?? '') === String(item.marks ?? '') &&\n String(previous.comments ?? '') === String(item.comments ?? '')\n )\n previous.text += item.text\n else result.push(item)\n })\n return result\n}\n\n/** A content node's attributes; those its spec lists as `optionalProps` only when set. */\nfunction propsOf(content: PMNode): BlockProps {\n const props: BlockProps = { ...content.attrs }\n for (const name of (content.type.spec.optionalProps as string[] | undefined) ?? [])\n if (props[name] === null || props[name] === undefined) delete props[name]\n return props\n}\n\n/**\n * Blocks already read, by node. Nodes never change, so a block that wasn't\n * edited is the same object as last time: reading a large document after\n * typing one key only builds the blocks on the path to that key.\n */\nconst blockCache = new WeakMap<PMNode, Block>()\n\nconst freeze = <T>(value: T): T => {\n if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.freeze(value)\n for (const item of Object.values(value)) freeze(item)\n }\n return value\n}\n\n/** A block of the document; shared between reads, so frozen. */\nexport function blockFromNode(node: PMNode): Block {\n let block = blockCache.get(node)\n if (!block) {\n block = readBlock(node)\n // a change to a shared block throws, instead of corrupting later reads\n freeze(block)\n blockCache.set(node, block)\n }\n return block\n}\n\nfunction readBlock(node: PMNode): Block {\n const content = node.firstChild!\n const group = node.childCount > 1 ? node.child(1) : null\n const children: Block[] = []\n group?.forEach((child) => children.push(blockFromNode(child)))\n const rows = content.type.spec.rows as RowsSpec | undefined\n if (rows) {\n const read = rows.read(content)\n return {\n id: node.attrs.id as string,\n type: content.type.name as ContentType,\n props: read.props,\n content: [],\n rows: read.rows,\n children,\n }\n }\n return {\n id: node.attrs.id as string,\n type: content.type.name as ContentType,\n props: propsOf(content),\n content: content.type.spec.code ? codeText(content) : inlineOf(content),\n children,\n }\n}\n\nfunction codeText(node: PMNode): InlineContent[] {\n return node.textContent ? [{ type: 'text', text: node.textContent }] : []\n}\n\nexport function docToBlocks(doc: PMNode): Block[] {\n const blocks: Block[] = []\n doc.forEach((node) => blocks.push(blockFromNode(node)))\n return blocks\n}\n\n/* blocks → document */\n\nfunction marksFor(schema: Schema, item: TextContent): Mark[] {\n const marks: Mark[] = []\n for (const name of item.marks ?? []) {\n const type = schema.marks[name]\n if (type) marks.push(type.create())\n }\n if (item.link && schema.marks.link) marks.push(schema.marks.link.create({ href: item.link }))\n for (const type of Object.values(schema.marks)) {\n const field = type.spec.field as MarkField | undefined\n const value = field && item[field]\n if (!value) continue\n // a color outside the palette is dropped\n if (!isColor(value)) warnOnce(`\"${value}\" is not a palette color; dropped`)\n else marks.push(type.create({ value }))\n }\n for (const id of item.comments ?? []) marks.push(schema.marks.comment!.create({ id }))\n return Mark.setFrom(marks) as Mark[]\n}\n\nexport function inlineNodes(\n schema: Schema,\n content: PartialBlock['content'],\n code: boolean,\n): PMNode[] {\n if (content === undefined) return []\n const items: InlineContent[] =\n typeof content === 'string' ? [{ type: 'text', text: content }] : content\n const nodes: PMNode[] = []\n for (const item of items) {\n if (item.type === 'break') {\n nodes.push(code ? schema.text('\\n') : schema.nodes.hardBreak!.create())\n } else if (item.type === 'inline') {\n // an inline node whose extension is missing is left out\n const type = schema.nodes[item.name]\n if (type?.isInline && !code) nodes.push(type.create(item.props))\n } else if (item.text) {\n nodes.push(schema.text(item.text, code ? [] : marksFor(schema, item)))\n }\n }\n return nodes\n}\n\n/**\n * The attributes a content type declares, taken from `props` where given.\n * A spec's `normalizeProps` can clamp them, e.g. a heading's level.\n */\nexport function contentAttrs(type: NodeType, props: BlockProps = {}) {\n const attrs: Record<string, unknown> = {}\n for (const name of Object.keys(type.spec.attrs ?? {})) {\n if (props[name] !== undefined) attrs[name] = props[name]\n }\n const normalize = type.spec.normalizeProps as\n ((attrs: Record<string, unknown>) => Record<string, unknown>) | undefined\n return normalize ? normalize(attrs) : attrs\n}\n\nexport function createContentNode(\n schema: Schema,\n type: ContentType,\n props?: BlockProps,\n content?: PartialBlock['content'],\n rows?: PartialTableRow[],\n): PMNode {\n const nodeType = schema.nodes[type]?.isInGroup('blockContent')\n ? schema.nodes[type]!\n : schema.nodes.paragraph!\n const rowsSpec = nodeType.spec.rows as RowsSpec | undefined\n if (rowsSpec) return rowsSpec.write(nodeType, props ?? {}, rows ?? [])\n const attrs = contentAttrs(nodeType, props)\n if (nodeType.isAtom || !nodeType.inlineContent) return nodeType.create(attrs)\n return nodeType.create(attrs, inlineNodes(schema, content, !!nodeType.spec.code))\n}\n\nexport function nodeFromBlock(schema: Schema, block: PartialBlock): PMNode {\n const content = createContentNode(\n schema,\n block.type ?? 'paragraph',\n block.props,\n block.content,\n block.rows,\n )\n const children = (block.children ?? []).map((child) => nodeFromBlock(schema, child))\n const parts = children.length\n ? [content, schema.nodes.blockGroup!.create(null, children)]\n : [content]\n return schema.nodes.block!.create({ id: block.id ?? createId() }, parts)\n}\n\nexport function blocksToDoc(schema: Schema, blocks: PartialBlock[]): PMNode {\n const list = blocks.length ? blocks : [{ type: 'paragraph' as const }]\n return schema.nodes.doc!.create(\n null,\n list.map((block) => nodeFromBlock(schema, block)),\n )\n}\n\nconst warned = new Set<string>()\n\n/** A development warning, once per message. */\nexport function warnOnce(message: string) {\n if (process.env.NODE_ENV === 'production' || warned.has(message)) return\n warned.add(message)\n console.warn(`[@dou-ui/editor] ${message}`)\n}\n"],"mappings":"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"}
|