mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 02:53:51 +00:00
Update blog metrics; add vercel imsights
This commit is contained in:
@@ -18,14 +18,17 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^5.0.3",
|
"@astrojs/mdx": "^5.0.3",
|
||||||
"@astrojs/vercel": "^10.0.3",
|
|
||||||
"@astrojs/rss": "^4.0.18",
|
"@astrojs/rss": "^4.0.18",
|
||||||
"@astrojs/sitemap": "^3.7.2",
|
"@astrojs/sitemap": "^3.7.2",
|
||||||
|
"@astrojs/vercel": "^10.0.3",
|
||||||
"@giscus/react": "^3.1.0",
|
"@giscus/react": "^3.1.0",
|
||||||
"@pilcrowjs/object-parser": "^0.0.4",
|
"@pilcrowjs/object-parser": "^0.0.4",
|
||||||
"@react-hook/intersection-observer": "^3.1.2",
|
"@react-hook/intersection-observer": "^3.1.2",
|
||||||
"@rehype-pretty/transformers": "^0.13.2",
|
"@rehype-pretty/transformers": "^0.13.2",
|
||||||
|
"@vercel/analytics": "^2.0.1",
|
||||||
|
"@vercel/speed-insights": "^2.0.0",
|
||||||
"arctic": "^3.7.0",
|
"arctic": "^3.7.0",
|
||||||
|
"ioredis": "^5.10.1",
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
"marked": "^15.0.12",
|
"marked": "^15.0.12",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|||||||
183
pnpm-lock.yaml
generated
183
pnpm-lock.yaml
generated
@@ -10,7 +10,7 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/mdx':
|
'@astrojs/mdx':
|
||||||
specifier: ^5.0.3
|
specifier: ^5.0.3
|
||||||
version: 5.0.3(astro@6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))
|
version: 5.0.3(astro@6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))
|
||||||
'@astrojs/rss':
|
'@astrojs/rss':
|
||||||
specifier: ^4.0.18
|
specifier: ^4.0.18
|
||||||
version: 4.0.18
|
version: 4.0.18
|
||||||
@@ -19,7 +19,7 @@ importers:
|
|||||||
version: 3.7.2
|
version: 3.7.2
|
||||||
'@astrojs/vercel':
|
'@astrojs/vercel':
|
||||||
specifier: ^10.0.3
|
specifier: ^10.0.3
|
||||||
version: 10.0.3(astro@6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))(react@18.3.1)(rollup@4.60.1)
|
version: 10.0.3(astro@6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))(react@18.3.1)(rollup@4.60.1)
|
||||||
'@giscus/react':
|
'@giscus/react':
|
||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 3.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -32,9 +32,18 @@ importers:
|
|||||||
'@rehype-pretty/transformers':
|
'@rehype-pretty/transformers':
|
||||||
specifier: ^0.13.2
|
specifier: ^0.13.2
|
||||||
version: 0.13.2
|
version: 0.13.2
|
||||||
|
'@vercel/analytics':
|
||||||
|
specifier: ^2.0.1
|
||||||
|
version: 2.0.1(react@18.3.1)
|
||||||
|
'@vercel/speed-insights':
|
||||||
|
specifier: ^2.0.0
|
||||||
|
version: 2.0.0(react@18.3.1)
|
||||||
arctic:
|
arctic:
|
||||||
specifier: ^3.7.0
|
specifier: ^3.7.0
|
||||||
version: 3.7.0
|
version: 3.7.0
|
||||||
|
ioredis:
|
||||||
|
specifier: ^5.10.1
|
||||||
|
version: 5.10.1
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.468.0
|
specifier: ^0.468.0
|
||||||
version: 0.468.0(react@18.3.1)
|
version: 0.468.0(react@18.3.1)
|
||||||
@@ -80,7 +89,7 @@ importers:
|
|||||||
version: 5.0.2(@types/node@24.12.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(jiti@1.21.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yaml@2.8.3)
|
version: 5.0.2(@types/node@24.12.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(jiti@1.21.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yaml@2.8.3)
|
||||||
'@astrojs/tailwind':
|
'@astrojs/tailwind':
|
||||||
specifier: ^6.0.2
|
specifier: ^6.0.2
|
||||||
version: 6.0.2(astro@6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))(tailwindcss@3.4.19(yaml@2.8.3))
|
version: 6.0.2(astro@6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))(tailwindcss@3.4.19(yaml@2.8.3))
|
||||||
'@tailwindcss/typography':
|
'@tailwindcss/typography':
|
||||||
specifier: ^0.5.19
|
specifier: ^0.5.19
|
||||||
version: 0.5.19(tailwindcss@3.4.19(yaml@2.8.3))
|
version: 0.5.19(tailwindcss@3.4.19(yaml@2.8.3))
|
||||||
@@ -92,7 +101,7 @@ importers:
|
|||||||
version: 18.3.7(@types/react@18.3.28)
|
version: 18.3.7(@types/react@18.3.28)
|
||||||
astro:
|
astro:
|
||||||
specifier: ^6.1.2
|
specifier: ^6.1.2
|
||||||
version: 6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3)
|
version: 6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3)
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.4.19
|
specifier: ^3.4.19
|
||||||
version: 3.4.19(yaml@2.8.3)
|
version: 3.4.19(yaml@2.8.3)
|
||||||
@@ -563,6 +572,9 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@ioredis/commands@1.5.1':
|
||||||
|
resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==}
|
||||||
|
|
||||||
'@isaacs/fs-minipass@4.0.1':
|
'@isaacs/fs-minipass@4.0.1':
|
||||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -913,6 +925,9 @@ packages:
|
|||||||
'@ungap/structured-clone@1.3.0':
|
'@ungap/structured-clone@1.3.0':
|
||||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
|
||||||
|
'@upstash/redis@1.37.0':
|
||||||
|
resolution: {integrity: sha512-LqOJ3+XWPLSZ2rGSed5DYG3ixybxb8EhZu3yQqF7MdZX1wLBG/FRcI6xcUZXHy/SS7mmXWyadrud0HJHkOc+uw==}
|
||||||
|
|
||||||
'@vercel/analytics@1.6.1':
|
'@vercel/analytics@1.6.1':
|
||||||
resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==}
|
resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -939,6 +954,35 @@ packages:
|
|||||||
vue-router:
|
vue-router:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@vercel/analytics@2.0.1':
|
||||||
|
resolution: {integrity: sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@remix-run/react': ^2
|
||||||
|
'@sveltejs/kit': ^1 || ^2
|
||||||
|
next: '>= 13'
|
||||||
|
nuxt: '>= 3'
|
||||||
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
svelte: '>= 4'
|
||||||
|
vue: ^3
|
||||||
|
vue-router: ^4
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@remix-run/react':
|
||||||
|
optional: true
|
||||||
|
'@sveltejs/kit':
|
||||||
|
optional: true
|
||||||
|
next:
|
||||||
|
optional: true
|
||||||
|
nuxt:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
svelte:
|
||||||
|
optional: true
|
||||||
|
vue:
|
||||||
|
optional: true
|
||||||
|
vue-router:
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@vercel/functions@3.4.3':
|
'@vercel/functions@3.4.3':
|
||||||
resolution: {integrity: sha512-kA14KIUVgAY6VXbhZ5jjY+s0883cV3cZqIU3WhrSRxuJ9KvxatMjtmzl0K23HK59oOUjYl7HaE/eYMmhmqpZzw==}
|
resolution: {integrity: sha512-kA14KIUVgAY6VXbhZ5jjY+s0883cV3cZqIU3WhrSRxuJ9KvxatMjtmzl0K23HK59oOUjYl7HaE/eYMmhmqpZzw==}
|
||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
@@ -948,6 +992,11 @@ packages:
|
|||||||
'@aws-sdk/credential-provider-web-identity':
|
'@aws-sdk/credential-provider-web-identity':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@vercel/kv@3.0.0':
|
||||||
|
resolution: {integrity: sha512-pKT8fRnfyYk2MgvyB6fn6ipJPCdfZwiKDdw7vB+HL50rjboEBHDVBEcnwfkEpVSp2AjNtoaOUH7zG+bVC/rvSg==}
|
||||||
|
engines: {node: '>=14.6'}
|
||||||
|
deprecated: 'Vercel KV is deprecated. If you had an existing KV store, it should have moved to Upstash Redis which you will see under Vercel Integrations. For new projects, install a Redis integration from Vercel Marketplace: https://vercel.com/marketplace?category=storage&search=redis'
|
||||||
|
|
||||||
'@vercel/nft@1.5.0':
|
'@vercel/nft@1.5.0':
|
||||||
resolution: {integrity: sha512-IWTDeIoWhQ7ZtRO/JRKH+jhmeQvZYhtGPmzw/QGDY+wDCQqfm25P9yIdoAFagu4fWsK4IwZXDFIjrmp5rRm/sA==}
|
resolution: {integrity: sha512-IWTDeIoWhQ7ZtRO/JRKH+jhmeQvZYhtGPmzw/QGDY+wDCQqfm25P9yIdoAFagu4fWsK4IwZXDFIjrmp5rRm/sA==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@@ -960,6 +1009,32 @@ packages:
|
|||||||
'@vercel/routing-utils@5.3.3':
|
'@vercel/routing-utils@5.3.3':
|
||||||
resolution: {integrity: sha512-KYm2sLNUD48gDScv8ob4ejc3Gww2jcJyW80hTdYlenAPz/5BQar1Gyh38xrUuZ532TUwSb5mV1uRbAuiykq0EQ==}
|
resolution: {integrity: sha512-KYm2sLNUD48gDScv8ob4ejc3Gww2jcJyW80hTdYlenAPz/5BQar1Gyh38xrUuZ532TUwSb5mV1uRbAuiykq0EQ==}
|
||||||
|
|
||||||
|
'@vercel/speed-insights@2.0.0':
|
||||||
|
resolution: {integrity: sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@sveltejs/kit': ^1 || ^2
|
||||||
|
next: '>= 13'
|
||||||
|
nuxt: '>= 3'
|
||||||
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
svelte: '>= 4'
|
||||||
|
vue: ^3
|
||||||
|
vue-router: ^4
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@sveltejs/kit':
|
||||||
|
optional: true
|
||||||
|
next:
|
||||||
|
optional: true
|
||||||
|
nuxt:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
svelte:
|
||||||
|
optional: true
|
||||||
|
vue:
|
||||||
|
optional: true
|
||||||
|
vue-router:
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@vitejs/plugin-react@5.2.0':
|
'@vitejs/plugin-react@5.2.0':
|
||||||
resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==}
|
resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@@ -1115,6 +1190,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
cluster-key-slot@1.1.2:
|
||||||
|
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
collapse-white-space@2.1.0:
|
collapse-white-space@2.1.0:
|
||||||
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
|
resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==}
|
||||||
|
|
||||||
@@ -1195,6 +1274,10 @@ packages:
|
|||||||
defu@6.1.4:
|
defu@6.1.4:
|
||||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||||
|
|
||||||
|
denque@2.1.0:
|
||||||
|
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
|
||||||
dequal@2.0.3:
|
dequal@2.0.3:
|
||||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1457,6 +1540,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==}
|
resolution: {integrity: sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==}
|
||||||
deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.
|
deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.
|
||||||
|
|
||||||
|
ioredis@5.10.1:
|
||||||
|
resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==}
|
||||||
|
engines: {node: '>=12.22.0'}
|
||||||
|
|
||||||
iron-webcrypto@1.2.1:
|
iron-webcrypto@1.2.1:
|
||||||
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
|
resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==}
|
||||||
|
|
||||||
@@ -1550,6 +1637,12 @@ packages:
|
|||||||
lit@3.3.2:
|
lit@3.3.2:
|
||||||
resolution: {integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==}
|
resolution: {integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==}
|
||||||
|
|
||||||
|
lodash.defaults@4.2.0:
|
||||||
|
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||||
|
|
||||||
|
lodash.isarguments@3.1.0:
|
||||||
|
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
|
||||||
|
|
||||||
longest-streak@3.1.0:
|
longest-streak@3.1.0:
|
||||||
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
||||||
|
|
||||||
@@ -2061,6 +2154,14 @@ packages:
|
|||||||
recma-stringify@1.0.0:
|
recma-stringify@1.0.0:
|
||||||
resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==}
|
resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==}
|
||||||
|
|
||||||
|
redis-errors@1.2.0:
|
||||||
|
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
redis-parser@3.0.0:
|
||||||
|
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
regex-recursion@6.0.2:
|
regex-recursion@6.0.2:
|
||||||
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
|
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
|
||||||
|
|
||||||
@@ -2202,6 +2303,9 @@ packages:
|
|||||||
space-separated-tokens@2.0.2:
|
space-separated-tokens@2.0.2:
|
||||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||||
|
|
||||||
|
standard-as-callback@2.1.0:
|
||||||
|
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
|
||||||
|
|
||||||
stream-replace-string@2.0.0:
|
stream-replace-string@2.0.0:
|
||||||
resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==}
|
resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==}
|
||||||
|
|
||||||
@@ -2559,12 +2663,12 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@astrojs/mdx@5.0.3(astro@6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))':
|
'@astrojs/mdx@5.0.3(astro@6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/markdown-remark': 7.1.0
|
'@astrojs/markdown-remark': 7.1.0
|
||||||
'@mdx-js/mdx': 3.1.1
|
'@mdx-js/mdx': 3.1.1
|
||||||
acorn: 8.16.0
|
acorn: 8.16.0
|
||||||
astro: 6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3)
|
astro: 6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3)
|
||||||
es-module-lexer: 2.0.0
|
es-module-lexer: 2.0.0
|
||||||
estree-util-visit: 2.0.0
|
estree-util-visit: 2.0.0
|
||||||
hast-util-to-html: 9.0.5
|
hast-util-to-html: 9.0.5
|
||||||
@@ -2619,9 +2723,9 @@ snapshots:
|
|||||||
stream-replace-string: 2.0.0
|
stream-replace-string: 2.0.0
|
||||||
zod: 4.3.6
|
zod: 4.3.6
|
||||||
|
|
||||||
'@astrojs/tailwind@6.0.2(astro@6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))(tailwindcss@3.4.19(yaml@2.8.3))':
|
'@astrojs/tailwind@6.0.2(astro@6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))(tailwindcss@3.4.19(yaml@2.8.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
astro: 6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3)
|
astro: 6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3)
|
||||||
autoprefixer: 10.4.27(postcss@8.5.8)
|
autoprefixer: 10.4.27(postcss@8.5.8)
|
||||||
postcss: 8.5.8
|
postcss: 8.5.8
|
||||||
postcss-load-config: 4.0.2(postcss@8.5.8)
|
postcss-load-config: 4.0.2(postcss@8.5.8)
|
||||||
@@ -2641,14 +2745,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@astrojs/vercel@10.0.3(astro@6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))(react@18.3.1)(rollup@4.60.1)':
|
'@astrojs/vercel@10.0.3(astro@6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3))(react@18.3.1)(rollup@4.60.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/internal-helpers': 0.8.0
|
'@astrojs/internal-helpers': 0.8.0
|
||||||
'@vercel/analytics': 1.6.1(react@18.3.1)
|
'@vercel/analytics': 1.6.1(react@18.3.1)
|
||||||
'@vercel/functions': 3.4.3
|
'@vercel/functions': 3.4.3
|
||||||
'@vercel/nft': 1.5.0(rollup@4.60.1)
|
'@vercel/nft': 1.5.0(rollup@4.60.1)
|
||||||
'@vercel/routing-utils': 5.3.3
|
'@vercel/routing-utils': 5.3.3
|
||||||
astro: 6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3)
|
astro: 6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3)
|
||||||
esbuild: 0.27.4
|
esbuild: 0.27.4
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -2975,6 +3079,8 @@ snapshots:
|
|||||||
'@img/sharp-win32-x64@0.34.5':
|
'@img/sharp-win32-x64@0.34.5':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@ioredis/commands@1.5.1': {}
|
||||||
|
|
||||||
'@isaacs/fs-minipass@4.0.1':
|
'@isaacs/fs-minipass@4.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass: 7.1.3
|
minipass: 7.1.3
|
||||||
@@ -3327,14 +3433,28 @@ snapshots:
|
|||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
|
'@upstash/redis@1.37.0':
|
||||||
|
dependencies:
|
||||||
|
uncrypto: 0.1.3
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@vercel/analytics@1.6.1(react@18.3.1)':
|
'@vercel/analytics@1.6.1(react@18.3.1)':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
|
'@vercel/analytics@2.0.1(react@18.3.1)':
|
||||||
|
optionalDependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
'@vercel/functions@3.4.3':
|
'@vercel/functions@3.4.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vercel/oidc': 3.2.0
|
'@vercel/oidc': 3.2.0
|
||||||
|
|
||||||
|
'@vercel/kv@3.0.0':
|
||||||
|
dependencies:
|
||||||
|
'@upstash/redis': 1.37.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@vercel/nft@1.5.0(rollup@4.60.1)':
|
'@vercel/nft@1.5.0(rollup@4.60.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@mapbox/node-pre-gyp': 2.0.3
|
'@mapbox/node-pre-gyp': 2.0.3
|
||||||
@@ -3363,6 +3483,10 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ajv: 6.14.0
|
ajv: 6.14.0
|
||||||
|
|
||||||
|
'@vercel/speed-insights@2.0.0(react@18.3.1)':
|
||||||
|
optionalDependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
'@vitejs/plugin-react@5.2.0(vite@7.3.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.3))':
|
'@vitejs/plugin-react@5.2.0(vite@7.3.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.3))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.29.0
|
'@babel/core': 7.29.0
|
||||||
@@ -3420,7 +3544,7 @@ snapshots:
|
|||||||
|
|
||||||
astring@1.9.0: {}
|
astring@1.9.0: {}
|
||||||
|
|
||||||
astro@6.1.2(@types/node@24.12.0)(@vercel/functions@3.4.3)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3):
|
astro@6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/compiler': 3.0.1
|
'@astrojs/compiler': 3.0.1
|
||||||
'@astrojs/internal-helpers': 0.8.0
|
'@astrojs/internal-helpers': 0.8.0
|
||||||
@@ -3470,7 +3594,7 @@ snapshots:
|
|||||||
ultrahtml: 1.6.0
|
ultrahtml: 1.6.0
|
||||||
unifont: 0.7.4
|
unifont: 0.7.4
|
||||||
unist-util-visit: 5.1.0
|
unist-util-visit: 5.1.0
|
||||||
unstorage: 1.17.5(@vercel/functions@3.4.3)
|
unstorage: 1.17.5(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)
|
||||||
vfile: 6.0.3
|
vfile: 6.0.3
|
||||||
vite: 7.3.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.3)
|
vite: 7.3.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.3)
|
||||||
vitefu: 1.1.2(vite@7.3.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.3))
|
vitefu: 1.1.2(vite@7.3.1(@types/node@24.12.0)(jiti@1.21.7)(yaml@2.8.3))
|
||||||
@@ -3593,6 +3717,8 @@ snapshots:
|
|||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
|
cluster-key-slot@1.1.2: {}
|
||||||
|
|
||||||
collapse-white-space@2.1.0: {}
|
collapse-white-space@2.1.0: {}
|
||||||
|
|
||||||
comma-separated-tokens@2.0.3: {}
|
comma-separated-tokens@2.0.3: {}
|
||||||
@@ -3655,6 +3781,8 @@ snapshots:
|
|||||||
|
|
||||||
defu@6.1.4: {}
|
defu@6.1.4: {}
|
||||||
|
|
||||||
|
denque@2.1.0: {}
|
||||||
|
|
||||||
dequal@2.0.3: {}
|
dequal@2.0.3: {}
|
||||||
|
|
||||||
destr@2.0.5: {}
|
destr@2.0.5: {}
|
||||||
@@ -4037,6 +4165,20 @@ snapshots:
|
|||||||
|
|
||||||
intersection-observer@0.10.0: {}
|
intersection-observer@0.10.0: {}
|
||||||
|
|
||||||
|
ioredis@5.10.1:
|
||||||
|
dependencies:
|
||||||
|
'@ioredis/commands': 1.5.1
|
||||||
|
cluster-key-slot: 1.1.2
|
||||||
|
debug: 4.4.3
|
||||||
|
denque: 2.1.0
|
||||||
|
lodash.defaults: 4.2.0
|
||||||
|
lodash.isarguments: 3.1.0
|
||||||
|
redis-errors: 1.2.0
|
||||||
|
redis-parser: 3.0.0
|
||||||
|
standard-as-callback: 2.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
iron-webcrypto@1.2.1: {}
|
iron-webcrypto@1.2.1: {}
|
||||||
|
|
||||||
is-alphabetical@2.0.1: {}
|
is-alphabetical@2.0.1: {}
|
||||||
@@ -4113,6 +4255,10 @@ snapshots:
|
|||||||
lit-element: 4.2.2
|
lit-element: 4.2.2
|
||||||
lit-html: 3.3.2
|
lit-html: 3.3.2
|
||||||
|
|
||||||
|
lodash.defaults@4.2.0: {}
|
||||||
|
|
||||||
|
lodash.isarguments@3.1.0: {}
|
||||||
|
|
||||||
longest-streak@3.1.0: {}
|
longest-streak@3.1.0: {}
|
||||||
|
|
||||||
loose-envify@1.4.0:
|
loose-envify@1.4.0:
|
||||||
@@ -4868,6 +5014,12 @@ snapshots:
|
|||||||
unified: 11.0.5
|
unified: 11.0.5
|
||||||
vfile: 6.0.3
|
vfile: 6.0.3
|
||||||
|
|
||||||
|
redis-errors@1.2.0: {}
|
||||||
|
|
||||||
|
redis-parser@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
redis-errors: 1.2.0
|
||||||
|
|
||||||
regex-recursion@6.0.2:
|
regex-recursion@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
regex-utilities: 2.3.0
|
regex-utilities: 2.3.0
|
||||||
@@ -5132,6 +5284,8 @@ snapshots:
|
|||||||
|
|
||||||
space-separated-tokens@2.0.2: {}
|
space-separated-tokens@2.0.2: {}
|
||||||
|
|
||||||
|
standard-as-callback@2.1.0: {}
|
||||||
|
|
||||||
stream-replace-string@2.0.0: {}
|
stream-replace-string@2.0.0: {}
|
||||||
|
|
||||||
stringify-entities@4.0.4:
|
stringify-entities@4.0.4:
|
||||||
@@ -5325,7 +5479,7 @@ snapshots:
|
|||||||
unist-util-is: 6.0.1
|
unist-util-is: 6.0.1
|
||||||
unist-util-visit-parents: 6.0.2
|
unist-util-visit-parents: 6.0.2
|
||||||
|
|
||||||
unstorage@1.17.5(@vercel/functions@3.4.3):
|
unstorage@1.17.5(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
anymatch: 3.1.3
|
anymatch: 3.1.3
|
||||||
chokidar: 5.0.0
|
chokidar: 5.0.0
|
||||||
@@ -5336,7 +5490,10 @@ snapshots:
|
|||||||
ofetch: 1.5.1
|
ofetch: 1.5.1
|
||||||
ufo: 1.6.3
|
ufo: 1.6.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
'@upstash/redis': 1.37.0
|
||||||
'@vercel/functions': 3.4.3
|
'@vercel/functions': 3.4.3
|
||||||
|
'@vercel/kv': 3.0.0
|
||||||
|
ioredis: 5.10.1
|
||||||
|
|
||||||
update-browserslist-db@1.2.3(browserslist@4.28.1):
|
update-browserslist-db@1.2.3(browserslist@4.28.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
11
src/components/analytics.tsx
Normal file
11
src/components/analytics.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Analytics } from "@vercel/analytics/react";
|
||||||
|
import { SpeedInsights } from "@vercel/speed-insights/react";
|
||||||
|
|
||||||
|
export default function VercelAnalytics() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Analytics />
|
||||||
|
<SpeedInsights />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -77,7 +77,7 @@ export const BlogPostList = ({ posts }: BlogPostListProps) => {
|
|||||||
className="text-xs md:text-base text-aqua hover:text-aqua-bright transition-colors duration-200"
|
className="text-xs md:text-base text-aqua hover:text-aqua-bright transition-colors duration-200"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.location.href = `/blog/tag/${tag}`;
|
window.location.href = `/blog/tags/${encodeURIComponent(tag)}`;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
#{tag}
|
#{tag}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { AnimateIn } from "@/components/animate-in";
|
||||||
|
|
||||||
interface BlogPost {
|
interface BlogPost {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -11,47 +12,49 @@ interface TagListProps {
|
|||||||
posts: BlogPost[];
|
posts: BlogPost[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagList: React.FC<TagListProps> = ({ posts }) => {
|
const spectrumColors = [
|
||||||
const spectrumColors = [
|
'text-red-bright',
|
||||||
'text-red-bright',
|
'text-orange-bright',
|
||||||
'text-orange-bright',
|
'text-yellow-bright',
|
||||||
'text-yellow-bright',
|
'text-green-bright',
|
||||||
'text-green-bright',
|
'text-aqua-bright',
|
||||||
'text-aqua-bright',
|
'text-blue-bright',
|
||||||
'text-blue-bright',
|
'text-purple-bright'
|
||||||
'text-purple-bright'
|
];
|
||||||
];
|
|
||||||
|
|
||||||
|
const sizeClasses = [
|
||||||
|
'text-3xl sm:text-4xl',
|
||||||
|
'text-2xl sm:text-3xl',
|
||||||
|
'text-xl sm:text-2xl',
|
||||||
|
'text-lg sm:text-xl',
|
||||||
|
'text-base sm:text-lg',
|
||||||
|
];
|
||||||
|
|
||||||
|
const TagList = ({ posts }: TagListProps) => {
|
||||||
const tagData = useMemo(() => {
|
const tagData = useMemo(() => {
|
||||||
if (!Array.isArray(posts)) return [];
|
if (!Array.isArray(posts)) return [];
|
||||||
|
|
||||||
const tagMap = new Map();
|
const tagMap = new Map<string, number>();
|
||||||
posts.forEach(post => {
|
posts.forEach(post => {
|
||||||
if (post?.data?.tags && Array.isArray(post.data.tags)) {
|
post?.data?.tags?.forEach(tag => {
|
||||||
post.data.tags.forEach(tag => {
|
tagMap.set(tag, (tagMap.get(tag) || 0) + 1);
|
||||||
if (!tagMap.has(tag)) {
|
});
|
||||||
tagMap.set(tag, {
|
|
||||||
name: tag,
|
|
||||||
count: 1
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const data = tagMap.get(tag);
|
|
||||||
data.count++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const tagArray = Array.from(tagMap.values());
|
const tags = Array.from(tagMap.entries())
|
||||||
const maxCount = Math.max(...tagArray.map(t => t.count));
|
.sort((a, b) => b[1] - a[1]);
|
||||||
|
const maxCount = tags[0]?.[1] || 1;
|
||||||
|
|
||||||
return tagArray
|
return tags.map(([name, count], i) => {
|
||||||
.sort((a, b) => b.count - a.count)
|
const ratio = count / maxCount;
|
||||||
.map((tag, index) => ({
|
const sizeIndex = ratio > 0.8 ? 0 : ratio > 0.6 ? 1 : ratio > 0.4 ? 2 : ratio > 0.2 ? 3 : 4;
|
||||||
...tag,
|
return {
|
||||||
color: spectrumColors[index % spectrumColors.length],
|
name,
|
||||||
frequency: tag.count / maxCount
|
count,
|
||||||
}));
|
color: spectrumColors[i % spectrumColors.length],
|
||||||
|
size: sizeClasses[sizeIndex],
|
||||||
|
};
|
||||||
|
});
|
||||||
}, [posts]);
|
}, [posts]);
|
||||||
|
|
||||||
if (tagData.length === 0) {
|
if (tagData.length === 0) {
|
||||||
@@ -63,50 +66,25 @@ const TagList: React.FC<TagListProps> = ({ posts }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 w-full bg-background p-4">
|
<div className="flex flex-wrap items-baseline justify-center gap-x-6 gap-y-4 sm:gap-x-8 sm:gap-y-5 px-4 py-8 max-w-4xl mx-auto">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
{tagData.map(({ name, count, color, size }, i) => (
|
||||||
{tagData.map(({ name, count, color, frequency }) => (
|
<AnimateIn key={name} delay={i * 50}>
|
||||||
<a
|
<a
|
||||||
key={name}
|
|
||||||
href={`/blog/tags/${encodeURIComponent(name)}`}
|
href={`/blog/tags/${encodeURIComponent(name)}`}
|
||||||
className={`
|
className={`
|
||||||
group relative
|
${color} ${size}
|
||||||
flex flex-col items-center justify-center
|
font-medium
|
||||||
min-h-[5rem]
|
hover:opacity-70 transition-opacity duration-200
|
||||||
px-6 py-4 rounded-lg
|
cursor-pointer whitespace-nowrap
|
||||||
text-xl
|
|
||||||
transition-all duration-300 ease-in-out
|
|
||||||
hover:scale-105
|
|
||||||
hover:bg-foreground/5
|
|
||||||
${color}
|
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{/* Main tag display */}
|
#{name}
|
||||||
<div className="font-medium text-center">
|
<span className="text-foreground/30 text-xs ml-1 align-super">
|
||||||
#{name}
|
{count}
|
||||||
</div>
|
</span>
|
||||||
|
|
||||||
{/* Post count */}
|
|
||||||
<div className="mt-2 text-base opacity-60">
|
|
||||||
{count} post{count !== 1 ? 's' : ''}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Background gradient */}
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 -z-10 rounded-lg opacity-10"
|
|
||||||
style={{
|
|
||||||
background: `
|
|
||||||
linear-gradient(
|
|
||||||
45deg,
|
|
||||||
currentColor ${frequency * 100}%,
|
|
||||||
transparent
|
|
||||||
)
|
|
||||||
`
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</a>
|
</a>
|
||||||
))}
|
</AnimateIn>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
123
src/components/blog/tagged-posts.tsx
Normal file
123
src/components/blog/tagged-posts.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { AnimateIn } from "@/components/animate-in";
|
||||||
|
import { RssIcon, TagIcon, TrendingUpIcon } from "lucide-react";
|
||||||
|
|
||||||
|
type BlogPost = {
|
||||||
|
id: string;
|
||||||
|
data: {
|
||||||
|
title: string;
|
||||||
|
author: string;
|
||||||
|
date: string;
|
||||||
|
tags: string[];
|
||||||
|
description: string;
|
||||||
|
image?: string;
|
||||||
|
imagePosition?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TaggedPostsProps {
|
||||||
|
tag: string;
|
||||||
|
posts: BlogPost[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (dateString: string) => {
|
||||||
|
return new Date(dateString).toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaggedPosts = ({ tag, posts }: TaggedPostsProps) => {
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-6xl mx-auto">
|
||||||
|
<div className="w-full px-4 pt-24 sm:pt-24">
|
||||||
|
<AnimateIn>
|
||||||
|
<h1 className="text-2xl sm:text-3xl font-bold text-purple mb-3 text-center px-4 leading-relaxed">
|
||||||
|
#{tag}
|
||||||
|
</h1>
|
||||||
|
</AnimateIn>
|
||||||
|
<AnimateIn delay={100}>
|
||||||
|
<div className="flex flex-wrap justify-center gap-4 mb-12 text-sm sm:text-base">
|
||||||
|
<a
|
||||||
|
href="/rss"
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-background border border-foreground/20 text-orange hover:text-orange-bright hover:border-orange/50 transition-colors duration-200"
|
||||||
|
>
|
||||||
|
<RssIcon className="w-4 h-4" />
|
||||||
|
<span>RSS Feed</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/blog/tags"
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-background border border-foreground/20 text-aqua hover:text-aqua-bright hover:border-aqua/50 transition-colors duration-200"
|
||||||
|
>
|
||||||
|
<TagIcon className="w-4 h-4" />
|
||||||
|
<span>Browse Tags</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/blog/popular"
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-background border border-foreground/20 text-blue hover:text-blue-bright hover:border-blue/50 transition-colors duration-200"
|
||||||
|
>
|
||||||
|
<TrendingUpIcon className="w-4 h-4" />
|
||||||
|
<span>Most Popular</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</AnimateIn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="space-y-6 md:space-y-10">
|
||||||
|
{posts.map((post, i) => (
|
||||||
|
<AnimateIn key={post.id} delay={200 + i * 80}>
|
||||||
|
<li className="group px-4 md:px-0">
|
||||||
|
<a href={`/blog/${post.id}`} className="block">
|
||||||
|
<article className="flex flex-col md:flex-row md:items-center gap-4 md:gap-8 p-2 md:p-4 border-b border-foreground/20 last:border-b-0 rounded-lg group-hover:outline group-hover:outline-2 group-hover:outline-purple transition-all duration-200">
|
||||||
|
<div className="w-full md:w-1/3 aspect-[16/9] overflow-hidden rounded-lg bg-background flex-shrink-0">
|
||||||
|
<img
|
||||||
|
src={post.data.image || "/blog/placeholder.png"}
|
||||||
|
alt={post.data.title}
|
||||||
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||||
|
style={{ objectPosition: post.data.imagePosition || "center center" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-full md:w-2/3 flex flex-col gap-2 md:gap-3">
|
||||||
|
<div className="space-y-1.5 md:space-y-3">
|
||||||
|
<h2 className="text-lg md:text-2xl font-semibold text-yellow group-hover:text-purple transition-colors duration-200 line-clamp-2">
|
||||||
|
{post.data.title}
|
||||||
|
</h2>
|
||||||
|
<div className="flex flex-wrap items-center gap-2 md:gap-3 text-sm md:text-base text-foreground/80">
|
||||||
|
<span className="text-orange">{post.data.author}</span>
|
||||||
|
<span className="text-foreground/50">•</span>
|
||||||
|
<time dateTime={post.data.date} className="text-blue">
|
||||||
|
{formatDate(post.data.date)}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-foreground/90 text-sm md:text-lg leading-relaxed line-clamp-2 mt-0.5 md:mt-0">
|
||||||
|
{post.data.description}
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-1.5 md:gap-3 mt-1 md:mt-2">
|
||||||
|
{post.data.tags.map((t) => (
|
||||||
|
<span
|
||||||
|
key={t}
|
||||||
|
className={`text-xs md:text-base transition-colors duration-200 ${
|
||||||
|
t === tag ? "text-aqua-bright" : "text-aqua hover:text-aqua-bright"
|
||||||
|
}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
window.location.href = `/blog/tags/${encodeURIComponent(t)}`;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
#{t}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</AnimateIn>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaggedPosts;
|
||||||
@@ -7,6 +7,7 @@ import Footer from "@/components/footer";
|
|||||||
import Background from "@/components/background";
|
import Background from "@/components/background";
|
||||||
import ThemeSwitcher from "@/components/theme-switcher";
|
import ThemeSwitcher from "@/components/theme-switcher";
|
||||||
import AnimationSwitcher from "@/components/animation-switcher";
|
import AnimationSwitcher from "@/components/animation-switcher";
|
||||||
|
import VercelAnalytics from "@/components/analytics";
|
||||||
import { THEME_LOADER_SCRIPT, THEME_NAV_SCRIPT } from "@/lib/themes/loader";
|
import { THEME_LOADER_SCRIPT, THEME_NAV_SCRIPT } from "@/lib/themes/loader";
|
||||||
import { ANIMATION_LOADER_SCRIPT, ANIMATION_NAV_SCRIPT } from "@/lib/animations/loader";
|
import { ANIMATION_LOADER_SCRIPT, ANIMATION_NAV_SCRIPT } from "@/lib/animations/loader";
|
||||||
|
|
||||||
@@ -68,6 +69,7 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
</div>
|
</div>
|
||||||
<ThemeSwitcher client:only="react" transition:persist />
|
<ThemeSwitcher client:only="react" transition:persist />
|
||||||
<AnimationSwitcher client:only="react" transition:persist />
|
<AnimationSwitcher client:only="react" transition:persist />
|
||||||
|
<VercelAnalytics client:load />
|
||||||
<script is:inline set:html={`window.scrollTo(0,0);` + THEME_NAV_SCRIPT} />
|
<script is:inline set:html={`window.scrollTo(0,0);` + THEME_NAV_SCRIPT} />
|
||||||
<script is:inline set:html={ANIMATION_NAV_SCRIPT} />
|
<script is:inline set:html={ANIMATION_NAV_SCRIPT} />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Footer from "@/components/footer";
|
|||||||
import Background from "@/components/background";
|
import Background from "@/components/background";
|
||||||
import ThemeSwitcher from "@/components/theme-switcher";
|
import ThemeSwitcher from "@/components/theme-switcher";
|
||||||
import AnimationSwitcher from "@/components/animation-switcher";
|
import AnimationSwitcher from "@/components/animation-switcher";
|
||||||
|
import VercelAnalytics from "@/components/analytics";
|
||||||
import { THEME_LOADER_SCRIPT, THEME_NAV_SCRIPT } from "@/lib/themes/loader";
|
import { THEME_LOADER_SCRIPT, THEME_NAV_SCRIPT } from "@/lib/themes/loader";
|
||||||
import { ANIMATION_LOADER_SCRIPT, ANIMATION_NAV_SCRIPT } from "@/lib/animations/loader";
|
import { ANIMATION_LOADER_SCRIPT, ANIMATION_NAV_SCRIPT } from "@/lib/animations/loader";
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
<Footer client:load transition:persist fixed=true />
|
<Footer client:load transition:persist fixed=true />
|
||||||
<ThemeSwitcher client:only="react" transition:persist />
|
<ThemeSwitcher client:only="react" transition:persist />
|
||||||
<AnimationSwitcher client:only="react" transition:persist />
|
<AnimationSwitcher client:only="react" transition:persist />
|
||||||
|
<VercelAnalytics client:load />
|
||||||
<script is:inline set:html={THEME_NAV_SCRIPT} />
|
<script is:inline set:html={THEME_NAV_SCRIPT} />
|
||||||
<script is:inline set:html={ANIMATION_NAV_SCRIPT} />
|
<script is:inline set:html={ANIMATION_NAV_SCRIPT} />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Footer from "@/components/footer";
|
|||||||
import Background from "@/components/background";
|
import Background from "@/components/background";
|
||||||
import ThemeSwitcher from "@/components/theme-switcher";
|
import ThemeSwitcher from "@/components/theme-switcher";
|
||||||
import AnimationSwitcher from "@/components/animation-switcher";
|
import AnimationSwitcher from "@/components/animation-switcher";
|
||||||
|
import VercelAnalytics from "@/components/analytics";
|
||||||
import { THEME_LOADER_SCRIPT, THEME_NAV_SCRIPT } from "@/lib/themes/loader";
|
import { THEME_LOADER_SCRIPT, THEME_NAV_SCRIPT } from "@/lib/themes/loader";
|
||||||
import { ANIMATION_LOADER_SCRIPT, ANIMATION_NAV_SCRIPT } from "@/lib/animations/loader";
|
import { ANIMATION_LOADER_SCRIPT, ANIMATION_NAV_SCRIPT } from "@/lib/animations/loader";
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
</main>
|
</main>
|
||||||
<ThemeSwitcher client:only="react" transition:persist />
|
<ThemeSwitcher client:only="react" transition:persist />
|
||||||
<AnimationSwitcher client:only="react" transition:persist />
|
<AnimationSwitcher client:only="react" transition:persist />
|
||||||
|
<VercelAnalytics client:load />
|
||||||
<script is:inline set:html={`window.scrollTo(0,0);` + THEME_NAV_SCRIPT} />
|
<script is:inline set:html={`window.scrollTo(0,0);` + THEME_NAV_SCRIPT} />
|
||||||
<script is:inline set:html={ANIMATION_NAV_SCRIPT} />
|
<script is:inline set:html={ANIMATION_NAV_SCRIPT} />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
55
src/lib/views.ts
Normal file
55
src/lib/views.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import Redis from "ioredis";
|
||||||
|
|
||||||
|
let redis: Redis | null = null;
|
||||||
|
|
||||||
|
function getRedis(): Redis | null {
|
||||||
|
if (redis) return redis;
|
||||||
|
|
||||||
|
const url = import.meta.env.REDIS_URL || process.env.REDIS_URL;
|
||||||
|
if (!url) return null;
|
||||||
|
|
||||||
|
redis = new Redis(url);
|
||||||
|
return redis;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function incrementViews(slug: string): Promise<number> {
|
||||||
|
const r = getRedis();
|
||||||
|
if (!r) return 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await r.incr(`views:${slug}`);
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getViews(slug: string): Promise<number> {
|
||||||
|
const r = getRedis();
|
||||||
|
if (!r) return 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const val = await r.get(`views:${slug}`);
|
||||||
|
return val ? parseInt(val, 10) : 0;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllViews(slugs: string[]): Promise<Record<string, number>> {
|
||||||
|
const r = getRedis();
|
||||||
|
const result: Record<string, number> = {};
|
||||||
|
|
||||||
|
if (!r || slugs.length === 0) return result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const keys = slugs.map(s => `views:${s}`);
|
||||||
|
const values = await r.mget(...keys);
|
||||||
|
for (let i = 0; i < slugs.length; i++) {
|
||||||
|
result[slugs[i]] = values[i] ? parseInt(values[i], 10) : 0;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Return empty counts if Redis unavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import ContentLayout from "@/layouts/content.astro";
|
|||||||
import { getArticleSchema } from "@/lib/structuredData";
|
import { getArticleSchema } from "@/lib/structuredData";
|
||||||
import { blogWebsite } from "@/lib/structuredData";
|
import { blogWebsite } from "@/lib/structuredData";
|
||||||
import { Comments } from "@/components/blog/comments";
|
import { Comments } from "@/components/blog/comments";
|
||||||
|
import { incrementViews, getViews } from "@/lib/views";
|
||||||
|
|
||||||
// This is a dynamic route in SSR mode
|
// This is a dynamic route in SSR mode
|
||||||
const { slug } = Astro.params;
|
const { slug } = Astro.params;
|
||||||
@@ -20,6 +21,14 @@ if (!post || (!import.meta.env.DEV && post.data.isDraft === true)) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track page view and get count
|
||||||
|
let views = 0;
|
||||||
|
if (!import.meta.env.DEV) {
|
||||||
|
views = await incrementViews(post.id);
|
||||||
|
} else {
|
||||||
|
views = await getViews(post.id);
|
||||||
|
}
|
||||||
|
|
||||||
// Dynamically render the content
|
// Dynamically render the content
|
||||||
const { Content } = await render(post);
|
const { Content } = await render(post);
|
||||||
|
|
||||||
@@ -84,12 +93,18 @@ const jsonLd = {
|
|||||||
<time dateTime={post.data.date instanceof Date ? post.data.date.toISOString() : post.data.date} class="text-blue">
|
<time dateTime={post.data.date instanceof Date ? post.data.date.toISOString() : post.data.date} class="text-blue">
|
||||||
{formattedDate}
|
{formattedDate}
|
||||||
</time>
|
</time>
|
||||||
|
{views > 0 && (
|
||||||
|
<>
|
||||||
|
<span class="text-foreground/50">•</span>
|
||||||
|
<span class="text-green">{views.toLocaleString()} view{views !== 1 ? "s" : ""}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2 mt-2">
|
<div class="flex flex-wrap gap-2 mt-2">
|
||||||
{post.data.tags.map((tag) => (
|
{post.data.tags.map((tag) => (
|
||||||
<span
|
<span
|
||||||
class="text-xs md:text-base text-aqua hover:text-aqua-bright transition-colors duration-200"
|
class="text-xs md:text-base text-aqua hover:text-aqua-bright transition-colors duration-200"
|
||||||
onclick={`window.location.href='/blog/tag/${tag}'`}
|
onclick={`window.location.href='/blog/tags/${encodeURIComponent(tag)}'`}
|
||||||
>
|
>
|
||||||
#{tag}
|
#{tag}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
32
src/pages/blog/popular/index.astro
Normal file
32
src/pages/blog/popular/index.astro
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
import ContentLayout from "@/layouts/content.astro";
|
||||||
|
import { BlogHeader } from "@/components/blog/header";
|
||||||
|
import { BlogPostList } from "@/components/blog/post-list";
|
||||||
|
import { getAllViews } from "@/lib/views";
|
||||||
|
|
||||||
|
const posts = (await getCollection("blog", ({ data }) => {
|
||||||
|
return import.meta.env.DEV || data.isDraft !== true;
|
||||||
|
})).map(post => ({
|
||||||
|
...post,
|
||||||
|
data: {
|
||||||
|
...post.data,
|
||||||
|
date: post.data.date.toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Get view counts and sort by popularity
|
||||||
|
const views = await getAllViews(posts.map(p => p.id));
|
||||||
|
const sorted = [...posts].sort((a, b) => (views[b.id] || 0) - (views[a.id] || 0));
|
||||||
|
---
|
||||||
|
<ContentLayout
|
||||||
|
title="Most Popular | Blog | Timothy Pidashev"
|
||||||
|
description="Most popular blog posts by view count."
|
||||||
|
>
|
||||||
|
<BlogHeader client:load />
|
||||||
|
<BlogPostList posts={sorted} client:load />
|
||||||
|
</ContentLayout>
|
||||||
38
src/pages/blog/tags/[...slug].astro
Normal file
38
src/pages/blog/tags/[...slug].astro
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
import ContentLayout from "@/layouts/content.astro";
|
||||||
|
import TaggedPosts from "@/components/blog/tagged-posts";
|
||||||
|
|
||||||
|
const { slug } = Astro.params;
|
||||||
|
const tag = decodeURIComponent(slug || "");
|
||||||
|
|
||||||
|
if (!tag) {
|
||||||
|
return Astro.redirect("/blog/tags");
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredPosts = (await getCollection("blog", ({ data }) => {
|
||||||
|
return (import.meta.env.DEV || data.isDraft !== true) && data.tags.includes(tag);
|
||||||
|
})).sort((a, b) => {
|
||||||
|
return b.data.date.valueOf() - a.data.date.valueOf();
|
||||||
|
}).map(post => ({
|
||||||
|
...post,
|
||||||
|
data: {
|
||||||
|
...post.data,
|
||||||
|
date: post.data.date.toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (filteredPosts.length === 0) {
|
||||||
|
return Astro.redirect("/blog/tags");
|
||||||
|
}
|
||||||
|
---
|
||||||
|
<ContentLayout
|
||||||
|
title={`#${tag} | Blog | Timothy Pidashev`}
|
||||||
|
description={`Blog posts tagged with "${tag}".`}
|
||||||
|
>
|
||||||
|
<TaggedPosts tag={tag} posts={filteredPosts} client:load />
|
||||||
|
</ContentLayout>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import ContentLayout from "@/layouts/content.astro";
|
import ContentLayout from "@/layouts/content.astro";
|
||||||
|
import { BlogHeader } from "@/components/blog/header";
|
||||||
import TagList from "@/components/blog/tag-list";
|
import TagList from "@/components/blog/tag-list";
|
||||||
|
|
||||||
const posts = (await getCollection("blog", ({ data }) => {
|
const posts = (await getCollection("blog", ({ data }) => {
|
||||||
@@ -21,8 +21,9 @@ const posts = (await getCollection("blog", ({ data }) => {
|
|||||||
}));
|
}));
|
||||||
---
|
---
|
||||||
<ContentLayout
|
<ContentLayout
|
||||||
title="Blog | Timothy Pidashev"
|
title="Browse Tags | Blog | Timothy Pidashev"
|
||||||
description="My experiences and technical insights into software development and the ever-evolving world of programming."
|
description="Browse blog posts by tag."
|
||||||
>
|
>
|
||||||
<TagList posts={posts} />
|
<BlogHeader client:load />
|
||||||
|
<TagList posts={posts} client:load />
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user