domma-cms 0.76.0 → 0.77.0
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/CLAUDE.md +1 -1
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/lib/action-editor-arrange.js +1 -0
- package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
- package/admin/js/lib/block-editor-arrange.js +2 -0
- package/admin/js/lib/collection-editor-arrange.js +3 -0
- package/admin/js/lib/collection-entries-arrange.js +1 -0
- package/admin/js/lib/component-editor-arrange.js +27 -0
- package/admin/js/lib/context-menu-editor-arrange.js +1 -0
- package/admin/js/lib/dashboard-arrange.js +1 -0
- package/admin/js/lib/form-editor-arrange.js +1 -0
- package/admin/js/lib/form-submissions-arrange.js +2 -0
- package/admin/js/lib/menu-editor-arrange.js +2 -0
- package/admin/js/lib/page-editor-arrange.js +1 -0
- package/admin/js/lib/plugin-code-arrange.js +1 -0
- package/admin/js/lib/project-page-arrange.js +1 -0
- package/admin/js/lib/role-editor-arrange.js +3 -0
- package/admin/js/lib/user-editor-arrange.js +1 -0
- package/admin/js/lib/view-editor-arrange.js +14 -0
- package/admin/js/templates/action-editor.html +223 -147
- package/admin/js/templates/api-endpoint-editor.html +172 -117
- package/admin/js/templates/block-editor.html +194 -203
- package/admin/js/templates/collection-editor.html +191 -113
- package/admin/js/templates/collection-entries.html +99 -36
- package/admin/js/templates/component-editor.html +128 -126
- package/admin/js/templates/context-menu-editor.html +208 -148
- package/admin/js/templates/dashboard.html +184 -22
- package/admin/js/templates/form-editor.html +41 -26
- package/admin/js/templates/form-submissions.html +78 -26
- package/admin/js/templates/menu-editor.html +57 -17
- package/admin/js/templates/menu-locations.html +54 -11
- package/admin/js/templates/page-editor.html +47 -26
- package/admin/js/templates/plugin-code.html +91 -73
- package/admin/js/templates/project-detail.html +83 -145
- package/admin/js/templates/project-settings.html +88 -53
- package/admin/js/templates/role-editor.html +158 -83
- package/admin/js/templates/user-editor.html +150 -14
- package/admin/js/templates/view-editor.html +203 -180
- package/admin/js/templates/view-preview.html +45 -45
- package/admin/js/views/action-editor.js +7 -1
- package/admin/js/views/api-endpoint-editor.js +2 -3
- package/admin/js/views/block-editor.js +14 -8
- package/admin/js/views/collection-editor.js +64 -5
- package/admin/js/views/collection-entries.js +50 -1
- package/admin/js/views/component-editor.js +2 -28
- package/admin/js/views/context-menu-editor.js +54 -55
- package/admin/js/views/dashboard.js +7 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +11 -1
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +21 -43
- package/admin/js/views/menu-locations.js +16 -1
- package/admin/js/views/page-editor.js +26 -26
- package/admin/js/views/plugin-code.js +23 -4
- package/admin/js/views/project-detail.js +9 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/role-editor.js +11 -3
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/view-preview.js +1 -1
- package/package.json +1 -1
- package/server/routes/api/dashboard.js +32 -4
- package/server/routes/api/forms.js +104 -13
- package/server/routes/api/users.js +12 -2
- package/server/routes/api/views.js +55 -1
- package/server/services/components.js +9 -0
- package/server/services/users.js +2 -2
- package/server/services/views.js +14 -0
- package/admin/js/templates/dashboard/activity-feed.html +0 -3
- package/admin/js/templates/dashboard/cache.html +0 -32
- package/admin/js/templates/dashboard/health-detail.html +0 -2
- package/admin/js/templates/dashboard/journeys.html +0 -17
- package/admin/js/templates/dashboard/kpi-strip.html +0 -34
- package/admin/js/templates/dashboard/spike-feed.html +0 -3
- package/admin/js/templates/dashboard/top-pages.html +0 -3
- package/admin/js/templates/dashboard/traffic-chart.html +0 -3
- package/admin/js/views/block-editor-enhance.js +0 -1
- package/admin/js/views/dashboard/lib/escape.js +0 -1
- package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
- package/admin/js/views/dashboard/widgets/cache.js +0 -1
- package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
- package/admin/js/views/dashboard/widgets/journeys.js +0 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
- package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
- package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
- package/admin/js/views/dashboard/widgets/traffic-chart.js +0 -1
package/CLAUDE.md
CHANGED
|
@@ -325,7 +325,7 @@ Both keep a fallback to their original listener for when Domma predates 0.43.0.
|
|
|
325
325
|
from stored JavaScript - `admin/` is replaced wholesale by the updater, and a stored script running on
|
|
326
326
|
public pages is XSS with extra steps. Adding one means three files in step:
|
|
327
327
|
`ACTION_TYPES` (server validation), `SPECIALS`/`toHandler` in `public/js/context-menus.js` (behaviour),
|
|
328
|
-
and `ACTION_GROUPS` in
|
|
328
|
+
and `ACTION_GROUPS` in `admin/js/lib/context-menu-editor-arrange.js` (the editor's picker; a test checks it against `ACTION_TYPES`). The "specials" - reload/back/forward/copy/cut/paste/
|
|
329
329
|
select-all/site-search - carry a `disabled` **function**, which Domma resolves on every gesture, so
|
|
330
330
|
Paste greys out per right-click rather than per page load. Prefer `disabled` to hiding: a menu that
|
|
331
331
|
changes shape depending on where you clicked is disorienting, and greyed-out is what native menus do.
|
package/admin/js/api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const m="/api";function a(){return S.get("auth_token")}function p(){return S.get("auth_refresh_token")}function y(e){S.set("auth_token",e)}function u(){S.remove("auth_token"),S.remove("auth_refresh_token"),S.remove("auth_user")}async function f(){const e=p();if(!e)throw new Error("No refresh token");const o=await fetch(`${m}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(!o.ok)throw u(),R.navigate("/login"),new Error("Token refresh failed");const{token:n}=await o.json();return y(n),n}async function t(e,o={}){let n=a();const r=i=>({...o.body!==void 0?{"Content-Type":"application/json"}:{},...o.headers,...i?{Authorization:`Bearer ${i}`}:{}});let s=await fetch(`${m}${e}`,{...o,headers:r(n)});if(s.status===401&&p())try{n=await f(),s=await fetch(`${m}${e}`,{...o,headers:r(n)})}catch{return}if(!s.ok){const i=await s.json().catch(()=>({error:"Request failed"})),d=new Error(i.error||i.message||`HTTP ${s.status}`);throw Array.isArray(i.validation)&&(d.validation=i.validation),d.status=s.status,d}return s.status===204?null:s.json()}async function g(e,o){const n=a(),r=n?{Authorization:`Bearer ${n}`}:{},s=await fetch(`${m}${e}`,{method:"POST",headers:r,body:o});if(!s.ok){const i=await s.json().catch(()=>({error:"Upload failed"}));throw new Error(i.error||i.message||`HTTP ${s.status}`)}return s.json()}export const api={auth:{setupStatus:()=>t("/auth/setup-status",{method:"GET"}),setup:e=>t("/auth/setup",{method:"POST",body:JSON.stringify(e)}),login:e=>t("/auth/login",{method:"POST",body:JSON.stringify(e)}),me:()=>t("/auth/me",{method:"GET"}),updateMe:e=>t("/auth/me",{method:"PUT",body:JSON.stringify(e)}),refresh:e=>t("/auth/refresh",{method:"POST",body:JSON.stringify({refreshToken:e})}),forgotPassword:e=>t("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e})}),resetPassword:(e,o)=>t("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e,password:o})})},pages:{list:()=>t("/pages",{method:"GET"}),get:e=>t(`/pages${e}`,{method:"GET"}),create:e=>t("/pages",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/pages${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/pages${e}`,{method:"DELETE"}),preview:e=>t("/pages/preview",{method:"POST",body:JSON.stringify({markdown:e})}),previewFull:(e,o,n)=>t("/pages/preview/full",{method:"POST",body:JSON.stringify({urlPath:e,frontmatter:o,body:n})}),previewLinks:{list:e=>t(`/pages/preview-links${e?`?urlPath=${encodeURIComponent(e)}`:""}`,{method:"GET"}).then(o=>o.links||[]),create:(e,o,n)=>t("/pages/preview-links",{method:"POST",body:JSON.stringify({urlPath:e,expiresIn:o,label:n})}),revoke:e=>t(`/pages/preview-links/${e}`,{method:"DELETE"})},tags:()=>t("/pages/tags",{method:"GET"}).then(e=>e.tags||[])},settings:{get:()=>t("/settings",{method:"GET"}),save:e=>t("/settings",{method:"PUT",body:JSON.stringify(e)}),getCustomCss:()=>t("/settings/custom-css",{method:"GET"}),saveCustomCss:e=>t("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:e})})},theme:{get:()=>t("/theme",{method:"GET"}),save:e=>t("/theme",{method:"PUT",body:JSON.stringify(e)}),catalog:()=>t("/theme/catalog",{method:"GET"}),admin:()=>t("/theme/admin",{method:"GET"}),themeTokens:e=>t(`/theme/themes/${encodeURIComponent(e)}`,{method:"GET"}),compile:e=>t("/theme/compile",{method:"POST",body:JSON.stringify(e)})},navigation:{get:()=>t("/navigation",{method:"GET"}),save:e=>t("/navigation",{method:"PUT",body:JSON.stringify(e)})},menus:{list:()=>t("/menus",{method:"GET"}),get:e=>t(`/menus/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/menus",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/menus/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/menus/${encodeURIComponent(e)}`,{method:"DELETE"}),duplicate:e=>t(`/menus/${encodeURIComponent(e)}/duplicate`,{method:"POST"})},contextMenus:{list:()=>t("/context-menus",{method:"GET"}),get:e=>t(`/context-menus/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/context-menus",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/context-menus/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/context-menus/${encodeURIComponent(e)}`,{method:"DELETE"}),duplicate:e=>t(`/context-menus/${encodeURIComponent(e)}/duplicate`,{method:"POST"}),actionTypes:()=>t("/context-menus/action-types",{method:"GET"})},menuLocations:{get:()=>t("/menu-locations",{method:"GET"}),save:e=>t("/menu-locations",{method:"PUT",body:JSON.stringify(e)}),registry:()=>t("/menu-locations/registry",{method:"GET"})},projects:{list:()=>t("/projects",{method:"GET"}),listWithCounts:()=>t("/projects?counts=1",{method:"GET"}),get:e=>t(`/projects/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/projects",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/projects/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/projects/${encodeURIComponent(e)}`,{method:"DELETE"}),artefacts:e=>t(`/projects/${encodeURIComponent(e)}/artefacts`,{method:"GET"}),untagAll:e=>t(`/projects/${encodeURIComponent(e)}/untag-all`,{method:"POST"})},apiTokens:{list:()=>t("/api-tokens",{method:"GET"}),create:e=>t("/api-tokens",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/api-tokens/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),revoke:e=>t(`/api-tokens/${encodeURIComponent(e)}`,{method:"DELETE"})},apiEndpoints:{list:()=>t("/api-endpoints",{method:"GET"}),get:e=>t(`/api-endpoints/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/api-endpoints",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/api-endpoints/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/api-endpoints/${encodeURIComponent(e)}`,{method:"DELETE"})},layouts:{get:()=>t("/layouts",{method:"GET"}),save:e=>t("/layouts",{method:"PUT",body:JSON.stringify(e)}),create:e=>t("/layouts",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/layouts/${e}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/layouts/${e}`,{method:"DELETE"}),getOptions:()=>t("/layouts/options",{method:"GET"}),saveOptions:e=>t("/layouts/options",{method:"PUT",body:JSON.stringify(e)})},media:{list:()=>t("/media",{method:"GET"}),upload:e=>g("/media",e),delete:e=>t(`/media/${encodeURIComponent(e)}`,{method:"DELETE"}),rename:(e,o)=>t(`/media/${encodeURIComponent(e)}`,{method:"PATCH",body:JSON.stringify({newName:o})}),info:e=>t(`/media/${encodeURIComponent(e)}/info`,{method:"GET"}),transform:(e,o)=>t(`/media/${encodeURIComponent(e)}/transform`,{method:"POST",body:JSON.stringify(o)})},users:{list:()=>t("/users",{method:"GET"}),get:e=>t(`/users/${e}`,{method:"GET"}),create:e=>t("/users",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/users/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/users/${e}`,{method:"DELETE"})},plugins:{list:()=>t("/plugins",{method:"GET"}),scaffold:e=>t("/plugins/scaffold",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/plugins/${e}`,{method:"PUT",body:JSON.stringify(o)}),files:e=>t(`/plugins/${e}/files`,{method:"GET"}),readFile:(e,o)=>t(`/plugins/${e}/file?path=${encodeURIComponent(o)}`,{method:"GET"}),writeFile:(e,o,n)=>t(`/plugins/${e}/file`,{method:"PUT",body:JSON.stringify({path:o,content:n})}),deleteFile:(e,o)=>t(`/plugins/${e}/file?path=${encodeURIComponent(o)}`,{method:"DELETE"}),restart:()=>t("/plugins/restart",{method:"POST"}),adminConfig:()=>t("/plugins/admin-config",{method:"GET"}),async exportBundle(e,{includeData:o=!1}={}){const n=a(),r=o?"?includeData=1":"",s=await fetch(`${m}/plugins/${encodeURIComponent(e)}/export${r}`,{headers:n?{Authorization:`Bearer ${n}`}:{}});if(!s.ok){const T=await s.json().catch(()=>({}));throw new Error(T.error||`Export failed (${s.status})`)}const i=s.headers.get("Content-Disposition")||"",d=/filename="([^"]+)"/.exec(i)?.[1],c=await s.blob(),h=URL.createObjectURL(c),l=document.createElement("a");l.href=h,l.download=d||`${e}.dcmsplugin`,document.body.appendChild(l),l.click(),l.remove(),URL.revokeObjectURL(h)},async installFile(e,{confirmUnsigned:o=!1,confirmUpgrade:n=!1,confirmDowngrade:r=!1}={}){const s=new FormData;o&&s.append("confirmUnsigned","true"),n&&s.append("confirmUpgrade","true"),r&&s.append("confirmDowngrade","true"),s.append("file",e);const i=a(),d=await fetch(`${m}/plugins/install-upload`,{method:"POST",headers:i?{Authorization:`Bearer ${i}`}:{},body:s}),c=await d.json().catch(()=>({}));if(d.status===409){const h=new Error(c.error||"Plugin already installed");throw h.code="CONFLICT",h.name=c.name,h.installedVersion=c.installedVersion,h.incomingVersion=c.incomingVersion,h}if(!d.ok)throw new Error(c.error||`Install failed (${d.status})`);return c}},marketplace:{catalogue:()=>t("/plugins/marketplace",{method:"GET"}),install:(e,o,{update:n=!1}={})=>t("/plugins/marketplace/install",{method:"POST",body:JSON.stringify({slug:e,version:o,update:n})}),uninstall:e=>t(`/plugins/marketplace/${encodeURIComponent(e)}`,{method:"DELETE"})},collections:{list:()=>t("/collections",{method:"GET"}),proStatus:()=>t("/collections/pro-status",{method:"GET"}),get:e=>t(`/collections/${e}`,{method:"GET"}),create:e=>t("/collections",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/collections/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/collections/${e}`,{method:"DELETE"}),listEntries:(e,o={})=>{const n=new URLSearchParams(o).toString();return t(`/collections/${e}/entries${n?"?"+n:""}`,{method:"GET"})},getEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"GET"}),createEntry:(e,o)=>t(`/collections/${e}/entries`,{method:"POST",body:JSON.stringify({data:o})}),updateEntry:(e,o,n)=>t(`/collections/${e}/entries/${o}`,{method:"PUT",body:JSON.stringify({data:n})}),deleteEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"DELETE"}),clearEntries:e=>t(`/collections/${e}/entries`,{method:"DELETE"}),import:(e,o)=>t(`/collections/${e}/import`,{method:"POST",body:JSON.stringify({entries:o})}),publicList:(e,o={})=>{const n=new URLSearchParams(o).toString();return t(`/collections/${e}/public${n?"?"+n:""}`,{method:"GET"})},getConnections:()=>t("/collections/connections",{method:"GET"}),saveConnections:e=>t("/collections/connections",{method:"PUT",body:JSON.stringify(e)}),migrateStorage:(e,o)=>t(`/collections/${e}/migrate-storage`,{method:"POST",body:JSON.stringify({storage:o})})},forms:{list:()=>t("/forms",{method:"GET"}),create:e=>t("/forms",{method:"POST",body:JSON.stringify(e)}),get:e=>t(`/forms/${e}`,{method:"GET"}),update:(e,o)=>t(`/forms/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/forms/${e}`,{method:"DELETE"}),listSubmissions:e=>t(`/forms/${e}/submissions`,{method:"GET"}),clearSubmissions:e=>t(`/forms/${e}/submissions`,{method:"DELETE"}),deleteSubmission:(e,o)=>t(`/forms/${e}/submissions/${o}`,{method:"DELETE"}),testEmail:e=>t("/forms/test-email",{method:"POST",body:JSON.stringify({to:e})})},views:{list:()=>t("/views",{method:"GET"}),get:e=>t(`/views/${e}`,{method:"GET"}),create:e=>t("/views",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/views/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/views/${e}`,{method:"DELETE"}),execute:(e,o={})=>{const n=new URLSearchParams(o).toString();return t(`/views/${e}/execute${n?"?"+n:""}`,{method:"GET"})},forCollection:e=>t(`/views/collection/${e}`,{method:"GET"})},actions:{list:()=>t("/actions",{method:"GET"}),get:e=>t(`/actions/${e}`,{method:"GET"}),create:e=>t("/actions",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/actions/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/actions/${e}`,{method:"DELETE"}),execute:(e,o)=>t(`/actions/${e}/execute`,{method:"POST",body:JSON.stringify({entryId:o})}),forCollection:e=>t(`/actions/collection/${e}`,{method:"GET"}),checkAccess:(e,o)=>t(`/actions/${e}/check-access`,{method:"POST",body:JSON.stringify({entryIds:o})})},versions:{list:e=>t(`/versions/list${e}`),get:(e,o)=>t(`/versions/get/${encodeURIComponent(o)}${e}`),create:(e,o)=>t(`/versions/create${e}`,{method:"POST",body:JSON.stringify({label:o})}),restore:(e,o)=>t(`/versions/restore/${encodeURIComponent(o)}${e}`,{method:"POST"}),delete:(e,o)=>t(`/versions/delete/${encodeURIComponent(o)}${e}`,{method:"DELETE"}),bulkDelete:(e,o)=>t(`/versions/bulk-delete${e}`,{method:"POST",body:JSON.stringify({filenames:o})}),prune:(e,o)=>t(`/versions/prune${e}`,{method:"POST",body:JSON.stringify({keep:o})})},blocks:{list:()=>t("/blocks",{method:"GET"}),get:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"GET"}),put:(e,o)=>t(`/blocks/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"DELETE"}),async exportBundle(e){const o=a(),n=await fetch(`${m}/blocks/${encodeURIComponent(e)}/export`,{headers:o?{Authorization:`Bearer ${o}`}:{}});if(!n.ok){const d=await n.json().catch(()=>({}));throw new Error(d.error||`Export failed (${n.status})`)}const r=await n.blob(),s=URL.createObjectURL(r),i=document.createElement("a");i.href=s,i.download=`${e}.dmblock.json`,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(s)},async importBundle(e,{overwrite:o=!1}={}){const n=await fetch(`${m}/blocks/import`,{method:"POST",headers:{"Content-Type":"application/json",...a()?{Authorization:`Bearer ${a()}`}:{}},body:JSON.stringify({...e,overwrite:o})});if(n.status===409){const r=await n.json().catch(()=>({})),s=new Error(r.error||"Block already exists");throw s.code="CONFLICT",s.name=r.name,s}if(!n.ok){const r=await n.json().catch(()=>({}));throw new Error(r.error||`Import failed (${n.status})`)}return n.json()}},components:{list:()=>t("/components",{method:"GET"}),get:e=>t(`/components/${encodeURIComponent(e)}`,{method:"GET"}),compile:(e,o)=>t("/components/compile",{method:"POST",body:JSON.stringify({name:e,source:o})}),put:(e,o)=>t(`/components/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/components/${encodeURIComponent(e)}`,{method:"DELETE"}),async exportBundle(e){const o=a(),n=await fetch(`${m}/components/${encodeURIComponent(e)}/export`,{headers:o?{Authorization:`Bearer ${o}`}:{}});if(!n.ok){const d=await n.json().catch(()=>({}));throw new Error(d.error||`Export failed (${n.status})`)}const r=await n.blob(),s=URL.createObjectURL(r),i=document.createElement("a");i.href=s,i.download=`${e}.dmcomponent.json`,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(s)},async importBundle(e,{overwrite:o=!1}={}){const n=await fetch(`${m}/components/import`,{method:"POST",headers:{"Content-Type":"application/json",...a()?{Authorization:`Bearer ${a()}`}:{}},body:JSON.stringify({...e,overwrite:o})});if(n.status===409){const r=await n.json().catch(()=>({})),s=new Error(r.error||"Component already exists");throw s.code="CONFLICT",s.name=r.name,s}if(!n.ok){const r=await n.json().catch(()=>({}));throw new Error(r.error||`Import failed (${n.status})`)}return n.json()}},get:e=>t(e,{method:"GET"}),post:(e,o)=>t(e,{method:"POST",body:JSON.stringify(o)}),put:(e,o)=>t(e,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(e,{method:"DELETE"}),settingsExt:{testEmail:e=>t("/settings/test-email",{method:"POST",body:JSON.stringify({to:e})})},system:{notifications:{list:()=>t("/system/notifications",{method:"GET"}),unreadCount:()=>t("/system/notifications/unread-count",{method:"GET"}),markRead:e=>t(`/system/notifications/${e}/read`,{method:"POST"}),dismiss:e=>t(`/system/notifications/${e}/dismiss`,{method:"POST"}),remove:e=>t(`/system/notifications/${e}`,{method:"DELETE"})}},dashboard:{summary:()=>t("/dashboard/summary",{method:"GET"}),summaryLite:()=>t("/dashboard/summary?lite=1",{method:"GET"})}};export function isAuthenticated(){return!!a()}export function getUser(){return S.get("auth_user")}export function setAuthData({token:e,refreshToken:o,user:n}){e&&y(e),o&&S.set("auth_refresh_token",o),n&&S.set("auth_user",n)}export function logout(){const e=p();e&&fetch(`${m}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})}).catch(()=>{}),u(),R.navigate("/login")}export{t as apiRequest,f as refreshAccessToken,a as getToken,u as clearAuth};
|
|
1
|
+
const m="/api";function d(){return S.get("auth_token")}function p(){return S.get("auth_refresh_token")}function y(e){S.set("auth_token",e)}function f(){S.remove("auth_token"),S.remove("auth_refresh_token"),S.remove("auth_user")}async function T(){const e=p();if(!e)throw new Error("No refresh token");const o=await fetch(`${m}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(!o.ok)throw f(),R.navigate("/login"),new Error("Token refresh failed");const{token:n}=await o.json();return y(n),n}async function t(e,o={}){let n=d();const r=i=>({...o.body!==void 0?{"Content-Type":"application/json"}:{},...o.headers,...i?{Authorization:`Bearer ${i}`}:{}});let s=await fetch(`${m}${e}`,{...o,headers:r(n)});if(s.status===401&&p())try{n=await T(),s=await fetch(`${m}${e}`,{...o,headers:r(n)})}catch{return}if(!s.ok){const i=await s.json().catch(()=>({error:"Request failed"})),a=new Error(i.error||i.message||`HTTP ${s.status}`);throw Array.isArray(i.validation)&&(a.validation=i.validation),a.status=s.status,a}return s.status===204?null:s.json()}async function $(e,o){const n=d(),r=n?{Authorization:`Bearer ${n}`}:{},s=await fetch(`${m}${e}`,{method:"POST",headers:r,body:o});if(!s.ok){const i=await s.json().catch(()=>({error:"Upload failed"}));throw new Error(i.error||i.message||`HTTP ${s.status}`)}return s.json()}export const api={auth:{setupStatus:()=>t("/auth/setup-status",{method:"GET"}),setup:e=>t("/auth/setup",{method:"POST",body:JSON.stringify(e)}),login:e=>t("/auth/login",{method:"POST",body:JSON.stringify(e)}),me:()=>t("/auth/me",{method:"GET"}),updateMe:e=>t("/auth/me",{method:"PUT",body:JSON.stringify(e)}),refresh:e=>t("/auth/refresh",{method:"POST",body:JSON.stringify({refreshToken:e})}),forgotPassword:e=>t("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e})}),resetPassword:(e,o)=>t("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e,password:o})})},pages:{list:()=>t("/pages",{method:"GET"}),get:e=>t(`/pages${e}`,{method:"GET"}),create:e=>t("/pages",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/pages${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/pages${e}`,{method:"DELETE"}),preview:e=>t("/pages/preview",{method:"POST",body:JSON.stringify({markdown:e})}),previewFull:(e,o,n)=>t("/pages/preview/full",{method:"POST",body:JSON.stringify({urlPath:e,frontmatter:o,body:n})}),previewLinks:{list:e=>t(`/pages/preview-links${e?`?urlPath=${encodeURIComponent(e)}`:""}`,{method:"GET"}).then(o=>o.links||[]),create:(e,o,n)=>t("/pages/preview-links",{method:"POST",body:JSON.stringify({urlPath:e,expiresIn:o,label:n})}),revoke:e=>t(`/pages/preview-links/${e}`,{method:"DELETE"})},tags:()=>t("/pages/tags",{method:"GET"}).then(e=>e.tags||[])},settings:{get:()=>t("/settings",{method:"GET"}),save:e=>t("/settings",{method:"PUT",body:JSON.stringify(e)}),getCustomCss:()=>t("/settings/custom-css",{method:"GET"}),saveCustomCss:e=>t("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:e})})},theme:{get:()=>t("/theme",{method:"GET"}),save:e=>t("/theme",{method:"PUT",body:JSON.stringify(e)}),catalog:()=>t("/theme/catalog",{method:"GET"}),admin:()=>t("/theme/admin",{method:"GET"}),themeTokens:e=>t(`/theme/themes/${encodeURIComponent(e)}`,{method:"GET"}),compile:e=>t("/theme/compile",{method:"POST",body:JSON.stringify(e)})},navigation:{get:()=>t("/navigation",{method:"GET"}),save:e=>t("/navigation",{method:"PUT",body:JSON.stringify(e)})},menus:{list:()=>t("/menus",{method:"GET"}),get:e=>t(`/menus/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/menus",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/menus/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/menus/${encodeURIComponent(e)}`,{method:"DELETE"}),duplicate:e=>t(`/menus/${encodeURIComponent(e)}/duplicate`,{method:"POST"})},contextMenus:{list:()=>t("/context-menus",{method:"GET"}),get:e=>t(`/context-menus/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/context-menus",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/context-menus/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/context-menus/${encodeURIComponent(e)}`,{method:"DELETE"}),duplicate:e=>t(`/context-menus/${encodeURIComponent(e)}/duplicate`,{method:"POST"}),actionTypes:()=>t("/context-menus/action-types",{method:"GET"})},menuLocations:{get:()=>t("/menu-locations",{method:"GET"}),save:e=>t("/menu-locations",{method:"PUT",body:JSON.stringify(e)}),registry:()=>t("/menu-locations/registry",{method:"GET"})},projects:{list:()=>t("/projects",{method:"GET"}),listWithCounts:()=>t("/projects?counts=1",{method:"GET"}),get:e=>t(`/projects/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/projects",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/projects/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/projects/${encodeURIComponent(e)}`,{method:"DELETE"}),artefacts:e=>t(`/projects/${encodeURIComponent(e)}/artefacts`,{method:"GET"}),untagAll:e=>t(`/projects/${encodeURIComponent(e)}/untag-all`,{method:"POST"})},apiTokens:{list:()=>t("/api-tokens",{method:"GET"}),create:e=>t("/api-tokens",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/api-tokens/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),revoke:e=>t(`/api-tokens/${encodeURIComponent(e)}`,{method:"DELETE"})},apiEndpoints:{list:()=>t("/api-endpoints",{method:"GET"}),get:e=>t(`/api-endpoints/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/api-endpoints",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/api-endpoints/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/api-endpoints/${encodeURIComponent(e)}`,{method:"DELETE"})},layouts:{get:()=>t("/layouts",{method:"GET"}),save:e=>t("/layouts",{method:"PUT",body:JSON.stringify(e)}),create:e=>t("/layouts",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/layouts/${e}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/layouts/${e}`,{method:"DELETE"}),getOptions:()=>t("/layouts/options",{method:"GET"}),saveOptions:e=>t("/layouts/options",{method:"PUT",body:JSON.stringify(e)})},media:{list:()=>t("/media",{method:"GET"}),upload:e=>$("/media",e),delete:e=>t(`/media/${encodeURIComponent(e)}`,{method:"DELETE"}),rename:(e,o)=>t(`/media/${encodeURIComponent(e)}`,{method:"PATCH",body:JSON.stringify({newName:o})}),info:e=>t(`/media/${encodeURIComponent(e)}/info`,{method:"GET"}),transform:(e,o)=>t(`/media/${encodeURIComponent(e)}/transform`,{method:"POST",body:JSON.stringify(o)})},users:{list:()=>t("/users",{method:"GET"}),get:e=>t(`/users/${e}`,{method:"GET"}),create:e=>t("/users",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/users/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/users/${e}`,{method:"DELETE"})},plugins:{list:()=>t("/plugins",{method:"GET"}),scaffold:e=>t("/plugins/scaffold",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/plugins/${e}`,{method:"PUT",body:JSON.stringify(o)}),files:e=>t(`/plugins/${e}/files`,{method:"GET"}),readFile:(e,o)=>t(`/plugins/${e}/file?path=${encodeURIComponent(o)}`,{method:"GET"}),writeFile:(e,o,n)=>t(`/plugins/${e}/file`,{method:"PUT",body:JSON.stringify({path:o,content:n})}),deleteFile:(e,o)=>t(`/plugins/${e}/file?path=${encodeURIComponent(o)}`,{method:"DELETE"}),restart:()=>t("/plugins/restart",{method:"POST"}),adminConfig:()=>t("/plugins/admin-config",{method:"GET"}),async exportBundle(e,{includeData:o=!1}={}){const n=d(),r=o?"?includeData=1":"",s=await fetch(`${m}/plugins/${encodeURIComponent(e)}/export${r}`,{headers:n?{Authorization:`Bearer ${n}`}:{}});if(!s.ok){const E=await s.json().catch(()=>({}));throw new Error(E.error||`Export failed (${s.status})`)}const i=s.headers.get("Content-Disposition")||"",a=/filename="([^"]+)"/.exec(i)?.[1],c=await s.blob(),h=URL.createObjectURL(c),l=document.createElement("a");l.href=h,l.download=a||`${e}.dcmsplugin`,document.body.appendChild(l),l.click(),l.remove(),URL.revokeObjectURL(h)},async installFile(e,{confirmUnsigned:o=!1,confirmUpgrade:n=!1,confirmDowngrade:r=!1}={}){const s=new FormData;o&&s.append("confirmUnsigned","true"),n&&s.append("confirmUpgrade","true"),r&&s.append("confirmDowngrade","true"),s.append("file",e);const i=d(),a=await fetch(`${m}/plugins/install-upload`,{method:"POST",headers:i?{Authorization:`Bearer ${i}`}:{},body:s}),c=await a.json().catch(()=>({}));if(a.status===409){const h=new Error(c.error||"Plugin already installed");throw h.code="CONFLICT",h.name=c.name,h.installedVersion=c.installedVersion,h.incomingVersion=c.incomingVersion,h}if(!a.ok)throw new Error(c.error||`Install failed (${a.status})`);return c}},marketplace:{catalogue:()=>t("/plugins/marketplace",{method:"GET"}),install:(e,o,{update:n=!1}={})=>t("/plugins/marketplace/install",{method:"POST",body:JSON.stringify({slug:e,version:o,update:n})}),uninstall:e=>t(`/plugins/marketplace/${encodeURIComponent(e)}`,{method:"DELETE"})},collections:{list:()=>t("/collections",{method:"GET"}),proStatus:()=>t("/collections/pro-status",{method:"GET"}),get:e=>t(`/collections/${e}`,{method:"GET"}),create:e=>t("/collections",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/collections/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/collections/${e}`,{method:"DELETE"}),listEntries:(e,o={})=>{const n=new URLSearchParams(o).toString();return t(`/collections/${e}/entries${n?"?"+n:""}`,{method:"GET"})},getEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"GET"}),createEntry:(e,o)=>t(`/collections/${e}/entries`,{method:"POST",body:JSON.stringify({data:o})}),updateEntry:(e,o,n)=>t(`/collections/${e}/entries/${o}`,{method:"PUT",body:JSON.stringify({data:n})}),deleteEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"DELETE"}),clearEntries:e=>t(`/collections/${e}/entries`,{method:"DELETE"}),import:(e,o)=>t(`/collections/${e}/import`,{method:"POST",body:JSON.stringify({entries:o})}),publicList:(e,o={})=>{const n=new URLSearchParams(o).toString();return t(`/collections/${e}/public${n?"?"+n:""}`,{method:"GET"})},getConnections:()=>t("/collections/connections",{method:"GET"}),saveConnections:e=>t("/collections/connections",{method:"PUT",body:JSON.stringify(e)}),migrateStorage:(e,o)=>t(`/collections/${e}/migrate-storage`,{method:"POST",body:JSON.stringify({storage:o})})},forms:{list:()=>t("/forms",{method:"GET"}),create:e=>t("/forms",{method:"POST",body:JSON.stringify(e)}),get:e=>t(`/forms/${e}`,{method:"GET"}),update:(e,o)=>t(`/forms/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/forms/${e}`,{method:"DELETE"}),listSubmissions:e=>t(`/forms/${e}/submissions`,{method:"GET"}),clearSubmissions:e=>t(`/forms/${e}/submissions`,{method:"DELETE"}),deleteSubmission:(e,o)=>t(`/forms/${e}/submissions/${o}`,{method:"DELETE"}),testEmail:e=>t("/forms/test-email",{method:"POST",body:JSON.stringify({to:e})})},views:{list:()=>t("/views",{method:"GET"}),get:e=>t(`/views/${e}`,{method:"GET"}),create:e=>t("/views",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/views/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/views/${e}`,{method:"DELETE"}),execute:(e,o={})=>{const n=new URLSearchParams(o).toString();return t(`/views/${e}/execute${n?"?"+n:""}`,{method:"GET"})},forCollection:e=>t(`/views/collection/${e}`,{method:"GET"})},actions:{list:()=>t("/actions",{method:"GET"}),get:e=>t(`/actions/${e}`,{method:"GET"}),create:e=>t("/actions",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/actions/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/actions/${e}`,{method:"DELETE"}),execute:(e,o)=>t(`/actions/${e}/execute`,{method:"POST",body:JSON.stringify({entryId:o})}),forCollection:e=>t(`/actions/collection/${e}`,{method:"GET"}),checkAccess:(e,o)=>t(`/actions/${e}/check-access`,{method:"POST",body:JSON.stringify({entryIds:o})})},versions:{list:e=>t(`/versions/list${e}`),get:(e,o)=>t(`/versions/get/${encodeURIComponent(o)}${e}`),create:(e,o)=>t(`/versions/create${e}`,{method:"POST",body:JSON.stringify({label:o})}),restore:(e,o)=>t(`/versions/restore/${encodeURIComponent(o)}${e}`,{method:"POST"}),delete:(e,o)=>t(`/versions/delete/${encodeURIComponent(o)}${e}`,{method:"DELETE"}),bulkDelete:(e,o)=>t(`/versions/bulk-delete${e}`,{method:"POST",body:JSON.stringify({filenames:o})}),prune:(e,o)=>t(`/versions/prune${e}`,{method:"POST",body:JSON.stringify({keep:o})})},blocks:{list:()=>t("/blocks",{method:"GET"}),get:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"GET"}),put:(e,o)=>t(`/blocks/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"DELETE"}),async exportBundle(e){const o=d(),n=await fetch(`${m}/blocks/${encodeURIComponent(e)}/export`,{headers:o?{Authorization:`Bearer ${o}`}:{}});if(!n.ok){const a=await n.json().catch(()=>({}));throw new Error(a.error||`Export failed (${n.status})`)}const r=await n.blob(),s=URL.createObjectURL(r),i=document.createElement("a");i.href=s,i.download=`${e}.dmblock.json`,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(s)},async importBundle(e,{overwrite:o=!1}={}){const n=await fetch(`${m}/blocks/import`,{method:"POST",headers:{"Content-Type":"application/json",...d()?{Authorization:`Bearer ${d()}`}:{}},body:JSON.stringify({...e,overwrite:o})});if(n.status===409){const r=await n.json().catch(()=>({})),s=new Error(r.error||"Block already exists");throw s.code="CONFLICT",s.name=r.name,s}if(!n.ok){const r=await n.json().catch(()=>({}));throw new Error(r.error||`Import failed (${n.status})`)}return n.json()}},components:{list:()=>t("/components",{method:"GET"}),get:e=>t(`/components/${encodeURIComponent(e)}`,{method:"GET"}),compile:(e,o)=>t("/components/compile",{method:"POST",body:JSON.stringify({name:e,source:o})}),put:(e,o)=>t(`/components/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/components/${encodeURIComponent(e)}`,{method:"DELETE"}),async exportBundle(e){const o=d(),n=await fetch(`${m}/components/${encodeURIComponent(e)}/export`,{headers:o?{Authorization:`Bearer ${o}`}:{}});if(!n.ok){const a=await n.json().catch(()=>({}));throw new Error(a.error||`Export failed (${n.status})`)}const r=await n.blob(),s=URL.createObjectURL(r),i=document.createElement("a");i.href=s,i.download=`${e}.dmcomponent.json`,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(s)},async importBundle(e,{overwrite:o=!1}={}){const n=await fetch(`${m}/components/import`,{method:"POST",headers:{"Content-Type":"application/json",...d()?{Authorization:`Bearer ${d()}`}:{}},body:JSON.stringify({...e,overwrite:o})});if(n.status===409){const r=await n.json().catch(()=>({})),s=new Error(r.error||"Component already exists");throw s.code="CONFLICT",s.name=r.name,s}if(!n.ok){const r=await n.json().catch(()=>({}));throw new Error(r.error||`Import failed (${n.status})`)}return n.json()}},get:e=>t(e,{method:"GET"}),post:(e,o)=>t(e,{method:"POST",body:JSON.stringify(o)}),put:(e,o)=>t(e,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(e,{method:"DELETE"}),settingsExt:{testEmail:e=>t("/settings/test-email",{method:"POST",body:JSON.stringify({to:e})})},system:{notifications:{list:()=>t("/system/notifications",{method:"GET"}),unreadCount:()=>t("/system/notifications/unread-count",{method:"GET"}),markRead:e=>t(`/system/notifications/${e}/read`,{method:"POST"}),dismiss:e=>t(`/system/notifications/${e}/dismiss`,{method:"POST"}),remove:e=>t(`/system/notifications/${e}`,{method:"DELETE"})}},dashboard:{summary:()=>t("/dashboard/summary",{method:"GET"}),summaryLite:()=>t("/dashboard/summary?lite=1",{method:"GET"})}};export function isAuthenticated(){return!!d()}export function getUser(){return S.get("auth_user")}export function setAuthData({token:e,refreshToken:o,user:n}){e&&y(e),o&&S.set("auth_refresh_token",o),n&&S.set("auth_user",n)}let u=!1,g=!1;export function logout(){!g&&window.M?.subscribe&&(g=!0,M.subscribe("router:afterChange",({to:e}={})=>{(e?.path||"")!=="/login"&&(u=!1)})),u=!0,R.navigate("/login")}export function takePendingLogout(){return u?(u=!1,O(),!0):!1}function O(){const e=p();e&&fetch(`${m}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})}).catch(()=>{}),f()}export{t as apiRequest,T as refreshAccessToken,d as getToken,f as clearAuth};
|
package/admin/js/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{views as F}from"./views/index.js";import{withPluginChrome as x}from"./lib/plugin-chrome.js";import{api as c,getUser as L,isAuthenticated as d,logout as H}from"./api.js";import{installHttpInterceptor as q}from"./http-interceptor.js";import{installResizableSlideovers as K}from"./lib/slideover-resizable.js";import{installActivityThrobber as z}from"./lib/activity-throbber.js";import{isPathAllowed as J,redirectFor as Y}from"./lib/admin-scope.js";$(()=>{q(),z(),K(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:e}=await import("./lib/admin-theme.js");d()?await e(c,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let l=null,r="/",u=!1,h=null;function C(t){const e=D[t]||Y(l,t);return e||((t.split("?")[0]||"/")==="/"&&r&&r!=="/"?D[r]||r:null)}const D={};R.use(async(t,e,o)=>{if(t.path==="/login"||t.path==="/reset-password")return o();if(!d()){R.navigate("/login");return}const i=C(t.path);if(i){R.navigate(i);return}o()});async function w(){try{const t=await c.get("/auth/permissions");l=t.adminScope||null,r=t.adminHome||"/";const e=t.permissions||[];return u=J(l,"/settings")&&e.some(o=>o==="settings"||o.startsWith("settings.")),h=e.some(o=>o==="notifications"||o.startsWith("notifications.")),e}catch{return l=null,r="/",u=!1,h=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{w()});let b=null,y=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(b){y=!0;return}b=(async()=>{do{y=!1;try{await P(await w())}catch{}}while(y)})().finally(()=>{b=null})});async function P(t){const{renderAdminSidebar:e}=await import("./lib/sidebar-renderer.js");await e({mount:"#admin-sidebar",permissions:t,adminScope:l})}M.subscribe("router:afterChange",({to:t})=>{(t.path==="/login"||t.path==="/reset-password")&&(p&&(clearInterval(p),p=null),l=null,r="/",u=!1,h=null)});const B=[{path:"/",view:"dashboard",title:"Dashboard - 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/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:"/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:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - 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:e})=>{if(!(t.path==="/login"||t.path==="/reset-password")){if($("#admin-sidebar").show(),$("#admin-topbar").show(),e?.path==="/login"||e?.path==="/reset-password"){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await I();const o=await w(),i=C(t.path);i&&R.navigate(i),P(o);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=g(s.data?.title||""),A=g((s.data?.body||"").slice(0,120));E.toast(`${f} - ${A}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}U()}}),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 W=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function _(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 e=t?String(t.path).split("?")[0]:"";if(!t||!_(e))return;const i=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!i||!/\/new$/.test(e))return;let a=0;const n=60,s=1500,f=()=>{a+=n;for(const A of W){const v=document.querySelector(A);if(!v)continue;if(Array.from(v.options||[]).find(O=>O.value===i)){v.value=i,v.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(f,n)};setTimeout(f,n)});const k="cms_card_states",j=S.get(k)||{};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 e=$(this).closest(".card");e.toggleClass("card-collapsed");const o=$(this).find("h2, h3").first().text().trim();o&&(j[o]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(k,j))}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash==="#/login"||window.location.hash.startsWith("#/reset-password"))return;const e=document.querySelector("#view-container .view-header button.btn-primary");e&&(t.preventDefault(),e.click())});const m={...F},V={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},analytics:{displayName:"Analytics",icon:"chart-bar"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"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"}};for(const[t,e]of Object.entries(V))m[t]&&(m[t]=x(m[t],{plugin:t,licence:"Built in",...e}));const N=[...B];async function I(){if(d())try{const t=await c.plugins.adminConfig();t.routes?.length&&N.push(...t.routes);for(const[e,o]of Object.entries(t.views||{}))try{const i=await import(`/plugins/${o.entry}`);m[e]=x(i[o.exportName],(t.meta||{})[e])}catch{}for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(D[`/${e}`]=o);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const i=document.createElement("link");i.id=e,i.rel="stylesheet",i.href=o,document.head.appendChild(i)}}catch{}}function U(){const t=L();if(!t||$("#topbar-user-name").length)return;const o={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role;$("#topbar-user").html(`
|
|
1
|
+
import{views as O}from"./views/index.js";import{withPluginChrome as T}from"./lib/plugin-chrome.js";import{api as c,getUser as L,isAuthenticated as d,logout as H}from"./api.js";import{installHttpInterceptor as q}from"./http-interceptor.js";import{installResizableSlideovers as z}from"./lib/slideover-resizable.js";import{installActivityThrobber as K}from"./lib/activity-throbber.js";import{isPathAllowed as J,redirectFor as Y}from"./lib/admin-scope.js";$(()=>{q(),K(),z(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:e}=await import("./lib/admin-theme.js");d()?await e(c,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let l=null,r="/",u=!1,h=null;function C(t){const e=b[t]||Y(l,t);return e||((t.split("?")[0]||"/")==="/"&&r&&r!=="/"?b[r]||r:null)}const b={};R.use(async(t,e,o)=>{if(t.path==="/login"||t.path==="/reset-password")return o();if(!d()){R.navigate("/login");return}const i=C(t.path);if(i){R.navigate(i);return}o()});async function w(){try{const t=await c.get("/auth/permissions");l=t.adminScope||null,r=t.adminHome||"/";const e=t.permissions||[];return u=J(l,"/settings")&&e.some(o=>o==="settings"||o.startsWith("settings.")),h=e.some(o=>o==="notifications"||o.startsWith("notifications.")),e}catch{return l=null,r="/",u=!1,h=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{w()});let D=null,y=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(D){y=!0;return}D=(async()=>{do{y=!1;try{await P(await w())}catch{}}while(y)})().finally(()=>{D=null})});async function P(t){const{renderAdminSidebar:e}=await import("./lib/sidebar-renderer.js");await e({mount:"#admin-sidebar",permissions:t,adminScope:l})}M.subscribe("router:afterChange",({to:t})=>{(t.path==="/login"||t.path==="/reset-password")&&(p&&(clearInterval(p),p=null),l=null,r="/",u=!1,h=null)});const B=[{path:"/",view:"dashboard",title:"Dashboard - 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/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:"/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:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - 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:e})=>{if(!(t.path==="/login"||t.path==="/reset-password")){if($("#admin-sidebar").show(),$("#admin-topbar").show(),e?.path==="/login"||e?.path==="/reset-password"){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(c)),await I();const o=await w(),i=C(t.path);i&&R.navigate(i),P(o);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=g(s.data?.title||""),A=g((s.data?.body||"").slice(0,120));E.toast(`${f} - ${A}`,{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 e=t?String(t.path).split("?")[0]:"";if(!t||!W(e))return;const i=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!i||!/\/new$/.test(e))return;let a=0;const n=60,s=1500,f=()=>{a+=n;for(const A of V){const v=document.querySelector(A);if(!v)continue;if(Array.from(v.options||[]).find(F=>F.value===i)){v.value=i,v.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(f,n)};setTimeout(f,n)});const k="cms_card_states",N=S.get(k)||{};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&&N[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 e=$(this).closest(".card");e.toggleClass("card-collapsed");const o=$(this).find("h2, h3").first().text().trim();o&&(N[o]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(k,N))}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash==="#/login"||window.location.hash.startsWith("#/reset-password"))return;const e=document.querySelector("#view-container .view-header button.btn-primary");e&&(t.preventDefault(),e.click())});const m={...O},_={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},analytics:{displayName:"Analytics",icon:"chart-bar"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"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"},dashboard:{displayName:"Dashboard",icon:"home"},pageEditor:{displayName:"Pages",section:"Edit",icon:"file-text"},menuEditor:{displayName:"Menus",section:"Edit",icon:"menu"},menuLocations:{displayName:"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,e]of Object.entries(_))m[t]&&(m[t]=T(m[t],{plugin:t,licence:"Built in",...e}));const j=[...B];async function I(){if(d())try{const t=await c.plugins.adminConfig();t.routes?.length&&j.push(...t.routes);for(const[e,o]of Object.entries(t.views||{}))try{const i=await import(`/plugins/${o.entry}`);m[e]=T(i[o.exportName],(t.meta||{})[e])}catch{}for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(b[`/${e}`]=o);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const i=document.createElement("link");i.id=e,i.rel="stylesheet",i.href=o,document.head.appendChild(i)}}catch{}}function x(){const t=L();if(!t||$("#topbar-user-name").length)return;const o={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role;$("#topbar-user").html(`
|
|
2
2
|
<span id="topbar-user-name" class="topbar-user-name">${g(t.name)}</span>
|
|
3
3
|
<span class="topbar-role-badge topbar-role-badge--${g(t.role)}">${o}</span>
|
|
4
4
|
`),$("#topbar-actions").html(`
|
|
@@ -18,4 +18,4 @@ import{views as F}from"./views/index.js";import{withPluginChrome as x}from"./lib
|
|
|
18
18
|
<span data-icon="log-out"></span>
|
|
19
19
|
<span>Sign out</span>
|
|
20
20
|
</a>
|
|
21
|
-
`);const i=$("#topbar-bell").get(0);i&&(i.hidden=!0);const a=$("#topbar-settings").get(0);a&&(a.hidden=!u),$("#topbar-logout-btn").on("click",n=>{n.preventDefault(),H()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),
|
|
21
|
+
`);const i=$("#topbar-bell").get(0);i&&(i.hidden=!0);const a=$("#topbar-settings").get(0);a&&(a.hidden=!u),$("#topbar-logout-btn").on("click",n=>{n.preventDefault(),H()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),U(),p||(p=setInterval(U,6e4))}function g(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}let p=null;async function U(){if(!(!d()||h===!1))try{const{count:t}=await c.system.notifications.unreadCount();$("#topbar-bell").removeAttr("hidden");const e=$("#topbar-bell-badge");t>0?e.text(t>99?"99+":String(t)).removeAttr("hidden"):e.attr("hidden","").text("")}catch{}}(async()=>{if(!d()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else if(await I(),L()){const a=await w(),n=(window.location.hash||"#/").slice(1)||"/",s=C(n);s&&(window.location.hash=`#${s}`),await P(a),x()}R.init({container:"#view-container",routes:j,views:m,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(i,a){const n=a.indexOf("?");if(n!==-1&&(a=a.slice(0,n)),i.endsWith("/*")){const s=i.slice(0,-2);return a.startsWith(s+"/")?{}:null}return t(i,a)};const e=(window.location.hash||"#/").slice(1)||"/";j.filter(i=>i.path.endsWith("/*")).some(i=>e.startsWith(i.path.slice(0,-2)+"/"))&&R._handleRouteChange()})()});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{slugify as u}from"./actions-arrange.js";export{u as slugify};export const STEP_TYPES={updateField:{label:"Update a field",help:"Sets one field of the entry to a value - fixed, or built from template variables such as {{now}} or {{user.name}}.",fields:[]},deleteEntry:{label:"Delete the entry",help:"Deletes the entry the action runs on. Put it last - nothing after it has an entry to work with.",fields:[]},moveToCollection:{label:"Move to another collection",help:"Copies the entry into another collection and removes it from this one.",fields:[{name:"targetCollection",label:"Target collection slug",placeholder:"e.g. archived-applications"}]},webhook:{label:"Call a webhook",help:"Sends an HTTP request to another service. The body may use template variables; JSON is sent as JSON.",fields:[{name:"url",label:"URL",placeholder:"https://hooks.example.com/notify"},{name:"method",label:"Method",placeholder:"POST"},{name:"body",label:"Body (JSON)",placeholder:'{"email": "{{entry.data.email}}"}',multiline:!0}]},email:{label:"Send an email",help:"Sends an email through the site's SMTP settings. Every field may use template variables.",fields:[{name:"to",label:"To",placeholder:"{{entry.data.email}}"},{name:"subject",label:"Subject",placeholder:"Your application update"},{name:"template",label:"Body",placeholder:"Your application has been approved.",multiline:!0}]}},ROW_LEVEL_MODES=["owner","field"],USER_KEYS=["id","email","name","role"];const n=e=>e==null?"":String(e);function f(e){const t=STEP_TYPES[e?.type]?e.type:String(e?.type||"updateField"),r=e?.config&&typeof e.config=="object"?e.config:{},l={};if(t==="updateField")l.field=n(r.field),l.value=n(r.value);else{for(const i of STEP_TYPES[t]?.fields||[]){const o=r[i.name];l[i.name]=o&&typeof o=="object"?JSON.stringify(o,null,2):n(o)}STEP_TYPES[t]||Object.assign(l,r)}return{type:t,config:l}}export function makeStep(e){return f({type:e,config:{}})}export function formFromAction(e,{project:t=""}={}){const r=e?.trigger||{},l=e?.access?.rowLevel||null,i=e?.transition||null;return{title:n(e?.title),slug:n(e?.slug),description:n(e?.description),collection:n(e?.collection),project:n(e?e.meta?.project:t),bundled:e?.bundled===!0,triggerType:n(r.type)||"manual",triggerLabel:e&&n(r.label)||"Run",triggerIcon:e&&n(r.icon)||"zap",triggerConfirm:n(r.confirmMessage),roles:Array.isArray(e?.access?.roles)?[...e.access.roles]:["admin"],rowLevelEnabled:!!l,rowLevelMode:ROW_LEVEL_MODES.includes(l?.mode)?l.mode:"owner",rowLevelUserKey:USER_KEYS.includes(l?.userKey)?l.userKey:"id",rowLevelField:n(l?.field),transitionEnabled:!!(i&&i.field),transitionField:n(i?.field),transitionFrom:(Array.isArray(i?.from)?i.from:[i?.from]).filter(o=>o!=null&&o!=="").join(", "),transitionTo:n(i?.to),steps:(Array.isArray(e?.steps)?e.steps:[]).map(f)}}export function splitList(e){return n(e).split(",").map(t=>t.trim()).filter(Boolean)}function g(e){const t=n(e).trim();if(!t)return"";try{return JSON.parse(t)}catch{return t}}export function payloadFromForm(e,{meta:t={},editing:r=!1}={}){const l=(e.steps||[]).map(o=>{if(o.type==="updateField")return{type:o.type,config:{field:n(o.config.field).trim(),value:n(o.config.value).trim()}};const s=STEP_TYPES[o.type];if(!s)return{type:o.type,config:{...o.config}};const a={};for(const c of s.fields)a[c.name]=c.multiline?g(o.config[c.name]):n(o.config[c.name]).trim();return{type:o.type,config:a}}),i=e.rowLevelEnabled?{mode:e.rowLevelMode,userKey:e.rowLevelUserKey,...e.rowLevelMode==="field"&&{field:n(e.rowLevelField).trim()}}:null;return{title:n(e.title).trim(),...!r&&n(e.slug).trim()&&{slug:n(e.slug).trim()},description:n(e.description).trim(),collection:e.collection,...e.bundled?{bundled:!0}:{bundled:!1},trigger:{type:e.triggerType||"manual",label:n(e.triggerLabel).trim()||"Run",icon:n(e.triggerIcon).trim()||"zap",confirmMessage:n(e.triggerConfirm).trim()||null},steps:l,transition:e.transitionEnabled&&n(e.transitionField).trim()?{field:n(e.transitionField).trim(),from:splitList(e.transitionFrom),to:n(e.transitionTo).trim()}:null,access:{roles:[...e.roles||[]],rowLevel:i},meta:{...t||{},project:e.project||null}}}export function snapshot(e){const{meta:t,...r}=payloadFromForm(e,{editing:!0});return JSON.stringify({...r,slug:n(e.slug).trim(),project:e.project||""})}const y=/^https?:\/\/\S+$/i,h=/\{\{[^}]+\}\}/;export function checkAction(e){const t={};n(e.title).trim()||(t.title="An action needs a title."),e.collection||(t.collection="Choose the collection it runs on.");const r=n(e.slug).trim();r&&u(r)!==r&&(t.slug=`Lowercase letters, numbers and hyphens - it would be saved as "${u(r)||"?"}".`),e.rowLevelEnabled&&e.rowLevelMode==="field"&&!n(e.rowLevelField).trim()&&(t.rowLevelField="Field match needs the entry field to compare."),e.transitionEnabled&&!n(e.transitionField).trim()&&(t.transitionField="A transition needs the field that holds the state."),(e.roles||[]).length||(t.roles="Nobody could run it - choose at least one role.");const l={};return(e.steps||[]).forEach((i,o)=>{const s=i.config||{};if(i.type==="updateField"&&!n(s.field).trim())l[o]="Choose the field to update.";else if(i.type==="moveToCollection"&&!n(s.targetCollection).trim())l[o]="Give the collection to move it to.";else if(i.type==="webhook"){const a=n(s.url).trim();a?!y.test(a)&&!h.test(a)&&(l[o]="The URL must start with http:// or https://."):l[o]="A webhook needs a URL."}else i.type==="email"&&!n(s.to).trim()&&(l[o]="An email needs a To address.")}),Object.keys(l).length&&(t.steps=l),t}export function problemsText(e){const t=[];for(const r of["title","collection","slug","roles","rowLevelField","transitionField"])e[r]&&t.push(e[r]);for(const[r,l]of Object.entries(e.steps||{}))t.push(`Step ${Number(r)+1}: ${l}`);return t.join(" ")}export function moveStep(e,t,r){const l=t+r;if(t<0||t>=e.length||l<0||l>=e.length)return e;const i=[...e];return[i[t],i[l]]=[i[l],i[t]],i}export function duplicateStep(e,t){if(t<0||t>=e.length)return e;const r={type:e[t].type,config:{...e[t].config}};return[...e.slice(0,t+1),r,...e.slice(t+1)]}export function removeStep(e,t){return e.filter((r,l)=>l!==t)}const p=(e,t=60)=>e.length>t?`${e.slice(0,t-1)}\u2026`:e;export function stepSummary(e){const t=e?.config||{};switch(e?.type){case"updateField":return t.field?p(`${t.field} \u2192 ${n(t.value)||"(empty)"}`):"No field chosen yet";case"deleteEntry":return"Deletes the entry";case"moveToCollection":return t.targetCollection?`To ${t.targetCollection}`:"No collection given yet";case"webhook":return t.url?p(`${(n(t.method).trim()||"POST").toUpperCase()} ${t.url}`):"No URL yet";case"email":return t.to?p(`To ${t.to}${t.subject?` - ${t.subject}`:""}`):"No recipient yet";default:return"Unknown step type - kept as it is"}}export function rolePicker(e,t=[]){const r=new Map((e||[]).filter(o=>o?.name).map(o=>[o.name,o])),l=new Set(t),i=[...r.values()].sort((o,s)=>(o.level??99)-(s.level??99)||String(o.name).localeCompare(String(s.name))).map(o=>({name:o.name,label:o.label||o.name,level:o.level??"",levelText:o.level===void 0||o.level===null?"":`level ${o.level}`,missing:!1,on:l.has(o.name)}));for(const o of t)r.has(o)||i.push({name:o,label:o,level:"",levelText:"",missing:!0,on:!0});return i}export const draftKey=e=>`action-editor.draft.${e||"new"}`;export function draftOf(e,t){const r=Array.isArray(t?.steps)?t.steps:[];return{...e,steps:(Array.isArray(e?.steps)?e.steps:[]).map(l=>{if(l?.type!=="webhook")return l;const{url:i,...o}=l.config||{},s=i?r.findIndex(a=>a?.type==="webhook"&&a.config?.url===i):-1;return{type:l.type,config:{...o,url:""},...s>=0&&{savedUrl:s},...i&&s<0&&{lostUrl:!0}}})}}export function restoreDraft(e,t){const r=Array.isArray(t?.steps)?t.steps:[];let l=0;const i=(Array.isArray(e?.steps)?e.steps:[]).map(o=>{if(o?.type!=="webhook")return o;const{savedUrl:s,lostUrl:a,...c}=o,d=Number.isInteger(s)?r[s]:null,m=d?.type==="webhook"?n(d.config?.url):"";return(a||Number.isInteger(s)&&!m)&&l++,{...c,config:{...c.config||{},url:m}}});return{form:{...t,...e,steps:i},lost:l}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const OPERATORS=[["_eq","equals"],["_ne","not equal"],["_gt",">"],["_gte",">="],["_lt","<"],["_lte","<="],["_in","in list"],["_nin","not in list"],["_contains","contains"],["_starts","starts with"],["_ends","ends with"],["_exists","exists"]],PSEUDO_FIELDS=["id","createdBy"];const g=/\{\{\s*(params|query)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;export function splitFilterKey(e){for(const[t]of OPERATORS)if(e.endsWith(t)&&e.length>t.length)return{field:e.slice(0,-t.length),op:t};return{field:e,op:"_eq"}}export const filterKey=(e,t)=>t==="_eq"?e:e+t;export function filterFromRows(e){const t={};for(const n of e||[])!n?.field||n.field==="__custom__"||(t[filterKey(n.field,n.op||"_eq")]=n.value??"");return t}export function placeholders(e,t){const n=new Set;for(const r of Object.values(e||{}))if(typeof r=="string")for(const o of r.matchAll(g))o[1]===t&&n.add(o[2]);return[...n]}export function pathParams(e){return(String(e||"").match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)||[]).map(t=>t.slice(1))}export const publicPath=(e,t)=>`/api/x/${e||"<project>"}${t||"/\u2026"}`;export function checkEndpoint(e){const t={};String(e?.name||"").trim()||(t.name="A name is required.");const n=String(e?.path||""),r=n.split("/").filter(Boolean);if(!n.startsWith("/"))t.path="Starts with / - e.g. /fixtures-day/:date.";else if(!r.length)t.path="Needs at least one segment after /.";else{const i=r.find(a=>!/^(:[a-zA-Z_][a-zA-Z0-9_]*|[a-z0-9-]+)$/.test(a));i!==void 0&&(t.path=`"${i}" - use lowercase letters, numbers, hyphens, or :param.`)}e?.project||(t.project="Choose a project."),e?.collection||(t.collection="Choose a collection.");const o=Number(e?.limit);(!Number.isInteger(o)||o<0||o>500)&&(t.limit="A whole number from 0 (no limit) to 500.");const l=placeholders(e?.filter,"params").filter(i=>!pathParams(n).includes(i));return l.length&&(t.filter=`{{params.${l[0]}}} has no :${l[0]} in the path.`),t}export function snapshot(e){const t=e||{},n=Object.fromEntries(Object.entries(t.filter||{}).sort(([r],[o])=>r.localeCompare(o)));return JSON.stringify({name:t.name??"",project:t.project??"",path:t.path??"",collection:t.collection??"",auth:t.auth??"",mode:t.mode??"",filter:n,sort:t.sort??null,order:t.order??"",limit:Number(t.limit)||0,fields:[...t.fields||[]].sort(),enabled:t.enabled!==!1})}export function normaliseDraft(e){const t=e?.state;return!t||typeof t!="object"?null:{name:String(t.name??""),project:String(t.project??""),path:String(t.path??""),collection:String(t.collection??""),auth:String(t.auth??"public"),mode:t.mode==="single"?"single":"list",filter:t.filter&&typeof t.filter=="object"?Object.fromEntries(Object.entries(t.filter).map(([n,r])=>[String(n),String(r??"")])):{},sort:t.sort?String(t.sort):null,order:t.order==="asc"?"asc":"desc",limit:Number.isInteger(Number(t.limit))?Number(t.limit):50,fields:Array.isArray(t.fields)?t.fields.map(String):[],enabled:t.enabled!==!1}}export function tryRequest(e,{params:t={},queryParams:n={},extra:r="",token:o="",origin:l=""}={}){let i=String(e?.path||"");for(const[s,c]of Object.entries(t)){const p=new RegExp(":"+s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"(?=/|$)","g");i=i.replace(p,encodeURIComponent(c??""))}const a=[];for(const[s,c]of Object.entries(n)){const p=String(c??"").trim();p&&a.push(`${encodeURIComponent(s)}=${encodeURIComponent(p)}`)}const f=String(r||"").trim().replace(/^\?/,"");f&&a.push(f);const m=`/api/x/${e?.project}${i}${a.length?"?"+a.join("&"):""}`,u=e?.auth==="token"&&o?{Authorization:`Bearer ${o}`}:{},h=s=>`'${String(s).replace(/'/g,"'\\''")}'`,d=[`curl ${h(l+m)}`,...Object.entries(u).map(([s,c])=>` -H ${h(`${s}: ${c}`)}`)].join(` \\
|
|
2
|
+
`);return{url:m,headers:u,curl:d}}export const statusTone=(e,t)=>t?"is-ok":!e||e>=500?"is-danger":"is-warn";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const NAME_RE=/^[a-z0-9][a-z0-9-]*$/,MAX_CSS=51200,FORM_FIELDS=["name","project","bundled","content","css"];export function formFromBlock(t,{project:n=""}={}){return t?{name:String(t.name||""),project:String(t.meta?.project||""),bundled:t.bundled===!0,content:String(t.content??""),css:String(t.css??"")}:{name:"",project:String(n||""),bundled:!1,content:"",css:""}}export function snapshot(t){const n=t||{};return JSON.stringify(FORM_FIELDS.map(e=>e==="bundled"?n[e]===!0:String(n[e]??"")))}export function checkForm(t,{isNew:n,taken:e=[]}={}){const r=String(t?.name||"").trim(),o={};n&&(r?NAME_RE.test(r)?e.includes(r)&&(o.name=`"${r}" is already a block - open it from the list, or choose another name.`):o.name="Lowercase letters, digits and hyphens, starting with a letter or digit.":o.name="A block needs a name.");const s=String(t?.css??"");return s.length>51200&&(o.css=`The CSS is ${Math.ceil(s.length/1024)} KB - a block's CSS is limited to 50 KB.`),{errors:o,name:r}}export function payloadFromForm(t,n={}){return{content:String(t?.content??""),bundled:t?.bundled===!0,css:String(t?.css??""),meta:{...n||{},project:String(t?.project||"")||null}}}export function extractPlaceholders(t){const n=new Set;for(const[,e]of String(t??"").matchAll(/\{\{([\w_]+)(?::[^{}]+)?\}\}/g))n.add(e);return[...n]}const u=t=>String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");export{u as escapeHtml};export function formatDate(t,n){if(t==null||t==="")return null;const e=t instanceof Date?t:new Date(t);if(Number.isNaN(e.getTime()))return null;const r=(a,l=2)=>String(a).padStart(l,"0"),o=["January","February","March","April","May","June","July","August","September","October","November","December"],s=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],i=()=>(e.getHours()+11)%12+1,c={YYYY:()=>e.getFullYear(),YY:()=>r(e.getFullYear()%100),MMMM:()=>o[e.getMonth()],MMM:()=>o[e.getMonth()].slice(0,3),MM:()=>r(e.getMonth()+1),M:()=>e.getMonth()+1,DD:()=>r(e.getDate()),D:()=>e.getDate(),dddd:()=>s[e.getDay()],ddd:()=>s[e.getDay()].slice(0,3),HH:()=>r(e.getHours()),H:()=>e.getHours(),hh:()=>r(i()),h:()=>i(),mm:()=>r(e.getMinutes()),m:()=>e.getMinutes(),ss:()=>r(e.getSeconds()),s:()=>e.getSeconds(),A:()=>e.getHours()<12?"AM":"PM",a:()=>e.getHours()<12?"am":"pm"};return String(n).replace(/YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|A|a/g,a=>String(c[a]()))}const g={icon:"box","facebook-icon":"facebook","instagram-icon":"instagram"};export function sampleValue(t,n=new Date){const e=String(t).toLowerCase();return g[e]?g[e]:e==="_id"?"a1b2c3d4-e5f6-7890-abcd-ef1234567890":e==="_createdat"||e==="_updatedat"?n.toISOString():e.includes("email")?"user@example.com":e.includes("phone")?"+44 7700 900000":e.includes("name")?"Jane Smith":e.includes("title")?"Sample Title":e.includes("message")||e.includes("content")||e.includes("description")?"This is a sample value for preview purposes.":e.includes("rating")?"excellent":e.includes("status")?"active":e.includes("priority")?"high":e.includes("date")?n.toLocaleDateString():e.includes("tag")?"tag1, tag2":e.includes("subject")?"Sample Subject":e.includes("category")?"general":`Sample ${t}`}export const scopeName=t=>String(t||"").trim()||"preview";export function renderPreview(t,n={}){const e=String(t?.content??"").replace(/\{\{([\w_]+)(?::([^{}]+))?\}\}/g,(i,c,a)=>{const l=n[c]??`[${c}]`;if(a){const d=formatDate(l,a);if(d!==null)return u(d)}return u(l)}),r=u(scopeName(t?.name)),o=String(t?.css??"");return`${o.trim()?`<style>[data-block="${r}"] { ${o.replace(/<\/style/gi,"<\\/style")} }</style>`:""}<div data-block="${r}">${e}</div>`}const m=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);export function formatHtml(t){const n=String(t??"").match(/(<[^>]+>|[^<]+)/g)||[],e=[];let r=0;for(const o of n){const s=o.trim();if(s)if(s.startsWith("</"))r=Math.max(0,r-1),e.push(" ".repeat(r)+s);else if(s.startsWith("<")&&!s.startsWith("<!")&&!s.endsWith("/>")){e.push(" ".repeat(r)+s);const i=(s.match(/^<(\w+)/)||[])[1]||"";m.has(i.toLowerCase())||r++}else e.push(" ".repeat(r)+s)}return e.join(`
|
|
2
|
+
`)}export const draftKey=t=>`block-editor.draft.${t||"new"}`;export function draftOffer(t,n){return!t||typeof t!="object"||!t.form||snapshot(t.form)===n?null:{form:t.form,stale:!!t.base&&t.base!==n,at:t.at||""}}export const shortcodeFor=t=>`[collection slug="\u2026" display="block" block="${t}" /]`;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const FIELD_TYPES=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"hidden",label:"Hidden field"}],OPTION_TYPES=new Set(["select","radio","checkbox-group"]),ACCESS_LEVELS=["public","token","subscriber","editor","manager","admin"],EXPORT_ACCESS=ACCESS_LEVELS.filter(e=>e!=="token"),OPERATIONS=["create","read","update","delete"],EXPORT_FORMATS=["csv","json"],FIELD_NAME_RE=/^[A-Za-z_][A-Za-z0-9_-]*$/;export function slugify(e){return String(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}export const nameFromLabel=e=>slugify(e).replace(/-/g,"_"),typeLabel=e=>FIELD_TYPES.find(r=>r.value===e)?.label||e||"";export function optionsText(e){return(Array.isArray(e)?e:[]).map(r=>typeof r=="string"?`${r}: ${r}`:`${r?.value??""}: ${r?.label??r?.value??""}`).join(`
|
|
2
|
+
`)}export function parseOptions(e){return String(e??"").split(`
|
|
3
|
+
`).filter(r=>r.trim()).map(r=>{const[t,...n]=r.split(":");return{value:t.trim(),label:n.join(":").trim()||t.trim()}})}const d=()=>({create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}}),f=()=>({enabled:!0,access:"subscriber",formats:["csv","json"]});let m=0;export const fieldKey=()=>`f${Date.now().toString(36)}${(m++).toString(36)}`;export function formFromCollection(e=null,{project:r=""}={}){const t=e||{},n={...d(),...t.api||{}},l={...f(),...t.export||{}},s={title:String(t.title??""),slug:String(t.slug??""),description:String(t.description??""),layout:t.layout==="grid"?"grid":"stacked",columns:String(Number.parseInt(t.columns,10)||2),project:String(t.meta?.project??r??""),bundled:t.bundled===!0,storageAdapter:t.storage?.adapter==="mongodb"?"mongodb":"file",storageConnection:String(t.storage?.connection||"default"),fields:(Array.isArray(t.fields)?t.fields:[]).map(a=>({...a,_key:fieldKey()})),readFields:Array.isArray(n.read?.fields)?n.read.fields.join(", "):"",exportEnabled:l.enabled!==!1,exportAccess:EXPORT_ACCESS.includes(l.access)?l.access:"subscriber",exportCsv:(Array.isArray(l.formats)?l.formats:EXPORT_FORMATS).includes("csv"),exportJson:(Array.isArray(l.formats)?l.formats:EXPORT_FORMATS).includes("json")};for(const a of OPERATIONS)s[`${a}Enabled`]=n[a]?.enabled===!0,s[`${a}Access`]=ACCESS_LEVELS.includes(n[a]?.access)?n[a].access:"admin";return s}export function cleanField(e){const{_key:r,...t}=e||{};return t}export function payloadFromForm(e,{isNew:r=!1,meta:t={}}={}){const n=e||{},l={};for(const o of OPERATIONS)l[o]={enabled:n[`${o}Enabled`]===!0,access:n[`${o}Access`]||"admin"};const s=String(n.readFields??"").split(",").map(o=>o.trim()).filter(Boolean);s.length&&(l.read.fields=s);const a=Math.min(6,Math.max(1,Number.parseInt(n.columns,10)||2));return{title:String(n.title??"").trim(),...r&&{slug:slugify(n.slug||n.title)},description:String(n.description??"").trim(),layout:n.layout==="grid"?"grid":"stacked",columns:a,fields:(n.fields||[]).map(cleanField),api:l,export:{enabled:n.exportEnabled!==!1,access:n.exportAccess||"subscriber",formats:EXPORT_FORMATS.filter(o=>o==="csv"?n.exportCsv:n.exportJson)},storage:n.storageAdapter==="mongodb"?{adapter:"mongodb",connection:n.storageConnection||"default"}:{adapter:"file"},meta:{...t||{},project:String(n.project??"")||null},bundled:n.bundled===!0}}export function snapshot(e){const{fields:r=[],...t}=e||{};return JSON.stringify({...t,fields:r.map(cleanField)})}export function checkForm(e,{isNew:r=!1,taken:t=[],original:n={}}={}){const l=e||{},s={};if(String(l.title??"").trim()||(s.title="A collection needs a title."),r){const i=slugify(l.slug||l.title);i?t.includes(i)&&(s.slug=`"${i}" is already a collection.`):s.slug="The slug needs a letter or a number."}const a=Number.parseInt(l.columns,10);l.layout==="grid"&&!(a>=1&&a<=6)&&(s.columns="Between 1 and 6 columns.");const o={},c=new Map;return(l.fields||[]).forEach(i=>{const p=fieldProblem(i,n[i._key]);p&&(o[i._key]=p);const u=String(i.name??"").trim();u&&(c.has(u)?o[i._key]=o[i._key]||`"${u}" is used by another field.`:c.set(u,i._key))}),Object.keys(o).length&&(s.fields=o),l.exportEnabled&&!l.exportCsv&&!l.exportJson&&(s.export="Pick at least one export format, or turn export off."),s}export function fieldProblem(e,r){const t=String(e?.name??"").trim();return t?t!==r&&!FIELD_NAME_RE.test(t)?"Names start with a letter or _, then letters, digits, _ or -.":OPTION_TYPES.has(e.type)&&!(Array.isArray(e.options)&&e.options.length)?`A ${typeLabel(e.type).toLowerCase()} needs at least one option.`:"":"A field needs a name - it is what each entry is keyed by."}export const errorCount=e=>Object.keys(e||{}).filter(r=>r!=="fields").length+Object.keys(e?.fields||{}).length;export function fieldRow(e,r={},{grid:t=!1}={}){const n=[e.name||"(no name)",typeLabel(e.type)];return OPTION_TYPES.has(e.type)&&n.push(`${(e.options||[]).length} options`),t&&n.push(e.fullWidth?"full width":`span ${e.span>1?e.span:1}`),{key:e._key,label:e.label||"(Untitled field)",meta:n.join(" \xB7 "),required:e.required===!0,hidden:e.type==="hidden",problem:r[e._key]||"",rowCls:r[e._key]?"is-bad":""}}export function moveField(e,r,t){const n=[...e];if(r<0||r>=n.length||t<0||t>=n.length||r===t)return n;const[l]=n.splice(r,1);return n.splice(t,0,l),n}export function duplicateField(e,r){const t=e[r];if(!t)return[...e];const n=new Set(e.map(c=>c.name)),l=`${t.name||"field"}_copy`;let s=l;for(let c=2;n.has(s);c++)s=`${l}_${c}`;const a={...JSON.parse(JSON.stringify(cleanField(t))),name:s,label:`${t.label||"Field"} (copy)`,_key:fieldKey()};delete a.id;const o=[...e];return o.splice(r+1,0,a),o}export const newField=()=>({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null,_key:fieldKey()});export function panelForm(e){return{label:String(e.label??""),name:String(e.name??""),type:e.type||"string",required:e.required===!0,placeholder:String(e.placeholder??""),helper:String(e.helper??""),options:optionsText(e.options),span:String(e.span>1?e.span:1),fullWidth:e.fullWidth===!0}}export function fieldFromPanel(e,r){const t={...e,label:String(r.label??"").trim(),name:String(r.name??"").trim(),type:r.type||"string",required:r.required===!0,placeholder:String(r.placeholder??"").trim(),helper:String(r.helper??"").trim()};if(OPTION_TYPES.has(t.type)&&(t.options=parseOptions(r.options)),r.fullWidth)t.fullWidth=!0,delete t.span;else{delete t.fullWidth;const n=Number.parseInt(r.span,10);n>1?t.span=Math.min(6,n):delete t.span}return t}export function storageChange(e,r){const t=e||"file",n=r||"file";return t===n?"":`${t} \u2192 ${n}`}export function draftOf(e){const{fields:r=[],...t}=e||{};return{...t,fields:r.map(cleanField)}}export function formFromDraft(e,r){const t=e||{};return{...r,...t,fields:(Array.isArray(t.fields)?t.fields:r.fields).map(n=>({...cleanField(n),_key:fieldKey()}))}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const OP_LABELS={eq:"=",ne:"\u2260",contains:"contains",gt:">",lt:"<"},MAX_COLUMNS=5,PAGE_SIZE=500,SHOW_STEP=100;export function matchesFilter(e,t){const r=e?.data?.[t.field],n=String(r??"");switch(t.op){case"eq":return n===String(t.value);case"ne":return n!==String(t.value);case"contains":return n.toLowerCase().includes(String(t.value).toLowerCase());case"gt":case"lt":{const i=o=>String(o).trim()!==""&&Number.isFinite(Number(o))?Number(o):NaN,s=i(n),a=i(t.value);if(!Number.isNaN(s)&&!Number.isNaN(a))return t.op==="gt"?s>a:s<a;const c=Date.parse(n),u=Date.parse(t.value);return!Number.isNaN(c)&&!Number.isNaN(u)?t.op==="gt"?c>u:c<u:t.op==="gt"?n>String(t.value):n<String(t.value)}default:return!0}}export function valueText(e){return e==null?"":typeof e=="boolean"?e?"Yes":"No":Array.isArray(e)?e.map(t=>t&&typeof t=="object"?JSON.stringify(t):String(t)).join(", "):typeof e=="object"?JSON.stringify(e):String(e)}export function cellText(e,t=60){const r=valueText(e).replace(/\s+/g," ").trim();return r.length>t?`${r.slice(0,t-1)}\u2026`:r}export function arrangeEntries(e,{filters:t=[],query:r="",sort:n={key:"created",dir:"desc"}}={}){const i=String(r||"").toLowerCase().trim();let s=(e||[]).filter(o=>t.every(l=>matchesFilter(o,l)));i&&(s=s.filter(o=>Object.values(o?.data||{}).some(l=>valueText(l).toLowerCase().includes(i))));const a=n?.dir==="asc"?1:-1,c=n?.key||"created",u=o=>c==="created"?String(o?.meta?.createdAt||""):o?.data?.[c];return[...s].sort((o,l)=>{const p=u(o),g=u(l),m=typeof p=="number"?p:parseFloat(p),b=typeof g=="number"?g:parseFloat(g);return c!=="created"&&!Number.isNaN(m)&&!Number.isNaN(b)&&String(m)===String(p??"").trim()&&String(b)===String(g??"").trim()?(m-b)*a:valueText(p).localeCompare(valueText(g),void 0,{numeric:!0,sensitivity:"base"})*a})}export function filterChoices(e,t){const r=valueText(t);if(r==="")return[];const n=r.length>28?`${r.slice(0,27)}\u2026`:r,i=[{op:"eq",value:r,label:`= \u201C${n}\u201D`},{op:"ne",value:r,label:`\u2260 \u201C${n}\u201D`}];return["number","date","time","datetime"].includes(e?.type)&&i.push({op:"gt",value:r,label:`> \u201C${n}\u201D`},{op:"lt",value:r,label:`< \u201C${n}\u201D`}),i}export function filterText(e){const t=String(e.value);return`${e.label} ${OP_LABELS[e.op]||e.op} \u201C${t.length>24?`${t.slice(0,23)}\u2026`:t}\u201D`}export function isSecretField(e){return e?.type==="password"||/pass(word)?|secret|token|api[-_]?key|private|credential/i.test(String(e?.name||""))}export function draftable(e,t=[]){const r=new Set(t.filter(isSecretField).map(n=>n.name));return Object.fromEntries(Object.entries(e||{}).filter(([n])=>!r.has(n)))}export function snapshot(e){const t=Object.keys(e||{}).sort();return JSON.stringify(t.map(r=>[r,typeof e[r]=="boolean"?e[r]:valueText(e[r])]))}export const hasForm=e=>!["roles","user-profiles"].includes(e),canClear=e=>e!=="roles";export function countText(e,t,r){const n=a=>a.toLocaleString("en-GB"),i=a=>`entr${a===1?"y":"ies"}`,s=e===t?`${n(t)} ${i(t)}`:`Showing ${n(e)} of ${n(t)}`;return r>t?`${s} \xB7 ${n(t)} of ${n(r)} loaded`:s}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const NAME_RE=/^[a-z][a-z0-9-]*$/,PANES=["template","props","script","style"],STARTER_SOURCE=`<template>
|
|
2
|
+
<div>Hello, <span>{{name}}</span>.</div>
|
|
3
|
+
</template>
|
|
4
|
+
<props>
|
|
5
|
+
{
|
|
6
|
+
"name": { "type": "string", "default": "world", "label": "Name" }
|
|
7
|
+
}
|
|
8
|
+
</props>
|
|
9
|
+
<script>
|
|
10
|
+
export default {
|
|
11
|
+
data() { return {}; }
|
|
12
|
+
};
|
|
13
|
+
<\/script>
|
|
14
|
+
<style>
|
|
15
|
+
div { font-family: system-ui, sans-serif; }
|
|
16
|
+
</style>
|
|
17
|
+
`;export function splitSource(t){const r=String(t??""),e=n=>{const o=r.match(new RegExp(`<${n}>([\\s\\S]*?)</${n}>`));return o?o[1].replace(/^\n/,"").replace(/\n$/,""):""};return{template:e("template"),props:e("props"),script:e("script"),style:e("style")}}export function assembleSource(t){const r=t||{};return[`<template>
|
|
18
|
+
${r.template??""}
|
|
19
|
+
</template>`,`<props>
|
|
20
|
+
${r.props??""}
|
|
21
|
+
</props>`,`<script>
|
|
22
|
+
${r.script??""}
|
|
23
|
+
<\/script>`,String(r.style??"").trim()?`<style>
|
|
24
|
+
${r.style}
|
|
25
|
+
</style>`:""].filter(Boolean).join(`
|
|
26
|
+
`)+`
|
|
27
|
+
`}export function nameProblem(t,{isNew:r=!1,taken:e=[]}={}){const n=String(t??"").trim();return n?NAME_RE.test(n)?r&&e.includes(n)?`"${n}" is already a component.`:"":"Start with a letter; then lowercase letters, digits and hyphens only.":"A component needs a name."}export const tagFor=t=>`<dm-${String(t??"").trim()||"\u2026"}>`;export function defaultForType(t){return t==="number"?0:t==="boolean"?!1:t==="array"?[]:t==="object"?{}:""}export function previewRows(t,r={}){let e;try{e=JSON.parse(String(t??"").trim()||"{}")}catch{return{rows:[],error:!0}}return!e||typeof e!="object"||Array.isArray(e)?{rows:[],error:!0}:{rows:Object.entries(e).map(([o,i])=>{const s=i&&typeof i=="object"?i:{},l=typeof s.type=="string"?s.type:"string",p=Object.prototype.hasOwnProperty.call(r,o)?r[o]:s.default!==void 0?s.default:defaultForType(l);return{name:o,label:String(s.label||o),type:l,value:p}}),error:!1}}export function readValue(t,r){if(t==="number")return r===""||r==null?null:Number(r);if(t==="boolean")return r===!0||r==="true";if(t==="array"||t==="object")try{return JSON.parse(r||"null")}catch{return null}return r??""}export function writeValue(t,r){return t==="boolean"?!!r:t==="array"||t==="object"?JSON.stringify(r??null):r==null?"":String(r)}export function attributeFor(t,r,e){if(r==="boolean")return e?t:"";const n=r==="array"||r==="object"?JSON.stringify(e??null):String(e??"");return`${t}="${n.replace(/&/g,"&").replace(/"/g,""")}"`}export function snapshot(t){const r=t||{};return JSON.stringify([String(r.name??"").trim(),String(r.project??""),r.bundled===!0,String(r.source??"")])}export const draftKey=t=>`component-editor.draft.${t||"new"}`;export function draftStatus(t,r){return!!t&&typeof t=="object"&&typeof t.source=="string"?{ok:!0,stale:typeof t.base=="string"&&t.base!==r}:{ok:!1,stale:!1}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ACTION_GROUPS=[{label:"This page",actions:{navigate:"Go to a URL",reload:"Reload the page",back:"Back",forward:"Forward",print:"Print","copy-link":"Copy link to this view","site-search":"Open site search"}},{label:"Clipboard and selection",actions:{copy:"Copy the selection",cut:"Cut",paste:"Paste","select-all":"Select all"}},{label:"Collection displays",actions:{filter:"Filter the display",search:"Search this display",sort:"Sort the display",export:"Export the display","mark-as-spam":"Mark as spam"}},{label:"This site",actions:{"open-modal":"Open a block in a modal","run-action":"Run an Action"}}],ACTION_LABELS=Object.assign({},...ACTION_GROUPS.map(e=>e.actions)),SELF_DISABLING={back:"greys out when there is no history behind the page",forward:"greys out unless you have gone back",copy:"greys out with nothing selected",cut:"greys out outside an editable field, or with nothing selected",paste:"greys out outside an editable field","site-search":"greys out on a site where search is unavailable",search:"greys out unless the click lands in a collection display",filter:"greys out unless the click lands in a collection display",sort:"greys out unless the click lands in a collection display",export:"greys out unless the click lands in a collection display"},NEEDS_TARGET={"mark-as-spam":"needs a right-click on an entry inside a collection display"},ACTION_ARG={navigate:{key:"url",label:"URL",placeholder:"/shop",required:!0},"run-action":{key:"slug",label:"Action slug",placeholder:"archive-entry",required:!0},"open-modal":{key:"block",label:"Block name",placeholder:"contact-card",required:!0},export:{key:"format",label:"Format",placeholder:"csv"},sort:{key:"field",label:"Field",placeholder:"price"},filter:{key:"field",label:"Field",placeholder:"category"},"mark-as-spam":{key:"field",label:"Flag field (optional)",placeholder:"spam"}},SLUG_RE=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,ACCENTS=["primary","success","danger","warning","info"];const y=/^#[0-9a-fA-F]{6}$/,m=new Set(["*","html","body",":root","head"]);export const EDITABLE_TYPES=["item","divider","header"];const b={item:"Item",divider:"Divider",header:"Heading",checkbox:"Checkbox",radio:"Radio"},f=["accent","transition","density","radius","shadow","opacity"];export function slugify(e){return String(e||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64).replace(/-+$/,"")}export function splitList(e){return String(e||"").split(",").map(t=>t.trim()).filter(Boolean)}export function formFromMenu(e=null){const t=e||{},n=t.theme||{};return{name:String(t.name||""),slug:String(t.slug||""),selector:String(t.target?.selector||""),match:String(t.target?.match||""),exclude:String(t.target?.exclude||""),inherit:t.inherit===!1?"false":String(t.inherit||"append"),enabled:t.enabled!==!1,accent:String(n.accent||""),transition:String(n.transition||""),density:String(n.density||""),radius:String(n.radius||""),shadow:String(n.shadow||""),opacity:n.opacity===void 0||n.opacity===null?"":String(n.opacity),pages:(t.binding?.pages||[]).join(", "),projects:(t.binding?.projects||[]).join(", ")}}export function themeFromForm(e){const t={};for(const n of f){const i=e[n];i===""||i===null||i===void 0||(t[n]=n==="opacity"?Number(i):i)}return t}export function recordFromForm(e,t){return{slug:String(e.slug||"").trim(),name:String(e.name||"").trim(),enabled:e.enabled!==!1,target:{selector:String(e.selector||"").trim(),match:String(e.match||"").trim(),exclude:String(e.exclude||"").trim()},inherit:e.inherit==="false"?!1:e.inherit||"append",theme:themeFromForm(e),binding:{...splitList(e.pages).length&&{pages:splitList(e.pages)},...splitList(e.projects).length&&{projects:splitList(e.projects)}},items:Array.isArray(t)?t:[]}}export function itemProblem(e){if(!e||typeof e!="object")return"Not an item";const t=e.type||"item";if(t==="divider")return"";if(!String(e.text||"").trim())return t==="header"?"The heading needs text":"Needs text";if(t==="header"||Array.isArray(e.submenu)&&e.submenu.length)return"";const n=e.action;if(!n||!n.type)return"Needs something to do";if(!ACTION_LABELS[n.type])return`"${n.type}" is not an action this site knows`;const i=ACTION_ARG[n.type];return i?.required&&!String(n[i.key]||"").trim()?`Needs a ${i.label.toLowerCase()}`:""}export function checkMenu(e,t,{isNew:n}){const i={},r=String(e.slug||"").trim();n&&(r?(r.length>64||!SLUG_RE.test(r))&&(i.slug="Lowercase letters, numbers and hyphens; no hyphen at either end."):i.slug="A slug is required."),String(e.name||"").trim()||(i.name="A name is required.");const a=String(e.selector||"").trim();if(a?m.has(a)&&(i.selector=`"${a}" would bind the whole page and swallow every other menu.`):i.selector="Say which region it covers.",e.opacity!==""&&e.opacity!==null&&e.opacity!==void 0){const o=Number(e.opacity);(!Number.isFinite(o)||o<20||o>100)&&(i.opacity="Between 20 and 100.")}e.accent&&!ACCENTS.includes(e.accent)&&!y.test(e.accent)&&(i.accent="A preset or #rrggbb.");const s=Array.isArray(t)?t:[],c=s.map(itemProblem);return s.some(o=>!["divider","header"].includes(o?.type||"item"))?c.some(Boolean)&&(i.items=`${c.filter(Boolean).length} item${c.filter(Boolean).length===1?"":"s"} not finished.`):i.items="Add at least one item that does something - headings and dividers alone open an empty menu.",{errors:i,itemErrors:c,ok:Object.keys(i).length===0}}export function itemRow(e,t,n,i=""){const r=e?.type||"item",a=Array.isArray(e?.submenu)&&e.submenu.length?e.submenu.length:0;let s="";if(r==="item"||r==="checkbox"||r==="radio"){if(a)s=`Opens a submenu of ${a}`;else if(e?.action?.type){const o=ACTION_ARG[e.action.type],l=o?String(e.action[o.key]||"").trim():"";s=(ACTION_LABELS[e.action.type]||e.action.type)+(l?`: ${l}`:"")}}const c=SELF_DISABLING[e?.action?.type]||NEEDS_TARGET[e?.action?.type]||"";return{key:`${t}`,index:t,type:r,typeLabel:b[r]||r,text:r==="divider"?"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500":String(e?.text||"").trim()||"(no text)",icon:String(e?.icon||""),does:s,note:c,danger:!!e?.danger,problem:i,first:t===0,last:t===n-1,cls:[r==="divider"&&"is-divider",r==="header"&&"is-header",i&&"is-bad"].filter(Boolean).join(" ")}}export function previewItems(e){const t=[];for(const n of e||[]){const i=n?.type||"item";if(i==="divider"){t.push({type:"divider"});continue}if(i==="header"){t.push({type:"header",header:n.text||""});continue}if(Array.isArray(n.submenu)&&n.submenu.length){const r=previewItems(n.submenu);r.length&&t.push({label:n.text||"(no text)",icon:n.icon||void 0,danger:!!n.danger,submenu:r});continue}t.push({label:n.text||"(no text)",icon:n.icon||void 0,danger:!!n.danger})}return t}export const previewable=e=>previewItems(e).some(t=>t.label);export function moveItem(e,t,n){const i=[...e];if(t<0||t>=i.length||n<0||n>=i.length||t===n)return i;const[r]=i.splice(t,1);return i.splice(n,0,r),i}export function duplicateItem(e,t){const n=[...e];return n[t]&&n.splice(t+1,0,JSON.parse(JSON.stringify(n[t]))),n}export function removeItem(e,t){return e.filter((n,i)=>i!==t)}export function insertItem(e,t,n){const i=[...e];return i.splice(Math.max(0,Math.min(t,i.length)),0,n),i}export const newItem=(e="item")=>e==="divider"?{type:"divider"}:e==="header"?{type:"header",text:"Heading"}:{type:"item",text:"New item",action:{type:"navigate",url:""}};export function itemForm(e){const t=e?.type||"item",n=e?.action?.type||"navigate",i=ACTION_ARG[n];return{type:t,text:String(e?.text||""),icon:String(e?.icon||""),action:n,arg:i?String(e?.action?.[i.key]||""):"",danger:!!e?.danger}}export function itemFromForm(e,t={}){const n=e.type||"item",{text:i,icon:r,action:a,danger:s,type:c,...o}=t||{};if(n==="divider")return{...o,type:"divider"};const l=String(e.text||"").trim();if(n==="header")return{...o,type:"header",text:l};const d={...o,type:n,text:l};if(String(e.icon||"").trim()&&(d.icon=String(e.icon).trim()),e.danger&&(d.danger=!0),!(Array.isArray(o.submenu)&&o.submenu.length)){const u=e.action||"navigate",p=t?.action?.type===u?{...t.action}:{type:u},g=ACTION_ARG[u];if(g){const h=String(e.arg||"").trim();h?p[g.key]=h:delete p[g.key]}d.action=p}return d}export function snapshot(e,t){return JSON.stringify([recordFromForm(e,t),e.slug])}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const BADGE_SOURCES=[{key:"pages",title:"Pages",url:"#/pages",countUrl:"/pages/_count",permission:"pages"},{key:"forms",title:"Forms",url:"#/forms",countUrl:"/forms/_count",permission:"collections"},{key:"notifications",title:"Notifications",url:"#/system/notifications",countUrl:"/system/notifications/_count",permission:"notifications"},{key:"settings",title:"Site Settings",url:"#/settings",countUrl:"/settings/_count",permission:"settings"},{key:"plugins",title:"Marketplace",url:"#/plugins",countUrl:"/plugins/_count",permission:"plugins"},{key:"views",title:"Views",url:"#/views",countUrl:"/views/_count",permission:"views"},{key:"layouts",title:"Layouts",url:"#/layouts",countUrl:"/layouts/_count",permission:"layouts"},{key:"apiTokens",title:"API Tokens",url:"#/api-tokens",countUrl:"/api-tokens/_count",permission:"api-tokens"},{key:"theme",title:"Theme",url:"#/theme",countUrl:"/theme/count",permission:"theme"}];const c=new Set(["notifications","settings","layouts","apiTokens"]);export function canAny(e=[],o){return o?e.some(t=>t===o||String(t).startsWith(`${o}.`)):!0}export function deltaText(e){if(e==null||!Number.isFinite(Number(e)))return"";const o=Number(e);return`${o>=0?"\u25B2":"\u25BC"} ${Math.abs(o).toFixed(0)}% vs yesterday`}export function rowValue(e,o){const t=(e?.details?.rows||[]).find(i=>i.label===o);return t?t.value:null}const a=e=>Number(e||0).toLocaleString("en-GB");export function kpisFrom(e,o={}){const t=o.pages,i=o.forms,n=rowValue(t,"Drafts"),s=rowValue(i,"Last 7 days"),r=e?.health?.status||null;return{hits:e?a(e.traffic?.today):"\u2014",hitsDelta:e?deltaText(e.traffic?.deltaPct):"",hitsUp:Number(e?.traffic?.deltaPct)>=0,active:e?a(e.realtime?.activeSessions):"\u2014",pages:t?a(t.count):"\u2014",pagesSub:t?Number(n)?`${a(n)} draft${Number(n)===1?"":"s"}`:"no drafts":"",submissions:i?a(s):"\u2014",submissionsSub:i?`${a(rowValue(i,"Last 24 hours"))} in the last 24 hours`:"",health:r?{ok:"Healthy",warn:"Needs a look",fail:"Failing"}[r]||r:"\u2014",healthTone:{ok:"ok",warn:"warn",fail:"danger"}[r]||""}}export function attentionFrom(e={},o=[]){const t={danger:0,attention:1,info:2},i=[];for(const n of BADGE_SOURCES){const s=e[n.key];if(!s||!(Number(s.count)>0&&(s.tone==="danger"||s.tone==="attention"||c.has(n.key))))continue;const l=(s.details?.items||[]).slice(0,2).map(u=>u.text).filter(Boolean);i.push({id:`badge:${n.key}`,title:n.title,text:String(s.label||""),meta:l.join(" \xB7 "),tone:s.tone==="danger"?"danger":"warn",rank:t[s.tone]??2,href:n.url})}for(const n of o||[])!n||n.level==="ok"||i.push({id:`health:${n.id||n.label}`,title:"Site health",text:`${n.label||n.id}: ${n.value??""}`.trim(),meta:Array.isArray(n.detail)?n.detail.slice(0,3).join(", "):"",tone:n.level==="fail"?"danger":"warn",rank:n.level==="fail"?0:1,href:""});return i.sort((n,s)=>n.rank-s.rank||n.title.localeCompare(s.title))}export function ago(e,o=Date.now()){const t=new Date(e).getTime();if(!Number.isFinite(t))return"";const i=Math.max(0,Math.round((o-t)/1e3));return i<60?"just now":i<3600?`${Math.round(i/60)} min ago`:i<86400?`${Math.round(i/3600)} hour${Math.round(i/3600)===1?"":"s"} ago`:i<86400*14?`${Math.round(i/86400)} day${Math.round(i/86400)===1?"":"s"} ago`:new Date(t).toISOString().slice(0,10)}export function activityRows(e=[],o=Date.now()){return(e||[]).map((t,i)=>{const n=ago(t.at,o);if(t.type==="entry"||t.type==="form"){const r=t.collection||t.form||"";return{id:`a${i}`,kind:"entry",icon:"database",text:`New entry in ${t.title||r}`,meta:n,href:r?`#/collections/${encodeURIComponent(r)}/entries`:"",url:"",slug:r}}const s=t.type==="publish"?"published":"edited";return{id:`a${i}`,kind:"page",icon:t.type==="publish"?"upload":"edit",text:`${t.actor||"Someone"} ${s} ${t.target||"/"}`,meta:n,href:`#/pages/edit${t.target||"/"}`,url:t.target||"/",slug:""}})}export function chartSeries(e=[]){const o=Array.isArray(e)?e:[];return{labels:o.map(t=>String(t.day||"").slice(5)),values:o.map(t=>Number(t.hits)||0),empty:!o.some(t=>Number(t.hits)>0)}}export function isSitePath(e){return typeof e=="string"&&/^\/(?!\/)/.test(e)&&!/^\/(admin|api|media|public)(\/|$)/.test(e)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SCREEN=/^\/forms\/(new|edit\/[^/?#]+)\/?(\?.*)?$/;const c=e=>String(e||"").split("?")[0].replace(/\/$/,"");export function leavingEditor(e,r){const t=c(e);return SCREEN.test(t)&&t!==c(r)}export const draftKey=e=>`form-editor.draft.${e||"new"}`;export function snapshotOf(e){const r=t=>Array.isArray(t)?t.map(r):t&&typeof t=="object"?Object.fromEntries(Object.keys(t).sort().filter(n=>t[n]!==void 0).map(n=>[n,r(t[n])])):t;return JSON.stringify(r(e))}export function draftOf(e){const r=JSON.parse(JSON.stringify(e||{}));return r.actions?.webhook&&delete r.actions.webhook.url,r}export function restoreDraft(e,r){const t=JSON.parse(JSON.stringify(e||{})),n=r?.actions?.webhook?.url;return t.actions?.webhook&&n!==void 0&&(t.actions.webhook.url=n),t}export function fingerprint(e){let r=2166136261;for(let t=0;t<String(e).length;t++)r^=String(e).charCodeAt(t),r=Math.imul(r,16777619)>>>0;return r.toString(16).padStart(8,"0")}export function uniqueName(e,r){const t=new Set(r),n=String(e||"field").replace(/_copy(_\d+)?$/,"")+"_copy";if(!t.has(n))return n;for(let o=2;;o++)if(!t.has(`${n}_${o}`))return`${n}_${o}`}export function duplicateField(e,r){const t=[...e||[]],n=t[r];if(!n)return t;const o=JSON.parse(JSON.stringify(n));return o.type==="page-break"?o.label=`${n.label||"Step"} (copy)`:o.type!=="spacer"&&(o.name=uniqueName(n.name,t.map(i=>i?.name).filter(Boolean)),o.label=`${n.label||"Field"} (copy)`),t.splice(r+1,0,o),t}export function moveField(e,r,t){const n=[...e||[]],o=r+t;if(r<0||r>=n.length||o<0||o>=n.length)return n;const[i]=n.splice(r,1);return n.splice(o,0,i),n}export function fieldTitle(e){return e?e.type==="spacer"?"Spacer":e.type==="page-break"?`Page break - ${e.label||"Untitled step"}`:e.label||e.name||"(unlabelled)":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const FILTERS=["inbox","new","spam"],WEEK_MS=6048e5;const g=new Set(["page-break","heading","paragraph","html","divider","spacer","banner"]);export function answerFields(e){return(e?.fields||[]).filter(t=>t&&t.name&&!g.has(t.type)).map(t=>({name:String(t.name),label:String(t.label||t.name),type:String(t.type||"text")}))}export function answerText(e){return e==null?"":Array.isArray(e)?e.map(answerText).filter(Boolean).join(", "):typeof e=="boolean"?e?"Yes":"No":typeof e=="object"?JSON.stringify(e):String(e).trim()}export const isSpam=e=>e?.data?.spam===!0,createdAt=e=>e?.meta?.createdAt||"",isNew=(e,t=Date.now())=>{const n=Date.parse(createdAt(e));return Number.isFinite(n)&&t-n<6048e5};const u=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;export function emailOf(e,t){const n=e?.data||{},r=t.find(a=>a.type==="email"&&u.test(answerText(n[a.name])));if(r)return answerText(n[r.name]);const i=t.find(a=>/e-?mail/i.test(a.name)&&u.test(answerText(n[a.name])));if(i)return answerText(n[i.name]);const c=t.find(a=>u.test(answerText(n[a.name])));return c?answerText(n[c.name]):""}const d=(e,t)=>e.length>t?`${e.slice(0,t-1)}\u2026`:e;export function submissionRow(e,t,n=Date.now()){const r=e?.data||{},i=t.filter(m=>answerText(r[m.name])!==""),c=i.find(m=>/(^|_|-)(full[-_ ]?)?name$|^name/i.test(m.name)&&m.type!=="email"),a=emailOf(e,t),o=c||i.find(m=>answerText(r[m.name])===a&&a)||i[0],p=o?d(answerText(r[o.name]),80):"Empty submission",s=i.filter(m=>m!==o).slice(0,3).map(m=>`${m.label}: ${d(answerText(r[m.name]),60)}`),l=isSpam(e),f=isNew(e,n);return{id:String(e?.id??""),title:p,meta:s.join(" \xB7 ")||"No other answers",email:a,at:createdAt(e),spam:l,fresh:f&&!l,rowCls:[l&&"is-spam",f&&!l&&"is-new"].filter(Boolean).join(" ")}}export function arrangeSubmissions(e,{filter:t="inbox",query:n="",from:r="",to:i="",now:c=Date.now()}={}){const a=String(n).trim().toLowerCase(),o=r?Date.parse(`${r}T00:00:00`):null,p=i?Date.parse(`${i}T23:59:59.999`):null;return[...e||[]].filter(s=>t==="spam"?isSpam(s):!isSpam(s)).filter(s=>t!=="new"||isNew(s,c)).filter(s=>!a||Object.values(s?.data||{}).some(l=>answerText(l).toLowerCase().includes(a))).filter(s=>{if(o===null&&p===null)return!0;const l=Date.parse(createdAt(s));return Number.isFinite(l)?(o===null||l>=o)&&(p===null||l<=p):!1}).sort((s,l)=>String(createdAt(l)).localeCompare(String(createdAt(s))))}export function submissionCounts(e,t=Date.now()){const n=e||[],r=n.filter(isSpam).length;return{inbox:n.length-r,new:n.filter(i=>!isSpam(i)&&isNew(i,t)).length,spam:r,all:n.length}}export function submissionText(e,t,{formTitle:n="",when:r=""}={}){const i=e?.data||{},c=t.filter(o=>answerText(i[o.name])!=="").map(o=>`${o.label}: ${answerText(i[o.name])}`),a=[n&&`${n} - submission`,r&&`Submitted ${r}`].filter(Boolean);return[...a,...a.length?[""]:[],...c].join(`
|
|
2
|
+
`)}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const d=["separator","spacer"];export const isStructural=t=>d.includes(t?.type);export function siblingsOf(t,n){const e=t.find(i=>i.id===n);return e?t.filter(i=>i.parentId===e.parentId):[]}export function moveRow(t,n,e){const i=siblingsOf(t,n),r=i.findIndex(a=>a.id===n),s=r+(e<0?-1:1);if(r<0||s<0||s>=i.length)return!1;i[r].touched=!0;const o=t.indexOf(i[r]),l=t.indexOf(i[s]);return[t[o],t[l]]=[t[l],t[o]],!0}export function indentRow(t,n){const e=siblingsOf(t,n),i=e.findIndex(r=>r.id===n);return i<=0||isStructural(e[i-1])?!1:(e[i].parentId=e[i-1].id,e[i].touched=!0,!0)}export function outdentRow(t,n){const e=t.find(r=>r.id===n);if(!e||e.parentId==null)return!1;const i=t.find(r=>r.id===e.parentId);return e.parentId=i?i.parentId:null,e.touched=!0,!0}export function newRow(t,n,e=null){return t==="separator"?{id:n,parentId:e,type:"separator"}:t==="spacer"?{id:n,parentId:e,type:"spacer",size:"md"}:{id:n,parentId:e,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}}export function insertBelow(t,n,e,i){const r=t.findIndex(o=>o.id===n);if(r===-1)return null;const s=newRow(e,i,t[r].parentId);return t.splice(r+1,0,s),s}export function addChild(t,n,e){const i=t.find(s=>s.id===n);if(!i||isStructural(i))return null;const r=newRow("item",e,i.id);return t.push(r),i.touched=!0,r}export function duplicateRow(t,n,e){const i=t.find(a=>a.id===n);if(!i||i.tool)return null;const r=new Map,s=[],o=(a,c)=>{if(a.tool)return;const u={...g(a),id:e(),parentId:c};delete u.tool,delete u.locked,u.touched=!0,r.set(a.id,u.id),s.push(u);for(const f of t.filter(p=>p.parentId===a.id))o(f,u.id)};if(o(i,i.parentId),!s.length)return null;s[0].text&&(s[0].text=`${s[0].text} (copy)`);const l=t.indexOf(i);return t.splice(l+1,0,s[0]),t.push(...s.slice(1)),s[0]}function g(t){return JSON.parse(JSON.stringify(t))}export function bindingFrom({slot:t="",projects:n=[],pagesText:e=""}={}){return t?{slot:t,projects:[...n],pages:String(e).split(`
|
|
2
|
+
`).map(i=>i.trim()).filter(Boolean)}:null}export function badPagePatterns(t=[]){return t.filter(n=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(n))}const m=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"];export function menuFromForm(t={},n={},e=null,i=[],r={}){const s=Object.fromEntries(m.filter(a=>t[a]).map(a=>[a,t[a]])),o={};if(t.position==="floating"){t.floatAnchor&&(o.anchor=t.floatAnchor);for(const[a,c]of[["offsetX",t.floatOffsetX],["offsetY",t.floatOffsetY],["width",t.floatWidth],["radius",t.floatRadius],["opacity",t.floatOpacity]])c!==""&&c!=null&&Number.isFinite(Number(c))&&(o[a]=Number(c));t.floatShadow&&(o.shadow=t.floatShadow),t.floatAccent&&(o.accent=t.floatAccent),t.floatTitle&&(o.title=String(t.floatTitle).trim()),t.floatCollapsible&&(o.collapsible=!0),t.floatCollapsible&&t.floatCollapsed&&(o.collapsed=!0),t.floatMovable&&(o.movable=!0),t.floatResizable&&(o.resizable=!0)}const l={};return t.highlightHover&&(l.hover=t.highlightHover),t.highlightActive&&(l.active=t.highlightActive),t.highlightAccent&&(l.accent=t.highlightAccent),{slug:String(n.slug||"").trim(),name:String(n.name||"").trim(),description:String(n.description||"").trim(),...t.variant&&{variant:t.variant},...t.position&&{position:t.position},...t.orientation&&{orientation:t.orientation},...t.orientation==="vertical"&&t.side&&{side:t.side},...t.orientation!=="vertical"&&t.align&&{align:t.align},...Object.keys(o).length&&{float:o},...Object.keys(l).length&&{highlight:l},...t.appearOnHover&&{appearOnHover:!0},...Object.keys(s).length&&{style:s},binding:e,items:i,meta:{...r||{},project:n.project||null}}}export function menuProblems(t){const n=[];/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(t.slug||"")||n.push("The slug must be lowercase letters, numbers and hyphens, no hyphen at either end (Meta tab)."),t.name||n.push("The menu needs a name (Meta tab).");const e=countUnlabelled(t.items);if(e&&n.push(`${e} item${e===1?" has":"s have"} no label - every item needs one (Items tab).`),t.binding){const i=badPagePatterns(t.binding.pages);i.length&&n.push(`Not valid page patterns: ${i.join(", ")} (Binding tab).`)}return n}export function countUnlabelled(t=[]){let n=0;for(const e of t||[])!isStructural(e)&&!(e.ref===!0&&e.url)&&!String(e.text||"").trim()&&n++,n+=countUnlabelled(e.items);return n}export const snapshot=t=>JSON.stringify(t);export function depthOfItems(t){if(!Array.isArray(t)||!t.length)return 0;let n=0;for(const e of t){const i=Array.isArray(e.items)&&e.items.length?depthOfItems(e.items):0;i>n&&(n=i)}return n+1}export function locationRows(t=[],n={},e={},i=[],r={}){const s=Object.fromEntries(i.map(o=>[o.slug,o.name||o.slug]));return t.map(o=>{const l=n[o.slot]||"",a=l&&r[l]||0,c=!!l&&o.maxDepth!=null&&a>o.maxDepth,u=o.slot==="overlay";return{slot:o.slot,label:o.label||o.slot,source:o.source||"",description:o.description||"",menu:l,menuName:l?s[l]||`${l} (missing)`:"",missing:!!l&&!s[l],tooDeep:c,depthText:c?`Nested ${a} deep - this slot shows ${o.maxDepth}`:"",empty:!l,emptyText:l?"":u?"Optional":"Nothing mapped",changed:(n[o.slot]||"")!==(e[o.slot]||"")}})}export function locationsPayload(t={},n=[]){const e=new Set(n.map(i=>i.slot));return Object.fromEntries(Object.entries(t).filter(([i,r])=>r&&(!n.length||e.has(i))))}export function locationsSnapshot(t={}){return JSON.stringify(Object.keys(t).filter(n=>t[n]).sort().map(n=>[n,t[n]]))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SCREEN=/^\/pages\/(new|edit\/.*?)(\?.*)?$/;export function pageOfPath(t){const e=String(t||"").match(/^\/pages\/edit(\/[^?#]*)/);return e?e[1]:""}export const draftKey=t=>`page-editor.draft.${t||"new"}`,FIELDS=["urlPath","title","description","layout","status","sortOrder","showInNav","sidebar","breadcrumbs","bundled","tags","category","visibility","project","theme","seoTitle","seoDesc","dconfig","body","css"];export function snapshot(t={}){const e=t||{},s={};for(const n of FIELDS){const o=e[n];n==="tags"?s[n]=Array.isArray(o)?o.map(String):[]:typeof o=="boolean"?s[n]=o:n==="css"?s[n]=o==null?null:String(o):s[n]=o==null?"":String(o)}return JSON.stringify(s)}export function makeDraft(t,e,s=new Date().toISOString()){return{state:JSON.parse(snapshot(t)),base:e,at:s}}export function readDraft(t){return!t||typeof t!="object"||!t.state||typeof t.state!="object"?null:{state:JSON.parse(snapshot(t.state)),base:typeof t.base=="string"?t.base:"",at:typeof t.at=="string"?t.at:""}}export const draftIsStale=(t,e)=>!!(t?.base&&e&&t.base!==e);export function problems(t={}){const e=[],s=String(t.urlPath||"").trim();s?s.startsWith("/")?/\s/.test(s)&&e.push({field:"urlPath",text:"The address cannot contain spaces."}):e.push({field:"urlPath",text:"The address starts with / - for example /about."}):e.push({field:"urlPath",text:"The page needs an address - its URL path."});const n=String(t.dconfig||"").trim();if(n)try{const o=JSON.parse(n);(!o||typeof o!="object"||Array.isArray(o))&&e.push({field:"dconfig",text:"DConfig must be a JSON object ({ \u2026 })."})}catch{e.push({field:"dconfig",text:"DConfig is not valid JSON."})}return e}export function bannerSection(t,e,s){if(!t)return"New page";const n=String(e||"").trim();return n?`Edit \u201C${n}\u201D`:`Edit ${s||""}`.trim()}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const REQUIRED_FILES=["plugin.json","plugin.js","config.js"],TEXT_EXTENSIONS=[".js",".json",".md",".html",".css",".txt",".svg"];const d=e=>e.includes("/")?e.slice(0,e.lastIndexOf("/")):"",p=e=>e.includes("/")?e.slice(e.lastIndexOf("/")+1):e;export function fileScope(e){return String(e||"").startsWith("admin/")?{kind:"admin",label:"Admin file",hint:"Applies when the admin tab is refreshed."}:{kind:"server",label:"Server file",hint:"Read when the server starts - restart to apply."}}export function fileRows(e,{openPath:n=null,dirty:a=new Set,query:t=""}={}){const l=String(t).trim().toLowerCase(),s=new Map;for(const r of e||[]){if(l&&!r.path.toLowerCase().includes(l))continue;const i=d(r.path);s.has(i)||s.set(i,[]),s.get(i).push(r)}const o=[];for(const r of[...s.keys()].sort((i,c)=>i===""?-1:c===""?1:i.localeCompare(c))){r&&o.push({key:`dir:${r}`,isDir:!0,path:r,label:`${r}/`,cls:"is-dir",dirty:!1,meta:""});for(const i of s.get(r).sort((c,f)=>c.path.localeCompare(f.path))){const c=a.has(i.path);o.push({key:i.path,isDir:!1,path:i.path,label:p(i.path),dirty:c,meta:[fileScope(i.path).label,i.size!=null?formatSize(i.size):""].filter(Boolean).join(" \xB7 "),cls:[r?"is-nested":"",i.path===n?"is-open":"",c?"is-dirty":""].filter(Boolean).join(" ")})}}return o}export function formatSize(e){const n=Number(e)||0;return n<1024?`${n} B`:`${(n/1024).toFixed(n<10240?1:0)} KB`}export function checkPath(e,{existing:n=[],from:a=""}={}){const t=String(e||"").trim().replace(/\\/g,"/");if(!t)return"Give it a path, e.g. admin/views/cart.js.";if(t.startsWith("/"))return"A path inside the plugin - no leading /.";const l=t.split("/");if(l.some(o=>!o||o==="."||o===".."))return"No empty, . or .. parts.";if(l.some(o=>o.startsWith(".")))return"No hidden (dot) files or folders.";const s=t.includes(".")?t.slice(t.lastIndexOf(".")).toLowerCase():"";return TEXT_EXTENSIONS.includes(s)?t!==a&&n.includes(t)?`${t} already exists.`:"":`Only ${TEXT_EXTENSIONS.join(", ")} files can be edited here.`}export function draftOf(e){const n={};for(const[a,t]of e)t.content!==t.saved&&(n[a]={content:t.content,base:t.saved});return Object.keys(n).length?{files:n}:null}export function restorePlan(e,n){const a=[],t=[],l=[];for(const[s,o]of Object.entries(e?.files||{})){if(!(s in n)||n[s]===null){l.push(s);continue}typeof o?.content=="string"&&(n[s]!==o.base&&t.push(s),a.push({path:s,content:o.content,saved:n[s]}))}return{restore:a,changed:t,gone:l}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{CORE as m,checkProject as p}from"./projects-arrange.js";export const TYPES=[{key:"pages",label:"Pages",singular:"page",icon:"file-text",tone:"var(--dm-primary)",path:"/pages/new"},{key:"collections",label:"Collections",singular:"collection",icon:"database",tone:"var(--dm-secondary)",path:"/collections/new"},{key:"forms",label:"Forms",singular:"form",icon:"layout",tone:"var(--dm-success)",path:"/forms/new"},{key:"actions",label:"Actions",singular:"action",icon:"zap",tone:"var(--dm-warning, var(--dm-primary))",path:"/actions/new"},{key:"menus",label:"Menus",singular:"menu",icon:"menu",tone:"var(--dm-info)",path:"/menus/new"},{key:"blocks",label:"Blocks",singular:"block",icon:"box",tone:"var(--dm-primary-hover, var(--dm-primary))",path:"/blocks/new"},{key:"components",label:"Components",singular:"component",icon:"component",tone:"var(--dm-warning-dark, var(--dm-warning, var(--dm-secondary)))",path:"/components/new"},{key:"views",label:"Views",singular:"view",icon:"eye",tone:"var(--dm-info)",path:"/views/new"},{key:"roles",label:"Roles",singular:"role",icon:"shield",tone:"var(--dm-danger)",path:"/roles"},{key:"users",label:"Users",singular:"user",icon:"users",tone:"var(--dm-secondary)",path:"/users/new"},{key:"apis",label:"APIs",singular:"API endpoint",icon:"code",tone:"var(--dm-success)",path:"/api-endpoints/new"}];export function projectFromPath(e,{settings:t=!1}={}){const r=String(e||"").replace(/^#/,"").split("?")[0].replace(/\/+$/,""),n=t?r.match(/^\/projects\/([^/]+)\/settings$/):r.match(/^\/projects\/([^/]+)$/);if(!n||!t&&(n[1]==="new"||n[1]==="edit"))return null;try{return decodeURIComponent(n[1])}catch{return null}}export function countsOf(e){return Object.fromEntries(Object.entries(e||{}).map(([t,r])=>[t,Array.isArray(r)?r.length:Number(r)||0]))}export function tilesFor(e,{slug:t,inline:r=[]}={}){const n=encodeURIComponent(t);let a=0;return{tiles:TYPES.map(o=>{const s=Number(e?.[o.key])||0;a+=s;const c=o.path.includes("?")?"&":"?";return{key:o.key,label:o.label,singular:o.singular,icon:o.icon,tone:o.tone,count:s,has:s>0,cls:s>0?"":"is-empty",inline:r.includes(o.key),listHash:`#/projects/${n}/${o.key}`,newHash:`#${o.path}${c}project=${n}`,countText:String(s)}}),total:a,totalText:`${a} artefact${a===1?"":"s"}`}}export function relTime(e,t=Date.now()){if(!e)return"";const r=new Date(e).getTime();if(Number.isNaN(r))return"";const n=Math.max(0,Math.round((t-r)/1e3)),a=Math.round(n/60),i=Math.round(a/60),o=Math.round(i/24),s=(l,u)=>`${l} ${u}${l===1?"":"s"} ago`;if(n<45)return"just now";if(a<60)return s(a,"min");if(i<24)return s(i,"hour");if(o<30)return s(o,"day");const c=Math.round(o/30);return c<12?s(c,"month"):s(Math.round(o/365),"year")}export function settingsForm(e={}){return{name:String(e.name??""),description:String(e.description??""),icon:String(e.icon||"folder"),rootUrl:String(e.rootUrl??""),sortOrder:String(e.sortOrder??0),enabled:e.enabled!==!1}}export function settingsSnapshot(e={}){return JSON.stringify({name:String(e.name??"").trim(),description:String(e.description??"").trim(),icon:String(e.icon??"").trim()||"folder",rootUrl:String(e.rootUrl??"").trim(),sortOrder:String(Number.parseInt(e.sortOrder,10)||0),enabled:e.enabled!==!1})}export function settingsPayload(e,t={}){return p(e,{isNew:!1,isProtected:t.protected===!0,slug:t.slug||""})}export function settingsRules(e={}){const t=e.slug===m,r=e.protected===!0;return{isCore:t,isProtected:r,canToggle:!t,rootLocked:r,canDelete:!r,canUntag:!r}}export const draftKey=e=>`project-settings.draft.${e}`;export function untagSummary(e){const t=TYPES.map(n=>[n,Number(e?.[n.key])||0]).filter(([,n])=>n>0).map(([n,a])=>`${a} ${a===1?n.singular:n.key==="apis"?"API endpoints":n.label.toLowerCase()}`);return{total:Object.values(e||{}).reduce((n,a)=>n+(Number(a)||0),0),text:t.length?t.join(", "):"nothing"}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export const BASE_ROLES=["super-admin","admin","user"],NAME_RE=/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/,ROUTE_RE=/^\/[A-Za-z0-9\-_/]*$/,NEEDED_TO_RETURN=[{perm:"plugins.read",why:"open Roles"},{perm:"collections.read",why:"load a role"},{perm:"collections.update",why:"save a role"}];const $=e=>(e?.actions||[]).map(n=>n.key);export function expandPerms(e,n=[]){const i=Array.isArray(e)?e:[],r=new Set;for(const o of n){const l=$(o);i.includes(o.key)?l.forEach(t=>r.add(`${o.key}.${t}`)):l.forEach(t=>{i.includes(`${o.key}.${t}`)&&r.add(`${o.key}.${t}`)})}return[...r].sort()}export function compactPerms(e,n=[],i=[]){const r=new Set(e),o=new Set(n.map(t=>t.key)),l=[];for(const t of n){const a=$(t),u=a.filter(c=>r.has(`${t.key}.${c}`));a.length&&u.length===a.length?l.push(t.key):u.forEach(c=>l.push(`${t.key}.${c}`))}for(const t of Array.isArray(i)?i:[])o.has(String(t).split(".")[0])||l.push(t);return l}export function formFromRole(e={},n=[]){return{name:String(e.name??""),label:String(e.label??""),level:e.level===void 0||e.level===null?"":String(e.level),badgeClass:String(e.badgeClass||"badge-secondary"),project:String(e.meta?.project||""),adminAllow:(Array.isArray(e.admin?.allow)?e.admin.allow:[]).join(`
|
|
2
|
+
`),adminHome:String(e.admin?.home||""),granted:expandPerms(e.permissions,n)}}export function snapshot(e){return JSON.stringify({name:String(e.name??"").trim(),label:String(e.label??"").trim(),level:String(e.level??"").trim(),badgeClass:e.badgeClass||"",project:e.project||"",adminAllow:w(e.adminAllow),adminHome:String(e.adminHome??"").trim(),granted:[...e.granted||[]].sort()})}const w=e=>String(e??"").split(`
|
|
3
|
+
`).map(n=>n.trim()).filter(Boolean);export function roleFacts(e={}){const n=e.level===0,i=BASE_ROLES.includes(e.name),r=typeof e.plugin=="string"?e.plugin:"",o=n||i?"A built-in role keeps its name.":r?`Made by the ${r} plugin, which finds it by this name.`:"";return{isRoot:n,isBase:i,plugin:r,nameLocked:o}}export function checkRole(e,{facts:n={},taken:i=[],originalName:r=""}={}){const o={},l=String(e.name??"").trim();if(n.nameLocked||(NAME_RE.test(l)?l!==r&&i.includes(l)&&(o.name=`"${l}" is already a role.`):o.name="Lowercase letters, numbers and hyphens; no hyphen at either end."),String(e.label??"").trim()||(o.label="A role needs a label."),!n.isRoot){const t=Number(String(e.level??"").trim());String(e.level??"").trim()===""||!Number.isInteger(t)?o.level="A whole number.":t<1&&(o.level="0 belongs to the root role - use 1 or more.");const a=[...w(e.adminAllow),String(e.adminHome??"").trim()].find(u=>u&&!ROUTE_RE.test(u));a&&(o.admin=`"${a}" is not an admin route - use a path like /portal.`)}return o}export function payloadFromForm(e,n={},{facts:i={},resources:r=[]}={}){const{admin:o,...l}=n,t=w(e.adminAllow),a=String(e.adminHome??"").trim(),u=!i.isRoot&&(t.length||a)?{allow:t,...a&&{home:a}}:null,c=Number.parseInt(e.level,10);return{...l,...u&&{admin:u},name:i.nameLocked?n.name:String(e.name??"").trim()||n.name,label:String(e.label??"").trim(),level:i.isRoot?n.level:Number.isFinite(c)?c:n.level,permissions:compactPerms(e.granted,r,n.permissions),badgeClass:e.badgeClass||n.badgeClass,meta:{...n.meta||{},project:e.project||null}}}export function matrixRows(e=[],n=[],i=[],{query:r="",show:o="all",locked:l=!1}={}){const t=new Set(i),a=String(r).trim().toLowerCase(),u=[],c=[...n,...new Set(e.map(m=>m.group).filter(m=>!n.includes(m)))];for(const m of c){const y=e.filter(s=>s.group===m),b=y.filter(s=>{const g=$(s).filter(h=>t.has(`${s.key}.${h}`)).length;return o==="on"&&!g||o==="off"&&g?!1:a?[m,s.key,s.label,s.description,...(s.actions||[]).map(h=>h.label)].some(h=>String(h||"").toLowerCase().includes(a)):!0});if(!b.length)continue;const f=y.reduce((s,p)=>s+$(p).length,0),d=y.reduce((s,p)=>s+$(p).filter(g=>t.has(`${p.key}.${g}`)).length,0);u.push({id:`g:${m}`,kind:"group",group:m,label:m,icon:"layers",description:"",meta:`${d} of ${f} granted`,state:d===0?"none":d===f?"all":"some",actions:[],locked:l});for(const s of b){const p=s.actions||[],g=p.filter(h=>t.has(`${s.key}.${h.key}`)).length;u.push({id:`r:${s.key}`,kind:"res",group:m,key:s.key,label:s.label||s.key,icon:s.icon||"box",description:s.description||"",meta:g===p.length&&g?"Full access":g?`${g} of ${p.length}`:"No access",state:g===0?"none":g===p.length?"all":"some",locked:l,actions:p.map(h=>({key:h.key,label:h.label||h.key,on:t.has(`${s.key}.${h.key}`)?"true":"false",title:h.description||h.label||h.key}))})}}return u}export function setActions(e,n,{group:i,resource:r,to:o,only:l}={}){const t=new Set(e);for(const a of n)if(!(r&&a.key!==r)&&!(i&&a.group!==i))for(const u of $(a)){const c=`${a.key}.${u}`;(l?u===l:o)?t.add(c):t.delete(c)}return[...t].sort()}export function toggleAction(e,n){const i=new Set(e);return i.has(n)?i.delete(n):i.add(n),[...i].sort()}const k=e=>e.length<=1?e.join(""):`${e.slice(0,-1).join(", ")} and ${e.at(-1)}`;export function summaryLines(e,n=[],i=[],{isRoot:r=!1,admin:o=null}={}){if(r)return[{id:"root",title:"Everything",text:"The root role holds every permission - core and plugins - whatever is ticked here."}];const l=new Set(e),t=[],a=[...i,...new Set(n.map(c=>c.group).filter(c=>!i.includes(c)))];for(const c of a){const m=[],y=[],b=[];for(const d of n.filter(s=>s.group===c)){const s=d.actions||[],p=s.filter(h=>l.has(`${d.key}.${h.key}`));if(!p.length)continue;const g=d.label||d.key;p.length===s.length?m.push(g):p.length===1&&p[0].key==="read"?y.push(g):b.push(`${g} - ${k(p.map(h=>String(h.label||h.key).toLowerCase()))}`)}const f=[];m.length&&f.push(`manage ${k(m)}`),y.length&&f.push(`view ${k(y)}`),f.push(...b),f.length&&t.push({id:c,title:c,text:f.join("; ")})}t.length||t.push({id:"none",title:"Nothing",text:"No permissions: holders can sign in and edit their own profile, nothing more."});const u=Array.isArray(o?.allow)?o.allow.filter(Boolean):[];return u.length&&t.push({id:"scope",title:"Admin area",text:`Confined to ${k(u)}${o.home?`, starting at ${o.home}`:""} (unless another role they hold is not confined).`}),t}export function selfRisks({holds:e=!1,otherRoot:n=!1,otherGranted:i=[],oldLevel:r,newLevel:o,granted:l=[],isRoot:t=!1}={}){if(!e||t||n)return[];const a=[];Number.isFinite(o)&&Number.isFinite(r)&&o>r&&a.push(`You hold this role: level ${r} \u2192 ${o} makes you less senior, and you may lose the right to manage users at levels ${r+1}-${o}.`);const u=new Set([...l,...i]),c=NEEDED_TO_RETURN.filter(m=>!u.has(m.perm));return c.length&&a.push(`You hold this role and no other role gives you ${k(c.map(m=>m.perm))} - without ${c.length===1?"it":"them"} you could not ${k(c.map(m=>m.why))} to put this back.`),a}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{fieldKind as g,profileValue as S}from"./my-profile-arrange.js";export const PASSWORD_MIN=8;const y=/^[^\s@]+@[^\s@]+\.[^\s@]+$/,u=(e,o)=>e?.[o]?.level??99;export const rolesHeld=e=>[...new Set([e?.role,...Array.isArray(e?.additionalRoles)?e.additionalRoles:[]].filter(Boolean))];export function effectiveLevel(e,o){const n=rolesHeld(e);return n.length?Math.min(...n.map(s=>u(o,s))):99}export function editRights(e,o,n){if(!o)return{self:!1,canEdit:!0,reason:""};const s=!!e?.id&&e.id===o.id,l=s||effectiveLevel(e,n)<effectiveLevel(o,n);return{self:s,canEdit:l,reason:l?"":"Your role is not senior to theirs, so you can look but not change anything."}}export function roleChoices(e,o,n=null){const s=effectiveLevel(o,e),l=new Set(rolesHeld(n));return Object.entries(e||{}).map(([t,r])=>{const c=(r.level??99)>s||l.has(t);return{name:t,label:r.label||t,level:r.level??99,assignable:c,reason:c?"":"Only a more senior role can give this one"}}).sort((t,r)=>t.level-r.level||t.label.localeCompare(r.label))}export function formFromUser(e,o=[],{project:n="",defaultRole:s="user"}={}){return{name:String(e?.name??""),email:String(e?.email??""),password:"",role:String(e?.role||s),additionalRoles:Array.isArray(e?.additionalRoles)?e.additionalRoles.filter(Boolean).map(String):[],ownedByProject:String(e?e.meta?.project??"":n),projects:Array.isArray(e?.projects)?e.projects.filter(Boolean).map(String):[],isActive:e?e.isActive!==!1:!0,profile:Object.fromEntries((o||[]).filter(l=>l?.name).map(l=>[l.name,S(l,e?.profile?.[l.name])]))}}export function draftOf(e){const{password:o,...n}=e||{};return n}const p=e=>[...new Set((e||[]).filter(Boolean).map(String))];export function cleanAdditional(e,o){return p(e).filter(n=>n!==o)}export function snapshot(e){const o=e||{};return JSON.stringify({name:String(o.name??"").trim(),email:String(o.email??"").trim().toLowerCase(),password:!!o.password,role:o.role||"",additionalRoles:cleanAdditional(o.additionalRoles,o.role).sort(),ownedByProject:o.ownedByProject||"",projects:p(o.projects).sort(),isActive:o.isActive!==!1,profile:o.profile||{}})}export function checkUserForm(e,{isNew:o,fields:n=[],saved:s,choices:l=[]}={}){const t={},r=e||{};String(r.name??"").trim().length<2&&(t.name="A name needs at least 2 characters."),y.test(String(r.email??"").trim())||(t.email="That does not look like an email address.");const c=String(r.password??"");o&&!c?t.password=`A new account needs a password (at least ${PASSWORD_MIN} characters).`:c&&c.length<PASSWORD_MIN&&(t.password=`At least ${PASSWORD_MIN} characters.`);const d=Object.fromEntries(l.map(i=>[i.name,i]));r.role?l.length&&!d[r.role]?.assignable&&(t.role="Only a more senior role can give this one."):t.role="Choose a role.";const f=cleanAdditional(r.additionalRoles,r.role).filter(i=>l.length&&!d[i]?.assignable);f.length&&(t.additionalRoles=`You cannot give ${f.join(", ")}.`);const a=!s||JSON.stringify(r.profile||{})!==JSON.stringify(s.profile||{});for(const i of a?n||[]:[]){if(!i?.name||!i.required||g(i.type)==="hidden")continue;const m=r.profile?.[i.name];(Array.isArray(m)?!m.length:g(i.type)==="checkbox"?m!==!0:String(m??"").trim()==="")&&(t[`profile.${i.name}`]=`${i.label||i.name} is required.`)}return t}export function buildUserPayload(e,o,{isNew:n,fields:s=[],meta:l={}}={}){const t=o||{},r=cleanAdditional(t.additionalRoles,t.role),c=p(t.projects),d=()=>Object.fromEntries((s||[]).filter(i=>i?.name).map(i=>[i.name,t.profile?.[i.name]??S(i,void 0)]));if(n){const i={name:String(t.name).trim(),email:String(t.email).trim(),password:String(t.password||""),role:t.role,additionalRoles:r};return c.length&&(i.projects=c),t.ownedByProject&&(i.meta={project:t.ownedByProject}),t.isActive===!1&&(i.isActive=!1),JSON.stringify(t.profile||{})!==JSON.stringify(e?.profile||{})&&(i.profile=d()),{create:i}}const f=e||{},a={};return String(t.name).trim()!==String(f.name??"").trim()&&(a.name=String(t.name).trim()),String(t.email).trim().toLowerCase()!==String(f.email??"").trim().toLowerCase()&&(a.email=String(t.email).trim()),t.password&&(a.password=String(t.password)),t.role!==f.role&&(a.role=t.role),(JSON.stringify([...r].sort())!==JSON.stringify(cleanAdditional(f.additionalRoles,f.role).sort())||a.role)&&(a.additionalRoles=r),JSON.stringify([...c].sort())!==JSON.stringify(p(f.projects).sort())&&(a.projects=c),(t.ownedByProject||"")!==(f.ownedByProject||"")&&(a.meta={...l||{},project:t.ownedByProject||null}),t.isActive!==!1!=(f.isActive!==!1)&&(a.isActive=t.isActive!==!1),JSON.stringify(t.profile||{})!==JSON.stringify(f.profile||{})&&(a.profile=d()),Object.keys(a).length?{update:a}:null}export function emailConflict(e){return e?.status===409&&/email/i.test(String(e?.message||""))?"Another account already uses this email.":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const STAGE_TYPES=[{type:"$match",label:"Filter rows",guided:!0},{type:"$sort",label:"Order rows",guided:!0},{type:"$lookup",label:"Join a collection",example:`{
|
|
2
|
+
"from": "other-collection",
|
|
3
|
+
"localField": "data.ownerId",
|
|
4
|
+
"foreignField": "id",
|
|
5
|
+
"as": "owner"
|
|
6
|
+
}`},{type:"$project",label:"Reshape fields",example:`{
|
|
7
|
+
"data.title": 1,
|
|
8
|
+
"data.price": 1
|
|
9
|
+
}`},{type:"$unwind",label:"Flatten an array",example:'"$data.tags"'},{type:"$addFields",label:"Compute fields",example:`{
|
|
10
|
+
"label": "$data.title"
|
|
11
|
+
}`},{type:"$group",label:"Group and total",example:`{
|
|
12
|
+
"_id": "$data.category",
|
|
13
|
+
"count": {"$sum": 1}
|
|
14
|
+
}`},{type:"$limit",label:"Keep the first N",example:"10"},{type:"$skip",label:"Skip the first N",example:"10"},{type:"$count",label:"Count the rows",example:'"total"'}];const f=Object.fromEntries(STAGE_TYPES.map(r=>[r.type,r]));export const isGuided=r=>f[r]?.guided===!0,OPERATORS=[{value:"eq",label:"= equals"},{value:"ne",label:"\u2260 not equals"},{value:"gt",label:"> greater than"},{value:"lt",label:"< less than"},{value:"gte",label:"\u2265 greater or equal"},{value:"lte",label:"\u2264 less or equal"},{value:"contains",label:"~ contains"},{value:"in",label:"\u2208 one of (comma list)"}];const g={eq:"=",ne:"\u2260",gt:">",lt:"<",gte:"\u2265",lte:"\u2264",contains:"~",in:"\u2208"};export const slugify=r=>String(r??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");export function matchToRows(r){const n=r?.$match||r||{},e=Array.isArray(n.$or),t=[],o={$eq:"eq",$ne:"ne",$gt:"gt",$lt:"lt",$gte:"gte",$lte:"lte",$in:"in"},s=a=>{for(const[i,l]of Object.entries(a||{}))if(l&&typeof l=="object"&&!Array.isArray(l)){if("$regex"in l){t.push({field:i,op:"contains",val:String(l.$regex??"")});continue}for(const[p,c]of Object.entries(l))o[p]&&t.push({field:i,op:o[p],val:Array.isArray(c)?c.join(", "):String(c)})}else t.push({field:i,op:"eq",val:String(l??"")})};return e?n.$or.forEach(s):s(n),{logic:e?"or":"and",rows:t}}const u=r=>String(r).startsWith("__")?String(r).slice(2):String(r),d=r=>r!==""&&!Number.isNaN(Number(r))?Number(r):r;export function rowsToMatch(r,n){const e=[];for(const t of n||[]){const o=String(t?.field??"").trim();if(!o)continue;const s=u(o),a=String(t.val??"").trim(),i={};switch(t.op){case"ne":i[s]={$ne:a};break;case"gt":case"lt":case"gte":case"lte":i[s]={[`$${t.op}`]:d(a)};break;case"contains":i[s]={$regex:a,$options:"i"};break;case"in":i[s]={$in:a.split(",").map(l=>l.trim()).filter(Boolean)};break;default:i[s]=a}e.push(i)}return e.length?r==="or"?{$or:e}:Object.assign({},...e):{}}export const sortToRows=r=>Object.entries(r?.$sort||r||{}).map(([n,e])=>({field:n,dir:Number(e)===-1?-1:1}));export function rowsToSort(r){const n={};for(const e of r||[]){const t=String(e?.field??"").trim();t&&(n[u(t)]=Number(e.dir)===-1?-1:1)}return n}export function parseStageJson(r,n){const e=String(n??"").trim();if(r==="$limit"||r==="$skip"){const t=Number(e.replace(/^"|"$/g,""));return Number.isInteger(t)&&t>=0?{config:t,error:""}:{config:null,error:`${r} takes a whole number, e.g. 10`}}if(r==="$count"){const t=e.replace(/^"|"$/g,"");return/^[A-Za-z_][\w]*$/.test(t)?{config:t,error:""}:{config:null,error:'$count takes a field name, e.g. "total"'}}if(!e)return{config:{},error:r==="$unwind"?'$unwind needs the array to flatten, e.g. "$data.tags"':""};try{const t=JSON.parse(e);return r==="$unwind"&&(typeof t=="string"||t&&typeof t=="object")?{config:t,error:""}:!t||typeof t!="object"||Array.isArray(t)?{config:null,error:`${r} takes an object - {\u2026}`}:{config:t,error:""}}catch(t){return{config:null,error:`Not valid JSON: ${t.message}`}}}export function stageText(r,n){return n==null||typeof n=="object"&&!Array.isArray(n)&&!Object.keys(n).length?"":typeof n=="number"?String(n):JSON.stringify(n,null,2)}export function stageSummary(r){const{type:n,config:e}=r||{};if(n==="$match"){const{logic:t,rows:o}=matchToRows(e);return o.length?o.slice(0,2).map(a=>`${u(a.field).replace(/^data\./,"")} ${g[a.op]||"="} ${a.val}`).join(t==="or"?" or ":" and ")+(o.length>2?` \u2026 (${o.length} conditions)`:""):"every row"}if(n==="$sort"){const t=sortToRows(e);return t.length?t.map(o=>`${o.field.replace(/^data\./,"")} ${o.dir===-1?"\u2193":"\u2191"}`).join(", "):"no order set"}if(n==="$lookup")return e?.from?`join ${e.from}${e.as?` as ${e.as}`:""}`:"join\u2026";if(n==="$limit")return`first ${e}`;if(n==="$skip")return`skip ${e}`;if(n==="$count")return`count as ${e}`;if(n==="$unwind")return`flatten ${typeof e=="string"?e:e?.path||"\u2026"}`;if(e&&typeof e=="object"){const t=Object.keys(e);return t.length?`${t.slice(0,3).join(", ")}${t.length>3?", \u2026":""}`:"empty"}return f[n]?.label||""}export function move(r,n,e){const t=n+e;if(n<0||n>=r.length||t<0||t>=r.length)return r.slice();const o=r.slice(),[s]=o.splice(n,1);return o.splice(t,0,s),o}export function checkView(r,{stageErrors:n=[],isNew:e=!1,taken:t=[]}={}){const o=r||{},s={source:[],pipeline:[],display:[],access:[]};if(String(o.title??"").trim()||s.source.push("A view needs a title."),o.pipeline?.source||s.source.push("Choose the collection it reads from."),e){const i=o.slug?slugify(o.slug):slugify(o.title);i?t.includes(i)&&s.source.push(`"${i}" is already a view - choose another slug.`):s.source.push("The title (or slug) needs at least one letter or number.")}for(const i of n)s.pipeline.push(`Stage ${i.index+1} (${i.type}): ${i.error}`);const a=Number(o.display?.pageSize);return(!Number.isInteger(a)||a<1||a>200)&&s.display.push("Page size is 1 to 200."),o.display?.mode==="block"&&!o.display?.block&&s.display.push("Block mode needs a block template."),o.access?.rowLevel?.mode==="field"&&!String(o.access.rowLevel.field??"").trim()&&s.access.push("Field Match needs the field to compare."),!o.access?.public&&!(o.access?.roles||[]).length&&s.access.push("Pick at least one role, or make it public."),s}export const errorCount=r=>Object.values(r||{}).reduce((n,e)=>n+e.length,0);export function snapshot(r){const n=e=>Array.isArray(e)?e.map(n):e&&typeof e=="object"?Object.fromEntries(Object.keys(e).sort().map(t=>[t,n(e[t])])):e;return JSON.stringify(n(r??null))}export const draftKey=r=>`view-editor.draft.${r||"new"}`;export function flattenDoc(r,n="",e={}){for(const[t,o]of Object.entries(r||{})){const s=n?`${n}.${t}`:t;o&&typeof o=="object"&&!Array.isArray(o)&&Object.keys(o).length<8?flattenDoc(o,s,e):e[s]=o}return e}export function resultColumns(r,n=[]){return Array.isArray(n)&&n.length?n.map(e=>({key:e.key,label:e.label||e.key})):Object.keys(flattenDoc(r?.[0]||{})).slice(0,6).map(e=>({key:e,label:e}))}export function cellText(r,n,e=80){let t=r;for(const s of String(n).split("."))t=t?.[s];const o=t==null?"":Array.isArray(t)?t.map(s=>s&&typeof s=="object"?JSON.stringify(s):String(s)).join(", "):typeof t=="object"?JSON.stringify(t):String(t);return o.length>e?`${o.slice(0,e)}\u2026`:o}
|