@portone/docx-editor 0.5.1 → 0.6.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/CHANGELOG.md +47 -0
- package/CONTRIBUTING.md +8 -2
- package/README.md +1 -1
- package/dist/DocxEditor.js +87 -29
- package/dist/docx/exportDocx.d.ts +2 -1
- package/dist/docx/exportDocx.js +3 -9
- package/dist/docx/headersFooters.js +35 -23
- package/dist/docx/identities.d.ts +28 -0
- package/dist/docx/identities.js +42 -10
- package/dist/docx/importDocx.js +25 -26
- package/dist/docx/importParagraph.d.ts +1 -4
- package/dist/docx/importParagraph.js +4 -18
- package/dist/docx/invariants.d.ts +1 -1
- package/dist/docx/invariants.js +45 -4
- package/dist/docx/notes/newNote.d.ts +39 -0
- package/dist/docx/notes/newNote.js +122 -0
- package/dist/docx/notes/numbering.d.ts +27 -0
- package/dist/docx/notes/numbering.js +120 -0
- package/dist/docx/notes/reading.d.ts +58 -0
- package/dist/docx/notes/reading.js +98 -0
- package/dist/docx/notes/writing.d.ts +19 -0
- package/dist/docx/notes/writing.js +96 -0
- package/dist/docx/partPlan.d.ts +3 -0
- package/dist/docx/partPlanners.d.ts +13 -0
- package/dist/docx/partPlanners.js +48 -0
- package/dist/docx/sections.d.ts +5 -0
- package/dist/docx/sections.js +7 -2
- package/dist/docx/serializeParagraph.js +7 -2
- package/dist/docx/session.d.ts +5 -0
- package/dist/docx/session.js +6 -0
- package/dist/docx/storyParts.d.ts +112 -0
- package/dist/docx/storyParts.js +289 -0
- package/dist/editor/clipboard/htmlReader.d.ts +24 -4
- package/dist/editor/clipboard/htmlReader.js +39 -4
- package/dist/editor/clipboard/internalChannel.d.ts +30 -3
- package/dist/editor/clipboard/internalChannel.js +43 -3
- package/dist/editor/clipboard/normalizers.d.ts +35 -8
- package/dist/editor/clipboard/normalizers.js +60 -11
- package/dist/editor/clipboard/plugin.js +42 -22
- package/dist/editor/commands/comments/editing.d.ts +4 -1
- package/dist/editor/commands/comments/editing.js +1 -3
- package/dist/editor/commands/comments/model.d.ts +7 -0
- package/dist/editor/commands/index.d.ts +1 -0
- package/dist/editor/commands/index.js +18 -0
- package/dist/editor/commands/noteCommands.d.ts +47 -0
- package/dist/editor/commands/noteCommands.js +95 -0
- package/dist/editor/commands/noteQueries.d.ts +22 -3
- package/dist/editor/commands/noteQueries.js +21 -13
- package/dist/editor/createEditor.js +25 -3
- package/dist/editor/documentStyles.d.ts +3 -0
- package/dist/editor/documentStyles.js +2 -1
- package/dist/editor/editorDocument.d.ts +24 -0
- package/dist/editor/editorDocument.js +34 -4
- package/dist/editor/notes/noteSurface.d.ts +53 -0
- package/dist/editor/notes/noteSurface.js +246 -0
- package/dist/editor/plugins/commentDecorations.js +5 -4
- package/dist/editor/plugins/commentRestoration.d.ts +20 -0
- package/dist/editor/plugins/commentRestoration.js +168 -0
- package/dist/editor/plugins/compositionSelection.d.ts +20 -0
- package/dist/editor/plugins/compositionSelection.js +19 -0
- package/dist/editor/plugins/keymap.d.ts +14 -0
- package/dist/editor/plugins/keymap.js +13 -1
- package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
- package/dist/editor/plugins/noteLifecycle.js +117 -0
- package/dist/editor/plugins/noteNavigation.d.ts +45 -0
- package/dist/editor/plugins/noteNavigation.js +86 -0
- package/dist/editor/plugins/noteNumbering.d.ts +13 -0
- package/dist/editor/plugins/noteNumbering.js +58 -0
- package/dist/editor/plugins/rowResize.js +2 -7
- package/dist/editor/plugins/tabLayout.js +2 -7
- package/dist/editor/stories/storyMarkup.d.ts +24 -0
- package/dist/editor/stories/storyMarkup.js +35 -0
- package/dist/editor/stories/storyState.d.ts +34 -0
- package/dist/editor/stories/storyState.js +74 -0
- package/dist/editor/stories/storyView.d.ts +115 -0
- package/dist/editor/stories/storyView.js +144 -0
- package/dist/numbering/spellers.d.ts +1 -1
- package/dist/numbering/spellers.js +6 -1
- package/dist/page/blockKinds.d.ts +23 -4
- package/dist/page/demands/footnoteDemands.d.ts +18 -0
- package/dist/page/demands/footnoteDemands.js +51 -0
- package/dist/page/demands/index.d.ts +43 -0
- package/dist/page/demands/index.js +8 -0
- package/dist/page/kinds/paragraphKind.js +7 -1
- package/dist/page/kinds/tableKind.js +9 -1
- package/dist/page/measureBlocks.d.ts +2 -0
- package/dist/page/measureBlocks.js +31 -10
- package/dist/page/pageDecorations.d.ts +9 -2
- package/dist/page/pageDecorations.js +18 -5
- package/dist/page/pageLayout.d.ts +77 -1
- package/dist/page/pageLayout.js +186 -9
- package/dist/page/usePageLayout.d.ts +57 -2
- package/dist/page/usePageLayout.js +50 -5
- package/dist/schema/attrRoles.js +3 -2
- package/dist/schema/docxSchema.js +5 -2
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.js +1 -2
- package/dist/schema/preservedGuards.d.ts +4 -6
- package/dist/schema/preservedGuards.js +0 -9
- package/dist/schema/stories.d.ts +34 -2
- package/dist/schema/stories.js +22 -5
- package/dist/styles/classNames.d.ts +23 -6
- package/dist/styles/classNames.js +23 -6
- package/dist/styles/inlineStyle.d.ts +7 -0
- package/dist/styles/inlineStyle.js +11 -5
- package/dist/styles/visualScale.d.ts +19 -0
- package/dist/styles/visualScale.js +26 -0
- package/dist/styles.css +96 -36
- package/dist/ui/CommentsPanel.d.ts +6 -1
- package/dist/ui/CommentsPanel.js +26 -8
- package/dist/ui/InsertImageButton.d.ts +3 -1
- package/dist/ui/InsertImageButton.js +3 -2
- package/dist/ui/TableMenu.d.ts +5 -2
- package/dist/ui/TableMenu.js +18 -1
- package/dist/ui/TextMenu.d.ts +5 -2
- package/dist/ui/TextMenu.js +21 -6
- package/dist/ui/Toolbar.d.ts +17 -2
- package/dist/ui/Toolbar.js +23 -10
- package/dist/ui/comments/useCommentRailLayout.js +2 -4
- package/dist/ui/noteItems.d.ts +21 -0
- package/dist/ui/noteItems.js +30 -0
- package/dist/ui/notes/NoteAreas.d.ts +73 -0
- package/dist/ui/notes/NoteAreas.js +131 -0
- package/dist/ui/notes/NoteList.d.ts +25 -0
- package/dist/ui/notes/NoteList.js +58 -0
- package/dist/ui/notes/StoryRow.d.ts +75 -0
- package/dist/ui/notes/StoryRow.js +122 -0
- package/dist/ui/notes/noteBands.d.ts +100 -0
- package/dist/ui/notes/noteBands.js +142 -0
- package/dist/ui/notes/noteHeights.d.ts +10 -0
- package/dist/ui/notes/noteHeights.js +24 -0
- package/dist/ui/notes/noteSeparator.d.ts +12 -0
- package/dist/ui/notes/noteSeparator.js +9 -0
- package/dist/ui/notes/useStorySurface.d.ts +68 -0
- package/dist/ui/notes/useStorySurface.js +95 -0
- package/dist/ui/runCommand.d.ts +8 -1
- package/dist/ui/runCommand.js +2 -2
- package/dist/ui/shortcutLabels.d.ts +16 -0
- package/dist/ui/shortcutLabels.js +9 -0
- package/dist/ui/usePageRoom.d.ts +13 -0
- package/dist/ui/usePageRoom.js +31 -0
- package/package.json +2 -1
- package/dist/docx/notes.d.ts +0 -27
- package/dist/docx/notes.js +0 -55
- package/dist/ui/NotesPanel.d.ts +0 -6
- package/dist/ui/NotesPanel.js +0 -33
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// src/editor/plugins/commentRestoration.ts
|
|
2
|
+
import {
|
|
3
|
+
Plugin,
|
|
4
|
+
TextSelection
|
|
5
|
+
} from "prosemirror-state";
|
|
6
|
+
import { storyKey, storyOf } from "../../docx/story.js";
|
|
7
|
+
import { docxSchema } from "../../schema/index.js";
|
|
8
|
+
import { editShut, transactionTouchesComments } from "../../schema/guards.js";
|
|
9
|
+
import { stringAttr } from "../commands/comments/model.js";
|
|
10
|
+
var NODE_FIELD = {
|
|
11
|
+
commentStart: "start",
|
|
12
|
+
commentEnd: "end",
|
|
13
|
+
commentReference: "reference"
|
|
14
|
+
};
|
|
15
|
+
var NODE_FIELDS = ["start", "end", "reference"];
|
|
16
|
+
var MAPPING_SIDE = {
|
|
17
|
+
start: 1,
|
|
18
|
+
end: -1,
|
|
19
|
+
reference: -1
|
|
20
|
+
};
|
|
21
|
+
var NO_NODES = { start: null, end: null, reference: null };
|
|
22
|
+
function commentNodesIn(doc) {
|
|
23
|
+
const found = /* @__PURE__ */ new Map();
|
|
24
|
+
doc.descendants((node, pos) => {
|
|
25
|
+
const field = NODE_FIELD[node.type.name];
|
|
26
|
+
if (field === void 0) return true;
|
|
27
|
+
const id = stringAttr(node.attrs.id);
|
|
28
|
+
if (id === null) return true;
|
|
29
|
+
const nodes = found.get(id) ?? { ...NO_NODES };
|
|
30
|
+
found.set(id, nodes);
|
|
31
|
+
if (nodes[field] === null) nodes[field] = { node, pos };
|
|
32
|
+
return true;
|
|
33
|
+
});
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
function inlineRanges(doc) {
|
|
37
|
+
const type = docxSchema.nodes.commentReference;
|
|
38
|
+
const ranges = [];
|
|
39
|
+
doc.descendants((node, pos) => {
|
|
40
|
+
if (!node.isTextblock) return true;
|
|
41
|
+
if (node.type.contentMatch.matchType(type) !== null) {
|
|
42
|
+
ranges.push({ from: pos + 1, to: pos + 1 + node.content.size });
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
});
|
|
46
|
+
return ranges;
|
|
47
|
+
}
|
|
48
|
+
function within(ranges, pos) {
|
|
49
|
+
return ranges.some((range) => pos >= range.from && pos <= range.to);
|
|
50
|
+
}
|
|
51
|
+
function* homes(ranges, wanted, caret) {
|
|
52
|
+
if (within(ranges, wanted)) yield wanted;
|
|
53
|
+
if (caret !== wanted && within(ranges, caret)) yield caret;
|
|
54
|
+
for (let index = ranges.length - 1; index >= 0; index -= 1) {
|
|
55
|
+
const range = ranges[index];
|
|
56
|
+
if (range !== void 0 && range.to < wanted) yield range.to;
|
|
57
|
+
}
|
|
58
|
+
for (const range of ranges) {
|
|
59
|
+
if (range.from > wanted) yield range.from;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function allowedHome(state, ranges, wanted, caret) {
|
|
63
|
+
for (const at of homes(ranges, wanted, caret)) {
|
|
64
|
+
if (!editShut(state, { kind: "insert", at })) return at;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function mappedThrough(transactions, pos, side) {
|
|
69
|
+
return transactions.reduce((at, tr) => tr.mapping.map(at, side), pos);
|
|
70
|
+
}
|
|
71
|
+
function restorable(node) {
|
|
72
|
+
return node.type.create(
|
|
73
|
+
node.attrs,
|
|
74
|
+
null,
|
|
75
|
+
node.marks.filter((mark) => mark.type === docxSchema.marks.run)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function planRestoration(transactions, oldState, newState) {
|
|
79
|
+
const before = commentNodesIn(oldState.doc);
|
|
80
|
+
const after = commentNodesIn(newState.doc);
|
|
81
|
+
const ranges = inlineRanges(newState.doc);
|
|
82
|
+
const caret = newState.selection.from;
|
|
83
|
+
const plan = { insertions: [], removals: [] };
|
|
84
|
+
for (const [id, was] of before) {
|
|
85
|
+
if (storyOf(newState.doc, storyKey("comment", id)) === null) continue;
|
|
86
|
+
const now = after.get(id) ?? NO_NODES;
|
|
87
|
+
for (const field of NODE_FIELDS) {
|
|
88
|
+
const lost = was[field];
|
|
89
|
+
if (lost === null || now[field] !== null) continue;
|
|
90
|
+
const counterpart = field === "start" ? now.end : field === "end" ? now.start : null;
|
|
91
|
+
if (field !== "reference" && counterpart === null) continue;
|
|
92
|
+
const at = allowedHome(
|
|
93
|
+
newState,
|
|
94
|
+
ranges,
|
|
95
|
+
mappedThrough(transactions, lost.pos, MAPPING_SIDE[field]),
|
|
96
|
+
caret
|
|
97
|
+
);
|
|
98
|
+
if (at !== null) {
|
|
99
|
+
plan.insertions.push({
|
|
100
|
+
node: restorable(lost.node),
|
|
101
|
+
at,
|
|
102
|
+
was: lost.pos
|
|
103
|
+
});
|
|
104
|
+
} else if (counterpart !== null && !markerShut(newState, counterpart)) {
|
|
105
|
+
plan.removals.push(counterpart);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return plan;
|
|
110
|
+
}
|
|
111
|
+
function markerShut(state, marker) {
|
|
112
|
+
return editShut(state, {
|
|
113
|
+
kind: "replace",
|
|
114
|
+
from: marker.pos,
|
|
115
|
+
to: marker.pos + marker.node.nodeSize
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function pastCommentNodes(doc, pos, direction) {
|
|
119
|
+
let at = pos;
|
|
120
|
+
for (; ; ) {
|
|
121
|
+
const resolved = doc.resolve(at);
|
|
122
|
+
const next = direction < 0 ? resolved.nodeBefore : resolved.nodeAfter;
|
|
123
|
+
if (next === null || NODE_FIELD[next.type.name] === void 0) return at;
|
|
124
|
+
at += direction * next.nodeSize;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function withCaretPastMarkers(tr, oldState, newState) {
|
|
128
|
+
const { selection } = newState;
|
|
129
|
+
if (!(selection instanceof TextSelection) || !selection.empty) return tr;
|
|
130
|
+
if (!tr.doc.eq(oldState.doc)) return tr;
|
|
131
|
+
const direction = selection.from < oldState.selection.from ? -1 : 1;
|
|
132
|
+
return tr.setSelection(
|
|
133
|
+
TextSelection.create(
|
|
134
|
+
tr.doc,
|
|
135
|
+
pastCommentNodes(tr.doc, tr.selection.from, direction)
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
function restoration(transactions, oldState, newState) {
|
|
140
|
+
const plan = planRestoration(transactions, oldState, newState);
|
|
141
|
+
if (plan.insertions.length === 0 && plan.removals.length === 0) return null;
|
|
142
|
+
const tr = newState.tr;
|
|
143
|
+
for (const marker of [...plan.removals].sort(
|
|
144
|
+
(left, right) => right.pos - left.pos
|
|
145
|
+
)) {
|
|
146
|
+
tr.delete(marker.pos, marker.pos + marker.node.nodeSize);
|
|
147
|
+
}
|
|
148
|
+
const ordered = [...plan.insertions].sort(
|
|
149
|
+
(left, right) => left.at - right.at || left.was - right.was
|
|
150
|
+
);
|
|
151
|
+
for (const { node, at } of ordered) {
|
|
152
|
+
tr.insert(tr.mapping.map(at), node);
|
|
153
|
+
}
|
|
154
|
+
if (!tr.docChanged) return null;
|
|
155
|
+
return withCaretPastMarkers(tr, oldState, newState);
|
|
156
|
+
}
|
|
157
|
+
function commentRestoration() {
|
|
158
|
+
return new Plugin({
|
|
159
|
+
appendTransaction(transactions, oldState, newState) {
|
|
160
|
+
if (!transactions.some((tr) => tr.docChanged)) return null;
|
|
161
|
+
if (!transactions.some(transactionTouchesComments)) return null;
|
|
162
|
+
return restoration(transactions, oldState, newState);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
export {
|
|
167
|
+
commentRestoration
|
|
168
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takes the selected text away before a composition opens over it.
|
|
3
|
+
*
|
|
4
|
+
* A composition normally replaces what is selected because the browser does the replacing and
|
|
5
|
+
* ProseMirror reads the result back. Chrome will not touch a selection that begins at an element
|
|
6
|
+
* it may not edit - the number a note is drawn by, a note reference - and does not say so: the
|
|
7
|
+
* composition lands beside the words it was meant to replace, or nothing happens at all. A note
|
|
8
|
+
* meets it every time, since its first paragraph opens with its number and selecting the whole of
|
|
9
|
+
* a note to write it again is an ordinary thing to do.
|
|
10
|
+
*
|
|
11
|
+
* So the selection is deleted here, in the frame the composition opens in and before ProseMirror
|
|
12
|
+
* marks the view as composing, which leaves the composition starting from a caret with nothing
|
|
13
|
+
* for the browser to refuse. ProseMirror does the same for a selection spanning whole blocks
|
|
14
|
+
* (`endComposition`).
|
|
15
|
+
*
|
|
16
|
+
* The deletion is an edit like any other, so the guards judge it: across a bookmark marker it is
|
|
17
|
+
* refused, and the composition is left to the browser where it stood.
|
|
18
|
+
*/
|
|
19
|
+
import { Plugin } from "prosemirror-state";
|
|
20
|
+
export declare function compositionSelection(): Plugin;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/editor/plugins/compositionSelection.ts
|
|
2
|
+
import { deleteSelection } from "prosemirror-commands";
|
|
3
|
+
import { Plugin } from "prosemirror-state";
|
|
4
|
+
function compositionSelection() {
|
|
5
|
+
return new Plugin({
|
|
6
|
+
props: {
|
|
7
|
+
handleDOMEvents: {
|
|
8
|
+
compositionstart(view) {
|
|
9
|
+
if (view.state.selection.empty) return false;
|
|
10
|
+
deleteSelection(view.state, (tr) => view.dispatch(tr));
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
compositionSelection
|
|
19
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Keys not handled here are taken by ProseMirror's base keymap.
|
|
3
3
|
*/
|
|
4
4
|
import { type Command, Plugin } from "prosemirror-state";
|
|
5
|
+
import type { NoteKind } from "../../schema/stories";
|
|
5
6
|
/**
|
|
6
7
|
* Keeps undo and redo on their keys where the keymap above cannot reach them.
|
|
7
8
|
*
|
|
@@ -12,4 +13,17 @@ import { type Command, Plugin } from "prosemirror-state";
|
|
|
12
13
|
* second time.
|
|
13
14
|
*/
|
|
14
15
|
export declare function historyKeys(): Plugin;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the editor is running on a Mac, which decides the Mod key and what the system takes
|
|
18
|
+
* before a page sees it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function onMac(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The key each kind of note is put in on.
|
|
23
|
+
*
|
|
24
|
+
* They are Word's own, except that Word for Windows' endnote key is the system's on a Mac:
|
|
25
|
+
* Cmd+Option+D hides the Dock, so the editor is never asked about it, and Word for Mac puts an
|
|
26
|
+
* endnote on Cmd+Option+E. The footnote key is the same on both, which is Google Docs' as well.
|
|
27
|
+
*/
|
|
28
|
+
export declare const NOTE_KEYS: Readonly<Record<NoteKind, string>>;
|
|
15
29
|
export declare const docxKeymap: Record<string, Command>;
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
increaseListLevel,
|
|
23
23
|
leaveEmptyListItem
|
|
24
24
|
} from "../commands/listCommands.js";
|
|
25
|
+
import { insertEndnote, insertFootnote } from "../commands/noteCommands.js";
|
|
25
26
|
import { insertTab, moveAcrossTab } from "../commands/tabCommands.js";
|
|
26
27
|
import { openLinkPanel } from "./linkPanel.js";
|
|
27
28
|
var insertTabFromKeyboard = (state, dispatch, view) => {
|
|
@@ -91,6 +92,13 @@ function historyKeys() {
|
|
|
91
92
|
}
|
|
92
93
|
});
|
|
93
94
|
}
|
|
95
|
+
function onMac() {
|
|
96
|
+
return typeof navigator !== "undefined" && navigator.platform.includes("Mac");
|
|
97
|
+
}
|
|
98
|
+
var NOTE_KEYS = {
|
|
99
|
+
footnote: "Mod-Alt-f",
|
|
100
|
+
endnote: onMac() ? "Mod-Alt-e" : "Mod-Alt-d"
|
|
101
|
+
};
|
|
94
102
|
var docxKeymap = {
|
|
95
103
|
Enter: chainCommands(leaveEmptyListItem, splitParagraph),
|
|
96
104
|
"Shift-Enter": insertLineBreak,
|
|
@@ -107,6 +115,8 @@ var docxKeymap = {
|
|
|
107
115
|
// The link key Word and Google Docs share. It opens the panel over the selection, and with
|
|
108
116
|
// nothing there to link it reports that it did nothing, so the browser keeps its own Cmd+K
|
|
109
117
|
"Mod-k": openLinkPanel,
|
|
118
|
+
[NOTE_KEYS.footnote]: insertFootnote,
|
|
119
|
+
[NOTE_KEYS.endnote]: insertEndnote,
|
|
110
120
|
// Inside a table, moving between cells comes first (Word does the same).
|
|
111
121
|
// In a list paragraph outside a table it shifts the level; an ordinary paragraph gets a document tab.
|
|
112
122
|
Tab: chainCommands(
|
|
@@ -124,6 +134,8 @@ var docxKeymap = {
|
|
|
124
134
|
"Shift-ArrowRight": moveAcrossTab("right", true)
|
|
125
135
|
};
|
|
126
136
|
export {
|
|
137
|
+
NOTE_KEYS,
|
|
127
138
|
docxKeymap,
|
|
128
|
-
historyKeys
|
|
139
|
+
historyKeys,
|
|
140
|
+
onMac
|
|
129
141
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeps each note of an editable kind in step with the references that call it.
|
|
3
|
+
*
|
|
4
|
+
* A note is written once and called from the text by its id, so what an edit does to the text
|
|
5
|
+
* decides what becomes of the note. An edit that takes away the last reference to a note takes the
|
|
6
|
+
* note away with it. An edit that copies a reference gives the copy a new id and a copy of the
|
|
7
|
+
* note, since two references naming one id are one note drawn with one number where the reader
|
|
8
|
+
* made two. A reference moved whole keeps its id and its note. Either change goes in a transaction
|
|
9
|
+
* appended to the edit, so it lands in the edit's history event and one undo takes both back.
|
|
10
|
+
*
|
|
11
|
+
* Only what the edit did is read. An entry the file arrived with and no reference to is never
|
|
12
|
+
* reached, a reference naming no note is left naming none, and the separators a notes part lays
|
|
13
|
+
* the page out with are left as they are. The notes part writer relies on the first of these: a
|
|
14
|
+
* note disappears only together with its last reference (`docx/storyParts`).
|
|
15
|
+
*/
|
|
16
|
+
import { Plugin } from "prosemirror-state";
|
|
17
|
+
import { type NoteKind } from "../../schema/stories";
|
|
18
|
+
export declare function noteLifecycle(kinds?: readonly NoteKind[]): Plugin;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/editor/plugins/noteLifecycle.ts
|
|
2
|
+
import { Plugin } from "prosemirror-state";
|
|
3
|
+
import { DocAttrStep, Mapping } from "prosemirror-transform";
|
|
4
|
+
import {
|
|
5
|
+
copiedNoteStory,
|
|
6
|
+
nextNoteId,
|
|
7
|
+
takenNoteIds
|
|
8
|
+
} from "../../docx/notes/newNote.js";
|
|
9
|
+
import { setStory, withoutStories } from "../../docx/story.js";
|
|
10
|
+
import { displayOnly } from "../../schema/displayDerivation.js";
|
|
11
|
+
import { stepReaches } from "../../schema/guards.js";
|
|
12
|
+
import {
|
|
13
|
+
EDITABLE_NOTE_KINDS,
|
|
14
|
+
storyKey,
|
|
15
|
+
storyNodeOf
|
|
16
|
+
} from "../../schema/stories.js";
|
|
17
|
+
import { documentOf } from "../editorDocument.js";
|
|
18
|
+
function kindOf(node, kinds) {
|
|
19
|
+
if (node.type.name !== "noteReference") return null;
|
|
20
|
+
return kinds.find((kind) => kind === node.attrs.kind) ?? null;
|
|
21
|
+
}
|
|
22
|
+
function referencesIn(doc, kinds) {
|
|
23
|
+
const found = [];
|
|
24
|
+
doc.descendants((node, pos) => {
|
|
25
|
+
const kind = kindOf(node, kinds);
|
|
26
|
+
const id = node.attrs.id;
|
|
27
|
+
if (kind !== null && typeof id === "string") {
|
|
28
|
+
found.push({ kind, key: storyKey(kind, id), pos });
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
});
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
function countsOf(references) {
|
|
35
|
+
return references.reduce(
|
|
36
|
+
(counts, { key }) => counts.set(key, (counts.get(key) ?? 0) + 1),
|
|
37
|
+
/* @__PURE__ */ new Map()
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
function reachesReferences(tr, kinds) {
|
|
41
|
+
if (!tr.docChanged || tr.getMeta(displayOnly) === true) return false;
|
|
42
|
+
return tr.steps.some(
|
|
43
|
+
(step, index) => !(step instanceof DocAttrStep) && stepReaches(
|
|
44
|
+
step,
|
|
45
|
+
tr.docs[index],
|
|
46
|
+
tr.docs[index + 1] ?? tr.doc,
|
|
47
|
+
(node) => kindOf(node, kinds) !== null
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function copiedReferences(transactions, before, after) {
|
|
52
|
+
const found = countsOf(before);
|
|
53
|
+
const left = countsOf(after);
|
|
54
|
+
const mapping = new Mapping(transactions.flatMap((tr) => tr.mapping.maps));
|
|
55
|
+
const carried = new Set(
|
|
56
|
+
before.flatMap(({ key, pos }) => {
|
|
57
|
+
const mapped = mapping.mapResult(pos, 1);
|
|
58
|
+
return mapped.deletedAfter ? [] : [`${key}@${mapped.pos}`];
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
const wasCarried = ({ key, pos }) => carried.has(`${key}@${pos}`);
|
|
62
|
+
return [...left].flatMap(([key, count]) => {
|
|
63
|
+
const kept = found.get(key) ?? 0;
|
|
64
|
+
if (kept === 0 || count <= kept) return [];
|
|
65
|
+
const standing = after.filter((reference) => reference.key === key);
|
|
66
|
+
return [
|
|
67
|
+
...standing.filter(wasCarried),
|
|
68
|
+
...standing.filter((reference) => !wasCarried(reference))
|
|
69
|
+
].slice(kept);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function settled(transactions, oldState, newState, kinds) {
|
|
73
|
+
const document = documentOf(newState);
|
|
74
|
+
const isNote = (key) => !document.specialNotes.has(key) && storyNodeOf(newState.doc, key) !== null;
|
|
75
|
+
const before = referencesIn(oldState.doc, kinds);
|
|
76
|
+
const after = referencesIn(newState.doc, kinds);
|
|
77
|
+
const left = countsOf(after);
|
|
78
|
+
const gone = [...countsOf(before).keys()].filter(
|
|
79
|
+
(key) => !left.has(key) && isNote(key)
|
|
80
|
+
);
|
|
81
|
+
const copies = copiedReferences(transactions, before, after).filter(({ key }) => isNote(key)).sort((a, b) => a.pos - b.pos);
|
|
82
|
+
if (gone.length === 0 && copies.length === 0) return null;
|
|
83
|
+
const tr = withoutStories(newState.tr, gone);
|
|
84
|
+
const taken = /* @__PURE__ */ new Map();
|
|
85
|
+
for (const copy of copies) {
|
|
86
|
+
const story = storyNodeOf(newState.doc, copy.key);
|
|
87
|
+
if (story === null) continue;
|
|
88
|
+
const ids = taken.get(copy.kind) ?? takenNoteIds(newState.doc, copy.kind, document.reservedNoteKeys);
|
|
89
|
+
taken.set(copy.kind, ids);
|
|
90
|
+
const id = nextNoteId(ids);
|
|
91
|
+
ids.add(id);
|
|
92
|
+
tr.setNodeAttribute(copy.pos, "id", id).setNodeAttribute(
|
|
93
|
+
copy.pos,
|
|
94
|
+
"referenceXml",
|
|
95
|
+
null
|
|
96
|
+
);
|
|
97
|
+
setStory(
|
|
98
|
+
tr,
|
|
99
|
+
storyKey(copy.kind, id),
|
|
100
|
+
copiedNoteStory(story, document.session)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return tr;
|
|
104
|
+
}
|
|
105
|
+
function noteLifecycle(kinds = EDITABLE_NOTE_KINDS) {
|
|
106
|
+
return new Plugin({
|
|
107
|
+
appendTransaction(transactions, oldState, newState) {
|
|
108
|
+
if (!transactions.some((tr) => reachesReferences(tr, kinds))) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return settled(transactions, oldState, newState, kinds);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
noteLifecycle
|
|
117
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Getting into a note and asking for one to be opened.
|
|
3
|
+
*
|
|
4
|
+
* A press on a note's number or a command asks for a note, and neither can mount anything itself,
|
|
5
|
+
* since what draws the notes is the surface around the editor; so the request is held in the state
|
|
6
|
+
* and the surface reads it back (`requestedNote`). The nonce is what makes a second request for
|
|
7
|
+
* the note already open reach the surface at all.
|
|
8
|
+
*
|
|
9
|
+
* The request moves the caret to just after the reference as it goes in, which is where Escape
|
|
10
|
+
* hands it back to (`editor/notes/noteSurface`), so leaving a note needs nothing remembered.
|
|
11
|
+
*/
|
|
12
|
+
import type { Node as PMNode } from "prosemirror-model";
|
|
13
|
+
import { type Command, type EditorState, Plugin, type Transaction } from "prosemirror-state";
|
|
14
|
+
import { type NoteKind, type StoryKey } from "../../schema/stories";
|
|
15
|
+
/** Which note the reader asked for, and which asking it was */
|
|
16
|
+
export interface RequestedNote {
|
|
17
|
+
readonly key: StoryKey;
|
|
18
|
+
/** Rises with every request, so asking twice for one note is two requests */
|
|
19
|
+
readonly nonce: number;
|
|
20
|
+
}
|
|
21
|
+
/** One reference to a note, as the document holds it */
|
|
22
|
+
export interface PlacedNoteReference {
|
|
23
|
+
readonly pos: number;
|
|
24
|
+
readonly node: PMNode;
|
|
25
|
+
}
|
|
26
|
+
/** Where the first reference to this note stands, and null where the document holds none */
|
|
27
|
+
export declare function noteReferenceAt(doc: PMNode, kind: NoteKind, id: string): PlacedNoteReference | null;
|
|
28
|
+
/** The transaction with the note it opens recorded on it, which `insertFootnote` also carries */
|
|
29
|
+
export declare function requestNote<T extends Transaction>(tr: T, key: StoryKey): T;
|
|
30
|
+
/** The note last asked for, and null where none was. Null for a state built without the plugin */
|
|
31
|
+
export declare function requestedNote(state: EditorState): RequestedNote | null;
|
|
32
|
+
/**
|
|
33
|
+
* Puts the caret just after the note's reference and asks for the note to be opened. It applies
|
|
34
|
+
* under every mode, since opening a note is reading it; what a reader may do once it stands open
|
|
35
|
+
* is the story view's question (`editor/stories`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function openNoteCommand(kind: NoteKind, id: string): Command;
|
|
38
|
+
/**
|
|
39
|
+
* Holds the note last asked for, and turns a press on a note's number into that asking.
|
|
40
|
+
*
|
|
41
|
+
* One press is what opens a note, which is what Google Docs does: the number is a small
|
|
42
|
+
* superscript, and a press on one is far more often a reader going to the note than a caret being
|
|
43
|
+
* placed on the digit. A reference to a kind no writer puts back opens nothing.
|
|
44
|
+
*/
|
|
45
|
+
export declare function noteNavigation(kinds?: readonly NoteKind[]): Plugin<RequestedNote | null>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// src/editor/plugins/noteNavigation.ts
|
|
2
|
+
import {
|
|
3
|
+
Plugin,
|
|
4
|
+
PluginKey,
|
|
5
|
+
TextSelection
|
|
6
|
+
} from "prosemirror-state";
|
|
7
|
+
import { docxSchema } from "../../schema/index.js";
|
|
8
|
+
import {
|
|
9
|
+
EDITABLE_NOTE_KINDS,
|
|
10
|
+
storyKey,
|
|
11
|
+
storyNodeOf
|
|
12
|
+
} from "../../schema/stories.js";
|
|
13
|
+
import { documentOf } from "../editorDocument.js";
|
|
14
|
+
var navigationKey = new PluginKey(
|
|
15
|
+
"docxEditorNoteNavigation"
|
|
16
|
+
);
|
|
17
|
+
function noteReferenceAt(doc, kind, id) {
|
|
18
|
+
let found = null;
|
|
19
|
+
doc.descendants((node, pos) => {
|
|
20
|
+
if (found !== null) return false;
|
|
21
|
+
if (node.type === docxSchema.nodes.noteReference && node.attrs.kind === kind && node.attrs.id === id) {
|
|
22
|
+
found = { pos, node };
|
|
23
|
+
}
|
|
24
|
+
return found === null;
|
|
25
|
+
});
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
28
|
+
function isRequest(value) {
|
|
29
|
+
if (typeof value !== "object" || value === null) return false;
|
|
30
|
+
return typeof Reflect.get(value, "key") === "string" && typeof Reflect.get(value, "nonce") === "number";
|
|
31
|
+
}
|
|
32
|
+
var asked = 0;
|
|
33
|
+
function requestNote(tr, key) {
|
|
34
|
+
asked += 1;
|
|
35
|
+
return tr.setMeta(navigationKey, { key, nonce: asked });
|
|
36
|
+
}
|
|
37
|
+
function requestedNote(state) {
|
|
38
|
+
return navigationKey.getState(state) ?? null;
|
|
39
|
+
}
|
|
40
|
+
function openNoteCommand(kind, id) {
|
|
41
|
+
return (state, dispatch) => {
|
|
42
|
+
const key = storyKey(kind, id);
|
|
43
|
+
if (documentOf(state).specialNotes.has(key)) return false;
|
|
44
|
+
if (storyNodeOf(state.doc, key) === null) return false;
|
|
45
|
+
const found = noteReferenceAt(state.doc, kind, id);
|
|
46
|
+
if (found === null) return false;
|
|
47
|
+
if (dispatch) {
|
|
48
|
+
const after = found.pos + found.node.nodeSize;
|
|
49
|
+
const tr = state.tr.setSelection(
|
|
50
|
+
TextSelection.near(state.doc.resolve(after))
|
|
51
|
+
);
|
|
52
|
+
dispatch(requestNote(tr, key).scrollIntoView());
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function noteNavigation(kinds = EDITABLE_NOTE_KINDS) {
|
|
58
|
+
return new Plugin({
|
|
59
|
+
key: navigationKey,
|
|
60
|
+
state: {
|
|
61
|
+
init: () => null,
|
|
62
|
+
apply: (tr, held) => {
|
|
63
|
+
const next = tr.getMeta(navigationKey);
|
|
64
|
+
return isRequest(next) ? next : held;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
props: {
|
|
68
|
+
handleClickOn(view, _pos, node, _nodePos, _event, direct) {
|
|
69
|
+
if (!direct || node.type !== docxSchema.nodes.noteReference) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const kind = kinds.find((named) => named === node.attrs.kind);
|
|
73
|
+
const id = node.attrs.id;
|
|
74
|
+
if (kind === void 0 || typeof id !== "string") return false;
|
|
75
|
+
return openNoteCommand(kind, id)(view.state, view.dispatch);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
noteNavigation,
|
|
82
|
+
noteReferenceAt,
|
|
83
|
+
openNoteCommand,
|
|
84
|
+
requestNote,
|
|
85
|
+
requestedNote
|
|
86
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from "prosemirror-state";
|
|
2
|
+
import { noteLabelsIn } from "../../docx/notes/numbering";
|
|
3
|
+
export type NoteLabeller = typeof noteLabelsIn;
|
|
4
|
+
/**
|
|
5
|
+
* Labels the note references again after a change that moves one, moves a section break, or
|
|
6
|
+
* replaces the numbering the snapshot holds.
|
|
7
|
+
*
|
|
8
|
+
* A label is a display value of an inline node, which the display derivation walk does not reach,
|
|
9
|
+
* so it is written here under the same pass: a relabel goes through a lock around the reference,
|
|
10
|
+
* and one undo takes it back with the edit that caused it. The relabel after a snapshot change
|
|
11
|
+
* follows no edit and goes to the history not at all.
|
|
12
|
+
*/
|
|
13
|
+
export declare function noteNumbering(labelsIn?: NoteLabeller): Plugin;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/editor/plugins/noteNumbering.ts
|
|
2
|
+
import { Plugin } from "prosemirror-state";
|
|
3
|
+
import { DocAttrStep } from "prosemirror-transform";
|
|
4
|
+
import { noteLabelSignature, noteLabelsIn } from "../../docx/notes/numbering.js";
|
|
5
|
+
import { sectionBreakOf } from "../../docx/sections.js";
|
|
6
|
+
import { displayOnly } from "../../schema/displayDerivation.js";
|
|
7
|
+
import { stepReaches } from "../../schema/guards.js";
|
|
8
|
+
import { documentOf } from "../editorDocument.js";
|
|
9
|
+
function countsNotes(node) {
|
|
10
|
+
if (node.type.name === "noteReference") return true;
|
|
11
|
+
const pPr = node.attrs.pPr;
|
|
12
|
+
return node.type.name === "paragraph" && typeof pPr === "string" && sectionBreakOf(pPr) !== null;
|
|
13
|
+
}
|
|
14
|
+
function reachesLabels(tr) {
|
|
15
|
+
return tr.steps.some(
|
|
16
|
+
(step, index) => step instanceof DocAttrStep ? step.attr === "sectPr" : stepReaches(
|
|
17
|
+
step,
|
|
18
|
+
tr.docs[index],
|
|
19
|
+
tr.docs[index + 1] ?? tr.doc,
|
|
20
|
+
countsNotes
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
function labelsMayHaveMoved(transactions, oldState, newState) {
|
|
25
|
+
return transactions.some(reachesLabels) && noteLabelSignature(oldState.doc) !== noteLabelSignature(newState.doc);
|
|
26
|
+
}
|
|
27
|
+
function numberingReplaced(before, after) {
|
|
28
|
+
return before.noteNumbering !== after.noteNumbering || before.specialNotes !== after.specialNotes;
|
|
29
|
+
}
|
|
30
|
+
function noteNumbering(labelsIn = noteLabelsIn) {
|
|
31
|
+
return new Plugin({
|
|
32
|
+
appendTransaction(transactions, oldState, newState) {
|
|
33
|
+
const document = documentOf(newState);
|
|
34
|
+
const replaced = numberingReplaced(documentOf(oldState), document);
|
|
35
|
+
if (!replaced && !labelsMayHaveMoved(transactions, oldState, newState)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const labels = labelsIn(
|
|
39
|
+
newState.doc,
|
|
40
|
+
document.noteNumbering,
|
|
41
|
+
document.specialNotes
|
|
42
|
+
);
|
|
43
|
+
const tr = newState.tr;
|
|
44
|
+
for (const [pos, label] of labels) {
|
|
45
|
+
if (tr.doc.nodeAt(pos)?.attrs.label !== label) {
|
|
46
|
+
tr.setNodeAttribute(pos, "label", label);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!tr.docChanged) return null;
|
|
50
|
+
tr.setMeta(displayOnly, true);
|
|
51
|
+
if (replaced) tr.setMeta("addToHistory", false);
|
|
52
|
+
return tr;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
noteNumbering
|
|
58
|
+
};
|
|
@@ -4,6 +4,7 @@ import { TableMap } from "prosemirror-tables";
|
|
|
4
4
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
5
5
|
import { isLockedCell } from "../../schema/locks.js";
|
|
6
6
|
import { editorClassNames } from "../../styles/classNames.js";
|
|
7
|
+
import { pageScaleAround } from "../../styles/visualScale.js";
|
|
7
8
|
import {
|
|
8
9
|
buildResizeRowTransaction,
|
|
9
10
|
resizedRowHeight,
|
|
@@ -52,12 +53,6 @@ function edgeUnder(view, event) {
|
|
|
52
53
|
if (!(rowDom instanceof HTMLTableRowElement)) return null;
|
|
53
54
|
return { ...spot, ...hit, tableDom, rowDom, rowPos };
|
|
54
55
|
}
|
|
55
|
-
function visualScale(view) {
|
|
56
|
-
const layer = view.dom.closest(`.${editorClassNames.pageLayer}`);
|
|
57
|
-
const raw = layer instanceof HTMLElement ? getComputedStyle(layer).zoom : "";
|
|
58
|
-
const parsed = Number.parseFloat(raw);
|
|
59
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
|
|
60
|
-
}
|
|
61
56
|
function previewDecorations(doc, preview) {
|
|
62
57
|
const row = doc.nodeAt(preview.rowPos);
|
|
63
58
|
if (row?.type.spec.tableRole !== "row") return DecorationSet.empty;
|
|
@@ -137,7 +132,7 @@ function rowResize() {
|
|
|
137
132
|
function start(view, event) {
|
|
138
133
|
const target = edgeUnder(view, event);
|
|
139
134
|
if (!target) return false;
|
|
140
|
-
const scale =
|
|
135
|
+
const scale = pageScaleAround(view.dom);
|
|
141
136
|
const startHeightPt = target.rowDom.getBoundingClientRect().height / scale * (72 / 96);
|
|
142
137
|
drag = {
|
|
143
138
|
view,
|
|
@@ -5,6 +5,7 @@ import { PX_PER_PT } from "../../docx/pageGeometry.js";
|
|
|
5
5
|
import { toParagraphFormat } from "../../model/format.js";
|
|
6
6
|
import { docxSchema } from "../../schema/index.js";
|
|
7
7
|
import { editorClassNames, editorCssVariables } from "../../styles/classNames.js";
|
|
8
|
+
import { pageScaleAround } from "../../styles/visualScale.js";
|
|
8
9
|
import { documentDefaultTabStopPt } from "../documentStyles.js";
|
|
9
10
|
import { setTabWidths, tabWidths } from "./tabDecorations.js";
|
|
10
11
|
var POSITION_EPSILON_PT = 0.01;
|
|
@@ -46,11 +47,6 @@ function numeric(value) {
|
|
|
46
47
|
function computedStyle(element) {
|
|
47
48
|
return element.ownerDocument.defaultView?.getComputedStyle(element) ?? getComputedStyle(element);
|
|
48
49
|
}
|
|
49
|
-
function scaleOf(view) {
|
|
50
|
-
const layer = view.dom.closest(`.${editorClassNames.pageLayer}`);
|
|
51
|
-
const parsed = Number.parseFloat(layer ? computedStyle(layer).zoom : "");
|
|
52
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
|
|
53
|
-
}
|
|
54
50
|
function paragraphLayoutContext(view, slot) {
|
|
55
51
|
const $pos = view.state.doc.resolve(view.posAtDOM(slot, 0));
|
|
56
52
|
for (let depth = $pos.depth; depth > 0; depth -= 1) {
|
|
@@ -84,7 +80,6 @@ function cloneParagraph(paragraph, widthPx, resetTabs) {
|
|
|
84
80
|
clone.style.textAlign = "start";
|
|
85
81
|
clone.style.visibility = "hidden";
|
|
86
82
|
clone.style.pointerEvents = "none";
|
|
87
|
-
clone.style.zoom = "1";
|
|
88
83
|
clone.setAttribute("aria-hidden", "true");
|
|
89
84
|
if (resetTabs) {
|
|
90
85
|
for (const slot of clone.querySelectorAll(
|
|
@@ -221,7 +216,7 @@ function sameWidths(left, right) {
|
|
|
221
216
|
return true;
|
|
222
217
|
}
|
|
223
218
|
function measureTabs(view, cache, generation) {
|
|
224
|
-
const scale =
|
|
219
|
+
const scale = pageScaleAround(view.dom);
|
|
225
220
|
const defaultStopPt = documentDefaultTabStopPt(view.state);
|
|
226
221
|
const widths = new Map(tabWidths(view.state));
|
|
227
222
|
const paragraphs = /* @__PURE__ */ new Map();
|