@dou-ui/editor 0.0.0-stage → 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/dist/catalog.js +18 -0
- package/dist/catalog.js.map +1 -0
- package/dist/code/code-editor-block.js +6 -0
- package/dist/code/code-editor-block.js.map +1 -0
- package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js +116 -0
- package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/index.js +69 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/languages.js +51 -0
- package/dist/code/languages.js.map +1 -0
- package/dist/code/view.js +255 -0
- package/dist/code/view.js.map +1 -0
- package/dist/collaboration/binding.js +381 -0
- package/dist/collaboration/binding.js.map +1 -0
- package/dist/collaboration/cursors.js +150 -0
- package/dist/collaboration/cursors.js.map +1 -0
- package/dist/collaboration/index.js +108 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/order.js +88 -0
- package/dist/collaboration/order.js.map +1 -0
- package/dist/collaboration/table.js +234 -0
- package/dist/collaboration/table.js.map +1 -0
- package/dist/collaboration/text.js +91 -0
- package/dist/collaboration/text.js.map +1 -0
- package/dist/components/block-handle.js +6 -0
- package/dist/components/block-handle.js.map +1 -0
- package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js +474 -0
- package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/bubble-toolbar.js +6 -0
- package/dist/components/bubble-toolbar.js.map +1 -0
- package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js +381 -0
- package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/comment-button.js +6 -0
- package/dist/components/comment-button.js.map +1 -0
- package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js +83 -0
- package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/comment-card.js +6 -0
- package/dist/components/comment-card.js.map +1 -0
- package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js +104 -0
- package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/menu-escape.js +25 -0
- package/dist/components/menu-escape.js.map +1 -0
- package/dist/components/search-bar.js +6 -0
- package/dist/components/search-bar.js.map +1 -0
- package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js +192 -0
- package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/slash-menu.js +6 -0
- package/dist/components/slash-menu.js.map +1 -0
- package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js +164 -0
- package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/core/align.js +94 -0
- package/dist/core/align.js.map +1 -0
- package/dist/core/block-selection.js +155 -0
- package/dist/core/block-selection.js.map +1 -0
- package/dist/core/blocks.js +132 -0
- package/dist/core/blocks.js.map +1 -0
- package/dist/core/changes.js +85 -0
- package/dist/core/changes.js.map +1 -0
- package/dist/core/colors.js +16 -0
- package/dist/core/colors.js.map +1 -0
- package/dist/core/commands.js +379 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/comments.js +186 -0
- package/dist/core/comments.js.map +1 -0
- package/dist/core/compare.js +106 -0
- package/dist/core/compare.js.map +1 -0
- package/dist/core/convert.js +28 -0
- package/dist/core/convert.js.map +1 -0
- package/dist/core/diff.js +141 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/editor.js +138 -0
- package/dist/core/editor.js.map +1 -0
- package/dist/core/extension.js +99 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/file-drop.js +56 -0
- package/dist/core/file-drop.js.map +1 -0
- package/dist/core/highlight.js +120 -0
- package/dist/core/highlight.js.map +1 -0
- package/dist/core/input-rules.js +61 -0
- package/dist/core/input-rules.js.map +1 -0
- package/dist/core/keymap.js +38 -0
- package/dist/core/keymap.js.map +1 -0
- package/dist/core/model.js +196 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/output.js +263 -0
- package/dist/core/output.js.map +1 -0
- package/dist/core/plugins.js +102 -0
- package/dist/core/plugins.js.map +1 -0
- package/dist/core/prepare.js +102 -0
- package/dist/core/prepare.js.map +1 -0
- package/dist/core/schema.js +158 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/search.js +197 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/selection.js +16 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/slash.js +83 -0
- package/dist/core/slash.js.map +1 -0
- package/dist/core/transaction.js +311 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/define-block.js +177 -0
- package/dist/define-block.js.map +1 -0
- package/dist/document.js +157 -0
- package/dist/document.js.map +1 -0
- package/dist/dom.js +48 -0
- package/dist/dom.js.map +1 -0
- package/dist/editor.js +33 -0
- package/dist/editor.js.map +1 -0
- package/dist/editor.vue_vue_type_script_setup_true_lang.js +564 -0
- package/dist/editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/editor2.js +6 -0
- package/dist/editor2.js.map +1 -0
- package/dist/extensions/align/align-menu.js +6 -0
- package/dist/extensions/align/align-menu.js.map +1 -0
- package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js +82 -0
- package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/align/align-sub.js +6 -0
- package/dist/extensions/align/align-sub.js.map +1 -0
- package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js +57 -0
- package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/align/commands.js +40 -0
- package/dist/extensions/align/commands.js.map +1 -0
- package/dist/extensions/align/index.js +27 -0
- package/dist/extensions/align/index.js.map +1 -0
- package/dist/extensions/attachment/attachment-block.js +6 -0
- package/dist/extensions/attachment/attachment-block.js.map +1 -0
- package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js +119 -0
- package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/attachment/files.js +66 -0
- package/dist/extensions/attachment/files.js.map +1 -0
- package/dist/extensions/attachment/index.js +132 -0
- package/dist/extensions/attachment/index.js.map +1 -0
- package/dist/extensions/blocks.js +170 -0
- package/dist/extensions/blocks.js.map +1 -0
- package/dist/extensions/callout/callout-block.js +6 -0
- package/dist/extensions/callout/callout-block.js.map +1 -0
- package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js +71 -0
- package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/callout/html.js +61 -0
- package/dist/extensions/callout/html.js.map +1 -0
- package/dist/extensions/callout/index.js +52 -0
- package/dist/extensions/callout/index.js.map +1 -0
- package/dist/extensions/callout/tones.js +12 -0
- package/dist/extensions/callout/tones.js.map +1 -0
- package/dist/extensions/code-block/code-block-view.js +6 -0
- package/dist/extensions/code-block/code-block-view.js.map +1 -0
- package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js +87 -0
- package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/code-block/index.js +98 -0
- package/dist/extensions/code-block/index.js.map +1 -0
- package/dist/extensions/code-block/languages.js +116 -0
- package/dist/extensions/code-block/languages.js.map +1 -0
- package/dist/extensions/colors/color-menu.js +6 -0
- package/dist/extensions/colors/color-menu.js.map +1 -0
- package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js +113 -0
- package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/colors/index.js +49 -0
- package/dist/extensions/colors/index.js.map +1 -0
- package/dist/extensions/colors/palette.js +26 -0
- package/dist/extensions/colors/palette.js.map +1 -0
- package/dist/extensions/editor-kit.js +35 -0
- package/dist/extensions/editor-kit.js.map +1 -0
- package/dist/extensions/embed/embed-block.js +6 -0
- package/dist/extensions/embed/embed-block.js.map +1 -0
- package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js +210 -0
- package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/embed/index.js +118 -0
- package/dist/extensions/embed/index.js.map +1 -0
- package/dist/extensions/embed/targets.js +79 -0
- package/dist/extensions/embed/targets.js.map +1 -0
- package/dist/extensions/formatting.js +153 -0
- package/dist/extensions/formatting.js.map +1 -0
- package/dist/extensions/image/image-block.js +6 -0
- package/dist/extensions/image/image-block.js.map +1 -0
- package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js +206 -0
- package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/image/index.js +125 -0
- package/dist/extensions/image/index.js.map +1 -0
- package/dist/extensions/image/uploads.js +96 -0
- package/dist/extensions/image/uploads.js.map +1 -0
- package/dist/extensions/list-html.js +58 -0
- package/dist/extensions/list-html.js.map +1 -0
- package/dist/extensions/lists.js +182 -0
- package/dist/extensions/lists.js.map +1 -0
- package/dist/extensions/mention/index.js +56 -0
- package/dist/extensions/mention/index.js.map +1 -0
- package/dist/extensions/mention/mention-menu.js +6 -0
- package/dist/extensions/mention/mention-menu.js.map +1 -0
- package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js +132 -0
- package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/mention/state.js +113 -0
- package/dist/extensions/mention/state.js.map +1 -0
- package/dist/extensions/table/commands.js +356 -0
- package/dist/extensions/table/commands.js.map +1 -0
- package/dist/extensions/table/html.js +134 -0
- package/dist/extensions/table/html.js.map +1 -0
- package/dist/extensions/table/index.js +56 -0
- package/dist/extensions/table/index.js.map +1 -0
- package/dist/extensions/table/plugin.js +81 -0
- package/dist/extensions/table/plugin.js.map +1 -0
- package/dist/extensions/table/schema.js +258 -0
- package/dist/extensions/table/schema.js.map +1 -0
- package/dist/extensions/table/table-controls.js +6 -0
- package/dist/extensions/table/table-controls.js.map +1 -0
- package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js +698 -0
- package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/extensions/table/view.js +19 -0
- package/dist/extensions/table/view.js.map +1 -0
- package/dist/extensions/toggle/index.js +87 -0
- package/dist/extensions/toggle/index.js.map +1 -0
- package/dist/extensions/toggle/state.js +120 -0
- package/dist/extensions/toggle/state.js.map +1 -0
- package/dist/extensions/toggle/toggle-block.js +6 -0
- package/dist/extensions/toggle/toggle-block.js.map +1 -0
- package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js +42 -0
- package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/locale.js +522 -0
- package/dist/locale.js.map +1 -0
- package/dist/markdown/index.js +24 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +8 -0
- package/dist/types/catalog.d.ts +8 -0
- package/dist/types/code/code-editor-block.vue.d.ts +17 -0
- package/dist/types/code/index.d.ts +18 -0
- package/dist/types/code/languages.d.ts +7 -0
- package/dist/types/code/types.d.ts +37 -0
- package/dist/types/code/view.d.ts +12 -0
- package/dist/types/collaboration/binding.d.ts +32 -0
- package/dist/types/collaboration/cursors.d.ts +26 -0
- package/dist/types/collaboration/index.d.ts +47 -0
- package/dist/types/collaboration/order.d.ts +20 -0
- package/dist/types/collaboration/table.d.ts +25 -0
- package/dist/types/collaboration/text.d.ts +9 -0
- package/dist/types/components/block-handle.vue.d.ts +3 -0
- package/dist/types/components/bubble-toolbar.vue.d.ts +7 -0
- package/dist/types/components/comment-button.vue.d.ts +3 -0
- package/dist/types/components/comment-card.vue.d.ts +3 -0
- package/dist/types/components/menu-escape.d.ts +7 -0
- package/dist/types/components/search-bar.vue.d.ts +22 -0
- package/dist/types/components/slash-menu.vue.d.ts +11 -0
- package/dist/types/core/align.d.ts +16 -0
- package/dist/types/core/block-selection.d.ts +22 -0
- package/dist/types/core/blocks.d.ts +52 -0
- package/dist/types/core/changes.d.ts +14 -0
- package/dist/types/core/colors.d.ts +4 -0
- package/dist/types/core/commands.d.ts +61 -0
- package/dist/types/core/comments.d.ts +44 -0
- package/dist/types/core/compare.d.ts +10 -0
- package/dist/types/core/convert.d.ts +12 -0
- package/dist/types/core/diff.d.ts +49 -0
- package/dist/types/core/editor.d.ts +64 -0
- package/dist/types/core/extension.d.ts +167 -0
- package/dist/types/core/file-drop.d.ts +19 -0
- package/dist/types/core/highlight.d.ts +32 -0
- package/dist/types/core/input-rules.d.ts +21 -0
- package/dist/types/core/keymap.d.ts +8 -0
- package/dist/types/core/model.d.ts +158 -0
- package/dist/types/core/output.d.ts +64 -0
- package/dist/types/core/plugins.d.ts +24 -0
- package/dist/types/core/prepare.d.ts +23 -0
- package/dist/types/core/schema.d.ts +38 -0
- package/dist/types/core/search.d.ts +83 -0
- package/dist/types/core/selection.d.ts +8 -0
- package/dist/types/core/slash.d.ts +21 -0
- package/dist/types/core/transaction.d.ts +97 -0
- package/dist/types/define-block.d.ts +66 -0
- package/dist/types/document.d.ts +46 -0
- package/dist/types/dom.d.ts +17 -0
- package/dist/types/editor.d.ts +252 -0
- package/dist/types/editor.vue.d.ts +210 -0
- package/dist/types/extensions/align/align-menu.vue.d.ts +3 -0
- package/dist/types/extensions/align/align-sub.vue.d.ts +7 -0
- package/dist/types/extensions/align/commands.d.ts +13 -0
- package/dist/types/extensions/align/index.d.ts +7 -0
- package/dist/types/extensions/attachment/attachment-block.vue.d.ts +17 -0
- package/dist/types/extensions/attachment/files.d.ts +6 -0
- package/dist/types/extensions/attachment/index.d.ts +20 -0
- package/dist/types/extensions/blocks.d.ts +8 -0
- package/dist/types/extensions/callout/callout-block.vue.d.ts +14 -0
- package/dist/types/extensions/callout/html.d.ts +4 -0
- package/dist/types/extensions/callout/index.d.ts +3 -0
- package/dist/types/extensions/callout/tones.d.ts +8 -0
- package/dist/types/extensions/code-block/code-block-view.vue.d.ts +16 -0
- package/dist/types/extensions/code-block/index.d.ts +19 -0
- package/dist/types/extensions/code-block/languages.d.ts +9 -0
- package/dist/types/extensions/colors/color-menu.vue.d.ts +3 -0
- package/dist/types/extensions/colors/index.d.ts +7 -0
- package/dist/types/extensions/colors/palette.d.ts +7 -0
- package/dist/types/extensions/editor-kit.d.ts +37 -0
- package/dist/types/extensions/embed/embed-block.vue.d.ts +16 -0
- package/dist/types/extensions/embed/index.d.ts +18 -0
- package/dist/types/extensions/embed/targets.d.ts +32 -0
- package/dist/types/extensions/formatting.d.ts +8 -0
- package/dist/types/extensions/image/image-block.vue.d.ts +16 -0
- package/dist/types/extensions/image/index.d.ts +16 -0
- package/dist/types/extensions/image/uploads.d.ts +35 -0
- package/dist/types/extensions/list-html.d.ts +11 -0
- package/dist/types/extensions/lists.d.ts +6 -0
- package/dist/types/extensions/mention/index.d.ts +11 -0
- package/dist/types/extensions/mention/mention-menu.vue.d.ts +7 -0
- package/dist/types/extensions/mention/state.d.ts +28 -0
- package/dist/types/extensions/table/commands.d.ts +69 -0
- package/dist/types/extensions/table/html.d.ts +4 -0
- package/dist/types/extensions/table/index.d.ts +8 -0
- package/dist/types/extensions/table/plugin.d.ts +4 -0
- package/dist/types/extensions/table/schema.d.ts +23 -0
- package/dist/types/extensions/table/table-controls.vue.d.ts +3 -0
- package/dist/types/extensions/table/view.d.ts +13 -0
- package/dist/types/extensions/toggle/index.d.ts +7 -0
- package/dist/types/extensions/toggle/state.d.ts +17 -0
- package/dist/types/extensions/toggle/toggle-block.vue.d.ts +14 -0
- package/dist/types/index.d.ts +266 -0
- package/dist/types/locale.d.ts +195 -0
- package/dist/types/markdown/index.d.ts +14 -0
- package/dist/types/use-comments.d.ts +17 -0
- package/dist/use-comments.js +141 -0
- package/dist/use-comments.js.map +1 -0
- package/package.json +98 -4
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createId } from "../../core/model.js";
|
|
2
|
+
import { fitColumns, isHeaderAt } from "./schema.js";
|
|
3
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
4
|
+
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
5
|
+
import { TableMap } from "prosemirror-tables";
|
|
6
|
+
// src/extensions/table/plugin.ts
|
|
7
|
+
/** Tables only sit in blocks; text and other content need no visit. */
|
|
8
|
+
var descends = (node) => node.type.name === "block" || node.type.name === "blockGroup";
|
|
9
|
+
var sameColumns = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
10
|
+
/**
|
|
11
|
+
* Keep every table well formed after each change: a unique id on every
|
|
12
|
+
* row, one column record per column, and header cells exactly where the
|
|
13
|
+
* table's `headerRow` / `headerColumn` say.
|
|
14
|
+
*/
|
|
15
|
+
function normalize(doc, tr) {
|
|
16
|
+
const rowIds = /* @__PURE__ */ new Set();
|
|
17
|
+
doc.descendants((node, pos) => {
|
|
18
|
+
if (node.type.spec.tableRole !== "table") return descends(node);
|
|
19
|
+
const map = TableMap.get(node);
|
|
20
|
+
const columns = fitColumns(node.attrs.columns, map.width);
|
|
21
|
+
if (!sameColumns(columns, node.attrs.columns)) tr.setNodeAttribute(pos, "columns", columns);
|
|
22
|
+
const start = pos + 1;
|
|
23
|
+
node.forEach((row, offset) => {
|
|
24
|
+
const id = row.attrs.id;
|
|
25
|
+
if (!id || rowIds.has(id)) tr.setNodeAttribute(start + offset, "id", createId());
|
|
26
|
+
else rowIds.add(id);
|
|
27
|
+
});
|
|
28
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29
|
+
for (const cellPos of map.map) {
|
|
30
|
+
if (seen.has(cellPos)) continue;
|
|
31
|
+
seen.add(cellPos);
|
|
32
|
+
const rect = map.findCell(cellPos);
|
|
33
|
+
const cell = node.nodeAt(cellPos);
|
|
34
|
+
const type = isHeaderAt(node.attrs, rect.top, rect.left) ? node.type.schema.nodes.tableHeader : node.type.schema.nodes.tableCell;
|
|
35
|
+
if (cell.type !== type) tr.setNodeMarkup(start + cellPos, type, cell.attrs);
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** Text alignment per column, laid on the cells. */
|
|
41
|
+
function alignments(doc) {
|
|
42
|
+
const decorations = [];
|
|
43
|
+
doc.descendants((node, pos) => {
|
|
44
|
+
if (node.type.spec.tableRole !== "table") return descends(node);
|
|
45
|
+
const columns = node.attrs.columns;
|
|
46
|
+
if (!columns.some((column) => column.align)) return false;
|
|
47
|
+
const map = TableMap.get(node);
|
|
48
|
+
const seen = /* @__PURE__ */ new Set();
|
|
49
|
+
for (const cellPos of map.map) {
|
|
50
|
+
if (seen.has(cellPos)) continue;
|
|
51
|
+
seen.add(cellPos);
|
|
52
|
+
const align = columns[map.findCell(cellPos).left]?.align;
|
|
53
|
+
if (!align) continue;
|
|
54
|
+
const cell = node.nodeAt(cellPos);
|
|
55
|
+
const from = pos + 1 + cellPos;
|
|
56
|
+
decorations.push(Decoration.node(from, from + cell.nodeSize, { style: `text-align: ${align}` }));
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
});
|
|
60
|
+
return DecorationSet.create(doc, decorations);
|
|
61
|
+
}
|
|
62
|
+
var tableKey = new PluginKey("dou-editor-table");
|
|
63
|
+
function tablePlugin() {
|
|
64
|
+
return new Plugin({
|
|
65
|
+
key: tableKey,
|
|
66
|
+
state: {
|
|
67
|
+
init: (_config, state) => alignments(state.doc),
|
|
68
|
+
apply: (tr, value, _old, state) => tr.docChanged ? alignments(state.doc) : value
|
|
69
|
+
},
|
|
70
|
+
appendTransaction(trs, _old, state) {
|
|
71
|
+
if (!trs.some((tr) => tr.docChanged)) return null;
|
|
72
|
+
const tr = state.tr;
|
|
73
|
+
normalize(state.doc, tr);
|
|
74
|
+
return tr.docChanged ? tr : null;
|
|
75
|
+
},
|
|
76
|
+
props: { decorations: (state) => tableKey.getState(state) }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export { tableKey, tablePlugin };
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","names":[],"sources":["../../../src/extensions/table/plugin.ts"],"sourcesContent":["import type { Node as PMNode } from 'prosemirror-model'\nimport { Plugin, PluginKey, type Transaction } from 'prosemirror-state'\nimport { TableMap } from 'prosemirror-tables'\nimport { Decoration, DecorationSet } from 'prosemirror-view'\nimport { createId } from '../../core/model'\nimport { fitColumns, isHeaderAt } from './schema'\n\n/** Tables only sit in blocks; text and other content need no visit. */\nconst descends = (node: PMNode) => node.type.name === 'block' || node.type.name === 'blockGroup'\n\nconst sameColumns = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b)\n\n/**\n * Keep every table well formed after each change: a unique id on every\n * row, one column record per column, and header cells exactly where the\n * table's `headerRow` / `headerColumn` say.\n */\nfunction normalize(doc: PMNode, tr: Transaction) {\n const rowIds = new Set<string>()\n doc.descendants((node, pos) => {\n if (node.type.spec.tableRole !== 'table') return descends(node)\n const map = TableMap.get(node)\n const columns = fitColumns(node.attrs.columns, map.width)\n if (!sameColumns(columns, node.attrs.columns)) tr.setNodeAttribute(pos, 'columns', columns)\n const start = pos + 1\n node.forEach((row, offset) => {\n const id = row.attrs.id as string | null\n if (!id || rowIds.has(id)) tr.setNodeAttribute(start + offset, 'id', createId())\n else rowIds.add(id)\n })\n const seen = new Set<number>()\n for (const cellPos of map.map) {\n if (seen.has(cellPos)) continue\n seen.add(cellPos)\n const rect = map.findCell(cellPos)\n const cell = node.nodeAt(cellPos)!\n const header = isHeaderAt(node.attrs, rect.top, rect.left)\n const type = header ? node.type.schema.nodes.tableHeader! : node.type.schema.nodes.tableCell!\n if (cell.type !== type) tr.setNodeMarkup(start + cellPos, type, cell.attrs)\n }\n return false\n })\n}\n\n/** Text alignment per column, laid on the cells. */\nfunction alignments(doc: PMNode): DecorationSet {\n const decorations: Decoration[] = []\n doc.descendants((node, pos) => {\n if (node.type.spec.tableRole !== 'table') return descends(node)\n const columns = node.attrs.columns as { align?: string }[]\n if (!columns.some((column) => column.align)) return false\n const map = TableMap.get(node)\n const seen = new Set<number>()\n for (const cellPos of map.map) {\n if (seen.has(cellPos)) continue\n seen.add(cellPos)\n const align = columns[map.findCell(cellPos).left]?.align\n if (!align) continue\n const cell = node.nodeAt(cellPos)!\n const from = pos + 1 + cellPos\n decorations.push(\n Decoration.node(from, from + cell.nodeSize, { style: `text-align: ${align}` }),\n )\n }\n return false\n })\n return DecorationSet.create(doc, decorations)\n}\n\nexport const tableKey = new PluginKey<DecorationSet>('dou-editor-table')\n\nexport function tablePlugin(): Plugin<DecorationSet> {\n return new Plugin<DecorationSet>({\n key: tableKey,\n state: {\n init: (_config, state) => alignments(state.doc),\n apply: (tr, value, _old, state) => (tr.docChanged ? alignments(state.doc) : value),\n },\n appendTransaction(trs, _old, state) {\n if (!trs.some((tr) => tr.docChanged)) return null\n const tr = state.tr\n normalize(state.doc, tr)\n return tr.docChanged ? tr : null\n },\n props: {\n decorations: (state) => tableKey.getState(state),\n },\n })\n}\n"],"mappings":";;;;;;;AAQA,IAAM,YAAY,SAAiB,KAAK,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS;AAEpF,IAAM,eAAe,GAAY,MAAe,KAAK,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC;;;;;;AAOtF,SAAS,UAAU,KAAa,IAAiB;CAC/C,MAAM,yBAAS,IAAI,IAAY;CAC/B,IAAI,aAAa,MAAM,QAAQ;EAC7B,IAAI,KAAK,KAAK,KAAK,cAAc,SAAS,OAAO,SAAS,IAAI;EAC9D,MAAM,MAAM,SAAS,IAAI,IAAI;EAC7B,MAAM,UAAU,WAAW,KAAK,MAAM,SAAS,IAAI,KAAK;EACxD,IAAI,CAAC,YAAY,SAAS,KAAK,MAAM,OAAO,GAAG,GAAG,iBAAiB,KAAK,WAAW,OAAO;EAC1F,MAAM,QAAQ,MAAM;EACpB,KAAK,SAAS,KAAK,WAAW;GAC5B,MAAM,KAAK,IAAI,MAAM;GACrB,IAAI,CAAC,MAAM,OAAO,IAAI,EAAE,GAAG,GAAG,iBAAiB,QAAQ,QAAQ,MAAM,SAAS,CAAC;QAC1E,OAAO,IAAI,EAAE;EACpB,CAAC;EACD,MAAM,uBAAO,IAAI,IAAY;EAC7B,KAAK,MAAM,WAAW,IAAI,KAAK;GAC7B,IAAI,KAAK,IAAI,OAAO,GAAG;GACvB,KAAK,IAAI,OAAO;GAChB,MAAM,OAAO,IAAI,SAAS,OAAO;GACjC,MAAM,OAAO,KAAK,OAAO,OAAO;GAEhC,MAAM,OADS,WAAW,KAAK,OAAO,KAAK,KAAK,KAAK,IACxC,IAAS,KAAK,KAAK,OAAO,MAAM,cAAe,KAAK,KAAK,OAAO,MAAM;GACnF,IAAI,KAAK,SAAS,MAAM,GAAG,cAAc,QAAQ,SAAS,MAAM,KAAK,KAAK;EAC5E;EACA,OAAO;CACT,CAAC;AACH;;AAGA,SAAS,WAAW,KAA4B;CAC9C,MAAM,cAA4B,CAAC;CACnC,IAAI,aAAa,MAAM,QAAQ;EAC7B,IAAI,KAAK,KAAK,KAAK,cAAc,SAAS,OAAO,SAAS,IAAI;EAC9D,MAAM,UAAU,KAAK,MAAM;EAC3B,IAAI,CAAC,QAAQ,MAAM,WAAW,OAAO,KAAK,GAAG,OAAO;EACpD,MAAM,MAAM,SAAS,IAAI,IAAI;EAC7B,MAAM,uBAAO,IAAI,IAAY;EAC7B,KAAK,MAAM,WAAW,IAAI,KAAK;GAC7B,IAAI,KAAK,IAAI,OAAO,GAAG;GACvB,KAAK,IAAI,OAAO;GAChB,MAAM,QAAQ,QAAQ,IAAI,SAAS,OAAO,CAAC,CAAC,KAAK,EAAE;GACnD,IAAI,CAAC,OAAO;GACZ,MAAM,OAAO,KAAK,OAAO,OAAO;GAChC,MAAM,OAAO,MAAM,IAAI;GACvB,YAAY,KACV,WAAW,KAAK,MAAM,OAAO,KAAK,UAAU,EAAE,OAAO,eAAe,QAAQ,CAAC,CAC/E;EACF;EACA,OAAO;CACT,CAAC;CACD,OAAO,cAAc,OAAO,KAAK,WAAW;AAC9C;AAEA,IAAa,WAAW,IAAI,UAAyB,kBAAkB;AAEvE,SAAgB,cAAqC;CACnD,OAAO,IAAI,OAAsB;EAC/B,KAAK;EACL,OAAO;GACL,OAAO,SAAS,UAAU,WAAW,MAAM,GAAG;GAC9C,QAAQ,IAAI,OAAO,MAAM,UAAW,GAAG,aAAa,WAAW,MAAM,GAAG,IAAI;EAC9E;EACA,kBAAkB,KAAK,MAAM,OAAO;GAClC,IAAI,CAAC,IAAI,MAAM,OAAO,GAAG,UAAU,GAAG,OAAO;GAC7C,MAAM,KAAK,MAAM;GACjB,UAAU,MAAM,KAAK,EAAE;GACvB,OAAO,GAAG,aAAa,KAAK;EAC9B;EACA,OAAO,EACL,cAAc,UAAU,SAAS,SAAS,KAAK,EACjD;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { createId, inlineNodes, inlineOf } from "../../core/model.js";
|
|
2
|
+
import { TableMap } from "prosemirror-tables";
|
|
3
|
+
// src/extensions/table/schema.ts
|
|
4
|
+
var ALIGNS = /* @__PURE__ */ new Set([
|
|
5
|
+
"left",
|
|
6
|
+
"center",
|
|
7
|
+
"right"
|
|
8
|
+
]);
|
|
9
|
+
var isAlign = (value) => ALIGNS.has(value);
|
|
10
|
+
/** One column record per column of the grid, keeping ids and alignment that are there. */
|
|
11
|
+
function fitColumns(columns, width) {
|
|
12
|
+
const list = Array.isArray(columns) ? columns : [];
|
|
13
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14
|
+
const result = [];
|
|
15
|
+
for (let index = 0; index < width; index++) {
|
|
16
|
+
const column = list[index] ?? {};
|
|
17
|
+
let id = typeof column.id === "string" && column.id ? column.id : createId();
|
|
18
|
+
if (seen.has(id)) id = createId();
|
|
19
|
+
seen.add(id);
|
|
20
|
+
result.push(isAlign(column.align) ? {
|
|
21
|
+
id,
|
|
22
|
+
align: column.align
|
|
23
|
+
} : { id });
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
var isHeaderAt = (attrs, row, col) => !!attrs.headerRow && row === 0 || !!attrs.headerColumn && col === 0;
|
|
28
|
+
/** The columns with their widths, which live on the cells. */
|
|
29
|
+
function columnsOf(node, map) {
|
|
30
|
+
const columns = fitColumns(node.attrs.columns, map.width);
|
|
31
|
+
const widths = [];
|
|
32
|
+
for (let index = 0; index < map.map.length; index++) {
|
|
33
|
+
const col = index % map.width;
|
|
34
|
+
if (widths[col] !== void 0) continue;
|
|
35
|
+
const pos = map.map[index];
|
|
36
|
+
const width = node.nodeAt(pos).attrs.colwidth?.[col - map.findCell(pos).left];
|
|
37
|
+
if (width) widths[col] = width;
|
|
38
|
+
}
|
|
39
|
+
return columns.map((column, index) => widths[index] ? {
|
|
40
|
+
...column,
|
|
41
|
+
width: widths[index]
|
|
42
|
+
} : column);
|
|
43
|
+
}
|
|
44
|
+
function readRows(node) {
|
|
45
|
+
const map = TableMap.get(node);
|
|
46
|
+
const rows = [];
|
|
47
|
+
for (let row = 0; row < map.height; row++) {
|
|
48
|
+
const cells = [];
|
|
49
|
+
for (let col = 0; col < map.width; col++) {
|
|
50
|
+
const pos = map.map[row * map.width + col];
|
|
51
|
+
const rect = map.findCell(pos);
|
|
52
|
+
if (rect.left !== col || rect.top !== row) {
|
|
53
|
+
cells.push({ content: [] });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const item = { content: inlineOf(node.nodeAt(pos)) };
|
|
57
|
+
if (rect.right - rect.left > 1) item.colspan = rect.right - rect.left;
|
|
58
|
+
if (rect.bottom - rect.top > 1) item.rowspan = rect.bottom - rect.top;
|
|
59
|
+
cells.push(item);
|
|
60
|
+
}
|
|
61
|
+
rows.push({
|
|
62
|
+
id: node.child(row).attrs.id ?? "",
|
|
63
|
+
cells
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
props: {
|
|
68
|
+
headerRow: !!node.attrs.headerRow,
|
|
69
|
+
headerColumn: !!node.attrs.headerColumn,
|
|
70
|
+
columns: columnsOf(node, map)
|
|
71
|
+
},
|
|
72
|
+
rows
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function gridOf(node) {
|
|
76
|
+
const map = TableMap.get(node);
|
|
77
|
+
const cells = [];
|
|
78
|
+
const seen = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const pos of map.map) {
|
|
80
|
+
if (seen.has(pos)) continue;
|
|
81
|
+
seen.add(pos);
|
|
82
|
+
const rect = map.findCell(pos);
|
|
83
|
+
cells.push({
|
|
84
|
+
row: rect.top,
|
|
85
|
+
col: rect.left,
|
|
86
|
+
rowspan: rect.bottom - rect.top,
|
|
87
|
+
colspan: rect.right - rect.left,
|
|
88
|
+
node: node.nodeAt(pos),
|
|
89
|
+
offset: pos
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const rows = [];
|
|
93
|
+
node.forEach((row) => rows.push(row.attrs.id ?? ""));
|
|
94
|
+
return {
|
|
95
|
+
props: {
|
|
96
|
+
headerRow: !!node.attrs.headerRow,
|
|
97
|
+
headerColumn: !!node.attrs.headerColumn
|
|
98
|
+
},
|
|
99
|
+
columns: columnsOf(node, map),
|
|
100
|
+
rows,
|
|
101
|
+
cells
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
var DEFAULT_SIZE = 3;
|
|
105
|
+
function writeRows(type, props, given) {
|
|
106
|
+
const schema = type.schema;
|
|
107
|
+
const rows = given.length ? given : Array.from({ length: DEFAULT_SIZE }, () => ({ cells: Array.from({ length: DEFAULT_SIZE }, () => "") }));
|
|
108
|
+
const listed = Array.isArray(props.columns) ? props.columns : [];
|
|
109
|
+
const width = Math.max(1, listed.length, ...rows.map((row) => row.cells.length));
|
|
110
|
+
const height = rows.length;
|
|
111
|
+
const columns = fitColumns(listed, width);
|
|
112
|
+
const widths = Array.from({ length: width }, (_, index) => {
|
|
113
|
+
const value = Number(listed[index]?.width);
|
|
114
|
+
return value > 0 ? Math.round(value) : 0;
|
|
115
|
+
});
|
|
116
|
+
const attrs = {
|
|
117
|
+
headerRow: props.headerRow === void 0 ? true : !!props.headerRow,
|
|
118
|
+
headerColumn: !!props.headerColumn,
|
|
119
|
+
columns
|
|
120
|
+
};
|
|
121
|
+
const covered = Array.from({ length: height }, () => Array.from({ length: width }, () => false));
|
|
122
|
+
const free = (top, left, bottom, right) => {
|
|
123
|
+
for (let row = top; row < bottom; row++) for (let col = left; col < right; col++) if (covered[row][col]) return false;
|
|
124
|
+
return true;
|
|
125
|
+
};
|
|
126
|
+
const rowNodes = rows.map((row, r) => {
|
|
127
|
+
const cells = [];
|
|
128
|
+
for (let c = 0; c < width; c++) {
|
|
129
|
+
if (covered[r][c]) continue;
|
|
130
|
+
const raw = row.cells[c];
|
|
131
|
+
const cell = typeof raw === "string" ? { content: raw } : raw ?? {};
|
|
132
|
+
let colspan = Math.max(1, Math.min(Math.floor(cell.colspan ?? 1) || 1, width - c));
|
|
133
|
+
let rowspan = Math.max(1, Math.min(Math.floor(cell.rowspan ?? 1) || 1, height - r));
|
|
134
|
+
while (colspan > 1 && !free(r, c, r + 1, c + colspan)) colspan--;
|
|
135
|
+
while (rowspan > 1 && !free(r, c, r + rowspan, c + colspan)) rowspan--;
|
|
136
|
+
for (let y = r; y < r + rowspan; y++) for (let x = c; x < c + colspan; x++) covered[y][x] = true;
|
|
137
|
+
const span = widths.slice(c, c + colspan);
|
|
138
|
+
const cellType = isHeaderAt(attrs, r, c) ? schema.nodes.tableHeader : schema.nodes.tableCell;
|
|
139
|
+
cells.push(cellType.create({
|
|
140
|
+
colspan,
|
|
141
|
+
rowspan,
|
|
142
|
+
colwidth: span.some(Boolean) ? span : null
|
|
143
|
+
}, inlineNodes(schema, cell.content, false)));
|
|
144
|
+
}
|
|
145
|
+
return schema.nodes.tableRow.create({ id: row.id || createId() }, cells);
|
|
146
|
+
});
|
|
147
|
+
return type.create(attrs, rowNodes);
|
|
148
|
+
}
|
|
149
|
+
var tableRows = {
|
|
150
|
+
read: readRows,
|
|
151
|
+
write: writeRows,
|
|
152
|
+
grid: gridOf
|
|
153
|
+
};
|
|
154
|
+
function cellAttrs(dom) {
|
|
155
|
+
const widthAttr = dom.getAttribute("data-colwidth");
|
|
156
|
+
const colspan = Number(dom.getAttribute("colspan") || 1);
|
|
157
|
+
const widths = widthAttr && /^\d+(,\d+)*$/.test(widthAttr) ? widthAttr.split(",").map(Number) : null;
|
|
158
|
+
return {
|
|
159
|
+
colspan,
|
|
160
|
+
rowspan: Number(dom.getAttribute("rowspan") || 1),
|
|
161
|
+
colwidth: widths && widths.length === colspan ? widths : null
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function cellDOM(tag) {
|
|
165
|
+
return (node) => {
|
|
166
|
+
const attrs = {};
|
|
167
|
+
if (node.attrs.colspan !== 1) attrs.colspan = String(node.attrs.colspan);
|
|
168
|
+
if (node.attrs.rowspan !== 1) attrs.rowspan = String(node.attrs.rowspan);
|
|
169
|
+
if (node.attrs.colwidth) attrs["data-colwidth"] = node.attrs.colwidth.join(",");
|
|
170
|
+
return [
|
|
171
|
+
tag,
|
|
172
|
+
attrs,
|
|
173
|
+
0
|
|
174
|
+
];
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
var cellSpec = (role, tag) => ({
|
|
178
|
+
content: "inline*",
|
|
179
|
+
tableRole: role,
|
|
180
|
+
isolating: true,
|
|
181
|
+
attrs: {
|
|
182
|
+
colspan: { default: 1 },
|
|
183
|
+
rowspan: { default: 1 },
|
|
184
|
+
colwidth: { default: null }
|
|
185
|
+
},
|
|
186
|
+
parseDOM: [{
|
|
187
|
+
tag,
|
|
188
|
+
getAttrs: (dom) => cellAttrs(dom)
|
|
189
|
+
}],
|
|
190
|
+
toDOM: cellDOM(tag)
|
|
191
|
+
});
|
|
192
|
+
/** The table's alignment per column, from the first row of a pasted table. */
|
|
193
|
+
function columnsFromDOM(table) {
|
|
194
|
+
const first = table.querySelector("tr");
|
|
195
|
+
const columns = [];
|
|
196
|
+
for (const cell of Array.from(first?.children ?? [])) {
|
|
197
|
+
const align = cell.style.textAlign || cell.getAttribute("align");
|
|
198
|
+
const span = Number(cell.getAttribute("colspan") || 1);
|
|
199
|
+
for (let index = 0; index < span; index++) columns.push(isAlign(align) ? {
|
|
200
|
+
id: createId(),
|
|
201
|
+
align
|
|
202
|
+
} : { id: createId() });
|
|
203
|
+
}
|
|
204
|
+
return columns;
|
|
205
|
+
}
|
|
206
|
+
var tableSpec = {
|
|
207
|
+
content: "tableRow+",
|
|
208
|
+
tableRole: "table",
|
|
209
|
+
isolating: true,
|
|
210
|
+
attrs: {
|
|
211
|
+
headerRow: { default: true },
|
|
212
|
+
headerColumn: { default: false },
|
|
213
|
+
columns: { default: [] }
|
|
214
|
+
},
|
|
215
|
+
rows: tableRows,
|
|
216
|
+
parseDOM: [{
|
|
217
|
+
tag: "table",
|
|
218
|
+
getAttrs: (dom) => {
|
|
219
|
+
const table = dom;
|
|
220
|
+
const rows = Array.from(table.querySelectorAll("tr"));
|
|
221
|
+
const firstCells = Array.from(rows[0]?.children ?? []);
|
|
222
|
+
return {
|
|
223
|
+
headerRow: firstCells.length > 0 && firstCells.every((cell) => cell.tagName === "TH"),
|
|
224
|
+
headerColumn: rows.length > 1 && rows.every((row) => row.firstElementChild?.tagName === "TH"),
|
|
225
|
+
columns: columnsFromDOM(table)
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}],
|
|
229
|
+
toDOM: () => [
|
|
230
|
+
"div",
|
|
231
|
+
{
|
|
232
|
+
class: "dou-editor__content dou-editor__content--table",
|
|
233
|
+
"data-content-type": "table"
|
|
234
|
+
},
|
|
235
|
+
["table", ["tbody", 0]]
|
|
236
|
+
]
|
|
237
|
+
};
|
|
238
|
+
var tableNodes = {
|
|
239
|
+
tableRow: {
|
|
240
|
+
content: "(tableCell | tableHeader)*",
|
|
241
|
+
tableRole: "row",
|
|
242
|
+
attrs: { id: { default: null } },
|
|
243
|
+
parseDOM: [{
|
|
244
|
+
tag: "tr",
|
|
245
|
+
getAttrs: (dom) => ({ id: dom.dataset.rowId ?? null })
|
|
246
|
+
}],
|
|
247
|
+
toDOM: (node) => [
|
|
248
|
+
"tr",
|
|
249
|
+
{ "data-row-id": node.attrs.id },
|
|
250
|
+
0
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
tableCell: cellSpec("cell", "td"),
|
|
254
|
+
tableHeader: cellSpec("header_cell", "th")
|
|
255
|
+
};
|
|
256
|
+
export { fitColumns, isAlign, isHeaderAt, tableNodes, tableRows, tableSpec };
|
|
257
|
+
|
|
258
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/extensions/table/schema.ts"],"sourcesContent":["import type { Node as PMNode, NodeSpec, NodeType } from 'prosemirror-model'\nimport { TableMap } from 'prosemirror-tables'\nimport {\n createId,\n inlineNodes,\n inlineOf,\n type BlockProps,\n type GridCell,\n type PartialTableCell,\n type PartialTableRow,\n type RowsSpec,\n type TableCell,\n type TableGrid,\n type TableRow,\n} from '../../core/model'\n\n/** A column of a table: a stable id, and how its cells align. */\nexport interface TableColumn {\n id: string\n /** In pixels; without it the column takes its share of the width. */\n width?: number\n align?: TableAlign\n}\n\nexport type TableAlign = 'left' | 'center' | 'right'\n\nconst ALIGNS = new Set(['left', 'center', 'right'])\n\n/** The columns as stored on the table node: id and alignment (widths live on the cells). */\ninterface StoredColumn {\n id: string\n align?: TableAlign\n}\n\nexport const isAlign = (value: unknown): value is TableAlign => ALIGNS.has(value as string)\n\n/** One column record per column of the grid, keeping ids and alignment that are there. */\nexport function fitColumns(columns: unknown, width: number): StoredColumn[] {\n const list = Array.isArray(columns) ? (columns as Partial<TableColumn>[]) : []\n const seen = new Set<string>()\n const result: StoredColumn[] = []\n for (let index = 0; index < width; index++) {\n const column = list[index] ?? {}\n let id = typeof column.id === 'string' && column.id ? column.id : createId()\n if (seen.has(id)) id = createId()\n seen.add(id)\n result.push(isAlign(column.align) ? { id, align: column.align } : { id })\n }\n return result\n}\n\nexport const isHeaderAt = (attrs: Record<string, unknown>, row: number, col: number) =>\n (!!attrs.headerRow && row === 0) || (!!attrs.headerColumn && col === 0)\n\n/* table node ↔ rows */\n\n/** The columns with their widths, which live on the cells. */\nfunction columnsOf(node: PMNode, map: TableMap): TableColumn[] {\n const columns = fitColumns(node.attrs.columns, map.width)\n const widths: (number | undefined)[] = []\n for (let index = 0; index < map.map.length; index++) {\n const col = index % map.width\n if (widths[col] !== undefined) continue\n const pos = map.map[index]!\n const width = (node.nodeAt(pos)!.attrs.colwidth as number[] | null)?.[\n col - map.findCell(pos).left\n ]\n if (width) widths[col] = width\n }\n return columns.map((column, index) =>\n widths[index] ? { ...column, width: widths[index] } : column,\n )\n}\n\nfunction readRows(node: PMNode): { props: BlockProps; rows: TableRow[] } {\n const map = TableMap.get(node)\n const rows: TableRow[] = []\n for (let row = 0; row < map.height; row++) {\n const cells: TableCell[] = []\n for (let col = 0; col < map.width; col++) {\n const pos = map.map[row * map.width + col]!\n const rect = map.findCell(pos)\n if (rect.left !== col || rect.top !== row) {\n // covered by a merged cell\n cells.push({ content: [] })\n continue\n }\n const item: TableCell = { content: inlineOf(node.nodeAt(pos)!) }\n if (rect.right - rect.left > 1) item.colspan = rect.right - rect.left\n if (rect.bottom - rect.top > 1) item.rowspan = rect.bottom - rect.top\n cells.push(item)\n }\n rows.push({ id: (node.child(row).attrs.id as string | null) ?? '', cells })\n }\n return {\n props: {\n headerRow: !!node.attrs.headerRow,\n headerColumn: !!node.attrs.headerColumn,\n columns: columnsOf(node, map),\n },\n rows,\n }\n}\n\nfunction gridOf(node: PMNode): TableGrid {\n const map = TableMap.get(node)\n const cells: GridCell[] = []\n const seen = new Set<number>()\n for (const pos of map.map) {\n if (seen.has(pos)) continue\n seen.add(pos)\n const rect = map.findCell(pos)\n cells.push({\n row: rect.top,\n col: rect.left,\n rowspan: rect.bottom - rect.top,\n colspan: rect.right - rect.left,\n node: node.nodeAt(pos)!,\n offset: pos,\n })\n }\n const rows: string[] = []\n node.forEach((row) => rows.push((row.attrs.id as string | null) ?? ''))\n return {\n props: { headerRow: !!node.attrs.headerRow, headerColumn: !!node.attrs.headerColumn },\n columns: columnsOf(node, map),\n rows,\n cells,\n }\n}\n\nconst DEFAULT_SIZE = 3\n\nfunction writeRows(type: NodeType, props: BlockProps, given: PartialTableRow[]): PMNode {\n const schema = type.schema\n const rows: PartialTableRow[] = given.length\n ? given\n : Array.from({ length: DEFAULT_SIZE }, () => ({\n cells: Array.from({ length: DEFAULT_SIZE }, () => ''),\n }))\n const listed = Array.isArray(props.columns) ? (props.columns as Partial<TableColumn>[]) : []\n const width = Math.max(1, listed.length, ...rows.map((row) => row.cells.length))\n const height = rows.length\n const columns = fitColumns(listed, width)\n const widths = Array.from({ length: width }, (_, index) => {\n const value = Number(listed[index]?.width)\n return value > 0 ? Math.round(value) : 0\n })\n const attrs = {\n headerRow: props.headerRow === undefined ? true : !!props.headerRow,\n headerColumn: !!props.headerColumn,\n columns,\n }\n const covered = Array.from({ length: height }, () => Array.from({ length: width }, () => false))\n const free = (top: number, left: number, bottom: number, right: number) => {\n for (let row = top; row < bottom; row++)\n for (let col = left; col < right; col++) if (covered[row]![col]) return false\n return true\n }\n const rowNodes = rows.map((row, r) => {\n const cells: PMNode[] = []\n for (let c = 0; c < width; c++) {\n if (covered[r]![c]) continue\n const raw = row.cells[c]\n const cell: PartialTableCell = typeof raw === 'string' ? { content: raw } : (raw ?? {})\n // spans stay inside the table and never overlap another merged cell\n let colspan = Math.max(1, Math.min(Math.floor(cell.colspan ?? 1) || 1, width - c))\n let rowspan = Math.max(1, Math.min(Math.floor(cell.rowspan ?? 1) || 1, height - r))\n while (colspan > 1 && !free(r, c, r + 1, c + colspan)) colspan--\n while (rowspan > 1 && !free(r, c, r + rowspan, c + colspan)) rowspan--\n for (let y = r; y < r + rowspan; y++)\n for (let x = c; x < c + colspan; x++) covered[y]![x] = true\n const span = widths.slice(c, c + colspan)\n const cellType = isHeaderAt(attrs, r, c) ? schema.nodes.tableHeader! : schema.nodes.tableCell!\n cells.push(\n cellType.create(\n { colspan, rowspan, colwidth: span.some(Boolean) ? span : null },\n inlineNodes(schema, cell.content, false),\n ),\n )\n }\n return schema.nodes.tableRow!.create({ id: row.id || createId() }, cells)\n })\n return type.create(attrs, rowNodes)\n}\n\nexport const tableRows: RowsSpec = { read: readRows, write: writeRows, grid: gridOf }\n\n/* DOM */\n\nfunction cellAttrs(dom: HTMLElement) {\n const widthAttr = dom.getAttribute('data-colwidth')\n const colspan = Number(dom.getAttribute('colspan') || 1)\n const widths =\n widthAttr && /^\\d+(,\\d+)*$/.test(widthAttr) ? widthAttr.split(',').map(Number) : null\n return {\n colspan,\n rowspan: Number(dom.getAttribute('rowspan') || 1),\n colwidth: widths && widths.length === colspan ? widths : null,\n }\n}\n\nfunction cellDOM(tag: 'td' | 'th') {\n return (node: PMNode) => {\n const attrs: Record<string, string> = {}\n if (node.attrs.colspan !== 1) attrs.colspan = String(node.attrs.colspan)\n if (node.attrs.rowspan !== 1) attrs.rowspan = String(node.attrs.rowspan)\n if (node.attrs.colwidth) attrs['data-colwidth'] = (node.attrs.colwidth as number[]).join(',')\n return [tag, attrs, 0] as const\n }\n}\n\nconst cellSpec = (role: 'cell' | 'header_cell', tag: 'td' | 'th'): NodeSpec => ({\n content: 'inline*',\n tableRole: role,\n isolating: true,\n attrs: { colspan: { default: 1 }, rowspan: { default: 1 }, colwidth: { default: null } },\n parseDOM: [{ tag, getAttrs: (dom) => cellAttrs(dom as HTMLElement) }],\n toDOM: cellDOM(tag),\n})\n\n/** The table's alignment per column, from the first row of a pasted table. */\nfunction columnsFromDOM(table: HTMLElement): StoredColumn[] {\n const first = table.querySelector('tr')\n const columns: StoredColumn[] = []\n for (const cell of Array.from(first?.children ?? [])) {\n const align = (cell as HTMLElement).style.textAlign || cell.getAttribute('align')\n const span = Number(cell.getAttribute('colspan') || 1)\n for (let index = 0; index < span; index++)\n columns.push(isAlign(align) ? { id: createId(), align } : { id: createId() })\n }\n return columns\n}\n\nexport const tableSpec: NodeSpec = {\n content: 'tableRow+',\n tableRole: 'table',\n isolating: true,\n attrs: {\n headerRow: { default: true },\n headerColumn: { default: false },\n columns: { default: [] },\n },\n rows: tableRows,\n parseDOM: [\n {\n tag: 'table',\n getAttrs: (dom) => {\n const table = dom as HTMLElement\n const rows = Array.from(table.querySelectorAll('tr'))\n const firstCells = Array.from(rows[0]?.children ?? [])\n return {\n headerRow: firstCells.length > 0 && firstCells.every((cell) => cell.tagName === 'TH'),\n headerColumn:\n rows.length > 1 && rows.every((row) => row.firstElementChild?.tagName === 'TH'),\n columns: columnsFromDOM(table),\n }\n },\n },\n ],\n toDOM: () => [\n 'div',\n { class: 'dou-editor__content dou-editor__content--table', 'data-content-type': 'table' },\n ['table', ['tbody', 0]],\n ],\n}\n\nexport const tableNodes: Record<string, NodeSpec> = {\n // rows a merged cell covers completely have no cells of their own\n tableRow: {\n content: '(tableCell | tableHeader)*',\n tableRole: 'row',\n attrs: { id: { default: null } },\n parseDOM: [\n { tag: 'tr', getAttrs: (dom) => ({ id: (dom as HTMLElement).dataset.rowId ?? null }) },\n ],\n toDOM: (node) => ['tr', { 'data-row-id': node.attrs.id }, 0],\n },\n tableCell: cellSpec('cell', 'td'),\n tableHeader: cellSpec('header_cell', 'th'),\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import table_controls_vue_vue_type_script_setup_true_lang_default from "./table-controls.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
// src/extensions/table/table-controls.vue
|
|
3
|
+
var table_controls_default = table_controls_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
export { table_controls_default as default };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=table-controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-controls.js","names":["_sfc_main"],"sources":["../../../src/extensions/table/table-controls.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ArrowDownFromLineIcon,\n ArrowDownIcon,\n ArrowLeftIcon,\n ArrowLeftToLineIcon,\n ArrowRightIcon,\n ArrowRightToLineIcon,\n ArrowUpIcon,\n ArrowUpToLineIcon,\n CheckIcon,\n EraserIcon,\n GripHorizontalIcon,\n GripVerticalIcon,\n LayoutColumnsIcon,\n LayoutRowsIcon,\n MergeIcon,\n PlusIcon,\n SplitIcon,\n TextAlignCenterIcon,\n TextAlignLeftIcon,\n TextAlignRightIcon,\n TrashIcon,\n} from '@dou-ui/icons'\nimport { DouDropdown, DouDropdownItem, DouDropdownSub } from 'dou-ui'\nimport { TextSelection, type Command } from 'prosemirror-state'\nimport { CellSelection, TableMap, addColumn, addRow, type Rect } from 'prosemirror-tables'\nimport { computed, onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'\nimport { createId } from '../../core/model'\nimport { useMenuEscape } from '../../components/menu-escape'\nimport { useEditor } from '../../editor'\nimport {\n alignColumns,\n band,\n boundaries,\n clearCells,\n deleteColumns,\n deleteRows,\n inMergedCell,\n insertColumn,\n insertRow,\n mergeCells,\n moveLines,\n moveLinesTo,\n selectLines,\n splitMergedCell,\n tableAt,\n toggleHeader,\n} from './commands'\nimport { fitColumns, type TableAlign } from './schema'\n\nconst { view, state, root, messages } = useEditor()\n\n/** The table and the cell the controls are about: the hovered one, or the selection's. */\ninterface Target {\n /** Position right before the table node. */\n pos: number\n /** The cell's place in the grid. */\n rect: Rect\n}\n\nconst hovered = shallowRef<Target | null>(null)\nconst columnMenu = shallowRef(false)\nconst rowMenu = shallowRef(false)\nconst menuOpen = computed(() => columnMenu.value || rowMenu.value)\nuseMenuEscape(menuOpen, () => {\n columnMenu.value = false\n rowMenu.value = false\n})\nconst barShown = computed(() => !!layout.value?.bar && !menuOpen.value && !dragging.value)\n// the toolbar over selected cells stands in for the handles; a merged cell's goes below it\nconst handlesHidden = computed(() => barShown.value && hasCellSelection.value)\n\nfunction targetOf(cell: HTMLElement): Target | null {\n const current = view.value\n if (!current || !current.dom.contains(cell)) return null\n const $pos = current.state.doc.resolve(current.posAtDOM(cell, 0))\n const found = tableAt($pos)\n if (!found) return null\n // the cell node starts right before its content\n for (let depth = $pos.depth; depth > 0; depth--) {\n const role = $pos.node(depth).type.spec.tableRole\n if (role === 'cell' || role === 'header_cell') {\n const cellPos = $pos.before(depth) - found.pos - 1\n return { pos: found.pos, rect: found.map.findCell(cellPos) }\n }\n }\n return null\n}\n\n/** The selection's cell when the pointer is elsewhere. */\nconst selected = computed<Target | null>(() => {\n const current = state.value\n if (!current) return null\n const selection = current.selection\n const $cell = selection instanceof CellSelection ? selection.$headCell : null\n const found = tableAt(selection.$head)\n if (!found) return null\n const start = found.pos + 1\n if ($cell) return { pos: found.pos, rect: found.map.findCell($cell.pos - start) }\n for (let depth = selection.$head.depth; depth > 0; depth--) {\n const role = selection.$head.node(depth).type.spec.tableRole\n if (role === 'cell' || role === 'header_cell')\n return { pos: found.pos, rect: found.map.findCell(selection.$head.before(depth) - start) }\n }\n return null\n})\n\nconst target = computed(() => hovered.value ?? selected.value)\n\n/* where things go */\n\ninterface Layout {\n /** The table, relative to the editor. */\n table: { top: number; left: number; width: number; height: number }\n /** The visible part of the table, when it scrolls sideways. */\n frame: { left: number; right: number }\n column: { left: number; width: number }\n row: { top: number; height: number }\n /**\n * Where the cell toolbar goes (its centre): above selected cells, or below\n * the merged cell with the cursor.\n */\n bar: { top: number; left: number; below: boolean } | null\n}\n\nconst layout = shallowRef<Layout | null>(null)\n\nfunction cellElement(tablePos: number, row: number, col: number): HTMLElement | null {\n const current = view.value\n const table = current?.state.doc.nodeAt(tablePos)\n if (!current || !table) return null\n const map = TableMap.get(table)\n if (row >= map.height || col >= map.width) return null\n const pos = tablePos + 1 + map.map[row * map.width + col]!\n return current.nodeDOM(pos) as HTMLElement | null\n}\n\nfunction measure() {\n const current = view.value\n const host = root.value\n const at = target.value\n if (!current || !host || !at) return (layout.value = null)\n const tableNode = current.state.doc.nodeAt(at.pos)\n const frameElement = current.nodeDOM(at.pos) as HTMLElement | null\n const tableElement = frameElement?.querySelector('table')\n if (!tableNode || !frameElement || !tableElement) return (layout.value = null)\n const box = host.getBoundingClientRect()\n const tableBox = tableElement.getBoundingClientRect()\n const frameBox = frameElement.getBoundingClientRect()\n const first = cellElement(at.pos, at.rect.top, at.rect.left)?.getBoundingClientRect()\n if (!first) return (layout.value = null)\n let bar: Layout['bar'] = null\n const boxes = barCells(current).map((pos) =>\n (current.nodeDOM(pos) as HTMLElement).getBoundingClientRect(),\n )\n if (boxes.length) {\n const left = Math.max(Math.min(...boxes.map((item) => item.left)), frameBox.left)\n const right = Math.min(Math.max(...boxes.map((item) => item.right)), frameBox.right)\n const below = !(current.state.selection instanceof CellSelection)\n bar = {\n top: below\n ? Math.max(...boxes.map((item) => item.bottom)) - box.top\n : Math.min(...boxes.map((item) => item.top)) - box.top,\n left: (left + right) / 2 - box.left,\n below,\n }\n }\n layout.value = {\n table: {\n top: tableBox.top - box.top,\n left: tableBox.left - box.left,\n width: tableBox.width,\n height: tableBox.height,\n },\n frame: { left: frameBox.left - box.left, right: frameBox.right - box.left },\n column: { left: first.left - box.left, width: first.width },\n row: { top: first.top - box.top, height: first.height },\n bar,\n }\n}\n\n/** The cells the toolbar is about: a cell selection's corners, or the merged cell with the cursor. */\nfunction barCells(current: NonNullable<typeof view.value>): number[] {\n const selection = current.state.selection\n if (selection instanceof CellSelection)\n return selection.$anchorCell.pos === selection.$headCell.pos\n ? []\n : [selection.$anchorCell.pos, selection.$headCell.pos]\n if (!inMergedCell(current.state)) return []\n const $head = selection.$head\n for (let depth = $head.depth; depth > 0; depth--) {\n const role = $head.node(depth).type.spec.tableRole\n if (role === 'cell' || role === 'header_cell') return [$head.before(depth)]\n }\n return []\n}\n\nlet frame = 0\nlet lastTarget: EventTarget | null = null\n\nfunction schedule() {\n if (frame) return\n frame = requestAnimationFrame(() => {\n frame = 0\n if (!menuOpen.value && !dragging.value) {\n const element = lastTarget instanceof Element ? lastTarget : null\n const cell = element?.closest<HTMLElement>('.dou-editor-table td, .dou-editor-table th')\n const inControls = element?.closest('.dou-editor-table-controls')\n if (cell) hovered.value = targetOf(cell)\n else if (!inControls) hovered.value = null\n }\n measure()\n })\n}\n\nfunction track(event: PointerEvent) {\n if (event.pointerType === 'touch') return\n lastTarget = event.target\n schedule()\n}\n\nfunction leave() {\n if (menuOpen.value || dragging.value) return\n lastTarget = null\n hovered.value = null\n schedule()\n}\n\nconst onScroll = () => schedule()\n\nonMounted(() => {\n root.value?.addEventListener('pointermove', track)\n root.value?.addEventListener('pointerleave', leave)\n // the table frame scrolls sideways; the page scrolls too\n window.addEventListener('scroll', onScroll, true)\n window.addEventListener('resize', onScroll)\n})\nonBeforeUnmount(() => {\n root.value?.removeEventListener('pointermove', track)\n root.value?.removeEventListener('pointerleave', leave)\n window.removeEventListener('scroll', onScroll, true)\n window.removeEventListener('resize', onScroll)\n if (frame) cancelAnimationFrame(frame)\n endDrag(false)\n})\n\nwatch([state, target], () => schedule())\n\n/* actions */\n\nfunction run(command: Command) {\n const current = view.value\n if (!current) return\n command(current.state, current.dispatch)\n current.focus()\n}\n\nfunction openMenu(axis: 'row' | 'column', pressed: Drag) {\n // the lines pressed on, even if the handles moved since\n const current = view.value\n if (!current) return\n current.dispatch(\n selectLines(current.state.tr, pressed.tablePos, axis, pressed.start, pressed.end),\n )\n // one menu at a time\n rowMenu.value = axis === 'row'\n columnMenu.value = axis === 'column'\n}\n\n/**\n * Whole lines are selected to show what a menu or a drag is about; after\n * it, the cursor goes into their first cell so the handles come back.\n */\nfunction collapseLines() {\n const current = view.value\n const selection = current?.state.selection\n if (!current || !(selection instanceof CellSelection)) return\n if (!selection.isRowSelection() && !selection.isColSelection()) return\n const $cell = current.state.doc.resolve(selection.$anchorCell.pos + 1)\n current.dispatch(current.state.tr.setSelection(TextSelection.near($cell)))\n}\n\nwatch(menuOpen, (open) => {\n if (open) return\n collapseLines()\n schedule()\n})\n\nconst can = (command: Command) => {\n const current = state.value\n return !!current && command(current)\n}\n\nconst tableNode = computed(() => {\n const at = target.value\n return at ? (state.value?.doc.nodeAt(at.pos) ?? null) : null\n})\n\nconst columnAlign = computed<TableAlign>(() => {\n const node = tableNode.value\n const at = target.value\n if (!node || !at) return 'left'\n return (fitColumns(node.attrs.columns, TableMap.get(node).width)[at.rect.left]?.align ??\n 'left') as TableAlign\n})\n\nconst aligns: { value: TableAlign; icon: unknown; label: () => string }[] = [\n { value: 'left', icon: TextAlignLeftIcon, label: () => messages.value.table.alignLeft },\n { value: 'center', icon: TextAlignCenterIcon, label: () => messages.value.table.alignCenter },\n { value: 'right', icon: TextAlignRightIcon, label: () => messages.value.table.alignRight },\n]\n\n/** The + bars: a column at the right end, a row at the bottom. */\nfunction addAtEnd(axis: 'row' | 'column') {\n const current = view.value\n const at = target.value\n if (!current || !at) return\n const table = current.state.doc.nodeAt(at.pos)!\n const map = TableMap.get(table)\n const rect = {\n left: 0,\n top: 0,\n right: map.width,\n bottom: map.height,\n map,\n table,\n tableStart: at.pos + 1,\n }\n let tr = current.state.tr\n if (axis === 'row') tr = addRow(tr, rect, map.height)\n else {\n const columns = fitColumns(table.attrs.columns, map.width)\n columns.push({ id: createId() })\n tr = addColumn(tr, rect, map.width).setNodeAttribute(at.pos, 'columns', columns)\n }\n current.dispatch(tr)\n}\n\n/* dragging rows and columns by their handle */\n\ninterface Drag {\n axis: 'row' | 'column'\n tablePos: number\n /** The lines that move: the target's, with any merged cells they belong to. */\n start: number\n end: number\n x: number\n y: number\n /** Where they go: the line they go before, or `null` for where they are. */\n to: number | null\n}\n\nlet drag: Drag | null = null\nconst dragging = shallowRef(false)\nconst drop = shallowRef<{ axis: 'row' | 'column'; at: number } | null>(null)\n/** A copy of the dragged lines that follows the pointer. */\nlet ghost: HTMLElement | null = null\n\nfunction onHandleDown(axis: 'row' | 'column', event: PointerEvent) {\n const current = view.value\n const at = target.value\n if (event.button !== 0 || !current || !at || !current.editable) return\n const map = TableMap.get(current.state.doc.nodeAt(at.pos)!)\n const [from, to] = axis === 'row' ? [at.rect.top, at.rect.bottom] : [at.rect.left, at.rect.right]\n const lines = band(map, axis, from, to)\n drag = { axis, tablePos: at.pos, ...lines, x: event.clientX, y: event.clientY, to: null }\n window.addEventListener('pointermove', onDragMove)\n window.addEventListener('pointerup', onDragUp)\n window.addEventListener('pointercancel', onDragCancel)\n}\n\n/** Each boundary a run can go to, and where it is (relative to the editor). */\nfunction cutPlaces(moving: Drag): { cut: number; at: number }[] {\n const current = view.value\n const host = root.value\n const table = current?.state.doc.nodeAt(moving.tablePos)\n const element = (current?.nodeDOM(moving.tablePos) as HTMLElement | null)?.querySelector('table')\n if (!current || !host || !table || !element) return []\n const box = host.getBoundingClientRect()\n const tableBox = element.getBoundingClientRect()\n const map = TableMap.get(table)\n const places: { cut: number; at: number }[] = []\n if (moving.axis === 'row') {\n const tops: number[] = []\n table.forEach((_row, offset) => {\n const row = current.nodeDOM(moving.tablePos + 1 + offset) as HTMLElement | null\n tops.push((row?.getBoundingClientRect().top ?? tableBox.top) - box.top)\n })\n for (const cut of boundaries(map, 'row'))\n places.push({ cut, at: cut < map.height ? tops[cut]! : tableBox.bottom - box.top })\n return places\n }\n for (const cut of boundaries(map, 'column')) {\n if (cut === map.width) {\n places.push({ cut, at: tableBox.right - box.left })\n continue\n }\n // no merged cell crosses a boundary, so the first row has a cell starting there\n let row = 0\n while (row < map.height && map.findCell(map.map[row * map.width + cut]!).left !== cut) row++\n const cell = cellElement(moving.tablePos, row, cut)\n if (cell) places.push({ cut, at: cell.getBoundingClientRect().left - box.left })\n }\n return places\n}\n\nfunction showGhost(moving: Drag) {\n const current = view.value\n const host = root.value\n const table = current?.state.doc.nodeAt(moving.tablePos)\n const element = (current?.nodeDOM(moving.tablePos) as HTMLElement | null)?.querySelector('table')\n if (!current || !host || !table || !element) return\n const map = TableMap.get(table)\n const copy = document.createElement('table')\n copy.className = 'dou-editor-table'\n const body = document.createElement('tbody')\n copy.append(body)\n let width = 0\n const [rows, cols] =\n moving.axis === 'row'\n ? [\n [moving.start, moving.end],\n [0, map.width],\n ]\n : [\n [0, map.height],\n [moving.start, moving.end],\n ]\n for (let row = rows[0]!; row < rows[1]!; row++) {\n const line = document.createElement('tr')\n let lineWidth = 0\n for (let col = cols[0]!; col < cols[1]!; col++) {\n const rect = map.findCell(map.map[row * map.width + col]!)\n if (rect.top !== row || rect.left !== col) continue\n const cell = cellElement(moving.tablePos, row, col)\n if (!cell) continue\n const clone = cell.cloneNode(true) as HTMLElement\n clone.classList.remove('selectedCell')\n clone.style.width = `${cell.getBoundingClientRect().width}px`\n lineWidth += cell.getBoundingClientRect().width\n line.append(clone)\n }\n width = Math.max(width, lineWidth)\n body.append(line)\n }\n ghost = document.createElement('div')\n ghost.className = 'dou-editor__surface dou-editor__ghost dou-editor-table__ghost'\n ghost.setAttribute('aria-hidden', 'true')\n ghost.style.width = `${Math.min(width, 360) + 2}px`\n ghost.append(copy)\n host.append(ghost)\n}\n\nfunction onDragMove(event: PointerEvent) {\n const moving = drag\n if (!moving) return\n if (!dragging.value) {\n if (Math.hypot(event.clientX - moving.x, event.clientY - moving.y) < 4) return\n dragging.value = true\n columnMenu.value = false\n rowMenu.value = false\n const current = view.value\n if (current)\n current.dispatch(\n selectLines(current.state.tr, moving.tablePos, moving.axis, moving.start, moving.end),\n )\n showGhost(moving)\n }\n if (ghost) ghost.style.transform = `translate(${event.clientX + 12}px, ${event.clientY + 12}px)`\n const host = root.value\n if (!host) return\n const box = host.getBoundingClientRect()\n const pointer = moving.axis === 'row' ? event.clientY - box.top : event.clientX - box.left\n let best: { cut: number; at: number } | null = null\n for (const place of cutPlaces(moving))\n if (!best || Math.abs(place.at - pointer) < Math.abs(best.at - pointer)) best = place\n // a boundary of the run itself leaves it where it is\n if (!best || (best.cut >= moving.start && best.cut <= moving.end)) {\n moving.to = null\n drop.value = null\n return\n }\n moving.to = best.cut\n drop.value = { axis: moving.axis, at: best.at }\n}\n\nfunction onDragUp() {\n endDrag(true)\n}\n\nfunction onDragCancel() {\n endDrag(false)\n}\n\nfunction endDrag(apply: boolean) {\n window.removeEventListener('pointermove', onDragMove)\n window.removeEventListener('pointerup', onDragUp)\n window.removeEventListener('pointercancel', onDragCancel)\n const moving = drag\n drag = null\n ghost?.remove()\n ghost = null\n drop.value = null\n if (!dragging.value) {\n // a press without a drag opens the menu; on release rather than on\n // click, as the press may close another menu and move the handles\n if (apply && moving) openMenu(moving.axis, moving)\n return\n }\n dragging.value = false\n if (apply && moving?.to !== null && moving)\n run(moveLinesTo(moving.tablePos, moving.axis, moving.start, moving.end, moving.to))\n collapseLines()\n schedule()\n}\n\nconst hasCellSelection = computed(() => {\n const selection = state.value?.selection\n return selection instanceof CellSelection && selection.$anchorCell.pos !== selection.$headCell.pos\n})\n\nconst visible = computed(() => !!layout.value && !!target.value)\n</script>\n\n<template>\n <div v-if=\"visible && layout\" class=\"dou-editor-table-controls\" @mousedown.prevent>\n <!-- column handle, above the column -->\n <DouDropdown\n v-model:open=\"columnMenu\"\n trigger=\"manual\"\n placement=\"bottom-start\"\n class=\"dou-editor-menu\"\n >\n <button\n type=\"button\"\n tabindex=\"-1\"\n v-show=\"!handlesHidden\"\n class=\"dou-editor-table__handle is-column\"\n :class=\"{ 'is-active': columnMenu }\"\n :style=\"{\n top: `${layout.table.top}px`,\n left: `${Math.min(Math.max(layout.column.left + layout.column.width / 2, layout.frame.left + 12), layout.frame.right - 12)}px`,\n }\"\n :aria-label=\"messages.table.column\"\n :title=\"messages.table.column\"\n @pointerdown=\"onHandleDown('column', $event)\"\n >\n <GripHorizontalIcon />\n </button>\n <template #menu>\n <DouDropdownItem @click=\"run(insertColumn('before'))\">\n <template #prefix><ArrowLeftToLineIcon /></template>\n {{ messages.table.insertLeft }}\n </DouDropdownItem>\n <DouDropdownItem @click=\"run(insertColumn('after'))\">\n <template #prefix><ArrowRightToLineIcon /></template>\n {{ messages.table.insertRight }}\n </DouDropdownItem>\n <DouDropdownItem\n divided\n :disabled=\"!can(moveLines('column', -1))\"\n @click=\"run(moveLines('column', -1))\"\n >\n <template #prefix><ArrowLeftIcon /></template>\n {{ messages.table.moveLeft }}\n </DouDropdownItem>\n <DouDropdownItem\n :disabled=\"!can(moveLines('column', 1))\"\n @click=\"run(moveLines('column', 1))\"\n >\n <template #prefix><ArrowRightIcon /></template>\n {{ messages.table.moveRight }}\n </DouDropdownItem>\n <DouDropdownSub divided :title=\"aligns.find((item) => item.value === columnAlign)!.label()\">\n <template #prefix>\n <component :is=\"aligns.find((item) => item.value === columnAlign)!.icon\" />\n </template>\n <DouDropdownItem\n v-for=\"item in aligns\"\n :key=\"item.value\"\n @click=\"run(alignColumns(item.value))\"\n >\n <template #prefix><component :is=\"item.icon\" /></template>\n {{ item.label() }}\n <template v-if=\"item.value === columnAlign\" #suffix><CheckIcon /></template>\n </DouDropdownItem>\n </DouDropdownSub>\n <DouDropdownItem v-if=\"target?.rect.left === 0\" @click=\"run(toggleHeader('headerColumn'))\">\n <template #prefix><LayoutColumnsIcon /></template>\n {{ messages.table.headerColumn }}\n <template v-if=\"tableNode?.attrs.headerColumn\" #suffix><CheckIcon /></template>\n </DouDropdownItem>\n <DouDropdownItem divided @click=\"run(clearCells)\">\n <template #prefix><EraserIcon /></template>\n {{ messages.table.clear }}\n </DouDropdownItem>\n <DouDropdownItem tone=\"danger\" :disabled=\"!can(deleteColumns)\" @click=\"run(deleteColumns)\">\n <template #prefix><TrashIcon /></template>\n {{ messages.table.deleteColumn }}\n </DouDropdownItem>\n </template>\n </DouDropdown>\n\n <!-- row handle, left of the row -->\n <DouDropdown\n v-model:open=\"rowMenu\"\n trigger=\"manual\"\n placement=\"right-start\"\n class=\"dou-editor-menu\"\n >\n <button\n type=\"button\"\n tabindex=\"-1\"\n v-show=\"!handlesHidden\"\n class=\"dou-editor-table__handle is-row\"\n :class=\"{ 'is-active': rowMenu }\"\n :style=\"{\n top: `${layout.row.top + layout.row.height / 2}px`,\n left: `${layout.frame.left}px`,\n }\"\n :aria-label=\"messages.table.row\"\n :title=\"messages.table.row\"\n @pointerdown=\"onHandleDown('row', $event)\"\n >\n <GripVerticalIcon />\n </button>\n <template #menu>\n <DouDropdownItem @click=\"run(insertRow('before'))\">\n <template #prefix><ArrowUpToLineIcon /></template>\n {{ messages.table.insertAbove }}\n </DouDropdownItem>\n <DouDropdownItem @click=\"run(insertRow('after'))\">\n <template #prefix><ArrowDownFromLineIcon /></template>\n {{ messages.table.insertBelow }}\n </DouDropdownItem>\n <DouDropdownItem\n divided\n :disabled=\"!can(moveLines('row', -1))\"\n @click=\"run(moveLines('row', -1))\"\n >\n <template #prefix><ArrowUpIcon /></template>\n {{ messages.table.moveUp }}\n </DouDropdownItem>\n <DouDropdownItem :disabled=\"!can(moveLines('row', 1))\" @click=\"run(moveLines('row', 1))\">\n <template #prefix><ArrowDownIcon /></template>\n {{ messages.table.moveDown }}\n </DouDropdownItem>\n <DouDropdownItem\n v-if=\"target?.rect.top === 0\"\n divided\n @click=\"run(toggleHeader('headerRow'))\"\n >\n <template #prefix><LayoutRowsIcon /></template>\n {{ messages.table.headerRow }}\n <template v-if=\"tableNode?.attrs.headerRow\" #suffix><CheckIcon /></template>\n </DouDropdownItem>\n <DouDropdownItem divided @click=\"run(clearCells)\">\n <template #prefix><EraserIcon /></template>\n {{ messages.table.clear }}\n </DouDropdownItem>\n <DouDropdownItem tone=\"danger\" :disabled=\"!can(deleteRows)\" @click=\"run(deleteRows)\">\n <template #prefix><TrashIcon /></template>\n {{ messages.table.deleteRow }}\n </DouDropdownItem>\n </template>\n </DouDropdown>\n\n <!-- + bars -->\n <button\n type=\"button\"\n tabindex=\"-1\"\n class=\"dou-editor-table__add is-column\"\n :style=\"{\n top: `${layout.table.top}px`,\n left: `${Math.min(layout.table.left + layout.table.width, layout.frame.right)}px`,\n height: `${layout.table.height}px`,\n }\"\n :aria-label=\"messages.table.addColumn\"\n :title=\"messages.table.addColumn\"\n @click=\"addAtEnd('column')\"\n >\n <PlusIcon />\n </button>\n <button\n type=\"button\"\n tabindex=\"-1\"\n class=\"dou-editor-table__add is-row\"\n :style=\"{\n top: `${layout.table.top + layout.table.height}px`,\n left: `${layout.frame.left}px`,\n width: `${Math.min(layout.table.left + layout.table.width, layout.frame.right) - layout.frame.left}px`,\n }\"\n :aria-label=\"messages.table.addRow\"\n :title=\"messages.table.addRow\"\n @click=\"addAtEnd('row')\"\n >\n <PlusIcon />\n </button>\n\n <!-- where dragged lines go -->\n <div\n v-if=\"drop\"\n class=\"dou-editor__drop\"\n :class=\"{ 'is-vertical': drop.axis === 'column' }\"\n :style=\"\n drop.axis === 'row'\n ? {\n top: `${drop.at}px`,\n left: `${layout.frame.left}px`,\n width: `${Math.min(layout.table.left + layout.table.width, layout.frame.right) - layout.frame.left}px`,\n }\n : {\n top: `${layout.table.top}px`,\n left: `${drop.at}px`,\n height: `${layout.table.height}px`,\n }\n \"\n />\n\n <!-- cells: merge, unmerge, clear -->\n <div\n v-if=\"barShown && layout.bar && !dragging\"\n class=\"dou-editor-table__cells\"\n :class=\"{ 'is-below': layout.bar.below }\"\n :style=\"{ top: `${layout.bar.top}px`, left: `${layout.bar.left}px` }\"\n >\n <button\n v-if=\"hasCellSelection\"\n type=\"button\"\n tabindex=\"-1\"\n class=\"dou-editor-table__cells-button\"\n :disabled=\"!can(mergeCells)\"\n @click=\"run(mergeCells)\"\n >\n <MergeIcon />{{ messages.table.merge }}\n </button>\n <button\n v-if=\"can(splitMergedCell)\"\n type=\"button\"\n tabindex=\"-1\"\n class=\"dou-editor-table__cells-button\"\n @click=\"run(splitMergedCell)\"\n >\n <SplitIcon />{{ messages.table.split }}\n </button>\n <button\n v-if=\"hasCellSelection\"\n type=\"button\"\n tabindex=\"-1\"\n class=\"dou-editor-table__cells-button\"\n :aria-label=\"messages.table.clear\"\n :title=\"messages.table.clear\"\n @click=\"run(clearCells)\"\n >\n <EraserIcon />\n </button>\n </div>\n </div>\n</template>\n"],"mappings":""}
|