@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.
Files changed (58) hide show
  1. package/migration/README.md +100 -0
  2. package/migration/cli.js +294 -0
  3. package/migration/index.d.ts +18 -0
  4. package/migration/index.js +242 -0
  5. package/migration/report-schema-v2.json +127 -0
  6. package/package.json +11 -1
  7. package/richtexteditor/CHANGELOG.md +326 -1
  8. package/richtexteditor/all_plugins.js +14959 -4832
  9. package/richtexteditor/all_plugins.min.js +62 -42
  10. package/richtexteditor/plugins/accessibilitychecker.js +8 -7
  11. package/richtexteditor/plugins/aitoolkit.css +17 -8
  12. package/richtexteditor/plugins/aitoolkit.js +8 -8
  13. package/richtexteditor/plugins/all_plugins.js +14959 -4832
  14. package/richtexteditor/plugins/all_plugins.min.js +62 -42
  15. package/richtexteditor/plugins/autocorrect.js +353 -0
  16. package/richtexteditor/plugins/bookmarkcard.js +12 -1
  17. package/richtexteditor/plugins/changecase.js +238 -0
  18. package/richtexteditor/plugins/comments.js +15 -28
  19. package/richtexteditor/plugins/contentminimap.js +6 -5
  20. package/richtexteditor/plugins/crossreference.js +408 -0
  21. package/richtexteditor/plugins/documentimport.js +915 -21
  22. package/richtexteditor/plugins/documentoutline.js +5 -4
  23. package/richtexteditor/plugins/docxexport.js +960 -0
  24. package/richtexteditor/plugins/draghandle.js +309 -0
  25. package/richtexteditor/plugins/footnotes.js +398 -0
  26. package/richtexteditor/plugins/formatpainter.js +298 -0
  27. package/richtexteditor/plugins/formattingmarks.js +246 -0
  28. package/richtexteditor/plugins/html2pdf.js +108 -4
  29. package/richtexteditor/plugins/imageeditor.js +37 -7
  30. package/richtexteditor/plugins/insertgallery.js +553 -170
  31. package/richtexteditor/plugins/keyboarda11y.js +316 -0
  32. package/richtexteditor/plugins/linenumbers.js +352 -0
  33. package/richtexteditor/plugins/linkchecker.js +396 -0
  34. package/richtexteditor/plugins/mentionplugin.js +4 -3
  35. package/richtexteditor/plugins/mergefields.js +328 -0
  36. package/richtexteditor/plugins/multilevellist.js +137 -0
  37. package/richtexteditor/plugins/pagination.js +505 -0
  38. package/richtexteditor/plugins/pdfexport.js +1137 -0
  39. package/richtexteditor/plugins/permanentpen.js +211 -0
  40. package/richtexteditor/plugins/revisionhistory.js +47 -34
  41. package/richtexteditor/plugins/rtlui.js +150 -0
  42. package/richtexteditor/plugins/sanitizer.js +486 -0
  43. package/richtexteditor/plugins/slashcommand.js +142 -17
  44. package/richtexteditor/plugins/spellcheck.js +6 -3
  45. package/richtexteditor/plugins/tableofcontents.js +353 -0
  46. package/richtexteditor/plugins/tabletools.js +342 -0
  47. package/richtexteditor/plugins/textdirection.js +313 -0
  48. package/richtexteditor/plugins/trackedchanges.js +37 -6
  49. package/richtexteditor/plugins/tui.image-editor/tui-code-snippet.min.js +1 -1
  50. package/richtexteditor/plugins/tui.image-editor/tui-color-picker.min.js +1 -1
  51. package/richtexteditor/plugins/tui.image-editor/tui-image-editor.min.js +1 -1
  52. package/richtexteditor/plugins/vendor/html2pdf.bundle.min.js +3 -0
  53. package/richtexteditor/plugins/watermark.js +214 -0
  54. package/richtexteditor/plugins/wordexport.js +0 -0
  55. package/richtexteditor/plugins/yjscollab.js +49 -11
  56. package/richtexteditor/rte-config.js +44 -44
  57. package/richtexteditor/rte.js +91 -63
  58. package/richtexteditor/rte_theme_default.css +462 -384
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+
3
+ var SOURCES = ["tinymce", "ckeditor", "froala", "quill", "tiptap", "lexical"];
4
+ var COMMANDS = {
5
+ bold: "bold", italic: "italic", underline: "underline", strike: "strikethrough", strikethrough: "strikethrough",
6
+ bullist: "insertunorderedlist", bulletedlist: "insertunorderedlist", bulletlist: "insertunorderedlist",
7
+ numlist: "insertorderedlist", numberedlist: "insertorderedlist", orderedlist: "insertorderedlist",
8
+ link: "insertlink", insertlink: "insertlink", image: "insertimage", imageupload: "insertimage", insertimage: "insertimage",
9
+ table: "inserttable", inserttable: "inserttable", blockquote: "blockquote", code: "code", codesample: "insertcode",
10
+ undo: "undo", redo: "redo", alignleft: "justifyleft", aligncenter: "justifycenter", alignright: "justifyright",
11
+ alignjustify: "justifyfull", removeformat: "removeformat", sourcecode: "htmlcode", fullscreen: "fullpage"
12
+ };
13
+
14
+ function assertSource(source) {
15
+ source = String(source || "").toLowerCase().replace(/[^a-z]/g, "");
16
+ if (source === "ckeditor5") { source = "ckeditor"; }
17
+ if (SOURCES.indexOf(source) < 0) { throw new TypeError("Unsupported migration source: " + source); }
18
+ return source;
19
+ }
20
+ function escapeHtml(value) { return String(value == null ? "" : value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); }
21
+ function safeHref(value) {
22
+ var href = String(value || "").trim();
23
+ return /^(?:https?:|mailto:|tel:|#|\/)/i.test(href) ? href : "";
24
+ }
25
+ function safeTarget(value) { value = String(value || ""); return /^(?:_blank|_self|_parent|_top)$/i.test(value) ? value.toLowerCase() : ""; }
26
+ function safeDimension(value) { var number = Number(value); return Number.isFinite(number) && number > 0 ? String(Math.round(number)) : ""; }
27
+ function positiveSpan(value) { var number = Number(value); return Number.isInteger(number) && number > 1 ? String(number) : ""; }
28
+ function linkAttributes(attrs) {
29
+ attrs = attrs || {}; var output = "", target = safeTarget(attrs.target), rel = String(attrs.rel || "").trim();
30
+ if (target) output += " target=\"" + target + "\"";
31
+ if (rel && /^[a-z\s-]+$/i.test(rel)) output += " rel=\"" + escapeHtml(rel) + "\"";
32
+ return output;
33
+ }
34
+ function imageAttributes(attrs) {
35
+ attrs = attrs || {}; var output = " alt=\"" + escapeHtml(attrs.alt || "") + "\"", width = safeDimension(attrs.width), height = safeDimension(attrs.height);
36
+ if (attrs.title) output += " title=\"" + escapeHtml(attrs.title) + "\"";
37
+ if (width) output += " width=\"" + width + "\"";
38
+ if (height) output += " height=\"" + height + "\"";
39
+ return output;
40
+ }
41
+ function unique(values) { return values.filter(function (value, index) { return values.indexOf(value) === index; }); }
42
+ function recordFinding(inventory, kind, name, path) {
43
+ var existing = inventory.filter(function (item) { return item.kind === kind && item.name === name; })[0];
44
+ if (existing) { existing.count += 1; if (path && existing.paths.indexOf(path) < 0) existing.paths.push(path); }
45
+ else inventory.push({ kind: kind, name: name, count: 1, paths: path ? [path] : [] });
46
+ }
47
+ function customConverter(options, source, kind, name) {
48
+ var sourceConverters = options && options.converters && options.converters[source];
49
+ var group = sourceConverters && sourceConverters[kind + "s"];
50
+ return group && Object.prototype.hasOwnProperty.call(group, name) && typeof group[name] === "function" ? group[name] : null;
51
+ }
52
+ function runCustomConverter(options, source, kind, name, value, context, warnings, applied) {
53
+ var converter = customConverter(options, source, kind, name), result;
54
+ if (!converter) return null;
55
+ result = converter(value, Object.assign({ source: source, kind: kind, name: name }, context || {}));
56
+ if (result == null) return null;
57
+ if (typeof result === "string") result = { html: result };
58
+ if (!result || typeof result.html !== "string") throw new TypeError("Custom " + source + " " + kind + " converter for " + name + " must return an HTML string or { html, warnings? }");
59
+ if (result.warnings != null) {
60
+ if (!Array.isArray(result.warnings) || result.warnings.some(function (warning) { return typeof warning !== "string"; })) throw new TypeError("Custom converter warnings must be an array of strings");
61
+ result.warnings.forEach(function (warning) { warnings.push(warning); });
62
+ }
63
+ recordFinding(applied, kind, name, context && context.path);
64
+ return result.html;
65
+ }
66
+ function flattenToolbar(value, output) {
67
+ output = output || [];
68
+ if (Array.isArray(value)) { value.forEach(function (item) { flattenToolbar(item, output); }); }
69
+ else if (value && typeof value === "object") {
70
+ if (Array.isArray(value.items)) { flattenToolbar(value.items, output); }
71
+ else if (value.header != null) { output.push("formatblock"); }
72
+ else if (value.list) { output.push(value.list === "ordered" ? "orderedlist" : "bulletlist"); }
73
+ } else if (typeof value === "string") { value.split(/[\s|,]+/).filter(Boolean).forEach(function (item) { output.push(item); }); }
74
+ return output;
75
+ }
76
+ function mapToolbar(value) {
77
+ var unknown = [], mapped = [];
78
+ flattenToolbar(value).forEach(function (raw) {
79
+ var key = String(raw).toLowerCase().replace(/[^a-z]/g, "");
80
+ if (!key || key === "separator") { return; }
81
+ if (COMMANDS[key]) { if (mapped.indexOf(COMMANDS[key]) < 0) { mapped.push(COMMANDS[key]); } }
82
+ else { unknown.push(String(raw)); }
83
+ });
84
+ return { commands: mapped, unmapped: unique(unknown) };
85
+ }
86
+ function migrateConfig(source, input) {
87
+ source = assertSource(source); input = input || {};
88
+ var config = {}, warnings = [], toolbarValue;
89
+ if (source === "tinymce") { toolbarValue = input.toolbar; config.placeholder = input.placeholder; config.height = input.height; config.width = input.width; if (input.readonly != null) config.readOnly = !!input.readonly; }
90
+ if (source === "ckeditor") { toolbarValue = input.toolbar && (input.toolbar.items || input.toolbar); config.placeholder = input.placeholder; config.contentLang = typeof input.language === "string" ? input.language : input.language && input.language.ui; }
91
+ if (source === "froala") { toolbarValue = input.toolbarButtons; config.placeholder = input.placeholderText; config.height = input.height || input.heightMin; config.maxLength = input.charCounterMax; if (input.editable === false) config.readOnly = true; }
92
+ if (source === "quill") { toolbarValue = input.modules && input.modules.toolbar; config.placeholder = input.placeholder; config.readOnly = !!input.readOnly; if (input.theme && input.theme !== "snow") warnings.push("Quill theme requires a RichTextEditor skin/CSS migration."); }
93
+ if (source === "tiptap") { toolbarValue = input.toolbar; config.placeholder = input.placeholder; config.readOnly = input.editable === false; if (input.extensions) warnings.push("Tiptap extensions require feature-by-feature mapping; inspect unmapped extensions manually."); }
94
+ if (source === "lexical") { toolbarValue = input.toolbar; config.readOnly = input.editable === false; config.contentLang = input.language; if (input.nodes) warnings.push("Lexical custom nodes require a serializer or custom RichTextEditor plugin."); }
95
+ Object.keys(config).forEach(function (key) { if (config[key] == null || config[key] === "") delete config[key]; });
96
+ var toolbar = mapToolbar(toolbarValue);
97
+ if (toolbar.commands.length) { config.toolbar = "custom"; config.toolbar_custom = "{" + toolbar.commands.join(",") + "}"; }
98
+ if (toolbar.unmapped.length) warnings.push("Unmapped toolbar items: " + toolbar.unmapped.join(", "));
99
+ return { source: source, config: config, mappedCommands: toolbar.commands, unmapped: toolbar.unmapped, warnings: warnings };
100
+ }
101
+
102
+ function applyMarks(text, attrs, warnings, context) {
103
+ var html = escapeHtml(text), href;
104
+ attrs = attrs || {};
105
+ if (attrs.code) html = "<code>" + html + "</code>";
106
+ if (attrs.bold) html = "<strong>" + html + "</strong>";
107
+ if (attrs.italic) html = "<em>" + html + "</em>";
108
+ if (attrs.underline) html = "<u>" + html + "</u>";
109
+ if (attrs.strike) html = "<s>" + html + "</s>";
110
+ href = safeHref(attrs.link); if (href) html = "<a href=\"" + escapeHtml(href) + "\"" + linkAttributes(attrs) + ">" + html + "</a>";
111
+ else if (attrs.link && warnings) warnings.push("Unsafe " + (context || "content") + " link URL was removed.");
112
+ return html;
113
+ }
114
+ function renderQuillLists(items) {
115
+ var roots = [], path = [];
116
+ function appendList(container, type) {
117
+ var list = { type: type, items: [] };
118
+ container.push(list);
119
+ return list;
120
+ }
121
+ items.forEach(function (item) {
122
+ var depth = Math.max(0, Math.floor(Number(item.indent) || 0));
123
+ if (depth > path.length) depth = path.length;
124
+ path.length = depth + 1;
125
+ var container = roots;
126
+ if (depth > 0) {
127
+ var parentList = path[depth - 1], parentItem = parentList && parentList.items[parentList.items.length - 1];
128
+ if (!parentItem) { depth = 0; path.length = 1; }
129
+ else container = parentItem.children;
130
+ }
131
+ var list = path[depth];
132
+ if (!list || list.type !== item.type || container.indexOf(list) < 0) {
133
+ list = appendList(container, item.type);
134
+ path[depth] = list;
135
+ path.length = depth + 1;
136
+ }
137
+ list.items.push({ html: item.html, checked: item.checked, children: [] });
138
+ });
139
+ function render(list) {
140
+ var tag = list.type === "ol" ? "ol" : "ul", listAttrs = list.type === "check" ? " data-type=\"taskList\"" : "";
141
+ return "<" + tag + listAttrs + ">" + list.items.map(function (item) {
142
+ var itemAttrs = list.type === "check" ? " data-type=\"taskItem\" data-checked=\"" + (item.checked ? "true" : "false") + "\"" : "";
143
+ return "<li" + itemAttrs + ">" + item.html + item.children.map(render).join("") + "</li>";
144
+ }).join("") + "</" + tag + ">";
145
+ }
146
+ return roots.map(render).join("");
147
+ }
148
+ function quillToHtml(delta, warnings, options, applied, unsupported) {
149
+ var ops = delta && Array.isArray(delta.ops) ? delta.ops : [], blocks = [], line = "", listItems = [];
150
+ function flushList() { if (listItems.length) { blocks.push(renderQuillLists(listItems)); listItems = []; } }
151
+ function flushLine(attrs) {
152
+ attrs = attrs || {};
153
+ if (attrs.list) { listItems.push({ type: attrs.list === "ordered" ? "ol" : attrs.list === "checked" || attrs.list === "unchecked" ? "check" : "ul", checked: attrs.list === "checked", indent: attrs.indent, html: line || "<br>" }); }
154
+ else { flushList(); var tag = attrs.header ? "h" + Math.max(1, Math.min(6, Number(attrs.header))) : attrs["code-block"] ? "pre" : attrs.blockquote ? "blockquote" : "p"; blocks.push("<" + tag + ">" + (line || "<br>") + "</" + tag + ">"); }
155
+ line = "";
156
+ }
157
+ ops.forEach(function (op, opIndex) {
158
+ if (typeof op.insert !== "string") {
159
+ var embed = op.insert || {}, imageUrl, videoUrl;
160
+ if (embed.image != null) { imageUrl = safeHref(embed.image); if (imageUrl) line += "<img src=\"" + escapeHtml(imageUrl) + "\"" + imageAttributes(op.attributes) + ">"; else warnings.push("Unsafe Quill image URL was removed."); }
161
+ else if (embed.formula != null) line += "<span class=\"rte-math-inline\" data-tex=\"" + escapeHtml(embed.formula) + "\">" + escapeHtml(embed.formula) + "</span>";
162
+ else if (embed.video != null) { videoUrl = safeHref(embed.video); if (videoUrl) line += "<a href=\"" + escapeHtml(videoUrl) + "\">" + escapeHtml(videoUrl) + "</a>"; else warnings.push("Unsafe Quill video URL was removed."); }
163
+ else {
164
+ var embedNames = Object.keys(embed), embedPath = "ops[" + opIndex + "].insert", handled = embedNames.length === 1 ? runCustomConverter(options, "quill", "embed", embedNames[0], embed[embedNames[0]], { operation: op, attributes: op.attributes || {}, path: embedPath + "." + embedNames[0] }, warnings, applied) : null;
165
+ if (handled != null) line += handled;
166
+ else { embedNames.forEach(function (name) { recordFinding(unsupported, "embed", name, embedPath + "." + name); }); warnings.push("Quill embed requires manual migration: " + embedNames.join(", ")); }
167
+ }
168
+ return;
169
+ }
170
+ var parts = op.insert.split("\n");
171
+ parts.forEach(function (part, index) { line += applyMarks(part, op.attributes, warnings, "Quill"); if (index < parts.length - 1) flushLine(op.attributes); });
172
+ });
173
+ if (line) flushLine({}); flushList(); return blocks.join("");
174
+ }
175
+ function tiptapNode(node, warnings, options, applied, unsupported, path) {
176
+ path = path || "$";
177
+ if (!node) return ""; if (node.type === "text") { var html = escapeHtml(node.text || ""); (node.marks || []).forEach(function (mark, markIndex) { var type = mark.type, href, handled, markPath = path + ".marks[" + markIndex + "]"; if (type === "bold") html = "<strong>" + html + "</strong>"; else if (type === "italic") html = "<em>" + html + "</em>"; else if (type === "underline") html = "<u>" + html + "</u>"; else if (type === "strike") html = "<s>" + html + "</s>"; else if (type === "code") html = "<code>" + html + "</code>"; else if (type === "link" && (href = safeHref(mark.attrs && mark.attrs.href))) html = "<a href=\"" + escapeHtml(href) + "\"" + linkAttributes(mark.attrs) + ">" + html + "</a>"; else if (type === "link") warnings.push("Unsafe Tiptap link URL was removed."); else if (["bold","italic","underline","strike","code"].indexOf(type) < 0) { handled = runCustomConverter(options, "tiptap", "mark", type, mark, { html: html, node: node, path: markPath }, warnings, applied); if (handled != null) html = handled; else { recordFinding(unsupported, "mark", type, markPath); warnings.push("Unsupported Tiptap mark: " + type); } } }); return html; }
178
+ var children = (node.content || []).map(function (child, index) { return tiptapNode(child, warnings, options, applied, unsupported, path + ".content[" + index + "]"); }).join("");
179
+ var tags = { doc: "", paragraph: "p", heading: "h" + Math.max(1, Math.min(6, Number(node.attrs && node.attrs.level || 1))), bulletList: "ul", orderedList: "ol", listItem: "li", blockquote: "blockquote", codeBlock: "pre", hardBreak: "br", horizontalRule: "hr", table: "table", tableRow: "tr", tableHeader: "th", tableCell: "td" };
180
+ if (node.type === "image") { var imageSrc = safeHref(node.attrs && node.attrs.src); if (!imageSrc && node.attrs && node.attrs.src) warnings.push("Unsafe Tiptap image URL was removed."); return imageSrc ? "<img src=\"" + escapeHtml(imageSrc) + "\"" + imageAttributes(node.attrs) + ">" : ""; }
181
+ if (node.type === "taskList") return "<ul data-type=\"taskList\">" + children + "</ul>";
182
+ if (node.type === "taskItem") return "<li data-type=\"taskItem\" data-checked=\"" + (node.attrs && node.attrs.checked ? "true" : "false") + "\">" + children + "</li>";
183
+ if (!(node.type in tags)) { var handledNode = runCustomConverter(options, "tiptap", "node", node.type, node, { childrenHtml: children, path: path }, warnings, applied); if (handledNode != null) return handledNode; recordFinding(unsupported, "node", node.type, path); warnings.push("Unsupported Tiptap node: " + node.type); return children; }
184
+ var tag = tags[node.type]; if (!tag) return children; if (tag === "br" || tag === "hr") return "<" + tag + ">"; var attrs = "";
185
+ if (tag === "td" || tag === "th") { var colspan = positiveSpan(node.attrs && node.attrs.colspan), rowspan = positiveSpan(node.attrs && node.attrs.rowspan); if (colspan) attrs += " colspan=\"" + colspan + "\""; if (rowspan) attrs += " rowspan=\"" + rowspan + "\""; }
186
+ return "<" + tag + attrs + ">" + children + "</" + tag + ">";
187
+ }
188
+ function lexicalNode(node, warnings, options, applied, unsupported, path) {
189
+ path = path || "$";
190
+ if (!node) return "";
191
+ if (node.type === "text") { var format = Number(node.format || 0), attrs = { bold: !!(format & 1), italic: !!(format & 2), strike: !!(format & 4), underline: !!(format & 8), code: !!(format & 16) }; return applyMarks(node.text || "", attrs); }
192
+ var children = (node.children || []).map(function (child, index) { return lexicalNode(child, warnings, options, applied, unsupported, path + ".children[" + index + "]"); }).join("");
193
+ if (node.type === "root") return children;
194
+ if (node.type === "link" || node.type === "autolink") { var href = safeHref(node.url); if (!href && node.url) warnings.push("Unsafe Lexical link URL was removed."); return href ? "<a href=\"" + escapeHtml(href) + "\"" + linkAttributes({ target: node.target, rel: node.rel }) + ">" + children + "</a>" : children; }
195
+ var tag = node.type === "heading" ? (/^h[1-6]$/.test(node.tag) ? node.tag : "h2") : ({ paragraph: "p", quote: "blockquote", list: node.listType === "number" ? "ol" : "ul", listitem: "li", linebreak: "br", code: "pre", table: "table", tablerow: "tr", tablecell: node.headerState ? "th" : "td" })[node.type];
196
+ if (!tag) { var handledNode = runCustomConverter(options, "lexical", "node", node.type, node, { childrenHtml: children, path: path }, warnings, applied); if (handledNode != null) return handledNode; recordFinding(unsupported, "node", node.type, path); warnings.push("Unsupported Lexical node: " + node.type); return children; }
197
+ if (tag === "br") return "<br>"; var attrs = "";
198
+ if (node.type === "list" && node.listType === "check") attrs += " data-type=\"taskList\"";
199
+ if (node.type === "listitem" && typeof node.checked === "boolean") attrs += " data-type=\"taskItem\" data-checked=\"" + (node.checked ? "true" : "false") + "\"";
200
+ if (tag === "td" || tag === "th") { var colspan = positiveSpan(node.colSpan), rowspan = positiveSpan(node.rowSpan); if (colspan) attrs += " colspan=\"" + colspan + "\""; if (rowspan) attrs += " rowspan=\"" + rowspan + "\""; }
201
+ return "<" + tag + attrs + ">" + children + "</" + tag + ">";
202
+ }
203
+ function convertContent(source, value, options) {
204
+ source = assertSource(source); var warnings = [], appliedConverters = [], unsupportedContent = [], html = "";
205
+ if (source === "quill") html = quillToHtml(value, warnings, options, appliedConverters, unsupportedContent);
206
+ else if (source === "tiptap") html = tiptapNode(value && value.type ? value : value && value.doc, warnings, options, appliedConverters, unsupportedContent, "$" );
207
+ else if (source === "lexical") html = lexicalNode(value && value.root ? value.root : value, warnings, options, appliedConverters, unsupportedContent, "$" );
208
+ else { if (typeof value !== "string") throw new TypeError(source + " content must be an HTML string"); html = cleanVendorHtml(source, value); }
209
+ return { source: source, html: html, warnings: unique(warnings), unsupportedContent: unsupportedContent, appliedConverters: appliedConverters };
210
+ }
211
+ function cleanVendorHtml(source, html) {
212
+ var patterns = source === "ckeditor" ? [/\sdata-list-item-id=("[^"]*"|'[^']*')/gi, /\sdata-ck-[\w-]+=("[^"]*"|'[^']*')/gi]
213
+ : source === "tinymce" ? [/\sdata-mce-[\w-]+=("[^"]*"|'[^']*')/gi]
214
+ : source === "froala" ? [/\sdata-fr-[\w-]+=("[^"]*"|'[^']*')/gi] : [];
215
+ patterns.forEach(function (pattern) { html = html.replace(pattern, ""); });
216
+ return html;
217
+ }
218
+ function readLegacyContent(source, editor) {
219
+ source = assertSource(source); if (!editor) throw new TypeError("A legacy editor instance is required");
220
+ if (source === "tinymce" && typeof editor.getContent === "function") return editor.getContent();
221
+ if (source === "ckeditor" && typeof editor.getData === "function") return editor.getData();
222
+ if (source === "froala" && editor.html && typeof editor.html.get === "function") return editor.html.get();
223
+ if (source === "quill" && typeof editor.getContents === "function") return editor.getContents();
224
+ if (source === "tiptap") return typeof editor.getJSON === "function" ? editor.getJSON() : editor.getHTML();
225
+ if (source === "lexical" && typeof editor.getEditorState === "function") return editor.getEditorState().toJSON();
226
+ throw new TypeError("The supplied " + source + " instance does not expose its expected content API");
227
+ }
228
+ function migrateEditor(source, legacyEditor, richTextEditor, options) {
229
+ var result = convertContent(source, readLegacyContent(source, legacyEditor), options);
230
+ if (!richTextEditor || typeof richTextEditor.setHTMLCode !== "function") throw new TypeError("RichTextEditor instance must expose setHTMLCode(html)");
231
+ richTextEditor.setHTMLCode(result.html); return result;
232
+ }
233
+
234
+ function createMigrationReport(source, options) {
235
+ options = options || {}; var configResult = migrateConfig(source, options.config || {}), contentResult = convertContent(source, options.content == null ? (assertSource(source) === "quill" ? { ops: [] } : assertSource(source) === "tiptap" ? { type: "doc", content: [] } : assertSource(source) === "lexical" ? { root: { type: "root", children: [] } } : "") : options.content, { converters: options.converters });
236
+ var warnings = unique(configResult.warnings.concat(contentResult.warnings));
237
+ var blockers = warnings.filter(function (warning) { return /Unsafe |Unsupported |requires manual migration/i.test(warning); });
238
+ var manualReview = ["Custom plugins and schemas", "Collaboration, comments, and tracked changes", "Uploads and asset URLs", "Permissions and application data", "Representative saved output comparison"];
239
+ return { source: configResult.source, readyForCutover: blockers.length === 0, config: configResult.config, html: contentResult.html, mappedCommands: configResult.mappedCommands, unmapped: configResult.unmapped, unsupportedContent: contentResult.unsupportedContent, appliedConverters: contentResult.appliedConverters, blockers: blockers, warnings: warnings, manualReview: manualReview };
240
+ }
241
+
242
+ module.exports = { SOURCES: SOURCES.slice(), COMMANDS: Object.assign({}, COMMANDS), convertContent: convertContent, createMigrationReport: createMigrationReport, mapToolbar: mapToolbar, migrateConfig: migrateConfig, migrateEditor: migrateEditor, readLegacyContent: readLegacyContent };
@@ -0,0 +1,127 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "urn:richtexteditor:migration-report:2",
4
+ "title": "RichTextEditor migration report schema v2",
5
+ "oneOf": [
6
+ { "$ref": "#/$defs/singleReport" },
7
+ { "$ref": "#/$defs/batchReport" }
8
+ ],
9
+ "$defs": {
10
+ "generator": {
11
+ "type": "object",
12
+ "required": ["name", "version"],
13
+ "properties": {
14
+ "name": { "const": "@richscripts/richtexteditor" },
15
+ "version": { "type": "string", "minLength": 1 }
16
+ },
17
+ "additionalProperties": false
18
+ },
19
+ "converterModule": {
20
+ "oneOf": [
21
+ { "type": "null" },
22
+ {
23
+ "type": "object",
24
+ "required": ["label", "sha256", "hashVerifiedBeforeExecution"],
25
+ "properties": {
26
+ "label": { "type": "string", "minLength": 1 },
27
+ "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
28
+ "hashVerifiedBeforeExecution": { "type": "boolean" }
29
+ },
30
+ "additionalProperties": false
31
+ }
32
+ ]
33
+ },
34
+ "finding": {
35
+ "type": "object",
36
+ "required": ["kind", "name", "count", "paths"],
37
+ "properties": {
38
+ "kind": { "enum": ["embed", "node", "mark"] },
39
+ "name": { "type": "string", "minLength": 1 },
40
+ "count": { "type": "integer", "minimum": 1 },
41
+ "paths": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
42
+ },
43
+ "additionalProperties": false
44
+ },
45
+ "documentReport": {
46
+ "type": "object",
47
+ "required": ["source", "readyForCutover", "config", "html", "mappedCommands", "unmapped", "unsupportedContent", "appliedConverters", "blockers", "warnings", "manualReview"],
48
+ "properties": {
49
+ "id": { "type": "string" },
50
+ "source": { "type": "string", "minLength": 1 },
51
+ "readyForCutover": { "type": "boolean" },
52
+ "config": { "type": "object" },
53
+ "html": { "type": "string" },
54
+ "mappedCommands": { "type": "array", "items": { "type": "string" } },
55
+ "unmapped": { "type": "array", "items": { "type": "string" } },
56
+ "unsupportedContent": { "type": "array", "items": { "$ref": "#/$defs/finding" } },
57
+ "appliedConverters": { "type": "array", "items": { "$ref": "#/$defs/finding" } },
58
+ "blockers": { "type": "array", "items": { "type": "string" } },
59
+ "warnings": { "type": "array", "items": { "type": "string" } },
60
+ "manualReview": { "type": "array", "items": { "type": "string" } }
61
+ },
62
+ "additionalProperties": true
63
+ },
64
+ "singleReport": {
65
+ "allOf": [
66
+ { "$ref": "#/$defs/documentReport" },
67
+ {
68
+ "type": "object",
69
+ "required": ["schemaVersion", "generator", "converterModule"],
70
+ "properties": {
71
+ "schemaVersion": { "const": 2 },
72
+ "generator": { "$ref": "#/$defs/generator" },
73
+ "converterModule": { "$ref": "#/$defs/converterModule" }
74
+ }
75
+ }
76
+ ]
77
+ },
78
+ "unsupportedTotal": {
79
+ "type": "object",
80
+ "required": ["source", "kind", "name", "count", "documents"],
81
+ "properties": {
82
+ "source": { "type": "string" },
83
+ "kind": { "enum": ["embed", "node", "mark"] },
84
+ "name": { "type": "string" },
85
+ "count": { "type": "integer", "minimum": 1 },
86
+ "documents": { "type": "integer", "minimum": 1 }
87
+ },
88
+ "additionalProperties": false
89
+ },
90
+ "batchReport": {
91
+ "type": "object",
92
+ "required": ["schemaVersion", "generator", "converterModule", "readyForCutover", "summary", "reports"],
93
+ "properties": {
94
+ "schemaVersion": { "const": 2 },
95
+ "generator": { "$ref": "#/$defs/generator" },
96
+ "converterModule": { "$ref": "#/$defs/converterModule" },
97
+ "readyForCutover": { "type": "boolean" },
98
+ "summary": {
99
+ "type": "object",
100
+ "required": ["documents", "readyDocuments", "blockedDocuments", "unsupportedTotals", "unsupportedByDocument"],
101
+ "properties": {
102
+ "documents": { "type": "integer", "minimum": 1 },
103
+ "readyDocuments": { "type": "integer", "minimum": 0 },
104
+ "blockedDocuments": { "type": "integer", "minimum": 0 },
105
+ "unsupportedTotals": { "type": "array", "items": { "$ref": "#/$defs/unsupportedTotal" } },
106
+ "unsupportedByDocument": {
107
+ "type": "array",
108
+ "items": {
109
+ "type": "object",
110
+ "required": ["id", "source", "items"],
111
+ "properties": {
112
+ "id": { "type": "string" },
113
+ "source": { "type": "string" },
114
+ "items": { "type": "array", "items": { "$ref": "#/$defs/finding" } }
115
+ },
116
+ "additionalProperties": false
117
+ }
118
+ }
119
+ },
120
+ "additionalProperties": false
121
+ },
122
+ "reports": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/documentReport" } }
123
+ },
124
+ "additionalProperties": false
125
+ }
126
+ }
127
+ }
package/package.json CHANGED
@@ -1,14 +1,19 @@
1
1
  {
2
2
  "name": "@richscripts/richtexteditor",
3
3
  "publishConfig": { "access": "public" },
4
- "version": "2.4.0",
4
+ "version": "2.4.1",
5
5
  "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 React, Vue, and server-safe conversion helpers.",
6
6
  "main": "./richtexteditor/rte.js",
7
+ "bin": {
8
+ "rte-migration-report": "./migration/cli.js"
9
+ },
7
10
  "scripts": {
8
11
  "test:server": "node tests/run-all.test.js",
9
12
  "test:admin": "node tests/run-all.test.js",
10
13
  "test": "node tests/run-all.test.js",
11
14
  "smoke:pack": "node tests/smoke-pack.js",
15
+ "artifact:migration": "node scripts/build-migration-fidelity-artifact.js",
16
+ "artifact:migration:runtime": "npm --prefix vendor-runtime-tests ci && npm --prefix vendor-runtime-tests run generate && node scripts/build-migration-fidelity-artifact.js",
12
17
  "guard:protected": "node tests/guard-protected-bundle.js",
13
18
  "prepublishOnly": "node tests/guard-protected-bundle.js"
14
19
  },
@@ -39,6 +44,10 @@
39
44
  "types": "./headless/index.d.ts",
40
45
  "default": "./headless/index.js"
41
46
  },
47
+ "./migration": {
48
+ "types": "./migration/index.d.ts",
49
+ "default": "./migration/index.js"
50
+ },
42
51
  "./server": {
43
52
  "types": "./server/index.d.ts",
44
53
  "default": "./server/index.js"
@@ -60,6 +69,7 @@
60
69
  "headless",
61
70
  "index.d.ts",
62
71
  "integrations",
72
+ "migration",
63
73
  "react",
64
74
  "richtexteditor",
65
75
  "server",