@sametalmdr/word-editor 0.2.0-alpha → 0.3.0-alpha
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 +108 -70
- package/LICENSE +21 -21
- package/README.md +286 -247
- package/dist/canvas/box-clipboard.js +238 -238
- package/dist/canvas/box-selection.js +80 -80
- package/dist/canvas/canvas-editor.js +502 -502
- package/dist/canvas/controller.js +717 -717
- package/dist/canvas/geometry.js +131 -131
- package/dist/canvas/gesture.d.ts +0 -1
- package/dist/canvas/gesture.js +289 -289
- package/dist/canvas/handles.js +212 -212
- package/dist/canvas/menu.js +82 -82
- package/dist/canvas/mode-controller.js +81 -81
- package/dist/canvas/nudge.js +108 -108
- package/dist/canvas/overlay.js +149 -149
- package/dist/canvas/snap.js +132 -132
- package/dist/canvas/viewport.js +112 -112
- package/dist/canvas/z-order.js +171 -171
- package/dist/commands/alignment.js +150 -150
- package/dist/commands/box.js +272 -272
- package/dist/commands/clipboard.js +93 -93
- package/dist/commands/graphemes.js +112 -112
- package/dist/commands/marks.js +153 -153
- package/dist/commands/paragraph-props.js +176 -176
- package/dist/commands/size.js +368 -368
- package/dist/commands/text.js +291 -291
- package/dist/docx.d.ts +1 -0
- package/dist/docx.js +15 -13
- package/dist/editor/editing-host.js +434 -434
- package/dist/element.js +41 -41
- package/dist/fonts/OFL-Carlito.txt +94 -0
- package/dist/fonts/calibri-metrics.d.ts +27 -0
- package/dist/fonts/calibri-metrics.js +88 -0
- package/dist/history/history.js +433 -433
- package/dist/index.js +63 -63
- package/dist/input/beforeinput.js +224 -224
- package/dist/input/clipboard.js +107 -107
- package/dist/input/composition.js +341 -341
- package/dist/input/keymap.js +54 -54
- package/dist/io/ooxml/custgeom.js +72 -72
- package/dist/io/ooxml/drawingml.js +232 -232
- package/dist/io/ooxml/edits.js +67 -67
- package/dist/io/ooxml/export-splice.js +880 -880
- package/dist/io/ooxml/export.js +365 -365
- package/dist/io/ooxml/fill.d.ts +93 -8
- package/dist/io/ooxml/fill.js +618 -562
- package/dist/io/ooxml/fit.d.ts +252 -0
- package/dist/io/ooxml/fit.js +598 -0
- package/dist/io/ooxml/import-gate.js +200 -200
- package/dist/io/ooxml/import.js +691 -691
- package/dist/io/ooxml/inflate.js +126 -126
- package/dist/io/ooxml/media.js +87 -87
- package/dist/io/ooxml/ns.d.ts +2 -0
- package/dist/io/ooxml/ns.js +26 -21
- package/dist/io/ooxml/numbering.js +290 -290
- package/dist/io/ooxml/opc-write.js +171 -171
- package/dist/io/ooxml/provenance.js +93 -93
- package/dist/io/ooxml/recipe.js +708 -708
- package/dist/io/ooxml/relationships.js +59 -59
- package/dist/io/ooxml/run-fonts.d.ts +89 -0
- package/dist/io/ooxml/run-fonts.js +378 -0
- package/dist/io/ooxml/slots.d.ts +4 -0
- package/dist/io/ooxml/slots.js +582 -581
- package/dist/io/ooxml/styles-resolve.js +257 -257
- package/dist/io/ooxml/units.js +43 -43
- package/dist/io/ooxml/value.js +120 -120
- package/dist/io/ooxml/xml-scan.js +616 -616
- package/dist/io/ooxml/zip-container.js +228 -228
- package/dist/io/ooxml/zip-reject.js +106 -106
- package/dist/measure/placement.js +83 -83
- package/dist/measure/report.js +75 -75
- package/dist/model/ids.js +70 -70
- package/dist/model/normalize.js +146 -146
- package/dist/model/position.js +283 -283
- package/dist/model/transaction.js +209 -209
- package/dist/platform/capabilities.js +25 -25
- package/dist/react/index.js +21 -21
- package/dist/selection/controller.js +315 -315
- package/dist/selection/dom-map.js +334 -334
- package/dist/state/editor-state.js +109 -109
- package/dist/styles.css +66 -66
- package/package.json +94 -93
package/dist/commands/text.js
CHANGED
|
@@ -1,291 +1,291 @@
|
|
|
1
|
-
import { devAssert } from "../dev.js";
|
|
2
|
-
import { createRun, locateParagraph, paragraphLength, paragraphText } from "../model/node.js";
|
|
3
|
-
import {
|
|
4
|
-
collapsedSelection,
|
|
5
|
-
containerIdOf,
|
|
6
|
-
createPosition,
|
|
7
|
-
isCollapsed,
|
|
8
|
-
isSingleContainer,
|
|
9
|
-
paragraphIdOf,
|
|
10
|
-
selectionRange,
|
|
11
|
-
withOffset,
|
|
12
|
-
} from "../model/position.js";
|
|
13
|
-
import { deleteStep, insertStep, mergeParagraphsStep, splitParagraphStep } from "../model/step.js";
|
|
14
|
-
import { createTransaction } from "../model/transaction.js";
|
|
15
|
-
import { marksAt } from "./marks.js";
|
|
16
|
-
import { previousGraphemeStart, nextGraphemeEnd } from "./graphemes.js";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Deletes the selected range, appending steps to `tr`, and returns the position
|
|
20
|
-
* the selection collapses to — already expressed in post-deletion coordinates.
|
|
21
|
-
*
|
|
22
|
-
* A multi-paragraph range decomposes into per-paragraph deletes plus merges,
|
|
23
|
-
* because `DeleteStep` never spans paragraphs (ADR-0003):
|
|
24
|
-
*
|
|
25
|
-
* A|B|C selected from A@2 to C@3
|
|
26
|
-
* delete A@2..A.end · delete all of B · delete C@0..C@3
|
|
27
|
-
* merge A (absorbs the emptied B) · merge A again (absorbs C's remainder)
|
|
28
|
-
*
|
|
29
|
-
* The range may span paragraphs but never containers (ADR-0007), which is what
|
|
30
|
-
* keeps that decomposition unchanged from P1 — every step it emits stays inside
|
|
31
|
-
* one container.
|
|
32
|
-
*
|
|
33
|
-
* @param {import("../model/transaction.js").TransactionBuilder} tr
|
|
34
|
-
* @param {import("../model/position.js").Selection} selection
|
|
35
|
-
* @returns {import("../model/position.js").Position}
|
|
36
|
-
*/
|
|
37
|
-
export function deleteRange(tr, selection) {
|
|
38
|
-
const doc = tr.doc;
|
|
39
|
-
devAssert(
|
|
40
|
-
isSingleContainer(selection),
|
|
41
|
-
"deleteRange: a text selection may not span containers",
|
|
42
|
-
);
|
|
43
|
-
const range = selectionRange(doc, selection);
|
|
44
|
-
if (range === null || !isSingleContainer(selection)) {
|
|
45
|
-
return selection.head;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const fromAt = locateParagraph(doc, paragraphIdOf(range.from));
|
|
49
|
-
const toAt = locateParagraph(doc, paragraphIdOf(range.to));
|
|
50
|
-
if (fromAt === null || toAt === null || fromAt.containerIndex !== toAt.containerIndex) {
|
|
51
|
-
return range.from;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (fromAt.paraIndex === toAt.paraIndex) {
|
|
55
|
-
if (range.to.offset > range.from.offset) {
|
|
56
|
-
tr.step(deleteStep(range.from, range.to));
|
|
57
|
-
}
|
|
58
|
-
return range.from;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const container = fromAt.container;
|
|
62
|
-
const containerId = container.id;
|
|
63
|
-
const firstId = fromAt.para.id;
|
|
64
|
-
|
|
65
|
-
// Trim each paragraph first, while their ids and offsets are still valid.
|
|
66
|
-
const firstEnd = paragraphLength(fromAt.para);
|
|
67
|
-
if (firstEnd > range.from.offset) {
|
|
68
|
-
tr.step(deleteStep(range.from, withOffset(range.from, firstEnd)));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
for (let i = fromAt.paraIndex + 1; i < toAt.paraIndex; i += 1) {
|
|
72
|
-
const para = container.content[i];
|
|
73
|
-
const length = paragraphLength(para);
|
|
74
|
-
if (length > 0) {
|
|
75
|
-
tr.step(
|
|
76
|
-
deleteStep(
|
|
77
|
-
createPosition(containerId, para.id, 0),
|
|
78
|
-
createPosition(containerId, para.id, length),
|
|
79
|
-
),
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const lastId = toAt.para.id;
|
|
85
|
-
if (range.to.offset > 0) {
|
|
86
|
-
tr.step(
|
|
87
|
-
deleteStep(
|
|
88
|
-
createPosition(containerId, lastId, 0),
|
|
89
|
-
createPosition(containerId, lastId, range.to.offset),
|
|
90
|
-
),
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Then pull the emptied paragraphs into the first one.
|
|
95
|
-
for (let i = fromAt.paraIndex; i < toAt.paraIndex; i += 1) {
|
|
96
|
-
tr.step(mergeParagraphsStep(firstId));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return createPosition(containerId, firstId, range.from.offset);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Inserts text at the cursor, replacing the selection if there is one.
|
|
104
|
-
*
|
|
105
|
-
* The replacement happens **in the same transaction** as the insert. Two
|
|
106
|
-
* transactions would mean two Ctrl+Z presses to undo one user action.
|
|
107
|
-
*
|
|
108
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
109
|
-
* @param {string} text
|
|
110
|
-
* @param {import("../model/transaction.js").TransactionOrigin} [origin]
|
|
111
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
112
|
-
*/
|
|
113
|
-
export function insertText(state, text, origin = "input") {
|
|
114
|
-
if (text === "") {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const tr = createTransaction(state.doc, state.selection, origin);
|
|
119
|
-
const at = isCollapsed(state.selection)
|
|
120
|
-
? state.selection.head
|
|
121
|
-
: deleteRange(tr, state.selection);
|
|
122
|
-
|
|
123
|
-
const marks = state.storedMarks ?? marksAt(tr.doc, at);
|
|
124
|
-
tr.step(insertStep(at, [createRun(text, marks)]));
|
|
125
|
-
tr.setSelection(collapsedSelection(withOffset(at, at.offset + text.length)));
|
|
126
|
-
|
|
127
|
-
return tr.build();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Enter. Replaces the selection first, same single-transaction rule as
|
|
132
|
-
* `insertText`. The new paragraph is a sibling inside the same container — Enter
|
|
133
|
-
* never creates a box.
|
|
134
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
135
|
-
* @param {import("../model/ids.js").IdFactory} ids
|
|
136
|
-
* @returns {import("../model/transaction.js").Transaction}
|
|
137
|
-
*/
|
|
138
|
-
export function splitParagraph(state, ids) {
|
|
139
|
-
const tr = createTransaction(state.doc, state.selection, "input");
|
|
140
|
-
const at = isCollapsed(state.selection)
|
|
141
|
-
? state.selection.head
|
|
142
|
-
: deleteRange(tr, state.selection);
|
|
143
|
-
|
|
144
|
-
const newId = ids.next();
|
|
145
|
-
tr.step(splitParagraphStep(at, newId));
|
|
146
|
-
tr.setSelection(collapsedSelection(createPosition(containerIdOf(at), newId, 0)));
|
|
147
|
-
|
|
148
|
-
return tr.build();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Backspace.
|
|
153
|
-
*
|
|
154
|
-
* | selection | position | action |
|
|
155
|
-
* |---|---|---|
|
|
156
|
-
* | range | — | delete the range |
|
|
157
|
-
* | collapsed | `offset > 0` | delete the previous grapheme |
|
|
158
|
-
* | collapsed | `offset === 0`, a paragraph precedes **in this container** | merge into it |
|
|
159
|
-
* | collapsed | `offset === 0`, first paragraph of the container | no-op |
|
|
160
|
-
*
|
|
161
|
-
* The last row is the container rule: backspace at the top of a box does
|
|
162
|
-
* nothing. It does **not** reach back into the previous box — boxes have no
|
|
163
|
-
* meaningful document order between them, so merging across one would join two
|
|
164
|
-
* unrelated pieces of the page.
|
|
165
|
-
*
|
|
166
|
-
* The merge case has to compute the join point *before* the step runs, because
|
|
167
|
-
* the current paragraph is about to stop existing.
|
|
168
|
-
*
|
|
169
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
170
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
171
|
-
*/
|
|
172
|
-
export function deleteBackward(state) {
|
|
173
|
-
const { doc, selection } = state;
|
|
174
|
-
|
|
175
|
-
if (!isCollapsed(selection)) {
|
|
176
|
-
return deleteSelection(state);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const pos = selection.head;
|
|
180
|
-
const at = locateParagraph(doc, paragraphIdOf(pos));
|
|
181
|
-
if (at === null) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (pos.offset > 0) {
|
|
186
|
-
const text = paragraphText(at.para);
|
|
187
|
-
const start = previousGraphemeStart(text, pos.offset);
|
|
188
|
-
const tr = createTransaction(doc, selection, "input");
|
|
189
|
-
tr.step(deleteStep(withOffset(pos, start), pos));
|
|
190
|
-
tr.setSelection(collapsedSelection(withOffset(pos, start)));
|
|
191
|
-
return tr.build();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (at.paraIndex === 0) {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const previous = at.container.content[at.paraIndex - 1];
|
|
199
|
-
const joinAt = createPosition(at.container.id, previous.id, paragraphLength(previous));
|
|
200
|
-
|
|
201
|
-
const tr = createTransaction(doc, selection, "input");
|
|
202
|
-
tr.step(mergeParagraphsStep(previous.id));
|
|
203
|
-
tr.setSelection(collapsedSelection(joinAt));
|
|
204
|
-
return tr.build();
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Delete (forward). Mirrors `deleteBackward`:
|
|
209
|
-
*
|
|
210
|
-
* | selection | position | action |
|
|
211
|
-
* |---|---|---|
|
|
212
|
-
* | range | — | delete the range |
|
|
213
|
-
* | collapsed | `offset < len` | delete the next grapheme |
|
|
214
|
-
* | collapsed | `offset === len`, a paragraph follows **in this container** | merge it into this one |
|
|
215
|
-
* | collapsed | `offset === len`, last paragraph of the container | no-op |
|
|
216
|
-
*
|
|
217
|
-
* Both directions land on the same join point; forward deletion simply already
|
|
218
|
-
* sits there, so the selection does not move.
|
|
219
|
-
*
|
|
220
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
221
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
222
|
-
*/
|
|
223
|
-
export function deleteForward(state) {
|
|
224
|
-
const { doc, selection } = state;
|
|
225
|
-
|
|
226
|
-
if (!isCollapsed(selection)) {
|
|
227
|
-
return deleteSelection(state);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const pos = selection.head;
|
|
231
|
-
const at = locateParagraph(doc, paragraphIdOf(pos));
|
|
232
|
-
if (at === null) {
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const { para } = at;
|
|
237
|
-
const length = paragraphLength(para);
|
|
238
|
-
|
|
239
|
-
if (pos.offset < length) {
|
|
240
|
-
const end = nextGraphemeEnd(paragraphText(para), pos.offset);
|
|
241
|
-
const tr = createTransaction(doc, selection, "input");
|
|
242
|
-
tr.step(deleteStep(pos, withOffset(pos, end)));
|
|
243
|
-
tr.setSelection(collapsedSelection(pos));
|
|
244
|
-
return tr.build();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (at.paraIndex === at.container.content.length - 1) {
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const tr = createTransaction(doc, selection, "input");
|
|
252
|
-
tr.step(mergeParagraphsStep(para.id));
|
|
253
|
-
tr.setSelection(collapsedSelection(pos));
|
|
254
|
-
return tr.build();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Deletes the current selection and nothing else.
|
|
259
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
260
|
-
* @param {import("../model/transaction.js").TransactionOrigin} [origin]
|
|
261
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
262
|
-
*/
|
|
263
|
-
export function deleteSelection(state, origin = "input") {
|
|
264
|
-
if (isCollapsed(state.selection)) {
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
const tr = createTransaction(state.doc, state.selection, origin);
|
|
268
|
-
const at = deleteRange(tr, state.selection);
|
|
269
|
-
if (!tr.changesDoc) {
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
tr.setSelection(collapsedSelection(at));
|
|
273
|
-
return tr.build();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Deletes an explicit range — used for the ranges the browser computes for word
|
|
278
|
-
* and line deletion via `getTargetRanges()`.
|
|
279
|
-
* @param {import("../state/editor-state.js").EditorState} state
|
|
280
|
-
* @param {import("../model/position.js").Selection} range
|
|
281
|
-
* @returns {import("../model/transaction.js").Transaction | null}
|
|
282
|
-
*/
|
|
283
|
-
export function deleteExplicitRange(state, range) {
|
|
284
|
-
const tr = createTransaction(state.doc, state.selection, "input");
|
|
285
|
-
const at = deleteRange(tr, range);
|
|
286
|
-
if (!tr.changesDoc) {
|
|
287
|
-
return null;
|
|
288
|
-
}
|
|
289
|
-
tr.setSelection(collapsedSelection(at));
|
|
290
|
-
return tr.build();
|
|
291
|
-
}
|
|
1
|
+
import { devAssert } from "../dev.js";
|
|
2
|
+
import { createRun, locateParagraph, paragraphLength, paragraphText } from "../model/node.js";
|
|
3
|
+
import {
|
|
4
|
+
collapsedSelection,
|
|
5
|
+
containerIdOf,
|
|
6
|
+
createPosition,
|
|
7
|
+
isCollapsed,
|
|
8
|
+
isSingleContainer,
|
|
9
|
+
paragraphIdOf,
|
|
10
|
+
selectionRange,
|
|
11
|
+
withOffset,
|
|
12
|
+
} from "../model/position.js";
|
|
13
|
+
import { deleteStep, insertStep, mergeParagraphsStep, splitParagraphStep } from "../model/step.js";
|
|
14
|
+
import { createTransaction } from "../model/transaction.js";
|
|
15
|
+
import { marksAt } from "./marks.js";
|
|
16
|
+
import { previousGraphemeStart, nextGraphemeEnd } from "./graphemes.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Deletes the selected range, appending steps to `tr`, and returns the position
|
|
20
|
+
* the selection collapses to — already expressed in post-deletion coordinates.
|
|
21
|
+
*
|
|
22
|
+
* A multi-paragraph range decomposes into per-paragraph deletes plus merges,
|
|
23
|
+
* because `DeleteStep` never spans paragraphs (ADR-0003):
|
|
24
|
+
*
|
|
25
|
+
* A|B|C selected from A@2 to C@3
|
|
26
|
+
* delete A@2..A.end · delete all of B · delete C@0..C@3
|
|
27
|
+
* merge A (absorbs the emptied B) · merge A again (absorbs C's remainder)
|
|
28
|
+
*
|
|
29
|
+
* The range may span paragraphs but never containers (ADR-0007), which is what
|
|
30
|
+
* keeps that decomposition unchanged from P1 — every step it emits stays inside
|
|
31
|
+
* one container.
|
|
32
|
+
*
|
|
33
|
+
* @param {import("../model/transaction.js").TransactionBuilder} tr
|
|
34
|
+
* @param {import("../model/position.js").Selection} selection
|
|
35
|
+
* @returns {import("../model/position.js").Position}
|
|
36
|
+
*/
|
|
37
|
+
export function deleteRange(tr, selection) {
|
|
38
|
+
const doc = tr.doc;
|
|
39
|
+
devAssert(
|
|
40
|
+
isSingleContainer(selection),
|
|
41
|
+
"deleteRange: a text selection may not span containers",
|
|
42
|
+
);
|
|
43
|
+
const range = selectionRange(doc, selection);
|
|
44
|
+
if (range === null || !isSingleContainer(selection)) {
|
|
45
|
+
return selection.head;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const fromAt = locateParagraph(doc, paragraphIdOf(range.from));
|
|
49
|
+
const toAt = locateParagraph(doc, paragraphIdOf(range.to));
|
|
50
|
+
if (fromAt === null || toAt === null || fromAt.containerIndex !== toAt.containerIndex) {
|
|
51
|
+
return range.from;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (fromAt.paraIndex === toAt.paraIndex) {
|
|
55
|
+
if (range.to.offset > range.from.offset) {
|
|
56
|
+
tr.step(deleteStep(range.from, range.to));
|
|
57
|
+
}
|
|
58
|
+
return range.from;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const container = fromAt.container;
|
|
62
|
+
const containerId = container.id;
|
|
63
|
+
const firstId = fromAt.para.id;
|
|
64
|
+
|
|
65
|
+
// Trim each paragraph first, while their ids and offsets are still valid.
|
|
66
|
+
const firstEnd = paragraphLength(fromAt.para);
|
|
67
|
+
if (firstEnd > range.from.offset) {
|
|
68
|
+
tr.step(deleteStep(range.from, withOffset(range.from, firstEnd)));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (let i = fromAt.paraIndex + 1; i < toAt.paraIndex; i += 1) {
|
|
72
|
+
const para = container.content[i];
|
|
73
|
+
const length = paragraphLength(para);
|
|
74
|
+
if (length > 0) {
|
|
75
|
+
tr.step(
|
|
76
|
+
deleteStep(
|
|
77
|
+
createPosition(containerId, para.id, 0),
|
|
78
|
+
createPosition(containerId, para.id, length),
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const lastId = toAt.para.id;
|
|
85
|
+
if (range.to.offset > 0) {
|
|
86
|
+
tr.step(
|
|
87
|
+
deleteStep(
|
|
88
|
+
createPosition(containerId, lastId, 0),
|
|
89
|
+
createPosition(containerId, lastId, range.to.offset),
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Then pull the emptied paragraphs into the first one.
|
|
95
|
+
for (let i = fromAt.paraIndex; i < toAt.paraIndex; i += 1) {
|
|
96
|
+
tr.step(mergeParagraphsStep(firstId));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return createPosition(containerId, firstId, range.from.offset);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Inserts text at the cursor, replacing the selection if there is one.
|
|
104
|
+
*
|
|
105
|
+
* The replacement happens **in the same transaction** as the insert. Two
|
|
106
|
+
* transactions would mean two Ctrl+Z presses to undo one user action.
|
|
107
|
+
*
|
|
108
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
109
|
+
* @param {string} text
|
|
110
|
+
* @param {import("../model/transaction.js").TransactionOrigin} [origin]
|
|
111
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
112
|
+
*/
|
|
113
|
+
export function insertText(state, text, origin = "input") {
|
|
114
|
+
if (text === "") {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const tr = createTransaction(state.doc, state.selection, origin);
|
|
119
|
+
const at = isCollapsed(state.selection)
|
|
120
|
+
? state.selection.head
|
|
121
|
+
: deleteRange(tr, state.selection);
|
|
122
|
+
|
|
123
|
+
const marks = state.storedMarks ?? marksAt(tr.doc, at);
|
|
124
|
+
tr.step(insertStep(at, [createRun(text, marks)]));
|
|
125
|
+
tr.setSelection(collapsedSelection(withOffset(at, at.offset + text.length)));
|
|
126
|
+
|
|
127
|
+
return tr.build();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Enter. Replaces the selection first, same single-transaction rule as
|
|
132
|
+
* `insertText`. The new paragraph is a sibling inside the same container — Enter
|
|
133
|
+
* never creates a box.
|
|
134
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
135
|
+
* @param {import("../model/ids.js").IdFactory} ids
|
|
136
|
+
* @returns {import("../model/transaction.js").Transaction}
|
|
137
|
+
*/
|
|
138
|
+
export function splitParagraph(state, ids) {
|
|
139
|
+
const tr = createTransaction(state.doc, state.selection, "input");
|
|
140
|
+
const at = isCollapsed(state.selection)
|
|
141
|
+
? state.selection.head
|
|
142
|
+
: deleteRange(tr, state.selection);
|
|
143
|
+
|
|
144
|
+
const newId = ids.next();
|
|
145
|
+
tr.step(splitParagraphStep(at, newId));
|
|
146
|
+
tr.setSelection(collapsedSelection(createPosition(containerIdOf(at), newId, 0)));
|
|
147
|
+
|
|
148
|
+
return tr.build();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Backspace.
|
|
153
|
+
*
|
|
154
|
+
* | selection | position | action |
|
|
155
|
+
* |---|---|---|
|
|
156
|
+
* | range | — | delete the range |
|
|
157
|
+
* | collapsed | `offset > 0` | delete the previous grapheme |
|
|
158
|
+
* | collapsed | `offset === 0`, a paragraph precedes **in this container** | merge into it |
|
|
159
|
+
* | collapsed | `offset === 0`, first paragraph of the container | no-op |
|
|
160
|
+
*
|
|
161
|
+
* The last row is the container rule: backspace at the top of a box does
|
|
162
|
+
* nothing. It does **not** reach back into the previous box — boxes have no
|
|
163
|
+
* meaningful document order between them, so merging across one would join two
|
|
164
|
+
* unrelated pieces of the page.
|
|
165
|
+
*
|
|
166
|
+
* The merge case has to compute the join point *before* the step runs, because
|
|
167
|
+
* the current paragraph is about to stop existing.
|
|
168
|
+
*
|
|
169
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
170
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
171
|
+
*/
|
|
172
|
+
export function deleteBackward(state) {
|
|
173
|
+
const { doc, selection } = state;
|
|
174
|
+
|
|
175
|
+
if (!isCollapsed(selection)) {
|
|
176
|
+
return deleteSelection(state);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const pos = selection.head;
|
|
180
|
+
const at = locateParagraph(doc, paragraphIdOf(pos));
|
|
181
|
+
if (at === null) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (pos.offset > 0) {
|
|
186
|
+
const text = paragraphText(at.para);
|
|
187
|
+
const start = previousGraphemeStart(text, pos.offset);
|
|
188
|
+
const tr = createTransaction(doc, selection, "input");
|
|
189
|
+
tr.step(deleteStep(withOffset(pos, start), pos));
|
|
190
|
+
tr.setSelection(collapsedSelection(withOffset(pos, start)));
|
|
191
|
+
return tr.build();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (at.paraIndex === 0) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const previous = at.container.content[at.paraIndex - 1];
|
|
199
|
+
const joinAt = createPosition(at.container.id, previous.id, paragraphLength(previous));
|
|
200
|
+
|
|
201
|
+
const tr = createTransaction(doc, selection, "input");
|
|
202
|
+
tr.step(mergeParagraphsStep(previous.id));
|
|
203
|
+
tr.setSelection(collapsedSelection(joinAt));
|
|
204
|
+
return tr.build();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Delete (forward). Mirrors `deleteBackward`:
|
|
209
|
+
*
|
|
210
|
+
* | selection | position | action |
|
|
211
|
+
* |---|---|---|
|
|
212
|
+
* | range | — | delete the range |
|
|
213
|
+
* | collapsed | `offset < len` | delete the next grapheme |
|
|
214
|
+
* | collapsed | `offset === len`, a paragraph follows **in this container** | merge it into this one |
|
|
215
|
+
* | collapsed | `offset === len`, last paragraph of the container | no-op |
|
|
216
|
+
*
|
|
217
|
+
* Both directions land on the same join point; forward deletion simply already
|
|
218
|
+
* sits there, so the selection does not move.
|
|
219
|
+
*
|
|
220
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
221
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
222
|
+
*/
|
|
223
|
+
export function deleteForward(state) {
|
|
224
|
+
const { doc, selection } = state;
|
|
225
|
+
|
|
226
|
+
if (!isCollapsed(selection)) {
|
|
227
|
+
return deleteSelection(state);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const pos = selection.head;
|
|
231
|
+
const at = locateParagraph(doc, paragraphIdOf(pos));
|
|
232
|
+
if (at === null) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const { para } = at;
|
|
237
|
+
const length = paragraphLength(para);
|
|
238
|
+
|
|
239
|
+
if (pos.offset < length) {
|
|
240
|
+
const end = nextGraphemeEnd(paragraphText(para), pos.offset);
|
|
241
|
+
const tr = createTransaction(doc, selection, "input");
|
|
242
|
+
tr.step(deleteStep(pos, withOffset(pos, end)));
|
|
243
|
+
tr.setSelection(collapsedSelection(pos));
|
|
244
|
+
return tr.build();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (at.paraIndex === at.container.content.length - 1) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const tr = createTransaction(doc, selection, "input");
|
|
252
|
+
tr.step(mergeParagraphsStep(para.id));
|
|
253
|
+
tr.setSelection(collapsedSelection(pos));
|
|
254
|
+
return tr.build();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Deletes the current selection and nothing else.
|
|
259
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
260
|
+
* @param {import("../model/transaction.js").TransactionOrigin} [origin]
|
|
261
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
262
|
+
*/
|
|
263
|
+
export function deleteSelection(state, origin = "input") {
|
|
264
|
+
if (isCollapsed(state.selection)) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const tr = createTransaction(state.doc, state.selection, origin);
|
|
268
|
+
const at = deleteRange(tr, state.selection);
|
|
269
|
+
if (!tr.changesDoc) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
tr.setSelection(collapsedSelection(at));
|
|
273
|
+
return tr.build();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Deletes an explicit range — used for the ranges the browser computes for word
|
|
278
|
+
* and line deletion via `getTargetRanges()`.
|
|
279
|
+
* @param {import("../state/editor-state.js").EditorState} state
|
|
280
|
+
* @param {import("../model/position.js").Selection} range
|
|
281
|
+
* @returns {import("../model/transaction.js").Transaction | null}
|
|
282
|
+
*/
|
|
283
|
+
export function deleteExplicitRange(state, range) {
|
|
284
|
+
const tr = createTransaction(state.doc, state.selection, "input");
|
|
285
|
+
const at = deleteRange(tr, range);
|
|
286
|
+
if (!tr.changesDoc) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
tr.setSelection(collapsedSelection(at));
|
|
290
|
+
return tr.build();
|
|
291
|
+
}
|
package/dist/docx.d.ts
CHANGED
package/dist/docx.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// The /docx subpath (FR2, ADR-0037): DOCX import/export for the editor, and
|
|
2
|
-
// the template fill engine. DOM-free — it runs in browsers, Node and Deno
|
|
3
|
-
// (invariant #4) and needs only TextEncoder/TextDecoder and
|
|
4
|
-
// CompressionStream/DecompressionStream("deflate-raw"). It makes no network
|
|
5
|
-
// request (invariant #3).
|
|
6
|
-
//
|
|
7
|
-
// importDocx/exportDocx are the editor's own IO; their shapes are not yet
|
|
8
|
-
// stable at 0.x alpha. The fill engine's surface is inspectTemplate,
|
|
9
|
-
// validateTemplate, fillTemplate and TemplateError
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
1
|
+
// The /docx subpath (FR2, ADR-0037): DOCX import/export for the editor, and
|
|
2
|
+
// the template fill engine. DOM-free — it runs in browsers, Node and Deno
|
|
3
|
+
// (invariant #4) and needs only TextEncoder/TextDecoder and
|
|
4
|
+
// CompressionStream/DecompressionStream("deflate-raw"). It makes no network
|
|
5
|
+
// request (invariant #3).
|
|
6
|
+
//
|
|
7
|
+
// importDocx/exportDocx are the editor's own IO; their shapes are not yet
|
|
8
|
+
// stable at 0.x alpha. The fill engine's surface is inspectTemplate,
|
|
9
|
+
// validateTemplate, fillTemplate and TemplateError, and checkFit (FR4a,
|
|
10
|
+
// ADR-0042): whether each value stays on the lines its box gives it.
|
|
11
|
+
|
|
12
|
+
export { importDocx } from "./io/ooxml/import.js";
|
|
13
|
+
export { exportDocx } from "./io/ooxml/export.js";
|
|
14
|
+
export { inspectTemplate, validateTemplate, fillTemplate, TemplateError, ENGINE_VERSION } from "./io/ooxml/fill.js";
|
|
15
|
+
export { checkFit } from "./io/ooxml/fit.js";
|