@richscripts/richtexteditor 2.11.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.
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.11.
|
|
6
|
+
"version": "2.11.1",
|
|
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": {
|
|
@@ -5,10 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
if (!window.RTE_DefaultConfig) window.RTE_DefaultConfig = { };
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 = "
|
|
956
|
+
dialoginner.style.padding = "14px";
|
|
850
957
|
if (selectedText) {
|
|
851
958
|
var preview = document.createElement("div");
|
|
852
|
-
preview.
|
|
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.
|
|
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.
|
|
971
|
+
textarea.className = "rte-comment-dialog-input";
|
|
863
972
|
dialoginner.appendChild(textarea);
|
|
864
973
|
var btnRow = document.createElement("div");
|
|
865
|
-
btnRow.
|
|
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.
|
|
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
|
-
|
|
989
|
+
var safeComment = comment.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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 + '">💬 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) {}
|