@shadow-garden/bapbong-editor 0.1.0 → 0.2.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/dist/index.cjs +949 -201
- package/dist/index.js +959 -203
- package/dist/lib/bapbong-editor.d.ts +27 -3
- package/dist/lib/bapbong-editor.d.ts.map +1 -1
- package/dist/lib/paste-images.d.ts +13 -0
- package/dist/lib/paste-images.d.ts.map +1 -0
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -1,8 +1,98 @@
|
|
|
1
1
|
// packages/editor/src/lib/bapbong-editor.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DOMParser as PMDOMParser,
|
|
4
|
+
Schema as Schema2
|
|
5
|
+
} from "prosemirror-model";
|
|
6
|
+
|
|
7
|
+
// packages/editor/src/lib/paste-images.ts
|
|
8
|
+
var MAX_DISPLAY_WIDTH = 600;
|
|
9
|
+
function imageFilesIn(dt) {
|
|
10
|
+
const files = [];
|
|
11
|
+
for (const item of Array.from(dt?.items ?? [])) {
|
|
12
|
+
if (item.kind === "file" && item.type.startsWith("image/")) {
|
|
13
|
+
const f = item.getAsFile();
|
|
14
|
+
if (f) files.push(f);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return files;
|
|
18
|
+
}
|
|
19
|
+
async function bitmapSize(blob) {
|
|
20
|
+
try {
|
|
21
|
+
const bmp = await createImageBitmap(blob);
|
|
22
|
+
const size = { width: bmp.width, height: bmp.height };
|
|
23
|
+
bmp.close();
|
|
24
|
+
return size;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
var toDataURL = (blob) => new Promise((resolve, reject) => {
|
|
30
|
+
const r = new FileReader();
|
|
31
|
+
r.onload = () => resolve(r.result);
|
|
32
|
+
r.onerror = () => reject(r.error);
|
|
33
|
+
r.readAsDataURL(blob);
|
|
34
|
+
});
|
|
35
|
+
async function insertImageBlobs(view, blobs) {
|
|
36
|
+
const imageType = view.state.schema.nodes["image"];
|
|
37
|
+
if (!imageType) return false;
|
|
38
|
+
const nodes = [];
|
|
39
|
+
for (const blob of blobs) {
|
|
40
|
+
const size = await bitmapSize(blob);
|
|
41
|
+
if (!size) continue;
|
|
42
|
+
const scale = Math.min(1, MAX_DISPLAY_WIDTH / size.width);
|
|
43
|
+
nodes.push(
|
|
44
|
+
imageType.create({
|
|
45
|
+
src: await toDataURL(blob),
|
|
46
|
+
width: Math.round(size.width * scale),
|
|
47
|
+
height: Math.round(size.height * scale)
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (!nodes.length) return false;
|
|
52
|
+
let tr = view.state.tr;
|
|
53
|
+
for (const node of nodes) tr = tr.replaceSelectionWith(node);
|
|
54
|
+
view.dispatch(tr.scrollIntoView());
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
function imagePasteHandler(view, event) {
|
|
58
|
+
const dt = event.clipboardData;
|
|
59
|
+
const files = imageFilesIn(dt);
|
|
60
|
+
if (!files.length) return false;
|
|
61
|
+
const html = dt?.getData("text/html") ?? "";
|
|
62
|
+
if (html) {
|
|
63
|
+
const body = new DOMParser().parseFromString(html, "text/html").body;
|
|
64
|
+
if ((body.textContent ?? "").trim()) return false;
|
|
65
|
+
}
|
|
66
|
+
void insertImageBlobs(view, files);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
3
69
|
|
|
4
70
|
// packages/model/dist/index.js
|
|
5
71
|
import { Schema } from "prosemirror-model";
|
|
72
|
+
function pastedParagraphAttrs(el, heading) {
|
|
73
|
+
const style = el.getAttribute("style") ?? "";
|
|
74
|
+
const m = /(?:^|;)\s*text-align\s*:\s*(center|right|justify)/i.exec(style);
|
|
75
|
+
return { heading, align: m ? m[1].toLowerCase() : null };
|
|
76
|
+
}
|
|
77
|
+
function pastedImageAttrs(el) {
|
|
78
|
+
const e = el;
|
|
79
|
+
const src = e.getAttribute("src") ?? "";
|
|
80
|
+
if (!/^data:image\//i.test(src)) return false;
|
|
81
|
+
const dim = (v) => {
|
|
82
|
+
const n = parseFloat(v ?? "");
|
|
83
|
+
return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
src,
|
|
87
|
+
alt: e.getAttribute("alt") ?? "",
|
|
88
|
+
width: dim(e.getAttribute("width")),
|
|
89
|
+
height: dim(e.getAttribute("height"))
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function pastedLinkAttrs(el) {
|
|
93
|
+
const href = (el.getAttribute("href") ?? "").trim();
|
|
94
|
+
return /^(https?:|mailto:|#)/i.test(href) ? { href } : false;
|
|
95
|
+
}
|
|
6
96
|
var schema = new Schema({
|
|
7
97
|
nodes: {
|
|
8
98
|
doc: {
|
|
@@ -38,6 +128,11 @@ var schema = new Schema({
|
|
|
38
128
|
// an <h1>–<h6> in toDOM so the a11y mirror is semantic), or null for a
|
|
39
129
|
// body paragraph.
|
|
40
130
|
heading: { default: null },
|
|
131
|
+
// Named Word paragraph style with no outline level: 'Title' |
|
|
132
|
+
// 'Subtitle', or null. Mutually exclusive with `heading` — the
|
|
133
|
+
// setParagraphStyle command is the only writer and keeps the
|
|
134
|
+
// invariant (styleId set ⇒ heading null).
|
|
135
|
+
styleId: { default: null },
|
|
41
136
|
// w:tabs — [{ pos, val: 'left'|'right'|'center'|'decimal', leader? }]
|
|
42
137
|
// in px from the paragraph's content left edge, or null. Importer-set.
|
|
43
138
|
tabs: { default: null },
|
|
@@ -46,15 +141,23 @@ var schema = new Schema({
|
|
|
46
141
|
// w:pageBreakBefore — start this paragraph on a new page.
|
|
47
142
|
pageBreakBefore: { default: false }
|
|
48
143
|
},
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
parseDOM: [
|
|
144
|
+
// HTML paste path: recover heading level from h1–h6 and alignment from
|
|
145
|
+
// inline style. Other attrs (list/indent/tabs/spacing) stay importer-only
|
|
146
|
+
// — pasted HTML rarely carries them faithfully.
|
|
147
|
+
parseDOM: [
|
|
148
|
+
{ tag: "p", getAttrs: (el) => pastedParagraphAttrs(el, null) },
|
|
149
|
+
...[1, 2, 3, 4, 5, 6].map((level) => ({
|
|
150
|
+
tag: `h${level}`,
|
|
151
|
+
getAttrs: (el) => pastedParagraphAttrs(el, level)
|
|
152
|
+
}))
|
|
153
|
+
],
|
|
53
154
|
toDOM(node) {
|
|
54
155
|
const attrs = node.attrs;
|
|
55
156
|
const style = paragraphStyle(attrs);
|
|
56
157
|
const tag = attrs.heading ? `h${attrs.heading}` : "p";
|
|
57
|
-
|
|
158
|
+
const dom = style ? { style } : {};
|
|
159
|
+
if (attrs.styleId) dom["data-style"] = attrs.styleId;
|
|
160
|
+
return [tag, dom, 0];
|
|
58
161
|
}
|
|
59
162
|
},
|
|
60
163
|
text: { group: "inline" },
|
|
@@ -94,9 +197,13 @@ var schema = new Schema({
|
|
|
94
197
|
// Paint-only: the layout box stays axis-aligned.
|
|
95
198
|
rotation: { default: 0 }
|
|
96
199
|
},
|
|
200
|
+
parseDOM: [{ tag: "img[src]", getAttrs: pastedImageAttrs }],
|
|
97
201
|
toDOM(node) {
|
|
98
202
|
const a = node.attrs;
|
|
99
|
-
const attrs = {
|
|
203
|
+
const attrs = {
|
|
204
|
+
src: a["src"],
|
|
205
|
+
alt: a["alt"]
|
|
206
|
+
};
|
|
100
207
|
if (a["width"] != null) attrs["width"] = String(a["width"]);
|
|
101
208
|
if (a["height"] != null) attrs["height"] = String(a["height"]);
|
|
102
209
|
return ["img", attrs];
|
|
@@ -133,7 +240,11 @@ var schema = new Schema({
|
|
|
133
240
|
align: { default: null }
|
|
134
241
|
},
|
|
135
242
|
parseDOM: [{ tag: "table" }],
|
|
136
|
-
toDOM: (node) => [
|
|
243
|
+
toDOM: (node) => [
|
|
244
|
+
"table",
|
|
245
|
+
node.attrs["borders"] ? { "data-borders": "1" } : {},
|
|
246
|
+
["tbody", 0]
|
|
247
|
+
]
|
|
137
248
|
},
|
|
138
249
|
table_row: {
|
|
139
250
|
content: "table_cell+",
|
|
@@ -169,9 +280,12 @@ var schema = new Schema({
|
|
|
169
280
|
parseDOM: [{ tag: "td" }, { tag: "th" }],
|
|
170
281
|
toDOM(node) {
|
|
171
282
|
const attrs = {};
|
|
172
|
-
if (node.attrs["colspan"] !== 1)
|
|
173
|
-
|
|
174
|
-
if (node.attrs["
|
|
283
|
+
if (node.attrs["colspan"] !== 1)
|
|
284
|
+
attrs["colspan"] = String(node.attrs["colspan"]);
|
|
285
|
+
if (node.attrs["rowspan"] !== 1)
|
|
286
|
+
attrs["rowspan"] = String(node.attrs["rowspan"]);
|
|
287
|
+
if (node.attrs["background"])
|
|
288
|
+
attrs["style"] = `background-color: ${node.attrs["background"]}`;
|
|
175
289
|
return ["td", attrs, 0];
|
|
176
290
|
}
|
|
177
291
|
}
|
|
@@ -200,9 +314,15 @@ var schema = new Schema({
|
|
|
200
314
|
// w:color — hex "#RRGGBB"
|
|
201
315
|
textColor: {
|
|
202
316
|
attrs: { color: {} },
|
|
203
|
-
parseDOM: [
|
|
317
|
+
parseDOM: [
|
|
318
|
+
{ style: "color", getAttrs: (value) => ({ color: value }) }
|
|
319
|
+
],
|
|
204
320
|
toDOM(mark) {
|
|
205
|
-
return [
|
|
321
|
+
return [
|
|
322
|
+
"span",
|
|
323
|
+
{ style: `color: ${mark.attrs["color"]}` },
|
|
324
|
+
0
|
|
325
|
+
];
|
|
206
326
|
}
|
|
207
327
|
},
|
|
208
328
|
// w:sz — size in points
|
|
@@ -218,7 +338,11 @@ var schema = new Schema({
|
|
|
218
338
|
}
|
|
219
339
|
],
|
|
220
340
|
toDOM(mark) {
|
|
221
|
-
return [
|
|
341
|
+
return [
|
|
342
|
+
"span",
|
|
343
|
+
{ style: `font-size: ${mark.attrs["size"]}pt` },
|
|
344
|
+
0
|
|
345
|
+
];
|
|
222
346
|
}
|
|
223
347
|
},
|
|
224
348
|
// w:vertAlign — superscript / subscript
|
|
@@ -235,26 +359,51 @@ var schema = new Schema({
|
|
|
235
359
|
highlight: {
|
|
236
360
|
attrs: { color: {} },
|
|
237
361
|
parseDOM: [
|
|
238
|
-
{
|
|
362
|
+
{
|
|
363
|
+
style: "background-color",
|
|
364
|
+
getAttrs: (value) => ({ color: value })
|
|
365
|
+
}
|
|
239
366
|
],
|
|
240
367
|
toDOM(mark) {
|
|
241
|
-
return [
|
|
368
|
+
return [
|
|
369
|
+
"span",
|
|
370
|
+
{ style: `background-color: ${mark.attrs["color"]}` },
|
|
371
|
+
0
|
|
372
|
+
];
|
|
242
373
|
}
|
|
243
374
|
},
|
|
244
375
|
// w:rFonts — font family
|
|
245
376
|
fontFamily: {
|
|
246
377
|
attrs: { family: {} },
|
|
247
|
-
parseDOM: [
|
|
378
|
+
parseDOM: [
|
|
379
|
+
{
|
|
380
|
+
style: "font-family",
|
|
381
|
+
getAttrs: (value) => ({ family: value })
|
|
382
|
+
}
|
|
383
|
+
],
|
|
248
384
|
toDOM(mark) {
|
|
249
|
-
return [
|
|
385
|
+
return [
|
|
386
|
+
"span",
|
|
387
|
+
{ style: `font-family: ${mark.attrs["family"]}` },
|
|
388
|
+
0
|
|
389
|
+
];
|
|
250
390
|
}
|
|
251
391
|
},
|
|
252
392
|
// w:hyperlink — external URL or "#anchor"
|
|
253
393
|
link: {
|
|
254
394
|
attrs: { href: {} },
|
|
255
395
|
inclusive: false,
|
|
396
|
+
parseDOM: [{ tag: "a[href]", getAttrs: pastedLinkAttrs }],
|
|
256
397
|
toDOM(mark) {
|
|
257
|
-
return [
|
|
398
|
+
return [
|
|
399
|
+
"a",
|
|
400
|
+
{
|
|
401
|
+
href: mark.attrs["href"],
|
|
402
|
+
rel: "noopener",
|
|
403
|
+
target: "_blank"
|
|
404
|
+
},
|
|
405
|
+
0
|
|
406
|
+
];
|
|
258
407
|
}
|
|
259
408
|
},
|
|
260
409
|
// w:footnoteReference — the carrier text is the superscript number; `num`
|
|
@@ -268,12 +417,20 @@ var schema = new Schema({
|
|
|
268
417
|
// `el` is an HTMLElement at runtime; this package has no DOM lib, so
|
|
269
418
|
// narrow structurally rather than naming the type.
|
|
270
419
|
getAttrs: (el) => ({
|
|
271
|
-
num: Number(
|
|
420
|
+
num: Number(
|
|
421
|
+
el.getAttribute(
|
|
422
|
+
"data-footnote"
|
|
423
|
+
)
|
|
424
|
+
) || 0
|
|
272
425
|
})
|
|
273
426
|
}
|
|
274
427
|
],
|
|
275
428
|
toDOM(mark) {
|
|
276
|
-
return [
|
|
429
|
+
return [
|
|
430
|
+
"sup",
|
|
431
|
+
{ "data-footnote": String(mark.attrs["num"]) },
|
|
432
|
+
0
|
|
433
|
+
];
|
|
277
434
|
}
|
|
278
435
|
}
|
|
279
436
|
// The `comment` mark (w:commentRangeStart/End) is contributed by the comment
|
|
@@ -286,7 +443,12 @@ var schema = new Schema({
|
|
|
286
443
|
var commentSchema = new Schema({
|
|
287
444
|
nodes: {
|
|
288
445
|
doc: { content: "block+" },
|
|
289
|
-
paragraph: {
|
|
446
|
+
paragraph: {
|
|
447
|
+
group: "block",
|
|
448
|
+
content: "inline*",
|
|
449
|
+
parseDOM: [{ tag: "p" }],
|
|
450
|
+
toDOM: () => ["p", 0]
|
|
451
|
+
},
|
|
290
452
|
text: { group: "inline" },
|
|
291
453
|
// @mention: an inline atom carrying the mentioned user's id + display name.
|
|
292
454
|
// `leafText` lets textContent include "@Name" (search / plain-text preview).
|
|
@@ -307,7 +469,10 @@ var commentSchema = new Schema({
|
|
|
307
469
|
tag: "span.mention",
|
|
308
470
|
getAttrs: (el) => {
|
|
309
471
|
const e = el;
|
|
310
|
-
return {
|
|
472
|
+
return {
|
|
473
|
+
id: e.getAttribute("data-id"),
|
|
474
|
+
label: (e.textContent ?? "").replace(/^@/, "")
|
|
475
|
+
};
|
|
311
476
|
}
|
|
312
477
|
}
|
|
313
478
|
]
|
|
@@ -329,7 +494,8 @@ function paragraphStyle(attrs) {
|
|
|
329
494
|
if (sp) {
|
|
330
495
|
if (sp.before) parts.push(`margin-top: ${sp.before}px`);
|
|
331
496
|
if (sp.after) parts.push(`margin-bottom: ${sp.after}px`);
|
|
332
|
-
if (sp.line && sp.lineRule === "auto")
|
|
497
|
+
if (sp.line && sp.lineRule === "auto")
|
|
498
|
+
parts.push(`line-height: ${sp.line}`);
|
|
333
499
|
else if (sp.line) parts.push(`line-height: ${sp.line}px`);
|
|
334
500
|
}
|
|
335
501
|
return parts.join("; ");
|
|
@@ -745,11 +911,16 @@ function propsToMarks(p, ctx) {
|
|
|
745
911
|
if (p.italic) marks.push(ctx.schema.marks["em"].create());
|
|
746
912
|
if (p.underline) marks.push(ctx.schema.marks["underline"].create());
|
|
747
913
|
if (p.strike) marks.push(ctx.schema.marks["strike"].create());
|
|
748
|
-
if (p.color)
|
|
749
|
-
|
|
750
|
-
if (p.
|
|
751
|
-
|
|
752
|
-
if (p.
|
|
914
|
+
if (p.color)
|
|
915
|
+
marks.push(ctx.schema.marks["textColor"].create({ color: p.color }));
|
|
916
|
+
if (p.sizePt !== void 0)
|
|
917
|
+
marks.push(ctx.schema.marks["fontSize"].create({ size: p.sizePt }));
|
|
918
|
+
if (p.fontFamily)
|
|
919
|
+
marks.push(ctx.schema.marks["fontFamily"].create({ family: p.fontFamily }));
|
|
920
|
+
if (p.highlight)
|
|
921
|
+
marks.push(ctx.schema.marks["highlight"].create({ color: p.highlight }));
|
|
922
|
+
if (p.vertAlign)
|
|
923
|
+
marks.push(ctx.schema.marks["vertAlign"].create({ value: p.vertAlign }));
|
|
753
924
|
return marks;
|
|
754
925
|
}
|
|
755
926
|
var SYMBOL_MAP = {
|
|
@@ -769,7 +940,9 @@ function symbolChar(code) {
|
|
|
769
940
|
return Number.isNaN(n) ? "" : String.fromCodePoint(n >= 61440 ? n - 61440 + 32 : n);
|
|
770
941
|
}
|
|
771
942
|
function hasPageBreak(run) {
|
|
772
|
-
return run.children.some(
|
|
943
|
+
return run.children.some(
|
|
944
|
+
(n) => n.name === "w:br" && attrOf(n, "w:type") === "page"
|
|
945
|
+
);
|
|
773
946
|
}
|
|
774
947
|
function effectiveChildren(nodes) {
|
|
775
948
|
const out = [];
|
|
@@ -818,8 +991,12 @@ function runInlineNodes(run, marks, ctx) {
|
|
|
818
991
|
if (id && ctx.notes.bodies[kind].has(id)) {
|
|
819
992
|
flush();
|
|
820
993
|
const num = ctx.notes.ref(kind, id);
|
|
821
|
-
const refMarks = [
|
|
822
|
-
|
|
994
|
+
const refMarks = [
|
|
995
|
+
...marks,
|
|
996
|
+
ctx.schema.marks["vertAlign"].create({ value: "super" })
|
|
997
|
+
];
|
|
998
|
+
if (kind === "footnote")
|
|
999
|
+
refMarks.push(ctx.schema.marks["footnote"].create({ num }));
|
|
823
1000
|
out.push(ctx.schema.text(String(num), refMarks));
|
|
824
1001
|
}
|
|
825
1002
|
} else if (node.name === "w:br" && attrOf(node, "w:type") !== "page") {
|
|
@@ -843,9 +1020,11 @@ function parseAnchorFloat(drawing) {
|
|
|
843
1020
|
const posH = child(anchor, "wp:positionH");
|
|
844
1021
|
if (posH) {
|
|
845
1022
|
const align = child(posH, "wp:align")?.text.trim();
|
|
846
|
-
if (align === "left" || align === "right" || align === "center")
|
|
1023
|
+
if (align === "left" || align === "right" || align === "center")
|
|
1024
|
+
float["hAlign"] = align;
|
|
847
1025
|
const off = emuToPxZero(child(posH, "wp:posOffset")?.text);
|
|
848
|
-
if (off !== void 0 && float["hAlign"] === void 0)
|
|
1026
|
+
if (off !== void 0 && float["hAlign"] === void 0)
|
|
1027
|
+
float["hOffset"] = off;
|
|
849
1028
|
const rel = attrOf(posH, "relativeFrom");
|
|
850
1029
|
float["hRel"] = rel === "page" ? "page" : "margin";
|
|
851
1030
|
}
|
|
@@ -1022,7 +1201,9 @@ function parseShape(run, ctx) {
|
|
|
1022
1201
|
function parseTextbox(wsp, ctx) {
|
|
1023
1202
|
const content = child(child(wsp, "wps:txbx"), "w:txbxContent");
|
|
1024
1203
|
if (!content) return null;
|
|
1025
|
-
const paragraphs = children(content, "w:p").map(
|
|
1204
|
+
const paragraphs = children(content, "w:p").map(
|
|
1205
|
+
(p) => parseParagraph(p, ctx).toJSON()
|
|
1206
|
+
);
|
|
1026
1207
|
if (paragraphs.length === 0) return null;
|
|
1027
1208
|
const bodyPr = child(wsp, "wps:bodyPr");
|
|
1028
1209
|
const ins = (name) => emuToPxZero(attrOf(bodyPr, name));
|
|
@@ -1117,7 +1298,10 @@ function flattenOmml(node) {
|
|
|
1117
1298
|
function parseParagraph(p, ctx) {
|
|
1118
1299
|
const pPr = child(p, "w:pPr");
|
|
1119
1300
|
const pStyleId = attrOf(child(pPr, "w:pStyle"), "w:val");
|
|
1120
|
-
const paraBase = mergeRunProps(
|
|
1301
|
+
const paraBase = mergeRunProps(
|
|
1302
|
+
ctx.styles.docDefaults,
|
|
1303
|
+
ctx.styles.resolveStyle(pStyleId)
|
|
1304
|
+
);
|
|
1121
1305
|
const pPrChain = [
|
|
1122
1306
|
ctx.styles.docDefaultsPPr,
|
|
1123
1307
|
...ctx.styles.resolveStylePPr(pStyleId),
|
|
@@ -1150,9 +1334,12 @@ function parseParagraph(p, ctx) {
|
|
|
1150
1334
|
} else if (fldType === "end") {
|
|
1151
1335
|
const kind = fieldKind(field.instr);
|
|
1152
1336
|
if (kind) {
|
|
1153
|
-
inline.push(
|
|
1337
|
+
inline.push(
|
|
1338
|
+
pageFieldNode(kind, field.resultRuns[0], paraBase, ctx)
|
|
1339
|
+
);
|
|
1154
1340
|
} else {
|
|
1155
|
-
for (const r of field.resultRuns)
|
|
1341
|
+
for (const r of field.resultRuns)
|
|
1342
|
+
inline.push(...runToInline(r, paraBase, ctx, null));
|
|
1156
1343
|
}
|
|
1157
1344
|
field = null;
|
|
1158
1345
|
} else if (field.phase === "instr") {
|
|
@@ -1169,7 +1356,8 @@ function parseParagraph(p, ctx) {
|
|
|
1169
1356
|
if (kind) {
|
|
1170
1357
|
inline.push(pageFieldNode(kind, resultRuns[0], paraBase, ctx));
|
|
1171
1358
|
} else {
|
|
1172
|
-
for (const r of resultRuns)
|
|
1359
|
+
for (const r of resultRuns)
|
|
1360
|
+
inline.push(...runToInline(r, paraBase, ctx, null));
|
|
1173
1361
|
}
|
|
1174
1362
|
} else if (node.name === "w:hyperlink") {
|
|
1175
1363
|
const rel = attrOf(node, "r:id") ? ctx.rels.get(attrOf(node, "r:id")) : void 0;
|
|
@@ -1182,7 +1370,9 @@ function parseParagraph(p, ctx) {
|
|
|
1182
1370
|
const text = flattenOmml(node);
|
|
1183
1371
|
if (text.length > 0) {
|
|
1184
1372
|
const first = findDescendant(node, "m:r");
|
|
1185
|
-
inline.push(
|
|
1373
|
+
inline.push(
|
|
1374
|
+
ctx.schema.text(text, runMarks(first, paraBase, ctx, null))
|
|
1375
|
+
);
|
|
1186
1376
|
}
|
|
1187
1377
|
} else if (node.name === "w:commentRangeStart") {
|
|
1188
1378
|
const id = Number(attrOf(node, "w:id"));
|
|
@@ -1199,6 +1389,9 @@ function parseParagraph(p, ctx) {
|
|
|
1199
1389
|
if (list) attrs.list = list;
|
|
1200
1390
|
if (align) attrs.align = align;
|
|
1201
1391
|
if (heading) attrs.heading = heading;
|
|
1392
|
+
else if (pStyleId && /^(title|subtitle)$/i.test(pStyleId)) {
|
|
1393
|
+
attrs.styleId = pStyleId.toLowerCase() === "title" ? "Title" : "Subtitle";
|
|
1394
|
+
}
|
|
1202
1395
|
if (indent) attrs.indent = indent;
|
|
1203
1396
|
if (spacing) attrs.spacing = spacing;
|
|
1204
1397
|
if (tabs) attrs.tabs = tabs;
|
|
@@ -1311,7 +1504,9 @@ function parseList(pPr) {
|
|
|
1311
1504
|
return { numId, level: Number.isNaN(ilvl) ? 0 : ilvl };
|
|
1312
1505
|
}
|
|
1313
1506
|
function emptyCell(ctx) {
|
|
1314
|
-
return ctx.schema.nodes["table_cell"].create(null, [
|
|
1507
|
+
return ctx.schema.nodes["table_cell"].create(null, [
|
|
1508
|
+
ctx.schema.nodes["paragraph"].create()
|
|
1509
|
+
]);
|
|
1315
1510
|
}
|
|
1316
1511
|
function parseCellMargins(tbl) {
|
|
1317
1512
|
const mar = child(child(tbl, "w:tblPr"), "w:tblCellMar");
|
|
@@ -1364,7 +1559,14 @@ function parseBordersEl(bordersEl, sides) {
|
|
|
1364
1559
|
}
|
|
1365
1560
|
return Object.keys(out).length > 0 ? out : null;
|
|
1366
1561
|
}
|
|
1367
|
-
var TABLE_SIDES = [
|
|
1562
|
+
var TABLE_SIDES = [
|
|
1563
|
+
"top",
|
|
1564
|
+
"bottom",
|
|
1565
|
+
"left",
|
|
1566
|
+
"right",
|
|
1567
|
+
"insideH",
|
|
1568
|
+
"insideV"
|
|
1569
|
+
];
|
|
1368
1570
|
var CELL_SIDES = ["top", "bottom", "left", "right"];
|
|
1369
1571
|
function parseTableBorders(tbl, ctx) {
|
|
1370
1572
|
const tblPr = child(tbl, "w:tblPr");
|
|
@@ -1391,8 +1593,18 @@ function parseTable(tbl, ctx) {
|
|
|
1391
1593
|
const vAlign = vAlignVal === "center" || vAlignVal === "bottom" ? vAlignVal : null;
|
|
1392
1594
|
const borders2 = parseBordersEl(child(tcPr, "w:tcBorders"), CELL_SIDES);
|
|
1393
1595
|
const content = parseBlocks(tc, ctx);
|
|
1394
|
-
if (content.length === 0)
|
|
1395
|
-
|
|
1596
|
+
if (content.length === 0)
|
|
1597
|
+
content.push(ctx.schema.nodes["paragraph"].create());
|
|
1598
|
+
cells.push({
|
|
1599
|
+
startCol: col,
|
|
1600
|
+
colspan,
|
|
1601
|
+
vMerge,
|
|
1602
|
+
colwidth: widths.length ? widths : null,
|
|
1603
|
+
background,
|
|
1604
|
+
vAlign,
|
|
1605
|
+
borders: borders2,
|
|
1606
|
+
content
|
|
1607
|
+
});
|
|
1396
1608
|
col += colspan;
|
|
1397
1609
|
}
|
|
1398
1610
|
return cells;
|
|
@@ -1403,12 +1615,17 @@ function parseTable(tbl, ctx) {
|
|
|
1403
1615
|
const header = hdr ? attrOf(hdr, "w:val") !== "false" && attrOf(hdr, "w:val") !== "0" : false;
|
|
1404
1616
|
const trH = child(trPr, "w:trHeight");
|
|
1405
1617
|
const hv = attrOf(trH, "w:val");
|
|
1406
|
-
const height = hv !== void 0 ? {
|
|
1618
|
+
const height = hv !== void 0 ? {
|
|
1619
|
+
value: twipsToPx(Number(hv)),
|
|
1620
|
+
exact: attrOf(trH, "w:hRule") === "exact"
|
|
1621
|
+
} : null;
|
|
1407
1622
|
const cs = child(trPr, "w:cantSplit");
|
|
1408
1623
|
const cantSplit = cs ? attrOf(cs, "w:val") !== "false" && attrOf(cs, "w:val") !== "0" : false;
|
|
1409
1624
|
return { header, height, cantSplit };
|
|
1410
1625
|
});
|
|
1411
|
-
const colIndex = logicalRows.map(
|
|
1626
|
+
const colIndex = logicalRows.map(
|
|
1627
|
+
(cells) => new Map(cells.map((c) => [c.startCol, c]))
|
|
1628
|
+
);
|
|
1412
1629
|
const rows = logicalRows.map((cells, r) => {
|
|
1413
1630
|
const emitted = [];
|
|
1414
1631
|
for (const cell of cells) {
|
|
@@ -1449,7 +1666,8 @@ function parseTable(tbl, ctx) {
|
|
|
1449
1666
|
const attrs = {};
|
|
1450
1667
|
if (cellPadding) attrs["cellPadding"] = cellPadding;
|
|
1451
1668
|
if (borders) attrs["borders"] = borders;
|
|
1452
|
-
if (jc === "center" || jc === "right" || jc === "end")
|
|
1669
|
+
if (jc === "center" || jc === "right" || jc === "end")
|
|
1670
|
+
attrs["align"] = jc === "end" ? "right" : jc;
|
|
1453
1671
|
return ctx.schema.nodes["table"].create(
|
|
1454
1672
|
Object.keys(attrs).length > 0 ? attrs : null,
|
|
1455
1673
|
rows.length > 0 ? rows : [ctx.schema.nodes["table_row"].create(null, [emptyCell(ctx)])]
|
|
@@ -1515,18 +1733,27 @@ async function readPartRels(zip, partPath) {
|
|
|
1515
1733
|
return xml ? parseXml(xml) : void 0;
|
|
1516
1734
|
}
|
|
1517
1735
|
async function buildNotesRegistry(zip) {
|
|
1518
|
-
const bodies = {
|
|
1736
|
+
const bodies = {
|
|
1737
|
+
footnote: /* @__PURE__ */ new Map(),
|
|
1738
|
+
endnote: /* @__PURE__ */ new Map()
|
|
1739
|
+
};
|
|
1519
1740
|
const load = async (path, root, tag, into) => {
|
|
1520
1741
|
const xml = await readPart(zip, path);
|
|
1521
1742
|
if (!xml) return;
|
|
1522
1743
|
for (const note of children(child(parseXml(xml), root), tag)) {
|
|
1523
1744
|
const id = attrOf(note, "w:id");
|
|
1524
1745
|
const type = attrOf(note, "w:type");
|
|
1525
|
-
if (id === void 0 || Number(id) < 1 || type && type !== "normal")
|
|
1746
|
+
if (id === void 0 || Number(id) < 1 || type && type !== "normal")
|
|
1747
|
+
continue;
|
|
1526
1748
|
into.set(id, note);
|
|
1527
1749
|
}
|
|
1528
1750
|
};
|
|
1529
|
-
await load(
|
|
1751
|
+
await load(
|
|
1752
|
+
"word/footnotes.xml",
|
|
1753
|
+
"w:footnotes",
|
|
1754
|
+
"w:footnote",
|
|
1755
|
+
bodies.footnote
|
|
1756
|
+
);
|
|
1530
1757
|
await load("word/endnotes.xml", "w:endnotes", "w:endnote", bodies.endnote);
|
|
1531
1758
|
const reg = {
|
|
1532
1759
|
bodies,
|
|
@@ -1561,7 +1788,10 @@ async function buildCommentsRegistry(zip) {
|
|
|
1561
1788
|
const ext = /* @__PURE__ */ new Map();
|
|
1562
1789
|
const extXml = await readPart(zip, "word/commentsExtended.xml");
|
|
1563
1790
|
if (extXml) {
|
|
1564
|
-
for (const ex of children(
|
|
1791
|
+
for (const ex of children(
|
|
1792
|
+
child(parseXml(extXml), "w15:commentsEx"),
|
|
1793
|
+
"w15:commentEx"
|
|
1794
|
+
)) {
|
|
1565
1795
|
const paraId2 = attrOf(ex, "w15:paraId");
|
|
1566
1796
|
if (!paraId2) continue;
|
|
1567
1797
|
const done = attrOf(ex, "w15:done");
|
|
@@ -1581,14 +1811,24 @@ function buildCommentsList(ctx) {
|
|
|
1581
1811
|
const out = [];
|
|
1582
1812
|
for (const id of ctx.comments.used) {
|
|
1583
1813
|
const def = ctx.comments.defs.get(id);
|
|
1584
|
-
if (def)
|
|
1814
|
+
if (def)
|
|
1815
|
+
out.push({
|
|
1816
|
+
id,
|
|
1817
|
+
author: def.author,
|
|
1818
|
+
date: def.date,
|
|
1819
|
+
text: collectText(def.body).trim()
|
|
1820
|
+
});
|
|
1585
1821
|
}
|
|
1586
1822
|
return out;
|
|
1587
1823
|
}
|
|
1588
1824
|
function commentBodyJSON(comment) {
|
|
1589
1825
|
const paras = children(comment, "w:p").map((p) => {
|
|
1590
1826
|
const text = collectText(p);
|
|
1591
|
-
return commentSchema.node(
|
|
1827
|
+
return commentSchema.node(
|
|
1828
|
+
"paragraph",
|
|
1829
|
+
null,
|
|
1830
|
+
text ? [commentSchema.text(text)] : []
|
|
1831
|
+
);
|
|
1592
1832
|
});
|
|
1593
1833
|
return commentSchema.node("doc", null, paras.length ? paras : [commentSchema.node("paragraph")]).toJSON();
|
|
1594
1834
|
}
|
|
@@ -1608,12 +1848,18 @@ function buildCommentNodes(ctx) {
|
|
|
1608
1848
|
if (usedSet.has(cur)) return true;
|
|
1609
1849
|
return false;
|
|
1610
1850
|
};
|
|
1611
|
-
const ids = [
|
|
1851
|
+
const ids = [
|
|
1852
|
+
...used.filter((id) => defs.has(id)),
|
|
1853
|
+
...[...defs.keys()].filter((id) => !usedSet.has(id))
|
|
1854
|
+
];
|
|
1612
1855
|
const out = [];
|
|
1613
1856
|
for (const id of ids) {
|
|
1614
1857
|
const def = defs.get(id);
|
|
1615
1858
|
if (!def || !referenced(id)) continue;
|
|
1616
|
-
const user = {
|
|
1859
|
+
const user = {
|
|
1860
|
+
id: def.author || "unknown",
|
|
1861
|
+
name: def.author || "Unknown"
|
|
1862
|
+
};
|
|
1617
1863
|
out.push({
|
|
1618
1864
|
id,
|
|
1619
1865
|
parentId: parentOf.get(id) ?? null,
|
|
@@ -1627,7 +1873,9 @@ function buildCommentNodes(ctx) {
|
|
|
1627
1873
|
}
|
|
1628
1874
|
function noteBlocks(note, num, ctx) {
|
|
1629
1875
|
const blocks = parseBlocks(note, ctx);
|
|
1630
|
-
const marker = ctx.schema.text(`${num}. `, [
|
|
1876
|
+
const marker = ctx.schema.text(`${num}. `, [
|
|
1877
|
+
ctx.schema.marks["vertAlign"].create({ value: "super" })
|
|
1878
|
+
]);
|
|
1631
1879
|
const first = blocks[0];
|
|
1632
1880
|
if (first && first.type.name === "paragraph") {
|
|
1633
1881
|
const kids = [marker];
|
|
@@ -1677,7 +1925,10 @@ async function extractMedia(zip) {
|
|
|
1677
1925
|
if (!path.startsWith("word/media/")) continue;
|
|
1678
1926
|
const entry = zip.file(path);
|
|
1679
1927
|
if (!entry || entry.dir) continue;
|
|
1680
|
-
media.set(
|
|
1928
|
+
media.set(
|
|
1929
|
+
path,
|
|
1930
|
+
`data:${mimeOf(path)};base64,${await entry.async("base64")}`
|
|
1931
|
+
);
|
|
1681
1932
|
}
|
|
1682
1933
|
return media;
|
|
1683
1934
|
}
|
|
@@ -1687,11 +1938,16 @@ async function importDocx(input, opts) {
|
|
|
1687
1938
|
if (rawDocumentXml === void 0) {
|
|
1688
1939
|
throw new Error("bapbong-docx: word/document.xml not found in archive");
|
|
1689
1940
|
}
|
|
1690
|
-
const
|
|
1941
|
+
const stylesXml2 = await readPart(zip, "word/styles.xml");
|
|
1691
1942
|
const numberingXml = await readPart(zip, "word/numbering.xml");
|
|
1692
1943
|
const themeXml = await readPart(zip, "word/theme/theme1.xml");
|
|
1693
|
-
const resolveTheme = buildThemeResolver(
|
|
1694
|
-
|
|
1944
|
+
const resolveTheme = buildThemeResolver(
|
|
1945
|
+
themeXml ? parseXml(themeXml) : void 0
|
|
1946
|
+
);
|
|
1947
|
+
const styles = buildStyleRegistry(
|
|
1948
|
+
stylesXml2 ? parseXml(stylesXml2) : void 0,
|
|
1949
|
+
resolveTheme
|
|
1950
|
+
);
|
|
1695
1951
|
const numberingRoot = numberingXml ? parseXml(numberingXml) : void 0;
|
|
1696
1952
|
const media = await extractMedia(zip);
|
|
1697
1953
|
const notes = await buildNotesRegistry(zip);
|
|
@@ -1740,7 +1996,11 @@ async function importDocx(input, opts) {
|
|
|
1740
1996
|
if (!xml) continue;
|
|
1741
1997
|
const partCtx = makeCtx(buildRels(await readPartRels(zip, partPath)));
|
|
1742
1998
|
const el = child(parseXml(xml), root);
|
|
1743
|
-
store[type] = storyDoc(
|
|
1999
|
+
store[type] = storyDoc(
|
|
2000
|
+
partCtx,
|
|
2001
|
+
el ? parseBlocks(el, partCtx) : [],
|
|
2002
|
+
ctx.numbering.defs
|
|
2003
|
+
);
|
|
1744
2004
|
}
|
|
1745
2005
|
};
|
|
1746
2006
|
await collect("w:headerReference", headers, "w:hdr");
|
|
@@ -1813,7 +2073,10 @@ var pxToEmu = (px) => Math.round(px * 9525);
|
|
|
1813
2073
|
var commentIdsOf = (node) => node.marks.find((m) => m.type.name === "comment")?.attrs["ids"] ?? [];
|
|
1814
2074
|
var isInlineLeaf = (node) => node.isText || node.type.name === "image" || node.type.name === "hard_break";
|
|
1815
2075
|
function esc(s) {
|
|
1816
|
-
return s.replace(
|
|
2076
|
+
return s.replace(
|
|
2077
|
+
/[&<>"]/g,
|
|
2078
|
+
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]
|
|
2079
|
+
);
|
|
1817
2080
|
}
|
|
1818
2081
|
var MIME_EXT = {
|
|
1819
2082
|
"image/png": "png",
|
|
@@ -1836,9 +2099,15 @@ function runProps(marks) {
|
|
|
1836
2099
|
const size = byName.get("fontSize")?.attrs["size"];
|
|
1837
2100
|
if (size != null) out.push(`<w:sz w:val="${Math.round(size * 2)}"/>`);
|
|
1838
2101
|
const hl = byName.get("highlight")?.attrs["color"];
|
|
1839
|
-
if (hl)
|
|
2102
|
+
if (hl)
|
|
2103
|
+
out.push(
|
|
2104
|
+
`<w:shd w:val="clear" w:color="auto" w:fill="${hl.replace(/^#/, "")}"/>`
|
|
2105
|
+
);
|
|
1840
2106
|
const va = byName.get("vertAlign")?.attrs["value"];
|
|
1841
|
-
if (va)
|
|
2107
|
+
if (va)
|
|
2108
|
+
out.push(
|
|
2109
|
+
`<w:vertAlign w:val="${va === "sub" ? "subscript" : "superscript"}"/>`
|
|
2110
|
+
);
|
|
1842
2111
|
return out.length ? `<w:rPr>${out.join("")}</w:rPr>` : "";
|
|
1843
2112
|
}
|
|
1844
2113
|
function anchorXml(float, cx, cy, n, graphic) {
|
|
@@ -1880,7 +2149,9 @@ function imageXml(node, ctx) {
|
|
|
1880
2149
|
const n = ctx.nextId++;
|
|
1881
2150
|
const rid = `rId${n}`;
|
|
1882
2151
|
ctx.media.push({ path: `word/media/image${n}.${ext}`, base64: m[2] });
|
|
1883
|
-
ctx.rels.push(
|
|
2152
|
+
ctx.rels.push(
|
|
2153
|
+
`<Relationship Id="${rid}" Type="${R_NS}/image" Target="media/image${n}.${ext}"/>`
|
|
2154
|
+
);
|
|
1884
2155
|
const cx = pxToEmu(node.attrs["width"] ?? 96);
|
|
1885
2156
|
const cy = pxToEmu(node.attrs["height"] ?? 96);
|
|
1886
2157
|
return `<w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:docPr id="${n}" name="Picture ${n}"/><a:graphic><a:graphicData uri="${PIC_NS}"><pic:pic><pic:nvPicPr><pic:cNvPr id="${n}" name="image${n}.${ext}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${rid}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm${rotAttr(node)}><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
|
|
@@ -1890,7 +2161,8 @@ function inlineXml(node, ctx) {
|
|
|
1890
2161
|
if (node.type.name === "image") return imageXml(node, ctx);
|
|
1891
2162
|
if (node.isText) {
|
|
1892
2163
|
const fn = node.marks.find((m) => m.type.name === "footnote");
|
|
1893
|
-
if (fn)
|
|
2164
|
+
if (fn)
|
|
2165
|
+
return `<w:r>${runProps(node.marks)}<w:footnoteReference w:id="${fn.attrs["num"]}"/></w:r>`;
|
|
1894
2166
|
return `<w:r>${runProps(node.marks)}<w:t xml:space="preserve">${esc(node.text ?? "")}</w:t></w:r>`;
|
|
1895
2167
|
}
|
|
1896
2168
|
return "";
|
|
@@ -1900,9 +2172,12 @@ function inlineUnit(node, ctx) {
|
|
|
1900
2172
|
const inner = inlineXml(node, ctx);
|
|
1901
2173
|
const href = linkHref(node);
|
|
1902
2174
|
if (!href || !inner) return inner;
|
|
1903
|
-
if (href.startsWith("#"))
|
|
2175
|
+
if (href.startsWith("#"))
|
|
2176
|
+
return `<w:hyperlink w:anchor="${esc(href.slice(1))}">${inner}</w:hyperlink>`;
|
|
1904
2177
|
const n = ctx.nextId++;
|
|
1905
|
-
ctx.rels.push(
|
|
2178
|
+
ctx.rels.push(
|
|
2179
|
+
`<Relationship Id="rId${n}" Type="${R_NS}/hyperlink" Target="${esc(href)}" TargetMode="External"/>`
|
|
2180
|
+
);
|
|
1906
2181
|
return `<w:hyperlink r:id="rId${n}">${inner}</w:hyperlink>`;
|
|
1907
2182
|
}
|
|
1908
2183
|
function inlineContent(node, ctx) {
|
|
@@ -1936,10 +2211,16 @@ function paraProps(node) {
|
|
|
1936
2211
|
const a = node.attrs;
|
|
1937
2212
|
const out = [];
|
|
1938
2213
|
const heading = a["heading"];
|
|
2214
|
+
const styleId = a["styleId"];
|
|
1939
2215
|
if (heading) out.push(`<w:pStyle w:val="Heading${heading}"/>`);
|
|
2216
|
+
else if (styleId === "Title" || styleId === "Subtitle")
|
|
2217
|
+
out.push(`<w:pStyle w:val="${styleId}"/>`);
|
|
1940
2218
|
if (a["pageBreakBefore"]) out.push("<w:pageBreakBefore/>");
|
|
1941
2219
|
const list = a["list"];
|
|
1942
|
-
if (list)
|
|
2220
|
+
if (list)
|
|
2221
|
+
out.push(
|
|
2222
|
+
`<w:numPr><w:ilvl w:val="${list.level}"/><w:numId w:val="${esc(list.numId)}"/></w:numPr>`
|
|
2223
|
+
);
|
|
1943
2224
|
const sp = a["spacing"];
|
|
1944
2225
|
if (sp) {
|
|
1945
2226
|
const at = [];
|
|
@@ -1947,7 +2228,9 @@ function paraProps(node) {
|
|
|
1947
2228
|
if (sp.after != null) at.push(`w:after="${pxToTwips(sp.after)}"`);
|
|
1948
2229
|
if (sp.line != null) {
|
|
1949
2230
|
const auto = sp.lineRule === "auto" || sp.lineRule == null;
|
|
1950
|
-
at.push(
|
|
2231
|
+
at.push(
|
|
2232
|
+
`w:line="${auto ? Math.round(sp.line * 240) : pxToTwips(sp.line)}"`
|
|
2233
|
+
);
|
|
1951
2234
|
at.push(`w:lineRule="${sp.lineRule ?? "auto"}"`);
|
|
1952
2235
|
}
|
|
1953
2236
|
if (at.length) out.push(`<w:spacing ${at.join(" ")}/>`);
|
|
@@ -1958,11 +2241,13 @@ function paraProps(node) {
|
|
|
1958
2241
|
if (ind.left != null) at.push(`w:left="${pxToTwips(ind.left)}"`);
|
|
1959
2242
|
if (ind.right != null) at.push(`w:right="${pxToTwips(ind.right)}"`);
|
|
1960
2243
|
if (ind.hanging != null) at.push(`w:hanging="${pxToTwips(ind.hanging)}"`);
|
|
1961
|
-
else if (ind.firstLine != null)
|
|
2244
|
+
else if (ind.firstLine != null)
|
|
2245
|
+
at.push(`w:firstLine="${pxToTwips(ind.firstLine)}"`);
|
|
1962
2246
|
if (at.length) out.push(`<w:ind ${at.join(" ")}/>`);
|
|
1963
2247
|
}
|
|
1964
2248
|
const align = a["align"];
|
|
1965
|
-
if (align)
|
|
2249
|
+
if (align)
|
|
2250
|
+
out.push(`<w:jc w:val="${align === "justify" ? "both" : align}"/>`);
|
|
1966
2251
|
return out.join("");
|
|
1967
2252
|
}
|
|
1968
2253
|
function paragraphXml(node, ctx, sectPr = "") {
|
|
@@ -1970,7 +2255,14 @@ function paragraphXml(node, ctx, sectPr = "") {
|
|
|
1970
2255
|
const pPr = props ? `<w:pPr>${props}</w:pPr>` : "";
|
|
1971
2256
|
return `<w:p>${pPr}${inlineContent(node, ctx)}</w:p>`;
|
|
1972
2257
|
}
|
|
1973
|
-
var TABLE_SIDES2 = [
|
|
2258
|
+
var TABLE_SIDES2 = [
|
|
2259
|
+
"top",
|
|
2260
|
+
"bottom",
|
|
2261
|
+
"left",
|
|
2262
|
+
"right",
|
|
2263
|
+
"insideH",
|
|
2264
|
+
"insideV"
|
|
2265
|
+
];
|
|
1974
2266
|
var CELL_SIDES2 = ["top", "bottom", "left", "right"];
|
|
1975
2267
|
var BORDER_STYLE_OUT = {
|
|
1976
2268
|
solid: "single",
|
|
@@ -1992,12 +2284,19 @@ function cellXml(cell, ctx) {
|
|
|
1992
2284
|
const a = cell.attrs;
|
|
1993
2285
|
const pr = [];
|
|
1994
2286
|
const colwidth = a["colwidth"];
|
|
1995
|
-
if (colwidth?.length)
|
|
1996
|
-
|
|
2287
|
+
if (colwidth?.length)
|
|
2288
|
+
pr.push(
|
|
2289
|
+
`<w:tcW w:w="${pxToTwips(colwidth.reduce((x, y) => x + y, 0))}" w:type="dxa"/>`
|
|
2290
|
+
);
|
|
2291
|
+
if (a["colspan"] > 1)
|
|
2292
|
+
pr.push(`<w:gridSpan w:val="${a["colspan"]}"/>`);
|
|
1997
2293
|
const borders = a["borders"];
|
|
1998
2294
|
if (borders) pr.push(bordersXml("w:tcBorders", borders, CELL_SIDES2));
|
|
1999
2295
|
const bg = a["background"];
|
|
2000
|
-
if (bg)
|
|
2296
|
+
if (bg)
|
|
2297
|
+
pr.push(
|
|
2298
|
+
`<w:shd w:val="clear" w:color="auto" w:fill="${bg.replace(/^#/, "")}"/>`
|
|
2299
|
+
);
|
|
2001
2300
|
const vAlign = a["vAlign"];
|
|
2002
2301
|
if (vAlign) pr.push(`<w:vAlign w:val="${vAlign}"/>`);
|
|
2003
2302
|
let content = "";
|
|
@@ -2010,7 +2309,10 @@ function rowXml(row, ctx) {
|
|
|
2010
2309
|
if (row.attrs["header"]) pr.push("<w:tblHeader/>");
|
|
2011
2310
|
if (row.attrs["cantSplit"]) pr.push("<w:cantSplit/>");
|
|
2012
2311
|
const h = row.attrs["height"];
|
|
2013
|
-
if (h)
|
|
2312
|
+
if (h)
|
|
2313
|
+
pr.push(
|
|
2314
|
+
`<w:trHeight w:val="${pxToTwips(h.value)}" w:hRule="${h.exact ? "exact" : "atLeast"}"/>`
|
|
2315
|
+
);
|
|
2014
2316
|
const trPr = pr.length ? `<w:trPr>${pr.join("")}</w:trPr>` : "";
|
|
2015
2317
|
let cells = "";
|
|
2016
2318
|
row.forEach((c) => cells += cellXml(c, ctx));
|
|
@@ -2029,7 +2331,9 @@ function tableXml(node, ctx) {
|
|
|
2029
2331
|
}
|
|
2030
2332
|
const firstRow = node.firstChild;
|
|
2031
2333
|
const grid = [];
|
|
2032
|
-
firstRow?.forEach(
|
|
2334
|
+
firstRow?.forEach(
|
|
2335
|
+
(c) => c.attrs["colwidth"]?.forEach((w) => grid.push(w))
|
|
2336
|
+
);
|
|
2033
2337
|
const gridXml = grid.length ? `<w:tblGrid>${grid.map((w) => `<w:gridCol w:w="${pxToTwips(w)}"/>`).join("")}</w:tblGrid>` : "";
|
|
2034
2338
|
let rows = "";
|
|
2035
2339
|
node.forEach((r) => rows += rowXml(r, ctx));
|
|
@@ -2064,10 +2368,14 @@ function commentBodyXml(body, firstParaId) {
|
|
|
2064
2368
|
doc.forEach((p, _o, i) => {
|
|
2065
2369
|
let runs = "";
|
|
2066
2370
|
p.forEach((inline) => {
|
|
2067
|
-
if (inline.isText)
|
|
2068
|
-
|
|
2371
|
+
if (inline.isText)
|
|
2372
|
+
runs += `<w:r><w:t xml:space="preserve">${esc(inline.text ?? "")}</w:t></w:r>`;
|
|
2373
|
+
else if (inline.type.name === "mention")
|
|
2374
|
+
runs += `<w:r><w:t xml:space="preserve">@${esc(String(inline.attrs["label"] ?? ""))}</w:t></w:r>`;
|
|
2069
2375
|
});
|
|
2070
|
-
ps.push(
|
|
2376
|
+
ps.push(
|
|
2377
|
+
`<w:p${i === 0 ? ` w14:paraId="${firstParaId}"` : ""}>${runs}</w:p>`
|
|
2378
|
+
);
|
|
2071
2379
|
});
|
|
2072
2380
|
return ps.join("") || `<w:p w14:paraId="${firstParaId}"/>`;
|
|
2073
2381
|
}
|
|
@@ -2089,16 +2397,65 @@ function commentsExtendedXml(comments) {
|
|
|
2089
2397
|
}
|
|
2090
2398
|
var ROOT_RELS = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2091
2399
|
<Relationships xmlns="${PR_NS}"><Relationship Id="rId1" Type="${R_NS}/officeDocument" Target="word/document.xml"/></Relationships>`;
|
|
2400
|
+
var STYLE_DEFS = (() => {
|
|
2401
|
+
const heading = (level, halfPt) => `<w:style w:type="paragraph" w:styleId="Heading${level}"><w:name w:val="heading ${level}"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="240" w:after="120"/><w:outlineLvl w:val="${level - 1}"/></w:pPr><w:rPr><w:b/><w:sz w:val="${halfPt}"/><w:szCs w:val="${halfPt}"/></w:rPr></w:style>`;
|
|
2402
|
+
return {
|
|
2403
|
+
Heading1: heading(1, 48),
|
|
2404
|
+
Heading2: heading(2, 36),
|
|
2405
|
+
Heading3: heading(3, 28),
|
|
2406
|
+
Heading4: heading(4, 24),
|
|
2407
|
+
Heading5: heading(5, 22),
|
|
2408
|
+
Heading6: heading(6, 22),
|
|
2409
|
+
Title: `<w:style w:type="paragraph" w:styleId="Title"><w:name w:val="Title"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:spacing w:after="80"/></w:pPr><w:rPr><w:sz w:val="56"/><w:szCs w:val="56"/></w:rPr></w:style>`,
|
|
2410
|
+
Subtitle: `<w:style w:type="paragraph" w:styleId="Subtitle"><w:name w:val="Subtitle"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:rPr><w:i/><w:sz w:val="28"/><w:szCs w:val="28"/></w:rPr></w:style>`
|
|
2411
|
+
};
|
|
2412
|
+
})();
|
|
2413
|
+
function usedStyleIds(doc) {
|
|
2414
|
+
const used = /* @__PURE__ */ new Set();
|
|
2415
|
+
doc.descendants((n) => {
|
|
2416
|
+
if (n.type.name !== "paragraph") return;
|
|
2417
|
+
const heading = n.attrs["heading"];
|
|
2418
|
+
const styleId = n.attrs["styleId"];
|
|
2419
|
+
if (heading) used.add(`Heading${heading}`);
|
|
2420
|
+
else if (styleId && STYLE_DEFS[styleId]) used.add(styleId);
|
|
2421
|
+
});
|
|
2422
|
+
return used;
|
|
2423
|
+
}
|
|
2424
|
+
function stylesXml(used) {
|
|
2425
|
+
const defs = [...used].map((id) => STYLE_DEFS[id]).join("");
|
|
2426
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2427
|
+
<w:styles xmlns:w="${W_NS}"><w:docDefaults><w:rPrDefault><w:rPr><w:sz w:val="22"/><w:szCs w:val="22"/></w:rPr></w:rPrDefault><w:pPrDefault/></w:docDefaults><w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/></w:style>` + defs + `</w:styles>`;
|
|
2428
|
+
}
|
|
2429
|
+
function mergeStyles(xml, used) {
|
|
2430
|
+
const missing = [...used].filter((id) => !xml.includes(`w:styleId="${id}"`));
|
|
2431
|
+
if (!missing.length) return xml;
|
|
2432
|
+
return xml.replace(
|
|
2433
|
+
"</w:styles>",
|
|
2434
|
+
`${missing.map((id) => STYLE_DEFS[id]).join("")}</w:styles>`
|
|
2435
|
+
);
|
|
2436
|
+
}
|
|
2092
2437
|
function contentTypes(exts, hasComments) {
|
|
2093
2438
|
const parts = [
|
|
2094
2439
|
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
|
|
2095
2440
|
'<Default Extension="xml" ContentType="application/xml"/>'
|
|
2096
2441
|
];
|
|
2097
|
-
for (const ext of exts)
|
|
2098
|
-
|
|
2442
|
+
for (const ext of exts)
|
|
2443
|
+
parts.push(
|
|
2444
|
+
`<Default Extension="${ext}" ContentType="image/${ext === "jpg" ? "jpeg" : ext}"/>`
|
|
2445
|
+
);
|
|
2446
|
+
parts.push(
|
|
2447
|
+
'<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
|
|
2448
|
+
);
|
|
2449
|
+
parts.push(
|
|
2450
|
+
'<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>'
|
|
2451
|
+
);
|
|
2099
2452
|
if (hasComments) {
|
|
2100
|
-
parts.push(
|
|
2101
|
-
|
|
2453
|
+
parts.push(
|
|
2454
|
+
'<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>'
|
|
2455
|
+
);
|
|
2456
|
+
parts.push(
|
|
2457
|
+
'<Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"/>'
|
|
2458
|
+
);
|
|
2102
2459
|
}
|
|
2103
2460
|
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2104
2461
|
<Types xmlns="${CT_NS}">${parts.join("")}</Types>`;
|
|
@@ -2106,17 +2463,30 @@ function contentTypes(exts, hasComments) {
|
|
|
2106
2463
|
function mergeContentTypes(xml, exts, hasComments) {
|
|
2107
2464
|
let out = xml;
|
|
2108
2465
|
const add = (frag, key) => {
|
|
2109
|
-
if (!out.includes(`"${key}"`))
|
|
2466
|
+
if (!out.includes(`"${key}"`))
|
|
2467
|
+
out = out.replace("</Types>", `${frag}</Types>`);
|
|
2110
2468
|
};
|
|
2111
|
-
for (const ext of exts)
|
|
2469
|
+
for (const ext of exts)
|
|
2470
|
+
add(
|
|
2471
|
+
`<Default Extension="${ext}" ContentType="image/${ext === "jpg" ? "jpeg" : ext}"/>`,
|
|
2472
|
+
ext
|
|
2473
|
+
);
|
|
2112
2474
|
if (hasComments) {
|
|
2113
|
-
add(
|
|
2114
|
-
|
|
2475
|
+
add(
|
|
2476
|
+
'<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>',
|
|
2477
|
+
"/word/comments.xml"
|
|
2478
|
+
);
|
|
2479
|
+
add(
|
|
2480
|
+
'<Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"/>',
|
|
2481
|
+
"/word/commentsExtended.xml"
|
|
2482
|
+
);
|
|
2115
2483
|
}
|
|
2116
2484
|
return out;
|
|
2117
2485
|
}
|
|
2118
2486
|
function extractBodySectPr(xml) {
|
|
2119
|
-
const all = xml.match(
|
|
2487
|
+
const all = xml.match(
|
|
2488
|
+
/<w:sectPr\b[^>]*>[\s\S]*?<\/w:sectPr>|<w:sectPr\b[^>]*\/>/g
|
|
2489
|
+
);
|
|
2120
2490
|
return all ? all[all.length - 1] : "";
|
|
2121
2491
|
}
|
|
2122
2492
|
function mergeRels(xml, newRels) {
|
|
@@ -2125,7 +2495,10 @@ function mergeRels(xml, newRels) {
|
|
|
2125
2495
|
/<Relationship\b[^>]*Target="comments(?:Extended)?\.xml"[^>]*\/>/g,
|
|
2126
2496
|
""
|
|
2127
2497
|
);
|
|
2128
|
-
return base.replace(
|
|
2498
|
+
return base.replace(
|
|
2499
|
+
"</Relationships>",
|
|
2500
|
+
`${newRels.join("")}</Relationships>`
|
|
2501
|
+
);
|
|
2129
2502
|
}
|
|
2130
2503
|
async function exportDocx(doc, opts) {
|
|
2131
2504
|
const comments = doc.attrs["comments"] ?? [];
|
|
@@ -2134,7 +2507,8 @@ async function exportDocx(doc, opts) {
|
|
|
2134
2507
|
let idx = 0;
|
|
2135
2508
|
doc.descendants((n) => {
|
|
2136
2509
|
if (!isInlineLeaf(n)) return;
|
|
2137
|
-
for (const id of commentIdsOf(n))
|
|
2510
|
+
for (const id of commentIdsOf(n))
|
|
2511
|
+
if (knownComments.has(id)) lastRun.set(id, idx);
|
|
2138
2512
|
idx++;
|
|
2139
2513
|
});
|
|
2140
2514
|
const ctx = {
|
|
@@ -2149,13 +2523,20 @@ async function exportDocx(doc, opts) {
|
|
|
2149
2523
|
};
|
|
2150
2524
|
const boundaries = sectionBoundaries(doc);
|
|
2151
2525
|
let body = "";
|
|
2152
|
-
doc.forEach(
|
|
2526
|
+
doc.forEach(
|
|
2527
|
+
(block, _offset, i) => body += blockXml(block, ctx, boundaries.get(i))
|
|
2528
|
+
);
|
|
2153
2529
|
const hasComments = comments.length > 0;
|
|
2154
2530
|
if (hasComments) {
|
|
2155
|
-
ctx.rels.push(
|
|
2156
|
-
|
|
2531
|
+
ctx.rels.push(
|
|
2532
|
+
`<Relationship Id="rIdComments" Type="${R_NS}/comments" Target="comments.xml"/>`
|
|
2533
|
+
);
|
|
2534
|
+
ctx.rels.push(
|
|
2535
|
+
`<Relationship Id="rIdCommentsExt" Type="${R_NS}/commentsExtended" Target="commentsExtended.xml"/>`
|
|
2536
|
+
);
|
|
2157
2537
|
}
|
|
2158
2538
|
const zip = new JSZip2();
|
|
2539
|
+
const styleIds = usedStyleIds(doc);
|
|
2159
2540
|
let sectPr = "";
|
|
2160
2541
|
if (opts?.carry) {
|
|
2161
2542
|
const carry = opts.carry;
|
|
@@ -2163,18 +2544,29 @@ async function exportDocx(doc, opts) {
|
|
|
2163
2544
|
if (!f.dir) zip.file(path, await f.async("uint8array"));
|
|
2164
2545
|
}
|
|
2165
2546
|
const ct = await carry.file("[Content_Types].xml")?.async("string");
|
|
2166
|
-
zip.file(
|
|
2547
|
+
zip.file(
|
|
2548
|
+
"[Content_Types].xml",
|
|
2549
|
+
ct ? mergeContentTypes(ct, ctx.exts, hasComments) : contentTypes(ctx.exts, hasComments)
|
|
2550
|
+
);
|
|
2551
|
+
const carriedStyles = await carry.file("word/styles.xml")?.async("string");
|
|
2552
|
+
if (carriedStyles)
|
|
2553
|
+
zip.file("word/styles.xml", mergeStyles(carriedStyles, styleIds));
|
|
2167
2554
|
const rels = await carry.file("word/_rels/document.xml.rels")?.async("string");
|
|
2168
2555
|
zip.file("word/_rels/document.xml.rels", mergeRels(rels, ctx.rels));
|
|
2169
2556
|
const origDoc = await carry.file("word/document.xml")?.async("string");
|
|
2170
2557
|
if (origDoc) sectPr = extractBodySectPr(origDoc);
|
|
2171
2558
|
} else {
|
|
2559
|
+
ctx.rels.push(
|
|
2560
|
+
`<Relationship Id="rIdStyles" Type="${R_NS}/styles" Target="styles.xml"/>`
|
|
2561
|
+
);
|
|
2562
|
+
zip.file("word/styles.xml", stylesXml(styleIds));
|
|
2172
2563
|
zip.file("[Content_Types].xml", contentTypes(ctx.exts, hasComments));
|
|
2173
2564
|
zip.file("_rels/.rels", ROOT_RELS);
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2565
|
+
zip.file(
|
|
2566
|
+
"word/_rels/document.xml.rels",
|
|
2567
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2568
|
+
<Relationships xmlns="${PR_NS}">${ctx.rels.join("")}</Relationships>`
|
|
2569
|
+
);
|
|
2178
2570
|
}
|
|
2179
2571
|
const documentXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2180
2572
|
<w:document xmlns:w="${W_NS}" xmlns:r="${R_NS}" xmlns:wp="${WP_NS}" xmlns:a="${A_NS}" xmlns:pic="${PIC_NS}" xmlns:wps="${WPS_NS}"><w:body>${body}${sectPr}</w:body></w:document>`;
|
|
@@ -2183,14 +2575,27 @@ async function exportDocx(doc, opts) {
|
|
|
2183
2575
|
zip.file("word/comments.xml", commentsXml(comments));
|
|
2184
2576
|
zip.file("word/commentsExtended.xml", commentsExtendedXml(comments));
|
|
2185
2577
|
}
|
|
2186
|
-
for (const { path, base64 } of ctx.media)
|
|
2578
|
+
for (const { path, base64 } of ctx.media)
|
|
2579
|
+
zip.file(path, base64, { base64: true });
|
|
2187
2580
|
return zip.generateAsync({ type: "uint8array" });
|
|
2188
2581
|
}
|
|
2189
2582
|
|
|
2190
2583
|
// packages/layout-engine/dist/index.js
|
|
2191
|
-
var DEFAULT_FONT = {
|
|
2584
|
+
var DEFAULT_FONT = {
|
|
2585
|
+
family: "Arial",
|
|
2586
|
+
sizePt: 11,
|
|
2587
|
+
bold: false,
|
|
2588
|
+
italic: false
|
|
2589
|
+
};
|
|
2192
2590
|
var PT_TO_PX = 96 / 72;
|
|
2193
|
-
var HEADING_PT = {
|
|
2591
|
+
var HEADING_PT = {
|
|
2592
|
+
1: 24,
|
|
2593
|
+
2: 18,
|
|
2594
|
+
3: 14,
|
|
2595
|
+
4: 12,
|
|
2596
|
+
5: 11,
|
|
2597
|
+
6: 11
|
|
2598
|
+
};
|
|
2194
2599
|
var LINE_HEIGHT_FACTOR = 1.2;
|
|
2195
2600
|
var BASELINE_FACTOR = 0.8;
|
|
2196
2601
|
var DEFAULT_TAB_WIDTH = 48;
|
|
@@ -2266,11 +2671,13 @@ function resolveImage(node, pos) {
|
|
|
2266
2671
|
function paragraphToFlow(node, base, nodePos, allowFloats = false, marker) {
|
|
2267
2672
|
const contentStart = nodePos + 1;
|
|
2268
2673
|
const headingLevel2 = node.attrs["heading"];
|
|
2269
|
-
const
|
|
2674
|
+
const styleId = node.attrs["styleId"];
|
|
2675
|
+
const runBase = headingLevel2 ? { ...base, sizePt: HEADING_PT[headingLevel2] ?? base.sizePt, bold: true } : styleId === "Title" ? { ...base, sizePt: 28 } : styleId === "Subtitle" ? { ...base, sizePt: 14, italic: true } : base;
|
|
2270
2676
|
const runs = [];
|
|
2271
2677
|
const floats = [];
|
|
2272
2678
|
node.forEach((child2, offset) => {
|
|
2273
|
-
if (child2.isText)
|
|
2679
|
+
if (child2.isText)
|
|
2680
|
+
runs.push(resolveRun(child2, runBase, contentStart + offset));
|
|
2274
2681
|
else if (child2.type.name === "image") {
|
|
2275
2682
|
const float = child2.attrs["float"];
|
|
2276
2683
|
if (float && allowFloats) {
|
|
@@ -2297,7 +2704,8 @@ function paragraphToFlow(node, base, nodePos, allowFloats = false, marker) {
|
|
|
2297
2704
|
}
|
|
2298
2705
|
} else if (child2.type.name === "page_field")
|
|
2299
2706
|
runs.push(resolveField(child2, runBase, contentStart + offset));
|
|
2300
|
-
else if (child2.type.name === "hard_break")
|
|
2707
|
+
else if (child2.type.name === "hard_break")
|
|
2708
|
+
runs.push({ break: true, pos: contentStart + offset });
|
|
2301
2709
|
});
|
|
2302
2710
|
const list = node.attrs["list"];
|
|
2303
2711
|
const align = node.attrs["align"];
|
|
@@ -2333,8 +2741,15 @@ function markerFor(node, counter) {
|
|
|
2333
2741
|
}
|
|
2334
2742
|
function nodeToBlock(node, base, nodePos, allowFloats = false, counter) {
|
|
2335
2743
|
if (node.type.name === "paragraph")
|
|
2336
|
-
return paragraphToFlow(
|
|
2337
|
-
|
|
2744
|
+
return paragraphToFlow(
|
|
2745
|
+
node,
|
|
2746
|
+
base,
|
|
2747
|
+
nodePos,
|
|
2748
|
+
allowFloats,
|
|
2749
|
+
markerFor(node, counter)
|
|
2750
|
+
);
|
|
2751
|
+
if (node.type.name === "table")
|
|
2752
|
+
return tableToFlow(node, base, nodePos, counter);
|
|
2338
2753
|
return null;
|
|
2339
2754
|
}
|
|
2340
2755
|
function tableToFlow(node, base, nodePos, counter) {
|
|
@@ -2346,7 +2761,13 @@ function tableToFlow(node, base, nodePos, counter) {
|
|
|
2346
2761
|
const cellPos = rowPos + 1 + cellOffset;
|
|
2347
2762
|
const content = [];
|
|
2348
2763
|
cellNode.forEach((child2, childOffset) => {
|
|
2349
|
-
const block = nodeToBlock(
|
|
2764
|
+
const block = nodeToBlock(
|
|
2765
|
+
child2,
|
|
2766
|
+
base,
|
|
2767
|
+
cellPos + 1 + childOffset,
|
|
2768
|
+
true,
|
|
2769
|
+
counter
|
|
2770
|
+
);
|
|
2350
2771
|
if (block) content.push(block);
|
|
2351
2772
|
});
|
|
2352
2773
|
const a = cellNode.attrs;
|
|
@@ -2378,7 +2799,9 @@ function tableToFlow(node, base, nodePos, counter) {
|
|
|
2378
2799
|
}
|
|
2379
2800
|
function toFlowBlocks(doc, defaultFont = {}, allowFloats = true) {
|
|
2380
2801
|
const base = { ...DEFAULT_FONT, ...defaultFont };
|
|
2381
|
-
const counter = createNumberingCounter(
|
|
2802
|
+
const counter = createNumberingCounter(
|
|
2803
|
+
doc.attrs["numbering"]
|
|
2804
|
+
);
|
|
2382
2805
|
const blocks = [];
|
|
2383
2806
|
doc.forEach((node, offset) => {
|
|
2384
2807
|
const block = nodeToBlock(node, base, offset, allowFloats, counter);
|
|
@@ -2388,7 +2811,16 @@ function toFlowBlocks(doc, defaultFont = {}, allowFloats = true) {
|
|
|
2388
2811
|
}
|
|
2389
2812
|
function tokenizeInline(inline, ctx) {
|
|
2390
2813
|
if ("break" in inline) {
|
|
2391
|
-
return [
|
|
2814
|
+
return [
|
|
2815
|
+
{
|
|
2816
|
+
isBreak: true,
|
|
2817
|
+
font: ctx.base,
|
|
2818
|
+
width: 0,
|
|
2819
|
+
isSpace: false,
|
|
2820
|
+
pos: inline.pos,
|
|
2821
|
+
size: 1
|
|
2822
|
+
}
|
|
2823
|
+
];
|
|
2392
2824
|
}
|
|
2393
2825
|
if ("src" in inline) {
|
|
2394
2826
|
return [
|
|
@@ -2499,7 +2931,12 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2499
2931
|
return lineLeft + k * tabWidth - x;
|
|
2500
2932
|
};
|
|
2501
2933
|
const tabStops = block.tabs ? [...block.tabs].sort((a, b) => a.pos - b.pos) : [];
|
|
2502
|
-
const LEADER_CHARS = {
|
|
2934
|
+
const LEADER_CHARS = {
|
|
2935
|
+
dot: ".",
|
|
2936
|
+
hyphen: "-",
|
|
2937
|
+
underscore: "_",
|
|
2938
|
+
middleDot: "\xB7"
|
|
2939
|
+
};
|
|
2503
2940
|
const MIN_TAB = 2;
|
|
2504
2941
|
const tabGroup = (ti) => {
|
|
2505
2942
|
let width = 0;
|
|
@@ -2509,7 +2946,8 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2509
2946
|
if (t.isTab) break;
|
|
2510
2947
|
if (decimalPrefix === null && t.text != null && !t.field) {
|
|
2511
2948
|
const m = /[.,]/.exec(t.text);
|
|
2512
|
-
if (m)
|
|
2949
|
+
if (m)
|
|
2950
|
+
decimalPrefix = width + measure(t.text.slice(0, m.index), t.font);
|
|
2513
2951
|
}
|
|
2514
2952
|
width += t.width;
|
|
2515
2953
|
}
|
|
@@ -2632,7 +3070,16 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2632
3070
|
height = target;
|
|
2633
3071
|
}
|
|
2634
3072
|
const painted = firstLine && marker ? [marker, ...segments] : segments;
|
|
2635
|
-
emit({
|
|
3073
|
+
emit({
|
|
3074
|
+
x: startX,
|
|
3075
|
+
width: lineRight - startX,
|
|
3076
|
+
height,
|
|
3077
|
+
baseline,
|
|
3078
|
+
segments: painted,
|
|
3079
|
+
images,
|
|
3080
|
+
from,
|
|
3081
|
+
to
|
|
3082
|
+
});
|
|
2636
3083
|
prevTo = to;
|
|
2637
3084
|
lineTokens = [];
|
|
2638
3085
|
lineWidth = 0;
|
|
@@ -2666,10 +3113,14 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2666
3113
|
softWrapped = false;
|
|
2667
3114
|
continue;
|
|
2668
3115
|
}
|
|
2669
|
-
if (token.isSpace && !token.isTab && lineTokens.length === 0 && softWrapped)
|
|
3116
|
+
if (token.isSpace && !token.isTab && lineTokens.length === 0 && softWrapped)
|
|
3117
|
+
continue;
|
|
2670
3118
|
if (token.isTab) resolveTab(token, lineStart() + lineWidth, ti);
|
|
2671
3119
|
if (clusterable(token) && clusterFont && sameFont(clusterFont, token.font)) {
|
|
2672
|
-
token.width = Math.max(
|
|
3120
|
+
token.width = Math.max(
|
|
3121
|
+
0,
|
|
3122
|
+
measure(clusterText + token.text, token.font) - clusterWidth
|
|
3123
|
+
);
|
|
2673
3124
|
}
|
|
2674
3125
|
const cursor = lineStart() + lineWidth;
|
|
2675
3126
|
if (!token.isSpace && lineTokens.length > 0 && cursor + token.width > lineRight) {
|
|
@@ -2677,14 +3128,21 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2677
3128
|
resetCluster();
|
|
2678
3129
|
softWrapped = true;
|
|
2679
3130
|
if (token.isTab) resolveTab(token, lineStart() + lineWidth, ti);
|
|
2680
|
-
else if (clusterable(token))
|
|
3131
|
+
else if (clusterable(token))
|
|
3132
|
+
token.width = measure(token.text, token.font);
|
|
2681
3133
|
}
|
|
2682
3134
|
if (clusterable(token) && lineTokens.length === 0 && token.text.length > 1 && lineStart() + token.width > lineRight) {
|
|
2683
3135
|
const avail = lineRight - lineStart();
|
|
2684
3136
|
let n = 1;
|
|
2685
|
-
while (n < token.text.length && measure(token.text.slice(0, n + 1), token.font) <= avail)
|
|
3137
|
+
while (n < token.text.length && measure(token.text.slice(0, n + 1), token.font) <= avail)
|
|
3138
|
+
n++;
|
|
2686
3139
|
const restText = token.text.slice(n);
|
|
2687
|
-
const rest = {
|
|
3140
|
+
const rest = {
|
|
3141
|
+
...token,
|
|
3142
|
+
text: restText,
|
|
3143
|
+
width: measure(restText, token.font),
|
|
3144
|
+
size: restText.length
|
|
3145
|
+
};
|
|
2688
3146
|
if (token.pos != null) rest.pos = token.pos + n;
|
|
2689
3147
|
token.text = token.text.slice(0, n);
|
|
2690
3148
|
token.size = n;
|
|
@@ -2705,7 +3163,10 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2705
3163
|
const rotH = rotatedBoxHeight(token.image);
|
|
2706
3164
|
maxImagePx = Math.max(maxImagePx, rotH);
|
|
2707
3165
|
maxAscent = Math.max(maxAscent, token.image.height / 2 + rotH / 2);
|
|
2708
|
-
maxDescent = Math.max(
|
|
3166
|
+
maxDescent = Math.max(
|
|
3167
|
+
maxDescent,
|
|
3168
|
+
Math.max(0, (rotH - token.image.height) / 2)
|
|
3169
|
+
);
|
|
2709
3170
|
} else {
|
|
2710
3171
|
maxFontPx = Math.max(maxFontPx, sizePx(token.font));
|
|
2711
3172
|
if (metrics) {
|
|
@@ -2758,7 +3219,13 @@ function shiftTableX(table, dx) {
|
|
|
2758
3219
|
}
|
|
2759
3220
|
var TEXTBOX_INSET = { l: 10, t: 5, r: 10, b: 5 };
|
|
2760
3221
|
function resolveFloat(f, x, y, ctx) {
|
|
2761
|
-
const rf = {
|
|
3222
|
+
const rf = {
|
|
3223
|
+
x,
|
|
3224
|
+
y,
|
|
3225
|
+
width: f.width,
|
|
3226
|
+
height: f.height,
|
|
3227
|
+
src: f.src
|
|
3228
|
+
};
|
|
2762
3229
|
if (f.pos != null) rf.pos = f.pos;
|
|
2763
3230
|
if (f.rotation) rf.rotation = f.rotation;
|
|
2764
3231
|
if (f.shape) rf.shape = f.shape;
|
|
@@ -2804,7 +3271,8 @@ function eachCell(rows, ncols, visit) {
|
|
|
2804
3271
|
for (const cell of rows[r].cells) {
|
|
2805
3272
|
while (col < ncols && spanned[col] > 0) col++;
|
|
2806
3273
|
visit(r, cell, col);
|
|
2807
|
-
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
3274
|
+
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
3275
|
+
spanned[col + k] = cell.rowspan;
|
|
2808
3276
|
col += cell.colspan;
|
|
2809
3277
|
}
|
|
2810
3278
|
for (let i = 0; i < ncols; i++) if (spanned[i] > 0) spanned[i]--;
|
|
@@ -2814,14 +3282,18 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2814
3282
|
const avail = contentRight - contentLeft;
|
|
2815
3283
|
const nrows = table.rows.length;
|
|
2816
3284
|
const ncols = table.rows.reduce(
|
|
2817
|
-
(m, r) => Math.max(
|
|
3285
|
+
(m, r) => Math.max(
|
|
3286
|
+
m,
|
|
3287
|
+
r.cells.reduce((s, c) => s + c.colspan, 0)
|
|
3288
|
+
),
|
|
2818
3289
|
0
|
|
2819
3290
|
);
|
|
2820
3291
|
const colWidths = new Array(ncols).fill(0);
|
|
2821
3292
|
eachCell(table.rows, ncols, (_r, cell, startCol) => {
|
|
2822
3293
|
if (cell.colwidth && cell.colwidth.length === cell.colspan) {
|
|
2823
3294
|
for (let k = 0; k < cell.colspan && startCol + k < ncols; k++) {
|
|
2824
|
-
if (colWidths[startCol + k] === 0)
|
|
3295
|
+
if (colWidths[startCol + k] === 0)
|
|
3296
|
+
colWidths[startCol + k] = cell.colwidth[k];
|
|
2825
3297
|
}
|
|
2826
3298
|
}
|
|
2827
3299
|
});
|
|
@@ -2829,7 +3301,8 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2829
3301
|
const unknown = colWidths.filter((w) => w === 0).length;
|
|
2830
3302
|
if (unknown > 0) {
|
|
2831
3303
|
const share = Math.max(0, (avail - known) / unknown);
|
|
2832
|
-
for (let i = 0; i < ncols; i++)
|
|
3304
|
+
for (let i = 0; i < ncols; i++)
|
|
3305
|
+
if (colWidths[i] === 0) colWidths[i] = share;
|
|
2833
3306
|
}
|
|
2834
3307
|
const natural = colWidths.reduce((s, w) => s + w, 0);
|
|
2835
3308
|
if (natural > avail && natural > 0) {
|
|
@@ -2849,9 +3322,15 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2849
3322
|
const cellDrafts = [];
|
|
2850
3323
|
eachCell(table.rows, ncols, (r, cell, col) => {
|
|
2851
3324
|
let cellWidth = 0;
|
|
2852
|
-
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
3325
|
+
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
3326
|
+
cellWidth += colWidths[col + k];
|
|
2853
3327
|
const cellLeft = colX[col];
|
|
2854
|
-
const flow = layoutFlow(
|
|
3328
|
+
const flow = layoutFlow(
|
|
3329
|
+
cell.content,
|
|
3330
|
+
cellLeft + pad.left,
|
|
3331
|
+
cellLeft + cellWidth - pad.right,
|
|
3332
|
+
ctx
|
|
3333
|
+
);
|
|
2855
3334
|
cellDrafts.push({
|
|
2856
3335
|
startRow: r,
|
|
2857
3336
|
startCol: col,
|
|
@@ -2871,14 +3350,18 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2871
3350
|
const rowHeight = new Array(nrows).fill(0);
|
|
2872
3351
|
for (const c of cellDrafts) {
|
|
2873
3352
|
if (c.rowspan === 1) {
|
|
2874
|
-
rowHeight[c.startRow] = Math.max(
|
|
3353
|
+
rowHeight[c.startRow] = Math.max(
|
|
3354
|
+
rowHeight[c.startRow],
|
|
3355
|
+
c.contentHeight + pad.top + pad.bottom
|
|
3356
|
+
);
|
|
2875
3357
|
}
|
|
2876
3358
|
}
|
|
2877
3359
|
for (const c of cellDrafts) {
|
|
2878
3360
|
if (c.rowspan > 1) {
|
|
2879
3361
|
const need = c.contentHeight + pad.top + pad.bottom;
|
|
2880
3362
|
let span = 0;
|
|
2881
|
-
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
3363
|
+
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
3364
|
+
span += rowHeight[r];
|
|
2882
3365
|
if (need > span) {
|
|
2883
3366
|
const last = Math.min(c.startRow + c.rowspan - 1, nrows - 1);
|
|
2884
3367
|
rowHeight[last] += need - span;
|
|
@@ -2893,7 +3376,8 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2893
3376
|
for (let r = 0; r < nrows; r++) rowY[r + 1] = rowY[r] + rowHeight[r];
|
|
2894
3377
|
const cells = cellDrafts.map((c) => {
|
|
2895
3378
|
let height = 0;
|
|
2896
|
-
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
3379
|
+
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
3380
|
+
height += rowHeight[r];
|
|
2897
3381
|
const slack = Math.max(0, height - pad.top - pad.bottom - c.contentHeight);
|
|
2898
3382
|
const vOffset = c.vAlign === "bottom" ? slack : c.vAlign === "center" ? slack / 2 : 0;
|
|
2899
3383
|
const dy = rowY[c.startRow] + pad.top + vOffset;
|
|
@@ -2911,19 +3395,30 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2911
3395
|
if (c.background) cell.background = c.background;
|
|
2912
3396
|
if (c.borders) cell.borders = c.borders;
|
|
2913
3397
|
if (c.tables.length > 0) cell.tables = c.tables;
|
|
2914
|
-
if (c.floats.length > 0)
|
|
3398
|
+
if (c.floats.length > 0)
|
|
3399
|
+
cell.floats = c.floats.map((f) => ({ ...f, y: f.y + dy }));
|
|
2915
3400
|
return cell;
|
|
2916
3401
|
});
|
|
2917
|
-
const resolved = {
|
|
3402
|
+
const resolved = {
|
|
3403
|
+
x: contentLeft + xShift,
|
|
3404
|
+
y: 0,
|
|
3405
|
+
width: tableWidth,
|
|
3406
|
+
height: rowY[nrows],
|
|
3407
|
+
cells
|
|
3408
|
+
};
|
|
2918
3409
|
if (table.borders) resolved.borders = table.borders;
|
|
2919
3410
|
let headerRows = 0;
|
|
2920
3411
|
while (headerRows < nrows && table.rows[headerRows].header) headerRows++;
|
|
2921
3412
|
if (headerRows > 0 && headerRows < nrows) {
|
|
2922
3413
|
const headerBottom = rowY[headerRows];
|
|
2923
|
-
const spansOut = cells.some(
|
|
3414
|
+
const spansOut = cells.some(
|
|
3415
|
+
(c) => c.y < headerBottom && c.y + c.height > headerBottom
|
|
3416
|
+
);
|
|
2924
3417
|
if (!spansOut) resolved.headerBottom = headerBottom;
|
|
2925
3418
|
}
|
|
2926
|
-
const cantSplitBands = table.rows.map(
|
|
3419
|
+
const cantSplitBands = table.rows.map(
|
|
3420
|
+
(row, r) => row.cantSplit ? { top: rowY[r], bottom: rowY[r + 1] } : null
|
|
3421
|
+
).filter((b) => b !== null);
|
|
2927
3422
|
if (cantSplitBands.length > 0) resolved.cantSplitBands = cantSplitBands;
|
|
2928
3423
|
return resolved;
|
|
2929
3424
|
}
|
|
@@ -2956,7 +3451,10 @@ function cloneTable(t) {
|
|
|
2956
3451
|
return { ...t, cells: t.cells.map(cloneCell) };
|
|
2957
3452
|
}
|
|
2958
3453
|
function cloneCell(cell) {
|
|
2959
|
-
const copy = {
|
|
3454
|
+
const copy = {
|
|
3455
|
+
...cell,
|
|
3456
|
+
lines: cell.lines.map((l) => ({ ...l }))
|
|
3457
|
+
};
|
|
2960
3458
|
if (cell.tables) copy.tables = cell.tables.map(cloneTable);
|
|
2961
3459
|
if (cell.floats) copy.floats = cell.floats.map((f) => ({ ...f }));
|
|
2962
3460
|
return copy;
|
|
@@ -2992,11 +3490,19 @@ function splitTableAt(table, cut) {
|
|
|
2992
3490
|
} else {
|
|
2993
3491
|
const c = straddlers.get(cell);
|
|
2994
3492
|
const topLines = cell.lines.filter((l) => l.y + l.height <= cut);
|
|
2995
|
-
const topTables = (cell.tables ?? []).filter(
|
|
2996
|
-
|
|
3493
|
+
const topTables = (cell.tables ?? []).filter(
|
|
3494
|
+
(t) => t.y + t.height <= cut
|
|
3495
|
+
);
|
|
3496
|
+
const topCell = {
|
|
3497
|
+
...cell,
|
|
3498
|
+
height: cut - cell.y,
|
|
3499
|
+
lines: topLines
|
|
3500
|
+
};
|
|
2997
3501
|
if (topTables.length > 0) topCell.tables = topTables;
|
|
2998
3502
|
else delete topCell.tables;
|
|
2999
|
-
const topFloats = (cell.floats ?? []).filter(
|
|
3503
|
+
const topFloats = (cell.floats ?? []).filter(
|
|
3504
|
+
(f) => f.y + f.height <= cut
|
|
3505
|
+
);
|
|
3000
3506
|
if (topFloats.length > 0) topCell.floats = topFloats;
|
|
3001
3507
|
else delete topCell.floats;
|
|
3002
3508
|
topCells.push(topCell);
|
|
@@ -3019,7 +3525,13 @@ function splitTableAt(table, cut) {
|
|
|
3019
3525
|
restCells.push(contCell);
|
|
3020
3526
|
}
|
|
3021
3527
|
}
|
|
3022
|
-
const top = {
|
|
3528
|
+
const top = {
|
|
3529
|
+
x: table.x,
|
|
3530
|
+
y: 0,
|
|
3531
|
+
width: table.width,
|
|
3532
|
+
height: cut,
|
|
3533
|
+
cells: topCells
|
|
3534
|
+
};
|
|
3023
3535
|
const rest = {
|
|
3024
3536
|
x: table.x,
|
|
3025
3537
|
y: 0,
|
|
@@ -3041,10 +3553,14 @@ function cloneHeaderCells(table, headerBottom) {
|
|
|
3041
3553
|
tables: void 0,
|
|
3042
3554
|
floats: cell.floats?.map((f) => ({ ...f })),
|
|
3043
3555
|
lines: cell.lines.map((l) => {
|
|
3044
|
-
const line = {
|
|
3556
|
+
const line = {
|
|
3557
|
+
...l,
|
|
3558
|
+
segments: l.segments.map((s) => ({ ...s, pos: void 0 }))
|
|
3559
|
+
};
|
|
3045
3560
|
delete line.from;
|
|
3046
3561
|
delete line.to;
|
|
3047
|
-
if (line.images)
|
|
3562
|
+
if (line.images)
|
|
3563
|
+
line.images = line.images.map((im) => ({ ...im, pos: void 0 }));
|
|
3048
3564
|
return line;
|
|
3049
3565
|
})
|
|
3050
3566
|
}));
|
|
@@ -3121,13 +3637,15 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
3121
3637
|
for (const l of ls)
|
|
3122
3638
|
for (const s of l.segments) {
|
|
3123
3639
|
const n = s.footnoteRef;
|
|
3124
|
-
if (n != null && footnotes?.has(n) && !pageFnSet.has(n) && !out.includes(n))
|
|
3640
|
+
if (n != null && footnotes?.has(n) && !pageFnSet.has(n) && !out.includes(n))
|
|
3641
|
+
out.push(n);
|
|
3125
3642
|
}
|
|
3126
3643
|
};
|
|
3127
3644
|
for (const c of t.cells) {
|
|
3128
3645
|
if (c.y >= maxY) continue;
|
|
3129
3646
|
scan(c.lines);
|
|
3130
|
-
if (c.tables)
|
|
3647
|
+
if (c.tables)
|
|
3648
|
+
for (const nt of c.tables) for (const nc of nt.cells) scan(nc.lines);
|
|
3131
3649
|
}
|
|
3132
3650
|
return out;
|
|
3133
3651
|
};
|
|
@@ -3156,10 +3674,16 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
3156
3674
|
return { separatorY: areaTop + FOOTNOTE_AREA_GAP / 2, lines: out };
|
|
3157
3675
|
};
|
|
3158
3676
|
const finalizePage = () => {
|
|
3159
|
-
const resolved = {
|
|
3677
|
+
const resolved = {
|
|
3678
|
+
index: pages.length,
|
|
3679
|
+
width: page.width,
|
|
3680
|
+
height: page.height,
|
|
3681
|
+
lines
|
|
3682
|
+
};
|
|
3160
3683
|
if (tables.length > 0) resolved.tables = tables;
|
|
3161
3684
|
if (pageFloats.length > 0) resolved.floats = pageFloats;
|
|
3162
|
-
if (pageFnNums.length > 0)
|
|
3685
|
+
if (pageFnNums.length > 0)
|
|
3686
|
+
resolved.footnotes = buildFootnoteArea(pageFnNums);
|
|
3163
3687
|
pages.push(resolved);
|
|
3164
3688
|
lines = [];
|
|
3165
3689
|
tables = [];
|
|
@@ -3249,7 +3773,9 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
3249
3773
|
}
|
|
3250
3774
|
const b = bandAt(y, estH);
|
|
3251
3775
|
if (b) return b;
|
|
3252
|
-
const blockers = exclusions.filter(
|
|
3776
|
+
const blockers = exclusions.filter(
|
|
3777
|
+
(ex) => ex.top < y + estH && ex.bottom > y
|
|
3778
|
+
);
|
|
3253
3779
|
if (blockers.length === 0) return { left: colX0(), right: colX1() };
|
|
3254
3780
|
y = Math.min(...blockers.map((ex) => ex.bottom));
|
|
3255
3781
|
}
|
|
@@ -3293,7 +3819,9 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
3293
3819
|
}
|
|
3294
3820
|
const drafts = item.para.drafts;
|
|
3295
3821
|
const draftsHeight = drafts?.reduce((s, d) => s + d.height, 0) ?? 0;
|
|
3296
|
-
const floatsAhead = exclusions.some(
|
|
3822
|
+
const floatsAhead = exclusions.some(
|
|
3823
|
+
(ex) => ex.bottom > y && ex.top < y + draftsHeight
|
|
3824
|
+
);
|
|
3297
3825
|
if (drafts && !floatsAhead) {
|
|
3298
3826
|
for (const d of drafts) emitLine(d);
|
|
3299
3827
|
} else {
|
|
@@ -3389,16 +3917,25 @@ function shiftDrafts(drafts, delta) {
|
|
|
3389
3917
|
...d,
|
|
3390
3918
|
from: d.from != null ? d.from + delta : d.from,
|
|
3391
3919
|
to: d.to != null ? d.to + delta : d.to,
|
|
3392
|
-
segments: d.segments.map(
|
|
3393
|
-
|
|
3920
|
+
segments: d.segments.map(
|
|
3921
|
+
(s) => s.pos != null ? { ...s, pos: s.pos + delta } : s
|
|
3922
|
+
),
|
|
3923
|
+
images: d.images.map(
|
|
3924
|
+
(im) => im.pos != null ? { ...im, pos: im.pos + delta } : im
|
|
3925
|
+
)
|
|
3394
3926
|
}));
|
|
3395
3927
|
}
|
|
3396
3928
|
function cloneLineShifted(l, delta) {
|
|
3397
3929
|
const out = {
|
|
3398
3930
|
...l,
|
|
3399
|
-
segments: l.segments.map(
|
|
3931
|
+
segments: l.segments.map(
|
|
3932
|
+
(s) => s.pos != null ? { ...s, pos: s.pos + delta } : { ...s }
|
|
3933
|
+
)
|
|
3400
3934
|
};
|
|
3401
|
-
if (l.images)
|
|
3935
|
+
if (l.images)
|
|
3936
|
+
out.images = l.images.map(
|
|
3937
|
+
(im) => im.pos != null ? { ...im, pos: im.pos + delta } : { ...im }
|
|
3938
|
+
);
|
|
3402
3939
|
if (l.from != null) out.from = l.from + delta;
|
|
3403
3940
|
if (l.to != null) out.to = l.to + delta;
|
|
3404
3941
|
return out;
|
|
@@ -3441,7 +3978,10 @@ function layVariants(docs, fn) {
|
|
|
3441
3978
|
return out;
|
|
3442
3979
|
}
|
|
3443
3980
|
function maxBandHeight(bands) {
|
|
3444
|
-
return Math.max(
|
|
3981
|
+
return Math.max(
|
|
3982
|
+
0,
|
|
3983
|
+
...[bands.default, bands.first, bands.even].filter(Boolean).map((b) => b.height)
|
|
3984
|
+
);
|
|
3445
3985
|
}
|
|
3446
3986
|
function anyBandHasFields(bands) {
|
|
3447
3987
|
return [bands.default, bands.first, bands.even].some(
|
|
@@ -3467,9 +4007,17 @@ function layoutChrome(doc, topY, left, right, ctx) {
|
|
|
3467
4007
|
const lines = flow.lines.map((l) => ({ ...l, y: l.y + topY }));
|
|
3468
4008
|
flow.tables.forEach((t) => offsetTable(t, topY));
|
|
3469
4009
|
stripPositions(lines, flow.tables);
|
|
3470
|
-
const band = {
|
|
4010
|
+
const band = {
|
|
4011
|
+
lines,
|
|
4012
|
+
tables: flow.tables,
|
|
4013
|
+
height: flow.height
|
|
4014
|
+
};
|
|
3471
4015
|
if (flow.floats.length > 0)
|
|
3472
|
-
band.floats = flow.floats.map((f) => ({
|
|
4016
|
+
band.floats = flow.floats.map((f) => ({
|
|
4017
|
+
...f,
|
|
4018
|
+
y: f.y + topY,
|
|
4019
|
+
pos: void 0
|
|
4020
|
+
}));
|
|
3473
4021
|
return band;
|
|
3474
4022
|
}
|
|
3475
4023
|
function layoutFooterChrome(doc, pageHeight, left, right, ctx) {
|
|
@@ -3481,12 +4029,21 @@ function layoutFooterChrome(doc, pageHeight, left, right, ctx) {
|
|
|
3481
4029
|
height: flow.height
|
|
3482
4030
|
};
|
|
3483
4031
|
band.tables.forEach((t) => offsetTable(t, topY));
|
|
3484
|
-
if (flow.floats)
|
|
4032
|
+
if (flow.floats)
|
|
4033
|
+
band.floats = flow.floats.map((f) => ({ ...f, y: f.y + topY }));
|
|
3485
4034
|
return band;
|
|
3486
4035
|
}
|
|
3487
4036
|
function layoutFootnoteBody(doc, left, right, ctx) {
|
|
3488
|
-
const fnCtx = {
|
|
3489
|
-
|
|
4037
|
+
const fnCtx = {
|
|
4038
|
+
...ctx,
|
|
4039
|
+
base: { ...ctx.base, sizePt: ctx.base.sizePt * FOOTNOTE_FONT_SCALE }
|
|
4040
|
+
};
|
|
4041
|
+
const flow = layoutFlow(
|
|
4042
|
+
toFlowBlocks(doc, fnCtx.base, false),
|
|
4043
|
+
left,
|
|
4044
|
+
right,
|
|
4045
|
+
fnCtx
|
|
4046
|
+
);
|
|
3490
4047
|
stripPositions(flow.lines, flow.tables);
|
|
3491
4048
|
return { lines: flow.lines, height: flow.height };
|
|
3492
4049
|
}
|
|
@@ -3495,10 +4052,24 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3495
4052
|
const { page } = config;
|
|
3496
4053
|
const left = page.margin.left;
|
|
3497
4054
|
const right = page.width - page.margin.right;
|
|
3498
|
-
const headerDocs = {
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
4055
|
+
const headerDocs = {
|
|
4056
|
+
default: chrome?.header,
|
|
4057
|
+
first: chrome?.headerFirst,
|
|
4058
|
+
even: chrome?.headerEven
|
|
4059
|
+
};
|
|
4060
|
+
const footerDocs = {
|
|
4061
|
+
default: chrome?.footer,
|
|
4062
|
+
first: chrome?.footerFirst,
|
|
4063
|
+
even: chrome?.footerEven
|
|
4064
|
+
};
|
|
4065
|
+
const layHeaders = (c) => layVariants(
|
|
4066
|
+
headerDocs,
|
|
4067
|
+
(d) => layoutChrome(d, CHROME_DISTANCE, left, right, c)
|
|
4068
|
+
);
|
|
4069
|
+
const layFooters = (c) => layVariants(
|
|
4070
|
+
footerDocs,
|
|
4071
|
+
(d) => layoutFooterChrome(d, page.height, left, right, c)
|
|
4072
|
+
);
|
|
3502
4073
|
let headers = layHeaders(ctx);
|
|
3503
4074
|
let footers = layFooters(ctx);
|
|
3504
4075
|
let top = page.margin.top;
|
|
@@ -3507,25 +4078,46 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3507
4078
|
top = Math.max(top, CHROME_DISTANCE + maxBandHeight(headers));
|
|
3508
4079
|
}
|
|
3509
4080
|
if (footers.default || footers.first || footers.even) {
|
|
3510
|
-
bottom = Math.min(
|
|
4081
|
+
bottom = Math.min(
|
|
4082
|
+
bottom,
|
|
4083
|
+
page.height - CHROME_DISTANCE - maxBandHeight(footers)
|
|
4084
|
+
);
|
|
3511
4085
|
}
|
|
3512
|
-
const counter = createNumberingCounter(
|
|
4086
|
+
const counter = createNumberingCounter(
|
|
4087
|
+
doc.attrs["numbering"]
|
|
4088
|
+
);
|
|
3513
4089
|
const sections = doc.attrs["sections"] ?? [
|
|
3514
|
-
{
|
|
4090
|
+
{
|
|
4091
|
+
blockCount: doc.childCount,
|
|
4092
|
+
columns: { count: 1, gap: 0 },
|
|
4093
|
+
newPage: true
|
|
4094
|
+
}
|
|
3515
4095
|
];
|
|
3516
4096
|
const blockSection = [];
|
|
3517
4097
|
for (const sec of sections) {
|
|
3518
4098
|
for (let k = 0; k < sec.blockCount; k++) {
|
|
3519
|
-
blockSection.push({
|
|
4099
|
+
blockSection.push({
|
|
4100
|
+
columns: sec.columns,
|
|
4101
|
+
start: k === 0,
|
|
4102
|
+
newPage: sec.newPage
|
|
4103
|
+
});
|
|
3520
4104
|
}
|
|
3521
4105
|
}
|
|
3522
4106
|
const lastSec = sections[sections.length - 1];
|
|
3523
4107
|
while (blockSection.length < doc.childCount) {
|
|
3524
|
-
blockSection.push({
|
|
4108
|
+
blockSection.push({
|
|
4109
|
+
columns: lastSec.columns,
|
|
4110
|
+
start: false,
|
|
4111
|
+
newPage: lastSec.newPage
|
|
4112
|
+
});
|
|
3525
4113
|
}
|
|
3526
4114
|
const items = [];
|
|
3527
4115
|
doc.forEach((node, offset, index) => {
|
|
3528
|
-
const bs = blockSection[index] ?? {
|
|
4116
|
+
const bs = blockSection[index] ?? {
|
|
4117
|
+
columns: { count: 1, gap: 0 },
|
|
4118
|
+
start: false,
|
|
4119
|
+
newPage: true
|
|
4120
|
+
};
|
|
3529
4121
|
const colRight = left + columnWidth(right - left, bs.columns);
|
|
3530
4122
|
const tag = (item) => {
|
|
3531
4123
|
if (bs.start) item.section = { ...bs.columns, newPage: bs.newPage };
|
|
@@ -3558,17 +4150,35 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3558
4150
|
}
|
|
3559
4151
|
const flow = paragraphToFlow(node, ctx.base, offset, true, marker);
|
|
3560
4152
|
const drafts = layoutParagraph(flow, left, colRight, ctx);
|
|
3561
|
-
cache?.paragraphs.set(node, {
|
|
4153
|
+
cache?.paragraphs.set(node, {
|
|
4154
|
+
left,
|
|
4155
|
+
right: colRight,
|
|
4156
|
+
basePos: contentStart,
|
|
4157
|
+
marker,
|
|
4158
|
+
drafts
|
|
4159
|
+
});
|
|
3562
4160
|
items.push(tag({ para: { ...para(drafts), getFlow: () => flow } }));
|
|
3563
4161
|
} else if (node.type.name === "table") {
|
|
3564
4162
|
const hit = cache?.tables.get(node);
|
|
3565
4163
|
if (hit && hit.left === left && hit.right === colRight) {
|
|
3566
|
-
items.push(
|
|
4164
|
+
items.push(
|
|
4165
|
+
tag({ table: cloneTableShifted(hit.table, offset - hit.basePos) })
|
|
4166
|
+
);
|
|
3567
4167
|
return;
|
|
3568
4168
|
}
|
|
3569
|
-
const table = layoutTable(
|
|
4169
|
+
const table = layoutTable(
|
|
4170
|
+
tableToFlow(node, ctx.base, offset, counter),
|
|
4171
|
+
left,
|
|
4172
|
+
colRight,
|
|
4173
|
+
ctx
|
|
4174
|
+
);
|
|
3570
4175
|
if (cache && !tableHasList(node)) {
|
|
3571
|
-
cache.tables.set(node, {
|
|
4176
|
+
cache.tables.set(node, {
|
|
4177
|
+
left,
|
|
4178
|
+
right: colRight,
|
|
4179
|
+
basePos: offset,
|
|
4180
|
+
table
|
|
4181
|
+
});
|
|
3572
4182
|
items.push(tag({ table: cloneTableShifted(table, 0) }));
|
|
3573
4183
|
} else {
|
|
3574
4184
|
items.push(tag({ table }));
|
|
@@ -3586,7 +4196,10 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3586
4196
|
assignSectionHeights(items);
|
|
3587
4197
|
const resolved = placeBlocks(items, config, ctx, { top, bottom }, fnMap);
|
|
3588
4198
|
if (anyBandHasFields(headers) || anyBandHasFields(footers)) {
|
|
3589
|
-
const fieldCtx = {
|
|
4199
|
+
const fieldCtx = {
|
|
4200
|
+
...ctx,
|
|
4201
|
+
fieldPlaceholder: String(resolved.pages.length)
|
|
4202
|
+
};
|
|
3590
4203
|
headers = layHeaders(fieldCtx);
|
|
3591
4204
|
footers = layFooters(fieldCtx);
|
|
3592
4205
|
}
|
|
@@ -3597,7 +4210,10 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3597
4210
|
if (footers.first) resolved.pageFooterFirst = footers.first;
|
|
3598
4211
|
if (footers.even) resolved.pageFooterEven = footers.even;
|
|
3599
4212
|
if (chrome?.titlePg || chrome?.evenAndOdd) {
|
|
3600
|
-
resolved.chromeSelect = {
|
|
4213
|
+
resolved.chromeSelect = {
|
|
4214
|
+
titlePg: !!chrome.titlePg,
|
|
4215
|
+
evenAndOdd: !!chrome.evenAndOdd
|
|
4216
|
+
};
|
|
3601
4217
|
}
|
|
3602
4218
|
return resolved;
|
|
3603
4219
|
}
|
|
@@ -4816,7 +5432,12 @@ function collectFontFamilies(...docs) {
|
|
|
4816
5432
|
import { baseKeymap } from "prosemirror-commands";
|
|
4817
5433
|
import { history, redo, undo } from "prosemirror-history";
|
|
4818
5434
|
import { keymap } from "prosemirror-keymap";
|
|
4819
|
-
import {
|
|
5435
|
+
import {
|
|
5436
|
+
EditorState,
|
|
5437
|
+
Plugin,
|
|
5438
|
+
PluginKey,
|
|
5439
|
+
TextSelection
|
|
5440
|
+
} from "prosemirror-state";
|
|
4820
5441
|
import { canSplit } from "prosemirror-transform";
|
|
4821
5442
|
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
|
|
4822
5443
|
function moveCaretCommand(compute, extend = false) {
|
|
@@ -4835,7 +5456,10 @@ var splitListItem = (state, dispatch) => {
|
|
|
4835
5456
|
if ($from.parent !== $to.parent) return false;
|
|
4836
5457
|
if (parent.content.size === 0) {
|
|
4837
5458
|
dispatch?.(
|
|
4838
|
-
state.tr.setNodeMarkup($from.before(), null, {
|
|
5459
|
+
state.tr.setNodeMarkup($from.before(), null, {
|
|
5460
|
+
...parent.attrs,
|
|
5461
|
+
list: null
|
|
5462
|
+
})
|
|
4839
5463
|
);
|
|
4840
5464
|
return true;
|
|
4841
5465
|
}
|
|
@@ -4911,6 +5535,7 @@ var InputBridge = class {
|
|
|
4911
5535
|
this.dom.appendChild(this.host);
|
|
4912
5536
|
this.view = new EditorView(this.host, {
|
|
4913
5537
|
state: createEditingState(options.doc, options.keys),
|
|
5538
|
+
handlePaste: options.handlePaste,
|
|
4914
5539
|
dispatchTransaction: (tr) => {
|
|
4915
5540
|
const state = this.view.state.apply(tr);
|
|
4916
5541
|
this.view.updateState(state);
|
|
@@ -4934,7 +5559,10 @@ var InputBridge = class {
|
|
|
4934
5559
|
setSelection(anchor, head = anchor) {
|
|
4935
5560
|
const { doc } = this.view.state;
|
|
4936
5561
|
const clamp2 = (p) => Math.max(0, Math.min(p, doc.content.size));
|
|
4937
|
-
const sel = TextSelection.between(
|
|
5562
|
+
const sel = TextSelection.between(
|
|
5563
|
+
doc.resolve(clamp2(anchor)),
|
|
5564
|
+
doc.resolve(clamp2(head))
|
|
5565
|
+
);
|
|
4938
5566
|
this.view.dispatch(this.view.state.tr.setSelection(sel));
|
|
4939
5567
|
}
|
|
4940
5568
|
/** Select the word around `pos` (double-click); falls back to a collapsed
|
|
@@ -5749,7 +6377,8 @@ function setAlign(align) {
|
|
|
5749
6377
|
const tr = state.tr;
|
|
5750
6378
|
let changed = false;
|
|
5751
6379
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
5752
|
-
if (node.type.name !== "paragraph" || node.attrs["align"] === align)
|
|
6380
|
+
if (node.type.name !== "paragraph" || node.attrs["align"] === align)
|
|
6381
|
+
return;
|
|
5753
6382
|
tr.setNodeAttribute(pos, "align", align);
|
|
5754
6383
|
changed = true;
|
|
5755
6384
|
});
|
|
@@ -5774,7 +6403,9 @@ function toggleHeading(level) {
|
|
|
5774
6403
|
const paras = selectedParagraphs(state);
|
|
5775
6404
|
if (paras.length === 0) return false;
|
|
5776
6405
|
if (dispatch) {
|
|
5777
|
-
const allThisLevel = paras.every(
|
|
6406
|
+
const allThisLevel = paras.every(
|
|
6407
|
+
(p) => p.node.attrs["heading"] === level
|
|
6408
|
+
);
|
|
5778
6409
|
const next = allThisLevel ? null : level;
|
|
5779
6410
|
const tr = state.tr;
|
|
5780
6411
|
for (const p of paras) tr.setNodeAttribute(p.pos, "heading", next);
|
|
@@ -6037,8 +6668,10 @@ var BapbongEditor = class {
|
|
|
6037
6668
|
* Built from the shared command layer; the same ops run on a backend.
|
|
6038
6669
|
* (Plugin-contributed commands are an additive follow-up.) */
|
|
6039
6670
|
commands = defaultCommands();
|
|
6671
|
+
readClipboardFallback;
|
|
6040
6672
|
constructor(stack, opts = {}) {
|
|
6041
6673
|
this.stack = stack;
|
|
6674
|
+
this.readClipboardFallback = opts.readClipboardFallback;
|
|
6042
6675
|
this.core = new RenderCore(stack, {
|
|
6043
6676
|
viewport: opts.viewport,
|
|
6044
6677
|
measureText: opts.measureText,
|
|
@@ -6055,9 +6688,12 @@ var BapbongEditor = class {
|
|
|
6055
6688
|
stack.addEventListener("dblclick", this.onDblClick);
|
|
6056
6689
|
stack.addEventListener("contextmenu", this.onContextMenu);
|
|
6057
6690
|
window.addEventListener("keydown", this.onKeyDown, true);
|
|
6058
|
-
this.plugins = new Collection(
|
|
6059
|
-
|
|
6060
|
-
|
|
6691
|
+
this.plugins = new Collection(
|
|
6692
|
+
[...createBuiltins(), ...opts.plugins ?? []],
|
|
6693
|
+
{
|
|
6694
|
+
idProperty: "name"
|
|
6695
|
+
}
|
|
6696
|
+
);
|
|
6061
6697
|
this.pointerPlugins = [...this.plugins].some((p) => p.onPointer);
|
|
6062
6698
|
this.pluginCtx = this.makePluginContext();
|
|
6063
6699
|
for (const p of this.plugins) {
|
|
@@ -6082,8 +6718,14 @@ var BapbongEditor = class {
|
|
|
6082
6718
|
setActionButton: (at, onActivate) => this.setActionButton(at, onActivate),
|
|
6083
6719
|
setFrame: (frame) => this.setFrame(frame)
|
|
6084
6720
|
};
|
|
6085
|
-
Object.defineProperty(ctx, "state", {
|
|
6086
|
-
|
|
6721
|
+
Object.defineProperty(ctx, "state", {
|
|
6722
|
+
enumerable: true,
|
|
6723
|
+
get: () => this.state
|
|
6724
|
+
});
|
|
6725
|
+
Object.defineProperty(ctx, "layout", {
|
|
6726
|
+
enumerable: true,
|
|
6727
|
+
get: () => this.core.layout
|
|
6728
|
+
});
|
|
6087
6729
|
return ctx;
|
|
6088
6730
|
}
|
|
6089
6731
|
// ── Public API ──────────────────────────────────────────────────────
|
|
@@ -6226,34 +6868,116 @@ var BapbongEditor = class {
|
|
|
6226
6868
|
this.bridge.focus();
|
|
6227
6869
|
return document.execCommand("cut");
|
|
6228
6870
|
}
|
|
6229
|
-
/** Paste clipboard content
|
|
6871
|
+
/** Paste clipboard content — HTML parsed by the schema, image blobs as
|
|
6872
|
+
* embedded images, else plain text.
|
|
6873
|
+
*
|
|
6874
|
+
* Menu-driven paste can't ride a native ClipboardEvent, so it climbs a
|
|
6875
|
+
* ladder of acquisition strategies until one yields content:
|
|
6876
|
+
* 1. the host's native clipboard reader (`readClipboardFallback`) when
|
|
6877
|
+
* provided — on WKWebView every programmatic clipboard READ (both
|
|
6878
|
+
* `navigator.clipboard.*` and execCommand) pops a "Paste" permission
|
|
6879
|
+
* callout and pends until the user taps it, so the desktop shell
|
|
6880
|
+
* must never touch the DOM clipboard from a menu click;
|
|
6881
|
+
* 2. async Clipboard API (`navigator.clipboard.read`) — browsers, where
|
|
6882
|
+
* a user-gesture read is granted silently;
|
|
6883
|
+
* 3. `document.execCommand('paste')` — legacy last resort. */
|
|
6230
6884
|
async paste() {
|
|
6231
6885
|
const view = this.bridge?.view;
|
|
6232
6886
|
if (!view) return;
|
|
6887
|
+
this.bridge?.focus();
|
|
6888
|
+
if (await this.pasteViaHostClipboard(view)) return;
|
|
6889
|
+
if (this.readClipboardFallback) return;
|
|
6890
|
+
if (await this.pasteViaClipboardApi(view)) return;
|
|
6891
|
+
document.execCommand("paste");
|
|
6892
|
+
}
|
|
6893
|
+
async pasteViaClipboardApi(view) {
|
|
6233
6894
|
try {
|
|
6234
6895
|
const items = await navigator.clipboard.read();
|
|
6896
|
+
const htmlItem = items.find((i) => i.types.includes("text/html"));
|
|
6897
|
+
if (htmlItem) {
|
|
6898
|
+
const html = await (await htmlItem.getType("text/html")).text();
|
|
6899
|
+
const dom = new DOMParser().parseFromString(html, "text/html").body;
|
|
6900
|
+
if ((dom.textContent ?? "").trim()) {
|
|
6901
|
+
const slice = PMDOMParser.fromSchema(view.state.schema).parseSlice(
|
|
6902
|
+
dom
|
|
6903
|
+
);
|
|
6904
|
+
view.dispatch(view.state.tr.replaceSelection(slice).scrollIntoView());
|
|
6905
|
+
return true;
|
|
6906
|
+
}
|
|
6907
|
+
}
|
|
6235
6908
|
for (const item of items) {
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6909
|
+
const type = item.types.find((t) => t.startsWith("image/"));
|
|
6910
|
+
if (type && await insertImageBlobs(view, [await item.getType(type)]))
|
|
6911
|
+
return true;
|
|
6912
|
+
}
|
|
6913
|
+
const text = await navigator.clipboard.readText();
|
|
6914
|
+
if (text) {
|
|
6915
|
+
view.dispatch(view.state.tr.insertText(text).scrollIntoView());
|
|
6916
|
+
return true;
|
|
6917
|
+
}
|
|
6918
|
+
return false;
|
|
6919
|
+
} catch (err) {
|
|
6920
|
+
console.warn(
|
|
6921
|
+
"[bapbong] Clipboard API paste unavailable, falling back:",
|
|
6922
|
+
err
|
|
6923
|
+
);
|
|
6924
|
+
return false;
|
|
6925
|
+
}
|
|
6926
|
+
}
|
|
6927
|
+
async pasteViaHostClipboard(view) {
|
|
6928
|
+
if (!this.readClipboardFallback) return false;
|
|
6929
|
+
try {
|
|
6930
|
+
const data = await this.readClipboardFallback();
|
|
6931
|
+
if (!data) return false;
|
|
6932
|
+
if (data.html) {
|
|
6933
|
+
const dom = new DOMParser().parseFromString(
|
|
6934
|
+
data.html,
|
|
6935
|
+
"text/html"
|
|
6936
|
+
).body;
|
|
6937
|
+
if ((dom.textContent ?? "").trim()) {
|
|
6938
|
+
const slice = PMDOMParser.fromSchema(view.state.schema).parseSlice(
|
|
6939
|
+
dom
|
|
6940
|
+
);
|
|
6241
6941
|
view.dispatch(view.state.tr.replaceSelection(slice).scrollIntoView());
|
|
6242
|
-
return;
|
|
6942
|
+
return true;
|
|
6243
6943
|
}
|
|
6244
6944
|
}
|
|
6245
|
-
|
|
6246
|
-
|
|
6945
|
+
if (data.image?.length) {
|
|
6946
|
+
const blob = new Blob([data.image], {
|
|
6947
|
+
type: data.imageMime ?? "image/png"
|
|
6948
|
+
});
|
|
6949
|
+
if (await insertImageBlobs(view, [blob])) return true;
|
|
6950
|
+
}
|
|
6951
|
+
if (data.text) {
|
|
6952
|
+
view.dispatch(view.state.tr.insertText(data.text).scrollIntoView());
|
|
6953
|
+
return true;
|
|
6954
|
+
}
|
|
6955
|
+
return false;
|
|
6956
|
+
} catch (err) {
|
|
6957
|
+
console.warn("[bapbong] host clipboard fallback failed:", err);
|
|
6958
|
+
return false;
|
|
6247
6959
|
}
|
|
6248
6960
|
}
|
|
6249
6961
|
/** Paste clipboard text as plain text (no formatting). */
|
|
6250
6962
|
async pasteText() {
|
|
6251
6963
|
const view = this.bridge?.view;
|
|
6252
6964
|
if (!view) return;
|
|
6965
|
+
this.bridge?.focus();
|
|
6966
|
+
if (this.readClipboardFallback) {
|
|
6967
|
+
try {
|
|
6968
|
+
const data = await this.readClipboardFallback();
|
|
6969
|
+
if (data?.text)
|
|
6970
|
+
view.dispatch(view.state.tr.insertText(data.text).scrollIntoView());
|
|
6971
|
+
} catch (err) {
|
|
6972
|
+
console.warn("[bapbong] host clipboard fallback failed:", err);
|
|
6973
|
+
}
|
|
6974
|
+
return;
|
|
6975
|
+
}
|
|
6253
6976
|
try {
|
|
6254
6977
|
const text = await navigator.clipboard.readText();
|
|
6255
6978
|
if (text) view.dispatch(view.state.tr.insertText(text).scrollIntoView());
|
|
6256
|
-
} catch {
|
|
6979
|
+
} catch (err) {
|
|
6980
|
+
console.warn("[bapbong] Clipboard API readText unavailable:", err);
|
|
6257
6981
|
}
|
|
6258
6982
|
}
|
|
6259
6983
|
destroy() {
|
|
@@ -6294,7 +7018,8 @@ var BapbongEditor = class {
|
|
|
6294
7018
|
"Shift-ArrowUp": this.verticalCmd(-1, true),
|
|
6295
7019
|
"Shift-ArrowDown": this.verticalCmd(1, true)
|
|
6296
7020
|
},
|
|
6297
|
-
onUpdate: (state, tr) => this.refresh(state, tr)
|
|
7021
|
+
onUpdate: (state, tr) => this.refresh(state, tr),
|
|
7022
|
+
handlePaste: imagePasteHandler
|
|
6298
7023
|
});
|
|
6299
7024
|
this.stack.appendChild(this.bridge.dom);
|
|
6300
7025
|
this.render(this.bridge.state, true);
|
|
@@ -6323,14 +7048,25 @@ var BapbongEditor = class {
|
|
|
6323
7048
|
else this.core.paintOverlay(overlay);
|
|
6324
7049
|
const caret = this.lastCaret;
|
|
6325
7050
|
if (caret && this.bridge) {
|
|
6326
|
-
const pt = this.core.pageToCanvas({
|
|
7051
|
+
const pt = this.core.pageToCanvas({
|
|
7052
|
+
pageIndex: caret.pageIndex,
|
|
7053
|
+
x: caret.x,
|
|
7054
|
+
y: caret.y
|
|
7055
|
+
});
|
|
6327
7056
|
if (pt) this.bridge.place(pt.x, pt.y, caret.height);
|
|
6328
7057
|
}
|
|
6329
|
-
this.emitChange({
|
|
7058
|
+
this.emitChange({
|
|
7059
|
+
state,
|
|
7060
|
+
pageCount: this.core.pageCount,
|
|
7061
|
+
docChanged: contentDirty
|
|
7062
|
+
});
|
|
6330
7063
|
}
|
|
6331
7064
|
/** The caret/selection overlay in the current blink phase. */
|
|
6332
7065
|
currentOverlay() {
|
|
6333
|
-
return {
|
|
7066
|
+
return {
|
|
7067
|
+
caret: this.caretVisible ? this.lastCaret : null,
|
|
7068
|
+
selection: this.lastSelection
|
|
7069
|
+
};
|
|
6334
7070
|
}
|
|
6335
7071
|
/** Each plugin's doc-range decorations (the core resolves them to rects). */
|
|
6336
7072
|
pluginDecorations() {
|
|
@@ -6405,7 +7141,8 @@ var BapbongEditor = class {
|
|
|
6405
7141
|
* gesture), so plugins never steal keys from other inputs on the page. */
|
|
6406
7142
|
onKeyDown = (ev) => {
|
|
6407
7143
|
const target = ev.target;
|
|
6408
|
-
if (target && target !== document.body && !this.stack.contains(target))
|
|
7144
|
+
if (target && target !== document.body && !this.stack.contains(target))
|
|
7145
|
+
return;
|
|
6409
7146
|
const offered = {
|
|
6410
7147
|
key: ev.key,
|
|
6411
7148
|
ctrlKey: ev.ctrlKey,
|
|
@@ -6439,7 +7176,10 @@ var BapbongEditor = class {
|
|
|
6439
7176
|
this.lastCaret = this.core.caretRect(this.dragHead);
|
|
6440
7177
|
this.lastSelection = from === to ? [] : this.core.selectionRects(from, to);
|
|
6441
7178
|
this.caretVisible = true;
|
|
6442
|
-
this.core.paintOverlay({
|
|
7179
|
+
this.core.paintOverlay({
|
|
7180
|
+
caret: this.lastCaret,
|
|
7181
|
+
selection: this.lastSelection
|
|
7182
|
+
});
|
|
6443
7183
|
}
|
|
6444
7184
|
onPointerUp = (ev) => {
|
|
6445
7185
|
if (this.offerPointer("up", ev)) {
|
|
@@ -6500,8 +7240,16 @@ var BapbongEditor = class {
|
|
|
6500
7240
|
this.guideEl.style.cssText = "position:absolute;width:0;border-left:2px dashed #378add;pointer-events:none;z-index:5;";
|
|
6501
7241
|
this.stack.appendChild(this.guideEl);
|
|
6502
7242
|
}
|
|
6503
|
-
const top = this.core.pageToCanvas({
|
|
6504
|
-
|
|
7243
|
+
const top = this.core.pageToCanvas({
|
|
7244
|
+
pageIndex: guide.pageIndex,
|
|
7245
|
+
x: guide.x,
|
|
7246
|
+
y: guide.y
|
|
7247
|
+
});
|
|
7248
|
+
const bottom = this.core.pageToCanvas({
|
|
7249
|
+
pageIndex: guide.pageIndex,
|
|
7250
|
+
x: guide.x,
|
|
7251
|
+
y: guide.y + guide.height
|
|
7252
|
+
});
|
|
6505
7253
|
if (!top || !bottom) {
|
|
6506
7254
|
this.guideEl.style.display = "none";
|
|
6507
7255
|
return;
|
|
@@ -6549,7 +7297,11 @@ var BapbongEditor = class {
|
|
|
6549
7297
|
this.stack.appendChild(el2);
|
|
6550
7298
|
this.frameEl = el2;
|
|
6551
7299
|
}
|
|
6552
|
-
const tl = this.core.pageToCanvas({
|
|
7300
|
+
const tl = this.core.pageToCanvas({
|
|
7301
|
+
pageIndex: frame.pageIndex,
|
|
7302
|
+
x: frame.x,
|
|
7303
|
+
y: frame.y
|
|
7304
|
+
});
|
|
6553
7305
|
const br = this.core.pageToCanvas({
|
|
6554
7306
|
pageIndex: frame.pageIndex,
|
|
6555
7307
|
x: frame.x + frame.width,
|
|
@@ -6585,7 +7337,11 @@ var BapbongEditor = class {
|
|
|
6585
7337
|
this.highlightEls.forEach((el, i) => {
|
|
6586
7338
|
const r = list[i];
|
|
6587
7339
|
const tl = r && this.core.pageToCanvas({ pageIndex: r.pageIndex, x: r.x, y: r.y });
|
|
6588
|
-
const br = r && this.core.pageToCanvas({
|
|
7340
|
+
const br = r && this.core.pageToCanvas({
|
|
7341
|
+
pageIndex: r.pageIndex,
|
|
7342
|
+
x: r.x + r.width,
|
|
7343
|
+
y: r.y + r.height
|
|
7344
|
+
});
|
|
6589
7345
|
if (!tl || !br) {
|
|
6590
7346
|
el.style.display = "none";
|
|
6591
7347
|
return;
|