@dou-ui/sheet 0.0.0-stage → 0.1.0-alpha.0
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/cell-types.js +20 -0
- package/dist/cell-types.js.map +1 -0
- package/dist/code/code-formula-bar.js +6 -0
- package/dist/code/code-formula-bar.js.map +1 -0
- package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js +192 -0
- package/dist/code/code-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/code/index.js +12 -0
- package/dist/code/index.js.map +1 -0
- package/dist/code/language.js +51 -0
- package/dist/code/language.js.map +1 -0
- package/dist/code/style.d.ts +1 -0
- package/dist/code/style.js +2 -0
- package/dist/collaboration/anchor.js +143 -0
- package/dist/collaboration/anchor.js.map +1 -0
- package/dist/collaboration/index.js +770 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/peers.js +87 -0
- package/dist/collaboration/peers.js.map +1 -0
- package/dist/collaboration/shared.js +450 -0
- package/dist/collaboration/shared.js.map +1 -0
- package/dist/components/cell-panel.js +6 -0
- package/dist/components/cell-panel.js.map +1 -0
- package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js +70 -0
- package/dist/components/cell-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/cell-renders.js +40 -0
- package/dist/components/cell-renders.js.map +1 -0
- package/dist/components/date-panel.js +6 -0
- package/dist/components/date-panel.js.map +1 -0
- package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js +48 -0
- package/dist/components/date-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/filter-panel.js +6 -0
- package/dist/components/filter-panel.js.map +1 -0
- package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js +259 -0
- package/dist/components/filter-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-dialog.js +6 -0
- package/dist/components/format-dialog.js.map +1 -0
- package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js +432 -0
- package/dist/components/format-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-list.js +6 -0
- package/dist/components/format-list.js.map +1 -0
- package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js +187 -0
- package/dist/components/format-list.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/format-presets.js +97 -0
- package/dist/components/format-presets.js.map +1 -0
- package/dist/components/option-panel.js +6 -0
- package/dist/components/option-panel.js.map +1 -0
- package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js +117 -0
- package/dist/components/option-panel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-comment.js +6 -0
- package/dist/components/sheet-comment.js.map +1 -0
- package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js +104 -0
- package/dist/components/sheet-comment.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-formula-bar.js +6 -0
- package/dist/components/sheet-formula-bar.js.map +1 -0
- package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js +124 -0
- package/dist/components/sheet-formula-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-link.js +6 -0
- package/dist/components/sheet-link.js.map +1 -0
- package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js +249 -0
- package/dist/components/sheet-link.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-menu.js +6 -0
- package/dist/components/sheet-menu.js.map +1 -0
- package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js +115 -0
- package/dist/components/sheet-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-name-box.js +6 -0
- package/dist/components/sheet-name-box.js.map +1 -0
- package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js +91 -0
- package/dist/components/sheet-name-box.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-note.js +6 -0
- package/dist/components/sheet-note.js.map +1 -0
- package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js +146 -0
- package/dist/components/sheet-note.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-outline.js +6 -0
- package/dist/components/sheet-outline.js.map +1 -0
- package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js +237 -0
- package/dist/components/sheet-outline.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-tabs.js +6 -0
- package/dist/components/sheet-tabs.js.map +1 -0
- package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js +487 -0
- package/dist/components/sheet-tabs.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/sheet-toolbar.js +6 -0
- package/dist/components/sheet-toolbar.js.map +1 -0
- package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js +1562 -0
- package/dist/components/sheet-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/validation-dialog.js +6 -0
- package/dist/components/validation-dialog.js.map +1 -0
- package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js +370 -0
- package/dist/components/validation-dialog.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/context.js +12 -0
- package/dist/context.js.map +1 -0
- package/dist/core/address.js +256 -0
- package/dist/core/address.js.map +1 -0
- package/dist/core/axis.js +72 -0
- package/dist/core/axis.js.map +1 -0
- package/dist/core/cell-type.js +30 -0
- package/dist/core/cell-type.js.map +1 -0
- package/dist/core/clipboard.js +141 -0
- package/dist/core/clipboard.js.map +1 -0
- package/dist/core/commands.js +63 -0
- package/dist/core/commands.js.map +1 -0
- package/dist/core/comments.js +74 -0
- package/dist/core/comments.js.map +1 -0
- package/dist/core/conditional.js +362 -0
- package/dist/core/conditional.js.map +1 -0
- package/dist/core/csv.js +191 -0
- package/dist/core/csv.js.map +1 -0
- package/dist/core/data.js +118 -0
- package/dist/core/data.js.map +1 -0
- package/dist/core/document.js +187 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/extension.js +60 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/file.js +336 -0
- package/dist/core/file.js.map +1 -0
- package/dist/core/fill.js +153 -0
- package/dist/core/fill.js.map +1 -0
- package/dist/core/find.js +42 -0
- package/dist/core/find.js.map +1 -0
- package/dist/core/format.js +404 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/group.js +134 -0
- package/dist/core/group.js.map +1 -0
- package/dist/core/history.js +68 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/index.js +17 -0
- package/dist/core/link.js +34 -0
- package/dist/core/link.js.map +1 -0
- package/dist/core/merge.js +105 -0
- package/dist/core/merge.js.map +1 -0
- package/dist/core/model.js +48 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/nav.js +44 -0
- package/dist/core/nav.js.map +1 -0
- package/dist/core/paint.js +106 -0
- package/dist/core/paint.js.map +1 -0
- package/dist/core/plain-cell-types.js +54 -0
- package/dist/core/plain-cell-types.js.map +1 -0
- package/dist/core/protect.js +246 -0
- package/dist/core/protect.js.map +1 -0
- package/dist/core/selection.js +37 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/transaction.js +1582 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/validation.js +135 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/core/versions.js +161 -0
- package/dist/core/versions.js.map +1 -0
- package/dist/export/color.js +36 -0
- package/dist/export/color.js.map +1 -0
- package/dist/export/draw.js +316 -0
- package/dist/export/draw.js.map +1 -0
- package/dist/export/index.js +390 -0
- package/dist/export/index.js.map +1 -0
- package/dist/formula/edit.js +210 -0
- package/dist/formula/edit.js.map +1 -0
- package/dist/formula/engine.js +526 -0
- package/dist/formula/engine.js.map +1 -0
- package/dist/formula/error.js +39 -0
- package/dist/formula/error.js.map +1 -0
- package/dist/formula/functions.js +1493 -0
- package/dist/formula/functions.js.map +1 -0
- package/dist/formula/help.js +408 -0
- package/dist/formula/help.js.map +1 -0
- package/dist/formula/index.js +7 -0
- package/dist/formula/parse.js +369 -0
- package/dist/formula/parse.js.map +1 -0
- package/dist/formula/values.js +123 -0
- package/dist/formula/values.js.map +1 -0
- package/dist/formulas.js +30 -0
- package/dist/formulas.js.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/locale.js +1312 -0
- package/dist/locale.js.map +1 -0
- package/dist/sheet.js +6 -0
- package/dist/sheet.js.map +1 -0
- package/dist/sheet.vue_vue_type_script_setup_true_lang.js +7166 -0
- package/dist/sheet.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +17 -0
- package/dist/types/cell-types.d.ts +2 -0
- package/dist/types/code/code-formula-bar.vue.d.ts +3 -0
- package/dist/types/code/index.d.ts +12 -0
- package/dist/types/code/language.d.ts +13 -0
- package/dist/types/collaboration/anchor.d.ts +65 -0
- package/dist/types/collaboration/index.d.ts +59 -0
- package/dist/types/collaboration/peers.d.ts +21 -0
- package/dist/types/collaboration/shared.d.ts +75 -0
- package/dist/types/components/cell-panel.vue.d.ts +20 -0
- package/dist/types/components/cell-renders.d.ts +9 -0
- package/dist/types/components/date-panel.vue.d.ts +9 -0
- package/dist/types/components/filter-panel.vue.d.ts +21 -0
- package/dist/types/components/format-dialog.vue.d.ts +24 -0
- package/dist/types/components/format-list.vue.d.ts +25 -0
- package/dist/types/components/format-presets.d.ts +30 -0
- package/dist/types/components/option-panel.vue.d.ts +13 -0
- package/dist/types/components/sheet-comment.vue.d.ts +29 -0
- package/dist/types/components/sheet-formula-bar.vue.d.ts +3 -0
- package/dist/types/components/sheet-link.vue.d.ts +32 -0
- package/dist/types/components/sheet-menu.vue.d.ts +20 -0
- package/dist/types/components/sheet-name-box.vue.d.ts +3 -0
- package/dist/types/components/sheet-note.vue.d.ts +22 -0
- package/dist/types/components/sheet-outline.vue.d.ts +36 -0
- package/dist/types/components/sheet-tabs.vue.d.ts +9 -0
- package/dist/types/components/sheet-toolbar.vue.d.ts +11 -0
- package/dist/types/components/validation-dialog.vue.d.ts +25 -0
- package/dist/types/context.d.ts +189 -0
- package/dist/types/core/address.d.ts +109 -0
- package/dist/types/core/axis.d.ts +23 -0
- package/dist/types/core/cell-type.d.ts +157 -0
- package/dist/types/core/clipboard.d.ts +48 -0
- package/dist/types/core/commands.d.ts +76 -0
- package/dist/types/core/comments.d.ts +22 -0
- package/dist/types/core/conditional.d.ts +114 -0
- package/dist/types/core/csv.d.ts +93 -0
- package/dist/types/core/data.d.ts +26 -0
- package/dist/types/core/document.d.ts +49 -0
- package/dist/types/core/extension.d.ts +177 -0
- package/dist/types/core/file.d.ts +301 -0
- package/dist/types/core/fill.d.ts +23 -0
- package/dist/types/core/find.d.ts +39 -0
- package/dist/types/core/format.d.ts +47 -0
- package/dist/types/core/group.d.ts +76 -0
- package/dist/types/core/history.d.ts +27 -0
- package/dist/types/core/index.d.ts +33 -0
- package/dist/types/core/link.d.ts +14 -0
- package/dist/types/core/merge.d.ts +26 -0
- package/dist/types/core/model.d.ts +363 -0
- package/dist/types/core/nav.d.ts +15 -0
- package/dist/types/core/paint.d.ts +19 -0
- package/dist/types/core/plain-cell-types.d.ts +6 -0
- package/dist/types/core/protect.d.ts +34 -0
- package/dist/types/core/selection.d.ts +25 -0
- package/dist/types/core/transaction.d.ts +470 -0
- package/dist/types/core/validation.d.ts +55 -0
- package/dist/types/core/versions.d.ts +38 -0
- package/dist/types/export/color.d.ts +7 -0
- package/dist/types/export/draw.d.ts +98 -0
- package/dist/types/export/index.d.ts +47 -0
- package/dist/types/formula/edit.d.ts +62 -0
- package/dist/types/formula/engine.d.ts +99 -0
- package/dist/types/formula/error.d.ts +30 -0
- package/dist/types/formula/functions.d.ts +62 -0
- package/dist/types/formula/help.d.ts +12 -0
- package/dist/types/formula/index.d.ts +13 -0
- package/dist/types/formula/parse.d.ts +92 -0
- package/dist/types/formula/values.d.ts +65 -0
- package/dist/types/formulas.d.ts +32 -0
- package/dist/types/index.d.ts +225 -0
- package/dist/types/locale.d.ts +531 -0
- package/dist/types/sheet.vue.d.ts +60 -0
- package/dist/types/types.d.ts +498 -0
- package/dist/types/use-comments.d.ts +47 -0
- package/dist/types/use-messages.d.ts +7 -0
- package/dist/use-comments.js +130 -0
- package/dist/use-comments.js.map +1 -0
- package/dist/use-messages.js +29 -0
- package/dist/use-messages.js.map +1 -0
- package/package.json +93 -4
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { formatValue, parseInput } from "./format.js";
|
|
2
|
+
import { cellOf, valueOf } from "./model.js";
|
|
3
|
+
import { shownValue } from "../formula/values.js";
|
|
4
|
+
import { parseCSV } from "./csv.js";
|
|
5
|
+
import { linkTarget } from "./link.js";
|
|
6
|
+
// src/core/clipboard.ts
|
|
7
|
+
var MARK = "data-dou-sheet";
|
|
8
|
+
/** A cell's text in tab-separated text: quoted when it has a tab, a line break or a quote. */
|
|
9
|
+
function tsvField(text) {
|
|
10
|
+
return /[\t\n\r"]/.test(text) ? `"${text.replaceAll("\"", "\"\"")}"` : text;
|
|
11
|
+
}
|
|
12
|
+
var escapeHTML = (text) => text.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
13
|
+
function cssOf(style, align) {
|
|
14
|
+
if (!style && !align) return "";
|
|
15
|
+
let css = "";
|
|
16
|
+
if (style?.bold) css += "font-weight:bold;";
|
|
17
|
+
if (style?.italic) css += "font-style:italic;";
|
|
18
|
+
const lines = [style?.underline && "underline", style?.strike && "line-through"].filter(Boolean);
|
|
19
|
+
if (lines.length) css += `text-decoration:${lines.join(" ")};`;
|
|
20
|
+
if (style?.color) css += `color:${style.color};`;
|
|
21
|
+
if (style?.background) css += `background-color:${style.background};`;
|
|
22
|
+
if (style?.wrap) css += "white-space:pre-wrap;";
|
|
23
|
+
const textAlign = style?.align ?? align;
|
|
24
|
+
if (textAlign) css += `text-align:${textAlign};`;
|
|
25
|
+
return css;
|
|
26
|
+
}
|
|
27
|
+
/** The clipboard's text and HTML for a block of cells, with the text as the cells show it. */
|
|
28
|
+
function formatClip(clip, options = {}) {
|
|
29
|
+
const { locale, text: textOf, result } = options;
|
|
30
|
+
const styles = clip.styles ?? {};
|
|
31
|
+
const text = [];
|
|
32
|
+
let rows = "";
|
|
33
|
+
for (const [row, line] of clip.cells.entries()) {
|
|
34
|
+
const fields = [];
|
|
35
|
+
let cols = "";
|
|
36
|
+
for (const [col, input] of line.entries()) {
|
|
37
|
+
const cell = cellOf(input);
|
|
38
|
+
const value = shownValue(valueOf(input), result, row, col);
|
|
39
|
+
const shown = textOf?.(input, value) ?? formatValue(value, cell?.format, locale);
|
|
40
|
+
fields.push(tsvField(shown));
|
|
41
|
+
const css = cssOf(cell?.style ? styles[cell.style] : void 0, typeof value === "number" ? "right" : void 0);
|
|
42
|
+
let body = escapeHTML(shown).replaceAll("\n", "<br>");
|
|
43
|
+
const target = linkTarget(cell?.link);
|
|
44
|
+
if (target?.kind === "web") body = `<a href="${escapeHTML(target.href)}">${body}</a>`;
|
|
45
|
+
cols += `<td${css ? ` style="${css}"` : ""}>${body}</td>`;
|
|
46
|
+
}
|
|
47
|
+
text.push(fields.join(" "));
|
|
48
|
+
rows += `<tr>${cols}</tr>`;
|
|
49
|
+
}
|
|
50
|
+
const data = escapeHTML(JSON.stringify(clip));
|
|
51
|
+
return {
|
|
52
|
+
text: text.join("\n"),
|
|
53
|
+
html: `<table ${MARK}="${data}" style="border-collapse:collapse"><tbody>${rows}</tbody></table>`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Splits tab-separated text into rows of fields, reading quoted fields. */
|
|
57
|
+
function parseTSV(text) {
|
|
58
|
+
return parseCSV(text, " ");
|
|
59
|
+
}
|
|
60
|
+
/** The text a table cell shows, with its line breaks. */
|
|
61
|
+
function cellText(cell) {
|
|
62
|
+
let text = "";
|
|
63
|
+
const walk = (node) => {
|
|
64
|
+
if (node.nodeType === 3) text += node.textContent?.replace(/\s+/g, " ") ?? "";
|
|
65
|
+
else if (node.nodeName === "BR") text += "\n";
|
|
66
|
+
else {
|
|
67
|
+
if (/^(P|DIV|LI)$/.test(node.nodeName) && text !== "" && !text.endsWith("\n")) text += "\n";
|
|
68
|
+
node.childNodes.forEach(walk);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
walk(cell);
|
|
72
|
+
return text.replace(/ *\n */g, "\n").trim();
|
|
73
|
+
}
|
|
74
|
+
/** Reads a typed-looking text into a cell, keeping the format it implies. */
|
|
75
|
+
function inputOf(text) {
|
|
76
|
+
const { value, format } = parseInput(text);
|
|
77
|
+
if (value == null) return void 0;
|
|
78
|
+
return format ? {
|
|
79
|
+
value,
|
|
80
|
+
format
|
|
81
|
+
} : value;
|
|
82
|
+
}
|
|
83
|
+
/** Reads a table from HTML: rows and cells, with merged cells spread over the cells they cover. */
|
|
84
|
+
function readTable(table) {
|
|
85
|
+
const out = [];
|
|
86
|
+
const taken = [];
|
|
87
|
+
let r = 0;
|
|
88
|
+
for (const tr of table.querySelectorAll("tr")) {
|
|
89
|
+
if (tr.closest("table") !== table) continue;
|
|
90
|
+
const line = out[r] ??= [];
|
|
91
|
+
let c = 0;
|
|
92
|
+
for (const td of tr.children) {
|
|
93
|
+
if (td.nodeName !== "TD" && td.nodeName !== "TH") continue;
|
|
94
|
+
while (taken[r]?.[c]) c++;
|
|
95
|
+
line[c] = inputOf(cellText(td));
|
|
96
|
+
const anchors = td.querySelectorAll("a[href]");
|
|
97
|
+
const href = anchors.length === 1 ? anchors[0].getAttribute("href") : null;
|
|
98
|
+
const target = href ? linkTarget(href) : null;
|
|
99
|
+
if (target?.kind === "web") line[c] = {
|
|
100
|
+
...cellOf(line[c]),
|
|
101
|
+
link: target.href
|
|
102
|
+
};
|
|
103
|
+
const rowSpan = Math.max(1, Number(td.getAttribute("rowspan")) || 1);
|
|
104
|
+
const colSpan = Math.max(1, Number(td.getAttribute("colspan")) || 1);
|
|
105
|
+
for (let dr = 0; dr < rowSpan; dr++) for (let dc = 0; dc < colSpan; dc++) {
|
|
106
|
+
if (dr === 0 && dc === 0) continue;
|
|
107
|
+
(taken[r + dr] ??= [])[c + dc] = true;
|
|
108
|
+
if (dr === 0) line[c + dc] = void 0;
|
|
109
|
+
}
|
|
110
|
+
c += colSpan;
|
|
111
|
+
}
|
|
112
|
+
r++;
|
|
113
|
+
}
|
|
114
|
+
return out.map((line) => Array.from(line, (cell) => cell));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Reads what was pasted. Cells copied from a sheet come back as they were;
|
|
118
|
+
* a table from elsewhere, or tab-separated text, comes back as values read
|
|
119
|
+
* the way typing them would, with formats such as `0%` they imply.
|
|
120
|
+
* `null` when there is nothing to paste.
|
|
121
|
+
*/
|
|
122
|
+
function parseClip(data) {
|
|
123
|
+
if (data.html && typeof DOMParser !== "undefined") {
|
|
124
|
+
const page = new DOMParser().parseFromString(data.html, "text/html");
|
|
125
|
+
const marked = page.querySelector(`table[${MARK}]`);
|
|
126
|
+
if (marked) try {
|
|
127
|
+
const clip = JSON.parse(marked.getAttribute(MARK));
|
|
128
|
+
if (Array.isArray(clip.cells)) return clip;
|
|
129
|
+
} catch {}
|
|
130
|
+
const table = page.querySelector("table");
|
|
131
|
+
if (table) {
|
|
132
|
+
const cells = readTable(table);
|
|
133
|
+
if (cells.length) return { cells };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (data.text == null || data.text === "") return null;
|
|
137
|
+
return { cells: parseTSV(data.text.replace(/\r?\n$/, "")).map((line) => line.map(inputOf)) };
|
|
138
|
+
}
|
|
139
|
+
export { formatClip, parseClip, parseTSV };
|
|
140
|
+
|
|
141
|
+
//# sourceMappingURL=clipboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.js","names":[],"sources":["../../src/core/clipboard.ts"],"sourcesContent":["import { parseCSV } from './csv'\nimport { formatValue, parseInput } from './format'\nimport { linkTarget } from './link'\nimport { cellOf, valueOf, type SheetCellInput, type SheetStyle, type SheetValue } from './model'\nimport { shownValue, type SheetResult } from '../formula/values'\n\n/*\n * Copying cells out as text and as a table, and reading them back from\n * either, so a block of cells moves between this grid, other spreadsheet\n * programs and web pages.\n *\n * Plain text is tab-separated, one row per line, with what each cell shows.\n * The table carries the same text with simple formatting for programs that\n * read tables, and the cells themselves (values, formats and styles) for\n * pasting into a sheet again without losing anything.\n */\n\n/** A block of cells on the clipboard. */\nexport interface SheetClip {\n /** The cells, row by row. */\n cells: SheetCellInput[][]\n /** The named styles the cells use. */\n styles?: Record<string, SheetStyle>\n /** Where the block was copied from, so formulas can move along when pasted. */\n from?: { row: number; col: number }\n /** Set when the block was cut: pasting it moves the cells. */\n cut?: string\n}\n\nconst MARK = 'data-dou-sheet'\n\n/** A cell's text in tab-separated text: quoted when it has a tab, a line break or a quote. */\nfunction tsvField(text: string) {\n return /[\\t\\n\\r\"]/.test(text) ? `\"${text.replaceAll('\"', '\"\"')}\"` : text\n}\n\nconst escapeHTML = (text: string) => text.replace(/[&<>\"']/g, (c) => `&#${c.charCodeAt(0)};`)\n\nfunction cssOf(style: SheetStyle | undefined, align: string | undefined) {\n if (!style && !align) return ''\n let css = ''\n if (style?.bold) css += 'font-weight:bold;'\n if (style?.italic) css += 'font-style:italic;'\n const lines = [style?.underline && 'underline', style?.strike && 'line-through'].filter(Boolean)\n if (lines.length) css += `text-decoration:${lines.join(' ')};`\n if (style?.color) css += `color:${style.color};`\n if (style?.background) css += `background-color:${style.background};`\n if (style?.wrap) css += 'white-space:pre-wrap;'\n const textAlign = style?.align ?? align\n if (textAlign) css += `text-align:${textAlign};`\n return css\n}\n\n/** How `formatClip` writes cells as text. */\nexport interface SheetClipFormat {\n /** The language dates are shown in. */\n locale?: string\n /**\n * What a formula comes to, by its row and column in the clip, e.g. from\n * `SheetFormulaEngine.result`; the formula itself by default.\n */\n result?: (row: number, col: number) => SheetResult\n /**\n * A cell's text, e.g. as its cell type shows it; `undefined` uses its\n * format. `value` is its value, or its formula's result.\n */\n text?: (input: SheetCellInput, value: SheetValue) => string | undefined\n}\n\n/** The clipboard's text and HTML for a block of cells, with the text as the cells show it. */\nexport function formatClip(clip: SheetClip, options: SheetClipFormat = {}) {\n const { locale, text: textOf, result } = options\n const styles = clip.styles ?? {}\n const text: string[] = []\n let rows = ''\n for (const [row, line] of clip.cells.entries()) {\n const fields: string[] = []\n let cols = ''\n for (const [col, input] of line.entries()) {\n const cell = cellOf(input)\n const value = shownValue(valueOf(input), result, row, col)\n const shown = textOf?.(input, value) ?? formatValue(value, cell?.format, locale)\n fields.push(tsvField(shown))\n const style = cell?.style ? styles[cell.style] : undefined\n const css = cssOf(style, typeof value === 'number' ? 'right' : undefined)\n let body = escapeHTML(shown).replaceAll('\\n', '<br>')\n // a link to a web page stays one in other programs\n const target = linkTarget(cell?.link)\n if (target?.kind === 'web') body = `<a href=\"${escapeHTML(target.href)}\">${body}</a>`\n cols += `<td${css ? ` style=\"${css}\"` : ''}>${body}</td>`\n }\n text.push(fields.join('\\t'))\n rows += `<tr>${cols}</tr>`\n }\n const data = escapeHTML(JSON.stringify(clip))\n return {\n text: text.join('\\n'),\n html: `<table ${MARK}=\"${data}\" style=\"border-collapse:collapse\"><tbody>${rows}</tbody></table>`,\n }\n}\n\n/** Splits tab-separated text into rows of fields, reading quoted fields. */\nexport function parseTSV(text: string): string[][] {\n return parseCSV(text, '\\t')\n}\n\n/** The text a table cell shows, with its line breaks. */\nfunction cellText(cell: Element) {\n let text = ''\n const walk = (node: Node) => {\n if (node.nodeType === 3) text += node.textContent?.replace(/\\s+/g, ' ') ?? ''\n else if (node.nodeName === 'BR') text += '\\n'\n else {\n const block = /^(P|DIV|LI)$/.test(node.nodeName) && text !== '' && !text.endsWith('\\n')\n if (block) text += '\\n'\n node.childNodes.forEach(walk)\n }\n }\n walk(cell)\n return text.replace(/ *\\n */g, '\\n').trim()\n}\n\n/** Reads a typed-looking text into a cell, keeping the format it implies. */\nfunction inputOf(text: string): SheetCellInput {\n const { value, format } = parseInput(text)\n if (value == null) return undefined\n return format ? { value, format } : value\n}\n\n/** Reads a table from HTML: rows and cells, with merged cells spread over the cells they cover. */\nfunction readTable(table: Element): SheetCellInput[][] {\n const out: SheetCellInput[][] = []\n // cells taken by a row span from above\n const taken: boolean[][] = []\n let r = 0\n for (const tr of table.querySelectorAll('tr')) {\n if (tr.closest('table') !== table) continue\n const line = (out[r] ??= [])\n let c = 0\n for (const td of tr.children) {\n if (td.nodeName !== 'TD' && td.nodeName !== 'TH') continue\n while (taken[r]?.[c]) c++\n line[c] = inputOf(cellText(td))\n // a cell that is one link to a web page keeps it\n const anchors = td.querySelectorAll('a[href]')\n const href = anchors.length === 1 ? anchors[0]!.getAttribute('href') : null\n const target = href ? linkTarget(href) : null\n if (target?.kind === 'web') line[c] = { ...cellOf(line[c]), link: target.href }\n const rowSpan = Math.max(1, Number(td.getAttribute('rowspan')) || 1)\n const colSpan = Math.max(1, Number(td.getAttribute('colspan')) || 1)\n for (let dr = 0; dr < rowSpan; dr++)\n for (let dc = 0; dc < colSpan; dc++) {\n if (dr === 0 && dc === 0) continue\n ;(taken[r + dr] ??= [])[c + dc] = true\n if (dr === 0) line[c + dc] = undefined\n }\n c += colSpan\n }\n r++\n }\n return out.map((line) => Array.from(line, (cell) => cell))\n}\n\n/**\n * Reads what was pasted. Cells copied from a sheet come back as they were;\n * a table from elsewhere, or tab-separated text, comes back as values read\n * the way typing them would, with formats such as `0%` they imply.\n * `null` when there is nothing to paste.\n */\nexport function parseClip(data: { html?: string; text?: string }): SheetClip | null {\n if (data.html && typeof DOMParser !== 'undefined') {\n const page = new DOMParser().parseFromString(data.html, 'text/html')\n const marked = page.querySelector(`table[${MARK}]`)\n if (marked) {\n try {\n const clip = JSON.parse(marked.getAttribute(MARK)!) as SheetClip\n if (Array.isArray(clip.cells)) return clip\n } catch {\n // not ours after all: read it as a table\n }\n }\n const table = page.querySelector('table')\n if (table) {\n const cells = readTable(table)\n if (cells.length) return { cells }\n }\n }\n if (data.text == null || data.text === '') return null\n // a single line copied from a page may end with a line break\n const text = data.text.replace(/\\r?\\n$/, '')\n return { cells: parseTSV(text).map((line) => line.map(inputOf)) }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/core/commands.ts
|
|
2
|
+
var labelOf = (label, messages) => typeof label === "function" ? label(messages) : label ?? "";
|
|
3
|
+
/** Declares a command; only gives it a type. */
|
|
4
|
+
function defineSheetCommand(command) {
|
|
5
|
+
return command;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Puts the built-in commands and the extensions' together. An extension's
|
|
9
|
+
* command with a name already taken is an error, unless it says `override`
|
|
10
|
+
* and the name is a built-in one.
|
|
11
|
+
*/
|
|
12
|
+
function mergeCommands(builtin, added) {
|
|
13
|
+
const map = new Map(Object.entries(builtin));
|
|
14
|
+
const owner = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const { extension, commands } of added) for (const name in commands) {
|
|
16
|
+
const command = commands[name];
|
|
17
|
+
const taken = owner.get(name);
|
|
18
|
+
if (taken) throw new Error(`[@dou-ui/sheet] the command "${name}" is added by both "${taken}" and "${extension}"`);
|
|
19
|
+
if (map.has(name) && !command.override) throw new Error(`[@dou-ui/sheet] "${extension}" adds the command "${name}", which the sheet already has; set \`override: true\` to replace it`);
|
|
20
|
+
owner.set(name, extension);
|
|
21
|
+
map.set(name, command);
|
|
22
|
+
}
|
|
23
|
+
return map;
|
|
24
|
+
}
|
|
25
|
+
/** The `commands` of a context, over the commands by name. */
|
|
26
|
+
function createCommands(commands, context, builtin = {}) {
|
|
27
|
+
const find = (name) => {
|
|
28
|
+
const command = commands().get(name);
|
|
29
|
+
if (!command) throw new Error(`[@dou-ui/sheet] there is no command "${name}"`);
|
|
30
|
+
return command;
|
|
31
|
+
};
|
|
32
|
+
const can = (name) => {
|
|
33
|
+
const command = commands().get(name);
|
|
34
|
+
return !!command && (command.enabled?.(context()) ?? true);
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
run(name, arg) {
|
|
38
|
+
const command = find(name);
|
|
39
|
+
const ctx = context();
|
|
40
|
+
if (command.enabled && !command.enabled(ctx)) return false;
|
|
41
|
+
return command.run(ctx, arg) !== false;
|
|
42
|
+
},
|
|
43
|
+
can,
|
|
44
|
+
pressed: (name) => !!commands().get(name)?.pressed?.(context()),
|
|
45
|
+
get(name) {
|
|
46
|
+
const command = commands().get(name);
|
|
47
|
+
if (!command) return void 0;
|
|
48
|
+
return {
|
|
49
|
+
name,
|
|
50
|
+
label: labelOf(command.label, context().messages.value),
|
|
51
|
+
icon: command.icon,
|
|
52
|
+
shortcut: command.shortcut,
|
|
53
|
+
toggle: !!command.pressed,
|
|
54
|
+
builtin: builtin[name] === command
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
has: (name) => commands().has(name),
|
|
58
|
+
names: () => [...commands().keys()]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export { createCommands, defineSheetCommand, labelOf, mergeCommands };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","names":[],"sources":["../../src/core/commands.ts"],"sourcesContent":["import type { Component } from 'vue'\nimport type { SheetContext } from '../context'\nimport type { SheetMessages } from '../locale'\n\n/** Text shown in a menu or tooltip: fixed, or read from the sheet's messages so it follows the language. */\nexport type SheetLabel = string | ((messages: SheetMessages) => string)\n\nexport const labelOf = (label: SheetLabel | undefined, messages: SheetMessages) =>\n typeof label === 'function' ? label(messages) : (label ?? '')\n\n/**\n * Something the sheet can do, by name: toolbar items, menu items and keys\n * can all refer to it, and take its label, icon and state from it.\n */\nexport interface SheetCommand<Arg = any> {\n label: SheetLabel\n icon?: Component\n /**\n * The key shown beside it, written as in `useHotkey` (e.g. `mod+shift+e`).\n * Only a hint: bind the key with the extension's `keymap`.\n */\n shortcut?: string\n /**\n * Does it. `arg` is what the caller passes to `commands.run`, if anything.\n * Return `false` when there was nothing to do.\n */\n run: (context: SheetContext, arg?: Arg) => boolean | void | Promise<unknown>\n /** Whether it can be run now; greyed out in menus and the toolbar when not. Defaults to `true`. */\n enabled?: (context: SheetContext) => boolean\n /** For a command that turns something on and off: whether it is on, e.g. bold for a bold cell. */\n pressed?: (context: SheetContext) => boolean\n /**\n * Lets it replace the built-in command of the same name. Without it, a\n * name the sheet already has is an error, so a command isn't replaced by accident.\n */\n override?: boolean\n}\n\n/** Declares a command; only gives it a type. */\nexport function defineSheetCommand<Arg = any>(command: SheetCommand<Arg>): SheetCommand<Arg> {\n return command\n}\n\n/** A command as menus and the toolbar show it. */\nexport interface SheetCommandInfo {\n name: string\n label: string\n icon?: Component\n shortcut?: string\n /** Whether it turns something on and off (it has `pressed`). */\n toggle: boolean\n /** Whether it is the sheet's own, not added or replaced by an extension. */\n builtin: boolean\n}\n\n/** The commands of a sheet, by name, for `useSheet().commands`. */\nexport interface SheetCommands {\n /**\n * Runs a command. Returns `false` when it can't be run now (`enabled`\n * says no) or it had nothing to do; `true` otherwise, also while an\n * asynchronous one is still running. An unknown name is an error.\n */\n run: (name: string, arg?: unknown) => boolean\n /** Whether a command is there and can be run now. */\n can: (name: string) => boolean\n /** Whether a command that turns something on and off is on. */\n pressed: (name: string) => boolean\n /** A command's label, icon and shortcut, or `undefined` when there is none by that name. */\n get: (name: string) => SheetCommandInfo | undefined\n /** Whether there is a command by that name. */\n has: (name: string) => boolean\n /** The names of every command, built in and added. */\n names: () => string[]\n}\n\n/**\n * Puts the built-in commands and the extensions' together. An extension's\n * command with a name already taken is an error, unless it says `override`\n * and the name is a built-in one.\n */\nexport function mergeCommands(\n builtin: Readonly<Record<string, SheetCommand>>,\n added: readonly { extension: string; commands: Readonly<Record<string, SheetCommand>> }[],\n) {\n const map = new Map<string, SheetCommand>(Object.entries(builtin))\n const owner = new Map<string, string>()\n for (const { extension, commands } of added)\n for (const name in commands) {\n const command = commands[name]!\n const taken = owner.get(name)\n if (taken)\n throw new Error(\n `[@dou-ui/sheet] the command \"${name}\" is added by both \"${taken}\" and \"${extension}\"`,\n )\n if (map.has(name) && !command.override)\n throw new Error(\n `[@dou-ui/sheet] \"${extension}\" adds the command \"${name}\", which the sheet already has; set \\`override: true\\` to replace it`,\n )\n owner.set(name, extension)\n map.set(name, command)\n }\n return map\n}\n\n/** The `commands` of a context, over the commands by name. */\nexport function createCommands(\n commands: () => ReadonlyMap<string, SheetCommand>,\n context: () => SheetContext,\n builtin: Readonly<Record<string, SheetCommand>> = {},\n): SheetCommands {\n const find = (name: string) => {\n const command = commands().get(name)\n if (!command) throw new Error(`[@dou-ui/sheet] there is no command \"${name}\"`)\n return command\n }\n const can = (name: string) => {\n const command = commands().get(name)\n return !!command && (command.enabled?.(context()) ?? true)\n }\n return {\n run(name, arg) {\n const command = find(name)\n const ctx = context()\n if (command.enabled && !command.enabled(ctx)) return false\n // a promise counts as done: it is the command's to report how it went\n return command.run(ctx, arg) !== false\n },\n can,\n pressed: (name) => !!commands().get(name)?.pressed?.(context()),\n get(name) {\n const command = commands().get(name)\n if (!command) return undefined\n return {\n name,\n label: labelOf(command.label, context().messages.value),\n icon: command.icon,\n shortcut: command.shortcut,\n toggle: !!command.pressed,\n builtin: builtin[name] === command,\n }\n },\n has: (name) => commands().has(name),\n names: () => [...commands().keys()],\n }\n}\n"],"mappings":";AAOA,IAAa,WAAW,OAA+B,aACrD,OAAO,UAAU,aAAa,MAAM,QAAQ,IAAK,SAAS;;AA+B5D,SAAgB,mBAA8B,SAA+C;CAC3F,OAAO;AACT;;;;;;AAuCA,SAAgB,cACd,SACA,OACA;CACA,MAAM,MAAM,IAAI,IAA0B,OAAO,QAAQ,OAAO,CAAC;CACjE,MAAM,wBAAQ,IAAI,IAAoB;CACtC,KAAK,MAAM,EAAE,WAAW,cAAc,OACpC,KAAK,MAAM,QAAQ,UAAU;EAC3B,MAAM,UAAU,SAAS;EACzB,MAAM,QAAQ,MAAM,IAAI,IAAI;EAC5B,IAAI,OACF,MAAM,IAAI,MACR,gCAAgC,KAAK,sBAAsB,MAAM,SAAS,UAAU,EACtF;EACF,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,QAAQ,UAC5B,MAAM,IAAI,MACR,oBAAoB,UAAU,sBAAsB,KAAK,qEAC3D;EACF,MAAM,IAAI,MAAM,SAAS;EACzB,IAAI,IAAI,MAAM,OAAO;CACvB;CACF,OAAO;AACT;;AAGA,SAAgB,eACd,UACA,SACA,UAAkD,CAAC,GACpC;CACf,MAAM,QAAQ,SAAiB;EAC7B,MAAM,UAAU,SAAS,CAAC,CAAC,IAAI,IAAI;EACnC,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,wCAAwC,KAAK,EAAE;EAC7E,OAAO;CACT;CACA,MAAM,OAAO,SAAiB;EAC5B,MAAM,UAAU,SAAS,CAAC,CAAC,IAAI,IAAI;EACnC,OAAO,CAAC,CAAC,YAAY,QAAQ,UAAU,QAAQ,CAAC,KAAK;CACvD;CACA,OAAO;EACL,IAAI,MAAM,KAAK;GACb,MAAM,UAAU,KAAK,IAAI;GACzB,MAAM,MAAM,QAAQ;GACpB,IAAI,QAAQ,WAAW,CAAC,QAAQ,QAAQ,GAAG,GAAG,OAAO;GAErD,OAAO,QAAQ,IAAI,KAAK,GAAG,MAAM;EACnC;EACA;EACA,UAAU,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,UAAU,QAAQ,CAAC;EAC9D,IAAI,MAAM;GACR,MAAM,UAAU,SAAS,CAAC,CAAC,IAAI,IAAI;GACnC,IAAI,CAAC,SAAS,OAAO,KAAA;GACrB,OAAO;IACL;IACA,OAAO,QAAQ,QAAQ,OAAO,QAAQ,CAAC,CAAC,SAAS,KAAK;IACtD,MAAM,QAAQ;IACd,UAAU,QAAQ;IAClB,QAAQ,CAAC,CAAC,QAAQ;IAClB,SAAS,QAAQ,UAAU;GAC7B;EACF;EACA,MAAM,SAAS,SAAS,CAAC,CAAC,IAAI,IAAI;EAClC,aAAa,CAAC,GAAG,SAAS,CAAC,CAAC,KAAK,CAAC;CACpC;AACF"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { formatCell } from "./address.js";
|
|
2
|
+
import { sheetKey } from "./transaction.js";
|
|
3
|
+
// src/core/comments.ts
|
|
4
|
+
/** Every anchored thread in a workbook, by thread id. */
|
|
5
|
+
function sheetCommentAnchors(book) {
|
|
6
|
+
const out = /* @__PURE__ */ new Map();
|
|
7
|
+
for (const sheet of book.sheets) {
|
|
8
|
+
const key = sheetKey(sheet);
|
|
9
|
+
sheet.cells.forEach((line, row) => line?.forEach((cell, col) => {
|
|
10
|
+
if (cell && typeof cell === "object" && cell.comment && !out.has(cell.comment)) out.set(cell.comment, {
|
|
11
|
+
sheet: key,
|
|
12
|
+
row,
|
|
13
|
+
col,
|
|
14
|
+
address: formatCell({
|
|
15
|
+
row,
|
|
16
|
+
col
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/** Where a line number goes with a step; `-1` once its line is gone. */
|
|
24
|
+
function follow(step, axis, at) {
|
|
25
|
+
if (step.type === "insertLines" && step.axis === axis) return at >= step.index ? at + step.count : at;
|
|
26
|
+
if (step.type === "removeLines" && step.axis === axis) {
|
|
27
|
+
if (at < step.index) return at;
|
|
28
|
+
return at < step.index + step.count ? -1 : at - step.count;
|
|
29
|
+
}
|
|
30
|
+
if (step.type === "moveLines" && step.axis === axis) {
|
|
31
|
+
const { from, count, to } = step;
|
|
32
|
+
if (at >= from && at < from + count) return (to > from ? to - count : to) + at - from;
|
|
33
|
+
if (to > from && at >= from + count && at < to) return at - count;
|
|
34
|
+
if (to < from && at >= to && at < from) return at + count;
|
|
35
|
+
}
|
|
36
|
+
return at;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Puts back the threads `tr` took off their cells by restoring what the
|
|
40
|
+
* cells held before them, as undo and redo do. `before` is
|
|
41
|
+
* `sheetCommentAnchors` of the workbook `tr` started from. A thread `tr`
|
|
42
|
+
* itself puts on another cell (undoing a move) goes there instead, and one
|
|
43
|
+
* whose row, column or sheet `tr` removed stays off. The threads are put
|
|
44
|
+
* back in `into`, by default `tr` itself; give another transaction that
|
|
45
|
+
* starts where `tr` ended to keep them a separate change.
|
|
46
|
+
*/
|
|
47
|
+
function keepCommentAnchors(before, tr, into = tr) {
|
|
48
|
+
if (!before.size) return;
|
|
49
|
+
const now = sheetCommentAnchors(into.doc);
|
|
50
|
+
for (const [id, anchor] of before) {
|
|
51
|
+
if (now.has(id)) continue;
|
|
52
|
+
let { row, col } = anchor;
|
|
53
|
+
let gone = false;
|
|
54
|
+
for (const step of tr.steps) {
|
|
55
|
+
if (step.type === "removeSheet" && step.sheet === anchor.sheet) gone = true;
|
|
56
|
+
if (!("sheet" in step) || step.sheet !== anchor.sheet) continue;
|
|
57
|
+
row = follow(step, "row", row);
|
|
58
|
+
col = follow(step, "col", col);
|
|
59
|
+
if (row < 0 || col < 0) gone = true;
|
|
60
|
+
}
|
|
61
|
+
if (gone || into.indexOf(anchor.sheet) === -1) continue;
|
|
62
|
+
const at = {
|
|
63
|
+
sheet: anchor.sheet,
|
|
64
|
+
row,
|
|
65
|
+
col
|
|
66
|
+
};
|
|
67
|
+
const cell = into.getCell(at);
|
|
68
|
+
if (cell && typeof cell === "object" && cell.comment) continue;
|
|
69
|
+
into.updateCell(at, { comment: id });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export { keepCommentAnchors, sheetCommentAnchors };
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=comments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comments.js","names":[],"sources":["../../src/core/comments.ts"],"sourcesContent":["import { formatCell } from './address'\nimport type { SheetWorkbook } from './model'\nimport { sheetKey, type SheetStep, type SheetTransaction } from './transaction'\n\n/*\n * Comment threads on cells. A cell names the thread it is about with\n * `comment`, one thread per cell; the discussions themselves live outside\n * the workbook (`v-model:comments`). A merged cell's thread is on its top\n * left cell.\n *\n * Writing or taking off an anchor is not a change to undo: undoing some\n * typing keeps the thread on its cell. `keepCommentAnchors` puts back the\n * anchors an undo or redo would otherwise take with the cells it restores.\n */\n\n/** Where a thread is: its sheet (by `id`, or name) and cell. */\nexport interface SheetCommentAnchor {\n sheet: string\n row: number\n col: number\n /** The cell as people write it, e.g. `B3`. */\n address: string\n}\n\n/** Every anchored thread in a workbook, by thread id. */\nexport function sheetCommentAnchors(book: SheetWorkbook): Map<string, SheetCommentAnchor> {\n const out = new Map<string, SheetCommentAnchor>()\n for (const sheet of book.sheets) {\n const key = sheetKey(sheet)\n sheet.cells.forEach((line, row) =>\n line?.forEach((cell, col) => {\n if (cell && typeof cell === 'object' && cell.comment && !out.has(cell.comment))\n out.set(cell.comment, { sheet: key, row, col, address: formatCell({ row, col }) })\n }),\n )\n }\n return out\n}\n\n/** Where a line number goes with a step; `-1` once its line is gone. */\nfunction follow(step: SheetStep, axis: 'row' | 'col', at: number) {\n if (step.type === 'insertLines' && step.axis === axis)\n return at >= step.index ? at + step.count : at\n if (step.type === 'removeLines' && step.axis === axis) {\n if (at < step.index) return at\n return at < step.index + step.count ? -1 : at - step.count\n }\n if (step.type === 'moveLines' && step.axis === axis) {\n const { from, count, to } = step\n if (at >= from && at < from + count) return (to > from ? to - count : to) + at - from\n if (to > from && at >= from + count && at < to) return at - count\n if (to < from && at >= to && at < from) return at + count\n }\n return at\n}\n\n/**\n * Puts back the threads `tr` took off their cells by restoring what the\n * cells held before them, as undo and redo do. `before` is\n * `sheetCommentAnchors` of the workbook `tr` started from. A thread `tr`\n * itself puts on another cell (undoing a move) goes there instead, and one\n * whose row, column or sheet `tr` removed stays off. The threads are put\n * back in `into`, by default `tr` itself; give another transaction that\n * starts where `tr` ended to keep them a separate change.\n */\nexport function keepCommentAnchors(\n before: ReadonlyMap<string, SheetCommentAnchor>,\n tr: SheetTransaction,\n into: SheetTransaction = tr,\n) {\n if (!before.size) return\n const now = sheetCommentAnchors(into.doc)\n for (const [id, anchor] of before) {\n if (now.has(id)) continue\n let { row, col } = anchor\n let gone = false\n for (const step of tr.steps) {\n if (step.type === 'removeSheet' && step.sheet === anchor.sheet) gone = true\n if (!('sheet' in step) || step.sheet !== anchor.sheet) continue\n row = follow(step, 'row', row)\n col = follow(step, 'col', col)\n if (row < 0 || col < 0) gone = true\n }\n if (gone || into.indexOf(anchor.sheet) === -1) continue\n const at = { sheet: anchor.sheet, row, col }\n const cell = into.getCell(at)\n if (cell && typeof cell === 'object' && cell.comment) continue\n into.updateCell(at, { comment: id })\n }\n}\n"],"mappings":";;;;AAyBA,SAAgB,oBAAoB,MAAsD;CACxF,MAAM,sBAAM,IAAI,IAAgC;CAChD,KAAK,MAAM,SAAS,KAAK,QAAQ;EAC/B,MAAM,MAAM,SAAS,KAAK;EAC1B,MAAM,MAAM,SAAS,MAAM,QACzB,MAAM,SAAS,MAAM,QAAQ;GAC3B,IAAI,QAAQ,OAAO,SAAS,YAAY,KAAK,WAAW,CAAC,IAAI,IAAI,KAAK,OAAO,GAC3E,IAAI,IAAI,KAAK,SAAS;IAAE,OAAO;IAAK;IAAK;IAAK,SAAS,WAAW;KAAE;KAAK;IAAI,CAAC;GAAE,CAAC;EACrF,CAAC,CACH;CACF;CACA,OAAO;AACT;;AAGA,SAAS,OAAO,MAAiB,MAAqB,IAAY;CAChE,IAAI,KAAK,SAAS,iBAAiB,KAAK,SAAS,MAC/C,OAAO,MAAM,KAAK,QAAQ,KAAK,KAAK,QAAQ;CAC9C,IAAI,KAAK,SAAS,iBAAiB,KAAK,SAAS,MAAM;EACrD,IAAI,KAAK,KAAK,OAAO,OAAO;EAC5B,OAAO,KAAK,KAAK,QAAQ,KAAK,QAAQ,KAAK,KAAK,KAAK;CACvD;CACA,IAAI,KAAK,SAAS,eAAe,KAAK,SAAS,MAAM;EACnD,MAAM,EAAE,MAAM,OAAO,OAAO;EAC5B,IAAI,MAAM,QAAQ,KAAK,OAAO,OAAO,QAAQ,KAAK,OAAO,KAAK,QAAQ,MAAM,KAAK;EACjF,IAAI,KAAK,QAAQ,MAAM,OAAO,SAAS,KAAK,IAAI,OAAO,KAAK;EAC5D,IAAI,KAAK,QAAQ,MAAM,MAAM,KAAK,MAAM,OAAO,KAAK;CACtD;CACA,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,mBACd,QACA,IACA,OAAyB,IACzB;CACA,IAAI,CAAC,OAAO,MAAM;CAClB,MAAM,MAAM,oBAAoB,KAAK,GAAG;CACxC,KAAK,MAAM,CAAC,IAAI,WAAW,QAAQ;EACjC,IAAI,IAAI,IAAI,EAAE,GAAG;EACjB,IAAI,EAAE,KAAK,QAAQ;EACnB,IAAI,OAAO;EACX,KAAK,MAAM,QAAQ,GAAG,OAAO;GAC3B,IAAI,KAAK,SAAS,iBAAiB,KAAK,UAAU,OAAO,OAAO,OAAO;GACvE,IAAI,EAAE,WAAW,SAAS,KAAK,UAAU,OAAO,OAAO;GACvD,MAAM,OAAO,MAAM,OAAO,GAAG;GAC7B,MAAM,OAAO,MAAM,OAAO,GAAG;GAC7B,IAAI,MAAM,KAAK,MAAM,GAAG,OAAO;EACjC;EACA,IAAI,QAAQ,KAAK,QAAQ,OAAO,KAAK,MAAM,IAAI;EAC/C,MAAM,KAAK;GAAE,OAAO,OAAO;GAAO;GAAK;EAAI;EAC3C,MAAM,OAAO,KAAK,QAAQ,EAAE;EAC5B,IAAI,QAAQ,OAAO,SAAS,YAAY,KAAK,SAAS;EACtD,KAAK,WAAW,IAAI,EAAE,SAAS,GAAG,CAAC;CACrC;AACF"}
|