domma-cms 0.41.1 → 0.43.1
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 +38 -2
- package/admin/js/templates/collection-editor.html +1 -1
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/block-editor.js +2 -2
- package/admin/js/views/collection-editor.js +4 -4
- package/admin/js/views/form-editor.js +6 -6
- package/admin/js/views/navigation.js +16 -16
- package/admin/js/views/page-editor.js +41 -41
- package/admin/js/views/view-editor.js +1 -1
- package/package.json +1 -1
- package/public/css/site.css +1 -1
- package/public/js/collection-context.js +2 -2
- package/public/js/collection-export.mjs +3 -0
- package/public/js/collection-query.mjs +1 -0
- package/public/js/collection-sort.mjs +1 -0
- package/public/js/site.js +1 -1
- package/scripts/setup.js +23 -2
- package/server/services/collections.js +12 -11
- package/server/services/markdown.js +116 -79
- package/server/services/menuRender.js +90 -0
- package/server/services/menus.js +46 -0
- package/server/services/renderer.js +33 -5
- package/server/templates/page.html +1 -1
package/CLAUDE.md
CHANGED
|
@@ -112,8 +112,9 @@ Collection entry I/O is delegated to a storage adapter. Schema management is alw
|
|
|
112
112
|
Roles are **dynamic** — stored as entries in `content/collections/user-types/` (a preset collection).
|
|
113
113
|
Seeded on first run with: `admin`, `manager`, `editor`, `subscriber`.
|
|
114
114
|
|
|
115
|
-
- `server/services/
|
|
116
|
-
|
|
115
|
+
- `server/services/roles.js` — `seed()`, `load()`, `invalidate()`, `getRoleLevel(role)`,
|
|
116
|
+
`getPermissionsFor(resource)`, `getRoleHierarchy()` (returns role names sorted most-privileged
|
|
117
|
+
first, level 0 = admin, so the least-privileged role is `.at(-1)`)
|
|
117
118
|
- `server/services/permissionRegistry.js` — central permission map; route guards read the cache at request time
|
|
118
119
|
- `server/services/rowAccess.js` — row-level access control for collections
|
|
119
120
|
- Route guards use `requirePermission('resource')`, **not** hardcoded role names
|
|
@@ -147,6 +148,8 @@ See `docs/configuration.md` for full schema reference.
|
|
|
147
148
|
|
|
148
149
|
Menus live in `config/menus/<slug>.json`. The slot → menu map lives in `config/menu-locations.json`. Built-in slots: `navbar`, `footer-primary`, `footer-legal`, `admin-sidebar`. Plugins register additional slots via `hooks.registerMenuLocation()`. The public navbar's brand is on `site.json.brand` (parallel to `adminBrand`); items + variant + position come from the menu mapped to `navbar`. The `[menu]` shortcode renders any menu inline (`[menu slug="…" /]` or `[menu location="…" /]`). On first boot the CMS auto-migrates the legacy `config/navigation.json` and `site.json.footer.links` into this system; the originals are renamed `.bak` and a one-release read-fallback restores the navbar from `navigation.json.bak` if the navbar slot is unmapped. See `docs/configuration.md` for full schema.
|
|
149
150
|
|
|
151
|
+
**Overlay slot (place a menu anywhere).** `overlay` is a core slot that is *not* one-menu-wins: `resolveOverlaysForPage(ctx, user)` returns **every** menu bound to it that matches the page, plus the menu mapped to it in `menu-locations.json` (site-wide, deduped). The renderer injects them at the end of `<body>` via `buildOverlayMenus()`, so a `position: floating` overlay pins anywhere on the viewport with no navbar/footer/shortcode involved. Overlays with zero visible items after per-user filtering are dropped. Menu→HTML lives in `server/services/menuRender.js` (`buildMenuNav`/`renderMenuItemsAsUl`) and is shared with the `[menu]` shortcode, so both render identically — change markup there, not in markdown.js.
|
|
152
|
+
|
|
150
153
|
**Bindings.** A menu can carry `binding: {slot, projects: [], pages: []}` and take that slot over on matching pages instead of being mapped globally. `resolveLocation(slot, user, {urlPath, project})` checks bindings first (exact page > `/glob/*` > project, longest pattern wins) and falls back to `menu-locations.json`; **callers that know the page must pass the ctx** or bindings silently never fire — `renderer.js` (both render paths) and `parseMarkdown({urlPath})` do. All menu files are cached in-process via `getAllMenus()`; anything writing a menu file outside `menus.js` must call `invalidateMenuIndex()` (the two boot migrations do).
|
|
151
154
|
|
|
152
155
|
**Orientation.** `orientation: horizontal|vertical` + `side: left|right`. Vertical navbar = CSS rail over Domma's normal navbar markup (`#site-navbar.dm-nav-vertical` + server-stamped `body.dm-vnav-*` gutter, ≥993px only; below that the mobile drawer already applies). The `[menu]` shortcode emits `dm-menu--vertical|horizontal` **only when set** — no class means the historic plain-list rendering.
|
|
@@ -157,6 +160,39 @@ Menus live in `config/menus/<slug>.json`. The slot → menu map lives in `config
|
|
|
157
160
|
|
|
158
161
|
The admin sidebar is now menu-data-driven — no more hardcoded `sidebar-config.js`. The menu lives at `config/menus/admin-sidebar.json` and is auto-seeded on first boot (idempotent, file-existence guarded) with the standard tree: Overview, Projects, Content, Data, System, Documentation. Renderer is at `admin/js/lib/sidebar-renderer.js`; it reads the menu, merges in plugin-registered items (via `hooks.registerSidebarItem({parent, item})`), filters by each item's optional `permission` field, then mounts. The **Menus** admin can edit the sidebar visually like any other menu — the item editor exposes the new `permission` field on each row (resource key from `permissionRegistry`). Plugin-injected items are render-time only and not shown in the Menus editor; admins hide them by adding a same-`text` item with `hidden: true` to the persisted menu.
|
|
159
162
|
|
|
163
|
+
## Collection displays & public filtering
|
|
164
|
+
|
|
165
|
+
`[collection]` and `[view]` render in any of table, cards, list, block, accordion, timeline,
|
|
166
|
+
carousel or listgroup (`display="…"`). Rendering is server-side in `markdown.js`; the browser
|
|
167
|
+
modules below add behaviour on top.
|
|
168
|
+
|
|
169
|
+
**Right-click menu** (`public/js/collection-context.js`, loaded as `type="module"` from
|
|
170
|
+
`server/templates/page.html`) works on the public site, not just the admin. Every
|
|
171
|
+
collection-rendered display carries a `data-ctx` payload (tables instead hydrate via `site.js`);
|
|
172
|
+
right-clicking inside one opens a menu of filters derived from the clicked entry's fields, plus
|
|
173
|
+
free-text search, sort, group-by, export, copy (CSV/JSON/Markdown to clipboard), print and
|
|
174
|
+
"copy link to this view". Active filters appear as removable chips above the display. Sort,
|
|
175
|
+
filters and grouping all persist in the URL hash (`dmf=`), so a filtered/sorted/grouped view can
|
|
176
|
+
be bookmarked or shared. Each display type filters differently — timeline via the Progression
|
|
177
|
+
instance's `.setItems()`, carousel by rebuilding the track from pristine slide copies and
|
|
178
|
+
re-initialising, table by re-creating the Domma table with the row subset, everything else by
|
|
179
|
+
toggling a hidden class on `[data-entry-id]`. Grouping needs entries rendered as direct DOM
|
|
180
|
+
siblings, so it's only offered on the five toggle-based displays (cards, list, listgroup,
|
|
181
|
+
accordion, block) — table/timeline/carousel manage their own rendering and hide the "Group by…"
|
|
182
|
+
row. See `docs/markdown-shortcodes.md` for the full capability table per display type.
|
|
183
|
+
|
|
184
|
+
**Export permission** lives in the collection schema's `export` block (`enabled`, `access`,
|
|
185
|
+
`formats`) — see `docs/configuration.md`. It is a convenience gate, not an access boundary: entry
|
|
186
|
+
data is already in the page's `data-ctx` attribute, so it never stops a determined reader of page
|
|
187
|
+
source, only the download button shown to everyone else.
|
|
188
|
+
|
|
189
|
+
**Shared modules**: `public/js/collection-export.mjs` (CSV/JSON/Markdown serialisation dialect —
|
|
190
|
+
CSV formula-injection sanitising, BOM handling) is imported by BOTH the browser
|
|
191
|
+
(`collection-context.js`) and the server (`server/services/collections.js`), so admin exports and
|
|
192
|
+
public right-click exports are byte-identical for the same values. `public/js/collection-sort.mjs`
|
|
193
|
+
holds `compareValues`/`sortEntries` — the same comparison ladder backs both the sort menu and the
|
|
194
|
+
`gt`/`lt` right-click filters.
|
|
195
|
+
|
|
160
196
|
## Key Gotchas
|
|
161
197
|
|
|
162
198
|
1. **Menu item sub-items**: use `items` key, NOT `children` — Domma navbar and the `[menu]` shortcode both read `items`.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<div class="tab-list">
|
|
15
15
|
<button class="tab-item active">Settings</button>
|
|
16
16
|
<button class="tab-item">Fields</button>
|
|
17
|
-
<button class="tab-item">API
|
|
17
|
+
<button class="tab-item">API & Export</button>
|
|
18
18
|
<button class="tab-item" id="storage-tab-btn" style="display:none;">Storage</button>
|
|
19
19
|
</div>
|
|
20
20
|
<div class="tab-content">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as _}from"../api.js";let N=null,O={};async function D(e){const t=e.find("#action-project").get(0);if(t)try{(await _.projects.list()).forEach(a=>{const n=document.createElement("option");n.value=a.slug,n.textContent=a.name||a.slug,t.appendChild(n)})}catch{}}const $={deleteEntry:[],moveToCollection:[{name:"targetCollection",label:"Target Collection Slug",placeholder:"e.g. archived-applications"}],webhook:[{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:[{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}]};export const actionEditorView={templateUrl:"/admin/js/templates/action-editor.html",async onMount(e){N=null,O={};const t=window.location.hash.match(/\/actions\/edit\/([^/?#]+)/);t&&(N=t[1]),E.tabs(e.find("#action-editor-tabs").get(0)),await I(e),await P(e),await D(e),N&&(e.find("#action-editor-title").text("Edit Action"),await B(e,N)),e.find("#add-step-btn").off("click").on("click",()=>{const l=e.find("#add-step-type").val()||"updateField";J(e,{type:l,config:{}})}),e.find("#save-action-btn").off("click").on("click",async()=>{await Y(e)}),U(e),Domma.icons.scan()}};async function I(e){const t=e.find("#action-collection").get(0);if(t)try{(await _.collections.list()).forEach(l=>{const a=document.createElement("option");a.value=l.slug,a.textContent=`${l.title} (${l.slug})`,t.appendChild(a)})}catch{}}async function P(e){const t=e.find("#action-roles-checkboxes").get(0);t&&["admin","manager","editor","subscriber"].forEach(l=>{const a=document.createElement("label");a.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const n=document.createElement("input");n.type="checkbox",n.value=l,n.dataset.role=l,n.className="action-role-cb",n.checked=l==="admin",a.appendChild(n),a.appendChild(document.createTextNode(l)),t.appendChild(a)})}async function B(e,t){try{const l=await _.actions.get(t);if(!l){E.toast("Action not found.",{type:"error"}),R.navigate("/actions");return}K(e,l)}catch(l){E.toast(l.message||"Failed to load action.",{type:"error"}),R.navigate("/actions")}}function U(e){const t=e.find("#action-rowlevel-enabled").get(0),l=e.find("#action-rowlevel-config").get(0),a=e.find("#action-rowlevel-mode").get(0),n=e.find("#action-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{l&&(l.style.display=t.checked?"flex":"none")}),a&&a.addEventListener("change",()=>{n&&(n.style.display=a.value==="field"?"":"none")}))}function K(e,t){e.find("#action-title").val(t.title||""),e.find("#action-slug").val(t.slug||""),e.find("#action-description").val(t.description||""),e.find("#action-collection").val(t.collection||""),O=t.meta||{},e.find("#action-project").val(t.meta?.project||""),e.find("#action-bundled").prop("checked",!!t.bundled),e.find("#action-trigger-type").val(t.trigger?.type||"manual"),e.find("#action-trigger-label").val(t.trigger?.label||"Run"),e.find("#action-trigger-icon").val(t.trigger?.icon||"zap"),e.find("#action-trigger-confirm").val(t.trigger?.confirmMessage||"");const l=t.access?.roles||["admin"];e.find(".action-role-cb").each(function(){this.checked=l.includes(this.value)});const a=t.access?.rowLevel;a&&(e.find("#action-rowlevel-enabled").prop("checked",!0),e.find("#action-rowlevel-config").css("display","flex"),e.find("#action-rowlevel-mode").val(a.mode||"owner"),e.find("#action-rowlevel-userkey").val(a.userKey||"id"),a.mode==="field"&&(e.find("#action-rowlevel-field-group").css("display",""),e.find("#action-rowlevel-field").val(a.field||"")));const n=e.find("#action-steps-list").get(0);if(n){const c=n.querySelector(".steps-empty-placeholder");for(c&&c.remove();n.firstChild;)n.removeChild(n.firstChild);const i=document.createElement("p");i.className="text-muted steps-empty-placeholder",i.textContent="No steps yet. Add a step to define what this action does.",i.style.cssText="text-align:center;padding:2rem 0;",n.appendChild(i)}(t.steps||[]).forEach(c=>J(e,c))}function J(e,t){const l=e.find("#action-steps-list").get(0);if(!l)return;const a=l.querySelector(".steps-empty-placeholder");a&&a.remove();const n=$[t.type]||[],c=document.createElement("div");c.className="card mb-2 step-card",c.dataset.stepType=t.type;const i=document.createElement("div");i.className="card-header",i.style.cssText="display:flex;align-items:center;gap:.5rem;";const p=document.createElement("code");p.textContent=t.type,p.style.cssText="flex:1;font-size:.85rem;";const r=document.createElement("button");r.type="button",r.className="btn btn-sm btn-ghost";const f=document.createElement("span");f.setAttribute("data-icon","trash"),r.appendChild(f),r.addEventListener("click",()=>{if(c.remove(),!l.querySelector(".step-card")){const o=document.createElement("p");o.className="text-muted steps-empty-placeholder",o.textContent="No steps yet. Add a step to define what this action does.",o.style.cssText="text-align:center;padding:2rem 0;",l.appendChild(o)}}),i.appendChild(p),i.appendChild(r);const h=document.createElement("div");if(h.className="card-body",n.length===0){const o=document.createElement("p");o.className="text-muted",o.textContent=t.type==="deleteEntry"?"This step deletes the entry. No configuration required.":"No additional configuration required.",o.style.margin="0",h.appendChild(o)}t.type==="updateField"?G(h,t,e):n.forEach(o=>{const g=document.createElement("div");g.style.cssText="margin-bottom:.75rem;";const b=document.createElement("label");b.className="form-label",b.textContent=o.label,g.appendChild(b);let m;o.multiline?(m=document.createElement("textarea"),m.rows=3,m.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",m.value=typeof t.config?.[o.name]=="object"?JSON.stringify(t.config[o.name],null,2):t.config?.[o.name]??""):(m=document.createElement("input"),m.type="text",m.value=t.config?.[o.name]??""),m.className=`form-input step-field-${o.name}`,m.placeholder=o.placeholder||"",m.dataset.field=o.name,g.appendChild(m),h.appendChild(g)}),c.appendChild(i),c.appendChild(h),l.appendChild(c),Domma.icons.scan(c)}async function G(e,t,l){const a=l.find("#action-collection").val(),n=document.createElement("div");n.style.cssText="margin-bottom:.75rem;";const c=document.createElement("label");c.className="form-label",c.textContent="Field",n.appendChild(c);const i=document.createElement("select");i.className="form-input step-field-field",i.dataset.field="field";const p=document.createElement("input");p.type="text",p.className="form-input mt-2 step-field-field-custom",p.placeholder="Field name, e.g. status",p.style.display="none";const r=document.createElement("div");r.style.cssText="margin-bottom:.75rem;";const f=document.createElement("label");f.className="form-label",f.textContent="New Value",r.appendChild(f);const h=document.createElement("div");r.appendChild(h);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm mt-2",o.style.cssText="font-size:.75rem;padding:.2rem .5rem;",o.textContent="Template Variables";const g=document.createElement("div");g.style.cssText="display:none;background:var(--dm-surface-raised,#1e1e2e);border-radius:.4rem;padding:.75rem;margin-top:.5rem;font-size:.8rem;line-height:1.7;";const b=document.createElement("strong");b.textContent="Available template variables:",g.appendChild(b);const m=document.createElement("table");m.style.cssText="width:100%;border-collapse:collapse;margin-top:.4rem;",[["{{now}}","Current ISO timestamp"],["{{user.id}}","Executing user's ID"],["{{user.name}}","Executing user's name"],["{{user.email}}","Executing user's email"],["{{entry.id}}","Entry's unique ID"],["{{entry.data.fieldName}}","Any field value from the entry"],["{{env.CMS_PUBLIC_*}}","Public environment variables"]].forEach(([d,u])=>{const x=document.createElement("tr"),w=document.createElement("td");w.style.cssText="padding:.2rem .5rem .2rem 0;opacity:.7;white-space:nowrap;";const T=document.createElement("code");T.textContent=d,w.appendChild(T);const s=document.createElement("td");s.textContent=u,x.appendChild(w),x.appendChild(s),m.appendChild(x)}),g.appendChild(m),o.addEventListener("click",()=>{const d=g.style.display!=="none";g.style.display=d?"none":"",o.textContent=d?"Template Variables":"Hide Variables"}),r.appendChild(o),r.appendChild(g),e.appendChild(n),e.appendChild(r);let S=[];if(a)try{S=(await _.collections.get(a)).fields||[]}catch{}const j=document.createElement("option");j.value="",j.textContent=S.length?"\u2014 select a field \u2014":"\u2014 no fields available \u2014",i.appendChild(j),S.forEach(d=>{const u=document.createElement("option");u.value=d.name,u.textContent=`${d.label} (${d.name})`,u.dataset.fieldType=d.type,u.dataset.fieldOptions=d.type==="select"?JSON.stringify(d.options||[]):"",i.appendChild(u)});const q=document.createElement("option");q.value="__custom__",q.textContent="\u2014 enter manually \u2014",i.appendChild(q);const C=t.config?.field||"",z=C&&[...i.options].find(d=>d.value===C);z?i.value=C:C&&(i.value="__custom__",p.value=C,p.style.display=""),n.appendChild(i),n.appendChild(p);function M(d,u){h.textContent="";const x=d?[...i.options].find(s=>s.value===d):null,w=x?.dataset.fieldType==="select",T=w?JSON.parse(x.dataset.fieldOptions||"[]"):[];if(w&&T.length){const s=document.createElement("select");s.className="form-input step-field-value",s.dataset.field="value";const A=document.createElement("option");A.value="",A.textContent="\u2014 select a value \u2014",s.appendChild(A),T.forEach(y=>{const k=typeof y=="string"?y:y.value??"",V=typeof y=="string"?y:y.label||y.value||k;if(!k||k==="undefined")return;const F=document.createElement("option");F.value=k,F.textContent=V,s.appendChild(F)});const L=document.createElement("option");L.value="__custom__",L.textContent="\u2014 enter manually \u2014",s.appendChild(L);const v=document.createElement("input");v.type="text",v.className="form-input mt-2",v.placeholder="e.g. approved or {{now}}",v.style.display="none",u&&[...s.options].find(y=>y.value===u&&y.value!=="__custom__")?s.value=u:u&&(s.value="__custom__",v.value=u,v.style.display=""),s.addEventListener("change",()=>{const y=s.value==="__custom__";v.style.display=y?"":"none",y||(v.value="")}),h.appendChild(s),h.appendChild(v)}else{const s=document.createElement("input");s.type="text",s.className="form-input step-field-value",s.dataset.field="value",s.placeholder="e.g. approved or {{now}}",s.value=u||"",h.appendChild(s)}}M(z?C:null,t.config?.value||""),i.addEventListener("change",()=>{const d=i.value==="__custom__";p.style.display=d?"":"none",d||(p.value=""),M(d?null:i.value,"")})}function H(e){const t=[];return e.find(".step-card").each(function(){const l=this.dataset.stepType,a={};if(l==="updateField"){let n=this.querySelector(".step-field-field")?.value?.trim()||"";n==="__custom__"&&(n=this.querySelector(".step-field-field-custom")?.value?.trim()||""),a.field=n;const c=this.querySelector(".step-field-value");let i=c?.value?.trim()||"";i==="__custom__"&&(i=c?.nextElementSibling?.value?.trim()||""),a.value=i}else($[l]||[]).forEach(n=>{const c=this.querySelector(`.step-field-${n.name}`);if(!c)return;const i=c.value.trim();if(n.multiline&&i)try{a[n.name]=JSON.parse(i)}catch{a[n.name]=i}else a[n.name]=i});t.push({type:l,config:a})}),t}async function Y(e){const t=e.find("#action-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const l=e.find("#action-collection").val();if(!l){E.toast("Target collection is required (General tab).",{type:"warning"});return}const a=[];e.find(".action-role-cb:checked").each(function(){a.push(this.value)});const n=e.find("#action-rowlevel-enabled").is(":checked");let c=null;if(n){const f=e.find("#action-rowlevel-mode").val()||"owner",h=e.find("#action-rowlevel-userkey").val()||"id";if(c={mode:f,userKey:h},f==="field"){const o=e.find("#action-rowlevel-field").val().trim();if(!o){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}c.field=o}}const i=e.find("#action-project").val()||"",p={title:t,slug:e.find("#action-slug").val().trim()||void 0,description:e.find("#action-description").val().trim(),collection:l,...e.find("#action-bundled").is(":checked")?{bundled:!0}:{},trigger:{type:e.find("#action-trigger-type").val()||"manual",label:e.find("#action-trigger-label").val().trim()||"Run",icon:e.find("#action-trigger-icon").val().trim()||"zap",confirmMessage:e.find("#action-trigger-confirm").val().trim()||null},steps:H(e),access:{roles:a,rowLevel:c},meta:{...O||{},project:i||null}},r=e.find("#save-action-btn").get(0);r&&(r.disabled=!0);try{if(N)await _.actions.update(N,p),E.toast("Action updated.",{type:"success"});else{const f=await _.actions.create(p);E.toast("Action created.",{type:"success"}),R.navigate(`/actions/edit/${f.slug}`)}}catch(f){E.toast(f.message||"Failed to save action.",{type:"error"})}finally{r&&(r.disabled=!1)}}
|
|
1
|
+
import{api as _}from"../api.js";let N=null,L={};async function J(e){const t=e.find("#action-project").get(0);if(t)try{(await _.projects.list()).forEach(l=>{const n=document.createElement("option");n.value=l.slug,n.textContent=l.name||l.slug,t.appendChild(n)})}catch{}}const z={deleteEntry:[],moveToCollection:[{name:"targetCollection",label:"Target Collection Slug",placeholder:"e.g. archived-applications"}],webhook:[{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:[{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}]};export const actionEditorView={templateUrl:"/admin/js/templates/action-editor.html",async onMount(e){N=null,L={};const t=window.location.hash.match(/\/actions\/edit\/([^/?#]+)/);t&&(N=t[1]),E.tabs(e.find("#action-editor-tabs").get(0)),await P(e),await U(e),await J(e),N&&(e.find("#action-editor-title").text("Edit Action"),await B(e,N)),e.find("#add-step-btn").off("click").on("click",()=>{const a=e.find("#add-step-type").val()||"updateField";M(e,{type:a,config:{}})}),e.find("#save-action-btn").off("click").on("click",async()=>{await Q(e)}),K(e),Domma.icons.scan()}};async function P(e){const t=e.find("#action-collection").get(0);if(t)try{(await _.collections.list()).forEach(a=>{const l=document.createElement("option");l.value=a.slug,l.textContent=`${a.title} (${a.slug})`,t.appendChild(l)})}catch{}}async function U(e){const t=e.find("#action-roles-checkboxes").get(0);t&&["admin","manager","editor","subscriber"].forEach(a=>{const l=document.createElement("label");l.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const n=document.createElement("input");n.type="checkbox",n.value=a,n.dataset.role=a,n.className="action-role-cb",n.checked=a==="admin",l.appendChild(n),l.appendChild(document.createTextNode(a)),t.appendChild(l)})}async function B(e,t){try{const a=await _.actions.get(t);if(!a){E.toast("Action not found.",{type:"error"}),R.navigate("/actions");return}Y(e,a)}catch(a){E.toast(a.message||"Failed to load action.",{type:"error"}),R.navigate("/actions")}}function K(e){const t=e.find("#action-rowlevel-enabled").get(0),a=e.find("#action-rowlevel-config").get(0),l=e.find("#action-rowlevel-mode").get(0),n=e.find("#action-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{a&&(a.style.display=t.checked?"flex":"none")}),l&&l.addEventListener("change",()=>{n&&(n.style.display=l.value==="field"?"":"none")}))}function Y(e,t){e.find("#action-title").val(t.title||""),e.find("#action-slug").val(t.slug||""),e.find("#action-description").val(t.description||""),e.find("#action-collection").val(t.collection||""),L=t.meta||{},e.find("#action-project").val(t.meta?.project||""),e.find("#action-bundled").prop("checked",!!t.bundled),e.find("#action-trigger-type").val(t.trigger?.type||"manual"),e.find("#action-trigger-label").val(t.trigger?.label||"Run"),e.find("#action-trigger-icon").val(t.trigger?.icon||"zap"),e.find("#action-trigger-confirm").val(t.trigger?.confirmMessage||"");const a=t.access?.roles||["admin"];e.find(".action-role-cb").each(function(){this.checked=a.includes(this.value)});const l=t.access?.rowLevel;l&&(e.find("#action-rowlevel-enabled").prop("checked",!0),e.find("#action-rowlevel-config").css("display","flex"),e.find("#action-rowlevel-mode").val(l.mode||"owner"),e.find("#action-rowlevel-userkey").val(l.userKey||"id"),l.mode==="field"&&(e.find("#action-rowlevel-field-group").css("display",""),e.find("#action-rowlevel-field").val(l.field||"")));const n=e.find("#action-steps-list").get(0);if(n){const s=n.querySelector(".steps-empty-placeholder");for(s&&s.remove();n.firstChild;)n.removeChild(n.firstChild);const i=document.createElement("p");i.className="text-muted steps-empty-placeholder",i.textContent="No steps yet. Add a step to define what this action does.",i.style.cssText="text-align:center;padding:2rem 0;",n.appendChild(i)}(t.steps||[]).forEach(s=>M(e,s))}function M(e,t){const a=e.find("#action-steps-list").get(0);if(!a)return;const l=a.querySelector(".steps-empty-placeholder");l&&l.remove();const n=z[t.type]||[],s=document.createElement("div");s.className="card mb-2 step-card",s.dataset.stepType=t.type;const i=document.createElement("div");i.className="card-header",i.style.cssText="display:flex;align-items:center;gap:.5rem;";const p=document.createElement("code");p.textContent=t.type,p.style.cssText="flex:1;font-size:.85rem;";const r=document.createElement("button");r.type="button",r.className="btn btn-sm btn-ghost";const f=document.createElement("span");f.setAttribute("data-icon","trash"),r.appendChild(f),r.addEventListener("click",()=>{if(s.remove(),!a.querySelector(".step-card")){const o=document.createElement("p");o.className="text-muted steps-empty-placeholder",o.textContent="No steps yet. Add a step to define what this action does.",o.style.cssText="text-align:center;padding:2rem 0;",a.appendChild(o)}}),i.appendChild(p),i.appendChild(r);const h=document.createElement("div");if(h.className="card-body",n.length===0){const o=document.createElement("p");o.className="text-muted",o.textContent=t.type==="deleteEntry"?"This step deletes the entry. No configuration required.":"No additional configuration required.",o.style.margin="0",h.appendChild(o)}t.type==="updateField"?G(h,t,e):n.forEach(o=>{const g=document.createElement("div");g.style.cssText="margin-bottom:.75rem;";const b=document.createElement("label");b.className="form-label",b.textContent=o.label,g.appendChild(b);let m;o.multiline?(m=document.createElement("textarea"),m.rows=3,m.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",m.value=typeof t.config?.[o.name]=="object"?JSON.stringify(t.config[o.name],null,2):t.config?.[o.name]??""):(m=document.createElement("input"),m.type="text",m.value=t.config?.[o.name]??""),m.className=`form-input step-field-${o.name}`,m.placeholder=o.placeholder||"",m.dataset.field=o.name,g.appendChild(m),h.appendChild(g)}),s.appendChild(i),s.appendChild(h),a.appendChild(s),Domma.icons.scan(s)}async function G(e,t,a){const l=a.find("#action-collection").val(),n=document.createElement("div");n.style.cssText="margin-bottom:.75rem;";const s=document.createElement("label");s.className="form-label",s.textContent="Field",n.appendChild(s);const i=document.createElement("select");i.className="form-input step-field-field",i.dataset.field="field";const p=document.createElement("input");p.type="text",p.className="form-input mt-2 step-field-field-custom",p.placeholder="Field name, e.g. status",p.style.display="none";const r=document.createElement("div");r.style.cssText="margin-bottom:.75rem;";const f=document.createElement("label");f.className="form-label",f.textContent="New Value",r.appendChild(f);const h=document.createElement("div");r.appendChild(h);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm mt-2",o.style.cssText="font-size:.75rem;padding:.2rem .5rem;",o.textContent="Template Variables";const g=document.createElement("div");g.style.cssText="display:none;background:var(--dm-surface-raised,#1e1e2e);border-radius:.4rem;padding:.75rem;margin-top:.5rem;font-size:.8rem;line-height:1.7;";const b=document.createElement("strong");b.textContent="Available template variables:",g.appendChild(b);const m=document.createElement("table");m.style.cssText="width:100%;border-collapse:collapse;margin-top:.4rem;",[["{{now}}","Current ISO timestamp"],["{{user.id}}","Executing user's ID"],["{{user.name}}","Executing user's name"],["{{user.email}}","Executing user's email"],["{{entry.id}}","Entry's unique ID"],["{{entry.data.fieldName}}","Any field value from the entry"],["{{env.CMS_PUBLIC_*}}","Public environment variables"]].forEach(([d,u])=>{const x=document.createElement("tr"),w=document.createElement("td");w.style.cssText="padding:.2rem .5rem .2rem 0;opacity:.7;white-space:nowrap;";const T=document.createElement("code");T.textContent=d,w.appendChild(T);const c=document.createElement("td");c.textContent=u,x.appendChild(w),x.appendChild(c),m.appendChild(x)}),g.appendChild(m),o.addEventListener("click",()=>{const d=g.style.display!=="none";g.style.display=d?"none":"",o.textContent=d?"Template Variables":"Hide Variables"}),r.appendChild(o),r.appendChild(g),e.appendChild(n),e.appendChild(r);let S=[];if(l)try{S=(await _.collections.get(l)).fields||[]}catch{}const F=document.createElement("option");F.value="",F.textContent=S.length?"\u2014 select a field \u2014":"\u2014 no fields available \u2014",i.appendChild(F),S.forEach(d=>{const u=document.createElement("option");u.value=d.name,u.textContent=`${d.label} (${d.name})`,u.dataset.fieldType=d.type,u.dataset.fieldOptions=d.type==="select"?JSON.stringify(d.options||[]):"",i.appendChild(u)});const j=document.createElement("option");j.value="__custom__",j.textContent="\u2014 enter manually \u2014",i.appendChild(j);const C=t.config?.field||"",V=C&&[...i.options].find(d=>d.value===C);V?i.value=C:C&&(i.value="__custom__",p.value=C,p.style.display=""),n.appendChild(i),n.appendChild(p);function I(d,u){h.textContent="";const x=d?[...i.options].find(c=>c.value===d):null,w=x?.dataset.fieldType==="select",T=w?JSON.parse(x.dataset.fieldOptions||"[]"):[];if(w&&T.length){const c=document.createElement("select");c.className="form-input step-field-value",c.dataset.field="value";const O=document.createElement("option");O.value="",O.textContent="\u2014 select a value \u2014",c.appendChild(O),T.forEach(y=>{const k=typeof y=="string"?y:y.value??"",D=typeof y=="string"?y:y.label||y.value||k;if(!k||k==="undefined")return;const q=document.createElement("option");q.value=k,q.textContent=D,c.appendChild(q)});const A=document.createElement("option");A.value="__custom__",A.textContent="\u2014 enter manually \u2014",c.appendChild(A);const v=document.createElement("input");v.type="text",v.className="form-input mt-2",v.placeholder="e.g. approved or {{now}}",v.style.display="none",u&&[...c.options].find(y=>y.value===u&&y.value!=="__custom__")?c.value=u:u&&(c.value="__custom__",v.value=u,v.style.display=""),c.addEventListener("change",()=>{const y=c.value==="__custom__";v.style.display=y?"":"none",y||(v.value="")}),h.appendChild(c),h.appendChild(v)}else{const c=document.createElement("input");c.type="text",c.className="form-input step-field-value",c.dataset.field="value",c.placeholder="e.g. approved or {{now}}",c.value=u||"",h.appendChild(c)}}I(V?C:null,t.config?.value||""),i.addEventListener("change",()=>{const d=i.value==="__custom__";p.style.display=d?"":"none",d||(p.value=""),I(d?null:i.value,"")})}function H(e){const t=[];return e.find(".step-card").each(function(){const a=this.dataset.stepType,l={};if(a==="updateField"){let n=this.querySelector(".step-field-field")?.value?.trim()||"";n==="__custom__"&&(n=this.querySelector(".step-field-field-custom")?.value?.trim()||""),l.field=n;const s=this.querySelector(".step-field-value");let i=s?.value?.trim()||"";i==="__custom__"&&(i=s?.nextElementSibling?.value?.trim()||""),l.value=i}else(z[a]||[]).forEach(n=>{const s=this.querySelector(`.step-field-${n.name}`);if(!s)return;const i=s.value.trim();if(n.multiline&&i)try{l[n.name]=JSON.parse(i)}catch{l[n.name]=i}else l[n.name]=i});t.push({type:a,config:l})}),t}async function Q(e){const t=e.find("#action-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const a=e.find("#action-collection").val();if(!a){E.toast("Target collection is required (General tab).",{type:"warning"});return}const l=[];e.find(".action-role-cb:checked").each(function(){l.push(this.value)});const n=e.find("#action-rowlevel-enabled").is(":checked");let s=null;if(n){const f=e.find("#action-rowlevel-mode").val()||"owner",h=e.find("#action-rowlevel-userkey").val()||"id";if(s={mode:f,userKey:h},f==="field"){const o=e.find("#action-rowlevel-field").val().trim();if(!o){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}s.field=o}}const i=e.find("#action-project").val()||"",p={title:t,slug:e.find("#action-slug").val().trim()||void 0,description:e.find("#action-description").val().trim(),collection:a,...e.find("#action-bundled").is(":checked")?{bundled:!0}:{},trigger:{type:e.find("#action-trigger-type").val()||"manual",label:e.find("#action-trigger-label").val().trim()||"Run",icon:e.find("#action-trigger-icon").val().trim()||"zap",confirmMessage:e.find("#action-trigger-confirm").val().trim()||null},steps:H(e),access:{roles:l,rowLevel:s},meta:{...L||{},project:i||null}},r=e.find("#save-action-btn").get(0);r&&(r.disabled=!0);try{if(N)await _.actions.update(N,p),E.toast("Action updated.",{type:"success"});else{const f=await _.actions.create(p);E.toast("Action created.",{type:"success"}),R.navigate(`/actions/edit/${f.slug}`)}}catch(f){E.toast(f.message||"Failed to save action.",{type:"error"})}finally{r&&(r.disabled=!1)}}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{api as f}from"../api.js";let u=null,h=!1,v={};function g(){h=!0;const e=document.getElementById("block-quick-switch");e&&(e.disabled=!0)}function x(){h=!1;const e=document.getElementById("block-quick-switch");e&&(e.disabled=!1)}function C(e,t){if(e==null||e==="")return null;const n=e instanceof Date?e:new Date(e);if(isNaN(n.getTime()))return null;const c=(s,o=2)=>String(s).padStart(o,"0"),a=["January","February","March","April","May","June","July","August","September","October","November","December"],l=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],i={YYYY:()=>n.getFullYear(),YY:()=>c(n.getFullYear()%100),MMMM:()=>a[n.getMonth()],MMM:()=>a[n.getMonth()].slice(0,3),MM:()=>c(n.getMonth()+1),M:()=>n.getMonth()+1,DD:()=>c(n.getDate()),D:()=>n.getDate(),dddd:()=>l[n.getDay()],ddd:()=>l[n.getDay()].slice(0,3),HH:()=>c(n.getHours()),H:()=>n.getHours(),hh:()=>c((n.getHours()+11)%12+1),h:()=>(n.getHours()+11)%12+1,mm:()=>c(n.getMinutes()),m:()=>n.getMinutes(),ss:()=>c(n.getSeconds()),s:()=>n.getSeconds(),A:()=>n.getHours()<12?"AM":"PM",a:()=>n.getHours()<12?"am":"pm"};return t.replace(/YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|A|a/g,s=>String(i[s]()))}export const blockEditorView={templateUrl:"/admin/js/templates/block-editor.html",async onMount(e){I.register("edit-2",{viewBox:"0 0 24 24",path:"M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),requestAnimationFrame(()=>{document.querySelectorAll('[data-icon="edit-2"]').forEach(l=>{if(l.tagName.toLowerCase()==="svg")return;const i=I.render("edit-2",{size:parseInt(l.dataset.iconSize,10)||24});i&&l.replaceWith(i)})}),u=null,h=!1,v={};const t=e.find("#block-project").get(0);if(t)try{(await f.projects.list()).forEach(i=>{const s=document.createElement("option");s.value=i.slug,s.textContent=i.name||i.slug,t.appendChild(s)})}catch{}const n=window.location.hash.match(/\/blocks\/edit\/([^/?#]+)/);n&&(u=decodeURIComponent(n[1]));const c=e.find("#block-name").get(0),a=e.find("#block-content").get(0);if(u){e.find("#block-editor-title").text("Edit Block"),c&&(c.value=u,c.disabled=!0);try{const l=await f.blocks.get(u);a&&(a.value=l.content||""),e.find("#block-bundled").prop("checked",!!l.bundled),e.find("#block-css").val(l.css||""),v=l.meta||{},e.find("#block-project").val(l.meta?.project||"");try{const i=await f.blocks.list(),s=e.find("#block-quick-switch").get(0);s&&Array.isArray(i)&&i.length&&(i.slice().sort((o,r)=>o.name.localeCompare(r.name)).forEach(o=>{const r=document.createElement("option");r.value=o.name,r.textContent=o.name,o.name===u&&(r.selected=!0),s.appendChild(r)}),s.style.display="",s.addEventListener("change",function(){const o=this.value;o&&o!==u&&!h&&R.navigate(`/blocks/edit/${encodeURIComponent(o)}`)}))}catch{}}catch(l){E.toast(l.message||"Block not found.",{type:"error"}),R.navigate("/blocks");return}}a&&(L(a,e),$(a,e)),e.find("#save-block-btn").off("click").on("click",async()=>{await
|
|
1
|
+
import{api as f}from"../api.js";let u=null,h=!1,v={};function g(){h=!0;const e=document.getElementById("block-quick-switch");e&&(e.disabled=!0)}function x(){h=!1;const e=document.getElementById("block-quick-switch");e&&(e.disabled=!1)}function C(e,t){if(e==null||e==="")return null;const n=e instanceof Date?e:new Date(e);if(isNaN(n.getTime()))return null;const c=(s,o=2)=>String(s).padStart(o,"0"),a=["January","February","March","April","May","June","July","August","September","October","November","December"],l=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],i={YYYY:()=>n.getFullYear(),YY:()=>c(n.getFullYear()%100),MMMM:()=>a[n.getMonth()],MMM:()=>a[n.getMonth()].slice(0,3),MM:()=>c(n.getMonth()+1),M:()=>n.getMonth()+1,DD:()=>c(n.getDate()),D:()=>n.getDate(),dddd:()=>l[n.getDay()],ddd:()=>l[n.getDay()].slice(0,3),HH:()=>c(n.getHours()),H:()=>n.getHours(),hh:()=>c((n.getHours()+11)%12+1),h:()=>(n.getHours()+11)%12+1,mm:()=>c(n.getMinutes()),m:()=>n.getMinutes(),ss:()=>c(n.getSeconds()),s:()=>n.getSeconds(),A:()=>n.getHours()<12?"AM":"PM",a:()=>n.getHours()<12?"am":"pm"};return t.replace(/YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|A|a/g,s=>String(i[s]()))}export const blockEditorView={templateUrl:"/admin/js/templates/block-editor.html",async onMount(e){I.register("edit-2",{viewBox:"0 0 24 24",path:"M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),requestAnimationFrame(()=>{document.querySelectorAll('[data-icon="edit-2"]').forEach(l=>{if(l.tagName.toLowerCase()==="svg")return;const i=I.render("edit-2",{size:parseInt(l.dataset.iconSize,10)||24});i&&l.replaceWith(i)})}),u=null,h=!1,v={};const t=e.find("#block-project").get(0);if(t)try{(await f.projects.list()).forEach(i=>{const s=document.createElement("option");s.value=i.slug,s.textContent=i.name||i.slug,t.appendChild(s)})}catch{}const n=window.location.hash.match(/\/blocks\/edit\/([^/?#]+)/);n&&(u=decodeURIComponent(n[1]));const c=e.find("#block-name").get(0),a=e.find("#block-content").get(0);if(u){e.find("#block-editor-title").text("Edit Block"),c&&(c.value=u,c.disabled=!0);try{const l=await f.blocks.get(u);a&&(a.value=l.content||""),e.find("#block-bundled").prop("checked",!!l.bundled),e.find("#block-css").val(l.css||""),v=l.meta||{},e.find("#block-project").val(l.meta?.project||"");try{const i=await f.blocks.list(),s=e.find("#block-quick-switch").get(0);s&&Array.isArray(i)&&i.length&&(i.slice().sort((o,r)=>o.name.localeCompare(r.name)).forEach(o=>{const r=document.createElement("option");r.value=o.name,r.textContent=o.name,o.name===u&&(r.selected=!0),s.appendChild(r)}),s.style.display="",s.addEventListener("change",function(){const o=this.value;o&&o!==u&&!h&&R.navigate(`/blocks/edit/${encodeURIComponent(o)}`)}))}catch{}}catch(l){E.toast(l.message||"Block not found.",{type:"error"}),R.navigate("/blocks");return}}a&&(L(a,e),$(a,e)),e.find("#save-block-btn").off("click").on("click",async()=>{await q(e)}),Domma.icons.scan()}};function L(e,t){const n=t.find("#block-line-numbers").get(0),c=t.find("#block-cursor-pos").get(0);function a(){const s=e.value.split(`
|
|
2
2
|
`).length;n.textContent=Array.from({length:s},(o,r)=>r+1).join(`
|
|
3
3
|
`),n.scrollTop=e.scrollTop}a(),e.addEventListener("input",a),e.addEventListener("scroll",()=>{n.scrollTop=e.scrollTop});function l(){if(!c)return;const s=e.value.slice(0,e.selectionStart).split(`
|
|
4
4
|
`);c.textContent=`Ln ${s.length}, Col ${s[s.length-1].length+1}`}e.addEventListener("keyup",l),e.addEventListener("click",l),e.addEventListener("keydown",s=>{if(s.key==="Tab"){s.preventDefault();const o=e.selectionStart,r=e.selectionEnd;if(!s.shiftKey)e.value=e.value.slice(0,o)+" "+e.value.slice(r),e.selectionStart=e.selectionEnd=o+2;else{const d=e.value.lastIndexOf(`
|
|
5
5
|
`,o-1)+1,m=e.value.slice(d,d+2),p=m===" "?2:m[0]===" "?1:0;p>0&&(e.value=e.value.slice(0,d)+e.value.slice(d+p),e.selectionStart=e.selectionEnd=Math.max(d,o-p))}e.dispatchEvent(new Event("input"))}if(s.key==="Enter"){s.preventDefault();const o=e.selectionStart,r=e.value.lastIndexOf(`
|
|
6
6
|
`,o-1)+1,d=e.value.slice(r,o).match(/^(\s*)/)[1];e.value=e.value.slice(0,o)+`
|
|
7
7
|
`+d+e.value.slice(e.selectionEnd),e.selectionStart=e.selectionEnd=o+1+d.length,e.dispatchEvent(new Event("input"))}});const i=t.find("#block-editor-toolbar").get(0);i&&i.addEventListener("click",s=>{const o=s.target.closest("[data-action]");o&&(D(o.dataset.action,e),e.focus())})}function D(e,t){switch(e){case"undo":document.execCommand("undo");break;case"redo":document.execCommand("redo");break;case"cut":t.selectionStart!==t.selectionEnd&&(navigator.clipboard.writeText(t.value.slice(t.selectionStart,t.selectionEnd)).catch(()=>{}),document.execCommand("cut"));break;case"copy":t.selectionStart!==t.selectionEnd&&navigator.clipboard.writeText(t.value.slice(t.selectionStart,t.selectionEnd)).catch(()=>{});break;case"paste":navigator.clipboard.readText().then(n=>{const c=t.selectionStart;t.value=t.value.slice(0,c)+n+t.value.slice(t.selectionEnd),t.selectionStart=t.selectionEnd=c+n.length,t.dispatchEvent(new Event("input"))}).catch(()=>E.toast("Use Ctrl+V to paste.",{type:"info"}));break;case"select-all":t.select();break;case"format":T(t);break}}function T(e){const t=e.value.match(/(<[^>]+>|[^<]+)/g)||[],n=[];let c=0;const a=" ";for(const s of t){const o=s.trim();if(o)if(o.startsWith("</"))c=Math.max(0,c-1),n.push(a.repeat(c)+o);else if(o.startsWith("<")&&!o.startsWith("<!")&&!o.endsWith("/>")){n.push(a.repeat(c)+o);const r=(o.match(/^<(\w+)/)||[])[1]||"";j.has(r.toLowerCase())||c++}else n.push(a.repeat(c)+o)}const l=n.join(`
|
|
8
|
-
`),i=e.selectionStart;e.value=l,e.selectionStart=e.selectionEnd=Math.min(i,l.length),e.dispatchEvent(new Event("input"))}const j=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);function $(e,t){const n=t.find("#block-editor-body").get(0),c=t.find("#block-sample-data").get(0),a=t.find("#block-preview-output");t.find("[data-mode]").each(function(){this.addEventListener("click",()=>{t.find("[data-mode]").each(function(){this.classList.remove("active")}),this.classList.add("active"),n.className=`editor-body editor-mode-${this.dataset.mode}`,this.dataset.mode!=="write"&&l()})}),e.addEventListener("input",()=>{M(c,e),l(),g()});{const i=t.find("#block-css").get(0);i&&i.addEventListener("input",()=>{l(),g()});const s=t.find("#block-name").get(0);s&&s.addEventListener("input",()=>{l(),g()});const o=t.find("#block-bundled").get(0);o&&o.addEventListener("change",g)}e.value.trim()&&(M(c,e),l());function l(){if(n.classList.contains("editor-mode-write"))return;const i=e.value.replace(/\{\{([\w_]+)(?::([^{}]+))?\}\}/g,(p,k,y)=>{const w=c.querySelector(`[data-placeholder="${CSS.escape(k)}"]`)?.value??`[${k}]`;if(y){const S=C(w,y);if(S!==null)return b(S)}return b(w)}),s=(t.find("#block-name").val()||"preview").trim()||"preview",o=b(s),r=t.find("#block-css").val()||"",d=t.find("#block-css-scope-hint").get(0);d&&(d.textContent=`[data-block="${s}"]`);const m=r.trim()?`<style>[data-block="${o}"] { ${String(r).replace(/<\/style/gi,"<\\/style")} }</style>`:"";a.html(m+`<div data-block="${o}">${i}</div>`,{safe:!1}),Domma.icons.scan(a.get(0))}c._renderPreview=l}function M(e,t){const n=
|
|
8
|
+
`),i=e.selectionStart;e.value=l,e.selectionStart=e.selectionEnd=Math.min(i,l.length),e.dispatchEvent(new Event("input"))}const j=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);function $(e,t){const n=t.find("#block-editor-body").get(0),c=t.find("#block-sample-data").get(0),a=t.find("#block-preview-output");t.find("[data-mode]").each(function(){this.addEventListener("click",()=>{t.find("[data-mode]").each(function(){this.classList.remove("active")}),this.classList.add("active"),n.className=`editor-body editor-mode-${this.dataset.mode}`,this.dataset.mode!=="write"&&l()})}),e.addEventListener("input",()=>{M(c,e),l(),g()});{const i=t.find("#block-css").get(0);i&&i.addEventListener("input",()=>{l(),g()});const s=t.find("#block-name").get(0);s&&s.addEventListener("input",()=>{l(),g()});const o=t.find("#block-bundled").get(0);o&&o.addEventListener("change",g)}e.value.trim()&&(M(c,e),l());function l(){if(n.classList.contains("editor-mode-write"))return;const i=e.value.replace(/\{\{([\w_]+)(?::([^{}]+))?\}\}/g,(p,k,y)=>{const w=c.querySelector(`[data-placeholder="${CSS.escape(k)}"]`)?.value??`[${k}]`;if(y){const S=C(w,y);if(S!==null)return b(S)}return b(w)}),s=(t.find("#block-name").val()||"preview").trim()||"preview",o=b(s),r=t.find("#block-css").val()||"",d=t.find("#block-css-scope-hint").get(0);d&&(d.textContent=`[data-block="${s}"]`);const m=r.trim()?`<style>[data-block="${o}"] { ${String(r).replace(/<\/style/gi,"<\\/style")} }</style>`:"";a.html(m+`<div data-block="${o}">${i}</div>`,{safe:!1}),Domma.icons.scan(a.get(0))}c._renderPreview=l}function M(e,t){const n=Y(t.value),c=new Set([...e.querySelectorAll("[data-placeholder]")].map(a=>a.dataset.placeholder));if(n.size>0&&e.querySelector("p.text-muted")?.remove(),n.forEach(a=>{c.has(a)||A(e,a)}),e.querySelectorAll("[data-placeholder]").forEach(a=>{n.has(a.dataset.placeholder)||a.closest(".block-sample-row")?.remove()}),n.size===0&&!e.querySelector("p.text-muted")){const a=document.createElement("p");a.className="text-muted",a.style.cssText="font-size:.8rem;margin:0;",a.textContent="No {{placeholders}} detected in template.",e.appendChild(a)}}function A(e,t){const n=document.createElement("div");n.className="block-sample-row",n.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.35rem;";const c=document.createElement("label");c.style.cssText="font-size:.75rem;color:var(--dm-text-muted,#888);white-space:nowrap;min-width:90px;text-align:right;flex-shrink:0;",c.textContent=`{{${t}}}`;const a=document.createElement("input");a.type="text",a.className="form-input form-input--sm",a.style.flex="1",a.dataset.placeholder=t,a.placeholder=`Sample ${t}`,a.value=H(t),a.addEventListener("input",()=>{e._renderPreview&&e._renderPreview()}),n.appendChild(c),n.appendChild(a),e.appendChild(n)}function H(e){const t=e.toLowerCase();return t==="_id"?"a1b2c3d4-e5f6-7890-abcd-ef1234567890":t==="_createdat"?new Date().toISOString():t==="_updatedat"?new Date().toISOString():t.includes("email")?"user@example.com":t.includes("phone")?"+44 7700 900000":t.includes("name")?"Jane Smith":t.includes("title")?"Sample Title":t.includes("message")||t.includes("content")||t.includes("description")?"This is a sample value for preview purposes.":t.includes("rating")?"excellent":t.includes("status")?"active":t.includes("priority")?"high":t.includes("date")?new Date().toLocaleDateString():t.includes("tag")?"tag1, tag2":t.includes("subject")?"Sample Subject":t.includes("category")?"general":`Sample ${e}`}function Y(e){const t=new Set;for(const[,n]of e.matchAll(/\{\{([\w_]+)(?::[^{}]+)?\}\}/g))t.add(n);return t}function b(e){return String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async function q(e){const t=e.find("#block-name").get(0),n=e.find("#block-content").get(0),c=(t?.value||"").trim(),a=n?.value??"";if(!c){E.toast("Block name is required.",{type:"warning"});return}if(!/^[a-z0-9][a-z0-9-]*$/.test(c)){E.toast("Name must start with a letter or digit and contain only lowercase letters, digits, and hyphens.",{type:"warning"});return}const l=!!e.find("#block-bundled").is(":checked"),i=e.find("#save-block-btn").get(0);i&&(i.disabled=!0);try{await f.blocks.put(c,{content:a,bundled:l,css:e.find("#block-css").val()||"",meta:{...v||{},project:e.find("#block-project").val()||""||null}}),x(),E.toast(u?"Block updated.":"Block created.",{type:"success"}),u||(u=c,R.navigate(`/blocks/edit/${encodeURIComponent(c)}`))}catch(s){E.toast(s.message||"Failed to save block.",{type:"error"})}finally{i&&(i.disabled=!1)}}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
`).filter(c=>c.trim()).map(c=>{const[
|
|
3
|
-
`),A.appendChild(_),A.appendChild(D);const x=document.createElement("div");x.className="fb-grid-row",x.style.gridTemplateColumns="1fr auto",x.style.gap=".6rem",x.style.alignItems="end",x.style.marginBottom=".6rem",x.style.display=document.getElementById("collection-layout")?.value==="grid"?"grid":"none";const G=document.createElement("div"),J=document.createElement("label");J.className="form-label",J.textContent="Column Span";const k=document.createElement("input");k.id=`fb-span-${t}`,k.type="number",k.className="form-input",k.min="1",k.max="6",k.value=e.span>1?String(e.span):"1",G.appendChild(J),G.appendChild(k);const K=document.createElement("div");K.style.cssText="padding-bottom:.35rem;";const U=document.createElement("label");U.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const H=document.createElement("input");return H.id=`fb-fullwidth-${t}`,H.type="checkbox",H.checked=!!e.fullWidth,U.appendChild(H),U.appendChild(document.createTextNode("Full Width")),K.appendChild(U),x.appendChild(G),x.appendChild(K),a.appendChild(c),a.appendChild(N),a.appendChild(A),a.appendChild(x),d.addEventListener("click",()=>{const i=a.style.display!=="none";a.style.display=i?"none":"",l.style.transform=i?"":"rotate(180deg)"}),n.appendChild(d),n.appendChild(a),n}function W(e){if(e){if(e.textContent="",g.length===0){const t=document.createElement("p");t.className="text-muted",t.id="fields-empty-msg",t.style.cssText="text-align:center;padding:2rem 0;",t.textContent='No fields yet. Click "Add Field" to get started.',e.appendChild(t);return}g.forEach((t,n)=>{e.appendChild(de(t,n))})}}function ie(e,t){t.textContent="",te.forEach(n=>{const d=e?.[n]||{enabled:!1,access:"admin"},s=document.createElement("div");s.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const r=document.createElement("strong");r.textContent=n.charAt(0).toUpperCase()+n.slice(1),r.style.cssText="font-size:.9rem;";const u=document.createElement("label");u.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const l=document.createElement("input");l.type="checkbox",l.id=`api-${n}-enabled`,l.checked=!!d.enabled,u.appendChild(l),u.appendChild(document.createTextNode("Enable public access"));const o=document.createElement("select");if(o.id=`api-${n}-access`,o.className="form-input",ae.forEach(a=>{const c=document.createElement("option");c.value=a,c.textContent=a.charAt(0).toUpperCase()+a.slice(1),a===d.access&&(c.selected=!0),o.appendChild(c)}),s.appendChild(r),s.appendChild(u),s.appendChild(o),t.appendChild(s),n==="read"){const a=document.createElement("div");a.style.cssText="display:grid;grid-template-columns:140px 1fr;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const c=document.createElement("label");c.htmlFor="api-read-fields",c.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",c.textContent="Read fields";const p=document.createElement("input");p.type="text",p.id="api-read-fields",p.className="form-input",p.placeholder="Comma-separated allowlist \u2014 empty = all fields",p.value=Array.isArray(d.fields)?d.fields.join(", "):"",a.appendChild(c),a.appendChild(p),t.appendChild(a)}})}function ce(e,t){E.dropdown("#storage-adapter-trigger",{items:[{label:"File (default)",value:"file"},{label:"MongoDB",value:"mongodb"}],onSelect:({item:d})=>{e.find("#storage-adapter").val(d.value),e.find("#storage-adapter-label").text(d.label);const s=d.value==="mongodb";e.find("#storage-connection-group").toggle(s),e.find("#storage-migration-warning").toggle(s&&!S)}});const n=t.map(d=>({label:d,value:d}));E.dropdown("#storage-connection-trigger",{items:n.length?n:[{label:"default",value:"default"}],onSelect:({item:d})=>{e.find("#storage-connection").val(d.value),e.find("#storage-connection-label").text(d.label)}})}function se(){return(document.getElementById("storage-adapter")?.value||"file")==="mongodb"?{adapter:"mongodb",connection:document.getElementById("storage-connection")?.value||"default"}:{adapter:"file"}}function re(){const e={};return te.forEach(t=>{const n=document.getElementById(`api-${t}-enabled`)?.checked??!1,d=document.getElementById(`api-${t}-access`)?.value||"admin";if(e[t]={enabled:n,access:d},t==="read"){const s=(document.getElementById("api-read-fields")?.value||"").split(",").map(r=>r.trim()).filter(Boolean);s.length&&(e[t].fields=s)}}),e}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(e){g=[],y=null,S=!0;const t=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);t&&(y=t[1],S=!1),E.tabs(e.find("#collection-tabs").get(0)),e.find("#collection-layout").get(0)?.addEventListener("change",function(){const l=this.value==="grid";e.find("#collection-columns-group").get(0).style.display=l?"":"none",document.querySelectorAll(".fb-grid-row").forEach(o=>{o.style.display=l?"grid":"none"})});const n=e.find("#fields-list").get(0),d=e.find("#api-access-rows").get(0);X={};const s=e.find("#collection-project").get(0);if(s)try{(await w.projects.list()).forEach(o=>{const a=document.createElement("option");a.value=o.slug,a.textContent=o.name||o.slug,s.appendChild(a)})}catch{}const r=await w.collections.proStatus();r?.pro&&y!=="roles"&&(e.find("#storage-tab-btn").show(),ce(e,r.connections));let u={create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}};if(S){const l=e.find("#field-title").get(0),o=e.find("#field-slug").get(0);l&&o&&(l.addEventListener("input",()=>{o.dataset.manual||(o.value=le(l.value))}),o.addEventListener("input",()=>{o.dataset.manual="1"}))}else try{const l=await w.collections.get(y);if(!l){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const o=e.find("#editor-title-text").get(0);o&&(o.textContent=l.title),e.find("#field-title").val(l.title||""),e.find("#field-slug").val(l.slug||""),e.find("#field-slug").prop("readonly",!0),e.find("#slug-hint").get(0).textContent="Slug cannot be changed after creation.",e.find("#field-description").val(l.description||""),e.find("#collection-layout").val(l.layout||"stacked"),e.find("#collection-columns").val(l.columns||2),e.find("#collection-bundled").prop("checked",!!l.bundled),X=l.meta||{},e.find("#collection-project").val(l.meta?.project||""),e.find("#collection-columns-group").get(0).style.display=l.layout==="grid"?"":"none",g=l.fields||[],u=l.api||u,L=l.storage?.adapter||"file",l.storage&&(e.find("#storage-adapter").val(l.storage.adapter||"file"),e.find("#storage-adapter-label").text(l.storage.adapter==="mongodb"?"MongoDB":"File (default)"),l.storage.adapter==="mongodb"&&(e.find("#storage-connection-group").show(),e.find("#storage-connection").val(l.storage.connection||"default"),e.find("#storage-connection-label").text(l.storage.connection||"default"))),y==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}W(n),ie(u,d),e.find("#add-field-btn").off("click").on("click",()=>{g=Z(),g.push({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null}),W(n);const l=n.querySelectorAll(".fb-field-card");if(l.length){const o=l[l.length-1],a=o.querySelector(".fb-field-body"),c=o.querySelector(".fb-field-chevron");a&&(a.style.display=""),c&&(c.style.transform="rotate(180deg)"),o.querySelector(`#fb-label-${g.length-1}`)?.focus()}}),e.find("#save-collection-btn").off("click").on("click",async()=>{const l=e.find("#field-title").val().trim(),o=e.find("#field-slug").val().trim(),a=e.find("#field-description").val().trim();if(!l){E.toast("Title is required.",{type:"warning"});return}const c=Z(),p=re(),h=e.find("#collection-layout").val()||"stacked",b=parseInt(e.find("#collection-columns").val(),10)||2,f=se(),$=e.find("#collection-bundled").is(":checked"),v=e.find("#collection-project").val()||"",T={...X||{},project:v||null},B=e.find("#save-collection-btn");B.prop("disabled",!0);try{if(S){const m=await w.collections.create({title:l,slug:o,description:a,layout:h,columns:b,fields:c,api:p,storage:f,meta:T,...$?{bundled:!0}:{}});y=m.slug,L=f.adapter||"file",S=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${m.slug}`)}else if((f.adapter||"file")!==L){let m=0;try{m=(await w.collections.listEntries(y,{limit:1}))?.total??0}catch{}const N=L==="file"?`file \u2192 ${f.adapter}`:`${L} \u2192 ${f.adapter||"file"}`;if(m>0&&await E.confirm(`You changed the storage adapter (${N}).
|
|
1
|
+
import{api as B}from"../api.js";const ee=[{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"}],Q=new Set(["select","radio","checkbox-group"]),te=["public","token","subscriber","editor","manager","admin"],le=["create","read","update","delete"];let x=[],$=null,q=!0,z=null,A="file",X={};function ne(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function ae(e){return ee.find(t=>t.value===e)?.label||e}function oe(e){const t={...x[e]},a=document.getElementById(`fb-label-${e}`),o=document.getElementById(`fb-name-${e}`),s=document.getElementById(`fb-type-${e}`),g=document.getElementById(`fb-required-${e}`),y=document.getElementById(`fb-placeholder-${e}`),b=document.getElementById(`fb-helper-${e}`);if(a&&(t.label=a.value.trim()||t.label),o&&(t.name=o.value.trim()||t.name),s&&(t.type=s.value||t.type),g&&(t.required=g.checked),y&&(t.placeholder=y.value.trim()),b&&(t.helper=b.value.trim()),Q.has(t.type)){const n=document.getElementById(`fb-options-${e}`);n&&(t.options=n.value.split(`
|
|
2
|
+
`).filter(c=>c.trim()).map(c=>{const[h,...w]=c.split(":");return{value:h.trim(),label:w.join(":").trim()||h.trim()}}))}const l=document.getElementById(`fb-span-${e}`);if(document.getElementById(`fb-fullwidth-${e}`)?.checked)t.fullWidth=!0,delete t.span;else{delete t.fullWidth;const n=parseInt(l?.value,10);n>1?t.span=n:delete t.span}return t}function Z(){return x.map((e,t)=>oe(t))}function de(e,t){const a=document.createElement("div");a.className="fb-field-card",a.dataset.index=t,a.style.cssText="border:1px solid var(--dm-border,#333);border-radius:8px;margin-bottom:.75rem;overflow:hidden;";const o=document.createElement("div");o.className="fb-field-header",o.style.cssText="display:flex;align-items:center;gap:.5rem;padding:.6rem .75rem;background:var(--dm-card-header-bg,rgba(255,255,255,.03));cursor:pointer;user-select:none;";const s=document.createElement("span");s.innerHTML='<span data-icon="menu" data-icon-size="14" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',s.title="Drag to reorder",s.style.cssText="cursor:grab;opacity:.4;flex-shrink:0;display:inline-flex;align-items:center;",a.draggable=!0,s.addEventListener("mousedown",()=>{a.draggable=!0}),a.addEventListener("dragstart",d=>{z=t,d.dataTransfer.effectAllowed="move",a.style.opacity="0.4"}),a.addEventListener("dragend",()=>{a.style.opacity="",document.querySelectorAll(".fb-field-card").forEach(d=>d.classList.remove("fb-drag-over"))}),a.addEventListener("dragover",d=>{d.preventDefault(),d.dataTransfer.dropEffect="move",document.querySelectorAll(".fb-field-card").forEach(N=>N.classList.remove("fb-drag-over")),a.classList.add("fb-drag-over")}),a.addEventListener("dragleave",()=>{a.classList.remove("fb-drag-over")}),a.addEventListener("drop",d=>{if(d.preventDefault(),a.classList.remove("fb-drag-over"),z===null||z===t)return;x=Z();const[N]=x.splice(z,1);x.splice(t,0,N),z=null,O(document.getElementById("fields-list"))});const g=document.createElement("span");g.className="fb-field-summary",g.style.cssText="flex:1;font-weight:500;font-size:.9rem;",g.textContent=e.label||"(Untitled field)";const y=document.createElement("span");y.style.cssText="font-size:.75rem;opacity:.5;",y.textContent=ae(e.type);const b=document.createElement("span");b.className="fb-field-chevron",b.innerHTML='<span data-icon="chevron-down" data-icon-size="12" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',b.style.cssText="opacity:.5;transition:transform .2s;display:inline-flex;align-items:center;";const l=document.createElement("button");l.type="button",l.innerHTML='<span data-icon="x" data-icon-size="13" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',l.className="btn btn-sm btn-ghost",l.style.cssText="padding:.15rem .45rem;line-height:1;opacity:.6;",l.title="Remove field",l.addEventListener("click",d=>{d.stopPropagation(),x.splice(t,1),O(document.getElementById("fields-list"))}),o.appendChild(s),o.appendChild(g),o.appendChild(y),o.appendChild(b),o.appendChild(l),Domma.icons.scan(o);const n=document.createElement("div");n.className="fb-field-body",n.style.cssText="padding:.75rem;display:none;";const c=document.createElement("div");c.style.cssText="display:grid;grid-template-columns:1fr 1fr 1fr;gap:.6rem;margin-bottom:.6rem;";const h=document.createElement("div"),w=document.createElement("label");w.className="form-label",w.textContent="Label";const i=document.createElement("input");i.id=`fb-label-${t}`,i.type="text",i.className="form-input",i.value=e.label||"",i.addEventListener("input",()=>{g.textContent=i.value.trim()||"(Untitled field)";const d=document.getElementById(`fb-name-${t}`);d&&!d.dataset.manual&&(d.value=ne(i.value).replace(/-/g,"_"))}),h.appendChild(w),h.appendChild(i);const p=document.createElement("div"),u=document.createElement("label");u.className="form-label",u.textContent="Name (key)";const m=document.createElement("input");m.id=`fb-name-${t}`,m.type="text",m.className="form-input",m.value=e.name||"",m.addEventListener("input",()=>{m.dataset.manual="1"}),p.appendChild(u),p.appendChild(m);const C=document.createElement("div"),L=document.createElement("label");L.className="form-label",L.textContent="Type";const v=document.createElement("select");v.id=`fb-type-${t}`,v.className="form-input",ee.forEach(d=>{const N=document.createElement("option");N.value=d.value,N.textContent=d.label,d.value===e.type&&(N.selected=!0),v.appendChild(N)}),v.addEventListener("change",()=>{y.textContent=ae(v.value);const d=n.querySelector(".fb-options-wrap");d&&(d.style.display=Q.has(v.value)?"":"none")}),C.appendChild(L),C.appendChild(v),c.appendChild(h),c.appendChild(p),c.appendChild(C);const f=document.createElement("div");f.style.cssText="display:grid;grid-template-columns:1fr 1fr auto;gap:.6rem;align-items:end;margin-bottom:.6rem;";const r=document.createElement("div"),T=document.createElement("label");T.className="form-label",T.textContent="Placeholder";const I=document.createElement("input");I.id=`fb-placeholder-${t}`,I.type="text",I.className="form-input",I.value=e.placeholder||"",r.appendChild(T),r.appendChild(I);const H=document.createElement("div"),_=document.createElement("label");_.className="form-label",_.textContent="Helper text";const F=document.createElement("input");F.id=`fb-helper-${t}`,F.type="text",F.className="form-input",F.value=e.helper||"",H.appendChild(_),H.appendChild(F);const V=document.createElement("div");V.style.cssText="padding-bottom:.35rem;";const D=document.createElement("label");D.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const M=document.createElement("input");M.id=`fb-required-${t}`,M.type="checkbox",M.checked=!!e.required,D.appendChild(M),D.appendChild(document.createTextNode("Required")),V.appendChild(D),f.appendChild(r),f.appendChild(H),f.appendChild(V);const j=document.createElement("div");j.className="fb-options-wrap",j.style.display=Q.has(e.type)?"":"none";const Y=document.createElement("label");Y.className="form-label",Y.textContent="Options (one per line: value: Label)";const W=document.createElement("textarea");W.id=`fb-options-${t}`,W.className="form-input",W.rows=4,W.value=(e.options||[]).map(d=>typeof d=="string"?`${d}: ${d}`:`${d.value??""}: ${d.label??d.value??""}`).join(`
|
|
3
|
+
`),j.appendChild(Y),j.appendChild(W);const k=document.createElement("div");k.className="fb-grid-row",k.style.gridTemplateColumns="1fr auto",k.style.gap=".6rem",k.style.alignItems="end",k.style.marginBottom=".6rem",k.style.display=document.getElementById("collection-layout")?.value==="grid"?"grid":"none";const G=document.createElement("div"),J=document.createElement("label");J.className="form-label",J.textContent="Column Span";const S=document.createElement("input");S.id=`fb-span-${t}`,S.type="number",S.className="form-input",S.min="1",S.max="6",S.value=e.span>1?String(e.span):"1",G.appendChild(J),G.appendChild(S);const K=document.createElement("div");K.style.cssText="padding-bottom:.35rem;";const U=document.createElement("label");U.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const P=document.createElement("input");return P.id=`fb-fullwidth-${t}`,P.type="checkbox",P.checked=!!e.fullWidth,U.appendChild(P),U.appendChild(document.createTextNode("Full Width")),K.appendChild(U),k.appendChild(G),k.appendChild(K),n.appendChild(c),n.appendChild(f),n.appendChild(j),n.appendChild(k),o.addEventListener("click",()=>{const d=n.style.display!=="none";n.style.display=d?"none":"",b.style.transform=d?"":"rotate(180deg)"}),a.appendChild(o),a.appendChild(n),a}function O(e){if(e){if(e.textContent="",x.length===0){const t=document.createElement("p");t.className="text-muted",t.id="fields-empty-msg",t.style.cssText="text-align:center;padding:2rem 0;",t.textContent='No fields yet. Click "Add Field" to get started.',e.appendChild(t);return}x.forEach((t,a)=>{e.appendChild(de(t,a))})}}function ie(e,t,a){t.textContent="",le.forEach(i=>{const p=e?.[i]||{enabled:!1,access:"admin"},u=document.createElement("div");u.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const m=document.createElement("strong");m.textContent=i.charAt(0).toUpperCase()+i.slice(1),m.style.cssText="font-size:.9rem;";const C=document.createElement("label");C.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const L=document.createElement("input");L.type="checkbox",L.id=`api-${i}-enabled`,L.checked=!!p.enabled,C.appendChild(L),C.appendChild(document.createTextNode("Enable public access"));const v=document.createElement("select");if(v.id=`api-${i}-access`,v.className="form-input",te.forEach(f=>{const r=document.createElement("option");r.value=f,r.textContent=f.charAt(0).toUpperCase()+f.slice(1),f===p.access&&(r.selected=!0),v.appendChild(r)}),u.appendChild(m),u.appendChild(C),u.appendChild(v),t.appendChild(u),i==="read"){const f=document.createElement("div");f.style.cssText="display:grid;grid-template-columns:140px 1fr;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const r=document.createElement("label");r.htmlFor="api-read-fields",r.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",r.textContent="Read fields";const T=document.createElement("input");T.type="text",T.id="api-read-fields",T.className="form-input",T.placeholder="Comma-separated allowlist \u2014 empty = all fields",T.value=Array.isArray(p.fields)?p.fields.join(", "):"",f.appendChild(r),f.appendChild(T),t.appendChild(f)}});const o=a||{},s=document.createElement("div");s.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-top:2px solid var(--dm-border,#333);";const g=document.createElement("strong");g.textContent="Export",g.style.cssText="font-size:.9rem;";const y=document.createElement("label");y.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const b=document.createElement("input");b.type="checkbox",b.id="export-enabled",b.checked=o.enabled!==!1,y.appendChild(b),y.appendChild(document.createTextNode("Allow export"));const l=document.createElement("select");l.id="export-access",l.className="form-input",te.filter(i=>i!=="token").forEach(i=>{const p=document.createElement("option");p.value=i,p.textContent=i.charAt(0).toUpperCase()+i.slice(1),i===o.access&&(p.selected=!0),l.appendChild(p)}),s.appendChild(g),s.appendChild(y),s.appendChild(l),t.appendChild(s);const n=document.createElement("div");n.style.cssText="display:grid;grid-template-columns:140px 1fr;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const c=document.createElement("label");c.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",c.textContent="Formats";const h=document.createElement("div");h.style.cssText="display:flex;gap:1rem;";const w=Array.isArray(o.formats)?o.formats:["csv","json"];["csv","json"].forEach(i=>{const p=document.createElement("label");p.style.cssText="display:flex;align-items:center;gap:.35rem;cursor:pointer;font-size:.875rem;";const u=document.createElement("input");u.type="checkbox",u.id=`export-format-${i}`,u.checked=w.includes(i),p.appendChild(u),p.appendChild(document.createTextNode(i.toUpperCase())),h.appendChild(p)}),n.appendChild(c),n.appendChild(h),t.appendChild(n)}function ce(e,t){E.dropdown("#storage-adapter-trigger",{items:[{label:"File (default)",value:"file"},{label:"MongoDB",value:"mongodb"}],onSelect:({item:o})=>{e.find("#storage-adapter").val(o.value),e.find("#storage-adapter-label").text(o.label);const s=o.value==="mongodb";e.find("#storage-connection-group").toggle(s),e.find("#storage-migration-warning").toggle(s&&!q)}});const a=t.map(o=>({label:o,value:o}));E.dropdown("#storage-connection-trigger",{items:a.length?a:[{label:"default",value:"default"}],onSelect:({item:o})=>{e.find("#storage-connection").val(o.value),e.find("#storage-connection-label").text(o.label)}})}function se(){return(document.getElementById("storage-adapter")?.value||"file")==="mongodb"?{adapter:"mongodb",connection:document.getElementById("storage-connection")?.value||"default"}:{adapter:"file"}}function re(){const e={};return le.forEach(t=>{const a=document.getElementById(`api-${t}-enabled`)?.checked??!1,o=document.getElementById(`api-${t}-access`)?.value||"admin";if(e[t]={enabled:a,access:o},t==="read"){const s=(document.getElementById("api-read-fields")?.value||"").split(",").map(g=>g.trim()).filter(Boolean);s.length&&(e[t].fields=s)}}),e}function pe(){const e=["csv","json"].filter(t=>document.getElementById(`export-format-${t}`)?.checked);return{enabled:document.getElementById("export-enabled")?.checked??!0,access:document.getElementById("export-access")?.value||"subscriber",formats:e}}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(e){x=[],$=null,q=!0;const t=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);t&&($=t[1],q=!1),E.tabs(e.find("#collection-tabs").get(0)),e.find("#collection-layout").get(0)?.addEventListener("change",function(){const l=this.value==="grid";e.find("#collection-columns-group").get(0).style.display=l?"":"none",document.querySelectorAll(".fb-grid-row").forEach(n=>{n.style.display=l?"grid":"none"})});const a=e.find("#fields-list").get(0),o=e.find("#api-access-rows").get(0);X={};const s=e.find("#collection-project").get(0);if(s)try{(await B.projects.list()).forEach(n=>{const c=document.createElement("option");c.value=n.slug,c.textContent=n.name||n.slug,s.appendChild(c)})}catch{}const g=await B.collections.proStatus();g?.pro&&$!=="roles"&&(e.find("#storage-tab-btn").show(),ce(e,g.connections));let y={create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}},b={enabled:!0,access:"subscriber",formats:["csv","json"]};if(q){const l=e.find("#field-title").get(0),n=e.find("#field-slug").get(0);l&&n&&(l.addEventListener("input",()=>{n.dataset.manual||(n.value=ne(l.value))}),n.addEventListener("input",()=>{n.dataset.manual="1"}))}else try{const l=await B.collections.get($);if(!l){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const n=e.find("#editor-title-text").get(0);n&&(n.textContent=l.title),e.find("#field-title").val(l.title||""),e.find("#field-slug").val(l.slug||""),e.find("#field-slug").prop("readonly",!0),e.find("#slug-hint").get(0).textContent="Slug cannot be changed after creation.",e.find("#field-description").val(l.description||""),e.find("#collection-layout").val(l.layout||"stacked"),e.find("#collection-columns").val(l.columns||2),e.find("#collection-bundled").prop("checked",!!l.bundled),X=l.meta||{},e.find("#collection-project").val(l.meta?.project||""),e.find("#collection-columns-group").get(0).style.display=l.layout==="grid"?"":"none",x=l.fields||[],y=l.api||y,b=l.export||b,A=l.storage?.adapter||"file",l.storage&&(e.find("#storage-adapter").val(l.storage.adapter||"file"),e.find("#storage-adapter-label").text(l.storage.adapter==="mongodb"?"MongoDB":"File (default)"),l.storage.adapter==="mongodb"&&(e.find("#storage-connection-group").show(),e.find("#storage-connection").val(l.storage.connection||"default"),e.find("#storage-connection-label").text(l.storage.connection||"default"))),$==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}O(a),ie(y,o,b),e.find("#add-field-btn").off("click").on("click",()=>{x=Z(),x.push({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null}),O(a);const l=a.querySelectorAll(".fb-field-card");if(l.length){const n=l[l.length-1],c=n.querySelector(".fb-field-body"),h=n.querySelector(".fb-field-chevron");c&&(c.style.display=""),h&&(h.style.transform="rotate(180deg)"),n.querySelector(`#fb-label-${x.length-1}`)?.focus()}}),e.find("#save-collection-btn").off("click").on("click",async()=>{const l=e.find("#field-title").val().trim(),n=e.find("#field-slug").val().trim(),c=e.find("#field-description").val().trim();if(!l){E.toast("Title is required.",{type:"warning"});return}const h=Z(),w=re(),i=pe(),p=e.find("#collection-layout").val()||"stacked",u=parseInt(e.find("#collection-columns").val(),10)||2,m=se(),C=e.find("#collection-bundled").is(":checked"),L=e.find("#collection-project").val()||"",v={...X||{},project:L||null},f=e.find("#save-collection-btn");f.prop("disabled",!0);try{if(q){const r=await B.collections.create({title:l,slug:n,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{}});$=r.slug,A=m.adapter||"file",q=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${r.slug}`)}else if((m.adapter||"file")!==A){let r=0;try{r=(await B.collections.listEntries($,{limit:1}))?.total??0}catch{}const T=A==="file"?`file \u2192 ${m.adapter}`:`${A} \u2192 ${m.adapter||"file"}`;if(r>0&&await E.confirm(`You changed the storage adapter (${T}).
|
|
4
4
|
|
|
5
|
-
Migrate ${
|
|
5
|
+
Migrate ${r} existing ${r===1?"entry":"entries"} to the new storage?`)){const I=await B.collections.migrateStorage($,m);A=m.adapter||"file",E.toast(`Migrated ${I.migrated} of ${I.total} entries.`,{type:"success"})}else await B.collections.update($,{title:l,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{bundled:!1}}),A=m.adapter||"file",E.toast("Collection saved.",{type:"success"})}else await B.collections.update($,{title:l,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{bundled:!1}}),E.toast("Collection saved.",{type:"success"})}catch(r){E.toast(r.message||"Failed to save.",{type:"error"})}finally{f.prop("disabled",!1)}}),Domma.icons.scan()}};
|