@tagperfect/emdash-plugin-indexnow 1.1.0 → 1.2.0
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/README.md +2 -1
- package/dist/index.mjs +2 -1
- package/dist/manifest.json +5 -1
- package/dist/plugin.mjs +1 -1
- package/emdash-plugin.jsonc +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ The key file must sit at the site root: per the protocol, a key file covers only
|
|
|
61
61
|
| Submit URLs to IndexNow | off | Master switch. Nothing is sent while it is off. |
|
|
62
62
|
| Key | empty | The IndexNow key. **Generate key** creates a 32-character key. |
|
|
63
63
|
| Endpoint | `api.indexnow.org` | Where requests go. Any participant shares the URLs with the others; Bing, Yandex, Seznam, Naver and Yep endpoints can be chosen directly. |
|
|
64
|
-
| Collections |
|
|
64
|
+
| Collections | none checked (all) | The collections to track, picked from the site's collections. With none checked, every collection is tracked and **Resubmit all** reads every visible collection. |
|
|
65
65
|
| Site URL | EmDash's site URL | The origin of submitted URLs, e.g. `https://example.com`. Set it when the admin runs on another host. Only URLs on this host are sent. |
|
|
66
66
|
| Key file | Site root | `https://site/{key}.txt`, or a custom URL on the same host. |
|
|
67
67
|
| URL rules | empty | Optional rules for URLs that the collection URL pattern cannot express (see below). |
|
|
@@ -97,6 +97,7 @@ price_items et /hinnad/ # a price change pings the pri
|
|
|
97
97
|
| Capability | Why |
|
|
98
98
|
| --- | --- |
|
|
99
99
|
| `content:read` | Read the changed entry: its status (drafts are skipped), SEO `noIndex` flag, slug, locale and fields used by URL rules, and its public URL. **Resubmit all** lists published entries of the tracked collections. |
|
|
100
|
+
| `schema:read` | List the site's collections for the **Collections** checkboxes, and for **Resubmit all** when none are selected. |
|
|
100
101
|
| `network:request` with `allowedHosts`: `api.indexnow.org`, `www.bing.com`, `yandex.com`, `search.seznam.cz`, `searchadvisor.naver.com`, `indexnow.yep.com` | `POST` the URL list to the IndexNow endpoint chosen in Settings. No other host can be reached. |
|
|
101
102
|
|
|
102
103
|
No other host APIs are used. Settings, the journal (declared storage `submissions`) and internal state (KV) are plugin-scoped and need no capability.
|
package/dist/index.mjs
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
/** @type {import("emdash").PluginDescriptor} */
|
|
8
8
|
const descriptor = Object.freeze({
|
|
9
9
|
"id": "indexnow",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.2.0",
|
|
11
11
|
"format": "standard",
|
|
12
12
|
"entrypoint": "@tagperfect/emdash-plugin-indexnow/sandbox",
|
|
13
13
|
"capabilities": [
|
|
14
14
|
"content:read",
|
|
15
|
+
"schema:read",
|
|
15
16
|
"network:request"
|
|
16
17
|
],
|
|
17
18
|
"allowedHosts": [
|
package/dist/manifest.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "indexnow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"declaredAccess": {
|
|
5
5
|
"content": {
|
|
6
6
|
"read": {}
|
|
7
7
|
},
|
|
8
|
+
"schema": {
|
|
9
|
+
"read": {}
|
|
10
|
+
},
|
|
8
11
|
"network": {
|
|
9
12
|
"request": {
|
|
10
13
|
"allowedHosts": [
|
|
@@ -20,6 +23,7 @@
|
|
|
20
23
|
},
|
|
21
24
|
"capabilities": [
|
|
22
25
|
"content:read",
|
|
26
|
+
"schema:read",
|
|
23
27
|
"network:request"
|
|
24
28
|
],
|
|
25
29
|
"allowedHosts": [
|
package/dist/plugin.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e={}){let t=[];return new Headers(e.headers).forEach((e,n)=>t.push([n,e])),{__emdashPluginResponse:!0,status:e.status??200,headers:t,body:e.body??null}}const t=[{host:`api.indexnow.org`,label:`api.indexnow.org (shared by all engines)`},{host:`www.bing.com`,label:`Bing`},{host:`yandex.com`,label:`Yandex`},{host:`search.seznam.cz`,label:`Seznam`},{host:`searchadvisor.naver.com`,label:`Naver`},{host:`indexnow.yep.com`,label:`Yep`}],n=`api.indexnow.org`,r=/^[A-Za-z0-9-]{8,128}$/;function i(e){return t.some(t=>t.host===e)}function a(e){return`https://${e}/indexnow`}function o(){return crypto.randomUUID().replaceAll(`-`,``)}function s(e,t,n,r){return{host:e,key:t,keyLocation:n,urlList:r}}function c(e,t=1e4){let n=[];for(let r=0;r<e.length;r+=t)n.push(e.slice(r,r+t));return n}const l={200:{ok:!0,retry:!1,setupProblem:!1,message:`Submitted.`},202:{ok:!0,retry:!1,setupProblem:!1,message:`Accepted; key validation pending.`},400:{ok:!1,retry:!1,setupProblem:!1,message:`Bad request: the request format is invalid.`},403:{ok:!1,retry:!1,setupProblem:!0,message:`Key not valid: the key file was not found at keyLocation or does not contain the key.`},422:{ok:!1,retry:!1,setupProblem:!0,message:`Unprocessable: the URLs do not belong to the host, or the key does not match the protocol format.`},429:{ok:!1,retry:!0,setupProblem:!1,message:`Too many requests: rate limited, will retry later.`}};function u(e,t=``){let n=l[e],r;r=n||(e===0?{ok:!1,retry:!0,setupProblem:!1,message:`Network error: no response, will retry later.`}:e>=500?{ok:!1,retry:!0,setupProblem:!1,message:`Search engine error ${e}, will retry later.`}:e>=200&&e<300?{ok:!0,retry:!1,setupProblem:!1,message:`Accepted (${e}).`}:{ok:!1,retry:!1,setupProblem:!1,message:`Unexpected response ${e}.`});let i=r.ok?``:t.replace(/\s+/g,` `).trim().slice(0,200);return{status:e,...r,message:i?`${r.message} ${i}`:r.message}}const d=864e5,f=e=>e.storage.submissions,p=e=>`stats:${e.slice(0,10)}`;async function ee(e,t){let n=`${t.at}-${crypto.randomUUID().slice(0,8)}`;await f(e).put(n,{...t,urls:t.urls.slice(0,20),count:t.urls.length});let r=p(t.at),i=await e.kv.get(r)??{ok:0,failed:0};await e.kv.set(r,t.ok?{...i,ok:i.ok+1}:{...i,failed:i.failed+1}),await te(e)}async function te(e){let t=f(e),n=await t.count()-100;if(n>0){let e=await t.query({orderBy:{at:`asc`},limit:Math.min(n,100)});await t.deleteMany(e.items.map(e=>e.id))}let r=p(new Date(Date.now()-8*d).toISOString()),i=(await e.kv.list(`stats:`)).filter(e=>e.key<r);await Promise.all(i.map(t=>e.kv.delete(t.key)))}async function m(e,t=100){return(await f(e).query({orderBy:{at:`desc`},limit:Math.min(t,100)})).items.map(e=>e.data)}async function h(e,t=new Date){let n=Array.from({length:7},(e,n)=>p(new Date(t.getTime()-n*d).toISOString()));return(await Promise.all(n.map(t=>e.kv.get(t)))).reduce((e,t)=>({ok:e.ok+(t?.ok??0),failed:e.failed+(t?.failed??0)}),{ok:0,failed:0})}const g={get:e=>e.kv.get(`state:setupProblem`),set:(e,t)=>e.kv.set(`state:setupProblem`,t),clear:e=>e.kv.delete(`state:setupProblem`)},_=/^[A-Za-z_]\w*$/,ne=/\{(slug|id|locale)\}/g;function v(e){let t=/^([A-Za-z_]\w*)(!=|=)(.*)$/.exec(e);if(t)return{field:t[1],op:t[2]===`=`?`eq`:`neq`,value:t[3]};let n=/^(!?)([A-Za-z_]\w*)$/.exec(e);return n?{field:n[2],op:n[1]?`unset`:`set`}:null}function y(e){let t=[],n=[];return e.split(/\r?\n/).forEach((e,r)=>{let i=r+1,a=e.trim();if(!a||a.startsWith(`#`))return;let[o,s,c,...l]=a.split(/\s+/);if(!o||!s||!c){n.push(`Line ${i}: expected "<collection> <locale|*> <pattern|->".`);return}if(!_.test(o)){n.push(`Line ${i}: "${o}" is not a collection slug.`);return}if(c!==`-`&&!c.startsWith(`/`)){n.push(`Line ${i}: the pattern must start with "/" or be "-".`);return}let u=[];for(let e of l){let t=v(e);if(!t){n.push(`Line ${i}: "${e}" is not a condition (field=value, field!=value, field, !field).`);return}u.push(t)}t.push({collection:o,locale:s,pattern:c===`-`?null:c,conditions:u,line:i})}),{rules:t,errors:n}}const re=e=>typeof e==`string`?e:typeof e==`number`||typeof e==`boolean`?String(e):``;function ie(e,t){return t===`slug`?e.slug??``:t===`locale`?e.locale??``:t===`id`?e.id:re(e.data[t])}function b(e,t){let n=ie(t,e.field);switch(e.op){case`eq`:return n===e.value;case`neq`:return n!==e.value;case`set`:return n!==``&&n!==`false`;case`unset`:return n===``||n===`false`}}function x(e,t){return e.find(e=>e.collection===t.collection&&(e.locale===`*`||e.locale===(t.locale??``))&&e.conditions.every(e=>b(e,t)))}function ae(e,t){let n=!1,r=e.replace(ne,(e,r)=>{let i=r===`slug`?t.slug:r===`id`?t.id:t.locale;return i||(n=!0),encodeURIComponent(i??``)});return n?null:r}function S(e){try{let t=new URL(e);return t.protocol===`https:`||t.protocol===`http:`?t.origin:null}catch{return null}}function C(e,t){return`${e.replace(/\/+$/,``)}${t.startsWith(`/`)?``:`/`}${t}`}function oe(e,t){let n=new URL(e);return C(t,`${n.pathname}${n.search}`)}function w(e){return e.status===`published`?e.seo?.noIndex===!0?`noindex`:`ok`:`draft`}function T(e){return[...new Set(e.split(/[\s,]+/).filter(e=>_.test(e)))]}function E(e,t){return e.length===0||e.includes(t)}function se(e,t){let n=new Set,r=[];for(let i of e){let e;try{e=new URL(i.trim())}catch{r.push(i);continue}if(e.protocol!==`https:`&&e.protocol!==`http:`||e.host!==t){r.push(i);continue}e.hash=``,n.add(e.href)}return{urls:[...n],dropped:r}}const D=e=>typeof e==`string`?e.trim():``;async function O(e){let[t,r,a,o,s,c,l,u]=await Promise.all([`enabled`,`key`,`endpoint`,`collections`,`siteUrl`,`keyLocation`,`keyLocationUrl`,`urlRules`].map(t=>e.settings.get(t))),d=D(a),f=D(o);return{enabled:t===!0,key:D(r),endpoint:i(d)?d:n,collectionsText:f,collections:T(f),siteUrl:D(s),keyLocation:c===`custom`?`custom`:`root`,keyLocationUrl:D(l),urlRules:typeof u==`string`?u:``}}function k(e){let t=D(e.key),a=D(e.siteUrl).replace(/\/+$/,``),o=e.keyLocation===`custom`?`custom`:`root`,s=D(e.keyLocationUrl),c=D(e.endpoint)||n,l=typeof e.urlRules==`string`?e.urlRules:``;if(t&&!r.test(t))return{ok:!1,error:`The key must be 8–128 characters: letters, digits and dashes.`};if(a&&!S(a))return{ok:!1,error:`Site URL must be an absolute http(s) URL, e.g. https://example.com.`};if(o===`custom`&&!S(s))return{ok:!1,error:`Key file URL must be an absolute http(s) URL.`};if(!i(c))return{ok:!1,error:`Unknown endpoint.`};let{errors:u}=y(l);return u.length?{ok:!1,error:`URL rules: ${u[0]}`}:e.enabled===!0&&!t?{ok:!1,error:`Set or generate a key before enabling.`}:{ok:!0,values:{enabled:e.enabled===!0,key:t,endpoint:c,collections:T(D(e.collections)).join(`, `),siteUrl:a,keyLocation:o,keyLocationUrl:s,urlRules:l}}}function A(e,t){return S(e.siteUrl||t.site.url||``)}function j(e,t){return e.keyLocation===`custom`&&e.keyLocationUrl?e.keyLocationUrl:C(t,`/${e.key}.txt`)}function M(e,t){return e.enabled?e.key?t?null:`The site URL is unknown: set Site URL.`:`No key is set.`:`IndexNow is turned off.`}const N=(e,t)=>`url:${e}:${t}`,P=(e,t)=>`gone:${e}:${t}`,F=`state:retry`;function I(e,t){let n=e=>typeof e==`string`?e:null;return{collection:e,id:String(t.id??``),slug:n(t.slug),locale:n(t.locale),status:n(t.status)??``,data:t.data&&typeof t.data==`object`?t.data:{},seo:t.seo??null}}async function L(e,t,n,r){let i=x(y(t.urlRules).rules,r);if(i){let e=i.pattern&&ae(i.pattern,r);return e?C(n,e):null}if(r.status!==`published`)return null;let a=await e.content?.getPublicUrl?.(r.collection,r.id);return a?oe(a,n):null}async function R(e,t,n,r){let i=A(t,e),o=M(t,i);if(o||!i)return e.log.info(`IndexNow: not sent (${o})`,{urls:n}),null;let l=new URL(i).host,d=se(n,l);if(d.dropped.length&&e.log.warn(`IndexNow: skipped URLs outside ${l}`,{urls:d.dropped}),!d.urls.length)return null;if(!e.http)throw Error(`IndexNow: the network:request capability is not granted.`);let f=null;for(let n of c(d.urls)){let o;try{let r=await e.http.fetch(a(t.endpoint),{method:`POST`,headers:{"Content-Type":`application/json; charset=utf-8`},body:JSON.stringify(s(l,t.key,j(t,i),n))});o=u(r.status,r.ok?``:await r.text().catch(()=>``))}catch(e){o=u(0,e instanceof Error?e.message:String(e))}let c=new Date().toISOString();await ee(e,{at:c,event:r,urls:n,status:o.status,ok:o.ok,message:o.message,endpoint:t.endpoint});let d={status:o.status,count:n.length,urls:n.slice(0,5),endpoint:t.endpoint};o.ok?(e.log.info(`IndexNow ${o.status}: ${o.message}`,d),await g.clear(e)):(e.log.warn(`IndexNow ${o.status}: ${o.message}`,d),o.setupProblem&&await g.set(e,{at:c,status:o.status,message:o.message}),o.retry&&r!==`retry`&&await z(e,n)),f=o}return f}async function z(e,t){let n=await e.kv.get(F)??[];await e.kv.set(F,[...new Set([...n,...t])].slice(-1e4))}async function ce(e){let t=await e.kv.get(F)??[];t.length&&(await e.kv.delete(F),(await R(e,await O(e),t,`retry`))?.retry&&await z(e,t))}async function B(e,t,n,r){let i=await O(e);if(!i.enabled||!E(i.collections,t)||!n)return;let a=await e.content?.get(t,n);if(!a)return;let o=I(t,a);if(r===`update`&&a.draftRevisionId&&a.draftRevisionId!==a.liveRevisionId)return;let s=w(o);if(s!==`ok`){e.log.info(`IndexNow: ${t}/${n} skipped (${s})`),s===`noindex`&&await e.kv.delete(N(t,n));return}let c=A(i,e),l=c&&await L(e,i,c,o);if(!l){e.log.info(`IndexNow: ${t}/${n} has no public URL`);return}let u=await e.kv.get(N(t,n));if(await e.kv.set(N(t,n),l),await U(e,l)){e.log.info(`IndexNow: ${l} skipped (sent less than ${V/1e3} s ago)`);return}await R(e,i,[l],r===`publish`&&u?`update`:r)}const V=6e4,H=`state:recent`;async function U(e,t,n=Date.now()){let r=await e.kv.get(H)??{},i=Object.fromEntries(Object.entries(r).filter(([,e])=>n-e<V));return i[t]===void 0?(await e.kv.set(H,{...i,[t]:n}),!1):!0}async function W(e,t,n){let r=await O(e);if(!r.enabled||!E(r.collections,t)||!n)return;let i=await e.kv.get(N(t,n));if(!i){let a=await e.content?.get(t,n),o=A(r,e);a&&o&&(i=await L(e,r,o,{...I(t,a),status:`published`}))}i&&(await e.kv.delete(N(t,n)),await R(e,r,[i],`unpublish`))}async function le(e,t,n){let r=await O(e);if(!r.enabled||!E(r.collections,t))return;let i=await e.kv.get(N(t,n));if(!i){let a=await e.content?.get(t,n),o=A(r,e);a&&o&&w(I(t,a))===`ok`&&(i=await L(e,r,o,I(t,a)))}i&&await e.kv.set(P(t,n),i)}async function ue(e,t,n){let r=await e.kv.get(P(t,n));r&&(await Promise.all([e.kv.delete(P(t,n)),e.kv.delete(N(t,n))]),await R(e,await O(e),[r],`delete`))}async function de(e){let t=await O(e),n=A(t,e),r=M(t,n);if(r||!n)return{outcome:null,urls:0,error:r??`Site URL unknown.`};let i=t.collections.length?t.collections:[...new Set(y(t.urlRules).rules.map(e=>e.collection))];if(!i.length)return{outcome:null,urls:0,error:`List the collections to submit in Settings → Collections.`};let a=[],o=0;for(let r of i){let i;do{let s=await e.content.list(r,{where:{status:`published`},limit:100,cursor:i});for(let i of s.items){let o=I(r,i);if(w(o)!==`ok`)continue;let s=await L(e,t,n,o);s&&(a.push(s),await e.kv.set(N(r,o.id),s))}o+=s.items.length,i=s.hasMore?s.cursor:void 0}while(i&&o<5e3)}let s=[...new Set(a)];return s.length?{outcome:await R(e,t,s,`resubmit`),urls:s.length}:{outcome:null,urls:0,error:`No published URLs found.`}}const G=`/submissions`,K={kind:`plugin-page`,path:`/settings`};function q(e){return e.status===0?`no response`:String(e.status)}function J(e){let t=e.urls[0]??``;return e.count>1?`${t} (+${e.count-1})`:t}async function Y(e,n){let r=n??await O(e),i=A(r,e),a=M(r,i),o=[{type:`header`,text:`IndexNow`}];return a&&o.push({type:`banner`,variant:`alert`,title:`Not submitting`,description:a}),r.key||o.push({type:`section`,text:`No key yet. Generate one, then serve it at the key file URL shown below.`,accessory:{type:`button`,action_id:`generate_key`,label:`Generate key`,style:`primary`}}),o.push({type:`form`,block_id:`settings`,fields:[{type:`toggle`,action_id:`enabled`,label:`Submit URLs to IndexNow`,initial_value:r.enabled},{type:`text_input`,action_id:`key`,label:`Key`,placeholder:`8–128 characters: a–z, A–Z, 0–9, -`,initial_value:r.key},{type:`select`,action_id:`endpoint`,label:`Endpoint`,options:t.map(e=>({label:e.label,value:e.host})),initial_value:r.endpoint},{type:`text_input`,action_id:`collections`,label:`Collections`,placeholder:`posts, pages (empty = all)`,initial_value:r.collectionsText},{type:`text_input`,action_id:`siteUrl`,label:`Site URL`,placeholder:e.site.url||`https://example.com`,initial_value:r.siteUrl},{type:`select`,action_id:`keyLocation`,label:`Key file`,options:[{label:`Site root: /{key}.txt (recommended)`,value:`root`},{label:`Custom URL`,value:`custom`}],initial_value:r.keyLocation},{type:`text_input`,action_id:`keyLocationUrl`,label:`Key file URL`,placeholder:`https://example.com/indexnow-key.txt`,initial_value:r.keyLocationUrl,condition:{field:`keyLocation`,eq:`custom`}},{type:`text_input`,action_id:`urlRules`,label:`URL rules (advanced)`,multiline:!0,initial_value:r.urlRules}],submit:{label:`Save`,action_id:`save_settings`}}),o.push({type:`context`,text:"One rule per line: `<collection> <locale|*> <pattern|-> [conditions]`. Placeholders {slug}, {id}, {locale}; `-` = no page. Conditions on entry fields: field=value, field!=value, field (set), !field (empty). First match wins; entries without a match use the collection's URL pattern."}),r.key&&i&&(o.push({type:`fields`,fields:[{label:`Key file must return the key at`,value:j(r,i)},{label:`Plugin key route`,value:`${i}/_emdash/api/plugins/indexnow/key`}]}),o.push({type:`context`,text:`A key file covers only URLs in its own folder and below, so serve it at the site root (a static file, a site route, or a URL rewrite to the plugin key route).`})),o.push({type:`actions`,elements:[{type:`link`,label:`Submissions`,target:{kind:`plugin-page`,path:G}}]}),{blocks:o}}async function X(e){let[t,n]=await Promise.all([m(e),h(e)]);return{blocks:[{type:`header`,text:`IndexNow submissions`},{type:`stats`,items:[{label:`Successful, 7 days`,value:n.ok},{label:`Failed, 7 days`,value:n.failed}]},{type:`actions`,elements:[{type:`button`,action_id:`resubmit_all`,label:`Resubmit all published URLs`,style:`primary`,confirm:{title:`Resubmit all published URLs?`,text:`Every live, indexable URL of the tracked collections is sent again. Use it after setup or a site move, not routinely.`,confirm:`Resubmit`,deny:`Cancel`}},{type:`link`,label:`Settings`,target:K}]},{type:`table`,page_action_id:`log_page`,columns:[{key:`at`,label:`Time`,format:`relative_time`},{key:`url`,label:`URL`},{key:`status`,label:`Status`,format:`badge`},{key:`event`,label:`Event`},{key:`message`,label:`Message`}],rows:t.map(e=>({at:e.at,url:J(e),status:q(e),event:e.event,message:e.message})),empty_text:`Nothing submitted yet.`}]}}async function fe(e){let t=await O(e);if(M(t,A(t,e)))return{blocks:[{type:`section`,text:t.enabled?`Not configured`:`Off`,accessory:{type:`link`,label:`Set up`,target:K,appearance:`primary`}}]};let[n,r,i]=await Promise.all([m(e,5),h(e),g.get(e)]),a=[];return i&&(a.push({type:`banner`,variant:`error`,title:i.status===403?`Key rejected (403)`:`Setup problem (${i.status})`,description:i.message}),a.push({type:`actions`,elements:[{type:`link`,label:`Check settings`,target:K,appearance:`primary`}]})),a.push({type:`stats`,items:[{label:`OK, 7 days`,value:r.ok},{label:`Failed, 7 days`,value:r.failed,...r.failed?{trend:`down`}:{}}]}),a.push({type:`table`,page_action_id:`widget_page`,columns:[{key:`url`,label:`URL`},{key:`status`,label:`Status`,format:`badge`},{key:`at`,label:`Time`,format:`relative_time`}],rows:n.map(e=>({url:J(e),status:q(e),at:e.at})),empty_text:`Nothing submitted yet.`}),{blocks:a}}async function Z(e,t){return t===`widget:status`?fe(e):t===G?X(e):Y(e)}function pe(e){if(!e||typeof e!=`object`)return!1;let t=e.type;return t===`page_load`||t===`block_action`||t===`form_submit`}async function me(e,t){if(!pe(e))return{blocks:[]};if(e.type===`page_load`)return Z(t,e.page);if(e.type===`form_submit`&&e.action_id===`save_settings`){let n=k(e.values);return n.ok?(await Promise.all(Object.entries(n.values).map(([e,n])=>t.settings.set(e,n))),await g.clear(t),{...await Y(t),toast:{type:`success`,message:`Settings saved`}}):{...await Y(t),toast:{type:`error`,message:n.error}}}if(e.type===`block_action`&&e.action_id===`generate_key`)return(await O(t)).key||await t.settings.set(`key`,o()),{...await Y(t),toast:{type:`success`,message:`Key generated. Serve the key file, then enable submissions.`}};if(e.type===`block_action`&&e.action_id===`resubmit_all`){let e=await de(t),n=e.error?{type:`error`,message:e.error}:e.outcome?.ok?{type:`success`,message:`${e.urls} URLs submitted (${e.outcome.status}).`}:{type:`error`,message:e.outcome?.message??`Nothing was sent.`};return{...await X(t),toast:n}}return Z(t,e.page)}const Q=`retry`,$=e=>typeof e.id==`string`?e.id:``,he={hooks:{"plugin:activate":async(e,t)=>{await t.cron?.schedule(Q,{schedule:`*/30 * * * *`})},"plugin:deactivate":async(e,t)=>{await t.cron?.cancel(Q)},"content:afterPublish":{errorPolicy:`continue`,handler:async(e,t)=>B(t,e.collection,$(e.content),`publish`)},"content:afterSave":{errorPolicy:`continue`,handler:async(e,t)=>B(t,e.collection,$(e.content),`update`)},"content:afterRestore":{errorPolicy:`continue`,handler:async(e,t)=>B(t,e.collection,$(e.content),`restore`)},"content:afterUnpublish":{errorPolicy:`continue`,handler:async(e,t)=>W(t,e.collection,$(e.content))},"content:beforeDelete":{errorPolicy:`continue`,handler:async(e,t)=>{await le(t,e.collection,e.id)}},"content:afterDelete":{errorPolicy:`continue`,handler:async(e,t)=>ue(t,e.collection,e.id)},cron:async(e,t)=>{e.name===Q&&await ce(t)}},routes:{admin:{permission:`plugins:manage`,handler:async(e,t)=>me(e.input,t)},key:{public:!0,methods:[`GET`,`HEAD`],response:`raw`,cacheControl:`public, max-age=300`,handler:async(t,n)=>{let{key:r}=await O(n);return e(r?{status:200,headers:{"Content-Type":`text/plain; charset=utf-8`},body:{kind:`text`,value:r}}:{status:404,headers:{"Content-Type":`text/plain; charset=utf-8`},body:{kind:`text`,value:`Not found`}})}}}};export{he as default};
|
|
1
|
+
function e(e={}){let t=[];return new Headers(e.headers).forEach((e,n)=>t.push([n,e])),{__emdashPluginResponse:!0,status:e.status??200,headers:t,body:e.body??null}}const t=[{host:`api.indexnow.org`,label:`api.indexnow.org (shared by all engines)`},{host:`www.bing.com`,label:`Bing`},{host:`yandex.com`,label:`Yandex`},{host:`search.seznam.cz`,label:`Seznam`},{host:`searchadvisor.naver.com`,label:`Naver`},{host:`indexnow.yep.com`,label:`Yep`}],n=`api.indexnow.org`,r=/^[A-Za-z0-9-]{8,128}$/;function i(e){return t.some(t=>t.host===e)}function a(e){return`https://${e}/indexnow`}function o(){return crypto.randomUUID().replaceAll(`-`,``)}function s(e,t,n,r){return{host:e,key:t,keyLocation:n,urlList:r}}function c(e,t=1e4){let n=[];for(let r=0;r<e.length;r+=t)n.push(e.slice(r,r+t));return n}const l={200:{ok:!0,retry:!1,setupProblem:!1,message:`Submitted.`},202:{ok:!0,retry:!1,setupProblem:!1,message:`Accepted; key validation pending.`},400:{ok:!1,retry:!1,setupProblem:!1,message:`Bad request: the request format is invalid.`},403:{ok:!1,retry:!1,setupProblem:!0,message:`Key not valid: the key file was not found at keyLocation or does not contain the key.`},422:{ok:!1,retry:!1,setupProblem:!0,message:`Unprocessable: the URLs do not belong to the host, or the key does not match the protocol format.`},429:{ok:!1,retry:!0,setupProblem:!1,message:`Too many requests: rate limited, will retry later.`}};function u(e,t=``){let n=l[e],r;r=n||(e===0?{ok:!1,retry:!0,setupProblem:!1,message:`Network error: no response, will retry later.`}:e>=500?{ok:!1,retry:!0,setupProblem:!1,message:`Search engine error ${e}, will retry later.`}:e>=200&&e<300?{ok:!0,retry:!1,setupProblem:!1,message:`Accepted (${e}).`}:{ok:!1,retry:!1,setupProblem:!1,message:`Unexpected response ${e}.`});let i=r.ok?``:t.replace(/\s+/g,` `).trim().slice(0,200);return{status:e,...r,message:i?`${r.message} ${i}`:r.message}}const d=864e5,f=e=>e.storage.submissions,p=e=>`stats:${e.slice(0,10)}`;async function ee(e,t){let n=`${t.at}-${crypto.randomUUID().slice(0,8)}`;await f(e).put(n,{...t,urls:t.urls.slice(0,20),count:t.urls.length});let r=p(t.at),i=await e.kv.get(r)??{ok:0,failed:0};await e.kv.set(r,t.ok?{...i,ok:i.ok+1}:{...i,failed:i.failed+1}),await te(e)}async function te(e){let t=f(e),n=await t.count()-100;if(n>0){let e=await t.query({orderBy:{at:`asc`},limit:Math.min(n,100)});await t.deleteMany(e.items.map(e=>e.id))}let r=p(new Date(Date.now()-8*d).toISOString()),i=(await e.kv.list(`stats:`)).filter(e=>e.key<r);await Promise.all(i.map(t=>e.kv.delete(t.key)))}async function m(e,t=100){return(await f(e).query({orderBy:{at:`desc`},limit:Math.min(t,100)})).items.map(e=>e.data)}async function h(e,t=new Date){let n=Array.from({length:7},(e,n)=>p(new Date(t.getTime()-n*d).toISOString()));return(await Promise.all(n.map(t=>e.kv.get(t)))).reduce((e,t)=>({ok:e.ok+(t?.ok??0),failed:e.failed+(t?.failed??0)}),{ok:0,failed:0})}const g={get:e=>e.kv.get(`state:setupProblem`),set:(e,t)=>e.kv.set(`state:setupProblem`,t),clear:e=>e.kv.delete(`state:setupProblem`)},_=/^[A-Za-z_]\w*$/,ne=/\{(slug|id|locale)\}/g;function v(e){let t=/^([A-Za-z_]\w*)(!=|=)(.*)$/.exec(e);if(t)return{field:t[1],op:t[2]===`=`?`eq`:`neq`,value:t[3]};let n=/^(!?)([A-Za-z_]\w*)$/.exec(e);return n?{field:n[2],op:n[1]?`unset`:`set`}:null}function y(e){let t=[],n=[];return e.split(/\r?\n/).forEach((e,r)=>{let i=r+1,a=e.trim();if(!a||a.startsWith(`#`))return;let[o,s,c,...l]=a.split(/\s+/);if(!o||!s||!c){n.push(`Line ${i}: expected "<collection> <locale|*> <pattern|->".`);return}if(!_.test(o)){n.push(`Line ${i}: "${o}" is not a collection slug.`);return}if(c!==`-`&&!c.startsWith(`/`)){n.push(`Line ${i}: the pattern must start with "/" or be "-".`);return}let u=[];for(let e of l){let t=v(e);if(!t){n.push(`Line ${i}: "${e}" is not a condition (field=value, field!=value, field, !field).`);return}u.push(t)}t.push({collection:o,locale:s,pattern:c===`-`?null:c,conditions:u,line:i})}),{rules:t,errors:n}}const re=e=>typeof e==`string`?e:typeof e==`number`||typeof e==`boolean`?String(e):``;function b(e,t){return t===`slug`?e.slug??``:t===`locale`?e.locale??``:t===`id`?e.id:re(e.data[t])}function ie(e,t){let n=b(t,e.field);switch(e.op){case`eq`:return n===e.value;case`neq`:return n!==e.value;case`set`:return n!==``&&n!==`false`;case`unset`:return n===``||n===`false`}}function ae(e,t){return e.find(e=>e.collection===t.collection&&(e.locale===`*`||e.locale===(t.locale??``))&&e.conditions.every(e=>ie(e,t)))}function oe(e,t){let n=!1,r=e.replace(ne,(e,r)=>{let i=r===`slug`?t.slug:r===`id`?t.id:t.locale;return i||(n=!0),encodeURIComponent(i??``)});return n?null:r}function x(e){try{let t=new URL(e);return t.protocol===`https:`||t.protocol===`http:`?t.origin:null}catch{return null}}function S(e,t){return`${e.replace(/\/+$/,``)}${t.startsWith(`/`)?``:`/`}${t}`}function C(e,t){let n=new URL(e);return S(t,`${n.pathname}${n.search}`)}function w(e){return e.status===`published`?e.seo?.noIndex===!0?`noindex`:`ok`:`draft`}function T(e){return[...new Set(e.split(/[\s,]+/).filter(e=>_.test(e)))]}function E(e,t){return e.length===0||e.includes(t)}function se(e,t){let n=new Set,r=[];for(let i of e){let e;try{e=new URL(i.trim())}catch{r.push(i);continue}if(e.protocol!==`https:`&&e.protocol!==`http:`||e.host!==t){r.push(i);continue}e.hash=``,n.add(e.href)}return{urls:[...n],dropped:r}}const D=e=>typeof e==`string`?e.trim():``;async function O(e){let[t,r,a,o,s,c,l,u]=await Promise.all([`enabled`,`key`,`endpoint`,`collections`,`siteUrl`,`keyLocation`,`keyLocationUrl`,`urlRules`].map(t=>e.settings.get(t))),d=D(a),f=D(o);return{enabled:t===!0,key:D(r),endpoint:i(d)?d:n,collectionsText:f,collections:T(f),siteUrl:D(s),keyLocation:c===`custom`?`custom`:`root`,keyLocationUrl:D(l),urlRules:typeof u==`string`?u:``}}function ce(e){let t=D(e.key),a=D(e.siteUrl).replace(/\/+$/,``),o=e.keyLocation===`custom`?`custom`:`root`,s=D(e.keyLocationUrl),c=D(e.endpoint)||n,l=typeof e.urlRules==`string`?e.urlRules:``;if(t&&!r.test(t))return{ok:!1,error:`The key must be 8–128 characters: letters, digits and dashes.`};if(a&&!x(a))return{ok:!1,error:`Site URL must be an absolute http(s) URL, e.g. https://example.com.`};if(o===`custom`&&!x(s))return{ok:!1,error:`Key file URL must be an absolute http(s) URL.`};if(!i(c))return{ok:!1,error:`Unknown endpoint.`};let{errors:u}=y(l);return u.length?{ok:!1,error:`URL rules: ${u[0]}`}:e.enabled===!0&&!t?{ok:!1,error:`Set or generate a key before enabling.`}:{ok:!0,values:{enabled:e.enabled===!0,key:t,endpoint:c,collections:T(Array.isArray(e.collections)?e.collections.join(`,`):D(e.collections)).join(`, `),siteUrl:a,keyLocation:o,keyLocationUrl:s,urlRules:l}}}function k(e,t){return x(e.siteUrl||t.site.url||``)}function A(e,t){return e.keyLocation===`custom`&&e.keyLocationUrl?e.keyLocationUrl:S(t,`/${e.key}.txt`)}function j(e,t){return e.enabled?e.key?t?null:`The site URL is unknown: set Site URL.`:`No key is set.`:`IndexNow is turned off.`}const M=(e,t)=>`url:${e}:${t}`,N=(e,t)=>`gone:${e}:${t}`,P=`state:retry`;function F(e,t){let n=e=>typeof e==`string`?e:null;return{collection:e,id:String(t.id??``),slug:n(t.slug),locale:n(t.locale),status:n(t.status)??``,data:t.data&&typeof t.data==`object`?t.data:{},seo:t.seo??null}}async function I(e,t,n,r){let i=ae(y(t.urlRules).rules,r);if(i){let e=i.pattern&&oe(i.pattern,r);return e?S(n,e):null}if(r.status!==`published`)return null;let a=await e.content?.getPublicUrl?.(r.collection,r.id);return a?C(a,n):null}async function L(e,t,n,r){let i=k(t,e),o=j(t,i);if(o||!i)return e.log.info(`IndexNow: not sent (${o})`,{urls:n}),null;let l=new URL(i).host,d=se(n,l);if(d.dropped.length&&e.log.warn(`IndexNow: skipped URLs outside ${l}`,{urls:d.dropped}),!d.urls.length)return null;if(!e.http)throw Error(`IndexNow: the network:request capability is not granted.`);let f=null;for(let n of c(d.urls)){let o;try{let r=await e.http.fetch(a(t.endpoint),{method:`POST`,headers:{"Content-Type":`application/json; charset=utf-8`},body:JSON.stringify(s(l,t.key,A(t,i),n))});o=u(r.status,r.ok?``:await r.text().catch(()=>``))}catch(e){o=u(0,e instanceof Error?e.message:String(e))}let c=new Date().toISOString();await ee(e,{at:c,event:r,urls:n,status:o.status,ok:o.ok,message:o.message,endpoint:t.endpoint});let d={status:o.status,count:n.length,urls:n.slice(0,5),endpoint:t.endpoint};o.ok?(e.log.info(`IndexNow ${o.status}: ${o.message}`,d),await g.clear(e)):(e.log.warn(`IndexNow ${o.status}: ${o.message}`,d),o.setupProblem&&await g.set(e,{at:c,status:o.status,message:o.message}),o.retry&&r!==`retry`&&await R(e,n)),f=o}return f}async function R(e,t){let n=await e.kv.get(P)??[];await e.kv.set(P,[...new Set([...n,...t])].slice(-1e4))}async function le(e){let t=await e.kv.get(P)??[];t.length&&(await e.kv.delete(P),(await L(e,await O(e),t,`retry`))?.retry&&await R(e,t))}async function z(e,t,n,r){let i=await O(e);if(!i.enabled||!E(i.collections,t)||!n)return;let a=await e.content?.get(t,n);if(!a)return;let o=F(t,a);if(r===`update`&&a.draftRevisionId&&a.draftRevisionId!==a.liveRevisionId)return;let s=w(o);if(s!==`ok`){e.log.info(`IndexNow: ${t}/${n} skipped (${s})`),s===`noindex`&&await e.kv.delete(M(t,n));return}let c=k(i,e),l=c&&await I(e,i,c,o);if(!l){e.log.info(`IndexNow: ${t}/${n} has no public URL`);return}let u=await e.kv.get(M(t,n));if(await e.kv.set(M(t,n),l),await H(e,l)){e.log.info(`IndexNow: ${l} skipped (sent less than ${B/1e3} s ago)`);return}await L(e,i,[l],r===`publish`&&u?`update`:r)}const B=6e4,V=`state:recent`;async function H(e,t,n=Date.now()){let r=await e.kv.get(V)??{},i=Object.fromEntries(Object.entries(r).filter(([,e])=>n-e<B));return i[t]===void 0?(await e.kv.set(V,{...i,[t]:n}),!1):!0}async function U(e,t,n){let r=await O(e);if(!r.enabled||!E(r.collections,t)||!n)return;let i=await e.kv.get(M(t,n));if(!i){let a=await e.content?.get(t,n),o=k(r,e);a&&o&&(i=await I(e,r,o,{...F(t,a),status:`published`}))}i&&(await e.kv.delete(M(t,n)),await L(e,r,[i],`unpublish`))}async function ue(e,t,n){let r=await O(e);if(!r.enabled||!E(r.collections,t))return;let i=await e.kv.get(M(t,n));if(!i){let a=await e.content?.get(t,n),o=k(r,e);a&&o&&w(F(t,a))===`ok`&&(i=await I(e,r,o,F(t,a)))}i&&await e.kv.set(N(t,n),i)}async function de(e,t,n){let r=await e.kv.get(N(t,n));r&&(await Promise.all([e.kv.delete(N(t,n)),e.kv.delete(M(t,n))]),await L(e,await O(e),[r],`delete`))}async function fe(e,t){let n=await e.schema?.listCollections().catch(()=>null);return n?.length?n.filter(e=>!e.hidden).map(e=>e.slug):[...new Set(y(t.urlRules).rules.map(e=>e.collection))]}async function pe(e){let t=await O(e),n=k(t,e),r=j(t,n);if(r||!n)return{outcome:null,urls:0,error:r??`Site URL unknown.`};let i=t.collections.length?t.collections:await fe(e,t);if(!i.length)return{outcome:null,urls:0,error:`Select the collections to submit in Settings → Collections.`};let a=[],o=0;for(let r of i){let i;do{let s=await e.content.list(r,{where:{status:`published`},limit:100,cursor:i});for(let i of s.items){let o=F(r,i);if(w(o)!==`ok`)continue;let s=await I(e,t,n,o);s&&(a.push(s),await e.kv.set(M(r,o.id),s))}o+=s.items.length,i=s.hasMore?s.cursor:void 0}while(i&&o<5e3)}let s=[...new Set(a)];return s.length?{outcome:await L(e,t,s,`resubmit`),urls:s.length}:{outcome:null,urls:0,error:`No published URLs found.`}}const W=`/submissions`,G={kind:`plugin-page`,path:`/settings`};function K(e){return e.status===0?`no response`:String(e.status)}function q(e){let t=e.urls[0]??``;return e.count>1?`${t} (+${e.count-1})`:t}async function J(e,t){let n=`Collections (none checked = all)`,r=await e.schema?.listCollections().catch(()=>null);if(!r?.length)return{type:`text_input`,action_id:`collections`,label:n,placeholder:`posts, pages`,initial_value:t.collectionsText};let i=r.filter(e=>!e.hidden).map(e=>({label:`${e.label} (${e.slug})`,value:e.slug}));for(let e of t.collections)i.some(t=>t.value===e)||i.push({label:`${e} (not found)`,value:e});return{type:`checkbox`,action_id:`collections`,label:n,options:i,initial_value:t.collections}}async function Y(e,n){let r=n??await O(e),i=k(r,e),a=j(r,i),o=[{type:`header`,text:`IndexNow`}];return a&&o.push({type:`banner`,variant:`alert`,title:`Not submitting`,description:a}),r.key||o.push({type:`section`,text:`No key yet. Generate one, then serve it at the key file URL shown below.`,accessory:{type:`button`,action_id:`generate_key`,label:`Generate key`,style:`primary`}}),o.push({type:`form`,block_id:`settings`,fields:[{type:`toggle`,action_id:`enabled`,label:`Submit URLs to IndexNow`,initial_value:r.enabled},{type:`text_input`,action_id:`key`,label:`Key`,placeholder:`8–128 characters: a–z, A–Z, 0–9, -`,initial_value:r.key},{type:`select`,action_id:`endpoint`,label:`Endpoint`,options:t.map(e=>({label:e.label,value:e.host})),initial_value:r.endpoint},await J(e,r),{type:`text_input`,action_id:`siteUrl`,label:`Site URL`,placeholder:e.site.url||`https://example.com`,initial_value:r.siteUrl},{type:`select`,action_id:`keyLocation`,label:`Key file`,options:[{label:`Site root: /{key}.txt (recommended)`,value:`root`},{label:`Custom URL`,value:`custom`}],initial_value:r.keyLocation},{type:`text_input`,action_id:`keyLocationUrl`,label:`Key file URL`,placeholder:`https://example.com/indexnow-key.txt`,initial_value:r.keyLocationUrl,condition:{field:`keyLocation`,eq:`custom`}},{type:`text_input`,action_id:`urlRules`,label:`URL rules (advanced)`,multiline:!0,initial_value:r.urlRules}],submit:{label:`Save`,action_id:`save_settings`}}),o.push({type:`context`,text:"One rule per line: `<collection> <locale|*> <pattern|-> [conditions]`. Placeholders {slug}, {id}, {locale}; `-` = no page. Conditions on entry fields: field=value, field!=value, field (set), !field (empty). First match wins; entries without a match use the collection's URL pattern."}),r.key&&i&&(o.push({type:`fields`,fields:[{label:`Key file must return the key at`,value:A(r,i)},{label:`Plugin key route`,value:`${i}/_emdash/api/plugins/indexnow/key`}]}),o.push({type:`context`,text:`A key file covers only URLs in its own folder and below, so serve it at the site root (a static file, a site route, or a URL rewrite to the plugin key route).`})),o.push({type:`actions`,elements:[{type:`link`,label:`Submissions`,target:{kind:`plugin-page`,path:W}}]}),{blocks:o}}async function X(e){let[t,n]=await Promise.all([m(e),h(e)]);return{blocks:[{type:`header`,text:`IndexNow submissions`},{type:`stats`,items:[{label:`Successful, 7 days`,value:n.ok},{label:`Failed, 7 days`,value:n.failed}]},{type:`actions`,elements:[{type:`button`,action_id:`resubmit_all`,label:`Resubmit all published URLs`,style:`primary`,confirm:{title:`Resubmit all published URLs?`,text:`Every live, indexable URL of the tracked collections is sent again. Use it after setup or a site move, not routinely.`,confirm:`Resubmit`,deny:`Cancel`}},{type:`link`,label:`Settings`,target:G}]},{type:`table`,page_action_id:`log_page`,columns:[{key:`at`,label:`Time`,format:`relative_time`},{key:`url`,label:`URL`},{key:`status`,label:`Status`,format:`badge`},{key:`event`,label:`Event`},{key:`message`,label:`Message`}],rows:t.map(e=>({at:e.at,url:q(e),status:K(e),event:e.event,message:e.message})),empty_text:`Nothing submitted yet.`}]}}async function me(e){let t=await O(e);if(j(t,k(t,e)))return{blocks:[{type:`section`,text:t.enabled?`Not configured`:`Off`,accessory:{type:`link`,label:`Set up`,target:G,appearance:`primary`}}]};let[n,r,i]=await Promise.all([m(e,5),h(e),g.get(e)]),a=[];return i&&(a.push({type:`banner`,variant:`error`,title:i.status===403?`Key rejected (403)`:`Setup problem (${i.status})`,description:i.message}),a.push({type:`actions`,elements:[{type:`link`,label:`Check settings`,target:G,appearance:`primary`}]})),a.push({type:`stats`,items:[{label:`OK, 7 days`,value:r.ok},{label:`Failed, 7 days`,value:r.failed,...r.failed?{trend:`down`}:{}}]}),a.push({type:`table`,page_action_id:`widget_page`,columns:[{key:`url`,label:`URL`},{key:`status`,label:`Status`,format:`badge`},{key:`at`,label:`Time`,format:`relative_time`}],rows:n.map(e=>({url:q(e),status:K(e),at:e.at})),empty_text:`Nothing submitted yet.`}),{blocks:a}}async function Z(e,t){return t===`widget:status`?me(e):t===W?X(e):Y(e)}function he(e){if(!e||typeof e!=`object`)return!1;let t=e.type;return t===`page_load`||t===`block_action`||t===`form_submit`}async function ge(e,t){if(!he(e))return{blocks:[]};if(e.type===`page_load`)return Z(t,e.page);if(e.type===`form_submit`&&e.action_id===`save_settings`){let n=ce(e.values);return n.ok?(await Promise.all(Object.entries(n.values).map(([e,n])=>t.settings.set(e,n))),await g.clear(t),{...await Y(t),toast:{type:`success`,message:`Settings saved`}}):{...await Y(t),toast:{type:`error`,message:n.error}}}if(e.type===`block_action`&&e.action_id===`generate_key`)return(await O(t)).key||await t.settings.set(`key`,o()),{...await Y(t),toast:{type:`success`,message:`Key generated. Serve the key file, then enable submissions.`}};if(e.type===`block_action`&&e.action_id===`resubmit_all`){let e=await pe(t),n=e.error?{type:`error`,message:e.error}:e.outcome?.ok?{type:`success`,message:`${e.urls} URLs submitted (${e.outcome.status}).`}:{type:`error`,message:e.outcome?.message??`Nothing was sent.`};return{...await X(t),toast:n}}return Z(t,e.page)}const Q=`retry`,$=e=>typeof e.id==`string`?e.id:``,_e={hooks:{"plugin:activate":async(e,t)=>{await t.cron?.schedule(Q,{schedule:`*/30 * * * *`})},"plugin:deactivate":async(e,t)=>{await t.cron?.cancel(Q)},"content:afterPublish":{errorPolicy:`continue`,handler:async(e,t)=>z(t,e.collection,$(e.content),`publish`)},"content:afterSave":{errorPolicy:`continue`,handler:async(e,t)=>z(t,e.collection,$(e.content),`update`)},"content:afterRestore":{errorPolicy:`continue`,handler:async(e,t)=>z(t,e.collection,$(e.content),`restore`)},"content:afterUnpublish":{errorPolicy:`continue`,handler:async(e,t)=>U(t,e.collection,$(e.content))},"content:beforeDelete":{errorPolicy:`continue`,handler:async(e,t)=>{await ue(t,e.collection,e.id)}},"content:afterDelete":{errorPolicy:`continue`,handler:async(e,t)=>de(t,e.collection,e.id)},cron:async(e,t)=>{e.name===Q&&await le(t)}},routes:{admin:{permission:`plugins:manage`,handler:async(e,t)=>ge(e.input,t)},key:{public:!0,methods:[`GET`,`HEAD`],response:`raw`,cacheControl:`public, max-age=300`,handler:async(t,n)=>{let{key:r}=await O(n);return e(r?{status:200,headers:{"Content-Type":`text/plain; charset=utf-8`},body:{kind:`text`,value:r}}:{status:404,headers:{"Content-Type":`text/plain; charset=utf-8`},body:{kind:`text`,value:`Not found`}})}}}};export{_e as default};
|
package/emdash-plugin.jsonc
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
// Trust contract. content:read: the entry's status, slug, locale, SEO noindex flag and public URL.
|
|
17
17
|
// network:request: POST to the IndexNow endpoints below and nowhere else.
|
|
18
|
-
"capabilities": ["content:read", "network:request"],
|
|
18
|
+
"capabilities": ["content:read", "schema:read", "network:request"],
|
|
19
19
|
"allowedHosts": [
|
|
20
20
|
"api.indexnow.org",
|
|
21
21
|
"www.bing.com",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tagperfect/emdash-plugin-indexnow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Notifies Bing, Yandex, Seznam, Naver and other IndexNow engines when EmDash content is published, changed, unpublished or deleted.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|