{"result":{"depiction_url":null,"edges":{"subject":{"objects":[{"created":"2026-10-13T15:23:53Z","object_id":{"id":2555,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_reference","title":"Reference","uri":"https:\/\/zotonic.com\/id\/2555"},"seq":1},{"created":"2026-10-13T15:23:53Z","object_id":{"id":2561,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_content_editor","title":"Content editor","uri":"https:\/\/zotonic.com\/id\/2561"},"seq":2},{"created":"2026-10-13T15:23:53Z","object_id":{"id":2563,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_frontend_developer","title":"Frontend developer","uri":"https:\/\/zotonic.com\/id\/2563"},"seq":3},{"created":"2026-10-13T15:23:53Z","object_id":{"id":2570,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_content_authoring","title":"Content authoring","uri":"https:\/\/zotonic.com\/id\/2570"},"seq":4},{"created":"2026-10-13T15:23:53Z","object_id":{"id":2576,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_media_management","title":"Media management","uri":"https:\/\/zotonic.com\/id\/2576"},"seq":5},{"created":"2026-10-13T15:23:53Z","object_id":{"id":2635,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_module","title":"Module","uri":"https:\/\/zotonic.com\/id\/2635"},"seq":6},{"created":"2026-10-13T15:23:53Z","object_id":{"id":2638,"is_a":["categorization","keyword","keyword_task"],"name":"zotonic_topic_edit","title":"Edit","uri":"https:\/\/zotonic.com\/id\/2638"},"seq":7},{"created":"2026-10-13T15:23:53Z","object_id":{"id":2672,"is_a":["categorization","keyword","keyword_technology"],"name":"zotonic_topic_html","title":"HTML","uri":"https:\/\/zotonic.com\/id\/2672"},"seq":8},{"created":"2026-10-13T15:23:53Z","object_id":{"id":2673,"is_a":["categorization","keyword","keyword_technology"],"name":"zotonic_topic_javascript","title":"JavaScript","uri":"https:\/\/zotonic.com\/id\/2673"},"seq":9}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":3088,"is_a":["text","documentation","reference","module"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3088"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_module_mod_editor_tiptap","page_url":{"en":"https:\/\/zotonic.com\/docs\/3088\/mod_editor_tiptap","x-default":"https:\/\/zotonic.com\/docs\/3088\/mod_editor_tiptap"},"preview_url":null,"resource":{"version":21,"pivot_location_lat":null,"title":"mod_editor_tiptap","is_authoritative":true,"body":"<p>Adds a rich-text editor based on Tiptap and ProseMirror to Zotonic forms.\nIt provides headings, emphasis, lists, links, Zotonic media, tables, undo\/redo,\nand full-window editing through a plain JavaScript interface.<\/p>\n<h2>Setup<\/h2>\n<p>Enable <code>mod_editor_tiptap<\/code> instead of <code>mod_editor_tinymce<\/code> on the site&#39;s Modules\npage. It depends on <code>mod_base<\/code> and <code>mod_admin<\/code>, but does not require TinyMCE to be\nenabled or load any TinyMCE templates or JavaScript. Existing <code>_editor.tpl<\/code>\nincludes select this editor when the module is enabled.<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% include &quot;_editor.tpl&quot; id=id %}\n&lt;textarea id=&quot;body&quot; name=&quot;body&quot; class=&quot;z_editor-init&quot;&gt;{{ id.body|escape }}&lt;\/textarea&gt;\n<\/code><\/pre>\n<p>The page must include Zotonic&#39;s normal JavaScript and <code>{% script %}<\/code> support.\nTextareas marked <code>do_zeditor<\/code> can use the existing widget initialization instead.\nThe original textarea remains the form field; the editor synchronizes its value\nbefore a wired or native form submission.<\/p>\n<h2>Editor lifecycle<\/h2>\n<p>Editors initialize near the viewport. Call <code>z_editor.init()<\/code> after showing a\nlanguage tab or otherwise revealing fields; the standard admin already does so.\nVisited editors stay alive when tabs are hidden, preserving their undo history.\nNew textareas in AJAX fragments are discovered automatically, and editors whose\nelements are removed are destroyed.<\/p>\n<table class=\"table\"><thead><tr><th>JavaScript API<\/th><th>Behavior<\/th><\/tr><\/thead><tbody><tr><td><code>z_editor.init()<\/code><\/td><td>Discover editor textareas throughout the document.<\/td><\/tr><tr><td><code>z_editor.add(root)<\/code><\/td><td>Discover editor textareas in the supplied element or container.<\/td><\/tr><tr><td><code>z_editor.save(root)<\/code><\/td><td>Write changed editor content to textareas in that scope.<\/td><\/tr><tr><td><code>z_editor.remove(root)<\/code><\/td><td>Save and destroy editors in that scope and cancel pending initialization.<\/td><\/tr><tr><td><code>z_editor_tiptap.get(textareaId)<\/code><\/td><td>Return the mounted Tiptap editor, or <code>undefined<\/code> before initialization or after removal.<\/td><\/tr><\/tbody><\/table>\n<p>The root argument accepts a DOM element, selector, or jQuery collection. Omitting\nit selects the document. Before directly serializing a form, call\n<code>z_editor.save(form)<\/code>. Explicit removal followed by addition starts a new undo\nhistory. Content edits emit the existing <code>z:editorChange<\/code> form notification.\nFull-window editing preserves the instance; Escape returns to the normal view.<\/p>\n<h2>Profiles and extensions<\/h2>\n<p>The three built-in profiles are <code>simple<\/code> (basic text and links), <code>normal<\/code>\n(formatting, lists, media and full-window editing), and <code>advanced<\/code> (all controls,\nincluding tables, alignment and text direction). Admin pages select <code>advanced<\/code>\non the body element.<\/p>\n<p>Select a profile with <code>data-editor-profile<\/code> on the textarea or any ancestor.\nThe nearest attribute wins; missing, empty or unknown names use <code>normal<\/code>.\nField names and the old <code>data-zeditor.config<\/code> no longer select profiles.<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-html\">&lt;section data-editor-profile=&quot;normal&quot;&gt;\n    &lt;textarea name=&quot;body$en&quot; class=&quot;z_editor-init&quot;&gt;&lt;\/textarea&gt;\n    &lt;textarea name=&quot;summary$en&quot; class=&quot;z_editor-init&quot; data-editor-profile=&quot;simple&quot;&gt;&lt;\/textarea&gt;\n&lt;\/section&gt;\n<\/code><\/pre>\n<p>Profiles are defined in <code>_editor_tiptap_profiles.tpl<\/code>, loaded before the editor\nbundle. Override that template in your site using its <code>simple<\/code>, <code>normal<\/code>, and\n<code>advanced<\/code> blocks. Each block contains JavaScript object properties:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% overrules %}\n{% block simple %}\ntoolbar: [&#39;bold&#39;, &#39;italic&#39;, &#39;link&#39;, &#39;unlink&#39;]\n{% endblock %}\n<\/code><\/pre>\n<p>A profile can also define extensions, paste handlers and custom buttons in the\nsame block. The optional <code>window.z_editor_tiptap_config.default<\/code> supplies shared\noptions; the selected profile is shallow-merged over it. Arrays are replaced,\nnot concatenated. Profiles are read at editor creation; changing attributes or\ndefinitions does not rebuild mounted editors. There are no <code>m.config<\/code> settings.\nThe admin <code>editor_profile<\/code> template block can override the body-level default.<\/p>\n<table class=\"table\"><thead><tr><th>Profile option<\/th><th>Purpose<\/th><\/tr><\/thead><tbody><tr><td><code>toolbar<\/code><\/td><td>Ordered array of control names; `<\/td><\/tr><tr><td><code>extensions<\/code><\/td><td>Additional Tiptap extensions, or a function receiving <code>{ Editor, Extension, Node, Mark }<\/code> and returning an array.<\/td><\/tr><tr><td><code>buttons<\/code><\/td><td>Custom controls keyed by toolbar name, each with <code>label<\/code>, optional Lucide <code>icon<\/code> data, and <code>run(editor)<\/code>.<\/td><\/tr><tr><td><code>transformPastedHTML(html)<\/code><\/td><td>Replace the default paste normalizer.<\/td><\/tr><tr><td><code>onCreate(editor, textarea)<\/code><\/td><td>Run additional integration after mounting.<\/td><\/tr><\/tbody><\/table>\n<p>Toolbar controls include <code>undo<\/code>, <code>redo<\/code>, <code>format<\/code>, <code>bold<\/code>, <code>italic<\/code>, <code>underline<\/code>,\n<code>strike<\/code>, <code>subscript<\/code>, <code>superscript<\/code>, <code>bulletList<\/code>, <code>orderedList<\/code>, <code>indent<\/code>,\n<code>outdent<\/code>, <code>blockquote<\/code>, <code>codeBlock<\/code>, <code>alignLeft<\/code>, <code>alignCenter<\/code>, <code>alignRight<\/code>,\n<code>ltr<\/code>, <code>rtl<\/code>, <code>horizontalRule<\/code>, <code>hardBreak<\/code>, <code>link<\/code>, <code>unlink<\/code>, <code>zlink<\/code>, <code>zmedia<\/code>,\n<code>table<\/code>, <code>removeFormat<\/code>, and <code>fullscreen<\/code>.<\/p>\n<p>A smaller toolbar does not remove content types from the document schema, so\nreduced frontend forms can preserve content created in the full admin editor.\nThe extension constructors are also exposed on <code>z_editor_tiptap<\/code>. Build custom\nextensions against the bundled Tiptap\/ProseMirror versions.<\/p>\n<p>Asides and Markdown admonitions are supported in every profile. Add <code>aside<\/code> and\n<code>note<\/code> to a profile&#39;s toolbar to expose the wrap\/unwrap controls. Notes are emitted\nas <code>&lt;div class=&quot;admonition note&quot; role=&quot;note&quot;&gt;<\/code> with an editable, translated title\nparagraph (<code>first admonition-title<\/code>). Existing aside-based admonitions retain their\ntag and classes. Both containers support regular block content, including media,\nlists and code. Their generic body styling can be overridden by site CSS.<\/p>\n<h2>Template options<\/h2>\n<p><code>_editor.tpl<\/code> accepts these integration options:<\/p>\n<table class=\"table\"><thead><tr><th>Option<\/th><th>Purpose<\/th><\/tr><\/thead><tbody><tr><td><code>id<\/code><\/td><td>Resource used as the subject of link\/media selection dialogs.<\/td><\/tr><tr><td><code>tiptap_overrides_tpl<\/code><\/td><td>JavaScript configuration include; defaults to <code>_editor_tiptap_overrides_js.tpl<\/code>.<\/td><\/tr><tr><td><code>is_editor_include<\/code><\/td><td>Load assets and configure the editor without calling <code>z_editor_init()<\/code> in this include.<\/td><\/tr><tr><td><code>zmedia_tabs_enabled<\/code>, <code>zmedia_tabs_disabled<\/code><\/td><td>Media chooser tab lists; the disabled list defaults to <code>[&quot;new&quot;]<\/code>.<\/td><\/tr><tr><td><code>zlink_tabs_enabled<\/code>, <code>zlink_tabs_disabled<\/code><\/td><td>Internal-link chooser tab lists.<\/td><\/tr><\/tbody><\/table>\n<p>The compatibility templates <code>_admin_tinymce.tpl<\/code> and\n<code>_admin_frontend_tinymce_init.tpl<\/code> integrate older callers and frontend admin\nwithout loading TinyMCE. TinyMCE&#39;s <code>tinyInit<\/code>, <code>overrides_tpl<\/code>, and plugin settings\nare not interpreted; migrate them to Tiptap profiles and extensions.<\/p>\n<h2>Styles and media<\/h2>\n<p>Generic document typography is defined in <code>priv\/lib\/css\/editor-tiptap-body.css<\/code>.\nOverride that asset in the site, or add styles through <code>_editor_tiptap_head.tpl<\/code>,\nwhich loads after the defaults. Scope content styles under <code>.z-tiptap-body<\/code>.\nEditor controls and structural styles are in <code>css\/editor-tiptap.css<\/code>. The content\nis ordinary page DOM, not an iframe, so site CSS can also affect it.<\/p>\n<p>The link\/media selection dialogs come from <code>mod_admin<\/code>; their callbacks are\n<code>z_editor_tiptap.linkDone<\/code> and <code>z_editor_tiptap.mediaDone<\/code>. Select a media item and\nuse the media button, or double-click it, to open its properties. The module owns\n<code>_editor_tiptap_media_props.tpl<\/code> and <code>_editor_tiptap_media_preview.tpl<\/code>; the latter\nuses the shared <code>admin_media_preview<\/code> dispatch. The properties template exposes\nblocks for caption help, alignment, size, crop, class, link, and the edit button.\nName a site-specific class control <code>class<\/code>.<\/p>\n<p>Media nodes serialize back to Zotonic <code>z-media<\/code> HTML comments, including their\nsite-specific options. Render saved bodies through the existing <code>show_media<\/code>\nfilter. Dialog selections are mapped through intervening editor transactions;\ncallbacks for removed editors are ignored.<\/p>\n<h2>Permissions and events<\/h2>\n<p>The <code>media_props<\/code> <code>postback_notify<\/code>, delegated to <code>mod_editor_tiptap<\/code>, accepts\n<code>id<\/code>, JSON-encoded <code>options<\/code>, and <code>request_id<\/code> for the originating dialog.\nIt requires an authenticated user and a visible resource. Invalid or inaccessible\nresources produce an error notification. Malformed option JSON becomes an empty\noptions map; only the known string\/boolean properties are passed to the template.\nThe handler opens a dialog and does not update a resource.<\/p>\n<p>The editor does not grant permission to save content. Resource updates must use\nthe normal ACL-checked and HTML-sanitizing Zotonic save paths. Browser-side URL\nchecks and read-only controls are not server-side authorization.<\/p>\n<h2>Content compatibility<\/h2>\n<p>Untouched textareas are not rewritten. After editing, content is serialized using\nthe supported schema. Standard text formatting, images, links, lists, and tables\nare supported; arbitrary HTML, scripts\/iframes, and every TinyMCE style or element\nare not. Test representative existing bodies before migrating a site.<\/p>\n<p>Paste cleanup removes Office typography and reconstructs basic <code>mso-list<\/code>\nparagraphs. It is not the commercial Tiptap Office paste handler: complex list\nnumbering, embedded images, footnotes, and exact Word layout are not guaranteed.\nCooperative editing with Yjs\/Cotonic is not included.<\/p>\n<h2>Building<\/h2>\n<p>Browser assets and third-party license notices ship in <code>priv\/lib<\/code>. No runtime\nCDN or Node.js service is required. SCSS sources live in <code>priv\/lib-src\/scss\/<\/code>;\nthe JavaScript package and tests live in <code>priv\/lib-src\/tiptap\/<\/code>.<\/p>\n<p>The default build (or <code>make css<\/code>) compiles only CSS using Dart Sass (<code>sass<\/code> on\nPATH, overridable with <code>SASS<\/code>). It never runs the JavaScript Makefile or npm.\nUse the explicit <code>js<\/code> target to rebuild the browser bundle:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-sh\">make -C apps\/zotonic_mod_editor_tiptap\nmake -C apps\/zotonic_mod_editor_tiptap js\nmake -C apps\/zotonic_mod_editor_tiptap test\n.\/rebar3 compile\n<\/code><\/pre>\n<p>Node.js\/npm are development dependencies. Package versions are pinned in\n<code>priv\/lib-src\/tiptap\/package-lock.json<\/code>; the JS build emits the JavaScript bundle and its\nthird-party license notices. Zotonic integration code is Apache-2.0 licensed.<\/p>","slug":"mod_editor_tiptap","doc_module_config":[],"is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"f4d0001f3a89996c11c413825d760dd8664127975696e7eb2cd845fd67dcebb8","is_featured":false,"content_group_id":{"id":2551,"is_a":["meta","content_group"],"name":"content_group_imported_docs","title":"Imported documentation","uri":"https:\/\/zotonic.com\/id\/content_group_imported_docs"},"category_id":{"id":320,"is_a":["meta","category"],"name":"module","title":"Modules","uri":"https:\/\/test.zotonic.com\/id\/320"},"doc_source_path":"apps\/zotonic_mod_editor_tiptap\/src\/mod_editor_tiptap.erl","publication_start":"2026-10-13T15:23:53Z","github_url":"https:\/\/github.com\/zotonic\/zotonic\/blob\/master\/apps\/zotonic_mod_editor_tiptap\/src\/mod_editor_tiptap.erl","pivot_location_lng":null,"doc_source_kind":"module","name":"doc_module_mod_editor_tiptap","is_unfindable":false,"is_published":true,"pivot_geocode":null,"created":"2026-10-13T15:23:53Z","uri":null,"doc_status":"current","is_dependent":false,"erlang_app":"zotonic_mod_editor_tiptap","publication_end":"9999-06-01T00:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_source_commit":"92d27433689eef55504265c6dd5486c65dd73483\n","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-10-13T15:40:14Z","title_slug":"mod_editor_tiptap"},"uri":"https:\/\/zotonic.com\/id\/3088","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3088"}},"status":"ok"}