Better Rich Text Buy licence

Migrating

On this page
  1. Quick start
  2. TinyMCE options
  3. TinyMCE toolbar
  4. TinyMCE plugins
  5. Froala options
  6. Froala toolbar
  7. Froala plugins
  8. Events
  9. Content
  10. Uploads
  11. Facade
  12. Limitations

@better-rich-text/migrate moves an existing TinyMCE or Froala integration across. It does three jobs:

  • Translates an editor configuration object into createRichTextEditor options, with a warning for everything it could not map.

  • Cleans HTML saved by those editors (bookmarks, helper attributes, vendor classes) so it loads as plain semantic content.

  • Offers a small tinymce-shaped facade, so an application can switch editors before rewriting every call site.

The package reads public option names and stored markup only. It does not import or bundle either editor.

Quick start

TypeScript
import { createRichTextEditor, standardPreset } from '@better-rich-text/core';
import { fromTinyMceConfig } from '@better-rich-text/migrate';

const result = fromTinyMceConfig({
  selector: '#body',
  plugins: 'lists link image table code',
  toolbar: 'undo redo | blocks | bold italic | bullist numlist | link image table | code',
  height: 400,
  menubar: false,
});

for (const warning of result.warnings) console.warn(warning.option, warning.message);
const editor = createRichTextEditor('#body', result.options);

fromFroalaConfig(config) works the same way. Both return:

FieldContents
options Options for createRichTextEditor: toolbar, mode, sizes, placeholder, read-only, locale, plugin config, and plugins (always including migrationPlugin()).
requires Package exports the configuration needs, with inStandardPreset telling you whether an import is needed at all.
config Plugin configuration keyed by plugin id (fonts, sizes, line heights, palette, character limit). Also copied into options.config.
css content_style or iframeStyle, scoped to .brt-content. Add it to the page.
imageUpload An upload provider that calls your existing upload endpoint or handler.
contentPolicy Element and attribute rules from valid_elements, htmlRemoveTags and similar options.
warnings { option, severity, message, suggestion? } for every option that changed meaning or was left out.
unsupported Toolbar buttons and plugins with no equivalent.
code TypeScript that builds the same editor, with the right imports.

The translator imports no plugin packages, so it does not grow your bundle. To get ready-to-use plugins and features lists, pass the objects you import:

TypeScript
import { documentsFeature, documentsPlugin } from '@better-rich-text/plugin-documents';
import { createImagePlugin } from '@better-rich-text/plugin-image';

const result = fromTinyMceConfig(config, {
  plugins: { documentsPlugin, createImagePlugin },
  features: { documentsFeature },
  preset: standardPreset,
});
// result.options.preset now uses an image plugin wired to your upload endpoint.

TinyMCE options

TinyMCEBetter Rich TextNotes
selector, target First argument of createRichTextEditor One editor per element.
plugins Plugins and features See TinyMCE plugins.
toolbar, toolbar1…toolbarN toolbar Strings, arrays of rows and { name, items } groups are all read. false hides the toolbar.
toolbar_mode toolbarMode wrap stays wrap; floating, sliding and scrolling become overflow.
toolbar_sticky None Give .brt-toolbar position: sticky in your CSS.
fixed_toolbar_container toolbarContainer Pass the element.
menubar, menu menubar, menu Needs menubarFeature. Layout strings and { title, items } menus are kept; TinyMCE item names work. false hides the bar.
contextmenu contextmenu Needs contextMenuFeature. Section names are kept (editimage joins image); false keeps the browser menu.
height, min_height, max_height height, minHeight, maxHeight Pixels only. With the autoresize plugin and no height, height: 'auto'.
width None Size the container with CSS.
statusbar statusBar false hides it. Word counts are in the status bar, so wordcount needs nothing.
inline mode: 'inline'
readonly, disabled readOnly
placeholder placeholder
browser_spellcheck spellcheck
auto_focus autofocus
language locale pt_BR becomes pt-BR. Load a built-in language from @better-rich-text/locales (Localisation) or your own messages with registerMessages.
skin, skin_url skin Pick one of the built-in skins.
content_css None URLs are listed in a warning. Scope their rules to .brt-content (scopeCss helps).
content_style css Scoped to .brt-content; body and html selectors map to the content element. @import and @font-face are removed and reported.
font_family_formats, font_formats config.textStyle.fontFamilies Label=stack; pairs. Invalid stacks are reported.
font_size_formats, fontsize_formats config.textStyle.fontSizes Points; px values are multiplied by 0.75 and em by 12.
line_height_formats config.textStyle.lineHeights
color_map, color_map_foreground, color_map_background config.textStyle.palette Flat [hex, label, …] lists and [hex, label] pairs. One palette serves text and highlight.
images_upload_url, images_upload_base_path, images_upload_credentials imageUpload Posts the file as file and reads location, as TinyMCE does.
images_upload_handler imageUpload Your handler is called with the same blobInfo object. Both the promise form and the older callback form work.
file_picker_callback None Insert from your picker with editor.execute('image.insert', { src }).
valid_elements contentPolicy.allowedTags Markup is defined by the installed plugins; the policy only narrows it.
invalid_elements contentPolicy.deniedTags
extended_valid_elements, valid_styles, valid_classes, custom_elements None Write a plugin with node or mark specs for anything extra you need to keep.
paste_as_text None There is no plain-text paste mode; use the browser's paste-as-plain-text command.
paste_preprocess, paste_postprocess clipboard.pasteHtml filter
forced_root_block None Blocks are always paragraphs; Shift+Enter inserts a line break.
style_formats createStylesPlugin({ formats }) Reported with the formats to paste in. block, inline: 'span', classes, attributes (lang, dir) and groups (items) keep their meaning.
templates createTemplatesPlugin({ templates }) Rename title to name; {{date}}, {{user.name}} and host variables resolve on insert.
block_formats, formats None The blocks menu offers paragraph, headings and code.
quickbars_selection_toolbar selectionToolbarFeature(layout) The warning includes the mapped layout.
setup, init_instance_callback onReady, editor.on(...), plugins The facade still runs them.
save_onsavecallback createSavePlugin({ provider })
license_key, api_key, branding, promotion None Not needed.

Options not listed produce a warning with the message "Option not migrated".

TinyMCE toolbar

TinyMCEBetter Rich TextTinyMCEBetter Rich Text
undo, redo undo, redo bullist, numlist bulletList, orderedList
bold, italic, underline same checklist taskList
strikethrough strike outdent, indent same
superscript, subscript same link, unlink same
blocks, formatselect, styles blocks image image
fontfamily, fontselect fontFamily media media (plugin-media)
fontsize, fontsizeselect fontSize table, tableprops… table, tableMenu
lineheight lineHeight hr rule
forecolor textColor blockquote quote
backcolor, hilitecolor highlight removeformat clearFormat
alignleft…alignjustify alignLeft…alignJustify searchreplace search
align align charmap, emoticons characters
code source fullscreen fullscreen
codesample codeBlock ltr, rtl same
casechange changeCase formatpainter formatPainter
exportword, importword exportWord, importWord print, exportpdf exportPdf
fullpage documentProperties addcomment comment
anchor anchor pagebreak pageBreak
insertdatetime dateTime template, inserttemplate templates
addtemplate saveTemplate visualblocks, visualchars visualBlocks, visualChars
preview preview help help
editimage editImage a11ycheck a11yCheck
spellchecker proofing accordion details
footnotes footnote mergetags mergeTags
tableofcontents toc permanentpen formatPainter
math math

newdocument, copy, cut, paste, selectall, openlink and other unlisted buttons go into unsupported; the first five are menu bar items instead. Group separators (|) are kept.

TinyMCE plugins

TinyMCEBetter Rich Text
lists, advlist, checklist, link, autolink, image, table, advtable, code, codesample, searchreplace, charmap, emoticons, nonbreaking, hr, casechange, formatpainter, quickbars, paste, powerpaste, directionality, wordcount, fullscreen, textpattern In the standard preset
media, pageembed mediaPlugin, mediaFeature from @better-rich-text/plugin-media
exportword, importword, exportpdf, print, fullpage, fullpagehtml, inlinecss documentsPlugin, documentsFeature from @better-rich-text/plugin-documents
preview, help, contextmenu previewFeature, helpFeature, contextMenuFeature from @better-rich-text/ui
visualblocks, visualchars visualAidsFeature() from @better-rich-text/ui
anchor anchorPlugin, anchorFeature from @better-rich-text/plugin-anchor
pagebreak pageBreakPlugin, pageBreakFeature from @better-rich-text/plugin-page-break
template, advtemplate createTemplatesPlugin, templatesFeature from @better-rich-text/plugin-templates
insertdatetime dateTimePlugin, dateTimeFeature from @better-rich-text/plugin-datetime
importcss importCssFormats, createStylesPlugin, stylesFeature from @better-rich-text/plugin-styles
editimage, imagetools imageEditorPlugin, imageEditorFeature from @better-rich-text/plugin-image-editor
a11ychecker createA11yCheckPlugin from @better-rich-text/plugin-a11y-check
footnotes footnotesPlugin, footnotesFeature from @better-rich-text/plugin-footnotes
mergetags mergeTagsPlugin, mergeTagsFeature from @better-rich-text/plugin-merge-tags
accordion detailsPlugin, detailsFeature from @better-rich-text/plugin-details
linkchecker createLinkCheckPlugin from @better-rich-text/plugin-link-check
tableofcontents tocPlugin, tocFeature from @better-rich-text/plugin-toc
mentions createMentionsPlugin, mentionsFeature from @better-rich-text/plugin-mentions
permanentpen Sticky formatting (format.togglePen) in formatPainterPlugin
autocorrect createAutocorrectPlugin from @better-rich-text/plugin-typography
suggestededits suggestionsPlugin, suggestionsFeature from @better-rich-text/plugin-suggestions
tinydrive createAssetsPlugin from @better-rich-text/plugin-assets (connect an AssetSource)
math mathPlugin, mathFeature from @better-rich-text/plugin-math
autosave, save createSavePlugin from @better-rich-text/plugin-save
tinymcespellchecker proofingPlugin from @better-rich-text/plugin-proofing
comments, tinycomments commentsPlugin, commentsFeature from @better-rich-text/plugin-comments
revisionhistory createRevisionsPlugin from @better-rich-text/plugin-revisions
ai createAiPlugin and aiFeature from @better-rich-text/plugin-ai (needs a model endpoint on your server)
markdown parseMarkdown and serializeMarkdown from @better-rich-text/format-markdown

Froala options

FroalaBetter Rich TextNotes
toolbarButtons toolbar Flat arrays (`
toolbarButtonsMD, SM, XS None The toolbar overflows into a menu on narrow screens.
pluginsEnabled Plugins and features See Froala plugins.
height, heightMin, heightMax height, minHeight, maxHeight Pixels only.
placeholderText placeholder
toolbarInline mode: 'inline' Add toolbarContainer for a fixed toolbar, or rely on the selection toolbar.
toolbarSticky, toolbarBottom None Use CSS or toolbarContainer.
iframe None The editor does not use an iframe.
iframeStyle css Scoped to .brt-content.
iframeStyleFiles None Scope the stylesheet rules to .brt-content.
fullPage Documents plugin Full-page HTML keeps <head> metadata through the documents plugin.
language locale
direction None Direction is stored per paragraph; set dir on the container.
charCounterMax config.limits.maxCharacters Adds limitsPlugin to requires.
spellcheck, autofocus spellcheck, autofocus
enter None Enter always makes paragraphs; a warning is raised for ENTER_BR and ENTER_DIV.
fontFamily config.textStyle.fontFamilies Keys are stacks, values are labels.
fontSize with fontSizeUnit config.textStyle.fontSizes Pixel sizes become points (×0.75).
lineHeights config.textStyle.lineHeights
colorsText, colorsBackground config.textStyle.palette REMOVE entries are skipped.
imageUploadURL, imageUploadParam, imageUploadParams, imageUploadMethod, imageMaxSize, imageAllowedTypes imageUpload Same request; the address is read from link.
imageUploadToS3, fileUploadURL, videoUploadURL None Write an UploadProvider; embed videos by URL with the media plugin.
htmlAllowedTags contentPolicy.allowedTags
htmlRemoveTags, pasteDeniedTags contentPolicy.deniedTags
pasteDeniedAttrs contentPolicy.deniedAttributes
htmlAllowedAttrs, htmlAllowedStyleProps, pasteAllowedStyleProps, htmlUntouched None Markup is always parsed through the schema.
pastePlain, wordPasteModal, wordPasteKeepFormatting None Word cleanup is automatic. There is no plain-text paste mode.
events onChange, onReady, editor.on(...) See Events.
key, attribution None Not needed.

Froala toolbar

FroalaBetter Rich TextFroalaBetter Rich Text
bold, italic, underline same formatOL, formatOLSimple orderedList
strikeThrough strike formatUL bulletList
subscript, superscript same paragraphFormat blocks
fontFamily, fontSize, lineHeight same quote quote
textColor textColor insertLink link
backgroundColor highlight insertImage image
clearFormatting clearFormat insertVideo, insertEmbed media
alignLeft…alignJustify, align same insertTable table
outdent, indent same insertHR rule
emoticons, specialCharacters characters html source
undo, redo, fullscreen same print, getPDF exportPdf
findReplaceButton search export_to_word exportWord
codeSnippet codeBlock import_from_word importWord

| inlineClass, inlineStyle, paragraphStyle | styles | | help | help | | insertFile, insertFiles, imageManager | assets | | insertAnchor | anchor | | pageBreak | pageBreak | | spellChecker | proofing | | trackChanges | suggest | | | |

selectAll, markdown, save and fontAwesome go into unsupported.

Froala plugins

FroalaBetter Rich Text
align, colors, fontFamily, fontSize, lineHeight, link, url, lists, paragraphFormat, quote, table, image, codeView, codeSnippet, emoticons, specialCharacters, findReplace, wordPaste, fullscreen, help, quickInsert, charCounter, draggable, lineBreaker, entities In the standard preset
video, embedly mediaPlugin, mediaFeature
print, exportToWord, importFromWord documentsPlugin, documentsFeature
save createSavePlugin
spellChecker proofingPlugin
markdown @better-rich-text/format-markdown
filesManager, imageManager createAssetsPlugin (connect an AssetSource)
imageTUI imageEditorPlugin, imageEditorFeature
inlineClass, inlineStyle, paragraphStyle createStylesPlugin, stylesFeature
trackChanges suggestionsPlugin, suggestionsFeature
pageBreak pageBreakPlugin, pageBreakFeature
file, fontAwesome, forms, filestack, trimVideo, editInPopup Not supported

Events

TinyMCEFroalaBetter Rich Text
init initialized onReady option
change, input contentChanged onChange option or editor.on('change', …)
NodeChange, SelectionChange click, keyup editor.on('selectionChange', …)
focus, blur focus, blur DOM focusin and focusout on instance.view.dom
remove destroy editor.on('destroy', …)
PastePreProcess paste.beforeCleanup clipboard.pasteHtml filter hook

Content

HTML saved by either editor can carry editor-only markup. cleanEditorHtml removes it and leaves the content:

TypeScript
import { cleanEditorHtml } from '@better-rich-text/migrate';

const { html, source, changes } = cleanEditorHtml(storedHtml);
// source: 'tinymce' | 'froala' | 'unknown'; changes: what was removed or converted

It is a string-to-string function with no DOM, so it can run in a batch job over stored records. HTML without vendor markup comes back unchanged.

SourceCleanup
TinyMCE Removes selection bookmarks (data-mce-type="bookmark", mce_SELRES_*, mce_marker), data-mce-bogus elements and helper <br>s. Restores src, href and style from data-mce-src, data-mce-href and data-mce-style, then drops every data-mce-* attribute. Removes mce-* classes and unwraps mce-nbsp-wrap spans. Turns <!-- pagebreak --> and img.mce-pagebreak into <hr>, or into page breaks when plugin-page-break is installed. Keeps tox-checklist lists, which load as task lists.
Froala Removes fr-marker spans and the "Powered by Froala" paragraph. Restores fr-original-style and fr-original-class. Converts image classes fr-dib and fr-fil/fr-fir to centred, left and right alignment. Replaces fr-video embeds with a link to the video. Turns fr-alternate-rows into banded rows. Unwraps fr-view and fr-element wrappers and emoticon spans. Removes the remaining fr-* classes and reports visual-only ones (rounded, bordered, shadow, dashed borders).
Both Removes contenteditable attributes, zero-width marker characters, scripts and empty or attribute-less spans.

To clean content as it is loaded and pasted, add the plugin. It hooks the parser: html.parse.input cleans string input, and html.parse.element handles HTML the browser has already parsed (as setHTML and paste do). After a parse that changed something it fires the migrate.cleaned action.

TypeScript
import { migrationPlugin } from '@better-rich-text/migrate';

const editor = createRichTextEditor('#body', { plugins: [migrationPlugin()], content: storedHtml });
editor.editor.hooks.addAction('migrate.cleaned', ({ source, changes }) => console.info(source, changes));

contentPolicyPlugin({ allowedTags, deniedTags, deniedAttributes }) applies element rules before the schema parser. Denied tags are removed with their content; tags outside the allow list are flattened so their text stays.

Uploads

The translators wrap existing upload endpoints so the server does not change:

TypeScript
import { createImagePlugin } from '@better-rich-text/plugin-image';
import { tinyMceHandlerProvider, urlUploadProvider } from '@better-rich-text/migrate';

// TinyMCE images_upload_handler, unchanged.
const fromHandler = createImagePlugin({ provider: tinyMceHandlerProvider(images_upload_handler) });

// A Froala endpoint that answers { link }.
const fromUrl = createImagePlugin({
  provider: urlUploadProvider({ url: '/upload_image', field: 'file', params: { folder: 'posts' }, responseKey: 'link' }),
});

Replace the image plugin in your preset with the new one (the generated code shows how).

Facade

createTinyMceShim gives a tinymce-shaped object for an incremental switch:

TypeScript
import { createRichTextEditor } from '@better-rich-text/core';
import { createTinyMceShim } from '@better-rich-text/migrate';

const tinymce = createTinyMceShim({ create: createRichTextEditor });
const [editor] = await tinymce.init({ selector: '#body', plugins: 'lists link', toolbar: 'bold italic | bullist' });
editor.setContent('<p>Hello</p>');
editor.execCommand('Bold');
editor.on('change', () => save(editor.getContent()));
FacadeBehaviour
tinymce.init(config) Translates the config and creates one editor per matching element. Runs setup and init_instance_callback, then fires init.
tinymce.get(), get(id), get(index), activeEditor, remove() As in TinyMCE.
getContent(), getContent({ format: 'text' }), setContent, insertContent HTML in and out through the schema.
execCommand Bold, Italic, Underline, Strikethrough, Superscript, Subscript, InsertUnorderedList, InsertOrderedList, JustifyLeft/Center/Right/Full, Indent, Outdent, Undo, Redo, RemoveFormat, Unlink, CreateLink, ForeColor, HiliteColor, FormatBlock, InsertHorizontalRule, mceBlockQuote, mceInsertContent, mceSetContent, SelectAll. Unknown commands return false.
on, off, fire change and dirty come from the editor; focus, blur, input, key, mouse and clipboard events come from the editable element.
isDirty, setDirty, save save writes the HTML back to a textarea target.
mode.set('readonly' | 'design'), getBody, getElement, focus, remove As in TinyMCE.

The facade covers common integration code. TinyMCE plugins, UI registry calls (editor.ui.registry), editor.dom, editor.selection and editor.formatter are not emulated; use facade.instance to reach the Better Rich Text editor.

Limitations

  • Custom formats (style_formats, paragraphStyle, inlineClass) are reported for you to pass to createStylesPlugin; they are not applied automatically. Custom toolbar buttons and menu items are not converted. Rewrite them as toolbar items and custom plugins as Better Rich Text plugins.

  • Option values given as functions are only used for upload handlers, setup and init_instance_callback (the last two by the facade).

  • Allowed-element rules can only narrow what the installed plugins accept; they cannot add new elements.

  • In the browser parse path, Froala video embeds are dropped rather than turned into links. Run cleanEditorHtml on stored content first to keep the links.

  • Comments, revisions and suggestions stored inside a vendor service are not recovered from HTML. Export them from the service and import them as records.