chore(ai): remove custom AI patches superseded by upstream endpoint-posts@beta.44
- Remove patch-preset-eleventy-ai-frontmatter: upstream now writes AI frontmatter natively using hyphenated keys (ai-text-level etc.) - Remove patch-endpoint-posts-ai-cleanup: upstream beta.44 natively removes empty ai-text-level/ai-code-level/ai-tools/ai-description fields - Remove patch-endpoint-posts-ai-fields: upstream beta.44 has AI form UI inline in post-form.njk; our separate templates would have duplicated fields - Remove patch-micropub-ai-block-resync: one-time stale-block migration, no longer relevant - Remove patch-endpoint-posts-prefill-url: upstream beta.44 has native prefill from query params; our patch would have conflicted - Remove patch-endpoint-posts-search-tags: upstream beta.44 has native search/filter/sort UI; patch already detected this and was a no-op - Bump @rmdes/indiekit-endpoint-posts beta.25→beta.44, override beta.41→beta.44 - Update indiekit.config.mjs: remove camelCase ai field names from all postTypes.fields (ai-* fields now rendered inline by upstream) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -207,10 +207,6 @@ export default {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
gardenStage: {},
|
gardenStage: {},
|
||||||
aiTextLevel: {},
|
|
||||||
aiCodeLevel: {},
|
|
||||||
aiTools: {},
|
|
||||||
aiDescription: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
note: {
|
note: {
|
||||||
@@ -221,10 +217,6 @@ export default {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
gardenStage: {},
|
gardenStage: {},
|
||||||
aiTextLevel: {},
|
|
||||||
aiCodeLevel: {},
|
|
||||||
aiTools: {},
|
|
||||||
aiDescription: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
bookmark: {
|
bookmark: {
|
||||||
@@ -235,10 +227,6 @@ export default {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
gardenStage: {},
|
gardenStage: {},
|
||||||
aiTextLevel: {},
|
|
||||||
aiCodeLevel: {},
|
|
||||||
aiTools: {},
|
|
||||||
aiDescription: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
like: {
|
like: {
|
||||||
@@ -259,10 +247,6 @@ export default {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
gardenStage: {},
|
gardenStage: {},
|
||||||
aiTextLevel: {},
|
|
||||||
aiCodeLevel: {},
|
|
||||||
aiTools: {},
|
|
||||||
aiDescription: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
photo: {
|
photo: {
|
||||||
@@ -277,10 +261,6 @@ export default {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
gardenStage: {},
|
gardenStage: {},
|
||||||
aiTextLevel: {},
|
|
||||||
aiCodeLevel: {},
|
|
||||||
aiTools: {},
|
|
||||||
aiDescription: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
reply: {
|
reply: {
|
||||||
@@ -291,10 +271,6 @@ export default {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
gardenStage: {},
|
gardenStage: {},
|
||||||
aiTextLevel: {},
|
|
||||||
aiCodeLevel: {},
|
|
||||||
aiTools: {},
|
|
||||||
aiDescription: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
@@ -305,10 +281,6 @@ export default {
|
|||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
gardenStage: {},
|
gardenStage: {},
|
||||||
aiTextLevel: {},
|
|
||||||
aiCodeLevel: {},
|
|
||||||
aiTools: {},
|
|
||||||
aiDescription: {},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+13
-13
@@ -26,7 +26,7 @@
|
|||||||
"@rmdes/indiekit-endpoint-lastfm": "^1.0.12",
|
"@rmdes/indiekit-endpoint-lastfm": "^1.0.12",
|
||||||
"@rmdes/indiekit-endpoint-microsub": "github:svemagie/indiekit-endpoint-microsub#bookmarks-import",
|
"@rmdes/indiekit-endpoint-microsub": "github:svemagie/indiekit-endpoint-microsub#bookmarks-import",
|
||||||
"@rmdes/indiekit-endpoint-podroll": "^1.0.11",
|
"@rmdes/indiekit-endpoint-podroll": "^1.0.11",
|
||||||
"@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.25",
|
"@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.44",
|
||||||
"@rmdes/indiekit-endpoint-readlater": "github:rmdes/indiekit-endpoint-readlater",
|
"@rmdes/indiekit-endpoint-readlater": "github:rmdes/indiekit-endpoint-readlater",
|
||||||
"@rmdes/indiekit-endpoint-rss": "^1.0.14",
|
"@rmdes/indiekit-endpoint-rss": "^1.0.14",
|
||||||
"@rmdes/indiekit-endpoint-webmention-io": "^1.0.7",
|
"@rmdes/indiekit-endpoint-webmention-io": "^1.0.7",
|
||||||
@@ -1515,14 +1515,14 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@indiekit/endpoint-posts": {
|
"node_modules/@indiekit/endpoint-posts": {
|
||||||
"name": "@rmdes/indiekit-endpoint-posts",
|
"name": "@rmdes/indiekit-endpoint-posts",
|
||||||
"version": "1.0.0-beta.41",
|
"version": "1.0.0-beta.44",
|
||||||
"resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-posts/-/indiekit-endpoint-posts-1.0.0-beta.41.tgz",
|
"resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-posts/-/indiekit-endpoint-posts-1.0.0-beta.44.tgz",
|
||||||
"integrity": "sha512-lJgDVGHvHT0bYLstBmHWMdLWLZzhC0bclspLM/NFVvBqu104DEcRwHm4k5H6n/+JUHQCmopsaWMHIe0Umgzv2w==",
|
"integrity": "sha512-xGvsmqIOiQU0tpocDmNJw0CRvdj98zOVzpX+qzpPHBcYXSUZmbhRHd9/OaOD4dOWsmNT4dHYhmJ0OjTdUMTtUw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@indiekit/endpoint-micropub": "^1.0.0-beta.25",
|
"@indiekit/endpoint-micropub": "^1.0.0-beta.27",
|
||||||
"@indiekit/error": "^1.0.0-beta.25",
|
"@indiekit/error": "^1.0.0-beta.27",
|
||||||
"@indiekit/frontend": "^1.0.0-beta.25",
|
"@indiekit/frontend": "^1.0.0-beta.27",
|
||||||
"@indiekit/util": "^1.0.0-beta.25",
|
"@indiekit/util": "^1.0.0-beta.25",
|
||||||
"@paulrobertlloyd/mf2tojf2": "^3.0.0",
|
"@paulrobertlloyd/mf2tojf2": "^3.0.0",
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
@@ -2563,14 +2563,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rmdes/indiekit-endpoint-posts": {
|
"node_modules/@rmdes/indiekit-endpoint-posts": {
|
||||||
"version": "1.0.0-beta.25",
|
"version": "1.0.0-beta.44",
|
||||||
"resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-posts/-/indiekit-endpoint-posts-1.0.0-beta.25.tgz",
|
"resolved": "https://registry.npmjs.org/@rmdes/indiekit-endpoint-posts/-/indiekit-endpoint-posts-1.0.0-beta.44.tgz",
|
||||||
"integrity": "sha512-Rh519QYIOQK3pipUESfp1lv2mDZSwVX0W32KPsMwZrs77LFCu4KCI+YjedgSr7dwZZO0qH0j24QhJ7SOkn+DaA==",
|
"integrity": "sha512-xGvsmqIOiQU0tpocDmNJw0CRvdj98zOVzpX+qzpPHBcYXSUZmbhRHd9/OaOD4dOWsmNT4dHYhmJ0OjTdUMTtUw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@indiekit/endpoint-micropub": "^1.0.0-beta.25",
|
"@indiekit/endpoint-micropub": "^1.0.0-beta.27",
|
||||||
"@indiekit/error": "^1.0.0-beta.25",
|
"@indiekit/error": "^1.0.0-beta.27",
|
||||||
"@indiekit/frontend": "^1.0.0-beta.25",
|
"@indiekit/frontend": "^1.0.0-beta.27",
|
||||||
"@indiekit/util": "^1.0.0-beta.25",
|
"@indiekit/util": "^1.0.0-beta.25",
|
||||||
"@paulrobertlloyd/mf2tojf2": "^3.0.0",
|
"@paulrobertlloyd/mf2tojf2": "^3.0.0",
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
|
|||||||
+4
-4
@@ -4,8 +4,8 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs",
|
"postinstall": "xattr -w com.apple.fileprovider.ignore#P 1 node_modules 2>/dev/null || true && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs",
|
||||||
"serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-posts-ai-fields.mjs && node scripts/patch-endpoint-posts-ai-cleanup.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-preset-eleventy-ai-frontmatter.mjs && node scripts/patch-micropub-ai-block-resync.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-endpoint-posts-prefill-url.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-endpoint-posts-search-tags.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
"serve": "export NODE_ENV=${NODE_ENV:-production} INDIEKIT_DEBUG=${INDIEKIT_DEBUG:-0} && node scripts/preflight-production-security.mjs && node scripts/preflight-mongo-connection.mjs && node scripts/preflight-activitypub-rsa-key.mjs && node scripts/preflight-activitypub-profile-urls.mjs && node scripts/patch-lightningcss.mjs && node scripts/patch-endpoint-media-scope.mjs && node scripts/patch-endpoint-media-sharp-runtime.mjs && node scripts/patch-frontend-sharp-runtime.mjs && node scripts/patch-endpoint-files-upload-route.mjs && node scripts/patch-endpoint-files-upload-locales.mjs && node scripts/patch-endpoint-activitypub-locales.mjs && node scripts/patch-endpoint-homepage-locales.mjs && node scripts/patch-endpoint-homepage-identity-defaults.mjs && node scripts/patch-federation-unlisted-guards.mjs && node scripts/patch-endpoint-micropub-where-note-visibility.mjs && node scripts/patch-endpoint-podroll-opml-upload.mjs && node scripts/patch-frontend-serviceworker-file.mjs && node scripts/patch-endpoint-comments-locales.mjs && node scripts/patch-endpoint-posts-locales.mjs && node scripts/patch-endpoint-conversations-locales.mjs && node scripts/patch-conversations-collection-guards.mjs && node scripts/patch-indiekit-routes-rate-limits.mjs && node scripts/patch-indiekit-error-production-stack.mjs && node scripts/patch-indieauth-devmode-guard.mjs && node scripts/patch-listening-endpoint-runtime-guards.mjs && node scripts/patch-endpoint-github-changelog-categories.mjs && node scripts/patch-microsub-reader-ap-dispatch.mjs && node scripts/patch-endpoint-blogroll-feeds-alias.mjs && node scripts/patch-endpoint-posts-uid-lookup.mjs && node scripts/patch-conversations-bluesky-self-filter.mjs && node scripts/patch-conversations-bluesky-cursor-fix.mjs && node scripts/patch-endpoint-micropub-source-filter.mjs && node scripts/patch-syndicate-force-checked-default.mjs && node scripts/patch-ap-url-lookup-api.mjs && node scripts/patch-ap-allow-private-address.mjs && node scripts/patch-ap-repost-commentary.mjs && node scripts/patch-endpoint-posts-fetch-diagnostic.mjs && node scripts/patch-micropub-fetch-internal-url.mjs && node scripts/patch-webmention-sender-hentry-syntax.mjs && node scripts/patch-webmention-sender-retry.mjs && node scripts/patch-webmention-sender-livefetch.mjs && node scripts/patch-webmention-sender-empty-details.mjs && node scripts/patch-bluesky-syndicator-internal-url.mjs && node node_modules/@indiekit/indiekit/bin/cli.js serve --config indiekit.config.mjs",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"@indiekit/endpoint-auth": "npm:@rmdes/indiekit-endpoint-auth@^1.0.0-beta.27",
|
"@indiekit/endpoint-auth": "npm:@rmdes/indiekit-endpoint-auth@^1.0.0-beta.27",
|
||||||
"@indiekit/endpoint-files": "npm:@rmdes/indiekit-endpoint-files@^1.0.0",
|
"@indiekit/endpoint-files": "npm:@rmdes/indiekit-endpoint-files@^1.0.0",
|
||||||
"@indiekit/endpoint-micropub": "npm:@rmdes/indiekit-endpoint-micropub@^1.0.0-beta.26",
|
"@indiekit/endpoint-micropub": "npm:@rmdes/indiekit-endpoint-micropub@^1.0.0-beta.26",
|
||||||
"@indiekit/endpoint-posts": "npm:@rmdes/indiekit-endpoint-posts@^1.0.0-beta.41",
|
"@indiekit/endpoint-posts": "npm:@rmdes/indiekit-endpoint-posts@^1.0.0-beta.44",
|
||||||
"@indiekit/endpoint-share": "npm:@rmdes/indiekit-endpoint-share@^1.0.0",
|
"@indiekit/endpoint-share": "npm:@rmdes/indiekit-endpoint-share@^1.0.0",
|
||||||
"@indiekit/endpoint-syndicate": "npm:@rmdes/indiekit-endpoint-syndicate@^1.0.0-beta.32",
|
"@indiekit/endpoint-syndicate": "npm:@rmdes/indiekit-endpoint-syndicate@^1.0.0-beta.32",
|
||||||
"@indiekit/frontend": "npm:@rmdes/indiekit-frontend@^1.0.0-beta.37"
|
"@indiekit/frontend": "npm:@rmdes/indiekit-frontend@^1.0.0-beta.37"
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"@rmdes/indiekit-endpoint-lastfm": "^1.0.12",
|
"@rmdes/indiekit-endpoint-lastfm": "^1.0.12",
|
||||||
"@rmdes/indiekit-endpoint-microsub": "github:svemagie/indiekit-endpoint-microsub#bookmarks-import",
|
"@rmdes/indiekit-endpoint-microsub": "github:svemagie/indiekit-endpoint-microsub#bookmarks-import",
|
||||||
"@rmdes/indiekit-endpoint-podroll": "^1.0.11",
|
"@rmdes/indiekit-endpoint-podroll": "^1.0.11",
|
||||||
"@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.25",
|
"@rmdes/indiekit-endpoint-posts": "^1.0.0-beta.44",
|
||||||
"@rmdes/indiekit-endpoint-readlater": "github:rmdes/indiekit-endpoint-readlater",
|
"@rmdes/indiekit-endpoint-readlater": "github:rmdes/indiekit-endpoint-readlater",
|
||||||
"@rmdes/indiekit-endpoint-rss": "^1.0.14",
|
"@rmdes/indiekit-endpoint-rss": "^1.0.14",
|
||||||
"@rmdes/indiekit-endpoint-webmention-io": "^1.0.7",
|
"@rmdes/indiekit-endpoint-webmention-io": "^1.0.7",
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
import { access, readFile, writeFile } from "node:fs/promises";
|
|
||||||
|
|
||||||
const candidates = [
|
|
||||||
"node_modules/@rmdes/indiekit-endpoint-posts/lib/controllers/form.js",
|
|
||||||
"node_modules/@indiekit/endpoint-posts/lib/controllers/form.js",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts/lib/controllers/form.js",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-posts/lib/controllers/form.js",
|
|
||||||
];
|
|
||||||
|
|
||||||
const marker = "Always remove legacy hyphenated keys — superseded by camelCase equivalents.";
|
|
||||||
|
|
||||||
const oldSnippet = [
|
|
||||||
" // Easy MDE appends `image` value to formData for last image uploaded",
|
|
||||||
" delete values.image;",
|
|
||||||
"",
|
|
||||||
" // Remove empty AI metadata fields so Micropub payload stays lean.",
|
|
||||||
" for (const key of [",
|
|
||||||
" \"aiTextLevel\",",
|
|
||||||
" \"aiCodeLevel\",",
|
|
||||||
" \"aiTools\",",
|
|
||||||
" \"aiDescription\",",
|
|
||||||
" \"ai-text-level\",",
|
|
||||||
" \"ai-code-level\",",
|
|
||||||
" \"ai-tools\",",
|
|
||||||
" \"ai-description\",",
|
|
||||||
" ]) {",
|
|
||||||
" if (",
|
|
||||||
" values[key] === undefined ||",
|
|
||||||
" values[key] === null ||",
|
|
||||||
" String(values[key]).trim() === \"\"",
|
|
||||||
" ) {",
|
|
||||||
" delete values[key];",
|
|
||||||
" }",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" const mf2 = jf2ToMf2({ properties: sanitise(values) });",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
const newSnippet = [
|
|
||||||
" // Easy MDE appends `image` value to formData for last image uploaded",
|
|
||||||
" delete values.image;",
|
|
||||||
"",
|
|
||||||
" // Remove empty AI metadata fields so Micropub payload stays lean.",
|
|
||||||
" for (const key of [",
|
|
||||||
" \"aiTextLevel\",",
|
|
||||||
" \"aiCodeLevel\",",
|
|
||||||
" \"aiTools\",",
|
|
||||||
" \"aiDescription\",",
|
|
||||||
" ]) {",
|
|
||||||
" if (",
|
|
||||||
" values[key] === undefined ||",
|
|
||||||
" values[key] === null ||",
|
|
||||||
" String(values[key]).trim() === \"\"",
|
|
||||||
" ) {",
|
|
||||||
" delete values[key];",
|
|
||||||
" }",
|
|
||||||
" }",
|
|
||||||
" // Always remove legacy hyphenated keys — superseded by camelCase equivalents.",
|
|
||||||
" delete values[\"ai-text-level\"];",
|
|
||||||
" delete values[\"ai-code-level\"];",
|
|
||||||
" delete values[\"ai-tools\"];",
|
|
||||||
" delete values[\"ai-description\"];",
|
|
||||||
"",
|
|
||||||
" const mf2 = jf2ToMf2({ properties: sanitise(values) });",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
async function exists(filePath) {
|
|
||||||
try {
|
|
||||||
await access(filePath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let checked = 0;
|
|
||||||
let patched = 0;
|
|
||||||
|
|
||||||
for (const filePath of candidates) {
|
|
||||||
if (!(await exists(filePath))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
checked += 1;
|
|
||||||
|
|
||||||
const source = await readFile(filePath, "utf8");
|
|
||||||
|
|
||||||
if (source.includes(marker)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!source.includes(oldSnippet)) {
|
|
||||||
// Already has AI field cleanup in some form — skip silently
|
|
||||||
if (
|
|
||||||
source.includes('"ai-text-level"') ||
|
|
||||||
source.includes('"aiTextLevel"') ||
|
|
||||||
!source.includes("jf2ToMf2")
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
console.warn(
|
|
||||||
`[postinstall] Skipping endpoint-posts AI cleanup patch for ${filePath}: upstream format changed`,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = source.replace(oldSnippet, newSnippet);
|
|
||||||
await writeFile(filePath, updated, "utf8");
|
|
||||||
patched += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checked === 0) {
|
|
||||||
console.log("[postinstall] No endpoint-posts form controller files found");
|
|
||||||
} else if (patched === 0) {
|
|
||||||
console.log("[postinstall] endpoint-posts AI cleanup patch already applied");
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`[postinstall] Patched endpoint-posts AI cleanup in ${patched} file(s)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
const endpointCandidates = [
|
|
||||||
"node_modules/@rmdes/indiekit-endpoint-posts",
|
|
||||||
"node_modules/@indiekit/endpoint-posts",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-posts",
|
|
||||||
];
|
|
||||||
|
|
||||||
const templates = {
|
|
||||||
"aiTextLevel-field.njk": [
|
|
||||||
'{% set aiTextLevelValue = fieldData("aiTextLevel").value or fieldData("ai-text-level").value or (properties.ai.textLevel if properties.ai and properties.ai.textLevel is defined else properties.aiTextLevel) or properties["ai-text-level"] or "0" %}',
|
|
||||||
"{{ radios({",
|
|
||||||
' name: "aiTextLevel",',
|
|
||||||
" values: aiTextLevelValue,",
|
|
||||||
" fieldset: {",
|
|
||||||
' legend: "AI text level",',
|
|
||||||
" optional: true",
|
|
||||||
" },",
|
|
||||||
" items: [{",
|
|
||||||
' label: "0 - None",',
|
|
||||||
' value: "0"',
|
|
||||||
" }, {",
|
|
||||||
' label: "1 - Editorial assistance",',
|
|
||||||
' value: "1"',
|
|
||||||
" }, {",
|
|
||||||
' label: "2 - Co-drafting",',
|
|
||||||
' value: "2"',
|
|
||||||
" }, {",
|
|
||||||
' label: "3 - AI-generated (human reviewed)",',
|
|
||||||
' value: "3"',
|
|
||||||
" }]",
|
|
||||||
"}) }}",
|
|
||||||
].join("\n"),
|
|
||||||
"aiCodeLevel-field.njk": [
|
|
||||||
'{% set aiCodeLevelValue = fieldData("aiCodeLevel").value or fieldData("ai-code-level").value or (properties.ai.codeLevel if properties.ai and properties.ai.codeLevel is defined else properties.aiCodeLevel) or properties["ai-code-level"] or "0" %}',
|
|
||||||
"{{ radios({",
|
|
||||||
' name: "aiCodeLevel",',
|
|
||||||
" values: aiCodeLevelValue,",
|
|
||||||
" fieldset: {",
|
|
||||||
' legend: "AI code level",',
|
|
||||||
" optional: true",
|
|
||||||
" },",
|
|
||||||
" items: [{",
|
|
||||||
' label: "0 - Human-written",',
|
|
||||||
' value: "0"',
|
|
||||||
" }, {",
|
|
||||||
' label: "1 - AI-assisted",',
|
|
||||||
' value: "1"',
|
|
||||||
" }, {",
|
|
||||||
' label: "2 - Primarily AI-generated",',
|
|
||||||
' value: "2"',
|
|
||||||
" }]",
|
|
||||||
"}) }}",
|
|
||||||
].join("\n"),
|
|
||||||
"aiTools-field.njk": [
|
|
||||||
'{% set aiToolsValue = fieldData("aiTools").value or fieldData("ai-tools").value or (properties.ai.aiTools if properties.ai and properties.ai.aiTools is defined else properties.aiTools) or properties["ai-tools"] %}',
|
|
||||||
"{{ input({",
|
|
||||||
' name: "aiTools",',
|
|
||||||
" value: aiToolsValue,",
|
|
||||||
' label: "AI Tools",',
|
|
||||||
' hint: "Optional, comma-separated (e.g. Claude, ChatGPT, Copilot)",',
|
|
||||||
" optional: true",
|
|
||||||
"}) }}",
|
|
||||||
].join("\n"),
|
|
||||||
"aiDescription-field.njk": [
|
|
||||||
'{% set aiDescriptionValue = fieldData("aiDescription").value or fieldData("ai-description").value or (properties.ai.aiDescription if properties.ai and properties.ai.aiDescription is defined else properties.aiDescription) or properties["ai-description"] %}',
|
|
||||||
"{{ textarea({",
|
|
||||||
' name: "aiDescription",',
|
|
||||||
" value: aiDescriptionValue,",
|
|
||||||
' label: "AI usage note",',
|
|
||||||
' hint: "Optional: short note describing how AI was used",',
|
|
||||||
" optional: true",
|
|
||||||
"}) }}",
|
|
||||||
].join("\n"),
|
|
||||||
};
|
|
||||||
|
|
||||||
async function exists(filePath) {
|
|
||||||
try {
|
|
||||||
await access(filePath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let checkedEndpoints = 0;
|
|
||||||
let checkedFiles = 0;
|
|
||||||
let patchedFiles = 0;
|
|
||||||
|
|
||||||
for (const endpointPath of endpointCandidates) {
|
|
||||||
if (!(await exists(endpointPath))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const includeDir = path.join(endpointPath, "includes", "post-types");
|
|
||||||
if (!(await exists(includeDir))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkedEndpoints += 1;
|
|
||||||
await mkdir(includeDir, { recursive: true });
|
|
||||||
|
|
||||||
for (const [fileName, template] of Object.entries(templates)) {
|
|
||||||
checkedFiles += 1;
|
|
||||||
|
|
||||||
const filePath = path.join(includeDir, fileName);
|
|
||||||
const desired = `${template}\n`;
|
|
||||||
|
|
||||||
let current = "";
|
|
||||||
if (await exists(filePath)) {
|
|
||||||
current = await readFile(filePath, "utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current === desired) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeFile(filePath, desired, "utf8");
|
|
||||||
patchedFiles += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkedEndpoints === 0) {
|
|
||||||
console.log("[postinstall] No endpoint-posts package directories found");
|
|
||||||
} else if (checkedFiles === 0) {
|
|
||||||
console.log("[postinstall] No endpoint-posts AI field templates checked");
|
|
||||||
} else if (patchedFiles === 0) {
|
|
||||||
console.log("[postinstall] endpoint-posts AI field templates already patched");
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`[postinstall] Patched endpoint-posts AI field templates in ${patchedFiles}/${checkedFiles} file(s)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
/**
|
|
||||||
* Patch: pre-fill reference URL when creating posts from /news "Post" button.
|
|
||||||
*
|
|
||||||
* share-post.js opens /posts/create?type=like&url=<link>&name=<title>
|
|
||||||
* but postData.create only reads request.body for properties, ignoring query params.
|
|
||||||
*
|
|
||||||
* Fix: in postData.create, when properties is empty and request.query.url is present,
|
|
||||||
* seed properties with the correct field name for that post type:
|
|
||||||
* like → like-of
|
|
||||||
* bookmark → bookmark-of
|
|
||||||
* reply → in-reply-to
|
|
||||||
* repost → repost-of
|
|
||||||
* and optionally seed name/bookmark title from request.query.name.
|
|
||||||
*/
|
|
||||||
import { access, readFile, writeFile } from "node:fs/promises";
|
|
||||||
|
|
||||||
const patchSpecs = [
|
|
||||||
{
|
|
||||||
name: "posts-prefill-url-from-query",
|
|
||||||
marker: "prefill reference URL from query param",
|
|
||||||
candidates: [
|
|
||||||
"node_modules/@rmdes/indiekit-endpoint-posts/lib/middleware/post-data.js",
|
|
||||||
"node_modules/@indiekit/endpoint-posts/lib/middleware/post-data.js",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts/lib/middleware/post-data.js",
|
|
||||||
],
|
|
||||||
oldSnippet: ` const postType = request.query.type || "note";
|
|
||||||
const properties = request.body || {};`,
|
|
||||||
newSnippet: ` const postType = request.query.type || "note";
|
|
||||||
// prefill reference URL from query param when opening from share-post button
|
|
||||||
let properties = request.body || {};
|
|
||||||
if (Object.entries(properties).length === 0 && request.query.url) {
|
|
||||||
const refUrl = request.query.url;
|
|
||||||
const refName = request.query.name || "";
|
|
||||||
const urlFieldByType = {
|
|
||||||
like: "like-of",
|
|
||||||
bookmark: "bookmark-of",
|
|
||||||
reply: "in-reply-to",
|
|
||||||
repost: "repost-of",
|
|
||||||
};
|
|
||||||
const urlField = urlFieldByType[postType];
|
|
||||||
if (urlField) {
|
|
||||||
properties = { [urlField]: refUrl };
|
|
||||||
if (postType === "bookmark" && refName) {
|
|
||||||
properties.name = refName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
async function exists(filePath) {
|
|
||||||
try {
|
|
||||||
await access(filePath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let filesChecked = 0;
|
|
||||||
let filesPatched = 0;
|
|
||||||
|
|
||||||
for (const spec of patchSpecs) {
|
|
||||||
let foundAnyTarget = false;
|
|
||||||
|
|
||||||
for (const filePath of spec.candidates) {
|
|
||||||
if (!(await exists(filePath))) continue;
|
|
||||||
|
|
||||||
foundAnyTarget = true;
|
|
||||||
filesChecked += 1;
|
|
||||||
|
|
||||||
const source = await readFile(filePath, "utf8");
|
|
||||||
|
|
||||||
if (source.includes(spec.marker)) continue;
|
|
||||||
if (!source.includes(spec.oldSnippet)) {
|
|
||||||
console.log(`[postinstall] ${spec.name}: snippet not found in ${filePath}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = source.replace(spec.oldSnippet, spec.newSnippet);
|
|
||||||
await writeFile(filePath, updated, "utf8");
|
|
||||||
filesPatched += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundAnyTarget) {
|
|
||||||
console.log(`[postinstall] ${spec.name}: no target files found`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filesChecked === 0) {
|
|
||||||
console.log("[postinstall] No posts endpoint post-data.js found");
|
|
||||||
} else if (filesPatched === 0) {
|
|
||||||
console.log("[postinstall] posts prefill-url already patched");
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`[postinstall] Patched posts prefill-url in ${filesPatched}/${filesChecked} file(s)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
/**
|
|
||||||
* Patch: add search input and tag chips to the /posts list page.
|
|
||||||
*
|
|
||||||
* Part A – posts.js controller:
|
|
||||||
* - forward `category` and `search` query params to the Micropub source query
|
|
||||||
* - expose `item.tagLinks` (pre-encoded href + text) for each post
|
|
||||||
* - pass `activeCategory` and `activeSearch` to the template
|
|
||||||
*
|
|
||||||
* Part B – posts.njk view:
|
|
||||||
* - replace the cardGrid call with a custom loop that appends clickable
|
|
||||||
* tag chips under each card
|
|
||||||
* - add a search form above the grid
|
|
||||||
*/
|
|
||||||
import { access, readFile, writeFile } from "node:fs/promises";
|
|
||||||
|
|
||||||
async function exists(filePath) {
|
|
||||||
try {
|
|
||||||
await access(filePath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Part A: posts controller ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const controllerCandidates = [
|
|
||||||
"node_modules/@rmdes/indiekit-endpoint-posts/lib/controllers/posts.js",
|
|
||||||
"node_modules/@indiekit/endpoint-posts/lib/controllers/posts.js",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts/lib/controllers/posts.js",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-posts/lib/controllers/posts.js",
|
|
||||||
];
|
|
||||||
|
|
||||||
const controllerMarker = "// search-tags controller patch";
|
|
||||||
|
|
||||||
// 1) forward category + search to the micropub URL
|
|
||||||
const oldForward = ` const { after, before, success } = request.query;
|
|
||||||
const limit = Number(request.query.limit) || 12;
|
|
||||||
|
|
||||||
const micropubUrl = new URL(application.micropubEndpoint);
|
|
||||||
micropubUrl.searchParams.append("q", "source");
|
|
||||||
micropubUrl.searchParams.append("limit", String(limit));
|
|
||||||
|
|
||||||
if (after) {
|
|
||||||
micropubUrl.searchParams.append("after", String(after));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (before) {
|
|
||||||
micropubUrl.searchParams.append("before", String(before));
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const newForward = ` // search-tags controller patch
|
|
||||||
const { after, before, category, search, success } = request.query;
|
|
||||||
const limit = Number(request.query.limit) || 12;
|
|
||||||
|
|
||||||
const micropubUrl = new URL(application.micropubEndpoint);
|
|
||||||
micropubUrl.searchParams.append("q", "source");
|
|
||||||
micropubUrl.searchParams.append("limit", String(limit));
|
|
||||||
|
|
||||||
if (after) {
|
|
||||||
micropubUrl.searchParams.append("after", String(after));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (before) {
|
|
||||||
micropubUrl.searchParams.append("before", String(before));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (category) {
|
|
||||||
micropubUrl.searchParams.append("category", String(category));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
micropubUrl.searchParams.append("search", String(search));
|
|
||||||
}`;
|
|
||||||
|
|
||||||
// 2) add tagLinks to each post item
|
|
||||||
const oldBadges = ` item.badges = getPostStatusBadges(item, response);
|
|
||||||
|
|
||||||
return item;`;
|
|
||||||
|
|
||||||
const newBadges = ` item.badges = getPostStatusBadges(item, response);
|
|
||||||
const rawTags = Array.isArray(item.category)
|
|
||||||
? item.category
|
|
||||||
: item.category
|
|
||||||
? [item.category]
|
|
||||||
: [];
|
|
||||||
item.tagLinks = rawTags.map((t) => ({
|
|
||||||
text: t,
|
|
||||||
href: \`?category=\${encodeURIComponent(t)}\`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return item;`;
|
|
||||||
|
|
||||||
// 3) pass activeCategory + activeSearch to the render call
|
|
||||||
const oldRender = ` statusTypes,
|
|
||||||
success,
|
|
||||||
});`;
|
|
||||||
|
|
||||||
const newRender = ` statusTypes,
|
|
||||||
success,
|
|
||||||
activeCategory: category || "",
|
|
||||||
activeSearch: search || "",
|
|
||||||
});`;
|
|
||||||
|
|
||||||
// ─── Part B: posts view ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const viewCandidates = [
|
|
||||||
"node_modules/@rmdes/indiekit-endpoint-posts/views/posts.njk",
|
|
||||||
"node_modules/@indiekit/endpoint-posts/views/posts.njk",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-posts/views/posts.njk",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-posts/views/posts.njk",
|
|
||||||
];
|
|
||||||
|
|
||||||
const viewMarker = "{# search-tags view patch #}";
|
|
||||||
|
|
||||||
const oldView = `{% extends "document.njk" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{%- if posts.length > 0 %}
|
|
||||||
{{ cardGrid({
|
|
||||||
cardSize: "16rem",
|
|
||||||
items: posts
|
|
||||||
}) }}
|
|
||||||
{{ pagination(cursor) }}
|
|
||||||
{%- else -%}
|
|
||||||
{{ prose({ text: __("posts.posts.none") }) }}
|
|
||||||
{%- endif %}
|
|
||||||
{% endblock %}`;
|
|
||||||
|
|
||||||
const newView = `{% extends "document.njk" %}
|
|
||||||
{# search-tags view patch #}
|
|
||||||
{% block content %}
|
|
||||||
<form method="get" action="">
|
|
||||||
<div style="display:flex;gap:0.5rem;align-items:flex-end;flex-wrap:wrap;margin-bottom:1.5rem">
|
|
||||||
{{ input({
|
|
||||||
name: "search",
|
|
||||||
label: "Search",
|
|
||||||
value: activeSearch,
|
|
||||||
type: "search",
|
|
||||||
optional: true,
|
|
||||||
field: { classes: "-!-flex-grow" }
|
|
||||||
}) }}
|
|
||||||
{{ button({ text: "Search", type: "submit" }) }}
|
|
||||||
{%- if activeSearch or activeCategory %}
|
|
||||||
<a href="{{ parentUrl }}" class="button" style="align-self:flex-end">Clear</a>
|
|
||||||
{%- endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{%- if activeCategory %}
|
|
||||||
<p style="margin-bottom:1rem">Filtered by tag: <strong>{{ activeCategory }}</strong></p>
|
|
||||||
{%- endif %}
|
|
||||||
{%- if posts.length > 0 %}
|
|
||||||
<ol class="card-grid" role="list" style="--min-card-size: 16rem">
|
|
||||||
{%- for item in posts %}
|
|
||||||
<li class="card-grid__item">
|
|
||||||
<article class="card">
|
|
||||||
{%- if item.photo %}
|
|
||||||
<div class="card__photo">
|
|
||||||
<img src="{{ item.photo.url | imageUrl(application, width=240, height=240) }}" alt="{{ item.photo.alt }}" width="240" height="240" decoding="async" loading="lazy" onerror="this.src='/assets/not-found.svg'">
|
|
||||||
</div>
|
|
||||||
{%- endif %}
|
|
||||||
<div class="card__body">
|
|
||||||
{%- if item.title %}
|
|
||||||
<h2 class="card__title">
|
|
||||||
{%- if item.url %}
|
|
||||||
<a href="{{ item.url }}" rel="bookmark">
|
|
||||||
{%- endif %}
|
|
||||||
{{- icon(item.icon) if item.icon -}}
|
|
||||||
{{- item.title | safe -}}
|
|
||||||
{%- if item.url %}
|
|
||||||
</a>
|
|
||||||
{%- endif %}
|
|
||||||
</h2>
|
|
||||||
{%- endif %}
|
|
||||||
{{ prose({ classes: "card__meta", text: item.description.text, html: item.description.html }) | indent(10) if item.description }}
|
|
||||||
{%- set hasNonGarden = false %}
|
|
||||||
{%- for tl in item.tagLinks %}
|
|
||||||
{%- if tl.text != "garden" %}{%- set hasNonGarden = true %}{%- endif %}
|
|
||||||
{%- endfor %}
|
|
||||||
{%- if hasNonGarden %}
|
|
||||||
<div style="padding:0.25rem 0 0.5rem;display:flex;flex-wrap:wrap;gap:0.25rem">
|
|
||||||
{%- for tl in item.tagLinks %}
|
|
||||||
{%- if tl.text != "garden" %}
|
|
||||||
<a href="{{ tl.href }}" class="tag" style="font-size:0.75em">{{ tl.text }}</a>
|
|
||||||
{%- endif %}
|
|
||||||
{%- endfor %}
|
|
||||||
</div>
|
|
||||||
{%- endif %}
|
|
||||||
{%- if item.published or item.badges %}
|
|
||||||
<footer class="card__footer" style="display:flex;align-items:center;gap:0.5rem">
|
|
||||||
{%- for badgeItem in item.badges %}
|
|
||||||
{{ badge(badgeItem) | indent(12) if badgeItem }}
|
|
||||||
{%- endfor %}
|
|
||||||
{%- for tl in item.tagLinks %}
|
|
||||||
{%- if tl.text == "garden" %}
|
|
||||||
<a href="{{ tl.href }}" class="tag" style="margin-left:auto;font-size:0.75em;flex-shrink:0">garden</a>
|
|
||||||
{%- endif %}
|
|
||||||
{%- endfor %}
|
|
||||||
<time datetime="{{ item.published }}">
|
|
||||||
{{ item.published | date("PPp", { locale: item.locale, timeZone: application.timeZone }) }}
|
|
||||||
</time>
|
|
||||||
</footer>
|
|
||||||
{%- endif %}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</li>
|
|
||||||
{%- endfor %}
|
|
||||||
</ol>
|
|
||||||
{{ pagination(cursor) }}
|
|
||||||
{%- else -%}
|
|
||||||
{{ prose({ text: __("posts.posts.none") }) }}
|
|
||||||
{%- endif %}
|
|
||||||
{% endblock %}`;
|
|
||||||
|
|
||||||
// ─── Apply patches ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
let controllerChecked = 0;
|
|
||||||
let controllerPatched = 0;
|
|
||||||
|
|
||||||
for (const filePath of controllerCandidates) {
|
|
||||||
if (!(await exists(filePath))) continue;
|
|
||||||
controllerChecked += 1;
|
|
||||||
|
|
||||||
let source = await readFile(filePath, "utf8");
|
|
||||||
if (source.includes(controllerMarker)) continue;
|
|
||||||
|
|
||||||
let changed = false;
|
|
||||||
|
|
||||||
for (const [oldSnip, newSnip] of [
|
|
||||||
[oldForward, newForward],
|
|
||||||
[oldBadges, newBadges],
|
|
||||||
[oldRender, newRender],
|
|
||||||
]) {
|
|
||||||
if (!source.includes(oldSnip)) {
|
|
||||||
// Beta.41+ has native filter/search/sort built in — skip silently
|
|
||||||
if (source.includes("buildFilterQuery") || source.includes("filters.postType")) {
|
|
||||||
changed = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
console.warn(
|
|
||||||
`[postinstall] posts search-tags: snippet not found in ${filePath}, skipping`,
|
|
||||||
);
|
|
||||||
changed = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
source = source.replace(oldSnip, newSnip);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
await writeFile(filePath, source, "utf8");
|
|
||||||
controllerPatched += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let viewChecked = 0;
|
|
||||||
let viewPatched = 0;
|
|
||||||
|
|
||||||
for (const filePath of viewCandidates) {
|
|
||||||
if (!(await exists(filePath))) continue;
|
|
||||||
viewChecked += 1;
|
|
||||||
|
|
||||||
const source = await readFile(filePath, "utf8");
|
|
||||||
if (source.includes(viewMarker)) continue;
|
|
||||||
|
|
||||||
if (!source.includes(oldView)) {
|
|
||||||
// Beta.41+ has native filter/sort UI built in — skip silently
|
|
||||||
if (source.includes("posts-filter-row") || source.includes("posts.filter.type")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
console.warn(
|
|
||||||
`[postinstall] posts search-tags: view not found in ${filePath}, skipping`,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = source.replace(oldView, newView);
|
|
||||||
await writeFile(filePath, updated, "utf8");
|
|
||||||
viewPatched += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controllerChecked === 0 && viewChecked === 0) {
|
|
||||||
console.log("[postinstall] No endpoint-posts files found");
|
|
||||||
} else {
|
|
||||||
if (controllerPatched === 0 && controllerChecked > 0) {
|
|
||||||
console.log("[postinstall] posts search-tags controller already patched");
|
|
||||||
} else if (controllerPatched > 0) {
|
|
||||||
console.log(
|
|
||||||
`[postinstall] Patched posts search-tags controller in ${controllerPatched} file(s)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (viewPatched === 0 && viewChecked > 0) {
|
|
||||||
console.log("[postinstall] posts search-tags view already patched");
|
|
||||||
} else if (viewPatched > 0) {
|
|
||||||
console.log(
|
|
||||||
`[postinstall] Patched posts search-tags view in ${viewPatched} file(s)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
/**
|
|
||||||
* Patch @indiekit/endpoint-micropub/lib/post-data.js to detect stale AI block files.
|
|
||||||
*
|
|
||||||
* Problem: The v3 patch bug (supportsAiDisclosure always false) caused Indiekit to update
|
|
||||||
* MongoDB with AI field values (aiTextLevel, aiCodeLevel, etc.) but write the post file
|
|
||||||
* WITHOUT the ai: frontmatter block. Now when the user re-saves with the same AI values,
|
|
||||||
* Indiekit's isDeepStrictEqual check says "no properties changed" and skips the file write.
|
|
||||||
* The file remains stale (missing ai: block) even though MongoDB has the right data.
|
|
||||||
*
|
|
||||||
* Fix: Store an `_aiBlockVersion` field in MongoDB alongside each post. On update, if the
|
|
||||||
* stored version doesn't match the current patch version AND the post has AI fields, bypass
|
|
||||||
* the no-change check and force a file re-write. This triggers exactly once per affected
|
|
||||||
* post, then every subsequent no-change save correctly skips the write.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { access, readFile, writeFile } from "node:fs/promises";
|
|
||||||
|
|
||||||
const AI_BLOCK_VERSION = "v4";
|
|
||||||
|
|
||||||
const candidates = [
|
|
||||||
"node_modules/@indiekit/endpoint-micropub/lib/post-data.js",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@indiekit/endpoint-micropub/lib/post-data.js",
|
|
||||||
];
|
|
||||||
|
|
||||||
const marker = "AI block version resync patch";
|
|
||||||
|
|
||||||
// --- Old: simple destructuring that ignores _aiBlockVersion ---
|
|
||||||
const oldDestructure = `let { path: _originalPath, properties } = await this.read(application, url);`;
|
|
||||||
|
|
||||||
const newDestructure = `let { path: _originalPath, properties, _aiBlockVersion: storedAiBlockVersion } = await this.read(application, url); // AI block version resync patch`;
|
|
||||||
|
|
||||||
// --- Old: early return when no properties changed ---
|
|
||||||
const oldNoChange = ` // Return if no changes to template properties detected
|
|
||||||
const newProperties = getPostTemplateProperties(properties);
|
|
||||||
oldProperties = getPostTemplateProperties(oldProperties);
|
|
||||||
if (isDeepStrictEqual(newProperties, oldProperties)) {
|
|
||||||
return;
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const newNoChange = ` // Return if no changes to template properties detected
|
|
||||||
const newProperties = getPostTemplateProperties(properties);
|
|
||||||
oldProperties = getPostTemplateProperties(oldProperties);
|
|
||||||
if (isDeepStrictEqual(newProperties, oldProperties)) {
|
|
||||||
// AI block version resync patch: if post has AI fields and the file was written by an
|
|
||||||
// older patch version (or never written with the ai: block), force a one-time re-write.
|
|
||||||
const hasAiFields =
|
|
||||||
newProperties.aiTextLevel !== undefined ||
|
|
||||||
newProperties.aiCodeLevel !== undefined;
|
|
||||||
const currentAiBlockVersion = "${AI_BLOCK_VERSION}";
|
|
||||||
if (!hasAiFields || storedAiBlockVersion === currentAiBlockVersion) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Fall through: force re-write to fix stale ai: block
|
|
||||||
}`;
|
|
||||||
|
|
||||||
// --- Old: postData construction without _aiBlockVersion ---
|
|
||||||
const oldPostData = ` // Update data in posts collection
|
|
||||||
const postData = { _originalPath, path, properties };`;
|
|
||||||
|
|
||||||
const newPostData = ` // Update data in posts collection
|
|
||||||
const postData = { _originalPath, path, properties, _aiBlockVersion: "${AI_BLOCK_VERSION}" }; // AI block version resync patch`;
|
|
||||||
|
|
||||||
async function exists(filePath) {
|
|
||||||
try {
|
|
||||||
await access(filePath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let checked = 0;
|
|
||||||
let patched = 0;
|
|
||||||
|
|
||||||
for (const filePath of candidates) {
|
|
||||||
if (!(await exists(filePath))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
checked += 1;
|
|
||||||
const source = await readFile(filePath, "utf8");
|
|
||||||
|
|
||||||
if (source.includes(marker)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!source.includes(oldDestructure) ||
|
|
||||||
!source.includes(oldNoChange) ||
|
|
||||||
!source.includes(oldPostData)
|
|
||||||
) {
|
|
||||||
console.warn(
|
|
||||||
`[postinstall] Skipping micropub AI block resync patch for ${filePath}: upstream format changed`,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = source
|
|
||||||
.replace(oldDestructure, newDestructure)
|
|
||||||
.replace(oldNoChange, newNoChange)
|
|
||||||
.replace(oldPostData, newPostData);
|
|
||||||
|
|
||||||
await writeFile(filePath, updated, "utf8");
|
|
||||||
patched += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checked === 0) {
|
|
||||||
console.log("[postinstall] No endpoint-micropub post-data.js found");
|
|
||||||
} else if (patched === 0) {
|
|
||||||
console.log("[postinstall] micropub AI block resync patch already applied");
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`[postinstall] Patched micropub AI block resync in ${patched} file(s)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,577 +0,0 @@
|
|||||||
import { access, readFile, writeFile } from "node:fs/promises";
|
|
||||||
|
|
||||||
const candidates = [
|
|
||||||
"node_modules/@rmdes/indiekit-preset-eleventy/lib/post-template.js",
|
|
||||||
"node_modules/@indiekit/preset-eleventy/lib/post-template.js",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-preset-eleventy/lib/post-template.js",
|
|
||||||
"node_modules/@indiekit/indiekit/node_modules/@indiekit/preset-eleventy/lib/post-template.js",
|
|
||||||
];
|
|
||||||
|
|
||||||
const patchMarker =
|
|
||||||
"Indiekit removes post-type before calling postTemplate; fall back to permalink-based detection.";
|
|
||||||
|
|
||||||
const upstreamBlock = [
|
|
||||||
" // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL",
|
|
||||||
" // Add trailing slash to generate /path/index.html instead of /path.html",
|
|
||||||
" if (properties.url) {",
|
|
||||||
" const url = properties.url;",
|
|
||||||
" properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;",
|
|
||||||
" }",
|
|
||||||
" delete properties.url;",
|
|
||||||
"",
|
|
||||||
" const frontMatter = YAML.stringify(properties, { lineWidth: 0 });",
|
|
||||||
" return `---\\n${frontMatter}---\\n`;",
|
|
||||||
"};",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
const v1PatchedBlock = [
|
|
||||||
" // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL",
|
|
||||||
" // Add trailing slash to generate /path/index.html instead of /path.html",
|
|
||||||
" if (properties.url) {",
|
|
||||||
" const url = properties.url;",
|
|
||||||
" properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;",
|
|
||||||
" }",
|
|
||||||
" delete properties.url;",
|
|
||||||
"",
|
|
||||||
" // Normalize AI disclosure metadata and default to no AI usage.",
|
|
||||||
" const aiSource =",
|
|
||||||
" properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)",
|
|
||||||
" ? properties.ai",
|
|
||||||
" : {};",
|
|
||||||
"",
|
|
||||||
" const aiTextLevel = String(",
|
|
||||||
" aiSource.textLevel ?? aiSource.aiTextLevel ?? properties.aiTextLevel ?? \"0\",",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiCodeLevel = String(",
|
|
||||||
" aiSource.codeLevel ?? aiSource.aiCodeLevel ?? properties.aiCodeLevel ?? \"0\",",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiTools = aiSource.aiTools ?? aiSource.tools ?? properties.aiTools;",
|
|
||||||
"",
|
|
||||||
" const aiDescription =",
|
|
||||||
" aiSource.aiDescription ?? aiSource.description ?? properties.aiDescription;",
|
|
||||||
"",
|
|
||||||
" delete properties.ai;",
|
|
||||||
" delete properties.aiTextLevel;",
|
|
||||||
" delete properties.aiCodeLevel;",
|
|
||||||
" delete properties.aiTools;",
|
|
||||||
" delete properties.aiDescription;",
|
|
||||||
"",
|
|
||||||
" const frontMatter = YAML.stringify(properties, { lineWidth: 0 });",
|
|
||||||
"",
|
|
||||||
" let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;",
|
|
||||||
"",
|
|
||||||
" if (aiTools !== undefined && aiTools !== null && aiTools !== \"\") {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',",
|
|
||||||
" ` aiTools: ${JSON.stringify(String(aiTools))}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" if (aiDescription !== undefined && aiDescription !== null && aiDescription !== \"\") {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',",
|
|
||||||
" ` aiDescription: ${JSON.stringify(String(aiDescription))}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" return `---\\n${frontMatter}${aiFrontMatter}---\\n`;",
|
|
||||||
"};",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
const v2PatchedBlock = [
|
|
||||||
" // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL",
|
|
||||||
" // Add trailing slash to generate /path/index.html instead of /path.html",
|
|
||||||
" if (properties.url) {",
|
|
||||||
" const url = properties.url;",
|
|
||||||
" properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;",
|
|
||||||
" }",
|
|
||||||
" delete properties.url;",
|
|
||||||
"",
|
|
||||||
" // Normalize AI disclosure metadata for articles and notes only, defaulting to no AI usage.",
|
|
||||||
" const aiSource =",
|
|
||||||
" properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)",
|
|
||||||
" ? properties.ai",
|
|
||||||
" : {};",
|
|
||||||
"",
|
|
||||||
" const aiTextLevel = String(",
|
|
||||||
" aiSource.textLevel ?? aiSource.aiTextLevel ?? properties.aiTextLevel ?? \"0\",",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiCodeLevel = String(",
|
|
||||||
" aiSource.codeLevel ?? aiSource.aiCodeLevel ?? properties.aiCodeLevel ?? \"0\",",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiTools = aiSource.aiTools ?? aiSource.tools ?? properties.aiTools;",
|
|
||||||
"",
|
|
||||||
" const aiDescription =",
|
|
||||||
" aiSource.aiDescription ?? aiSource.description ?? properties.aiDescription;",
|
|
||||||
"",
|
|
||||||
" delete properties.ai;",
|
|
||||||
" delete properties.aiTextLevel;",
|
|
||||||
" delete properties.aiCodeLevel;",
|
|
||||||
" delete properties.aiTools;",
|
|
||||||
" delete properties.aiDescription;",
|
|
||||||
"",
|
|
||||||
" const postType = String(",
|
|
||||||
" properties.postType ?? properties[\"post-type\"] ?? \"\",",
|
|
||||||
" ).toLowerCase();",
|
|
||||||
" const supportsAiDisclosure = postType === \"article\" || postType === \"note\";",
|
|
||||||
"",
|
|
||||||
" const frontMatter = YAML.stringify(properties, { lineWidth: 0 });",
|
|
||||||
"",
|
|
||||||
" if (!supportsAiDisclosure) {",
|
|
||||||
" return `---\\n${frontMatter}---\\n`;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;",
|
|
||||||
"",
|
|
||||||
" if (aiTools !== undefined && aiTools !== null && aiTools !== \"\") {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',",
|
|
||||||
" ` aiTools: ${JSON.stringify(String(aiTools))}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" if (aiDescription !== undefined && aiDescription !== null && aiDescription !== \"\") {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',",
|
|
||||||
" ` aiDescription: ${JSON.stringify(String(aiDescription))}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" return `---\\n${frontMatter}${aiFrontMatter}---\\n`;",
|
|
||||||
"};",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
const v3Block = [
|
|
||||||
" // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL",
|
|
||||||
" // Add trailing slash to generate /path/index.html instead of /path.html",
|
|
||||||
" if (properties.url) {",
|
|
||||||
" const url = properties.url;",
|
|
||||||
" properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;",
|
|
||||||
" }",
|
|
||||||
" delete properties.url;",
|
|
||||||
"",
|
|
||||||
" // Normalize and sanitize AI disclosure metadata for articles and notes only.",
|
|
||||||
" const aiSource =",
|
|
||||||
" properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)",
|
|
||||||
" ? properties.ai",
|
|
||||||
" : {};",
|
|
||||||
"",
|
|
||||||
" const normaliseString = (value) => {",
|
|
||||||
" if (value === undefined || value === null) {",
|
|
||||||
" return undefined;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" const text = String(value).trim();",
|
|
||||||
" return text === \"\" ? undefined : text;",
|
|
||||||
" };",
|
|
||||||
"",
|
|
||||||
" const normaliseLevel = (value, allowedValues, fallback = \"0\") => {",
|
|
||||||
" const candidate = normaliseString(value);",
|
|
||||||
"",
|
|
||||||
" if (!candidate) {",
|
|
||||||
" return fallback;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" return allowedValues.includes(candidate) ? candidate : fallback;",
|
|
||||||
" };",
|
|
||||||
"",
|
|
||||||
" const aiTextLevelRaw =",
|
|
||||||
" aiSource.textLevel ??",
|
|
||||||
" aiSource.aiTextLevel ??",
|
|
||||||
" properties.aiTextLevel ??",
|
|
||||||
" properties[\"ai-text-level\"] ??",
|
|
||||||
" \"0\";",
|
|
||||||
"",
|
|
||||||
" const aiCodeLevelRaw =",
|
|
||||||
" aiSource.codeLevel ??",
|
|
||||||
" aiSource.aiCodeLevel ??",
|
|
||||||
" properties.aiCodeLevel ??",
|
|
||||||
" properties[\"ai-code-level\"] ??",
|
|
||||||
" \"0\";",
|
|
||||||
"",
|
|
||||||
" const aiTextLevel = normaliseLevel(aiTextLevelRaw, [\"0\", \"1\", \"2\", \"3\"]);",
|
|
||||||
" // Legacy value \"3\" is folded into \"2\" for code-level taxonomy compatibility.",
|
|
||||||
" const aiCodeLevel = normaliseLevel(",
|
|
||||||
" aiCodeLevelRaw === \"3\" ? \"2\" : aiCodeLevelRaw,",
|
|
||||||
" [\"0\", \"1\", \"2\"],",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiTools = normaliseString(",
|
|
||||||
" aiSource.aiTools ?? aiSource.tools ?? properties.aiTools ?? properties[\"ai-tools\"],",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiDescription = normaliseString(",
|
|
||||||
" aiSource.aiDescription ??",
|
|
||||||
" aiSource.description ??",
|
|
||||||
" properties.aiDescription ??",
|
|
||||||
" properties[\"ai-description\"],",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" delete properties.ai;",
|
|
||||||
" delete properties.aiTextLevel;",
|
|
||||||
" delete properties.aiCodeLevel;",
|
|
||||||
" delete properties.aiTools;",
|
|
||||||
" delete properties.aiDescription;",
|
|
||||||
" delete properties[\"ai-text-level\"];",
|
|
||||||
" delete properties[\"ai-code-level\"];",
|
|
||||||
" delete properties[\"ai-tools\"];",
|
|
||||||
" delete properties[\"ai-description\"];",
|
|
||||||
"",
|
|
||||||
" const postType = String(",
|
|
||||||
" properties.postType ?? properties[\"post-type\"] ?? properties.type ?? \"\",",
|
|
||||||
" ).toLowerCase();",
|
|
||||||
" const supportsAiDisclosure = postType === \"article\" || postType === \"note\";",
|
|
||||||
"",
|
|
||||||
" const frontMatter = YAML.stringify(properties, { lineWidth: 0 });",
|
|
||||||
"",
|
|
||||||
" if (!supportsAiDisclosure) {",
|
|
||||||
" return `---\\n${frontMatter}---\\n`;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;",
|
|
||||||
"",
|
|
||||||
" if (aiTools) {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',",
|
|
||||||
" ` aiTools: ${JSON.stringify(aiTools)}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" if (aiDescription) {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',",
|
|
||||||
" ` aiDescription: ${JSON.stringify(aiDescription)}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" return `---\\n${frontMatter}${aiFrontMatter}---\\n`;",
|
|
||||||
"};",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
// v4: fix post-type detection — Indiekit removes post-type before calling postTemplate,
|
|
||||||
// so fall back to permalink URL pattern to detect articles and notes.
|
|
||||||
const v4Block = [
|
|
||||||
" // Convert url to Eleventy permalink so generated URL matches Indiekit's stored URL",
|
|
||||||
" // Add trailing slash to generate /path/index.html instead of /path.html",
|
|
||||||
" if (properties.url) {",
|
|
||||||
" const url = properties.url;",
|
|
||||||
" properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;",
|
|
||||||
" }",
|
|
||||||
" delete properties.url;",
|
|
||||||
"",
|
|
||||||
" // Normalize and sanitize AI disclosure metadata for articles and notes only.",
|
|
||||||
" const aiSource =",
|
|
||||||
" properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)",
|
|
||||||
" ? properties.ai",
|
|
||||||
" : {};",
|
|
||||||
"",
|
|
||||||
" const normaliseString = (value) => {",
|
|
||||||
" if (value === undefined || value === null) {",
|
|
||||||
" return undefined;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" const text = String(value).trim();",
|
|
||||||
" return text === \"\" ? undefined : text;",
|
|
||||||
" };",
|
|
||||||
"",
|
|
||||||
" const normaliseLevel = (value, allowedValues, fallback = \"0\") => {",
|
|
||||||
" const candidate = normaliseString(value);",
|
|
||||||
"",
|
|
||||||
" if (!candidate) {",
|
|
||||||
" return fallback;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" return allowedValues.includes(candidate) ? candidate : fallback;",
|
|
||||||
" };",
|
|
||||||
"",
|
|
||||||
" const aiTextLevelRaw =",
|
|
||||||
" aiSource.textLevel ??",
|
|
||||||
" aiSource.aiTextLevel ??",
|
|
||||||
" properties.aiTextLevel ??",
|
|
||||||
" properties[\"ai-text-level\"] ??",
|
|
||||||
" \"0\";",
|
|
||||||
"",
|
|
||||||
" const aiCodeLevelRaw =",
|
|
||||||
" aiSource.codeLevel ??",
|
|
||||||
" aiSource.aiCodeLevel ??",
|
|
||||||
" properties.aiCodeLevel ??",
|
|
||||||
" properties[\"ai-code-level\"] ??",
|
|
||||||
" \"0\";",
|
|
||||||
"",
|
|
||||||
" const aiTextLevel = normaliseLevel(aiTextLevelRaw, [\"0\", \"1\", \"2\", \"3\"]);",
|
|
||||||
" // Legacy value \"3\" is folded into \"2\" for code-level taxonomy compatibility.",
|
|
||||||
" const aiCodeLevel = normaliseLevel(",
|
|
||||||
" aiCodeLevelRaw === \"3\" ? \"2\" : aiCodeLevelRaw,",
|
|
||||||
" [\"0\", \"1\", \"2\"],",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiTools = normaliseString(",
|
|
||||||
" aiSource.aiTools ?? aiSource.tools ?? properties.aiTools ?? properties[\"ai-tools\"],",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiDescription = normaliseString(",
|
|
||||||
" aiSource.aiDescription ??",
|
|
||||||
" aiSource.description ??",
|
|
||||||
" properties.aiDescription ??",
|
|
||||||
" properties[\"ai-description\"],",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" delete properties.ai;",
|
|
||||||
" delete properties.aiTextLevel;",
|
|
||||||
" delete properties.aiCodeLevel;",
|
|
||||||
" delete properties.aiTools;",
|
|
||||||
" delete properties.aiDescription;",
|
|
||||||
" delete properties[\"ai-text-level\"];",
|
|
||||||
" delete properties[\"ai-code-level\"];",
|
|
||||||
" delete properties[\"ai-tools\"];",
|
|
||||||
" delete properties[\"ai-description\"];",
|
|
||||||
"",
|
|
||||||
" // Indiekit removes post-type before calling postTemplate; fall back to permalink-based detection.",
|
|
||||||
" const postType = String(",
|
|
||||||
" properties.postType ?? properties[\"post-type\"] ?? properties.type ?? \"\",",
|
|
||||||
" ).toLowerCase();",
|
|
||||||
" const permalink = String(properties.permalink ?? \"\");",
|
|
||||||
" const supportsAiDisclosure =",
|
|
||||||
" postType === \"article\" || postType === \"note\" ||",
|
|
||||||
" /\\/articles(?:\\/|$)/.test(permalink) || /\\/notes(?:\\/|$)/.test(permalink);",
|
|
||||||
"",
|
|
||||||
" const frontMatter = YAML.stringify(properties, { lineWidth: 0 });",
|
|
||||||
"",
|
|
||||||
" if (!supportsAiDisclosure) {",
|
|
||||||
" return `---\\n${frontMatter}---\\n`;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;",
|
|
||||||
"",
|
|
||||||
" if (aiTools) {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',",
|
|
||||||
" ` aiTools: ${JSON.stringify(aiTools)}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" if (aiDescription) {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',",
|
|
||||||
" ` aiDescription: ${JSON.stringify(aiDescription)}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" return `---\\n${frontMatter}${aiFrontMatter}---\\n`;",
|
|
||||||
"};",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
// v5: upstream added mpUrl storage + URL normalization (pathname extraction).
|
|
||||||
// Matches the new block structure added after v4.
|
|
||||||
const v5UpstreamBlock = [
|
|
||||||
" // Store the Micropub URL for frontend edit links before deleting it",
|
|
||||||
" if (properties.url) {",
|
|
||||||
" properties.mpUrl = properties.url;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" // Convert Indiekit URL to Eleventy permalink so pages generate",
|
|
||||||
" // at the canonical URL (e.g., /notes/2026/02/22/slug/) instead of",
|
|
||||||
" // the file-path-based URL (e.g., /content/notes/2026-02-22-slug/).",
|
|
||||||
" if (properties.url) {",
|
|
||||||
" let url = properties.url;",
|
|
||||||
" if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {",
|
|
||||||
" try {",
|
|
||||||
" url = new URL(url).pathname;",
|
|
||||||
" } catch {",
|
|
||||||
" // If URL parsing fails, use as-is",
|
|
||||||
" }",
|
|
||||||
" }",
|
|
||||||
" properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;",
|
|
||||||
" }",
|
|
||||||
" delete properties.url;",
|
|
||||||
"",
|
|
||||||
" const frontMatter = YAML.stringify(properties, { lineWidth: 0 });",
|
|
||||||
" return `---\\n${frontMatter}---\\n`;",
|
|
||||||
"};",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
const v5PatchedBlock = [
|
|
||||||
" // Store the Micropub URL for frontend edit links before deleting it",
|
|
||||||
" if (properties.url) {",
|
|
||||||
" properties.mpUrl = properties.url;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" // Convert Indiekit URL to Eleventy permalink so pages generate",
|
|
||||||
" // at the canonical URL (e.g., /notes/2026/02/22/slug/) instead of",
|
|
||||||
" // the file-path-based URL (e.g., /content/notes/2026-02-22-slug/).",
|
|
||||||
" if (properties.url) {",
|
|
||||||
" let url = properties.url;",
|
|
||||||
" if (url.startsWith(\"http://\") || url.startsWith(\"https://\")) {",
|
|
||||||
" try {",
|
|
||||||
" url = new URL(url).pathname;",
|
|
||||||
" } catch {",
|
|
||||||
" // If URL parsing fails, use as-is",
|
|
||||||
" }",
|
|
||||||
" }",
|
|
||||||
" properties.permalink = url.endsWith(\"/\") ? url : `${url}/`;",
|
|
||||||
" }",
|
|
||||||
" delete properties.url;",
|
|
||||||
"",
|
|
||||||
" // Normalize and sanitize AI disclosure metadata for articles and notes only.",
|
|
||||||
" const aiSource =",
|
|
||||||
" properties.ai && typeof properties.ai === \"object\" && !Array.isArray(properties.ai)",
|
|
||||||
" ? properties.ai",
|
|
||||||
" : {};",
|
|
||||||
"",
|
|
||||||
" const normaliseString = (value) => {",
|
|
||||||
" if (value === undefined || value === null) {",
|
|
||||||
" return undefined;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" const text = String(value).trim();",
|
|
||||||
" return text === \"\" ? undefined : text;",
|
|
||||||
" };",
|
|
||||||
"",
|
|
||||||
" const normaliseLevel = (value, allowedValues, fallback = \"0\") => {",
|
|
||||||
" const candidate = normaliseString(value);",
|
|
||||||
"",
|
|
||||||
" if (!candidate) {",
|
|
||||||
" return fallback;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" return allowedValues.includes(candidate) ? candidate : fallback;",
|
|
||||||
" };",
|
|
||||||
"",
|
|
||||||
" const aiTextLevelRaw =",
|
|
||||||
" aiSource.textLevel ??",
|
|
||||||
" aiSource.aiTextLevel ??",
|
|
||||||
" properties.aiTextLevel ??",
|
|
||||||
" properties[\"ai-text-level\"] ??",
|
|
||||||
" \"0\";",
|
|
||||||
"",
|
|
||||||
" const aiCodeLevelRaw =",
|
|
||||||
" aiSource.codeLevel ??",
|
|
||||||
" aiSource.aiCodeLevel ??",
|
|
||||||
" properties.aiCodeLevel ??",
|
|
||||||
" properties[\"ai-code-level\"] ??",
|
|
||||||
" \"0\";",
|
|
||||||
"",
|
|
||||||
" const aiTextLevel = normaliseLevel(aiTextLevelRaw, [\"0\", \"1\", \"2\", \"3\"]);",
|
|
||||||
" // Legacy value \"3\" is folded into \"2\" for code-level taxonomy compatibility.",
|
|
||||||
" const aiCodeLevel = normaliseLevel(",
|
|
||||||
" aiCodeLevelRaw === \"3\" ? \"2\" : aiCodeLevelRaw,",
|
|
||||||
" [\"0\", \"1\", \"2\"],",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiTools = normaliseString(",
|
|
||||||
" aiSource.aiTools ?? aiSource.tools ?? properties.aiTools ?? properties[\"ai-tools\"],",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" const aiDescription = normaliseString(",
|
|
||||||
" aiSource.aiDescription ??",
|
|
||||||
" aiSource.description ??",
|
|
||||||
" properties.aiDescription ??",
|
|
||||||
" properties[\"ai-description\"],",
|
|
||||||
" );",
|
|
||||||
"",
|
|
||||||
" delete properties.ai;",
|
|
||||||
" delete properties.aiTextLevel;",
|
|
||||||
" delete properties.aiCodeLevel;",
|
|
||||||
" delete properties.aiTools;",
|
|
||||||
" delete properties.aiDescription;",
|
|
||||||
" delete properties[\"ai-text-level\"];",
|
|
||||||
" delete properties[\"ai-code-level\"];",
|
|
||||||
" delete properties[\"ai-tools\"];",
|
|
||||||
" delete properties[\"ai-description\"];",
|
|
||||||
"",
|
|
||||||
" // Indiekit removes post-type before calling postTemplate; fall back to permalink-based detection.",
|
|
||||||
" const postType = String(",
|
|
||||||
" properties.postType ?? properties[\"post-type\"] ?? properties.type ?? \"\",",
|
|
||||||
" ).toLowerCase();",
|
|
||||||
" const permalink = String(properties.permalink ?? \"\");",
|
|
||||||
" const supportsAiDisclosure =",
|
|
||||||
" postType === \"article\" || postType === \"note\" ||",
|
|
||||||
" /\\/articles(?:\\/|$)/.test(permalink) || /\\/notes(?:\\/|$)/.test(permalink);",
|
|
||||||
"",
|
|
||||||
" const frontMatter = YAML.stringify(properties, { lineWidth: 0 });",
|
|
||||||
"",
|
|
||||||
" if (!supportsAiDisclosure) {",
|
|
||||||
" return `---\\n${frontMatter}---\\n`;",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" let aiFrontMatter = `ai:\\n textLevel: \\\"${aiTextLevel}\\\"\\n codeLevel: \\\"${aiCodeLevel}\\\"\\n # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n`;",
|
|
||||||
"",
|
|
||||||
" if (aiTools) {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiTools: \\\"Claude, ChatGPT, Copilot\\\"\\n',",
|
|
||||||
" ` aiTools: ${JSON.stringify(aiTools)}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" if (aiDescription) {",
|
|
||||||
" aiFrontMatter = aiFrontMatter.replace(",
|
|
||||||
" ' # aiDescription: \\\"Optional disclosure about how AI was used\\\"\\n',",
|
|
||||||
" ` aiDescription: ${JSON.stringify(aiDescription)}\\n`,",
|
|
||||||
" );",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" return `---\\n${frontMatter}${aiFrontMatter}---\\n`;",
|
|
||||||
"};",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
async function exists(filePath) {
|
|
||||||
try {
|
|
||||||
await access(filePath);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let checked = 0;
|
|
||||||
let patched = 0;
|
|
||||||
|
|
||||||
for (const filePath of candidates) {
|
|
||||||
if (!(await exists(filePath))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
checked += 1;
|
|
||||||
|
|
||||||
const source = await readFile(filePath, "utf8");
|
|
||||||
|
|
||||||
if (source.includes(patchMarker)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let updated = source;
|
|
||||||
|
|
||||||
if (source.includes(v5UpstreamBlock)) {
|
|
||||||
updated = source.replace(v5UpstreamBlock, v5PatchedBlock);
|
|
||||||
} else if (source.includes(v3Block)) {
|
|
||||||
updated = source.replace(v3Block, v4Block);
|
|
||||||
} else if (source.includes(v2PatchedBlock)) {
|
|
||||||
updated = source.replace(v2PatchedBlock, v4Block);
|
|
||||||
} else if (source.includes(v1PatchedBlock)) {
|
|
||||||
updated = source.replace(v1PatchedBlock, v4Block);
|
|
||||||
} else if (source.includes(upstreamBlock)) {
|
|
||||||
updated = source.replace(upstreamBlock, v4Block);
|
|
||||||
} else {
|
|
||||||
console.warn(
|
|
||||||
`[postinstall] Skipping preset-eleventy AI frontmatter patch for ${filePath}: upstream format changed`,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeFile(filePath, updated, "utf8");
|
|
||||||
patched += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checked === 0) {
|
|
||||||
console.log("[postinstall] No preset-eleventy post-template files found");
|
|
||||||
} else if (patched === 0) {
|
|
||||||
console.log("[postinstall] preset-eleventy AI frontmatter patch already applied");
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`[postinstall] Patched preset-eleventy AI frontmatter in ${patched} file(s)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user