diff --git a/README.md b/README.md
index c5e13eb..485099a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
-
+
+
@@ -44,8 +44,8 @@ Retypeset is a static blog theme based on the [Astro](https://astro.build/) fram
-
-
+
+
@@ -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.
- [](https://app.netlify.com/start)
- [](https://vercel.com/new)
+ [](https://app.netlify.com/start)
+ [](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 @@
-
-
+
+
@@ -45,7 +45,7 @@ Retypeset 是一款基于 [Astro](https://astro.build/) 框架的静态博客主
-
+
@@ -78,8 +78,8 @@ Retypeset 是一款基于 [Astro](https://astro.build/) 框架的静态博客主
5. 参考 [Astro 部署指南](https://docs.astro.build/zh-cn/guides/deploy/),将博客部署至 Netlify、Vercel 等平台。
- [](https://app.netlify.com/start)
- [](https://vercel.com/new)
+ [](https://app.netlify.com/start)
+ [](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: {
@@ -32,8 +32,8 @@ dark: {
## Raven Teal
-
-
+
+
```
light: {
@@ -50,8 +50,8 @@ dark: {
## Ink Blue
-
-
+
+
```
light: {
@@ -68,8 +68,8 @@ dark: {
## Ecru
-
-
+
+
```
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: {
@@ -32,8 +32,8 @@ dark: {
## Azul Cuervo
-
-
+
+
```
light: {
@@ -50,8 +50,8 @@ dark: {
## Azul Tinta
-
-
+
+
```
light: {
@@ -68,8 +68,8 @@ dark: {
## Beige
-
-
+
+
```
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: {
@@ -32,8 +32,8 @@ dark: {
## 烏青
-
-
+
+
```
light: {
@@ -50,8 +50,8 @@ dark: {
## 墨藍
-
-
+
+
```
light: {
@@ -68,8 +68,8 @@ dark: {
## 米色
-
-
+
+
```
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: {
@@ -32,8 +32,8 @@ dark: {
## Воронёный
-
-
+
+
```
light: {
@@ -50,8 +50,8 @@ dark: {
## Чернильно-синий
-
-
+
+
```
light: {
@@ -68,8 +68,8 @@ dark: {
## Кремовый
-
-
+
+
```
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: {
@@ -32,8 +32,8 @@ dark: {
## 鴉青
-
-
+
+
```
light: {
@@ -50,8 +50,8 @@ dark: {
## 墨藍
-
-
+
+
```
light: {
@@ -68,8 +68,8 @@ dark: {
## 米黃
-
-
+
+
```
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: {
@@ -32,8 +32,8 @@ dark: {
## 鸦青
-
-
+
+
```
light: {
@@ -50,8 +50,8 @@ dark: {
## 墨蓝
-
-
+
+
```
light: {
@@ -68,8 +68,8 @@ dark: {
## 米黄
-
-
+
+
```
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({