domma-cms 0.114.0 → 0.115.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/admin/index.html +1 -0
- package/admin/js/api.js +1 -1
- package/admin/js/lib/project-remove.js +1 -0
- package/admin/js/templates/project-detail.html +8 -0
- package/admin/js/templates/project-settings.html +5 -0
- package/admin/js/views/project-detail.js +2 -2
- package/admin/js/views/project-settings.js +1 -1
- package/package.json +1 -1
- package/public/css/site.css +1 -1
- package/public/js/site.js +1 -1
- package/server/routes/api/projects.js +28 -0
- package/server/routes/favicon.js +59 -0
- package/server/server.js +6 -0
- package/server/services/embedCsp.js +33 -0
- package/server/services/markdown.js +10 -4
- package/server/services/presetCollections.js +3 -1
- package/server/services/projects.js +199 -1
- package/server/templates/page.html +3 -2
package/admin/index.html
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Domma CMS - Admin</title>
|
|
7
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
7
8
|
|
|
8
9
|
<!-- DommaJS CSS -->
|
|
9
10
|
<link rel="stylesheet" href="/dist/domma/domma.css">
|
package/admin/js/api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{loginHashFor as $}from"./lib/login-arrange.js";const c="/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 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(`${c}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(!o.ok)throw f(),sendToLogin({expired:!0}),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(`${c}${e}`,{...o,headers:r(n)});if(s.status===401&&p())try{n=await T(),s=await fetch(`${c}${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.body=i,d}return s.status===204?null:s.json()}async function O(e,o){const n=a(),r=n?{Authorization:`Bearer ${n}`}:{},s=await fetch(`${c}${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)}),verifyLogin:e=>t("/auth/login/verify",{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})}),checkReset:(e,o)=>t("/auth/reset-password/check",{method:"POST",body:JSON.stringify({token:e,...o&&{password:o}})}),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=>O("/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"}),passwordReset:(e,o)=>t(`/users/${encodeURIComponent(e)}/password-reset`,{method:"POST",body:JSON.stringify({via:o})}),cancelPasswordReset:e=>t(`/users/${encodeURIComponent(e)}/password-reset`,{method:"DELETE"})},tools:{list:()=>t("/tools",{method:"GET"}),update:(e,o)=>t(`/tools/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)})},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(`${c}/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")||"",d=/filename="([^"]+)"/.exec(i)?.[1],m=await s.blob(),h=URL.createObjectURL(m),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(`${c}/plugins/install-upload`,{method:"POST",headers:i?{Authorization:`Bearer ${i}`}:{},body:s}),m=await d.json().catch(()=>({}));if(d.status===409){const h=new Error(m.error||"Plugin already installed");throw h.code="CONFLICT",h.name=m.name,h.installedVersion=m.installedVersion,h.incomingVersion=m.incomingVersion,h}if(!d.ok)throw new Error(m.error||`Install failed (${d.status})`);return m}},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(`${c}/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(`${c}/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(`${c}/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(`${c}/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)}let u=!1,g=!1;export function sendToLogin({expired:e=!1}={}){/^#\/(login|reset-password)(\?|\/|$)/.test(location.hash)||(location.hash=$(location.hash,{expired:e}))}export function logout(){!g&&window.M?.subscribe&&(g=!0,M.subscribe("router:afterChange",({to:e}={})=>{/^\/login(\?|$)/.test(e?.path||"")||(u=!1)})),u=!0,R.navigate("/login")}export function takePendingLogout(){return u?(u=!1,b(),!0):!1}function b(){const e=p();e&&fetch(`${c}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})}).catch(()=>{}),f()}export{t as apiRequest,T as refreshAccessToken,a as getToken,f as clearAuth};
|
|
1
|
+
import{loginHashFor as $}from"./lib/login-arrange.js";const c="/api";function a(){return S.get("auth_token")}function u(){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=u();if(!e)throw new Error("No refresh token");const o=await fetch(`${c}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(!o.ok)throw f(),sendToLogin({expired:!0}),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(`${c}${e}`,{...o,headers:r(n)});if(s.status===401&&u())try{n=await T(),s=await fetch(`${c}${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.body=i,d}return s.status===204?null:s.json()}async function O(e,o){const n=a(),r=n?{Authorization:`Bearer ${n}`}:{},s=await fetch(`${c}${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)}),verifyLogin:e=>t("/auth/login/verify",{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})}),checkReset:(e,o)=>t("/auth/reset-password/check",{method:"POST",body:JSON.stringify({token:e,...o&&{password:o}})}),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"}),removeContents:(e,o)=>t(`/projects/${encodeURIComponent(e)}/remove-contents`,{method:"POST",body:JSON.stringify({confirm:o})})},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=>O("/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"}),passwordReset:(e,o)=>t(`/users/${encodeURIComponent(e)}/password-reset`,{method:"POST",body:JSON.stringify({via:o})}),cancelPasswordReset:e=>t(`/users/${encodeURIComponent(e)}/password-reset`,{method:"DELETE"})},tools:{list:()=>t("/tools",{method:"GET"}),update:(e,o)=>t(`/tools/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)})},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(`${c}/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")||"",d=/filename="([^"]+)"/.exec(i)?.[1],m=await s.blob(),h=URL.createObjectURL(m),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(`${c}/plugins/install-upload`,{method:"POST",headers:i?{Authorization:`Bearer ${i}`}:{},body:s}),m=await d.json().catch(()=>({}));if(d.status===409){const h=new Error(m.error||"Plugin already installed");throw h.code="CONFLICT",h.name=m.name,h.installedVersion=m.installedVersion,h.incomingVersion=m.incomingVersion,h}if(!d.ok)throw new Error(m.error||`Install failed (${d.status})`);return m}},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(`${c}/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(`${c}/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(`${c}/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(`${c}/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)}let p=!1,g=!1;export function sendToLogin({expired:e=!1}={}){/^#\/(login|reset-password)(\?|\/|$)/.test(location.hash)||(location.hash=$(location.hash,{expired:e}))}export function logout(){!g&&window.M?.subscribe&&(g=!0,M.subscribe("router:afterChange",({to:e}={})=>{/^\/login(\?|$)/.test(e?.path||"")||(p=!1)})),p=!0,R.navigate("/login")}export function takePendingLogout(){return p?(p=!1,b(),!0):!1}function b(){const e=u();e&&fetch(`${c}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})}).catch(()=>{}),f()}export{t as apiRequest,T as refreshAccessToken,a as getToken,f as clearAuth};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as u}from"../api.js";const c=(e,r,n=`${r}s`)=>`${e} ${e===1?r:n}`,a=e=>String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");export function contentsText(e={}){const n=Object.entries({pages:["page"],collections:["collection"],forms:["form"],blocks:["block"],views:["view"],components:["component"],actions:["action"],menus:["menu"],apis:["API endpoint"]}).filter(([o])=>e[o]>0).map(([o,[s]])=>c(e[o],s));return n.length?n.length===1?n[0]:`${n.slice(0,-1).join(", ")} and ${n.at(-1)}`:"nothing"}export async function removeProjectAndContents({slug:e,name:r,sample:n=!1}){let o;try{o=await u.projects.artefacts(e)}catch(t){return E.toast(`Could not read the project: ${t.message||t}`,{type:"error"}),null}const s=Object.fromEntries(Object.entries(o||{}).map(([t,l])=>[t,Array.isArray(l)?l.length:0]));if((s.users||0)+(s.roles||0))return E.toast(`${c(s.users||0,"user")} and ${c(s.roles||0,"role")} are filed under "${r}". Move them to another project first - people are never deleted along with content.`,{type:"warning",duration:8e3}),null;const p=contentsText(s),i=(o?.pages||[]).map(t=>t.urlPath||t.url).filter(Boolean),m=i.slice(0,8).map(a).join(", ")+(i.length>8?` and ${i.length-8} more`:""),h=n?`Remove the sample content? This deletes the "${a(r)}" project and everything filed under it - ${a(p)}.`:`Delete "${a(r)}" and everything filed under it - ${a(p)}?`,$=i.length?`<br><br>Pages: ${m}. Links to them leave your menus; pages not filed under this project are not touched.`:"",d=await E.prompt(`${h}${$}<br><br>This cannot be undone. Type <strong>${a(e)}</strong> to confirm.`,{title:n?"Remove sample content":"Delete project and contents",inputPlaceholder:e,confirmText:n?"Remove":"Delete"});if(d==null)return null;if(String(d).trim()!==e)return E.toast(`Nothing removed - that was not "${e}".`,{type:"info"}),null;try{const t=await u.projects.removeContents(e,e),l=Object.values(t?.removed||{}).reduce((g,f)=>g+(Number(f)||0),0);E.toast(`${n?"Sample content removed":`"${r}" deleted`}: ${c(l,"item")}${t?.links?`, ${c(t.links,"menu link")}`:""}.`,{type:"success",duration:6e3});try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}return t}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error",duration:1e4});try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}return null}}
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
<span>This project is <strong>disabled</strong>: its public pages 404 and its external API does not answer. Editing it here carries on.</span>
|
|
8
8
|
<button type="button" class="btn btn-sm btn-primary" data-on-click="enable" data-bind-disabled="busy.value">Enable</button>
|
|
9
9
|
</p>
|
|
10
|
+
<p class="pdx-alert is-sample" data-bind-hidden="!isSample.value">
|
|
11
|
+
<span data-icon="star" data-icon-size="16"></span>
|
|
12
|
+
<span>This is <strong>sample content</strong>, added when your site was made, to show what it can do. Keep it, learn from it - or remove it all in one go (it shows exactly what goes before anything does).</span>
|
|
13
|
+
<button type="button" class="btn btn-sm btn-danger" data-on-click="removeSample" data-bind-disabled="busy.value">
|
|
14
|
+
<span data-icon="trash" data-icon-size="13"></span> Remove sample content</button>
|
|
15
|
+
</p>
|
|
10
16
|
<p class="pdx-alert is-bad" data-bind-hidden="!loadError.value">
|
|
11
17
|
<span data-bind-text="loadError.value"></span>
|
|
12
18
|
<button type="button" class="btn btn-sm btn-ghost" data-on-click="retry">Try again</button>
|
|
@@ -49,6 +55,8 @@
|
|
|
49
55
|
.pdx-alert { margin: 0; display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; padding: .6rem .8rem; border-radius: var(--dm-radius-md);
|
|
50
56
|
background: color-mix(in srgb, var(--dm-warning, var(--dm-primary)) 16%, transparent); color: var(--dm-text); }
|
|
51
57
|
.pdx-alert.is-bad { background: color-mix(in srgb, var(--dm-danger) 14%, transparent); }
|
|
58
|
+
.pdx-alert.is-sample { background: color-mix(in srgb, var(--dm-info) 14%, transparent); }
|
|
59
|
+
.pdx-alert.is-sample > span:nth-child(2) { flex: 1; min-width: 14rem; }
|
|
52
60
|
.pdx-alert > span:not([data-icon]) { flex: 1; min-width: 12rem; }
|
|
53
61
|
.pdx-hero { display: flex; gap: 1.1rem; align-items: flex-start; padding: 1.1rem 1.25rem; border: 1px solid var(--dm-border);
|
|
54
62
|
border-left: 4px solid var(--dm-primary); border-radius: var(--dm-radius-md);
|
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
<button type="button" class="btn btn-sm btn-warning" data-on-click="untagAll" data-bind-disabled="noneFiled.value || busy.value">
|
|
66
66
|
<span data-icon="tag" data-icon-size="13"></span> Untag all…</button>
|
|
67
67
|
</div>
|
|
68
|
+
<div class="psx-danger-row" data-bind-hidden="!canDelete">
|
|
69
|
+
<span><strong>Delete the project and everything in it</strong><small>Its pages, collections and their data, forms, blocks, menus and API endpoints are deleted, and links to its pages leave your menus. Users and roles are never deleted. Asks you to type the slug.</small></span>
|
|
70
|
+
<button type="button" class="btn btn-sm btn-danger" data-on-click="removeAll" data-bind-disabled="noneFiled.value || busy.value">
|
|
71
|
+
<span data-icon="trash-2" data-icon-size="13"></span> Delete with contents…</button>
|
|
72
|
+
</div>
|
|
68
73
|
<div class="psx-danger-row" data-bind-hidden="!canDelete">
|
|
69
74
|
<span><strong>Delete this project</strong><small data-bind-text="deleteHint.value"></small></span>
|
|
70
75
|
<button type="button" class="btn btn-sm btn-danger" data-on-click="remove" data-bind-disabled="deleteBlocked.value">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as h}from"../api.js";import{openQuickCreate as K,INLINE_TYPES as V}from"../lib/project-quick-create.js";import{addBannerAction as x}from"../lib/plugin-chrome.js";import{helpPopovers as Q}from"../lib/help-popover.js";import{openMenuAt as U,viewLifetime as Y}from"../lib/tool-kit.js";import{countsOf as N,projectFromPath as P,relTime as _,tilesFor as G}from"../lib/project-page-arrange.js";import{fmtDate as J,fmtDateTime as W}from"../lib/dates.js";import{removeProjectAndContents as X}from"../lib/project-remove.js";const q=/^#\/projects\/(?!new(?:[/?#]|$))[^/?#]+\/?(\?.*)?$/,l=Y(q),Z=`
|
|
2
2
|
<div class="pdx-tile" data-key="{{key}}" tabindex="0" data-bind-class="cls" style="--c: {{tone}}">
|
|
3
3
|
<span class="pdx-tile-chip" aria-hidden="true"><span data-icon="{{icon}}" data-icon-size="20"></span></span>
|
|
4
4
|
<span class="pdx-tile-body"><strong data-bind-text="countText"></strong><small>{{label}}</small></span>
|
|
@@ -6,4 +6,4 @@ import{api as b}from"../api.js";import{openQuickCreate as z,INLINE_TYPES as K}fr
|
|
|
6
6
|
<button type="button" class="pdx-add" title="New {{singular}}" aria-label="New {{singular}}">+</button>
|
|
7
7
|
<button type="button" class="pdx-kebab" title="Actions" aria-label="Actions for {{label}}">\u22EE</button>
|
|
8
8
|
</span>
|
|
9
|
-
</div>`;export function setBannerSection(s,
|
|
9
|
+
</div>`;export function setBannerSection(s,n){const a=s.querySelector(".dm-plugin-banner-name")?.querySelector(".dm-plugin-banner-sep");if(!a)return;const d=a.nextSibling;d&&d.nodeType===3?d.nodeValue=n:a.after(document.createTextNode(n))}export const projectDetailView={templateUrl:"/admin/js/templates/project-detail.html",async onMount(s){l.reset();const n=P(location.hash);if(!n){location.hash="#/projects";return}const r=s.get(0).querySelector(".pdx")||s.get(0),a=()=>q.test(location.hash)&&r.isConnected&&P(location.hash)===n,d=encodeURIComponent(n),p=M.observable(null),y=M.observable({}),v=M.observable(!0),g=M.observable(""),c=M.observable(!1),u=M.observable([]),w=M.observable("");l.add(M.effect(()=>{const e=G(y.value,{slug:n,inline:V});u.value=e.tiles,w.value=v.value?"":e.totalText}));const o=()=>p.value||{},$={loading:v,loadError:g,busy:c,tiles:u,totalText:w,name:M.computed(()=>o().name||n),description:M.computed(()=>o().description||""),off:M.computed(()=>p.value&&o().enabled===!1),isSample:M.computed(()=>p.value&&o().sample===!0&&o().protected!==!0),builtIn:M.computed(()=>o().protected===!0),rootUrl:M.computed(()=>o().rootUrl||""),rootText:M.computed(()=>o().rootUrl?`Pages under ${o().rootUrl}`:"No root URL - pages are filed by hand"),created:M.computed(()=>o().createdAt?J(o().createdAt):""),creator:M.computed(()=>o().createdBy||""),updated:M.computed(()=>_(o().updatedAt)),updatedTitle:M.computed(()=>o().updatedAt?W(o().updatedAt):"")},C=()=>{try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}};async function m(){try{const[e,t]=await Promise.all([h.projects.get(n),h.projects.artefacts(n).catch(()=>({}))]);if(!a())return;p.value=e,y.value=N(t),g.value="",setBannerSection(s.get(0),e?.name||n);const b=r.querySelector(".pdx-hero-icon");if(b){b.replaceChildren();const k=document.createElement("span");k.setAttribute("data-icon",e?.icon||"folder"),k.setAttribute("data-icon-size","32"),b.appendChild(k),I.scan(b)}}catch(e){if(!a())return;if(/not found|404/i.test(String(e?.message||e))){E.toast("Project not found.",{type:"error"}),location.hash="#/projects";return}g.value=`Could not load the project: ${e.message||e}`}finally{v.value=!1}}async function B(){const e=await h.projects.artefacts(n).catch(()=>null);!a()||!e||(y.value=N(e),C())}async function D(){if(!c.peek()){c.value=!0;try{await X({slug:n,name:o().name||n,sample:!0})&&(location.hash="#/projects")}finally{c.value=!1}}}async function F(){if(!c.peek()){c.value=!0;try{await h.projects.update(n,{enabled:!0}),E.toast(`"${o().name||n}" enabled - its public pages and API are back.`,{type:"success"}),C(),a()&&await m()}catch(e){E.toast(`Not enabled: ${e.message||e}`,{type:"error"})}finally{c.value=!1}}}const j=e=>u.peek().find(t=>t.key===e),O=()=>({slug:n,name:o().name||n,rootUrl:o().rootUrl||""});function f(e,{inEditor:t=!1}={}){if(e){if(e.inline&&!t){K({type:e.key,project:O(),onCreated:B});return}location.hash=e.newHash}}const A=e=>{e?.has&&(location.hash=e.listHash)},T=e=>{const t=j(e);t&&(t.has?A(t):f(t))};function R(e){const t=j(e);return t?[{header:`${t.label} \xB7 ${t.count}`},{label:"Open the list",icon:"list",disabled:!t.has,action:()=>A(t)},{label:`New ${t.singular}\u2026`,icon:"plus",action:()=>f(t)},...t.inline?[{label:`New ${t.singular} in its editor`,icon:"edit",action:()=>f(t,{inEditor:!0})}]:[]]:[]}const i=r.querySelector(".pdx-tiles");i.innerHTML=Z;const H=M.applyBindings($,r,{methods:{enable:F,removeSample:D,retry:()=>m()}});l.add(()=>H.dispose()),l.add(Q(r,{position:"bottom"})),l.add(M.effect(()=>{u.value,requestAnimationFrame(()=>{i.isConnected&&I.scan(i)})}));const z=E.contextMenu(i,{cursor:"glyph",match:".pdx-tile",inherit:!1,density:"compact",items:e=>R(e.dataset.key)});l.add(()=>z?.destroy?.());const S=e=>{const t=e.target.closest(".pdx-tile");if(t){if(e.target.closest(".pdx-kebab")){U(e.target.closest(".pdx-kebab"));return}if(e.target.closest(".pdx-add")){f(j(t.dataset.key));return}T(t.dataset.key)}},L=e=>{const t=e.target.closest(".pdx-tile");t&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),U(t)):e.key==="Enter"&&e.target===t&&T(t.dataset.key))};i.addEventListener("click",S),i.addEventListener("keydown",L),l.add(()=>{i.removeEventListener("click",S),i.removeEventListener("keydown",L)}),x(s,{id:"project-overview-settings",icon:"settings",title:"Project settings",onClick:()=>{location.hash=`#/projects/${d}/settings`}}),x(s,{id:"project-overview-refresh",icon:"refresh-cw",title:"Refresh",onClick:()=>m()}),x(s,{id:"project-overview-all",icon:"folder",title:"All projects",onClick:()=>{location.hash="#/projects"}}),I.scan(r),await m(),a()&&I.scan(r)}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{removeProjectAndContents as me}from"../lib/project-remove.js";import{api as h}from"../api.js";import{makeIconInput as he}from"../lib/shortcode-modal.js";import{addBannerAction as B}from"../lib/plugin-chrome.js";import{helpPopovers as ve}from"../lib/help-popover.js";import{sidebarCount as ge}from"../lib/tool-kit.js";import{countsOf as oe,draftKey as $,projectFromPath as ye,settingsForm as K,settingsPayload as be,settingsRules as ke,settingsSnapshot as re,untagSummary as je}from"../lib/project-page-arrange.js";import{setBannerSection as ne}from"./project-detail.js";import{fmtDateTime as we}from"../lib/dates.js";const N=r=>String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""),O=r=>String(r||"").replace(/^#/,"").split("?")[0].replace(/\/+$/,"");let c=null,ae=!1;function Ee(){ae||!window.R?.use||(ae=!0,R.use((r,t,v)=>!c||O(t?.path)!==c.path||O(r?.path)===c.path||!c.dirty()?v():E.confirm("You have unsaved changes to this project. Leave anyway? They are kept as a draft for your next visit.").then(P=>{P?v():history.replaceState(null,"",`#${t.path}`)})),M.subscribe("router:afterChange",({to:r}={})=>{if(c&&O(r?.path)!==c.path){const t=c;c=null,t.dispose()}}))}export const projectSettingsView={templateUrl:"/admin/js/templates/project-settings.html",async onMount(r){if(Ee(),c){const e=c;c=null,e.dispose()}const t=ye(location.hash,{settings:!0});if(!t){location.hash="#/projects";return}const v=encodeURIComponent(t),P=`/projects/${v}/settings`,Y=[],g=e=>(Y.push(e),e);let V=!1;const p=r.get(0).querySelector(".psx")||r.get(0),x=()=>!V&&p.isConnected,H=E.loader(r.get(0),{type:"dots"});let y,W={};try{[y,W]=await Promise.all([h.projects.get(t),h.projects.artefacts(t).then(oe).catch(()=>({}))])}catch(e){H.destroy(),E.toast(`Could not load the project: ${e.message||e}`,{type:"error"}),location.hash="#/projects";return}if(H.destroy(),!p.isConnected)return;const n=M.observable(y),d=ke(y),z=K(y),a=Object.fromEntries(Object.entries(z).map(([e,o])=>[e,M.observable(o)])),G=()=>Object.fromEntries(Object.keys(a).map(e=>[e,a[e].value])),D=()=>Object.fromEntries(Object.keys(a).map(e=>[e,a[e].peek()])),f=M.observable(z),L=M.computed(()=>re(f.value)),j=M.computed(()=>re(G())!==L.value),T=M.computed(()=>be(G(),n.value)),J=e=>M.computed(()=>T.value.errors[e]||""),b=M.observable(!1),i=M.observable(!1),w=M.observable(""),A=M.observable(W),k=M.computed(()=>je(A.value)),se={...a,saving:b,busy:i,dirty:j,errorText:w,slug:t,rootLocked:d.rootLocked,canToggle:d.canToggle,canDelete:d.canDelete,canUntag:d.canUntag,dangerShown:d.canDelete||d.canUntag,nameErr:J("name"),rootErr:J("rootUrl"),cannot:M.computed(()=>b.value||Object.keys(T.value.errors).length>0),filedText:M.computed(()=>{const e=k.value;if(!e.total)return"Nothing is filed under this project.";const o=n.value.rootUrl&&(A.value.pages||0)>0?` Pages under ${n.value.rootUrl} belong here by their address - untagging does not move them; clear the root URL (and save) for that.`:"";return`${e.total} filed here: ${e.text}.${o}`}),noneFiled:M.computed(()=>k.value.total===0),deleteBlocked:M.computed(()=>k.value.total>0||i.value),deleteHint:M.computed(()=>k.value.total?"Untag what is filed here first - a project can only be deleted once nothing is filed under it.":"Deleting removes the project record. It cannot be undone.")},U=()=>{try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}ge("#/projects")},C=()=>{try{S.remove?S.remove($(t)):S.set($(t),null)}catch{}};async function F(){if(b.peek())return;const{errors:e,payload:o}=T.peek();if(Object.keys(e).length){E.toast("Fix the marked fields first.",{type:"warning"});return}b.value=!0,w.value="";try{const s=await h.projects.update(t,o);if(!x())return;n.value=s||{...n.peek(),...o},f.value=K(n.peek());for(const[m,q]of Object.entries(f.peek()))a[m].value=q;C(),ne(r.get(0),n.peek().name||t),U(),E.toast("Project saved.",{type:"success"})}catch(s){w.value=s.message||"Could not save the project.",E.toast("Not saved - see the message at the top.",{type:"error"})}finally{b.value=!1}}async function ce(){await E.confirm("Throw away your unsaved changes to this project?")&&(Q(f.peek()),w.value="")}function Q(e){for(const[o,s]of Object.entries(e))a[o]&&(a[o].value=s);l&&l.input.value!==String(a.icon.peek())&&(l.input.value=String(a.icon.peek()))}async function X(){const e=await h.projects.artefacts(t).then(oe).catch(()=>null);x()&&e&&(A.value=e)}async function ie(){if(i.peek())return;const e=k.peek();if(!e.total)return;const o=N(n.peek().name||t);if(await E.confirm(`Untag everything filed under "${o}" (${N(e.text)})? It all stays where it is and belongs to Core from then on. The project itself is kept.`)){i.value=!0;try{const s=await h.projects.untagAll(t),m=Object.values(s?.untagged||{}).reduce((q,fe)=>q+(Number(fe)||0),0);E.toast(`Untagged ${m} artefact${m===1?"":"s"}.${n.peek().rootUrl?` Pages under ${n.peek().rootUrl} stay - they belong here by their address.`:""}`,{type:"success",duration:6e3}),U(),await X()}catch(s){E.toast(`Untag failed: ${s.message||s}`,{type:"error"})}finally{i.value=!1}}}async function le(){if(!i.peek()){i.value=!0;try{await me({slug:t,name:n.peek().name||t,sample:n.peek().sample===!0})&&(C(),f.value=D(),U(),location.hash="#/projects")}finally{i.value=!1}}}async function ue(){if(i.peek())return;const e=N(n.peek().name||t);if(await E.confirm(`Delete the project "${e}"? This cannot be undone.`)){i.value=!0;try{await h.projects.remove(t),C(),f.value=D(),E.toast(`Deleted "${n.peek().name||t}".`,{type:"success"}),U(),location.hash="#/projects"}catch(o){E.toast(`Not deleted: ${o.message||o}`,{type:"error",duration:7e3}),await X()}finally{i.value=!1}}}const pe=p.querySelector(".psx-icon-mount"),l=he("folder, users, box\u2026",a.icon.peek());l.input.id="psx-icon",l.input.classList.add("form-input");const Z=()=>{a.icon.value=l.input.value};l.input.addEventListener("input",Z),g(()=>l.input.removeEventListener("input",Z)),pe.appendChild(l.el),p.querySelector("#psx-slug").value=t;const de=M.applyBindings(se,p,{methods:{save:F,discard:ce,untagAll:ie,remove:ue,removeAll:le}});g(()=>de.dispose()),g(ve(p,{position:"bottom"})),ne(r.get(0),y.name||t),B(r,{id:"project-settings-overview",icon:"eye",title:"Project overview",onClick:()=>{location.hash=`#/projects/${v}`}}),B(r,{id:"project-settings-all",icon:"folder",title:"All projects",onClick:()=>{location.hash="#/projects"}}),B(r,{id:"project-settings-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>F()});const _=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),F())};document.addEventListener("keydown",_,!0),g(()=>document.removeEventListener("keydown",_,!0));const ee=()=>{if(j.peek())try{S.set($(t),{form:D(),base:L.peek(),at:new Date().toISOString()})}catch{}},te=e=>{j.peek()&&(ee(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",te),g(()=>window.removeEventListener("beforeunload",te)),c={path:O(P),dirty:()=>j.peek(),dispose(){V=!0,ee(),Y.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(p);let u=null;try{u=S.get($(t))}catch{}if(u?.form){C();const e=u.base&&u.base!==L.peek(),o=u.at?` (${we(u.at)})`:"",s=e?`You left unsaved changes to this project${o}, but it has been changed since. Put your changes back over the current settings?`:`You left unsaved changes to this project${o}. Pick up where you left off?`;if(await E.confirm(s)&&x()){const m=K({...n.peek(),...u.form,enabled:u.form.enabled!==!1});Q(m)}}}};
|