wendang-react 0.6.3 → 0.6.5

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.
Files changed (120) hide show
  1. package/NOTICE.md +1 -1
  2. package/README.md +133 -1
  3. package/dist/bindings/yjs.js +1 -5
  4. package/dist/chunks/AnchoredPanel-C7jMoPze.js +1 -0
  5. package/dist/chunks/DocumentExportMenu-CUMsgq3w.js +2 -0
  6. package/dist/chunks/Dropdown-D98tXfe0.js +3 -0
  7. package/dist/chunks/MindMapCanvas-C46Q5JBU.js +163 -0
  8. package/dist/chunks/MindMapViewer-C9zxZIW8.js +13 -0
  9. package/dist/chunks/SpreadsheetViewer-CoQ4VIgj.js +23 -0
  10. package/dist/chunks/code-highlight-BmaqTe1m.js +1 -30
  11. package/dist/chunks/code-highlight-engine-BFDzDPzy.js +1 -40
  12. package/dist/chunks/comment-hint-BhUxKjKK.js +14 -0
  13. package/dist/chunks/composition-state-Bl-U1R_2.js +1 -40
  14. package/dist/chunks/extensions-BefQ1tDm.js +14 -0
  15. package/dist/chunks/fuzzy-match-DYQR-4w2.js +1 -89
  16. package/dist/chunks/hooks-C53Dp0pr.js +1 -35
  17. package/dist/chunks/i18n-B0kCsIjc.js +2 -0
  18. package/dist/chunks/index-COeA2QcQ.js +15 -0
  19. package/dist/chunks/katex-WjrodtTU.js +1 -71
  20. package/dist/chunks/locale-DWpIJ_9X.js +1 -0
  21. package/dist/chunks/mind-map-controller-Ce76mETT.js +1 -0
  22. package/dist/chunks/mind-map-delete-CVsxdXfs.js +1 -31
  23. package/dist/chunks/mind-map-input-CEWRz_t6.js +1 -43
  24. package/dist/chunks/mind-topic-text-BHXkxexr.js +30 -0
  25. package/dist/chunks/popover-placement-D3xVYdkt.js +1 -47
  26. package/dist/chunks/worksheet-engine-c84sdD4N.js +1 -0
  27. package/dist/chunks/worksheet-preview-DsDVWxIM.js +1 -129
  28. package/dist/document/viewer.js +30 -289
  29. package/dist/document.js +120 -3811
  30. package/dist/export/json.js +1 -5
  31. package/dist/export/markdown.js +1 -2
  32. package/dist/export/mindmap.js +1 -2
  33. package/dist/export/pdf.js +1 -6
  34. package/dist/export/word.js +1 -2
  35. package/dist/export.js +1 -2
  36. package/dist/i18n.js +1 -0
  37. package/dist/index.js +1 -8
  38. package/dist/locales/en-US.js +1 -0
  39. package/dist/locales/es-ES.js +1 -0
  40. package/dist/locales/ja-JP.js +1 -0
  41. package/dist/locales/ko-KR.js +1 -0
  42. package/dist/locales/zh-CN.js +1 -0
  43. package/dist/locales/zh-TW.js +1 -0
  44. package/dist/mindmap/viewer.js +1 -6
  45. package/dist/mindmap.js +3 -123
  46. package/dist/shared.js +1 -52
  47. package/dist/spreadsheet/viewer.js +1 -5
  48. package/dist/spreadsheet/xlsx.js +1 -299
  49. package/dist/spreadsheet.js +2 -120
  50. package/dist/styles/callout.css +1 -34
  51. package/dist/styles/code-block.css +1 -58
  52. package/dist/styles/content-style.css +1 -619
  53. package/dist/styles/content-tokens.css +1 -29
  54. package/dist/styles/editor-interaction.css +1 -621
  55. package/dist/styles/file-video.css +1 -12
  56. package/dist/styles/image-preview.css +1 -103
  57. package/dist/styles/mind-map-block.css +1 -14
  58. package/dist/styles/mind-map-canvas.css +1 -80
  59. package/dist/styles/mind-map-comments.css +1 -93
  60. package/dist/styles/mind-map-details.css +1 -7
  61. package/dist/styles/mind-map-preview.css +1 -7
  62. package/dist/styles/mind-map-topic.css +1 -112
  63. package/dist/styles/page-picker.css +1 -47
  64. package/dist/styles/renderer.css +1 -45
  65. package/dist/styles/spreadsheet-editor.css +1 -3
  66. package/dist/styles/tokens.css +1 -142
  67. package/dist/styles/video-dialog.css +1 -41
  68. package/dist/styles/video-embed.css +1 -35
  69. package/dist/styles.css +1 -2655
  70. package/dist/types/editor/BlockAffordance.d.ts +5 -2
  71. package/dist/types/editor/CalloutView.d.ts +2 -1
  72. package/dist/types/editor/ImagePreview.d.ts +1 -1
  73. package/dist/types/editor/PagePicker.d.ts +1 -1
  74. package/dist/types/editor/extensions.d.ts +4 -1
  75. package/dist/types/engine/editor/block-commands.d.ts +1 -1
  76. package/dist/types/engine/editor/block-menu-hover.d.ts +16 -0
  77. package/dist/types/engine/editor/extensions/locale.d.ts +3 -0
  78. package/dist/types/engine/editor/nodes/image-nodeview.d.ts +1 -1
  79. package/dist/types/engine/editor/video-block-controls.d.ts +13 -0
  80. package/dist/types/engine/host.d.ts +60 -2
  81. package/dist/types/engine/renderer/attachments.d.ts +3 -3
  82. package/dist/types/engine/shared/video-feedback.d.ts +1 -1
  83. package/dist/types/engine/worksheets/worksheet-engine.d.ts +1 -0
  84. package/dist/types/engine/worksheets/worksheet-xlsx.d.ts +6 -1
  85. package/dist/types/host-types.d.ts +2 -60
  86. package/dist/types/host.d.ts +8 -1
  87. package/dist/types/i18n.d.ts +3 -0
  88. package/dist/types/index.d.ts +2 -0
  89. package/dist/types/locales/en-US.d.ts +1 -0
  90. package/dist/types/locales/es-ES.d.ts +1 -0
  91. package/dist/types/locales/ja-JP.d.ts +1 -0
  92. package/dist/types/locales/ko-KR.d.ts +1 -0
  93. package/dist/types/locales/zh-CN.d.ts +1 -0
  94. package/dist/types/locales/zh-TW.d.ts +1 -0
  95. package/dist/types/renderer/media.d.ts +2 -1
  96. package/dist/types/spreadsheet/xlsx.d.ts +1 -1
  97. package/dist/types/wui/Dropdown.d.ts +2 -1
  98. package/dist/types/wui/index.d.ts +2 -1
  99. package/integrity.json +3140 -0
  100. package/package.json +15 -5
  101. package/verify-integrity.mjs +138 -0
  102. package/dist/chunks/AnchoredPanel-Cv4RURwF.js +0 -68
  103. package/dist/chunks/DocumentExportMenu-BrGipZ59.js +0 -75
  104. package/dist/chunks/MindMapCanvas-tkFBBs4g.js +0 -2090
  105. package/dist/chunks/MindMapViewer-DJrryb2Y.js +0 -180
  106. package/dist/chunks/SpreadsheetViewer-DxCxIUx_.js +0 -163
  107. package/dist/chunks/comment-hint-B9qcrqvn.js +0 -296
  108. package/dist/chunks/extensions-ByORRoRa.js +0 -4210
  109. package/dist/chunks/host-C-V688Rq.js +0 -197
  110. package/dist/chunks/index-DdfMnva8.js +0 -726
  111. package/dist/chunks/mind-map-controller-B6SxK3GZ.js +0 -479
  112. package/dist/chunks/mind-topic-text-DSyb95vL.js +0 -446
  113. package/dist/chunks/nodes-iKqTGr_N.js +0 -291
  114. package/dist/chunks/worksheet-engine-TJ54m0KC.js +0 -125
  115. package/third-party-notices/wendang-export@0.6.0-NOTICE.md.txt +0 -51
  116. package/third-party-notices/wendang-export@0.6.1-LICENSE.txt +0 -21
  117. package/third-party-notices/wendang-export@0.6.2-LICENSE.txt +0 -21
  118. package/third-party-notices/wendang-export@0.6.2-NOTICE.md.txt +0 -70
  119. /package/third-party-notices/{wendang-export@0.6.0-LICENSE.txt → wendang-export@0.6.4-LICENSE.txt} +0 -0
  120. /package/third-party-notices/{wendang-export@0.6.1-NOTICE.md.txt → wendang-export@0.6.4-NOTICE.md.txt} +0 -0
package/dist/document.js CHANGED
@@ -1,3811 +1,120 @@
1
- "use client";
2
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
- import { getMarkRange, Editor, getSchema } from "@tiptap/core";
4
- import { TextSelection, Selection, NodeSelection } from "@tiptap/pm/state";
5
- import { createPortal } from "react-dom";
6
- import { useRef, useState, useLayoutEffect, useEffect, useMemo, useId, forwardRef, useCallback, useImperativeHandle } from "react";
7
- import { ContentSchemaService, requiredSchemaVersion, SCHEMA_VERSION } from "wendang/schema";
8
- import { safeHref, TEXT_COLORS, HIGHLIGHT_COLORS, documentColorAttributes, parseVideoUrl } from "wendang/shared";
9
- import { b as buildColumnsJson, d as dismissSlash, u as unwrapCallout, h as highlightMenuBlock, T as TEXT_ALIGNS, c as currentTableCellPosition, r as readTableGeometry, t as tableHeaderActive, a as tablePositionAtCell, s as selectTableAxis, i as insertTableBoundary, e as CELL_ALIGNS, f as cellPositionAtDOM, g as selectTableCell, j as clippedTableSelection, k as tableSelectionMarkActive, l as setTableSelectionMark, m as setTableHeaderBackground, n as currentCellRect, o as setColumnWidth, p as autoFitColumn, q as distributeTableColumns, v as getSelectedBlocks, B as BlockRangeSelection, w as setBlockRangeSelection, x as setImageRangeSelection, y as pastedImageFiles, z as pastedRemoteImageUrls, A as coreExtensions, D as refreshInlineComments, E as addUploadPlaceholder, F as currentPlaceholderPos, G as insertUploadedBlock, H as removeUploadPlaceholder, I as replacePlaceholderWithNode } from "./chunks/extensions-ByORRoRa.js";
10
- import { C } from "./chunks/extensions-ByORRoRa.js";
11
- import { a as useContentContext, p as primeAttachment, u as unavailableAttachment } from "./chunks/host-C-V688Rq.js";
12
- import { c as createMindMapController } from "./chunks/mind-map-controller-B6SxK3GZ.js";
13
- import { M as MindMapBlock } from "./chunks/MindMapViewer-DJrryb2Y.js";
14
- import { p as previewItemsFrom, I as ImagePreview, V as VideoView } from "./chunks/mind-topic-text-DSyb95vL.js";
15
- import { u as useLatest, a as useReady, b as asError } from "./chunks/hooks-C53Dp0pr.js";
16
- import { a as WButton, b as WIcon, p as placePanel, l as lockScroll, t as trapTab, c as WDialog } from "./chunks/index-DdfMnva8.js";
17
- import { p as placeAnchoredPopover } from "./chunks/popover-placement-D3xVYdkt.js";
18
- import { A, a, b } from "./chunks/popover-placement-D3xVYdkt.js";
19
- import { isSafeHref } from "wendang/internal/safe-href";
20
- import { createMindMap } from "wendang/internal/mind-map";
21
- import { s as shouldIgnoreForIme } from "./chunks/composition-state-Bl-U1R_2.js";
22
- import { DOMSerializer, Fragment as Fragment$1 } from "@tiptap/pm/model";
23
- import { createTable } from "@tiptap/extension-table";
24
- import { createMindMap as createMindMap$1 } from "wendang/mindmap";
25
- import { closeHistory } from "@tiptap/pm/history";
26
- import { yUndoPluginKey, ySyncPluginKey } from "@tiptap/y-tiptap";
27
- import { CellSelection, TableMap } from "@tiptap/pm/tables";
28
- import { CELL_BACKGROUNDS } from "wendang/internal/table-cell-format";
29
- import { r as rankPages } from "./chunks/fuzzy-match-DYQR-4w2.js";
30
- import { inspectVideoUrl, VIDEO_PROVIDER_LABELS, videoSourceUrl } from "wendang/internal/video-embed";
31
- import { D } from "./chunks/DocumentExportMenu-BrGipZ59.js";
32
- function linkHrefProblem(value) {
33
- const href = value.trim();
34
- if (!href) return "请输入链接地址。";
35
- if (href.length > 2048 || /[\u0000-\u0020\u007f]/.test(href) || !isSafeHref(href)) {
36
- return "请输入有效的网页、邮箱或站内链接。";
37
- }
38
- if (/^https?:/i.test(href)) {
39
- try {
40
- const url = new URL(href);
41
- if (!/^https?:\/\//i.test(href) || !url.hostname) return "请输入完整的网址,例如 https://example.com。";
42
- } catch {
43
- return "请输入完整的网址,例如 https://example.com。";
44
- }
45
- }
46
- if (/^mailto:$/i.test(href)) return "请输入完整的邮箱链接,例如 mailto:name@example.com。";
47
- return "";
48
- }
49
- function createLinkEditTarget(state, selection = state.selection) {
50
- if (!(selection instanceof TextSelection)) return null;
51
- const link = state.schema.marks.link;
52
- if (!link) return null;
53
- let { from, to } = selection;
54
- const range = getMarkRange(selection.$from, link);
55
- if (range && to <= range.to) ({ from, to } = range);
56
- if (from === to) return null;
57
- let href = "";
58
- state.doc.nodesBetween(from, to, (node) => {
59
- var _a;
60
- const value = (_a = node.marks.find((mark) => mark.type === link)) == null ? void 0 : _a.attrs.href;
61
- if (!href && typeof value === "string") href = value;
62
- });
63
- return {
64
- from,
65
- to,
66
- valid: true,
67
- href,
68
- hasLink: state.doc.rangeHasMark(from, to, link),
69
- contentJSON: JSON.stringify(state.doc.slice(from, to).toJSON())
70
- };
71
- }
72
- function linkEditTargetCurrent(state, target) {
73
- return target.valid && target.from < target.to && target.from >= 0 && target.to <= state.doc.content.size && JSON.stringify(state.doc.slice(target.from, target.to).toJSON()) === target.contentJSON;
74
- }
75
- function LinkPopover({ target, onEdit, onSave, onRemove, onClose }) {
76
- const { portal } = useContentContext(), root = useRef(null);
77
- const [value, setValue] = useState(target.href), [error, setError] = useState(""), [geometry, setGeometry] = useState({ left: target.anchor.left, top: target.anchor.top + target.anchor.height + 8 });
78
- useLayoutEffect(() => {
79
- const measure = () => {
80
- var _a;
81
- const rect = (_a = root.current) == null ? void 0 : _a.getBoundingClientRect();
82
- if (!rect) return;
83
- setGeometry(placeAnchoredPopover({ align: target.mode === "preview" ? "center" : "start", prefer: target.mode === "preview" ? "above" : "below", anchor: target.anchor, host: { left: 0, top: 0 }, card: rect, bounds: { left: 0, top: 0, width: innerWidth, height: innerHeight } }));
84
- };
85
- measure();
86
- const observer = new ResizeObserver(measure);
87
- if (root.current) observer.observe(root.current);
88
- return () => observer.disconnect();
89
- }, [target.mode, target.anchor, portal]);
90
- useEffect(() => {
91
- const outside = (event) => {
92
- var _a;
93
- if (event.target instanceof Node && !((_a = root.current) == null ? void 0 : _a.contains(event.target))) onClose();
94
- };
95
- const escape = (event) => {
96
- if (event.key === "Escape") {
97
- event.stopPropagation();
98
- onClose();
99
- }
100
- };
101
- document.addEventListener("pointerdown", outside);
102
- document.addEventListener("keydown", escape);
103
- window.addEventListener("scroll", onClose, true);
104
- return () => {
105
- document.removeEventListener("pointerdown", outside);
106
- document.removeEventListener("keydown", escape);
107
- window.removeEventListener("scroll", onClose, true);
108
- };
109
- }, []);
110
- useEffect(() => {
111
- if (target.mode !== "preview") return;
112
- let timer;
113
- const move = (event) => {
114
- var _a;
115
- const a2 = target.anchor;
116
- const inside = ((_a = root.current) == null ? void 0 : _a.contains(event.target)) || event.clientX >= a2.left - 5 && event.clientX <= a2.left + a2.width + 5 && event.clientY >= a2.top - 5 && event.clientY <= a2.top + a2.height + 5;
117
- if (inside) {
118
- clearTimeout(timer);
119
- timer = void 0;
120
- } else if (!timer) timer = setTimeout(onClose, 180);
121
- };
122
- document.addEventListener("mousemove", move);
123
- return () => {
124
- clearTimeout(timer);
125
- document.removeEventListener("mousemove", move);
126
- };
127
- }, [target.mode, target.anchor, onClose]);
128
- if (!portal) return null;
129
- return createPortal(/* @__PURE__ */ jsx("div", { ref: root, className: "wr-link-popover", style: geometry, role: target.mode === "edit" ? "dialog" : "toolbar", "aria-label": target.mode === "edit" ? "编辑链接" : "链接预览", children: target.mode === "preview" ? /* @__PURE__ */ jsxs("div", { className: "wr-toolbar", children: [
130
- /* @__PURE__ */ jsx("a", { href: safeHref(target.href), target: "_blank", rel: "noopener noreferrer", children: target.href }),
131
- /* @__PURE__ */ jsx(WButton, { onClick: onEdit, children: "编辑链接" }),
132
- /* @__PURE__ */ jsx(WButton, { onClick: onRemove, children: "取消链接" })
133
- ] }) : /* @__PURE__ */ jsxs("form", { onSubmit: (event) => {
134
- event.preventDefault();
135
- const problem = linkHrefProblem(value);
136
- if (problem) {
137
- setError(problem);
138
- return;
139
- }
140
- onSave(value.trim());
141
- }, children: [
142
- /* @__PURE__ */ jsxs("div", { className: "wr-toolbar", children: [
143
- /* @__PURE__ */ jsx("input", { autoFocus: true, "aria-label": "链接地址", value, onChange: (event) => {
144
- setValue(event.target.value);
145
- setError("");
146
- }, placeholder: "粘贴或输入链接" }),
147
- /* @__PURE__ */ jsx(WButton, { htmlType: "submit", children: "确认" }),
148
- /* @__PURE__ */ jsx(WButton, { onClick: onRemove, children: "取消链接" })
149
- ] }),
150
- error && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
151
- ] }) }), portal);
152
- }
153
- function DocumentColorPanel({ textColor, highlightColor, onTextColor, onHighlightColor, onReset, onClose }) {
154
- return /* @__PURE__ */ jsxs("div", { className: "document-color-panel", role: "group", "aria-label": "文档颜色", onMouseDown: (event) => event.preventDefault(), children: [
155
- /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
156
- /* @__PURE__ */ jsx("div", { className: "color-label", children: "字体颜色" }),
157
- /* @__PURE__ */ jsx("div", { className: "color-grid", role: "group", "aria-label": "字体颜色", children: TEXT_COLORS.map((color) => /* @__PURE__ */ jsx(
158
- "button",
159
- {
160
- type: "button",
161
- className: `color-choice${textColor === color.value ? " selected" : ""}`,
162
- "aria-label": `字体颜色:${color.name}`,
163
- title: `字体颜色:${color.name}`,
164
- "aria-pressed": textColor === color.value,
165
- onClick: () => onTextColor(color.value),
166
- children: /* @__PURE__ */ jsx("span", { className: "font-sample", style: textColorStyle(color.value), children: "A" })
167
- },
168
- color.name
169
- )) })
170
- ] }),
171
- /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
172
- /* @__PURE__ */ jsx("div", { className: "color-label", children: "背景颜色" }),
173
- /* @__PURE__ */ jsx("div", { className: "color-grid", role: "group", "aria-label": "背景颜色", children: HIGHLIGHT_COLORS.map((color) => /* @__PURE__ */ jsx(
174
- "button",
175
- {
176
- type: "button",
177
- className: `color-choice background-choice${highlightColor === color.value ? " selected" : ""}${color.value === null ? " no-background" : ""}`,
178
- style: color.value ? { background: color.value } : void 0,
179
- "aria-label": `背景颜色:${color.name}`,
180
- title: `背景颜色:${color.name}`,
181
- "aria-pressed": highlightColor === color.value,
182
- onClick: () => onHighlightColor(color.value),
183
- children: color.value === null && /* @__PURE__ */ jsx("span", { className: "clear-stroke", "aria-hidden": "true" })
184
- },
185
- color.name
186
- )) })
187
- ] }),
188
- /* @__PURE__ */ jsx(WButton, { variant: "outline", block: true, onClick: onReset, children: "恢复默认" }),
189
- onClose && /* @__PURE__ */ jsx(WButton, { variant: "text", block: true, onClick: onClose, children: "关闭" })
190
- ] });
191
- }
192
- function textColorStyle(value) {
193
- const css = documentColorAttributes("text", value).style;
194
- if (!css) return {};
195
- const index = css.indexOf(":");
196
- return { color: css.slice(index + 1).trim() };
197
- }
198
- function SelectionToolbar({ editor, x, y, canComment, onRequestComment, onRequestLink, onRemoveLink }) {
199
- const [panel, setPanel] = useState("none");
200
- const active = (name) => editor.isActive(name);
201
- const textColor = typeof editor.getAttributes("textStyle").color === "string" ? editor.getAttributes("textStyle").color : null;
202
- const highlight = typeof editor.getAttributes("highlight").color === "string" ? editor.getAttributes("highlight").color : null;
203
- const hasLink = active("link") || (() => {
204
- const { from, to } = editor.state.selection;
205
- try {
206
- return editor.state.doc.rangeHasMark(from, to, editor.schema.marks.link);
207
- } catch {
208
- return false;
209
- }
210
- })();
211
- const chain = () => editor.chain().focus();
212
- function toggleMark(name) {
213
- const c = chain();
214
- if (name === "bold") c.toggleBold().run();
215
- else if (name === "italic") c.toggleItalic().run();
216
- else if (name === "strike") c.toggleStrike().run();
217
- else if (name === "underline") c.toggleUnderline().run();
218
- else c.toggleCode().run();
219
- setPanel("none");
220
- }
221
- const marks = [
222
- { name: "bold", icon: "bold", label: "加粗" },
223
- { name: "italic", icon: "italic", label: "斜体" },
224
- { name: "strike", icon: "strike", label: "删除线" },
225
- { name: "underline", icon: "underline", label: "下划线" },
226
- { name: "code", icon: "inline-code", label: "行内代码" }
227
- ];
228
- return /* @__PURE__ */ jsxs(
229
- "div",
230
- {
231
- className: "sel-toolbar",
232
- role: "toolbar",
233
- "aria-label": "文字格式",
234
- style: { left: x, top: y },
235
- onMouseDown: (event) => event.preventDefault(),
236
- children: [
237
- marks.map((mark) => /* @__PURE__ */ jsx(
238
- "button",
239
- {
240
- type: "button",
241
- className: `tb-btn${active(mark.name) ? " on" : ""}`,
242
- "aria-label": mark.label,
243
- "aria-pressed": active(mark.name),
244
- title: mark.label,
245
- onClick: () => toggleMark(mark.name),
246
- children: /* @__PURE__ */ jsx(WIcon, { name: mark.icon, size: 20 })
247
- },
248
- mark.name
249
- )),
250
- /* @__PURE__ */ jsx(
251
- "button",
252
- {
253
- type: "button",
254
- className: `tb-btn${hasLink ? " on" : ""}`,
255
- "aria-label": hasLink ? "编辑链接" : "添加链接",
256
- title: hasLink ? "编辑链接" : "添加链接",
257
- "aria-haspopup": "dialog",
258
- onClick: () => onRequestLink == null ? void 0 : onRequestLink(),
259
- children: /* @__PURE__ */ jsx(WIcon, { name: "format-link", size: 20 })
260
- }
261
- ),
262
- hasLink && /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn", "aria-label": "取消链接", title: "取消链接", onClick: () => onRemoveLink == null ? void 0 : onRemoveLink(), children: /* @__PURE__ */ jsx(WIcon, { name: "unlink", size: 20 }) }),
263
- /* @__PURE__ */ jsx("span", { className: "tb-sep", "aria-hidden": "true" }),
264
- /* @__PURE__ */ jsxs("div", { className: "tb-color-wrap", children: [
265
- /* @__PURE__ */ jsxs(
266
- "button",
267
- {
268
- type: "button",
269
- className: `tb-btn${panel === "color" ? " on" : ""}`,
270
- title: "文字与背景颜色",
271
- "aria-label": "文字与背景颜色",
272
- "aria-expanded": panel === "color",
273
- onClick: () => setPanel(panel === "color" ? "none" : "color"),
274
- children: [
275
- /* @__PURE__ */ jsx("span", { className: "color-letter", style: colorLetterStyle(textColor), children: "A" }),
276
- /* @__PURE__ */ jsx(WIcon, { name: "chevron-down", size: 12, className: "tb-caret" })
277
- ]
278
- }
279
- ),
280
- panel === "color" && /* @__PURE__ */ jsx("div", { className: "tb-panel", children: /* @__PURE__ */ jsx(
281
- DocumentColorPanel,
282
- {
283
- textColor,
284
- highlightColor: highlight,
285
- onTextColor: (value) => {
286
- const c = chain();
287
- value ? c.setMark("textStyle", { color: value }).run() : c.unsetMark("textStyle").run();
288
- },
289
- onHighlightColor: (value) => {
290
- const c = chain();
291
- value ? c.setMark("highlight", { color: value }).run() : c.unsetMark("highlight").run();
292
- },
293
- onReset: () => {
294
- chain().unsetMark("textStyle").unsetMark("highlight").run();
295
- setPanel("none");
296
- },
297
- onClose: () => setPanel("none")
298
- }
299
- ) })
300
- ] }),
301
- canComment && /* @__PURE__ */ jsxs(Fragment, { children: [
302
- /* @__PURE__ */ jsx("span", { className: "tb-sep", "aria-hidden": "true" }),
303
- /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn", "aria-label": "评论选中的文字", title: "评论选中的文字", onClick: () => onRequestComment == null ? void 0 : onRequestComment(), children: /* @__PURE__ */ jsx(WIcon, { name: "comment", size: 20 }) })
304
- ] })
305
- ]
306
- }
307
- );
308
- }
309
- function colorLetterStyle(value) {
310
- const data = documentColorAttributes("text", value);
311
- const css = data.style;
312
- if (!css) return {};
313
- const index = css.indexOf(":");
314
- return { [css.slice(0, index).trim()]: css.slice(index + 1).trim() };
315
- }
316
- const MAX_GRID = 8;
317
- const MIN_GRID = 1;
318
- const MAX_COLUMN_COUNT = 5;
319
- function buildGridCells(hoverRows, hoverCols) {
320
- const cells = [];
321
- for (let row = 1; row <= MAX_GRID; row += 1) {
322
- for (let col = 1; col <= MAX_GRID; col += 1) {
323
- cells.push({ row, col, on: row <= hoverRows && col <= hoverCols });
324
- }
325
- }
326
- return cells;
327
- }
328
- function clampGrid(value) {
329
- const n = Math.floor(Number(value));
330
- if (Number.isNaN(n)) return MIN_GRID;
331
- return Math.min(Math.max(n, MIN_GRID), MAX_GRID);
332
- }
333
- function computePanelTop(preferredTop, height, min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY, preferredBottom = preferredTop + height) {
334
- const fits = (t) => (!Number.isFinite(min) || t >= min) && (!Number.isFinite(max) || t + height <= max);
335
- const topAligned = preferredTop;
336
- if (fits(topAligned)) return Math.round(topAligned);
337
- const bottomAligned = preferredBottom - height;
338
- if (fits(bottomAligned)) return Math.round(bottomAligned);
339
- return Math.round(fitRange(topAligned, height, min, max));
340
- }
341
- function fitRange(top, height, min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY) {
342
- let t = top;
343
- if (Number.isFinite(max) && t + height > max) t = max - height;
344
- if (Number.isFinite(min) && t < min) t = min;
345
- return Math.round(t);
346
- }
347
- const BLOCK_COMMANDS = [
348
- {
349
- id: "text",
350
- label: "正文",
351
- hint: "/text",
352
- group: "basic",
353
- icon: "text",
354
- convertible: true,
355
- keywords: ["text", "paragraph", "正文", "zhengwen", "普通文本"],
356
- run: ({ chain }) => chain().setParagraph().run()
357
- },
358
- // ---------------------------------------------------------------- 基础
359
- {
360
- id: "h1",
361
- convertible: true,
362
- label: "一级标题",
363
- hint: "/h1",
364
- group: "basic",
365
- icon: "h1",
366
- keywords: ["h1", "标题", "biaoti", "heading", "yijibiaoti"],
367
- run: ({ chain }) => chain().setHeading({ level: 1 }).run()
368
- },
369
- {
370
- id: "h2",
371
- convertible: true,
372
- label: "二级标题",
373
- hint: "/h2",
374
- group: "basic",
375
- icon: "h2",
376
- keywords: ["h2", "标题", "biaoti", "erjibiaoti"],
377
- run: ({ chain }) => chain().setHeading({ level: 2 }).run()
378
- },
379
- {
380
- id: "h3",
381
- convertible: true,
382
- label: "三级标题",
383
- hint: "/h3",
384
- group: "basic",
385
- icon: "h3",
386
- keywords: ["h3", "标题", "biaoti", "sanjibiaoti"],
387
- run: ({ chain }) => chain().setHeading({ level: 3 }).run()
388
- },
389
- {
390
- id: "h4",
391
- convertible: true,
392
- label: "四级标题",
393
- hint: "/h4",
394
- group: "basic",
395
- icon: "h4",
396
- keywords: ["h4", "标题", "biaoti", "sijibiaoti"],
397
- run: ({ chain }) => chain().setHeading({ level: 4 }).run()
398
- },
399
- {
400
- id: "h5",
401
- convertible: true,
402
- label: "五级标题",
403
- hint: "/h5",
404
- group: "basic",
405
- icon: "h5",
406
- keywords: ["h5", "标题", "biaoti", "wujibiaoti"],
407
- run: ({ chain }) => chain().setHeading({ level: 5 }).run()
408
- },
409
- {
410
- id: "h6",
411
- convertible: true,
412
- label: "六级标题",
413
- hint: "/h6",
414
- group: "basic",
415
- icon: "h6",
416
- keywords: ["h6", "标题", "biaoti", "liujibiaoti"],
417
- run: ({ chain }) => chain().setHeading({ level: 6 }).run()
418
- },
419
- {
420
- id: "ordered",
421
- convertible: true,
422
- label: "有序列表",
423
- hint: "/ol",
424
- group: "basic",
425
- icon: "ordered-list",
426
- keywords: ["list", "ol", "列表", "liebiao", "youxuliebiao"],
427
- run: ({ chain }) => chain().toggleOrderedList().run()
428
- },
429
- {
430
- id: "bullet",
431
- convertible: true,
432
- label: "无序列表",
433
- hint: "/ul",
434
- group: "basic",
435
- icon: "bullet-list",
436
- keywords: ["list", "ul", "列表", "liebiao", "wuxuliebiao"],
437
- run: ({ chain }) => chain().toggleBulletList().run()
438
- },
439
- {
440
- id: "quote",
441
- convertible: true,
442
- label: "引用",
443
- hint: "/quote",
444
- group: "basic",
445
- icon: "quote",
446
- keywords: ["quote", "引用", "yinyong"],
447
- run: ({ chain }) => chain().toggleBlockquote().run()
448
- },
449
- {
450
- id: "code",
451
- convertible: true,
452
- label: "代码块",
453
- hint: "/code",
454
- group: "basic",
455
- icon: "code",
456
- keywords: ["code", "代码", "daima", "daimakuai"],
457
- run: ({ chain }) => chain().toggleCodeBlock().run()
458
- },
459
- {
460
- id: "hr",
461
- label: "分割线",
462
- hint: "/hr",
463
- group: "basic",
464
- icon: "divider",
465
- keywords: ["hr", "divider", "分割线", "fengexian"],
466
- run: ({ chain }) => chain().setHorizontalRule().run()
467
- },
468
- {
469
- id: "link",
470
- label: "链接",
471
- hint: "/link",
472
- group: "basic",
473
- icon: "link",
474
- keywords: ["link", "链接", "lianjie", "url"],
475
- run: ({ chain }) => {
476
- const href = "https://";
477
- chain().insertContent({
478
- type: "text",
479
- text: href,
480
- marks: [{ type: "link", attrs: { href } }]
481
- }).run();
482
- }
483
- },
484
- // ---------------------------------------------------------------- 常用
485
- {
486
- id: "mindmap",
487
- label: "思维导图",
488
- hint: "/mindmap",
489
- group: "common",
490
- icon: "mind-map",
491
- keywords: ["mindmap", "mind", "思维导图", "导图", "siweidaotu", "脑图"],
492
- run: ({ chain }) => chain().insertContent([createMindMap(), { type: "paragraph" }]).run()
493
- },
494
- {
495
- id: "task",
496
- convertible: true,
497
- label: "任务",
498
- hint: "/todo",
499
- group: "common",
500
- icon: "task",
501
- keywords: ["task", "todo", "待办", "daiban", "renwu", "checkbox"],
502
- run: ({ chain }) => chain().toggleTaskList().run()
503
- },
504
- {
505
- id: "image",
506
- label: "图片",
507
- hint: "/image",
508
- group: "common",
509
- icon: "image",
510
- keywords: ["image", "img", "图片", "tupian", "photo"],
511
- run: ({ requestPickFile }) => requestPickFile == null ? void 0 : requestPickFile("image")
512
- },
513
- {
514
- id: "file",
515
- label: "上传文件",
516
- hint: "/file",
517
- group: "common",
518
- icon: "file",
519
- keywords: ["file", "文件", "wenjian", "上传", "attachment"],
520
- run: ({ requestPickFile }) => requestPickFile == null ? void 0 : requestPickFile("file")
521
- },
522
- {
523
- id: "video",
524
- label: "嵌入视频",
525
- hint: "/video",
526
- group: "common",
527
- icon: "file",
528
- keywords: ["video", "youtube", "bilibili", "视频", "shipin", "b站"],
529
- run: ({ requestVideo }) => requestVideo == null ? void 0 : requestVideo()
530
- },
531
- {
532
- id: "table",
533
- label: "表格",
534
- hint: "/table",
535
- group: "common",
536
- icon: "table",
537
- keywords: ["table", "表格", "biaoge"],
538
- panel: "table",
539
- run: ({ chain, panelValue }) => chain().insertTable({
540
- rows: clampGrid((panelValue == null ? void 0 : panelValue.rows) ?? 3),
541
- cols: clampGrid((panelValue == null ? void 0 : panelValue.cols) ?? 3),
542
- withHeaderRow: true
543
- }).run()
544
- },
545
- {
546
- id: "columns",
547
- label: "分栏",
548
- hint: "/columns",
549
- group: "common",
550
- icon: "columns",
551
- keywords: ["columns", "column", "分栏", "fenlan", "多栏", "duolan", "layout"],
552
- panel: "columns",
553
- run: ({ chain, panelValue }) => {
554
- chain().insertContent(buildColumnsJson((panelValue == null ? void 0 : panelValue.count) ?? 2)).run();
555
- }
556
- },
557
- {
558
- id: "callout",
559
- convertible: true,
560
- label: "高亮块",
561
- hint: "/callout",
562
- group: "common",
563
- icon: "callout",
564
- keywords: ["callout", "highlight", "高亮", "gaoliang", "提示块", "tishikuai"],
565
- run: ({ chain }) => {
566
- chain().insertContent({
567
- type: "callout",
568
- attrs: { tone: "info" },
569
- content: [{ type: "paragraph" }]
570
- }).run();
571
- }
572
- },
573
- {
574
- id: "math",
575
- label: "公式",
576
- hint: "/math",
577
- group: "common",
578
- icon: "math",
579
- keywords: ["math", "formula", "公式", "gongshi", "latex", "katex"],
580
- run: ({ chain }) => {
581
- chain().insertContent({
582
- type: "mathBlock",
583
- attrs: { latex: "E = mc^2" }
584
- }).run();
585
- }
586
- },
587
- {
588
- id: "page",
589
- label: "子文档",
590
- hint: "/page",
591
- group: "common",
592
- icon: "page",
593
- keywords: ["page", "子文档", "ziwendang", "页面", "yemian", "link", "引用"],
594
- run: ({ requestPickPage }) => requestPickPage == null ? void 0 : requestPickPage()
595
- }
596
- ];
597
- const BLOCK_COMMAND_GROUPS = [
598
- { id: "basic", label: "基础" },
599
- { id: "common", label: "常用" }
600
- ];
601
- function filterBlockCommands(query) {
602
- const q = query.trim().toLowerCase();
603
- if (!q) return BLOCK_COMMANDS;
604
- return BLOCK_COMMANDS.filter(
605
- (c) => c.label.toLowerCase().includes(q) || c.hint.toLowerCase().includes(q) || c.keywords.some((k) => k.includes(q) || k.startsWith(q))
606
- );
607
- }
608
- function groupOf(commands) {
609
- return BLOCK_COMMAND_GROUPS.map((g) => ({
610
- ...g,
611
- items: commands.filter((c) => c.group === g.id)
612
- })).filter((g) => g.items.length > 0);
613
- }
614
- function InsertPicker({ mode, x, y, yBottom, boundTop, boundBottom, onPick, onClose }) {
615
- const root = useRef(null), [top, setTop] = useState(y);
616
- const [hover, setHover] = useState({ rows: 3, cols: 3 }), [count, setCount] = useState(2);
617
- useEffect(() => {
618
- const element = root.current;
619
- if (!element) return;
620
- setTop(computePanelTop(y, element.offsetHeight, boundTop, boundBottom, yBottom ?? y + element.offsetHeight));
621
- }, [mode, y, yBottom, boundTop, boundBottom]);
622
- const cells = buildGridCells(hover.rows, hover.cols);
623
- return /* @__PURE__ */ jsxs(
624
- "div",
625
- {
626
- ref: root,
627
- className: "insert-picker",
628
- role: "dialog",
629
- "aria-label": mode === "table" ? "选择表格尺寸" : "选择分栏栏数",
630
- style: { left: x, top },
631
- onMouseDown: (event) => event.preventDefault(),
632
- children: [
633
- /* @__PURE__ */ jsxs("div", { className: "ip-head", children: [
634
- /* @__PURE__ */ jsx("span", { children: mode === "table" ? "插入表格" : "选择栏数" }),
635
- /* @__PURE__ */ jsx("span", { className: "ip-dim", children: mode === "table" ? `${hover.rows} x ${hover.cols}` : `${count} 栏` })
636
- ] }),
637
- mode === "table" ? /* @__PURE__ */ jsx("div", { className: "ip-grid", children: cells.map((cell) => /* @__PURE__ */ jsx(
638
- "button",
639
- {
640
- type: "button",
641
- className: `ip-cell${cell.on ? " on" : ""}`,
642
- "aria-label": `${cell.row} 行 ${cell.col} 列`,
643
- onMouseEnter: () => setHover({ rows: cell.row, cols: cell.col }),
644
- onFocus: () => setHover({ rows: cell.row, cols: cell.col }),
645
- onClick: () => onPick({ rows: cell.row, cols: cell.col })
646
- },
647
- `${cell.row}-${cell.col}`
648
- )) }) : /* @__PURE__ */ jsx("div", { className: "ip-counts", children: Array.from({ length: MAX_COLUMN_COUNT }, (_, index) => index + 1).map((value) => /* @__PURE__ */ jsxs(
649
- "button",
650
- {
651
- type: "button",
652
- className: `ip-count${count === value ? " on" : ""}`,
653
- "aria-label": `${value} 栏`,
654
- onMouseEnter: () => setCount(value),
655
- onClick: () => onPick({ count: value }),
656
- children: [
657
- /* @__PURE__ */ jsx("span", { className: "ip-count-box", children: Array.from({ length: value }, (_, index) => /* @__PURE__ */ jsx("span", { className: "ip-count-bar" }, index)) }),
658
- /* @__PURE__ */ jsx("span", { className: "ip-count-num", children: value })
659
- ]
660
- },
661
- value
662
- )) }),
663
- /* @__PURE__ */ jsx("button", { type: "button", className: "ip-close", "aria-label": "关闭参数面板", onClick: onClose, children: /* @__PURE__ */ jsx(WIcon, { name: "x", size: 14 }) })
664
- ]
665
- }
666
- );
667
- }
668
- function SlashMenu({ editor, from, query, x, y, onClose, onPickFile, onPickPage, onPickVideo }) {
669
- const filtered = useMemo(() => filterBlockCommands(query), [query]);
670
- const groups = useMemo(() => groupOf(filtered), [filtered]);
671
- const [activeIndex, setActiveIndex] = useState(0);
672
- const [panel, setPanel] = useState(null);
673
- const [panelY, setPanelY] = useState(0), [panelYBottom, setPanelYBottom] = useState(0);
674
- const [panelX, setPanelX] = useState(274);
675
- const [viewport, setViewport] = useState({ top: 0, bottom: 0 });
676
- const [anchorTop, setAnchorTop] = useState(y);
677
- const root = useRef(null), menu = useRef(null), viewportRef = useRef(null), content = useRef(null);
678
- const keyboardNav = useRef(false);
679
- const flatIndex = (command) => filtered.findIndex((item) => item.id === command.id);
680
- useEffect(() => {
681
- setActiveIndex(0);
682
- setPanel(null);
683
- }, [query]);
684
- useEffect(() => {
685
- const measure = () => {
686
- var _a;
687
- const anchor = root.current, element = menu.current;
688
- if (!anchor || !element) return;
689
- const parentTop = ((_a = anchor.parentElement) == null ? void 0 : _a.getBoundingClientRect().top) ?? 0;
690
- const desired = parentTop + y, margin = 8;
691
- const fitted = Math.round(Math.max(margin, Math.min(desired, window.innerHeight - margin - element.offsetHeight)));
692
- setAnchorTop(fitted - parentTop);
693
- setViewport({ top: margin - fitted, bottom: window.innerHeight - margin - fitted });
694
- };
695
- const frame = requestAnimationFrame(measure);
696
- window.addEventListener("resize", measure);
697
- window.addEventListener("scroll", measure, true);
698
- return () => {
699
- cancelAnimationFrame(frame);
700
- window.removeEventListener("resize", measure);
701
- window.removeEventListener("scroll", measure, true);
702
- };
703
- }, [y, query, filtered.length]);
704
- useEffect(() => {
705
- var _a;
706
- if (!keyboardNav.current) return;
707
- keyboardNav.current = false;
708
- const row = (_a = menu.current) == null ? void 0 : _a.querySelectorAll("li")[activeIndex];
709
- const scroller = content.current;
710
- if (!row || !scroller) return;
711
- const rowTop = row.offsetTop, rowBottom = rowTop + row.offsetHeight;
712
- if (rowTop < scroller.scrollTop) scroller.scrollTop = rowTop;
713
- else if (rowBottom > scroller.scrollTop + scroller.clientHeight) scroller.scrollTop = rowBottom - scroller.clientHeight;
714
- }, [activeIndex, filtered]);
715
- function openPanel(command) {
716
- var _a, _b, _c;
717
- if (!(command == null ? void 0 : command.panel)) {
718
- setPanel(null);
719
- return;
720
- }
721
- if ((panel == null ? void 0 : panel.id) === command.id) return;
722
- setPanel(command);
723
- const row = (_a = menu.current) == null ? void 0 : _a.querySelectorAll("li")[flatIndex(command)];
724
- const view = (_b = viewportRef.current) == null ? void 0 : _b.getBoundingClientRect();
725
- if (!row || !view) return;
726
- const rect = row.getBoundingClientRect();
727
- if (rect.bottom <= view.top || rect.top >= view.bottom) {
728
- setPanel(null);
729
- return;
730
- }
731
- if (!root.current) return;
732
- const top = rect.top - root.current.getBoundingClientRect().top;
733
- setPanelY(top);
734
- setPanelYBottom(top + rect.height);
735
- setPanelX((((_c = content.current) == null ? void 0 : _c.getBoundingClientRect().width) ?? 268) + 6);
736
- }
737
- function choose(command, value) {
738
- if (!editor.isEditable || editor.isDestroyed) return;
739
- if (from < 0 || from > editor.state.selection.from) return;
740
- const range = { from, to: editor.state.selection.from };
741
- if (["image", "file", "page", "video"].includes(command.id) && range.to > range.from) editor.chain().deleteRange(range).run();
742
- command.run({
743
- chain: () => {
744
- const chain = editor.chain().focus();
745
- return range.to > range.from ? chain.deleteRange(range) : chain;
746
- },
747
- requestPickFile: onPickFile,
748
- requestPickPage: onPickPage,
749
- requestVideo: onPickVideo,
750
- panelValue: value
751
- });
752
- dismissSlash(editor.view);
753
- onClose();
754
- }
755
- function activate(command) {
756
- if (command.panel) {
757
- openPanel(command);
758
- return;
759
- }
760
- choose(command);
761
- }
762
- function keydown(event) {
763
- var _a;
764
- if (shouldIgnoreForIme(event) || !editor.view.dom.contains(event.target)) return;
765
- if (event.key === "Escape") {
766
- event.preventDefault();
767
- event.stopImmediatePropagation();
768
- if (panel) setPanel(null);
769
- else {
770
- dismissSlash(editor.view);
771
- onClose();
772
- }
773
- return;
774
- }
775
- if (!filtered.length) return;
776
- if (["ArrowDown", "ArrowUp", "Enter"].includes(event.key) || event.key === "ArrowRight" && ((_a = filtered[activeIndex]) == null ? void 0 : _a.panel)) {
777
- event.stopImmediatePropagation();
778
- }
779
- if (event.key === "ArrowDown") {
780
- event.preventDefault();
781
- keyboardNav.current = true;
782
- setPanel(null);
783
- setActiveIndex((activeIndex + 1) % filtered.length);
784
- } else if (event.key === "ArrowUp") {
785
- event.preventDefault();
786
- keyboardNav.current = true;
787
- setPanel(null);
788
- setActiveIndex((activeIndex - 1 + filtered.length) % filtered.length);
789
- } else if (event.key === "Enter") {
790
- event.preventDefault();
791
- const command = filtered[activeIndex];
792
- if (command && (panel == null ? void 0 : panel.id) === command.id) choose(command);
793
- else if (command) activate(command);
794
- } else if (event.key === "ArrowRight") {
795
- const command = filtered[activeIndex];
796
- if (command == null ? void 0 : command.panel) {
797
- event.preventDefault();
798
- openPanel(command);
799
- }
800
- }
801
- }
802
- useEffect(() => {
803
- const node = editor.view.dom;
804
- node.addEventListener("keydown", keydown, true);
805
- return () => node.removeEventListener("keydown", keydown, true);
806
- });
807
- useEffect(() => {
808
- const outside = (event) => {
809
- var _a;
810
- if (event.target instanceof Node && !((_a = root.current) == null ? void 0 : _a.contains(event.target)) && !editor.view.dom.contains(event.target)) onClose();
811
- };
812
- document.addEventListener("pointerdown", outside);
813
- return () => document.removeEventListener("pointerdown", outside);
814
- }, [onClose, editor]);
815
- return /* @__PURE__ */ jsxs("div", { ref: root, className: "slash-anchor", style: { left: x, top: anchorTop }, children: [
816
- /* @__PURE__ */ jsx("div", { ref: menu, className: "slash-menu", role: "listbox", "aria-label": "插入内容", children: /* @__PURE__ */ jsx("div", { ref: viewportRef, children: /* @__PURE__ */ jsxs("div", { ref: content, className: "slash-content", children: [
817
- groups.map((group) => /* @__PURE__ */ jsxs("div", { children: [
818
- /* @__PURE__ */ jsx("div", { className: "slash-group", children: group.label }),
819
- /* @__PURE__ */ jsx("ul", { children: group.items.map((command) => {
820
- const index = flatIndex(command);
821
- return /* @__PURE__ */ jsxs(
822
- "li",
823
- {
824
- role: "option",
825
- "aria-selected": index === activeIndex,
826
- className: index === activeIndex ? "active" : void 0,
827
- onMouseMove: () => {
828
- setActiveIndex(index);
829
- openPanel(command);
830
- },
831
- onMouseDown: (event) => {
832
- event.preventDefault();
833
- activate(command);
834
- },
835
- children: [
836
- /* @__PURE__ */ jsx(WIcon, { name: command.icon, size: 18 }),
837
- /* @__PURE__ */ jsx("span", { className: "slash-label", children: command.label }),
838
- /* @__PURE__ */ jsx("span", { className: "slash-hint", children: command.hint }),
839
- command.panel && /* @__PURE__ */ jsx(WIcon, { name: "chevron", size: 16, className: "slash-chevron" })
840
- ]
841
- },
842
- command.id
843
- );
844
- }) })
845
- ] }, group.id)),
846
- !filtered.length && /* @__PURE__ */ jsx("div", { className: "slash-empty", children: "无匹配命令" })
847
- ] }) }) }),
848
- (panel == null ? void 0 : panel.panel) && /* @__PURE__ */ jsx(
849
- InsertPicker,
850
- {
851
- mode: panel.panel,
852
- x: panelX,
853
- y: panelY,
854
- yBottom: panelYBottom,
855
- boundTop: viewport.top,
856
- boundBottom: viewport.bottom,
857
- onPick: (value) => choose(panel, value),
858
- onClose: () => setPanel(null)
859
- }
860
- )
861
- ] });
862
- }
863
- function menuBlockRange(state, pos) {
864
- const blocks = [];
865
- state.doc.forEach((node, from2) => blocks.push({ from: from2, to: from2 + node.nodeSize, node }));
866
- const target = blocks.find((b2) => b2.from === pos);
867
- if (!target) return null;
868
- const { from, to, empty: empty2 } = state.selection;
869
- const chosen = !empty2 && from < target.to && to > target.from ? blocks.filter((b2) => from < b2.to && to > b2.from) : [target];
870
- return { from: chosen[0].from, to: chosen.at(-1).to, nodes: chosen.map((b2) => b2.node) };
871
- }
872
- function containerContent(range) {
873
- const only = range.nodes.length === 1 ? range.nodes[0] : null;
874
- return only && ["callout", "blockquote"].includes(only.type.name) ? only.content : range.nodes;
875
- }
876
- function canConvertMenuBlocks(state, pos, id) {
877
- const range = menuBlockRange(state, pos);
878
- if (!range) return false;
879
- if (id === "callout" || id === "quote") {
880
- const type = state.schema.nodes[id === "callout" ? "callout" : "blockquote"];
881
- return !!(type == null ? void 0 : type.validContent(type.create(null, containerContent(range)).content));
882
- }
883
- if (id === "text" && range.nodes.length === 1 && ["callout", "blockquote"].includes(range.nodes[0].type.name)) return true;
884
- if (!["text", "h1", "h2", "h3", "h4", "h5", "h6", "bullet", "ordered", "task", "code"].includes(id)) return false;
885
- const allowed = ["paragraph", "heading", "blockquote", "codeBlock", "bulletList", "orderedList", "taskList", "callout", "listItem", "taskItem", "text", "hardBreak"];
886
- return range.nodes.every((node) => {
887
- if (!allowed.includes(node.type.name)) return false;
888
- let valid = true;
889
- node.descendants((child) => {
890
- if (!allowed.includes(child.type.name)) valid = false;
891
- return valid;
892
- });
893
- return valid;
894
- });
895
- }
896
- function dispatchContainer(editor, range, type) {
897
- var _a;
898
- if (range.nodes.length === 1 && range.nodes[0].type.name === type) return true;
899
- const wrapper = editor.schema.nodes[type].create(type === "callout" ? { tone: "info" } : null, containerContent(range));
900
- const tr = editor.state.tr.replaceWith(range.from, range.to, wrapper);
901
- tr.setSelection(Selection.near(tr.doc.resolve(range.from + 1)));
902
- const manager = (_a = yUndoPluginKey.getState(editor.state)) == null ? void 0 : _a.undoManager;
903
- manager == null ? void 0 : manager.stopCapturing();
904
- editor.view.dispatch(closeHistory(tr));
905
- manager == null ? void 0 : manager.stopCapturing();
906
- editor.view.focus();
907
- return true;
908
- }
909
- function convertMenuBlocks(editor, pos, id) {
910
- var _a, _b;
911
- if (editor.isDestroyed || !editor.isEditable || !canConvertMenuBlocks(editor.state, pos, id)) return false;
912
- const range = menuBlockRange(editor.state, pos);
913
- if (id === "callout" || id === "quote") return dispatchContainer(editor, range, id === "callout" ? "callout" : "blockquote");
914
- if (id === "text" && range.nodes.length === 1 && range.nodes[0].type.name === "callout") {
915
- const result2 = unwrapCallout(editor, range.from);
916
- if (result2) editor.view.focus();
917
- return result2;
918
- }
919
- if (id === "text" && range.nodes.length === 1 && range.nodes[0].type.name === "blockquote") {
920
- const tr = editor.state.tr.replaceWith(range.from, range.to, range.nodes[0].content);
921
- const manager2 = (_a = yUndoPluginKey.getState(editor.state)) == null ? void 0 : _a.undoManager;
922
- manager2 == null ? void 0 : manager2.stopCapturing();
923
- editor.view.dispatch(closeHistory(tr));
924
- manager2 == null ? void 0 : manager2.stopCapturing();
925
- editor.view.focus();
926
- return true;
927
- }
928
- const desiredType = { text: "paragraph", bullet: "bulletList", ordered: "orderedList", task: "taskList", code: "codeBlock" }[id];
929
- if (range.nodes.length === 1 && range.nodes[0].type.name === desiredType) return true;
930
- const manager = (_b = yUndoPluginKey.getState(editor.state)) == null ? void 0 : _b.undoManager;
931
- manager == null ? void 0 : manager.stopCapturing();
932
- const chain = editor.chain().focus().command(({ tr }) => {
933
- let end = range.to;
934
- if (range.nodes.length === 1 && ["callout", "blockquote"].includes(range.nodes[0].type.name)) {
935
- tr.replaceWith(range.from, range.to, range.nodes[0].content);
936
- end -= 2;
937
- }
938
- tr.setSelection(TextSelection.between(tr.doc.resolve(range.from + 1), tr.doc.resolve(end - 1)));
939
- closeHistory(tr);
940
- return true;
941
- }).clearNodes();
942
- if (id === "text") chain.setParagraph();
943
- else if (/^h[1-6]$/.test(id)) chain.setHeading({ level: Number(id.slice(1)) });
944
- else if (id === "bullet") chain.toggleBulletList();
945
- else if (id === "ordered") chain.toggleOrderedList();
946
- else if (id === "task") chain.toggleTaskList();
947
- else if (id === "code") chain.toggleCodeBlock();
948
- const result = chain.command(({ tr }) => {
949
- tr.setSelection(Selection.near(tr.doc.resolve(tr.selection.from)));
950
- return true;
951
- }).run();
952
- manager == null ? void 0 : manager.stopCapturing();
953
- return result;
954
- }
955
- function deleteTopLevelBlock(state, pos) {
956
- let found = false;
957
- state.doc.forEach((_node, offset) => {
958
- if (offset === pos) found = true;
959
- });
960
- const node = found ? state.doc.nodeAt(pos) : null;
961
- if (!node) return null;
962
- return state.doc.childCount === 1 ? state.tr.replaceWith(0, state.doc.content.size, state.schema.nodes.paragraph.create()) : state.tr.delete(pos, pos + node.nodeSize);
963
- }
964
- const BLOCK_SUBMENU_GAP = 6;
965
- const BLOCK_MENU_VIEW_MARGIN = 8;
966
- function placeBlockSubmenu(parent, rowTop, panel, viewport) {
967
- const inset = BLOCK_MENU_VIEW_MARGIN;
968
- const maxHeight = Math.max(0, viewport.height - inset * 2);
969
- const right = parent.right + BLOCK_SUBMENU_GAP;
970
- const left = parent.left - BLOCK_SUBMENU_GAP - panel.width;
971
- const preferredLeft = right + panel.width <= viewport.width - inset || left < inset ? right : left;
972
- return {
973
- left: Math.max(inset, Math.min(preferredLeft, viewport.width - inset - panel.width)),
974
- top: fitRange(rowTop, Math.min(panel.height, maxHeight), inset, viewport.height - inset),
975
- maxHeight
976
- };
977
- }
978
- const PLUS = { width: 28, height: 28 }, CHIP = { width: 48, height: 28 }, HANDLE_HEIGHT = 26, GUTTER_GAP$1 = 8;
979
- function blockAffordanceStyle(info, shell, kind) {
980
- const size = kind === "plus" ? PLUS : CHIP;
981
- const preferred = info.contentLeft - GUTTER_GAP$1 - size.width;
982
- const minLeft = shell ? 8 - shell.getBoundingClientRect().left : 8;
983
- return {
984
- left: Math.max(preferred, minLeft),
985
- top: info.top + (HANDLE_HEIGHT - size.height) / 2
986
- };
987
- }
988
- function BlockPlus({ style, expanded, onClick, onMouseEnter }) {
989
- return /* @__PURE__ */ jsx(
990
- "button",
991
- {
992
- type: "button",
993
- className: "block-plus",
994
- "aria-label": "插入内容",
995
- title: "在此空行插入内容(表格 / 分栏 / 图片…)",
996
- "aria-expanded": expanded,
997
- "aria-haspopup": "menu",
998
- style,
999
- onMouseEnter,
1000
- onClick,
1001
- children: /* @__PURE__ */ jsx(WIcon, { name: "plus", size: 17 })
1002
- }
1003
- );
1004
- }
1005
- function typeIcon(editor, info) {
1006
- var _a;
1007
- if (info.type === "heading") {
1008
- const level = Number(((_a = editor.state.doc.nodeAt(info.pos)) == null ? void 0 : _a.attrs.level) ?? 1);
1009
- return `h${Math.min(Math.max(Number.isFinite(level) ? level : 1, 1), 6)}`;
1010
- }
1011
- return {
1012
- paragraph: "text",
1013
- image: "image",
1014
- imageGallery: "image",
1015
- table: "table",
1016
- blockquote: "quote",
1017
- codeBlock: "code",
1018
- bulletList: "bullet-list",
1019
- orderedList: "ordered-list",
1020
- taskList: "task",
1021
- callout: "callout",
1022
- columns: "columns",
1023
- mindMap: "mind-map",
1024
- fileAttachment: "file",
1025
- videoEmbed: "file",
1026
- mathBlock: "math"
1027
- }[info.type] ?? "text";
1028
- }
1029
- function BlockChip({ editor, info, style, expanded, onOpen, onOpenFull, onMouseEnter, onHandleMouseDown }) {
1030
- return /* @__PURE__ */ jsxs("div", { className: "block-chip", role: "toolbar", "aria-label": "块快捷操作", style, onMouseEnter, children: [
1031
- /* @__PURE__ */ jsx("button", { type: "button", className: "chip-btn", "aria-label": "转换为", title: "转换为…", "aria-expanded": expanded, "aria-haspopup": "menu", onClick: onOpen, children: /* @__PURE__ */ jsx(WIcon, { name: typeIcon(editor, info), size: 17 }) }),
1032
- /* @__PURE__ */ jsx(
1033
- "button",
1034
- {
1035
- type: "button",
1036
- className: "chip-btn chip-grip",
1037
- "aria-label": "拖动或打开块菜单",
1038
- title: "按住拖动可移动整段;单击打开块菜单",
1039
- "aria-expanded": expanded,
1040
- "aria-haspopup": "menu",
1041
- draggable: false,
1042
- onMouseDown: (event) => onHandleMouseDown ? onHandleMouseDown(event) : event.preventDefault(),
1043
- onClick: onOpenFull,
1044
- children: /* @__PURE__ */ jsx(WIcon, { name: "handle", size: 17 })
1045
- }
1046
- )
1047
- ] });
1048
- }
1049
- function BlockMenu(props) {
1050
- var _a, _b, _c, _d, _e, _f;
1051
- const { editor, info } = props;
1052
- const [submenu, setSubmenu] = useState(props.panelOnly ?? null);
1053
- const [copied, setCopied] = useState(false);
1054
- const [panel, setPanel] = useState(null);
1055
- const node = editor.state.doc.nodeAt(info.pos);
1056
- const chain = () => {
1057
- const range = menuBlockRange(editor.state, info.pos);
1058
- const selection = editor.state.selection;
1059
- const keep = selection instanceof TextSelection && !selection.empty && node && selection.from >= info.pos + 1 && selection.to <= info.pos + node.nodeSize - 1;
1060
- return editor.chain().focus().command(({ tr }) => {
1061
- tr.setSelection(TextSelection.between(
1062
- tr.doc.resolve(keep ? selection.from : ((range == null ? void 0 : range.from) ?? info.pos) + 1),
1063
- tr.doc.resolve(keep ? selection.to : ((range == null ? void 0 : range.to) ?? info.pos + node.nodeSize) - 1)
1064
- ));
1065
- return true;
1066
- });
1067
- };
1068
- const convertibles = BLOCK_COMMANDS.filter((command) => command.convertible);
1069
- const insertCommands = BLOCK_COMMANDS.filter((command) => command.id !== "link");
1070
- const currentAlign = String(((_a = node == null ? void 0 : node.attrs) == null ? void 0 : _a.textAlign) ?? "left");
1071
- const textual = node ? ["paragraph", "heading", "blockquote", "codeBlock", "bulletList", "orderedList", "taskList", "callout"].includes(node.type.name) : false;
1072
- const target = useRef(null);
1073
- const submenuRef = useRef(null), rowRef = useRef(null);
1074
- const [submenuPlace, setSubmenuPlace] = useState();
1075
- const [place, setPlace] = useState({ left: 0, top: 0 });
1076
- useEffect(() => {
1077
- highlightMenuBlock(editor.view, info.pos);
1078
- const outside = (event) => {
1079
- var _a2;
1080
- if (!(event.target instanceof Element) || ((_a2 = target.current) == null ? void 0 : _a2.contains(event.target))) return;
1081
- if (event.target.closest(".block-chip,.block-plus")) return;
1082
- props.onClose();
1083
- };
1084
- const key = (event) => {
1085
- var _a2, _b2;
1086
- if (event.isComposing) return;
1087
- if (event.key === "Escape") {
1088
- event.preventDefault();
1089
- event.stopPropagation();
1090
- props.onClose();
1091
- return;
1092
- }
1093
- if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
1094
- const menu = ((_a2 = document.activeElement) == null ? void 0 : _a2.closest('[role="menu"]')) ?? target.current;
1095
- const items = Array.from((menu == null ? void 0 : menu.querySelectorAll('[role="menuitem"]:not(:disabled)')) ?? []);
1096
- if (!items.length) return;
1097
- event.preventDefault();
1098
- const index = items.indexOf(document.activeElement);
1099
- (_b2 = items[event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : (index + (event.key === "ArrowUp" ? -1 : 1) + items.length) % items.length]) == null ? void 0 : _b2.focus();
1100
- };
1101
- const changed = ({ transaction }) => {
1102
- if (transaction.docChanged) props.onClose();
1103
- };
1104
- document.addEventListener("pointerdown", outside, true);
1105
- document.addEventListener("keydown", key);
1106
- editor.on("transaction", changed);
1107
- return () => {
1108
- document.removeEventListener("pointerdown", outside, true);
1109
- document.removeEventListener("keydown", key);
1110
- editor.off("transaction", changed);
1111
- if (!editor.isDestroyed) highlightMenuBlock(editor.view, null);
1112
- };
1113
- }, [editor, info.pos]);
1114
- useLayoutEffect(() => {
1115
- var _a2;
1116
- if (props.panelOnly || !submenuRef.current || !target.current) {
1117
- setSubmenuPlace(void 0);
1118
- return;
1119
- }
1120
- const anchor = target.current.getBoundingClientRect(), card = submenuRef.current.getBoundingClientRect();
1121
- const placed = placeBlockSubmenu(anchor, ((_a2 = rowRef.current) == null ? void 0 : _a2.getBoundingClientRect().top) ?? anchor.top, card, { width: innerWidth, height: innerHeight });
1122
- setSubmenuPlace({ left: placed.left - anchor.left, top: placed.top - anchor.top, maxHeight: placed.maxHeight });
1123
- }, [submenu, props.panelOnly, place.left, place.top]);
1124
- useLayoutEffect(() => {
1125
- const panel2 = target.current;
1126
- const shell = editor.view.dom.closest(".editor-shell");
1127
- if (!panel2 || !shell) return;
1128
- const measure = () => {
1129
- const host = shell.getBoundingClientRect();
1130
- const width = panel2.offsetWidth, height = panel2.offsetHeight, margin = 8;
1131
- const left = Math.min(Math.max(host.left + info.contentLeft, margin), Math.max(margin, innerWidth - margin - width)) - host.left;
1132
- const top = Math.min(Math.max(host.top + info.top, margin), Math.max(margin, innerHeight - margin - height)) - host.top;
1133
- setPlace({ left, top });
1134
- };
1135
- measure();
1136
- window.addEventListener("resize", measure);
1137
- window.addEventListener("scroll", measure, true);
1138
- return () => {
1139
- window.removeEventListener("resize", measure);
1140
- window.removeEventListener("scroll", measure, true);
1141
- };
1142
- }, [editor, info.contentLeft, info.top, props.panelOnly, submenu]);
1143
- useEffect(() => {
1144
- setSubmenu(props.panelOnly ?? null);
1145
- }, [props.panelOnly, info.pos]);
1146
- function runInsert(command) {
1147
- if (command.panel) {
1148
- setPanel({ mode: command.panel, x: 0, y: 0 });
1149
- return;
1150
- }
1151
- insertCommand(command);
1152
- }
1153
- function insertWithPanel(value) {
1154
- const command = insertCommands.find((item2) => item2.panel === (panel == null ? void 0 : panel.mode));
1155
- setPanel(null);
1156
- if (!command) return;
1157
- insertCommand(command, value);
1158
- }
1159
- function insertCommand(command, value) {
1160
- if (!node || !editor.isEditable || editor.isDestroyed) return;
1161
- const reuse = props.panelOnly === "insert-below" && node.isTextblock && node.content.size === 0;
1162
- const after = info.pos + node.nodeSize, at = reuse ? { from: info.pos, to: after } : after;
1163
- const media = ["image", "file", "page", "video"].includes(command.id);
1164
- if (media) {
1165
- if (!reuse) editor.chain().insertContentAt(after, { type: "paragraph" }).setTextSelection(after + 1).run();
1166
- else editor.commands.setTextSelection(info.pos + 1);
1167
- props.onClose();
1168
- if (command.id === "page") props.onPickPage();
1169
- else if (command.id === "video") props.onPickVideo();
1170
- else props.onPickFile(command.id === "image" ? "image" : "file");
1171
- return;
1172
- }
1173
- let content;
1174
- if (/^h[1-6]$/.test(command.id)) content = { type: "heading", attrs: { level: Number(command.id.slice(1)) } };
1175
- else if (command.id === "table") content = createTable(editor.schema, (value == null ? void 0 : value.rows) ?? 3, (value == null ? void 0 : value.cols) ?? 3, true).toJSON();
1176
- else if (command.id === "columns") content = buildColumnsJson((value == null ? void 0 : value.count) ?? 2);
1177
- else if (command.id === "mindmap") content = [createMindMap$1(), { type: "paragraph" }];
1178
- else if (["bullet", "ordered", "task"].includes(command.id)) content = {
1179
- type: command.id === "bullet" ? "bulletList" : command.id === "ordered" ? "orderedList" : "taskList",
1180
- content: [{ type: command.id === "task" ? "taskItem" : "listItem", attrs: command.id === "task" ? { checked: false } : void 0, content: [{ type: "paragraph" }] }]
1181
- };
1182
- else content = {
1183
- text: { type: "paragraph" },
1184
- quote: { type: "blockquote", content: [{ type: "paragraph" }] },
1185
- code: { type: "codeBlock" },
1186
- hr: { type: "horizontalRule" },
1187
- callout: { type: "callout", attrs: { tone: "info" }, content: [{ type: "paragraph" }] },
1188
- math: { type: "mathBlock", attrs: { latex: "E = mc^2" } }
1189
- }[command.id];
1190
- if (content) editor.chain().focus().insertContentAt(at, content).run();
1191
- props.onClose();
1192
- }
1193
- function action(name) {
1194
- if (!node) return;
1195
- {
1196
- const tr = deleteTopLevelBlock(editor.state, info.pos);
1197
- if (tr) editor.view.dispatch(tr);
1198
- }
1199
- props.onClose();
1200
- }
1201
- async function writeBlockClipboard() {
1202
- var _a2;
1203
- if (!node) return;
1204
- if (typeof ClipboardItem === "undefined" || !((_a2 = navigator.clipboard) == null ? void 0 : _a2.write)) {
1205
- if (!node.isTextblock) throw new Error("当前浏览器不支持复制此块的富文本结构");
1206
- await navigator.clipboard.writeText(node.textContent);
1207
- return;
1208
- }
1209
- const container = document.createElement("div");
1210
- container.append(DOMSerializer.fromSchema(editor.schema).serializeFragment(Fragment$1.from(node)));
1211
- await navigator.clipboard.write([new ClipboardItem({
1212
- "text/html": new Blob([container.innerHTML], { type: "text/html" }),
1213
- "text/plain": new Blob([node.textContent], { type: "text/plain" })
1214
- })]);
1215
- }
1216
- async function copyBlock() {
1217
- var _a2;
1218
- try {
1219
- await writeBlockClipboard();
1220
- setCopied(true);
1221
- setTimeout(() => setCopied(false), 1600);
1222
- } catch {
1223
- (_a2 = props.onNotice) == null ? void 0 : _a2.call(props, "无法完整复制该块,请选中后使用 Cmd/Ctrl+C。");
1224
- }
1225
- }
1226
- async function cutBlock() {
1227
- var _a2, _b2;
1228
- if (!node) return;
1229
- const originalDoc = editor.state.doc, originalNode = node;
1230
- try {
1231
- await writeBlockClipboard();
1232
- } catch {
1233
- (_a2 = props.onNotice) == null ? void 0 : _a2.call(props, "无法访问剪贴板,已取消剪切(内容未删除)。");
1234
- return;
1235
- }
1236
- if (editor.isDestroyed || !editor.isEditable || editor.state.doc !== originalDoc || editor.state.doc.nodeAt(info.pos) !== originalNode) {
1237
- (_b2 = props.onNotice) == null ? void 0 : _b2.call(props, "复制期间正文发生了变化,已取消删除,请重新剪切。");
1238
- return;
1239
- }
1240
- const tr = deleteTopLevelBlock(editor.state, info.pos);
1241
- if (tr) editor.view.dispatch(tr);
1242
- props.onClose();
1243
- }
1244
- const item = (icon, label, onClick, options = {}) => /* @__PURE__ */ jsxs(
1245
- "button",
1246
- {
1247
- type: "button",
1248
- role: "menuitem",
1249
- className: `bm-item${options.active ? " active" : ""}${options.disabled ? " disabled" : ""}${options.danger ? " danger" : ""}`,
1250
- "aria-haspopup": options.submenu ? "menu" : void 0,
1251
- "aria-expanded": options.submenu ? submenu === options.submenu : void 0,
1252
- disabled: options.disabled,
1253
- "aria-disabled": options.disabled || void 0,
1254
- onMouseEnter: (event) => {
1255
- rowRef.current = event.currentTarget;
1256
- if (options.submenu) setSubmenu(options.submenu);
1257
- else if (!props.panelOnly) setSubmenu(null);
1258
- },
1259
- onClick,
1260
- children: [
1261
- /* @__PURE__ */ jsx(WIcon, { name: icon, size: 18 }),
1262
- /* @__PURE__ */ jsx("span", { children: label }),
1263
- options.submenu && /* @__PURE__ */ jsx(WIcon, { name: "chevron", size: 16, className: "bm-chevron" })
1264
- ]
1265
- },
1266
- label
1267
- );
1268
- return /* @__PURE__ */ jsxs("div", { ref: target, className: "block-anchor", role: "menu", "aria-label": "块操作菜单", style: place, onMouseDown: (event) => event.preventDefault(), children: [
1269
- !props.panelOnly && /* @__PURE__ */ jsxs("div", { className: "block-menu", children: [
1270
- item("h1", "转换为", () => setSubmenu(submenu === "format" ? null : "format"), { submenu: "format" }),
1271
- item("indent", "缩进和对齐", () => setSubmenu(submenu === "indent-align" ? null : "indent-align"), { submenu: "indent-align" }),
1272
- item("palette", "颜色", () => setSubmenu(submenu === "color" ? null : "color"), { submenu: "color" }),
1273
- item("plus", "在下方添加", () => setSubmenu(submenu === "insert-below" ? null : "insert-below"), { submenu: "insert-below" }),
1274
- props.canComment && /* @__PURE__ */ jsx("div", { className: "bm-sep" }),
1275
- props.canComment && item("comment", "评论", () => {
1276
- var _a2;
1277
- (_a2 = props.onRequestComment) == null ? void 0 : _a2.call(props);
1278
- props.onClose();
1279
- }),
1280
- item("cut", "剪切", cutBlock),
1281
- item(copied ? "check" : "copy", copied ? "已复制" : "复制", () => void copyBlock()),
1282
- props.onCopyLink && item("copy-link", "复制本页链接", () => {
1283
- var _a2;
1284
- (_a2 = props.onCopyLink) == null ? void 0 : _a2.call(props);
1285
- props.onClose();
1286
- }),
1287
- /* @__PURE__ */ jsx("div", { className: "bm-sep" }),
1288
- props.onSharePage && item("share", "分享此页面", () => {
1289
- var _a2;
1290
- (_a2 = props.onSharePage) == null ? void 0 : _a2.call(props);
1291
- props.onClose();
1292
- }),
1293
- props.onConvertToSubpage && item("page", "转换为子文档", () => {
1294
- var _a2;
1295
- (_a2 = props.onConvertToSubpage) == null ? void 0 : _a2.call(props, node == null ? void 0 : node.toJSON());
1296
- props.onClose();
1297
- }),
1298
- props.onSaveAsTemplate && item("file", "保存为模板", () => {
1299
- var _a2;
1300
- (_a2 = props.onSaveAsTemplate) == null ? void 0 : _a2.call(props, node == null ? void 0 : node.toJSON());
1301
- props.onClose();
1302
- }),
1303
- /* @__PURE__ */ jsx("div", { className: "bm-sep" }),
1304
- item("trash", "删除", () => action(), { danger: true })
1305
- ] }),
1306
- submenu && /* @__PURE__ */ jsxs(
1307
- "div",
1308
- {
1309
- ref: submenuRef,
1310
- className: `block-submenu${submenu === "color" ? " block-submenu-color" : ""}`,
1311
- role: submenu === "color" ? "dialog" : "menu",
1312
- style: submenuPlace ? { position: "absolute", ...submenuPlace, overflowY: "auto" } : void 0,
1313
- "aria-label": submenu === "color" ? "块颜色" : void 0,
1314
- onMouseEnter: () => setSubmenu(submenu),
1315
- children: [
1316
- submenu === "format" && convertibles.map((command) => {
1317
- const convertible = canConvertMenuBlocks(editor.state, info.pos, command.id);
1318
- const active = editableActive(editor, info.pos, command.id);
1319
- return /* @__PURE__ */ jsxs(
1320
- "button",
1321
- {
1322
- type: "button",
1323
- role: "menuitem",
1324
- className: `bm-item${active ? " active" : ""}${convertible ? "" : " disabled"}`,
1325
- disabled: !convertible,
1326
- "aria-disabled": !convertible,
1327
- onClick: () => {
1328
- convertMenuBlocks(editor, info.pos, command.id);
1329
- props.onClose();
1330
- },
1331
- children: [
1332
- /* @__PURE__ */ jsx(WIcon, { name: command.icon, size: 18 }),
1333
- /* @__PURE__ */ jsx("span", { children: command.label }),
1334
- active && /* @__PURE__ */ jsx(WIcon, { name: "check", size: 16, className: "bm-chevron" })
1335
- ]
1336
- },
1337
- command.id
1338
- );
1339
- }),
1340
- submenu === "indent-align" && /* @__PURE__ */ jsxs(Fragment, { children: [
1341
- item("indent", "增加缩进", () => {
1342
- chain().indentBlock().run();
1343
- props.onClose();
1344
- }, { disabled: !textual }),
1345
- item("outdent", "减少缩进", () => {
1346
- chain().outdentBlock().run();
1347
- props.onClose();
1348
- }, { disabled: !textual }),
1349
- /* @__PURE__ */ jsx("div", { className: "bm-sep" }),
1350
- TEXT_ALIGNS.map((align) => item(
1351
- `align-${align}`,
1352
- align === "left" ? "左对齐" : align === "center" ? "居中对齐" : align === "right" ? "右对齐" : "两端对齐",
1353
- () => {
1354
- chain().setTextAlign(align).run();
1355
- props.onClose();
1356
- },
1357
- { active: currentAlign === align, disabled: !textual }
1358
- ))
1359
- ] }),
1360
- submenu === "color" && /* @__PURE__ */ jsxs("div", { className: "block-color-panel", role: "group", "aria-label": "文档颜色", children: [
1361
- /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
1362
- /* @__PURE__ */ jsx("div", { className: "color-label", children: "字体颜色" }),
1363
- /* @__PURE__ */ jsx("div", { className: "color-grid", role: "group", "aria-label": "字体颜色", children: TEXT_COLORS.map((color) => /* @__PURE__ */ jsx(
1364
- "button",
1365
- {
1366
- type: "button",
1367
- className: `color-choice${textColorActive(editor, color.value) ? " selected" : ""}`,
1368
- "aria-label": `字体颜色:${color.name}`,
1369
- title: `字体颜色:${color.name}`,
1370
- "aria-pressed": textColorActive(editor, color.value),
1371
- onClick: () => {
1372
- const attrs = color.value ? { color: color.value } : null;
1373
- attrs ? chain().setMark("textStyle", attrs).run() : chain().unsetMark("textStyle").run();
1374
- },
1375
- children: /* @__PURE__ */ jsx("span", { className: "font-sample", style: color.value ? { color: color.value } : void 0, children: "A" })
1376
- },
1377
- color.name
1378
- )) })
1379
- ] }),
1380
- /* @__PURE__ */ jsxs("div", { className: "color-section", children: [
1381
- /* @__PURE__ */ jsx("div", { className: "color-label", children: "背景颜色" }),
1382
- /* @__PURE__ */ jsx("div", { className: "color-grid", role: "group", "aria-label": "背景颜色", children: HIGHLIGHT_COLORS.map((color) => /* @__PURE__ */ jsx(
1383
- "button",
1384
- {
1385
- type: "button",
1386
- className: `color-choice background-choice${highlightActive(editor, color.value) ? " selected" : ""}${color.value === null ? " no-background" : ""}`,
1387
- "aria-label": `背景颜色:${color.name}`,
1388
- title: `背景颜色:${color.name}`,
1389
- "aria-pressed": highlightActive(editor, color.value),
1390
- style: color.value ? { background: color.value } : void 0,
1391
- onClick: () => {
1392
- const attrs = color.value ? { color: color.value } : null;
1393
- attrs ? chain().setMark("highlight", attrs).run() : chain().unsetMark("highlight").run();
1394
- },
1395
- children: color.value === null && /* @__PURE__ */ jsx("span", { className: "clear-stroke", "aria-hidden": "true" })
1396
- },
1397
- color.name
1398
- )) })
1399
- ] }),
1400
- /* @__PURE__ */ jsx(WButton, { variant: "outline", block: true, onClick: () => {
1401
- chain().unsetMark("textStyle").unsetMark("highlight").run();
1402
- props.onClose();
1403
- }, children: "恢复默认" })
1404
- ] }),
1405
- submenu === "insert-below" && insertCommands.map((command) => item(command.icon, command.label, () => runInsert(command)))
1406
- ]
1407
- }
1408
- ),
1409
- panel && /* @__PURE__ */ jsx(
1410
- InsertPicker,
1411
- {
1412
- mode: panel.mode,
1413
- x: ((submenuPlace == null ? void 0 : submenuPlace.left) ?? 0) + (((_b = submenuRef.current) == null ? void 0 : _b.offsetWidth) ?? 240) + 6,
1414
- y: Math.max(0, (((_c = rowRef.current) == null ? void 0 : _c.getBoundingClientRect().top) ?? 0) - (((_d = target.current) == null ? void 0 : _d.getBoundingClientRect().top) ?? 0)),
1415
- boundTop: 8 - (((_e = target.current) == null ? void 0 : _e.getBoundingClientRect().top) ?? 0),
1416
- boundBottom: innerHeight - 8 - (((_f = target.current) == null ? void 0 : _f.getBoundingClientRect().top) ?? 0),
1417
- onPick: insertWithPanel,
1418
- onClose: () => setPanel(null)
1419
- }
1420
- )
1421
- ] });
1422
- }
1423
- function editableActive(editor, pos, id) {
1424
- const range = menuBlockRange(editor.state, pos);
1425
- if (!range) return false;
1426
- const node = range.nodes[0];
1427
- if (!node) return false;
1428
- if (id === "text") return node.type.name === "paragraph";
1429
- if (id === "h1") return node.type.name === "heading" && node.attrs.level === 1;
1430
- if (id === "h2") return node.type.name === "heading" && node.attrs.level === 2;
1431
- if (id === "h3") return node.type.name === "heading" && node.attrs.level === 3;
1432
- if (id === "h4") return node.type.name === "heading" && node.attrs.level === 4;
1433
- if (id === "h5") return node.type.name === "heading" && node.attrs.level === 5;
1434
- if (id === "h6") return node.type.name === "heading" && node.attrs.level === 6;
1435
- return node.type.name === { ordered: "orderedList", bullet: "bulletList", quote: "blockquote", code: "codeBlock", task: "taskList", callout: "callout" }[id];
1436
- }
1437
- function textColorActive(editor, value) {
1438
- const color = editor.getAttributes("textStyle").color;
1439
- return value === null ? typeof color !== "string" : color === value;
1440
- }
1441
- function highlightActive(editor, value) {
1442
- const color = editor.getAttributes("highlight").color;
1443
- return value === null ? typeof color !== "string" : color === value;
1444
- }
1445
- async function writeRichTable(node, schema2) {
1446
- var _a;
1447
- if (typeof ClipboardItem === "undefined" || !((_a = navigator.clipboard) == null ? void 0 : _a.write)) {
1448
- throw new Error("rich clipboard unavailable");
1449
- }
1450
- const container = document.createElement("div");
1451
- container.appendChild(DOMSerializer.fromSchema(schema2).serializeFragment(Fragment$1.from(node)));
1452
- await navigator.clipboard.write([new ClipboardItem({
1453
- "text/html": new Blob([container.innerHTML], { type: "text/html" }),
1454
- "text/plain": new Blob([node.textBetween(0, node.content.size, "\n")], { type: "text/plain" })
1455
- })]);
1456
- }
1457
- async function copyTableToClipboard(editor, pos, cut, write = writeRichTable) {
1458
- if (editor.isDestroyed || !editor.isEditable) return null;
1459
- const originalDoc = editor.state.doc;
1460
- const node = originalDoc.nodeAt(pos);
1461
- if ((node == null ? void 0 : node.type.spec.tableRole) !== "table") return null;
1462
- await write(node, editor.state.schema);
1463
- if (!cut) return "copied";
1464
- if (editor.isDestroyed || !editor.isEditable || editor.state.doc !== originalDoc || editor.state.doc.nodeAt(pos) !== node) return "changed";
1465
- editor.view.dispatch(editor.state.tr.deleteRange(pos, pos + node.nodeSize));
1466
- editor.view.focus();
1467
- return "cut";
1468
- }
1469
- function placeTableToolbar(selection, toolbar, viewport) {
1470
- const inset = 12, gap = 14;
1471
- const width = Math.min(toolbar.width, Math.max(0, viewport.width - inset * 2));
1472
- const above = selection.top - gap - toolbar.height;
1473
- return {
1474
- left: Math.max(inset, Math.min(
1475
- (selection.left + selection.right - width) / 2,
1476
- viewport.width - width - inset
1477
- )),
1478
- top: above >= viewport.topInset ? above : Math.max(viewport.topInset, Math.min(
1479
- selection.top + 40,
1480
- viewport.height - toolbar.height - inset
1481
- ))
1482
- };
1483
- }
1484
- function placeTableMenu(anchor, menu, viewport) {
1485
- const inset = 12, gap = 6, topInset = viewport.topInset;
1486
- const anchorTop = Math.max(topInset, Math.min(anchor.top, viewport.height - inset));
1487
- const anchorBottom = Math.max(anchorTop, Math.min(anchor.bottom, viewport.height - inset));
1488
- const below = Math.max(0, viewport.height - inset - anchorBottom - gap);
1489
- const above = Math.max(0, anchorTop - gap - topInset);
1490
- const useBelow = menu.height <= below || menu.height > above && below >= above;
1491
- const maxHeight = useBelow ? below : above;
1492
- const height = Math.min(menu.height, maxHeight);
1493
- return {
1494
- left: Math.max(inset, Math.min(anchor.left, viewport.width - menu.width - inset)),
1495
- top: useBelow ? anchorBottom + gap : anchorTop - gap - height,
1496
- maxHeight
1497
- };
1498
- }
1499
- function attachOverlayScrollbar(host, viewport, content) {
1500
- const track = document.createElement("div"), thumb = document.createElement("div");
1501
- track.className = "w-overlay-scrollbar__track";
1502
- thumb.className = "w-overlay-scrollbar__thumb";
1503
- const originalId = viewport.id;
1504
- if (!viewport.id) viewport.id = `wd-scroll-${crypto.randomUUID()}`;
1505
- thumb.tabIndex = 0;
1506
- thumb.setAttribute("role", "scrollbar");
1507
- thumb.setAttribute("aria-label", "菜单滚动条");
1508
- thumb.setAttribute("aria-orientation", "vertical");
1509
- thumb.setAttribute("aria-controls", viewport.id);
1510
- thumb.setAttribute("aria-valuemin", "0");
1511
- track.append(thumb);
1512
- host.append(track);
1513
- let timer;
1514
- let drag;
1515
- let thumbHeight = 0;
1516
- const events = new AbortController();
1517
- function active() {
1518
- host.classList.add("is-scrolling");
1519
- clearTimeout(timer);
1520
- timer = setTimeout(() => {
1521
- if (!drag) host.classList.remove("is-scrolling");
1522
- }, 700);
1523
- }
1524
- function update() {
1525
- const maximum = Math.max(0, viewport.scrollHeight - viewport.clientHeight);
1526
- track.hidden = maximum <= 1;
1527
- const height = track.clientHeight;
1528
- thumbHeight = Math.min(height, Math.max(28, height * viewport.clientHeight / Math.max(1, viewport.scrollHeight)));
1529
- thumb.style.height = `${thumbHeight}px`;
1530
- thumb.style.transform = `translateY(${maximum ? (height - thumbHeight) * viewport.scrollTop / maximum : 0}px)`;
1531
- thumb.setAttribute("aria-valuemax", String(Math.round(maximum)));
1532
- thumb.setAttribute("aria-valuenow", String(Math.round(viewport.scrollTop)));
1533
- }
1534
- viewport.addEventListener("scroll", () => {
1535
- update();
1536
- active();
1537
- }, { passive: true, signal: events.signal });
1538
- thumb.addEventListener("pointerdown", (event) => {
1539
- if (event.button !== 0) return;
1540
- event.preventDefault();
1541
- event.stopPropagation();
1542
- thumb.focus({ preventScroll: true });
1543
- drag = {
1544
- y: event.clientY,
1545
- top: viewport.scrollTop,
1546
- maximum: viewport.scrollHeight - viewport.clientHeight,
1547
- travel: Math.max(1, track.clientHeight - thumbHeight)
1548
- };
1549
- host.classList.add("is-dragging-scrollbar");
1550
- active();
1551
- }, { signal: events.signal });
1552
- window.addEventListener("pointermove", (event) => {
1553
- if (!drag) return;
1554
- event.preventDefault();
1555
- viewport.scrollTop = Math.max(0, Math.min(drag.maximum, drag.top + (event.clientY - drag.y) * drag.maximum / drag.travel));
1556
- update();
1557
- }, { signal: events.signal });
1558
- function finish() {
1559
- if (!drag) return;
1560
- drag = void 0;
1561
- host.classList.remove("is-dragging-scrollbar");
1562
- active();
1563
- }
1564
- window.addEventListener("pointerup", finish, { signal: events.signal });
1565
- window.addEventListener("pointercancel", finish, { signal: events.signal });
1566
- window.addEventListener("blur", finish, { signal: events.signal });
1567
- track.addEventListener("pointerdown", (event) => {
1568
- if (event.target !== track || event.button !== 0) return;
1569
- event.preventDefault();
1570
- event.stopPropagation();
1571
- const top = track.getBoundingClientRect().top;
1572
- viewport.scrollTop = (event.clientY - top - thumbHeight / 2) / Math.max(1, track.clientHeight - thumbHeight) * (viewport.scrollHeight - viewport.clientHeight);
1573
- update();
1574
- active();
1575
- }, { signal: events.signal });
1576
- thumb.addEventListener("keydown", (event) => {
1577
- const deltas = { ArrowDown: 32, ArrowUp: -32, PageDown: viewport.clientHeight * 0.8, PageUp: -viewport.clientHeight * 0.8 };
1578
- const delta = deltas[event.key];
1579
- if (delta === void 0 && event.key !== "Home" && event.key !== "End") return;
1580
- event.preventDefault();
1581
- event.stopPropagation();
1582
- viewport.scrollTop = event.key === "Home" ? 0 : event.key === "End" ? viewport.scrollHeight : viewport.scrollTop + delta;
1583
- update();
1584
- active();
1585
- }, { signal: events.signal });
1586
- const resize = new ResizeObserver(update);
1587
- resize.observe(viewport);
1588
- resize.observe(content);
1589
- resize.observe(host);
1590
- update();
1591
- return {
1592
- update,
1593
- destroy() {
1594
- events.abort();
1595
- resize.disconnect();
1596
- clearTimeout(timer);
1597
- track.remove();
1598
- host.classList.remove("is-scrolling", "is-dragging-scrollbar");
1599
- if (!originalId) viewport.removeAttribute("id");
1600
- }
1601
- };
1602
- }
1603
- const CORNER_W = 44, CORNER_H = 26, GUTTER_GAP = 8;
1604
- const anchorOf = (event) => {
1605
- const rect = event.currentTarget.getBoundingClientRect();
1606
- return { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom };
1607
- };
1608
- function TableControls({ editor, hover, revision, onNotice, onBlockDrag }) {
1609
- var _a, _b, _c;
1610
- const { portal } = useContentContext(), axisCleanup = useRef(null);
1611
- const [dismissed, setDismissed] = useState(false), [panel, setPanel] = useState("none");
1612
- const [menuOpen, setMenuOpen] = useState(false), [menuScope, setMenuScope] = useState("table");
1613
- const [menuFromToolbar, setMenuFromToolbar] = useState(false);
1614
- const [showStyle, setShowStyle] = useState(false), [widthOpen, setWidthOpen] = useState(false), [width, setWidth] = useState(""), [busy, setBusy] = useState(false);
1615
- const [toolbarSize, setToolbarSize] = useState({ width: 0, height: 0 });
1616
- const [, setViewportRevision] = useState(0);
1617
- const [menuAnchor, setMenuAnchor] = useState(null), [panelAnchor, setPanelAnchor] = useState(null);
1618
- const menuRef = useRef(null), panelRef = useRef(null);
1619
- const menuViewport = useRef(null), menuContent = useRef(null);
1620
- const menuTrigger = useRef(null), panelTrigger = useRef(null);
1621
- const [menuPlace, setMenuPlace] = useState({ left: 0, top: 0, maxHeight: 480 }), [panelPlace, setPanelPlace] = useState({ left: 0, top: 0 });
1622
- useLayoutEffect(() => {
1623
- var _a2;
1624
- if (!menuOpen || !menuAnchor) return;
1625
- const element = menuRef.current;
1626
- if (!element) return;
1627
- setMenuPlace(placeTableMenu(menuAnchor, { width: element.offsetWidth, height: (((_a2 = menuViewport.current) == null ? void 0 : _a2.scrollHeight) ?? element.scrollHeight) + 2 }, { width: innerWidth, height: innerHeight, topInset: 12 }));
1628
- }, [menuOpen, menuAnchor, widthOpen, showStyle, menuScope]);
1629
- useEffect(() => {
1630
- var _a2;
1631
- if (!menuOpen || !menuRef.current || !menuViewport.current || !menuContent.current) return;
1632
- const scrollbar = attachOverlayScrollbar(menuRef.current, menuViewport.current, menuContent.current);
1633
- (_a2 = menuRef.current.querySelector('[role="menuitem"]')) == null ? void 0 : _a2.focus({ preventScroll: true });
1634
- return () => scrollbar.destroy();
1635
- }, [menuOpen, portal]);
1636
- useLayoutEffect(() => {
1637
- if (panel === "none" || !panelAnchor) return;
1638
- const measure = () => {
1639
- var _a2;
1640
- const element = panelRef.current, trigger = (_a2 = panelTrigger.current) == null ? void 0 : _a2.getBoundingClientRect();
1641
- if (!element || !trigger) return;
1642
- const placement = placePanel({
1643
- trigger,
1644
- panel: { width: element.offsetWidth, height: element.offsetHeight },
1645
- position: "bl",
1646
- gap: 6,
1647
- viewport: { width: innerWidth, height: innerHeight }
1648
- });
1649
- setPanelPlace((previous) => previous.left === placement.left && previous.top === placement.top ? previous : { left: placement.left, top: placement.top });
1650
- };
1651
- measure();
1652
- const observer = new ResizeObserver(measure);
1653
- if (panelRef.current) observer.observe(panelRef.current);
1654
- let frame = 0;
1655
- const scroll = () => {
1656
- cancelAnimationFrame(frame);
1657
- frame = requestAnimationFrame(measure);
1658
- };
1659
- window.addEventListener("scroll", scroll, true);
1660
- return () => {
1661
- observer.disconnect();
1662
- cancelAnimationFrame(frame);
1663
- window.removeEventListener("scroll", scroll, true);
1664
- };
1665
- });
1666
- useEffect(() => {
1667
- const update = (event) => {
1668
- var _a2, _b2;
1669
- if (event.target instanceof Node && (((_a2 = menuRef.current) == null ? void 0 : _a2.contains(event.target)) || ((_b2 = panelRef.current) == null ? void 0 : _b2.contains(event.target)))) return;
1670
- setViewportRevision((value) => value + 1);
1671
- };
1672
- window.addEventListener("scroll", update, true);
1673
- window.addEventListener("resize", update);
1674
- return () => {
1675
- window.removeEventListener("scroll", update, true);
1676
- window.removeEventListener("resize", update);
1677
- };
1678
- }, []);
1679
- const selectionRef = useRef(editor.state.selection), docRef = useRef(editor.state.doc);
1680
- useEffect(() => {
1681
- if (!selectionRef.current.eq(editor.state.selection) || docRef.current !== editor.state.doc) {
1682
- setPanel("none");
1683
- setDismissed(false);
1684
- setWidthOpen(false);
1685
- selectionRef.current = editor.state.selection;
1686
- docRef.current = editor.state.doc;
1687
- }
1688
- }, [revision, editor]);
1689
- const activeCell = currentTableCellPosition(editor.view);
1690
- const cellPos = (hover == null ? void 0 : hover.cellPos) ?? activeCell;
1691
- const fresh = cellPos == null ? null : readTableGeometry(editor.view, cellPos);
1692
- const geometry = fresh && hover ? { ...fresh, rowIndex: hover.rowIndex, colIndex: hover.colIndex } : fresh;
1693
- const cellSelection = editor.state.selection instanceof CellSelection ? editor.state.selection : null;
1694
- const showControls = !!hover || !!cellSelection || menuOpen;
1695
- const canMerge = editor.can().mergeCells(), canSplit = editor.can().splitCell();
1696
- const headerRow = tableHeaderActive(editor, "row"), headerColumn = tableHeaderActive(editor, "column");
1697
- const currentAlign = editor.getAttributes("tableCell").align ?? editor.getAttributes("tableHeader").align;
1698
- const currentTextColor = typeof editor.getAttributes("textStyle").color === "string" ? editor.getAttributes("textStyle").color : null;
1699
- const currentHighlight = typeof editor.getAttributes("highlight").color === "string" ? editor.getAttributes("highlight").color : null;
1700
- const currentCellBackground = typeof (editor.getAttributes("tableCell").backgroundColor ?? editor.getAttributes("tableHeader").backgroundColor) === "string" ? editor.getAttributes("tableCell").backgroundColor ?? editor.getAttributes("tableHeader").backgroundColor : null;
1701
- const chain = () => editor.chain().focus();
1702
- const shell = editor.view.dom.closest(".editor-shell");
1703
- useEffect(() => {
1704
- const context = (event) => {
1705
- if (!editor.isEditable || !(event.target instanceof Element)) return;
1706
- const cell = event.target.closest("td,th");
1707
- if (!cell) return;
1708
- const pos = cellPositionAtDOM(editor.view, cell);
1709
- if (pos === null) return;
1710
- event.preventDefault();
1711
- selectTableCell(editor.view, pos);
1712
- setPanel("none");
1713
- setMenuScope("cell");
1714
- setMenuFromToolbar(false);
1715
- menuTrigger.current = null;
1716
- setShowStyle(false);
1717
- setMenuAnchor({ left: event.clientX, right: event.clientX, top: event.clientY, bottom: event.clientY });
1718
- setMenuOpen(true);
1719
- };
1720
- editor.view.dom.addEventListener("contextmenu", context, true);
1721
- return () => {
1722
- var _a2;
1723
- if (!editor.isDestroyed) editor.view.dom.removeEventListener("contextmenu", context, true);
1724
- (_a2 = axisCleanup.current) == null ? void 0 : _a2.call(axisCleanup);
1725
- };
1726
- }, [editor]);
1727
- useEffect(() => {
1728
- if (!menuOpen && panel === "none") return;
1729
- const close = (event) => {
1730
- var _a2, _b2;
1731
- if (event.target instanceof Element && (((_a2 = menuRef.current) == null ? void 0 : _a2.contains(event.target)) || ((_b2 = panelRef.current) == null ? void 0 : _b2.contains(event.target)) || event.target.closest(".tbl-ui-host"))) return;
1732
- setMenuOpen(false);
1733
- setPanel("none");
1734
- };
1735
- const resize = () => {
1736
- setMenuOpen(false);
1737
- setPanel("none");
1738
- };
1739
- const scroll = (event) => {
1740
- var _a2, _b2;
1741
- if (event.target instanceof Node && (((_a2 = menuRef.current) == null ? void 0 : _a2.contains(event.target)) || ((_b2 = panelRef.current) == null ? void 0 : _b2.contains(event.target)))) return;
1742
- setMenuOpen(false);
1743
- };
1744
- document.addEventListener("pointerdown", close, true);
1745
- window.addEventListener("resize", resize);
1746
- window.addEventListener("scroll", scroll, true);
1747
- return () => {
1748
- document.removeEventListener("pointerdown", close, true);
1749
- window.removeEventListener("resize", resize);
1750
- window.removeEventListener("scroll", scroll, true);
1751
- };
1752
- }, [menuOpen, panel]);
1753
- function axisDown(axis, index, event) {
1754
- var _a2;
1755
- if (!geometry || event.button !== 0) return;
1756
- event.preventDefault();
1757
- (_a2 = axisCleanup.current) == null ? void 0 : _a2.call(axisCleanup);
1758
- const view = editor.view, doc = view.state.doc, original = view.state.selection;
1759
- const cell = doc.resolve(geometry.cellPos), table = cell.node(-1), start = cell.start(-1), map = TableMap.get(table);
1760
- const axisCells = axis === "row" ? geometry.rows : geometry.columns;
1761
- const host = shell == null ? void 0 : shell.getBoundingClientRect();
1762
- if (!host) return;
1763
- const select = (endIndex) => {
1764
- const lo = Math.min(index, endIndex), hi = Math.max(index, endIndex);
1765
- const first = axis === "row" ? lo * map.width : lo;
1766
- const last = axis === "row" ? (hi + 1) * map.width - 1 : (map.height - 1) * map.width + hi;
1767
- view.dispatch(view.state.tr.setSelection(CellSelection.create(doc, start + map.map[first], start + map.map[last])));
1768
- view.focus();
1769
- };
1770
- select(index);
1771
- const move = (next) => {
1772
- var _a3;
1773
- if (!editor.isEditable || view.state.doc !== doc) {
1774
- (_a3 = axisCleanup.current) == null ? void 0 : _a3.call(axisCleanup);
1775
- return;
1776
- }
1777
- const coordinate = axis === "row" ? next.clientY - host.top : next.clientX - host.left;
1778
- const closest = axisCells.reduce((best, item) => Math.abs(item.start + item.size / 2 - coordinate) < Math.abs(best.start + best.size / 2 - coordinate) ? item : best);
1779
- select(closest.index);
1780
- next.preventDefault();
1781
- };
1782
- const key = (next) => {
1783
- var _a3;
1784
- if (next.key === "Escape") {
1785
- if (view.state.doc === doc) view.dispatch(view.state.tr.setSelection(original));
1786
- next.preventDefault();
1787
- (_a3 = axisCleanup.current) == null ? void 0 : _a3.call(axisCleanup);
1788
- }
1789
- };
1790
- const done = () => {
1791
- var _a3;
1792
- return (_a3 = axisCleanup.current) == null ? void 0 : _a3.call(axisCleanup);
1793
- };
1794
- axisCleanup.current = () => {
1795
- window.removeEventListener("mousemove", move, true);
1796
- window.removeEventListener("mouseup", done, true);
1797
- window.removeEventListener("keydown", key, true);
1798
- window.removeEventListener("blur", done);
1799
- axisCleanup.current = null;
1800
- };
1801
- window.addEventListener("mousemove", move, true);
1802
- window.addEventListener("mouseup", done, true);
1803
- window.addEventListener("keydown", key, true);
1804
- window.addEventListener("blur", done);
1805
- }
1806
- useEffect(() => {
1807
- const escape = (event) => {
1808
- var _a2, _b2;
1809
- if (event.key !== "Escape" || event.isComposing) return;
1810
- if (panel !== "none") {
1811
- event.preventDefault();
1812
- setPanel("none");
1813
- (_a2 = panelTrigger.current) == null ? void 0 : _a2.focus({ preventScroll: true });
1814
- } else if (menuOpen) {
1815
- event.preventDefault();
1816
- setMenuOpen(false);
1817
- (_b2 = menuTrigger.current) == null ? void 0 : _b2.focus({ preventScroll: true });
1818
- } else if (cellSelection) {
1819
- event.preventDefault();
1820
- setDismissed(true);
1821
- editor.view.focus();
1822
- }
1823
- };
1824
- document.addEventListener("keydown", escape);
1825
- return () => document.removeEventListener("keydown", escape);
1826
- });
1827
- function cornerLeft() {
1828
- if (!geometry || !shell) return 0;
1829
- return Math.max(geometry.contentLeft - GUTTER_GAP - CORNER_W, 8 - shell.getBoundingClientRect().left);
1830
- }
1831
- function selectedBounds() {
1832
- var _a2;
1833
- if (!shell || !cellSelection) return null;
1834
- const rects = [];
1835
- cellSelection.forEachCell((_cell, pos) => {
1836
- const dom = editor.view.nodeDOM(pos);
1837
- if (dom instanceof HTMLElement) rects.push(dom.getBoundingClientRect());
1838
- });
1839
- const table = geometry ? (_a2 = editor.view.nodeDOM(geometry.cellPos)) == null ? void 0 : _a2.closest("table") : null;
1840
- if (!table) return null;
1841
- const bounds = clippedTableSelection(rects, (table.closest(".tableWrapper") ?? table).getBoundingClientRect());
1842
- if (!bounds) return null;
1843
- const host = shell.getBoundingClientRect();
1844
- const placement = placeTableToolbar(bounds, toolbarSize, { width: innerWidth, height: innerHeight, topInset: 66 });
1845
- return { left: bounds.left - host.left, top: bounds.top - host.top, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top, toolbarLeft: placement.left, toolbarTop: placement.top };
1846
- }
1847
- const selection = selectedBounds();
1848
- function mark(name) {
1849
- return tableSelectionMarkActive(editor, name);
1850
- }
1851
- function toggleMark(name) {
1852
- setTableSelectionMark(editor, name, mark(name) ? null : {});
1853
- }
1854
- function setColor(name, value) {
1855
- setTableSelectionMark(editor, name, value ? { color: value } : null);
1856
- setPanel("none");
1857
- }
1858
- function applySwatch(value) {
1859
- if (panel === "background") {
1860
- chain().setCellAttribute("backgroundColor", value).run();
1861
- setPanel("none");
1862
- } else setColor(panel === "color" ? "textStyle" : "highlight", value);
1863
- }
1864
- function showContext(event, scope = "table") {
1865
- var _a2;
1866
- if (!event) return;
1867
- const trigger = event.currentTarget;
1868
- if (menuOpen && menuTrigger.current === trigger) {
1869
- setMenuOpen(false);
1870
- return;
1871
- }
1872
- menuTrigger.current = trigger;
1873
- const bar = (_a2 = trigger.closest(".tbl-cellbar")) == null ? void 0 : _a2.getBoundingClientRect();
1874
- if (scope === "table" && geometry) selectTableAxis(editor.view, geometry.cellPos, "table");
1875
- const anchor = anchorOf(event);
1876
- setPanel("none");
1877
- setShowStyle(false);
1878
- setMenuScope(scope);
1879
- setMenuFromToolbar(!!bar);
1880
- setMenuAnchor({ ...anchor, top: (bar == null ? void 0 : bar.top) ?? anchor.top, bottom: (bar == null ? void 0 : bar.bottom) ?? anchor.bottom });
1881
- setMenuOpen(true);
1882
- }
1883
- async function copyTable(cut) {
1884
- const target = tableTarget();
1885
- if (!target || busy || !editor.isEditable) return;
1886
- setBusy(true);
1887
- try {
1888
- const result = await copyTableToClipboard(editor, target.pos, cut);
1889
- if (result === "changed") onNotice == null ? void 0 : onNotice("表格已复制;内容发生变化,未执行剪切。");
1890
- else if (result) onNotice == null ? void 0 : onNotice(result === "cut" ? "表格已剪切。" : "表格已复制。");
1891
- setMenuOpen(false);
1892
- } catch {
1893
- onNotice == null ? void 0 : onNotice("无法写入剪贴板,请允许浏览器访问剪贴板后重试。");
1894
- } finally {
1895
- setBusy(false);
1896
- }
1897
- }
1898
- function applyStyle(color) {
1899
- setTableHeaderBackground(editor, color);
1900
- setMenuOpen(false);
1901
- editor.view.focus();
1902
- }
1903
- function insertParagraphBelow() {
1904
- const target = tableTarget();
1905
- if (!target) return;
1906
- const end = target.pos + target.node.nodeSize;
1907
- editor.chain().focus().insertContentAt(end, { type: "paragraph" }).setTextSelection(end + 1).run();
1908
- setMenuOpen(false);
1909
- }
1910
- function showPanel(name, event) {
1911
- if (panel === name) {
1912
- setPanel("none");
1913
- return;
1914
- }
1915
- panelTrigger.current = event.currentTarget;
1916
- setMenuOpen(false);
1917
- setPanelAnchor(anchorOf(event));
1918
- setPanel(name);
1919
- }
1920
- function tableTarget() {
1921
- const pos = cellPos == null ? null : tablePositionAtCell(editor.view, cellPos);
1922
- const node = pos === null ? null : editor.state.doc.nodeAt(pos);
1923
- return pos !== null && node ? { pos, node } : null;
1924
- }
1925
- function distribute() {
1926
- const target = tableTarget(), dom = target ? editor.view.nodeDOM(target.pos) : null;
1927
- const table = dom instanceof HTMLElement ? dom.matches("table") ? dom : dom.querySelector("table") : null;
1928
- const wrapper = (table == null ? void 0 : table.closest(".tableWrapper")) ?? table;
1929
- if (wrapper) distributeTableColumns(editor, wrapper.clientWidth - 2);
1930
- setMenuOpen(false);
1931
- }
1932
- function applyWidth() {
1933
- var _a2;
1934
- const col = (_a2 = currentCellRect(editor)) == null ? void 0 : _a2.col, px = Math.round(Number(width));
1935
- if (col == null || !Number.isFinite(px) || px < 40 || px > 2e3) return;
1936
- setColumnWidth(editor, col, px);
1937
- setWidthOpen(false);
1938
- setMenuOpen(false);
1939
- }
1940
- const colorEntries = [
1941
- { key: "color", label: "表格文字颜色", tip: "文字颜色", hint: "只改文字本身的颜色,不影响格子底色", colors: TEXT_COLORS, current: currentTextColor },
1942
- { key: "highlight", label: "表格文字高亮", tip: "文字高亮", hint: "只给文字加荧光底色,格子仍是原本的底色", colors: HIGHLIGHT_COLORS, current: currentHighlight },
1943
- { key: "background", label: "单元格底色", tip: "单元格底色", hint: "给整个单元格铺底色,与文字高亮互不影响", colors: CELL_BACKGROUNDS, current: currentCellBackground }
1944
- ];
1945
- const menuItem = (icon, label, onClick, options = {}) => /* @__PURE__ */ jsxs(
1946
- "button",
1947
- {
1948
- type: "button",
1949
- role: "menuitem",
1950
- className: `ctx-item${options.danger ? " danger" : ""}`,
1951
- disabled: options.disabled,
1952
- "aria-checked": options.checked,
1953
- "aria-expanded": options.expanded,
1954
- onMouseDown: (event) => event.preventDefault(),
1955
- onClick,
1956
- children: [
1957
- /* @__PURE__ */ jsx(WIcon, { name: icon, size: 16 }),
1958
- /* @__PURE__ */ jsx("span", { children: label }),
1959
- options.checked && /* @__PURE__ */ jsx(WIcon, { name: "check", size: 16, className: "ctx-chevron" }),
1960
- options.expanded !== void 0 && /* @__PURE__ */ jsx(WIcon, { name: "chevron-down", size: 14, className: "ctx-chevron" })
1961
- ]
1962
- }
1963
- );
1964
- const tool = (label, icon, onClick, active = false, disabled = false, extra) => /* @__PURE__ */ jsxs(
1965
- "button",
1966
- {
1967
- type: "button",
1968
- className: `tool${active ? " on" : ""}`,
1969
- "aria-label": label,
1970
- title: label,
1971
- "aria-pressed": active,
1972
- "aria-disabled": disabled || void 0,
1973
- onClick: () => {
1974
- if (!disabled) onClick();
1975
- },
1976
- children: [
1977
- /* @__PURE__ */ jsx(WIcon, { name: icon, size: ["bold", "italic", "strike", "underline"].includes(icon) ? 20 : 18 }),
1978
- extra
1979
- ]
1980
- }
1981
- );
1982
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1983
- /* @__PURE__ */ jsxs("div", { className: "tbl-ui-host", children: [
1984
- geometry && showControls && /* @__PURE__ */ jsxs(Fragment, { children: [
1985
- /* @__PURE__ */ jsxs("div", { className: "tbl-corner", style: { left: cornerLeft(), top: geometry.tableTop + (((_a = geometry.rows[0]) == null ? void 0 : _a.size) ?? CORNER_H) / 2 - CORNER_H / 2 }, children: [
1986
- /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "表格操作", "aria-haspopup": "menu", title: "表格操作", onMouseDown: (event) => event.preventDefault(), onClick: (event) => showContext(event, "table"), children: /* @__PURE__ */ jsx(WIcon, { name: "table", size: 18 }) }),
1987
- /* @__PURE__ */ jsx(
1988
- "button",
1989
- {
1990
- type: "button",
1991
- className: "tbl-grip",
1992
- "aria-label": "拖动或打开表格菜单",
1993
- "aria-haspopup": "menu",
1994
- title: "拖动表格",
1995
- draggable: false,
1996
- onMouseDown: (event) => {
1997
- const pos = tablePositionAtCell(editor.view, geometry.cellPos), anchor = anchorOf(event);
1998
- if (pos !== null) onBlockDrag == null ? void 0 : onBlockDrag(pos, event.nativeEvent, () => {
1999
- selectTableAxis(editor.view, geometry.cellPos, "table");
2000
- setMenuScope("table");
2001
- setMenuFromToolbar(false);
2002
- setShowStyle(false);
2003
- setMenuAnchor(anchor);
2004
- setMenuOpen(true);
2005
- });
2006
- },
2007
- onClick: (event) => {
2008
- if (event.detail === 0) showContext(event, "table");
2009
- },
2010
- children: /* @__PURE__ */ jsx(WIcon, { name: "handle", size: 16 })
2011
- }
2012
- )
2013
- ] }),
2014
- geometry.rows.map((row) => /* @__PURE__ */ jsx(
2015
- "button",
2016
- {
2017
- type: "button",
2018
- className: `tbl-axis tbl-row-axis${row.index === geometry.rowIndex ? " current" : ""}`,
2019
- "aria-label": `选择第 ${row.index + 1} 行`,
2020
- title: `选择第 ${row.index + 1} 行`,
2021
- style: { left: geometry.tableLeft - 10, top: row.start + row.size / 2 - 12 },
2022
- onMouseDown: (event) => axisDown("row", row.index, event),
2023
- onClick: (event) => {
2024
- if (event.detail === 0) selectTableAxis(editor.view, geometry.cellPos, "row", row.index);
2025
- }
2026
- },
2027
- `row-${row.index}`
2028
- )),
2029
- geometry.columns.map((col) => /* @__PURE__ */ jsx(
2030
- "button",
2031
- {
2032
- type: "button",
2033
- className: `tbl-axis tbl-col-axis${col.index === geometry.colIndex ? " current" : ""}`,
2034
- "aria-label": `选择第 ${col.index + 1} 列`,
2035
- title: `选择第 ${col.index + 1} 列`,
2036
- style: { left: col.start + col.size / 2 - 12, top: geometry.tableTop - 10 },
2037
- onMouseDown: (event) => axisDown("column", col.index, event),
2038
- onClick: (event) => {
2039
- if (event.detail === 0) selectTableAxis(editor.view, geometry.cellPos, "column", col.index);
2040
- }
2041
- },
2042
- `col-${col.index}`
2043
- )),
2044
- [...geometry.rows.map((row) => ({ index: row.index, start: row.start })), { index: geometry.rows.length, start: geometry.tableTop + geometry.tableHeight }].map((boundary) => /* @__PURE__ */ jsx(
2045
- "button",
2046
- {
2047
- type: "button",
2048
- className: "tbl-boundary tbl-boundary-row",
2049
- "aria-label": `在第 ${boundary.index + 1} 行位置添加一行`,
2050
- title: "添加一行",
2051
- style: { left: geometry.tableLeft - 14, top: boundary.start - 10, "--insert-length": `${geometry.tableWidth}px` },
2052
- onMouseDown: (event) => event.preventDefault(),
2053
- onClick: () => insertTableBoundary(editor, geometry.cellPos, "row", boundary.index),
2054
- children: /* @__PURE__ */ jsx(WIcon, { name: "plus", size: 14 })
2055
- },
2056
- `row-boundary-${boundary.index}`
2057
- )),
2058
- [...geometry.columns.map((col) => ({ index: col.index, start: col.start })), { index: geometry.columns.length, start: (((_b = geometry.columns.at(-1)) == null ? void 0 : _b.start) ?? 0) + (((_c = geometry.columns.at(-1)) == null ? void 0 : _c.size) ?? 0) }].map((boundary) => /* @__PURE__ */ jsx(
2059
- "button",
2060
- {
2061
- type: "button",
2062
- className: "tbl-boundary tbl-boundary-col",
2063
- "aria-label": `在第 ${boundary.index + 1} 列位置添加一列`,
2064
- title: "添加一列",
2065
- style: { left: boundary.start - 10, top: geometry.tableTop - 14, "--insert-length": `${geometry.tableHeight}px` },
2066
- onMouseDown: (event) => event.preventDefault(),
2067
- onClick: () => insertTableBoundary(editor, geometry.cellPos, "column", boundary.index),
2068
- children: /* @__PURE__ */ jsx(WIcon, { name: "plus", size: 14 })
2069
- },
2070
- `col-boundary-${boundary.index}`
2071
- ))
2072
- ] }),
2073
- selection && /* @__PURE__ */ jsxs(Fragment, { children: [
2074
- /* @__PURE__ */ jsx("div", { className: "tbl-selection", style: { left: selection.left, top: selection.top, width: selection.width, height: selection.height } }),
2075
- (!menuOpen || menuFromToolbar) && /* @__PURE__ */ jsx(
2076
- "button",
2077
- {
2078
- type: "button",
2079
- className: "tbl-selection-more",
2080
- "aria-label": "打开单元格格式",
2081
- "aria-expanded": !!cellSelection && !dismissed,
2082
- style: { left: selection.left + selection.width / 2 - 16, top: selection.top - 10 },
2083
- onMouseDown: (event) => event.preventDefault(),
2084
- onClick: () => {
2085
- if (activeCell == null) return;
2086
- if (!cellSelection) {
2087
- editor.view.dispatch(editor.state.tr.setSelection(CellSelection.create(editor.state.doc, activeCell)));
2088
- editor.view.focus();
2089
- setDismissed(false);
2090
- } else setDismissed(!dismissed);
2091
- },
2092
- children: "···"
2093
- }
2094
- ),
2095
- cellSelection && !dismissed && (!menuOpen || menuFromToolbar) && /* @__PURE__ */ jsx(
2096
- "div",
2097
- {
2098
- className: "tbl-cellbar",
2099
- role: "toolbar",
2100
- "aria-label": "表格选区格式",
2101
- ref: (element) => {
2102
- if (element) {
2103
- const { width: w, height: h } = element.getBoundingClientRect();
2104
- if (w !== toolbarSize.width || h !== toolbarSize.height) setToolbarSize({ width: w, height: h });
2105
- }
2106
- },
2107
- style: { left: selection.toolbarLeft, top: selection.toolbarTop },
2108
- onMouseDown: (event) => event.preventDefault(),
2109
- children: /* @__PURE__ */ jsxs("div", { className: "tbl-tools", children: [
2110
- tool("标题列", "table-header-column", () => chain().toggleHeaderColumn().run(), headerColumn),
2111
- tool("标题行", "table-header-row", () => chain().toggleHeaderRow().run(), headerRow),
2112
- /* @__PURE__ */ jsx("span", { className: "tool-sep", "aria-hidden": "true" }),
2113
- tool("合并单元格", "merge", () => chain().mergeCells().run(), false, !canMerge),
2114
- tool("拆分单元格", "split", () => chain().splitCell().run(), false, !canSplit),
2115
- /* @__PURE__ */ jsx("span", { className: "tool-sep", "aria-hidden": "true" }),
2116
- /* @__PURE__ */ jsxs("button", { type: "button", className: `tool${panel === "align" ? " on" : ""}`, "aria-label": "单元格对齐", onClick: (event) => showPanel("align", event), children: [
2117
- /* @__PURE__ */ jsx(WIcon, { name: `align-${currentAlign ?? "left"}`, size: 18 }),
2118
- /* @__PURE__ */ jsx(WIcon, { name: "chevron-down", size: 12 })
2119
- ] }),
2120
- /* @__PURE__ */ jsx("span", { className: "tool-sep", "aria-hidden": "true" }),
2121
- tool("表格文字加粗", "bold", () => toggleMark("bold"), mark("bold")),
2122
- tool("表格文字斜体", "italic", () => toggleMark("italic"), mark("italic")),
2123
- tool("表格文字删除线", "strike", () => toggleMark("strike"), mark("strike")),
2124
- tool("表格文字下划线", "underline", () => toggleMark("underline"), mark("underline")),
2125
- colorEntries.map((item) => /* @__PURE__ */ jsxs(
2126
- "button",
2127
- {
2128
- type: "button",
2129
- className: `tool color-tool${panel === item.key ? " on" : ""}`,
2130
- "aria-label": item.label,
2131
- title: item.tip,
2132
- "aria-haspopup": "dialog",
2133
- "aria-expanded": panel === item.key,
2134
- onClick: (event) => showPanel(item.key, event),
2135
- children: [
2136
- item.key === "color" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2137
- "A",
2138
- /* @__PURE__ */ jsx(WIcon, { name: "chevron-down", size: 12 })
2139
- ] }) : /* @__PURE__ */ jsx(WIcon, { name: item.key === "highlight" ? "marker" : "cell-fill", size: 20 }),
2140
- /* @__PURE__ */ jsx("span", { className: "color-bar", style: { background: item.current ?? "transparent" }, "aria-hidden": "true" })
2141
- ]
2142
- },
2143
- item.key
2144
- )),
2145
- /* @__PURE__ */ jsx("span", { className: "tool-sep", "aria-hidden": "true" }),
2146
- tool("选择整个表格", "table", () => selectTableAxis(editor.view, geometry.cellPos, "table")),
2147
- /* @__PURE__ */ jsx("button", { type: "button", className: "tool", "aria-label": "更多表格操作", "aria-haspopup": "menu", "aria-expanded": menuOpen, onClick: (event) => showContext(event, "cell"), children: "···" })
2148
- ] })
2149
- }
2150
- )
2151
- ] })
2152
- ] }),
2153
- portal && createPortal(/* @__PURE__ */ jsxs(Fragment, { children: [
2154
- panel === "align" && /* @__PURE__ */ jsx("div", { ref: panelRef, className: "tbl-align-menu", style: { position: "fixed", left: panelPlace.left, top: panelPlace.top }, role: "menu", "aria-label": "单元格对齐菜单", onMouseDown: (event) => event.preventDefault(), children: CELL_ALIGNS.map((align) => /* @__PURE__ */ jsxs(
2155
- "button",
2156
- {
2157
- type: "button",
2158
- role: "menuitemradio",
2159
- "aria-checked": (currentAlign ?? "left") === align,
2160
- onClick: () => {
2161
- chain().setCellAttribute("align", align).run();
2162
- setPanel("none");
2163
- },
2164
- children: [
2165
- /* @__PURE__ */ jsx(WIcon, { name: `align-${align}`, size: 18 }),
2166
- /* @__PURE__ */ jsx("span", { children: align === "left" ? "左对齐" : align === "center" ? "居中对齐" : "右对齐" }),
2167
- (currentAlign ?? "left") === align && /* @__PURE__ */ jsx(WIcon, { name: "check", size: 16 })
2168
- ]
2169
- },
2170
- align
2171
- )) }),
2172
- colorEntries.map((item) => panel === item.key && /* @__PURE__ */ jsxs("div", { ref: panelRef, className: "tbl-palette", style: { position: "fixed", left: panelPlace.left, top: panelPlace.top }, role: "dialog", "aria-label": `${item.tip}选择`, onMouseDown: (event) => event.preventDefault(), children: [
2173
- /* @__PURE__ */ jsxs("div", { className: "palette-head", children: [
2174
- /* @__PURE__ */ jsx("span", { className: "palette-title", children: item.tip }),
2175
- /* @__PURE__ */ jsx("span", { className: "palette-hint", children: item.hint })
2176
- ] }),
2177
- /* @__PURE__ */ jsx("div", { className: "palette-grid", children: item.colors.map((color) => /* @__PURE__ */ jsx(
2178
- "button",
2179
- {
2180
- type: "button",
2181
- className: `swatch${item.current === color.value ? " selected" : ""}`,
2182
- "aria-label": `${item.tip}:${color.value ? color.name : "默认颜色"}`,
2183
- "aria-pressed": item.current === color.value,
2184
- onClick: () => applySwatch(color.value),
2185
- children: color.value && item.key === "color" ? /* @__PURE__ */ jsx("span", { className: "glyph", style: { color: color.value }, children: "A" }) : color.value ? /* @__PURE__ */ jsx("span", { style: { background: color.value } }) : /* @__PURE__ */ jsx("span", { children: "/" })
2186
- },
2187
- color.name
2188
- )) })
2189
- ] }, item.key)),
2190
- menuOpen && /* @__PURE__ */ jsx(
2191
- "div",
2192
- {
2193
- ref: menuRef,
2194
- className: "tbl-ctx w-overlay-scrollbar",
2195
- style: { position: "fixed", left: menuPlace.left, top: menuPlace.top, maxHeight: menuPlace.maxHeight, width: 216 },
2196
- role: "menu",
2197
- "aria-label": "表格操作菜单",
2198
- onMouseDown: (event) => {
2199
- if (!(event.target instanceof HTMLInputElement)) event.preventDefault();
2200
- },
2201
- onContextMenu: (event) => event.preventDefault(),
2202
- onKeyDown: (event) => {
2203
- var _a2;
2204
- if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key) || event.target instanceof HTMLInputElement) return;
2205
- const buttons = [...((_a2 = menuRef.current) == null ? void 0 : _a2.querySelectorAll('[role^="menuitem"]:not(:disabled)')) ?? []];
2206
- if (!buttons.length) return;
2207
- event.preventDefault();
2208
- const current = buttons.indexOf(document.activeElement);
2209
- const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (current + (event.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
2210
- buttons[next].focus();
2211
- },
2212
- children: /* @__PURE__ */ jsx("div", { ref: menuViewport, className: "w-overlay-scrollbar__viewport", style: { maxHeight: Math.max(0, menuPlace.maxHeight - 2) }, children: /* @__PURE__ */ jsx("div", { ref: menuContent, children: menuScope === "table" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2213
- menuItem("columns", "均分列宽", distribute),
2214
- menuItem("palette", "表格样式", () => setShowStyle(!showStyle), { expanded: showStyle }),
2215
- showStyle && /* @__PURE__ */ jsxs("div", { className: "ctx-styles", "aria-label": "表头样式", children: [
2216
- [{ label: "浅灰表头", color: null }, { label: "蓝色表头", color: "#d0ebff" }, { label: "绿色表头", color: "#d3f9d8" }].map((style) => /* @__PURE__ */ jsxs("button", { type: "button", className: "ctx-item", role: "menuitem", onMouseDown: (event) => event.preventDefault(), onClick: () => applyStyle(style.color), children: [
2217
- /* @__PURE__ */ jsx("span", { className: "ctx-style-swatch", style: { background: style.color ?? "var(--color-surface-subtle)" } }),
2218
- style.label
2219
- ] }, style.label)),
2220
- /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2221
- menuItem("table-header-row", "标题行", () => {
2222
- chain().toggleHeaderRow().run();
2223
- setMenuOpen(false);
2224
- }, { checked: headerRow }),
2225
- menuItem("table-header-column", "标题列", () => {
2226
- chain().toggleHeaderColumn().run();
2227
- setMenuOpen(false);
2228
- }, { checked: headerColumn })
2229
- ] }),
2230
- /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2231
- menuItem("cut", "剪切表格", () => void copyTable(true), { disabled: busy }),
2232
- menuItem("copy", "复制表格", () => void copyTable(false), { disabled: busy }),
2233
- menuItem("trash", "删除表格", () => {
2234
- chain().deleteTable().run();
2235
- setMenuOpen(false);
2236
- }, { danger: true }),
2237
- /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2238
- menuItem("add-below", "在下方插入段落", insertParagraphBelow)
2239
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2240
- menuItem("table-row-plus", "在上方插入行", () => {
2241
- chain().addRowBefore().run();
2242
- setMenuOpen(false);
2243
- }),
2244
- menuItem("table-row-plus", "在下方插入行", () => {
2245
- chain().addRowAfter().run();
2246
- setMenuOpen(false);
2247
- }),
2248
- menuItem("table-col-plus", "在左侧插入列", () => {
2249
- chain().addColumnBefore().run();
2250
- setMenuOpen(false);
2251
- }),
2252
- menuItem("table-col-plus", "在右侧插入列", () => {
2253
- chain().addColumnAfter().run();
2254
- setMenuOpen(false);
2255
- }),
2256
- /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2257
- menuItem("table", "切换首行表头", () => {
2258
- chain().toggleHeaderRow().run();
2259
- setMenuOpen(false);
2260
- }),
2261
- menuItem("table", "切换首列表头", () => {
2262
- chain().toggleHeaderColumn().run();
2263
- setMenuOpen(false);
2264
- }),
2265
- /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2266
- canMerge && menuItem("merge", "合并单元格", () => {
2267
- chain().mergeCells().run();
2268
- setMenuOpen(false);
2269
- }),
2270
- canSplit && menuItem("split", "拆分单元格", () => {
2271
- chain().splitCell().run();
2272
- setMenuOpen(false);
2273
- }),
2274
- /* @__PURE__ */ jsx("div", { className: "ctx-label", children: "对齐" }),
2275
- /* @__PURE__ */ jsxs("div", { className: "ctx-align", children: [
2276
- CELL_ALIGNS.map((align) => /* @__PURE__ */ jsx(
2277
- "button",
2278
- {
2279
- type: "button",
2280
- className: `al-btn${currentAlign === align ? " on" : ""}`,
2281
- title: align === "left" ? "左对齐" : align === "center" ? "居中" : "右对齐",
2282
- onClick: () => {
2283
- chain().setCellAttribute("align", align).run();
2284
- setMenuOpen(false);
2285
- },
2286
- children: /* @__PURE__ */ jsx(WIcon, { name: `align-${align}`, size: 16 })
2287
- },
2288
- align
2289
- )),
2290
- /* @__PURE__ */ jsx(
2291
- "button",
2292
- {
2293
- type: "button",
2294
- className: `al-btn${currentAlign === null ? " on" : ""}`,
2295
- title: "清除对齐",
2296
- onClick: () => {
2297
- chain().setCellAttribute("align", null).run();
2298
- setMenuOpen(false);
2299
- },
2300
- children: "默认"
2301
- }
2302
- )
2303
- ] }),
2304
- /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2305
- !widthOpen ? menuItem("columns", "列宽…", () => setWidthOpen(true)) : /* @__PURE__ */ jsxs("div", { className: "ctx-width", children: [
2306
- /* @__PURE__ */ jsx("span", { className: "ctx-label-inline", children: "列宽" }),
2307
- /* @__PURE__ */ jsx(
2308
- "input",
2309
- {
2310
- className: "ctx-num",
2311
- type: "number",
2312
- min: 40,
2313
- max: 2e3,
2314
- placeholder: "像素",
2315
- value: width,
2316
- "aria-label": "列宽像素",
2317
- onChange: (event) => setWidth(event.target.value),
2318
- onKeyDown: (event) => {
2319
- if (event.key === "Enter") {
2320
- event.preventDefault();
2321
- applyWidth();
2322
- }
2323
- }
2324
- }
2325
- ),
2326
- /* @__PURE__ */ jsx("span", { className: "faint small", children: "像素" }),
2327
- /* @__PURE__ */ jsx("button", { type: "button", className: "ctx-mini primary", onClick: applyWidth, children: "应用" })
2328
- ] }),
2329
- menuItem("columns", "列宽自适应", () => {
2330
- var _a2;
2331
- const col = (_a2 = currentCellRect(editor)) == null ? void 0 : _a2.col;
2332
- if (col != null) {
2333
- autoFitColumn(editor, col);
2334
- setMenuOpen(false);
2335
- }
2336
- }),
2337
- /* @__PURE__ */ jsx("div", { className: "ctx-sep" }),
2338
- menuItem("trash", "删除行", () => {
2339
- chain().deleteRow().run();
2340
- setMenuOpen(false);
2341
- }),
2342
- menuItem("trash", "删除列", () => {
2343
- chain().deleteColumn().run();
2344
- setMenuOpen(false);
2345
- }),
2346
- menuItem("table", "删除表格", () => {
2347
- chain().deleteTable().run();
2348
- setMenuOpen(false);
2349
- }, { danger: true })
2350
- ] }) }) })
2351
- }
2352
- )
2353
- ] }), portal)
2354
- ] });
2355
- }
2356
- function pickNearestGap(gaps, pointerY) {
2357
- if (gaps.length === 0) return null;
2358
- let best = gaps[0];
2359
- let bestDist = Math.abs(gaps[0].y - pointerY);
2360
- for (const g of gaps) {
2361
- const d = Math.abs(g.y - pointerY);
2362
- if (d < bestDist) {
2363
- best = g;
2364
- bestDist = d;
2365
- }
2366
- }
2367
- return best;
2368
- }
2369
- function focusTrailingParagraph(state) {
2370
- const tr = state.tr;
2371
- const last = tr.doc.lastChild;
2372
- if ((last == null ? void 0 : last.type.name) !== "paragraph" || last.content.size !== 0) {
2373
- tr.insert(tr.doc.content.size, state.schema.nodes.paragraph.create());
2374
- }
2375
- return tr.setSelection(Selection.atEnd(tr.doc)).setStoredMarks(null);
2376
- }
2377
- const IMAGE_GESTURE_SLOP_PX = 6;
2378
- const IMAGE_PREVIEW_LONG_PRESS_MS = 450;
2379
- function isImagePreviewTap(movedDistance, elapsedMs) {
2380
- if (!Number.isFinite(movedDistance) || !Number.isFinite(elapsedMs)) return false;
2381
- if (movedDistance >= IMAGE_GESTURE_SLOP_PX) return false;
2382
- return elapsedMs < IMAGE_PREVIEW_LONG_PRESS_MS;
2383
- }
2384
- function documentGestures(editor, shell, options) {
2385
- const view = editor.view;
2386
- let stop = null, suppressUntil = 0, picked = null;
2387
- const writable = () => !editor.isDestroyed && editor.isEditable;
2388
- function pickedImage(pos) {
2389
- picked = pos;
2390
- options.onImagePick(pos);
2391
- }
2392
- function cancel() {
2393
- stop == null ? void 0 : stop();
2394
- stop = null;
2395
- }
2396
- function consumeClick() {
2397
- const consume = Date.now() < suppressUntil;
2398
- suppressUntil = 0;
2399
- return consume;
2400
- }
2401
- function gaps() {
2402
- const result = [];
2403
- view.state.doc.forEach((node, pos) => {
2404
- const dom = view.nodeDOM(pos);
2405
- if (!(dom instanceof HTMLElement)) return;
2406
- const rect = dom.getBoundingClientRect();
2407
- result.push({ pos, y: rect.top }, { pos: pos + node.nodeSize, y: rect.bottom });
2408
- });
2409
- return result;
2410
- }
2411
- function start(pos, event, onClick) {
2412
- if (!writable() || event.button !== 0 || view.composing) return;
2413
- cancel();
2414
- let blocks = getSelectedBlocks(view);
2415
- if (!blocks.some((block) => block.from === pos)) {
2416
- const node = view.state.doc.nodeAt(pos);
2417
- if (!node) return;
2418
- setBlockRangeSelection(view, pos, pos);
2419
- blocks = getSelectedBlocks(view);
2420
- }
2421
- if (!blocks.length) return;
2422
- const doc = view.state.doc, from = blocks[0].from, to = blocks.at(-1).to;
2423
- const partial = blocks.some((block) => {
2424
- var _a;
2425
- return (_a = block.images) == null ? void 0 : _a.length;
2426
- });
2427
- const originalSelection = view.state.selection;
2428
- const origin = { x: event.clientX, y: event.clientY };
2429
- let moved = false, pointer = origin, gap = null, raf = 0;
2430
- const line = document.createElement("div");
2431
- line.className = "wr-block-drop-line";
2432
- line.setAttribute("aria-hidden", "true");
2433
- const oldCursor = document.documentElement.style.cursor, oldSelect = document.body.style.userSelect;
2434
- function paint() {
2435
- if (!writable() || view.state.doc !== doc) {
2436
- cancel();
2437
- return;
2438
- }
2439
- gap = pickNearestGap(gaps(), pointer.y);
2440
- const rect = view.dom.getBoundingClientRect();
2441
- if (gap) Object.assign(line.style, { left: `${rect.left}px`, top: `${gap.y}px`, width: `${rect.width}px` });
2442
- line.hidden = !gap || gap.pos >= from && gap.pos <= to;
2443
- }
2444
- const scroll = () => {
2445
- if (!moved) return;
2446
- let scroller = view.dom.parentElement;
2447
- while (scroller && !(scroller.scrollHeight > scroller.clientHeight && /auto|scroll/.test(getComputedStyle(scroller).overflowY))) scroller = scroller.parentElement;
2448
- const rect = (scroller == null ? void 0 : scroller.getBoundingClientRect()) ?? { top: 0, bottom: innerHeight };
2449
- const amount = pointer.y < rect.top + 42 ? -14 : pointer.y > rect.bottom - 42 ? 14 : 0;
2450
- if (amount) {
2451
- if (scroller) scroller.scrollTop += amount;
2452
- else window.scrollBy(0, amount);
2453
- paint();
2454
- }
2455
- raf = requestAnimationFrame(scroll);
2456
- };
2457
- const move = (next) => {
2458
- pointer = { x: next.clientX, y: next.clientY };
2459
- if (!moved && Math.hypot(pointer.x - origin.x, pointer.y - origin.y) < 4) return;
2460
- if (!moved) {
2461
- moved = true;
2462
- options.onStart();
2463
- options.layer().append(line);
2464
- document.documentElement.style.cursor = "grabbing";
2465
- document.body.style.userSelect = "none";
2466
- raf = requestAnimationFrame(scroll);
2467
- }
2468
- paint();
2469
- next.preventDefault();
2470
- };
2471
- const up = (next) => {
2472
- const target = gap, didMove = moved;
2473
- cancel();
2474
- if (!writable() || view.state.doc !== doc) return;
2475
- if (!didMove) {
2476
- if (onClick) onClick();
2477
- else {
2478
- const at2 = view.posAtCoords({ left: next.clientX, top: next.clientY });
2479
- if (at2) view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(at2.pos))));
2480
- view.focus();
2481
- }
2482
- return;
2483
- }
2484
- suppressUntil = Date.now() + 400;
2485
- if (!target || target.pos >= from && target.pos <= to) return;
2486
- const selection = view.state.selection;
2487
- if (partial && (!(selection instanceof BlockRangeSelection) || !selection.eq(originalSelection))) return;
2488
- const slice = partial ? selection.content() : view.state.doc.slice(from, to);
2489
- const tr = view.state.tr;
2490
- if (partial) selection.replace(tr);
2491
- else tr.delete(from, to);
2492
- const at = tr.mapping.map(target.pos), steps = tr.steps.length;
2493
- tr.insert(at, slice.content);
2494
- if (tr.steps.length === steps) return;
2495
- view.dispatch(tr.scrollIntoView());
2496
- view.focus();
2497
- };
2498
- const key = (next) => {
2499
- if (next.key === "Escape" && !next.isComposing) {
2500
- next.preventDefault();
2501
- next.stopImmediatePropagation();
2502
- suppressUntil = Date.now() + 400;
2503
- cancel();
2504
- }
2505
- };
2506
- const change2 = () => {
2507
- if (view.state.doc !== doc || !writable()) cancel();
2508
- };
2509
- stop = () => {
2510
- window.removeEventListener("mousemove", move, true);
2511
- window.removeEventListener("mouseup", up, true);
2512
- window.removeEventListener("keydown", key, true);
2513
- window.removeEventListener("blur", cancel);
2514
- window.removeEventListener("pointercancel", cancel);
2515
- editor.off("transaction", change2);
2516
- cancelAnimationFrame(raf);
2517
- line.remove();
2518
- document.documentElement.style.cursor = oldCursor;
2519
- document.body.style.userSelect = oldSelect;
2520
- options.onEnd();
2521
- };
2522
- window.addEventListener("mousemove", move, true);
2523
- window.addEventListener("mouseup", up, true);
2524
- window.addEventListener("keydown", key, true);
2525
- window.addEventListener("blur", cancel);
2526
- window.addEventListener("pointercancel", cancel);
2527
- editor.on("transaction", change2);
2528
- event.preventDefault();
2529
- }
2530
- function pickImage(frame) {
2531
- var _a;
2532
- try {
2533
- const pos = view.posAtDOM(frame, 0);
2534
- if (((_a = view.state.doc.nodeAt(pos)) == null ? void 0 : _a.type.name) !== "image") return;
2535
- pickedImage(pos);
2536
- view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos)));
2537
- view.focus();
2538
- } catch {
2539
- }
2540
- }
2541
- function gallerySelect(gallery, event) {
2542
- cancel();
2543
- event.preventDefault();
2544
- event.stopPropagation();
2545
- const doc = view.state.doc, origin = { x: event.clientX, y: event.clientY };
2546
- const frames = Array.from(gallery.children).filter((node) => node instanceof HTMLElement && node.classList.contains("ywiki-image-frame"));
2547
- const box = document.createElement("div");
2548
- box.className = "ywiki-gallery-marquee";
2549
- box.hidden = true;
2550
- options.layer().append(box);
2551
- let moved = false;
2552
- const oldSelect = document.body.style.userSelect;
2553
- document.body.style.userSelect = "none";
2554
- const move = (next) => {
2555
- if (!writable() || view.state.doc !== doc) {
2556
- cancel();
2557
- return;
2558
- }
2559
- if (!moved && Math.hypot(next.clientX - origin.x, next.clientY - origin.y) < 6) return;
2560
- moved = true;
2561
- box.hidden = false;
2562
- const left = Math.min(origin.x, next.clientX), top = Math.min(origin.y, next.clientY), right = Math.max(origin.x, next.clientX), bottom = Math.max(origin.y, next.clientY);
2563
- Object.assign(box.style, { left: `${left}px`, top: `${top}px`, width: `${right - left}px`, height: `${bottom - top}px` });
2564
- for (const frame of frames) {
2565
- const r = frame.getBoundingClientRect();
2566
- frame.classList.toggle("is-multi-picked", r.right > left && r.left < right && r.bottom > top && r.top < bottom);
2567
- }
2568
- next.preventDefault();
2569
- };
2570
- const up = () => {
2571
- const selected = frames.filter((frame) => frame.classList.contains("is-multi-picked"));
2572
- cancel();
2573
- if (!writable() || view.state.doc !== doc) return;
2574
- if (!moved) {
2575
- const nearest = frames.sort((a2, b2) => Math.abs(origin.x - (a2.getBoundingClientRect().left + a2.getBoundingClientRect().right) / 2) - Math.abs(origin.x - (b2.getBoundingClientRect().left + b2.getBoundingClientRect().right) / 2))[0];
2576
- if (nearest) pickImage(nearest);
2577
- } else if (selected.length === 1) pickImage(selected[0]);
2578
- else if (selected.length > 1) {
2579
- const raw = view.posAtDOM(gallery, 0), $raw = view.state.doc.resolve(raw);
2580
- const pos = $raw.depth ? $raw.before() : raw;
2581
- const ranges = selected.map((frame) => {
2582
- const from = view.posAtDOM(frame, 0);
2583
- return { from, to: from + view.state.doc.nodeAt(from).nodeSize };
2584
- });
2585
- setImageRangeSelection(view, pos, ranges);
2586
- view.focus();
2587
- }
2588
- };
2589
- const key = (next) => {
2590
- if (next.key === "Escape" && !next.isComposing) {
2591
- next.preventDefault();
2592
- cancel();
2593
- }
2594
- };
2595
- stop = () => {
2596
- window.removeEventListener("mousemove", move, true);
2597
- window.removeEventListener("mouseup", up, true);
2598
- window.removeEventListener("keydown", key, true);
2599
- window.removeEventListener("blur", cancel);
2600
- window.removeEventListener("pointercancel", cancel);
2601
- box.remove();
2602
- document.body.style.userSelect = oldSelect;
2603
- for (const frame of frames) frame.classList.remove("is-multi-picked");
2604
- };
2605
- window.addEventListener("mousemove", move, true);
2606
- window.addEventListener("mouseup", up, true);
2607
- window.addEventListener("keydown", key, true);
2608
- window.addEventListener("blur", cancel);
2609
- window.addEventListener("pointercancel", cancel);
2610
- }
2611
- function down(event) {
2612
- if (!writable() || view.composing || event.button !== 0 || !(event.target instanceof Element)) return;
2613
- const target = event.target;
2614
- if (target.closest("button,input,textarea,select,a,video,audio,.math-node")) return;
2615
- const blocks = getSelectedBlocks(view);
2616
- const gallery = target.closest(".ywiki-image-gallery");
2617
- if (gallery && !target.closest(".ywiki-image-frame") && !gallery.matches(".is-block-selected,.is-block-image-selected")) {
2618
- gallerySelect(gallery, event);
2619
- return;
2620
- }
2621
- if (event.detail < 2 && blocks.length) {
2622
- let block = target.closest(".ProseMirror > *");
2623
- if ((block == null ? void 0 : block.parentElement) !== view.dom) block = null;
2624
- if (block) {
2625
- let pos = -1;
2626
- view.state.doc.forEach((_node, offset) => {
2627
- if (view.nodeDOM(offset) === block) pos = offset;
2628
- });
2629
- const frame2 = target.closest(".ywiki-image-frame");
2630
- const hit = blocks.find((item) => {
2631
- var _a;
2632
- return item.from === pos && (!((_a = item.images) == null ? void 0 : _a.length) || frame2 && item.images.some((range) => view.posAtDOM(frame2, 0) === range.from));
2633
- });
2634
- if (hit) {
2635
- start(hit.from, event);
2636
- event.stopPropagation();
2637
- return;
2638
- }
2639
- }
2640
- }
2641
- const image = target.closest("img.ywiki-image"), frame = image == null ? void 0 : image.closest(".ywiki-image-frame");
2642
- if (image && frame) {
2643
- const pos = view.posAtDOM(frame, 0), previous = picked;
2644
- pickedImage(pos);
2645
- {
2646
- cancel();
2647
- const origin = { x: event.clientX, y: event.clientY, time: Date.now() };
2648
- let distance = 0;
2649
- const move = (next) => {
2650
- distance = Math.max(distance, Math.hypot(next.clientX - origin.x, next.clientY - origin.y));
2651
- };
2652
- const up = () => {
2653
- cancel();
2654
- if (distance >= 6) {
2655
- pickedImage(null);
2656
- return;
2657
- }
2658
- if (previous === pos && writable() && image.isConnected && isImagePreviewTap(distance, Date.now() - origin.time)) options.onPreview(image);
2659
- };
2660
- stop = () => {
2661
- window.removeEventListener("mousemove", move, true);
2662
- window.removeEventListener("mouseup", up, true);
2663
- window.removeEventListener("blur", cancel);
2664
- };
2665
- window.addEventListener("mousemove", move, true);
2666
- window.addEventListener("mouseup", up, true);
2667
- window.addEventListener("blur", cancel);
2668
- }
2669
- } else pickedImage(null);
2670
- if (target === view.dom) {
2671
- const last = view.state.doc.lastChild;
2672
- const dom = last ? view.nodeDOM(view.state.doc.content.size - last.nodeSize) : null;
2673
- if (dom instanceof HTMLElement && event.clientY >= dom.getBoundingClientRect().bottom - 4) {
2674
- event.preventDefault();
2675
- view.dispatch(focusTrailingParagraph(view.state));
2676
- view.focus();
2677
- }
2678
- }
2679
- }
2680
- const nativeDrag = (event) => {
2681
- if (stop && view.state.selection instanceof BlockRangeSelection) event.preventDefault();
2682
- };
2683
- const outside = (event) => {
2684
- if (!(event.target instanceof Element) || shell.contains(event.target) || event.target.closest(".img-toolbar,.img-preview-mask,.w-popover")) return;
2685
- pickedImage(null);
2686
- };
2687
- const change = ({ transaction }) => {
2688
- if (transaction.docChanged || view.state.selection instanceof NodeSelection && view.state.selection.from !== picked) pickedImage(null);
2689
- };
2690
- editor.on("transaction", change);
2691
- document.addEventListener("mousedown", outside, true);
2692
- view.dom.addEventListener("mousedown", down, true);
2693
- view.dom.addEventListener("dragstart", nativeDrag, true);
2694
- return { start, consumeClick, cancel, destroy() {
2695
- cancel();
2696
- view.dom.removeEventListener("mousedown", down, true);
2697
- view.dom.removeEventListener("dragstart", nativeDrag, true);
2698
- editor.off("transaction", change);
2699
- document.removeEventListener("mousedown", outside, true);
2700
- } };
2701
- }
2702
- function advanceTarget(target, tr) {
2703
- if (!target.valid || !tr.docChanged) return;
2704
- const point = target.from === target.to;
2705
- const from = tr.mapping.mapResult(target.from, 1);
2706
- const to = tr.mapping.mapResult(target.to, -1);
2707
- if (from.deleted || target.to > target.from && to.deleted) {
2708
- if (target.nodeJSON && tr.getMeta("y-sync$")) {
2709
- const matches = [];
2710
- tr.doc.descendants((node, pos) => {
2711
- if (JSON.stringify(node.toJSON()) === target.nodeJSON) matches.push({ pos, node });
2712
- });
2713
- if (matches.length === 1) {
2714
- target.from = matches[0].pos;
2715
- target.to = target.from + matches[0].node.nodeSize;
2716
- return;
2717
- }
2718
- }
2719
- target.valid = false;
2720
- return;
2721
- }
2722
- target.from = from.pos;
2723
- target.to = point ? from.pos : Math.max(from.pos, to.pos);
2724
- }
2725
- function isRemoteDocumentChange(transaction) {
2726
- const meta = transaction.getMeta(ySyncPluginKey);
2727
- return !!(meta == null ? void 0 : meta.isChangeOrigin) && !meta.isUndoRedoOperation;
2728
- }
2729
- function pageCandidates(initial, remote, query, excludeId) {
2730
- const pages = [...new Map([...initial, ...remote].map((page) => [page.id, page])).values()].filter((page) => page.id !== excludeId);
2731
- return query.trim() ? rankPages(pages, query.trim()).map((result) => result.page) : pages.sort((a2, b2) => (b2.updatedAt ?? "").localeCompare(a2.updatedAt ?? ""));
2732
- }
2733
- function PagePicker({ spaceId, excludeId, title = "选择页面", onClose, onPick }) {
2734
- const { host, portal } = useContentContext(), root = useRef(null), input = useRef(null);
2735
- const [initial, setInitial] = useState([]), [remote, setRemote] = useState([]);
2736
- const [query, setQuery] = useState(""), [error, setError] = useState(""), [loading, setLoading] = useState(true);
2737
- const [attempt, setAttempt] = useState(0), [active, setActive] = useState(0), id = useId();
2738
- const close = useRef(onClose);
2739
- close.current = onClose;
2740
- useEffect(() => {
2741
- var _a;
2742
- let alive = true;
2743
- setLoading(true);
2744
- setError("");
2745
- void (((_a = host.references) == null ? void 0 : _a.search("", { spaceId, excludeId })) ?? Promise.resolve([])).then((items) => {
2746
- if (alive) setInitial(items);
2747
- }).catch((error2) => {
2748
- if (alive) setError(error2 instanceof Error ? error2.message : "页面加载失败");
2749
- }).finally(() => {
2750
- if (alive) setLoading(false);
2751
- });
2752
- return () => {
2753
- alive = false;
2754
- };
2755
- }, [host.references, spaceId, excludeId, attempt]);
2756
- useEffect(() => {
2757
- let alive = true;
2758
- setRemote([]);
2759
- if (!query.trim()) return;
2760
- const timer = setTimeout(() => {
2761
- var _a;
2762
- void ((_a = host.references) == null ? void 0 : _a.search(query.trim(), { spaceId, excludeId }).then((items) => {
2763
- if (alive) setRemote(items);
2764
- }).catch(() => {
2765
- }));
2766
- }, 180);
2767
- return () => {
2768
- alive = false;
2769
- clearTimeout(timer);
2770
- };
2771
- }, [query, host.references, spaceId, excludeId]);
2772
- useEffect(() => {
2773
- var _a;
2774
- if (!portal) return;
2775
- const previous = document.activeElement, scroll = lockScroll();
2776
- (_a = input.current) == null ? void 0 : _a.focus();
2777
- function keydown(event) {
2778
- if (event.isComposing || event.keyCode === 229) return;
2779
- if (event.key === "Escape") {
2780
- event.preventDefault();
2781
- event.stopImmediatePropagation();
2782
- close.current();
2783
- } else if (root.current && trapTab(root.current, event)) event.preventDefault();
2784
- }
2785
- document.addEventListener("keydown", keydown, true);
2786
- return () => {
2787
- scroll.release();
2788
- document.removeEventListener("keydown", keydown, true);
2789
- if (previous == null ? void 0 : previous.isConnected) previous.focus();
2790
- };
2791
- }, [portal]);
2792
- const candidates = useMemo(() => pageCandidates(initial, remote, query, excludeId).slice(0, 20), [initial, remote, query, excludeId]);
2793
- useEffect(() => {
2794
- setActive(0);
2795
- }, [candidates]);
2796
- useEffect(() => {
2797
- var _a, _b;
2798
- (_b = (_a = root.current) == null ? void 0 : _a.querySelector(`[id="${id}-${active}"]`)) == null ? void 0 : _b.scrollIntoView({ block: "nearest" });
2799
- }, [active, id]);
2800
- const visible = !!candidates.length && !loading && !error;
2801
- function choose(candidate) {
2802
- if (!loading && !error) onPick(candidate.id, candidate.title);
2803
- }
2804
- if (!portal) return null;
2805
- return createPortal(/* @__PURE__ */ jsx("div", { className: "picker-backdrop", onMouseDown: (event) => {
2806
- if (event.target === event.currentTarget) onClose();
2807
- }, children: /* @__PURE__ */ jsxs("div", { ref: root, className: "picker", role: "dialog", "aria-modal": "true", "aria-label": title, children: [
2808
- /* @__PURE__ */ jsx(
2809
- "input",
2810
- {
2811
- ref: input,
2812
- className: "picker-input",
2813
- type: "text",
2814
- role: "combobox",
2815
- "aria-label": title,
2816
- "aria-autocomplete": "list",
2817
- "aria-controls": visible ? id : void 0,
2818
- "aria-expanded": !!visible,
2819
- "aria-activedescendant": visible ? `${id}-${active}` : void 0,
2820
- placeholder: "搜索页面标题(支持拼音)…",
2821
- value: query,
2822
- onChange: (event) => {
2823
- setQuery(event.target.value);
2824
- setActive(0);
2825
- },
2826
- onKeyDown: (event) => {
2827
- if (event.nativeEvent.isComposing || event.keyCode === 229 || !visible) return;
2828
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
2829
- event.preventDefault();
2830
- setActive((value) => (value + (event.key === "ArrowDown" ? 1 : -1) + candidates.length) % candidates.length);
2831
- } else if (event.key === "Enter") {
2832
- event.preventDefault();
2833
- const candidate = candidates[active];
2834
- if (candidate) choose(candidate);
2835
- }
2836
- }
2837
- }
2838
- ),
2839
- error ? /* @__PURE__ */ jsxs("p", { role: "alert", children: [
2840
- error,
2841
- " ",
2842
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setAttempt((value) => value + 1), children: "重试" })
2843
- ] }) : loading ? /* @__PURE__ */ jsx("p", { role: "status", children: "加载中…" }) : candidates.length ? /* @__PURE__ */ jsx("ul", { id, className: "picker-list", role: "listbox", "aria-label": "页面候选", children: candidates.map((candidate, index) => /* @__PURE__ */ jsx(
2844
- "li",
2845
- {
2846
- id: `${id}-${index}`,
2847
- role: "option",
2848
- "aria-selected": index === active,
2849
- className: index === active ? "active" : void 0,
2850
- onMouseEnter: () => setActive(index),
2851
- onMouseDown: (event) => {
2852
- event.preventDefault();
2853
- choose(candidate);
2854
- },
2855
- children: /* @__PURE__ */ jsx("span", { className: "picker-title", children: candidate.title })
2856
- },
2857
- candidate.id
2858
- )) }) : /* @__PURE__ */ jsx("p", { className: "picker-empty", children: "没有匹配的页面" })
2859
- ] }) }), portal);
2860
- }
2861
- function videoProblem(value, attempted = false) {
2862
- const inspection = inspectVideoUrl(value);
2863
- if (inspection.data) return "";
2864
- if (inspection.issue === "empty") return attempted ? "请粘贴视频链接。" : "";
2865
- if (inspection.issue === "short-link") return "B站短链(b23.tv)不能在正文里直接播放,请先在浏览器打开,再复制地址栏里的完整链接。";
2866
- if (inspection.issue === "missing-id") return "链接里没有识别到视频 ID,请确认复制的是单个视频的播放页,而不是首页或频道页。";
2867
- if (inspection.issue === "unsupported") return `暂不支持该平台的视频链接。目前支持 ${Object.values(VIDEO_PROVIDER_LABELS).join("、")}。`;
2868
- return "链接格式不正确,请粘贴以 http:// 或 https:// 开头的完整地址。";
2869
- }
2870
- function VideoEmbedDialog({ initialUrl = "", onClose, onInsert }) {
2871
- const [value, setValue] = useState(initialUrl), [attempted, setAttempted] = useState(false), id = useId();
2872
- const input = useRef(null);
2873
- useEffect(() => {
2874
- var _a;
2875
- (_a = input.current) == null ? void 0 : _a.focus();
2876
- }, []);
2877
- const data = inspectVideoUrl(value).data, problem = videoProblem(value, attempted);
2878
- function submit() {
2879
- setAttempted(true);
2880
- if (data) onInsert(data);
2881
- }
2882
- return /* @__PURE__ */ jsx(WDialog, { workspace: true, title: "嵌入视频", onClose, className: "video-dialog", footer: /* @__PURE__ */ jsxs("div", { className: "wr-toolbar", children: [
2883
- /* @__PURE__ */ jsx(WButton, { onClick: onClose, children: "取消" }),
2884
- /* @__PURE__ */ jsx(WButton, { variant: "primary", disabled: !data, onClick: submit, children: "插入视频" })
2885
- ] }), children: /* @__PURE__ */ jsxs("form", { className: "video-form", onSubmit: (event) => {
2886
- event.preventDefault();
2887
- submit();
2888
- }, children: [
2889
- /* @__PURE__ */ jsxs("div", { className: "video-field", children: [
2890
- /* @__PURE__ */ jsx("label", { htmlFor: id, children: "视频链接" }),
2891
- /* @__PURE__ */ jsx(
2892
- "input",
2893
- {
2894
- ref: input,
2895
- id,
2896
- type: "url",
2897
- maxLength: 2048,
2898
- placeholder: "粘贴视频播放页链接",
2899
- autoComplete: "off",
2900
- spellCheck: false,
2901
- value,
2902
- onChange: (event) => setValue(event.target.value)
2903
- }
2904
- ),
2905
- /* @__PURE__ */ jsxs("p", { className: "video-hint", children: [
2906
- "支持 ",
2907
- Object.values(VIDEO_PROVIDER_LABELS).join("、"),
2908
- " 的视频页与官方嵌入链接,番剧、剧集和多 P 视频可直接粘贴。"
2909
- ] })
2910
- ] }),
2911
- data ? /* @__PURE__ */ jsxs("p", { className: "video-status video-status--ok", role: "status", children: [
2912
- "已识别 ",
2913
- VIDEO_PROVIDER_LABELS[data.provider],
2914
- " 视频",
2915
- /* @__PURE__ */ jsx("span", { className: "video-status-meta", children: data.videoId }),
2916
- data.page > 1 ? ` · 第 ${data.page} P` : "",
2917
- data.startSeconds ? ` · 从 ${data.startSeconds} 秒开始` : ""
2918
- ] }) : problem ? /* @__PURE__ */ jsx("p", { className: "video-status video-status--error", role: "alert", children: problem }) : null,
2919
- data && /* @__PURE__ */ jsx("p", { className: "video-source", children: videoSourceUrl(data) })
2920
- ] }) });
2921
- }
2922
- const empty = { type: "doc", content: [{ type: "paragraph" }] };
2923
- const DocumentEditor = forwardRef(function DocumentEditor2(props, ref) {
2924
- const mount = useRef(null), shell = useRef(null), editor = useRef(null);
2925
- const context = useContentContext(), host = context.host, latest = useLatest(props), hostRef = useLatest(host), emitted = useRef(null);
2926
- const contextRef = useLatest(context);
2927
- const [epoch, update] = useState(0), [notice, setNotice] = useState(""), [error, setError] = useState("");
2928
- const [slash, setSlash] = useState(null);
2929
- const [hover, setHover] = useState(null), [frozen, setFrozen] = useState(null), [blockMenu, setBlockMenu] = useState(false);
2930
- const [panelOnly, setPanelOnly] = useState(void 0);
2931
- const [tableHover, setTableHover] = useState(null);
2932
- const [link, setLink] = useState(null);
2933
- const linkRef = useLatest(link);
2934
- const [video, setVideo] = useState(false), [videoUrl, setVideoUrl] = useState(""), [references, setReferences] = useState(false);
2935
- const [atoms, setAtoms] = useState([]), disposed = useRef(false), pending = useRef(0), fileInput = useRef(null), fileKind = useRef("image");
2936
- const [preview, setPreview] = useState(null);
2937
- const hoverTimer = useRef(null), lastHover = useRef(null);
2938
- const gestures = useRef(null);
2939
- const pendingInsert = useRef(null);
2940
- const remoteTargets = useRef(/* @__PURE__ */ new Set());
2941
- const [dragging, setDragging] = useState(false);
2942
- const [imagePick, setImagePick] = useState(null);
2943
- const menuOpenRef = useLatest(blockMenu);
2944
- const mindViewState = useRef({ openId: null, maps: {} });
2945
- const ready = useReady();
2946
- useEffect(() => () => {
2947
- if (hoverTimer.current) clearTimeout(hoverTimer.current);
2948
- }, []);
2949
- useEffect(() => {
2950
- const input = fileInput.current, cancel = () => {
2951
- pendingInsert.current = null;
2952
- };
2953
- input == null ? void 0 : input.addEventListener("cancel", cancel);
2954
- return () => input == null ? void 0 : input.removeEventListener("cancel", cancel);
2955
- }, []);
2956
- const fail = useCallback((error2) => {
2957
- var _a, _b;
2958
- setError(asError(error2).message);
2959
- (_b = (_a = latest.current).onError) == null ? void 0 : _b.call(_a, asError(error2));
2960
- }, [latest]);
2961
- const tell = useCallback((message) => {
2962
- var _a, _b;
2963
- setNotice(message);
2964
- (_b = (_a = latest.current).onNotice) == null ? void 0 : _b.call(_a, message);
2965
- }, [latest]);
2966
- function onBlockHover(info) {
2967
- if (hoverTimer.current) {
2968
- clearTimeout(hoverTimer.current);
2969
- hoverTimer.current = null;
2970
- }
2971
- if (info) {
2972
- lastHover.current = info;
2973
- setHover(info);
2974
- return;
2975
- }
2976
- hoverTimer.current = setTimeout(() => {
2977
- hoverTimer.current = null;
2978
- setHover(null);
2979
- if (!menuOpenRef.current) setFrozen(null);
2980
- }, 220);
2981
- }
2982
- useEffect(() => {
2983
- var _a, _b, _c;
2984
- const target = mount.current;
2985
- if (!target) return;
2986
- disposed.current = false;
2987
- let alive = true;
2988
- const container = document.createElement("div");
2989
- target.append(container);
2990
- let ed2;
2991
- const atom = ({ node, editor: owner, getPos }) => {
2992
- const dom = document.createElement("div");
2993
- dom.contentEditable = "false";
2994
- dom.dataset.blockChrome = "";
2995
- const id = crypto.randomUUID();
2996
- let current = node;
2997
- if (node.type.name === "mindMap") dom.dataset.mindMapNode = "";
2998
- const controller = node.type.name === "mindMap" ? createMindMapController(owner, () => {
2999
- const pos = getPos();
3000
- return typeof pos === "number" ? pos : void 0;
3001
- }, {
3002
- get pageId() {
3003
- return latest.current.pageId;
3004
- },
3005
- canComment: () => !!latest.current.canComment,
3006
- canExport: () => !!latest.current.allowExport,
3007
- status: () => latest.current.saveStatus ?? "",
3008
- fetchAttachment: (id2) => {
3009
- var _a2;
3010
- return (((_a2 = hostRef.current.attachments) == null ? void 0 : _a2.fetch) ?? unavailableAttachment)(id2);
3011
- },
3012
- get uploadImage() {
3013
- var _a2;
3014
- return latest.current.uploadImage ?? ((_a2 = hostRef.current.attachments) == null ? void 0 : _a2.uploadImage);
3015
- },
3016
- viewState: latest.current.mindMapViewState ?? mindViewState.current,
3017
- awareness: latest.current.collabAwareness
3018
- }) : void 0;
3019
- const render = () => {
3020
- let content;
3021
- if (current.type.name === "mindMap") {
3022
- content = /* @__PURE__ */ jsx(
3023
- MindMapBlock,
3024
- {
3025
- node: current.toJSON(),
3026
- controller,
3027
- pageId: latest.current.pageId,
3028
- canComment: latest.current.canComment,
3029
- onCommentsChanged: latest.current.onCommentsChanged
3030
- }
3031
- );
3032
- } else content = /* @__PURE__ */ jsx(VideoView, { attrs: current.attrs });
3033
- setAtoms((previous) => [...previous.filter((item) => item.id !== id), { id, dom, content }]);
3034
- };
3035
- queueMicrotask(() => {
3036
- if (alive) render();
3037
- });
3038
- return {
3039
- dom,
3040
- update(next) {
3041
- if (next.type !== current.type || current.type.name === "mindMap" && next.attrs.id !== current.attrs.id) return false;
3042
- current = next;
3043
- if (alive) render();
3044
- return true;
3045
- },
3046
- stopEvent: () => true,
3047
- ignoreMutation: () => true,
3048
- destroy() {
3049
- controller == null ? void 0 : controller.dispose();
3050
- if (alive) queueMicrotask(() => {
3051
- if (alive) setAtoms((previous) => previous.filter((item) => item.id !== id));
3052
- });
3053
- }
3054
- };
3055
- };
3056
- const menuCoords = (from) => {
3057
- var _a2;
3058
- try {
3059
- const coords = ed2.view.coordsAtPos(from), rect = ((_a2 = shell.current) == null ? void 0 : _a2.getBoundingClientRect()) ?? { left: 0, top: 0 };
3060
- return { x: coords.left - rect.left, y: coords.bottom - rect.top + 6 };
3061
- } catch {
3062
- return { x: 0, y: 0 };
3063
- }
3064
- };
3065
- try {
3066
- const initial = latest.current.value ?? latest.current.defaultValue ?? empty;
3067
- new ContentSchemaService().validate(initial, latest.current.schemaVersion ?? requiredSchemaVersion(initial, SCHEMA_VERSION));
3068
- ed2 = new Editor({
3069
- element: container,
3070
- editable: !latest.current.disabled,
3071
- content: latest.current.collabDoc ? void 0 : initial,
3072
- extensions: coreExtensions({
3073
- placeholder: latest.current.placeholder ?? "输入 / 唤起命令,开始记录…",
3074
- collabDoc: latest.current.collabDoc,
3075
- collabAwareness: latest.current.collabAwareness,
3076
- canCopyCode: () => !!latest.current.allowExport,
3077
- contentContext: () => contextRef.current,
3078
- attachmentFetcher: (_a = hostRef.current.attachments) == null ? void 0 : _a.fetch,
3079
- attachmentScope: hostRef.current.scope,
3080
- inlineComments: { getComments: () => latest.current.inlineComments ?? [], getActiveId: () => latest.current.activeCommentId ?? null, onMarkClick: (id) => {
3081
- var _a2, _b2;
3082
- return (_b2 = (_a2 = latest.current).onInlineMarkClick) == null ? void 0 : _b2.call(_a2, id);
3083
- } },
3084
- slash: {
3085
- onOpen: (state) => {
3086
- setSlash({ ...state, ...menuCoords(state.from) });
3087
- setBlockMenu(false);
3088
- setHover(null);
3089
- },
3090
- onUpdate: (query) => setSlash((previous) => previous ? { ...previous, query } : null),
3091
- onClose: () => setSlash(null)
3092
- },
3093
- blockHandle: { onHover: onBlockHover },
3094
- uploadPlaceholder: { onLost: () => tell("上传目标已移除,附件未插入。") },
3095
- tableInteraction: { onHover: (value) => setTableHover(value) },
3096
- mindMapRenderer: atom,
3097
- videoRenderer: atom
3098
- }),
3099
- editorProps: {
3100
- attributes: { role: "textbox", "aria-label": "文档正文", "aria-multiline": "true", class: "editor-canvas ywiki-prose ywiki-content" },
3101
- handleKeyDown: (_view, event) => {
3102
- if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
3103
- event.preventDefault();
3104
- openLink(ed2);
3105
- return true;
3106
- }
3107
- return false;
3108
- },
3109
- handlePaste: (view, event) => {
3110
- var _a2, _b2;
3111
- if (!view.editable) return false;
3112
- const files = pastedImageFiles(event);
3113
- if (files.length) {
3114
- void upload(files);
3115
- return true;
3116
- }
3117
- const text = ((_b2 = (_a2 = event.clipboardData) == null ? void 0 : _a2.getData("text/plain")) == null ? void 0 : _b2.trim()) ?? "";
3118
- if (view.state.selection.empty && !view.state.selection.$from.parent.content.size && parseVideoUrl(text)) {
3119
- const from = view.state.selection.from;
3120
- const tr = view.state.tr.insertText(text);
3121
- tr.addMark(from, from + text.length, view.state.schema.marks.link.create({ href: text }));
3122
- view.dispatch(tr);
3123
- pendingInsert.current = { from, to: from + text.length, valid: true };
3124
- setVideoUrl(text);
3125
- setVideo(true);
3126
- return true;
3127
- }
3128
- if (pastedRemoteImageUrls(event).length) setTimeout(() => {
3129
- if (alive) void upgradeRemoteImages(ed2);
3130
- }, 0);
3131
- return false;
3132
- },
3133
- handleDrop: (view, event) => {
3134
- var _a2;
3135
- if (!view.editable || !((_a2 = event.dataTransfer) == null ? void 0 : _a2.files.length)) return false;
3136
- const at = view.posAtCoords({ left: event.clientX, top: event.clientY });
3137
- if (!at) return false;
3138
- event.preventDefault();
3139
- void upload([...event.dataTransfer.files], at.pos);
3140
- return true;
3141
- },
3142
- handleDOMEvents: {
3143
- contextmenu: (view, event) => {
3144
- var _a2;
3145
- if (!view.editable || !(event.target instanceof Element)) return false;
3146
- const cell = event.target.closest("td,th");
3147
- if (cell) return false;
3148
- const block = event.target.closest(".ProseMirror > *"), hr = (_a2 = shell.current) == null ? void 0 : _a2.getBoundingClientRect();
3149
- if (!block || block.parentElement !== view.dom || !hr) return false;
3150
- let pos = -1;
3151
- view.state.doc.forEach((_node, offset) => {
3152
- if (view.nodeDOM(offset) === block) pos = offset;
3153
- });
3154
- const node = view.state.doc.nodeAt(pos);
3155
- if (!node) return false;
3156
- event.preventDefault();
3157
- setLink(null);
3158
- setSlash(null);
3159
- setFrozen({ pos, type: node.type.name, top: event.clientY - hr.top, contentLeft: event.clientX - hr.left, isEmpty: node.isTextblock && !node.content.size });
3160
- setPanelOnly(void 0);
3161
- setBlockMenu(true);
3162
- return true;
3163
- },
3164
- mouseover: (view, event) => {
3165
- var _a2;
3166
- if (linkRef.current || !view.editable) return false;
3167
- const anchor = (_a2 = event.target) == null ? void 0 : _a2.closest("a[href]");
3168
- if (!anchor || !view.dom.contains(anchor)) return false;
3169
- const pos = view.posAtDOM(anchor, 0), range = getMarkRange(view.state.doc.resolve(pos), view.state.schema.marks.link);
3170
- if (!range) return false;
3171
- const rect = anchor.getBoundingClientRect();
3172
- const tracked = createLinkEditTarget(view.state, TextSelection.create(view.state.doc, range.from, range.to));
3173
- if (tracked) setLink({ ...tracked, tracked, mode: "preview", anchor: { left: rect.left, top: rect.top, width: rect.width, height: rect.height } });
3174
- return false;
3175
- }
3176
- }
3177
- },
3178
- onUpdate: ({ editor: editor2, transaction }) => {
3179
- var _a2, _b2;
3180
- emitted.current = editor2.getJSON();
3181
- update((value) => value + 1);
3182
- (_b2 = (_a2 = latest.current).onChange) == null ? void 0 : _b2.call(_a2, emitted.current, { remote: isRemoteDocumentChange(transaction) });
3183
- },
3184
- onSelectionUpdate: () => update((value) => value + 1),
3185
- onTransaction: ({ transaction }) => {
3186
- if (pendingInsert.current) advanceTarget(pendingInsert.current, transaction);
3187
- for (const target3 of remoteTargets.current) advanceTarget(target3, transaction);
3188
- const target2 = linkRef.current;
3189
- if (target2 == null ? void 0 : target2.tracked) {
3190
- advanceTarget(target2.tracked, transaction);
3191
- target2.from = target2.tracked.from;
3192
- target2.to = target2.tracked.to;
3193
- }
3194
- update((value) => value + 1);
3195
- },
3196
- onBlur: () => {
3197
- var _a2, _b2;
3198
- return (_b2 = (_a2 = latest.current).onBlur) == null ? void 0 : _b2.call(_a2);
3199
- }
3200
- });
3201
- editor.current = ed2;
3202
- if (shell.current) {
3203
- gestures.current = documentGestures(ed2, shell.current, {
3204
- layer: () => contextRef.current.portal ?? shell.current ?? document.body,
3205
- onStart: () => {
3206
- setDragging(true);
3207
- setBlockMenu(false);
3208
- dismissSlash(ed2.view);
3209
- setSlash(null);
3210
- setLink(null);
3211
- },
3212
- onEnd: () => {
3213
- setDragging(false);
3214
- update((value) => value + 1);
3215
- },
3216
- onImagePick: setImagePick,
3217
- onPreview: (image) => {
3218
- const source = image.getAttribute("src") ?? "";
3219
- if (!source) return;
3220
- const items = previewItemsFrom(image, "图片预览");
3221
- setPreview({ src: source, label: image.alt || "图片预览", items: items.items.length ? items.items : [{ src: source, label: image.alt || "图片预览" }], index: items.items.length ? items.index : 0 });
3222
- }
3223
- });
3224
- }
3225
- ready.markReady();
3226
- (_c = (_b = latest.current).onReady) == null ? void 0 : _c.call(_b);
3227
- } catch (error2) {
3228
- fail(error2);
3229
- }
3230
- return () => {
3231
- var _a2;
3232
- alive = false;
3233
- disposed.current = true;
3234
- (_a2 = gestures.current) == null ? void 0 : _a2.destroy();
3235
- gestures.current = null;
3236
- pendingInsert.current = null;
3237
- editor.current = null;
3238
- ed2 == null ? void 0 : ed2.destroy();
3239
- container.remove();
3240
- setAtoms([]);
3241
- };
3242
- }, [props.collabDoc, props.collabAwareness]);
3243
- useEffect(() => {
3244
- const ed2 = editor.current;
3245
- if (ed2 && !ed2.isDestroyed) ed2.view.dispatch(ed2.state.tr.setMeta("content-context", true));
3246
- }, [context.portal, context.theme, context.accent, host.preferences, props.allowExport]);
3247
- useEffect(() => {
3248
- var _a;
3249
- const ed2 = editor.current;
3250
- if (ed2) {
3251
- ed2.setEditable(!props.disabled, false);
3252
- ed2.view.dispatch(ed2.state.tr.setMeta("content-capabilities", true));
3253
- }
3254
- if (props.disabled) {
3255
- (_a = gestures.current) == null ? void 0 : _a.cancel();
3256
- closeBlockMenu();
3257
- setLink(null);
3258
- setSlash(null);
3259
- setHover(null);
3260
- setFrozen(null);
3261
- pendingInsert.current = null;
3262
- setReferences(false);
3263
- setVideo(false);
3264
- setTableHover(null);
3265
- }
3266
- }, [props.disabled]);
3267
- useEffect(() => {
3268
- const ed2 = editor.current;
3269
- if (!ed2 || !props.value || props.collabDoc || props.value === emitted.current) return;
3270
- try {
3271
- new ContentSchemaService().validate(props.value, props.schemaVersion ?? requiredSchemaVersion(props.value, SCHEMA_VERSION));
3272
- const parsed = ed2.schema.nodeFromJSON(props.value);
3273
- if (!parsed.eq(ed2.state.doc)) ed2.commands.setContent(props.value, { emitUpdate: false });
3274
- setError("");
3275
- } catch (error2) {
3276
- fail(error2);
3277
- }
3278
- }, [props.value, props.collabDoc, fail]);
3279
- useEffect(() => {
3280
- if (editor.current) refreshInlineComments(editor.current, props.inlineComments ?? []);
3281
- }, [props.inlineComments, props.activeCommentId]);
3282
- const snapshot = () => {
3283
- var _a;
3284
- return ((_a = editor.current) == null ? void 0 : _a.getJSON()) ?? latest.current.value ?? latest.current.defaultValue ?? empty;
3285
- };
3286
- function hasPendingTasks() {
3287
- var _a;
3288
- const ed2 = editor.current;
3289
- return !!ed2 && (pending.current > 0 || remoteTargets.current.size > 0 || !!pendingInsert.current || references || video || ed2.view.composing || !!((_a = ed2.storage.mindMap) == null ? void 0 : _a.composing) || !!ed2.view.dom.querySelector(".math-latex-input"));
3290
- }
3291
- function selectionRect() {
3292
- const ed2 = editor.current;
3293
- if (!ed2 || ed2.state.selection.empty) return null;
3294
- try {
3295
- const rect = ed2.view.coordsAtPos(ed2.state.selection.from);
3296
- return { left: rect.left, top: rect.top, width: Math.max(1, rect.right - rect.left), height: Math.max(1, rect.bottom - rect.top) };
3297
- } catch {
3298
- return null;
3299
- }
3300
- }
3301
- useImperativeHandle(ref, () => ({
3302
- isReady: ready.isReady,
3303
- whenReady: ready.whenReady,
3304
- getJSON: snapshot,
3305
- getSnapshot: snapshot,
3306
- async prepareSnapshot() {
3307
- if (!editor.current) throw new Error("文档尚未就绪");
3308
- if (hasPendingTasks()) throw new Error("请先完成上传、公式或组合输入");
3309
- const doc = snapshot(), schemaVersion = requiredSchemaVersion(doc, latest.current.schemaVersion ?? SCHEMA_VERSION);
3310
- new ContentSchemaService().validate(doc, schemaVersion);
3311
- return { doc, schemaVersion };
3312
- },
3313
- hasPendingTasks,
3314
- focus: () => {
3315
- var _a;
3316
- (_a = editor.current) == null ? void 0 : _a.commands.focus();
3317
- },
3318
- undo: () => {
3319
- var _a;
3320
- return ((_a = editor.current) == null ? void 0 : _a.commands.undo()) ?? false;
3321
- },
3322
- redo: () => {
3323
- var _a;
3324
- return ((_a = editor.current) == null ? void 0 : _a.commands.redo()) ?? false;
3325
- },
3326
- hasSelection: () => !!editor.current && !editor.current.state.selection.empty,
3327
- getSelectionRect: selectionRect,
3328
- getSelectionForAnchor() {
3329
- const ed2 = editor.current;
3330
- if (!ed2 || ed2.state.selection.empty) return null;
3331
- const { from, to } = ed2.state.selection;
3332
- const quote = ed2.state.doc.textBetween(from, to, "\n");
3333
- if (!quote.trim()) return null;
3334
- return { quote, prefix: ed2.state.doc.textBetween(Math.max(0, from - 64), from, "\n"), suffix: ed2.state.doc.textBetween(to, Math.min(ed2.state.doc.content.size, to + 64), "\n"), start: ed2.state.doc.textBetween(0, from, "\n").length };
3335
- },
3336
- setSelection(from, to = from) {
3337
- var _a;
3338
- (_a = editor.current) == null ? void 0 : _a.commands.setTextSelection({ from, to });
3339
- },
3340
- setContent(doc) {
3341
- var _a;
3342
- if (latest.current.collabDoc) return;
3343
- try {
3344
- const next = doc ?? empty;
3345
- new ContentSchemaService().validate(next, latest.current.schemaVersion ?? requiredSchemaVersion(next, SCHEMA_VERSION));
3346
- (_a = editor.current) == null ? void 0 : _a.commands.setContent(next, { emitUpdate: false });
3347
- setError("");
3348
- } catch (error2) {
3349
- fail(error2);
3350
- }
3351
- }
3352
- }));
3353
- function openLink(ed2 = editor.current) {
3354
- if (!ed2 || !ed2.isEditable) return;
3355
- const tracked = createLinkEditTarget(ed2.state);
3356
- if (!tracked) {
3357
- tell("请先选中要添加链接的文字");
3358
- return;
3359
- }
3360
- const rect = ed2.view.coordsAtPos(tracked.from);
3361
- setLink({ ...tracked, tracked, mode: "edit", anchor: { left: rect.left, top: rect.top, width: Math.max(1, rect.right - rect.left), height: rect.bottom - rect.top } });
3362
- }
3363
- function rememberInsert() {
3364
- const ed2 = editor.current;
3365
- if (!(ed2 == null ? void 0 : ed2.isEditable)) return;
3366
- pendingInsert.current = { from: ed2.state.selection.from, to: ed2.state.selection.from, valid: true };
3367
- }
3368
- function requestPage() {
3369
- if (!hostRef.current.references) {
3370
- tell("未配置资源选择服务");
3371
- return;
3372
- }
3373
- rememberInsert();
3374
- setReferences(true);
3375
- }
3376
- function requestVideo() {
3377
- rememberInsert();
3378
- setVideoUrl("");
3379
- setVideo(true);
3380
- }
3381
- function insertPending(content) {
3382
- const ed2 = editor.current, target = pendingInsert.current;
3383
- pendingInsert.current = null;
3384
- if (!(ed2 == null ? void 0 : ed2.isEditable) || !(target == null ? void 0 : target.valid) || target.from > ed2.state.doc.content.size) {
3385
- tell("插入目标已移除,请重新选择位置");
3386
- return;
3387
- }
3388
- ed2.chain().focus().insertContentAt({ from: target.from, to: target.to }, content).run();
3389
- }
3390
- function requestFile(kind) {
3391
- rememberInsert();
3392
- fileKind.current = kind;
3393
- if (fileInput.current) {
3394
- fileInput.current.accept = kind === "image" ? "image/*" : "";
3395
- fileInput.current.click();
3396
- }
3397
- }
3398
- async function upload(files, position, kind = "auto") {
3399
- var _a, _b, _c;
3400
- const ed2 = editor.current;
3401
- if (!ed2 || !ed2.isEditable) return;
3402
- const at = position ?? ed2.state.selection.from;
3403
- const imageUploader = latest.current.uploadImage ?? ((_a = hostRef.current.attachments) == null ? void 0 : _a.uploadImage);
3404
- if (kind !== "file" && files.length > 1 && files.every((file) => file.type.startsWith("image/")) && imageUploader) {
3405
- const id = crypto.randomUUID();
3406
- pending.current++;
3407
- addUploadPlaceholder(ed2.view, { id, pos: at, kind: "image", label: `${files.length} 张图片`, file: files[0] });
3408
- try {
3409
- if (files.some((file) => file.size > 20 * 1024 * 1024)) throw new Error("图片不能超过 20 MB");
3410
- const nodes = await Promise.all(files.map(async (file) => {
3411
- const result = await imageUploader(file);
3412
- primeAttachment(result.id, file);
3413
- return ed2.schema.nodes.image.create({ attachmentId: result.id, alt: result.alt });
3414
- }));
3415
- if (disposed.current || ed2.isDestroyed || !ed2.isEditable) return;
3416
- const pos = currentPlaceholderPos(ed2.view, id);
3417
- if (pos === null) return;
3418
- const tr = ed2.state.tr;
3419
- let nextPos = pos;
3420
- for (let index = 0; index < nodes.length; index += 10) {
3421
- const chunk = nodes.slice(index, index + 10), block = chunk.length === 1 ? chunk[0] : ed2.schema.nodes.imageGallery.create({ height: 120 }, chunk);
3422
- const start = tr.mapping.maps.length, at2 = nextPos;
3423
- insertUploadedBlock(tr, at2, block);
3424
- nextPos = tr.mapping.slice(start).map(at2, 1);
3425
- }
3426
- ed2.view.dispatch(tr);
3427
- removeUploadPlaceholder(ed2.view, id);
3428
- } catch (error2) {
3429
- if (!disposed.current && !ed2.isDestroyed) {
3430
- removeUploadPlaceholder(ed2.view, id);
3431
- fail(error2);
3432
- }
3433
- } finally {
3434
- if (!ed2.isDestroyed) removeUploadPlaceholder(ed2.view, id);
3435
- pending.current--;
3436
- if (!disposed.current) update((value) => value + 1);
3437
- }
3438
- return;
3439
- }
3440
- const slots = files.map((file) => ({ file, id: crypto.randomUUID() }));
3441
- for (const { file, id } of slots) addUploadPlaceholder(ed2.view, { id, pos: at, kind: file.type.startsWith("image/") && kind !== "file" ? "image" : "file", label: file.name, file });
3442
- pending.current += slots.length;
3443
- update((value) => value + 1);
3444
- for (const { file, id } of slots) {
3445
- if (ed2.isDestroyed) {
3446
- pending.current--;
3447
- continue;
3448
- }
3449
- const image = file.type.startsWith("image/") && kind !== "file";
3450
- const uploader = image ? latest.current.uploadImage ?? ((_b = hostRef.current.attachments) == null ? void 0 : _b.uploadImage) : latest.current.uploadFile ?? ((_c = hostRef.current.attachments) == null ? void 0 : _c.uploadFile);
3451
- try {
3452
- if (!uploader) throw new Error("未配置附件上传服务");
3453
- if (file.size > 20 * 1024 * 1024) throw new Error("附件不能超过 20 MB");
3454
- const result = await uploader(file);
3455
- if (disposed.current || ed2.isDestroyed || !ed2.isEditable || currentPlaceholderPos(ed2.view, id) === null) continue;
3456
- primeAttachment(result.id, file);
3457
- const attrs = image ? { attachmentId: result.id, alt: "alt" in result ? result.alt : file.name } : { attachmentId: result.id, filename: file.name, mimeType: file.type, size: file.size };
3458
- replacePlaceholderWithNode(ed2.view, id, { type: image ? "image" : "fileAttachment", attrs });
3459
- } catch (error2) {
3460
- if (!ed2.isDestroyed && !disposed.current) {
3461
- removeUploadPlaceholder(ed2.view, id);
3462
- fail(error2);
3463
- }
3464
- } finally {
3465
- if (!ed2.isDestroyed) removeUploadPlaceholder(ed2.view, id);
3466
- pending.current--;
3467
- if (!disposed.current) update((value) => value + 1);
3468
- }
3469
- }
3470
- }
3471
- async function upgradeRemoteImages(ed2) {
3472
- var _a, _b;
3473
- const uploader = latest.current.uploadImage ?? ((_a = hostRef.current.attachments) == null ? void 0 : _a.uploadImage);
3474
- if (!uploader) {
3475
- tell("未配置图片上传服务,无法保存粘贴的外链图片");
3476
- return;
3477
- }
3478
- const targets = [];
3479
- ed2.state.doc.descendants((node, pos) => {
3480
- if (node.type.name === "image" && !node.attrs.attachmentId && /^https?:\/\//i.test(String(node.attrs.src))) {
3481
- const target = { from: pos, to: pos + node.nodeSize, valid: true };
3482
- remoteTargets.current.add(target);
3483
- targets.push({ target, src: node.attrs.src });
3484
- }
3485
- });
3486
- pending.current += targets.length;
3487
- update((value) => value + 1);
3488
- for (const { target, src } of targets) try {
3489
- const response = await fetch(src, { credentials: "omit", signal: AbortSignal.timeout(15e3) });
3490
- if (!response.ok || Number(response.headers.get("content-length")) > 20 * 1024 * 1024) throw new Error("外链图片读取失败或超过 20 MB");
3491
- const blob = await response.blob();
3492
- if (!blob.type.startsWith("image/") || blob.size > 20 * 1024 * 1024) throw new Error("外链图片格式无效或超过 20 MB");
3493
- const result = await uploader(new File([blob], "粘贴图片", { type: blob.type }));
3494
- if (ed2.isDestroyed || !ed2.isEditable || !target.valid || ((_b = ed2.state.doc.nodeAt(target.from)) == null ? void 0 : _b.attrs.src) !== src) continue;
3495
- const node = ed2.state.doc.nodeAt(target.from);
3496
- primeAttachment(result.id, blob);
3497
- ed2.view.dispatch(ed2.state.tr.setNodeMarkup(target.from, void 0, { ...node.attrs, src: null, attachmentId: result.id, alt: result.alt }));
3498
- } catch (error2) {
3499
- if (!ed2.isDestroyed) fail(error2);
3500
- } finally {
3501
- remoteTargets.current.delete(target);
3502
- pending.current--;
3503
- if (!ed2.isDestroyed) update((value) => value + 1);
3504
- }
3505
- }
3506
- const ed = editor.current;
3507
- const selection = ed == null ? void 0 : ed.state.selection;
3508
- const selectedImage = selection instanceof NodeSelection && selection.node.type.name === "image" ? { pos: selection.from, node: selection.node } : null;
3509
- const slashOpen = !!slash;
3510
- let selectionToolbar = null;
3511
- let imageToolbar = null;
3512
- if (ed && shell.current && !props.disabled && !slashOpen && !blockMenu && !link && !dragging) {
3513
- const hostRect = shell.current.getBoundingClientRect();
3514
- if (selection instanceof TextSelection && !selection.empty) {
3515
- try {
3516
- const start = ed.view.coordsAtPos(selection.from), end = ed.view.coordsAtPos(selection.to);
3517
- selectionToolbar = { x: (start.left + end.right) / 2 - hostRect.left, y: Math.min(start.top, end.top) - hostRect.top - 8 };
3518
- } catch {
3519
- selectionToolbar = null;
3520
- }
3521
- }
3522
- if (selectedImage && imagePick === selectedImage.pos) {
3523
- try {
3524
- const dom = ed.view.nodeDOM(selectedImage.pos);
3525
- if (dom instanceof HTMLElement) {
3526
- const rect = dom.getBoundingClientRect(), above = rect.top - hostRect.top - 44;
3527
- imageToolbar = { x: rect.left - hostRect.left + rect.width / 2, y: above < 4 ? rect.bottom - hostRect.top + 10 : above };
3528
- }
3529
- } catch {
3530
- imageToolbar = null;
3531
- }
3532
- }
3533
- }
3534
- const hoverInfo = blockMenu ? frozen ?? hover : hover;
3535
- const style = hoverInfo && shell.current ? blockAffordanceStyle(hoverInfo, shell.current, hoverInfo.isEmpty ? "plus" : "chip") : { left: 0, top: 0 };
3536
- function imageAlign(value) {
3537
- if (!ed || !selectedImage || !ed.isEditable) return;
3538
- ed.view.dispatch(ed.state.tr.setNodeMarkup(selectedImage.pos, void 0, { ...selectedImage.node.attrs, align: value }));
3539
- ed.commands.focus();
3540
- }
3541
- function previewImage() {
3542
- if (!ed || !selectedImage) return;
3543
- const dom = ed.view.nodeDOM(selectedImage.pos), img = dom instanceof HTMLElement ? dom.querySelector("img") : null;
3544
- const src = (img == null ? void 0 : img.getAttribute("src")) ?? "";
3545
- if (!src) return;
3546
- const { items, index } = previewItemsFrom(img, "图片预览");
3547
- setPreview({ src, label: selectedImage.node.attrs.alt ? String(selectedImage.node.attrs.alt) : "图片预览", items: items.length ? items : [{ src, label: "图片预览" }], index: items.length ? index : 0 });
3548
- }
3549
- function removeImage() {
3550
- if (!ed || !selectedImage || !ed.isEditable) return;
3551
- const $pos = ed.state.doc.resolve(selectedImage.pos);
3552
- if ($pos.parent.type.name === "imageGallery") {
3553
- const from = $pos.before(), to = from + $pos.parent.nodeSize;
3554
- const range = new BlockRangeSelection(ed.state.doc.resolve(from), ed.state.doc.resolve(to), [{
3555
- kind: "images",
3556
- from,
3557
- to,
3558
- images: [{ from: selectedImage.pos, to: selectedImage.pos + selectedImage.node.nodeSize }]
3559
- }]);
3560
- const tr = ed.state.tr;
3561
- range.replace(tr);
3562
- ed.view.dispatch(tr);
3563
- ed.view.focus();
3564
- return;
3565
- }
3566
- ed.chain().focus().deleteRange({ from: selectedImage.pos, to: selectedImage.pos + selectedImage.node.nodeSize }).run();
3567
- }
3568
- const quickVisible = !!hoverInfo && !props.disabled && !blockMenu && !dragging && !tableHover && hoverInfo.type !== "table" && !slash && !selectionToolbar && !preview && !link;
3569
- return /* @__PURE__ */ jsxs("div", { ref: shell, className: `wr-document editor-shell${selectedImage && imagePick === selectedImage.pos ? " img-pick-active" : ""}${props.className ? ` ${props.className}` : ""}`, "data-block-select-band": true, children: [
3570
- notice && /* @__PURE__ */ jsx("p", { role: "status", children: notice }),
3571
- error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
3572
- /* @__PURE__ */ jsx("div", { ref: mount, className: "wr-document-mount" }),
3573
- selectionToolbar && ed && !selectedImage && /* @__PURE__ */ jsx(
3574
- SelectionToolbar,
3575
- {
3576
- editor: ed,
3577
- x: selectionToolbar.x,
3578
- y: selectionToolbar.y,
3579
- canComment: props.canComment,
3580
- onRequestComment: () => {
3581
- var _a, _b;
3582
- return (_b = (_a = latest.current).onRequestComment) == null ? void 0 : _b.call(_a);
3583
- },
3584
- onRequestLink: () => openLink(ed),
3585
- onRemoveLink: () => {
3586
- ed.chain().focus().unsetLink().run();
3587
- }
3588
- }
3589
- ),
3590
- imageToolbar && selectedImage && ed && /* @__PURE__ */ jsxs("div", { className: "img-toolbar", role: "toolbar", "aria-label": "图片操作", style: { left: imageToolbar.x, top: imageToolbar.y }, onMouseDown: (event) => event.preventDefault(), children: [
3591
- ["left", "center", "right"].map((align) => /* @__PURE__ */ jsx(
3592
- "button",
3593
- {
3594
- type: "button",
3595
- className: `it-btn${selectedImage.node.attrs.align === align ? " on" : ""}`,
3596
- "aria-label": `图片${align === "left" ? "左" : align === "center" ? "居中" : "右"}对齐`,
3597
- title: `图片${align === "left" ? "左" : align === "center" ? "居中" : "右"}对齐`,
3598
- "aria-pressed": selectedImage.node.attrs.align === align,
3599
- onClick: () => imageAlign(align),
3600
- children: /* @__PURE__ */ jsx(WIcon, { name: `align-${align}`, size: 16 })
3601
- },
3602
- align
3603
- )),
3604
- /* @__PURE__ */ jsx("span", { className: "it-sep", "aria-hidden": "true" }),
3605
- /* @__PURE__ */ jsx("button", { type: "button", className: "it-btn", "aria-label": "编辑图注", title: "编辑图注", onClick: () => {
3606
- const dom = ed.view.nodeDOM(selectedImage.pos);
3607
- if (dom instanceof HTMLElement) dom.dispatchEvent(new CustomEvent("ywiki-caption-edit"));
3608
- }, children: /* @__PURE__ */ jsx(WIcon, { name: "text", size: 16 }) }),
3609
- /* @__PURE__ */ jsx("button", { type: "button", className: "it-btn", "aria-label": "查看大图", title: "查看大图", onClick: previewImage, children: /* @__PURE__ */ jsx(WIcon, { name: "zoom-in", size: 16 }) }),
3610
- /* @__PURE__ */ jsx("span", { className: "it-sep", "aria-hidden": "true" }),
3611
- /* @__PURE__ */ jsx("button", { type: "button", className: "it-btn danger", "aria-label": "删除图片", title: "删除图片", onClick: removeImage, children: /* @__PURE__ */ jsx(WIcon, { name: "trash", size: 16 }) })
3612
- ] }),
3613
- hoverInfo && quickVisible && (hoverInfo.isEmpty ? /* @__PURE__ */ jsx(BlockPlus, { info: hoverInfo, style, expanded: blockMenu, onMouseEnter: () => onBlockHover(lastHover.current), onClick: () => {
3614
- setFrozen(hoverInfo);
3615
- setPanelOnly("insert-below");
3616
- setBlockMenu(true);
3617
- setHover(hoverInfo);
3618
- ed == null ? void 0 : ed.commands.focus();
3619
- } }) : /* @__PURE__ */ jsx(
3620
- BlockChip,
3621
- {
3622
- editor: ed,
3623
- info: hoverInfo,
3624
- style,
3625
- expanded: blockMenu,
3626
- onMouseEnter: () => onBlockHover(lastHover.current),
3627
- onOpen: () => {
3628
- setFrozen(hoverInfo);
3629
- setPanelOnly("format");
3630
- setBlockMenu(true);
3631
- setHover(hoverInfo);
3632
- },
3633
- onOpenFull: () => {
3634
- var _a;
3635
- if ((_a = gestures.current) == null ? void 0 : _a.consumeClick()) return;
3636
- setFrozen(hoverInfo);
3637
- setPanelOnly(void 0);
3638
- setBlockMenu(true);
3639
- setHover(hoverInfo);
3640
- },
3641
- onHandleMouseDown: (event) => {
3642
- var _a;
3643
- (_a = gestures.current) == null ? void 0 : _a.start(hoverInfo.pos, event.nativeEvent, () => {
3644
- });
3645
- }
3646
- }
3647
- )),
3648
- blockMenu && frozen && ed && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, zIndex: 45, pointerEvents: "none" }, children: /* @__PURE__ */ jsx(
3649
- BlockMenu,
3650
- {
3651
- editor: ed,
3652
- info: frozen,
3653
- canComment: props.canComment,
3654
- panelOnly,
3655
- onClose: () => {
3656
- setBlockMenu(false);
3657
- setFrozen(null);
3658
- setPanelOnly(void 0);
3659
- setHover(lastHover.current);
3660
- },
3661
- onPickFile: (kind) => {
3662
- closeBlockMenu();
3663
- requestFile(kind);
3664
- },
3665
- onPickPage: () => {
3666
- closeBlockMenu();
3667
- requestPage();
3668
- },
3669
- onPickVideo: () => {
3670
- closeBlockMenu();
3671
- requestVideo();
3672
- },
3673
- onRequestComment: () => {
3674
- var _a, _b;
3675
- const node = ed.state.doc.nodeAt(frozen.pos);
3676
- if (node) ed.commands.setTextSelection({ from: frozen.pos + 1, to: frozen.pos + node.nodeSize - 1 });
3677
- (_b = (_a = latest.current).onRequestComment) == null ? void 0 : _b.call(_a);
3678
- },
3679
- onNotice: tell,
3680
- onCopyLink: props.pageId ? () => {
3681
- var _a;
3682
- const target = (_a = host.references) == null ? void 0 : _a.resolve(props.pageId, "当前内容");
3683
- if (!(target == null ? void 0 : target.href)) {
3684
- tell("宿主未配置当前内容链接");
3685
- return;
3686
- }
3687
- void navigator.clipboard.writeText(target.href).catch(() => tell("无法写入剪贴板"));
3688
- } : void 0,
3689
- onSharePage: props.onSharePage,
3690
- onConvertToSubpage: props.onConvertToSubpage,
3691
- onSaveAsTemplate: props.onSaveAsTemplate
3692
- }
3693
- ) }),
3694
- slash && ed && /* @__PURE__ */ jsx(
3695
- SlashMenu,
3696
- {
3697
- editor: ed,
3698
- from: slash.from,
3699
- query: slash.query,
3700
- x: slash.x,
3701
- y: slash.y,
3702
- onClose: () => setSlash(null),
3703
- onPickFile: requestFile,
3704
- onPickPage: requestPage,
3705
- onPickVideo: requestVideo
3706
- }
3707
- ),
3708
- ed && !props.disabled && !dragging && /* @__PURE__ */ jsx(
3709
- TableControls,
3710
- {
3711
- editor: ed,
3712
- hover: tableHover,
3713
- revision: epoch,
3714
- onNotice: tell,
3715
- onBlockDrag: (pos, event, onClick) => {
3716
- var _a;
3717
- return (_a = gestures.current) == null ? void 0 : _a.start(pos, event, onClick);
3718
- }
3719
- }
3720
- ),
3721
- /* @__PURE__ */ jsx("input", { ref: fileInput, hidden: true, type: "file", multiple: true, onChange: (event) => {
3722
- const target = pendingInsert.current;
3723
- pendingInsert.current = null;
3724
- if (!(target == null ? void 0 : target.valid)) tell("插入目标已移除,请重新选择位置");
3725
- else if (event.target.files) void upload([...event.target.files], target.from, fileKind.current);
3726
- event.target.value = "";
3727
- } }),
3728
- link && /* @__PURE__ */ jsx(
3729
- LinkPopover,
3730
- {
3731
- target: link,
3732
- onClose: () => setLink(null),
3733
- onEdit: () => setLink({ ...link, mode: "edit" }),
3734
- onSave: (href) => applyLink(href),
3735
- onRemove: () => applyLink(null)
3736
- },
3737
- link.mode
3738
- ),
3739
- video && /* @__PURE__ */ jsx(
3740
- VideoEmbedDialog,
3741
- {
3742
- initialUrl: videoUrl,
3743
- onClose: () => {
3744
- pendingInsert.current = null;
3745
- setVideo(false);
3746
- },
3747
- onInsert: (attrs) => {
3748
- insertPending({ type: "videoEmbed", attrs });
3749
- setVideo(false);
3750
- }
3751
- }
3752
- ),
3753
- references && /* @__PURE__ */ jsx(
3754
- PagePicker,
3755
- {
3756
- spaceId: props.spaceId,
3757
- excludeId: props.pageId,
3758
- title: "选择要引用的页面",
3759
- onClose: () => {
3760
- pendingInsert.current = null;
3761
- setReferences(false);
3762
- },
3763
- onPick: (id, title) => {
3764
- insertPending({ type: "pageLink", attrs: { pageId: id, label: title } });
3765
- setReferences(false);
3766
- }
3767
- }
3768
- ),
3769
- preview && /* @__PURE__ */ jsx(ImagePreview, { src: preview.src, label: preview.label, items: preview.items, index: preview.index, onClose: () => setPreview(null) }),
3770
- atoms.map((atom) => createPortal(atom.content, atom.dom, atom.id))
3771
- ] });
3772
- function closeBlockMenu() {
3773
- setBlockMenu(false);
3774
- setFrozen(null);
3775
- setPanelOnly(void 0);
3776
- }
3777
- function applyLink(href) {
3778
- var _a;
3779
- const ed2 = editor.current, target = (_a = linkRef.current) == null ? void 0 : _a.tracked;
3780
- if (!(ed2 == null ? void 0 : ed2.isEditable) || !target || !linkEditTargetCurrent(ed2.state, target)) {
3781
- tell("目标文字或链接已变化,请重新选择");
3782
- setLink(null);
3783
- return;
3784
- }
3785
- const chain = ed2.chain().focus().setTextSelection({ from: target.from, to: target.to });
3786
- if (href) chain.setLink({ href }).run();
3787
- else chain.unsetLink().run();
3788
- setLink(null);
3789
- }
3790
- });
3791
- let schema;
3792
- function sameDocument(a2, b2) {
3793
- if (!a2 || !b2) return a2 === b2;
3794
- try {
3795
- schema ?? (schema = getSchema(coreExtensions()));
3796
- return schema.nodeFromJSON(a2).eq(schema.nodeFromJSON(b2));
3797
- } catch {
3798
- return false;
3799
- }
3800
- }
3801
- export {
3802
- A as ANCHORED_POPOVER_ARROW_INSET,
3803
- a as ANCHORED_POPOVER_GAP,
3804
- b as ANCHORED_POPOVER_VIEW_MARGIN,
3805
- C as COLLAB_FIELD,
3806
- DocumentEditor,
3807
- D as DocumentExportMenu,
3808
- coreExtensions,
3809
- placeAnchoredPopover,
3810
- sameDocument
3811
- };
1
+ "use client";import{jsx as e,jsxs as t,Fragment as n}from"react/jsx-runtime";import{c as o,a as l,p as r,u as i}from"./chunks/i18n-B0kCsIjc.js";import{getMarkRange as a,Editor as s,getSchema as c}from"@tiptap/core";import{TextSelection as u,Selection as d,NodeSelection as m}from"@tiptap/pm/state";import{createPortal as p}from"react-dom";import{useRef as h,useState as v,useLayoutEffect as f,useEffect as g,useMemo as b,useId as w,forwardRef as y,useCallback as k,useImperativeHandle as x}from"react";import{ContentSchemaService as C,requiredSchemaVersion as E,SCHEMA_VERSION as M}from"wendang/schema";import{safeHref as N,TEXT_COLORS as L,HIGHLIGHT_COLORS as D,documentColorAttributes as S,parseVideoUrl as A}from"wendang/shared";import{b as T,d as B,u as I,h as P,T as R,c as z,r as H,t as O,a as q,s as $,i as F,e as V,f as W,g as Y,j as U,k as j,l as _,m as J,n as X,o as G,p as Q,q as K,v as Z,B as ee,w as te,x as ne,y as oe,z as le,A as re,D as ie,E as ae,F as se,G as ce,H as ue,I as de}from"./chunks/extensions-BefQ1tDm.js";import{C as me}from"./chunks/extensions-BefQ1tDm.js";import{c as pe}from"./chunks/mind-map-controller-Ce76mETT.js";import{M as he}from"./chunks/MindMapViewer-C9zxZIW8.js";import{p as ve,I as fe,V as ge}from"./chunks/mind-topic-text-BHXkxexr.js";import{u as be,a as we,b as ye}from"./chunks/hooks-C53Dp0pr.js";import{a as ke,b as xe,p as Ce,l as Ee,t as Me,c as Ne}from"./chunks/index-COeA2QcQ.js";import{p as Le}from"./chunks/popover-placement-D3xVYdkt.js";import{A as De,a as Se,b as Ae}from"./chunks/popover-placement-D3xVYdkt.js";import{isSafeHref as Te}from"wendang/internal/safe-href";import{createMindMap as Be}from"wendang/internal/mind-map";import{s as Ie}from"./chunks/composition-state-Bl-U1R_2.js";import{DOMSerializer as Pe,Fragment as Re}from"@tiptap/pm/model";import{createTable as ze}from"@tiptap/extension-table";import{createMindMap as He}from"wendang/mindmap";import{closeHistory as Oe}from"@tiptap/pm/history";import{yUndoPluginKey as qe,ySyncPluginKey as $e}from"@tiptap/y-tiptap";import{CellSelection as Fe,TableMap as Ve}from"@tiptap/pm/tables";import{CELL_BACKGROUNDS as We}from"wendang/internal/table-cell-format";import{r as Ye}from"./chunks/fuzzy-match-DYQR-4w2.js";import{inspectVideoUrl as Ue,VIDEO_PROVIDER_LABELS as je,videoSourceUrl as _e,videoLoadsAutomatically as Je,normalizeVideoEmbed as Xe}from"wendang/internal/video-embed";import{createI18n as Ge,readI18n as Qe}from"wendang/i18n";import{D as Ke}from"./chunks/DocumentExportMenu-CUMsgq3w.js";function createLinkEditTarget(e,t=e.selection){if(!(t instanceof u))return null;const n=e.schema.marks.link;if(!n)return null;let{from:o,to:l}=t;const r=a(t.$from,n);if(r&&l<=r.to&&({from:o,to:l}=r),o===l)return null;let i="";return e.doc.nodesBetween(o,l,e=>{var t;const o=null==(t=e.marks.find(e=>e.type===n))?void 0:t.attrs.href;i||"string"!=typeof o||(i=o)}),{from:o,to:l,valid:!0,href:i,hasLink:e.doc.rangeHasMark(o,l,n),contentJSON:JSON.stringify(e.doc.slice(o,l).toJSON())}}function LinkPopover({target:n,onEdit:r,onSave:i,onRemove:a,onClose:s}){const c=o().text,{portal:u}=l(),d=h(null),m=h(null),[b,w]=v(n.href),[y,k]=v(""),[x,C]=v({left:n.anchor.left,top:n.anchor.top+n.anchor.height+8});return f(()=>{const measure=()=>{var e;const t=null==(e=d.current)?void 0:e.getBoundingClientRect();t&&C(Le({align:"preview"===n.mode?"center":"start",prefer:"preview"===n.mode?"above":"below",anchor:n.anchor,host:{left:0,top:0},card:t,bounds:{left:0,top:0,width:innerWidth,height:innerHeight}}))};measure();const e=new ResizeObserver(measure);return d.current&&e.observe(d.current),()=>e.disconnect()},[n.mode,n.anchor,u]),f(()=>{var e,t;"edit"===n.mode&&u&&(null==(e=m.current)||e.focus({preventScroll:!0}),null==(t=m.current)||t.select())},[n.mode,u]),g(()=>{const outside=e=>{var t;e.target instanceof Node&&!(null==(t=d.current)?void 0:t.contains(e.target))&&s()},escape=e=>{"Escape"===e.key&&(e.stopPropagation(),s())};return document.addEventListener("pointerdown",outside),document.addEventListener("keydown",escape),window.addEventListener("scroll",s,!0),()=>{document.removeEventListener("pointerdown",outside),document.removeEventListener("keydown",escape),window.removeEventListener("scroll",s,!0)}},[]),g(()=>{if("preview"!==n.mode)return;let e;const move=t=>{var o;const l=n.anchor;(null==(o=d.current)?void 0:o.contains(t.target))||t.clientX>=l.left-5&&t.clientX<=l.left+l.width+5&&t.clientY>=l.top-5&&t.clientY<=l.top+l.height+5?(clearTimeout(e),e=void 0):e||(e=setTimeout(s,180))};return document.addEventListener("mousemove",move),()=>{clearTimeout(e),document.removeEventListener("mousemove",move)}},[n.mode,n.anchor,s]),u?p(/* @__PURE__ */e("div",{ref:d,className:"wr-link-popover",style:x,role:"edit"===n.mode?"dialog":"toolbar","aria-label":"edit"===n.mode?c("编辑链接"):c("链接预览"),children:"preview"===n.mode?/* @__PURE__ */t("div",{className:"wr-toolbar",children:[
2
+ /* @__PURE__ */e("a",{href:N(n.href),target:"_blank",rel:"noopener noreferrer",children:n.href}),
3
+ /* @__PURE__ */e(ke,{onClick:r,children:c("编辑链接")}),
4
+ /* @__PURE__ */e(ke,{onClick:a,children:c("取消链接")})]}):/* @__PURE__ */t("form",{onSubmit:e=>{e.preventDefault();const t=function linkHrefProblem(e){const t=e.trim();if(!t)return"请输入链接地址。";if(t.length>2048||/[\u0000-\u0020\u007f]/.test(t)||!Te(t))return"请输入有效的网页、邮箱或站内链接。";if(/^https?:/i.test(t))try{const e=new URL(t);if(!/^https?:\/\//i.test(t)||!e.hostname)return"请输入完整的网址,例如 https://example.com。"}catch{return"请输入完整的网址,例如 https://example.com。"}return/^mailto:$/i.test(t)?"请输入完整的邮箱链接,例如 mailto:name@example.com。":""}(b);t?k(t):i(b.trim())},children:[
5
+ /* @__PURE__ */t("div",{className:"wr-toolbar",children:[
6
+ /* @__PURE__ */e("input",{ref:m,"aria-label":c("链接地址"),value:b,onChange:e=>{w(e.target.value),k("")},placeholder:c("粘贴或输入链接")}),
7
+ /* @__PURE__ */e(ke,{htmlType:"submit",children:c("确认")}),
8
+ /* @__PURE__ */e(ke,{onClick:a,children:c("取消链接")})]}),y&&/* @__PURE__ */e("p",{role:"alert",children:y})]})}),u):null}function DocumentColorPanel({textColor:n,highlightColor:l,onTextColor:r,onHighlightColor:i,onReset:a,onClose:s}){const c=o().text;/* @__PURE__ */
9
+ return t("div",{className:"document-color-panel",role:"group","aria-label":c("文档颜色"),onMouseDown:e=>e.preventDefault(),children:[
10
+ /* @__PURE__ */t("div",{className:"color-section",children:[
11
+ /* @__PURE__ */e("div",{className:"color-label",children:c("字体颜色")}),
12
+ /* @__PURE__ */e("div",{className:"color-grid",role:"group","aria-label":c("字体颜色"),children:L.map(t=>/* @__PURE__ */e("button",{type:"button",className:"color-choice"+(n===t.value?" selected":""),"aria-label":c("字体颜色:{0}",{0:c(t.name)}),title:c("字体颜色:{0}",{0:c(t.name)}),"aria-pressed":n===t.value,onClick:()=>r(t.value),children:/* @__PURE__ */e("span",{className:"font-sample",style:textColorStyle(t.value),children:"A"})},t.name))})]}),
13
+ /* @__PURE__ */t("div",{className:"color-section",children:[
14
+ /* @__PURE__ */e("div",{className:"color-label",children:c("背景颜色")}),
15
+ /* @__PURE__ */e("div",{className:"color-grid",role:"group","aria-label":c("背景颜色"),children:D.map(t=>/* @__PURE__ */e("button",{type:"button",className:`color-choice background-choice${l===t.value?" selected":""}${null===t.value?" no-background":""}`,style:t.value?{background:t.value}:void 0,"aria-label":c("背景颜色:{0}",{0:c(t.name)}),title:c("背景颜色:{0}",{0:c(t.name)}),"aria-pressed":l===t.value,onClick:()=>i(t.value),children:null===t.value&&/* @__PURE__ */e("span",{className:"color-transparency-grid","aria-hidden":"true"})},t.name))})]}),
16
+ /* @__PURE__ */e(ke,{variant:"outline",block:!0,onClick:a,children:c("恢复默认")}),s&&/* @__PURE__ */e(ke,{variant:"text",block:!0,onClick:s,children:c("关闭")})]})}function textColorStyle(e){const t=S("text",e).style;if(!t)return{};const n=t.indexOf(":");return{color:t.slice(n+1).trim()}}function SelectionToolbar({editor:l,x:r,y:i,canComment:a,onRequestComment:s,onRequestLink:c,onRemoveLink:u}){const d=o().text,[m,p]=v("none"),g=h(null),[b,w]=v(r);f(()=>{const measure=()=>{const e=g.current,t=null==e?void 0:e.closest(".editor-shell");if(!e||!t)return;const n=t.getBoundingClientRect(),o=e.getBoundingClientRect().width;w(Math.max(8-n.left,Math.min(r,innerWidth-8-o-n.left)))};return measure(),window.addEventListener("resize",measure),()=>window.removeEventListener("resize",measure)},[r,i]);const active=e=>l.isActive(e),y="string"==typeof l.getAttributes("textStyle").color?l.getAttributes("textStyle").color:null,k="string"==typeof l.getAttributes("highlight").color?l.getAttributes("highlight").color:null,x=active("link")||(()=>{const{from:e,to:t}=l.state.selection;try{return l.state.doc.rangeHasMark(e,t,l.schema.marks.link)}catch{return!1}})(),chain=()=>l.chain().focus(),C=[{name:"bold",icon:"bold",label:d("加粗")},{name:"italic",icon:"italic",label:d("斜体")},{name:"strike",icon:"strike",label:d("删除线")},{name:"underline",icon:"underline",label:d("下划线")},{name:"code",icon:"inline-code",label:d("行内代码")}];/* @__PURE__ */
17
+ return t("div",{ref:g,className:"sel-toolbar",role:"toolbar","aria-label":d("文字格式"),style:{left:b,top:i},onMouseDown:e=>e.preventDefault(),children:[C.map(t=>/* @__PURE__ */e("button",{type:"button",className:"tb-btn"+(active(t.name)?" on":""),"aria-label":t.label,"aria-pressed":active(t.name),title:t.label,onClick:()=>function toggleMark(e){const t=chain();"bold"===e?t.toggleBold().run():"italic"===e?t.toggleItalic().run():"strike"===e?t.toggleStrike().run():"underline"===e?t.toggleUnderline().run():t.toggleCode().run(),p("none")}(t.name),children:/* @__PURE__ */e(xe,{name:t.icon,size:20})},t.name)),
18
+ /* @__PURE__ */e("button",{type:"button",className:"tb-btn"+(x?" on":""),"aria-label":d(x?"编辑链接":"添加链接"),title:d(x?"编辑链接":"添加链接"),"aria-haspopup":"dialog",onClick:()=>null==c?void 0:c(),children:/* @__PURE__ */e(xe,{name:"format-link",size:20})}),x&&/* @__PURE__ */e("button",{type:"button",className:"tb-btn","aria-label":d("取消链接"),title:d("取消链接"),onClick:()=>null==u?void 0:u(),children:/* @__PURE__ */e(xe,{name:"unlink",size:20})}),
19
+ /* @__PURE__ */e("span",{className:"tb-sep","aria-hidden":"true"}),
20
+ /* @__PURE__ */t("div",{className:"tb-color-wrap",children:[
21
+ /* @__PURE__ */e("button",{type:"button",className:"tb-btn"+("color"===m?" on":""),title:d("文字与背景颜色"),"aria-label":d("文字与背景颜色"),"aria-expanded":"color"===m,onClick:()=>p("color"===m?"none":"color"),children:/* @__PURE__ */e(xe,{name:"text-color",size:20,style:colorLetterStyle(y)})}),"color"===m&&/* @__PURE__ */e("div",{className:"tb-panel",children:/* @__PURE__ */e(DocumentColorPanel,{textColor:y,highlightColor:k,onTextColor:e=>{const t=chain();e?t.setMark("textStyle",{color:e}).run():t.unsetMark("textStyle").run()},onHighlightColor:e=>{const t=chain();e?t.setMark("highlight",{color:e}).run():t.unsetMark("highlight").run()},onReset:()=>{chain().unsetMark("textStyle").unsetMark("highlight").run(),p("none")},onClose:()=>p("none")})})]}),a&&/* @__PURE__ */t(n,{children:[
22
+ /* @__PURE__ */e("span",{className:"tb-sep","aria-hidden":"true"}),
23
+ /* @__PURE__ */e("button",{type:"button",className:"tb-btn","aria-label":d("评论选中的文字"),title:d("评论选中的文字"),onClick:()=>null==s?void 0:s(),children:/* @__PURE__ */e(xe,{name:"comment",size:20})})]})]})}function colorLetterStyle(e){const t=S("text",e).style;if(!t)return{};const n=t.indexOf(":");return{[t.slice(0,n).trim()]:t.slice(n+1).trim()}}function clampGrid(e){const t=Math.floor(Number(e));return Number.isNaN(t)?1:Math.min(Math.max(t,1),8)}function fitRange(e,t,n=Number.NEGATIVE_INFINITY,o=Number.POSITIVE_INFINITY){let l=e;return Number.isFinite(o)&&l+t>o&&(l=o-t),Number.isFinite(n)&&l<n&&(l=n),Math.round(l)}const Ze=[{id:"text",label:"正文",hint:"/text",group:"basic",icon:"text",convertible:!0,keywords:["text","paragraph","正文","zhengwen","普通文本"],run:({chain:e})=>e().setParagraph().run()},{id:"h1",convertible:!0,label:"一级标题",hint:"/h1",group:"basic",icon:"h1",keywords:["h1","标题","biaoti","heading","yijibiaoti"],run:({chain:e})=>e().setHeading({level:1}).run()},{id:"h2",convertible:!0,label:"二级标题",hint:"/h2",group:"basic",icon:"h2",keywords:["h2","标题","biaoti","erjibiaoti"],run:({chain:e})=>e().setHeading({level:2}).run()},{id:"h3",convertible:!0,label:"三级标题",hint:"/h3",group:"basic",icon:"h3",keywords:["h3","标题","biaoti","sanjibiaoti"],run:({chain:e})=>e().setHeading({level:3}).run()},{id:"h4",convertible:!0,label:"四级标题",hint:"/h4",group:"basic",icon:"h4",keywords:["h4","标题","biaoti","sijibiaoti"],run:({chain:e})=>e().setHeading({level:4}).run()},{id:"h5",convertible:!0,label:"五级标题",hint:"/h5",group:"basic",icon:"h5",keywords:["h5","标题","biaoti","wujibiaoti"],run:({chain:e})=>e().setHeading({level:5}).run()},{id:"h6",convertible:!0,label:"六级标题",hint:"/h6",group:"basic",icon:"h6",keywords:["h6","标题","biaoti","liujibiaoti"],run:({chain:e})=>e().setHeading({level:6}).run()},{id:"ordered",convertible:!0,label:"有序列表",hint:"/ol",group:"basic",icon:"ordered-list",keywords:["list","ol","列表","liebiao","youxuliebiao"],run:({chain:e})=>e().toggleOrderedList().run()},{id:"bullet",convertible:!0,label:"无序列表",hint:"/ul",group:"basic",icon:"bullet-list",keywords:["list","ul","列表","liebiao","wuxuliebiao"],run:({chain:e})=>e().toggleBulletList().run()},{id:"quote",convertible:!0,label:"引用",hint:"/quote",group:"basic",icon:"quote",keywords:["quote","引用","yinyong"],run:({chain:e})=>e().toggleBlockquote().run()},{id:"code",convertible:!0,label:"代码块",hint:"/code",group:"basic",icon:"code",keywords:["code","代码","daima","daimakuai"],run:({chain:e})=>e().toggleCodeBlock().run()},{id:"hr",label:"分割线",hint:"/hr",group:"basic",icon:"divider",keywords:["hr","divider","分割线","fengexian"],run:({chain:e})=>e().setHorizontalRule().run()},{id:"link",label:"链接",hint:"/link",group:"basic",icon:"link",keywords:["link","链接","lianjie","url"],run:({chain:e})=>{const t="https://";e().insertContent({type:"text",text:t,marks:[{type:"link",attrs:{href:t}}]}).run()}},{id:"mindmap",label:"思维导图",hint:"/mindmap",group:"common",icon:"mind-map",keywords:["mindmap","mind","思维导图","导图","siweidaotu","脑图"],run:({chain:e})=>e().insertContent([Be(),{type:"paragraph"}]).run()},{id:"task",convertible:!0,label:"任务",hint:"/todo",group:"common",icon:"task",keywords:["task","todo","待办","daiban","renwu","checkbox"],run:({chain:e})=>e().toggleTaskList().run()},{id:"image",label:"图片",hint:"/image",group:"common",icon:"image",keywords:["image","img","图片","tupian","photo"],run:({requestPickFile:e})=>null==e?void 0:e("image")},{id:"file",label:"上传文件",hint:"/file",group:"common",icon:"file",keywords:["file","文件","wenjian","上传","attachment"],run:({requestPickFile:e})=>null==e?void 0:e("file")},{id:"video",label:"嵌入视频",hint:"/video",group:"common",icon:"file",keywords:["video","youtube","bilibili","视频","shipin","b站"],run:({requestVideo:e})=>null==e?void 0:e()},{id:"table",label:"表格",hint:"/table",group:"common",icon:"table",keywords:["table","表格","biaoge"],panel:"table",run:({chain:e,panelValue:t})=>e().insertTable({rows:clampGrid((null==t?void 0:t.rows)??3),cols:clampGrid((null==t?void 0:t.cols)??3),withHeaderRow:!0}).run()},{id:"columns",label:"分栏",hint:"/columns",group:"common",icon:"columns",keywords:["columns","column","分栏","fenlan","多栏","duolan","layout"],panel:"columns",run:({chain:e,panelValue:t})=>{e().insertContent(T((null==t?void 0:t.count)??2)).run()}},{id:"callout",convertible:!0,label:"高亮块",hint:"/callout",group:"common",icon:"callout",keywords:["callout","highlight","高亮","gaoliang","提示块","tishikuai"],run:({chain:e})=>{e().insertContent({type:"callout",attrs:{tone:"info"},content:[{type:"paragraph"}]}).run()}},{id:"math",label:"公式",hint:"/math",group:"common",icon:"math",keywords:["math","formula","公式","gongshi","latex","katex"],run:({chain:e})=>{e().insertContent({type:"mathBlock",attrs:{latex:"E = mc^2"}}).run()}},{id:"page",label:"子文档",hint:"/page",group:"common",icon:"page",keywords:["page","子文档","ziwendang","页面","yemian","link","引用"],run:({requestPickPage:e})=>null==e?void 0:e()}],et=[{id:"basic",label:"基础"},{id:"common",label:"常用"}];function InsertPicker({mode:n,x:l,y:r,yBottom:i,boundTop:a,boundBottom:s,onPick:c,onClose:u}){const d=o().text,m=h(null),[p,f]=v(r),[b,w]=v({rows:3,cols:3}),[y,k]=v(2);g(()=>{const e=m.current;e&&f(function computePanelTop(e,t,n=Number.NEGATIVE_INFINITY,o=Number.POSITIVE_INFINITY,l=e+t){const fits=e=>(!Number.isFinite(n)||e>=n)&&(!Number.isFinite(o)||e+t<=o),r=e;if(fits(r))return Math.round(r);const i=l-t;return fits(i)?Math.round(i):Math.round(fitRange(r,t,n,o))}(r,e.offsetHeight,a,s,i??r+e.offsetHeight))},[n,r,i,a,s]);const x=function buildGridCells(e,t){const n=[];for(let o=1;o<=8;o+=1)for(let l=1;l<=8;l+=1)n.push({row:o,col:l,on:o<=e&&l<=t});return n}(b.rows,b.cols);/* @__PURE__ */
24
+ return t("div",{ref:m,className:"insert-picker",role:"dialog","aria-label":d("table"===n?"选择表格尺寸":"选择分栏栏数"),style:{left:l,top:p},onMouseDown:e=>e.preventDefault(),children:[
25
+ /* @__PURE__ */t("div",{className:"ip-head",children:[
26
+ /* @__PURE__ */e("span",{children:d("table"===n?"插入表格":"选择栏数")}),
27
+ /* @__PURE__ */e("span",{className:"ip-dim",children:"table"===n?`${b.rows} x ${b.cols}`:d("{0} 栏",{0:y})})]}),/* @__PURE__ */e("div","table"===n?{className:"ip-grid",children:x.map(t=>/* @__PURE__ */e("button",{type:"button",className:"ip-cell"+(t.on?" on":""),"aria-label":d("{0} 行 {1} 列",{0:t.row,1:t.col}),onMouseEnter:()=>w({rows:t.row,cols:t.col}),onFocus:()=>w({rows:t.row,cols:t.col}),onClick:()=>c({rows:t.row,cols:t.col})},`${t.row}-${t.col}`))}:{className:"ip-counts",children:Array.from({length:5},(e,t)=>t+1).map(n=>/* @__PURE__ */t("button",{type:"button",className:"ip-count"+(y===n?" on":""),"aria-label":d("{0} 栏",{0:n}),onMouseEnter:()=>k(n),onClick:()=>c({count:n}),children:[
28
+ /* @__PURE__ */e("span",{className:"ip-count-box",children:Array.from({length:n},(t,n)=>/* @__PURE__ */e("span",{className:"ip-count-bar"},n))}),
29
+ /* @__PURE__ */e("span",{className:"ip-count-num",children:n})]},n))}),
30
+ /* @__PURE__ */e("button",{type:"button",className:"ip-close","aria-label":d("关闭参数面板"),onClick:u,children:/* @__PURE__ */e(xe,{name:"x",size:14})})]})}function SlashMenu({editor:n,from:l,query:r,x:i,y:a,onClose:s,onPickFile:c,onPickPage:u,onPickVideo:d}){const m=o(),p=m.text,f=b(()=>function filterBlockCommands(e,t=e=>e){const n=e.trim().toLowerCase();return n?Ze.filter(e=>e.label.toLowerCase().includes(n)||t(e.label).toLowerCase().includes(n)||e.hint.toLowerCase().includes(n)||e.keywords.some(e=>e.includes(n)||e.startsWith(n))):Ze}(r,p),[r,m.version]),w=b(()=>function groupOf(e){return et.map(t=>({...t,items:e.filter(e=>e.group===t.id)})).filter(e=>e.items.length>0)}(f),[f]),[y,k]=v(0),[x,C]=v(null),[E,M]=v(0),[N,L]=v(0),[D,S]=v(274),[A,T]=v({top:0,bottom:0}),[I,P]=v(a),R=h(null),z=h(null),H=h(null),O=h(null),q=h(!1),flatIndex=e=>f.findIndex(t=>t.id===e.id);function openPanel(e){var t,n,o;if(!(null==e?void 0:e.panel))return void C(null);if((null==x?void 0:x.id)===e.id)return;C(e);const l=null==(t=z.current)?void 0:t.querySelectorAll("li")[flatIndex(e)],r=null==(n=H.current)?void 0:n.getBoundingClientRect();if(!l||!r)return;const i=l.getBoundingClientRect();if(i.bottom<=r.top||i.top>=r.bottom)return void C(null);if(!R.current)return;const a=i.top-R.current.getBoundingClientRect().top;M(a),L(a+i.height),S(((null==(o=O.current)?void 0:o.getBoundingClientRect().width)??268)+6)}function choose(e,t){if(!n.isEditable||n.isDestroyed)return;if(l<0||l>n.state.selection.from)return;const o={from:l,to:n.state.selection.from};["image","file","page","video"].includes(e.id)&&o.to>o.from&&n.chain().deleteRange(o).run(),e.run({chain:()=>{const e=n.chain().focus();return o.to>o.from?e.deleteRange(o):e},requestPickFile:c,requestPickPage:u,requestVideo:d,panelValue:t}),B(n.view),s()}function activate(e){e.panel?openPanel(e):choose(e)}function keydown(e){var t;if(!Ie(e)&&n.view.dom.contains(e.target)){if("Escape"===e.key)return e.preventDefault(),e.stopImmediatePropagation(),void(x?C(null):(B(n.view),s()));if(f.length)if((["ArrowDown","ArrowUp","Enter"].includes(e.key)||"ArrowRight"===e.key&&(null==(t=f[y])?void 0:t.panel))&&e.stopImmediatePropagation(),"ArrowDown"===e.key)e.preventDefault(),q.current=!0,C(null),k((y+1)%f.length);else if("ArrowUp"===e.key)e.preventDefault(),q.current=!0,C(null),k((y-1+f.length)%f.length);else if("Enter"===e.key){e.preventDefault();const t=f[y];t&&(null==x?void 0:x.id)===t.id?choose(t):t&&activate(t)}else if("ArrowRight"===e.key){const t=f[y];(null==t?void 0:t.panel)&&(e.preventDefault(),openPanel(t))}}}return g(()=>{k(0),C(null)},[r]),g(()=>{const measure=()=>{var e;const t=R.current,n=z.current;if(!t||!n)return;const o=(null==(e=t.parentElement)?void 0:e.getBoundingClientRect().top)??0,l=o+a,r=Math.round(Math.max(8,Math.min(l,window.innerHeight-8-n.offsetHeight)));P(r-o),T({top:8-r,bottom:window.innerHeight-8-r})},e=requestAnimationFrame(measure);return window.addEventListener("resize",measure),window.addEventListener("scroll",measure,!0),()=>{cancelAnimationFrame(e),window.removeEventListener("resize",measure),window.removeEventListener("scroll",measure,!0)}},[a,r,f.length]),g(()=>{var e;if(!q.current)return;q.current=!1;const t=null==(e=z.current)?void 0:e.querySelectorAll("li")[y],n=O.current;if(!t||!n)return;const o=t.offsetTop,l=o+t.offsetHeight;o<n.scrollTop?n.scrollTop=o:l>n.scrollTop+n.clientHeight&&(n.scrollTop=l-n.clientHeight)},[y,f]),g(()=>{const e=n.view.dom;return e.addEventListener("keydown",keydown,!0),()=>e.removeEventListener("keydown",keydown,!0)}),g(()=>{const outside=e=>{var t;e.target instanceof Node&&!(null==(t=R.current)?void 0:t.contains(e.target))&&!n.view.dom.contains(e.target)&&s()};return document.addEventListener("pointerdown",outside),()=>document.removeEventListener("pointerdown",outside)},[s,n]),/* @__PURE__ */t("div",{ref:R,className:"slash-anchor",style:{left:i,top:I},children:[
31
+ /* @__PURE__ */e("div",{ref:z,className:"slash-menu",role:"listbox","aria-label":p("插入内容"),children:/* @__PURE__ */e("div",{ref:H,children:/* @__PURE__ */t("div",{ref:O,className:"slash-content",children:[w.map(n=>/* @__PURE__ */t("div",{children:[
32
+ /* @__PURE__ */e("div",{className:"slash-group",children:p(n.label)}),
33
+ /* @__PURE__ */e("ul",{children:n.items.map(n=>{const o=flatIndex(n);/* @__PURE__ */
34
+ return t("li",{role:"option","aria-selected":o===y,className:o===y?"active":void 0,onMouseMove:()=>{k(o),openPanel(n)},onMouseDown:e=>{e.preventDefault(),activate(n)},children:[
35
+ /* @__PURE__ */e(xe,{name:n.icon,size:18}),
36
+ /* @__PURE__ */e("span",{className:"slash-label",children:p(n.label)}),
37
+ /* @__PURE__ */e("span",{className:"slash-hint",children:p(n.hint)}),n.panel&&/* @__PURE__ */e(xe,{name:"chevron",size:16,className:"slash-chevron"})]},n.id)})})]},n.id)),!f.length&&/* @__PURE__ */e("div",{className:"slash-empty",children:p("无匹配命令")})]})})}),(null==x?void 0:x.panel)&&/* @__PURE__ */e(InsertPicker,{mode:x.panel,x:D,y:E,yBottom:N,boundTop:A.top,boundBottom:A.bottom,onPick:e=>choose(x,e),onClose:()=>C(null)})]})}const tt=["paragraph","heading","blockquote","codeBlock","bulletList","orderedList","taskList","callout"];function menuBlockRange(e,t){const n=[];e.doc.forEach((e,t)=>n.push({from:t,to:t+e.nodeSize,node:e}));const o=n.find(e=>e.from===t);if(!o)return null;const{from:l,to:r,empty:i}=e.selection,a=!i&&l<o.to&&r>o.from?n.filter(e=>l<e.to&&r>e.from):[o];return{from:a[0].from,to:a.at(-1).to,nodes:a.map(e=>e.node)}}function containerContent(e){const t=1===e.nodes.length?e.nodes[0]:null;return t&&["callout","blockquote"].includes(t.type.name)?t.content:e.nodes}function canConvertMenuBlocks(e,t,n){const o=menuBlockRange(e,t);if(!o)return!1;if("callout"===n||"quote"===n){const t=e.schema.nodes["callout"===n?"callout":"blockquote"];return!!(null==t?void 0:t.validContent(t.create(null,containerContent(o)).content))}if("text"===n&&1===o.nodes.length&&["callout","blockquote"].includes(o.nodes[0].type.name))return!0;if(!["text","h1","h2","h3","h4","h5","h6","bullet","ordered","task","code"].includes(n))return!1;const l=["paragraph","heading","blockquote","codeBlock","bulletList","orderedList","taskList","callout","listItem","taskItem","text","hardBreak"];return o.nodes.every(e=>{if(!l.includes(e.type.name))return!1;let t=!0;return e.descendants(e=>(l.includes(e.type.name)||(t=!1),t)),t})}function deleteTopLevelBlock(e,t){let n=!1;e.doc.forEach((e,o)=>{o===t&&(n=!0)});const o=n?e.doc.nodeAt(t):null;return o?1===e.doc.childCount?e.tr.replaceWith(0,e.doc.content.size,e.schema.nodes.paragraph.create()):e.tr.delete(t,t+o.nodeSize):null}const nt={width:28,height:28},ot={width:48,height:28};function BlockPlus({style:t,expanded:n,onClick:l,onMouseEnter:r,onMouseLeave:i}){const a=o().text;/* @__PURE__ */
38
+ return e("button",{type:"button",className:"block-plus","aria-label":a("插入内容"),title:a("在此空行插入内容(表格 / 分栏 / 图片…)"),"aria-expanded":n,"aria-haspopup":"menu",style:t,onMouseEnter:r,onMouseLeave:i,onClick:l,children:/* @__PURE__ */e(xe,{name:"plus",size:17})})}function typeIcon(e,t){var n;if("heading"===t.type){const o=Number((null==(n=e.state.doc.nodeAt(t.pos))?void 0:n.attrs.level)??1);return`h${Math.min(Math.max(Number.isFinite(o)?o:1,1),6)}`}return{paragraph:"text",image:"image",imageGallery:"image",table:"table",blockquote:"quote",codeBlock:"code",bulletList:"bullet-list",orderedList:"ordered-list",taskList:"task",callout:"callout",columns:"columns",mindMap:"mind-map",fileAttachment:"file",videoEmbed:"file",mathBlock:"math",horizontalRule:"divider",pageLink:"page"}[t.type]??"text"}function BlockChip({editor:n,info:l,style:r,expanded:i,onOpen:a,onOpenFull:s,onMouseEnter:c,onMouseLeave:u,onHandleMouseDown:d}){const m=o().text;/* @__PURE__ */
39
+ return t("div",{className:"block-chip",role:"toolbar","aria-label":m("块快捷操作"),style:r,onMouseEnter:c,onMouseLeave:u,children:[
40
+ /* @__PURE__ */e("button",{type:"button",className:"chip-btn","aria-label":m("转换为"),title:m("转换为…"),"aria-expanded":i,"aria-haspopup":"menu",onClick:a,children:/* @__PURE__ */e(xe,{name:typeIcon(n,l),size:17})}),
41
+ /* @__PURE__ */e("button",{type:"button",className:"chip-btn chip-grip","aria-label":m("拖动或打开块菜单"),title:i?void 0:m("按住拖动可移动整段;单击打开块菜单"),"aria-expanded":i,"aria-haspopup":"menu",draggable:!1,onMouseDown:e=>d?d(e):e.preventDefault(),onClick:s,children:/* @__PURE__ */e(xe,{name:"handle",size:17})})]})}function BlockMenu(l){var r,i,a,s,c,m;const p=o().text,{editor:b,info:w}=l,[y,k]=v(l.panelOnly??null),[x,C]=v(!1),[E,M]=v(null),N=b.state.doc.nodeAt(w.pos),chain=()=>{const e=menuBlockRange(b.state,w.pos),t=b.state.selection,n=t instanceof u&&!t.empty&&N&&t.from>=w.pos+1&&t.to<=w.pos+N.nodeSize-1;return b.chain().focus().command(({tr:o})=>(o.setSelection(u.between(o.doc.resolve(n?t.from:((null==e?void 0:e.from)??w.pos)+1),o.doc.resolve(n?t.to:((null==e?void 0:e.to)??w.pos+N.nodeSize)-1))),!0))},S=Ze.filter(e=>e.convertible),A=Ze.filter(e=>"link"!==e.id),B=String((null==(r=null==N?void 0:N.attrs)?void 0:r.textAlign)??"left"),z=!!N&&["paragraph","heading","blockquote","codeBlock","bulletList","orderedList","taskList","callout"].includes(N.type.name),H=h(null),O=h(null),q=h(null),$=h(null),F=h(y);F.current=y;const V=h(E);V.current=E;const W=h(void 0);function cancelSubmenuClose(){clearTimeout(W.current),W.current=void 0}function openSubmenu(e,t){cancelSubmenuClose(),$.current=t,M(null),k(e)}function scheduleSubmenuClose(){l.panelOnly||(cancelSubmenuClose(),W.current=setTimeout(()=>{k(null),M(null)},180))}g(()=>()=>cancelSubmenuClose(),[]);const[Y,U]=v(),[j,_]=v({left:0,top:0});function insertCommand(e,t){if(!N||!b.isEditable||b.isDestroyed)return;const n="insert-below"===l.panelOnly&&N.isTextblock&&0===N.content.size,o=w.pos+N.nodeSize,r=n?{from:w.pos,to:o}:o;if(["image","file","page","video"].includes(e.id))return n?b.commands.setTextSelection(w.pos+1):b.chain().insertContentAt(o,{type:"paragraph"}).setTextSelection(o+1).run(),l.onClose(),void("page"===e.id?l.onPickPage():"video"===e.id?l.onPickVideo():l.onPickFile("image"===e.id?"image":"file"));let i;i=/^h[1-6]$/.test(e.id)?{type:"heading",attrs:{level:Number(e.id.slice(1))}}:"table"===e.id?ze(b.schema,(null==t?void 0:t.rows)??3,(null==t?void 0:t.cols)??3,!0).toJSON():"columns"===e.id?T((null==t?void 0:t.count)??2):"mindmap"===e.id?[He(),{type:"paragraph"}]:["bullet","ordered","task"].includes(e.id)?{type:"bullet"===e.id?"bulletList":"ordered"===e.id?"orderedList":"taskList",content:[{type:"task"===e.id?"taskItem":"listItem",attrs:"task"===e.id?{checked:!1}:void 0,content:[{type:"paragraph"}]}]}:{text:{type:"paragraph"},quote:{type:"blockquote",content:[{type:"paragraph"}]},code:{type:"codeBlock"},hr:{type:"horizontalRule"},callout:{type:"callout",attrs:{tone:"info"},content:[{type:"paragraph"}]},math:{type:"mathBlock",attrs:{latex:"E = mc^2"}}}[e.id],i&&b.chain().focus().insertContentAt(r,i).run(),l.onClose()}async function writeBlockClipboard(){var e;if(!N)return;if("undefined"==typeof ClipboardItem||!(null==(e=navigator.clipboard)?void 0:e.write)){if(!N.isTextblock)throw new Error(p("当前浏览器不支持复制此块的富文本结构"));return void await navigator.clipboard.writeText(N.textContent)}const t=document.createElement("div");t.append(Pe.fromSchema(b.schema).serializeFragment(Re.from(N))),await navigator.clipboard.write([new ClipboardItem({"text/html":new Blob([t.innerHTML],{type:"text/html"}),"text/plain":new Blob([N.textContent],{type:"text/plain"})})])}g(()=>{P(b.view,w.pos);const outside=e=>{var t;e.target instanceof Element&&!(null==(t=H.current)?void 0:t.contains(e.target))&&(e.target.closest(".block-chip,.block-plus")||l.onClose())},key=e=>{var t,n,o,r;if(e.isComposing)return;const i=document.activeElement;if("Escape"===e.key&&V.current)return e.preventDefault(),e.stopPropagation(),M(null),void(null==(t=$.current)||t.focus());if("ArrowRight"===e.key&&(null==i?void 0:i.dataset.submenu))return e.preventDefault(),openSubmenu(i.dataset.submenu,i),void requestAnimationFrame(()=>{var e,t;return null==(t=null==(e=q.current)?void 0:e.querySelector('[role="menuitem"]:not(:disabled)'))?void 0:t.focus()});if(!l.panelOnly&&F.current&&("Escape"===e.key||"ArrowLeft"===e.key&&(null==(n=q.current)?void 0:n.contains(i))))return e.preventDefault(),e.stopPropagation(),k(null),M(null),void(null==(o=$.current)||o.focus());if("Escape"===e.key)return e.preventDefault(),e.stopPropagation(),void l.onClose();if(!["ArrowDown","ArrowUp","Home","End"].includes(e.key))return;const a=(null==i?void 0:i.closest('[role="menu"]'))??O.current??q.current,s=Array.from((null==a?void 0:a.querySelectorAll('[role="menuitem"]:not(:disabled)'))??[]);if(!s.length)return;e.preventDefault();const c=s.indexOf(document.activeElement);null==(r=s["Home"===e.key?0:"End"===e.key?s.length-1:(c+("ArrowUp"===e.key?-1:1)+s.length)%s.length])||r.focus()},changed=({transaction:e})=>{e.docChanged&&l.onClose()};return document.addEventListener("pointerdown",outside,!0),document.addEventListener("keydown",key,!0),b.on("transaction",changed),()=>{document.removeEventListener("pointerdown",outside,!0),document.removeEventListener("keydown",key,!0),b.off("transaction",changed),b.isDestroyed||P(b.view,null)}},[b,w.pos,l.panelOnly]),f(()=>{const e=H.current,t=l.panelOnly?q.current:O.current,n=b.view.dom.closest(".editor-shell");if(!e||!t||!n)return;const measure=()=>{var o;const r=n.getBoundingClientRect(),i=t.offsetWidth,a=t.offsetHeight,s=Math.min(Math.max(r.left+w.contentLeft,8),Math.max(8,innerWidth-8-i))-r.left,c=Math.min(Math.max(r.top+w.top,8),Math.max(8,innerHeight-8-a))-r.top;if(_({left:s,top:c}),!l.panelOnly&&q.current){const n=t.getBoundingClientRect(),l=e.getBoundingClientRect(),r=q.current.getBoundingClientRect(),i=function placeBlockSubmenu(e,t,n,o){const l=Math.max(0,o.height-16),r=e.right+6,i=e.left-6-n.width,a=r+n.width<=o.width-8||i<8?r:i;return{left:Math.max(8,Math.min(a,o.width-8-n.width)),top:fitRange(t,Math.min(n.height,l),8,o.height-8),maxHeight:l}}(n,(null==(o=$.current)?void 0:o.getBoundingClientRect().top)??n.top,r,{width:innerWidth,height:innerHeight});U({left:i.left-l.left,top:i.top-l.top,maxHeight:i.maxHeight})}else U(void 0)};measure();const o=new ResizeObserver(measure);return o.observe(t),q.current&&q.current!==t&&o.observe(q.current),window.addEventListener("resize",measure),window.addEventListener("scroll",measure,!0),()=>{o.disconnect(),window.removeEventListener("resize",measure),window.removeEventListener("scroll",measure,!0)}},[b,w.contentLeft,w.top,l.panelOnly,y,j.left,j.top]),g(()=>{k(l.panelOnly??null)},[l.panelOnly,w.pos]);const item=(n,o,l,r={})=>/* @__PURE__ */t("button",{type:"button",role:"menuitem",className:`bm-item${r.active?" active":""}${r.disabled?" disabled":""}${r.danger?" danger":""}`,"aria-haspopup":r.submenu?"menu":void 0,"aria-expanded":r.submenu?y===r.submenu:void 0,"data-submenu":r.submenu,disabled:r.disabled,"aria-disabled":r.disabled||void 0,onMouseEnter:e=>{cancelSubmenuClose(),r.submenu?openSubmenu(r.submenu,e.currentTarget):e.currentTarget.parentElement===O.current?(k(null),M(null)):"insert-below"===y&&($.current=e.currentTarget)},onClick:e=>r.submenu?openSubmenu(r.submenu,e.currentTarget):l(),children:[
42
+ /* @__PURE__ */e(xe,{name:n,size:18}),
43
+ /* @__PURE__ */e("span",{children:o}),r.submenu&&/* @__PURE__ */e(xe,{name:"chevron",size:16,className:"bm-chevron"})]},o);/* @__PURE__ */
44
+ return t("div",{ref:H,className:"block-anchor",role:"menu","aria-label":p("块操作菜单"),style:j,onMouseDown:e=>e.preventDefault(),onMouseEnter:()=>{var e;cancelSubmenuClose(),null==(e=l.onRegion)||e.call(l,!0)},onMouseLeave:()=>{var e,t;scheduleSubmenuClose(),null==(t=l.onRegion)||t.call(l,!0===(null==(e=H.current)?void 0:e.contains(document.activeElement)))},onFocusCapture:()=>{var e;cancelSubmenuClose(),null==(e=l.onRegion)||e.call(l,!0)},onBlurCapture:()=>queueMicrotask(()=>{var e,t,n;return null==(n=l.onRegion)?void 0:n.call(l,!0===(null==(e=H.current)?void 0:e.contains(document.activeElement))||!0===(null==(t=H.current)?void 0:t.matches(":hover")))}),children:[!l.panelOnly&&/* @__PURE__ */t("div",{ref:O,className:"block-menu",role:"menu",onMouseLeave:scheduleSubmenuClose,children:[item("h1",p("转换为"),()=>k("format"===y?null:"format"),{submenu:"format"}),item("indent",p("缩进和对齐"),()=>k("indent-align"===y?null:"indent-align"),{submenu:"indent-align"}),item("palette",p("颜色"),()=>k("color"===y?null:"color"),{submenu:"color"}),item("plus",p("在下方添加"),()=>k("insert-below"===y?null:"insert-below"),{submenu:"insert-below"}),l.canComment&&/* @__PURE__ */e("div",{className:"bm-sep"}),l.canComment&&item("comment",p("评论"),()=>{var e;null==(e=l.onRequestComment)||e.call(l),l.onClose()}),item("cut",p("剪切"),async function cutBlock(){var e,t;if(!N)return;const n=b.state.doc,o=N;try{await writeBlockClipboard()}catch{return void(null==(e=l.onNotice)||e.call(l,p("无法访问剪贴板,已取消剪切(内容未删除)。")))}if(b.isDestroyed||!b.isEditable||b.state.doc!==n||b.state.doc.nodeAt(w.pos)!==o)return void(null==(t=l.onNotice)||t.call(l,p("复制期间正文发生了变化,已取消删除,请重新剪切。")));const r=deleteTopLevelBlock(b.state,w.pos);r&&b.view.dispatch(r),l.onClose()}),item(x?"check":"copy",p(x?"已复制":"复制"),()=>{!async function copyBlock(){var e;try{await writeBlockClipboard(),C(!0),setTimeout(()=>C(!1),1600)}catch{null==(e=l.onNotice)||e.call(l,p("无法完整复制该块,请选中后使用 Cmd/Ctrl+C。"))}}()}),item("copy-link",p("复制本页链接"),()=>{var e;null==(e=l.onCopyLink)||e.call(l),l.onClose()}),
45
+ /* @__PURE__ */e("div",{className:"bm-sep"}),item("share",p("分享此页面"),()=>{var e;null==(e=l.onSharePage)||e.call(l),l.onClose()}),item("page",p("转换为子文档"),()=>{var e;N&&(null==(e=l.onConvertToSubpage)||e.call(l,N.toJSON())),l.onClose()}),item("file",p("保存为模板"),()=>{var e;N&&(null==(e=l.onSaveAsTemplate)||e.call(l,N.toJSON())),l.onClose()}),
46
+ /* @__PURE__ */e("div",{className:"bm-sep"}),item("trash",p("删除"),()=>function action(){if(N){{const e=deleteTopLevelBlock(b.state,w.pos);e&&b.view.dispatch(e)}l.onClose()}}(),{danger:!0})]}),y&&/* @__PURE__ */t("div",{ref:q,className:"block-submenu"+("color"===y?" block-submenu-color":""),role:"color"===y?"dialog":"menu",style:l.panelOnly?{position:"relative"}:{position:"absolute",...Y,visibility:Y?"visible":"hidden"},"aria-label":"color"===y?p("块颜色"):void 0,onMouseEnter:cancelSubmenuClose,onMouseLeave:scheduleSubmenuClose,children:["format"===y&&S.map(n=>{const o=canConvertMenuBlocks(b.state,w.pos,n.id),r=function editableActive(e,t,n){const o=menuBlockRange(e.state,t);if(!o)return!1;const l=o.nodes[0];return!!l&&("text"===n?"paragraph"===l.type.name:"h1"===n?"heading"===l.type.name&&1===l.attrs.level:"h2"===n?"heading"===l.type.name&&2===l.attrs.level:"h3"===n?"heading"===l.type.name&&3===l.attrs.level:"h4"===n?"heading"===l.type.name&&4===l.attrs.level:"h5"===n?"heading"===l.type.name&&5===l.attrs.level:"h6"===n?"heading"===l.type.name&&6===l.attrs.level:l.type.name==={ordered:"orderedList",bullet:"bulletList",quote:"blockquote",code:"codeBlock",task:"taskList",callout:"callout"}[n])}(b,w.pos,n.id);/* @__PURE__ */
47
+ return t("button",{type:"button",role:"menuitem",className:`bm-item${r?" active":""}${o?"":" disabled"}`,disabled:!o,"aria-disabled":!o,onClick:()=>{(function convertMenuBlocks(e,t,n){var o,l;if(e.isDestroyed||!e.isEditable||!canConvertMenuBlocks(e.state,t,n))return!1;const r=menuBlockRange(e.state,t);if("callout"===n||"quote"===n)return function dispatchContainer(e,t,n){var o;if(1===t.nodes.length&&t.nodes[0].type.name===n)return!0;const l=e.schema.nodes[n].create("callout"===n?{tone:"info"}:null,containerContent(t)),r=e.state.tr.replaceWith(t.from,t.to,l);r.setSelection(d.near(r.doc.resolve(t.from+1)));const i=null==(o=qe.getState(e.state))?void 0:o.undoManager;return null==i||i.stopCapturing(),e.view.dispatch(Oe(r)),null==i||i.stopCapturing(),e.view.focus(),!0}(e,r,"callout"===n?"callout":"blockquote");if("text"===n&&1===r.nodes.length&&"callout"===r.nodes[0].type.name){const t=I(e,r.from);return t&&e.view.focus(),t}if("text"===n&&1===r.nodes.length&&"blockquote"===r.nodes[0].type.name){const t=e.state.tr.replaceWith(r.from,r.to,r.nodes[0].content),n=null==(o=qe.getState(e.state))?void 0:o.undoManager;return null==n||n.stopCapturing(),e.view.dispatch(Oe(t)),null==n||n.stopCapturing(),e.view.focus(),!0}const i={text:"paragraph",bullet:"bulletList",ordered:"orderedList",task:"taskList",code:"codeBlock"}[n];if(1===r.nodes.length&&r.nodes[0].type.name===i)return!0;const a=null==(l=qe.getState(e.state))?void 0:l.undoManager;null==a||a.stopCapturing();const s=e.chain().focus().command(({tr:e})=>{let t=r.to;return 1===r.nodes.length&&["callout","blockquote"].includes(r.nodes[0].type.name)&&(e.replaceWith(r.from,r.to,r.nodes[0].content),t-=2),e.setSelection(u.between(e.doc.resolve(r.from+1),e.doc.resolve(t-1))),Oe(e),!0}).clearNodes();"text"===n?s.setParagraph():/^h[1-6]$/.test(n)?s.setHeading({level:Number(n.slice(1))}):"bullet"===n?s.toggleBulletList():"ordered"===n?s.toggleOrderedList():"task"===n?s.toggleTaskList():"code"===n&&s.toggleCodeBlock();const c=s.command(({tr:e})=>(e.setSelection(d.near(e.doc.resolve(e.selection.from))),!0)).run();return null==a||a.stopCapturing(),c})(b,w.pos,n.id),l.onClose()},children:[
48
+ /* @__PURE__ */e(xe,{name:n.icon,size:18}),
49
+ /* @__PURE__ */e("span",{children:p(n.label)}),r&&/* @__PURE__ */e(xe,{name:"check",size:16,className:"bm-chevron"})]},n.id)}),"indent-align"===y&&/* @__PURE__ */t(n,{children:[item("indent",p("增加缩进"),()=>{chain().indentBlock().run(),l.onClose()},{disabled:!z}),item("outdent",p("减少缩进"),()=>{chain().outdentBlock().run(),l.onClose()},{disabled:!z}),
50
+ /* @__PURE__ */e("div",{className:"bm-sep"}),R.map(e=>item(`align-${e}`,p("left"===e?"左对齐":"center"===e?"居中对齐":"right"===e?"右对齐":"两端对齐"),()=>{chain().setTextAlign(e).run(),l.onClose()},{active:B===e,disabled:!z}))]}),"color"===y&&/* @__PURE__ */t("div",{className:"block-color-panel",role:"group","aria-label":p("文档颜色"),children:[
51
+ /* @__PURE__ */t("div",{className:"color-section",children:[
52
+ /* @__PURE__ */e("div",{className:"color-label",children:p("字体颜色")}),
53
+ /* @__PURE__ */e("div",{className:"color-grid",role:"group","aria-label":p("字体颜色"),children:L.map(t=>/* @__PURE__ */e("button",{type:"button",className:"color-choice"+(textColorActive(b,t.value)?" selected":""),"aria-label":p("字体颜色:{0}",{0:t.name}),title:p("字体颜色:{0}",{0:t.name}),"aria-pressed":textColorActive(b,t.value),onClick:()=>{const e=t.value?{color:t.value}:null;e?chain().setMark("textStyle",e).run():chain().unsetMark("textStyle").run()},children:/* @__PURE__ */e("span",{className:"font-sample",style:t.value?{color:t.value}:void 0,children:"A"})},t.name))})]}),
54
+ /* @__PURE__ */t("div",{className:"color-section",children:[
55
+ /* @__PURE__ */e("div",{className:"color-label",children:p("背景颜色")}),
56
+ /* @__PURE__ */e("div",{className:"color-grid",role:"group","aria-label":p("背景颜色"),children:D.map(t=>/* @__PURE__ */e("button",{type:"button",className:`color-choice background-choice${highlightActive(b,t.value)?" selected":""}${null===t.value?" no-background":""}`,"aria-label":p("背景颜色:{0}",{0:t.name}),title:p("背景颜色:{0}",{0:t.name}),"aria-pressed":highlightActive(b,t.value),style:t.value?{background:t.value}:void 0,onClick:()=>{const e=t.value?{color:t.value}:null;e?chain().setMark("highlight",e).run():chain().unsetMark("highlight").run()},children:null===t.value&&/* @__PURE__ */e("span",{className:"color-transparency-grid","aria-hidden":"true"})},t.name))})]}),
57
+ /* @__PURE__ */e(ke,{variant:"outline",block:!0,onClick:()=>{chain().unsetMark("textStyle").unsetMark("highlight").run(),l.onClose()},children:p("恢复默认")})]}),"insert-below"===y&&A.map(e=>item(e.icon,e.label,()=>function runInsert(e){e.panel?M({mode:e.panel,x:0,y:0}):insertCommand(e)}(e)))]}),E&&/* @__PURE__ */e(InsertPicker,{mode:E.mode,x:((null==Y?void 0:Y.left)??0)+((null==(i=q.current)?void 0:i.offsetWidth)??240)+6,y:Math.max(0,((null==(a=$.current)?void 0:a.getBoundingClientRect().top)??0)-((null==(s=H.current)?void 0:s.getBoundingClientRect().top)??0)),boundTop:8-((null==(c=H.current)?void 0:c.getBoundingClientRect().top)??0),boundBottom:innerHeight-8-((null==(m=H.current)?void 0:m.getBoundingClientRect().top)??0),onPick:function insertWithPanel(e){const t=A.find(e=>e.panel===(null==E?void 0:E.mode));M(null),t&&insertCommand(t,e)},onClose:()=>M(null)})]})}function textColorActive(e,t){const n=e.getAttributes("textStyle").color;return null===t?"string"!=typeof n:n===t}function highlightActive(e,t){const n=e.getAttributes("highlight").color;return null===t?"string"!=typeof n:n===t}async function writeRichTable(e,t){var n;if("undefined"==typeof ClipboardItem||!(null==(n=navigator.clipboard)?void 0:n.write))throw new Error("rich clipboard unavailable");const o=document.createElement("div");o.appendChild(Pe.fromSchema(t).serializeFragment(Re.from(e))),await navigator.clipboard.write([new ClipboardItem({"text/html":new Blob([o.innerHTML],{type:"text/html"}),"text/plain":new Blob([e.textBetween(0,e.content.size,"\n")],{type:"text/plain"})})])}async function copyTableToClipboard(e,t,n,o=writeRichTable){if(e.isDestroyed||!e.isEditable)return null;const l=e.state.doc,r=l.nodeAt(t);return"table"!==(null==r?void 0:r.type.spec.tableRole)?null:(await o(r,e.state.schema),n?e.isDestroyed||!e.isEditable||e.state.doc!==l||e.state.doc.nodeAt(t)!==r?"changed":(e.view.dispatch(e.state.tr.deleteRange(t,t+r.nodeSize)),e.view.focus(),"cut"):"copied")}const anchorOf=e=>{const t=e.currentTarget.getBoundingClientRect();return{left:t.left,top:t.top,right:t.right,bottom:t.bottom}};function TableControls({editor:r,hover:i,revision:a,onNotice:s,onBlockDrag:c}){var u,d,m;const b=o().text,{portal:w}=l(),y=h(null),[k,x]=v(!1),[C,E]=v("none"),[M,N]=v(!1),[S,A]=v("table"),[T,B]=v(!1),[I,P]=v(!1),[R,Z]=v(!1),[ee,te]=v(""),[ne,oe]=v(!1),[le,re]=v({width:0,height:0}),[,ie]=v(0),[ae,se]=v(null),[ce,ue]=v(null),de=h(null),me=h(null),pe=h(null),he=h(null),ve=h(null),fe=h(null),[ge,be]=v({left:0,top:0,maxHeight:480}),[we,ye]=v({left:0,top:0});f(()=>{var e;if(!M||!ae)return;const t=de.current;t&&be(function placeTableMenu(e,t,n){const o=n.topInset,l=Math.max(o,Math.min(e.top,n.height-12)),r=Math.max(l,Math.min(e.bottom,n.height-12)),i=Math.max(0,n.height-12-r-6),a=Math.max(0,l-6-o),s=t.height<=i||t.height>a&&i>=a,c=s?i:a,u=Math.min(t.height,c);return{left:Math.max(12,Math.min(e.left,n.width-t.width-12)),top:s?r+6:l-6-u,maxHeight:c}}(ae,{width:t.offsetWidth,height:((null==(e=pe.current)?void 0:e.scrollHeight)??t.scrollHeight)+2},{width:innerWidth,height:innerHeight,topInset:12}))},[M,ae,R,I,S]),g(()=>{var e;if(!(M&&de.current&&pe.current&&he.current))return;const t=function attachOverlayScrollbar(e,t,n){const o=document.createElement("div"),l=document.createElement("div");o.className="w-overlay-scrollbar__track",l.className="w-overlay-scrollbar__thumb";const r=t.id;let i,a;t.id||(t.id=`wd-scroll-${crypto.randomUUID()}`),l.tabIndex=0,l.setAttribute("role","scrollbar"),l.setAttribute("aria-label","菜单滚动条"),l.setAttribute("aria-orientation","vertical"),l.setAttribute("aria-controls",t.id),l.setAttribute("aria-valuemin","0"),o.append(l),e.append(o);let s=0;const c=new AbortController;function active(){e.classList.add("is-scrolling"),clearTimeout(i),i=setTimeout(()=>{a||e.classList.remove("is-scrolling")},700)}function update(){const e=Math.max(0,t.scrollHeight-t.clientHeight);o.hidden=e<=1;const n=o.clientHeight;s=Math.min(n,Math.max(28,n*t.clientHeight/Math.max(1,t.scrollHeight))),l.style.height=`${s}px`,l.style.transform=`translateY(${e?(n-s)*t.scrollTop/e:0}px)`,l.setAttribute("aria-valuemax",String(Math.round(e))),l.setAttribute("aria-valuenow",String(Math.round(t.scrollTop)))}function finish(){a&&(a=void 0,e.classList.remove("is-dragging-scrollbar"),active())}t.addEventListener("scroll",()=>{update(),active()},{passive:!0,signal:c.signal}),l.addEventListener("pointerdown",n=>{0===n.button&&(n.preventDefault(),n.stopPropagation(),l.focus({preventScroll:!0}),a={y:n.clientY,top:t.scrollTop,maximum:t.scrollHeight-t.clientHeight,travel:Math.max(1,o.clientHeight-s)},e.classList.add("is-dragging-scrollbar"),active())},{signal:c.signal}),window.addEventListener("pointermove",e=>{a&&(e.preventDefault(),t.scrollTop=Math.max(0,Math.min(a.maximum,a.top+(e.clientY-a.y)*a.maximum/a.travel)),update())},{signal:c.signal}),window.addEventListener("pointerup",finish,{signal:c.signal}),window.addEventListener("pointercancel",finish,{signal:c.signal}),window.addEventListener("blur",finish,{signal:c.signal}),o.addEventListener("pointerdown",e=>{if(e.target!==o||0!==e.button)return;e.preventDefault(),e.stopPropagation();const n=o.getBoundingClientRect().top;t.scrollTop=(e.clientY-n-s/2)/Math.max(1,o.clientHeight-s)*(t.scrollHeight-t.clientHeight),update(),active()},{signal:c.signal}),l.addEventListener("keydown",e=>{const n={ArrowDown:32,ArrowUp:-32,PageDown:.8*t.clientHeight,PageUp:.8*-t.clientHeight}[e.key];void 0===n&&"Home"!==e.key&&"End"!==e.key||(e.preventDefault(),e.stopPropagation(),t.scrollTop="Home"===e.key?0:"End"===e.key?t.scrollHeight:t.scrollTop+n,update(),active())},{signal:c.signal});const u=new ResizeObserver(update);return u.observe(t),u.observe(n),u.observe(e),update(),{update:update,destroy(){c.abort(),u.disconnect(),clearTimeout(i),o.remove(),e.classList.remove("is-scrolling","is-dragging-scrollbar"),r||t.removeAttribute("id")}}}(de.current,pe.current,he.current);return null==(e=de.current.querySelector('[role="menuitem"]'))||e.focus({preventScroll:!0}),()=>t.destroy()},[M,w]),f(()=>{if("none"===C||!ce)return;const measure=()=>{var e;const t=me.current,n=null==(e=fe.current)?void 0:e.getBoundingClientRect();if(!t||!n)return;const o=Ce({trigger:n,panel:{width:t.offsetWidth,height:t.offsetHeight},position:"bl",gap:6,viewport:{width:innerWidth,height:innerHeight}});ye(e=>e.left===o.left&&e.top===o.top?e:{left:o.left,top:o.top})};measure();const e=new ResizeObserver(measure);me.current&&e.observe(me.current);let t=0;const scroll=()=>{cancelAnimationFrame(t),t=requestAnimationFrame(measure)};return window.addEventListener("scroll",scroll,!0),()=>{e.disconnect(),cancelAnimationFrame(t),window.removeEventListener("scroll",scroll,!0)}}),g(()=>{const update=e=>{var t,n;e.target instanceof Node&&((null==(t=de.current)?void 0:t.contains(e.target))||(null==(n=me.current)?void 0:n.contains(e.target)))||ie(e=>e+1)};return window.addEventListener("scroll",update,!0),window.addEventListener("resize",update),()=>{window.removeEventListener("scroll",update,!0),window.removeEventListener("resize",update)}},[]);const ke=h(r.state.selection),Ee=h(r.state.doc);g(()=>{ke.current.eq(r.state.selection)&&Ee.current===r.state.doc||(E("none"),x(!1),Z(!1),ke.current=r.state.selection,Ee.current=r.state.doc)},[a,r]);const Me=z(r.view),Ne=(null==i?void 0:i.cellPos)??Me,Le=null==Ne?null:H(r.view,Ne),De=Le&&i?{...Le,rowIndex:i.rowIndex,colIndex:i.colIndex}:Le,Se=r.state.selection instanceof Fe?r.state.selection:null,Ae=!!i||!!Se||M,Te=r.can().mergeCells(),Be=r.can().splitCell(),Ie=O(r,"row"),Pe=O(r,"column"),Re=r.getAttributes("tableCell").align??r.getAttributes("tableHeader").align,ze="string"==typeof r.getAttributes("textStyle").color?r.getAttributes("textStyle").color:null,He="string"==typeof r.getAttributes("highlight").color?r.getAttributes("highlight").color:null,Oe="string"==typeof(r.getAttributes("tableCell").backgroundColor??r.getAttributes("tableHeader").backgroundColor)?r.getAttributes("tableCell").backgroundColor??r.getAttributes("tableHeader").backgroundColor:null,chain=()=>r.chain().focus(),qe=r.view.dom.closest(".editor-shell");function axisDown(e,t,n){var o;if(!De||0!==n.button)return;n.preventDefault(),null==(o=y.current)||o.call(y);const l=r.view,i=l.state.doc,a=l.state.selection,s=i.resolve(De.cellPos),c=s.node(-1),u=s.start(-1),d=Ve.get(c),m="row"===e?De.rows:De.columns,p=null==qe?void 0:qe.getBoundingClientRect();if(!p)return;const select=n=>{const o=Math.min(t,n),r=Math.max(t,n),a="row"===e?o*d.width:o,s="row"===e?(r+1)*d.width-1:(d.height-1)*d.width+r;l.dispatch(l.state.tr.setSelection(Fe.create(i,u+d.map[a],u+d.map[s]))),l.focus()};select(t);const move=t=>{var n;if(!r.isEditable||l.state.doc!==i)return void(null==(n=y.current)||n.call(y));const o="row"===e?t.clientY-p.top:t.clientX-p.left,a=m.reduce((e,t)=>Math.abs(t.start+t.size/2-o)<Math.abs(e.start+e.size/2-o)?t:e);select(a.index),t.preventDefault()},key=e=>{var t;"Escape"===e.key&&(l.state.doc===i&&l.dispatch(l.state.tr.setSelection(a)),e.preventDefault(),null==(t=y.current)||t.call(y))},done=()=>{var e;return null==(e=y.current)?void 0:e.call(y)};y.current=()=>{window.removeEventListener("mousemove",move,!0),window.removeEventListener("mouseup",done,!0),window.removeEventListener("keydown",key,!0),window.removeEventListener("blur",done),y.current=null},window.addEventListener("mousemove",move,!0),window.addEventListener("mouseup",done,!0),window.addEventListener("keydown",key,!0),window.addEventListener("blur",done)}g(()=>{const context=e=>{if(!(r.isEditable&&e.target instanceof Element))return;const t=e.target.closest("td,th");if(!t)return;const n=W(r.view,t);null!==n&&(e.preventDefault(),Y(r.view,n),E("none"),A("cell"),B(!1),ve.current=null,P(!1),se({left:e.clientX,right:e.clientX,top:e.clientY,bottom:e.clientY}),N(!0))};return r.view.dom.addEventListener("contextmenu",context,!0),()=>{var e;r.isDestroyed||r.view.dom.removeEventListener("contextmenu",context,!0),null==(e=y.current)||e.call(y)}},[r]),g(()=>{if(!M&&"none"===C)return;const close=e=>{var t,n;e.target instanceof Element&&((null==(t=de.current)?void 0:t.contains(e.target))||(null==(n=me.current)?void 0:n.contains(e.target))||e.target.closest(".tbl-ui-host"))||(N(!1),E("none"))},resize=()=>{N(!1),E("none")},scroll=e=>{var t,n;e.target instanceof Node&&((null==(t=de.current)?void 0:t.contains(e.target))||(null==(n=me.current)?void 0:n.contains(e.target)))||N(!1)};return document.addEventListener("pointerdown",close,!0),window.addEventListener("resize",resize),window.addEventListener("scroll",scroll,!0),()=>{document.removeEventListener("pointerdown",close,!0),window.removeEventListener("resize",resize),window.removeEventListener("scroll",scroll,!0)}},[M,C]),g(()=>{const escape=e=>{var t,n;"Escape"!==e.key||e.isComposing||("none"!==C?(e.preventDefault(),E("none"),null==(t=fe.current)||t.focus({preventScroll:!0})):M?(e.preventDefault(),N(!1),null==(n=ve.current)||n.focus({preventScroll:!0})):Se&&(e.preventDefault(),x(!0),r.view.focus()))};return document.addEventListener("keydown",escape),()=>document.removeEventListener("keydown",escape)});const $e=function selectedBounds(){var e;if(!qe||!Se)return null;const t=[];Se.forEachCell((e,n)=>{const o=r.view.nodeDOM(n);o instanceof HTMLElement&&t.push(o.getBoundingClientRect())});const n=De?null==(e=r.view.nodeDOM(De.cellPos))?void 0:e.closest("table"):null;if(!n)return null;const o=U(t,(n.closest(".tableWrapper")??n).getBoundingClientRect());if(!o)return null;const l=qe.getBoundingClientRect(),i=function placeTableToolbar(e,t,n){const o=Math.min(t.width,Math.max(0,n.width-24)),l=e.top-14-t.height;return{left:Math.max(12,Math.min((e.left+e.right-o)/2,n.width-o-12)),top:l>=n.topInset?l:Math.max(n.topInset,Math.min(e.top+40,n.height-t.height-12))}}(o,le,{width:innerWidth,height:innerHeight,topInset:66});return{left:o.left-l.left,top:o.top-l.top,width:o.right-o.left,height:o.bottom-o.top,toolbarLeft:i.left,toolbarTop:i.top}}();function mark(e){return j(r,e)}function toggleMark(e){_(r,e,mark(e)?null:{})}function showContext(e,t="table"){var n;if(!e)return;const o=e.currentTarget;if(M&&ve.current===o)return void N(!1);ve.current=o;const l=null==(n=o.closest(".tbl-cellbar"))?void 0:n.getBoundingClientRect();"table"===t&&De&&$(r.view,De.cellPos,"table");const i=anchorOf(e);E("none"),P(!1),A(t),B(!!l),se({...i,top:(null==l?void 0:l.top)??i.top,bottom:(null==l?void 0:l.bottom)??i.bottom}),N(!0)}async function copyTable(e){const t=tableTarget();if(t&&!ne&&r.isEditable){oe(!0);try{const n=await copyTableToClipboard(r,t.pos,e);"changed"===n?null==s||s("表格已复制;内容发生变化,未执行剪切。"):n&&(null==s||s("cut"===n?"表格已剪切。":"表格已复制。")),N(!1)}catch{null==s||s(b("无法写入剪贴板,请允许浏览器访问剪贴板后重试。"))}finally{oe(!1)}}}function showPanel(e,t){C!==e?(fe.current=t.currentTarget,N(!1),ue(anchorOf(t)),E(e)):E("none")}function tableTarget(){const e=null==Ne?null:q(r.view,Ne),t=null===e?null:r.state.doc.nodeAt(e);return null!==e&&t?{pos:e,node:t}:null}function applyWidth(){var e;const t=null==(e=X(r))?void 0:e.col,n=Math.round(Number(ee));null==t||!Number.isFinite(n)||n<40||n>2e3||(G(r,t,n),Z(!1),N(!1))}const Ye=[{key:"color",label:b("表格文字颜色"),tip:b("文字颜色"),hint:b("只改文字本身的颜色,不影响格子底色"),colors:L,current:ze},{key:"highlight",label:b("表格文字高亮"),tip:b("文字高亮"),hint:b("只给文字加荧光底色,格子仍是原本的底色"),colors:D,current:He},{key:"background",label:b("单元格底色"),tip:b("单元格底色"),hint:b("给整个单元格铺底色,与文字高亮互不影响"),colors:We,current:Oe}],menuItem=(n,o,l,r={})=>/* @__PURE__ */t("button",{type:"button",role:"menuitem",className:"ctx-item"+(r.danger?" danger":""),disabled:r.disabled,"aria-checked":r.checked,"aria-expanded":r.expanded,onMouseDown:e=>e.preventDefault(),onClick:l,children:[
58
+ /* @__PURE__ */e(xe,{name:n,size:16}),
59
+ /* @__PURE__ */e("span",{children:o}),r.checked&&/* @__PURE__ */e(xe,{name:"check",size:16,className:"ctx-chevron"}),void 0!==r.expanded&&/* @__PURE__ */e(xe,{name:"chevron-down",size:14,className:"ctx-chevron"})]}),tool=(n,o,l,r=!1,i=!1,a)=>/* @__PURE__ */t("button",{type:"button",className:"tool"+(r?" on":""),"aria-label":n,title:n,"aria-pressed":r,"aria-disabled":i||void 0,onClick:()=>{i||l()},children:[
60
+ /* @__PURE__ */e(xe,{name:o,size:["bold","italic","strike","underline"].includes(o)?20:18}),a]});/* @__PURE__ */
61
+ return t(n,{children:[
62
+ /* @__PURE__ */t("div",{className:"tbl-ui-host",children:[De&&Ae&&/* @__PURE__ */t(n,{children:[
63
+ /* @__PURE__ */t("div",{className:"tbl-corner",style:{left:function cornerLeft(){return De&&qe?Math.max(De.contentLeft-8-44,8-qe.getBoundingClientRect().left):0}(),top:De.tableTop+((null==(u=De.rows[0])?void 0:u.size)??26)/2-13},children:[
64
+ /* @__PURE__ */e("button",{type:"button","aria-label":b("表格操作"),"aria-haspopup":"menu",title:b("表格操作"),onMouseDown:e=>e.preventDefault(),onClick:e=>showContext(e,"table"),children:/* @__PURE__ */e(xe,{name:"table",size:18})}),
65
+ /* @__PURE__ */e("button",{type:"button",className:"tbl-grip","aria-label":b("拖动或打开表格菜单"),"aria-haspopup":"menu",title:b("拖动表格"),draggable:!1,onMouseDown:e=>{const t=q(r.view,De.cellPos),n=anchorOf(e);null!==t&&(null==c||c(t,e.nativeEvent,()=>{$(r.view,De.cellPos,"table"),A("table"),B(!1),P(!1),se(n),N(!0)}))},onClick:e=>{0===e.detail&&showContext(e,"table")},children:/* @__PURE__ */e(xe,{name:"handle",size:16})})]}),De.rows.map(t=>/* @__PURE__ */e("button",{type:"button",className:"tbl-axis tbl-row-axis"+(t.index===De.rowIndex?" current":""),"aria-label":b("选择第 {0} 行",{0:t.index+1}),title:b("选择第 {0} 行",{0:t.index+1}),style:{left:De.tableLeft-10,top:t.start+t.size/2-12},onMouseDown:e=>axisDown("row",t.index,e),onClick:e=>{0===e.detail&&$(r.view,De.cellPos,"row",t.index)}},`row-${t.index}`)),De.columns.map(t=>/* @__PURE__ */e("button",{type:"button",className:"tbl-axis tbl-col-axis"+(t.index===De.colIndex?" current":""),"aria-label":b("选择第 {0} 列",{0:t.index+1}),title:b("选择第 {0} 列",{0:t.index+1}),style:{left:t.start+t.size/2-12,top:De.tableTop-10},onMouseDown:e=>axisDown("column",t.index,e),onClick:e=>{0===e.detail&&$(r.view,De.cellPos,"column",t.index)}},`col-${t.index}`)),[...De.rows.map(e=>({index:e.index,start:e.start})),{index:De.rows.length,start:De.tableTop+De.tableHeight}].map(t=>/* @__PURE__ */e("button",{type:"button",className:"tbl-boundary tbl-boundary-row","aria-label":b("在第 {0} 行位置添加一行",{0:t.index+1}),title:b("添加一行"),style:{left:De.tableLeft-14,top:t.start-10,"--insert-length":`${De.tableWidth}px`},onMouseDown:e=>e.preventDefault(),onClick:()=>F(r,De.cellPos,"row",t.index),children:/* @__PURE__ */e(xe,{name:"plus",size:14})},`row-boundary-${t.index}`)),[...De.columns.map(e=>({index:e.index,start:e.start})),{index:De.columns.length,start:((null==(d=De.columns.at(-1))?void 0:d.start)??0)+((null==(m=De.columns.at(-1))?void 0:m.size)??0)}].map(t=>/* @__PURE__ */e("button",{type:"button",className:"tbl-boundary tbl-boundary-col","aria-label":b("在第 {0} 列位置添加一列",{0:t.index+1}),title:b("添加一列"),style:{left:t.start-10,top:De.tableTop-14,"--insert-length":`${De.tableHeight}px`},onMouseDown:e=>e.preventDefault(),onClick:()=>F(r,De.cellPos,"column",t.index),children:/* @__PURE__ */e(xe,{name:"plus",size:14})},`col-boundary-${t.index}`))]}),$e&&/* @__PURE__ */t(n,{children:[
66
+ /* @__PURE__ */e("div",{className:"tbl-selection",style:{left:$e.left,top:$e.top,width:$e.width,height:$e.height}}),(!M||T)&&/* @__PURE__ */e("button",{type:"button",className:"tbl-selection-more","aria-label":b("打开单元格格式"),"aria-expanded":!!Se&&!k,style:{left:$e.left+$e.width/2-16,top:$e.top-10},onMouseDown:e=>e.preventDefault(),onClick:()=>{null!=Me&&(Se?x(!k):(r.view.dispatch(r.state.tr.setSelection(Fe.create(r.state.doc,Me))),r.view.focus(),x(!1)))},children:/* @__PURE__ */e(xe,{name:"ellipsis",size:20})}),Se&&!k&&(!M||T)&&/* @__PURE__ */e("div",{className:"tbl-cellbar",role:"toolbar","aria-label":b("表格选区格式"),ref:e=>{if(e){const{width:t,height:n}=e.getBoundingClientRect();t===le.width&&n===le.height||re({width:t,height:n})}},style:{left:$e.toolbarLeft,top:$e.toolbarTop},onMouseDown:e=>e.preventDefault(),children:/* @__PURE__ */t("div",{className:"tbl-tools",children:[tool(b("标题列"),"table-header-column",()=>chain().toggleHeaderColumn().run(),Pe),tool(b("标题行"),"table-header-row",()=>chain().toggleHeaderRow().run(),Ie),
67
+ /* @__PURE__ */e("span",{className:"tool-sep","aria-hidden":"true"}),tool(b("合并单元格"),"merge",()=>chain().mergeCells().run(),!1,!Te),tool(b("拆分单元格"),"split",()=>chain().splitCell().run(),!1,!Be),
68
+ /* @__PURE__ */e("span",{className:"tool-sep","aria-hidden":"true"}),
69
+ /* @__PURE__ */t("button",{type:"button",className:"tool"+("align"===C?" on":""),"aria-label":b("单元格对齐"),onClick:e=>showPanel("align",e),children:[
70
+ /* @__PURE__ */e(xe,{name:`align-${Re??"left"}`,size:18}),
71
+ /* @__PURE__ */e(xe,{name:"chevron-down",size:12})]}),
72
+ /* @__PURE__ */e("span",{className:"tool-sep","aria-hidden":"true"}),tool(b("表格文字加粗"),"bold",()=>toggleMark("bold"),mark("bold")),tool(b("表格文字斜体"),"italic",()=>toggleMark("italic"),mark("italic")),tool(b("表格文字删除线"),"strike",()=>toggleMark("strike"),mark("strike")),tool(b("表格文字下划线"),"underline",()=>toggleMark("underline"),mark("underline")),Ye.map(n=>/* @__PURE__ */t("button",{type:"button",className:"tool color-tool"+(C===n.key?" on":""),"aria-label":b(n.label),title:b(n.tip),"aria-haspopup":"dialog","aria-expanded":C===n.key,onClick:e=>showPanel(n.key,e),children:[
73
+ /* @__PURE__ */e(xe,{name:"color"===n.key?"text-color":"highlight"===n.key?"marker":"cell-fill",size:20}),
74
+ /* @__PURE__ */e("span",{className:"color-bar",style:{background:n.current??"transparent"},"aria-hidden":"true"})]},n.key)),
75
+ /* @__PURE__ */e("span",{className:"tool-sep","aria-hidden":"true"}),
76
+ /* @__PURE__ */e("button",{type:"button",className:"tool","aria-label":b("选择整个表格"),onClick:()=>$(r.view,De.cellPos,"table"),children:b("全选")}),
77
+ /* @__PURE__ */e("button",{type:"button",className:"tool","aria-label":b("更多表格操作"),"aria-haspopup":"menu","aria-expanded":M,onClick:e=>showContext(e,"cell"),children:/* @__PURE__ */e(xe,{name:"ellipsis",size:20})})]})})]})]}),w&&p(/* @__PURE__ */t(n,{children:["align"===C&&/* @__PURE__ */e("div",{ref:me,className:"tbl-align-menu",style:{position:"fixed",left:we.left,top:we.top},role:"menu","aria-label":b("单元格对齐菜单"),onMouseDown:e=>e.preventDefault(),children:V.map(n=>/* @__PURE__ */t("button",{type:"button",role:"menuitemradio","aria-checked":(Re??"left")===n,onClick:()=>{chain().setCellAttribute("align",n).run(),E("none")},children:[
78
+ /* @__PURE__ */e(xe,{name:`align-${n}`,size:18}),
79
+ /* @__PURE__ */e("span",{children:b("left"===n?"左对齐":"center"===n?"居中对齐":"右对齐")}),(Re??"left")===n&&/* @__PURE__ */e(xe,{name:"check",size:16})]},n))}),Ye.map(n=>C===n.key&&/* @__PURE__ */t("div",{ref:me,className:"tbl-palette",style:{position:"fixed",left:we.left,top:we.top},role:"dialog","aria-label":b("{0}选择",{0:n.tip}),onMouseDown:e=>e.preventDefault(),children:[
80
+ /* @__PURE__ */t("div",{className:"palette-head",children:[
81
+ /* @__PURE__ */e("span",{className:"palette-title",children:b(n.tip)}),
82
+ /* @__PURE__ */e("span",{className:"palette-hint",children:b(n.hint)})]}),
83
+ /* @__PURE__ */e("div",{className:"palette-grid",children:n.colors.map(t=>/* @__PURE__ */e("button",{type:"button",className:"swatch"+(n.current===t.value?" selected":""),"aria-label":`${b(n.tip)}:${t.value?b(t.name):b("默认颜色")}`,"aria-pressed":n.current===t.value,onClick:()=>function applySwatch(e){"background"===C?(chain().setCellAttribute("backgroundColor",e).run(),E("none")):function setColor(e,t){_(r,e,t?{color:t}:null),E("none")}("color"===C?"textStyle":"highlight",e)}(t.value),children:t.value&&"color"===n.key?/* @__PURE__ */e("span",{className:"glyph",style:{color:t.value},children:"A"}):t.value?/* @__PURE__ */e("span",{style:{background:t.value}}):/* @__PURE__ */e("span",{className:"color-transparency-grid","aria-hidden":"true"})},t.name))})]},n.key)),M&&/* @__PURE__ */e("div",{ref:de,className:"tbl-ctx w-overlay-scrollbar",style:{position:"fixed",left:ge.left,top:ge.top,maxHeight:ge.maxHeight,width:216},role:"menu","aria-label":b("表格操作菜单"),onMouseDown:e=>{e.target instanceof HTMLInputElement||e.preventDefault()},onContextMenu:e=>e.preventDefault(),onKeyDown:e=>{var t;if(!["ArrowDown","ArrowUp","Home","End"].includes(e.key)||e.target instanceof HTMLInputElement)return;const n=[...(null==(t=de.current)?void 0:t.querySelectorAll('[role^="menuitem"]:not(:disabled)'))??[]];if(!n.length)return;e.preventDefault();const o=n.indexOf(document.activeElement);n["Home"===e.key?0:"End"===e.key?n.length-1:(o+("ArrowDown"===e.key?1:-1)+n.length)%n.length].focus()},children:/* @__PURE__ */e("div",{ref:pe,className:"w-overlay-scrollbar__viewport",style:{maxHeight:Math.max(0,ge.maxHeight-2)},children:/* @__PURE__ */e("div",{ref:he,children:/* @__PURE__ */t(n,"table"===S?{children:[menuItem("columns",b("均分列宽"),function distribute(){const e=tableTarget(),t=e?r.view.nodeDOM(e.pos):null,n=t instanceof HTMLElement?t.matches("table")?t:t.querySelector("table"):null,o=(null==n?void 0:n.closest(".tableWrapper"))??n;o&&K(r,o.clientWidth-2),N(!1)}),menuItem("palette",b("表格样式"),()=>P(!I),{expanded:I}),I&&/* @__PURE__ */t("div",{className:"ctx-styles","aria-label":b("表头样式"),children:[[{label:b("浅灰表头"),color:null},{label:b("蓝色表头"),color:"#d0ebff"},{label:b("绿色表头"),color:"#d3f9d8"}].map(n=>/* @__PURE__ */t("button",{type:"button",className:"ctx-item",role:"menuitem",onMouseDown:e=>e.preventDefault(),onClick:()=>function applyStyle(e){J(r,e),N(!1),r.view.focus()}(n.color),children:[
84
+ /* @__PURE__ */e("span",{className:"ctx-style-swatch",style:{background:n.color??"var(--color-surface-subtle)"}}),n.label]},n.label)),
85
+ /* @__PURE__ */e("div",{className:"ctx-sep"}),menuItem("table-header-row",b("标题行"),()=>{chain().toggleHeaderRow().run(),N(!1)},{checked:Ie}),menuItem("table-header-column",b("标题列"),()=>{chain().toggleHeaderColumn().run(),N(!1)},{checked:Pe})]}),
86
+ /* @__PURE__ */e("div",{className:"ctx-sep"}),menuItem("cut",b("剪切表格"),()=>{copyTable(!0)},{disabled:ne}),menuItem("copy",b("复制表格"),()=>{copyTable(!1)},{disabled:ne}),menuItem("trash",b("删除表格"),()=>{chain().deleteTable().run(),N(!1)},{danger:!0}),
87
+ /* @__PURE__ */e("div",{className:"ctx-sep"}),menuItem("add-below",b("在下方插入段落"),function insertParagraphBelow(){const e=tableTarget();if(!e)return;const t=e.pos+e.node.nodeSize;r.chain().focus().insertContentAt(t,{type:"paragraph"}).setTextSelection(t+1).run(),N(!1)})]}:{children:[menuItem("table-row-plus",b("在上方插入行"),()=>{chain().addRowBefore().run(),N(!1)}),menuItem("table-row-plus",b("在下方插入行"),()=>{chain().addRowAfter().run(),N(!1)}),menuItem("table-col-plus",b("在左侧插入列"),()=>{chain().addColumnBefore().run(),N(!1)}),menuItem("table-col-plus",b("在右侧插入列"),()=>{chain().addColumnAfter().run(),N(!1)}),
88
+ /* @__PURE__ */e("div",{className:"ctx-sep"}),menuItem("table",b("切换首行表头"),()=>{chain().toggleHeaderRow().run(),N(!1)}),menuItem("table",b("切换首列表头"),()=>{chain().toggleHeaderColumn().run(),N(!1)}),
89
+ /* @__PURE__ */e("div",{className:"ctx-sep"}),Te&&menuItem("merge",b("合并单元格"),()=>{chain().mergeCells().run(),N(!1)}),Be&&menuItem("split",b("拆分单元格"),()=>{chain().splitCell().run(),N(!1)}),
90
+ /* @__PURE__ */e("div",{className:"ctx-label",children:b("对齐")}),
91
+ /* @__PURE__ */t("div",{className:"ctx-align",children:[V.map(t=>/* @__PURE__ */e("button",{type:"button",className:"al-btn"+(Re===t?" on":""),title:b("left"===t?"左对齐":"center"===t?"居中":"右对齐"),onClick:()=>{chain().setCellAttribute("align",t).run(),N(!1)},children:/* @__PURE__ */e(xe,{name:`align-${t}`,size:16})},t)),
92
+ /* @__PURE__ */e("button",{type:"button",className:"al-btn"+(null===Re?" on":""),title:b("清除对齐"),onClick:()=>{chain().setCellAttribute("align",null).run(),N(!1)},children:b("默认")})]}),
93
+ /* @__PURE__ */e("div",{className:"ctx-sep"}),R?/* @__PURE__ */t("div",{className:"ctx-width",children:[
94
+ /* @__PURE__ */e("span",{className:"ctx-label-inline",children:b("列宽")}),
95
+ /* @__PURE__ */e("input",{className:"ctx-num",type:"number",min:40,max:2e3,placeholder:b("像素"),value:ee,"aria-label":b("列宽像素"),onChange:e=>te(e.target.value),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),applyWidth())}}),
96
+ /* @__PURE__ */e("span",{className:"faint small",children:b("像素")}),
97
+ /* @__PURE__ */e("button",{type:"button",className:"ctx-mini primary",onClick:applyWidth,children:b("应用")})]}):menuItem("columns",b("列宽…"),()=>Z(!0)),menuItem("columns",b("列宽自适应"),()=>{var e;const t=null==(e=X(r))?void 0:e.col;null!=t&&(Q(r,t),N(!1))}),
98
+ /* @__PURE__ */e("div",{className:"ctx-sep"}),menuItem("trash",b("删除行"),()=>{chain().deleteRow().run(),N(!1)}),menuItem("trash",b("删除列"),()=>{chain().deleteColumn().run(),N(!1)}),menuItem("table",b("删除表格"),()=>{chain().deleteTable().run(),N(!1)},{danger:!0})]})})})})]}),w)]})}function advanceTarget(e,t){if(!e.valid||!t.docChanged)return;const n=e.from===e.to,o=t.mapping.mapResult(e.from,1),l=t.mapping.mapResult(e.to,-1);if(o.deleted||e.to>e.from&&l.deleted){if(e.nodeJSON&&t.getMeta("y-sync$")){const n=[];if(t.doc.descendants((t,o)=>{JSON.stringify(t.toJSON())===e.nodeJSON&&n.push({pos:o,node:t})}),1===n.length)return e.from=n[0].pos,void(e.to=e.from+n[0].node.nodeSize)}e.valid=!1}else e.from=o.pos,e.to=n?o.pos:Math.max(o.pos,l.pos)}function isRemoteDocumentChange(e){const t=e.getMeta($e);return!!(null==t?void 0:t.isChangeOrigin)&&!t.isUndoRedoOperation}function PagePicker({spaceId:n,excludeId:r,title:i,onClose:a,onPick:s}){const c=o().text,u=i??c("选择页面"),{host:d,portal:m}=l(),f=h(null),y=h(null),[k,x]=v([]),[C,E]=v([]),[M,N]=v(""),[L,D]=v(""),[S,A]=v(!0),[T,B]=v(0),[I,P]=v(0),R=w(),z=h(a);z.current=a,g(()=>{var e;let t=!0;return A(!0),D(""),((null==(e=d.references)?void 0:e.search("",{spaceId:n,excludeId:r}))??Promise.resolve([])).then(e=>{t&&x(e)}).catch(e=>{t&&D(e instanceof Error?e.message:c("页面加载失败"))}).finally(()=>{t&&A(!1)}),()=>{t=!1}},[d.references,n,r,T]),g(()=>{let e=!0;if(E([]),!M.trim())return;const t=setTimeout(()=>{var t;null==(t=d.references)||t.search(M.trim(),{spaceId:n,excludeId:r}).then(t=>{e&&E(t)}).catch(()=>{})},180);return()=>{e=!1,clearTimeout(t)}},[M,d.references,n,r]),g(()=>{var e;if(!m)return;const t=document.activeElement,n=Ee();function keydown(e){e.isComposing||229===e.keyCode||("Escape"===e.key?(e.preventDefault(),e.stopImmediatePropagation(),z.current()):f.current&&Me(f.current,e)&&e.preventDefault())}return null==(e=y.current)||e.focus(),document.addEventListener("keydown",keydown,!0),()=>{n.release(),document.removeEventListener("keydown",keydown,!0),(null==t?void 0:t.isConnected)&&t.focus()}},[m]);const H=b(()=>function pageCandidates(e,t,n,o){const l=[...new Map([...e,...t].map(e=>[e.id,e])).values()].filter(e=>e.id!==o);return n.trim()?Ye(l,n.trim()).map(e=>e.page):l.sort((e,t)=>(t.updatedAt??"").localeCompare(e.updatedAt??""))}(k,C,M,r).slice(0,20),[k,C,M,r]);g(()=>{P(0)},[H]),g(()=>{var e,t;null==(t=null==(e=f.current)?void 0:e.querySelector(`[id="${R}-${I}"]`))||t.scrollIntoView({block:"nearest"})},[I,R]);const O=!!H.length&&!S&&!L;function choose(e){S||L||s(e.id,e.title)}return m?p(/* @__PURE__ */e("div",{className:"picker-backdrop",onMouseDown:e=>{e.target===e.currentTarget&&a()},children:/* @__PURE__ */t("div",{ref:f,className:"picker",role:"dialog","aria-modal":"true","aria-label":u,children:[
99
+ /* @__PURE__ */e("input",{ref:y,className:"picker-input",type:"text",role:"combobox","aria-label":u,"aria-autocomplete":"list","aria-controls":O?R:void 0,"aria-expanded":!!O,"aria-activedescendant":O?`${R}-${I}`:void 0,placeholder:c("搜索页面标题(支持拼音)…"),value:M,onChange:e=>{N(e.target.value),P(0)},onKeyDown:e=>{if(!e.nativeEvent.isComposing&&229!==e.keyCode&&O)if("ArrowDown"===e.key||"ArrowUp"===e.key)e.preventDefault(),P(t=>(t+("ArrowDown"===e.key?1:-1)+H.length)%H.length);else if("Enter"===e.key){e.preventDefault();const t=H[I];t&&choose(t)}}}),L?/* @__PURE__ */t("p",{role:"alert",children:[L," ",
100
+ /* @__PURE__ */e("button",{type:"button",onClick:()=>B(e=>e+1),children:c("重试")})]}):S?/* @__PURE__ */e("p",{role:"status",children:c("加载中…")}):H.length?/* @__PURE__ */e("ul",{id:R,className:"picker-list",role:"listbox","aria-label":c("页面候选"),children:H.map((t,n)=>/* @__PURE__ */e("li",{id:`${R}-${n}`,role:"option","aria-selected":n===I,className:n===I?"active":void 0,onMouseEnter:()=>P(n),onMouseDown:e=>{e.preventDefault(),choose(t)},children:/* @__PURE__ */e("span",{className:"picker-title",children:t.title})},t.id))}):/* @__PURE__ */e("p",{className:"picker-empty",children:c("没有匹配的页面")})]})}),m):null}function videoProblem(e,t=!1,n=Ge().text){const o=Ue(e);return o.data?"":"empty"===o.issue?t?n("请粘贴视频链接。"):"":"short-link"===o.issue?n("B站短链(b23.tv)不能在正文里直接播放,请先在浏览器打开,再复制地址栏里的完整链接。"):"missing-id"===o.issue?n("链接里没有识别到视频 ID,请确认复制的是单个视频的播放页,而不是首页或频道页。"):"unsupported"===o.issue?n("暂不支持该平台的视频链接。目前支持 {0}。",{0:Object.values(je).join("、")}):n("链接格式不正确,请粘贴以 http:// 或 https:// 开头的完整地址。")}function VideoEmbedDialog({initialUrl:n="",onClose:r,onInsert:i}){var a;const s=o(),c=s.text,[u,d]=v(n),[m,p]=v(!1),f=w(),b=h(null);g(()=>{var e;null==(e=b.current)||e.focus()},[]);const y=Ue(u).data,k=l().video,[x,C]=v("auto"===(null==k?void 0:k.load)),E=!y||((null==(a=null==k?void 0:k.providers)?void 0:a.includes(y.provider))??!0),M=E?y:null,N=E?videoProblem(u,m,c):s.t("video.notEnabled");function submit(){p(!0),M&&i({...M,autoLoad:x})}/* @__PURE__ */
101
+ return e(Ne,{workspace:!0,title:c("嵌入视频"),onClose:r,className:"video-dialog",footer:/* @__PURE__ */t("div",{className:"wr-toolbar",children:[
102
+ /* @__PURE__ */e(ke,{onClick:r,children:c("取消")}),
103
+ /* @__PURE__ */e(ke,{variant:"primary",disabled:!M,onClick:submit,children:c("插入视频")})]}),children:/* @__PURE__ */t("form",{className:"video-form",onSubmit:e=>{e.preventDefault(),submit()},children:[
104
+ /* @__PURE__ */t("div",{className:"video-field",children:[
105
+ /* @__PURE__ */e("label",{htmlFor:f,children:c("视频链接")}),
106
+ /* @__PURE__ */e("input",{ref:b,id:f,type:"url",maxLength:2048,placeholder:c("粘贴视频播放页链接"),autoComplete:"off",spellCheck:!1,value:u,onChange:e=>d(e.target.value)})]}),M?/* @__PURE__ */t("p",{className:"video-status video-status--ok",role:"status",children:[c("已识别"),je[M.provider]," ",c("视频"),
107
+ /* @__PURE__ */e("span",{className:"video-status-meta",children:M.videoId}),M.page>1?s.t("video.part",{0:M.page}):"",M.startSeconds?s.t("video.from",{0:M.startSeconds}):""]}):N?/* @__PURE__ */e("p",{className:"video-status video-status--error",role:"alert",children:N}):null,M&&/* @__PURE__ */e("p",{className:"video-source",children:_e(M)}),
108
+ /* @__PURE__ */t("div",{className:"video-loading-option",children:[
109
+ /* @__PURE__ */t("label",{className:"video-loading-choice",children:[
110
+ /* @__PURE__ */e("input",{type:"checkbox",checked:x,"aria-describedby":`${f}-loading`,onChange:e=>C(e.target.checked)}),
111
+ /* @__PURE__ */e("span",{children:s.t("video.autoLoad")})]}),
112
+ /* @__PURE__ */e("p",{id:`${f}-loading`,className:"video-hint",children:s.t("video.autoLoadHint")})]})]})})}const lt=/* @__PURE__ */new WeakMap,rt={type:"doc",content:[{type:"paragraph"}]},it=y(function DocumentEditor2(n,c){const f=o(),b=f.text,w=h(null),y=h(null),N=h(null),L=l(),D=L.host,S=be(n),T=be(D),I=h(null),P=be(L),[R,z]=v(0),[H,O]=v(""),[q,$]=v(""),[F,V]=v(null),[W,Y]=v(null),[U,j]=v(null),[_,J]=v(!1),[X,G]=v(void 0),[Q,K]=v(null),[me,ke]=v(null),Ce=be(me),[Ee,Me]=v(!1),[Ne,De]=v(""),[Se,Ae]=v(!1),[Te,Be]=v([]),Ie=h(!1),Pe=h(0),Re=h(null),ze=h("image"),[He,qe]=v(null),$e=h(null),Fe=h(null),Ve=h(null),We=h(null),Ye=h(/* @__PURE__ */new Set),[je,Ge]=v(!1),[Ke,Ze]=v(null),et=be(_),it=h(!1),at=h(void 0),st=h(!1),ct=h("explicit"),ut=h(null),dt=be({dragging:je,slash:F,link:me,preview:He}),mt=h({openId:null,maps:{}}),pt=we();g(()=>()=>{$e.current&&clearTimeout($e.current)},[]),g(()=>{const e=function createBlockMenuHover(e){const t=/* @__PURE__ */new Set;let n,o,l=!1;function clearOpen(){clearTimeout(n),n=void 0}function clearClose(){clearTimeout(o),o=void 0}function reset(){clearOpen(),clearClose(),t.clear()}return{enter:function enter(o){l||(t.add(o),clearClose(),"trigger"!==o||e.isOpen()||n||(n=setTimeout(()=>{n=void 0,!l&&t.has("trigger")&&!e.isOpen()&&e.canOpen()&&e.open()},e.openDelay??120)))},leave:function leave(n){t.delete(n),l||t.size||(clearOpen(),clearClose(),e.isOpen()&&(o=setTimeout(()=>{o=void 0,!l&&!t.size&&e.isOpen()&&e.canClose()&&e.close()},e.closeDelay??240)))},reset:reset,dispose:function dispose(){l=!0,reset()}}}({isOpen:()=>et.current,canOpen:()=>!(S.current.disabled||st.current||dt.current.dragging||dt.current.slash||dt.current.link||dt.current.preview||!Fe.current),canClose:()=>{var e,t;return"hover"===ct.current&&!(null==(t=null==(e=y.current)?void 0:e.querySelector(".block-anchor"))?void 0:t.contains(document.activeElement))},open:()=>{Fe.current&&openQuickMenu(Fe.current,at.current,"hover")},close:closeBlockMenu});return ut.current=e,()=>{e.dispose(),ut.current=null}},[]),g(()=>{const e=Re.current,cancel=()=>{We.current=null};return null==e||e.addEventListener("cancel",cancel),()=>null==e?void 0:e.removeEventListener("cancel",cancel)},[]);const ht=k(e=>{var t,n;$(ye(e).message),null==(n=(t=S.current).onError)||n.call(t,ye(e))},[S]),vt=k(e=>{var t,n;O(e),null==(n=(t=S.current).onNotice)||n.call(t,e)},[S]);function onBlockHover(e){if($e.current&&(clearTimeout($e.current),$e.current=null),e)return Fe.current=e,void Y(e);$e.current=setTimeout(()=>{$e.current=null,Y(null),et.current||j(null)},220)}function openQuickMenu(e,t,n="explicit"){it.current=!0,ct.current=n,j(e),G(t),J(!0),Y(e)}function enterQuickMenu(e){var t;onBlockHover(e),at.current=e.isEmpty?"insert-below":void 0,null==(t=ut.current)||t.enter("trigger")}g(()=>{var t,n,o;const l=w.current;if(!l)return;Ie.current=!1;let c=!0;const p=document.createElement("div");let h;l.append(p);const atom=({node:t,editor:n,getPos:o})=>{const l=document.createElement("div");l.contentEditable="false",l.dataset.blockChrome="";const r=crypto.randomUUID(),a="videoEmbed"===t.type.name?document.createElement("div"):l;a!==l&&l.append(a);let s=t,u=0;"mindMap"===t.type.name&&(l.dataset.mindMapNode="");const d="mindMap"===t.type.name?pe(n,()=>{const e=o();return"number"==typeof e?e:void 0},{get pageId(){return S.current.pageId},canComment:()=>!!S.current.canComment,canExport:()=>!!S.current.allowExport,status:()=>S.current.saveStatus??"",fetchAttachment:e=>{var t;return((null==(t=T.current.attachments)?void 0:t.fetch)??i)(e)},get uploadImage(){var e;return S.current.uploadImage??(null==(e=T.current.attachments)?void 0:e.uploadImage)},viewState:S.current.mindMapViewState??mt.current,awareness:S.current.collabAwareness}):void 0,render=()=>{let t;t="mindMap"===s.type.name?/* @__PURE__ */e(he,{node:s.toJSON(),controller:d,pageId:S.current.pageId,canComment:S.current.canComment,onCommentsChanged:S.current.onCommentsChanged}):/* @__PURE__ */e(ge,{attrs:s.attrs,reloadKey:u}),Be(e=>[...e.filter(e=>e.id!==r),{id:r,dom:a,content:t}])},p="videoEmbed"===t.type.name?function createVideoBlockControls(e){const{editor:t,dom:n,getPos:o}=e,l=Qe(t),r=document.createElement("div");r.className="video-block-tools",r.contentEditable="false",r.hidden=!0;const i=document.createElement("div");i.className="video-block-toolbar",i.setAttribute("role","toolbar"),r.append(i),n.classList.add("video-editor-node"),n.setAttribute("role","group"),n.setAttribute("aria-keyshortcuts","Enter"),n.tabIndex=0;let a,s,c=!1,u=!1,d=!1,p=!1,h=!1,v=0,f=NaN,g=NaN;const b=[],current=()=>{if(c||t.isDestroyed)return null;const e=o();if("number"!=typeof e||e<0||e>=t.state.doc.content.size)return null;const n=t.state.doc.nodeAt(e);return"videoEmbed"===(null==n?void 0:n.type.name)?{pos:e,node:n}:null},data=()=>{var e;return Xe(null==(e=current())?void 0:e.node.attrs)},allowed=()=>{var t,n,o;const l=data();return!!l&&((null==(o=null==(n=null==(t=e.policy)?void 0:t.call(e))?void 0:n.providers)?void 0:o.includes(l.provider))??!0)};function select(){const e=current();if(e&&t.isEditable&&!(t.state.selection instanceof m&&t.state.selection.from===e.pos)){const o=document.activeElement;t.view.dispatch(t.state.tr.setSelection(m.create(t.state.doc,e.pos))),t.view.focus(),o instanceof HTMLElement&&(o===n||r.contains(o))&&o.focus({preventScroll:!0})}}function iconButton(e,n,o){const l=document.createElement("button");l.type="button";const r=document.createElementNS("http://www.w3.org/2000/svg","svg");for(const[e,t]of Object.entries({width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"1.8","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"}))r.setAttribute(e,t);for(const e of n){const t=document.createElementNS(r.namespaceURI,"path");t.setAttribute("d",e),r.append(t)}return l.append(r),b.push({element:l,key:e}),l.addEventListener("mousedown",e=>e.preventDefault()),l.addEventListener("click",()=>{t.isEditable&&current()&&(p=!1,select(),o())}),i.append(l),l}const w=iconButton("video.reload",["M20 7v5h-5","M20 12a8 8 0 1 1-2-5.3L20 9"],()=>{allowed()&&e.reload()}),y=iconButton("video.copySource",["M9 9h12v12H9z","M5 15H3V3h12v2"],()=>{const e=data();e&&(async()=>{try{await navigator.clipboard.writeText(_e(e)),E.textContent=l.t("code.copied")}catch{E.textContent=l.t("error.clipboardPermission")}c||(clearTimeout(s),s=setTimeout(()=>{E.textContent="",sync()},3e3),sync())})()}),k=iconButton("video.autoLoad",["M3 4h18v13H3z","m10 8 5 3-5 3Z","M8 21h8m-4-4v4"],()=>{var n;const o=current();o&&t.view.dispatch(Oe(t.state.tr.setNodeMarkup(o.pos,void 0,{...o.node.attrs,autoLoad:!Je(data(),null==(n=e.policy)?void 0:n.call(e))})))}),x=document.createElement("span");k.append(x),k.className="video-block-loading",iconButton("video.openOriginal",["M14 3h7v7m0-7L10 14","M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5"],()=>{const e=data();e&&window.open(_e(e),"_blank","noopener,noreferrer")});const C=iconButton("video.editLink",["M10 13a5 5 0 0 0 7 .1l3-3a5 5 0 0 0-7-7l-2 2","M14 11a5 5 0 0 0-7-.1l-3 3a5 5 0 0 0 7 7l2-2"],()=>{if(d)return closeEdit(),void C.focus();clearTimeout(s),E.textContent="",d=!0,M.hidden=!1,S.value=data()?_e(data()):"",C.setAttribute("aria-expanded","true"),validate(),sync(),S.focus(),S.select()});C.setAttribute("aria-haspopup","dialog"),C.setAttribute("aria-expanded","false"),iconButton("video.delete",["M3 6h18M9 6V3h6v3","m5 6 1 15h12l1-15M10 10v7m4-7v7"],()=>{const e=current();e&&(t.view.dispatch(Oe(t.state.tr.delete(e.pos,e.pos+e.node.nodeSize)).scrollIntoView()),t.view.focus())}).className="video-block-delete";const E=document.createElement("div");E.className="video-block-feedback",E.setAttribute("role","status"),r.append(E);const M=document.createElement("form");M.className="video-block-link-form",M.hidden=!0,M.setAttribute("role","dialog"),M.noValidate=!0;const N=document.createElement("span");N.className="video-block-link-arrow",N.setAttribute("aria-hidden","true");const L=document.createElement("div"),D=document.createElement("div"),S=document.createElement("textarea");L.className="video-block-link-row",D.className="video-block-link-field";const A=C.querySelector("svg").cloneNode(!0);A.classList.add("video-block-link-icon"),A.setAttribute("width","16"),A.setAttribute("height","16");const T=`video-link-${crypto.randomUUID()}`;S.id=T,S.rows=1,S.wrap="soft",S.inputMode="url",S.maxLength=2048,S.autocomplete="off",S.autocapitalize="off",S.spellcheck=!1;const B=document.createElement("p");B.id=`${T}-problem`,B.setAttribute("role","status"),S.setAttribute("aria-describedby",B.id);const I=document.createElement("button");function resizeInput(){if(!d||r.hidden||!r.isConnected)return;S.style.height="34px";const e=Math.max(34,Math.min(110,S.scrollHeight+2));S.style.height=`${e}px`,S.style.overflowY=S.scrollHeight>108?"auto":"hidden"}function validate(){var n,o,r;const i=Ue(S.value).data,a=!i||((null==(r=null==(o=null==(n=e.policy)?void 0:n.call(e))?void 0:o.providers)?void 0:r.includes(i.provider))??!0);B.textContent=a?videoProblem(S.value,!0,l.text):l.t("video.notEnabled"),I.disabled=!i||!a||!t.isEditable,S.setAttribute("aria-invalid",String(!!B.textContent)),resizeInput(),schedule()}function closeEdit(){d=!1,M.hidden=!0,C.setAttribute("aria-expanded","false"),sync()}I.type="submit",I.className="video-block-link-confirm",D.append(A,S),L.append(D,I),M.append(N,L,B),r.append(M);const P={isEditing:()=>d,isFocused:()=>n.contains(document.activeElement)||r.contains(document.activeElement),hide(){p=!0,u=!1,d=!1,r.hidden=!0,M.hidden=!0,C.setAttribute("aria-expanded","false")}};function activate(e=!1){const n=lt.get(t);return n===P||!((null==n?void 0:n.isEditing())&&!e)&&(null==n||n.hide(),lt.set(t,P),!0)}function localize(){i.setAttribute("aria-label",l.t("video.actions")),n.setAttribute("aria-label",l.t("video.actions")),M.setAttribute("aria-label",l.t("video.editLink"));for(const{element:e,key:t}of b)e.title=l.t(t),e.setAttribute("aria-label",l.t(t));S.setAttribute("aria-label",l.t("video.link")),S.placeholder=l.t("link.placeholder"),I.textContent=l.t("common.confirm"),d&&validate(),sync()}function place(){if(v=0,r.hidden||c)return;const e=n.getBoundingClientRect(),t=Le({align:"center",prefer:"above",anchor:{left:e.left,top:Math.max(8,e.top),width:e.width,height:0},host:{left:0,top:0},card:{width:i.offsetWidth,height:i.offsetHeight},bounds:{left:0,top:0,width:document.documentElement.clientWidth,height:window.innerHeight}});if(r.style.left=`${t.left}px`,r.style.top=`${t.top}px`,d){resizeInput();const e=C.getBoundingClientRect(),t=Le({align:"start",prefer:"below",anchor:{left:e.left,top:e.top,width:e.width,height:e.height},host:{left:0,top:0},card:{width:M.offsetWidth,height:M.offsetHeight},bounds:{left:0,top:0,width:document.documentElement.clientWidth,height:window.innerHeight}});M.style.left=`${t.left}px`,M.style.top=`${t.top}px`,N.dataset.side=t.side,N.style.left=`${t.arrowLeft}px`}}function schedule(){v||(v=requestAnimationFrame(place))}function sync(){var o,i;if(c||t.isDestroyed)return;const a=current(),s=t.state.selection,v=!!a&&s instanceof m&&s.from===a.pos;v&&!h&&(p=!1,activate(!0)),h=v,n.classList.toggle("is-video-selected",t.isEditable&&v),n.tabIndex=t.isEditable?0:-1,t.isEditable&&a||(d=!1,M.hidden=!0,C.setAttribute("aria-expanded","false"));const f=n.contains(document.activeElement)||r.contains(document.activeElement),g=n.getBoundingClientRect(),b=g.bottom>8&&g.top<window.innerHeight-8&&g.right>0&&g.left<window.innerWidth;if(r.hidden=lt.get(t)!==P||!t.isEditable||!a||!b||p||!(u||v&&t.view.hasFocus()||d||f),!r.hidden){const t=(null==(o=e.portal)?void 0:o.call(e))??document.body;r.parentElement!==t&&t.append(r);const n=Je(data(),null==(i=e.policy)?void 0:i.call(e));k.setAttribute("aria-pressed",String(n)),x.textContent=l.t(n?"video.automatic":"video.manual"),w.disabled=!allowed(),k.disabled=!data(),y.disabled=!data(),E.hidden=!E.textContent,place()}}function enter(e){const n=lt.get(t);n!==P&&(null==n?void 0:n.isFocused())&&e.clientX===f&&e.clientY===g||activate()&&(clearTimeout(a),u=!0,p=!1,sync())}function pointerMove(e){f=e.clientX,g=e.clientY}function leave(){clearTimeout(a),a=setTimeout(()=>{u=!1,sync()},160)}function focus(){activate(!0),p=!1,document.activeElement===n&&select(),sync()}function blur(){queueMicrotask(()=>{c||sync()})}function keydown(e){return e.target!==S||"Enter"!==e.key||e.isComposing||e.shiftKey?e.target!==n||!t.isEditable||e.isComposing||"Enter"!==e.key&&" "!==e.key?void("Escape"!==e.key||e.isComposing||(e.preventDefault(),e.stopPropagation(),d?(closeEdit(),C.focus()):(p=!0,t.view.focus(),sync()))):(e.preventDefault(),e.stopPropagation(),activate(!0),select(),p=!1,sync(),void(w.disabled?C:w).focus()):(e.preventDefault(),e.stopPropagation(),void M.requestSubmit())}function outside(e){n.contains(e.target)||r.contains(e.target)||(u=!1,d=!1,M.hidden=!0,C.setAttribute("aria-expanded","false"),p=!0,sync())}function click(e){var t;(null==(t=e.target)?void 0:t.closest("button,a,iframe"))||(p=!1,select(),sync())}function viewport(){sync()}S.addEventListener("input",validate),M.addEventListener("submit",e=>{e.preventDefault(),validate();const n=current(),o=Ue(S.value).data;n&&o&&!I.disabled&&t.isEditable&&(t.view.dispatch(Oe(t.state.tr.setNodeMarkup(n.pos,void 0,{...n.node.attrs,...o}))),closeEdit(),C.focus())});for(const e of[n,r])e.addEventListener("pointerenter",enter),e.addEventListener("pointerleave",leave),e.addEventListener("focusin",focus),e.addEventListener("focusout",blur),e.addEventListener("keydown",keydown);n.addEventListener("click",click),document.addEventListener("pointerdown",outside,!0),document.addEventListener("pointermove",pointerMove,!0),window.addEventListener("scroll",viewport,!0),window.addEventListener("resize",viewport),t.on("transaction",sync),t.on("update",sync);const R="undefined"==typeof ResizeObserver?void 0:new ResizeObserver(schedule);null==R||R.observe(n),null==R||R.observe(r);const z=l.subscribe(localize);return localize(),{update:sync,destroy(){c=!0,clearTimeout(a),clearTimeout(s),cancelAnimationFrame(v),z(),null==R||R.disconnect(),t.off("transaction",sync),t.off("update",sync),document.removeEventListener("pointerdown",outside,!0),document.removeEventListener("pointermove",pointerMove,!0),window.removeEventListener("scroll",viewport,!0),window.removeEventListener("resize",viewport),n.removeEventListener("click",click);for(const e of[n,r])e.removeEventListener("pointerenter",enter),e.removeEventListener("pointerleave",leave),e.removeEventListener("focusin",focus),e.removeEventListener("focusout",blur),e.removeEventListener("keydown",keydown);r.remove(),lt.get(t)===P&&lt.delete(t)}}}({editor:n,dom:l,getPos:()=>o(),policy:()=>P.current.video,portal:()=>P.current.portal,reload:()=>{u++,c&&render()}}):void 0;return queueMicrotask(()=>{c&&render()}),{dom:l,update:e=>e.type===s.type&&("mindMap"!==s.type.name||e.attrs.id===s.attrs.id)&&(s=e,c&&render(),null==p||p.update(),!0),stopEvent:()=>!0,ignoreMutation:()=>!0,destroy(){null==d||d.dispose(),null==p||p.destroy(),c&&queueMicrotask(()=>{c&&Be(e=>e.filter(e=>e.id!==r))})}}},menuCoords=e=>{var t;try{const n=h.view.coordsAtPos(e),o=(null==(t=y.current)?void 0:t.getBoundingClientRect())??{left:0,top:0};return{x:n.left-o.left,y:n.bottom-o.top+6}}catch{return{x:0,y:0}}};try{const e=S.current.value??S.current.defaultValue??rt,l=(new C).validate(e,S.current.schemaVersion??E(e,M));h=new s({element:p,editable:!S.current.disabled,content:S.current.collabDoc?void 0:l.doc,extensions:re({i18n:f,placeholder:()=>S.current.placeholder??b("输入 / 唤起命令,开始记录…"),collabDoc:S.current.collabDoc,collabAwareness:S.current.collabAwareness,canCopyCode:()=>!!S.current.allowExport,contentContext:()=>P.current,attachmentFetcher:null==(t=T.current.attachments)?void 0:t.fetch,attachmentScope:T.current.scope,attachmentPortal:()=>P.current.portal,inlineComments:{getComments:()=>S.current.inlineComments??[],getActiveId:()=>S.current.activeCommentId??null,onMarkClick:e=>{var t,n;return null==(n=(t=S.current).onInlineMarkClick)?void 0:n.call(t,e)}},slash:{onOpen:e=>{V({...e,...menuCoords(e.from)}),J(!1),Y(null)},onUpdate:e=>V(t=>t?{...t,query:e}:null),onClose:()=>V(null)},blockHandle:{onHover:onBlockHover},uploadPlaceholder:{onLost:()=>vt(b("上传目标已移除,附件未插入。"))},tableInteraction:{onHover:e=>K(e)},mindMapRenderer:atom,videoRenderer:atom}),editorProps:{attributes:{role:"textbox","aria-label":b("文档正文"),"aria-multiline":"true",class:"editor-canvas ywiki-prose ywiki-content"},handleKeyDown:(e,t)=>!(!t.ctrlKey&&!t.metaKey||"k"!==t.key.toLowerCase()||(t.preventDefault(),openLink(h),0)),handlePaste:(e,t)=>{var n,o;if(!e.editable)return!1;const l=oe(t);if(l.length)return upload(l),!0;const i=(null==(o=null==(n=t.clipboardData)?void 0:n.getData("text/plain"))?void 0:o.trim())??"";if(e.state.selection.empty&&!e.state.selection.$from.parent.content.size&&A(i)){const t=e.state.selection.from,n=e.state.tr.insertText(i);return n.addMark(t,t+i.length,e.state.schema.marks.link.create({href:i})),e.dispatch(n),We.current={from:t,to:t+i.length,valid:!0},De(i),Me(!0),!0}return le(t).length&&setTimeout(()=>{c&&async function upgradeRemoteImages(e){var t,n,o;const l=T.current.scope,i=null==(t=T.current.attachments)?void 0:t.fetch,a=S.current.uploadImage??(null==(n=T.current.attachments)?void 0:n.uploadImage);if(!a)return void vt(b("未配置图片上传服务,无法保存粘贴的外链图片"));const s=[];e.state.doc.descendants((e,t)=>{if("image"===e.type.name&&!e.attrs.attachmentId&&/^https?:\/\//i.test(String(e.attrs.src))){const n={from:t,to:t+e.nodeSize,valid:!0};Ye.current.add(n),s.push({target:n,src:e.attrs.src})}}),Pe.current+=s.length,z(e=>e+1);for(const{target:t,src:n}of s)try{const s=await fetch(n,{credentials:"omit",signal:AbortSignal.timeout(15e3)});if(!s.ok||Number(s.headers.get("content-length"))>20971520)throw new Error(b("外链图片读取失败或超过 20 MB"));const c=await s.blob();if(!c.type.startsWith("image/")||c.size>20971520)throw new Error(b("外链图片格式无效或超过 20 MB"));const u=await a(new File([c],b("粘贴图片"),{type:c.type}));if(e.isDestroyed||!e.isEditable||!t.valid||(null==(o=e.state.doc.nodeAt(t.from))?void 0:o.attrs.src)!==n)continue;const d=e.state.doc.nodeAt(t.from);r(u.id,c,l,i),e.view.dispatch(e.state.tr.setNodeMarkup(t.from,void 0,{...d.attrs,src:null,attachmentId:u.id,alt:u.alt}))}catch(t){e.isDestroyed||ht(t)}finally{Ye.current.delete(t),Pe.current--,e.isDestroyed||z(e=>e+1)}}(h)},0),!1},handleDrop:(e,t)=>{var n;if(!e.editable||!(null==(n=t.dataTransfer)?void 0:n.files.length))return!1;const o=e.posAtCoords({left:t.clientX,top:t.clientY});return!!o&&(t.preventDefault(),upload([...t.dataTransfer.files],o.pos),!0)},handleDOMEvents:{contextmenu:(e,t)=>{var n,o;if(!(e.editable&&t.target instanceof Element))return!1;if(t.target.closest("td,th"))return!1;const l=t.target.closest(".ProseMirror > *"),r=null==(n=y.current)?void 0:n.getBoundingClientRect();if(!l||l.parentElement!==e.dom||!r)return!1;let i=-1;e.state.doc.forEach((t,n)=>{e.nodeDOM(n)===l&&(i=n)});const a=e.state.doc.nodeAt(i);return!!a&&(t.preventDefault(),ke(null),V(null),it.current=!1,ct.current="explicit",null==(o=ut.current)||o.reset(),j({pos:i,type:a.type.name,top:t.clientY-r.top,contentLeft:t.clientX-r.left,isEmpty:a.isTextblock&&!a.content.size}),G(void 0),J(!0),!0)},mouseover:(e,t)=>{var n;if(Ce.current||!e.editable)return!1;const o=null==(n=t.target)?void 0:n.closest("a[href]");if(!o||!e.dom.contains(o))return!1;const l=e.posAtDOM(o,0),r=a(e.state.doc.resolve(l),e.state.schema.marks.link);if(!r)return!1;const i=o.getBoundingClientRect(),s=createLinkEditTarget(e.state,u.create(e.state.doc,r.from,r.to));return s&&ke({...s,tracked:s,mode:"preview",anchor:{left:i.left,top:i.top,width:i.width,height:i.height}}),!1}}},onUpdate:({editor:e,transaction:t})=>{var n,o;I.current=e.getJSON(),z(e=>e+1),null==(o=(n=S.current).onChange)||o.call(n,I.current,{remote:isRemoteDocumentChange(t)})},onSelectionUpdate:()=>z(e=>e+1),onTransaction:({transaction:e})=>{We.current&&advanceTarget(We.current,e);for(const t of Ye.current)advanceTarget(t,e);const t=Ce.current;(null==t?void 0:t.tracked)&&(advanceTarget(t.tracked,e),t.from=t.tracked.from,t.to=t.tracked.to),z(e=>e+1)},onBlur:()=>{var e,t;return null==(t=(e=S.current).onBlur)?void 0:t.call(e)}}),N.current=h,y.current&&(Ve.current=function documentGestures(e,t,n){const o=e.view;let l=null,r=0,i=null;const writable=()=>!e.isDestroyed&&e.isEditable;function pickedImage(e){i=e,n.onImagePick(e)}function cancel(){null==l||l(),l=null}function start(t,i,a){if(!writable()||0!==i.button||o.composing)return;cancel();let s=Z(o);if(!s.some(e=>e.from===t)){const e=o.state.doc.nodeAt(t);if(!e)return;s=[{from:t,to:t+e.nodeSize}]}if(!s.length)return;const c=o.state.doc,d=s[0].from,m=s.at(-1).to,p=s.some(e=>{var t;return null==(t=e.images)?void 0:t.length}),h=o.state.selection,v={x:i.clientX,y:i.clientY};let f=!1,g=v,b=null,w=0;const y=document.createElement("div");y.className="wr-block-drop-line block-drop-line",y.setAttribute("aria-hidden","true");const k=document.documentElement.style.cursor,x=document.body.style.userSelect;function paint(){if(!writable()||o.state.doc!==c)return void cancel();b=function pickNearestGap(e,t){if(0===e.length)return null;let n=e[0],o=Math.abs(e[0].y-t);for(const l of e){const e=Math.abs(l.y-t);e<o&&(n=l,o=e)}return n}(function gaps(){const e=[];return o.state.doc.forEach((t,n)=>{const l=o.nodeDOM(n);if(!(l instanceof HTMLElement))return;const r=l.getBoundingClientRect();e.push({pos:n,y:r.top}),n+t.nodeSize===o.state.doc.content.size&&e.push({pos:n+t.nodeSize,y:r.bottom})}),e}(),g.y);const e=o.dom.getBoundingClientRect();b&&Object.assign(y.style,{left:`${e.left}px`,top:`${b.y}px`,width:`${e.width}px`}),y.hidden=!b||b.pos>=d&&b.pos<=m}const scroll=()=>{if(!f)return;let e=o.dom.parentElement;for(;e&&!(e.scrollHeight>e.clientHeight&&/auto|scroll/.test(getComputedStyle(e).overflowY));)e=e.parentElement;const t=(null==e?void 0:e.getBoundingClientRect())??{top:0,bottom:innerHeight},n=g.y<t.top+42?-14:g.y>t.bottom-42?14:0;n&&(e?e.scrollTop+=n:window.scrollBy(0,n),paint()),w=requestAnimationFrame(scroll)},move=e=>{g={x:e.clientX,y:e.clientY},!f&&Math.hypot(g.x-v.x,g.y-v.y)<4||(f||(Z(o).some(e=>e.from===t)||te(o,t,t),f=!0,n.onStart(),n.layer().append(y),o.dom.classList.add("is-block-dragging"),document.documentElement.style.cursor="grabbing",document.body.style.userSelect="none",w=requestAnimationFrame(scroll)),paint(),e.preventDefault())},up=e=>{const t=b,n=f;if(cancel(),!writable()||o.state.doc!==c)return;if(!n){if(a)a();else{const t=o.posAtCoords({left:e.clientX,top:e.clientY});t&&o.dispatch(o.state.tr.setSelection(u.near(o.state.doc.resolve(t.pos)))),o.focus()}return}if(r=Date.now()+400,!t||t.pos>=d&&t.pos<=m)return;const l=o.state.selection;if(p&&(!(l instanceof ee)||!l.eq(h)))return;const i=p?l.content():o.state.doc.slice(d,m),s=o.state.tr;p?l.replace(s):s.delete(d,m);const v=s.mapping.map(t.pos),g=s.steps.length;s.insert(v,i.content),s.steps.length!==g&&(o.dispatch(s.scrollIntoView()),o.focus())},key=e=>{"Escape"!==e.key||e.isComposing||(e.preventDefault(),e.stopImmediatePropagation(),r=Date.now()+400,cancel())},change2=()=>{o.state.doc===c&&writable()||cancel()};l=()=>{window.removeEventListener("mousemove",move,!0),window.removeEventListener("mouseup",up,!0),window.removeEventListener("keydown",key,!0),window.removeEventListener("blur",cancel),window.removeEventListener("pointercancel",cancel),e.off("transaction",change2),cancelAnimationFrame(w),y.remove(),o.dom.classList.remove("is-block-dragging"),document.documentElement.style.cursor=k,document.body.style.userSelect=x,n.onEnd()},window.addEventListener("mousemove",move,!0),window.addEventListener("mouseup",up,!0),window.addEventListener("keydown",key,!0),window.addEventListener("blur",cancel),window.addEventListener("pointercancel",cancel),e.on("transaction",change2),i.preventDefault()}function pickImage(e){var t;try{const n=o.posAtDOM(e,0);if("image"!==(null==(t=o.state.doc.nodeAt(n))?void 0:t.type.name))return;pickedImage(n),o.dispatch(o.state.tr.setSelection(m.create(o.state.doc,n))),o.focus()}catch{}}function down(e){if(!writable()||o.composing||0!==e.button||!(e.target instanceof Element))return;const t=e.target;if(t.closest("button,input,textarea,select,a,video,audio,.math-node"))return;const r=Z(o),a=t.closest(".ywiki-image-gallery");if(a&&!t.closest(".ywiki-image-frame")&&!a.matches(".is-block-selected,.is-block-image-selected"))return void function gallerySelect(e,t){cancel(),t.preventDefault(),t.stopPropagation();const r=o.state.doc,i={x:t.clientX,y:t.clientY},a=Array.from(e.children).filter(e=>e instanceof HTMLElement&&e.classList.contains("ywiki-image-frame")),s=document.createElement("div");s.className="ywiki-gallery-marquee",s.hidden=!0,n.layer().append(s);let c=!1;const u=document.body.style.userSelect;document.body.style.userSelect="none";const move=e=>{if(!writable()||o.state.doc!==r)return void cancel();if(!c&&Math.hypot(e.clientX-i.x,e.clientY-i.y)<6)return;c=!0,s.hidden=!1;const t=Math.min(i.x,e.clientX),n=Math.min(i.y,e.clientY),l=Math.max(i.x,e.clientX),u=Math.max(i.y,e.clientY);Object.assign(s.style,{left:`${t}px`,top:`${n}px`,width:l-t+"px",height:u-n+"px"});for(const e of a){const o=e.getBoundingClientRect();e.classList.toggle("is-multi-picked",o.right>t&&o.left<l&&o.bottom>n&&o.top<u)}e.preventDefault()},up=()=>{const t=a.filter(e=>e.classList.contains("is-multi-picked"));if(cancel(),writable()&&o.state.doc===r)if(c){if(1===t.length)pickImage(t[0]);else if(t.length>1){const n=o.posAtDOM(e,0),l=o.state.doc.resolve(n),r=l.depth?l.before():n,i=t.map(e=>{const t=o.posAtDOM(e,0);return{from:t,to:t+o.state.doc.nodeAt(t).nodeSize}});ne(o,r,i),o.focus()}}else{const e=a.sort((e,t)=>Math.abs(i.x-(e.getBoundingClientRect().left+e.getBoundingClientRect().right)/2)-Math.abs(i.x-(t.getBoundingClientRect().left+t.getBoundingClientRect().right)/2))[0];e&&pickImage(e)}},key=e=>{"Escape"!==e.key||e.isComposing||(e.preventDefault(),cancel())};l=()=>{window.removeEventListener("mousemove",move,!0),window.removeEventListener("mouseup",up,!0),window.removeEventListener("keydown",key,!0),window.removeEventListener("blur",cancel),window.removeEventListener("pointercancel",cancel),s.remove(),document.body.style.userSelect=u;for(const e of a)e.classList.remove("is-multi-picked")},window.addEventListener("mousemove",move,!0),window.addEventListener("mouseup",up,!0),window.addEventListener("keydown",key,!0),window.addEventListener("blur",cancel),window.addEventListener("pointercancel",cancel)}(a,e);if(e.detail<2&&r.length){let n=t.closest(".ProseMirror > *");if((null==n?void 0:n.parentElement)!==o.dom&&(n=null),n){let l=-1;o.state.doc.forEach((e,t)=>{o.nodeDOM(t)===n&&(l=t)});const i=t.closest(".ywiki-image-frame"),a=r.find(e=>{var t;return e.from===l&&(!(null==(t=e.images)?void 0:t.length)||i&&e.images.some(e=>o.posAtDOM(i,0)===e.from))});if(a)return start(a.from,e),void e.stopPropagation()}}const s=t.closest("img.ywiki-image"),c=null==s?void 0:s.closest(".ywiki-image-frame");if(s&&c){const t=o.posAtDOM(c,0),r=i;pickedImage(t);{cancel();const o={x:e.clientX,y:e.clientY,time:Date.now()};let i=0;const move=e=>{i=Math.max(i,Math.hypot(e.clientX-o.x,e.clientY-o.y))},up=()=>{cancel(),i>=6?pickedImage(null):r===t&&writable()&&s.isConnected&&function isImagePreviewTap(e,t){return!(!Number.isFinite(e)||!Number.isFinite(t))&&!(e>=6)&&t<450}(i,Date.now()-o.time)&&n.onPreview(s)};l=()=>{window.removeEventListener("mousemove",move,!0),window.removeEventListener("mouseup",up,!0),window.removeEventListener("blur",cancel)},window.addEventListener("mousemove",move,!0),window.addEventListener("mouseup",up,!0),window.addEventListener("blur",cancel)}}else pickedImage(null);if(t===o.dom){const t=o.state.doc.lastChild,n=t?o.nodeDOM(o.state.doc.content.size-t.nodeSize):null;n instanceof HTMLElement&&e.clientY>=n.getBoundingClientRect().bottom-4&&(e.preventDefault(),o.dispatch(function focusTrailingParagraph(e){const t=e.tr,n=t.doc.lastChild;return"paragraph"===(null==n?void 0:n.type.name)&&0===n.content.size||t.insert(t.doc.content.size,e.schema.nodes.paragraph.create()),t.setSelection(d.atEnd(t.doc)).setStoredMarks(null)}(o.state)),o.focus())}}const nativeDrag=e=>{l&&o.state.selection instanceof ee&&e.preventDefault()},outside=e=>{e.target instanceof Element&&!t.contains(e.target)&&!e.target.closest(".img-toolbar,.img-preview-mask,.w-popover")&&pickedImage(null)},change=({transaction:e})=>{(e.docChanged||o.state.selection instanceof m&&o.state.selection.from!==i)&&pickedImage(null)};return e.on("transaction",change),document.addEventListener("mousedown",outside,!0),o.dom.addEventListener("mousedown",down,!0),o.dom.addEventListener("dragstart",nativeDrag,!0),{start:start,consumeClick:function consumeClick(){const e=Date.now()<r;return r=0,e},cancel:cancel,destroy(){cancel(),o.dom.removeEventListener("mousedown",down,!0),o.dom.removeEventListener("dragstart",nativeDrag,!0),e.off("transaction",change),document.removeEventListener("mousedown",outside,!0)}}}(h,y.current,{layer:()=>P.current.portal??y.current??document.body,onStart:()=>{Ge(!0),closeBlockMenu(),B(h.view),V(null),ke(null)},onEnd:()=>{st.current=!1,Ge(!1),z(e=>e+1)},onImagePick:Ze,onPreview:e=>{const t=e.getAttribute("src")??"";if(!t)return;const n=ve(e,b("图片预览"));qe({src:t,label:e.alt||b("图片预览"),items:n.items.length?n.items:[{src:t,label:e.alt||b("图片预览")}],index:n.items.length?n.index:0})}})),pt.markReady(),null==(o=(n=S.current).onReady)||o.call(n)}catch(e){ht(e)}return()=>{var e;c=!1,Ie.current=!0,null==(e=Ve.current)||e.destroy(),Ve.current=null,We.current=null,N.current=null,null==h||h.destroy(),p.remove(),Be([])}},[n.collabDoc,n.collabAwareness]),g(()=>{const e=N.current;e&&!e.isDestroyed&&e.view.dispatch(e.state.tr.setMeta("content-context",!0))},[L.portal,L.theme,L.accent,D.preferences,n.allowExport,f.version]),g(()=>{var e;const t=N.current;t&&(t.setEditable(!n.disabled,!1),t.view.dispatch(t.state.tr.setMeta("content-capabilities",!0))),n.disabled&&(null==(e=Ve.current)||e.cancel(),closeBlockMenu(),ke(null),V(null),Y(null),j(null),We.current=null,Ae(!1),Me(!1),K(null))},[n.disabled]),g(()=>{const e=N.current;if(e&&n.value&&!n.collabDoc&&n.value!==I.current)try{const t=(new C).validate(n.value,n.schemaVersion??E(n.value,M));e.schema.nodeFromJSON(t.doc).eq(e.state.doc)||e.commands.setContent(t.doc,{emitUpdate:!1}),$("")}catch(e){ht(e)}},[n.value,n.collabDoc,ht]),g(()=>{N.current&&ie(N.current,n.inlineComments??[])},[n.inlineComments,n.activeCommentId]);const snapshot=()=>{var e;return(null==(e=N.current)?void 0:e.getJSON())??S.current.value??S.current.defaultValue??rt};function hasPendingTasks(){var e;const t=N.current;return!!t&&(Pe.current>0||Ye.current.size>0||!!We.current||Se||Ee||t.view.composing||!!(null==(e=t.storage.mindMap)?void 0:e.composing)||!!t.view.dom.querySelector(".math-latex-input"))}function selectionRect(){const e=N.current;if(!e||e.state.selection.empty)return null;try{const t=e.view.coordsAtPos(e.state.selection.from);return{left:t.left,top:t.top,width:Math.max(1,t.right-t.left),height:Math.max(1,t.bottom-t.top)}}catch{return null}}function openLink(e=N.current){if(!e||!e.isEditable)return;const t=createLinkEditTarget(e.state);if(!t)return void vt(b("请先选中要添加链接的文字"));const n=e.view.coordsAtPos(t.from);ke({...t,tracked:t,mode:"edit",anchor:{left:n.left,top:n.top,width:Math.max(1,n.right-n.left),height:n.bottom-n.top}})}function rememberInsert(){const e=N.current;(null==e?void 0:e.isEditable)&&(We.current={from:e.state.selection.from,to:e.state.selection.from,valid:!0})}function requestPage(){T.current.references?(rememberInsert(),Ae(!0)):vt(b("未配置资源选择服务"))}function requestVideo(){rememberInsert(),De(""),Me(!0)}function insertPending(e){const t=N.current,n=We.current;We.current=null,!(null==t?void 0:t.isEditable)||!(null==n?void 0:n.valid)||n.from>t.state.doc.content.size?vt(b("插入目标已移除,请重新选择位置")):t.chain().focus().insertContentAt({from:n.from,to:n.to},e).run()}function requestFile(e){rememberInsert(),ze.current=e,Re.current&&(Re.current.accept="image"===e?"image/*":"",Re.current.click())}async function upload(e,t,n="auto"){var o,l,i,a;const s=N.current;if(!s||!s.isEditable)return;const c=T.current.scope,u=null==(o=T.current.attachments)?void 0:o.fetch,d=t??s.state.selection.from,m=S.current.uploadImage??(null==(l=T.current.attachments)?void 0:l.uploadImage);if("file"!==n&&e.length>1&&e.every(e=>e.type.startsWith("image/"))&&m){const t=crypto.randomUUID();Pe.current++,ae(s.view,{id:t,pos:d,kind:"image",label:b("{0} 张图片",{0:e.length}),file:e[0]});try{if(e.some(e=>e.size>20971520))throw new Error(b("图片不能超过 20 MB"));const n=await Promise.all(e.map(async e=>{const t=await m(e);return!s.isDestroyed&&s.isEditable&&T.current.scope===c&&r(t.id,e,c,u),s.schema.nodes.image.create({attachmentId:t.id,alt:t.alt})}));if(Ie.current||s.isDestroyed||!s.isEditable)return;const o=se(s.view,t);if(null===o)return;const l=s.state.tr;let i=o;for(let e=0;e<n.length;e+=10){const t=n.slice(e,e+10),o=1===t.length?t[0]:s.schema.nodes.imageGallery.create({height:120},t),r=l.mapping.maps.length,a=i;ce(l,a,o),i=l.mapping.slice(r).map(a,1)}s.view.dispatch(l),ue(s.view,t)}catch(e){Ie.current||s.isDestroyed||(ue(s.view,t),ht(e))}finally{s.isDestroyed||ue(s.view,t),Pe.current--,Ie.current||z(e=>e+1)}return}const p=e.map(e=>({file:e,id:crypto.randomUUID()}));for(const{file:e,id:t}of p)ae(s.view,{id:t,pos:d,kind:e.type.startsWith("image/")&&"file"!==n?"image":"file",label:e.name,file:e});Pe.current+=p.length,z(e=>e+1);for(const{file:e,id:t}of p){if(s.isDestroyed){Pe.current--;continue}const o=e.type.startsWith("image/")&&"file"!==n,l=o?S.current.uploadImage??(null==(i=T.current.attachments)?void 0:i.uploadImage):S.current.uploadFile??(null==(a=T.current.attachments)?void 0:a.uploadFile);try{if(!l)throw new Error(b("未配置附件上传服务"));if(e.size>20971520)throw new Error(b("附件不能超过 20 MB"));const n=await l(e);if(Ie.current||s.isDestroyed||!s.isEditable||null===se(s.view,t))continue;r(n.id,e,c,u);const i=o?{attachmentId:n.id,alt:"alt"in n?n.alt:e.name}:{attachmentId:n.id,filename:e.name,mimeType:e.type,size:e.size};de(s.view,t,{type:o?"image":"fileAttachment",attrs:i})}catch(e){s.isDestroyed||Ie.current||(ue(s.view,t),ht(e))}finally{s.isDestroyed||ue(s.view,t),Pe.current--,Ie.current||z(e=>e+1)}}}x(c,()=>({isReady:pt.isReady,whenReady:pt.whenReady,getJSON:snapshot,getSnapshot:snapshot,async prepareSnapshot(){if(!N.current)throw new Error(b("文档尚未就绪"));if(hasPendingTasks())throw new Error(b("请先完成上传、公式或组合输入"));const e=snapshot(),t=E(e,S.current.schemaVersion??M);return(new C).validate(e,t),{doc:e,schemaVersion:t}},hasPendingTasks:hasPendingTasks,focus:()=>{var e;null==(e=N.current)||e.commands.focus()},undo:()=>{var e;return(null==(e=N.current)?void 0:e.commands.undo())??!1},redo:()=>{var e;return(null==(e=N.current)?void 0:e.commands.redo())??!1},hasSelection:()=>!!N.current&&!N.current.state.selection.empty,getSelectionRect:selectionRect,getSelectionForAnchor(){const e=N.current;if(!e||e.state.selection.empty)return null;const{from:t,to:n}=e.state.selection,o=e.state.doc.textBetween(t,n,"\n");return o.trim()?{quote:o,prefix:e.state.doc.textBetween(Math.max(0,t-64),t,"\n"),suffix:e.state.doc.textBetween(n,Math.min(e.state.doc.content.size,n+64),"\n"),start:e.state.doc.textBetween(0,t,"\n").length}:null},setSelection(e,t=e){var n;null==(n=N.current)||n.commands.setTextSelection({from:e,to:t})},setContent(e){var t;if(!S.current.collabDoc)try{const n=e??rt,o=(new C).validate(n,S.current.schemaVersion??E(n,M));null==(t=N.current)||t.commands.setContent(o.doc,{emitUpdate:!1}),$("")}catch(e){ht(e)}}}));const ft=N.current,gt=null==ft?void 0:ft.state.selection,bt=gt instanceof m&&"image"===gt.node.type.name?{pos:gt.from,node:gt.node}:null,wt=!!F;let yt=null,kt=null;if(ft&&y.current&&!n.disabled&&!wt&&!_&&!me&&!je){const e=y.current.getBoundingClientRect();if(gt instanceof u&&!gt.empty)try{const t=ft.view.coordsAtPos(gt.from),n=ft.view.coordsAtPos(gt.to);yt={x:(t.left+n.right)/2-e.left,y:Math.min(t.top,n.top)-e.top-8}}catch{yt=null}if(bt&&Ke===bt.pos)try{const t=ft.view.nodeDOM(bt.pos);if(t instanceof HTMLElement){const n=t.getBoundingClientRect(),o=n.top-e.top-44;kt={x:n.left-e.left+n.width/2,y:o<4?n.bottom-e.top+10:o}}}catch{kt=null}}const xt=_?U??W:W,Ct=xt&&y.current?function blockAffordanceStyle(e,t,n){const o="plus"===n?nt:ot,l=e.contentLeft-8-o.width,r=t?8-t.getBoundingClientRect().left:8;return{left:Math.max(l,r),top:e.top+(26-o.height)/2}}(xt,y.current,xt.isEmpty?"plus":"chip"):{left:0,top:0},Et=!!xt&&!n.disabled&&(!_||it.current)&&!je&&(!Q||_)&&"table"!==xt.type&&!F&&!yt&&!He&&!me;/* @__PURE__ */
113
+ return t("div",{ref:y,className:`wr-document editor-shell${bt&&Ke===bt.pos?" img-pick-active":""}${n.className?` ${n.className}`:""}`,"data-block-select-band":!0,children:[H&&/* @__PURE__ */e("p",{role:"status",children:H}),q&&/* @__PURE__ */e("p",{role:"alert",children:q}),
114
+ /* @__PURE__ */e("div",{ref:w,className:"wr-document-mount"}),yt&&ft&&!bt&&/* @__PURE__ */e(SelectionToolbar,{editor:ft,x:yt.x,y:yt.y,canComment:n.canComment,onRequestComment:()=>{var e,t;return null==(t=(e=S.current).onRequestComment)?void 0:t.call(e)},onRequestLink:()=>openLink(ft),onRemoveLink:()=>{ft.chain().focus().unsetLink().run()}}),kt&&bt&&ft&&/* @__PURE__ */t("div",{className:"img-toolbar",role:"toolbar","aria-label":b("图片操作"),style:{left:kt.x,top:kt.y},onMouseDown:e=>e.preventDefault(),children:[["left","center","right"].map(t=>/* @__PURE__ */e("button",{type:"button",className:"it-btn"+(bt.node.attrs.align===t?" on":""),"aria-label":b("图片{0}对齐",{0:b("left"===t?"左":"center"===t?"居中":"右")}),title:b("图片{0}对齐",{0:b("left"===t?"左":"center"===t?"居中":"右")}),"aria-pressed":bt.node.attrs.align===t,onClick:()=>function imageAlign(e){ft&&bt&&ft.isEditable&&(ft.view.dispatch(ft.state.tr.setNodeMarkup(bt.pos,void 0,{...bt.node.attrs,align:e})),ft.commands.focus())}(t),children:/* @__PURE__ */e(xe,{name:`align-${t}`,size:16})},t)),
115
+ /* @__PURE__ */e("span",{className:"it-sep","aria-hidden":"true"}),
116
+ /* @__PURE__ */e("button",{type:"button",className:"it-btn","aria-label":b("编辑图注"),title:b("编辑图注"),onClick:()=>{const e=ft.view.nodeDOM(bt.pos);e instanceof HTMLElement&&e.dispatchEvent(new CustomEvent("ywiki-caption-edit"))},children:/* @__PURE__ */e(xe,{name:"text",size:16})}),
117
+ /* @__PURE__ */e("button",{type:"button",className:"it-btn","aria-label":b("查看大图"),title:b("查看大图"),onClick:function previewImage(){if(!ft||!bt)return;const e=ft.view.nodeDOM(bt.pos),t=e instanceof HTMLElement?e.querySelector("img"):null,n=(null==t?void 0:t.getAttribute("src"))??"";if(!n)return;const{items:o,index:l}=ve(t,b("图片预览"));qe({src:n,label:bt.node.attrs.alt?String(bt.node.attrs.alt):b("图片预览"),items:o.length?o:[{src:n,label:b("图片预览")}],index:o.length?l:0})},children:/* @__PURE__ */e(xe,{name:"zoom-in",size:16})}),
118
+ /* @__PURE__ */e("span",{className:"it-sep","aria-hidden":"true"}),
119
+ /* @__PURE__ */e("button",{type:"button",className:"it-btn danger","aria-label":b("删除图片"),title:b("删除图片"),onClick:function removeImage(){if(!ft||!bt||!ft.isEditable)return;const e=ft.state.doc.resolve(bt.pos);if("imageGallery"===e.parent.type.name){const t=e.before(),n=t+e.parent.nodeSize,o=new ee(ft.state.doc.resolve(t),ft.state.doc.resolve(n),[{kind:"images",from:t,to:n,images:[{from:bt.pos,to:bt.pos+bt.node.nodeSize}]}]),l=ft.state.tr;return o.replace(l),ft.view.dispatch(l),void ft.view.focus()}ft.chain().focus().deleteRange({from:bt.pos,to:bt.pos+bt.node.nodeSize}).run()},children:/* @__PURE__ */e(xe,{name:"trash",size:16})})]}),xt&&Et&&(xt.isEmpty?/* @__PURE__ */e(BlockPlus,{info:xt,style:Ct,expanded:_,onMouseEnter:()=>enterQuickMenu(xt),onMouseLeave:()=>{var e;return null==(e=ut.current)?void 0:e.leave("trigger")},onClick:()=>openQuickMenu(xt,"insert-below")}):/* @__PURE__ */e(BlockChip,{editor:ft,info:xt,style:Ct,expanded:_,onMouseEnter:()=>enterQuickMenu(xt),onMouseLeave:()=>{var e;return null==(e=ut.current)?void 0:e.leave("trigger")},onOpen:()=>openQuickMenu(xt,tt.some(e=>e===xt.type)?"format":void 0),onOpenFull:()=>{var e;(null==(e=Ve.current)?void 0:e.consumeClick())||openQuickMenu(xt,void 0)},onHandleMouseDown:e=>{var t;0===e.button&&(null==ft?void 0:ft.isEditable)&&!ft.view.composing&&(st.current=!0,null==(t=Ve.current)||t.start(xt.pos,e.nativeEvent,()=>{}))}})),_&&U&&ft&&/* @__PURE__ */e("div",{style:{position:"absolute",inset:0,zIndex:45,pointerEvents:"none"},children:/* @__PURE__ */e(BlockMenu,{editor:ft,info:U,canComment:n.canComment,panelOnly:X,onClose:closeBlockMenu,onRegion:e=>{var t,n;return e?null==(t=ut.current)?void 0:t.enter("menu"):null==(n=ut.current)?void 0:n.leave("menu")},onPickFile:e=>{closeBlockMenu(),requestFile(e)},onPickPage:()=>{closeBlockMenu(),requestPage()},onPickVideo:()=>{closeBlockMenu(),requestVideo()},onRequestComment:()=>{var e,t;const n=ft.state.doc.nodeAt(U.pos);n&&ft.commands.setTextSelection({from:U.pos+1,to:U.pos+n.nodeSize-1}),null==(t=(e=S.current).onRequestComment)||t.call(e)},onNotice:vt,onCopyLink:()=>{var e;const t=n.pageId?null==(e=D.references)?void 0:e.resolve(n.pageId,"当前内容"):null;(null==t?void 0:t.href)?navigator.clipboard.writeText(t.href).catch(()=>vt(b("无法写入剪贴板"))):vt(b("宿主未配置当前内容链接"))},onSharePage:n.onSharePage,onConvertToSubpage:n.onConvertToSubpage,onSaveAsTemplate:n.onSaveAsTemplate})}),F&&ft&&/* @__PURE__ */e(SlashMenu,{editor:ft,from:F.from,query:F.query,x:F.x,y:F.y,onClose:()=>V(null),onPickFile:requestFile,onPickPage:requestPage,onPickVideo:requestVideo}),ft&&!n.disabled&&!je&&/* @__PURE__ */e(TableControls,{editor:ft,hover:Q,revision:R,onNotice:vt,onBlockDrag:(e,t,n)=>{var o;return null==(o=Ve.current)?void 0:o.start(e,t,n)}}),
120
+ /* @__PURE__ */e("input",{ref:Re,hidden:!0,type:"file",multiple:!0,onChange:e=>{const t=We.current;We.current=null,(null==t?void 0:t.valid)?e.target.files&&upload([...e.target.files],t.from,ze.current):vt(b("插入目标已移除,请重新选择位置")),e.target.value=""}}),me&&/* @__PURE__ */e(LinkPopover,{target:me,onClose:()=>ke(null),onEdit:()=>ke({...me,mode:"edit"}),onSave:e=>applyLink(e),onRemove:()=>applyLink(null)},me.mode),Ee&&/* @__PURE__ */e(VideoEmbedDialog,{initialUrl:Ne,onClose:()=>{We.current=null,Me(!1)},onInsert:e=>{insertPending({type:"videoEmbed",attrs:e}),Me(!1)}}),Se&&/* @__PURE__ */e(PagePicker,{spaceId:n.spaceId,excludeId:n.pageId,title:b("选择要引用的页面"),onClose:()=>{We.current=null,Ae(!1)},onPick:(e,t)=>{insertPending({type:"pageLink",attrs:{pageId:e,label:t}}),Ae(!1)}}),He&&/* @__PURE__ */e(fe,{src:He.src,label:He.label,items:He.items,index:He.index,onClose:()=>qe(null)}),Te.map(e=>p(e.content,e.dom,e.id))]});function closeBlockMenu(){var e;null==(e=ut.current)||e.reset(),it.current=!1,J(!1),j(null),G(void 0)}function applyLink(e){var t;const n=N.current,o=null==(t=Ce.current)?void 0:t.tracked;if(!(null==n?void 0:n.isEditable)||!o||!function linkEditTargetCurrent(e,t){return t.valid&&t.from<t.to&&t.from>=0&&t.to<=e.doc.content.size&&JSON.stringify(e.doc.slice(t.from,t.to).toJSON())===t.contentJSON}(n.state,o))return vt(b("目标文字或链接已变化,请重新选择")),void ke(null);const l=n.chain().setTextSelection({from:o.from,to:o.to}).focus(void 0,{scrollIntoView:!1});e?l.setLink({href:e}).run():l.unsetLink().run(),ke(null)}});let at;function sameDocument(e,t){if(!e||!t)return e===t;try{return at??(at=c(re())),at.nodeFromJSON(e).eq(at.nodeFromJSON(t))}catch{return!1}}export{De as ANCHORED_POPOVER_ARROW_INSET,Se as ANCHORED_POPOVER_GAP,Ae as ANCHORED_POPOVER_VIEW_MARGIN,me as COLLAB_FIELD,it as DocumentEditor,Ke as DocumentExportMenu,re as coreExtensions,Le as placeAnchoredPopover,sameDocument};