domma-cms 1.8.24 → 1.8.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/CLAUDE.md +31 -5
- package/admin/js/app.js +1 -1
- package/admin/js/templates/docs/tutorial-editor.html +156 -0
- package/admin/js/templates/documentation.html +4 -0
- package/admin/js/templates/tutorials.html +10 -0
- package/admin/js/views/doc-pages.js +1 -1
- package/package.json +1 -1
- package/server/services/docs.js +1 -0
- package/server/services/sidebar-migration.js +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.25] - 2026-10-18
|
|
4
|
+
|
|
5
|
+
- **Tutorial: The Editor.** A walk through a working session in the editor - the list, the tabs, coloured text, the live preview, saving and drafts, and the settings (font, wrapping, smooth typing, the admin menu folding away). Under Documentation › Tutorials, first in the list on every site.
|
|
6
|
+
|
|
3
7
|
## [1.8.24] - 2026-10-18
|
|
4
8
|
|
|
5
9
|
- **The page editor is a workspace.** Pages and blocks open side by side, a tab each, the way a code editor works. On the left, a list: **This page** (the blocks the open page uses), every page as a tree and every block, with a filter box - click one and it opens in a tab; **+** starts a new page. Across the top, the open tabs: a dot marks unsaved work, **×** or a middle-click closes one (unsaved changes are kept as a draft and come back when you open it again). The toolbar's three buttons - or **Ctrl+\** - switch between editor and preview, editor only and preview only; preview only has the width presets, Refresh and Share link that the Live Preview tab used to. **Ctrl+Shift+E** hides the list; fullscreen hides the admin. The page's details, SEO, DConfig and history are in a dock on the right (the sliders button). The status bar at the bottom shows where you are, **Unsaved**, **Discard**, **Save** (Ctrl+S - the tab you are on: a page together with the changed blocks it uses) and **Save all** (every open page and block). Each page is in the form on its own: switch tabs freely, nothing is lost. A page address reached from the Pages list or a link opens in a tab of the workspace already up; the open tabs come back after a reload. A block no open page uses is previewed on its own instead of "the preview cannot show it".
|
package/CLAUDE.md
CHANGED
|
@@ -583,9 +583,33 @@ ones plugins open. The width is keyed off the slideover's TITLE
|
|
|
583
583
|
**View width**: `.view-container` caps at 1200px with no `margin: 0 auto`, so a view
|
|
584
584
|
sits left with the remainder empty. Plugin views lift the cap via
|
|
585
585
|
`:has(> .dm-plugin-shell)` - the shell is stamped by `plugin-chrome.js`, so this
|
|
586
|
-
reaches every plugin without the author doing anything.
|
|
587
|
-
same way, keyed on a marker element (`#
|
|
588
|
-
|
|
586
|
+
reaches every plugin without the author doing anything. Three core editors lift it the
|
|
587
|
+
same way, keyed on a marker element (`#pe-workspace`, `#ep-editor-tabs`, `.fb-fields-layout`).
|
|
588
|
+
|
|
589
|
+
## The editor workspace
|
|
590
|
+
|
|
591
|
+
Pages, blocks and components are all edited in ONE screen, `admin/js/views/page-editor.js` +
|
|
592
|
+
`templates/page-editor.html`, which answers every editor address (`/pages/new`, `/pages/edit<path>`,
|
|
593
|
+
`/blocks/new`, `/blocks/edit/<name>`, `/components/new`, `/components/edit/<name>` - `SCREEN` in
|
|
594
|
+
`lib/page-editor-arrange.js`). There is no separate Blocks or Components editor any more (1.8.24); a
|
|
595
|
+
list's Edit, a link or a reload opens a tab in the workspace, and a `…/new` address asks for a name.
|
|
596
|
+
Pure parts: `lib/editor-workspace-arrange.js` (tabs, explorer rows, layouts, keys, sample values),
|
|
597
|
+
`lib/page-blocks-arrange.js`, `lib/editor-hop.js` (the HELD set + drafts per kind: a closed tab's
|
|
598
|
+
draft comes back without a question; leaving the workspace clears the held set).
|
|
599
|
+
|
|
600
|
+
- One page at a time is in the form (`cur`); other open pages are parked state in `docs`. Blocks are
|
|
601
|
+
working copies in `working`, components in `comps` (compiled as typed through `POST /api/components/compile`).
|
|
602
|
+
- **A block's and a component's source are written in the body's own editor** (`lib/plain-editor.js`):
|
|
603
|
+
the same `textarea.editor-textarea` + `.editor-line-numbers` gutter under `#editor-body`, so the
|
|
604
|
+
lettering settings (`--pe-font/size/line`, admin.css "Editor lettering") reach them by the same rule.
|
|
605
|
+
The syntax colours are a `<pre>` behind the textarea carrying the SAME `editor-textarea` class, so the
|
|
606
|
+
two layers cannot take different fonts (`tests/admin/plain-editor.test.js` guards it). Never give the
|
|
607
|
+
text and its colour layer separate font rules - that is how the caret ended up beside the wrong letter.
|
|
608
|
+
`lib/simple-editor.js` (its own overlay and font rules) is for the plugin code editor only.
|
|
609
|
+
- The address bar follows the active tab (`addressFor`); `history.state.pe` marks the workspace's own
|
|
610
|
+
address so a reload restores the remembered tabs (`startingTabs`). The router guard opens an editor
|
|
611
|
+
address in place (`active.open`) and treats a workspace whose `#pe-workspace` root is gone (the 404
|
|
612
|
+
page replaced it without `router:afterChange`) as dead.
|
|
589
613
|
|
|
590
614
|
## Admin loading states
|
|
591
615
|
|
|
@@ -626,8 +650,10 @@ Keep `E.loader` for work in progress (a save, an upload), where there is no shap
|
|
|
626
650
|
authenticate via the `dm_session` cookie (`server/services/viewerSession.js`), which is read **only** by the public
|
|
627
651
|
page renderer; `authenticate()` stays Bearer-only, so no write route accepts ambient credentials.
|
|
628
652
|
**Editor preview**: `POST /api/pages/preview/full` renders unsaved `{urlPath, frontmatter, body}` through the real
|
|
629
|
-
`renderPage`; the editor injects the result as iframe `srcdoc`. `
|
|
630
|
-
by Save and preview on purpose - fork them and the preview starts lying.
|
|
653
|
+
`renderPage`; the editor injects the result as iframe `srcdoc`. `frontmatterFrom()` in `page-editor.js` is shared
|
|
654
|
+
by Save and preview on purpose - fork them and the preview starts lying. A block no open page uses is previewed
|
|
655
|
+
on its own as `[block template="…" <sample attributes> /]` (`blockAloneBody`); a component in the component
|
|
656
|
+
preview page (`admin/preview/component-preview.html`).
|
|
631
657
|
**Share links**: `server/services/previewLinks.js` + `GET /_preview?token=…`. The token goes in the QUERY STRING, not
|
|
632
658
|
a path param - Fastify caps a route param at 100 chars and a signed token is ~350, so `/_preview/:token` silently
|
|
633
659
|
404s. Revocation needs the server-side index (`content/preview-links.json`); a JWT alone cannot be withdrawn.
|
package/admin/js/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{installSmoothCarets as K}from"./lib/smooth-caret.js";import{views as J}from"./views/index.js";import{withPluginChrome as O}from"./lib/plugin-chrome.js";import{api as c,getUser as x,isAuthenticated as m,logout as Y,sendToLogin as Q}from"./api.js";import{claimDrafts as X}from"./lib/draft-owner.js";import{isAuthScreen as b,loginHashFor as Z}from"./lib/login-arrange.js";import{installHttpInterceptor as tt}from"./http-interceptor.js";import{installResizableSlideovers as et}from"./lib/slideover-resizable.js";import{installActivityThrobber as it}from"./lib/activity-throbber.js";import{installUpdateNotice as ot}from"./lib/update-notice.js";import{isPathAllowed as at,redirectFor as st}from"./lib/admin-scope.js";import{deliverDesktop as nt,desktopActive as rt,notificationSettings as lt}from"./lib/desktop-notify.js";import{avatarUrl as ct}from"./lib/avatar.js";import{installCalculator as mt}from"./lib/calculator.js";import{takeOverviewPass as pt,withDashboardStrip as _}from"./lib/dashboards-strip.js";import{installHotkeys as dt}from"./lib/hotkeys-ui.js";import{applyAdminBrand as V}from"./lib/admin-brand.js";import{installRouterCatchUp as ut}from"./lib/router-catch-up.js";import{installSidebarDrawer as ht}from"./lib/sidebar-drawer.js";$(()=>{K(document),tt(),mt(),ot(),it(),et(),ht(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:i}=await import("./lib/admin-theme.js");m()?await i(c,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let p=null,h=null,l="/",w=!1,g=null;function D(t){if(P[t])return E.toast(`${P[t]} is switched off on this site.`,{type:"info"}),"/";if(t.split("?")[0]==="/my-dashboard")return"/dashboards";const i=y[t]||st(p,t);return i||((t.split("?")[0]||"/")==="/"&&l&&l!=="/"&&!pt()?y[l]||l:null)}const y={},P={};R.use(async(t,i,e)=>{if(b(t.path))return e();if(!m()){Q();return}const o=D(t.path);if(o){R.navigate(o);return}e()});async function f(){try{const t=await c.get("/auth/permissions");p=t.adminScope||null,h=t.adminBrand||null,V(h),l=t.adminHome||"/";const i=t.permissions||[];return w=at(p,"/settings")&&i.some(e=>e==="settings"||e.startsWith("settings.")),g=i.some(e=>e==="notifications"||e.startsWith("notifications.")),i}catch{return p=null,l="/",h||V(null),w=!1,g=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{f()});let N=null,A=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(N){A=!0;return}N=(async()=>{do{A=!1;try{await j(await f())}catch{}}while(A)})().finally(()=>{N=null})});async function j(t){const{renderAdminSidebar:i}=await import("./lib/sidebar-renderer.js");await i({mount:"#admin-sidebar",permissions:t,adminScope:p,brand:h})}M.subscribe("router:afterChange",({to:t})=>{b(t.path)&&(u&&(clearInterval(u),u=null),p=null,l="/",w=!1,g=null)});const W=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/dashboards",view:"dashboards",title:"Dashboards - Domma CMS"},{path:"/my-dashboard",view:"dashboards",title:"Dashboards - Domma CMS"},{path:"/pages",view:"pages",title:"Pages - Domma CMS"},{path:"/pages/new",view:"pageEditor",title:"New Page - Domma CMS"},{path:"/pages/edit/*",view:"pageEditor",title:"Edit Page - Domma CMS"},{path:"/media",view:"media",title:"Media - Domma CMS"},{path:"/menus",view:"menus",title:"Menus - Domma CMS"},{path:"/context-menus",view:"contextMenus",title:"Context Menus - Domma CMS"},{path:"/context-menus/new",view:"contextMenuEditor",title:"New Context Menu - Domma CMS"},{path:"/context-menus/edit/:slug",view:"contextMenuEditor",title:"Edit Context Menu - Domma CMS"},{path:"/menus/new",view:"menuEditor",title:"New Menu - Domma CMS"},{path:"/menus/edit/:slug",view:"menuEditor",title:"Edit Menu - Domma CMS"},{path:"/menu-locations",view:"menuLocations",title:"Menu Locations - Domma CMS"},{path:"/navigation",view:"menusRedirect",title:"Menus - Domma CMS"},{path:"/layouts",view:"layouts",title:"Layouts - Domma CMS"},{path:"/settings",view:"settings",title:"Settings - Domma CMS"},{path:"/theme",view:"theme",title:"Theme - Domma CMS"},{path:"/users",view:"users",title:"Users - Domma CMS"},{path:"/users/new",view:"userEditor",title:"New User - Domma CMS"},{path:"/users/edit/:id",view:"userEditor",title:"Edit User - Domma CMS"},{path:"/plugins",view:"plugins",title:"Plugins - Domma CMS"},{path:"/plugins/code/:name",view:"pluginCode",title:"Plugin Code - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/tutorials/crud",view:"tutorialCrud",title:"Building a CRUD App - Domma CMS"},{path:"/tutorials/plugin",view:"tutorialPlugin",title:"Writing a Tool - Domma CMS"},{path:"/tutorials/forms",view:"tutorialForms",title:"Form Follow-Up - Domma CMS"},{path:"/docs/components",view:"componentsReference",title:"Components Reference - Domma CMS"},{path:"/docs/components-howto",view:"componentsHowto",title:"Components How-To - Domma CMS"},{path:"/docs/components-walkthrough",view:"componentsWalkthrough",title:"Components Walkthrough - Domma CMS"},{path:"/docs/components-rules",view:"componentsRules",title:"Components Rules - Domma CMS"},{path:"/docs/usage/pages",view:"usagePages",title:"Pages - Usage - Domma CMS"},{path:"/docs/usage/media",view:"usageMedia",title:"Media - Usage - Domma CMS"},{path:"/docs/usage/navigation",view:"usageNavigation",title:"Navigation - Usage - Domma CMS"},{path:"/docs/usage/dconfig",view:"usageDconfig",title:"DConfig - Usage - Domma CMS"},{path:"/docs/usage/shortcodes",view:"usageShortcodes",title:"Shortcodes - Usage - Domma CMS"},{path:"/docs/usage/site-settings",view:"usageSiteSettings",title:"Site Settings - Usage - Domma CMS"},{path:"/docs/usage/plugins",view:"usagePlugins",title:"Plugins - Usage - Domma CMS"},{path:"/docs/usage/users-roles",view:"usageUsersRoles",title:"Users & Roles - Usage - Domma CMS"},{path:"/docs/usage/views",view:"usageViews",title:"Views - Usage - Domma CMS"},{path:"/docs/usage/actions",view:"usageActions",title:"Actions - Usage - Domma CMS"},{path:"/docs/usage/cta-shortcode",view:"usageCtaShortcode",title:"CTA Shortcode - Usage - Domma CMS"},{path:"/docs/usage/editions",view:"usageEditions",title:"Editions & Licences - Usage - Domma CMS"},{path:"/docs/usage/collections",view:"usageCollections",title:"Collections & Forms - Usage - Domma CMS"},{path:"/docs/usage/blocks",view:"usageBlocks",title:"Blocks - Usage - Domma CMS"},{path:"/docs/usage/tools",view:"usageTools",title:"Built-in Tools - Usage - Domma CMS"},{path:"/docs/usage/dashboard",view:"usageDashboard",title:"Dashboard - Usage - Domma CMS"},{path:"/docs/plugins",view:"pluginGuides",title:"Plugin Guides - Domma CMS"},{path:"/docs/plugins/*",view:"pluginGuide",title:"Plugin Guide - Domma CMS"},{path:"/docs/api/authentication",view:"apiRefAuthentication",title:"Authentication - API - Domma CMS"},{path:"/docs/api/pages",view:"apiRefPages",title:"Pages - API - Domma CMS"},{path:"/docs/api/settings",view:"apiRefSettings",title:"Settings - API - Domma CMS"},{path:"/docs/api/layouts",view:"apiRefLayouts",title:"Layouts - API - Domma CMS"},{path:"/docs/api/navigation",view:"apiRefNavigation",title:"Navigation - API - Domma CMS"},{path:"/docs/api/media",view:"apiRefMedia",title:"Media - API - Domma CMS"},{path:"/docs/api/users",view:"apiRefUsers",title:"Users - API - Domma CMS"},{path:"/docs/api/plugins",view:"apiRefPlugins",title:"Plugins - API - Domma CMS"},{path:"/docs/api/collections",view:"apiRefCollections",title:"Collections - API - Domma CMS"},{path:"/docs/api/views",view:"apiRefViews",title:"Views API - Domma CMS"},{path:"/docs/api/actions",view:"apiRefActions",title:"Actions API - Domma CMS"},{path:"/docs/api/forms",view:"apiRefForms",title:"Forms API - Domma CMS"},{path:"/docs/api/scaffold",view:"apiRefScaffold",title:"Scaffold API - Domma CMS"},{path:"/docs/api/external",view:"apiRefExternal",title:"External API & Tokens - Domma CMS"},{path:"/docs/api/content",view:"apiRefContent",title:"Content API - Domma CMS"},{path:"/docs/api/builder",view:"apiRefBuilder",title:"API Builder - Domma CMS"},{path:"/api-reference",view:"apiReference",title:"API Reference - Domma CMS"},{path:"/collections",view:"collections",title:"Collections - Domma CMS"},{path:"/collections/new",view:"collectionEditor",title:"New Collection - Domma CMS"},{path:"/collections/edit/:slug",view:"collectionEditor",title:"Edit Collection - Domma CMS"},{path:"/collections/:slug/entries",view:"collectionEntries",title:"Entries - Domma CMS"},{path:"/forms",view:"forms",title:"Forms - Domma CMS"},{path:"/forms/new",view:"formEditor",title:"New Form - Domma CMS"},{path:"/forms/edit/:slug",view:"formEditor",title:"Edit Form - Domma CMS"},{path:"/forms/:slug/submissions",view:"formSubmissions",title:"Submissions - Domma CMS"},{path:"/views",view:"viewsList",title:"Views - Domma CMS"},{path:"/views/new",view:"viewEditor",title:"New View - Domma CMS"},{path:"/views/edit/:slug",view:"viewEditor",title:"Edit View - Domma CMS"},{path:"/views/:slug/preview",view:"viewPreview",title:"View Preview - Domma CMS"},{path:"/actions",view:"actionsList",title:"Actions - Domma CMS"},{path:"/actions/new",view:"actionEditor",title:"New Action - Domma CMS"},{path:"/actions/edit/:slug",view:"actionEditor",title:"Edit Action - Domma CMS"},{path:"/pro/docs",view:"proDocs",title:"Pro Documentation - Domma CMS"},{path:"/blocks",view:"blocks",title:"Blocks - Domma CMS"},{path:"/blocks/new",view:"pageEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"pageEditor",title:"Edit Block - Domma CMS"},{path:"/components",view:"components",title:"Components - Domma CMS"},{path:"/components/new",view:"pageEditor",title:"New Component - Domma CMS"},{path:"/components/edit/:name",view:"pageEditor",title:"Edit Component - Domma CMS"},{path:"/my-profile",view:"myProfile",title:"My Profile - Domma CMS"},{path:"/roles",view:"roles",title:"Roles & Permissions - Domma CMS"},{path:"/roles/edit/:id",view:"roleEditor",title:"Edit Role - Domma CMS"},{path:"/effects",view:"effects",title:"Effects - Domma CMS"},{path:"/search",view:"search",title:"Search - Domma CMS"},{path:"/content-api",view:"contentApi",title:"Content API - Domma CMS"},{path:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/calculator",view:"calculator",title:"Calculator - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/seo",view:"seo",title:"SEO - Domma CMS"},{path:"/seo/:tab",view:"seo",title:"SEO - Domma CMS"},{path:"/wordpress-import",view:"wordpressImport",title:"Import from WordPress - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - Domma CMS"},{path:"/calls/new",view:"callNew",title:"Call now - Domma CMS"},{path:"/system/notifications",view:"notifications",title:"Notifications - Domma CMS"},{path:"/api-tokens",view:"apiTokens",title:"API Tokens - Domma CMS"},{path:"/api-endpoints",view:"apiEndpoints",title:"API Builder - Domma CMS"},{path:"/api-endpoints/new",view:"apiEndpointEditor",title:"New API Endpoint - Domma CMS"},{path:"/api-endpoints/edit/:id",view:"apiEndpointEditor",title:"Edit API Endpoint - Domma CMS"},{path:"/projects",view:"projects",title:"Projects - Domma CMS"},{path:"/projects/new",view:"projects",title:"New Project - Domma CMS"},{path:"/projects/edit/:slug",view:"projects",title:"Edit Project - Domma CMS"},{path:"/projects/:slug/settings",view:"projectSettings",title:"Project settings - Domma CMS"},{path:"/projects/:slug/pages",view:"pages",title:"Project pages - Domma CMS"},{path:"/projects/:slug/collections",view:"collections",title:"Project collections - Domma CMS"},{path:"/projects/:slug/forms",view:"forms",title:"Project forms - Domma CMS"},{path:"/projects/:slug/actions",view:"actionsList",title:"Project actions - Domma CMS"},{path:"/projects/:slug/menus",view:"menus",title:"Project menus - Domma CMS"},{path:"/projects/:slug/blocks",view:"blocks",title:"Project blocks - Domma CMS"},{path:"/projects/:slug/components",view:"components",title:"Project components - Domma CMS"},{path:"/projects/:slug/views",view:"viewsList",title:"Project views - Domma CMS"},{path:"/projects/:slug/roles",view:"roles",title:"Project roles - Domma CMS"},{path:"/projects/:slug/users",view:"users",title:"Project users - Domma CMS"},{path:"/projects/:slug/apis",view:"apiEndpoints",title:"Project APIs - Domma CMS"},{path:"/projects/:slug",view:"projectDetail",title:"Project - Domma CMS"},{path:"/login",view:"login",title:"Sign in - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}},{path:"/reset-password",view:"login",title:"Reset Password - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}}];M.subscribe("router:afterChange",async({to:t,from:i})=>{if(!b(t.path)){if($("#admin-sidebar").show(),$("#admin-topbar").show(),b(i?.path)){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await U();const e=await f(),o=D(t.path);if(o&&R.navigate(o),j(e),lt.read().signInToasts)try{const a=await c.system.notifications.list().catch(()=>[]),n=(Array.isArray(a)?a:[]).filter(s=>s.unread&&["warning","critical"].includes(s.data?.severity)).slice(0,3);for(const s of n){const v=d(s.data?.title||""),T=d((s.data?.body||"").slice(0,120));E.toast(`${v} - ${T}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}B()}}),M.subscribe("router:afterChange",()=>{setTimeout(()=>{$(".btn-primary, .btn-danger").length&&Domma.effects.reveal(".btn-primary, .btn-danger",{animation:"fade",stagger:40,duration:300})},50)});const H=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function q(t){return/^\/(pages|menus|context-menus|collections|forms|actions|views|blocks|components|users|roles|api-endpoints)\/(new|edit\/)/.test(t)}M.subscribe("router:afterChange",({to:t})=>{const i=t?String(t.path).split("?")[0]:"";if(!t||!q(i))return;const o=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!o||!/\/new$/.test(i))return;let a=0;const n=60,s=1500,v=()=>{a+=n;for(const T of H){const C=document.querySelector(T);if(!C)continue;if(Array.from(C.options||[]).find(z=>z.value===o)){C.value=o,C.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(v,n)};setTimeout(v,n)});const L="cms_card_states",k=S.get(L)||{};M.subscribe("router:afterChange",()=>{setTimeout(()=>{$("#view-container .card-collapsible").each(function(){const t=$(this).find(".card-header h2, .card-header h3").first().text().trim();t&&k[t]==="collapsed"&&$(this).addClass("card-collapsed")})},200)}),$("#view-container").on("click",".card-collapsible .card-header",function(t){if($(t.target).closest("button, a").length)return;const i=$(this).closest(".card");i.toggleClass("card-collapsed");const e=$(this).find("h2, h3").first().text().trim();e&&(k[e]=i.hasClass("card-collapsed")?"collapsed":"open",S.set(L,k))}),dt(),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash.startsWith("#/login")||window.location.hash.startsWith("#/reset-password"))return;const i=document.querySelector("#view-container .view-header button.btn-primary");i&&(t.preventDefault(),i.click())});const r={...J},G={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},calculator:{displayName:"Calculator",icon:"calculator"},analytics:{displayName:"Analytics",icon:"chart-bar"},seo:{displayName:"SEO",icon:"search"},wordpressImport:{displayName:"Import from WordPress",icon:"download"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"Site menus",icon:"menu"},contextMenus:{displayName:"Context Menus",icon:"menu"},theme:{displayName:"Theme",icon:"droplet"},collections:{displayName:"Collections",icon:"database"},forms:{displayName:"Forms",icon:"layout"},viewsList:{displayName:"Views",icon:"eye"},actionsList:{displayName:"Actions",icon:"zap"},apiEndpoints:{displayName:"API Builder",icon:"code"},blocks:{displayName:"Blocks",icon:"box"},components:{displayName:"Components",icon:"component"},notifications:{displayName:"Notifications",icon:"bell"},roles:{displayName:"Roles",icon:"shield"},users:{displayName:"Users",icon:"users"},settings:{displayName:"Site Settings",icon:"settings"},effects:{displayName:"Effects",icon:"sparkles"},layouts:{displayName:"Layouts",icon:"layout"},plugins:{displayName:"Marketplace",icon:"store"},myProfile:{displayName:"My Profile",icon:"user"},apiTokens:{displayName:"API Tokens",icon:"key"},search:{displayName:"Search",icon:"search"},contentApi:{displayName:"Content API",icon:"file-text"},dashboard:{displayName:"Dashboard",icon:"home"},dashboards:{displayName:"Dashboards",icon:"home"},pageEditor:{displayName:"Pages",section:"Edit",icon:"file-text"},menuEditor:{displayName:"Site menus",section:"Edit",icon:"menu"},menuLocations:{displayName:"Site menus",section:"Locations",icon:"map-pin"},contextMenuEditor:{displayName:"Context Menus",section:"Edit",icon:"menu"},collectionEditor:{displayName:"Collections",section:"Edit",icon:"database"},collectionEntries:{displayName:"Collections",section:"Entries",icon:"database"},formEditor:{displayName:"Forms",section:"Edit",icon:"layout"},formSubmissions:{displayName:"Forms",section:"Submissions",icon:"inbox"},viewEditor:{displayName:"Views",section:"Edit",icon:"eye"},viewPreview:{displayName:"Views",section:"Preview",icon:"eye"},actionEditor:{displayName:"Actions",section:"Edit",icon:"zap"},apiEndpointEditor:{displayName:"API Builder",section:"Edit",icon:"code"},roleEditor:{displayName:"Roles",section:"Edit",icon:"shield"},userEditor:{displayName:"Users",section:"Edit",icon:"users"},projectDetail:{displayName:"Projects",section:"Overview",icon:"folder"},projectSettings:{displayName:"Projects",section:"Settings",icon:"folder"},pluginCode:{displayName:"Marketplace",section:"Code",icon:"code"}};for(const[t,i]of Object.entries(G))r[t]&&(r[t]=O(r[t],{plugin:t,licence:"Built in",...i}));r.dashboard=_(r.dashboard);const I=[...W];async function U(){if(m())try{const t=await c.plugins.adminConfig(),i=new Set(I.map(e=>e.path));for(const e of t.routes||[])i.has(e.path)||I.push(e);for(const[e,o]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${o.entry}`);r[e]=O(a[o.exportName],(t.meta||{})[e])}catch{}r.dashboard=_(r.dashboard);for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(y[`/${e}`]=o);for(const[e,{enabled:o,displayName:a}]of Object.entries(t.tools||{}))o===!1&&(P[`/${e}`]=a);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const a=document.createElement("link");a.id=e,a.rel="stylesheet",a.href=o,document.head.appendChild(a)}}catch{}}function B(){const t=x();if(!t||$("#topbar-user-name").length)return;const e={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role,o=ct(t);$("#topbar-user").html(`
|
|
1
|
+
import{installSmoothCarets as K}from"./lib/smooth-caret.js";import{views as J}from"./views/index.js";import{withPluginChrome as O}from"./lib/plugin-chrome.js";import{api as c,getUser as x,isAuthenticated as m,logout as Y,sendToLogin as Q}from"./api.js";import{claimDrafts as X}from"./lib/draft-owner.js";import{isAuthScreen as b,loginHashFor as Z}from"./lib/login-arrange.js";import{installHttpInterceptor as tt}from"./http-interceptor.js";import{installResizableSlideovers as et}from"./lib/slideover-resizable.js";import{installActivityThrobber as it}from"./lib/activity-throbber.js";import{installUpdateNotice as ot}from"./lib/update-notice.js";import{isPathAllowed as at,redirectFor as st}from"./lib/admin-scope.js";import{deliverDesktop as nt,desktopActive as rt,notificationSettings as lt}from"./lib/desktop-notify.js";import{avatarUrl as ct}from"./lib/avatar.js";import{installCalculator as mt}from"./lib/calculator.js";import{takeOverviewPass as pt,withDashboardStrip as _}from"./lib/dashboards-strip.js";import{installHotkeys as dt}from"./lib/hotkeys-ui.js";import{applyAdminBrand as V}from"./lib/admin-brand.js";import{installRouterCatchUp as ut}from"./lib/router-catch-up.js";import{installSidebarDrawer as ht}from"./lib/sidebar-drawer.js";$(()=>{K(document),tt(),mt(),ot(),it(),et(),ht(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:i}=await import("./lib/admin-theme.js");m()?await i(c,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let p=null,h=null,l="/",w=!1,g=null;function D(t){if(P[t])return E.toast(`${P[t]} is switched off on this site.`,{type:"info"}),"/";if(t.split("?")[0]==="/my-dashboard")return"/dashboards";const i=y[t]||st(p,t);return i||((t.split("?")[0]||"/")==="/"&&l&&l!=="/"&&!pt()?y[l]||l:null)}const y={},P={};R.use(async(t,i,e)=>{if(b(t.path))return e();if(!m()){Q();return}const o=D(t.path);if(o){R.navigate(o);return}e()});async function f(){try{const t=await c.get("/auth/permissions");p=t.adminScope||null,h=t.adminBrand||null,V(h),l=t.adminHome||"/";const i=t.permissions||[];return w=at(p,"/settings")&&i.some(e=>e==="settings"||e.startsWith("settings.")),g=i.some(e=>e==="notifications"||e.startsWith("notifications.")),i}catch{return p=null,l="/",h||V(null),w=!1,g=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{f()});let N=null,A=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(N){A=!0;return}N=(async()=>{do{A=!1;try{await j(await f())}catch{}}while(A)})().finally(()=>{N=null})});async function j(t){const{renderAdminSidebar:i}=await import("./lib/sidebar-renderer.js");await i({mount:"#admin-sidebar",permissions:t,adminScope:p,brand:h})}M.subscribe("router:afterChange",({to:t})=>{b(t.path)&&(u&&(clearInterval(u),u=null),p=null,l="/",w=!1,g=null)});const W=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/dashboards",view:"dashboards",title:"Dashboards - Domma CMS"},{path:"/my-dashboard",view:"dashboards",title:"Dashboards - Domma CMS"},{path:"/pages",view:"pages",title:"Pages - Domma CMS"},{path:"/pages/new",view:"pageEditor",title:"New Page - Domma CMS"},{path:"/pages/edit/*",view:"pageEditor",title:"Edit Page - Domma CMS"},{path:"/media",view:"media",title:"Media - Domma CMS"},{path:"/menus",view:"menus",title:"Menus - Domma CMS"},{path:"/context-menus",view:"contextMenus",title:"Context Menus - Domma CMS"},{path:"/context-menus/new",view:"contextMenuEditor",title:"New Context Menu - Domma CMS"},{path:"/context-menus/edit/:slug",view:"contextMenuEditor",title:"Edit Context Menu - Domma CMS"},{path:"/menus/new",view:"menuEditor",title:"New Menu - Domma CMS"},{path:"/menus/edit/:slug",view:"menuEditor",title:"Edit Menu - Domma CMS"},{path:"/menu-locations",view:"menuLocations",title:"Menu Locations - Domma CMS"},{path:"/navigation",view:"menusRedirect",title:"Menus - Domma CMS"},{path:"/layouts",view:"layouts",title:"Layouts - Domma CMS"},{path:"/settings",view:"settings",title:"Settings - Domma CMS"},{path:"/theme",view:"theme",title:"Theme - Domma CMS"},{path:"/users",view:"users",title:"Users - Domma CMS"},{path:"/users/new",view:"userEditor",title:"New User - Domma CMS"},{path:"/users/edit/:id",view:"userEditor",title:"Edit User - Domma CMS"},{path:"/plugins",view:"plugins",title:"Plugins - Domma CMS"},{path:"/plugins/code/:name",view:"pluginCode",title:"Plugin Code - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/tutorials/crud",view:"tutorialCrud",title:"Building a CRUD App - Domma CMS"},{path:"/tutorials/editor",view:"tutorialEditor",title:"The Editor - Domma CMS"},{path:"/tutorials/plugin",view:"tutorialPlugin",title:"Writing a Tool - Domma CMS"},{path:"/tutorials/forms",view:"tutorialForms",title:"Form Follow-Up - Domma CMS"},{path:"/docs/components",view:"componentsReference",title:"Components Reference - Domma CMS"},{path:"/docs/components-howto",view:"componentsHowto",title:"Components How-To - Domma CMS"},{path:"/docs/components-walkthrough",view:"componentsWalkthrough",title:"Components Walkthrough - Domma CMS"},{path:"/docs/components-rules",view:"componentsRules",title:"Components Rules - Domma CMS"},{path:"/docs/usage/pages",view:"usagePages",title:"Pages - Usage - Domma CMS"},{path:"/docs/usage/media",view:"usageMedia",title:"Media - Usage - Domma CMS"},{path:"/docs/usage/navigation",view:"usageNavigation",title:"Navigation - Usage - Domma CMS"},{path:"/docs/usage/dconfig",view:"usageDconfig",title:"DConfig - Usage - Domma CMS"},{path:"/docs/usage/shortcodes",view:"usageShortcodes",title:"Shortcodes - Usage - Domma CMS"},{path:"/docs/usage/site-settings",view:"usageSiteSettings",title:"Site Settings - Usage - Domma CMS"},{path:"/docs/usage/plugins",view:"usagePlugins",title:"Plugins - Usage - Domma CMS"},{path:"/docs/usage/users-roles",view:"usageUsersRoles",title:"Users & Roles - Usage - Domma CMS"},{path:"/docs/usage/views",view:"usageViews",title:"Views - Usage - Domma CMS"},{path:"/docs/usage/actions",view:"usageActions",title:"Actions - Usage - Domma CMS"},{path:"/docs/usage/cta-shortcode",view:"usageCtaShortcode",title:"CTA Shortcode - Usage - Domma CMS"},{path:"/docs/usage/editions",view:"usageEditions",title:"Editions & Licences - Usage - Domma CMS"},{path:"/docs/usage/collections",view:"usageCollections",title:"Collections & Forms - Usage - Domma CMS"},{path:"/docs/usage/blocks",view:"usageBlocks",title:"Blocks - Usage - Domma CMS"},{path:"/docs/usage/tools",view:"usageTools",title:"Built-in Tools - Usage - Domma CMS"},{path:"/docs/usage/dashboard",view:"usageDashboard",title:"Dashboard - Usage - Domma CMS"},{path:"/docs/plugins",view:"pluginGuides",title:"Plugin Guides - Domma CMS"},{path:"/docs/plugins/*",view:"pluginGuide",title:"Plugin Guide - Domma CMS"},{path:"/docs/api/authentication",view:"apiRefAuthentication",title:"Authentication - API - Domma CMS"},{path:"/docs/api/pages",view:"apiRefPages",title:"Pages - API - Domma CMS"},{path:"/docs/api/settings",view:"apiRefSettings",title:"Settings - API - Domma CMS"},{path:"/docs/api/layouts",view:"apiRefLayouts",title:"Layouts - API - Domma CMS"},{path:"/docs/api/navigation",view:"apiRefNavigation",title:"Navigation - API - Domma CMS"},{path:"/docs/api/media",view:"apiRefMedia",title:"Media - API - Domma CMS"},{path:"/docs/api/users",view:"apiRefUsers",title:"Users - API - Domma CMS"},{path:"/docs/api/plugins",view:"apiRefPlugins",title:"Plugins - API - Domma CMS"},{path:"/docs/api/collections",view:"apiRefCollections",title:"Collections - API - Domma CMS"},{path:"/docs/api/views",view:"apiRefViews",title:"Views API - Domma CMS"},{path:"/docs/api/actions",view:"apiRefActions",title:"Actions API - Domma CMS"},{path:"/docs/api/forms",view:"apiRefForms",title:"Forms API - Domma CMS"},{path:"/docs/api/scaffold",view:"apiRefScaffold",title:"Scaffold API - Domma CMS"},{path:"/docs/api/external",view:"apiRefExternal",title:"External API & Tokens - Domma CMS"},{path:"/docs/api/content",view:"apiRefContent",title:"Content API - Domma CMS"},{path:"/docs/api/builder",view:"apiRefBuilder",title:"API Builder - Domma CMS"},{path:"/api-reference",view:"apiReference",title:"API Reference - Domma CMS"},{path:"/collections",view:"collections",title:"Collections - Domma CMS"},{path:"/collections/new",view:"collectionEditor",title:"New Collection - Domma CMS"},{path:"/collections/edit/:slug",view:"collectionEditor",title:"Edit Collection - Domma CMS"},{path:"/collections/:slug/entries",view:"collectionEntries",title:"Entries - Domma CMS"},{path:"/forms",view:"forms",title:"Forms - Domma CMS"},{path:"/forms/new",view:"formEditor",title:"New Form - Domma CMS"},{path:"/forms/edit/:slug",view:"formEditor",title:"Edit Form - Domma CMS"},{path:"/forms/:slug/submissions",view:"formSubmissions",title:"Submissions - Domma CMS"},{path:"/views",view:"viewsList",title:"Views - Domma CMS"},{path:"/views/new",view:"viewEditor",title:"New View - Domma CMS"},{path:"/views/edit/:slug",view:"viewEditor",title:"Edit View - Domma CMS"},{path:"/views/:slug/preview",view:"viewPreview",title:"View Preview - Domma CMS"},{path:"/actions",view:"actionsList",title:"Actions - Domma CMS"},{path:"/actions/new",view:"actionEditor",title:"New Action - Domma CMS"},{path:"/actions/edit/:slug",view:"actionEditor",title:"Edit Action - Domma CMS"},{path:"/pro/docs",view:"proDocs",title:"Pro Documentation - Domma CMS"},{path:"/blocks",view:"blocks",title:"Blocks - Domma CMS"},{path:"/blocks/new",view:"pageEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"pageEditor",title:"Edit Block - Domma CMS"},{path:"/components",view:"components",title:"Components - Domma CMS"},{path:"/components/new",view:"pageEditor",title:"New Component - Domma CMS"},{path:"/components/edit/:name",view:"pageEditor",title:"Edit Component - Domma CMS"},{path:"/my-profile",view:"myProfile",title:"My Profile - Domma CMS"},{path:"/roles",view:"roles",title:"Roles & Permissions - Domma CMS"},{path:"/roles/edit/:id",view:"roleEditor",title:"Edit Role - Domma CMS"},{path:"/effects",view:"effects",title:"Effects - Domma CMS"},{path:"/search",view:"search",title:"Search - Domma CMS"},{path:"/content-api",view:"contentApi",title:"Content API - Domma CMS"},{path:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/calculator",view:"calculator",title:"Calculator - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/seo",view:"seo",title:"SEO - Domma CMS"},{path:"/seo/:tab",view:"seo",title:"SEO - Domma CMS"},{path:"/wordpress-import",view:"wordpressImport",title:"Import from WordPress - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - Domma CMS"},{path:"/calls/new",view:"callNew",title:"Call now - Domma CMS"},{path:"/system/notifications",view:"notifications",title:"Notifications - Domma CMS"},{path:"/api-tokens",view:"apiTokens",title:"API Tokens - Domma CMS"},{path:"/api-endpoints",view:"apiEndpoints",title:"API Builder - Domma CMS"},{path:"/api-endpoints/new",view:"apiEndpointEditor",title:"New API Endpoint - Domma CMS"},{path:"/api-endpoints/edit/:id",view:"apiEndpointEditor",title:"Edit API Endpoint - Domma CMS"},{path:"/projects",view:"projects",title:"Projects - Domma CMS"},{path:"/projects/new",view:"projects",title:"New Project - Domma CMS"},{path:"/projects/edit/:slug",view:"projects",title:"Edit Project - Domma CMS"},{path:"/projects/:slug/settings",view:"projectSettings",title:"Project settings - Domma CMS"},{path:"/projects/:slug/pages",view:"pages",title:"Project pages - Domma CMS"},{path:"/projects/:slug/collections",view:"collections",title:"Project collections - Domma CMS"},{path:"/projects/:slug/forms",view:"forms",title:"Project forms - Domma CMS"},{path:"/projects/:slug/actions",view:"actionsList",title:"Project actions - Domma CMS"},{path:"/projects/:slug/menus",view:"menus",title:"Project menus - Domma CMS"},{path:"/projects/:slug/blocks",view:"blocks",title:"Project blocks - Domma CMS"},{path:"/projects/:slug/components",view:"components",title:"Project components - Domma CMS"},{path:"/projects/:slug/views",view:"viewsList",title:"Project views - Domma CMS"},{path:"/projects/:slug/roles",view:"roles",title:"Project roles - Domma CMS"},{path:"/projects/:slug/users",view:"users",title:"Project users - Domma CMS"},{path:"/projects/:slug/apis",view:"apiEndpoints",title:"Project APIs - Domma CMS"},{path:"/projects/:slug",view:"projectDetail",title:"Project - Domma CMS"},{path:"/login",view:"login",title:"Sign in - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}},{path:"/reset-password",view:"login",title:"Reset Password - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}}];M.subscribe("router:afterChange",async({to:t,from:i})=>{if(!b(t.path)){if($("#admin-sidebar").show(),$("#admin-topbar").show(),b(i?.path)){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await U();const e=await f(),o=D(t.path);if(o&&R.navigate(o),j(e),lt.read().signInToasts)try{const a=await c.system.notifications.list().catch(()=>[]),n=(Array.isArray(a)?a:[]).filter(s=>s.unread&&["warning","critical"].includes(s.data?.severity)).slice(0,3);for(const s of n){const v=d(s.data?.title||""),T=d((s.data?.body||"").slice(0,120));E.toast(`${v} - ${T}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}B()}}),M.subscribe("router:afterChange",()=>{setTimeout(()=>{$(".btn-primary, .btn-danger").length&&Domma.effects.reveal(".btn-primary, .btn-danger",{animation:"fade",stagger:40,duration:300})},50)});const H=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function q(t){return/^\/(pages|menus|context-menus|collections|forms|actions|views|blocks|components|users|roles|api-endpoints)\/(new|edit\/)/.test(t)}M.subscribe("router:afterChange",({to:t})=>{const i=t?String(t.path).split("?")[0]:"";if(!t||!q(i))return;const o=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!o||!/\/new$/.test(i))return;let a=0;const n=60,s=1500,v=()=>{a+=n;for(const T of H){const C=document.querySelector(T);if(!C)continue;if(Array.from(C.options||[]).find(z=>z.value===o)){C.value=o,C.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(v,n)};setTimeout(v,n)});const L="cms_card_states",k=S.get(L)||{};M.subscribe("router:afterChange",()=>{setTimeout(()=>{$("#view-container .card-collapsible").each(function(){const t=$(this).find(".card-header h2, .card-header h3").first().text().trim();t&&k[t]==="collapsed"&&$(this).addClass("card-collapsed")})},200)}),$("#view-container").on("click",".card-collapsible .card-header",function(t){if($(t.target).closest("button, a").length)return;const i=$(this).closest(".card");i.toggleClass("card-collapsed");const e=$(this).find("h2, h3").first().text().trim();e&&(k[e]=i.hasClass("card-collapsed")?"collapsed":"open",S.set(L,k))}),dt(),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash.startsWith("#/login")||window.location.hash.startsWith("#/reset-password"))return;const i=document.querySelector("#view-container .view-header button.btn-primary");i&&(t.preventDefault(),i.click())});const r={...J},G={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},calculator:{displayName:"Calculator",icon:"calculator"},analytics:{displayName:"Analytics",icon:"chart-bar"},seo:{displayName:"SEO",icon:"search"},wordpressImport:{displayName:"Import from WordPress",icon:"download"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"Site menus",icon:"menu"},contextMenus:{displayName:"Context Menus",icon:"menu"},theme:{displayName:"Theme",icon:"droplet"},collections:{displayName:"Collections",icon:"database"},forms:{displayName:"Forms",icon:"layout"},viewsList:{displayName:"Views",icon:"eye"},actionsList:{displayName:"Actions",icon:"zap"},apiEndpoints:{displayName:"API Builder",icon:"code"},blocks:{displayName:"Blocks",icon:"box"},components:{displayName:"Components",icon:"component"},notifications:{displayName:"Notifications",icon:"bell"},roles:{displayName:"Roles",icon:"shield"},users:{displayName:"Users",icon:"users"},settings:{displayName:"Site Settings",icon:"settings"},effects:{displayName:"Effects",icon:"sparkles"},layouts:{displayName:"Layouts",icon:"layout"},plugins:{displayName:"Marketplace",icon:"store"},myProfile:{displayName:"My Profile",icon:"user"},apiTokens:{displayName:"API Tokens",icon:"key"},search:{displayName:"Search",icon:"search"},contentApi:{displayName:"Content API",icon:"file-text"},dashboard:{displayName:"Dashboard",icon:"home"},dashboards:{displayName:"Dashboards",icon:"home"},pageEditor:{displayName:"Pages",section:"Edit",icon:"file-text"},menuEditor:{displayName:"Site menus",section:"Edit",icon:"menu"},menuLocations:{displayName:"Site menus",section:"Locations",icon:"map-pin"},contextMenuEditor:{displayName:"Context Menus",section:"Edit",icon:"menu"},collectionEditor:{displayName:"Collections",section:"Edit",icon:"database"},collectionEntries:{displayName:"Collections",section:"Entries",icon:"database"},formEditor:{displayName:"Forms",section:"Edit",icon:"layout"},formSubmissions:{displayName:"Forms",section:"Submissions",icon:"inbox"},viewEditor:{displayName:"Views",section:"Edit",icon:"eye"},viewPreview:{displayName:"Views",section:"Preview",icon:"eye"},actionEditor:{displayName:"Actions",section:"Edit",icon:"zap"},apiEndpointEditor:{displayName:"API Builder",section:"Edit",icon:"code"},roleEditor:{displayName:"Roles",section:"Edit",icon:"shield"},userEditor:{displayName:"Users",section:"Edit",icon:"users"},projectDetail:{displayName:"Projects",section:"Overview",icon:"folder"},projectSettings:{displayName:"Projects",section:"Settings",icon:"folder"},pluginCode:{displayName:"Marketplace",section:"Code",icon:"code"}};for(const[t,i]of Object.entries(G))r[t]&&(r[t]=O(r[t],{plugin:t,licence:"Built in",...i}));r.dashboard=_(r.dashboard);const I=[...W];async function U(){if(m())try{const t=await c.plugins.adminConfig(),i=new Set(I.map(e=>e.path));for(const e of t.routes||[])i.has(e.path)||I.push(e);for(const[e,o]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${o.entry}`);r[e]=O(a[o.exportName],(t.meta||{})[e])}catch{}r.dashboard=_(r.dashboard);for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(y[`/${e}`]=o);for(const[e,{enabled:o,displayName:a}]of Object.entries(t.tools||{}))o===!1&&(P[`/${e}`]=a);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const a=document.createElement("link");a.id=e,a.rel="stylesheet",a.href=o,document.head.appendChild(a)}}catch{}}function B(){const t=x();if(!t||$("#topbar-user-name").length)return;const e={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role,o=ct(t);$("#topbar-user").html(`
|
|
2
2
|
${o?`<img id="topbar-avatar" class="topbar-avatar" src="${d(o)}" alt="">`:""}
|
|
3
3
|
<span id="topbar-user-name" class="topbar-user-name">${d(t.name)}</span>
|
|
4
4
|
<span class="topbar-role-badge topbar-role-badge--${d(t.role)}">${e}</span>
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="edit"></span> The Editor</h1>
|
|
3
|
+
<a href="#/tutorials" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All tutorials</a>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="row">
|
|
7
|
+
<div class="col-12">
|
|
8
|
+
<div class="docs-body" id="tutorial-editor">
|
|
9
|
+
|
|
10
|
+
<p class="lead">
|
|
11
|
+
Pages, blocks and components are all written in one place: the editor. It works the way a code
|
|
12
|
+
editor does - a list on the left, a tab for everything you have open, the text and a live preview
|
|
13
|
+
in the middle, and a settings dock on the right. This tutorial walks through a working session:
|
|
14
|
+
opening things, writing with colour, previewing, saving, and setting the editor up the way you
|
|
15
|
+
like it, smooth typing included.
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<hr>
|
|
19
|
+
|
|
20
|
+
<h2>The screen</h2>
|
|
21
|
+
|
|
22
|
+
<p>Open any page from <strong>Pages</strong> and you are in the editor. Five parts:</p>
|
|
23
|
+
|
|
24
|
+
<ul>
|
|
25
|
+
<li><strong>The list</strong> (left): <em>This page</em> - the blocks and components the open page uses -
|
|
26
|
+
then every page as a tree, every block and every component. A filter box at the top narrows all three.
|
|
27
|
+
<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>E</kbd> hides and shows it.</li>
|
|
28
|
+
<li><strong>The tabs</strong> (top): one for each page, block or component you have open, in the order you
|
|
29
|
+
opened them. A dot on a tab means unsaved work.</li>
|
|
30
|
+
<li><strong>The editor and the preview</strong> (middle): the text on the left, the page as your site will
|
|
31
|
+
show it on the right. Drag the divider between them.</li>
|
|
32
|
+
<li><strong>The status bar</strong> (bottom): where you are, <strong>Unsaved</strong>, <strong>Discard</strong>,
|
|
33
|
+
<strong>Save</strong> and <strong>Save all</strong>.</li>
|
|
34
|
+
<li><strong>The dock</strong> (right, the sliders button at the top right): the page's details, SEO, DConfig
|
|
35
|
+
and history. On a block's tab it holds the block's details and sample values; on a component's, its details
|
|
36
|
+
and preview props.</li>
|
|
37
|
+
</ul>
|
|
38
|
+
|
|
39
|
+
<p>The banner above the editor names what you are on - <em>Pages › About</em>, <em>Blocks › contact-card</em>,
|
|
40
|
+
<em>Components › counter</em> - and the address bar follows the tab, so a bookmark or a link always opens the
|
|
41
|
+
right thing.</p>
|
|
42
|
+
|
|
43
|
+
<h2>Opening things</h2>
|
|
44
|
+
|
|
45
|
+
<p>Everything in the list opens with a click, as a tab. So does <strong>Edit</strong> in the Pages, Blocks and
|
|
46
|
+
Components lists, and so does an address someone sends you. If the editor is already up, the new tab joins the
|
|
47
|
+
ones you have; nothing you were doing is disturbed.</p>
|
|
48
|
+
|
|
49
|
+
<p>The <strong>+</strong> beside each section makes a new one. A new page opens straight away as a tab. A new
|
|
50
|
+
block or component asks for its name first (lowercase letters, digits and hyphens - it becomes the tag, so it
|
|
51
|
+
cannot change later), is created, and opens.</p>
|
|
52
|
+
|
|
53
|
+
<p>Close a tab with its <strong>×</strong>, a middle-click, or <kbd>Delete</kbd> with the tab focused. Right-click
|
|
54
|
+
a tab for more: close the others, close those to its right, close all, move it left or right, clone the page
|
|
55
|
+
or duplicate the block or component, or copy its link. The open tabs are remembered: reload, or come back
|
|
56
|
+
tomorrow, and they are where you left them.</p>
|
|
57
|
+
|
|
58
|
+
<div class="alert alert-info">
|
|
59
|
+
<strong>Try it:</strong> open <em>About</em>, then click a block under <em>This page</em>, then a component from
|
|
60
|
+
the <em>Components</em> section. Three tabs. Click between them - each keeps its own text, scroll position
|
|
61
|
+
and unsaved changes.
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<h2>Writing</h2>
|
|
65
|
+
|
|
66
|
+
<h3>A page</h3>
|
|
67
|
+
<p>The page is Markdown with shortcodes, and the text is coloured as you type: headings, code, emphasis, links
|
|
68
|
+
and lists in Markdown's colours, and every <code>[shortcode]</code> - its opening tag, its closing tag, a
|
|
69
|
+
self-closing one - in its own, so a block or a card stands out from the prose around it. The toolbar inserts
|
|
70
|
+
shortcodes with a dialog each; right-click inside one to edit its attributes or jump to the block it names.
|
|
71
|
+
Long shortcodes fold (the chevrons in the gutter, or the fold-all button).</p>
|
|
72
|
+
|
|
73
|
+
<h3>A block</h3>
|
|
74
|
+
<p>A block's tab has two parts, <strong>HTML</strong> and <strong>CSS</strong>, in the same editor the page uses,
|
|
75
|
+
coloured for each language. The <strong>Format</strong> button tidies the HTML's indentation. The dock's
|
|
76
|
+
<strong>Block</strong> panel shows its short tag and shortcode (copy either), its project, <strong>Bundled</strong>
|
|
77
|
+
and where it is used; <strong>Samples</strong> is one value per <code>{{placeholder}}</code> - right-click one
|
|
78
|
+
to insert the placeholder into the template.</p>
|
|
79
|
+
|
|
80
|
+
<h3>A component</h3>
|
|
81
|
+
<p>A component's tab has four parts - <code><template></code>, <code><props></code>,
|
|
82
|
+
<code><script></code> and <code><style></code> - coloured as HTML, JSON, JS and CSS. It is
|
|
83
|
+
compiled as you type; the dot beside the parts says <em>Compiles</em> or <em>Does not compile</em>, and the
|
|
84
|
+
errors show above the text. The dock's <strong>Preview props</strong> are the values the preview tries - change
|
|
85
|
+
one and the preview updates; right-click a row to copy it as an attribute or a whole tag. A component from a
|
|
86
|
+
plugin opens read only, with <strong>Copy to a new component</strong> to make your own.</p>
|
|
87
|
+
|
|
88
|
+
<h2>Previewing</h2>
|
|
89
|
+
|
|
90
|
+
<p>Three layouts, from the toolbar's three buttons or <kbd>Ctrl</kbd>+<kbd>\</kbd> to cycle: editor and preview
|
|
91
|
+
side by side, editor only, preview only. Preview only adds the width presets (full, tablet, phone),
|
|
92
|
+
<strong>Refresh</strong> and <strong>Share link</strong> - a link that lets someone see the page before it is
|
|
93
|
+
published, without signing in, that expires and can be revoked.</p>
|
|
94
|
+
|
|
95
|
+
<p>The preview is the real page, rendered by your site with your theme, fonts and custom CSS, and it shows your
|
|
96
|
+
unsaved changes - to the page and to any open block it uses, outlined so you can find it. A block no open
|
|
97
|
+
page uses is drawn on its own, its placeholders filled from the sample values. A component is drawn on its
|
|
98
|
+
own too, with its preview props.</p>
|
|
99
|
+
|
|
100
|
+
<h2>Saving</h2>
|
|
101
|
+
|
|
102
|
+
<p><strong>Save</strong> (<kbd>Ctrl</kbd>+<kbd>S</kbd>) saves the tab you are on. On a page, that includes the
|
|
103
|
+
blocks of its you changed - and if one of them is also used elsewhere, you are told where and asked first,
|
|
104
|
+
once. <strong>Save all</strong> saves every open page, block and component with changes. A component that does
|
|
105
|
+
not compile is not saved, and the status bar says so.</p>
|
|
106
|
+
|
|
107
|
+
<p>Nothing is lost by closing a tab or leaving the editor: unsaved work is kept as a draft and comes back when
|
|
108
|
+
you open that page, block or component again. Leaving the editor itself with unsaved work asks first, and
|
|
109
|
+
<strong>Cancel</strong> keeps you where you were. <strong>Discard</strong> throws away the current tab's
|
|
110
|
+
changes.</p>
|
|
111
|
+
|
|
112
|
+
<h2>Setting the editor up</h2>
|
|
113
|
+
|
|
114
|
+
<p>The cog in the banner opens the editor settings, kept per browser:</p>
|
|
115
|
+
|
|
116
|
+
<ul>
|
|
117
|
+
<li><strong>Font, size and line spacing</strong>: the lettering of the text, the gutter and the colours - all
|
|
118
|
+
three follow the same setting, on pages, blocks and components alike. Monospace fonts keep shortcodes and
|
|
119
|
+
Markdown lined up.</li>
|
|
120
|
+
<li><strong>Wrap long lines</strong>: off, a long line scrolls sideways and each line number is exactly one
|
|
121
|
+
line.</li>
|
|
122
|
+
<li><strong>Caret movement</strong> - smooth typing. <em>Smooth</em> draws the caret as a small light that
|
|
123
|
+
glides from letter to letter as you type and move, instead of jumping, so your eye never loses it on a busy
|
|
124
|
+
line. Typing, selecting, undo and dictation are exactly as before: only the caret you see is animated.
|
|
125
|
+
<em>Automatic</em> is smooth unless your system asks for reduced motion; <em>Native</em> is the browser's
|
|
126
|
+
own caret. The choice reaches every text box in the admin and your site's forms in this browser.</li>
|
|
127
|
+
<li><strong>Close the admin menu when the editor opens</strong>: the left-hand menu folds to its icon rail for
|
|
128
|
+
the room while you edit, and comes back when you leave the editor.</li>
|
|
129
|
+
</ul>
|
|
130
|
+
|
|
131
|
+
<p>The fullscreen button hides the admin around the editor. On a narrow screen the list and the dock slide over
|
|
132
|
+
the text instead of sitting beside it.</p>
|
|
133
|
+
|
|
134
|
+
<h2>Keys</h2>
|
|
135
|
+
|
|
136
|
+
<table class="table">
|
|
137
|
+
<thead><tr><th>Key</th><th>Does</th></tr></thead>
|
|
138
|
+
<tbody>
|
|
139
|
+
<tr><td><kbd>Ctrl</kbd>+<kbd>S</kbd></td><td>Save the tab you are on</td></tr>
|
|
140
|
+
<tr><td><kbd>Ctrl</kbd>+<kbd>\</kbd></td><td>Next layout: split, editor only, preview only</td></tr>
|
|
141
|
+
<tr><td><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>E</kbd></td><td>Show or hide the list</td></tr>
|
|
142
|
+
<tr><td><kbd>Ctrl</kbd>+<kbd>B</kbd>, <kbd>Ctrl</kbd>+<kbd>I</kbd>, <kbd>Ctrl</kbd>+<kbd>K</kbd></td><td>Bold, italic, link in the page text</td></tr>
|
|
143
|
+
<tr><td><kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd></td><td>Indent and dedent in a block's or component's source</td></tr>
|
|
144
|
+
<tr><td><kbd>Delete</kbd> on a tab, <kbd>Shift</kbd>+<kbd>F10</kbd></td><td>Close it; its menu</td></tr>
|
|
145
|
+
</tbody>
|
|
146
|
+
</table>
|
|
147
|
+
|
|
148
|
+
<div class="alert alert-success">
|
|
149
|
+
<strong>Where next:</strong> <a href="#/docs/usage/pages">Pages</a> for frontmatter, visibility and history,
|
|
150
|
+
<a href="#/docs/usage/blocks">Blocks</a> for placeholders and collection displays,
|
|
151
|
+
<a href="#/docs/components-walkthrough">the Components walkthrough</a> to build one from scratch.
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
@@ -96,6 +96,10 @@
|
|
|
96
96
|
<h2 style="margin-top:2rem;">Tutorials</h2>
|
|
97
97
|
<p class="text-muted">End-to-end walkthroughs.</p>
|
|
98
98
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:1rem;margin-top:1rem;">
|
|
99
|
+
<a href="#/tutorials/editor" class="card" style="text-decoration:none;display:block;">
|
|
100
|
+
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="edit"></span> The Editor</h3>
|
|
101
|
+
<p class="text-muted" style="margin-bottom:0;">One editor for pages, blocks and components: tabs, colour, preview, drafts, smooth typing.</p></div>
|
|
102
|
+
</a>
|
|
99
103
|
<a href="#/tutorials/crud" class="card" style="text-decoration:none;display:block;">
|
|
100
104
|
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="database"></span> Building a CRUD App</h3>
|
|
101
105
|
<p class="text-muted" style="margin-bottom:0;">Collections, forms, actions and pages composed into a working app.</p></div>
|
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
|
|
15
15
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:1rem;margin-top:1.5rem;">
|
|
16
16
|
|
|
17
|
+
<a href="#/tutorials/editor" class="card" style="text-decoration:none;display:block;">
|
|
18
|
+
<div class="card-body">
|
|
19
|
+
<h3 style="margin-top:0;"><span data-icon="edit"></span> The Editor</h3>
|
|
20
|
+
<p class="text-muted" style="margin-bottom:0;">
|
|
21
|
+
Pages, blocks and components in one editor: the list, the tabs, coloured text, the live preview,
|
|
22
|
+
saving and drafts, and the settings - fonts, wrapping, smooth typing.
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
</a>
|
|
26
|
+
|
|
17
27
|
<a href="#/tutorials/crud" class="card" style="text-decoration:none;display:block;">
|
|
18
28
|
<div class="card-body">
|
|
19
29
|
<h3 style="margin-top:0;"><span data-icon="database"></span> Building a CRUD App</h3>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mountScaffolder as i}from"../lib/crud-tutorial.js";function t(o,s){return{templateUrl:`/admin/js/templates/docs/${o}.html`,async onMount(e){typeof s=="function"&&s(e),Domma.icons.scan(),Domma.syntax.scan()}}}export const docViews={tutorialCrud:t("tutorial-crud",o=>{const s=o.find("#tutorial-scaffolder-mount").get(0);s&&i(s,null,null)}),tutorialPlugin:t("tutorial-plugin"),tutorialForms:t("tutorial-forms"),componentsReference:t("components-reference"),componentsHowto:t("components-howto"),componentsWalkthrough:t("components-walkthrough"),componentsRules:t("components-rules")};const a=["pages","media","navigation","dconfig","shortcodes","site-settings","plugins","users-roles","views","actions","cta-shortcode","editions","collections","blocks","tools","dashboard"],c=["authentication","pages","settings","layouts","navigation","media","users","plugins","collections","views","actions","forms","scaffold","external","content","builder"],n=o=>o.split("-").map(s=>s.charAt(0).toUpperCase()+s.slice(1)).join("");for(const o of a)docViews["usage"+n(o)]=t(`usage-${o}`);for(const o of c)docViews["apiRef"+n(o)]=t(`api-${o}`);
|
|
1
|
+
import{mountScaffolder as i}from"../lib/crud-tutorial.js";function t(o,s){return{templateUrl:`/admin/js/templates/docs/${o}.html`,async onMount(e){typeof s=="function"&&s(e),Domma.icons.scan(),Domma.syntax.scan()}}}export const docViews={tutorialCrud:t("tutorial-crud",o=>{const s=o.find("#tutorial-scaffolder-mount").get(0);s&&i(s,null,null)}),tutorialPlugin:t("tutorial-plugin"),tutorialEditor:t("tutorial-editor"),tutorialForms:t("tutorial-forms"),componentsReference:t("components-reference"),componentsHowto:t("components-howto"),componentsWalkthrough:t("components-walkthrough"),componentsRules:t("components-rules")};const a=["pages","media","navigation","dconfig","shortcodes","site-settings","plugins","users-roles","views","actions","cta-shortcode","editions","collections","blocks","tools","dashboard"],c=["authentication","pages","settings","layouts","navigation","media","users","plugins","collections","views","actions","forms","scaffold","external","content","builder"],n=o=>o.split("-").map(s=>s.charAt(0).toUpperCase()+s.slice(1)).join("");for(const o of a)docViews["usage"+n(o)]=t(`usage-${o}`);for(const o of c)docViews["apiRef"+n(o)]=t(`api-${o}`);
|
package/package.json
CHANGED
package/server/services/docs.js
CHANGED
|
@@ -63,6 +63,7 @@ const MAP = [
|
|
|
63
63
|
['usage-tools', 'usage/tools', 'Built-in Tools'],
|
|
64
64
|
['usage-dashboard', 'usage/dashboard', 'Dashboard'],
|
|
65
65
|
['tutorial-crud', 'tutorials/crud', 'Building a CRUD App'],
|
|
66
|
+
['tutorial-editor', 'tutorials/editor', 'The Editor'],
|
|
66
67
|
['tutorial-plugin', 'tutorials/plugin', 'Writing a Tool'],
|
|
67
68
|
['tutorial-forms', 'tutorials/forms', 'Form Follow-Up'],
|
|
68
69
|
['components-reference', 'components', 'Components Reference'],
|
|
@@ -110,6 +110,7 @@ function DOC_TUTORIALS_SUBMENU() {
|
|
|
110
110
|
return {
|
|
111
111
|
text: 'Tutorials', icon: 'document',
|
|
112
112
|
items: [
|
|
113
|
+
{text: 'The Editor', url: '#/tutorials/editor', icon: 'edit'},
|
|
113
114
|
{text: 'Building a CRUD App', url: '#/tutorials/crud', icon: 'database'},
|
|
114
115
|
{text: 'Writing a Tool', url: '#/tutorials/plugin', icon: 'package'},
|
|
115
116
|
{text: 'Form Follow-Up', url: '#/tutorials/forms', icon: 'layout'}
|
|
@@ -397,6 +398,21 @@ export async function ensureFolderKeys(opts = {}) {
|
|
|
397
398
|
changed = true;
|
|
398
399
|
}
|
|
399
400
|
|
|
401
|
+
// The Editor tutorial (1.8.25) joins an existing Tutorials submenu once (`meta.editorTutorial`),
|
|
402
|
+
// first in the list, wherever that submenu is and only if it is not there already.
|
|
403
|
+
if (!menu.meta?.editorTutorial && items.length) {
|
|
404
|
+
const add = (list) => (list || []).forEach(n => {
|
|
405
|
+
if (n && String(n.text || '').trim() === 'Tutorials' && Array.isArray(n.items)
|
|
406
|
+
&& n.items.some(i => i?.url === '#/tutorials/crud') && !n.items.some(i => i?.url === '#/tutorials/editor')) {
|
|
407
|
+
n.items.unshift({text: 'The Editor', url: '#/tutorials/editor', icon: 'edit'});
|
|
408
|
+
}
|
|
409
|
+
if (Array.isArray(n?.items)) add(n.items);
|
|
410
|
+
});
|
|
411
|
+
add(items);
|
|
412
|
+
menu.meta = {...(menu.meta || {}), editorTutorial: true};
|
|
413
|
+
changed = true;
|
|
414
|
+
}
|
|
415
|
+
|
|
400
416
|
// An empty menu is left empty: the renderer's minimal fallback (Dashboard,
|
|
401
417
|
// Menus) is what an admin needs to repair it, and a lone Tools folder would
|
|
402
418
|
// stop that fallback from applying.
|