diff --git a/README.md b/README.md index c5e13eb..485099a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -Cover Image -Cover Image +Cover Image +Cover Image
@@ -44,8 +44,8 @@ Retypeset is a static blog theme based on the [Astro](https://astro.build/) fram

- - Retypeset Lighthouse Score + + Retypeset Lighthouse Score

@@ -78,8 +78,8 @@ Retypeset is a static blog theme based on the [Astro](https://astro.build/) fram 5. Refer to the [Astro Deployment Guides](https://docs.astro.build/en/guides/deploy/) to deploy your blog to Netlify, Vercel, or other platforms. - [![Deploy to Netlify](assets/deploy-netlify.svg)](https://app.netlify.com/start) - [![Deploy to Vercel](assets/deploy-vercel.svg)](https://vercel.com/new) + [![Deploy to Netlify](images/deploy-netlify.svg)](https://app.netlify.com/start) + [![Deploy to Vercel](images/deploy-vercel.svg)](https://vercel.com/new) ## Updates diff --git a/README.zh.md b/README.zh.md index e543d58..8517578 100644 --- a/README.zh.md +++ b/README.zh.md @@ -1,5 +1,5 @@ -Cover Image -Cover Image +Cover Image +Cover Image
@@ -45,7 +45,7 @@ Retypeset 是一款基于 [Astro](https://astro.build/) 框架的静态博客主

- Retypeset Lighthouse Score + Retypeset Lighthouse Score

@@ -78,8 +78,8 @@ Retypeset 是一款基于 [Astro](https://astro.build/) 框架的静态博客主 5. 参考 [Astro 部署指南](https://docs.astro.build/zh-cn/guides/deploy/),将博客部署至 Netlify、Vercel 等平台。 - [![Deploy to Netlify](assets/deploy-netlify.svg)](https://app.netlify.com/start) - [![Deploy to Vercel](assets/deploy-vercel.svg)](https://vercel.com/new) + [![Deploy to Netlify](images/deploy-netlify.svg)](https://app.netlify.com/start) + [![Deploy to Vercel](images/deploy-vercel.svg)](https://vercel.com/new) ## 更新 diff --git a/assets/deploy-netlify.svg b/images/deploy-netlify.svg similarity index 100% rename from assets/deploy-netlify.svg rename to images/deploy-netlify.svg diff --git a/assets/deploy-vercel.svg b/images/deploy-vercel.svg similarity index 100% rename from assets/deploy-vercel.svg rename to images/deploy-vercel.svg diff --git a/assets/retypeset-en-desktop.webp b/images/retypeset-en-desktop.webp similarity index 100% rename from assets/retypeset-en-desktop.webp rename to images/retypeset-en-desktop.webp diff --git a/assets/retypeset-en-mobile.webp b/images/retypeset-en-mobile.webp similarity index 100% rename from assets/retypeset-en-mobile.webp rename to images/retypeset-en-mobile.webp diff --git a/assets/retypeset-lighthouse-score.svg b/images/retypeset-lighthouse-score.svg similarity index 100% rename from assets/retypeset-lighthouse-score.svg rename to images/retypeset-lighthouse-score.svg diff --git a/assets/retypeset-zh-desktop.webp b/images/retypeset-zh-desktop.webp similarity index 100% rename from assets/retypeset-zh-desktop.webp rename to images/retypeset-zh-desktop.webp diff --git a/assets/retypeset-zh-mobile.webp b/images/retypeset-zh-mobile.webp similarity index 100% rename from assets/retypeset-zh-mobile.webp rename to images/retypeset-zh-mobile.webp diff --git a/package.json b/package.json index 566d6f7..ad85495 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "katex": "^0.16.22", "markdown-it": "^14.1.0", "mdast-util-to-string": "^4.0.0", + "node-html-parser": "^7.0.1", "overlayscrollbars": "^2.11.2", "photoswipe": "^5.4.4", "reading-time": "^1.5.0", @@ -48,7 +49,7 @@ "astro-eslint-parser": "^1.2.2", "eslint": "^9.26.0", "eslint-plugin-astro": "^1.3.1", - "lint-staged": "^15.5.2", + "lint-staged": "^16.0.0", "sharp": "^0.34.1", "typescript": "~5.8.3", "unocss": "66.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa3b657..34a7688 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: mdast-util-to-string: specifier: ^4.0.0 version: 4.0.0 + node-html-parser: + specifier: ^7.0.1 + version: 7.0.1 overlayscrollbars: specifier: ^2.11.2 version: 2.11.2 @@ -109,8 +112,8 @@ importers: specifier: ^1.3.1 version: 1.3.1(eslint@9.26.0(jiti@2.4.2)) lint-staged: - specifier: ^15.5.2 - version: 15.5.2 + specifier: ^16.0.0 + version: 16.0.0 sharp: specifier: ^0.34.1 version: 0.34.1 @@ -1741,10 +1744,17 @@ packages: crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + css-tree@3.1.0: resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -1997,8 +2007,8 @@ packages: peerDependencies: eslint: ^8.57.0 || ^9.0.0 - eslint-plugin-jsdoc@50.6.11: - resolution: {integrity: sha512-k4+MnBCGR8cuIB5MZ++FGd4gbXxjob2rX1Nq0q3nWFF4xSGZENTgTLZSjb+u9B8SAnP6lpGV2FJrBjllV3pVSg==} + eslint-plugin-jsdoc@50.6.14: + resolution: {integrity: sha512-JUudvooQbUx3iB8n/MzXMOV/VtaXq7xL4CeXhYryinr8osck7nV6fE2/xUXTiH3epPXcvq6TE3HQfGQuRHErTQ==} engines: {node: '>=18'} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 @@ -2161,10 +2171,6 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - expect-type@1.2.1: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} @@ -2296,10 +2302,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - get-tsconfig@4.10.0: resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} @@ -2403,6 +2405,10 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + html-escaper@3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} @@ -2419,10 +2425,6 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -2535,10 +2537,6 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} @@ -2683,9 +2681,9 @@ packages: linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} - lint-staged@15.5.2: - resolution: {integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==} - engines: {node: '>=18.12.0'} + lint-staged@16.0.0: + resolution: {integrity: sha512-sUCprePs6/rbx4vKC60Hez6X10HPkpDJaGcy3D1NdwR7g1RcNkWL8q9mJMreOqmHBTs+1sNFp+wOiX9fr+hoOQ==} + engines: {node: '>=20.18'} hasBin: true listr2@8.3.3: @@ -2824,9 +2822,6 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2957,10 +2952,6 @@ packages: resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} engines: {node: '>= 0.6'} - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -2993,6 +2984,10 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + nano-spawn@1.0.1: + resolution: {integrity: sha512-BfcvzBlUTxSDWfT+oH7vd6CbUV+rThLLHCIym/QO6GGLBsyVXleZs00fto2i2jzC/wPiBYk5jyOmpXWg4YopiA==} + engines: {node: '>=20.18'} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -3033,6 +3028,9 @@ packages: encoding: optional: true + node-html-parser@7.0.1: + resolution: {integrity: sha512-KGtmPY2kS0thCWGK0VuPyOS+pBKhhe8gXztzA2ilAOhbUbxa9homF1bOyKvhGzMLXUoRds9IOmr/v5lr/lqNmA==} + node-mock-http@1.0.0: resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==} @@ -3043,10 +3041,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -3071,10 +3065,6 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -3159,10 +3149,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -3616,10 +3602,6 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - strip-indent@4.0.0: resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} engines: {node: '>=12'} @@ -4281,7 +4263,7 @@ snapshots: eslint-plugin-antfu: 3.1.1(eslint@9.26.0(jiti@2.4.2)) eslint-plugin-command: 3.2.0(eslint@9.26.0(jiti@2.4.2)) eslint-plugin-import-x: 4.11.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-jsdoc: 50.6.11(eslint@9.26.0(jiti@2.4.2)) + eslint-plugin-jsdoc: 50.6.14(eslint@9.26.0(jiti@2.4.2)) eslint-plugin-jsonc: 2.20.0(eslint@9.26.0(jiti@2.4.2)) eslint-plugin-n: 17.18.0(eslint@9.26.0(jiti@2.4.2)) eslint-plugin-no-only-tests: 3.3.0 @@ -6086,11 +6068,21 @@ snapshots: dependencies: uncrypto: 0.1.3 + css-select@5.1.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + css-tree@3.1.0: dependencies: mdn-data: 2.12.2 source-map-js: 1.2.1 + css-what@6.1.0: {} + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -6343,7 +6335,7 @@ snapshots: - supports-color - typescript - eslint-plugin-jsdoc@50.6.11(eslint@9.26.0(jiti@2.4.2)): + eslint-plugin-jsdoc@50.6.14(eslint@9.26.0(jiti@2.4.2)): dependencies: '@es-joy/jsdoccomment': 0.49.0 are-docs-informative: 0.0.2 @@ -6604,18 +6596,6 @@ snapshots: dependencies: eventsource-parser: 3.0.1 - execa@8.0.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - expect-type@1.2.1: optional: true @@ -6778,8 +6758,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stream@8.0.1: {} - get-tsconfig@4.10.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -6979,6 +6957,8 @@ snapshots: property-information: 7.1.0 space-separated-tokens: 2.0.2 + he@1.2.0: {} + html-escaper@3.0.3: {} html-void-elements@3.0.0: {} @@ -7000,8 +6980,6 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 - human-signals@5.0.0: {} - iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -7082,8 +7060,6 @@ snapshots: is-promise@4.0.0: {} - is-stream@3.0.0: {} - is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 @@ -7192,15 +7168,15 @@ snapshots: dependencies: uc.micro: 2.1.0 - lint-staged@15.5.2: + lint-staged@16.0.0: dependencies: chalk: 5.4.1 commander: 13.1.0 debug: 4.4.0 - execa: 8.0.1 lilconfig: 3.1.3 listr2: 8.3.3 micromatch: 4.0.8 + nano-spawn: 1.0.1 pidtree: 0.6.0 string-argv: 0.3.2 yaml: 2.7.1 @@ -7490,8 +7466,6 @@ snapshots: merge-descriptors@2.0.0: {} - merge-stream@2.0.0: {} - merge2@1.4.1: {} micromark-core-commonmark@2.0.3: @@ -7796,8 +7770,6 @@ snapshots: dependencies: mime-db: 1.54.0 - mimic-fn@4.0.0: {} - mimic-function@5.0.1: {} min-indent@1.0.1: {} @@ -7827,6 +7799,8 @@ snapshots: muggle-string@0.4.1: {} + nano-spawn@1.0.1: {} + nanoid@3.3.11: {} napi-postinstall@0.2.3: {} @@ -7849,16 +7823,17 @@ snapshots: dependencies: whatwg-url: 5.0.0 + node-html-parser@7.0.1: + dependencies: + css-select: 5.1.0 + he: 1.2.0 + node-mock-http@1.0.0: {} node-releases@2.0.19: {} normalize-path@3.0.0: {} - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -7883,10 +7858,6 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -7978,8 +7949,6 @@ snapshots: path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-to-regexp@8.2.0: {} @@ -8614,8 +8583,6 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-final-newline@3.0.0: {} - strip-indent@4.0.0: dependencies: min-indent: 1.0.1 diff --git a/src/content/posts/diaries/Birth of Retypeset-zh.md b/src/content/posts/Birth of Retypeset-zh.md similarity index 100% rename from src/content/posts/diaries/Birth of Retypeset-zh.md rename to src/content/posts/Birth of Retypeset-zh.md diff --git a/src/assets/images/1-dark.jpeg b/src/content/posts/_images/1-dark.jpeg similarity index 100% rename from src/assets/images/1-dark.jpeg rename to src/content/posts/_images/1-dark.jpeg diff --git a/src/assets/images/1-light.jpeg b/src/content/posts/_images/1-light.jpeg similarity index 100% rename from src/assets/images/1-light.jpeg rename to src/content/posts/_images/1-light.jpeg diff --git a/src/assets/images/2-dark.jpeg b/src/content/posts/_images/2-dark.jpeg similarity index 100% rename from src/assets/images/2-dark.jpeg rename to src/content/posts/_images/2-dark.jpeg diff --git a/src/assets/images/2-light.jpeg b/src/content/posts/_images/2-light.jpeg similarity index 100% rename from src/assets/images/2-light.jpeg rename to src/content/posts/_images/2-light.jpeg diff --git a/src/assets/images/3-dark.jpeg b/src/content/posts/_images/3-dark.jpeg similarity index 100% rename from src/assets/images/3-dark.jpeg rename to src/content/posts/_images/3-dark.jpeg diff --git a/src/assets/images/3-light.jpeg b/src/content/posts/_images/3-light.jpeg similarity index 100% rename from src/assets/images/3-light.jpeg rename to src/content/posts/_images/3-light.jpeg diff --git a/src/assets/images/4-dark.jpeg b/src/content/posts/_images/4-dark.jpeg similarity index 100% rename from src/assets/images/4-dark.jpeg rename to src/content/posts/_images/4-dark.jpeg diff --git a/src/assets/images/4-light.jpeg b/src/content/posts/_images/4-light.jpeg similarity index 100% rename from src/assets/images/4-light.jpeg rename to src/content/posts/_images/4-light.jpeg diff --git a/src/content/posts/guides/Theme Color Schemes-en.md b/src/content/posts/guides/Theme Color Schemes-en.md index 48ffe61..c231abb 100644 --- a/src/content/posts/guides/Theme Color Schemes-en.md +++ b/src/content/posts/guides/Theme Color Schemes-en.md @@ -14,8 +14,8 @@ To meet personalization needs, I've created several color schemes for the theme. ## Scallion White -![Light mode](../../../assets/images/1-light.jpeg) -![Dark mode](../../../assets/images/1-dark.jpeg) +![Light mode](../_images/1-light.jpeg) +![Dark mode](../_images/1-dark.jpeg) ``` light: { @@ -32,8 +32,8 @@ dark: { ## Raven Teal -![Light mode](../../../assets/images/2-light.jpeg) -![Dark mode](../../../assets/images/2-dark.jpeg) +![Light mode](../_images/2-light.jpeg) +![Dark mode](../_images/2-dark.jpeg) ``` light: { @@ -50,8 +50,8 @@ dark: { ## Ink Blue -![Light mode](../../../assets/images/4-light.jpeg) -![Dark mode](../../../assets/images/4-dark.jpeg) +![Light mode](../_images/4-light.jpeg) +![Dark mode](../_images/4-dark.jpeg) ``` light: { @@ -68,8 +68,8 @@ dark: { ## Ecru -![Light mode](../../../assets/images/3-light.jpeg) -![Dark mode](../../../assets/images/3-dark.jpeg) +![Light mode](../_images/3-light.jpeg) +![Dark mode](../_images/3-dark.jpeg) ``` light: { diff --git a/src/content/posts/guides/Theme Color Schemes-es.md b/src/content/posts/guides/Theme Color Schemes-es.md index 110ae5a..638e121 100644 --- a/src/content/posts/guides/Theme Color Schemes-es.md +++ b/src/content/posts/guides/Theme Color Schemes-es.md @@ -14,8 +14,8 @@ Para satisfacer las necesidades de personalización, he creado varios esquemas d ## Blanco Cebollino -![Light mode](../../../assets/images/1-light.jpeg) -![Dark mode](../../../assets/images/1-dark.jpeg) +![Light mode](../_images/1-light.jpeg) +![Dark mode](../_images/1-dark.jpeg) ``` light: { @@ -32,8 +32,8 @@ dark: { ## Azul Cuervo -![Light mode](../../../assets/images/2-light.jpeg) -![Dark mode](../../../assets/images/2-dark.jpeg) +![Light mode](../_images/2-light.jpeg) +![Dark mode](../_images/2-dark.jpeg) ``` light: { @@ -50,8 +50,8 @@ dark: { ## Azul Tinta -![Light mode](../../../assets/images/4-light.jpeg) -![Dark mode](../../../assets/images/4-dark.jpeg) +![Light mode](../_images/4-light.jpeg) +![Dark mode](../_images/4-dark.jpeg) ``` light: { @@ -68,8 +68,8 @@ dark: { ## Beige -![Light mode](../../../assets/images/3-light.jpeg) -![Dark mode](../../../assets/images/3-dark.jpeg) +![Light mode](../_images/3-light.jpeg) +![Dark mode](../_images/3-dark.jpeg) ``` light: { diff --git a/src/content/posts/guides/Theme Color Schemes-ja.md b/src/content/posts/guides/Theme Color Schemes-ja.md index 109167f..5f419c3 100644 --- a/src/content/posts/guides/Theme Color Schemes-ja.md +++ b/src/content/posts/guides/Theme Color Schemes-ja.md @@ -14,8 +14,8 @@ Retypesetは、[OKLCH](https://oklch.com/)カラースペースに基づいて ## 葱白色 -![Light mode](../../../assets/images/1-light.jpeg) -![Dark mode](../../../assets/images/1-dark.jpeg) +![Light mode](../_images/1-light.jpeg) +![Dark mode](../_images/1-dark.jpeg) ``` light: { @@ -32,8 +32,8 @@ dark: { ## 烏青 -![Light mode](../../../assets/images/2-light.jpeg) -![Dark mode](../../../assets/images/2-dark.jpeg) +![Light mode](../_images/2-light.jpeg) +![Dark mode](../_images/2-dark.jpeg) ``` light: { @@ -50,8 +50,8 @@ dark: { ## 墨藍 -![Light mode](../../../assets/images/4-light.jpeg) -![Dark mode](../../../assets/images/4-dark.jpeg) +![Light mode](../_images/4-light.jpeg) +![Dark mode](../_images/4-dark.jpeg) ``` light: { @@ -68,8 +68,8 @@ dark: { ## 米色 -![Light mode](../../../assets/images/3-light.jpeg) -![Dark mode](../../../assets/images/3-dark.jpeg) +![Light mode](../_images/3-light.jpeg) +![Dark mode](../_images/3-dark.jpeg) ``` light: { diff --git a/src/content/posts/guides/Theme Color Schemes-ru.md b/src/content/posts/guides/Theme Color Schemes-ru.md index 6688ae6..862ba8a 100644 --- a/src/content/posts/guides/Theme Color Schemes-ru.md +++ b/src/content/posts/guides/Theme Color Schemes-ru.md @@ -14,8 +14,8 @@ Retypeset определяет цветовые схемы темы на осн ## Бледно-зелёный -![Light mode](../../../assets/images/1-light.jpeg) -![Dark mode](../../../assets/images/1-dark.jpeg) +![Light mode](../_images/1-light.jpeg) +![Dark mode](../_images/1-dark.jpeg) ``` light: { @@ -32,8 +32,8 @@ dark: { ## Воронёный -![Light mode](../../../assets/images/2-light.jpeg) -![Dark mode](../../../assets/images/2-dark.jpeg) +![Light mode](../_images/2-light.jpeg) +![Dark mode](../_images/2-dark.jpeg) ``` light: { @@ -50,8 +50,8 @@ dark: { ## Чернильно-синий -![Light mode](../../../assets/images/4-light.jpeg) -![Dark mode](../../../assets/images/4-dark.jpeg) +![Light mode](../_images/4-light.jpeg) +![Dark mode](../_images/4-dark.jpeg) ``` light: { @@ -68,8 +68,8 @@ dark: { ## Кремовый -![Light mode](../../../assets/images/3-light.jpeg) -![Dark mode](../../../assets/images/3-dark.jpeg) +![Light mode](../_images/3-light.jpeg) +![Dark mode](../_images/3-dark.jpeg) ``` light: { diff --git a/src/content/posts/guides/Theme Color Schemes-zh-tw.md b/src/content/posts/guides/Theme Color Schemes-zh-tw.md index a480dbe..1ef7e60 100644 --- a/src/content/posts/guides/Theme Color Schemes-zh-tw.md +++ b/src/content/posts/guides/Theme Color Schemes-zh-tw.md @@ -14,8 +14,8 @@ Retypeset 基於 [OKLCH](https://oklch.com/) 顏色空間來定義主題配色 ## 蔥白 -![Light mode](../../../assets/images/1-light.jpeg) -![Dark mode](../../../assets/images/1-dark.jpeg) +![Light mode](../_images/1-light.jpeg) +![Dark mode](../_images/1-dark.jpeg) ``` light: { @@ -32,8 +32,8 @@ dark: { ## 鴉青 -![Light mode](../../../assets/images/2-light.jpeg) -![Dark mode](../../../assets/images/2-dark.jpeg) +![Light mode](../_images/2-light.jpeg) +![Dark mode](../_images/2-dark.jpeg) ``` light: { @@ -50,8 +50,8 @@ dark: { ## 墨藍 -![Light mode](../../../assets/images/4-light.jpeg) -![Dark mode](../../../assets/images/4-dark.jpeg) +![Light mode](../_images/4-light.jpeg) +![Dark mode](../_images/4-dark.jpeg) ``` light: { @@ -68,8 +68,8 @@ dark: { ## 米黃 -![Light mode](../../../assets/images/3-light.jpeg) -![Dark mode](../../../assets/images/3-dark.jpeg) +![Light mode](../_images/3-light.jpeg) +![Dark mode](../_images/3-dark.jpeg) ``` light: { diff --git a/src/content/posts/guides/Theme Color Schemes-zh.md b/src/content/posts/guides/Theme Color Schemes-zh.md index 543417c..02dd6d0 100644 --- a/src/content/posts/guides/Theme Color Schemes-zh.md +++ b/src/content/posts/guides/Theme Color Schemes-zh.md @@ -14,8 +14,8 @@ Retypeset 基于 [OKLCH](https://oklch.com/) 颜色空间来定义主题配色 ## 葱白 -![Light mode](../../../assets/images/1-light.jpeg) -![Dark mode](../../../assets/images/1-dark.jpeg) +![Light mode](../_images/1-light.jpeg) +![Dark mode](../_images/1-dark.jpeg) ``` light: { @@ -32,8 +32,8 @@ dark: { ## 鸦青 -![Light mode](../../../assets/images/2-light.jpeg) -![Dark mode](../../../assets/images/2-dark.jpeg) +![Light mode](../_images/2-light.jpeg) +![Dark mode](../_images/2-dark.jpeg) ``` light: { @@ -50,8 +50,8 @@ dark: { ## 墨蓝 -![Light mode](../../../assets/images/4-light.jpeg) -![Dark mode](../../../assets/images/4-dark.jpeg) +![Light mode](../_images/4-light.jpeg) +![Dark mode](../_images/4-dark.jpeg) ``` light: { @@ -68,8 +68,8 @@ dark: { ## 米黄 -![Light mode](../../../assets/images/3-light.jpeg) -![Dark mode](../../../assets/images/3-dark.jpeg) +![Light mode](../_images/3-light.jpeg) +![Dark mode](../_images/3-dark.jpeg) ``` light: { @@ -82,4 +82,4 @@ dark: { secondary: 'oklch(0.80 0.017 59.39)', background: 'oklch(0.23 0 0)', }, -``` \ No newline at end of file +``` diff --git a/src/utils/feed.ts b/src/utils/feed.ts index f21a332..be09078 100644 --- a/src/utils/feed.ts +++ b/src/utils/feed.ts @@ -1,24 +1,100 @@ -import type { APIContext } from 'astro' +import type { APIContext, ImageMetadata } from 'astro' import type { CollectionEntry } from 'astro:content' import type { Author } from 'feed' import { defaultLocale, themeConfig } from '@/config' import { ui } from '@/i18n/ui' +import { memoize } from '@/utils/cache' import { generateDescription } from '@/utils/description' +import { getImage } from 'astro:assets' import { getCollection } from 'astro:content' import { Feed } from 'feed' import MarkdownIt from 'markdown-it' +import { parse as htmlParser } from 'node-html-parser' import sanitizeHtml from 'sanitize-html' -const markdownParser = new MarkdownIt() -const { title, description, url, author: siteAuthor } = themeConfig.site -const followConfig = themeConfig.seo?.follow - interface GenerateFeedOptions { lang?: string } +const markdownParser = new MarkdownIt() +const { title, description, url, author: siteAuthor } = themeConfig.site +const followConfig = themeConfig.seo?.follow + +// Dynamically import all images from /src/content/posts/_images +const imagesGlob = import.meta.glob<{ default: ImageMetadata }>( + '/src/content/posts/_images/**/*.{jpeg,jpg,png,gif,webp}', +) + +/** + * Optimize image URLs + * + * @param srcPath Source relative path of the image + * @param baseUrl Base URL of the site + * @returns Optimized full image URL or null + */ +const getOptimizedImageUrl = memoize(async (srcPath: string, baseUrl: string) => { + const prefixRemoved = srcPath.replace(/^\.\.\/|^\.\//g, '') + const rawImagePath = `/src/content/posts/${prefixRemoved}` + const rawImageMetadata = await imagesGlob[rawImagePath]?.()?.then(res => res.default) + + if (rawImageMetadata) { + const processedImageData = await getImage({ src: rawImageMetadata }) + return new URL(processedImageData.src, baseUrl).toString() + } + + return null +}) + +/** + * Fix relative image paths in HTML content + * + * @param htmlContent HTML content string + * @param baseUrl Base URL of the site + * @returns Processed HTML string with all image paths converted to absolute URLs + */ +async function fixRelativeImagePaths(htmlContent: string, baseUrl: string): Promise { + const htmlDoc = htmlParser(htmlContent) + const images = htmlDoc.querySelectorAll('img') + const imagePromises = [] + + for (const img of images) { + const src = img.getAttribute('src') + + if (!src) + continue + + imagePromises.push((async () => { + try { + // Process images from src/content/posts/_images directory + if (src.startsWith('./') || src.startsWith('../')) { + const optimizedImageUrl = await getOptimizedImageUrl(src, baseUrl) + + if (optimizedImageUrl) { + img.setAttribute('src', optimizedImageUrl) + } + } + // Process images from public/images directory + else if (src.startsWith('/images')) { + const publicImageUrl = new URL(src, baseUrl).toString() + img.setAttribute('src', publicImageUrl) + } + } + catch (error) { + console.warn(`Failed to process image in RSS feed: ${src}`, error) + } + })()) + } + + await Promise.all(imagePromises) + return htmlDoc.toString() +} + /** * Generate post URL with language prefix and abbrlink/slug + * + * @param post The post collection entry + * @param baseUrl Base URL of the site + * @returns The fully formed URL for the post */ function generatePostUrl(post: CollectionEntry<'posts'>, baseUrl: string): string { const needsLangPrefix = post.data.lang !== defaultLocale && post.data.lang !== '' @@ -30,6 +106,10 @@ function generatePostUrl(post: CollectionEntry<'posts'>, baseUrl: string): strin /** * Generate a feed object supporting both RSS and Atom formats + * + * @param options Feed generation options + * @param options.lang Optional language code + * @returns A Feed instance ready for RSS or Atom output */ export async function generateFeed({ lang }: GenerateFeedOptions = {}) { const currentUI = ui[lang as keyof typeof ui] || ui[defaultLocale as keyof typeof ui] @@ -66,24 +146,28 @@ export async function generateFeed({ lang }: GenerateFeedOptions = {}) { (!data.draft && (data.lang === lang || data.lang === '' || (lang === undefined && data.lang === defaultLocale))), ) - // Sort posts by published date in descending order - const sortedPosts = [...posts].sort((a, b) => - new Date(b.data.published).getTime() - new Date(a.data.published).getTime(), - ) - - // Limit to the latest 25 posts - const limitedPosts = sortedPosts.slice(0, 25) + // Sort posts by published date in descending order and limit to the latest 25 + const limitedPosts = [...posts] + .sort((a, b) => new Date(b.data.published).getTime() - new Date(a.data.published).getTime()) + .slice(0, 25) // Add posts to feed for (const post of limitedPosts) { const postLink = generatePostUrl(post, url) + // Optimize content processing const postContent = post.body - ? sanitizeHtml(markdownParser.render(post.body), { - allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']), - }) + ? sanitizeHtml( + await fixRelativeImagePaths(markdownParser.render(post.body), url), + { allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']) }, + ) : '' + // publishDate -> Atom:, RSS: + const publishDate = new Date(post.data.published) + // updateDate -> Atom:, RSS has no update tag + const updateDate = post.data.updated ? new Date(post.data.updated) : publishDate + feed.addItem({ title: post.data.title, id: postLink, @@ -91,10 +175,8 @@ export async function generateFeed({ lang }: GenerateFeedOptions = {}) { description: generateDescription(post, 'feed'), content: postContent, author: [author], - // published -> Atom:, RSS: - published: new Date(post.data.published), - // date -> Atom:, RSS has no update tag - date: post.data.updated ? new Date(post.data.updated) : new Date(post.data.published), + published: publishDate, + date: updateDate, }) } @@ -114,6 +196,9 @@ export async function generateFeed({ lang }: GenerateFeedOptions = {}) { /** * Generate RSS 2.0 format feed + * + * @param context Astro API context containing request params + * @returns Response object with RSS XML content */ export async function generateRSS(context: APIContext) { const feed = await generateFeed({ @@ -135,6 +220,9 @@ export async function generateRSS(context: APIContext) { /** * Generate Atom 1.0 format feed + * + * @param context Astro API context containing request params + * @returns Response object with Atom XML content */ export async function generateAtom(context: APIContext) { const feed = await generateFeed({