domma-cms 1.8.7 → 1.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
- import{apiRequest as A,getToken as Aa}from"../api.js";import{addBannerAction as Ia}from"../lib/plugin-chrome.js";import{helpPopovers as pt}from"../lib/help-popover.js";import{onceClosed as Na,openMenuAt as H,sidebarCount as N,viewLifetime as Oa}from"../lib/tool-kit.js";import{SHAPES as ct,whileLoading as dt}from"../lib/loading.js";import{fmtDateTime as Da}from"../lib/dates.js";import{applyChoice as Ha,chooseAllPeople as ut,choosePerson as Ua,choiceItems as Ba,cleanOptions as fe,counts as mt,customTypeRows as _a,inviteNote as za,peopleSummary as ja,personMenuItems as Fa,personRow as Wa,reportPersonRow as Ka,defaultOptions as Ne,draftKey as Za,draftQuestion as Va,historyRow as Ja,internalTypeRows as Ya,isDirty as Ga,kindTabs as Xa,liveDrafts as vt,optionsSnapshot as ht,PLAN_FILTERS as wt,planMatches as ft,planRow as Qa,planTiles as bt,productSummary as eo,progressPercent as to,progressText as ao,REPORT_FILTERS as yt,reportMatches as gt,reportRow as oo,stagedRow as so,typeMenuItems as no,withDraft as xt,withListPage as ro,withTypeChoice as lo}from"../lib/wordpress-import-arrange.js";const kt=/^#\/wordpress-import\/?(\?.*)?$/,se="#/wordpress-import",$="/import/wordpress",be=400,io=`
1
+ import{apiRequest as C,getToken as Za}from"../api.js";import{addBannerAction as Va}from"../lib/plugin-chrome.js";import{helpPopovers as yt}from"../lib/help-popover.js";import{onceClosed as Ga,openMenuAt as O,sidebarCount as R,viewLifetime as Ja}from"../lib/tool-kit.js";import{SHAPES as xt,whileLoading as kt}from"../lib/loading.js";import{fmtDateTime as Ya}from"../lib/dates.js";import{applyChoice as Xa,chooseAllPeople as Mt,choosePerson as Qa,choiceItems as Tt,cleanOptions as ye,counts as Et,customTypeRows as es,inviteNote as ts,peopleSummary as as,personMenuItems as ss,personRow as os,reportPersonRow as ns,defaultOptions as He,draftKey as ls,draftQuestion as rs,historyRow as is,internalTypeRows as cs,isDirty as ps,kindTabs as ds,liveDrafts as $t,optionsSnapshot as Lt,PLAN_FILTERS as St,planMatches as Pt,planRow as us,planTiles as Ct,productSummary as ms,progressPercent as hs,progressText as ws,REPORT_FILTERS as Rt,reportMatches as qt,reportRow as vs,stagedRow as fs,typeMenuItems as bs,widgetAreaRows as gs,widgetsFileText as ys,withDraft as At,withFooterChoice as xs,withListPage as ks,withTypeChoice as Ms}from"../lib/wordpress-import-arrange.js";const It=/^#\/wordpress-import\/?(\?.*)?$/,re="#/wordpress-import",$="/import/wordpress",xe=400,Ts=`
2
2
  <div class="wpx-row" data-id="{{id}}" tabindex="0">
3
3
  <span class="wpx-main"><strong>{{title}}</strong><small>{{meta}}</small></span>
4
4
  <span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillClass">{{pill}}</span></span>
5
5
  <button type="button" class="wpx-kebab" aria-label="Actions for {{title}}" title="Actions">\u22EE</button>
6
- </div>`,Mt=`
6
+ </div>`,Nt=`
7
7
  <div class="wpx-row" data-key="{{key}}" tabindex="0">
8
8
  <span class="wpx-main">
9
9
  <strong>{{title}}</strong>
@@ -14,17 +14,17 @@ import{apiRequest as A,getToken as Aa}from"../api.js";import{addBannerAction as
14
14
  </span>
15
15
  <span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillCls">{{pill}}</span></span>
16
16
  <button type="button" class="wpx-kebab" aria-label="Actions for {{title}}" title="Actions">\u22EE</button>
17
- </div>`,Tt=`
17
+ </div>`,Ot=`
18
18
  <div class="wpx-row" data-from="{{from}}" tabindex="0">
19
19
  <span class="wpx-main"><span class="wpx-where">{{from}} \u2192 {{to}}</span><span class="wpx-reason" data-bind-hidden="!reason">{{reason}}</span></span>
20
20
  <span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillClass">{{pill}}</span></span>
21
21
  <button type="button" class="wpx-kebab" aria-label="Actions for {{from}}" title="Actions">\u22EE</button>
22
- </div>`,po=`
22
+ </div>`,Es=`
23
23
  <div class="wpx-row" data-id="{{id}}" tabindex="0">
24
24
  <span class="wpx-main"><strong>{{title}}</strong><small>{{meta}}</small><span class="wpx-reason">{{summary}}</span></span>
25
25
  <span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillClass">{{pill}}</span></span>
26
26
  <button type="button" class="wpx-kebab" aria-label="Actions for {{title}}" title="Actions">\u22EE</button>
27
- </div>`,co=`
27
+ </div>`,$s=`
28
28
  <div class="wpx-row wpx-type-row" data-type="{{type}}" tabindex="0">
29
29
  <span class="wpx-main">
30
30
  <strong>{{label}}</strong>
@@ -42,7 +42,7 @@ import{apiRequest as A,getToken as Aa}from"../api.js";import{addBannerAction as
42
42
  </span>
43
43
  <span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillClass">{{pill}}</span></span>
44
44
  <button type="button" class="wpx-kebab" aria-label="Actions for {{label}}" title="Actions">\u22EE</button>
45
- </div>`,uo=`
45
+ </div>`,Ls=`
46
46
  <div class="wpx-row wpx-person" data-key="{{key}}" tabindex="0">
47
47
  <span class="wpx-main">
48
48
  <strong>{{name}}</strong>
@@ -57,12 +57,26 @@ import{apiRequest as A,getToken as Aa}from"../api.js";import{addBannerAction as
57
57
  </span>
58
58
  <span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillClass">{{pill}}</span></span>
59
59
  <button type="button" class="wpx-kebab" aria-label="Actions for {{name}}" title="Actions">\u22EE</button>
60
- </div>`,mo=`
60
+ </div>`,Ss=`
61
61
  <div class="wpx-row" data-key="{{key}}" tabindex="0">
62
62
  <span class="wpx-main"><strong>{{name}}</strong><small>{{meta}}</small><span class="wpx-reason" data-bind-hidden="!reason">{{reason}}</span></span>
63
63
  <span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillClass">{{pill}}</span></span>
64
64
  <button type="button" class="wpx-kebab" aria-label="Actions for {{name}}" title="Actions">\u22EE</button>
65
- </div>`,Et='<div class="wpx-tile"><strong>{{value}}</strong><small>{{label}} <span data-bind-hidden="!sub">{{sub}}</span></small></div>',St='<p class="wpx-alert"><span>{{text}}</span></p>',$t="<li>{{text}}</li>",vo='<div class="wpx-fact"><dt>{{label}}</dt><dd>{{value}}</dd></div>',Oe='<button type="button" data-on-click="$root.pickSeg($data)" data-bind-class="cls">{{label}} <b>{{count}}</b></button>',y=Oa(kt);function Lt(d,n,p){return new Promise((f,k)=>{const m=new XMLHttpRequest,P=new FormData;P.append("file",n),m.open("POST",`/api${d}`);const w=Aa();w&&m.setRequestHeader("Authorization",`Bearer ${w}`),m.upload.onprogress=u=>{u.lengthComputable&&p(u.loaded/u.total)},m.onload=()=>{let u=null;try{u=JSON.parse(m.responseText)}catch{}m.status>=200&&m.status<300?f(u):k(new Error(u?.error||`The upload failed (${m.status}).`))},m.onerror=()=>k(new Error("The upload failed - the connection dropped.")),m.send(P),y.add(()=>{m.readyState!==4&&m.abort()})})}const ye=d=>d>=1048576?`${Math.round(d/1048576)} MB`:`${Math.max(1,Math.round(d/1024))} KB`,F=d=>String(d??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");function ho(d){const n=document.createElement("template");return n.innerHTML=d.trim(),n.content.firstElementChild}async function ge(d,n){const p=E.confirm(d,n);let f=null;try{f=[...document.querySelectorAll(".dm-dialog-overlay")].at(-1)??null}catch{}const k=y.add(()=>{if(!f)return;const m=f;f=null;try{m.querySelector(".dm-dialog-cancel")?.click(),m.remove()}catch{}});try{return await p}finally{f=null,k()}}function De(d){navigator.clipboard?.writeText(d).then(()=>E.toast("Copied.",{type:"success"}),()=>E.toast(d,{type:"info",duration:8e3}))}function W(d,n,p,f){const k=E.contextMenu(d,{cursor:"glyph",match:n,inherit:!1,density:"compact",items:p});y.add(()=>k?.destroy?.());const m=w=>{const u=w.target.closest(n);if(!u||!d.contains(u))return;const v=w.target.closest(".wpx-kebab");if(v){H(v);return}f?.(u)},P=w=>{const u=w.target.closest(n);u&&(w.key==="F10"&&w.shiftKey?(w.preventDefault(),H(u)):w.key==="Enter"&&w.target===u&&f&&(w.preventDefault(),w.stopPropagation(),f(u)))};d.addEventListener("click",m),d.addEventListener("keydown",P),y.add(()=>{d.removeEventListener("click",m),d.removeEventListener("keydown",P)})}export const wordpressImportView={templateUrl:"/admin/js/templates/wordpress-import.html",async onMount(d){y.reset();const n=d.get(0).querySelector(".wpx")||d.get(0),p=()=>kt.test(location.hash)&&n.isConnected;let f="";try{f=S.get("auth_user")?.id||""}catch{}const k=Za(f),m=()=>{try{return k?S.get(k):null}catch{return null}},P=e=>{try{k&&S.set(k,e)}catch{}},w=n.querySelector(".wpx-loading");let u;try{u=await dt(w,A(`${$}/status`),ct.rows)}catch(e){if(!n.isConnected)return;w&&(w.hidden=!0);const t=n.querySelector(".wpx-failed");t&&(t.textContent=`Could not load the importer: ${e.message||e}. Reload the page to try again.`,t.hidden=!1);return}if(!p())return;w&&(w.hidden=!0),n.querySelector(".wpx-body").hidden=!1;const v=M.observable(u.staged||[]),U=M.observable(u.history||[]),K=M.observable(u.blog||{available:!1}),He=M.observable(u.shop||{available:!1}),Z=u.limits||{},Ue=u.people?.defaultRole||"",g=M.observable(u.running||null),h=M.observable(""),b=M.observable(null),ne=M.observable(!1),xe=M.observable(!1),ke=M.observable(""),l=M.observable(Ne(K.peek().available)),B=M.observable("all"),O=M.observable(""),V=M.observable(""),re=M.observable(""),J=M.observable(""),le=M.observable(""),ie=M.observable(!1);let pe=!0;const wo=()=>v.peek().find(e=>e.id===h.peek())||null,Me=M.computed(()=>v.value.find(e=>e.id===h.value)||null),Ct=M.computed(()=>h.value?"preview":"start"),D=M.computed(()=>!!K.value.available),Pt=M.computed(()=>!!(b.value?.shop||He.value).available),Be=M.computed(()=>!!h.value&&Ga(l.value,D.value)),Y=e=>M.computed({read:()=>l.value[e]!==!1,write:t=>{l.value={...l.peek(),[e]:!!t}}}),L=e=>{l.value={...l.peek(),...e}},G=e=>{try{return Da(e)}catch{return e}},Rt=M.computed(()=>v.value.map(e=>so(e,G))),qt=M.computed(()=>U.value.map(e=>Ja(e,G))),X=M.computed(()=>(b.value?.items||[]).map(e=>({...e,choice:l.value.choices?.[e.key]}))),At=M.computed(()=>Xa(X.value).map(e=>({...e,cls:O.value===e.id?"is-on":"",seg:"kind"}))),It=M.computed(()=>mt(X.value,wt,ft,O.value)),Nt=M.computed(()=>wt.map(e=>({...e,count:It.value[e.id],cls:B.value===e.id?"is-on":"",seg:"filter"}))),Te=M.computed(()=>{const e=V.value.trim().toLowerCase();return X.value.filter(t=>(!O.value||t.kind===O.value)&&ft(t,B.value)&&(!e||`${t.title} ${t.target} ${t.oldUrl} ${t.postType||""}`.toLowerCase().includes(e)))}),Ot=M.computed(()=>Te.value.slice(0,be).map(e=>{const t=Qa(e);return{...t,pillCls:`${t.pillClass}${e.choice?" is-chosen":""}`}})),Dt=M.computed(()=>Te.value.length>be?`Showing the first ${be} of ${Te.value.length} - search or filter to find the rest.`:""),Ht=M.computed(()=>X.value.length?"Nothing matches.":"This export has nothing to import."),Ut=M.computed(()=>bt(b.value)),Bt=M.computed(()=>(b.value?.notes||[]).map(e=>({text:e}))),_t=M.computed(()=>(b.value?.redirects||[]).slice(0,be).map(e=>({...e,pill:e.action==="skip"?"Skipped":e.action==="update"?"Update":"New",pillClass:e.action==="skip"?"is-muted":"is-ok"}))),zt=M.computed(()=>String((b.value?.redirects||[]).filter(e=>e.action!=="skip").length)),jt=M.computed(()=>b.value?.site?.title||Me.value?.siteTitle||"WordPress export"),Ft=M.computed(()=>{const e=Me.value,t=b.value?.site;return[t?.url||e?.siteUrl,e?.fileName,t?.wxrVersion&&`WXR ${t.wxrVersion}`,e?.at&&`uploaded ${G(e.at)}`].filter(Boolean).join(" \xB7 ")}),Q=M.computed(()=>eo(b.value)),Wt=M.computed(()=>Q.value?.note||""),Kt=M.computed(()=>Q.value?.tone==="warn"?"is-warn":"is-info"),Zt=M.computed(()=>(Q.value?.rows||[]).map(e=>({...e,id:e.label}))),Vt=M.computed(()=>(Q.value?.notes||[]).map(e=>({text:e}))),Jt=M.computed(()=>!!Q.value),Ee=M.computed(()=>_a(b.value)),Se=M.computed(()=>Ya(b.value)),Yt=M.computed(()=>!!(Ee.value.length||Se.value.length)),Gt=M.computed(()=>{const e=Se.value.length;return e?`Not imported: ${e} kind${e===1?"":"s"} of WordPress or plugin housekeeping`:""}),T=M.computed(()=>b.value?.people||null),Xt=M.computed(()=>!!T.value?.rows?.length),Qt=M.computed(()=>(T.value?.rows||[]).map(Wa)),ea=M.computed(()=>!!T.value?.canCreate),ta=M.computed(()=>(T.value?.rows||[]).some(e=>e.choosable)),_e=M.computed(()=>ja(T.value)),aa=M.computed(()=>_e.value.text),oa=M.computed(()=>_e.value.tone==="warn"?"is-warn":"is-info"),sa=M.computed(()=>!!(T.value?.canCreate&&T.value?.mailReady&&T.value?.counts?.create)),na=M.computed(()=>za(T.value)),ra=M.computed({read:()=>l.value.invite===!0,write:e=>{l.value={...l.peek(),invite:!!e}}}),la=M.computed(()=>D.value?`Posts go to ${K.value.basePath||"/blog"}.`:K.value.reason||"No Blog is switched on here."),ce=M.computed(()=>Me.value?.uploads||null),ia=M.computed(()=>{const e=ce.value;return e?`${e.fileName} - ${e.files} file${e.files===1?"":"s"}, ${ye(e.size)}${e.unsafe?`. ${e.unsafe} with unsafe names (like ../) were ignored`:""}.`:`Zip the wp-content/uploads folder and upload it here (up to ${ye(Z.uploads||0)}).`}),pa=M.computed(()=>ce.value?"Replace the zip":"Upload the uploads zip"),ze=M.computed(()=>!!b.value&&!g.value&&!ne.value&&!(l.value.media==="zip"&&!ce.value)),ca=M.computed(()=>g.value?`Importing${g.value.siteTitle?` ${g.value.siteTitle}`:""}\u2026`:""),da=M.computed(()=>ao(g.value)),je=M.computed(()=>to(g.value)),ua=M.observable(ye(Z.wxr||0)),Fe=n.querySelector(".wpx-running .wpx-bar-fill");y.add(M.effect(()=>{Fe&&(Fe.style.width=`${je.value}%`)}));async function $e(){const e=await A(`${$}/status`);return p()?(v.value=e.staged||[],U.value=e.history||[],K.value=e.blog||{available:!1},He.value=e.shop||{available:!1},e.running&&!g.peek()&&(g.value=e.running,te()),e):null}let de=0,Le=null;async function ue({first:e=!1}={}){const t=h.peek();if(!t)return;const a=++de;ke.value="";const o=A(`${$}/staged/${t}/preview`,{method:"POST",body:JSON.stringify({options:fe(l.peek(),D.peek())})});try{let i;if(e?(ne.value=!0,i=await dt(n.querySelector(".wpx-plan-loading"),o,ct.table)):(xe.value=!0,i=await o),a!==de||!p()||h.peek()!==t)return;b.value=i.plan}catch(i){if(a!==de||!p())return;if(i.status===404){E.toast("That upload has gone - upload the export again.",{type:"warning"}),ee(),$e().catch(()=>{});return}ke.value=`Could not read the export: ${i.message||i}`}finally{a===de&&(ne.value=!1,xe.value=!1)}}let me="";y.add(M.effect(()=>{const e=`${h.value}|${ht(l.value)}`;if(e===me)return;const t=me.split("|")[0];me=e,!(!h.value||t!==h.value)&&(clearTimeout(Le),Le=setTimeout(()=>ue(),250))})),y.add(()=>clearTimeout(Le)),y.add(M.effect(()=>{const e=h.value,t=l.value,a=Be.value;if(!e||!pe||!k)return;const o=m();P(xt(o,f,e,a?{options:fe(t,!0),at:new Date().toISOString(),was:e}:null))}));const Ce=e=>{k&&P(xt(m(),f,e,null))};async function ve(e){if(!v.peek().some(c=>c.id===e))return;const t=v.peek().find(c=>c.id===e),a=vt(m(),v.peek().map(c=>c.id),f).items[e];if(pe=!a,b.value=null,B.value="all",O.value="",V.value="",l.value=Ne(D.peek()),me=`${e}|${ht(l.peek())}`,h.value=e,await ue({first:!0}),!p()||h.peek()!==e||!a)return;await new Promise(c=>setTimeout(c,0));const o=Va({when:a.at?G(a.at):"",file:t?.fileName||""}),i=await ge(F(o.text),{title:o.title,confirmText:o.confirmText,cancelText:o.cancelText});!p()||h.peek()!==e||(pe=!0,i?Ce(e):l.value=fe(a.options,D.peek()))}function ee(){h.value="",b.value=null,pe=!0}async function We(e=h.peek()){const t=v.peek().find(a=>a.id===e);if(t&&await ge(`Discard the upload of <strong>${F(t.siteTitle||t.fileName)}</strong>? Nothing has been imported from it.`,{title:"Discard upload",confirmText:"Discard",danger:!0}))try{if(await A(`${$}/staged/${e}`,{method:"DELETE"}),Ce(e),!p())return;h.peek()===e&&ee(),v.value=v.peek().filter(a=>a.id!==e),N(se)}catch(a){E.toast(`Not discarded: ${a.message||a}`,{type:"error"})}}async function Ke(e){if(!e)return;if(J.value="",!/\.(xml|zip)$/i.test(e.name)){J.value="Upload the .xml file WordPress exports, or a .zip holding it.";return}if(Z.wxr&&e.size>Z.wxr*1.2&&/\.xml$/i.test(e.name)){J.value=`That file is over ${ye(Z.wxr)}.`;return}const t=n.querySelector(".wpx-start .wpx-upload .wpx-bar-fill");re.value=`Uploading ${e.name}\u2026`;try{const a=await Lt(`${$}/upload`,e,o=>{t&&(t.style.width=`${Math.round(o*100)}%`),re.value=o<1?`Uploading ${e.name} - ${Math.round(o*100)}%`:`Reading ${e.name}\u2026`});if(!p())return;v.value=[a,...v.peek().filter(o=>o.id!==a.id)],N(se),await ve(a.id)}catch(a){p()&&(J.value=a.message||String(a))}finally{p()&&(re.value="",t&&(t.style.width="0"))}}async function ma(e){const t=h.peek();if(!e||!t)return;if(!/\.zip$/i.test(e.name)){E.toast("Zip the wp-content/uploads folder and upload the .zip.",{type:"warning"});return}const a=n.querySelector(".wpx-zip-fill");le.value=`Uploading ${e.name}\u2026`;try{const o=await Lt(`${$}/staged/${t}/uploads`,e,i=>{a&&(a.style.width=`${Math.round(i*100)}%`),le.value=i<1?`Uploading ${e.name} - ${Math.round(i*100)}%`:`Checking ${e.name}\u2026`});if(!p())return;v.value=v.peek().map(i=>i.id===o.id?o:i),h.peek()===t&&ue()}catch(o){p()&&E.toast(o.message||String(o),{type:"error"})}finally{p()&&(le.value="",a&&(a.style.width="0"))}}async function va(){const e=h.peek();try{const t=await A(`${$}/staged/${e}/uploads`,{method:"DELETE"});if(!p())return;v.value=v.peek().map(a=>a.id===t.id?t:a),ue()}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error"})}}async function ha(){const e=h.peek(),t=b.peek();if(!e||!t||!ze.peek())return;const o=bt(t).filter(c=>c.value&&c.id!=="comments").map(c=>`${c.value} ${c.label.toLowerCase()}`);if(!(!await ge(`Import <strong>${F(t.site?.title||"this export")}</strong>: ${F(o.join(", ")||"nothing new")}? Pages edited here since an earlier import are left alone unless you chose Overwrite.`,{title:"Run the import",confirmText:"Run import"})||!p()||h.peek()!==e))try{const c=await A(`${$}/staged/${e}/run`,{method:"POST",body:JSON.stringify({options:fe(l.peek(),D.peek())})});if(Ce(e),!p())return;g.value={...c.job,siteTitle:t.site?.title||""},ee(),N(se),te()}catch(c){E.toast(`The import did not start: ${c.message||c}`,{type:"error"})}}let Pe=null;function te(){clearTimeout(Pe);const e=g.peek();e&&(Pe=setTimeout(async()=>{try{const t=await A(`${$}/jobs/${e.id}`);if(!p())return;if(g.value={...t.job,siteTitle:e.siteTitle},t.job.status==="running")return te();if(g.value=null,await $e().catch(()=>{}),N(se),N("#/pages"),N("#/media"),N("#/plugins/shopping-cart"),N("#/collections"),!p())return;t.job.status==="done"?(E.toast("The import has finished - here is its report.",{type:"success"}),he(t.job.reportId)):(E.toast(`The import stopped: ${t.job.error||"an error"}`,{type:"error"}),t.job.reportId&&he(t.job.reportId))}catch(t){if(!p())return;if(t.status===404){g.value=null,$e().catch(()=>{});return}te()}},700))}y.add(()=>clearTimeout(Pe)),g.peek()&&te();let _=!1;async function he(e){if(_||!e)return;_=!0;let t;try{t=await A(`${$}/history/${e}`)}catch(s){_=!1,E.toast(`Could not open the report: ${s.message||s}`,{type:"error"});return}if(!p()){_=!1;return}let a;try{a=E.slideover({title:F(`Report - ${t.run?.siteTitle||"WordPress import"}`),size:"lg",position:"right"})}catch(s){throw _=!1,s}const o=ho(`
65
+ </div>`,Ps=`
66
+ <div class="wpx-row wpx-widget-row" data-area="{{area}}" tabindex="0">
67
+ <span class="wpx-main">
68
+ <strong>{{label}}</strong>
69
+ <small>{{meta}}</small>
70
+ <span class="wpx-where">{{creates}}</span>
71
+ <span class="wpx-type-ctl" data-bind-hidden="!canPlace">
72
+ <label class="wpx-toggle"><input type="checkbox" class="form-switch" data-footer data-bind-checked="placeOn"> Place in the footer</label>
73
+ </span>
74
+ <span class="wpx-reason" data-bind-hidden="!placeText">{{placeText}}</span>
75
+ <span class="wpx-rebuild" data-bind-hidden="!rebuild">{{rebuild}}</span>
76
+ </span>
77
+ <span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillClass">{{pill}}</span></span>
78
+ <button type="button" class="wpx-kebab" aria-label="Actions for {{label}}" title="Actions">\u22EE</button>
79
+ </div>`,Dt='<div class="wpx-tile"><strong>{{value}}</strong><small>{{label}} <span data-bind-hidden="!sub">{{sub}}</span></small></div>',Ut='<p class="wpx-alert"><span>{{text}}</span></p>',Ht="<li>{{text}}</li>",Cs='<div class="wpx-fact"><dt>{{label}}</dt><dd>{{value}}</dd></div>',Be='<button type="button" data-on-click="$root.pickSeg($data)" data-bind-class="cls">{{label}} <b>{{count}}</b></button>',f=Ja(It);function _e(u,l,i){return new Promise((b,k)=>{const w=new XMLHttpRequest,q=new FormData;q.append("file",l),w.open("POST",`/api${u}`);const v=Za();v&&w.setRequestHeader("Authorization",`Bearer ${v}`),w.upload.onprogress=m=>{m.lengthComputable&&i(m.loaded/m.total)},w.onload=()=>{let m=null;try{m=JSON.parse(w.responseText)}catch{}w.status>=200&&w.status<300?b(m):k(new Error(m?.error||`The upload failed (${w.status}).`))},w.onerror=()=>k(new Error("The upload failed - the connection dropped.")),w.send(q),f.add(()=>{w.readyState!==4&&w.abort()})})}const ke=u=>u>=1048576?`${Math.round(u/1048576)} MB`:`${Math.max(1,Math.round(u/1024))} KB`,V=u=>String(u??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");function Rs(u){const l=document.createElement("template");return l.innerHTML=u.trim(),l.content.firstElementChild}async function Me(u,l){const i=E.confirm(u,l);let b=null;try{b=[...document.querySelectorAll(".dm-dialog-overlay")].at(-1)??null}catch{}const k=f.add(()=>{if(!b)return;const w=b;b=null;try{w.querySelector(".dm-dialog-cancel")?.click(),w.remove()}catch{}});try{return await i}finally{b=null,k()}}function ze(u){navigator.clipboard?.writeText(u).then(()=>E.toast("Copied.",{type:"success"}),()=>E.toast(u,{type:"info",duration:8e3}))}function H(u,l,i,b){const k=E.contextMenu(u,{cursor:"glyph",match:l,inherit:!1,density:"compact",items:i});f.add(()=>k?.destroy?.());const w=v=>{const m=v.target.closest(l);if(!m||!u.contains(m))return;const p=v.target.closest(".wpx-kebab");if(p){O(p);return}b?.(m)},q=v=>{const m=v.target.closest(l);m&&(v.key==="F10"&&v.shiftKey?(v.preventDefault(),O(m)):v.key==="Enter"&&v.target===m&&b&&(v.preventDefault(),v.stopPropagation(),b(m)))};u.addEventListener("click",w),u.addEventListener("keydown",q),f.add(()=>{u.removeEventListener("click",w),u.removeEventListener("keydown",q)})}export const wordpressImportView={templateUrl:"/admin/js/templates/wordpress-import.html",async onMount(u){f.reset();const l=u.get(0).querySelector(".wpx")||u.get(0),i=()=>It.test(location.hash)&&l.isConnected;let b="";try{b=S.get("auth_user")?.id||""}catch{}const k=ls(b),w=()=>{try{return k?S.get(k):null}catch{return null}},q=e=>{try{k&&S.set(k,e)}catch{}},v=l.querySelector(".wpx-loading");let m;try{m=await kt(v,C(`${$}/status`),xt.rows)}catch(e){if(!l.isConnected)return;v&&(v.hidden=!0);const t=l.querySelector(".wpx-failed");t&&(t.textContent=`Could not load the importer: ${e.message||e}. Reload the page to try again.`,t.hidden=!1);return}if(!i())return;v&&(v.hidden=!0),l.querySelector(".wpx-body").hidden=!1;const p=M.observable(m.staged||[]),B=M.observable(m.history||[]),G=M.observable(m.blog||{available:!1}),je=M.observable(m.shop||{available:!1}),_=m.limits||{},We=m.people?.defaultRole||"",y=M.observable(m.running||null),h=M.observable(""),g=M.observable(null),ie=M.observable(!1),Te=M.observable(!1),Ee=M.observable(""),c=M.observable(He(G.peek().available)),z=M.observable("all"),D=M.observable(""),J=M.observable(""),ce=M.observable(""),Y=M.observable(""),pe=M.observable(""),de=M.observable(!1);let ue=!0;const qs=()=>p.peek().find(e=>e.id===h.peek())||null,X=M.computed(()=>p.value.find(e=>e.id===h.value)||null),Bt=M.computed(()=>h.value?"preview":"start"),U=M.computed(()=>!!G.value.available),_t=M.computed(()=>!!(g.value?.shop||je.value).available),Fe=M.computed(()=>!!h.value&&ps(c.value,U.value)),Q=e=>M.computed({read:()=>c.value[e]!==!1,write:t=>{c.value={...c.peek(),[e]:!!t}}}),P=e=>{c.value={...c.peek(),...e}},ee=e=>{try{return Ya(e)}catch{return e}},zt=M.computed(()=>p.value.map(e=>fs(e,ee))),jt=M.computed(()=>B.value.map(e=>is(e,ee))),j=M.computed(()=>(g.value?.items||[]).map(e=>({...e,choice:c.value.choices?.[e.key]}))),Wt=M.computed(()=>ds(j.value).map(e=>({...e,cls:D.value===e.id?"is-on":"",seg:"kind"}))),Ft=M.computed(()=>Et(j.value,St,Pt,D.value)),Kt=M.computed(()=>St.map(e=>({...e,count:Ft.value[e.id],cls:z.value===e.id?"is-on":"",seg:"filter"}))),$e=M.computed(()=>{const e=J.value.trim().toLowerCase();return j.value.filter(t=>(!D.value||t.kind===D.value)&&Pt(t,z.value)&&(!e||`${t.title} ${t.target} ${t.oldUrl} ${t.postType||""}`.toLowerCase().includes(e)))}),Zt=M.computed(()=>$e.value.slice(0,xe).map(e=>{const t=us(e);return{...t,pillCls:`${t.pillClass}${e.choice?" is-chosen":""}`}})),Vt=M.computed(()=>$e.value.length>xe?`Showing the first ${xe} of ${$e.value.length} - search or filter to find the rest.`:""),Gt=M.computed(()=>j.value.length?"Nothing matches.":"This export has nothing to import."),Jt=M.computed(()=>Ct(g.value)),Yt=M.computed(()=>(g.value?.notes||[]).map(e=>({text:e}))),Xt=M.computed(()=>(g.value?.redirects||[]).slice(0,xe).map(e=>({...e,pill:e.action==="skip"?"Skipped":e.action==="update"?"Update":"New",pillClass:e.action==="skip"?"is-muted":"is-ok"}))),Qt=M.computed(()=>String((g.value?.redirects||[]).filter(e=>e.action!=="skip").length)),ea=M.computed(()=>g.value?.site?.title||X.value?.siteTitle||"WordPress export"),ta=M.computed(()=>{const e=X.value,t=g.value?.site;return[t?.url||e?.siteUrl,e?.fileName,t?.wxrVersion&&`WXR ${t.wxrVersion}`,e?.at&&`uploaded ${ee(e.at)}`].filter(Boolean).join(" \xB7 ")}),te=M.computed(()=>ms(g.value)),aa=M.computed(()=>te.value?.note||""),sa=M.computed(()=>te.value?.tone==="warn"?"is-warn":"is-info"),oa=M.computed(()=>(te.value?.rows||[]).map(e=>({...e,id:e.label}))),na=M.computed(()=>(te.value?.notes||[]).map(e=>({text:e}))),la=M.computed(()=>!!te.value),Le=M.computed(()=>es(g.value)),Se=M.computed(()=>cs(g.value)),ra=M.computed(()=>!!(Le.value.length||Se.value.length)),ia=M.computed(()=>{const e=Se.value.length;return e?`Not imported: ${e} kind${e===1?"":"s"} of WordPress or plugin housekeeping`:""}),T=M.computed(()=>g.value?.people||null),ca=M.computed(()=>!!T.value?.rows?.length),pa=M.computed(()=>(T.value?.rows||[]).map(os)),da=M.computed(()=>!!T.value?.canCreate),ua=M.computed(()=>(T.value?.rows||[]).some(e=>e.choosable)),Ke=M.computed(()=>as(T.value)),ma=M.computed(()=>Ke.value.text),ha=M.computed(()=>Ke.value.tone==="warn"?"is-warn":"is-info"),wa=M.computed(()=>!!(T.value?.canCreate&&T.value?.mailReady&&T.value?.counts?.create)),va=M.computed(()=>ts(T.value)),fa=M.computed({read:()=>c.value.invite===!0,write:e=>{c.value={...c.peek(),invite:!!e}}}),ba=M.computed(()=>U.value?`Posts go to ${G.value.basePath||"/blog"}.`:G.value.reason||"No Blog is switched on here."),me=M.computed(()=>X.value?.uploads||null),ga=M.computed(()=>{const e=me.value;return e?`${e.fileName} - ${e.files} file${e.files===1?"":"s"}, ${ke(e.size)}${e.unsafe?`. ${e.unsafe} with unsafe names (like ../) were ignored`:""}.`:`Zip the wp-content/uploads folder and upload it here (up to ${ke(_.uploads||0)}).`}),ya=M.computed(()=>me.value?"Replace the zip":"Upload the uploads zip"),Ze=M.computed(()=>X.value?.widgets||null),xa=M.computed(()=>ys(X.value,_.widgets||0)),ka=M.computed(()=>Ze.value?"Replace the widgets file":"Upload the widgets file"),Pe=M.observable(""),Ve=M.computed(()=>gs(g.value)),Ge=M.computed(()=>!!g.value&&!y.value&&!ie.value&&!(c.value.media==="zip"&&!me.value)),Ma=M.computed(()=>y.value?`Importing${y.value.siteTitle?` ${y.value.siteTitle}`:""}\u2026`:""),Ta=M.computed(()=>ws(y.value)),Je=M.computed(()=>hs(y.value)),Ea=M.observable(ke(_.wxr||0)),Ye=l.querySelector(".wpx-running .wpx-bar-fill");f.add(M.effect(()=>{Ye&&(Ye.style.width=`${Je.value}%`)}));async function Ce(){const e=await C(`${$}/status`);return i()?(p.value=e.staged||[],B.value=e.history||[],G.value=e.blog||{available:!1},je.value=e.shop||{available:!1},e.running&&!y.peek()&&(y.value=e.running,se()),e):null}let he=0,Re=null;async function W({first:e=!1}={}){const t=h.peek();if(!t)return;const a=++he;Ee.value="";const s=C(`${$}/staged/${t}/preview`,{method:"POST",body:JSON.stringify({options:ye(c.peek(),U.peek())})});try{let r;if(e?(ie.value=!0,r=await kt(l.querySelector(".wpx-plan-loading"),s,xt.table)):(Te.value=!0,r=await s),a!==he||!i()||h.peek()!==t)return;g.value=r.plan}catch(r){if(a!==he||!i())return;if(r.status===404){E.toast("That upload has gone - upload the export again.",{type:"warning"}),ae(),Ce().catch(()=>{});return}Ee.value=`Could not read the export: ${r.message||r}`}finally{a===he&&(ie.value=!1,Te.value=!1)}}let we="";f.add(M.effect(()=>{const e=`${h.value}|${Lt(c.value)}`;if(e===we)return;const t=we.split("|")[0];we=e,!(!h.value||t!==h.value)&&(clearTimeout(Re),Re=setTimeout(()=>W(),250))})),f.add(()=>clearTimeout(Re)),f.add(M.effect(()=>{const e=h.value,t=c.value,a=Fe.value;if(!e||!ue||!k)return;const s=w();q(At(s,b,e,a?{options:ye(t,!0),at:new Date().toISOString(),was:e}:null))}));const qe=e=>{k&&q(At(w(),b,e,null))};async function ve(e){if(!p.peek().some(d=>d.id===e))return;const t=p.peek().find(d=>d.id===e),a=$t(w(),p.peek().map(d=>d.id),b).items[e];if(ue=!a,g.value=null,z.value="all",D.value="",J.value="",c.value=He(U.peek()),we=`${e}|${Lt(c.peek())}`,h.value=e,await W({first:!0}),!i()||h.peek()!==e||!a)return;await new Promise(d=>setTimeout(d,0));const s=rs({when:a.at?ee(a.at):"",file:t?.fileName||""}),r=await Me(V(s.text),{title:s.title,confirmText:s.confirmText,cancelText:s.cancelText});!i()||h.peek()!==e||(ue=!0,r?qe(e):c.value=ye(a.options,U.peek()))}function ae(){h.value="",g.value=null,ue=!0}async function Xe(e=h.peek()){const t=p.peek().find(a=>a.id===e);if(t&&await Me(`Discard the upload of <strong>${V(t.siteTitle||t.fileName)}</strong>? Nothing has been imported from it.`,{title:"Discard upload",confirmText:"Discard",danger:!0}))try{if(await C(`${$}/staged/${e}`,{method:"DELETE"}),qe(e),!i())return;h.peek()===e&&ae(),p.value=p.peek().filter(a=>a.id!==e),R(re)}catch(a){E.toast(`Not discarded: ${a.message||a}`,{type:"error"})}}async function Qe(e){if(!e)return;if(Y.value="",!/\.(xml|zip)$/i.test(e.name)){Y.value="Upload the .xml file WordPress exports, or a .zip holding it.";return}if(_.wxr&&e.size>_.wxr*1.2&&/\.xml$/i.test(e.name)){Y.value=`That file is over ${ke(_.wxr)}.`;return}const t=l.querySelector(".wpx-start .wpx-upload .wpx-bar-fill");ce.value=`Uploading ${e.name}\u2026`;try{const a=await _e(`${$}/upload`,e,s=>{t&&(t.style.width=`${Math.round(s*100)}%`),ce.value=s<1?`Uploading ${e.name} - ${Math.round(s*100)}%`:`Reading ${e.name}\u2026`});if(!i())return;p.value=[a,...p.peek().filter(s=>s.id!==a.id)],R(re),await ve(a.id)}catch(a){i()&&(Y.value=a.message||String(a))}finally{i()&&(ce.value="",t&&(t.style.width="0"))}}async function $a(e){const t=h.peek();if(!e||!t)return;if(!/\.zip$/i.test(e.name)){E.toast("Zip the wp-content/uploads folder and upload the .zip.",{type:"warning"});return}const a=l.querySelector(".wpx-zip-fill");pe.value=`Uploading ${e.name}\u2026`;try{const s=await _e(`${$}/staged/${t}/uploads`,e,r=>{a&&(a.style.width=`${Math.round(r*100)}%`),pe.value=r<1?`Uploading ${e.name} - ${Math.round(r*100)}%`:`Checking ${e.name}\u2026`});if(!i())return;p.value=p.peek().map(r=>r.id===s.id?s:r),h.peek()===t&&W()}catch(s){i()&&E.toast(s.message||String(s),{type:"error"})}finally{i()&&(pe.value="",a&&(a.style.width="0"))}}async function La(e){const t=h.peek();if(!(!e||!t)){if(!/\.(wie|json)$/i.test(e.name)){E.toast('Upload the .wie file the "Widget Importer & Exporter" plugin exports.',{type:"warning"});return}Pe.value=`Reading ${e.name}\u2026`;try{const a=await _e(`${$}/staged/${t}/widgets`,e,()=>{});if(!i())return;p.value=p.peek().map(s=>s.id===a.id?a:s),h.peek()===t&&W()}catch(a){i()&&E.toast(a.message||String(a),{type:"error"})}finally{i()&&(Pe.value="")}}}async function Sa(){const e=h.peek();try{const t=await C(`${$}/staged/${e}/widgets`,{method:"DELETE"});if(!i())return;p.value=p.peek().map(a=>a.id===t.id?t:a),W()}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error"})}}async function Pa(){const e=h.peek();try{const t=await C(`${$}/staged/${e}/uploads`,{method:"DELETE"});if(!i())return;p.value=p.peek().map(a=>a.id===t.id?t:a),W()}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error"})}}async function Ca(){const e=h.peek(),t=g.peek();if(!e||!t||!Ge.peek())return;const s=Ct(t).filter(d=>d.value&&d.id!=="comments").map(d=>`${d.value} ${d.label.toLowerCase()}`);if(!(!await Me(`Import <strong>${V(t.site?.title||"this export")}</strong>: ${V(s.join(", ")||"nothing new")}? Pages edited here since an earlier import are left alone unless you chose Overwrite.`,{title:"Run the import",confirmText:"Run import"})||!i()||h.peek()!==e))try{const d=await C(`${$}/staged/${e}/run`,{method:"POST",body:JSON.stringify({options:ye(c.peek(),U.peek())})});if(qe(e),!i())return;y.value={...d.job,siteTitle:t.site?.title||""},ae(),R(re),se()}catch(d){E.toast(`The import did not start: ${d.message||d}`,{type:"error"})}}let Ae=null;function se(){clearTimeout(Ae);const e=y.peek();e&&(Ae=setTimeout(async()=>{try{const t=await C(`${$}/jobs/${e.id}`);if(!i())return;if(y.value={...t.job,siteTitle:e.siteTitle},t.job.status==="running")return se();if(y.value=null,await Ce().catch(()=>{}),R(re),R("#/pages"),R("#/media"),R("#/plugins/shopping-cart"),R("#/collections"),R("#/blocks"),!i())return;t.job.status==="done"?(E.toast("The import has finished - here is its report.",{type:"success"}),fe(t.job.reportId)):(E.toast(`The import stopped: ${t.job.error||"an error"}`,{type:"error"}),t.job.reportId&&fe(t.job.reportId))}catch(t){if(!i())return;if(t.status===404){y.value=null,Ce().catch(()=>{});return}se()}},700))}f.add(()=>clearTimeout(Ae)),y.peek()&&se();let F=!1;async function fe(e){if(F||!e)return;F=!0;let t;try{t=await C(`${$}/history/${e}`)}catch(o){F=!1,E.toast(`Could not open the report: ${o.message||o}`,{type:"error"});return}if(!i()){F=!1;return}let a;try{a=E.slideover({title:V(`Report - ${t.run?.siteTitle||"WordPress import"}`),size:"lg",position:"right"})}catch(o){throw F=!1,o}const s=Rs(`
66
80
  <div class="wpx wpx-report">
67
81
  <p class="wpx-note" data-bind-text="meta.value"></p>
68
82
  <div class="wpx-tiles" data-each="tiles.value key=id"></div>
@@ -76,4 +90,4 @@ import{apiRequest as A,getToken as Aa}from"../api.js";import{addBannerAction as
76
90
  <div class="wpx-list wpx-report-people" data-each="people.value key=key"></div>
77
91
  <div class="wpx-card-title" data-bind-hidden="!redirects.value.length">Redirects</div>
78
92
  <div class="wpx-list wpx-report-redirects" data-each="redirects.value key=from"></div>
79
- </div>`);o.querySelector(".wpx-tiles").innerHTML=Et,o.querySelector(".wpx-note-list").innerHTML=St,o.querySelector(".wpx-seg").innerHTML=Oe,o.querySelector(".wpx-report-items").innerHTML=Mt,o.querySelector(".wpx-report-redirects").innerHTML=Tt,o.querySelector(".wpx-report-people").innerHTML=mo;const i=M.observable(t.run?.summary?.failed?"failed":"all"),c=t.items||[],rt=mt(c,yt,gt),q=t.run?.summary||{},Ae=s=>Object.values(q.kinds||{}).reduce((r,x)=>r+(x[s]||0),0),xa={meta:M.observable([G(t.run?.at),t.run?.by,t.run?.file,t.run?.durationMs?`${Math.max(1,Math.round(t.run.durationMs/1e3))} s`:""].filter(Boolean).join(" \xB7 ")),tiles:M.observable([{id:"made",label:"Made",value:Ae("created"),sub:""},{id:"updated",label:"Updated",value:Ae("updated"),sub:""},{id:"kept",label:"Left as is",value:Ae("kept"),sub:""},{id:"redirects",label:"Redirects",value:(q.redirects?.created||0)+(q.redirects?.updated||0),sub:""},...q.people?[{id:"people",label:"Accounts made",value:q.people.created||0,sub:q.people.invited?`${q.people.invited} emailed`:""}]:[],{id:"comments",label:"Comments",value:q.comments||0,sub:""},{id:"failed",label:"Failed",value:q.failed||0,sub:""}]),notes:M.observable((t.notes||[]).map(s=>({text:s}))),people:M.observable((t.people||[]).map(Ka)),filters:M.computed(()=>yt.filter(s=>s.id==="all"||rt[s.id]).map(s=>({...s,count:rt[s.id],cls:i.value===s.id?"is-on":""}))),rows:M.computed(()=>c.filter(s=>gt(s,i.value)).slice(0,1e3).map(s=>{const r=oo(s);return{...r,pillCls:r.pillClass}})),redirects:M.observable((t.redirects||[]).filter(s=>s.result!=="skipped").slice(0,1e3).map(s=>({...s,reason:s.error||"",pill:s.result==="failed"?"Failed":s.result==="updated"?"Updated":"Added",pillClass:s.result==="failed"?"is-bad":"is-ok"})))};a.element.appendChild(o);const ka=M.applyBindings(xa,o,{methods:{pickSeg:s=>{i.value=s.id}}}),Ma=pt(o),lt=new Map(c.map(s=>[s.key,s])),Ta=o.querySelector(".wpx-report-items"),Ea=s=>{const r=lt.get(s.dataset.key);if(!r)return[];const x=typeof r.target=="string"&&r.target.startsWith("/")?r.target:"",C=[{header:r.title}];if(x&&r.result!=="failed"&&r.result!=="skipped"&&C.push({label:"Open on the site",icon:"external-link",action:()=>window.open(x,"_blank","noopener")}),x&&(r.kind==="page"||r.kind==="custom"&&!r.collection)&&(r.result==="created"||r.result==="updated"||r.result==="kept")&&C.push({label:"Edit the page",icon:"edit",action:()=>{a.close(),location.hash=`#/pages/edit${x}`}}),(r.kind==="collection"||r.kind==="custom")&&r.result!=="failed"&&r.result!=="skipped"&&/^[a-z0-9-]{1,80}$/.test(r.kind==="collection"?r.target:r.collection||"")){const j=r.kind==="collection"?r.target:r.collection;C.push({label:"Open the collection",icon:"database",action:()=>{a.close(),location.hash=`#/collections/${j}/entries`}})}return r.kind==="product"&&r.entryId&&/^[\w-]{1,64}$/.test(r.entryId)&&C.push({label:"Edit in the Shop",icon:"shopping-bag",action:()=>{a.close(),location.hash=`#/plugins/shopping-cart/products/${encodeURIComponent(r.entryId)}`}}),/^https?:\/\//i.test(r.oldUrl)&&C.push({label:"Open the old address",icon:"link",action:()=>window.open(r.oldUrl,"_blank","noopener,noreferrer")}),x&&C.push({label:"Copy the new address",icon:"copy",action:()=>De(new URL(x,location.origin).href)}),C},Sa=E.contextMenu(Ta,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:Ea}),$a=o.querySelector(".wpx-report-people"),La=new Map((t.people||[]).map(s=>[s.key,s])),Ca=E.contextMenu($a,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:s=>{const r=La.get(s.dataset.key);if(!r)return[];const x=/^[0-9a-f-]{36}$/i.test(r.userId||"")?r.userId:"";return[{header:r.name},...x&&r.result!=="skipped"?[{label:"Open their account",icon:"user",action:()=>{a.close(),location.hash=`#/users/edit/${x}`}}]:[],{label:"Open Users",icon:"users",action:()=>{a.close(),location.hash="#/users"}}]}}),Pa=o.querySelector(".wpx-report-redirects"),Ra=E.contextMenu(Pa,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:s=>[{header:s.dataset.from},...s.dataset.from.includes("*")?[]:[{label:"Try it on the site",icon:"external-link",action:()=>window.open(s.dataset.from,"_blank","noopener")}],{label:"Open SEO \u203A Redirects",icon:"corner-up-right",action:()=>{a.close(),location.hash="#/seo/redirects"}}]}),Ie=s=>{const r=s.target.closest(".wpx-kebab");if(r){H(r);return}const x=s.target.closest(".wpx-report-people .wpx-row");if(x){H(x.querySelector(".wpx-kebab")||x);return}const C=s.target.closest(".wpx-report-items .wpx-row");if(!C)return;const j=lt.get(C.dataset.key);j?.target?.startsWith("/")&&j.result!=="failed"&&j.result!=="skipped"&&window.open(j.target,"_blank","noopener")},it=s=>{const r=s.target.closest(".wpx-row");r&&(s.key==="F10"&&s.shiftKey?(s.preventDefault(),H(r)):s.key==="Enter"&&s.target===r&&(s.preventDefault(),s.stopPropagation(),Ie({target:r})))};o.addEventListener("click",Ie),o.addEventListener("keydown",it);const qa=y.add(()=>a.close());Na(a,()=>{_=!1,ka.dispose(),Ma(),Sa?.destroy?.(),Ra?.destroy?.(),Ca?.destroy?.(),o.removeEventListener("click",Ie),o.removeEventListener("keydown",it),qa()}),a.open(),I.scan(o)}async function wa(e){const t=U.peek().find(a=>a.id===e);if(t&&await ge(`Remove the import of <strong>${F(t.siteTitle||t.source)}</strong> from this list? Everything it imported stays on the site.`,{title:"Remove from the history",confirmText:"Remove"}))try{if(await A(`${$}/history/${e}`,{method:"DELETE"}),!p())return;U.value=U.peek().filter(a=>a.id!==e),N(se)}catch(a){E.toast(`Not removed: ${a.message||a}`,{type:"error"})}}n.querySelector(".wpx-staged").innerHTML=io,n.querySelector(".wpx-items").innerHTML=Mt,n.querySelector(".wpx-redirects").innerHTML=Tt,n.querySelector(".wpx-history").innerHTML=po,n.querySelector(".wpx-tiles").innerHTML=Et,n.querySelector(".wpx-note-list").innerHTML=St,n.querySelector(".wpx-kinds").innerHTML=Oe,n.querySelector(".wpx-filters").innerHTML=Oe,n.querySelector(".wpx-facts").innerHTML=vo,n.querySelector(".wpx-product-notes").innerHTML=$t,n.querySelector(".wpx-types").innerHTML=co,n.querySelector(".wpx-internal").innerHTML=$t,n.querySelector(".wpx-people-list").innerHTML=uo;const fa={mode:Ct,job:g,jobTitle:ca,jobText:da,jobPct:je,staged:v,stagedRows:Rt,historyRows:qt,plan:b,planLoading:ne,planError:ke,refreshing:xe,opt:l,blogAvailable:D,blogNote:la,dirty:Be,canRun:ze,siteTitle:jt,siteMeta:Ft,tiles:Ut,notes:Bt,kinds:At,filters:Nt,rows:Ot,moreText:Dt,emptyText:Ht,redirectRows:_t,redirectCount:zt,query:V,uploadText:re,uploadError:J,dropping:ie,wxrLimit:ua,zipInfo:ce,zipText:ia,zipButton:pa,zipUploadText:le,hasPeople:Xt,peopleRows:Qt,peopleCanCreate:ea,peopleChoosable:ta,peopleNote:aa,peopleNoteCls:oa,inviteAvailable:sa,inviteText:na,optInvite:ra,shopAvailable:Pt,hasProducts:Jt,productNote:Wt,productNoteCls:Kt,productFacts:Zt,productNotes:Vt,typeRows:Ee,internalRows:Se,hasCustom:Yt,internalSummary:Gt,optComments:Y("comments"),optMenus:Y("menus"),optRedirects:Y("redirects"),optDrafts:Y("drafts"),optProducts:Y("products")},ba=M.applyBindings(fa,n,{methods:{back:()=>ee(),discard:()=>We(),run:()=>ha(),resetChoices:()=>{l.value=Ne(D.peek())},postsBlog:()=>L({posts:"blog"}),postsPages:()=>L({posts:"pages"}),postsSkip:()=>L({posts:"skip"}),mediaDownload:()=>L({media:"download"}),mediaZip:()=>L({media:"zip"}),mediaSkip:()=>L({media:"skip"}),removeZip:()=>va(),peopleAll:()=>L({users:ut(T.peek(),"create")}),peopleNone:()=>L({users:ut(T.peek(),"skip")}),pickSeg:e=>{e.seg==="kind"?O.value=e.id:B.value=e.id},showProducts:()=>{O.value="product",B.value="all",V.value="",n.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})}}});y.add(()=>ba.dispose()),y.add(pt(n,{position:"bottom"}));const ae=n.querySelector(".wpx-file"),Ze=()=>{const e=ae.files?.[0];ae.value="",Ke(e)};ae.addEventListener("change",Ze);const z=n.querySelector(".wpx-drop"),Ve=e=>{e.preventDefault(),ie.value=!0},Je=()=>{ie.value=!1},Ye=e=>{e.preventDefault(),ie.value=!1,Ke(e.dataTransfer?.files?.[0])};z.addEventListener("dragover",Ve),z.addEventListener("dragleave",Je),z.addEventListener("drop",Ye);const we=n.querySelector(".wpx-zip-file"),Ge=()=>{const e=we.files?.[0];we.value="",ma(e)};we.addEventListener("change",Ge),y.add(()=>{ae.removeEventListener("change",Ze),z.removeEventListener("dragover",Ve),z.removeEventListener("dragleave",Je),z.removeEventListener("drop",Ye),we.removeEventListener("change",Ge)}),W(n.querySelector(".wpx-staged"),".wpx-row",e=>[{header:v.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Upload"},{label:"Preview the import",icon:"eye",action:()=>ve(e.dataset.id)},{label:"Discard the upload",icon:"trash-2",action:()=>We(e.dataset.id)}],e=>ve(e.dataset.id));const ya=(e,t)=>{l.value={...l.peek(),choices:Ha(l.peek().choices,e,t)}};W(n.querySelector(".wpx-items"),".wpx-row",e=>{const t=X.peek().find(o=>o.key===e.dataset.key);if(!t)return[];const a=[{header:t.title}];for(const o of Ba(t,t.choice))a.push({label:o.label,icon:o.icon,action:()=>ya(t.key,o.id)});return t.oldUrl&&/^https?:\/\//i.test(t.oldUrl)&&a.push({label:"Open the old address",icon:"link",action:()=>window.open(t.oldUrl,"_blank","noopener,noreferrer")}),t.target?.startsWith("/")&&(t.action==="update"||t.action==="keep")&&a.push({label:"Open it on this site",icon:"external-link",action:()=>window.open(t.target,"_blank","noopener")}),a.length===1&&a.push({label:"Nothing to change here",icon:"info",disabled:!0,action:()=>{}}),a},e=>H(e.querySelector(".wpx-kebab")||e));const Xe=(e,t)=>{l.value=lo(l.peek(),e,t)},Qe=(e,t)=>{l.value=ro(l.peek(),e,t)},ga=e=>{O.value="",B.value="all",V.value=e,n.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})},oe=n.querySelector(".wpx-types"),et=e=>{if(!e.target.closest(".wpx-type-ctl"))return;e.stopPropagation();const a=e.target.closest("button[data-as]"),o=e.target.closest(".wpx-type-row");a&&o&&Xe(o.dataset.type,a.dataset.as)},tt=e=>{const t=e.target.closest("input[data-list-page]"),a=e.target.closest(".wpx-type-row");t&&a&&Qe(a.dataset.type,t.checked)};oe.addEventListener("click",et,!0),oe.addEventListener("change",tt),y.add(()=>{oe.removeEventListener("click",et,!0),oe.removeEventListener("change",tt)}),W(oe,".wpx-type-row",e=>{const t=Ee.peek().find(a=>a.type===e.dataset.type);return t?[{header:`${t.label} ("${t.type}")`},...no(t).map(a=>({label:a.label,icon:a.icon,action:()=>{a.id==="show"?ga(t.type):a.id==="list"||a.id==="nolist"?Qe(t.type,a.id==="list"):Xe(t.type,a.id)}}))]:[]},e=>H(e.querySelector(".wpx-kebab")||e));const at=(e,t)=>{const a=T.peek()?.rows?.find(o=>o.key===e);a&&L({users:Ua(l.peek().users,a,t)})},Re=n.querySelector(".wpx-people-list"),ot=e=>{if(!e.target.closest(".wpx-type-ctl"))return;e.stopPropagation();const a=e.target.closest("button[data-user]"),o=e.target.closest(".wpx-person");a&&o&&at(o.dataset.key,a.dataset.user)};Re.addEventListener("click",ot,!0),y.add(()=>Re.removeEventListener("click",ot,!0)),W(Re,".wpx-person",e=>{const t=T.peek()?.rows?.find(a=>a.key===e.dataset.key);return t?[{header:t.name},...Fa(t).map(a=>({label:a.label,icon:a.icon,disabled:!!a.disabled,action:()=>{a.disabled||(a.id==="copy"?De(t.email):at(t.key,a.id))}}))]:[]},e=>H(e.querySelector(".wpx-kebab")||e));const R=n.querySelector(".wpx-role");y.add(M.effect(()=>{const e=T.value;if(!R)return;const t=e?.roles||[],a=e?.role||"",o=`${t.map(i=>i.name).join(",")}|${a}`;R.dataset.sig!==o&&(R.dataset.sig=o,R.replaceChildren(...t.map(i=>{const c=document.createElement("option");return c.value=i.name,c.textContent=i.name===Ue?`${i.label} (default)`:i.label,c})),R.value=a)}));const st=()=>L({userRole:R.value===Ue?"":R.value});R?.addEventListener("change",st),y.add(()=>R?.removeEventListener("change",st)),W(n.querySelector(".wpx-redirects"),".wpx-row",e=>[{header:e.dataset.from},{label:"Copy the old address",icon:"copy",action:()=>De(e.dataset.from)}],null),W(n.querySelector(".wpx-history"),".wpx-row",e=>[{header:U.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Import"},{label:"Open the report",icon:"file-text",action:()=>he(e.dataset.id)},{label:"Remove from the history",icon:"trash-2",action:()=>wa(e.dataset.id)}],e=>he(e.dataset.id)),Ia(d,{id:"wpx-upload",icon:"upload",title:"Upload an export",onClick:()=>{h.peek()&&ee(),setTimeout(()=>ae.click(),0)}}),I.scan(n);const qe=vt(m(),v.peek().map(e=>e.id),f);qe.pruned&&k&&P({owner:f,items:qe.items});const nt=Object.entries(qe.items).sort((e,t)=>String(t[1].at).localeCompare(String(e[1].at)))[0];nt&&!g.peek()&&await ve(nt[0])}};
93
+ </div>`);s.querySelector(".wpx-tiles").innerHTML=Dt,s.querySelector(".wpx-note-list").innerHTML=Ut,s.querySelector(".wpx-seg").innerHTML=Be,s.querySelector(".wpx-report-items").innerHTML=Nt,s.querySelector(".wpx-report-redirects").innerHTML=Ot,s.querySelector(".wpx-report-people").innerHTML=Ss;const r=M.observable(t.run?.summary?.failed?"failed":"all"),d=t.items||[],ft=Et(d,Rt,qt),N=t.run?.summary||{},De=o=>Object.values(N.kinds||{}).reduce((n,x)=>n+(x[o]||0),0),Na={meta:M.observable([ee(t.run?.at),t.run?.by,t.run?.file,t.run?.durationMs?`${Math.max(1,Math.round(t.run.durationMs/1e3))} s`:""].filter(Boolean).join(" \xB7 ")),tiles:M.observable([{id:"made",label:"Made",value:De("created"),sub:""},{id:"updated",label:"Updated",value:De("updated"),sub:""},{id:"kept",label:"Left as is",value:De("kept"),sub:""},{id:"redirects",label:"Redirects",value:(N.redirects?.created||0)+(N.redirects?.updated||0),sub:""},...N.people?[{id:"people",label:"Accounts made",value:N.people.created||0,sub:N.people.invited?`${N.people.invited} emailed`:""}]:[],{id:"comments",label:"Comments",value:N.comments||0,sub:""},{id:"failed",label:"Failed",value:N.failed||0,sub:""}]),notes:M.observable((t.notes||[]).map(o=>({text:o}))),people:M.observable((t.people||[]).map(ns)),filters:M.computed(()=>Rt.filter(o=>o.id==="all"||ft[o.id]).map(o=>({...o,count:ft[o.id],cls:r.value===o.id?"is-on":""}))),rows:M.computed(()=>d.filter(o=>qt(o,r.value)).slice(0,1e3).map(o=>{const n=vs(o);return{...n,pillCls:n.pillClass}})),redirects:M.observable((t.redirects||[]).filter(o=>o.result!=="skipped").slice(0,1e3).map(o=>({...o,reason:o.error||"",pill:o.result==="failed"?"Failed":o.result==="updated"?"Updated":"Added",pillClass:o.result==="failed"?"is-bad":"is-ok"})))};a.element.appendChild(s);const Oa=M.applyBindings(Na,s,{methods:{pickSeg:o=>{r.value=o.id}}}),Da=yt(s),bt=new Map(d.map(o=>[o.key,o])),Ua=s.querySelector(".wpx-report-items"),Ha=o=>{const n=bt.get(o.dataset.key);if(!n)return[];const x=typeof n.target=="string"&&n.target.startsWith("/")?n.target:"",L=[{header:n.title}];if(x&&n.result!=="failed"&&n.result!=="skipped"&&L.push({label:"Open on the site",icon:"external-link",action:()=>window.open(x,"_blank","noopener")}),x&&(n.kind==="page"||n.kind==="custom"&&!n.collection)&&(n.result==="created"||n.result==="updated"||n.result==="kept")&&L.push({label:"Edit the page",icon:"edit",action:()=>{a.close(),location.hash=`#/pages/edit${x}`}}),n.kind==="widgets"&&/^[a-z0-9][a-z0-9-]{0,79}$/.test(n.target||"")&&["created","updated","kept"].includes(n.result)&&(L.push({label:"Edit the block",icon:"edit",action:()=>{a.close(),location.hash=`#/blocks/edit/${n.target}`}}),n.footer&&L.push({label:"Footer settings",icon:"settings",action:()=>{a.close(),location.hash="#/settings"}})),(n.kind==="collection"||n.kind==="custom")&&n.result!=="failed"&&n.result!=="skipped"&&/^[a-z0-9-]{1,80}$/.test(n.kind==="collection"?n.target:n.collection||"")){const Z=n.kind==="collection"?n.target:n.collection;L.push({label:"Open the collection",icon:"database",action:()=>{a.close(),location.hash=`#/collections/${Z}/entries`}})}return n.kind==="product"&&n.entryId&&/^[\w-]{1,64}$/.test(n.entryId)&&L.push({label:"Edit in the Shop",icon:"shopping-bag",action:()=>{a.close(),location.hash=`#/plugins/shopping-cart/products/${encodeURIComponent(n.entryId)}`}}),/^https?:\/\//i.test(n.oldUrl)&&L.push({label:"Open the old address",icon:"link",action:()=>window.open(n.oldUrl,"_blank","noopener,noreferrer")}),x&&L.push({label:"Copy the new address",icon:"copy",action:()=>ze(new URL(x,location.origin).href)}),L},Ba=E.contextMenu(Ua,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:Ha}),_a=s.querySelector(".wpx-report-people"),za=new Map((t.people||[]).map(o=>[o.key,o])),ja=E.contextMenu(_a,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:o=>{const n=za.get(o.dataset.key);if(!n)return[];const x=/^[0-9a-f-]{36}$/i.test(n.userId||"")?n.userId:"";return[{header:n.name},...x&&n.result!=="skipped"?[{label:"Open their account",icon:"user",action:()=>{a.close(),location.hash=`#/users/edit/${x}`}}]:[],{label:"Open Users",icon:"users",action:()=>{a.close(),location.hash="#/users"}}]}}),Wa=s.querySelector(".wpx-report-redirects"),Fa=E.contextMenu(Wa,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:o=>[{header:o.dataset.from},...o.dataset.from.includes("*")?[]:[{label:"Try it on the site",icon:"external-link",action:()=>window.open(o.dataset.from,"_blank","noopener")}],{label:"Open SEO \u203A Redirects",icon:"corner-up-right",action:()=>{a.close(),location.hash="#/seo/redirects"}}]}),Ue=o=>{const n=o.target.closest(".wpx-kebab");if(n){O(n);return}const x=o.target.closest(".wpx-report-people .wpx-row");if(x){O(x.querySelector(".wpx-kebab")||x);return}const L=o.target.closest(".wpx-report-items .wpx-row");if(!L)return;const Z=bt.get(L.dataset.key);Z?.target?.startsWith("/")&&Z.result!=="failed"&&Z.result!=="skipped"&&window.open(Z.target,"_blank","noopener")},gt=o=>{const n=o.target.closest(".wpx-row");n&&(o.key==="F10"&&o.shiftKey?(o.preventDefault(),O(n)):o.key==="Enter"&&o.target===n&&(o.preventDefault(),o.stopPropagation(),Ue({target:n})))};s.addEventListener("click",Ue),s.addEventListener("keydown",gt);const Ka=f.add(()=>a.close());Ga(a,()=>{F=!1,Oa.dispose(),Da(),Ba?.destroy?.(),Fa?.destroy?.(),ja?.destroy?.(),s.removeEventListener("click",Ue),s.removeEventListener("keydown",gt),Ka()}),a.open(),I.scan(s)}async function Ra(e){const t=B.peek().find(a=>a.id===e);if(t&&await Me(`Remove the import of <strong>${V(t.siteTitle||t.source)}</strong> from this list? Everything it imported stays on the site.`,{title:"Remove from the history",confirmText:"Remove"}))try{if(await C(`${$}/history/${e}`,{method:"DELETE"}),!i())return;B.value=B.peek().filter(a=>a.id!==e),R(re)}catch(a){E.toast(`Not removed: ${a.message||a}`,{type:"error"})}}l.querySelector(".wpx-staged").innerHTML=Ts,l.querySelector(".wpx-items").innerHTML=Nt,l.querySelector(".wpx-redirects").innerHTML=Ot,l.querySelector(".wpx-history").innerHTML=Es,l.querySelector(".wpx-tiles").innerHTML=Dt,l.querySelector(".wpx-note-list").innerHTML=Ut,l.querySelector(".wpx-kinds").innerHTML=Be,l.querySelector(".wpx-filters").innerHTML=Be,l.querySelector(".wpx-facts").innerHTML=Cs,l.querySelector(".wpx-product-notes").innerHTML=Ht,l.querySelector(".wpx-types").innerHTML=$s,l.querySelector(".wpx-internal").innerHTML=Ht,l.querySelector(".wpx-people-list").innerHTML=Ls,l.querySelector(".wpx-widget-areas").innerHTML=Ps;const qa={mode:Bt,job:y,jobTitle:Ma,jobText:Ta,jobPct:Je,staged:p,stagedRows:zt,historyRows:jt,plan:g,planLoading:ie,planError:Ee,refreshing:Te,opt:c,blogAvailable:U,blogNote:ba,dirty:Fe,canRun:Ge,siteTitle:ea,siteMeta:ta,tiles:Jt,notes:Yt,kinds:Wt,filters:Kt,rows:Zt,moreText:Vt,emptyText:Gt,redirectRows:Xt,redirectCount:Qt,query:J,uploadText:ce,uploadError:Y,dropping:de,wxrLimit:Ea,zipInfo:me,zipText:ga,zipButton:ya,zipUploadText:pe,hasPeople:ca,peopleRows:pa,peopleCanCreate:da,peopleChoosable:ua,peopleNote:ma,peopleNoteCls:ha,inviteAvailable:wa,inviteText:va,optInvite:fa,shopAvailable:_t,hasProducts:la,productNote:aa,productNoteCls:sa,productFacts:oa,productNotes:na,typeRows:Le,internalRows:Se,hasCustom:ra,internalSummary:ia,widgetsInfo:Ze,widgetsText:xa,widgetsButton:ka,widgetsUploadText:Pe,widgetRows:Ve,optComments:Q("comments"),optMenus:Q("menus"),optRedirects:Q("redirects"),optDrafts:Q("drafts"),optProducts:Q("products")},Aa=M.applyBindings(qa,l,{methods:{back:()=>ae(),discard:()=>Xe(),run:()=>Ca(),resetChoices:()=>{c.value=He(U.peek())},postsBlog:()=>P({posts:"blog"}),postsPages:()=>P({posts:"pages"}),postsSkip:()=>P({posts:"skip"}),mediaDownload:()=>P({media:"download"}),mediaZip:()=>P({media:"zip"}),mediaSkip:()=>P({media:"skip"}),removeZip:()=>Pa(),removeWidgets:()=>Sa(),peopleAll:()=>P({users:Mt(T.peek(),"create")}),peopleNone:()=>P({users:Mt(T.peek(),"skip")}),pickSeg:e=>{e.seg==="kind"?D.value=e.id:z.value=e.id},showProducts:()=>{D.value="product",z.value="all",J.value="",l.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})}}});f.add(()=>Aa.dispose()),f.add(yt(l,{position:"bottom"}));const oe=l.querySelector(".wpx-file"),et=()=>{const e=oe.files?.[0];oe.value="",Qe(e)};oe.addEventListener("change",et);const K=l.querySelector(".wpx-drop"),tt=e=>{e.preventDefault(),de.value=!0},at=()=>{de.value=!1},st=e=>{e.preventDefault(),de.value=!1,Qe(e.dataTransfer?.files?.[0])};K.addEventListener("dragover",tt),K.addEventListener("dragleave",at),K.addEventListener("drop",st);const be=l.querySelector(".wpx-zip-file"),ot=()=>{const e=be.files?.[0];be.value="",$a(e)};be.addEventListener("change",ot);const ge=l.querySelector(".wpx-wie-file"),nt=()=>{const e=ge.files?.[0];ge.value="",La(e)};ge.addEventListener("change",nt),f.add(()=>ge.removeEventListener("change",nt)),f.add(()=>{oe.removeEventListener("change",et),K.removeEventListener("dragover",tt),K.removeEventListener("dragleave",at),K.removeEventListener("drop",st),be.removeEventListener("change",ot)}),H(l.querySelector(".wpx-staged"),".wpx-row",e=>[{header:p.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Upload"},{label:"Preview the import",icon:"eye",action:()=>ve(e.dataset.id)},{label:"Discard the upload",icon:"trash-2",action:()=>Xe(e.dataset.id)}],e=>ve(e.dataset.id));const lt=(e,t)=>{c.value={...c.peek(),choices:Xa(c.peek().choices,e,t)}};H(l.querySelector(".wpx-items"),".wpx-row",e=>{const t=j.peek().find(s=>s.key===e.dataset.key);if(!t)return[];const a=[{header:t.title}];for(const s of Tt(t,t.choice))a.push({label:s.label,icon:s.icon,action:()=>lt(t.key,s.id)});return t.oldUrl&&/^https?:\/\//i.test(t.oldUrl)&&a.push({label:"Open the old address",icon:"link",action:()=>window.open(t.oldUrl,"_blank","noopener,noreferrer")}),t.target?.startsWith("/")&&(t.action==="update"||t.action==="keep")&&a.push({label:"Open it on this site",icon:"external-link",action:()=>window.open(t.target,"_blank","noopener")}),a.length===1&&a.push({label:"Nothing to change here",icon:"info",disabled:!0,action:()=>{}}),a},e=>O(e.querySelector(".wpx-kebab")||e));const rt=(e,t)=>{c.value=Ms(c.peek(),e,t)},it=(e,t)=>{c.value=ks(c.peek(),e,t)},Ia=e=>{D.value="",z.value="all",J.value=e,l.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})},ne=l.querySelector(".wpx-types"),ct=e=>{if(!e.target.closest(".wpx-type-ctl"))return;e.stopPropagation();const a=e.target.closest("button[data-as]"),s=e.target.closest(".wpx-type-row");a&&s&&rt(s.dataset.type,a.dataset.as)},pt=e=>{const t=e.target.closest("input[data-list-page]"),a=e.target.closest(".wpx-type-row");t&&a&&it(a.dataset.type,t.checked)};ne.addEventListener("click",ct,!0),ne.addEventListener("change",pt),f.add(()=>{ne.removeEventListener("click",ct,!0),ne.removeEventListener("change",pt)}),H(ne,".wpx-type-row",e=>{const t=Le.peek().find(a=>a.type===e.dataset.type);return t?[{header:`${t.label} ("${t.type}")`},...bs(t).map(a=>({label:a.label,icon:a.icon,action:()=>{a.id==="show"?Ia(t.type):a.id==="list"||a.id==="nolist"?it(t.type,a.id==="list"):rt(t.type,a.id)}}))]:[]},e=>O(e.querySelector(".wpx-kebab")||e));const Ie=(e,t)=>{c.value=xs(c.peek(),e,t)},le=l.querySelector(".wpx-widget-areas"),dt=e=>{e.target.closest(".wpx-type-ctl")&&e.stopPropagation()},ut=e=>{const t=e.target.closest("input[data-footer]"),a=e.target.closest(".wpx-widget-row");t&&a&&Ie(a.dataset.area,t.checked)};le.addEventListener("click",dt,!0),le.addEventListener("change",ut),f.add(()=>{le.removeEventListener("click",dt,!0),le.removeEventListener("change",ut)}),H(le,".wpx-widget-row",e=>{const t=Ve.peek().find(r=>r.area===e.dataset.area),a=j.peek().find(r=>r.key===`widgets:${e.dataset.area}`);if(!t)return[];const s=[{header:`${t.label} ("${t.area}")`}];if(t.canPlace&&s.push(t.placeOn?{label:"Leave it out of the footer",icon:"eye-off",action:()=>Ie(t.area,!1)}:{label:"Place in the footer",icon:"layout",action:()=>Ie(t.area,!0)}),a)for(const r of Tt(a,a.choice))s.push({label:r.label,icon:r.icon,action:()=>lt(a.key,r.id)});return s.length===1&&s.push({label:"Nothing to change here",icon:"info",disabled:!0,action:()=>{}}),s},e=>O(e.querySelector(".wpx-kebab")||e));const mt=(e,t)=>{const a=T.peek()?.rows?.find(s=>s.key===e);a&&P({users:Qa(c.peek().users,a,t)})},Ne=l.querySelector(".wpx-people-list"),ht=e=>{if(!e.target.closest(".wpx-type-ctl"))return;e.stopPropagation();const a=e.target.closest("button[data-user]"),s=e.target.closest(".wpx-person");a&&s&&mt(s.dataset.key,a.dataset.user)};Ne.addEventListener("click",ht,!0),f.add(()=>Ne.removeEventListener("click",ht,!0)),H(Ne,".wpx-person",e=>{const t=T.peek()?.rows?.find(a=>a.key===e.dataset.key);return t?[{header:t.name},...ss(t).map(a=>({label:a.label,icon:a.icon,disabled:!!a.disabled,action:()=>{a.disabled||(a.id==="copy"?ze(t.email):mt(t.key,a.id))}}))]:[]},e=>O(e.querySelector(".wpx-kebab")||e));const A=l.querySelector(".wpx-role");f.add(M.effect(()=>{const e=T.value;if(!A)return;const t=e?.roles||[],a=e?.role||"",s=`${t.map(r=>r.name).join(",")}|${a}`;A.dataset.sig!==s&&(A.dataset.sig=s,A.replaceChildren(...t.map(r=>{const d=document.createElement("option");return d.value=r.name,d.textContent=r.name===We?`${r.label} (default)`:r.label,d})),A.value=a)}));const wt=()=>P({userRole:A.value===We?"":A.value});A?.addEventListener("change",wt),f.add(()=>A?.removeEventListener("change",wt)),H(l.querySelector(".wpx-redirects"),".wpx-row",e=>[{header:e.dataset.from},{label:"Copy the old address",icon:"copy",action:()=>ze(e.dataset.from)}],null),H(l.querySelector(".wpx-history"),".wpx-row",e=>[{header:B.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Import"},{label:"Open the report",icon:"file-text",action:()=>fe(e.dataset.id)},{label:"Remove from the history",icon:"trash-2",action:()=>Ra(e.dataset.id)}],e=>fe(e.dataset.id)),Va(u,{id:"wpx-upload",icon:"upload",title:"Upload an export",onClick:()=>{h.peek()&&ae(),setTimeout(()=>oe.click(),0)}}),I.scan(l);const Oe=$t(w(),p.peek().map(e=>e.id),b);Oe.pruned&&k&&q({owner:b,items:Oe.items});const vt=Object.entries(Oe.items).sort((e,t)=>String(t[1].at).localeCompare(String(e[1].at)))[0];vt&&!y.peek()&&await ve(vt[0])}};
@@ -41,7 +41,7 @@ Code was treated as the truth. Where docs disagree with it, the disagreement is
41
41
  | `data-transfer` | paid | 1.3.3 | Move collections between file and MongoDB storage, back them up and restore them (all or some, merged or replaced) - keeping every entry's id - and clone them (cloned entries get new ids, so references stay unambiguous). Every restore and transfer takes a safety snapshot first. |
42
42
  | `docs` | paid | 2.8.1 | Document editor: Home and editor screens, sharing with Viewers/Editors, public links, import/export, send to page/blog/email, edit lock, Trash, version history, templates. |
43
43
  | `docs-pro` | paid | 0.8.1 | Domma Spaces (publish a Docs folder at `/spaces` with a Kindle-style reader), Cloud copies (Google Drive, OneDrive, Dropbox, S3), Signatures (emailed signing links, typed or drawn, signed copy with a certificate page) and protected public links (expiry date, password). Adds to Docs. |
44
- | `email-pro` | paid | 0.5.9 | Full mail: read, write, send and search, over a synchronised store. Search, threads, rules, scheduled sends, snooze and vacation replies need MongoDB; without it, reading, filing and sending still work. |
44
+ | `email-pro` | paid | 0.9.0 | Full mail: read, write, send and search, over a synchronised store. Search, threads, rules, scheduled sends, snooze and vacation replies need MongoDB; without it, reading, filing and sending still work. |
45
45
  | `feedback` | free | 1.1.4 | Tell Domma what is broken, missing or good - straight from your admin - and see what became of it: seen, planned, in progress, done, with Domma's reply. |
46
46
  | `garage` | paid | 1.4.3 | UK vehicle management with DVLA API lookup, save vehicles, and search history. |
47
47
  | `invoice` | paid | 1.7.1 | Invoices and quotes. Issuer profiles, receivers importable from Contacts, line items, optional VAT, payments, archiving, CSV/Excel export, and a print-ready invoice you can email or download as a PDF. Quotes with a validity date and an emailed accept link - the client accepts or declines online, and one click turns an accepted quote into a draft invoice. Self-billing too: invoices raised on a supplier's behalf, marked and numbered their own way, with agreement review reminders. |
@@ -1144,7 +1144,7 @@ The four Tools below were plugins until 0.67.0 and are now part of the CMS. They
1144
1144
  Since: 0.72.0.
1145
1145
  - **Calendar sources and related items** - Two registries let plugins build on the core Tools without core depending on them:
1146
1146
  - `registerCalendarSource` - Todo supplies a "To-dos" layer of your open to-dos on their due days.
1147
- - `registerRelatedItems` - lets a plugin such as Calendar show "Events" beside a contact or note and add "Schedule with..." and "Add to calendar..." menu items.
1147
+ - `registerRelatedItems` - lets a plugin such as Calendar show "Events" beside a contact or note and add "Schedule with..." and "Add to calendar..." menu items. Email Pro 0.9 adds "Emails" to the contact card: the viewer's own newest messages with any of the contact's addresses, each opening in Email (engine `store.listByAddresses()`, reader links `#/plugins/<name>?open=<uid>&account=<id>&folder=<path>`; since 1.8.8).
1148
1148
 
1149
1149
  Before any provider is asked, core checks that the viewer owns the record. A provider that fails or is slow is named and left out.
1150
1150
  Where: `server/services/hooks.js`, `GET /api/related`, `GET /api/related/providers`, `admin/js/lib/related.js`; `contact:deleted` and `note:deleted` events - Since: 0.79.0.
@@ -1902,7 +1902,7 @@ Tier: paid (closed source) · Version: 0.8.0 · Requires: `docs` 2.6+ (2.8+ for
1902
1902
 
1903
1903
  ### Email (`email-pro`)
1904
1904
 
1905
- Tier: paid (closed source) · Version: 0.5.4 · Requires: MongoDB for the message store's extras (without it reading, starring, moving, deleting, folders and sending still work; search, conversations, rules, undo send, send later, snooze and the away message need MongoDB and say so); npm `imapflow` ^2.0.5, `mailparser` ^3.9.28, `nodemailer` 8.0.5; no `minCmsVersion` declared (Contacts switch-off handling core 0.88) · Uses: `contacts` (recipient suggestions and group expansion in compose; Add sender to contacts) · Supersedes: `mail-reader`
1905
+ Tier: paid (closed source) · Version: 0.9.0 · Requires: MongoDB for the message store's extras (without it reading, starring, moving, deleting, folders and sending still work; search, conversations, rules, undo send, send later, snooze and the away message need MongoDB and say so); npm `imapflow` ^2.0.5, `mailparser` ^3.9.28, `nodemailer` 8.0.5; no `minCmsVersion` declared (Contacts switch-off handling core 0.88) · Uses: `contacts` (recipient suggestions and group expansion in compose; Add sender to contacts) · Supersedes: `mail-reader`
1906
1906
 
1907
1907
  A full email client in the admin: connect your own IMAP mailboxes, read, write, send, search and file mail. Chosen folders are mirrored (envelopes and flags, never bodies) into MongoDB so big mailboxes open instantly, and on top come identities, templates, rules, an away responder, undo send, send later, snooze, threads and one-click hand-off of messages into the CMS.
1908
1908
 
@@ -1921,6 +1921,7 @@ A full email client in the admin: connect your own IMAP mailboxes, read, write,
1921
1921
  - Away (vacation) responder with a preview that runs the real decision over the mailbox and explains each message (skips lists, bounces, repeat senders).
1922
1922
  - Unsubscribe: RFC 8058 one-click performed server-side; other methods handed back to open or compose.
1923
1923
  - Hand-off to the CMS: save an attachment to Media, turn a message into a draft page, file a message as a collection entry; Add sender to contacts.
1924
+ - Emails on a contact's card (0.9.0, engine 1.8.8+): the contact slideover in Contacts (and Contacts Pro) lists your ten newest emails to or from the contact's main, billing and additional addresses - Received/Sent, unread, and the mailbox when you have several - each opening the message in Email. Your own mailboxes and mirrored folders only; needs MongoDB, and is absent without it.
1924
1925
  - Keyboard shortcuts: j/k or arrows to move, Enter open, u read/unread, s star, r reply, Shift+R or a reply all, f forward, c compose, `/` search, x select, Shift+A select all, `!` spam, `#` or Delete delete, Esc, `?` help.
1925
1926
  - Sidebar badge (`/unread`): unread in Inbox (counted from the store).
1926
1927
  - Notifications to the mailbox owner only: `email-pro:mailbox` (warning after 5 failed checks in a row; resolved when a check works) and `email-pro:sending` (a scheduled/undo-send message that failed).
@@ -253,6 +253,7 @@ the host is empty you get, in order of how likely you are to see it:
253
253
  | `copyright` | string | Copyright line - above the footer navigation in the one-row layout, at the left of the bottom bar in the columns layout |
254
254
  | `layout` | string | `auto` (default), `columns` or `row`. **Columns**: the brand (the navbar's text, logo or icon), the blurb and the social links beside one column per group of the `footer-primary` menu (a top-level item with sub-items; its text is the heading, a link only if it has a URL), over a bottom bar of copyright, the `footer-legal` menu and the motion control. **Row**: everything on one wrapped row, as before. **Auto**: columns when the footer menu has groups, else a row. Site Settings > Footer > Layout. |
255
255
  | `blurb` | string | A sentence or two under the brand in the columns layout. Plain text. |
256
+ | `blocks` | array | Block names (`content/blocks/<name>.html`) drawn side by side at the top of the footer on every page, in order - up to 8, lower-case letters, digits and hyphens. Rendered by the server (`buildFooterBlocks()` in `renderer.js`, both render paths) as `[block template="…" /]`, so sanitised like a page; a name that is not a block is left out. Site Settings > Footer > Footer blocks; Import from WordPress adds a site's footer widget areas here. |
256
257
  | `showMotionToggle` | boolean | Whether the footer shows the "Reduce motion" accessibility switch. **Defaults to `true`** - omit the key and the switch appears, so sites that predate this setting are unchanged. Editable from Site Settings > Footer. |
257
258
  | `class` | string | Your own CSS classes on the site footer (`#site-footer`). Site Settings > Footer. |
258
259
  | `socialClass` | string | Your own CSS classes on every social media icon link in the footer. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.8.7",
3
+ "version": "1.8.9",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -6126,6 +6126,38 @@ return {
6126
6126
  await startNewMessage(draft);
6127
6127
  }
6128
6128
 
6129
+ /**
6130
+ * Another screen linked to one message - a contact's Emails list
6131
+ * (Email Pro's related items): `#/plugins/<name>?open=<uid>&account=
6132
+ * <id>&folder=<path>`. Opens that mailbox, that folder and the
6133
+ * message, then takes the request off the address so a reload or Back
6134
+ * does not open it again. A mailbox that is not the viewer's is never
6135
+ * opened: the accounts list is theirs alone.
6136
+ */
6137
+ async function takeOpenRequest() {
6138
+ const params = new URLSearchParams(location.hash.split('?')[1] ?? '');
6139
+ if (!params.has('open')) return;
6140
+ history.replaceState(history.state, '', `${location.pathname}${location.search}#/plugins/${pluginName}`);
6141
+ const uid = Number(params.get('open'));
6142
+ const path = params.get('folder');
6143
+ const account = params.get('account');
6144
+ if (!Number.isInteger(uid) || uid <= 0 || !path) return;
6145
+ if (account && account !== currentAccountId) {
6146
+ if (!mailboxes.some(b => b.id === account)) {
6147
+ E.toast('That message is in a mailbox that is not yours.', {type: 'warning'});
6148
+ return;
6149
+ }
6150
+ await selectAccount(account);
6151
+ if (!here()) return;
6152
+ }
6153
+ const folder = folders.find(f => f.path === path);
6154
+ if (folder && folder.path !== currentFolder) {
6155
+ await selectFolder(folder);
6156
+ if (!here()) return;
6157
+ }
6158
+ await openMessage(uid, path);
6159
+ }
6160
+
6129
6161
  if (features.send) {
6130
6162
  const compose = $container.find('#mail-compose-btn').get(0);
6131
6163
  if (compose && bannerCompose) compose.remove();
@@ -6177,6 +6209,8 @@ return {
6177
6209
 
6178
6210
  await start();
6179
6211
  if (!here()) return;
6212
+ await takeOpenRequest();
6213
+ if (!here()) return;
6180
6214
  await takeComposeRequest();
6181
6215
  // The sidebar updates independently; keep an open inbox in step with
6182
6216
  // the mirror without interrupting selection, search or older pages.
@@ -147,6 +147,32 @@ export function messageId(accountId, path, uid) {
147
147
  return `${accountId}:${path}:${uid}`;
148
148
  }
149
149
 
150
+ /**
151
+ * The Mongo filter for "mail to or from any of these people".
152
+ *
153
+ * Whole-address and case-insensitive: Sam@ and sam@ are one person, but
154
+ * sam@example.com must not match pam.sam@example.com - so each address is an
155
+ * anchored, escaped pattern rather than the substring the search box uses.
156
+ * Blank and duplicate addresses are dropped; none left = null, and the
157
+ * caller asks nothing.
158
+ *
159
+ * @param {string[]} accountIds
160
+ * @param {string[]} addresses
161
+ * @returns {object|null}
162
+ */
163
+ export function addressFilter(accountIds, addresses) {
164
+ const ids = [...new Set((accountIds ?? []).filter(id => typeof id === 'string' && id))];
165
+ const wanted = [...new Set((addresses ?? [])
166
+ .map(a => String(a ?? '').trim().toLowerCase())
167
+ .filter(a => a.includes('@')))];
168
+ if (!ids.length || !wanted.length) return null;
169
+ const patterns = wanted.map(a => new RegExp(`^${a.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i'));
170
+ return {
171
+ accountId: {$in: ids},
172
+ $or: ['from', 'to', 'cc'].map(field => ({[`${field}.address`]: {$in: patterns}}))
173
+ };
174
+ }
175
+
150
176
  /**
151
177
  * Create a store bound to one connection.
152
178
  *
@@ -800,6 +826,38 @@ export function createStore(connection) {
800
826
  .toArray();
801
827
  }
802
828
 
829
+ /**
830
+ * The newest mirrored messages to or from any of these addresses, across
831
+ * the given mailboxes - a contact's emails, for the card in Contacts.
832
+ * Headers only; never a body. The same message filed in two folders (or
833
+ * sent to yourself) appears once, by its Message-ID.
834
+ *
835
+ * @param {string[]} accountIds
836
+ * @param {string[]} addresses
837
+ * @param {{limit?: number}} [options]
838
+ * @returns {Promise<{accountId: string, folder: string, uid: number, subject: string, from: object[], to: object[], date: string|null, seen: boolean}[]>}
839
+ */
840
+ async function listByAddresses(accountIds, addresses, {limit = 10} = {}) {
841
+ const filter = addressFilter(accountIds, addresses);
842
+ if (!filter) return [];
843
+ const database = await connect();
844
+ const rows = await database.collection(MESSAGES)
845
+ .find(filter, {projection: {_id: 0, accountId: 1, folder: 1, uid: 1, subject: 1, from: 1, to: 1, date: 1, seen: 1, messageId: 1}})
846
+ .sort({date: -1, uid: -1})
847
+ .limit(limit * 2)
848
+ .toArray();
849
+ const seen = new Set();
850
+ const out = [];
851
+ for (const row of rows) {
852
+ const key = row.messageId || `${row.accountId}:${row.folder}:${row.uid}`;
853
+ if (seen.has(key)) continue;
854
+ seen.add(key);
855
+ out.push(row);
856
+ if (out.length >= limit) break;
857
+ }
858
+ return out;
859
+ }
860
+
803
861
  /** Folders already backfilled in this process, so it runs at most once each. */
804
862
  const backfilled = new Set();
805
863
 
@@ -1023,7 +1081,7 @@ export function createStore(connection) {
1023
1081
  connect,
1024
1082
  getFolder, saveFolder,
1025
1083
  putMessages, setFlags, setMessageAction, removeMessages, clearFolder,
1026
- storedUids, listMessages, listThreads, searchMessages, countUnread, listUnread, forgetAccount,
1084
+ storedUids, listMessages, listThreads, searchMessages, countUnread, listUnread, listByAddresses, forgetAccount,
1027
1085
  queueSend, listQueuedSends, claimDueSends, finishSend, cancelSend, getQueuedSend,
1028
1086
  queueSnooze, listSnoozed, claimDueSnoozes, finishSnooze, wakeSnooze,
1029
1087
  lastAutoReply, noteAutoReply, listAutoReplies, clearAutoReplies,