@richscripts/richtexteditor 2.6.4 → 2.7.0
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/README.md +21 -1
- package/package.json +2 -2
- package/richtexteditor/all_plugins.js +355 -5
- package/richtexteditor/all_plugins.min.js +56 -56
- package/richtexteditor/plugins/all_plugins.js +355 -5
- package/richtexteditor/plugins/all_plugins.min.js +56 -56
- package/richtexteditor/plugins/documentimport.js +25 -2
- package/richtexteditor/plugins/docxexport.js +31 -0
- package/richtexteditor/plugins/inlinecode.js +270 -0
- package/richtexteditor/plugins/pdfexport.js +25 -2
- package/richtexteditor/plugins/wordexport.js +3 -1
- package/richtexteditor/rte.js +15 -3
package/README.md
CHANGED
|
@@ -446,4 +446,24 @@ In order to start using Rich Text Editor, configure or customize it further, ple
|
|
|
446
446
|
|
|
447
447
|
## License
|
|
448
448
|
|
|
449
|
-
For licensing information please read [Licensing Info](http://richtexteditor.com/license.aspx).
|
|
449
|
+
For licensing information please read [Licensing Info](http://richtexteditor.com/license.aspx).
|
|
450
|
+
|
|
451
|
+
### State inspector (developer tool)
|
|
452
|
+
|
|
453
|
+
A derived view of the document the editor is actually holding: a serializable
|
|
454
|
+
snapshot (node tree, selection, plugin initialization order) plus structural
|
|
455
|
+
checks for the faults that break documents downstream - zero-length text nodes,
|
|
456
|
+
empty inline wrappers, duplicate nested formatting, control characters that make
|
|
457
|
+
an exported .docx unreadable, unsafe URL schemes, `iframe[srcdoc]`, orphaned
|
|
458
|
+
list items, images with no alt text, and unprotected atomic content.
|
|
459
|
+
|
|
460
|
+
```js
|
|
461
|
+
editor.stateInspector.open(); // panel with Issues / Tree / Selection / Plugins / Log
|
|
462
|
+
editor.stateInspector.issues(); // [{ id, severity, message, detail }]
|
|
463
|
+
editor.stateInspector.snapshot(); // plain JSON - attach this to a bug report
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
It is **not** in any default toolbar. Add the button with
|
|
467
|
+
`stateInspectorToolbarButton: true`, open it from the API, or type `/inspect`.
|
|
468
|
+
Other options: `stateInspectorEnabled`, `stateInspectorAutoOpen`,
|
|
469
|
+
`stateInspectorMaxDepth`, `stateInspectorLogLimit`.
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.7.0",
|
|
7
7
|
"description": "A full-featured JavaScript rich text editor (WYSIWYG) with toolbar, plugins, i18n support, a built-in AI Toolkit (Ask AI, AI Chat, AI Review drawer, BYOK resolver), structured content APIs (JSON/Markdown), and an optional collaboration / review stack: slash commands, @mentions, human-author Track Changes, threaded comments, revision history, and Yjs-backed presence. Ships first-class React, Vue, Angular and Svelte bindings, a headless \"bring your own UI\" core, and server-safe conversion helpers.",
|
|
8
8
|
"main": "./richtexteditor/rte.js",
|
|
9
9
|
"bin": {
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"ai-editor"
|
|
134
134
|
],
|
|
135
135
|
"author": "CuteSoft Components Inc.",
|
|
136
|
-
"license": "SEE LICENSE IN
|
|
136
|
+
"license": "SEE LICENSE IN https://richtexteditor.com/license",
|
|
137
137
|
"homepage": "https://richtexteditor.com",
|
|
138
138
|
"repository": {
|
|
139
139
|
"type": "git",
|
|
@@ -24520,6 +24520,19 @@ function RTE_Plugin_DocumentImport() {
|
|
|
24520
24520
|
// matches the old behaviour rather than inventing <ol>s.
|
|
24521
24521
|
info.ordered = !!fmt && fmt !== "bullet" && fmt !== "none";
|
|
24522
24522
|
}
|
|
24523
|
+
// Word paragraph indentation. Nothing read this before 2026-09-04,
|
|
24524
|
+
// so every indented paragraph in every imported .docx arrived
|
|
24525
|
+
// flush left and the indentation was lost silently.
|
|
24526
|
+
// w:left is in twips; 15 twips = 1px.
|
|
24527
|
+
var ind = firstChildEl(ppr, "ind");
|
|
24528
|
+
if (ind) {
|
|
24529
|
+
var lft = ind.getAttributeNS(W, "left") || ind.getAttribute("w:left") || "";
|
|
24530
|
+
var tw = parseInt(lft, 10);
|
|
24531
|
+
// 720 twips alongside the Quote style is the blockquote's own
|
|
24532
|
+
// indent, already carried by the blockquote element itself.
|
|
24533
|
+
if (tw > 0 && !(info.quote && tw === 720))
|
|
24534
|
+
info.indentPx = Math.round(tw / 15);
|
|
24535
|
+
}
|
|
24523
24536
|
var jc = firstChildEl(ppr, "jc");
|
|
24524
24537
|
if (jc) {
|
|
24525
24538
|
var a = jc.getAttributeNS(W, "val") || jc.getAttribute("w:val") || "";
|
|
@@ -24586,10 +24599,20 @@ function RTE_Plugin_DocumentImport() {
|
|
|
24586
24599
|
continue;
|
|
24587
24600
|
}
|
|
24588
24601
|
closeListsTo(0);
|
|
24589
|
-
if (st.heading)
|
|
24602
|
+
if (st.heading) {
|
|
24603
|
+
// Headings carry w:ind too; without this an indented
|
|
24604
|
+
// heading imported flush left.
|
|
24605
|
+
var hs = st.indentPx ? ' style="margin-left:' + st.indentPx + 'px"' : "";
|
|
24606
|
+
out.push("<h" + st.heading + hs + ">" + (inner || "") + "</h" + st.heading + ">");
|
|
24607
|
+
}
|
|
24590
24608
|
else if (st.quote) out.push("<blockquote>" + (inner || "<br>") + "</blockquote>");
|
|
24591
24609
|
else {
|
|
24592
|
-
var
|
|
24610
|
+
var decl = [];
|
|
24611
|
+
if (st.align) decl.push("text-align:" + st.align);
|
|
24612
|
+
// indentUseMargin defaults to margin, and margin is what
|
|
24613
|
+
// w:ind means, so import writes margin-left to match.
|
|
24614
|
+
if (st.indentPx) decl.push("margin-left:" + st.indentPx + "px");
|
|
24615
|
+
var style = decl.length ? ' style="' + decl.join(";") + '"' : "";
|
|
24593
24616
|
// A paragraph that contained ONLY a text box has no text of
|
|
24594
24617
|
// its own; emitting an empty <p> before the box adds a blank
|
|
24595
24618
|
// line that was never in the document.
|
|
@@ -25612,6 +25635,31 @@ function RTE_Plugin_DocxExport() {
|
|
|
25612
25635
|
var revSeq = { n: 1 };
|
|
25613
25636
|
var base = { bold: false, italic: false, underline: false, strike: false, sizeHalfPoints: 0, color: null, highlight: null, vertAlign: null, mono: false };
|
|
25614
25637
|
|
|
25638
|
+
|
|
25639
|
+
// A block indented in the editor carries margin-left (or padding-left,
|
|
25640
|
+
// per indentUseMargin). Word expresses the same thing as w:ind, in
|
|
25641
|
+
// TWIPS. Before 2026-09-04 indentation reached Word only when it was
|
|
25642
|
+
// spelled <blockquote>, which dragged the Quote style along with it -
|
|
25643
|
+
// so indented body text arrived italic and accent-coloured.
|
|
25644
|
+
// 1px = 0.75pt and 1pt = 20 twips, hence px * 15.
|
|
25645
|
+
function indentTwips(node) {
|
|
25646
|
+
if (!node || !node.style) return 0;
|
|
25647
|
+
var raw = node.style.marginLeft || node.style.paddingLeft || "";
|
|
25648
|
+
var m = /^\s*(-?[\d.]+)\s*([a-z%]*)\s*$/i.exec(raw);
|
|
25649
|
+
if (!m) return 0;
|
|
25650
|
+
var n = parseFloat(m[1]);
|
|
25651
|
+
if (!n || n <= 0) return 0;
|
|
25652
|
+
var unit = (m[2] || "px").toLowerCase();
|
|
25653
|
+
var px = unit === "px" ? n
|
|
25654
|
+
: unit === "pt" ? n * (4 / 3)
|
|
25655
|
+
: unit === "in" ? n * 96
|
|
25656
|
+
: unit === "cm" ? n * 37.795
|
|
25657
|
+
: unit === "mm" ? n * 3.7795
|
|
25658
|
+
: (unit === "em" || unit === "rem") ? n * 16
|
|
25659
|
+
: 0;
|
|
25660
|
+
return Math.round(px * 15);
|
|
25661
|
+
}
|
|
25662
|
+
|
|
25615
25663
|
function para(node, style, opts) {
|
|
25616
25664
|
opts = opts || {};
|
|
25617
25665
|
var pr = "";
|
|
@@ -25620,6 +25668,12 @@ function RTE_Plugin_DocxExport() {
|
|
|
25620
25668
|
if (opts.pre) pr += '<w:pStyle w:val="Code"/>';
|
|
25621
25669
|
if (opts.numId) pr += "<w:numPr><w:ilvl w:val=\"" + (opts.level || 0) + "\"/><w:numId w:val=\"" + opts.numId + "\"/></w:numPr>";
|
|
25622
25670
|
if (opts.pageBreakBefore) pr += "<w:pageBreakBefore/>";
|
|
25671
|
+
// Independent of the Quote style: a real blockquote already got
|
|
25672
|
+
// its w:ind above, so only emit this for everything else.
|
|
25673
|
+
if (!opts.quote) {
|
|
25674
|
+
var indTw = indentTwips(node);
|
|
25675
|
+
if (indTw > 0) pr += '<w:ind w:left="' + indTw + '"/>';
|
|
25676
|
+
}
|
|
25623
25677
|
var align = node && node.style ? node.style.textAlign : "";
|
|
25624
25678
|
if (align === "center" || align === "right") pr += '<w:jc w:val="' + align + '"/>';
|
|
25625
25679
|
else if (align === "justify") pr += '<w:jc w:val="both"/>';
|
|
@@ -29261,6 +29315,277 @@ function RTE_Plugin_ImportStyles() {
|
|
|
29261
29315
|
}
|
|
29262
29316
|
}
|
|
29263
29317
|
|
|
29318
|
+
if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = {};
|
|
29319
|
+
|
|
29320
|
+
// 2026-09-04 Inline code — CKEditor 5's "Code" feature (`<code>`), the one
|
|
29321
|
+
// basic-style it ships that this editor had no command for. Markdown `x` was
|
|
29322
|
+
// the only way to produce inline code here, which meant there was no way to
|
|
29323
|
+
// REMOVE it and no toolbar affordance at all.
|
|
29324
|
+
//
|
|
29325
|
+
// The command is "inlinecode", NOT "code". "code" is already taken by the
|
|
29326
|
+
// HTML-source-view toggle in the core (__Is_Cmd_Active case "code" ->
|
|
29327
|
+
// ___Is_CodeMode()), so registering "code" here would have collided with source
|
|
29328
|
+
// view. keyboarda11y.js has carried "inlinecode" in its TOGGLE_CMD list all
|
|
29329
|
+
// along, with a comment that it matched nothing in this build; a core
|
|
29330
|
+
// __Is_Cmd_Active case added alongside this plugin is what makes it match, so
|
|
29331
|
+
// the button reports aria-pressed and not just a highlight.
|
|
29332
|
+
//
|
|
29333
|
+
// Design notes:
|
|
29334
|
+
// - Toggling is done by SPLITTING the <code> element around the selection
|
|
29335
|
+
// rather than by lifting text nodes out of it. Splitting preserves nested
|
|
29336
|
+
// inline markup: un-coding the middle of <code>a <em>b</em> c</code> keeps
|
|
29337
|
+
// the <em>. Lifting text nodes would have dropped it.
|
|
29338
|
+
// - A collapsed selection acts on the word under the caret. That is the same
|
|
29339
|
+
// choice changecase.js makes, and for the same reason: it makes the command
|
|
29340
|
+
// usable from a keyboard shortcut without selecting first. The alternative -
|
|
29341
|
+
// inserting an empty <code> and putting the caret inside - is the caret trap
|
|
29342
|
+
// documented in the footnotes work, so it is deliberately not done.
|
|
29343
|
+
// - Never nests inside <pre> or an existing <code>: both already mean
|
|
29344
|
+
// "preformatted", and nesting produces markup no serializer round-trips.
|
|
29345
|
+
// - Emits a bare <code> element and no rte-* class. That keeps the styling in
|
|
29346
|
+
// the content stylesheet where authors can override it, and keeps the plugin
|
|
29347
|
+
// out of check-css-contract.mjs, which only scans the four theme sheets.
|
|
29348
|
+
RTE_DefaultConfig.plugin_inlinecode = RTE_Plugin_InlineCode;
|
|
29349
|
+
|
|
29350
|
+
if (!RTE_DefaultConfig.text_inlinecode) RTE_DefaultConfig.text_inlinecode = "Inline code";
|
|
29351
|
+
|
|
29352
|
+
if (!RTE_DefaultConfig.svgCode_inlinecode) {
|
|
29353
|
+
RTE_DefaultConfig.svgCode_inlinecode = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="7 8 3 12 7 16"/><polyline points="17 8 21 12 17 16"/></svg>';
|
|
29354
|
+
}
|
|
29355
|
+
|
|
29356
|
+
function RTE_Plugin_InlineCode() {
|
|
29357
|
+
var obj = this;
|
|
29358
|
+
var config, editor;
|
|
29359
|
+
|
|
29360
|
+
obj.PluginName = "InlineCode";
|
|
29361
|
+
|
|
29362
|
+
obj.InitConfig = function (argconfig) { config = argconfig; };
|
|
29363
|
+
|
|
29364
|
+
obj.InitEditor = function (argeditor) {
|
|
29365
|
+
editor = argeditor;
|
|
29366
|
+
|
|
29367
|
+
editor.attachEvent("exec_command_inlinecode", function (state) {
|
|
29368
|
+
state.returnValue = true;
|
|
29369
|
+
obj.Toggle();
|
|
29370
|
+
});
|
|
29371
|
+
|
|
29372
|
+
// Public API, matching the shape the other toggle plugins expose.
|
|
29373
|
+
editor.toggleInlineCode = function () { return obj.Toggle(); };
|
|
29374
|
+
editor.isInlineCode = function () { return obj.IsActive(); };
|
|
29375
|
+
};
|
|
29376
|
+
|
|
29377
|
+
function getDoc() { try { return editor.getDocument(); } catch (e) { return null; } }
|
|
29378
|
+
function getEditable() { try { return editor.getEditable(); } catch (e) { return null; } }
|
|
29379
|
+
|
|
29380
|
+
function getRange(doc) {
|
|
29381
|
+
var sel = doc && doc.getSelection ? doc.getSelection() : null;
|
|
29382
|
+
if (!sel || !sel.rangeCount) return null;
|
|
29383
|
+
return sel.getRangeAt(0);
|
|
29384
|
+
}
|
|
29385
|
+
|
|
29386
|
+
// Nearest <code> ancestor that is still inside the editable area.
|
|
29387
|
+
function codeAncestor(node, editable) {
|
|
29388
|
+
while (node && node !== editable) {
|
|
29389
|
+
if (node.nodeType === 1 && node.nodeName === "CODE") return node;
|
|
29390
|
+
node = node.parentNode;
|
|
29391
|
+
}
|
|
29392
|
+
return null;
|
|
29393
|
+
}
|
|
29394
|
+
|
|
29395
|
+
function inPre(node, editable) {
|
|
29396
|
+
while (node && node !== editable) {
|
|
29397
|
+
if (node.nodeType === 1 && node.nodeName === "PRE") return true;
|
|
29398
|
+
node = node.parentNode;
|
|
29399
|
+
}
|
|
29400
|
+
return false;
|
|
29401
|
+
}
|
|
29402
|
+
|
|
29403
|
+
obj.IsActive = function () {
|
|
29404
|
+
var doc = getDoc(), editable = getEditable();
|
|
29405
|
+
if (!doc || !editable) return false;
|
|
29406
|
+
var range = getRange(doc);
|
|
29407
|
+
if (!range) return false;
|
|
29408
|
+
return !!codeAncestor(range.startContainer, editable);
|
|
29409
|
+
};
|
|
29410
|
+
|
|
29411
|
+
// ---- selection helpers -----------------------------------------------
|
|
29412
|
+
|
|
29413
|
+
// Expand a collapsed caret to the word under it. Returns false when the
|
|
29414
|
+
// caret is not sitting in a word, in which case the command does nothing
|
|
29415
|
+
// rather than creating an empty element.
|
|
29416
|
+
function expandToWord(range) {
|
|
29417
|
+
var node = range.startContainer;
|
|
29418
|
+
if (!node || node.nodeType !== 3) return false;
|
|
29419
|
+
var text = node.data || "";
|
|
29420
|
+
var offset = range.startOffset;
|
|
29421
|
+
var start = offset, end = offset;
|
|
29422
|
+
while (start > 0 && /\S/.test(text.charAt(start - 1))) start--;
|
|
29423
|
+
while (end < text.length && /\S/.test(text.charAt(end))) end++;
|
|
29424
|
+
if (start === end) return false;
|
|
29425
|
+
range.setStart(node, start);
|
|
29426
|
+
range.setEnd(node, end);
|
|
29427
|
+
return true;
|
|
29428
|
+
}
|
|
29429
|
+
|
|
29430
|
+
// Every text node the range actually covers. Boundary text nodes are split
|
|
29431
|
+
// first so a partially-selected node is not treated as fully selected.
|
|
29432
|
+
function selectedTextNodes(doc, range, editable) {
|
|
29433
|
+
if (range.startContainer.nodeType === 3 && range.startOffset > 0) {
|
|
29434
|
+
var s = range.startContainer;
|
|
29435
|
+
if (range.startOffset < s.data.length) {
|
|
29436
|
+
var rest = s.splitText(range.startOffset);
|
|
29437
|
+
var endSame = (range.endContainer === s);
|
|
29438
|
+
var endOff = range.endOffset;
|
|
29439
|
+
range.setStart(rest, 0);
|
|
29440
|
+
if (endSame) range.setEnd(rest, endOff - s.data.length);
|
|
29441
|
+
}
|
|
29442
|
+
}
|
|
29443
|
+
if (range.endContainer.nodeType === 3 && range.endOffset < range.endContainer.data.length && range.endOffset > 0) {
|
|
29444
|
+
range.endContainer.splitText(range.endOffset);
|
|
29445
|
+
}
|
|
29446
|
+
|
|
29447
|
+
var out = [];
|
|
29448
|
+
var walker = doc.createTreeWalker(editable, 4 /* SHOW_TEXT */, null, false);
|
|
29449
|
+
var n;
|
|
29450
|
+
while ((n = walker.nextNode())) {
|
|
29451
|
+
if (!n.data) continue;
|
|
29452
|
+
if (range.intersectsNode ? range.intersectsNode(n) : true) {
|
|
29453
|
+
// intersectsNode is true for merely-touching nodes, so confirm
|
|
29454
|
+
// the node is genuinely within the boundaries.
|
|
29455
|
+
var r = doc.createRange();
|
|
29456
|
+
r.selectNodeContents(n);
|
|
29457
|
+
if (range.compareBoundaryPoints(Range.END_TO_START, r) < 0 &&
|
|
29458
|
+
range.compareBoundaryPoints(Range.START_TO_END, r) > 0) {
|
|
29459
|
+
out.push(n);
|
|
29460
|
+
}
|
|
29461
|
+
}
|
|
29462
|
+
}
|
|
29463
|
+
return out;
|
|
29464
|
+
}
|
|
29465
|
+
|
|
29466
|
+
// ---- apply / remove ---------------------------------------------------
|
|
29467
|
+
|
|
29468
|
+
// Split `code` around the selection and unwrap only the overlapping part,
|
|
29469
|
+
// so partial removal keeps the untouched halves coded.
|
|
29470
|
+
function unwrapPart(doc, code, range) {
|
|
29471
|
+
var parent = code.parentNode;
|
|
29472
|
+
if (!parent) return;
|
|
29473
|
+
|
|
29474
|
+
var startsInside = code.contains(range.startContainer);
|
|
29475
|
+
var endsInside = code.contains(range.endContainer);
|
|
29476
|
+
|
|
29477
|
+
// Tail first: extracting it cannot disturb the head's boundaries.
|
|
29478
|
+
var after = doc.createRange();
|
|
29479
|
+
if (endsInside) after.setStart(range.endContainer, range.endOffset);
|
|
29480
|
+
else after.setStart(code, code.childNodes.length);
|
|
29481
|
+
after.setEnd(code, code.childNodes.length);
|
|
29482
|
+
var afterFrag = after.extractContents();
|
|
29483
|
+
|
|
29484
|
+
var before = doc.createRange();
|
|
29485
|
+
before.setStart(code, 0);
|
|
29486
|
+
if (startsInside) before.setEnd(range.startContainer, range.startOffset);
|
|
29487
|
+
else before.setEnd(code, 0);
|
|
29488
|
+
var beforeFrag = before.extractContents();
|
|
29489
|
+
|
|
29490
|
+
if (beforeFrag && beforeFrag.textContent !== "") {
|
|
29491
|
+
var head = doc.createElement("code");
|
|
29492
|
+
head.appendChild(beforeFrag);
|
|
29493
|
+
parent.insertBefore(head, code);
|
|
29494
|
+
}
|
|
29495
|
+
|
|
29496
|
+
// What is left in `code` is exactly the selected part: unwrap it.
|
|
29497
|
+
while (code.firstChild) parent.insertBefore(code.firstChild, code);
|
|
29498
|
+
|
|
29499
|
+
if (afterFrag && afterFrag.textContent !== "") {
|
|
29500
|
+
var tail = doc.createElement("code");
|
|
29501
|
+
tail.appendChild(afterFrag);
|
|
29502
|
+
parent.insertBefore(tail, code);
|
|
29503
|
+
}
|
|
29504
|
+
|
|
29505
|
+
parent.removeChild(code);
|
|
29506
|
+
}
|
|
29507
|
+
|
|
29508
|
+
function wrapNodes(doc, nodes, editable) {
|
|
29509
|
+
// Group contiguous siblings so a run of text under one parent becomes a
|
|
29510
|
+
// single <code>, not one per text node.
|
|
29511
|
+
var group = [], groups = [];
|
|
29512
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
29513
|
+
var n = nodes[i];
|
|
29514
|
+
if (inPre(n, editable) || codeAncestor(n, editable)) continue;
|
|
29515
|
+
if (group.length && group[group.length - 1].parentNode === n.parentNode &&
|
|
29516
|
+
group[group.length - 1].nextSibling === n) {
|
|
29517
|
+
group.push(n);
|
|
29518
|
+
} else {
|
|
29519
|
+
if (group.length) groups.push(group);
|
|
29520
|
+
group = [n];
|
|
29521
|
+
}
|
|
29522
|
+
}
|
|
29523
|
+
if (group.length) groups.push(group);
|
|
29524
|
+
|
|
29525
|
+
for (var g = 0; g < groups.length; g++) {
|
|
29526
|
+
var run = groups[g];
|
|
29527
|
+
// Whitespace-only runs would produce a <code> containing nothing but
|
|
29528
|
+
// a space, which is invisible formatting debris.
|
|
29529
|
+
var joined = "";
|
|
29530
|
+
for (var k = 0; k < run.length; k++) joined += run[k].data;
|
|
29531
|
+
if (!/\S/.test(joined)) continue;
|
|
29532
|
+
|
|
29533
|
+
var code = doc.createElement("code");
|
|
29534
|
+
run[0].parentNode.insertBefore(code, run[0]);
|
|
29535
|
+
for (var j = 0; j < run.length; j++) code.appendChild(run[j]);
|
|
29536
|
+
}
|
|
29537
|
+
}
|
|
29538
|
+
|
|
29539
|
+
// Adjacent <code><code> pairs read as one to the user but serialize as two.
|
|
29540
|
+
function mergeAdjacent(editable) {
|
|
29541
|
+
var all = editable.querySelectorAll("code");
|
|
29542
|
+
for (var i = 0; i < all.length; i++) {
|
|
29543
|
+
var code = all[i];
|
|
29544
|
+
var next = code.nextSibling;
|
|
29545
|
+
while (next && next.nodeType === 1 && next.nodeName === "CODE") {
|
|
29546
|
+
while (next.firstChild) code.appendChild(next.firstChild);
|
|
29547
|
+
next.parentNode.removeChild(next);
|
|
29548
|
+
next = code.nextSibling;
|
|
29549
|
+
}
|
|
29550
|
+
}
|
|
29551
|
+
}
|
|
29552
|
+
|
|
29553
|
+
obj.Toggle = function () {
|
|
29554
|
+
var doc = getDoc(), editable = getEditable();
|
|
29555
|
+
if (!doc || !editable) return false;
|
|
29556
|
+
var range = getRange(doc);
|
|
29557
|
+
if (!range) return false;
|
|
29558
|
+
if (!editable.contains(range.startContainer)) return false;
|
|
29559
|
+
|
|
29560
|
+
if (range.collapsed && !expandToWord(range)) return false;
|
|
29561
|
+
|
|
29562
|
+
var nodes = selectedTextNodes(doc, range, editable);
|
|
29563
|
+
if (!nodes.length) return false;
|
|
29564
|
+
|
|
29565
|
+
// Remove when every selected text node is already coded; otherwise apply.
|
|
29566
|
+
var allCoded = true;
|
|
29567
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
29568
|
+
if (!/\S/.test(nodes[i].data)) continue;
|
|
29569
|
+
if (!codeAncestor(nodes[i], editable)) { allCoded = false; break; }
|
|
29570
|
+
}
|
|
29571
|
+
|
|
29572
|
+
if (allCoded) {
|
|
29573
|
+
var codes = [], seen = [];
|
|
29574
|
+
for (var n = 0; n < nodes.length; n++) {
|
|
29575
|
+
var c = codeAncestor(nodes[n], editable);
|
|
29576
|
+
if (c && seen.indexOf(c) < 0) { seen.push(c); codes.push(c); }
|
|
29577
|
+
}
|
|
29578
|
+
for (var x = 0; x < codes.length; x++) unwrapPart(doc, codes[x], range);
|
|
29579
|
+
} else {
|
|
29580
|
+
wrapNodes(doc, nodes, editable);
|
|
29581
|
+
mergeAdjacent(editable);
|
|
29582
|
+
}
|
|
29583
|
+
|
|
29584
|
+
try { editor.updateToolbarStatus && editor.updateToolbarStatus(); } catch (e) { }
|
|
29585
|
+
return true;
|
|
29586
|
+
};
|
|
29587
|
+
}
|
|
29588
|
+
|
|
29264
29589
|
|
|
29265
29590
|
if (!RTE_DefaultConfig.svgCode_insertcode) {
|
|
29266
29591
|
RTE_DefaultConfig.svgCode_insertcode = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><polyline points="8 7 3 12 8 17"/><polyline points="16 7 21 12 16 17"/><line x1="14" y1="5" x2="10" y2="19"/></svg>';
|
|
@@ -35450,6 +35775,28 @@ function RTE_Plugin_PdfExport() {
|
|
|
35450
35775
|
var self = this;
|
|
35451
35776
|
var base = { bold: false, italic: false, underline: false, strike: false, size: this.baseSize, color: [0, 0, 0], mono: false, link: null };
|
|
35452
35777
|
|
|
35778
|
+
// A block indented in the editor carries margin-left (or padding-left, per
|
|
35779
|
+
// indentUseMargin). Before 2026-09-04 the PDF honoured an indent only when
|
|
35780
|
+
// it was spelled <blockquote>, because that was the only way the editor
|
|
35781
|
+
// could express one. Points, since that is the PDF's unit: 1px = 0.75pt.
|
|
35782
|
+
function cssIndentPt(node) {
|
|
35783
|
+
if (!node || !node.style) return 0;
|
|
35784
|
+
var raw = node.style.marginLeft || node.style.paddingLeft || "";
|
|
35785
|
+
var m = /^\s*(-?[\d.]+)\s*([a-z%]*)\s*$/i.exec(raw);
|
|
35786
|
+
if (!m) return 0;
|
|
35787
|
+
var n = parseFloat(m[1]);
|
|
35788
|
+
if (!n || n <= 0) return 0;
|
|
35789
|
+
var unit = (m[2] || "px").toLowerCase();
|
|
35790
|
+
var px = unit === "px" ? n
|
|
35791
|
+
: unit === "pt" ? n * (4 / 3)
|
|
35792
|
+
: unit === "in" ? n * 96
|
|
35793
|
+
: unit === "cm" ? n * 37.795
|
|
35794
|
+
: unit === "mm" ? n * 3.7795
|
|
35795
|
+
: (unit === "em" || unit === "rem") ? n * 16
|
|
35796
|
+
: 0;
|
|
35797
|
+
return px * 0.75;
|
|
35798
|
+
}
|
|
35799
|
+
|
|
35453
35800
|
function block(node, style, indent, listMarker, listId, listOrdered) {
|
|
35454
35801
|
var tag = node.tagName.toLowerCase();
|
|
35455
35802
|
var s = styleOf(node, style);
|
|
@@ -35522,8 +35869,9 @@ function RTE_Plugin_PdfExport() {
|
|
|
35522
35869
|
continue;
|
|
35523
35870
|
}
|
|
35524
35871
|
if (isBlockTag(tag)) {
|
|
35525
|
-
|
|
35526
|
-
|
|
35872
|
+
var own = (tag === "blockquote" ? self.baseSize * 1.5 : 0) + cssIndentPt(c);
|
|
35873
|
+
if (hasBlockChildren(c)) { walk(c, styleOf(c, style), indent + own); continue; }
|
|
35874
|
+
block(c, style, indent + own, null);
|
|
35527
35875
|
continue;
|
|
35528
35876
|
}
|
|
35529
35877
|
// Inline content sitting directly under a container.
|
|
@@ -43026,7 +43374,9 @@ function RTE_Plugin_WordExport() {
|
|
|
43026
43374
|
"table { border-collapse: collapse; }\r\n" +
|
|
43027
43375
|
"td, th { border: 1px solid #999; padding: 4px 8px; }\r\n" +
|
|
43028
43376
|
"img { max-width: 100%; height: auto; }\r\n" +
|
|
43029
|
-
|
|
43377
|
+
// margin-left was zeroed here, which also flattened any indentation
|
|
43378
|
+
// expressed as a margin. Keep the quote bar, keep the offset.
|
|
43379
|
+
"blockquote { border-left: 3px solid #ccc; padding-left: 12px; color: #444; }\r\n" +
|
|
43030
43380
|
"pre { font-family: Consolas, 'Courier New', monospace; background: #f4f4f4; padding: 8px; }\r\n" +
|
|
43031
43381
|
"</style>\r\n" +
|
|
43032
43382
|
"</head>\r\n" +
|