@moorage/plugin-seo 0.1.8 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @moorage/plugin-seo
2
2
 
3
+ ## 0.1.11
4
+
5
+ ### Patch Changes
6
+
7
+ - 8370eca: New screenshots of the current admin page, with the help line at its foot.
8
+ - 6516427: Admins see a small help line at the foot of the page, with a link to the guide; it can be hidden in Settings.
9
+ - ee58b7f: The README now says to restart the site after changing its address.
10
+
11
+ ## 0.1.10
12
+
13
+ ### Patch Changes
14
+
15
+ - 8e315f1: The EmDash registry listing gets an icon and a banner; no change to how the plugin works.
16
+
17
+ ## 0.1.9
18
+
19
+ ### Patch Changes
20
+
21
+ - 395c5f0: On sites that run SEO sandboxed on Cloudflare, every page view, save, background step and SEO page action now stays within the sandbox's limit of 10 calls per request. Before, some of them could fail part-way:
22
+
23
+ - The first view of a page saved before the plugin was installed (on a new site, every page until the page check had run) went over the limit. That view now describes the page from the page itself, with the same structured data as before (dates, authors, picture sizes, your own JSON-LD), and stores nothing; the page's check result is stored by the page check or the page's next save.
24
+ - A page view no longer builds records or copies settings; it writes only when the picture shared for a page changes. Settings saved by 0.1.7 or earlier are copied to their new place by a visit to the SEO page once the page check has finished, or by the next save.
25
+ - Switching the plugin on starts the page check with fewer calls, and a check step on a site with several empty collections no longer goes over.
26
+ - Importing redirects adds what fits in the click and the rest in the background, a few each minute. Removing an import works the same way, with a progress bar, "Remove more now" and "Stop here". The SEO page says how far it got.
27
+
3
28
  ## 0.1.8
4
29
 
5
30
  ### Patch Changes
package/README.md CHANGED
@@ -65,7 +65,9 @@ Not yet: Product and Event types (they need price and date fields on the collect
65
65
  - Does it add FAQ rich results? No. Google stopped showing FAQ rich results in May 2026.
66
66
  - How do I import redirects from WordPress? Export them as CSV from Redirection, Yoast SEO Premium or Rank Math (Rank Math's CSV export is in its paid version; Redirection's JSON, .htaccess and Nginx exports are not read), open SEO > Redirects > Import from WordPress, paste the list and press Check list. Nothing is added until you confirm.
67
67
  - Why were some redirects skipped? EmDash has no regular-expression redirects. Exact addresses and "everything under a folder" are converted; other patterns are listed for you to rewrite by hand. Redirects to another website and addresses with a `?` are not supported by EmDash.
68
- - Why does a large import take a while on Cloudflare? Sandboxed plugins may only do a few operations per request, so a long list is added a few rows at a time in the background. You can leave the page.
68
+ - Why does a large import take a while on Cloudflare? Sandboxed plugins may only do a few operations per request, so a long list is added a few rows at a time in the background, and removing an import works the same way. The page shows how far it got. You can leave the page.
69
+ - What is the line at the foot of the page? Only admins see it: "Help · More free plugins · by Moorage", links to the guide on moorage.dev and to more free plugins. Editors never see it. To hide it, switch on "Hide the help line at the foot of this page" at the bottom of the Settings tab and press Save.
70
+ - Why do the Google preview and the checks show the site's old address? Plugins keep the site address EmDash had when the server started; SEO uses it for the preview, for checking canonicals and logo links, and for the structured data when a page has no address of its own. After changing the site's address, restart the site.
69
71
 
70
72
  ## Changelog
71
73
 
package/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  /** @type {import("emdash").PluginDescriptor} */
8
8
  const descriptor = Object.freeze({
9
9
  "id": "seo",
10
- "version": "0.1.8",
10
+ "version": "0.1.11",
11
11
  "format": "standard",
12
12
  "entrypoint": "@moorage/plugin-seo/sandbox",
13
13
  "capabilities": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "seo",
3
- "version": "0.1.8",
3
+ "version": "0.1.11",
4
4
  "declaredAccess": {
5
5
  "content": {
6
6
  "read": {}
package/dist/plugin.mjs CHANGED
@@ -1,6 +1,6 @@
1
- function e(e){return e!=null}async function t(e,t,n){try{await e.settings.set(t.name,n)}catch(n){e.log.warn(`could not copy the settings to their new place`,{key:t.name,error:String(n)})}}async function n(n,r){let i=await n.settings.get(r.name);if(e(i))return i;let a=await n.kv.get(r.legacy);return e(a)?(await t(n,r,a),a):null}async function r(n,r,i){let a=r.get(`settings:${i.name}`);if(e(a))return a;let o=r.get(i.legacy);return e(o)?(await t(n,i,o),o):null}async function i(e,t,n){await e.settings.set(t.name,n);try{await e.kv.set(t.legacy,n)}catch(n){e.log.warn(`could not keep a copy of the settings for the earlier release`,{key:t.legacy,error:String(n)})}}function a(e,t){try{return new URL(e,t)}catch{return null}}const o={name:`config`,legacy:`config`};async function s(e){return x(await n(e,o))}const c=[`WebPage`,`AboutPage`,`ContactPage`,`CollectionPage`,`ProfilePage`],l=[`Article`,`BlogPosting`,`NewsArticle`],u=[`Service`];function d(e){let t=e.toLowerCase();return[`posts`,`post`,`blog`].includes(t)?`BlogPosting`:[`news`,`articles`,`article`].includes(t)?`Article`:[`services`,`service`].includes(t)?`Service`:[`team`,`people`,`staff`,`authors`].includes(t)?`ProfilePage`:`WebPage`}const f={site:{type:`Organization`,sameAs:[],openingHours:[],areaServed:[]},collections:{},robots:{maxSnippet:-1,maxImagePreview:`large`,maxVideoPreview:-1,nosnippet:!1},jsonLdField:`json_ld`,breadcrumbs:!0,steps:{identity:!1,types:!1}};function p(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function m(e){if(typeof e!=`string`)return;let t=e.trim();return t===``?void 0:t}function h(e){let t=m(e),n=t?a(t)?.protocol:void 0;return n===`https:`||n===`http:`?t:void 0}function g(e,t){return typeof e==`number`&&Number.isInteger(e)&&e>=-1?e:t}function _(e){let t=[...c,...l,...u];return typeof e==`string`&&t.includes(e)?e:void 0}function v(e){if(!p(e))return;let t={};for(let n of[`streetAddress`,`addressLocality`,`addressRegion`,`postalCode`,`addressCountry`]){let r=m(e[n]);r&&(t[n]=r)}return Object.keys(t).length>0?t:void 0}function y(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(!p(n)||!Array.isArray(n.days))continue;let e=n.days.filter(e=>typeof e==`string`&&e!==``),r=m(n.opens),i=m(n.closes);e.length>0&&r&&i&&t.push({days:e,opens:r,closes:i})}return t}function b(e){if(!p(e))return{...f.site};let t=p(e.geo)?e.geo:void 0,n=t?.latitude,r=t?.longitude;return{type:m(e.type)??`Organization`,name:m(e.name),alternateName:m(e.alternateName),legalName:m(e.legalName),description:m(e.description),logo:h(e.logo),sameAs:Array.isArray(e.sameAs)?e.sameAs.map(h).filter(e=>e!==void 0):[],email:m(e.email),telephone:m(e.telephone),address:v(e.address),geo:typeof n==`number`&&typeof r==`number`?{latitude:n,longitude:r}:void 0,openingHours:y(e.openingHours),priceRange:m(e.priceRange),areaServed:Array.isArray(e.areaServed)?e.areaServed.map(m).filter(e=>e!==void 0):[],foundingDate:m(e.foundingDate),vatID:m(e.vatID),taxID:m(e.taxID),numberOfEmployees:typeof e.numberOfEmployees==`number`&&e.numberOfEmployees>0?Math.round(e.numberOfEmployees):void 0}}function x(e){if(!p(e))return f;let t={};if(p(e.collections))for(let[n,r]of Object.entries(e.collections))p(r)&&(t[n]={type:_(r.type),noindex:r.noindex===!0,...r.hiddenOk===!0?{hiddenOk:!0}:{}});let n=p(e.robots)?e.robots:{},r=n.maxImagePreview;return{site:b(e.site),collections:t,robots:{maxSnippet:g(n.maxSnippet,f.robots.maxSnippet),maxImagePreview:r===`none`||r===`standard`||r===`large`?r:f.robots.maxImagePreview,maxVideoPreview:g(n.maxVideoPreview,f.robots.maxVideoPreview),nosnippet:n.nosnippet===!0},jsonLdField:m(e.jsonLdField)??f.jsonLdField,breadcrumbs:e.breadcrumbs!==!1,steps:{identity:p(e.steps)&&e.steps.identity===!0,types:p(e.steps)&&e.steps.types===!0}}}const S=[`Monday`,`Tuesday`,`Wednesday`,`Thursday`,`Friday`,`Saturday`,`Sunday`],C=/^(\d{1,2})(?:[:.](\d{2}))?\s*[-–—]\s*(\d{1,2})(?:[:.](\d{2}))?$/;function w(e,t){let n=Number(e),r=Number(t??`0`);return n>24||r>59||n===24&&r!==0?null:`${String(n).padStart(2,`0`)}:${String(r).padStart(2,`0`)}`}function T(e,t){let n=t.trim();if(n===``||n.toLowerCase()===`closed`)return[];let r=n.split(/\s*[,;]\s*|\s+and\s+/i).filter(e=>e!==``);if(r.length>2)return null;let i=[];for(let t of r){let n=C.exec(t);if(!n)return null;let r=w(n[1]??``,n[2]),a=w(n[3]??``,n[4]);if(!r||!a)return null;i.push({days:[e],opens:r,closes:a})}return i}function E(e,t){return t.filter(t=>t.days.includes(e)).map(e=>`${e.opens}-${e.closes}`).join(`, `)}const D=[{value:`WebPage`,label:`Web page`,short:`Web page`},{value:`AboutPage`,label:`About page`,short:`About page`},{value:`ContactPage`,label:`Contact page`,short:`Contact page`},{value:`BlogPosting`,label:`Blog post`,short:`Blog post`},{value:`Article`,label:`Article`,short:`Article`},{value:`Service`,label:`Service`,short:`Service`},{value:`ProfilePage`,label:`Person or team member`,short:`Person`}];function O(e){return D.find(t=>t.value===e)?.short??e}const k=[301,302,307,308,410,451],A=2e3,j={empty:`No old address`,query:`Addresses with a ? are not supported`,external:`Points to another website`,noTarget:`No new address`,same:`Old and new address are the same`,code:`Unknown redirect type`,pattern:`Uses a pattern. Add it by hand under Manage > Redirects`,inactive:`Switched off in the export`,repeated:`Listed twice`,exists:`Already exists`,conflict:`Conflicts with an existing redirect`,loop:`Would send visitors in a circle`,tooMany:`Only the first ${A} rows are imported at once`};function M(e,t){let n=[],r=``,i=!1;for(let a=0;a<e.length;a+=1){let o=e[a];i?o===`"`&&e[a+1]===`"`?(r+=`"`,a+=1):o===`"`?i=!1:r+=o:o===`"`?i=!0:o===t?(n.push(r.trim()),r=``):r+=o}n.push(r.trim());let a=n[0]??``;return n.length===1&&/\S\s+\S/.test(a)?a.split(/\s+/):n}const ee={source:`source`,origin:`source`,from:`source`,"old url":`source`,old:`source`,sources:`source`,target:`target`,destination:`target`,to:`target`,"new url":`target`,new:`target`,url_to:`target`,regex:`regex`,code:`code`,"http code":`code`,type:`code`,header_code:`code`,format:`format`,matching:`matching`,status:`status`,ignore:`ignore`,id:`id`};function te(e,t){let n=e.map(e=>ee[e.toLowerCase()]),r=n.includes(`source`)&&n.includes(`target`);if(r){let e={};return n.forEach((t,n)=>{t&&e[t]===void 0&&(e[t]=n)}),{format:e.matching===void 0?e.format===void 0?e.regex===void 0?`plain`:`redirection`:`yoast`:`rankmath`,hasHeader:r,index:e}}let i=t.slice(0,20),a=i.map(e=>e[2]??``),o=i.map(e=>(e[3]??``).toLowerCase());return o.length>0&&o.every(e=>e===`plain`||e===`regex`)?{format:`yoast`,hasHeader:r,index:{source:0,target:1,code:2,format:3}}:a.length>0&&a.every(e=>e===`0`||e===`1`)?{format:`redirection`,hasHeader:r,index:{source:0,target:1,regex:2,code:3}}:{format:`plain`,hasHeader:r,index:{source:0,target:1,code:2}}}function ne(e){let t=e.trim();if(t===``)return{reason:j.empty};if(/^https?:\/\//i.test(t)){let e=a(t);if(!e)return{reason:j.empty};t=`${e.pathname}${e.search}`}return t.includes(`?`)?{reason:j.query}:(t.startsWith(`/`)||(t=`/${t}`),t.length>1&&(t=t.replace(/\/+$/,``)),{path:t===``?`/`:t})}const re=/[\\^$.*+?()[\]{}|]/;function ie(e,t){let n=e.trim();if(n.startsWith(`^`)&&(n=n.slice(1)),n.endsWith(`$`)&&(n=n.slice(0,-1)),n.endsWith(`/?`)&&(n=n.slice(0,-2)),!re.test(n))return{source:n,target:t};let r=/^(\/[^\\^$.*+?()[\]{}|]*\/)\(\.\*\)$/.exec(n);return r&&!/\$[2-9]/.test(t)?{source:`${r[1]}[...rest]`,target:t.replace(/\$1/g,`[...rest]`)}:null}function ae(e){if(e===void 0||e.trim()===``)return 301;let t=Number(e.trim());return k.includes(t)?t:null}function oe(e,t){let n=e.split(/\r?\n/),r=n.some(e=>e.includes(` `))?` `:`,`,i=n.map((e,t)=>({line:t+1,cells:M(e,r)})).filter(e=>e.cells.some(e=>e!==``)),o=i[0];if(!o)return{format:`plain`,ready:[],skipped:[]};let s=te(o.cells,i.map(e=>e.cells)),c=s.hasHeader?i.slice(1):i,l=(e,t)=>{let n=s.index[t];return n===void 0?void 0:e[n]},u=[],d=[],f=new Map(t.map(e=>[e.source,e.destination])),p=new Set;for(let{line:e,cells:t}of c){let n=l(t,`source`)??``,r=t=>d.push({line:e,source:n,reason:t});if(u.length>=A){r(j.tooMany);continue}if([`inactive`,`disabled`].includes((l(t,`status`)??`active`).toLowerCase())){r(j.inactive);continue}let i=ae(l(t,`code`));if(i===null){r(j.code);continue}let o=n,s=l(t,`target`)??``,c=(l(t,`matching`)??`exact`).toLowerCase();if(l(t,`regex`)===`1`||(l(t,`format`)??``).toLowerCase()===`regex`||c===`regex`){let e=ie(o,s);if(!e){r(j.pattern);continue}o=e.source,s=e.target}else if(c===`start`&&o.trim().endsWith(`/`))o=`${o.trim()}[...rest]`;else if(c!==`exact`){r(j.pattern);continue}let m=ne(o);if(`reason`in m){r(m.reason);continue}let h=i===410||i===451,g=``;if(!h){if(s.trim()===``){r(j.noTarget);continue}let e=/^https?:\/\//i.test(s.trim()),t=/^https?:\/\//i.test(n.trim())?a(n.trim())?.host:void 0;if(e&&t!==a(s.trim())?.host){r(j.external);continue}let i=ne(s);if(`reason`in i){r(i.reason===j.empty?j.noTarget:i.reason);continue}if(g=i.path,g===m.path){r(j.same);continue}}if(p.has(m.path)){r(j.repeated);continue}let _=f.get(m.path);if(_!==void 0){r(_===g?j.exists:j.conflict);continue}p.add(m.path),u.push({line:e,source:m.path,destination:g,type:i})}let m=new Map(f);for(let e of u)m.set(e.source,e.destination);let h=[];for(let e of u){let t=e.destination,n=!1;for(let r=0;r<20&&t!==``&&m.has(t);r+=1)if(t=m.get(t)??``,t===e.source){n=!0;break}n?d.push({line:e.line,source:e.source,reason:j.loop}):h.push({source:e.source,destination:e.destination,type:e.type})}return{format:s.format,ready:h,skipped:d}}function se(e){let t=e=>/[",\n]/.test(e)?`"${e.replace(/"/g,`""`)}"`:e;return[`source,target,code`,...e.map(e=>`${t(e.source)},${t(e.destination)},${e.type}`)].join(`
2
- `)}const N=`redirects`,P=`redirects-import`,ce={redirection:`Redirection`,yoast:`Yoast SEO Premium`,rankmath:`Rank Math`,plain:`a plain list`};async function le(e){let t=await e.kv.get(N);return t&&Array.isArray(t.rows)?t:null}async function ue(e){let t=[],n;for(let r=0;r<5;r+=1){let r=await e.redirects?.list({limit:100,cursor:n});if(!r||(t.push(...r.items),n=r.hasMore?r.cursor:void 0,!n))break}return t}async function de(e,t){let n=oe(t,await ue(e)),r={status:`checked`,format:n.format,rows:n.ready,skipped:n.skipped,cursor:0,added:0,group:`Imported ${new Date().toISOString().slice(0,16).replace(`T`,` `)}`};return await e.kv.set(N,r),r}function fe(e){let t=e instanceof Error?e.message:String(e);return/already exists/i.test(t)?{kind:`exists`,message:t}:/invalid source pattern|must be different|destination|path traversal|loop/i.test(t)?{kind:`invalid`,message:t}:{kind:`stop`,message:t}}async function pe(e,t){let n={...t,status:`adding`,skipped:[...t.skipped]},r=async()=>{try{return await e.kv.set(N,n),!0}catch{return!1}};if(t.status===`checked`)try{await e.cron?.schedule(P,{schedule:`* * * * *`})}catch(t){e.log.warn(`could not schedule background import`,{error:String(t)})}let i=0,a=n.cursor;for(;n.cursor<n.rows.length;){let t=n.rows[n.cursor];if(!t)break;try{await e.redirects?.create?.({source:t.source,destination:t.destination,type:t.type,groupName:n.group}),n.added+=1}catch(e){let{kind:r,message:i}=fe(e);if(r===`stop`){if(n.cursor!==a||(n.stalls=(n.stalls??0)+1,n.stalls<3))break;n.skipped.push({line:0,source:t.source,reason:`EmDash could not add it: ${i}`})}else r===`exists`?n.added+=1:n.skipped.push({line:0,source:t.source,reason:`EmDash refused it: ${i}`})}if(n.cursor+=1,n.stalls=0,i+=1,i>=4){if(!await r())return n;i=0}}if(n.cursor>=n.rows.length){n.status=`done`;try{await e.cron?.cancel(P)}catch{}}return await r(),n}async function me(e,t){let n=await e.redirects?.list({limit:100,group:t.group}),r=n?.items??[],i=r.length;for(let t of r)try{let n=await e.redirects?.get(t.id);n&&await e.redirects?.delete?.(t.id,{_rev:n._rev}),--i}catch{break}if(i===0&&!n?.hasMore){try{await e.cron?.cancel(P)}catch{}return await e.kv.delete(N),0}return i}async function he(e){let t=[],n;for(let r=0;r<5;r+=1){let r=await e.redirects?.list({limit:100,cursor:n});if(!r||(t.push(...r.items),n=r.hasMore?r.cursor:void 0,!n))break}return{text:se(t),count:t.length}}function F(e,t){return`${e} ${t}${e===1?``:`s`}`}function ge(e){if(e.length===0)return[];let t=[{type:`header`,text:`Skipped (${e.length})`},{type:`context`,text:`Skipped rows are not added. Copy any you still need and add them by hand under Manage > Redirects, which also accepts patterns.`},{type:`table`,page_action_id:`skipped_table`,columns:[{key:`line`,label:`Row`},{key:`source`,label:`Old address`},{key:`reason`,label:`Why`}],rows:e.slice(0,50).map(e=>({line:e.line>0?String(e.line):``,source:e.source,reason:e.reason}))}];return e.length>50&&t.push({type:`context`,text:`And ${e.length-50} more.`}),t}const _e=[{value:`301`,label:`Permanent`},{value:`302`,label:`Temporary, the old address will come back`},{value:`410`,label:`Page removed on purpose, no new address`}];function ve(e){return e===301||e===308?`Permanent`:e===302||e===307?`Temporary`:e===410?`Deleted on purpose`:String(e)}async function ye(e){let t=await e.redirects?.list({limit:20});return{rows:(t?.items??[]).map(({id:e,source:t,destination:n,type:r,hits:i})=>({id:e,source:t,destination:n,type:r,hits:i})),more:t?.hasMore??!1}}function be(e){let t=e instanceof Error?e.message:String(e);return/already exists/i.test(t)?`There is already a redirect from that old address. Use Change in the list below.`:/must be different/i.test(t)?`The old and new address are the same.`:/loop/i.test(t)?`That would send visitors in a circle.`:/changed since/i.test(t)?`That redirect was just changed elsewhere. Try again.`:`EmDash could not save that redirect. Check both addresses start with /.`}function xe(e,t,n){let r=oe(se([{source:e,destination:t,type:Number(n)||301}]),[]);return r.ready[0]??r.skipped[0]?.reason??`Type the old address first.`}async function Se(e,t,n,r){let i=xe(t,n,r);if(typeof i==`string`)return`${i}.`;try{return await e.redirects?.create?.(i),null}catch(e){return be(e)}}async function Ce(e,t,n,r,i){let a=xe(n,r,i);if(typeof a==`string`)return`${a}.`;try{let n=await e.redirects?.get(t);return n?(await e.redirects?.update?.(t,{...a,_rev:n._rev}),null):`That redirect no longer exists.`}catch(e){return be(e)}}async function we(e,t){try{let n=await e.redirects?.get(t);return n&&await e.redirects?.delete?.(t,{_rev:n._rev}),null}catch(e){return be(e)}}function Te(e){if(!e)return[{type:`context`,text:`Paste a list exported from Redirection, Yoast SEO Premium or Rank Math, or any list of old address, new address. Nothing is added until you confirm.`},{type:`form`,block_id:`redirects`,fields:[{type:`text_input`,action_id:`list`,label:`Your list, one redirect per line`,multiline:!0,placeholder:`/old-page,/new-page`,initial_value:``}],submit:{label:`Check list`,action_id:`check_redirects`}}];if(e.status===`checked`){let t=e.rows.length;return[{type:`banner`,variant:t===0?`alert`:`default`,title:`${t} ready, ${e.skipped.length} skipped`,description:t===0?`Nothing in this list can be added. See why below.`:`Read as ${ce[e.format]}. Nothing has been added yet. Existing redirects are never changed.`},{type:`actions`,elements:[...t>0?[{type:`button`,action_id:`add_redirects`,label:`Add ${F(t,`redirect`)}`,style:`primary`,confirm:{title:`Add ${F(t,`redirect`)}?`,text:`Visitors to these old addresses will be sent to the new ones. You can remove them all again afterwards.`,confirm:`Add`,deny:`Cancel`}}]:[],{type:`button`,action_id:`reset_redirects`,label:`Start again`}]},...ge(e.skipped)]}return e.status===`adding`?[{type:`meter`,label:`${e.added} of ${e.rows.length} added`,value:e.cursor,max:e.rows.length},{type:`context`,text:`The rest are added in the background, a few each minute. You can leave this page.`},{type:`actions`,elements:[{type:`button`,action_id:`more_redirects`,label:`Add more now`,style:`primary`},{type:`button`,action_id:`stop_redirects`,label:`Stop here`}]}]:[{type:`banner`,title:`${F(e.added,`redirect`)} added from your list`,description:`They are labelled "${e.group}".`},{type:`actions`,elements:[{type:`button`,action_id:`reset_redirects`,label:`Import another list`},...e.added>0?[{type:`button`,action_id:`remove_redirects`,label:`Remove the ${e.added} just added`,confirm:{title:`Remove these redirects?`,text:`Only the redirects this import added are removed.`,confirm:`Remove`,deny:`Cancel`,style:`danger`}}]:[]]},...ge(e.skipped)]}function Ee(e,t){let n=e?String(e.type):`301`;return{type:`form`,block_id:e?`edit_redirect_${e.id}`:`add_one_${t}`,fields:[{type:`text_input`,action_id:`source`,label:`Redirect from`,placeholder:`/old-page`,initial_value:e?.source??``},{type:`text_input`,action_id:`destination`,label:`Redirect to`,placeholder:`/new-page`,initial_value:e?.destination??``,condition:{field:`code`,neq:`410`}},{type:`select`,action_id:`code`,label:`Kind`,options:_e.some(e=>e.value===n)?_e:[..._e,{value:n,label:`${ve(Number(n))} (${n})`}],initial_value:n}],submit:e?{label:`Save changes`,action_id:`save_redirect:${e.id}`}:{label:`Add redirect`,action_id:`add_redirect`}}}function De(e){let{state:t,rows:n,editing:r}=e,i=r?void 0:e.panel??(t?`import`:void 0),a=n[0]?.id??`first`,o=[{type:`context`,text:`Send visitors from an old address to a new one. Pages you rename in EmDash are redirected for you.`},{type:`actions`,elements:[{type:`button`,action_id:`open_add`,label:`Add a redirect`,...i===void 0&&!r?{style:`primary`}:{}},{type:`button`,action_id:`open_import`,label:`Import from WordPress`},{type:`button`,action_id:`export_redirects`,label:`Export`},...e.managerUrl?[{type:`link`,label:`Full Redirects screen`,target:{kind:`external`,url:e.managerUrl}}]:[]]}],s={type:`actions`,elements:[{type:`button`,action_id:`close_panel`,label:`Close`}]},c=e.problem?[{type:`banner`,variant:`error`,title:e.problem}]:[];return r?o.push({type:`header`,text:`Change a redirect`},...c,Ee(r,a),s,{type:`divider`}):i===`add`?o.push({type:`header`,text:`Add a redirect`},...c,Ee(void 0,a),s,{type:`divider`}):i===`import`?(o.push({type:`header`,text:`Import from WordPress`},...Te(t)),t||o.push(s),o.push({type:`divider`})):i===`export`&&o.push({type:`header`,text:`Export`},{type:`context`,text:`Copy this list to keep a backup or to paste into a spreadsheet. It can be imported again here.`},{type:`code`,code:e.exported??``,language:`bash`},s,{type:`divider`}),n.length===0?o.push({type:`empty`,title:`No redirects yet`,description:`Add one, or import a list from your old site.`,size:`sm`}):(o.push({type:`table`,page_action_id:`redirects_table`,columns:[{key:`source`,label:`Redirect from`},{key:`destination`,label:`Redirect to`},{key:`kind`,label:`Kind`},{key:`hits`,label:`Visits`,format:`number`},{key:`edit`,label:``,format:`element`},{key:`remove`,label:``,format:`element`}],rows:n.map(e=>({source:e.source,destination:e.destination||`(none)`,kind:ve(e.type),hits:e.hits,edit:{type:`button`,action_id:`edit_redirect`,label:`Change`,value:e.id},remove:{type:`button`,action_id:`remove_redirect`,label:`Remove`,value:e.id,confirm:{title:`Remove this redirect?`,text:`Visitors to ${e.source} will see a not-found page.`,confirm:`Remove`,deny:`Keep`,style:`danger`}}}))}),e.more&&o.push({type:`context`,text:`Showing the newest ${n.length}. The rest are in the full Redirects screen.`})),o}async function Oe(e,t){let n={...t,status:`done`};try{await e.cron?.cancel(P)}catch{}return await e.kv.set(N,n),n}const ke=new Set("iIl.,:;|!'`jtfr ()[]".split(``)),Ae=new Set(`mwMWQO@%`.split(``));function je(e){let t=0;for(let n of e)ke.has(n)?t+=5:Ae.has(n)?t+=15:n>=`A`&&n<=`Z`?t+=12:t+=9.5;return Math.round(t)}function Me(e){if(je(e)<=580)return e;let t=e;for(;t.length>1&&je(`${t}…`)>580;)t=t.slice(0,-1);return`${t.trimEnd()}…`}function Ne(e){return e.toLowerCase().replace(/\s+/g,` `).trim()}const Pe=new Set([`home`,`homepage`,`home page`,`index`,`untitled`,`welcome`]),Fe=new Set([`_type`,`_key`,`id`,`slug`,`url`,`href`,`src`,`style`,`icon`]);function Ie(e){let t={textLength:0,imagesWithoutAlt:0,hasImage:!1},n=(e,r,i)=>{if(i>12)return;if(typeof e==`string`){!Fe.has(r)&&!/^(https?:)?\//.test(e)&&(t.textLength+=e.trim().length);return}if(Array.isArray(e)){for(let t of e)n(t,r,i+1);return}if(typeof e!=`object`||!e)return;let a=e;(a._type===`image`||typeof a.src==`string`&&`alt`in a)&&(t.hasImage=!0,(typeof a.alt!=`string`||a.alt.trim()===``)&&(t.imagesWithoutAlt+=1));for(let[e,t]of Object.entries(a))n(t,e,i+1)};for(let[t,r]of Object.entries(e))t!==`json_ld`&&n(r,t,0);return t}const I={id:`no-social-image`,level:`look`,message:`This page has no picture for shared links, unless your site has a default sharing image.`,fix:`Add an image in the SEO panel. If your theme adds a picture itself, this clears once the page has been viewed.`};function Le(e){return typeof e.excerpt==`string`?e.excerpt.trim():``}function Re(e){let t=[],{seo:n}=e,r=Ie(e.data),i=n?.title?.trim()||e.title.trim();if(i===``&&t.push({id:`no-title`,level:`fix`,message:`This page has no title.`,fix:`Give the page a title.`}),Pe.has(i.toLowerCase())&&t.push({id:`generic-title`,level:`fix`,message:`The title is just "${i}", which is what Google shows as the headline.`,fix:`Put your name and what you do in SEO Title, in the page's SEO panel.`}),n&&!n.description?.trim()&&Le(e.data)===``&&t.push({id:`no-description`,level:`fix`,message:`No search description, so Google writes its own summary of this page.`,fix:`Fill in Meta Description in the page's SEO panel.`}),e.duplicateTitle&&i!==``&&t.push({id:`duplicate-title`,level:`fix`,message:`Another page has the same title, so Google cannot tell them apart.`,fix:`Give this page its own title in the SEO panel.`}),n?.canonical&&e.slug){let r=a(n.canonical,`${e.siteUrl.replace(/\/+$/,``)}/`),i=r?.pathname.replace(/\/+$/,``).split(`/`).pop(),o=[`home`,`index`].includes(e.slug)&&(i??``)===``;r&&i!==e.slug&&!o&&t.push({id:`canonical-elsewhere`,level:`fix`,message:`This page tells Google to show a different page instead (canonical).`,fix:`Clear the canonical address in the SEO panel unless that is intended.`})}if(i!==``&&je(i)>580&&t.push({id:`title-long`,level:`look`,message:`The end of this title may be cut off in Google.`,fix:`Shorten the title, or put the important words first.`}),n&&!n.image&&!r.hasImage&&t.push(I),e.isArticle&&e.authorCount===0&&t.push({id:`no-author`,level:`look`,message:`This article has no author.`,fix:`Add a byline to the article.`}),r.imagesWithoutAlt>0){let e=r.imagesWithoutAlt;t.push({id:`images-without-alt`,level:`look`,message:`${e} ${e===1?`picture has`:`pictures have`} no description for screen readers.`,fix:`Add alt text to each picture.`})}return r.textLength<80&&t.push({id:`nearly-empty`,level:`look`,message:`This page has almost no text.`,fix:`Add some content, or hide the page from search.`}),t}function ze(e,t){return t?`hidden`:e.some(e=>e.level===`fix`)?`fix`:e.length>0?`look`:`ready`}const Be=/^[0-9A-HJKMNP-TV-Z]{26}(\.[A-Za-z0-9]+)?$/i;function Ve(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function He(e){return typeof e==`number`&&Number.isFinite(e)&&e>=1?Math.round(e):void 0}function Ue(e){if(!e.startsWith(`/_emdash/api/media/file/`))return;let t=e.slice(24);try{t=decodeURIComponent(t)}catch{return}return t!==``&&!t.includes(`/`)?t:void 0}function We(e,t){let n=a(e,`${t.replace(/\/+$/,``)}/`);if(!n||!/^https?:$/.test(n.protocol)||n.search!==``)return[];let{pathname:r}=n;if(r===`/_image`||r.includes(`/cdn-cgi/image/`))return[];let i=Ue(r);if(i)return[`file:${i}`];let o=[`url:${n.origin}${r}`],s=r.slice(1);return Be.test(s)&&o.push(`file:${s}`),o}function Ge(e){if(typeof e!=`string`||e===``)return[];if(e.startsWith(`/`)){let t=e.includes(`?`)?void 0:Ue(e);return t?[`file:${t}`]:[]}return/^https?:\/\//i.test(e)?We(e,`http://localhost`).filter(e=>e.startsWith(`url:`)):[]}function Ke(e){return typeof e==`string`&&e!==``&&!e.includes(`/`)?[`file:${e}`]:[]}function qe(e){let t=He(e.width),n=He(e.height);if(t===void 0||n===void 0||typeof e.mimeType==`string`&&!e.mimeType.startsWith(`image/`))return;let r=[];if(e._type===`image`&&Ve(e.asset))r.push(...Ke(e.asset._ref),...Ge(e.asset.url));else if(typeof e.id==`string`){if(e.provider===void 0||e.provider===`local`){let t=Ve(e.meta)?e.meta:{};r.push(...Ke(t.storageKey),...Ke(e.id))}r.push(...Ge(e.src))}let i=[...new Set(r)];return i.length>0?{keys:i,width:t,height:n}:void 0}function Je(e){let t=[],n=new Set,r=(e,i)=>{if(t.length>=10||i>12)return;if(Array.isArray(e)){for(let t of e)r(t,i+1);return}if(!Ve(e))return;let a=qe(e);if(a&&!a.keys.some(e=>n.has(e))){for(let e of a.keys)n.add(e);t.push(a)}for(let t of Object.values(e))r(t,i+1)};for(let[t,n]of Object.entries(e))t!==`json_ld`&&r(n,0);return t.slice(0,10)}function Ye(e,t,n){if(!e||e.length===0)return;let r=We(t,n);if(r.length===0)return;let i=e.find(e=>e.keys.some(e=>r.includes(e)));return i?{width:i.width,height:i.height}:void 0}const Xe=16*1024;function L(e,t){return`${e}:${t}`}function R(e){return e.storage.entries}function Ze(e){return typeof e==`object`&&!!e&&!Array.isArray(e)&&`@type`in e}function Qe(e){let t=e;if(typeof e==`string`){if(e.trim()===``||e.length>Xe)return[];try{t=JSON.parse(e)}catch{return[]}}let n=(Array.isArray(t)?t:[t]).filter(Ze);return JSON.stringify(n).length>Xe?[]:n}function $e(e){let{title:t,name:n}=e.data;return typeof t==`string`?t:typeof n==`string`?n:``}function z(e){let{collection:t,entry:n,authors:r,previous:i,config:a,siteUrl:o}=e,s=a.collections[t],c=i?.type??s?.type??d(t),u=n.seo?.title?.trim()||$e(n),f=n.status===`published`,p=f&&e.hasPages,m=e.hasPages?Re({slug:n.slug,data:n.data,seo:n.seo,title:$e(n),siteUrl:o,isArticle:l.includes(c),authorCount:r.length,duplicateTitle:e.duplicateTitle}):[],h=m.some(e=>e.id===I.id),g=h&&i?.imageSeen===!0,_=g?m.filter(e=>e.id!==I.id):m,v=e.hasPages?Je(n.data):[];return{v:1,publishedAt:n.publishedAt,updatedAt:n.updatedAt,authors:r,...i?.type?{type:i.type}:{},custom:Qe(n.data[a.jsonLdField]),...v.length>0?{images:v}:{},needsImage:h,...g?{imageSeen:g}:{},collection:t,entryId:n.id,title:u,status:e.hasPages?f?ze(_,n.seo?.noIndex===!0||s?.noindex===!0):`draft`:`skip`,titleKey:p?Ne(u):``,issues:_,checkedAt:new Date().toISOString()}}function et(e,t){return e.status===`hidden`||e.status===`draft`||e.status===`skip`?e.status:ze(t,!1)}function tt(e,t){let n=e.issues??[],r=n.some(e=>e.id===I.id);if(!(e.needsImage??r)||e.imageSeen===!0===t&&r!==t)return null;let i=n.filter(e=>e.id!==I.id),a=t?i:[...i,I],{imageSeen:o,...s}=e;return{...s,needsImage:!0,...t?{imageSeen:!0}:{},issues:a,status:et(e,a)}}async function nt(e,t){return(await rt(e,[t]))[0]??[]}async function rt(e,t){let n=[...new Set(t.filter(e=>e!==``))];if(n.length===0)return t.map(()=>[]);let r=await R(e).query({where:{titleKey:n.length===1?n[0]??``:{in:n}},limit:10*n.length});return t.map(e=>e===``?[]:r.items.filter(t=>t.data.titleKey===e))}function B(e){return e.issues?.some(e=>e.id===`duplicate-title`)??!1}function it(e,t){let n=e.length>1;for(let{id:r,data:i}of e)B(i)!==n&&t.set(r,V(i,n))}function V(e,t){let n=(e.issues??[]).filter(e=>e.id!==`duplicate-title`),r=t?[...n,{id:`duplicate-title`,level:`fix`,message:`Another page has the same title, so Google cannot tell them apart.`,fix:`Give this page its own title in the SEO panel.`}]:n;return{...e,issues:r,status:et(e,r)}}async function at(e,t,n,r){return(await st(e,t,n,r))?.record??null}async function ot(e,t,n,r){let i=await e.content?.get(t,n);if(!i)return null;let a=R(e),o=L(t,n),[s,c,l]=await Promise.all([e.bylines?.getEntriesBylines(t,[n])??[],a.get(o),e.schema?.getCollection(t)]),u=z({collection:t,entry:i,authors:(s[0]?.bylines??[]).map(({byline:e})=>({name:e.displayName,...e.websiteUrl?{url:e.websiteUrl}:{}})),previous:c,config:r,siteUrl:e.site.url,duplicateTitle:!1,hasPages:l?l.routable&&!l.hidden:!0}),d=u.titleKey??``,f=c?.titleKey??``;return{entry:i,previous:c,record:V(u,d!==``&&d===f&&c!==null&&B(c)),newKey:d,oldKey:f}}async function st(e,t,n,r){let i=await ot(e,t,n,r);if(!i)return null;let{entry:a,record:o,newKey:s,oldKey:c}=i,l=R(e),u=L(t,n);await l.put(u,o);let[d=[],f=[]]=await rt(e,[s,c===s?``:c]),p=new Map,m=d,h=d.find(e=>e.id===u)?.data;if(s!==``&&h?.checkedAt!==o.checkedAt){let e=h??f.find(e=>e.id===u)?.data??null;e&&lt(e.updatedAt,o.updatedAt)&&(p.set(u,o),m=[...d.filter(e=>e.id!==u),{id:u,data:o}])}return it(m,p),it(f.filter(e=>e.id!==u),p),p.size>0&&await l.putMany([...p].map(([e,t])=>({id:e,data:t}))),{record:p.get(u)??o,entry:a}}async function ct(e,t,n,r,i){let a=await ot(e,t,n,r);if(!a)return null;let{entry:o,previous:s,newKey:c,oldKey:l}=a,u=i?i(a.record):a.record,d=L(t,n);return s===null||c===l?await R(e).put(d,u):i&&await R(e).put(d,i(s)),{record:u,entry:o}}function lt(e,t){if(!e||!t)return!1;let n=Date.parse(e),r=Date.parse(t);return Number.isFinite(n)&&Number.isFinite(r)&&n<r}async function ut(e,t,n){let r=R(e),i=L(t,n),a=await r.get(i);await r.delete(i);let o=new Map;it((await nt(e,a?.titleKey??``)).filter(e=>e.id!==i),o),o.size>0&&await r.putMany([...o].map(([e,t])=>({id:e,data:t})))}const H=`scan`,dt=`stats`,U=`seo-scan`,ft=[`ready`,`look`,`fix`,`hidden`];async function pt(e){return e.kv.get(H)}async function mt(e,t,n){let r={status:`running`,collections:t,scope:[...t],phase:`entries`,checked:0,tally:{checked:0,fix:0,hidden:0,rows:[],more:!1},startedAt:new Date().toISOString()};try{await e.cron?.schedule(U,{schedule:`* * * * *`})}catch(t){e.log.warn(`could not schedule background check`,{error:String(t)})}return yt(e,r,n)}async function ht(e,t,n){let r=t.collections[0];if(r===void 0){t.phase=`titles`;return}let i=new Date().toISOString(),a=e=>(e.checkedAt??``)>=t.startedAt,o=await e.content?.list(r,{limit:20,cursor:t.cursor,where:{status:`published`}}),s=o?.items??[];if(s.length>0){let c=R(e),l=s.map(e=>e.id),[u,d]=await Promise.all([e.bylines?.getEntriesBylines(r,l)??[],c.getMany(l.map(e=>L(r,e)))]),f=new Map(u.map(e=>[e.entryId,e.bylines])),p=new Set,m=s.map(t=>{let o=L(r,t.id),s=d.get(o)??null;return s&&(lt(t.updatedAt,s.updatedAt)&&a(s)||(s.checkedAt??``)>=i)?(p.add(o),{id:o,data:s}):{id:o,data:z({hasPages:!0,collection:r,entry:t,authors:(f.get(t.id)??[]).map(({byline:e})=>({name:e.displayName,...e.websiteUrl?{url:e.websiteUrl}:{}})),previous:s,config:n,siteUrl:e.site.url,duplicateTitle:s?B(s):!1})}});if(t.cursor===void 0&&t.checked===0&&!o?.hasMore&&t.collections.length===1){let e=new Map;for(let{data:t}of m)t.titleKey&&e.set(t.titleKey,(e.get(t.titleKey)??0)+1);for(let t of m){let n=(e.get(t.data.titleKey??``)??0)>1;p.has(t.id)&&B(t.data)!==n&&p.delete(t.id),t.data=V(t.data,n)}t.status=`done`,t.finishedAt=new Date().toISOString(),t.sweep={}}let h=m.filter(e=>!p.has(e.id));h.length>0&&await c.putMany(h);for(let{data:e}of m)vt(t.tally,e,1);t.checked+=s.length}o?.hasMore&&o.cursor?t.cursor=o.cursor:(t.cursor=void 0,t.collections=t.collections.slice(1),t.collections.length===0&&t.status!==`done`&&(t.phase=`titles`,t.sweep={}))}async function gt(e,t,n){let r=new Set(t.scope??[]),i=R(e),a=t.sweep?.cursor,o=new Set(t.sweep?.kept??[]),s=r.size>0&&e.content?await i.query({where:{status:{in:ft}},limit:100,cursor:a}):{items:[],hasMore:!1,cursor:void 0},c=s.items.filter(({id:e,data:n})=>!o.has(e)&&r.has(n.collection??``)&&(n.entryId??``)!==``&&(n.checkedAt??``)<t.startedAt).slice(0,1),l=await Promise.all(c.map(({data:t})=>e.content?.get(t.collection??``,t.entryId??``))),u=[],d=[];c.forEach(({id:t,data:r},i)=>{let a=l[i];a?a.status===`published`?o.add(t):d.push({id:t,data:z({hasPages:!0,collection:r.collection??``,entry:a,authors:r.authors,previous:r,config:n,siteUrl:e.site.url,duplicateTitle:!1})}):u.push(t)}),u.length>0&&await i.deleteMany(u),d.length>0&&await i.putMany(d);let f=o.size>0?{kept:[...o]}:{};c.length>0?t.sweep={...a?{cursor:a}:{},...f}:s.hasMore&&s.cursor?t.sweep={cursor:s.cursor,...f}:delete t.sweep}async function _t(e,t){let n=R(e),r=await n.query({orderBy:{titleKey:`asc`},limit:100,cursor:t.titleCursor}),i=r.items.filter(e=>(e.data.titleKey??``)!==``),a=new Map(i.map(e=>[e.id,e.data])),o=new Map,s=(e,t,n)=>{B(t)!==n&&o.set(e,n)},c=t.lastTitle;for(let e=0;e<i.length;e+=1){let t=i[e];if(!t)continue;let r=t.data.titleKey??``,o=i[e+1],l=c?.key===r,u=o?.data.titleKey===r;if(l&&c&&!c.flagged&&e===0){let e=await n.get(c.id);e&&(a.set(c.id,e),s(c.id,e,!0))}let d=l||u;s(t.id,t.data,d),c={key:r,id:t.id,flagged:d}}if(o.size>0){let e=new Set([...o.keys()].map(e=>a.get(e)?.titleKey??``)),r=[...a].filter(([,t])=>e.has(t.titleKey??``)).map(([e])=>e),i=await n.getMany(r);for(let t of r){let n=a.get(t),r=i.get(t);(!n||r?.checkedAt!==n.checkedAt||r?.titleKey!==n.titleKey)&&e.delete(n?.titleKey??``)}let s=[];for(let[n,r]of o){let i=a.get(n);if(!i||!e.has(i.titleKey??``))continue;let o=V(i,r);s.push({id:n,data:o}),vt(t.tally,i,-1),vt(t.tally,o,1)}s.length>0&&await n.putMany(s)}t.lastTitle=c,r.hasMore&&r.cursor?t.titleCursor=r.cursor:(t.status=`done`,t.finishedAt=new Date().toISOString(),t.titleCursor=void 0,t.lastTitle=void 0)}function W(e,t=`fix`){return{collection:e.collection??``,id:e.entryId??``,title:e.title||`(no title)`,issues:(e.issues??[]).filter(e=>e.level===t).map(e=>e.message)}}function vt(e,t,n){if(!(!t.status||t.status===`draft`||t.status===`skip`)){if(e.checked+=n,t.status===`hidden`){e.hidden+=n;let r=e.hiddenTitles??[],i=t.title||`(no title)`;e.hiddenTitles=n===1?[...r,i].slice(0,10):r.filter(e=>e!==i)}if(t.status===`look`){let r=e.look??[];e.look=n===1?[...r,W(t,`look`)].slice(0,10):r.filter(e=>e.id!==t.entryId)}t.status===`fix`&&(e.fix+=n,n===1?e.rows.length<10?e.rows.push(W(t)):e.more=!0:e.rows=e.rows.filter(e=>e.id!==t.entryId))}}async function yt(e,t,n){let r={...t,collections:[...t.collections],tally:{...t.tally,rows:[...t.tally.rows]}};if(r.status===`done`&&!r.sweep)return r;if(r.sweep)await gt(e,r,n);else if(r.phase===`entries`){for(let t=0;t<5&&r.phase===`entries`;t+=1){let t=r.checked;if(await ht(e,r,n),r.checked!==t||r.status===`done`)break}r.phase===`titles`&&r.checked===0&&(r.status=`done`,r.finishedAt=new Date().toISOString())}else await _t(e,r);if(r.status===`done`&&!r.sweep)try{await e.cron?.cancel(U)}catch{}return await e.kv.set(H,r),r}async function bt(e){let t=R(e),[n,r,i,a,o]=await Promise.all([t.count({status:{in:[`ready`,`look`,`fix`,`hidden`]}}),t.count({status:`fix`}),t.query({where:{status:`fix`},limit:10}),t.query({where:{status:`look`},limit:10}),t.query({where:{status:`hidden`},limit:10})]);return{checked:n,fix:r,hidden:o.hasMore?11:o.items.length,rows:i.items.map(({data:e})=>W(e)),more:i.hasMore,look:a.items.map(({data:e})=>W(e,`look`)),hiddenTitles:o.items.map(({data:e})=>e.title||`(no title)`)}}const xt=new Map;async function St(e,t,{fresh:n=!1,now:r=Date.now()}={}){let i=e.site?.url??``,a=xt.get(e.plugin.id);a||xt.set(e.plugin.id,a=new Map);let o=a.get(i);if(n||!o||r>=o.until){let e=t(),n={value:e,until:r+3e4};a.set(i,n),e.catch(()=>{a.get(i)===n&&a.delete(i)}),o=n}return structuredClone(await o.value)}function Ct(e){xt.delete(e)}const G={overview:0,redirects:1,identity:2,types:2},K=[{value:`FoodEstablishment`,label:`Restaurant, cafe or bakery`},{value:`Store`,label:`Shop`},{value:`MedicalBusiness`,label:`Health and medical`},{value:`Dentist`,label:`Dentist`},{value:`LegalService`,label:`Legal`},{value:`FinancialService`,label:`Financial`},{value:`HomeAndConstructionBusiness`,label:`Home services (plumber, electrician, builder)`},{value:`HealthAndBeautyBusiness`,label:`Beauty and hair`},{value:`SportsActivityLocation`,label:`Fitness`},{value:`LodgingBusiness`,label:`Hotel or lodging`},{value:`AutomotiveBusiness`,label:`Automotive`},{value:`ProfessionalService`,label:`Professional service`},{value:`LocalBusiness`,label:`Other`}],wt=[``,`$`,`$$`,`$$$`,`$$$$`],Tt=[{id:`facebook`,label:`Facebook`,hosts:[`www.facebook.com`,`facebook.com`,`fb.com`]},{id:`instagram`,label:`Instagram`,hosts:[`www.instagram.com`,`instagram.com`]},{id:`linkedin`,label:`LinkedIn`,hosts:[`www.linkedin.com`,`linkedin.com`]},{id:`x`,label:`X (Twitter)`,hosts:[`x.com`,`www.x.com`,`twitter.com`,`www.twitter.com`]},{id:`youtube`,label:`YouTube`,hosts:[`www.youtube.com`,`youtube.com`]}];function Et(e){let t={},n=[];for(let r of e){let e=a(r)?.host??``,i=Tt.find(t=>t.hosts.includes(e));i&&!(i.id in t)?t[i.id]=r:n.push(r)}return{named:t,other:n}}const Dt={needed:`needed`,worth:`worth adding`,optional:`optional`};function q(e,t,n){let r=n.filter(e=>!!e);return r.length>0?`${e}: ${r.join(`, `)}`:`${e} (${Dt[t]}, not added yet)`}function Ot(e){return S.map(t=>{let n=E(t,e.openingHours).replace(`, `,` and `);return n===``?void 0:`${t.slice(0,3)} ${n}`}).filter(e=>e!==void 0)}function kt(e){let{site:t}=e.config,n=J(t),r=[];return n===`local`?((!t.address?.streetAddress||!t.address.addressLocality)&&r.push({what:`Address`,why:`Without it Google cannot treat you as a local business.`}),t.openingHours.length===0&&r.push({what:`Opening hours`,why:`Google cannot show when you are open.`}),t.telephone||r.push({what:`Phone number`,why:`Customers cannot call you from Google.`})):(n===`organization`&&!t.logo&&r.push({what:`Logo`,why:`Google shows it beside your name.`}),t.sameAs.length===0&&r.push({what:`Profiles elsewhere`,why:`They help Google connect your other pages to this site.`})),r}function J(e){return e.type===`Person`?`person`:e.type===`Organization`?`organization`:`local`}function At(e,t){let n=a(e);if(!(!n||n.protocol!==`https:`&&n.protocol!==`http:`))return`${n.origin}${t}`}function jt(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Y(e,t,n=``){let r=e[t];return typeof r==`string`?r.trim():n}function Mt(e,t,n){let r=e[t];return typeof r==`boolean`?r:n}function Nt(e){return e.split(/\r?\n/).map(e=>e.trim()).filter(e=>e!==``)}async function Pt(e){let[t,n,i]=await Promise.all([e.kv.list(),e.schema?.listCollections()??[],ye(e)]),a=new Map(t.map(e=>[e.key,e.value])),s=a.get(N);return{config:x(await r(e,a,o)),collections:n.filter(e=>e.routable&&!e.hidden),redirects:s&&Array.isArray(s.rows)?s:null,redirectRows:i.rows,moreRedirects:i.more,scan:a.get(H)??null,stats:a.get(dt)??null}}async function Ft(e,t){let n=await bt(e);return JSON.stringify(n)!==JSON.stringify(t.stats)&&await e.kv.set(dt,n),{...t,stats:n}}function It(e){return e.stats??(e.scan?.status===`done`?e.scan.tally:null)}function Lt(e){let{scan:t}=e,n=It(e);if(!t||t.status===`running`)return[{type:`banner`,title:`Checking your pages`,description:t?`${t.checked} checked so far. The rest are checked in the background.`:`This starts by itself and takes about a minute.`}];if(!n)return[];let r=n.checked-n.fix-n.hidden;return[{type:`stats`,items:[{label:`Pages checked`,value:n.checked},{label:`Nothing to fix`,value:r},{label:`To fix`,value:n.fix}]}]}function Rt(e){let{scan:t}=e,n=It(e);if(!n||!t||t.status===`running`)return[];let r=[];n.checked===0?r.push({type:`empty`,title:`No published pages yet`,size:`sm`}):n.fix===0?r.push({type:`empty`,title:`No pages to fix. Nice work.`,size:`sm`}):(r.push({type:`header`,text:`To fix`},{type:`table`,page_action_id:`fix_table`,columns:[{key:`page`,label:`Page`},{key:`missing`,label:`What's missing`},{key:`open`,label:``,format:`element`}],rows:n.rows.map(e=>({page:e.title,missing:e.issues.join(` `),open:{type:`link`,label:`Open page`,target:{kind:`content`,collection:e.collection,id:e.id}}}))}),n.more&&r.push({type:`context`,text:`Showing the first 10. Fix these and the next ones appear.`}));let i=n.look??[];if(i.length>0&&r.push({type:`accordion`,label:`Worth a look (${i.length}${i.length===10?`+`:``})`,blocks:[{type:`context`,text:`Nothing is wrong with these pages. These are improvements for when you have time.`},{type:`table`,page_action_id:`look_table`,columns:[{key:`page`,label:`Page`},{key:`advice`,label:`Could be better`},{key:`open`,label:``,format:`element`}],rows:i.map(e=>({page:e.title,advice:e.issues.join(` `),open:{type:`link`,label:`Open page`,target:{kind:`content`,collection:e.collection,id:e.id}}}))}]}),n.hidden>0){let e=(n.hiddenTitles??[]).join(`, `);r.push({type:`context`,text:`${n.hidden>10?`More than 10`:n.hidden} ${n.hidden===1?`page is`:`pages are`} hidden from Google and not counted${e?`: ${e}`:``}. To show one again, open it and switch off "Hide from search engines" in its SEO panel.`})}if(t.finishedAt){let e=new Date(t.finishedAt).toLocaleDateString(`en-GB`,{day:`numeric`,month:`short`});r.push({type:`context`,text:`Each page is checked again when it is saved. Last full check: ${e}.`})}return r}function zt(e){if(!e.scan)return[];let t=e.scan.status===`running`;return[{type:`actions`,elements:[{type:`button`,action_id:t?`check_more`:`check_pages`,label:t?`Update`:`Check all pages again`}]}]}function Bt(e){return e.collections.filter(t=>{let n=e.config.collections[t.slug];return n?.noindex===!0&&n.hiddenOk!==!0})}function Vt(e,t){let n=Bt(e);if(n.length===0)return[];let r=[{type:`banner`,variant:`alert`,title:`Some pages are hidden from Google`,description:`Every page in ${n.map(e=>e.label).join(`, `)} is hidden from search engines. If that is a leftover from building the site, switch it back on in the Settings tab, under Content types.`}];return t&&r.push({type:`actions`,elements:[{type:`button`,action_id:`hidden_ok`,label:`This is intended`}]}),r}function Ht(e,t){let{steps:n,site:r}=e.config;return n.identity?[]:[{type:`banner`,title:`Tell Google who this site is for`,description:`Google can then show your name and details beside your pages. It takes a minute.`},{type:`accordion`,label:`Set this up (1 minute)`,default_open:!0,blocks:[{type:`form`,block_id:`setup_identity`,fields:[{type:`radio`,action_id:`kind`,label:`This site is for`,options:[{label:`A local business customers visit (shop, cafe, clinic)`,value:`local`},{label:`A company or organisation`,value:`organization`},{label:`A person`,value:`person`}]},{type:`select`,action_id:`business`,label:`Kind of business`,options:[{label:`Choose the closest`,value:``},...K],initial_value:``,condition:{field:`kind`,eq:`local`}},{type:`text_input`,action_id:`name`,label:`Name`,placeholder:`Your name or your business's name`,initial_value:r.name??t},{type:`text_input`,action_id:`street`,label:`Street address`,initial_value:r.address?.streetAddress??``,condition:{field:`kind`,eq:`local`}},{type:`text_input`,action_id:`town`,label:`Town or city`,initial_value:r.address?.addressLocality??``,condition:{field:`kind`,eq:`local`}}],submit:{label:`Save`,action_id:`setup_identity`}}]}]}function Ut(e,t){let{site:n}=e.config,r=J(n),i=K.find(e=>e.value===n.type)?.label,a=r===`person`?`a person`:r===`local`?(i??`a local business`).toLowerCase():`a company`,o=[n.address?.streetAddress,n.address?.addressLocality].filter(Boolean).join(`, `),s=Ot(n);return[`Google is told: ${n.name??t}, ${a}`,o?`, ${o}`:``,s.length>0?`, open ${s.join(`, `)}`:``,`. Change this in the Settings tab.`].join(``)}function Wt(e,t){if(!e.config.steps.identity)return[];let n=kt(e);return n.length===0?[{type:`context`,text:Ut(e,t)}]:[{type:`header`,text:`Your details`},{type:`context`,text:`${n.length} ${n.length===1?`thing`:`things`} Google needs from you. Add ${n.length===1?`it`:`them`} in the Settings tab.`},{type:`table`,page_action_id:`details_table`,columns:[{key:`what`,label:`Missing`},{key:`why`,label:`Why it matters`}],rows:n}]}function Gt(e,t){return[...Lt(e),...Vt(e,!0),...Ht(e,t),...Wt(e,t),...Rt(e),...zt(e)]}function Kt(e){let t=It(e),n={type:`actions`,elements:[{type:`link`,label:`Open SEO`,target:{kind:`plugin-page`,path:`/overview`}}]};if(!t||t.checked===0){let{steps:t}=e.config;return{blocks:[{type:`section`,text:t.identity?`Your pages are being checked.`:`One thing left: tell Google who this site is for.`},n]}}return{blocks:[{type:`stats`,items:[t.fix===0?{label:`SEO`,value:`All pages ready`}:{label:t.fix===1?`Page to fix`:`Pages to fix`,value:t.fix}]},n]}}function qt(e,t){let{site:n}=e.config,r=J(n),i=r===`local`,o=e.config.steps.identity,s=Et(n.sameAs),c=[{type:`context`,text:`Google reads these details from your site and can show them beside your pages. Sections marked needed are followed up on the Overview; the rest are up to you.`},{type:`form`,block_id:`identity`,fields:[{type:`radio`,action_id:`kind`,label:`This site is for`,options:[{label:`A local business customers visit (shop, cafe, clinic)`,value:`local`},{label:`A company or organisation`,value:`organization`},{label:`A person`,value:`person`}],...o?{initial_value:r}:{}},{type:`select`,action_id:`business`,label:`Kind of business`,options:[{label:`Choose the closest`,value:``},...K],initial_value:i?n.type:``,condition:{field:`kind`,eq:`local`}},{type:`text_input`,action_id:`name`,label:`Name`,placeholder:`Your name or your business's name`,initial_value:n.name??t},{type:`text_input`,action_id:`description`,label:`What you do, in one or two sentences`,multiline:!0,initial_value:n.description??``}],submit:{label:`Save`,action_id:`save_identity`}},{type:`context`,text:`Each section below is saved with its own Save button.`}],l={type:`accordion`,label:q(`Address and phone`,i?`needed`:`optional`,[n.address?.streetAddress,n.address?.addressLocality,n.telephone,n.email]),default_open:i&&kt(e).some(e=>e.what!==`Opening hours`),blocks:[{type:`context`,text:i?`Google needs an address before it treats you as a local business. Use the same details as your Google Business Profile, which is what puts you on the map.`:`Leave the address empty if you have no public one.`},{type:`form`,block_id:`contact`,fields:[{type:`text_input`,action_id:`street`,label:`Street address`,initial_value:n.address?.streetAddress??``},{type:`text_input`,action_id:`town`,label:`Town or city`,initial_value:n.address?.addressLocality??``},{type:`text_input`,action_id:`region`,label:`State or region`,initial_value:n.address?.addressRegion??``},{type:`text_input`,action_id:`postcode`,label:`Postcode`,initial_value:n.address?.postalCode??``},{type:`text_input`,action_id:`country`,label:`Country code (GB, US, IN, AU)`,initial_value:n.address?.addressCountry??``},{type:`text_input`,action_id:`telephone`,label:`Phone`,initial_value:n.telephone??``},{type:`text_input`,action_id:`email`,label:`Email`,initial_value:n.email??``},{type:`select`,action_id:`price_range`,label:`Price range`,options:wt.map(e=>({label:e===``?`Not shown`:e,value:e})),initial_value:n.priceRange??``},{type:`text_input`,action_id:`areas`,label:`Areas you serve, one per line (if you travel to customers)`,multiline:!0,initial_value:n.areaServed.join(`
3
- `)}],submit:{label:`Save address and phone`,action_id:`save_contact`}}]},u=i?{type:`accordion`,label:q(`Opening hours`,`needed`,Ot(n)),blocks:[{type:`context`,text:`Type the hours for each day, like 9:00-17:00. For a break in the day use two ranges: 12:00-14:30, 18:00-22:00. A day left empty is shown as closed.`},{type:`form`,block_id:`hours`,fields:S.map(e=>({type:`text_input`,action_id:`hours_${e.toLowerCase()}`,label:e,placeholder:`Closed`,initial_value:E(e,n.openingHours)})),submit:{label:`Save opening hours`,action_id:`save_hours`}}]}:{type:`accordion`,label:`Opening hours (only for a local business)`,blocks:[{type:`context`,text:`Opening hours are for a place customers visit. Choose "A local business customers visit" above and save, and this section opens up.`}]},d={type:`accordion`,label:q(`Logo`,r===`organization`?`needed`:`worth`,[n.logo?`added`:void 0]),blocks:[{type:`context`,text:`Google can show your logo beside your name. In Media, open your logo and use Copy URL, then paste it here. Use a square image at least 112 pixels wide.`},{type:`form`,block_id:`logo`,fields:[{type:`text_input`,action_id:`logo_url`,label:`Logo link`,placeholder:`https://example.com/logo.png`,initial_value:n.logo??``}],submit:{label:`Save logo`,action_id:`save_logo`}}]},f={type:`accordion`,label:q(`Your profiles elsewhere`,i?`worth`:`needed`,n.sameAs.map(e=>a(e)?.host.replace(/^www\./,``))),blocks:[{type:`context`,text:`Links to your pages on other sites help Google connect them to you. Paste the full address of each profile you have.`},{type:`form`,block_id:`profiles`,fields:[...Tt.map(e=>({type:`text_input`,action_id:`profile_${e.id}`,label:e.label,placeholder:`https://${e.hosts[0]}/yourname`,initial_value:s.named[e.id]??``})),{type:`text_input`,action_id:`profiles`,label:`Other profiles, one per line`,multiline:!0,initial_value:s.other.join(`
4
- `)}],submit:{label:`Save profiles`,action_id:`save_profiles`}}]},p={type:`accordion`,label:q(`More about you`,`optional`,[n.alternateName,n.legalName,n.foundingDate?`founded ${n.foundingDate}`:void 0]),blocks:[{type:`context`,text:`Nothing here is needed. Skip anything that does not apply.`},{type:`form`,block_id:`company`,fields:[{type:`text_input`,action_id:`alternate_name`,label:`Also known as (a short name or abbreviation)`,initial_value:n.alternateName??``},{type:`text_input`,action_id:`legal_name`,label:`Registered legal name`,initial_value:n.legalName??``},{type:`text_input`,action_id:`founded`,label:`Founded (year, or YYYY-MM-DD)`,placeholder:`2012`,initial_value:n.foundingDate??``}],submit:{label:`Save`,action_id:`save_company`}}]},m=i?[l,u,d,f,p]:r===`organization`?[d,f,l,p,u]:[f,d,l,p,u];return c.push(...m),c}function Jt(e,t){return e.config.collections[t]?.type??d(t)}function Yt(e,t){let{config:n}=e,r=e.collections.find(e=>e.slug===t),i=[{type:`context`,text:`Google is told what each kind of content is, so it can show it properly: a blog post with its author and date, a service as a service. These were worked out from the names and are usually right. Every kind of content that has its own pages is listed here; content that only appears inside other pages, such as testimonials, is not. To change a single page (an About or Contact page, say), open it and use Change page type in its Google preview panel. Picking from a menu saves straight away.`}];return r&&i.push({type:`banner`,variant:`alert`,title:`Hide every page in ${r.label} from Google?`,description:`People will no longer find these pages through search engines. You can switch this back at any time.`},{type:`actions`,elements:[{type:`button`,action_id:`confirm_hide`,label:`Yes, hide them`,style:`danger`,value:r.slug},{type:`button`,action_id:`cancel_hide`,label:`Cancel`}]}),i.push({type:`table`,page_action_id:`types_table`,empty_text:`This site has no content types with pages yet.`,columns:[{key:`name`,label:`Content type`},{key:`shown`,label:``,format:`element`},{key:`visible`,label:``,format:`element`}],rows:e.collections.map(t=>{let r=n.collections[t.slug]?.noindex===!0;return{name:t.label,shown:{type:`menu`,action_id:`set_type`,label:`Google treats these as: ${O(Jt(e,t.slug))}`,items:D.map(({value:e,label:n})=>({label:n,value:`${t.slug}|${e}`}))},visible:{type:`menu`,action_id:`set_visible`,label:r?`Hidden from Google`:`Can appear on Google`,items:[{label:`Can appear on Google`,value:`${t.slug}|show`},{label:`Hide from Google and other search engines`,value:`${t.slug}|hide`}]}}})},{type:`form`,block_id:`breadcrumbs`,fields:[{type:`toggle`,action_id:`breadcrumbs`,label:`Show the path to each page on Google (Home > Services > Cakes)`,description:`Leave on unless your theme already does this.`,initial_value:n.breadcrumbs}],submit:{label:`Save`,action_id:`save_advanced`}}),[{type:`accordion`,label:`Content types (usually right as they are): ${e.collections.map(t=>{let r=n.collections[t.slug]?.noindex===!0;return`${t.label} as ${O(Jt(e,t.slug))}${r?`, hidden`:``}`}).join(`; `)}`,default_open:r!==void 0,blocks:i}]}function X(e,t,n,r){let i=[{type:`header`,text:`SEO`},{type:`context`,text:`How your site appears on Google and other search engines.`}];return n?{blocks:[...i,{type:`tab`,default_tab:r.tab,panels:[{label:`Overview`,blocks:Gt(e,t.site.name)},{label:`Redirects`,blocks:De({state:e.redirects,rows:e.redirectRows,more:e.moreRedirects,editing:r.editing,panel:r.panel,problem:r.problem,exported:r.exported,managerUrl:At(t.site.url,`/_emdash/admin/redirects`)})},{label:`Settings`,blocks:[...qt(e,t.site.name),...Yt(e,r.pendingHide)]}]}],...r.toast?{toast:r.toast}:{}}:{blocks:[...i,...Lt(e),...Vt(e,!1),...Rt(e),{type:`context`,text:`Business details, opening hours and redirects are set by your site admin. Ask them if something there is wrong.`}]}}function Xt(e,t){if(e===``)return{};let n=a(e,`${t.replace(/\/+$/,``)}/`);return!n||n.protocol!==`https:`&&n.protocol!==`http:`?{error:`Couldn't save. The logo link must be a web address starting with https://`}:n.pathname.includes(`/_emdash/api/media/asset/`)?{error:`That link only works when signed in. In Media, use Copy URL on the image instead.`}:{logo:n.href}}function Zt(e,t){let n={tab:G.identity},{site:r}=e.config,i=Y(t,`kind`,e.config.steps.identity?J(r):``);if(i!==`local`&&i!==`organization`&&i!==`person`)return{error:`Choose who this site is for.`,state:n};let a=Y(t,`business`,J(r)===`local`?r.type:``),o=K.find(e=>e.value===a)?.value;if(i===`local`&&!o)return{error:`Choose the kind of business.`,state:n};let s=i===`person`?`Person`:i===`local`?o??`LocalBusiness`:`Organization`,c=Y(t,`name`,r.name??``);return{config:{...e.config,site:{...r,type:s,name:c===``?void 0:c,description:Y(t,`description`,r.description??``)||void 0,openingHours:i===`local`?r.openingHours:[]},steps:{...e.config.steps,identity:!0}},state:n}}function Qt(e,t,n){let r={tab:G.identity},i=Xt(Y(t,`logo_url`,e.config.site.logo??``),n);return`error`in i?{error:i.error,state:r}:{config:{...e.config,site:{...e.config.site,logo:i.logo}},state:r}}function $t(e,t){let{site:n}=e.config,r={streetAddress:Y(t,`street`)||void 0,addressLocality:Y(t,`town`)||void 0,addressRegion:Y(t,`region`)||void 0,postalCode:Y(t,`postcode`)||void 0,addressCountry:Y(t,`country`).toUpperCase()||void 0},i=Y(t,`price_range`);return{config:{...e.config,site:{...n,email:Y(t,`email`)||void 0,telephone:Y(t,`telephone`)||void 0,address:Object.values(r).some(e=>e!==void 0)?r:void 0,priceRange:wt.includes(i)&&i!==``?i:void 0,areaServed:Nt(Y(t,`areas`,n.areaServed.join(`
5
- `)))}},state:{tab:G.identity}}}function en(e,t){let n={tab:G.identity},r=[];for(let i of S){let a=T(i,Y(t,`hours_${i.toLowerCase()}`,E(i,e.config.site.openingHours)));if(!a)return{error:`Couldn't read the hours for ${i}. Write them like 9:00-17:00, with at most two ranges.`,state:n};r.push(...a)}return{config:{...e.config,site:{...e.config.site,openingHours:r}},state:n}}function tn(e,t){let n={tab:G.identity},r=Et(e.config.site.sameAs),i=[...Tt.map(e=>Y(t,`profile_${e.id}`,r.named[e.id]??``)),...Nt(Y(t,`profiles`,r.other.join(`
6
- `)))].filter(e=>e!==``),o=[];for(let e of i){let t=a(/^https?:\/\//i.test(e)?e:`https://${e}`);if(!t||!t.host.includes(`.`))return{error:`"${e}" is not a web address. Paste the full link to the profile.`,state:n};o.push(t.href)}return{config:{...e.config,site:{...e.config.site,sameAs:o}},state:n}}function nn(e,t){let n={tab:G.identity},{site:r}=e.config,i=Y(t,`founded`,r.foundingDate??``);return i!==``&&!/^\d{4}(-\d{2}-\d{2})?$/.test(i)?{error:`Write the founding date as a year, like 2012, or as 2012-05-01.`,state:n}:{config:{...e.config,site:{...r,alternateName:Y(t,`alternate_name`,r.alternateName??``)||void 0,legalName:Y(t,`legal_name`,r.legalName??``)||void 0,foundingDate:i||void 0}},state:n}}function rn(e,t){let{robots:n}=e.config,r=Y(t,`snippet`,n.nosnippet?`none`:`full`),i=Y(t,`image_preview`,n.maxImagePreview);return{config:{...e.config,breadcrumbs:Mt(t,`breadcrumbs`,e.config.breadcrumbs),robots:{...n,nosnippet:r===`none`,maxSnippet:r===`short`?160:-1,maxImagePreview:i===`none`||i===`standard`||i===`large`?i:n.maxImagePreview}},state:{tab:G.types}}}function an(e,t,n){let r={tab:G.types},[i,a]=typeof t==`string`?t.split(`|`):[];if(!i||!e.collections.some(e=>e.slug===i))return{error:`That content type no longer exists.`,state:r};let o=e.config.collections[i]??{},s=n===`type`?D.find(e=>e.value===a)?.value:o.type??Jt(e,i);if(!s)return{error:`Choose one of the listed types.`,state:r};let c=n===`visible`?a===`hide`:o.noindex===!0;return{config:{...e.config,collections:{...e.config.collections,[i]:{type:s,noindex:c}},steps:{...e.config.steps,types:!0}},state:r}}async function on(e,t){if(t.scan?.status===`done`)return Ft(e,t);let n=t.scan?await yt(e,t.scan,t.config):await mt(e,t.collections.map(e=>e.slug),t.config);return{...t,scan:n,stats:n.status===`done`?n.tally:null}}async function sn(e,t,n){let r=await Pt(n),a=(t??0)>=50,s=jt(e)?e:{},c=typeof s.action_id==`string`?s.action_id:``;if(s.type===`page_load`&&s.page===`widget:seo`)return Kt(await on(n,r));if(!a||s.type===`page_load`||c===``)return X(await on(n,r),n,a,{tab:G.overview});let l=async(e,t=`Saved. Google will pick this up next time it visits.`)=>{if(`error`in e)return X(r,n,a,{...e.state,toast:{type:`error`,message:e.error}});let s=x(e.config);return await i(n,o,s),Ct(n.plugin.id),X({...r,config:s},n,a,{...e.state,toast:{type:`success`,message:t}})},u=async e=>{try{let{rows:t,more:r}=await ye(n);return{...e,redirectRows:t,moreRedirects:r}}catch{return e}},d=async(e,t,i)=>e?X(r,n,a,{tab:G.redirects,editing:i,...i?{}:{panel:`add`},problem:e}):X(await u(r),n,a,{tab:G.redirects,toast:{type:`success`,message:t}});if(s.type===`block_action`){if(c===`check_pages`||c===`check_more`){let e=await on(n,c===`check_pages`?{...r,scan:null}:r);return X(e,n,a,{tab:G.overview,toast:e.scan?.status===`done`?{type:`success`,message:`All pages checked.`}:{type:`info`,message:`${e.scan?.checked??0} pages checked so far. The rest follow in the background.`}})}if(c===`set_type`)return l(an(r,s.value,`type`));if(c===`set_visible`){let[e,t]=typeof s.value==`string`?s.value.split(`|`):[];return t===`hide`?X(r,n,a,{tab:G.types,pendingHide:e}):l(an(r,s.value,`visible`),`These pages can appear on Google again.`)}if(c===`confirm_hide`&&typeof s.value==`string`)return l(an(r,`${s.value}|hide`,`visible`),`These pages are now hidden from Google and other search engines.`);if(c===`cancel_hide`)return X(r,n,a,{tab:G.types});if(c===`hidden_ok`){let e={...r.config.collections};for(let t of Bt(r))e[t.slug]={...e[t.slug],hiddenOk:!0};return l({config:{...r.config,collections:e},state:{tab:G.overview}},`Noted. These pages stay hidden from search.`)}if(c===`edit_redirect`){let e=r.redirectRows.find(e=>e.id===s.value);return X(r,n,a,{tab:G.redirects,editing:e})}if(c===`open_add`)return X(r,n,a,{tab:G.redirects,panel:`add`});if(c===`open_import`)return X(r,n,a,{tab:G.redirects,panel:`import`});if(c===`close_panel`)return X(r,n,a,{tab:G.redirects});if(c===`remove_redirect`&&typeof s.value==`string`)return d(await we(n,s.value),`Redirect removed.`);if(c===`add_redirects`||c===`more_redirects`){if(!r.redirects||r.redirects.status===`done`)return X(r,n,a,{tab:G.redirects});let e=await pe(n,r.redirects);return X(await u({...r,redirects:e}),n,a,{tab:G.redirects,toast:e.status===`done`?{type:`success`,message:`${F(e.added,`redirect`)} added.`}:{type:`info`,message:`${e.added} added so far. The rest follow in the background.`}})}if(c===`stop_redirects`&&r.redirects?.status===`adding`){let e=await Oe(n,r.redirects);return X({...r,redirects:e},n,a,{tab:G.redirects,toast:{type:`info`,message:`Stopped. ${F(e.added,`redirect`)} added.`}})}if(c===`reset_redirects`)return r.redirects?.status!==`adding`&&await n.kv.delete(N),X({...r,redirects:r.redirects?.status===`adding`?r.redirects:null},n,a,{tab:G.redirects});if(c===`remove_redirects`&&r.redirects){let e=await me(n,r.redirects);return X(await u({...r,redirects:e===0?null:r.redirects}),n,a,{tab:G.redirects,toast:e===0?{type:`success`,message:`Removed. Your other redirects are untouched.`}:{type:`info`,message:`${e} still to remove. Press Remove again.`}})}if(c===`export_redirects`){let{text:e,count:t}=await he(n);return X(r,n,a,{tab:G.redirects,panel:`export`,exported:e,toast:{type:`info`,message:`${F(t,`redirect`)} listed below.`}})}return X(r,n,a,{tab:G.overview})}if(s.type!==`form_submit`)return X(r,n,a,{tab:G.overview});let f=jt(s.values)?s.values:{};if(c===`check_redirects`){let e=Y(f,`list`);if(e===``)return X(r,n,a,{tab:G.redirects,toast:{type:`error`,message:`Paste a list first.`}});let t=await de(n,e);return X({...r,redirects:t},n,a,{tab:G.redirects,toast:{type:`success`,message:`Checked. Nothing has been added yet.`}})}if(c===`add_redirect`){let e=Y(f,`source`),t=Y(f,`code`,`301`)===`410`?``:Y(f,`destination`);return d(await Se(n,e,t,Y(f,`code`,`301`)),t===``?`Done. ${e} now tells visitors the page was removed.`:`Redirect added. ${e} now goes to ${t}.`)}if(c.startsWith(`save_redirect:`)){let e=c.slice(14),t=r.redirectRows.find(t=>t.id===e);return d(await Ce(n,e,Y(f,`source`,t?.source??``),Y(f,`destination`,t?.destination??``),Y(f,`code`,String(t?.type??301))),`Redirect changed.`,t)}if(c===`setup_identity`){let e=Zt(r,f);if(`error`in e)return l(e);let t=Y(f,`street`),n=Y(f,`town`),{site:i}=e.config,a=t!==``||n!==``?{...i.address,...t?{streetAddress:t}:{},...n?{addressLocality:n}:{}}:i.address;return l({config:{...e.config,site:{...i,address:a}},state:{tab:G.overview}})}return c===`save_identity`?l(Zt(r,f)):c===`save_logo`?l(Qt(r,f,n.site.url)):c===`save_contact`?l($t(r,f)):c===`save_hours`?l(en(r,f)):c===`save_profiles`?l(tn(r,f)):c===`save_company`?l(nn(r,f)):c===`save_advanced`?l(rn(r,f)):X(r,n,a,{tab:G.overview})}function Z(e){return{"@id":e}}function Q(e){let t={};for(let[n,r]of Object.entries(e))r==null||r===``||Array.isArray(r)&&r.length===0||(t[n]=r);return t}function cn(e,t){return a(e,t)?.href??e}function ln(e,t){return(e.siteUrl||t||(a(e.url)?.origin??``)).replace(/\/+$/,``)}function un(e,t){if(e.canonical)return cn(e.canonical,`${t}/`);let n=a(e.url,`${t}/`);return n?(n.search=``,n.hash=``,n.href):e.url}function dn(e){return e.path===`/`||e.path===``}function fn(e){return l.includes(e)}function pn(e,t,n){let r=e.content?t.collections[e.content.collection]:void 0;if(n?.type)return n.type;if(r?.type)return r.type;let i=e.content?d(e.content.collection):`WebPage`;return i===`WebPage`?e.pageType===`article`?`BlogPosting`:`WebPage`:i}function mn(e,t,n,r){let i=`${n}/#logo`,a=e.type===`Person`,o=!a&&e.type!==`Organization`&&e.address===void 0?`Organization`:e.type,s=!a&&(e.address!==void 0||e.geo!==void 0||e.openingHours.length>0);return Q({"@type":o,"@id":r,name:t,alternateName:e.alternateName,legalName:a?void 0:e.legalName,description:e.description,url:`${n}/`,logo:e.logo&&!a?{"@type":`ImageObject`,"@id":i,url:e.logo,contentUrl:e.logo,caption:t}:void 0,image:e.logo?a?e.logo:Z(i):void 0,sameAs:e.sameAs,email:e.email,telephone:e.telephone,address:e.address?{"@type":`PostalAddress`,...e.address}:void 0,geo:e.geo&&!a?{"@type":`GeoCoordinates`,...e.geo}:void 0,openingHoursSpecification:s?e.openingHours.map(e=>({"@type":`OpeningHoursSpecification`,dayOfWeek:e.days,opens:e.opens,closes:e.closes})):void 0,priceRange:a?void 0:e.priceRange,areaServed:a?void 0:e.areaServed,foundingDate:a?void 0:e.foundingDate,vatID:a?void 0:e.vatID,taxID:a?void 0:e.taxID,numberOfEmployees:!a&&e.numberOfEmployees?{"@type":`QuantitativeValue`,value:e.numberOfEmployees}:void 0})}function hn(e){let t=e;try{t=decodeURIComponent(e)}catch{}let n=t.replace(/[-_]+/g,` `).trim();return n.charAt(0).toUpperCase()+n.slice(1)}function gn(e,t,n){let{page:r,config:i,site:a}=e;if(r.breadcrumbs)return r.breadcrumbs.map(e=>({name:e.name,url:cn(e.url,`${t}/`)}));if(!i.breadcrumbs||dn(r))return[];let o=r.path.split(`/`).filter(e=>e!==``);if(o.length===0)return[];let s=a.trailingSlash===`always`?`/`:``,c=[{name:`Home`,url:`${t}/`}],l=``;return o.forEach((e,r)=>{l+=`/${e}`;let i=r===o.length-1;c.push({name:i?n:hn(e),url:`${t}${l}${s}`})}),c}function _n(e,t){return e.length<=t?e:`${e.slice(0,t-1).trimEnd()}…`}function vn(e,t,n){let r;for(let{"@context":i,...a}of t){let t=typeof a[`@id`]==`string`?a[`@id`]:void 0;if(t!==void 0){if(n.has(t))continue;n.add(t),r??=t}e.push(a)}return r}function yn(e){let{page:t,config:n,record:r,site:i}=e,a=ln(t,i.url),o=un(t,a),s=t.locale??i.locale,c=n.site.name??t.siteName??i.name,l=t.pageTitle??t.title??c,u=n.site.type===`Person`,d=`${a}/#${u?`person`:`organization`}`,f=`${a}/#website`,p=o,m=`${o}#breadcrumb`,h=pn(t,n,r),g=fn(h),_=h===`Service`,v=h===`ProfilePage`,y=`${o}#service`,b=`${o}#person`,x=r?.publishedAt??t.articleMeta?.publishedTime??void 0,S=r?.updatedAt??t.articleMeta?.modifiedTime??void 0,C=t.image?cn(t.image,`${a}/`):void 0,w=C??``,T=gn(e,a,l),E=[mn(n.site,c,a,d),Q({"@type":`WebSite`,"@id":f,url:`${a}/`,name:c,alternateName:n.site.alternateName,description:n.site.description,publisher:Z(d),inLanguage:s})];if(e.noindex)return E;let D=Q({"@type":g||_?`WebPage`:h,"@id":p,url:o,name:l,description:t.description,isPartOf:Z(f),about:dn(t)?Z(d):void 0,primaryImageOfPage:C?Z(w):void 0,image:C?Z(w):void 0,breadcrumb:T.length>0?Z(m):void 0,mainEntity:_?Z(y):v?Z(b):void 0,datePublished:x,dateModified:S,inLanguage:s});if(E.push(D),g){let e=(r?.authors??[]).map(e=>Q({"@type":`Person`,name:e.name,url:e.url})),n=t.articleMeta?.author?[{"@type":`Person`,name:t.articleMeta.author}]:[Z(d)];E.push(Q({"@type":h,"@id":`${o}#article`,isPartOf:Z(p),mainEntityOfPage:Z(p),headline:_n(l,110),description:t.description,image:C?Z(w):void 0,datePublished:x,dateModified:S,author:e.length>0?e:n,publisher:Z(d),inLanguage:s}))}_&&E.push(Q({"@type":`Service`,"@id":y,name:l,description:t.description,url:o,image:C?Z(w):void 0,provider:Z(d),areaServed:n.site.areaServed})),v&&E.push(Q({"@type":`Person`,"@id":b,name:l,description:t.description,url:o,image:C?Z(w):void 0,worksFor:u?void 0:Z(d)})),C&&E.push({"@type":`ImageObject`,"@id":w,url:C,contentUrl:C,...Ye(r?.images,C,a)}),T.length>0&&E.push({"@type":`BreadcrumbList`,"@id":m,itemListElement:T.map((e,t)=>Q({"@type":`ListItem`,position:t+1,name:e.name,item:t===T.length-1?void 0:e.url}))});let O=new Set([`${a}/#logo`]);for(let e of E)typeof e[`@id`]==`string`&&O.add(e[`@id`]);let k=vn(E,r?.custom??[],O);return k!==void 0&&!g&&D.mainEntity===void 0&&(D.mainEntity=Z(k)),E}const bn=`default`;function xn(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Sn(e){let t=a(e);if(!t)return e;let n=t.pathname.split(`/`).filter(e=>e!==``);return[t.host,...n].join(` › `)}function Cn(e,t){return e.length<=t?e:`${e.slice(0,t-1).trimEnd()}…`}async function wn(e,t,n,r){if(!t)return{blocks:[]};let i=(n??0)>=50,o=xn(e)?e:{},c=await s(r),l=i&&o.type===`block_action`&&o.action_id===`set_type`,u=typeof o.value==`string`?o.value:bn,f=D.find(e=>e.value===u)?.value,[p,m]=await Promise.all([ct(r,t.collection,t.id,c,l?e=>{let{type:t,...n}=e;return f?{...n,type:f}:n}:void 0),r.content?.getPublicUrl?.(t.collection,t.id)]),h=p?.record,g=p?.entry,_=h&&l?{type:`success`,message:`Saved. Google will pick this up next time it visits.`}:void 0;if(!h||!g)return{blocks:[{type:`context`,text:`Save this page to see how it may look on Google.`}]};if(h.status===`skip`)return{blocks:[{type:`context`,text:`This kind of content has no page of its own, so Google only sees it inside other pages. There is nothing to set here.`}]};let v=c.site.name??r.site.name,y=r.site.url.replace(/\/+$/,``),b=[`home`,`index`,``].includes(g.slug??``)?`${y}/`:`${y}/${g.slug}`,x=[`home`,`index`].includes(g.slug??``)?`${y}/`:m??b,S=h.title||`(no title)`,C=g.seo?.description?.trim()??``,w=C===``&&Le(g.data)!==``,T=C||Le(g.data),E=c.collections[t.collection]?.type??d(t.collection),k=h.type??E,A=h.status===`hidden`,j=h.issues??[],M=[{type:`context`,text:v?`${v} · ${Sn(x)}`:Sn(x)},{type:`header`,text:Me(S)},{type:`section`,text:T===``?`No description yet. Google will pick some text from the page.`:Cn(T,160)},...w?[{type:`context`,text:`This description is the page's excerpt. To write a different one for search, fill in Meta Description in the SEO panel.`}]:[],{type:`context`,text:`What Google sees: the published page, with the SEO title and description as last saved. Google may rewrite them, and your site may add its name to the title.`},{type:`fields`,fields:[{label:`Shown to Google as`,value:O(k)},{label:`In search results`,value:h.status===`draft`?`Not published yet`:A?`Hidden from search`:`Yes`}]}];if(j.length===0)M.push({type:`context`,text:`Nothing to fix on this page.`});else{let e=[...j].sort((e,t)=>Number(t.level===`fix`)-Number(e.level===`fix`));for(let t of e.slice(0,3))M.push({type:`section`,text:`${t.level===`fix`?`To fix`:`Worth a look`}: ${t.message}`},{type:`context`,text:t.fix});e.length>3&&M.push({type:`context`,text:`And ${e.length-3} more.`})}if(i){let e=yn({page:{url:x,path:a(x)?.pathname??`/`,locale:g.locale,kind:`content`,pageType:`website`,title:S,pageTitle:S,description:T===``?null:T,canonical:g.seo?.canonical??null,image:null,content:{collection:t.collection,id:t.id,slug:g.slug}},config:c,record:h,noindex:A,site:r.site});M.push({type:`divider`},{type:`actions`,elements:[{type:`menu`,action_id:`set_type`,label:`Change page type`,items:[{label:`Same as other pages here (${O(E)})`,value:bn},...D.map(({value:e,label:t})=>({value:e,label:t}))]},.../^https?:\/\//.test(x)?[{type:`link`,label:`Test on Google`,target:{kind:`external`,url:`https://search.google.com/test/rich-results?url=${encodeURIComponent(x)}`}}]:[]]},{type:`accordion`,label:`View the markup`,blocks:[{type:`context`,text:`The schema markup (JSON-LD) this page sends to search engines.`},{type:`code`,language:`jsonc`,code:JSON.stringify({"@context":`https://schema.org`,"@graph":e},null,2)}]})}return{blocks:M,..._?{toast:_}:{}}}const Tn=/\s*,\s*/;function En(e,t,n){let r=(e??``).toLowerCase().split(Tn).map(e=>e.trim()).filter(e=>e!==``),i=t||r.includes(`noindex`)||r.includes(`none`),a=r.includes(`nofollow`)||r.includes(`none`),o=[i?`noindex`:`index`,a?`nofollow`:`follow`],s=new Set([`index`,`noindex`,`follow`,`nofollow`,`none`,`all`]),c=r.filter(e=>!s.has(e));if(!i){let e=e=>c.some(t=>t.startsWith(e));n.nosnippet||c.includes(`nosnippet`)?c.includes(`nosnippet`)||c.push(`nosnippet`):e(`max-snippet:`)||c.push(`max-snippet:${n.maxSnippet}`),e(`max-image-preview:`)||c.push(`max-image-preview:${n.maxImagePreview}`),e(`max-video-preview:`)||c.push(`max-video-preview:${n.maxVideoPreview}`)}return[...o,...new Set(c)].join(`, `)}function Dn(e){return e.startsWith(`noindex`)}function On(e){return/[?&]_preview=/.test(e)}async function $(e,t,n){let r=n?.id;if(typeof r==`string`)try{await at(e,t,r,await s(e))}catch(n){e.log.warn(`could not refresh entry record`,{collection:t,id:r,error:String(n)})}}const kn={hooks:{"page:metadata":async(e,t)=>{let{page:n}=e,r=n.content,[i,a]=await Promise.all([St(t,()=>s(t),{fresh:On(n.url)}),(async()=>{if(!r)return null;try{return{stored:await R(t).get(L(r.collection,r.id))}}catch(e){return{error:e}}})()]),o=null;if(r&&a){let{collection:e,id:i}=r;try{if(`error`in a)throw a.error;let{stored:r}=a,c=R(t);o=r??await at(t,e,i,await s(t));let l=!!(n.seo?.ogImage||n.image);if(r&&tt(r,l)){let t=await c.get(L(e,i)),n=t?tt(t,l):null;n&&await c.put(L(e,i),n),o=n??t??r}}catch(n){t.log.warn(`could not load entry record`,{collection:e,id:i,error:String(n)})}}let c=n.content?i.collections[n.content.collection]:void 0,l=En(n.seo?.robots,c?.noindex===!0,i.robots);return[{kind:`meta`,name:`robots`,content:l},{kind:`jsonld`,id:`primary`,graph:{"@context":`https://schema.org`,"@graph":yn({page:n,config:i,record:o,noindex:Dn(l),site:t.site})}}]},"content:afterSave":async(e,t)=>{await $(t,e.collection,e.content)},"content:afterPublish":async(e,t)=>{await $(t,e.collection,e.content)},"content:afterUnpublish":async(e,t)=>{await $(t,e.collection,e.content)},"content:afterRestore":async(e,t)=>{await $(t,e.collection,e.content)},"plugin:activate":async(e,t)=>{try{if(await pt(t))return;let e=await mt(t,(await t.schema?.listCollections()??[]).filter(e=>e.routable&&!e.hidden).map(e=>e.slug),await s(t));e.status===`done`&&e.checked===0&&await t.kv.delete(H)}catch(e){t.log.warn(`could not start the first page check`,{error:String(e)})}},cron:async(e,t)=>{if(e.name===U){let e=await pt(t);e?.status===`running`||e?.sweep?await yt(t,e,await s(t)):await t.cron?.cancel(U);return}if(e.name!==P)return;let n=await le(t);n?.status===`adding`?await pe(t,n):await t.cron?.cancel(P)},"content:afterDelete":async(e,t)=>{await ut(t,e.collection,e.id)}},routes:{admin:{permission:`plugins:read`,handler:async(e,t)=>sn(e.input,e.user?.role,t)},"editor/preview":{permission:`content:read`,handler:async(e,t)=>wn(e.input,e.ui?.entry,e.user?.role,t)},hello:{handler:async(e,t)=>(t.log.info(`hello route called`,{pluginId:t.plugin.id}),{greeting:`hello`,pluginId:t.plugin.id})}}};export{kn as default};
1
+ function e(e){return e!=null}async function t(e,t,n){try{await e.settings.set(t.name,n)}catch(n){e.log.warn(`could not copy the settings to their new place`,{key:t.name,error:String(n)})}}async function n(n,r,{copy:i=!0}={}){let a=await n.settings.get(r.name);if(e(a))return a;let o=await n.kv.get(r.legacy);return e(o)?(i&&await t(n,r,o),o):null}async function r(n,r,i,{copy:a=!0}={}){let o=r.get(`settings:${i.name}`);if(e(o))return o;let s=r.get(i.legacy);return e(s)?(a&&await t(n,i,s),s):null}function i(t,n){return!e(t.get(`settings:${n.name}`))&&e(t.get(n.legacy))}async function a(e,t,n){await e.settings.set(t.name,n);try{await e.kv.set(t.legacy,n)}catch(n){e.log.warn(`could not keep a copy of the settings for the earlier release`,{key:t.legacy,error:String(n)})}}function o(e,t){try{return new URL(e,t)}catch{return null}}const s={name:`config`,legacy:`config`};async function c(e){return S(await n(e,s,{copy:!1}))}const l=[`WebPage`,`AboutPage`,`ContactPage`,`CollectionPage`,`ProfilePage`],u=[`Article`,`BlogPosting`,`NewsArticle`],d=[`Service`];function f(e){let t=e.toLowerCase();return[`posts`,`post`,`blog`].includes(t)?`BlogPosting`:[`news`,`articles`,`article`].includes(t)?`Article`:[`services`,`service`].includes(t)?`Service`:[`team`,`people`,`staff`,`authors`].includes(t)?`ProfilePage`:`WebPage`}const p={site:{type:`Organization`,sameAs:[],openingHours:[],areaServed:[]},collections:{},robots:{maxSnippet:-1,maxImagePreview:`large`,maxVideoPreview:-1,nosnippet:!1},jsonLdField:`json_ld`,breadcrumbs:!0,steps:{identity:!1,types:!1},hideHelpLine:!1};function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e){if(typeof e!=`string`)return;let t=e.trim();return t===``?void 0:t}function g(e){let t=h(e),n=t?o(t)?.protocol:void 0;return n===`https:`||n===`http:`?t:void 0}function _(e,t){return typeof e==`number`&&Number.isInteger(e)&&e>=-1?e:t}function v(e){let t=[...l,...u,...d];return typeof e==`string`&&t.includes(e)?e:void 0}function y(e){if(!m(e))return;let t={};for(let n of[`streetAddress`,`addressLocality`,`addressRegion`,`postalCode`,`addressCountry`]){let r=h(e[n]);r&&(t[n]=r)}return Object.keys(t).length>0?t:void 0}function b(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(!m(n)||!Array.isArray(n.days))continue;let e=n.days.filter(e=>typeof e==`string`&&e!==``),r=h(n.opens),i=h(n.closes);e.length>0&&r&&i&&t.push({days:e,opens:r,closes:i})}return t}function x(e){if(!m(e))return{...p.site};let t=m(e.geo)?e.geo:void 0,n=t?.latitude,r=t?.longitude;return{type:h(e.type)??`Organization`,name:h(e.name),alternateName:h(e.alternateName),legalName:h(e.legalName),description:h(e.description),logo:g(e.logo),sameAs:Array.isArray(e.sameAs)?e.sameAs.map(g).filter(e=>e!==void 0):[],email:h(e.email),telephone:h(e.telephone),address:y(e.address),geo:typeof n==`number`&&typeof r==`number`?{latitude:n,longitude:r}:void 0,openingHours:b(e.openingHours),priceRange:h(e.priceRange),areaServed:Array.isArray(e.areaServed)?e.areaServed.map(h).filter(e=>e!==void 0):[],foundingDate:h(e.foundingDate),vatID:h(e.vatID),taxID:h(e.taxID),numberOfEmployees:typeof e.numberOfEmployees==`number`&&e.numberOfEmployees>0?Math.round(e.numberOfEmployees):void 0}}function S(e){if(!m(e))return p;let t={};if(m(e.collections))for(let[n,r]of Object.entries(e.collections))m(r)&&(t[n]={type:v(r.type),noindex:r.noindex===!0,...r.hiddenOk===!0?{hiddenOk:!0}:{}});let n=m(e.robots)?e.robots:{},r=n.maxImagePreview;return{site:x(e.site),collections:t,robots:{maxSnippet:_(n.maxSnippet,p.robots.maxSnippet),maxImagePreview:r===`none`||r===`standard`||r===`large`?r:p.robots.maxImagePreview,maxVideoPreview:_(n.maxVideoPreview,p.robots.maxVideoPreview),nosnippet:n.nosnippet===!0},jsonLdField:h(e.jsonLdField)??p.jsonLdField,breadcrumbs:e.breadcrumbs!==!1,steps:{identity:m(e.steps)&&e.steps.identity===!0,types:m(e.steps)&&e.steps.types===!0},hideHelpLine:e.hideHelpLine===!0}}const C=`help_line`,w=`hide_help_line`,T=`save_help_line`;function E(e){return{type:`toggle`,action_id:w,label:`Hide the help line at the foot of this page`,description:`Only admins see it: links to the guide and to more free plugins.`,initial_value:e}}function D(e){return{type:`form`,block_id:`${C}_switch_${e?`on`:`off`}`,fields:[E(e)],submit:{label:`Save`,action_id:T}}}function O(e,t){let n=e[w];return typeof n==`boolean`?n:t}function k(e){if(!e)return[];let t=(e,t)=>({type:`link`,label:e,target:{kind:`external`,url:t}});return[{type:`divider`,block_id:`${C}_rule`},{type:`actions`,block_id:C,elements:[t(`Help`,`https://moorage.dev/guides/add-schema-markup-to-emdash`),t(`More free plugins`,`https://moorage.dev/plugins`),t(`by Moorage`,`https://moorage.dev`)]}]}const A=[`Monday`,`Tuesday`,`Wednesday`,`Thursday`,`Friday`,`Saturday`,`Sunday`],ee=/^(\d{1,2})(?:[:.](\d{2}))?\s*[-–—]\s*(\d{1,2})(?:[:.](\d{2}))?$/;function te(e,t){let n=Number(e),r=Number(t??`0`);return n>24||r>59||n===24&&r!==0?null:`${String(n).padStart(2,`0`)}:${String(r).padStart(2,`0`)}`}function ne(e,t){let n=t.trim();if(n===``||n.toLowerCase()===`closed`)return[];let r=n.split(/\s*[,;]\s*|\s+and\s+/i).filter(e=>e!==``);if(r.length>2)return null;let i=[];for(let t of r){let n=ee.exec(t);if(!n)return null;let r=te(n[1]??``,n[2]),a=te(n[3]??``,n[4]);if(!r||!a)return null;i.push({days:[e],opens:r,closes:a})}return i}function re(e,t){return t.filter(t=>t.days.includes(e)).map(e=>`${e.opens}-${e.closes}`).join(`, `)}const j=[{value:`WebPage`,label:`Web page`,short:`Web page`},{value:`AboutPage`,label:`About page`,short:`About page`},{value:`ContactPage`,label:`Contact page`,short:`Contact page`},{value:`BlogPosting`,label:`Blog post`,short:`Blog post`},{value:`Article`,label:`Article`,short:`Article`},{value:`Service`,label:`Service`,short:`Service`},{value:`ProfilePage`,label:`Person or team member`,short:`Person`}];function M(e){return j.find(t=>t.value===e)?.short??e}const ie=[301,302,307,308,410,451],ae=2e3,N={empty:`No old address`,query:`Addresses with a ? are not supported`,external:`Points to another website`,noTarget:`No new address`,same:`Old and new address are the same`,code:`Unknown redirect type`,pattern:`Uses a pattern. Add it by hand under Manage > Redirects`,inactive:`Switched off in the export`,repeated:`Listed twice`,exists:`Already exists`,conflict:`Conflicts with an existing redirect`,loop:`Would send visitors in a circle`,tooMany:`Only the first ${ae} rows are imported at once`};function oe(e,t){let n=[],r=``,i=!1;for(let a=0;a<e.length;a+=1){let o=e[a];i?o===`"`&&e[a+1]===`"`?(r+=`"`,a+=1):o===`"`?i=!1:r+=o:o===`"`?i=!0:o===t?(n.push(r.trim()),r=``):r+=o}n.push(r.trim());let a=n[0]??``;return n.length===1&&/\S\s+\S/.test(a)?a.split(/\s+/):n}const se={source:`source`,origin:`source`,from:`source`,"old url":`source`,old:`source`,sources:`source`,target:`target`,destination:`target`,to:`target`,"new url":`target`,new:`target`,url_to:`target`,regex:`regex`,code:`code`,"http code":`code`,type:`code`,header_code:`code`,format:`format`,matching:`matching`,status:`status`,ignore:`ignore`,id:`id`};function ce(e,t){let n=e.map(e=>se[e.toLowerCase()]),r=n.includes(`source`)&&n.includes(`target`);if(r){let e={};return n.forEach((t,n)=>{t&&e[t]===void 0&&(e[t]=n)}),{format:e.matching===void 0?e.format===void 0?e.regex===void 0?`plain`:`redirection`:`yoast`:`rankmath`,hasHeader:r,index:e}}let i=t.slice(0,20),a=i.map(e=>e[2]??``),o=i.map(e=>(e[3]??``).toLowerCase());return o.length>0&&o.every(e=>e===`plain`||e===`regex`)?{format:`yoast`,hasHeader:r,index:{source:0,target:1,code:2,format:3}}:a.length>0&&a.every(e=>e===`0`||e===`1`)?{format:`redirection`,hasHeader:r,index:{source:0,target:1,regex:2,code:3}}:{format:`plain`,hasHeader:r,index:{source:0,target:1,code:2}}}function le(e){let t=e.trim();if(t===``)return{reason:N.empty};if(/^https?:\/\//i.test(t)){let e=o(t);if(!e)return{reason:N.empty};t=`${e.pathname}${e.search}`}return t.includes(`?`)?{reason:N.query}:(t.startsWith(`/`)||(t=`/${t}`),t.length>1&&(t=t.replace(/\/+$/,``)),{path:t===``?`/`:t})}const ue=/[\\^$.*+?()[\]{}|]/;function de(e,t){let n=e.trim();if(n.startsWith(`^`)&&(n=n.slice(1)),n.endsWith(`$`)&&(n=n.slice(0,-1)),n.endsWith(`/?`)&&(n=n.slice(0,-2)),!ue.test(n))return{source:n,target:t};let r=/^(\/[^\\^$.*+?()[\]{}|]*\/)\(\.\*\)$/.exec(n);return r&&!/\$[2-9]/.test(t)?{source:`${r[1]}[...rest]`,target:t.replace(/\$1/g,`[...rest]`)}:null}function fe(e){if(e===void 0||e.trim()===``)return 301;let t=Number(e.trim());return ie.includes(t)?t:null}function pe(e,t){let n=e.split(/\r?\n/),r=n.some(e=>e.includes(` `))?` `:`,`,i=n.map((e,t)=>({line:t+1,cells:oe(e,r)})).filter(e=>e.cells.some(e=>e!==``)),a=i[0];if(!a)return{format:`plain`,ready:[],skipped:[]};let s=ce(a.cells,i.map(e=>e.cells)),c=s.hasHeader?i.slice(1):i,l=(e,t)=>{let n=s.index[t];return n===void 0?void 0:e[n]},u=[],d=[],f=new Map(t.map(e=>[e.source,e.destination])),p=new Set;for(let{line:e,cells:t}of c){let n=l(t,`source`)??``,r=t=>d.push({line:e,source:n,reason:t});if(u.length>=ae){r(N.tooMany);continue}if([`inactive`,`disabled`].includes((l(t,`status`)??`active`).toLowerCase())){r(N.inactive);continue}let i=fe(l(t,`code`));if(i===null){r(N.code);continue}let a=n,s=l(t,`target`)??``,c=(l(t,`matching`)??`exact`).toLowerCase();if(l(t,`regex`)===`1`||(l(t,`format`)??``).toLowerCase()===`regex`||c===`regex`){let e=de(a,s);if(!e){r(N.pattern);continue}a=e.source,s=e.target}else if(c===`start`&&a.trim().endsWith(`/`))a=`${a.trim()}[...rest]`;else if(c!==`exact`){r(N.pattern);continue}let m=le(a);if(`reason`in m){r(m.reason);continue}let h=i===410||i===451,g=``;if(!h){if(s.trim()===``){r(N.noTarget);continue}let e=/^https?:\/\//i.test(s.trim()),t=/^https?:\/\//i.test(n.trim())?o(n.trim())?.host:void 0;if(e&&t!==o(s.trim())?.host){r(N.external);continue}let i=le(s);if(`reason`in i){r(i.reason===N.empty?N.noTarget:i.reason);continue}if(g=i.path,g===m.path){r(N.same);continue}}if(p.has(m.path)){r(N.repeated);continue}let _=f.get(m.path);if(_!==void 0){r(_===g?N.exists:N.conflict);continue}p.add(m.path),u.push({line:e,source:m.path,destination:g,type:i})}let m=new Map(f);for(let e of u)m.set(e.source,e.destination);let h=[];for(let e of u){let t=e.destination,n=!1;for(let r=0;r<20&&t!==``&&m.has(t);r+=1)if(t=m.get(t)??``,t===e.source){n=!0;break}n?d.push({line:e.line,source:e.source,reason:N.loop}):h.push({source:e.source,destination:e.destination,type:e.type})}return{format:s.format,ready:h,skipped:d}}function me(e){let t=e=>/[",\n]/.test(e)?`"${e.replace(/"/g,`""`)}"`:e;return[`source,target,code`,...e.map(e=>`${t(e.source)},${t(e.destination)},${e.type}`)].join(`
2
+ `)}const P=`redirects`,F=`redirects-import`,he={redirection:`Redirection`,yoast:`Yoast SEO Premium`,rankmath:`Rank Math`,plain:`a plain list`},ge={schedule:`* * * * *`};async function _e(e){let t=await e.kv.get(P);return t&&Array.isArray(t.rows)?t:null}async function ve(e){let t=[],n;for(let r=0;r<5;r+=1){let r=await e.redirects?.list({limit:100,cursor:n});if(!r||(t.push(...r.items),n=r.hasMore?r.cursor:void 0,!n))break}return t}async function ye(e,t){let n=pe(t,await ve(e)),r={status:`checked`,format:n.format,rows:n.ready,skipped:n.skipped,cursor:0,added:0,group:`Imported ${new Date().toISOString().slice(0,16).replace(`T`,` `)}`};return await e.kv.set(P,r),r}function be(e){let t=e instanceof Error?e.message:String(e);return/already exists/i.test(t)?{kind:`exists`,message:t}:/invalid source pattern|must be different|destination|path traversal|loop/i.test(t)?{kind:`invalid`,message:t}:{kind:`stop`,message:t}}async function xe(e){try{await e.cron?.schedule(F,ge)}catch{}}async function Se(e){try{await e.cron?.cancel(F)}catch{}}async function Ce(e,t,n=8){let r={...t,status:`adding`,skipped:[...t.skipped]},i=t.status===`adding`,a=0,o=async()=>{a+=1;try{return await e.kv.set(P,r),!0}catch{return!1}},s=e=>1+(e===i?1:0),c=0,l=r.cursor;for(;r.cursor<r.rows.length;){let t=r.rows[r.cursor];if(!t)break;let i=r.cursor===r.rows.length-1;if(r.cursor!==l&&a+1+s(i)>n)break;a+=1;try{await e.redirects?.create?.({source:t.source,destination:t.destination,type:t.type,groupName:r.group}),r.added+=1}catch(e){let{kind:n,message:i}=be(e);if(n===`stop`){if(r.cursor!==l||(r.stalls=(r.stalls??0)+1,r.stalls<3))break;r.skipped.push({line:0,source:t.source,reason:`EmDash could not add it: ${i}`})}else n===`exists`?r.added+=1:r.skipped.push({line:0,source:t.source,reason:`EmDash refused it: ${i}`})}if(r.cursor+=1,r.stalls=0,c+=1,c>=4&&a+1+1+s(!1)<=n){if(!await o())return r;c=0}}return r.cursor>=r.rows.length?(r.status=`done`,i&&await Se(e)):i||await xe(e),await o(),r}async function we(e,t,n=8){let r=t.status===`removing`,i={...t,status:`removing`,removed:t.removed??0},a=await e.redirects?.list({limit:100,group:t.group}),o=1,s=a?.items??[],c=e=>1+(e===r?1:0),l=s.length,u=!1;for(let t of s){let r=l===1&&!a?.hasMore;if(o+2+c(r)>n)break;o+=2;try{let n=await e.redirects?.get(t.id);n&&await e.redirects?.delete?.(t.id,{_rev:n._rev}),--l,i.removed=(i.removed??0)+1}catch{u=!0;break}}return l===0&&!a?.hasMore?(await e.kv.delete(P),r&&await Se(e),null):(i.stalls=u&&i.removed===(t.removed??0)?(t.stalls??0)+1:0,i.stalls>=3?Te(e,i,r):(r||await xe(e),await e.kv.set(P,i),i))}async function Te(e,t,n=!0){let{stalls:r,removed:i,...a}=t,o={...a,status:`done`,added:Math.max(0,t.added-(i??0))};return n&&await Se(e),await e.kv.set(P,o),o}async function Ee(e){let t=[],n;for(let r=0;r<5;r+=1){let r=await e.redirects?.list({limit:100,cursor:n});if(!r||(t.push(...r.items),n=r.hasMore?r.cursor:void 0,!n))break}return{text:me(t),count:t.length}}function I(e,t){return`${e} ${t}${e===1?``:`s`}`}function De(e){if(e.length===0)return[];let t=[{type:`header`,text:`Skipped (${e.length})`},{type:`context`,text:`Skipped rows are not added. Copy any you still need and add them by hand under Manage > Redirects, which also accepts patterns.`},{type:`table`,page_action_id:`skipped_table`,columns:[{key:`line`,label:`Row`},{key:`source`,label:`Old address`},{key:`reason`,label:`Why`}],rows:e.slice(0,50).map(e=>({line:e.line>0?String(e.line):``,source:e.source,reason:e.reason}))}];return e.length>50&&t.push({type:`context`,text:`And ${e.length-50} more.`}),t}const Oe=[{value:`301`,label:`Permanent`},{value:`302`,label:`Temporary, the old address will come back`},{value:`410`,label:`Page removed on purpose, no new address`}];function ke(e){return e===301||e===308?`Permanent`:e===302||e===307?`Temporary`:e===410?`Deleted on purpose`:String(e)}async function Ae(e){let t=await e.redirects?.list({limit:20});return{rows:(t?.items??[]).map(({id:e,source:t,destination:n,type:r,hits:i})=>({id:e,source:t,destination:n,type:r,hits:i})),more:t?.hasMore??!1}}function je(e){let t=e instanceof Error?e.message:String(e);return/already exists/i.test(t)?`There is already a redirect from that old address. Use Change in the list below.`:/must be different/i.test(t)?`The old and new address are the same.`:/loop/i.test(t)?`That would send visitors in a circle.`:/changed since/i.test(t)?`That redirect was just changed elsewhere. Try again.`:`EmDash could not save that redirect. Check both addresses start with /.`}function Me(e,t,n){let r=pe(me([{source:e,destination:t,type:Number(n)||301}]),[]);return r.ready[0]??r.skipped[0]?.reason??`Type the old address first.`}async function Ne(e,t,n,r){let i=Me(t,n,r);if(typeof i==`string`)return`${i}.`;try{return await e.redirects?.create?.(i),null}catch(e){return je(e)}}async function Pe(e,t,n,r,i){let a=Me(n,r,i);if(typeof a==`string`)return`${a}.`;try{let n=await e.redirects?.get(t);return n?(await e.redirects?.update?.(t,{...a,_rev:n._rev}),null):`That redirect no longer exists.`}catch(e){return je(e)}}async function Fe(e,t){try{let n=await e.redirects?.get(t);return n&&await e.redirects?.delete?.(t,{_rev:n._rev}),null}catch(e){return je(e)}}function Ie(e){if(!e)return[{type:`context`,text:`Paste a list exported from Redirection, Yoast SEO Premium or Rank Math, or any list of old address, new address. Nothing is added until you confirm.`},{type:`form`,block_id:`redirects`,fields:[{type:`text_input`,action_id:`list`,label:`Your list, one redirect per line`,multiline:!0,placeholder:`/old-page,/new-page`,initial_value:``}],submit:{label:`Check list`,action_id:`check_redirects`}}];if(e.status===`checked`){let t=e.rows.length;return[{type:`banner`,variant:t===0?`alert`:`default`,title:`${t} ready, ${e.skipped.length} skipped`,description:t===0?`Nothing in this list can be added. See why below.`:`Read as ${he[e.format]}. Nothing has been added yet. Existing redirects are never changed.`},{type:`actions`,elements:[...t>0?[{type:`button`,action_id:`add_redirects`,label:`Add ${I(t,`redirect`)}`,style:`primary`,confirm:{title:`Add ${I(t,`redirect`)}?`,text:`Visitors to these old addresses will be sent to the new ones. You can remove them all again afterwards.`,confirm:`Add`,deny:`Cancel`}}]:[],{type:`button`,action_id:`reset_redirects`,label:`Start again`}]},...De(e.skipped)]}if(e.status===`removing`){let t=e.removed??0;return[{type:`meter`,label:`${t} of ${e.added} removed`,value:t,max:Math.max(e.added,t,1)},{type:`context`,text:`The rest are removed in the background, a few each minute. You can leave this page.`},{type:`actions`,elements:[{type:`button`,action_id:`remove_redirects`,label:`Remove more now`,style:`primary`},{type:`button`,action_id:`stop_redirects`,label:`Stop here`}]}]}return e.status===`adding`?[{type:`meter`,label:`${e.added} of ${e.rows.length} added`,value:e.cursor,max:e.rows.length},{type:`context`,text:`The rest are added in the background, a few each minute. You can leave this page.`},{type:`actions`,elements:[{type:`button`,action_id:`more_redirects`,label:`Add more now`,style:`primary`},{type:`button`,action_id:`stop_redirects`,label:`Stop here`}]}]:[{type:`banner`,title:`${I(e.added,`redirect`)} added from your list`,description:`They are labelled "${e.group}".`},{type:`actions`,elements:[{type:`button`,action_id:`reset_redirects`,label:`Import another list`},...e.added>0?[{type:`button`,action_id:`remove_redirects`,label:`Remove the ${e.added} just added`,confirm:{title:`Remove these redirects?`,text:`Only the redirects this import added are removed.`,confirm:`Remove`,deny:`Cancel`,style:`danger`}}]:[]]},...De(e.skipped)]}function Le(e,t){let n=e?String(e.type):`301`;return{type:`form`,block_id:e?`edit_redirect_${e.id}`:`add_one_${t}`,fields:[{type:`text_input`,action_id:`source`,label:`Redirect from`,placeholder:`/old-page`,initial_value:e?.source??``},{type:`text_input`,action_id:`destination`,label:`Redirect to`,placeholder:`/new-page`,initial_value:e?.destination??``,condition:{field:`code`,neq:`410`}},{type:`select`,action_id:`code`,label:`Kind`,options:Oe.some(e=>e.value===n)?Oe:[...Oe,{value:n,label:`${ke(Number(n))} (${n})`}],initial_value:n}],submit:e?{label:`Save changes`,action_id:`save_redirect:${e.id}`}:{label:`Add redirect`,action_id:`add_redirect`}}}function Re(e){let{state:t,rows:n,editing:r}=e,i=r?void 0:e.panel??(t?`import`:void 0),a=n[0]?.id??`first`,o=[{type:`context`,text:`Send visitors from an old address to a new one. Pages you rename in EmDash are redirected for you.`},{type:`actions`,elements:[{type:`button`,action_id:`open_add`,label:`Add a redirect`,...i===void 0&&!r?{style:`primary`}:{}},{type:`button`,action_id:`open_import`,label:`Import from WordPress`},{type:`button`,action_id:`export_redirects`,label:`Export`},...e.managerUrl?[{type:`link`,label:`Full Redirects screen`,target:{kind:`external`,url:e.managerUrl}}]:[]]}],s={type:`actions`,elements:[{type:`button`,action_id:`close_panel`,label:`Close`}]},c=e.problem?[{type:`banner`,variant:`error`,title:e.problem}]:[];return r?o.push({type:`header`,text:`Change a redirect`},...c,Le(r,a),s,{type:`divider`}):i===`add`?o.push({type:`header`,text:`Add a redirect`},...c,Le(void 0,a),s,{type:`divider`}):i===`import`?(o.push({type:`header`,text:`Import from WordPress`},...Ie(t)),t||o.push(s),o.push({type:`divider`})):i===`export`&&o.push({type:`header`,text:`Export`},{type:`context`,text:`Copy this list to keep a backup or to paste into a spreadsheet. It can be imported again here.`},{type:`code`,code:e.exported??``,language:`bash`},s,{type:`divider`}),n.length===0?o.push({type:`empty`,title:`No redirects yet`,description:`Add one, or import a list from your old site.`,size:`sm`}):(o.push({type:`table`,page_action_id:`redirects_table`,columns:[{key:`source`,label:`Redirect from`},{key:`destination`,label:`Redirect to`},{key:`kind`,label:`Kind`},{key:`hits`,label:`Visits`,format:`number`},{key:`edit`,label:``,format:`element`},{key:`remove`,label:``,format:`element`}],rows:n.map(e=>({source:e.source,destination:e.destination||`(none)`,kind:ke(e.type),hits:e.hits,edit:{type:`button`,action_id:`edit_redirect`,label:`Change`,value:e.id},remove:{type:`button`,action_id:`remove_redirect`,label:`Remove`,value:e.id,confirm:{title:`Remove this redirect?`,text:`Visitors to ${e.source} will see a not-found page.`,confirm:`Remove`,deny:`Keep`,style:`danger`}}}))}),e.more&&o.push({type:`context`,text:`Showing the newest ${n.length}. The rest are in the full Redirects screen.`})),o}async function ze(e,t){let n={...t,status:`done`};try{await e.cron?.cancel(F)}catch{}return await e.kv.set(P,n),n}const Be=new Set("iIl.,:;|!'`jtfr ()[]".split(``)),Ve=new Set(`mwMWQO@%`.split(``));function He(e){let t=0;for(let n of e)Be.has(n)?t+=5:Ve.has(n)?t+=15:n>=`A`&&n<=`Z`?t+=12:t+=9.5;return Math.round(t)}function Ue(e){if(He(e)<=580)return e;let t=e;for(;t.length>1&&He(`${t}…`)>580;)t=t.slice(0,-1);return`${t.trimEnd()}…`}function We(e){return e.toLowerCase().replace(/\s+/g,` `).trim()}const Ge=new Set([`home`,`homepage`,`home page`,`index`,`untitled`,`welcome`]),Ke=new Set([`_type`,`_key`,`id`,`slug`,`url`,`href`,`src`,`style`,`icon`]);function qe(e){let t={textLength:0,imagesWithoutAlt:0,hasImage:!1},n=(e,r,i)=>{if(i>12)return;if(typeof e==`string`){!Ke.has(r)&&!/^(https?:)?\//.test(e)&&(t.textLength+=e.trim().length);return}if(Array.isArray(e)){for(let t of e)n(t,r,i+1);return}if(typeof e!=`object`||!e)return;let a=e;(a._type===`image`||typeof a.src==`string`&&`alt`in a)&&(t.hasImage=!0,(typeof a.alt!=`string`||a.alt.trim()===``)&&(t.imagesWithoutAlt+=1));for(let[e,t]of Object.entries(a))n(t,e,i+1)};for(let[t,r]of Object.entries(e))t!==`json_ld`&&n(r,t,0);return t}const L={id:`no-social-image`,level:`look`,message:`This page has no picture for shared links, unless your site has a default sharing image.`,fix:`Add an image in the SEO panel. If your theme adds a picture itself, this clears once the page has been viewed.`};function Je(e){return typeof e.excerpt==`string`?e.excerpt.trim():``}function Ye(e){let t=[],{seo:n}=e,r=qe(e.data),i=n?.title?.trim()||e.title.trim();if(i===``&&t.push({id:`no-title`,level:`fix`,message:`This page has no title.`,fix:`Give the page a title.`}),Ge.has(i.toLowerCase())&&t.push({id:`generic-title`,level:`fix`,message:`The title is just "${i}", which is what Google shows as the headline.`,fix:`Put your name and what you do in SEO Title, in the page's SEO panel.`}),n&&!n.description?.trim()&&Je(e.data)===``&&t.push({id:`no-description`,level:`fix`,message:`No search description, so Google writes its own summary of this page.`,fix:`Fill in Meta Description in the page's SEO panel.`}),e.duplicateTitle&&i!==``&&t.push({id:`duplicate-title`,level:`fix`,message:`Another page has the same title, so Google cannot tell them apart.`,fix:`Give this page its own title in the SEO panel.`}),n?.canonical&&e.slug){let r=o(n.canonical,`${e.siteUrl.replace(/\/+$/,``)}/`),i=r?.pathname.replace(/\/+$/,``).split(`/`).pop(),a=[`home`,`index`].includes(e.slug)&&(i??``)===``;r&&i!==e.slug&&!a&&t.push({id:`canonical-elsewhere`,level:`fix`,message:`This page tells Google to show a different page instead (canonical).`,fix:`Clear the canonical address in the SEO panel unless that is intended.`})}if(i!==``&&He(i)>580&&t.push({id:`title-long`,level:`look`,message:`The end of this title may be cut off in Google.`,fix:`Shorten the title, or put the important words first.`}),n&&!n.image&&!r.hasImage&&t.push(L),e.isArticle&&e.authorCount===0&&t.push({id:`no-author`,level:`look`,message:`This article has no author.`,fix:`Add a byline to the article.`}),r.imagesWithoutAlt>0){let e=r.imagesWithoutAlt;t.push({id:`images-without-alt`,level:`look`,message:`${e} ${e===1?`picture has`:`pictures have`} no description for screen readers.`,fix:`Add alt text to each picture.`})}return r.textLength<80&&t.push({id:`nearly-empty`,level:`look`,message:`This page has almost no text.`,fix:`Add some content, or hide the page from search.`}),t}function Xe(e,t){return t?`hidden`:e.some(e=>e.level===`fix`)?`fix`:e.length>0?`look`:`ready`}const Ze=/^[0-9A-HJKMNP-TV-Z]{26}(\.[A-Za-z0-9]+)?$/i;function Qe(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function $e(e){return typeof e==`number`&&Number.isFinite(e)&&e>=1?Math.round(e):void 0}function et(e){if(!e.startsWith(`/_emdash/api/media/file/`))return;let t=e.slice(24);try{t=decodeURIComponent(t)}catch{return}return t!==``&&!t.includes(`/`)?t:void 0}function tt(e,t){let n=o(e,`${t.replace(/\/+$/,``)}/`);if(!n||!/^https?:$/.test(n.protocol)||n.search!==``)return[];let{pathname:r}=n;if(r===`/_image`||r.includes(`/cdn-cgi/image/`))return[];let i=et(r);if(i)return[`file:${i}`];let a=[`url:${n.origin}${r}`],s=r.slice(1);return Ze.test(s)&&a.push(`file:${s}`),a}function nt(e){if(typeof e!=`string`||e===``)return[];if(e.startsWith(`/`)){let t=e.includes(`?`)?void 0:et(e);return t?[`file:${t}`]:[]}return/^https?:\/\//i.test(e)?tt(e,`http://localhost`).filter(e=>e.startsWith(`url:`)):[]}function rt(e){return typeof e==`string`&&e!==``&&!e.includes(`/`)?[`file:${e}`]:[]}function it(e){let t=$e(e.width),n=$e(e.height);if(t===void 0||n===void 0||typeof e.mimeType==`string`&&!e.mimeType.startsWith(`image/`))return;let r=[];if(e._type===`image`&&Qe(e.asset))r.push(...rt(e.asset._ref),...nt(e.asset.url));else if(typeof e.id==`string`){if(e.provider===void 0||e.provider===`local`){let t=Qe(e.meta)?e.meta:{};r.push(...rt(t.storageKey),...rt(e.id))}r.push(...nt(e.src))}let i=[...new Set(r)];return i.length>0?{keys:i,width:t,height:n}:void 0}function at(e){let t=[],n=new Set,r=(e,i)=>{if(t.length>=10||i>12)return;if(Array.isArray(e)){for(let t of e)r(t,i+1);return}if(!Qe(e))return;let a=it(e);if(a&&!a.keys.some(e=>n.has(e))){for(let e of a.keys)n.add(e);t.push(a)}for(let t of Object.values(e))r(t,i+1)};for(let[t,n]of Object.entries(e))t!==`json_ld`&&r(n,0);return t.slice(0,10)}function ot(e,t,n){if(!e||e.length===0)return;let r=tt(t,n);if(r.length===0)return;let i=e.find(e=>e.keys.some(e=>r.includes(e)));return i?{width:i.width,height:i.height}:void 0}const st=16*1024;function R(e,t){return`${e}:${t}`}function z(e){return e.storage.entries}function ct(e){return typeof e==`object`&&!!e&&!Array.isArray(e)&&`@type`in e}function lt(e){let t=e;if(typeof e==`string`){if(e.trim()===``||e.length>st)return[];try{t=JSON.parse(e)}catch{return[]}}let n=(Array.isArray(t)?t:[t]).filter(ct);return JSON.stringify(n).length>st?[]:n}function ut(e){let{title:t,name:n}=e.data;return typeof t==`string`?t:typeof n==`string`?n:``}function B(e){let{collection:t,entry:n,authors:r,previous:i,config:a,siteUrl:o}=e,s=a.collections[t],c=i?.type??s?.type??f(t),l=n.seo?.title?.trim()||ut(n),d=n.status===`published`,p=d&&e.hasPages,m=e.hasPages?Ye({slug:n.slug,data:n.data,seo:n.seo,title:ut(n),siteUrl:o,isArticle:u.includes(c),authorCount:r.length,duplicateTitle:e.duplicateTitle}):[],h=m.some(e=>e.id===L.id),g=h&&i?.imageSeen===!0,_=g?m.filter(e=>e.id!==L.id):m,v=e.hasPages?at(n.data):[];return{v:1,publishedAt:n.publishedAt,updatedAt:n.updatedAt,authors:r,...i?.type?{type:i.type}:{},custom:lt(n.data[a.jsonLdField]),...v.length>0?{images:v}:{},needsImage:h,...g?{imageSeen:g}:{},collection:t,entryId:n.id,title:l,status:e.hasPages?d?Xe(_,n.seo?.noIndex===!0||s?.noindex===!0):`draft`:`skip`,titleKey:p?We(l):``,issues:_,checkedAt:new Date().toISOString()}}function dt(e,t){return e.status===`hidden`||e.status===`draft`||e.status===`skip`?e.status:Xe(t,!1)}function ft(e,t){let n=e.issues??[],r=n.some(e=>e.id===L.id);if(!(e.needsImage??r)||e.imageSeen===!0===t&&r!==t)return null;let i=n.filter(e=>e.id!==L.id),a=t?i:[...i,L],{imageSeen:o,...s}=e;return{...s,needsImage:!0,...t?{imageSeen:!0}:{},issues:a,status:dt(e,a)}}async function pt(e,t){return(await mt(e,[t]))[0]??[]}async function mt(e,t){let n=[...new Set(t.filter(e=>e!==``))];if(n.length===0)return t.map(()=>[]);let r=await z(e).query({where:{titleKey:n.length===1?n[0]??``:{in:n}},limit:10*n.length});return t.map(e=>e===``?[]:r.items.filter(t=>t.data.titleKey===e))}function V(e){return e.issues?.some(e=>e.id===`duplicate-title`)??!1}function ht(e,t){let n=e.length>1;for(let{id:r,data:i}of e)V(i)!==n&&t.set(r,H(i,n))}function H(e,t){let n=(e.issues??[]).filter(e=>e.id!==`duplicate-title`),r=t?[...n,{id:`duplicate-title`,level:`fix`,message:`Another page has the same title, so Google cannot tell them apart.`,fix:`Give this page its own title in the SEO panel.`}]:n;return{...e,issues:r,status:dt(e,r)}}async function gt(e,t,n,r){return(await bt(e,t,n,r))?.record??null}function _t(e){return(e[0]?.bylines??[]).map(({byline:e})=>({name:e.displayName,...e.websiteUrl?{url:e.websiteUrl}:{}}))}async function vt(e,t,n,r){if(!e.content)return null;let[i,a]=await Promise.all([e.content.get(t,n),e.bylines?.getEntriesBylines(t,[n])??[]]);return i?B({collection:t,entry:i,authors:_t(a),previous:null,config:r,siteUrl:e.site.url,duplicateTitle:!1,hasPages:!0}):null}async function yt(e,t,n,r){let i=await e.content?.get(t,n);if(!i)return null;let a=z(e),o=R(t,n),[s,c,l]=await Promise.all([e.bylines?.getEntriesBylines(t,[n])??[],a.get(o),e.schema?.getCollection(t)]),u=B({collection:t,entry:i,authors:_t(s),previous:c,config:r,siteUrl:e.site.url,duplicateTitle:!1,hasPages:l?l.routable&&!l.hidden:!0}),d=u.titleKey??``,f=c?.titleKey??``;return{entry:i,previous:c,record:H(u,d!==``&&d===f&&c!==null&&V(c)),newKey:d,oldKey:f}}async function bt(e,t,n,r){let i=await yt(e,t,n,r);if(!i)return null;let{entry:a,record:o,newKey:s,oldKey:c}=i,l=z(e),u=R(t,n);await l.put(u,o);let[d=[],f=[]]=await mt(e,[s,c===s?``:c]),p=new Map,m=d,h=d.find(e=>e.id===u)?.data;if(s!==``&&h?.checkedAt!==o.checkedAt){let e=h??f.find(e=>e.id===u)?.data??null;e&&St(e.updatedAt,o.updatedAt)&&(p.set(u,o),m=[...d.filter(e=>e.id!==u),{id:u,data:o}])}return ht(m,p),ht(f.filter(e=>e.id!==u),p),p.size>0&&await l.putMany([...p].map(([e,t])=>({id:e,data:t}))),{record:p.get(u)??o,entry:a}}async function xt(e,t,n,r,i){let a=await yt(e,t,n,r);if(!a)return null;let{entry:o,previous:s,newKey:c,oldKey:l}=a,u=i?i(a.record):a.record,d=R(t,n);return s===null||c===l?await z(e).put(d,u):i&&await z(e).put(d,i(s)),{record:u,entry:o}}function St(e,t){if(!e||!t)return!1;let n=Date.parse(e),r=Date.parse(t);return Number.isFinite(n)&&Number.isFinite(r)&&n<r}async function Ct(e,t,n){let r=z(e),i=R(t,n),a=await r.get(i);await r.delete(i);let o=new Map;ht((await pt(e,a?.titleKey??``)).filter(e=>e.id!==i),o),o.size>0&&await r.putMany([...o].map(([e,t])=>({id:e,data:t})))}const U=`scan`,wt=`stats`,W=`seo-scan`,Tt=[`ready`,`look`,`fix`,`hidden`];async function Et(e){return e.kv.get(U)}async function Dt(e,t,n,r=6){let i=await Mt(e,{status:`running`,collections:t,scope:[...t],phase:`entries`,checked:0,tally:{checked:0,fix:0,hidden:0,rows:[],more:!1},startedAt:new Date().toISOString()},n,r,{scheduled:!1});if(i.status===`done`&&!i.sweep)return i;try{await e.cron?.schedule(W,{schedule:`* * * * *`})}catch{}return i}async function Ot(e,t,n,r=4){let i=t.collections[0];if(i===void 0)return t.phase=`titles`,0;let a=1,o=new Date().toISOString(),s=e=>(e.checkedAt??``)>=t.startedAt,c=await e.content?.list(i,{limit:20,cursor:t.cursor,where:{status:`published`}}),l=c?.items??[];if(l.length>0&&r<4)return a;if(l.length>0){let r=z(e),u=l.map(e=>e.id),[d,f]=await Promise.all([e.bylines?.getEntriesBylines(i,u)??[],r.getMany(u.map(e=>R(i,e)))]);a+=2;let p=new Map(d.map(e=>[e.entryId,e.bylines])),m=new Set,h=l.map(t=>{let r=R(i,t.id),a=f.get(r)??null;return a&&(St(t.updatedAt,a.updatedAt)&&s(a)||(a.checkedAt??``)>=o)?(m.add(r),{id:r,data:a}):{id:r,data:B({hasPages:!0,collection:i,entry:t,authors:(p.get(t.id)??[]).map(({byline:e})=>({name:e.displayName,...e.websiteUrl?{url:e.websiteUrl}:{}})),previous:a,config:n,siteUrl:e.site.url,duplicateTitle:a?V(a):!1})}});if(t.cursor===void 0&&t.checked===0&&!c?.hasMore&&t.collections.length===1){let e=new Map;for(let{data:t}of h)t.titleKey&&e.set(t.titleKey,(e.get(t.titleKey)??0)+1);for(let t of h){let n=(e.get(t.data.titleKey??``)??0)>1;m.has(t.id)&&V(t.data)!==n&&m.delete(t.id),t.data=H(t.data,n)}t.status=`done`,t.finishedAt=new Date().toISOString(),t.sweep={}}let g=h.filter(e=>!m.has(e.id));g.length>0&&(await r.putMany(g),a+=1);for(let{data:e}of h)jt(t.tally,e,1);t.checked+=l.length}return c?.hasMore&&c.cursor?t.cursor=c.cursor:(t.cursor=void 0,t.collections=t.collections.slice(1),t.collections.length===0&&t.status!==`done`&&(t.phase=`titles`,t.sweep={})),a}async function kt(e,t,n){let r=new Set(t.scope??[]),i=z(e),a=t.sweep?.cursor,o=new Set(t.sweep?.kept??[]),s=r.size>0&&e.content?await i.query({where:{status:{in:Tt}},limit:100,cursor:a}):{items:[],hasMore:!1,cursor:void 0},c=s.items.filter(({id:e,data:n})=>!o.has(e)&&r.has(n.collection??``)&&(n.entryId??``)!==``&&(n.checkedAt??``)<t.startedAt).slice(0,1),l=await Promise.all(c.map(({data:t})=>e.content?.get(t.collection??``,t.entryId??``))),u=[],d=[];c.forEach(({id:t,data:r},i)=>{let a=l[i];a?a.status===`published`?o.add(t):d.push({id:t,data:B({hasPages:!0,collection:r.collection??``,entry:a,authors:r.authors,previous:r,config:n,siteUrl:e.site.url,duplicateTitle:!1})}):u.push(t)}),u.length>0&&await i.deleteMany(u),d.length>0&&await i.putMany(d);let f=o.size>0?{kept:[...o]}:{};c.length>0?t.sweep={...a?{cursor:a}:{},...f}:s.hasMore&&s.cursor?t.sweep={cursor:s.cursor,...f}:delete t.sweep}async function At(e,t){let n=z(e),r=await n.query({orderBy:{titleKey:`asc`},limit:100,cursor:t.titleCursor}),i=r.items.filter(e=>(e.data.titleKey??``)!==``),a=new Map(i.map(e=>[e.id,e.data])),o=new Map,s=(e,t,n)=>{V(t)!==n&&o.set(e,n)},c=t.lastTitle;for(let e=0;e<i.length;e+=1){let t=i[e];if(!t)continue;let r=t.data.titleKey??``,o=i[e+1],l=c?.key===r,u=o?.data.titleKey===r;if(l&&c&&!c.flagged&&e===0){let e=await n.get(c.id);e&&(a.set(c.id,e),s(c.id,e,!0))}let d=l||u;s(t.id,t.data,d),c={key:r,id:t.id,flagged:d}}if(o.size>0){let e=new Set([...o.keys()].map(e=>a.get(e)?.titleKey??``)),r=[...a].filter(([,t])=>e.has(t.titleKey??``)).map(([e])=>e),i=await n.getMany(r);for(let t of r){let n=a.get(t),r=i.get(t);(!n||r?.checkedAt!==n.checkedAt||r?.titleKey!==n.titleKey)&&e.delete(n?.titleKey??``)}let s=[];for(let[n,r]of o){let i=a.get(n);if(!i||!e.has(i.titleKey??``))continue;let o=H(i,r);s.push({id:n,data:o}),jt(t.tally,i,-1),jt(t.tally,o,1)}s.length>0&&await n.putMany(s)}t.lastTitle=c,r.hasMore&&r.cursor?t.titleCursor=r.cursor:(t.status=`done`,t.finishedAt=new Date().toISOString(),t.titleCursor=void 0,t.lastTitle=void 0)}function G(e,t=`fix`){return{collection:e.collection??``,id:e.entryId??``,title:e.title||`(no title)`,issues:(e.issues??[]).filter(e=>e.level===t).map(e=>e.message)}}function jt(e,t,n){if(!(!t.status||t.status===`draft`||t.status===`skip`)){if(e.checked+=n,t.status===`hidden`){e.hidden+=n;let r=e.hiddenTitles??[],i=t.title||`(no title)`;e.hiddenTitles=n===1?[...r,i].slice(0,10):r.filter(e=>e!==i)}if(t.status===`look`){let r=e.look??[];e.look=n===1?[...r,G(t,`look`)].slice(0,10):r.filter(e=>e.id!==t.entryId)}t.status===`fix`&&(e.fix+=n,n===1?e.rows.length<10?e.rows.push(G(t)):e.more=!0:e.rows=e.rows.filter(e=>e.id!==t.entryId))}}async function Mt(e,t,n,r=6,{scheduled:i=!0}={}){let a={...t,collections:[...t.collections],tally:{...t.tally,rows:[...t.tally.rows]}};if(a.status===`done`&&!a.sweep)return a;if(a.sweep)await kt(e,a,n);else if(a.phase===`entries`){let t=0;for(let i=0;i<5&&a.phase===`entries`;i+=1){let o=r-2-t;if(i>0&&o<1)break;let s={checked:a.checked,collections:a.collections.length,cursor:a.cursor};if(t+=await Ot(e,a,n,i===0?4:o),a.checked!==s.checked||a.status===`done`||a.collections.length===s.collections&&a.cursor===s.cursor)break}a.phase===`titles`&&a.checked===0&&(a.status=`done`,a.finishedAt=new Date().toISOString())}else await At(e,a);if(i&&a.status===`done`&&!a.sweep)try{await e.cron?.cancel(W)}catch{}return await e.kv.set(U,a),a}async function Nt(e){let t=z(e),[n,r,i,a,o]=await Promise.all([t.count({status:{in:[`ready`,`look`,`fix`,`hidden`]}}),t.count({status:`fix`}),t.query({where:{status:`fix`},limit:10}),t.query({where:{status:`look`},limit:10}),t.query({where:{status:`hidden`},limit:10})]);return{checked:n,fix:r,hidden:o.hasMore?11:o.items.length,rows:i.items.map(({data:e})=>G(e)),more:i.hasMore,look:a.items.map(({data:e})=>G(e,`look`)),hiddenTitles:o.items.map(({data:e})=>e.title||`(no title)`)}}const Pt=new Map;async function Ft(e,t,{fresh:n=!1,now:r=Date.now()}={}){let i=e.site?.url??``,a=Pt.get(e.plugin.id);a||Pt.set(e.plugin.id,a=new Map);let o=a.get(i);if(n||!o||r>=o.until){let e=t(),n={value:e,until:r+3e4};a.set(i,n),e.catch(()=>{a.get(i)===n&&a.delete(i)}),o=n}return structuredClone(await o.value)}function It(e){Pt.delete(e)}const K={overview:0,redirects:1,identity:2,types:2},Lt=[{value:`FoodEstablishment`,label:`Restaurant, cafe or bakery`},{value:`Store`,label:`Shop`},{value:`MedicalBusiness`,label:`Health and medical`},{value:`Dentist`,label:`Dentist`},{value:`LegalService`,label:`Legal`},{value:`FinancialService`,label:`Financial`},{value:`HomeAndConstructionBusiness`,label:`Home services (plumber, electrician, builder)`},{value:`HealthAndBeautyBusiness`,label:`Beauty and hair`},{value:`SportsActivityLocation`,label:`Fitness`},{value:`LodgingBusiness`,label:`Hotel or lodging`},{value:`AutomotiveBusiness`,label:`Automotive`},{value:`ProfessionalService`,label:`Professional service`},{value:`LocalBusiness`,label:`Other`}],Rt=[``,`$`,`$$`,`$$$`,`$$$$`],zt=[{id:`facebook`,label:`Facebook`,hosts:[`www.facebook.com`,`facebook.com`,`fb.com`]},{id:`instagram`,label:`Instagram`,hosts:[`www.instagram.com`,`instagram.com`]},{id:`linkedin`,label:`LinkedIn`,hosts:[`www.linkedin.com`,`linkedin.com`]},{id:`x`,label:`X (Twitter)`,hosts:[`x.com`,`www.x.com`,`twitter.com`,`www.twitter.com`]},{id:`youtube`,label:`YouTube`,hosts:[`www.youtube.com`,`youtube.com`]}];function Bt(e){let t={},n=[];for(let r of e){let e=o(r)?.host??``,i=zt.find(t=>t.hosts.includes(e));i&&!(i.id in t)?t[i.id]=r:n.push(r)}return{named:t,other:n}}const Vt={needed:`needed`,worth:`worth adding`,optional:`optional`};function q(e,t,n){let r=n.filter(e=>!!e);return r.length>0?`${e}: ${r.join(`, `)}`:`${e} (${Vt[t]}, not added yet)`}function Ht(e){return A.map(t=>{let n=re(t,e.openingHours).replace(`, `,` and `);return n===``?void 0:`${t.slice(0,3)} ${n}`}).filter(e=>e!==void 0)}function Ut(e){let{site:t}=e.config,n=J(t),r=[];return n===`local`?((!t.address?.streetAddress||!t.address.addressLocality)&&r.push({what:`Address`,why:`Without it Google cannot treat you as a local business.`}),t.openingHours.length===0&&r.push({what:`Opening hours`,why:`Google cannot show when you are open.`}),t.telephone||r.push({what:`Phone number`,why:`Customers cannot call you from Google.`})):(n===`organization`&&!t.logo&&r.push({what:`Logo`,why:`Google shows it beside your name.`}),t.sameAs.length===0&&r.push({what:`Profiles elsewhere`,why:`They help Google connect your other pages to this site.`})),r}function J(e){return e.type===`Person`?`person`:e.type===`Organization`?`organization`:`local`}function Wt(e,t){let n=o(e);if(!(!n||n.protocol!==`https:`&&n.protocol!==`http:`))return`${n.origin}${t}`}function Gt(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Y(e,t,n=``){let r=e[t];return typeof r==`string`?r.trim():n}function Kt(e,t,n){let r=e[t];return typeof r==`boolean`?r:n}function qt(e){return e.split(/\r?\n/).map(e=>e.trim()).filter(e=>e!==``)}async function Jt(e,{rows:t=!0}={}){let[n,a,o]=await Promise.all([e.kv.list(),e.schema?.listCollections()??[],t?Ae(e):{rows:[],more:!1}]),c=new Map(n.map(e=>[e.key,e.value])),l=c.get(P);return{...i(c,s)?{uncopied:{value:c.get(s.legacy)}}:{},config:S(await r(e,c,s,{copy:!1})),collections:a.filter(e=>e.routable&&!e.hidden),redirects:l&&Array.isArray(l.rows)?l:null,redirectRows:o.rows,moreRedirects:o.more,scan:c.get(U)??null,stats:c.get(wt)??null}}async function Yt(e,n){let r=await Nt(e);n.uncopied?await t(e,s,n.uncopied.value):JSON.stringify(r)!==JSON.stringify(n.stats)&&await e.kv.set(wt,r);let{uncopied:i,...a}=n;return{...a,stats:r}}function Xt(e){return e.stats??(e.scan?.status===`done`?e.scan.tally:null)}function Zt(e){let{scan:t}=e,n=Xt(e);if(!t||t.status===`running`)return[{type:`banner`,title:`Checking your pages`,description:t?`${t.checked} checked so far. The rest are checked in the background.`:`This starts by itself and takes about a minute.`}];if(!n)return[];let r=n.checked-n.fix-n.hidden;return[{type:`stats`,items:[{label:`Pages checked`,value:n.checked},{label:`Nothing to fix`,value:r},{label:`To fix`,value:n.fix}]}]}function Qt(e){let{scan:t}=e,n=Xt(e);if(!n||!t||t.status===`running`)return[];let r=[];n.checked===0?r.push({type:`empty`,title:`No published pages yet`,size:`sm`}):n.fix===0?r.push({type:`empty`,title:`No pages to fix. Nice work.`,size:`sm`}):(r.push({type:`header`,text:`To fix`},{type:`table`,page_action_id:`fix_table`,columns:[{key:`page`,label:`Page`},{key:`missing`,label:`What's missing`},{key:`open`,label:``,format:`element`}],rows:n.rows.map(e=>({page:e.title,missing:e.issues.join(` `),open:{type:`link`,label:`Open page`,target:{kind:`content`,collection:e.collection,id:e.id}}}))}),n.more&&r.push({type:`context`,text:`Showing the first 10. Fix these and the next ones appear.`}));let i=n.look??[];if(i.length>0&&r.push({type:`accordion`,label:`Worth a look (${i.length}${i.length===10?`+`:``})`,blocks:[{type:`context`,text:`Nothing is wrong with these pages. These are improvements for when you have time.`},{type:`table`,page_action_id:`look_table`,columns:[{key:`page`,label:`Page`},{key:`advice`,label:`Could be better`},{key:`open`,label:``,format:`element`}],rows:i.map(e=>({page:e.title,advice:e.issues.join(` `),open:{type:`link`,label:`Open page`,target:{kind:`content`,collection:e.collection,id:e.id}}}))}]}),n.hidden>0){let e=(n.hiddenTitles??[]).join(`, `);r.push({type:`context`,text:`${n.hidden>10?`More than 10`:n.hidden} ${n.hidden===1?`page is`:`pages are`} hidden from Google and not counted${e?`: ${e}`:``}. To show one again, open it and switch off "Hide from search engines" in its SEO panel.`})}if(t.finishedAt){let e=new Date(t.finishedAt).toLocaleDateString(`en-GB`,{day:`numeric`,month:`short`});r.push({type:`context`,text:`Each page is checked again when it is saved. Last full check: ${e}.`})}return r}function $t(e){if(!e.scan)return[];let t=e.scan.status===`running`;return[{type:`actions`,elements:[{type:`button`,action_id:t?`check_more`:`check_pages`,label:t?`Update`:`Check all pages again`}]}]}function en(e){return e.collections.filter(t=>{let n=e.config.collections[t.slug];return n?.noindex===!0&&n.hiddenOk!==!0})}function tn(e,t){let n=en(e);if(n.length===0)return[];let r=[{type:`banner`,variant:`alert`,title:`Some pages are hidden from Google`,description:`Every page in ${n.map(e=>e.label).join(`, `)} is hidden from search engines. If that is a leftover from building the site, switch it back on in the Settings tab, under Content types.`}];return t&&r.push({type:`actions`,elements:[{type:`button`,action_id:`hidden_ok`,label:`This is intended`}]}),r}function nn(e,t){let{steps:n,site:r}=e.config;return n.identity?[]:[{type:`banner`,title:`Tell Google who this site is for`,description:`Google can then show your name and details beside your pages. It takes a minute.`},{type:`accordion`,label:`Set this up (1 minute)`,default_open:!0,blocks:[{type:`form`,block_id:`setup_identity`,fields:[{type:`radio`,action_id:`kind`,label:`This site is for`,options:[{label:`A local business customers visit (shop, cafe, clinic)`,value:`local`},{label:`A company or organisation`,value:`organization`},{label:`A person`,value:`person`}]},{type:`select`,action_id:`business`,label:`Kind of business`,options:[{label:`Choose the closest`,value:``},...Lt],initial_value:``,condition:{field:`kind`,eq:`local`}},{type:`text_input`,action_id:`name`,label:`Name`,placeholder:`Your name or your business's name`,initial_value:r.name??t},{type:`text_input`,action_id:`street`,label:`Street address`,initial_value:r.address?.streetAddress??``,condition:{field:`kind`,eq:`local`}},{type:`text_input`,action_id:`town`,label:`Town or city`,initial_value:r.address?.addressLocality??``,condition:{field:`kind`,eq:`local`}}],submit:{label:`Save`,action_id:`setup_identity`}}]}]}function rn(e,t){let{site:n}=e.config,r=J(n),i=Lt.find(e=>e.value===n.type)?.label,a=r===`person`?`a person`:r===`local`?(i??`a local business`).toLowerCase():`a company`,o=[n.address?.streetAddress,n.address?.addressLocality].filter(Boolean).join(`, `),s=Ht(n);return[`Google is told: ${n.name??t}, ${a}`,o?`, ${o}`:``,s.length>0?`, open ${s.join(`, `)}`:``,`. Change this in the Settings tab.`].join(``)}function an(e,t){if(!e.config.steps.identity)return[];let n=Ut(e);return n.length===0?[{type:`context`,text:rn(e,t)}]:[{type:`header`,text:`Your details`},{type:`context`,text:`${n.length} ${n.length===1?`thing`:`things`} Google needs from you. Add ${n.length===1?`it`:`them`} in the Settings tab.`},{type:`table`,page_action_id:`details_table`,columns:[{key:`what`,label:`Missing`},{key:`why`,label:`Why it matters`}],rows:n}]}function on(e,t){return[...Zt(e),...tn(e,!0),...nn(e,t),...an(e,t),...Qt(e),...$t(e)]}function sn(e){let t=Xt(e),n={type:`actions`,elements:[{type:`link`,label:`Open SEO`,target:{kind:`plugin-page`,path:`/overview`}}]};if(!t||t.checked===0){let{steps:t}=e.config;return{blocks:[{type:`section`,text:t.identity?`Your pages are being checked.`:`One thing left: tell Google who this site is for.`},n]}}return{blocks:[{type:`stats`,items:[t.fix===0?{label:`SEO`,value:`All pages ready`}:{label:t.fix===1?`Page to fix`:`Pages to fix`,value:t.fix}]},n]}}function cn(e,t){let{site:n}=e.config,r=J(n),i=r===`local`,a=e.config.steps.identity,s=Bt(n.sameAs),c=[{type:`context`,text:`Google reads these details from your site and can show them beside your pages. Sections marked needed are followed up on the Overview; the rest are up to you.`},{type:`form`,block_id:`identity`,fields:[{type:`radio`,action_id:`kind`,label:`This site is for`,options:[{label:`A local business customers visit (shop, cafe, clinic)`,value:`local`},{label:`A company or organisation`,value:`organization`},{label:`A person`,value:`person`}],...a?{initial_value:r}:{}},{type:`select`,action_id:`business`,label:`Kind of business`,options:[{label:`Choose the closest`,value:``},...Lt],initial_value:i?n.type:``,condition:{field:`kind`,eq:`local`}},{type:`text_input`,action_id:`name`,label:`Name`,placeholder:`Your name or your business's name`,initial_value:n.name??t},{type:`text_input`,action_id:`description`,label:`What you do, in one or two sentences`,multiline:!0,initial_value:n.description??``}],submit:{label:`Save`,action_id:`save_identity`}},{type:`context`,text:`Each section below is saved with its own Save button.`}],l={type:`accordion`,label:q(`Address and phone`,i?`needed`:`optional`,[n.address?.streetAddress,n.address?.addressLocality,n.telephone,n.email]),default_open:i&&Ut(e).some(e=>e.what!==`Opening hours`),blocks:[{type:`context`,text:i?`Google needs an address before it treats you as a local business. Use the same details as your Google Business Profile, which is what puts you on the map.`:`Leave the address empty if you have no public one.`},{type:`form`,block_id:`contact`,fields:[{type:`text_input`,action_id:`street`,label:`Street address`,initial_value:n.address?.streetAddress??``},{type:`text_input`,action_id:`town`,label:`Town or city`,initial_value:n.address?.addressLocality??``},{type:`text_input`,action_id:`region`,label:`State or region`,initial_value:n.address?.addressRegion??``},{type:`text_input`,action_id:`postcode`,label:`Postcode`,initial_value:n.address?.postalCode??``},{type:`text_input`,action_id:`country`,label:`Country code (GB, US, IN, AU)`,initial_value:n.address?.addressCountry??``},{type:`text_input`,action_id:`telephone`,label:`Phone`,initial_value:n.telephone??``},{type:`text_input`,action_id:`email`,label:`Email`,initial_value:n.email??``},{type:`select`,action_id:`price_range`,label:`Price range`,options:Rt.map(e=>({label:e===``?`Not shown`:e,value:e})),initial_value:n.priceRange??``},{type:`text_input`,action_id:`areas`,label:`Areas you serve, one per line (if you travel to customers)`,multiline:!0,initial_value:n.areaServed.join(`
3
+ `)}],submit:{label:`Save address and phone`,action_id:`save_contact`}}]},u=i?{type:`accordion`,label:q(`Opening hours`,`needed`,Ht(n)),blocks:[{type:`context`,text:`Type the hours for each day, like 9:00-17:00. For a break in the day use two ranges: 12:00-14:30, 18:00-22:00. A day left empty is shown as closed.`},{type:`form`,block_id:`hours`,fields:A.map(e=>({type:`text_input`,action_id:`hours_${e.toLowerCase()}`,label:e,placeholder:`Closed`,initial_value:re(e,n.openingHours)})),submit:{label:`Save opening hours`,action_id:`save_hours`}}]}:{type:`accordion`,label:`Opening hours (only for a local business)`,blocks:[{type:`context`,text:`Opening hours are for a place customers visit. Choose "A local business customers visit" above and save, and this section opens up.`}]},d={type:`accordion`,label:q(`Logo`,r===`organization`?`needed`:`worth`,[n.logo?`added`:void 0]),blocks:[{type:`context`,text:`Google can show your logo beside your name. In Media, open your logo and use Copy URL, then paste it here. Use a square image at least 112 pixels wide.`},{type:`form`,block_id:`logo`,fields:[{type:`text_input`,action_id:`logo_url`,label:`Logo link`,placeholder:`https://example.com/logo.png`,initial_value:n.logo??``}],submit:{label:`Save logo`,action_id:`save_logo`}}]},f={type:`accordion`,label:q(`Your profiles elsewhere`,i?`worth`:`needed`,n.sameAs.map(e=>o(e)?.host.replace(/^www\./,``))),blocks:[{type:`context`,text:`Links to your pages on other sites help Google connect them to you. Paste the full address of each profile you have.`},{type:`form`,block_id:`profiles`,fields:[...zt.map(e=>({type:`text_input`,action_id:`profile_${e.id}`,label:e.label,placeholder:`https://${e.hosts[0]}/yourname`,initial_value:s.named[e.id]??``})),{type:`text_input`,action_id:`profiles`,label:`Other profiles, one per line`,multiline:!0,initial_value:s.other.join(`
4
+ `)}],submit:{label:`Save profiles`,action_id:`save_profiles`}}]},p={type:`accordion`,label:q(`More about you`,`optional`,[n.alternateName,n.legalName,n.foundingDate?`founded ${n.foundingDate}`:void 0]),blocks:[{type:`context`,text:`Nothing here is needed. Skip anything that does not apply.`},{type:`form`,block_id:`company`,fields:[{type:`text_input`,action_id:`alternate_name`,label:`Also known as (a short name or abbreviation)`,initial_value:n.alternateName??``},{type:`text_input`,action_id:`legal_name`,label:`Registered legal name`,initial_value:n.legalName??``},{type:`text_input`,action_id:`founded`,label:`Founded (year, or YYYY-MM-DD)`,placeholder:`2012`,initial_value:n.foundingDate??``}],submit:{label:`Save`,action_id:`save_company`}}]},m=i?[l,u,d,f,p]:r===`organization`?[d,f,l,p,u]:[f,d,l,p,u];return c.push(...m),c}function ln(e,t){return e.config.collections[t]?.type??f(t)}function un(e,t){let{config:n}=e,r=e.collections.find(e=>e.slug===t),i=[{type:`context`,text:`Google is told what each kind of content is, so it can show it properly: a blog post with its author and date, a service as a service. These were worked out from the names and are usually right. Every kind of content that has its own pages is listed here; content that only appears inside other pages, such as testimonials, is not. To change a single page (an About or Contact page, say), open it and use Change page type in its Google preview panel. Picking from a menu saves straight away.`}];return r&&i.push({type:`banner`,variant:`alert`,title:`Hide every page in ${r.label} from Google?`,description:`People will no longer find these pages through search engines. You can switch this back at any time.`},{type:`actions`,elements:[{type:`button`,action_id:`confirm_hide`,label:`Yes, hide them`,style:`danger`,value:r.slug},{type:`button`,action_id:`cancel_hide`,label:`Cancel`}]}),i.push({type:`table`,page_action_id:`types_table`,empty_text:`This site has no content types with pages yet.`,columns:[{key:`name`,label:`Content type`},{key:`shown`,label:``,format:`element`},{key:`visible`,label:``,format:`element`}],rows:e.collections.map(t=>{let r=n.collections[t.slug]?.noindex===!0;return{name:t.label,shown:{type:`menu`,action_id:`set_type`,label:`Google treats these as: ${M(ln(e,t.slug))}`,items:j.map(({value:e,label:n})=>({label:n,value:`${t.slug}|${e}`}))},visible:{type:`menu`,action_id:`set_visible`,label:r?`Hidden from Google`:`Can appear on Google`,items:[{label:`Can appear on Google`,value:`${t.slug}|show`},{label:`Hide from Google and other search engines`,value:`${t.slug}|hide`}]}}})},{type:`form`,block_id:`breadcrumbs`,fields:[{type:`toggle`,action_id:`breadcrumbs`,label:`Show the path to each page on Google (Home > Services > Cakes)`,description:`Leave on unless your theme already does this.`,initial_value:n.breadcrumbs}],submit:{label:`Save`,action_id:`save_advanced`}}),[{type:`accordion`,label:`Content types (usually right as they are): ${e.collections.map(t=>{let r=n.collections[t.slug]?.noindex===!0;return`${t.label} as ${M(ln(e,t.slug))}${r?`, hidden`:``}`}).join(`; `)}`,default_open:r!==void 0,blocks:i}]}function X(e,t,n,r){let i=[{type:`header`,text:`SEO`},{type:`context`,text:`How your site appears on Google and other search engines.`}];return n?{blocks:[...i,{type:`tab`,default_tab:r.tab,panels:[{label:`Overview`,blocks:on(e,t.site.name)},{label:`Redirects`,blocks:Re({state:e.redirects,rows:e.redirectRows,more:e.moreRedirects,editing:r.editing,panel:r.panel,problem:r.problem,exported:r.exported,managerUrl:Wt(t.site.url,`/_emdash/admin/redirects`)})},{label:`Settings`,blocks:[...cn(e,t.site.name),...un(e,r.pendingHide),D(e.config.hideHelpLine)]}]},...k(!e.config.hideHelpLine)],...r.toast?{toast:r.toast}:{}}:{blocks:[...i,...Zt(e),...tn(e,!1),...Qt(e),{type:`context`,text:`Business details, opening hours and redirects are set by your site admin. Ask them if something there is wrong.`}]}}function dn(e,t){if(e===``)return{};let n=o(e,`${t.replace(/\/+$/,``)}/`);return!n||n.protocol!==`https:`&&n.protocol!==`http:`?{error:`Couldn't save. The logo link must be a web address starting with https://`}:n.pathname.includes(`/_emdash/api/media/asset/`)?{error:`That link only works when signed in. In Media, use Copy URL on the image instead.`}:{logo:n.href}}function fn(e,t){let n={tab:K.identity},{site:r}=e.config,i=Y(t,`kind`,e.config.steps.identity?J(r):``);if(i!==`local`&&i!==`organization`&&i!==`person`)return{error:`Choose who this site is for.`,state:n};let a=Y(t,`business`,J(r)===`local`?r.type:``),o=Lt.find(e=>e.value===a)?.value;if(i===`local`&&!o)return{error:`Choose the kind of business.`,state:n};let s=i===`person`?`Person`:i===`local`?o??`LocalBusiness`:`Organization`,c=Y(t,`name`,r.name??``);return{config:{...e.config,site:{...r,type:s,name:c===``?void 0:c,description:Y(t,`description`,r.description??``)||void 0,openingHours:i===`local`?r.openingHours:[]},steps:{...e.config.steps,identity:!0}},state:n}}function pn(e,t,n){let r={tab:K.identity},i=dn(Y(t,`logo_url`,e.config.site.logo??``),n);return`error`in i?{error:i.error,state:r}:{config:{...e.config,site:{...e.config.site,logo:i.logo}},state:r}}function mn(e,t){let{site:n}=e.config,r={streetAddress:Y(t,`street`)||void 0,addressLocality:Y(t,`town`)||void 0,addressRegion:Y(t,`region`)||void 0,postalCode:Y(t,`postcode`)||void 0,addressCountry:Y(t,`country`).toUpperCase()||void 0},i=Y(t,`price_range`);return{config:{...e.config,site:{...n,email:Y(t,`email`)||void 0,telephone:Y(t,`telephone`)||void 0,address:Object.values(r).some(e=>e!==void 0)?r:void 0,priceRange:Rt.includes(i)&&i!==``?i:void 0,areaServed:qt(Y(t,`areas`,n.areaServed.join(`
5
+ `)))}},state:{tab:K.identity}}}function hn(e,t){let n={tab:K.identity},r=[];for(let i of A){let a=ne(i,Y(t,`hours_${i.toLowerCase()}`,re(i,e.config.site.openingHours)));if(!a)return{error:`Couldn't read the hours for ${i}. Write them like 9:00-17:00, with at most two ranges.`,state:n};r.push(...a)}return{config:{...e.config,site:{...e.config.site,openingHours:r}},state:n}}function gn(e,t){let n={tab:K.identity},r=Bt(e.config.site.sameAs),i=[...zt.map(e=>Y(t,`profile_${e.id}`,r.named[e.id]??``)),...qt(Y(t,`profiles`,r.other.join(`
6
+ `)))].filter(e=>e!==``),a=[];for(let e of i){let t=o(/^https?:\/\//i.test(e)?e:`https://${e}`);if(!t||!t.host.includes(`.`))return{error:`"${e}" is not a web address. Paste the full link to the profile.`,state:n};a.push(t.href)}return{config:{...e.config,site:{...e.config.site,sameAs:a}},state:n}}function _n(e,t){let n={tab:K.identity},{site:r}=e.config,i=Y(t,`founded`,r.foundingDate??``);return i!==``&&!/^\d{4}(-\d{2}-\d{2})?$/.test(i)?{error:`Write the founding date as a year, like 2012, or as 2012-05-01.`,state:n}:{config:{...e.config,site:{...r,alternateName:Y(t,`alternate_name`,r.alternateName??``)||void 0,legalName:Y(t,`legal_name`,r.legalName??``)||void 0,foundingDate:i||void 0}},state:n}}function vn(e,t){let{robots:n}=e.config,r=Y(t,`snippet`,n.nosnippet?`none`:`full`),i=Y(t,`image_preview`,n.maxImagePreview);return{config:{...e.config,breadcrumbs:Kt(t,`breadcrumbs`,e.config.breadcrumbs),robots:{...n,nosnippet:r===`none`,maxSnippet:r===`short`?160:-1,maxImagePreview:i===`none`||i===`standard`||i===`large`?i:n.maxImagePreview}},state:{tab:K.types}}}function yn(e,t,n){let r={tab:K.types},[i,a]=typeof t==`string`?t.split(`|`):[];if(!i||!e.collections.some(e=>e.slug===i))return{error:`That content type no longer exists.`,state:r};let o=e.config.collections[i]??{},s=n===`type`?j.find(e=>e.value===a)?.value:o.type??ln(e,i);if(!s)return{error:`Choose one of the listed types.`,state:r};let c=n===`visible`?a===`hide`:o.noindex===!0;return{config:{...e.config,collections:{...e.config.collections,[i]:{type:s,noindex:c}},steps:{...e.config.steps,types:!0}},state:r}}async function bn(e,t){if(t.scan?.status===`done`)return Yt(e,t);let n=t.scan?await Mt(e,t.scan,t.config,6):await Dt(e,t.collections.map(e=>e.slug),t.config,6);return{...t,scan:n,stats:n.status===`done`?n.tally:null}}async function xn(e,t,n){let r=(t??0)>=50,i=Gt(e)?e:{},o=typeof i.action_id==`string`?i.action_id:``,c=await Jt(n,{rows:!(r&&i.type===`block_action`&&(o===`add_redirects`||o===`more_redirects`||o===`remove_redirects`))});if(i.type===`page_load`&&i.page===`widget:seo`)return sn(await bn(n,c));if(!r||i.type===`page_load`||o===``)return X(await bn(n,c),n,r,{tab:K.overview});let l=async(e,t=`Saved. Google will pick this up next time it visits.`)=>{if(`error`in e)return X(c,n,r,{...e.state,toast:{type:`error`,message:e.error}});let i=S(e.config);return await a(n,s,i),It(n.plugin.id),X({...c,config:i},n,r,{...e.state,toast:{type:`success`,message:t}})},u=async e=>{try{let{rows:t,more:r}=await Ae(n);return{...e,redirectRows:t,moreRedirects:r}}catch{return e}},d=async(e,t,i)=>e?X(c,n,r,{tab:K.redirects,editing:i,...i?{}:{panel:`add`},problem:e}):X(await u(c),n,r,{tab:K.redirects,toast:{type:`success`,message:t}});if(i.type===`block_action`){if(o===`check_pages`||o===`check_more`){let e=await bn(n,o===`check_pages`?{...c,scan:null}:c);return X(e,n,r,{tab:K.overview,toast:e.scan?.status===`done`?{type:`success`,message:`All pages checked.`}:{type:`info`,message:`${e.scan?.checked??0} pages checked so far. The rest follow in the background.`}})}if(o===`set_type`)return l(yn(c,i.value,`type`));if(o===`set_visible`){let[e,t]=typeof i.value==`string`?i.value.split(`|`):[];return t===`hide`?X(c,n,r,{tab:K.types,pendingHide:e}):l(yn(c,i.value,`visible`),`These pages can appear on Google again.`)}if(o===`confirm_hide`&&typeof i.value==`string`)return l(yn(c,`${i.value}|hide`,`visible`),`These pages are now hidden from Google and other search engines.`);if(o===`cancel_hide`)return X(c,n,r,{tab:K.types});if(o===`hidden_ok`){let e={...c.config.collections};for(let t of en(c))e[t.slug]={...e[t.slug],hiddenOk:!0};return l({config:{...c.config,collections:e},state:{tab:K.overview}},`Noted. These pages stay hidden from search.`)}if(o===`edit_redirect`){let e=c.redirectRows.find(e=>e.id===i.value);return X(c,n,r,{tab:K.redirects,editing:e})}if(o===`open_add`)return X(c,n,r,{tab:K.redirects,panel:`add`});if(o===`open_import`)return X(c,n,r,{tab:K.redirects,panel:`import`});if(o===`close_panel`)return X(c,n,r,{tab:K.redirects});if(o===`remove_redirect`&&typeof i.value==`string`)return d(await Fe(n,i.value),`Redirect removed.`);if(o===`add_redirects`||o===`more_redirects`){let e=c.redirects;if(!e||e.status!==`checked`&&e.status!==`adding`)return X(await u(c),n,r,{tab:K.redirects});let t=await Ce(n,e,6);return X(await u({...c,redirects:t}),n,r,{tab:K.redirects,toast:t.status===`done`?{type:`success`,message:`${I(t.added,`redirect`)} added.`}:{type:`info`,message:`${t.added} added so far. The rest follow in the background, a few each minute.`}})}if(o===`stop_redirects`&&c.redirects?.status===`removing`){let e=c.redirects.removed??0,t=await Te(n,c.redirects);return X(await u({...c,redirects:t}),n,r,{tab:K.redirects,toast:{type:`info`,message:`Stopped. ${I(e,`redirect`)} removed.`}})}if(o===`stop_redirects`&&c.redirects?.status===`adding`){let e=await ze(n,c.redirects);return X({...c,redirects:e},n,r,{tab:K.redirects,toast:{type:`info`,message:`Stopped. ${I(e.added,`redirect`)} added.`}})}if(o===`reset_redirects`){let e=c.redirects?.status===`adding`||c.redirects?.status===`removing`;return e||await n.kv.delete(P),X({...c,redirects:e?c.redirects:null},n,r,{tab:K.redirects})}if(o===`remove_redirects`&&(c.redirects?.status===`done`||c.redirects?.status===`removing`)){let e=await we(n,c.redirects,6);return X(await u({...c,redirects:e}),n,r,{tab:K.redirects,toast:e?e.status===`done`?{type:`error`,message:`EmDash would not remove some of them. Press Remove to try again, or remove them in the full Redirects screen.`}:{type:`info`,message:`${e.removed??0} removed so far. The rest are removed in the background, a few each minute.`}:{type:`success`,message:`Removed. Your other redirects are untouched.`}})}if(o===`remove_redirects`)return X(await u(c),n,r,{tab:K.redirects});if(o===`export_redirects`){let{text:e,count:t}=await Ee(n);return X(c,n,r,{tab:K.redirects,panel:`export`,exported:e,toast:{type:`info`,message:`${I(t,`redirect`)} listed below.`}})}return X(c,n,r,{tab:K.overview})}if(i.type!==`form_submit`)return X(c,n,r,{tab:K.overview});let f=Gt(i.values)?i.values:{};if(o===`check_redirects`){let e=Y(f,`list`);if(e===``)return X(c,n,r,{tab:K.redirects,toast:{type:`error`,message:`Paste a list first.`}});let t=await ye(n,e);return X({...c,redirects:t},n,r,{tab:K.redirects,toast:{type:`success`,message:`Checked. Nothing has been added yet.`}})}if(o===`add_redirect`){let e=Y(f,`source`),t=Y(f,`code`,`301`)===`410`?``:Y(f,`destination`);return d(await Ne(n,e,t,Y(f,`code`,`301`)),t===``?`Done. ${e} now tells visitors the page was removed.`:`Redirect added. ${e} now goes to ${t}.`)}if(o.startsWith(`save_redirect:`)){let e=o.slice(14),t=c.redirectRows.find(t=>t.id===e);return d(await Pe(n,e,Y(f,`source`,t?.source??``),Y(f,`destination`,t?.destination??``),Y(f,`code`,String(t?.type??301))),`Redirect changed.`,t)}if(o===`setup_identity`){let e=fn(c,f);if(`error`in e)return l(e);let t=Y(f,`street`),n=Y(f,`town`),{site:r}=e.config,i=t!==``||n!==``?{...r.address,...t?{streetAddress:t}:{},...n?{addressLocality:n}:{}}:r.address;return l({config:{...e.config,site:{...r,address:i}},state:{tab:K.overview}})}if(o===`save_identity`)return l(fn(c,f));if(o===`save_logo`)return l(pn(c,f,n.site.url));if(o===`save_contact`)return l(mn(c,f));if(o===`save_hours`)return l(hn(c,f));if(o===`save_profiles`)return l(gn(c,f));if(o===`save_company`)return l(_n(c,f));if(o===`save_advanced`)return l(vn(c,f));if(o===T){let e=O(f,c.config.hideHelpLine);return l({config:{...c.config,hideHelpLine:e},state:{tab:K.types}},e?`Saved. The help line is hidden.`:`Saved. The help line shows again.`)}return X(c,n,r,{tab:K.overview})}function Z(e){return{"@id":e}}function Q(e){let t={};for(let[n,r]of Object.entries(e))r==null||r===``||Array.isArray(r)&&r.length===0||(t[n]=r);return t}function Sn(e,t){return o(e,t)?.href??e}function Cn(e,t){return(e.siteUrl||t||(o(e.url)?.origin??``)).replace(/\/+$/,``)}function wn(e,t){if(e.canonical)return Sn(e.canonical,`${t}/`);let n=o(e.url,`${t}/`);return n?(n.search=``,n.hash=``,n.href):e.url}function Tn(e){return e.path===`/`||e.path===``}function En(e){return u.includes(e)}function Dn(e,t,n){let r=e.content?t.collections[e.content.collection]:void 0;if(n?.type)return n.type;if(r?.type)return r.type;let i=e.content?f(e.content.collection):`WebPage`;return i===`WebPage`?e.pageType===`article`?`BlogPosting`:`WebPage`:i}function On(e,t,n,r){let i=`${n}/#logo`,a=e.type===`Person`,o=!a&&e.type!==`Organization`&&e.address===void 0?`Organization`:e.type,s=!a&&(e.address!==void 0||e.geo!==void 0||e.openingHours.length>0);return Q({"@type":o,"@id":r,name:t,alternateName:e.alternateName,legalName:a?void 0:e.legalName,description:e.description,url:`${n}/`,logo:e.logo&&!a?{"@type":`ImageObject`,"@id":i,url:e.logo,contentUrl:e.logo,caption:t}:void 0,image:e.logo?a?e.logo:Z(i):void 0,sameAs:e.sameAs,email:e.email,telephone:e.telephone,address:e.address?{"@type":`PostalAddress`,...e.address}:void 0,geo:e.geo&&!a?{"@type":`GeoCoordinates`,...e.geo}:void 0,openingHoursSpecification:s?e.openingHours.map(e=>({"@type":`OpeningHoursSpecification`,dayOfWeek:e.days,opens:e.opens,closes:e.closes})):void 0,priceRange:a?void 0:e.priceRange,areaServed:a?void 0:e.areaServed,foundingDate:a?void 0:e.foundingDate,vatID:a?void 0:e.vatID,taxID:a?void 0:e.taxID,numberOfEmployees:!a&&e.numberOfEmployees?{"@type":`QuantitativeValue`,value:e.numberOfEmployees}:void 0})}function kn(e){let t=e;try{t=decodeURIComponent(e)}catch{}let n=t.replace(/[-_]+/g,` `).trim();return n.charAt(0).toUpperCase()+n.slice(1)}function An(e,t,n){let{page:r,config:i,site:a}=e;if(r.breadcrumbs)return r.breadcrumbs.map(e=>({name:e.name,url:Sn(e.url,`${t}/`)}));if(!i.breadcrumbs||Tn(r))return[];let o=r.path.split(`/`).filter(e=>e!==``);if(o.length===0)return[];let s=a.trailingSlash===`always`?`/`:``,c=[{name:`Home`,url:`${t}/`}],l=``;return o.forEach((e,r)=>{l+=`/${e}`;let i=r===o.length-1;c.push({name:i?n:kn(e),url:`${t}${l}${s}`})}),c}function jn(e,t){return e.length<=t?e:`${e.slice(0,t-1).trimEnd()}…`}function Mn(e,t,n){let r;for(let{"@context":i,...a}of t){let t=typeof a[`@id`]==`string`?a[`@id`]:void 0;if(t!==void 0){if(n.has(t))continue;n.add(t),r??=t}e.push(a)}return r}function Nn(e){let{page:t,config:n,record:r,site:i}=e,a=Cn(t,i.url),o=wn(t,a),s=t.locale??i.locale,c=n.site.name??t.siteName??i.name,l=t.pageTitle??t.title??c,u=n.site.type===`Person`,d=`${a}/#${u?`person`:`organization`}`,f=`${a}/#website`,p=o,m=`${o}#breadcrumb`,h=Dn(t,n,r),g=En(h),_=h===`Service`,v=h===`ProfilePage`,y=`${o}#service`,b=`${o}#person`,x=r?.publishedAt??t.articleMeta?.publishedTime??void 0,S=r?.updatedAt??t.articleMeta?.modifiedTime??void 0,C=t.image?Sn(t.image,`${a}/`):void 0,w=C??``,T=An(e,a,l),E=[On(n.site,c,a,d),Q({"@type":`WebSite`,"@id":f,url:`${a}/`,name:c,alternateName:n.site.alternateName,description:n.site.description,publisher:Z(d),inLanguage:s})];if(e.noindex)return E;let D=Q({"@type":g||_?`WebPage`:h,"@id":p,url:o,name:l,description:t.description,isPartOf:Z(f),about:Tn(t)?Z(d):void 0,primaryImageOfPage:C?Z(w):void 0,image:C?Z(w):void 0,breadcrumb:T.length>0?Z(m):void 0,mainEntity:_?Z(y):v?Z(b):void 0,datePublished:x,dateModified:S,inLanguage:s});if(E.push(D),g){let e=(r?.authors??[]).map(e=>Q({"@type":`Person`,name:e.name,url:e.url})),n=t.articleMeta?.author?[{"@type":`Person`,name:t.articleMeta.author}]:[Z(d)];E.push(Q({"@type":h,"@id":`${o}#article`,isPartOf:Z(p),mainEntityOfPage:Z(p),headline:jn(l,110),description:t.description,image:C?Z(w):void 0,datePublished:x,dateModified:S,author:e.length>0?e:n,publisher:Z(d),inLanguage:s}))}_&&E.push(Q({"@type":`Service`,"@id":y,name:l,description:t.description,url:o,image:C?Z(w):void 0,provider:Z(d),areaServed:n.site.areaServed})),v&&E.push(Q({"@type":`Person`,"@id":b,name:l,description:t.description,url:o,image:C?Z(w):void 0,worksFor:u?void 0:Z(d)})),C&&E.push({"@type":`ImageObject`,"@id":w,url:C,contentUrl:C,...ot(r?.images,C,a)}),T.length>0&&E.push({"@type":`BreadcrumbList`,"@id":m,itemListElement:T.map((e,t)=>Q({"@type":`ListItem`,position:t+1,name:e.name,item:t===T.length-1?void 0:e.url}))});let O=new Set([`${a}/#logo`]);for(let e of E)typeof e[`@id`]==`string`&&O.add(e[`@id`]);let k=Mn(E,r?.custom??[],O);return k!==void 0&&!g&&D.mainEntity===void 0&&(D.mainEntity=Z(k)),E}const Pn=`default`;function Fn(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function In(e){let t=o(e);if(!t)return e;let n=t.pathname.split(`/`).filter(e=>e!==``);return[t.host,...n].join(` › `)}function Ln(e,t){return e.length<=t?e:`${e.slice(0,t-1).trimEnd()}…`}async function Rn(e,t,n,r){if(!t)return{blocks:[]};let i=(n??0)>=50,a=Fn(e)?e:{},s=await c(r),l=i&&a.type===`block_action`&&a.action_id===`set_type`,u=typeof a.value==`string`?a.value:Pn,d=j.find(e=>e.value===u)?.value,[p,m]=await Promise.all([xt(r,t.collection,t.id,s,l?e=>{let{type:t,...n}=e;return d?{...n,type:d}:n}:void 0),r.content?.getPublicUrl?.(t.collection,t.id)]),h=p?.record,g=p?.entry,_=h&&l?{type:`success`,message:`Saved. Google will pick this up next time it visits.`}:void 0;if(!h||!g)return{blocks:[{type:`context`,text:`Save this page to see how it may look on Google.`}]};if(h.status===`skip`)return{blocks:[{type:`context`,text:`This kind of content has no page of its own, so Google only sees it inside other pages. There is nothing to set here.`}]};let v=s.site.name??r.site.name,y=r.site.url.replace(/\/+$/,``),b=[`home`,`index`,``].includes(g.slug??``)?`${y}/`:`${y}/${g.slug}`,x=[`home`,`index`].includes(g.slug??``)?`${y}/`:m??b,S=h.title||`(no title)`,C=g.seo?.description?.trim()??``,w=C===``&&Je(g.data)!==``,T=C||Je(g.data),E=s.collections[t.collection]?.type??f(t.collection),D=h.type??E,O=h.status===`hidden`,k=h.issues??[],A=[{type:`context`,text:v?`${v} · ${In(x)}`:In(x)},{type:`header`,text:Ue(S)},{type:`section`,text:T===``?`No description yet. Google will pick some text from the page.`:Ln(T,160)},...w?[{type:`context`,text:`This description is the page's excerpt. To write a different one for search, fill in Meta Description in the SEO panel.`}]:[],{type:`context`,text:`What Google sees: the published page, with the SEO title and description as last saved. Google may rewrite them, and your site may add its name to the title.`},{type:`fields`,fields:[{label:`Shown to Google as`,value:M(D)},{label:`In search results`,value:h.status===`draft`?`Not published yet`:O?`Hidden from search`:`Yes`}]}];if(k.length===0)A.push({type:`context`,text:`Nothing to fix on this page.`});else{let e=[...k].sort((e,t)=>Number(t.level===`fix`)-Number(e.level===`fix`));for(let t of e.slice(0,3))A.push({type:`section`,text:`${t.level===`fix`?`To fix`:`Worth a look`}: ${t.message}`},{type:`context`,text:t.fix});e.length>3&&A.push({type:`context`,text:`And ${e.length-3} more.`})}if(i){let e=Nn({page:{url:x,path:o(x)?.pathname??`/`,locale:g.locale,kind:`content`,pageType:`website`,title:S,pageTitle:S,description:T===``?null:T,canonical:g.seo?.canonical??null,image:null,content:{collection:t.collection,id:t.id,slug:g.slug}},config:s,record:h,noindex:O,site:r.site});A.push({type:`divider`},{type:`actions`,elements:[{type:`menu`,action_id:`set_type`,label:`Change page type`,items:[{label:`Same as other pages here (${M(E)})`,value:Pn},...j.map(({value:e,label:t})=>({value:e,label:t}))]},.../^https?:\/\//.test(x)?[{type:`link`,label:`Test on Google`,target:{kind:`external`,url:`https://search.google.com/test/rich-results?url=${encodeURIComponent(x)}`}}]:[]]},{type:`accordion`,label:`View the markup`,blocks:[{type:`context`,text:`The schema markup (JSON-LD) this page sends to search engines.`},{type:`code`,language:`jsonc`,code:JSON.stringify({"@context":`https://schema.org`,"@graph":e},null,2)}]})}return{blocks:A,..._?{toast:_}:{}}}const zn=/\s*,\s*/;function Bn(e,t,n){let r=(e??``).toLowerCase().split(zn).map(e=>e.trim()).filter(e=>e!==``),i=t||r.includes(`noindex`)||r.includes(`none`),a=r.includes(`nofollow`)||r.includes(`none`),o=[i?`noindex`:`index`,a?`nofollow`:`follow`],s=new Set([`index`,`noindex`,`follow`,`nofollow`,`none`,`all`]),c=r.filter(e=>!s.has(e));if(!i){let e=e=>c.some(t=>t.startsWith(e));n.nosnippet||c.includes(`nosnippet`)?c.includes(`nosnippet`)||c.push(`nosnippet`):e(`max-snippet:`)||c.push(`max-snippet:${n.maxSnippet}`),e(`max-image-preview:`)||c.push(`max-image-preview:${n.maxImagePreview}`),e(`max-video-preview:`)||c.push(`max-video-preview:${n.maxVideoPreview}`)}return[...o,...new Set(c)].join(`, `)}function Vn(e){return e.startsWith(`noindex`)}function Hn(e){return/[?&]_preview=/.test(e)}async function $(e,t,n){let r=n?.id;if(typeof r==`string`)try{await gt(e,t,r,await c(e))}catch(n){e.log.warn(`could not refresh entry record`,{collection:t,id:r,error:String(n)})}}const Un={hooks:{"page:metadata":async(e,t)=>{let{page:n}=e,r=n.content,[i,a]=await Promise.all([Ft(t,()=>c(t),{fresh:Hn(n.url)}),(async()=>{if(!r)return null;try{return{stored:await z(t).get(R(r.collection,r.id))}}catch(e){return{error:e}}})()]),o=null;if(r&&a){let{collection:e,id:s}=r;try{if(`error`in a)throw a.error;let{stored:r}=a,c=z(t);o=r??await vt(t,e,s,i);let l=!!(n.seo?.ogImage||n.image);if(r&&ft(r,l)){let t=await c.get(R(e,s)),n=t?ft(t,l):null;n&&await c.put(R(e,s),n),o=n??t??r}}catch(n){t.log.warn(`could not load entry record`,{collection:e,id:s,error:String(n)})}}let s=n.content?i.collections[n.content.collection]:void 0,l=Bn(n.seo?.robots,s?.noindex===!0,i.robots);return[{kind:`meta`,name:`robots`,content:l},{kind:`jsonld`,id:`primary`,graph:{"@context":`https://schema.org`,"@graph":Nn({page:n,config:i,record:o,noindex:Vn(l),site:t.site})}}]},"content:afterSave":async(e,t)=>{await $(t,e.collection,e.content)},"content:afterPublish":async(e,t)=>{await $(t,e.collection,e.content)},"content:afterUnpublish":async(e,t)=>{await $(t,e.collection,e.content)},"content:afterRestore":async(e,t)=>{await $(t,e.collection,e.content)},"plugin:activate":async(e,t)=>{try{let[e,n]=await Promise.all([t.kv.list(),t.schema?.listCollections()??[]]),i=new Map(e.map(e=>[e.key,e.value]));if(i.get(U))return;let a=S(await r(t,i,s,{copy:!1})),o=await Dt(t,n.filter(e=>e.routable&&!e.hidden).map(e=>e.slug),a,7);o.status===`done`&&o.checked===0&&await t.kv.delete(U)}catch(e){t.log.warn(`could not start the first page check`,{error:String(e)})}},cron:async(e,t)=>{if(e.name===W){let e=await Et(t);e?.status===`running`||e?.sweep?await Mt(t,e,await c(t)):await t.cron?.cancel(W);return}if(e.name!==F)return;let n=await _e(t);n?.status===`adding`?await Ce(t,n):n?.status===`removing`?await we(t,n):await t.cron?.cancel(F)},"content:afterDelete":async(e,t)=>{await Ct(t,e.collection,e.id)}},routes:{admin:{permission:`plugins:read`,handler:async(e,t)=>xn(e.input,e.user?.role,t)},"editor/preview":{permission:`content:read`,handler:async(e,t)=>Rn(e.input,e.ui?.entry,e.user?.role,t)},hello:{handler:async(e,t)=>(t.log.info(`hello route called`,{pluginId:t.plugin.id}),{greeting:`hello`,pluginId:t.plugin.id})}}};export{Un as default};
@@ -18,23 +18,28 @@
18
18
  "description": { "file": "listing/description.md" },
19
19
  "installation": { "file": "listing/installation.md" },
20
20
  "faq": { "file": "listing/faq.md" },
21
+ "security": { "file": "listing/security.md" },
21
22
  },
22
23
  // Registry listing only: the EmDash version a site needs, and the gallery.
23
24
  "release": {
24
25
  "requires": { "env:emdash": ">=1.2.0" },
25
26
  "artifacts": {
27
+ // This plugin's own glyph on the Moorage land tile, from icon.svg (scripts/render-banners.mjs in apps/demo-site).
28
+ "icon": { "file": "./images/icon.png" },
29
+ // Cover at the top of the listing and its share image: 3:1, from banner.svg (scripts/render-banners.mjs in apps/demo-site).
30
+ "banner": { "file": "./images/banner.png" },
26
31
  "screenshots": [
27
- { "file": "./images/overview.png", "lang": "en" },
28
- { "file": "./images/google-preview.png", "lang": "en" },
29
- { "file": "./images/redirects.png", "lang": "en" },
30
- { "file": "./images/settings.png", "lang": "en" },
32
+ { "file": "./images/overview-0-1-11.png", "lang": "en" },
33
+ { "file": "./images/google-preview-0-1-11.png", "lang": "en" },
34
+ { "file": "./images/redirects-0-1-11.png", "lang": "en" },
35
+ { "file": "./images/settings-0-1-11.png", "lang": "en" },
31
36
  ],
32
37
  },
33
38
  },
34
39
  "publisher": "did:plc:cus3srdqglppyowqrw3hvjfj",
35
40
 
36
41
  "license": "MIT",
37
- "author": { "name": "Moorage" },
42
+ "author": { "name": "Moorage", "url": "https://moorage.dev" },
38
43
  "security": { "email": "security@moorage.dev" },
39
44
 
40
45
  // Trust contract — what runtime APIs the plugin asks for.
@@ -0,0 +1,7 @@
1
+ ## Reporting a problem
2
+
3
+ Email **security@moorage.dev**, not a public issue. Say which version you use, what you found and how to reproduce it. Fixes go into the latest release; update to it to receive them. The source is public on GitHub.
4
+
5
+ ## What it keeps and sends
6
+
7
+ It keeps one small record per entry in the site's own database: dates, authors, the page type and any extra structured data you add. It sends nothing to any outside service and loads no outside scripts. Redirects change only when you add, edit or remove one, or confirm an import.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moorage/plugin-seo",
3
- "version": "0.1.8",
3
+ "version": "0.1.11",
4
4
  "description": "SEO for EmDash: schema markup, JSON-LD structured data, Google preview, SEO checks, noindex, WordPress redirects import. Yoast alternative.",
5
5
  "keywords": [
6
6
  "emdash",