@coding01/docsjs 0.1.5 → 0.1.7

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.
@@ -1,2193 +0,0 @@
1
- // src/lib/htmlSnapshot.ts
2
- var SNAPSHOT_SHELL_START = '<!DOCTYPE html><html><head><meta charset="utf-8"/>';
3
- var SNAPSHOT_SHELL_END = "</head><body></body></html>";
4
- function buildHtmlSnapshot(rawHtml) {
5
- if (!rawHtml.trim()) {
6
- return `${SNAPSHOT_SHELL_START}${SNAPSHOT_SHELL_END}`;
7
- }
8
- const hasHtmlTag = /<html[\s>]/i.test(rawHtml);
9
- if (hasHtmlTag) {
10
- return rawHtml;
11
- }
12
- return `${SNAPSHOT_SHELL_START}${SNAPSHOT_SHELL_END}`.replace(
13
- "<body></body>",
14
- `<body>${rawHtml}</body>`
15
- );
16
- }
17
-
18
- // src/lib/docxHtml.ts
19
- import JSZip from "jszip";
20
- function escapeHtml(text) {
21
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
22
- }
23
- function parseXml(xmlText) {
24
- const parser = new DOMParser();
25
- return parser.parseFromString(xmlText, "application/xml");
26
- }
27
- function queryAllByLocalName(root, localName) {
28
- return Array.from(root.querySelectorAll("*")).filter((el) => el.localName === localName);
29
- }
30
- function queryByLocalName(root, localName) {
31
- return queryAllByLocalName(root, localName)[0] ?? null;
32
- }
33
- function directChildrenByLocalName(node, localName) {
34
- return Array.from(node.children).filter((child) => child.localName === localName);
35
- }
36
- function getAttr(node, name) {
37
- if (!node) return null;
38
- return node.getAttribute(name);
39
- }
40
- function emuToPx(emu) {
41
- return emu * 96 / 914400;
42
- }
43
- function twipToPx(twip) {
44
- return twip * 96 / 1440;
45
- }
46
- function parseDrawingSizePx(drawing) {
47
- const extentNode = queryAllByLocalName(drawing, "extent").find((node) => {
48
- const parent = node.parentElement;
49
- return parent?.localName === "inline" || parent?.localName === "anchor";
50
- }) ?? null;
51
- if (!extentNode) {
52
- return { widthPx: null, heightPx: null };
53
- }
54
- const rawCx = getAttr(extentNode, "cx");
55
- const rawCy = getAttr(extentNode, "cy");
56
- const cx = rawCx ? Number.parseInt(rawCx, 10) : Number.NaN;
57
- const cy = rawCy ? Number.parseInt(rawCy, 10) : Number.NaN;
58
- const widthPx = Number.isFinite(cx) && cx > 0 ? emuToPx(cx) : null;
59
- const heightPx = Number.isFinite(cy) && cy > 0 ? emuToPx(cy) : null;
60
- return { widthPx, heightPx };
61
- }
62
- function imageDimensionAttributes(sizePx) {
63
- const attrs = [];
64
- if (sizePx.widthPx !== null) attrs.push(`width="${Math.round(sizePx.widthPx)}"`);
65
- if (sizePx.heightPx !== null) attrs.push(`height="${Math.round(sizePx.heightPx)}"`);
66
- if (sizePx.widthPx !== null || sizePx.heightPx !== null) {
67
- const style = ["max-width:100%"];
68
- if (sizePx.widthPx !== null) style.push(`width:${sizePx.widthPx.toFixed(2)}px`);
69
- if (sizePx.heightPx !== null) style.push(`height:${sizePx.heightPx.toFixed(2)}px`);
70
- attrs.push(`style="${style.join(";")}"`);
71
- }
72
- return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
73
- }
74
- function parseAnchorPositionPx(anchor) {
75
- let leftPx = null;
76
- let topPx = null;
77
- const positionH = directChildrenByLocalName(anchor, "positionH")[0] ?? null;
78
- const positionV = directChildrenByLocalName(anchor, "positionV")[0] ?? null;
79
- const posH = positionH ? directChildrenByLocalName(positionH, "posOffset")[0] ?? null : null;
80
- const posV = positionV ? directChildrenByLocalName(positionV, "posOffset")[0] ?? null : null;
81
- const rawLeft = posH?.textContent?.trim() ?? "";
82
- const rawTop = posV?.textContent?.trim() ?? "";
83
- const left = rawLeft ? Number.parseFloat(rawLeft) : Number.NaN;
84
- const top = rawTop ? Number.parseFloat(rawTop) : Number.NaN;
85
- if (Number.isFinite(left)) leftPx = emuToPx(left);
86
- if (Number.isFinite(top)) topPx = emuToPx(top);
87
- return { leftPx, topPx };
88
- }
89
- function parseAnchorWrapMode(anchor) {
90
- if (directChildrenByLocalName(anchor, "wrapSquare")[0]) return "square";
91
- if (directChildrenByLocalName(anchor, "wrapTight")[0]) return "tight";
92
- if (directChildrenByLocalName(anchor, "wrapTopAndBottom")[0]) return "topAndBottom";
93
- if (directChildrenByLocalName(anchor, "wrapNone")[0]) return "none";
94
- return null;
95
- }
96
- function parseAnchorMeta(drawing) {
97
- const anchor = directChildrenByLocalName(drawing, "anchor")[0] ?? null;
98
- if (!anchor) return null;
99
- const positionH = directChildrenByLocalName(anchor, "positionH")[0] ?? null;
100
- const positionV = directChildrenByLocalName(anchor, "positionV")[0] ?? null;
101
- const relativeFromH = getAttr(positionH, "relativeFrom");
102
- const relativeFromV = getAttr(positionV, "relativeFrom");
103
- const parseDistPx = (name) => {
104
- const raw = getAttr(anchor, name);
105
- const emu = raw ? Number.parseInt(raw, 10) : Number.NaN;
106
- return Number.isFinite(emu) && emu >= 0 ? emuToPx(emu) : null;
107
- };
108
- const rawHeight = getAttr(anchor, "relativeHeight");
109
- const parsedHeight = rawHeight ? Number.parseInt(rawHeight, 10) : Number.NaN;
110
- const boolAttr = (name, fallback) => {
111
- const raw = (getAttr(anchor, name) ?? "").toLowerCase();
112
- if (raw === "1" || raw === "true" || raw === "on") return true;
113
- if (raw === "0" || raw === "false" || raw === "off") return false;
114
- return fallback;
115
- };
116
- return {
117
- position: parseAnchorPositionPx(anchor),
118
- wrapMode: parseAnchorWrapMode(anchor),
119
- distTPx: parseDistPx("distT"),
120
- distBPx: parseDistPx("distB"),
121
- distLPx: parseDistPx("distL"),
122
- distRPx: parseDistPx("distR"),
123
- relativeFromH,
124
- relativeFromV,
125
- behindDoc: boolAttr("behindDoc", false),
126
- allowOverlap: boolAttr("allowOverlap", true),
127
- layoutInCell: boolAttr("layoutInCell", true),
128
- relativeHeight: Number.isFinite(parsedHeight) ? parsedHeight : null
129
- };
130
- }
131
- function mergeImageStyle(baseAttrs, anchorMeta) {
132
- if (!anchorMeta) return baseAttrs;
133
- const { position, wrapMode } = anchorMeta;
134
- if (position.leftPx === null && position.topPx === null) return baseAttrs;
135
- const styleParts = [
136
- "position:absolute",
137
- position.leftPx !== null ? `left:${position.leftPx.toFixed(2)}px` : "",
138
- position.topPx !== null ? `top:${position.topPx.toFixed(2)}px` : "",
139
- `z-index:${anchorMeta.behindDoc ? 0 : anchorMeta.relativeHeight ?? 3}`,
140
- anchorMeta.distTPx !== null ? `margin-top:${anchorMeta.distTPx.toFixed(2)}px` : "",
141
- anchorMeta.distBPx !== null ? `margin-bottom:${anchorMeta.distBPx.toFixed(2)}px` : "",
142
- anchorMeta.distLPx !== null ? `margin-left:${anchorMeta.distLPx.toFixed(2)}px` : "",
143
- anchorMeta.distRPx !== null ? `margin-right:${anchorMeta.distRPx.toFixed(2)}px` : ""
144
- ].filter((x) => x.length > 0);
145
- if (wrapMode === "topAndBottom") {
146
- styleParts.push("display:block", "clear:both");
147
- }
148
- const anchorAttrs = [
149
- `data-word-anchor="1"`,
150
- wrapMode ? `data-word-wrap="${wrapMode}"` : "",
151
- anchorMeta.relativeFromH ? `data-word-anchor-relh="${escapeHtml(anchorMeta.relativeFromH)}"` : "",
152
- anchorMeta.relativeFromV ? `data-word-anchor-relv="${escapeHtml(anchorMeta.relativeFromV)}"` : "",
153
- anchorMeta.behindDoc ? `data-word-anchor-behind="1"` : `data-word-anchor-behind="0"`,
154
- anchorMeta.allowOverlap ? `data-word-anchor-overlap="1"` : `data-word-anchor-overlap="0"`,
155
- anchorMeta.layoutInCell ? `data-word-anchor-layout-cell="1"` : `data-word-anchor-layout-cell="0"`
156
- ].filter((x) => x.length > 0).join(" ");
157
- if (!baseAttrs.includes("style=")) {
158
- return `${baseAttrs} style="${styleParts.join(";")}" ${anchorAttrs}`;
159
- }
160
- return baseAttrs.replace(/style="([^"]*)"/, (_m, styleText) => {
161
- const merged = [styleText, ...styleParts].filter((x) => x.length > 0).join(";");
162
- return `style="${merged}" ${anchorAttrs}`;
163
- });
164
- }
165
- function parseDocRelsMap(relsXmlText) {
166
- if (!relsXmlText) return {};
167
- const rels = parseXml(relsXmlText);
168
- const relationNodes = queryAllByLocalName(rels, "Relationship");
169
- const map = {};
170
- for (const rel of relationNodes) {
171
- const id = getAttr(rel, "Id");
172
- const target = getAttr(rel, "Target");
173
- if (!id || !target) continue;
174
- map[id] = target;
175
- }
176
- return map;
177
- }
178
- function extToMime(ext) {
179
- const lower = ext.toLowerCase();
180
- if (lower === "png") return "image/png";
181
- if (lower === "jpg" || lower === "jpeg") return "image/jpeg";
182
- if (lower === "gif") return "image/gif";
183
- if (lower === "webp") return "image/webp";
184
- if (lower === "bmp") return "image/bmp";
185
- if (lower === "svg") return "image/svg+xml";
186
- return "application/octet-stream";
187
- }
188
- function normalizeWordPath(relTarget) {
189
- const normalized = relTarget.replace(/\\/g, "/").replace(/^\/+/, "");
190
- if (normalized.startsWith("word/")) return normalized;
191
- if (normalized.startsWith("../")) return `word/${normalized.replace(/^(\.\.\/)+/, "")}`;
192
- return `word/${normalized}`;
193
- }
194
- async function imageRidToDataUrl(zip, relMap, rid) {
195
- const relTarget = relMap[rid];
196
- if (!relTarget) return null;
197
- const path = normalizeWordPath(relTarget);
198
- const file = zip.file(path);
199
- if (!file) return null;
200
- const base64 = await file.async("base64");
201
- const ext = path.split(".").pop() ?? "bin";
202
- const mime = extToMime(ext);
203
- return `data:${mime};base64,${base64}`;
204
- }
205
- async function readXmlByRid(zip, relMap, rid) {
206
- const relTarget = relMap[rid];
207
- if (!relTarget) return null;
208
- const path = normalizeWordPath(relTarget);
209
- const file = zip.file(path);
210
- return file ? file.async("string") : null;
211
- }
212
- function parseChartType(chartDoc) {
213
- const known = ["barChart", "lineChart", "pieChart", "areaChart", "scatterChart", "radarChart", "doughnutChart"];
214
- for (const type of known) {
215
- if (queryByLocalName(chartDoc, type)) return type.replace(/Chart$/, "");
216
- }
217
- return "unknown";
218
- }
219
- function parseChartSummary(chartXmlText) {
220
- const chartDoc = parseXml(chartXmlText);
221
- const title = queryAllByLocalName(chartDoc, "t").map((n) => (n.textContent ?? "").trim()).find((v) => v.length > 0) ?? "Chart";
222
- const seriesCount = queryAllByLocalName(chartDoc, "ser").length;
223
- const pointCount = queryAllByLocalName(chartDoc, "pt").length;
224
- const type = parseChartType(chartDoc);
225
- return { title, type, seriesCount, pointCount };
226
- }
227
- function extractSmartArtText(diagramXmlText) {
228
- const diagramDoc = parseXml(diagramXmlText);
229
- return queryAllByLocalName(diagramDoc, "t").map((n) => (n.textContent ?? "").trim()).filter((v) => v.length > 0).slice(0, 12);
230
- }
231
- function ommlNodeToText(node) {
232
- if (node.localName === "t") return node.textContent ?? "";
233
- if (node.localName === "f") {
234
- const num = queryByLocalName(node, "num");
235
- const den = queryByLocalName(node, "den");
236
- return `(${num ? ommlNodeToText(num) : "?"})/(${den ? ommlNodeToText(den) : "?"})`;
237
- }
238
- if (node.localName === "sSup") {
239
- const e = queryByLocalName(node, "e");
240
- const sup = queryByLocalName(node, "sup");
241
- return `${e ? ommlNodeToText(e) : ""}^(${sup ? ommlNodeToText(sup) : ""})`;
242
- }
243
- if (node.localName === "sSub") {
244
- const e = queryByLocalName(node, "e");
245
- const sub = queryByLocalName(node, "sub");
246
- return `${e ? ommlNodeToText(e) : ""}_(${sub ? ommlNodeToText(sub) : ""})`;
247
- }
248
- if (node.localName === "rad") {
249
- const e = queryByLocalName(node, "e");
250
- return `sqrt(${e ? ommlNodeToText(e) : ""})`;
251
- }
252
- return Array.from(node.children).map((child) => ommlNodeToText(child)).join("");
253
- }
254
- function runStyleToCss(rPr) {
255
- if (!rPr) return "";
256
- const declarations = [];
257
- if (queryByLocalName(rPr, "b")) declarations.push("font-weight:700");
258
- if (queryByLocalName(rPr, "i")) declarations.push("font-style:italic");
259
- if (queryByLocalName(rPr, "u")) declarations.push("text-decoration:underline");
260
- if (queryByLocalName(rPr, "strike")) declarations.push("text-decoration:line-through");
261
- const color = queryByLocalName(rPr, "color");
262
- const colorVal = getAttr(color, "w:val") ?? getAttr(color, "val");
263
- if (colorVal && colorVal.toLowerCase() !== "auto") declarations.push(`color:#${colorVal}`);
264
- const highlight = queryByLocalName(rPr, "highlight");
265
- const highlightVal = (getAttr(highlight, "w:val") ?? getAttr(highlight, "val") ?? "").toLowerCase();
266
- if (highlightVal === "yellow") declarations.push("background-color:#fff200");
267
- const vertAlign = queryByLocalName(rPr, "vertAlign");
268
- const vertVal = (getAttr(vertAlign, "w:val") ?? getAttr(vertAlign, "val") ?? "").toLowerCase();
269
- if (vertVal === "superscript") declarations.push("vertical-align:super;font-size:0.83em");
270
- if (vertVal === "subscript") declarations.push("vertical-align:sub;font-size:0.83em");
271
- return declarations.join(";");
272
- }
273
- function paragraphTag(paragraph) {
274
- const pPr = queryByLocalName(paragraph, "pPr");
275
- const pStyle = pPr ? queryByLocalName(pPr, "pStyle") : null;
276
- const val = (getAttr(pStyle, "w:val") ?? getAttr(pStyle, "val") ?? "").toLowerCase();
277
- if (val.includes("heading1") || val === "1" || val === "heading 1") return "h1";
278
- if (val.includes("heading2") || val === "2" || val === "heading 2") return "h2";
279
- if (val.includes("heading3") || val === "3" || val === "heading 3") return "h3";
280
- return "p";
281
- }
282
- function paragraphAlignStyle(paragraph) {
283
- const pPr = queryByLocalName(paragraph, "pPr");
284
- const jc = pPr ? queryByLocalName(pPr, "jc") : null;
285
- const align = (getAttr(jc, "w:val") ?? getAttr(jc, "val") ?? "").toLowerCase();
286
- if (align === "center" || align === "right" || align === "left") {
287
- return `text-align:${align};`;
288
- }
289
- return "";
290
- }
291
- function paragraphDataAttr(paragraphIndex) {
292
- return paragraphIndex === null ? "" : ` data-word-p-index="${paragraphIndex}"`;
293
- }
294
- function parseFootnotesMap(footnotesXmlText) {
295
- if (!footnotesXmlText) return {};
296
- const footnotesDoc = parseXml(footnotesXmlText);
297
- const map = {};
298
- const footnotes = queryAllByLocalName(footnotesDoc, "footnote");
299
- for (const footnote of footnotes) {
300
- const idRaw = getAttr(footnote, "w:id") ?? getAttr(footnote, "id");
301
- const idNum = idRaw ? Number.parseInt(idRaw, 10) : Number.NaN;
302
- if (!Number.isFinite(idNum) || idNum <= 0) continue;
303
- const paragraphs = queryAllByLocalName(footnote, "p");
304
- const text = paragraphs.map((p) => paragraphText(p)).join("<br/>").trim();
305
- if (!text) continue;
306
- map[String(idNum)] = text;
307
- }
308
- return map;
309
- }
310
- function parseCommentsMap(commentsXmlText) {
311
- if (!commentsXmlText) return {};
312
- const commentsDoc = parseXml(commentsXmlText);
313
- const map = {};
314
- const comments = queryAllByLocalName(commentsDoc, "comment");
315
- for (const comment of comments) {
316
- const idRaw = getAttr(comment, "w:id") ?? getAttr(comment, "id");
317
- if (!idRaw) continue;
318
- const paragraphs = queryAllByLocalName(comment, "p");
319
- const text = paragraphs.map((p) => paragraphText(p)).join("<br/>").trim();
320
- if (!text) continue;
321
- map[idRaw] = {
322
- author: getAttr(comment, "w:author") ?? getAttr(comment, "author"),
323
- date: getAttr(comment, "w:date") ?? getAttr(comment, "date"),
324
- text
325
- };
326
- }
327
- return map;
328
- }
329
- function parseEndnotesMap(endnotesXmlText) {
330
- if (!endnotesXmlText) return {};
331
- const endnotesDoc = parseXml(endnotesXmlText);
332
- const map = {};
333
- const endnotes = queryAllByLocalName(endnotesDoc, "endnote");
334
- for (const endnote of endnotes) {
335
- const idRaw = getAttr(endnote, "w:id") ?? getAttr(endnote, "id");
336
- const idNum = idRaw ? Number.parseInt(idRaw, 10) : Number.NaN;
337
- if (!Number.isFinite(idNum) || idNum <= 0) continue;
338
- const paragraphs = queryAllByLocalName(endnote, "p");
339
- const text = paragraphs.map((p) => paragraphText(p)).join("<br/>").trim();
340
- if (!text) continue;
341
- map[String(idNum)] = text;
342
- }
343
- return map;
344
- }
345
- function renderFootnotesSection(usedIds, footnotesMap) {
346
- const uniq = [...new Set(usedIds)].filter((id) => footnotesMap[id]);
347
- if (uniq.length === 0) return "";
348
- const items = uniq.map((id) => `<li id="word-footnote-${id}" data-word-footnote-id="${id}">${footnotesMap[id]}</li>`).join("");
349
- return `<section data-word-footnotes="1"><hr/><ol>${items}</ol></section>`;
350
- }
351
- function renderCommentsSection(usedIds, commentsMap) {
352
- const uniq = [...new Set(usedIds)].filter((id) => commentsMap[id]);
353
- if (uniq.length === 0) return "";
354
- const items = uniq.map((id) => {
355
- const item = commentsMap[id];
356
- const meta = [item.author ?? "", item.date ?? ""].filter((x) => x.length > 0).join(" \xB7 ");
357
- const metaHtml = meta ? `<div data-word-comment-meta="1">${escapeHtml(meta)}</div>` : "";
358
- return `<li id="word-comment-${id}" data-word-comment-id="${id}">${metaHtml}<div>${item.text}</div></li>`;
359
- }).join("");
360
- return `<section data-word-comments="1"><hr/><ol>${items}</ol></section>`;
361
- }
362
- function renderEndnotesSection(usedIds, endnotesMap) {
363
- const uniq = [...new Set(usedIds)].filter((id) => endnotesMap[id]);
364
- if (uniq.length === 0) return "";
365
- const items = uniq.map((id) => `<li id="word-endnote-${id}" data-word-endnote-id="${id}">${endnotesMap[id]}</li>`).join("");
366
- return `<section data-word-endnotes="1"><hr/><ol>${items}</ol></section>`;
367
- }
368
- async function paragraphToHtml(zip, relMap, paragraph, paragraphIndex, footnotesMap, usedFootnoteIds, endnotesMap, usedEndnoteIds, commentsMap, usedCommentIds) {
369
- const tag = paragraphTag(paragraph);
370
- const alignStyle = paragraphAlignStyle(paragraph);
371
- const dataAttr = paragraphDataAttr(paragraphIndex);
372
- const hasRenderableNode = queryAllByLocalName(paragraph, "r").length > 0 || queryAllByLocalName(paragraph, "oMath").length > 0 || queryAllByLocalName(paragraph, "oMathPara").length > 0;
373
- if (!hasRenderableNode) {
374
- return `<${tag}${dataAttr}${alignStyle ? ` style="${alignStyle}"` : ""}><br/></${tag}>`;
375
- }
376
- function parseRevisionMeta(node, type) {
377
- return {
378
- type,
379
- id: getAttr(node, "w:id") ?? getAttr(node, "id"),
380
- author: getAttr(node, "w:author") ?? getAttr(node, "author"),
381
- date: getAttr(node, "w:date") ?? getAttr(node, "date")
382
- };
383
- }
384
- function inferRevisionMeta(run, fallback) {
385
- if (fallback) return fallback;
386
- let cursor = run;
387
- while (cursor) {
388
- if (cursor.localName === "ins") return parseRevisionMeta(cursor, "ins");
389
- if (cursor.localName === "del") return parseRevisionMeta(cursor, "del");
390
- if (cursor.localName === "p") break;
391
- cursor = cursor.parentElement;
392
- }
393
- return null;
394
- }
395
- function revisionMetaAttrs(meta) {
396
- const attrs = [`data-word-revision="${meta.type}"`];
397
- if (meta.id) attrs.push(`data-word-revision-id="${escapeHtml(meta.id)}"`);
398
- if (meta.author) attrs.push(`data-word-revision-author="${escapeHtml(meta.author)}"`);
399
- if (meta.date) attrs.push(`data-word-revision-date="${escapeHtml(meta.date)}"`);
400
- return attrs.join(" ");
401
- }
402
- async function runToHtml(run, revisionFallback) {
403
- const result = [];
404
- const rPr = queryByLocalName(run, "rPr");
405
- const css = runStyleToCss(rPr);
406
- const footnoteRef = queryByLocalName(run, "footnoteReference");
407
- const footnoteId = getAttr(footnoteRef, "w:id") ?? getAttr(footnoteRef, "id");
408
- if (footnoteId && footnotesMap[footnoteId]) {
409
- usedFootnoteIds.push(footnoteId);
410
- result.push(
411
- `<sup data-word-footnote-ref="${footnoteId}"><a href="#word-footnote-${footnoteId}">[${footnoteId}]</a></sup>`
412
- );
413
- return result;
414
- }
415
- const endnoteRef = queryByLocalName(run, "endnoteReference");
416
- const endnoteId = getAttr(endnoteRef, "w:id") ?? getAttr(endnoteRef, "id");
417
- if (endnoteId && endnotesMap[endnoteId]) {
418
- usedEndnoteIds.push(endnoteId);
419
- result.push(
420
- `<sup data-word-endnote-ref="${endnoteId}"><a href="#word-endnote-${endnoteId}">[${endnoteId}]</a></sup>`
421
- );
422
- return result;
423
- }
424
- const commentRef = queryByLocalName(run, "commentReference");
425
- const commentId = getAttr(commentRef, "w:id") ?? getAttr(commentRef, "id");
426
- if (commentId && commentsMap[commentId]) {
427
- usedCommentIds.push(commentId);
428
- result.push(
429
- `<sup data-word-comment-ref="${commentId}"><a href="#word-comment-${commentId}">[c${commentId}]</a></sup>`
430
- );
431
- return result;
432
- }
433
- const drawing = queryByLocalName(run, "drawing");
434
- if (drawing) {
435
- const blip = queryByLocalName(drawing, "blip");
436
- const rid = getAttr(blip, "r:embed") ?? getAttr(blip, "embed");
437
- if (rid) {
438
- const src = await imageRidToDataUrl(zip, relMap, rid);
439
- if (src) {
440
- const imageSize = parseDrawingSizePx(drawing);
441
- const dimensionAttrs = imageDimensionAttributes(imageSize);
442
- const anchorMeta = parseAnchorMeta(drawing);
443
- const attrs = mergeImageStyle(dimensionAttrs, anchorMeta);
444
- result.push(`<img src="${src}" alt="word-image"${attrs}/>`);
445
- return result;
446
- }
447
- }
448
- const chartRef = queryByLocalName(drawing, "chart");
449
- const chartRid = getAttr(chartRef, "r:id") ?? getAttr(chartRef, "id");
450
- if (chartRid) {
451
- const chartXmlText = await readXmlByRid(zip, relMap, chartRid);
452
- if (chartXmlText) {
453
- const summary = parseChartSummary(chartXmlText);
454
- result.push(
455
- `<figure data-word-chart="1" data-word-chart-type="${summary.type}" data-word-chart-series="${summary.seriesCount}" data-word-chart-points="${summary.pointCount}"><figcaption>${escapeHtml(summary.title)}</figcaption><div>Chart(${escapeHtml(summary.type)}): series=${summary.seriesCount}, points=${summary.pointCount}</div></figure>`
456
- );
457
- return result;
458
- }
459
- }
460
- const smartArtRef = queryByLocalName(drawing, "relIds");
461
- const smartArtRid = getAttr(smartArtRef, "r:dm") ?? getAttr(smartArtRef, "dm");
462
- if (smartArtRid) {
463
- const diagramXmlText = await readXmlByRid(zip, relMap, smartArtRid);
464
- const textItems = diagramXmlText ? extractSmartArtText(diagramXmlText) : [];
465
- const preview = textItems.length > 0 ? `: ${escapeHtml(textItems.join(" / "))}` : "";
466
- result.push(
467
- `<figure data-word-smartart="1" data-word-smartart-items="${textItems.length}"><figcaption>SmartArt fallback${preview}</figcaption></figure>`
468
- );
469
- return result;
470
- }
471
- }
472
- const texts = queryAllByLocalName(run, "t").map((t) => t.textContent ?? "").join("");
473
- const delTexts = queryAllByLocalName(run, "delText").map((t) => t.textContent ?? "").join("");
474
- const brNodes = queryAllByLocalName(run, "br");
475
- const pageBreakCount = brNodes.filter((node) => {
476
- const type = (getAttr(node, "w:type") ?? getAttr(node, "type") ?? "").toLowerCase();
477
- return type === "page";
478
- }).length;
479
- const lineBreakCount = Math.max(0, brNodes.length - pageBreakCount);
480
- const runText2 = `${escapeHtml(texts || delTexts)}${"<br/>".repeat(lineBreakCount)}`;
481
- if (runText2) {
482
- const revisionMeta = inferRevisionMeta(run, revisionFallback);
483
- if (css) {
484
- const span = `<span style="${css}">${runText2}</span>`;
485
- if (revisionMeta) {
486
- const tagName = revisionMeta.type === "ins" ? "ins" : "del";
487
- result.push(`<${tagName} ${revisionMetaAttrs(revisionMeta)}>${span}</${tagName}>`);
488
- } else {
489
- result.push(span);
490
- }
491
- } else if (revisionMeta) {
492
- const tagName = revisionMeta.type === "ins" ? "ins" : "del";
493
- result.push(`<${tagName} ${revisionMetaAttrs(revisionMeta)}>${runText2}</${tagName}>`);
494
- } else {
495
- result.push(runText2);
496
- }
497
- }
498
- for (let i = 0; i < pageBreakCount; i += 1) {
499
- result.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
500
- }
501
- return result;
502
- }
503
- async function nodeToHtml(node, revisionFallback) {
504
- if (node.localName === "commentRangeStart") {
505
- const id = getAttr(node, "w:id") ?? getAttr(node, "id");
506
- return id ? [`<span data-word-comment-range-start="${id}"></span>`] : [];
507
- }
508
- if (node.localName === "commentRangeEnd") {
509
- const id = getAttr(node, "w:id") ?? getAttr(node, "id");
510
- return id ? [`<span data-word-comment-range-end="${id}"></span>`] : [];
511
- }
512
- if (node.localName === "r") {
513
- return runToHtml(node, revisionFallback);
514
- }
515
- if (node.localName === "oMath" || node.localName === "oMathPara") {
516
- const linear = ommlNodeToText(node).trim();
517
- if (!linear) return [];
518
- return [`<span data-word-omml="1">${escapeHtml(linear)}</span>`];
519
- }
520
- if (node.localName === "ins" || node.localName === "del") {
521
- const scopedMeta = parseRevisionMeta(node, node.localName === "ins" ? "ins" : "del");
522
- const nested2 = [];
523
- for (const child of Array.from(node.children)) {
524
- nested2.push(...await nodeToHtml(child, scopedMeta));
525
- }
526
- return nested2;
527
- }
528
- const nested = [];
529
- for (const child of Array.from(node.children)) {
530
- nested.push(...await nodeToHtml(child, revisionFallback));
531
- }
532
- return nested;
533
- }
534
- const parts = [];
535
- const renderedPageBreakCount = queryAllByLocalName(paragraph, "lastRenderedPageBreak").length;
536
- for (let i = 0; i < renderedPageBreakCount; i += 1) {
537
- parts.push(`<span data-word-page-break="1" style="display:block;break-before:page"></span>`);
538
- }
539
- for (const child of Array.from(paragraph.children)) {
540
- parts.push(...await nodeToHtml(child, null));
541
- }
542
- const content = parts.join("") || "<br/>";
543
- return `<${tag}${dataAttr}${alignStyle ? ` style="${alignStyle}"` : ""}>${content}</${tag}>`;
544
- }
545
- function runText(run) {
546
- const text = queryAllByLocalName(run, "t").map((t) => t.textContent ?? "").join("");
547
- const delText = queryAllByLocalName(run, "delText").map((t) => t.textContent ?? "").join("");
548
- const brCount = queryAllByLocalName(run, "br").length;
549
- return `${escapeHtml(text || delText)}${"<br/>".repeat(brCount)}`;
550
- }
551
- function paragraphText(paragraph) {
552
- const runs = queryAllByLocalName(paragraph, "r");
553
- const content = runs.map((run) => runText(run)).join("");
554
- return content || "<br/>";
555
- }
556
- function parseTcGridSpan(tc) {
557
- const tcPr = directChildrenByLocalName(tc, "tcPr")[0] ?? null;
558
- const gridSpan = tcPr ? directChildrenByLocalName(tcPr, "gridSpan")[0] ?? null : null;
559
- const rawVal = getAttr(gridSpan, "w:val") ?? getAttr(gridSpan, "val");
560
- const parsed = rawVal ? Number.parseInt(rawVal, 10) : Number.NaN;
561
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
562
- }
563
- function parseTcVMerge(tc) {
564
- const tcPr = directChildrenByLocalName(tc, "tcPr")[0] ?? null;
565
- const vMerge = tcPr ? directChildrenByLocalName(tcPr, "vMerge")[0] ?? null : null;
566
- if (!vMerge) return "none";
567
- const rawVal = (getAttr(vMerge, "w:val") ?? getAttr(vMerge, "val") ?? "continue").toLowerCase();
568
- return rawVal === "restart" ? "restart" : "continue";
569
- }
570
- function parseTblGridWidthsPx(table) {
571
- const grid = directChildrenByLocalName(table, "tblGrid")[0] ?? null;
572
- if (!grid) return [];
573
- return directChildrenByLocalName(grid, "gridCol").map((col) => {
574
- const raw = getAttr(col, "w:w") ?? getAttr(col, "w");
575
- const twip = raw ? Number.parseInt(raw, 10) : Number.NaN;
576
- return Number.isFinite(twip) && twip > 0 ? twipToPx(twip) : 0;
577
- }).filter((px) => px > 0);
578
- }
579
- function borderSizeToPx(size) {
580
- return size / 6;
581
- }
582
- function parseBorderCss(borderNode) {
583
- if (!borderNode) return null;
584
- const val = (getAttr(borderNode, "w:val") ?? getAttr(borderNode, "val") ?? "").toLowerCase();
585
- if (!val || val === "nil" || val === "none") return "none";
586
- const color = (getAttr(borderNode, "w:color") ?? getAttr(borderNode, "color") ?? "222222").replace(/^#/, "");
587
- const rawSize = getAttr(borderNode, "w:sz") ?? getAttr(borderNode, "sz");
588
- const size = rawSize ? Number.parseInt(rawSize, 10) : Number.NaN;
589
- const px = Number.isFinite(size) && size > 0 ? borderSizeToPx(size) : 1;
590
- const style = val === "single" ? "solid" : val;
591
- return `${px.toFixed(2)}px ${style} #${color}`;
592
- }
593
- function parseTableStyleProfile(table) {
594
- const tblPr = directChildrenByLocalName(table, "tblPr")[0] ?? null;
595
- const tblBorders = tblPr ? directChildrenByLocalName(tblPr, "tblBorders")[0] ?? null : null;
596
- const layout = tblPr ? directChildrenByLocalName(tblPr, "tblLayout")[0] ?? null : null;
597
- const spacing = tblPr ? directChildrenByLocalName(tblPr, "tblCellSpacing")[0] ?? null : null;
598
- const spacingType = (getAttr(spacing, "w:type") ?? getAttr(spacing, "type") ?? "dxa").toLowerCase();
599
- const spacingRaw = getAttr(spacing, "w:w") ?? getAttr(spacing, "w");
600
- const spacingVal = spacingRaw ? Number.parseFloat(spacingRaw) : Number.NaN;
601
- const borderSpacingPx = spacingType === "dxa" && Number.isFinite(spacingVal) && spacingVal > 0 ? twipToPx(spacingVal) : 0;
602
- const borderCollapse = borderSpacingPx > 0 ? "separate" : "collapse";
603
- const tableLayout = (getAttr(layout, "w:type") ?? getAttr(layout, "type") ?? "").toLowerCase() === "autofit" ? "auto" : "fixed";
604
- const top = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "top")[0] ?? null : null);
605
- const bottom = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "bottom")[0] ?? null : null);
606
- const left = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "left")[0] ?? null : null);
607
- const right = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "right")[0] ?? null : null);
608
- const insideH = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "insideH")[0] ?? null : null);
609
- const insideV = parseBorderCss(tblBorders ? directChildrenByLocalName(tblBorders, "insideV")[0] ?? null : null);
610
- const borderCss = top ?? right ?? bottom ?? left ?? "1px solid #222";
611
- return {
612
- tableLayout,
613
- borderCollapse,
614
- borderSpacingPx,
615
- borderCss,
616
- insideHCss: insideH,
617
- insideVCss: insideV
618
- };
619
- }
620
- function parseTableWidthStyle(table, gridWidthsPx) {
621
- const tblPr = directChildrenByLocalName(table, "tblPr")[0] ?? null;
622
- const tblW = tblPr ? directChildrenByLocalName(tblPr, "tblW")[0] ?? null : null;
623
- const type = (getAttr(tblW, "w:type") ?? getAttr(tblW, "type") ?? "").toLowerCase();
624
- const rawVal = getAttr(tblW, "w:w") ?? getAttr(tblW, "w");
625
- const numericVal = rawVal ? Number.parseFloat(rawVal) : Number.NaN;
626
- if (type === "dxa" && Number.isFinite(numericVal) && numericVal > 0) {
627
- return `width:${twipToPx(numericVal).toFixed(2)}px`;
628
- }
629
- if (type === "pct" && Number.isFinite(numericVal) && numericVal > 0) {
630
- return `width:${(numericVal / 50).toFixed(2)}%`;
631
- }
632
- const gridTotal = gridWidthsPx.reduce((sum, item) => sum + item, 0);
633
- if (gridTotal > 0) return `width:${gridTotal.toFixed(2)}px;max-width:100%`;
634
- return "width:100%";
635
- }
636
- function parseCellWidthStyle(cell, colCursor, colSpan, gridWidthsPx) {
637
- const tcPr = directChildrenByLocalName(cell, "tcPr")[0] ?? null;
638
- const tcW = tcPr ? directChildrenByLocalName(tcPr, "tcW")[0] ?? null : null;
639
- const type = (getAttr(tcW, "w:type") ?? getAttr(tcW, "type") ?? "").toLowerCase();
640
- const rawVal = getAttr(tcW, "w:w") ?? getAttr(tcW, "w");
641
- const numericVal = rawVal ? Number.parseFloat(rawVal) : Number.NaN;
642
- if (type === "dxa" && Number.isFinite(numericVal) && numericVal > 0) {
643
- return `width:${twipToPx(numericVal).toFixed(2)}px`;
644
- }
645
- if (type === "pct" && Number.isFinite(numericVal) && numericVal > 0) {
646
- return `width:${(numericVal / 50).toFixed(2)}%`;
647
- }
648
- const width = gridWidthsPx.slice(colCursor, colCursor + colSpan).reduce((sum, item) => sum + item, 0);
649
- if (width > 0) return `width:${width.toFixed(2)}px`;
650
- return "";
651
- }
652
- function parseCellBorderStyle(cell, tableStyle) {
653
- const tcPr = directChildrenByLocalName(cell, "tcPr")[0] ?? null;
654
- const tcBorders = tcPr ? directChildrenByLocalName(tcPr, "tcBorders")[0] ?? null : null;
655
- if (!tcBorders) {
656
- const fallback = tableStyle.insideHCss ?? tableStyle.insideVCss ?? tableStyle.borderCss;
657
- return `border:${fallback}`;
658
- }
659
- const top = parseBorderCss(directChildrenByLocalName(tcBorders, "top")[0] ?? null) ?? tableStyle.insideHCss ?? tableStyle.borderCss;
660
- const right = parseBorderCss(directChildrenByLocalName(tcBorders, "right")[0] ?? null) ?? tableStyle.insideVCss ?? tableStyle.borderCss;
661
- const bottom = parseBorderCss(directChildrenByLocalName(tcBorders, "bottom")[0] ?? null) ?? tableStyle.insideHCss ?? tableStyle.borderCss;
662
- const left = parseBorderCss(directChildrenByLocalName(tcBorders, "left")[0] ?? null) ?? tableStyle.insideVCss ?? tableStyle.borderCss;
663
- return `border-top:${top};border-right:${right};border-bottom:${bottom};border-left:${left}`;
664
- }
665
- function tableCellHtml(cell, paragraphIndexMap) {
666
- const blocks = [];
667
- for (const child of Array.from(cell.children)) {
668
- if (child.localName === "tcPr") continue;
669
- if (child.localName === "p") {
670
- const paragraphIndex = paragraphIndexMap.get(child) ?? null;
671
- blocks.push(`<p${paragraphDataAttr(paragraphIndex)}>${paragraphText(child)}</p>`);
672
- continue;
673
- }
674
- if (child.localName === "tbl") {
675
- blocks.push(tableToHtml(child, paragraphIndexMap));
676
- continue;
677
- }
678
- }
679
- if (blocks.length > 0) return blocks.join("");
680
- const text = queryAllByLocalName(cell, "t").map((t) => t.textContent ?? "").join("").trim();
681
- return escapeHtml(text) || "<br/>";
682
- }
683
- function tableToHtml(table, paragraphIndexMap) {
684
- const rows = directChildrenByLocalName(table, "tr");
685
- const gridWidthsPx = parseTblGridWidthsPx(table);
686
- const tableStyle = parseTableStyleProfile(table);
687
- const activeByCol = /* @__PURE__ */ new Map();
688
- const allOrigins = [];
689
- let nextOriginId = 1;
690
- const htmlRows = rows.map((row, rowIndex) => {
691
- const directCells = directChildrenByLocalName(row, "tc");
692
- const continued = /* @__PURE__ */ new Set();
693
- const emittedCells = [];
694
- let colCursor = 0;
695
- for (const cell of directCells) {
696
- const colSpan = parseTcGridSpan(cell);
697
- const vMerge = parseTcVMerge(cell);
698
- if (vMerge === "continue") {
699
- const activeOrigins = Array.from(new Set(activeByCol.values())).filter((origin2) => !continued.has(origin2)).sort((a, b) => a.startCol - b.startCol);
700
- const origin = activeOrigins.find((item) => item.startCol >= colCursor) ?? activeOrigins[0] ?? null;
701
- if (origin) {
702
- origin.rowSpan += 1;
703
- continued.add(origin);
704
- colCursor = origin.startCol + origin.colSpan;
705
- }
706
- continue;
707
- }
708
- while (activeByCol.has(colCursor)) {
709
- colCursor += 1;
710
- }
711
- const html = tableCellHtml(cell, paragraphIndexMap);
712
- const attrs = [];
713
- const widthStyle = parseCellWidthStyle(cell, colCursor, colSpan, gridWidthsPx);
714
- const borderStyle = parseCellBorderStyle(cell, tableStyle);
715
- if (vMerge === "restart") {
716
- const origin = {
717
- id: `m${nextOriginId}`,
718
- startCol: colCursor,
719
- colSpan,
720
- rowSpan: 1,
721
- startedRow: rowIndex
722
- };
723
- nextOriginId += 1;
724
- allOrigins.push(origin);
725
- for (let i = 0; i < colSpan; i += 1) {
726
- activeByCol.set(colCursor + i, origin);
727
- }
728
- attrs.push(`data-word-merge-id="${origin.id}"`);
729
- }
730
- if (colSpan > 1) attrs.push(`colspan="${colSpan}"`);
731
- emittedCells.push(
732
- `<td${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${borderStyle};vertical-align:top;${widthStyle}">${html}</td>`
733
- );
734
- colCursor += colSpan;
735
- }
736
- for (const origin of Array.from(new Set(activeByCol.values()))) {
737
- if (origin.startedRow < rowIndex && !continued.has(origin)) {
738
- for (let i = 0; i < origin.colSpan; i += 1) {
739
- activeByCol.delete(origin.startCol + i);
740
- }
741
- }
742
- }
743
- return `<tr>${emittedCells.join("")}</tr>`;
744
- });
745
- let merged = htmlRows.join("");
746
- for (const origin of allOrigins) {
747
- const marker = `data-word-merge-id="${origin.id}"`;
748
- const replacement = origin.rowSpan > 1 ? `rowspan="${origin.rowSpan}"` : "";
749
- merged = merged.replace(marker, replacement).replace(/\s{2,}/g, " ");
750
- }
751
- const tableWidthStyle = parseTableWidthStyle(table, gridWidthsPx);
752
- const spacing = tableStyle.borderSpacingPx > 0 ? `border-spacing:${tableStyle.borderSpacingPx.toFixed(2)}px;` : "";
753
- return `<table style="border-collapse:${tableStyle.borderCollapse};${spacing}table-layout:${tableStyle.tableLayout};${tableWidthStyle};border:${tableStyle.borderCss};">${merged}</table>`;
754
- }
755
- async function parseDocxToHtmlSnapshot(file) {
756
- const maybeArrayBuffer = file.arrayBuffer;
757
- const buffer = maybeArrayBuffer ? await maybeArrayBuffer.call(file) : await new Response(file).arrayBuffer();
758
- const zip = await JSZip.loadAsync(buffer);
759
- const documentXmlText = await zip.file("word/document.xml")?.async("string");
760
- if (!documentXmlText) {
761
- throw new Error("DOCX missing document.xml");
762
- }
763
- const relsText = await zip.file("word/_rels/document.xml.rels")?.async("string");
764
- const footnotesText = await zip.file("word/footnotes.xml")?.async("string");
765
- const endnotesText = await zip.file("word/endnotes.xml")?.async("string");
766
- const commentsText = await zip.file("word/comments.xml")?.async("string");
767
- const relMap = parseDocRelsMap(relsText ?? null);
768
- const footnotesMap = parseFootnotesMap(footnotesText ?? null);
769
- const endnotesMap = parseEndnotesMap(endnotesText ?? null);
770
- const commentsMap = parseCommentsMap(commentsText ?? null);
771
- const usedFootnoteIds = [];
772
- const usedEndnoteIds = [];
773
- const usedCommentIds = [];
774
- const documentXml = parseXml(documentXmlText);
775
- const body = queryByLocalName(documentXml, "body");
776
- if (!body) {
777
- throw new Error("DOCX missing body");
778
- }
779
- const paragraphIndexMap = /* @__PURE__ */ new Map();
780
- queryAllByLocalName(documentXml, "p").forEach((paragraph, index) => {
781
- paragraphIndexMap.set(paragraph, index);
782
- });
783
- const blockHtml = [];
784
- for (const child of Array.from(body.children)) {
785
- if (child.localName === "sectPr") continue;
786
- if (child.localName === "p") {
787
- const paragraphIndex = paragraphIndexMap.get(child) ?? null;
788
- blockHtml.push(
789
- await paragraphToHtml(
790
- zip,
791
- relMap,
792
- child,
793
- paragraphIndex,
794
- footnotesMap,
795
- usedFootnoteIds,
796
- endnotesMap,
797
- usedEndnoteIds,
798
- commentsMap,
799
- usedCommentIds
800
- )
801
- );
802
- continue;
803
- }
804
- if (child.localName === "tbl") {
805
- blockHtml.push(tableToHtml(child, paragraphIndexMap));
806
- continue;
807
- }
808
- }
809
- blockHtml.push(renderFootnotesSection(usedFootnoteIds, footnotesMap));
810
- blockHtml.push(renderEndnotesSection(usedEndnoteIds, endnotesMap));
811
- blockHtml.push(renderCommentsSection(usedCommentIds, commentsMap));
812
- return buildHtmlSnapshot(blockHtml.join("\n"));
813
- }
814
-
815
- // src/lib/pastePipeline.ts
816
- function escapeAttr(value) {
817
- return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;");
818
- }
819
- async function fileToDataUrl(file) {
820
- const buffer = await new Response(file).arrayBuffer();
821
- const bytes = new Uint8Array(buffer);
822
- let binary = "";
823
- for (const b of bytes) binary += String.fromCharCode(b);
824
- const base64 = btoa(binary);
825
- const mime = file.type || "application/octet-stream";
826
- return `data:${mime};base64,${base64}`;
827
- }
828
- function isUnstableImageSrc(src) {
829
- const normalized = src.trim().toLowerCase();
830
- return normalized.startsWith("file:") || normalized.startsWith("blob:") || normalized.startsWith("cid:") || normalized.startsWith("mhtml:") || normalized.startsWith("ms-appx:") || normalized.startsWith("ms-appdata:");
831
- }
832
- async function replaceUnstableImageSrc(rawHtml, imageFiles) {
833
- if (!rawHtml.trim() || imageFiles.length === 0) return rawHtml;
834
- const parser = new DOMParser();
835
- const doc = parser.parseFromString(rawHtml, "text/html");
836
- const images = Array.from(doc.querySelectorAll("img"));
837
- const replacementDataUrls = await Promise.all(imageFiles.map((file) => fileToDataUrl(file)));
838
- let replacementIndex = 0;
839
- for (const img of images) {
840
- const src = img.getAttribute("src") ?? "";
841
- if (isUnstableImageSrc(src) && replacementIndex < replacementDataUrls.length) {
842
- img.setAttribute("src", replacementDataUrls[replacementIndex]);
843
- replacementIndex += 1;
844
- }
845
- }
846
- return doc.body.innerHTML;
847
- }
848
- async function buildImageOnlyHtml(imageFiles) {
849
- if (imageFiles.length === 0) return "";
850
- const urls = await Promise.all(imageFiles.map((f) => fileToDataUrl(f)));
851
- return urls.map((url) => `<p><img src="${escapeAttr(url)}" alt="clipboard-image" /></p>`).join("\n");
852
- }
853
- async function extractFromClipboardDataTransfer(dataTransfer) {
854
- const html = dataTransfer.getData("text/html") || "";
855
- const text = dataTransfer.getData("text/plain") || "";
856
- const imageFiles = Array.from(dataTransfer.items).filter((item) => item.kind === "file" && item.type.startsWith("image/")).map((item) => item.getAsFile()).filter((file) => file !== null);
857
- const hydratedHtml = await replaceUnstableImageSrc(html, imageFiles);
858
- const finalHtml = hydratedHtml.trim() ? hydratedHtml : await buildImageOnlyHtml(imageFiles);
859
- return {
860
- html: finalHtml,
861
- text,
862
- imageFiles
863
- };
864
- }
865
- async function extractFromClipboardItems(items) {
866
- let html = "";
867
- let text = "";
868
- const imageFiles = [];
869
- for (const item of items) {
870
- if (item.types.includes("text/html") && !html) {
871
- const blob = await item.getType("text/html");
872
- html = await blob.text();
873
- }
874
- if (item.types.includes("text/plain") && !text) {
875
- const blob = await item.getType("text/plain");
876
- text = await blob.text();
877
- }
878
- for (const type of item.types) {
879
- if (!type.startsWith("image/")) continue;
880
- const blob = await item.getType(type);
881
- const name = `clipboard-${Date.now()}-${imageFiles.length}.${type.split("/")[1] ?? "bin"}`;
882
- imageFiles.push(new File([blob], name, { type }));
883
- }
884
- }
885
- const hydratedHtml = await replaceUnstableImageSrc(html, imageFiles);
886
- const finalHtml = hydratedHtml.trim() ? hydratedHtml : await buildImageOnlyHtml(imageFiles);
887
- return {
888
- html: finalHtml,
889
- text,
890
- imageFiles
891
- };
892
- }
893
-
894
- // src/lib/htmlCompat.ts
895
- function parseStyle(styleText) {
896
- const map = /* @__PURE__ */ new Map();
897
- for (const seg of styleText.split(";")) {
898
- const [rawKey, ...rawValue] = seg.split(":");
899
- const key = rawKey?.trim().toLowerCase();
900
- const value = rawValue.join(":").trim();
901
- if (!key || !value) continue;
902
- map.set(key, value);
903
- }
904
- return map;
905
- }
906
- function serializeStyle(styleMap) {
907
- return Array.from(styleMap.entries()).map(([k, v]) => `${k}: ${v}`).join("; ");
908
- }
909
- function applyMsoMappings(styleMap) {
910
- const mappingPairs = [
911
- ["mso-ansi-font-size", "font-size"],
912
- ["mso-bidi-font-size", "font-size"],
913
- ["mso-hansi-font-size", "font-size"],
914
- ["mso-margin-top-alt", "margin-top"],
915
- ["mso-margin-bottom-alt", "margin-bottom"],
916
- ["mso-margin-left-alt", "margin-left"],
917
- ["mso-margin-right-alt", "margin-right"],
918
- ["mso-line-height-alt", "line-height"]
919
- ];
920
- for (const [msoKey, cssKey] of mappingPairs) {
921
- const value = styleMap.get(msoKey);
922
- if (!value) continue;
923
- if (!styleMap.has(cssKey)) {
924
- styleMap.set(cssKey, value);
925
- }
926
- }
927
- const msoFore = styleMap.get("mso-foreground");
928
- if (msoFore && !styleMap.has("color")) {
929
- styleMap.set("color", msoFore);
930
- }
931
- if (styleMap.get("mso-table-lspace") && !styleMap.has("margin-left")) {
932
- styleMap.set("margin-left", "0");
933
- }
934
- if (styleMap.get("mso-table-rspace") && !styleMap.has("margin-right")) {
935
- styleMap.set("margin-right", "0");
936
- }
937
- }
938
- function applyListFallback(el, styleMap) {
939
- const className = (el.getAttribute("class") ?? "").toLowerCase();
940
- const msoList = styleMap.get("mso-list") ?? "";
941
- const maybeList = className.includes("msolist") || msoList.length > 0;
942
- if (!maybeList) return;
943
- if (!styleMap.has("text-indent")) {
944
- styleMap.set("text-indent", "0");
945
- }
946
- const marginLeft = styleMap.get("margin-left");
947
- if (marginLeft && !styleMap.has("padding-left")) {
948
- styleMap.set("padding-left", marginLeft);
949
- }
950
- }
951
- function applyTagSpecificFallback(el, styleMap) {
952
- const tag = el.tagName.toLowerCase();
953
- if (tag === "table") {
954
- if (!styleMap.has("border-collapse")) {
955
- styleMap.set("border-collapse", "collapse");
956
- }
957
- if (!styleMap.has("border-spacing")) {
958
- styleMap.set("border-spacing", "0");
959
- }
960
- }
961
- if ((tag === "td" || tag === "th") && !styleMap.has("vertical-align")) {
962
- styleMap.set("vertical-align", "top");
963
- }
964
- if (tag === "p" && !styleMap.has("min-height")) {
965
- styleMap.set("min-height", "1em");
966
- }
967
- }
968
- function normalizeWordEmptyParagraphs(doc) {
969
- const paragraphs = Array.from(doc.querySelectorAll("p"));
970
- for (const p of paragraphs) {
971
- const text = (p.textContent ?? "").replace(/\u00a0/g, " ").trim();
972
- const hasVisibleChildren = p.querySelector("img,table,svg,canvas,br") !== null;
973
- if (!hasVisibleChildren && text.length === 0) {
974
- p.innerHTML = "<br/>";
975
- }
976
- }
977
- }
978
- function applyGlobalDocFixes(doc, options) {
979
- const body = doc.body;
980
- if (!body) return;
981
- if (options?.forceBodyFontFamily) {
982
- body.style.fontFamily = options.forceBodyFontFamily;
983
- }
984
- if (options?.forceHeadingFontFamily) {
985
- const headings = Array.from(doc.querySelectorAll("h1,h2,h3,h4,h5,h6"));
986
- for (const heading of headings) {
987
- heading.style.fontFamily = options.forceHeadingFontFamily;
988
- }
989
- }
990
- }
991
- function applyWordHtmlCompatibility(doc, options) {
992
- const elements = Array.from(doc.querySelectorAll("[style], p, table, td, th, li, div, span"));
993
- for (const el of elements) {
994
- const htmlEl = el;
995
- const rawStyle = htmlEl.getAttribute("style") ?? "";
996
- const styleMap = parseStyle(rawStyle);
997
- applyMsoMappings(styleMap);
998
- applyListFallback(htmlEl, styleMap);
999
- applyTagSpecificFallback(htmlEl, styleMap);
1000
- if (styleMap.size > 0) {
1001
- htmlEl.setAttribute("style", serializeStyle(styleMap));
1002
- }
1003
- }
1004
- normalizeWordEmptyParagraphs(doc);
1005
- applyGlobalDocFixes(doc, options);
1006
- }
1007
-
1008
- // src/lib/styleProfile.ts
1009
- import JSZip2 from "jszip";
1010
- var FALLBACK_PROFILE = {
1011
- bodyFontPx: 14.6667,
1012
- bodyLineHeightRatio: 1.158333,
1013
- bodyLineHeightPx: null,
1014
- bodyLineHeightRule: "auto",
1015
- paragraphAfterPx: 10.67,
1016
- contentWidthPx: 553.73,
1017
- pageHeightPx: 1122.53,
1018
- pageMarginTopPx: 96,
1019
- pageMarginBottomPx: 96,
1020
- titleFontPx: 32,
1021
- titleColor: "#0F4761",
1022
- titleAlign: "center",
1023
- bodyFontFamily: '"Times New Roman", "Noto Serif SC", serif',
1024
- titleFontFamily: 'DengXian, "Noto Sans SC", "Microsoft YaHei", sans-serif',
1025
- discoveredFonts: [],
1026
- tableCellPaddingTopPx: 0,
1027
- tableCellPaddingLeftPx: 7.2,
1028
- tableCellPaddingBottomPx: 0,
1029
- tableCellPaddingRightPx: 7.2,
1030
- paragraphProfiles: [],
1031
- trailingDateText: null,
1032
- trailingDateAlignedRight: false,
1033
- trailingDateParagraphIndex: null,
1034
- trailingEmptyParagraphCountBeforeDate: 0
1035
- };
1036
- function createFallbackWordStyleProfile(sourceFileName = "snapshot") {
1037
- return {
1038
- sourceFileName,
1039
- ...FALLBACK_PROFILE,
1040
- paragraphProfiles: []
1041
- };
1042
- }
1043
- function twipToPx2(twip) {
1044
- return twip / 15;
1045
- }
1046
- function getAttr2(node, attr) {
1047
- if (!node) return null;
1048
- return node.getAttribute(attr);
1049
- }
1050
- function getTwipAttr(node, attr) {
1051
- const raw = getAttr2(node, attr);
1052
- if (!raw) return null;
1053
- const parsed = Number.parseFloat(raw);
1054
- return Number.isFinite(parsed) ? parsed : null;
1055
- }
1056
- function queryByLocalName2(root, localName) {
1057
- const all = Array.from(root.querySelectorAll("*"));
1058
- return all.find((el) => el.localName === localName) ?? null;
1059
- }
1060
- function queryAllByLocalName2(root, localName) {
1061
- const all = Array.from(root.querySelectorAll("*"));
1062
- return all.filter((el) => el.localName === localName);
1063
- }
1064
- function parseXml2(xmlText) {
1065
- const parser = new DOMParser();
1066
- return parser.parseFromString(xmlText, "application/xml");
1067
- }
1068
- function parsePageGeometry(documentXml) {
1069
- const sectPr = queryByLocalName2(documentXml, "sectPr");
1070
- if (!sectPr) {
1071
- return {
1072
- contentWidthPx: null,
1073
- pageHeightPx: null,
1074
- marginTopPx: null,
1075
- marginBottomPx: null
1076
- };
1077
- }
1078
- const pgSz = queryAllByLocalName2(sectPr, "pgSz")[0] ?? null;
1079
- const pgMar = queryAllByLocalName2(sectPr, "pgMar")[0] ?? null;
1080
- const pageW = getTwipAttr(pgSz, "w:w") ?? getTwipAttr(pgSz, "w") ?? null;
1081
- const pageH = getTwipAttr(pgSz, "w:h") ?? getTwipAttr(pgSz, "h") ?? null;
1082
- const left = getTwipAttr(pgMar, "w:left") ?? getTwipAttr(pgMar, "left") ?? 0;
1083
- const right = getTwipAttr(pgMar, "w:right") ?? getTwipAttr(pgMar, "right") ?? 0;
1084
- const top = getTwipAttr(pgMar, "w:top") ?? getTwipAttr(pgMar, "top") ?? null;
1085
- const bottom = getTwipAttr(pgMar, "w:bottom") ?? getTwipAttr(pgMar, "bottom") ?? null;
1086
- return {
1087
- contentWidthPx: pageW === null ? null : twipToPx2(pageW - left - right),
1088
- pageHeightPx: pageH === null ? null : twipToPx2(pageH),
1089
- marginTopPx: top === null ? null : twipToPx2(top),
1090
- marginBottomPx: bottom === null ? null : twipToPx2(bottom)
1091
- };
1092
- }
1093
- function parseHeadingAlignFromDocument(documentXml) {
1094
- const paragraphs = queryAllByLocalName2(documentXml, "p");
1095
- for (const paragraph of paragraphs) {
1096
- const pPr = queryAllByLocalName2(paragraph, "pPr")[0] ?? null;
1097
- if (!pPr) continue;
1098
- const pStyle = queryAllByLocalName2(pPr, "pStyle")[0] ?? null;
1099
- const styleVal = getAttr2(pStyle, "w:val") ?? getAttr2(pStyle, "val") ?? "";
1100
- const isHeading = styleVal === "1" || styleVal.toLowerCase().includes("heading");
1101
- if (!isHeading) continue;
1102
- const jc = queryAllByLocalName2(pPr, "jc")[0] ?? null;
1103
- const alignRaw = (getAttr2(jc, "w:val") ?? getAttr2(jc, "val") ?? "").toLowerCase();
1104
- if (alignRaw === "center") return "center";
1105
- if (alignRaw === "right") return "right";
1106
- return "left";
1107
- }
1108
- return null;
1109
- }
1110
- function parseParagraphText(paragraph) {
1111
- const textNodes = queryAllByLocalName2(paragraph, "t");
1112
- return textNodes.map((node) => node.textContent ?? "").join("").trim();
1113
- }
1114
- function parseParagraphAlign(paragraph) {
1115
- const pPr = queryAllByLocalName2(paragraph, "pPr")[0] ?? null;
1116
- const jc = pPr ? queryAllByLocalName2(pPr, "jc")[0] ?? null : null;
1117
- const alignRaw = (getAttr2(jc, "w:val") ?? getAttr2(jc, "val") ?? "").toLowerCase();
1118
- if (alignRaw === "center") return "center";
1119
- if (alignRaw === "right") return "right";
1120
- return "left";
1121
- }
1122
- function parseTrailingDateAnchor(documentXml) {
1123
- const paragraphs = queryAllByLocalName2(documentXml, "p");
1124
- if (paragraphs.length === 0) {
1125
- return {
1126
- trailingDateText: null,
1127
- trailingDateAlignedRight: false,
1128
- trailingDateParagraphIndex: null,
1129
- trailingEmptyParagraphCountBeforeDate: 0
1130
- };
1131
- }
1132
- let lastNonEmptyIndex = -1;
1133
- for (let i = paragraphs.length - 1; i >= 0; i -= 1) {
1134
- if (parseParagraphText(paragraphs[i]).length > 0) {
1135
- lastNonEmptyIndex = i;
1136
- break;
1137
- }
1138
- }
1139
- if (lastNonEmptyIndex < 0) {
1140
- return {
1141
- trailingDateText: null,
1142
- trailingDateAlignedRight: false,
1143
- trailingDateParagraphIndex: null,
1144
- trailingEmptyParagraphCountBeforeDate: 0
1145
- };
1146
- }
1147
- const dateParagraph = paragraphs[lastNonEmptyIndex];
1148
- const dateText = parseParagraphText(dateParagraph);
1149
- const looksLikeDate = /\d{4}\s*年\s*\d+\s*月\s*\d+\s*日/.test(dateText);
1150
- const align = parseParagraphAlign(dateParagraph);
1151
- let trailingEmpty = 0;
1152
- for (let i = lastNonEmptyIndex - 1; i >= 0; i -= 1) {
1153
- if (parseParagraphText(paragraphs[i]).length === 0) {
1154
- trailingEmpty += 1;
1155
- continue;
1156
- }
1157
- break;
1158
- }
1159
- return {
1160
- trailingDateText: looksLikeDate ? dateText : null,
1161
- trailingDateAlignedRight: looksLikeDate && align === "right",
1162
- trailingDateParagraphIndex: looksLikeDate ? lastNonEmptyIndex : null,
1163
- trailingEmptyParagraphCountBeforeDate: looksLikeDate ? trailingEmpty : 0
1164
- };
1165
- }
1166
- function toInt(value) {
1167
- if (!value) return null;
1168
- const n = Number.parseInt(value, 10);
1169
- return Number.isFinite(n) ? n : null;
1170
- }
1171
- function parseNumberingMap(numberingXml) {
1172
- const levelMap = /* @__PURE__ */ new Map();
1173
- if (!numberingXml) return levelMap;
1174
- const abstractMap = /* @__PURE__ */ new Map();
1175
- const abstractNums = queryAllByLocalName2(numberingXml, "abstractNum");
1176
- for (const abs of abstractNums) {
1177
- const absId = toInt(getAttr2(abs, "w:abstractNumId") ?? getAttr2(abs, "abstractNumId"));
1178
- if (absId === null) continue;
1179
- const lvlNodes = queryAllByLocalName2(abs, "lvl");
1180
- const lvlMap = /* @__PURE__ */ new Map();
1181
- for (const lvl of lvlNodes) {
1182
- const ilvl = toInt(getAttr2(lvl, "w:ilvl") ?? getAttr2(lvl, "ilvl"));
1183
- if (ilvl === null) continue;
1184
- const numFmtNode = queryAllByLocalName2(lvl, "numFmt")[0] ?? null;
1185
- const lvlTextNode = queryAllByLocalName2(lvl, "lvlText")[0] ?? null;
1186
- lvlMap.set(ilvl, {
1187
- numFmt: getAttr2(numFmtNode, "w:val") ?? getAttr2(numFmtNode, "val") ?? null,
1188
- lvlText: getAttr2(lvlTextNode, "w:val") ?? getAttr2(lvlTextNode, "val") ?? null,
1189
- startAt: toInt(getAttr2(queryAllByLocalName2(lvl, "start")[0] ?? null, "w:val") ?? getAttr2(queryAllByLocalName2(lvl, "start")[0] ?? null, "val")) ?? 1
1190
- });
1191
- }
1192
- abstractMap.set(absId, lvlMap);
1193
- }
1194
- const nums = queryAllByLocalName2(numberingXml, "num");
1195
- for (const num of nums) {
1196
- const numId = toInt(getAttr2(num, "w:numId") ?? getAttr2(num, "numId"));
1197
- if (numId === null) continue;
1198
- const abstractRefNode = queryAllByLocalName2(num, "abstractNumId")[0] ?? null;
1199
- const absId = toInt(getAttr2(abstractRefNode, "w:val") ?? getAttr2(abstractRefNode, "val"));
1200
- if (absId === null) continue;
1201
- const lvlMap = abstractMap.get(absId);
1202
- if (!lvlMap) continue;
1203
- for (const [lvl, spec] of lvlMap.entries()) {
1204
- levelMap.set(`${numId}:${lvl}`, { ...spec });
1205
- }
1206
- const lvlOverrides = queryAllByLocalName2(num, "lvlOverride");
1207
- for (const override of lvlOverrides) {
1208
- const ilvl = toInt(getAttr2(override, "w:ilvl") ?? getAttr2(override, "ilvl"));
1209
- if (ilvl === null) continue;
1210
- const key = `${numId}:${ilvl}`;
1211
- const base = levelMap.get(key) ?? { numFmt: null, lvlText: null, startAt: 1 };
1212
- const overrideStart = toInt(
1213
- getAttr2(queryAllByLocalName2(override, "startOverride")[0] ?? null, "w:val") ?? getAttr2(queryAllByLocalName2(override, "startOverride")[0] ?? null, "val")
1214
- );
1215
- const overrideLvl = queryAllByLocalName2(override, "lvl")[0] ?? null;
1216
- const overrideNumFmtNode = overrideLvl ? queryAllByLocalName2(overrideLvl, "numFmt")[0] ?? null : null;
1217
- const overrideLvlTextNode = overrideLvl ? queryAllByLocalName2(overrideLvl, "lvlText")[0] ?? null : null;
1218
- const overrideLvlStart = toInt(
1219
- getAttr2(queryAllByLocalName2(overrideLvl ?? override, "start")[0] ?? null, "w:val") ?? getAttr2(queryAllByLocalName2(overrideLvl ?? override, "start")[0] ?? null, "val")
1220
- );
1221
- levelMap.set(key, {
1222
- numFmt: getAttr2(overrideNumFmtNode, "w:val") ?? getAttr2(overrideNumFmtNode, "val") ?? base.numFmt,
1223
- lvlText: getAttr2(overrideLvlTextNode, "w:val") ?? getAttr2(overrideLvlTextNode, "val") ?? base.lvlText,
1224
- startAt: overrideStart ?? overrideLvlStart ?? base.startAt
1225
- });
1226
- }
1227
- }
1228
- return levelMap;
1229
- }
1230
- function parseParagraphProfiles(documentXml, numberingMap) {
1231
- const paragraphs = queryAllByLocalName2(documentXml, "p");
1232
- return paragraphs.map((paragraph, index) => {
1233
- const text = parseParagraphText(paragraph);
1234
- const pPr = queryAllByLocalName2(paragraph, "pPr")[0] ?? null;
1235
- const spacing = pPr ? queryAllByLocalName2(pPr, "spacing")[0] ?? null : null;
1236
- const ind = pPr ? queryAllByLocalName2(pPr, "ind")[0] ?? null : null;
1237
- const numPr = pPr ? queryAllByLocalName2(pPr, "numPr")[0] ?? null : null;
1238
- const ilvlNode = numPr ? queryAllByLocalName2(numPr, "ilvl")[0] ?? null : null;
1239
- const numIdNode = numPr ? queryAllByLocalName2(numPr, "numId")[0] ?? null : null;
1240
- const listLevel = toInt(getAttr2(ilvlNode, "w:val") ?? getAttr2(ilvlNode, "val"));
1241
- const listNumId = toInt(getAttr2(numIdNode, "w:val") ?? getAttr2(numIdNode, "val"));
1242
- const listSpec = listNumId !== null && listLevel !== null ? numberingMap.get(`${listNumId}:${listLevel}`) : void 0;
1243
- const keepNextNode = pPr ? queryAllByLocalName2(pPr, "keepNext")[0] ?? null : null;
1244
- const keepLinesNode = pPr ? queryAllByLocalName2(pPr, "keepLines")[0] ?? null : null;
1245
- const pageBreakBeforeNode = pPr ? queryAllByLocalName2(pPr, "pageBreakBefore")[0] ?? null : null;
1246
- const renderedPageBreakNode = queryAllByLocalName2(paragraph, "lastRenderedPageBreak")[0] ?? null;
1247
- const sectionBreakNode = pPr ? queryAllByLocalName2(pPr, "sectPr")[0] ?? null : null;
1248
- const before = getTwipAttr(spacing, "w:before") ?? getTwipAttr(spacing, "before") ?? null;
1249
- const after = getTwipAttr(spacing, "w:after") ?? getTwipAttr(spacing, "after") ?? null;
1250
- const line = getTwipAttr(spacing, "w:line") ?? getTwipAttr(spacing, "line") ?? null;
1251
- const rawLineRule = (getAttr2(spacing, "w:lineRule") ?? getAttr2(spacing, "lineRule") ?? "auto").toLowerCase();
1252
- const lineHeightRule = line === null ? null : rawLineRule === "exact" ? "exact" : rawLineRule === "atleast" ? "atLeast" : "auto";
1253
- const left = getTwipAttr(ind, "w:left") ?? getTwipAttr(ind, "left") ?? null;
1254
- const right = getTwipAttr(ind, "w:right") ?? getTwipAttr(ind, "right") ?? null;
1255
- const firstLine = getTwipAttr(ind, "w:firstLine") ?? getTwipAttr(ind, "firstLine") ?? null;
1256
- const hanging = getTwipAttr(ind, "w:hanging") ?? getTwipAttr(ind, "hanging") ?? null;
1257
- const runs = parseRunProfiles(paragraph);
1258
- return {
1259
- index,
1260
- text,
1261
- isEmpty: text.length === 0,
1262
- align: parseParagraphAlign(paragraph),
1263
- beforePx: before === null ? null : twipToPx2(before),
1264
- afterPx: after === null ? null : twipToPx2(after),
1265
- lineHeightRatio: line === null || lineHeightRule !== "auto" ? null : line / 240,
1266
- lineHeightPx: line === null || lineHeightRule === "auto" ? null : twipToPx2(line),
1267
- lineHeightRule,
1268
- indentLeftPx: left === null ? null : twipToPx2(left),
1269
- indentRightPx: right === null ? null : twipToPx2(right),
1270
- firstLinePx: firstLine === null ? null : twipToPx2(firstLine),
1271
- hangingPx: hanging === null ? null : twipToPx2(hanging),
1272
- listNumId,
1273
- listLevel,
1274
- listFormat: listSpec?.numFmt ?? null,
1275
- listTextPattern: listSpec?.lvlText ?? null,
1276
- listStartAt: listSpec?.startAt ?? 1,
1277
- keepNext: keepNextNode !== null && (getAttr2(keepNextNode, "w:val") ?? getAttr2(keepNextNode, "val") ?? "1") !== "0",
1278
- keepLines: keepLinesNode !== null && (getAttr2(keepLinesNode, "w:val") ?? getAttr2(keepLinesNode, "val") ?? "1") !== "0",
1279
- pageBreakBefore: renderedPageBreakNode !== null || pageBreakBeforeNode !== null && (getAttr2(pageBreakBeforeNode, "w:val") ?? getAttr2(pageBreakBeforeNode, "val") ?? "1") !== "0",
1280
- sectionBreakBefore: sectionBreakNode !== null,
1281
- runs
1282
- };
1283
- });
1284
- }
1285
- function parseTableDefaults(stylesXml) {
1286
- const tableStyles = queryAllByLocalName2(stylesXml, "style").filter((style) => {
1287
- const type = (getAttr2(style, "w:type") ?? getAttr2(style, "type") ?? "").toLowerCase();
1288
- return type === "table";
1289
- });
1290
- const targetStyle = tableStyles.find((style) => {
1291
- const styleId = (getAttr2(style, "w:styleId") ?? getAttr2(style, "styleId") ?? "").toLowerCase();
1292
- return styleId === "a1";
1293
- }) ?? tableStyles[0] ?? null;
1294
- if (!targetStyle) {
1295
- return { topPx: null, leftPx: null, bottomPx: null, rightPx: null };
1296
- }
1297
- const tblPr = queryAllByLocalName2(targetStyle, "tblPr")[0] ?? null;
1298
- const tblCellMar = tblPr ? queryAllByLocalName2(tblPr, "tblCellMar")[0] ?? null : null;
1299
- const top = tblCellMar ? queryAllByLocalName2(tblCellMar, "top")[0] ?? null : null;
1300
- const left = tblCellMar ? queryAllByLocalName2(tblCellMar, "left")[0] ?? null : null;
1301
- const bottom = tblCellMar ? queryAllByLocalName2(tblCellMar, "bottom")[0] ?? null : null;
1302
- const right = tblCellMar ? queryAllByLocalName2(tblCellMar, "right")[0] ?? null : null;
1303
- return {
1304
- topPx: (() => {
1305
- const v = getTwipAttr(top, "w:w") ?? getTwipAttr(top, "w") ?? null;
1306
- return v === null ? null : twipToPx2(v);
1307
- })(),
1308
- leftPx: (() => {
1309
- const v = getTwipAttr(left, "w:w") ?? getTwipAttr(left, "w") ?? null;
1310
- return v === null ? null : twipToPx2(v);
1311
- })(),
1312
- bottomPx: (() => {
1313
- const v = getTwipAttr(bottom, "w:w") ?? getTwipAttr(bottom, "w") ?? null;
1314
- return v === null ? null : twipToPx2(v);
1315
- })(),
1316
- rightPx: (() => {
1317
- const v = getTwipAttr(right, "w:w") ?? getTwipAttr(right, "w") ?? null;
1318
- return v === null ? null : twipToPx2(v);
1319
- })()
1320
- };
1321
- }
1322
- function parseRunProfiles(paragraph) {
1323
- const runNodes = queryAllByLocalName2(paragraph, "r");
1324
- return runNodes.map((run) => {
1325
- const rPr = queryAllByLocalName2(run, "rPr")[0] ?? null;
1326
- const textNodes = queryAllByLocalName2(run, "t");
1327
- const breakNodes = queryAllByLocalName2(run, "br");
1328
- let text = textNodes.map((node) => node.textContent ?? "").join("");
1329
- if (breakNodes.length > 0) {
1330
- text += "\n".repeat(breakNodes.length);
1331
- }
1332
- if (!text) {
1333
- return null;
1334
- }
1335
- const sz = rPr ? queryAllByLocalName2(rPr, "sz")[0] ?? null : null;
1336
- const halfPoints = getTwipAttr(sz, "w:val") ?? getTwipAttr(sz, "val") ?? null;
1337
- const fontSizePx = halfPoints === null ? null : halfPoints / 2 * (96 / 72);
1338
- const colorNode = rPr ? queryAllByLocalName2(rPr, "color")[0] ?? null : null;
1339
- const colorRaw = getAttr2(colorNode, "w:val") ?? getAttr2(colorNode, "val") ?? null;
1340
- const color = colorRaw && colorRaw.toLowerCase() !== "auto" ? `#${colorRaw}` : null;
1341
- const highlightNode = rPr ? queryAllByLocalName2(rPr, "highlight")[0] ?? null : null;
1342
- const highlightRaw = (getAttr2(highlightNode, "w:val") ?? getAttr2(highlightNode, "val") ?? "").toLowerCase();
1343
- const highlightMap = {
1344
- yellow: "#fff59d",
1345
- green: "#b9f6ca",
1346
- cyan: "#b2ebf2",
1347
- magenta: "#f8bbd0",
1348
- blue: "#bbdefb",
1349
- red: "#ffcdd2",
1350
- darkyellow: "#fbc02d",
1351
- darkgreen: "#66bb6a",
1352
- darkblue: "#64b5f6",
1353
- darkred: "#e57373",
1354
- darkcyan: "#4dd0e1",
1355
- darkmagenta: "#ba68c8",
1356
- gray: "#e0e0e0",
1357
- lightgray: "#f5f5f5"
1358
- };
1359
- const highlightColor = highlightRaw && highlightRaw !== "none" ? highlightMap[highlightRaw] ?? null : null;
1360
- const shdNode = rPr ? queryAllByLocalName2(rPr, "shd")[0] ?? null : null;
1361
- const shdFill = (getAttr2(shdNode, "w:fill") ?? getAttr2(shdNode, "fill") ?? "").toLowerCase();
1362
- const shadingColor = shdFill && shdFill !== "auto" ? `#${shdFill}` : null;
1363
- const spacingNode = rPr ? queryAllByLocalName2(rPr, "spacing")[0] ?? null : null;
1364
- const spacingVal = getTwipAttr(spacingNode, "w:val") ?? getTwipAttr(spacingNode, "val") ?? null;
1365
- const charSpacingPx = spacingVal === null ? null : spacingVal / 20 * (96 / 72);
1366
- const bNode = rPr ? queryAllByLocalName2(rPr, "b")[0] ?? null : null;
1367
- const iNode = rPr ? queryAllByLocalName2(rPr, "i")[0] ?? null : null;
1368
- const uNode = rPr ? queryAllByLocalName2(rPr, "u")[0] ?? null : null;
1369
- const strikeNode = rPr ? queryAllByLocalName2(rPr, "strike")[0] ?? null : null;
1370
- const shadowNode = rPr ? queryAllByLocalName2(rPr, "shadow")[0] ?? null : null;
1371
- const vertAlignNode = rPr ? queryAllByLocalName2(rPr, "vertAlign")[0] ?? null : null;
1372
- const bold = bNode !== null && (getAttr2(bNode, "w:val") ?? getAttr2(bNode, "val") ?? "1") !== "0";
1373
- const italic = iNode !== null && (getAttr2(iNode, "w:val") ?? getAttr2(iNode, "val") ?? "1") !== "0";
1374
- const underlineVal = (getAttr2(uNode, "w:val") ?? getAttr2(uNode, "val") ?? "").toLowerCase();
1375
- const underline = uNode !== null && underlineVal !== "none";
1376
- const strike = strikeNode !== null && (getAttr2(strikeNode, "w:val") ?? getAttr2(strikeNode, "val") ?? "1") !== "0";
1377
- const shadow = shadowNode !== null && (getAttr2(shadowNode, "w:val") ?? getAttr2(shadowNode, "val") ?? "1") !== "0";
1378
- const vertAlign = (getAttr2(vertAlignNode, "w:val") ?? getAttr2(vertAlignNode, "val") ?? "").toLowerCase();
1379
- const superscript = vertAlign === "superscript";
1380
- const subscript = vertAlign === "subscript";
1381
- const rFonts = rPr ? queryAllByLocalName2(rPr, "rFonts")[0] ?? null : null;
1382
- const fontFamily = getAttr2(rFonts, "w:eastAsia") ?? getAttr2(rFonts, "eastAsia") ?? getAttr2(rFonts, "w:ascii") ?? getAttr2(rFonts, "ascii") ?? getAttr2(rFonts, "w:hAnsi") ?? getAttr2(rFonts, "hAnsi") ?? null;
1383
- return {
1384
- text,
1385
- fontSizePx,
1386
- color,
1387
- highlightColor,
1388
- shadingColor,
1389
- charSpacingPx,
1390
- shadow,
1391
- bold,
1392
- italic,
1393
- underline,
1394
- strike,
1395
- superscript,
1396
- subscript,
1397
- fontFamily
1398
- };
1399
- }).filter((run) => run !== null);
1400
- }
1401
- function parseDefaults(stylesXml) {
1402
- const docDefaults = queryByLocalName2(stylesXml, "docDefaults");
1403
- if (!docDefaults) {
1404
- return { bodyFontPx: null, bodyLineHeightRatio: null, bodyLineHeightPx: null, bodyLineHeightRule: "auto", paragraphAfterPx: null };
1405
- }
1406
- const rPrDefault = queryByLocalName2(docDefaults, "rPrDefault");
1407
- const sz = rPrDefault ? queryByLocalName2(rPrDefault, "sz") : null;
1408
- const halfPoints = getTwipAttr(sz, "w:val") ?? getTwipAttr(sz, "val") ?? null;
1409
- const bodyFontPx = halfPoints === null ? null : halfPoints / 2 * (96 / 72);
1410
- const pPrDefault = queryByLocalName2(docDefaults, "pPrDefault");
1411
- const spacing = pPrDefault ? queryByLocalName2(pPrDefault, "spacing") : null;
1412
- const line = getTwipAttr(spacing, "w:line") ?? getTwipAttr(spacing, "line") ?? null;
1413
- const rawLineRule = (getAttr2(spacing, "w:lineRule") ?? getAttr2(spacing, "lineRule") ?? "auto").toLowerCase();
1414
- const bodyLineHeightRule = rawLineRule === "exact" ? "exact" : rawLineRule === "atleast" ? "atLeast" : "auto";
1415
- const bodyLineHeightRatio = line === null || bodyLineHeightRule !== "auto" ? null : line / 240;
1416
- const bodyLineHeightPx = line === null || bodyLineHeightRule === "auto" ? null : twipToPx2(line);
1417
- const after = getTwipAttr(spacing, "w:after") ?? getTwipAttr(spacing, "after") ?? null;
1418
- const paragraphAfterPx = after === null ? null : twipToPx2(after);
1419
- return { bodyFontPx, bodyLineHeightRatio, bodyLineHeightPx, bodyLineHeightRule, paragraphAfterPx };
1420
- }
1421
- function parseHeading1Style(stylesXml) {
1422
- const styles = queryAllByLocalName2(stylesXml, "style");
1423
- const headingStyle = styles.find((style) => {
1424
- const styleId = (getAttr2(style, "w:styleId") ?? getAttr2(style, "styleId") ?? "").toLowerCase();
1425
- const nameNode = queryByLocalName2(style, "name");
1426
- const nameVal = (getAttr2(nameNode, "w:val") ?? getAttr2(nameNode, "val") ?? "").toLowerCase();
1427
- return styleId === "1" || nameVal === "heading 1" || nameVal === "\u6807\u9898 1";
1428
- });
1429
- if (!headingStyle) {
1430
- return { titleFontPx: null, titleColor: null };
1431
- }
1432
- const rPr = queryByLocalName2(headingStyle, "rPr");
1433
- const sz = rPr ? queryByLocalName2(rPr, "sz") : null;
1434
- const halfPoints = getTwipAttr(sz, "w:val") ?? getTwipAttr(sz, "val") ?? null;
1435
- const titleFontPx = halfPoints === null ? null : halfPoints / 2 * (96 / 72);
1436
- const colorNode = rPr ? queryByLocalName2(rPr, "color") : null;
1437
- const colorRaw = getAttr2(colorNode, "w:val") ?? getAttr2(colorNode, "val") ?? null;
1438
- const titleColor = colorRaw ? `#${colorRaw}` : null;
1439
- return { titleFontPx, titleColor };
1440
- }
1441
- function parseFontTableFamilies(fontTableXml) {
1442
- if (!fontTableXml) return [];
1443
- const fontNodes = queryAllByLocalName2(fontTableXml, "font");
1444
- const families = fontNodes.map((node) => getAttr2(node, "w:name") ?? getAttr2(node, "name") ?? "").map((name) => name.trim()).filter((name) => name.length > 0);
1445
- return [...new Set(families)];
1446
- }
1447
- function hasFontLike(families, candidates) {
1448
- return families.some(
1449
- (family) => candidates.some((candidate) => family.toLowerCase().includes(candidate.toLowerCase()))
1450
- );
1451
- }
1452
- function inferBodyFontFamily(families) {
1453
- if (hasFontLike(families, ["times new roman"])) {
1454
- return '"Times New Roman", "Noto Serif SC", serif';
1455
- }
1456
- if (hasFontLike(families, ["dengxian", "\u7B49\u7EBF", "yahei", "hei", "song"])) {
1457
- return 'DengXian, "Microsoft YaHei", "PingFang SC", "Noto Sans SC", sans-serif';
1458
- }
1459
- return FALLBACK_PROFILE.bodyFontFamily;
1460
- }
1461
- function inferTitleFontFamily(families) {
1462
- if (hasFontLike(families, ["dengxian", "\u7B49\u7EBF"])) {
1463
- return 'DengXian, "Noto Sans SC", "Microsoft YaHei", sans-serif';
1464
- }
1465
- if (hasFontLike(families, ["times new roman"])) {
1466
- return '"Times New Roman", "Noto Serif SC", serif';
1467
- }
1468
- return FALLBACK_PROFILE.titleFontFamily;
1469
- }
1470
- async function parseDocxStyleProfile(file) {
1471
- const maybeArrayBuffer = file.arrayBuffer;
1472
- const buffer = maybeArrayBuffer ? await maybeArrayBuffer.call(file) : await new Response(file).arrayBuffer();
1473
- const zip = await JSZip2.loadAsync(buffer);
1474
- const documentXmlText = await zip.file("word/document.xml")?.async("string");
1475
- const stylesXmlText = await zip.file("word/styles.xml")?.async("string");
1476
- const fontTableXmlText = await zip.file("word/fontTable.xml")?.async("string");
1477
- const numberingXmlText = await zip.file("word/numbering.xml")?.async("string");
1478
- if (!documentXmlText || !stylesXmlText) {
1479
- throw new Error("DOCX missing document.xml or styles.xml");
1480
- }
1481
- const documentXml = parseXml2(documentXmlText);
1482
- const stylesXml = parseXml2(stylesXmlText);
1483
- const fontTableXml = fontTableXmlText ? parseXml2(fontTableXmlText) : null;
1484
- const numberingXml = numberingXmlText ? parseXml2(numberingXmlText) : null;
1485
- const numberingMap = parseNumberingMap(numberingXml);
1486
- const defaults = parseDefaults(stylesXml);
1487
- const heading1 = parseHeading1Style(stylesXml);
1488
- const tableDefaults = parseTableDefaults(stylesXml);
1489
- const pageGeometry = parsePageGeometry(documentXml);
1490
- const titleAlign = parseHeadingAlignFromDocument(documentXml);
1491
- const trailingDate = parseTrailingDateAnchor(documentXml);
1492
- const discoveredFonts = parseFontTableFamilies(fontTableXml);
1493
- const bodyFontFamily = inferBodyFontFamily(discoveredFonts);
1494
- const titleFontFamily = inferTitleFontFamily(discoveredFonts);
1495
- const paragraphProfiles = parseParagraphProfiles(documentXml, numberingMap);
1496
- return {
1497
- sourceFileName: file.name,
1498
- bodyFontPx: defaults.bodyFontPx ?? FALLBACK_PROFILE.bodyFontPx,
1499
- bodyLineHeightRatio: defaults.bodyLineHeightRatio ?? FALLBACK_PROFILE.bodyLineHeightRatio,
1500
- bodyLineHeightPx: defaults.bodyLineHeightPx ?? FALLBACK_PROFILE.bodyLineHeightPx,
1501
- bodyLineHeightRule: defaults.bodyLineHeightRule ?? FALLBACK_PROFILE.bodyLineHeightRule,
1502
- paragraphAfterPx: defaults.paragraphAfterPx ?? FALLBACK_PROFILE.paragraphAfterPx,
1503
- contentWidthPx: pageGeometry.contentWidthPx ?? FALLBACK_PROFILE.contentWidthPx,
1504
- pageHeightPx: pageGeometry.pageHeightPx ?? FALLBACK_PROFILE.pageHeightPx,
1505
- pageMarginTopPx: pageGeometry.marginTopPx ?? FALLBACK_PROFILE.pageMarginTopPx,
1506
- pageMarginBottomPx: pageGeometry.marginBottomPx ?? FALLBACK_PROFILE.pageMarginBottomPx,
1507
- titleFontPx: heading1.titleFontPx ?? FALLBACK_PROFILE.titleFontPx,
1508
- titleColor: heading1.titleColor ?? FALLBACK_PROFILE.titleColor,
1509
- titleAlign: titleAlign ?? FALLBACK_PROFILE.titleAlign,
1510
- bodyFontFamily,
1511
- titleFontFamily,
1512
- discoveredFonts,
1513
- tableCellPaddingTopPx: tableDefaults.topPx ?? FALLBACK_PROFILE.tableCellPaddingTopPx,
1514
- tableCellPaddingLeftPx: tableDefaults.leftPx ?? FALLBACK_PROFILE.tableCellPaddingLeftPx,
1515
- tableCellPaddingBottomPx: tableDefaults.bottomPx ?? FALLBACK_PROFILE.tableCellPaddingBottomPx,
1516
- tableCellPaddingRightPx: tableDefaults.rightPx ?? FALLBACK_PROFILE.tableCellPaddingRightPx,
1517
- paragraphProfiles,
1518
- trailingDateText: trailingDate.trailingDateText,
1519
- trailingDateAlignedRight: trailingDate.trailingDateAlignedRight,
1520
- trailingDateParagraphIndex: trailingDate.trailingDateParagraphIndex,
1521
- trailingEmptyParagraphCountBeforeDate: trailingDate.trailingEmptyParagraphCountBeforeDate
1522
- };
1523
- }
1524
-
1525
- // src/lib/renderApply.ts
1526
- function setImportantStyle(el, prop, value) {
1527
- el.style.setProperty(prop, value, "important");
1528
- }
1529
- function escapeHtml2(text) {
1530
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
1531
- }
1532
- function runStyleToCss2(run) {
1533
- const declarations = [];
1534
- if (run.fontSizePx !== null) declarations.push(`font-size:${run.fontSizePx.toFixed(2)}px`);
1535
- if (run.color) declarations.push(`color:${run.color}`);
1536
- if (run.highlightColor) declarations.push(`background-color:${run.highlightColor}`);
1537
- if (run.shadingColor) declarations.push(`background-color:${run.shadingColor}`);
1538
- if (run.charSpacingPx !== null) declarations.push(`letter-spacing:${run.charSpacingPx.toFixed(2)}px`);
1539
- if (run.shadow) declarations.push("text-shadow:0.5px 0.5px 0 rgba(0,0,0,0.28)");
1540
- if (run.bold) declarations.push("font-weight:700");
1541
- if (run.italic) declarations.push("font-style:italic");
1542
- const textDecorations = [];
1543
- if (run.underline) textDecorations.push("underline");
1544
- if (run.strike) textDecorations.push("line-through");
1545
- if (textDecorations.length > 0) declarations.push(`text-decoration:${textDecorations.join(" ")}`);
1546
- if (run.superscript) declarations.push("vertical-align:super");
1547
- if (run.subscript) declarations.push("vertical-align:sub");
1548
- if (run.superscript || run.subscript) declarations.push("font-size:0.83em");
1549
- if (run.fontFamily) declarations.push(`font-family:${run.fontFamily}`);
1550
- return declarations.join(";");
1551
- }
1552
- function paragraphToRunHtml(runs) {
1553
- return runs.map((run) => {
1554
- const css = runStyleToCss2(run);
1555
- const parts = run.text.split("\n");
1556
- const html = parts.map((part) => escapeHtml2(part)).join("<br/>");
1557
- if (!css) return html;
1558
- return `<span style="${css}">${html}</span>`;
1559
- }).join("");
1560
- }
1561
- function toLowerLetter(n) {
1562
- const alphabet = "abcdefghijklmnopqrstuvwxyz";
1563
- if (n <= 0) return "a";
1564
- let x = n;
1565
- let out = "";
1566
- while (x > 0) {
1567
- x -= 1;
1568
- out = alphabet[x % 26] + out;
1569
- x = Math.floor(x / 26);
1570
- }
1571
- return out;
1572
- }
1573
- function toRoman(num) {
1574
- if (num <= 0) return "I";
1575
- const map = [
1576
- [1e3, "M"],
1577
- [900, "CM"],
1578
- [500, "D"],
1579
- [400, "CD"],
1580
- [100, "C"],
1581
- [90, "XC"],
1582
- [50, "L"],
1583
- [40, "XL"],
1584
- [10, "X"],
1585
- [9, "IX"],
1586
- [5, "V"],
1587
- [4, "IV"],
1588
- [1, "I"]
1589
- ];
1590
- let n = num;
1591
- let result = "";
1592
- for (const [v, s] of map) {
1593
- while (n >= v) {
1594
- result += s;
1595
- n -= v;
1596
- }
1597
- }
1598
- return result;
1599
- }
1600
- function formatListMarker(format, counter) {
1601
- switch ((format ?? "").toLowerCase()) {
1602
- case "decimal":
1603
- return `${counter}.`;
1604
- case "lowerletter":
1605
- return `${toLowerLetter(counter)}.`;
1606
- case "upperletter":
1607
- return `${toLowerLetter(counter).toUpperCase()}.`;
1608
- case "lowerroman":
1609
- return `${toRoman(counter).toLowerCase()}.`;
1610
- case "upperroman":
1611
- return `${toRoman(counter)}.`;
1612
- case "bullet":
1613
- default:
1614
- return "\u2022";
1615
- }
1616
- }
1617
- function formatListMarkerByPattern(pattern, currentLevel, countersByLevel, currentFormat) {
1618
- if (!pattern || pattern.trim().length === 0) {
1619
- return formatListMarker(currentFormat, countersByLevel[currentLevel] ?? 1);
1620
- }
1621
- const replaced = pattern.replace(/%(\d+)/g, (_, g1) => {
1622
- const level1Based = Number.parseInt(g1, 10);
1623
- if (!Number.isFinite(level1Based) || level1Based <= 0) return "";
1624
- const levelIdx = level1Based - 1;
1625
- const n = countersByLevel[levelIdx] ?? 0;
1626
- if (n <= 0) return "";
1627
- if (levelIdx === currentLevel) {
1628
- return formatListMarker(currentFormat, n).replace(/\.$/, "");
1629
- }
1630
- return String(n);
1631
- });
1632
- const normalized = replaced.trim();
1633
- if (!normalized) {
1634
- return formatListMarker(currentFormat, countersByLevel[currentLevel] ?? 1);
1635
- }
1636
- return normalized;
1637
- }
1638
- function ensureStyleTag(doc, id) {
1639
- let styleEl = doc.getElementById(id);
1640
- if (!styleEl) {
1641
- styleEl = doc.createElement("style");
1642
- styleEl.id = id;
1643
- doc.head.appendChild(styleEl);
1644
- }
1645
- return styleEl;
1646
- }
1647
- function applyBaseProfileCss(doc, styleProfile) {
1648
- const styleEl = ensureStyleTag(doc, "__word_style_profile__");
1649
- const targetWidthPx = styleProfile.contentWidthPx.toFixed(2);
1650
- const topPaddingPx = styleProfile.pageMarginTopPx.toFixed(2);
1651
- const bottomPaddingPx = styleProfile.pageMarginBottomPx.toFixed(2);
1652
- const pageHeightPx = styleProfile.pageHeightPx.toFixed(2);
1653
- const bodyLineHeightCss = styleProfile.bodyLineHeightRule === "auto" || styleProfile.bodyLineHeightPx === null ? styleProfile.bodyLineHeightRatio.toFixed(6) : `${styleProfile.bodyLineHeightPx.toFixed(2)}px`;
1654
- styleEl.textContent = `
1655
- @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&family=Noto+Serif+SC:wght@400;700&display=swap');
1656
- html, body { box-sizing: border-box; }
1657
- body {
1658
- min-height: ${pageHeightPx}px !important;
1659
- width: ${targetWidthPx}px !important;
1660
- max-width: calc(100% - 24px) !important;
1661
- margin-left: auto !important;
1662
- margin-right: auto !important;
1663
- padding-top: ${topPaddingPx}px !important;
1664
- padding-bottom: ${bottomPaddingPx}px !important;
1665
- padding-left: 0 !important;
1666
- padding-right: 0 !important;
1667
- font-family: ${styleProfile.bodyFontFamily} !important;
1668
- }
1669
- p {
1670
- font-size: ${styleProfile.bodyFontPx.toFixed(4)}px !important;
1671
- line-height: ${bodyLineHeightCss} !important;
1672
- margin-bottom: ${styleProfile.paragraphAfterPx.toFixed(2)}px !important;
1673
- }
1674
- table { border-collapse: collapse !important; border-spacing: 0 !important; }
1675
- td, th {
1676
- padding-top: ${styleProfile.tableCellPaddingTopPx.toFixed(2)}px !important;
1677
- padding-left: ${styleProfile.tableCellPaddingLeftPx.toFixed(2)}px !important;
1678
- padding-bottom: ${styleProfile.tableCellPaddingBottomPx.toFixed(2)}px !important;
1679
- padding-right: ${styleProfile.tableCellPaddingRightPx.toFixed(2)}px !important;
1680
- vertical-align: top !important;
1681
- }
1682
- h1 {
1683
- font-size: ${styleProfile.titleFontPx.toFixed(2)}px !important;
1684
- color: ${styleProfile.titleColor} !important;
1685
- text-align: ${styleProfile.titleAlign} !important;
1686
- font-family: ${styleProfile.titleFontFamily} !important;
1687
- }
1688
- `;
1689
- }
1690
- function applyInlineLayoutGuards(doc, styleProfile) {
1691
- const body = doc.body;
1692
- const targetWidthPx = styleProfile.contentWidthPx.toFixed(2);
1693
- const topPaddingPx = styleProfile.pageMarginTopPx.toFixed(2);
1694
- const bottomPaddingPx = styleProfile.pageMarginBottomPx.toFixed(2);
1695
- const pageHeightPx = styleProfile.pageHeightPx.toFixed(2);
1696
- setImportantStyle(body, "box-sizing", "border-box");
1697
- setImportantStyle(body, "min-height", `${pageHeightPx}px`);
1698
- setImportantStyle(body, "width", `${targetWidthPx}px`);
1699
- setImportantStyle(body, "max-width", `${targetWidthPx}px`);
1700
- setImportantStyle(body, "margin-left", "auto");
1701
- setImportantStyle(body, "margin-right", "auto");
1702
- setImportantStyle(body, "padding-top", `${topPaddingPx}px`);
1703
- setImportantStyle(body, "padding-bottom", `${bottomPaddingPx}px`);
1704
- setImportantStyle(body, "padding-left", "0");
1705
- setImportantStyle(body, "padding-right", "0");
1706
- setImportantStyle(body, "font-family", styleProfile.bodyFontFamily);
1707
- for (const child of Array.from(body.children)) {
1708
- if (!(child instanceof HTMLElement)) continue;
1709
- const tag = child.tagName.toLowerCase();
1710
- if (tag === "script" || tag === "style") continue;
1711
- setImportantStyle(child, "box-sizing", "border-box");
1712
- setImportantStyle(child, "max-width", "100%");
1713
- }
1714
- for (const img of Array.from(doc.body.querySelectorAll("img"))) {
1715
- if (!(img instanceof HTMLElement)) continue;
1716
- setImportantStyle(img, "max-width", "100%");
1717
- setImportantStyle(img, "height", "auto");
1718
- }
1719
- }
1720
- function normalizeEmptyParagraphMarkers(paragraphs) {
1721
- for (const p of paragraphs) {
1722
- const hasVisualContent = (p.textContent ?? "").trim().length > 0 || p.querySelector("img,table,svg,canvas") !== null;
1723
- if (!hasVisualContent) {
1724
- p.setAttribute("data-word-empty", "1");
1725
- if (p.innerHTML.trim().length === 0) {
1726
- p.innerHTML = "<br/>";
1727
- }
1728
- } else {
1729
- p.removeAttribute("data-word-empty");
1730
- }
1731
- }
1732
- }
1733
- function hasMeaningfulParagraphAfter(paragraphs, index) {
1734
- for (let i = index + 1; i < paragraphs.length; i += 1) {
1735
- const p = paragraphs[i];
1736
- const hasText = (p.textContent ?? "").trim().length > 0;
1737
- const hasVisual = p.querySelector("img,table,svg,canvas") !== null;
1738
- if (hasText || hasVisual) return true;
1739
- }
1740
- return false;
1741
- }
1742
- function applyParagraphProfiles(doc, styleProfile) {
1743
- const fallbackParagraphs = Array.from(doc.body.querySelectorAll("p"));
1744
- fallbackParagraphs.forEach((p) => {
1745
- p.classList.remove("__word-date-anchor");
1746
- p.querySelectorAll("span.__word-list-marker").forEach((node) => node.remove());
1747
- });
1748
- const resolvedTargets = styleProfile.paragraphProfiles.map((profile, index) => {
1749
- const byIndex = doc.body.querySelector(`[data-word-p-index="${profile.index}"]`) ?? null;
1750
- const fallback = fallbackParagraphs[index] ?? null;
1751
- return {
1752
- profile,
1753
- node: byIndex ?? fallback
1754
- };
1755
- });
1756
- if (styleProfile.trailingDateAlignedRight && styleProfile.trailingDateText) {
1757
- let dateParagraph = null;
1758
- if (styleProfile.trailingDateParagraphIndex !== null && styleProfile.trailingDateParagraphIndex >= 0) {
1759
- dateParagraph = doc.body.querySelector(
1760
- `[data-word-p-index="${styleProfile.trailingDateParagraphIndex}"]`
1761
- ) ?? fallbackParagraphs[styleProfile.trailingDateParagraphIndex] ?? null;
1762
- } else {
1763
- dateParagraph = fallbackParagraphs.slice().reverse().find((p) => {
1764
- const text = (p.textContent ?? "").replace(/\s+/g, "");
1765
- const target = styleProfile.trailingDateText?.replace(/\s+/g, "") ?? "";
1766
- return target.length > 0 && text.includes(target);
1767
- }) ?? null;
1768
- }
1769
- const dateIndex = dateParagraph ? fallbackParagraphs.indexOf(dateParagraph) : -1;
1770
- const hasContentAfterDate = dateIndex >= 0 ? hasMeaningfulParagraphAfter(fallbackParagraphs, dateIndex) : false;
1771
- if (dateParagraph && !hasContentAfterDate) {
1772
- let existingEmptyCount = 0;
1773
- let cursor = dateParagraph.previousElementSibling;
1774
- while (cursor && cursor.tagName.toLowerCase() === "p" && (cursor.textContent ?? "").trim().length === 0) {
1775
- existingEmptyCount += 1;
1776
- cursor = cursor.previousElementSibling;
1777
- }
1778
- const needed = Math.max(0, styleProfile.trailingEmptyParagraphCountBeforeDate - existingEmptyCount);
1779
- for (let i = 0; i < needed; i += 1) {
1780
- const spacer = doc.createElement("p");
1781
- spacer.innerHTML = "<br/>";
1782
- dateParagraph.parentElement?.insertBefore(spacer, dateParagraph);
1783
- }
1784
- }
1785
- }
1786
- const paragraphs = Array.from(doc.body.querySelectorAll("p"));
1787
- const listCounters = /* @__PURE__ */ new Map();
1788
- const orderedTargets = [];
1789
- for (const target of resolvedTargets) {
1790
- const para = target.node;
1791
- const profile = target.profile;
1792
- if (!para) continue;
1793
- orderedTargets.push(para);
1794
- para.removeAttribute("data-word-list");
1795
- para.style.textAlign = profile.align;
1796
- if (profile.beforePx !== null) para.style.marginTop = `${profile.beforePx.toFixed(2)}px`;
1797
- if (profile.afterPx !== null) para.style.marginBottom = `${profile.afterPx.toFixed(2)}px`;
1798
- if (profile.lineHeightRule === "auto" && profile.lineHeightRatio !== null) {
1799
- para.style.lineHeight = profile.lineHeightRatio.toFixed(6);
1800
- } else if ((profile.lineHeightRule === "exact" || profile.lineHeightRule === "atLeast") && profile.lineHeightPx !== null) {
1801
- para.style.lineHeight = `${profile.lineHeightPx.toFixed(2)}px`;
1802
- }
1803
- if (profile.indentLeftPx !== null) para.style.marginLeft = `${profile.indentLeftPx.toFixed(2)}px`;
1804
- if (profile.indentRightPx !== null) para.style.marginRight = `${profile.indentRightPx.toFixed(2)}px`;
1805
- if (profile.firstLinePx !== null) para.style.textIndent = `${profile.firstLinePx.toFixed(2)}px`;
1806
- if (profile.hangingPx !== null) para.style.textIndent = `${(-profile.hangingPx).toFixed(2)}px`;
1807
- if (profile.runs.length > 0 && para.querySelector("img,table,svg,canvas") === null) {
1808
- const currentTextNormalized = (para.textContent ?? "").replace(/\s+/g, "");
1809
- const runTextNormalized = profile.runs.map((run) => run.text).join("").replace(/\s+/g, "");
1810
- if (runTextNormalized.length > 0 && currentTextNormalized === runTextNormalized) {
1811
- para.innerHTML = paragraphToRunHtml(profile.runs);
1812
- }
1813
- }
1814
- if (profile.listNumId !== null && profile.listLevel !== null) {
1815
- para.setAttribute("data-word-list", "1");
1816
- if (profile.sectionBreakBefore) {
1817
- listCounters.set(profile.listNumId, []);
1818
- }
1819
- const currentLevel = Math.max(0, profile.listLevel);
1820
- const levels = listCounters.get(profile.listNumId) ?? [];
1821
- const prevValue = levels[currentLevel] ?? profile.listStartAt - 1;
1822
- const nextValue = prevValue + 1;
1823
- levels[currentLevel] = nextValue;
1824
- for (let lv = currentLevel + 1; lv < levels.length; lv += 1) {
1825
- levels[lv] = 0;
1826
- }
1827
- listCounters.set(profile.listNumId, levels);
1828
- const markerText = formatListMarkerByPattern(profile.listTextPattern, currentLevel, levels, profile.listFormat);
1829
- const plainText = (para.textContent ?? "").replace(/\s+/g, " ").trim();
1830
- const alreadyHasMarker = plainText.startsWith(markerText);
1831
- if (!alreadyHasMarker) {
1832
- const marker = doc.createElement("span");
1833
- marker.className = "__word-list-marker";
1834
- marker.setAttribute("data-word-list-marker", "1");
1835
- marker.textContent = `${markerText} `;
1836
- marker.style.display = "inline-block";
1837
- marker.style.minWidth = "1.8em";
1838
- marker.style.marginLeft = currentLevel > 0 ? `${currentLevel * 1.2}em` : "0";
1839
- marker.style.color = "inherit";
1840
- marker.style.fontWeight = "inherit";
1841
- para.prepend(marker);
1842
- }
1843
- }
1844
- }
1845
- normalizeEmptyParagraphMarkers(paragraphs);
1846
- return orderedTargets;
1847
- }
1848
- function paragraphHeightPx(paragraph) {
1849
- const rect = paragraph.getBoundingClientRect();
1850
- if (rect.height > 0) return rect.height;
1851
- const lh = Number.parseFloat(getComputedStyle(paragraph).lineHeight || "0");
1852
- if (Number.isFinite(lh) && lh > 0) return lh;
1853
- return 16;
1854
- }
1855
- function insertPageSpacerBefore(doc, paragraph, heightPx) {
1856
- if (heightPx <= 0.5) return;
1857
- const spacer = doc.createElement("div");
1858
- spacer.dataset.wordPageSpacer = "1";
1859
- spacer.style.height = `${heightPx.toFixed(2)}px`;
1860
- spacer.style.width = "100%";
1861
- spacer.style.pointerEvents = "none";
1862
- spacer.style.userSelect = "none";
1863
- paragraph.parentElement?.insertBefore(spacer, paragraph);
1864
- }
1865
- function removePaginationSpacers(doc) {
1866
- doc.querySelectorAll("[data-word-page-spacer='1']").forEach((node) => node.remove());
1867
- }
1868
- function estimateGroupHeight(paragraphs, idx, profile, contentHeight) {
1869
- const currentH = paragraphHeightPx(paragraphs[idx]);
1870
- if (!profile.keepNext) return currentH;
1871
- const next = paragraphs[idx + 1];
1872
- if (!next) return currentH;
1873
- const nextH = paragraphHeightPx(next);
1874
- const sum = currentH + nextH;
1875
- if (sum > contentHeight) return currentH;
1876
- return sum;
1877
- }
1878
- function applyKeepPagination(doc, styleProfile, paragraphs) {
1879
- removePaginationSpacers(doc);
1880
- const contentHeight = Math.max(120, styleProfile.pageHeightPx - styleProfile.pageMarginTopPx - styleProfile.pageMarginBottomPx);
1881
- const count = Math.min(styleProfile.paragraphProfiles.length, paragraphs.length);
1882
- let used = 0;
1883
- for (let i = 0; i < count; i += 1) {
1884
- const p = paragraphs[i];
1885
- const profile = styleProfile.paragraphProfiles[i];
1886
- const h = paragraphHeightPx(p);
1887
- const forceBreak = profile.pageBreakBefore;
1888
- if (forceBreak && used > 0) {
1889
- insertPageSpacerBefore(doc, p, contentHeight - used);
1890
- used = 0;
1891
- }
1892
- const groupHeight = estimateGroupHeight(paragraphs, i, profile, contentHeight);
1893
- if ((profile.keepLines || profile.keepNext) && used > 0 && used + groupHeight > contentHeight) {
1894
- insertPageSpacerBefore(doc, p, contentHeight - used);
1895
- used = 0;
1896
- }
1897
- if (used > 0 && used + h > contentHeight) {
1898
- insertPageSpacerBefore(doc, p, contentHeight - used);
1899
- used = 0;
1900
- }
1901
- used += h;
1902
- if (used >= contentHeight) {
1903
- used = used % contentHeight;
1904
- }
1905
- }
1906
- }
1907
- function applyFormattingMarks(doc, showFormattingMarks) {
1908
- const styleEl = ensureStyleTag(doc, "__word_view_options__");
1909
- styleEl.textContent = `
1910
- p[data-word-empty="1"]::before { content: "\\00a0"; }
1911
- ${showFormattingMarks ? `
1912
- p::after {
1913
- content: "\u21B5";
1914
- color: #66aef9;
1915
- font-size: 0.85em;
1916
- margin-left: 3px;
1917
- }
1918
- br::after {
1919
- content: "\u21B5";
1920
- color: #66aef9;
1921
- }
1922
- ` : ""}
1923
- `;
1924
- }
1925
- function applyWordRenderModel({ doc, styleProfile, showFormattingMarks }) {
1926
- const effectiveProfile = styleProfile ?? createFallbackWordStyleProfile("__default_a4__");
1927
- applyWordHtmlCompatibility(doc, {
1928
- forceBodyFontFamily: effectiveProfile.bodyFontFamily,
1929
- forceHeadingFontFamily: effectiveProfile.titleFontFamily
1930
- });
1931
- let paragraphs = Array.from(doc.body.querySelectorAll("p"));
1932
- normalizeEmptyParagraphMarkers(paragraphs);
1933
- applyBaseProfileCss(doc, effectiveProfile);
1934
- applyInlineLayoutGuards(doc, effectiveProfile);
1935
- if (styleProfile) {
1936
- paragraphs = applyParagraphProfiles(doc, styleProfile);
1937
- applyKeepPagination(doc, styleProfile, paragraphs);
1938
- }
1939
- applyFormattingMarks(doc, showFormattingMarks);
1940
- }
1941
-
1942
- // src/core/DocsWordElement.ts
1943
- var VERSION = "0.1.2";
1944
- var MESSAGES = {
1945
- zh: {
1946
- readClipboard: "\u4ECE\u7CFB\u7EDF\u526A\u8D34\u677F\u8BFB\u53D6",
1947
- uploadWord: "\u4E0A\u4F20 Word",
1948
- clear: "\u6E05\u7A7A",
1949
- pastePlaceholder: "\u5728\u6B64\u5904\u7C98\u8D34 Word/WPS/Google Docs \u5185\u5BB9\uFF08Ctrl/Cmd+V\uFF09",
1950
- waitImport: "\u7B49\u5F85\u5185\u5BB9\u5BFC\u5165",
1951
- loadedHtml: "\u5DF2\u52A0\u8F7D HTML \u5FEB\u7167",
1952
- cleared: "\u6587\u6863\u5DF2\u6E05\u7A7A",
1953
- loadedWord: (name) => `\u5DF2\u52A0\u8F7D Word \u6587\u4EF6: ${name}`,
1954
- importedClipboard: "\u5DF2\u5BFC\u5165\u526A\u8D34\u677F\u5185\u5BB9",
1955
- noContent: "\u672A\u68C0\u6D4B\u5230\u53EF\u5BFC\u5165\u5185\u5BB9",
1956
- noClipboardRead: "\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301 clipboard.read",
1957
- parseFailed: "Word \u89E3\u6790\u5931\u8D25",
1958
- clipboardReadFailed: "\u8BFB\u53D6\u526A\u8D34\u677F\u5931\u8D25",
1959
- errorPrefix: "\u9519\u8BEF: "
1960
- },
1961
- en: {
1962
- readClipboard: "Read clipboard",
1963
- uploadWord: "Upload Word",
1964
- clear: "Clear",
1965
- pastePlaceholder: "Paste Word/WPS/Google Docs content here (Ctrl/Cmd+V)",
1966
- waitImport: "Waiting for input",
1967
- loadedHtml: "HTML snapshot loaded",
1968
- cleared: "Document cleared",
1969
- loadedWord: (name) => `Word file loaded: ${name}`,
1970
- importedClipboard: "Clipboard content imported",
1971
- noContent: "No importable content detected",
1972
- noClipboardRead: "navigator.clipboard.read is not supported in this browser",
1973
- parseFailed: "Word parse failed",
1974
- clipboardReadFailed: "Failed to read clipboard",
1975
- errorPrefix: "Error: "
1976
- }
1977
- };
1978
- var BASE_CSS = `
1979
- :host{display:block;border:1px solid #d8deea;border-radius:12px;background:#fff;overflow:hidden;font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto}
1980
- .toolbar{display:flex;gap:8px;flex-wrap:wrap;padding:10px;border-bottom:1px solid #e8edf6;background:#f8faff}
1981
- button{border:1px solid #c8d2eb;background:#fff;border-radius:9px;padding:6px 10px;cursor:pointer}
1982
- .paste{width:100%;min-height:40px;border:1px dashed #95acef;border-radius:10px;background:#edf3ff;padding:8px 10px;box-sizing:border-box;resize:vertical}
1983
- .hint{display:block;padding:0 10px 10px;color:#4b5c82;font-size:12px}
1984
- iframe{width:100%;min-height:760px;border:0}
1985
- `;
1986
- var DocsWordElement = class extends HTMLElement {
1987
- rootRef;
1988
- toolbar;
1989
- btnRead;
1990
- btnUpload;
1991
- btnClear;
1992
- frame;
1993
- pasteArea;
1994
- fileInput;
1995
- hint;
1996
- htmlSnapshot;
1997
- styleProfile = null;
1998
- frameHeight = 0;
1999
- locale = "zh";
2000
- constructor() {
2001
- super();
2002
- this.rootRef = this.attachShadow({ mode: "open" });
2003
- this.locale = this.parseLocale(this.getAttribute("lang"));
2004
- this.htmlSnapshot = buildHtmlSnapshot("<p><br/></p>");
2005
- const style = document.createElement("style");
2006
- style.textContent = BASE_CSS;
2007
- this.toolbar = document.createElement("div");
2008
- this.toolbar.className = "toolbar";
2009
- this.btnRead = document.createElement("button");
2010
- this.btnRead.onclick = () => void this.loadClipboard();
2011
- this.btnUpload = document.createElement("button");
2012
- this.btnUpload.onclick = () => this.fileInput.click();
2013
- this.btnClear = document.createElement("button");
2014
- this.btnClear.onclick = () => this.clear();
2015
- this.fileInput = document.createElement("input");
2016
- this.fileInput.type = "file";
2017
- this.fileInput.accept = ".docx";
2018
- this.fileInput.style.display = "none";
2019
- this.fileInput.onchange = () => void this.onUpload();
2020
- this.toolbar.append(this.btnRead, this.btnUpload, this.btnClear, this.fileInput);
2021
- this.pasteArea = document.createElement("textarea");
2022
- this.pasteArea.className = "paste";
2023
- this.pasteArea.placeholder = "";
2024
- this.pasteArea.onpaste = (event) => {
2025
- event.preventDefault();
2026
- void this.applyFromClipboardData(event.clipboardData);
2027
- };
2028
- this.hint = document.createElement("span");
2029
- this.hint.className = "hint";
2030
- this.hint.textContent = "";
2031
- this.frame = document.createElement("iframe");
2032
- this.frame.sandbox.add("allow-same-origin", "allow-scripts");
2033
- this.frame.onload = () => this.onFrameLoad();
2034
- this.rootRef.append(style, this.toolbar, this.pasteArea, this.hint, this.frame);
2035
- this.syncLocaleText();
2036
- this.syncToolbarVisibility();
2037
- }
2038
- static get observedAttributes() {
2039
- return ["lang", "show-toolbar"];
2040
- }
2041
- attributeChangedCallback(name, _, newValue) {
2042
- if (name === "lang") {
2043
- this.locale = this.parseLocale(newValue);
2044
- this.syncLocaleText();
2045
- return;
2046
- }
2047
- if (name === "show-toolbar") {
2048
- this.syncToolbarVisibility();
2049
- }
2050
- }
2051
- connectedCallback() {
2052
- this.renderSnapshot();
2053
- this.dispatchEvent(new CustomEvent("docsjs-ready", { detail: { version: VERSION } }));
2054
- }
2055
- setSnapshot(rawHtml) {
2056
- this.loadHtml(rawHtml);
2057
- }
2058
- loadHtml(rawHtml) {
2059
- this.styleProfile = null;
2060
- this.htmlSnapshot = buildHtmlSnapshot(rawHtml);
2061
- this.renderSnapshot();
2062
- this.setHint(MESSAGES[this.locale].loadedHtml);
2063
- this.emitChange("api");
2064
- }
2065
- getSnapshot() {
2066
- return this.htmlSnapshot;
2067
- }
2068
- clear() {
2069
- this.styleProfile = null;
2070
- this.htmlSnapshot = buildHtmlSnapshot("<p><br/></p>");
2071
- this.renderSnapshot();
2072
- this.setHint(MESSAGES[this.locale].cleared);
2073
- this.emitChange("clear");
2074
- }
2075
- async loadDocx(file) {
2076
- await this.applyDocx(file);
2077
- }
2078
- async onUpload() {
2079
- const file = this.fileInput.files?.[0];
2080
- if (!file) return;
2081
- await this.applyDocx(file);
2082
- this.fileInput.value = "";
2083
- }
2084
- async applyDocx(file) {
2085
- try {
2086
- const [snapshot, profile] = await Promise.all([
2087
- parseDocxToHtmlSnapshot(file),
2088
- parseDocxStyleProfile(file)
2089
- ]);
2090
- this.styleProfile = profile;
2091
- this.htmlSnapshot = snapshot;
2092
- this.renderSnapshot();
2093
- this.setHint(MESSAGES[this.locale].loadedWord(profile.sourceFileName));
2094
- this.emitChange("upload", profile.sourceFileName);
2095
- } catch (error) {
2096
- this.emitError(error instanceof Error ? error.message : MESSAGES[this.locale].parseFailed);
2097
- }
2098
- }
2099
- async loadClipboard() {
2100
- if (!navigator.clipboard?.read) {
2101
- this.emitError(MESSAGES[this.locale].noClipboardRead);
2102
- return;
2103
- }
2104
- try {
2105
- const items = await navigator.clipboard.read();
2106
- const payload = await extractFromClipboardItems(items);
2107
- this.applyPayload(payload.html, payload.text);
2108
- } catch (error) {
2109
- this.emitError(error instanceof Error ? error.message : MESSAGES[this.locale].clipboardReadFailed);
2110
- }
2111
- }
2112
- async applyFromClipboardData(data) {
2113
- if (!data) return;
2114
- const payload = await extractFromClipboardDataTransfer(data);
2115
- this.applyPayload(payload.html, payload.text);
2116
- }
2117
- applyPayload(html, text) {
2118
- this.styleProfile = null;
2119
- if (html.trim()) {
2120
- this.htmlSnapshot = buildHtmlSnapshot(html);
2121
- } else if (text.trim()) {
2122
- this.htmlSnapshot = buildHtmlSnapshot(`<p>${text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")}</p>`);
2123
- } else {
2124
- this.setHint(MESSAGES[this.locale].noContent);
2125
- return;
2126
- }
2127
- this.renderSnapshot();
2128
- this.setHint(MESSAGES[this.locale].importedClipboard);
2129
- this.emitChange("paste");
2130
- }
2131
- onFrameLoad() {
2132
- const doc = this.frame.contentDocument;
2133
- if (!doc) return;
2134
- applyWordRenderModel({
2135
- doc,
2136
- styleProfile: this.styleProfile,
2137
- showFormattingMarks: false
2138
- });
2139
- this.syncHeight();
2140
- window.setTimeout(() => this.syncHeight(), 120);
2141
- }
2142
- syncHeight() {
2143
- const doc = this.frame.contentDocument;
2144
- if (!doc) return;
2145
- const measured = Math.max(760, doc.body.scrollHeight, doc.documentElement.scrollHeight);
2146
- const next = measured + 24;
2147
- if (Math.abs(next - this.frameHeight) < 2) return;
2148
- this.frameHeight = next;
2149
- this.frame.style.height = `${next}px`;
2150
- }
2151
- renderSnapshot() {
2152
- this.frame.srcdoc = this.htmlSnapshot;
2153
- }
2154
- emitChange(source, fileName) {
2155
- this.dispatchEvent(new CustomEvent("docsjs-change", { detail: { htmlSnapshot: this.htmlSnapshot, source, fileName } }));
2156
- }
2157
- emitError(message) {
2158
- this.dispatchEvent(new CustomEvent("docsjs-error", { detail: { message } }));
2159
- this.setHint(`${MESSAGES[this.locale].errorPrefix}${message}`);
2160
- }
2161
- setHint(text) {
2162
- this.hint.textContent = text;
2163
- }
2164
- parseLocale(value) {
2165
- return value?.toLowerCase() === "en" ? "en" : "zh";
2166
- }
2167
- syncToolbarVisibility() {
2168
- const raw = this.getAttribute("show-toolbar");
2169
- const show = raw === null || raw === "" || raw === "1" || raw.toLowerCase() === "true";
2170
- this.toolbar.style.display = show ? "flex" : "none";
2171
- }
2172
- syncLocaleText() {
2173
- const t = MESSAGES[this.locale];
2174
- this.btnRead.textContent = t.readClipboard;
2175
- this.btnUpload.textContent = t.uploadWord;
2176
- this.btnClear.textContent = t.clear;
2177
- this.pasteArea.placeholder = t.pastePlaceholder;
2178
- if (!this.hint.textContent || this.hint.textContent === MESSAGES.en.waitImport || this.hint.textContent === MESSAGES.zh.waitImport) {
2179
- this.hint.textContent = t.waitImport;
2180
- }
2181
- }
2182
- };
2183
- function defineDocsWordElement() {
2184
- if (!customElements.get("docs-word-editor")) {
2185
- customElements.define("docs-word-editor", DocsWordElement);
2186
- }
2187
- }
2188
-
2189
- export {
2190
- DocsWordElement,
2191
- defineDocsWordElement
2192
- };
2193
- //# sourceMappingURL=chunk-IBVWD4UO.js.map