se7enxweb / explayouts-ui-api
JSON HTTP API and SPA shell for the Exponential Layouts admin UI, served under /explayouts_ui_api/app.
Package info
github.com/se7enxweb/explayouts_ui_api
Language:JavaScript
Type:ezpublish-legacy-extension
pkg:composer/se7enxweb/explayouts-ui-api
Fund package maintenance!
Requires
- php: ^8.0 || ^8.1 || ^8.2 || ^8.3 || ^8.4
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.3.31
- v1.3.30
- v1.3.29
- v1.3.28
- v1.3.27
- v1.3.26
- v1.3.25
- v1.3.24
- v1.3.23
- v1.3.22
- v1.3.21
- v1.3.20
- v1.3.19
- v1.3.18
- v1.3.17
- v1.3.16
- v1.3.15
- v1.3.14
- v1.3.13
- v1.3.12
- v1.3.11
- v1.3.10
- v1.3.9
- v1.3.8
- v1.3.7
- v1.3.6
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.4
- v1.2.3
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.0
This package is auto-updated.
Last update: 2026-10-24 02:27:00 UTC
README
General description
Exponential Layouts UI API (explayouts_ui_api) is the JSON HTTP API and SPA shell for the Exponential Layouts admin UI on Exponential 6 / Exponential Legacy. It serves the built layouts admin app assets from design/standard and exposes the JSON API the app expects under /explayouts_ui_api/app/api/..., replacing the Symfony backend of the upstream stack with plain Exponential Legacy module views and the explayouts_core services.
It is an Exponential Legacy port inspired by the netgen/layouts-ui package (and the app endpoints of netgen/layouts-core).
This extension provides the following capabilities:
- SPA shell - Bootstraps the layouts admin app at
/explayouts_ui_api/app(optionally/app/<locale>), including the meta tags the JavaScript reads and the bundled CSS/JS. - JSON API - A single dispatcher routing
config,layouts,blocks,rules,mappings,transfer,collections,forms,parameters,versionsandshareresources under/explayouts_ui_api/app/api/.... - Layout lifecycle over HTTP - Create draft layouts, publish them, delete drafts, list versions and manage share tokens.
- Block editing over HTTP - Create, load and update blocks (name, view type, position, parameters) per layout and locale, plus HTML form fragments for the block edit sidebar.
- Rules and mappings - List layout mapping rules with targets and conditions, and rule counts per layout.
- CSRF-protected writes - All modifying requests validate the eZ form token via the
ezformtokenextension. - Standalone JSON views - Simple read-only module views for layouts, rules and blocks outside the SPA dispatcher.
Features
The following features are provided by the Exponential Layouts UI API extension:
- Central dispatcher -
expLayoutsUIApplicationApi(classes/explayoutsuiapplicationapi.php) routes every/app/api/<resource>request viahandle( $parts ); all responses areapplication/json, debug output is disabled in the dispatcher so responses stay clean, and list responses use thevalues/totalshape the app expects. - Policies - every view needs
explayouts_ui_api/read; every changing request (any method butGET,HEADandOPTIONS) also needsexplayouts_ui_api/editand is refused with a403JSON answer without it. An error's message, file and line reach the client only with debug output on; the log always has them. - App shell module -
modules/explayouts_ui_api/app.phpserves the SPA shell, the API entry, the layout preview (/explayouts_ui_api/app/preview/<layout_id>) and the HTML block-edit form fragments. - Bootstrap template -
design/standard/templates/explayouts_ui_api/app.tplrenders the meta tags the JavaScript reads:explayouts-route-prefix(set to/explayouts_ui_api),explayouts-base-path,ngcb-base-pathandezxform-token. - Configuration endpoints -
GET /app/api/config(including the current CSRF token),GET /app/api/config/layout_types(layout types and their zones fromexplayouts.ini) andGET /app/api/config/block_types(block types grouped asblock_typesandblock_type_groups). - Layout endpoints -
GET/POST /app/api/layouts,GET /app/api/layouts/<id>,POST /app/api/layouts/<id>/publishandDELETE /app/api/layouts/<id>/draft. Created layouts are drafts (status=1) with zones created from the layout type definition; the identifier is generated from the name if not provided. - Block endpoints -
GET /app/api/<locale>/layouts/<layout_id>/blocks,GET/POST/PUT/PATCH /app/api/<locale>/blocks/<id>andPOST /app/api/<locale>/blocks. Updates acceptname,view_type,positionandparameters(object keyed by parameter name). - Rule and mapping endpoints -
GET /app/api/rules,GET /app/api/rules/<id>andGET /app/api/mappings(rule counts perlayout_id). - Collection endpoints -
/app/api/collections/...manage collections and collection items for blocks with collections (backed byexpLayoutsCoreCollectionService). - Transfer, forms, parameters, versions, share -
/app/api/transfer/...(import/export viaexpLayoutsImporter/expLayoutsExporter),/app/api/forms/...and/app/api/parameters/...(form/parameter metadata for the block edit UI),GET /app/api/versions/<layout_id>(draft and published versions) andGET|POST /app/api/share/<layout_id>(share tokens, stored in theexplayouts_sharetable, created on demand). - HTML form fragments -
GET /explayouts_ui_api/app/<locale>/blocks/<id>/edit(wrapper,form_block_edit.tpl) and.../form(the<form>,form_block_fields.tpl); the form submits toPOST /explayouts_ui_api/app/api/<locale>/blocks/<id>and returns the updated block as JSON. - CSRF protection -
POST,PUT,PATCHandDELETErequests must carry the eZ form token, either as anX-CSRF-TokenHTTP header or anezxform_tokenform field; the current token is available fromGET /app/api/config. - Flexible request bodies -
POSTbodies may beapplication/x-www-form-urlencodedorapplication/json;requestData()inexpLayoutsUIApplicationApifalls back tophp://inputwhen$_POSTis empty. - Standalone JSON module views -
/explayouts_ui_api/layouts/(LayoutID)/<id>,/explayouts_ui_api/rules/(RuleID)/<id>and/explayouts_ui_api/blocks/(ZoneID)/<id>are simple read-only views built on theexplayouts_coreservices.
Version
- The current version of Exponential Layouts UI API is 1.0.0
- Last Major update: July 30, 2026
Copyright
- Exponential Layouts UI API is copyright 1998 - 2026 7x
- See: LICENSE.md for more information on the terms of the copyright and license
License
Exponential Layouts UI API is licensed under the GNU General Public License.
The complete license agreement is included in the LICENSE.md file.
Exponential Layouts UI API is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License or at your option a later version.
Exponential Layouts UI API is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
The GNU GPL gives you the right to use, modify and redistribute Exponential Layouts UI API under certain conditions. The GNU GPL license is distributed with the software, see the file LICENSE.md.
It is also available at http://www.gnu.org/licenses/gpl.txt
You should have received a copy of the GNU General Public License along with Exponential Layouts UI API in LICENSE.md. If not, see http://www.gnu.org/licenses/.
Using Exponential Layouts UI API under the terms of the GNU GPL is free (as in freedom).
Third-party code
Two parts of this extension come from Netgen Layouts and stay under its MIT License, whose notice ships with them:
design/standard/javascript/netgen-layouts.js, the editor's application bundle, built fromnetgen/layouts-uiand patched for Exponential: notice indesign/standard/javascript/netgen-layouts-LICENSE.txt, which also names the libraries the bundle still contains (Backbone, Underscore, Handlebars, NProgress, striptags, the content browser with React: MIT; source-map: BSD 3-Clause) with their notices.design/standard/media/netgen-layouts.*, the editor's icon font, fromnetgen/layouts-core: notice indesign/standard/media/netgen-layouts-LICENSE.txt.
The editor's Material Icons font (design/standard/media/MaterialIcons-Regular.* and its copy in
design/standard/fonts/) comes from Google's google/material-design-icons and stays under the Apache License,
Version 2.0: notice in design/standard/media/MaterialIcons-LICENSE.txt and design/standard/fonts/MaterialIcons-LICENSE.txt.
For more information or questions please contact info@se7enx.com
Requirements
The following requirements exists for using the Exponential Layouts UI API extension:
Exponential version
- Make sure you use Exponential 6 / eZ Publish Legacy (required) or higher.
PHP version
- Make sure you have PHP 8.1 or higher.
Sibling extensions
extension/explayouts— value objects,explayouts_*tables,explayouts.iniblock/layout type definitions.extension/explayouts_core— service classes the API handlers call.extension/ezformtoken— CSRF token validation for modifying requests.
Siteaccess
- Activate the extension for the admin siteaccess — the app runs inside the authenticated admin session.
Browser
- The editor page loads no jQuery and no jQuery UI (and nothing of
ezjscore): the app bundle brings its own DOM layer, dialogs and drag and drop (see "The app bundle and its patches"). Scripts added throughexplayouts_ui_api.ini [App] Javascripts[]must not expectwindow.jQuery.
Installation
In short: place the extension in extension/explayouts_ui_api, activate it after its dependencies (explayouts, explayouts_core) via site.ini [ExtensionSettings] ActiveExtensions[] (or per siteaccess via ActiveAccessExtensions[]) for the admin siteaccess, then regenerate autoloads and clear all caches. Verify by opening /explayouts_ui_api/app in the admin siteaccess — the layouts app shell should load, and GET /explayouts_ui_api/app/api/config returns JSON including the current form token. The companion extension explayouts_ui adds the "Exponential Layouts UI" admin menu whose "Edit in modern editor" links open this app.
See INSTALL.md for the full step-by-step installation instructions, including the settings shipped with the extension (module.ini with views app, layouts, rules, blocks; design.ini for the SPA assets).
Usage
Key classes and views:
| Item | Purpose |
|---|---|
expLayoutsUIApplicationApi (classes/explayoutsuiapplicationapi.php) |
JSON API dispatcher: handle( $parts ) routes config, layouts, blocks, rules, mappings, transfer, collections, forms, parameters, versions, share |
modules/explayouts_ui_api/app.php |
SPA shell (/explayouts_ui_api/app), API entry, preview and HTML block-edit form fragments |
modules/explayouts_ui_api/layouts.php, rules.php, blocks.php |
Standalone JSON module views built on the explayouts_core services |
design/standard/templates/explayouts_ui_api/app.tpl |
SPA bootstrap page (route prefix, base paths and form token meta tags) |
Endpoint overview (all under /explayouts_ui_api, responses are application/json):
| Endpoint | Purpose |
|---|---|
GET /app/api/config, GET /app/api/config/layout_types, GET /app/api/config/block_types |
App configuration, CSRF token, layout and block type definitions |
GET/POST /app/api/layouts, GET /app/api/layouts/<id>, POST /app/api/layouts/<id>/publish, DELETE /app/api/layouts/<id>/draft |
Layout lifecycle |
GET /app/api/<locale>/layouts/<layout_id>/blocks, GET/POST/PUT/PATCH /app/api/<locale>/blocks/<id>, POST /app/api/<locale>/blocks |
Block management |
GET /app/api/rules, GET /app/api/rules/<id>, GET /app/api/mappings |
Mapping rules and per-layout rule counts |
/app/api/collections/... |
Collection and collection item management |
/app/api/transfer/..., /app/api/forms/..., /app/api/parameters/... |
Import/export and block edit form metadata |
GET /app/api/versions/<layout_id>, `GET |
POST /app/api/share/<layout_id>` |
GET /app/<locale>/blocks/<id>/edit, GET /app/<locale>/blocks/<id>/form |
HTML block-edit form fragments |
GET /app/preview/<layout_id> |
Layout preview |
Creating a layout:
POST /explayouts_ui_api/app/api/layouts
{ "name": "My layout", "layout_type": "2_column" }
Modifying requests must carry the eZ form token, either as an X-CSRF-Token HTTP header or an ezxform_token form field; fetch the current token from GET /app/api/config. Use placeholder credentials in examples — never commit real credentials.
See doc/USAGE.md for exhaustive scenarios: every endpoint with request/response bodies, a complete curl session, the HTML form fragment flow, implementation notes, and the full customization guide covering the settings layer (INI cascade — everything the API exposes is defined in explayouts.ini and overridable without code changes; access control via module policies), the template layer (design override cascade for app.tpl and the form templates — the supported way to swap CSS/JS bundles) and the PHP layer (building against the JSON contract, adding block parameters via handlers, custom read-only JSON views).
The app bundle and its patches
design/standard/javascript/netgen-layouts.js is the built bundle of
netgen/layouts-ui 2.0.0 (commit e61f093, file
bundle/Resources/public/js/netgen-layouts.js; the first import of this package was that file byte for byte), with
changes for Exponential made to the built bundle. The bundle is not rebuilt from the layouts-ui sources: their build
(Grunt, browserify, Node 14, node-sass) is not reproducible any more, and a rebuild would drop every change below.
What differs from the original is versioned in src/app-patches/:
manifest.json: the original (version, commit, download address, sha256 of the bundle, of each of its 157 modules and of the skeleton around them) and each patched module with what was changed and in which commit;header.js: what stands in front of the bundle (nglT()/nglTh()for the interface texts);modules/NNN-name.js: browserify module NNN's function exactly as the bundle carries it (49 of 157: the interface texts, the editor's own DOM layer in place of jQuery (module 140), its own drag and drop on Pointer Events in place of jQuery UI (modules 135-138; 134 is empty) and its keyboard mode (module 139), the view type select, the plain DOM code in place of jQuery plugins, and the others listed in the manifest).
The editor runs without jQuery. Module 140 (jquery; until 1.3.29 the page's jQuery 4 from ezjscore, before that
the jQuery 2.2.4 built in) is the editor's DOM layer: the part of jQuery the bundle calls, with jQuery's behaviour,
on plain DOM. Backbone and the app's modules reach it as Backbone.$ and Core.$; it is not put on the page as
window.jQuery or window.$.
Core.$ |
|
|---|---|
$( selector | html | element | array | fn ) |
collections; $( fn ) runs when the document is ready, after the current script |
each, map, get, toArray, eq, first, filter, index, find, closest, children, parent, parents, next, siblings |
traversal; a selector starting with > is relative, as in jQuery |
addClass, removeClass, hasClass, attr, removeAttr, prop, data, removeData, css, height, val |
data() reads data-* attributes as jQuery converts them; data( '_view' ) is the element's ngl_view, where every Backbone view of the app is kept (plain code: el.ngl_view) |
html, text, append, after, replaceWith, remove, empty, show, hide, fadeIn, fadeOut |
what is taken out loses the layer's handlers and data (jQuery's cleanData); the scripts of HTML put in run once when they reach the document, as with jQuery; fades queue per element |
on, off, trigger, ajaxStart, ajaxSend, ajaxSuccess, ajaxError, ajaxComplete, ajaxStop |
one native listener per element and type; delegation, namespaces (Backbone's .delegateEvents<cid>), jQuery's event object (currentTarget, delegateTarget, originalEvent, which, return false); trigger() dispatches a bubbling CustomEvent (the arguments as detail) that addEventListener listeners get too; focus, blur, click call the element's own method |
serialize, serializeArray |
as jQuery |
$.ajax, $.get, $.active |
XMLHttpRequest with jQuery's options, jqXHR (a promise with done, fail, always, then, responseJSON ...), headers and encodings; the global events on the document drive the progress bar, the count of running writes (the warning on leaving), and the session timeout and permission dialogs. javascript/explayouts-app.js sends its requests through Core.$.ajax for that |
$.Deferred, $.when, $.param, $.extend, $.each, $.inArray |
as jQuery 3+ (done at once, then later) |
tests/js/editor-without-jquery.test.mjs runs the whole page without jQuery through the main flows against a fake of
the API, then the same flows with jQuery 4 in place of module 140 and compares every request and the document after
each flow; that run records what the bundle calls of jQuery, and the test fails if the layer lacks any of it.
tests/js/dom-layer.test.mjs tests the layer on its own, against jQuery 4 where it can.
Dialogs, collapsible panels, dropdown menus, tooltips, the date field and the inline switches of the sidebar run
without jQuery plugins. Bootstrap 3's JavaScript (modal, collapse, dropdown, transition), Moment.js with its locales
and eonasdan-bootstrap-datetimepicker are no longer in the bundle; their modules keep their ids and hold plain DOM
code (Core.ui, put together in ./extended/jquery_override) or nothing:
Core.ui |
instead of | |
|---|---|---|
dialog.show( el, options ), hide( el ), is_shown( el ) |
$.fn.modal |
Bootstrap 3 markup and CSS; backdrop, keyboard, on_shown, on_hidden; Tab stays in the open dialog, the focus goes back on close; shown again while it fades out (or closed while it fades in) it turns round where it stands |
collapse.show/hide/toggle( el ) |
$.fn.collapse |
clicks on [data-toggle="collapse"] |
dropdown.toggle( toggle ), close_all() |
$.fn.dropdown |
clicks and keys on [data-toggle="dropdown"], opens upwards when there is no room below |
xeditable, dependable_select, master_slave_selects, view_types, video_thumb_fetcher, bm_tooltip ( elements ) |
the jQuery plugins of the same names | called by reinitialize_plugins (module init) |
show( el, ms ), hide( el, ms ), transition_end, find, elements, tabbables, fire |
jQuery's show/hide, emulateTransitionEnd |
helpers |
The DOM events show.bs.modal, hidden.bs.collapse, shown.bs.dropdown ... are still fired (as DOM events: listen
with addEventListener). The content browser dialog of javascript/explayouts-app.js is a Core.ui.dialog too. The date field of ./views/datetime_picker is the browser's <input type="datetime-local">
(it sends the same YYYY-MM-DDTHH:mm in local time); the template helper date formats with
Intl.DateTimeFormat.
After npm ci (the bundle is read with acorn, not evaluated), node src/app-patches/apply.mjs builds the bundle
from them (--check compares, --from=<original bundle> builds
from the layouts-ui file, --diff shows the changes against it, --extract=<id> copies a module out to change it;
details in the file). tests/js/app-patches.test.mjs checks that the bundle is the original plus exactly these
patches, without needing the original (with LAYOUTS_UI_BUNDLE=<original bundle> it also rebuilds from it,
LAYOUTS_UI_BUNDLE=download fetches the original from the address in the manifest and checks its sha256 first).
To change the bundle: --extract the module if it is not patched yet, edit its file, note the change in
manifest.json and run apply.mjs. The bundle is linked without a version query: after a deploy, a hard reload makes a
browser fetch it again.
Moving with the keyboard
Everything the pointer moves can be moved with the keyboard as well (module 139, on the sortable lists and draggables of modules 135 and 138). The grips are buttons: the handle in a block's header ("Move block ..."), a block type of the toolbar ("Add block ..."), a collection item's handle ("Move item ...", manual items, and in a dynamic collection the items pinned to a slot); a hint tells screen readers the keys.
| Key | |
|---|---|
| Space, Enter | pick up; picked up: drop where it is |
| Arrow up, Arrow down | one place up or down; at the first or last place of a zone into the zone before or after it |
| Home, End | the first or last place in the zone (in the collection) |
| Escape | puts everything back; so does leaving the grip (Tab, a click elsewhere) |
| Alt+Arrow up, Alt+Arrow down | (not picked up) one place at once, saved at once |
What is picked up moves itself (outlined) and keeps the focus. Zones the pointer cannot drop into are skipped and
said: linked zones, zones that do not take the block type or are inherited, a container block's placeholders for a
container block. A block type picked up from the toolbar starts at the first place of the first zone that takes it;
dropped, it becomes a new block there, and Escape returns it to the toolbar (and the focus to the toolbar's button).
Each step is said in one polite live region ("Zone Main, position 2 of 3."), with the texts of spa_strings.tpl.
After a drop the grip of the block or item keeps the focus, also when the editor draws it again, for a few seconds and
only until the user presses, types or focuses anywhere else. The page scrolls to
follow, smoothly unless the browser asks for reduced motion. Set position in an item's menu stays as it was.
A keyboard move runs through the same callbacks as a pointer drag (keyStart, keyMove, keyDrop, keyCancel of a
sortable list; keyStart, keyOver, keyDrop of a draggable), so it is saved with the same requests.
tests/js/keyboard-move.test.mjs makes each move both ways and compares the calls.
Leaving the editor
The editor saves every change to the draft at once (sidebar fields on change and 500 ms after the last key, inline
texts and the rich text and code editors 500 ms after the last change). Leaving the page therefore asks "There could
be unsaved changes" only when something can still be lost (Core.has_unsaved_changes(), module init):
- a request other than GET is running (a save, a move, a new block; a form without a
methodattribute is sent as POST, as the API expects); - a block's inline text was typed in, pasted into or otherwise changed within the last second (its save has not started yet; an inline text emptied completely is saved too, netgen/layouts-ui skipped an empty value);
- a form in the sidebar or in a dialog holds other values than when it was loaded or last saved (
save_via_formkeeps the state it sent once saved, the form token does not count); that includes the hidden fields the inline texts and the rich text and code editors write into, also after their save failed.
Publish, discard, close and the dialog's Cancel leave without asking, as before. netgen/layouts-ui asked on every
other leave, also right after opening a layout. Not covered: the collection type selector of the sidebar with its own
Apply button (not a form), and a sidebar form whose save the server refused: the editor replaces that form with the
answer and shows its error dialog.
tests/js/unsaved-changes.test.mjs runs both modules from the bundle.
Rich text and code editors
The app bundle (design/standard/javascript/netgen-layouts.js) was written against CKEditor 4 (rich text blocks) and the
Ace editor (HTML snippet and Markdown blocks). Neither is shipped any more: CKEditor 4 is end of life without free
security updates. design/standard/javascript/explayouts-tiptap.js, loaded by app.tpl before the app bundle, provides
window.CKEDITOR and window.ace with exactly the calls the bundle makes of them, backed by Tiptap:
- Rich text: Tiptap with the toolbar CKEditor had there (undo/redo, paragraph format with heading 1-3 and
preformatted, bold, italic, underline, strikethrough, remove formatting, link with "open in a new window", bulleted
and numbered list with indent, block quote, alignment, horizontal line, table). Reads and writes HTML. While the
content is unchanged (also after undoing every change) the stored HTML is returned exactly as it was. Once it
changed, it is written as CKEditor wrote it (
src/editors/ckeditor-html.js):<li>a</li>and<td>x</td>without an inner<p>,<pre>without<code>, tables without a<colgroup>or min-width, alignment asstyle="text-align:center"(the justify plugin's default; nojustifyClasseswere configured),classandstyleon paragraphs, headings, lists, list items and table parts andborder/cellpadding/cellspacingon tables kept. An empty editor gives an empty string. The link form completesexample.com/xtohttps://example.com/x(only when the start looks like a host:www.…,localhost,host:portor a name whose last label is not a file extension, sopage.htmlstays relative) andx@y.detomailto:x@y.de, keeps/…,#…,mailto:andtel:as typed, and refuses addresses such asjavascript:ordata:with a message. Keptstyleattributes are limited to text-align, width, height, border, padding, vertical-align, color, background-color, font-weight, font-style, text-decoration, list-style-type and margin-left, without values containingurl(,expression,javascript:,behavior,-moz-bindingor@import. - Code: one Tiptap code block with syntax highlighting (lowlight/highlight.js: HTML, Markdown). The value is the plain text, byte for byte. Tab and Shift+Tab indent and outdent with spaces, Enter keeps the indentation, Ctrl+S / Cmd+S saves as before, Escape moves the focus from the text to the editor element.
Both run with Tiptap's injectCSS: false: its base rules are in the stylesheet, so no <style> element is added
(style-src without 'unsafe-inline'). The style attributes of the content itself (alignment, table widths) are
still written into the editor's DOM; under an enforced style-src without 'unsafe-inline' the browser would not
show them while editing, though they are kept in the value.
Still normalised once the content changes: <div> and <span> are not in the schema, so their text is kept but the
elements (and their class or style) are not (<div class="box"><span style="color:red">x</span></div> becomes
<p>x</p>); <b>, <i>, <del>, <strike> become <strong>, <em>, <s>; attributes other than those listed
above (for example id, title on blocks, scope on table headers), the <thead> element (its rows stay, in <tbody>) and images are dropped, and a <caption> becomes a first table row.
Not carried over from CKEditor: anchors, bidi buttons, indentation of paragraphs outside lists, paste-from-Word
cleanup. From Ace: syntax checking (the bundle only filtered its DOCTYPE warning), themes, search box, code folding.
Sources are in src/editors/, styles in design/standard/stylesheets/explayouts-tiptap.css (admin4's --a4-*
tokens, light and dark mode, when admin4's stylesheet is on the page; fallbacks otherwise), interface texts in spa_strings.tpl (context design/standard/explayouts_ui_api/spa). Build
(Node 24, exact versions from package-lock.json; the result is byte-identical for the same lock file):
cd extension/explayouts_ui_api npm ci npm run build # writes design/standard/javascript/explayouts-tiptap.js npm test # node --test tests/js/*.test.mjs (happy-dom)
node_modules/ is not committed. The bundles are linked without a version query; a hard reload fetches a rebuilt one.
Documentation
| Document | Description |
|---|---|
| INSTALL.md | Step-by-step installation: activation, dependencies, shipped settings, verification |
| doc/USAGE.md | All endpoints, curl examples, form fragments, customization layers |
| doc/FAQ.md | Answers to the most common questions and problems |
| doc/TODO.md | Known gaps and planned improvements |
| doc/SUPPORT.md | How and where to get help |
| LICENSE.md | The complete GNU General Public License agreement |
Troubleshooting
Read the FAQ
- Some problems are more common than others. The most common ones are listed in doc/FAQ.md.
Use our support systems
- If you have questions not handled by this document or the FAQ, you can reach us via 7x : se7enx.com.
- If you find a bug or defect, please report it to the Exponential Layouts UI API: Issue Tracker.