domma-cms 1.6.47 → 1.6.48
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/admin/js/app.js +2 -2
- package/admin/js/templates/blocks.html +1 -1
- package/admin/js/templates/docs/tutorial-crud.html +1 -1
- package/admin/js/templates/docs/tutorial-plugin.html +114 -11
- package/admin/js/templates/docs/usage-blocks.html +243 -0
- package/admin/js/templates/docs/usage-plugins.html +1 -1
- package/admin/js/templates/documentation.html +5 -1
- package/admin/js/templates/tutorials.html +1 -1
- package/admin/js/views/block-editor.js +4 -4
- package/admin/js/views/blocks.js +4 -4
- package/admin/js/views/doc-pages.js +1 -1
- package/admin/js/views/plugins.js +6 -6
- package/config/menus/admin-sidebar.json +1 -1
- package/package.json +1 -1
- package/server/server.js +1 -0
- package/server/services/docs.js +2 -1
- package/server/services/markdown.js +87 -72
- package/server/services/sidebar-migration.js +13 -1
package/admin/js/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{views as G}from"./views/index.js";import{withPluginChrome as B}from"./lib/plugin-chrome.js";import{api as c,getUser as F,isAuthenticated as m,logout as z,sendToLogin as K}from"./api.js";import{isAuthScreen as C,loginHashFor as J}from"./lib/login-arrange.js";import{installHttpInterceptor as Y}from"./http-interceptor.js";import{installResizableSlideovers as Q}from"./lib/slideover-resizable.js";import{installActivityThrobber as X}from"./lib/activity-throbber.js";import{installUpdateNotice as Z}from"./lib/update-notice.js";import{isPathAllowed as tt,redirectFor as et}from"./lib/admin-scope.js";import{deliverDesktop as it,desktopActive as ot,notificationSettings as at}from"./lib/desktop-notify.js";import{avatarUrl as st}from"./lib/avatar.js";import{installCalculator as nt}from"./lib/calculator.js";import{takeOverviewPass as lt,withDashboardStrip as O}from"./lib/dashboards-strip.js";import{installHotkeys as rt}from"./lib/hotkeys-ui.js";$(()=>{Y(),nt(),Z(),X(),Q(),(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,r="/",h=!1,w=null;function b(t){if(y[t])return E.toast(`${y[t]} is switched off on this site.`,{type:"info"}),"/";if(t.split("?")[0]==="/my-dashboard")return"/dashboards";const i=D[t]||et(p,t);return i||((t.split("?")[0]||"/")==="/"&&r&&r!=="/"&&!lt()?D[r]||r:null)}const D={},y={};R.use(async(t,i,e)=>{if(C(t.path))return e();if(!m()){K();return}const o=b(t.path);if(o){R.navigate(o);return}e()});async function g(){try{const t=await c.get("/auth/permissions");p=t.adminScope||null,r=t.adminHome||"/";const i=t.permissions||[];return h=tt(p,"/settings")&&i.some(e=>e==="settings"||e.startsWith("settings.")),w=i.some(e=>e==="notifications"||e.startsWith("notifications.")),i}catch{return p=null,r="/",h=!1,w=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{g()});let N=null,P=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(N){P=!0;return}N=(async()=>{do{P=!1;try{await A(await g())}catch{}}while(P)})().finally(()=>{N=null})});async function A(t){const{renderAdminSidebar:i}=await import("./lib/sidebar-renderer.js");await i({mount:"#admin-sidebar",permissions:t,adminScope:p})}M.subscribe("router:afterChange",({to:t})=>{C(t.path)&&(u&&(clearInterval(u),u=null),p=null,r="/",h=!1,w=null)});const _=[{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 Plugin - 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/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:"blockEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"blockEditor",title:"Edit Block - Domma CMS"},{path:"/components",view:"components",title:"Components - Domma CMS"},{path:"/components/new",view:"componentEditor",title:"New Component - Domma CMS"},{path:"/components/edit/:name",view:"componentEditor",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:"/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(!C(t.path)){if($("#admin-sidebar").show(),$("#admin-topbar").show(),C(i?.path)){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await I();const e=await g(),o=b(t.path);if(o&&R.navigate(o),A(e),at.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 f=d(s.data?.title||""),U=d((s.data?.body||"").slice(0,120));E.toast(`${f} - ${U}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}T()}}),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 V=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function W(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||!W(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,f=()=>{a+=n;for(const U of V){const v=document.querySelector(U);if(!v)continue;if(Array.from(v.options||[]).find(q=>q.value===o)){v.value=o,v.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(f,n)};setTimeout(f,n)});const x="cms_card_states",j=S.get(x)||{};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&&j[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&&(j[e]=i.hasClass("card-collapsed")?"collapsed":"open",S.set(x,j))}),rt(),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 l={...G},H={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"},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"},blockEditor:{displayName:"Blocks",section:"Edit",icon:"box"},componentEditor:{displayName:"Components",section:"Edit",icon:"component"},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(H))l[t]&&(l[t]=B(l[t],{plugin:t,licence:"Built in",...i}));l.dashboard=O(l.dashboard);const k=[..._];async function I(){if(m())try{const t=await c.plugins.adminConfig(),i=new Set(k.map(e=>e.path));for(const e of t.routes||[])i.has(e.path)||k.push(e);for(const[e,o]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${o.entry}`);l[e]=B(a[o.exportName],(t.meta||{})[e])}catch{}l.dashboard=O(l.dashboard);for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(D[`/${e}`]=o);for(const[e,{enabled:o,displayName:a}]of Object.entries(t.tools||{}))o===!1&&(y[`/${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 T(){const t=F();if(!t||$("#topbar-user-name").length)return;const e={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role,o=st(t);$("#topbar-user").html(`
|
|
1
|
+
import{views as G}from"./views/index.js";import{withPluginChrome as B}from"./lib/plugin-chrome.js";import{api as c,getUser as F,isAuthenticated as m,logout as z,sendToLogin as K}from"./api.js";import{isAuthScreen as C,loginHashFor as J}from"./lib/login-arrange.js";import{installHttpInterceptor as Y}from"./http-interceptor.js";import{installResizableSlideovers as Q}from"./lib/slideover-resizable.js";import{installActivityThrobber as X}from"./lib/activity-throbber.js";import{installUpdateNotice as Z}from"./lib/update-notice.js";import{isPathAllowed as tt,redirectFor as et}from"./lib/admin-scope.js";import{deliverDesktop as it,desktopActive as ot,notificationSettings as at}from"./lib/desktop-notify.js";import{avatarUrl as st}from"./lib/avatar.js";import{installCalculator as nt}from"./lib/calculator.js";import{takeOverviewPass as lt,withDashboardStrip as O}from"./lib/dashboards-strip.js";import{installHotkeys as rt}from"./lib/hotkeys-ui.js";$(()=>{Y(),nt(),Z(),X(),Q(),(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,r="/",h=!1,w=null;function b(t){if(y[t])return E.toast(`${y[t]} is switched off on this site.`,{type:"info"}),"/";if(t.split("?")[0]==="/my-dashboard")return"/dashboards";const i=D[t]||et(p,t);return i||((t.split("?")[0]||"/")==="/"&&r&&r!=="/"&&!lt()?D[r]||r:null)}const D={},y={};R.use(async(t,i,e)=>{if(C(t.path))return e();if(!m()){K();return}const o=b(t.path);if(o){R.navigate(o);return}e()});async function g(){try{const t=await c.get("/auth/permissions");p=t.adminScope||null,r=t.adminHome||"/";const i=t.permissions||[];return h=tt(p,"/settings")&&i.some(e=>e==="settings"||e.startsWith("settings.")),w=i.some(e=>e==="notifications"||e.startsWith("notifications.")),i}catch{return p=null,r="/",h=!1,w=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{g()});let N=null,P=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(N){P=!0;return}N=(async()=>{do{P=!1;try{await A(await g())}catch{}}while(P)})().finally(()=>{N=null})});async function A(t){const{renderAdminSidebar:i}=await import("./lib/sidebar-renderer.js");await i({mount:"#admin-sidebar",permissions:t,adminScope:p})}M.subscribe("router:afterChange",({to:t})=>{C(t.path)&&(u&&(clearInterval(u),u=null),p=null,r="/",h=!1,w=null)});const _=[{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:"blockEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"blockEditor",title:"Edit Block - Domma CMS"},{path:"/components",view:"components",title:"Components - Domma CMS"},{path:"/components/new",view:"componentEditor",title:"New Component - Domma CMS"},{path:"/components/edit/:name",view:"componentEditor",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:"/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(!C(t.path)){if($("#admin-sidebar").show(),$("#admin-topbar").show(),C(i?.path)){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await I();const e=await g(),o=b(t.path);if(o&&R.navigate(o),A(e),at.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 f=d(s.data?.title||""),U=d((s.data?.body||"").slice(0,120));E.toast(`${f} - ${U}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}x()}}),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 V=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function W(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||!W(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,f=()=>{a+=n;for(const U of V){const v=document.querySelector(U);if(!v)continue;if(Array.from(v.options||[]).find(q=>q.value===o)){v.value=o,v.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(f,n)};setTimeout(f,n)});const T="cms_card_states",j=S.get(T)||{};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&&j[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&&(j[e]=i.hasClass("card-collapsed")?"collapsed":"open",S.set(T,j))}),rt(),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 l={...G},H={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"},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"},blockEditor:{displayName:"Blocks",section:"Edit",icon:"box"},componentEditor:{displayName:"Components",section:"Edit",icon:"component"},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(H))l[t]&&(l[t]=B(l[t],{plugin:t,licence:"Built in",...i}));l.dashboard=O(l.dashboard);const k=[..._];async function I(){if(m())try{const t=await c.plugins.adminConfig(),i=new Set(k.map(e=>e.path));for(const e of t.routes||[])i.has(e.path)||k.push(e);for(const[e,o]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${o.entry}`);l[e]=B(a[o.exportName],(t.meta||{})[e])}catch{}l.dashboard=O(l.dashboard);for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(D[`/${e}`]=o);for(const[e,{enabled:o,displayName:a}]of Object.entries(t.tools||{}))o===!1&&(y[`/${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 x(){const t=F();if(!t||$("#topbar-user-name").length)return;const e={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role,o=st(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>
|
|
@@ -20,4 +20,4 @@ import{views as G}from"./views/index.js";import{withPluginChrome as B}from"./lib
|
|
|
20
20
|
<span data-icon="log-out"></span>
|
|
21
21
|
<span>Sign out</span>
|
|
22
22
|
</a>
|
|
23
|
-
`);const a=$("#topbar-bell").get(0);a&&(a.hidden=!0);const n=$("#topbar-settings").get(0);n&&(n.hidden=!h),$("#topbar-logout-btn").on("click",s=>{s.preventDefault(),z()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),L(),u||(u=setInterval(L,6e4))}function d(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}let u=null;async function L(){if(!(!m()||w===!1))try{const{count:t}=await c.system.notifications.unreadCount();$("#topbar-bell").removeAttr("hidden");const i=$("#topbar-bell-badge");t>0?i.text(t>99?"99+":String(t)).removeAttr("hidden"):i.attr("hidden","").text(""),t>0&&ot()&&it(await c.system.notifications.list().catch(()=>[]))}catch{}}(async()=>{if(!m()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash.startsWith("#/login")||(window.location.hash=J(window.location.hash));else if(await I(),F()){const a=await g(),n=(window.location.hash||"#/").slice(1)||"/",s=b(n);s&&(window.location.hash=`#${s}`),await A(a),
|
|
23
|
+
`);const a=$("#topbar-bell").get(0);a&&(a.hidden=!0);const n=$("#topbar-settings").get(0);n&&(n.hidden=!h),$("#topbar-logout-btn").on("click",s=>{s.preventDefault(),z()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),L(),u||(u=setInterval(L,6e4))}function d(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}let u=null;async function L(){if(!(!m()||w===!1))try{const{count:t}=await c.system.notifications.unreadCount();$("#topbar-bell").removeAttr("hidden");const i=$("#topbar-bell-badge");t>0?i.text(t>99?"99+":String(t)).removeAttr("hidden"):i.attr("hidden","").text(""),t>0&&ot()&&it(await c.system.notifications.list().catch(()=>[]))}catch{}}(async()=>{if(!m()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash.startsWith("#/login")||(window.location.hash=J(window.location.hash));else if(await I(),F()){const a=await g(),n=(window.location.hash||"#/").slice(1)||"/",s=b(n);s&&(window.location.hash=`#${s}`),await A(a),x()}const t=R._extractParams.bind(R);R._extractParams=function(o,a){const n=a.indexOf("?");if(n!==-1&&(a=a.slice(0,n)),o.endsWith("/*")){const s=o.slice(0,-2);return a.startsWith(s+"/")?{}:null}return t(o,a)};const i=R._renderNotFound.bind(R);let e="";R._renderNotFound=async function(o){return m()&&e!==o&&(e=o,await I(),R._matchRoute(o).route)?(e="",R._handleRouteChange()):(e="",m()&&$("#admin-sidebar").get(0)?.style.display==="none"?(E.toast("That link goes nowhere - here is your home page.",{type:"warning"}),R.navigate("/")):i(o))},R.init({container:"#view-container",routes:k,views:l,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}})})()});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<div class="blx">
|
|
5
5
|
<p class="blx-note">Reusable HTML templates - one look for a collection's entries, a card, a call to action - placed on a
|
|
6
6
|
page with a shortcode.
|
|
7
|
-
<span data-help-title="Blocks" data-help="A block is an HTML template with a placeholder for each field (the field name in double curly braces), and optionally its own CSS. Use it to show collection entries ([collection … display="block" block="name"]), on its own ([block template="name" …]), or by its name as a short tag ([name … /]). Each block's comment at the top says what it expects; right-click a block to copy its shortcode."></span></p>
|
|
7
|
+
<span data-help-title="Blocks" data-help="A block is an HTML template with a placeholder for each field (the field name in double curly braces), and optionally its own CSS. Use it to show collection entries ([collection … display="block" block="name"]), on its own ([block template="name" …]), or by its name as a short tag ([name … /]). Each block's comment at the top says what it expects; right-click a block to copy its shortcode. The book button above opens the full Blocks guide."></span></p>
|
|
8
8
|
|
|
9
9
|
<!-- Hidden file picker driven by the Import banner action -->
|
|
10
10
|
<input class="blx-import-file" type="file" accept=".dmblock.json,application/json" hidden>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="view-header">
|
|
2
|
-
<h1><span data-icon="document"></span> Writing a
|
|
2
|
+
<h1><span data-icon="document"></span> Writing a Tool</h1>
|
|
3
3
|
<a href="#/tutorials" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All tutorials</a>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
<div class="col-12">
|
|
8
8
|
<div class="docs-body">
|
|
9
9
|
|
|
10
|
-
<p>
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
<p>A <strong>Tool</strong> is a screen in the admin with its own place in the sidebar: Notes, Invoices,
|
|
11
|
+
Booking and Email are all Tools. The ones you add yourself are <strong>plugins</strong>. That is the older
|
|
12
|
+
name, and it is still used for the folder they live in, the API under <code>/api/plugins/</code> and the
|
|
13
|
+
files you share. Once a plugin is switched on it is a Tool like any other: it files under
|
|
14
|
+
<strong>Tools</strong> in the sidebar, gets the same banner, and an admin can move it, hide it or give it a
|
|
15
|
+
hotkey.</p>
|
|
16
|
+
<p>Each Tool is a self-contained folder in <code>plugins/</code>, with three required files and optional
|
|
17
|
+
<code>admin/</code>, <code>public/</code> and <code>docs/</code> subdirectories.</p>
|
|
13
18
|
<p>The quickest start is <strong>New plugin</strong> in the banner of <a href="#/plugins">System >
|
|
14
19
|
Plugins</a> (the Marketplace screen). It copies the built-in template into <code>plugins/<slug>/</code>
|
|
15
|
-
with a server entry, config defaults, a wired admin view and a <code>CLAUDE.md</code>,
|
|
20
|
+
with a server entry, config defaults, a wired admin view, a starter user guide and a <code>CLAUDE.md</code>,
|
|
21
|
+
switched on. Restart
|
|
16
22
|
the server to activate it. The rest of this page explains what those files do.</p>
|
|
17
23
|
|
|
18
24
|
<h3>Directory structure</h3>
|
|
@@ -81,7 +87,9 @@
|
|
|
81
87
|
<tr>
|
|
82
88
|
<td><code>admin.sidebar</code></td>
|
|
83
89
|
<td>array</td>
|
|
84
|
-
<td>
|
|
90
|
+
<td>The Tool's sidebar entries. Each may name its default <code>folder</code> (<code>tools</code>
|
|
91
|
+
unless you say otherwise), a <code>group</code> inside it, a <code>hotkey</code> and a
|
|
92
|
+
<code>countUrl</code> for its badge - see section 5.</td>
|
|
85
93
|
</tr>
|
|
86
94
|
<tr>
|
|
87
95
|
<td><code>admin.routes</code></td>
|
|
@@ -252,7 +260,94 @@ export const myPluginView = {
|
|
|
252
260
|
|
|
253
261
|
<hr>
|
|
254
262
|
|
|
255
|
-
<h3>5.
|
|
263
|
+
<h3>5. Making it feel like a Tool</h3>
|
|
264
|
+
<p>These are what make a screen look and behave like the built-in Tools. None is required, and each one is
|
|
265
|
+
small.</p>
|
|
266
|
+
|
|
267
|
+
<h4>Its place in the sidebar</h4>
|
|
268
|
+
<pre class="code-block"><code>"sidebar": [
|
|
269
|
+
{
|
|
270
|
+
"text": "Invoices",
|
|
271
|
+
"url": "#/plugins/invoices",
|
|
272
|
+
"icon": "file-text",
|
|
273
|
+
"folder": "tools",
|
|
274
|
+
"group": "Business",
|
|
275
|
+
"hotkey": "i",
|
|
276
|
+
"permission": "invoices",
|
|
277
|
+
"countUrl": "/api/plugins/invoices/_count"
|
|
278
|
+
}
|
|
279
|
+
]</code></pre>
|
|
280
|
+
<ul>
|
|
281
|
+
<li><code>folder</code> is where the Tool starts: <code>overview</code>, <code>content</code>,
|
|
282
|
+
<code>data</code>, <code>tools</code> (the default), <code>system</code> or <code>documentation</code>.
|
|
283
|
+
<code>group</code> puts it in a sub-folder by name, made if it is not there. An admin can move the Tool
|
|
284
|
+
anywhere in <strong>Content > Site menus > Admin sidebar</strong>, and their choice wins.</li>
|
|
285
|
+
<li><code>hotkey</code> is one letter: <kbd>G</kbd> then that letter opens the Tool. The built-in Tools claim
|
|
286
|
+
their letters first, and the first Tool to ask for a free one gets it.</li>
|
|
287
|
+
<li>A Tool is known by its <code>url</code>, so give each entry its own.</li>
|
|
288
|
+
</ul>
|
|
289
|
+
|
|
290
|
+
<h4>A badge with a popover</h4>
|
|
291
|
+
<p>A <code>countUrl</code> on the entry draws a count badge beside it. The route answers
|
|
292
|
+
<code>{count, label, tone, details}</code>. <code>tone</code> is <code>danger</code> (red, something is late),
|
|
293
|
+
<code>live</code> (a pulsing dot, happening now) or <code>info</code>. <code>details</code> fills the popover
|
|
294
|
+
shown on hover: <code>{title, rows: [{label, value}], items: [{text, meta}], empty}</code>, all plain
|
|
295
|
+
text. Counts refresh every minute. When your screen changes the number, tell the sidebar straight away with
|
|
296
|
+
<code>sidebarCount(url)</code> from the tool kit (below). With Dashboard Pro, the same answer is
|
|
297
|
+
offered as a Dashboard widget.</p>
|
|
298
|
+
<pre class="code-block"><code>fastify.get('/_count', { preHandler: [authenticate] }, async () => {
|
|
299
|
+
const overdue = await countOverdue();
|
|
300
|
+
return {
|
|
301
|
+
count: overdue,
|
|
302
|
+
label: `${overdue} overdue`,
|
|
303
|
+
tone: overdue ? 'danger' : 'info',
|
|
304
|
+
details: { title: 'Invoices', rows: [{ label: 'Overdue', value: String(overdue) }], empty: 'Nothing overdue.' }
|
|
305
|
+
};
|
|
306
|
+
});</code></pre>
|
|
307
|
+
|
|
308
|
+
<h4>The banner, help and the tool kit</h4>
|
|
309
|
+
<p>Every Tool screen sits under a banner showing its name, version, date and licence, drawn by the admin. Put
|
|
310
|
+
screen-level buttons (New, Import, a settings cog) <strong>in</strong> the banner with
|
|
311
|
+
<code>addBannerAction</code>. Add a (?) beside anything that needs explaining with a <code>data-help</code>
|
|
312
|
+
attribute (plain text) and <code>helpPopovers</code>. <code>tool-kit.js</code> has the rest:
|
|
313
|
+
<code>openMenuAt</code> for a ⋮ button that opens a row's right-click menu, <code>viewLifetime</code> to
|
|
314
|
+
tidy up listeners when the user leaves, <code>onceClosed</code> to remove a slideover once it closes, and
|
|
315
|
+
<code>sidebarCount</code>.</p>
|
|
316
|
+
<p>Load these <strong>at run time</strong> with <code>import()</code>, never a static import. The admin is
|
|
317
|
+
replaced on every CMS update, so your Tool must keep working, with less, on a CMS that lacks one of them:</p>
|
|
318
|
+
<pre class="code-block"><code>async onMount($container) {
|
|
319
|
+
try {
|
|
320
|
+
const chrome = await import('/admin/js/lib/plugin-chrome.js');
|
|
321
|
+
chrome.addBannerAction?.($container, { icon: 'plus', title: 'New invoice', onClick: () => openNew() });
|
|
322
|
+
const help = await import('/admin/js/lib/help-popover.js');
|
|
323
|
+
help.helpPopovers($container, { position: 'bottom' });
|
|
324
|
+
} catch {
|
|
325
|
+
// an older admin: put the button in your own toolbar instead
|
|
326
|
+
}
|
|
327
|
+
}</code></pre>
|
|
328
|
+
<p>Right-click menus come from <code>E.contextMenu</code> on your rows, and screens stay readable in every theme
|
|
329
|
+
if your CSS uses only the theme's <code>--dm-*</code> variables.</p>
|
|
330
|
+
|
|
331
|
+
<h4>Needing other Tools</h4>
|
|
332
|
+
<p><code>requires</code> lists Tools yours cannot work without: it is not loaded while one of them is off.
|
|
333
|
+
<code>uses</code> lists the ones it can do without, with what is lost
|
|
334
|
+
(<code>{"contacts": "Invite a contact group"}</code>). When an admin switches one of those off, the
|
|
335
|
+
Marketplace shows them that text first.</p>
|
|
336
|
+
|
|
337
|
+
<h4>Data and blocks</h4>
|
|
338
|
+
<p>A Tool that keeps records can ship collections (<code>collections/<slug>/schema.json</code>, created the
|
|
339
|
+
first time it is switched on), forms and pages. To give your public pages a design site owners can change, use
|
|
340
|
+
a <a href="#/docs/usage/blocks">block</a>. Create it in <code>onEnable</code>, which runs every time the Tool
|
|
341
|
+
is switched on, so only when it is not there yet - otherwise you would undo the site owner's changes in
|
|
342
|
+
<strong>Data > Blocks</strong>:</p>
|
|
343
|
+
<pre class="code-block"><code>export async function onEnable({ services }) {
|
|
344
|
+
const exists = await services.blocks.getBlock('invoice-card').then(() => true, () => false);
|
|
345
|
+
if (!exists) await services.blocks.saveBlock('invoice-card', INVOICE_CARD_HTML, { css: INVOICE_CARD_CSS });
|
|
346
|
+
}</code></pre>
|
|
347
|
+
|
|
348
|
+
<hr>
|
|
349
|
+
|
|
350
|
+
<h3>6. Injection snippets (optional)</h3>
|
|
256
351
|
<p>HTML snippets declared in <code>inject.head</code>, <code>inject.headLate</code> and
|
|
257
352
|
<code>inject.bodyEnd</code> are read from the plugin's folder and inserted into every public page. Use
|
|
258
353
|
this for analytics scripts, stylesheets, or widgets. They are read once at start-up.</p>
|
|
@@ -272,15 +367,16 @@ export const myPluginView = {
|
|
|
272
367
|
|
|
273
368
|
<hr>
|
|
274
369
|
|
|
275
|
-
<h3>
|
|
370
|
+
<h3>7. User guide</h3>
|
|
276
371
|
<p>Markdown files in the plugin's <code>docs/</code> folder become pages under <strong>Documentation >
|
|
277
|
-
Plugins</strong> while the
|
|
372
|
+
Plugins</strong> while the Tool is on, shown only to users who can use it.
|
|
278
373
|
<code>guide.md</code> is the first page; further files become tabs. Start with <code>##</code> sections,
|
|
279
|
-
not a top-level heading, and write for site owners
|
|
374
|
+
not a top-level heading, and write for site owners: what it does, getting started, one section per
|
|
375
|
+
screen, settings, permissions. Update it in the same change as the feature.</p>
|
|
280
376
|
|
|
281
377
|
<hr>
|
|
282
378
|
|
|
283
|
-
<h3>
|
|
379
|
+
<h3>8. Switching it on and testing</h3>
|
|
284
380
|
<ol>
|
|
285
381
|
<li>Create the <code>plugins/my-plugin/</code> directory with all three required files (or use
|
|
286
382
|
<strong>New plugin</strong>, which does this and switches it on).</li>
|
|
@@ -294,6 +390,13 @@ export const myPluginView = {
|
|
|
294
390
|
<li>Verify your endpoint: <code>GET /api/plugins/my-plugin/hello</code></li>
|
|
295
391
|
</ol>
|
|
296
392
|
|
|
393
|
+
<h3>9. Sharing it</h3>
|
|
394
|
+
<p>On the Marketplace screen, right-click your Tool and choose <strong>Export .dcmsplugin</strong>. On another
|
|
395
|
+
site, <strong>Install from file</strong> in the Marketplace banner takes that file. Installing a newer version
|
|
396
|
+
of a Tool that is already there updates it in place, keeping its <code>data/</code>, and if anything goes
|
|
397
|
+
wrong the old version is put back. An unsigned file, or an older version than the one installed, asks for
|
|
398
|
+
your confirmation first.</p>
|
|
399
|
+
|
|
297
400
|
<p class="text-muted" style="font-size:.9rem">Tip: use <code>npm run dev</code> during development -
|
|
298
401
|
the server restarts automatically on file changes. Server-side files (<code>plugin.js</code>,
|
|
299
402
|
<code>config.js</code>, <code>plugin.json</code>, snippets) only apply after a restart; the in-admin code
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="box"></span> Blocks</h1>
|
|
3
|
+
<a href="#/documentation" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All usage topics</a>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="row">
|
|
7
|
+
<div class="col-12">
|
|
8
|
+
<div class="docs-body">
|
|
9
|
+
|
|
10
|
+
<p>A <strong>block</strong> is a small piece of your own HTML, with an optional stylesheet, that you write once
|
|
11
|
+
and place anywhere. Its <strong>placeholders</strong> are filled in when the page is drawn: from a collection's
|
|
12
|
+
entries, from a view, or from values you type into the shortcode. Use a block when the built-in displays are
|
|
13
|
+
not quite the design you want. Team cards, product tiles, testimonials, a price table row and a contact panel
|
|
14
|
+
are all one block each, and the data behind them stays in a collection where anyone can edit it.</p>
|
|
15
|
+
<p>Blocks are kept in <strong>Data > Blocks</strong>. Each one is a file in <code>content/blocks/</code>:
|
|
16
|
+
<code><name>.html</code> holds the template, <code><name>.css</code> its styles (optional) and
|
|
17
|
+
<code><name>.meta.json</code> its project and bundled flag (optional).</p>
|
|
18
|
+
|
|
19
|
+
<h3>Three ways to place a block</h3>
|
|
20
|
+
<table class="table table-sm">
|
|
21
|
+
<thead>
|
|
22
|
+
<tr>
|
|
23
|
+
<th>You want</th>
|
|
24
|
+
<th>Write</th>
|
|
25
|
+
<th>Placeholders come from</th>
|
|
26
|
+
</tr>
|
|
27
|
+
</thead>
|
|
28
|
+
<tbody>
|
|
29
|
+
<tr>
|
|
30
|
+
<td>One copy per collection entry</td>
|
|
31
|
+
<td><code>[collection slug="team" display="block" block="team-card" cols="3" /]</code></td>
|
|
32
|
+
<td>Each entry's fields</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr>
|
|
35
|
+
<td>One copy per row of a view (filtered, joined, sorted)</td>
|
|
36
|
+
<td><code>[view slug="open-jobs" display="block" block="job-card" /]</code></td>
|
|
37
|
+
<td>Each row of the view</td>
|
|
38
|
+
</tr>
|
|
39
|
+
<tr>
|
|
40
|
+
<td>One copy by itself, with values you type</td>
|
|
41
|
+
<td><code>[block template="cta-banner" heading="Book a call" /]</code> or the short tag
|
|
42
|
+
<code>[cta-banner heading="Book a call" /]</code></td>
|
|
43
|
+
<td>The shortcode's own attributes</td>
|
|
44
|
+
</tr>
|
|
45
|
+
</tbody>
|
|
46
|
+
</table>
|
|
47
|
+
<p>A block also draws each slide of a carousel or each row of a list group:
|
|
48
|
+
<code>[collection slug="team" display="carousel" block="team-card" /]</code> and
|
|
49
|
+
<code>display="listgroup" block="…"</code> work the same way.</p>
|
|
50
|
+
|
|
51
|
+
<hr>
|
|
52
|
+
|
|
53
|
+
<h3>Blocks with collections and views</h3>
|
|
54
|
+
<p>This is where blocks are most useful. The collection holds the data and the block holds the design, so a new
|
|
55
|
+
entry shows up on the page, styled, as soon as it is saved. There is no page to edit.</p>
|
|
56
|
+
<ul>
|
|
57
|
+
<li><code>display="block"</code> plus <code>block="<name>"</code> draws every entry through the block.</li>
|
|
58
|
+
<li>On a <code>[collection]</code>, <code>cols="2"</code> to <code>cols="6"</code> lays the copies out in a
|
|
59
|
+
responsive grid. Leave it out to stack them. A <code>[view]</code> always stacks them.</li>
|
|
60
|
+
<li>Everything else on <code>[collection]</code> still works: <code>limit</code>, <code>sort</code>,
|
|
61
|
+
<code>order</code>, the <code>where_<field></code> filters, <code>scope="mine"</code>,
|
|
62
|
+
<code>empty</code>, <code>item-class</code> and the per-entry <code>cta</code> buttons (drawn under each
|
|
63
|
+
copy).</li>
|
|
64
|
+
<li>On a <code>[view]</code>, <code>block="…"</code> may be left out when the view's Display tab already names
|
|
65
|
+
a block.</li>
|
|
66
|
+
<li>Visitors can still right-click any copy to filter, sort, group, copy or export, as they can on the other
|
|
67
|
+
displays. Only the fields the block actually uses are offered.</li>
|
|
68
|
+
</ul>
|
|
69
|
+
<p>If the named block does not exist, the page shows <em>Block template "name" not found</em> where the entries
|
|
70
|
+
would be, so a typo is easy to spot.</p>
|
|
71
|
+
|
|
72
|
+
<h3>Blocks on their own</h3>
|
|
73
|
+
<p><code>[block template="name" … /]</code> draws one copy, with each other attribute filling the placeholder
|
|
74
|
+
of the same name. Every block can also be written with its name as the tag, so
|
|
75
|
+
<code>[contact-card full_name="Jane Doe" /]</code> means the same as
|
|
76
|
+
<code>[block template="contact-card" full_name="Jane Doe" /]</code>.</p>
|
|
77
|
+
<ul>
|
|
78
|
+
<li>Short tags are self-closing (end with <code>/]</code>). They work for any block whose name does not clash
|
|
79
|
+
with a built-in shortcode. A block called <code>card</code> or <code>menu</code> can only be placed with
|
|
80
|
+
<code>[block template="…"]</code>.</li>
|
|
81
|
+
<li><code>class</code> and <code>id</code> go on the wrapper around the copy.</li>
|
|
82
|
+
<li>A placeholder with no value comes out empty, and a block that does not exist is removed without a
|
|
83
|
+
message.</li>
|
|
84
|
+
<li>Values are written in double quotes. For a value that itself contains a double quote, use single quotes
|
|
85
|
+
around it instead.</li>
|
|
86
|
+
<li>Blocks are expanded even inside inline code and code fences on a page. To mention a block's shortcode in
|
|
87
|
+
ordinary text, write its opening bracket as <code>&#91;</code>. Inside code that does not work (it shows
|
|
88
|
+
as written), so keep block shortcodes out of code samples on your pages.</li>
|
|
89
|
+
</ul>
|
|
90
|
+
|
|
91
|
+
<hr>
|
|
92
|
+
|
|
93
|
+
<h3>Writing the template</h3>
|
|
94
|
+
<p>A template is ordinary HTML. Domma classes (<code>card</code>, <code>badge</code>, <code>grid</code>,
|
|
95
|
+
<code>btn</code>) and icons (<code><span data-icon="mail"></span></code>) work as they do anywhere
|
|
96
|
+
else on the site. Placeholders are field names in double curly braces:</p>
|
|
97
|
+
<table class="table table-sm">
|
|
98
|
+
<thead>
|
|
99
|
+
<tr>
|
|
100
|
+
<th>Placeholder</th>
|
|
101
|
+
<th>What goes in</th>
|
|
102
|
+
</tr>
|
|
103
|
+
</thead>
|
|
104
|
+
<tbody>
|
|
105
|
+
<tr>
|
|
106
|
+
<td><code>{{field}}</code></td>
|
|
107
|
+
<td>The value, made safe as text (HTML is escaped). Use this almost everywhere.</td>
|
|
108
|
+
</tr>
|
|
109
|
+
<tr>
|
|
110
|
+
<td><code>{{field:DD MMM YYYY}}</code></td>
|
|
111
|
+
<td>The value formatted as a date. Tokens: <code>YYYY YY MMMM MMM MM M DD D dddd ddd HH H hh h mm m ss s A
|
|
112
|
+
a</code>; anything else passes through, so <code>DD/MM/YYYY</code> and <code>HH:mm</code> work. A value
|
|
113
|
+
that is not a date is shown as it is.</td>
|
|
114
|
+
</tr>
|
|
115
|
+
<tr>
|
|
116
|
+
<td><code>{{{field}}}</code></td>
|
|
117
|
+
<td>The value inserted <strong>as HTML</strong>, unescaped. Only for fields you trust, such as rich text
|
|
118
|
+
written by your own editors.</td>
|
|
119
|
+
</tr>
|
|
120
|
+
<tr>
|
|
121
|
+
<td><code>{{_id}}</code>, <code>{{_createdAt}}</code>,
|
|
122
|
+
<code>{{_updatedAt}}</code></td>
|
|
123
|
+
<td>The entry's id and its created and updated times (collections and views only). Combine them with a
|
|
124
|
+
format: <code>{{_createdAt:D MMMM YYYY}}</code>.</td>
|
|
125
|
+
</tr>
|
|
126
|
+
</tbody>
|
|
127
|
+
</table>
|
|
128
|
+
<p>The page is cleaned before it is sent, so anything in a block that could run code (a
|
|
129
|
+
<code><script></code>, an <code>onclick</code> or <code>onerror</code> handler, a
|
|
130
|
+
<code>javascript:</code> link) is removed, exactly as it would be in page content.</p>
|
|
131
|
+
|
|
132
|
+
<h4>Describe the block in a comment</h4>
|
|
133
|
+
<p>Start the template with an HTML comment saying what it is, what it expects and how to use it. The Blocks list
|
|
134
|
+
shows the first line as the block's description, and <strong>Copy shortcode</strong> copies the example.
|
|
135
|
+
Comments are stripped when the block is drawn, so they never reach the page.</p>
|
|
136
|
+
<pre class="code-block"><code><!--
|
|
137
|
+
team-card - One team member: photo, name, role and a short bio.
|
|
138
|
+
|
|
139
|
+
Parameters:
|
|
140
|
+
name - Full name
|
|
141
|
+
role - Job title
|
|
142
|
+
photo - Image URL
|
|
143
|
+
bio - One or two sentences
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
[collection slug="team" display="block" block="team-card" cols="3" /]
|
|
147
|
+
-->
|
|
148
|
+
<div class="card team-card">
|
|
149
|
+
<img class="team-card-photo" src="{{photo}}" alt="{{name}}">
|
|
150
|
+
<div class="card-body">
|
|
151
|
+
<h3>{{name}}</h3>
|
|
152
|
+
<span class="badge badge-secondary">{{role}}</span>
|
|
153
|
+
<p>{{bio}}</p>
|
|
154
|
+
</div>
|
|
155
|
+
</div></code></pre>
|
|
156
|
+
|
|
157
|
+
<h4>Styles</h4>
|
|
158
|
+
<p>A block's <strong>Custom CSS</strong> applies to that block only: the CMS wraps it in
|
|
159
|
+
<code>[data-block="name"] { … }</code>, so plain rules like <code>.team-card-photo { … }</code> cannot leak
|
|
160
|
+
onto the rest of the page. Use CSS nesting for child rules, and the theme's <code>--dm-*</code> variables
|
|
161
|
+
(<code>var(--dm-primary)</code>, <code>var(--dm-border)</code>) so the block follows the site theme and dark
|
|
162
|
+
mode.</p>
|
|
163
|
+
<pre class="code-block"><code>.team-card-photo {
|
|
164
|
+
width: 100%;
|
|
165
|
+
aspect-ratio: 1;
|
|
166
|
+
object-fit: cover;
|
|
167
|
+
border-bottom: 3px solid var(--dm-primary);
|
|
168
|
+
}</code></pre>
|
|
169
|
+
<p><code>@keyframes</code>, <code>@font-face</code> and <code>@import</code> cannot be scoped. Put those in
|
|
170
|
+
<strong>Site Settings > Custom CSS</strong>. A block's stylesheet may be up to 50 KB. It is sent once for
|
|
171
|
+
each shortcode that uses the block, however many entries that shortcode shows.</p>
|
|
172
|
+
|
|
173
|
+
<hr>
|
|
174
|
+
|
|
175
|
+
<h3>The Blocks screen</h3>
|
|
176
|
+
<p><strong>Data > Blocks</strong> lists every block with its description, whether it has CSS, and how many
|
|
177
|
+
pages and views use it.</p>
|
|
178
|
+
<ul>
|
|
179
|
+
<li><strong>All</strong>, <strong>In use</strong> and <strong>Unused</strong> filter the list, and the search
|
|
180
|
+
box finds a block by name or description.</li>
|
|
181
|
+
<li><strong>Click</strong> a block to edit it. <strong>Right-click</strong> it (or use ⋮, or
|
|
182
|
+
<kbd>Shift</kbd>+<kbd>F10</kbd>) to copy its shortcode, see where it is used, duplicate, export or
|
|
183
|
+
delete it.</li>
|
|
184
|
+
<li><strong>New block</strong> and <strong>Import</strong> are in the banner at the top.</li>
|
|
185
|
+
<li>The sidebar badge counts your blocks. Hover over it for the unused ones and the latest changes.</li>
|
|
186
|
+
</ul>
|
|
187
|
+
<p><em>In use</em> is worked out from page sources and view settings. A Tool that draws a block from its own code
|
|
188
|
+
is not counted, so check before deleting a block marked unused.</p>
|
|
189
|
+
|
|
190
|
+
<h3>The block editor</h3>
|
|
191
|
+
<ul>
|
|
192
|
+
<li><strong>Details</strong>: the name (lowercase letters, digits and hyphens, fixed once the block is saved),
|
|
193
|
+
an optional project, and <strong>Bundled</strong>, which ships the block with fresh installs.</li>
|
|
194
|
+
<li><strong>Template HTML</strong>: the editor, with undo, cut, copy, paste and <strong>Format</strong> (tidies
|
|
195
|
+
the indentation). <strong>Edit</strong>, <strong>Split</strong> and <strong>Preview</strong> choose what you
|
|
196
|
+
see.</li>
|
|
197
|
+
<li><strong>Sample values</strong>: one box per placeholder the template uses, filled in for the preview only.
|
|
198
|
+
Nothing in them is saved. Right-click one to insert its placeholder into the template.</li>
|
|
199
|
+
<li><strong>Preview</strong>: the block drawn inside your own site page, with its theme, fonts and Custom CSS.
|
|
200
|
+
Try it at full, tablet or mobile width, as <strong>Three in a grid</strong> (as a collection with
|
|
201
|
+
<code>cols="3"</code> shows it), or in another theme. The site itself is not changed.</li>
|
|
202
|
+
<li><strong>Custom CSS</strong>: the block's scoped stylesheet (see Styles above).</li>
|
|
203
|
+
<li><strong>Placeholder reference</strong>: the table above, at the foot of the screen.</li>
|
|
204
|
+
</ul>
|
|
205
|
+
<p><kbd>Ctrl</kbd>+<kbd>S</kbd> saves from anywhere on the screen. If you leave with unsaved changes you are
|
|
206
|
+
asked first, and your work is kept as a draft for that block until you come back to it.</p>
|
|
207
|
+
|
|
208
|
+
<h3>Copying blocks between sites</h3>
|
|
209
|
+
<p><strong>Export</strong> on a block's right-click menu downloads it as a <code><name>.dmblock.json</code>
|
|
210
|
+
file, with its HTML and CSS. On the other site, use <strong>Import</strong> in the Blocks banner. If a block
|
|
211
|
+
with that name already exists, you are asked before it is replaced.</p>
|
|
212
|
+
|
|
213
|
+
<hr>
|
|
214
|
+
|
|
215
|
+
<h3>Worked example: a team page</h3>
|
|
216
|
+
<ol>
|
|
217
|
+
<li>In <strong>Data > Collections</strong>, create a <code>team</code> collection with the fields
|
|
218
|
+
<code>name</code>, <code>role</code>, <code>photo</code> and <code>bio</code>, and add a few people.</li>
|
|
219
|
+
<li>In <strong>Data > Blocks</strong>, choose <strong>New block</strong>, call it <code>team-card</code> and
|
|
220
|
+
paste the template and CSS from above. Fill in the sample values and check the preview at mobile width and as
|
|
221
|
+
three in a grid. Save.</li>
|
|
222
|
+
<li>On your Team page, write
|
|
223
|
+
<code>[collection slug="team" display="block" block="team-card" cols="3" sort="name" order="asc" /]</code>.</li>
|
|
224
|
+
<li>From now on, adding a person to the collection adds a card to the page. Changing the block restyles every
|
|
225
|
+
card at once.</li>
|
|
226
|
+
</ol>
|
|
227
|
+
|
|
228
|
+
<h3>Who can use Blocks</h3>
|
|
229
|
+
<p>Blocks share the <strong>pages</strong> permission: <em>read</em> to see them, <em>update</em> to create, edit
|
|
230
|
+
and import, and <em>delete</em> to remove them. A block filed under a project is only shown to users who can see
|
|
231
|
+
that project.</p>
|
|
232
|
+
|
|
233
|
+
<h3>Blocks in your own Tools</h3>
|
|
234
|
+
<p>A Tool (plugin) can rely on blocks too. It can render one of its pages with a block shortcode, or
|
|
235
|
+
create the block it needs the first time it is switched on, so the site owner can restyle it here. See <a href="#/tutorials/plugin">Writing a Tool</a>.</p>
|
|
236
|
+
|
|
237
|
+
<p class="text-muted" style="font-size:.9rem;">Full shortcode syntax:
|
|
238
|
+
<a href="#/docs/usage/shortcodes">Shortcodes</a>. Collection displays:
|
|
239
|
+
<a href="#/docs/usage/collections">Collections & Forms</a> and <a href="#/docs/usage/views">Views</a>.</p>
|
|
240
|
+
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
what it does, its screens, settings and permissions.</li>
|
|
121
121
|
<li><a href="#/docs/usage/editions">Editions & Licences</a> - free and Pro, rental and
|
|
122
122
|
perpetual licences, and what happens when one ends.</li>
|
|
123
|
-
<li><a href="#/tutorials/plugin">Writing a
|
|
123
|
+
<li><a href="#/tutorials/plugin">Writing a Tool</a> - build your own.</li>
|
|
124
124
|
</ul>
|
|
125
125
|
|
|
126
126
|
</div>
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="users"></span> Users & Roles</h3>
|
|
59
59
|
<p class="text-muted" style="margin-bottom:0;">Accounts, roles, the role ladder, confined roles and resets.</p></div>
|
|
60
60
|
</a>
|
|
61
|
+
<a href="#/docs/usage/blocks" class="card" style="text-decoration:none;display:block;">
|
|
62
|
+
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="box"></span> Blocks</h3>
|
|
63
|
+
<p class="text-muted" style="margin-bottom:0;">Your own HTML templates for collection entries, views and one-off pieces.</p></div>
|
|
64
|
+
</a>
|
|
61
65
|
<a href="#/docs/usage/tools" class="card" style="text-decoration:none;display:block;">
|
|
62
66
|
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="list"></span> Built-in Tools</h3>
|
|
63
67
|
<p class="text-muted" style="margin-bottom:0;">Contacts, Notes, Todo, Analytics, SEO, notifications and search.</p></div>
|
|
@@ -101,7 +105,7 @@
|
|
|
101
105
|
<p class="text-muted" style="margin-bottom:0;">Emails, webhooks, Actions and what happens after a submission.</p></div>
|
|
102
106
|
</a>
|
|
103
107
|
<a href="#/tutorials/plugin" class="card" style="text-decoration:none;display:block;">
|
|
104
|
-
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="package"></span> Writing a
|
|
108
|
+
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="package"></span> Writing a Tool</h3>
|
|
105
109
|
<p class="text-muted" style="margin-bottom:0;">Server routes, admin screens and page snippets of your own.</p></div>
|
|
106
110
|
</a>
|
|
107
111
|
</div>
|