domma-cms 1.10.37 → 1.10.39

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.39] - 2026-10-27
4
+
5
+ - Email rules can be sorted into named groups, each switched on or off as a whole; rules run in the order shown (ungrouped first, then each group). The message menu offers "Create a rule from this message…", which opens Rules with a new rule matching the sender (or the mailing list). Requires Email Pro 0.12.0 for the screens.
6
+
7
+ ## [1.10.38] - 2026-10-27
8
+
9
+ - Email gains Thunderbird's compose settings. Per mailbox: default read and delivery receipts (SMTP DSN), replies filed in the folder of the message they answer, and require secure delivery (REQUIRETLS - refused, with the reason, when the outgoing server cannot promise it). In this browser: default editor (rich or plain text), automatic draft saving (an automatic-only draft goes on Discard), spell checking, and registering Email as the mailto: link handler. Compose shows Read receipt, Delivery receipt, Sign and Encrypt ticks, and a reopened draft keeps them.
10
+ - New-mail desktop notifications and a sound, from the sidebar's count while the admin is open in any tab. A Tool's count route can add `notify` (see `admin/js/lib/sidebar-notify.js`); the free Mail Reader and Email Pro both do.
11
+ - A hook for signed and encrypted (S/MIME) mail - sending, drafts and reading - used by Email Pro 0.11.0. The reader shows whether a message is signed, by whom, and whether it checks out. Adds the `node-forge` dependency (pinned).
12
+
3
13
  ## [1.10.37] - 2026-10-27
4
14
 
5
15
  - The updater's backup no longer copies the runtime data of plugins it does not update. A marketplace or site plugin's `data/`, `backups/` and `node_modules/` stay out of `.domma-backups` (its code is still copied); the engine's own plugins are still backed up whole. A File Store's members' uploads were otherwise copied into every one of up to sixteen kept backups.
@@ -1 +1 @@
1
- import{isPathAllowed as m}from"./admin-scope.js";export const MAIL_PLUGIN="email-pro",MAIL_ROUTE=`#/plugins/${MAIL_PLUGIN}`;const d=2e3;export const MAILTO_LIMIT=1800;export function looksLikeAddress(t){return typeof t=="string"&&/^[^\s@<>,;"]+@[^\s@<>,;"]+$/.test(t)}export function recipientList(t){const e=new Set,n=[],o=(Array.isArray(t)?t:t==null?[]:[t]).flatMap(c=>typeof c=="string"?c.split(/[,;]/).map(f):[c]);for(const c of o){const s=String(typeof c=="string"?c:c?.address??c?.email??"").trim();if(!looksLikeAddress(s))continue;const r=s.toLowerCase();if(e.has(r))continue;e.add(r);const a=typeof c=="object"&&c?String(c.name??"").replace(/[\r\n<>",;]+/g," ").replace(/\s+/g," ").trim():"";n.push({name:a,address:s})}return n}function f(t){const e=String(t).trim().match(/^(.*?)\s*<([^<>]+)>$/);return e?{name:e[1].replace(/^["']|["']$/g,""),address:e[2].trim()}:String(t).trim()}function h(t){return t.map(e=>e.name?`${e.name} <${e.address}>`:e.address).join(", ")}function l(t){return recipientList(String(t??""))}function u(t,{oneLine:e=!1}={}){let n=String(t??"");return e&&(n=n.replace(/[\r\n]+/g," ")),n.slice(0,d).trim()}export function composeRequest({to:t,cc:e,bcc:n,subject:o,body:c}={}){return{to:recipientList(t),cc:recipientList(e),bcc:recipientList(n),subject:u(o,{oneLine:!0}),body:u(c)}}export function composeHash(t){const e=composeRequest(t),n=new URLSearchParams({compose:"1"});for(const o of["to","cc","bcc"])e[o].length&&n.set(o,h(e[o]));return e.subject&&n.set("subject",e.subject),e.body&&n.set("body",e.body),`${MAIL_ROUTE}?${n}`}export function readComposeRequest(t){const e=String(t??"").match(/^#\/plugins\/[\w-]+\?(.*)$/);if(!e)return null;const n=new URLSearchParams(e[1]);return n.get("compose")!=="1"?null:composeRequest({to:l(n.get("to")),cc:l(n.get("cc")),bcc:l(n.get("bcc")),subject:n.get("subject")??"",body:n.get("body")??""})}export function mailtoHref(t){const e=composeRequest(t),n=c=>c.map(s=>encodeURIComponent(s.address).replace(/%40/g,"@")).join(","),o=[];return e.cc.length&&o.push(`cc=${n(e.cc)}`),e.bcc.length&&o.push(`bcc=${n(e.bcc)}`),e.subject&&o.push(`subject=${encodeURIComponent(e.subject)}`),e.body&&o.push(`body=${encodeURIComponent(e.body)}`),`mailto:${n(e.to)}${o.length?`?${o.join("&")}`:""}`}export function composeAvailable({routes:t,accounts:e,scope:n=null}={}){const o=`/plugins/${MAIL_PLUGIN}`;return Array.isArray(t)&&t.some(s=>s?.path===o)&&m(n,o)&&Array.isArray(e)&&e.length>0}export function allAddresses(t){const e=composeRequest(t);return[...e.to,...e.cc,...e.bcc].map(n=>n.address)}export function copyNotice(t){const e=composeRequest(t),n=allAddresses(e).length,o=e.bcc.length?"Bcc":"To",c=n===1?"the address":`${n} addresses`,s=[e.subject&&`the subject "${e.subject.length>60?`${e.subject.slice(0,57)}...`:e.subject}"`,e.body&&"the message"].filter(Boolean),r=s.length?` ${s.join(" and ").replace(/^./,a=>a.toUpperCase())} ${s.length>1?"weren't":"wasn't"} included.`:"";return`Copied ${c} - paste ${n===1?"it":"them"} into ${o}.${r}`}const g=1e4;let i=null;export function emailReady(){if(i&&Date.now()-i.at<g)return i.answer;const t={at:Date.now(),answer:null};return t.answer=(async()=>{const e=await b();return!e&&i===t&&(i=null),e})(),i=t,t.answer}async function b(){try{const{apiRequest:t}=await import("/admin/js/api.js"),[e,n]=await Promise.all([t("/plugins/admin-config",{method:"GET"}),t("/auth/permissions",{method:"GET"}).catch(()=>null)]),o=n?.adminScope||null;if(!composeAvailable({routes:e?.routes,accounts:[1],scope:o}))return!1;const c=await t(`/plugins/${MAIL_PLUGIN}/accounts`,{method:"GET"});return composeAvailable({routes:e.routes,accounts:c?.accounts,scope:o})}catch{return!1}}function p(t,e="info"){try{globalThis.E?.toast?.(t,{type:e})}catch{}}async function y(t){try{if(!navigator.clipboard?.writeText)throw new Error("no async clipboard");return await navigator.clipboard.writeText(t),!0}catch{try{const e=document.createElement("textarea");e.value=t,e.style.cssText="position:fixed;opacity:0",document.body.appendChild(e),e.select();const n=document.execCommand("copy");return e.remove(),n}catch{return!1}}}function w(t){const e=allAddresses(t),n=E.modal({title:`${e.length} ${e.length===1?"address":"addresses"}`,size:"md"}),o=document.createElement("div");o.className="flex flex-col gap-2";const c=document.createElement("p");c.className="text-sm",c.textContent=`Too many for your mail program to take from a link, and the browser would not copy them. Select them, copy, and paste into ${t.bcc.length?"Bcc":"To"}.`;const s=document.createElement("textarea");if(s.className="form-textarea mail-compose-addresses",s.readOnly=!0,s.rows=8,s.value=e.join(", "),o.append(c,s),t.subject){const r=document.createElement("label");r.className="form-label",r.textContent="Subject";const a=document.createElement("input");a.className="form-input",a.readOnly=!0,a.value=t.subject,o.append(r,a)}n.element.appendChild(o),n.open(),setTimeout(()=>{s.focus(),s.select()},60)}export async function openEmail(t){const e=composeRequest(t);if(!e.to.length&&!e.cc.length&&!e.bcc.length)return p("There is no valid email address to write to.","warning"),"none";if(await emailReady())return location.hash=composeHash(e),"compose";const n=mailtoHref(e);return n.length>MAILTO_LIMIT?await y(allAddresses(e).join(", "))?(p(copyNotice(e),"info"),"copied"):(w(e),"shown"):(location.href=n,"mailto")}
1
+ import{isPathAllowed as b}from"./admin-scope.js";export const MAIL_PLUGIN="email-pro",MAIL_ROUTE=`#/plugins/${MAIL_PLUGIN}`;const g=2e3;export const MAILTO_LIMIT=1800;export function looksLikeAddress(t){return typeof t=="string"&&/^[^\s@<>,;"]+@[^\s@<>,;"]+$/.test(t)}export function recipientList(t){const e=new Set,n=[],o=(Array.isArray(t)?t:t==null?[]:[t]).flatMap(c=>typeof c=="string"?c.split(/[,;]/).map(y):[c]);for(const c of o){const s=String(typeof c=="string"?c:c?.address??c?.email??"").trim();if(!looksLikeAddress(s))continue;const r=s.toLowerCase();if(e.has(r))continue;e.add(r);const a=typeof c=="object"&&c?String(c.name??"").replace(/[\r\n<>",;]+/g," ").replace(/\s+/g," ").trim():"";n.push({name:a,address:s})}return n}function y(t){const e=String(t).trim().match(/^(.*?)\s*<([^<>]+)>$/);return e?{name:e[1].replace(/^["']|["']$/g,""),address:e[2].trim()}:String(t).trim()}function w(t){return t.map(e=>e.name?`${e.name} <${e.address}>`:e.address).join(", ")}function d(t){return recipientList(String(t??""))}function f(t,{oneLine:e=!1}={}){let n=String(t??"");return e&&(n=n.replace(/[\r\n]+/g," ")),n.slice(0,g).trim()}export function composeRequest({to:t,cc:e,bcc:n,subject:o,body:c}={}){return{to:recipientList(t),cc:recipientList(e),bcc:recipientList(n),subject:f(o,{oneLine:!0}),body:f(c)}}export function composeHash(t){const e=composeRequest(t),n=new URLSearchParams({compose:"1"});for(const o of["to","cc","bcc"])e[o].length&&n.set(o,w(e[o]));return e.subject&&n.set("subject",e.subject),e.body&&n.set("body",e.body),`${MAIL_ROUTE}?${n}`}export function readComposeRequest(t){const e=String(t??"").match(/^#\/plugins\/[\w-]+\?(.*)$/);if(!e)return null;const n=new URLSearchParams(e[1]);return n.get("compose")!=="1"?null:n.has("mailto")?composeRequest(parseMailto(n.get("mailto"))):composeRequest({to:d(n.get("to")),cc:d(n.get("cc")),bcc:d(n.get("bcc")),subject:n.get("subject")??"",body:n.get("body")??""})}export function parseMailto(t){const e={to:"",cc:"",bcc:"",subject:"",body:""},n=String(t??"").trim();if(!/^mailto:/i.test(n))return e;const o=i=>{try{return decodeURIComponent(i)}catch{return i}},c=n.slice(7),s=c.indexOf("?"),r={to:[],cc:[],bcc:[]},a=s===-1?c:c.slice(0,s);a&&r.to.push(o(a));for(const i of(s===-1?"":c.slice(s+1)).split("&")){if(!i)continue;const p=i.indexOf("="),l=o(p===-1?i:i.slice(0,p)).toLowerCase(),m=o(p===-1?"":i.slice(p+1));l in r?r[l].push(m):(l==="subject"||l==="body")&&!e[l]&&(e[l]=m)}for(const i of Object.keys(r))e[i]=r[i].filter(Boolean).join(", ");return e}export function mailtoHandlerUrl(t){return`${String(t).replace(/#.*$/,"")}${MAIL_ROUTE}?compose=1&mailto=%s`}export function mailtoHref(t){const e=composeRequest(t),n=c=>c.map(s=>encodeURIComponent(s.address).replace(/%40/g,"@")).join(","),o=[];return e.cc.length&&o.push(`cc=${n(e.cc)}`),e.bcc.length&&o.push(`bcc=${n(e.bcc)}`),e.subject&&o.push(`subject=${encodeURIComponent(e.subject)}`),e.body&&o.push(`body=${encodeURIComponent(e.body)}`),`mailto:${n(e.to)}${o.length?`?${o.join("&")}`:""}`}export function composeAvailable({routes:t,accounts:e,scope:n=null}={}){const o=`/plugins/${MAIL_PLUGIN}`;return Array.isArray(t)&&t.some(s=>s?.path===o)&&b(n,o)&&Array.isArray(e)&&e.length>0}export function allAddresses(t){const e=composeRequest(t);return[...e.to,...e.cc,...e.bcc].map(n=>n.address)}export function copyNotice(t){const e=composeRequest(t),n=allAddresses(e).length,o=e.bcc.length?"Bcc":"To",c=n===1?"the address":`${n} addresses`,s=[e.subject&&`the subject "${e.subject.length>60?`${e.subject.slice(0,57)}...`:e.subject}"`,e.body&&"the message"].filter(Boolean),r=s.length?` ${s.join(" and ").replace(/^./,a=>a.toUpperCase())} ${s.length>1?"weren't":"wasn't"} included.`:"";return`Copied ${c} - paste ${n===1?"it":"them"} into ${o}.${r}`}const x=1e4;let u=null;export function emailReady(){if(u&&Date.now()-u.at<x)return u.answer;const t={at:Date.now(),answer:null};return t.answer=(async()=>{const e=await $();return!e&&u===t&&(u=null),e})(),u=t,t.answer}async function $(){try{const{apiRequest:t}=await import("/admin/js/api.js"),[e,n]=await Promise.all([t("/plugins/admin-config",{method:"GET"}),t("/auth/permissions",{method:"GET"}).catch(()=>null)]),o=n?.adminScope||null;if(!composeAvailable({routes:e?.routes,accounts:[1],scope:o}))return!1;const c=await t(`/plugins/${MAIL_PLUGIN}/accounts`,{method:"GET"});return composeAvailable({routes:e.routes,accounts:c?.accounts,scope:o})}catch{return!1}}function h(t,e="info"){try{globalThis.E?.toast?.(t,{type:e})}catch{}}async function j(t){try{if(!navigator.clipboard?.writeText)throw new Error("no async clipboard");return await navigator.clipboard.writeText(t),!0}catch{try{const e=document.createElement("textarea");e.value=t,e.style.cssText="position:fixed;opacity:0",document.body.appendChild(e),e.select();const n=document.execCommand("copy");return e.remove(),n}catch{return!1}}}function T(t){const e=allAddresses(t),n=E.modal({title:`${e.length} ${e.length===1?"address":"addresses"}`,size:"md"}),o=document.createElement("div");o.className="flex flex-col gap-2";const c=document.createElement("p");c.className="text-sm",c.textContent=`Too many for your mail program to take from a link, and the browser would not copy them. Select them, copy, and paste into ${t.bcc.length?"Bcc":"To"}.`;const s=document.createElement("textarea");if(s.className="form-textarea mail-compose-addresses",s.readOnly=!0,s.rows=8,s.value=e.join(", "),o.append(c,s),t.subject){const r=document.createElement("label");r.className="form-label",r.textContent="Subject";const a=document.createElement("input");a.className="form-input",a.readOnly=!0,a.value=t.subject,o.append(r,a)}n.element.appendChild(o),n.open(),setTimeout(()=>{s.focus(),s.select()},60)}export async function openEmail(t){const e=composeRequest(t);if(!e.to.length&&!e.cc.length&&!e.bcc.length)return h("There is no valid email address to write to.","warning"),"none";if(await emailReady())return location.hash=composeHash(e),"compose";const n=mailtoHref(e);return n.length>MAILTO_LIMIT?await j(allAddresses(e).join(", "))?(h(copyNotice(e),"info"),"copied"):(T(e),"shown"):(location.href=n,"mailto")}
@@ -0,0 +1 @@
1
+ export const prefsKey=(t,e)=>`dm.notify.${t||"anon"}.${e}`,marksKey=(t,e)=>`dm.notify.seen.${t||"anon"}.${e}`;const y=50,b=200;export function cleanNotify(t){if(!t||typeof t!="object"||Array.isArray(t))return null;const e={};if(t.marks&&typeof t.marks=="object"&&!Array.isArray(t.marks))for(const[o,s]of Object.entries(t.marks).slice(0,50)){const r=Number(s);Number.isFinite(r)&&o.length<=200&&!["__proto__","constructor","prototype"].includes(o)&&(e[o]=r)}if(!Object.keys(e).length)return null;const n=o=>String(o??"").replace(/\s+/g," ").trim().slice(0,200);return{marks:e,title:n(t.title)||"Something new",body:n(t.body)}}export function compareMarks(t,e){const n=t&&typeof t=="object"?t:{},o={...n};let s=!1;for(const[r,c]of Object.entries(e)){const i=Number(n[r]);Number.isFinite(i)?c>i&&(s=!0,o[r]=c):o[r]=c}return{rose:s,next:o}}export function readNotifyPrefs(t,e,n=globalThis.S){try{const o=n?.get(prefsKey(t,e));return{popup:o?.popup===!0,sound:o?.sound===!0}}catch{return{popup:!1,sound:!1}}}export function writeNotifyPrefs(t,e,n,o=globalThis.S){o.set(prefsKey(t,e),{popup:n?.popup===!0,sound:n?.sound===!0})}export function playChime(){try{const t=globalThis.AudioContext||globalThis.webkitAudioContext;if(!t)return;const e=new t,n=e.currentTime;[[880,0],[1318.5,.12]].forEach(([o,s])=>{const r=e.createOscillator(),c=e.createGain();r.type="sine",r.frequency.value=o,c.gain.setValueAtTime(1e-4,n+s),c.gain.exponentialRampToValueAtTime(.18,n+s+.02),c.gain.exponentialRampToValueAtTime(1e-4,n+s+.35),r.connect(c).connect(e.destination),r.start(n+s),r.stop(n+s+.4)}),setTimeout(()=>{try{e.close()}catch{}},1e3)}catch{}}export function noticeFromCount(t,e,n,o={}){const s=o.store??globalThis.S,r=cleanNotify(n);if(!r||!s)return!1;let c=null;try{c=s.get(marksKey(t,e))}catch{c=null}const{rose:i,next:f}=compareMarks(c,r.marks);try{s.set(marksKey(t,e),f)}catch{}if(!i)return!1;const u=readNotifyPrefs(t,e,s);let a=!1;const l=o.Notification??globalThis.Notification;if(u.popup&&l&&l.permission==="granted")try{const p=new l(r.title,{body:r.body,tag:`dm-notify:${e}`});p.onclick=()=>{try{globalThis.focus?.()}catch{}(o.open??(m=>{globalThis.location.hash=m}))(e),p.close?.()},a=!0}catch{}return u.sound&&((o.chime??playChime)(),a=!0),a}
@@ -1,7 +1,7 @@
1
- import{api as g,getUser as k,isAuthenticated as Se}from"../api.js";import{createRequestQueue as ke}from"./background-requests.js";import{requestSidebarCounts as qe}from"./sidebar-counts.js";import{beginSidebarCounts as Ce,publishSidebarCount as Ae}from"./sidebar-count-cache.js";import{isPathAllowed as Fe}from"./admin-scope.js";import{colourToCss as te,applyFloat as Le,applyHighlight as Ee,itemHighlightAttrs as je}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{placeTools as Be,ensureLockedTools as Oe,managePluginsItem as Me,stripItemByUrl as Te,insertFoldersBeforeSystem as He,pruneEmptyToolsFolder as Ne,sidebarStateKey as ne,canAny as W,filterByPermission as Re,MANAGE_PLUGINS_URL as se}from"./sidebar-grouping.js";import{arrangeButtonMarkup as Ie,canArrange as Pe,startArrange as _e}from"./sidebar-arrange.js";import{collapseButtonMarkup as Ue,wireSidebarSize as ze,COLLAPSED_WIDTH as We}from"./sidebar-size.js";import{FREQUENT_MAX as oe,addFrequent as De,frequentFolder as Ge,moveFrequent as ie,readFrequent as K,readFrequentHidden as Ke,removeFrequent as Ve,saveFrequent as Xe,syncFrequent as Ze,writeFrequent as Je,writeFrequentHidden as ae}from"./sidebar-frequent.js";import{hotkeyFor as re,hotkeyTitle as Ye,setHotkeyTree as Qe}from"./hotkeys-ui.js";import{hotkeyLabel as et}from"./hotkeys.js";function m(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const tt=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Site menus",url:"#/menus",icon:"menu",permission:"menus"}];async function nt(e){if(!W(e,"projects"))return[];let n=[];try{n=await g.projects.listWithCounts()}catch{return[]}if(!Array.isArray(n))return[];const t=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Site menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"components",text:"Components",icon:"component",path:"/components"},{key:"views",text:"Views",icon:"eye",path:"/views"},{key:"roles",text:"Roles",icon:"shield",path:"/roles"},{key:"users",text:"Users",icon:"users",path:"/users"},{key:"apis",text:"APIs",icon:"code",path:"/apis"}];return n.map(o=>{const l="#/projects/"+encodeURIComponent(o.slug),u=o.counts||{},i=[{text:"Overview",url:l,icon:o.icon||"folder"}];for(const d of t){const r=Number(u[d.key])||0;r<=0||i.push({text:`${d.text} (${r})`,url:l+d.path,icon:d.icon})}i.push({text:"Settings",url:l+"/settings",icon:"settings"});const a=o.enabled===!1;return{text:o.name||o.slug,key:`project.${o.slug}`,icon:o.icon||"folder",items:i,...a&&{colour:"neutral",badge:{text:"off"}}}})}function st(e,n){for(const t of e)if(!t.hidden&&(t.key==="projects"||!t.key&&(t.text||"").toLowerCase()==="projects")){t.items=Array.isArray(t.items)?t.items.slice():[],t.items.push(...n);return}}function le(e,n){const t=de.includes(n.tone)?n.tone:"info",o=n.label||String(n.count);n.details?R.set(e,n.details):R.delete(e);const s=!n.inventory&&L.some(l=>l.url===e&&!l.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${t}" data-count-for="${m(e)}"${s} data-badge-label="${m(o)}" aria-label="${m(o)}">${m(String(n.count))}</span>`}function ce(e){for(const n of e.querySelectorAll("details")){const t=n.querySelector(":scope > summary");if(!t)continue;let o=0,s=!1;const l=[];for(const r of n.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const f=Number(r.textContent)||0;f&&(o+=f,s||(s=r.classList.contains("sidebar-badge--danger")),l.push({text:r.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:r.dataset.badgeLabel,tone:r.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const u=`folder:${n.dataset.stateKey||t.textContent.trim()}`;let i=t.querySelector(":scope > .sidebar-badge--sum");if(!o){i?.remove(),R.delete(u);continue}i||(i=document.createElement("span"),t.querySelector(".sidebar-text")?.after(i));const a=t.querySelector(".sidebar-text")?.textContent.trim()||"This folder",d=l.map(r=>`${r.text}: ${r.meta}`).join(" \xB7 ");i.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${s?"danger":"info"}`,i.dataset.countFor=u,i.dataset.badgeLabel=d,i.setAttribute("aria-label",d),i.textContent=String(o),R.set(u,{title:a,items:l})}}const R=new Map;function ot(e,n){if(!e.url||!n)return"";const t=n[e.url];return!t||!(t.count>0)&&!t.showZero?"":le(e.url,t)}const de=["info","attention","danger","live"],it={"#/pages":["page","pages"],"#/media":["file in the media library","files in the media library"],"#/menus":["menu","menus"],"#/context-menus":["context menu","context menus"],"#/theme":["element override in the theme","element overrides in the theme"],"#/collections":["collection","collections"],"#/forms":["form","forms"],"#/views":["view","views"],"#/actions":["action","actions"],"#/blocks":["block","blocks"],"#/components":["component","components"],"#/users":["user","users"],"#/plugins":["plugin installed","plugins installed"],"#/system/notifications":["unread notification","unread notifications"]};function D(e,{fallbackLabel:n="",tone:t="info"}={}){const o=Number(typeof e=="object"&&e!==null?e.count:e);if(!Number.isFinite(o)||o<0)return null;const s=typeof e?.label=="string"&&e.label||(n?`${o} - ${n}`:String(o)),l={count:Math.round(o),label:s,tone:de.includes(e?.tone)?e.tone:t};return e?.details&&typeof e.details=="object"&&(l.details=e.details),e?.inventory===!0&&(l.inventory=!0),e?.showZero===!0&&(l.showZero=!0),l}function ue(e,n,t=null,{inFrequent:o=!1}={}){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="theme-switcher")return"";if(e&&e.type==="spacer"){const b=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${m(b)}" aria-hidden="true"></div>`}const s=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",l=e.icon?`<span data-icon="${m(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${m(s)}</span>`,u=m(e.text||""),i=e.url?m(e.url):"",a=Array.isArray(e.items)&&e.items.length>0,d=e.colour?te(e.colour):"",r=je(e),f=[r.hover&&` data-menu-item-hover="${m(r.hover)}"`,r.active&&` data-menu-item-active="${m(r.active)}"`].filter(Boolean).join(""),h=[d&&`color:${m(d)}`,r.accent&&`--dm-menu-highlight:${m(r.accent)}`].filter(Boolean).join(";"),v=h?` style="${h}"`:"",w=e.url?Ye(e.url,e.text||"",{inFrequent:o}):"",q=w?` title="${m(w)}"`:"";let F="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const b=e.badge.variant?te(e.badge.variant):"";F=`<span class="dm-menu-badge"${b?` style="background:${m(b)};color:var(--dm-text-inverse,#fff)"`:""}>${m(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const b=ne(e,t),A=S.get(b)!==!1;return`<details data-state-key="${m(b)}"${A?" open":""} data-frequent data-frequent-empty>
2
- <summary${v}>${l} <span class="sidebar-text">${u}</span></summary>
1
+ import{api as g,getUser as w,isAuthenticated as Se}from"../api.js";import{createRequestQueue as ke}from"./background-requests.js";import{requestSidebarCounts as qe}from"./sidebar-counts.js";import{beginSidebarCounts as Ce,publishSidebarCount as Ae}from"./sidebar-count-cache.js";import{isPathAllowed as Fe}from"./admin-scope.js";import{cleanNotify as Le,noticeFromCount as Ee}from"./sidebar-notify.js";import{colourToCss as te,applyFloat as je,applyHighlight as Be,itemHighlightAttrs as Oe}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{placeTools as Me,ensureLockedTools as Te,managePluginsItem as Ne,stripItemByUrl as He,insertFoldersBeforeSystem as Re,pruneEmptyToolsFolder as Ie,sidebarStateKey as ne,canAny as W,filterByPermission as Pe,MANAGE_PLUGINS_URL as se}from"./sidebar-grouping.js";import{arrangeButtonMarkup as _e,canArrange as Ue,startArrange as ze}from"./sidebar-arrange.js";import{collapseButtonMarkup as We,wireSidebarSize as De,COLLAPSED_WIDTH as Ge}from"./sidebar-size.js";import{FREQUENT_MAX as oe,addFrequent as Ke,frequentFolder as Ve,moveFrequent as ie,readFrequent as K,readFrequentHidden as Xe,removeFrequent as Ze,saveFrequent as Je,syncFrequent as Ye,writeFrequent as Qe,writeFrequentHidden as re}from"./sidebar-frequent.js";import{hotkeyFor as ae,hotkeyTitle as et,setHotkeyTree as tt}from"./hotkeys-ui.js";import{hotkeyLabel as nt}from"./hotkeys.js";function m(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const st=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Site menus",url:"#/menus",icon:"menu",permission:"menus"}];async function ot(e){if(!W(e,"projects"))return[];let n=[];try{n=await g.projects.listWithCounts()}catch{return[]}if(!Array.isArray(n))return[];const t=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Site menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"components",text:"Components",icon:"component",path:"/components"},{key:"views",text:"Views",icon:"eye",path:"/views"},{key:"roles",text:"Roles",icon:"shield",path:"/roles"},{key:"users",text:"Users",icon:"users",path:"/users"},{key:"apis",text:"APIs",icon:"code",path:"/apis"}];return n.map(o=>{const a="#/projects/"+encodeURIComponent(o.slug),u=o.counts||{},i=[{text:"Overview",url:a,icon:o.icon||"folder"}];for(const d of t){const l=Number(u[d.key])||0;l<=0||i.push({text:`${d.text} (${l})`,url:a+d.path,icon:d.icon})}i.push({text:"Settings",url:a+"/settings",icon:"settings"});const r=o.enabled===!1;return{text:o.name||o.slug,key:`project.${o.slug}`,icon:o.icon||"folder",items:i,...r&&{colour:"neutral",badge:{text:"off"}}}})}function it(e,n){for(const t of e)if(!t.hidden&&(t.key==="projects"||!t.key&&(t.text||"").toLowerCase()==="projects")){t.items=Array.isArray(t.items)?t.items.slice():[],t.items.push(...n);return}}function le(e,n){const t=de.includes(n.tone)?n.tone:"info",o=n.label||String(n.count);n.details?R.set(e,n.details):R.delete(e);const s=!n.inventory&&L.some(a=>a.url===e&&!a.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${t}" data-count-for="${m(e)}"${s} data-badge-label="${m(o)}" aria-label="${m(o)}">${m(String(n.count))}</span>`}function ce(e){for(const n of e.querySelectorAll("details")){const t=n.querySelector(":scope > summary");if(!t)continue;let o=0,s=!1;const a=[];for(const l of n.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const f=Number(l.textContent)||0;f&&(o+=f,s||(s=l.classList.contains("sidebar-badge--danger")),a.push({text:l.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:l.dataset.badgeLabel,tone:l.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const u=`folder:${n.dataset.stateKey||t.textContent.trim()}`;let i=t.querySelector(":scope > .sidebar-badge--sum");if(!o){i?.remove(),R.delete(u);continue}i||(i=document.createElement("span"),t.querySelector(".sidebar-text")?.after(i));const r=t.querySelector(".sidebar-text")?.textContent.trim()||"This folder",d=a.map(l=>`${l.text}: ${l.meta}`).join(" \xB7 ");i.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${s?"danger":"info"}`,i.dataset.countFor=u,i.dataset.badgeLabel=d,i.setAttribute("aria-label",d),i.textContent=String(o),R.set(u,{title:r,items:a})}}const R=new Map;function rt(e,n){if(!e.url||!n)return"";const t=n[e.url];return!t||!(t.count>0)&&!t.showZero?"":le(e.url,t)}const de=["info","attention","danger","live"],at={"#/pages":["page","pages"],"#/media":["file in the media library","files in the media library"],"#/menus":["menu","menus"],"#/context-menus":["context menu","context menus"],"#/theme":["element override in the theme","element overrides in the theme"],"#/collections":["collection","collections"],"#/forms":["form","forms"],"#/views":["view","views"],"#/actions":["action","actions"],"#/blocks":["block","blocks"],"#/components":["component","components"],"#/users":["user","users"],"#/plugins":["plugin installed","plugins installed"],"#/system/notifications":["unread notification","unread notifications"]};function D(e,{fallbackLabel:n="",tone:t="info"}={}){const o=Number(typeof e=="object"&&e!==null?e.count:e);if(!Number.isFinite(o)||o<0)return null;const s=typeof e?.label=="string"&&e.label||(n?`${o} - ${n}`:String(o)),a={count:Math.round(o),label:s,tone:de.includes(e?.tone)?e.tone:t};e?.details&&typeof e.details=="object"&&(a.details=e.details),e?.inventory===!0&&(a.inventory=!0),e?.showZero===!0&&(a.showZero=!0);const u=Le(e?.notify);return u&&(a.notify=u),a}function ue(e,n,t=null,{inFrequent:o=!1}={}){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="theme-switcher")return"";if(e&&e.type==="spacer"){const b=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${m(b)}" aria-hidden="true"></div>`}const s=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",a=e.icon?`<span data-icon="${m(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${m(s)}</span>`,u=m(e.text||""),i=e.url?m(e.url):"",r=Array.isArray(e.items)&&e.items.length>0,d=e.colour?te(e.colour):"",l=Oe(e),f=[l.hover&&` data-menu-item-hover="${m(l.hover)}"`,l.active&&` data-menu-item-active="${m(l.active)}"`].filter(Boolean).join(""),h=[d&&`color:${m(d)}`,l.accent&&`--dm-menu-highlight:${m(l.accent)}`].filter(Boolean).join(";"),v=h?` style="${h}"`:"",k=e.url?et(e.url,e.text||"",{inFrequent:o}):"",q=k?` title="${m(k)}"`:"";let F="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const b=e.badge.variant?te(e.badge.variant):"";F=`<span class="dm-menu-badge"${b?` style="background:${m(b)};color:var(--dm-text-inverse,#fff)"`:""}>${m(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const b=ne(e,t),A=S.get(b)!==!1;return`<details data-state-key="${m(b)}"${A?" open":""} data-frequent data-frequent-empty>
2
+ <summary${v}>${a} <span class="sidebar-text">${u}</span></summary>
3
3
  <div class="sidebar-children"><p class="sidebar-frequent-empty">Right-click any Tool and choose <strong>Add to Frequent</strong> to keep it here.</p></div>
4
- </details>`}if(a){const b=ne(e,t),A=S.get(b)!==!1,B=e.items.map(I=>ue(I,n,b,{inFrequent:!!e.frequent})).join(""),j=e.frequent?' data-frequent title="Your Frequent Tools - right-click any Tool to add or remove it"':"";return`<details data-state-key="${m(b)}"${A?" open":""}${j}>
5
- <summary${v}${q}>${l} <span class="sidebar-text">${u}</span>${F}</summary>
4
+ </details>`}if(r){const b=ne(e,t),A=S.get(b)!==!1,B=e.items.map(I=>ue(I,n,b,{inFrequent:!!e.frequent})).join(""),j=e.frequent?' data-frequent title="Your Frequent Tools - right-click any Tool to add or remove it"':"";return`<details data-state-key="${m(b)}"${A?" open":""}${j}>
5
+ <summary${v}${q}>${a} <span class="sidebar-text">${u}</span>${F}</summary>
6
6
  <div class="sidebar-children">${B}</div>
7
- </details>`}return`<a href="${i}" class="sidebar-link" data-url="${i}"${v}${f}${q}>${l} <span class="sidebar-text">${u}</span>${ot(e,n)}${F}</a>`}async function at(e=[],n=[],t=null){const o=y=>y.then(C=>Array.isArray(C)?C.length:Array.isArray(C?.entries)?C.entries.length:0).catch(()=>0),s=fe(e,t),l=new Set(s.map(y=>y.url)),u={pages:"#/pages",media:"#/media",menus:"#/menus","context-menus":"#/context-menus",theme:"#/theme",collections:"#/collections",forms:"#/forms",views:"#/views",actions:"#/actions",blocks:"#/blocks",components:"#/components",users:"#/users",plugins:"#/plugins",notifications:"#/system/notifications"},i=(y,C)=>W(n,y)&&!l.has(u[y])?C():Promise.resolve(0),[a,d,r,f,h,v,w,q,F,b,A,B,j,I]=await Promise.all([i("pages",()=>o(g.pages.list())),i("media",()=>o(g.media.list())),i("menus",()=>o(g.menus.list())),i("context-menus",()=>o(g.contextMenus.list())),i("theme",()=>g.theme.get().then(y=>(y?.overrides||[]).length).catch(()=>0)),i("collections",()=>o(g.collections.list())),i("forms",()=>o(g.forms.list())),i("views",()=>o(g.views.list())),i("actions",()=>o(g.actions.list())),i("blocks",()=>o(g.blocks.list())),i("components",()=>o(g.components.list())),i("users",()=>o(g.users.list())),i("plugins",()=>o(g.plugins.list())),i("notifications",()=>g.system?.notifications?.unreadCount?.().then(y=>y?.count??0).catch(()=>0)??Promise.resolve(0))]),P=await pe(s),U={"#/pages":a,"#/media":d,"#/menus":r,"#/context-menus":f,"#/theme":h,"#/collections":v,"#/forms":w,"#/views":q,"#/actions":F,"#/blocks":b,"#/components":A,"#/users":B,"#/plugins":j,"#/system/notifications":I},z={};for(const[y,C]of Object.entries(U)){if(l.has(y))continue;const[Y,G]=it[y]??["item","items"],O=D(C);O&&(z[y]={...O,label:`${O.count} ${O.count===1?Y:G}`})}return{...z,...P}}let L=[];const rt="dm:sidebar-count",lt=6e4,ct=ke(4);function fe(e=[],n=null){const t=[],o=s=>{if(s&&!(n&&typeof s.url=="string"&&s.url.startsWith("#/")&&!Fe(n,s.url.slice(1)))){s.url&&typeof s.countUrl=="string"&&s.countUrl&&t.push({url:s.url,countUrl:s.countUrl,text:s.text||"",countLabel:s.countLabel||"",inventory:s.inventory===!0});for(const l of[...s.children||[],...s.items||[]])o(l)}};for(const s of e??[])o(s?.item??s);return t}async function me(e,{priority:n=!1}={}){try{const t=new Date,o=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,s=e.countUrl+(e.countUrl.includes("?")?"&":"?")+`today=${o}`,l=k()?.id,u=await ct(()=>!Se()||k()?.id!==l?null:H.get(s,{priority:"low"}),{key:JSON.stringify([l,s]),priority:n});return D(u,{fallbackLabel:e.countLabel||e.text})}catch{return null}}function V(e,n,t){Ae(n,t);const o=[...e.querySelectorAll("a.sidebar-link")].filter(s=>s.dataset.url===n);if(o.length){for(const s of o){const l=s.querySelector(".sidebar-badge[data-count-for]");if(!t||!(t.count>0)&&!t.showZero)l?.remove();else{const u=document.createRange();u.selectNodeContents(s);const i=u.createContextualFragment(le(n,t)).firstElementChild;l?l.replaceWith(i):s.querySelector(".sidebar-text")?.after(i)}}X.set(n,t),ce(e)}}const X=new Map;async function pe(e){const n=await qe(e,{legacy:async t=>{const o=await Promise.all(t.map(async s=>[s.url,await me(s)]));return Object.fromEntries(o)}});return Object.fromEntries(e.map(t=>[t.url,n[t.url]==null?null:D(n[t.url],{fallbackLabel:t.countLabel||t.text})]))}async function Z(e,n=null){const t=k()?.id,o=L;try{const s=n?{[n]:await me(L.find(l=>l.url===n),{priority:!0})}:await pe(L);if(k()?.id!==t||L!==o)return;for(const[l,u]of Object.entries(s)){const i=X.get(l);JSON.stringify(i??null)!==JSON.stringify(u)&&V(e,l,u)}}catch{}}function dt(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(rt,n=>{const t=n.detail||{};if(t.url)if(t.count!==void 0){const o=L.find(s=>s.url===t.url);V(e,t.url,D(t,{fallbackLabel:o?.countLabel||o?.text||""}))}else Z(e,t.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&Z(e)},lt),document.addEventListener("visibilitychange",()=>{document.hidden||Z(e)}),ut(e))}function he(e,n,t){const o=(u,i,a)=>{const d=document.createElement(u);return i&&(d.className=i),a!=null&&(d.textContent=String(a)),d};if(!t){e.textContent=n;return}e.appendChild(o("strong","dm-badge-pop-title",t.title||n)),(t.rows?.length||!t.items?.length)&&e.appendChild(o("p","dm-badge-pop-sub",n));const s=Array.isArray(t.rows)?t.rows.slice(0,6):[];if(s.length){const u=o("dl","dm-badge-pop-rows");for(const i of s)u.appendChild(o("dt",null,i.label)),u.appendChild(o("dd",i.tone==="danger"?"is-danger":null,i.value));e.appendChild(u)}const l=Array.isArray(t.items)?t.items.slice(0,6):[];if(l.length){const u=o("ul","dm-badge-pop-items");for(const i of l){const a=o("li",i.tone==="danger"?"is-danger":null);a.appendChild(o("span","dm-badge-pop-text",i.text)),i.meta&&a.appendChild(o("span","dm-badge-pop-meta",i.meta)),u.appendChild(a)}e.appendChild(u)}else t.empty&&e.appendChild(o("p","dm-badge-pop-empty",t.empty))}function ut(e){let n=null,t=null;const o=()=>e.classList.contains("is-collapsed"),s=()=>{clearTimeout(t),n?.remove(),n=null},l=(d,r)=>{s(),t=setTimeout(()=>{n=document.createElement("div"),n.className="dm-help-pop dm-badge-pop",n.setAttribute("role","tooltip"),r(n),document.body.appendChild(n);const f=d.getBoundingClientRect(),h=n.getBoundingClientRect(),v=Math.min(f.right+10,innerWidth-h.width-8);n.style.left=`${Math.max(8,v)}px`,n.style.top=`${Math.max(8,Math.min(f.top+f.height/2-h.height/2,innerHeight-h.height-8))}px`},150)},u=d=>{const r=d?.dataset.badgeLabel;if(!r)return s();l(d,f=>he(f,r,R.get(d.dataset.countFor)))},i=d=>{const r=d.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",f=d.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");l(d,h=>{const v=f&&R.get(f.dataset.countFor);if(v)return he(h,f.dataset.badgeLabel,{...v,title:v.title||r});if(!f){h.textContent=r;return}const w=document.createElement("strong");w.className="dm-badge-pop-title",w.textContent=r;const q=document.createElement("p");q.className="dm-badge-pop-sub",q.textContent=f.dataset.badgeLabel,h.append(w,q)})},a=d=>{const r=d.closest?.("a.sidebar-link, summary");return r&&e.contains(r)?r:null};e.addEventListener("mouseover",d=>{if(o()){const f=a(d.target);f&&!f.contains(d.relatedTarget)&&i(f);return}const r=d.target.closest?.(".sidebar-badge[data-badge-label]");r&&u(r)}),e.addEventListener("mouseout",d=>{if(o()){const r=a(d.target);r&&!r.contains(d.relatedTarget)&&s();return}d.target.closest?.(".sidebar-badge[data-badge-label]")&&s()}),e.addEventListener("focusin",d=>{const r=d.target.closest?.("a, summary");if(o()&&r&&e.contains(r)&&r.querySelector(":scope > .sidebar-text"))return i(r);const f=r?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");f?u(f):s()}),e.addEventListener("focusout",s),e.addEventListener("click",s),window.addEventListener("scroll",s,!0),window.addEventListener("hashchange",s)}async function ft(e=[],n=null){if(n?.title)return n;if(!W(e,"settings"))return{title:"Domma CMS"};try{const t=await g.settings.get();return{...t?.adminBrand,title:t?.adminBrand?.title||t?.title||"Domma CMS"}}catch{return{title:"Domma CMS"}}}export async function renderAdminSidebar({mount:e,permissions:n,adminScope:t=null,brand:o=null}){const s=$(e).get(0);if(!s)return;const l=Ze(g,k()).catch(()=>{}),u=Promise.resolve().then(()=>H.get("/api/sidebar/registered-items")).catch(()=>[]),i=nt(n);let a,d=null,r=null,f=null,h=null,v=null;try{const c=await g.get("/sidebar/menu");a=Array.isArray(c?.items)?c.items:null,d=c?.variant||null,r=c?.position||null,f=c?.style||null,h=c?.float||null,v=c?.highlight||null}catch{a=null}(!a||!a.some(c=>c&&(!c.hidden||c.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),a=tt.slice());let w=[];try{w=await u||[]}catch{}a=Te(a,se),a=Be(a,w.filter(c=>(c?.item??c)?.url!==se)),a=Oe(a),a=He(a,[Me()]);const q=await i;q.length&&st(a,q),a=Re(a,n,t),a=Ne(a),await l;const F=Ge(a,K(k()),{hidden:Ke(k())});F&&(a=[F,...a]),Qe(a);const b=w.filter(c=>W(n,c?.item?.permission));L=fe(b,t);const A=Ce({user:k(),permissions:n,adminScope:t,registered:b,tree:a,actions:{linkItems:ye,folderItems:ve},fetcher:()=>at(b,n,t)}),B=A.counts,j=await ft(n,o),I=d?` dm-admin-sidebar--${m(d)}`:"";function P(c,x="px"){if(c==null)return c;const p=String(c).trim();return/^\d+(\.\d+)?$/.test(p)?`${p}${x}`:p}let U="";if(f){const c=[];f.fontFamily&&c.push(`font-family: ${f.fontFamily}, sans-serif`),f.fontSize&&c.push(`font-size: ${P(f.fontSize)}`),f.fontWeight&&c.push(`font-weight: ${f.fontWeight}`),f.letterSpacing&&c.push(`letter-spacing: ${P(f.letterSpacing,"em")}`);const x=[];if(c.length&&x.push(`#admin-sidebar .dm-admin-sidebar, #admin-sidebar .dm-admin-sidebar .sidebar-link, #admin-sidebar .dm-admin-sidebar summary, #admin-sidebar .dm-admin-sidebar .sidebar-text { ${c.join("; ")} }`),f.iconSize){const p=P(f.iconSize);x.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${p} !important; height: ${p} !important; }`)}x.length&&(U=`<style data-admin-sidebar-style>${x.join(" ")}</style>`)}const z=Pe(n)?Ie():"",y=String(j.logo||"").trim(),C=/^(?:\/(?!\/)|https?:\/\/)/i.test(y)?y:"",G=`<div class="dm-admin-sidebar-header">${C?`<img src="${m(C)}" alt="" style="width:28px;height:28px;object-fit:contain;flex-shrink:0;">`:`<span data-icon="${m(j.icon||"layout")}"></span>`}<span class="dm-admin-sidebar-title">${m(j.title)}</span>${z}${Ue()}</div>`,O=a.some(c=>c&&c.type==="spacer"&&c.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",xe=`${U}<nav class="dm-admin-sidebar${I}${O}">${G}${a.map(c=>ue(c,B)).join("")}</nav>`,Q=document.createRange();Q.selectNodeContents(s);const $e=Q.createContextualFragment(xe);s.replaceChildren($e),Domma.icons.scan(s),s.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>_e(s));for(const c of L)X.set(c.url,B[c.url]??null);ce(s),dt(s),A.refresh.then(c=>{if(!(!A.isActive()||!s.isConnected))for(const[x,p]of Object.entries(c))V(s,x,p)}),s.removeAttribute("data-menu-hover"),s.removeAttribute("data-menu-active"),s.style.removeProperty("--dm-menu-highlight"),Ee(s,{highlight:v});const T=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(c=>{s.style[c]=""}),s.classList.remove("dm-menu-floating"),T&&(T.style.paddingLeft="",T.style.paddingRight=""),s.__dmGutter=()=>{if(r!=="floating"||!T)return;const c=h&&h.anchor||"TL",x=Number.isFinite(Number(h?.offsetX))?Number(h.offsetX):16,p=s.classList.contains("is-collapsed")?We:parseInt(s.style.width,10)||s.getBoundingClientRect().width||260,N=Math.round(p+x*2)+"px";c==="R"||c==="TR"||c==="BR"?T.style.paddingRight=N:T.style.paddingLeft=N},r==="floating"&&Le(s,{position:"floating",float:h||{}}),s.querySelectorAll("details").forEach(c=>{c.addEventListener("toggle",function(){const x=this.getAttribute("data-state-key");x&&S.set(x,this.open);const p=this.querySelector(":scope > .sidebar-children");if(p)if(this.open){const N=p.scrollHeight;p.style.maxHeight="0",requestAnimationFrame(()=>{p.style.maxHeight=N+"px",p.addEventListener("transitionend",function we(){p.style.maxHeight="none",p.removeEventListener("transitionend",we)})})}else{const N=p.scrollHeight;p.style.maxHeight=N+"px",requestAnimationFrame(()=>{p.style.maxHeight="0"})}})}),ze(s),pt(s),s.dataset.routeWired||(s.dataset.routeWired="1",M.subscribe("router:afterChange",()=>s.__dmSetActive?.()));function ee(){const c=/^#\/(dashboards|my-dashboard)\b/.test(location.hash)?"#/":location.hash||"#/";$(s).find(".sidebar-link").removeClass("active"),$(s).find(`.sidebar-link[data-url="${c}"]`).addClass("active")}ee(),s.__dmSetActive=ee}function ge(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function J(e){return`${location.origin}${location.pathname}${e}`}function _(e,n){const t=k(),o=e(K(t));if(!Je(t,o)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}o.length&&ae(t,!1),be(t),n&&E.toast(n,{type:"success",duration:2500}),ge()}function be(e){Xe(g,e).catch(()=>E.toast("Frequent is saved in this browser only - the server could not keep it.",{type:"warning"}))}function mt(e){if(!ae(k(),e)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}be(k()),E.toast(e?"Frequent hidden. Add a Tool to it from its right-click menu to bring it back.":"Frequent shown.",{type:"success",duration:3500}),ge()}function ye(e){const n=e.dataset.url;if(!n)return[];const t=e.querySelector(".sidebar-text")?.textContent.trim()||n,o=K(k()),s=o.includes(n),l=!!e.closest("[data-frequent]"),u=o.indexOf(n),i=!s&&o.length>=oe;return[{header:t},{label:"Open",icon:"arrow-right",...re(n)&&{shortcut:et(re(n))},action:()=>{location.hash=n}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(J(n),"_blank","noopener")},{type:"divider"},s?{label:"Remove from Frequent",icon:"star",action:()=>_(a=>Ve(a,n),`${t} removed from Frequent.`)}:{label:i?`Frequent is full (${oe})`:"Add to Frequent",icon:"star",disabled:i,action:()=>_(a=>De(a,n),`${t} added to Frequent.`)},...l?[{label:"Move up",icon:"arrow-up",disabled:u<=0,action:()=>_(a=>ie(a,n,-1))},{label:"Move down",icon:"arrow-down",disabled:u<0||u>=o.length-1,action:()=>_(a=>ie(a,n,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(J(n)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(J(n),{type:"info",duration:8e3})}}}]}function ve(e,n,t={}){const o=e.closest("details, [data-frequent]"),s=t.isOpen||(()=>!!o?.open),l=t.setOpen||(a=>{o&&(o.open=a)}),u=e.querySelector(".sidebar-text")?.textContent.trim()||"Folder",i=t.setAll||(a=>n.querySelectorAll("details").forEach(d=>{d.open!==a&&(d.open=a)}));return[{header:u},{label:s()?"Close":"Open",icon:s()?"chevron-up":"chevron-down",action:()=>l(!s())},...t.singleBranch?[]:[{label:"Open every folder",icon:"chevrons-down",action:()=>i(!0)}],{label:t.singleBranch?"Close all menus":"Close every folder",icon:"chevrons-up",action:()=>i(!1)},...o?.hasAttribute("data-frequent-empty")?[{type:"divider"},{label:"Hide Frequent until I add a Tool",icon:"eye-off",action:()=>mt(!0)}]:o?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>_(()=>[],"Frequent cleared.")}]:[]]}function pt(e){e.dataset.ctxWired||typeof E>"u"||!E.contextMenu||(e.dataset.ctxWired="1",E.contextMenu(e,{match:"a.sidebar-link, details > summary",inherit:!1,density:"compact",items:n=>n.matches("summary")?ve(n,e):ye(n)}))}
7
+ </details>`}return`<a href="${i}" class="sidebar-link" data-url="${i}"${v}${f}${q}>${a} <span class="sidebar-text">${u}</span>${rt(e,n)}${F}</a>`}async function lt(e=[],n=[],t=null){const o=y=>y.then(C=>Array.isArray(C)?C.length:Array.isArray(C?.entries)?C.entries.length:0).catch(()=>0),s=fe(e,t),a=new Set(s.map(y=>y.url)),u={pages:"#/pages",media:"#/media",menus:"#/menus","context-menus":"#/context-menus",theme:"#/theme",collections:"#/collections",forms:"#/forms",views:"#/views",actions:"#/actions",blocks:"#/blocks",components:"#/components",users:"#/users",plugins:"#/plugins",notifications:"#/system/notifications"},i=(y,C)=>W(n,y)&&!a.has(u[y])?C():Promise.resolve(0),[r,d,l,f,h,v,k,q,F,b,A,B,j,I]=await Promise.all([i("pages",()=>o(g.pages.list())),i("media",()=>o(g.media.list())),i("menus",()=>o(g.menus.list())),i("context-menus",()=>o(g.contextMenus.list())),i("theme",()=>g.theme.get().then(y=>(y?.overrides||[]).length).catch(()=>0)),i("collections",()=>o(g.collections.list())),i("forms",()=>o(g.forms.list())),i("views",()=>o(g.views.list())),i("actions",()=>o(g.actions.list())),i("blocks",()=>o(g.blocks.list())),i("components",()=>o(g.components.list())),i("users",()=>o(g.users.list())),i("plugins",()=>o(g.plugins.list())),i("notifications",()=>g.system?.notifications?.unreadCount?.().then(y=>y?.count??0).catch(()=>0)??Promise.resolve(0))]),P=await pe(s),U={"#/pages":r,"#/media":d,"#/menus":l,"#/context-menus":f,"#/theme":h,"#/collections":v,"#/forms":k,"#/views":q,"#/actions":F,"#/blocks":b,"#/components":A,"#/users":B,"#/plugins":j,"#/system/notifications":I},z={};for(const[y,C]of Object.entries(U)){if(a.has(y))continue;const[Y,G]=at[y]??["item","items"],O=D(C);O&&(z[y]={...O,label:`${O.count} ${O.count===1?Y:G}`})}return{...z,...P}}let L=[];const ct="dm:sidebar-count",dt=6e4,ut=ke(4);function fe(e=[],n=null){const t=[],o=s=>{if(s&&!(n&&typeof s.url=="string"&&s.url.startsWith("#/")&&!Fe(n,s.url.slice(1)))){s.url&&typeof s.countUrl=="string"&&s.countUrl&&t.push({url:s.url,countUrl:s.countUrl,text:s.text||"",countLabel:s.countLabel||"",inventory:s.inventory===!0});for(const a of[...s.children||[],...s.items||[]])o(a)}};for(const s of e??[])o(s?.item??s);return t}async function me(e,{priority:n=!1}={}){try{const t=new Date,o=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,s=e.countUrl+(e.countUrl.includes("?")?"&":"?")+`today=${o}`,a=w()?.id,u=await ut(()=>!Se()||w()?.id!==a?null:H.get(s,{priority:"low"}),{key:JSON.stringify([a,s]),priority:n});return D(u,{fallbackLabel:e.countLabel||e.text})}catch{return null}}function V(e,n,t){Ae(n,t),t?.notify&&Ee(w()?.id??null,n,t.notify);const o=[...e.querySelectorAll("a.sidebar-link")].filter(s=>s.dataset.url===n);if(o.length){for(const s of o){const a=s.querySelector(".sidebar-badge[data-count-for]");if(!t||!(t.count>0)&&!t.showZero)a?.remove();else{const u=document.createRange();u.selectNodeContents(s);const i=u.createContextualFragment(le(n,t)).firstElementChild;a?a.replaceWith(i):s.querySelector(".sidebar-text")?.after(i)}}X.set(n,t),ce(e)}}const X=new Map;async function pe(e){const n=await qe(e,{legacy:async t=>{const o=await Promise.all(t.map(async s=>[s.url,await me(s)]));return Object.fromEntries(o)}});return Object.fromEntries(e.map(t=>[t.url,n[t.url]==null?null:D(n[t.url],{fallbackLabel:t.countLabel||t.text})]))}async function Z(e,n=null){const t=w()?.id,o=L;try{const s=n?{[n]:await me(L.find(a=>a.url===n),{priority:!0})}:await pe(L);if(w()?.id!==t||L!==o)return;for(const[a,u]of Object.entries(s)){const i=X.get(a);JSON.stringify(i??null)!==JSON.stringify(u)&&V(e,a,u)}}catch{}}function ft(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(ct,n=>{const t=n.detail||{};if(t.url)if(t.count!==void 0){const o=L.find(s=>s.url===t.url);V(e,t.url,D(t,{fallbackLabel:o?.countLabel||o?.text||""}))}else Z(e,t.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&Z(e)},dt),document.addEventListener("visibilitychange",()=>{document.hidden||Z(e)}),mt(e))}function he(e,n,t){const o=(u,i,r)=>{const d=document.createElement(u);return i&&(d.className=i),r!=null&&(d.textContent=String(r)),d};if(!t){e.textContent=n;return}e.appendChild(o("strong","dm-badge-pop-title",t.title||n)),(t.rows?.length||!t.items?.length)&&e.appendChild(o("p","dm-badge-pop-sub",n));const s=Array.isArray(t.rows)?t.rows.slice(0,6):[];if(s.length){const u=o("dl","dm-badge-pop-rows");for(const i of s)u.appendChild(o("dt",null,i.label)),u.appendChild(o("dd",i.tone==="danger"?"is-danger":null,i.value));e.appendChild(u)}const a=Array.isArray(t.items)?t.items.slice(0,6):[];if(a.length){const u=o("ul","dm-badge-pop-items");for(const i of a){const r=o("li",i.tone==="danger"?"is-danger":null);r.appendChild(o("span","dm-badge-pop-text",i.text)),i.meta&&r.appendChild(o("span","dm-badge-pop-meta",i.meta)),u.appendChild(r)}e.appendChild(u)}else t.empty&&e.appendChild(o("p","dm-badge-pop-empty",t.empty))}function mt(e){let n=null,t=null;const o=()=>e.classList.contains("is-collapsed"),s=()=>{clearTimeout(t),n?.remove(),n=null},a=(d,l)=>{s(),t=setTimeout(()=>{n=document.createElement("div"),n.className="dm-help-pop dm-badge-pop",n.setAttribute("role","tooltip"),l(n),document.body.appendChild(n);const f=d.getBoundingClientRect(),h=n.getBoundingClientRect(),v=Math.min(f.right+10,innerWidth-h.width-8);n.style.left=`${Math.max(8,v)}px`,n.style.top=`${Math.max(8,Math.min(f.top+f.height/2-h.height/2,innerHeight-h.height-8))}px`},150)},u=d=>{const l=d?.dataset.badgeLabel;if(!l)return s();a(d,f=>he(f,l,R.get(d.dataset.countFor)))},i=d=>{const l=d.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",f=d.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");a(d,h=>{const v=f&&R.get(f.dataset.countFor);if(v)return he(h,f.dataset.badgeLabel,{...v,title:v.title||l});if(!f){h.textContent=l;return}const k=document.createElement("strong");k.className="dm-badge-pop-title",k.textContent=l;const q=document.createElement("p");q.className="dm-badge-pop-sub",q.textContent=f.dataset.badgeLabel,h.append(k,q)})},r=d=>{const l=d.closest?.("a.sidebar-link, summary");return l&&e.contains(l)?l:null};e.addEventListener("mouseover",d=>{if(o()){const f=r(d.target);f&&!f.contains(d.relatedTarget)&&i(f);return}const l=d.target.closest?.(".sidebar-badge[data-badge-label]");l&&u(l)}),e.addEventListener("mouseout",d=>{if(o()){const l=r(d.target);l&&!l.contains(d.relatedTarget)&&s();return}d.target.closest?.(".sidebar-badge[data-badge-label]")&&s()}),e.addEventListener("focusin",d=>{const l=d.target.closest?.("a, summary");if(o()&&l&&e.contains(l)&&l.querySelector(":scope > .sidebar-text"))return i(l);const f=l?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");f?u(f):s()}),e.addEventListener("focusout",s),e.addEventListener("click",s),window.addEventListener("scroll",s,!0),window.addEventListener("hashchange",s)}async function pt(e=[],n=null){if(n?.title)return n;if(!W(e,"settings"))return{title:"Domma CMS"};try{const t=await g.settings.get();return{...t?.adminBrand,title:t?.adminBrand?.title||t?.title||"Domma CMS"}}catch{return{title:"Domma CMS"}}}export async function renderAdminSidebar({mount:e,permissions:n,adminScope:t=null,brand:o=null}){const s=$(e).get(0);if(!s)return;const a=Ye(g,w()).catch(()=>{}),u=Promise.resolve().then(()=>H.get("/api/sidebar/registered-items")).catch(()=>[]),i=ot(n);let r,d=null,l=null,f=null,h=null,v=null;try{const c=await g.get("/sidebar/menu");r=Array.isArray(c?.items)?c.items:null,d=c?.variant||null,l=c?.position||null,f=c?.style||null,h=c?.float||null,v=c?.highlight||null}catch{r=null}(!r||!r.some(c=>c&&(!c.hidden||c.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),r=st.slice());let k=[];try{k=await u||[]}catch{}r=He(r,se),r=Me(r,k.filter(c=>(c?.item??c)?.url!==se)),r=Te(r),r=Re(r,[Ne()]);const q=await i;q.length&&it(r,q),r=Pe(r,n,t),r=Ie(r),await a;const F=Ve(r,K(w()),{hidden:Xe(w())});F&&(r=[F,...r]),tt(r);const b=k.filter(c=>W(n,c?.item?.permission));L=fe(b,t);const A=Ce({user:w(),permissions:n,adminScope:t,registered:b,tree:r,actions:{linkItems:ye,folderItems:ve},fetcher:()=>lt(b,n,t)}),B=A.counts,j=await pt(n,o),I=d?` dm-admin-sidebar--${m(d)}`:"";function P(c,x="px"){if(c==null)return c;const p=String(c).trim();return/^\d+(\.\d+)?$/.test(p)?`${p}${x}`:p}let U="";if(f){const c=[];f.fontFamily&&c.push(`font-family: ${f.fontFamily}, sans-serif`),f.fontSize&&c.push(`font-size: ${P(f.fontSize)}`),f.fontWeight&&c.push(`font-weight: ${f.fontWeight}`),f.letterSpacing&&c.push(`letter-spacing: ${P(f.letterSpacing,"em")}`);const x=[];if(c.length&&x.push(`#admin-sidebar .dm-admin-sidebar, #admin-sidebar .dm-admin-sidebar .sidebar-link, #admin-sidebar .dm-admin-sidebar summary, #admin-sidebar .dm-admin-sidebar .sidebar-text { ${c.join("; ")} }`),f.iconSize){const p=P(f.iconSize);x.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${p} !important; height: ${p} !important; }`)}x.length&&(U=`<style data-admin-sidebar-style>${x.join(" ")}</style>`)}const z=Ue(n)?_e():"",y=String(j.logo||"").trim(),C=/^(?:\/(?!\/)|https?:\/\/)/i.test(y)?y:"",G=`<div class="dm-admin-sidebar-header">${C?`<img src="${m(C)}" alt="" style="width:28px;height:28px;object-fit:contain;flex-shrink:0;">`:`<span data-icon="${m(j.icon||"layout")}"></span>`}<span class="dm-admin-sidebar-title">${m(j.title)}</span>${z}${We()}</div>`,O=r.some(c=>c&&c.type==="spacer"&&c.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",xe=`${U}<nav class="dm-admin-sidebar${I}${O}">${G}${r.map(c=>ue(c,B)).join("")}</nav>`,Q=document.createRange();Q.selectNodeContents(s);const $e=Q.createContextualFragment(xe);s.replaceChildren($e),Domma.icons.scan(s),s.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>ze(s));for(const c of L)X.set(c.url,B[c.url]??null);ce(s),ft(s),A.refresh.then(c=>{if(!(!A.isActive()||!s.isConnected))for(const[x,p]of Object.entries(c))V(s,x,p)}),s.removeAttribute("data-menu-hover"),s.removeAttribute("data-menu-active"),s.style.removeProperty("--dm-menu-highlight"),Be(s,{highlight:v});const T=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(c=>{s.style[c]=""}),s.classList.remove("dm-menu-floating"),T&&(T.style.paddingLeft="",T.style.paddingRight=""),s.__dmGutter=()=>{if(l!=="floating"||!T)return;const c=h&&h.anchor||"TL",x=Number.isFinite(Number(h?.offsetX))?Number(h.offsetX):16,p=s.classList.contains("is-collapsed")?Ge:parseInt(s.style.width,10)||s.getBoundingClientRect().width||260,N=Math.round(p+x*2)+"px";c==="R"||c==="TR"||c==="BR"?T.style.paddingRight=N:T.style.paddingLeft=N},l==="floating"&&je(s,{position:"floating",float:h||{}}),s.querySelectorAll("details").forEach(c=>{c.addEventListener("toggle",function(){const x=this.getAttribute("data-state-key");x&&S.set(x,this.open);const p=this.querySelector(":scope > .sidebar-children");if(p)if(this.open){const N=p.scrollHeight;p.style.maxHeight="0",requestAnimationFrame(()=>{p.style.maxHeight=N+"px",p.addEventListener("transitionend",function we(){p.style.maxHeight="none",p.removeEventListener("transitionend",we)})})}else{const N=p.scrollHeight;p.style.maxHeight=N+"px",requestAnimationFrame(()=>{p.style.maxHeight="0"})}})}),De(s),gt(s),s.dataset.routeWired||(s.dataset.routeWired="1",M.subscribe("router:afterChange",()=>s.__dmSetActive?.()));function ee(){const c=/^#\/(dashboards|my-dashboard)\b/.test(location.hash)?"#/":location.hash||"#/";$(s).find(".sidebar-link").removeClass("active"),$(s).find(`.sidebar-link[data-url="${c}"]`).addClass("active")}ee(),s.__dmSetActive=ee}function ge(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function J(e){return`${location.origin}${location.pathname}${e}`}function _(e,n){const t=w(),o=e(K(t));if(!Qe(t,o)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}o.length&&re(t,!1),be(t),n&&E.toast(n,{type:"success",duration:2500}),ge()}function be(e){Je(g,e).catch(()=>E.toast("Frequent is saved in this browser only - the server could not keep it.",{type:"warning"}))}function ht(e){if(!re(w(),e)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}be(w()),E.toast(e?"Frequent hidden. Add a Tool to it from its right-click menu to bring it back.":"Frequent shown.",{type:"success",duration:3500}),ge()}function ye(e){const n=e.dataset.url;if(!n)return[];const t=e.querySelector(".sidebar-text")?.textContent.trim()||n,o=K(w()),s=o.includes(n),a=!!e.closest("[data-frequent]"),u=o.indexOf(n),i=!s&&o.length>=oe;return[{header:t},{label:"Open",icon:"arrow-right",...ae(n)&&{shortcut:nt(ae(n))},action:()=>{location.hash=n}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(J(n),"_blank","noopener")},{type:"divider"},s?{label:"Remove from Frequent",icon:"star",action:()=>_(r=>Ze(r,n),`${t} removed from Frequent.`)}:{label:i?`Frequent is full (${oe})`:"Add to Frequent",icon:"star",disabled:i,action:()=>_(r=>Ke(r,n),`${t} added to Frequent.`)},...a?[{label:"Move up",icon:"arrow-up",disabled:u<=0,action:()=>_(r=>ie(r,n,-1))},{label:"Move down",icon:"arrow-down",disabled:u<0||u>=o.length-1,action:()=>_(r=>ie(r,n,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(J(n)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(J(n),{type:"info",duration:8e3})}}}]}function ve(e,n,t={}){const o=e.closest("details, [data-frequent]"),s=t.isOpen||(()=>!!o?.open),a=t.setOpen||(r=>{o&&(o.open=r)}),u=e.querySelector(".sidebar-text")?.textContent.trim()||"Folder",i=t.setAll||(r=>n.querySelectorAll("details").forEach(d=>{d.open!==r&&(d.open=r)}));return[{header:u},{label:s()?"Close":"Open",icon:s()?"chevron-up":"chevron-down",action:()=>a(!s())},...t.singleBranch?[]:[{label:"Open every folder",icon:"chevrons-down",action:()=>i(!0)}],{label:t.singleBranch?"Close all menus":"Close every folder",icon:"chevrons-up",action:()=>i(!1)},...o?.hasAttribute("data-frequent-empty")?[{type:"divider"},{label:"Hide Frequent until I add a Tool",icon:"eye-off",action:()=>ht(!0)}]:o?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>_(()=>[],"Frequent cleared.")}]:[]]}function gt(e){e.dataset.ctxWired||typeof E>"u"||!E.contextMenu||(e.dataset.ctxWired="1",E.contextMenu(e,{match:"a.sidebar-link, details > summary",inherit:!1,density:"compact",items:n=>n.matches("summary")?ve(n,e):ye(n)}))}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.10.37",
3
+ "version": "1.10.39",
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",
@@ -94,6 +94,7 @@
94
94
  "imapflow": "^2.8.0",
95
95
  "mailparser": "^3.12.2",
96
96
  "marked": "^15.0.0",
97
+ "node-forge": "1.4.0",
97
98
  "nodemailer": "10.2.0",
98
99
  "parse5": "^8.0.1",
99
100
  "parse5-htmlparser2-tree-adapter": "^8.0.1",
@@ -71,7 +71,7 @@ function formatSize(bytes) {
71
71
  import {applyIdentity, findIdentity, identityFrom, swapSignature} from './identity.js';
72
72
  import {describeWhen, quickTimes, sendQuestion} from './scheduling.js';
73
73
  import {isSendShortcut} from './keyboard.js';
74
- import {fmtDateTime} from '../ui/dates.js';
74
+ import {fmtDateTime, fmtTime} from '../ui/dates.js';
75
75
  import {applyTemplate, expandTemplate} from './templates.js';
76
76
  import {makeResizable} from './resizable.js';
77
77
  import {whenClosed} from './when-closed.js';
@@ -267,13 +267,6 @@ const FORM = `
267
267
  <option value="high">High</option>
268
268
  <option value="low">Low</option>
269
269
  </select>
270
- <div class="flex items-center gap-1 mt-2">
271
- <label class="form-check">
272
- <input type="checkbox" class="form-check-input" data-ref="receipt">
273
- <span class="form-check-label">Ask for a read receipt</span>
274
- </label>
275
- <span data-help-title="Read receipts" data-help="A request, not a guarantee. Many mail clients ignore it, or ask the recipient first - so no receipt does not mean the message is unread."></span>
276
- </div>
277
270
  </div>
278
271
  <div>
279
272
  <label class="form-label">Subject</label>
@@ -289,6 +282,33 @@ const FORM = `
289
282
  </div>
290
283
  <div class="mail-compose-editor" data-ref="editor"></div>
291
284
  </div>
285
+ <div class="flex items-center gap-3 flex-wrap mail-compose-options">
286
+ <span class="flex items-center gap-1">
287
+ <label class="form-check">
288
+ <input type="checkbox" class="form-check-input" data-ref="receipt">
289
+ <span class="form-check-label">Read receipt</span>
290
+ </label>
291
+ <span data-help-title="Read receipts" data-help="Asks the recipient's mail program to say when the message is opened. A request, not a guarantee: many ignore it, or ask the recipient first - so no receipt does not mean the message is unread."></span>
292
+ </span>
293
+ <span class="flex items-center gap-1">
294
+ <label class="form-check">
295
+ <input type="checkbox" class="form-check-input" data-ref="delivery">
296
+ <span class="form-check-label">Delivery receipt</span>
297
+ </label>
298
+ <span data-help-title="Delivery receipts" data-help="Asks the mail servers on the way to report when the message reaches each recipient's mailbox (a delivery status notification). Arrives as an email from the server. Only servers that support it reply, so silence proves nothing either way."></span>
299
+ </span>
300
+ <span class="flex items-center gap-2" data-bind-hidden="!showSecurity">
301
+ <label class="form-check">
302
+ <input type="checkbox" class="form-check-input" data-ref="sign">
303
+ <span class="form-check-label">Sign</span>
304
+ </label>
305
+ <label class="form-check">
306
+ <input type="checkbox" class="form-check-input" data-ref="encrypt">
307
+ <span class="form-check-label">Encrypt</span>
308
+ </label>
309
+ <span data-help-title="Sign and encrypt (S/MIME)" data-help="Sign adds your certificate's digital signature, so recipients can see the message is from you and unchanged. Encrypt makes it readable only by the recipients (and you): it needs a certificate for every recipient, which you get when they send you a signed message or add in Settings > Security. Subjects are never encrypted."></span>
310
+ </span>
311
+ </div>
292
312
  <div class="flex items-center gap-2 flex-wrap">
293
313
  <button type="button" class="btn btn-sm btn-ghost" data-on-click="pickFiles">Attach files</button>
294
314
  <span data-bind-class="attachTone.value" data-bind-text="attachSummary.value"></span>
@@ -318,7 +338,7 @@ export function openCompose({
318
338
  draft: opened = {}, base, accountId, api, maxAttachmentBytes = 20_000_000,
319
339
  onSent = () => {}, onSaved = () => {}, onQueued = () => {}, contacts = [],
320
340
  identities = [], mailboxAddress = '', templates = [], undoSeconds = 0,
321
- mode = 'new'
341
+ mode = 'new', defaults = null, security = null, prefs = {}
322
342
  }) {
323
343
  // How this message was started, decided once. A reopened draft is never
324
344
  // treated as new: it already carries the signature it was written with,
@@ -356,6 +376,9 @@ export function openCompose({
356
376
  const bccInput = ref('bcc');
357
377
  const prioritySelect = ref('priority');
358
378
  const receiptInput = ref('receipt');
379
+ const deliveryInput = ref('delivery');
380
+ const signInput = ref('sign');
381
+ const encryptInput = ref('encrypt');
359
382
  const subjectInput = ref('subject');
360
383
  const picker = ref('template');
361
384
  const editorHost = ref('editor');
@@ -376,7 +399,6 @@ export function openCompose({
376
399
  // picker folded away would look ordinary.
377
400
  const extrasOpen = M.observable(Boolean(
378
401
  draft.cc?.length || draft.bcc?.length || ['high', 'low'].includes(draft.priority)
379
- || draft.readReceipt === true
380
402
  ));
381
403
  const extrasLabel = M.observable('');
382
404
  // A string: a bound boolean becomes an empty attribute, and aria-expanded needs "true"/"false".
@@ -465,7 +487,28 @@ export function openCompose({
465
487
  bccInput.value = addressesToText(draft.bcc);
466
488
  subjectInput.value = draft.subject ?? '';
467
489
  prioritySelect.value = ['high', 'low'].includes(draft.priority) ? draft.priority : 'normal';
468
- receiptInput.checked = draft.readReceipt === true;
490
+
491
+ // The ticks. A reopened draft keeps what it was saved with; anything else
492
+ // starts from the mailbox's choices (Settings > Composing), unless the
493
+ // caller already said - a message handed back by Undo carries its own.
494
+ const tick = key => (openedAs === 'edit' ? draft[key] === true : (draft[key] ?? defaults?.[key]) === true);
495
+ receiptInput.checked = tick('readReceipt');
496
+ deliveryInput.checked = tick('deliveryReceipt');
497
+ // Signing and encrypting where this edition can (Email Pro) and the
498
+ // mailbox has a certificate. A message that ASKED for them (a reopened
499
+ // draft, Undo, the mailbox's default) keeps the tick even when they cannot
500
+ // be done now - an expired certificate, a profile that did not load - so
501
+ // the server refuses it with the reason rather than it quietly going out
502
+ // in the clear. Only an unticked box that cannot be used is greyed.
503
+ const wanted = {sign: tick('sign'), encrypt: tick('encrypt')};
504
+ const showSecurity = security?.available === true || wanted.sign || wanted.encrypt;
505
+ for (const [input, can, key] of [[signInput, security?.canSign, 'sign'], [encryptInput, security?.canEncrypt, 'encrypt']]) {
506
+ input.checked = wanted[key];
507
+ input.disabled = can !== true && !wanted[key];
508
+ if (can !== true && security?.reason) input.closest('label').title = security.reason;
509
+ // Once unticked, a box that cannot be used is greyed for good.
510
+ input.addEventListener('change', () => { if (!input.checked && can !== true) input.disabled = true; });
511
+ }
469
512
 
470
513
  /**
471
514
  * Does this identity have a signature that would have been added?
@@ -572,7 +615,10 @@ export function openCompose({
572
615
 
573
616
  let editor = null;
574
617
  let fontControls = null;
575
- if (typeof E?.editor === 'function') {
618
+ // Rich unless this browser chose plain text - but a draft written with
619
+ // formatting reopens with it, or saving it again would strip it.
620
+ const wantRich = prefs?.editor !== 'plain' || (openedAs === 'edit' && Boolean(draft.html));
621
+ if (wantRich && typeof E?.editor === 'function') {
576
622
  try {
577
623
  editor = E.editor(editorHost, {mode: 'rich', height: '34vh'});
578
624
  editor.setValue(draft.html || textToHtml(draft.body ?? ''));
@@ -583,6 +629,13 @@ export function openCompose({
583
629
  }
584
630
  if (!editor) editorHost.replaceWith(bodyInput);
585
631
 
632
+ // Spelling: the browser's own checker, on the fields that hold words.
633
+ // Never on addresses - every one would be underlined.
634
+ const spell = prefs?.spellcheck !== false;
635
+ for (const input of [toInput, ccInput, bccInput]) input.spellcheck = false;
636
+ for (const input of [subjectInput, bodyInput]) input.spellcheck = spell;
637
+ for (const el of editorHost.querySelectorAll('[contenteditable]')) el.spellcheck = spell;
638
+
586
639
  // ---- Attachments -------------------------------------------------------
587
640
 
588
641
  fileInput.addEventListener('change', async () => {
@@ -623,6 +676,16 @@ export function openCompose({
623
676
  ccInput.value.trim(), bccInput.value.trim(), attachments.peek().length]);
624
677
  }
625
678
  let savedSnapshot = null; // set once the window is built (below)
679
+ // The last state written to Drafts by either kind of save, so the timer
680
+ // does not save the same text twice.
681
+ let persistedSnapshot = null;
682
+ let savedByHand = false;
683
+ /** A save in flight - Send waits for it, so `replaces` names the draft it wrote. */
684
+ let savePromise = null;
685
+ // Only automatic saves so far, of a message that was not a draft when it
686
+ // opened: discarding it then takes the automatic draft away too, as
687
+ // every mail program with autosave does.
688
+ let autoOnly = false;
626
689
 
627
690
  /**
628
691
  * Whether closing now would lose something.
@@ -633,7 +696,11 @@ export function openCompose({
633
696
  * count as unsaved work until the reader actually changes something.
634
697
  */
635
698
  function unsaved() {
636
- return savedSnapshot !== null && snapshot() !== savedSnapshot;
699
+ if (savedSnapshot === null) return false;
700
+ const now = snapshot();
701
+ if (now === savedSnapshot) return false;
702
+ // Written by an automatic save into a draft that stays: nothing to lose.
703
+ return !(now === persistedSnapshot && !autoOnly);
637
704
  }
638
705
 
639
706
  /** The question the close guard asks, for what would be lost. */
@@ -662,6 +729,9 @@ export function openCompose({
662
729
  bcc: textToAddresses(bccInput.value),
663
730
  priority: prioritySelect.value,
664
731
  readReceipt: receiptInput.checked,
732
+ deliveryReceipt: deliveryInput.checked,
733
+ sign: signInput.checked,
734
+ encrypt: encryptInput.checked,
665
735
  // Which of the mailbox's identities is sending. Null where there
666
736
  // is only one, which is what the server reads as "the default".
667
737
  identityId: currentIdentity?.id ?? null,
@@ -682,19 +752,39 @@ export function openCompose({
682
752
  *
683
753
  * @returns {Promise<object|null>} the server's answer, or null if it failed
684
754
  */
685
- async function saveDraft() {
755
+ async function saveDraft({auto = false} = {}) {
756
+ // One save at a time, in order - a chain, so two waiting saves cannot
757
+ // start together with the same `replaces` and leave two drafts. Send
758
+ // waits on the tail, so it names the latest draft.
759
+ const run = (savePromise ?? Promise.resolve()).catch(() => {}).then(() => saveDraftNow({auto}));
760
+ savePromise = run;
761
+ try { return await run; } finally { if (savePromise === run) savePromise = null; }
762
+ }
763
+
764
+ async function saveDraftNow({auto = false} = {}) {
686
765
  saving.value = true;
687
- say('Saving…');
766
+ if (!auto) say('Saving…');
688
767
  try {
689
768
  const params = new URLSearchParams({account: accountId});
769
+ const taken = snapshot();
690
770
  const result = await api(`${base}/drafts?${params}`, 'POST', {
691
771
  ...collect(),
692
772
  replaces: savedUid
693
773
  });
694
774
  savedUid = result.uid ?? savedUid;
695
- // Saved is the new baseline: closing now loses nothing.
696
- savedSnapshot = snapshot();
697
- say(`Saved to ${result.savedTo}.`, 'ok');
775
+ persistedSnapshot = taken;
776
+ if (auto) {
777
+ // NOT the close question's baseline: an automatic save is a
778
+ // safety net, and closing still asks whether to keep it.
779
+ autoOnly = !savedByHand && !opened.savedUid;
780
+ say(`Draft saved automatically at ${fmtTime(new Date())}.`);
781
+ } else {
782
+ // Saved is the new baseline: closing now loses nothing.
783
+ savedSnapshot = taken;
784
+ savedByHand = true;
785
+ autoOnly = false;
786
+ say(`Saved to ${result.savedTo}.`, 'ok');
787
+ }
698
788
  // The Drafts folder now holds a different set of UIDs, so a list
699
789
  // showing it is out of date whether or not the window closes.
700
790
  onSaved(result);
@@ -767,6 +857,9 @@ export function openCompose({
767
857
  async function send() {
768
858
  const at = scheduledAt.peek();
769
859
  sending.value = true;
860
+ // An automatic save may be writing a draft right now; sending before
861
+ // it lands would leave that draft behind once the message has gone.
862
+ if (savePromise) await savePromise.catch(() => {});
770
863
  say(at ? 'Scheduling…' : 'Sending…');
771
864
 
772
865
  try {
@@ -815,7 +908,7 @@ export function openCompose({
815
908
  const stopHelp = helpPopovers(form, {position: 'bottom'});
816
909
 
817
910
  const bindings = M.applyBindings({
818
- hasFrom, hasTemplates, extrasOpen, extrasLabel, extrasAria, attachRows, attachSummary, attachTone,
911
+ hasFrom, hasTemplates, showSecurity, extrasOpen, extrasLabel, extrasAria, attachRows, attachSummary, attachTone,
819
912
  statusText, statusClass, saving, cannotSend, sendLabel, sendTitle
820
913
  }, form, {
821
914
  methods: {
@@ -907,7 +1000,27 @@ export function openCompose({
907
1000
  // even a new message opens "changed". Comparing against what was actually
908
1001
  // put in front of the person is both the fix and the only baseline that
909
1002
  // stays true once anything else seeds the body.
910
- setTimeout(() => { if (savedSnapshot === null) savedSnapshot = snapshot(); }, 0);
1003
+ setTimeout(() => {
1004
+ if (savedSnapshot === null) savedSnapshot = snapshot();
1005
+ if (persistedSnapshot === null) persistedSnapshot = savedSnapshot;
1006
+ }, 0);
1007
+
1008
+ // Saving as you go, when this browser asked for it (Settings).
1009
+ const autosaveMs = Math.max(0, Number(prefs?.autosaveMinutes) || 0) * 60_000;
1010
+ const autosaveTimer = autosaveMs ? setInterval(() => {
1011
+ if (closed || saving.peek() || sending.peek() || persistedSnapshot === null) return;
1012
+ if (snapshot() === persistedSnapshot) return;
1013
+ saveDraft({auto: true});
1014
+ }, autosaveMs) : null;
1015
+
1016
+ /** Take an automatic-only draft away with the discarded message. Best effort. */
1017
+ function dropAutoDraft() {
1018
+ if (!autoOnly || !savedUid) return;
1019
+ const params = new URLSearchParams({account: accountId});
1020
+ api(`${base}/drafts/${encodeURIComponent(savedUid)}?${params}`, 'DELETE')
1021
+ .then(() => onSaved({uid: null, removed: savedUid}))
1022
+ .catch(() => { /* a leftover draft is untidy, not lost work */ });
1023
+ }
911
1024
 
912
1025
  // Every way out asks when something would be lost - Discard, the X, Escape
913
1026
  // and the backdrop all call close(). Once closed it goes straight through:
@@ -927,6 +1040,10 @@ export function openCompose({
927
1040
  asking = false;
928
1041
  if (!ok) return undefined;
929
1042
  }
1043
+ // Discarded (asked, or nothing worth asking about): an automatic-only
1044
+ // draft goes with it. Not on a forced close - Send and leaving the
1045
+ // screen have dealt with the draft themselves.
1046
+ if (!closed && force !== true) dropAutoDraft();
930
1047
  if (closed) return undefined;
931
1048
  closed = true;
932
1049
  return rawClose();
@@ -935,6 +1052,7 @@ export function openCompose({
935
1052
  // The editor is a live component with its own listeners and autosave.
936
1053
  whenClosed(slideover, () => {
937
1054
  closed = true;
1055
+ if (autosaveTimer) clearInterval(autosaveTimer);
938
1056
  // Before the editor: it closes an open menu (left on <body> by a
939
1057
  // Ctrl+Enter send) and drops its dropdowns and window listener.
940
1058
  fontControls?.destroy();
@@ -1 +1 @@
1
- #view-container.mail-fullbleed{max-width:none}.mail-layout [hidden],.dm-slideover [hidden]{display:none!important}.mail-reader-summary{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap}.mail-header-toggle,.mail-extras-toggle{padding:0!important;font-size:.78rem;opacity:.75;text-decoration:underline}.mail-header-toggle:hover,.mail-extras-toggle:hover{opacity:1}.mail-header-details{display:grid;grid-template-columns:max-content 1fr;gap:.15rem .75rem;margin:.5rem 0 0;font-size:.8rem}.mail-header-details dt{opacity:.65}.mail-header-details dd{margin:0;overflow-wrap:anywhere}.mail-layout{display:grid;grid-template-columns:var(--mail-col-1, 190px) auto var(--mail-col-2, 340px) auto 1fr;height:calc(100vh - 140px);min-height:320px;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 8px);overflow:hidden;background:var(--dm-surface)}.mail-folders-pane,.mail-list-pane{display:flex;flex-direction:column;border-right:1px solid var(--dm-border);min-width:0;min-height:0}.mail-reader-pane{display:flex;flex-direction:column;min-width:0;min-height:0}.mail-pane-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.5rem .75rem;font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-text-muted);border-bottom:1px solid var(--dm-border);flex-shrink:0}.mail-folders-list,.mail-list{overflow-y:auto;flex:1;min-height:0}.mail-search{display:flex;align-items:center;gap:.4rem;padding:.4rem .6rem;border-bottom:1px solid var(--dm-border);color:var(--dm-text-muted);flex-shrink:0}.mail-search input{flex:1;min-width:0;font-size:.85rem;padding:.25rem .4rem}.mail-search-note{padding:.3rem .6rem;font-size:.74rem;color:var(--dm-text-muted);border-bottom:1px solid var(--dm-border);flex-shrink:0}.mail-slideover-resizer{padding:0;position:absolute;left:0;top:0;bottom:0;width:6px;cursor:col-resize;z-index:2;background:transparent}.mail-slideover-resizer:hover,.is-resizing>.mail-slideover-resizer{background:var(--dm-primary);opacity:.7}.is-resizing{user-select:none;transition:none!important}.mail-selection{display:flex;align-items:center;gap:.35rem;flex-wrap:wrap;padding:.35rem .6rem;font-size:.78rem;background:var(--dm-primary);color:var(--dm-primary-text);flex-shrink:0}.mail-selection span{flex:1;min-width:0}.mail-selection .btn{color:var(--dm-primary-text);border:1px solid currentColor;background:transparent}.mail-selection .btn:hover{background:color-mix(in srgb,var(--dm-primary-text) 15%,transparent)}.mail-item.is-selected{background:var(--dm-active-bg);box-shadow:inset 3px 0 0 var(--dm-primary)}.mail-undo{display:flex;align-items:center;gap:.5rem;padding:.4rem .6rem;font-size:.78rem;background:var(--dm-active-bg);border-bottom:1px solid var(--dm-border);flex-shrink:0}.mail-undo span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-splitter{width:5px;cursor:col-resize;background:var(--dm-border);flex-shrink:0;background-clip:content-box;border-left:2px solid transparent;border-right:2px solid transparent;box-sizing:content-box}.mail-splitter:hover,.mail-layout.is-resizing .mail-splitter{background-color:var(--dm-primary)}.mail-layout.is-resizing{user-select:none}.mail-layout.is-resizing .mail-folders-pane,.mail-layout.is-resizing .mail-list-pane,.mail-layout.is-resizing .mail-reader-pane,.mail-layout.is-resizing iframe{pointer-events:none}.mail-unread-filter{display:flex;align-items:center;gap:.3rem;font-size:.7rem;text-transform:none;letter-spacing:0;cursor:pointer;margin-left:auto;margin-right:.25rem}.mail-unread-pill{flex-shrink:0;min-width:1.15rem;padding:0 .3rem;border-radius:999px;background:var(--dm-primary);color:var(--dm-primary-text);font-size:.66rem;font-weight:700;line-height:1.35;text-align:center}.mail-folder.is-context .mail-folder-name{opacity:.6}.mail-account{display:flex;align-items:center;gap:.4rem;width:100%;padding:.45rem .6rem;border:none;background:var(--dm-surface);cursor:pointer;text-align:left;font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--dm-text-muted)}.mail-account:hover{background:var(--dm-hover-bg)}.mail-account.is-active{color:var(--dm-text);position:sticky;top:0;z-index:1;background:var(--dm-surface)}.mail-account-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-folder{display:flex;align-items:center;gap:.5rem;padding:.45rem .75rem;font-size:.875rem;cursor:pointer;border:none;background:none;width:100%;text-align:left;color:var(--dm-text)}.mail-folder:hover{background:var(--dm-hover-bg)}.mail-folder.is-active{background:var(--dm-active-bg);color:var(--dm-primary);font-weight:600}.mail-folder.is-unselectable{opacity:.6;cursor:default}.mail-folder.is-unselectable:hover{background:none}.mail-folder-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item{display:block;width:100%;text-align:left;padding:.55rem .75rem;border:none;border-bottom:1px solid var(--dm-border);background:none;cursor:pointer;color:var(--dm-text)}.mail-item:hover{background:var(--dm-hover-bg)}.mail-item.is-active{background:var(--dm-active-bg)}.mail-item-top{display:flex;justify-content:space-between;gap:.5rem;align-items:baseline}.mail-item-from{font-size:.85rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item.is-unread .mail-item-from,.mail-item.is-unread .mail-item-subject{font-weight:700}.mail-item-date{font-size:.72rem;color:var(--dm-text-muted);flex-shrink:0}.mail-item-subject{font-size:.82rem;margin-top:.1rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item-meta{display:flex;align-items:center;gap:.35rem;margin-top:.15rem;color:var(--dm-text-muted)}.mail-pager{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.4rem .6rem;border-top:1px solid var(--dm-border);font-size:.75rem;color:var(--dm-text-muted);flex-shrink:0}.mail-reader-head{padding:.75rem 1rem;border-bottom:1px solid var(--dm-border);flex-shrink:0}.mail-reader-head:empty{display:none}.mail-reader-subject{font-size:1.05rem;font-weight:600;margin:0 0 .3rem;color:var(--dm-text)}.mail-reader-actions{display:flex;gap:.25rem;margin:.1rem 0 .35rem;flex-wrap:wrap}.mail-reader-meta{font-size:.8rem;color:var(--dm-text-muted)}.mail-reader-body{flex:1;position:relative;overflow:hidden;display:flex;flex-direction:column;min-height:0;background:var(--dm-surface)}.mail-reader-body iframe{width:100%;flex:1;border:none;display:block;background:#fff}.mail-notice{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;padding:.4rem .75rem;font-size:.78rem;background:var(--dm-warning);color:var(--dm-warning-text);border-bottom:1px solid var(--dm-border)}.mail-notice .btn{color:var(--dm-warning-text);border:1px solid currentColor;background:transparent;opacity:.95}.mail-notice .btn:hover{background:color-mix(in srgb,var(--dm-warning-text) 15%,transparent);opacity:1}.mail-attachments{display:flex;flex-wrap:wrap;gap:.4rem;padding:.5rem .75rem;border-bottom:1px solid var(--dm-border);background:var(--dm-background-alt)}.mail-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;height:100%;color:var(--dm-text-muted);text-align:center;padding:1rem}.mail-empty p{margin:0;font-size:.875rem}@media(max-width:900px){.mail-layout{grid-template-columns:1fr;height:auto!important}.mail-splitter{display:none}.mail-folders-pane,.mail-list-pane{border-right:none;border-bottom:1px solid var(--dm-border)}.mail-folders-list,.mail-list{max-height:240px}.mail-reader-body{min-height:60vh}}.mail-box-row{display:flex;align-items:center;gap:.5rem;padding:.5rem .6rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 6px)}.mail-default-badge{margin-left:.4rem;font-size:.7rem;font-weight:500;vertical-align:middle}.mail-item.is-dragging{opacity:.45}.mail-folder.is-drop-target{background:var(--dm-primary);color:var(--dm-primary-text);outline:2px solid var(--dm-primary);outline-offset:-2px}.mail-folder.is-drop-target .mail-folder-name,.mail-folder.is-drop-target .mail-unread-pill{color:inherit}.mail-item.is-dragging *{pointer-events:none}.mail-item{position:relative}.mail-item-tick{position:absolute;left:.45rem;top:.55rem;display:inline-flex;opacity:0;color:var(--dm-text-muted);transition:opacity .12s ease;padding:2px;margin:-2px;border-radius:var(--dm-radius-sm, 3px)}.mail-item:hover .mail-item-tick,.mail-item:focus-visible .mail-item-tick,.mail-list.is-selecting .mail-item-tick,.mail-item-tick[aria-checked=true]{opacity:1}.mail-item-tick[aria-checked=true]{color:var(--dm-primary)}.mail-item-tick:hover{color:var(--dm-primary);background:var(--dm-hover-bg)}.mail-item-top,.mail-item-subject,.mail-item-meta{padding-left:1.5rem}.mail-select-all{display:inline-flex;align-items:center;margin-right:.1rem;cursor:pointer}.mail-select-all input{cursor:pointer;margin:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mail-item-meta>span{display:inline-flex}.mail-item-meta .is-replied{color:var(--dm-success)}.mail-item-meta .is-forwarded{color:var(--dm-info, var(--dm-primary))}.mail-item-meta .is-high{color:var(--dm-danger)}.mail-item-meta .is-low{color:var(--dm-text-muted);opacity:.7}.mail-folder.is-hidden-folder .mail-folder-name{opacity:.5;font-style:italic}.mail-thread-chip{display:inline-flex;align-items:center;margin-left:.4rem;padding:0 .35rem;border-radius:999px;font-size:.68rem;font-weight:700;line-height:1.5;color:var(--dm-text-muted);background:var(--dm-active-bg);cursor:pointer}.mail-thread-chip:hover{color:var(--dm-primary)}.mail-thread-chip.is-unread{background:var(--dm-primary);color:var(--dm-primary-text)}.mail-item.is-in-thread{padding-left:1.6rem;box-shadow:inset 3px 0 0 var(--dm-border)}.mail-item.is-in-thread .mail-item-from{font-size:.8rem}.mail-pane-head .btn.is-on{color:var(--dm-primary);background:var(--dm-active-bg)}#mail-outbox-btn{font-size:.72rem;font-weight:700}#mail-outbox-btn.is-failed{color:var(--dm-danger)}.mail-attachment{display:inline-flex;align-items:center;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 6px);overflow:hidden}.mail-attachment .btn{border:none;border-radius:0}.mail-attachment-save{padding-left:.4rem!important;padding-right:.4rem!important;border-left:1px solid var(--dm-border)!important;opacity:.7}.mail-attachment-save:hover{opacity:1;color:var(--dm-primary)}.mail-section-heading{margin:1rem 0 .1rem;font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-text-muted)}.mail-section-heading:first-child{margin-top:.25rem}.mail-section-form .domma-form{margin:0}.mail-section-form .domma-form-field:last-child{margin-bottom:0}.mail-item-card{margin:0}.mail-item-card .card-header{padding:.5rem .6rem}.mail-item-card .card-body{padding:.6rem}.mail-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.mail-status{min-height:1.2em}.mail-empty-line{margin:.25rem 0}.mail-editor-fallback{min-height:8rem;font-family:inherit;resize:vertical}.mail-clause{display:flex;gap:.3rem;align-items:center;flex-wrap:wrap}.mail-clause .form-select,.mail-clause .form-input{width:auto;flex:0 1 auto}.mail-clause .mail-clause-value{flex:1 1 8rem;min-width:8rem}.mail-section-list{display:flex;flex-direction:column;gap:.5rem}.mail-scroll-sm{max-height:20vh;overflow-y:auto}.mail-scroll-md{max-height:46vh;overflow-y:auto}.mail-section-form .domma-form-field:has(select) .form-label .text-danger{display:none}.mail-compose-form{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.6rem;height:100%}.mail-compose-body{display:flex;flex-direction:column;flex:1;min-height:0}.mail-compose-editor{flex:1;min-height:0}.mail-compose-textarea{flex:1;min-height:34vh;font-family:inherit;resize:vertical}.mail-attach-list{display:flex;flex-direction:column;gap:.2rem}.mail-template-row{margin:-.15rem 0 .35rem}.mail-template-picker{max-width:16rem}.mail-suggest-row{display:flex;flex-direction:column;line-height:1.25}.mail-suggest-detail{opacity:.7}.mail-rule-off{opacity:.55}.mail-panel{padding:.25rem 0 .5rem}.mail-panel-stack{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:1rem}.mail-form-stack{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem}.mail-dialog-stack{display:flex;flex-direction:column;gap:.5rem;padding:.25rem 0}.mail-hint{margin-top:.2rem}.mail-box-sub{overflow:hidden;text-overflow:ellipsis}.mail-add-btn{align-self:flex-start;margin-top:.5rem}.mail-none{margin:0;padding:.5rem .6rem}.mail-kv{padding:.5rem 0;display:flex;flex-direction:column;gap:.35rem}.mail-kv-row{display:flex;gap:.75rem}.mail-kv-key{min-width:8.5rem;flex-shrink:0}.mail-toggle-row{display:flex;align-items:center;gap:.5rem;margin-bottom:.75rem}.mail-sender-list{display:flex;flex-direction:column;gap:.2rem;max-height:40vh;overflow-y:auto}.mail-folder-pick{display:flex;align-items:center;gap:.5rem;padding:.3rem .4rem;border-radius:var(--dm-radius-md, 6px)}.mail-diag-log{width:100%;height:30vh;margin-top:.5rem;font-family:monospace;font-size:.72rem}.mail-facts{display:flex;flex-direction:column;gap:.2rem;margin-bottom:.75rem}.mail-item-more{position:absolute;right:.4rem;bottom:.35rem;display:inline-flex;align-items:center;justify-content:center;min-width:1.4rem;height:1.4rem;border-radius:var(--dm-radius-sm, 3px);font-size:1rem;line-height:1;color:var(--dm-text-muted);background:inherit;opacity:0;transition:opacity .12s ease}.mail-item:hover .mail-item-more,.mail-item:focus-visible .mail-item-more,.mail-item.is-active .mail-item-more{opacity:1}.mail-item-more:hover{color:var(--dm-primary)}.mail-section-intro{display:flex;align-items:center;gap:.35rem;margin:0 0 .75rem;padding-bottom:.5rem;border-bottom:1px solid var(--dm-border)}.mail-pager-middle{display:flex;align-items:center;gap:.5rem;min-width:0}.mail-page-size{font:inherit;color:inherit;background:transparent;border:1px solid var(--dm-border);border-radius:4px;padding:.1rem .25rem}.mail-auth-choice{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;padding-bottom:.5rem;border-bottom:1px solid var(--dm-border)}.mail-oauth{display:flex;flex-direction:column;gap:.75rem}.mail-oauth-warn{margin:0;padding:.5rem .7rem;border:1px solid var(--dm-warning);border-radius:var(--dm-radius-md);color:var(--dm-warning-text)}.mail-oauth-state{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.mail-oauth-chip{margin-left:.4rem;border:1px solid var(--dm-border);background:var(--dm-surface);color:var(--dm-text-muted)}.mail-oauth-state .mail-oauth-chip{margin-left:0}.mail-oauth-chip--ok{border-color:var(--dm-success);color:var(--dm-success)}.mail-oauth-chip--bad{border-color:var(--dm-danger);color:var(--dm-danger)}.mail-oauth-guide{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);padding:.5rem .75rem;background:var(--dm-background-alt)}.mail-oauth-guide>summary{cursor:pointer;font-weight:600}.mail-oauth-guide-body ol{padding-left:1.2rem;margin:.5rem 0 0}.mail-oauth-guide-body li{margin-bottom:.45rem}.mail-oauth-code{word-break:break-all;user-select:all}.mail-loading-slot{display:block;min-height:3rem;padding:.5rem .6rem}.mail-row-more{flex-shrink:0;margin-left:auto;display:inline-flex;align-items:center;justify-content:center;min-width:1.3rem;height:1.3rem;border-radius:var(--dm-radius-sm);font-size:.95rem;line-height:1;color:var(--dm-text-muted);opacity:0;transition:opacity .12s ease}.mail-unread-pill+.mail-row-more{margin-left:0}.mail-folder:hover .mail-row-more,.mail-folder:focus-visible .mail-row-more,.mail-folder.is-active .mail-row-more,.mail-account:hover .mail-row-more,.mail-account:focus-visible .mail-row-more,.mail-account.is-active .mail-row-more{opacity:1}.mail-row-more:hover{color:var(--dm-primary)}.mail-box-row .mail-row-more{opacity:1;margin-left:0}.mail-box-row:focus-visible{outline:2px solid var(--dm-primary);outline-offset:-2px}.mail-restored-note{margin:0;padding:.4rem .6rem;border-left:3px solid var(--dm-warning);background:var(--dm-background-alt)}.mail-font-controls .mail-font-btn{width:auto;min-width:3.5rem;max-width:10rem;padding:0 .5rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.8125rem}.domma-dropdown-menu:has(.mail-font-item),.domma-dropdown-menu:has(.mail-size-item){max-height:min(60vh,26rem);overflow-y:auto;z-index:10050}.domma-dropdown-item:has(.mail-font-item){display:flex;align-items:center;justify-content:space-between;gap:.75rem}.mail-font-item{font-size:.9375rem}.mail-font-web{font-size:.6875rem;color:var(--dm-text-muted);text-transform:uppercase;letter-spacing:.04em}
1
+ #view-container.mail-fullbleed{max-width:none}.mail-layout [hidden],.dm-slideover [hidden]{display:none!important}.mail-reader-summary{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap}.mail-header-toggle,.mail-extras-toggle{padding:0!important;font-size:.78rem;opacity:.75;text-decoration:underline}.mail-header-toggle:hover,.mail-extras-toggle:hover{opacity:1}.mail-header-details{display:grid;grid-template-columns:max-content 1fr;gap:.15rem .75rem;margin:.5rem 0 0;font-size:.8rem}.mail-header-details dt{opacity:.65}.mail-header-details dd{margin:0;overflow-wrap:anywhere}.mail-layout{display:grid;grid-template-columns:var(--mail-col-1, 190px) auto var(--mail-col-2, 340px) auto 1fr;height:calc(100vh - 140px);min-height:320px;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 8px);overflow:hidden;background:var(--dm-surface)}.mail-folders-pane,.mail-list-pane{display:flex;flex-direction:column;border-right:1px solid var(--dm-border);min-width:0;min-height:0}.mail-reader-pane{display:flex;flex-direction:column;min-width:0;min-height:0}.mail-pane-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.5rem .75rem;font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-text-muted);border-bottom:1px solid var(--dm-border);flex-shrink:0}.mail-folders-list,.mail-list{overflow-y:auto;flex:1;min-height:0}.mail-search{display:flex;align-items:center;gap:.4rem;padding:.4rem .6rem;border-bottom:1px solid var(--dm-border);color:var(--dm-text-muted);flex-shrink:0}.mail-search input{flex:1;min-width:0;font-size:.85rem;padding:.25rem .4rem}.mail-search-note{padding:.3rem .6rem;font-size:.74rem;color:var(--dm-text-muted);border-bottom:1px solid var(--dm-border);flex-shrink:0}.mail-slideover-resizer{padding:0;position:absolute;left:0;top:0;bottom:0;width:6px;cursor:col-resize;z-index:2;background:transparent}.mail-slideover-resizer:hover,.is-resizing>.mail-slideover-resizer{background:var(--dm-primary);opacity:.7}.is-resizing{user-select:none;transition:none!important}.mail-selection{display:flex;align-items:center;gap:.35rem;flex-wrap:wrap;padding:.35rem .6rem;font-size:.78rem;background:var(--dm-primary);color:var(--dm-primary-text);flex-shrink:0}.mail-selection span{flex:1;min-width:0}.mail-selection .btn{color:var(--dm-primary-text);border:1px solid currentColor;background:transparent}.mail-selection .btn:hover{background:color-mix(in srgb,var(--dm-primary-text) 15%,transparent)}.mail-item.is-selected{background:var(--dm-active-bg);box-shadow:inset 3px 0 0 var(--dm-primary)}.mail-undo{display:flex;align-items:center;gap:.5rem;padding:.4rem .6rem;font-size:.78rem;background:var(--dm-active-bg);border-bottom:1px solid var(--dm-border);flex-shrink:0}.mail-undo span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-splitter{width:5px;cursor:col-resize;background:var(--dm-border);flex-shrink:0;background-clip:content-box;border-left:2px solid transparent;border-right:2px solid transparent;box-sizing:content-box}.mail-splitter:hover,.mail-layout.is-resizing .mail-splitter{background-color:var(--dm-primary)}.mail-layout.is-resizing{user-select:none}.mail-layout.is-resizing .mail-folders-pane,.mail-layout.is-resizing .mail-list-pane,.mail-layout.is-resizing .mail-reader-pane,.mail-layout.is-resizing iframe{pointer-events:none}.mail-unread-filter{display:flex;align-items:center;gap:.3rem;font-size:.7rem;text-transform:none;letter-spacing:0;cursor:pointer;margin-left:auto;margin-right:.25rem}.mail-unread-pill{flex-shrink:0;min-width:1.15rem;padding:0 .3rem;border-radius:999px;background:var(--dm-primary);color:var(--dm-primary-text);font-size:.66rem;font-weight:700;line-height:1.35;text-align:center}.mail-folder.is-context .mail-folder-name{opacity:.6}.mail-account{display:flex;align-items:center;gap:.4rem;width:100%;padding:.45rem .6rem;border:none;background:var(--dm-surface);cursor:pointer;text-align:left;font-size:.78rem;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--dm-text-muted)}.mail-account:hover{background:var(--dm-hover-bg)}.mail-account.is-active{color:var(--dm-text);position:sticky;top:0;z-index:1;background:var(--dm-surface)}.mail-account-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-folder{display:flex;align-items:center;gap:.5rem;padding:.45rem .75rem;font-size:.875rem;cursor:pointer;border:none;background:none;width:100%;text-align:left;color:var(--dm-text)}.mail-folder:hover{background:var(--dm-hover-bg)}.mail-folder.is-active{background:var(--dm-active-bg);color:var(--dm-primary);font-weight:600}.mail-folder.is-unselectable{opacity:.6;cursor:default}.mail-folder.is-unselectable:hover{background:none}.mail-folder-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item{display:block;width:100%;text-align:left;padding:.55rem .75rem;border:none;border-bottom:1px solid var(--dm-border);background:none;cursor:pointer;color:var(--dm-text)}.mail-item:hover{background:var(--dm-hover-bg)}.mail-item.is-active{background:var(--dm-active-bg)}.mail-item-top{display:flex;justify-content:space-between;gap:.5rem;align-items:baseline}.mail-item-from{font-size:.85rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item.is-unread .mail-item-from,.mail-item.is-unread .mail-item-subject{font-weight:700}.mail-item-date{font-size:.72rem;color:var(--dm-text-muted);flex-shrink:0}.mail-item-subject{font-size:.82rem;margin-top:.1rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mail-item-meta{display:flex;align-items:center;gap:.35rem;margin-top:.15rem;color:var(--dm-text-muted)}.mail-pager{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.4rem .6rem;border-top:1px solid var(--dm-border);font-size:.75rem;color:var(--dm-text-muted);flex-shrink:0}.mail-reader-head{padding:.75rem 1rem;border-bottom:1px solid var(--dm-border);flex-shrink:0}.mail-reader-head:empty{display:none}.mail-reader-subject{font-size:1.05rem;font-weight:600;margin:0 0 .3rem;color:var(--dm-text)}.mail-reader-actions{display:flex;gap:.25rem;margin:.1rem 0 .35rem;flex-wrap:wrap}.mail-reader-meta{font-size:.8rem;color:var(--dm-text-muted)}.mail-reader-body{flex:1;position:relative;overflow:hidden;display:flex;flex-direction:column;min-height:0;background:var(--dm-surface)}.mail-reader-body iframe{width:100%;flex:1;border:none;display:block;background:#fff}.mail-notice{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;padding:.4rem .75rem;font-size:.78rem;background:var(--dm-warning);color:var(--dm-warning-text);border-bottom:1px solid var(--dm-border)}.mail-notice .btn{color:var(--dm-warning-text);border:1px solid currentColor;background:transparent;opacity:.95}.mail-notice .btn:hover{background:color-mix(in srgb,var(--dm-warning-text) 15%,transparent);opacity:1}.mail-security--ok{background:color-mix(in srgb,var(--dm-success) 14%,var(--dm-surface));color:var(--dm-text)}.mail-security--bad{background:var(--dm-danger);color:var(--dm-danger-text)}.mail-security-detail{flex-basis:100%;opacity:.85;overflow-wrap:anywhere}.mail-attachments{display:flex;flex-wrap:wrap;gap:.4rem;padding:.5rem .75rem;border-bottom:1px solid var(--dm-border);background:var(--dm-background-alt)}.mail-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;height:100%;color:var(--dm-text-muted);text-align:center;padding:1rem}.mail-empty p{margin:0;font-size:.875rem}@media(max-width:900px){.mail-layout{grid-template-columns:1fr;height:auto!important}.mail-splitter{display:none}.mail-folders-pane,.mail-list-pane{border-right:none;border-bottom:1px solid var(--dm-border)}.mail-folders-list,.mail-list{max-height:240px}.mail-reader-body{min-height:60vh}}.mail-box-row{display:flex;align-items:center;gap:.5rem;padding:.5rem .6rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 6px)}.mail-default-badge{margin-left:.4rem;font-size:.7rem;font-weight:500;vertical-align:middle}.mail-item.is-dragging{opacity:.45}.mail-folder.is-drop-target{background:var(--dm-primary);color:var(--dm-primary-text);outline:2px solid var(--dm-primary);outline-offset:-2px}.mail-folder.is-drop-target .mail-folder-name,.mail-folder.is-drop-target .mail-unread-pill{color:inherit}.mail-item.is-dragging *{pointer-events:none}.mail-item{position:relative}.mail-item-tick{position:absolute;left:.45rem;top:.55rem;display:inline-flex;opacity:0;color:var(--dm-text-muted);transition:opacity .12s ease;padding:2px;margin:-2px;border-radius:var(--dm-radius-sm, 3px)}.mail-item:hover .mail-item-tick,.mail-item:focus-visible .mail-item-tick,.mail-list.is-selecting .mail-item-tick,.mail-item-tick[aria-checked=true]{opacity:1}.mail-item-tick[aria-checked=true]{color:var(--dm-primary)}.mail-item-tick:hover{color:var(--dm-primary);background:var(--dm-hover-bg)}.mail-item-top,.mail-item-subject,.mail-item-meta{padding-left:1.5rem}.mail-select-all{display:inline-flex;align-items:center;margin-right:.1rem;cursor:pointer}.mail-select-all input{cursor:pointer;margin:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mail-item-meta>span{display:inline-flex}.mail-item-meta .is-replied{color:var(--dm-success)}.mail-item-meta .is-forwarded{color:var(--dm-info, var(--dm-primary))}.mail-item-meta .is-high{color:var(--dm-danger)}.mail-item-meta .is-low{color:var(--dm-text-muted);opacity:.7}.mail-folder.is-hidden-folder .mail-folder-name{opacity:.5;font-style:italic}.mail-thread-chip{display:inline-flex;align-items:center;margin-left:.4rem;padding:0 .35rem;border-radius:999px;font-size:.68rem;font-weight:700;line-height:1.5;color:var(--dm-text-muted);background:var(--dm-active-bg);cursor:pointer}.mail-thread-chip:hover{color:var(--dm-primary)}.mail-thread-chip.is-unread{background:var(--dm-primary);color:var(--dm-primary-text)}.mail-item.is-in-thread{padding-left:1.6rem;box-shadow:inset 3px 0 0 var(--dm-border)}.mail-item.is-in-thread .mail-item-from{font-size:.8rem}.mail-pane-head .btn.is-on{color:var(--dm-primary);background:var(--dm-active-bg)}#mail-outbox-btn{font-size:.72rem;font-weight:700}#mail-outbox-btn.is-failed{color:var(--dm-danger)}.mail-attachment{display:inline-flex;align-items:center;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 6px);overflow:hidden}.mail-attachment .btn{border:none;border-radius:0}.mail-attachment-save{padding-left:.4rem!important;padding-right:.4rem!important;border-left:1px solid var(--dm-border)!important;opacity:.7}.mail-attachment-save:hover{opacity:1;color:var(--dm-primary)}.mail-section-heading{margin:1rem 0 .1rem;font-size:.8rem;text-transform:uppercase;letter-spacing:.04em;color:var(--dm-text-muted)}.mail-section-heading:first-child{margin-top:.25rem}.mail-section-form .domma-form{margin:0}.mail-section-form .domma-form-field:last-child{margin-bottom:0}.mail-item-card{margin:0}.mail-item-card .card-header{padding:.5rem .6rem}.mail-item-card .card-body{padding:.6rem}.mail-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.mail-status{min-height:1.2em}.mail-empty-line{margin:.25rem 0}.mail-editor-fallback{min-height:8rem;font-family:inherit;resize:vertical}.mail-clause{display:flex;gap:.3rem;align-items:center;flex-wrap:wrap}.mail-clause .form-select,.mail-clause .form-input{width:auto;flex:0 1 auto}.mail-clause .mail-clause-value{flex:1 1 8rem;min-width:8rem}.mail-section-list{display:flex;flex-direction:column;gap:.5rem}.mail-scroll-sm{max-height:20vh;overflow-y:auto}.mail-scroll-md{max-height:46vh;overflow-y:auto}.mail-section-form .domma-form-field:has(select) .form-label .text-danger{display:none}.mail-compose-form{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.6rem;height:100%}.mail-compose-body{display:flex;flex-direction:column;flex:1;min-height:0}.mail-compose-editor{flex:1;min-height:0}.mail-compose-textarea{flex:1;min-height:34vh;font-family:inherit;resize:vertical}.mail-attach-list{display:flex;flex-direction:column;gap:.2rem}.mail-template-row{margin:-.15rem 0 .35rem}.mail-template-picker{max-width:16rem}.mail-suggest-row{display:flex;flex-direction:column;line-height:1.25}.mail-suggest-detail{opacity:.7}.mail-rule-off{opacity:.55}.mail-panel{padding:.25rem 0 .5rem}.mail-panel-stack{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:1rem}.mail-form-stack{padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem}.mail-dialog-stack{display:flex;flex-direction:column;gap:.5rem;padding:.25rem 0}.mail-hint{margin-top:.2rem}.mail-box-sub{overflow:hidden;text-overflow:ellipsis}.mail-add-btn{align-self:flex-start;margin-top:.5rem}.mail-none{margin:0;padding:.5rem .6rem}.mail-kv{padding:.5rem 0;display:flex;flex-direction:column;gap:.35rem}.mail-kv-row{display:flex;gap:.75rem}.mail-kv-key{min-width:8.5rem;flex-shrink:0}.mail-toggle-row{display:flex;align-items:center;gap:.5rem;margin-bottom:.75rem}.mail-sender-list{display:flex;flex-direction:column;gap:.2rem;max-height:40vh;overflow-y:auto}.mail-folder-pick{display:flex;align-items:center;gap:.5rem;padding:.3rem .4rem;border-radius:var(--dm-radius-md, 6px)}.mail-diag-log{width:100%;height:30vh;margin-top:.5rem;font-family:monospace;font-size:.72rem}.mail-facts{display:flex;flex-direction:column;gap:.2rem;margin-bottom:.75rem}.mail-item-more{position:absolute;right:.4rem;bottom:.35rem;display:inline-flex;align-items:center;justify-content:center;min-width:1.4rem;height:1.4rem;border-radius:var(--dm-radius-sm, 3px);font-size:1rem;line-height:1;color:var(--dm-text-muted);background:inherit;opacity:0;transition:opacity .12s ease}.mail-item:hover .mail-item-more,.mail-item:focus-visible .mail-item-more,.mail-item.is-active .mail-item-more{opacity:1}.mail-item-more:hover{color:var(--dm-primary)}.mail-section-intro{display:flex;align-items:center;gap:.35rem;margin:0 0 .75rem;padding-bottom:.5rem;border-bottom:1px solid var(--dm-border)}.mail-pager-middle{display:flex;align-items:center;gap:.5rem;min-width:0}.mail-page-size{font:inherit;color:inherit;background:transparent;border:1px solid var(--dm-border);border-radius:4px;padding:.1rem .25rem}.mail-auth-choice{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;padding-bottom:.5rem;border-bottom:1px solid var(--dm-border)}.mail-oauth{display:flex;flex-direction:column;gap:.75rem}.mail-oauth-warn{margin:0;padding:.5rem .7rem;border:1px solid var(--dm-warning);border-radius:var(--dm-radius-md);color:var(--dm-warning-text)}.mail-oauth-state{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.mail-oauth-chip{margin-left:.4rem;border:1px solid var(--dm-border);background:var(--dm-surface);color:var(--dm-text-muted)}.mail-oauth-state .mail-oauth-chip{margin-left:0}.mail-oauth-chip--ok{border-color:var(--dm-success);color:var(--dm-success)}.mail-oauth-chip--bad{border-color:var(--dm-danger);color:var(--dm-danger)}.mail-oauth-guide{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);padding:.5rem .75rem;background:var(--dm-background-alt)}.mail-oauth-guide>summary{cursor:pointer;font-weight:600}.mail-oauth-guide-body ol{padding-left:1.2rem;margin:.5rem 0 0}.mail-oauth-guide-body li{margin-bottom:.45rem}.mail-oauth-code{word-break:break-all;user-select:all}.mail-loading-slot{display:block;min-height:3rem;padding:.5rem .6rem}.mail-row-more{flex-shrink:0;margin-left:auto;display:inline-flex;align-items:center;justify-content:center;min-width:1.3rem;height:1.3rem;border-radius:var(--dm-radius-sm);font-size:.95rem;line-height:1;color:var(--dm-text-muted);opacity:0;transition:opacity .12s ease}.mail-unread-pill+.mail-row-more{margin-left:0}.mail-folder:hover .mail-row-more,.mail-folder:focus-visible .mail-row-more,.mail-folder.is-active .mail-row-more,.mail-account:hover .mail-row-more,.mail-account:focus-visible .mail-row-more,.mail-account.is-active .mail-row-more{opacity:1}.mail-row-more:hover{color:var(--dm-primary)}.mail-box-row .mail-row-more{opacity:1;margin-left:0}.mail-box-row:focus-visible{outline:2px solid var(--dm-primary);outline-offset:-2px}.mail-restored-note{margin:0;padding:.4rem .6rem;border-left:3px solid var(--dm-warning);background:var(--dm-background-alt)}.mail-font-controls .mail-font-btn{width:auto;min-width:3.5rem;max-width:10rem;padding:0 .5rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.8125rem}.domma-dropdown-menu:has(.mail-font-item),.domma-dropdown-menu:has(.mail-size-item){max-height:min(60vh,26rem);overflow-y:auto;z-index:10050}.domma-dropdown-item:has(.mail-font-item){display:flex;align-items:center;justify-content:space-between;gap:.75rem}.mail-font-item{font-size:.9375rem}.mail-font-web{font-size:.6875rem;color:var(--dm-text-muted);text-transform:uppercase;letter-spacing:.04em}