mod_editor_tiptap
Adds a rich-text editor based on Tiptap and ProseMirror to Zotonic forms. It provides headings, emphasis, lists, links, Zotonic media, tables, undo/redo, and full-window editing through a plain JavaScript interface.
Setup
Enable mod_editor_tiptap instead of mod_editor_tinymce on the site's Modules
page. It depends on mod_base and mod_admin, but does not require TinyMCE to be
enabled or load any TinyMCE templates or JavaScript. Existing _editor.tpl
includes select this editor when the module is enabled.
{% include "_editor.tpl" id=id %}
<textarea id="body" name="body" class="z_editor-init">{{ id.body|escape }}</textarea>
The page must include Zotonic's normal JavaScript and {% script %} support.
Textareas marked do_zeditor can use the existing widget initialization instead.
The original textarea remains the form field; the editor synchronizes its value
before a wired or native form submission.
Editor lifecycle
Editors initialize near the viewport. Call z_editor.init() after showing a
language tab or otherwise revealing fields; the standard admin already does so.
Visited editors stay alive when tabs are hidden, preserving their undo history.
New textareas in AJAX fragments are discovered automatically, and editors whose
elements are removed are destroyed.
| JavaScript API | Behavior |
|---|---|
z_editor.init() | Discover editor textareas throughout the document. |
z_editor.add(root) | Discover editor textareas in the supplied element or container. |
z_editor.save(root) | Write changed editor content to textareas in that scope. |
z_editor.remove(root) | Save and destroy editors in that scope and cancel pending initialization. |
z_editor_tiptap.get(textareaId) | Return the mounted Tiptap editor, or undefined before initialization or after removal. |
The root argument accepts a DOM element, selector, or jQuery collection. Omitting
it selects the document. Before directly serializing a form, call
z_editor.save(form). Explicit removal followed by addition starts a new undo
history. Content edits emit the existing z:editorChange form notification.
Full-window editing preserves the instance; Escape returns to the normal view.
Profiles and extensions
The three built-in profiles are simple (basic text and links), normal
(formatting, lists, media and full-window editing), and advanced (all controls,
including tables, alignment and text direction). Admin pages select advanced
on the body element.
Select a profile with data-editor-profile on the textarea or any ancestor.
The nearest attribute wins; missing, empty or unknown names use normal.
Field names and the old data-zeditor.config no longer select profiles.
<section data-editor-profile="normal">
<textarea name="body$en" class="z_editor-init"></textarea>
<textarea name="summary$en" class="z_editor-init" data-editor-profile="simple"></textarea>
</section>
Profiles are defined in _editor_tiptap_profiles.tpl, loaded before the editor
bundle. Override that template in your site using its simple, normal, and
advanced blocks. Each block contains JavaScript object properties:
{% overrules %}
{% block simple %}
toolbar: ['bold', 'italic', 'link', 'unlink']
{% endblock %}
A profile can also define extensions, paste handlers and custom buttons in the
same block. The optional window.z_editor_tiptap_config.default supplies shared
options; the selected profile is shallow-merged over it. Arrays are replaced,
not concatenated. Profiles are read at editor creation; changing attributes or
definitions does not rebuild mounted editors. There are no m.config settings.
The admin editor_profile template block can override the body-level default.
| Profile option | Purpose |
|---|---|
toolbar | Ordered array of control names; ` |
extensions | Additional Tiptap extensions, or a function receiving { Editor, Extension, Node, Mark } and returning an array. |
buttons | Custom controls keyed by toolbar name, each with label, optional Lucide icon data, and run(editor). |
transformPastedHTML(html) | Replace the default paste normalizer. |
onCreate(editor, textarea) | Run additional integration after mounting. |
Toolbar controls include undo, redo, format, bold, italic, underline,
strike, subscript, superscript, bulletList, orderedList, indent,
outdent, blockquote, codeBlock, alignLeft, alignCenter, alignRight,
ltr, rtl, horizontalRule, hardBreak, link, unlink, zlink, zmedia,
table, removeFormat, and fullscreen.
A smaller toolbar does not remove content types from the document schema, so
reduced frontend forms can preserve content created in the full admin editor.
The extension constructors are also exposed on z_editor_tiptap. Build custom
extensions against the bundled Tiptap/ProseMirror versions.
Asides and Markdown admonitions are supported in every profile. Add aside and
note to a profile's toolbar to expose the wrap/unwrap controls. Notes are emitted
as <div class="admonition note" role="note"> with an editable, translated title
paragraph (first admonition-title). Existing aside-based admonitions retain their
tag and classes. Both containers support regular block content, including media,
lists and code. Their generic body styling can be overridden by site CSS.
Template options
_editor.tpl accepts these integration options:
| Option | Purpose |
|---|---|
id | Resource used as the subject of link/media selection dialogs. |
tiptap_overrides_tpl | JavaScript configuration include; defaults to _editor_tiptap_overrides_js.tpl. |
is_editor_include | Load assets and configure the editor without calling z_editor_init() in this include. |
zmedia_tabs_enabled, zmedia_tabs_disabled | Media chooser tab lists; the disabled list defaults to ["new"]. |
zlink_tabs_enabled, zlink_tabs_disabled | Internal-link chooser tab lists. |
The compatibility templates _admin_tinymce.tpl and
_admin_frontend_tinymce_init.tpl integrate older callers and frontend admin
without loading TinyMCE. TinyMCE's tinyInit, overrides_tpl, and plugin settings
are not interpreted; migrate them to Tiptap profiles and extensions.
Styles and media
Generic document typography is defined in priv/lib/css/editor-tiptap-body.css.
Override that asset in the site, or add styles through _editor_tiptap_head.tpl,
which loads after the defaults. Scope content styles under .z-tiptap-body.
Editor controls and structural styles are in css/editor-tiptap.css. The content
is ordinary page DOM, not an iframe, so site CSS can also affect it.
The link/media selection dialogs come from mod_admin; their callbacks are
z_editor_tiptap.linkDone and z_editor_tiptap.mediaDone. Select a media item and
use the media button, or double-click it, to open its properties. The module owns
_editor_tiptap_media_props.tpl and _editor_tiptap_media_preview.tpl; the latter
uses the shared admin_media_preview dispatch. The properties template exposes
blocks for caption help, alignment, size, crop, class, link, and the edit button.
Name a site-specific class control class.
Media nodes serialize back to Zotonic z-media HTML comments, including their
site-specific options. Render saved bodies through the existing show_media
filter. Dialog selections are mapped through intervening editor transactions;
callbacks for removed editors are ignored.
Permissions and events
The media_props postback_notify, delegated to mod_editor_tiptap, accepts
id, JSON-encoded options, and request_id for the originating dialog.
It requires an authenticated user and a visible resource. Invalid or inaccessible
resources produce an error notification. Malformed option JSON becomes an empty
options map; only the known string/boolean properties are passed to the template.
The handler opens a dialog and does not update a resource.
The editor does not grant permission to save content. Resource updates must use the normal ACL-checked and HTML-sanitizing Zotonic save paths. Browser-side URL checks and read-only controls are not server-side authorization.
Content compatibility
Untouched textareas are not rewritten. After editing, content is serialized using the supported schema. Standard text formatting, images, links, lists, and tables are supported; arbitrary HTML, scripts/iframes, and every TinyMCE style or element are not. Test representative existing bodies before migrating a site.
Paste cleanup removes Office typography and reconstructs basic mso-list
paragraphs. It is not the commercial Tiptap Office paste handler: complex list
numbering, embedded images, footnotes, and exact Word layout are not guaranteed.
Cooperative editing with Yjs/Cotonic is not included.
Building
Browser assets and third-party license notices ship in priv/lib. No runtime
CDN or Node.js service is required. SCSS sources live in priv/lib-src/scss/;
the JavaScript package and tests live in priv/lib-src/tiptap/.
The default build (or make css) compiles only CSS using Dart Sass (sass on
PATH, overridable with SASS). It never runs the JavaScript Makefile or npm.
Use the explicit js target to rebuild the browser bundle:
make -C apps/zotonic_mod_editor_tiptap
make -C apps/zotonic_mod_editor_tiptap js
make -C apps/zotonic_mod_editor_tiptap test
./rebar3 compile
Node.js/npm are development dependencies. Package versions are pinned in
priv/lib-src/tiptap/package-lock.json; the JS build emits the JavaScript bundle and its
third-party license notices. Zotonic integration code is Apache-2.0 licensed.