domma-cms 0.69.4 → 0.70.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/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/lib/shortcode-context-menu.js +1 -1
- package/admin/js/views/analytics.js +1 -1
- package/admin/js/views/contacts.js +1 -1
- package/admin/js/views/context-menu-editor.js +1 -1
- package/admin/js/views/notes.js +1 -1
- package/admin/js/views/todo.js +1 -1
- package/package.json +2 -2
- package/plugins/_lib/admin/mail/reader-view.js +2 -0
- package/plugins/blog/CLAUDE.md +23 -2
- package/plugins/blog/admin/css/index.css +28 -0
- package/plugins/blog/admin/templates/post-editor.html +25 -9
- package/plugins/blog/admin/views/blog.js +2 -0
- package/plugins/blog/admin/views/categories.js +1 -0
- package/plugins/blog/admin/views/comments.js +1 -0
- package/plugins/blog/admin/views/kit.js +1 -1
- package/plugins/blog/admin/views/post-editor.js +228 -45
- package/plugins/blog/blocks/blog-post-essay.css +29 -0
- package/plugins/blog/blocks/blog-post-essay.html +28 -0
- package/plugins/blog/blocks/blog-post-split.css +22 -0
- package/plugins/blog/blocks/blog-post-split.html +32 -0
- package/plugins/blog/lib/layouts.js +4 -2
- package/plugins/blog/lib/nav-link.js +72 -0
- package/plugins/blog/plugin.js +40 -1
- package/plugins/blog/plugin.json +6 -6
- package/plugins/blog/plugin.public.js +1 -1
- package/plugins/blog/tests/layouts.test.js +1 -1
- package/plugins/blog/tests/nav-link.test.js +57 -0
- package/plugins/free-tier.lock.json +21 -15
- package/public/js/collection-context.js +2 -2
- package/public/js/context-menus.js +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Domma Tools CSS v0.44.
|
|
2
|
+
* Domma Tools CSS v0.44.1
|
|
3
3
|
* Dynamic Object Manipulation & Modeling API
|
|
4
4
|
* (c) 2026 Darryl Waterhouse & DCBW-IT
|
|
5
|
-
* Built: 2026-09-
|
|
6
|
-
* Commit:
|
|
5
|
+
* Built: 2026-09-23T16:21:30.188Z
|
|
6
|
+
* Commit: eca0079
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/*!
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Domma Tools v0.44.
|
|
2
|
+
* Domma Tools v0.44.1
|
|
3
3
|
* Developer tools: Theme Roller & Page Roller
|
|
4
4
|
* (c) 2026 Darryl Waterhouse & DCBW-IT
|
|
5
|
-
* Built: 2026-09-
|
|
6
|
-
* Commit:
|
|
5
|
+
* Built: 2026-09-23T16:21:21.035Z
|
|
6
|
+
* Commit: eca0079
|
|
7
7
|
*
|
|
8
8
|
* Requires: domma.min.js
|
|
9
9
|
*/
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{SHORTCODES as $}from"./shortcode-registry.js";import{EFFECTS as C}from"./effect-defs.js";import{boolState as V,boolValue as G,findEnclosingShortcode as M,setAttr as N,setAttrs as X,effectAttrKeys as q}from"./shortcode-tags.js";import{insertAtCursor as J}from"./markdown-toolbar.js";import{clearLookupCache as Q,lookupOptions as Z}from"./shortcode-lookups.js";import{insertMenuGroups as ee,insertShortcodeByName as te}from"./shortcode-insert.js";import{openShortcodeForm as R}from"./shortcode-form.js";function ne(e){return Z(e,c?.span?.attrs||{})}const oe=$.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),se=new Map($.map(e=>[e.name,e])),ie=new Map(C.map(e=>[e.name,e])),ce=C.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),z=[...oe,...ce],D=new Set(["card","hero","banner","col","button"]);let c=null;const P="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",p="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",S="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",H="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",ae="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function m(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e){return typeof Domma>"u"||!Domma.elements?.contextMenu?(e.addEventListener("contextmenu",n=>{n.preventDefault(),f();const t=M(e,z);F(e,t,n.clientX,n.clientY)}),null):Domma.elements.contextMenu(e,{inherit:!1,render:n=>{f();const t=M(e,z);F(e,t,n.x,n.y)}})}function f(){c&&(v(),c.root.remove(),document.removeEventListener("mousedown",W,!0),document.removeEventListener("keydown",j,!0),window.removeEventListener("scroll",Y,!0),c=null)}const W=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&f()},Y=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&f()},j=e=>{e.key==="Escape"&&f()};function F(e,n,t,o){Q();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=P,c={root:s,textarea:e,span:n,submenu:null};const i=n&&(se.get(n.name)||ie.get(n.name)),l=n&&D.has(n.name)?C.filter(r=>r.name in n.attrs):[];i&&re(s,i);for(const r of l)xe(s,r);(i||l.length)&&s.appendChild(le()),s.appendChild(y("Add effect\u2026",()=>be())),s.appendChild(y("Insert\u2026",()=>ye()));const a=e.selectionStart!==e.selectionEnd;(n||a)&&s.appendChild(y(a?"Wrap selection in\u2026":"Wrap in\u2026",()=>Ee())),document.body.appendChild(s),U(s,t,o),document.addEventListener("mousedown",W,!0),document.addEventListener("keydown",j,!0),window.addEventListener("scroll",Y,!0),window.I&&I.scan(s)}function le(){const e=document.createElement("div");return e.style.cssText=ae,e}function k(e){const n=document.createElement("div");return n.style.cssText=S,n.textContent=e,n}function re(e,n){e.appendChild(k(`${n.label} options`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="edit" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>',t.appendChild(document.createTextNode(`Edit ${n.label}\u2026`)),t.addEventListener("click",()=>{const{textarea:o,span:s}=c;f(),R(o,n,{span:s})}),e.appendChild(t);for(const o of n.attrs||[])e.appendChild(O(o))}function O(e,n=e.name){const t=c.span.attrs[n];switch(e.type){case"checkbox":return de(e,n,V(t,e.default));case"select":return me(e,n,t);case"lookup":return he(e,n,t);case"colour":return pe(e,n,t);case"icon":return fe(e,n,t);default:return ue(e,n,t)}}function b(e,n){c.span=N(c.textarea,c.span,e,n)}function de(e,n,t){const o=m(document.createElement("label"));o.style.cssText=p;const s=document.createElement("input");return s.type="checkbox",s.checked=t,s.addEventListener("change",()=>b(n,G(e,s.checked))),o.appendChild(s),o.appendChild(document.createTextNode(e.label)),o}function ue(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const i=document.createElement("input");i.type=e.type==="number"?"number":"text",i.value=t===!0||t==null?"":t,e.default!=null&&e.default!==""&&(i.placeholder=String(e.default)),i.style.cssText=H;let l=null;return i.addEventListener("input",()=>{clearTimeout(l),l=setTimeout(()=>b(n,i.value.trim()),180)}),i.addEventListener("mousedown",a=>a.stopPropagation()),o.appendChild(s),o.appendChild(i),o}function pe(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.flex="1";const i=document.createElement("input");return i.type="color",typeof t=="string"&&/^#/.test(t)?i.value=t:typeof e.default=="string"&&/^#/.test(e.default)&&(i.value=e.default),i.addEventListener("input",()=>b(n,i.value)),o.appendChild(s),o.appendChild(i),o}function me(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();for(const[i,l]of e.options||[]){const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent=l,i===o&&(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,i),v()}),s.appendChild(a)}return s})}function fe(e,n,t){const o=t==null||t===!0?"":String(t),s=o?`${e.label}: ${o}`:e.label;return y(s,()=>{const i=g();i.style.width="232px";const l=document.createElement("input");l.type="text",l.placeholder="Search icons\u2026",l.style.cssText=H+"margin:6px;width:calc(100% - 12px);",l.addEventListener("mousedown",T=>T.stopPropagation()),i.appendChild(l);const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent="- none -",o||(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,null),v()}),i.appendChild(a);const r=document.createElement("div");r.style.cssText="display:flex;flex-wrap:wrap;gap:2px;padding:4px 6px;max-height:220px;overflow-y:auto;",i.appendChild(r);const u=96,w=()=>{const T=typeof I<"u"&&typeof I.list=="function"?I.list():[],A=l.value.trim().toLowerCase(),_=A?T.filter(d=>d.toLowerCase().includes(A)):T;r.replaceChildren();for(const d of _.slice(0,u)){const x=m(document.createElement("button"));x.type="button",x.title=d,x.style.cssText="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:4px;cursor:pointer;color:inherit;",d===o&&(x.style.borderColor="var(--dm-primary,#5b8cff)");const L=document.createElement("span");L.setAttribute("data-icon",d),L.setAttribute("data-icon-size","15"),L.style.cssText="display:inline-flex;align-items:center;",x.appendChild(L),x.addEventListener("click",()=>{b(n,d),v()}),r.appendChild(x)}if(_.length>u){const d=document.createElement("div");d.style.cssText=S,d.textContent=`${_.length-u} more - refine search`,r.appendChild(d)}if(!_.length){const d=document.createElement("div");d.style.cssText=S,d.textContent="No icons match",r.appendChild(d)}window.I&&I.scan(r)};let h=null;return l.addEventListener("input",()=>{clearTimeout(h),h=setTimeout(w,120)}),w(),setTimeout(()=>l.focus(),0),i})}function he(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();s.style.minHeight="48px";const i=typeof E<"u"&&E.loader?E.loader(s,{type:"dots"}):null;return ne(e.source).then(l=>{if(i&&i.destroy(),!c||c.submenu!==s)return;if(s.style.minHeight="",!l.length){const u=document.createElement("div");u.style.cssText=S,u.textContent="Nothing found",s.appendChild(u)}const a=l.some(([u])=>u===o),r=[["","- none -"],...l];o!==""&&!a&&r.push([o,`${o} (missing)`]);for(const[u,w]of r){const h=m(document.createElement("button"));h.type="button",h.style.cssText=p,h.textContent=w,u===o&&(h.style.fontWeight="700"),h.addEventListener("click",()=>{b(n,u===""?null:u),v()}),s.appendChild(h)}}),s})}function xe(e,n){e.appendChild(k(`Effect: ${n.label}`));for(const o of n.attrs||[])e.appendChild(O(o,`${n.name}-${o.name}`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',t.addEventListener("click",()=>{const o={};for(const s of q(c.span.attrs,n.name))o[s]=null;X(c.textarea,c.span,o),f()}),e.appendChild(t)}function be(){const e=g(),n=c.span,t=n&&D.has(n.name)?n.name:null;for(const o of[...new Set(C.map(s=>s.category))]){const s=C.filter(i=>i.category===o);if(s.length){e.appendChild(k(o));for(const i of s){const l=t&&(i.hosts||[]).includes(t)&&!(i.name in n.attrs),a=m(document.createElement("button"));if(a.type="button",a.style.cssText=p+"justify-content:space-between;",a.appendChild(document.createTextNode(i.label)),l){const r=document.createElement("span");r.textContent=`inject \u2192 [${t}]`,r.style.cssText="opacity:.6;font-size:.7rem;",a.appendChild(r)}a.addEventListener("click",()=>{if(l)N(c.textarea,c.span,i.name,!0);else{const r=i.selfClosing?`[${i.name} /]`:`[${i.name}]
|
|
1
|
+
import{SHORTCODES as $}from"./shortcode-registry.js";import{EFFECTS as C}from"./effect-defs.js";import{boolState as V,boolValue as G,findEnclosingShortcode as M,setAttr as N,setAttrs as X,effectAttrKeys as q}from"./shortcode-tags.js";import{insertAtCursor as J}from"./markdown-toolbar.js";import{clearLookupCache as Q,lookupOptions as Z}from"./shortcode-lookups.js";import{insertMenuGroups as ee,insertShortcodeByName as te}from"./shortcode-insert.js";import{openShortcodeForm as R}from"./shortcode-form.js";function ne(e){return Z(e,c?.span?.attrs||{})}const oe=$.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),se=new Map($.map(e=>[e.name,e])),ie=new Map(C.map(e=>[e.name,e])),ce=C.map(e=>({name:e.name,selfClosing:!!e.selfClosing})),z=[...oe,...ce],D=new Set(["card","hero","banner","col","button"]);let c=null;const P="position:fixed;z-index:10000;min-width:210px;max-height:70vh;overflow-y:auto;background:var(--dm-surface,#1c1c30);border:1px solid var(--dm-border,#2a2a40);border-radius:6px;box-shadow:var(--dm-shadow-lg);padding:.3rem;font-size:.83rem;color:var(--dm-text,#e5e5e5);",p="display:flex;align-items:center;gap:.5rem;width:100%;text-align:left;padding:.35rem .55rem;border:none;background:transparent;color:inherit;border-radius:4px;cursor:pointer;font-size:.83rem;",S="padding:.3rem .55rem;color:var(--dm-text-muted,#8a8a9a);font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;",H="flex:1;min-width:0;padding:.2rem .4rem;background:var(--dm-input-bg,#12121e);border:1px solid var(--dm-border,#333);border-radius:3px;color:var(--dm-text,#eee);font-size:.8rem;",ae="height:1px;background:var(--dm-border,#2a2a40);margin:.3rem .2rem;";function m(e){return e.addEventListener("mouseenter",()=>{e.style.background="var(--dm-primary,#4338ca)",e.style.color="var(--dm-primary-text,#fff)"}),e.addEventListener("mouseleave",()=>{e.style.background="transparent",e.style.color="inherit"}),e}export function attachContextMenu(e){return typeof Domma>"u"||!Domma.elements?.contextMenu?(e.addEventListener("contextmenu",n=>{n.preventDefault(),f();const t=M(e,z);F(e,t,n.clientX,n.clientY)}),null):Domma.elements.contextMenu(e,{inherit:!1,cursor:!1,render:n=>{f();const t=M(e,z);F(e,t,n.x,n.y)}})}function f(){c&&(v(),c.root.remove(),document.removeEventListener("mousedown",W,!0),document.removeEventListener("keydown",j,!0),window.removeEventListener("scroll",Y,!0),c=null)}const W=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&f()},Y=e=>{c&&!c.root.contains(e.target)&&!(c.submenu&&c.submenu.contains(e.target))&&f()},j=e=>{e.key==="Escape"&&f()};function F(e,n,t,o){Q();const s=document.createElement("div");s.className="dm-sc-menu",s.style.cssText=P,c={root:s,textarea:e,span:n,submenu:null};const i=n&&(se.get(n.name)||ie.get(n.name)),l=n&&D.has(n.name)?C.filter(r=>r.name in n.attrs):[];i&&re(s,i);for(const r of l)xe(s,r);(i||l.length)&&s.appendChild(le()),s.appendChild(y("Add effect\u2026",()=>be())),s.appendChild(y("Insert\u2026",()=>ye()));const a=e.selectionStart!==e.selectionEnd;(n||a)&&s.appendChild(y(a?"Wrap selection in\u2026":"Wrap in\u2026",()=>Ee())),document.body.appendChild(s),U(s,t,o),document.addEventListener("mousedown",W,!0),document.addEventListener("keydown",j,!0),window.addEventListener("scroll",Y,!0),window.I&&I.scan(s)}function le(){const e=document.createElement("div");return e.style.cssText=ae,e}function k(e){const n=document.createElement("div");return n.style.cssText=S,n.textContent=e,n}function re(e,n){e.appendChild(k(`${n.label} options`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="edit" data-icon-size="13" style="display:inline-flex;align-items:center;"></span>',t.appendChild(document.createTextNode(`Edit ${n.label}\u2026`)),t.addEventListener("click",()=>{const{textarea:o,span:s}=c;f(),R(o,n,{span:s})}),e.appendChild(t);for(const o of n.attrs||[])e.appendChild(O(o))}function O(e,n=e.name){const t=c.span.attrs[n];switch(e.type){case"checkbox":return de(e,n,V(t,e.default));case"select":return me(e,n,t);case"lookup":return he(e,n,t);case"colour":return pe(e,n,t);case"icon":return fe(e,n,t);default:return ue(e,n,t)}}function b(e,n){c.span=N(c.textarea,c.span,e,n)}function de(e,n,t){const o=m(document.createElement("label"));o.style.cssText=p;const s=document.createElement("input");return s.type="checkbox",s.checked=t,s.addEventListener("change",()=>b(n,G(e,s.checked))),o.appendChild(s),o.appendChild(document.createTextNode(e.label)),o}function ue(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.cssText="white-space:nowrap;color:var(--dm-text-muted,#9a9aac);";const i=document.createElement("input");i.type=e.type==="number"?"number":"text",i.value=t===!0||t==null?"":t,e.default!=null&&e.default!==""&&(i.placeholder=String(e.default)),i.style.cssText=H;let l=null;return i.addEventListener("input",()=>{clearTimeout(l),l=setTimeout(()=>b(n,i.value.trim()),180)}),i.addEventListener("mousedown",a=>a.stopPropagation()),o.appendChild(s),o.appendChild(i),o}function pe(e,n,t){const o=document.createElement("div");o.style.cssText=p+"cursor:default;";const s=document.createElement("span");s.textContent=e.label,s.style.flex="1";const i=document.createElement("input");return i.type="color",typeof t=="string"&&/^#/.test(t)?i.value=t:typeof e.default=="string"&&/^#/.test(e.default)&&(i.value=e.default),i.addEventListener("input",()=>b(n,i.value)),o.appendChild(s),o.appendChild(i),o}function me(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();for(const[i,l]of e.options||[]){const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent=l,i===o&&(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,i),v()}),s.appendChild(a)}return s})}function fe(e,n,t){const o=t==null||t===!0?"":String(t),s=o?`${e.label}: ${o}`:e.label;return y(s,()=>{const i=g();i.style.width="232px";const l=document.createElement("input");l.type="text",l.placeholder="Search icons\u2026",l.style.cssText=H+"margin:6px;width:calc(100% - 12px);",l.addEventListener("mousedown",T=>T.stopPropagation()),i.appendChild(l);const a=m(document.createElement("button"));a.type="button",a.style.cssText=p,a.textContent="- none -",o||(a.style.fontWeight="700"),a.addEventListener("click",()=>{b(n,null),v()}),i.appendChild(a);const r=document.createElement("div");r.style.cssText="display:flex;flex-wrap:wrap;gap:2px;padding:4px 6px;max-height:220px;overflow-y:auto;",i.appendChild(r);const u=96,w=()=>{const T=typeof I<"u"&&typeof I.list=="function"?I.list():[],A=l.value.trim().toLowerCase(),_=A?T.filter(d=>d.toLowerCase().includes(A)):T;r.replaceChildren();for(const d of _.slice(0,u)){const x=m(document.createElement("button"));x.type="button",x.title=d,x.style.cssText="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid transparent;border-radius:4px;cursor:pointer;color:inherit;",d===o&&(x.style.borderColor="var(--dm-primary,#5b8cff)");const L=document.createElement("span");L.setAttribute("data-icon",d),L.setAttribute("data-icon-size","15"),L.style.cssText="display:inline-flex;align-items:center;",x.appendChild(L),x.addEventListener("click",()=>{b(n,d),v()}),r.appendChild(x)}if(_.length>u){const d=document.createElement("div");d.style.cssText=S,d.textContent=`${_.length-u} more - refine search`,r.appendChild(d)}if(!_.length){const d=document.createElement("div");d.style.cssText=S,d.textContent="No icons match",r.appendChild(d)}window.I&&I.scan(r)};let h=null;return l.addEventListener("input",()=>{clearTimeout(h),h=setTimeout(w,120)}),w(),setTimeout(()=>l.focus(),0),i})}function he(e,n,t){const o=t==null||t===!0?e.default??"":t;return y(`${e.label}${o!==""?`: ${o}`:""}`,()=>{const s=g();s.style.minHeight="48px";const i=typeof E<"u"&&E.loader?E.loader(s,{type:"dots"}):null;return ne(e.source).then(l=>{if(i&&i.destroy(),!c||c.submenu!==s)return;if(s.style.minHeight="",!l.length){const u=document.createElement("div");u.style.cssText=S,u.textContent="Nothing found",s.appendChild(u)}const a=l.some(([u])=>u===o),r=[["","- none -"],...l];o!==""&&!a&&r.push([o,`${o} (missing)`]);for(const[u,w]of r){const h=m(document.createElement("button"));h.type="button",h.style.cssText=p,h.textContent=w,u===o&&(h.style.fontWeight="700"),h.addEventListener("click",()=>{b(n,u===""?null:u),v()}),s.appendChild(h)}}),s})}function xe(e,n){e.appendChild(k(`Effect: ${n.label}`));for(const o of n.attrs||[])e.appendChild(O(o,`${n.name}-${o.name}`));const t=m(document.createElement("button"));t.type="button",t.style.cssText=p,t.innerHTML='<span data-icon="trash" data-icon-size="13" style="display:inline-flex;align-items:center;"></span> Remove effect',t.addEventListener("click",()=>{const o={};for(const s of q(c.span.attrs,n.name))o[s]=null;X(c.textarea,c.span,o),f()}),e.appendChild(t)}function be(){const e=g(),n=c.span,t=n&&D.has(n.name)?n.name:null;for(const o of[...new Set(C.map(s=>s.category))]){const s=C.filter(i=>i.category===o);if(s.length){e.appendChild(k(o));for(const i of s){const l=t&&(i.hosts||[]).includes(t)&&!(i.name in n.attrs),a=m(document.createElement("button"));if(a.type="button",a.style.cssText=p+"justify-content:space-between;",a.appendChild(document.createTextNode(i.label)),l){const r=document.createElement("span");r.textContent=`inject \u2192 [${t}]`,r.style.cssText="opacity:.6;font-size:.7rem;",a.appendChild(r)}a.addEventListener("click",()=>{if(l)N(c.textarea,c.span,i.name,!0);else{const r=i.selfClosing?`[${i.name} /]`:`[${i.name}]
|
|
2
2
|
Content
|
|
3
3
|
[/${i.name}]`;J(c.textarea,r)}f()}),e.appendChild(a)}}}return e}function y(e,n){const t=m(document.createElement("button"));t.type="button",t.style.cssText=p+"justify-content:space-between;",t.appendChild(document.createTextNode(e));const o=document.createElement("span");return o.setAttribute("data-icon","chevron-right"),o.setAttribute("data-icon-size","12"),o.style.cssText="opacity:.6;display:inline-flex;align-items:center;flex-shrink:0;",t.appendChild(o),t.addEventListener("mouseenter",()=>B(t,n)),t.addEventListener("click",s=>{s.stopPropagation(),B(t,n)}),t}function g(){const e=document.createElement("div");return e.className="dm-sc-submenu",e.style.cssText=P,e}function B(e,n){v();const t=n();c.submenu=t,document.body.appendChild(t);const o=e.getBoundingClientRect();U(t,o.right+2,o.top,o.left-2),window.I&&I.scan(t)}function v(){c&&c.submenu&&(c.submenu.remove(),c.submenu=null)}function K(e){const n=m(document.createElement("button"));if(n.type="button",n.style.cssText=p,e.icon){const t=document.createElement("span");t.setAttribute("data-icon",e.icon),t.setAttribute("data-icon-size","14"),t.style.cssText="opacity:.75;display:inline-flex;align-items:center;flex-shrink:0;",n.appendChild(t)}return n.appendChild(document.createTextNode(e.label)),n}function ye(){const e=g();for(const{category:n,items:t}of ee()){e.appendChild(k(n));for(const o of t){const s=K(o);s.addEventListener("click",()=>{const i=c.textarea;f(),te(o.name,i)}),e.appendChild(s)}}return e}function Ee(){const e=g();for(const n of $.filter(t=>t.wrappable||t.container)){const t=K(n);t.addEventListener("click",()=>{const o=c.textarea;f(),R(o,n)}),e.appendChild(t)}return e}function U(e,n,t,o){e.style.left="0px",e.style.top="0px";const s=e.getBoundingClientRect();let i=n;n+s.width>window.innerWidth-8&&(i=o!=null?o-s.width:n-s.width);let l=t;t+s.height>window.innerHeight-8&&(l=Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.max(8,i)}px`,e.style.top=`${Math.max(8,l)}px`}
|
|
@@ -59,4 +59,4 @@ import{apiRequest as v,getToken as Z,refreshAccessToken as Q}from"../api.js";imp
|
|
|
59
59
|
<span class="spacer"></span>
|
|
60
60
|
<button type="button" class="btn btn-sm btn-primary" data-on-click="done">Done</button>
|
|
61
61
|
</div>
|
|
62
|
-
</div>`),n=(o,K=T=>T)=>M.computed({read:()=>t.value[o],write:T=>W(o,K(T))}),p={range:n("range"),limit:n("limit",Number),chart:n("chart"),live:n("live"),confirmReset:n("confirmReset")};e.element.appendChild(a);const m=V(a),f=M.applyBindings(p,a,{methods:{done:()=>e.close(),reset:()=>{t.value={...oe}}}});ee(e,()=>{f.dispose(),m()}),e.open()}X(r,{id:"analytics-settings",icon:"settings",title:"Analytics settings",onClick:J});const h=[...s.querySelectorAll(".an-list")];h[0].innerHTML=ve,h[1].innerHTML=A,h[2].innerHTML=A,h[3].innerHTML=A,h[4].innerHTML=be,l.add(V(s,{position:"bottom"}));const z=M.applyBindings(G,s,{methods:{refresh:()=>{w(),C()},showPages:()=>{d.value="pages"},showJourneys:()=>{d.value="journeys"},showSpikes:()=>{d.value="spikes"}}});l.add(()=>z.dispose());const N=e=>new URL(e,location.origin).href,Y=E.contextMenu(s,{match:".an-row",inherit:!1,density:"compact",items:e=>{const a=e.dataset.url;return[{label:"Open on the site",icon:"external-link",action:()=>window.open(N(a),"_blank","noopener")},{label:"Copy address",icon:"copy",action:async()=>{try{await navigator.clipboard.writeText(N(a)),E.toast("Address copied",{type:"success"})}catch{E.toast("The browser would not allow copying",{type:"error"})}}},{label:"Show only this page",icon:"filter",action:()=>{d.value="pages",u.value=a}}]}});l.add(()=>Y?.destroy?.());const F=E.contextMenu(s.querySelector(".an-bar-end"),{match:".an-more",inherit:!1,density:"compact",items:()=>[{label:`Export ${B[c.peek()].toLowerCase()} as CSV`,icon:"download",action:async()=>{try{await ye(c.peek())}catch{E.toast("Export failed.",{type:"error"})}}},{type:"divider"},{label:"Reset all statistics\u2026",icon:"trash",danger:!0,action:async()=>{if(!(t.peek().confirmReset&&!await E.confirm("Delete every analytics counter for this site? This cannot be undone.")))try{await v("/analytics/stats",{method:"DELETE"}),E.toast("Analytics reset.",{type:"success"}),w()}catch{E.toast("Reset failed.",{type:"error"})}}}]});l.add(()=>F?.destroy?.()),s.addEventListener("click",e=>{const a=e.target.closest(".an-kebab, .an-more");a&&te(a)}),I.scan(s);try{await he()}catch{E.toast("The chart library could not be loaded; figures still work.",{type:"warning"})}await Promise.all([w(),C()])}};
|
|
62
|
+
</div>`),n=(o,K=T=>T)=>M.computed({read:()=>t.value[o],write:T=>W(o,K(T))}),p={range:n("range"),limit:n("limit",Number),chart:n("chart"),live:n("live"),confirmReset:n("confirmReset")};e.element.appendChild(a);const m=V(a),f=M.applyBindings(p,a,{methods:{done:()=>e.close(),reset:()=>{t.value={...oe}}}});ee(e,()=>{f.dispose(),m()}),e.open()}X(r,{id:"analytics-settings",icon:"settings",title:"Analytics settings",onClick:J});const h=[...s.querySelectorAll(".an-list")];h[0].innerHTML=ve,h[1].innerHTML=A,h[2].innerHTML=A,h[3].innerHTML=A,h[4].innerHTML=be,l.add(V(s,{position:"bottom"}));const z=M.applyBindings(G,s,{methods:{refresh:()=>{w(),C()},showPages:()=>{d.value="pages"},showJourneys:()=>{d.value="journeys"},showSpikes:()=>{d.value="spikes"}}});l.add(()=>z.dispose());const N=e=>new URL(e,location.origin).href,Y=E.contextMenu(s,{cursor:"glyph",match:".an-row",inherit:!1,density:"compact",items:e=>{const a=e.dataset.url;return[{label:"Open on the site",icon:"external-link",action:()=>window.open(N(a),"_blank","noopener")},{label:"Copy address",icon:"copy",action:async()=>{try{await navigator.clipboard.writeText(N(a)),E.toast("Address copied",{type:"success"})}catch{E.toast("The browser would not allow copying",{type:"error"})}}},{label:"Show only this page",icon:"filter",action:()=>{d.value="pages",u.value=a}}]}});l.add(()=>Y?.destroy?.());const F=E.contextMenu(s.querySelector(".an-bar-end"),{cursor:"glyph",match:".an-more",inherit:!1,density:"compact",items:()=>[{label:`Export ${B[c.peek()].toLowerCase()} as CSV`,icon:"download",action:async()=>{try{await ye(c.peek())}catch{E.toast("Export failed.",{type:"error"})}}},{type:"divider"},{label:"Reset all statistics\u2026",icon:"trash",danger:!0,action:async()=>{if(!(t.peek().confirmReset&&!await E.confirm("Delete every analytics counter for this site? This cannot be undone.")))try{await v("/analytics/stats",{method:"DELETE"}),E.toast("Analytics reset.",{type:"success"}),w()}catch{E.toast("Reset failed.",{type:"error"})}}}]});l.add(()=>F?.destroy?.()),s.addEventListener("click",e=>{const a=e.target.closest(".an-kebab, .an-more");a&&te(a)}),I.scan(s);try{await he()}catch{E.toast("The chart library could not be loaded; figures still work.",{type:"warning"})}await Promise.all([w(),C()])}};
|
|
@@ -171,4 +171,4 @@ import{apiRequest as Ue}from"../api.js";import{addBannerAction as Fe}from"../lib
|
|
|
171
171
|
<div class="ct-groups-rows" data-each="list.value key=name"></div>
|
|
172
172
|
</div>
|
|
173
173
|
<p class="text-muted" data-if="noGroups.value">No groups yet.</p>
|
|
174
|
-
</div>`),a=M.observable(""),s={draft:a,list:M.observable([])},u=M.effect(()=>{const d=ne.value.groups;s.list.value=b.value.map(v=>({name:v,countText:A(d[v]??0,"contact")}))});s.hasGroups=M.computed(()=>s.list.value.length>0),s.noGroups=M.computed(()=>s.list.value.length===0),s.cannotAdd=M.computed(()=>!a.value.trim());async function l(){const d=a.peek().trim();d&&await U(d)&&(a.value="",E.toast("Group added.",{type:"success"}))}e.addEventListener("click",async d=>{const v=d.target.closest("[data-act]"),C=v?.closest("[data-group]")?.dataset.group;if(!(!v||!C)){if(v.dataset.act==="show")j.value={kind:"group",value:C},t.close();else if(v.dataset.act==="rename"){const R=String(await E.prompt("Rename the group to:",{inputValue:C})??"").trim();if(!R||R===C)return;try{await h(`/contacts/groups/${encodeURIComponent(C)}`,"PUT",{newName:R}),await W(),E.toast("Group renamed.",{type:"success"})}catch(_){E.toast(_?.message||"Failed to rename the group.",{type:"error"})}}else if(v.dataset.act==="delete"){if(!await E.confirm("Delete this group? Its contacts stay - they are just no longer in it."))return;try{await h(`/contacts/groups/${encodeURIComponent(C)}`,"DELETE"),await W(),E.toast("Group deleted.",{type:"success"})}catch{E.toast("Failed to delete the group.",{type:"error"})}}}}),t.element.appendChild(e);const $=J(e);e.querySelector(".ct-groups-rows").innerHTML=Xe;const n=M.applyBindings(s,e,{methods:{add:()=>(l(),!1)}});Z(t,()=>{u?.(),n.dispose(),$()}),t.open()}Fe(x,{id:"contacts-settings",icon:"settings",title:"Contacts settings",onClick:Be}),r.querySelector(".ct-rows").innerHTML=_e,r.querySelector(".ct-chips").innerHTML=Ve;const O=r.querySelector(".ct-list"),pe=r.querySelector(".ct-bar"),Ge=r.querySelector(".ct-chips-row"),ue=r.querySelector(".ct-bulk");k.add(J(r,{position:"bottom"}));const Oe=M.applyBindings(Se,r,{methods:{create:()=>F(null),bulkDelete:Ae,clearSelection:()=>{i.value=new Set}}});k.add(()=>Oe.dispose()),k.add(M.effect(()=>{const t=m.value;O.classList.toggle("is-compact",t.density==="compact");const e={company:!t.showCompany,email:!t.showEmail,phone:!t.showPhone,groups:!t.showGroups};for(const[s,u]of Object.entries(e))O.classList.toggle(`hide-${s}`,u);const a=["1.1rem","1.5rem","minmax(10rem, 1.4fr)"];e.company||a.push("minmax(0, 1fr)"),e.email||a.push("minmax(0, 1.2fr)"),e.phone||a.push("minmax(0, .9fr)"),e.groups||a.push("minmax(0, 1fr)"),a.push("1.75rem"),O.style.setProperty("--ct-cols",a.join(" "))}));const De=E.contextMenu(O,{match:".ct-row",inherit:!1,density:"compact",items:t=>{const e=t.dataset.id,a=N(e);if(!a)return[];const s=he(a),u=ge(a),l=a.groups||[];return(n=>n.filter(d=>d.visible!==!1))([{label:"Edit\u2026",icon:"edit",action:()=>F(e)},{type:"divider"},{label:`Email ${s}`,icon:"mail",visible:!!s,action:()=>{location.href=`mailto:${encodeURIComponent(s).replace(/%40/g,"@")}`}},{label:`Call ${u}`,icon:"phone",visible:!!u,action:()=>{location.href=`tel:${u.replace(/[^\d+]/g,"")}`}},{label:"Copy email",icon:"copy",visible:!!s,action:()=>ie(s,"Email")},{label:"Copy phone",icon:"copy",visible:!!u,action:()=>ie(u,"Phone")},{type:"divider",visible:!!(s||u)},{label:a.favourite===!0?"Unstar":"Star",icon:"star",action:()=>q(e,{favourite:a.favourite!==!0})},{label:"Groups",icon:"folder",submenu:[...b.peek().map(n=>({label:`${l.includes(n)?"\u2713 ":""}${n}`,action:()=>Te(e,n,!l.includes(n))})),...b.peek().length?[{type:"divider"}]:[],{label:"New group\u2026",action:()=>re([e])}]},{label:"Duplicate",icon:"copy",action:()=>xe(e)},{label:i.peek().has(e)?"Deselect":"Select",icon:"check-square",action:()=>de(e)},{type:"divider"},{label:"Delete",icon:"trash",danger:!0,action:()=>le(e)}])}});k.add(()=>De?.destroy?.());const Re=E.contextMenu(pe,{match:".ct-more",inherit:!1,density:"compact",items:()=>[{label:"Manage groups\u2026",icon:"folder",action:Pe},{type:"divider"},{label:"Export all (JSON)",icon:"download",action:Le},{label:"Import from file\u2026",icon:"upload",action:()=>Y.click()},{type:"divider"},{label:"Select all shown",icon:"check-square",disabled:!S.peek().length,action:()=>{i.value=new Set(S.peek().map(t=>t.id))}},{label:"Clear selection",icon:"x",disabled:!i.peek().size,action:()=>{i.value=new Set}}]});k.add(()=>Re?.destroy?.());const Ie=E.contextMenu(ue,{match:".ct-bulk-group",inherit:!1,density:"compact",items:()=>[...b.peek().map(t=>({label:t,action:()=>Ne(t)})),...b.peek().length?[{type:"divider"}]:[],{label:"New group\u2026",action:()=>re([...i.peek()])}]});k.add(()=>Ie?.destroy?.()),pe.addEventListener("click",t=>{const e=t.target.closest(".ct-more");e&&ee(e)}),ue.addEventListener("click",t=>{const e=t.target.closest(".ct-bulk-group");e&&ee(e)}),Ge.addEventListener("click",t=>{const e=t.target.closest(".ct-chip");if(!e)return;const a=e.dataset.chip;j.value=a==="fav"?{kind:"fav"}:a.startsWith("group:")?{kind:"group",value:a.slice(6)}:{kind:"all"}}),O.addEventListener("click",t=>{if(t.target.closest(".ct-select-all"))return;const e=t.target.closest(".ct-row");if(!e)return;const a=e.dataset.id;if(t.target.closest(".ct-kebab"))return ee(t.target.closest(".ct-kebab"));if(!t.target.closest(".ct-select")){if(t.target.closest(".ct-fav")){const s=N(a);s&&q(a,{favourite:s.favourite!==!0});return}F(a)}}),O.addEventListener("change",t=>{if(t.target.matches(".ct-select")&&de(t.target.closest(".ct-row").dataset.id),t.target.matches(".ct-select-all")){const e=S.peek().map(a=>a.id);i.value=t.target.checked?new Set(e):new Set}}),O.addEventListener("keydown",t=>{const e=t.target.closest?.(".ct-row");e&&t.key==="Enter"&&t.target===e&&F(e.dataset.id)});const me=t=>{t.ctrlKey||t.metaKey||t.altKey||t.target.closest?.("input, textarea, select, [contenteditable], .dm-slideover")||(t.key==="/"?(t.preventDefault(),se.value=!0):(t.key==="n"||t.key==="N")&&(t.preventDefault(),F(null)))};document.addEventListener("keydown",me),k.add(()=>document.removeEventListener("keydown",me)),I.scan(r),await W()}};
|
|
174
|
+
</div>`),a=M.observable(""),s={draft:a,list:M.observable([])},u=M.effect(()=>{const d=ne.value.groups;s.list.value=b.value.map(v=>({name:v,countText:A(d[v]??0,"contact")}))});s.hasGroups=M.computed(()=>s.list.value.length>0),s.noGroups=M.computed(()=>s.list.value.length===0),s.cannotAdd=M.computed(()=>!a.value.trim());async function l(){const d=a.peek().trim();d&&await U(d)&&(a.value="",E.toast("Group added.",{type:"success"}))}e.addEventListener("click",async d=>{const v=d.target.closest("[data-act]"),C=v?.closest("[data-group]")?.dataset.group;if(!(!v||!C)){if(v.dataset.act==="show")j.value={kind:"group",value:C},t.close();else if(v.dataset.act==="rename"){const R=String(await E.prompt("Rename the group to:",{inputValue:C})??"").trim();if(!R||R===C)return;try{await h(`/contacts/groups/${encodeURIComponent(C)}`,"PUT",{newName:R}),await W(),E.toast("Group renamed.",{type:"success"})}catch(_){E.toast(_?.message||"Failed to rename the group.",{type:"error"})}}else if(v.dataset.act==="delete"){if(!await E.confirm("Delete this group? Its contacts stay - they are just no longer in it."))return;try{await h(`/contacts/groups/${encodeURIComponent(C)}`,"DELETE"),await W(),E.toast("Group deleted.",{type:"success"})}catch{E.toast("Failed to delete the group.",{type:"error"})}}}}),t.element.appendChild(e);const $=J(e);e.querySelector(".ct-groups-rows").innerHTML=Xe;const n=M.applyBindings(s,e,{methods:{add:()=>(l(),!1)}});Z(t,()=>{u?.(),n.dispose(),$()}),t.open()}Fe(x,{id:"contacts-settings",icon:"settings",title:"Contacts settings",onClick:Be}),r.querySelector(".ct-rows").innerHTML=_e,r.querySelector(".ct-chips").innerHTML=Ve;const O=r.querySelector(".ct-list"),pe=r.querySelector(".ct-bar"),Ge=r.querySelector(".ct-chips-row"),ue=r.querySelector(".ct-bulk");k.add(J(r,{position:"bottom"}));const Oe=M.applyBindings(Se,r,{methods:{create:()=>F(null),bulkDelete:Ae,clearSelection:()=>{i.value=new Set}}});k.add(()=>Oe.dispose()),k.add(M.effect(()=>{const t=m.value;O.classList.toggle("is-compact",t.density==="compact");const e={company:!t.showCompany,email:!t.showEmail,phone:!t.showPhone,groups:!t.showGroups};for(const[s,u]of Object.entries(e))O.classList.toggle(`hide-${s}`,u);const a=["1.1rem","1.5rem","minmax(10rem, 1.4fr)"];e.company||a.push("minmax(0, 1fr)"),e.email||a.push("minmax(0, 1.2fr)"),e.phone||a.push("minmax(0, .9fr)"),e.groups||a.push("minmax(0, 1fr)"),a.push("1.75rem"),O.style.setProperty("--ct-cols",a.join(" "))}));const De=E.contextMenu(O,{cursor:"glyph",match:".ct-row",inherit:!1,density:"compact",items:t=>{const e=t.dataset.id,a=N(e);if(!a)return[];const s=he(a),u=ge(a),l=a.groups||[];return(n=>n.filter(d=>d.visible!==!1))([{label:"Edit\u2026",icon:"edit",action:()=>F(e)},{type:"divider"},{label:`Email ${s}`,icon:"mail",visible:!!s,action:()=>{location.href=`mailto:${encodeURIComponent(s).replace(/%40/g,"@")}`}},{label:`Call ${u}`,icon:"phone",visible:!!u,action:()=>{location.href=`tel:${u.replace(/[^\d+]/g,"")}`}},{label:"Copy email",icon:"copy",visible:!!s,action:()=>ie(s,"Email")},{label:"Copy phone",icon:"copy",visible:!!u,action:()=>ie(u,"Phone")},{type:"divider",visible:!!(s||u)},{label:a.favourite===!0?"Unstar":"Star",icon:"star",action:()=>q(e,{favourite:a.favourite!==!0})},{label:"Groups",icon:"folder",submenu:[...b.peek().map(n=>({label:`${l.includes(n)?"\u2713 ":""}${n}`,action:()=>Te(e,n,!l.includes(n))})),...b.peek().length?[{type:"divider"}]:[],{label:"New group\u2026",action:()=>re([e])}]},{label:"Duplicate",icon:"copy",action:()=>xe(e)},{label:i.peek().has(e)?"Deselect":"Select",icon:"check-square",action:()=>de(e)},{type:"divider"},{label:"Delete",icon:"trash",danger:!0,action:()=>le(e)}])}});k.add(()=>De?.destroy?.());const Re=E.contextMenu(pe,{cursor:"glyph",match:".ct-more",inherit:!1,density:"compact",items:()=>[{label:"Manage groups\u2026",icon:"folder",action:Pe},{type:"divider"},{label:"Export all (JSON)",icon:"download",action:Le},{label:"Import from file\u2026",icon:"upload",action:()=>Y.click()},{type:"divider"},{label:"Select all shown",icon:"check-square",disabled:!S.peek().length,action:()=>{i.value=new Set(S.peek().map(t=>t.id))}},{label:"Clear selection",icon:"x",disabled:!i.peek().size,action:()=>{i.value=new Set}}]});k.add(()=>Re?.destroy?.());const Ie=E.contextMenu(ue,{cursor:"glyph",match:".ct-bulk-group",inherit:!1,density:"compact",items:()=>[...b.peek().map(t=>({label:t,action:()=>Ne(t)})),...b.peek().length?[{type:"divider"}]:[],{label:"New group\u2026",action:()=>re([...i.peek()])}]});k.add(()=>Ie?.destroy?.()),pe.addEventListener("click",t=>{const e=t.target.closest(".ct-more");e&&ee(e)}),ue.addEventListener("click",t=>{const e=t.target.closest(".ct-bulk-group");e&&ee(e)}),Ge.addEventListener("click",t=>{const e=t.target.closest(".ct-chip");if(!e)return;const a=e.dataset.chip;j.value=a==="fav"?{kind:"fav"}:a.startsWith("group:")?{kind:"group",value:a.slice(6)}:{kind:"all"}}),O.addEventListener("click",t=>{if(t.target.closest(".ct-select-all"))return;const e=t.target.closest(".ct-row");if(!e)return;const a=e.dataset.id;if(t.target.closest(".ct-kebab"))return ee(t.target.closest(".ct-kebab"));if(!t.target.closest(".ct-select")){if(t.target.closest(".ct-fav")){const s=N(a);s&&q(a,{favourite:s.favourite!==!0});return}F(a)}}),O.addEventListener("change",t=>{if(t.target.matches(".ct-select")&&de(t.target.closest(".ct-row").dataset.id),t.target.matches(".ct-select-all")){const e=S.peek().map(a=>a.id);i.value=t.target.checked?new Set(e):new Set}}),O.addEventListener("keydown",t=>{const e=t.target.closest?.(".ct-row");e&&t.key==="Enter"&&t.target===e&&F(e.dataset.id)});const me=t=>{t.ctrlKey||t.metaKey||t.altKey||t.target.closest?.("input, textarea, select, [contenteditable], .dm-slideover")||(t.key==="/"?(t.preventDefault(),se.value=!0):(t.key==="n"||t.key==="N")&&(t.preventDefault(),F(null)))};document.addEventListener("keydown",me),k.add(()=>document.removeEventListener("keydown",me)),I.scan(r),await W()}};
|
|
@@ -52,4 +52,4 @@ import{api as v}from"../api.js";import{makeIconInput as B}from"/admin/js/lib/sho
|
|
|
52
52
|
<button class="btn btn-sm btn-ghost ctx-item-down" data-i="${i}" ${i===a.length-1?"disabled":""}>Down</button>
|
|
53
53
|
<button class="btn btn-sm btn-ghost ctx-item-remove" data-i="${i}">Remove</button>
|
|
54
54
|
</div>
|
|
55
|
-
</div>`;const w=r.querySelector(".ctx-icon-slot");if(w){const l=B("edit",e.icon||"");l.input.className+=" ctx-item-icon",l.input.dataset.i=String(i),w.appendChild(l.el)}const N=r.querySelector(".ctx-url-slot");if(N){const l=L(o.placeholder,e.action?.url||"");l.input.className+=" ctx-item-arg",l.input.dataset.i=String(i),l.input.dataset.key="url",N.appendChild(l.el)}t.appendChild(r)}),Domma.icons.scan("#ctx-items"),p()}c.on("change",".ctx-item-type",function(){const t=Number(this.dataset.i);a[t].type=this.value,this.value!=="item"?delete a[t].action:a[t].action||(a[t].action={type:"navigate"}),u()}),c.on("input",".ctx-item-text",function(){a[Number(this.dataset.i)].text=this.value,p()}),c.on("input",".ctx-item-icon",function(){a[Number(this.dataset.i)].icon=this.value,p()}),c.on("change",".ctx-item-action",function(){const t=Number(this.dataset.i);a[t].action={type:this.value},u()}),c.on("input",".ctx-item-arg",function(){const t=Number(this.dataset.i);a[t].action=a[t].action||{},a[t].action[this.dataset.key]=this.value,p()}),c.on("change",".ctx-item-danger",function(){a[Number(this.dataset.i)].danger=this.checked,p()}),c.on("click",".ctx-item-remove",function(){a.splice(Number(this.dataset.i),1),u()}),c.on("click",".ctx-item-up",function(){const t=Number(this.dataset.i);[a[t-1],a[t]]=[a[t],a[t-1]],u()}),c.on("click",".ctx-item-down",function(){const t=Number(this.dataset.i);[a[t+1],a[t]]=[a[t],a[t+1]],u()}),$("#ctx-add-item").on("click",()=>{a.push({type:"item",text:"New item",action:{type:"navigate",url:""}}),u()});function k(){const t={},s=$("#ctx-accent").val(),e=$("#ctx-transition").val(),i=$("#ctx-density").val(),n=$("#ctx-radius").val(),o=$("#ctx-shadow").val(),r=$("#ctx-opacity").val();return s&&(t.accent=s),e&&(t.transition=e),i&&(t.density=i),n&&(t.radius=n),o&&(t.shadow=o),r!==""&&r!=null&&(t.opacity=Number(r)),t}function A(){const t=$("#ctx-inherit").val(),s=j($("#ctx-pages").val()),e=j($("#ctx-projects").val()),i={slug:$("#ctx-slug").val().trim(),name:$("#ctx-name").val().trim(),enabled:$("#ctx-enabled").is(":checked"),target:{selector:$("#ctx-selector").val().trim(),match:$("#ctx-match").val().trim(),exclude:$("#ctx-exclude").val().trim()},inherit:t==="false"?!1:t,items:a},n=k();return Object.keys(n).length&&(i.theme=n),(s.length||e.length)&&(i.binding={},s.length&&(i.binding.pages=s),e.length&&(i.binding.projects=e)),i}E.tooltip&&E.tooltip(".ctx-help",{trigger:"click",position:"right"});function p(){b();const t=a.map(e=>{const i=e.type||"item";return i==="divider"?{type:"divider"}:i==="header"?{type:"header",header:e.text||""}:{label:e.text||"(no text)",icon:e.icon||void 0,danger:!!e.danger}});if(!t.some(e=>e.label))return;const s=k();g=E.contextMenu("#ctx-preview",{items:t,inherit:!1,accent:s.accent,transition:s.transition,density:s.density,radius:s.radius,shadow:s.shadow,opacity:s.opacity})}c.on("change","#ctx-accent, #ctx-transition, #ctx-density, #ctx-radius, #ctx-shadow",p),c.on("input","#ctx-opacity",p);function h(t){const s=document.getElementById("ctx-errors");if(!t){s.style.display="none";return}s.style.display="",s.innerHTML=String(t).split("; ").map(e=>`<div>${d(e)}</div>`).join(""),s.scrollIntoView({behavior:"smooth",block:"nearest"})}$("#ctx-save").on("click",async function(){h(null);const t=A();if(!t.slug)return h("Slug is required");if(!t.name)return h("Name is required");if(!t.target.selector)return h("Container selector is required");this.disabled=!0;try{x?(await v.contextMenus.create(t),E.toast("Context menu created.",{type:"success"}),location.hash="#/context-menus"):(await v.contextMenus.update(y,t),E.toast("Context menu saved.",{type:"success"}))}catch(s){h(s.message||String(s))}finally{this.disabled=!1}}),u(),Domma.icons.scan(c.get(0))},onUnmount(){E.contextMenu&&E.contextMenu.closeAll&&E.contextMenu.closeAll(),b()}};
|
|
55
|
+
</div>`;const w=r.querySelector(".ctx-icon-slot");if(w){const l=B("edit",e.icon||"");l.input.className+=" ctx-item-icon",l.input.dataset.i=String(i),w.appendChild(l.el)}const N=r.querySelector(".ctx-url-slot");if(N){const l=L(o.placeholder,e.action?.url||"");l.input.className+=" ctx-item-arg",l.input.dataset.i=String(i),l.input.dataset.key="url",N.appendChild(l.el)}t.appendChild(r)}),Domma.icons.scan("#ctx-items"),p()}c.on("change",".ctx-item-type",function(){const t=Number(this.dataset.i);a[t].type=this.value,this.value!=="item"?delete a[t].action:a[t].action||(a[t].action={type:"navigate"}),u()}),c.on("input",".ctx-item-text",function(){a[Number(this.dataset.i)].text=this.value,p()}),c.on("input",".ctx-item-icon",function(){a[Number(this.dataset.i)].icon=this.value,p()}),c.on("change",".ctx-item-action",function(){const t=Number(this.dataset.i);a[t].action={type:this.value},u()}),c.on("input",".ctx-item-arg",function(){const t=Number(this.dataset.i);a[t].action=a[t].action||{},a[t].action[this.dataset.key]=this.value,p()}),c.on("change",".ctx-item-danger",function(){a[Number(this.dataset.i)].danger=this.checked,p()}),c.on("click",".ctx-item-remove",function(){a.splice(Number(this.dataset.i),1),u()}),c.on("click",".ctx-item-up",function(){const t=Number(this.dataset.i);[a[t-1],a[t]]=[a[t],a[t-1]],u()}),c.on("click",".ctx-item-down",function(){const t=Number(this.dataset.i);[a[t+1],a[t]]=[a[t],a[t+1]],u()}),$("#ctx-add-item").on("click",()=>{a.push({type:"item",text:"New item",action:{type:"navigate",url:""}}),u()});function k(){const t={},s=$("#ctx-accent").val(),e=$("#ctx-transition").val(),i=$("#ctx-density").val(),n=$("#ctx-radius").val(),o=$("#ctx-shadow").val(),r=$("#ctx-opacity").val();return s&&(t.accent=s),e&&(t.transition=e),i&&(t.density=i),n&&(t.radius=n),o&&(t.shadow=o),r!==""&&r!=null&&(t.opacity=Number(r)),t}function A(){const t=$("#ctx-inherit").val(),s=j($("#ctx-pages").val()),e=j($("#ctx-projects").val()),i={slug:$("#ctx-slug").val().trim(),name:$("#ctx-name").val().trim(),enabled:$("#ctx-enabled").is(":checked"),target:{selector:$("#ctx-selector").val().trim(),match:$("#ctx-match").val().trim(),exclude:$("#ctx-exclude").val().trim()},inherit:t==="false"?!1:t,items:a},n=k();return Object.keys(n).length&&(i.theme=n),(s.length||e.length)&&(i.binding={},s.length&&(i.binding.pages=s),e.length&&(i.binding.projects=e)),i}E.tooltip&&E.tooltip(".ctx-help",{trigger:"click",position:"right"});function p(){b();const t=a.map(e=>{const i=e.type||"item";return i==="divider"?{type:"divider"}:i==="header"?{type:"header",header:e.text||""}:{label:e.text||"(no text)",icon:e.icon||void 0,danger:!!e.danger}});if(!t.some(e=>e.label))return;const s=k();g=E.contextMenu("#ctx-preview",{cursor:"glyph",items:t,inherit:!1,accent:s.accent,transition:s.transition,density:s.density,radius:s.radius,shadow:s.shadow,opacity:s.opacity})}c.on("change","#ctx-accent, #ctx-transition, #ctx-density, #ctx-radius, #ctx-shadow",p),c.on("input","#ctx-opacity",p);function h(t){const s=document.getElementById("ctx-errors");if(!t){s.style.display="none";return}s.style.display="",s.innerHTML=String(t).split("; ").map(e=>`<div>${d(e)}</div>`).join(""),s.scrollIntoView({behavior:"smooth",block:"nearest"})}$("#ctx-save").on("click",async function(){h(null);const t=A();if(!t.slug)return h("Slug is required");if(!t.name)return h("Name is required");if(!t.target.selector)return h("Container selector is required");this.disabled=!0;try{x?(await v.contextMenus.create(t),E.toast("Context menu created.",{type:"success"}),location.hash="#/context-menus"):(await v.contextMenus.update(y,t),E.toast("Context menu saved.",{type:"success"}))}catch(s){h(s.message||String(s))}finally{this.disabled=!1}}),u(),Domma.icons.scan(c.get(0))},onUnmount(){E.contextMenu&&E.contextMenu.closeAll&&E.contextMenu.closeAll(),b()}};
|
package/admin/js/views/notes.js
CHANGED
|
@@ -101,4 +101,4 @@ import{apiRequest as te}from"../api.js";import{addBannerAction as ae}from"../lib
|
|
|
101
101
|
<span class="spacer"></span>
|
|
102
102
|
<button type="button" class="btn btn-sm btn-primary" data-on-click="done">Done</button>
|
|
103
103
|
</div>
|
|
104
|
-
</div>`),a=(c,f=w=>w)=>M.computed({read:()=>o.value[c],write:w=>x(c,f(w))}),s={layout:a("layout"),sort:a("sort"),previewLength:a("previewLength",Number),showCategories:a("showCategories"),confirmDelete:a("confirmDelete"),previewText:M.computed(()=>o.value.previewLength?`${o.value.previewLength} ch`:"Off")};e.element.appendChild(t);let n;const g=M.applyBindings(s,t,{methods:{done:()=>e.close(),reset:()=>{o.value={...re},n?.setValue([])}}});n=E.pillbox(t.querySelector(".notes-set-defaults"),{data:k.peek().map(c=>({value:c,label:c})),value:o.peek().defaultCategories,creatable:!0,placeholder:"None",onChange:()=>x("defaultCategories",n.getValue())});const p=S(t);B(e,()=>{g.dispose(),p(),n.destroy?.()}),e.open()}ae(v,{id:"notes-settings",icon:"settings",title:"Notes settings",onClick:X});const N=i.querySelector(".notes-list"),Z=E.contextMenu(N,{match:".note-card",inherit:!1,density:"compact",items:e=>{const t=e.dataset.id,s=b(t)?.categories||[];return[{label:"Edit",icon:"edit",action:()=>h(b(t))},{label:"Duplicate",icon:"copy",action:()=>G(t)},{label:"Copy text",icon:"clipboard",action:()=>J(t)},{label:"Show only\u2026",icon:"filter",disabled:s.length===0,submenu:s.map(n=>({label:n,icon:"tag",action:()=>{u.value=n}}))},{type:"divider"},{label:"Delete",icon:"trash",danger:!0,action:()=>q(t)}]}});d.add(()=>Z?.destroy?.()),N.addEventListener("click",e=>{const t=e.target.closest(".note-card");if(!t)return;const a=e.target.closest(".note-cat");if(a){u.value=a.dataset.cat;return}const s=e.target.closest(".note-kebab");if(s){se(s);return}h(b(t.dataset.id))}),N.addEventListener("keydown",e=>{const t=e.target.closest?.(".note-card");t&&e.key==="Enter"&&e.target===t&&h(b(t.dataset.id))});const F=e=>{e.ctrlKey||e.metaKey||e.altKey||e.target.closest?.("input, textarea, select, [contenteditable], .dm-slideover")||(e.key==="/"?(e.preventDefault(),A.value=!0):(e.key==="n"||e.key==="N")&&(e.preventDefault(),h(null)))};document.addEventListener("keydown",F),d.add(()=>document.removeEventListener("keydown",F)),d.add(()=>y?.close()),I.scan(i),await _()}};
|
|
104
|
+
</div>`),a=(c,f=w=>w)=>M.computed({read:()=>o.value[c],write:w=>x(c,f(w))}),s={layout:a("layout"),sort:a("sort"),previewLength:a("previewLength",Number),showCategories:a("showCategories"),confirmDelete:a("confirmDelete"),previewText:M.computed(()=>o.value.previewLength?`${o.value.previewLength} ch`:"Off")};e.element.appendChild(t);let n;const g=M.applyBindings(s,t,{methods:{done:()=>e.close(),reset:()=>{o.value={...re},n?.setValue([])}}});n=E.pillbox(t.querySelector(".notes-set-defaults"),{data:k.peek().map(c=>({value:c,label:c})),value:o.peek().defaultCategories,creatable:!0,placeholder:"None",onChange:()=>x("defaultCategories",n.getValue())});const p=S(t);B(e,()=>{g.dispose(),p(),n.destroy?.()}),e.open()}ae(v,{id:"notes-settings",icon:"settings",title:"Notes settings",onClick:X});const N=i.querySelector(".notes-list"),Z=E.contextMenu(N,{cursor:"glyph",match:".note-card",inherit:!1,density:"compact",items:e=>{const t=e.dataset.id,s=b(t)?.categories||[];return[{label:"Edit",icon:"edit",action:()=>h(b(t))},{label:"Duplicate",icon:"copy",action:()=>G(t)},{label:"Copy text",icon:"clipboard",action:()=>J(t)},{label:"Show only\u2026",icon:"filter",disabled:s.length===0,submenu:s.map(n=>({label:n,icon:"tag",action:()=>{u.value=n}}))},{type:"divider"},{label:"Delete",icon:"trash",danger:!0,action:()=>q(t)}]}});d.add(()=>Z?.destroy?.()),N.addEventListener("click",e=>{const t=e.target.closest(".note-card");if(!t)return;const a=e.target.closest(".note-cat");if(a){u.value=a.dataset.cat;return}const s=e.target.closest(".note-kebab");if(s){se(s);return}h(b(t.dataset.id))}),N.addEventListener("keydown",e=>{const t=e.target.closest?.(".note-card");t&&e.key==="Enter"&&e.target===t&&h(b(t.dataset.id))});const F=e=>{e.ctrlKey||e.metaKey||e.altKey||e.target.closest?.("input, textarea, select, [contenteditable], .dm-slideover")||(e.key==="/"?(e.preventDefault(),A.value=!0):(e.key==="n"||e.key==="N")&&(e.preventDefault(),h(null)))};document.addEventListener("keydown",F),d.add(()=>document.removeEventListener("keydown",F)),d.add(()=>y?.close()),I.scan(i),await _()}};
|
package/admin/js/views/todo.js
CHANGED
|
@@ -93,4 +93,4 @@ import{apiRequest as at}from"../api.js";import{addBannerAction as st}from"../lib
|
|
|
93
93
|
<span class="spacer"></span>
|
|
94
94
|
<button type="button" class="btn btn-sm btn-primary" data-on-click="done">Done</button>
|
|
95
95
|
</div>
|
|
96
|
-
</div>`),s=r=>M.computed({read:()=>n.value[r],write:a=>_(r,a)}),i=Object.fromEntries(Object.keys(H).map(r=>[r,s(r)]));e.element.appendChild(o);const h=O(o),$=M.applyBindings(i,o,{methods:{done:()=>e.close(),reset:()=>{n.value={...H}}}});j(e,()=>{$.dispose(),h()}),e.open()}st(b,{id:"todo-settings",icon:"settings",title:"Todo settings",onClick:tt}),d.querySelector(".todo-rows").innerHTML=vt,p.add(O(d,{position:"bottom"}));const et=M.applyBindings(W,d,{methods:{add:X,clearCompleted:Z}});p.add(()=>et.dispose());const L=d.querySelector(".todo-list"),ot=E.contextMenu(L,{match:".todo-row",inherit:!1,density:"compact",items:e=>{const t=e.dataset.id,o=C(t);return o?[{label:"Edit\u2026",icon:"edit",action:()=>Y(t)},{label:o.status==="completed"?"Reopen":"Complete",icon:"check",action:()=>F(t)},{type:"divider"},{label:"Status",icon:"circle",submenu:B.map(s=>({label:u[s],disabled:o.status===s,action:()=>c(t,{status:s})}))},{label:"Priority",icon:"flag",submenu:P.map(s=>({label:u[s],disabled:o.priority===s,action:()=>c(t,{priority:s})}))},{label:"Due",icon:"calendar",submenu:[{label:"Today",action:()=>c(t,{dueAt:k(0)})},{label:"Tomorrow",action:()=>c(t,{dueAt:k(1)})},{label:"Next week",action:()=>c(t,{dueAt:k(7)})},{type:"divider"},{label:"No due date",disabled:!o.dueAt,action:()=>c(t,{dueAt:null})}]},{type:"divider"},{label:"Delete",icon:"trash",danger:!0,action:()=>q(t)}]:[]}});p.add(()=>ot?.destroy?.()),L.addEventListener("click",e=>{const t=e.target.closest(".todo-row");if(t){if(e.target.closest(".todo-kebab, .todo-pill"))return lt(e.target.closest(".todo-kebab, .todo-pill"));e.target.closest(".todo-text")&&Y(t.dataset.id)}}),L.addEventListener("change",e=>{e.target.matches(".todo-check")&&F(e.target.closest(".todo-row").dataset.id)});const R=e=>{e.ctrlKey||e.metaKey||e.altKey||e.target.closest?.("input, textarea, select, [contenteditable], .dm-slideover")||(e.key==="/"?(e.preventDefault(),N.value=!0):(e.key==="n"||e.key==="N")&&(e.preventDefault(),A.value=!0))};document.addEventListener("keydown",R),p.add(()=>document.removeEventListener("keydown",R)),I.scan(d),await V()}};
|
|
96
|
+
</div>`),s=r=>M.computed({read:()=>n.value[r],write:a=>_(r,a)}),i=Object.fromEntries(Object.keys(H).map(r=>[r,s(r)]));e.element.appendChild(o);const h=O(o),$=M.applyBindings(i,o,{methods:{done:()=>e.close(),reset:()=>{n.value={...H}}}});j(e,()=>{$.dispose(),h()}),e.open()}st(b,{id:"todo-settings",icon:"settings",title:"Todo settings",onClick:tt}),d.querySelector(".todo-rows").innerHTML=vt,p.add(O(d,{position:"bottom"}));const et=M.applyBindings(W,d,{methods:{add:X,clearCompleted:Z}});p.add(()=>et.dispose());const L=d.querySelector(".todo-list"),ot=E.contextMenu(L,{cursor:"glyph",match:".todo-row",inherit:!1,density:"compact",items:e=>{const t=e.dataset.id,o=C(t);return o?[{label:"Edit\u2026",icon:"edit",action:()=>Y(t)},{label:o.status==="completed"?"Reopen":"Complete",icon:"check",action:()=>F(t)},{type:"divider"},{label:"Status",icon:"circle",submenu:B.map(s=>({label:u[s],disabled:o.status===s,action:()=>c(t,{status:s})}))},{label:"Priority",icon:"flag",submenu:P.map(s=>({label:u[s],disabled:o.priority===s,action:()=>c(t,{priority:s})}))},{label:"Due",icon:"calendar",submenu:[{label:"Today",action:()=>c(t,{dueAt:k(0)})},{label:"Tomorrow",action:()=>c(t,{dueAt:k(1)})},{label:"Next week",action:()=>c(t,{dueAt:k(7)})},{type:"divider"},{label:"No due date",disabled:!o.dueAt,action:()=>c(t,{dueAt:null})}]},{type:"divider"},{label:"Delete",icon:"trash",danger:!0,action:()=>q(t)}]:[]}});p.add(()=>ot?.destroy?.()),L.addEventListener("click",e=>{const t=e.target.closest(".todo-row");if(t){if(e.target.closest(".todo-kebab, .todo-pill"))return lt(e.target.closest(".todo-kebab, .todo-pill"));e.target.closest(".todo-text")&&Y(t.dataset.id)}}),L.addEventListener("change",e=>{e.target.matches(".todo-check")&&F(e.target.closest(".todo-row").dataset.id)});const R=e=>{e.ctrlKey||e.metaKey||e.altKey||e.target.closest?.("input, textarea, select, [contenteditable], .dm-slideover")||(e.key==="/"?(e.preventDefault(),N.value=!0):(e.key==="n"||e.key==="N")&&(e.preventDefault(),A.value=!0))};document.addEventListener("keydown",R),p.add(()=>document.removeEventListener("keydown",R)),I.scan(d),await V()}};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.1",
|
|
4
4
|
"description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"@fastify/rate-limit": "^10.3.0",
|
|
83
83
|
"@fastify/static": "9.1.1",
|
|
84
84
|
"bcryptjs": "^3.0.3",
|
|
85
|
-
"domma-js": "0.44.
|
|
85
|
+
"domma-js": "0.44.1",
|
|
86
86
|
"dotenv": "^17.2.3",
|
|
87
87
|
"fastify": "5.8.5",
|
|
88
88
|
"gray-matter": "^4.0.3",
|
|
@@ -2382,6 +2382,7 @@ return {
|
|
|
2382
2382
|
if (typeof elements?.contextMenu !== 'function') return;
|
|
2383
2383
|
|
|
2384
2384
|
messageMenu = elements.contextMenu(listEl, {
|
|
2385
|
+
cursor: 'glyph',
|
|
2385
2386
|
match: '.mail-item',
|
|
2386
2387
|
inherit: false,
|
|
2387
2388
|
items: [],
|
|
@@ -3487,6 +3488,7 @@ return {
|
|
|
3487
3488
|
if (typeof elements?.contextMenu !== 'function' || !foldersEl) return;
|
|
3488
3489
|
|
|
3489
3490
|
folderMenu = elements.contextMenu(foldersEl, {
|
|
3491
|
+
cursor: 'glyph',
|
|
3490
3492
|
match: '.mail-folder',
|
|
3491
3493
|
inherit: false,
|
|
3492
3494
|
items: [],
|
package/plugins/blog/CLAUDE.md
CHANGED
|
@@ -16,10 +16,11 @@ Public-facing blog for the CMS. Supports posts with categories, tags, and option
|
|
|
16
16
|
- `lib/render.js` - blocks I/O: seed, load (site copy, else shipped), render cards
|
|
17
17
|
- `lib/page.js` - a post page through its layout; shared by the public route and `POST /preview`
|
|
18
18
|
- `lib/samples.js` - the sample posts and categories
|
|
19
|
+
- `lib/nav-link.js` - the navbar Blog link added on enable, removed on disable
|
|
19
20
|
- `blocks/*.html|css` - the shipped layouts, copied into content/blocks/ once
|
|
20
21
|
- `public/blog.js`, `public/blog.css` - the browser half (listing, right-click menu, post extras)
|
|
21
22
|
- `public/samples/*.svg` - sample covers
|
|
22
|
-
- `tests/layouts.test.js`, `tests/public.test.js`
|
|
23
|
+
- `tests/layouts.test.js`, `tests/public.test.js`, `tests/nav-link.test.js`
|
|
23
24
|
- `admin/views/blog.js` - post list (KPIs + status/category/search filters)
|
|
24
25
|
- `admin/views/post-editor.js` - create / edit a post
|
|
25
26
|
- `admin/views/categories.js`, `admin/views/comments.js`, `admin/views/settings.js`
|
|
@@ -85,7 +86,7 @@ Registered at the Fastify root (no `/api/plugins/blog` prefix). Base path defaul
|
|
|
85
86
|
| `publishedAt` | ISO string | Set on publish or schedule |
|
|
86
87
|
| `seo.title` | string | SEO title override |
|
|
87
88
|
| `seo.description` | string | SEO description override |
|
|
88
|
-
| `template` | string | Post layout block (`blog-post
|
|
89
|
+
| `template` | string | Post layout block (`blog-post-*`: classic, feature, minimal, sidebar, split, essay); `''` = the site default |
|
|
89
90
|
| `sample` | boolean | Added by "Add sample posts"; removed by "Remove" |
|
|
90
91
|
|
|
91
92
|
### categories
|
|
@@ -198,6 +199,26 @@ with `?display=` and swaps the cards, since each display is different block mark
|
|
|
198
199
|
`Collection "blog-posts" not found` from every route. Existing sites were
|
|
199
200
|
unaffected only because the collections were already on disk.
|
|
200
201
|
|
|
202
|
+
- **Enabling adds a Blog link to the navbar** (`onEnable` in plugin.js, `lib/nav-link.js`): appended to
|
|
203
|
+
the menu in the `navbar` slot, with an icon only if most of its links have one, and skipped if any item
|
|
204
|
+
already points at the base path. What it added is remembered as `navLink: {menu, url}` in the plugin's
|
|
205
|
+
state - NOT as a marker on the item, because the Menus editor rewrites items with the keys it knows and
|
|
206
|
+
drops the rest. `onDisable` removes a top-level item with that url, and only when `navLink` says the
|
|
207
|
+
plugin added it, so a site's own Blog link is never touched. Sites where the blog was already enabled
|
|
208
|
+
get no link; there is no enable event for them. A later change of `basePath` does not move the link.
|
|
209
|
+
|
|
210
|
+
- **The admin already does Ctrl+S.** app.js clicks the view's primary Save button and calls
|
|
211
|
+
`preventDefault()`. The editor's own handler saved as well, so every Ctrl+S was two PUTs - and on a
|
|
212
|
+
new post, two POSTs and a duplicate post. It now steps aside when `e.defaultPrevented`.
|
|
213
|
+
|
|
214
|
+
- **Categories and tags are Domma Pillboxes** (`mountPills()` in post-editor.js). Pillbox's script
|
|
215
|
+
emits `.dm-pill` / `.dm-pill-remove` / `.dm-pillbox-empty` but Domma's elements.css styles
|
|
216
|
+
`.dm-pillbox-pill` / `-remove` / `-message`, so admin/css/index.css carries the pill styles until
|
|
217
|
+
domma-js agrees with itself. Pills render labels as TEXT (`renderPill` returns a node) - the default
|
|
218
|
+
`pillTemplate` goes through innerHTML. Tags match case-insensitively, reuse an existing spelling,
|
|
219
|
+
and take commas, pastes and text left in the box. A category id with no category left is kept on
|
|
220
|
+
save, not dropped. The Status card's dates are domma-reactive (`mountStatusCard()`).
|
|
221
|
+
|
|
201
222
|
- **The post editor reads its id off the HASH**: Domma's router hands `onMount`
|
|
202
223
|
one argument and never the params, and `R.current` is a method, so
|
|
203
224
|
`R.current?.params?.id` was always undefined - the editor opened blank on
|
|
@@ -133,6 +133,32 @@
|
|
|
133
133
|
/* The old settings page renders the same panel in a card. */
|
|
134
134
|
.blog-settings-card { max-width: 600px; }
|
|
135
135
|
|
|
136
|
+
/* ── Post editor: status stamps, category / tag pills ─────────────────── */
|
|
137
|
+
.blog-stamps { margin: .85rem 0 0; font-size: var(--dm-text-sm, .875rem); }
|
|
138
|
+
.blog-stamp { display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); gap: .5rem; padding: .3rem 0; border-top: 1px solid var(--dm-border); }
|
|
139
|
+
.blog-stamp dt { color: var(--dm-text-muted); font-weight: 400; }
|
|
140
|
+
.blog-stamp dd { margin: 0; color: var(--dm-text); }
|
|
141
|
+
.blog-stamp-ago { color: var(--dm-text-muted); white-space: nowrap; }
|
|
142
|
+
.blog-stamp-none { margin: .75rem 0 0; font-size: var(--dm-text-sm, .875rem); color: var(--dm-text-muted); }
|
|
143
|
+
.blog-field-note { margin: .4rem 0 0; font-size: var(--dm-text-xs, .75rem); color: var(--dm-text-muted); }
|
|
144
|
+
.blog-editor-side .dm-pillbox { width: 100%; }
|
|
145
|
+
/* The suggestion list hangs below its card; a clipping card cut it off. */
|
|
146
|
+
.blog-editor-side .card:has(.dm-pillbox) { overflow: visible; }
|
|
147
|
+
/* Domma's Pillbox script emits .dm-pill / .dm-pill-remove / .dm-pillbox-empty,
|
|
148
|
+
but elements.css styles .dm-pillbox-pill / -remove / -message - so until the
|
|
149
|
+
two agree the pills arrive unstyled. These mirror Domma's own rules. */
|
|
150
|
+
.blog-editor-side .dm-pill { display: inline-flex; align-items: center; gap: .25rem; padding: .2rem .3rem .2rem .6rem;
|
|
151
|
+
background: var(--dm-primary-light); color: var(--dm-text); border-radius: var(--dm-radius-full); font-size: var(--dm-font-size-sm); line-height: 1.2; }
|
|
152
|
+
.blog-editor-side .dm-pill-remove { display: inline-flex; align-items: center; justify-content: center; width: 1.1rem; height: 1.1rem; padding: 0;
|
|
153
|
+
background: none; border: 0; border-radius: 50%; color: inherit; cursor: pointer; opacity: .65; font-size: 1rem; line-height: 1; }
|
|
154
|
+
.blog-editor-side .dm-pill-remove:hover { opacity: 1; background: color-mix(in srgb, currentColor 15%, transparent); }
|
|
155
|
+
.blog-editor-side .dm-pillbox-container:focus-within { border-color: var(--dm-primary); box-shadow: 0 0 0 3px var(--dm-focus-ring); }
|
|
156
|
+
.blog-editor-side .dm-pillbox-input { min-width: 6rem; font-size: var(--dm-font-size-sm); }
|
|
157
|
+
.blog-editor-side .dm-pillbox-options { list-style: none; margin: 0; padding: .25rem 0; }
|
|
158
|
+
.blog-editor-side .dm-pillbox-option { font-size: var(--dm-font-size-sm); padding: .4rem .75rem; }
|
|
159
|
+
.blog-editor-side .dm-pillbox-option:hover { background-color: var(--dm-hover-bg); }
|
|
160
|
+
.blog-editor-side .dm-pillbox-empty { padding: .6rem .75rem; color: var(--dm-text-muted); font-size: var(--dm-font-size-sm); }
|
|
161
|
+
|
|
136
162
|
/* ── Post editor: layout picker ─────────────────────────────────────── */
|
|
137
163
|
.blog-card-head { display: flex; align-items: center; gap: .25rem; }
|
|
138
164
|
.blog-preview-btn { margin-left: auto; }
|
|
@@ -149,6 +175,8 @@
|
|
|
149
175
|
.blog-thumb[data-thumb="feature"] { --thumb: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2060%2040'%3E%3Crect%20x='0'%20y='0'%20width='60'%20height='20'%20rx='1'%20opacity='0.45'/%3E%3Crect%20x='6'%20y='11'%20width='30'%20height='3.2'%20rx='1'%20opacity='1'/%3E%3Crect%20x='6'%20y='16'%20width='20'%20height='1.8'%20rx='1'%20opacity='0.8'/%3E%3Crect%20x='14'%20y='24.0'%20width='32'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='14'%20y='27.2'%20width='32'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='14'%20y='30.4'%20width='32'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='14'%20y='33.6'%20width='32'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='14'%20y='36.8'%20width='32'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3C/svg%3E"); }
|
|
150
176
|
.blog-thumb[data-thumb="minimal"] { --thumb: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2060%2040'%3E%3Crect%20x='20'%20y='5'%20width='20'%20height='1.5'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='20'%20y='9'%20width='22'%20height='3'%20rx='1'%20opacity='1'/%3E%3Crect%20x='20'%20y='16.0'%20width='20'%20height='1.5'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='20'%20y='19.2'%20width='20'%20height='1.5'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='20'%20y='22.4'%20width='20'%20height='1.5'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='20'%20y='25.6'%20width='20'%20height='1.5'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='20'%20y='28.8'%20width='20'%20height='1.5'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='20'%20y='32.0'%20width='20'%20height='1.5'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='20'%20y='35.2'%20width='20'%20height='1.5'%20rx='1'%20opacity='0.6'/%3E%3C/svg%3E"); }
|
|
151
177
|
.blog-thumb[data-thumb="sidebar"] { --thumb: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2060%2040'%3E%3Crect%20x='4'%20y='4'%20width='30'%20height='3'%20rx='1'%20opacity='1'/%3E%3Crect%20x='4'%20y='10'%20width='34'%20height='9'%20rx='1'%20opacity='0.45'/%3E%3Crect%20x='4'%20y='22.0'%20width='34'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='4'%20y='25.2'%20width='34'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='4'%20y='28.4'%20width='34'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='4'%20y='31.6'%20width='34'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='4'%20y='34.8'%20width='34'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='42'%20y='10'%20width='14'%20height='10'%20rx='1'%20opacity='0.35'/%3E%3Crect%20x='43'%20y='23'%20width='10'%20height='1.4'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='43'%20y='26'%20width='10'%20height='1.4'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='43'%20y='29'%20width='10'%20height='1.4'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='43'%20y='32'%20width='10'%20height='1.4'%20rx='1'%20opacity='0.6'/%3E%3C/svg%3E"); }
|
|
178
|
+
.blog-thumb[data-thumb="split"] { --thumb: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2060%2040'%3E%3Crect%20x='4'%20y='6'%20width='24'%20height='28'%20rx='1'%20opacity='0.45'/%3E%3Crect%20x='32'%20y='11'%20width='24'%20height='3'%20rx='1'%20opacity='1'/%3E%3Crect%20x='32'%20y='16'%20width='18'%20height='3'%20rx='1'%20opacity='1'/%3E%3Crect%20x='32'%20y='22'%20width='22'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='32'%20y='25'%20width='20'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='32'%20y='30.5'%20width='1.2'%20height='4'%20rx='1'%20opacity='1'/%3E%3Crect%20x='34.5'%20y='31'%20width='12'%20height='1.4'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='34.5'%20y='33.4'%20width='9'%20height='1.4'%20rx='1'%20opacity='0.6'/%3E%3C/svg%3E"); }
|
|
179
|
+
.blog-thumb[data-thumb="essay"] { --thumb: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2060%2040'%3E%3Crect%20x='14'%20y='3'%20width='32'%20height='4'%20rx='1'%20opacity='1'/%3E%3Crect%20x='18'%20y='9.5'%20width='24'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='20'%20y='13.2'%20width='20'%20height='0.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='24'%20y='14.6'%20width='12'%20height='1.2'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='20'%20y='16.6'%20width='20'%20height='0.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='14'%20y='20.5'%20width='4'%20height='4'%20rx='1'%20opacity='0.9'/%3E%3Crect%20x='19.5'%20y='20.5'%20width='26.5'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='19.5'%20y='23.3'%20width='26.5'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='14'%20y='26.5'%20width='32'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='14'%20y='29.5'%20width='32'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='14'%20y='32.5'%20width='23'%20height='1.6'%20rx='1'%20opacity='0.6'/%3E%3Crect%20x='38.5'%20y='32.5'%20width='1.6'%20height='1.6'%20rx='1'%20opacity='1'/%3E%3C/svg%3E"); }
|
|
152
180
|
.blog-thumb[data-thumb="custom"] { --thumb: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2060%2040'%3E%3Crect%20x='6'%20y='5'%20width='48'%20height='30'%20rx='3'%20fill='none'%20stroke='%23000'%20stroke-width='1.6'%20stroke-dasharray='3%202.4'/%3E%3Crect%20x='28.8'%20y='14'%20width='2.4'%20height='12'%20rx='1'%20opacity='1'/%3E%3Crect%20x='24'%20y='18.8'%20width='12'%20height='2.4'%20rx='1'%20opacity='1'/%3E%3C/svg%3E"); }
|
|
153
181
|
.blog-layout-desc { margin: .6rem 0 0; font-size: var(--dm-text-xs); color: var(--dm-text-muted); min-height: 2.2em; }
|
|
154
182
|
|
|
@@ -68,14 +68,29 @@
|
|
|
68
68
|
|
|
69
69
|
<!-- Sidebar -->
|
|
70
70
|
<div class="blog-editor-side">
|
|
71
|
-
<div class="card mb-3">
|
|
72
|
-
<div class="card-header">Status <span data-help="Save
|
|
71
|
+
<div class="card mb-3" id="status-card">
|
|
72
|
+
<div class="card-header">Status <span data-help="Save keeps the post as it is. Publish puts it live now; Schedule publishes it at the date and time you choose. The dates below come from the last save."></span></div>
|
|
73
73
|
<div class="card-body">
|
|
74
74
|
<div id="schedule-picker" class="mb-3 blog-hidden">
|
|
75
75
|
<label class="form-label" for="post-scheduled-at">Scheduled for</label>
|
|
76
76
|
<input type="datetime-local" id="post-scheduled-at" class="form-input">
|
|
77
77
|
</div>
|
|
78
|
-
<span id="post-status-badge" class="badge
|
|
78
|
+
<span id="post-status-badge" class="badge" data-bind-class="statusClass.value" data-bind-text="status.value">draft</span>
|
|
79
|
+
<dl class="blog-stamps">
|
|
80
|
+
<div class="blog-stamp" data-bind-class="liveWhen.value ? '' : 'blog-hidden'">
|
|
81
|
+
<dt data-bind-text="liveLabel.value"></dt>
|
|
82
|
+
<dd><span data-bind-text="liveWhen.value"></span> <span class="blog-stamp-ago" data-bind-text="liveAgo.value"></span></dd>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="blog-stamp" data-bind-class="savedWhen.value ? '' : 'blog-hidden'">
|
|
85
|
+
<dt>Last saved</dt>
|
|
86
|
+
<dd><span data-bind-text="savedWhen.value"></span> <span class="blog-stamp-ago" data-bind-text="savedAgo.value"></span></dd>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="blog-stamp" data-bind-class="createdWhen.value ? '' : 'blog-hidden'">
|
|
89
|
+
<dt>Created</dt>
|
|
90
|
+
<dd><span data-bind-text="createdWhen.value"></span></dd>
|
|
91
|
+
</div>
|
|
92
|
+
</dl>
|
|
93
|
+
<p class="blog-stamp-none" data-bind-class="createdWhen.value ? 'blog-hidden' : ''">Not saved yet.</p>
|
|
79
94
|
</div>
|
|
80
95
|
</div>
|
|
81
96
|
|
|
@@ -94,17 +109,18 @@
|
|
|
94
109
|
</div>
|
|
95
110
|
|
|
96
111
|
<div class="card mb-3">
|
|
97
|
-
<div class="card-header">Categories <span data-help="
|
|
98
|
-
<div class="card-body"
|
|
99
|
-
<
|
|
112
|
+
<div class="card-header">Categories <span data-help="Type to find a category, or click the box to see them all. A post can be in several; the x on a pill takes it out. Manage the list under Blog > Categories."></span></div>
|
|
113
|
+
<div class="card-body">
|
|
114
|
+
<input type="text" id="post-categories" aria-label="Categories">
|
|
115
|
+
<p class="blog-field-note blog-hidden" id="categories-note">No categories yet - add some under Blog > Categories.</p>
|
|
100
116
|
</div>
|
|
101
117
|
</div>
|
|
102
118
|
|
|
103
119
|
<div class="card mb-3">
|
|
104
|
-
<div class="card-header">Tags <span data-help="Free-form labels
|
|
120
|
+
<div class="card-header">Tags <span data-help="Free-form labels. Type a tag and press Enter or a comma; tags already used on other posts are suggested as you type. Each tag gets its own page on the site."></span></div>
|
|
105
121
|
<div class="card-body">
|
|
106
|
-
<input type="text" id="post-tags"
|
|
107
|
-
<
|
|
122
|
+
<input type="text" id="post-tags" aria-label="Tags">
|
|
123
|
+
<p class="blog-field-note">Enter or a comma adds a tag.</p>
|
|
108
124
|
</div>
|
|
109
125
|
</div>
|
|
110
126
|
|
|
@@ -237,6 +237,7 @@ export const blogView = {
|
|
|
237
237
|
|
|
238
238
|
// ---- Context menu: every action on a post ----
|
|
239
239
|
const menu = E.contextMenu(list, {
|
|
240
|
+
cursor: 'glyph',
|
|
240
241
|
match: '.blog-row',
|
|
241
242
|
inherit: false,
|
|
242
243
|
density: 'compact',
|
|
@@ -269,6 +270,7 @@ export const blogView = {
|
|
|
269
270
|
.catch(() => { /* not shown, then */ });
|
|
270
271
|
const samples = () => posts.peek().filter(p => p.sample === true).length;
|
|
271
272
|
const barMenu = E.contextMenu(root.querySelector('.blog-bar-end'), {
|
|
273
|
+
cursor: 'glyph',
|
|
272
274
|
match: '.blog-more',
|
|
273
275
|
inherit: false,
|
|
274
276
|
density: 'compact',
|
|
@@ -219,7 +219,7 @@ const SETTINGS_PANEL = `
|
|
|
219
219
|
</h4>
|
|
220
220
|
<div class="blog-field">
|
|
221
221
|
<label class="form-label" for="blog-set-layout">Post layout
|
|
222
|
-
<span data-help="How a post looks unless it picks its own in the editor. Classic: title, image, story. Feature: a full-width hero. Minimal: just the words. Sidebar: contents list and details beside the story."></span>
|
|
222
|
+
<span data-help="How a post looks unless it picks its own in the editor. Classic: title, image, story. Feature: a full-width hero. Minimal: just the words. Sidebar: contents list and details beside the story. Split: picture and headline side by side. Essay: a long read set like print."></span>
|
|
223
223
|
</label>
|
|
224
224
|
<select id="blog-set-layout" class="form-select" data-model="postLayout.value"
|
|
225
225
|
data-options="layoutOptions.value" data-options-text="label" data-options-value="id"></select>
|