@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,182 @@
|
|
|
1
|
+
import { createId } from "../core/model.js";
|
|
2
|
+
import { contentDOM, textBlock } from "../core/schema.js";
|
|
3
|
+
import { defineEditorExtension } from "../core/extension.js";
|
|
4
|
+
import { setBlockType } from "../core/commands.js";
|
|
5
|
+
import { blockInputRule } from "../core/input-rules.js";
|
|
6
|
+
import { numberingPlugin } from "../core/plugins.js";
|
|
7
|
+
import { listItemMarkdown, prepareLists } from "./list-html.js";
|
|
8
|
+
import { ListIcon, ListOrderedIcon, ListTodoIcon } from "@dou-ui/icons";
|
|
9
|
+
// src/extensions/lists.ts
|
|
10
|
+
var SVG = "http://www.w3.org/2000/svg";
|
|
11
|
+
/** The check of `CheckIcon`, drawn at the checkbox's stroke width. */
|
|
12
|
+
function checkMark() {
|
|
13
|
+
const svg = document.createElementNS(SVG, "svg");
|
|
14
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
15
|
+
svg.setAttribute("aria-hidden", "true");
|
|
16
|
+
const path = document.createElementNS(SVG, "path");
|
|
17
|
+
path.setAttribute("d", "m5 12.5 4.5 4.5L19 7.5");
|
|
18
|
+
for (const [name, value] of Object.entries({
|
|
19
|
+
fill: "none",
|
|
20
|
+
stroke: "currentColor",
|
|
21
|
+
"stroke-width": "2",
|
|
22
|
+
"stroke-linecap": "round",
|
|
23
|
+
"stroke-linejoin": "round"
|
|
24
|
+
})) path.setAttribute(name, value);
|
|
25
|
+
svg.append(path);
|
|
26
|
+
return svg;
|
|
27
|
+
}
|
|
28
|
+
/** A to-do item: a real checkbox in front of the text, its state in the node's attributes. */
|
|
29
|
+
var checkItemView = (node, view, getPos) => {
|
|
30
|
+
const dom = document.createElement("div");
|
|
31
|
+
dom.className = "dou-editor__content dou-editor__content--checkItem";
|
|
32
|
+
dom.dataset.contentType = "checkItem";
|
|
33
|
+
const box = document.createElement("span");
|
|
34
|
+
box.className = "dou-editor__check";
|
|
35
|
+
box.contentEditable = "false";
|
|
36
|
+
const button = document.createElement("button");
|
|
37
|
+
button.type = "button";
|
|
38
|
+
button.className = "dou-editor__checkbox";
|
|
39
|
+
button.setAttribute("role", "checkbox");
|
|
40
|
+
button.append(checkMark());
|
|
41
|
+
box.append(button);
|
|
42
|
+
const text = document.createElement("div");
|
|
43
|
+
text.className = "dou-editor__text";
|
|
44
|
+
text.id = `dou-editor-check-${createId()}`;
|
|
45
|
+
button.setAttribute("aria-labelledby", text.id);
|
|
46
|
+
dom.append(box, text);
|
|
47
|
+
let current = node;
|
|
48
|
+
const sync = () => {
|
|
49
|
+
const checked = !!current.attrs.checked;
|
|
50
|
+
dom.dataset.checked = String(checked);
|
|
51
|
+
button.setAttribute("aria-checked", String(checked));
|
|
52
|
+
button.disabled = !view.editable;
|
|
53
|
+
};
|
|
54
|
+
sync();
|
|
55
|
+
button.addEventListener("mousedown", (event) => event.preventDefault());
|
|
56
|
+
button.addEventListener("click", () => {
|
|
57
|
+
const pos = getPos();
|
|
58
|
+
if (pos === void 0 || !view.editable) return;
|
|
59
|
+
view.dispatch(view.state.tr.setNodeAttribute(pos, "checked", !current.attrs.checked));
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
dom,
|
|
63
|
+
contentDOM: text,
|
|
64
|
+
update(next) {
|
|
65
|
+
if (next.type !== current.type) return false;
|
|
66
|
+
current = next;
|
|
67
|
+
sync();
|
|
68
|
+
return true;
|
|
69
|
+
},
|
|
70
|
+
ignoreMutation: (mutation) => box.contains(mutation.target),
|
|
71
|
+
stopEvent: (event) => box.contains(event.target)
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Bulleted, numbered and to-do lists. Each item is a block of its own:
|
|
76
|
+
* `Enter` continues the list, `Tab` nests an item. Pasted HTML lists keep
|
|
77
|
+
* their nesting, and task lists become to-do items.
|
|
78
|
+
*/
|
|
79
|
+
var lists = defineEditorExtension({
|
|
80
|
+
name: "lists",
|
|
81
|
+
blocks: {
|
|
82
|
+
bulletItem: textBlock("bulletItem", {
|
|
83
|
+
continues: true,
|
|
84
|
+
parseDOM: [{ tag: "div[data-content-type=\"bulletItem\"]" }, { tag: "ul > li" }]
|
|
85
|
+
}),
|
|
86
|
+
numberedItem: textBlock("numberedItem", {
|
|
87
|
+
continues: true,
|
|
88
|
+
parseDOM: [{ tag: "div[data-content-type=\"numberedItem\"]" }, { tag: "ol > li" }]
|
|
89
|
+
}),
|
|
90
|
+
checkItem: textBlock("checkItem", {
|
|
91
|
+
continues: true,
|
|
92
|
+
attrs: { checked: { default: false } },
|
|
93
|
+
parseDOM: [{
|
|
94
|
+
tag: "div[data-content-type=\"checkItem\"]",
|
|
95
|
+
getAttrs: (dom) => ({ checked: dom.dataset.checked === "true" })
|
|
96
|
+
}],
|
|
97
|
+
toDOM: (node) => contentDOM("checkItem", { "data-checked": String(node.attrs.checked) })
|
|
98
|
+
})
|
|
99
|
+
},
|
|
100
|
+
nodeViews: { checkItem: checkItemView },
|
|
101
|
+
plugins: () => [numberingPlugin()],
|
|
102
|
+
prepareHTML: prepareLists,
|
|
103
|
+
output: { blocks: {
|
|
104
|
+
bulletItem: {
|
|
105
|
+
group: "ul",
|
|
106
|
+
html: (_block, { content, children }) => `<li>${content}${children}</li>`,
|
|
107
|
+
markdown: (_block, context) => listItemMarkdown("- ", context)
|
|
108
|
+
},
|
|
109
|
+
numberedItem: {
|
|
110
|
+
group: "ol",
|
|
111
|
+
html: (_block, { content, children }) => `<li>${content}${children}</li>`,
|
|
112
|
+
markdown: (_block, context) => listItemMarkdown(`${context.index}. `, context)
|
|
113
|
+
},
|
|
114
|
+
checkItem: {
|
|
115
|
+
group: "ul class=\"task-list\"",
|
|
116
|
+
html: (block, { content, children }) => `<li><input type="checkbox" disabled${block.props.checked ? " checked" : ""}> ${content}${children}</li>`,
|
|
117
|
+
markdown: (block, context) => listItemMarkdown(block.props.checked ? "- [x] " : "- [ ] ", context)
|
|
118
|
+
}
|
|
119
|
+
} },
|
|
120
|
+
inputRules: () => [
|
|
121
|
+
blockInputRule(/^[-*+]\s$/, "bulletItem"),
|
|
122
|
+
blockInputRule(/^1[.)]\s$/, "numberedItem"),
|
|
123
|
+
blockInputRule(/^\[( |x)?\]\s$/i, "checkItem", (match) => ({ checked: match[1]?.toLowerCase() === "x" }))
|
|
124
|
+
],
|
|
125
|
+
keymap: () => ({
|
|
126
|
+
"mod+shift+7": setBlockType("numberedItem"),
|
|
127
|
+
"mod+shift+8": setBlockType("bulletItem"),
|
|
128
|
+
"mod+shift+9": setBlockType("checkItem")
|
|
129
|
+
}),
|
|
130
|
+
blockOptions: [
|
|
131
|
+
{
|
|
132
|
+
key: "bulletItem",
|
|
133
|
+
type: "bulletItem",
|
|
134
|
+
label: (m) => m.types.bulletItem,
|
|
135
|
+
hint: (m) => m.hints.bulletItem,
|
|
136
|
+
icon: ListIcon,
|
|
137
|
+
keywords: [
|
|
138
|
+
"ul",
|
|
139
|
+
"bullet",
|
|
140
|
+
"list",
|
|
141
|
+
"lb",
|
|
142
|
+
"liebiao"
|
|
143
|
+
],
|
|
144
|
+
shortcut: "-"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
key: "numberedItem",
|
|
148
|
+
type: "numberedItem",
|
|
149
|
+
label: (m) => m.types.numberedItem,
|
|
150
|
+
hint: (m) => m.hints.numberedItem,
|
|
151
|
+
icon: ListOrderedIcon,
|
|
152
|
+
keywords: [
|
|
153
|
+
"ol",
|
|
154
|
+
"number",
|
|
155
|
+
"ordered",
|
|
156
|
+
"list",
|
|
157
|
+
"lb",
|
|
158
|
+
"bianhao"
|
|
159
|
+
],
|
|
160
|
+
shortcut: "1."
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
key: "checkItem",
|
|
164
|
+
type: "checkItem",
|
|
165
|
+
label: (m) => m.types.checkItem,
|
|
166
|
+
hint: (m) => m.hints.checkItem,
|
|
167
|
+
icon: ListTodoIcon,
|
|
168
|
+
keywords: [
|
|
169
|
+
"todo",
|
|
170
|
+
"task",
|
|
171
|
+
"check",
|
|
172
|
+
"db",
|
|
173
|
+
"daiban",
|
|
174
|
+
"renwu"
|
|
175
|
+
],
|
|
176
|
+
shortcut: "[]"
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
});
|
|
180
|
+
export { lists };
|
|
181
|
+
|
|
182
|
+
//# sourceMappingURL=lists.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lists.js","names":[],"sources":["../../src/extensions/lists.ts"],"sourcesContent":["import { ListIcon, ListOrderedIcon, ListTodoIcon } from '@dou-ui/icons'\nimport type { NodeViewConstructor } from 'prosemirror-view'\nimport { setBlockType } from '../core/commands'\nimport { defineEditorExtension } from '../core/extension'\nimport { blockInputRule } from '../core/input-rules'\nimport { createId } from '../core/model'\nimport { numberingPlugin } from '../core/plugins'\nimport { contentDOM, textBlock } from '../core/schema'\nimport { listItemMarkdown, prepareLists } from './list-html'\n\nconst SVG = 'http://www.w3.org/2000/svg'\n\n/** The check of `CheckIcon`, drawn at the checkbox's stroke width. */\nfunction checkMark(): SVGSVGElement {\n const svg = document.createElementNS(SVG, 'svg')\n svg.setAttribute('viewBox', '0 0 24 24')\n svg.setAttribute('aria-hidden', 'true')\n const path = document.createElementNS(SVG, 'path')\n path.setAttribute('d', 'm5 12.5 4.5 4.5L19 7.5')\n for (const [name, value] of Object.entries({\n fill: 'none',\n stroke: 'currentColor',\n 'stroke-width': '2',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n }))\n path.setAttribute(name, value)\n svg.append(path)\n return svg\n}\n\n/** A to-do item: a real checkbox in front of the text, its state in the node's attributes. */\nconst checkItemView: NodeViewConstructor = (node, view, getPos) => {\n const dom = document.createElement('div')\n dom.className = 'dou-editor__content dou-editor__content--checkItem'\n dom.dataset.contentType = 'checkItem'\n const box = document.createElement('span')\n box.className = 'dou-editor__check'\n box.contentEditable = 'false'\n const button = document.createElement('button')\n button.type = 'button'\n button.className = 'dou-editor__checkbox'\n button.setAttribute('role', 'checkbox')\n button.append(checkMark())\n box.append(button)\n const text = document.createElement('div')\n text.className = 'dou-editor__text'\n // the item's own text names the checkbox\n text.id = `dou-editor-check-${createId()}`\n button.setAttribute('aria-labelledby', text.id)\n dom.append(box, text)\n\n let current = node\n const sync = () => {\n const checked = !!current.attrs.checked\n dom.dataset.checked = String(checked)\n button.setAttribute('aria-checked', String(checked))\n button.disabled = !view.editable\n }\n sync()\n button.addEventListener('mousedown', (event) => event.preventDefault())\n button.addEventListener('click', () => {\n const pos = getPos()\n if (pos === undefined || !view.editable) return\n view.dispatch(view.state.tr.setNodeAttribute(pos, 'checked', !current.attrs.checked))\n })\n return {\n dom,\n contentDOM: text,\n update(next) {\n if (next.type !== current.type) return false\n current = next\n sync()\n return true\n },\n ignoreMutation: (mutation) => box.contains(mutation.target),\n stopEvent: (event) => box.contains(event.target as Node),\n }\n}\n\n/**\n * Bulleted, numbered and to-do lists. Each item is a block of its own:\n * `Enter` continues the list, `Tab` nests an item. Pasted HTML lists keep\n * their nesting, and task lists become to-do items.\n */\nexport const lists = defineEditorExtension({\n name: 'lists',\n blocks: {\n bulletItem: textBlock('bulletItem', {\n continues: true,\n parseDOM: [{ tag: 'div[data-content-type=\"bulletItem\"]' }, { tag: 'ul > li' }],\n }),\n numberedItem: textBlock('numberedItem', {\n continues: true,\n parseDOM: [{ tag: 'div[data-content-type=\"numberedItem\"]' }, { tag: 'ol > li' }],\n }),\n checkItem: textBlock('checkItem', {\n continues: true,\n attrs: { checked: { default: false } },\n parseDOM: [\n {\n tag: 'div[data-content-type=\"checkItem\"]',\n getAttrs: (dom) => ({ checked: (dom as HTMLElement).dataset.checked === 'true' }),\n },\n ],\n toDOM: (node) => contentDOM('checkItem', { 'data-checked': String(node.attrs.checked) }),\n }),\n },\n nodeViews: { checkItem: checkItemView },\n plugins: () => [numberingPlugin()],\n prepareHTML: prepareLists,\n output: {\n blocks: {\n bulletItem: {\n group: 'ul',\n html: (_block, { content, children }) => `<li>${content}${children}</li>`,\n markdown: (_block, context) => listItemMarkdown('- ', context),\n },\n numberedItem: {\n group: 'ol',\n html: (_block, { content, children }) => `<li>${content}${children}</li>`,\n markdown: (_block, context) => listItemMarkdown(`${context.index}. `, context),\n },\n checkItem: {\n group: 'ul class=\"task-list\"',\n html: (block, { content, children }) =>\n `<li><input type=\"checkbox\" disabled${block.props.checked ? ' checked' : ''}> ${content}${children}</li>`,\n markdown: (block, context) =>\n listItemMarkdown(block.props.checked ? '- [x] ' : '- [ ] ', context),\n },\n },\n },\n inputRules: () => [\n blockInputRule(/^[-*+]\\s$/, 'bulletItem'),\n blockInputRule(/^1[.)]\\s$/, 'numberedItem'),\n blockInputRule(/^\\[( |x)?\\]\\s$/i, 'checkItem', (match) => ({\n checked: match[1]?.toLowerCase() === 'x',\n })),\n ],\n keymap: () => ({\n 'mod+shift+7': setBlockType('numberedItem'),\n 'mod+shift+8': setBlockType('bulletItem'),\n 'mod+shift+9': setBlockType('checkItem'),\n }),\n blockOptions: [\n {\n key: 'bulletItem',\n type: 'bulletItem',\n label: (m) => m.types.bulletItem,\n hint: (m) => m.hints.bulletItem,\n icon: ListIcon,\n keywords: ['ul', 'bullet', 'list', 'lb', 'liebiao'],\n shortcut: '-',\n },\n {\n key: 'numberedItem',\n type: 'numberedItem',\n label: (m) => m.types.numberedItem,\n hint: (m) => m.hints.numberedItem,\n icon: ListOrderedIcon,\n keywords: ['ol', 'number', 'ordered', 'list', 'lb', 'bianhao'],\n shortcut: '1.',\n },\n {\n key: 'checkItem',\n type: 'checkItem',\n label: (m) => m.types.checkItem,\n hint: (m) => m.hints.checkItem,\n icon: ListTodoIcon,\n keywords: ['todo', 'task', 'check', 'db', 'daiban', 'renwu'],\n shortcut: '[]',\n },\n ],\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { defineEditorExtension } from "../../core/extension.js";
|
|
2
|
+
import { escapeHTML } from "../../core/output.js";
|
|
3
|
+
import { mentionPlugin } from "./state.js";
|
|
4
|
+
import mention_menu_default from "./mention-menu.js";
|
|
5
|
+
import { defineComponent, h } from "vue";
|
|
6
|
+
// src/extensions/mention/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* Mentions: typing `@` suggests people (or anything else) from `search`,
|
|
9
|
+
* and the chosen one goes into the text as a mention, kept in the
|
|
10
|
+
* document as `{ type: 'inline', name: 'mention', props: { id, label } }`.
|
|
11
|
+
*/
|
|
12
|
+
var mentionHTML = (props) => `<span data-mention="${escapeHTML(String(props.id ?? ""))}">@${escapeHTML(String(props.label ?? ""))}</span>`;
|
|
13
|
+
function mention(options) {
|
|
14
|
+
const Menu = defineComponent({
|
|
15
|
+
name: "DouEditorMentionMenu",
|
|
16
|
+
setup() {
|
|
17
|
+
return () => h(mention_menu_default, { search: options.search });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return defineEditorExtension({
|
|
21
|
+
name: "mention",
|
|
22
|
+
inlines: { mention: {
|
|
23
|
+
atom: true,
|
|
24
|
+
selectable: false,
|
|
25
|
+
attrs: {
|
|
26
|
+
id: { default: "" },
|
|
27
|
+
label: { default: "" }
|
|
28
|
+
},
|
|
29
|
+
leafText: (node) => `@${node.attrs.label}`,
|
|
30
|
+
parseDOM: [{
|
|
31
|
+
tag: "span[data-mention]",
|
|
32
|
+
getAttrs: (dom) => ({
|
|
33
|
+
id: dom.dataset.mention ?? "",
|
|
34
|
+
label: (dom.textContent ?? "").replace(/^@/, "")
|
|
35
|
+
})
|
|
36
|
+
}],
|
|
37
|
+
toDOM: (node) => [
|
|
38
|
+
"span",
|
|
39
|
+
{
|
|
40
|
+
class: "dou-editor__mention",
|
|
41
|
+
"data-mention": node.attrs.id
|
|
42
|
+
},
|
|
43
|
+
`@${node.attrs.label}`
|
|
44
|
+
]
|
|
45
|
+
} },
|
|
46
|
+
output: { inlines: { mention: {
|
|
47
|
+
html: mentionHTML,
|
|
48
|
+
markdown: mentionHTML
|
|
49
|
+
} } },
|
|
50
|
+
plugins: () => [mentionPlugin()],
|
|
51
|
+
overlays: [Menu]
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export { mention };
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["MentionMenu"],"sources":["../../../src/extensions/mention/index.ts"],"sourcesContent":["import { defineComponent, h } from 'vue'\nimport { escapeHTML } from '../../core/output'\nimport { defineEditorExtension, type EditorExtension } from '../../core/extension'\nimport MentionMenu from './mention-menu.vue'\nimport { mentionPlugin, type MentionItem } from './state'\n\nexport interface MentionOptions {\n /**\n * Finds who matches what was typed after the `@` (empty right after it).\n * Can return a Promise; only the answer to the latest query is shown.\n */\n search: (query: string) => MentionItem[] | Promise<MentionItem[]>\n}\n\n/**\n * Mentions: typing `@` suggests people (or anything else) from `search`,\n * and the chosen one goes into the text as a mention, kept in the\n * document as `{ type: 'inline', name: 'mention', props: { id, label } }`.\n */\nconst mentionHTML = (props: Record<string, unknown>) =>\n `<span data-mention=\"${escapeHTML(String(props.id ?? ''))}\">@${escapeHTML(String(props.label ?? ''))}</span>`\n\nexport function mention(options: MentionOptions): EditorExtension {\n const Menu = defineComponent({\n name: 'DouEditorMentionMenu',\n setup() {\n return () => h(MentionMenu, { search: options.search })\n },\n })\n return defineEditorExtension({\n name: 'mention',\n inlines: {\n mention: {\n atom: true,\n selectable: false,\n attrs: { id: { default: '' }, label: { default: '' } },\n leafText: (node) => `@${node.attrs.label}`,\n parseDOM: [\n {\n tag: 'span[data-mention]',\n getAttrs: (dom) => ({\n id: (dom as HTMLElement).dataset.mention ?? '',\n label: ((dom as HTMLElement).textContent ?? '').replace(/^@/, ''),\n }),\n },\n ],\n toDOM: (node) => [\n 'span',\n { class: 'dou-editor__mention', 'data-mention': node.attrs.id },\n `@${node.attrs.label}`,\n ],\n },\n },\n output: {\n // Markdown has no mention: the same HTML keeps the id when it is read back\n inlines: { mention: { html: mentionHTML, markdown: mentionHTML } },\n },\n plugins: () => [mentionPlugin()],\n overlays: [Menu],\n })\n}\n\nexport type { MentionItem }\n"],"mappings":";;;;;;;;;;;AAmBA,IAAM,eAAe,UACnB,uBAAuB,WAAW,OAAO,MAAM,MAAM,EAAE,CAAC,EAAE,KAAK,WAAW,OAAO,MAAM,SAAS,EAAE,CAAC,EAAE;AAEvG,SAAgB,QAAQ,SAA0C;CAChE,MAAM,OAAO,gBAAgB;EAC3B,MAAM;EACN,QAAQ;GACN,aAAa,EAAEA,sBAAa,EAAE,QAAQ,QAAQ,OAAO,CAAC;EACxD;CACF,CAAC;CACD,OAAO,sBAAsB;EAC3B,MAAM;EACN,SAAS,EACP,SAAS;GACP,MAAM;GACN,YAAY;GACZ,OAAO;IAAE,IAAI,EAAE,SAAS,GAAG;IAAG,OAAO,EAAE,SAAS,GAAG;GAAE;GACrD,WAAW,SAAS,IAAI,KAAK,MAAM;GACnC,UAAU,CACR;IACE,KAAK;IACL,WAAW,SAAS;KAClB,IAAK,IAAoB,QAAQ,WAAW;KAC5C,QAAS,IAAoB,eAAe,GAAA,CAAI,QAAQ,MAAM,EAAE;IAClE;GACF,CACF;GACA,QAAQ,SAAS;IACf;IACA;KAAE,OAAO;KAAuB,gBAAgB,KAAK,MAAM;IAAG;IAC9D,IAAI,KAAK,MAAM;GACjB;EACF,EACF;EACA,QAAQ,EAEN,SAAS,EAAE,SAAS;GAAE,MAAM;GAAa,UAAU;EAAY,EAAE,EACnE;EACA,eAAe,CAAC,cAAc,CAAC;EAC/B,UAAU,CAAC,IAAI;CACjB,CAAC;AACH"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import mention_menu_vue_vue_type_script_setup_true_lang_default from "./mention-menu.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
// src/extensions/mention/mention-menu.vue
|
|
3
|
+
var mention_menu_default = mention_menu_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
export { mention_menu_default as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=mention-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mention-menu.js","names":["_sfc_main"],"sources":["../../../src/extensions/mention/mention-menu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { DouAvatar, useFloating, useLayer, useZIndex, type VirtualElement } from 'dou-ui'\nimport { computed, nextTick, shallowRef, useId, useTemplateRef, watch } from 'vue'\nimport { useEditor } from '../../editor'\nimport {\n chooseMention,\n closeMention,\n getMention,\n setMentionActive,\n setMentionItems,\n type MentionItem,\n} from './state'\n\nconst props = defineProps<{\n search: (query: string) => MentionItem[] | Promise<MentionItem[]>\n}>()\n\nconst { view, state, messages } = useEditor()\nconst id = `dou-editor-mention-${useId()}`\nconst panel = useTemplateRef<HTMLElement>('panel')\n\nconst mention = computed(() => getMention(state.value))\nconst open = computed(() => !!mention.value)\nconst loading = shallowRef(false)\n\n// the list hangs below the `@` that opened it\nconst anchor: VirtualElement = {\n getBoundingClientRect() {\n const current = view.value\n const from = mention.value?.from\n if (!current || from === undefined) return new DOMRect()\n const { left, top, bottom } = current.coordsAtPos(from)\n return new DOMRect(left, top, 0, bottom - top)\n },\n}\nconst { style, placement, update } = useFloating(() => (open.value ? anchor : null), panel, {\n open,\n placement: 'bottom-start',\n offset: 6,\n})\nconst { next } = useZIndex()\nconst zIndex = shallowRef(0)\nwatch(open, (value) => value && (zIndex.value = next()), { immediate: true })\n// a press outside the editor and the list closes it; inside, the cursor move does\nuseLayer({\n open,\n elements: () => [panel.value, view.value?.dom],\n onOutside: () => view.value && closeMention(view.value),\n onEscape: () => view.value && closeMention(view.value),\n})\n\n// search again as the query changes; a slower, older answer never wins\nlet run = 0\nwatch(\n () => (mention.value ? mention.value.query : null),\n async (query) => {\n const current = ++run\n if (query === null) {\n loading.value = false\n return\n }\n loading.value = true\n let items: MentionItem[] = []\n try {\n items = await props.search(query)\n } catch {\n // a failed search shows nothing\n }\n if (current !== run || !view.value || !getMention(view.value.state)) return\n loading.value = false\n setMentionItems(view.value, items.slice(0, 50))\n void nextTick(update)\n },\n { immediate: true },\n)\n\n// the editor keeps focus; the active item is announced through it\nwatch(\n () => [open.value, mention.value?.active, mention.value?.items] as const,\n () => {\n const dom = view.value?.dom\n if (!dom) return\n const items = mention.value?.items ?? []\n const option = open.value && items.length ? `${id}-${mention.value!.active}` : null\n if (open.value) dom.setAttribute('aria-controls', id)\n else dom.removeAttribute('aria-controls')\n if (option) dom.setAttribute('aria-activedescendant', option)\n else dom.removeAttribute('aria-activedescendant')\n if (option) document.getElementById(option)?.scrollIntoView({ block: 'nearest' })\n },\n { flush: 'post' },\n)\n\nfunction choose(item: MentionItem) {\n if (view.value) chooseMention(view.value, item)\n}\n\nfunction hover(index: number) {\n if (view.value && mention.value?.active !== index) setMentionActive(view.value, index)\n}\n</script>\n\n<template>\n <Transition name=\"dou-floating\">\n <div\n v-if=\"open\"\n :id=\"id\"\n ref=\"panel\"\n role=\"listbox\"\n :aria-label=\"messages.mention.label\"\n class=\"dou-dropdown dou-floating dou-editor-mention\"\n :style=\"[style, { zIndex }]\"\n :data-placement=\"placement\"\n @mousedown.prevent\n >\n <div\n v-for=\"(item, index) in mention!.items\"\n :id=\"`${id}-${index}`\"\n :key=\"item.id\"\n role=\"option\"\n :aria-selected=\"index === mention!.active\"\n :class=\"[\n 'dou-dropdown__item',\n 'dou-editor-mention__item',\n { 'is-active': index === mention!.active },\n ]\"\n @pointermove=\"hover(index)\"\n @click=\"choose(item)\"\n >\n <DouAvatar :src=\"item.avatar\" :name=\"item.label\" size=\"sm\" />\n <span class=\"dou-editor-mention__text\">\n <span class=\"dou-editor-mention__label\">{{ item.label }}</span>\n <span v-if=\"item.description\" class=\"dou-editor-mention__description\">{{\n item.description\n }}</span>\n </span>\n </div>\n <div v-if=\"!mention!.items.length\" class=\"dou-editor-slash__empty\">\n {{ loading ? messages.mention.searching : messages.mention.noResults }}\n </div>\n </div>\n </Transition>\n</template>\n"],"mappings":""}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useEditor } from "../../editor.js";
|
|
2
|
+
import { chooseMention, closeMention, getMention, setMentionActive, setMentionItems } from "./state.js";
|
|
3
|
+
import { DouAvatar, useFloating, useLayer, useZIndex } from "dou-ui";
|
|
4
|
+
import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, openBlock, renderList, shallowRef, toDisplayString, unref, useId, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
5
|
+
// src/extensions/mention/mention-menu.vue?vue&type=script&setup=true&lang.ts
|
|
6
|
+
var _hoisted_1 = ["aria-label", "data-placement"];
|
|
7
|
+
var _hoisted_2 = [
|
|
8
|
+
"id",
|
|
9
|
+
"aria-selected",
|
|
10
|
+
"onPointermove",
|
|
11
|
+
"onClick"
|
|
12
|
+
];
|
|
13
|
+
var _hoisted_3 = { class: "dou-editor-mention__text" };
|
|
14
|
+
var _hoisted_4 = { class: "dou-editor-mention__label" };
|
|
15
|
+
var _hoisted_5 = {
|
|
16
|
+
key: 0,
|
|
17
|
+
class: "dou-editor-mention__description"
|
|
18
|
+
};
|
|
19
|
+
var _hoisted_6 = {
|
|
20
|
+
key: 0,
|
|
21
|
+
class: "dou-editor-slash__empty"
|
|
22
|
+
};
|
|
23
|
+
var mention_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
24
|
+
__name: "mention-menu",
|
|
25
|
+
props: { search: { type: Function } },
|
|
26
|
+
setup(__props) {
|
|
27
|
+
const props = __props;
|
|
28
|
+
const { view, state, messages } = useEditor();
|
|
29
|
+
const id = `dou-editor-mention-${useId()}`;
|
|
30
|
+
const panel = useTemplateRef("panel");
|
|
31
|
+
const mention = computed(() => getMention(state.value));
|
|
32
|
+
const open = computed(() => !!mention.value);
|
|
33
|
+
const loading = shallowRef(false);
|
|
34
|
+
const anchor = { getBoundingClientRect() {
|
|
35
|
+
const current = view.value;
|
|
36
|
+
const from = mention.value?.from;
|
|
37
|
+
if (!current || from === void 0) return new DOMRect();
|
|
38
|
+
const { left, top, bottom } = current.coordsAtPos(from);
|
|
39
|
+
return new DOMRect(left, top, 0, bottom - top);
|
|
40
|
+
} };
|
|
41
|
+
const { style, placement, update } = useFloating(() => open.value ? anchor : null, panel, {
|
|
42
|
+
open,
|
|
43
|
+
placement: "bottom-start",
|
|
44
|
+
offset: 6
|
|
45
|
+
});
|
|
46
|
+
const { next } = useZIndex();
|
|
47
|
+
const zIndex = shallowRef(0);
|
|
48
|
+
watch(open, (value) => value && (zIndex.value = next()), { immediate: true });
|
|
49
|
+
useLayer({
|
|
50
|
+
open,
|
|
51
|
+
elements: () => [panel.value, view.value?.dom],
|
|
52
|
+
onOutside: () => view.value && closeMention(view.value),
|
|
53
|
+
onEscape: () => view.value && closeMention(view.value)
|
|
54
|
+
});
|
|
55
|
+
let run = 0;
|
|
56
|
+
watch(() => mention.value ? mention.value.query : null, async (query) => {
|
|
57
|
+
const current = ++run;
|
|
58
|
+
if (query === null) {
|
|
59
|
+
loading.value = false;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
loading.value = true;
|
|
63
|
+
let items = [];
|
|
64
|
+
try {
|
|
65
|
+
items = await props.search(query);
|
|
66
|
+
} catch {}
|
|
67
|
+
if (current !== run || !view.value || !getMention(view.value.state)) return;
|
|
68
|
+
loading.value = false;
|
|
69
|
+
setMentionItems(view.value, items.slice(0, 50));
|
|
70
|
+
nextTick(update);
|
|
71
|
+
}, { immediate: true });
|
|
72
|
+
watch(() => [
|
|
73
|
+
open.value,
|
|
74
|
+
mention.value?.active,
|
|
75
|
+
mention.value?.items
|
|
76
|
+
], () => {
|
|
77
|
+
const dom = view.value?.dom;
|
|
78
|
+
if (!dom) return;
|
|
79
|
+
const items = mention.value?.items ?? [];
|
|
80
|
+
const option = open.value && items.length ? `${id}-${mention.value.active}` : null;
|
|
81
|
+
if (open.value) dom.setAttribute("aria-controls", id);
|
|
82
|
+
else dom.removeAttribute("aria-controls");
|
|
83
|
+
if (option) dom.setAttribute("aria-activedescendant", option);
|
|
84
|
+
else dom.removeAttribute("aria-activedescendant");
|
|
85
|
+
if (option) document.getElementById(option)?.scrollIntoView({ block: "nearest" });
|
|
86
|
+
}, { flush: "post" });
|
|
87
|
+
function choose(item) {
|
|
88
|
+
if (view.value) chooseMention(view.value, item);
|
|
89
|
+
}
|
|
90
|
+
function hover(index) {
|
|
91
|
+
if (view.value && mention.value?.active !== index) setMentionActive(view.value, index);
|
|
92
|
+
}
|
|
93
|
+
return (_ctx, _cache) => {
|
|
94
|
+
return openBlock(), createBlock(Transition, { name: "dou-floating" }, {
|
|
95
|
+
default: withCtx(() => [open.value ? (openBlock(), createElementBlock("div", {
|
|
96
|
+
key: 0,
|
|
97
|
+
id,
|
|
98
|
+
ref_key: "panel",
|
|
99
|
+
ref: panel,
|
|
100
|
+
role: "listbox",
|
|
101
|
+
"aria-label": unref(messages).mention.label,
|
|
102
|
+
class: "dou-dropdown dou-floating dou-editor-mention",
|
|
103
|
+
style: normalizeStyle([unref(style), { zIndex: zIndex.value }]),
|
|
104
|
+
"data-placement": unref(placement),
|
|
105
|
+
onMousedown: _cache[0] || (_cache[0] = withModifiers(() => {}, ["prevent"]))
|
|
106
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(mention.value.items, (item, index) => {
|
|
107
|
+
return openBlock(), createElementBlock("div", {
|
|
108
|
+
id: `${id}-${index}`,
|
|
109
|
+
key: item.id,
|
|
110
|
+
role: "option",
|
|
111
|
+
"aria-selected": index === mention.value.active,
|
|
112
|
+
class: normalizeClass([
|
|
113
|
+
"dou-dropdown__item",
|
|
114
|
+
"dou-editor-mention__item",
|
|
115
|
+
{ "is-active": index === mention.value.active }
|
|
116
|
+
]),
|
|
117
|
+
onPointermove: ($event) => hover(index),
|
|
118
|
+
onClick: ($event) => choose(item)
|
|
119
|
+
}, [createVNode(unref(DouAvatar), {
|
|
120
|
+
src: item.avatar,
|
|
121
|
+
name: item.label,
|
|
122
|
+
size: "sm"
|
|
123
|
+
}, null, 8, ["src", "name"]), createElementVNode("span", _hoisted_3, [createElementVNode("span", _hoisted_4, toDisplayString(item.label), 1), item.description ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(item.description), 1)) : createCommentVNode("", true)])], 42, _hoisted_2);
|
|
124
|
+
}), 128)), !mention.value.items.length ? (openBlock(), createElementBlock("div", _hoisted_6, toDisplayString(loading.value ? unref(messages).mention.searching : unref(messages).mention.noResults), 1)) : createCommentVNode("", true)], 44, _hoisted_1)) : createCommentVNode("", true)]),
|
|
125
|
+
_: 1
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
export { mention_menu_vue_vue_type_script_setup_true_lang_default as default };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=mention-menu.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mention-menu.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createBlock","_Transition","_withCtx","_createElementBlock","_unref","_normalizeStyle","_withModifiers","_Fragment","_renderList","_normalizeClass","_createVNode","_createElementVNode","_toDisplayString","_createCommentVNode"],"sources":["../../../src/extensions/mention/mention-menu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { DouAvatar, useFloating, useLayer, useZIndex, type VirtualElement } from 'dou-ui'\nimport { computed, nextTick, shallowRef, useId, useTemplateRef, watch } from 'vue'\nimport { useEditor } from '../../editor'\nimport {\n chooseMention,\n closeMention,\n getMention,\n setMentionActive,\n setMentionItems,\n type MentionItem,\n} from './state'\n\nconst props = defineProps<{\n search: (query: string) => MentionItem[] | Promise<MentionItem[]>\n}>()\n\nconst { view, state, messages } = useEditor()\nconst id = `dou-editor-mention-${useId()}`\nconst panel = useTemplateRef<HTMLElement>('panel')\n\nconst mention = computed(() => getMention(state.value))\nconst open = computed(() => !!mention.value)\nconst loading = shallowRef(false)\n\n// the list hangs below the `@` that opened it\nconst anchor: VirtualElement = {\n getBoundingClientRect() {\n const current = view.value\n const from = mention.value?.from\n if (!current || from === undefined) return new DOMRect()\n const { left, top, bottom } = current.coordsAtPos(from)\n return new DOMRect(left, top, 0, bottom - top)\n },\n}\nconst { style, placement, update } = useFloating(() => (open.value ? anchor : null), panel, {\n open,\n placement: 'bottom-start',\n offset: 6,\n})\nconst { next } = useZIndex()\nconst zIndex = shallowRef(0)\nwatch(open, (value) => value && (zIndex.value = next()), { immediate: true })\n// a press outside the editor and the list closes it; inside, the cursor move does\nuseLayer({\n open,\n elements: () => [panel.value, view.value?.dom],\n onOutside: () => view.value && closeMention(view.value),\n onEscape: () => view.value && closeMention(view.value),\n})\n\n// search again as the query changes; a slower, older answer never wins\nlet run = 0\nwatch(\n () => (mention.value ? mention.value.query : null),\n async (query) => {\n const current = ++run\n if (query === null) {\n loading.value = false\n return\n }\n loading.value = true\n let items: MentionItem[] = []\n try {\n items = await props.search(query)\n } catch {\n // a failed search shows nothing\n }\n if (current !== run || !view.value || !getMention(view.value.state)) return\n loading.value = false\n setMentionItems(view.value, items.slice(0, 50))\n void nextTick(update)\n },\n { immediate: true },\n)\n\n// the editor keeps focus; the active item is announced through it\nwatch(\n () => [open.value, mention.value?.active, mention.value?.items] as const,\n () => {\n const dom = view.value?.dom\n if (!dom) return\n const items = mention.value?.items ?? []\n const option = open.value && items.length ? `${id}-${mention.value!.active}` : null\n if (open.value) dom.setAttribute('aria-controls', id)\n else dom.removeAttribute('aria-controls')\n if (option) dom.setAttribute('aria-activedescendant', option)\n else dom.removeAttribute('aria-activedescendant')\n if (option) document.getElementById(option)?.scrollIntoView({ block: 'nearest' })\n },\n { flush: 'post' },\n)\n\nfunction choose(item: MentionItem) {\n if (view.value) chooseMention(view.value, item)\n}\n\nfunction hover(index: number) {\n if (view.value && mention.value?.active !== index) setMentionActive(view.value, index)\n}\n</script>\n\n<template>\n <Transition name=\"dou-floating\">\n <div\n v-if=\"open\"\n :id=\"id\"\n ref=\"panel\"\n role=\"listbox\"\n :aria-label=\"messages.mention.label\"\n class=\"dou-dropdown dou-floating dou-editor-mention\"\n :style=\"[style, { zIndex }]\"\n :data-placement=\"placement\"\n @mousedown.prevent\n >\n <div\n v-for=\"(item, index) in mention!.items\"\n :id=\"`${id}-${index}`\"\n :key=\"item.id\"\n role=\"option\"\n :aria-selected=\"index === mention!.active\"\n :class=\"[\n 'dou-dropdown__item',\n 'dou-editor-mention__item',\n { 'is-active': index === mention!.active },\n ]\"\n @pointermove=\"hover(index)\"\n @click=\"choose(item)\"\n >\n <DouAvatar :src=\"item.avatar\" :name=\"item.label\" size=\"sm\" />\n <span class=\"dou-editor-mention__text\">\n <span class=\"dou-editor-mention__label\">{{ item.label }}</span>\n <span v-if=\"item.description\" class=\"dou-editor-mention__description\">{{\n item.description\n }}</span>\n </span>\n </div>\n <div v-if=\"!mention!.items.length\" class=\"dou-editor-slash__empty\">\n {{ loading ? messages.mention.searching : messages.mention.noResults }}\n </div>\n </div>\n </Transition>\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { isComposing } from "dou-ui";
|
|
2
|
+
import { Plugin, PluginKey, TextSelection } from "prosemirror-state";
|
|
3
|
+
// src/extensions/mention/state.ts
|
|
4
|
+
var mentionKey = new PluginKey("dou-editor-mention");
|
|
5
|
+
var TRIGGER = "@";
|
|
6
|
+
function getMention(state) {
|
|
7
|
+
return state ? mentionKey.getState(state) ?? null : null;
|
|
8
|
+
}
|
|
9
|
+
function setMeta(view, meta) {
|
|
10
|
+
view.dispatch(view.state.tr.setMeta(mentionKey, meta).setMeta("addToHistory", false));
|
|
11
|
+
}
|
|
12
|
+
var closeMention = (view) => setMeta(view, { close: true });
|
|
13
|
+
var setMentionItems = (view, items) => setMeta(view, { items });
|
|
14
|
+
var setMentionActive = (view, active) => setMeta(view, { active });
|
|
15
|
+
/** Replace the `@query` with a mention and a space after it. */
|
|
16
|
+
function chooseMention(view, item) {
|
|
17
|
+
const current = getMention(view.state);
|
|
18
|
+
if (!current) return;
|
|
19
|
+
const type = view.state.schema.nodes.mention;
|
|
20
|
+
const { head } = view.state.selection;
|
|
21
|
+
const tr = view.state.tr.replaceWith(current.from, head, [type.create({
|
|
22
|
+
id: item.id,
|
|
23
|
+
label: item.label
|
|
24
|
+
}), view.state.schema.text(" ")]);
|
|
25
|
+
tr.setSelection(TextSelection.create(tr.doc, current.from + 2));
|
|
26
|
+
view.dispatch(tr.setMeta(mentionKey, { close: true }).scrollIntoView());
|
|
27
|
+
view.focus();
|
|
28
|
+
}
|
|
29
|
+
function mentionPlugin() {
|
|
30
|
+
let opening = null;
|
|
31
|
+
return new Plugin({
|
|
32
|
+
key: mentionKey,
|
|
33
|
+
appendTransaction(_trs, _old, state) {
|
|
34
|
+
if (opening === null) return null;
|
|
35
|
+
const from = opening;
|
|
36
|
+
opening = null;
|
|
37
|
+
if (state.doc.textBetween(from, from + 1) !== TRIGGER) return null;
|
|
38
|
+
return state.tr.setMeta(mentionKey, { open: from });
|
|
39
|
+
},
|
|
40
|
+
state: {
|
|
41
|
+
init: () => null,
|
|
42
|
+
apply(tr, value, _old, state) {
|
|
43
|
+
const meta = tr.getMeta(mentionKey);
|
|
44
|
+
if (meta?.close) return null;
|
|
45
|
+
if (meta?.open !== void 0) return {
|
|
46
|
+
from: meta.open,
|
|
47
|
+
query: "",
|
|
48
|
+
items: [],
|
|
49
|
+
active: 0
|
|
50
|
+
};
|
|
51
|
+
if (!value) return null;
|
|
52
|
+
const from = tr.mapping.map(value.from);
|
|
53
|
+
const { $head, empty } = state.selection;
|
|
54
|
+
if (!empty || $head.pos <= from || $head.pos - from > 33) return null;
|
|
55
|
+
if (!state.doc.resolve(from).sameParent($head)) return null;
|
|
56
|
+
const text = state.doc.textBetween(from, $head.pos, "\n", "\n");
|
|
57
|
+
if (text[0] !== TRIGGER) return null;
|
|
58
|
+
const query = text.slice(1);
|
|
59
|
+
if (/\s/.test(query)) return null;
|
|
60
|
+
let { items, active } = value;
|
|
61
|
+
if (meta?.items) {
|
|
62
|
+
items = meta.items;
|
|
63
|
+
active = 0;
|
|
64
|
+
}
|
|
65
|
+
if (meta?.active !== void 0) active = meta.active;
|
|
66
|
+
if (from === value.from && query === value.query && items === value.items && active === value.active) return value;
|
|
67
|
+
return {
|
|
68
|
+
from,
|
|
69
|
+
query,
|
|
70
|
+
items,
|
|
71
|
+
active
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
props: {
|
|
76
|
+
handleTextInput(view, from, _to, text) {
|
|
77
|
+
if (text !== TRIGGER || getMention(view.state)) return false;
|
|
78
|
+
const $from = view.state.doc.resolve(from);
|
|
79
|
+
if (!$from.parent.isTextblock || $from.parent.type.spec.code) return false;
|
|
80
|
+
const before = $from.parent.textBetween(0, $from.parentOffset, "", "");
|
|
81
|
+
if (/[\w.+-]$/.test(before)) return false;
|
|
82
|
+
opening = from;
|
|
83
|
+
return false;
|
|
84
|
+
},
|
|
85
|
+
handleKeyDown(view, event) {
|
|
86
|
+
const current = getMention(view.state);
|
|
87
|
+
if (!current || isComposing(event)) return false;
|
|
88
|
+
const count = current.items.length;
|
|
89
|
+
switch (event.key) {
|
|
90
|
+
case "Escape":
|
|
91
|
+
closeMention(view);
|
|
92
|
+
return true;
|
|
93
|
+
case "ArrowDown":
|
|
94
|
+
case "ArrowUp":
|
|
95
|
+
if (!count) return false;
|
|
96
|
+
setMentionActive(view, (current.active + (event.key === "ArrowDown" ? 1 : -1) + count) % count);
|
|
97
|
+
return true;
|
|
98
|
+
case "Enter":
|
|
99
|
+
case "Tab": {
|
|
100
|
+
const item = current.items[current.active];
|
|
101
|
+
if (!item) return false;
|
|
102
|
+
chooseMention(view, item);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
default: return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export { chooseMention, closeMention, getMention, mentionKey, mentionPlugin, setMentionActive, setMentionItems };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","names":[],"sources":["../../../src/extensions/mention/state.ts"],"sourcesContent":["import { isComposing } from 'dou-ui'\nimport { Plugin, PluginKey, TextSelection, type EditorState } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\n\n/** Someone (or something) that can be mentioned. */\nexport interface MentionItem {\n /** Stored in the document. */\n id: string\n /** Shown after the `@`, and stored too so the mention reads without a lookup. */\n label: string\n /** A second line in the suggestions, e.g. a team or an email. */\n description?: string\n /** An image shown in the suggestions. */\n avatar?: string\n}\n\n/** The suggestions: open from the `@` at `from` to the cursor, with what was typed as the query. */\nexport interface MentionState {\n from: number\n query: string\n items: MentionItem[]\n active: number\n}\n\ninterface MentionMeta {\n open?: number\n close?: true\n items?: MentionItem[]\n active?: number\n}\n\nexport const mentionKey = new PluginKey<MentionState | null>('dou-editor-mention')\n\nconst TRIGGER = '@'\nconst MAX_QUERY = 32\n\nexport function getMention(state: EditorState | null): MentionState | null {\n return state ? (mentionKey.getState(state) ?? null) : null\n}\n\nfunction setMeta(view: EditorView, meta: MentionMeta) {\n view.dispatch(view.state.tr.setMeta(mentionKey, meta).setMeta('addToHistory', false))\n}\n\nexport const closeMention = (view: EditorView) => setMeta(view, { close: true })\nexport const setMentionItems = (view: EditorView, items: MentionItem[]) => setMeta(view, { items })\nexport const setMentionActive = (view: EditorView, active: number) => setMeta(view, { active })\n\n/** Replace the `@query` with a mention and a space after it. */\nexport function chooseMention(view: EditorView, item: MentionItem) {\n const current = getMention(view.state)\n if (!current) return\n const type = view.state.schema.nodes.mention!\n const { head } = view.state.selection\n const tr = view.state.tr.replaceWith(current.from, head, [\n type.create({ id: item.id, label: item.label }),\n view.state.schema.text(' '),\n ])\n tr.setSelection(TextSelection.create(tr.doc, current.from + 2))\n view.dispatch(tr.setMeta(mentionKey, { close: true } satisfies MentionMeta).scrollIntoView())\n view.focus()\n}\n\nexport function mentionPlugin(): Plugin<MentionState | null> {\n let opening: number | null = null\n return new Plugin<MentionState | null>({\n key: mentionKey,\n // the browser types the `@` itself; the suggestions open once it is in\n appendTransaction(_trs, _old, state) {\n if (opening === null) return null\n const from = opening\n opening = null\n if (state.doc.textBetween(from, from + 1) !== TRIGGER) return null\n return state.tr.setMeta(mentionKey, { open: from } satisfies MentionMeta)\n },\n state: {\n init: () => null,\n apply(tr, value, _old, state) {\n const meta = tr.getMeta(mentionKey) as MentionMeta | undefined\n if (meta?.close) return null\n if (meta?.open !== undefined) return { from: meta.open, query: '', items: [], active: 0 }\n if (!value) return null\n const from = tr.mapping.map(value.from)\n const { $head, empty } = state.selection\n // close when the cursor leaves the query, or the `@` is deleted\n if (!empty || $head.pos <= from || $head.pos - from > MAX_QUERY + 1) return null\n if (!state.doc.resolve(from).sameParent($head)) return null\n const text = state.doc.textBetween(from, $head.pos, '\\n', '\\n')\n if (text[0] !== TRIGGER) return null\n const query = text.slice(1)\n if (/\\s/.test(query)) return null\n let { items, active } = value\n if (meta?.items) {\n items = meta.items\n active = 0\n }\n if (meta?.active !== undefined) active = meta.active\n if (\n from === value.from &&\n query === value.query &&\n items === value.items &&\n active === value.active\n )\n return value\n return { from, query, items, active }\n },\n },\n props: {\n handleTextInput(view, from, _to, text) {\n if (text !== TRIGGER || getMention(view.state)) return false\n const $from = view.state.doc.resolve(from)\n if (!$from.parent.isTextblock || $from.parent.type.spec.code) return false\n const before = $from.parent.textBetween(0, $from.parentOffset, '', '')\n // not inside a word or an email address\n if (/[\\w.+-]$/.test(before)) return false\n opening = from\n return false\n },\n handleKeyDown(view, event) {\n const current = getMention(view.state)\n if (!current || isComposing(event)) return false\n const count = current.items.length\n switch (event.key) {\n case 'Escape':\n closeMention(view)\n return true\n case 'ArrowDown':\n case 'ArrowUp':\n if (!count) return false\n setMentionActive(\n view,\n (current.active + (event.key === 'ArrowDown' ? 1 : -1) + count) % count,\n )\n return true\n case 'Enter':\n case 'Tab': {\n const item = current.items[current.active]\n if (!item) return false\n chooseMention(view, item)\n return true\n }\n default:\n return false\n }\n },\n },\n })\n}\n"],"mappings":"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"}
|