mirror of
https://github.com/BradNut/personal-website-sveltekit
synced 2025-09-08 23:20:18 +00:00
commit
f38a53e657
28 changed files with 971 additions and 628 deletions
144
biome.json
144
biome.json
|
|
@ -1,65 +1,83 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": { "ignoreUnknown": false, "ignore": [] },
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 150,
|
||||
"attributePosition": "auto",
|
||||
"ignore": [
|
||||
"**/.DS_Store",
|
||||
"**/node_modules",
|
||||
"./build",
|
||||
"./.svelte-kit",
|
||||
"./package",
|
||||
"**/.env",
|
||||
"**/.env.*",
|
||||
"**/pnpm-lock.yaml",
|
||||
"**/package-lock.json",
|
||||
"**/yarn.lock",
|
||||
"**/paraglide/**"
|
||||
]
|
||||
},
|
||||
"organizeImports": { "enabled": true },
|
||||
"linter": { "enabled": true, "rules": { "recommended": true } },
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"jsxQuoteStyle": "single",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingCommas": "all",
|
||||
"indentStyle": "space",
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 150,
|
||||
"semicolons": "always",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"quoteStyle": "single",
|
||||
"attributePosition": "auto"
|
||||
},
|
||||
"parser": {
|
||||
"unsafeParameterDecoratorsEnabled": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": ["*.svelte"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"useConst": "off",
|
||||
"useImportType": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": { "ignoreUnknown": false, "includes": ["**"] },
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 150,
|
||||
"attributePosition": "auto",
|
||||
"includes": [
|
||||
"**",
|
||||
"!**/.DS_Store",
|
||||
"!**/node_modules",
|
||||
"!build",
|
||||
"!.svelte-kit",
|
||||
"!package",
|
||||
"!**/.env",
|
||||
"!**/.env.*",
|
||||
"!**/pnpm-lock.yaml",
|
||||
"!**/package-lock.json",
|
||||
"!**/yarn.lock",
|
||||
"!**/paraglide/**"
|
||||
]
|
||||
},
|
||||
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"style": {
|
||||
"noParameterAssign": "error",
|
||||
"useAsConstAssertion": "error",
|
||||
"useDefaultParameterLast": "error",
|
||||
"useEnumInitializers": "error",
|
||||
"useSelfClosingElements": "error",
|
||||
"useSingleVarDeclarator": "error",
|
||||
"noUnusedTemplateLiteral": "error",
|
||||
"useNumberNamespace": "error",
|
||||
"noInferrableTypes": "error",
|
||||
"noUselessElse": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"jsxQuoteStyle": "single",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingCommas": "all",
|
||||
"indentStyle": "space",
|
||||
"lineEnding": "lf",
|
||||
"lineWidth": 150,
|
||||
"semicolons": "always",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"quoteStyle": "single",
|
||||
"attributePosition": "auto"
|
||||
},
|
||||
"parser": {
|
||||
"unsafeParameterDecoratorsEnabled": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"includes": ["**/*.svelte"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"style": {
|
||||
"useConst": "off",
|
||||
"useImportType": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
version: "3.8"
|
||||
services:
|
||||
redis:
|
||||
image: redis:latest
|
||||
|
|
@ -7,5 +6,6 @@ services:
|
|||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
network_mode: host
|
||||
volumes:
|
||||
redis_data:
|
||||
|
|
|
|||
23
package.json
23
package.json
|
|
@ -17,10 +17,11 @@
|
|||
"test:unit": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@biomejs/biome": "^2.1.4",
|
||||
"@internationalized/date": "^3.8.2",
|
||||
"@playwright/test": "^1.54.2",
|
||||
"@sveltejs/enhanced-img": "^0.5.1",
|
||||
"@sveltejs/kit": "^2.26.1",
|
||||
"@sveltejs/kit": "^2.29.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.1.1",
|
||||
"@unpic/svelte": "^1.0.0",
|
||||
"@zerodevx/svelte-img": "^2.1.2",
|
||||
|
|
@ -33,26 +34,26 @@
|
|||
"postcss-preset-env": "^10.2.4",
|
||||
"satori": "^0.12.2",
|
||||
"satori-html": "^0.3.2",
|
||||
"svelte": "^5.37.0",
|
||||
"svelte-check": "^4.3.0",
|
||||
"svelte": "^5.38.1",
|
||||
"svelte-check": "^4.3.1",
|
||||
"svelte-meta-tags": "^4.4.0",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"svelte-sequential-preprocessor": "^2.0.2",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript": "^5.9.2",
|
||||
"vanilla-lazyload": "^19.1.3",
|
||||
"vite": "^6.3.5",
|
||||
"vite-imagetools": "^7.1.0",
|
||||
"vite-imagetools": "^7.1.1",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@sveltejs/adapter-node": "^5.2.13",
|
||||
"@sveltejs/adapter-node": "^5.2.14",
|
||||
"@vercel/og": "^0.6.8",
|
||||
"bits-ui": "1.4.7",
|
||||
"bits-ui": "2.9.2",
|
||||
"flexsearch": "^0.8.205",
|
||||
"ioredis": "^5.6.1",
|
||||
"lucide-svelte": "^0.509.0",
|
||||
"ioredis": "^5.7.0",
|
||||
"lucide-svelte": "^0.539.0",
|
||||
"scrape-it": "^6.1.11",
|
||||
"sharp": "^0.34.3",
|
||||
"svelte-local-storage-store": "^0.6.4"
|
||||
|
|
|
|||
399
pnpm-lock.yaml
399
pnpm-lock.yaml
|
|
@ -12,23 +12,23 @@ importers:
|
|||
specifier: ^2.6.2
|
||||
version: 2.6.2
|
||||
'@sveltejs/adapter-node':
|
||||
specifier: ^5.2.13
|
||||
version: 5.2.13(@sveltejs/kit@2.26.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))
|
||||
specifier: ^5.2.14
|
||||
version: 5.2.14(@sveltejs/kit@2.29.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))
|
||||
'@vercel/og':
|
||||
specifier: ^0.6.8
|
||||
version: 0.6.8
|
||||
bits-ui:
|
||||
specifier: 1.4.7
|
||||
version: 1.4.7(svelte@5.37.0)
|
||||
specifier: 2.9.2
|
||||
version: 2.9.2(@internationalized/date@3.8.2)(svelte@5.38.1)
|
||||
flexsearch:
|
||||
specifier: ^0.8.205
|
||||
version: 0.8.205
|
||||
ioredis:
|
||||
specifier: ^5.6.1
|
||||
version: 5.6.1
|
||||
specifier: ^5.7.0
|
||||
version: 5.7.0
|
||||
lucide-svelte:
|
||||
specifier: ^0.509.0
|
||||
version: 0.509.0(svelte@5.37.0)
|
||||
specifier: ^0.539.0
|
||||
version: 0.539.0(svelte@5.38.1)
|
||||
scrape-it:
|
||||
specifier: ^6.1.11
|
||||
version: 6.1.11
|
||||
|
|
@ -37,29 +37,32 @@ importers:
|
|||
version: 0.34.3
|
||||
svelte-local-storage-store:
|
||||
specifier: ^0.6.4
|
||||
version: 0.6.4(svelte@5.37.0)
|
||||
version: 0.6.4(svelte@5.38.1)
|
||||
devDependencies:
|
||||
'@biomejs/biome':
|
||||
specifier: ^1.9.4
|
||||
version: 1.9.4
|
||||
specifier: ^2.1.4
|
||||
version: 2.1.4
|
||||
'@internationalized/date':
|
||||
specifier: ^3.8.2
|
||||
version: 3.8.2
|
||||
'@playwright/test':
|
||||
specifier: ^1.54.1
|
||||
version: 1.54.1
|
||||
specifier: ^1.54.2
|
||||
version: 1.54.2
|
||||
'@sveltejs/enhanced-img':
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))(rollup@4.34.8)(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))
|
||||
version: 0.5.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))(rollup@4.34.8)(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))
|
||||
'@sveltejs/kit':
|
||||
specifier: ^2.26.1
|
||||
version: 2.26.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))
|
||||
specifier: ^2.29.0
|
||||
version: 2.29.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))
|
||||
version: 5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))
|
||||
'@unpic/svelte':
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0(svelte@5.37.0)
|
||||
version: 1.0.0(svelte@5.38.1)
|
||||
'@zerodevx/svelte-img':
|
||||
specifier: ^2.1.2
|
||||
version: 2.1.2(rollup@4.34.8)(svelte@5.37.0)
|
||||
version: 2.1.2(rollup@4.34.8)(svelte@5.38.1)
|
||||
autoprefixer:
|
||||
specifier: ^10.4.21
|
||||
version: 10.4.21(postcss@8.5.6)
|
||||
|
|
@ -88,17 +91,17 @@ importers:
|
|||
specifier: ^0.3.2
|
||||
version: 0.3.2
|
||||
svelte:
|
||||
specifier: ^5.37.0
|
||||
version: 5.37.0
|
||||
specifier: ^5.38.1
|
||||
version: 5.38.1
|
||||
svelte-check:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(picomatch@4.0.2)(svelte@5.37.0)(typescript@5.8.3)
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1(picomatch@4.0.2)(svelte@5.38.1)(typescript@5.9.2)
|
||||
svelte-meta-tags:
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0(svelte@5.37.0)
|
||||
version: 4.4.0(svelte@5.38.1)
|
||||
svelte-preprocess:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3(postcss-load-config@6.0.1(postcss@8.5.6)(yaml@2.7.0))(postcss@8.5.6)(svelte@5.37.0)(typescript@5.8.3)
|
||||
version: 6.0.3(postcss-load-config@6.0.1(postcss@8.5.6)(yaml@2.7.0))(postcss@8.5.6)(svelte@5.38.1)(typescript@5.9.2)
|
||||
svelte-sequential-preprocessor:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
|
|
@ -106,8 +109,8 @@ importers:
|
|||
specifier: ^2.8.1
|
||||
version: 2.8.1
|
||||
typescript:
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
specifier: ^5.9.2
|
||||
version: 5.9.2
|
||||
vanilla-lazyload:
|
||||
specifier: ^19.1.3
|
||||
version: 19.1.3
|
||||
|
|
@ -115,8 +118,8 @@ importers:
|
|||
specifier: ^6.3.5
|
||||
version: 6.3.5(yaml@2.7.0)
|
||||
vite-imagetools:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0(rollup@4.34.8)
|
||||
specifier: ^7.1.1
|
||||
version: 7.1.1(rollup@4.34.8)
|
||||
vitest:
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(yaml@2.7.0)
|
||||
|
|
@ -127,55 +130,55 @@ packages:
|
|||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@biomejs/biome@1.9.4':
|
||||
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
|
||||
'@biomejs/biome@2.1.4':
|
||||
resolution: {integrity: sha512-QWlrqyxsU0FCebuMnkvBIkxvPqH89afiJzjMl+z67ybutse590jgeaFdDurE9XYtzpjRGTI1tlUZPGWmbKsElA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
hasBin: true
|
||||
|
||||
'@biomejs/cli-darwin-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==}
|
||||
'@biomejs/cli-darwin-arm64@2.1.4':
|
||||
resolution: {integrity: sha512-sCrNENE74I9MV090Wq/9Dg7EhPudx3+5OiSoQOkIe3DLPzFARuL1dOwCWhKCpA3I5RHmbrsbNSRfZwCabwd8Qg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-darwin-x64@1.9.4':
|
||||
resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==}
|
||||
'@biomejs/cli-darwin-x64@2.1.4':
|
||||
resolution: {integrity: sha512-gOEICJbTCy6iruBywBDcG4X5rHMbqCPs3clh3UQ+hRKlgvJTk4NHWQAyHOXvaLe+AxD1/TNX1jbZeffBJzcrOw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@1.9.4':
|
||||
resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==}
|
||||
'@biomejs/cli-linux-arm64-musl@2.1.4':
|
||||
resolution: {integrity: sha512-nYr7H0CyAJPaLupFE2cH16KZmRC5Z9PEftiA2vWxk+CsFkPZQ6dBRdcC6RuS+zJlPc/JOd8xw3uCCt9Pv41WvQ==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==}
|
||||
'@biomejs/cli-linux-arm64@2.1.4':
|
||||
resolution: {integrity: sha512-juhEkdkKR4nbUi5k/KRp1ocGPNWLgFRD4NrHZSveYrD6i98pyvuzmS9yFYgOZa5JhaVqo0HPnci0+YuzSwT2fw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
||||
resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==}
|
||||
'@biomejs/cli-linux-x64-musl@2.1.4':
|
||||
resolution: {integrity: sha512-lvwvb2SQQHctHUKvBKptR6PLFCM7JfRjpCCrDaTmvB7EeZ5/dQJPhTYBf36BE/B4CRWR2ZiBLRYhK7hhXBCZAg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64@1.9.4':
|
||||
resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==}
|
||||
'@biomejs/cli-linux-x64@2.1.4':
|
||||
resolution: {integrity: sha512-Eoy9ycbhpJVYuR+LskV9s3uyaIkp89+qqgqhGQsWnp/I02Uqg2fXFblHJOpGZR8AxdB9ADy87oFVxn9MpFKUrw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-win32-arm64@1.9.4':
|
||||
resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==}
|
||||
'@biomejs/cli-win32-arm64@2.1.4':
|
||||
resolution: {integrity: sha512-3WRYte7orvyi6TRfIZkDN9Jzoogbv+gSvR+b9VOXUg1We1XrjBg6WljADeVEaKTvOcpVdH0a90TwyOQ6ue4fGw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@biomejs/cli-win32-x64@1.9.4':
|
||||
resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==}
|
||||
'@biomejs/cli-win32-x64@2.1.4':
|
||||
resolution: {integrity: sha512-tBc+W7anBPSFXGAoQW+f/+svkpt8/uXfRwDzN1DvnatkRMt16KIYpEi/iw8u9GahJlFv98kgHcIrSsZHZTR0sw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
|
@ -594,14 +597,14 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@floating-ui/core@1.7.0':
|
||||
resolution: {integrity: sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==}
|
||||
'@floating-ui/core@1.7.3':
|
||||
resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
|
||||
|
||||
'@floating-ui/dom@1.7.0':
|
||||
resolution: {integrity: sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==}
|
||||
'@floating-ui/dom@1.7.3':
|
||||
resolution: {integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==}
|
||||
|
||||
'@floating-ui/utils@0.2.9':
|
||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
|
||||
|
|
@ -830,16 +833,22 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@internationalized/date@3.8.0':
|
||||
resolution: {integrity: sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw==}
|
||||
'@internationalized/date@3.8.2':
|
||||
resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==}
|
||||
|
||||
'@ioredis/commands@1.2.0':
|
||||
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
|
||||
'@ioredis/commands@1.3.0':
|
||||
resolution: {integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
|
@ -854,8 +863,11 @@ packages:
|
|||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@playwright/test@1.54.1':
|
||||
resolution: {integrity: sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==}
|
||||
'@jridgewell/trace-mapping@0.3.30':
|
||||
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
|
||||
|
||||
'@playwright/test@1.54.2':
|
||||
resolution: {integrity: sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -1178,13 +1190,16 @@ packages:
|
|||
engines: {node: '>= 8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@standard-schema/spec@1.0.0':
|
||||
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
|
||||
|
||||
'@sveltejs/acorn-typescript@1.0.5':
|
||||
resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==}
|
||||
peerDependencies:
|
||||
acorn: ^8.9.0
|
||||
|
||||
'@sveltejs/adapter-node@5.2.13':
|
||||
resolution: {integrity: sha512-yS2TVFmIrxjGhYaV5/iIUrJ3mJl6zjaYn0lBD70vTLnYvJeqf3cjvLXeXCUCuYinhSBoyF4DpfGla49BnIy7sQ==}
|
||||
'@sveltejs/adapter-node@5.2.14':
|
||||
resolution: {integrity: sha512-TjJvfw0HZlbBGGAW2vFtdGjdKhqpGW3ZDIz0nzy8Zx6Ki6oFmYTjV5Kwn3LWTsyjbsUSXhfFPCuYop3z1iS9qQ==}
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^2.4.0
|
||||
|
||||
|
|
@ -1195,8 +1210,8 @@ packages:
|
|||
svelte: ^5.0.0
|
||||
vite: '>= 5.0.0'
|
||||
|
||||
'@sveltejs/kit@2.26.1':
|
||||
resolution: {integrity: sha512-FwDhHAoXYUGnYndrrEzEYcKdYWpSoRKq4kli29oMe83hLri4/DOGQk3xUgwjDo0LKpSmj5M/Sj29/Ug3wO0Cbg==}
|
||||
'@sveltejs/kit@2.29.0':
|
||||
resolution: {integrity: sha512-gOynQRBThrtF/RjljB8Oybs9VHVmLbk9q7E7ALJT6ImppJtc/yx3sTGiBV64y+lwmagnBCmEMmJ40CVChGy8lA==}
|
||||
engines: {node: '>=18.13'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
|
@ -1331,11 +1346,12 @@ packages:
|
|||
resolution: {integrity: sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
bits-ui@1.4.7:
|
||||
resolution: {integrity: sha512-oqfSbgB/2Nc3qwOvohkRzw0nQcUKsNPwthD4uzy9E21wSbhc00RDcZqCJmFrrcW336J+aStM1sITsVGQFjT+iw==}
|
||||
engines: {node: '>=18', pnpm: '>=8.7.0'}
|
||||
bits-ui@2.9.2:
|
||||
resolution: {integrity: sha512-GGbyr4oVKtHin//Q0AhlygkasmfWt328VjsnmB3sP+h8Sh+Eyghm+1AQ8o+xQMDCYbdL35JZ9UZGTZYTMar4Uw==}
|
||||
engines: {node: '>=20'}
|
||||
peerDependencies:
|
||||
svelte: ^5.11.0
|
||||
'@internationalized/date': ^3.8.1
|
||||
svelte: ^5.33.0
|
||||
|
||||
boolbase@1.0.0:
|
||||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
|
|
@ -1361,11 +1377,8 @@ packages:
|
|||
camelize@1.0.1:
|
||||
resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==}
|
||||
|
||||
caniuse-lite@1.0.30001706:
|
||||
resolution: {integrity: sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==}
|
||||
|
||||
caniuse-lite@1.0.30001727:
|
||||
resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==}
|
||||
caniuse-lite@1.0.30001731:
|
||||
resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==}
|
||||
|
||||
chai@5.2.0:
|
||||
resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
|
||||
|
|
@ -1722,8 +1735,8 @@ packages:
|
|||
inline-style-parser@0.2.4:
|
||||
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
|
||||
|
||||
ioredis@5.6.1:
|
||||
resolution: {integrity: sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==}
|
||||
ioredis@5.7.0:
|
||||
resolution: {integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
|
||||
is-arrayish@0.3.2:
|
||||
|
|
@ -1780,8 +1793,8 @@ packages:
|
|||
loupe@3.2.0:
|
||||
resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==}
|
||||
|
||||
lucide-svelte@0.509.0:
|
||||
resolution: {integrity: sha512-6U83jZ0RKvLYLGdx/hTqZyWcquwApQ2Q1E5bKFELXtOw7g8dk1P0qwbAQqs1fqWAtpNevtXTpgShHv/yWAcbjQ==}
|
||||
lucide-svelte@0.539.0:
|
||||
resolution: {integrity: sha512-p4k3GOje/9Si1eIkg1W1OQUhozeja5Ka5shjVpfyP5X2ye+B7sfyMnX3d5D2et+MYJwUFGrMna5MIYgq6bLfqw==}
|
||||
peerDependencies:
|
||||
svelte: ^3 || ^4 || ^5.0.0-next.42
|
||||
|
||||
|
|
@ -1878,13 +1891,13 @@ packages:
|
|||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
playwright-core@1.54.1:
|
||||
resolution: {integrity: sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==}
|
||||
playwright-core@1.54.2:
|
||||
resolution: {integrity: sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.54.1:
|
||||
resolution: {integrity: sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==}
|
||||
playwright@1.54.2:
|
||||
resolution: {integrity: sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -2112,8 +2125,8 @@ packages:
|
|||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
runed@0.23.4:
|
||||
resolution: {integrity: sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==}
|
||||
runed@0.29.2:
|
||||
resolution: {integrity: sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA==}
|
||||
peerDependencies:
|
||||
svelte: ^5.7.0
|
||||
|
||||
|
|
@ -2191,15 +2204,15 @@ packages:
|
|||
style-object-to-css-string@1.1.3:
|
||||
resolution: {integrity: sha512-bISQoUsir/qGfo7vY8rw00ia9nnyE1jvYt3zZ2jhdkcXZ6dAEi74inMzQ6On57vFI+I4Fck6wOv5UI9BEwJDgw==}
|
||||
|
||||
style-to-object@1.0.8:
|
||||
resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
|
||||
style-to-object@1.0.9:
|
||||
resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==}
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
svelte-check@4.3.0:
|
||||
resolution: {integrity: sha512-Iz8dFXzBNAM7XlEIsUjUGQhbEE+Pvv9odb9+0+ITTgFWZBGeJRRYqHUUglwe2EkLD5LIsQaAc4IUJyvtKuOO5w==}
|
||||
svelte-check@4.3.1:
|
||||
resolution: {integrity: sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg==}
|
||||
engines: {node: '>= 18.0.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
|
|
@ -2263,18 +2276,18 @@ packages:
|
|||
resolution: {integrity: sha512-DIFm0kSNscVxtBmKkBiygAHB5otoqN1aVmJ3t57jZhJfCB7Np/lUSoTtSrvPFjmlBbMeOsb1VQ06cut1+rBYOg==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
svelte-toolbelt@0.7.1:
|
||||
resolution: {integrity: sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==}
|
||||
svelte-toolbelt@0.9.3:
|
||||
resolution: {integrity: sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw==}
|
||||
engines: {node: '>=18', pnpm: '>=8.7.0'}
|
||||
peerDependencies:
|
||||
svelte: ^5.0.0
|
||||
svelte: ^5.30.2
|
||||
|
||||
svelte@4.2.20:
|
||||
resolution: {integrity: sha512-eeEgGc2DtiUil5ANdtd8vPwt9AgaMdnuUFnPft9F5oMvU/FHu5IHFic+p1dR/UOB7XU2mX2yHW+NcTch4DCh5Q==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
svelte@5.37.0:
|
||||
resolution: {integrity: sha512-BAHgWdKncZ4F1DVBrkKAvelx2Nv3mR032ca8/yj9Gxf5s9zzK1uGXiZTjCFDvmO2e9KQfcR2lEkVjw+ZxExJow==}
|
||||
svelte@5.38.1:
|
||||
resolution: {integrity: sha512-fO6CLDfJYWHgfo6lQwkQU2vhCiHc2MBl6s3vEhK+sSZru17YL4R5s1v14ndRpqKAIkq8nCz6MTk1yZbESZWeyQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tabbable@6.2.0:
|
||||
|
|
@ -2319,8 +2332,8 @@ packages:
|
|||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
typescript@5.8.3:
|
||||
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
||||
typescript@5.9.2:
|
||||
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -2356,8 +2369,8 @@ packages:
|
|||
resolution: {integrity: sha512-C4ZYhgj2vAj43/TpZ06XlDNP0p/7LIeYbgUYr+xG44nM++4HGX6YZBKAYpiBNgiCFUTJ6eXkRppWBrfPMevgmg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
vite-imagetools@7.1.0:
|
||||
resolution: {integrity: sha512-Mqh1uUY2DEMuBOogFz5Rd7cAs70VP6wsdQh2IShrJ+qGk5f7yQa4pN8w0YMLlGIKYW1JfM8oXrznUwVkhG+qxg==}
|
||||
vite-imagetools@7.1.1:
|
||||
resolution: {integrity: sha512-6Dz0ZD2u1u59dw0ryid4beaDcCNNiTfLXUg6XLxx9n7Qm4BgAAm8TozYUnIH0aT4FDAADZqHsh9ZtISzAs4eKA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
vite-node@3.2.4:
|
||||
|
|
@ -2472,39 +2485,39 @@ snapshots:
|
|||
'@jridgewell/gen-mapping': 0.3.8
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@biomejs/biome@1.9.4':
|
||||
'@biomejs/biome@2.1.4':
|
||||
optionalDependencies:
|
||||
'@biomejs/cli-darwin-arm64': 1.9.4
|
||||
'@biomejs/cli-darwin-x64': 1.9.4
|
||||
'@biomejs/cli-linux-arm64': 1.9.4
|
||||
'@biomejs/cli-linux-arm64-musl': 1.9.4
|
||||
'@biomejs/cli-linux-x64': 1.9.4
|
||||
'@biomejs/cli-linux-x64-musl': 1.9.4
|
||||
'@biomejs/cli-win32-arm64': 1.9.4
|
||||
'@biomejs/cli-win32-x64': 1.9.4
|
||||
'@biomejs/cli-darwin-arm64': 2.1.4
|
||||
'@biomejs/cli-darwin-x64': 2.1.4
|
||||
'@biomejs/cli-linux-arm64': 2.1.4
|
||||
'@biomejs/cli-linux-arm64-musl': 2.1.4
|
||||
'@biomejs/cli-linux-x64': 2.1.4
|
||||
'@biomejs/cli-linux-x64-musl': 2.1.4
|
||||
'@biomejs/cli-win32-arm64': 2.1.4
|
||||
'@biomejs/cli-win32-x64': 2.1.4
|
||||
|
||||
'@biomejs/cli-darwin-arm64@1.9.4':
|
||||
'@biomejs/cli-darwin-arm64@2.1.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-darwin-x64@1.9.4':
|
||||
'@biomejs/cli-darwin-x64@2.1.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@1.9.4':
|
||||
'@biomejs/cli-linux-arm64-musl@2.1.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64@1.9.4':
|
||||
'@biomejs/cli-linux-arm64@2.1.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@1.9.4':
|
||||
'@biomejs/cli-linux-x64-musl@2.1.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64@1.9.4':
|
||||
'@biomejs/cli-linux-x64@2.1.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-arm64@1.9.4':
|
||||
'@biomejs/cli-win32-arm64@2.1.4':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-x64@1.9.4':
|
||||
'@biomejs/cli-win32-x64@2.1.4':
|
||||
optional: true
|
||||
|
||||
'@csstools/cascade-layer-name-parser@2.0.5(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||
|
|
@ -2853,16 +2866,16 @@ snapshots:
|
|||
'@esbuild/win32-x64@0.25.4':
|
||||
optional: true
|
||||
|
||||
'@floating-ui/core@1.7.0':
|
||||
'@floating-ui/core@1.7.3':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.9
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/dom@1.7.0':
|
||||
'@floating-ui/dom@1.7.3':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.0
|
||||
'@floating-ui/utils': 0.2.9
|
||||
'@floating-ui/core': 1.7.3
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/utils@0.2.9': {}
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.33.5':
|
||||
optionalDependencies:
|
||||
|
|
@ -3025,11 +3038,16 @@ snapshots:
|
|||
'@img/sharp-win32-x64@0.34.3':
|
||||
optional: true
|
||||
|
||||
'@internationalized/date@3.8.0':
|
||||
'@internationalized/date@3.8.2':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.17
|
||||
|
||||
'@ioredis/commands@1.2.0': {}
|
||||
'@ioredis/commands@1.3.0': {}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@jridgewell/trace-mapping': 0.3.30
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
dependencies:
|
||||
|
|
@ -3037,6 +3055,11 @@ snapshots:
|
|||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.30
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/set-array@1.2.1': {}
|
||||
|
|
@ -3048,9 +3071,14 @@ snapshots:
|
|||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@playwright/test@1.54.1':
|
||||
'@jridgewell/trace-mapping@0.3.30':
|
||||
dependencies:
|
||||
playwright: 1.54.1
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@playwright/test@1.54.2':
|
||||
dependencies:
|
||||
playwright: 1.54.2
|
||||
|
||||
'@polka/url@1.0.0-next.29': {}
|
||||
|
||||
|
|
@ -3265,6 +3293,8 @@ snapshots:
|
|||
fflate: 0.7.4
|
||||
string.prototype.codepointat: 0.2.1
|
||||
|
||||
'@standard-schema/spec@1.0.0': {}
|
||||
|
||||
'@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1)':
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
|
|
@ -3273,31 +3303,32 @@ snapshots:
|
|||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
'@sveltejs/adapter-node@5.2.13(@sveltejs/kit@2.26.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))':
|
||||
'@sveltejs/adapter-node@5.2.14(@sveltejs/kit@2.29.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))':
|
||||
dependencies:
|
||||
'@rollup/plugin-commonjs': 28.0.2(rollup@4.34.8)
|
||||
'@rollup/plugin-json': 6.1.0(rollup@4.34.8)
|
||||
'@rollup/plugin-node-resolve': 16.0.0(rollup@4.34.8)
|
||||
'@sveltejs/kit': 2.26.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))
|
||||
'@sveltejs/kit': 2.29.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))
|
||||
rollup: 4.34.8
|
||||
|
||||
'@sveltejs/enhanced-img@0.5.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))(rollup@4.34.8)(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))':
|
||||
'@sveltejs/enhanced-img@0.5.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))(rollup@4.34.8)(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))
|
||||
magic-string: 0.30.17
|
||||
sharp: 0.34.3
|
||||
svelte: 5.37.0
|
||||
svelte-parse-markup: 0.1.5(svelte@5.37.0)
|
||||
svelte: 5.38.1
|
||||
svelte-parse-markup: 0.1.5(svelte@5.38.1)
|
||||
vite: 6.3.5(yaml@2.7.0)
|
||||
vite-imagetools: 7.1.0(rollup@4.34.8)
|
||||
vite-imagetools: 7.1.1(rollup@4.34.8)
|
||||
zimmerframe: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
|
||||
'@sveltejs/kit@2.26.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))':
|
||||
'@sveltejs/kit@2.29.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.0.0
|
||||
'@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0)
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))
|
||||
'@types/cookie': 0.6.0
|
||||
acorn: 8.15.0
|
||||
cookie: 0.6.0
|
||||
|
|
@ -3309,26 +3340,26 @@ snapshots:
|
|||
sade: 1.8.1
|
||||
set-cookie-parser: 2.7.1
|
||||
sirv: 3.0.1
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
vite: 6.3.5(yaml@2.7.0)
|
||||
|
||||
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))':
|
||||
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))
|
||||
'@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))
|
||||
debug: 4.4.1
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
vite: 6.3.5(yaml@2.7.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))':
|
||||
'@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0)))(svelte@5.37.0)(vite@6.3.5(yaml@2.7.0))
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0)))(svelte@5.38.1)(vite@6.3.5(yaml@2.7.0))
|
||||
debug: 4.4.1
|
||||
deepmerge: 4.3.1
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.17
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
vite: 6.3.5(yaml@2.7.0)
|
||||
vitefu: 1.1.1(vite@6.3.5(yaml@2.7.0))
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -3356,11 +3387,11 @@ snapshots:
|
|||
dependencies:
|
||||
unpic: 4.1.2
|
||||
|
||||
'@unpic/svelte@1.0.0(svelte@5.37.0)':
|
||||
'@unpic/svelte@1.0.0(svelte@5.38.1)':
|
||||
dependencies:
|
||||
'@unpic/core': 1.0.1
|
||||
style-object-to-css-string: 1.1.3
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
|
||||
'@vercel/og@0.6.8':
|
||||
dependencies:
|
||||
|
|
@ -3410,9 +3441,9 @@ snapshots:
|
|||
loupe: 3.2.0
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@zerodevx/svelte-img@2.1.2(rollup@4.34.8)(svelte@5.37.0)':
|
||||
'@zerodevx/svelte-img@2.1.2(rollup@4.34.8)(svelte@5.38.1)':
|
||||
dependencies:
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
vite-imagetools: 6.2.9(rollup@4.34.8)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
|
|
@ -3435,7 +3466,7 @@ snapshots:
|
|||
autoprefixer@10.4.21(postcss@8.5.6):
|
||||
dependencies:
|
||||
browserslist: 4.24.4
|
||||
caniuse-lite: 1.0.30001706
|
||||
caniuse-lite: 1.0.30001731
|
||||
fraction.js: 4.3.7
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.1.1
|
||||
|
|
@ -3460,29 +3491,29 @@ snapshots:
|
|||
|
||||
base64-js@0.0.8: {}
|
||||
|
||||
bits-ui@1.4.7(svelte@5.37.0):
|
||||
bits-ui@2.9.2(@internationalized/date@3.8.2)(svelte@5.38.1):
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.0
|
||||
'@floating-ui/dom': 1.7.0
|
||||
'@internationalized/date': 3.8.0
|
||||
'@floating-ui/core': 1.7.3
|
||||
'@floating-ui/dom': 1.7.3
|
||||
'@internationalized/date': 3.8.2
|
||||
esm-env: 1.2.2
|
||||
runed: 0.23.4(svelte@5.37.0)
|
||||
svelte: 5.37.0
|
||||
svelte-toolbelt: 0.7.1(svelte@5.37.0)
|
||||
runed: 0.29.2(svelte@5.38.1)
|
||||
svelte: 5.38.1
|
||||
svelte-toolbelt: 0.9.3(svelte@5.38.1)
|
||||
tabbable: 6.2.0
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
|
||||
browserslist@4.24.4:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001706
|
||||
caniuse-lite: 1.0.30001731
|
||||
electron-to-chromium: 1.5.120
|
||||
node-releases: 2.0.19
|
||||
update-browserslist-db: 1.1.3(browserslist@4.24.4)
|
||||
|
||||
browserslist@4.25.1:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001727
|
||||
caniuse-lite: 1.0.30001731
|
||||
electron-to-chromium: 1.5.191
|
||||
node-releases: 2.0.19
|
||||
update-browserslist-db: 1.1.3(browserslist@4.25.1)
|
||||
|
|
@ -3496,9 +3527,7 @@ snapshots:
|
|||
|
||||
camelize@1.0.1: {}
|
||||
|
||||
caniuse-lite@1.0.30001706: {}
|
||||
|
||||
caniuse-lite@1.0.30001727: {}
|
||||
caniuse-lite@1.0.30001731: {}
|
||||
|
||||
chai@5.2.0:
|
||||
dependencies:
|
||||
|
|
@ -3853,9 +3882,9 @@ snapshots:
|
|||
|
||||
inline-style-parser@0.2.4: {}
|
||||
|
||||
ioredis@5.6.1:
|
||||
ioredis@5.7.0:
|
||||
dependencies:
|
||||
'@ioredis/commands': 1.2.0
|
||||
'@ioredis/commands': 1.3.0
|
||||
cluster-key-slot: 1.1.2
|
||||
debug: 4.4.0
|
||||
denque: 2.1.0
|
||||
|
|
@ -3910,9 +3939,9 @@ snapshots:
|
|||
|
||||
loupe@3.2.0: {}
|
||||
|
||||
lucide-svelte@0.509.0(svelte@5.37.0):
|
||||
lucide-svelte@0.539.0(svelte@5.38.1):
|
||||
dependencies:
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
|
||||
magic-string@0.30.17:
|
||||
dependencies:
|
||||
|
|
@ -3990,11 +4019,11 @@ snapshots:
|
|||
|
||||
pify@2.3.0: {}
|
||||
|
||||
playwright-core@1.54.1: {}
|
||||
playwright-core@1.54.2: {}
|
||||
|
||||
playwright@1.54.1:
|
||||
playwright@1.54.2:
|
||||
dependencies:
|
||||
playwright-core: 1.54.1
|
||||
playwright-core: 1.54.2
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
|
|
@ -4311,10 +4340,10 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc': 4.40.2
|
||||
fsevents: 2.3.3
|
||||
|
||||
runed@0.23.4(svelte@5.37.0):
|
||||
runed@0.29.2(svelte@5.38.1):
|
||||
dependencies:
|
||||
esm-env: 1.2.2
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
|
||||
sade@1.8.1:
|
||||
dependencies:
|
||||
|
|
@ -4450,56 +4479,56 @@ snapshots:
|
|||
|
||||
style-object-to-css-string@1.1.3: {}
|
||||
|
||||
style-to-object@1.0.8:
|
||||
style-to-object@1.0.9:
|
||||
dependencies:
|
||||
inline-style-parser: 0.2.4
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
svelte-check@4.3.0(picomatch@4.0.2)(svelte@5.37.0)(typescript@5.8.3):
|
||||
svelte-check@4.3.1(picomatch@4.0.2)(svelte@5.38.1)(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
chokidar: 4.0.3
|
||||
fdir: 6.4.4(picomatch@4.0.2)
|
||||
picocolors: 1.1.1
|
||||
sade: 1.8.1
|
||||
svelte: 5.37.0
|
||||
typescript: 5.8.3
|
||||
svelte: 5.38.1
|
||||
typescript: 5.9.2
|
||||
transitivePeerDependencies:
|
||||
- picomatch
|
||||
|
||||
svelte-local-storage-store@0.6.4(svelte@5.37.0):
|
||||
svelte-local-storage-store@0.6.4(svelte@5.38.1):
|
||||
dependencies:
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
|
||||
svelte-meta-tags@4.4.0(svelte@5.37.0):
|
||||
svelte-meta-tags@4.4.0(svelte@5.38.1):
|
||||
dependencies:
|
||||
schema-dts: 1.1.5
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
|
||||
svelte-parse-markup@0.1.5(svelte@5.37.0):
|
||||
svelte-parse-markup@0.1.5(svelte@5.38.1):
|
||||
dependencies:
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
|
||||
svelte-preprocess@6.0.3(postcss-load-config@6.0.1(postcss@8.5.6)(yaml@2.7.0))(postcss@8.5.6)(svelte@5.37.0)(typescript@5.8.3):
|
||||
svelte-preprocess@6.0.3(postcss-load-config@6.0.1(postcss@8.5.6)(yaml@2.7.0))(postcss@8.5.6)(svelte@5.38.1)(typescript@5.9.2):
|
||||
dependencies:
|
||||
svelte: 5.37.0
|
||||
svelte: 5.38.1
|
||||
optionalDependencies:
|
||||
postcss: 8.5.6
|
||||
postcss-load-config: 6.0.1(postcss@8.5.6)(yaml@2.7.0)
|
||||
typescript: 5.8.3
|
||||
typescript: 5.9.2
|
||||
|
||||
svelte-sequential-preprocessor@2.0.2:
|
||||
dependencies:
|
||||
svelte: 4.2.20
|
||||
tslib: 2.7.0
|
||||
|
||||
svelte-toolbelt@0.7.1(svelte@5.37.0):
|
||||
svelte-toolbelt@0.9.3(svelte@5.38.1):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
runed: 0.23.4(svelte@5.37.0)
|
||||
style-to-object: 1.0.8
|
||||
svelte: 5.37.0
|
||||
runed: 0.29.2(svelte@5.38.1)
|
||||
style-to-object: 1.0.9
|
||||
svelte: 5.38.1
|
||||
|
||||
svelte@4.2.20:
|
||||
dependencies:
|
||||
|
|
@ -4518,9 +4547,9 @@ snapshots:
|
|||
magic-string: 0.30.17
|
||||
periscopic: 3.1.0
|
||||
|
||||
svelte@5.37.0:
|
||||
svelte@5.38.1:
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1)
|
||||
'@types/estree': 1.0.7
|
||||
|
|
@ -4565,7 +4594,7 @@ snapshots:
|
|||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
typescript@5.8.3: {}
|
||||
typescript@5.9.2: {}
|
||||
|
||||
typpy@2.4.0:
|
||||
dependencies:
|
||||
|
|
@ -4605,7 +4634,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- rollup
|
||||
|
||||
vite-imagetools@7.1.0(rollup@4.34.8):
|
||||
vite-imagetools@7.1.1(rollup@4.34.8):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.34.8)
|
||||
imagetools-core: 7.1.0
|
||||
|
|
|
|||
302
src/lib/api.ts
302
src/lib/api.ts
|
|
@ -1,144 +1,222 @@
|
|||
import {
|
||||
WALLABAG_CLIENT_ID,
|
||||
WALLABAG_CLIENT_SECRET,
|
||||
WALLABAG_USERNAME,
|
||||
WALLABAG_PASSWORD,
|
||||
WALLABAG_URL,
|
||||
PAGE_SIZE,
|
||||
USE_REDIS_CACHE,
|
||||
WALLABAG_CLIENT_ID,
|
||||
WALLABAG_CLIENT_SECRET,
|
||||
WALLABAG_PASSWORD,
|
||||
WALLABAG_URL,
|
||||
WALLABAG_USERNAME,
|
||||
} from "$env/static/private";
|
||||
import intersect from "just-intersect";
|
||||
import { redis } from "$lib/server/redis";
|
||||
import type {
|
||||
Article,
|
||||
ArticlePageLoad,
|
||||
WallabagArticle,
|
||||
} from "$lib/types/article";
|
||||
import { ArticleTag } from "$lib/types/articleTag";
|
||||
import intersect from "just-intersect";
|
||||
import type { PageQuery } from "./types/pageQuery";
|
||||
import { URLSearchParams } from "node:url";
|
||||
import { redis } from "$lib/server/redis";
|
||||
|
||||
const base: string = WALLABAG_URL;
|
||||
|
||||
// Retry helper with exponential backoff
|
||||
async function retryWithBackoff<T>(
|
||||
fn: () => Promise<T>,
|
||||
maxRetries = 3,
|
||||
baseDelay = 500
|
||||
): Promise<T> {
|
||||
let lastError: Error | undefined;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
|
||||
if (attempt === maxRetries) {
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
// Exponential backoff: 500ms, 1s, 2s
|
||||
const delay = baseDelay * (2 ** attempt);
|
||||
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
export async function fetchArticlesApi(
|
||||
method: string,
|
||||
resource: string,
|
||||
queryParams: Record<string, string>
|
||||
) {
|
||||
let perPage = Number(queryParams?.limit);
|
||||
if (perPage > 30) {
|
||||
perPage = Number(PAGE_SIZE);
|
||||
} else {
|
||||
perPage = Number(queryParams?.limit);
|
||||
}
|
||||
|
||||
const pageQuery: PageQuery = {
|
||||
sort: "updated",
|
||||
perPage,
|
||||
since: 0,
|
||||
page: Number(queryParams?.page) || 1,
|
||||
tags: "programming",
|
||||
content: "metadata",
|
||||
};
|
||||
const entriesQueryParams = new URLSearchParams({
|
||||
...pageQuery,
|
||||
perPage: `${pageQuery.perPage}`,
|
||||
since: `${pageQuery.since}`,
|
||||
page: `${pageQuery.page}`,
|
||||
});
|
||||
|
||||
if (USE_REDIS_CACHE) {
|
||||
console.log('Using redis cache');
|
||||
const cached = await redis.get(entriesQueryParams.toString());
|
||||
|
||||
if (cached) {
|
||||
console.log("Cache hit!");
|
||||
const response = JSON.parse(cached);
|
||||
const ttl = await redis.ttl(entriesQueryParams.toString());
|
||||
|
||||
console.log(`Response ${JSON.stringify(response)}`);
|
||||
console.log(`Returning cached response with ttl of ${ttl} seconds`);
|
||||
return { ...response, cacheControl: `max-age=${ttl}` };
|
||||
try {
|
||||
let perPage = Number(queryParams?.limit);
|
||||
if (perPage === undefined || perPage > 30 || perPage < 1) {
|
||||
perPage = Number(PAGE_SIZE);
|
||||
} else {
|
||||
perPage = Number(queryParams?.limit);
|
||||
}
|
||||
}
|
||||
|
||||
const authBody = {
|
||||
grant_type: "password",
|
||||
client_id: WALLABAG_CLIENT_ID,
|
||||
client_secret: WALLABAG_CLIENT_SECRET,
|
||||
username: WALLABAG_USERNAME,
|
||||
password: WALLABAG_PASSWORD,
|
||||
};
|
||||
const pageQuery: PageQuery = {
|
||||
sort: "updated",
|
||||
perPage,
|
||||
since: 0,
|
||||
page: Number(queryParams?.page) || 1,
|
||||
tags: "programming",
|
||||
content: "metadata",
|
||||
};
|
||||
const entriesQueryParams = new URLSearchParams({
|
||||
sort: pageQuery.sort,
|
||||
perPage: `${pageQuery.perPage}`,
|
||||
since: `${pageQuery.since}`,
|
||||
page: `${pageQuery.page}`,
|
||||
tags: pageQuery.tags,
|
||||
content: pageQuery.content,
|
||||
});
|
||||
|
||||
const authResponse = await fetch(`${base}/oauth/v2/token`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams(authBody),
|
||||
});
|
||||
if (USE_REDIS_CACHE === 'true') {
|
||||
console.log('Using redis cache');
|
||||
const cacheKey = entriesQueryParams.toString();
|
||||
console.log(`Cache key: ${cacheKey}`);
|
||||
const cached = await redis.get(cacheKey);
|
||||
|
||||
const auth = await authResponse.json();
|
||||
if (cached) {
|
||||
console.log("Cache hit!");
|
||||
const response = JSON.parse(cached);
|
||||
const ttl = await redis.ttl(cacheKey);
|
||||
|
||||
const pageResponse = await fetch(
|
||||
`${WALLABAG_URL}/api/entries.json?${entriesQueryParams}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${auth.access_token}`,
|
||||
},
|
||||
console.log(`Returning cached response for page ${pageQuery.page} with ttl of ${ttl} seconds`);
|
||||
console.log(`Response: ${JSON.stringify(response)}`);
|
||||
return { ...response, cacheControl: `max-age=${ttl}` };
|
||||
}
|
||||
console.log(`Cache miss for page ${pageQuery.page}, fetching from API`);
|
||||
}
|
||||
);
|
||||
|
||||
if (!pageResponse.ok) {
|
||||
throw new Error(pageResponse.statusText);
|
||||
}
|
||||
const authBody = {
|
||||
grant_type: "password",
|
||||
client_id: WALLABAG_CLIENT_ID,
|
||||
client_secret: WALLABAG_CLIENT_SECRET,
|
||||
username: WALLABAG_USERNAME,
|
||||
password: WALLABAG_PASSWORD,
|
||||
};
|
||||
|
||||
const cacheControl = pageResponse.headers.get("cache-control") || "no-cache";
|
||||
console.log(`Auth body: ${JSON.stringify(authBody)}`);
|
||||
|
||||
const {
|
||||
_embedded: favoriteArticles,
|
||||
page,
|
||||
pages,
|
||||
total,
|
||||
limit,
|
||||
} = await pageResponse.json();
|
||||
const articles: Article[] = [];
|
||||
|
||||
for (const article of favoriteArticles.items as WallabagArticle[]) {
|
||||
const rawTags = article?.tags?.map((tag) => tag.slug);
|
||||
if (intersect(rawTags, Object.values(ArticleTag))?.length > 0) {
|
||||
const tags = rawTags.map((rawTag) => rawTag as unknown as ArticleTag);
|
||||
articles.push({
|
||||
tags,
|
||||
title: article.title,
|
||||
url: new URL(article.url),
|
||||
domain_name: article.domain_name?.replace("www.", "") ?? "",
|
||||
hashed_url: article.hashed_url,
|
||||
reading_time: article.reading_time,
|
||||
preview_picture: article.preview_picture,
|
||||
created_at: new Date(article.created_at),
|
||||
updated_at: new Date(article.updated_at),
|
||||
archived_at: article.archived_at ? new Date(article.archived_at) : null,
|
||||
const auth = await retryWithBackoff(async () => {
|
||||
const authResponse = await fetch(`${base}/oauth/v2/token`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams(authBody),
|
||||
signal: AbortSignal.timeout(10000), // 10 second timeout
|
||||
});
|
||||
|
||||
if (!authResponse.ok) {
|
||||
throw new Error(`Auth failed: ${authResponse.status} ${authResponse.statusText}`);
|
||||
}
|
||||
|
||||
return await authResponse.json();
|
||||
});
|
||||
|
||||
console.log(`Got auth response: ${JSON.stringify(auth)}`);
|
||||
|
||||
const { wallabagResponse, cacheControl } = await retryWithBackoff(async () => {
|
||||
const pageResponse = await fetch(
|
||||
`${WALLABAG_URL}/api/entries.json?${entriesQueryParams}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${auth.access_token}`,
|
||||
},
|
||||
signal: AbortSignal.timeout(15000), // 15 second timeout
|
||||
}
|
||||
);
|
||||
|
||||
console.log('pageResponse', pageResponse);
|
||||
|
||||
if (!pageResponse.ok) {
|
||||
console.log('pageResponse not ok', pageResponse);
|
||||
throw new Error(`API request failed: ${pageResponse.status} ${pageResponse.statusText}`);
|
||||
}
|
||||
|
||||
const cacheControl = pageResponse.headers.get("cache-control") || "no-cache";
|
||||
const wallabagResponse = await pageResponse.json();
|
||||
|
||||
return { wallabagResponse, cacheControl };
|
||||
});
|
||||
console.log('wallabagResponse', JSON.stringify(wallabagResponse));
|
||||
const {
|
||||
_embedded: favoriteArticles,
|
||||
page,
|
||||
pages,
|
||||
total,
|
||||
limit,
|
||||
} = wallabagResponse;
|
||||
const articles: Article[] = [];
|
||||
|
||||
console.log('favoriteArticles', JSON.stringify(favoriteArticles));
|
||||
console.log('pages', pages);
|
||||
console.log('page', page);
|
||||
console.log('total', total);
|
||||
console.log('limit', limit);
|
||||
|
||||
for (const article of favoriteArticles.items as WallabagArticle[]) {
|
||||
const rawTags = article?.tags?.map((tag) => tag.slug);
|
||||
if (intersect(rawTags, Object.values(ArticleTag))?.length > 0) {
|
||||
const tags = rawTags.map((rawTag) => rawTag as unknown as ArticleTag);
|
||||
articles.push({
|
||||
tags,
|
||||
title: article.title,
|
||||
url: new URL(article.url),
|
||||
domain_name: article.domain_name?.replace("www.", "") ?? "",
|
||||
hashed_url: article.hashed_url,
|
||||
reading_time: article.reading_time,
|
||||
preview_picture: article.preview_picture,
|
||||
created_at: new Date(article.created_at),
|
||||
updated_at: new Date(article.updated_at),
|
||||
archived_at: article.archived_at ? new Date(article.archived_at) : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const responseData: ArticlePageLoad = {
|
||||
articles,
|
||||
currentPage: page,
|
||||
totalPages: pages,
|
||||
limit,
|
||||
totalArticles: total,
|
||||
cacheControl,
|
||||
};
|
||||
|
||||
console.log('Response data from API: ', JSON.stringify(responseData))
|
||||
|
||||
if (USE_REDIS_CACHE === 'true' && responseData?.articles?.length > 0) {
|
||||
const cacheKey = entriesQueryParams.toString();
|
||||
console.log(`Storing in cache with key: ${cacheKey} for page ${page}`);
|
||||
redis.set(
|
||||
cacheKey,
|
||||
JSON.stringify(responseData),
|
||||
"EX",
|
||||
43200
|
||||
);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching articles for page ${queryParams?.page}:`, error);
|
||||
|
||||
// Return empty response on error to prevent app crash
|
||||
const fallbackResponse: ArticlePageLoad = {
|
||||
articles: [],
|
||||
currentPage: Number(queryParams?.page) || 1,
|
||||
totalPages: 0,
|
||||
limit: Number(queryParams?.limit) || Number(PAGE_SIZE),
|
||||
totalArticles: 0,
|
||||
cacheControl: 'no-cache',
|
||||
};
|
||||
|
||||
return fallbackResponse;
|
||||
}
|
||||
|
||||
const responseData: ArticlePageLoad = {
|
||||
articles,
|
||||
currentPage: page,
|
||||
totalPages: pages,
|
||||
limit,
|
||||
totalArticles: total,
|
||||
cacheControl,
|
||||
};
|
||||
|
||||
if (USE_REDIS_CACHE) {
|
||||
redis.set(
|
||||
entriesQueryParams.toString(),
|
||||
JSON.stringify(responseData),
|
||||
"EX",
|
||||
43200
|
||||
);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +1,100 @@
|
|||
<script lang="ts">
|
||||
import type { Article } from '$lib/types/article';
|
||||
import { ArrowRight } from 'lucide-svelte';
|
||||
import ExternalLink from './ExternalLink.svelte';
|
||||
import { ArrowRight } from "lucide-svelte";
|
||||
import { beforeNavigate, onNavigate } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import ArticlesSkeleton from "$lib/components/ArticlesSkeleton.svelte";
|
||||
import type { Article } from "$lib/types/article";
|
||||
import ExternalLink from "./ExternalLink.svelte";
|
||||
|
||||
const {
|
||||
articles,
|
||||
totalArticles,
|
||||
compact = false,
|
||||
classes = [],
|
||||
}: { articles: Article[]; totalArticles: number; compact: boolean; classes?: string[] } = $props();
|
||||
type LoadData = {
|
||||
articles: Article[];
|
||||
totalArticles: number;
|
||||
classes?: string[];
|
||||
compact?: boolean;
|
||||
};
|
||||
|
||||
const { data }: { data: LoadData } = $props();
|
||||
|
||||
// Use $derived to maintain reactivity when data prop changes
|
||||
const articles = $derived(data.articles || []);
|
||||
const totalArticles = $derived(data.totalArticles || 0);
|
||||
const compact = $derived(data.compact);
|
||||
const classes = $derived(data.classes || []);
|
||||
|
||||
const articlesData = $derived(articles);
|
||||
let loadingArticles = $state(false);
|
||||
|
||||
beforeNavigate(() => {
|
||||
loadingArticles = true;
|
||||
});
|
||||
|
||||
onNavigate((navigation) => {
|
||||
loadingArticles = true;
|
||||
|
||||
// Resolve the promise when the page is done loading
|
||||
navigation?.complete.then(() => {
|
||||
loadingArticles = false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="articles">
|
||||
<h2>Favorite Articles</h2>
|
||||
<div class={classes.join(' ')}>
|
||||
{#each articles as article (article.hashed_url)}
|
||||
<article class="card">
|
||||
<section>
|
||||
<h3>
|
||||
<ExternalLink
|
||||
textData={{
|
||||
text: compact ? article.title.substring(0, 50).trim() : article.title,
|
||||
location: 'left',
|
||||
showIcon: true,
|
||||
}}
|
||||
linkData={{
|
||||
href: article.url.toString(),
|
||||
ariaLabel: `Link to ${article.title}`,
|
||||
title: `Link to ${article.title}`,
|
||||
target: '_blank',
|
||||
}}
|
||||
iconData={{ iconClass: 'center' }}
|
||||
/>
|
||||
</h3>
|
||||
<p>{article.domain_name}</p>
|
||||
</section>
|
||||
<section>
|
||||
<p>Reading time: {article.reading_time} minutes</p>
|
||||
<div class="tagsStyles">
|
||||
<p>Tags:</p>
|
||||
{#each article.tags as tag}
|
||||
<p>{tag}</p>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
{/each}
|
||||
<div class={classes.join(" ")}>
|
||||
{#if loadingArticles}
|
||||
<ArticlesSkeleton count={6} />
|
||||
{:else}
|
||||
{#each articlesData as article (article.hashed_url)}
|
||||
<article class="card">
|
||||
<section>
|
||||
<h3>
|
||||
<ExternalLink
|
||||
textData={{
|
||||
text: compact
|
||||
? article.title.substring(0, 50).trim()
|
||||
: article.title,
|
||||
location: "left",
|
||||
showIcon: true,
|
||||
}}
|
||||
linkData={{
|
||||
href: article.url.toString(),
|
||||
ariaLabel: `Link to ${article.title}`,
|
||||
title: `Link to ${article.title}`,
|
||||
target: "_blank",
|
||||
}}
|
||||
iconData={{ iconClass: "center" }}
|
||||
/>
|
||||
</h3>
|
||||
<p>{article.domain_name}</p>
|
||||
</section>
|
||||
<section>
|
||||
<p>Reading time: {article.reading_time} minutes</p>
|
||||
<div class="tagsStyles">
|
||||
<p>Tags:</p>
|
||||
{#each article.tags as tag}
|
||||
<p>{tag}</p>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<a class="moreArticles" href="/articles">{`${totalArticles} more articles`} <ArrowRight /></a>
|
||||
{#if page.url.pathname === "/"}
|
||||
<a class="moreArticles" href="/articles"
|
||||
>{`${totalArticles} more articles`} <ArrowRight /></a
|
||||
>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
|
||||
<style lang="postcss">
|
||||
article {
|
||||
article {
|
||||
margin: 1.5rem 0;
|
||||
|
||||
& p {
|
||||
margin: 0.25rem 0rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.articles {
|
||||
display: grid;
|
||||
|
|
@ -68,17 +104,17 @@ const {
|
|||
.columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(250px, 1fr));
|
||||
min-height: 800px;
|
||||
min-height: 800px;
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
grid-template-columns: repeat(2, minmax(250px, 1fr));
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
grid-template-columns: repeat(2, minmax(250px, 1fr));
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
grid-template-columns: minmax(250px, 1fr);
|
||||
}
|
||||
@media (max-width: 650px) {
|
||||
grid-template-columns: minmax(250px, 1fr);
|
||||
}
|
||||
|
||||
gap: 2.5rem;
|
||||
gap: 2.5rem;
|
||||
}
|
||||
|
||||
.tagsStyles {
|
||||
|
|
|
|||
115
src/lib/components/ArticlesSkeleton.svelte
Normal file
115
src/lib/components/ArticlesSkeleton.svelte
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<script lang="ts">
|
||||
interface Props {
|
||||
count?: number;
|
||||
classes?: string[];
|
||||
}
|
||||
|
||||
let { count = 6, classes = [""] }: Props = $props();
|
||||
const placeholders = Array.from({ length: count });
|
||||
</script>
|
||||
|
||||
{#each placeholders as _, i (i)}
|
||||
<article class={`card skeleton ${classes.join(" ")}`}>
|
||||
<section>
|
||||
<h3>
|
||||
<span class="skeleton-text skeleton-title" aria-hidden="true"
|
||||
>Loading article title...</span
|
||||
>
|
||||
</h3>
|
||||
<p>
|
||||
<span class="skeleton-text skeleton-domain" aria-hidden="true"
|
||||
>Loading domain...</span
|
||||
>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<p>
|
||||
<span class="skeleton-text skeleton-reading" aria-hidden="true"
|
||||
>Loading reading time...</span
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
<span class="skeleton-text skeleton-tags" aria-hidden="true"
|
||||
>Loading tags...</span
|
||||
>
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
{/each}
|
||||
|
||||
<style lang="postcss">
|
||||
p {
|
||||
min-height: 1em;
|
||||
line-height: 1.2;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
.skeleton {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--grey, #e5e7eb);
|
||||
background: var(--surface, #0f172a0d);
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
display: block;
|
||||
margin: 0.5rem 0;
|
||||
border-radius: 6px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(148, 163, 184, 0.18) 0%,
|
||||
rgba(148, 163, 184, 0.35) 50%,
|
||||
rgba(148, 163, 184, 0.18) 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.2s ease-in-out infinite;
|
||||
min-height: 1.25em;
|
||||
line-height: 1.25em;
|
||||
padding: 0.25rem 0.5rem;
|
||||
color: transparent; /* hide placeholder text */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skeleton-title {
|
||||
height: 1.6em;
|
||||
}
|
||||
|
||||
.skeleton-domain {
|
||||
width: 40%;
|
||||
height: 1.25em;
|
||||
}
|
||||
|
||||
.skeleton-reading {
|
||||
width: 55%;
|
||||
height: 1.25em;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.skeleton-tags {
|
||||
width: 65%;
|
||||
height: 1.6em; /* closer to tag chip height */
|
||||
border-radius: 9999px; /* pill-like to match tags */
|
||||
}
|
||||
|
||||
/* layout tweaks to avoid overlap and match spacing */
|
||||
.card.skeleton {
|
||||
align-items: start;
|
||||
}
|
||||
.skeleton > section {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.skeleton > section + section {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,31 +1,25 @@
|
|||
<script lang="ts">
|
||||
import { ExternalLink } from "lucide-svelte";
|
||||
import type {
|
||||
ExternalLinkType,
|
||||
LinkIconType,
|
||||
} from "$lib/types/externalLinkTypes";
|
||||
import type { ExternalLinkType, LinkIconType } from '$lib/types/externalLinkTypes';
|
||||
import { ExternalLink } from 'lucide-svelte';
|
||||
|
||||
const { iconData, linkData, textData }: ExternalLinkType = $props();
|
||||
|
||||
let textLocationClass = "";
|
||||
if (textData?.location === "top") {
|
||||
textLocationClass = "text-top";
|
||||
} else if (textData?.location === "bottom") {
|
||||
textLocationClass = "text-bottom";
|
||||
} else if (textData?.location === "left") {
|
||||
textLocationClass = "text-left";
|
||||
} else if (textData?.location === "right") {
|
||||
textLocationClass = "text-right";
|
||||
let textLocationClass = '';
|
||||
if (textData?.location === 'top') {
|
||||
textLocationClass = 'text-top';
|
||||
} else if (textData?.location === 'bottom') {
|
||||
textLocationClass = 'text-bottom';
|
||||
} else if (textData?.location === 'left') {
|
||||
textLocationClass = 'text-left';
|
||||
} else if (textData?.location === 'right') {
|
||||
textLocationClass = 'text-right';
|
||||
} else {
|
||||
textLocationClass = "text-left";
|
||||
textLocationClass = 'text-left';
|
||||
}
|
||||
|
||||
const linkDecoration =
|
||||
linkData?.textDecoration && linkData?.textDecoration === "none"
|
||||
? `text-decoration-${linkData?.textDecoration}`
|
||||
: "text-decoration-underline";
|
||||
const linkClass =
|
||||
`${linkData?.clazz} ${textLocationClass} ${linkDecoration}`.trim();
|
||||
linkData?.textDecoration && linkData?.textDecoration === 'none' ? `text-decoration-${linkData?.textDecoration}` : 'text-decoration-underline';
|
||||
const linkClass = `${linkData?.clazz || ''} ${textLocationClass} ${linkDecoration}`.trim();
|
||||
</script>
|
||||
|
||||
{#snippet externalLink({ iconData, linkData, textData }: ExternalLinkType)}
|
||||
|
|
@ -41,7 +35,7 @@
|
|||
{textData?.text}
|
||||
{/if}
|
||||
{#if textData?.showIcon}
|
||||
{@render linkIcon?.(iconData ?? {})}
|
||||
{@render linkIcon(iconData ?? {})}
|
||||
{/if}
|
||||
{#if textData?.location === "bottom" || (textData?.location === "right" && textData?.text)}
|
||||
{textData?.text}
|
||||
|
|
@ -50,7 +44,7 @@
|
|||
{/snippet}
|
||||
|
||||
{#snippet linkIcon({ type, icon, iconClass }: LinkIconType)}
|
||||
{#if type === "svg" && icon}
|
||||
{#if type === "svg" && icon && typeof icon === 'function' && icon.length !== undefined}
|
||||
<svg
|
||||
style="width: 2.5rem; height: 2.5rem;"
|
||||
class={iconClass ?? ""}
|
||||
|
|
@ -58,7 +52,7 @@
|
|||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{@render icon?.()}
|
||||
{@render (icon as any)()}
|
||||
</svg>
|
||||
{:else if type === "icon" && icon}
|
||||
{@const Icon = icon}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
pageSize: number;
|
||||
totalCount: number;
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
base: string;
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +21,7 @@
|
|||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<Pagination.Root count={totalCount} perPage={pageSize} page={currentPage || 1} class={`${additionalClasses}`}
|
||||
<Pagination.Root count={totalCount} perPage={pageSize} page={currentPage || 1} class={`${additionalClasses}`}
|
||||
onPageChange={(page) => goto(`${base}/${page}`)}>
|
||||
{#snippet children({ pages })}
|
||||
<Pagination.PrevButton>
|
||||
|
|
@ -31,7 +32,7 @@
|
|||
<div class="ellipsis text-[15px] font-medium text-foreground-alt">...</div>
|
||||
{:else}
|
||||
<Pagination.Page {page}>
|
||||
<a href={`${base}/${page.value}`}>
|
||||
<a href={`${base}/${page.value}`} data-sveltekit-preload-data="hover">
|
||||
{page.value}
|
||||
</a>
|
||||
</Pagination.Page>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { Picture } from 'vite-imagetools';
|
||||
import { ExternalLinkType } from '../types/externalLinkType';
|
||||
import type { ExternalLinkType } from '../types/externalLinkType';
|
||||
|
||||
const {
|
||||
links,
|
||||
|
|
@ -15,8 +15,8 @@ const {
|
|||
fetchpriority = 'auto',
|
||||
loading = 'lazy',
|
||||
}: {
|
||||
links: Snippet<ExternalLinkType[]>;
|
||||
details: Snippet<string>;
|
||||
links: Snippet<[ExternalLinkType[]]>;
|
||||
details: Snippet<[]>;
|
||||
portfolioDetails: string;
|
||||
externalLinks: ExternalLinkType[];
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import type { Article } from '$lib/types/article';
|
||||
|
||||
// Custom store
|
||||
const state = () => {
|
||||
const { subscribe, set, update } = writable<Article[]>([]);
|
||||
|
||||
function addAll(articles: Article[]) {
|
||||
// console.log(typeof articles);
|
||||
for (const article of articles) {
|
||||
add(article);
|
||||
}
|
||||
}
|
||||
|
||||
function add(article: Article) {
|
||||
update((store) => [...store, article]);
|
||||
}
|
||||
|
||||
function addSorted(article: Article, index: number) {
|
||||
update((store) => {
|
||||
store.splice(index, 0, article);
|
||||
return store;
|
||||
});
|
||||
}
|
||||
|
||||
function remove(hashed_url: string) {
|
||||
update((store) => {
|
||||
const newStore = store.filter((item: Article) => item.hashed_url !== hashed_url);
|
||||
return [...newStore];
|
||||
});
|
||||
}
|
||||
|
||||
function removeAll() {
|
||||
update(() => {
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
return { subscribe, set, update, add, addSorted, addAll, remove, removeAll };
|
||||
};
|
||||
|
||||
export const articleStore = state();
|
||||
|
|
@ -6,12 +6,12 @@ import type { Album, BandCampResults } from '../types/album';
|
|||
|
||||
export async function fetchBandcampAlbums() {
|
||||
try {
|
||||
if (USE_REDIS_CACHE) {
|
||||
if (USE_REDIS_CACHE === 'true') {
|
||||
const cached: string | null = await redis.get('bandcampAlbums');
|
||||
|
||||
if (cached) {
|
||||
const response: Album[] = JSON.parse(cached);
|
||||
console.log(`Cache hit!`);
|
||||
console.log('Cache hit!');
|
||||
const ttl = await redis.ttl('bandcampAlbums');
|
||||
|
||||
return { ...response, cacheControl: `max-age=${ttl}` };
|
||||
|
|
@ -46,7 +46,7 @@ export async function fetchBandcampAlbums() {
|
|||
const albums: Album[] = data?.collectionItems || [];
|
||||
|
||||
if (albums && albums?.length > 0) {
|
||||
if (USE_REDIS_CACHE) {
|
||||
if (USE_REDIS_CACHE === 'true') {
|
||||
redis.set('bandcampAlbums', JSON.stringify(albums), 'EX', 43200);
|
||||
}
|
||||
return albums;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<script module>
|
||||
<script module lang="ts">
|
||||
export { blueSkyIcon, dockerIcon, drizzleIcon, honoIcon, gitHubIcon, linkedInIcon, lucideIcon, nextDotJsIcon, reactIcon, svelteIcon, typescriptIcon, xIcon };
|
||||
</script>
|
||||
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
<path fill="currentColor" d="M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z"/>
|
||||
{/snippet}
|
||||
|
||||
{#snippet lucideIcon(icon)}
|
||||
{#snippet lucideIcon(icon: any)}
|
||||
{@const Icon = icon}
|
||||
<Icon />
|
||||
{/snippet}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onNavigate } from "$app/navigation";
|
||||
import { onNavigate, beforeNavigate } from "$app/navigation";
|
||||
import { LoaderCircle } from "lucide-svelte";
|
||||
|
||||
let visible = $state(false);
|
||||
let progress = $state(0);
|
||||
|
|
@ -9,22 +10,40 @@
|
|||
);
|
||||
|
||||
const increment = 1;
|
||||
let interval: number | null = null;
|
||||
|
||||
onNavigate((navigation) => {
|
||||
const typical_load_time = average_load || 200; //ms
|
||||
const frequency = typical_load_time / 100;
|
||||
let start = performance.now();
|
||||
// Start the progress bar
|
||||
beforeNavigate(() => {
|
||||
// Start the progress bar immediately when navigation begins
|
||||
visible = true;
|
||||
progress = 0;
|
||||
const interval = setInterval(() => {
|
||||
// Increment the progress bar
|
||||
progress += increment;
|
||||
|
||||
// Clear any existing interval
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
const typical_load_time = average_load || 200; // ms
|
||||
const frequency = typical_load_time / 100;
|
||||
|
||||
interval = setInterval(() => {
|
||||
// Increment the progress bar but cap at 20% during beforeNavigate
|
||||
if (progress < 20) {
|
||||
progress += increment;
|
||||
}
|
||||
}, frequency);
|
||||
});
|
||||
|
||||
onNavigate((navigation) => {
|
||||
console.log("Navigating to", navigation?.to);
|
||||
let start = performance.now();
|
||||
|
||||
// Resolve the promise when the page is done loading
|
||||
navigation?.complete.then(() => {
|
||||
progress = 100; // Fill out the progress bar
|
||||
clearInterval(interval);
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
interval = null;
|
||||
}
|
||||
// after 100 ms hide the progress bar
|
||||
setTimeout(() => {
|
||||
visible = false;
|
||||
|
|
@ -39,6 +58,9 @@
|
|||
|
||||
{#if visible}
|
||||
<div class="progress" style="width: {progress}%;"></div>
|
||||
<div class="loader-container">
|
||||
<LoaderCircle class="loader-icon" size={20} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
|
|
@ -52,4 +74,29 @@
|
|||
z-index: 50;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.loader-container {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 50;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:global(.loader-icon) {
|
||||
animation: spin 1s linear infinite;
|
||||
color: var(--lightGrey);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ const userNames = {
|
|||
</p>
|
||||
<div class="social-info">
|
||||
<Bandcamp {albums} />
|
||||
<Articles {articles} {totalArticles} compact />
|
||||
<Articles data={{ articles, totalArticles, compact: true }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,9 @@
|
|||
</script>
|
||||
|
||||
<ExternalLink
|
||||
ariaLabel={ariaLabel}
|
||||
href={href}
|
||||
linkClass={clazz}
|
||||
icon={lucideIcon}
|
||||
linkData={{ href, ariaLabel, clazz }}
|
||||
textData={textData}
|
||||
iconData={{ type: 'icon', icon }}
|
||||
/>
|
||||
|
||||
<style lang="postcss">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { json, error } from '@sveltejs/kit';
|
||||
import { PAGE_SIZE } from '$env/static/private';
|
||||
import { fetchArticlesApi } from '$lib/api';
|
||||
import type { ArticlePageLoad } from '@/lib/types/article.js';
|
||||
|
||||
export async function GET({ setHeaders, url }) {
|
||||
const page = url?.searchParams?.get('page') || '1';
|
||||
|
|
@ -10,13 +11,11 @@ export async function GET({ setHeaders, url }) {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await fetchArticlesApi('get', 'fetchArticles', {
|
||||
const response: ArticlePageLoad = await fetchArticlesApi('get', 'fetchArticles', {
|
||||
page,
|
||||
limit
|
||||
});
|
||||
|
||||
console.log(`JSON articles response: ${JSON.stringify(response)}`);
|
||||
|
||||
if (response?.articles) {
|
||||
if (response?.cacheControl) {
|
||||
if (!response.cacheControl.includes('no-cache')) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { json, error } from '@sveltejs/kit';
|
||||
import { BANDCAMP_USERNAME, PAGE_SIZE, USE_REDIS_CACHE } from '$env/static/private';
|
||||
import { fetchArticlesApi } from '$lib/api';
|
||||
import { BANDCAMP_USERNAME, USE_REDIS_CACHE } from '$env/static/private';
|
||||
import { redis } from '$lib/server/redis';
|
||||
import type { Album, BandCampResults } from '$lib/types/album';
|
||||
import scrapeIt, { type ScrapeResult } from 'scrape-it';
|
||||
|
||||
export async function GET({ setHeaders, url }) {
|
||||
try {
|
||||
if (USE_REDIS_CACHE) {
|
||||
if (USE_REDIS_CACHE === 'true') {
|
||||
const cached: string | null = await redis.get('bandcampAlbums');
|
||||
|
||||
if (cached) {
|
||||
|
|
@ -51,7 +50,7 @@ export async function GET({ setHeaders, url }) {
|
|||
const albums: Album[] = data?.collectionItems || [];
|
||||
|
||||
if (albums && albums?.length > 0) {
|
||||
if (USE_REDIS_CACHE) {
|
||||
if (USE_REDIS_CACHE === 'true') {
|
||||
redis.set('bandcampAlbums', JSON.stringify(albums), 'EX', 43200);
|
||||
}
|
||||
setHeaders({
|
||||
|
|
|
|||
17
src/routes/articles/+layout.server.ts
Normal file
17
src/routes/articles/+layout.server.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async ({ fetch }) => {
|
||||
// Fetch the first page to get common metadata (total articles, total pages, etc.)
|
||||
const resp = await fetch('/api/articles?page=1');
|
||||
const data = await resp.json();
|
||||
|
||||
console.log('Data: ', JSON.stringify(data));
|
||||
|
||||
return {
|
||||
// Common metadata available to all child routes
|
||||
totalArticles: data.totalArticles,
|
||||
totalPages: data.totalPages,
|
||||
limit: data.limit,
|
||||
cacheControl: data.cacheControl
|
||||
};
|
||||
};
|
||||
43
src/routes/articles/+layout.svelte
Normal file
43
src/routes/articles/+layout.svelte
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<script lang="ts">
|
||||
import type { LayoutData } from './$types';
|
||||
|
||||
interface Props {
|
||||
data: LayoutData;
|
||||
children: import('svelte').Snippet;
|
||||
}
|
||||
|
||||
let { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="articles-layout">
|
||||
<h1 style:margin-bottom="2rem">Favorite Tech Articles</h1>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.articles-layout {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-theme-1);
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-theme-1);
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
:global(.top-pagination) {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
:global(.bottom-pagination) {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,68 +1,59 @@
|
|||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { PUBLIC_SITE_URL } from '$env/static/public';
|
||||
import type { ArticlePageLoad } from '$lib/types/article';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch, params, setHeaders, url }) => {
|
||||
const { page } = params;
|
||||
const resp = await fetch(`/api/articles?page=${page}`);
|
||||
const {
|
||||
articles,
|
||||
currentPage,
|
||||
totalPages,
|
||||
limit,
|
||||
totalArticles,
|
||||
cacheControl,
|
||||
}: ArticlePageLoad = await resp.json();
|
||||
export const load: PageServerLoad = async ({ fetch, params, setHeaders, url, parent }) => {
|
||||
const { page } = params;
|
||||
const { cacheControl } = await parent();
|
||||
|
||||
if (cacheControl?.includes('no-cache')) {
|
||||
setHeaders({
|
||||
'cache-control': cacheControl,
|
||||
});
|
||||
} else {
|
||||
setHeaders({
|
||||
'cache-control': 'max-age=43200', // 12 hours
|
||||
});
|
||||
}
|
||||
if (cacheControl?.includes('no-cache')) {
|
||||
setHeaders({
|
||||
'cache-control': cacheControl,
|
||||
});
|
||||
} else {
|
||||
setHeaders({
|
||||
'cache-control': 'max-age=43200', // 12 hours
|
||||
});
|
||||
}
|
||||
|
||||
const baseUrl = new URL(url.origin).href || PUBLIC_SITE_URL || 'https://bradleyshellnut.com';
|
||||
const currentPageUrl = new URL(url.pathname, url.origin).href;
|
||||
const baseUrl = new URL(url.origin).href || PUBLIC_SITE_URL || 'https://bradleyshellnut.com';
|
||||
const currentPageUrl = new URL(url.pathname, url.origin).href;
|
||||
|
||||
const metaTags: MetaTagsProps = Object.freeze({
|
||||
title: 'Favorite Articles',
|
||||
description: 'My favorite articles',
|
||||
openGraph: {
|
||||
title: 'Favorite Articles',
|
||||
description: 'My favorite articles',
|
||||
url: currentPageUrl,
|
||||
siteName: 'Bradley Shellnut Personal Website',
|
||||
type: 'website',
|
||||
locale: 'en_US',
|
||||
images: [
|
||||
{
|
||||
url: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`,
|
||||
alt: `Bradley Shellnut Articles Page ${page}`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
title: 'Favorite Articles',
|
||||
description: 'My favorite articles',
|
||||
card: 'summary_large_image',
|
||||
image: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`,
|
||||
imageAlt: 'Bradley Shellnut Website Logo',
|
||||
},
|
||||
url: currentPageUrl,
|
||||
});
|
||||
const metaTags: MetaTagsProps = Object.freeze({
|
||||
title: 'Favorite Articles',
|
||||
description: 'My favorite articles',
|
||||
openGraph: {
|
||||
title: 'Favorite Articles',
|
||||
description: 'My favorite articles',
|
||||
url: currentPageUrl,
|
||||
siteName: 'Bradley Shellnut Personal Website',
|
||||
type: 'website',
|
||||
locale: 'en_US',
|
||||
images: [
|
||||
{
|
||||
url: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`,
|
||||
alt: `Bradley Shellnut Articles Page ${page}`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
title: 'Favorite Articles',
|
||||
description: 'My favorite articles',
|
||||
card: 'summary_large_image',
|
||||
image: `${baseUrl}og?header=Articles Page ${page} | bradleyshellnut.com&page=My favorite articles`,
|
||||
imageAlt: 'Bradley Shellnut Website Logo',
|
||||
},
|
||||
url: currentPageUrl,
|
||||
});
|
||||
|
||||
return {
|
||||
articles,
|
||||
currentPage,
|
||||
totalPages,
|
||||
limit,
|
||||
totalArticles,
|
||||
metaTagsChild: metaTags,
|
||||
};
|
||||
const articlesData = await fetch(`/api/articles?page=${page}`);
|
||||
const { articles, currentPage } = await articlesData.json();
|
||||
|
||||
return {
|
||||
articles,
|
||||
currentPage,
|
||||
metaTagsChild: metaTags,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,33 +1,58 @@
|
|||
<script lang="ts">
|
||||
import Pagination from '$lib/components/Pagination.svelte';
|
||||
import type { Article } from '$lib/types/article';
|
||||
import Articles from '$lib/components/Articles.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import Articles from "$lib/components/Articles.svelte";
|
||||
import Pagination from "$lib/components/Pagination.svelte";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
}
|
||||
|
||||
let { data }: Props = $props();
|
||||
|
||||
// Use the data directly - it will be reactive when navigating between pages
|
||||
let articles = $derived(data?.articles || []);
|
||||
let currentPage: number = $derived(data?.currentPage || 1);
|
||||
let totalArticles: number = $derived(data?.totalArticles || 0);
|
||||
let limit: number = $derived(data?.limit || 10);
|
||||
let totalPages: number = $derived(data?.totalPages || 1);
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
}
|
||||
|
||||
let { data }: Props = $props();
|
||||
let articles: Article[] = $state(data?.articles);
|
||||
let currentPage: number = $state(data?.currentPage);
|
||||
let totalArticles: number = $state(data?.totalArticles);
|
||||
let limit: number = $state(data?.limit);
|
||||
</script>
|
||||
|
||||
<h1 style:margin-bottom={"2rem"}>Favorite Tech Articles</h1>
|
||||
<Pagination
|
||||
additionalClasses="top-pagination"
|
||||
pageSize={limit}
|
||||
totalCount={totalArticles}
|
||||
currentPage={currentPage || 1}
|
||||
base="/articles"
|
||||
/>
|
||||
<Articles {articles} {totalArticles} classes={['columns']} />
|
||||
<Pagination
|
||||
additionalClasses="bottom-pagination"
|
||||
pageSize={limit}
|
||||
totalCount={totalArticles}
|
||||
currentPage={currentPage || 1}
|
||||
base="/articles"
|
||||
/>
|
||||
<div class="articles-content">
|
||||
<Pagination
|
||||
additionalClasses="top-pagination"
|
||||
pageSize={limit}
|
||||
totalCount={totalArticles}
|
||||
currentPage={currentPage || 1}
|
||||
totalPages={totalPages || 1}
|
||||
base="/articles"
|
||||
/>
|
||||
|
||||
<Articles
|
||||
data={{ articles, totalArticles, classes: ["columns"], compact: false }}
|
||||
/>
|
||||
|
||||
<Pagination
|
||||
additionalClasses="bottom-pagination"
|
||||
pageSize={limit}
|
||||
totalCount={totalArticles}
|
||||
currentPage={currentPage || 1}
|
||||
totalPages={totalPages || 1}
|
||||
base="/articles"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.articles-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:global(.top-pagination) {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
:global(.bottom-pagination) {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ export async function GET({ url }) {
|
|||
const page = url.searchParams.get('page') ?? undefined;
|
||||
const content = url.searchParams.get('content') ?? '';
|
||||
|
||||
// @ts-expect-error: Argument of type 'typeof SocialImageCard__SvelteComponent_' is not assignable to parameter of type 'SvelteComponent<any, any, any>'
|
||||
return componentToPng(
|
||||
SocialImageCard,
|
||||
SocialImageCard as any,
|
||||
{
|
||||
header,
|
||||
page,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
<script lang="ts">
|
||||
import ExternalLink from "$lib/components/ExternalLink.svelte";
|
||||
import Portfolio from "./Portfolio.svelte";
|
||||
// @ts-expect-error: Cannot find module '$lib/content/uses/development.md' or its corresponding type declarations.ts(2307)
|
||||
// import OldWebsite from "$lib/content/portfolio/personal/old-website.md";
|
||||
// @ts-expect-error: Cannot find module '$lib/content/uses/development.md' or its corresponding type declarations.ts(2307)
|
||||
// import PersonalWebsiteSvelteKit from "$lib/content/portfolio/personal/personal-website-sveltekit.md";
|
||||
// @ts-expect-error: Cannot find module '$lib/content/uses/development.md' or its corresponding type declarations.ts(2307)
|
||||
// import WeddingWebsite from "$lib/content/portfolio/personal/wedding-website.md";
|
||||
// @ts-expect-error: Cannot find module '$lib/content/uses/development.md' or its corresponding type declarations.ts(2307)
|
||||
// import MarkShellnutArchitect from "$lib/content/portfolio/professional/mark-shellnut-architect.md";
|
||||
import type { ExternalLinkType } from "$lib/types/externalLinkType";
|
||||
import { Tabs } from "bits-ui";
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
loading = "lazy",
|
||||
children,
|
||||
}: {
|
||||
links: Snippet<ExternalLinkType[]>;
|
||||
links: Snippet<[ExternalLinkType[]]>;
|
||||
externalLinks: ExternalLinkType[];
|
||||
name: string;
|
||||
src: string | Picture;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts">
|
||||
import desktop from '$lib/assets/images/Desktop_so_clean.jpg?enhanced';
|
||||
import ExternalLink from '$lib/components/ExternalLink.svelte';
|
||||
import Development from './development.svelte';
|
||||
import HardwareAccessories from './hardware-accessories.svelte';
|
||||
import PrivacyHardwareSoftware from './privacy-hardware-software.svelte';
|
||||
import desktop from '$lib/assets/images/Desktop_so_clean.jpg?enhanced';
|
||||
import ExternalLink from '$lib/components/ExternalLink.svelte';
|
||||
import Development from './development.svelte';
|
||||
import HardwareAccessories from './hardware-accessories.svelte';
|
||||
import PrivacyHardwareSoftware from './privacy-hardware-software.svelte';
|
||||
</script>
|
||||
|
||||
<div class="uses">
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ const config = {
|
|||
adapter: adapter(),
|
||||
alias: {
|
||||
$: './src',
|
||||
$lib: './src/lib',
|
||||
'@': './src'
|
||||
}
|
||||
},
|
||||
compilerOptions: {
|
||||
|
|
|
|||
|
|
@ -12,10 +12,7 @@
|
|||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
"emitDecoratorMetadata": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
|
|
|
|||
Loading…
Reference in a new issue