domma-cms 0.6.7 → 0.6.9
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/css/admin.css +1 -1
- package/admin/js/app.js +4 -4
- package/admin/js/config/sidebar-config.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +9 -9
- package/admin/js/lib/themes.js +1 -0
- package/admin/js/templates/navigation.html +1 -1
- package/admin/js/templates/page-editor.html +1 -21
- package/admin/js/templates/settings.html +2 -108
- package/admin/js/views/collection-editor.js +3 -3
- package/admin/js/views/login.js +7 -7
- package/admin/js/views/navigation.js +18 -16
- package/admin/js/views/page-editor.js +27 -17
- package/admin/js/views/settings.js +2 -2
- package/config/navigation.json +4 -3
- package/package.json +1 -1
- package/plugins/analytics/stats.json +3 -3
- package/server/services/markdown.js +100 -3
- package/server/services/renderer.js +31 -2
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
`).filter(
|
|
3
|
-
`),
|
|
1
|
+
import{api as z}from"../api.js";const Z=[{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"]),ne=["public","subscriber","editor","manager","admin"],$=["create","read","update","delete"];let u=[],w=null,T=!0,q=null;function ee(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function te(e){return Z.find(t=>t.value===e)?.label||e}function ae(e){const t={...u[e]},n=document.getElementById(`fb-label-${e}`),a=document.getElementById(`fb-name-${e}`),i=document.getElementById(`fb-type-${e}`),p=document.getElementById(`fb-required-${e}`),r=document.getElementById(`fb-placeholder-${e}`),l=document.getElementById(`fb-helper-${e}`);if(n&&(t.label=n.value.trim()||t.label),a&&(t.name=a.value.trim()||t.name),i&&(t.type=i.value||t.type),p&&(t.required=p.checked),r&&(t.placeholder=r.value.trim()),l&&(t.helper=l.value.trim()),Q.has(t.type)){const c=document.getElementById(`fb-options-${e}`);c&&(t.options=c.value.split(`
|
|
2
|
+
`).filter(f=>f.trim()).map(f=>{const[g,...m]=f.split(":");return{value:g.trim(),label:m.join(":").trim()||g.trim()}}))}const o=document.getElementById(`fb-span-${e}`);if(document.getElementById(`fb-fullwidth-${e}`)?.checked)t.fullWidth=!0,delete t.span;else{delete t.fullWidth;const c=parseInt(o?.value,10);c>1?t.span=c:delete t.span}return t}function X(){return u.map((e,t)=>ae(t))}function oe(e,t){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px solid var(--border-color,#333);border-radius:8px;margin-bottom:.75rem;overflow:hidden;";const a=document.createElement("div");a.className="fb-field-header",a.style.cssText="display:flex;align-items:center;gap:.5rem;padding:.6rem .75rem;background:var(--card-header-bg,rgba(255,255,255,.03));cursor:pointer;user-select:none;";const i=document.createElement("span");i.textContent="\u283F",i.style.cssText="cursor:grab;opacity:.4;font-size:1.1rem;flex-shrink:0;",n.draggable=!0,i.addEventListener("mousedown",()=>{n.draggable=!0}),n.addEventListener("dragstart",s=>{q=t,s.dataTransfer.effectAllowed="move",n.style.opacity="0.4"}),n.addEventListener("dragend",()=>{n.style.opacity="",document.querySelectorAll(".fb-field-card").forEach(s=>s.classList.remove("fb-drag-over"))}),n.addEventListener("dragover",s=>{s.preventDefault(),s.dataTransfer.dropEffect="move",document.querySelectorAll(".fb-field-card").forEach(y=>y.classList.remove("fb-drag-over")),n.classList.add("fb-drag-over")}),n.addEventListener("dragleave",()=>{n.classList.remove("fb-drag-over")}),n.addEventListener("drop",s=>{if(s.preventDefault(),n.classList.remove("fb-drag-over"),q===null||q===t)return;u=X();const[y]=u.splice(q,1);u.splice(t,0,y),q=null,P(document.getElementById("fields-list"))});const p=document.createElement("span");p.className="fb-field-summary",p.style.cssText="flex:1;font-weight:500;font-size:.9rem;",p.textContent=e.label||"(Untitled field)";const r=document.createElement("span");r.style.cssText="font-size:.75rem;opacity:.5;",r.textContent=te(e.type);const l=document.createElement("span");l.className="fb-field-chevron",l.textContent="\u25BE",l.style.cssText="opacity:.5;transition:transform .2s;";const o=document.createElement("button");o.type="button",o.textContent="\xD7",o.className="btn btn-sm",o.style.cssText="padding:.15rem .45rem;line-height:1;font-size:1rem;opacity:.6;",o.title="Remove field",o.addEventListener("click",s=>{s.stopPropagation(),u.splice(t,1),P(document.getElementById("fields-list"))}),a.appendChild(i),a.appendChild(p),a.appendChild(r),a.appendChild(l),a.appendChild(o);const d=document.createElement("div");d.className="fb-field-body",d.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 f=document.createElement("div"),g=document.createElement("label");g.className="form-label",g.textContent="Label";const m=document.createElement("input");m.id=`fb-label-${t}`,m.type="text",m.className="form-input",m.value=e.label||"",m.addEventListener("input",()=>{p.textContent=m.value.trim()||"(Untitled field)";const s=document.getElementById(`fb-name-${t}`);s&&!s.dataset.manual&&(s.value=ee(m.value).replace(/-/g,"_"))}),f.appendChild(g),f.appendChild(m);const L=document.createElement("div"),h=document.createElement("label");h.className="form-label",h.textContent="Name (key)";const v=document.createElement("input");v.id=`fb-name-${t}`,v.type="text",v.className="form-input",v.value=e.name||"",v.addEventListener("input",()=>{v.dataset.manual="1"}),L.appendChild(h),L.appendChild(v);const O=document.createElement("div"),U=document.createElement("label");U.className="form-label",U.textContent="Type";const C=document.createElement("select");C.id=`fb-type-${t}`,C.className="form-input",Z.forEach(s=>{const y=document.createElement("option");y.value=s.value,y.textContent=s.label,s.value===e.type&&(y.selected=!0),C.appendChild(y)}),C.addEventListener("change",()=>{r.textContent=te(C.value);const s=d.querySelector(".fb-options-wrap");s&&(s.style.display=Q.has(C.value)?"":"none")}),O.appendChild(U),O.appendChild(C),c.appendChild(f),c.appendChild(L),c.appendChild(O);const N=document.createElement("div");N.style.cssText="display:grid;grid-template-columns:1fr 1fr auto;gap:.6rem;align-items:end;margin-bottom:.6rem;";const _=document.createElement("div"),M=document.createElement("label");M.className="form-label",M.textContent="Placeholder";const I=document.createElement("input");I.id=`fb-placeholder-${t}`,I.type="text",I.className="form-input",I.value=e.placeholder||"",_.appendChild(M),_.appendChild(I);const j=document.createElement("div"),V=document.createElement("label");V.className="form-label",V.textContent="Helper text";const S=document.createElement("input");S.id=`fb-helper-${t}`,S.type="text",S.className="form-input",S.value=e.helper||"",j.appendChild(V),j.appendChild(S);const H=document.createElement("div");H.style.cssText="padding-bottom:.35rem;";const A=document.createElement("label");A.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const W=document.createElement("input");W.id=`fb-required-${t}`,W.type="checkbox",W.checked=!!e.required,A.appendChild(W),A.appendChild(document.createTextNode("Required")),H.appendChild(A),N.appendChild(_),N.appendChild(j),N.appendChild(H);const k=document.createElement("div");k.className="fb-options-wrap",k.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 B=document.createElement("textarea");B.id=`fb-options-${t}`,B.className="form-input",B.rows=4,B.value=(e.options||[]).map(s=>typeof s=="string"?`${s}: ${s}`:`${s.value??""}: ${s.label??s.value??""}`).join(`
|
|
3
|
+
`),k.appendChild(Y),k.appendChild(B);const b=document.createElement("div");b.className="fb-grid-row",b.style.gridTemplateColumns="1fr auto",b.style.gap=".6rem",b.style.alignItems="end",b.style.marginBottom=".6rem",b.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 x=document.createElement("input");x.id=`fb-span-${t}`,x.type="number",x.className="form-input",x.min="1",x.max="6",x.value=e.span>1?String(e.span):"1",G.appendChild(J),G.appendChild(x);const K=document.createElement("div");K.style.cssText="padding-bottom:.35rem;";const F=document.createElement("label");F.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const D=document.createElement("input");return D.id=`fb-fullwidth-${t}`,D.type="checkbox",D.checked=!!e.fullWidth,F.appendChild(D),F.appendChild(document.createTextNode("Full Width")),K.appendChild(F),b.appendChild(G),b.appendChild(K),d.appendChild(c),d.appendChild(N),d.appendChild(k),d.appendChild(b),a.addEventListener("click",()=>{const s=d.style.display!=="none";d.style.display=s?"none":"",l.style.transform=s?"":"rotate(180deg)"}),n.appendChild(a),n.appendChild(d),n}function P(e){if(e){if(e.textContent="",u.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}u.forEach((t,n)=>{e.appendChild(oe(t,n))})}}function se(e,t){t.textContent="",$.forEach(n=>{const a=e?.[n]||{enabled:!1,access:"admin"},i=document.createElement("div");i.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--border-color,#333);";const p=document.createElement("strong");p.textContent=n.charAt(0).toUpperCase()+n.slice(1),p.style.cssText="font-size:.9rem;";const r=document.createElement("label");r.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=!!a.enabled,r.appendChild(l),r.appendChild(document.createTextNode("Enable public access"));const o=document.createElement("select");o.id=`api-${n}-access`,o.className="form-input",ne.forEach(d=>{const c=document.createElement("option");c.value=d,c.textContent=d.charAt(0).toUpperCase()+d.slice(1),d===a.access&&(c.selected=!0),o.appendChild(c)}),i.appendChild(p),i.appendChild(r),i.appendChild(o),t.appendChild(i)})}function de(e,t){E.dropdown("#storage-adapter-trigger",{items:[{label:"File (default)",value:"file"},{label:"MongoDB",value:"mongodb"}],onSelect:({item:a})=>{e.find("#storage-adapter").val(a.value),e.find("#storage-adapter-label").text(a.label);const i=a.value==="mongodb";e.find("#storage-connection-group").toggle(i),e.find("#storage-migration-warning").toggle(i&&!T)}});const n=t.map(a=>({label:a,value:a}));E.dropdown("#storage-connection-trigger",{items:n.length?n:[{label:"default",value:"default"}],onSelect:({item:a})=>{e.find("#storage-connection").val(a.value),e.find("#storage-connection-label").text(a.label)}})}function le(){return(document.getElementById("storage-adapter")?.value||"file")==="mongodb"?{adapter:"mongodb",connection:document.getElementById("storage-connection")?.value||"default"}:{adapter:"file"}}function ce(){const e={};return $.forEach(t=>{const n=document.getElementById(`api-${t}-enabled`)?.checked??!1,a=document.getElementById(`api-${t}-access`)?.value||"admin";e[t]={enabled:n,access:a}}),e}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(e){u=[],w=null,T=!0;const n=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);n&&(w=n[1],T=!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 a=e.find("#fields-list").get(0),i=e.find("#api-access-rows").get(0),p=await z.collections.proStatus();p?.pro&&w!=="roles"&&(e.find("#storage-tab-btn").show(),de(e,p.connections));let r={create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}};if(T){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=ee(l.value))}),o.addEventListener("input",()=>{o.dataset.manual="1"}))}else try{const l=await z.collections.get(w);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-columns-group").get(0).style.display=l.layout==="grid"?"":"none",u=l.fields||[],r=l.api||r,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"))),w==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}P(a),se(r,i),e.find("#add-field-btn").off("click").on("click",()=>{u=X(),u.push({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null}),P(a);const l=a.querySelectorAll(".fb-field-card");if(l.length){const o=l[l.length-1],d=o.querySelector(".fb-field-body"),c=o.querySelector(".fb-field-chevron");d&&(d.style.display=""),c&&(c.style.transform="rotate(180deg)"),o.querySelector(`#fb-label-${u.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(),d=e.find("#field-description").val().trim();if(!l){E.toast("Title is required.",{type:"warning"});return}const c=X(),f=ce(),g=e.find("#collection-layout").val()||"stacked",m=parseInt(e.find("#collection-columns").val(),10)||2,L=e.find("#save-collection-btn");L.prop("disabled",!0);try{if(T){const h=await z.collections.create({title:l,slug:o,description:d,layout:g,columns:m,fields:c,api:f,storage:le()});w=h.slug,T=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${h.slug}`)}else await z.collections.update(w,{title:l,description:d,layout:g,columns:m,fields:c,api:f,storage:le()}),E.toast("Collection saved.",{type:"success"})}catch(h){E.toast(h.message||"Failed to save.",{type:"error"})}finally{L.prop("disabled",!1)}}),Domma.icons.scan()}};
|
package/admin/js/views/login.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<div class="theme-swatch" data-theme="${
|
|
3
|
-
<div class="theme-swatch-preview" style="background:${
|
|
4
|
-
<div class="theme-swatch-accent" style="background:${
|
|
1
|
+
import{api as o,isAuthenticated as u,setAuthData as l}from"../api.js";import{THEMES as p}from"../lib/themes.js";const f={name:{type:"string",required:!0,minLength:2,label:"Full Name",formConfig:{placeholder:"Your name",autocomplete:"name"}},email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"admin@example.com",autocomplete:"email"}},password:{type:"password",required:!0,minLength:8,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}}},h={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}},password:{type:"password",required:!0,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"current-password"}}};export const loginView={templateUrl:"/admin/js/templates/login.html",async onMount(e){if(u()){R.navigate("/");return}const a=window.location.hash,t=a.match(/[?&]token=([a-f0-9]{64})/);if(a.startsWith("#/reset-password")&&t){r(e,"reset"),S(e,t[1]),Domma.icons.scan();return}if(a.startsWith("#/reset-password")){R.navigate("/login");return}let s=!1;try{s=(await o.auth.setupStatus()).needsSetup}catch{E.toast("Could not reach the server.",{type:"error"})}s?(r(e,"setup"),y(e)):(r(e,"login"),k(e)),Domma.icons.scan()}};const g={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}}},b={password:{type:"password",required:!0,minLength:8,label:"New Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}},confirmPassword:{type:"password",required:!0,label:"Confirm Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password"}}},w=["setup","onboarding-site","onboarding-theme","onboarding-done","login","forgot","forgot-success","reset"];function r(e,a){w.forEach(t=>e.find(`#${t}-panel`).hide()),e.find(`#${a}-panel`).show(),Domma.icons.scan()}function d(e,a,t){e.find(`#${a}`).text(t).show()}function y(e){F.render("#setup-form-container",f,{},{layout:"stacked",submitText:"Create admin account",onSubmit:async a=>{try{const t=await o.auth.setup(a);l(t),r(e,"onboarding-site"),v(e)}catch(t){return E.toast(t.message||"Setup failed. Please try again.",{type:"error"}),!1}}})}function v(e){e.find("#ob-site-skip").on("click",a=>{a.preventDefault(),r(e,"onboarding-theme"),c(e),m(e)}),e.find("#ob-site-btn").on("click",async()=>{e.find("#ob-site-error").hide();const a=e.find("#ob-title").val().trim(),t=e.find("#ob-tagline").val().trim(),s=e.find("#ob-site-btn").prop("disabled",!0).text("Saving\u2026");try{const i=await o.settings.get();await o.settings.save({...i,title:a||i.title,tagline:t||i.tagline,seo:{...i.seo||{},defaultTitle:a||i.seo&&i.seo.defaultTitle}});const n=await o.navigation.get();await o.navigation.save({...n,brand:{...n.brand||{},text:a||n.brand.text}}),r(e,"onboarding-theme"),c(e),m(e)}catch(i){d(e,"ob-site-error",i.message||"Could not save site details.")}finally{s.prop("disabled",!1).text("Continue")}})}function c(e){const a=e.find("#theme-grid").empty();p.forEach(t=>{const s=$(`
|
|
2
|
+
<div class="theme-swatch" data-theme="${t.id}" title="${t.id}">
|
|
3
|
+
<div class="theme-swatch-preview" style="background:${t.bg}">
|
|
4
|
+
<div class="theme-swatch-accent" style="background:${t.primary}"></div>
|
|
5
5
|
</div>
|
|
6
6
|
<div class="theme-swatch-label">
|
|
7
|
-
<span>${
|
|
8
|
-
<span class="theme-swatch-mode">${
|
|
7
|
+
<span>${t.label}</span>
|
|
8
|
+
<span class="theme-swatch-mode">${t.dark?"Dark":"Light"}</span>
|
|
9
9
|
</div>
|
|
10
10
|
</div>
|
|
11
|
-
`);
|
|
11
|
+
`);s.on("click",()=>{e.find(".theme-swatch").removeClass("selected"),s.addClass("selected")}),a.append(s)}),e.find('[data-theme="charcoal-dark"]').addClass("selected")}function m(e){e.find("#ob-theme-skip").on("click",a=>{a.preventDefault(),r(e,"onboarding-done")}),e.find("#ob-theme-btn").on("click",async()=>{e.find("#ob-theme-error").hide();const a=e.find(".theme-swatch.selected").attr("data-theme")||"charcoal-dark",t=e.find("#ob-theme-btn").prop("disabled",!0).text("Applying\u2026");try{const s=await o.settings.get();await o.settings.save({...s,theme:a}),r(e,"onboarding-done")}catch(s){d(e,"ob-theme-error",s.message||"Could not save theme.")}finally{t.prop("disabled",!1).text("Apply theme")}})}function k(e){F.render("#login-form-container",h,{},{layout:"stacked",submitText:"Sign in",onSubmit:async a=>{try{const t=await o.auth.login(a);l(t),R.navigate("/")}catch(t){return E.toast(t.message||"Invalid credentials. Please try again.",{type:"error"}),!1}}}),e.find("#forgot-link").on("click",a=>{a.preventDefault(),r(e,"forgot"),C(e)})}function C(e){F.render("#forgot-form-container",g,{},{layout:"stacked",submitText:"Send reset link",onSubmit:async t=>{try{await o.auth.forgotPassword(t.email)}catch{}r(e,"forgot-success"),Domma.icons.scan()}});const a=t=>{t.preventDefault(),r(e,"login")};e.find("#forgot-back-link").on("click",a),e.find("#forgot-success-back-link").on("click",a)}function S(e,a){F.render("#reset-form-container",b,{},{layout:"stacked",submitText:"Set new password",onSubmit:async t=>{if(t.password!==t.confirmPassword)return E.toast("Passwords do not match.",{type:"error"}),!1;try{await o.auth.resetPassword(a,t.password),E.toast("Password updated. Please sign in.",{type:"success"}),window.location.hash="#/login"}catch(s){return E.toast(s.message||"Link expired or invalid.",{type:"error"}),!1}}})}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<div class="nav-item-row${
|
|
3
|
-
<span class="nav-col-indent">${
|
|
4
|
-
<input type="text" class="form-input item-text nav-col-main" value="${
|
|
5
|
-
<input type="text" class="form-input item-url nav-col-main" value="${
|
|
6
|
-
<input type="text" class="form-input item-icon nav-col-icon" value="${
|
|
7
|
-
<select class="form-select item-parent nav-col-parent">${
|
|
1
|
+
import{api as v}from"../api.js";let b=1;const f=e=>String(e||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<");function g(e){const i=[];return(e||[]).forEach(s=>{const l=b++;i.push({_id:l,text:s.text||"",url:s.url||"",icon:s.icon||"",hidden:s.hidden||!1,parentId:null}),(s.items||s.children||[]).forEach(o=>{i.push({_id:b++,text:o.text||"",url:o.url||"",icon:o.icon||"",hidden:o.hidden||!1,parentId:l})})}),i}function k(e){return e.filter(i=>i.parentId===null).map(i=>{const s=e.filter(o=>o.parentId===i._id).map(o=>({text:o.text,url:o.url,...o.icon&&{icon:o.icon},...o.hidden&&{hidden:!0}})),l={text:i.text,url:i.url,...i.icon&&{icon:i.icon},...i.hidden&&{hidden:!0}};return s.length&&(l.items=s),l})}function I(e){const i=[];return e.filter(s=>s.parentId===null).forEach(s=>{i.push(s),e.filter(l=>l.parentId===s._id).forEach(l=>i.push(l))}),i}export const navigationView={templateUrl:"/admin/js/templates/navigation.html",async onMount(e){let[i,s]=await Promise.all([v.navigation.get().catch(()=>({brand:{},items:[]})),v.settings.get().catch(()=>({}))]),l=g(i.items),o=(s.footer?.links||[]).map(d=>({text:d.text||"",url:d.url||"",...d.hidden&&{hidden:!0}}));const p=()=>{e.find("#nav-items-list .nav-item-row").each(function(){const t=parseInt($(this).data("id"),10),n=l.find(a=>a._id===t);if(!n)return;n.text=$(this).find(".item-text").val(),n.url=$(this).find(".item-url").val(),n.icon=$(this).find(".item-icon").val(),n.hidden=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true";const r=$(this).find(".item-parent").val();n.parentId=r?parseInt(r,10):null});const d=new Set(l.filter(t=>t.parentId!==null).map(t=>t._id));l.forEach(t=>{t.parentId!==null&&d.has(t.parentId)&&(t.parentId=null)})},c=()=>{const d=e.find("#nav-items-list").empty(),t=l.filter(n=>n.parentId===null);I(l).forEach(n=>{const r=n.parentId!==null,a=n.hidden===!0,x='<option value="">\u2014 top-level \u2014</option>'+t.filter(h=>h._id!==n._id).map(h=>`<option value="${h._id}"${h._id===n.parentId?" selected":""}>${f(h.text)||"(untitled)"}</option>`).join("");d.append(`
|
|
2
|
+
<div class="nav-item-row${r?" nav-item-row--child":""}${a?" nav-item-row--hidden":""}" data-id="${n._id}">
|
|
3
|
+
<span class="nav-col-indent">${r?"\u21B3":""}</span>
|
|
4
|
+
<input type="text" class="form-input item-text nav-col-main" value="${f(n.text)}" placeholder="Label">
|
|
5
|
+
<input type="text" class="form-input item-url nav-col-main" value="${f(n.url)}" placeholder="/url">
|
|
6
|
+
<input type="text" class="form-input item-icon nav-col-icon" value="${f(n.icon)}" placeholder="icon">
|
|
7
|
+
<select class="form-select item-parent nav-col-parent">${x}</select>
|
|
8
8
|
<span class="nav-col-action">
|
|
9
|
+
<button class="btn btn-sm btn-ghost btn-toggle-hidden${a?" active":""}" data-id="${n._id}" data-hidden="${a}" data-tooltip="${a?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
|
|
9
10
|
<button class="btn btn-sm btn-danger btn-remove-item" data-id="${n._id}" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
10
11
|
</span>
|
|
11
12
|
</div>
|
|
12
|
-
`)}),Domma.icons.scan("#nav-items-list"),document.querySelectorAll("#nav-items-list [data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
</
|
|
20
|
-
|
|
13
|
+
`)}),Domma.icons.scan("#nav-items-list"),document.querySelectorAll("#nav-items-list [data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})},u=()=>{const d=e.find("#footer-links-list").empty();o.forEach((t,n)=>{const r=t.hidden===!0;d.append(`
|
|
14
|
+
<div class="nav-item-row${r?" nav-item-row--hidden":""}" data-footer-idx="${n}">
|
|
15
|
+
<input type="text" class="form-input footer-link-text nav-col-main" value="${f(t.text)}" placeholder="Label">
|
|
16
|
+
<input type="text" class="form-input footer-link-url nav-col-main" value="${f(t.url)}" placeholder="/url">
|
|
17
|
+
<span class="nav-col-action">
|
|
18
|
+
<button class="btn btn-sm btn-ghost btn-toggle-hidden${r?" active":""}" data-idx="${n}" data-hidden="${r}" data-tooltip="${r?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
|
|
19
|
+
<button class="btn btn-sm btn-danger btn-remove-footer" data-idx="${n}" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
20
|
+
</span>
|
|
21
|
+
</div>
|
|
22
|
+
`)}),Domma.icons.scan("#footer-links-list"),document.querySelectorAll("#footer-links-list [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})},m=()=>{o=[],e.find("#footer-links-list .nav-item-row").each(function(){const d=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true",t={text:$(this).find(".footer-link-text").val().trim(),url:$(this).find(".footer-link-url").val().trim()};d&&(t.hidden=!0),o.push(t)})};e.find("#add-footer-link").on("click",()=>{m(),o.push({text:"",url:"",hidden:!1}),u()}),e.off("click",".btn-remove-footer").on("click",".btn-remove-footer",function(){m();const d=parseInt($(this).data("idx"),10);o.splice(d,1),u()}),e.find("#field-brand-text").val(i.brand?.text||""),e.find("#field-brand-url").val(i.brand?.url||"/"),e.find("#field-brand-icon").val(i.brand?.icon||""),e.find("#field-nav-variant").val(i.variant||"dark"),c(),u(),e.find("#add-nav-item").on("click",()=>{p(),l.push({_id:b++,text:"",url:"",icon:"",hidden:!1,parentId:null}),c()}),e.off("click",".btn-remove-item").on("click",".btn-remove-item",function(){p();const d=parseInt($(this).data("id"),10);l=l.filter(t=>t._id!==d&&t.parentId!==d),c()}),e.off("click","#nav-items-list .btn-toggle-hidden").on("click","#nav-items-list .btn-toggle-hidden",function(d){d.stopPropagation(),p();const t=parseInt($(this).data("id"),10),n=l.find(r=>r._id===t);n&&(n.hidden=!n.hidden),c()}),e.off("click","#footer-links-list .btn-toggle-hidden").on("click","#footer-links-list .btn-toggle-hidden",function(d){d.stopPropagation(),m();const t=parseInt($(this).data("idx"),10);o[t]&&(o[t].hidden=!o[t].hidden),u()}),e.off("change",".item-parent").on("change",".item-parent",function(){p(),c()}),e.find("#save-nav-btn").on("click",async()=>{p(),m();const d=k(l.map(a=>({...a,text:a.text.trim(),url:a.url.trim(),icon:a.icon.trim()}))).filter(a=>a.text||a.url),t=e.find("#field-brand-icon").val().trim(),n={brand:{text:e.find("#field-brand-text").val().trim(),url:e.find("#field-brand-url").val().trim()||"/",...t&&{icon:t}},items:d,variant:e.find("#field-nav-variant").val(),position:i.position||"sticky"},r=o.filter(a=>a.text||a.url);try{await v.navigation.save(n),i=n,l=g(i.items),c(),E.toast("Navigation saved.",{type:"success"})}catch(a){console.error("[navigation] save failed:",a),E.toast("Failed to save navigation.",{type:"error"});return}try{const a=await v.settings.get().catch(()=>({}));await v.settings.save({...a,footer:{...a.footer||{},links:r}}),o=r,u()}catch(a){console.error("[navigation] footer links save failed:",a),E.toast("Footer links could not be saved.",{type:"warning"})}})}};
|