domma-cms 1.8.4 → 1.8.6
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 +8 -0
- package/CLAUDE.md +27 -0
- package/admin/js/lib/wordpress-import-arrange.js +1 -1
- package/admin/js/templates/wordpress-import.html +15 -1
- package/admin/js/views/wordpress-import.js +24 -6
- package/package.json +1 -1
- package/server/routes/api/wordpress-import.js +4 -1
- package/server/services/markdown.js +60 -23
- package/server/services/wpImport/custom.js +376 -0
- package/server/services/wpImport/index.js +194 -14
- package/server/services/wpImport/plan.js +133 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.6] - 2026-10-16
|
|
4
|
+
|
|
5
|
+
- **Security: what is stored in a collection no longer runs as a shortcode.** A `[collection]` or `[view]` used to pass its entries on to the later shortcode steps, so an entry - a public form submission, say - holding `[form]`, `[button]`, `[embed]`, `[slideover]` or another `[collection]` was carried out on the page. Entry values now show exactly as typed, in every display type and in `{{field}}` / `{{{field}}}` block placeholders, while the page's own shortcodes and those written into a block template work as before. Filter, search and export still see the stored values. A value containing `$&` or `` $` `` is also shown as written instead of pulling in other parts of the page.
|
|
6
|
+
|
|
7
|
+
## [1.8.5] - 2026-10-16
|
|
8
|
+
|
|
9
|
+
- **Import from WordPress brings custom post types across.** Content a theme or plugin added to WordPress - events, team members, portfolio pieces - now has its own Custom types section in the preview, with how many there are, a few titles, and what each will become. By default a type becomes a collection (wp-event, wp-team-member…) holding each item's title, address, text, excerpt, date, status and featured image, its categories, and every custom field it had, Advanced Custom Fields included - dates, numbers, links and pictures recognised as such. A list page at the type's old address (/events) shows the published ones, and the old single-item addresses and `?p=` links lead there; drafts come across but are never shown or readable from the site. Or choose Pages - each item a page at its old address, its categories as tags - or Skip. The type's address is read from the export's own links, never guessed. WordPress's and plugins' own housekeeping (revisions, reusable blocks, templates, form definitions, orders…) is listed once with why it is not imported. Run it again and entries you edited here are left alone unless you choose Overwrite, and a collection only ever gains fields - your own fields and labels stay.
|
|
10
|
+
|
|
3
11
|
## [1.8.4] - 2026-10-16
|
|
4
12
|
|
|
5
13
|
- Restore the visible caret in the email body. The shared smooth caret now positions itself after detached compose editors are mounted, keeping it beside the text during typing, formatting and scrolling, and after reopening compose.
|
package/CLAUDE.md
CHANGED
|
@@ -80,6 +80,13 @@ Containers that render their own body (tabs, accordion, carousel, timeline, hero
|
|
|
80
80
|
wrappers) run the inline tags plus `[grid]`/`[card]` over it first - keep that set in step with
|
|
81
81
|
`processInlineShortcodes()` when adding an inline tag. The full order is in `docs/markdown-shortcodes.md`.
|
|
82
82
|
|
|
83
|
+
**Entry data never runs as a shortcode.** `[collection]` / `[view]` output carries entry values into stages 3-10,
|
|
84
|
+
so every value they draw goes through `inertShortcodes()` (the character after each `[` becomes an entity, so
|
|
85
|
+
`[form` is `[form` and nothing matches, while Markdown links in values still work): displays with no authored
|
|
86
|
+
markup encode their whole output, block templates encode each `{{field}}` / `{{{field}}}` value and leave the
|
|
87
|
+
template's own shortcodes live. A new display or placeholder must do the same. The base64 `data-ctx` / table
|
|
88
|
+
payloads stay raw (no brackets in base64), so filter, search and export see the stored value.
|
|
89
|
+
|
|
83
90
|
Safe nesting: `[card]` inside `[slideover]`, `[grid]` inside `[slideover]`, `[col]` inside `[grid]`, `[card]` inside
|
|
84
91
|
`[box]` and `[box]` inside `[card]`.
|
|
85
92
|
Do NOT nest `[slideover]` inside another `[slideover]`.
|
|
@@ -361,6 +368,26 @@ A built-in Tool (`wordpress-import` in `services/tools.js`, permission `import.w
|
|
|
361
368
|
imported and redirect to the Shop's (`wooPageRole`). The WXR has no currency or weight unit: the Shop's currency
|
|
362
369
|
and kilograms are assumed, and the preview says so. Orders, customers, coupons, reviews, tax and shipping
|
|
363
370
|
settings are never imported.
|
|
371
|
+
- **Custom post types** (`custom.js`, pure): every type not handled above is either plumbing - `INTERNAL_TYPES`, the ONE
|
|
372
|
+
list (a string or RegExp per entry, each with the plain reason the preview shows; extend it there) - or content,
|
|
373
|
+
offered per type as `options.types[type]` = collection (default) / pages / skip, plus `options.listPages[type] =
|
|
374
|
+
false` to drop the list page (only non-defaults are sent). The type's address (`rewriteSlugOf`) is read from the
|
|
375
|
+
items' own pretty links, never guessed; with none, the new address is the type name and nothing old is redirected.
|
|
376
|
+
Collection: slug `wp-<type>` (`freePath` on a clash), fields = core seven + one per taxonomy + one per public postmeta
|
|
377
|
+
key (no `_` keys, so ACF values in, its field references out; `META_SKIP` drops SEO/builder keys), types inferred by
|
|
378
|
+
`inferType` (an attachment id -> url to the file here); serialised values only through `readSerialised`, an object
|
|
379
|
+
refused and left empty with a report note. Written with the collections service; `api.read` is created disabled
|
|
380
|
+
because entries include drafts - the list page (`[collection … where_status="published"]`, plan key
|
|
381
|
+
`typepage:<type>`, a `kind: 'page'` row with `listFor`) is what the public sees, and old single-item addresses and
|
|
382
|
+
`?p=` lead to it. Re-runs: the mapping's `collection:<type>` slug is used again and its schema only GAINS fields
|
|
383
|
+
(`mergeFields` - the person's fields and labels stay); entries are `custom:<id>` with a fingerprint of their
|
|
384
|
+
NON-EMPTY values (`entryFingerprintText`), so a re-save that adds blanks is not an edit. A deleted collection is
|
|
385
|
+
offered back ("Import again"), never silently remade. Pages: `custom:<id>` rows with `as: 'pages'` go through the
|
|
386
|
+
ordinary page path (`planDocument`), taxonomies as tags. A `post_type_archive` menu item for a type leads to its list
|
|
387
|
+
page (`archive:<type>`). Switching a type between runs: a `custom:<id>` mapping keeps BOTH halves (page `path`/`hash`,
|
|
388
|
+
entry `collection`/`entryId`/`entryHash`), so switching back finds what was made rather than making it twice, and a
|
|
389
|
+
redirect an earlier run added from an address this run gave a page is removed (report result `removed`). Excerpts lose
|
|
390
|
+
their shortcode tags (`stripShortcodes`) - the list page's display rows go through the later shortcode stages.
|
|
364
391
|
|
|
365
392
|
## Site search
|
|
366
393
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const KIND_LABEL={page:"Page",post:"Post",product:"Product",media:"Media",menu:"Menu",category:"Category"},KIND_PLURAL={page:"Pages",post:"Posts",product:"Products",media:"Media",menu:"Menus",category:"Categories"},KIND_ICON={page:"file-text",post:"edit-3",product:"shopping-bag",media:"image",menu:"menu",category:"tag"},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 p=e=>!!(e.builder||e.needs||e.shortcodes?.length),c=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 p(e);default:return!0}}export function reportMatches(e,t){return t==="rebuild"?p(e):t==="all"||!t||e.result===t}export function counts(e,t,o,i=""){const a=e.filter(s=>!i||s.kind===i);return Object.fromEntries(t.map(s=>[s.id,a.filter(n=>o(n,s.id)).length]))}export function kindTabs(e){const t=["page","post","product","media","menu","category"],o={};for(const i of e)o[i.kind]=(o[i.kind]||0)+1;return[{id:"",label:"Everything",count:e.length},...t.filter(i=>o[i]).map(i=>({id:i,label:KIND_PLURAL[i],count:o[i]}))]}export function planRow(e){const t=ACTION[e.action]||ACTION.skip,o=e.target?e.kind==="menu"?`Menu "${e.target}"`:e.target:"",i=[KIND_LABEL[e.kind]||e.kind];return e.status&&(e.kind==="page"||e.kind==="post")&&i.push(e.status),e.kind==="product"&&(e.productType&&i.push(PRODUCT_TYPE_LABEL[e.productType]||e.productType),e.variants&&i.push(`${e.variants} option${e.variants===1?"":"s"}`),e.action!=="skip"&&e.status&&i.push(e.status==="active"?"on sale":"draft")),e.comments&&i.push(`${e.comments} comment${e.comments===1?"":"s"}`),e.count&&i.push(`${e.count} item${e.count===1?"":"s"}`),{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:i.join(" \xB7 "),reason:e.reason||"",rebuild:c(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":""].filter(Boolean).join(" \xB7 "),reason:[e.reason,...e.notes||[]].filter(Boolean).join(" "),rebuild:c(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)/.test(e.reason||"")&&o.push({id:"import",label:"Import again",icon:"download"}),o}export function applyChoice(e,t,o){const i={...e||{}};return!o||o==="reset"?delete i[t]:i[t]=o,i}export function defaultOptions(e=!1){return{posts:e?"blog":"pages",media:"download",comments:!0,menus:!0,redirects:!0,drafts:!0,products:!0,choices:{}}}export function cleanOptions(e={},t=!1){const o=defaultOptions(t),i=(a,s,n)=>s.includes(a)?a:n;return{posts:i(e.posts,t?["blog","pages","skip"]:["pages","skip"],o.posts),media:i(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,choices:Object.fromEntries(Object.entries(e.choices||{}).filter(([a,s])=>typeof a=="string"&&["skip","overwrite","import"].includes(s)))}}export function optionsSnapshot(e){const t=cleanOptions(e,!0),o=Object.keys(t.choices).sort().map(i=>`${i}=${t.choices[i]}`).join(",");return[t.posts,t.media,t.comments,t.menus,t.redirects,t.drafts,t.products,o].join("|")}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 i={};let a=!1;for(const[s,n]of Object.entries(e.items))t.includes(s)&&n?.options&&n.was===s?i[s]=n:a=!0;return{items:i,pruned:a}}export function withDraft(e,t,o,i){const a={...e&&e.owner===t&&e.items||{}};return i?a[o]=i:delete a[o],{owner:t,items:a}}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=i=>t[i]?t[i].total-t[i].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}]:[],{id:"media",label:"Media",value:o("media"),of:t.media?.total||0},{id:"menu",label:"Menus",value:o("menu"),of:t.menu?.total||0},{id:"comments",label:"Comments",value:e?.summary?.comments||0,of:0},{id:"redirects",label:"Redirects",value:e?.summary?.redirects||0,of:0}].map(i=>({...i,sub:i.of&&i.of!==i.value?`of ${i.of}`:""}))}export function historyRow(e,t=o=>o){const o=e.summary||{},i=Object.values(o.kinds||{}).reduce((n,l)=>n+(l.created||0),0),a=Object.values(o.kinds||{}).reduce((n,l)=>n+(l.updated||0),0),s=[];return i&&s.push(`${i} made`),a&&s.push(`${a} updated`),o.redirects?.created&&s.push(`${o.redirects.created} redirect${o.redirects.created===1?"":"s"}`),o.rebuild&&s.push(`${o.rebuild} to rebuild`),o.failed&&s.push(`${o.failed} failed`),e.status==="failed"&&s.splice(0,s.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:s.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||{},i=[o.page&&`${o.page} pages`,o.post&&`${o.post} posts`,o.product&&`${o.product} products`,o.attachment&&`${o.attachment} media`].filter(Boolean);return{id:e.id,title:e.siteTitle||e.fileName||"WordPress export",meta:[e.fileName,t(e.at),i.join(", ")].filter(Boolean).join(" \xB7 "),pill:e.uploads?"Uploads zip added":"Waiting",pillClass:e.uploads?"is-ok":"is-muted"}}export function productSummary(e){const t=e?.summary?.products;if(!t?.total)return null;const o=t.shop||e.shop||{},i=e.options?.products!==!1,a=Object.entries(t.types||{}).map(([d,u])=>`${u} ${(PRODUCT_TYPE_LABEL[d]||d).toLowerCase()}`).join(", "),s=e.summary.counts?.product||{},n=[{label:"In the export",value:`${t.total}${a?` (${a})`:""}`}];o.available&&i&&(n.push({label:"On sale in the Shop",value:String(t.onSale||0)}),n.push({label:"As drafts",value:String(t.drafts||0)}),s.keep&&n.push({label:"Left as is",value:String(s.keep)}),n.push({label:"Skipped",value:String(s.skip||0)})),t.rebuild&&n.push({label:"Need rebuilding",value:String(t.rebuild)});let l,r="info";return o.available?i?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:n,notes:Array.isArray(t.notes)?t.notes:[]}}
|
|
1
|
+
export const KIND_LABEL={page:"Page",post:"Post",product:"Product",custom:"Custom item",collection:"Collection",media:"Media",menu:"Menu",category:"Category"},KIND_PLURAL={page:"Pages",post:"Posts",product:"Products",custom:"Custom items",collection:"Collections",media:"Media",menu:"Menus",category:"Categories"},KIND_ICON={page:"file-text",post:"edit-3",product:"shopping-bag",custom:"box",collection:"database",media:"image",menu:"menu",category:"tag"},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 d=e=>!!(e.builder||e.needs||e.shortcodes?.length),u=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 d(e);default:return!0}}export function reportMatches(e,t){return t==="rebuild"?d(e):t==="all"||!t||e.result===t}export function counts(e,t,s,i=""){const n=e.filter(o=>!i||o.kind===i);return Object.fromEntries(t.map(o=>[o.id,n.filter(a=>s(a,o.id)).length]))}export function kindTabs(e){const t=["page","post","product","custom","collection","media","menu","category"],s={};for(const i of e)s[i.kind]=(s[i.kind]||0)+1;return[{id:"",label:"Everything",count:e.length},...t.filter(i=>s[i]).map(i=>({id:i,label:KIND_PLURAL[i],count:s[i]}))]}export function planRow(e){const t=ACTION[e.action]||ACTION.skip;let s=e.target?e.kind==="menu"?`Menu "${e.target}"`:e.kind==="collection"?`Collection "${e.target}"`:e.target:"";e.kind==="custom"&&e.as==="collection"&&e.collection&&e.action!=="skip"&&(s=`In the collection "${e.collection}"${e.listAt?`, listed at ${e.listAt}`:""}`);const i=[e.kind==="custom"&&e.postType?`${KIND_LABEL.custom} (${e.postType})`:KIND_LABEL[e.kind]||e.kind];return e.listFor&&i.push("list page"),e.status&&(e.kind==="page"||e.kind==="post"||e.kind==="custom")&&i.push(e.status),e.kind==="collection"&&e.fields&&i.push(`${e.fields} fields`),e.kind==="product"&&(e.productType&&i.push(PRODUCT_TYPE_LABEL[e.productType]||e.productType),e.variants&&i.push(`${e.variants} option${e.variants===1?"":"s"}`),e.action!=="skip"&&e.status&&i.push(e.status==="active"?"on sale":"draft")),e.comments&&i.push(`${e.comments} comment${e.comments===1?"":"s"}`),e.count&&i.push(`${e.count} item${e.count===1?"":"s"}`),{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:s,meta:i.join(" \xB7 "),reason:e.reason||"",rebuild:u(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":""].filter(Boolean).join(" \xB7 "),reason:[e.reason,...e.notes||[]].filter(Boolean).join(" "),rebuild:u(e)}}export function choiceItems(e,t){const s=[];return t&&s.push({id:"reset",label:"Undo my choice",icon:"rotate-ccw"}),t!=="skip"&&e.action!=="skip"&&e.action!=="keep"&&s.push({id:"skip",label:"Skip this",icon:"x-circle"}),t!=="overwrite"&&e.action==="keep"&&s.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"&&s.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||"")&&s.push({id:"import",label:"Import again",icon:"download"}),s}export function applyChoice(e,t,s){const i={...e||{}};return!s||s==="reset"?delete i[t]:i[t]=s,i}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:{}}}export function cleanOptions(e={},t=!1){const s=defaultOptions(t),i=(n,o,a)=>o.includes(n)?n:a;return{posts:i(e.posts,t?["blog","pages","skip"]:["pages","skip"],s.posts),media:i(e.media,["download","zip","skip"],s.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,o])=>f.test(n)&&(o==="pages"||o==="skip"))),listPages:Object.fromEntries(Object.entries(e.listPages||{}).filter(([n,o])=>f.test(n)&&o===!1)),choices:Object.fromEntries(Object.entries(e.choices||{}).filter(([n,o])=>typeof n=="string"&&["skip","overwrite","import"].includes(o)))}}export function optionsSnapshot(e){const t=cleanOptions(e,!0),s=Object.keys(t.choices).sort().map(o=>`${o}=${t.choices[o]}`).join(","),i=Object.keys(t.types).sort().map(o=>`${o}=${t.types[o]}`).join(","),n=Object.keys(t.listPages).sort().join(",");return[t.posts,t.media,t.comments,t.menus,t.redirects,t.drafts,t.products,s,i,n].join("|")}const f=/^[a-z0-9_-]{1,40}$/;export function withTypeChoice(e,t,s){const i={...e.types||{}};return s==="pages"||s==="skip"?i[t]=s:delete i[t],{...e,types:i}}export function withListPage(e,t,s){const i={...e.listPages||{}};return s?delete i[t]:i[t]=!1,{...e,listPages:i}}export const isDirty=(e,t)=>optionsSnapshot(e)!==optionsSnapshot(defaultOptions(t)),draftKey=e=>e?`wordpress-import.draft.${e}`:"";export function liveDrafts(e,t,s){if(!e||typeof e!="object"||!e.items||typeof e.items!="object")return{items:{},pruned:!1};if(e.owner&&s&&e.owner!==s)return{items:{},pruned:!1};const i={};let n=!1;for(const[o,a]of Object.entries(e.items))t.includes(o)&&a?.options&&a.was===o?i[o]=a:n=!0;return{items:i,pruned:n}}export function withDraft(e,t,s,i){const n={...e&&e.owner===t&&e.items||{}};return i?n[s]=i:delete n[s],{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||{},s=i=>t[i]?t[i].total-t[i].skip:0;return[{id:"page",label:"Pages",value:s("page"),of:t.page?.total||0},{id:"post",label:"Posts",value:s("post"),of:t.post?.total||0},...t.product?[{id:"product",label:"Products",value:s("product"),of:t.product.total}]:[],...t.custom?[{id:"custom",label:"Custom items",value:s("custom"),of:t.custom.total}]:[],{id:"media",label:"Media",value:s("media"),of:t.media?.total||0},{id:"menu",label:"Menus",value:s("menu"),of:t.menu?.total||0},{id:"comments",label:"Comments",value:e?.summary?.comments||0,of:0},{id:"redirects",label:"Redirects",value:e?.summary?.redirects||0,of:0}].map(i=>({...i,sub:i.of&&i.of!==i.value?`of ${i.of}`:""}))}export function historyRow(e,t=s=>s){const s=e.summary||{},i=Object.values(s.kinds||{}).reduce((a,l)=>a+(l.created||0),0),n=Object.values(s.kinds||{}).reduce((a,l)=>a+(l.updated||0),0),o=[];return i&&o.push(`${i} made`),n&&o.push(`${n} updated`),s.redirects?.created&&o.push(`${s.redirects.created} redirect${s.redirects.created===1?"":"s"}`),s.rebuild&&o.push(`${s.rebuild} to rebuild`),s.failed&&o.push(`${s.failed} failed`),e.status==="failed"&&o.splice(0,o.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:o.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=s=>s){const s=e.counts||{},i=[s.page&&`${s.page} pages`,s.post&&`${s.post} posts`,s.product&&`${s.product} products`,s.attachment&&`${s.attachment} media`].filter(Boolean);return{id:e.id,title:e.siteTitle||e.fileName||"WordPress export",meta:[e.fileName,t(e.at),i.join(", ")].filter(Boolean).join(" \xB7 "),pill:e.uploads?"Uploads zip added":"Waiting",pillClass:e.uploads?"is-ok":"is-muted"}}export function productSummary(e){const t=e?.summary?.products;if(!t?.total)return null;const s=t.shop||e.shop||{},i=e.options?.products!==!1,n=Object.entries(t.types||{}).map(([r,m])=>`${m} ${(PRODUCT_TYPE_LABEL[r]||r).toLowerCase()}`).join(", "),o=e.summary.counts?.product||{},a=[{label:"In the export",value:`${t.total}${n?` (${n})`:""}`}];s.available&&i&&(a.push({label:"On sale in the Shop",value:String(t.onSale||0)}),a.push({label:"As drafts",value:String(t.drafts||0)}),o.keep&&a.push({label:"Left as is",value:String(o.keep)}),a.push({label:"Skipped",value:String(o.skip||0)})),t.rebuild&&a.push({label:"Need rebuilding",value:String(t.rebuild)});let l,c="info";return s.available?i?l=`Products go to the Shop at ${s.basePath||"/shop"}.`:l='Products are not being imported - switch on "WooCommerce products" above to bring them in.':(l=s.reason||"Switch on the Shop to bring these in.",c="warn"),{note:l,tone:c,rows:a,notes:Array.isArray(t.notes)?t.notes:[]}}const p=(e,t,s=`${t}s`)=>`${e} ${e===1?t:s}`;export function customTypeRows(e){return(e?.summary?.customTypes||[]).map(t=>{const s=TYPE_AS[t.as]?t.as:"collection",i=TYPE_AS[s],n=t.rewrite?`was at /${t.rewrite}/\u2026`:"its old addresses are not in the export";let o;if(s==="skip")o="Nothing - left out.";else if(s==="pages")o=`${p(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 ${p(t.fields?.length||0,"field")}: ${p(t.importing||0,"entry","entries")}${t.drafts?` (${t.drafts} draft${t.drafts===1?"":"s"}, not shown on the site)`:""}.`,c=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.";o=`${l}${c}`}return{type:t.type,label:t.label||t.type,meta:[p(t.count||0,"item"),`"${t.type}"`,n].join(" \xB7 "),samples:(t.samples||[]).length?`For example: ${t.samples.join(", ")}`:"",creates:o,pill:i.text,pillClass:`is-${i.tone}`,as:s,listOn:s==="collection"&&t.listPage!==!1,canList:s==="collection",listLabel:`List page at ${t.prefix||`/${t.type}`}`,segCollection:s==="collection"?"is-on":"",segPages:s==="pages"?"is-on":"",segSkip:s==="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}
|
|
@@ -127,9 +127,19 @@
|
|
|
127
127
|
<ul class="wpx-note wpx-product-notes" data-each="productNotes.value key=text"></ul>
|
|
128
128
|
</div>
|
|
129
129
|
|
|
130
|
+
<div class="wpx-card wpx-custom" hidden data-bind-hidden="!hasCustom.value">
|
|
131
|
+
<div class="wpx-card-title"><span data-icon="box" data-icon-size="16"></span> Custom types
|
|
132
|
+
<span data-help-title="Custom types" data-help="Content a theme or plugin added to WordPress - events, team members, portfolio pieces. Collection (the usual choice): each type becomes a collection named wp-…, with the title, address, text, excerpt, date, status and featured image of each item, plus its categories and every custom field it had (Advanced Custom Fields included). Drafts come across but are never shown on the site. A list page at the type's old address shows the published ones, and the old single-item addresses lead to it. Pages: each item becomes a page at its old address, its categories as tags. Skip: left out. Run it again later and entries you edited here are left alone unless you choose Overwrite; a collection only ever gains fields."></span></div>
|
|
133
|
+
<div class="wpx-list wpx-types" data-each="typeRows.value key=type"></div>
|
|
134
|
+
<details class="wpx-internal-box" hidden data-bind-hidden="!internalRows.value.length">
|
|
135
|
+
<summary data-bind-text="internalSummary.value"></summary>
|
|
136
|
+
<ul class="wpx-note wpx-internal" data-each="internalRows.value key=type"></ul>
|
|
137
|
+
</details>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
130
140
|
<div class="wpx-card">
|
|
131
141
|
<div class="wpx-card-title">Item by item
|
|
132
|
-
<span data-help-title="Item by item" data-help="What happens to each page, post, product, file and menu. New: made here. Renamed: its address is taken here, so it gets the next free one (the old address redirects to it). Update: made by an earlier import and unchanged here since - refreshed. Left as is: made by an earlier import but edited here since - not touched unless you choose Overwrite. Right-click (or ⋮, or Shift+F10) an item to skip it or change what happens. Needs rebuilding: shortcodes and page builders this site has no equivalent for - the text stays, the layout is yours to rebuild."></span>
|
|
142
|
+
<span data-help-title="Item by item" data-help="What happens to each page, post, product, custom item, collection, file and menu. New: made here. Renamed: its address is taken here, so it gets the next free one (the old address redirects to it). Update: made by an earlier import and unchanged here since - refreshed. Left as is: made by an earlier import but edited here since - not touched unless you choose Overwrite. Right-click (or ⋮, or Shift+F10) an item to skip it or change what happens. Needs rebuilding: shortcodes and page builders this site has no equivalent for - the text stays, the layout is yours to rebuild."></span>
|
|
133
143
|
<span class="wpx-spacer"></span>
|
|
134
144
|
<small class="wpx-note" hidden data-bind-hidden="!refreshing.value">Updating…</small></div>
|
|
135
145
|
<div class="wpx-bar-row">
|
|
@@ -236,6 +246,10 @@
|
|
|
236
246
|
.wpx-chip.is-chosen { box-shadow: inset 0 0 0 1px var(--dm-primary); }
|
|
237
247
|
.wpx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
|
|
238
248
|
.wpx-kebab:hover { color: var(--dm-primary); }
|
|
249
|
+
.wpx-type-ctl { display: flex; flex-wrap: wrap; gap: .4rem .9rem; align-items: center; margin: .3rem 0 .15rem; cursor: default; }
|
|
250
|
+
.wpx-type-row .wpx-where { font-family: inherit; font-size: var(--dm-text-sm); }
|
|
251
|
+
.wpx-internal-box summary { cursor: pointer; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
252
|
+
.wpx-internal { padding-left: 1.1rem; margin: .4rem 0 0; display: flex; flex-direction: column; gap: .25rem; }
|
|
239
253
|
.wpx-report { display: flex; flex-direction: column; gap: .75rem; padding: .25rem 0; }
|
|
240
254
|
@media (max-width: 720px) { .wpx-toggles { grid-template-columns: 1fr; } .wpx-row { flex-wrap: wrap; } .wpx-chips { justify-content: flex-start; } }
|
|
241
255
|
</style>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{apiRequest as q,getToken as
|
|
1
|
+
import{apiRequest as q,getToken as ua}from"../api.js";import{addBannerAction as ma}from"../lib/plugin-chrome.js";import{helpPopovers as Qe}from"../lib/help-popover.js";import{onceClosed as ha,openMenuAt as U,sidebarCount as C,viewLifetime as va}from"../lib/tool-kit.js";import{SHAPES as et,whileLoading as tt}from"../lib/loading.js";import{fmtDateTime as fa}from"../lib/dates.js";import{applyChoice as wa,choiceItems as ba,cleanOptions as he,counts as at,customTypeRows as ya,defaultOptions as Pe,draftKey as ga,draftQuestion as xa,historyRow as ka,internalTypeRows as Ma,isDirty as Ta,kindTabs as Ea,liveDrafts as st,optionsSnapshot as ot,PLAN_FILTERS as nt,planMatches as rt,planRow as $a,planTiles as it,productSummary as La,progressPercent as Sa,progressText as qa,REPORT_FILTERS as lt,reportMatches as ct,reportRow as Ca,stagedRow as Pa,typeMenuItems as Ra,withDraft as pt,withListPage as Aa,withTypeChoice as Ia}from"../lib/wordpress-import-arrange.js";const dt=/^#\/wordpress-import\/?(\?.*)?$/,ee="#/wordpress-import",$="/import/wordpress",ve=400,Da=`
|
|
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>`,
|
|
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,35 @@ import{apiRequest as q,getToken as ea}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>`,
|
|
17
|
+
</div>`,mt=`
|
|
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>`,
|
|
22
|
+
</div>`,Oa=`
|
|
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>`,it='<div class="wpx-tile"><strong>{{value}}</strong><small>{{label}} <span data-bind-hidden="!sub">{{sub}}</span></small></div>',lt='<p class="wpx-alert"><span>{{text}}</span></p>',ya="<li>{{text}}</li>",xa='<div class="wpx-fact"><dt>{{label}}</dt><dd>{{value}}</dd></div>',qe='<button type="button" data-on-click="$root.pickSeg($data)" data-bind-class="cls">{{label}} <b>{{count}}</b></button>',x=oa(st);function ct(l,n,i){return new Promise((w,y)=>{const p=new XMLHttpRequest,L=new FormData;L.append("file",n),p.open("POST",`/api${l}`);const v=ea();v&&p.setRequestHeader("Authorization",`Bearer ${v}`),p.upload.onprogress=c=>{c.lengthComputable&&i(c.loaded/c.total)},p.onload=()=>{let c=null;try{c=JSON.parse(p.responseText)}catch{}p.status>=200&&p.status<300?w(c):y(new Error(c?.error||`The upload failed (${p.status}).`))},p.onerror=()=>y(new Error("The upload failed - the connection dropped.")),p.send(L),x.add(()=>{p.readyState!==4&&p.abort()})})}const fe=l=>l>=1048576?`${Math.round(l/1048576)} MB`:`${Math.max(1,Math.round(l/1024))} KB`,N=l=>String(l??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");function ka(l){const n=document.createElement("template");return n.innerHTML=l.trim(),n.content.firstElementChild}async function ve(l,n){const i=E.confirm(l,n);let w=null;try{w=[...document.querySelectorAll(".dm-dialog-overlay")].at(-1)??null}catch{}const y=x.add(()=>{if(!w)return;const p=w;w=null;try{p.querySelector(".dm-dialog-cancel")?.click(),p.remove()}catch{}});try{return await i}finally{w=null,y()}}function pt(l){navigator.clipboard?.writeText(l).then(()=>E.toast("Copied.",{type:"success"}),()=>E.toast(l,{type:"info",duration:8e3}))}function he(l,n,i,w){const y=E.contextMenu(l,{cursor:"glyph",match:n,inherit:!1,density:"compact",items:i});x.add(()=>y?.destroy?.());const p=v=>{const c=v.target.closest(n);if(!c||!l.contains(c))return;const d=v.target.closest(".wpx-kebab");if(d){X(d);return}w?.(c)},L=v=>{const c=v.target.closest(n);c&&(v.key==="F10"&&v.shiftKey?(v.preventDefault(),X(c)):v.key==="Enter"&&v.target===c&&w&&(v.preventDefault(),v.stopPropagation(),w(c)))};l.addEventListener("click",p),l.addEventListener("keydown",L),x.add(()=>{l.removeEventListener("click",p),l.removeEventListener("keydown",L)})}export const wordpressImportView={templateUrl:"/admin/js/templates/wordpress-import.html",async onMount(l){x.reset();const n=l.get(0).querySelector(".wpx")||l.get(0),i=()=>st.test(location.hash)&&n.isConnected;let w="";try{w=S.get("auth_user")?.id||""}catch{}const y=ia(w),p=()=>{try{return y?S.get(y):null}catch{return null}},L=e=>{try{y&&S.set(y,e)}catch{}},v=n.querySelector(".wpx-loading");let c;try{c=await Ve(v,q(`${$}/status`),Ze.rows)}catch(e){if(!n.isConnected)return;v&&(v.hidden=!0);const t=n.querySelector(".wpx-failed");t&&(t.textContent=`Could not load the importer: ${e.message||e}. Reload the page to try again.`,t.hidden=!1);return}if(!i())return;v&&(v.hidden=!0),n.querySelector(".wpx-body").hidden=!1;const d=M.observable(c.staged||[]),A=M.observable(c.history||[]),H=M.observable(c.blog||{available:!1}),Ce=M.observable(c.shop||{available:!1}),_=c.limits||{},b=M.observable(c.running||null),u=M.observable(""),g=M.observable(null),Q=M.observable(!1),we=M.observable(!1),be=M.observable(""),h=M.observable(Le(H.peek().available)),z=M.observable("all"),P=M.observable(""),ee=M.observable(""),te=M.observable(""),j=M.observable(""),ae=M.observable(""),oe=M.observable(!1);let se=!0;const Ma=()=>d.peek().find(e=>e.id===u.peek())||null,ge=M.computed(()=>d.value.find(e=>e.id===u.value)||null),dt=M.computed(()=>u.value?"preview":"start"),C=M.computed(()=>!!H.value.available),ut=M.computed(()=>!!(g.value?.shop||Ce.value).available),Re=M.computed(()=>!!u.value&&pa(h.value,C.value)),B=e=>M.computed({read:()=>h.value[e]!==!1,write:t=>{h.value={...h.peek(),[e]:!!t}}}),D=e=>{h.value={...h.peek(),...e}},F=e=>{try{return sa(e)}catch{return e}},mt=M.computed(()=>d.value.map(e=>wa(e,F))),ft=M.computed(()=>A.value.map(e=>ca(e,F))),K=M.computed(()=>(g.value?.items||[]).map(e=>({...e,choice:h.value.choices?.[e.key]}))),vt=M.computed(()=>da(K.value).map(e=>({...e,cls:P.value===e.id?"is-on":"",seg:"kind"}))),ht=M.computed(()=>Je(K.value,Ye,Qe,P.value)),wt=M.computed(()=>Ye.map(e=>({...e,count:ht.value[e.id],cls:z.value===e.id?"is-on":"",seg:"filter"}))),ye=M.computed(()=>{const e=ee.value.trim().toLowerCase();return K.value.filter(t=>(!P.value||t.kind===P.value)&&Qe(t,z.value)&&(!e||`${t.title} ${t.target} ${t.oldUrl}`.toLowerCase().includes(e)))}),bt=M.computed(()=>ye.value.slice(0,me).map(e=>{const t=ua(e);return{...t,pillCls:`${t.pillClass}${e.choice?" is-chosen":""}`}})),gt=M.computed(()=>ye.value.length>me?`Showing the first ${me} of ${ye.value.length} - search or filter to find the rest.`:""),yt=M.computed(()=>K.value.length?"Nothing matches.":"This export has nothing to import."),xt=M.computed(()=>et(g.value)),kt=M.computed(()=>(g.value?.notes||[]).map(e=>({text:e}))),Mt=M.computed(()=>(g.value?.redirects||[]).slice(0,me).map(e=>({...e,pill:e.action==="skip"?"Skipped":e.action==="update"?"Update":"New",pillClass:e.action==="skip"?"is-muted":"is-ok"}))),Tt=M.computed(()=>String((g.value?.redirects||[]).filter(e=>e.action!=="skip").length)),Et=M.computed(()=>g.value?.site?.title||ge.value?.siteTitle||"WordPress export"),$t=M.computed(()=>{const e=ge.value,t=g.value?.site;return[t?.url||e?.siteUrl,e?.fileName,t?.wxrVersion&&`WXR ${t.wxrVersion}`,e?.at&&`uploaded ${F(e.at)}`].filter(Boolean).join(" \xB7 ")}),W=M.computed(()=>ma(g.value)),St=M.computed(()=>W.value?.note||""),Lt=M.computed(()=>W.value?.tone==="warn"?"is-warn":"is-info"),qt=M.computed(()=>(W.value?.rows||[]).map(e=>({...e,id:e.label}))),Ct=M.computed(()=>(W.value?.notes||[]).map(e=>({text:e}))),Rt=M.computed(()=>!!W.value),Pt=M.computed(()=>C.value?`Posts go to ${H.value.basePath||"/blog"}.`:H.value.reason||"No Blog is switched on here."),ne=M.computed(()=>ge.value?.uploads||null),At=M.computed(()=>{const e=ne.value;return e?`${e.fileName} - ${e.files} file${e.files===1?"":"s"}, ${fe(e.size)}${e.unsafe?`. ${e.unsafe} with unsafe names (like ../) were ignored`:""}.`:`Zip the wp-content/uploads folder and upload it here (up to ${fe(_.uploads||0)}).`}),It=M.computed(()=>ne.value?"Replace the zip":"Upload the uploads zip"),Pe=M.computed(()=>!!g.value&&!b.value&&!Q.value&&!(h.value.media==="zip"&&!ne.value)),Dt=M.computed(()=>b.value?`Importing${b.value.siteTitle?` ${b.value.siteTitle}`:""}\u2026`:""),Ot=M.computed(()=>va(b.value)),Ae=M.computed(()=>fa(b.value)),Ut=M.observable(fe(_.wxr||0)),Ie=n.querySelector(".wpx-running .wpx-bar-fill");x.add(M.effect(()=>{Ie&&(Ie.style.width=`${Ae.value}%`)}));async function xe(){const e=await q(`${$}/status`);return i()?(d.value=e.staged||[],A.value=e.history||[],H.value=e.blog||{available:!1},Ce.value=e.shop||{available:!1},e.running&&!b.peek()&&(b.value=e.running,V()),e):null}let re=0,ke=null;async function ie({first:e=!1}={}){const t=u.peek();if(!t)return;const a=++re;be.value="";const s=q(`${$}/staged/${t}/preview`,{method:"POST",body:JSON.stringify({options:ue(h.peek(),C.peek())})});try{let f;if(e?(Q.value=!0,f=await Ve(n.querySelector(".wpx-plan-loading"),s,Ze.table)):(we.value=!0,f=await s),a!==re||!i()||u.peek()!==t)return;g.value=f.plan}catch(f){if(a!==re||!i())return;if(f.status===404){E.toast("That upload has gone - upload the export again.",{type:"warning"}),Z(),xe().catch(()=>{});return}be.value=`Could not read the export: ${f.message||f}`}finally{a===re&&(Q.value=!1,we.value=!1)}}let le="";x.add(M.effect(()=>{const e=`${u.value}|${Xe(h.value)}`;if(e===le)return;const t=le.split("|")[0];le=e,!(!u.value||t!==u.value)&&(clearTimeout(ke),ke=setTimeout(()=>ie(),250))})),x.add(()=>clearTimeout(ke)),x.add(M.effect(()=>{const e=u.value,t=h.value,a=Re.value;if(!e||!se||!y)return;const s=p();L(ot(s,w,e,a?{options:ue(t,!0),at:new Date().toISOString(),was:e}:null))}));const Me=e=>{y&&L(ot(p(),w,e,null))};async function ce(e){if(!d.peek().some(m=>m.id===e))return;const t=d.peek().find(m=>m.id===e),a=Ge(p(),d.peek().map(m=>m.id),w).items[e];if(se=!a,g.value=null,z.value="all",P.value="",ee.value="",h.value=Le(C.peek()),le=`${e}|${Xe(h.peek())}`,u.value=e,await ie({first:!0}),!i()||u.peek()!==e||!a)return;await new Promise(m=>setTimeout(m,0));const s=la({when:a.at?F(a.at):"",file:t?.fileName||""}),f=await ve(N(s.text),{title:s.title,confirmText:s.confirmText,cancelText:s.cancelText});!i()||u.peek()!==e||(se=!0,f?Me(e):h.value=ue(a.options,C.peek()))}function Z(){u.value="",g.value=null,se=!0}async function De(e=u.peek()){const t=d.peek().find(a=>a.id===e);if(t&&await ve(`Discard the upload of <strong>${N(t.siteTitle||t.fileName)}</strong>? Nothing has been imported from it.`,{title:"Discard upload",confirmText:"Discard",danger:!0}))try{if(await q(`${$}/staged/${e}`,{method:"DELETE"}),Me(e),!i())return;u.peek()===e&&Z(),d.value=d.peek().filter(a=>a.id!==e),R(Y)}catch(a){E.toast(`Not discarded: ${a.message||a}`,{type:"error"})}}async function Oe(e){if(!e)return;if(j.value="",!/\.(xml|zip)$/i.test(e.name)){j.value="Upload the .xml file WordPress exports, or a .zip holding it.";return}if(_.wxr&&e.size>_.wxr*1.2&&/\.xml$/i.test(e.name)){j.value=`That file is over ${fe(_.wxr)}.`;return}const t=n.querySelector(".wpx-start .wpx-upload .wpx-bar-fill");te.value=`Uploading ${e.name}\u2026`;try{const a=await ct(`${$}/upload`,e,s=>{t&&(t.style.width=`${Math.round(s*100)}%`),te.value=s<1?`Uploading ${e.name} - ${Math.round(s*100)}%`:`Reading ${e.name}\u2026`});if(!i())return;d.value=[a,...d.peek().filter(s=>s.id!==a.id)],R(Y),await ce(a.id)}catch(a){i()&&(j.value=a.message||String(a))}finally{i()&&(te.value="",t&&(t.style.width="0"))}}async function Nt(e){const t=u.peek();if(!e||!t)return;if(!/\.zip$/i.test(e.name)){E.toast("Zip the wp-content/uploads folder and upload the .zip.",{type:"warning"});return}const a=n.querySelector(".wpx-zip-fill");ae.value=`Uploading ${e.name}\u2026`;try{const s=await ct(`${$}/staged/${t}/uploads`,e,f=>{a&&(a.style.width=`${Math.round(f*100)}%`),ae.value=f<1?`Uploading ${e.name} - ${Math.round(f*100)}%`:`Checking ${e.name}\u2026`});if(!i())return;d.value=d.peek().map(f=>f.id===s.id?s:f),u.peek()===t&&ie()}catch(s){i()&&E.toast(s.message||String(s),{type:"error"})}finally{i()&&(ae.value="",a&&(a.style.width="0"))}}async function Ht(){const e=u.peek();try{const t=await q(`${$}/staged/${e}/uploads`,{method:"DELETE"});if(!i())return;d.value=d.peek().map(a=>a.id===t.id?t:a),ie()}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error"})}}async function _t(){const e=u.peek(),t=g.peek();if(!e||!t||!Pe.peek())return;const s=et(t).filter(m=>m.value&&m.id!=="comments").map(m=>`${m.value} ${m.label.toLowerCase()}`);if(!(!await ve(`Import <strong>${N(t.site?.title||"this export")}</strong>: ${N(s.join(", ")||"nothing new")}? Pages edited here since an earlier import are left alone unless you chose Overwrite.`,{title:"Run the import",confirmText:"Run import"})||!i()||u.peek()!==e))try{const m=await q(`${$}/staged/${e}/run`,{method:"POST",body:JSON.stringify({options:ue(h.peek(),C.peek())})});if(Me(e),!i())return;b.value={...m.job,siteTitle:t.site?.title||""},Z(),R(Y),V()}catch(m){E.toast(`The import did not start: ${m.message||m}`,{type:"error"})}}let Te=null;function V(){clearTimeout(Te);const e=b.peek();e&&(Te=setTimeout(async()=>{try{const t=await q(`${$}/jobs/${e.id}`);if(!i())return;if(b.value={...t.job,siteTitle:e.siteTitle},t.job.status==="running")return V();if(b.value=null,await xe().catch(()=>{}),R(Y),R("#/pages"),R("#/media"),R("#/plugins/shopping-cart"),!i())return;t.job.status==="done"?(E.toast("The import has finished - here is its report.",{type:"success"}),pe(t.job.reportId)):(E.toast(`The import stopped: ${t.job.error||"an error"}`,{type:"error"}),t.job.reportId&&pe(t.job.reportId))}catch(t){if(!i())return;if(t.status===404){b.value=null,xe().catch(()=>{});return}V()}},700))}x.add(()=>clearTimeout(Te)),b.peek()&&V();let O=!1;async function pe(e){if(O||!e)return;O=!0;let t;try{t=await q(`${$}/history/${e}`)}catch(o){O=!1,E.toast(`Could not open the report: ${o.message||o}`,{type:"error"});return}if(!i()){O=!1;return}let a;try{a=E.slideover({title:N(`Report - ${t.run?.siteTitle||"WordPress import"}`),size:"lg",position:"right"})}catch(o){throw O=!1,o}const s=ka(`
|
|
27
|
+
</div>`,Ha=`
|
|
28
|
+
<div class="wpx-row wpx-type-row" data-type="{{type}}" tabindex="0">
|
|
29
|
+
<span class="wpx-main">
|
|
30
|
+
<strong>{{label}}</strong>
|
|
31
|
+
<small>{{meta}}</small>
|
|
32
|
+
<span class="wpx-reason" data-bind-hidden="!samples">{{samples}}</span>
|
|
33
|
+
<span class="wpx-type-ctl">
|
|
34
|
+
<span class="wpx-seg" role="group" aria-label="Import {{label}} as">
|
|
35
|
+
<button type="button" data-as="collection" data-bind-class="segCollection">Collection</button>
|
|
36
|
+
<button type="button" data-as="pages" data-bind-class="segPages">Pages</button>
|
|
37
|
+
<button type="button" data-as="skip" data-bind-class="segSkip">Skip</button>
|
|
38
|
+
</span>
|
|
39
|
+
<label class="wpx-toggle" data-bind-hidden="!canList"><input type="checkbox" class="form-switch" data-list-page data-bind-checked="listOn"> {{listLabel}}</label>
|
|
40
|
+
</span>
|
|
41
|
+
<span class="wpx-where">{{creates}}</span>
|
|
42
|
+
</span>
|
|
43
|
+
<span class="wpx-chips"><span class="wpx-chip" data-bind-class="pillClass">{{pill}}</span></span>
|
|
44
|
+
<button type="button" class="wpx-kebab" aria-label="Actions for {{label}}" title="Actions">\u22EE</button>
|
|
45
|
+
</div>`,ht='<div class="wpx-tile"><strong>{{value}}</strong><small>{{label}} <span data-bind-hidden="!sub">{{sub}}</span></small></div>',vt='<p class="wpx-alert"><span>{{text}}</span></p>',ft="<li>{{text}}</li>",Na='<div class="wpx-fact"><dt>{{label}}</dt><dd>{{value}}</dd></div>',Re='<button type="button" data-on-click="$root.pickSeg($data)" data-bind-class="cls">{{label}} <b>{{count}}</b></button>',g=va(dt);function wt(l,n,i){return new Promise((w,x)=>{const d=new XMLHttpRequest,L=new FormData;L.append("file",n),d.open("POST",`/api${l}`);const f=ua();f&&d.setRequestHeader("Authorization",`Bearer ${f}`),d.upload.onprogress=c=>{c.lengthComputable&&i(c.loaded/c.total)},d.onload=()=>{let c=null;try{c=JSON.parse(d.responseText)}catch{}d.status>=200&&d.status<300?w(c):x(new Error(c?.error||`The upload failed (${d.status}).`))},d.onerror=()=>x(new Error("The upload failed - the connection dropped.")),d.send(L),g.add(()=>{d.readyState!==4&&d.abort()})})}const fe=l=>l>=1048576?`${Math.round(l/1048576)} MB`:`${Math.max(1,Math.round(l/1024))} KB`,_=l=>String(l??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");function Ua(l){const n=document.createElement("template");return n.innerHTML=l.trim(),n.content.firstElementChild}async function we(l,n){const i=E.confirm(l,n);let w=null;try{w=[...document.querySelectorAll(".dm-dialog-overlay")].at(-1)??null}catch{}const x=g.add(()=>{if(!w)return;const d=w;w=null;try{d.querySelector(".dm-dialog-cancel")?.click(),d.remove()}catch{}});try{return await i}finally{w=null,x()}}function bt(l){navigator.clipboard?.writeText(l).then(()=>E.toast("Copied.",{type:"success"}),()=>E.toast(l,{type:"info",duration:8e3}))}function te(l,n,i,w){const x=E.contextMenu(l,{cursor:"glyph",match:n,inherit:!1,density:"compact",items:i});g.add(()=>x?.destroy?.());const d=f=>{const c=f.target.closest(n);if(!c||!l.contains(c))return;const u=f.target.closest(".wpx-kebab");if(u){U(u);return}w?.(c)},L=f=>{const c=f.target.closest(n);c&&(f.key==="F10"&&f.shiftKey?(f.preventDefault(),U(c)):f.key==="Enter"&&f.target===c&&w&&(f.preventDefault(),f.stopPropagation(),w(c)))};l.addEventListener("click",d),l.addEventListener("keydown",L),g.add(()=>{l.removeEventListener("click",d),l.removeEventListener("keydown",L)})}export const wordpressImportView={templateUrl:"/admin/js/templates/wordpress-import.html",async onMount(l){g.reset();const n=l.get(0).querySelector(".wpx")||l.get(0),i=()=>dt.test(location.hash)&&n.isConnected;let w="";try{w=S.get("auth_user")?.id||""}catch{}const x=ga(w),d=()=>{try{return x?S.get(x):null}catch{return null}},L=e=>{try{x&&S.set(x,e)}catch{}},f=n.querySelector(".wpx-loading");let c;try{c=await tt(f,q(`${$}/status`),et.rows)}catch(e){if(!n.isConnected)return;f&&(f.hidden=!0);const t=n.querySelector(".wpx-failed");t&&(t.textContent=`Could not load the importer: ${e.message||e}. Reload the page to try again.`,t.hidden=!1);return}if(!i())return;f&&(f.hidden=!0),n.querySelector(".wpx-body").hidden=!1;const u=M.observable(c.staged||[]),A=M.observable(c.history||[]),z=M.observable(c.blog||{available:!1}),Ae=M.observable(c.shop||{available:!1}),B=c.limits||{},y=M.observable(c.running||null),m=M.observable(""),b=M.observable(null),ae=M.observable(!1),be=M.observable(!1),ye=M.observable(""),p=M.observable(Pe(z.peek().available)),D=M.observable("all"),P=M.observable(""),j=M.observable(""),se=M.observable(""),F=M.observable(""),oe=M.observable(""),ne=M.observable(!1);let re=!0;const _a=()=>u.peek().find(e=>e.id===m.peek())||null,ge=M.computed(()=>u.value.find(e=>e.id===m.value)||null),yt=M.computed(()=>m.value?"preview":"start"),R=M.computed(()=>!!z.value.available),gt=M.computed(()=>!!(b.value?.shop||Ae.value).available),Ie=M.computed(()=>!!m.value&&Ta(p.value,R.value)),K=e=>M.computed({read:()=>p.value[e]!==!1,write:t=>{p.value={...p.peek(),[e]:!!t}}}),O=e=>{p.value={...p.peek(),...e}},W=e=>{try{return fa(e)}catch{return e}},xt=M.computed(()=>u.value.map(e=>Pa(e,W))),kt=M.computed(()=>A.value.map(e=>ka(e,W))),Z=M.computed(()=>(b.value?.items||[]).map(e=>({...e,choice:p.value.choices?.[e.key]}))),Mt=M.computed(()=>Ea(Z.value).map(e=>({...e,cls:P.value===e.id?"is-on":"",seg:"kind"}))),Tt=M.computed(()=>at(Z.value,nt,rt,P.value)),Et=M.computed(()=>nt.map(e=>({...e,count:Tt.value[e.id],cls:D.value===e.id?"is-on":"",seg:"filter"}))),xe=M.computed(()=>{const e=j.value.trim().toLowerCase();return Z.value.filter(t=>(!P.value||t.kind===P.value)&&rt(t,D.value)&&(!e||`${t.title} ${t.target} ${t.oldUrl} ${t.postType||""}`.toLowerCase().includes(e)))}),$t=M.computed(()=>xe.value.slice(0,ve).map(e=>{const t=$a(e);return{...t,pillCls:`${t.pillClass}${e.choice?" is-chosen":""}`}})),Lt=M.computed(()=>xe.value.length>ve?`Showing the first ${ve} of ${xe.value.length} - search or filter to find the rest.`:""),St=M.computed(()=>Z.value.length?"Nothing matches.":"This export has nothing to import."),qt=M.computed(()=>it(b.value)),Ct=M.computed(()=>(b.value?.notes||[]).map(e=>({text:e}))),Pt=M.computed(()=>(b.value?.redirects||[]).slice(0,ve).map(e=>({...e,pill:e.action==="skip"?"Skipped":e.action==="update"?"Update":"New",pillClass:e.action==="skip"?"is-muted":"is-ok"}))),Rt=M.computed(()=>String((b.value?.redirects||[]).filter(e=>e.action!=="skip").length)),At=M.computed(()=>b.value?.site?.title||ge.value?.siteTitle||"WordPress export"),It=M.computed(()=>{const e=ge.value,t=b.value?.site;return[t?.url||e?.siteUrl,e?.fileName,t?.wxrVersion&&`WXR ${t.wxrVersion}`,e?.at&&`uploaded ${W(e.at)}`].filter(Boolean).join(" \xB7 ")}),V=M.computed(()=>La(b.value)),Dt=M.computed(()=>V.value?.note||""),Ot=M.computed(()=>V.value?.tone==="warn"?"is-warn":"is-info"),Ht=M.computed(()=>(V.value?.rows||[]).map(e=>({...e,id:e.label}))),Nt=M.computed(()=>(V.value?.notes||[]).map(e=>({text:e}))),Ut=M.computed(()=>!!V.value),ke=M.computed(()=>ya(b.value)),Me=M.computed(()=>Ma(b.value)),_t=M.computed(()=>!!(ke.value.length||Me.value.length)),zt=M.computed(()=>{const e=Me.value.length;return e?`Not imported: ${e} kind${e===1?"":"s"} of WordPress or plugin housekeeping`:""}),Bt=M.computed(()=>R.value?`Posts go to ${z.value.basePath||"/blog"}.`:z.value.reason||"No Blog is switched on here."),ie=M.computed(()=>ge.value?.uploads||null),jt=M.computed(()=>{const e=ie.value;return e?`${e.fileName} - ${e.files} file${e.files===1?"":"s"}, ${fe(e.size)}${e.unsafe?`. ${e.unsafe} with unsafe names (like ../) were ignored`:""}.`:`Zip the wp-content/uploads folder and upload it here (up to ${fe(B.uploads||0)}).`}),Ft=M.computed(()=>ie.value?"Replace the zip":"Upload the uploads zip"),De=M.computed(()=>!!b.value&&!y.value&&!ae.value&&!(p.value.media==="zip"&&!ie.value)),Kt=M.computed(()=>y.value?`Importing${y.value.siteTitle?` ${y.value.siteTitle}`:""}\u2026`:""),Wt=M.computed(()=>qa(y.value)),Oe=M.computed(()=>Sa(y.value)),Zt=M.observable(fe(B.wxr||0)),He=n.querySelector(".wpx-running .wpx-bar-fill");g.add(M.effect(()=>{He&&(He.style.width=`${Oe.value}%`)}));async function Te(){const e=await q(`${$}/status`);return i()?(u.value=e.staged||[],A.value=e.history||[],z.value=e.blog||{available:!1},Ae.value=e.shop||{available:!1},e.running&&!y.peek()&&(y.value=e.running,Y()),e):null}let le=0,Ee=null;async function ce({first:e=!1}={}){const t=m.peek();if(!t)return;const a=++le;ye.value="";const o=q(`${$}/staged/${t}/preview`,{method:"POST",body:JSON.stringify({options:he(p.peek(),R.peek())})});try{let v;if(e?(ae.value=!0,v=await tt(n.querySelector(".wpx-plan-loading"),o,et.table)):(be.value=!0,v=await o),a!==le||!i()||m.peek()!==t)return;b.value=v.plan}catch(v){if(a!==le||!i())return;if(v.status===404){E.toast("That upload has gone - upload the export again.",{type:"warning"}),J(),Te().catch(()=>{});return}ye.value=`Could not read the export: ${v.message||v}`}finally{a===le&&(ae.value=!1,be.value=!1)}}let pe="";g.add(M.effect(()=>{const e=`${m.value}|${ot(p.value)}`;if(e===pe)return;const t=pe.split("|")[0];pe=e,!(!m.value||t!==m.value)&&(clearTimeout(Ee),Ee=setTimeout(()=>ce(),250))})),g.add(()=>clearTimeout(Ee)),g.add(M.effect(()=>{const e=m.value,t=p.value,a=Ie.value;if(!e||!re||!x)return;const o=d();L(pt(o,w,e,a?{options:he(t,!0),at:new Date().toISOString(),was:e}:null))}));const $e=e=>{x&&L(pt(d(),w,e,null))};async function de(e){if(!u.peek().some(h=>h.id===e))return;const t=u.peek().find(h=>h.id===e),a=st(d(),u.peek().map(h=>h.id),w).items[e];if(re=!a,b.value=null,D.value="all",P.value="",j.value="",p.value=Pe(R.peek()),pe=`${e}|${ot(p.peek())}`,m.value=e,await ce({first:!0}),!i()||m.peek()!==e||!a)return;await new Promise(h=>setTimeout(h,0));const o=xa({when:a.at?W(a.at):"",file:t?.fileName||""}),v=await we(_(o.text),{title:o.title,confirmText:o.confirmText,cancelText:o.cancelText});!i()||m.peek()!==e||(re=!0,v?$e(e):p.value=he(a.options,R.peek()))}function J(){m.value="",b.value=null,re=!0}async function Ne(e=m.peek()){const t=u.peek().find(a=>a.id===e);if(t&&await we(`Discard the upload of <strong>${_(t.siteTitle||t.fileName)}</strong>? Nothing has been imported from it.`,{title:"Discard upload",confirmText:"Discard",danger:!0}))try{if(await q(`${$}/staged/${e}`,{method:"DELETE"}),$e(e),!i())return;m.peek()===e&&J(),u.value=u.peek().filter(a=>a.id!==e),C(ee)}catch(a){E.toast(`Not discarded: ${a.message||a}`,{type:"error"})}}async function Ue(e){if(!e)return;if(F.value="",!/\.(xml|zip)$/i.test(e.name)){F.value="Upload the .xml file WordPress exports, or a .zip holding it.";return}if(B.wxr&&e.size>B.wxr*1.2&&/\.xml$/i.test(e.name)){F.value=`That file is over ${fe(B.wxr)}.`;return}const t=n.querySelector(".wpx-start .wpx-upload .wpx-bar-fill");se.value=`Uploading ${e.name}\u2026`;try{const a=await wt(`${$}/upload`,e,o=>{t&&(t.style.width=`${Math.round(o*100)}%`),se.value=o<1?`Uploading ${e.name} - ${Math.round(o*100)}%`:`Reading ${e.name}\u2026`});if(!i())return;u.value=[a,...u.peek().filter(o=>o.id!==a.id)],C(ee),await de(a.id)}catch(a){i()&&(F.value=a.message||String(a))}finally{i()&&(se.value="",t&&(t.style.width="0"))}}async function Vt(e){const t=m.peek();if(!e||!t)return;if(!/\.zip$/i.test(e.name)){E.toast("Zip the wp-content/uploads folder and upload the .zip.",{type:"warning"});return}const a=n.querySelector(".wpx-zip-fill");oe.value=`Uploading ${e.name}\u2026`;try{const o=await wt(`${$}/staged/${t}/uploads`,e,v=>{a&&(a.style.width=`${Math.round(v*100)}%`),oe.value=v<1?`Uploading ${e.name} - ${Math.round(v*100)}%`:`Checking ${e.name}\u2026`});if(!i())return;u.value=u.peek().map(v=>v.id===o.id?o:v),m.peek()===t&&ce()}catch(o){i()&&E.toast(o.message||String(o),{type:"error"})}finally{i()&&(oe.value="",a&&(a.style.width="0"))}}async function Jt(){const e=m.peek();try{const t=await q(`${$}/staged/${e}/uploads`,{method:"DELETE"});if(!i())return;u.value=u.peek().map(a=>a.id===t.id?t:a),ce()}catch(t){E.toast(`Not removed: ${t.message||t}`,{type:"error"})}}async function Yt(){const e=m.peek(),t=b.peek();if(!e||!t||!De.peek())return;const o=it(t).filter(h=>h.value&&h.id!=="comments").map(h=>`${h.value} ${h.label.toLowerCase()}`);if(!(!await we(`Import <strong>${_(t.site?.title||"this export")}</strong>: ${_(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"})||!i()||m.peek()!==e))try{const h=await q(`${$}/staged/${e}/run`,{method:"POST",body:JSON.stringify({options:he(p.peek(),R.peek())})});if($e(e),!i())return;y.value={...h.job,siteTitle:t.site?.title||""},J(),C(ee),Y()}catch(h){E.toast(`The import did not start: ${h.message||h}`,{type:"error"})}}let Le=null;function Y(){clearTimeout(Le);const e=y.peek();e&&(Le=setTimeout(async()=>{try{const t=await q(`${$}/jobs/${e.id}`);if(!i())return;if(y.value={...t.job,siteTitle:e.siteTitle},t.job.status==="running")return Y();if(y.value=null,await Te().catch(()=>{}),C(ee),C("#/pages"),C("#/media"),C("#/plugins/shopping-cart"),C("#/collections"),!i())return;t.job.status==="done"?(E.toast("The import has finished - here is its report.",{type:"success"}),ue(t.job.reportId)):(E.toast(`The import stopped: ${t.job.error||"an error"}`,{type:"error"}),t.job.reportId&&ue(t.job.reportId))}catch(t){if(!i())return;if(t.status===404){y.value=null,Te().catch(()=>{});return}Y()}},700))}g.add(()=>clearTimeout(Le)),y.peek()&&Y();let H=!1;async function ue(e){if(H||!e)return;H=!0;let t;try{t=await q(`${$}/history/${e}`)}catch(s){H=!1,E.toast(`Could not open the report: ${s.message||s}`,{type:"error"});return}if(!i()){H=!1;return}let a;try{a=E.slideover({title:_(`Report - ${t.run?.siteTitle||"WordPress import"}`),size:"lg",position:"right"})}catch(s){throw H=!1,s}const o=Ua(`
|
|
28
46
|
<div class="wpx wpx-report">
|
|
29
47
|
<p class="wpx-note" data-bind-text="meta.value"></p>
|
|
30
48
|
<div class="wpx-tiles" data-each="tiles.value key=id"></div>
|
|
@@ -35,4 +53,4 @@ import{apiRequest as q,getToken as ea}from"../api.js";import{addBannerAction as
|
|
|
35
53
|
<div class="wpx-list wpx-report-items" data-each="rows.value key=key"></div>
|
|
36
54
|
<div class="wpx-card-title" data-bind-hidden="!redirects.value.length">Redirects</div>
|
|
37
55
|
<div class="wpx-list wpx-report-redirects" data-each="redirects.value key=from"></div>
|
|
38
|
-
</div>`);s.querySelector(".wpx-tiles").innerHTML=it,s.querySelector(".wpx-note-list").innerHTML=lt,s.querySelector(".wpx-seg").innerHTML=qe,s.querySelector(".wpx-report-items").innerHTML=nt,s.querySelector(".wpx-report-redirects").innerHTML=rt;const f=M.observable(t.run?.summary?.failed?"failed":"all"),m=t.items||[],Be=Je(m,tt,at),G=t.run?.summary||{},$e=o=>Object.values(G.kinds||{}).reduce((r,k)=>r+(k[o]||0),0),Kt={meta:M.observable([F(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:$e("created"),sub:""},{id:"updated",label:"Updated",value:$e("updated"),sub:""},{id:"kept",label:"Left as is",value:$e("kept"),sub:""},{id:"redirects",label:"Redirects",value:(G.redirects?.created||0)+(G.redirects?.updated||0),sub:""},{id:"comments",label:"Comments",value:G.comments||0,sub:""},{id:"failed",label:"Failed",value:G.failed||0,sub:""}]),notes:M.observable((t.notes||[]).map(o=>({text:o}))),filters:M.computed(()=>tt.filter(o=>o.id==="all"||Be[o.id]).map(o=>({...o,count:Be[o.id],cls:f.value===o.id?"is-on":""}))),rows:M.computed(()=>m.filter(o=>at(o,f.value)).slice(0,1e3).map(o=>{const r=ha(o);return{...r,pillCls:r.pillClass}})),redirects:M.observable((t.redirects||[]).filter(o=>o.result!=="skipped").slice(0,1e3).map(o=>({...o,reason:o.error||"",pill:o.result==="failed"?"Failed":o.result==="updated"?"Updated":"Added",pillClass:o.result==="failed"?"is-bad":"is-ok"})))};a.element.appendChild(s);const Wt=M.applyBindings(Kt,s,{methods:{pickSeg:o=>{f.value=o.id}}}),Zt=We(s),Fe=new Map(m.map(o=>[o.key,o])),Vt=s.querySelector(".wpx-report-items"),Jt=o=>{const r=Fe.get(o.dataset.key);if(!r)return[];const k=typeof r.target=="string"&&r.target.startsWith("/")?r.target:"",T=[{header:r.title}];return k&&r.result!=="failed"&&r.result!=="skipped"&&T.push({label:"Open on the site",icon:"external-link",action:()=>window.open(k,"_blank","noopener")}),k&&r.kind==="page"&&(r.result==="created"||r.result==="updated"||r.result==="kept")&&T.push({label:"Edit the page",icon:"edit",action:()=>{a.close(),location.hash=`#/pages/edit${k}`}}),r.kind==="product"&&r.entryId&&/^[\w-]{1,64}$/.test(r.entryId)&&T.push({label:"Edit in the Shop",icon:"shopping-bag",action:()=>{a.close(),location.hash=`#/plugins/shopping-cart/products/${encodeURIComponent(r.entryId)}`}}),/^https?:\/\//i.test(r.oldUrl)&&T.push({label:"Open the old address",icon:"link",action:()=>window.open(r.oldUrl,"_blank","noopener,noreferrer")}),k&&T.push({label:"Copy the new address",icon:"copy",action:()=>pt(new URL(k,location.origin).href)}),T},Gt=E.contextMenu(Vt,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:Jt}),Xt=s.querySelector(".wpx-report-redirects"),Yt=E.contextMenu(Xt,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:o=>[{header:o.dataset.from},...o.dataset.from.includes("*")?[]:[{label:"Try it on the site",icon:"external-link",action:()=>window.open(o.dataset.from,"_blank","noopener")}],{label:"Open SEO \u203A Redirects",icon:"corner-up-right",action:()=>{a.close(),location.hash="#/seo/redirects"}}]}),Se=o=>{const r=o.target.closest(".wpx-kebab");if(r){X(r);return}const k=o.target.closest(".wpx-report-items .wpx-row");if(!k)return;const T=Fe.get(k.dataset.key);T?.target?.startsWith("/")&&T.result!=="failed"&&T.result!=="skipped"&&window.open(T.target,"_blank","noopener")},Ke=o=>{const r=o.target.closest(".wpx-row");r&&(o.key==="F10"&&o.shiftKey?(o.preventDefault(),X(r)):o.key==="Enter"&&o.target===r&&(o.preventDefault(),o.stopPropagation(),Se({target:r})))};s.addEventListener("click",Se),s.addEventListener("keydown",Ke);const Qt=x.add(()=>a.close());aa(a,()=>{O=!1,Wt.dispose(),Zt(),Gt?.destroy?.(),Yt?.destroy?.(),s.removeEventListener("click",Se),s.removeEventListener("keydown",Ke),Qt()}),a.open(),I.scan(s)}async function zt(e){const t=A.peek().find(a=>a.id===e);if(t&&await ve(`Remove the import of <strong>${N(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 q(`${$}/history/${e}`,{method:"DELETE"}),!i())return;A.value=A.peek().filter(a=>a.id!==e),R(Y)}catch(a){E.toast(`Not removed: ${a.message||a}`,{type:"error"})}}n.querySelector(".wpx-staged").innerHTML=ba,n.querySelector(".wpx-items").innerHTML=nt,n.querySelector(".wpx-redirects").innerHTML=rt,n.querySelector(".wpx-history").innerHTML=ga,n.querySelector(".wpx-tiles").innerHTML=it,n.querySelector(".wpx-note-list").innerHTML=lt,n.querySelector(".wpx-kinds").innerHTML=qe,n.querySelector(".wpx-filters").innerHTML=qe,n.querySelector(".wpx-facts").innerHTML=xa,n.querySelector(".wpx-product-notes").innerHTML=ya;const jt={mode:dt,job:b,jobTitle:Dt,jobText:Ot,jobPct:Ae,staged:d,stagedRows:mt,historyRows:ft,plan:g,planLoading:Q,planError:be,refreshing:we,opt:h,blogAvailable:C,blogNote:Pt,dirty:Re,canRun:Pe,siteTitle:Et,siteMeta:$t,tiles:xt,notes:kt,kinds:vt,filters:wt,rows:bt,moreText:gt,emptyText:yt,redirectRows:Mt,redirectCount:Tt,query:ee,uploadText:te,uploadError:j,dropping:oe,wxrLimit:Ut,zipInfo:ne,zipText:At,zipButton:It,zipUploadText:ae,shopAvailable:ut,hasProducts:Rt,productNote:St,productNoteCls:Lt,productFacts:qt,productNotes:Ct,optComments:B("comments"),optMenus:B("menus"),optRedirects:B("redirects"),optDrafts:B("drafts"),optProducts:B("products")},Bt=M.applyBindings(jt,n,{methods:{back:()=>Z(),discard:()=>De(),run:()=>_t(),resetChoices:()=>{h.value=Le(C.peek())},postsBlog:()=>D({posts:"blog"}),postsPages:()=>D({posts:"pages"}),postsSkip:()=>D({posts:"skip"}),mediaDownload:()=>D({media:"download"}),mediaZip:()=>D({media:"zip"}),mediaSkip:()=>D({media:"skip"}),removeZip:()=>Ht(),pickSeg:e=>{e.seg==="kind"?P.value=e.id:z.value=e.id},showProducts:()=>{P.value="product",z.value="all",ee.value="",n.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})}}});x.add(()=>Bt.dispose()),x.add(We(n,{position:"bottom"}));const J=n.querySelector(".wpx-file"),Ue=()=>{const e=J.files?.[0];J.value="",Oe(e)};J.addEventListener("change",Ue);const U=n.querySelector(".wpx-drop"),Ne=e=>{e.preventDefault(),oe.value=!0},He=()=>{oe.value=!1},_e=e=>{e.preventDefault(),oe.value=!1,Oe(e.dataTransfer?.files?.[0])};U.addEventListener("dragover",Ne),U.addEventListener("dragleave",He),U.addEventListener("drop",_e);const de=n.querySelector(".wpx-zip-file"),ze=()=>{const e=de.files?.[0];de.value="",Nt(e)};de.addEventListener("change",ze),x.add(()=>{J.removeEventListener("change",Ue),U.removeEventListener("dragover",Ne),U.removeEventListener("dragleave",He),U.removeEventListener("drop",_e),de.removeEventListener("change",ze)}),he(n.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:()=>ce(e.dataset.id)},{label:"Discard the upload",icon:"trash-2",action:()=>De(e.dataset.id)}],e=>ce(e.dataset.id));const Ft=(e,t)=>{h.value={...h.peek(),choices:na(h.peek().choices,e,t)}};he(n.querySelector(".wpx-items"),".wpx-row",e=>{const t=K.peek().find(s=>s.key===e.dataset.key);if(!t)return[];const a=[{header:t.title}];for(const s of ra(t,t.choice))a.push({label:s.label,icon:s.icon,action:()=>Ft(t.key,s.id)});return t.oldUrl&&/^https?:\/\//i.test(t.oldUrl)&&a.push({label:"Open the old address",icon:"link",action:()=>window.open(t.oldUrl,"_blank","noopener,noreferrer")}),t.target?.startsWith("/")&&(t.action==="update"||t.action==="keep")&&a.push({label:"Open it on this site",icon:"external-link",action:()=>window.open(t.target,"_blank","noopener")}),a.length===1&&a.push({label:"Nothing to change here",icon:"info",disabled:!0,action:()=>{}}),a},e=>X(e.querySelector(".wpx-kebab")||e)),he(n.querySelector(".wpx-redirects"),".wpx-row",e=>[{header:e.dataset.from},{label:"Copy the old address",icon:"copy",action:()=>pt(e.dataset.from)}],null),he(n.querySelector(".wpx-history"),".wpx-row",e=>[{header:A.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Import"},{label:"Open the report",icon:"file-text",action:()=>pe(e.dataset.id)},{label:"Remove from the history",icon:"trash-2",action:()=>zt(e.dataset.id)}],e=>pe(e.dataset.id)),ta(l,{id:"wpx-upload",icon:"upload",title:"Upload an export",onClick:()=>{u.peek()&&Z(),setTimeout(()=>J.click(),0)}}),I.scan(n);const Ee=Ge(p(),d.peek().map(e=>e.id),w);Ee.pruned&&y&&L({owner:w,items:Ee.items});const je=Object.entries(Ee.items).sort((e,t)=>String(t[1].at).localeCompare(String(e[1].at)))[0];je&&!b.peek()&&await ce(je[0])}};
|
|
56
|
+
</div>`);o.querySelector(".wpx-tiles").innerHTML=ht,o.querySelector(".wpx-note-list").innerHTML=vt,o.querySelector(".wpx-seg").innerHTML=Re,o.querySelector(".wpx-report-items").innerHTML=ut,o.querySelector(".wpx-report-redirects").innerHTML=mt;const v=M.observable(t.run?.summary?.failed?"failed":"all"),h=t.items||[],Ye=at(h,lt,ct),Q=t.run?.summary||{},qe=s=>Object.values(Q.kinds||{}).reduce((r,T)=>r+(T[s]||0),0),aa={meta:M.observable([W(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:qe("created"),sub:""},{id:"updated",label:"Updated",value:qe("updated"),sub:""},{id:"kept",label:"Left as is",value:qe("kept"),sub:""},{id:"redirects",label:"Redirects",value:(Q.redirects?.created||0)+(Q.redirects?.updated||0),sub:""},{id:"comments",label:"Comments",value:Q.comments||0,sub:""},{id:"failed",label:"Failed",value:Q.failed||0,sub:""}]),notes:M.observable((t.notes||[]).map(s=>({text:s}))),filters:M.computed(()=>lt.filter(s=>s.id==="all"||Ye[s.id]).map(s=>({...s,count:Ye[s.id],cls:v.value===s.id?"is-on":""}))),rows:M.computed(()=>h.filter(s=>ct(s,v.value)).slice(0,1e3).map(s=>{const r=Ca(s);return{...r,pillCls:r.pillClass}})),redirects:M.observable((t.redirects||[]).filter(s=>s.result!=="skipped").slice(0,1e3).map(s=>({...s,reason:s.error||"",pill:s.result==="failed"?"Failed":s.result==="updated"?"Updated":"Added",pillClass:s.result==="failed"?"is-bad":"is-ok"})))};a.element.appendChild(o);const sa=M.applyBindings(aa,o,{methods:{pickSeg:s=>{v.value=s.id}}}),oa=Qe(o),Ge=new Map(h.map(s=>[s.key,s])),na=o.querySelector(".wpx-report-items"),ra=s=>{const r=Ge.get(s.dataset.key);if(!r)return[];const T=typeof r.target=="string"&&r.target.startsWith("/")?r.target:"",k=[{header:r.title}];if(T&&r.result!=="failed"&&r.result!=="skipped"&&k.push({label:"Open on the site",icon:"external-link",action:()=>window.open(T,"_blank","noopener")}),T&&(r.kind==="page"||r.kind==="custom"&&!r.collection)&&(r.result==="created"||r.result==="updated"||r.result==="kept")&&k.push({label:"Edit the page",icon:"edit",action:()=>{a.close(),location.hash=`#/pages/edit${T}`}}),(r.kind==="collection"||r.kind==="custom")&&r.result!=="failed"&&r.result!=="skipped"&&/^[a-z0-9-]{1,80}$/.test(r.kind==="collection"?r.target:r.collection||"")){const da=r.kind==="collection"?r.target:r.collection;k.push({label:"Open the collection",icon:"database",action:()=>{a.close(),location.hash=`#/collections/${da}/entries`}})}return r.kind==="product"&&r.entryId&&/^[\w-]{1,64}$/.test(r.entryId)&&k.push({label:"Edit in the Shop",icon:"shopping-bag",action:()=>{a.close(),location.hash=`#/plugins/shopping-cart/products/${encodeURIComponent(r.entryId)}`}}),/^https?:\/\//i.test(r.oldUrl)&&k.push({label:"Open the old address",icon:"link",action:()=>window.open(r.oldUrl,"_blank","noopener,noreferrer")}),T&&k.push({label:"Copy the new address",icon:"copy",action:()=>bt(new URL(T,location.origin).href)}),k},ia=E.contextMenu(na,{cursor:"glyph",match:".wpx-row",inherit:!1,density:"compact",items:ra}),la=o.querySelector(".wpx-report-redirects"),ca=E.contextMenu(la,{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"}}]}),Ce=s=>{const r=s.target.closest(".wpx-kebab");if(r){U(r);return}const T=s.target.closest(".wpx-report-items .wpx-row");if(!T)return;const k=Ge.get(T.dataset.key);k?.target?.startsWith("/")&&k.result!=="failed"&&k.result!=="skipped"&&window.open(k.target,"_blank","noopener")},Xe=s=>{const r=s.target.closest(".wpx-row");r&&(s.key==="F10"&&s.shiftKey?(s.preventDefault(),U(r)):s.key==="Enter"&&s.target===r&&(s.preventDefault(),s.stopPropagation(),Ce({target:r})))};o.addEventListener("click",Ce),o.addEventListener("keydown",Xe);const pa=g.add(()=>a.close());ha(a,()=>{H=!1,sa.dispose(),oa(),ia?.destroy?.(),ca?.destroy?.(),o.removeEventListener("click",Ce),o.removeEventListener("keydown",Xe),pa()}),a.open(),I.scan(o)}async function Gt(e){const t=A.peek().find(a=>a.id===e);if(t&&await we(`Remove the import of <strong>${_(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 q(`${$}/history/${e}`,{method:"DELETE"}),!i())return;A.value=A.peek().filter(a=>a.id!==e),C(ee)}catch(a){E.toast(`Not removed: ${a.message||a}`,{type:"error"})}}n.querySelector(".wpx-staged").innerHTML=Da,n.querySelector(".wpx-items").innerHTML=ut,n.querySelector(".wpx-redirects").innerHTML=mt,n.querySelector(".wpx-history").innerHTML=Oa,n.querySelector(".wpx-tiles").innerHTML=ht,n.querySelector(".wpx-note-list").innerHTML=vt,n.querySelector(".wpx-kinds").innerHTML=Re,n.querySelector(".wpx-filters").innerHTML=Re,n.querySelector(".wpx-facts").innerHTML=Na,n.querySelector(".wpx-product-notes").innerHTML=ft,n.querySelector(".wpx-types").innerHTML=Ha,n.querySelector(".wpx-internal").innerHTML=ft;const Xt={mode:yt,job:y,jobTitle:Kt,jobText:Wt,jobPct:Oe,staged:u,stagedRows:xt,historyRows:kt,plan:b,planLoading:ae,planError:ye,refreshing:be,opt:p,blogAvailable:R,blogNote:Bt,dirty:Ie,canRun:De,siteTitle:At,siteMeta:It,tiles:qt,notes:Ct,kinds:Mt,filters:Et,rows:$t,moreText:Lt,emptyText:St,redirectRows:Pt,redirectCount:Rt,query:j,uploadText:se,uploadError:F,dropping:ne,wxrLimit:Zt,zipInfo:ie,zipText:jt,zipButton:Ft,zipUploadText:oe,shopAvailable:gt,hasProducts:Ut,productNote:Dt,productNoteCls:Ot,productFacts:Ht,productNotes:Nt,typeRows:ke,internalRows:Me,hasCustom:_t,internalSummary:zt,optComments:K("comments"),optMenus:K("menus"),optRedirects:K("redirects"),optDrafts:K("drafts"),optProducts:K("products")},Qt=M.applyBindings(Xt,n,{methods:{back:()=>J(),discard:()=>Ne(),run:()=>Yt(),resetChoices:()=>{p.value=Pe(R.peek())},postsBlog:()=>O({posts:"blog"}),postsPages:()=>O({posts:"pages"}),postsSkip:()=>O({posts:"skip"}),mediaDownload:()=>O({media:"download"}),mediaZip:()=>O({media:"zip"}),mediaSkip:()=>O({media:"skip"}),removeZip:()=>Jt(),pickSeg:e=>{e.seg==="kind"?P.value=e.id:D.value=e.id},showProducts:()=>{P.value="product",D.value="all",j.value="",n.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})}}});g.add(()=>Qt.dispose()),g.add(Qe(n,{position:"bottom"}));const G=n.querySelector(".wpx-file"),_e=()=>{const e=G.files?.[0];G.value="",Ue(e)};G.addEventListener("change",_e);const N=n.querySelector(".wpx-drop"),ze=e=>{e.preventDefault(),ne.value=!0},Be=()=>{ne.value=!1},je=e=>{e.preventDefault(),ne.value=!1,Ue(e.dataTransfer?.files?.[0])};N.addEventListener("dragover",ze),N.addEventListener("dragleave",Be),N.addEventListener("drop",je);const me=n.querySelector(".wpx-zip-file"),Fe=()=>{const e=me.files?.[0];me.value="",Vt(e)};me.addEventListener("change",Fe),g.add(()=>{G.removeEventListener("change",_e),N.removeEventListener("dragover",ze),N.removeEventListener("dragleave",Be),N.removeEventListener("drop",je),me.removeEventListener("change",Fe)}),te(n.querySelector(".wpx-staged"),".wpx-row",e=>[{header:u.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Upload"},{label:"Preview the import",icon:"eye",action:()=>de(e.dataset.id)},{label:"Discard the upload",icon:"trash-2",action:()=>Ne(e.dataset.id)}],e=>de(e.dataset.id));const ea=(e,t)=>{p.value={...p.peek(),choices:wa(p.peek().choices,e,t)}};te(n.querySelector(".wpx-items"),".wpx-row",e=>{const t=Z.peek().find(o=>o.key===e.dataset.key);if(!t)return[];const a=[{header:t.title}];for(const o of ba(t,t.choice))a.push({label:o.label,icon:o.icon,action:()=>ea(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=>U(e.querySelector(".wpx-kebab")||e));const Ke=(e,t)=>{p.value=Ia(p.peek(),e,t)},We=(e,t)=>{p.value=Aa(p.peek(),e,t)},ta=e=>{P.value="",D.value="all",j.value=e,n.querySelector(".wpx-items")?.closest(".wpx-card")?.scrollIntoView({behavior:"smooth",block:"start"})},X=n.querySelector(".wpx-types"),Ze=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&&Ke(o.dataset.type,a.dataset.as)},Ve=e=>{const t=e.target.closest("input[data-list-page]"),a=e.target.closest(".wpx-type-row");t&&a&&We(a.dataset.type,t.checked)};X.addEventListener("click",Ze,!0),X.addEventListener("change",Ve),g.add(()=>{X.removeEventListener("click",Ze,!0),X.removeEventListener("change",Ve)}),te(X,".wpx-type-row",e=>{const t=ke.peek().find(a=>a.type===e.dataset.type);return t?[{header:`${t.label} ("${t.type}")`},...Ra(t).map(a=>({label:a.label,icon:a.icon,action:()=>{a.id==="show"?ta(t.type):a.id==="list"||a.id==="nolist"?We(t.type,a.id==="list"):Ke(t.type,a.id)}}))]:[]},e=>U(e.querySelector(".wpx-kebab")||e)),te(n.querySelector(".wpx-redirects"),".wpx-row",e=>[{header:e.dataset.from},{label:"Copy the old address",icon:"copy",action:()=>bt(e.dataset.from)}],null),te(n.querySelector(".wpx-history"),".wpx-row",e=>[{header:A.peek().find(t=>t.id===e.dataset.id)?.siteTitle||"Import"},{label:"Open the report",icon:"file-text",action:()=>ue(e.dataset.id)},{label:"Remove from the history",icon:"trash-2",action:()=>Gt(e.dataset.id)}],e=>ue(e.dataset.id)),ma(l,{id:"wpx-upload",icon:"upload",title:"Upload an export",onClick:()=>{m.peek()&&J(),setTimeout(()=>G.click(),0)}}),I.scan(n);const Se=st(d(),u.peek().map(e=>e.id),w);Se.pruned&&x&&L({owner:w,items:Se.items});const Je=Object.entries(Se.items).sort((e,t)=>String(t[1].at).localeCompare(String(e[1].at)))[0];Je&&!y.peek()&&await de(Je[0])}};
|
package/package.json
CHANGED
|
@@ -65,7 +65,10 @@ export function planForScreen(plan, blog, shop = {}) {
|
|
|
65
65
|
target: r.target || '', oldUrl: r.oldUrl || '', status: r.status || '', wpStatus: r.wpStatus || '',
|
|
66
66
|
shortcodes: r.shortcodes || [], builder: r.builder || '', comments: r.comments || 0, count: r.count || 0,
|
|
67
67
|
replaces: Boolean(r.replaces), wanted: r.wanted || '',
|
|
68
|
-
...(r.kind === 'product' && {productType: r.productType || '', variants: r.variants || 0, needs: r.needs || ''})
|
|
68
|
+
...(r.kind === 'product' && {productType: r.productType || '', variants: r.variants || 0, needs: r.needs || ''}),
|
|
69
|
+
...(r.kind === 'custom' && {as: r.as || '', postType: r.postType || '', collection: r.collection || '', listAt: r.listAt || ''}),
|
|
70
|
+
...(r.kind === 'collection' && {postType: r.postType || '', fields: r.fields || 0}),
|
|
71
|
+
...(r.listFor && {listFor: r.listFor})
|
|
69
72
|
})),
|
|
70
73
|
redirects: plan.redirects.map(r => ({from: r.from, to: r.to, action: r.action, reason: r.reason || ''}))
|
|
71
74
|
};
|
|
@@ -57,6 +57,37 @@ marked.setOptions({
|
|
|
57
57
|
// Collection shortcode helpers
|
|
58
58
|
// ---------------------------------------------------------------------------
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Make text from a collection entry inert to the shortcode stages that run
|
|
62
|
+
* after `[collection]` / `[view]`.
|
|
63
|
+
*
|
|
64
|
+
* Entry values are DATA - public form submissions among them - and the
|
|
65
|
+
* rendered display is spliced into the page before tabs, form, embed, badge,
|
|
66
|
+
* button, card, slideover and the rest run, so a stored `[form id="x" /]`
|
|
67
|
+
* would otherwise be executed on the public page. Applied to the HTML that
|
|
68
|
+
* carries entry values (escaped text, attribute values, a rendered Markdown
|
|
69
|
+
* body, a `{{{raw}}}` placeholder), never to authored markup: a block
|
|
70
|
+
* template's own shortcodes still run.
|
|
71
|
+
*
|
|
72
|
+
* Every shortcode pattern starts `[name`, `[/name` or `[ name`, so the
|
|
73
|
+
* character after each `[` is written as a numeric entity (`[form` becomes
|
|
74
|
+
* `[form`) and a trailing `[` as `[`. Nothing can match, yet the
|
|
75
|
+
* brackets themselves stay, so a Markdown link in a value that a block
|
|
76
|
+
* template leaves to marked (`[text](url)`) is still a link. Entities read
|
|
77
|
+
* back as the same characters in text and attribute values, and the final
|
|
78
|
+
* sanitiser decodes them, so nothing visible changes; the base64 `data-ctx`
|
|
79
|
+
* / table payloads carry no brackets and are left as is, so filter, search
|
|
80
|
+
* and export see the stored values.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} html - already-escaped HTML or text
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
export function inertShortcodes(html) {
|
|
86
|
+
return String(html ?? '')
|
|
87
|
+
.replace(/\[([\w\s\/-])/g, (_, c) => `[&#${c.codePointAt(0)};`)
|
|
88
|
+
.replace(/\[$/, '[');
|
|
89
|
+
}
|
|
90
|
+
|
|
60
91
|
function escapeHtmlText(str) {
|
|
61
92
|
return String(str ?? '')
|
|
62
93
|
.replace(/&/g, '&')
|
|
@@ -349,7 +380,9 @@ function interpolateBlockTemplate(blockTemplate, e) {
|
|
|
349
380
|
// One pass: a raw value put in (pre-rendered HTML) is never read again for
|
|
350
381
|
// placeholders - text that shows "{{title}}" keeps it.
|
|
351
382
|
return blockTemplate.replace(/\{\{\{(\w+)\}\}\}|\{\{([\w_]+)(?::([^{}]+))?\}\}/g, (_, raw, key, fmt) => {
|
|
352
|
-
|
|
383
|
+
// Entry values never carry a shortcode into the page (inertShortcodes);
|
|
384
|
+
// the template's own markup is untouched.
|
|
385
|
+
if (raw !== undefined) return inertShortcodes(String(e.data?.[raw] ?? ''));
|
|
353
386
|
let value;
|
|
354
387
|
if (key === '_id') value = e.id ?? '';
|
|
355
388
|
else if (key === '_createdAt') value = e.meta?.createdAt ?? '';
|
|
@@ -357,9 +390,9 @@ function interpolateBlockTemplate(blockTemplate, e) {
|
|
|
357
390
|
else value = e.data?.[key] ?? '';
|
|
358
391
|
if (fmt) {
|
|
359
392
|
const formatted = formatDate(value, fmt);
|
|
360
|
-
if (formatted !== null) return escapeHtmlText(formatted);
|
|
393
|
+
if (formatted !== null) return inertShortcodes(escapeHtmlText(formatted));
|
|
361
394
|
}
|
|
362
|
-
return escapeHtmlText(value);
|
|
395
|
+
return inertShortcodes(escapeHtmlText(value));
|
|
363
396
|
});
|
|
364
397
|
}
|
|
365
398
|
|
|
@@ -401,14 +434,14 @@ function renderCollectionBlocks(entries, blockTemplate, emptyMsg, ctaOpts, cols,
|
|
|
401
434
|
|
|
402
435
|
const items = entries.map(e => {
|
|
403
436
|
const entryCls = entryClassSuffix(ctxOpts).trim();
|
|
404
|
-
const wrapOpen = `<div${entryCls ? ` class="${entryCls}"` : ''}${blockAttr} data-entry-id="${escapeAttr(String(e.id ?? ''))}">`;
|
|
437
|
+
const wrapOpen = `<div${entryCls ? ` class="${entryCls}"` : ''}${blockAttr} data-entry-id="${inertShortcodes(escapeAttr(String(e.id ?? '')))}">`;
|
|
405
438
|
const wrapClose = `</div>`;
|
|
406
439
|
let html = interpolateBlockTemplate(blockTemplate, e);
|
|
407
440
|
|
|
408
441
|
if (ctaOpts) {
|
|
409
442
|
const ctaStyle = escapeAttr(ctaOpts.style || 'primary');
|
|
410
443
|
let btnCls = `btn btn-${ctaStyle} dm-cta-trigger`;
|
|
411
|
-
let btnData = `data-action="${escapeAttr(ctaOpts.action)}" data-entry="${escapeAttr(e.id || '')}"`;
|
|
444
|
+
let btnData = `data-action="${escapeAttr(ctaOpts.action)}" data-entry="${inertShortcodes(escapeAttr(e.id || ''))}"`;
|
|
412
445
|
if (ctaOpts.confirm) btnData += ` data-confirm="${escapeAttr(ctaOpts.confirm)}"`;
|
|
413
446
|
if (ctaOpts.scope) btnData += ` data-scope="${escapeAttr(ctaOpts.scope)}"`;
|
|
414
447
|
const iconHtml = ctaOpts.icon ? `<span data-icon="${escapeAttr(ctaOpts.icon)}"></span> ` : '';
|
|
@@ -636,15 +669,16 @@ function renderCollectionCards(entries, visibleFields, titleField, columns, empt
|
|
|
636
669
|
if (ctaOpts) {
|
|
637
670
|
const ctaStyle = escapeAttr(ctaOpts.style || 'primary');
|
|
638
671
|
let btnCls = `btn btn-${ctaStyle} dm-cta-trigger`;
|
|
639
|
-
let btnData = `data-action="${escapeAttr(ctaOpts.action)}" data-entry="${escapeAttr(e.id || '')}"`;
|
|
672
|
+
let btnData = `data-action="${escapeAttr(ctaOpts.action)}" data-entry="${inertShortcodes(escapeAttr(e.id || ''))}"`;
|
|
640
673
|
if (ctaOpts.confirm) btnData += ` data-confirm="${escapeAttr(ctaOpts.confirm)}"`;
|
|
641
674
|
if (ctaOpts.scope) btnData += ` data-scope="${escapeAttr(ctaOpts.scope)}"`;
|
|
642
675
|
const iconHtml = ctaOpts.icon ? `<span data-icon="${escapeAttr(ctaOpts.icon)}"></span> ` : '';
|
|
643
676
|
footer = `<div class="card-footer"><button class="${btnCls}" ${btnData}>${iconHtml}${escapeHtmlText(ctaOpts.label || 'Run')}</button></div>`;
|
|
644
677
|
}
|
|
645
|
-
return `<div class="card${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${title ? `<div class="card-header">${title}</div>` : ''}<div class="card-body">${body || ' '}</div>${footer}</div>`;
|
|
678
|
+
return `<div class="card${entryClassSuffix(ctxOpts)}" data-entry-id="${inertShortcodes(escapeAttr(String(e.id ?? '')))}">${title ? `<div class="card-header">${title}</div>` : ''}<div class="card-body">${body || ' '}</div>${footer}</div>`;
|
|
646
679
|
}).join('\n');
|
|
647
|
-
|
|
680
|
+
// No authored markup in this display - everything bracketed in it is entry data.
|
|
681
|
+
return inertShortcodes(`<div class="dm-collection-display grid ${responsiveGridCols(cols).join(' ')} gap-4"${buildCtxAttr(visibleFields, entries, ctxOpts)}>\n${cards}\n</div>`);
|
|
648
682
|
}
|
|
649
683
|
|
|
650
684
|
function renderCollectionList(entries, visibleFields, titleField, emptyMsg, ctaOpts, ctxOpts = {}) {
|
|
@@ -664,15 +698,16 @@ function renderCollectionList(entries, visibleFields, titleField, emptyMsg, ctaO
|
|
|
664
698
|
if (ctaOpts) {
|
|
665
699
|
const ctaStyle = escapeAttr(ctaOpts.style || 'primary');
|
|
666
700
|
let btnCls = `btn btn-${ctaStyle} dm-cta-trigger`;
|
|
667
|
-
let btnData = `data-action="${escapeAttr(ctaOpts.action)}" data-entry="${escapeAttr(e.id || '')}"`;
|
|
701
|
+
let btnData = `data-action="${escapeAttr(ctaOpts.action)}" data-entry="${inertShortcodes(escapeAttr(e.id || ''))}"`;
|
|
668
702
|
if (ctaOpts.confirm) btnData += ` data-confirm="${escapeAttr(ctaOpts.confirm)}"`;
|
|
669
703
|
if (ctaOpts.scope) btnData += ` data-scope="${escapeAttr(ctaOpts.scope)}"`;
|
|
670
704
|
const iconHtml = ctaOpts.icon ? `<span data-icon="${escapeAttr(ctaOpts.icon)}"></span> ` : '';
|
|
671
705
|
ctaHtml = `<button class="${btnCls}" ${btnData}>${iconHtml}${escapeHtmlText(ctaOpts.label || 'Run')}</button>`;
|
|
672
706
|
}
|
|
673
|
-
return `<div class="dm-collection-list-item${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${title ? `<strong>${title}</strong>` : ''}${rest}${ctaHtml}</div>`;
|
|
707
|
+
return `<div class="dm-collection-list-item${entryClassSuffix(ctxOpts)}" data-entry-id="${inertShortcodes(escapeAttr(String(e.id ?? '')))}">${title ? `<strong>${title}</strong>` : ''}${rest}${ctaHtml}</div>`;
|
|
674
708
|
}).join('\n');
|
|
675
|
-
|
|
709
|
+
// No authored markup in this display - everything bracketed in it is entry data.
|
|
710
|
+
return inertShortcodes(`<div class="dm-collection-display dm-collection-list"${buildCtxAttr(visibleFields, entries, ctxOpts)}>\n${items}\n</div>`);
|
|
676
711
|
}
|
|
677
712
|
|
|
678
713
|
/**
|
|
@@ -695,7 +730,7 @@ function renderCollectionAccordion(entries, titleField, bodyField, multiple, emp
|
|
|
695
730
|
const bodyVal = String(e.data?.[bodyField] ?? '');
|
|
696
731
|
const bodyHtml = marked.parse(bodyVal);
|
|
697
732
|
return (
|
|
698
|
-
`<div class="accordion-item${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">\n` +
|
|
733
|
+
`<div class="accordion-item${entryClassSuffix(ctxOpts)}" data-entry-id="${inertShortcodes(escapeAttr(String(e.id ?? '')))}">\n` +
|
|
699
734
|
` <h3 class="accordion-header"><button class="accordion-button" type="button">${title}` +
|
|
700
735
|
`<span class="accordion-icon" data-icon="chevron-down"></span></button></h3>\n` +
|
|
701
736
|
` <div class="accordion-body"><div class="accordion-content">${bodyHtml}</div></div>\n` +
|
|
@@ -704,7 +739,8 @@ function renderCollectionAccordion(entries, titleField, bodyField, multiple, emp
|
|
|
704
739
|
}).join('\n');
|
|
705
740
|
|
|
706
741
|
const ctx = buildCtxAttr([{name: titleField}, {name: bodyField}], entries, ctxOpts);
|
|
707
|
-
|
|
742
|
+
// No authored markup in this display - everything bracketed in it is entry data.
|
|
743
|
+
return inertShortcodes(`<div class="dm-collection-display accordion"${multiAttr}${ctx}>\n${items}\n</div>`);
|
|
708
744
|
}
|
|
709
745
|
|
|
710
746
|
/**
|
|
@@ -740,7 +776,7 @@ function renderCollectionTimeline(entries, opts, ctxOpts = {}) {
|
|
|
740
776
|
const bodyHtml = bodyField
|
|
741
777
|
? marked.parse(String(e.data?.[bodyField] ?? ''))
|
|
742
778
|
: '';
|
|
743
|
-
return `<div class="dm-progression-item${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}"${date}${status}${icon}><div class="dm-progression-item-title">${title}</div><div class="dm-progression-item-body">${bodyHtml}</div></div>`;
|
|
779
|
+
return `<div class="dm-progression-item${entryClassSuffix(ctxOpts)}" data-entry-id="${inertShortcodes(escapeAttr(String(e.id ?? '')))}"${date}${status}${icon}><div class="dm-progression-item-title">${title}</div><div class="dm-progression-item-body">${bodyHtml}</div></div>`;
|
|
744
780
|
}).join('\n');
|
|
745
781
|
|
|
746
782
|
const ctx = buildCtxAttr(
|
|
@@ -748,7 +784,8 @@ function renderCollectionTimeline(entries, opts, ctxOpts = {}) {
|
|
|
748
784
|
entries,
|
|
749
785
|
ctxOpts
|
|
750
786
|
);
|
|
751
|
-
|
|
787
|
+
// No authored markup in this display - everything bracketed in it is entry data.
|
|
788
|
+
return inertShortcodes(`<div class="dm-collection-display dm-progression" data-layout="${layout}" data-theme="${theme}" data-mode="${mode}"${ctx}>\n${items}\n</div>`);
|
|
752
789
|
}
|
|
753
790
|
|
|
754
791
|
/**
|
|
@@ -799,9 +836,9 @@ function renderCollectionCarousel(entries, opts, ctxOpts = {}) {
|
|
|
799
836
|
const bodyHtml = bodyField && e.data?.[bodyField] != null
|
|
800
837
|
? `<div class="carousel-slide-description">${marked.parse(String(e.data[bodyField]))}</div>`
|
|
801
838
|
: '';
|
|
802
|
-
inner = img + (title || bodyHtml ? `<div class="carousel-slide-content">${title}${bodyHtml}</div>` : '');
|
|
839
|
+
inner = inertShortcodes(img + (title || bodyHtml ? `<div class="carousel-slide-content">${title}${bodyHtml}</div>` : ''));
|
|
803
840
|
}
|
|
804
|
-
return `<div class="carousel-slide${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${inner}</div>`;
|
|
841
|
+
return `<div class="carousel-slide${entryClassSuffix(ctxOpts)}" data-entry-id="${inertShortcodes(escapeAttr(String(e.id ?? '')))}">${inner}</div>`;
|
|
805
842
|
}).join('\n');
|
|
806
843
|
|
|
807
844
|
const ctxFields = blockTemplate
|
|
@@ -892,9 +929,9 @@ function renderCollectionListgroup(entries, opts, ctxOpts = {}) {
|
|
|
892
929
|
const body = bodyField && e.data?.[bodyField] != null
|
|
893
930
|
? `${title ? ' ' : ''}${marked.parseInline(String(e.data[bodyField]))}`
|
|
894
931
|
: '';
|
|
895
|
-
inner = (title + body) || escapeHtmlText(String(e.id ?? ''));
|
|
932
|
+
inner = inertShortcodes((title + body) || escapeHtmlText(String(e.id ?? '')));
|
|
896
933
|
}
|
|
897
|
-
return `<div class="${itemClassStr}${entryClassSuffix(ctxOpts)}" data-entry-id="${escapeAttr(String(e.id ?? ''))}">${inner}</div>`;
|
|
934
|
+
return `<div class="${itemClassStr}${entryClassSuffix(ctxOpts)}" data-entry-id="${inertShortcodes(escapeAttr(String(e.id ?? '')))}">${inner}</div>`;
|
|
898
935
|
}).join('\n');
|
|
899
936
|
|
|
900
937
|
const ctxFields = blockTemplate
|
|
@@ -954,7 +991,7 @@ async function processViewBlocks(markdown, tagSet, user = null, flags = {}) {
|
|
|
954
991
|
// browsing happens without the shortcode itself having to learn joins.
|
|
955
992
|
if (isInteractiveCollection(attrs)) {
|
|
956
993
|
const shell = await renderViewBrowserShell(attrs, {viewConfig, allowed, user});
|
|
957
|
-
result = result.replace(fullMatch, shell);
|
|
994
|
+
result = result.replace(fullMatch, () => shell);
|
|
958
995
|
continue;
|
|
959
996
|
}
|
|
960
997
|
|
|
@@ -1097,7 +1134,7 @@ async function processViewBlocks(markdown, tagSet, user = null, flags = {}) {
|
|
|
1097
1134
|
replacement = `<div class="dm-view-wrapper${cls}"${id}>${replacement}</div>`;
|
|
1098
1135
|
}
|
|
1099
1136
|
|
|
1100
|
-
result = result.replace(fullMatch, replacement);
|
|
1137
|
+
result = result.replace(fullMatch, () => replacement);
|
|
1101
1138
|
}
|
|
1102
1139
|
|
|
1103
1140
|
return restore(result);
|
|
@@ -1154,12 +1191,12 @@ async function processCollectionBlocks(markdown, tagSet) {
|
|
|
1154
1191
|
// browser hydrates and replaces it).
|
|
1155
1192
|
if (isInteractiveCollection(attrs)) {
|
|
1156
1193
|
const shell = await renderCollectionBrowserShell(attrs);
|
|
1157
|
-
result = result.replace(fullMatch, shell);
|
|
1194
|
+
result = result.replace(fullMatch, () => shell);
|
|
1158
1195
|
continue;
|
|
1159
1196
|
}
|
|
1160
1197
|
|
|
1161
1198
|
const replacement = await renderCollectionFragment(attrs);
|
|
1162
|
-
result = result.replace(fullMatch, replacement);
|
|
1199
|
+
result = result.replace(fullMatch, () => replacement);
|
|
1163
1200
|
}
|
|
1164
1201
|
|
|
1165
1202
|
return restore(result);
|