@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,505 @@
1
+ if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
2
+
3
+ // 2026-07-17 Pagination / page view. Renders the editable as a stack of paper
4
+ // pages — the "print layout" view Word and Google Docs have, and the feature
5
+ // CKEditor and Tiptap both sell as a paid add-on.
6
+ //
7
+ // Design constraint (same as the typewriter/focus plugin): this is PURELY
8
+ // PRESENTATIONAL. It never mutates content and nothing it draws is serialized:
9
+ // - page geometry is applied as styles on the editable (width/padding/background)
10
+ // - page boundaries, page numbers, and header/footer text are drawn in an
11
+ // overlay layer that is contenteditable="false", aria-hidden, pointer-events
12
+ // none, and stripped around every serialize call as a belt-and-braces guard.
13
+ // Turning page view off restores the editable exactly as it was.
14
+ //
15
+ // Geometry comes from the document's own page setup when the host app supplies
16
+ // one (editor.getDocumentPageSetup() / config), so the on-screen page matches
17
+ // what html2pdf and the Word export produce: format (A4/Letter/Legal),
18
+ // orientation, margins, and optional header/footer HTML.
19
+ //
20
+ // Forced breaks: any element carrying data-rte-page-break (what the
21
+ // insertpagebreak toolbar command inserts) starts a new page, exactly as it
22
+ // does on export.
23
+ RTE_DefaultConfig.plugin_pagination = RTE_Plugin_Pagination;
24
+
25
+ // Off by default — page view is a deliberate mode, not a surprise.
26
+ if (typeof RTE_DefaultConfig.paginationEnabled === "undefined") RTE_DefaultConfig.paginationEnabled = false;
27
+ // Paper format when the document carries no page setup: A4 | Letter | Legal.
28
+ if (typeof RTE_DefaultConfig.pageFormat === "undefined") RTE_DefaultConfig.pageFormat = "A4";
29
+ if (typeof RTE_DefaultConfig.pageOrientation === "undefined") RTE_DefaultConfig.pageOrientation = "portrait";
30
+ // Margins in inches (Word's default is 1in all round).
31
+ if (typeof RTE_DefaultConfig.pageMargins === "undefined") RTE_DefaultConfig.pageMargins = { top: 1, right: 1, bottom: 1, left: 1 };
32
+ if (typeof RTE_DefaultConfig.paginationShowPageNumbers === "undefined") RTE_DefaultConfig.paginationShowPageNumbers = true;
33
+ // "Page 3" vs "Page 3 of 12".
34
+ if (typeof RTE_DefaultConfig.paginationShowPageCount === "undefined") RTE_DefaultConfig.paginationShowPageCount = true;
35
+
36
+ function RTE_Plugin_Pagination() {
37
+ var obj = this;
38
+ var config, editor;
39
+ var active = false;
40
+ var overlay = null;
41
+ var boundDoc = null;
42
+ var wrapped = false;
43
+ var raf = 0;
44
+ var pageCount = 1;
45
+ var lastSignature = "";
46
+
47
+ obj.PluginName = "Pagination";
48
+
49
+ // 96 CSS px per inch is the CSS reference pixel — the same basis html2pdf
50
+ // and the browser's own print pipeline use.
51
+ var DPI = 96;
52
+ var MM_PER_IN = 25.4;
53
+
54
+ var FORMATS = {
55
+ a4: { w: 210 / MM_PER_IN, h: 297 / MM_PER_IN },
56
+ letter: { w: 8.5, h: 11 },
57
+ legal: { w: 8.5, h: 14 },
58
+ a3: { w: 297 / MM_PER_IN, h: 420 / MM_PER_IN },
59
+ a5: { w: 148 / MM_PER_IN, h: 210 / MM_PER_IN },
60
+ tabloid: { w: 11, h: 17 }
61
+ };
62
+
63
+ obj.InitConfig = function (argconfig) {
64
+ config = argconfig;
65
+ active = !!config.paginationEnabled;
66
+ };
67
+
68
+ obj.InitEditor = function (argeditor) {
69
+ editor = argeditor;
70
+ setup();
71
+ try { editor.attachEvent("ready", setup); } catch (e) {}
72
+ try { editor.attachEvent("aftersethtml", schedule); } catch (e) {}
73
+
74
+ // Public API.
75
+ editor.setPageView = function (on) { active = !!on; apply(); return active; };
76
+ editor.togglePageView = function () { active = !active; apply(); return active; };
77
+ editor.isPageView = function () { return active; };
78
+ editor.getPageCount = function () { if (active) paginate(); return pageCount; };
79
+ // Let the host change paper without rebuilding the editor.
80
+ editor.setPageSetup = function (setup) {
81
+ if (setup && typeof setup === "object") {
82
+ if (setup.format) config.pageFormat = setup.format;
83
+ if (setup.orientation) config.pageOrientation = setup.orientation;
84
+ if (setup.margins) config.pageMargins = setup.margins;
85
+ if (typeof setup.headerHtml === "string") config.pageHeaderHtml = setup.headerHtml;
86
+ if (typeof setup.footerHtml === "string") config.pageFooterHtml = setup.footerHtml;
87
+ }
88
+ lastSignature = "";
89
+ apply();
90
+ return geometry();
91
+ };
92
+ editor.getPageGeometry = function () { return geometry(); };
93
+ // 2026-07-27 Which page is this element on? Added for the table-of-contents
94
+ // plugin, which pairs page numbers with its entries the way a word
95
+ // processor does. Returns null when page view is off, because outside
96
+ // page view a "page number" would be a fiction.
97
+ editor.getPageOfElement = function (el) { return pageOfElement(el); };
98
+
99
+ setTimeout(setup, 0);
100
+ setTimeout(schedule, 250);
101
+ };
102
+
103
+ // Re-runnable: the editor can recreate its document/body (setHTML, mode
104
+ // switches), so every hook re-attaches defensively.
105
+ function setup() {
106
+ var doc = getDoc();
107
+ if (doc) {
108
+ injectStyles(doc);
109
+ if (doc !== boundDoc) {
110
+ doc.addEventListener("input", schedule, true);
111
+ doc.addEventListener("keyup", schedule, true);
112
+ boundDoc = doc;
113
+ var win = doc.defaultView || doc.parentWindow;
114
+ if (win) { try { win.addEventListener("resize", schedule, true); } catch (e) {} }
115
+ }
116
+ }
117
+ wrapSerializers();
118
+ apply();
119
+ }
120
+
121
+ function getDoc() { try { return editor.getDocument(); } catch (e) { return null; } }
122
+ function getEditable() { try { return editor.getEditable(); } catch (e) { return null; } }
123
+
124
+ // ---- geometry -------------------------------------------------------
125
+
126
+ // Prefer the document's own page setup (so screen matches export), then
127
+ // config, then the A4 default.
128
+ function pageSetup() {
129
+ var s = null;
130
+ try { if (typeof editor.getDocumentPageSetup === "function") s = editor.getDocumentPageSetup(); } catch (e) { s = null; }
131
+ if (!s || typeof s !== "object") s = {};
132
+ return {
133
+ format: s.format || config.pageFormat || "A4",
134
+ orientation: s.orientation || config.pageOrientation || "portrait",
135
+ margins: s.margins || config.pageMargins || { top: 1, right: 1, bottom: 1, left: 1 },
136
+ headerHtml: typeof s.headerHtml === "string" ? s.headerHtml : (config.pageHeaderHtml || ""),
137
+ footerHtml: typeof s.footerHtml === "string" ? s.footerHtml : (config.pageFooterHtml || "")
138
+ };
139
+ }
140
+
141
+ // Margins may arrive as inches (number) or a CSS length string.
142
+ function toPx(v, fallbackIn) {
143
+ if (typeof v === "number" && isFinite(v)) return v * DPI;
144
+ if (typeof v === "string") {
145
+ var m = /^\s*(-?[\d.]+)\s*(mm|cm|in|px|pt)?\s*$/.exec(v);
146
+ if (m) {
147
+ var n = parseFloat(m[1]);
148
+ switch (m[2]) {
149
+ case "mm": return (n / MM_PER_IN) * DPI;
150
+ case "cm": return (n * 10 / MM_PER_IN) * DPI;
151
+ case "pt": return (n / 72) * DPI;
152
+ case "px": return n;
153
+ default: return n * DPI; // bare number or "in"
154
+ }
155
+ }
156
+ }
157
+ return (fallbackIn || 0) * DPI;
158
+ }
159
+
160
+ function geometry() {
161
+ var s = pageSetup();
162
+ var f = FORMATS[String(s.format).toLowerCase()] || FORMATS.a4;
163
+ var wIn = f.w, hIn = f.h;
164
+ if (String(s.orientation).toLowerCase() === "landscape") { var t = wIn; wIn = hIn; hIn = t; }
165
+ var m = s.margins || {};
166
+ var g = {
167
+ pageWidth: Math.round(wIn * DPI),
168
+ pageHeight: Math.round(hIn * DPI),
169
+ marginTop: Math.round(toPx(m.top, 1)),
170
+ marginRight: Math.round(toPx(m.right, 1)),
171
+ marginBottom: Math.round(toPx(m.bottom, 1)),
172
+ marginLeft: Math.round(toPx(m.left, 1)),
173
+ format: s.format,
174
+ orientation: s.orientation,
175
+ headerHtml: s.headerHtml,
176
+ footerHtml: s.footerHtml
177
+ };
178
+ // The usable text column on one page.
179
+ g.contentHeight = Math.max(120, g.pageHeight - g.marginTop - g.marginBottom);
180
+ g.contentWidth = Math.max(120, g.pageWidth - g.marginLeft - g.marginRight);
181
+ return g;
182
+ }
183
+
184
+ // ---- apply / teardown ----------------------------------------------
185
+
186
+ function apply() {
187
+ var ed = getEditable();
188
+ if (!ed) return;
189
+ if (!active) { teardown(ed); return; }
190
+ var g = geometry();
191
+ // The editable becomes the paper column: fixed content width, page
192
+ // margins as padding, and a top offset so the first page has a margin.
193
+ ed.classList.add("rte-page-view");
194
+ ed.style.width = g.contentWidth + "px";
195
+ ed.style.paddingLeft = g.marginLeft + "px";
196
+ ed.style.paddingRight = g.marginRight + "px";
197
+ ed.style.paddingTop = g.marginTop + "px";
198
+ ed.style.paddingBottom = g.marginBottom + "px";
199
+ ed.style.margin = "24px auto";
200
+ ed.style.boxSizing = "content-box";
201
+ ed.style.background = "#fff";
202
+ ed.style.minHeight = g.contentHeight + "px";
203
+ schedule();
204
+ }
205
+
206
+ function teardown(ed) {
207
+ ed = ed || getEditable();
208
+ if (ed) {
209
+ ed.classList.remove("rte-page-view");
210
+ var props = ["width", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "margin", "boxSizing", "background", "minHeight"];
211
+ for (var i = 0; i < props.length; i++) ed.style[props[i]] = "";
212
+ }
213
+ removeOverlay();
214
+ pageCount = 1;
215
+ lastSignature = "";
216
+ }
217
+
218
+ function schedule() {
219
+ if (!active) return;
220
+ if (raf) return;
221
+ var doc = getDoc();
222
+ var win = (doc && (doc.defaultView || doc.parentWindow)) || window;
223
+ var rq = (win && typeof win.requestAnimationFrame === "function")
224
+ ? function (f) { return win.requestAnimationFrame(f); }
225
+ : function (f) { return setTimeout(f, 16); };
226
+ raf = rq(function () { raf = 0; paginate(); });
227
+ }
228
+
229
+ // ---- pagination -----------------------------------------------------
230
+
231
+ // Block-level pagination: walk the top-level blocks and cut a page whenever
232
+ // the running height would overflow the usable page height, or when a
233
+ // forced break is hit. A block taller than a page simply owns its page(s) —
234
+ // we never split a block, matching how the print pipeline behaves for
235
+ // unbreakable content.
236
+ // All offsets here are raw offsetTop values (the editable's children share an
237
+ // offsetParent, so they're directly comparable). "origin" is where the first
238
+ // block actually sits, which is NOT zero — it's the editable's content box
239
+ // start. Everything is measured from there so page 1 gets a full page of
240
+ // content rather than a page minus the top margin.
241
+ function contentOrigin(ed) {
242
+ var kids = ed.children;
243
+ for (var i = 0; i < kids.length; i++) {
244
+ var el = kids[i];
245
+ if (el && el.nodeType === 1 && !(el.getAttribute && el.getAttribute("data-rte-page-overlay") === "true")) {
246
+ return el.offsetTop;
247
+ }
248
+ }
249
+ return ed.offsetTop;
250
+ }
251
+
252
+ // Map an element to its 1-based page number, using the same break offsets the
253
+ // overlay draws so the number always agrees with the boundary the user sees.
254
+ // Walks up to the element's top-level block, because computeBreaks measures
255
+ // offsetTop of the editable's own children.
256
+ function pageOfElement(el) {
257
+ if (!active || !el) return null;
258
+ var ed = getEditable();
259
+ if (!ed) return null;
260
+ var block = el;
261
+ while (block && block.parentNode && block.parentNode !== ed) block = block.parentNode;
262
+ if (!block || block.parentNode !== ed) return null;
263
+ var breaks = computeBreaks(ed, geometry());
264
+ var top = block.offsetTop;
265
+ var page = 1;
266
+ for (var i = 0; i < breaks.length; i++) if (breaks[i] <= top) page++;
267
+ return page;
268
+ }
269
+
270
+ function computeBreaks(ed, g) {
271
+ var breaks = [];
272
+ var kids = ed.children;
273
+ if (!kids || !kids.length) return breaks;
274
+
275
+ // Build the list of real content blocks once, so we can use the NEXT
276
+ // block's top as this block's effective bottom. offsetHeight is a
277
+ // border-box measure and excludes margins, so summing it undercounts the
278
+ // flow by every collapsed margin — which would let a page overflow.
279
+ var blocks = [];
280
+ for (var k = 0; k < kids.length; k++) {
281
+ var kid = kids[k];
282
+ if (!kid || kid.nodeType !== 1) continue;
283
+ if (kid.getAttribute && kid.getAttribute("data-rte-page-overlay") === "true") continue;
284
+ blocks.push(kid);
285
+ }
286
+ if (!blocks.length) return breaks;
287
+
288
+ var pageTop = blocks[0].offsetTop; // offsetTop where the current page's content starts
289
+ var i, el, top, bottom;
290
+
291
+ for (i = 0; i < blocks.length; i++) {
292
+ el = blocks[i];
293
+ top = el.offsetTop;
294
+ // Effective bottom = where the following block begins (captures the
295
+ // margin gap); for the last block fall back to its own box bottom.
296
+ bottom = (i + 1 < blocks.length) ? blocks[i + 1].offsetTop : (top + el.offsetHeight);
297
+
298
+ // Forced break: this element starts a new page.
299
+ var forced = el.hasAttribute && (el.hasAttribute("data-rte-page-break") ||
300
+ (el.querySelector && !!el.querySelector("[data-rte-page-break]")));
301
+ if (forced && top > pageTop) {
302
+ breaks.push(top);
303
+ pageTop = top;
304
+ continue;
305
+ }
306
+
307
+ // Natural overflow: cut before this block.
308
+ if (bottom - pageTop > g.contentHeight) {
309
+ if (top > pageTop) {
310
+ breaks.push(top);
311
+ pageTop = top;
312
+ }
313
+ // A single over-tall block: advance whole pages past it so the
314
+ // following content lands on a fresh page rather than cascading.
315
+ while (bottom - pageTop > g.contentHeight) {
316
+ pageTop += g.contentHeight;
317
+ breaks.push(pageTop);
318
+ }
319
+ }
320
+ }
321
+ return breaks;
322
+ }
323
+
324
+ function paginate() {
325
+ if (!active) return;
326
+ var ed = getEditable(), doc = getDoc();
327
+ if (!ed || !doc) return;
328
+ var g = geometry();
329
+ var breaks = computeBreaks(ed, g);
330
+ pageCount = breaks.length + 1;
331
+
332
+ // Cheap change-detection so we don't rebuild the overlay on every keystroke.
333
+ var sig = g.pageWidth + "x" + g.pageHeight + "|" + g.contentHeight + "|" + breaks.join(",") + "|" + ed.offsetHeight;
334
+ if (sig === lastSignature) return;
335
+ lastSignature = sig;
336
+
337
+ render(doc, ed, g, breaks);
338
+ }
339
+
340
+ // ---- overlay rendering ----------------------------------------------
341
+
342
+ function ensureOverlay(doc, ed) {
343
+ if (overlay && overlay.parentNode) return overlay;
344
+ overlay = doc.createElement("div");
345
+ overlay.setAttribute("data-rte-page-overlay", "true");
346
+ overlay.setAttribute("contenteditable", "false");
347
+ overlay.setAttribute("aria-hidden", "true");
348
+ overlay.className = "rte-page-overlay";
349
+ // Sits in the same offset parent as the editable's children so the
350
+ // boundary offsets line up without extra math.
351
+ (ed.parentNode || doc.body || doc.documentElement).appendChild(overlay);
352
+ return overlay;
353
+ }
354
+
355
+ function removeOverlay() {
356
+ if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
357
+ overlay = null;
358
+ }
359
+
360
+ // Header/footer HTML may use {page} and {total} placeholders, the same
361
+ // convention Word and Tiptap's paged view use.
362
+ function expand(html, page, total) {
363
+ return String(html)
364
+ .replace(/\{\s*page\s*\}/gi, String(page))
365
+ .replace(/\{\s*total\s*\}/gi, String(total));
366
+ }
367
+
368
+ // Coordinate model: `breaks` and `origin` are raw offsetTop values in the
369
+ // editable's offsetParent space. The overlay is pinned at the editable's own
370
+ // offset, so overlay-local y = absoluteOffset - ed.offsetTop.
371
+ function render(doc, ed, g, breaks) {
372
+ var ov = ensureOverlay(doc, ed);
373
+ while (ov.firstChild) ov.removeChild(ov.firstChild);
374
+
375
+ var edTop = ed.offsetTop;
376
+ var origin = contentOrigin(ed);
377
+ var starts = [origin].concat(breaks);
378
+ var total = starts.length;
379
+
380
+ // Cover from the top of the editable to the bottom of the last page.
381
+ var lastBottom = starts[total - 1] + g.contentHeight + g.marginBottom;
382
+ ov.style.position = "absolute";
383
+ ov.style.left = ed.offsetLeft + "px";
384
+ ov.style.top = edTop + "px";
385
+ ov.style.width = ed.offsetWidth + "px";
386
+ ov.style.height = Math.max(ed.offsetHeight, lastBottom - edTop) + "px";
387
+ ov.style.pointerEvents = "none";
388
+ ov.style.zIndex = "1";
389
+
390
+ var i, y;
391
+
392
+ // A separator per break: the visual gap between two sheets of paper. The
393
+ // break offset is where the NEXT page's content starts, so the gap is
394
+ // drawn just above it (in the bottom-margin band of the previous page).
395
+ for (i = 0; i < breaks.length; i++) {
396
+ y = breaks[i] - edTop - Math.round(g.marginTop / 2);
397
+ var sep = doc.createElement("div");
398
+ sep.className = "rte-page-sep";
399
+ sep.style.top = y + "px";
400
+ ov.appendChild(sep);
401
+ }
402
+
403
+ for (i = 0; i < starts.length; i++) {
404
+ var pageNo = i + 1;
405
+ var contentTop = starts[i] - edTop; // page's first line
406
+ var contentBottom = contentTop + g.contentHeight;
407
+
408
+ if (config.paginationShowPageNumbers) {
409
+ var label = doc.createElement("div");
410
+ label.className = "rte-page-num";
411
+ label.style.top = (contentBottom + 6) + "px";
412
+ label.appendChild(doc.createTextNode(
413
+ config.paginationShowPageCount ? ("Page " + pageNo + " of " + total) : ("Page " + pageNo)
414
+ ));
415
+ ov.appendChild(label);
416
+ }
417
+
418
+ if (g.headerHtml) {
419
+ var h = doc.createElement("div");
420
+ h.className = "rte-page-hf rte-page-header";
421
+ h.style.top = (contentTop - Math.round(g.marginTop / 2)) + "px";
422
+ h.innerHTML = expand(g.headerHtml, pageNo, total);
423
+ ov.appendChild(h);
424
+ }
425
+ if (g.footerHtml) {
426
+ var f = doc.createElement("div");
427
+ f.className = "rte-page-hf rte-page-footer";
428
+ f.style.top = (contentBottom + Math.round(g.marginBottom / 3)) + "px";
429
+ f.innerHTML = expand(g.footerHtml, pageNo, total);
430
+ ov.appendChild(f);
431
+ }
432
+ }
433
+ }
434
+
435
+ // ---- styles ---------------------------------------------------------
436
+
437
+ function injectStyles(doc) {
438
+ if (!doc || doc.getElementById("rte-pagination-styles")) return;
439
+ var css =
440
+ // The paper itself: a white column with a soft shadow on a grey desk.
441
+ ".rte-page-view{box-shadow:0 1px 4px rgba(15,23,42,.16),0 8px 28px rgba(15,23,42,.10);}" +
442
+ // Grey "desk" behind the page so the paper edge reads clearly.
443
+ "body.rte-page-desk{background:#f1f5f9;}" +
444
+ ".rte-page-overlay{position:absolute;pointer-events:none;}" +
445
+ // Page boundary. Content flows continuously, so this is a crisp rule
446
+ // drawn across the column (the way Word's page-break line reads) —
447
+ // never a filled band, which would cover the text underneath.
448
+ ".rte-page-sep{position:absolute;left:-24px;right:-24px;height:0;" +
449
+ "border-top:1px dashed #94a3b8;}" +
450
+ ".rte-page-num{position:absolute;right:8px;font:600 10px/1.4 'Segoe UI',system-ui,sans-serif;" +
451
+ "letter-spacing:.04em;color:#94a3b8;text-transform:uppercase;}" +
452
+ ".rte-page-hf{position:absolute;left:0;right:0;font:400 11px/1.4 'Segoe UI',system-ui,sans-serif;color:#64748b;}" +
453
+ ".rte-page-header{text-align:left;}" +
454
+ ".rte-page-footer{text-align:center;}" +
455
+ "@media print{.rte-page-overlay{display:none !important;}" +
456
+ ".rte-page-view{box-shadow:none !important;margin:0 !important;width:auto !important;}}";
457
+ var st = doc.createElement("style");
458
+ st.id = "rte-pagination-styles";
459
+ st.appendChild(doc.createTextNode(css));
460
+ (doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement).appendChild(st);
461
+ }
462
+
463
+ // ---- serialization guard --------------------------------------------
464
+
465
+ // The overlay lives next to the editable, not inside it, so it should never
466
+ // serialize. This is the belt-and-braces guard for hosts where the editable
467
+ // IS the body (inline mode), where "next to" can still mean "inside".
468
+ function stripFor() {
469
+ var ed = getEditable();
470
+ if (!ed) return function () {};
471
+ var nodes = ed.querySelectorAll ? ed.querySelectorAll("[data-rte-page-overlay]") : [];
472
+ var parked = [];
473
+ for (var i = 0; i < nodes.length; i++) {
474
+ var n = nodes[i];
475
+ parked.push({ node: n, parent: n.parentNode, next: n.nextSibling });
476
+ if (n.parentNode) n.parentNode.removeChild(n);
477
+ }
478
+ return function restore() {
479
+ for (var j = 0; j < parked.length; j++) {
480
+ var p = parked[j];
481
+ if (p.parent) p.parent.insertBefore(p.node, p.next || null);
482
+ }
483
+ };
484
+ }
485
+
486
+ function wrapSerializers() {
487
+ if (wrapped) return;
488
+ var names = ["getHTMLCode", "getJSON", "getHTMLContent", "getText"];
489
+ var did = false;
490
+ for (var i = 0; i < names.length; i++) {
491
+ (function (name) {
492
+ var orig = editor[name];
493
+ if (typeof orig !== "function" || orig.__rtePgWrapped) return;
494
+ var w = function () {
495
+ var restore = stripFor();
496
+ try { return orig.apply(editor, arguments); } finally { restore(); }
497
+ };
498
+ w.__rtePgWrapped = true;
499
+ editor[name] = w;
500
+ did = true;
501
+ })(names[i]);
502
+ }
503
+ if (did) wrapped = true;
504
+ }
505
+ }