@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
|
@@ -233,10 +233,129 @@ function RTE_Plugin_SlashCommand() {
|
|
|
233
233
|
push("Tools", "readaloud", "Read aloud", "Speak the selection or document (text-to-speech)", ["tts", "speak", "speech", "voice", "accessibility", "narrate"], iconReadAloud(),
|
|
234
234
|
function () { editor.execCommand("readaloud"); });
|
|
235
235
|
}
|
|
236
|
+
// 2026-07-27 Footnotes / format painter / change case. Each is gated
|
|
237
|
+
// behind a premium plan by CKEditor and/or TinyMCE; all three ship here.
|
|
238
|
+
// One entry per defined merge field, so the slash menu's own search does
|
|
239
|
+
// the filtering — the same shape as TinyMCE's searchable merge-tag menu.
|
|
240
|
+
if (typeof editor.sortTableAtCaret === "function") {
|
|
241
|
+
push("Tools", "sorttableasc", "Sort table by this column", "Sort the table rows by the column the caret is in", ["sort", "table", "column", "order", "ascending"], iconSort(),
|
|
242
|
+
function () { editor.sortTableAtCaret("asc"); });
|
|
243
|
+
push("Tools", "sorttabledesc", "Sort table (descending)", "Sort the table rows by this column, largest first", ["sort", "table", "column", "descending", "reverse"], iconSort(),
|
|
244
|
+
function () { editor.sortTableAtCaret("desc"); });
|
|
245
|
+
push("Tools", "tablerownumbers", "Number table rows", "Add or remove an automatic row-numbering column", ["number", "rows", "table", "count", "index"], iconSort(),
|
|
246
|
+
function () { editor.execCommand("tablerownumbers"); });
|
|
247
|
+
}
|
|
248
|
+
if (typeof editor.moveBlockUp === "function") {
|
|
249
|
+
push("Tools", "moveblockup", "Move block up", "Move this paragraph or block above the previous one (Alt+Shift+Up)", ["move", "block", "up", "reorder", "drag"], iconMoveBlock(),
|
|
250
|
+
function () { editor.moveBlockUp(); });
|
|
251
|
+
push("Tools", "moveblockdown", "Move block down", "Move this paragraph or block below the next one (Alt+Shift+Down)", ["move", "block", "down", "reorder", "drag"], iconMoveBlock(),
|
|
252
|
+
function () { editor.moveBlockDown(); });
|
|
253
|
+
}
|
|
254
|
+
if (typeof editor.toggleRtlUserInterface === "function") {
|
|
255
|
+
push("Tools", "rtlui", "Mirror editor for RTL", "Flip the toolbar, menus and dialogs for right-to-left languages", ["rtl", "mirror", "interface", "toolbar", "arabic", "hebrew"], iconRtl(),
|
|
256
|
+
function () { editor.toggleRtlUserInterface(); });
|
|
257
|
+
}
|
|
258
|
+
if (typeof editor.setTextDirection === "function") {
|
|
259
|
+
push("Tools", "rtl", "Right-to-left", "Set this paragraph to right-to-left (Arabic, Hebrew, Persian)", ["rtl", "arabic", "hebrew", "persian", "urdu", "direction", "bidi"], iconRtl(),
|
|
260
|
+
function () { editor.setTextDirection("rtl"); });
|
|
261
|
+
push("Tools", "ltr", "Left-to-right", "Set this paragraph to left-to-right", ["ltr", "direction", "bidi"], iconLtr(),
|
|
262
|
+
function () { editor.setTextDirection("ltr"); });
|
|
263
|
+
push("Tools", "autodir", "Detect text direction", "Set each paragraph's direction from its own content", ["direction", "detect", "auto", "rtl", "bidi"], iconRtl(),
|
|
264
|
+
function () { editor.autoDetectTextDirection(); });
|
|
265
|
+
}
|
|
266
|
+
if (typeof editor.toggleFormattingMarks === "function") {
|
|
267
|
+
push("Tools", "formattingmarks", "Formatting marks", "Show paragraph marks, block outlines and invisible characters", ["marks", "pilcrow", "paragraph", "invisible", "whitespace", "nbsp"], iconPilcrow(),
|
|
268
|
+
function () { editor.toggleFormattingMarks(); });
|
|
269
|
+
}
|
|
270
|
+
if (typeof editor.toggleWatermark === "function") {
|
|
271
|
+
push("Tools", "watermark", "Watermark", "Draw DRAFT or CONFIDENTIAL behind the content", ["watermark", "draft", "confidential", "background", "stamp"], iconWatermark(),
|
|
272
|
+
function () { editor.toggleWatermark(); });
|
|
273
|
+
}
|
|
274
|
+
if (typeof editor.toggleLineNumbers === "function") {
|
|
275
|
+
push("Tools", "linenumbers", "Line numbers", "Number every line in the margin, as pleadings and transcripts require", ["line", "numbers", "margin", "pleading", "legal", "transcript"], iconLineNumbers(),
|
|
276
|
+
function () { editor.toggleLineNumbers(); });
|
|
277
|
+
}
|
|
278
|
+
if (typeof editor.togglePermanentPen === "function") {
|
|
279
|
+
push("Tools", "permanentpen", "Permanent pen", "Keep typing in a fixed annotation format until switched off", ["pen", "marker", "annotate", "highlight", "review"], iconPen(),
|
|
280
|
+
function () { editor.togglePermanentPen(); });
|
|
281
|
+
}
|
|
282
|
+
if (typeof editor.checkLinks === "function") {
|
|
283
|
+
push("Tools", "checklinks", "Check links", "Find broken anchors, unsafe schemes and unlabelled links", ["link", "check", "broken", "audit", "url", "accessibility"], iconLinkCheck(),
|
|
284
|
+
function () {
|
|
285
|
+
editor.highlightLinkIssues(true);
|
|
286
|
+
editor.checkLinks();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (typeof editor.getMergeFieldDefinitions === "function") {
|
|
290
|
+
var mfDefs = editor.getMergeFieldDefinitions();
|
|
291
|
+
for (var mfi = 0; mfi < mfDefs.length; mfi++) {
|
|
292
|
+
(function (def) {
|
|
293
|
+
if (!def || !def.id) return;
|
|
294
|
+
push("Insert", "mergefield-" + def.id, def.label || def.id,
|
|
295
|
+
"Insert the " + (def.label || def.id) + " merge field",
|
|
296
|
+
["merge", "field", "placeholder", "variable", "mail merge", def.id],
|
|
297
|
+
iconMergeField(),
|
|
298
|
+
function () { editor.insertMergeField(def.id); });
|
|
299
|
+
})(mfDefs[mfi]);
|
|
300
|
+
}
|
|
301
|
+
push("Tools", "mergepreview", "Preview merge fields", "Swap merge-field placeholders for sample data", ["merge", "preview", "sample", "data", "placeholder"], iconMergeField(),
|
|
302
|
+
function () { editor.toggleMergeFieldPreview(); });
|
|
303
|
+
}
|
|
304
|
+
// One entry per referable target, resolved when the menu is built, so the
|
|
305
|
+
// slash menu's own search filters headings/clauses/tables/figures.
|
|
306
|
+
if (typeof editor.listCrossReferenceTargets === "function") {
|
|
307
|
+
var xrefTargets = [];
|
|
308
|
+
try { xrefTargets = editor.listCrossReferenceTargets(); } catch (e) { xrefTargets = []; }
|
|
309
|
+
for (var xi = 0; xi < xrefTargets.length && xi < 40; xi++) {
|
|
310
|
+
(function (tg) {
|
|
311
|
+
if (!tg || !tg.id) return;
|
|
312
|
+
var shown = tg.number ? (tg.number + " " + tg.label) : tg.label;
|
|
313
|
+
push("Insert", "xref-" + tg.id, "Reference: " + shown,
|
|
314
|
+
"Insert a cross-reference to this " + tg.type + " that updates itself",
|
|
315
|
+
["cross", "reference", "xref", "see", "refer", tg.type, tg.number || ""],
|
|
316
|
+
iconXref(),
|
|
317
|
+
function () { editor.insertCrossReference(tg.id, tg.number ? "label" : "text"); });
|
|
318
|
+
})(xrefTargets[xi]);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (typeof editor.insertTableOfContents === "function") {
|
|
322
|
+
push("Insert", "toc", "Table of contents", "Insert a contents block that updates itself from your headings", ["toc", "contents", "outline", "index", "headings"], iconToc(),
|
|
323
|
+
function () { editor.insertTableOfContents(); });
|
|
324
|
+
}
|
|
325
|
+
if (typeof editor.insertFootnote === "function") {
|
|
326
|
+
push("Insert", "footnote", "Footnote", "Insert a numbered footnote reference and note", ["footnote", "note", "citation", "reference", "endnote", "cite"], iconFootnote(),
|
|
327
|
+
function () { editor.insertFootnote(); });
|
|
328
|
+
}
|
|
329
|
+
if (typeof editor.toggleFormatPainter === "function") {
|
|
330
|
+
push("Tools", "formatpainter", "Format painter", "Copy formatting here, then select the text to paint it onto", ["format", "painter", "brush", "copy", "style", "clone"], iconBrush(),
|
|
331
|
+
function () { editor.toggleFormatPainter(); });
|
|
332
|
+
}
|
|
333
|
+
if (typeof editor.changeCase === "function") {
|
|
334
|
+
push("Tools", "caseupper", "UPPERCASE", "Convert the selection to upper case", ["case", "upper", "caps", "capitals"], iconCase(),
|
|
335
|
+
function () { editor.changeCase("upper"); });
|
|
336
|
+
push("Tools", "caselower", "lowercase", "Convert the selection to lower case", ["case", "lower", "small"], iconCase(),
|
|
337
|
+
function () { editor.changeCase("lower"); });
|
|
338
|
+
push("Tools", "casetitle", "Title Case", "Capitalise each significant word in the selection", ["case", "title", "headline", "capitalise", "capitalize"], iconCase(),
|
|
339
|
+
function () { editor.changeCase("title"); });
|
|
340
|
+
push("Tools", "casesentence", "Sentence case", "Capitalise the first letter of each sentence", ["case", "sentence", "capitalise", "capitalize"], iconCase(),
|
|
341
|
+
function () { editor.changeCase("sentence"); });
|
|
342
|
+
}
|
|
236
343
|
if (typeof editor.requestGhostText === "function") {
|
|
237
344
|
push("Tools", "aicomplete", "AI complete", "Suggest an inline AI completion of the current sentence", ["ai", "complete", "autocomplete", "ghost", "suggest", "continue", "copilot"], iconGhost(),
|
|
238
345
|
function () { editor.requestGhostText(); });
|
|
239
346
|
}
|
|
347
|
+
// Gated on the API rather than registered by the plugin itself: plugins
|
|
348
|
+
// initialise in bundle order, so anything sorting before "slashcommand"
|
|
349
|
+
// would call slashCommands.register() before it exists and vanish from
|
|
350
|
+
// this menu without any error. This check runs when the menu opens.
|
|
351
|
+
if (typeof editor.exportToPdf === "function") {
|
|
352
|
+
push("Tools", "exportpdf", "Export to PDF", "Real text, not a screenshot — selectable, searchable and screen-reader friendly", ["pdf", "export", "download", "save", "print", "text", "accessible", "searchable"], iconPdfText(),
|
|
353
|
+
function () { editor.exportToPdf(); });
|
|
354
|
+
}
|
|
355
|
+
if (typeof editor.exportToDocx === "function") {
|
|
356
|
+
push("Tools", "exportdocx", "Export to Word (.docx)", "A real OOXML document, built in the browser with no upload", ["word", "docx", "ooxml", "export", "download", "save", "office"], iconWordFile(),
|
|
357
|
+
function () { editor.exportToDocx(); });
|
|
358
|
+
}
|
|
240
359
|
if (typeof editor.copyAsMarkdown === "function") {
|
|
241
360
|
push("Tools", "copymarkdown", "Copy as Markdown", "Copy the whole document to the clipboard as Markdown", ["markdown", "md", "copy", "clipboard", "export"], iconMarkdown(),
|
|
242
361
|
function () { editor.copyAsMarkdown(); });
|
|
@@ -436,9 +555,8 @@ function RTE_Plugin_SlashCommand() {
|
|
|
436
555
|
popupEl = popupHost.createElement("div");
|
|
437
556
|
popupEl.className = "rte-slash-popup";
|
|
438
557
|
// The popup is body-appended (outside the editor container), so the
|
|
439
|
-
// container's rte-dark class can't reach it via CSS. Mirror
|
|
440
|
-
// dark
|
|
441
|
-
// the media query in the injected stylesheet.
|
|
558
|
+
// container's rte-dark class can't reach it via CSS. Mirror only an
|
|
559
|
+
// explicit forced-dark editor; system color preference stays light.
|
|
442
560
|
try {
|
|
443
561
|
var host = editor.container || (editor.getEditable && editor.getEditable().closest && editor.getEditable().closest(".richtexteditor"));
|
|
444
562
|
if (host && host.classList && host.classList.contains("rte-dark")) popupEl.className += " rte-slash-popup-dark";
|
|
@@ -641,20 +759,8 @@ function RTE_Plugin_SlashCommand() {
|
|
|
641
759
|
".rte-slash-item-desc{font-size:11px;color:#64748b;line-height:1.24;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
642
760
|
".rte-slash-item-active .rte-slash-item-desc{color:#315277}",
|
|
643
761
|
".rte-slash-empty{padding:16px;color:#52657e;font-size:12px;font-weight:700;text-align:center}",
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
// forced dark mode (the popup is body-appended, outside the editor
|
|
647
|
-
// container, so the container's .rte-dark class can't reach it).
|
|
648
|
-
"@media (prefers-color-scheme:dark){",
|
|
649
|
-
" .rte-slash-popup{background:linear-gradient(180deg,#1e293b,#0f172a);border-color:#334155;color:#e2e8f0;box-shadow:0 20px 46px rgba(0,0,0,.5),0 2px 8px rgba(0,0,0,.4)}",
|
|
650
|
-
" .rte-slash-section{background:linear-gradient(180deg,rgba(30,41,59,.98),rgba(30,41,59,.86));color:#94a3b8}",
|
|
651
|
-
" .rte-slash-item:hover,.rte-slash-item-active{background:#1d4ed8;color:#fff}",
|
|
652
|
-
" .rte-slash-item-icon{color:#cbd5e1;background:#0f172a;box-shadow:inset 0 0 0 1px rgba(148,163,184,.25)}",
|
|
653
|
-
" .rte-slash-item-active .rte-slash-item-icon{color:#fff;background:#1e3a8a}",
|
|
654
|
-
" .rte-slash-item-desc{color:#94a3b8}",
|
|
655
|
-
" .rte-slash-item-active .rte-slash-item-desc{color:#dbeafe}",
|
|
656
|
-
" .rte-slash-empty{color:#94a3b8}",
|
|
657
|
-
"}",
|
|
762
|
+
// The command menu remains light by default. Its explicit
|
|
763
|
+
// rte-slash-popup-dark rules below follow a forced-dark editor.
|
|
658
764
|
".rte-slash-popup-dark{background:linear-gradient(180deg,#1e293b,#0f172a);border-color:#334155;color:#e2e8f0;box-shadow:0 20px 46px rgba(0,0,0,.5),0 2px 8px rgba(0,0,0,.4)}",
|
|
659
765
|
".rte-slash-popup-dark .rte-slash-section{background:linear-gradient(180deg,rgba(30,41,59,.98),rgba(30,41,59,.86));color:#94a3b8}",
|
|
660
766
|
".rte-slash-popup-dark .rte-slash-item:hover,.rte-slash-popup-dark .rte-slash-item-active{background:#1d4ed8;color:#fff}",
|
|
@@ -694,6 +800,21 @@ function RTE_Plugin_SlashCommand() {
|
|
|
694
800
|
function iconEmail() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M4 7l8 6 8-6"/></svg>'; }
|
|
695
801
|
function iconDiagram() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="6" height="5" rx="1"/><rect x="15" y="3" width="6" height="5" rx="1"/><rect x="9" y="16" width="6" height="5" rx="1"/><path d="M6 8v3h12V8M12 11v5"/></svg>'; }
|
|
696
802
|
function iconReadAloud() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 9v6h4l5 4V5L8 9H4z"/><path d="M16 8.5a4 4 0 010 7"/><path d="M19 6a7 7 0 010 12"/></svg>'; }
|
|
803
|
+
function iconMoveBlock() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18"/><path d="M8 7l4-4 4 4"/><path d="M8 17l4 4 4-4"/></svg>'; }
|
|
804
|
+
function iconRtl() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6H10a3 3 0 000 6h3"/><path d="M13 6v12"/><path d="M17 6v12"/><path d="M7 18l-3-3 3-3"/></svg>'; }
|
|
805
|
+
function iconLtr() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 6H7a3 3 0 000 6h3"/><path d="M11 6v12"/><path d="M7 6v12"/><path d="M17 12l3 3-3 3"/></svg>'; }
|
|
806
|
+
function iconPilcrow() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 4v16"/><path d="M17 4v16"/><path d="M13 4H9a4 4 0 000 8h4"/></svg>'; }
|
|
807
|
+
function iconWatermark() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 15l4-5 3 3 3-4"/></svg>'; }
|
|
808
|
+
function iconLineNumbers() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h1v4"/><path d="M4 14h2l-2 3h2"/><path d="M10 7h10"/><path d="M10 12h10"/><path d="M10 17h10"/></svg>'; }
|
|
809
|
+
function iconSort() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 4v16"/><path d="M4 8l3-4 3 4"/><path d="M14 6h6"/><path d="M14 12h5"/><path d="M14 18h3"/></svg>'; }
|
|
810
|
+
function iconPen() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3l6 6L9 21H3v-6z"/><path d="M13 5l6 6"/></svg>'; }
|
|
811
|
+
function iconLinkCheck() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 13a4 4 0 005.7 0l2-2a4 4 0 10-5.7-5.7l-1 1"/><path d="M13 11a4 4 0 00-5.7 0l-2 2a4 4 0 105.7 5.7l1-1"/><path d="M16 18l2 2 4-4"/></svg>'; }
|
|
812
|
+
function iconMergeField() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 4H7a2 2 0 00-2 2v3a2 2 0 01-2 2 2 2 0 012 2v3a2 2 0 002 2h2"/><path d="M15 4h2a2 2 0 012 2v3a2 2 0 002 2 2 2 0 00-2 2v3a2 2 0 01-2 2h-2"/></svg>'; }
|
|
813
|
+
function iconXref() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h8"/><path d="M4 12h5"/><path d="M4 17h8"/><path d="M14 12h6"/><path d="M17 9l3 3-3 3"/></svg>'; }
|
|
814
|
+
function iconToc() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h7"/><path d="M4 11h9"/><path d="M4 16h5"/><path d="M17 6h3"/><path d="M17 11h3"/><path d="M17 16h3"/></svg>'; }
|
|
815
|
+
function iconFootnote() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h9"/><path d="M4 11h9"/><path d="M4 18h16"/><path d="M17 4v6"/><path d="M20 7h-6"/></svg>'; }
|
|
816
|
+
function iconBrush() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3h11a2 2 0 012 2v4a2 2 0 01-2 2H6z"/><path d="M12 11v3"/><path d="M10 14h4v5a2 2 0 01-4 0z"/></svg>'; }
|
|
817
|
+
function iconCase() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 18L8 6l5 12"/><path d="M4.6 14.5h6.8"/><path d="M21 11.5a3 3 0 00-6 0"/><path d="M21 11.5V18"/><path d="M21 15.5a3 3 0 11-6 0 3 3 0 016 0z"/></svg>'; }
|
|
697
818
|
function iconTodo() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="7" height="7" rx="1.5"/><path d="M4.5 7.5 6 9l2.5-3"/><line x1="13" y1="6" x2="21" y2="6"/><line x1="13" y1="13" x2="21" y2="13"/><line x1="3" y1="17" x2="21" y2="17"/><line x1="3" y1="20.5" x2="14" y2="20.5"/></svg>'; }
|
|
698
819
|
function iconFold() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l3 3 3-3"/><path d="M9 18l3-3 3 3"/><line x1="4" y1="12" x2="20" y2="12"/></svg>'; }
|
|
699
820
|
function iconKeyboard() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M6 14h.01M18 14h.01M9 14h6"/></svg>'; }
|
|
@@ -702,4 +823,8 @@ function RTE_Plugin_SlashCommand() {
|
|
|
702
823
|
function iconFocus() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M3 12h3M18 12h3M12 3v3M12 18v3"/></svg>'; }
|
|
703
824
|
function iconMarkdown() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M6 15V9l3 3 3-3v6"/><path d="M17 9v6M14.5 12.5L17 15l2.5-2.5"/></svg>'; }
|
|
704
825
|
function iconGhost() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 21V9a7 7 0 0114 0v12l-3-2-2 2-2-2-2 2-3-2z"/><circle cx="9.5" cy="10" r="1" fill="currentColor"/><circle cx="14.5" cy="10" r="1" fill="currentColor"/></svg>'; }
|
|
826
|
+
// A page with text lines on it, rather than the usual PDF badge: the point
|
|
827
|
+
// of this entry is that the output contains text.
|
|
828
|
+
function iconPdfText() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H7a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V8z"/><path d="M14 3v5h5"/><path d="M9 13h6M9 17h4"/></svg>'; }
|
|
829
|
+
function iconWordFile() { return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H7a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V8z"/><path d="M14 3v5h5"/><path d="M8.5 12l1.3 5 2.2-3.6 2.2 3.6 1.3-5"/></svg>'; }
|
|
705
830
|
}
|
|
@@ -62,6 +62,8 @@ function RTE_Plugin_SpellCheck() {
|
|
|
62
62
|
var host = editable.ownerDocument;
|
|
63
63
|
var t = host.createElement("div");
|
|
64
64
|
t.className = "rte-spell-toast";
|
|
65
|
+
t.setAttribute("role", "status");
|
|
66
|
+
t.setAttribute("aria-live", "polite");
|
|
65
67
|
t.textContent = msg;
|
|
66
68
|
host.body.appendChild(t);
|
|
67
69
|
setTimeout(function () { if (t.parentNode) t.parentNode.removeChild(t); }, 2600);
|
|
@@ -111,7 +113,7 @@ function RTE_Plugin_SpellCheck() {
|
|
|
111
113
|
"rte-dialog-spell"
|
|
112
114
|
);
|
|
113
115
|
var close = typeof dlg.close === "function" ? function () { dlg.close(); } : function () { editor.closeCurrentPopup(); };
|
|
114
|
-
var wrap = append(dlg, "div", "
|
|
116
|
+
var wrap = append(dlg, "div", "box-sizing:border-box;width:min(420px,calc(100vw - 32px));max-height:60vh;overflow:auto;padding:14px;font:13px -apple-system,Segoe UI,sans-serif");
|
|
115
117
|
var status = append(wrap, "div", "color:#64748b;margin-bottom:8px");
|
|
116
118
|
status.innerText = "Checking…";
|
|
117
119
|
var list = append(wrap, "div", "");
|
|
@@ -134,7 +136,7 @@ function RTE_Plugin_SpellCheck() {
|
|
|
134
136
|
append(sug, "span", "font-size:12px;color:#94a3b8").textContent = "(no suggestions)";
|
|
135
137
|
}
|
|
136
138
|
suggestions.forEach(function (s) {
|
|
137
|
-
var chip = append(sug, "button", "font-size:12px;padding:
|
|
139
|
+
var chip = append(sug, "button", "font-size:12px;padding:4px 9px;border:1px solid #cbd5e1;border-radius:6px;background:#f8fbff;color:#315277;cursor:pointer");
|
|
138
140
|
chip.type = "button";
|
|
139
141
|
chip.textContent = s;
|
|
140
142
|
chip.onclick = function () {
|
|
@@ -165,7 +167,8 @@ function RTE_Plugin_SpellCheck() {
|
|
|
165
167
|
|
|
166
168
|
function injectStyles() {
|
|
167
169
|
var css = [
|
|
168
|
-
"
|
|
170
|
+
"/* 2026-07-12 spell-check feedback precision */",
|
|
171
|
+
".rte-spell-toast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%);max-width:calc(100vw - 32px);padding:9px 12px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;color:#172033;font:600 13px/1.35 -apple-system,Segoe UI,sans-serif;z-index:2147483600;box-shadow:0 10px 24px rgba(15,23,42,.14)}",
|
|
169
172
|
".rte-spell-row:last-child{border-bottom:0}"
|
|
170
173
|
].join("\n");
|
|
171
174
|
try {
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
|
|
2
|
+
|
|
3
|
+
// 2026-07-27 Table of contents. An insertable, self-updating contents block
|
|
4
|
+
// built from the document's headings, with links that jump to them.
|
|
5
|
+
//
|
|
6
|
+
// Premium in both majors:
|
|
7
|
+
// - CKEditor 5 table of contents: "Unlock this feature with selected CKEditor
|
|
8
|
+
// Plans". Their pitch is that "the list stays up-to-date automatically as
|
|
9
|
+
// the user works on the document", which is the bar this has to clear.
|
|
10
|
+
// - TinyMCE tableofcontents: "This plugin is only available for paid TinyMCE
|
|
11
|
+
// subscriptions".
|
|
12
|
+
//
|
|
13
|
+
// This editor already ships documentoutline.js, but that is a NAVIGATION PANEL
|
|
14
|
+
// beside the editor — chrome, not content. It never lands in the saved HTML.
|
|
15
|
+
// A table of contents is the opposite: it is part of the document you publish
|
|
16
|
+
// or print, so it has to survive getHTMLCode() and read correctly with no
|
|
17
|
+
// JavaScript running.
|
|
18
|
+
//
|
|
19
|
+
// Design notes:
|
|
20
|
+
// - The block is contenteditable=false and rebuilt from the headings on every
|
|
21
|
+
// mutation, so it cannot drift from the document. Hand-editing generated
|
|
22
|
+
// content is a trap, so the block simply is not editable; delete it as a
|
|
23
|
+
// unit and re-insert to move it.
|
|
24
|
+
// - Entry text and numbers are written as REAL TEXT into the block (the same
|
|
25
|
+
// reasoning as footnotes.js): the saved HTML has to stand alone once it
|
|
26
|
+
// leaves the editor, where no script will regenerate anything.
|
|
27
|
+
// - Headings inside generated regions (this block, the footnotes section) are
|
|
28
|
+
// skipped, otherwise the TOC lists its own title.
|
|
29
|
+
// - Page numbers are shown when pagination.js is loaded AND page view is on,
|
|
30
|
+
// via editor.getPageOfElement(). Outside page view a page number would be a
|
|
31
|
+
// fiction, so none is written.
|
|
32
|
+
RTE_DefaultConfig.plugin_tableofcontents = RTE_Plugin_TableOfContents;
|
|
33
|
+
|
|
34
|
+
// Heading above the list. Set to "" to omit it.
|
|
35
|
+
if (typeof RTE_DefaultConfig.tocTitle === "undefined") RTE_DefaultConfig.tocTitle = "Contents";
|
|
36
|
+
// Heading levels included, inclusive.
|
|
37
|
+
if (typeof RTE_DefaultConfig.tocMinLevel === "undefined") RTE_DefaultConfig.tocMinLevel = 1;
|
|
38
|
+
if (typeof RTE_DefaultConfig.tocMaxLevel === "undefined") RTE_DefaultConfig.tocMaxLevel = 3;
|
|
39
|
+
// "auto" = page numbers when page view is on | true = always try | false = never.
|
|
40
|
+
if (typeof RTE_DefaultConfig.tocPageNumbers === "undefined") RTE_DefaultConfig.tocPageNumbers = "auto";
|
|
41
|
+
|
|
42
|
+
function RTE_Plugin_TableOfContents() {
|
|
43
|
+
var obj = this;
|
|
44
|
+
var config, editor;
|
|
45
|
+
var boundDoc = null;
|
|
46
|
+
var queued = false;
|
|
47
|
+
var idSeq = 0;
|
|
48
|
+
|
|
49
|
+
obj.PluginName = "TableOfContents";
|
|
50
|
+
|
|
51
|
+
obj.InitConfig = function (argconfig) { config = argconfig; };
|
|
52
|
+
|
|
53
|
+
obj.InitEditor = function (argeditor) {
|
|
54
|
+
editor = argeditor;
|
|
55
|
+
|
|
56
|
+
editor.attachEvent("exec_command_inserttoc", function (state) {
|
|
57
|
+
state.returnValue = true;
|
|
58
|
+
obj.Insert();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
setup();
|
|
62
|
+
try { editor.attachEvent("ready", setup); } catch (e) {}
|
|
63
|
+
try { editor.attachEvent("aftersethtml", function () { setup(); queue(); }); } catch (e) {}
|
|
64
|
+
setTimeout(function () { setup(); queue(); }, 0);
|
|
65
|
+
|
|
66
|
+
// Public API.
|
|
67
|
+
editor.insertTableOfContents = function () { return obj.Insert(); };
|
|
68
|
+
editor.updateTableOfContents = function () { return rebuild(); };
|
|
69
|
+
editor.removeTableOfContents = function () { return obj.Remove(); };
|
|
70
|
+
editor.hasTableOfContents = function () { return !!block(); };
|
|
71
|
+
editor.getTableOfContents = function () { return obj.List(); };
|
|
72
|
+
editor.getTocCss = function () { return css(); };
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function setup() {
|
|
76
|
+
var doc = getDoc();
|
|
77
|
+
if (!doc) return;
|
|
78
|
+
injectStyles(doc);
|
|
79
|
+
if (doc === boundDoc) return;
|
|
80
|
+
boundDoc = doc;
|
|
81
|
+
var editable = getEditable();
|
|
82
|
+
if (!editable) return;
|
|
83
|
+
editable.addEventListener("keyup", queue);
|
|
84
|
+
editable.addEventListener("cut", queue);
|
|
85
|
+
editable.addEventListener("paste", queue);
|
|
86
|
+
editable.addEventListener("drop", queue);
|
|
87
|
+
// Clicking an entry scrolls to its heading. The block is not editable, so
|
|
88
|
+
// the anchors would otherwise do nothing inside the iframe.
|
|
89
|
+
editable.addEventListener("click", function (e) {
|
|
90
|
+
var a = closestClass(e.target, "rte-toc-link", editable);
|
|
91
|
+
if (!a) return;
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
var target = editable.querySelector("#" + cssEscapeId(a.getAttribute("data-toc-target")));
|
|
94
|
+
if (!target) return;
|
|
95
|
+
try { target.scrollIntoView({ block: "center" }); } catch (e2) { target.scrollIntoView(); }
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getDoc() { try { return editor.getDocument(); } catch (e) { return null; } }
|
|
100
|
+
function getEditable() { try { return editor.getEditable(); } catch (e) { return null; } }
|
|
101
|
+
|
|
102
|
+
// setTimeout not rAF: a hidden iframe or background tab never fires rAF, and
|
|
103
|
+
// the contents would silently stop updating.
|
|
104
|
+
function queue() {
|
|
105
|
+
if (queued) return;
|
|
106
|
+
queued = true;
|
|
107
|
+
setTimeout(function () { queued = false; rebuild(); }, 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function closestClass(node, cls, root) {
|
|
111
|
+
while (node && node !== root) {
|
|
112
|
+
if (node.nodeType === 1 && node.classList && node.classList.contains(cls)) return node;
|
|
113
|
+
node = node.parentNode;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function cssEscapeId(s) { return String(s || "").replace(/["\\\]\[]/g, "\\$&"); }
|
|
119
|
+
|
|
120
|
+
function block() {
|
|
121
|
+
var editable = getEditable();
|
|
122
|
+
return editable ? editable.querySelector("nav.rte-toc") : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- heading collection ---------------------------------------------
|
|
126
|
+
|
|
127
|
+
function levelRange() {
|
|
128
|
+
var min = Math.max(1, Math.min(6, parseInt(config.tocMinLevel, 10) || 1));
|
|
129
|
+
var max = Math.max(min, Math.min(6, parseInt(config.tocMaxLevel, 10) || 3));
|
|
130
|
+
return { min: min, max: max };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function headings() {
|
|
134
|
+
var editable = getEditable();
|
|
135
|
+
if (!editable) return [];
|
|
136
|
+
var r = levelRange();
|
|
137
|
+
var sel = [];
|
|
138
|
+
for (var l = r.min; l <= r.max; l++) sel.push("h" + l);
|
|
139
|
+
var all = editable.querySelectorAll(sel.join(","));
|
|
140
|
+
var out = [];
|
|
141
|
+
for (var i = 0; i < all.length; i++) {
|
|
142
|
+
var h = all[i];
|
|
143
|
+
// Skip generated regions, or the TOC lists its own "Contents" title
|
|
144
|
+
// and the footnotes section heading.
|
|
145
|
+
if (h.closest && (h.closest("nav.rte-toc") || h.closest("section.rte-footnotes"))) continue;
|
|
146
|
+
if (!(h.textContent || "").trim()) continue; // empty heading = noise
|
|
147
|
+
out.push(h);
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function ensureHeadingId(h) {
|
|
153
|
+
var id = h.getAttribute("id");
|
|
154
|
+
if (id && id.indexOf("rte-h-") === 0) return id;
|
|
155
|
+
if (id) return id; // respect an id the author already chose
|
|
156
|
+
id = "rte-h-" + Math.floor(Math.random() * 1e9).toString(36) + (idSeq++).toString(36);
|
|
157
|
+
h.setAttribute("id", id);
|
|
158
|
+
return id;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function wantPageNumbers() {
|
|
162
|
+
var mode = config.tocPageNumbers;
|
|
163
|
+
if (mode === false) return false;
|
|
164
|
+
if (typeof editor.getPageOfElement !== "function") return false;
|
|
165
|
+
if (mode === true) return true;
|
|
166
|
+
// "auto": only in page view, where the numbers mean something.
|
|
167
|
+
try { return typeof editor.isPageView === "function" && editor.isPageView(); } catch (e) { return false; }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---- build -----------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
obj.Insert = function () {
|
|
173
|
+
var doc = getDoc();
|
|
174
|
+
var editable = getEditable();
|
|
175
|
+
if (!doc || !editable) return false;
|
|
176
|
+
var existing = block();
|
|
177
|
+
if (existing) { rebuild(); return true; } // one per document
|
|
178
|
+
|
|
179
|
+
var nav = doc.createElement("nav");
|
|
180
|
+
nav.className = "rte-toc";
|
|
181
|
+
nav.setAttribute("contenteditable", "false");
|
|
182
|
+
nav.setAttribute("data-rte-toc", "1");
|
|
183
|
+
|
|
184
|
+
var placed = false;
|
|
185
|
+
try {
|
|
186
|
+
if (typeof editor.insertElement === "function") { editor.insertElement(nav); placed = true; }
|
|
187
|
+
} catch (e) {}
|
|
188
|
+
if (!placed) editable.insertBefore(nav, editable.firstChild);
|
|
189
|
+
|
|
190
|
+
rebuild();
|
|
191
|
+
fireChange();
|
|
192
|
+
return true;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
obj.Remove = function () {
|
|
196
|
+
var b = block();
|
|
197
|
+
if (!b || !b.parentNode) return false;
|
|
198
|
+
b.parentNode.removeChild(b);
|
|
199
|
+
fireChange();
|
|
200
|
+
return true;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
function rebuild() {
|
|
204
|
+
var nav = block();
|
|
205
|
+
if (!nav) return [];
|
|
206
|
+
var doc = getDoc();
|
|
207
|
+
if (!doc) return [];
|
|
208
|
+
|
|
209
|
+
var hs = headings();
|
|
210
|
+
var r = levelRange();
|
|
211
|
+
var pages = wantPageNumbers();
|
|
212
|
+
|
|
213
|
+
// Carry forward the page numbers already in the block, keyed by heading.
|
|
214
|
+
// Without this, opening a document that was saved in page view and then
|
|
215
|
+
// rebuilding with page view OFF silently deletes numbers that were
|
|
216
|
+
// computed for real — destroying part of the saved document just by
|
|
217
|
+
// loading it. Stale-until-you-re-enter-page-view matches how a word
|
|
218
|
+
// processor treats a contents field; silently losing them does not.
|
|
219
|
+
var priorPages = {};
|
|
220
|
+
var priorItems = nav.querySelectorAll("li.rte-toc-item");
|
|
221
|
+
for (var p = 0; p < priorItems.length; p++) {
|
|
222
|
+
var pa = priorItems[p].querySelector("a.rte-toc-link");
|
|
223
|
+
var pp = priorItems[p].querySelector(".rte-toc-page");
|
|
224
|
+
if (pa && pp) {
|
|
225
|
+
var n = parseInt(pp.textContent, 10);
|
|
226
|
+
if (n) priorPages[pa.getAttribute("data-toc-target")] = n;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Rebuild the inner list wholesale. The block is not editable, so there
|
|
231
|
+
// is no caret inside it to preserve.
|
|
232
|
+
while (nav.firstChild) nav.removeChild(nav.firstChild);
|
|
233
|
+
|
|
234
|
+
var title = String(config.tocTitle == null ? "" : config.tocTitle);
|
|
235
|
+
if (title) {
|
|
236
|
+
var h = doc.createElement("div");
|
|
237
|
+
h.className = "rte-toc-title";
|
|
238
|
+
h.textContent = title;
|
|
239
|
+
nav.appendChild(h);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!hs.length) {
|
|
243
|
+
var empty = doc.createElement("div");
|
|
244
|
+
empty.className = "rte-toc-empty";
|
|
245
|
+
empty.textContent = "No headings yet.";
|
|
246
|
+
nav.appendChild(empty);
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
var ol = doc.createElement("ol");
|
|
251
|
+
ol.className = "rte-toc-list";
|
|
252
|
+
var out = [];
|
|
253
|
+
for (var i = 0; i < hs.length; i++) {
|
|
254
|
+
var head = hs[i];
|
|
255
|
+
var level = parseInt(head.nodeName.substring(1), 10) || 1;
|
|
256
|
+
var id = ensureHeadingId(head);
|
|
257
|
+
var text = (head.textContent || "").trim();
|
|
258
|
+
|
|
259
|
+
var li = doc.createElement("li");
|
|
260
|
+
li.className = "rte-toc-item rte-toc-l" + (level - r.min + 1);
|
|
261
|
+
|
|
262
|
+
var a = doc.createElement("a");
|
|
263
|
+
a.className = "rte-toc-link";
|
|
264
|
+
a.setAttribute("href", "#" + id);
|
|
265
|
+
a.setAttribute("data-toc-target", id);
|
|
266
|
+
a.textContent = text;
|
|
267
|
+
li.appendChild(a);
|
|
268
|
+
|
|
269
|
+
var page = null;
|
|
270
|
+
if (pages) {
|
|
271
|
+
try { page = editor.getPageOfElement(head); } catch (e) { page = null; }
|
|
272
|
+
}
|
|
273
|
+
// Could not compute one (page view off) but this entry already had a
|
|
274
|
+
// number: keep it rather than throw it away.
|
|
275
|
+
if (!page && priorPages[id]) page = priorPages[id];
|
|
276
|
+
if (page) {
|
|
277
|
+
var span = doc.createElement("span");
|
|
278
|
+
span.className = "rte-toc-page";
|
|
279
|
+
span.textContent = String(page);
|
|
280
|
+
li.appendChild(span);
|
|
281
|
+
}
|
|
282
|
+
ol.appendChild(li);
|
|
283
|
+
out.push({ id: id, level: level, text: text, page: page });
|
|
284
|
+
}
|
|
285
|
+
nav.appendChild(ol);
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
obj.List = function () {
|
|
290
|
+
var nav = block();
|
|
291
|
+
if (!nav) return [];
|
|
292
|
+
var items = nav.querySelectorAll("li.rte-toc-item");
|
|
293
|
+
var out = [];
|
|
294
|
+
for (var i = 0; i < items.length; i++) {
|
|
295
|
+
var a = items[i].querySelector("a.rte-toc-link");
|
|
296
|
+
var p = items[i].querySelector(".rte-toc-page");
|
|
297
|
+
var cls = items[i].className.match(/rte-toc-l(\d+)/);
|
|
298
|
+
out.push({
|
|
299
|
+
id: a ? a.getAttribute("data-toc-target") : null,
|
|
300
|
+
level: cls ? parseInt(cls[1], 10) : 1,
|
|
301
|
+
text: a ? a.textContent : "",
|
|
302
|
+
page: p ? parseInt(p.textContent, 10) : null
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
function fireChange() {
|
|
309
|
+
try { if (typeof editor.updateDesign === "function") editor.updateDesign(); } catch (e) {}
|
|
310
|
+
try { if (typeof editor.fireChange === "function") editor.fireChange(); } catch (e) {}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ---- styles ----------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
function css() {
|
|
316
|
+
return (
|
|
317
|
+
"nav.rte-toc{margin:0 0 1.4em;padding:.9em 1.1em;border:1px solid #ddd;border-radius:6px;" +
|
|
318
|
+
"background:#fafbfc;}" +
|
|
319
|
+
"nav.rte-toc .rte-toc-title{font-weight:700;margin-bottom:.45em;}" +
|
|
320
|
+
"nav.rte-toc .rte-toc-empty{color:#888;font-style:italic;}" +
|
|
321
|
+
"ol.rte-toc-list{list-style:none;margin:0;padding:0;counter-reset:rte-toc;}" +
|
|
322
|
+
"ol.rte-toc-list>li{counter-increment:rte-toc;margin:.16em 0;display:flex;align-items:baseline;gap:.5em;}" +
|
|
323
|
+
// A leader line between the entry and its page number, the way a
|
|
324
|
+
// printed contents page sets it.
|
|
325
|
+
"ol.rte-toc-list>li>a{color:#1474ea;text-decoration:none;cursor:pointer;}" +
|
|
326
|
+
"ol.rte-toc-list>li>a:hover{text-decoration:underline;}" +
|
|
327
|
+
"ol.rte-toc-list>li:has(>.rte-toc-page)>a::after{content:'';flex:1 1 auto;margin:0 .4em;" +
|
|
328
|
+
"border-bottom:1px dotted #bbb;transform:translateY(-.25em);display:inline-block;min-width:1.5em;}" +
|
|
329
|
+
"ol.rte-toc-list>li>a{flex:1 1 auto;display:flex;align-items:baseline;}" +
|
|
330
|
+
".rte-toc-page{flex:0 0 auto;color:#666;font-variant-numeric:tabular-nums;}" +
|
|
331
|
+
".rte-toc-l2{padding-left:1.3em;}" +
|
|
332
|
+
".rte-toc-l3{padding-left:2.6em;}" +
|
|
333
|
+
".rte-toc-l4{padding-left:3.9em;}" +
|
|
334
|
+
".rte-toc-l5{padding-left:5.2em;}" +
|
|
335
|
+
".rte-toc-l6{padding-left:6.5em;}"
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function injectStyles(doc) {
|
|
340
|
+
if (!doc) return;
|
|
341
|
+
var existing = doc.getElementById("rte-toc-styles");
|
|
342
|
+
var text = css();
|
|
343
|
+
if (existing) {
|
|
344
|
+
if (existing.getAttribute("data-css") === text) return;
|
|
345
|
+
existing.parentNode && existing.parentNode.removeChild(existing);
|
|
346
|
+
}
|
|
347
|
+
var st = doc.createElement("style");
|
|
348
|
+
st.id = "rte-toc-styles";
|
|
349
|
+
st.setAttribute("data-css", text);
|
|
350
|
+
st.appendChild(doc.createTextNode(text));
|
|
351
|
+
(doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement).appendChild(st);
|
|
352
|
+
}
|
|
353
|
+
}
|