domma-cms 1.6.55 → 1.6.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/css/calculator.css +1 -1
- package/admin/js/lib/analytics-shape.js +1 -1
- package/admin/js/lib/calculator-engine.js +1 -1
- package/admin/js/lib/calculator.js +22 -10
- package/admin/js/lib/form-editor-arrange.js +1 -1
- package/admin/js/lib/forms-arrange.js +1 -1
- package/admin/js/lib/notes-arrange.js +1 -1
- package/admin/js/lib/search-settings-arrange.js +1 -1
- package/admin/js/lib/seo-arrange.js +1 -0
- package/admin/js/lib/todo-arrange.js +1 -1
- package/admin/js/lib/tool-kit.js +1 -1
- package/admin/js/lib/tool-recovery.js +4 -4
- package/admin/js/templates/analytics.html +26 -14
- package/admin/js/templates/contacts.html +26 -5
- package/admin/js/templates/form-editor.html +2 -0
- package/admin/js/templates/notes.html +11 -8
- package/admin/js/templates/search.html +9 -3
- package/admin/js/templates/seo.html +23 -17
- package/admin/js/templates/todo.html +18 -14
- package/admin/js/views/analytics.js +10 -10
- package/admin/js/views/contacts.js +47 -47
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +2 -2
- package/admin/js/views/forms.js +5 -5
- package/admin/js/views/notes.js +6 -6
- package/admin/js/views/search.js +2 -2
- package/admin/js/views/seo.js +43 -43
- package/admin/js/views/todo.js +8 -8
- package/package.json +1 -1
- package/plugins/_lib/admin/mail/compose-window.js +4 -1
- package/plugins/_lib/admin/mail/mail.css +1 -1
- package/plugins/_lib/admin/mail/reader-kit.js +364 -0
- package/plugins/_lib/admin/mail/reader-view.js +1009 -161
- package/plugins/_lib/admin/mail/when-closed.js +33 -3
- package/plugins/_lib/mail/readRoutes.js +48 -10
- package/plugins/_lib/mail/unreadBadge.js +4 -1
- package/plugins/blog/CLAUDE.md +14 -0
- package/plugins/blog/admin/css/index.css +5 -0
- package/plugins/blog/admin/lib/drafts.js +95 -0
- package/plugins/blog/admin/templates/blog.html +1 -1
- package/plugins/blog/admin/templates/categories.html +1 -1
- package/plugins/blog/admin/templates/comments.html +1 -1
- package/plugins/blog/admin/templates/post-editor.html +5 -1
- package/plugins/blog/admin/views/blog.js +44 -19
- package/plugins/blog/admin/views/categories.js +138 -20
- package/plugins/blog/admin/views/comments.js +32 -23
- package/plugins/blog/admin/views/kit.js +232 -22
- package/plugins/blog/admin/views/post-editor.js +348 -178
- package/plugins/blog/admin/views/settings.js +17 -3
- package/plugins/blog/plugin.js +1 -1
- package/plugins/blog/plugin.json +7 -7
- package/plugins/blog/plugin.public.js +1 -1
- package/plugins/blog/tests/drafts.test.js +50 -0
- package/plugins/booking/CLAUDE.md +13 -0
- package/plugins/booking/admin/lib/drafts.js +139 -0
- package/plugins/booking/admin/lib/kit.js +246 -25
- package/plugins/booking/admin/views/booking.js +11 -2
- package/plugins/booking/admin/views/bookings.js +7 -2
- package/plugins/booking/admin/views/diary.js +13 -6
- package/plugins/booking/admin/views/panels.js +85 -12
- package/plugins/booking/admin/views/people.js +41 -2
- package/plugins/booking/admin/views/services.js +45 -3
- package/plugins/booking/admin/views/settings.js +22 -2
- package/plugins/booking/plugin.json +2 -2
- package/plugins/booking/tests/admin-drafts.test.js +67 -0
- package/plugins/feedback/CLAUDE.md +12 -0
- package/plugins/feedback/admin/css/index.css +5 -0
- package/plugins/feedback/admin/lib/drafts.js +145 -0
- package/plugins/feedback/admin/lib/kit.js +161 -5
- package/plugins/feedback/admin/lib/settings.js +46 -28
- package/plugins/feedback/admin/views/feedback.js +1 -1
- package/plugins/feedback/admin/views/inbox.js +110 -46
- package/plugins/feedback/admin/views/send.js +174 -83
- package/plugins/feedback/plugin.json +3 -3
- package/plugins/feedback/tests/drafts.test.js +93 -0
- package/plugins/free-tier.lock.json +75 -63
- package/plugins/security/CLAUDE.md +9 -0
- package/plugins/security/admin/lib/screen-state.js +120 -0
- package/plugins/security/admin/templates/security.html +15 -10
- package/plugins/security/admin/views/security.js +270 -132
- package/plugins/security/plugin.json +2 -2
- package/plugins/security/tests/screen-state.test.js +69 -0
- package/plugins/shopping-cart/CLAUDE.md +13 -0
- package/plugins/shopping-cart/admin/css/index.css +21 -5
- package/plugins/shopping-cart/admin/lib/drafts.js +174 -0
- package/plugins/shopping-cart/admin/lib/kit.js +136 -28
- package/plugins/shopping-cart/admin/views/orders.js +117 -36
- package/plugins/shopping-cart/admin/views/overview.js +38 -8
- package/plugins/shopping-cart/admin/views/products.js +317 -68
- package/plugins/shopping-cart/admin/views/sales.js +21 -3
- package/plugins/shopping-cart/admin/views/settings.js +62 -3
- package/plugins/shopping-cart/admin/views/shop.js +18 -3
- package/plugins/shopping-cart/plugin.json +3 -3
- package/plugins/shopping-cart/tests/accounts.test.js +11 -0
- package/plugins/shopping-cart/tests/drafts.test.js +118 -0
- package/plugins/support/CLAUDE.md +14 -0
- package/plugins/support/admin/css/index.css +10 -0
- package/plugins/support/admin/lib/drafts.js +162 -0
- package/plugins/support/admin/lib/kit.js +156 -16
- package/plugins/support/admin/lib/settings.js +52 -29
- package/plugins/support/admin/views/desk.js +114 -33
- package/plugins/support/admin/views/inbox.js +85 -26
- package/plugins/support/admin/views/support.js +1 -1
- package/plugins/support/plugin.json +3 -3
- package/plugins/support/tests/drafts.test.js +92 -0
- package/public/js/site.js +1 -1
- package/server/routes/api/analytics.js +5 -1
- package/server/routes/api/contacts.js +42 -0
- package/server/routes/api/forms.js +8 -0
- package/server/routes/api/seo.js +4 -1
- package/server/routes/api/todo.js +50 -10
- package/server/server.js +1 -1
- package/server/templates/page.html +1 -1
package/admin/css/calculator.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.calculator-page{max-width:760px;margin:24px auto}.calculator-panel{--calc-accent:
|
|
1
|
+
.calculator-page{max-width:760px;margin:24px auto}.calculator-panel{--calc-accent: var(--dm-primary);--calc-on-accent: var(--dm-primary-text, var(--dm-text-inverse));color:var(--dm-text);background:var(--dm-surface);border:1px solid var(--dm-border);border-radius:18px;padding:20px;max-width:440px;margin:auto;box-shadow:var(--dm-shadow-md)}.calculator-panel.accent-teal{--calc-accent: #16b8a6;--calc-on-accent: #071f24}.calculator-panel.accent-blue{--calc-accent: #5099f5;--calc-on-accent: #071f24}.calculator-panel.accent-violet{--calc-accent: #a480ef;--calc-on-accent: #071f24}.calculator-panel.accent-amber{--calc-accent: #eca83e;--calc-on-accent: #071f24}.calculator-panel.is-scientific{max-width:740px}.calculator-floating{position:fixed;z-index:900;width:440px;max-width:calc(100vw - 24px);max-height:calc(100dvh - 24px);overflow:auto;border:1px solid var(--dm-border);border-radius:18px;background:var(--dm-surface);box-shadow:var(--dm-shadow-xl)}.calculator-floating:has(.is-scientific){width:680px}.calculator-floating .dm-plugin-banner{cursor:move;touch-action:none;padding:14px 18px;margin:0;border-radius:0}.calculator-floating .dm-plugin-banner-name{font-size:1.1rem}.calculator-floating .dm-plugin-body{padding:0}.calculator-floating .calculator-panel{border:0;box-shadow:none;border-radius:0}.calculator-toolbar{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:16px}.calculator-layout{display:flex;padding:4px;gap:4px;border-radius:10px;background:color-mix(in srgb,var(--calc-accent) 8%,transparent)}.calculator-layout button{border:0;border-radius:7px;padding:7px 12px;color:var(--dm-text);background:transparent;cursor:pointer}.calculator-layout .is-active{background:var(--calc-accent);color:var(--calc-on-accent);font-weight:700}.calculator-angle{max-width:120px}.calculator-panel:not(.is-scientific) .calculator-angle,.calculator-panel:not(.is-scientific) .calculator-scientific{display:none}.calculator-screen{border-radius:12px;border:1px solid color-mix(in srgb,var(--calc-accent) 35%,transparent);background:linear-gradient(135deg,color-mix(in srgb,var(--calc-accent) 16%,transparent),color-mix(in srgb,var(--calc-accent) 4%,transparent));padding:14px 18px 10px;margin-bottom:14px;box-shadow:inset 0 2px 8px color-mix(in srgb,var(--dm-text) 8%,transparent)}.calculator-screen-meta{display:flex;justify-content:space-between;gap:12px;color:var(--calc-accent);font-size:.72rem;font-weight:700;letter-spacing:.06em;overflow-wrap:anywhere}.calculator-history{min-height:22px;text-align:right;margin-top:8px;font-size:.75rem;opacity:.6;overflow-wrap:anywhere}.calculator-expression{width:100%;box-sizing:border-box;border:0;border-radius:4px;background:transparent;color:var(--dm-text);font-size:1rem;text-align:right;padding:8px 0;outline-offset:3px}.calculator-result{display:block;min-height:52px;font-size:clamp(1.9rem,4vw,2.6rem);font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:-.04em;text-align:right;overflow-wrap:anywhere}.calculator-error{color:var(--dm-danger);min-height:20px;font-size:.8rem;text-align:right}.calculator-memory{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;margin-bottom:16px}.calculator-memory button{border:0;border-radius:7px;padding:8px;background:color-mix(in srgb,var(--calc-accent) 10%,transparent);color:var(--calc-accent);font-weight:700;cursor:pointer}.calculator-memory button:disabled{opacity:.35;cursor:default}.calculator-keypads{display:grid;gap:16px}.is-scientific .calculator-keypads{grid-template-columns:1fr 1.4fr}.calculator-keys{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.calculator-scientific{grid-template-columns:repeat(3,1fr);gap:6px}.calculator-key{min-height:48px;border:1px solid var(--dm-border);border-radius:10px;background:var(--dm-background-alt);color:var(--dm-text);font-size:1.15rem;font-weight:600;cursor:pointer;box-shadow:0 2px color-mix(in srgb,var(--dm-text) 8%,transparent);transition:filter .12s,transform .12s}.calculator-key:hover{filter:brightness(1.15);transform:translateY(-1px)}.calculator-key:active{transform:translateY(1px);box-shadow:none}.calculator-key.key-operator{color:var(--calc-accent);background:color-mix(in srgb,var(--calc-accent) 15%,transparent);border-color:color-mix(in srgb,var(--calc-accent) 20%,transparent)}.calculator-key.key-clear{color:var(--dm-danger);background:color-mix(in srgb,var(--dm-danger) 12%,transparent);border-color:color-mix(in srgb,var(--dm-danger) 20%,transparent)}.calculator-key.key-equals{background:var(--calc-accent);color:var(--calc-on-accent);border-color:var(--calc-accent)}.calculator-key.key-science{min-height:38px;font-size:.9rem;color:var(--dm-text-secondary, var(--dm-text));background:color-mix(in srgb,var(--dm-text) 6%,transparent);border-color:var(--dm-border)}.calculator-hint{font-size:.7rem;color:var(--dm-text-muted);text-align:center;margin-top:16px}.calculator-dialog{width:400px;max-width:calc(100vw - 48px);border:1px solid var(--dm-border);border-radius:12px;background:var(--dm-surface);color:var(--dm-text);padding:24px}.calculator-dialog::backdrop{background:var(--dm-modal-backdrop)}.calculator-dialog-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:20px}.calculator-launch-options{display:grid;gap:12px;margin-top:20px}.calculator-launch-options button{text-align:left;padding:14px;cursor:pointer}.calculator-launch-options small{display:block;margin-top:4px;white-space:normal}.calculator-settings{padding:20px}.calculator-settings label{display:block;margin:20px 0}.calculator-settings select{display:block;width:100%;margin-top:8px}@media(max-width:580px){.is-scientific .calculator-keypads{grid-template-columns:1fr}.calculator-scientific{grid-template-columns:repeat(6,1fr)}.calculator-panel{padding:14px}.calculator-key.key-science{font-size:.75rem}}.calculator-panel{box-sizing:border-box;width:100%;min-width:0}.calculator-keypads,.calculator-keys,.calculator-memory{min-width:0}.calculator-keys{grid-template-columns:repeat(4,minmax(0,1fr))}.calculator-scientific{grid-template-columns:repeat(3,minmax(0,1fr))}.calculator-expression{min-width:0}@media(max-width:580px){.calculator-scientific{grid-template-columns:repeat(6,minmax(0,1fr))}}.calculator-memory-label,.calculator-settings-label{display:inline-flex;align-items:center;gap:4px}.calculator-tape{margin-top:16px;border:1px solid var(--dm-border);border-radius:12px;background:var(--dm-surface);overflow:hidden}.calculator-tape-head{display:flex;align-items:center;gap:6px;padding:6px 10px;font-size:.72rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--dm-text-muted);border-bottom:1px solid var(--dm-border)}.calculator-tape-count{display:inline-block;min-width:1.4em;padding:0 6px;border-radius:999px;background:var(--dm-hover-bg);color:var(--dm-text-muted);text-align:center;letter-spacing:0;font-variant-numeric:tabular-nums}.calculator-tape-clear{margin-left:auto;border:0;background:none;color:var(--dm-text-muted);font-size:.72rem;cursor:pointer;padding:2px 6px;border-radius:6px}.calculator-tape-clear:hover{color:var(--dm-danger);background:var(--dm-hover-bg)}.calculator-tape-list{max-height:168px;overflow-y:auto}.calculator-tape-row{display:flex;align-items:center;gap:8px;padding:6px 6px 6px 10px;font-variant-numeric:tabular-nums;cursor:pointer;border-top:1px solid color-mix(in srgb,var(--dm-border) 50%,transparent)}.calculator-tape-row:first-child{border-top:0}.calculator-tape-row:hover{background:var(--dm-hover-bg)}.calculator-tape-row:focus-visible{outline:2px solid var(--calc-accent);outline-offset:-2px}.calculator-tape-expr{flex:1;min-width:0;color:var(--dm-text-muted);font-size:.8rem;text-align:right;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.calculator-tape-answer{flex:0 1 auto;max-width:50%;font-weight:600;overflow-wrap:anywhere;text-align:right}.calculator-tape-kebab{flex:0 0 auto;border:0;background:none;color:var(--dm-text-muted);font-size:1.1rem;line-height:1;padding:0 4px;border-radius:6px;cursor:pointer}.calculator-tape-kebab:hover{color:var(--calc-accent)}.calculator-loading{min-height:320px;max-width:440px;margin:auto}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RANGES=["today","7d","30d","90d","all"],RANGE_LABEL={today:"Today","7d":"Last 7 days","30d":"Last 30 days","90d":"Last 90 days",all:"All time"},LIMITS=[10,25,50,0],CHARTS=["line","bar"],DEFAULT_SETTINGS=Object.freeze({range:"7d",limit:25,chart:"line",live:!0,confirmReset:!0});export function normaliseSettings(
|
|
1
|
+
export const RANGES=["today","7d","30d","90d","all"],RANGE_LABEL={today:"Today","7d":"Last 7 days","30d":"Last 30 days","90d":"Last 90 days",all:"All time"},LIMITS=[10,25,50,0],CHARTS=["line","bar"],DEFAULT_SETTINGS=Object.freeze({range:"7d",limit:25,chart:"line",live:!0,confirmReset:!0});export function normaliseSettings(t){const e=t&&typeof t=="object"?t:{},n=DEFAULT_SETTINGS,o=Number(e.limit);return{range:RANGES.includes(e.range)?e.range:n.range,limit:LIMITS.includes(o)?o:n.limit,chart:CHARTS.includes(e.chart)?e.chart:n.chart,live:typeof e.live=="boolean"?e.live:n.live,confirmReset:typeof e.confirmReset=="boolean"?e.confirmReset:n.confirmReset}}export function topRows(t,{filter:e="",limit:n=25,total:o=0}={}){const s=String(e).trim().toLowerCase(),r=(t||[]).filter(i=>!s||String(i.url).toLowerCase().includes(s)),a=n>0?r.slice(0,n):r,u=a.reduce((i,l)=>Math.max(i,l.hits),0)||1,c=o||r.reduce((i,l)=>i+l.hits,0)||1;return{matched:r.length,rows:a.map((i,l)=>({key:i.url,rank:l+1,url:i.url,hits:i.hits,hitsText:i.hits.toLocaleString("en-GB"),share:`${(i.hits/c*100).toFixed(i.hits/c<.1?1:0)}%`,bar:`${Math.max(2,Math.round(i.hits/u*100))}%`}))}}export function journeyRows(t){const e=(n,o)=>{const s=(n||[]).reduce((r,a)=>Math.max(r,a.count),0)||1;return(n||[]).map((r,a)=>({key:`${a}:${o(r)}`,url:r.url??r.from,label:o(r),count:r.count,bar:`${Math.max(2,Math.round(r.count/s*100))}%`}))};return{entry:e(t?.entry,n=>n.url),exit:e(t?.exit,n=>n.url),paths:e(t?.paths,n=>`${n.from} \u2192 ${n.to}`)}}export function kpis(t,e){const n=o=>Number.isFinite(o)?o.toLocaleString("en-GB"):"-";return{hits:n(t?.total),pages:n(t?.uniquePages),lifetime:n(t?.lifetimeTotal),sessions:n(e?.totalSessions),bounce:e&&e.totalSessions?`${Math.round(e.bounceRate*100)}%`:"-"}}export const seriesRange=t=>t==="all"?"30d":t;export function tabCounts(t,e,n){const o=s=>Number.isFinite(s)&&s>=0?s:null;return{pages:t?o(Array.isArray(t.top)?t.top.length:t.uniquePages):null,journeys:e?o(e.totalSessions):null,spikes:Array.isArray(n)?n.length:null}}export function siteAddress(t,e){if(typeof t!="string"||!t.startsWith("/"))return null;try{const n=new URL(t,e);return n.origin===e?n.href:null}catch{return null}}export function pageEditHash(t,e){return typeof t!="string"||!t.startsWith("/")?null:(e instanceof Set?e:new Set(e||[])).has(t)?`#/pages/edit${t}`:null}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function calculate(
|
|
1
|
+
export function calculate(r,f="deg"){const a=String(r).replace(/×/g,"*").replace(/[÷]/g,"/").replace(/[−]/g,"-").replace(/π/g,"pi");if(!a.trim()||a.length>256)throw new Error("Enter a calculation (up to 256 characters).");const o=a.match(/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?|[a-z]+|[+*/^()%!,-]/gi)||[];if(o.join("")!==a.replace(/\s/g,""))throw new Error("Use numbers and calculator operations only.");let n=0,i=0;const c=t=>{if(!Number.isFinite(t))throw new Error("Result is outside the supported range.");return t},l=t=>f==="deg"?t*Math.PI/180:t,w=t=>f==="deg"?t*180/Math.PI:t,b={sin:t=>Math.sin(l(t)),cos:t=>Math.cos(l(t)),tan:t=>{if(Math.abs(Math.cos(l(t)))<1e-14)throw new Error("Tangent is undefined at this angle.");return Math.tan(l(t))},asin:t=>w(Math.asin(t)),acos:t=>w(Math.acos(t)),atan:t=>w(Math.atan(t)),sqrt:Math.sqrt,abs:Math.abs,ln:Math.log,log:Math.log10,exp:Math.exp},x=t=>{if(!Number.isInteger(t)||t<0||t>170)throw new Error("Factorial needs a whole number from 0 to 170.");let s=1;for(let e=2;e<=t;e++)s*=e;return s};function g(t=0){if(++i>32)throw new Error("Calculation is too deeply nested.");let s=o[n++],e;if(s==="+"||s==="-")e=(s==="-"?-1:1)*g(25);else if(s==="("){if(e=g(),o[n++]!==")")throw new Error("Close the parentheses.")}else if(/^(?:\d|\.)/.test(s||""))e=Number(s);else if(s?.toLowerCase()==="pi")e=Math.PI;else if(s?.toLowerCase()==="e")e=Math.E;else if(Object.hasOwn(b,s?.toLowerCase())){if(o[n++]!=="(")throw new Error("Put the function value in parentheses.");if(e=b[s.toLowerCase()](g()),o[n++]!==")")throw new Error("Close the parentheses.")}else throw new Error("Enter a number or a supported function.");for(e=c(e);n<o.length;){const u=o[n];if(u==="%"||u==="!"){if(40<t)break;n++,e=u==="%"?e/100:x(e);continue}const m=u==="("||/^[a-z]/i.test(u),h=m?"*":u,d={"+":10,"-":10,"*":20,"/":20,"^":30}[h];if(d===void 0||d<t)break;m||n++;const p=g(h==="^"?d:d+1);if(h==="/"&&p===0)throw new Error("Cannot divide by zero.");e=c(h==="+"?e+p:h==="-"?e-p:h==="*"?e*p:h==="/"?e/p:e**p)}return i--,e}const M=g();if(n!==o.length)throw new Error("Check the calculation.");return Number(M.toPrecision(14))}export const POSITIONS=["top-left","top-center","top-right","center-left","center","center-right","bottom-left","bottom-center","bottom-right"],ACCENTS=["theme","teal","blue","violet","amber"];export function calculatorSettings(r){return{accent:ACCENTS.includes(r?.accent)?r.accent:"theme",layout:r?.layout==="scientific"?"scientific":"basic",angle:r?.angle==="rad"?"rad":"deg",position:POSITIONS.includes(r?.position)?r.position:"bottom-right",offset:r?.offset&&Number.isFinite(r.offset.x)&&Number.isFinite(r.offset.y)?{x:Math.max(0,r.offset.x),y:Math.max(0,r.offset.y)}:null}}export const TAPE_MAX=20;export function addToTape(r,{expression:f,answer:a},o=Date.now(),n=TAPE_MAX){const i=Array.isArray(r)?r:[],c=String(f??""),l=String(a??"");return i[0]&&i[0].expression===c&&i[0].answer===l?i:[{key:`t${o}`,expression:c,answer:l},...i].slice(0,Math.max(1,n))}export function insertValue(r,f,a){const o=String(a),n=o.startsWith("-")?`(${o})`:o,i=String(r??""),c=!f&&/[+*/^(-]$/.test(i)?i+n:n;return c.length<=256?c:i}
|
|
@@ -1,17 +1,29 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<label>Calculator layout<select class="form-input" name="layout" data-model="layout.value">${
|
|
1
|
+
import{api as W,getUser as J,isAuthenticated as C}from"../api.js";import{settingsStore as Q,onceClosed as Z,openMenuAt as z}from"./tool-kit.js";import{addBannerAction as h,buildBanner as ee}from"./plugin-chrome.js";import{helpPopovers as U}from"./help-popover.js";import{SHAPES as te,whileLoading as ae}from"./loading.js";import{ACCENTS as le,addToTape as ne,calculate as j,calculatorSettings as q,insertValue as Y,POSITIONS as se}from"./calculator-engine.js";const oe=le,X=Q("calculator.settings",q);let b;function V(){try{return J()?.id??null}catch{return null}}function D(){if(!b||b.userId===V())return;y(),B();const e=m?.parentElement;m?.dispose(),m?.remove(),m=null,b.persist?.(),b=null,e?.isConnected&&C()&&/^#\/calculator(?:\?|$)/.test(location.hash)&&(m=F(),e.append(m))}function S(){if(D(),b)return b;const e=M.observable(X.read()),a=M.observable(""),l=M.observable("0"),i=M.observable(""),c=M.observable(""),r=M.observable(null),d=M.observable([]),p=M.computed(()=>{if(!a.value)return l.value;try{return String(j(a.value,e.value.angle))}catch{return l.value}}),o=M.effect(()=>{X.write(e.value),requestAnimationFrame(T)});return b={userId:V(),settings:e,expression:a,result:l,error:i,history:c,memory:r,tape:d,display:p,persist:o,finished:!1,tapeSeq:0,tapeEmpty:M.computed(()=>!d.value.length),tapeCount:M.computed(()=>d.value.length),panelClass:M.computed(()=>`${e.value.layout==="scientific"?"is-scientific":""} accent-${e.value.accent}`),basic:M.computed(()=>e.value.layout==="basic"),scientific:M.computed(()=>e.value.layout==="scientific"),memoryEmpty:M.computed(()=>r.value===null),memoryLabel:M.computed(()=>r.value===null?"Memory empty":`M ${r.value}`),angleLabel:M.computed(()=>e.value.angle==="deg"?"DEG":"RAD")},b}function w(e,a){const l=S();l.settings.value=q({...l.settings.value,[e]:a,...e==="position"?{offset:null}:{}})}let f=null,m=null,x=null,v=null;function ie(e,a){x?.close();const l=document.createElement("dialog");l.className="calculator-dialog",l.setAttribute("aria-label",e),l.innerHTML=`<h2>${e}</h2>${a}`;const i=document.activeElement;return l.addEventListener("close",()=>{l.remove(),x===l&&(x=null),i?.isConnected&&i.focus()}),l.addEventListener("click",c=>{c.target===l&&l.close()}),document.body.append(l),x=l,l.showModal(),l}function T(){if(!f)return;const e=S().settings.value,a=f.getBoundingClientRect(),l=Math.max(12,window.innerWidth-a.width-12),i=Math.max(12,window.innerHeight-a.height-12),c=e.position,r=e.offset?.x??(c.endsWith("left")?12:c.endsWith("right")?l:(window.innerWidth-a.width)/2),d=e.offset?.y??(c.startsWith("top")?12:c.startsWith("bottom")?i:(window.innerHeight-a.height)/2);f.style.left=`${Math.max(12,Math.min(l,r))}px`,f.style.top=`${Math.max(12,Math.min(i,d))}px`}function _(){if(!v){v=!0;try{ce()}catch(e){throw v=null,e}}}function B(){if(v&&v!==!0)try{v.close()}catch{}}function ce(){const e=S(),a=E.slideover({title:"Calculator settings",size:"sm",position:"right"}),l=document.createElement("div");l.className="calculator-settings";const i=p=>p.map(o=>`<option value="${o}">${o.replace(/-/g," ").replace(/^./,g=>g.toUpperCase())}</option>`).join("");l.innerHTML=`<p class="text-muted">Saved in this browser as you change them.</p>
|
|
2
|
+
<label>Calculator layout<select class="form-input" name="layout" data-model="layout.value">${i(["basic","scientific"])}</select></label>
|
|
3
3
|
<label>Angle units<select class="form-input" name="angle" data-model="angle.value"><option value="deg">Degrees</option><option value="rad">Radians</option></select></label>
|
|
4
|
-
<label>Accent colour<select class="form-input" name="accent" data-model="accent.value">${
|
|
5
|
-
<label>Floating position<select class="form-input" name="position" data-model="position.value">${
|
|
4
|
+
<label><span class="calculator-settings-label">Accent colour <span data-help="Theme follows the admin theme's main colour, so it changes with it. The others stay the same colour whatever the theme."></span></span><select class="form-input" name="accent" data-model="accent.value">${i(oe)}</select></label>
|
|
5
|
+
<label><span class="calculator-settings-label">Floating position <span data-help="Where the floating calculator opens. Dragging it by its header moves it; choosing a position here puts it back."></span></span><select class="form-input" name="position" data-model="position.value">${i(se)}</select></label>
|
|
6
6
|
<p class="text-muted">You can also drag the floating calculator by its card header.</p>
|
|
7
|
-
<div class="calculator-dialog-actions"><button class="btn btn-secondary" data-on-click="reset">Reset defaults</button><button class="btn btn-primary" data-on-click="done">Done</button></div>`,
|
|
8
|
-
<div class="calculator-screen"><div class="calculator-screen-meta"><span data-bind-text="memoryLabel.value"></span><span data-bind-text="angleLabel.value"></span></div>
|
|
7
|
+
<div class="calculator-dialog-actions"><button class="btn btn-secondary" data-on-click="reset">Reset defaults</button><button class="btn btn-primary" data-on-click="done">Done</button></div>`,a.element.append(l);const c=Object.fromEntries(["layout","angle","accent","position"].map(p=>[p,M.computed({read:()=>e.settings.value[p],write:o=>w(p,o)})])),r=M.applyBindings(c,l,{methods:{reset:()=>{e.settings.value=q(null)},done:()=>a.close()}}),d=U(l);Z(a,()=>{r.dispose(),d(),v===a&&(v=null)}),v=a,a.open()}function $(e){R._transitioning?M.once("router:afterChange",()=>{C()&&R.navigate(e)}):R.navigate(e)}function y(){f?.dispose(),f?.remove(),f=null}function F(){const e=S(),a=document.createElement("section");a.className="calculator-panel",a.setAttribute("aria-label","Calculator"),a.setAttribute("data-bind-class","panelClass.value");const l=[["sin(","sin"],["cos(","cos"],["tan(","tan"],["asin(","sin\u207B\xB9"],["acos(","cos\u207B\xB9"],["atan(","tan\u207B\xB9"],["sqrt(","\u221A"],["^2","x\xB2"],["^","x\u02B8"],["log(","log"],["ln(","ln"],["!","n!"],["pi","\u03C0"],["e","e"],["exp(","e\u02E3"],["(","("],[")",")"],["abs(","|x|"]],i=[["clear","AC"],["backspace","\u232B"],["%","%"],["/","\xF7"],["7","7"],["8","8"],["9","9"],["*","\xD7"],["4","4"],["5","5"],["6","6"],["-","\u2212"],["1","1"],["2","2"],["3","3"],["+","+"],["sign","\xB1"],["0","0"],[".","."],["equals","="]],c=(t,n=!1)=>t.map(([s,u])=>`<button type="button" class="calculator-key ${n?"key-science":s==="equals"?"key-equals":["clear","backspace"].includes(s)?"key-clear":["/","*","-","+","%"].includes(s)?"key-operator":""}" data-key="${s}"${s==="backspace"?' aria-label="Backspace"':s==="sign"?' aria-label="Change sign"':""}>${u}</button>`).join("");a.innerHTML=`<div class="calculator-toolbar"><div class="calculator-layout"><button type="button" data-layout="basic" data-bind-class="basic.value && 'is-active'" data-on-click="selectBasic">Basic</button><button type="button" data-layout="scientific" data-bind-class="scientific.value && 'is-active'" data-on-click="selectScientific">Scientific</button></div><select class="form-input calculator-angle" aria-label="Angle units" data-model="angle.value"><option value="deg">Degrees</option><option value="rad">Radians</option></select></div>
|
|
8
|
+
<div class="calculator-screen"><div class="calculator-screen-meta"><span class="calculator-memory-label"><span data-bind-text="memoryLabel.value"></span><span data-help-title="Memory" data-help="MS stores the answer, M+ and M\u2212 add it to or take it from what is stored, MR puts the stored number into the calculation and MC clears it. It lasts until you sign out."></span></span><span data-bind-text="angleLabel.value"></span></div>
|
|
9
9
|
<div class="calculator-history" data-bind-text="history.value"></div>
|
|
10
10
|
<input class="calculator-expression" aria-label="Calculation" data-model="expression.value" autocomplete="off" spellcheck="false" maxlength="256" placeholder="Enter a calculation">
|
|
11
11
|
<output class="calculator-result" aria-live="polite" data-bind-text="display.value"></output><div class="calculator-error" role="alert" data-bind-text="error.value"></div></div>
|
|
12
|
-
<div class="calculator-memory">${[["MC","Clear memory"],["MR","Recall memory"],["MS","Store in memory"],["M+","Add to memory"],["M\u2212","Subtract from memory"]].map(([
|
|
13
|
-
<div class="calculator-keypads"><div class="calculator-keys calculator-scientific">${
|
|
14
|
-
<
|
|
12
|
+
<div class="calculator-memory">${[["MC","Clear memory"],["MR","Recall memory"],["MS","Store in memory"],["M+","Add to memory"],["M\u2212","Subtract from memory"]].map(([t,n])=>`<button type="button" data-memory="${t}" title="${n}" aria-label="${n}"${["MC","MR"].includes(t)?' data-bind-disabled="memoryEmpty.value"':""}>${t}</button>`).join("")}</div>
|
|
13
|
+
<div class="calculator-keypads"><div class="calculator-keys calculator-scientific">${c(l,!0)}</div><div class="calculator-keys">${c(i)}</div></div>
|
|
14
|
+
<section class="calculator-tape" data-bind-hidden="tapeEmpty.value" aria-label="Recent calculations">
|
|
15
|
+
<div class="calculator-tape-head"><span>Recent <span class="calculator-tape-count" data-bind-text="tapeCount.value"></span></span>
|
|
16
|
+
<span data-help-title="Recent calculations" data-help="Click one (or press Enter on it) to use its answer in the next calculation. Right-click it, use its \u22EE or press Shift+F10 to copy it, bring the whole calculation back or remove it. The list lasts until you sign out."></span>
|
|
17
|
+
<button type="button" class="calculator-tape-clear" data-on-click="clearTape">Clear</button></div>
|
|
18
|
+
<div class="calculator-tape-list" data-each="tape.value key=key">
|
|
19
|
+
<div class="calculator-tape-row" tabindex="0" data-tape="{{key}}" title="Use this answer">
|
|
20
|
+
<span class="calculator-tape-expr">{{expression}} =</span>
|
|
21
|
+
<span class="calculator-tape-answer">{{answer}}</span>
|
|
22
|
+
<button type="button" class="calculator-tape-kebab" aria-label="Calculation actions" title="Actions">\u22EE</button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</section>
|
|
26
|
+
<div class="calculator-hint">Enter to calculate \xB7 Escape to clear</div>`;const r=M.computed({read:()=>e.settings.value.angle,write:t=>w("angle",t)}),d=U(a,{position:"bottom"}),p=M.applyBindings({...e,angle:r},a,{methods:{selectBasic:()=>w("layout","basic"),selectScientific:()=>w("layout","scientific"),clearTape:()=>{e.tape.value=[],o.focus()}}}),o=a.querySelector("input"),g=new AbortController,k=E.contextMenu(a,{cursor:"glyph",match:".calculator-tape-row",inherit:!1,density:"compact",items:t=>{const n=L(t);return n?[{header:`${n.expression} = ${n.answer}`},{label:"Use the answer",icon:"corner-down-left",action:()=>A(n)},{label:"Bring back the calculation",icon:"rotate-ccw",action:()=>{e.expression.value=n.expression,e.finished=!1,e.error.value="",N()}},{label:"Copy the answer",icon:"copy",action:()=>H(n.answer)},{label:"Copy the whole line",icon:"clipboard",action:()=>H(`${n.expression} = ${n.answer}`)},{type:"divider"},{label:"Remove from the list",icon:"x",action:()=>{e.tape.value=e.tape.value.filter(s=>s.key!==n.key)}},{label:"Clear the list",icon:"trash",danger:!0,action:()=>{e.tape.value=[]}}]:[]}});a.dispose=()=>{p.dispose(),d(),g.abort();try{k?.destroy?.()}catch{}};const L=t=>e.tape.value.find(n=>n.key===t?.dataset.tape),N=()=>{o.isConnected&&(o.focus(),o.setSelectionRange(o.value.length,o.value.length))},A=t=>{e.expression.value=Y(e.expression.value,e.finished,t.answer),e.finished=!1,e.error.value="",N()},H=async t=>{try{await navigator.clipboard.writeText(t),E.toast("Copied.",{type:"success"})}catch{E.toast(t,{type:"info",duration:8e3})}},I=()=>j(e.expression.value||e.result.value,e.settings.value.angle),K=()=>{try{const t=String(I()),n=e.expression.value||e.result.value;e.history.value=`${n} = ${t}`,n!==t&&(e.tape.value=ne(e.tape.value,{expression:n,answer:t},++e.tapeSeq)),e.result.value=t,e.error.value="",e.finished=!0}catch(t){e.error.value=t.message,e.finished=!1}},O=()=>{e.expression.value="",e.result.value="0",e.error.value="",e.finished=!1};return o.addEventListener("input",()=>{e.error.value="",e.finished=!1},{signal:g.signal}),o.addEventListener("keydown",t=>{e.finished&&!t.ctrlKey&&!t.metaKey&&!t.altKey&&/^[0-9.a-z()+*/^%!-]$/i.test(t.key)&&(e.expression.value=/^[+*/^%!-]$/.test(t.key)?e.result.value:"",o.value=e.expression.value,e.finished=!1),(t.key==="Enter"||t.key==="=")&&(t.preventDefault(),K()),t.key==="Escape"&&(t.preventDefault(),O())},{signal:g.signal}),a.addEventListener("click",t=>{const n=t.target.closest(".calculator-tape-row");if(!n)return;if(t.target.closest(".calculator-tape-kebab")){z(t.target.closest(".calculator-tape-kebab"));return}const s=L(n);s&&A(s)},{signal:g.signal}),a.addEventListener("keydown",t=>{const n=t.target.closest(".calculator-tape-row");if(n){if(t.key==="F10"&&t.shiftKey)t.preventDefault(),z(n);else if(t.key==="Enter"&&t.target===n){t.preventDefault();const s=L(n);s&&A(s)}}},{signal:g.signal}),a.addEventListener("click",t=>{const n=t.target.closest("[data-key], [data-memory]");if(n){if(n.dataset.memory){const s=n.dataset.memory;try{if(s==="MC")e.memory.value=null;else if(s==="MR"&&e.memory.value!==null)e.expression.value=Y(e.expression.value,e.finished,e.memory.value),e.finished=!1;else if(s!=="MR"){const u=I(),P=s==="MS"?u:(e.memory.value??0)+(s==="M\u2212"?-u:u);if(!Number.isFinite(P))throw new Error("Memory value is too large.");e.memory.value=Number(P.toPrecision(14))}e.error.value=""}catch(u){e.error.value=u.message}}else{const s=n.dataset.key;if(s==="equals")K();else if(s==="clear")O();else{let u=e.expression.value;s==="backspace"?u=u.slice(0,-1):s==="sign"?u=u?`-(${u})`:"-":(e.finished&&(u=/^[+*/^%!\-]/.test(s)?e.result.value:""),u.length+s.length<=256&&(u+=s)),e.expression.value=u,e.finished=!1,e.error.value=""}}o.focus(),o.setSelectionRange(o.value.length,o.value.length)}},{signal:g.signal}),a}function G(){if(!f){const e=document.createElement("section");e.className="calculator-floating dm-plugin-shell",e.setAttribute("aria-label","Floating calculator"),e.append(ee({plugin:"calculator",displayName:"Calculator",icon:"calculator"}));const a=document.createElement("div");a.className="dm-plugin-body";const l=F();a.append(l),e.append(a),f=e,document.body.append(e),h(e,{id:"calculator-settings",icon:"settings",title:"Calculator settings",onClick:_}),h(e,{id:"calculator-standard",icon:"maximize",title:"Open on its own page",onClick:()=>{y(),$("/calculator")}}),h(e,{id:"calculator-close",icon:"x",title:"Close calculator",onClick:y});const i=new AbortController;e.dispose=()=>{i.abort(),l.dispose()};const c=e.querySelector(".dm-plugin-banner");c.addEventListener("pointerdown",r=>{if(r.target.closest("button")||r.button!==0)return;r.preventDefault();const d=e.getBoundingClientRect(),p={x:r.clientX,y:r.clientY};c.setPointerCapture(r.pointerId);const o=k=>w("offset",{x:d.left+k.clientX-p.x,y:d.top+k.clientY-p.y}),g=()=>c.removeEventListener("pointermove",o);c.addEventListener("pointermove",o,{signal:i.signal}),c.addEventListener("pointerup",g,{once:!0,signal:i.signal}),c.addEventListener("pointercancel",g,{once:!0,signal:i.signal})},{signal:i.signal}),window.addEventListener("resize",T,{signal:i.signal}),Domma.icons.scan(e)}T(),f.querySelector("input").focus()}export async function launchCalculator(){try{await W.get("/calculator/access")}catch{E.toast("Calculator is unavailable for this account.",{type:"warning"});return}if(!C())return;const e=ie("Open Calculator",`<p>Choose how you want to use Calculator.</p><div class="calculator-launch-options">
|
|
15
27
|
<button type="button" class="btn btn-primary" data-floating>Floating<small>Keep it alongside the page you are working on.</small></button>
|
|
16
28
|
<button type="button" class="btn btn-secondary" data-standard>Standard<small>Open Calculator on its own page.</small></button>
|
|
17
|
-
</div><div class="calculator-dialog-actions"><button type="button" class="btn btn-secondary" data-cancel>Cancel</button></div>`);e.querySelector("[data-floating]").onclick=()=>{e.close(),
|
|
29
|
+
</div><div class="calculator-dialog-actions"><button type="button" class="btn btn-secondary" data-cancel>Cancel</button></div>`);e.querySelector("[data-floating]").onclick=()=>{e.close(),G(),/^#\/calculator(?:\?|$)/.test(location.hash)&&$("/")},e.querySelector("[data-standard]").onclick=()=>{e.close(),y(),$("/calculator")},e.querySelector("[data-cancel]").onclick=()=>e.close()}export function installCalculator(){document.addEventListener("click",e=>{!e.target.closest('a[href="#/calculator"]')||e.ctrlKey||e.metaKey||e.shiftKey||e.altKey||e.button!==0||(e.preventDefault(),e.stopImmediatePropagation(),launchCalculator())},!0),window.addEventListener("storage",()=>D()),window.addEventListener("hashchange",()=>{D(),(!C()||/^#\/(login|register|forgot|reset)/.test(location.hash))&&(y(),B(),x?.close(),b?.persist?.(),b=null),/^#\/calculator(?:\?|$)/.test(location.hash)||(m?.dispose(),m=null,f||B())})}export const calculatorView={template:'<div class="calculator-page"><div class="calculator-loading dm-loading-slot">Loading\u2026</div></div>',async onMount(e){const a=e.get(0).querySelector(".calculator-page"),l=a.querySelector(".calculator-loading");try{await ae(l,W.get("/calculator/access"),te.figure)}catch{l.remove(),a.textContent="Calculator is unavailable for this account.";return}l.remove(),!(!a.isConnected||!/^#\/calculator(?:\?|$)/.test(location.hash))&&(y(),m?.dispose(),m=F(),a.append(m),h(e,{id:"calculator-settings",icon:"settings",title:"Calculator settings",onClick:_}),h(e,{id:"calculator-float",icon:"minimize",title:"Open floating calculator",onClick:()=>{G(),$("/")}}),m.querySelector("input").focus())}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SCREEN=/^\/forms\/(new|edit\/[^/?#]+)\/?(\?.*)?$/;const
|
|
1
|
+
export const SCREEN=/^\/forms\/(new|edit\/[^/?#]+)\/?(\?.*)?$/;const s=e=>String(e||"").split("?")[0].replace(/\/$/,"");export function leavingEditor(e,r){const t=s(e);return SCREEN.test(t)&&t!==s(r)}export const draftKey=e=>`form-editor.draft.${e||"new"}`;export function snapshotOf(e){const r=t=>Array.isArray(t)?t.map(r):t&&typeof t=="object"?Object.fromEntries(Object.keys(t).sort().filter(n=>t[n]!==void 0).map(n=>[n,r(t[n])])):t;return JSON.stringify(r(e))}export function draftOf(e){const r=JSON.parse(JSON.stringify(e||{}));return r.actions?.webhook&&delete r.actions.webhook.url,r}export function restoreDraft(e,r){const t=JSON.parse(JSON.stringify(e||{})),n=r?.actions?.webhook?.url;return t.actions?.webhook&&n!==void 0&&(t.actions.webhook.url=n),t}export function fingerprint(e){let r=2166136261;for(let t=0;t<String(e).length;t++)r^=String(e).charCodeAt(t),r=Math.imul(r,16777619)>>>0;return r.toString(16).padStart(8,"0")}export function uniqueName(e,r){const t=new Set(r),n=String(e||"field").replace(/_copy(_\d+)?$/,"")+"_copy";if(!t.has(n))return n;for(let o=2;;o++)if(!t.has(`${n}_${o}`))return`${n}_${o}`}export function duplicateField(e,r){const t=[...e||[]],n=t[r];if(!n)return t;const o=JSON.parse(JSON.stringify(n));return o.type==="page-break"?o.label=`${n.label||"Step"} (copy)`:o.type!=="spacer"&&(o.name=uniqueName(n.name,t.map(a=>a?.name).filter(Boolean)),o.label=`${n.label||"Field"} (copy)`),t.splice(r+1,0,o),t}export function moveField(e,r,t){const n=[...e||[]],o=r+t;if(r<0||r>=n.length||o<0||o>=n.length)return n;const[a]=n.splice(r,1);return n.splice(o,0,a),n}export function fieldTitle(e){return e?e.type==="spacer"?"Spacer":e.type==="page-break"?`Page break - ${e.label||"Untitled step"}`:e.label||e.name||"(unlabelled)":""}export function editorDraftQuestion(e,{current:r="",missing:t=!1,when:n=""}={}){const o=n?` (${n})`:"";if(draftChoice(e,{missing:t})==="refuse")return`You left unsaved changes${o} to a form that was deleted, and a different form has this address now - they cannot be put back over it. Throw them away?`;if(t)return`You left unsaved changes to this form${o}, and the form has been deleted since. Throw them away? Keep them to start a new form from them.`;const a=!!e?.base&&e.base!==r;return`You left unsaved changes to this form${o}.`+(a?" The saved form has changed since then - keeping them puts your version over those changes.":"")+" Throw them away?"}export const draftBaseOf=(e,r)=>e??r;export function draftChoice(e,{missing:r=!1}={}){return r?"new":e?.newFrom?"refuse":"restore"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fmtDate as u}from"./dates.js";export const FILTERS=["all","recent","empty"];const c=new Set(["page-break","spacer"]),
|
|
1
|
+
import{fmtDate as u}from"./dates.js";export const FILTERS=["all","recent","empty"];const c=new Set(["page-break","spacer"]),l=(t,o,i=`${o}s`)=>`${t} ${t===1?o:i}`;export function ago(t,o=Date.now()){const i=new Date(t).getTime();if(!Number.isFinite(i))return"";const e=Math.max(0,Math.round((o-i)/1e3));return e<60?"just now":e<3600?`${Math.round(e/60)} min ago`:e<86400?`${l(Math.round(e/3600),"hour")} ago`:e<86400*14?`${l(Math.round(e/86400),"day")} ago`:u(i)}export function formShape(t){const o=Array.isArray(t?.fields)?t.fields:[],i=o.filter(e=>e?.type==="page-break").length;return{questions:o.filter(e=>e&&!c.has(e.type)).length,steps:i?i+1:0,logic:o.some(e=>e&&(e.logic||Array.isArray(e.triggers)&&e.triggers.length))}}export function actionsOn(t){const o=t?.actions||{};return Object.entries({email:"email",collection:"saved",webhook:"webhook",action:"action"}).filter(([e])=>o[e]?.enabled).map(([,e])=>e)}export function formRow(t,o=Date.now()){const{questions:i,steps:e,logic:n}=formShape(t),s=Number(t.submissionCount)||0,r=Number(t.submissionsThisWeek)||0,a=[t.slug,l(i,"question"),e?`${e}-step wizard`:"",n?"conditional logic":"",t.lastSubmissionAt?`last submission ${ago(t.lastSubmissionAt,o)}`:"no submissions yet"].filter(Boolean).join(" \xB7 ");return{slug:t.slug,title:t.title||t.slug,meta:a,total:s,totalText:String(s),week:r,weekText:r?`${r} new this week`:"",plugin:t.plugin||"",project:t.meta?.project||"",wizard:e>0,rowCls:r?"is-recent":""}}export function arrangeForms(t,{filter:o="all",query:i=""}={}){const e=String(i).trim().toLowerCase();return[...t||[]].filter(n=>o==="recent"?Number(n.submissionsThisWeek)>0:o==="empty"?!Number(n.submissionCount):!0).filter(n=>!e||[n.title,n.slug,n.description,n.plugin,n.meta?.project].some(s=>String(s||"").toLowerCase().includes(e))).sort((n,s)=>String(s.lastSubmissionAt||"").localeCompare(String(n.lastSubmissionAt||""))||String(n.title||n.slug).localeCompare(String(s.title||s.slug)))}export function duplicatePayload(t,o){const{collection:i,...e}=t?.actions||{};return{title:String(o||`${t?.title||t?.slug||"Form"} (Copy)`).trim(),description:t?.description||"",fields:t?.fields||[],settings:t?.settings||{},actions:e}}export const shortcodeFor=t=>`[form name="${t}" /]`,slugify=t=>String(t??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");export function newFormDraftQuestion(t,o=[],i=Date.now()){const e=String(t?.values?.title||"").trim(),n=t?.at?ago(t.at,i):"",s=slugify(e),r=s&&(o||[]).some(a=>a?.slug===s);return[`You started a new form, "${e}"${n?` (${n})`:""}, and left it unsaved.`,r?`A form called "${s}" has been made since, so it will need another title.`:"","Throw it away?"].filter(Boolean).join(" ")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEFAULT_SETTINGS=Object.freeze({layout:"cards",sort:"updated",previewLength:160,showCategories:!0,confirmDelete:!0,defaultCategories:[]});const
|
|
1
|
+
export const DEFAULT_SETTINGS=Object.freeze({layout:"cards",sort:"updated",previewLength:160,showCategories:!0,confirmDelete:!0,defaultCategories:[]});const f=new Set(["cards","list"]),g=new Set(["updated","created","title"]);export const PREVIEW_MIN=0,PREVIEW_MAX=600;export function normaliseSettings(o){const t=o&&typeof o=="object"?o:{},e=DEFAULT_SETTINGS,r=Number(t.previewLength);return{layout:f.has(t.layout)?t.layout:e.layout,sort:g.has(t.sort)?t.sort:e.sort,previewLength:Number.isFinite(r)?Math.min(PREVIEW_MAX,Math.max(PREVIEW_MIN,Math.round(r))):e.previewLength,showCategories:typeof t.showCategories=="boolean"?t.showCategories:e.showCategories,confirmDelete:typeof t.confirmDelete=="boolean"?t.confirmDelete:e.confirmDelete,defaultCategories:parseCategories(t.defaultCategories)}}export function parseCategories(o){const t=Array.isArray(o)?o:String(o??"").split(","),e=new Set,r=[];for(const s of t){const a=String(s??"").trim();!a||e.has(a.toLowerCase())||(e.add(a.toLowerCase()),r.push(a))}return r}export function collectCategories(o){return parseCategories((o||[]).flatMap(e=>Array.isArray(e.categories)?e.categories:[])).sort((e,r)=>e.localeCompare(r,void 0,{sensitivity:"base"}))}export function preview(o,t){const e=String(o??"").replace(/\s+/g," ").trim();if(!t||t<=0)return"";if(e.length<=t)return e;const r=e.slice(0,t),s=r.lastIndexOf(" ");return(s>t*.6?r.slice(0,s):r).trimEnd()+"\u2026"}const c=o=>{const t=Date.parse(o);return Number.isNaN(t)?0:t};export function arrangeNotes(o,{query:t="",category:e="",sort:r="updated"}={}){const s=String(t).trim().toLowerCase(),a=String(e).trim().toLowerCase(),l=(o||[]).filter(n=>{const i=Array.isArray(n.categories)?n.categories:[];return a&&!i.some(u=>String(u).toLowerCase()===a)?!1:s?String(n.title??"").toLowerCase().includes(s)||String(n.content??"").toLowerCase().includes(s)||i.some(u=>String(u).toLowerCase().includes(s)):!0}),p=r==="title"?(n,i)=>String(n.title||"").localeCompare(String(i.title||""),void 0,{sensitivity:"base"}):r==="created"?(n,i)=>c(i.createdAt)-c(n.createdAt):(n,i)=>c(i.updatedAt)-c(n.updatedAt);return[...l].sort(p)}export function categoryOptions(o){const t=new Map;for(const e of o||[]){const r=parseCategories(Array.isArray(e.categories)?e.categories:[]);for(const s of r)t.set(s.toLowerCase(),(t.get(s.toLowerCase())??0)+1)}return collectCategories(o).map(e=>{const r=t.get(e.toLowerCase())??0;return{value:e,label:`${e} (${r})`,count:r}})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const LIMITS={maxResults:[1,50],minQueryLength:[1,5],debounceMs:[100,1e3],offsetX:[0,500],offsetY:[0,500],iconSize:[32,96]},DEFAULTS={enabled:!0,placeholder:"Search pages...",keyboardShortcut:!0,maxResults:10,minQueryLength:2,debounceMs:300,displayMode:"navbar",position:"bottom-right",offsetX:32,offsetY:32,iconOnly:!0,iconSize:52,iconRadius:26},POSITIONS=["top-left","top-right","left-center","right-center","bottom-left","bottom-right"],POSITION_LABEL={"top-left":"Top left","top-right":"Top right","left-center":"Left side","right-center":"Right side","bottom-left":"Bottom left","bottom-right":"Bottom right"};const
|
|
1
|
+
export const LIMITS={maxResults:[1,50],minQueryLength:[1,5],debounceMs:[100,1e3],offsetX:[0,500],offsetY:[0,500],iconSize:[32,96]},DEFAULTS={enabled:!0,placeholder:"Search pages...",keyboardShortcut:!0,maxResults:10,minQueryLength:2,debounceMs:300,displayMode:"navbar",position:"bottom-right",offsetX:32,offsetY:32,iconOnly:!0,iconSize:52,iconRadius:26},POSITIONS=["top-left","top-right","left-center","right-center","bottom-left","bottom-right"],POSITION_LABEL={"top-left":"Top left","top-right":"Top right","left-center":"Left side","right-center":"Right side","bottom-left":"Bottom left","bottom-right":"Bottom right"};const c={maxResults:"Max results",minQueryLength:"Minimum query length",debounceMs:"Debounce delay",offsetX:"Horizontal offset",offsetY:"Vertical offset",iconSize:"Icon size"};export const maxRadius=t=>Math.floor((Number(t)||DEFAULTS.iconSize)/2);export function radiusForSize(t,e,o){const n=Number(t)||0,s=maxRadius(o);return n>=maxRadius(e)||n>s?s:n}const r=t=>{const e=typeof t=="number"?t:parseInt(t,10);return Number.isFinite(e)?Math.round(e):null},a=(t,[e,o])=>Math.min(o,Math.max(e,t));export function formFromSettings(t){const e={...DEFAULTS,...t&&typeof t=="object"?t:{}};return{enabled:e.enabled!==!1,placeholder:String(e.placeholder??DEFAULTS.placeholder),keyboardShortcut:e.keyboardShortcut!==!1,maxResults:String(e.maxResults),minQueryLength:String(e.minQueryLength),debounceMs:String(e.debounceMs),displayMode:e.displayMode==="floating"?"floating":"navbar",position:POSITIONS.includes(e.position)?e.position:DEFAULTS.position,offsetX:String(e.offsetX),offsetY:String(e.offsetY),iconOnly:e.iconOnly!==!1,iconSize:String(e.iconSize),iconRadius:String(e.iconRadius)}}export function payloadFromForm(t){const e={...formFromSettings(null),...t||{}},o=i=>{const l=r(e[i]);return l===null?DEFAULTS[i]:a(l,LIMITS[i])},n=o("iconSize"),s=r(e.iconRadius);return{enabled:e.enabled!==!1,placeholder:String(e.placeholder??"").trim().slice(0,120)||DEFAULTS.placeholder,keyboardShortcut:e.keyboardShortcut!==!1,maxResults:o("maxResults"),minQueryLength:o("minQueryLength"),debounceMs:o("debounceMs"),displayMode:e.displayMode==="floating"?"floating":"navbar",position:POSITIONS.includes(e.position)?e.position:DEFAULTS.position,offsetX:o("offsetX"),offsetY:o("offsetY"),iconOnly:e.iconOnly!==!1,iconSize:n,iconRadius:s===null?Math.min(DEFAULTS.iconRadius,maxRadius(n)):a(s,[0,maxRadius(n)])}}export const snapshot=t=>JSON.stringify(payloadFromForm(t));export function clampNotes(t){const e=[];for(const[o,n]of Object.entries(LIMITS)){const s=String(t?.[o]??"").trim(),i=r(s);s===""||i===null?e.push(`${c[o]} is empty - it will be saved as ${DEFAULTS[o]}.`):(i<n[0]||i>n[1])&&e.push(`${c[o]} is ${n[0]}-${n[1]} - ${i} will be saved as ${a(i,n)}.`)}return e}export function liveChips(t,e=null){const o={...DEFAULTS,...t||{}};if(!o.enabled)return[{text:"Off - nothing is added to public pages",tone:"warn"}];const n=[{text:"On",tone:"ok"}];return n.push({text:o.displayMode==="floating"?`Floating button, ${(POSITION_LABEL[o.position]||o.position).toLowerCase()}`:"Icon in the navigation bar",tone:""}),o.keyboardShortcut&&n.push({text:"Ctrl+K / \u2318K",tone:""}),n.push({text:`Up to ${o.maxResults} results`,tone:""}),e&&n.push({text:`${e.searchable} page${e.searchable===1?"":"s"} searchable`,tone:""}),n}export function previewState(t,e){const o=String(t??"").trim(),n=r(e)??DEFAULTS.minQueryLength;return o?o.length<n?{run:!0,note:`Visitors need at least ${n} characters - on the site this would not search yet.`}:{run:!0,note:""}:{run:!1,note:"Type a word or two - results appear as you type."}}export function resultRow(t,e){return{id:`${e}|${t.url}`,url:String(t.url||"/"),title:String(t.title||"Untitled"),snippet:String(t.snippet||t.description||""),meta:[t.url,(t.tags||[]).length?`tags: ${(t.tags||[]).join(", ")}`:""].filter(Boolean).join(" \xB7 "),score:`score ${Number(t.score)||0}`,rank:String(e+1)}}export const searchDraftKey=t=>t?`search.draft.${t}`:null;export function keptDraft(t,e,o){if(!o||!t||typeof t!="object"||t.owner!==o||!t.form||typeof t.form!="object")return null;const n=typeof t.was=="string"?t.was:typeof t.base=="string"?t.base:"";let s=!1;try{s=snapshot(t.form)===e}catch{}return s?null:{form:t.form,was:n,at:typeof t.at=="string"?t.at:"",stale:!!n&&n!==e}}export function draftQuestion({when:t="",stale:e=!1}={}){return{title:"Unsaved changes",text:`Throw away the search settings you left unsaved${t?` (${t})`:""}?`+(e?" Note: the saved settings have changed since you began - check before you save.":""),confirmText:"Throw away",cancelText:"Keep them"}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const fingerprint=e=>JSON.stringify(e??null);const u=e=>!!(e.noindex||e.unlisted),c=e=>!e.noindex&&!!(e.error||e.failing>0);export function filterPages(e=[],{show:t="all",source:n="",query:r=""}={}){const o=String(r||"").trim().toLowerCase();return(e||[]).filter(s=>(t==="all"||(t==="hidden"?u(s):c(s)))&&(!n||s.source===n)&&(!o||[s.urlPath,s.title,s.label].some(i=>String(i||"").toLowerCase().includes(o)))).sort((s,i)=>(s.error?-1:0)-(i.error?-1:0)||(s.score??101)-(i.score??101))}export function pageCounts(e=[],{source:t=""}={}){const n=(e||[]).filter(r=>!t||r.source===t);return{all:n.length,problems:n.filter(c).length,hidden:n.filter(u).length}}export const countText=e=>typeof e=="number"&&e>0?String(e):"";export function draftKey(e,t,n=""){return!e||!t?null:`seo.draft.${e}.${t}${n===""||n===null||n===void 0?"":`.${encodeURIComponent(String(n))}`}`}export function usableDraft(e,t){if(!e||typeof e!="object"||!e.values||typeof e.values!="object")return null;const n=Object.keys(t||{});if(!n.length||!n.every(o=>o in e.values))return null;const r=Object.fromEntries(n.map(o=>[o,e.values[o]]));return fingerprint(r)===fingerprint(Object.fromEntries(n.map(o=>[o,t[o]])))?null:{values:r,was:typeof e.was=="string"?e.was:"",at:typeof e.at=="string"?e.at:""}}export function restoreQuestion({what:e,when:t="",stale:n=!1,gone:r=""}){const o=t?` (${t})`:"",l=r?` Note: ${e} has been removed since. ${r}`:n?` Note: ${e} changed since you began - check before you save.`:"";return{title:"Unsaved changes",text:`Throw away the changes you left unsaved on ${e}${o}?${l}`,confirmText:"Throw away",cancelText:"Keep them"}}export function draftAction({dirty:e,owns:t}){return e?"write":t?"remove":"none"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const STATUSES=["not-started","in-progress","completed"],PRIORITIES=["highest","high","medium","low","lowest"],FILTERS=["open","all","not-started","in-progress","completed","overdue"],SORTS=["created","priority","due","status"],DEFAULT_SETTINGS=Object.freeze({defaultPriority:"medium",defaultFilter:"open",sort:"created",relativeDue:!0,confirmDelete:!0});const
|
|
1
|
+
export const STATUSES=["not-started","in-progress","completed"],PRIORITIES=["highest","high","medium","low","lowest"],FILTERS=["open","all","not-started","in-progress","completed","overdue"],SORTS=["created","priority","due","status"],DEFAULT_SETTINGS=Object.freeze({defaultPriority:"medium",defaultFilter:"open",sort:"created",relativeDue:!0,confirmDelete:!0});const l=(e,t,r)=>t.includes(e)?e:r;export function normaliseSettings(e){const t=e&&typeof e=="object"?e:{},r=DEFAULT_SETTINGS;return{defaultPriority:l(t.defaultPriority,PRIORITIES,r.defaultPriority),defaultFilter:l(t.defaultFilter,FILTERS,r.defaultFilter),sort:l(t.sort,SORTS,r.sort),relativeDue:typeof t.relativeDue=="boolean"?t.relativeDue:r.relativeDue,confirmDelete:typeof t.confirmDelete=="boolean"?t.confirmDelete:r.confirmDelete}}export function startOfDay(e=new Date){const t=new Date(e);return t.setHours(0,0,0,0),t.getTime()}export function dueDay(e){if(!e)return null;const t=/^(\d{4})-(\d{2})-(\d{2})/.exec(String(e));return t?new Date(Number(t[1]),Number(t[2])-1,Number(t[3])).getTime():null}export function daysUntil(e,t=new Date){const r=dueDay(e);return r===null?null:Math.round((r-startOfDay(t))/864e5)}export function isOverdue(e,t=new Date){const r=daysUntil(e.dueAt,t);return r!==null&&r<0&&e.status!=="completed"}export function dueLabel(e,{relative:t=!0,now:r=new Date,format:o}={}){const u=daysUntil(e,r);return u===null?"":t?u===0?"Today":u===1?"Tomorrow":u===-1?"Yesterday":u>0?`In ${u} days`:`${-u} days ago`:o?o(dueDay(e)):String(e).slice(0,10)}export function isoDay(e,t=new Date){const r=new Date(startOfDay(t)+e*864e5+432e5),o=u=>String(u).padStart(2,"0");return`${r.getFullYear()}-${o(r.getMonth()+1)}-${o(r.getDate())}`}export function arrangeTodos(e,{filter:t="open",query:r="",sort:o="created",now:u=new Date}={}){const d=String(r).trim().toLowerCase(),p=(e||[]).filter(n=>t==="open"&&n.status==="completed"||STATUSES.includes(t)&&n.status!==t||t==="overdue"&&!isOverdue(n,u)?!1:!d||String(n.text??"").toLowerCase().includes(d)),s=(n,i)=>{const f=n.indexOf(i);return f===-1?n.length:f},a=(n,i)=>String(n.createdAt??"").localeCompare(String(i.createdAt??"")),y={created:a,priority:(n,i)=>s(PRIORITIES,n.priority)-s(PRIORITIES,i.priority)||a(n,i),due:(n,i)=>(dueDay(n.dueAt)??1/0)-(dueDay(i.dueAt)??1/0)||s(PRIORITIES,n.priority)-s(PRIORITIES,i.priority),status:(n,i)=>s(STATUSES,n.status)-s(STATUSES,i.status)||a(n,i)}[o]??a,c=n=>n.status==="completed"?1:0;return[...p].sort((n,i)=>c(n)-c(i)||y(n,i))}export function countTodos(e,t=new Date){const r={all:0,open:0,"not-started":0,"in-progress":0,completed:0,overdue:0};for(const o of e||[])r.all++,o.status!=="completed"&&r.open++,r[o.status]!==void 0&&r[o.status]++,isOverdue(o,t)&&r.overdue++;return r}export function quickAddKept(e,t){return!t||!String(t.text??"").trim()?!1:String(t.text).trim()!==String(e?.text??"").trim()?!0:(t.priority??e.priority)!==e.priority||(t.due??"")!==(e.due??"")}export function changedFields(e,t){const r={};for(const[o,u]of Object.entries(t||{}))(u??null)!==(e?.[o]??null)&&(r[o]=u);return r}
|
package/admin/js/lib/tool-kit.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export function onceClosed(t,e=()=>{}){const
|
|
1
|
+
export function onceClosed(t,e=()=>{}){const n=t.close.bind(t),o=t.close;let r=!1;t.close=(...c)=>{const i=n(...c);return r||(r=!0,e(),setTimeout(()=>{t.close=o;try{t.destroy?.()}catch{}t.element?.remove()},400)),i}}export function openMenuAt(t){const e=t.getBoundingClientRect();t.dispatchEvent(new MouseEvent("contextmenu",{bubbles:!0,cancelable:!0,clientX:e.left,clientY:e.bottom,view:window}))}export function settingsStore(t,e){return{read(){try{return e(S.get(t))}catch{return e(null)}},write(n){try{S.set(t,n)}catch{}}}}export function viewLifetime(t){let e=[],n=!1;const o=()=>{const c=e;e=[],n=!0;try{c.forEach(i=>{try{i?.()}catch{}})}finally{n=!1}},r=()=>{t.test(location.hash)||o()};return{get leaving(){return n},add(c){return e.push(c),()=>{const i=e.indexOf(c);i!==-1&&e.splice(i,1)}},reset(){o(),window.addEventListener("hashchange",r),e.push(()=>window.removeEventListener("hashchange",r))}}}export function sidebarCount(t,e={}){try{window.dispatchEvent(new CustomEvent("dm:sidebar-count",{detail:{url:t,...e}}))}catch{}}export function promptWith(t,e,n){const o=E.prompt(t,e),r=[...document.querySelectorAll(".dm-dialog-input")].at(-1);return r&&(r.value=String(n??""),setTimeout(()=>{try{r.select()}catch{}},60)),o}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{apiRequest as
|
|
1
|
+
import{apiRequest as H}from"../api.js";import{onceClosed as W,openMenuAt as Y}from"./tool-kit.js";const G=(a,e="GET")=>H(a,{method:e}),U=a=>{const e=document.createElement("template");return e.innerHTML=a,e.content.firstElementChild};export function toolDraftKey(a,e){let n;try{n=S.get("auth_user")?.id}catch{}return n?`tools.draft.${n}.${a}.${e||"new"}`:null}export function orphanToolDrafts(a,e){const n=toolDraftKey(a,"x")?.slice(0,-1);if(!n)return[];const d=new Set(e);let f=[];try{f=typeof S.keys=="function"?S.keys():[]}catch{return[]}const h=[];for(const p of f){if(typeof p!="string"||!p.startsWith(n))continue;const k=p.slice(n.length);if(!k||k==="new"||k.includes(".")||d.has(k))continue;let i=null;try{i=S.get(p)}catch{}i?.values&&h.push({id:k,draft:i})}return h}export function restoreQuestion({gone:a=!1,changed:e=!1,noun:n="item"}={}){return{message:a?`You left unsaved changes to a ${n} that has since been deleted. Throw them away? Keep them to save them as a new ${n}.`:"You left unsaved changes here last time. Throw them away?"+(e?` The saved ${n} has changed since you made them - if you keep them, check them before saving.`:" Keep them to carry on where you left off."),options:{title:"Unsaved changes from last time",confirmText:"Throw away",cancelText:"Keep them"}}}export function protectToolEditor({so:a,panel:e,tool:n,id:d,read:f,write:h,lifetime:p,was:k,gone:i=!1,noun:T="item",persist:b=null}){let v=toolDraftKey(n,d);const x=t=>{if(typeof b!="function")return t;try{return b(structuredClone(t))??t}catch{return t}},y=t=>{if(typeof b!="function"||typeof t!="string")return t;try{return JSON.stringify(x(JSON.parse(t)))}catch{return t}};let o=JSON.stringify(f()),l=!1,c=!1,g=!1,w=o,m=i?"":k,$,s=null,q=null;const r=a.close.bind(a),u=()=>{try{return v?S.get(v):null}catch{return null}},C=()=>{try{v&&S.remove(v)}catch{}},R=()=>JSON.stringify(f())!==o,N=()=>{if(!(l||g||!v))try{const t=x(f());R()&&JSON.stringify(t)!==y(o)?S.set(v,{values:t,base:y(w),...m!==void 0?{was:m}:{}}):C()}catch{}},O=()=>queueMicrotask(N);e.addEventListener("input",O),e.addEventListener("change",O),e.addEventListener("click",O);const B=t=>{N(),!l&&R()&&(t.preventDefault(),t.returnValue="")};window.addEventListener("beforeunload",B);const P=()=>{if(l=!0,q?.(),s){try{(s.querySelector(".dm-dialog-cancel")||s).click?.(),s.remove?.()}catch{}s=null}$?.(),e.removeEventListener("input",O),e.removeEventListener("change",O),e.removeEventListener("click",O),window.removeEventListener("beforeunload",B),r()};a.close=async()=>{if(l)return!1;if(p?.leaving)return N(),P(),!0;if(c||g)return!1;if(R()){c=!0;const t=await E.confirm("Close this editor? Your changes will be kept as a browser draft for the next time you open it.",{title:"Unsaved changes",confirmText:"Keep draft and close",cancelText:"Keep editing"});if(c=!1,!t||l)return!1}return N(),P(),!0},$=p.add(()=>{l||(N(),P())});const L=u();let F=Promise.resolve("none");if(L?.values&&JSON.stringify(L.values)!==y(o)){const t=!i&&(m!==void 0&&L.was!==void 0?L.was!==m:L.base!==void 0&&L.base!==y(o)),{message:J,options:K}=restoreQuestion({gone:i,changed:t,noun:T});g=!0,F=Promise.resolve().then(()=>{const A=E.confirm(J,K);try{s=typeof document>"u"?null:[...document.querySelectorAll(".dm-dialog-overlay")].at(-1)??null}catch{s=null}if(l&&s){const I=s;s=null,I.querySelector(".dm-dialog-cancel")?.click(),I.remove()}else s&&(q=z(s));return A}).catch(()=>!1).then(A=>(g=!1,s=null,q?.(),q=null,l?"left":A?(C(),"thrown"):(h(L.values),i||(L.was!==void 0&&(m=L.was),L.base!==void 0&&(w=L.base)),N(),"kept")))}return{saved(t){const{id:J,was:K}=t!==null&&typeof t=="object"?t:{was:t};C(),J!=null&&J!==""&&(v=toolDraftKey(n,J),i=!1),o=JSON.stringify(f()),w=o,K!==void 0&&(m=K),C()},discard(){o=JSON.stringify(f()),w=o,C()},asked:F}}function z(a){const e=a.querySelector(".dm-dialog-cancel"),n=h=>{h.key==="Enter"&&(h.stopImmediatePropagation(),h.target!==e&&h.preventDefault())};document.addEventListener("keydown",n,!0);const d=()=>{try{e?.isConnected&&e.focus()}catch{}};d();const f=[setTimeout(d,120),setTimeout(d,300)];return()=>{document.removeEventListener("keydown",n,!0),f.forEach(clearTimeout)}}export function mountToolTrash({root:a,toolbar:e,tool:n,endpoint:d,itemPath:f,label:h,titleOf:p,reload:k,lifetime:i}){const T=U('<button type="button" class="btn btn-sm btn-ghost tool-trash-button">Trash</button>');T.setAttribute("aria-label",`${h} Trash`),e.appendChild(T);let b;const v=()=>{if(b)return;const x=E.slideover({title:`${h} Trash`,size:"md",position:"right"});b=x;const y=U(`<div class="tool-trash-panel" style="display:flex;flex-direction:column;gap:.75rem">
|
|
2
2
|
<p>Items stay here until you restore them or delete them permanently.</p>
|
|
3
3
|
<div role="alert" data-if="error.value"><span data-bind-text="error.value"></span>
|
|
4
4
|
<button type="button" class="btn btn-sm btn-ghost" data-on-click="retry" data-bind-disabled="loading.value">Retry</button></div>
|
|
5
5
|
<p data-if="loading.value">Loading Trash\u2026</p>
|
|
6
6
|
<p data-if="empty.value">Trash is empty.</p>
|
|
7
7
|
<div class="tool-trash-rows" data-each="rows.value key=id"></div>
|
|
8
|
-
</div>`);
|
|
8
|
+
</div>`);y.querySelector(".tool-trash-rows").innerHTML=`<div class="tool-trash-row" data-id="{{id}}" tabindex="0" style="display:flex;align-items:center;gap:.5rem;padding:.5rem;border-bottom:1px solid var(--dm-border)">
|
|
9
9
|
<span style="flex:1;overflow-wrap:anywhere">{{title}}</span><small>{{when}}</small>
|
|
10
|
-
<button type="button" class="btn btn-sm btn-ghost tool-trash-more" aria-label="Trash item actions">\u22EE</button></div>`;const
|
|
10
|
+
<button type="button" class="btn btn-sm btn-ghost tool-trash-more" aria-label="Trash item actions">\u22EE</button></div>`;const o=M.observable([]),l=M.observable(""),c=M.observable(!1),g=M.observable(!1);let w=!1;const m=async()=>{if(!c.peek()){c.value=!0;try{const r=await G(`${d}/trash`);if(w)return;o.value=r.map(u=>({id:u.id,title:String(p(u)||"Untitled").slice(0,160),when:D(u.trashedAt).fromNow()})),l.value=""}catch{w||(l.value="Could not load Trash.")}finally{w||(c.value=!1)}}},$=async(r,u)=>{if(!g.peek()){g.value=!0;try{if(u&&!await E.confirm("Delete this item permanently? This cannot be undone.",{confirmText:"Delete permanently"}))return;await G(u?`${d}/trash/${r}`:`${d}${f}/${r}/restore`,u?"DELETE":"POST"),w||(o.value=o.peek().filter(C=>C.id!==r)),await k(),E.toast(u?"Permanently deleted.":"Restored.",{type:"success"})}catch{E.toast(u?"Could not delete the item.":"Could not restore the item.",{type:"error"})}finally{g.value=!1}}};x.element.appendChild(y);const s=M.applyBindings({rows:o,error:l,loading:c,empty:M.computed(()=>!c.value&&!l.value&&!o.value.length)},y,{methods:{retry:m}}),q=E.contextMenu(y,{match:".tool-trash-row",inherit:!1,density:"compact",items:r=>[{label:"Restore",disabled:g.peek(),action:()=>$(r.dataset.id,!1)},{label:"Delete permanently\u2026",disabled:g.peek(),action:()=>$(r.dataset.id,!0)}]});y.addEventListener("click",r=>{r.target.closest(".tool-trash-more")&&Y(r.target.closest(".tool-trash-row"))}),y.addEventListener("keydown",r=>{const u=r.target.closest?.(".tool-trash-row");!u||r.target!==u||(r.key==="F10"&&r.shiftKey||r.key==="Enter")&&(r.preventDefault(),Y(u))}),W(x,()=>{w=!0,b=null,s.dispose(),q?.destroy?.()}),x.open(),m()};T.addEventListener("click",v),i.add(()=>{T.removeEventListener("click",v),b?.close(),T.remove()})}export function mountGroupBatch({root:a,ensureGroup:e,byId:n,patch:d,withGroup:f,lifetime:h}){const p=U(`<div class="tool-bulk-result" role="status" data-if="message.value" style="margin:.5rem 0">
|
|
11
11
|
<span data-bind-text="message.value"></span>
|
|
12
|
-
<button type="button" class="btn btn-sm btn-ghost" data-if="hasFailed.value" data-on-click="retry" data-bind-disabled="busy.value">Retry failed</button></div>`),
|
|
12
|
+
<button type="button" class="btn btn-sm btn-ghost" data-if="hasFailed.value" data-on-click="retry" data-bind-disabled="busy.value">Retry failed</button></div>`),k=a.querySelector(".ct-bar");k?k.after(p):a.appendChild(p);const i=M.observable(""),T=M.observable(!1),b=M.observable([]);let v="",x=!1;const y=async(l,c)=>{if(!T.peek()){T.value=!0,v=c;try{if(!await e(c)){b.value=[...l],i.value="Could not create the group. Retry when it is available.";return}let g=0,w=0;const m=[];for(const $ of[...new Set(l)]){if(x)return;const s=n($);if(!s){m.push($);continue}if((s.groups||[]).includes(c)){w++;continue}await d($,{groups:f(s.groups,c,!0)})?g++:m.push($)}if(x)return;b.value=m,i.value=`${g} added to ${c} \xB7 ${w} already in group \xB7 ${m.length} failed`}finally{T.value=!1}}},o=M.applyBindings({message:i,busy:T,hasFailed:M.computed(()=>b.value.length>0)},p,{methods:{retry:()=>y(b.peek(),v)}});return h.add(()=>{x=!0,o.dispose(),p.remove()}),{run:y}}
|
|
@@ -47,13 +47,16 @@
|
|
|
47
47
|
<p class="an-note" data-if="isAllTime.value">The chart shows the last 30 days; the figures above are all time.</p>
|
|
48
48
|
</div>
|
|
49
49
|
|
|
50
|
+
<div class="an-card">
|
|
50
51
|
<div class="an-tabs" role="tablist">
|
|
51
|
-
<button type="button" role="tab" data-on-click="showPages" data-bind-class="section.value === 'pages' && 'is-active'">Top pages
|
|
52
|
-
|
|
53
|
-
<button type="button" role="tab" data-on-click="
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
52
|
+
<button type="button" role="tab" data-on-click="showPages" data-bind-class="section.value === 'pages' && 'is-active'">Top pages
|
|
53
|
+
<span class="an-count" data-if="counts.value.pages !== null" data-bind-text="counts.value.pages"></span></button>
|
|
54
|
+
<button type="button" role="tab" data-on-click="showJourneys" data-bind-class="section.value === 'journeys' && 'is-active'">Journeys
|
|
55
|
+
<span class="an-count" data-if="counts.value.journeys !== null" data-bind-text="counts.value.journeys" title="Sessions"></span></button>
|
|
56
|
+
<button type="button" role="tab" data-on-click="showSpikes" data-bind-class="section.value === 'spikes' && 'is-active'">Spikes
|
|
57
|
+
<span class="an-count is-alert" data-if="counts.value.spikes" data-bind-text="counts.value.spikes"></span></button>
|
|
58
|
+
<span data-help-title="Pages in these lists"
|
|
59
|
+
data-help="Click a page (or press Enter on it) to open it on the site. Right-click it, use its ⋮ or press Shift+F10 for the rest: edit it, copy its address, or narrow the list to it."></span>
|
|
57
60
|
<label class="an-filter" data-if="section.value === 'pages'">
|
|
58
61
|
<span data-icon="search" data-icon-size="13"></span>
|
|
59
62
|
<input type="search" class="form-input" placeholder="Filter pages…" aria-label="Filter pages" data-model="filter.value">
|
|
@@ -61,8 +64,9 @@
|
|
|
61
64
|
</div>
|
|
62
65
|
|
|
63
66
|
<section class="an-panel" data-if="section.value === 'pages'">
|
|
67
|
+
<div class="an-loading dm-loading-slot" data-bind-hidden="!loading.value">Loading…</div>
|
|
64
68
|
<div class="an-list" data-each="pageRows.value key=key"></div>
|
|
65
|
-
<p class="an-note" data-bind-text="pagesNote.value"></p>
|
|
69
|
+
<p class="an-note" data-bind-hidden="loading.value" data-bind-text="pagesNote.value"></p>
|
|
66
70
|
</section>
|
|
67
71
|
|
|
68
72
|
<section class="an-panel an-journeys" data-if="section.value === 'journeys'">
|
|
@@ -72,13 +76,14 @@
|
|
|
72
76
|
<div class="an-list" data-each="exitRows.value key=key"></div></div>
|
|
73
77
|
<div class="an-wide"><h3>Common paths <span data-help="The most frequent step from one page straight to another."></span></h3>
|
|
74
78
|
<div class="an-list" data-each="pathRows.value key=key"></div></div>
|
|
75
|
-
<p class="an-note" data-if="!sessionsCount.value">No sessions recorded in this range yet.</p>
|
|
79
|
+
<p class="an-note" data-if="!loading.value && !sessionsCount.value">No sessions recorded in this range yet.</p>
|
|
76
80
|
</section>
|
|
77
81
|
|
|
78
82
|
<section class="an-panel" data-if="section.value === 'spikes'">
|
|
79
83
|
<div class="an-list" data-each="spikes.value key=key"></div>
|
|
80
|
-
<p class="an-note" data-if="!spikes.value.length">Nothing unusual today. A spike is a page running well above its last seven days at this hour.</p>
|
|
84
|
+
<p class="an-note" data-if="!loading.value && !spikes.value.length">Nothing unusual today. A spike is a page running well above its last seven days at this hour.</p>
|
|
81
85
|
</section>
|
|
86
|
+
</div>
|
|
82
87
|
</div>
|
|
83
88
|
|
|
84
89
|
<style>
|
|
@@ -86,14 +91,14 @@
|
|
|
86
91
|
.an-bar { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
|
|
87
92
|
.an-range { width: auto; height: 2rem; padding-top: 0; padding-bottom: 0; }
|
|
88
93
|
.an-live { display: inline-flex; align-items: center; gap: .4rem; font-size: var(--dm-text-xs); color: var(--dm-text-muted);
|
|
89
|
-
padding: .15rem .6rem; border-radius: 999px; background: var(--dm-
|
|
94
|
+
padding: .15rem .6rem; border-radius: 999px; background: var(--dm-hover-bg); }
|
|
90
95
|
.an-live-dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--dm-text-muted); }
|
|
91
|
-
.an-live.is-on .an-live-dot { background: var(--dm-success
|
|
96
|
+
.an-live.is-on .an-live-dot { background: var(--dm-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-success) 25%, transparent); }
|
|
92
97
|
.an-bar-end { display: flex; gap: .3rem; margin-left: auto; }
|
|
93
98
|
.an-more { font-size: var(--dm-text-lg); line-height: 1; padding: .2rem .5rem; }
|
|
94
99
|
|
|
95
100
|
.an-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .6rem; }
|
|
96
|
-
.an-kpi { display: flex; flex-direction: column; gap: .15rem; padding: .6rem .8rem;
|
|
101
|
+
.an-kpi { display: flex; flex-direction: column; gap: .15rem; padding: .6rem .8rem; background: var(--dm-surface);
|
|
97
102
|
border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); }
|
|
98
103
|
.an-kpi-value { font-size: var(--dm-text-xl); font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
99
104
|
.an-kpi-label { font-size: var(--dm-text-xs); color: var(--dm-text-muted); display: inline-flex; align-items: center; }
|
|
@@ -105,12 +110,17 @@
|
|
|
105
110
|
.an-tabs [role="tab"] { border: 0; background: none; cursor: pointer; font: inherit; font-size: var(--dm-text-sm);
|
|
106
111
|
padding: .45rem .7rem; color: var(--dm-text-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; }
|
|
107
112
|
.an-tabs [role="tab"].is-active { color: var(--dm-text); border-bottom-color: var(--dm-primary); }
|
|
108
|
-
.an-count { font-size: var(--dm-text-xs); padding: 0 .4rem; border-radius: 999px;
|
|
113
|
+
.an-count { font-size: var(--dm-text-xs); padding: 0 .4rem; border-radius: 999px; margin-left: .2rem; font-variant-numeric: tabular-nums;
|
|
114
|
+
background: var(--dm-hover-bg); color: var(--dm-text-muted); }
|
|
115
|
+
.an-count.is-alert { background: var(--dm-warning); color: var(--dm-text-inverse); }
|
|
109
116
|
.an-filter { position: relative; margin: 0 0 .3rem auto; }
|
|
110
117
|
.an-filter > svg, .an-filter > [data-icon] { position: absolute; left: .55rem; top: 50%; transform: translateY(-50%); color: var(--dm-text-muted); pointer-events: none; }
|
|
111
118
|
.an-filter .form-input { height: 1.85rem; padding-left: 1.75rem; width: 14rem; }
|
|
112
119
|
|
|
120
|
+
.an-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); background: var(--dm-surface); padding: .25rem .75rem .75rem; }
|
|
121
|
+
.an-card .an-panel { padding-top: .5rem; }
|
|
113
122
|
.an-panel { display: flex; flex-direction: column; gap: .5rem; }
|
|
123
|
+
|
|
114
124
|
.an-journeys { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
|
115
125
|
.an-journeys .an-wide, .an-journeys .an-note { grid-column: 1 / -1; }
|
|
116
126
|
.an-journeys h3 { margin: 0 0 .35rem; font-size: var(--dm-text-xs); font-weight: 600; color: var(--dm-text-muted);
|
|
@@ -120,7 +130,9 @@
|
|
|
120
130
|
.an-list { display: flex; flex-direction: column; }
|
|
121
131
|
.an-row { position: relative; display: flex; align-items: center; gap: .6rem; padding: .3rem .4rem .3rem .6rem;
|
|
122
132
|
font-size: var(--dm-text-sm); border-radius: var(--dm-radius-sm); isolation: isolate; }
|
|
123
|
-
.an-row:hover { background: var(--dm-
|
|
133
|
+
.an-row:hover { background: var(--dm-hover-bg); }
|
|
134
|
+
.an-row { cursor: pointer; }
|
|
135
|
+
.an-row:focus-visible { outline: 2px solid var(--dm-focus-ring, var(--dm-primary)); outline-offset: -2px; }
|
|
124
136
|
.an-bar-fill { position: absolute; inset: 2px auto 2px 0; z-index: -1; border-radius: var(--dm-radius-sm);
|
|
125
137
|
background: color-mix(in srgb, var(--dm-primary) 14%, transparent); }
|
|
126
138
|
.an-rank { flex: 0 0 1.6rem; color: var(--dm-text-muted); font-size: var(--dm-text-xs); text-align: right; }
|
|
@@ -40,9 +40,21 @@
|
|
|
40
40
|
<span data-icon="trash" data-icon-size="13"></span> Delete
|
|
41
41
|
</button>
|
|
42
42
|
<button type="button" class="btn btn-sm btn-ghost" data-on-click="clearSelection">Clear</button>
|
|
43
|
+
<span data-help-title="Working with several"
|
|
44
|
+
data-help="Tick contacts (or ⋮ > Select all shown) to file them in a group together or move them to Trash. Deleted contacts can be restored from Trash."></span>
|
|
43
45
|
</div>
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
<!-- Changes left unsaved on a contact that has since been deleted (or moved to Trash). -->
|
|
48
|
+
<div class="ct-orphans" role="status" data-if="hasOrphans.value">
|
|
49
|
+
<span data-icon="alert-circle" data-icon-size="14"></span>
|
|
50
|
+
<span class="ct-orphans-text" data-bind-text="orphanText.value"></span>
|
|
51
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="reviewOrphan">Review…</button>
|
|
52
|
+
<span data-help-title="Unsaved changes, contact gone"
|
|
53
|
+
data-help="You left an editor with changes not saved, and the contact has since been deleted - here or in another tab. Review opens them as a new contact: save it to keep them, or throw them away. Restoring the contact from Trash puts the changes back on it instead."></span>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<!-- The first load's skeleton (lib/loading.js); a block, so the skeleton has room. -->
|
|
57
|
+
<div class="ct-loading dm-loading-slot" data-bind-hidden="!firstLoad.value">Loading contacts…</div>
|
|
46
58
|
<p class="ct-state" data-if="isEmpty.value" data-bind-text="emptyText.value"></p>
|
|
47
59
|
|
|
48
60
|
<div class="ct-list">
|
|
@@ -80,12 +92,20 @@
|
|
|
80
92
|
.ct-chip { display: inline-flex; align-items: center; gap: .35rem; padding: .15rem .65rem; border-radius: 999px; cursor: pointer;
|
|
81
93
|
border: 1px solid var(--dm-border); background: none; color: var(--dm-text-muted); font: inherit; font-size: var(--dm-text-xs); }
|
|
82
94
|
.ct-chip:hover { border-color: var(--dm-primary); color: var(--dm-text); }
|
|
83
|
-
.ct-chip.is-active { background: var(--dm-primary); border-color: var(--dm-primary); color: var(--dm-primary-text
|
|
95
|
+
.ct-chip.is-active { background: var(--dm-primary); border-color: var(--dm-primary); color: var(--dm-primary-text); }
|
|
84
96
|
.ct-chip b { font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
85
97
|
|
|
86
98
|
.ct-bulk { display: flex; align-items: center; gap: .5rem; padding: .4rem .75rem; font-size: var(--dm-text-sm);
|
|
87
99
|
border-radius: var(--dm-radius-md); background: color-mix(in srgb, var(--dm-primary) 10%, transparent); }
|
|
88
100
|
.ct-state { margin: 1.5rem 0; color: var(--dm-text-muted); text-align: center; }
|
|
101
|
+
.ct-loading { display: block; min-height: 4rem; color: var(--dm-text-muted); }
|
|
102
|
+
.ct-orphans { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; padding: .4rem .75rem; font-size: var(--dm-text-sm);
|
|
103
|
+
border: 1px solid var(--dm-warning); border-radius: var(--dm-radius-md); background: var(--dm-warning-light); color: var(--dm-text); }
|
|
104
|
+
.ct-orphans-text { flex: 1 1 16rem; min-width: 0; }
|
|
105
|
+
.ct-panel.is-saving { opacity: .7; }
|
|
106
|
+
.ct-group-row { cursor: pointer; }
|
|
107
|
+
.ct-group-row:hover, .ct-group-row:focus-visible { background: var(--dm-hover-bg); outline: none; }
|
|
108
|
+
.ct-group-row:focus-visible { box-shadow: inset 0 0 0 2px var(--dm-border-focus); }
|
|
89
109
|
|
|
90
110
|
.ct-list { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
91
111
|
.ct-list:has(.ct-rows:empty) { border: 0; }
|
|
@@ -99,11 +119,12 @@
|
|
|
99
119
|
.ct-head > span { display: inline-flex; align-items: center; min-width: 0; }
|
|
100
120
|
.ct-row { border-bottom: 1px solid var(--dm-border); font-size: var(--dm-text-sm); cursor: pointer; }
|
|
101
121
|
.ct-row:last-child { border-bottom: 0; }
|
|
102
|
-
.ct-row:hover, .ct-row:focus-visible { background: var(--dm-
|
|
122
|
+
.ct-row:hover, .ct-row:focus-visible { background: var(--dm-hover-bg); outline: none; }
|
|
123
|
+
.ct-row:focus-visible { box-shadow: inset 0 0 0 2px var(--dm-border-focus); }
|
|
103
124
|
.ct-row.is-selected { background: color-mix(in srgb, var(--dm-primary) 8%, transparent); }
|
|
104
125
|
.ct-fav { border: 0; background: none; cursor: pointer; padding: 0; font-size: var(--dm-text-base); line-height: 1; color: var(--dm-text-muted); opacity: .35; }
|
|
105
126
|
.ct-fav:hover { opacity: .8; }
|
|
106
|
-
.ct-fav.is-on { color: var(--dm-warning
|
|
127
|
+
.ct-fav.is-on { color: var(--dm-warning); opacity: 1; }
|
|
107
128
|
.ct-name { display: flex; align-items: center; gap: .6rem; min-width: 0; }
|
|
108
129
|
.ct-avatar { flex: 0 0 auto; width: 1.9rem; height: 1.9rem; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
|
|
109
130
|
font-size: var(--dm-text-xs); font-weight: 600; background: color-mix(in srgb, var(--dm-primary) 18%, transparent); color: var(--dm-primary); }
|
|
@@ -116,7 +137,7 @@
|
|
|
116
137
|
.ct-groups-text { font-size: var(--dm-text-xs); }
|
|
117
138
|
.ct-kebab { border: 0; background: none; cursor: pointer; color: var(--dm-text-muted); font-size: var(--dm-text-base); line-height: 1;
|
|
118
139
|
padding: 0 .3rem; border-radius: var(--dm-radius-sm); }
|
|
119
|
-
.ct-kebab:hover { color: var(--dm-primary); background: var(--dm-
|
|
140
|
+
.ct-kebab:hover { color: var(--dm-primary); background: var(--dm-hover-bg); }
|
|
120
141
|
|
|
121
142
|
/* Density and hidden columns, from settings - classes on the list, so rows never re-render for them. */
|
|
122
143
|
.ct-list.is-compact .ct-row { padding-top: .25rem; padding-bottom: .25rem; }
|