domma-cms 1.8.11 → 1.8.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.12] - 2026-10-16
4
+
5
+ - **Import from WordPress asks before changing a home page an earlier import set.** Until now the home page was set by the first import only, and a later import - of another WordPress site, or of the same one after its home page changed - quietly left it alone. Now, when an earlier import set the home page and this export's home page is a different page, the preview's Home page card asks, for example, "Make 'Welcome' the home page instead of 'Home'?". There is no default: Run import stays greyed out, with the reason beside it, until you choose. Yes does what the first import did - the current home page moves to /previous-home (or -2, -3…) as a draft, nothing is deleted, and the new page's old addresses lead to /. No imports the page at its own address and leaves the home page as it is. Running the same export again with the same home page asks nothing. A run started without the answer (from the API, say) is refused with a plain message.
6
+ - **Moving the home page no longer hides the next one.** Moving the home page to another address in the page editor made SEO add a redirect from / to the new address, so any page later put at / was never shown - visitors were sent to the old one. / is now never the source of an automatic redirect, and a published page at / (made there, moved there, or published there) removes any redirect from / - the same way a page moved to any address removes a redirect from that address. A redirect from / that an earlier version added this way is removed when the site starts, if there is a page at /; one you set up yourself is left until a published page is put at / (a draft there does not count, since visitors cannot see it).
7
+
3
8
  ## [1.8.11] - 2026-10-16
4
9
 
5
10
  - **Import from WordPress sets the home page.** A WordPress export does not say which page was the home page, but it gives each page's address, and the home page's address is the site's own - so that page is found, and only that one (a page merely called "Home" is just a page). The preview's new Home page card says which page WordPress showed at its home address, what this site's home page is now, and what will happen. By default the WordPress home page becomes this site's home page; the page that was there is never deleted or overwritten - it moves to /previous-home as a draft, and the preview and the report say so, with a link to it. Menus that lead to / still do, nothing redirects to the old page, and the WordPress home page's own old address (/home/, say) and its ?page_id= links lead to /. This happens once: run the import again and the home page is updated where it is, left as it is if you edited it here (unless you choose Overwrite), and left alone altogether if you have changed the home page yourself since - the report says so.
package/CLAUDE.md CHANGED
@@ -351,6 +351,11 @@ A built-in Tool (`wordpress-import` in `services/tools.js`, permission `import.w
351
351
  - **Posts go to a Blog by feature, not by name** (`blogTarget()`): a switched-on plugin that ships `blog-posts`,
352
352
  with the fields a post needs; comments only when `blog-comments` has the Blog's shape. Otherwise posts become
353
353
  pages (under /blog, or /posts when a Blog is on).
354
+ - **"/" is never a redirect source**: `onMoved()` (seo-redirects.js) adds nothing when a page leaves "/" (the page
355
+ editor's move, any `renamePage`), and SEO drops an exact rule from "/" when a PUBLISHED page is created, saved or renamed
356
+ to "/" (even with `redirect: false` or onRename off; a draft there 404s for the public, so the rule stays) - a page wins at its address, as `onMoved` already did for the
357
+ address a page moves to. At start an `auto` rule from "/" is removed if a published page is there (older versions made
358
+ them); a hand-made one stays until a page is put at "/".
354
359
  - **Redirects**: path ones go into SEO's store (`seoEngine().addRule`); `/?p=`, `/?page_id=`, `/?attachment_id=`
355
360
  are answered by the importer's own `onRequest` hook from `map.json`, because SEO's rules match paths only. That
356
361
  hook is registered whatever the Tool switch says (the redirects belong to the migrated site) and re-reads
@@ -422,8 +427,15 @@ A built-in Tool (`wordpress-import` in `services/tools.js`, permission `import.w
422
427
  `/` to `/previous-home` (`freePath`, so `-2`…) as a draft (`runHome()` in index.js, before the Pages phase) via
423
428
  `renamePage(…, {redirect: false, source: 'wordpress-import'})` - `redirect: false` stops SEO's onRename rule (it would
424
429
  redirect `/` itself) and menus are NOT relinked (`/` stays `/`). `mapping.items.home` records it; while it exists -
425
- in ANY source's mapping (`site.homeSetBy`: once per site, not per export) - nothing moves again (`claimHome()` is the
426
- one place that decides a move). If the new home page is not made, `putHomeBack()` returns the old one to `/`. A
430
+ in ANY source's mapping (`site.homeSetBy`, the latest by `at`; the newer of it and this source's own is `homeRec`) -
431
+ nothing moves again without asking (`claimHome()` is the one place that decides a move). **The question**: when
432
+ `homeRec` exists, "/" has a page, and this export's front page is not the page `homeRec` names (another source, or
433
+ this source's record has another `wpId`), `plan.home.question` = `{title, current, answer}` and nothing moves until
434
+ `options.homeReplace` answers it - no default; `startRun()` refuses an unanswered one with 400 and the screen
435
+ disables Run (`runWhy`). Yes = `claimHome(true)` BEFORE the pages loop (so this source's own page at "/" is planned
436
+ as a `keep` row with `movedAside` at the new address, never over "/"), and `runHome()` moves again despite the
437
+ record; the record it replaces is put back if the run cannot make the new home (`priorHome`). No = the old
438
+ behaviour, the page at its own address, `plan.home.changed` says so. If the new home page is not made, `putHomeBack()` returns the old one to `/`. A
427
439
  password-protected front page is not offered (it would arrive as a draft); a posts page an earlier run imported as a
428
440
  page is not offered either (a redirect would hide it). A home page changed by hand since is left
429
441
  and `plan.home.changed` says so. The front page's slug address and `?page_id=` lead to `/`. The posts page is only a
@@ -1 +1 @@
1
- export const CODES=[301,302,307,308,410],MAX_RULES=5e3;export function normalisePath(n){let e=String(n??"").trim();if(!e)return"";try{e=decodeURI(e)}catch{}return e=e.split("#")[0].split("?")[0],e.startsWith("/")||(e=`/${e}`),e=e.replace(/\/{2,}/g,"/"),e.length>1&&(e=e.replace(/\/+$/,"")),e.toLowerCase()}const u=n=>String(n).endsWith("/*"),f=n=>/^https?:\/\//i.test(String(n));export function checkRule(n={},e=[]){const t={},l=Number(n.code??301),s=String(n.from??"").trim(),o=u(s),r=o?`${normalisePath(s.slice(0,-2))==="/"?"":normalisePath(s.slice(0,-2))}/*`:normalisePath(s);let i=String(n.to??"").trim();if(CODES.includes(l)||(t.code="Choose 301, 302, 307, 308 or 410."),s?/^https?:\/\//i.test(s)?t.from="The address to redirect is a path on this site, like /old-page.":/^\/(admin|api)(\/|$)/.test(r)?t.from="The admin and the API cannot be redirected.":r==="/*"&&(t.from="That would redirect the whole site."):t.from="Say which address to redirect.",l===410)i="";else if(!i)t.to="Say where it goes.";else if(f(i))try{new URL(i.replace("*","x"))}catch{t.to="Not a web address."}else if(/^[a-z][a-z0-9+.-]*:/i.test(i)||i.startsWith("//"))t.to="A path on this site (/new-page) or an http(s) address.";else{const[a,c]=i.split("?");i=normalisePath(a)+(c?`?${c}`:""),i.includes("*")&&!o&&(t.to='A "*" in the target needs a "/*" rule to fill it.')}if(!t.from&&!t.to&&l!==410&&!f(i)){const a=normalisePath(i.split("?")[0]);!o&&a===r?t.to="It would redirect to itself.":!o&&h(r,a,e)&&(t.to="That makes a loop with another redirect.")}return!t.from&&e.some(a=>a.from===r)&&(t.from=`There is already a redirect from ${r}.`),{errors:t,rule:{from:r,to:i,code:l,note:String(n.note??"").trim().slice(0,200),...n.auto===!0&&{auto:!0}}}}function h(n,e,t){const l=new Set([n]);let s=e;for(let o=0;o<20;o++){if(l.has(s))return!0;l.add(s);const r=matchRedirect(t,s);if(!r||!r.location||f(r.location))return!1;s=normalisePath(r.location.split("?")[0])}return!0}export function matchRedirect(n,e){const[t,l]=String(e??"").split("?"),s=normalisePath(t);if(!s)return null;let o=null,r="";for(const a of n)if(a.from===s){o=a,r="";break}if(!o){let a=-1;for(const c of n){if(!u(c.from))continue;const d=c.from.slice(0,-2);(s===d||s.startsWith(`${d}/`))&&d.length>a&&(a=d.length,o=c,r=s.slice(d.length+1))}}if(!o)return null;if(o.code===410)return{rule:o,location:"",code:410};let i=o.to.includes("*")?o.to.replace("*",r):o.to;return f(i)||(i=i.replace(/\/{2,}/g,"/").replace(/(.)\/$/,"$1")),l&&!i.includes("?")&&(i+=`?${l}`),{rule:o,location:i,code:o.code}}export function onMoved(n,e,t,{now:l=new Date().toISOString(),id:s}={}){const o=normalisePath(e),r=normalisePath(t);if(!o||!r||o===r)return n;const i=n.filter(c=>c.from!==r).map(c=>normalisePath(String(c.to).split("?")[0])===o&&!f(c.to)?{...c,to:r}:c),a=i.find(c=>c.from===o);return a?i.map(c=>c===a?{...c,to:r,code:301}:c):[...i,{id:s||`r${Date.now().toString(36)}`,from:o,to:r,code:301,note:"The page moved",auto:!0,hits:0,lastHit:null,at:l}]}export function chains(n){const e=[];for(const t of n){if(t.code===410||f(t.to)||u(t.from))continue;const l=matchRedirect(n,t.to);l&&l.rule!==t&&e.push({rule:t,via:l.rule})}return e}
1
+ export const CODES=[301,302,307,308,410],MAX_RULES=5e3;export function normalisePath(i){let e=String(i??"").trim();if(!e)return"";try{e=decodeURI(e)}catch{}return e=e.split("#")[0].split("?")[0],e.startsWith("/")||(e=`/${e}`),e=e.replace(/\/{2,}/g,"/"),e.length>1&&(e=e.replace(/\/+$/,"")),e.toLowerCase()}const u=i=>String(i).endsWith("/*"),f=i=>/^https?:\/\//i.test(String(i));export function checkRule(i={},e=[]){const t={},l=Number(i.code??301),c=String(i.from??"").trim(),o=u(c),r=o?`${normalisePath(c.slice(0,-2))==="/"?"":normalisePath(c.slice(0,-2))}/*`:normalisePath(c);let n=String(i.to??"").trim();if(CODES.includes(l)||(t.code="Choose 301, 302, 307, 308 or 410."),c?/^https?:\/\//i.test(c)?t.from="The address to redirect is a path on this site, like /old-page.":/^\/(admin|api)(\/|$)/.test(r)?t.from="The admin and the API cannot be redirected.":r==="/*"&&(t.from="That would redirect the whole site."):t.from="Say which address to redirect.",l===410)n="";else if(!n)t.to="Say where it goes.";else if(f(n))try{new URL(n.replace("*","x"))}catch{t.to="Not a web address."}else if(/^[a-z][a-z0-9+.-]*:/i.test(n)||n.startsWith("//"))t.to="A path on this site (/new-page) or an http(s) address.";else{const[a,s]=n.split("?");n=normalisePath(a)+(s?`?${s}`:""),n.includes("*")&&!o&&(t.to='A "*" in the target needs a "/*" rule to fill it.')}if(!t.from&&!t.to&&l!==410&&!f(n)){const a=normalisePath(n.split("?")[0]);!o&&a===r?t.to="It would redirect to itself.":!o&&h(r,a,e)&&(t.to="That makes a loop with another redirect.")}return!t.from&&e.some(a=>a.from===r)&&(t.from=`There is already a redirect from ${r}.`),{errors:t,rule:{from:r,to:n,code:l,note:String(i.note??"").trim().slice(0,200),...i.auto===!0&&{auto:!0}}}}function h(i,e,t){const l=new Set([i]);let c=e;for(let o=0;o<20;o++){if(l.has(c))return!0;l.add(c);const r=matchRedirect(t,c);if(!r||!r.location||f(r.location))return!1;c=normalisePath(r.location.split("?")[0])}return!0}export function matchRedirect(i,e){const[t,l]=String(e??"").split("?"),c=normalisePath(t);if(!c)return null;let o=null,r="";for(const a of i)if(a.from===c){o=a,r="";break}if(!o){let a=-1;for(const s of i){if(!u(s.from))continue;const d=s.from.slice(0,-2);(c===d||c.startsWith(`${d}/`))&&d.length>a&&(a=d.length,o=s,r=c.slice(d.length+1))}}if(!o)return null;if(o.code===410)return{rule:o,location:"",code:410};let n=o.to.includes("*")?o.to.replace("*",r):o.to;return f(n)||(n=n.replace(/\/{2,}/g,"/").replace(/(.)\/$/,"$1")),l&&!n.includes("?")&&(n+=`?${l}`),{rule:o,location:n,code:o.code}}export function onMoved(i,e,t,{now:l=new Date().toISOString(),id:c}={}){const o=normalisePath(e),r=normalisePath(t);if(!o||!r||o===r)return i;if(o==="/")return i.filter(s=>s.from!==r);const n=i.filter(s=>s.from!==r).map(s=>normalisePath(String(s.to).split("?")[0])===o&&!f(s.to)?{...s,to:r}:s),a=n.find(s=>s.from===o);return a?n.map(s=>s===a?{...s,to:r,code:301}:s):[...n,{id:c||`r${Date.now().toString(36)}`,from:o,to:r,code:301,note:"The page moved",auto:!0,hits:0,lastHit:null,at:l}]}export function chains(i){const e=[];for(const t of i){if(t.code===410||f(t.to)||u(t.from))continue;const l=matchRedirect(i,t.to);l&&l.rule!==t&&e.push({rule:t,via:l.rule})}return e}
@@ -1 +1 @@
1
- export const KIND_LABEL={page:"Page",post:"Post",product:"Product",custom:"Custom item",collection:"Collection",media:"Media",menu:"Menu",category:"Category",widgets:"Widget area"},KIND_PLURAL={page:"Pages",post:"Posts",product:"Products",custom:"Custom items",collection:"Collections",media:"Media",menu:"Menus",category:"Categories",widgets:"Widgets"},KIND_ICON={page:"file-text",post:"edit-3",product:"shopping-bag",custom:"box",collection:"database",media:"image",menu:"menu",category:"tag",widgets:"layout"},PLACE_TEXT={add:"Goes in the footer, after what is there now",there:"Already in the footer",off:"Not put in the footer (you chose this)",removed:"Not put back in the footer - it was taken out here since the last import",full:"Not put in the footer - it already has 8 blocks; make room in Site Settings \u203A Footer"},TYPE_AS={collection:{text:"Collection",tone:"ok"},pages:{text:"Pages",tone:"info"},skip:{text:"Skipped",tone:"muted"}},PRODUCT_TYPE_LABEL={simple:"Simple",variable:"With options",grouped:"Grouped",external:"External"},ACTION={create:{text:"New",tone:"ok"},rename:{text:"New, renamed",tone:"warn"},update:{text:"Update",tone:"info"},keep:{text:"Left as is",tone:"muted"},skip:{text:"Skipped",tone:"muted"}},RESULT={created:{text:"Made",tone:"ok"},updated:{text:"Updated",tone:"info"},kept:{text:"Left as is",tone:"muted"},skipped:{text:"Skipped",tone:"muted"},failed:{text:"Failed",tone:"bad"}},PLAN_FILTERS=[{id:"all",label:"All"},{id:"new",label:"New"},{id:"update",label:"Updates"},{id:"renamed",label:"Renamed"},{id:"keep",label:"Left as is"},{id:"skip",label:"Skipped"},{id:"rebuild",label:"Needs rebuilding"}],REPORT_FILTERS=[{id:"all",label:"All"},{id:"created",label:"Made"},{id:"updated",label:"Updated"},{id:"kept",label:"Left as is"},{id:"skipped",label:"Skipped"},{id:"failed",label:"Failed"},{id:"rebuild",label:"Needs rebuilding"}];const f=e=>!!(e.builder||e.needs||e.shortcodes?.length),h=e=>[e.builder?`Built with ${e.builder}`:"",e.needs||"",...(e.shortcodes||[]).map(t=>`[${t}]`)].filter(Boolean).join(" ");export function planMatches(e,t){switch(t){case"new":return e.action==="create"||e.action==="rename";case"update":return e.action==="update";case"renamed":return e.action==="rename"||!!e.replaces;case"keep":return e.action==="keep";case"skip":return e.action==="skip";case"rebuild":return f(e);default:return!0}}export function reportMatches(e,t){return t==="rebuild"?f(e):t==="all"||!t||e.result===t}export function counts(e,t,o,s=""){const n=e.filter(i=>!s||i.kind===s);return Object.fromEntries(t.map(i=>[i.id,n.filter(a=>o(a,i.id)).length]))}export function kindTabs(e){const t=["page","post","product","custom","collection","media","menu","category","widgets"],o={};for(const s of e)o[s.kind]=(o[s.kind]||0)+1;return[{id:"",label:"Everything",count:e.length},...t.filter(s=>o[s]).map(s=>({id:s,label:KIND_PLURAL[s],count:o[s]}))]}export function planRow(e){const t=ACTION[e.action]||ACTION.skip;let o=e.target?e.kind==="menu"?`Menu "${e.target}"`:e.kind==="collection"?`Collection "${e.target}"`:e.kind==="widgets"?`Block "${e.target}"`:e.target:"";e.kind==="widgets"&&e.place&&e.action!=="skip"&&(o=`${o}${o?" \xB7 ":""}${PLACE_TEXT[e.place]||""}`),e.kind==="custom"&&e.as==="collection"&&e.collection&&e.action!=="skip"&&(o=`In the collection "${e.collection}"${e.listAt?`, listed at ${e.listAt}`:""}`);const s=[e.kind==="custom"&&e.postType?`${KIND_LABEL.custom} (${e.postType})`:KIND_LABEL[e.kind]||e.kind];return e.listFor&&s.push("list page"),e.status&&(e.kind==="page"||e.kind==="post"||e.kind==="custom")&&s.push(e.status),e.kind==="collection"&&e.fields&&s.push(`${e.fields} fields`),e.kind==="product"&&(e.productType&&s.push(PRODUCT_TYPE_LABEL[e.productType]||e.productType),e.variants&&s.push(`${e.variants} option${e.variants===1?"":"s"}`),e.action!=="skip"&&e.status&&s.push(e.status==="active"?"on sale":"draft")),e.comments&&s.push(`${e.comments} comment${e.comments===1?"":"s"}`),e.count&&s.push(e.kind==="widgets"?`${e.count} widget${e.count===1?"":"s"}`:`${e.count} item${e.count===1?"":"s"}`),e.kind==="widgets"&&e.widgetTypes?.length&&s.push(e.widgetTypes.join(", ")),{key:e.key,kind:e.kind,icon:KIND_ICON[e.kind]||"file",title:e.title||"(untitled)",pill:e.replaces?"Replaces a page":t.text,pillClass:`is-${e.replaces?"warn":t.tone}`,where:o,meta:s.join(" \xB7 "),reason:e.reason||"",rebuild:h(e),chosen:!!e.choice}}export function reportRow(e){const t=RESULT[e.result]||RESULT.skipped;return{key:e.key,kind:e.kind,icon:KIND_ICON[e.kind]||"file",title:e.title||"(untitled)",pill:t.text,pillClass:`is-${t.tone}`,where:e.target||"",meta:[KIND_LABEL[e.kind]||e.kind,e.format==="html"?"kept as HTML":"",e.kind==="widgets"&&e.placed?"added to the footer":""].filter(Boolean).join(" \xB7 "),reason:[e.reason,...e.notes||[]].filter(Boolean).join(" "),rebuild:h(e)}}export function choiceItems(e,t){const o=[];return t&&o.push({id:"reset",label:"Undo my choice",icon:"rotate-ccw"}),t!=="skip"&&e.action!=="skip"&&e.action!=="keep"&&o.push({id:"skip",label:"Skip this",icon:"x-circle"}),t!=="overwrite"&&e.action==="keep"&&o.push({id:"overwrite",label:"Overwrite my changes here",icon:"refresh-cw"}),t!=="overwrite"&&e.action==="rename"&&(e.kind==="page"||e.kind==="post")&&e.wanted&&e.kind==="page"&&o.push({id:"overwrite",label:`Replace the page at ${e.wanted}`,icon:"refresh-cw"}),t!=="import"&&e.action==="skip"&&/Deleted (on this site|from the Blog|from the Shop|from the collection)/.test(e.reason||"")&&o.push({id:"import",label:"Import again",icon:"download"}),o}export function applyChoice(e,t,o){const s={...e||{}};return!o||o==="reset"?delete s[t]:s[t]=o,s}export function defaultOptions(e=!1){return{posts:e?"blog":"pages",media:"download",comments:!0,menus:!0,redirects:!0,drafts:!0,products:!0,types:{},listPages:{},choices:{},users:{},userRole:"",invite:!1,footer:{},home:!0,postsPage:0,latestPosts:!1}}export function cleanOptions(e={},t=!1){const o=defaultOptions(t),s=(n,i,a)=>i.includes(n)?n:a;return{posts:s(e.posts,t?["blog","pages","skip"]:["pages","skip"],o.posts),media:s(e.media,["download","zip","skip"],o.media),comments:e.comments!==!1,menus:e.menus!==!1,redirects:e.redirects!==!1,drafts:e.drafts!==!1,products:e.products!==!1,types:Object.fromEntries(Object.entries(e.types||{}).filter(([n,i])=>m.test(n)&&(i==="pages"||i==="skip"))),listPages:Object.fromEntries(Object.entries(e.listPages||{}).filter(([n,i])=>m.test(n)&&i===!1)),choices:Object.fromEntries(Object.entries(e.choices||{}).filter(([n,i])=>typeof n=="string"&&["skip","overwrite","import"].includes(i))),users:Object.fromEntries(Object.entries(e.users||{}).filter(([n,i])=>w.test(n)&&(i==="create"||i==="skip"))),userRole:typeof e.userRole=="string"&&b.test(e.userRole)?e.userRole:"",invite:e.invite===!0,footer:Object.fromEntries(Object.entries(e.footer||{}).filter(([n,i])=>g.test(n)&&i===!1)),home:e.home!==!1,postsPage:Number.isInteger(e.postsPage)&&e.postsPage>0?e.postsPage:0,latestPosts:e.latestPosts===!0}}const g=/^[A-Za-z0-9_-]{1,100}$/;export function withFooterChoice(e,t,o){const s={...e.footer||{}};return o?delete s[t]:s[t]=!1,{...e,footer:s}}export function optionsSnapshot(e){const t=cleanOptions(e,!0),o=Object.keys(t.choices).sort().map(l=>`${l}=${t.choices[l]}`).join(","),s=Object.keys(t.types).sort().map(l=>`${l}=${t.types[l]}`).join(","),n=Object.keys(t.listPages).sort().join(","),i=Object.keys(t.users).sort().map(l=>`${l}=${t.users[l]}`).join(","),a=Object.keys(t.footer).sort().join(",");return[t.posts,t.media,t.comments,t.menus,t.redirects,t.drafts,t.products,o,s,n,i,t.userRole,t.invite,a,t.home,t.postsPage,t.latestPosts].join("|")}const m=/^[a-z0-9_-]{1,40}$/;export function withTypeChoice(e,t,o){const s={...e.types||{}};return o==="pages"||o==="skip"?s[t]=o:delete s[t],{...e,types:s}}export function withListPage(e,t,o){const s={...e.listPages||{}};return o?delete s[t]:s[t]=!1,{...e,listPages:s}}export const isDirty=(e,t)=>optionsSnapshot(e)!==optionsSnapshot(defaultOptions(t)),draftKey=e=>e?`wordpress-import.draft.${e}`:"";export function liveDrafts(e,t,o){if(!e||typeof e!="object"||!e.items||typeof e.items!="object")return{items:{},pruned:!1};if(e.owner&&o&&e.owner!==o)return{items:{},pruned:!1};const s={};let n=!1;for(const[i,a]of Object.entries(e.items))t.includes(i)&&a?.options&&a.was===i?s[i]=a:n=!0;return{items:s,pruned:n}}export function withDraft(e,t,o,s){const n={...e&&e.owner===t&&e.items||{}};return s?n[o]=s:delete n[o],{owner:t,items:n}}export function draftQuestion({when:e="",file:t=""}={}){return{title:"Choices you left last time",text:`You changed the import options${t?` for ${t}`:""}${e?` (${e})`:""} and left before running it. Throw away those choices and start from the defaults?`,confirmText:"Throw away",cancelText:"Keep them"}}export function progressText(e){if(!e)return"";const t=e.total?` \xB7 ${e.done} of ${e.total}`:"";return`${e.phase||"Working"}${t}${e.current?` \xB7 ${String(e.current).slice(0,80)}`:""}`}export const progressPercent=e=>e?.total?Math.min(100,Math.round(e.done/e.total*100)):0;export function planTiles(e){const t=e?.summary?.counts||{},o=s=>t[s]?t[s].total-t[s].skip:0;return[{id:"page",label:"Pages",value:o("page"),of:t.page?.total||0},{id:"post",label:"Posts",value:o("post"),of:t.post?.total||0},...t.product?[{id:"product",label:"Products",value:o("product"),of:t.product.total}]:[],...t.custom?[{id:"custom",label:"Custom items",value:o("custom"),of:t.custom.total}]:[],{id:"media",label:"Media",value:o("media"),of:t.media?.total||0},{id:"menu",label:"Menus",value:o("menu"),of:t.menu?.total||0},...e?.people?.rows?.length?[{id:"people",label:"New accounts",value:e.people.counts?.create||0,of:0}]:[],...t.widgets?[{id:"widgets",label:"Widget areas",value:o("widgets"),of:t.widgets.total}]:[],{id:"comments",label:"Comments",value:e?.summary?.comments||0,of:0},{id:"redirects",label:"Redirects",value:e?.summary?.redirects||0,of:0}].map(s=>({...s,sub:s.of&&s.of!==s.value?`of ${s.of}`:""}))}const w=/^author:(\d{1,12}|login-[\w.-]{1,60})$/,b=/^[a-z0-9][a-z0-9_-]{0,59}$/i;export const PERSON={create:{text:"New account",tone:"ok"},match:{text:"Matched",tone:"info"},linked:{text:"Linked before",tone:"info"},same:{text:"Same email",tone:"muted"},skip:{text:"No account",tone:"muted"}};export function creditText(e={}){const t=(o,s,n)=>e[o]?`${e[o]} ${e[o]===1?s:n}`:"";return[t("post","post","posts"),t("page","page","pages"),t("product","product","products"),t("custom","item","items")].filter(Boolean).join(", ")||"nothing in this import"}export function personRow(e){const t=PERSON[e.action]||PERSON.skip,o=e.problem==="email";return{key:e.key,name:e.name||"Author",meta:[e.email||(o?"no usable email":""),creditText(e.items)].filter(Boolean).join(" \xB7 "),reason:e.reason||"",pill:o?"No email":t.text,pillClass:`is-${o?"warn":t.tone}${e.choice?" is-chosen":""}`,choosable:!!e.choosable,segCreate:e.action==="create"?"is-on":"",segSkip:e.action==="skip"?"is-on":""}}export function chooseAllPeople(e,t){const o={};for(const s of e?.rows||[])s.choosable&&(o[s.key]=t);return o}export function choosePerson(e,t,o){const s={...e||{}},n=t.total?"create":"skip";return!o||o==="reset"||o===n?delete s[t.key]:s[t.key]=o,s}export function personMenuItems(e){const t=[];return e.choosable&&e.action!=="create"&&t.push({id:"create",label:"Make a new account",icon:"user-plus"}),e.choosable&&e.action!=="skip"&&t.push({id:"skip",label:"Do not make an account",icon:"user-x"}),e.choice&&t.push({id:"reset",label:"Undo my choice",icon:"rotate-ccw"}),e.email&&e.problem!=="email"&&t.push({id:"copy",label:"Copy the email address",icon:"copy"}),t.length||t.push({id:"none",label:"Nothing to choose for them",icon:"info",disabled:!0}),t}export function peopleSummary(e){const t=e?.counts||{};if(!e?.rows?.length)return{text:"This export names no authors.",tone:"info"};const o=[];t.create&&o.push(`${t.create} new account${t.create===1?"":"s"}`),t.match+t.linked&&o.push(`${t.match+t.linked} matched to ${t.match+t.linked===1?"a user":"users"} here`),t.skip&&o.push(`${t.skip} without an account`);let s=`${e.rows.length} author${e.rows.length===1?"":"s"}: ${o.join(", ")||"nothing to do"}.`;return t.create&&(s+=" New accounts have no password - nobody can sign in to one until a password is set."),!e.canCreate&&e.why&&(s+=` ${e.why}`),{text:s,tone:t.problems||!e.canCreate?"warn":"info"}}export function inviteNote(e){return e?.canCreate?e.mailReady?e.counts?.create?"Sent once the import has finished: an invitation saying an account has been made for them, with a link to set their password that works for 7 days.":"Nobody new to email.":"No mail server is set up (Settings \u203A Email), so this cannot be sent. After the import, use Users \u203A \u22EE \u203A Copy a reset link instead.":""}export function reportPersonRow(e){const t={created:{text:"Account made",tone:"ok"},matched:{text:"Matched",tone:"info"},linked:{text:"Linked before",tone:"info"},same:{text:"Same email",tone:"muted"},skipped:{text:"No account",tone:"muted"},failed:{text:"Failed",tone:"bad"}},o=t[e.result]||t.skipped,s=[creditText(e.items)];return e.result==="created"&&s.push(e.invited?"invited by email":e.inviteFailed?"the invitation was not sent":"no password set yet"),{key:e.key,name:e.name||"Author",meta:s.join(" \xB7 "),reason:e.reason||"",pill:o.text,pillClass:`is-${o.tone}`,userId:e.userId||""}}export function historyRow(e,t=o=>o){const o=e.summary||{},s=Object.values(o.kinds||{}).reduce((a,l)=>a+(l.created||0),0),n=Object.values(o.kinds||{}).reduce((a,l)=>a+(l.updated||0),0),i=[];return s&&i.push(`${s} made`),o.people?.created&&i.push(`${o.people.created} account${o.people.created===1?"":"s"}`),n&&i.push(`${n} updated`),o.redirects?.created&&i.push(`${o.redirects.created} redirect${o.redirects.created===1?"":"s"}`),o.rebuild&&i.push(`${o.rebuild} to rebuild`),o.failed&&i.push(`${o.failed} failed`),e.status==="failed"&&i.splice(0,i.length,"Stopped before the end - run it again to finish"),{id:e.id,title:e.siteTitle||e.source||"WordPress site",meta:[t(e.at),e.by,e.file].filter(Boolean).join(" \xB7 "),summary:i.join(", ")||"Nothing changed",pill:e.status==="failed"?"Stopped":e.status==="partial"?"Some failed":"Done",pillClass:e.status==="failed"||e.status==="partial"?"is-bad":"is-ok"}}export function stagedRow(e,t=o=>o){const o=e.counts||{},s=[o.page&&`${o.page} pages`,o.post&&`${o.post} posts`,o.product&&`${o.product} products`,o.attachment&&`${o.attachment} media`,e.widgets&&`${e.widgets.widgets} widget${e.widgets.widgets===1?"":"s"}`].filter(Boolean);return{id:e.id,title:e.siteTitle||e.fileName||"WordPress export",meta:[e.fileName,t(e.at),s.join(", ")].filter(Boolean).join(" \xB7 "),pill:e.uploads?"Uploads zip added":e.widgets?"Widgets added":"Waiting",pillClass:e.uploads||e.widgets?"is-ok":"is-muted"}}export function productSummary(e){const t=e?.summary?.products;if(!t?.total)return null;const o=t.shop||e.shop||{},s=e.options?.products!==!1,n=Object.entries(t.types||{}).map(([d,u])=>`${u} ${(PRODUCT_TYPE_LABEL[d]||d).toLowerCase()}`).join(", "),i=e.summary.counts?.product||{},a=[{label:"In the export",value:`${t.total}${n?` (${n})`:""}`}];o.available&&s&&(a.push({label:"On sale in the Shop",value:String(t.onSale||0)}),a.push({label:"As drafts",value:String(t.drafts||0)}),i.keep&&a.push({label:"Left as is",value:String(i.keep)}),a.push({label:"Skipped",value:String(i.skip||0)})),t.rebuild&&a.push({label:"Need rebuilding",value:String(t.rebuild)});let l,r="info";return o.available?s?l=`Products go to the Shop at ${o.basePath||"/shop"}.`:l='Products are not being imported - switch on "WooCommerce products" above to bring them in.':(l=o.reason||"Switch on the Shop to bring these in.",r="warn"),{note:l,tone:r,rows:a,notes:Array.isArray(t.notes)?t.notes:[]}}const c=(e,t,o=`${t}s`)=>`${e} ${e===1?t:o}`;export function customTypeRows(e){return(e?.summary?.customTypes||[]).map(t=>{const o=TYPE_AS[t.as]?t.as:"collection",s=TYPE_AS[o],n=t.rewrite?`was at /${t.rewrite}/\u2026`:"its old addresses are not in the export";let i;if(o==="skip")i="Nothing - left out.";else if(o==="pages")i=`${c(t.importing||0,"page")} under ${t.prefix}${t.drafts?` (${t.drafts} as draft${t.drafts===1?"":"s"})`:""}.`;else{const a=t.collection||{},l=a.action==="skip"?`The collection "${a.slug}" was deleted here - choose Import again on it below.`:`${a.action==="update"?"The collection":"A new collection"} "${a.slug}" with ${c(t.fields?.length||0,"field")}: ${c(t.importing||0,"entry","entries")}${t.drafts?` (${t.drafts} draft${t.drafts===1?"":"s"}, not shown on the site)`:""}.`,r=t.listPage?t.listAt?` A list page at ${t.listAt}${t.listAction==="keep"?" (edited here - left as it is)":""}; the old addresses lead there.`:"":" No list page - the old addresses are not redirected.";i=`${l}${r}`}return{type:t.type,label:t.label||t.type,meta:[c(t.count||0,"item"),`"${t.type}"`,n].join(" \xB7 "),samples:(t.samples||[]).length?`For example: ${t.samples.join(", ")}`:"",creates:i,pill:s.text,pillClass:`is-${s.tone}`,as:o,listOn:o==="collection"&&t.listPage!==!1,canList:o==="collection",listLabel:`List page at ${t.prefix||`/${t.type}`}`,segCollection:o==="collection"?"is-on":"",segPages:o==="pages"?"is-on":"",segSkip:o==="skip"?"is-on":""}})}export function internalTypeRows(e){return(e?.summary?.internalTypes||[]).map(t=>({type:t.type,text:`${t.type} (${t.count}) - ${t.reason}`}))}export function typeMenuItems(e){if(!e)return[];const t=[];return e.as!=="collection"&&t.push({id:"collection",label:"Import as a collection",icon:"database"}),e.as!=="pages"&&t.push({id:"pages",label:"Import as pages",icon:"file-text"}),e.as!=="skip"&&t.push({id:"skip",label:"Leave this type out",icon:"x-circle"}),e.as==="collection"&&t.push(e.listOn?{id:"nolist",label:"No list page",icon:"eye-off"}:{id:"list",label:"Make a list page",icon:"list"}),t.push({id:"show",label:"Show its items",icon:"filter"}),t}export function widgetAreaRows(e){return(e?.summary?.widgets?.areas||[]).map(t=>{const o=ACTION[t.action]||ACTION.skip,s=t.action==="skip"?"Not imported.":`${t.action==="update"?"Updates the block":t.action==="keep"?"The block (edited here - left as it is)":"A new block"} "${t.block}"${t.coming<t.count?` with ${c(t.coming,"widget")}`:""}.`;return{area:t.id,label:t.label||t.id,meta:[c(t.count||0,"widget"),`"${t.id}"`,(t.types||[]).join(", ")].filter(Boolean).join(" \xB7 "),creates:s,placeText:t.footer?PLACE_TEXT[t.place]||"":t.action==="skip"?"":"A sidebar - a block only; put it on a page with [block].",rebuild:(t.rebuild||[]).length?`Needs rebuilding: ${t.rebuild.join("; ")}`:"",pill:o.text,pillClass:`is-${o.tone}`,canPlace:!!t.footer&&t.action!=="skip"&&t.place!=="there"&&t.place!=="removed",placeOn:t.place==="add"}})}export function widgetsFileText(e,t=0){const o=e?.widgets;return o?`${o.fileName} - ${c(o.areas||0,"widget area")}, ${c(o.widgets||0,"widget")}.`:`Optional: the widgets, as the .wie file the free "Widget Importer & Exporter" WordPress plugin exports${t?` (up to ${Math.max(1,Math.round(t/1048576))} MB)`:""}.`}const p=e=>`"${String(e||"").trim()||"Untitled"}"`;export function homeCard(e){const t=e?.home;if(!t)return null;const o=e.options||{},s=[];let n;t.how==="page"&&t.page?n=`WordPress's home page was the page ${p(t.page.title)}${t.page.oldUrl?` (${t.page.oldUrl})`:""}.`:t.how==="unclear"?n=`${t.matches} pages had the home page's address in WordPress, so it cannot be told which one was the home page - none of them is made this site's home page.`:n="WordPress showed its latest posts on the home page (no page was set as the home page).";const i=t.now?`This site's home page now: ${p(t.now.title)}.`:"This site has no home page now.";let a="",l="info";t.done?.source?a=`An import of another WordPress site (${t.done.source}) set this site's home page - it is not changed.`:t.done?(a=t.done.how==="latest"?"An earlier import put the latest posts on the home page - it is not moved again.":"An earlier import made it this site's home page - it is not moved again.",t.done.previous&&(a+=` The home page from before is at ${t.done.previous}.`)):t.move?(a=`${t.how==="page"?p(t.page?.title):"A page showing the latest Blog posts"} becomes the home page. ${t.now?p(t.now.title):"The page at /"} moves to ${t.move.to} as a draft - nothing is deleted.`,l="warn"):t.page?.isHome||t.latest?.on?a=`${t.how==="page"?p(t.page?.title):"A page showing the latest Blog posts"} becomes the home page.`:t.how==="page"&&t.page?a=o.home===!1?`It is imported as an ordinary page${t.page.target?` at ${t.page.target}`:""} - this site's home page stays as it is.`:"":t.how==="latest"&&(a=t.latest?.offered?"This site's home page stays as it is unless you choose to show the latest posts on it.":"This site's home page stays as it is."),t.changed&&!t.done?.source&&(s.push(t.changed),l="warn");const r=t.postsPage;let d="";return r&&(d=r.confirmed?`${p(r.title)} was WordPress's posts page: it is not made, and its old address (and links and menus that pointed to it) lead to ${r.to}.`:`${p(r.title)} has nothing in it and is called like a blog - it was probably WordPress's posts page. If it was, it is not needed here: its address can lead to ${r.to} instead.`),{wp:n,now:i,after:a,tone:l,notes:s,frontSwitch:t.how==="page"&&!!t.page&&!t.done,frontLabel:"Make it this site's home page",latestSwitch:!!t.latest?.offered&&!t.done,postsSwitch:!!r,postsText:d,postsLabel:"It was the posts page - lead its address to the posts",postsId:r?.wpId||0,editNow:t.move&&t.now?"#/pages/edit/":""}}
1
+ export const KIND_LABEL={page:"Page",post:"Post",product:"Product",custom:"Custom item",collection:"Collection",media:"Media",menu:"Menu",category:"Category",widgets:"Widget area"},KIND_PLURAL={page:"Pages",post:"Posts",product:"Products",custom:"Custom items",collection:"Collections",media:"Media",menu:"Menus",category:"Categories",widgets:"Widgets"},KIND_ICON={page:"file-text",post:"edit-3",product:"shopping-bag",custom:"box",collection:"database",media:"image",menu:"menu",category:"tag",widgets:"layout"},PLACE_TEXT={add:"Goes in the footer, after what is there now",there:"Already in the footer",off:"Not put in the footer (you chose this)",removed:"Not put back in the footer - it was taken out here since the last import",full:"Not put in the footer - it already has 8 blocks; make room in Site Settings \u203A Footer"},TYPE_AS={collection:{text:"Collection",tone:"ok"},pages:{text:"Pages",tone:"info"},skip:{text:"Skipped",tone:"muted"}},PRODUCT_TYPE_LABEL={simple:"Simple",variable:"With options",grouped:"Grouped",external:"External"},ACTION={create:{text:"New",tone:"ok"},rename:{text:"New, renamed",tone:"warn"},update:{text:"Update",tone:"info"},keep:{text:"Left as is",tone:"muted"},skip:{text:"Skipped",tone:"muted"}},RESULT={created:{text:"Made",tone:"ok"},updated:{text:"Updated",tone:"info"},kept:{text:"Left as is",tone:"muted"},skipped:{text:"Skipped",tone:"muted"},failed:{text:"Failed",tone:"bad"}},PLAN_FILTERS=[{id:"all",label:"All"},{id:"new",label:"New"},{id:"update",label:"Updates"},{id:"renamed",label:"Renamed"},{id:"keep",label:"Left as is"},{id:"skip",label:"Skipped"},{id:"rebuild",label:"Needs rebuilding"}],REPORT_FILTERS=[{id:"all",label:"All"},{id:"created",label:"Made"},{id:"updated",label:"Updated"},{id:"kept",label:"Left as is"},{id:"skipped",label:"Skipped"},{id:"failed",label:"Failed"},{id:"rebuild",label:"Needs rebuilding"}];const h=e=>!!(e.builder||e.needs||e.shortcodes?.length),m=e=>[e.builder?`Built with ${e.builder}`:"",e.needs||"",...(e.shortcodes||[]).map(t=>`[${t}]`)].filter(Boolean).join(" ");export function planMatches(e,t){switch(t){case"new":return e.action==="create"||e.action==="rename";case"update":return e.action==="update";case"renamed":return e.action==="rename"||!!e.replaces;case"keep":return e.action==="keep";case"skip":return e.action==="skip";case"rebuild":return h(e);default:return!0}}export function reportMatches(e,t){return t==="rebuild"?h(e):t==="all"||!t||e.result===t}export function counts(e,t,o,s=""){const n=e.filter(i=>!s||i.kind===s);return Object.fromEntries(t.map(i=>[i.id,n.filter(a=>o(a,i.id)).length]))}export function kindTabs(e){const t=["page","post","product","custom","collection","media","menu","category","widgets"],o={};for(const s of e)o[s.kind]=(o[s.kind]||0)+1;return[{id:"",label:"Everything",count:e.length},...t.filter(s=>o[s]).map(s=>({id:s,label:KIND_PLURAL[s],count:o[s]}))]}export function planRow(e){const t=ACTION[e.action]||ACTION.skip;let o=e.target?e.kind==="menu"?`Menu "${e.target}"`:e.kind==="collection"?`Collection "${e.target}"`:e.kind==="widgets"?`Block "${e.target}"`:e.target:"";e.kind==="widgets"&&e.place&&e.action!=="skip"&&(o=`${o}${o?" \xB7 ":""}${PLACE_TEXT[e.place]||""}`),e.kind==="custom"&&e.as==="collection"&&e.collection&&e.action!=="skip"&&(o=`In the collection "${e.collection}"${e.listAt?`, listed at ${e.listAt}`:""}`);const s=[e.kind==="custom"&&e.postType?`${KIND_LABEL.custom} (${e.postType})`:KIND_LABEL[e.kind]||e.kind];return e.listFor&&s.push("list page"),e.status&&(e.kind==="page"||e.kind==="post"||e.kind==="custom")&&s.push(e.status),e.kind==="collection"&&e.fields&&s.push(`${e.fields} fields`),e.kind==="product"&&(e.productType&&s.push(PRODUCT_TYPE_LABEL[e.productType]||e.productType),e.variants&&s.push(`${e.variants} option${e.variants===1?"":"s"}`),e.action!=="skip"&&e.status&&s.push(e.status==="active"?"on sale":"draft")),e.comments&&s.push(`${e.comments} comment${e.comments===1?"":"s"}`),e.count&&s.push(e.kind==="widgets"?`${e.count} widget${e.count===1?"":"s"}`:`${e.count} item${e.count===1?"":"s"}`),e.kind==="widgets"&&e.widgetTypes?.length&&s.push(e.widgetTypes.join(", ")),{key:e.key,kind:e.kind,icon:KIND_ICON[e.kind]||"file",title:e.title||"(untitled)",pill:e.replaces?"Replaces a page":t.text,pillClass:`is-${e.replaces?"warn":t.tone}`,where:o,meta:s.join(" \xB7 "),reason:e.reason||"",rebuild:m(e),chosen:!!e.choice}}export function reportRow(e){const t=RESULT[e.result]||RESULT.skipped;return{key:e.key,kind:e.kind,icon:KIND_ICON[e.kind]||"file",title:e.title||"(untitled)",pill:t.text,pillClass:`is-${t.tone}`,where:e.target||"",meta:[KIND_LABEL[e.kind]||e.kind,e.format==="html"?"kept as HTML":"",e.kind==="widgets"&&e.placed?"added to the footer":""].filter(Boolean).join(" \xB7 "),reason:[e.reason,...e.notes||[]].filter(Boolean).join(" "),rebuild:m(e)}}export function choiceItems(e,t){const o=[];return t&&o.push({id:"reset",label:"Undo my choice",icon:"rotate-ccw"}),t!=="skip"&&e.action!=="skip"&&e.action!=="keep"&&o.push({id:"skip",label:"Skip this",icon:"x-circle"}),t!=="overwrite"&&e.action==="keep"&&o.push({id:"overwrite",label:"Overwrite my changes here",icon:"refresh-cw"}),t!=="overwrite"&&e.action==="rename"&&(e.kind==="page"||e.kind==="post")&&e.wanted&&e.kind==="page"&&o.push({id:"overwrite",label:`Replace the page at ${e.wanted}`,icon:"refresh-cw"}),t!=="import"&&e.action==="skip"&&/Deleted (on this site|from the Blog|from the Shop|from the collection)/.test(e.reason||"")&&o.push({id:"import",label:"Import again",icon:"download"}),o}export function applyChoice(e,t,o){const s={...e||{}};return!o||o==="reset"?delete s[t]:s[t]=o,s}export function defaultOptions(e=!1){return{posts:e?"blog":"pages",media:"download",comments:!0,menus:!0,redirects:!0,drafts:!0,products:!0,types:{},listPages:{},choices:{},users:{},userRole:"",invite:!1,footer:{},home:!0,postsPage:0,latestPosts:!1,homeReplace:null}}export function cleanOptions(e={},t=!1){const o=defaultOptions(t),s=(n,i,a)=>i.includes(n)?n:a;return{posts:s(e.posts,t?["blog","pages","skip"]:["pages","skip"],o.posts),media:s(e.media,["download","zip","skip"],o.media),comments:e.comments!==!1,menus:e.menus!==!1,redirects:e.redirects!==!1,drafts:e.drafts!==!1,products:e.products!==!1,types:Object.fromEntries(Object.entries(e.types||{}).filter(([n,i])=>g.test(n)&&(i==="pages"||i==="skip"))),listPages:Object.fromEntries(Object.entries(e.listPages||{}).filter(([n,i])=>g.test(n)&&i===!1)),choices:Object.fromEntries(Object.entries(e.choices||{}).filter(([n,i])=>typeof n=="string"&&["skip","overwrite","import"].includes(i))),users:Object.fromEntries(Object.entries(e.users||{}).filter(([n,i])=>b.test(n)&&(i==="create"||i==="skip"))),userRole:typeof e.userRole=="string"&&$.test(e.userRole)?e.userRole:"",invite:e.invite===!0,footer:Object.fromEntries(Object.entries(e.footer||{}).filter(([n,i])=>w.test(n)&&i===!1)),home:e.home!==!1,postsPage:Number.isInteger(e.postsPage)&&e.postsPage>0?e.postsPage:0,latestPosts:e.latestPosts===!0,homeReplace:typeof e.homeReplace=="boolean"?e.homeReplace:null}}const w=/^[A-Za-z0-9_-]{1,100}$/;export function withFooterChoice(e,t,o){const s={...e.footer||{}};return o?delete s[t]:s[t]=!1,{...e,footer:s}}export function optionsSnapshot(e){const t=cleanOptions(e,!0),o=Object.keys(t.choices).sort().map(l=>`${l}=${t.choices[l]}`).join(","),s=Object.keys(t.types).sort().map(l=>`${l}=${t.types[l]}`).join(","),n=Object.keys(t.listPages).sort().join(","),i=Object.keys(t.users).sort().map(l=>`${l}=${t.users[l]}`).join(","),a=Object.keys(t.footer).sort().join(",");return[t.posts,t.media,t.comments,t.menus,t.redirects,t.drafts,t.products,o,s,n,i,t.userRole,t.invite,a,t.home,t.postsPage,t.latestPosts,t.homeReplace].join("|")}const g=/^[a-z0-9_-]{1,40}$/;export function withTypeChoice(e,t,o){const s={...e.types||{}};return o==="pages"||o==="skip"?s[t]=o:delete s[t],{...e,types:s}}export function withListPage(e,t,o){const s={...e.listPages||{}};return o?delete s[t]:s[t]=!1,{...e,listPages:s}}export const isDirty=(e,t)=>optionsSnapshot(e)!==optionsSnapshot(defaultOptions(t)),draftKey=e=>e?`wordpress-import.draft.${e}`:"";export function liveDrafts(e,t,o){if(!e||typeof e!="object"||!e.items||typeof e.items!="object")return{items:{},pruned:!1};if(e.owner&&o&&e.owner!==o)return{items:{},pruned:!1};const s={};let n=!1;for(const[i,a]of Object.entries(e.items))t.includes(i)&&a?.options&&a.was===i?s[i]=a:n=!0;return{items:s,pruned:n}}export function withDraft(e,t,o,s){const n={...e&&e.owner===t&&e.items||{}};return s?n[o]=s:delete n[o],{owner:t,items:n}}export function draftQuestion({when:e="",file:t=""}={}){return{title:"Choices you left last time",text:`You changed the import options${t?` for ${t}`:""}${e?` (${e})`:""} and left before running it. Throw away those choices and start from the defaults?`,confirmText:"Throw away",cancelText:"Keep them"}}export function progressText(e){if(!e)return"";const t=e.total?` \xB7 ${e.done} of ${e.total}`:"";return`${e.phase||"Working"}${t}${e.current?` \xB7 ${String(e.current).slice(0,80)}`:""}`}export const progressPercent=e=>e?.total?Math.min(100,Math.round(e.done/e.total*100)):0;export function planTiles(e){const t=e?.summary?.counts||{},o=s=>t[s]?t[s].total-t[s].skip:0;return[{id:"page",label:"Pages",value:o("page"),of:t.page?.total||0},{id:"post",label:"Posts",value:o("post"),of:t.post?.total||0},...t.product?[{id:"product",label:"Products",value:o("product"),of:t.product.total}]:[],...t.custom?[{id:"custom",label:"Custom items",value:o("custom"),of:t.custom.total}]:[],{id:"media",label:"Media",value:o("media"),of:t.media?.total||0},{id:"menu",label:"Menus",value:o("menu"),of:t.menu?.total||0},...e?.people?.rows?.length?[{id:"people",label:"New accounts",value:e.people.counts?.create||0,of:0}]:[],...t.widgets?[{id:"widgets",label:"Widget areas",value:o("widgets"),of:t.widgets.total}]:[],{id:"comments",label:"Comments",value:e?.summary?.comments||0,of:0},{id:"redirects",label:"Redirects",value:e?.summary?.redirects||0,of:0}].map(s=>({...s,sub:s.of&&s.of!==s.value?`of ${s.of}`:""}))}const b=/^author:(\d{1,12}|login-[\w.-]{1,60})$/,$=/^[a-z0-9][a-z0-9_-]{0,59}$/i;export const PERSON={create:{text:"New account",tone:"ok"},match:{text:"Matched",tone:"info"},linked:{text:"Linked before",tone:"info"},same:{text:"Same email",tone:"muted"},skip:{text:"No account",tone:"muted"}};export function creditText(e={}){const t=(o,s,n)=>e[o]?`${e[o]} ${e[o]===1?s:n}`:"";return[t("post","post","posts"),t("page","page","pages"),t("product","product","products"),t("custom","item","items")].filter(Boolean).join(", ")||"nothing in this import"}export function personRow(e){const t=PERSON[e.action]||PERSON.skip,o=e.problem==="email";return{key:e.key,name:e.name||"Author",meta:[e.email||(o?"no usable email":""),creditText(e.items)].filter(Boolean).join(" \xB7 "),reason:e.reason||"",pill:o?"No email":t.text,pillClass:`is-${o?"warn":t.tone}${e.choice?" is-chosen":""}`,choosable:!!e.choosable,segCreate:e.action==="create"?"is-on":"",segSkip:e.action==="skip"?"is-on":""}}export function chooseAllPeople(e,t){const o={};for(const s of e?.rows||[])s.choosable&&(o[s.key]=t);return o}export function choosePerson(e,t,o){const s={...e||{}},n=t.total?"create":"skip";return!o||o==="reset"||o===n?delete s[t.key]:s[t.key]=o,s}export function personMenuItems(e){const t=[];return e.choosable&&e.action!=="create"&&t.push({id:"create",label:"Make a new account",icon:"user-plus"}),e.choosable&&e.action!=="skip"&&t.push({id:"skip",label:"Do not make an account",icon:"user-x"}),e.choice&&t.push({id:"reset",label:"Undo my choice",icon:"rotate-ccw"}),e.email&&e.problem!=="email"&&t.push({id:"copy",label:"Copy the email address",icon:"copy"}),t.length||t.push({id:"none",label:"Nothing to choose for them",icon:"info",disabled:!0}),t}export function peopleSummary(e){const t=e?.counts||{};if(!e?.rows?.length)return{text:"This export names no authors.",tone:"info"};const o=[];t.create&&o.push(`${t.create} new account${t.create===1?"":"s"}`),t.match+t.linked&&o.push(`${t.match+t.linked} matched to ${t.match+t.linked===1?"a user":"users"} here`),t.skip&&o.push(`${t.skip} without an account`);let s=`${e.rows.length} author${e.rows.length===1?"":"s"}: ${o.join(", ")||"nothing to do"}.`;return t.create&&(s+=" New accounts have no password - nobody can sign in to one until a password is set."),!e.canCreate&&e.why&&(s+=` ${e.why}`),{text:s,tone:t.problems||!e.canCreate?"warn":"info"}}export function inviteNote(e){return e?.canCreate?e.mailReady?e.counts?.create?"Sent once the import has finished: an invitation saying an account has been made for them, with a link to set their password that works for 7 days.":"Nobody new to email.":"No mail server is set up (Settings \u203A Email), so this cannot be sent. After the import, use Users \u203A \u22EE \u203A Copy a reset link instead.":""}export function reportPersonRow(e){const t={created:{text:"Account made",tone:"ok"},matched:{text:"Matched",tone:"info"},linked:{text:"Linked before",tone:"info"},same:{text:"Same email",tone:"muted"},skipped:{text:"No account",tone:"muted"},failed:{text:"Failed",tone:"bad"}},o=t[e.result]||t.skipped,s=[creditText(e.items)];return e.result==="created"&&s.push(e.invited?"invited by email":e.inviteFailed?"the invitation was not sent":"no password set yet"),{key:e.key,name:e.name||"Author",meta:s.join(" \xB7 "),reason:e.reason||"",pill:o.text,pillClass:`is-${o.tone}`,userId:e.userId||""}}export function historyRow(e,t=o=>o){const o=e.summary||{},s=Object.values(o.kinds||{}).reduce((a,l)=>a+(l.created||0),0),n=Object.values(o.kinds||{}).reduce((a,l)=>a+(l.updated||0),0),i=[];return s&&i.push(`${s} made`),o.people?.created&&i.push(`${o.people.created} account${o.people.created===1?"":"s"}`),n&&i.push(`${n} updated`),o.redirects?.created&&i.push(`${o.redirects.created} redirect${o.redirects.created===1?"":"s"}`),o.rebuild&&i.push(`${o.rebuild} to rebuild`),o.failed&&i.push(`${o.failed} failed`),e.status==="failed"&&i.splice(0,i.length,"Stopped before the end - run it again to finish"),{id:e.id,title:e.siteTitle||e.source||"WordPress site",meta:[t(e.at),e.by,e.file].filter(Boolean).join(" \xB7 "),summary:i.join(", ")||"Nothing changed",pill:e.status==="failed"?"Stopped":e.status==="partial"?"Some failed":"Done",pillClass:e.status==="failed"||e.status==="partial"?"is-bad":"is-ok"}}export function stagedRow(e,t=o=>o){const o=e.counts||{},s=[o.page&&`${o.page} pages`,o.post&&`${o.post} posts`,o.product&&`${o.product} products`,o.attachment&&`${o.attachment} media`,e.widgets&&`${e.widgets.widgets} widget${e.widgets.widgets===1?"":"s"}`].filter(Boolean);return{id:e.id,title:e.siteTitle||e.fileName||"WordPress export",meta:[e.fileName,t(e.at),s.join(", ")].filter(Boolean).join(" \xB7 "),pill:e.uploads?"Uploads zip added":e.widgets?"Widgets added":"Waiting",pillClass:e.uploads||e.widgets?"is-ok":"is-muted"}}export function productSummary(e){const t=e?.summary?.products;if(!t?.total)return null;const o=t.shop||e.shop||{},s=e.options?.products!==!1,n=Object.entries(t.types||{}).map(([u,p])=>`${p} ${(PRODUCT_TYPE_LABEL[u]||u).toLowerCase()}`).join(", "),i=e.summary.counts?.product||{},a=[{label:"In the export",value:`${t.total}${n?` (${n})`:""}`}];o.available&&s&&(a.push({label:"On sale in the Shop",value:String(t.onSale||0)}),a.push({label:"As drafts",value:String(t.drafts||0)}),i.keep&&a.push({label:"Left as is",value:String(i.keep)}),a.push({label:"Skipped",value:String(i.skip||0)})),t.rebuild&&a.push({label:"Need rebuilding",value:String(t.rebuild)});let l,r="info";return o.available?s?l=`Products go to the Shop at ${o.basePath||"/shop"}.`:l='Products are not being imported - switch on "WooCommerce products" above to bring them in.':(l=o.reason||"Switch on the Shop to bring these in.",r="warn"),{note:l,tone:r,rows:a,notes:Array.isArray(t.notes)?t.notes:[]}}const d=(e,t,o=`${t}s`)=>`${e} ${e===1?t:o}`;export function customTypeRows(e){return(e?.summary?.customTypes||[]).map(t=>{const o=TYPE_AS[t.as]?t.as:"collection",s=TYPE_AS[o],n=t.rewrite?`was at /${t.rewrite}/\u2026`:"its old addresses are not in the export";let i;if(o==="skip")i="Nothing - left out.";else if(o==="pages")i=`${d(t.importing||0,"page")} under ${t.prefix}${t.drafts?` (${t.drafts} as draft${t.drafts===1?"":"s"})`:""}.`;else{const a=t.collection||{},l=a.action==="skip"?`The collection "${a.slug}" was deleted here - choose Import again on it below.`:`${a.action==="update"?"The collection":"A new collection"} "${a.slug}" with ${d(t.fields?.length||0,"field")}: ${d(t.importing||0,"entry","entries")}${t.drafts?` (${t.drafts} draft${t.drafts===1?"":"s"}, not shown on the site)`:""}.`,r=t.listPage?t.listAt?` A list page at ${t.listAt}${t.listAction==="keep"?" (edited here - left as it is)":""}; the old addresses lead there.`:"":" No list page - the old addresses are not redirected.";i=`${l}${r}`}return{type:t.type,label:t.label||t.type,meta:[d(t.count||0,"item"),`"${t.type}"`,n].join(" \xB7 "),samples:(t.samples||[]).length?`For example: ${t.samples.join(", ")}`:"",creates:i,pill:s.text,pillClass:`is-${s.tone}`,as:o,listOn:o==="collection"&&t.listPage!==!1,canList:o==="collection",listLabel:`List page at ${t.prefix||`/${t.type}`}`,segCollection:o==="collection"?"is-on":"",segPages:o==="pages"?"is-on":"",segSkip:o==="skip"?"is-on":""}})}export function internalTypeRows(e){return(e?.summary?.internalTypes||[]).map(t=>({type:t.type,text:`${t.type} (${t.count}) - ${t.reason}`}))}export function typeMenuItems(e){if(!e)return[];const t=[];return e.as!=="collection"&&t.push({id:"collection",label:"Import as a collection",icon:"database"}),e.as!=="pages"&&t.push({id:"pages",label:"Import as pages",icon:"file-text"}),e.as!=="skip"&&t.push({id:"skip",label:"Leave this type out",icon:"x-circle"}),e.as==="collection"&&t.push(e.listOn?{id:"nolist",label:"No list page",icon:"eye-off"}:{id:"list",label:"Make a list page",icon:"list"}),t.push({id:"show",label:"Show its items",icon:"filter"}),t}export function widgetAreaRows(e){return(e?.summary?.widgets?.areas||[]).map(t=>{const o=ACTION[t.action]||ACTION.skip,s=t.action==="skip"?"Not imported.":`${t.action==="update"?"Updates the block":t.action==="keep"?"The block (edited here - left as it is)":"A new block"} "${t.block}"${t.coming<t.count?` with ${d(t.coming,"widget")}`:""}.`;return{area:t.id,label:t.label||t.id,meta:[d(t.count||0,"widget"),`"${t.id}"`,(t.types||[]).join(", ")].filter(Boolean).join(" \xB7 "),creates:s,placeText:t.footer?PLACE_TEXT[t.place]||"":t.action==="skip"?"":"A sidebar - a block only; put it on a page with [block].",rebuild:(t.rebuild||[]).length?`Needs rebuilding: ${t.rebuild.join("; ")}`:"",pill:o.text,pillClass:`is-${o.tone}`,canPlace:!!t.footer&&t.action!=="skip"&&t.place!=="there"&&t.place!=="removed",placeOn:t.place==="add"}})}export function widgetsFileText(e,t=0){const o=e?.widgets;return o?`${o.fileName} - ${d(o.areas||0,"widget area")}, ${d(o.widgets||0,"widget")}.`:`Optional: the widgets, as the .wie file the free "Widget Importer & Exporter" WordPress plugin exports${t?` (up to ${Math.max(1,Math.round(t/1048576))} MB)`:""}.`}const c=e=>`"${String(e||"").trim()||"Untitled"}"`;export function homeCard(e){const t=e?.home;if(!t)return null;const o=e.options||{},s=[];let n;t.how==="page"&&t.page?n=`WordPress's home page was the page ${c(t.page.title)}${t.page.oldUrl?` (${t.page.oldUrl})`:""}.`:t.how==="unclear"?n=`${t.matches} pages had the home page's address in WordPress, so it cannot be told which one was the home page - none of them is made this site's home page.`:n="WordPress showed its latest posts on the home page (no page was set as the home page).";const i=t.now?`This site's home page now: ${c(t.now.title)}.`:"This site has no home page now.";let a="",l="info";const r=t.question,u=r?`Make ${c(r.title)} the home page instead of ${c(r.current||"the page at /")}?`:"";r?r.answer===!0&&t.move?(a=`${c(r.title)} becomes the home page. ${c(r.current||"The page at /")} moves to ${t.move.to} as a draft - nothing is deleted.`,l="warn"):r.answer===!1&&(a=`${c(r.title)} is imported as an ordinary page${t.page?.target?` at ${t.page.target}`:""} - this site's home page stays as it is.`):t.done?.source?a=`An import of another WordPress site (${t.done.source}) set this site's home page - it is not changed.`:t.done?(a=t.done.how==="latest"?"An earlier import put the latest posts on the home page - it is not moved again.":"An earlier import made it this site's home page - it is not moved again.",t.done.previous&&(a+=` The home page from before is at ${t.done.previous}.`)):t.move?(a=`${t.how==="page"?c(t.page?.title):"A page showing the latest Blog posts"} becomes the home page. ${t.now?c(t.now.title):"The page at /"} moves to ${t.move.to} as a draft - nothing is deleted.`,l="warn"):t.page?.isHome||t.latest?.on?a=`${t.how==="page"?c(t.page?.title):"A page showing the latest Blog posts"} becomes the home page.`:t.how==="page"&&t.page?a=o.home===!1?`It is imported as an ordinary page${t.page.target?` at ${t.page.target}`:""} - this site's home page stays as it is.`:"":t.how==="latest"&&(a=t.latest?.offered?"This site's home page stays as it is unless you choose to show the latest posts on it.":"This site's home page stays as it is."),t.changed&&!t.done?.source&&!r&&(s.push(t.changed),l="warn");const p=t.postsPage;let f="";return p&&(f=p.confirmed?`${c(p.title)} was WordPress's posts page: it is not made, and its old address (and links and menus that pointed to it) lead to ${p.to}.`:`${c(p.title)} has nothing in it and is called like a blog - it was probably WordPress's posts page. If it was, it is not needed here: its address can lead to ${p.to} instead.`),{wp:n,now:i,after:a,tone:l,notes:s,frontSwitch:t.how==="page"&&!!t.page&&!t.done,frontLabel:"Make it this site's home page",latestSwitch:!!t.latest?.offered&&!t.done,postsSwitch:!!p,postsText:f,postsLabel:"It was the posts page - lead its address to the posts",postsId:p?.wpId||0,editNow:t.move&&t.now?"#/pages/edit/":"",question:u,answer:r&&typeof r.answer=="boolean"?r.answer:null}}
@@ -51,6 +51,7 @@
51
51
  <button type="button" class="btn btn-sm btn-ghost" data-on-click="back"><span data-icon="arrow-left" data-icon-size="14"></span> Back</button>
52
52
  <button type="button" class="btn btn-sm btn-outline" data-on-click="discard">Discard</button>
53
53
  <button type="button" class="btn btn-sm btn-primary wpx-run" data-on-click="run" data-bind-disabled="!canRun.value"><span data-icon="play" data-icon-size="14"></span> Run import</button>
54
+ <small class="wpx-note wpx-run-why" hidden data-bind-hidden="!runWhy.value" data-bind-text="runWhy.value"></small>
54
55
  </div>
55
56
  </div>
56
57
  <p class="wpx-alert is-dirty" hidden data-bind-hidden="!dirty.value">
@@ -132,10 +133,17 @@
132
133
 
133
134
  <div class="wpx-card wpx-home" hidden data-bind-hidden="!hasHome.value">
134
135
  <div class="wpx-card-title"><span data-icon="home" data-icon-size="16"></span> Home page
135
- <span data-help-title="The home page" data-help="A WordPress export does not say which page was the home page, but each page's address is in it - and the home page's address is the site's own. That page is the one offered here; a page called &quot;Home&quot; with another address is just a page. Made this site's home page, it goes at / and the page there now moves to /previous-home as a draft - nothing is deleted, menus that lead to / keep doing so, and nothing redirects to the old one. Its WordPress address (/home/, say) and ?page_id= links lead to /. That happens once: run the import again and the home page is updated where it is, left as it is if you edited it here (unless you choose Overwrite), and left alone altogether if you changed the home page yourself since. The posts page cannot be told from the export, so an empty page with a blog's name is only suggested: switch it on if it was, and its address leads to the posts instead of an empty page being made. When WordPress showed its latest posts on the home page, you can do the same here with the Blog's list."></span></div>
136
+ <span data-help-title="The home page" data-help="A WordPress export does not say which page was the home page, but each page's address is in it - and the home page's address is the site's own. That page is the one offered here; a page called &quot;Home&quot; with another address is just a page. Made this site's home page, it goes at / and the page there now moves to /previous-home as a draft - nothing is deleted, menus that lead to / keep doing so, and nothing redirects to the old one. Its WordPress address (/home/, say) and ?page_id= links lead to /. That happens once: run the import again and the home page is updated where it is, left as it is if you edited it here (unless you choose Overwrite), and left alone altogether if you changed the home page yourself since. When an earlier import (of this site or another) set the home page and this export's home page is a different page, you are asked whether it should take over - Yes moves the current home page to /previous-home as a draft in the same way, No imports the page at its own address - and the import waits for your answer. The posts page cannot be told from the export, so an empty page with a blog's name is only suggested: switch it on if it was, and its address leads to the posts instead of an empty page being made. When WordPress showed its latest posts on the home page, you can do the same here with the Blog's list."></span></div>
136
137
  <p class="wpx-note" data-bind-text="homeWp.value"></p>
137
138
  <p class="wpx-note"><span data-bind-text="homeNow.value"></span> <a class="wpx-home-edit" hidden data-bind-hidden="!homeEdit.value" href="#/pages/edit/">Open it</a></p>
138
139
  <label class="wpx-toggle" hidden data-bind-hidden="!homeFrontSwitch.value"><input type="checkbox" class="form-switch" data-model="optHome.value"> Make it this site's home page</label>
140
+ <div class="wpx-home-question" hidden data-bind-hidden="!homeQuestion.value">
141
+ <strong data-bind-text="homeQuestion.value"></strong>
142
+ <div class="wpx-seg" role="group" aria-label="Make it the home page">
143
+ <button type="button" data-on-click="homeYes" data-bind-class="opt.value.homeReplace === true && 'is-on'">Yes</button>
144
+ <button type="button" data-on-click="homeNo" data-bind-class="opt.value.homeReplace === false && 'is-on'">No</button>
145
+ </div>
146
+ </div>
139
147
  <label class="wpx-toggle" hidden data-bind-hidden="!homeLatestSwitch.value"><input type="checkbox" class="form-switch" data-model="optLatest.value"> Show the latest posts on the home page</label>
140
148
  <p class="wpx-alert" hidden data-bind-hidden="!homeAfter.value" data-bind-class="homeAfterCls.value"><span data-bind-text="homeAfter.value"></span></p>
141
149
  <div class="wpx-note-list wpx-home-notes" data-each="homeNotes.value key=text"></div>
@@ -308,6 +316,8 @@
308
316
  .wpx-people-ctl .wpx-note { flex-basis: 100%; }
309
317
  .wpx-home-posts { display: flex; flex-direction: column; gap: .4rem; padding-top: .5rem; border-top: 1px solid var(--dm-border); }
310
318
  .wpx-home-edit { font-size: var(--dm-text-sm); }
319
+ .wpx-home-question { display: flex; flex-direction: column; gap: .4rem; padding: .6rem .8rem; border-radius: var(--dm-radius-md); background: color-mix(in srgb, var(--dm-warning, var(--dm-primary)) 14%, transparent); }
320
+ .wpx-run-why { flex-basis: 100%; }
311
321
  .wpx-report { display: flex; flex-direction: column; gap: .75rem; padding: .25rem 0; }
312
322
  @media (max-width: 720px) { .wpx-toggles { grid-template-columns: 1fr; } .wpx-row { flex-wrap: wrap; } .wpx-chips { justify-content: flex-start; } }
313
323
  </style>
@@ -1,9 +1,9 @@
1
- import{apiRequest as R,getToken as io}from"../api.js";import{addBannerAction as po}from"../lib/plugin-chrome.js";import{helpPopovers as Mt}from"../lib/help-popover.js";import{onceClosed as co,openMenuAt as B,sidebarCount as q,viewLifetime as uo}from"../lib/tool-kit.js";import{SHAPES as Tt,whileLoading as Et}from"../lib/loading.js";import{fmtDateTime as mo}from"../lib/dates.js";import{applyChoice as ho,chooseAllPeople as $t,choosePerson as vo,choiceItems as St,cleanOptions as xe,counts as Lt,customTypeRows as wo,homeCard as fo,inviteNote as bo,peopleSummary as go,personMenuItems as yo,personRow as xo,reportPersonRow as ko,defaultOptions as _e,draftKey as Mo,draftQuestion as To,historyRow as Eo,internalTypeRows as $o,isDirty as So,kindTabs as Lo,liveDrafts as Pt,optionsSnapshot as Ct,PLAN_FILTERS as Rt,planMatches as qt,planRow as Po,planTiles as At,productSummary as Co,progressPercent as Ro,progressText as qo,REPORT_FILTERS as It,reportMatches as Nt,reportRow as Ao,stagedRow as Io,typeMenuItems as No,widgetAreaRows as Oo,widgetsFileText as Bo,withDraft as Ot,withFooterChoice as Do,withListPage as Ho,withTypeChoice as Uo}from"../lib/wordpress-import-arrange.js";const Bt=/^#\/wordpress-import\/?(\?.*)?$/,ie="#/wordpress-import",L="/import/wordpress",ke=400,_o=`
1
+ import{apiRequest as R,getToken as uo}from"../api.js";import{addBannerAction as mo}from"../lib/plugin-chrome.js";import{helpPopovers as Et}from"../lib/help-popover.js";import{onceClosed as ho,openMenuAt as B,sidebarCount as q,viewLifetime as vo}from"../lib/tool-kit.js";import{SHAPES as $t,whileLoading as St}from"../lib/loading.js";import{fmtDateTime as wo}from"../lib/dates.js";import{applyChoice as fo,chooseAllPeople as Lt,choosePerson as bo,choiceItems as Pt,cleanOptions as xe,counts as Ct,customTypeRows as go,homeCard as yo,inviteNote as xo,peopleSummary as ko,personMenuItems as Mo,personRow as To,reportPersonRow as Eo,defaultOptions as _e,draftKey as $o,draftQuestion as So,historyRow as Lo,internalTypeRows as Po,isDirty as Co,kindTabs as Ro,liveDrafts as Rt,optionsSnapshot as qt,PLAN_FILTERS as At,planMatches as It,planRow as qo,planTiles as Nt,productSummary as Ao,progressPercent as Io,progressText as No,REPORT_FILTERS as Ot,reportMatches as Bt,reportRow as Oo,stagedRow as Bo,typeMenuItems as Do,widgetAreaRows as Ho,widgetsFileText as Uo,withDraft as Dt,withFooterChoice as _o,withListPage as zo,withTypeChoice as jo}from"../lib/wordpress-import-arrange.js";const Ht=/^#\/wordpress-import\/?(\?.*)?$/,ie="#/wordpress-import",P="/import/wordpress",ke=400,Wo=`
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>`,Dt=`
6
+ </div>`,Ut=`
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 R,getToken as io}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>`,Ht=`
17
+ </div>`,_t=`
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>`,zo=`
22
+ </div>`,Fo=`
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>`,jo=`
27
+ </div>`,Ko=`
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 R,getToken as io}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>`,Wo=`
45
+ </div>`,Zo=`
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,12 @@ import{apiRequest as R,getToken as io}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>`,Fo=`
60
+ </div>`,Vo=`
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>`,Ko=`
65
+ </div>`,Yo=`
66
66
  <div class="wpx-row wpx-widget-row" data-area="{{area}}" tabindex="0">
67
67
  <span class="wpx-main">
68
68
  <strong>{{label}}</strong>
@@ -76,7 +76,7 @@ import{apiRequest as R,getToken as io}from"../api.js";import{addBannerAction as
76
76
  </span>
77
77
  <span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillClass">{{pill}}</span></span>
78
78
  <button type="button" class="wpx-kebab" aria-label="Actions for {{label}}" title="Actions">\u22EE</button>
79
- </div>`,Ut='<div class="wpx-tile"><strong>{{value}}</strong><small>{{label}} <span data-bind-hidden="!sub">{{sub}}</span></small></div>',ze='<p class="wpx-alert"><span>{{text}}</span></p>',_t="<li>{{text}}</li>",Zo='<div class="wpx-fact"><dt>{{label}}</dt><dd>{{value}}</dd></div>',je='<button type="button" data-on-click="$root.pickSeg($data)" data-bind-class="cls">{{label}} <b>{{count}}</b></button>',b=uo(Bt);function We(u,l,p){return new Promise((g,k)=>{const v=new XMLHttpRequest,A=new FormData;A.append("file",l),v.open("POST",`/api${u}`);const w=io();w&&v.setRequestHeader("Authorization",`Bearer ${w}`),v.upload.onprogress=m=>{m.lengthComputable&&p(m.loaded/m.total)},v.onload=()=>{let m=null;try{m=JSON.parse(v.responseText)}catch{}v.status>=200&&v.status<300?g(m):k(new Error(m?.error||`The upload failed (${v.status}).`))},v.onerror=()=>k(new Error("The upload failed - the connection dropped.")),v.send(A),b.add(()=>{v.readyState!==4&&v.abort()})})}const Me=u=>u>=1048576?`${Math.round(u/1048576)} MB`:`${Math.max(1,Math.round(u/1024))} KB`,J=u=>String(u??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");function Vo(u){const l=document.createElement("template");return l.innerHTML=u.trim(),l.content.firstElementChild}async function Te(u,l){const p=E.confirm(u,l);let g=null;try{g=[...document.querySelectorAll(".dm-dialog-overlay")].at(-1)??null}catch{}const k=b.add(()=>{if(!g)return;const v=g;g=null;try{v.querySelector(".dm-dialog-cancel")?.click(),v.remove()}catch{}});try{return await p}finally{g=null,k()}}function Fe(u){navigator.clipboard?.writeText(u).then(()=>E.toast("Copied.",{type:"success"}),()=>E.toast(u,{type:"info",duration:8e3}))}function U(u,l,p,g){const k=E.contextMenu(u,{cursor:"glyph",match:l,inherit:!1,density:"compact",items:p});b.add(()=>k?.destroy?.());const v=w=>{const m=w.target.closest(l);if(!m||!u.contains(m))return;const d=w.target.closest(".wpx-kebab");if(d){B(d);return}g?.(m)},A=w=>{const m=w.target.closest(l);m&&(w.key==="F10"&&w.shiftKey?(w.preventDefault(),B(m)):w.key==="Enter"&&w.target===m&&g&&(w.preventDefault(),w.stopPropagation(),g(m)))};u.addEventListener("click",v),u.addEventListener("keydown",A),b.add(()=>{u.removeEventListener("click",v),u.removeEventListener("keydown",A)})}export const wordpressImportView={templateUrl:"/admin/js/templates/wordpress-import.html",async onMount(u){b.reset();const l=u.get(0).querySelector(".wpx")||u.get(0),p=()=>Bt.test(location.hash)&&l.isConnected;let g="";try{g=S.get("auth_user")?.id||""}catch{}const k=Mo(g),v=()=>{try{return k?S.get(k):null}catch{return null}},A=e=>{try{k&&S.set(k,e)}catch{}},w=l.querySelector(".wpx-loading");let m;try{m=await Et(w,R(`${L}/status`),Tt.rows)}catch(e){if(!l.isConnected)return;w&&(w.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(!p())return;w&&(w.hidden=!0),l.querySelector(".wpx-body").hidden=!1;const d=M.observable(m.staged||[]),_=M.observable(m.history||[]),Y=M.observable(m.blog||{available:!1}),Ke=M.observable(m.shop||{available:!1}),z=m.limits||{},Ze=m.people?.defaultRole||"",y=M.observable(m.running||null),h=M.observable(""),f=M.observable(null),pe=M.observable(!1),Ee=M.observable(!1),$e=M.observable(""),i=M.observable(_e(Y.peek().available)),j=M.observable("all"),D=M.observable(""),X=M.observable(""),ce=M.observable(""),Q=M.observable(""),de=M.observable(""),ue=M.observable(!1);let me=!0;const Go=()=>d.peek().find(e=>e.id===h.peek())||null,ee=M.computed(()=>d.value.find(e=>e.id===h.value)||null),zt=M.computed(()=>h.value?"preview":"start"),H=M.computed(()=>!!Y.value.available),jt=M.computed(()=>!!(f.value?.shop||Ke.value).available),Ve=M.computed(()=>!!h.value&&So(i.value,H.value)),W=e=>M.computed({read:()=>i.value[e]!==!1,write:t=>{i.value={...i.peek(),[e]:!!t}}}),P=e=>{i.value={...i.peek(),...e}},te=e=>{try{return mo(e)}catch{return e}},Wt=M.computed(()=>d.value.map(e=>Io(e,te))),Ft=M.computed(()=>_.value.map(e=>Eo(e,te))),F=M.computed(()=>(f.value?.items||[]).map(e=>({...e,choice:i.value.choices?.[e.key]}))),Kt=M.computed(()=>Lo(F.value).map(e=>({...e,cls:D.value===e.id?"is-on":"",seg:"kind"}))),Zt=M.computed(()=>Lt(F.value,Rt,qt,D.value)),Vt=M.computed(()=>Rt.map(e=>({...e,count:Zt.value[e.id],cls:j.value===e.id?"is-on":"",seg:"filter"}))),Se=M.computed(()=>{const e=X.value.trim().toLowerCase();return F.value.filter(t=>(!D.value||t.kind===D.value)&&qt(t,j.value)&&(!e||`${t.title} ${t.target} ${t.oldUrl} ${t.postType||""}`.toLowerCase().includes(e)))}),Gt=M.computed(()=>Se.value.slice(0,ke).map(e=>{const t=Po(e);return{...t,pillCls:`${t.pillClass}${e.choice?" is-chosen":""}`}})),Jt=M.computed(()=>Se.value.length>ke?`Showing the first ${ke} of ${Se.value.length} - search or filter to find the rest.`:""),Yt=M.computed(()=>F.value.length?"Nothing matches.":"This export has nothing to import."),Xt=M.computed(()=>At(f.value)),Qt=M.computed(()=>(f.value?.notes||[]).map(e=>({text:e}))),ea=M.computed(()=>(f.value?.redirects||[]).slice(0,ke).map(e=>({...e,pill:e.action==="skip"?"Skipped":e.action==="update"?"Update":"New",pillClass:e.action==="skip"?"is-muted":"is-ok"}))),ta=M.computed(()=>String((f.value?.redirects||[]).filter(e=>e.action!=="skip").length)),aa=M.computed(()=>f.value?.site?.title||ee.value?.siteTitle||"WordPress export"),oa=M.computed(()=>{const e=ee.value,t=f.value?.site;return[t?.url||e?.siteUrl,e?.fileName,t?.wxrVersion&&`WXR ${t.wxrVersion}`,e?.at&&`uploaded ${te(e.at)}`].filter(Boolean).join(" \xB7 ")}),ae=M.computed(()=>Co(f.value)),sa=M.computed(()=>ae.value?.note||""),na=M.computed(()=>ae.value?.tone==="warn"?"is-warn":"is-info"),la=M.computed(()=>(ae.value?.rows||[]).map(e=>({...e,id:e.label}))),ra=M.computed(()=>(ae.value?.notes||[]).map(e=>({text:e}))),ia=M.computed(()=>!!ae.value),T=M.computed(()=>fo(f.value)),pa=M.computed(()=>!!T.value),ca=M.computed(()=>T.value?.wp||""),da=M.computed(()=>T.value?.now||""),ua=M.computed(()=>T.value?.after||""),ma=M.computed(()=>T.value?.tone==="warn"?"":"is-info"),ha=M.computed(()=>(T.value?.notes||[]).map(e=>({text:e}))),va=M.computed(()=>T.value?.editNow||""),wa=M.computed(()=>!!T.value?.frontSwitch),fa=M.computed(()=>!!T.value?.latestSwitch),ba=M.computed(()=>!!T.value?.postsSwitch),ga=M.computed(()=>T.value?.postsText||""),ya=M.computed({read:()=>i.value.latestPosts===!0,write:e=>P({latestPosts:!!e})}),xa=M.computed({read:()=>!!T.value?.postsId&&i.value.postsPage===T.value.postsId,write:e=>P({postsPage:e&&T.peek()?.postsId||0})}),Le=M.computed(()=>wo(f.value)),Pe=M.computed(()=>$o(f.value)),ka=M.computed(()=>!!(Le.value.length||Pe.value.length)),Ma=M.computed(()=>{const e=Pe.value.length;return e?`Not imported: ${e} kind${e===1?"":"s"} of WordPress or plugin housekeeping`:""}),$=M.computed(()=>f.value?.people||null),Ta=M.computed(()=>!!$.value?.rows?.length),Ea=M.computed(()=>($.value?.rows||[]).map(xo)),$a=M.computed(()=>!!$.value?.canCreate),Sa=M.computed(()=>($.value?.rows||[]).some(e=>e.choosable)),Ge=M.computed(()=>go($.value)),La=M.computed(()=>Ge.value.text),Pa=M.computed(()=>Ge.value.tone==="warn"?"is-warn":"is-info"),Ca=M.computed(()=>!!($.value?.canCreate&&$.value?.mailReady&&$.value?.counts?.create)),Ra=M.computed(()=>bo($.value)),qa=M.computed({read:()=>i.value.invite===!0,write:e=>{i.value={...i.peek(),invite:!!e}}}),Aa=M.computed(()=>H.value?`Posts go to ${Y.value.basePath||"/blog"}.`:Y.value.reason||"No Blog is switched on here."),he=M.computed(()=>ee.value?.uploads||null),Ia=M.computed(()=>{const e=he.value;return e?`${e.fileName} - ${e.files} file${e.files===1?"":"s"}, ${Me(e.size)}${e.unsafe?`. ${e.unsafe} with unsafe names (like ../) were ignored`:""}.`:`Zip the wp-content/uploads folder and upload it here (up to ${Me(z.uploads||0)}).`}),Na=M.computed(()=>he.value?"Replace the zip":"Upload the uploads zip"),Je=M.computed(()=>ee.value?.widgets||null),Oa=M.computed(()=>Bo(ee.value,z.widgets||0)),Ba=M.computed(()=>Je.value?"Replace the widgets file":"Upload the widgets file"),Ce=M.observable(""),Ye=M.computed(()=>Oo(f.value)),Xe=M.computed(()=>!!f.value&&!y.value&&!pe.value&&!(i.value.media==="zip"&&!he.value)),Da=M.computed(()=>y.value?`Importing${y.value.siteTitle?` ${y.value.siteTitle}`:""}\u2026`:""),Ha=M.computed(()=>qo(y.value)),Qe=M.computed(()=>Ro(y.value)),Ua=M.observable(Me(z.wxr||0)),et=l.querySelector(".wpx-running .wpx-bar-fill");b.add(M.effect(()=>{et&&(et.style.width=`${Qe.value}%`)}));async function Re(){const e=await R(`${L}/status`);return p()?(d.value=e.staged||[],_.value=e.history||[],Y.value=e.blog||{available:!1},Ke.value=e.shop||{available:!1},e.running&&!y.peek()&&(y.value=e.running,se()),e):null}let ve=0,qe=null;async function K({first:e=!1}={}){const t=h.peek();if(!t)return;const a=++ve;$e.value="";const o=R(`${L}/staged/${t}/preview`,{method:"POST",body:JSON.stringify({options:xe(i.peek(),H.peek())})});try{let r;if(e?(pe.value=!0,r=await Et(l.querySelector(".wpx-plan-loading"),o,Tt.table)):(Ee.value=!0,r=await o),a!==ve||!p()||h.peek()!==t)return;f.value=r.plan}catch(r){if(a!==ve||!p())return;if(r.status===404){E.toast("That upload has gone - upload the export again.",{type:"warning"}),oe(),Re().catch(()=>{});return}$e.value=`Could not read the export: ${r.message||r}`}finally{a===ve&&(pe.value=!1,Ee.value=!1)}}let we="";b.add(M.effect(()=>{const e=`${h.value}|${Ct(i.value)}`;if(e===we)return;const t=we.split("|")[0];we=e,!(!h.value||t!==h.value)&&(clearTimeout(qe),qe=setTimeout(()=>K(),250))})),b.add(()=>clearTimeout(qe)),b.add(M.effect(()=>{const e=h.value,t=i.value,a=Ve.value;if(!e||!me||!k)return;const o=v();A(Ot(o,g,e,a?{options:xe(t,!0),at:new Date().toISOString(),was:e}:null))}));const Ae=e=>{k&&A(Ot(v(),g,e,null))};async function fe(e){if(!d.peek().some(c=>c.id===e))return;const t=d.peek().find(c=>c.id===e),a=Pt(v(),d.peek().map(c=>c.id),g).items[e];if(me=!a,f.value=null,j.value="all",D.value="",X.value="",i.value=_e(H.peek()),we=`${e}|${Ct(i.peek())}`,h.value=e,await K({first:!0}),!p()||h.peek()!==e||!a)return;await new Promise(c=>setTimeout(c,0));const o=To({when:a.at?te(a.at):"",file:t?.fileName||""}),r=await Te(J(o.text),{title:o.title,confirmText:o.confirmText,cancelText:o.cancelText});!p()||h.peek()!==e||(me=!0,r?Ae(e):i.value=xe(a.options,H.peek()))}function oe(){h.value="",f.value=null,me=!0}async function tt(e=h.peek()){const t=d.peek().find(a=>a.id===e);if(t&&await Te(`Discard the upload of <strong>${J(t.siteTitle||t.fileName)}</strong>? Nothing has been imported from it.`,{title:"Discard upload",confirmText:"Discard",danger:!0}))try{if(await R(`${L}/staged/${e}`,{method:"DELETE"}),Ae(e),!p())return;h.peek()===e&&oe(),d.value=d.peek().filter(a=>a.id!==e),q(ie)}catch(a){E.toast(`Not discarded: ${a.message||a}`,{type:"error"})}}async function at(e){if(!e)return;if(Q.value="",!/\.(xml|zip)$/i.test(e.name)){Q.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)){Q.value=`That file is over ${Me(z.wxr)}.`;return}const t=l.querySelector(".wpx-start .wpx-upload .wpx-bar-fill");ce.value=`Uploading ${e.name}\u2026`;try{const a=await We(`${L}/upload`,e,o=>{t&&(t.style.width=`${Math.round(o*100)}%`),ce.value=o<1?`Uploading ${e.name} - ${Math.round(o*100)}%`:`Reading ${e.name}\u2026`});if(!p())return;d.value=[a,...d.peek().filter(o=>o.id!==a.id)],q(ie),await fe(a.id)}catch(a){p()&&(Q.value=a.message||String(a))}finally{p()&&(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");de.value=`Uploading ${e.name}\u2026`;try{const o=await We(`${L}/staged/${t}/uploads`,e,r=>{a&&(a.style.width=`${Math.round(r*100)}%`),de.value=r<1?`Uploading ${e.name} - ${Math.round(r*100)}%`:`Checking ${e.name}\u2026`});if(!p())return;d.value=d.peek().map(r=>r.id===o.id?o:r),h.peek()===t&&K()}catch(o){p()&&E.toast(o.message||String(o),{type:"error"})}finally{p()&&(de.value="",a&&(a.style.width="0"))}}async function za(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}Ce.value=`Reading ${e.name}\u2026`;try{const a=await We(`${L}/staged/${t}/widgets`,e,()=>{});if(!p())return;d.value=d.peek().map(o=>o.id===a.id?a:o),h.peek()===t&&K()}catch(a){p()&&E.toast(a.message||String(a),{type:"error"})}finally{p()&&(Ce.value="")}}}async function ja(){const e=h.peek();try{const t=await R(`${L}/staged/${e}/widgets`,{method:"DELETE"});if(!p())return;d.value=d.peek().map(a=>a.id===t.id?t:a),K()}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error"})}}async function Wa(){const e=h.peek();try{const t=await R(`${L}/staged/${e}/uploads`,{method:"DELETE"});if(!p())return;d.value=d.peek().map(a=>a.id===t.id?t:a),K()}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error"})}}async function Fa(){const e=h.peek(),t=f.peek();if(!e||!t||!Xe.peek())return;const o=At(t).filter(c=>c.value&&c.id!=="comments").map(c=>`${c.value} ${c.label.toLowerCase()}`);if(!(!await Te(`Import <strong>${J(t.site?.title||"this export")}</strong>: ${J(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 R(`${L}/staged/${e}/run`,{method:"POST",body:JSON.stringify({options:xe(i.peek(),H.peek())})});if(Ae(e),!p())return;y.value={...c.job,siteTitle:t.site?.title||""},oe(),q(ie),se()}catch(c){E.toast(`The import did not start: ${c.message||c}`,{type:"error"})}}let Ie=null;function se(){clearTimeout(Ie);const e=y.peek();e&&(Ie=setTimeout(async()=>{try{const t=await R(`${L}/jobs/${e.id}`);if(!p())return;if(y.value={...t.job,siteTitle:e.siteTitle},t.job.status==="running")return se();if(y.value=null,await Re().catch(()=>{}),q(ie),q("#/pages"),q("#/media"),q("#/plugins/shopping-cart"),q("#/collections"),q("#/blocks"),!p())return;t.job.status==="done"?(E.toast("The import has finished - here is its report.",{type:"success"}),be(t.job.reportId)):(E.toast(`The import stopped: ${t.job.error||"an error"}`,{type:"error"}),t.job.reportId&&be(t.job.reportId))}catch(t){if(!p())return;if(t.status===404){y.value=null,Re().catch(()=>{});return}se()}},700))}b.add(()=>clearTimeout(Ie)),y.peek()&&se();let Z=!1;async function be(e){if(Z||!e)return;Z=!0;let t;try{t=await R(`${L}/history/${e}`)}catch(s){Z=!1,E.toast(`Could not open the report: ${s.message||s}`,{type:"error"});return}if(!p()){Z=!1;return}let a;try{a=E.slideover({title:J(`Report - ${t.run?.siteTitle||"WordPress import"}`),size:"lg",position:"right"})}catch(s){throw Z=!1,s}const o=Vo(`
79
+ </div>`,zt='<div class="wpx-tile"><strong>{{value}}</strong><small>{{label}} <span data-bind-hidden="!sub">{{sub}}</span></small></div>',ze='<p class="wpx-alert"><span>{{text}}</span></p>',jt="<li>{{text}}</li>",Go='<div class="wpx-fact"><dt>{{label}}</dt><dd>{{value}}</dd></div>',je='<button type="button" data-on-click="$root.pickSeg($data)" data-bind-class="cls">{{label}} <b>{{count}}</b></button>',b=vo(Ht);function We(u,l,p){return new Promise((g,T)=>{const v=new XMLHttpRequest,A=new FormData;A.append("file",l),v.open("POST",`/api${u}`);const w=uo();w&&v.setRequestHeader("Authorization",`Bearer ${w}`),v.upload.onprogress=m=>{m.lengthComputable&&p(m.loaded/m.total)},v.onload=()=>{let m=null;try{m=JSON.parse(v.responseText)}catch{}v.status>=200&&v.status<300?g(m):T(new Error(m?.error||`The upload failed (${v.status}).`))},v.onerror=()=>T(new Error("The upload failed - the connection dropped.")),v.send(A),b.add(()=>{v.readyState!==4&&v.abort()})})}const Me=u=>u>=1048576?`${Math.round(u/1048576)} MB`:`${Math.max(1,Math.round(u/1024))} KB`,G=u=>String(u??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");function Jo(u){const l=document.createElement("template");return l.innerHTML=u.trim(),l.content.firstElementChild}async function Te(u,l){const p=E.confirm(u,l);let g=null;try{g=[...document.querySelectorAll(".dm-dialog-overlay")].at(-1)??null}catch{}const T=b.add(()=>{if(!g)return;const v=g;g=null;try{v.querySelector(".dm-dialog-cancel")?.click(),v.remove()}catch{}});try{return await p}finally{g=null,T()}}function Fe(u){navigator.clipboard?.writeText(u).then(()=>E.toast("Copied.",{type:"success"}),()=>E.toast(u,{type:"info",duration:8e3}))}function U(u,l,p,g){const T=E.contextMenu(u,{cursor:"glyph",match:l,inherit:!1,density:"compact",items:p});b.add(()=>T?.destroy?.());const v=w=>{const m=w.target.closest(l);if(!m||!u.contains(m))return;const d=w.target.closest(".wpx-kebab");if(d){B(d);return}g?.(m)},A=w=>{const m=w.target.closest(l);m&&(w.key==="F10"&&w.shiftKey?(w.preventDefault(),B(m)):w.key==="Enter"&&w.target===m&&g&&(w.preventDefault(),w.stopPropagation(),g(m)))};u.addEventListener("click",v),u.addEventListener("keydown",A),b.add(()=>{u.removeEventListener("click",v),u.removeEventListener("keydown",A)})}export const wordpressImportView={templateUrl:"/admin/js/templates/wordpress-import.html",async onMount(u){b.reset();const l=u.get(0).querySelector(".wpx")||u.get(0),p=()=>Ht.test(location.hash)&&l.isConnected;let g="";try{g=S.get("auth_user")?.id||""}catch{}const T=$o(g),v=()=>{try{return T?S.get(T):null}catch{return null}},A=e=>{try{T&&S.set(T,e)}catch{}},w=l.querySelector(".wpx-loading");let m;try{m=await St(w,R(`${P}/status`),$t.rows)}catch(e){if(!l.isConnected)return;w&&(w.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(!p())return;w&&(w.hidden=!0),l.querySelector(".wpx-body").hidden=!1;const d=M.observable(m.staged||[]),_=M.observable(m.history||[]),J=M.observable(m.blog||{available:!1}),Ke=M.observable(m.shop||{available:!1}),z=m.limits||{},Ze=m.people?.defaultRole||"",y=M.observable(m.running||null),h=M.observable(""),f=M.observable(null),pe=M.observable(!1),Ee=M.observable(!1),$e=M.observable(""),i=M.observable(_e(J.peek().available)),j=M.observable("all"),D=M.observable(""),Q=M.observable(""),ce=M.observable(""),X=M.observable(""),de=M.observable(""),ue=M.observable(!1);let me=!0;const Qo=()=>d.peek().find(e=>e.id===h.peek())||null,ee=M.computed(()=>d.value.find(e=>e.id===h.value)||null),Wt=M.computed(()=>h.value?"preview":"start"),H=M.computed(()=>!!J.value.available),Ft=M.computed(()=>!!(f.value?.shop||Ke.value).available),Ve=M.computed(()=>!!h.value&&Co(i.value,H.value)),W=e=>M.computed({read:()=>i.value[e]!==!1,write:t=>{i.value={...i.peek(),[e]:!!t}}}),$=e=>{i.value={...i.peek(),...e}},te=e=>{try{return wo(e)}catch{return e}},Kt=M.computed(()=>d.value.map(e=>Bo(e,te))),Zt=M.computed(()=>_.value.map(e=>Lo(e,te))),F=M.computed(()=>(f.value?.items||[]).map(e=>({...e,choice:i.value.choices?.[e.key]}))),Vt=M.computed(()=>Ro(F.value).map(e=>({...e,cls:D.value===e.id?"is-on":"",seg:"kind"}))),Yt=M.computed(()=>Ct(F.value,At,It,D.value)),Gt=M.computed(()=>At.map(e=>({...e,count:Yt.value[e.id],cls:j.value===e.id?"is-on":"",seg:"filter"}))),Se=M.computed(()=>{const e=Q.value.trim().toLowerCase();return F.value.filter(t=>(!D.value||t.kind===D.value)&&It(t,j.value)&&(!e||`${t.title} ${t.target} ${t.oldUrl} ${t.postType||""}`.toLowerCase().includes(e)))}),Jt=M.computed(()=>Se.value.slice(0,ke).map(e=>{const t=qo(e);return{...t,pillCls:`${t.pillClass}${e.choice?" is-chosen":""}`}})),Qt=M.computed(()=>Se.value.length>ke?`Showing the first ${ke} of ${Se.value.length} - search or filter to find the rest.`:""),Xt=M.computed(()=>F.value.length?"Nothing matches.":"This export has nothing to import."),ea=M.computed(()=>Nt(f.value)),ta=M.computed(()=>(f.value?.notes||[]).map(e=>({text:e}))),aa=M.computed(()=>(f.value?.redirects||[]).slice(0,ke).map(e=>({...e,pill:e.action==="skip"?"Skipped":e.action==="update"?"Update":"New",pillClass:e.action==="skip"?"is-muted":"is-ok"}))),oa=M.computed(()=>String((f.value?.redirects||[]).filter(e=>e.action!=="skip").length)),sa=M.computed(()=>f.value?.site?.title||ee.value?.siteTitle||"WordPress export"),na=M.computed(()=>{const e=ee.value,t=f.value?.site;return[t?.url||e?.siteUrl,e?.fileName,t?.wxrVersion&&`WXR ${t.wxrVersion}`,e?.at&&`uploaded ${te(e.at)}`].filter(Boolean).join(" \xB7 ")}),ae=M.computed(()=>Ao(f.value)),la=M.computed(()=>ae.value?.note||""),ra=M.computed(()=>ae.value?.tone==="warn"?"is-warn":"is-info"),ia=M.computed(()=>(ae.value?.rows||[]).map(e=>({...e,id:e.label}))),pa=M.computed(()=>(ae.value?.notes||[]).map(e=>({text:e}))),ca=M.computed(()=>!!ae.value),x=M.computed(()=>yo(f.value)),da=M.computed(()=>!!x.value),ua=M.computed(()=>x.value?.wp||""),ma=M.computed(()=>x.value?.now||""),ha=M.computed(()=>x.value?.after||""),va=M.computed(()=>x.value?.tone==="warn"?"":"is-info"),wa=M.computed(()=>(x.value?.notes||[]).map(e=>({text:e}))),fa=M.computed(()=>x.value?.editNow||""),ba=M.computed(()=>!!x.value?.frontSwitch),ga=M.computed(()=>!!x.value?.latestSwitch),ya=M.computed(()=>!!x.value?.postsSwitch),xa=M.computed(()=>x.value?.postsText||""),Ye=M.computed(()=>x.value?.question||""),Ge=M.computed(()=>!!Ye.value&&typeof i.value.homeReplace!="boolean"),ka=M.computed({read:()=>i.value.latestPosts===!0,write:e=>$({latestPosts:!!e})}),Ma=M.computed({read:()=>!!x.value?.postsId&&i.value.postsPage===x.value.postsId,write:e=>$({postsPage:e&&x.peek()?.postsId||0})}),Le=M.computed(()=>go(f.value)),Pe=M.computed(()=>Po(f.value)),Ta=M.computed(()=>!!(Le.value.length||Pe.value.length)),Ea=M.computed(()=>{const e=Pe.value.length;return e?`Not imported: ${e} kind${e===1?"":"s"} of WordPress or plugin housekeeping`:""}),L=M.computed(()=>f.value?.people||null),$a=M.computed(()=>!!L.value?.rows?.length),Sa=M.computed(()=>(L.value?.rows||[]).map(To)),La=M.computed(()=>!!L.value?.canCreate),Pa=M.computed(()=>(L.value?.rows||[]).some(e=>e.choosable)),Je=M.computed(()=>ko(L.value)),Ca=M.computed(()=>Je.value.text),Ra=M.computed(()=>Je.value.tone==="warn"?"is-warn":"is-info"),qa=M.computed(()=>!!(L.value?.canCreate&&L.value?.mailReady&&L.value?.counts?.create)),Aa=M.computed(()=>xo(L.value)),Ia=M.computed({read:()=>i.value.invite===!0,write:e=>{i.value={...i.peek(),invite:!!e}}}),Na=M.computed(()=>H.value?`Posts go to ${J.value.basePath||"/blog"}.`:J.value.reason||"No Blog is switched on here."),he=M.computed(()=>ee.value?.uploads||null),Oa=M.computed(()=>{const e=he.value;return e?`${e.fileName} - ${e.files} file${e.files===1?"":"s"}, ${Me(e.size)}${e.unsafe?`. ${e.unsafe} with unsafe names (like ../) were ignored`:""}.`:`Zip the wp-content/uploads folder and upload it here (up to ${Me(z.uploads||0)}).`}),Ba=M.computed(()=>he.value?"Replace the zip":"Upload the uploads zip"),Qe=M.computed(()=>ee.value?.widgets||null),Da=M.computed(()=>Uo(ee.value,z.widgets||0)),Ha=M.computed(()=>Qe.value?"Replace the widgets file":"Upload the widgets file"),Ce=M.observable(""),Xe=M.computed(()=>Ho(f.value)),Ua=M.computed(()=>Ge.value?"Answer the home page question first.":""),et=M.computed(()=>!!f.value&&!y.value&&!pe.value&&!(i.value.media==="zip"&&!he.value)&&!Ge.value),_a=M.computed(()=>y.value?`Importing${y.value.siteTitle?` ${y.value.siteTitle}`:""}\u2026`:""),za=M.computed(()=>No(y.value)),tt=M.computed(()=>Io(y.value)),ja=M.observable(Me(z.wxr||0)),at=l.querySelector(".wpx-running .wpx-bar-fill");b.add(M.effect(()=>{at&&(at.style.width=`${tt.value}%`)}));async function Re(){const e=await R(`${P}/status`);return p()?(d.value=e.staged||[],_.value=e.history||[],J.value=e.blog||{available:!1},Ke.value=e.shop||{available:!1},e.running&&!y.peek()&&(y.value=e.running,se()),e):null}let ve=0,qe=null;async function K({first:e=!1}={}){const t=h.peek();if(!t)return;const a=++ve;$e.value="";const o=R(`${P}/staged/${t}/preview`,{method:"POST",body:JSON.stringify({options:xe(i.peek(),H.peek())})});try{let r;if(e?(pe.value=!0,r=await St(l.querySelector(".wpx-plan-loading"),o,$t.table)):(Ee.value=!0,r=await o),a!==ve||!p()||h.peek()!==t)return;f.value=r.plan}catch(r){if(a!==ve||!p())return;if(r.status===404){E.toast("That upload has gone - upload the export again.",{type:"warning"}),oe(),Re().catch(()=>{});return}$e.value=`Could not read the export: ${r.message||r}`}finally{a===ve&&(pe.value=!1,Ee.value=!1)}}let we="";b.add(M.effect(()=>{const e=`${h.value}|${qt(i.value)}`;if(e===we)return;const t=we.split("|")[0];we=e,!(!h.value||t!==h.value)&&(clearTimeout(qe),qe=setTimeout(()=>K(),250))})),b.add(()=>clearTimeout(qe)),b.add(M.effect(()=>{const e=h.value,t=i.value,a=Ve.value;if(!e||!me||!T)return;const o=v();A(Dt(o,g,e,a?{options:xe(t,!0),at:new Date().toISOString(),was:e}:null))}));const Ae=e=>{T&&A(Dt(v(),g,e,null))};async function fe(e){if(!d.peek().some(c=>c.id===e))return;const t=d.peek().find(c=>c.id===e),a=Rt(v(),d.peek().map(c=>c.id),g).items[e];if(me=!a,f.value=null,j.value="all",D.value="",Q.value="",i.value=_e(H.peek()),we=`${e}|${qt(i.peek())}`,h.value=e,await K({first:!0}),!p()||h.peek()!==e||!a)return;await new Promise(c=>setTimeout(c,0));const o=So({when:a.at?te(a.at):"",file:t?.fileName||""}),r=await Te(G(o.text),{title:o.title,confirmText:o.confirmText,cancelText:o.cancelText});!p()||h.peek()!==e||(me=!0,r?Ae(e):i.value=xe(a.options,H.peek()))}function oe(){h.value="",f.value=null,me=!0}async function ot(e=h.peek()){const t=d.peek().find(a=>a.id===e);if(t&&await Te(`Discard the upload of <strong>${G(t.siteTitle||t.fileName)}</strong>? Nothing has been imported from it.`,{title:"Discard upload",confirmText:"Discard",danger:!0}))try{if(await R(`${P}/staged/${e}`,{method:"DELETE"}),Ae(e),!p())return;h.peek()===e&&oe(),d.value=d.peek().filter(a=>a.id!==e),q(ie)}catch(a){E.toast(`Not discarded: ${a.message||a}`,{type:"error"})}}async function st(e){if(!e)return;if(X.value="",!/\.(xml|zip)$/i.test(e.name)){X.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)){X.value=`That file is over ${Me(z.wxr)}.`;return}const t=l.querySelector(".wpx-start .wpx-upload .wpx-bar-fill");ce.value=`Uploading ${e.name}\u2026`;try{const a=await We(`${P}/upload`,e,o=>{t&&(t.style.width=`${Math.round(o*100)}%`),ce.value=o<1?`Uploading ${e.name} - ${Math.round(o*100)}%`:`Reading ${e.name}\u2026`});if(!p())return;d.value=[a,...d.peek().filter(o=>o.id!==a.id)],q(ie),await fe(a.id)}catch(a){p()&&(X.value=a.message||String(a))}finally{p()&&(ce.value="",t&&(t.style.width="0"))}}async function Wa(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");de.value=`Uploading ${e.name}\u2026`;try{const o=await We(`${P}/staged/${t}/uploads`,e,r=>{a&&(a.style.width=`${Math.round(r*100)}%`),de.value=r<1?`Uploading ${e.name} - ${Math.round(r*100)}%`:`Checking ${e.name}\u2026`});if(!p())return;d.value=d.peek().map(r=>r.id===o.id?o:r),h.peek()===t&&K()}catch(o){p()&&E.toast(o.message||String(o),{type:"error"})}finally{p()&&(de.value="",a&&(a.style.width="0"))}}async function Fa(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}Ce.value=`Reading ${e.name}\u2026`;try{const a=await We(`${P}/staged/${t}/widgets`,e,()=>{});if(!p())return;d.value=d.peek().map(o=>o.id===a.id?a:o),h.peek()===t&&K()}catch(a){p()&&E.toast(a.message||String(a),{type:"error"})}finally{p()&&(Ce.value="")}}}async function Ka(){const e=h.peek();try{const t=await R(`${P}/staged/${e}/widgets`,{method:"DELETE"});if(!p())return;d.value=d.peek().map(a=>a.id===t.id?t:a),K()}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error"})}}async function Za(){const e=h.peek();try{const t=await R(`${P}/staged/${e}/uploads`,{method:"DELETE"});if(!p())return;d.value=d.peek().map(a=>a.id===t.id?t:a),K()}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error"})}}async function Va(){const e=h.peek(),t=f.peek();if(!e||!t||!et.peek())return;const o=Nt(t).filter(c=>c.value&&c.id!=="comments").map(c=>`${c.value} ${c.label.toLowerCase()}`);if(!(!await Te(`Import <strong>${G(t.site?.title||"this export")}</strong>: ${G(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 R(`${P}/staged/${e}/run`,{method:"POST",body:JSON.stringify({options:xe(i.peek(),H.peek())})});if(Ae(e),!p())return;y.value={...c.job,siteTitle:t.site?.title||""},oe(),q(ie),se()}catch(c){E.toast(`The import did not start: ${c.message||c}`,{type:"error"})}}let Ie=null;function se(){clearTimeout(Ie);const e=y.peek();e&&(Ie=setTimeout(async()=>{try{const t=await R(`${P}/jobs/${e.id}`);if(!p())return;if(y.value={...t.job,siteTitle:e.siteTitle},t.job.status==="running")return se();if(y.value=null,await Re().catch(()=>{}),q(ie),q("#/pages"),q("#/media"),q("#/plugins/shopping-cart"),q("#/collections"),q("#/blocks"),!p())return;t.job.status==="done"?(E.toast("The import has finished - here is its report.",{type:"success"}),be(t.job.reportId)):(E.toast(`The import stopped: ${t.job.error||"an error"}`,{type:"error"}),t.job.reportId&&be(t.job.reportId))}catch(t){if(!p())return;if(t.status===404){y.value=null,Re().catch(()=>{});return}se()}},700))}b.add(()=>clearTimeout(Ie)),y.peek()&&se();let Z=!1;async function be(e){if(Z||!e)return;Z=!0;let t;try{t=await R(`${P}/history/${e}`)}catch(s){Z=!1,E.toast(`Could not open the report: ${s.message||s}`,{type:"error"});return}if(!p()){Z=!1;return}let a;try{a=E.slideover({title:G(`Report - ${t.run?.siteTitle||"WordPress import"}`),size:"lg",position:"right"})}catch(s){throw Z=!1,s}const o=Jo(`
80
80
  <div class="wpx wpx-report">
81
81
  <p class="wpx-note" data-bind-text="meta.value"></p>
82
82
  <div class="wpx-tiles" data-each="tiles.value key=id"></div>
@@ -92,4 +92,4 @@ import{apiRequest as R,getToken as io}from"../api.js";import{addBannerAction as
92
92
  <div class="wpx-list wpx-report-people" data-each="people.value key=key"></div>
93
93
  <div class="wpx-card-title" data-bind-hidden="!redirects.value.length">Redirects</div>
94
94
  <div class="wpx-list wpx-report-redirects" data-each="redirects.value key=from"></div>
95
- </div>`);o.querySelector(".wpx-tiles").innerHTML=Ut,o.querySelector(".wpx-note-list").innerHTML=ze,o.querySelector(".wpx-seg").innerHTML=je,o.querySelector(".wpx-report-items").innerHTML=Dt,o.querySelector(".wpx-report-redirects").innerHTML=Ht,o.querySelector(".wpx-report-people").innerHTML=Fo;const r=M.observable(t.run?.summary?.failed?"failed":"all"),c=/^\/[a-z0-9][a-z0-9/-]{0,200}$/.test(t.home?.previous||"")?t.home.previous:"",De=t.items||[],yt=Lt(De,It,Nt),O=t.run?.summary||{},He=s=>Object.values(O.kinds||{}).reduce((n,x)=>n+(x[s]||0),0),Ja={meta:M.observable([te(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:He("created"),sub:""},{id:"updated",label:"Updated",value:He("updated"),sub:""},{id:"kept",label:"Left as is",value:He("kept"),sub:""},{id:"redirects",label:"Redirects",value:(O.redirects?.created||0)+(O.redirects?.updated||0),sub:""},...O.people?[{id:"people",label:"Accounts made",value:O.people.created||0,sub:O.people.invited?`${O.people.invited} emailed`:""}]:[],{id:"comments",label:"Comments",value:O.comments||0,sub:""},{id:"failed",label:"Failed",value:O.failed||0,sub:""}]),notes:M.observable((t.notes||[]).map(s=>({text:s}))),homePrevious:M.observable(c),homePreviousText:M.observable(c?`This site's home page from before the import${t.home?.previousTitle?` ("${t.home.previousTitle}")`:""} is now a draft at ${c} - nothing was deleted. Publish it there, or delete it, from Pages.`:""),people:M.observable((t.people||[]).map(ko)),filters:M.computed(()=>It.filter(s=>s.id==="all"||yt[s.id]).map(s=>({...s,count:yt[s.id],cls:r.value===s.id?"is-on":""}))),rows:M.computed(()=>De.filter(s=>Nt(s,r.value)).slice(0,1e3).map(s=>{const n=Ao(s);return{...n,pillCls:n.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 Ya=M.applyBindings(Ja,o,{methods:{pickSeg:s=>{r.value=s.id}}}),Xa=Mt(o),xt=new Map(De.map(s=>[s.key,s])),Qa=o.querySelector(".wpx-report-items"),eo=s=>{const n=xt.get(s.dataset.key);if(!n)return[];const x=typeof n.target=="string"&&n.target.startsWith("/")?n.target:"",C=[{header:n.title}];if(x&&n.result!=="failed"&&n.result!=="skipped"&&C.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")&&C.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)&&(C.push({label:"Edit the block",icon:"edit",action:()=>{a.close(),location.hash=`#/blocks/edit/${n.target}`}}),n.footer&&C.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 G=n.kind==="collection"?n.target:n.collection;C.push({label:"Open the collection",icon:"database",action:()=>{a.close(),location.hash=`#/collections/${G}/entries`}})}return n.kind==="product"&&n.entryId&&/^[\w-]{1,64}$/.test(n.entryId)&&C.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)&&C.push({label:"Open the old address",icon:"link",action:()=>window.open(n.oldUrl,"_blank","noopener,noreferrer")}),x&&C.push({label:"Copy the new address",icon:"copy",action:()=>Fe(new URL(x,location.origin).href)}),C},to=E.contextMenu(Qa,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:eo}),ao=o.querySelector(".wpx-report-people"),oo=new Map((t.people||[]).map(s=>[s.key,s])),so=E.contextMenu(ao,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:s=>{const n=oo.get(s.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"}}]}}),no=o.querySelector(".wpx-report-redirects"),lo=E.contextMenu(no,{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"}}]}),Ue=s=>{if(s.target.closest(".wpx-open-previous")&&c){a.close(),location.hash=`#/pages/edit${c}`;return}const n=s.target.closest(".wpx-kebab");if(n){B(n);return}const x=s.target.closest(".wpx-report-people .wpx-row");if(x){B(x.querySelector(".wpx-kebab")||x);return}const C=s.target.closest(".wpx-report-items .wpx-row");if(!C)return;const G=xt.get(C.dataset.key);G?.target?.startsWith("/")&&G.result!=="failed"&&G.result!=="skipped"&&window.open(G.target,"_blank","noopener")},kt=s=>{const n=s.target.closest(".wpx-row");n&&(s.key==="F10"&&s.shiftKey?(s.preventDefault(),B(n)):s.key==="Enter"&&s.target===n&&(s.preventDefault(),s.stopPropagation(),Ue({target:n})))};o.addEventListener("click",Ue),o.addEventListener("keydown",kt);const ro=b.add(()=>a.close());co(a,()=>{Z=!1,Ya.dispose(),Xa(),to?.destroy?.(),lo?.destroy?.(),so?.destroy?.(),o.removeEventListener("click",Ue),o.removeEventListener("keydown",kt),ro()}),a.open(),I.scan(o)}async function Ka(e){const t=_.peek().find(a=>a.id===e);if(t&&await Te(`Remove the import of <strong>${J(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 R(`${L}/history/${e}`,{method:"DELETE"}),!p())return;_.value=_.peek().filter(a=>a.id!==e),q(ie)}catch(a){E.toast(`Not removed: ${a.message||a}`,{type:"error"})}}l.querySelector(".wpx-staged").innerHTML=_o,l.querySelector(".wpx-items").innerHTML=Dt,l.querySelector(".wpx-redirects").innerHTML=Ht,l.querySelector(".wpx-history").innerHTML=zo,l.querySelector(".wpx-tiles").innerHTML=Ut,l.querySelector(".wpx-note-list").innerHTML=ze,l.querySelector(".wpx-home-notes").innerHTML=ze,l.querySelector(".wpx-kinds").innerHTML=je,l.querySelector(".wpx-filters").innerHTML=je,l.querySelector(".wpx-facts").innerHTML=Zo,l.querySelector(".wpx-product-notes").innerHTML=_t,l.querySelector(".wpx-types").innerHTML=jo,l.querySelector(".wpx-internal").innerHTML=_t,l.querySelector(".wpx-people-list").innerHTML=Wo,l.querySelector(".wpx-widget-areas").innerHTML=Ko;const Za={mode:zt,job:y,jobTitle:Da,jobText:Ha,jobPct:Qe,staged:d,stagedRows:Wt,historyRows:Ft,plan:f,planLoading:pe,planError:$e,refreshing:Ee,opt:i,blogAvailable:H,blogNote:Aa,dirty:Ve,canRun:Xe,siteTitle:aa,siteMeta:oa,tiles:Xt,notes:Qt,kinds:Kt,filters:Vt,rows:Gt,moreText:Jt,emptyText:Yt,redirectRows:ea,redirectCount:ta,query:X,uploadText:ce,uploadError:Q,dropping:ue,wxrLimit:Ua,zipInfo:he,zipText:Ia,zipButton:Na,zipUploadText:de,hasPeople:Ta,peopleRows:Ea,peopleCanCreate:$a,peopleChoosable:Sa,peopleNote:La,peopleNoteCls:Pa,inviteAvailable:Ca,inviteText:Ra,optInvite:qa,shopAvailable:jt,hasProducts:ia,productNote:sa,productNoteCls:na,productFacts:la,productNotes:ra,typeRows:Le,internalRows:Pe,hasCustom:ka,internalSummary:Ma,widgetsInfo:Je,widgetsText:Oa,widgetsButton:Ba,widgetsUploadText:Ce,widgetRows:Ye,hasHome:pa,homeWp:ca,homeNow:da,homeAfter:ua,homeAfterCls:ma,homeNotes:ha,homeEdit:va,homeFrontSwitch:wa,homeLatestSwitch:fa,homePostsSwitch:ba,homePostsText:ga,optHome:W("home"),optLatest:ya,optPostsPage:xa,optComments:W("comments"),optMenus:W("menus"),optRedirects:W("redirects"),optDrafts:W("drafts"),optProducts:W("products")},Va=M.applyBindings(Za,l,{methods:{back:()=>oe(),discard:()=>tt(),run:()=>Fa(),resetChoices:()=>{i.value=_e(H.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:()=>Wa(),removeWidgets:()=>ja(),peopleAll:()=>P({users:$t($.peek(),"create")}),peopleNone:()=>P({users:$t($.peek(),"skip")}),pickSeg:e=>{e.seg==="kind"?D.value=e.id:j.value=e.id},showProducts:()=>{D.value="product",j.value="all",X.value="",l.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})}}});b.add(()=>Va.dispose()),b.add(Mt(l,{position:"bottom"}));const ne=l.querySelector(".wpx-file"),ot=()=>{const e=ne.files?.[0];ne.value="",at(e)};ne.addEventListener("change",ot);const V=l.querySelector(".wpx-drop"),st=e=>{e.preventDefault(),ue.value=!0},nt=()=>{ue.value=!1},lt=e=>{e.preventDefault(),ue.value=!1,at(e.dataTransfer?.files?.[0])};V.addEventListener("dragover",st),V.addEventListener("dragleave",nt),V.addEventListener("drop",lt);const ge=l.querySelector(".wpx-zip-file"),rt=()=>{const e=ge.files?.[0];ge.value="",_a(e)};ge.addEventListener("change",rt);const ye=l.querySelector(".wpx-wie-file"),it=()=>{const e=ye.files?.[0];ye.value="",za(e)};ye.addEventListener("change",it),b.add(()=>ye.removeEventListener("change",it)),b.add(()=>{ne.removeEventListener("change",ot),V.removeEventListener("dragover",st),V.removeEventListener("dragleave",nt),V.removeEventListener("drop",lt),ge.removeEventListener("change",rt)}),U(l.querySelector(".wpx-staged"),".wpx-row",e=>[{header:d.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Upload"},{label:"Preview the import",icon:"eye",action:()=>fe(e.dataset.id)},{label:"Discard the upload",icon:"trash-2",action:()=>tt(e.dataset.id)}],e=>fe(e.dataset.id));const pt=(e,t)=>{i.value={...i.peek(),choices:ho(i.peek().choices,e,t)}};U(l.querySelector(".wpx-items"),".wpx-row",e=>{const t=F.peek().find(o=>o.key===e.dataset.key);if(!t)return[];const a=[{header:t.title}];for(const o of St(t,t.choice))a.push({label:o.label,icon:o.icon,action:()=>pt(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=>B(e.querySelector(".wpx-kebab")||e));const ct=(e,t)=>{i.value=Uo(i.peek(),e,t)},dt=(e,t)=>{i.value=Ho(i.peek(),e,t)},Ga=e=>{D.value="",j.value="all",X.value=e,l.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})},le=l.querySelector(".wpx-types"),ut=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&&ct(o.dataset.type,a.dataset.as)},mt=e=>{const t=e.target.closest("input[data-list-page]"),a=e.target.closest(".wpx-type-row");t&&a&&dt(a.dataset.type,t.checked)};le.addEventListener("click",ut,!0),le.addEventListener("change",mt),b.add(()=>{le.removeEventListener("click",ut,!0),le.removeEventListener("change",mt)}),U(le,".wpx-type-row",e=>{const t=Le.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"?dt(t.type,a.id==="list"):ct(t.type,a.id)}}))]:[]},e=>B(e.querySelector(".wpx-kebab")||e));const Ne=(e,t)=>{i.value=Do(i.peek(),e,t)},re=l.querySelector(".wpx-widget-areas"),ht=e=>{e.target.closest(".wpx-type-ctl")&&e.stopPropagation()},vt=e=>{const t=e.target.closest("input[data-footer]"),a=e.target.closest(".wpx-widget-row");t&&a&&Ne(a.dataset.area,t.checked)};re.addEventListener("click",ht,!0),re.addEventListener("change",vt),b.add(()=>{re.removeEventListener("click",ht,!0),re.removeEventListener("change",vt)}),U(re,".wpx-widget-row",e=>{const t=Ye.peek().find(r=>r.area===e.dataset.area),a=F.peek().find(r=>r.key===`widgets:${e.dataset.area}`);if(!t)return[];const o=[{header:`${t.label} ("${t.area}")`}];if(t.canPlace&&o.push(t.placeOn?{label:"Leave it out of the footer",icon:"eye-off",action:()=>Ne(t.area,!1)}:{label:"Place in the footer",icon:"layout",action:()=>Ne(t.area,!0)}),a)for(const r of St(a,a.choice))o.push({label:r.label,icon:r.icon,action:()=>pt(a.key,r.id)});return o.length===1&&o.push({label:"Nothing to change here",icon:"info",disabled:!0,action:()=>{}}),o},e=>B(e.querySelector(".wpx-kebab")||e));const wt=(e,t)=>{const a=$.peek()?.rows?.find(o=>o.key===e);a&&P({users:vo(i.peek().users,a,t)})},Oe=l.querySelector(".wpx-people-list"),ft=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&&wt(o.dataset.key,a.dataset.user)};Oe.addEventListener("click",ft,!0),b.add(()=>Oe.removeEventListener("click",ft,!0)),U(Oe,".wpx-person",e=>{const t=$.peek()?.rows?.find(a=>a.key===e.dataset.key);return t?[{header:t.name},...yo(t).map(a=>({label:a.label,icon:a.icon,disabled:!!a.disabled,action:()=>{a.disabled||(a.id==="copy"?Fe(t.email):wt(t.key,a.id))}}))]:[]},e=>B(e.querySelector(".wpx-kebab")||e));const N=l.querySelector(".wpx-role");b.add(M.effect(()=>{const e=$.value;if(!N)return;const t=e?.roles||[],a=e?.role||"",o=`${t.map(r=>r.name).join(",")}|${a}`;N.dataset.sig!==o&&(N.dataset.sig=o,N.replaceChildren(...t.map(r=>{const c=document.createElement("option");return c.value=r.name,c.textContent=r.name===Ze?`${r.label} (default)`:r.label,c})),N.value=a)}));const bt=()=>P({userRole:N.value===Ze?"":N.value});N?.addEventListener("change",bt),b.add(()=>N?.removeEventListener("change",bt)),U(l.querySelector(".wpx-redirects"),".wpx-row",e=>[{header:e.dataset.from},{label:"Copy the old address",icon:"copy",action:()=>Fe(e.dataset.from)}],null),U(l.querySelector(".wpx-history"),".wpx-row",e=>[{header:_.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Import"},{label:"Open the report",icon:"file-text",action:()=>be(e.dataset.id)},{label:"Remove from the history",icon:"trash-2",action:()=>Ka(e.dataset.id)}],e=>be(e.dataset.id)),po(u,{id:"wpx-upload",icon:"upload",title:"Upload an export",onClick:()=>{h.peek()&&oe(),setTimeout(()=>ne.click(),0)}}),I.scan(l);const Be=Pt(v(),d.peek().map(e=>e.id),g);Be.pruned&&k&&A({owner:g,items:Be.items});const gt=Object.entries(Be.items).sort((e,t)=>String(t[1].at).localeCompare(String(e[1].at)))[0];gt&&!y.peek()&&await fe(gt[0])}};
95
+ </div>`);o.querySelector(".wpx-tiles").innerHTML=zt,o.querySelector(".wpx-note-list").innerHTML=ze,o.querySelector(".wpx-seg").innerHTML=je,o.querySelector(".wpx-report-items").innerHTML=Ut,o.querySelector(".wpx-report-redirects").innerHTML=_t,o.querySelector(".wpx-report-people").innerHTML=Vo;const r=M.observable(t.run?.summary?.failed?"failed":"all"),c=/^\/[a-z0-9][a-z0-9/-]{0,200}$/.test(t.home?.previous||"")?t.home.previous:"",De=t.items||[],kt=Ct(De,Ot,Bt),O=t.run?.summary||{},He=s=>Object.values(O.kinds||{}).reduce((n,k)=>n+(k[s]||0),0),Xa={meta:M.observable([te(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:He("created"),sub:""},{id:"updated",label:"Updated",value:He("updated"),sub:""},{id:"kept",label:"Left as is",value:He("kept"),sub:""},{id:"redirects",label:"Redirects",value:(O.redirects?.created||0)+(O.redirects?.updated||0),sub:""},...O.people?[{id:"people",label:"Accounts made",value:O.people.created||0,sub:O.people.invited?`${O.people.invited} emailed`:""}]:[],{id:"comments",label:"Comments",value:O.comments||0,sub:""},{id:"failed",label:"Failed",value:O.failed||0,sub:""}]),notes:M.observable((t.notes||[]).map(s=>({text:s}))),homePrevious:M.observable(c),homePreviousText:M.observable(c?`This site's home page from before the import${t.home?.previousTitle?` ("${t.home.previousTitle}")`:""} is now a draft at ${c} - nothing was deleted. Publish it there, or delete it, from Pages.`:""),people:M.observable((t.people||[]).map(Eo)),filters:M.computed(()=>Ot.filter(s=>s.id==="all"||kt[s.id]).map(s=>({...s,count:kt[s.id],cls:r.value===s.id?"is-on":""}))),rows:M.computed(()=>De.filter(s=>Bt(s,r.value)).slice(0,1e3).map(s=>{const n=Oo(s);return{...n,pillCls:n.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 eo=M.applyBindings(Xa,o,{methods:{pickSeg:s=>{r.value=s.id}}}),to=Et(o),Mt=new Map(De.map(s=>[s.key,s])),ao=o.querySelector(".wpx-report-items"),oo=s=>{const n=Mt.get(s.dataset.key);if(!n)return[];const k=typeof n.target=="string"&&n.target.startsWith("/")?n.target:"",C=[{header:n.title}];if(k&&n.result!=="failed"&&n.result!=="skipped"&&C.push({label:"Open on the site",icon:"external-link",action:()=>window.open(k,"_blank","noopener")}),k&&(n.kind==="page"||n.kind==="custom"&&!n.collection)&&(n.result==="created"||n.result==="updated"||n.result==="kept")&&C.push({label:"Edit the page",icon:"edit",action:()=>{a.close(),location.hash=`#/pages/edit${k}`}}),n.kind==="widgets"&&/^[a-z0-9][a-z0-9-]{0,79}$/.test(n.target||"")&&["created","updated","kept"].includes(n.result)&&(C.push({label:"Edit the block",icon:"edit",action:()=>{a.close(),location.hash=`#/blocks/edit/${n.target}`}}),n.footer&&C.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 Y=n.kind==="collection"?n.target:n.collection;C.push({label:"Open the collection",icon:"database",action:()=>{a.close(),location.hash=`#/collections/${Y}/entries`}})}return n.kind==="product"&&n.entryId&&/^[\w-]{1,64}$/.test(n.entryId)&&C.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)&&C.push({label:"Open the old address",icon:"link",action:()=>window.open(n.oldUrl,"_blank","noopener,noreferrer")}),k&&C.push({label:"Copy the new address",icon:"copy",action:()=>Fe(new URL(k,location.origin).href)}),C},so=E.contextMenu(ao,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:oo}),no=o.querySelector(".wpx-report-people"),lo=new Map((t.people||[]).map(s=>[s.key,s])),ro=E.contextMenu(no,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:s=>{const n=lo.get(s.dataset.key);if(!n)return[];const k=/^[0-9a-f-]{36}$/i.test(n.userId||"")?n.userId:"";return[{header:n.name},...k&&n.result!=="skipped"?[{label:"Open their account",icon:"user",action:()=>{a.close(),location.hash=`#/users/edit/${k}`}}]:[],{label:"Open Users",icon:"users",action:()=>{a.close(),location.hash="#/users"}}]}}),io=o.querySelector(".wpx-report-redirects"),po=E.contextMenu(io,{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"}}]}),Ue=s=>{if(s.target.closest(".wpx-open-previous")&&c){a.close(),location.hash=`#/pages/edit${c}`;return}const n=s.target.closest(".wpx-kebab");if(n){B(n);return}const k=s.target.closest(".wpx-report-people .wpx-row");if(k){B(k.querySelector(".wpx-kebab")||k);return}const C=s.target.closest(".wpx-report-items .wpx-row");if(!C)return;const Y=Mt.get(C.dataset.key);Y?.target?.startsWith("/")&&Y.result!=="failed"&&Y.result!=="skipped"&&window.open(Y.target,"_blank","noopener")},Tt=s=>{const n=s.target.closest(".wpx-row");n&&(s.key==="F10"&&s.shiftKey?(s.preventDefault(),B(n)):s.key==="Enter"&&s.target===n&&(s.preventDefault(),s.stopPropagation(),Ue({target:n})))};o.addEventListener("click",Ue),o.addEventListener("keydown",Tt);const co=b.add(()=>a.close());ho(a,()=>{Z=!1,eo.dispose(),to(),so?.destroy?.(),po?.destroy?.(),ro?.destroy?.(),o.removeEventListener("click",Ue),o.removeEventListener("keydown",Tt),co()}),a.open(),I.scan(o)}async function Ya(e){const t=_.peek().find(a=>a.id===e);if(t&&await Te(`Remove the import of <strong>${G(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 R(`${P}/history/${e}`,{method:"DELETE"}),!p())return;_.value=_.peek().filter(a=>a.id!==e),q(ie)}catch(a){E.toast(`Not removed: ${a.message||a}`,{type:"error"})}}l.querySelector(".wpx-staged").innerHTML=Wo,l.querySelector(".wpx-items").innerHTML=Ut,l.querySelector(".wpx-redirects").innerHTML=_t,l.querySelector(".wpx-history").innerHTML=Fo,l.querySelector(".wpx-tiles").innerHTML=zt,l.querySelector(".wpx-note-list").innerHTML=ze,l.querySelector(".wpx-home-notes").innerHTML=ze,l.querySelector(".wpx-kinds").innerHTML=je,l.querySelector(".wpx-filters").innerHTML=je,l.querySelector(".wpx-facts").innerHTML=Go,l.querySelector(".wpx-product-notes").innerHTML=jt,l.querySelector(".wpx-types").innerHTML=Ko,l.querySelector(".wpx-internal").innerHTML=jt,l.querySelector(".wpx-people-list").innerHTML=Zo,l.querySelector(".wpx-widget-areas").innerHTML=Yo;const Ga={mode:Wt,job:y,jobTitle:_a,jobText:za,jobPct:tt,staged:d,stagedRows:Kt,historyRows:Zt,plan:f,planLoading:pe,planError:$e,refreshing:Ee,opt:i,blogAvailable:H,blogNote:Na,dirty:Ve,canRun:et,siteTitle:sa,siteMeta:na,tiles:ea,notes:ta,kinds:Vt,filters:Gt,rows:Jt,moreText:Qt,emptyText:Xt,redirectRows:aa,redirectCount:oa,query:Q,uploadText:ce,uploadError:X,dropping:ue,wxrLimit:ja,zipInfo:he,zipText:Oa,zipButton:Ba,zipUploadText:de,hasPeople:$a,peopleRows:Sa,peopleCanCreate:La,peopleChoosable:Pa,peopleNote:Ca,peopleNoteCls:Ra,inviteAvailable:qa,inviteText:Aa,optInvite:Ia,shopAvailable:Ft,hasProducts:ca,productNote:la,productNoteCls:ra,productFacts:ia,productNotes:pa,typeRows:Le,internalRows:Pe,hasCustom:Ta,internalSummary:Ea,widgetsInfo:Qe,widgetsText:Da,widgetsButton:Ha,widgetsUploadText:Ce,widgetRows:Xe,hasHome:da,homeWp:ua,homeNow:ma,homeAfter:ha,homeAfterCls:va,homeNotes:wa,homeEdit:fa,homeFrontSwitch:ba,homeLatestSwitch:ga,homePostsSwitch:ya,homePostsText:xa,homeQuestion:Ye,runWhy:Ua,optHome:W("home"),optLatest:ka,optPostsPage:Ma,optComments:W("comments"),optMenus:W("menus"),optRedirects:W("redirects"),optDrafts:W("drafts"),optProducts:W("products")},Ja=M.applyBindings(Ga,l,{methods:{back:()=>oe(),discard:()=>ot(),run:()=>Va(),resetChoices:()=>{i.value=_e(H.peek())},postsBlog:()=>$({posts:"blog"}),postsPages:()=>$({posts:"pages"}),postsSkip:()=>$({posts:"skip"}),mediaDownload:()=>$({media:"download"}),mediaZip:()=>$({media:"zip"}),mediaSkip:()=>$({media:"skip"}),homeYes:()=>$({homeReplace:!0}),homeNo:()=>$({homeReplace:!1}),removeZip:()=>Za(),removeWidgets:()=>Ka(),peopleAll:()=>$({users:Lt(L.peek(),"create")}),peopleNone:()=>$({users:Lt(L.peek(),"skip")}),pickSeg:e=>{e.seg==="kind"?D.value=e.id:j.value=e.id},showProducts:()=>{D.value="product",j.value="all",Q.value="",l.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})}}});b.add(()=>Ja.dispose()),b.add(Et(l,{position:"bottom"}));const ne=l.querySelector(".wpx-file"),nt=()=>{const e=ne.files?.[0];ne.value="",st(e)};ne.addEventListener("change",nt);const V=l.querySelector(".wpx-drop"),lt=e=>{e.preventDefault(),ue.value=!0},rt=()=>{ue.value=!1},it=e=>{e.preventDefault(),ue.value=!1,st(e.dataTransfer?.files?.[0])};V.addEventListener("dragover",lt),V.addEventListener("dragleave",rt),V.addEventListener("drop",it);const ge=l.querySelector(".wpx-zip-file"),pt=()=>{const e=ge.files?.[0];ge.value="",Wa(e)};ge.addEventListener("change",pt);const ye=l.querySelector(".wpx-wie-file"),ct=()=>{const e=ye.files?.[0];ye.value="",Fa(e)};ye.addEventListener("change",ct),b.add(()=>ye.removeEventListener("change",ct)),b.add(()=>{ne.removeEventListener("change",nt),V.removeEventListener("dragover",lt),V.removeEventListener("dragleave",rt),V.removeEventListener("drop",it),ge.removeEventListener("change",pt)}),U(l.querySelector(".wpx-staged"),".wpx-row",e=>[{header:d.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Upload"},{label:"Preview the import",icon:"eye",action:()=>fe(e.dataset.id)},{label:"Discard the upload",icon:"trash-2",action:()=>ot(e.dataset.id)}],e=>fe(e.dataset.id));const dt=(e,t)=>{i.value={...i.peek(),choices:fo(i.peek().choices,e,t)}};U(l.querySelector(".wpx-items"),".wpx-row",e=>{const t=F.peek().find(o=>o.key===e.dataset.key);if(!t)return[];const a=[{header:t.title}];for(const o of Pt(t,t.choice))a.push({label:o.label,icon:o.icon,action:()=>dt(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=>B(e.querySelector(".wpx-kebab")||e));const ut=(e,t)=>{i.value=jo(i.peek(),e,t)},mt=(e,t)=>{i.value=zo(i.peek(),e,t)},Qa=e=>{D.value="",j.value="all",Q.value=e,l.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})},le=l.querySelector(".wpx-types"),ht=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&&ut(o.dataset.type,a.dataset.as)},vt=e=>{const t=e.target.closest("input[data-list-page]"),a=e.target.closest(".wpx-type-row");t&&a&&mt(a.dataset.type,t.checked)};le.addEventListener("click",ht,!0),le.addEventListener("change",vt),b.add(()=>{le.removeEventListener("click",ht,!0),le.removeEventListener("change",vt)}),U(le,".wpx-type-row",e=>{const t=Le.peek().find(a=>a.type===e.dataset.type);return t?[{header:`${t.label} ("${t.type}")`},...Do(t).map(a=>({label:a.label,icon:a.icon,action:()=>{a.id==="show"?Qa(t.type):a.id==="list"||a.id==="nolist"?mt(t.type,a.id==="list"):ut(t.type,a.id)}}))]:[]},e=>B(e.querySelector(".wpx-kebab")||e));const Ne=(e,t)=>{i.value=_o(i.peek(),e,t)},re=l.querySelector(".wpx-widget-areas"),wt=e=>{e.target.closest(".wpx-type-ctl")&&e.stopPropagation()},ft=e=>{const t=e.target.closest("input[data-footer]"),a=e.target.closest(".wpx-widget-row");t&&a&&Ne(a.dataset.area,t.checked)};re.addEventListener("click",wt,!0),re.addEventListener("change",ft),b.add(()=>{re.removeEventListener("click",wt,!0),re.removeEventListener("change",ft)}),U(re,".wpx-widget-row",e=>{const t=Xe.peek().find(r=>r.area===e.dataset.area),a=F.peek().find(r=>r.key===`widgets:${e.dataset.area}`);if(!t)return[];const o=[{header:`${t.label} ("${t.area}")`}];if(t.canPlace&&o.push(t.placeOn?{label:"Leave it out of the footer",icon:"eye-off",action:()=>Ne(t.area,!1)}:{label:"Place in the footer",icon:"layout",action:()=>Ne(t.area,!0)}),a)for(const r of Pt(a,a.choice))o.push({label:r.label,icon:r.icon,action:()=>dt(a.key,r.id)});return o.length===1&&o.push({label:"Nothing to change here",icon:"info",disabled:!0,action:()=>{}}),o},e=>B(e.querySelector(".wpx-kebab")||e));const bt=(e,t)=>{const a=L.peek()?.rows?.find(o=>o.key===e);a&&$({users:bo(i.peek().users,a,t)})},Oe=l.querySelector(".wpx-people-list"),gt=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&&bt(o.dataset.key,a.dataset.user)};Oe.addEventListener("click",gt,!0),b.add(()=>Oe.removeEventListener("click",gt,!0)),U(Oe,".wpx-person",e=>{const t=L.peek()?.rows?.find(a=>a.key===e.dataset.key);return t?[{header:t.name},...Mo(t).map(a=>({label:a.label,icon:a.icon,disabled:!!a.disabled,action:()=>{a.disabled||(a.id==="copy"?Fe(t.email):bt(t.key,a.id))}}))]:[]},e=>B(e.querySelector(".wpx-kebab")||e));const N=l.querySelector(".wpx-role");b.add(M.effect(()=>{const e=L.value;if(!N)return;const t=e?.roles||[],a=e?.role||"",o=`${t.map(r=>r.name).join(",")}|${a}`;N.dataset.sig!==o&&(N.dataset.sig=o,N.replaceChildren(...t.map(r=>{const c=document.createElement("option");return c.value=r.name,c.textContent=r.name===Ze?`${r.label} (default)`:r.label,c})),N.value=a)}));const yt=()=>$({userRole:N.value===Ze?"":N.value});N?.addEventListener("change",yt),b.add(()=>N?.removeEventListener("change",yt)),U(l.querySelector(".wpx-redirects"),".wpx-row",e=>[{header:e.dataset.from},{label:"Copy the old address",icon:"copy",action:()=>Fe(e.dataset.from)}],null),U(l.querySelector(".wpx-history"),".wpx-row",e=>[{header:_.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Import"},{label:"Open the report",icon:"file-text",action:()=>be(e.dataset.id)},{label:"Remove from the history",icon:"trash-2",action:()=>Ya(e.dataset.id)}],e=>be(e.dataset.id)),mo(u,{id:"wpx-upload",icon:"upload",title:"Upload an export",onClick:()=>{h.peek()&&oe(),setTimeout(()=>ne.click(),0)}}),I.scan(l);const Be=Rt(v(),d.peek().map(e=>e.id),g);Be.pruned&&T&&A({owner:g,items:Be.items});const xt=Object.entries(Be.items).sort((e,t)=>String(t[1].at).localeCompare(String(e[1].at)))[0];xt&&!y.peek()&&await fe(xt[0])}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.8.11",
3
+ "version": "1.8.12",
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",
@@ -3,7 +3,11 @@
3
3
  *
4
4
  * - Redirects: a global onRequest guard (GET/HEAD, never /api or /admin);
5
5
  * rules in content/seo/redirects.json (admin/js/lib/seo-redirects.js).
6
- * A renamed page gets one (content:pageRenamed), older rules re-aimed.
6
+ * A renamed page gets one (content:pageRenamed), older rules re-aimed -
7
+ * never one FROM "/" (the home address). A page at "/" always wins over a
8
+ * rule from "/": made, saved or moved there published, the rule is removed
9
+ * (as a page moved anywhere removes the rule from its new address), and at
10
+ * start an automatic one an older version left is removed if one is there.
7
11
  * - What search engines are given: the 'seo:meta', 'sitemap:entries' and
8
12
  * 'robots:lines' transforms (hooks.js section 8) - a URL's overrides,
9
13
  * Organization / BlogPosting JSON-LD, sitemap exclusions, robots rules.
@@ -30,7 +34,7 @@ import {applySeo, robotsLines} from '../../../admin/js/lib/seo-schema.js';
30
34
  import {checkSettings, excluded, mergeDefaults} from '../../../admin/js/lib/seo-settings.js';
31
35
  import {getConfig, saveConfig} from '../../config.js';
32
36
  import {hooks as coreHooks, getSitemapSources, registerTransform} from '../hooks.js';
33
- import {listPages} from '../content.js';
37
+ import {getPage, listPages} from '../content.js';
34
38
  import {invalidateTags} from '../cache/index.js';
35
39
 
36
40
  const SKIP = /^\/(api|admin)(\/|$)/;
@@ -129,7 +133,28 @@ async function seo(fastify, {enabled = true, getBaseUrl = null, rolesService = n
129
133
  const saveHits = setInterval(() => store.saveHits(), 60_000);
130
134
  saveHits.unref?.();
131
135
 
136
+ /**
137
+ * A page is at "/" now: a rule from "/" would hide it, so it goes - the page wins, as it does at any
138
+ * address a page is moved to (onMoved). `autoOnly`: only rules SEO added itself (the start-up tidy).
139
+ */
140
+ const clearHomeRule = (why, {autoOnly = false} = {}) => {
141
+ const drop = store.rules().filter(r => r.from === '/' && (!autoOnly || r.auto));
142
+ if (!drop.length) return;
143
+ const ids = new Set(drop.map(r => r.id));
144
+ store.setRules(store.rulesWithHits().filter(r => !ids.has(r.id)));
145
+ sitemapChanged();
146
+ log.info(`[seo] a page is at / (${why}): the redirect from / to ${drop.map(r => r.to || 'gone').join(', ')} was removed.`);
147
+ };
148
+ // Earlier versions added "/ -> its new address" when the home page was moved away, hiding any page made at "/" since.
149
+ // Only a page the public can see wins: a draft at "/" answers 404 to them, so a redirect from "/" stays until it is published.
150
+ const liveHome = (page) => Boolean(page) && page.status !== 'draft';
151
+ try { if (store.rules().some(r => r.from === '/' && r.auto) && liveHome(await getPage('/').catch(() => null))) clearHomeRule('found at start', {autoOnly: true}); } catch { /* left as it is */ }
152
+
132
153
  const onRenamed = ({from, to, redirect} = {}) => {
154
+ // A page moved to "/" lives there now, whatever the settings - a rule from "/" would hide it.
155
+ if (to && normalisePath(to) === '/') {
156
+ getPage('/').then((p) => { if (liveHome(p)) clearHomeRule(`moved there from ${from}`); }).catch(() => {});
157
+ }
133
158
  // redirect: false - the old address is not to lead to the new one (the home page moved aside).
134
159
  if (!cfg.redirects.onRename || !from || !to || redirect === false) return;
135
160
  try {
@@ -140,6 +165,12 @@ async function seo(fastify, {enabled = true, getBaseUrl = null, rolesService = n
140
165
  }
141
166
  };
142
167
  coreHooks.on('content:pageRenamed', onRenamed);
168
+ // Made at "/", or saved there as published (a draft made first, then published).
169
+ const onSaved = ({page, urlPath} = {}) => {
170
+ if (urlPath && normalisePath(urlPath) === '/' && liveHome(page)) { try { clearHomeRule('published there'); } catch { /* left as it is */ } }
171
+ };
172
+ coreHooks.on('content:pageCreated', onSaved);
173
+ coreHooks.on('content:pageUpdated', onSaved);
143
174
 
144
175
  // ---- 2. What search engines are given ----
145
176
  registerTransform('seo:meta', (meta, ctx) => {
@@ -327,6 +358,8 @@ async function seo(fastify, {enabled = true, getBaseUrl = null, rolesService = n
327
358
  fastify.addHook('onClose', (_i, done) => {
328
359
  clearInterval(timer); clearInterval(saveHits); clearTimeout(first);
329
360
  coreHooks.off?.('content:pageRenamed', onRenamed);
361
+ coreHooks.off?.('content:pageCreated', onSaved);
362
+ coreHooks.off?.('content:pageUpdated', onSaved);
330
363
  store.close();
331
364
  done();
332
365
  });
@@ -45,7 +45,9 @@
45
45
  * The home page (./front.js): the page WordPress showed at its home address
46
46
  * becomes this site's home page. The page at "/" before is moved to
47
47
  * /previous-home as a draft - never deleted, never overwritten - by the first
48
- * run that does it, and never again (mapping `home`). That move leaves the
48
+ * run that does it, and never again unless the person says Yes to the preview's
49
+ * question (an earlier import set the home page and this export's front page is
50
+ * another page; a run without the answer is refused). That move leaves the
49
51
  * menus' "/" alone and adds no redirect (renamePage's `redirect: false`).
50
52
  *
51
53
  * The one request hook here answers WordPress's query addresses (/?p=12,
@@ -295,11 +297,13 @@ export async function siteState(mapping = {}, source = '') {
295
297
  try { searchOn = getSearchSettings().enabled !== false; } catch { /* no search settings */ }
296
298
  const seo = seoEngine();
297
299
  // The users here - only what matching an author needs (never a password or a token).
298
- // The home page set by an import of ANOTHER WordPress site: once per site, not once per export.
300
+ // The home page set by an import of ANOTHER WordPress site (the latest, when several did): the plan asks
301
+ // before this export's front page takes its place.
299
302
  let homeSetBy = null;
300
303
  try {
301
304
  for (const [key, src] of Object.entries((await readMap()).sources || {})) {
302
- if (key !== source && src?.items?.home?.kind === 'home') { homeSetBy = {...src.items.home, source: key}; break; }
305
+ const rec = src?.items?.home;
306
+ if (key !== source && rec?.kind === 'home' && (!homeSetBy || String(rec.at || '') > String(homeSetBy.at || ''))) homeSetBy = {...rec, source: key};
303
307
  }
304
308
  } catch { /* no imports yet */ }
305
309
  const users = (await listUsers().catch(() => [])).map(u => ({id: u.id, email: String(u.email || '').toLowerCase(), name: u.name || ''}));
@@ -454,7 +458,9 @@ export function cleanOptions(o = {}) {
454
458
  // confirmed (its WordPress id - the plan accepts only the one it suggests), the latest posts instead.
455
459
  home: o.home !== false,
456
460
  postsPage: Number.isInteger(o.postsPage) && o.postsPage > 0 && o.postsPage < 1e12 ? o.postsPage : 0,
457
- latestPosts: o.latestPosts === true
461
+ latestPosts: o.latestPosts === true,
462
+ // The answer to the home page question (plan.home.question): true, false, or not answered.
463
+ homeReplace: typeof o.homeReplace === 'boolean' ? o.homeReplace : null
458
464
  };
459
465
  }
460
466
 
@@ -508,6 +514,11 @@ export async function startRun(id, options, user, log = console) {
508
514
  if (pre.plan.options.media === 'zip' && !pre.meta.uploads) {
509
515
  throw Object.assign(new Error('Upload the wp-content/uploads zip first, or choose another media source.'), {statusCode: 400});
510
516
  }
517
+ // Whether this export's front page replaces the home page an earlier import set is never assumed.
518
+ const q = pre.plan.home?.question;
519
+ if (q && typeof q.answer !== 'boolean') {
520
+ throw Object.assign(new Error(`Answer the home page question first: make "${q.title}" the home page instead of "${q.current || 'the page at /'}"?`), {statusCode: 400});
521
+ }
511
522
  } catch (err) {
512
523
  running = null;
513
524
  throw err;
@@ -709,6 +720,11 @@ async function runPhases(job, {plan, wxr, oldMapping, meta, inviteOrigin, wie},
709
720
  for (const r of docRows) {
710
721
  if (!works(r) && r.moveFrom && homeOutcome?.error) { set(r.key, {result: 'failed', error: homeOutcome.error}); continue; }
711
722
  if (!works(r)) {
723
+ if (r.movedAside) {
724
+ // The home page until now, moved aside by runHome (which followed it in the mapping) - or still at "/" if that failed.
725
+ set(r.key, {result: 'kept', target: mapping.items[r.key]?.path || r.target});
726
+ continue;
727
+ }
712
728
  if (r.action === 'keep') {
713
729
  // Moved to "/" by this run (the home page), its edits here left as they are.
714
730
  mapping.items[r.key] = r.moveFrom && homeOutcome?.moved ? {...oldMapping.items[r.key], path: r.target} : oldMapping.items[r.key];
@@ -752,7 +768,11 @@ async function runPhases(job, {plan, wxr, oldMapping, meta, inviteOrigin, wie},
752
768
  // The new home page was not made: the one moved aside for it goes back.
753
769
  if (homeOutcome?.moved && homeOutcome.previous && results.get(homeOutcome.key)?.result === 'failed') {
754
770
  await putHomeBack(homeOutcome, mapping, user?.email || 'WordPress import');
755
- if (homeOutcome.restored) (results.get(homeOutcome.key).notes ||= []).push('The home page from before was put back at /.');
771
+ if (homeOutcome.restored) {
772
+ (results.get(homeOutcome.key).notes ||= []).push('The home page from before was put back at /.');
773
+ // A page of this import's that was moved aside for it is back at "/" too.
774
+ for (const r of plan.items) if (r.movedAside && results.get(r.key)) results.set(r.key, {...results.get(r.key), target: mapping.items[r.key]?.path || '/'});
775
+ }
756
776
  }
757
777
 
758
778
  await checkpoint(true);
@@ -1118,8 +1138,10 @@ async function runPhases(job, {plan, wxr, oldMapping, meta, inviteOrigin, wie},
1118
1138
  */
1119
1139
  async function runHome({plan, mapping, oldMapping, user, results}) {
1120
1140
  const claim = plan.items.find(r => (r.front || r.homeLatest) && r.target === '/' && r.action !== 'skip');
1121
- if (!claim || oldMapping.items?.home?.kind === 'home') return null;
1122
- const out = {moved: false, previous: '', title: '', status: '', from: claim.moveFrom || '', key: claim.key};
1141
+ // Set by an earlier run of this import: moved again only when the person said Yes to the question.
1142
+ if (!claim || (oldMapping.items?.home?.kind === 'home' && plan.home?.question?.answer !== true)) return null;
1143
+ // The record of the home page before (put back if this one cannot be made).
1144
+ const out = {moved: false, previous: '', title: '', status: '', from: claim.moveFrom || '', key: claim.key, priorHome: oldMapping.items?.home || null};
1123
1145
  const author = user?.email || 'WordPress import';
1124
1146
  try {
1125
1147
  const move = plan.home?.move;
@@ -1146,6 +1168,11 @@ async function runHome({plan, mapping, oldMapping, user, results}) {
1146
1168
  await followRename(claim.moveFrom, '/');
1147
1169
  followInMapping(mapping, claim.moveFrom, '/');
1148
1170
  await relinkMenus(claim.moveFrom, '/').catch(() => {});
1171
+ // Left as it is (edited here), it may be a draft - an earlier home page change moved it aside as one.
1172
+ // Chosen as the home page, it is published: a draft at "/" would leave the site without one.
1173
+ if (claim.action === 'keep' && (await getPage('/').catch(() => null))?.status === 'draft') {
1174
+ await updatePage('/', {status: 'published'}, undefined, {author});
1175
+ }
1149
1176
  }
1150
1177
  out.moved = true;
1151
1178
  mapping.items.home = {kind: 'home', how: claim.homeLatest ? 'latest' : 'page', wpId: claim.wpId || 0, previous: out.previous, at: new Date().toISOString()};
@@ -1170,7 +1197,8 @@ async function putHomeBack(out, mapping, author) {
1170
1197
  await followRename(out.previous, '/');
1171
1198
  followInMapping(mapping, out.previous, '/');
1172
1199
  await updatePage('/', {status: out.status || 'published', slug: 'index'}, undefined, {author});
1173
- delete mapping.items.home;
1200
+ if (out.priorHome) mapping.items.home = out.priorHome;
1201
+ else delete mapping.items.home;
1174
1202
  out.restored = true;
1175
1203
  out.moved = false;
1176
1204
  out.previous = '';
@@ -28,7 +28,11 @@
28
28
  * The home page (./front.js, plan.home): the page WordPress showed at its home
29
29
  * address becomes this site's home page (options.home, on by default when one
30
30
  * is found); the page there now moves to /previous-home as a draft - once, on
31
- * the first run that does it (mapping `home`), never again. A suggested posts
31
+ * the first run that does it (mapping `home`), never again without asking: when
32
+ * an earlier import set the home page and this export's front page is another
33
+ * page, plan.home.question asks (options.homeReplace, no default - the run is
34
+ * refused until it is answered). Yes moves the home page aside the same way;
35
+ * No imports the page at its own address. A suggested posts
32
36
  * page, once confirmed (options.postsPage), is not made: its old address leads
33
37
  * to the Blog. With no front page, the latest posts can be the home page
34
38
  * (options.latestPosts, a page holding the Blog's [blog-list]).
@@ -231,6 +235,8 @@ export function isoDate(gmt, local) {
231
235
  * @param {boolean} [options.home=true] - the WordPress front page becomes this site's home page
232
236
  * @param {number} [options.postsPage] - the suggested posts page, confirmed (its WordPress id)
233
237
  * @param {boolean} [options.latestPosts=false] - no front page: the latest posts on the home page
238
+ * @param {boolean|null} [options.homeReplace=null] - the answer to plan.home.question: make this export's
239
+ * front page the home page instead of the one an earlier import set (null: not answered)
234
240
  * @param {string|null} [site.homeTitle] - the title of the page at / now (null: none)
235
241
  * @returns {object}
236
242
  */
@@ -239,7 +245,7 @@ export function planImport(wxr, site, mapping = {}, options = {}) {
239
245
  posts: site.blog?.available ? 'blog' : 'pages',
240
246
  media: 'download', comments: true, menus: true, redirects: true, drafts: true, products: true, choices: {}, zipPaths: null,
241
247
  types: {}, listPages: {}, users: {}, userRole: '', invite: false, footer: {}, wie: null,
242
- home: true, postsPage: 0, latestPosts: false,
248
+ home: true, postsPage: 0, latestPosts: false, homeReplace: null,
243
249
  ...options
244
250
  };
245
251
  opt.types ||= {};
@@ -311,9 +317,11 @@ export function planImport(wxr, site, mapping = {}, options = {}) {
311
317
  const depthOf = (it, seen = new Set()) => (it.parent && byId.has(it.parent) && !seen.has(it.id) ? 1 + depthOf(byId.get(it.parent), seen.add(it.id)) : 0);
312
318
  const orderedPages = [...pages].sort((a, b) => depthOf(a) - depthOf(b) || a.order - b.order || a.id - b.id);
313
319
  // ---- The home page: WordPress's front page, its posts page (./front.js) ----
314
- // `home` in the mapping: an earlier run set this site's home page - it is never moved again.
315
- // An import of another WordPress site that set it counts too (site.homeSetBy): once per site.
316
- const homeRec = mapped.home?.kind === 'home' ? mapped.home : site.homeSetBy?.kind === 'home' ? site.homeSetBy : null;
320
+ // `home` in the mapping: an earlier run set this site's home page - it is not moved again without asking.
321
+ // An import of another WordPress site that set it counts too (site.homeSetBy): the latest one is the home page.
322
+ const ownHome = mapped.home?.kind === 'home' ? mapped.home : null;
323
+ const otherHome = site.homeSetBy?.kind === 'home' ? site.homeSetBy : null;
324
+ const homeRec = otherHome && (!ownHome || String(otherHome.at || '') > String(ownHome.at || '')) ? otherHome : ownHome;
317
325
  const leftAsIs = homeRec?.source
318
326
  ? `An earlier import (of ${homeRec.source}) set this site's home page - it is left as it is.`
319
327
  : 'This site\'s home page was changed here since the last import - it is left as it is.';
@@ -326,15 +334,34 @@ export function planImport(wxr, site, mapping = {}, options = {}) {
326
334
  const latestOffered = !front && !frontMatches && opt.posts === 'blog' && Boolean(site.shortcodes?.has('blog-list'));
327
335
  let homeMove = null;
328
336
  let homeChanged = '';
329
- /** Something of this run's takes "/" for the first time: the page there now moves to /previous-home (-2…), never deleted. */
330
- const claimHome = () => {
331
- if (site.pagePaths.has('/') && !ownPaths.has('/')) {
337
+ /**
338
+ * Something of this run's takes "/": the page there now moves to /previous-home (-2…), never deleted.
339
+ * `replace`: the person said Yes to the question - even a page an earlier run of this import put there moves.
340
+ */
341
+ const claimHome = (replace = false) => {
342
+ if (homeMove) return;
343
+ if (site.pagePaths.has('/') && (replace || !ownPaths.has('/'))) {
332
344
  const to = freePath('/previous-home', takenPaths);
333
345
  takenPaths.add(to);
334
346
  takenPaths.delete('/');
335
347
  homeMove = {from: '/', to};
336
348
  }
337
349
  };
350
+ // ---- The question: an earlier import set the home page, and this export's front page is another page ----
351
+ const frontKey = front ? `page:${front.id}` : '';
352
+ const frontWas = front ? mapped[frontKey] : null;
353
+ // Deleted here since an earlier run made it: not imported, or ("Import again") brought back at its old address -
354
+ // either way it does not take "/", so there is nothing to ask.
355
+ const frontGone = Boolean(frontWas?.path && !site.pagePaths.has(frontWas.path));
356
+ // The same page this import made the home page (a re-run), or already at "/" anyway: nothing to ask.
357
+ const sameHome = Boolean(homeRec && !homeRec.source && homeRec.how !== 'latest' && Number(homeRec.wpId) === front?.id);
358
+ const frontIsHome = frontWas?.path === '/' && site.pagePaths.has('/');
359
+ const askHome = Boolean(front && opt.home && choices[frontKey] !== 'skip' && !frontGone && homeRec && !sameHome && !frontIsHome && site.pagePaths.has('/'));
360
+ const homeAnswer = askHome && typeof opt.homeReplace === 'boolean' ? opt.homeReplace : null;
361
+ const replaceHome = homeAnswer === true;
362
+ // Yes: what is at "/" moves aside before any page is planned, so a page of this import's that is there now
363
+ // is planned at its new address (planDocument), not over the new home page.
364
+ if (replaceHome) claimHome(true);
338
365
  const homeAddress = (it) => {
339
366
  const p = oldPathOf(it.link, wxr.site);
340
367
  return p !== null && relative(p, base) === '/';
@@ -807,8 +834,8 @@ export function planImport(wxr, site, mapping = {}, options = {}) {
807
834
  const here = was?.path && site.pagePaths.has(was.path) ? was.path : '';
808
835
  const slugPath = slugAddress(it);
809
836
  let row;
810
- if (homeRec) {
811
- // Set by an earlier run: never moved again.
837
+ if (homeRec && !replaceHome) {
838
+ // Set by an earlier run: never moved again (unless the person said Yes to the question).
812
839
  if (here && here !== '/') homeChanged = `This site's home page was changed here since the last import - "${it.title || 'the home page'}" stays at ${here}.`;
813
840
  let wanted = '/';
814
841
  if (!here && takenPaths.has('/')) {
@@ -816,9 +843,11 @@ export function planImport(wxr, site, mapping = {}, options = {}) {
816
843
  homeChanged = leftAsIs;
817
844
  }
818
845
  row = planDocument({it, key, kind: 'page', wanted, oldPath, statusMap: PAGE_STATUS});
846
+ // Asked: No keeps this site's home page and the page arrives at its own address; unanswered, the card asks.
847
+ if (askHome) homeChanged = homeAnswer === false ? `You chose to keep this site's home page - "${it.title || 'the page'}" is imported${row.target ? ` at ${row.target}` : ''}.` : '';
819
848
  } else {
820
849
  // Deleted here since an earlier run made it (not as the home page): that is the person's call, as for any page.
821
- if (!was?.path || here) claimHome();
850
+ if (!was?.path || here) claimHome(replaceHome);
822
851
  row = planDocument({it, key, kind: 'page', wanted: '/', oldPath, statusMap: PAGE_STATUS});
823
852
  // Imported before at another address (not as the home page then): it moves to "/".
824
853
  if (here && here !== '/' && row.action !== 'skip') {
@@ -843,6 +872,8 @@ export function planImport(wxr, site, mapping = {}, options = {}) {
843
872
  page: front ? {wpId: front.id, title: front.title || '(untitled)', oldUrl: front.link, target: frontRow?.target || '', action: frontRow?.action || 'skip', isHome: frontRow?.target === '/' && frontRow.action !== 'skip'} : null,
844
873
  now: site.pagePaths.has('/') ? {title: site.homeTitle ?? ''} : null,
845
874
  move: homeMove,
875
+ // An earlier import set the home page and this front page is another: asked, never assumed.
876
+ question: askHome ? {title: front.title || '(untitled)', current: site.homeTitle ?? '', answer: homeAnswer} : null,
846
877
  done: homeRec ? {how: homeRec.how || 'page', previous: homeRec.previous || '', source: homeRec.source || ''} : null,
847
878
  changed: homeChanged,
848
879
  latest: {offered: latestOffered, on: Boolean(latestOffered && opt.latestPosts), target: latestRow?.target || '', action: latestRow?.action || ''},
@@ -869,6 +900,11 @@ export function planImport(wxr, site, mapping = {}, options = {}) {
869
900
  if (it.password) row.status = 'draft';
870
901
  const was = mapped[key];
871
902
  const why = excluded(it);
903
+ // The home page until now, made by an earlier run of this import: Yes to the question moves it aside as a draft.
904
+ if (replaceHome && homeMove && was?.path === '/' && key !== frontKey && site.pagePaths.has('/')) {
905
+ return push({...row, action: 'keep', target: homeMove.to, movedAside: true,
906
+ reason: `This site's home page until now - it moves to ${homeMove.to} as a draft, nothing is deleted.`});
907
+ }
872
908
  if (choices[key] === 'skip') return push({...row, action: 'skip', reason: 'You chose to skip it.'});
873
909
  if (why) return push({...row, action: 'skip', reason: why});
874
910
  if (was?.path) {