domma-cms 0.101.1 → 0.101.3
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/js/lib/block-editor-arrange.js +37 -2
- package/admin/js/templates/block-editor.html +35 -5
- package/admin/js/templates/docs/usage-shortcodes.html +20 -0
- package/admin/js/templates/users.html +6 -2
- package/admin/js/views/block-editor.js +5 -5
- package/admin/js/views/users.js +3 -3
- package/package.json +1 -1
|
@@ -1,2 +1,37 @@
|
|
|
1
|
-
import{fmtDate as
|
|
2
|
-
|
|
1
|
+
import{fmtDate as M}from"./dates.js";export const NAME_RE=/^[a-z0-9][a-z0-9-]*$/,MAX_CSS=50*1024,FORM_FIELDS=["name","project","bundled","content","css"];export function formFromBlock(e,{project:r=""}={}){return e?{name:String(e.name||""),project:String(e.meta?.project||""),bundled:e.bundled===!0,content:String(e.content??""),css:String(e.css??"")}:{name:"",project:String(r||""),bundled:!1,content:"",css:""}}export function snapshot(e){const r=e||{};return JSON.stringify(FORM_FIELDS.map(n=>n==="bundled"?r[n]===!0:String(r[n]??"")))}export function checkForm(e,{isNew:r,taken:n=[]}={}){const t=String(e?.name||"").trim(),o={};r&&(t?NAME_RE.test(t)?n.includes(t)&&(o.name=`"${t}" is already a block - open it from the list, or choose another name.`):o.name="Lowercase letters, digits and hyphens, starting with a letter or digit.":o.name="A block needs a name.");const i=String(e?.css??"");return i.length>MAX_CSS&&(o.css=`The CSS is ${Math.ceil(i.length/1024)} KB - a block's CSS is limited to 50 KB.`),{errors:o,name:t}}export function payloadFromForm(e,r={}){return{content:String(e?.content??""),bundled:e?.bundled===!0,css:String(e?.css??""),meta:{...r||{},project:String(e?.project||"")||null}}}export function extractPlaceholders(e){const r=new Set;for(const[,n]of y(e).matchAll(/\{\{([\w_]+)(?::[^{}]+)?\}\}/g))r.add(n);return[...r]}export function rawPlaceholders(e){return new Set([...y(e).matchAll(/\{\{\{(\w+)\}\}\}/g)].map(r=>r[1]))}const y=e=>String(e??"").replace(/<!--[\s\S]*?-->/g,""),d=e=>String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");export{d as escapeHtml};export function formatDate(e,r){if(e==null||e==="")return null;const n=e instanceof Date?e:new Date(e);if(Number.isNaN(n.getTime()))return null;const t=(a,l=2)=>String(a).padStart(l,"0"),o=["January","February","March","April","May","June","July","August","September","October","November","December"],i=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],s=()=>(n.getHours()+11)%12+1,c={YYYY:()=>n.getFullYear(),YY:()=>t(n.getFullYear()%100),MMMM:()=>o[n.getMonth()],MMM:()=>o[n.getMonth()].slice(0,3),MM:()=>t(n.getMonth()+1),M:()=>n.getMonth()+1,DD:()=>t(n.getDate()),D:()=>n.getDate(),dddd:()=>i[n.getDay()],ddd:()=>i[n.getDay()].slice(0,3),HH:()=>t(n.getHours()),H:()=>n.getHours(),hh:()=>t(s()),h:()=>s(),mm:()=>t(n.getMinutes()),m:()=>n.getMinutes(),ss:()=>t(n.getSeconds()),s:()=>n.getSeconds(),A:()=>n.getHours()<12?"AM":"PM",a:()=>n.getHours()<12?"am":"pm"};return String(r).replace(/YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|A|a/g,a=>String(c[a]()))}const S={icon:"box","facebook-icon":"facebook","instagram-icon":"instagram"};export const SAMPLE_IMAGE="data:image/svg+xml,"+encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="800" height="500" viewBox="0 0 800 500"><rect width="800" height="500" fill="#94a3b8"/><path d="M0 500 L260 230 L420 390 L540 290 L800 500 Z" fill="#64748b"/><circle cx="610" cy="140" r="52" fill="#cbd5e1"/></svg>');const v=/(image|img|photo|picture|avatar|logo|thumb|thumbnail|cover|banner)(url|src)?$/;export function sampleValue(e,r=new Date,{raw:n=!1}={}){const t=String(e).toLowerCase();return S[t]?S[t]:/(class|style)$/.test(t)?"":t==="_id"?"a1b2c3d4-e5f6-7890-abcd-ef1234567890":t==="_createdat"||t==="_updatedat"?r.toISOString():t.replace(/update(?!d)/g,"").includes("date")||/(published|created|updated)at$/.test(t)?M(r):v.test(t)?n?t.includes("avatar")?`<img src="${SAMPLE_IMAGE}" alt="" width="40" height="40">`:`<img src="${SAMPLE_IMAGE}" alt="">`:SAMPLE_IMAGE:/(url|href|link)$/.test(t)?"#":t.includes("email")?"user@example.com":t.includes("phone")?"+44 7700 900000":t.includes("name")?"Jane Smith":t.includes("title")?"Sample Title":["message","content","description","summary","excerpt"].some(o=>t.includes(o))?"This is a sample value for preview purposes.":t.includes("rating")?"excellent":t.includes("status")?"active":t.includes("priority")?"high":t.includes("tag")?"tag1, tag2":t.includes("subject")?"Sample Subject":t.includes("categor")?n?'<a href="#">General</a>':"general":`Sample ${e}`}export const scopeName=e=>String(e||"").trim()||"preview",PREVIEW_REPEATS=[1,3];export function renderPreview(e,r={},{repeat:n=1,sanitize:t=null}={}){if(typeof t!="function")return"";const o=u=>r[u]??`[${u}]`,i=String(e?.content??"").replace(/<!--[\s\S]*?-->/g,"").replace(/\{\{\{(\w+)\}\}\}/g,(u,g)=>String(o(g))).replace(/\{\{([\w_]+)(?::([^{}]+))?\}\}/g,(u,g,p)=>{const f=o(g);if(p){const h=formatDate(f,p);if(h!==null)return d(h)}return d(f)}),s=d(scopeName(e?.name)),c=String(e?.css??""),a=c.trim()?`<style>[data-block="${s}"] { ${c.replace(/<\/style/gi,"<\\/style")} }</style>`:"",l=PREVIEW_REPEATS.includes(Number(n))?Number(n):1,m=String(t(i)??"");if(l===1)return`${a}<div class="dm-static-block" data-block="${s}">${m}</div>`;const b=`<div data-block="${s}">${m}</div>`;return`${a}<div class="dm-collection-display dm-collection-blocks grid grid-cols-1 grid-cols-md-2 grid-cols-lg-3 gap-4">`+Array.from({length:l},()=>b).join("")+"</div>"}export function previewBridge(e,r){const n=JSON.stringify(Number(e)||0),t=JSON.stringify(String(r)).replace(/</g,"\\u003c");return`<script>(function () {
|
|
2
|
+
var P = window.parent, TICK = ${n}, HOST = ${t};
|
|
3
|
+
function host() { return document.getElementById(HOST); }
|
|
4
|
+
function up(msg) { msg.source = 'dm-block-preview'; msg.tick = TICK; P.postMessage(msg, '*'); }
|
|
5
|
+
function hideFloating() {
|
|
6
|
+
var h = host();
|
|
7
|
+
document.querySelectorAll('body *').forEach(function (el) {
|
|
8
|
+
if (!h || el.contains(h) || h.contains(el)) return;
|
|
9
|
+
var pos = getComputedStyle(el).position;
|
|
10
|
+
if (pos === 'fixed' || pos === 'sticky') el.style.setProperty('display', 'none', 'important');
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
window.addEventListener('message', function (e) {
|
|
14
|
+
if (e.source !== P) return;
|
|
15
|
+
var d = e.data || {};
|
|
16
|
+
if (d.source !== 'dm-block-editor' || d.tick !== TICK || d.type !== 'paint') return;
|
|
17
|
+
var el = host();
|
|
18
|
+
if (!el) return;
|
|
19
|
+
el.innerHTML = String(d.html || '');
|
|
20
|
+
try { if (window.I && I.scan) I.scan(el); } catch (err) { /* icons are a nicety */ }
|
|
21
|
+
});
|
|
22
|
+
document.addEventListener('click', function (e) {
|
|
23
|
+
if (e.target.closest && e.target.closest('a[href], button, [type="submit"]')) e.preventDefault();
|
|
24
|
+
}, true);
|
|
25
|
+
document.addEventListener('submit', function (e) { e.preventDefault(); }, true);
|
|
26
|
+
document.addEventListener('keydown', function (e) {
|
|
27
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === 's' || e.key === 'S')) { e.preventDefault(); up({type: 'save'}); }
|
|
28
|
+
}, true);
|
|
29
|
+
function ready() {
|
|
30
|
+
hideFloating();
|
|
31
|
+
new MutationObserver(hideFloating).observe(document.body, {childList: true});
|
|
32
|
+
setTimeout(hideFloating, 800);
|
|
33
|
+
up({type: 'ready', host: !!host()});
|
|
34
|
+
}
|
|
35
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ready); else ready();
|
|
36
|
+
})();<\/script>`}export function previewShell(e,r,n){const t=`<style>body.dm-cloaked{opacity:1 !important;transition:none !important}#${n}{padding:1.5rem 0}.dm-breadcrumbs{display:none !important}body.dm-vnav-left,body.dm-vnav-right{padding-left:0 !important;padding-right:0 !important}</style>`;let o=String(e??"");o=/<\/head>/i.test(o)?o.replace(/<\/head>/i,`${t}</head>`):t+o;const i=previewBridge(r,n),s=o.toLowerCase().lastIndexOf("</body>");return s>=0?o.slice(0,s)+i+o.slice(s):o+i}export function previewMessage(e,r,n){if(!e||!r||e.source!==r)return null;const t=e.data;return!t||t.source!=="dm-block-preview"||t.tick!==n?null:["ready","save"].includes(t.type)?t:null}const w=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);export function formatHtml(e){const r=String(e??"").match(/(<[^>]+>|[^<]+)/g)||[],n=[];let t=0;for(const o of r){const i=o.trim();if(i)if(i.startsWith("</"))t=Math.max(0,t-1),n.push(" ".repeat(t)+i);else if(i.startsWith("<")&&!i.startsWith("<!")&&!i.endsWith("/>")){n.push(" ".repeat(t)+i);const s=(i.match(/^<(\w+)/)||[])[1]||"";w.has(s.toLowerCase())||t++}else n.push(" ".repeat(t)+i)}return n.join(`
|
|
37
|
+
`)}export const draftKey=e=>`block-editor.draft.${e||"new"}`;export function draftOffer(e,r){return!e||typeof e!="object"||!e.form||snapshot(e.form)===r?null:{form:e.form,stale:!!e.base&&e.base!==r,at:e.at||""}}export const shortcodeFor=e=>`[collection slug="\u2026" display="block" block="${e}" /]`;
|
|
@@ -84,7 +84,32 @@
|
|
|
84
84
|
<p class="bex-note" data-bind-hidden="!noSamples.value" data-bind-text="noSamplesText"></p>
|
|
85
85
|
<div class="bex-samples" data-each="sampleRows.value key=key"></div>
|
|
86
86
|
</div>
|
|
87
|
-
<div
|
|
87
|
+
<div class="bex-output">
|
|
88
|
+
<div class="bex-output-bar">
|
|
89
|
+
<div class="live-preview-widths" role="group" aria-label="Preview width">
|
|
90
|
+
<button type="button" class="live-preview-width" title="Full width" aria-label="Full width" data-on-click="setWidth('full')"
|
|
91
|
+
data-bind-class="pvWidth.value === 'full' && 'active'"><span data-icon="monitor" data-icon-size="14"></span></button>
|
|
92
|
+
<button type="button" class="live-preview-width" title="Tablet (1024px)" aria-label="Tablet width" data-on-click="setWidth('1024')"
|
|
93
|
+
data-bind-class="pvWidth.value === '1024' && 'active'"><span data-icon="tablet" data-icon-size="14"></span></button>
|
|
94
|
+
<button type="button" class="live-preview-width" title="Mobile (390px)" aria-label="Mobile width" data-on-click="setWidth('390')"
|
|
95
|
+
data-bind-class="pvWidth.value === '390' && 'active'"><span data-icon="smartphone" data-icon-size="14"></span></button>
|
|
96
|
+
</div>
|
|
97
|
+
<select class="form-select form-select-sm bex-pv-select" aria-label="How many copies" data-model="pvRepeat.value">
|
|
98
|
+
<option value="1">One</option>
|
|
99
|
+
<option value="3">Three in a grid</option>
|
|
100
|
+
</select>
|
|
101
|
+
<select id="block-preview-theme" class="form-select form-select-sm bex-pv-select" aria-label="Site theme for the preview" data-model="pvTheme.value">
|
|
102
|
+
<option value="">Site theme</option>
|
|
103
|
+
</select>
|
|
104
|
+
<small class="bex-pv-status" data-bind-text="pvStatus.value"></small>
|
|
105
|
+
<span data-help-title="Preview" data-help="The block drawn inside your site's own page - its theme, fonts, site styles and Custom CSS - exactly as a page would show it, from what is in the editor now. Try it at tablet or mobile width, as three entries in a collection grid, or in another theme. Links in the preview do not navigate."></span>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="bex-stage">
|
|
108
|
+
<iframe id="block-preview-frame" class="page-live-preview-frame" src="about:blank" title="Block preview"
|
|
109
|
+
data-bind-data-width="pvWidth.value === 'full' ? null : pvWidth.value"
|
|
110
|
+
sandbox="allow-same-origin allow-scripts"></iframe>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
88
113
|
</div>
|
|
89
114
|
</div>
|
|
90
115
|
</div>
|
|
@@ -184,7 +209,7 @@
|
|
|
184
209
|
.bex-seg button + button { border-left: 1px solid var(--dm-border); }
|
|
185
210
|
.bex-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
|
|
186
211
|
.bex-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
|
|
187
|
-
.bex-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0; height: min(
|
|
212
|
+
.bex-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0; height: min(82vh, 980px); min-height: 520px;
|
|
188
213
|
border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
189
214
|
.bex-body.is-write { grid-template-columns: minmax(0, 1fr); }
|
|
190
215
|
.bex-body.is-write .bex-preview { display: none; }
|
|
@@ -194,7 +219,7 @@
|
|
|
194
219
|
.bex-write { border-right: 1px solid var(--dm-border); }
|
|
195
220
|
.bex-body.is-write .bex-write { border-right: 0; }
|
|
196
221
|
.bex-preview { display: flex; flex-direction: column; }
|
|
197
|
-
.bex-samples-wrap { flex-shrink: 0; max-height:
|
|
222
|
+
.bex-samples-wrap { flex-shrink: 0; max-height: 30%; overflow-y: auto; padding: .6rem .75rem; border-bottom: 1px solid var(--dm-border);
|
|
198
223
|
background: var(--dm-surface-raised, color-mix(in srgb, var(--dm-text) 4%, transparent)); display: flex; flex-direction: column; gap: .4rem; }
|
|
199
224
|
.bex-samples-title { display: flex; align-items: center; gap: .3rem; font-size: var(--dm-text-sm); font-weight: 600; }
|
|
200
225
|
.bex-samples { display: flex; flex-direction: column; gap: .3rem; }
|
|
@@ -205,7 +230,12 @@
|
|
|
205
230
|
.bex-sample input { flex: 1; min-width: 0; }
|
|
206
231
|
.bex-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
|
|
207
232
|
.bex-kebab:hover { color: var(--dm-primary); }
|
|
208
|
-
.bex-output { flex: 1;
|
|
233
|
+
.bex-output { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
|
234
|
+
.bex-output-bar { flex: none; display: flex; align-items: center; flex-wrap: wrap; gap: .5rem; padding: .4rem .75rem; border-bottom: 1px solid var(--dm-border); }
|
|
235
|
+
.bex-pv-select { width: auto; }
|
|
236
|
+
.bex-pv-status { flex: 1 1 auto; color: var(--dm-text-muted); font-size: var(--dm-text-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
237
|
+
.bex-stage { flex: 1; min-height: 0; display: flex; justify-content: center; overflow: auto; background: var(--dm-background-alt, var(--dm-surface)); }
|
|
238
|
+
.bex-stage .page-live-preview-frame { border-radius: 0; }
|
|
209
239
|
.bex-css { height: 260px; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
210
240
|
.bex-ref summary { cursor: pointer; list-style: revert; }
|
|
211
241
|
.bex-ref-body { font-size: var(--dm-text-sm); line-height: 1.7; }
|
|
@@ -214,6 +244,6 @@
|
|
|
214
244
|
.bex-grid { grid-template-columns: 1fr; }
|
|
215
245
|
.bex-body, .bex-body.is-split { grid-template-columns: minmax(0, 1fr); height: auto; }
|
|
216
246
|
.bex-body .bex-write { height: 55vh; border-right: 0; border-bottom: 1px solid var(--dm-border); }
|
|
217
|
-
.bex-body .bex-preview { min-height:
|
|
247
|
+
.bex-body .bex-preview { min-height: 60vh; }
|
|
218
248
|
}
|
|
219
249
|
</style>
|
|
@@ -759,6 +759,26 @@ Body content in **Markdown** is supported.
|
|
|
759
759
|
</tbody>
|
|
760
760
|
</table>
|
|
761
761
|
|
|
762
|
+
<h3>Designing a block</h3>
|
|
763
|
+
<p>Blocks live in <strong>Data > Blocks</strong>. A block is HTML with
|
|
764
|
+
<code>{{field}}</code> placeholders (HTML-escaped),
|
|
765
|
+
<code>{{{field}}}</code> for ready-made HTML, and
|
|
766
|
+
<code>{{date:DD MMM YYYY}}</code> for dates. Its Custom CSS is scoped to the
|
|
767
|
+
block; site-wide rules can live in <strong>Site Settings > Custom CSS</strong> instead.</p>
|
|
768
|
+
<p>The editor's preview draws the block <strong>inside your site's own page</strong> - its theme,
|
|
769
|
+
fonts, site styles and Custom CSS - filled from the sample values, and redraws it as you type.
|
|
770
|
+
Above the preview you can:</p>
|
|
771
|
+
<ul>
|
|
772
|
+
<li>switch between full, tablet (1024px) and mobile (390px) width;</li>
|
|
773
|
+
<li>show <strong>One</strong> copy, as <code>[block]</code> places it, or <strong>Three in a grid</strong>,
|
|
774
|
+
as <code>[collection display="block" cols="3"]</code> lists entries - use this for cards
|
|
775
|
+
and for rules such as <code>:nth-child(even)</code>;</li>
|
|
776
|
+
<li>try the block in another site theme (the site itself is not changed).</li>
|
|
777
|
+
</ul>
|
|
778
|
+
<p>Image fields get a grey picture and link fields <code>#</code> as samples; links in the preview do
|
|
779
|
+
not navigate, and anything a block's HTML could run (an <code>onerror</code> handler, a <code>javascript:</code> link, a script) is removed first, as the site removes it. Ctrl+S saves even while the preview has focus. The site's floating buttons (search, theme switcher) are hidden there so they do
|
|
780
|
+
not cover the block. Unsaved Site Settings CSS is not shown - save it first.</p>
|
|
781
|
+
|
|
762
782
|
<h3>Collections on a page</h3>
|
|
763
783
|
<pre class="code-block"><code>[collection slug="team" display="cards" columns="3" title-field="name" /]
|
|
764
784
|
[collection slug="jobs" display="cards" searchable filterable="location,type" sortable page-size="9" where_status="open" /]
|
|
@@ -51,9 +51,13 @@
|
|
|
51
51
|
.usx-row:last-child { border-bottom: 0; }
|
|
52
52
|
.usx-row:hover, .usx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
|
|
53
53
|
.usx-row.is-locked { cursor: default; }
|
|
54
|
-
.
|
|
54
|
+
/* The row's avatar is Domma's (.avatar, colour from the name); the plain circle is the look on a core without it. */
|
|
55
|
+
.usx-avatar:not(.avatar) { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; flex-shrink: 0; border-radius: 50%;
|
|
55
56
|
font-size: var(--dm-text-xs); font-weight: 700; color: var(--dm-primary); background: color-mix(in srgb, var(--dm-primary) 14%, transparent); }
|
|
56
|
-
.usx-
|
|
57
|
+
.usx-avatar:not(.avatar) > .avatar-img { display: none; }
|
|
58
|
+
.usx-row.is-off .usx-avatar:not(.avatar) { color: var(--dm-text-muted); background: color-mix(in srgb, var(--dm-text-muted) 14%, transparent); }
|
|
59
|
+
/* An inactive user cannot sign in: their avatar fades with the rest of the row. */
|
|
60
|
+
.usx-row.is-off .usx-avatar.avatar { opacity: .5; filter: grayscale(1); }
|
|
57
61
|
.usx-row.is-off .usx-main strong { color: var(--dm-text-muted); }
|
|
58
62
|
.usx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
59
63
|
.usx-main strong, .usx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as $}from"../api.js";import{addBannerAction as ue}from"../lib/plugin-chrome.js";import{helpPopovers as at}from"../lib/help-popover.js";import{openMenuAt as Fe,sidebarCount as nt}from"../lib/tool-kit.js";import{createSimpleEditor as Ie}from"../lib/simple-editor.js";import{checkForm as st,draftKey as j,draftOffer as rt,escapeHtml as Ne,extractPlaceholders as ct,formFromBlock as lt,formatHtml as it,payloadFromForm as ut,previewMessage as dt,previewShell as pt,rawPlaceholders as We,renderPreview as mt,sampleValue as _e,scopeName as vt,shortcodeFor as ft,snapshot as de}from"../lib/block-editor-arrange.js";import{fmtDateTime as He}from"../lib/dates.js";const ht=/^\/blocks\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,J=u=>String(u||"").split("?")[0],pe="block-editor.mode",bt=["write","split","preview"],Be="block-editor.preview",Ve=["full","1024","390"],yt="/__block-preview",me="dm-block-preview-root",Ke={title:"Block preview",layout:"blank",breadcrumbs:!1},kt={ADD_ATTR:["target","loading"],ALLOW_DATA_ATTR:!0,FORCE_BODY:!0},wt=()=>window.DOMPurify?.sanitize?u=>window.DOMPurify.sanitize(u,kt):null,gt=`
|
|
2
2
|
<div class="bex-sample" data-key="{{key}}" tabindex="0">
|
|
3
3
|
<label class="bex-sample-label" for="bex-sample-{{key}}">{{label}}</label>
|
|
4
4
|
<input id="bex-sample-{{key}}" type="text" class="form-input form-input--sm" data-bind-value="value" data-sample="{{key}}" placeholder="Sample {{key}}">
|
|
5
5
|
<button type="button" class="bex-kebab" aria-label="Actions for {{label}}" title="Actions">\u22EE</button>
|
|
6
|
-
</div>`;let
|
|
7
|
-
`).length} lines`),
|
|
6
|
+
</div>`;let f=null,Ue=!1;function St(){Ue||!window.R?.use||(Ue=!0,R.use((u,p,s)=>{if(!(ht.test(p?.path||"")&&J(u?.path)!==J(p?.path))||!f?.dirty())return s();const c=f;return E.confirm(`You have unsaved changes to ${Ne(c.label())}. Leave anyway? They are kept as a draft for your next visit.`).then(B=>{B?s():history.replaceState(null,"",c.hash)})}),M.subscribe("router:afterChange",({to:u}={})=>{if(f&&J(u?.path)!==f.path){const p=f;f=null,p.dispose()}}))}function Et(u,p){const s=u.querySelector(".dm-plugin-banner-name"),g=s?.lastChild;g&&g.nodeType===3&&s.querySelector(".dm-plugin-banner-sep")&&(g.textContent=p)}function G(u){return navigator.clipboard?.writeText(u).then(()=>!0,()=>!1)??Promise.resolve(!1)}export const blockEditorView={templateUrl:"/admin/js/templates/block-editor.html",async onMount(u){if(St(),f){const e=f;f=null,e.dispose()}const p=u.get(0),s=p.querySelector(".bex")||p,g=[],c=e=>(g.push(e),e);let B=!1;const C=()=>!B&&s.isConnected,ve=location.hash.match(/^#\/blocks\/edit\/([^/?#]+)/),l=ve?decodeURIComponent(ve[1]):null,d=!l,Ye=location.hash,ze=J(location.hash.slice(1)),Je=d&&new URLSearchParams(location.hash.split("?")[1]||"").get("project")||"",fe=E.loader(p,{type:"dots"});let y=null,Q=[],X=[];try{[y,Q,X]=await Promise.all([l?$.blocks.get(l):Promise.resolve(null),$.blocks.list().catch(()=>[]),$.projects.list().catch(()=>[])])}catch(e){fe.destroy(),E.toast(e.message||"Block not found.",{type:"error"}),R.navigate("/blocks");return}if(fe.destroy(),!s.isConnected)return;const he=(Array.isArray(Q)?Q:[]).map(e=>e.name).sort((e,t)=>e.localeCompare(t));let be=y?.meta||{};const v=lt(y?{...y,name:l}:null,{project:Je}),a={};for(const[e,t]of Object.entries(v))a[e]=M.observable(t);const Z=()=>Object.fromEntries(Object.keys(a).map(e=>[e,a[e].peek()])),ye=()=>Object.fromEntries(Object.keys(a).map(e=>[e,a[e].value])),O=M.observable(de(v)),D=M.computed(()=>de(ye())!==O.value),q=M.observable(!1),V=M.observable(!1),K=M.computed(()=>st(ye(),{isNew:d,taken:he}));let ke="split";try{ke=bt.includes(S.get(pe))?S.get(pe):"split"}catch{}const k=M.observable(ke);c(M.effect(()=>{const e=k.value;try{S.set(pe,e)}catch{}}));const we=M.observable("Ln 1, Col 1");let L={};try{L=S.get(Be)||{}}catch{}const ee=M.observable(Ve.includes(L.width)?L.width:"full"),U=M.observable(String(L.repeat)==="3"?"3":"1"),m=M.observable(typeof L.theme=="string"?L.theme:""),x=M.observable("");c(M.effect(()=>{const e={width:ee.value,repeat:U.value,theme:m.value};try{S.set(Be,e)}catch{}}));const h={},P={},Y=M.observable(0),te=M.observable([]),ge=()=>We(a.content.peek());c(M.effect(()=>{const e=ct(a.content.value),t=We(a.content.value);let n=!1;for(const o of e){const w=t.has(o);if(!(o in h)||o in P&&P[o]!==w){const H=o in h;if(h[o]=_e(o,new Date,{raw:w}),P[o]=w,H){n=!0;const i=s.querySelector(`.bex-samples [data-sample="${CSS.escape(o)}"]`);i&&(i.value=h[o])}}}te.value=e.map(o=>({key:o,label:t.has(o)?`{{{${o}}}}`:`{{${o}}}`,value:h[o]})),n&&queueMicrotask(()=>Y.value++)}));const Ge={...a,dirty:D,saving:q,mode:k,cursor:we,sampleRows:te,isNew:d,notNew:!d,pvWidth:ee,pvRepeat:U,pvTheme:m,pvStatus:x,titleText:d?"New block":l,metaText:M.computed(()=>{if(d)return"Not saved yet";const e=[];return(y?.updatedAt||y?.meta?.updatedAt)&&e.push(`changed ${He(y.updatedAt||y.meta.updatedAt)}`),e.push(`${a.content.value.split(`
|
|
7
|
+
`).length} lines`),a.css.value.trim()&&e.push(`${Math.ceil(a.css.value.length/1024)} KB CSS`),e.join(" \xB7 ")}),nameErr:M.computed(()=>(V.value||a.name.value)&&K.value.errors.name||""),cssErr:M.computed(()=>K.value.errors.css||""),scopeHint:M.computed(()=>`[data-block="${vt(a.name.value)}"]`),noSamples:M.computed(()=>te.value.length===0),noSamplesText:"No {{placeholders}} in the template yet.",modeCls:M.computed(()=>`bex-body is-${k.value}`),cannotSave:M.computed(()=>q.value||V.value&&Object.keys(K.value.errors).length>0)},oe=s.querySelector("#block-project");for(const e of Array.isArray(X)?X:[]){const t=document.createElement("option");t.value=e.slug,t.textContent=e.name||e.slug,oe.appendChild(t)}if(v.project&&![...oe.options].some(e=>e.value===v.project)){const e=document.createElement("option");e.value=v.project,e.textContent=`${v.project} (no longer a project)`,oe.appendChild(e)}const F=s.querySelector("#block-quick-switch");for(const e of he){const t=document.createElement("option");t.value=e,t.textContent=e,e===l&&(t.selected=!0),F.appendChild(t)}const b=s.querySelector(".bex-samples");b.innerHTML=gt;let A=null,N=null,r=null;function Se(e){for(const[t,n]of Object.entries(e))a[t]&&(a[t].value=n);A.setValue(String(e.content??"")),N.setValue(String(e.css??""))}const ae=s.querySelector("#block-preview-frame");let T="none",W=0;const Qe="As your site shows it \xB7 not saved",Ee=()=>{if(!C()||k.peek()==="write"||T!=="ready")return;const e={content:a.content.peek(),css:a.css.peek(),name:a.name.peek()},t=wt();if(!t){x.value="The preview needs the HTML cleaner, which did not load - reload the admin.";return}const n=mt(e,h,{repeat:Number(U.peek()),sanitize:t});try{ae.contentWindow?.postMessage({source:"dm-block-editor",type:"paint",tick:W,html:n},"*")}catch{}},Me=e=>{const t=dt(e,ae.contentWindow,W);if(!(!t||!C())){if(t.type==="save"){z();return}if(T=t.host?"ready":"failed",T==="failed"){x.value="Your site's page came back without a place for the block - the preview cannot show it.";return}x.peek()==="Loading your site\u2026"&&(x.value=Qe),Ee()}};window.addEventListener("message",Me),c(()=>window.removeEventListener("message",Me));async function Ce(){const e=++W;T="loading",x.value="Loading your site\u2026";let t;try{const n=m.peek()?{...Ke,theme:m.peek()}:Ke;({html:t}=await $.pages.previewFull(yt,n,`<div id="${me}"></div>`))}catch(n){t=`<!doctype html><html><head><meta charset="utf-8"></head><body style="font:14px system-ui;margin:0"><div id="${me}" style="padding:1rem"></div></body></html>`,x.value=`Without your site's styles - ${n?.message||"the page did not render"}`}!C()||e!==W||(ae.srcdoc=pt(t,e,me))}let ne=0;c(M.effect(()=>{if(a.content.value,a.css.value,a.name.value,Y.value,U.value,k.value!=="write"){if(T==="none"){Ce();return}cancelAnimationFrame(ne),ne=requestAnimationFrame(Ee)}}));let xe=m.peek();c(M.effect(()=>{const e=m.value;e!==xe&&(xe=e,T!=="none"&&Ce())})),c(()=>{cancelAnimationFrame(ne),W++}),$.theme.catalog().then(({themes:e=[],custom:t=[]}={})=>{if(!C())return;const n=s.querySelector("#block-preview-theme"),o=i=>String(i).replace(/-/g," ").replace(/\b\w/g,le=>le.toUpperCase()),w=[...e.filter(i=>!String(i).startsWith("admin-")).map(i=>({id:i,label:o(i)})),...(Array.isArray(t)?t:[]).filter(i=>i?.id).map(i=>({id:i.id,label:i.label||o(i.id)}))],H=w.map(i=>i.id);for(const{id:i,label:le}of w){const ie=document.createElement("option");ie.value=i,ie.textContent=le,n.appendChild(ie)}m.peek()&&!H.includes(m.peek())?m.value="":n.value=m.peek()}).catch(()=>{s.querySelector("#block-preview-theme")?.remove(),C()&&m.peek()&&(m.value="")});const Ae=e=>{const t=e.target?.dataset?.sample;t&&(h[t]=e.target.value,delete P[t],Y.value++)};b.addEventListener("input",Ae),c(()=>b.removeEventListener("input",Ae));function se(e){r.focus(),r.setRangeText(e,r.selectionStart,r.selectionEnd,"end"),r.dispatchEvent(new Event("input",{bubbles:!0}))}async function Le(e){switch(e){case"undo":r.focus(),document.execCommand("undo");break;case"redo":r.focus(),document.execCommand("redo");break;case"cut":r.selectionStart!==r.selectionEnd&&(await G(r.value.slice(r.selectionStart,r.selectionEnd)),se(""));break;case"copy":r.selectionStart!==r.selectionEnd&&await G(r.value.slice(r.selectionStart,r.selectionEnd));break;case"paste":try{se(await navigator.clipboard.readText())}catch{E.toast("Use Ctrl+V to paste.",{type:"info"})}break;case"select-all":r.focus(),r.select();break;case"format":{const t=it(r.value);A.setValue(t),a.content.value=t;break}default:break}}async function z(){if(q.peek())return;V.value=!0;const{errors:e,name:t}=K.peek();if(Object.keys(e).length){E.toast(e.name||e.css,{type:"warning"});return}const n=d?t:l;q.value=!0;try{const o=ut(Z(),be);if(await $.blocks.put(n,o),!C())return;be=o.meta,O.value=de(Z());try{S.remove?S.remove(j(l)):S.set(j(l),null)}catch{}nt("#/blocks"),E.toast(d?`Created "${n}".`:"Block saved.",{type:"success"}),d&&(location.hash=`#/blocks/edit/${encodeURIComponent(n)}`)}catch(o){E.toast(`Not saved: ${o.message||o}`,{type:"error"})}finally{q.value=!1}}async function Xe(){D.peek()&&await E.confirm("Discard the changes you have not saved?")&&(V.value=!1,Se(Ze(O.peek())))}function Ze(e){try{const[t,n,o,w,H]=JSON.parse(e);return{name:t,project:n,bundled:o,content:w,css:H}}catch{return v}}const et=M.applyBindings(Ge,s,{methods:{save:()=>z(),discard:()=>Xe(),setWrite:()=>{k.value="write"},setSplit:()=>{k.value="split"},setPreview:()=>{k.value="preview"},setWidth:e=>{Ve.includes(String(e))&&(ee.value=String(e))},act:e=>Le(e)}});c(()=>et.dispose()),c(at(s,{position:"bottom"})),A=Ie(s.querySelector(".bex-write"),{initialValue:v.content,language:"html",autoIndent:!0,onChange:e=>{a.content.value=e},onCursor:({line:e,col:t})=>{we.value=`Ln ${e}, Col ${t}`}}),N=Ie(s.querySelector(".bex-css"),{initialValue:v.css,language:"css",autoIndent:!0,onChange:e=>{a.css.value=e}}),A.textarea.setAttribute("aria-label","Template HTML"),A.textarea.placeholder=`<div class="card mb-3">
|
|
8
8
|
<div class="card-body">
|
|
9
9
|
<h3>{{title}}</h3>
|
|
10
10
|
<p>{{message}}</p>
|
|
11
11
|
</div>
|
|
12
|
-
</div>`,
|
|
12
|
+
</div>`,N.textarea.setAttribute("aria-label","Custom CSS"),N.textarea.placeholder=`/* Scoped to this block - e.g.
|
|
13
13
|
color: var(--dm-primary);
|
|
14
|
-
h3 { font-size: 1.25rem; } */`,
|
|
14
|
+
h3 { font-size: 1.25rem; } */`,c(()=>{A.destroy(),N.destroy()}),r=A.textarea;const Te=s.querySelector(".bex-toolbar"),Re=e=>{const t=e.target.closest("[data-action]");t&&Le(t.dataset.action)};Te.addEventListener("click",Re),c(()=>Te.removeEventListener("click",Re));const $e=()=>{const e=F.value;e&&e!==l?location.hash=`#/blocks/edit/${encodeURIComponent(e)}`:F.value=l||""};F.addEventListener("change",$e),c(()=>F.removeEventListener("change",$e));const re=e=>ge().has(e)?`{{{${e}}}}`:`{{${e}}}`,tt=e=>[{header:re(e)},{label:"Insert into the template",icon:"corner-down-right",action:()=>se(re(e))},{label:"Copy placeholder",icon:"copy",action:async()=>{const t=re(e);E.toast(await G(t)?"Copied.":t,{type:"info"})}},{label:"Reset sample value",icon:"rotate-ccw",action:()=>{const t=ge().has(e);h[e]=_e(e,new Date,{raw:t}),P[e]=t;const n=b.querySelector(`[data-sample="${CSS.escape(e)}"]`);n&&(n.value=h[e]),Y.value++}}],ot=E.contextMenu(b,{cursor:"glyph",match:".bex-sample",inherit:!1,density:"compact",items:e=>tt(e.dataset.key)});c(()=>ot?.destroy?.());const je=e=>{const t=e.target.closest(".bex-kebab");t&&Fe(t)},Oe=e=>{const t=e.target.closest(".bex-sample");t&&e.key==="F10"&&e.shiftKey&&(e.preventDefault(),Fe(t))};b.addEventListener("click",je),b.addEventListener("keydown",Oe),c(()=>{b.removeEventListener("click",je),b.removeEventListener("keydown",Oe)}),Et(p,d?"New block":l),ue(u,{id:"block-editor-list",icon:"arrow-left",title:"All blocks",onClick:()=>{location.hash="#/blocks"}}),d?p.querySelector('[data-banner-action="block-editor-shortcode"]')?.remove():ue(u,{id:"block-editor-shortcode",icon:"copy",title:"Copy shortcode",onClick:async()=>{const e=ft(l);E.toast(await G(e)?"Shortcode copied - set the collection slug.":e,{type:"info",duration:5e3})}}),ue(u,{id:"block-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>z()});const De=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),z())};document.addEventListener("keydown",De,!0),c(()=>document.removeEventListener("keydown",De,!0));const qe=()=>{if(D.peek())try{S.set(j(l),{form:Z(),base:O.peek(),at:new Date().toISOString()})}catch{}},Pe=e=>{D.peek()&&(qe(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Pe),c(()=>window.removeEventListener("beforeunload",Pe)),f={hash:Ye,path:ze,dirty:()=>D.peek(),label:()=>d?"the new block":`"${l}"`,dispose(){B=!0,qe(),g.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(s);let ce=null;try{ce=S.get(j(l))}catch{}const _=rt(ce,O.peek());if(ce)try{S.remove?S.remove(j(l)):S.set(j(l),null)}catch{}if(_){const e=_.at?` (${He(_.at)})`:"",t=d?"a new block":`"${Ne(l)}"`,n=_.stale?`You left unsaved changes to ${t}${e}, but the block has been saved since. Restore your version anyway?`:`You left unsaved changes to ${t}${e}. Restore them?`;if(await E.confirm(n)&&C()){const o={...v,..._.form};d||(o.name=l),Se(o)}}}};
|
package/admin/js/views/users.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{api as B,apiRequest as O,getUser as
|
|
1
|
+
import{api as B,apiRequest as O,getUser as se}from"../api.js";import{addBannerAction as ne}from"../lib/plugin-chrome.js";import{SHAPES as oe,whileLoading as ie}from"../lib/loading.js";import{helpPopovers as re}from"../lib/help-popover.js";import{openMenuAt as F,settingsStore as le,sidebarCount as f,viewLifetime as ce}from"../lib/tool-kit.js";import{resetPassword as de,withdrawReset as ue}from"../lib/password-reset-actions.js";import{filterByProject as pe}from"../lib/project-context.js";import{FILTERS as me,SORTS as he,arrangeUsers as ve,canManage as W,resetAllowed as fe,roleIndex as be,userRow as ye}from"../lib/users-arrange.js";import{fmtDateTime as we}from"../lib/dates.js";import{avatarUrl as ge}from"../lib/avatar.js";const H=/^#\/(projects\/[^/?#]+\/)?users\/?(\?.*)?$/,b="#/users",ke=`
|
|
2
2
|
<div class="usx-row" data-id="{{id}}" tabindex="0" data-bind-class="rowCls">
|
|
3
|
-
<span class="usx-avatar" aria-hidden="true">{{initials}}</span>
|
|
3
|
+
<span class="usx-avatar {{avatarCls}}" aria-hidden="true"><img class="avatar-img" alt="" data-bind-src="avatarSrc" data-bind-hidden="!avatarSrc"><span class="avatar-initials" data-bind-hidden="avatarSrc">{{initials}}</span></span>
|
|
4
4
|
<span class="usx-main"><strong>{{name}}</strong><small>{{meta}}</small></span>
|
|
5
5
|
<span class="usx-chips">
|
|
6
6
|
<span class="usx-chip is-ok" data-bind-hidden="!self">You</span>
|
|
@@ -11,4 +11,4 @@ import{api as B,apiRequest as O,getUser as oe}from"../api.js";import{addBannerAc
|
|
|
11
11
|
<span class="usx-chip is-outline" data-bind-hidden="!resetOpen" title="A password reset link was sent and has not been used yet">Reset link open</span>
|
|
12
12
|
</span>
|
|
13
13
|
<button type="button" class="usx-kebab" aria-label="Actions for {{name}}" title="Actions">\u22EE</button>
|
|
14
|
-
</div
|
|
14
|
+
</div>`;function Ee(s){const o=typeof E.avatar?.tone=="function"?E.avatar.tone(s.name||s.email||""):null;return{avatarCls:o==null?"":`avatar avatar-sm avatar-tone-${o}`,avatarSrc:o==null?"":ge(s)}}const K=s=>String(s??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""),Y=le("users.list",s=>({filter:me.includes(s?.filter)?s.filter:"all",sort:he.includes(s?.sort)?s.sort:"name"})),r=ce(H);function xe(){const s=location.hash.match(/^#\/projects\/([^/?#]+)\/users/);if(!s)return null;try{return decodeURIComponent(s[1])}catch{return s[1]}}export const usersView={templateUrl:"/admin/js/templates/users.html",async onMount(s){r.reset();const o=s.get(0).querySelector(".usx")||s.get(0),m=()=>H.test(location.hash)&&o.isConnected,v=se(),l=xe(),x=Y.read(),c=M.observable([]),d=M.observable({}),R=M.observable(!0),y=M.observable(""),u=M.observable(x.filter),w=M.observable(x.sort),g=M.observable(""),$=M.observable(""),k=M.observable(0);r.add(M.effect(()=>{Y.write({filter:u.value,sort:w.value})}));const S=M.observable([]);r.add(M.effect(()=>{const e=d.value,t=a=>{try{return D(a).fromNow?D(a).fromNow():we(a)}catch{return a}};S.value=ve(c.value,{filter:u.value,role:g.value,query:$.value,sort:w.value}).map(a=>({...ye(a,{roles:e,me:v,when:t}),...Ee(a)}))}));const _=M.computed(()=>{const e=c.value,t=e.filter(a=>a.isActive===!1).length;return{all:e.length,active:e.length-t,inactive:t}}),G=M.computed(()=>c.value.length?"Nobody matches.":"No users yet - New user in the banner adds one."),J=M.computed(()=>l?`Showing only users in the project "${l}"${k.value?` - ${k.value} more elsewhere`:""}.`:""),A=e=>c.peek().find(t=>t.id===e);async function h(){try{const[e,t]=await ie(o.querySelector(".dm-loading-slot"),Promise.all([B.users.list(),O("/collections/roles/entries?limit=100",{method:"GET"}).catch(()=>({entries:[]}))]),oe.iconRows);if(!m())return;let a=Array.isArray(e)?e:[];if(l){const n=pe(a,l);k.value=a.length-n.length,a=n}d.value=be(t?.entries||[]),V(),c.value=a,y.value=""}catch(e){y.value=`Could not load the users: ${e.message||e}`}finally{R.value=!1}}function V(){const e=o.querySelector('.usx-select[aria-label="Role"]');if(!e)return;const t=g.peek();e.querySelectorAll('option:not([value=""])').forEach(a=>a.remove()),Object.entries(d.peek()).sort((a,n)=>a[1].level-n[1].level).forEach(([a,n])=>{const i=document.createElement("option");i.value=a,i.textContent=n.label,e.appendChild(i)}),e.value=t}const C=e=>{location.hash=`#/users/edit/${encodeURIComponent(e)}`};async function T(e,t){if(!(!t&&!await E.confirm(`Make ${K(e.name||e.email)} inactive? They will not be able to sign in until the account is made active again. Their work stays.`)))try{await O(`/users/${encodeURIComponent(e.id)}`,{method:"PUT",body:JSON.stringify({isActive:t})}),E.toast(`${e.name||e.email} is ${t?"active":"inactive"}.`,{type:"success"}),f(b),m()&&await h()}catch(a){E.toast(`Not changed: ${a.message||a}`,{type:"error"})}}async function z(e){if(await E.confirm(`Delete ${K(e.name||e.email)}? They lose access at once and this cannot be undone.`))try{await B.delete(`/users/${encodeURIComponent(e.id)}`),E.toast(`Deleted ${e.name||e.email}.`,{type:"success"}),f(b),m()&&await h()}catch(t){E.toast(`Not deleted: ${t.message||t}`,{type:"error"})}}async function L(e,t){await de(e,t)&&(f(b),m()&&await h())}async function Q(e){await ue(e)&&(f(b),m()&&await h())}async function X(e){try{await navigator.clipboard.writeText(e.email||""),E.toast("Email copied.",{type:"success"})}catch{E.toast(e.email||"No email",{type:"info",duration:6e3})}}function Z(e){const t=A(e);if(!t)return[];const a=W(v,t,d.peek()),n=a.self?"not on your own account":"your role is not senior to theirs",i=fe(v,t,d.peek()),ae=!!t.resetLinkExpires&&Date.parse(t.resetLinkExpires)>Date.now(),P=q=>i.ok?q:`${q.replace(/…$/,"")} - ${i.why}`;return[{header:t.name||t.email},{label:a.self?"Edit my account":a.edit?"Edit":`Edit - ${n}`,icon:"edit",disabled:!a.edit,action:()=>C(t.id)},{label:"Copy email",icon:"copy",disabled:!t.email,action:()=>X(t)},{type:"divider"},{label:P("Send password reset email\u2026"),icon:"mail",disabled:!i.ok,action:()=>L(t,"email")},{label:P("Copy a reset link"),icon:"link",disabled:!i.ok,action:()=>L(t,"link")},...ae?[{label:a.other?"Withdraw the reset link":`Withdraw the reset link - ${n}`,icon:"x-circle",disabled:!a.other,action:()=>Q(t)}]:[],{type:"divider"},t.isActive===!1?{label:a.other?"Make active":`Make active - ${n}`,icon:"play",disabled:!a.other,action:()=>T(t,!0)}:{label:a.other?"Make inactive\u2026":`Make inactive - ${n}`,icon:"pause",disabled:!a.other,action:()=>T(t,!1)},{label:a.other?"Delete\u2026":`Delete - ${n}`,icon:"trash",danger:a.other,disabled:!a.other,action:()=>z(t)}]}const p=o.querySelector(".usx-list");p.innerHTML=ke;const ee=M.applyBindings({users:c,loading:R,loadError:y,filter:u,sort:w,role:g,query:$,rows:S,counts:_,emptyText:G,projectNote:J},o,{methods:{showAll:()=>{u.value="all"},showActive:()=>{u.value="active"},showInactive:()=>{u.value="inactive"}}});r.add(()=>ee.dispose()),r.add(re(o,{position:"bottom"}));const te=E.contextMenu(p,{cursor:"glyph",match:".usx-row",inherit:!1,density:"compact",items:e=>Z(e.dataset.id)});r.add(()=>te?.destroy?.());const j=e=>{const t=A(e);t&&(W(v,t,d.peek()).edit?C(e):E.toast("Your role is not senior to theirs, so you cannot edit this account.",{type:"info"}))},U=e=>{const t=e.target.closest(".usx-row");if(!t)return;const a=e.target.closest(".usx-kebab");if(a){F(a);return}j(t.dataset.id)},N=e=>{const t=e.target.closest(".usx-row");t&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),F(t)):e.key==="Enter"&&e.target===t&&j(t.dataset.id))};p.addEventListener("click",U),p.addEventListener("keydown",N),r.add(()=>{p.removeEventListener("click",U),p.removeEventListener("keydown",N)}),ne(s,{id:"users-new",icon:"plus",title:"New user",onClick:()=>{location.hash=l?`#/users/new?project=${encodeURIComponent(l)}`:"#/users/new"}}),I.scan(o),await h()}};
|