@richscripts/richtexteditor 2.4.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/migration/README.md +100 -0
- package/migration/cli.js +294 -0
- package/migration/index.d.ts +18 -0
- package/migration/index.js +242 -0
- package/migration/report-schema-v2.json +127 -0
- package/package.json +11 -1
- package/richtexteditor/CHANGELOG.md +326 -1
- package/richtexteditor/all_plugins.js +14959 -4832
- package/richtexteditor/all_plugins.min.js +62 -42
- package/richtexteditor/plugins/accessibilitychecker.js +8 -7
- package/richtexteditor/plugins/aitoolkit.css +17 -8
- package/richtexteditor/plugins/aitoolkit.js +8 -8
- package/richtexteditor/plugins/all_plugins.js +14959 -4832
- package/richtexteditor/plugins/all_plugins.min.js +62 -42
- package/richtexteditor/plugins/autocorrect.js +353 -0
- package/richtexteditor/plugins/bookmarkcard.js +12 -1
- package/richtexteditor/plugins/changecase.js +238 -0
- package/richtexteditor/plugins/comments.js +15 -28
- package/richtexteditor/plugins/contentminimap.js +6 -5
- package/richtexteditor/plugins/crossreference.js +408 -0
- package/richtexteditor/plugins/documentimport.js +915 -21
- package/richtexteditor/plugins/documentoutline.js +5 -4
- package/richtexteditor/plugins/docxexport.js +960 -0
- package/richtexteditor/plugins/draghandle.js +309 -0
- package/richtexteditor/plugins/footnotes.js +398 -0
- package/richtexteditor/plugins/formatpainter.js +298 -0
- package/richtexteditor/plugins/formattingmarks.js +246 -0
- package/richtexteditor/plugins/html2pdf.js +108 -4
- package/richtexteditor/plugins/imageeditor.js +37 -7
- package/richtexteditor/plugins/insertgallery.js +553 -170
- package/richtexteditor/plugins/keyboarda11y.js +316 -0
- package/richtexteditor/plugins/linenumbers.js +352 -0
- package/richtexteditor/plugins/linkchecker.js +396 -0
- package/richtexteditor/plugins/mentionplugin.js +4 -3
- package/richtexteditor/plugins/mergefields.js +328 -0
- package/richtexteditor/plugins/multilevellist.js +137 -0
- package/richtexteditor/plugins/pagination.js +505 -0
- package/richtexteditor/plugins/pdfexport.js +1137 -0
- package/richtexteditor/plugins/permanentpen.js +211 -0
- package/richtexteditor/plugins/revisionhistory.js +47 -34
- package/richtexteditor/plugins/rtlui.js +150 -0
- package/richtexteditor/plugins/sanitizer.js +486 -0
- package/richtexteditor/plugins/slashcommand.js +142 -17
- package/richtexteditor/plugins/spellcheck.js +6 -3
- package/richtexteditor/plugins/tableofcontents.js +353 -0
- package/richtexteditor/plugins/tabletools.js +342 -0
- package/richtexteditor/plugins/textdirection.js +313 -0
- package/richtexteditor/plugins/trackedchanges.js +37 -6
- package/richtexteditor/plugins/tui.image-editor/tui-code-snippet.min.js +1 -1
- package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.js +1 -1
- package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.js +1 -1
- package/richtexteditor/plugins/vendor/html2pdf.bundle.min.js +3 -0
- package/richtexteditor/plugins/watermark.js +214 -0
- package/richtexteditor/plugins/wordexport.js +0 -0
- package/richtexteditor/plugins/yjscollab.js +49 -11
- package/richtexteditor/rte-config.js +44 -44
- package/richtexteditor/rte.js +91 -63
- package/richtexteditor/rte_theme_default.css +462 -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, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
73
|
+
.replace(/"/g, """)
|
|
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
|
+
}
|