@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.cjs
CHANGED
|
@@ -39,8 +39,95 @@ module.exports = __toCommonJS(index_exports);
|
|
|
39
39
|
// packages/editor/src/lib/bapbong-editor.ts
|
|
40
40
|
var import_prosemirror_model3 = require("prosemirror-model");
|
|
41
41
|
|
|
42
|
+
// packages/editor/src/lib/paste-images.ts
|
|
43
|
+
var MAX_DISPLAY_WIDTH = 600;
|
|
44
|
+
function imageFilesIn(dt) {
|
|
45
|
+
const files = [];
|
|
46
|
+
for (const item of Array.from(dt?.items ?? [])) {
|
|
47
|
+
if (item.kind === "file" && item.type.startsWith("image/")) {
|
|
48
|
+
const f = item.getAsFile();
|
|
49
|
+
if (f) files.push(f);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return files;
|
|
53
|
+
}
|
|
54
|
+
async function bitmapSize(blob) {
|
|
55
|
+
try {
|
|
56
|
+
const bmp = await createImageBitmap(blob);
|
|
57
|
+
const size = { width: bmp.width, height: bmp.height };
|
|
58
|
+
bmp.close();
|
|
59
|
+
return size;
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var toDataURL = (blob) => new Promise((resolve, reject) => {
|
|
65
|
+
const r = new FileReader();
|
|
66
|
+
r.onload = () => resolve(r.result);
|
|
67
|
+
r.onerror = () => reject(r.error);
|
|
68
|
+
r.readAsDataURL(blob);
|
|
69
|
+
});
|
|
70
|
+
async function insertImageBlobs(view, blobs) {
|
|
71
|
+
const imageType = view.state.schema.nodes["image"];
|
|
72
|
+
if (!imageType) return false;
|
|
73
|
+
const nodes = [];
|
|
74
|
+
for (const blob of blobs) {
|
|
75
|
+
const size = await bitmapSize(blob);
|
|
76
|
+
if (!size) continue;
|
|
77
|
+
const scale = Math.min(1, MAX_DISPLAY_WIDTH / size.width);
|
|
78
|
+
nodes.push(
|
|
79
|
+
imageType.create({
|
|
80
|
+
src: await toDataURL(blob),
|
|
81
|
+
width: Math.round(size.width * scale),
|
|
82
|
+
height: Math.round(size.height * scale)
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (!nodes.length) return false;
|
|
87
|
+
let tr = view.state.tr;
|
|
88
|
+
for (const node of nodes) tr = tr.replaceSelectionWith(node);
|
|
89
|
+
view.dispatch(tr.scrollIntoView());
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
function imagePasteHandler(view, event) {
|
|
93
|
+
const dt = event.clipboardData;
|
|
94
|
+
const files = imageFilesIn(dt);
|
|
95
|
+
if (!files.length) return false;
|
|
96
|
+
const html = dt?.getData("text/html") ?? "";
|
|
97
|
+
if (html) {
|
|
98
|
+
const body = new DOMParser().parseFromString(html, "text/html").body;
|
|
99
|
+
if ((body.textContent ?? "").trim()) return false;
|
|
100
|
+
}
|
|
101
|
+
void insertImageBlobs(view, files);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
42
105
|
// packages/model/dist/index.js
|
|
43
106
|
var import_prosemirror_model = require("prosemirror-model");
|
|
107
|
+
function pastedParagraphAttrs(el, heading) {
|
|
108
|
+
const style = el.getAttribute("style") ?? "";
|
|
109
|
+
const m = /(?:^|;)\s*text-align\s*:\s*(center|right|justify)/i.exec(style);
|
|
110
|
+
return { heading, align: m ? m[1].toLowerCase() : null };
|
|
111
|
+
}
|
|
112
|
+
function pastedImageAttrs(el) {
|
|
113
|
+
const e = el;
|
|
114
|
+
const src = e.getAttribute("src") ?? "";
|
|
115
|
+
if (!/^data:image\//i.test(src)) return false;
|
|
116
|
+
const dim = (v) => {
|
|
117
|
+
const n = parseFloat(v ?? "");
|
|
118
|
+
return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
src,
|
|
122
|
+
alt: e.getAttribute("alt") ?? "",
|
|
123
|
+
width: dim(e.getAttribute("width")),
|
|
124
|
+
height: dim(e.getAttribute("height"))
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function pastedLinkAttrs(el) {
|
|
128
|
+
const href = (el.getAttribute("href") ?? "").trim();
|
|
129
|
+
return /^(https?:|mailto:|#)/i.test(href) ? { href } : false;
|
|
130
|
+
}
|
|
44
131
|
var schema = new import_prosemirror_model.Schema({
|
|
45
132
|
nodes: {
|
|
46
133
|
doc: {
|
|
@@ -76,6 +163,11 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
76
163
|
// an <h1>–<h6> in toDOM so the a11y mirror is semantic), or null for a
|
|
77
164
|
// body paragraph.
|
|
78
165
|
heading: { default: null },
|
|
166
|
+
// Named Word paragraph style with no outline level: 'Title' |
|
|
167
|
+
// 'Subtitle', or null. Mutually exclusive with `heading` — the
|
|
168
|
+
// setParagraphStyle command is the only writer and keeps the
|
|
169
|
+
// invariant (styleId set ⇒ heading null).
|
|
170
|
+
styleId: { default: null },
|
|
79
171
|
// w:tabs — [{ pos, val: 'left'|'right'|'center'|'decimal', leader? }]
|
|
80
172
|
// in px from the paragraph's content left edge, or null. Importer-set.
|
|
81
173
|
tabs: { default: null },
|
|
@@ -84,15 +176,23 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
84
176
|
// w:pageBreakBefore — start this paragraph on a new page.
|
|
85
177
|
pageBreakBefore: { default: false }
|
|
86
178
|
},
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
parseDOM: [
|
|
179
|
+
// HTML paste path: recover heading level from h1–h6 and alignment from
|
|
180
|
+
// inline style. Other attrs (list/indent/tabs/spacing) stay importer-only
|
|
181
|
+
// — pasted HTML rarely carries them faithfully.
|
|
182
|
+
parseDOM: [
|
|
183
|
+
{ tag: "p", getAttrs: (el) => pastedParagraphAttrs(el, null) },
|
|
184
|
+
...[1, 2, 3, 4, 5, 6].map((level) => ({
|
|
185
|
+
tag: `h${level}`,
|
|
186
|
+
getAttrs: (el) => pastedParagraphAttrs(el, level)
|
|
187
|
+
}))
|
|
188
|
+
],
|
|
91
189
|
toDOM(node) {
|
|
92
190
|
const attrs = node.attrs;
|
|
93
191
|
const style = paragraphStyle(attrs);
|
|
94
192
|
const tag = attrs.heading ? `h${attrs.heading}` : "p";
|
|
95
|
-
|
|
193
|
+
const dom = style ? { style } : {};
|
|
194
|
+
if (attrs.styleId) dom["data-style"] = attrs.styleId;
|
|
195
|
+
return [tag, dom, 0];
|
|
96
196
|
}
|
|
97
197
|
},
|
|
98
198
|
text: { group: "inline" },
|
|
@@ -132,9 +232,13 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
132
232
|
// Paint-only: the layout box stays axis-aligned.
|
|
133
233
|
rotation: { default: 0 }
|
|
134
234
|
},
|
|
235
|
+
parseDOM: [{ tag: "img[src]", getAttrs: pastedImageAttrs }],
|
|
135
236
|
toDOM(node) {
|
|
136
237
|
const a = node.attrs;
|
|
137
|
-
const attrs = {
|
|
238
|
+
const attrs = {
|
|
239
|
+
src: a["src"],
|
|
240
|
+
alt: a["alt"]
|
|
241
|
+
};
|
|
138
242
|
if (a["width"] != null) attrs["width"] = String(a["width"]);
|
|
139
243
|
if (a["height"] != null) attrs["height"] = String(a["height"]);
|
|
140
244
|
return ["img", attrs];
|
|
@@ -171,7 +275,11 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
171
275
|
align: { default: null }
|
|
172
276
|
},
|
|
173
277
|
parseDOM: [{ tag: "table" }],
|
|
174
|
-
toDOM: (node) => [
|
|
278
|
+
toDOM: (node) => [
|
|
279
|
+
"table",
|
|
280
|
+
node.attrs["borders"] ? { "data-borders": "1" } : {},
|
|
281
|
+
["tbody", 0]
|
|
282
|
+
]
|
|
175
283
|
},
|
|
176
284
|
table_row: {
|
|
177
285
|
content: "table_cell+",
|
|
@@ -207,9 +315,12 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
207
315
|
parseDOM: [{ tag: "td" }, { tag: "th" }],
|
|
208
316
|
toDOM(node) {
|
|
209
317
|
const attrs = {};
|
|
210
|
-
if (node.attrs["colspan"] !== 1)
|
|
211
|
-
|
|
212
|
-
if (node.attrs["
|
|
318
|
+
if (node.attrs["colspan"] !== 1)
|
|
319
|
+
attrs["colspan"] = String(node.attrs["colspan"]);
|
|
320
|
+
if (node.attrs["rowspan"] !== 1)
|
|
321
|
+
attrs["rowspan"] = String(node.attrs["rowspan"]);
|
|
322
|
+
if (node.attrs["background"])
|
|
323
|
+
attrs["style"] = `background-color: ${node.attrs["background"]}`;
|
|
213
324
|
return ["td", attrs, 0];
|
|
214
325
|
}
|
|
215
326
|
}
|
|
@@ -238,9 +349,15 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
238
349
|
// w:color — hex "#RRGGBB"
|
|
239
350
|
textColor: {
|
|
240
351
|
attrs: { color: {} },
|
|
241
|
-
parseDOM: [
|
|
352
|
+
parseDOM: [
|
|
353
|
+
{ style: "color", getAttrs: (value) => ({ color: value }) }
|
|
354
|
+
],
|
|
242
355
|
toDOM(mark) {
|
|
243
|
-
return [
|
|
356
|
+
return [
|
|
357
|
+
"span",
|
|
358
|
+
{ style: `color: ${mark.attrs["color"]}` },
|
|
359
|
+
0
|
|
360
|
+
];
|
|
244
361
|
}
|
|
245
362
|
},
|
|
246
363
|
// w:sz — size in points
|
|
@@ -256,7 +373,11 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
256
373
|
}
|
|
257
374
|
],
|
|
258
375
|
toDOM(mark) {
|
|
259
|
-
return [
|
|
376
|
+
return [
|
|
377
|
+
"span",
|
|
378
|
+
{ style: `font-size: ${mark.attrs["size"]}pt` },
|
|
379
|
+
0
|
|
380
|
+
];
|
|
260
381
|
}
|
|
261
382
|
},
|
|
262
383
|
// w:vertAlign — superscript / subscript
|
|
@@ -273,26 +394,51 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
273
394
|
highlight: {
|
|
274
395
|
attrs: { color: {} },
|
|
275
396
|
parseDOM: [
|
|
276
|
-
{
|
|
397
|
+
{
|
|
398
|
+
style: "background-color",
|
|
399
|
+
getAttrs: (value) => ({ color: value })
|
|
400
|
+
}
|
|
277
401
|
],
|
|
278
402
|
toDOM(mark) {
|
|
279
|
-
return [
|
|
403
|
+
return [
|
|
404
|
+
"span",
|
|
405
|
+
{ style: `background-color: ${mark.attrs["color"]}` },
|
|
406
|
+
0
|
|
407
|
+
];
|
|
280
408
|
}
|
|
281
409
|
},
|
|
282
410
|
// w:rFonts — font family
|
|
283
411
|
fontFamily: {
|
|
284
412
|
attrs: { family: {} },
|
|
285
|
-
parseDOM: [
|
|
413
|
+
parseDOM: [
|
|
414
|
+
{
|
|
415
|
+
style: "font-family",
|
|
416
|
+
getAttrs: (value) => ({ family: value })
|
|
417
|
+
}
|
|
418
|
+
],
|
|
286
419
|
toDOM(mark) {
|
|
287
|
-
return [
|
|
420
|
+
return [
|
|
421
|
+
"span",
|
|
422
|
+
{ style: `font-family: ${mark.attrs["family"]}` },
|
|
423
|
+
0
|
|
424
|
+
];
|
|
288
425
|
}
|
|
289
426
|
},
|
|
290
427
|
// w:hyperlink — external URL or "#anchor"
|
|
291
428
|
link: {
|
|
292
429
|
attrs: { href: {} },
|
|
293
430
|
inclusive: false,
|
|
431
|
+
parseDOM: [{ tag: "a[href]", getAttrs: pastedLinkAttrs }],
|
|
294
432
|
toDOM(mark) {
|
|
295
|
-
return [
|
|
433
|
+
return [
|
|
434
|
+
"a",
|
|
435
|
+
{
|
|
436
|
+
href: mark.attrs["href"],
|
|
437
|
+
rel: "noopener",
|
|
438
|
+
target: "_blank"
|
|
439
|
+
},
|
|
440
|
+
0
|
|
441
|
+
];
|
|
296
442
|
}
|
|
297
443
|
},
|
|
298
444
|
// w:footnoteReference — the carrier text is the superscript number; `num`
|
|
@@ -306,12 +452,20 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
306
452
|
// `el` is an HTMLElement at runtime; this package has no DOM lib, so
|
|
307
453
|
// narrow structurally rather than naming the type.
|
|
308
454
|
getAttrs: (el) => ({
|
|
309
|
-
num: Number(
|
|
455
|
+
num: Number(
|
|
456
|
+
el.getAttribute(
|
|
457
|
+
"data-footnote"
|
|
458
|
+
)
|
|
459
|
+
) || 0
|
|
310
460
|
})
|
|
311
461
|
}
|
|
312
462
|
],
|
|
313
463
|
toDOM(mark) {
|
|
314
|
-
return [
|
|
464
|
+
return [
|
|
465
|
+
"sup",
|
|
466
|
+
{ "data-footnote": String(mark.attrs["num"]) },
|
|
467
|
+
0
|
|
468
|
+
];
|
|
315
469
|
}
|
|
316
470
|
}
|
|
317
471
|
// The `comment` mark (w:commentRangeStart/End) is contributed by the comment
|
|
@@ -324,7 +478,12 @@ var schema = new import_prosemirror_model.Schema({
|
|
|
324
478
|
var commentSchema = new import_prosemirror_model.Schema({
|
|
325
479
|
nodes: {
|
|
326
480
|
doc: { content: "block+" },
|
|
327
|
-
paragraph: {
|
|
481
|
+
paragraph: {
|
|
482
|
+
group: "block",
|
|
483
|
+
content: "inline*",
|
|
484
|
+
parseDOM: [{ tag: "p" }],
|
|
485
|
+
toDOM: () => ["p", 0]
|
|
486
|
+
},
|
|
328
487
|
text: { group: "inline" },
|
|
329
488
|
// @mention: an inline atom carrying the mentioned user's id + display name.
|
|
330
489
|
// `leafText` lets textContent include "@Name" (search / plain-text preview).
|
|
@@ -345,7 +504,10 @@ var commentSchema = new import_prosemirror_model.Schema({
|
|
|
345
504
|
tag: "span.mention",
|
|
346
505
|
getAttrs: (el) => {
|
|
347
506
|
const e = el;
|
|
348
|
-
return {
|
|
507
|
+
return {
|
|
508
|
+
id: e.getAttribute("data-id"),
|
|
509
|
+
label: (e.textContent ?? "").replace(/^@/, "")
|
|
510
|
+
};
|
|
349
511
|
}
|
|
350
512
|
}
|
|
351
513
|
]
|
|
@@ -367,7 +529,8 @@ function paragraphStyle(attrs) {
|
|
|
367
529
|
if (sp) {
|
|
368
530
|
if (sp.before) parts.push(`margin-top: ${sp.before}px`);
|
|
369
531
|
if (sp.after) parts.push(`margin-bottom: ${sp.after}px`);
|
|
370
|
-
if (sp.line && sp.lineRule === "auto")
|
|
532
|
+
if (sp.line && sp.lineRule === "auto")
|
|
533
|
+
parts.push(`line-height: ${sp.line}`);
|
|
371
534
|
else if (sp.line) parts.push(`line-height: ${sp.line}px`);
|
|
372
535
|
}
|
|
373
536
|
return parts.join("; ");
|
|
@@ -783,11 +946,16 @@ function propsToMarks(p, ctx) {
|
|
|
783
946
|
if (p.italic) marks.push(ctx.schema.marks["em"].create());
|
|
784
947
|
if (p.underline) marks.push(ctx.schema.marks["underline"].create());
|
|
785
948
|
if (p.strike) marks.push(ctx.schema.marks["strike"].create());
|
|
786
|
-
if (p.color)
|
|
787
|
-
|
|
788
|
-
if (p.
|
|
789
|
-
|
|
790
|
-
if (p.
|
|
949
|
+
if (p.color)
|
|
950
|
+
marks.push(ctx.schema.marks["textColor"].create({ color: p.color }));
|
|
951
|
+
if (p.sizePt !== void 0)
|
|
952
|
+
marks.push(ctx.schema.marks["fontSize"].create({ size: p.sizePt }));
|
|
953
|
+
if (p.fontFamily)
|
|
954
|
+
marks.push(ctx.schema.marks["fontFamily"].create({ family: p.fontFamily }));
|
|
955
|
+
if (p.highlight)
|
|
956
|
+
marks.push(ctx.schema.marks["highlight"].create({ color: p.highlight }));
|
|
957
|
+
if (p.vertAlign)
|
|
958
|
+
marks.push(ctx.schema.marks["vertAlign"].create({ value: p.vertAlign }));
|
|
791
959
|
return marks;
|
|
792
960
|
}
|
|
793
961
|
var SYMBOL_MAP = {
|
|
@@ -807,7 +975,9 @@ function symbolChar(code) {
|
|
|
807
975
|
return Number.isNaN(n) ? "" : String.fromCodePoint(n >= 61440 ? n - 61440 + 32 : n);
|
|
808
976
|
}
|
|
809
977
|
function hasPageBreak(run) {
|
|
810
|
-
return run.children.some(
|
|
978
|
+
return run.children.some(
|
|
979
|
+
(n) => n.name === "w:br" && attrOf(n, "w:type") === "page"
|
|
980
|
+
);
|
|
811
981
|
}
|
|
812
982
|
function effectiveChildren(nodes) {
|
|
813
983
|
const out = [];
|
|
@@ -856,8 +1026,12 @@ function runInlineNodes(run, marks, ctx) {
|
|
|
856
1026
|
if (id && ctx.notes.bodies[kind].has(id)) {
|
|
857
1027
|
flush();
|
|
858
1028
|
const num = ctx.notes.ref(kind, id);
|
|
859
|
-
const refMarks = [
|
|
860
|
-
|
|
1029
|
+
const refMarks = [
|
|
1030
|
+
...marks,
|
|
1031
|
+
ctx.schema.marks["vertAlign"].create({ value: "super" })
|
|
1032
|
+
];
|
|
1033
|
+
if (kind === "footnote")
|
|
1034
|
+
refMarks.push(ctx.schema.marks["footnote"].create({ num }));
|
|
861
1035
|
out.push(ctx.schema.text(String(num), refMarks));
|
|
862
1036
|
}
|
|
863
1037
|
} else if (node.name === "w:br" && attrOf(node, "w:type") !== "page") {
|
|
@@ -881,9 +1055,11 @@ function parseAnchorFloat(drawing) {
|
|
|
881
1055
|
const posH = child(anchor, "wp:positionH");
|
|
882
1056
|
if (posH) {
|
|
883
1057
|
const align = child(posH, "wp:align")?.text.trim();
|
|
884
|
-
if (align === "left" || align === "right" || align === "center")
|
|
1058
|
+
if (align === "left" || align === "right" || align === "center")
|
|
1059
|
+
float["hAlign"] = align;
|
|
885
1060
|
const off = emuToPxZero(child(posH, "wp:posOffset")?.text);
|
|
886
|
-
if (off !== void 0 && float["hAlign"] === void 0)
|
|
1061
|
+
if (off !== void 0 && float["hAlign"] === void 0)
|
|
1062
|
+
float["hOffset"] = off;
|
|
887
1063
|
const rel = attrOf(posH, "relativeFrom");
|
|
888
1064
|
float["hRel"] = rel === "page" ? "page" : "margin";
|
|
889
1065
|
}
|
|
@@ -1060,7 +1236,9 @@ function parseShape(run, ctx) {
|
|
|
1060
1236
|
function parseTextbox(wsp, ctx) {
|
|
1061
1237
|
const content = child(child(wsp, "wps:txbx"), "w:txbxContent");
|
|
1062
1238
|
if (!content) return null;
|
|
1063
|
-
const paragraphs = children(content, "w:p").map(
|
|
1239
|
+
const paragraphs = children(content, "w:p").map(
|
|
1240
|
+
(p) => parseParagraph(p, ctx).toJSON()
|
|
1241
|
+
);
|
|
1064
1242
|
if (paragraphs.length === 0) return null;
|
|
1065
1243
|
const bodyPr = child(wsp, "wps:bodyPr");
|
|
1066
1244
|
const ins = (name) => emuToPxZero(attrOf(bodyPr, name));
|
|
@@ -1155,7 +1333,10 @@ function flattenOmml(node) {
|
|
|
1155
1333
|
function parseParagraph(p, ctx) {
|
|
1156
1334
|
const pPr = child(p, "w:pPr");
|
|
1157
1335
|
const pStyleId = attrOf(child(pPr, "w:pStyle"), "w:val");
|
|
1158
|
-
const paraBase = mergeRunProps(
|
|
1336
|
+
const paraBase = mergeRunProps(
|
|
1337
|
+
ctx.styles.docDefaults,
|
|
1338
|
+
ctx.styles.resolveStyle(pStyleId)
|
|
1339
|
+
);
|
|
1159
1340
|
const pPrChain = [
|
|
1160
1341
|
ctx.styles.docDefaultsPPr,
|
|
1161
1342
|
...ctx.styles.resolveStylePPr(pStyleId),
|
|
@@ -1188,9 +1369,12 @@ function parseParagraph(p, ctx) {
|
|
|
1188
1369
|
} else if (fldType === "end") {
|
|
1189
1370
|
const kind = fieldKind(field.instr);
|
|
1190
1371
|
if (kind) {
|
|
1191
|
-
inline.push(
|
|
1372
|
+
inline.push(
|
|
1373
|
+
pageFieldNode(kind, field.resultRuns[0], paraBase, ctx)
|
|
1374
|
+
);
|
|
1192
1375
|
} else {
|
|
1193
|
-
for (const r of field.resultRuns)
|
|
1376
|
+
for (const r of field.resultRuns)
|
|
1377
|
+
inline.push(...runToInline(r, paraBase, ctx, null));
|
|
1194
1378
|
}
|
|
1195
1379
|
field = null;
|
|
1196
1380
|
} else if (field.phase === "instr") {
|
|
@@ -1207,7 +1391,8 @@ function parseParagraph(p, ctx) {
|
|
|
1207
1391
|
if (kind) {
|
|
1208
1392
|
inline.push(pageFieldNode(kind, resultRuns[0], paraBase, ctx));
|
|
1209
1393
|
} else {
|
|
1210
|
-
for (const r of resultRuns)
|
|
1394
|
+
for (const r of resultRuns)
|
|
1395
|
+
inline.push(...runToInline(r, paraBase, ctx, null));
|
|
1211
1396
|
}
|
|
1212
1397
|
} else if (node.name === "w:hyperlink") {
|
|
1213
1398
|
const rel = attrOf(node, "r:id") ? ctx.rels.get(attrOf(node, "r:id")) : void 0;
|
|
@@ -1220,7 +1405,9 @@ function parseParagraph(p, ctx) {
|
|
|
1220
1405
|
const text = flattenOmml(node);
|
|
1221
1406
|
if (text.length > 0) {
|
|
1222
1407
|
const first = findDescendant(node, "m:r");
|
|
1223
|
-
inline.push(
|
|
1408
|
+
inline.push(
|
|
1409
|
+
ctx.schema.text(text, runMarks(first, paraBase, ctx, null))
|
|
1410
|
+
);
|
|
1224
1411
|
}
|
|
1225
1412
|
} else if (node.name === "w:commentRangeStart") {
|
|
1226
1413
|
const id = Number(attrOf(node, "w:id"));
|
|
@@ -1237,6 +1424,9 @@ function parseParagraph(p, ctx) {
|
|
|
1237
1424
|
if (list) attrs.list = list;
|
|
1238
1425
|
if (align) attrs.align = align;
|
|
1239
1426
|
if (heading) attrs.heading = heading;
|
|
1427
|
+
else if (pStyleId && /^(title|subtitle)$/i.test(pStyleId)) {
|
|
1428
|
+
attrs.styleId = pStyleId.toLowerCase() === "title" ? "Title" : "Subtitle";
|
|
1429
|
+
}
|
|
1240
1430
|
if (indent) attrs.indent = indent;
|
|
1241
1431
|
if (spacing) attrs.spacing = spacing;
|
|
1242
1432
|
if (tabs) attrs.tabs = tabs;
|
|
@@ -1349,7 +1539,9 @@ function parseList(pPr) {
|
|
|
1349
1539
|
return { numId, level: Number.isNaN(ilvl) ? 0 : ilvl };
|
|
1350
1540
|
}
|
|
1351
1541
|
function emptyCell(ctx) {
|
|
1352
|
-
return ctx.schema.nodes["table_cell"].create(null, [
|
|
1542
|
+
return ctx.schema.nodes["table_cell"].create(null, [
|
|
1543
|
+
ctx.schema.nodes["paragraph"].create()
|
|
1544
|
+
]);
|
|
1353
1545
|
}
|
|
1354
1546
|
function parseCellMargins(tbl) {
|
|
1355
1547
|
const mar = child(child(tbl, "w:tblPr"), "w:tblCellMar");
|
|
@@ -1402,7 +1594,14 @@ function parseBordersEl(bordersEl, sides) {
|
|
|
1402
1594
|
}
|
|
1403
1595
|
return Object.keys(out).length > 0 ? out : null;
|
|
1404
1596
|
}
|
|
1405
|
-
var TABLE_SIDES = [
|
|
1597
|
+
var TABLE_SIDES = [
|
|
1598
|
+
"top",
|
|
1599
|
+
"bottom",
|
|
1600
|
+
"left",
|
|
1601
|
+
"right",
|
|
1602
|
+
"insideH",
|
|
1603
|
+
"insideV"
|
|
1604
|
+
];
|
|
1406
1605
|
var CELL_SIDES = ["top", "bottom", "left", "right"];
|
|
1407
1606
|
function parseTableBorders(tbl, ctx) {
|
|
1408
1607
|
const tblPr = child(tbl, "w:tblPr");
|
|
@@ -1429,8 +1628,18 @@ function parseTable(tbl, ctx) {
|
|
|
1429
1628
|
const vAlign = vAlignVal === "center" || vAlignVal === "bottom" ? vAlignVal : null;
|
|
1430
1629
|
const borders2 = parseBordersEl(child(tcPr, "w:tcBorders"), CELL_SIDES);
|
|
1431
1630
|
const content = parseBlocks(tc, ctx);
|
|
1432
|
-
if (content.length === 0)
|
|
1433
|
-
|
|
1631
|
+
if (content.length === 0)
|
|
1632
|
+
content.push(ctx.schema.nodes["paragraph"].create());
|
|
1633
|
+
cells.push({
|
|
1634
|
+
startCol: col,
|
|
1635
|
+
colspan,
|
|
1636
|
+
vMerge,
|
|
1637
|
+
colwidth: widths.length ? widths : null,
|
|
1638
|
+
background,
|
|
1639
|
+
vAlign,
|
|
1640
|
+
borders: borders2,
|
|
1641
|
+
content
|
|
1642
|
+
});
|
|
1434
1643
|
col += colspan;
|
|
1435
1644
|
}
|
|
1436
1645
|
return cells;
|
|
@@ -1441,12 +1650,17 @@ function parseTable(tbl, ctx) {
|
|
|
1441
1650
|
const header = hdr ? attrOf(hdr, "w:val") !== "false" && attrOf(hdr, "w:val") !== "0" : false;
|
|
1442
1651
|
const trH = child(trPr, "w:trHeight");
|
|
1443
1652
|
const hv = attrOf(trH, "w:val");
|
|
1444
|
-
const height = hv !== void 0 ? {
|
|
1653
|
+
const height = hv !== void 0 ? {
|
|
1654
|
+
value: twipsToPx(Number(hv)),
|
|
1655
|
+
exact: attrOf(trH, "w:hRule") === "exact"
|
|
1656
|
+
} : null;
|
|
1445
1657
|
const cs = child(trPr, "w:cantSplit");
|
|
1446
1658
|
const cantSplit = cs ? attrOf(cs, "w:val") !== "false" && attrOf(cs, "w:val") !== "0" : false;
|
|
1447
1659
|
return { header, height, cantSplit };
|
|
1448
1660
|
});
|
|
1449
|
-
const colIndex = logicalRows.map(
|
|
1661
|
+
const colIndex = logicalRows.map(
|
|
1662
|
+
(cells) => new Map(cells.map((c) => [c.startCol, c]))
|
|
1663
|
+
);
|
|
1450
1664
|
const rows = logicalRows.map((cells, r) => {
|
|
1451
1665
|
const emitted = [];
|
|
1452
1666
|
for (const cell of cells) {
|
|
@@ -1487,7 +1701,8 @@ function parseTable(tbl, ctx) {
|
|
|
1487
1701
|
const attrs = {};
|
|
1488
1702
|
if (cellPadding) attrs["cellPadding"] = cellPadding;
|
|
1489
1703
|
if (borders) attrs["borders"] = borders;
|
|
1490
|
-
if (jc === "center" || jc === "right" || jc === "end")
|
|
1704
|
+
if (jc === "center" || jc === "right" || jc === "end")
|
|
1705
|
+
attrs["align"] = jc === "end" ? "right" : jc;
|
|
1491
1706
|
return ctx.schema.nodes["table"].create(
|
|
1492
1707
|
Object.keys(attrs).length > 0 ? attrs : null,
|
|
1493
1708
|
rows.length > 0 ? rows : [ctx.schema.nodes["table_row"].create(null, [emptyCell(ctx)])]
|
|
@@ -1553,18 +1768,27 @@ async function readPartRels(zip, partPath) {
|
|
|
1553
1768
|
return xml ? parseXml(xml) : void 0;
|
|
1554
1769
|
}
|
|
1555
1770
|
async function buildNotesRegistry(zip) {
|
|
1556
|
-
const bodies = {
|
|
1771
|
+
const bodies = {
|
|
1772
|
+
footnote: /* @__PURE__ */ new Map(),
|
|
1773
|
+
endnote: /* @__PURE__ */ new Map()
|
|
1774
|
+
};
|
|
1557
1775
|
const load = async (path, root, tag, into) => {
|
|
1558
1776
|
const xml = await readPart(zip, path);
|
|
1559
1777
|
if (!xml) return;
|
|
1560
1778
|
for (const note of children(child(parseXml(xml), root), tag)) {
|
|
1561
1779
|
const id = attrOf(note, "w:id");
|
|
1562
1780
|
const type = attrOf(note, "w:type");
|
|
1563
|
-
if (id === void 0 || Number(id) < 1 || type && type !== "normal")
|
|
1781
|
+
if (id === void 0 || Number(id) < 1 || type && type !== "normal")
|
|
1782
|
+
continue;
|
|
1564
1783
|
into.set(id, note);
|
|
1565
1784
|
}
|
|
1566
1785
|
};
|
|
1567
|
-
await load(
|
|
1786
|
+
await load(
|
|
1787
|
+
"word/footnotes.xml",
|
|
1788
|
+
"w:footnotes",
|
|
1789
|
+
"w:footnote",
|
|
1790
|
+
bodies.footnote
|
|
1791
|
+
);
|
|
1568
1792
|
await load("word/endnotes.xml", "w:endnotes", "w:endnote", bodies.endnote);
|
|
1569
1793
|
const reg = {
|
|
1570
1794
|
bodies,
|
|
@@ -1599,7 +1823,10 @@ async function buildCommentsRegistry(zip) {
|
|
|
1599
1823
|
const ext = /* @__PURE__ */ new Map();
|
|
1600
1824
|
const extXml = await readPart(zip, "word/commentsExtended.xml");
|
|
1601
1825
|
if (extXml) {
|
|
1602
|
-
for (const ex of children(
|
|
1826
|
+
for (const ex of children(
|
|
1827
|
+
child(parseXml(extXml), "w15:commentsEx"),
|
|
1828
|
+
"w15:commentEx"
|
|
1829
|
+
)) {
|
|
1603
1830
|
const paraId2 = attrOf(ex, "w15:paraId");
|
|
1604
1831
|
if (!paraId2) continue;
|
|
1605
1832
|
const done = attrOf(ex, "w15:done");
|
|
@@ -1619,14 +1846,24 @@ function buildCommentsList(ctx) {
|
|
|
1619
1846
|
const out = [];
|
|
1620
1847
|
for (const id of ctx.comments.used) {
|
|
1621
1848
|
const def = ctx.comments.defs.get(id);
|
|
1622
|
-
if (def)
|
|
1849
|
+
if (def)
|
|
1850
|
+
out.push({
|
|
1851
|
+
id,
|
|
1852
|
+
author: def.author,
|
|
1853
|
+
date: def.date,
|
|
1854
|
+
text: collectText(def.body).trim()
|
|
1855
|
+
});
|
|
1623
1856
|
}
|
|
1624
1857
|
return out;
|
|
1625
1858
|
}
|
|
1626
1859
|
function commentBodyJSON(comment) {
|
|
1627
1860
|
const paras = children(comment, "w:p").map((p) => {
|
|
1628
1861
|
const text = collectText(p);
|
|
1629
|
-
return commentSchema.node(
|
|
1862
|
+
return commentSchema.node(
|
|
1863
|
+
"paragraph",
|
|
1864
|
+
null,
|
|
1865
|
+
text ? [commentSchema.text(text)] : []
|
|
1866
|
+
);
|
|
1630
1867
|
});
|
|
1631
1868
|
return commentSchema.node("doc", null, paras.length ? paras : [commentSchema.node("paragraph")]).toJSON();
|
|
1632
1869
|
}
|
|
@@ -1646,12 +1883,18 @@ function buildCommentNodes(ctx) {
|
|
|
1646
1883
|
if (usedSet.has(cur)) return true;
|
|
1647
1884
|
return false;
|
|
1648
1885
|
};
|
|
1649
|
-
const ids = [
|
|
1886
|
+
const ids = [
|
|
1887
|
+
...used.filter((id) => defs.has(id)),
|
|
1888
|
+
...[...defs.keys()].filter((id) => !usedSet.has(id))
|
|
1889
|
+
];
|
|
1650
1890
|
const out = [];
|
|
1651
1891
|
for (const id of ids) {
|
|
1652
1892
|
const def = defs.get(id);
|
|
1653
1893
|
if (!def || !referenced(id)) continue;
|
|
1654
|
-
const user = {
|
|
1894
|
+
const user = {
|
|
1895
|
+
id: def.author || "unknown",
|
|
1896
|
+
name: def.author || "Unknown"
|
|
1897
|
+
};
|
|
1655
1898
|
out.push({
|
|
1656
1899
|
id,
|
|
1657
1900
|
parentId: parentOf.get(id) ?? null,
|
|
@@ -1665,7 +1908,9 @@ function buildCommentNodes(ctx) {
|
|
|
1665
1908
|
}
|
|
1666
1909
|
function noteBlocks(note, num, ctx) {
|
|
1667
1910
|
const blocks = parseBlocks(note, ctx);
|
|
1668
|
-
const marker = ctx.schema.text(`${num}. `, [
|
|
1911
|
+
const marker = ctx.schema.text(`${num}. `, [
|
|
1912
|
+
ctx.schema.marks["vertAlign"].create({ value: "super" })
|
|
1913
|
+
]);
|
|
1669
1914
|
const first = blocks[0];
|
|
1670
1915
|
if (first && first.type.name === "paragraph") {
|
|
1671
1916
|
const kids = [marker];
|
|
@@ -1715,7 +1960,10 @@ async function extractMedia(zip) {
|
|
|
1715
1960
|
if (!path.startsWith("word/media/")) continue;
|
|
1716
1961
|
const entry = zip.file(path);
|
|
1717
1962
|
if (!entry || entry.dir) continue;
|
|
1718
|
-
media.set(
|
|
1963
|
+
media.set(
|
|
1964
|
+
path,
|
|
1965
|
+
`data:${mimeOf(path)};base64,${await entry.async("base64")}`
|
|
1966
|
+
);
|
|
1719
1967
|
}
|
|
1720
1968
|
return media;
|
|
1721
1969
|
}
|
|
@@ -1725,11 +1973,16 @@ async function importDocx(input, opts) {
|
|
|
1725
1973
|
if (rawDocumentXml === void 0) {
|
|
1726
1974
|
throw new Error("bapbong-docx: word/document.xml not found in archive");
|
|
1727
1975
|
}
|
|
1728
|
-
const
|
|
1976
|
+
const stylesXml2 = await readPart(zip, "word/styles.xml");
|
|
1729
1977
|
const numberingXml = await readPart(zip, "word/numbering.xml");
|
|
1730
1978
|
const themeXml = await readPart(zip, "word/theme/theme1.xml");
|
|
1731
|
-
const resolveTheme = buildThemeResolver(
|
|
1732
|
-
|
|
1979
|
+
const resolveTheme = buildThemeResolver(
|
|
1980
|
+
themeXml ? parseXml(themeXml) : void 0
|
|
1981
|
+
);
|
|
1982
|
+
const styles = buildStyleRegistry(
|
|
1983
|
+
stylesXml2 ? parseXml(stylesXml2) : void 0,
|
|
1984
|
+
resolveTheme
|
|
1985
|
+
);
|
|
1733
1986
|
const numberingRoot = numberingXml ? parseXml(numberingXml) : void 0;
|
|
1734
1987
|
const media = await extractMedia(zip);
|
|
1735
1988
|
const notes = await buildNotesRegistry(zip);
|
|
@@ -1778,7 +2031,11 @@ async function importDocx(input, opts) {
|
|
|
1778
2031
|
if (!xml) continue;
|
|
1779
2032
|
const partCtx = makeCtx(buildRels(await readPartRels(zip, partPath)));
|
|
1780
2033
|
const el = child(parseXml(xml), root);
|
|
1781
|
-
store[type] = storyDoc(
|
|
2034
|
+
store[type] = storyDoc(
|
|
2035
|
+
partCtx,
|
|
2036
|
+
el ? parseBlocks(el, partCtx) : [],
|
|
2037
|
+
ctx.numbering.defs
|
|
2038
|
+
);
|
|
1782
2039
|
}
|
|
1783
2040
|
};
|
|
1784
2041
|
await collect("w:headerReference", headers, "w:hdr");
|
|
@@ -1851,7 +2108,10 @@ var pxToEmu = (px) => Math.round(px * 9525);
|
|
|
1851
2108
|
var commentIdsOf = (node) => node.marks.find((m) => m.type.name === "comment")?.attrs["ids"] ?? [];
|
|
1852
2109
|
var isInlineLeaf = (node) => node.isText || node.type.name === "image" || node.type.name === "hard_break";
|
|
1853
2110
|
function esc(s) {
|
|
1854
|
-
return s.replace(
|
|
2111
|
+
return s.replace(
|
|
2112
|
+
/[&<>"]/g,
|
|
2113
|
+
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]
|
|
2114
|
+
);
|
|
1855
2115
|
}
|
|
1856
2116
|
var MIME_EXT = {
|
|
1857
2117
|
"image/png": "png",
|
|
@@ -1874,9 +2134,15 @@ function runProps(marks) {
|
|
|
1874
2134
|
const size = byName.get("fontSize")?.attrs["size"];
|
|
1875
2135
|
if (size != null) out.push(`<w:sz w:val="${Math.round(size * 2)}"/>`);
|
|
1876
2136
|
const hl = byName.get("highlight")?.attrs["color"];
|
|
1877
|
-
if (hl)
|
|
2137
|
+
if (hl)
|
|
2138
|
+
out.push(
|
|
2139
|
+
`<w:shd w:val="clear" w:color="auto" w:fill="${hl.replace(/^#/, "")}"/>`
|
|
2140
|
+
);
|
|
1878
2141
|
const va = byName.get("vertAlign")?.attrs["value"];
|
|
1879
|
-
if (va)
|
|
2142
|
+
if (va)
|
|
2143
|
+
out.push(
|
|
2144
|
+
`<w:vertAlign w:val="${va === "sub" ? "subscript" : "superscript"}"/>`
|
|
2145
|
+
);
|
|
1880
2146
|
return out.length ? `<w:rPr>${out.join("")}</w:rPr>` : "";
|
|
1881
2147
|
}
|
|
1882
2148
|
function anchorXml(float, cx, cy, n, graphic) {
|
|
@@ -1918,7 +2184,9 @@ function imageXml(node, ctx) {
|
|
|
1918
2184
|
const n = ctx.nextId++;
|
|
1919
2185
|
const rid = `rId${n}`;
|
|
1920
2186
|
ctx.media.push({ path: `word/media/image${n}.${ext}`, base64: m[2] });
|
|
1921
|
-
ctx.rels.push(
|
|
2187
|
+
ctx.rels.push(
|
|
2188
|
+
`<Relationship Id="${rid}" Type="${R_NS}/image" Target="media/image${n}.${ext}"/>`
|
|
2189
|
+
);
|
|
1922
2190
|
const cx = pxToEmu(node.attrs["width"] ?? 96);
|
|
1923
2191
|
const cy = pxToEmu(node.attrs["height"] ?? 96);
|
|
1924
2192
|
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>`;
|
|
@@ -1928,7 +2196,8 @@ function inlineXml(node, ctx) {
|
|
|
1928
2196
|
if (node.type.name === "image") return imageXml(node, ctx);
|
|
1929
2197
|
if (node.isText) {
|
|
1930
2198
|
const fn = node.marks.find((m) => m.type.name === "footnote");
|
|
1931
|
-
if (fn)
|
|
2199
|
+
if (fn)
|
|
2200
|
+
return `<w:r>${runProps(node.marks)}<w:footnoteReference w:id="${fn.attrs["num"]}"/></w:r>`;
|
|
1932
2201
|
return `<w:r>${runProps(node.marks)}<w:t xml:space="preserve">${esc(node.text ?? "")}</w:t></w:r>`;
|
|
1933
2202
|
}
|
|
1934
2203
|
return "";
|
|
@@ -1938,9 +2207,12 @@ function inlineUnit(node, ctx) {
|
|
|
1938
2207
|
const inner = inlineXml(node, ctx);
|
|
1939
2208
|
const href = linkHref(node);
|
|
1940
2209
|
if (!href || !inner) return inner;
|
|
1941
|
-
if (href.startsWith("#"))
|
|
2210
|
+
if (href.startsWith("#"))
|
|
2211
|
+
return `<w:hyperlink w:anchor="${esc(href.slice(1))}">${inner}</w:hyperlink>`;
|
|
1942
2212
|
const n = ctx.nextId++;
|
|
1943
|
-
ctx.rels.push(
|
|
2213
|
+
ctx.rels.push(
|
|
2214
|
+
`<Relationship Id="rId${n}" Type="${R_NS}/hyperlink" Target="${esc(href)}" TargetMode="External"/>`
|
|
2215
|
+
);
|
|
1944
2216
|
return `<w:hyperlink r:id="rId${n}">${inner}</w:hyperlink>`;
|
|
1945
2217
|
}
|
|
1946
2218
|
function inlineContent(node, ctx) {
|
|
@@ -1974,10 +2246,16 @@ function paraProps(node) {
|
|
|
1974
2246
|
const a = node.attrs;
|
|
1975
2247
|
const out = [];
|
|
1976
2248
|
const heading = a["heading"];
|
|
2249
|
+
const styleId = a["styleId"];
|
|
1977
2250
|
if (heading) out.push(`<w:pStyle w:val="Heading${heading}"/>`);
|
|
2251
|
+
else if (styleId === "Title" || styleId === "Subtitle")
|
|
2252
|
+
out.push(`<w:pStyle w:val="${styleId}"/>`);
|
|
1978
2253
|
if (a["pageBreakBefore"]) out.push("<w:pageBreakBefore/>");
|
|
1979
2254
|
const list = a["list"];
|
|
1980
|
-
if (list)
|
|
2255
|
+
if (list)
|
|
2256
|
+
out.push(
|
|
2257
|
+
`<w:numPr><w:ilvl w:val="${list.level}"/><w:numId w:val="${esc(list.numId)}"/></w:numPr>`
|
|
2258
|
+
);
|
|
1981
2259
|
const sp = a["spacing"];
|
|
1982
2260
|
if (sp) {
|
|
1983
2261
|
const at = [];
|
|
@@ -1985,7 +2263,9 @@ function paraProps(node) {
|
|
|
1985
2263
|
if (sp.after != null) at.push(`w:after="${pxToTwips(sp.after)}"`);
|
|
1986
2264
|
if (sp.line != null) {
|
|
1987
2265
|
const auto = sp.lineRule === "auto" || sp.lineRule == null;
|
|
1988
|
-
at.push(
|
|
2266
|
+
at.push(
|
|
2267
|
+
`w:line="${auto ? Math.round(sp.line * 240) : pxToTwips(sp.line)}"`
|
|
2268
|
+
);
|
|
1989
2269
|
at.push(`w:lineRule="${sp.lineRule ?? "auto"}"`);
|
|
1990
2270
|
}
|
|
1991
2271
|
if (at.length) out.push(`<w:spacing ${at.join(" ")}/>`);
|
|
@@ -1996,11 +2276,13 @@ function paraProps(node) {
|
|
|
1996
2276
|
if (ind.left != null) at.push(`w:left="${pxToTwips(ind.left)}"`);
|
|
1997
2277
|
if (ind.right != null) at.push(`w:right="${pxToTwips(ind.right)}"`);
|
|
1998
2278
|
if (ind.hanging != null) at.push(`w:hanging="${pxToTwips(ind.hanging)}"`);
|
|
1999
|
-
else if (ind.firstLine != null)
|
|
2279
|
+
else if (ind.firstLine != null)
|
|
2280
|
+
at.push(`w:firstLine="${pxToTwips(ind.firstLine)}"`);
|
|
2000
2281
|
if (at.length) out.push(`<w:ind ${at.join(" ")}/>`);
|
|
2001
2282
|
}
|
|
2002
2283
|
const align = a["align"];
|
|
2003
|
-
if (align)
|
|
2284
|
+
if (align)
|
|
2285
|
+
out.push(`<w:jc w:val="${align === "justify" ? "both" : align}"/>`);
|
|
2004
2286
|
return out.join("");
|
|
2005
2287
|
}
|
|
2006
2288
|
function paragraphXml(node, ctx, sectPr = "") {
|
|
@@ -2008,7 +2290,14 @@ function paragraphXml(node, ctx, sectPr = "") {
|
|
|
2008
2290
|
const pPr = props ? `<w:pPr>${props}</w:pPr>` : "";
|
|
2009
2291
|
return `<w:p>${pPr}${inlineContent(node, ctx)}</w:p>`;
|
|
2010
2292
|
}
|
|
2011
|
-
var TABLE_SIDES2 = [
|
|
2293
|
+
var TABLE_SIDES2 = [
|
|
2294
|
+
"top",
|
|
2295
|
+
"bottom",
|
|
2296
|
+
"left",
|
|
2297
|
+
"right",
|
|
2298
|
+
"insideH",
|
|
2299
|
+
"insideV"
|
|
2300
|
+
];
|
|
2012
2301
|
var CELL_SIDES2 = ["top", "bottom", "left", "right"];
|
|
2013
2302
|
var BORDER_STYLE_OUT = {
|
|
2014
2303
|
solid: "single",
|
|
@@ -2030,12 +2319,19 @@ function cellXml(cell, ctx) {
|
|
|
2030
2319
|
const a = cell.attrs;
|
|
2031
2320
|
const pr = [];
|
|
2032
2321
|
const colwidth = a["colwidth"];
|
|
2033
|
-
if (colwidth?.length)
|
|
2034
|
-
|
|
2322
|
+
if (colwidth?.length)
|
|
2323
|
+
pr.push(
|
|
2324
|
+
`<w:tcW w:w="${pxToTwips(colwidth.reduce((x, y) => x + y, 0))}" w:type="dxa"/>`
|
|
2325
|
+
);
|
|
2326
|
+
if (a["colspan"] > 1)
|
|
2327
|
+
pr.push(`<w:gridSpan w:val="${a["colspan"]}"/>`);
|
|
2035
2328
|
const borders = a["borders"];
|
|
2036
2329
|
if (borders) pr.push(bordersXml("w:tcBorders", borders, CELL_SIDES2));
|
|
2037
2330
|
const bg = a["background"];
|
|
2038
|
-
if (bg)
|
|
2331
|
+
if (bg)
|
|
2332
|
+
pr.push(
|
|
2333
|
+
`<w:shd w:val="clear" w:color="auto" w:fill="${bg.replace(/^#/, "")}"/>`
|
|
2334
|
+
);
|
|
2039
2335
|
const vAlign = a["vAlign"];
|
|
2040
2336
|
if (vAlign) pr.push(`<w:vAlign w:val="${vAlign}"/>`);
|
|
2041
2337
|
let content = "";
|
|
@@ -2048,7 +2344,10 @@ function rowXml(row, ctx) {
|
|
|
2048
2344
|
if (row.attrs["header"]) pr.push("<w:tblHeader/>");
|
|
2049
2345
|
if (row.attrs["cantSplit"]) pr.push("<w:cantSplit/>");
|
|
2050
2346
|
const h = row.attrs["height"];
|
|
2051
|
-
if (h)
|
|
2347
|
+
if (h)
|
|
2348
|
+
pr.push(
|
|
2349
|
+
`<w:trHeight w:val="${pxToTwips(h.value)}" w:hRule="${h.exact ? "exact" : "atLeast"}"/>`
|
|
2350
|
+
);
|
|
2052
2351
|
const trPr = pr.length ? `<w:trPr>${pr.join("")}</w:trPr>` : "";
|
|
2053
2352
|
let cells = "";
|
|
2054
2353
|
row.forEach((c) => cells += cellXml(c, ctx));
|
|
@@ -2067,7 +2366,9 @@ function tableXml(node, ctx) {
|
|
|
2067
2366
|
}
|
|
2068
2367
|
const firstRow = node.firstChild;
|
|
2069
2368
|
const grid = [];
|
|
2070
|
-
firstRow?.forEach(
|
|
2369
|
+
firstRow?.forEach(
|
|
2370
|
+
(c) => c.attrs["colwidth"]?.forEach((w) => grid.push(w))
|
|
2371
|
+
);
|
|
2071
2372
|
const gridXml = grid.length ? `<w:tblGrid>${grid.map((w) => `<w:gridCol w:w="${pxToTwips(w)}"/>`).join("")}</w:tblGrid>` : "";
|
|
2072
2373
|
let rows = "";
|
|
2073
2374
|
node.forEach((r) => rows += rowXml(r, ctx));
|
|
@@ -2102,10 +2403,14 @@ function commentBodyXml(body, firstParaId) {
|
|
|
2102
2403
|
doc.forEach((p, _o, i) => {
|
|
2103
2404
|
let runs = "";
|
|
2104
2405
|
p.forEach((inline) => {
|
|
2105
|
-
if (inline.isText)
|
|
2106
|
-
|
|
2406
|
+
if (inline.isText)
|
|
2407
|
+
runs += `<w:r><w:t xml:space="preserve">${esc(inline.text ?? "")}</w:t></w:r>`;
|
|
2408
|
+
else if (inline.type.name === "mention")
|
|
2409
|
+
runs += `<w:r><w:t xml:space="preserve">@${esc(String(inline.attrs["label"] ?? ""))}</w:t></w:r>`;
|
|
2107
2410
|
});
|
|
2108
|
-
ps.push(
|
|
2411
|
+
ps.push(
|
|
2412
|
+
`<w:p${i === 0 ? ` w14:paraId="${firstParaId}"` : ""}>${runs}</w:p>`
|
|
2413
|
+
);
|
|
2109
2414
|
});
|
|
2110
2415
|
return ps.join("") || `<w:p w14:paraId="${firstParaId}"/>`;
|
|
2111
2416
|
}
|
|
@@ -2127,16 +2432,65 @@ function commentsExtendedXml(comments) {
|
|
|
2127
2432
|
}
|
|
2128
2433
|
var ROOT_RELS = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2129
2434
|
<Relationships xmlns="${PR_NS}"><Relationship Id="rId1" Type="${R_NS}/officeDocument" Target="word/document.xml"/></Relationships>`;
|
|
2435
|
+
var STYLE_DEFS = (() => {
|
|
2436
|
+
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>`;
|
|
2437
|
+
return {
|
|
2438
|
+
Heading1: heading(1, 48),
|
|
2439
|
+
Heading2: heading(2, 36),
|
|
2440
|
+
Heading3: heading(3, 28),
|
|
2441
|
+
Heading4: heading(4, 24),
|
|
2442
|
+
Heading5: heading(5, 22),
|
|
2443
|
+
Heading6: heading(6, 22),
|
|
2444
|
+
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>`,
|
|
2445
|
+
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>`
|
|
2446
|
+
};
|
|
2447
|
+
})();
|
|
2448
|
+
function usedStyleIds(doc) {
|
|
2449
|
+
const used = /* @__PURE__ */ new Set();
|
|
2450
|
+
doc.descendants((n) => {
|
|
2451
|
+
if (n.type.name !== "paragraph") return;
|
|
2452
|
+
const heading = n.attrs["heading"];
|
|
2453
|
+
const styleId = n.attrs["styleId"];
|
|
2454
|
+
if (heading) used.add(`Heading${heading}`);
|
|
2455
|
+
else if (styleId && STYLE_DEFS[styleId]) used.add(styleId);
|
|
2456
|
+
});
|
|
2457
|
+
return used;
|
|
2458
|
+
}
|
|
2459
|
+
function stylesXml(used) {
|
|
2460
|
+
const defs = [...used].map((id) => STYLE_DEFS[id]).join("");
|
|
2461
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2462
|
+
<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>`;
|
|
2463
|
+
}
|
|
2464
|
+
function mergeStyles(xml, used) {
|
|
2465
|
+
const missing = [...used].filter((id) => !xml.includes(`w:styleId="${id}"`));
|
|
2466
|
+
if (!missing.length) return xml;
|
|
2467
|
+
return xml.replace(
|
|
2468
|
+
"</w:styles>",
|
|
2469
|
+
`${missing.map((id) => STYLE_DEFS[id]).join("")}</w:styles>`
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2130
2472
|
function contentTypes(exts, hasComments) {
|
|
2131
2473
|
const parts = [
|
|
2132
2474
|
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
|
|
2133
2475
|
'<Default Extension="xml" ContentType="application/xml"/>'
|
|
2134
2476
|
];
|
|
2135
|
-
for (const ext of exts)
|
|
2136
|
-
|
|
2477
|
+
for (const ext of exts)
|
|
2478
|
+
parts.push(
|
|
2479
|
+
`<Default Extension="${ext}" ContentType="image/${ext === "jpg" ? "jpeg" : ext}"/>`
|
|
2480
|
+
);
|
|
2481
|
+
parts.push(
|
|
2482
|
+
'<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
|
|
2483
|
+
);
|
|
2484
|
+
parts.push(
|
|
2485
|
+
'<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>'
|
|
2486
|
+
);
|
|
2137
2487
|
if (hasComments) {
|
|
2138
|
-
parts.push(
|
|
2139
|
-
|
|
2488
|
+
parts.push(
|
|
2489
|
+
'<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>'
|
|
2490
|
+
);
|
|
2491
|
+
parts.push(
|
|
2492
|
+
'<Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"/>'
|
|
2493
|
+
);
|
|
2140
2494
|
}
|
|
2141
2495
|
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2142
2496
|
<Types xmlns="${CT_NS}">${parts.join("")}</Types>`;
|
|
@@ -2144,17 +2498,30 @@ function contentTypes(exts, hasComments) {
|
|
|
2144
2498
|
function mergeContentTypes(xml, exts, hasComments) {
|
|
2145
2499
|
let out = xml;
|
|
2146
2500
|
const add = (frag, key) => {
|
|
2147
|
-
if (!out.includes(`"${key}"`))
|
|
2501
|
+
if (!out.includes(`"${key}"`))
|
|
2502
|
+
out = out.replace("</Types>", `${frag}</Types>`);
|
|
2148
2503
|
};
|
|
2149
|
-
for (const ext of exts)
|
|
2504
|
+
for (const ext of exts)
|
|
2505
|
+
add(
|
|
2506
|
+
`<Default Extension="${ext}" ContentType="image/${ext === "jpg" ? "jpeg" : ext}"/>`,
|
|
2507
|
+
ext
|
|
2508
|
+
);
|
|
2150
2509
|
if (hasComments) {
|
|
2151
|
-
add(
|
|
2152
|
-
|
|
2510
|
+
add(
|
|
2511
|
+
'<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>',
|
|
2512
|
+
"/word/comments.xml"
|
|
2513
|
+
);
|
|
2514
|
+
add(
|
|
2515
|
+
'<Override PartName="/word/commentsExtended.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"/>',
|
|
2516
|
+
"/word/commentsExtended.xml"
|
|
2517
|
+
);
|
|
2153
2518
|
}
|
|
2154
2519
|
return out;
|
|
2155
2520
|
}
|
|
2156
2521
|
function extractBodySectPr(xml) {
|
|
2157
|
-
const all = xml.match(
|
|
2522
|
+
const all = xml.match(
|
|
2523
|
+
/<w:sectPr\b[^>]*>[\s\S]*?<\/w:sectPr>|<w:sectPr\b[^>]*\/>/g
|
|
2524
|
+
);
|
|
2158
2525
|
return all ? all[all.length - 1] : "";
|
|
2159
2526
|
}
|
|
2160
2527
|
function mergeRels(xml, newRels) {
|
|
@@ -2163,7 +2530,10 @@ function mergeRels(xml, newRels) {
|
|
|
2163
2530
|
/<Relationship\b[^>]*Target="comments(?:Extended)?\.xml"[^>]*\/>/g,
|
|
2164
2531
|
""
|
|
2165
2532
|
);
|
|
2166
|
-
return base.replace(
|
|
2533
|
+
return base.replace(
|
|
2534
|
+
"</Relationships>",
|
|
2535
|
+
`${newRels.join("")}</Relationships>`
|
|
2536
|
+
);
|
|
2167
2537
|
}
|
|
2168
2538
|
async function exportDocx(doc, opts) {
|
|
2169
2539
|
const comments = doc.attrs["comments"] ?? [];
|
|
@@ -2172,7 +2542,8 @@ async function exportDocx(doc, opts) {
|
|
|
2172
2542
|
let idx = 0;
|
|
2173
2543
|
doc.descendants((n) => {
|
|
2174
2544
|
if (!isInlineLeaf(n)) return;
|
|
2175
|
-
for (const id of commentIdsOf(n))
|
|
2545
|
+
for (const id of commentIdsOf(n))
|
|
2546
|
+
if (knownComments.has(id)) lastRun.set(id, idx);
|
|
2176
2547
|
idx++;
|
|
2177
2548
|
});
|
|
2178
2549
|
const ctx = {
|
|
@@ -2187,13 +2558,20 @@ async function exportDocx(doc, opts) {
|
|
|
2187
2558
|
};
|
|
2188
2559
|
const boundaries = sectionBoundaries(doc);
|
|
2189
2560
|
let body = "";
|
|
2190
|
-
doc.forEach(
|
|
2561
|
+
doc.forEach(
|
|
2562
|
+
(block, _offset, i) => body += blockXml(block, ctx, boundaries.get(i))
|
|
2563
|
+
);
|
|
2191
2564
|
const hasComments = comments.length > 0;
|
|
2192
2565
|
if (hasComments) {
|
|
2193
|
-
ctx.rels.push(
|
|
2194
|
-
|
|
2566
|
+
ctx.rels.push(
|
|
2567
|
+
`<Relationship Id="rIdComments" Type="${R_NS}/comments" Target="comments.xml"/>`
|
|
2568
|
+
);
|
|
2569
|
+
ctx.rels.push(
|
|
2570
|
+
`<Relationship Id="rIdCommentsExt" Type="${R_NS}/commentsExtended" Target="commentsExtended.xml"/>`
|
|
2571
|
+
);
|
|
2195
2572
|
}
|
|
2196
2573
|
const zip = new import_jszip2.default();
|
|
2574
|
+
const styleIds = usedStyleIds(doc);
|
|
2197
2575
|
let sectPr = "";
|
|
2198
2576
|
if (opts?.carry) {
|
|
2199
2577
|
const carry = opts.carry;
|
|
@@ -2201,18 +2579,29 @@ async function exportDocx(doc, opts) {
|
|
|
2201
2579
|
if (!f.dir) zip.file(path, await f.async("uint8array"));
|
|
2202
2580
|
}
|
|
2203
2581
|
const ct = await carry.file("[Content_Types].xml")?.async("string");
|
|
2204
|
-
zip.file(
|
|
2582
|
+
zip.file(
|
|
2583
|
+
"[Content_Types].xml",
|
|
2584
|
+
ct ? mergeContentTypes(ct, ctx.exts, hasComments) : contentTypes(ctx.exts, hasComments)
|
|
2585
|
+
);
|
|
2586
|
+
const carriedStyles = await carry.file("word/styles.xml")?.async("string");
|
|
2587
|
+
if (carriedStyles)
|
|
2588
|
+
zip.file("word/styles.xml", mergeStyles(carriedStyles, styleIds));
|
|
2205
2589
|
const rels = await carry.file("word/_rels/document.xml.rels")?.async("string");
|
|
2206
2590
|
zip.file("word/_rels/document.xml.rels", mergeRels(rels, ctx.rels));
|
|
2207
2591
|
const origDoc = await carry.file("word/document.xml")?.async("string");
|
|
2208
2592
|
if (origDoc) sectPr = extractBodySectPr(origDoc);
|
|
2209
2593
|
} else {
|
|
2594
|
+
ctx.rels.push(
|
|
2595
|
+
`<Relationship Id="rIdStyles" Type="${R_NS}/styles" Target="styles.xml"/>`
|
|
2596
|
+
);
|
|
2597
|
+
zip.file("word/styles.xml", stylesXml(styleIds));
|
|
2210
2598
|
zip.file("[Content_Types].xml", contentTypes(ctx.exts, hasComments));
|
|
2211
2599
|
zip.file("_rels/.rels", ROOT_RELS);
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2600
|
+
zip.file(
|
|
2601
|
+
"word/_rels/document.xml.rels",
|
|
2602
|
+
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2603
|
+
<Relationships xmlns="${PR_NS}">${ctx.rels.join("")}</Relationships>`
|
|
2604
|
+
);
|
|
2216
2605
|
}
|
|
2217
2606
|
const documentXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2218
2607
|
<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>`;
|
|
@@ -2221,14 +2610,27 @@ async function exportDocx(doc, opts) {
|
|
|
2221
2610
|
zip.file("word/comments.xml", commentsXml(comments));
|
|
2222
2611
|
zip.file("word/commentsExtended.xml", commentsExtendedXml(comments));
|
|
2223
2612
|
}
|
|
2224
|
-
for (const { path, base64 } of ctx.media)
|
|
2613
|
+
for (const { path, base64 } of ctx.media)
|
|
2614
|
+
zip.file(path, base64, { base64: true });
|
|
2225
2615
|
return zip.generateAsync({ type: "uint8array" });
|
|
2226
2616
|
}
|
|
2227
2617
|
|
|
2228
2618
|
// packages/layout-engine/dist/index.js
|
|
2229
|
-
var DEFAULT_FONT = {
|
|
2619
|
+
var DEFAULT_FONT = {
|
|
2620
|
+
family: "Arial",
|
|
2621
|
+
sizePt: 11,
|
|
2622
|
+
bold: false,
|
|
2623
|
+
italic: false
|
|
2624
|
+
};
|
|
2230
2625
|
var PT_TO_PX = 96 / 72;
|
|
2231
|
-
var HEADING_PT = {
|
|
2626
|
+
var HEADING_PT = {
|
|
2627
|
+
1: 24,
|
|
2628
|
+
2: 18,
|
|
2629
|
+
3: 14,
|
|
2630
|
+
4: 12,
|
|
2631
|
+
5: 11,
|
|
2632
|
+
6: 11
|
|
2633
|
+
};
|
|
2232
2634
|
var LINE_HEIGHT_FACTOR = 1.2;
|
|
2233
2635
|
var BASELINE_FACTOR = 0.8;
|
|
2234
2636
|
var DEFAULT_TAB_WIDTH = 48;
|
|
@@ -2304,11 +2706,13 @@ function resolveImage(node, pos) {
|
|
|
2304
2706
|
function paragraphToFlow(node, base, nodePos, allowFloats = false, marker) {
|
|
2305
2707
|
const contentStart = nodePos + 1;
|
|
2306
2708
|
const headingLevel2 = node.attrs["heading"];
|
|
2307
|
-
const
|
|
2709
|
+
const styleId = node.attrs["styleId"];
|
|
2710
|
+
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;
|
|
2308
2711
|
const runs = [];
|
|
2309
2712
|
const floats = [];
|
|
2310
2713
|
node.forEach((child2, offset) => {
|
|
2311
|
-
if (child2.isText)
|
|
2714
|
+
if (child2.isText)
|
|
2715
|
+
runs.push(resolveRun(child2, runBase, contentStart + offset));
|
|
2312
2716
|
else if (child2.type.name === "image") {
|
|
2313
2717
|
const float = child2.attrs["float"];
|
|
2314
2718
|
if (float && allowFloats) {
|
|
@@ -2335,7 +2739,8 @@ function paragraphToFlow(node, base, nodePos, allowFloats = false, marker) {
|
|
|
2335
2739
|
}
|
|
2336
2740
|
} else if (child2.type.name === "page_field")
|
|
2337
2741
|
runs.push(resolveField(child2, runBase, contentStart + offset));
|
|
2338
|
-
else if (child2.type.name === "hard_break")
|
|
2742
|
+
else if (child2.type.name === "hard_break")
|
|
2743
|
+
runs.push({ break: true, pos: contentStart + offset });
|
|
2339
2744
|
});
|
|
2340
2745
|
const list = node.attrs["list"];
|
|
2341
2746
|
const align = node.attrs["align"];
|
|
@@ -2371,8 +2776,15 @@ function markerFor(node, counter) {
|
|
|
2371
2776
|
}
|
|
2372
2777
|
function nodeToBlock(node, base, nodePos, allowFloats = false, counter) {
|
|
2373
2778
|
if (node.type.name === "paragraph")
|
|
2374
|
-
return paragraphToFlow(
|
|
2375
|
-
|
|
2779
|
+
return paragraphToFlow(
|
|
2780
|
+
node,
|
|
2781
|
+
base,
|
|
2782
|
+
nodePos,
|
|
2783
|
+
allowFloats,
|
|
2784
|
+
markerFor(node, counter)
|
|
2785
|
+
);
|
|
2786
|
+
if (node.type.name === "table")
|
|
2787
|
+
return tableToFlow(node, base, nodePos, counter);
|
|
2376
2788
|
return null;
|
|
2377
2789
|
}
|
|
2378
2790
|
function tableToFlow(node, base, nodePos, counter) {
|
|
@@ -2384,7 +2796,13 @@ function tableToFlow(node, base, nodePos, counter) {
|
|
|
2384
2796
|
const cellPos = rowPos + 1 + cellOffset;
|
|
2385
2797
|
const content = [];
|
|
2386
2798
|
cellNode.forEach((child2, childOffset) => {
|
|
2387
|
-
const block = nodeToBlock(
|
|
2799
|
+
const block = nodeToBlock(
|
|
2800
|
+
child2,
|
|
2801
|
+
base,
|
|
2802
|
+
cellPos + 1 + childOffset,
|
|
2803
|
+
true,
|
|
2804
|
+
counter
|
|
2805
|
+
);
|
|
2388
2806
|
if (block) content.push(block);
|
|
2389
2807
|
});
|
|
2390
2808
|
const a = cellNode.attrs;
|
|
@@ -2416,7 +2834,9 @@ function tableToFlow(node, base, nodePos, counter) {
|
|
|
2416
2834
|
}
|
|
2417
2835
|
function toFlowBlocks(doc, defaultFont = {}, allowFloats = true) {
|
|
2418
2836
|
const base = { ...DEFAULT_FONT, ...defaultFont };
|
|
2419
|
-
const counter = createNumberingCounter(
|
|
2837
|
+
const counter = createNumberingCounter(
|
|
2838
|
+
doc.attrs["numbering"]
|
|
2839
|
+
);
|
|
2420
2840
|
const blocks = [];
|
|
2421
2841
|
doc.forEach((node, offset) => {
|
|
2422
2842
|
const block = nodeToBlock(node, base, offset, allowFloats, counter);
|
|
@@ -2426,7 +2846,16 @@ function toFlowBlocks(doc, defaultFont = {}, allowFloats = true) {
|
|
|
2426
2846
|
}
|
|
2427
2847
|
function tokenizeInline(inline, ctx) {
|
|
2428
2848
|
if ("break" in inline) {
|
|
2429
|
-
return [
|
|
2849
|
+
return [
|
|
2850
|
+
{
|
|
2851
|
+
isBreak: true,
|
|
2852
|
+
font: ctx.base,
|
|
2853
|
+
width: 0,
|
|
2854
|
+
isSpace: false,
|
|
2855
|
+
pos: inline.pos,
|
|
2856
|
+
size: 1
|
|
2857
|
+
}
|
|
2858
|
+
];
|
|
2430
2859
|
}
|
|
2431
2860
|
if ("src" in inline) {
|
|
2432
2861
|
return [
|
|
@@ -2537,7 +2966,12 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2537
2966
|
return lineLeft + k * tabWidth - x;
|
|
2538
2967
|
};
|
|
2539
2968
|
const tabStops = block.tabs ? [...block.tabs].sort((a, b) => a.pos - b.pos) : [];
|
|
2540
|
-
const LEADER_CHARS = {
|
|
2969
|
+
const LEADER_CHARS = {
|
|
2970
|
+
dot: ".",
|
|
2971
|
+
hyphen: "-",
|
|
2972
|
+
underscore: "_",
|
|
2973
|
+
middleDot: "\xB7"
|
|
2974
|
+
};
|
|
2541
2975
|
const MIN_TAB = 2;
|
|
2542
2976
|
const tabGroup = (ti) => {
|
|
2543
2977
|
let width = 0;
|
|
@@ -2547,7 +2981,8 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2547
2981
|
if (t.isTab) break;
|
|
2548
2982
|
if (decimalPrefix === null && t.text != null && !t.field) {
|
|
2549
2983
|
const m = /[.,]/.exec(t.text);
|
|
2550
|
-
if (m)
|
|
2984
|
+
if (m)
|
|
2985
|
+
decimalPrefix = width + measure(t.text.slice(0, m.index), t.font);
|
|
2551
2986
|
}
|
|
2552
2987
|
width += t.width;
|
|
2553
2988
|
}
|
|
@@ -2670,7 +3105,16 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2670
3105
|
height = target;
|
|
2671
3106
|
}
|
|
2672
3107
|
const painted = firstLine && marker ? [marker, ...segments] : segments;
|
|
2673
|
-
emit({
|
|
3108
|
+
emit({
|
|
3109
|
+
x: startX,
|
|
3110
|
+
width: lineRight - startX,
|
|
3111
|
+
height,
|
|
3112
|
+
baseline,
|
|
3113
|
+
segments: painted,
|
|
3114
|
+
images,
|
|
3115
|
+
from,
|
|
3116
|
+
to
|
|
3117
|
+
});
|
|
2674
3118
|
prevTo = to;
|
|
2675
3119
|
lineTokens = [];
|
|
2676
3120
|
lineWidth = 0;
|
|
@@ -2704,10 +3148,14 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2704
3148
|
softWrapped = false;
|
|
2705
3149
|
continue;
|
|
2706
3150
|
}
|
|
2707
|
-
if (token.isSpace && !token.isTab && lineTokens.length === 0 && softWrapped)
|
|
3151
|
+
if (token.isSpace && !token.isTab && lineTokens.length === 0 && softWrapped)
|
|
3152
|
+
continue;
|
|
2708
3153
|
if (token.isTab) resolveTab(token, lineStart() + lineWidth, ti);
|
|
2709
3154
|
if (clusterable(token) && clusterFont && sameFont(clusterFont, token.font)) {
|
|
2710
|
-
token.width = Math.max(
|
|
3155
|
+
token.width = Math.max(
|
|
3156
|
+
0,
|
|
3157
|
+
measure(clusterText + token.text, token.font) - clusterWidth
|
|
3158
|
+
);
|
|
2711
3159
|
}
|
|
2712
3160
|
const cursor = lineStart() + lineWidth;
|
|
2713
3161
|
if (!token.isSpace && lineTokens.length > 0 && cursor + token.width > lineRight) {
|
|
@@ -2715,14 +3163,21 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2715
3163
|
resetCluster();
|
|
2716
3164
|
softWrapped = true;
|
|
2717
3165
|
if (token.isTab) resolveTab(token, lineStart() + lineWidth, ti);
|
|
2718
|
-
else if (clusterable(token))
|
|
3166
|
+
else if (clusterable(token))
|
|
3167
|
+
token.width = measure(token.text, token.font);
|
|
2719
3168
|
}
|
|
2720
3169
|
if (clusterable(token) && lineTokens.length === 0 && token.text.length > 1 && lineStart() + token.width > lineRight) {
|
|
2721
3170
|
const avail = lineRight - lineStart();
|
|
2722
3171
|
let n = 1;
|
|
2723
|
-
while (n < token.text.length && measure(token.text.slice(0, n + 1), token.font) <= avail)
|
|
3172
|
+
while (n < token.text.length && measure(token.text.slice(0, n + 1), token.font) <= avail)
|
|
3173
|
+
n++;
|
|
2724
3174
|
const restText = token.text.slice(n);
|
|
2725
|
-
const rest = {
|
|
3175
|
+
const rest = {
|
|
3176
|
+
...token,
|
|
3177
|
+
text: restText,
|
|
3178
|
+
width: measure(restText, token.font),
|
|
3179
|
+
size: restText.length
|
|
3180
|
+
};
|
|
2726
3181
|
if (token.pos != null) rest.pos = token.pos + n;
|
|
2727
3182
|
token.text = token.text.slice(0, n);
|
|
2728
3183
|
token.size = n;
|
|
@@ -2743,7 +3198,10 @@ function wrapParagraph(block, ctx, bandFn, emit) {
|
|
|
2743
3198
|
const rotH = rotatedBoxHeight(token.image);
|
|
2744
3199
|
maxImagePx = Math.max(maxImagePx, rotH);
|
|
2745
3200
|
maxAscent = Math.max(maxAscent, token.image.height / 2 + rotH / 2);
|
|
2746
|
-
maxDescent = Math.max(
|
|
3201
|
+
maxDescent = Math.max(
|
|
3202
|
+
maxDescent,
|
|
3203
|
+
Math.max(0, (rotH - token.image.height) / 2)
|
|
3204
|
+
);
|
|
2747
3205
|
} else {
|
|
2748
3206
|
maxFontPx = Math.max(maxFontPx, sizePx(token.font));
|
|
2749
3207
|
if (metrics) {
|
|
@@ -2796,7 +3254,13 @@ function shiftTableX(table, dx) {
|
|
|
2796
3254
|
}
|
|
2797
3255
|
var TEXTBOX_INSET = { l: 10, t: 5, r: 10, b: 5 };
|
|
2798
3256
|
function resolveFloat(f, x, y, ctx) {
|
|
2799
|
-
const rf = {
|
|
3257
|
+
const rf = {
|
|
3258
|
+
x,
|
|
3259
|
+
y,
|
|
3260
|
+
width: f.width,
|
|
3261
|
+
height: f.height,
|
|
3262
|
+
src: f.src
|
|
3263
|
+
};
|
|
2800
3264
|
if (f.pos != null) rf.pos = f.pos;
|
|
2801
3265
|
if (f.rotation) rf.rotation = f.rotation;
|
|
2802
3266
|
if (f.shape) rf.shape = f.shape;
|
|
@@ -2842,7 +3306,8 @@ function eachCell(rows, ncols, visit) {
|
|
|
2842
3306
|
for (const cell of rows[r].cells) {
|
|
2843
3307
|
while (col < ncols && spanned[col] > 0) col++;
|
|
2844
3308
|
visit(r, cell, col);
|
|
2845
|
-
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
3309
|
+
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
3310
|
+
spanned[col + k] = cell.rowspan;
|
|
2846
3311
|
col += cell.colspan;
|
|
2847
3312
|
}
|
|
2848
3313
|
for (let i = 0; i < ncols; i++) if (spanned[i] > 0) spanned[i]--;
|
|
@@ -2852,14 +3317,18 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2852
3317
|
const avail = contentRight - contentLeft;
|
|
2853
3318
|
const nrows = table.rows.length;
|
|
2854
3319
|
const ncols = table.rows.reduce(
|
|
2855
|
-
(m, r) => Math.max(
|
|
3320
|
+
(m, r) => Math.max(
|
|
3321
|
+
m,
|
|
3322
|
+
r.cells.reduce((s, c) => s + c.colspan, 0)
|
|
3323
|
+
),
|
|
2856
3324
|
0
|
|
2857
3325
|
);
|
|
2858
3326
|
const colWidths = new Array(ncols).fill(0);
|
|
2859
3327
|
eachCell(table.rows, ncols, (_r, cell, startCol) => {
|
|
2860
3328
|
if (cell.colwidth && cell.colwidth.length === cell.colspan) {
|
|
2861
3329
|
for (let k = 0; k < cell.colspan && startCol + k < ncols; k++) {
|
|
2862
|
-
if (colWidths[startCol + k] === 0)
|
|
3330
|
+
if (colWidths[startCol + k] === 0)
|
|
3331
|
+
colWidths[startCol + k] = cell.colwidth[k];
|
|
2863
3332
|
}
|
|
2864
3333
|
}
|
|
2865
3334
|
});
|
|
@@ -2867,7 +3336,8 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2867
3336
|
const unknown = colWidths.filter((w) => w === 0).length;
|
|
2868
3337
|
if (unknown > 0) {
|
|
2869
3338
|
const share = Math.max(0, (avail - known) / unknown);
|
|
2870
|
-
for (let i = 0; i < ncols; i++)
|
|
3339
|
+
for (let i = 0; i < ncols; i++)
|
|
3340
|
+
if (colWidths[i] === 0) colWidths[i] = share;
|
|
2871
3341
|
}
|
|
2872
3342
|
const natural = colWidths.reduce((s, w) => s + w, 0);
|
|
2873
3343
|
if (natural > avail && natural > 0) {
|
|
@@ -2887,9 +3357,15 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2887
3357
|
const cellDrafts = [];
|
|
2888
3358
|
eachCell(table.rows, ncols, (r, cell, col) => {
|
|
2889
3359
|
let cellWidth = 0;
|
|
2890
|
-
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
3360
|
+
for (let k = 0; k < cell.colspan && col + k < ncols; k++)
|
|
3361
|
+
cellWidth += colWidths[col + k];
|
|
2891
3362
|
const cellLeft = colX[col];
|
|
2892
|
-
const flow = layoutFlow(
|
|
3363
|
+
const flow = layoutFlow(
|
|
3364
|
+
cell.content,
|
|
3365
|
+
cellLeft + pad.left,
|
|
3366
|
+
cellLeft + cellWidth - pad.right,
|
|
3367
|
+
ctx
|
|
3368
|
+
);
|
|
2893
3369
|
cellDrafts.push({
|
|
2894
3370
|
startRow: r,
|
|
2895
3371
|
startCol: col,
|
|
@@ -2909,14 +3385,18 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2909
3385
|
const rowHeight = new Array(nrows).fill(0);
|
|
2910
3386
|
for (const c of cellDrafts) {
|
|
2911
3387
|
if (c.rowspan === 1) {
|
|
2912
|
-
rowHeight[c.startRow] = Math.max(
|
|
3388
|
+
rowHeight[c.startRow] = Math.max(
|
|
3389
|
+
rowHeight[c.startRow],
|
|
3390
|
+
c.contentHeight + pad.top + pad.bottom
|
|
3391
|
+
);
|
|
2913
3392
|
}
|
|
2914
3393
|
}
|
|
2915
3394
|
for (const c of cellDrafts) {
|
|
2916
3395
|
if (c.rowspan > 1) {
|
|
2917
3396
|
const need = c.contentHeight + pad.top + pad.bottom;
|
|
2918
3397
|
let span = 0;
|
|
2919
|
-
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
3398
|
+
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
3399
|
+
span += rowHeight[r];
|
|
2920
3400
|
if (need > span) {
|
|
2921
3401
|
const last = Math.min(c.startRow + c.rowspan - 1, nrows - 1);
|
|
2922
3402
|
rowHeight[last] += need - span;
|
|
@@ -2931,7 +3411,8 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2931
3411
|
for (let r = 0; r < nrows; r++) rowY[r + 1] = rowY[r] + rowHeight[r];
|
|
2932
3412
|
const cells = cellDrafts.map((c) => {
|
|
2933
3413
|
let height = 0;
|
|
2934
|
-
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
3414
|
+
for (let r = c.startRow; r < c.startRow + c.rowspan && r < nrows; r++)
|
|
3415
|
+
height += rowHeight[r];
|
|
2935
3416
|
const slack = Math.max(0, height - pad.top - pad.bottom - c.contentHeight);
|
|
2936
3417
|
const vOffset = c.vAlign === "bottom" ? slack : c.vAlign === "center" ? slack / 2 : 0;
|
|
2937
3418
|
const dy = rowY[c.startRow] + pad.top + vOffset;
|
|
@@ -2949,19 +3430,30 @@ function layoutTable(table, contentLeft, contentRight, ctx) {
|
|
|
2949
3430
|
if (c.background) cell.background = c.background;
|
|
2950
3431
|
if (c.borders) cell.borders = c.borders;
|
|
2951
3432
|
if (c.tables.length > 0) cell.tables = c.tables;
|
|
2952
|
-
if (c.floats.length > 0)
|
|
3433
|
+
if (c.floats.length > 0)
|
|
3434
|
+
cell.floats = c.floats.map((f) => ({ ...f, y: f.y + dy }));
|
|
2953
3435
|
return cell;
|
|
2954
3436
|
});
|
|
2955
|
-
const resolved = {
|
|
3437
|
+
const resolved = {
|
|
3438
|
+
x: contentLeft + xShift,
|
|
3439
|
+
y: 0,
|
|
3440
|
+
width: tableWidth,
|
|
3441
|
+
height: rowY[nrows],
|
|
3442
|
+
cells
|
|
3443
|
+
};
|
|
2956
3444
|
if (table.borders) resolved.borders = table.borders;
|
|
2957
3445
|
let headerRows = 0;
|
|
2958
3446
|
while (headerRows < nrows && table.rows[headerRows].header) headerRows++;
|
|
2959
3447
|
if (headerRows > 0 && headerRows < nrows) {
|
|
2960
3448
|
const headerBottom = rowY[headerRows];
|
|
2961
|
-
const spansOut = cells.some(
|
|
3449
|
+
const spansOut = cells.some(
|
|
3450
|
+
(c) => c.y < headerBottom && c.y + c.height > headerBottom
|
|
3451
|
+
);
|
|
2962
3452
|
if (!spansOut) resolved.headerBottom = headerBottom;
|
|
2963
3453
|
}
|
|
2964
|
-
const cantSplitBands = table.rows.map(
|
|
3454
|
+
const cantSplitBands = table.rows.map(
|
|
3455
|
+
(row, r) => row.cantSplit ? { top: rowY[r], bottom: rowY[r + 1] } : null
|
|
3456
|
+
).filter((b) => b !== null);
|
|
2965
3457
|
if (cantSplitBands.length > 0) resolved.cantSplitBands = cantSplitBands;
|
|
2966
3458
|
return resolved;
|
|
2967
3459
|
}
|
|
@@ -2994,7 +3486,10 @@ function cloneTable(t) {
|
|
|
2994
3486
|
return { ...t, cells: t.cells.map(cloneCell) };
|
|
2995
3487
|
}
|
|
2996
3488
|
function cloneCell(cell) {
|
|
2997
|
-
const copy = {
|
|
3489
|
+
const copy = {
|
|
3490
|
+
...cell,
|
|
3491
|
+
lines: cell.lines.map((l) => ({ ...l }))
|
|
3492
|
+
};
|
|
2998
3493
|
if (cell.tables) copy.tables = cell.tables.map(cloneTable);
|
|
2999
3494
|
if (cell.floats) copy.floats = cell.floats.map((f) => ({ ...f }));
|
|
3000
3495
|
return copy;
|
|
@@ -3030,11 +3525,19 @@ function splitTableAt(table, cut) {
|
|
|
3030
3525
|
} else {
|
|
3031
3526
|
const c = straddlers.get(cell);
|
|
3032
3527
|
const topLines = cell.lines.filter((l) => l.y + l.height <= cut);
|
|
3033
|
-
const topTables = (cell.tables ?? []).filter(
|
|
3034
|
-
|
|
3528
|
+
const topTables = (cell.tables ?? []).filter(
|
|
3529
|
+
(t) => t.y + t.height <= cut
|
|
3530
|
+
);
|
|
3531
|
+
const topCell = {
|
|
3532
|
+
...cell,
|
|
3533
|
+
height: cut - cell.y,
|
|
3534
|
+
lines: topLines
|
|
3535
|
+
};
|
|
3035
3536
|
if (topTables.length > 0) topCell.tables = topTables;
|
|
3036
3537
|
else delete topCell.tables;
|
|
3037
|
-
const topFloats = (cell.floats ?? []).filter(
|
|
3538
|
+
const topFloats = (cell.floats ?? []).filter(
|
|
3539
|
+
(f) => f.y + f.height <= cut
|
|
3540
|
+
);
|
|
3038
3541
|
if (topFloats.length > 0) topCell.floats = topFloats;
|
|
3039
3542
|
else delete topCell.floats;
|
|
3040
3543
|
topCells.push(topCell);
|
|
@@ -3057,7 +3560,13 @@ function splitTableAt(table, cut) {
|
|
|
3057
3560
|
restCells.push(contCell);
|
|
3058
3561
|
}
|
|
3059
3562
|
}
|
|
3060
|
-
const top = {
|
|
3563
|
+
const top = {
|
|
3564
|
+
x: table.x,
|
|
3565
|
+
y: 0,
|
|
3566
|
+
width: table.width,
|
|
3567
|
+
height: cut,
|
|
3568
|
+
cells: topCells
|
|
3569
|
+
};
|
|
3061
3570
|
const rest = {
|
|
3062
3571
|
x: table.x,
|
|
3063
3572
|
y: 0,
|
|
@@ -3079,10 +3588,14 @@ function cloneHeaderCells(table, headerBottom) {
|
|
|
3079
3588
|
tables: void 0,
|
|
3080
3589
|
floats: cell.floats?.map((f) => ({ ...f })),
|
|
3081
3590
|
lines: cell.lines.map((l) => {
|
|
3082
|
-
const line = {
|
|
3591
|
+
const line = {
|
|
3592
|
+
...l,
|
|
3593
|
+
segments: l.segments.map((s) => ({ ...s, pos: void 0 }))
|
|
3594
|
+
};
|
|
3083
3595
|
delete line.from;
|
|
3084
3596
|
delete line.to;
|
|
3085
|
-
if (line.images)
|
|
3597
|
+
if (line.images)
|
|
3598
|
+
line.images = line.images.map((im) => ({ ...im, pos: void 0 }));
|
|
3086
3599
|
return line;
|
|
3087
3600
|
})
|
|
3088
3601
|
}));
|
|
@@ -3159,13 +3672,15 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
3159
3672
|
for (const l of ls)
|
|
3160
3673
|
for (const s of l.segments) {
|
|
3161
3674
|
const n = s.footnoteRef;
|
|
3162
|
-
if (n != null && footnotes?.has(n) && !pageFnSet.has(n) && !out.includes(n))
|
|
3675
|
+
if (n != null && footnotes?.has(n) && !pageFnSet.has(n) && !out.includes(n))
|
|
3676
|
+
out.push(n);
|
|
3163
3677
|
}
|
|
3164
3678
|
};
|
|
3165
3679
|
for (const c of t.cells) {
|
|
3166
3680
|
if (c.y >= maxY) continue;
|
|
3167
3681
|
scan(c.lines);
|
|
3168
|
-
if (c.tables)
|
|
3682
|
+
if (c.tables)
|
|
3683
|
+
for (const nt of c.tables) for (const nc of nt.cells) scan(nc.lines);
|
|
3169
3684
|
}
|
|
3170
3685
|
return out;
|
|
3171
3686
|
};
|
|
@@ -3194,10 +3709,16 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
3194
3709
|
return { separatorY: areaTop + FOOTNOTE_AREA_GAP / 2, lines: out };
|
|
3195
3710
|
};
|
|
3196
3711
|
const finalizePage = () => {
|
|
3197
|
-
const resolved = {
|
|
3712
|
+
const resolved = {
|
|
3713
|
+
index: pages.length,
|
|
3714
|
+
width: page.width,
|
|
3715
|
+
height: page.height,
|
|
3716
|
+
lines
|
|
3717
|
+
};
|
|
3198
3718
|
if (tables.length > 0) resolved.tables = tables;
|
|
3199
3719
|
if (pageFloats.length > 0) resolved.floats = pageFloats;
|
|
3200
|
-
if (pageFnNums.length > 0)
|
|
3720
|
+
if (pageFnNums.length > 0)
|
|
3721
|
+
resolved.footnotes = buildFootnoteArea(pageFnNums);
|
|
3201
3722
|
pages.push(resolved);
|
|
3202
3723
|
lines = [];
|
|
3203
3724
|
tables = [];
|
|
@@ -3287,7 +3808,9 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
3287
3808
|
}
|
|
3288
3809
|
const b = bandAt(y, estH);
|
|
3289
3810
|
if (b) return b;
|
|
3290
|
-
const blockers = exclusions.filter(
|
|
3811
|
+
const blockers = exclusions.filter(
|
|
3812
|
+
(ex) => ex.top < y + estH && ex.bottom > y
|
|
3813
|
+
);
|
|
3291
3814
|
if (blockers.length === 0) return { left: colX0(), right: colX1() };
|
|
3292
3815
|
y = Math.min(...blockers.map((ex) => ex.bottom));
|
|
3293
3816
|
}
|
|
@@ -3331,7 +3854,9 @@ function placeBlocks(items, config, ctx, band, footnotes) {
|
|
|
3331
3854
|
}
|
|
3332
3855
|
const drafts = item.para.drafts;
|
|
3333
3856
|
const draftsHeight = drafts?.reduce((s, d) => s + d.height, 0) ?? 0;
|
|
3334
|
-
const floatsAhead = exclusions.some(
|
|
3857
|
+
const floatsAhead = exclusions.some(
|
|
3858
|
+
(ex) => ex.bottom > y && ex.top < y + draftsHeight
|
|
3859
|
+
);
|
|
3335
3860
|
if (drafts && !floatsAhead) {
|
|
3336
3861
|
for (const d of drafts) emitLine(d);
|
|
3337
3862
|
} else {
|
|
@@ -3427,16 +3952,25 @@ function shiftDrafts(drafts, delta) {
|
|
|
3427
3952
|
...d,
|
|
3428
3953
|
from: d.from != null ? d.from + delta : d.from,
|
|
3429
3954
|
to: d.to != null ? d.to + delta : d.to,
|
|
3430
|
-
segments: d.segments.map(
|
|
3431
|
-
|
|
3955
|
+
segments: d.segments.map(
|
|
3956
|
+
(s) => s.pos != null ? { ...s, pos: s.pos + delta } : s
|
|
3957
|
+
),
|
|
3958
|
+
images: d.images.map(
|
|
3959
|
+
(im) => im.pos != null ? { ...im, pos: im.pos + delta } : im
|
|
3960
|
+
)
|
|
3432
3961
|
}));
|
|
3433
3962
|
}
|
|
3434
3963
|
function cloneLineShifted(l, delta) {
|
|
3435
3964
|
const out = {
|
|
3436
3965
|
...l,
|
|
3437
|
-
segments: l.segments.map(
|
|
3966
|
+
segments: l.segments.map(
|
|
3967
|
+
(s) => s.pos != null ? { ...s, pos: s.pos + delta } : { ...s }
|
|
3968
|
+
)
|
|
3438
3969
|
};
|
|
3439
|
-
if (l.images)
|
|
3970
|
+
if (l.images)
|
|
3971
|
+
out.images = l.images.map(
|
|
3972
|
+
(im) => im.pos != null ? { ...im, pos: im.pos + delta } : { ...im }
|
|
3973
|
+
);
|
|
3440
3974
|
if (l.from != null) out.from = l.from + delta;
|
|
3441
3975
|
if (l.to != null) out.to = l.to + delta;
|
|
3442
3976
|
return out;
|
|
@@ -3479,7 +4013,10 @@ function layVariants(docs, fn) {
|
|
|
3479
4013
|
return out;
|
|
3480
4014
|
}
|
|
3481
4015
|
function maxBandHeight(bands) {
|
|
3482
|
-
return Math.max(
|
|
4016
|
+
return Math.max(
|
|
4017
|
+
0,
|
|
4018
|
+
...[bands.default, bands.first, bands.even].filter(Boolean).map((b) => b.height)
|
|
4019
|
+
);
|
|
3483
4020
|
}
|
|
3484
4021
|
function anyBandHasFields(bands) {
|
|
3485
4022
|
return [bands.default, bands.first, bands.even].some(
|
|
@@ -3505,9 +4042,17 @@ function layoutChrome(doc, topY, left, right, ctx) {
|
|
|
3505
4042
|
const lines = flow.lines.map((l) => ({ ...l, y: l.y + topY }));
|
|
3506
4043
|
flow.tables.forEach((t) => offsetTable(t, topY));
|
|
3507
4044
|
stripPositions(lines, flow.tables);
|
|
3508
|
-
const band = {
|
|
4045
|
+
const band = {
|
|
4046
|
+
lines,
|
|
4047
|
+
tables: flow.tables,
|
|
4048
|
+
height: flow.height
|
|
4049
|
+
};
|
|
3509
4050
|
if (flow.floats.length > 0)
|
|
3510
|
-
band.floats = flow.floats.map((f) => ({
|
|
4051
|
+
band.floats = flow.floats.map((f) => ({
|
|
4052
|
+
...f,
|
|
4053
|
+
y: f.y + topY,
|
|
4054
|
+
pos: void 0
|
|
4055
|
+
}));
|
|
3511
4056
|
return band;
|
|
3512
4057
|
}
|
|
3513
4058
|
function layoutFooterChrome(doc, pageHeight, left, right, ctx) {
|
|
@@ -3519,12 +4064,21 @@ function layoutFooterChrome(doc, pageHeight, left, right, ctx) {
|
|
|
3519
4064
|
height: flow.height
|
|
3520
4065
|
};
|
|
3521
4066
|
band.tables.forEach((t) => offsetTable(t, topY));
|
|
3522
|
-
if (flow.floats)
|
|
4067
|
+
if (flow.floats)
|
|
4068
|
+
band.floats = flow.floats.map((f) => ({ ...f, y: f.y + topY }));
|
|
3523
4069
|
return band;
|
|
3524
4070
|
}
|
|
3525
4071
|
function layoutFootnoteBody(doc, left, right, ctx) {
|
|
3526
|
-
const fnCtx = {
|
|
3527
|
-
|
|
4072
|
+
const fnCtx = {
|
|
4073
|
+
...ctx,
|
|
4074
|
+
base: { ...ctx.base, sizePt: ctx.base.sizePt * FOOTNOTE_FONT_SCALE }
|
|
4075
|
+
};
|
|
4076
|
+
const flow = layoutFlow(
|
|
4077
|
+
toFlowBlocks(doc, fnCtx.base, false),
|
|
4078
|
+
left,
|
|
4079
|
+
right,
|
|
4080
|
+
fnCtx
|
|
4081
|
+
);
|
|
3528
4082
|
stripPositions(flow.lines, flow.tables);
|
|
3529
4083
|
return { lines: flow.lines, height: flow.height };
|
|
3530
4084
|
}
|
|
@@ -3533,10 +4087,24 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3533
4087
|
const { page } = config;
|
|
3534
4088
|
const left = page.margin.left;
|
|
3535
4089
|
const right = page.width - page.margin.right;
|
|
3536
|
-
const headerDocs = {
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
4090
|
+
const headerDocs = {
|
|
4091
|
+
default: chrome?.header,
|
|
4092
|
+
first: chrome?.headerFirst,
|
|
4093
|
+
even: chrome?.headerEven
|
|
4094
|
+
};
|
|
4095
|
+
const footerDocs = {
|
|
4096
|
+
default: chrome?.footer,
|
|
4097
|
+
first: chrome?.footerFirst,
|
|
4098
|
+
even: chrome?.footerEven
|
|
4099
|
+
};
|
|
4100
|
+
const layHeaders = (c) => layVariants(
|
|
4101
|
+
headerDocs,
|
|
4102
|
+
(d) => layoutChrome(d, CHROME_DISTANCE, left, right, c)
|
|
4103
|
+
);
|
|
4104
|
+
const layFooters = (c) => layVariants(
|
|
4105
|
+
footerDocs,
|
|
4106
|
+
(d) => layoutFooterChrome(d, page.height, left, right, c)
|
|
4107
|
+
);
|
|
3540
4108
|
let headers = layHeaders(ctx);
|
|
3541
4109
|
let footers = layFooters(ctx);
|
|
3542
4110
|
let top = page.margin.top;
|
|
@@ -3545,25 +4113,46 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3545
4113
|
top = Math.max(top, CHROME_DISTANCE + maxBandHeight(headers));
|
|
3546
4114
|
}
|
|
3547
4115
|
if (footers.default || footers.first || footers.even) {
|
|
3548
|
-
bottom = Math.min(
|
|
4116
|
+
bottom = Math.min(
|
|
4117
|
+
bottom,
|
|
4118
|
+
page.height - CHROME_DISTANCE - maxBandHeight(footers)
|
|
4119
|
+
);
|
|
3549
4120
|
}
|
|
3550
|
-
const counter = createNumberingCounter(
|
|
4121
|
+
const counter = createNumberingCounter(
|
|
4122
|
+
doc.attrs["numbering"]
|
|
4123
|
+
);
|
|
3551
4124
|
const sections = doc.attrs["sections"] ?? [
|
|
3552
|
-
{
|
|
4125
|
+
{
|
|
4126
|
+
blockCount: doc.childCount,
|
|
4127
|
+
columns: { count: 1, gap: 0 },
|
|
4128
|
+
newPage: true
|
|
4129
|
+
}
|
|
3553
4130
|
];
|
|
3554
4131
|
const blockSection = [];
|
|
3555
4132
|
for (const sec of sections) {
|
|
3556
4133
|
for (let k = 0; k < sec.blockCount; k++) {
|
|
3557
|
-
blockSection.push({
|
|
4134
|
+
blockSection.push({
|
|
4135
|
+
columns: sec.columns,
|
|
4136
|
+
start: k === 0,
|
|
4137
|
+
newPage: sec.newPage
|
|
4138
|
+
});
|
|
3558
4139
|
}
|
|
3559
4140
|
}
|
|
3560
4141
|
const lastSec = sections[sections.length - 1];
|
|
3561
4142
|
while (blockSection.length < doc.childCount) {
|
|
3562
|
-
blockSection.push({
|
|
4143
|
+
blockSection.push({
|
|
4144
|
+
columns: lastSec.columns,
|
|
4145
|
+
start: false,
|
|
4146
|
+
newPage: lastSec.newPage
|
|
4147
|
+
});
|
|
3563
4148
|
}
|
|
3564
4149
|
const items = [];
|
|
3565
4150
|
doc.forEach((node, offset, index) => {
|
|
3566
|
-
const bs = blockSection[index] ?? {
|
|
4151
|
+
const bs = blockSection[index] ?? {
|
|
4152
|
+
columns: { count: 1, gap: 0 },
|
|
4153
|
+
start: false,
|
|
4154
|
+
newPage: true
|
|
4155
|
+
};
|
|
3567
4156
|
const colRight = left + columnWidth(right - left, bs.columns);
|
|
3568
4157
|
const tag = (item) => {
|
|
3569
4158
|
if (bs.start) item.section = { ...bs.columns, newPage: bs.newPage };
|
|
@@ -3596,17 +4185,35 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3596
4185
|
}
|
|
3597
4186
|
const flow = paragraphToFlow(node, ctx.base, offset, true, marker);
|
|
3598
4187
|
const drafts = layoutParagraph(flow, left, colRight, ctx);
|
|
3599
|
-
cache?.paragraphs.set(node, {
|
|
4188
|
+
cache?.paragraphs.set(node, {
|
|
4189
|
+
left,
|
|
4190
|
+
right: colRight,
|
|
4191
|
+
basePos: contentStart,
|
|
4192
|
+
marker,
|
|
4193
|
+
drafts
|
|
4194
|
+
});
|
|
3600
4195
|
items.push(tag({ para: { ...para(drafts), getFlow: () => flow } }));
|
|
3601
4196
|
} else if (node.type.name === "table") {
|
|
3602
4197
|
const hit = cache?.tables.get(node);
|
|
3603
4198
|
if (hit && hit.left === left && hit.right === colRight) {
|
|
3604
|
-
items.push(
|
|
4199
|
+
items.push(
|
|
4200
|
+
tag({ table: cloneTableShifted(hit.table, offset - hit.basePos) })
|
|
4201
|
+
);
|
|
3605
4202
|
return;
|
|
3606
4203
|
}
|
|
3607
|
-
const table = layoutTable(
|
|
4204
|
+
const table = layoutTable(
|
|
4205
|
+
tableToFlow(node, ctx.base, offset, counter),
|
|
4206
|
+
left,
|
|
4207
|
+
colRight,
|
|
4208
|
+
ctx
|
|
4209
|
+
);
|
|
3608
4210
|
if (cache && !tableHasList(node)) {
|
|
3609
|
-
cache.tables.set(node, {
|
|
4211
|
+
cache.tables.set(node, {
|
|
4212
|
+
left,
|
|
4213
|
+
right: colRight,
|
|
4214
|
+
basePos: offset,
|
|
4215
|
+
table
|
|
4216
|
+
});
|
|
3610
4217
|
items.push(tag({ table: cloneTableShifted(table, 0) }));
|
|
3611
4218
|
} else {
|
|
3612
4219
|
items.push(tag({ table }));
|
|
@@ -3624,7 +4231,10 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3624
4231
|
assignSectionHeights(items);
|
|
3625
4232
|
const resolved = placeBlocks(items, config, ctx, { top, bottom }, fnMap);
|
|
3626
4233
|
if (anyBandHasFields(headers) || anyBandHasFields(footers)) {
|
|
3627
|
-
const fieldCtx = {
|
|
4234
|
+
const fieldCtx = {
|
|
4235
|
+
...ctx,
|
|
4236
|
+
fieldPlaceholder: String(resolved.pages.length)
|
|
4237
|
+
};
|
|
3628
4238
|
headers = layHeaders(fieldCtx);
|
|
3629
4239
|
footers = layFooters(fieldCtx);
|
|
3630
4240
|
}
|
|
@@ -3635,7 +4245,10 @@ function layout(doc, config, cache, chrome, footnotes) {
|
|
|
3635
4245
|
if (footers.first) resolved.pageFooterFirst = footers.first;
|
|
3636
4246
|
if (footers.even) resolved.pageFooterEven = footers.even;
|
|
3637
4247
|
if (chrome?.titlePg || chrome?.evenAndOdd) {
|
|
3638
|
-
resolved.chromeSelect = {
|
|
4248
|
+
resolved.chromeSelect = {
|
|
4249
|
+
titlePg: !!chrome.titlePg,
|
|
4250
|
+
evenAndOdd: !!chrome.evenAndOdd
|
|
4251
|
+
};
|
|
3639
4252
|
}
|
|
3640
4253
|
return resolved;
|
|
3641
4254
|
}
|
|
@@ -4873,7 +5486,10 @@ var splitListItem = (state, dispatch) => {
|
|
|
4873
5486
|
if ($from.parent !== $to.parent) return false;
|
|
4874
5487
|
if (parent.content.size === 0) {
|
|
4875
5488
|
dispatch?.(
|
|
4876
|
-
state.tr.setNodeMarkup($from.before(), null, {
|
|
5489
|
+
state.tr.setNodeMarkup($from.before(), null, {
|
|
5490
|
+
...parent.attrs,
|
|
5491
|
+
list: null
|
|
5492
|
+
})
|
|
4877
5493
|
);
|
|
4878
5494
|
return true;
|
|
4879
5495
|
}
|
|
@@ -4949,6 +5565,7 @@ var InputBridge = class {
|
|
|
4949
5565
|
this.dom.appendChild(this.host);
|
|
4950
5566
|
this.view = new import_prosemirror_view.EditorView(this.host, {
|
|
4951
5567
|
state: createEditingState(options.doc, options.keys),
|
|
5568
|
+
handlePaste: options.handlePaste,
|
|
4952
5569
|
dispatchTransaction: (tr) => {
|
|
4953
5570
|
const state = this.view.state.apply(tr);
|
|
4954
5571
|
this.view.updateState(state);
|
|
@@ -4972,7 +5589,10 @@ var InputBridge = class {
|
|
|
4972
5589
|
setSelection(anchor, head = anchor) {
|
|
4973
5590
|
const { doc } = this.view.state;
|
|
4974
5591
|
const clamp2 = (p) => Math.max(0, Math.min(p, doc.content.size));
|
|
4975
|
-
const sel = import_prosemirror_state.TextSelection.between(
|
|
5592
|
+
const sel = import_prosemirror_state.TextSelection.between(
|
|
5593
|
+
doc.resolve(clamp2(anchor)),
|
|
5594
|
+
doc.resolve(clamp2(head))
|
|
5595
|
+
);
|
|
4976
5596
|
this.view.dispatch(this.view.state.tr.setSelection(sel));
|
|
4977
5597
|
}
|
|
4978
5598
|
/** Select the word around `pos` (double-click); falls back to a collapsed
|
|
@@ -5787,7 +6407,8 @@ function setAlign(align) {
|
|
|
5787
6407
|
const tr = state.tr;
|
|
5788
6408
|
let changed = false;
|
|
5789
6409
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
5790
|
-
if (node.type.name !== "paragraph" || node.attrs["align"] === align)
|
|
6410
|
+
if (node.type.name !== "paragraph" || node.attrs["align"] === align)
|
|
6411
|
+
return;
|
|
5791
6412
|
tr.setNodeAttribute(pos, "align", align);
|
|
5792
6413
|
changed = true;
|
|
5793
6414
|
});
|
|
@@ -5812,7 +6433,9 @@ function toggleHeading(level) {
|
|
|
5812
6433
|
const paras = selectedParagraphs(state);
|
|
5813
6434
|
if (paras.length === 0) return false;
|
|
5814
6435
|
if (dispatch) {
|
|
5815
|
-
const allThisLevel = paras.every(
|
|
6436
|
+
const allThisLevel = paras.every(
|
|
6437
|
+
(p) => p.node.attrs["heading"] === level
|
|
6438
|
+
);
|
|
5816
6439
|
const next = allThisLevel ? null : level;
|
|
5817
6440
|
const tr = state.tr;
|
|
5818
6441
|
for (const p of paras) tr.setNodeAttribute(p.pos, "heading", next);
|
|
@@ -6075,8 +6698,10 @@ var BapbongEditor = class {
|
|
|
6075
6698
|
* Built from the shared command layer; the same ops run on a backend.
|
|
6076
6699
|
* (Plugin-contributed commands are an additive follow-up.) */
|
|
6077
6700
|
commands = defaultCommands();
|
|
6701
|
+
readClipboardFallback;
|
|
6078
6702
|
constructor(stack, opts = {}) {
|
|
6079
6703
|
this.stack = stack;
|
|
6704
|
+
this.readClipboardFallback = opts.readClipboardFallback;
|
|
6080
6705
|
this.core = new RenderCore(stack, {
|
|
6081
6706
|
viewport: opts.viewport,
|
|
6082
6707
|
measureText: opts.measureText,
|
|
@@ -6093,9 +6718,12 @@ var BapbongEditor = class {
|
|
|
6093
6718
|
stack.addEventListener("dblclick", this.onDblClick);
|
|
6094
6719
|
stack.addEventListener("contextmenu", this.onContextMenu);
|
|
6095
6720
|
window.addEventListener("keydown", this.onKeyDown, true);
|
|
6096
|
-
this.plugins = new Collection(
|
|
6097
|
-
|
|
6098
|
-
|
|
6721
|
+
this.plugins = new Collection(
|
|
6722
|
+
[...createBuiltins(), ...opts.plugins ?? []],
|
|
6723
|
+
{
|
|
6724
|
+
idProperty: "name"
|
|
6725
|
+
}
|
|
6726
|
+
);
|
|
6099
6727
|
this.pointerPlugins = [...this.plugins].some((p) => p.onPointer);
|
|
6100
6728
|
this.pluginCtx = this.makePluginContext();
|
|
6101
6729
|
for (const p of this.plugins) {
|
|
@@ -6120,8 +6748,14 @@ var BapbongEditor = class {
|
|
|
6120
6748
|
setActionButton: (at, onActivate) => this.setActionButton(at, onActivate),
|
|
6121
6749
|
setFrame: (frame) => this.setFrame(frame)
|
|
6122
6750
|
};
|
|
6123
|
-
Object.defineProperty(ctx, "state", {
|
|
6124
|
-
|
|
6751
|
+
Object.defineProperty(ctx, "state", {
|
|
6752
|
+
enumerable: true,
|
|
6753
|
+
get: () => this.state
|
|
6754
|
+
});
|
|
6755
|
+
Object.defineProperty(ctx, "layout", {
|
|
6756
|
+
enumerable: true,
|
|
6757
|
+
get: () => this.core.layout
|
|
6758
|
+
});
|
|
6125
6759
|
return ctx;
|
|
6126
6760
|
}
|
|
6127
6761
|
// ── Public API ──────────────────────────────────────────────────────
|
|
@@ -6264,34 +6898,116 @@ var BapbongEditor = class {
|
|
|
6264
6898
|
this.bridge.focus();
|
|
6265
6899
|
return document.execCommand("cut");
|
|
6266
6900
|
}
|
|
6267
|
-
/** Paste clipboard content
|
|
6901
|
+
/** Paste clipboard content — HTML parsed by the schema, image blobs as
|
|
6902
|
+
* embedded images, else plain text.
|
|
6903
|
+
*
|
|
6904
|
+
* Menu-driven paste can't ride a native ClipboardEvent, so it climbs a
|
|
6905
|
+
* ladder of acquisition strategies until one yields content:
|
|
6906
|
+
* 1. the host's native clipboard reader (`readClipboardFallback`) when
|
|
6907
|
+
* provided — on WKWebView every programmatic clipboard READ (both
|
|
6908
|
+
* `navigator.clipboard.*` and execCommand) pops a "Paste" permission
|
|
6909
|
+
* callout and pends until the user taps it, so the desktop shell
|
|
6910
|
+
* must never touch the DOM clipboard from a menu click;
|
|
6911
|
+
* 2. async Clipboard API (`navigator.clipboard.read`) — browsers, where
|
|
6912
|
+
* a user-gesture read is granted silently;
|
|
6913
|
+
* 3. `document.execCommand('paste')` — legacy last resort. */
|
|
6268
6914
|
async paste() {
|
|
6269
6915
|
const view = this.bridge?.view;
|
|
6270
6916
|
if (!view) return;
|
|
6917
|
+
this.bridge?.focus();
|
|
6918
|
+
if (await this.pasteViaHostClipboard(view)) return;
|
|
6919
|
+
if (this.readClipboardFallback) return;
|
|
6920
|
+
if (await this.pasteViaClipboardApi(view)) return;
|
|
6921
|
+
document.execCommand("paste");
|
|
6922
|
+
}
|
|
6923
|
+
async pasteViaClipboardApi(view) {
|
|
6271
6924
|
try {
|
|
6272
6925
|
const items = await navigator.clipboard.read();
|
|
6926
|
+
const htmlItem = items.find((i) => i.types.includes("text/html"));
|
|
6927
|
+
if (htmlItem) {
|
|
6928
|
+
const html = await (await htmlItem.getType("text/html")).text();
|
|
6929
|
+
const dom = new DOMParser().parseFromString(html, "text/html").body;
|
|
6930
|
+
if ((dom.textContent ?? "").trim()) {
|
|
6931
|
+
const slice = import_prosemirror_model3.DOMParser.fromSchema(view.state.schema).parseSlice(
|
|
6932
|
+
dom
|
|
6933
|
+
);
|
|
6934
|
+
view.dispatch(view.state.tr.replaceSelection(slice).scrollIntoView());
|
|
6935
|
+
return true;
|
|
6936
|
+
}
|
|
6937
|
+
}
|
|
6273
6938
|
for (const item of items) {
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6939
|
+
const type = item.types.find((t) => t.startsWith("image/"));
|
|
6940
|
+
if (type && await insertImageBlobs(view, [await item.getType(type)]))
|
|
6941
|
+
return true;
|
|
6942
|
+
}
|
|
6943
|
+
const text = await navigator.clipboard.readText();
|
|
6944
|
+
if (text) {
|
|
6945
|
+
view.dispatch(view.state.tr.insertText(text).scrollIntoView());
|
|
6946
|
+
return true;
|
|
6947
|
+
}
|
|
6948
|
+
return false;
|
|
6949
|
+
} catch (err) {
|
|
6950
|
+
console.warn(
|
|
6951
|
+
"[bapbong] Clipboard API paste unavailable, falling back:",
|
|
6952
|
+
err
|
|
6953
|
+
);
|
|
6954
|
+
return false;
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6957
|
+
async pasteViaHostClipboard(view) {
|
|
6958
|
+
if (!this.readClipboardFallback) return false;
|
|
6959
|
+
try {
|
|
6960
|
+
const data = await this.readClipboardFallback();
|
|
6961
|
+
if (!data) return false;
|
|
6962
|
+
if (data.html) {
|
|
6963
|
+
const dom = new DOMParser().parseFromString(
|
|
6964
|
+
data.html,
|
|
6965
|
+
"text/html"
|
|
6966
|
+
).body;
|
|
6967
|
+
if ((dom.textContent ?? "").trim()) {
|
|
6968
|
+
const slice = import_prosemirror_model3.DOMParser.fromSchema(view.state.schema).parseSlice(
|
|
6969
|
+
dom
|
|
6970
|
+
);
|
|
6279
6971
|
view.dispatch(view.state.tr.replaceSelection(slice).scrollIntoView());
|
|
6280
|
-
return;
|
|
6972
|
+
return true;
|
|
6281
6973
|
}
|
|
6282
6974
|
}
|
|
6283
|
-
|
|
6284
|
-
|
|
6975
|
+
if (data.image?.length) {
|
|
6976
|
+
const blob = new Blob([data.image], {
|
|
6977
|
+
type: data.imageMime ?? "image/png"
|
|
6978
|
+
});
|
|
6979
|
+
if (await insertImageBlobs(view, [blob])) return true;
|
|
6980
|
+
}
|
|
6981
|
+
if (data.text) {
|
|
6982
|
+
view.dispatch(view.state.tr.insertText(data.text).scrollIntoView());
|
|
6983
|
+
return true;
|
|
6984
|
+
}
|
|
6985
|
+
return false;
|
|
6986
|
+
} catch (err) {
|
|
6987
|
+
console.warn("[bapbong] host clipboard fallback failed:", err);
|
|
6988
|
+
return false;
|
|
6285
6989
|
}
|
|
6286
6990
|
}
|
|
6287
6991
|
/** Paste clipboard text as plain text (no formatting). */
|
|
6288
6992
|
async pasteText() {
|
|
6289
6993
|
const view = this.bridge?.view;
|
|
6290
6994
|
if (!view) return;
|
|
6995
|
+
this.bridge?.focus();
|
|
6996
|
+
if (this.readClipboardFallback) {
|
|
6997
|
+
try {
|
|
6998
|
+
const data = await this.readClipboardFallback();
|
|
6999
|
+
if (data?.text)
|
|
7000
|
+
view.dispatch(view.state.tr.insertText(data.text).scrollIntoView());
|
|
7001
|
+
} catch (err) {
|
|
7002
|
+
console.warn("[bapbong] host clipboard fallback failed:", err);
|
|
7003
|
+
}
|
|
7004
|
+
return;
|
|
7005
|
+
}
|
|
6291
7006
|
try {
|
|
6292
7007
|
const text = await navigator.clipboard.readText();
|
|
6293
7008
|
if (text) view.dispatch(view.state.tr.insertText(text).scrollIntoView());
|
|
6294
|
-
} catch {
|
|
7009
|
+
} catch (err) {
|
|
7010
|
+
console.warn("[bapbong] Clipboard API readText unavailable:", err);
|
|
6295
7011
|
}
|
|
6296
7012
|
}
|
|
6297
7013
|
destroy() {
|
|
@@ -6332,7 +7048,8 @@ var BapbongEditor = class {
|
|
|
6332
7048
|
"Shift-ArrowUp": this.verticalCmd(-1, true),
|
|
6333
7049
|
"Shift-ArrowDown": this.verticalCmd(1, true)
|
|
6334
7050
|
},
|
|
6335
|
-
onUpdate: (state, tr) => this.refresh(state, tr)
|
|
7051
|
+
onUpdate: (state, tr) => this.refresh(state, tr),
|
|
7052
|
+
handlePaste: imagePasteHandler
|
|
6336
7053
|
});
|
|
6337
7054
|
this.stack.appendChild(this.bridge.dom);
|
|
6338
7055
|
this.render(this.bridge.state, true);
|
|
@@ -6361,14 +7078,25 @@ var BapbongEditor = class {
|
|
|
6361
7078
|
else this.core.paintOverlay(overlay);
|
|
6362
7079
|
const caret = this.lastCaret;
|
|
6363
7080
|
if (caret && this.bridge) {
|
|
6364
|
-
const pt = this.core.pageToCanvas({
|
|
7081
|
+
const pt = this.core.pageToCanvas({
|
|
7082
|
+
pageIndex: caret.pageIndex,
|
|
7083
|
+
x: caret.x,
|
|
7084
|
+
y: caret.y
|
|
7085
|
+
});
|
|
6365
7086
|
if (pt) this.bridge.place(pt.x, pt.y, caret.height);
|
|
6366
7087
|
}
|
|
6367
|
-
this.emitChange({
|
|
7088
|
+
this.emitChange({
|
|
7089
|
+
state,
|
|
7090
|
+
pageCount: this.core.pageCount,
|
|
7091
|
+
docChanged: contentDirty
|
|
7092
|
+
});
|
|
6368
7093
|
}
|
|
6369
7094
|
/** The caret/selection overlay in the current blink phase. */
|
|
6370
7095
|
currentOverlay() {
|
|
6371
|
-
return {
|
|
7096
|
+
return {
|
|
7097
|
+
caret: this.caretVisible ? this.lastCaret : null,
|
|
7098
|
+
selection: this.lastSelection
|
|
7099
|
+
};
|
|
6372
7100
|
}
|
|
6373
7101
|
/** Each plugin's doc-range decorations (the core resolves them to rects). */
|
|
6374
7102
|
pluginDecorations() {
|
|
@@ -6443,7 +7171,8 @@ var BapbongEditor = class {
|
|
|
6443
7171
|
* gesture), so plugins never steal keys from other inputs on the page. */
|
|
6444
7172
|
onKeyDown = (ev) => {
|
|
6445
7173
|
const target = ev.target;
|
|
6446
|
-
if (target && target !== document.body && !this.stack.contains(target))
|
|
7174
|
+
if (target && target !== document.body && !this.stack.contains(target))
|
|
7175
|
+
return;
|
|
6447
7176
|
const offered = {
|
|
6448
7177
|
key: ev.key,
|
|
6449
7178
|
ctrlKey: ev.ctrlKey,
|
|
@@ -6477,7 +7206,10 @@ var BapbongEditor = class {
|
|
|
6477
7206
|
this.lastCaret = this.core.caretRect(this.dragHead);
|
|
6478
7207
|
this.lastSelection = from === to ? [] : this.core.selectionRects(from, to);
|
|
6479
7208
|
this.caretVisible = true;
|
|
6480
|
-
this.core.paintOverlay({
|
|
7209
|
+
this.core.paintOverlay({
|
|
7210
|
+
caret: this.lastCaret,
|
|
7211
|
+
selection: this.lastSelection
|
|
7212
|
+
});
|
|
6481
7213
|
}
|
|
6482
7214
|
onPointerUp = (ev) => {
|
|
6483
7215
|
if (this.offerPointer("up", ev)) {
|
|
@@ -6538,8 +7270,16 @@ var BapbongEditor = class {
|
|
|
6538
7270
|
this.guideEl.style.cssText = "position:absolute;width:0;border-left:2px dashed #378add;pointer-events:none;z-index:5;";
|
|
6539
7271
|
this.stack.appendChild(this.guideEl);
|
|
6540
7272
|
}
|
|
6541
|
-
const top = this.core.pageToCanvas({
|
|
6542
|
-
|
|
7273
|
+
const top = this.core.pageToCanvas({
|
|
7274
|
+
pageIndex: guide.pageIndex,
|
|
7275
|
+
x: guide.x,
|
|
7276
|
+
y: guide.y
|
|
7277
|
+
});
|
|
7278
|
+
const bottom = this.core.pageToCanvas({
|
|
7279
|
+
pageIndex: guide.pageIndex,
|
|
7280
|
+
x: guide.x,
|
|
7281
|
+
y: guide.y + guide.height
|
|
7282
|
+
});
|
|
6543
7283
|
if (!top || !bottom) {
|
|
6544
7284
|
this.guideEl.style.display = "none";
|
|
6545
7285
|
return;
|
|
@@ -6587,7 +7327,11 @@ var BapbongEditor = class {
|
|
|
6587
7327
|
this.stack.appendChild(el2);
|
|
6588
7328
|
this.frameEl = el2;
|
|
6589
7329
|
}
|
|
6590
|
-
const tl = this.core.pageToCanvas({
|
|
7330
|
+
const tl = this.core.pageToCanvas({
|
|
7331
|
+
pageIndex: frame.pageIndex,
|
|
7332
|
+
x: frame.x,
|
|
7333
|
+
y: frame.y
|
|
7334
|
+
});
|
|
6591
7335
|
const br = this.core.pageToCanvas({
|
|
6592
7336
|
pageIndex: frame.pageIndex,
|
|
6593
7337
|
x: frame.x + frame.width,
|
|
@@ -6623,7 +7367,11 @@ var BapbongEditor = class {
|
|
|
6623
7367
|
this.highlightEls.forEach((el, i) => {
|
|
6624
7368
|
const r = list[i];
|
|
6625
7369
|
const tl = r && this.core.pageToCanvas({ pageIndex: r.pageIndex, x: r.x, y: r.y });
|
|
6626
|
-
const br = r && this.core.pageToCanvas({
|
|
7370
|
+
const br = r && this.core.pageToCanvas({
|
|
7371
|
+
pageIndex: r.pageIndex,
|
|
7372
|
+
x: r.x + r.width,
|
|
7373
|
+
y: r.y + r.height
|
|
7374
|
+
});
|
|
6627
7375
|
if (!tl || !br) {
|
|
6628
7376
|
el.style.display = "none";
|
|
6629
7377
|
return;
|