@richscripts/richtexteditor 2.10.0 → 2.11.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.
@@ -165,7 +165,8 @@ function RTE_Plugin_ImageEditor() {
165
165
  savebtn.innerText = "Saving...";
166
166
 
167
167
  var dataurl = imgeditor.toDataURL();
168
- if (!config.file_upload_handler) {
168
+ var uploadHandler = config.file_upload_handler || window.rte_file_upload_handler;
169
+ if (!uploadHandler) {
169
170
  img.src = dataurl;
170
171
  editor.notifySelectionChange();
171
172
  dialoginner.close();
@@ -174,7 +175,7 @@ function RTE_Plugin_ImageEditor() {
174
175
 
175
176
  var file = dataURLToBlob(dataurl);
176
177
 
177
- config.file_upload_handler(file, function (url, error) {
178
+ uploadHandler(file, function (url, error) {
178
179
  if (url) {
179
180
  img.src = url;
180
181
  editor.notifySelectionChange();
@@ -5,10 +5,14 @@
5
5
 
6
6
  if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = { };
7
7
 
8
- RTE_DefaultConfig.editablePaddingTop = 2;
9
- RTE_DefaultConfig.editablePaddingBottom = 2;
10
- RTE_DefaultConfig.editablePaddingLeft = 2;
11
- RTE_DefaultConfig.editablePaddingRight = 2;
8
+ // Writing gutters. These are applied as an INLINE style on the editing body, so
9
+ // they win over any stylesheet - this config is the one place to change them.
10
+ // 2px left text hard against the frame, which is the clearest "unstyled textarea"
11
+ // tell there is; these values give the content room to read as a document.
12
+ RTE_DefaultConfig.editablePaddingTop = 18;
13
+ RTE_DefaultConfig.editablePaddingBottom = 24;
14
+ RTE_DefaultConfig.editablePaddingLeft = 22;
15
+ RTE_DefaultConfig.editablePaddingRight = 22;
12
16
 
13
17
  // 2026-05-08: empty-editor placeholder ("Type something…" hint that
14
18
  // disappears the moment the user starts typing). Off by default — set
@@ -42,6 +46,8 @@ RTE_DefaultConfig.enableDragDrop = true; // Enables or disables drag-and-drop su
42
46
  RTE_DefaultConfig.enableObjectResizing = true; //Specifies whether or not to allow the users resize an object winthin the RichTextEditor.
43
47
  RTE_DefaultConfig.toggleBorder = true; //Specifies the ToggleBorder state. ToggleBorder is a handy function which allows you to see the borders without setting things to border = 1 or something like that in code.
44
48
  RTE_DefaultConfig.readOnly = false; //Gets or sets a value which indicates whether the RichTextEditor should be an active HTML editor, or a read-only document viewer.
49
+ RTE_DefaultConfig.reviewCanDecide = null;
50
+ RTE_DefaultConfig.onReviewDecisionDenied = null;
45
51
  RTE_DefaultConfig.commentsOnly = false; //Comments-only review mode: when true, the editor blocks document edits but still allows inline comments. Toggle at runtime via editor.setCommentsOnly(true).
46
52
  RTE_DefaultConfig.autoLinkOnType = true; // When true, typing a URL/email followed by space auto-wraps it in an <a> tag.
47
53
  RTE_DefaultConfig.showFloatLinkUrlPreview = true; // Show clickable URL preview in the float toolbar when caret is in an <a>.
@@ -61,6 +67,7 @@ RTE_DefaultConfig.imageHoverQuickBar = true; // Show a small "Alt / Replace / X"
61
67
  RTE_DefaultConfig.inlineMath = true; // When true, editor.applyMathMarkup() wraps $...$ runs in <span class="rte-math-inline" data-tex="..."> so a math renderer (KaTeX/MathJax) can target them.
62
68
  RTE_DefaultConfig.imageLazyLoad = true; // Newly-inserted <img> tags get loading="lazy" + decoding="async" so big documents stay responsive.
63
69
  RTE_DefaultConfig.documentUploadAccept = ".txt,.text,.rtf,.md,.markdown,.html,.htm,.pdf,.doc,.docx,.odt,.xls,.xlsx,.csv,.tsv,.json,.xml,.zip,.rar"; // Default Insert Document chooser filter.
70
+ RTE_DefaultConfig.maxUploadFileSize = 0; // Largest file the editor will insert or upload, in BYTES. 0 = no limit. Applies to the image and document dialogs, paste and drag-and-drop. Over the limit the file is refused before it is read, so nothing is embedded or sent.
64
71
  RTE_DefaultConfig.fileUploadLocalObjectUrl = true; // Without file_upload_handler, local document files insert as temporary blob: links instead of failing.
65
72
  // Reading-mode persistence key. Default is session-only so refresh restores the toolbar.
66
73
  // Set to true for per-editor localStorage persistence, or to a custom string to share state across editors.
@@ -112,8 +119,80 @@ RTE_DefaultConfig.maxTextLength = 0; //Gets or sets the maximum number of charac
112
119
  RTE_DefaultConfig.tagWhiteList = []; // The white list contains a list of tags that can be used in the editor.
113
120
  RTE_DefaultConfig.tagBlackList = []; // The black list contains a list of tags that cannot be used in the editor.
114
121
 
122
+ // ---- Content sanitizer (plugins/sanitizer.js) --------------------------------
123
+ // An allowlist filter that runs on INPUT, on OUTPUT and over the live DOM. It is
124
+ // what stops a stored XSS surviving into saved HTML - keep it on unless you
125
+ // sanitize server-side instead. Note this is a different mechanism from
126
+ // tagWhiteList/tagBlackList above: those pick which tags an AUTHOR may produce,
127
+ // the sanitizer decides what is safe to keep at all.
128
+ //
129
+ // Every default below is the behaviour you already get; setting them explicitly
130
+ // only makes the options discoverable.
131
+
132
+ RTE_DefaultConfig.contentSanitizer = true; // false disables the sanitizer entirely.
133
+
134
+ // Elements the editor does not know are UNWRAPPED by default - their text is kept
135
+ // and the tag is dropped. That is what silently removes custom elements, and it
136
+ // is also why schema.org microdata disappears (itemscope/itemtype/itemprop are
137
+ // not in the attribute allowlist). Both are recoverable with the two lists below;
138
+ // this is the equivalent of what CKEditor calls "General HTML Support".
139
+ //
140
+ // Entries may be exact names, "*" wildcards, or RegExp:
141
+ // sanitizerAllowTags = ["custom-widget", "x-*", /^ui-/]
142
+ // sanitizerAllowAttributes = ["my-attr", "data-cms-*", "itemscope", "itemtype", "itemprop"]
143
+ // data-* and aria-* are always allowed and need no entry here.
144
+ RTE_DefaultConfig.sanitizerAllowTags = [];
145
+ RTE_DefaultConfig.sanitizerAllowAttributes = [];
146
+
147
+ RTE_DefaultConfig.sanitizerAllowStyleTags = false; // true keeps <style> blocks inside content.
148
+
149
+ // Iframes: false drops them outright. Left true, only http(s) sources survive and
150
+ // an iframe with no sandbox attribute is given a restrictive one. Naming hosts is
151
+ // the stronger control - with a list, everything else is dropped.
152
+ // sanitizerAllowedIframeHosts = ["www.youtube.com", "player.vimeo.com"]
153
+ RTE_DefaultConfig.sanitizerAllowIframes = true;
154
+ RTE_DefaultConfig.sanitizerAllowedIframeHosts = [];
155
+
156
+ // Also filter the LIVE editing DOM, not just input and output. This is the guard
157
+ // that matters for collaboration: a peer's CRDT update writes into the DOM
158
+ // directly and never passes through setHTMLCode, so with this off a collaborator
159
+ // could run script in every other participant's browser.
160
+ RTE_DefaultConfig.sanitizerGuardLiveDom = true;
161
+
162
+ // Inspect what the last pass removed: editor.getSanitizerReport()
163
+ // Sanitize a string yourself: editor.sanitizeHtml(html)
164
+
165
+
115
166
  RTE_DefaultConfig.tabSpaces = 4; //Gets or sets the number of spaces to be inserted when the user hits the "tab" key.
116
167
  RTE_DefaultConfig.enterKeyTag = "p" // Determines what happens when the "enter" key is pressed in the editor. div, p or br.
168
+ RTE_DefaultConfig.boldTag = "b" // Tag the Bold command writes to the saved HTML: "b" (default) or "strong".
169
+ RTE_DefaultConfig.italicTag = "i" // Tag the Italic command writes to the saved HTML: "i" (default) or "em".
170
+ RTE_DefaultConfig.strikeTag = "s" // Tag the Strikethrough command writes to the saved HTML: "s" (default), "strike" or "del".
171
+ // <s> is the default because <strike> was removed from the HTML spec, and because
172
+ // three spellings reach the document: execCommand emits <strike>, markdown ~~x~~
173
+ // emits <s>, and pasted content arrives as <del>. There is no underlineTag: the
174
+ // only alternative to <u> is <ins>, which would assert "inserted text".
175
+ // Applies to the OUTPUT of getHTMLCode(), so it also unifies content typed as
176
+ // markdown (**bold**), which is produced as <strong>/<em> internally.
177
+
178
+ RTE_DefaultConfig.htmlcode_forcehexformat = false; // Write colors as #993300 instead of rgb(153, 51, 0) in the saved HTML.
179
+ // Why the default output is rgb() even though the color picker passes "#993300":
180
+ // the editor sets the value as-is (style.cssText += ";color:#993300"), and it is
181
+ // the BROWSER's CSSOM that normalises it to rgb(153, 51, 0) when the style is read
182
+ // back. Nothing in this editor converts it, which is why there is no "use rgb"
183
+ // setting to turn off - this option converts it BACK on the way out instead.
184
+ //
185
+ // Set true to get hex. Applies in __fixHTML(), which runs on both getHTMLCode()
186
+ // and setHTMLCode(), so it normalises colors that were already rgb() in loaded or
187
+ // pasted content as well as colors the user applies. Covers color and
188
+ // background-color, on spans and on table cells.
189
+ //
190
+ // rgba() is deliberately left alone: it carries an alpha channel that a 6-digit
191
+ // hex cannot represent, so converting it would silently drop the transparency.
192
+ //
193
+ // Availability: 2.x only. In 1.x this flag is compiled to a constant false and
194
+ // no configuration can enable it - for 1.x, post-process the output of
195
+ // getHTMLCode() instead.
117
196
 
118
197
  RTE_DefaultConfig.pasteMode = "Auto"; // Specifies the manner in which the editor handles pasted text. Auto,Disabled,PasteText,PasteWord.
119
198
  RTE_DefaultConfig.markdownShortcutsEnabled = true; // Enables Markdown-style block shortcuts such as "# ", "> ", "- ", "1. ", and "---" + Enter.
@@ -264,18 +343,33 @@ RTE_DefaultConfig.revisionHistoryEnabled = true;
264
343
  RTE_DefaultConfig.revisionHistoryMaxEntries = 50;
265
344
  RTE_DefaultConfig.revisionHistoryAutoSnapshotMs = 0;
266
345
  RTE_DefaultConfig.revisionHistoryUrl = "";
346
+ RTE_DefaultConfig.revisionHistoryRequestHeaders = null;
347
+ RTE_DefaultConfig.onRevisionHistorySyncError = null;
267
348
 
268
- // Yjs collaboration (Option B: presence + shared review ledger; NO content CRDT in v1).
349
+ // Yjs collaboration: presence and shared review ledger, with optional per-node CRDT text synchronization.
269
350
  // Yjs + a provider (y-websocket / y-webrtc / y-indexeddb) are PEER DEPENDENCIES — customers
270
351
  // load them separately and call editor.collab.attach({ doc, provider, user }).
271
- // The plugin handles: awareness (live cursors + presence), and bridging editor.reviewLedger
352
+ // The plugin handles awareness, optional CRDT text sync, and bridges editor.reviewLedger
272
353
  // into a shared Y.Map so AI suggestions, tracked changes, and comments replicate across peers.
273
354
  RTE_DefaultConfig.collabEnabled = true;
274
355
  RTE_DefaultConfig.collabLedgerMapName = "reviewLedger";
275
356
  RTE_DefaultConfig.collabShowPresence = true;
276
357
  RTE_DefaultConfig.collabShowRemoteCursors = true;
358
+ RTE_DefaultConfig.collabTextSync = false;
359
+ RTE_DefaultConfig.collabRequireCrdt = false;
360
+ RTE_DefaultConfig.onCollabStatus = null;
361
+
277
362
 
363
+ RTE_DefaultConfig.indentation = "40px"; // Step size for indent/outdent on non-list blocks. Any CSS length (px, pt, em, %).
364
+ RTE_DefaultConfig.indentUseMargin = true; // true: indent writes margin-left/right. false: padding-left/right. Margin is the default because Word's w:ind and pasted Word content both use margin.
278
365
 
366
+ RTE_DefaultConfig.stylesMerge = false; // true: the Quick Styles lists below are APPENDED to the shipped defaults instead of replacing them (TinyMCE calls this style_formats_merge).
367
+
368
+ // The four Quick Styles lists accept, in the same array:
369
+ // ["Red", "color:red", "color:red"] title, value, optional preview CSS
370
+ // { title: "Red", value: "color:red" } object form of the same thing
371
+ // { title: "Callouts", items: [ ... ] } a labelled GROUP; items are indented under a heading
372
+ // Groups render as a heading plus indented items, not as a flyout submenu.
279
373
  RTE_DefaultConfig.inlineStyles = [["Red", "color:red", "color:red"], ["Bold", "font-weight:bold", "font-weight:bold"], ["Mark", "my-cls-mark"], ["Warning", "my-cls-warning"]]; // Default CSS styles for inline styles dropdown.
280
374
  RTE_DefaultConfig.paragraphStyles = [["Red", "color:red", "color:red"], ["Bold", "font-weight:bold", "font-weight:bold"], ["Quote", "my-cls-quote"], ["LargeCenter", "my-cls-largecenter"]]; // Default CSS styles for paragraph styles dropdown.
281
375
  RTE_DefaultConfig.imageStyles = [["Border", "border: 1px solid #ddd; border-radius: 4px; padding: 5px;"], ["grayscale", "filter: grayscale(100%);"], ["Shadow", "box-shadow:0 0 8px gray"], ["Margin10", "margin:10px"], ["Padding:10", "padding:10px"]
@@ -298,18 +392,24 @@ RTE_DefaultConfig.toolbar_mobile = "{bold,italic,underline|fontname:toggle,fonts
298
392
  RTE_DefaultConfig.toolbar_basic = "{bold,italic,underline}|{fontname,fontsize}|{insertlink,insertemoji,insertimage,insertvideo}|removeformat|code"
299
393
  + "#{toggleborder,fullscreenenter,fullscreenexit,undo,redo,togglemore}"; // Basic set of buttons that appears in the rich text editor's toolbar.
300
394
 
301
- RTE_DefaultConfig.toolbar_full = "{bold,italic,underline,forecolor,backcolor}|{justifyleft,justifycenter,justifyright,justifyfull}|{insertorderedlist,insertunorderedlist,indent,outdent}{superscript,subscript}|{aiassist}"
395
+ RTE_DefaultConfig.toolbar_full = "{bold,italic,underline,strike,forecolor,backcolor}|{justifyleft,justifycenter,justifyright,justifyfull}|{insertorderedlist,insertunorderedlist,indent,outdent}{superscript,subscript,inlinecode}|{aiassist}"
302
396
  + " #{paragraphs:toggle,fontname:toggle,fontsize:toggle,inlinestyle,lineheight}"
303
- + " / {spellcheck,insertcomment,removeformat,cut,copy,paste,delete,find}|{insertlink,unlink,insertblockquote,insertemoji,insertchars,inserttable,menu_tablecell,menu_tablerow,menu_tablecolumn,insertimage,insertgallery,insertvideo,insertdocument,inserttemplate,insertcode}"
397
+ + " / {spellcheck,textpartlanguage,insertcomment,removeformat,cut,copy,paste,delete,find}|{insertlink,unlink,insertblockquote,insertemoji,insertchars,inserttable,menu_tablecell,menu_tablerow,menu_tablecolumn,insertimage,insertgallery,insertvideo,insertdocument,inserttemplate,insertcode}"
304
398
  + "#{preview,code,selectall}"
305
399
  + " /{paragraphs:dropdown | fontname:dropdown | fontsize:dropdown} {paragraphstyle,toggle_paragraphop,menu_paragraphop}"
306
400
  + "#{insertmergefield,insertfootnote,inserttoc,insertpagebreak,revisionhistory,newdoc,save,toggleborder,fullscreenenter,fullscreenexit,undo,redo,togglemore}"; // Full set of buttons that appears in the rich text editor's toolbar.
307
401
 
308
402
  RTE_DefaultConfig.toolbar_richtextboxjs = RTE_DefaultConfig.toolbar_full; // Compatibility alias for older demo pages still referencing the temporary preset name.
309
403
 
310
- RTE_DefaultConfig.toolbar_office = "<@COMMON,ribbonpaste,pastetext,pasteword,{save,new,print}/{cut,copy,delete,find}/{undo,redo|formatpainter}><@FORMAT,[fontname,fontsize]/{bold,italic,underlinemenu|forecolor,backcolor}/{superscript,subscript,changecase|removeformat,cleancode,selectall}><@PARAGRAPHS,[paragraphs,styles]/{justifymenu,lineheight,ltr,rtl,insertlinemenu}/{insertorderedlist,insertunorderedlist,indent,outdent,insertblockquote}><@INSERT,ribbontable,insertgallery,insertimage,{insertform,insertbox,insertlayer,insertfieldset,pageproperties,help,toggleborder,fullscreen}/{insertlink,unlink,insertanchor,insertimagemap,insertdate,insertchars,virtualkeyboard}/{inserttemplate,insertdocument,insertvideo,syntaxhighlighter,insertyoutube,html5,googlemap}>";
404
+ RTE_DefaultConfig.toolbar_office = "<@COMMON,ribbonpaste,pastetext,pasteword,{save,new,print}/{cut,copy,delete,find}/{undo,redo|formatpainter}><@FORMAT,[fontname,fontsize]/{bold,italic,underlinemenu|forecolor,backcolor}/{superscript,subscript,changecase|removeformat,cleancode,selectall}><@PARAGRAPHS,[paragraphs,styles]/{justifymenu,lineheight,ltr,rtl,textpartlanguage,insertlinemenu}/{insertorderedlist,insertunorderedlist,indent,outdent,insertblockquote}><@INSERT,ribbontable,insertgallery,insertimage,{insertform,insertbox,insertlayer,insertfieldset,pageproperties,help,toggleborder,fullscreen}/{insertlink,unlink,insertanchor,insertimagemap,insertdate,insertchars,virtualkeyboard}/{inserttemplate,insertdocument,insertvideo,syntaxhighlighter,insertyoutube,html5,googlemap}>";
311
405
 
312
- RTE_DefaultConfig.subtoolbar_more = "{strike,superscript,subscript,ucase,lcase,titlecase,inserthorizontalrule,highlight,sortlines,insertcallout,insertcolumns,inserttoggle,inserttodolist,insertmath,insertdiagram,insertbookmark,insertdatechip,spellcheck,emailexport,readaloud,html2pdf,insertdate} #{newdoc,load,save,print,printpreview,readingmode,help}"; // A set of buttons that appears in the subtoolbar of default toolbar set.
406
+ RTE_DefaultConfig.subtoolbar_more = "{strike,superscript,subscript,inlinecode,ucase,lcase,titlecase,inserthorizontalrule,highlight,sortlines,insertcallout,insertcolumns,inserttoggle,inserttodolist,insertmath,insertdiagram,insertbookmark,insertdatechip,spellcheck,emailexport,readaloud,html2pdf,insertdate} #{newdoc,load,save,print,printpreview,readingmode,help}"; // A set of buttons that appears in the subtoolbar of default toolbar set.
407
+ // `inlinecode` is deliberately absent from subtoolbar_more_full below: it is
408
+ // already in toolbar_full's main row, so adding it here would render the button
409
+ // TWICE in that preset. It belongs in subtoolbar_more (above) because that is the
410
+ // overflow set for the DEFAULT toolbar, and toolbar_default carries neither strike
411
+ // nor inlinecode - without that line the feature is unreachable for every user who
412
+ // never configured a toolbar, which is how it shipped in 2.5.8.
313
413
  RTE_DefaultConfig.subtoolbar_more_full = "{strike,ucase,lcase,titlecase,inserthorizontalrule,highlight,sortlines,html2pdf,insertdate} #{newdoc,save,print,printpreview,readingmode,help}";// A set of buttons that appears in the subtoolbar of full toolbar set.
314
414
  RTE_DefaultConfig.subtoolbar_more_mobile = "{save} #{newdoc,help}"; // A set of buttons that appears in the subtoolbar of mobile toolbar set.
315
415
  RTE_DefaultConfig.subtoolbar_paste = "pasteauto,pastetext,pasteword"; // A set of buttons that appears in the rich text editor's paste subtoolbar.
@@ -442,7 +542,9 @@ RTE_DefaultConfig.svgCode_newdoc = '<svg viewBox="0 0 24 24" fill="none" stroke=
442
542
  RTE_DefaultConfig.svgCode_lineheight = '<svg viewBox="-2 -2 36 36" fill="currentColor"><rect width="13" height="2" x="17" y="6"></rect><rect width="10" height="2" x="17" y="12"></rect><rect width="13" height="2" x="17" y="18"></rect><rect width="10" height="2" x="17" y="24"></rect><polygon points="11.59 13.41 8 9.83 8 9.83 4.41 13.42 3 12 8 7 13 12 11.59 13.41"></polygon><polygon points="11.59 18.59 8 22.17 8 22.17 4.41 18.58 3 20 8 25 13 20 11.59 18.59"></polygon></svg>';
443
543
  RTE_DefaultConfig.svgCode_insertemoji = '<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="9"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>';
444
544
  RTE_DefaultConfig.svgCode_insertchars = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 20h4l-2.3-2.6a6 6 0 1 1 8.6 0L14 20h4"/></svg>';
445
- RTE_DefaultConfig.svgCode_selectall = '<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="18" height="18" rx="2" stroke-dasharray="3 3"/><path d="M8.5 12.5l2.5 2.5 4.5-5"/></svg>';
545
+ // Language of parts (WCAG 3.1.2). A speech bubble over a globe: the phrase, not the page.
546
+ RTE_DefaultConfig.svgCode_textpartlanguage = '<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="9"/><path d="M3 12h18"/><path d="M12 3a15 15 0 0 1 0 18a15 15 0 0 1 0-18z"/></svg>';
547
+ RTE_DefaultConfig.svgCode_selectall ='<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="18" height="18" rx="2" stroke-dasharray="3 3"/><path d="M8.5 12.5l2.5 2.5 4.5-5"/></svg>';
446
548
  RTE_DefaultConfig.svgCode_inserthorizontalrule = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="12" x2="21" y2="12"/></svg>';
447
549
  RTE_DefaultConfig.svgCode_insertdate = '<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="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>';
448
550
  RTE_DefaultConfig.svgCode_forecolor = '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M13.6,12.6h1.2l-4.3-9.8H9.3L5,12.6h1.2l1-2.3h5.4L13.6,12.6z M7.8,9.2l2.1-4.8H10l2.1,4.8L7.8,9.2z M3.8,14.4h12.3v2.3H3.8V14.4z"/></svg>';
@@ -627,6 +729,8 @@ RTE_DefaultConfig.text_revisionempty = "No text captured in this snapshot.";
627
729
  RTE_DefaultConfig.text_html2pdf = "Create PDF";
628
730
  RTE_DefaultConfig.text_insertemoji = "Insert Emoji";
629
731
  RTE_DefaultConfig.text_insertchars = "Special characters";
732
+ RTE_DefaultConfig.text_textpartlanguage = "Language";// Mark the language of a phrase (WCAG 3.1.2).
733
+ RTE_DefaultConfig.text_removelanguage = "Remove language";
630
734
  RTE_DefaultConfig.text_characters = "Characters";
631
735
  RTE_DefaultConfig.text_words = "Words";
632
736
 
@@ -829,6 +933,9 @@ RTE_DefaultConfig.text_searchemojis = "Search";
829
933
  RTE_DefaultConfig.text_insertgallerytitle = "@insertgallery";
830
934
  RTE_DefaultConfig.text_inserttemplatetitle = "@inserttemplate";
831
935
 
936
+ RTE_DefaultConfig.text_uploadfailed = "The file could not be uploaded."; // Shown when the upload handler reports an error; the error code follows in brackets.
937
+ RTE_DefaultConfig.text_uploadnothandler = "This file cannot be attached, because file uploads are not set up on this site."; // Shown when no file_upload_handler is configured and fileUploadLocalObjectUrl is false.
938
+ RTE_DefaultConfig.text_uploadtoobig = "This file is {0}. The maximum allowed size is {1}."; // {0} = the file size, {1} = maxUploadFileSize. Shown when a file is refused; handle the "uploadtoobig" customdialog hook for your own dialog.
832
939
  RTE_DefaultConfig.text_reachmaxlength = "The text to be added has reached the character limit for this field.";
833
940
 
834
941
  RTE_DefaultConfig.translation = RTE_DefaultConfig.translation || {
@@ -846,27 +953,29 @@ RTE_DefaultConfig.plugin_insertcomment = function () {
846
953
  var selectedText = sel && !sel.isCollapsed ? sel.toString() : "";
847
954
 
848
955
  var dialoginner = editor.createDialog("Add Comment", "rte-dialog-insertcomment");
849
- dialoginner.style.padding = "16px";
956
+ dialoginner.style.padding = "14px";
850
957
  if (selectedText) {
851
958
  var preview = document.createElement("div");
852
- preview.style.cssText = "background:#f5f5f5;border-left:3px solid #0f8b8d;padding:8px 12px;margin-bottom:12px;font-size:12px;color:#555;border-radius:0 4px 4px 0;max-height:60px;overflow:hidden;";
959
+ preview.className = "rte-comment-selection-preview";
853
960
  preview.innerText = selectedText.substring(0, 120) + (selectedText.length > 120 ? "..." : "");
854
961
  dialoginner.appendChild(preview);
855
962
  }
856
963
  var label = document.createElement("label");
857
964
  label.innerText = "Comment:";
858
- label.style.cssText = "display:block;margin-bottom:6px;font-size:13px;font-weight:600;";
965
+ label.className = "rte-comment-dialog-label";
859
966
  dialoginner.appendChild(label);
860
967
  var textarea = document.createElement("textarea");
968
+ textarea.id = "rte-comment-input-" + new Date().getTime();
969
+ label.htmlFor = textarea.id;
861
970
  textarea.placeholder = "Type your comment...";
862
- textarea.style.cssText = "width:100%;height:80px;padding:8px;border:1px solid #ccc;border-radius:4px;font-size:13px;box-sizing:border-box;resize:vertical;";
971
+ textarea.className = "rte-comment-dialog-input";
863
972
  dialoginner.appendChild(textarea);
864
973
  var btnRow = document.createElement("div");
865
- btnRow.style.cssText = "margin-top:12px;text-align:right;";
974
+ btnRow.className = "rte-comment-dialog-actions";
866
975
  var insertBtn = document.createElement("button");
867
976
  insertBtn.innerText = "Add Comment";
868
977
  insertBtn.type = "button";
869
- insertBtn.style.cssText = "padding:6px 18px;background:#0f8b8d;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;";
978
+ insertBtn.className = "rte-comment-dialog-submit";
870
979
  insertBtn.onclick = function () {
871
980
  var comment = textarea.value.replace(/^\s+|\s+$/g, "");
872
981
  if (comment && selectedText) {
@@ -877,11 +986,30 @@ RTE_DefaultConfig.plugin_insertcomment = function () {
877
986
  mark.setAttribute("data-comment", comment);
878
987
  try { range.surroundContents(mark); } catch (e) {}
879
988
  } else if (comment) {
880
- editor.insertHTML('<span class="rte-comment-marker" contenteditable="false" style="background:#fff9c4;border:1px solid #f9a825;border-radius:3px;padding:1px 6px;font-size:11px;color:#f57f17;cursor:pointer;" title="' + comment.replace(/"/g, "&quot;") + '">&#128172; Comment</span>');
989
+ var safeComment = comment.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
990
+ editor.insertHTML('<span class="rte-comment-marker" contenteditable="false" style="background:#fff9c4;border:1px solid #f9a825;border-radius:3px;padding:1px 6px;font-size:11px;color:#f57f17;cursor:pointer;" title="' + safeComment + '">&#128172; Comment</span>');
881
991
  }
882
992
  dialoginner.close();
883
993
  editor.focus();
884
994
  };
995
+ var cancelBtn = document.createElement("button");
996
+ cancelBtn.innerText = "Cancel";
997
+ cancelBtn.type = "button";
998
+ cancelBtn.className = "rte-comment-dialog-cancel";
999
+ cancelBtn.onclick = function () {
1000
+ dialoginner.close();
1001
+ editor.focus();
1002
+ };
1003
+ textarea.onkeydown = function (event) {
1004
+ if (event.key === "Escape") {
1005
+ event.preventDefault();
1006
+ cancelBtn.click();
1007
+ } else if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
1008
+ event.preventDefault();
1009
+ insertBtn.click();
1010
+ }
1011
+ };
1012
+ btnRow.appendChild(cancelBtn);
885
1013
  btnRow.appendChild(insertBtn);
886
1014
  dialoginner.appendChild(btnRow);
887
1015
  setTimeout(function () { textarea.focus(); }, 100);
@@ -931,3 +1059,9 @@ RTE_DefaultConfig.plugin_insertcomment = function () {
931
1059
  add("highlight", mod + "+" + shift + "+H");
932
1060
  add("readingmode", mod + "+" + shift + "+M");
933
1061
  })();
1062
+
1063
+ // 2026-09-04 State inspector (developer tool). The button is opt-in via
1064
+ // stateInspectorToolbarButton; these strings exist for hosts that turn it on.
1065
+ RTE_DefaultConfig.svgCode_stateinspector = '<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="18" height="18" rx="2"/><path d="M8 8h3v3"/><path d="M8 16h8"/><path d="M14 8l2 2-2 2"/></svg>';
1066
+ RTE_DefaultConfig.text_stateinspector = "State inspector";
1067
+ RTE_DefaultConfig.text_stateinspectorhint = "Derived document model, structural checks, and a copyable snapshot for bug reports.";
@@ -10,6 +10,7 @@
10
10
  * - a chrome-less editing surface (toolbar / sub-toolbar / status bar hidden)
11
11
  * - run(command, value) -> dispatch any RTE command
12
12
  * - isActive(name) / state() -> query active formatting for your buttons
13
+ * - onFormatChange(cmds, cb) -> subscribe to state changes; returns unsubscribe
13
14
  * - can(command) -> queryCommandEnabled (undo/redo/...)
14
15
  * - on("update" | "change" | "selectionchange" | "focus" | "blur", cb)
15
16
  * - getHTML / setHTML / getText / setText / getMarkdown / setMarkdown / getJSON
@@ -266,6 +267,36 @@
266
267
  return String(this.getText() || "").replace(/\s+/g, "").length === 0;
267
268
  };
268
269
 
270
+ // Subscribe to formatting-state changes instead of polling isActive() on a
271
+ // timer or on every keystroke. This is the piece a bring-your-own-UI
272
+ // consumer actually needs: without it, a custom toolbar has to guess when to
273
+ // re-read state, which is why TinyMCE exposes formatChanged().
274
+ //
275
+ // Returns an unsubscribe function. Call it when your component unmounts - a
276
+ // framework component that leaks the callback keeps firing into a dead tree.
277
+ HeadlessController.prototype.onFormatChange = function (commands, callback) {
278
+ var noop = function () { };
279
+ if (typeof callback !== "function") return noop;
280
+ // Fall back to the destroy-aware no-op rather than throwing when the
281
+ // core predates this API, so a consumer on an older rte.js degrades to
282
+ // "no live updates" instead of a crash on construction.
283
+ if (!this.editor || typeof this.editor.formatChanged !== "function") return noop;
284
+ var self = this;
285
+ var off = this.editor.formatChanged(commands, function (state, info) {
286
+ if (self._destroyed) return;
287
+ callback(state, info);
288
+ });
289
+ this._formatOffs = this._formatOffs || [];
290
+ this._formatOffs.push(off);
291
+ return function () {
292
+ try { off(); } catch (e) { /* ignore */ }
293
+ if (self._formatOffs) {
294
+ var ix = self._formatOffs.indexOf(off);
295
+ if (ix !== -1) self._formatOffs.splice(ix, 1);
296
+ }
297
+ };
298
+ };
299
+
269
300
  HeadlessController.prototype.focus = function () {
270
301
  try { this.editor.focus(); } catch (e) { /* ignore */ }
271
302
  return this;
@@ -274,6 +305,15 @@
274
305
  HeadlessController.prototype.destroy = function () {
275
306
  this._destroyed = true;
276
307
  this._handlers = {};
308
+ // Drop format subscriptions here too: _destroyed alone only silences the
309
+ // callback, it does not stop the core from evaluating the commands on
310
+ // every toolbar refresh for a controller nobody is using any more.
311
+ if (this._formatOffs) {
312
+ for (var fi = 0; fi < this._formatOffs.length; fi++) {
313
+ try { this._formatOffs[fi](); } catch (e) { /* ignore */ }
314
+ }
315
+ this._formatOffs = null;
316
+ }
277
317
  if (this._boundDoc && this._docListeners) {
278
318
  for (var i = 0; i < this._docListeners.length; i++) {
279
319
  try { this._boundDoc.removeEventListener(this._docListeners[i][0], this._docListeners[i][1], true); } catch (e) {}