@richscripts/richtexteditor 2.4.0 → 2.4.2

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.
Files changed (60) hide show
  1. package/migration/README.md +100 -0
  2. package/migration/cli.js +294 -0
  3. package/migration/index.d.ts +18 -0
  4. package/migration/index.js +242 -0
  5. package/migration/report-schema-v2.json +127 -0
  6. package/package.json +14 -2
  7. package/richtexteditor/CHANGELOG.md +326 -1
  8. package/richtexteditor/all_plugins.js +14982 -4831
  9. package/richtexteditor/all_plugins.min.js +62 -42
  10. package/richtexteditor/plugins/accessibilitychecker.js +8 -7
  11. package/richtexteditor/plugins/aitoolkit.css +17 -8
  12. package/richtexteditor/plugins/aitoolkit.js +8 -8
  13. package/richtexteditor/plugins/all_plugins.js +14982 -4831
  14. package/richtexteditor/plugins/all_plugins.min.js +62 -42
  15. package/richtexteditor/plugins/autocorrect.js +353 -0
  16. package/richtexteditor/plugins/bookmarkcard.js +12 -1
  17. package/richtexteditor/plugins/changecase.js +238 -0
  18. package/richtexteditor/plugins/comments.js +15 -28
  19. package/richtexteditor/plugins/contentminimap.js +6 -5
  20. package/richtexteditor/plugins/crdt-engine.js +2201 -198
  21. package/richtexteditor/plugins/crdt-engine.min.js +5 -5
  22. package/richtexteditor/plugins/crossreference.js +408 -0
  23. package/richtexteditor/plugins/documentimport.js +915 -21
  24. package/richtexteditor/plugins/documentoutline.js +5 -4
  25. package/richtexteditor/plugins/docxexport.js +960 -0
  26. package/richtexteditor/plugins/draghandle.js +309 -0
  27. package/richtexteditor/plugins/footnotes.js +398 -0
  28. package/richtexteditor/plugins/formatpainter.js +298 -0
  29. package/richtexteditor/plugins/formattingmarks.js +246 -0
  30. package/richtexteditor/plugins/html2pdf.js +108 -4
  31. package/richtexteditor/plugins/imageeditor.js +37 -7
  32. package/richtexteditor/plugins/insertgallery.js +553 -170
  33. package/richtexteditor/plugins/keyboarda11y.js +316 -0
  34. package/richtexteditor/plugins/linenumbers.js +352 -0
  35. package/richtexteditor/plugins/linkchecker.js +396 -0
  36. package/richtexteditor/plugins/mentionplugin.js +4 -3
  37. package/richtexteditor/plugins/mergefields.js +328 -0
  38. package/richtexteditor/plugins/multilevellist.js +137 -0
  39. package/richtexteditor/plugins/pagination.js +505 -0
  40. package/richtexteditor/plugins/pdfexport.js +1137 -0
  41. package/richtexteditor/plugins/permanentpen.js +211 -0
  42. package/richtexteditor/plugins/revisionhistory.js +47 -34
  43. package/richtexteditor/plugins/rtlui.js +150 -0
  44. package/richtexteditor/plugins/sanitizer.js +510 -0
  45. package/richtexteditor/plugins/slashcommand.js +142 -17
  46. package/richtexteditor/plugins/spellcheck.js +6 -3
  47. package/richtexteditor/plugins/tableofcontents.js +353 -0
  48. package/richtexteditor/plugins/tabletools.js +342 -0
  49. package/richtexteditor/plugins/textdirection.js +313 -0
  50. package/richtexteditor/plugins/trackedchanges.js +37 -6
  51. package/richtexteditor/plugins/tui.image-editor/tui-code-snippet.min.js +1 -1
  52. package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.js +1 -1
  53. package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.js +1 -1
  54. package/richtexteditor/plugins/vendor/html2pdf.bundle.min.js +3 -0
  55. package/richtexteditor/plugins/watermark.js +214 -0
  56. package/richtexteditor/plugins/wordexport.js +0 -0
  57. package/richtexteditor/plugins/yjscollab.js +49 -11
  58. package/richtexteditor/rte-config.js +44 -44
  59. package/richtexteditor/rte.js +91 -63
  60. package/richtexteditor/rte_theme_default.css +483 -384
@@ -0,0 +1,960 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-08-02 Real .docx export, in the browser, with no library and no server.
4
+ //
5
+ // The asymmetry this closes: `documentimport.js` reads a genuine OOXML package
6
+ // client-side, but the only export was `wordexport.js`, which wraps the HTML in
7
+ // an MSO-flavoured container and names it .doc. Word opens that, but it is not
8
+ // a .docx — it round-trips badly, some corporate policies block HTML-with-a-doc
9
+ // extension outright, and Word nags on save. A real .docx was only available
10
+ // through a server route that ships with the marketing site, not with the
11
+ // product.
12
+ //
13
+ // So: import real OOXML, export real OOXML, same page, no upload.
14
+ //
15
+ // This deliberately emits exactly what documentimport.js reads back — same
16
+ // hyperlink relationships, same numbering ids, same footnote/comment parts,
17
+ // same revision conventions — so HTML -> .docx -> HTML is a genuine round trip
18
+ // rather than two features that happen to share a file extension.
19
+ //
20
+ // API:
21
+ // editor.getDocxBlob(options) -> Promise<Blob>
22
+ // editor.exportToDocx(filename, options) -> Promise<string> (downloads)
23
+ // Command: exec_command "exportdocx".
24
+ // Config:
25
+ // config.docxExport = false // disable
26
+ // config.docxExportFileName = "report"
27
+ // config.docxExportTitle / docxExportAuthor
28
+ RTE_DefaultConfig.plugin_docxexport = RTE_Plugin_DocxExport;
29
+ if (typeof RTE_DefaultConfig.docxExport === "undefined") RTE_DefaultConfig.docxExport = true;
30
+
31
+ function RTE_Plugin_DocxExport() {
32
+ var obj = this;
33
+ var config, editor;
34
+
35
+ obj.PluginName = "DocxExport";
36
+
37
+ obj.InitConfig = function (argconfig) { config = argconfig; };
38
+
39
+ obj.InitEditor = function (argeditor) {
40
+ editor = argeditor;
41
+ if (config.docxExport === false) return;
42
+
43
+ editor.getDocxBlob = function (options) { return buildDocx(options || {}); };
44
+ editor.exportToDocx = function (filename, options) {
45
+ return buildDocx(options || {}).then(function (blob) {
46
+ var base = sanitizeName(filename) || sanitizeName(config.docxExportFileName) || defaultBase();
47
+ return download(blob, base + ".docx");
48
+ });
49
+ };
50
+
51
+ editor.attachEvent("exec_command_exportdocx", function (state) {
52
+ state.returnValue = true;
53
+ state.stopBubble = true;
54
+ try {
55
+ editor.exportToDocx().catch(function (e) { if (window.console) console.error("docxexport:", e); });
56
+ } catch (e) { if (window.console) console.error("docxexport:", e); }
57
+ });
58
+ // The slash entry lives in slashcommand.js, gated on the API being
59
+ // present: plugins initialise in bundle order and "docxexport" sorts
60
+ // before "slashcommand", so registering from here would silently do
61
+ // nothing.
62
+ };
63
+
64
+ // ------------------------------------------------------------- XML basics
65
+ var NS = {
66
+ w: "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
67
+ r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
68
+ m: "http://schemas.openxmlformats.org/officeDocument/2006/math"
69
+ };
70
+ function esc(s) {
71
+ return String(s == null ? "" : s)
72
+ .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
73
+ .replace(/"/g, "&quot;")
74
+ // XML 1.0 permits only tab, newline and carriage return below 0x20.
75
+ // ONE stray control character makes the whole package unreadable —
76
+ // Word refuses to open the file rather than skipping the character.
77
+ // Written as \u escapes on purpose: a literal control byte in the
78
+ // source is invisible, survives copy-paste, and turns this file
79
+ // binary to every text tool that touches it.
80
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, "");
81
+ }
82
+
83
+ // --------------------------------------------------------------- ZIP part
84
+ //
85
+ // Deflate through CompressionStream — the mirror image of the
86
+ // DecompressionStream the importer uses, so the two halves rest on the same
87
+ // platform capability. Entries fall back to STORED when compression is
88
+ // unavailable, which is a valid ZIP and still opens in Word.
89
+ var CRC_TABLE = (function () {
90
+ var t = [], c, n, k;
91
+ for (n = 0; n < 256; n++) {
92
+ c = n;
93
+ for (k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
94
+ t[n] = c >>> 0;
95
+ }
96
+ return t;
97
+ })();
98
+ function crc32(bytes) {
99
+ var crc = 0xFFFFFFFF;
100
+ for (var i = 0; i < bytes.length; i++) crc = CRC_TABLE[(crc ^ bytes[i]) & 0xFF] ^ (crc >>> 8);
101
+ return (crc ^ 0xFFFFFFFF) >>> 0;
102
+ }
103
+ function utf8Bytes(str) { return new TextEncoder().encode(str); }
104
+
105
+ function deflateRaw(bytes) {
106
+ if (typeof CompressionStream === "undefined") return Promise.resolve(null);
107
+ try {
108
+ var cs = new CompressionStream("deflate-raw");
109
+ var writer = cs.writable.getWriter();
110
+ writer.write(bytes);
111
+ writer.close();
112
+ return new Response(cs.readable).arrayBuffer().then(function (buf) { return new Uint8Array(buf); });
113
+ } catch (e) { return Promise.resolve(null); }
114
+ }
115
+
116
+ function zip(entries) {
117
+ // entries: [{ name, bytes }]
118
+ var jobs = entries.map(function (e) {
119
+ return deflateRaw(e.bytes).then(function (comp) {
120
+ var useDeflate = comp && comp.length < e.bytes.length;
121
+ return {
122
+ name: e.name, raw: e.bytes,
123
+ data: useDeflate ? comp : e.bytes,
124
+ method: useDeflate ? 8 : 0
125
+ };
126
+ });
127
+ });
128
+ return Promise.all(jobs).then(function (items) {
129
+ var locals = [], central = [], offset = 0;
130
+ for (var i = 0; i < items.length; i++) {
131
+ var it = items[i];
132
+ var nameBytes = utf8Bytes(it.name);
133
+ var crc = crc32(it.raw);
134
+ var lh = new DataView(new ArrayBuffer(30));
135
+ lh.setUint32(0, 0x04034b50, true);
136
+ lh.setUint16(4, 20, true);
137
+ // Bit 11 marks the name as UTF-8. Without it a non-ASCII entry
138
+ // name is read in the archive's legacy code page.
139
+ lh.setUint16(6, 0x0800, true);
140
+ lh.setUint16(8, it.method, true);
141
+ lh.setUint32(14, crc, true);
142
+ lh.setUint32(18, it.data.length, true);
143
+ lh.setUint32(22, it.raw.length, true);
144
+ lh.setUint16(26, nameBytes.length, true);
145
+ var ch = new DataView(new ArrayBuffer(46));
146
+ ch.setUint32(0, 0x02014b50, true);
147
+ ch.setUint16(4, 20, true); ch.setUint16(6, 20, true);
148
+ ch.setUint16(8, 0x0800, true);
149
+ ch.setUint16(10, it.method, true);
150
+ ch.setUint32(16, crc, true);
151
+ ch.setUint32(20, it.data.length, true);
152
+ ch.setUint32(24, it.raw.length, true);
153
+ ch.setUint16(28, nameBytes.length, true);
154
+ ch.setUint32(42, offset, true);
155
+ locals.push(new Uint8Array(lh.buffer), nameBytes, it.data);
156
+ central.push(new Uint8Array(ch.buffer), nameBytes);
157
+ offset += 30 + nameBytes.length + it.data.length;
158
+ }
159
+ var centralSize = central.reduce(function (n, b) { return n + b.length; }, 0);
160
+ var eocd = new DataView(new ArrayBuffer(22));
161
+ eocd.setUint32(0, 0x06054b50, true);
162
+ eocd.setUint16(8, items.length, true);
163
+ eocd.setUint16(10, items.length, true);
164
+ eocd.setUint32(12, centralSize, true);
165
+ eocd.setUint32(16, offset, true);
166
+ return new Blob(locals.concat(central, [new Uint8Array(eocd.buffer)]),
167
+ { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
168
+ });
169
+ }
170
+
171
+ // ------------------------------------------------------------- conversion
172
+ function Builder() {
173
+ this.rels = []; // { id, type, target, mode }
174
+ this.media = []; // { name, bytes }
175
+ this.footnotes = []; // { id, text }
176
+ this.endnotes = [];
177
+ this.comments = []; // { id, author, initials, text }
178
+ this.numbering = {}; // numId -> "bullet" | "decimal"
179
+ this.relSeq = 0;
180
+ this.noteSeq = 1; // 0 and -1 are reserved for the separators
181
+ this.commentSeq = 0;
182
+ this.numSeq = 0;
183
+ }
184
+ Builder.prototype.rel = function (type, target, mode) {
185
+ var id = "rId" + (++this.relSeq);
186
+ this.rels.push({ id: id, type: type, target: target, mode: mode });
187
+ return id;
188
+ };
189
+
190
+ var HEADING = { h1: 1, h2: 2, h3: 3, h4: 4, h5: 5, h6: 6 };
191
+
192
+ function styleFrom(node, inherited) {
193
+ var s = {
194
+ bold: inherited.bold, italic: inherited.italic, underline: inherited.underline,
195
+ strike: inherited.strike, sizeHalfPoints: inherited.sizeHalfPoints,
196
+ color: inherited.color, highlight: inherited.highlight, vertAlign: inherited.vertAlign,
197
+ mono: inherited.mono
198
+ };
199
+ var tag = node.tagName ? node.tagName.toLowerCase() : "";
200
+ if (tag === "b" || tag === "strong") s.bold = true;
201
+ if (tag === "i" || tag === "em") s.italic = true;
202
+ if (tag === "u") s.underline = true;
203
+ if (tag === "s" || tag === "strike") s.strike = true;
204
+ if (tag === "sup") s.vertAlign = "superscript";
205
+ if (tag === "sub") s.vertAlign = "subscript";
206
+ if (tag === "code" || tag === "kbd" || tag === "samp" || tag === "pre") s.mono = true;
207
+
208
+ var css = node.style;
209
+ if (css) {
210
+ if (/bold|^[6-9]00$/.test(css.fontWeight || "")) s.bold = true;
211
+ if (css.fontStyle === "italic") s.italic = true;
212
+ var deco = css.textDecorationLine || css.textDecoration || "";
213
+ if (deco.indexOf("underline") >= 0) s.underline = true;
214
+ if (deco.indexOf("line-through") >= 0) s.strike = true;
215
+ if (css.color) { var c = hexColor(css.color); if (c) s.color = c; }
216
+ if (css.backgroundColor) { var h = hexColor(css.backgroundColor); if (h) s.highlight = h; }
217
+ if (css.fontSize) {
218
+ var pt = cssToPoints(css.fontSize);
219
+ // w:sz is in HALF-POINTS. Writing points here makes every
220
+ // exported document render at half its intended size.
221
+ if (pt) s.sizeHalfPoints = Math.round(pt * 2);
222
+ }
223
+ }
224
+ return s;
225
+ }
226
+ function hexColor(v) {
227
+ var m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(v).replace(/\s/g, ""));
228
+ if (m) {
229
+ var hex = m[1];
230
+ if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
231
+ return hex.toUpperCase();
232
+ }
233
+ var rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(String(v));
234
+ if (!rgb) return null;
235
+ function two(n) { var s = Number(n).toString(16).toUpperCase(); return s.length < 2 ? "0" + s : s; }
236
+ return two(rgb[1]) + two(rgb[2]) + two(rgb[3]);
237
+ }
238
+ function cssToPoints(v) {
239
+ var m = /^\s*([\d.]+)\s*(px|pt|em|rem|%)?\s*$/.exec(String(v));
240
+ if (!m) return null;
241
+ var n = parseFloat(m[1]);
242
+ if (!isFinite(n)) return null;
243
+ switch (m[2]) {
244
+ case "pt": return n;
245
+ case "em": case "rem": return n * 11;
246
+ case "%": return n / 100 * 11;
247
+ default: return n * 0.75;
248
+ }
249
+ }
250
+
251
+ function runProps(s) {
252
+ var p = "";
253
+ if (s.mono) p += '<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/>';
254
+ if (s.bold) p += "<w:b/>";
255
+ if (s.italic) p += "<w:i/>";
256
+ if (s.underline) p += '<w:u w:val="single"/>';
257
+ if (s.strike) p += "<w:strike/>";
258
+ if (s.color) p += '<w:color w:val="' + s.color + '"/>';
259
+ if (s.highlight) p += '<w:shd w:val="clear" w:color="auto" w:fill="' + s.highlight + '"/>';
260
+ if (s.vertAlign) p += '<w:vertAlign w:val="' + s.vertAlign + '"/>';
261
+ if (s.sizeHalfPoints) p += '<w:sz w:val="' + s.sizeHalfPoints + '"/><w:szCs w:val="' + s.sizeHalfPoints + '"/>';
262
+ return p ? "<w:rPr>" + p + "</w:rPr>" : "";
263
+ }
264
+
265
+ // A run's text must preserve its spaces, or Word collapses the leading and
266
+ // trailing ones and words run together across formatting boundaries.
267
+ function textRun(text, s, deleted) {
268
+ if (!text) return "";
269
+ var parts = String(text).split("\n");
270
+ var out = "";
271
+ for (var i = 0; i < parts.length; i++) {
272
+ if (i) out += "<w:r>" + runProps(s) + "<w:br/></w:r>";
273
+ if (!parts[i]) continue;
274
+ var tag = deleted ? "w:delText" : "w:t";
275
+ out += "<w:r>" + runProps(s) + "<" + tag + ' xml:space="preserve">' + esc(parts[i]) + "</" + tag + ">";
276
+ out += "</w:r>";
277
+ }
278
+ return out;
279
+ }
280
+
281
+ function stamp(node) {
282
+ var author = node.getAttribute("data-author") || node.getAttribute("data-comment-author") || "RichTextEditor";
283
+ var date = node.getAttribute("data-date") || new Date().toISOString().replace(/\.\d+Z$/, "Z");
284
+ return ' w:author="' + esc(author) + '" w:date="' + esc(date) + '"';
285
+ }
286
+
287
+ // Inline content of one block-level element.
288
+ function inlineXml(node, style, b, revSeq) {
289
+ var out = "";
290
+ for (var i = 0; i < node.childNodes.length; i++) {
291
+ var c = node.childNodes[i];
292
+ if (c.nodeType === 3) {
293
+ var t = (c.nodeValue || "").replace(/[\r\n\t]+/g, " ");
294
+ if (t) out += textRun(t, style, false);
295
+ continue;
296
+ }
297
+ if (c.nodeType !== 1) continue;
298
+ var tag = c.tagName.toLowerCase();
299
+ if (tag === "script" || tag === "style") continue;
300
+ if (tag === "br") { out += "<w:r>" + runProps(style) + "<w:br/></w:r>"; continue; }
301
+
302
+ // Equations: the editor stores LaTeX in data-tex. Emitting the span's
303
+ // visible text would put "\frac{a}{b}" into the document as prose.
304
+ if (c.getAttribute && c.getAttribute("data-tex")) {
305
+ out += ommlFromTex(c.getAttribute("data-tex"));
306
+ continue;
307
+ }
308
+ // Footnote / endnote markers written by the importer and the
309
+ // footnotes plugin.
310
+ if ((tag === "sup" || tag === "span") && c.getAttribute("data-footnote")) {
311
+ out += noteRun(b, "footnote", c.getAttribute("data-footnote"));
312
+ continue;
313
+ }
314
+ if ((tag === "sup" || tag === "span") && c.getAttribute("data-endnote")) {
315
+ out += noteRun(b, "endnote", c.getAttribute("data-endnote"));
316
+ continue;
317
+ }
318
+ if (c.classList && c.classList.contains("rte-fn-ref")) {
319
+ var id = c.getAttribute("data-fn-id");
320
+ var body = id ? noteBodyFor(node.ownerDocument, id) : "";
321
+ out += noteRun(b, "footnote", body || (c.textContent || ""));
322
+ continue;
323
+ }
324
+ if (tag === "img") { out += imageXml(c, b); continue; }
325
+
326
+ var next = styleFrom(c, style);
327
+
328
+ if (tag === "a" && c.getAttribute("href")) {
329
+ var href = c.getAttribute("href");
330
+ var inner = inlineXml(c, next, b, revSeq);
331
+ if (href.charAt(0) === "#") {
332
+ out += '<w:hyperlink w:anchor="' + esc(href.slice(1)) + '">' + inner + "</w:hyperlink>";
333
+ } else {
334
+ // The address lives in the rels part; a hyperlink carries
335
+ // only an r:id. This is the same indirection the importer
336
+ // resolves on the way in.
337
+ var rid = b.rel("hyperlink", href, "External");
338
+ out += '<w:hyperlink r:id="' + rid + '">' + inner + "</w:hyperlink>";
339
+ }
340
+ continue;
341
+ }
342
+ if (tag === "ins" || tag === "del") {
343
+ var isDel = tag === "del";
344
+ var body2 = "";
345
+ // Deleted text goes in w:delText, never w:t.
346
+ (function collect(n, st) {
347
+ for (var j = 0; j < n.childNodes.length; j++) {
348
+ var k = n.childNodes[j];
349
+ if (k.nodeType === 3) { body2 += textRun((k.nodeValue || "").replace(/[\r\n\t]+/g, " "), st, isDel); continue; }
350
+ if (k.nodeType === 1) collect(k, styleFrom(k, st));
351
+ }
352
+ })(c, next);
353
+ out += "<w:" + tag + ' w:id="' + (revSeq.n++) + '"' + stamp(c) + ">" + body2 + "</w:" + tag + ">";
354
+ continue;
355
+ }
356
+ if (c.getAttribute && c.getAttribute("data-comment")) {
357
+ var cid = b.commentSeq++;
358
+ b.comments.push({
359
+ id: cid,
360
+ author: c.getAttribute("data-comment-author") || "RichTextEditor user",
361
+ initials: c.getAttribute("data-comment-initials") || "RTE",
362
+ text: c.getAttribute("data-comment")
363
+ });
364
+ out += '<w:commentRangeStart w:id="' + cid + '"/>' +
365
+ inlineXml(c, next, b, revSeq) +
366
+ '<w:commentRangeEnd w:id="' + cid + '"/>' +
367
+ '<w:r><w:commentReference w:id="' + cid + '"/></w:r>';
368
+ continue;
369
+ }
370
+ out += inlineXml(c, next, b, revSeq);
371
+ }
372
+ return out;
373
+ }
374
+
375
+ function noteBodyFor(doc, id) {
376
+ try {
377
+ var el = doc.querySelector('.rte-fn-note[data-fn-id="' + id + '"]');
378
+ return el ? (el.textContent || "").replace(/\s+/g, " ").trim() : "";
379
+ } catch (e) { return ""; }
380
+ }
381
+ function noteRun(b, kind, text) {
382
+ var id = b.noteSeq++;
383
+ (kind === "endnote" ? b.endnotes : b.footnotes).push({ id: id, text: text || "" });
384
+ return '<w:r><w:rPr><w:vertAlign w:val="superscript"/></w:rPr><w:' + kind + 'Reference w:id="' + id + '"/></w:r>';
385
+ }
386
+
387
+ function imageXml(img, b) {
388
+ var data = encodeImage(img);
389
+ if (!data) return "";
390
+ var name = "image" + (b.media.length + 1) + ".png";
391
+ b.media.push({ name: name, bytes: data.bytes });
392
+ var rid = b.rel("image", "media/" + name);
393
+ // EMU: 914400 per inch, and a CSS pixel is 1/96 inch.
394
+ var cx = Math.round(data.width * 914400 / 96);
395
+ var cy = Math.round(data.height * 914400 / 96);
396
+ var docPrId = b.media.length;
397
+ return '<w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0" ' +
398
+ 'xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing">' +
399
+ '<wp:extent cx="' + cx + '" cy="' + cy + '"/>' +
400
+ '<wp:docPr id="' + docPrId + '" name="Picture ' + docPrId + '" descr="' + esc(img.getAttribute("alt") || "") + '"/>' +
401
+ '<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">' +
402
+ '<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">' +
403
+ '<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">' +
404
+ '<pic:nvPicPr><pic:cNvPr id="' + docPrId + '" name="Picture ' + docPrId + '"/><pic:cNvPicPr/></pic:nvPicPr>' +
405
+ '<pic:blipFill><a:blip r:embed="' + rid + '"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill>' +
406
+ '<pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="' + cx + '" cy="' + cy + '"/></a:xfrm>' +
407
+ '<a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr>' +
408
+ "</pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>";
409
+ }
410
+
411
+ function encodeImage(img) {
412
+ try {
413
+ var w = img.naturalWidth || img.width, h = img.naturalHeight || img.height;
414
+ if (!w || !h) return null;
415
+ var scale = Math.min(1, 1600 / Math.max(w, h));
416
+ var cw = Math.max(1, Math.round(w * scale)), chh = Math.max(1, Math.round(h * scale));
417
+ var canvas = document.createElement("canvas");
418
+ canvas.width = cw; canvas.height = chh;
419
+ canvas.getContext("2d").drawImage(img, 0, 0, cw, chh);
420
+ var uri = canvas.toDataURL("image/png");
421
+ var comma = uri.indexOf(",");
422
+ if (comma < 0) return null;
423
+ var bin = atob(uri.slice(comma + 1));
424
+ var bytes = new Uint8Array(bin.length);
425
+ for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
426
+ return { bytes: bytes, width: cw, height: chh };
427
+ } catch (e) {
428
+ // Cross-origin images taint the canvas and cannot be read. Dropping
429
+ // one decorative image beats failing the whole export.
430
+ return null;
431
+ }
432
+ }
433
+
434
+ // --------------------------------------------------------------- OMML out
435
+ //
436
+ // A compact LaTeX -> OMML writer, matching the reader in documentimport.js
437
+ // so equations survive the round trip instead of arriving as their source.
438
+ var TEX_GLYPH = {
439
+ alpha: "α", beta: "β", gamma: "γ", delta: "δ", epsilon: "ε", zeta: "ζ", eta: "η",
440
+ theta: "θ", iota: "ι", kappa: "κ", lambda: "λ", mu: "μ", nu: "ν", xi: "ξ", pi: "π",
441
+ rho: "ρ", sigma: "σ", tau: "τ", upsilon: "υ", phi: "φ", varphi: "φ", chi: "χ",
442
+ psi: "ψ", omega: "ω", Gamma: "Γ", Delta: "Δ", Theta: "Θ", Lambda: "Λ", Xi: "Ξ",
443
+ Pi: "Π", Sigma: "Σ", Phi: "Φ", Psi: "Ψ", Omega: "Ω",
444
+ pm: "±", mp: "∓", times: "×", div: "÷", cdot: "⋅", neq: "≠", leq: "≤", geq: "≥",
445
+ approx: "≈", equiv: "≡", propto: "∝", infty: "∞", partial: "∂", nabla: "∇",
446
+ to: "→", rightarrow: "→", leftarrow: "←", Rightarrow: "⇒", Leftrightarrow: "⇔",
447
+ "in": "∈", notin: "∉", subset: "⊂", subseteq: "⊆", cup: "∪", cap: "∩",
448
+ emptyset: "∅", forall: "∀", exists: "∃", neg: "¬", land: "∧", lor: "∨",
449
+ dots: "…", ldots: "…", cdots: "⋯", langle: "⟨", rangle: "⟩",
450
+ lfloor: "⌊", rfloor: "⌋", lceil: "⌈", rceil: "⌉", quad: " ", ",": " ", ";": " "
451
+ };
452
+ var TEX_FUNCS = /^(sin|cos|tan|cot|sec|csc|sinh|cosh|tanh|log|ln|lg|exp|det|dim|gcd|arg|max|min|sup|inf)$/;
453
+
454
+ function ommlFromTex(tex) {
455
+ var src = String(tex || "").trim();
456
+ if (!src) return "";
457
+ var i = 0;
458
+ function mrun(t) { return t ? "<m:r><m:t>" + esc(t) + "</m:t></m:r>" : ""; }
459
+ function group() {
460
+ skipWs();
461
+ if (src.charAt(i) === "{") { i++; return until("}"); }
462
+ return one();
463
+ }
464
+ function skipWs() { while (i < src.length && /\s/.test(src.charAt(i))) i++; }
465
+ function one() {
466
+ skipWs();
467
+ if (src.charAt(i) === "\\") return command();
468
+ var ch = src.charAt(i++);
469
+ return mrun(ch);
470
+ }
471
+ function until(close) {
472
+ var out = "", depth = 0;
473
+ while (i < src.length) {
474
+ var ch = src.charAt(i);
475
+ if (ch === "{") depth++;
476
+ if (ch === close && depth === 0) { i++; break; }
477
+ if (ch === close) depth--;
478
+ out += token();
479
+ }
480
+ return out;
481
+ }
482
+ function command() {
483
+ i++; // backslash
484
+ var m = /^[A-Za-z]+/.exec(src.slice(i));
485
+ var name = m ? m[0] : src.charAt(i);
486
+ i += name.length;
487
+ switch (name) {
488
+ case "frac": case "dfrac": case "tfrac":
489
+ return "<m:f><m:fPr/><m:num>" + group() + "</m:num><m:den>" + group() + "</m:den></m:f>";
490
+ case "sqrt": {
491
+ skipWs();
492
+ var deg = "";
493
+ if (src.charAt(i) === "[") { i++; deg = until("]"); }
494
+ return "<m:rad><m:radPr><m:degHide m:val=\"" + (deg ? "0" : "1") + "\"/></m:radPr>" +
495
+ "<m:deg>" + deg + "</m:deg><m:e>" + group() + "</m:e></m:rad>";
496
+ }
497
+ case "sum": case "prod": case "int": case "oint": case "bigcup": case "bigcap": {
498
+ var chr = { sum: "∑", prod: "∏", "int": "∫", oint: "∮", bigcup: "⋃", bigcap: "⋂" }[name];
499
+ var sub = "", sup = "";
500
+ for (var g = 0; g < 2; g++) {
501
+ skipWs();
502
+ if (src.charAt(i) === "_") { i++; sub = group(); }
503
+ else if (src.charAt(i) === "^") { i++; sup = group(); }
504
+ else break;
505
+ }
506
+ return '<m:nary><m:naryPr><m:chr m:val="' + chr + '"/>' +
507
+ '<m:subHide m:val="' + (sub ? "0" : "1") + '"/><m:supHide m:val="' + (sup ? "0" : "1") + '"/></m:naryPr>' +
508
+ "<m:sub>" + sub + "</m:sub><m:sup>" + sup + "</m:sup><m:e>" + rest() + "</m:e></m:nary>";
509
+ }
510
+ case "left": {
511
+ var open = fence();
512
+ var inner = "", depth = 0;
513
+ while (i < src.length) {
514
+ if (src.slice(i, i + 5) === "\\left") depth++;
515
+ if (src.slice(i, i + 6) === "\\right") {
516
+ if (depth === 0) { i += 6; var close = fence(); return delim(open, close, inner); }
517
+ depth--;
518
+ }
519
+ inner += token();
520
+ }
521
+ return delim(open, ")", inner);
522
+ }
523
+ case "right": fence(); return "";
524
+ case "lim": {
525
+ skipWs();
526
+ var lim = "";
527
+ if (src.charAt(i) === "_") { i++; lim = group(); }
528
+ return "<m:limLow><m:e>" + mrun("lim") + "</m:e><m:lim>" + lim + "</m:lim></m:limLow>";
529
+ }
530
+ case "operatorname":
531
+ return "<m:func><m:funcPr/><m:fName>" + group() + "</m:fName><m:e>" + group() + "</m:e></m:func>";
532
+ case "hat": case "vec": case "tilde": case "bar": case "overline": case "dot": case "ddot": {
533
+ var mark = { hat: "̂", vec: "⃗", tilde: "̃", dot: "̇", ddot: "̈" }[name];
534
+ if (name === "bar" || name === "overline") return "<m:bar><m:barPr><m:pos m:val=\"top\"/></m:barPr><m:e>" + group() + "</m:e></m:bar>";
535
+ return '<m:acc><m:accPr><m:chr m:val="' + mark + '"/></m:accPr><m:e>' + group() + "</m:e></m:acc>";
536
+ }
537
+ case "underline":
538
+ return "<m:bar><m:barPr><m:pos m:val=\"bot\"/></m:barPr><m:e>" + group() + "</m:e></m:bar>";
539
+ case "boxed":
540
+ return "<m:borderBox><m:e>" + group() + "</m:e></m:borderBox>";
541
+ case "text": case "mathrm": case "mathbf": case "mathit":
542
+ return group();
543
+ case "\\":
544
+ return mrun(" ");
545
+ default:
546
+ if (TEX_FUNCS.test(name)) {
547
+ return "<m:func><m:funcPr/><m:fName>" + mrun(name) + "</m:fName><m:e>" + group() + "</m:e></m:func>";
548
+ }
549
+ if (TEX_GLYPH[name] !== undefined) return mrun(TEX_GLYPH[name]);
550
+ return mrun(name);
551
+ }
552
+ }
553
+ function fence() {
554
+ skipWs();
555
+ if (src.charAt(i) === "\\") { i++; var m2 = /^[A-Za-z]+|^./.exec(src.slice(i)); var n2 = m2 ? m2[0] : ""; i += n2.length; return TEX_GLYPH[n2] || n2; }
556
+ return src.charAt(i++);
557
+ }
558
+ function delim(open, close, inner) {
559
+ return '<m:d><m:dPr><m:begChr m:val="' + esc(open) + '"/><m:endChr m:val="' + esc(close) + '"/></m:dPr><m:e>' + inner + "</m:e></m:d>";
560
+ }
561
+ function rest() {
562
+ var out = "";
563
+ while (i < src.length && src.charAt(i) !== "}") out += token();
564
+ return out;
565
+ }
566
+ function token() {
567
+ skipWs();
568
+ if (i >= src.length) return "";
569
+ var ch = src.charAt(i);
570
+ if (ch === "\\") return command();
571
+ if (ch === "{") { i++; return until("}"); }
572
+ if (ch === "^" || ch === "_") {
573
+ i++;
574
+ var arg = group();
575
+ return ch === "^"
576
+ ? "<m:sSup><m:sSupPr/><m:e>" + mrun("") + "</m:e><m:sup>" + arg + "</m:sup></m:sSup>"
577
+ : "<m:sSub><m:sSubPr/><m:e>" + mrun("") + "</m:e><m:sub>" + arg + "</m:sub></m:sSub>";
578
+ }
579
+ // Plain text, up to the next construct. Superscripts and subscripts
580
+ // bind to the character before them, so the last character is peeled
581
+ // off and handled as a base when one follows.
582
+ var run = "";
583
+ while (i < src.length && !/[\\{}^_]/.test(src.charAt(i))) run += src.charAt(i++);
584
+ if ((src.charAt(i) === "^" || src.charAt(i) === "_") && run) {
585
+ var base = run.slice(-1);
586
+ run = run.slice(0, -1);
587
+ var op = src.charAt(i); i++;
588
+ var sup = group();
589
+ // A base can carry both scripts.
590
+ skipWs();
591
+ var other = "";
592
+ if ((src.charAt(i) === "^" || src.charAt(i) === "_") && src.charAt(i) !== op) { var op2 = src.charAt(i); i++; other = group(); }
593
+ var head = mrun(run);
594
+ if (other) {
595
+ return head + "<m:sSubSup><m:sSubSupPr/><m:e>" + mrun(base) + "</m:e><m:sub>" +
596
+ (op === "_" ? sup : other) + "</m:sub><m:sup>" + (op === "^" ? sup : other) + "</m:sup></m:sSubSup>";
597
+ }
598
+ return head + (op === "^"
599
+ ? "<m:sSup><m:sSupPr/><m:e>" + mrun(base) + "</m:e><m:sup>" + sup + "</m:sup></m:sSup>"
600
+ : "<m:sSub><m:sSubPr/><m:e>" + mrun(base) + "</m:e><m:sub>" + sup + "</m:sub></m:sSub>");
601
+ }
602
+ return mrun(run);
603
+ }
604
+ var body = "";
605
+ while (i < src.length) body += token();
606
+ return "<m:oMath>" + body + "</m:oMath>";
607
+ }
608
+
609
+ // ----------------------------------------------------------- block walker
610
+ function blocksXml(root, b) {
611
+ var out = "";
612
+ var revSeq = { n: 1 };
613
+ var base = { bold: false, italic: false, underline: false, strike: false, sizeHalfPoints: 0, color: null, highlight: null, vertAlign: null, mono: false };
614
+
615
+ function para(node, style, opts) {
616
+ opts = opts || {};
617
+ var pr = "";
618
+ if (opts.heading) pr += '<w:pStyle w:val="Heading' + opts.heading + '"/>';
619
+ if (opts.quote) pr += '<w:pStyle w:val="Quote"/><w:ind w:left="720"/>';
620
+ if (opts.pre) pr += '<w:pStyle w:val="Code"/>';
621
+ if (opts.numId) pr += "<w:numPr><w:ilvl w:val=\"" + (opts.level || 0) + "\"/><w:numId w:val=\"" + opts.numId + "\"/></w:numPr>";
622
+ if (opts.pageBreakBefore) pr += "<w:pageBreakBefore/>";
623
+ var align = node && node.style ? node.style.textAlign : "";
624
+ if (align === "center" || align === "right") pr += '<w:jc w:val="' + align + '"/>';
625
+ else if (align === "justify") pr += '<w:jc w:val="both"/>';
626
+ var inner = node ? inlineXml(node, style, b, revSeq) : "";
627
+ return "<w:p>" + (pr ? "<w:pPr>" + pr + "</w:pPr>" : "") + inner + "</w:p>";
628
+ }
629
+
630
+ function list(node, style, level, pendingBreak) {
631
+ var ordered = node.tagName.toLowerCase() === "ol";
632
+ // One numId per list, so two adjacent lists restart rather than
633
+ // continuing each other's numbering.
634
+ var numId = ++b.numSeq;
635
+ b.numbering[numId] = ordered ? "decimal" : "bullet";
636
+ var xml = "";
637
+ for (var i = 0; i < node.childNodes.length; i++) {
638
+ var li = node.childNodes[i];
639
+ if (li.nodeType !== 1 || li.tagName.toLowerCase() !== "li") continue;
640
+ xml += para(li, styleFrom(li, style), { numId: numId, level: level, pageBreakBefore: pendingBreak });
641
+ pendingBreak = false;
642
+ for (var j = 0; j < li.childNodes.length; j++) {
643
+ var sub = li.childNodes[j];
644
+ if (sub.nodeType === 1 && /^(ul|ol)$/.test(sub.tagName.toLowerCase())) {
645
+ xml += list(sub, style, Math.min(8, level + 1), false);
646
+ }
647
+ }
648
+ }
649
+ return xml;
650
+ }
651
+
652
+ function table(node, style) {
653
+ var xml = '<w:tbl><w:tblPr><w:tblW w:w="0" w:type="auto"/>' +
654
+ '<w:tblBorders>' +
655
+ ["top", "left", "bottom", "right", "insideH", "insideV"].map(function (s) {
656
+ return '<w:' + s + ' w:val="single" w:sz="4" w:space="0" w:color="BFBFBF"/>';
657
+ }).join("") +
658
+ "</w:tblBorders></w:tblPr>";
659
+ var trs = node.getElementsByTagName("tr");
660
+ for (var r = 0; r < trs.length; r++) {
661
+ var cells = [];
662
+ for (var c = 0; c < trs[r].childNodes.length; c++) {
663
+ var cell = trs[r].childNodes[c];
664
+ if (cell.nodeType !== 1) continue;
665
+ var t = cell.tagName.toLowerCase();
666
+ if (t === "td" || t === "th") cells.push({ node: cell, header: t === "th" });
667
+ }
668
+ if (!cells.length) continue;
669
+ // A header row must repeat when the table splits across pages,
670
+ // or the continuation arrives as unlabelled numbers.
671
+ var isHeader = cells.length && cells[0].header;
672
+ xml += "<w:tr>" + (isHeader ? "<w:trPr><w:tblHeader/></w:trPr>" : "");
673
+ for (var k = 0; k < cells.length; k++) {
674
+ var span = parseInt(cells[k].node.getAttribute("colspan") || "1", 10) || 1;
675
+ var cs = styleFrom(cells[k].node, style);
676
+ if (cells[k].header) cs.bold = true;
677
+ var body = "";
678
+ var kids = cells[k].node.childNodes, hasBlock = false;
679
+ for (var q = 0; q < kids.length; q++) {
680
+ if (kids[q].nodeType === 1 && /^(p|div|ul|ol|table|h[1-6]|blockquote)$/.test(kids[q].tagName.toLowerCase())) { hasBlock = true; break; }
681
+ }
682
+ body = hasBlock ? walk(cells[k].node, cs) : para(cells[k].node, cs, {});
683
+ // A table cell may never be empty in OOXML.
684
+ if (!body) body = "<w:p/>";
685
+ xml += "<w:tc><w:tcPr>" +
686
+ (span > 1 ? '<w:gridSpan w:val="' + span + '"/>' : "") +
687
+ (cells[k].header ? '<w:shd w:val="clear" w:color="auto" w:fill="F1F3F7"/>' : "") +
688
+ "</w:tcPr>" + body + "</w:tc>";
689
+ }
690
+ xml += "</w:tr>";
691
+ }
692
+ return xml + "</w:tbl><w:p/>";
693
+ }
694
+
695
+ function walk(node, style) {
696
+ var xml = "", pendingBreak = false;
697
+ for (var i = 0; i < node.childNodes.length; i++) {
698
+ var c = node.childNodes[i];
699
+ if (c.nodeType === 3) {
700
+ if ((c.nodeValue || "").trim()) xml += "<w:p>" + textRun(c.nodeValue.replace(/\s+/g, " "), style, false) + "</w:p>";
701
+ continue;
702
+ }
703
+ if (c.nodeType !== 1) continue;
704
+ var tag = c.tagName.toLowerCase();
705
+ if (tag === "script" || tag === "style" || tag === "noscript") continue;
706
+ if (c.classList && (c.classList.contains("rte-footnotes") || c.classList.contains("rte-fn-note"))) continue;
707
+
708
+ if (c.getAttribute("data-rte-page-break") !== null || /always|page/.test((c.style && (c.style.pageBreakBefore || c.style.breakBefore)) || "")) {
709
+ pendingBreak = true;
710
+ }
711
+
712
+ if (tag === "hr") { xml += '<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:space="1" w:color="C8CDD4"/></w:pBdr></w:pPr></w:p>'; continue; }
713
+ if (tag === "ul" || tag === "ol") { xml += list(c, style, 0, pendingBreak); pendingBreak = false; continue; }
714
+ if (tag === "table") { xml += table(c, style); continue; }
715
+ if (HEADING[tag]) { xml += para(c, styleFrom(c, style), { heading: HEADING[tag], pageBreakBefore: pendingBreak }); pendingBreak = false; continue; }
716
+ if (tag === "blockquote") { xml += para(c, styleFrom(c, style), { quote: true, pageBreakBefore: pendingBreak }); pendingBreak = false; continue; }
717
+ if (tag === "pre") { xml += para(c, styleFrom(c, style), { pre: true, pageBreakBefore: pendingBreak }); pendingBreak = false; continue; }
718
+ if (tag === "p" || tag === "div" || tag === "section" || tag === "article" || tag === "figure" || tag === "address") {
719
+ if (hasBlockChild(c)) { xml += walk(c, styleFrom(c, style)); continue; }
720
+ xml += para(c, styleFrom(c, style), { pageBreakBefore: pendingBreak });
721
+ pendingBreak = false;
722
+ continue;
723
+ }
724
+ xml += para(c, styleFrom(c, style), { pageBreakBefore: pendingBreak });
725
+ pendingBreak = false;
726
+ }
727
+ return xml;
728
+ }
729
+
730
+ function hasBlockChild(node) {
731
+ for (var i = 0; i < node.childNodes.length; i++) {
732
+ var c = node.childNodes[i];
733
+ if (c.nodeType === 1 && /^(p|div|ul|ol|table|h[1-6]|blockquote|pre|hr|section|article)$/.test(c.tagName.toLowerCase())) return true;
734
+ }
735
+ return false;
736
+ }
737
+
738
+ out = walk(root, base);
739
+ return out || "<w:p/>";
740
+ }
741
+
742
+ // ------------------------------------------------------------- assembling
743
+ function sectionXml() {
744
+ var setup = null;
745
+ try { if (typeof editor.getDocumentPageSetup === "function") setup = editor.getDocumentPageSetup(); } catch (e) {}
746
+ setup = setup || {};
747
+ var SIZES = { letter: [12240, 15840], legal: [12240, 20160], tabloid: [15840, 24480], a3: [16838, 23811], a4: [11906, 16838], a5: [8391, 11906] };
748
+ var size = SIZES[String(setup.format || "letter").toLowerCase()] || SIZES.letter;
749
+ var landscape = String(setup.orientation || "").toLowerCase() === "landscape";
750
+ var w = landscape ? size[1] : size[0], h = landscape ? size[0] : size[1];
751
+ function twips(v, fallback) {
752
+ if (typeof v === "number" && isFinite(v)) return Math.round(v * 1440);
753
+ var m = /^\s*(-?[\d.]+)\s*(mm|cm|in|px|pt)?\s*$/.exec(String(v || ""));
754
+ if (!m) return fallback;
755
+ var n = parseFloat(m[1]);
756
+ switch (m[2]) {
757
+ case "mm": return Math.round(n * 1440 / 25.4);
758
+ case "cm": return Math.round(n * 1440 / 2.54);
759
+ case "pt": return Math.round(n * 20);
760
+ case "px": return Math.round(n * 15);
761
+ default: return Math.round(n * 1440);
762
+ }
763
+ }
764
+ var mg = setup.margins || {};
765
+ return '<w:sectPr><w:pgSz w:w="' + w + '" w:h="' + h + '"' + (landscape ? ' w:orient="landscape"' : "") + "/>" +
766
+ '<w:pgMar w:top="' + twips(mg.top, 1440) + '" w:right="' + twips(mg.right, 1440) +
767
+ '" w:bottom="' + twips(mg.bottom, 1440) + '" w:left="' + twips(mg.left, 1440) +
768
+ '" w:header="720" w:footer="720" w:gutter="0"/></w:sectPr>';
769
+ }
770
+
771
+ function buildDocx(options) {
772
+ return new Promise(function (resolve, reject) {
773
+ try {
774
+ var host = document.createElement("div");
775
+ // getHTMLCode() is the serialized document: presentational
776
+ // plugins strip their own chrome from it, so page overlays,
777
+ // watermarks and formatting marks never reach the file.
778
+ host.innerHTML = editor.getHTMLCode() || "";
779
+ document.body.appendChild(host);
780
+ host.style.cssText = "position:absolute;left:-99999px;top:0;width:800px;";
781
+
782
+ var b = new Builder();
783
+ var body = blocksXml(host, b);
784
+ host.parentNode.removeChild(host);
785
+
786
+ var files = [];
787
+ function put(name, xml) { files.push({ name: name, bytes: utf8Bytes(xml) }); }
788
+
789
+ var hasFootnotes = b.footnotes.length > 0;
790
+ var hasEndnotes = b.endnotes.length > 0;
791
+ var hasComments = b.comments.length > 0;
792
+
793
+ put("[Content_Types].xml",
794
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
795
+ '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
796
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
797
+ '<Default Extension="xml" ContentType="application/xml"/>' +
798
+ '<Default Extension="png" ContentType="image/png"/>' +
799
+ '<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>' +
800
+ '<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>' +
801
+ '<Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/>' +
802
+ (hasFootnotes ? '<Override PartName="/word/footnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"/>' : "") +
803
+ (hasEndnotes ? '<Override PartName="/word/endnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"/>' : "") +
804
+ (hasComments ? '<Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"/>' : "") +
805
+ '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>' +
806
+ '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>' +
807
+ "</Types>");
808
+
809
+ put("_rels/.rels",
810
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
811
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
812
+ '<Relationship Id="rIdDoc" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>' +
813
+ '<Relationship Id="rIdCore" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>' +
814
+ '<Relationship Id="rIdApp" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>' +
815
+ "</Relationships>");
816
+
817
+ var relBase = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/";
818
+ var relXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
819
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
820
+ '<Relationship Id="rIdStyles" Type="' + relBase + 'styles" Target="styles.xml"/>' +
821
+ '<Relationship Id="rIdNum" Type="' + relBase + 'numbering" Target="numbering.xml"/>' +
822
+ (hasFootnotes ? '<Relationship Id="rIdFn" Type="' + relBase + 'footnotes" Target="footnotes.xml"/>' : "") +
823
+ (hasEndnotes ? '<Relationship Id="rIdEn" Type="' + relBase + 'endnotes" Target="endnotes.xml"/>' : "") +
824
+ (hasComments ? '<Relationship Id="rIdCm" Type="' + relBase + 'comments" Target="comments.xml"/>' : "");
825
+ for (var i = 0; i < b.rels.length; i++) {
826
+ var rl = b.rels[i];
827
+ relXml += '<Relationship Id="' + rl.id + '" Type="' + relBase + rl.type + '" Target="' + esc(rl.target) + '"' +
828
+ (rl.mode ? ' TargetMode="' + rl.mode + '"' : "") + "/>";
829
+ }
830
+ put("word/_rels/document.xml.rels", relXml + "</Relationships>");
831
+
832
+ put("word/document.xml",
833
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
834
+ '<w:document xmlns:w="' + NS.w + '" xmlns:r="' + NS.r + '" xmlns:m="' + NS.m + '">' +
835
+ "<w:body>" + body + sectionXml() + "</w:body></w:document>");
836
+
837
+ put("word/styles.xml", stylesXml());
838
+ put("word/numbering.xml", numberingXml(b));
839
+
840
+ if (hasFootnotes) put("word/footnotes.xml", notesXml(b.footnotes, "footnote"));
841
+ if (hasEndnotes) put("word/endnotes.xml", notesXml(b.endnotes, "endnote"));
842
+ if (hasComments) put("word/comments.xml", commentsXml(b.comments));
843
+
844
+ var title = options.title || config.docxExportTitle || document.title || "Document";
845
+ var author = options.author || config.docxExportAuthor || "RichTextEditor";
846
+ put("docProps/core.xml",
847
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
848
+ '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"' +
849
+ ' xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/"' +
850
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
851
+ "<dc:title>" + esc(title) + "</dc:title><dc:creator>" + esc(author) + "</dc:creator>" +
852
+ '<cp:lastModifiedBy>' + esc(author) + "</cp:lastModifiedBy>" +
853
+ '<dcterms:created xsi:type="dcterms:W3CDTF">' + new Date().toISOString().replace(/\.\d+Z$/, "Z") + "</dcterms:created>" +
854
+ "</cp:coreProperties>");
855
+ put("docProps/app.xml",
856
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
857
+ '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">' +
858
+ "<Application>RichTextEditor</Application></Properties>");
859
+
860
+ for (var mi = 0; mi < b.media.length; mi++) {
861
+ files.push({ name: "word/media/" + b.media[mi].name, bytes: b.media[mi].bytes });
862
+ }
863
+
864
+ zip(files).then(resolve, reject);
865
+ } catch (e) { reject(e); }
866
+ });
867
+ }
868
+
869
+ function stylesXml() {
870
+ var heads = "";
871
+ for (var i = 1; i <= 6; i++) {
872
+ var sz = [36, 30, 26, 24, 22, 20][i - 1];
873
+ heads += '<w:style w:type="paragraph" w:styleId="Heading' + i + '">' +
874
+ '<w:name w:val="heading ' + i + '"/><w:basedOn w:val="Normal"/>' +
875
+ '<w:pPr><w:outlineLvl w:val="' + (i - 1) + '"/><w:spacing w:before="240" w:after="120"/></w:pPr>' +
876
+ '<w:rPr><w:b/><w:sz w:val="' + sz + '"/><w:szCs w:val="' + sz + '"/></w:rPr></w:style>';
877
+ }
878
+ return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
879
+ '<w:styles xmlns:w="' + NS.w + '">' +
880
+ '<w:docDefaults><w:rPrDefault><w:rPr><w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/><w:sz w:val="22"/><w:szCs w:val="22"/></w:rPr></w:rPrDefault></w:docDefaults>' +
881
+ '<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/></w:style>' +
882
+ heads +
883
+ '<w:style w:type="paragraph" w:styleId="Quote"><w:name w:val="Quote"/><w:basedOn w:val="Normal"/><w:rPr><w:i/><w:color w:val="4A5568"/></w:rPr></w:style>' +
884
+ '<w:style w:type="paragraph" w:styleId="Code"><w:name w:val="Code"/><w:basedOn w:val="Normal"/><w:rPr><w:rFonts w:ascii="Consolas" w:hAnsi="Consolas"/></w:rPr></w:style>' +
885
+ "</w:styles>";
886
+ }
887
+
888
+ function numberingXml(b) {
889
+ var abstracts = "", nums = "";
890
+ var ids = Object.keys(b.numbering);
891
+ for (var i = 0; i < ids.length; i++) {
892
+ var numId = ids[i], fmt = b.numbering[numId];
893
+ var levels = "";
894
+ for (var lvl = 0; lvl < 9; lvl++) {
895
+ // Word distinguishes bullets from numbers ONLY here; the
896
+ // document body stores every list identically.
897
+ levels += '<w:lvl w:ilvl="' + lvl + '">' +
898
+ '<w:start w:val="1"/>' +
899
+ '<w:numFmt w:val="' + (fmt === "bullet" ? "bullet" : "decimal") + '"/>' +
900
+ '<w:lvlText w:val="' + (fmt === "bullet" ? "\uF0B7" : "%" + (lvl + 1) + ".") + '"/>' +
901
+ '<w:lvlJc w:val="left"/>' +
902
+ '<w:pPr><w:ind w:left="' + (720 * (lvl + 1)) + '" w:hanging="360"/></w:pPr>' +
903
+ (fmt === "bullet" ? '<w:rPr><w:rFonts w:ascii="Symbol" w:hAnsi="Symbol" w:hint="default"/></w:rPr>' : "") +
904
+ "</w:lvl>";
905
+ }
906
+ abstracts += '<w:abstractNum w:abstractNumId="' + numId + '">' + levels + "</w:abstractNum>";
907
+ nums += '<w:num w:numId="' + numId + '"><w:abstractNumId w:val="' + numId + '"/></w:num>';
908
+ }
909
+ return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
910
+ '<w:numbering xmlns:w="' + NS.w + '">' + abstracts + nums + "</w:numbering>";
911
+ }
912
+
913
+ function notesXml(notes, kind) {
914
+ // Ids 0 and -1 are the separator rules Word draws above notes. They are
915
+ // not content, but the part is malformed without them.
916
+ var xml = '<w:' + kind + ' w:type="separator" w:id="-1"><w:p><w:r><w:separator/></w:r></w:p></w:' + kind + ">" +
917
+ '<w:' + kind + ' w:type="continuationSeparator" w:id="0"><w:p><w:r><w:continuationSeparator/></w:r></w:p></w:' + kind + ">";
918
+ for (var i = 0; i < notes.length; i++) {
919
+ xml += '<w:' + kind + ' w:id="' + notes[i].id + '"><w:p><w:r>' +
920
+ '<w:rPr><w:vertAlign w:val="superscript"/></w:rPr><w:' + kind + 'Ref/></w:r>' +
921
+ '<w:r><w:t xml:space="preserve"> ' + esc(notes[i].text) + "</w:t></w:r></w:p></w:" + kind + ">";
922
+ }
923
+ return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
924
+ "<w:" + kind + "s xmlns:w=\"" + NS.w + "\">" + xml + "</w:" + kind + "s>";
925
+ }
926
+
927
+ function commentsXml(comments) {
928
+ var xml = "";
929
+ for (var i = 0; i < comments.length; i++) {
930
+ var c = comments[i];
931
+ xml += '<w:comment w:id="' + c.id + '" w:author="' + esc(c.author) + '" w:initials="' + esc(c.initials) +
932
+ '" w:date="' + new Date().toISOString().replace(/\.\d+Z$/, "Z") + '">' +
933
+ "<w:p><w:r><w:t xml:space=\"preserve\">" + esc(c.text) + "</w:t></w:r></w:p></w:comment>";
934
+ }
935
+ return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
936
+ '<w:comments xmlns:w="' + NS.w + '">' + xml + "</w:comments>";
937
+ }
938
+
939
+ // ------------------------------------------------------------- downloads
940
+ function sanitizeName(name) {
941
+ if (!name || typeof name !== "string") return "";
942
+ return name.replace(/\.docx?$/i, "").replace(/[\\/:*?"<>|]+/g, "").trim().slice(0, 120);
943
+ }
944
+ function defaultBase() {
945
+ var d = new Date();
946
+ function two(n) { return (n < 10 ? "0" : "") + n; }
947
+ return "Export-" + String(d.getFullYear()).slice(2) + two(d.getMonth() + 1) + two(d.getDate()) +
948
+ "-" + two(d.getHours()) + two(d.getMinutes()) + two(d.getSeconds());
949
+ }
950
+ function download(blob, filename) {
951
+ var url = URL.createObjectURL(blob);
952
+ var a = document.createElement("a");
953
+ a.href = url; a.download = filename;
954
+ document.body.appendChild(a);
955
+ a.click();
956
+ document.body.removeChild(a);
957
+ setTimeout(function () { URL.revokeObjectURL(url); }, 4000);
958
+ return filename;
959
+ }
960
+ }