BoxLang 🚀 A New JVM Dynamic Language Learn More...
A Preside CMS extension that replaces the legacy CKEditor 4 rich text editor with a modern Tiptap (v3) editor — install it and every existing form, renderer, picker and piece of stored content keeps working unchanged.
Preside's admin interface ships with a vendored copy of CKEditor 4, which reached end-of-life in June 2023 and no longer receives security patches. The editor is deeply woven into Preside — content tokens, picker dialogs, form validation, dirty-form tracking, front-end inline editing — which makes upgrading it in place a daunting task.
This extension takes a different approach: it swaps the editor underneath Preside's integration layer. It provides a Tiptap v3 editor behind a facade that reproduces the exact JavaScript contract Preside expects from CKEditor, and it delivers itself through Preside's Sticker asset system — so no core Preside file is edited (the same override mechanism as preside-ext-jsmodern).
| Legacy | Replaced by |
|---|---|
| CKEditor 4.x (vendored, EOL) | Tiptap v3 (ProseMirror-based, actively maintained) |
preside.richeditor.js
(PresideRichEditor) | CKEditor-API-compatible facade over Tiptap |
| CKEditor toolbar PNG icons | Inline SVG icons (Tabler Icons, MIT) |
| CKEditor bundled language packs | Preside resource
bundle (i18n/tiptap.properties), translated
server-side per admin-user locale |
?v= query-string cache
busting | Content-hashed asset filenames
(facade.<hash>.min.js), resolved dynamically
by the Sticker bundle |
Nothing else changes. Editors get a modern editing surface; developers keep every existing dependency, form definition and content renderer.
New niceties on top, each of which can be switched off (see Configuration):
The editor chrome is also fully localisable — add a
tiptap_<lang>.properties resource-bundle override
to translate every toolbar tooltip, dialog, outline and footer label.
Everything the extension adds is switched off through the
same defaultConfigs structure CKEditor already
used — no new settings namespace:
defaultConfigs key |
Default | Set to false to remove |
|---|---|---|
outline
| on | the document outline navigator (heading rail + hover panel) |
darkMode
| on | the light / dark mode toggle |
wordcount
| on | the footer status bar (word / character counts, reading time) |
Site-wide — in your site's (or another extension's) config/Config.cfc:
settings.ckeditor.defaults.defaultConfigs.outline = false;
settings.ckeditor.defaults.defaultConfigs.darkMode = false;
settings.ckeditor.defaults.defaultConfigs.wordcount = false;
Per field — customDefaultConfigs takes a
struct, so pass it where you render the control (core's
formcontrols/richeditor view serialises it into data-custom-default-configs):
renderFormControl(
name = "body"
, type = "richeditor"
, customDefaultConfigs = { outline = false }
);
In a hand-written view or template, the same thing as markup:
<textarea name="body" class="richeditor"
data-custom-default-configs='{"outline":false}'></textarea>
Per-field values are layered over the site-wide ones, so a field can opt out of something the site leaves on (and vice versa).
The extension reproduces Preside's editor integration contract exactly:
{{image:…:image}},
{{attachment:…:attachment}}, {{widget🆔
cfg:widget}} content tokens and {{link:…}} /
{{asset:…}} / {{custom:…}} link hrefs
round-trip byte-identically, so all server-side
renderers, forms and AJAX endpoints keep working — and you can
uninstall at any time with no content migration.onDialogEvent protocol. Custom link types and picker
categories work unchanged.PresideRichEditor constructor, the
CKEDITOR.instances[name] registry
(getData/setData/initialdata),
the ckeditorinstance jQuery-data hook, and the instance
event API — so AJAX form submit, jquery.validate, dirty-form
tracking, quick-add modals and front-end inline editing (including
its keyboard shortcuts) all keep working.settings.ckeditor.defaults (stylesheets, min/max
height, autoParagraph, enterMode), format_tags, the
stylesheet-driven Styles dropdown
(stylesheetParser_validSelectors), and paste filtering
(disallowedContent / pasteFromWordDisallow).settings.ckeditor.defaults.configFile (or
per-field customConfig) is loaded and executed exactly
as CKEditor would: its CKEDITOR.editorConfig( config )
output becomes the base configuration, overridden by anything set
per instance — including client-side
config.toolbar_<name> named-toolbar definitions,
format_tags, enterMode,
contentsCss and content filtering. The only keys
without meaning in Tiptap are CKEditor plugin-loading directives
(extraPlugins / removePlugins), which are
ignored harmlessly.Preside registers every extension's assets/ directory as
a Sticker bundle after the core bundle, and re-declaring an
asset id lets the last declaration win. This extension re-declares:
ckeditor → the Tiptap vendor bundle (window.PresideTiptap)tiptap-facade → the facade that re-assigns window.PresideRichEditor
tiptap-css → editor chrome / content stylesThe facade loads after /js/admin/presidecore/ (so it
overwrites the CKEditor implementation of
PresideRichEditor) but before the scripts that
instantiate editors — ordering enforced by Sticker
.after() rules. A single view override
(views/admin/layout/ckEditorJs.cfm) adds the two extra includes.
Inside the editor, Preside-specific behaviour is implemented as Tiptap extensions: content tokens become atom nodes that carry the original token text verbatim (guaranteeing byte fidelity), links/anchors are custom marks using Preside's own link serialization, and an output normaliser keeps saved markup consistent with what CKEditor produced — so re-saving a CKEditor-era document produces clean diffs, not churn.
box install preside-ext-tiptap
Reload the application. Rich-editor fields throughout the admin (and front-end inline editing) now use Tiptap.
To roll back, uninstall the extension — Preside falls straight back to its vendored CKEditor, and because stored content never changed, nothing else needs doing.
npm install
npm run build # → assets/dist/ (committed)
npm run watch # rebuild on change
src/ holds the editor source (esbuild input); it is not
shipped in the installed package. assets/dist/ is
committed so no build is needed to use the extension.Architecture.md documents the three layers (core
Tiptap, the Preside Tiptap extensions, and the
CKEditor-compatibility facade) with load-order and data-flow diagrams.
harness/ is a standalone browser harness — no CFML, no
database, no Preside boot. It serves both editors side by side with
Preside's server dependencies (pickers, preview renderers,
cfrequest data) mocked:
cd harness
node server.mjs # http://localhost:8700
(The CKEditor comparison pane needs a Preside checkout — set
PRESIDE_ASSETS=<Preside-CMS>/system/assets if it
isn't a sibling directory.)
/ — CKEditor 4 and Tiptap side by side, identical
markup and content/fidelity.html — a CKEditor-vs-Tiptap
persistence-fidelity matrix (does saved output round-trip
byte-stably through each editor?)/test-realworld.html — self-running assertions where
every case is a CKEditor customisation pattern taken verbatim from
real Preside sites: named toolbars, toolbars full of CKEditor
buttons with no Tiptap analogue, stylesSet / stylesheet
appends, and custom CKEditor config files (including the stock core /ckeditorExtensions/config.js)node harness/test-link-serialization.mjs — unit tests
for the link href ↔ {{link|asset|custom}} token contractSee harness/README.md for the full tour. For end-to-end
verification in a real Preside admin, the flows worth exercising are
the link, image/asset and widget pickers (insert and
re-open to edit) — these are the historically fragile paths.
MIT © Pixl8 Group.
Toolbar icons are Tabler Icons (MIT, © Paweł Kuna), inlined as SVG strings — the extension ships no image assets and no CKEditor-derived artwork.
$
box install preside-ext-tiptap