domma-cms 1.10.15 → 1.10.16

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.
@@ -1,23 +1,14 @@
1
- import{assignHotkeys as q,chipEntries as D,CHIP_DELAY_MS as H,createSequence as S,frequentKeys as P,hotkeyLabel as g,isPaletteKey as A,isTypingTarget as T,matchTools as M,paletteKeyLabel as G,SEQUENCE_MS as j}from"./hotkeys.js";let d={tools:[],byKey:new Map,byUrl:new Map,conflicts:[],frequent:[]},v=!1,u=null;const E=()=>/Mac|iPhone|iPad/.test(navigator.userAgentData?.platform||navigator.platform||navigator.userAgent||"");function p(e){return String(e??"").replace(/[&<>"']/g,o=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[o])}function r(...e){return e.map(o=>`<kbd>${p(String(o).toUpperCase())}</kbd>`).join("")}export function setHotkeyTree(e){const o=q(e);d={...o,frequent:P(e)},o.conflicts.length&&console.info("[hotkeys] Letters already taken:",o.conflicts.map(i=>`${i.text} wanted G ${i.key.toUpperCase()} (${i.takenBy} has it)`).join("; "))}export function hotkeyFor(e){return d.byUrl.get(e)||null}export function frequentNumberFor(e){return d.frequent.find(o=>o.url===e)?.n||null}export function currentHotkeys(){return d}export function hotkeyTitle(e,o,{inFrequent:i=!1}={}){const t=[],a=i?frequentNumberFor(e):null;a&&t.push(g(a));const n=hotkeyFor(e);return n&&t.push(g(n)),t.length?`${o} - ${t.join(" or ")}`:""}function x(e){e&&location.hash!==e&&(location.hash=e)}const f=S(()=>d),K=()=>d.tools.length>0&&!!document.querySelector(".dm-admin-sidebar");let h=null,k=null,$=null;function m(){clearTimeout(k),clearTimeout($),k=$=null,h?.remove(),h=null}function N(){const{frequent:e,letters:o,more:i}=D(d),t=(a,n)=>`<span class="dm-hk-chip-item">${r(a)}<span>${p(n)}</span></span>`;h=document.createElement("div"),h.className="dm-hk-chip",h.setAttribute("role","status"),h.setAttribute("aria-live","polite"),h.innerHTML=`<span class="dm-hk-chip-lead">${r("G")} then</span>`+e.map(a=>t(a.n,a.text)).join("")+(e.length&&o.length?'<span class="dm-hk-chip-sep" aria-hidden="true"></span>':"")+o.map(a=>t(a.key,a.text)).join("")+`<span class="dm-hk-chip-more">${i?`+${i} more \xB7 `:""}${r("?")} for all</span>`,document.body.appendChild(h)}function U(){m(),k=setTimeout(()=>{k=null,f.pending(Date.now())&&N()},H),$=setTimeout(m,j)}function F(e){if(!K()){m();return}const o=(e.ctrlKey||e.metaKey)&&!e.altKey&&!e.shiftKey,i=String(e.key||"").toLowerCase();if(u&&o&&(i==="k"||i==="d")){e.preventDefault(),e.stopImmediatePropagation(),u.close();return}if(u)return;if(A(e)){m(),f.reset(),e.preventDefault(),e.stopImmediatePropagation(),C();return}if(e.ctrlKey||e.metaKey||e.altKey||e.shiftKey||e.isComposing){f.reset(),m();return}if(T(e.target)){f.reset(),m();return}const t=f.press(e.key,Date.now());if(t?.type==="start"){U();return}m(),t&&(e.preventDefault(),e.stopImmediatePropagation(),t.type==="go"&&x(t.url))}function z(e){e.key!=="?"||e.defaultPrevented||u||!K()||e.ctrlKey||e.metaKey||e.altKey||T(e.target)||(e.preventDefault(),b())}export function installHotkeys(){v||typeof window>"u"||(v=!0,window.addEventListener("keydown",F,!0),window.addEventListener("keydown",z,!1))}export function showPalette(){u||C()}export function showKeyList(){u||b()}function L(e,o,i){const t=document.createElement("div");t.className="dm-hk-backdrop",t.innerHTML=`<div class="dm-hk-panel ${e}" role="dialog" aria-modal="true" aria-label="${p(o)}">${i}</div>`;const a=document.activeElement,n=()=>{!u||u.el!==t||(u=null,t.remove(),a&&typeof a.focus=="function"&&a.isConnected&&a.focus())};if(t.addEventListener("mousedown",l=>{l.target===t&&n()}),t.addEventListener("keydown",l=>{l.key==="Escape"&&(l.preventDefault(),l.stopPropagation(),n())}),document.body.appendChild(t),u={el:t,close:n},typeof I<"u"&&I.scan)try{I.scan(t)}catch{}return{el:t.firstElementChild,close:n}}function C(){const e=E(),{el:o,close:i}=L("dm-hk-palette","Go to a Tool",`
2
- <div class="dm-hk-search"><span data-icon="search" data-icon-size="16"></span>
3
- <input type="text" placeholder="Go to a Tool\u2026" aria-label="Tool name" autocomplete="off" spellcheck="false"></div>
4
- <ul class="dm-hk-list" role="listbox" aria-label="Tools"></ul>
5
- <div class="dm-hk-foot">${r("\u2191","\u2193")} move \xB7 ${r("Enter")} open \xB7 ${r("Esc")} close \xB7 ${r("?")} every hotkey
6
- <span class="dm-hk-foot-end">${p(G(e))}</span></div>`),t=o.querySelector("input"),a=o.querySelector(".dm-hk-list");let n=[],l=0;const y=()=>{if(n=M(d.tools,t.value),l=Math.min(l,Math.max(n.length-1,0)),a.innerHTML=n.length?n.map((s,c)=>`<li role="option" class="dm-hk-item${c===l?" is-active":""}" aria-selected="${c===l}" data-at="${c}">
7
- <span class="dm-hk-icon">${s.icon?`<span data-icon="${p(s.icon)}" data-icon-size="16"></span>`:""}</span>
8
- <span class="dm-hk-name">${p(s.text)}${s.path.length?`<small>${p(s.path.join(" \u203A "))}</small>`:""}</span>
9
- ${s.key?`<span class="dm-hk-keys" title="${p(g(s.key))}">${r("G",s.key)}</span>`:""}
10
- </li>`).join(""):'<li class="dm-hk-empty">No Tool by that name.</li>',typeof I<"u"&&I.scan)try{I.scan(a)}catch{}a.querySelector(".is-active")?.scrollIntoView({block:"nearest"})},w=s=>{const c=n[s];c&&(i(),x(c.url))};t.addEventListener("input",()=>{l=0,y()}),t.addEventListener("keydown",s=>{if(s.key==="ArrowDown"||s.key==="ArrowUp"){if(s.preventDefault(),!n.length)return;l=(l+(s.key==="ArrowDown"?1:-1)+n.length)%n.length,y()}else s.key==="Enter"?(s.preventDefault(),w(l)):s.key==="?"&&!t.value&&(s.preventDefault(),i(),b())}),a.addEventListener("mousemove",s=>{const c=s.target.closest("[data-at]");c&&Number(c.dataset.at)!==l&&(l=Number(c.dataset.at),y())}),a.addEventListener("click",s=>{const c=s.target.closest("[data-at]");c&&w(Number(c.dataset.at))}),y(),t.focus()}function b(){const e=E(),o=d.tools.filter(n=>n.key).sort((n,l)=>n.text.localeCompare(l.text)),i=(n,l)=>`<li><span class="dm-hk-keys">${n}</span><span>${p(l)}</span></li>`,{el:t,close:a}=L("dm-hk-keylist","Hotkeys",`
1
+ import{api as g,getUser as P}from"../api.js";import{assignHotkeys as D,chipEntries as H,CHIP_DELAY_MS as v,createSequence as S,frequentKeys as N,hotkeyLabel as T,isPaletteKey as A,isTypingTarget as K,SEQUENCE_MS as G}from"./hotkeys.js";import{paletteCommands as M,recentKeyFor as j}from"./palette-commands.js";let l={tools:[],byKey:new Map,byUrl:new Map,conflicts:[],frequent:[]},C=!1,r=null;const F=()=>/Mac|iPhone|iPad/.test(navigator.userAgentData?.platform||navigator.platform||navigator.userAgent||"");function h(e){return String(e??"").replace(/[&<>"']/g,o=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[o])}function c(...e){return e.map(o=>`<kbd>${h(String(o).toUpperCase())}</kbd>`).join("")}export function setHotkeyTree(e){const o=D(e);l={...o,frequent:N(e)},s&&s.setCommands(w()),o.conflicts.length&&console.info("[hotkeys] Letters already taken:",o.conflicts.map(t=>`${t.text} wanted G ${t.key.toUpperCase()} (${t.takenBy} has it)`).join("; "))}export function hotkeyFor(e){return l.byUrl.get(e)||null}export function frequentNumberFor(e){return l.frequent.find(o=>o.url===e)?.n||null}export function currentHotkeys(){return l}export function hotkeyTitle(e,o,{inFrequent:t=!1}={}){const n=[],i=t?frequentNumberFor(e):null;i&&n.push(T(i));const a=hotkeyFor(e);return a&&n.push(T(a)),n.length?`${o} - ${n.join(" or ")}`:""}function x(e){e&&location.hash!==e&&(location.hash=e)}const f=S(()=>l),$=()=>l.tools.length>0&&!!document.querySelector(".dm-admin-sidebar");let p=null,y=null,b=null;function d(){clearTimeout(y),clearTimeout(b),y=b=null,p?.remove(),p=null}function U(){const{frequent:e,letters:o,more:t}=H(l),n=(i,a)=>`<span class="dm-hk-chip-item">${c(i)}<span>${h(a)}</span></span>`;p=document.createElement("div"),p.className="dm-hk-chip",p.setAttribute("role","status"),p.setAttribute("aria-live","polite"),p.innerHTML=`<span class="dm-hk-chip-lead">${c("G")} then</span>`+e.map(i=>n(i.n,i.text)).join("")+(e.length&&o.length?'<span class="dm-hk-chip-sep" aria-hidden="true"></span>':"")+o.map(i=>n(i.key,i.text)).join("")+`<span class="dm-hk-chip-more">${t?`+${t} more \xB7 `:""}${c("?")} for all</span>`,document.body.appendChild(p)}function O(){d(),y=setTimeout(()=>{y=null,f.pending(Date.now())&&U()},v),b=setTimeout(d,G)}function _(e){if(!$()){d(),m()&&s.close();return}const o=(e.ctrlKey||e.metaKey)&&!e.altKey&&!e.shiftKey,t=String(e.key||"").toLowerCase();if((r||m())&&o&&(t==="k"||t==="d")){e.preventDefault(),e.stopImmediatePropagation(),r?r.close():s.close();return}if(r||m())return;if(A(e)){d(),f.reset(),e.preventDefault(),e.stopImmediatePropagation(),q();return}if(e.ctrlKey||e.metaKey||e.altKey||e.shiftKey||e.isComposing){f.reset(),d();return}if(K(e.target)){f.reset(),d();return}const n=f.press(e.key,Date.now());if(n?.type==="start"){O();return}d(),n&&(e.preventDefault(),e.stopImmediatePropagation(),n.type==="go"&&x(n.url))}function B(e){e.key!=="?"||e.defaultPrevented||r||m()||!$()||e.ctrlKey||e.metaKey||e.altKey||K(e.target)||(e.preventDefault(),k())}export function installHotkeys(){C||typeof window>"u"||(C=!0,window.addEventListener("keydown",_,!0),window.addEventListener("keydown",B,!1))}export function showPalette(){!r&&!m()&&q()}export function showKeyList(){!r&&!m()&&k()}function R(e,o,t){const n=document.createElement("div");n.className="dm-hk-backdrop",n.innerHTML=`<div class="dm-hk-panel ${e}" role="dialog" aria-modal="true" aria-label="${h(o)}">${t}</div>`;const i=document.activeElement,a=()=>{!r||r.el!==n||(r=null,n.remove(),i&&typeof i.focus=="function"&&i.isConnected&&i.focus())};if(n.addEventListener("mousedown",u=>{u.target===n&&a()}),n.addEventListener("keydown",u=>{u.key==="Escape"&&(u.preventDefault(),u.stopPropagation(),a())}),document.body.appendChild(n),r={el:n,close:a},typeof I<"u"&&I.scan)try{I.scan(n)}catch{}return{el:n.firstElementChild,close:a}}let s=null,L=null;const z={pages:()=>g.pages.list(),collections:()=>g.collections.list(),forms:()=>g.forms.list()},w=()=>M(l.tools,{load:z,showKeys:()=>k()});function Q(e,{newTab:o}={}){if(o){window.open(e,"_blank","noopener");return}let t;try{t=new URL(e,location.href)}catch{return}t.origin===location.origin&&t.pathname===location.pathname&&t.hash?x(t.hash):location.assign(t.href)}function Y(){const e=j(P());if(s&&L===e)return s;s?.destroy(),L=e,s=E.commandPalette({commands:w(),hotkey:!1,label:"Go to a Tool",placeholder:"Go to a Tool, or find a page\u2026",recentKey:e,recentLimit:5,maxResults:500,emptyText:"Nothing by that name.",errorText:"Could not load that list. Try again.",navigate:Q,className:"dm-hk-palette"}),s.element.addEventListener("keydown",t=>{t.key!=="?"||t.ctrlKey||t.metaKey||t.altKey||s.query()!==""||(t.preventDefault(),t.stopPropagation(),s.close(),k())},!0);const o=s.element.querySelector(".dm-command-palette-footer");if(o){const t=document.createElement("span");t.className="dm-command-palette-hint";const n=document.createElement("kbd");n.textContent="?",t.append(n,document.createTextNode(" every hotkey")),o.appendChild(t)}return s}function J(){const e=typeof E<"u"&&E.focusTrap?.top?E.focusTrap.top():null;return e?!(s&&s.element&&e.element===s.element):!1}const m=()=>!!s?.isOpen();function q(){if(J())return;const e=Y();e.setCommands(w()),e.open()}function k(){const e=F(),o=l.tools.filter(a=>a.key).sort((a,u)=>a.text.localeCompare(u.text)),t=(a,u)=>`<li><span class="dm-hk-keys">${a}</span><span>${h(u)}</span></li>`,{el:n,close:i}=R("dm-hk-keylist","Hotkeys",`
11
2
  <div class="dm-hk-head"><strong>Hotkeys</strong>
12
3
  <button type="button" class="dm-hk-close" aria-label="Close"><span data-icon="x" data-icon-size="16"></span></button></div>
13
4
  <div class="dm-hk-cols">
14
5
  <section><h4>Go to a Tool</h4><p class="dm-hk-note">Press G, let go, then the letter.</p>
15
- <ul>${o.map(n=>i(r("G",n.key),n.text)).join("")||'<li class="dm-hk-empty">No Tools with a letter yet.</li>'}</ul></section>
6
+ <ul>${o.map(a=>t(c("G",a.key),a.text)).join("")||'<li class="dm-hk-empty">No Tools with a letter yet.</li>'}</ul></section>
16
7
  <section><h4>Frequent</h4>
17
- <ul>${d.frequent.map(n=>i(r("G",n.n),n.text)).join("")||'<li class="dm-hk-empty">Right-click a Tool and choose Add to Frequent: the first nine get G then 1 to 9.</li>'}</ul>
8
+ <ul>${l.frequent.map(a=>t(c("G",a.n),a.text)).join("")||'<li class="dm-hk-empty">Right-click a Tool and choose Add to Frequent: the first nine get G then 1 to 9.</li>'}</ul>
18
9
  <h4>Anywhere</h4>
19
- <ul>${i(r(e?"\u2318":"Ctrl","K"),"Find any Tool by name")}
20
- ${i(r(e?"\u2318":"Ctrl","D"),"The same - whichever is handier")}
21
- ${i(r("?"),"This list")}</ul>
10
+ <ul>${t(c(e?"\u2318":"Ctrl","K"),"Find any Tool, page, collection or form by name")}
11
+ ${t(c(e?"\u2318":"Ctrl","D"),"The same - whichever is handier")}
12
+ ${t(c("?"),"This list")}</ul>
22
13
  <p class="dm-hk-note">Hotkeys wait while you are typing in a field. Hover over a Tool in the sidebar to see its key.</p></section>
23
- </div>`);t.querySelector(".dm-hk-close")?.addEventListener("click",a),t.setAttribute("tabindex","-1"),t.focus()}
14
+ </div>`);n.querySelector(".dm-hk-close")?.addEventListener("click",i),n.setAttribute("tabindex","-1"),n.focus()}
@@ -1 +1 @@
1
- export const CORE_HOTKEYS=Object.freeze({"#/":"d","#/pages":"p","#/media":"m","#/collections":"l","#/forms":"f","#/views":"v","#/users":"u","#/analytics":"a","#/notes":"n","#/todo":"t","#/contacts":"c","#/settings":","}),LEADER="g",SEQUENCE_MS=1500,FREQUENT_KEYS=9;export function normaliseHotkey(e){if(typeof e!="string")return null;const r=e.trim().toLowerCase();return r.length!==1||r===LEADER?null:/^[a-z,]$/.test(r)?r:null}export function hotkeyLabel(e){return`G then ${String(e).toUpperCase()}`}export function paletteKeyLabel(e=!1){return e?"\u2318K or \u2318D":"Ctrl+K or Ctrl+D"}export function sidebarTools(e){const r=[],o=new Set,n=(c,s)=>{for(const t of c||[]){if(!t||typeof t!="object"||t.hidden||t.frequent||t.type==="separator"||t.type==="spacer"||t.type==="theme-switcher")continue;const i=String(t.text||"").replace(/\s*\(\d+\)\s*$/,"").trim(),l=typeof t.url=="string"?t.url:"";if(l.startsWith("#/")&&i&&!o.has(l)){o.add(l);const u=normaliseHotkey(t.hotkey),f=CORE_HOTKEYS[l]||null;r.push({url:l,text:i,icon:t.icon||null,path:s,wants:f||u,core:!!f})}Array.isArray(t.items)&&t.items.length&&n(t.items,i?[...s,i]:s)}};return n(e,[]),r}export function assignHotkeys(e){const r=sidebarTools(e),o=new Map,n=new Map,c=[],s=t=>{if(!t.wants)return;const i=o.get(t.wants);if(i){c.push({url:t.url,text:t.text,key:t.wants,takenBy:i.text});return}o.set(t.wants,t),n.set(t.url,t.wants)};return r.filter(t=>t.core).forEach(s),r.filter(t=>!t.core).forEach(s),{tools:r.map(t=>({...t,key:n.get(t.url)||null})),byKey:o,byUrl:n,conflicts:c}}export function frequentKeys(e){return((e||[]).find(o=>o&&o.frequent)?.items||[]).filter(o=>o&&typeof o.url=="string"&&o.url.startsWith("#/")).slice(0,FREQUENT_KEYS).map((o,n)=>({n:n+1,url:o.url,text:String(o.text||"")}))}export const CHIP_DELAY_MS=300;export function chipEntries(e,{max:r=14}={}){const o=(e?.frequent||[]).slice(0,r).map(s=>({n:s.n,text:s.text})),n=[...(e?.byKey||new Map).entries()].map(([s,t])=>({key:s,text:t.text})).sort((s,t)=>(s.key===","?1:0)-(t.key===","?1:0)||s.key.localeCompare(t.key)),c=Math.max(r-o.length,0);return{frequent:o,letters:n.slice(0,c),more:Math.max(n.length-c,0)}}export function createSequence(e){let r=null;const o=n=>r!==null&&n-r<=SEQUENCE_MS;return{pending:o,reset(){r=null},press(n,c){const s=typeof n=="string"&&n.length===1?n.toLowerCase():null;if(o(c)){if(r=null,!s)return{type:"miss"};const{byKey:t,frequent:i}=e();if(/^[1-9]$/.test(s)){const u=(i||[]).find(f=>f.n===Number(s));return u?{type:"go",url:u.url}:{type:"miss"}}const l=t?.get(s);return l?{type:"go",url:l.url}:{type:"miss"}}return r=null,s===LEADER?(r=c,{type:"start"}):null}}}export function matchTools(e,r,o=50){const n=String(r||"").trim().toLowerCase(),c=(t,i)=>(i.t.key?1:0)-(t.t.key?1:0)||t.t.path.length-i.t.path.length||t.at-i.at;if(!n)return e.map((t,i)=>({t,at:i})).sort(c).slice(0,o).map(t=>t.t);const s=[];return e.forEach((t,i)=>{const l=t.text.toLowerCase(),u=l.split(/[\s\-_/&]+/).filter(Boolean),f=u.map(p=>p[0]).join(""),y=t.path.join(" ").toLowerCase();let a=0;n.length===1&&t.key===n?a=120:l===n?a=110:l.startsWith(n)?a=100:u.some(p=>p.startsWith(n))?a=80:n.length>1&&f.startsWith(n)?a=70:l.includes(n)?a=50:y.includes(n)&&(a=20),a&&s.push({t,score:a,at:i})}),s.sort((t,i)=>i.score-t.score||c(t,i)),s.slice(0,o).map(t=>t.t)}export function isTypingTarget(e){if(!e||typeof e!="object")return!1;const r=String(e.tagName||"").toUpperCase();if(r==="TEXTAREA"||r==="SELECT")return!0;if(r==="INPUT"){const o=String(e.type||"text").toLowerCase();return!["button","submit","reset","checkbox","radio","range","color","file","image"].includes(o)}return e.isContentEditable?!0:!!(e.closest&&e.closest('[contenteditable=""], [contenteditable="true"], .cm-editor, .CodeMirror, .monaco-editor, .ProseMirror, [data-own-keys]'))}export function isPaletteKey(e){if(!e||e.altKey||e.shiftKey||!(e.ctrlKey||e.metaKey)||e.ctrlKey&&e.metaKey)return!1;const r=String(e.key||"").toLowerCase();return r==="d"?!isTypingTarget(e.target):r==="k"?!m(e.target):!1}function m(e){return!e||typeof e!="object"?!1:e.isContentEditable?!0:!!(e.closest&&e.closest('[contenteditable=""], [contenteditable="true"], .cm-editor, .CodeMirror, .monaco-editor, .ProseMirror'))}
1
+ export const CORE_HOTKEYS=Object.freeze({"#/":"d","#/pages":"p","#/media":"m","#/collections":"l","#/forms":"f","#/views":"v","#/users":"u","#/analytics":"a","#/notes":"n","#/todo":"t","#/contacts":"c","#/settings":","}),LEADER="g",SEQUENCE_MS=1500,FREQUENT_KEYS=9;export function normaliseHotkey(t){if(typeof t!="string")return null;const r=t.trim().toLowerCase();return r.length!==1||r===LEADER?null:/^[a-z,]$/.test(r)?r:null}export function hotkeyLabel(t){return`G then ${String(t).toUpperCase()}`}export function sidebarTools(t){const r=[],n=new Set,s=(i,o)=>{for(const e of i||[]){if(!e||typeof e!="object"||e.hidden||e.frequent||e.type==="separator"||e.type==="spacer"||e.type==="theme-switcher")continue;const c=String(e.text||"").replace(/\s*\(\d+\)\s*$/,"").trim(),u=typeof e.url=="string"?e.url:"";if(u.startsWith("#/")&&c&&!n.has(u)){n.add(u);const l=normaliseHotkey(e.hotkey),a=CORE_HOTKEYS[u]||null;r.push({url:u,text:c,icon:e.icon||null,path:o,wants:a||l,core:!!a})}Array.isArray(e.items)&&e.items.length&&s(e.items,c?[...o,c]:o)}};return s(t,[]),r}export function assignHotkeys(t){const r=sidebarTools(t),n=new Map,s=new Map,i=[],o=e=>{if(!e.wants)return;const c=n.get(e.wants);if(c){i.push({url:e.url,text:e.text,key:e.wants,takenBy:c.text});return}n.set(e.wants,e),s.set(e.url,e.wants)};return r.filter(e=>e.core).forEach(o),r.filter(e=>!e.core).forEach(o),{tools:r.map(e=>({...e,key:s.get(e.url)||null})),byKey:n,byUrl:s,conflicts:i}}export function frequentKeys(t){return((t||[]).find(n=>n&&n.frequent)?.items||[]).filter(n=>n&&typeof n.url=="string"&&n.url.startsWith("#/")).slice(0,FREQUENT_KEYS).map((n,s)=>({n:s+1,url:n.url,text:String(n.text||"")}))}export const CHIP_DELAY_MS=300;export function chipEntries(t,{max:r=14}={}){const n=(t?.frequent||[]).slice(0,r).map(o=>({n:o.n,text:o.text})),s=[...(t?.byKey||new Map).entries()].map(([o,e])=>({key:o,text:e.text})).sort((o,e)=>(o.key===","?1:0)-(e.key===","?1:0)||o.key.localeCompare(e.key)),i=Math.max(r-n.length,0);return{frequent:n,letters:s.slice(0,i),more:Math.max(s.length-i,0)}}export function createSequence(t){let r=null;const n=s=>r!==null&&s-r<=SEQUENCE_MS;return{pending:n,reset(){r=null},press(s,i){const o=typeof s=="string"&&s.length===1?s.toLowerCase():null;if(n(i)){if(r=null,!o)return{type:"miss"};const{byKey:e,frequent:c}=t();if(/^[1-9]$/.test(o)){const l=(c||[]).find(a=>a.n===Number(o));return l?{type:"go",url:l.url}:{type:"miss"}}const u=e?.get(o);return u?{type:"go",url:u.url}:{type:"miss"}}return r=null,o===LEADER?(r=i,{type:"start"}):null}}}export function isTypingTarget(t){if(!t||typeof t!="object")return!1;const r=String(t.tagName||"").toUpperCase();if(r==="TEXTAREA"||r==="SELECT")return!0;if(r==="INPUT"){const n=String(t.type||"text").toLowerCase();return!["button","submit","reset","checkbox","radio","range","color","file","image"].includes(n)}return t.isContentEditable?!0:!!(t.closest&&t.closest('[contenteditable=""], [contenteditable="true"], .cm-editor, .CodeMirror, .monaco-editor, .ProseMirror, [data-own-keys]'))}export function isPaletteKey(t){if(!t||t.altKey||t.shiftKey||!(t.ctrlKey||t.metaKey)||t.ctrlKey&&t.metaKey)return!1;const r=String(t.key||"").toLowerCase();return r==="d"?!isTypingTarget(t.target):r==="k"?!f(t.target):!1}function f(t){return!t||typeof t!="object"?!1:t.isContentEditable?!0:!!(t.closest&&t.closest('[contenteditable=""], [contenteditable="true"], .cm-editor, .CodeMirror, .monaco-editor, .ProseMirror'))}
@@ -1 +1 @@
1
- import{fmtDate as c}from"./dates.js";export const KINDS=["all","image","video","audio","document","other"],KIND_LABEL={all:"All",image:"Images",video:"Video",audio:"Audio",document:"Documents",other:"Other"},SORTS=["newest","oldest","name","size"];const u={image:["png","jpg","jpeg","gif","webp","svg","avif","ico","bmp","tif","tiff"],video:["mp4","webm","ogv","mov"],audio:["mp3","ogg","oga","wav","weba","m4a"],document:["pdf","txt","csv","doc","docx","xls","xlsx"]},m=new Set(["png","jpg","jpeg","gif","webp","svg","avif","ico","bmp"]),p=new Set(["jpg","jpeg","png","webp","gif","tiff"]);export const extOf=e=>{const t=/\.([a-z0-9]+)$/i.exec(String(e??""));return t?t[1].toLowerCase():""};export function kindOf(e){const t=extOf(e);for(const[o,n]of Object.entries(u))if(n.includes(t))return o;return"other"}export const isPreviewable=e=>m.has(extOf(e)),isEditable=e=>p.has(extOf(e));export function formatBytes(e){const t=Number(e)||0;if(t<1024)return`${t} B`;const o=["KB","MB","GB","TB"];let n=t/1024,r=0;for(;n>=1024&&r<o.length-1;)n/=1024,r++;return`${n.toFixed(1)} ${o[r]}`}export function ago(e,t=Date.now()){const o=new Date(e).getTime();if(!Number.isFinite(o))return"";const n=Math.max(0,Math.round((t-o)/1e3));if(n<60)return"just now";if(n<3600)return`${Math.round(n/60)} min ago`;if(n<86400){const r=Math.round(n/3600);return`${r} hour${r===1?"":"s"} ago`}if(n<86400*14){const r=Math.round(n/86400);return`${r} day${r===1?"":"s"} ago`}return c(o)}export function safeName(e){return String(e??"").split(/[\\/]/).pop().replace(/[^a-zA-Z0-9._-]/g,"_")}export function clashes(e,t){const o=new Set(t);return[...new Set(e.map(safeName))].filter(n=>o.has(n))}export function altFromName(e){return String(e??"").replace(/\.[^.]+$/,"").replace(/[-_]+/g," ").replace(/\s+/g," ").trim()}export function markdownFor(e){return isPreviewable(e.name)?`![${altFromName(e.name)}](${e.url})`:`[${e.name}](${e.url})`}export function kindCounts(e){const t=Object.fromEntries(KINDS.map(o=>[o,0]));for(const o of e||[])t.all++,t[kindOf(o.name)]++;return t}export function arrangeMedia(e,{kind:t="all",query:o="",sort:n="newest"}={}){const r=String(o).trim().toLowerCase(),s={newest:(a,i)=>String(i.createdAt).localeCompare(String(a.createdAt)),oldest:(a,i)=>String(a.createdAt).localeCompare(String(i.createdAt)),name:(a,i)=>a.name.localeCompare(i.name,void 0,{numeric:!0,sensitivity:"base"}),size:(a,i)=>(i.size||0)-(a.size||0)}[n]||(()=>0);return[...e||[]].filter(a=>t==="all"||kindOf(a.name)===t).filter(a=>!r||a.name.toLowerCase().includes(r)).sort((a,i)=>s(a,i)||a.name.localeCompare(i.name))}export function mediaTile(e,{now:t=Date.now(),stamp:o=""}={}){const n=kindOf(e.name),r=isPreviewable(e.name);return{name:e.name,url:e.url,thumb:r?`${e.url}${o?`?t=${encodeURIComponent(o)}`:""}`:null,preview:r,icon:{video:"video",audio:"music",document:"file-text"}[n]||(n==="image"?"image":"paperclip"),kind:n,kindLabel:(extOf(e.name)||n).toUpperCase(),meta:[formatBytes(e.size),ago(e.createdAt,t)].filter(Boolean).join(" \xB7 ")}}export function normalisePrefs(e){const t=e&&typeof e=="object"?e:{};return{kind:KINDS.includes(t.kind)?t.kind:"all",sort:SORTS.includes(t.sort)?t.sort:"newest"}}
1
+ import{fmtDate as u}from"./dates.js";export const KINDS=["all","image","video","audio","document","other"],KIND_LABEL={all:"All",image:"Images",video:"Video",audio:"Audio",document:"Documents",other:"Other"},SORTS=["newest","oldest","name","size"];const c={image:["png","jpg","jpeg","gif","webp","svg","avif","ico","bmp","tif","tiff"],video:["mp4","webm","ogv","mov"],audio:["mp3","ogg","oga","wav","weba","m4a"],document:["pdf","txt","csv","doc","docx","xls","xlsx"]},m=new Set(["png","jpg","jpeg","gif","webp","svg","avif","ico","bmp"]),l=new Set(["jpg","jpeg","png","webp","gif","tiff"]);export const extOf=e=>{const n=/\.([a-z0-9]+)$/i.exec(String(e??""));return n?n[1].toLowerCase():""};export function kindOf(e){const n=extOf(e);for(const[t,o]of Object.entries(c))if(o.includes(n))return t;return"other"}export const isPreviewable=e=>m.has(extOf(e)),isEditable=e=>l.has(extOf(e));export function formatBytes(e){const n=Number(e)||0;if(n<1024)return`${n} B`;const t=["KB","MB","GB","TB"];let o=n/1024,r=0;for(;o>=1024&&r<t.length-1;)o/=1024,r++;return`${o.toFixed(1)} ${t[r]}`}export function ago(e,n=Date.now()){const t=new Date(e).getTime();if(!Number.isFinite(t))return"";const o=Math.max(0,Math.round((n-t)/1e3));if(o<60)return"just now";if(o<3600)return`${Math.round(o/60)} min ago`;if(o<86400){const r=Math.round(o/3600);return`${r} hour${r===1?"":"s"} ago`}if(o<86400*14){const r=Math.round(o/86400);return`${r} day${r===1?"":"s"} ago`}return u(t)}export function safeName(e){return String(e??"").split(/[\\/]/).pop().replace(/[^a-zA-Z0-9._-]/g,"_")}export function clashes(e,n){const t=new Set(n);return[...new Set(e.map(safeName))].filter(o=>t.has(o))}export function altFromName(e){return String(e??"").replace(/\.[^.]+$/,"").replace(/[-_]+/g," ").replace(/\s+/g," ").trim()}export function markdownFor(e){return isPreviewable(e.name)?`![${altFromName(e.name)}](${e.url})`:`[${e.name}](${e.url})`}export function kindCounts(e){const n=Object.fromEntries(KINDS.map(t=>[t,0]));for(const t of e||[])n.all++,n[kindOf(t.name)]++;return n}export function arrangeMedia(e,{kind:n="all",query:t="",sort:o="newest"}={}){const r=String(t).trim().toLowerCase(),s={newest:(i,a)=>String(a.createdAt).localeCompare(String(i.createdAt)),oldest:(i,a)=>String(i.createdAt).localeCompare(String(a.createdAt)),name:(i,a)=>i.name.localeCompare(a.name,void 0,{numeric:!0,sensitivity:"base"}),size:(i,a)=>(a.size||0)-(i.size||0)}[o]||(()=>0);return[...e||[]].filter(i=>n==="all"||kindOf(i.name)===n).filter(i=>!r||i.name.toLowerCase().includes(r)).sort((i,a)=>s(i,a)||i.name.localeCompare(a.name))}export function mediaTile(e,{now:n=Date.now(),stamp:t=""}={}){const o=kindOf(e.name),r=isPreviewable(e.name);return{name:e.name,url:e.url,thumb:r?`${e.url}${t?`?t=${encodeURIComponent(t)}`:""}`:null,preview:r,icon:{video:"video",audio:"music",document:"file-text"}[o]||(o==="image"?"image":"paperclip"),kind:o,kindLabel:(extOf(e.name)||o).toUpperCase(),meta:[formatBytes(e.size),ago(e.createdAt,n)].filter(Boolean).join(" \xB7 ")}}export function normalisePrefs(e){const n=e&&typeof e=="object"?e:{};return{kind:KINDS.includes(n.kind)?n.kind:"all",sort:SORTS.includes(n.sort)?n.sort:"newest"}}export function normaliseLimits(e){const n=e&&typeof e=="object"?e:{},t=Array.isArray(n.types)?n.types.filter(s=>typeof s=="string"&&/^[\w.+-]+\/[\w.+-]+$/.test(s)):[],o=Number(n.maxFileSize),r=Number(n.maxNameLength);return{types:t,maxFileSize:Number.isFinite(o)&&o>0?o:null,maxNameLength:Number.isInteger(r)&&r>0?r:null}}export function dropzoneRules(e){const n=normaliseLimits(e);return{accept:n.types.length?n.types.join(","):null,maxSize:n.maxFileSize}}export function uploadHint(e){const n=normaliseLimits(e),t=[],o=s=>n.types.some(i=>s.test(i));o(/^image\//)&&t.push("images"),o(/^(application|text)\//)&&t.push("documents"),o(/^audio\//)&&t.push("audio"),o(/^video\//)&&t.push("video");const r=[];if(t.length){const s=t.length===1?t[0]:`${t.slice(0,-1).join(", ")} and ${t.at(-1)}`;r.push(s[0].toUpperCase()+s.slice(1))}return n.maxFileSize&&r.push(`up to ${formatBytes(n.maxFileSize)} each`),r.join(" \xB7 ")}export function nameRefusal(e,n){const t=normaliseLimits(n),o=safeName(e);return o?t.maxNameLength&&o.length>t.maxNameLength?`its name is longer than ${t.maxNameLength} characters`:null:"it has no name"}export function shortName(e,n=60){const t=String(e??"");return t.length>n?`${t.slice(0,n-15)}\u2026${t.slice(-12)}`:t}export function rejectText(e,n,t){const o=normaliseLimits(t),r=shortName(e);return n==="type"?`${r}: that type of file is not taken here`:n==="size"?`${r}: it is larger than ${o.maxFileSize?formatBytes(o.maxFileSize):"the limit"}`:`${r}: not added`}export function uploadErrorText(e,n){const t=e||{},o=t.response&&typeof t.response=="object"?t.response:null,r=o&&(typeof o.error=="string"?o.error:typeof o.message=="string"?o.message:"");if(t.status===413){const s=normaliseLimits(n);return`larger than ${s.maxFileSize?formatBytes(s.maxFileSize):"the server takes"}`}return r||(t.status===0?"the connection failed":t.status===401?"signed out - sign in and retry":t.status===403?"you may not upload media":t.message||"failed")}
@@ -0,0 +1 @@
1
+ import{hotkeyLabel as a}from"./hotkeys.js";export function recentKeyFor(r){const e=r&&(r.id??r._id??r.email);return`admin.palette.recent.${e==null||e===""?"anon":String(e)}`}export function toolCommands(r){return(r||[]).filter(e=>e&&e.url&&e.text).map((e,t)=>({t:e,at:t,path:Array.isArray(e.path)?e.path:[]})).sort((e,t)=>(t.t.key?1:0)-(e.t.key?1:0)||e.path.length-t.path.length||e.at-t.at).map(({t:e,path:t})=>{const o=[t.join(" \u203A "),e.key?a(e.key):""].filter(Boolean).join(" \xB7 ");return{id:`tool:${e.url}`,title:e.text,...o?{subtitle:o}:{},...e.icon?{icon:e.icon}:{},keywords:t.slice(),href:e.url}})}const i=(r,e)=>r.title.localeCompare(e.title,void 0,{sensitivity:"base"});export function pageCommands(r,e="file-text"){return(Array.isArray(r)?r:[]).filter(t=>t&&typeof t.urlPath=="string").map(t=>({id:`page:${t.urlPath}`,title:String(t.title||t.urlPath),subtitle:t.status==="draft"?`${t.urlPath} \xB7 Draft`:t.urlPath,icon:e,keywords:[t.urlPath],href:`#/pages/edit${t.urlPath}`})).sort(i)}export function collectionCommands(r,e="database"){return(Array.isArray(r)?r:[]).filter(t=>t&&t.slug).map(t=>({id:`collection:${t.slug}`,title:String(t.title||t.slug),subtitle:String(t.slug),icon:e,keywords:[String(t.slug)],href:`#/collections/${encodeURIComponent(t.slug)}/entries`})).sort(i)}export function formCommands(r,e="clipboard"){return(Array.isArray(r)?r:[]).filter(t=>t&&t.slug).map(t=>({id:`form:${t.slug}`,title:String(t.title||t.slug),subtitle:String(t.slug),icon:e,keywords:[String(t.slug)],href:`#/forms/edit/${encodeURIComponent(t.slug)}`})).sort(i)}const s=[{url:"#/pages",id:"find:pages",title:"Open page\u2026",placeholder:"Find a page\u2026",load:"pages",make:pageCommands,icon:"file-text",words:["edit","page"]},{url:"#/collections",id:"find:collections",title:"Open collection\u2026",placeholder:"Find a collection\u2026",load:"collections",make:collectionCommands,icon:"database",words:["entries","data"]},{url:"#/forms",id:"find:forms",title:"Open form\u2026",placeholder:"Find a form\u2026",load:"forms",make:formCommands,icon:"clipboard",words:["edit","form"]}];export function findCommands(r,e={}){const t=new Map((r||[]).map(o=>[o.url,o]));return s.filter(o=>t.has(o.url)&&typeof e[o.load]=="function").map(o=>{const n=t.get(o.url).icon||o.icon;return{id:o.id,title:o.title,group:"Find",icon:n,keywords:o.words,placeholder:o.placeholder,children:async(d,l)=>o.make(await e[o.load](l),n)}})}export function paletteCommands(r,{load:e={},showKeys:t=null}={}){const o=[...toolCommands(r),...findCommands(r,e)];return typeof t=="function"&&o.push({id:"help:hotkeys",title:"Every hotkey",subtitle:"Or press ? with nothing typed",group:"Help",icon:"keyboard",keywords:["shortcuts","keys","keyboard","?"],action:()=>t()}),o}
@@ -2,11 +2,17 @@
2
2
  (the router renders a templateUrl through _.render, eating any {{…}}). No page heading: the Tool
3
3
  banner above is the heading, and Upload is a banner action. -->
4
4
  <div class="mdx">
5
- <input class="mdx-file-input" type="file" multiple accept="image/*,video/*,audio/*,.pdf,.svg,.txt,.csv,.doc,.docx,.xls,.xlsx" hidden>
6
- <p class="mdx-note">Files for your pages, stored in <code>content/media/</code>. Drop files anywhere on this screen to
7
- upload them.
8
- <span data-help-title="Media" data-help="Upload with the button in the banner, or drop files onto this screen. Click a file for its details - its address, a snippet to paste into a page, a rename box, and the image editor for pictures. Right-click a file (or ⋮, or Shift+F10) for the same actions without opening it. Uploading a file with the same name as one already here replaces it; you are asked first."></span></p>
9
- <p class="mdx-busy" data-bind-hidden="!busyText.value"><span class="mdx-spinner" aria-hidden="true"></span><span data-bind-text="busyText.value"></span></p>
5
+ <!-- The dropzone's input: its accept and size rules come from GET /api/media/_limits (views/media.js). -->
6
+ <input class="mdx-file-input" type="file" multiple hidden>
7
+ <p class="mdx-note">Files for your pages, stored in <code>content/media/</code>. Drop or paste files anywhere on this
8
+ screen to upload them.
9
+ <span data-help-title="Media" data-help="Upload with the drop area or the button in the banner, drop files anywhere on this screen, or paste them (Ctrl+V). Each file shows its progress; cancel one with its ×, and Retry one that failed. A type or size the site does not take is refused before it is sent. Click a file for its details - its address, a snippet to paste into a page, a rename box, and the image editor for pictures. Right-click a file (or ⋮, or Shift+F10) for the same actions without opening it. Uploading a file with the same name as one already here replaces it; you are asked first."></span></p>
10
+ <!-- E.dropzone: the area is one button (click, Enter or Space opens the picker); its file list is drawn after it. -->
11
+ <div class="mdx-zone">
12
+ <span class="mdx-zone-icon" data-icon="upload" data-icon-size="20" aria-hidden="true"></span>
13
+ <span class="mdx-zone-text"><b>Drop files here, paste, or click to choose</b>
14
+ <span class="mdx-zone-hint" data-bind-text="hintText.value" data-bind-hidden="!hintText.value"></span></span>
15
+ </div>
10
16
 
11
17
  <div class="mdx-card" data-bind-class="dragging.value && 'is-drop'">
12
18
  <div class="mdx-bar">
@@ -43,11 +49,14 @@
43
49
  .mdx [hidden], .mdx-panel [hidden] { display: none !important; }
44
50
  .mdx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
45
51
  .mdx-note.is-bad { color: var(--dm-danger); }
46
- .mdx-busy { margin: 0; display: flex; align-items: center; gap: .5rem; padding: .5rem .75rem; border-radius: var(--dm-radius-md);
47
- background: color-mix(in srgb, var(--dm-primary) 12%, transparent); color: var(--dm-text); font-size: var(--dm-text-sm); }
48
- .mdx-spinner { width: 14px; height: 14px; border-radius: 50%; border: 2px solid color-mix(in srgb, var(--dm-primary) 30%, transparent);
49
- border-top-color: var(--dm-primary); animation: mdx-spin .8s linear infinite; flex-shrink: 0; }
50
- @keyframes mdx-spin { to { transform: rotate(360deg); } }
52
+ /* A one-line strip rather than Domma's tall default area: the grid is the screen's subject. */
53
+ .mdx-zone.dm-dropzone { flex-direction: row; justify-content: flex-start; text-align: left; min-height: 0; gap: .75rem;
54
+ padding: .65rem .9rem; font-size: var(--dm-text-sm); }
55
+ .mdx-zone-icon { color: var(--dm-primary); flex-shrink: 0; display: inline-flex; }
56
+ .mdx-zone-text { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
57
+ .mdx-zone-text b { font-weight: 600; }
58
+ .mdx-zone-hint { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
59
+ .mdx .dm-dropzone-files { margin: 0; }
51
60
  .mdx-card { position: relative; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .75rem; }
52
61
  .mdx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
53
62
  .mdx-search { flex: 1; min-width: 10rem; max-width: 20rem; }