Compare commits
2 Commits
5fae01c06d
...
5598b19ec9
| Author | SHA1 | Date | |
|---|---|---|---|
|
5598b19ec9
|
|||
|
a48ae3ff3c
|
@@ -34,6 +34,7 @@
|
||||
"jsdom": "^27.2.0",
|
||||
"svelte": "^5.38.6",
|
||||
"svelte-check": "^4.0.0",
|
||||
"terser": "^5.46.0",
|
||||
"tslib": "^2.6.0",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^7.1.3",
|
||||
|
||||
202
pnpm-lock.yaml
generated
202
pnpm-lock.yaml
generated
@@ -10,16 +10,19 @@ importers:
|
||||
dependencies:
|
||||
'@auth/sveltekit':
|
||||
specifier: ^1.11.1
|
||||
version: 1.11.1(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)
|
||||
version: 1.11.1(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)
|
||||
'@sveltejs/adapter-node':
|
||||
specifier: ^5.0.0
|
||||
version: 5.3.1(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))
|
||||
version: 5.3.1(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))
|
||||
chart.js:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0
|
||||
cheerio:
|
||||
specifier: 1.0.0-rc.12
|
||||
version: 1.0.0-rc.12
|
||||
file-type:
|
||||
specifier: ^19.0.0
|
||||
version: 19.6.0
|
||||
ioredis:
|
||||
specifier: ^5.9.0
|
||||
version: 5.9.0
|
||||
@@ -34,23 +37,23 @@ importers:
|
||||
version: 0.33.5
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: ^1.56.1
|
||||
specifier: 1.56.1
|
||||
version: 1.56.1
|
||||
'@sveltejs/adapter-auto':
|
||||
specifier: ^6.1.0
|
||||
version: 6.1.0(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))
|
||||
version: 6.1.0(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))
|
||||
'@sveltejs/kit':
|
||||
specifier: ^2.37.0
|
||||
version: 2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))
|
||||
version: 2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^6.1.3
|
||||
version: 6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))
|
||||
version: 6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^6.9.1
|
||||
version: 6.9.1
|
||||
'@testing-library/svelte':
|
||||
specifier: ^5.2.9
|
||||
version: 5.2.9(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))(vitest@4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0))
|
||||
version: 5.2.9(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))(vitest@4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0)(terser@5.46.0))
|
||||
'@types/node':
|
||||
specifier: ^22.12.0
|
||||
version: 22.18.0
|
||||
@@ -69,6 +72,9 @@ importers:
|
||||
svelte-check:
|
||||
specifier: ^4.0.0
|
||||
version: 4.3.1(picomatch@4.0.3)(svelte@5.38.6)(typescript@5.1.6)
|
||||
terser:
|
||||
specifier: ^5.46.0
|
||||
version: 5.46.0
|
||||
tslib:
|
||||
specifier: ^2.6.0
|
||||
version: 2.6.0
|
||||
@@ -77,10 +83,10 @@ importers:
|
||||
version: 5.1.6
|
||||
vite:
|
||||
specifier: ^7.1.3
|
||||
version: 7.1.3(@types/node@22.18.0)
|
||||
version: 7.1.3(@types/node@22.18.0)(terser@5.46.0)
|
||||
vitest:
|
||||
specifier: ^4.0.10
|
||||
version: 4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0)
|
||||
version: 4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0)(terser@5.46.0)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -141,6 +147,9 @@ packages:
|
||||
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@borewit/text-codec@0.2.1':
|
||||
resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==}
|
||||
|
||||
'@csstools/color-helpers@5.1.0':
|
||||
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -450,6 +459,9 @@ packages:
|
||||
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/source-map@0.3.11':
|
||||
resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.4.15':
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
|
||||
@@ -626,6 +638,9 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1':
|
||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||
|
||||
'@standard-schema/spec@1.0.0':
|
||||
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
|
||||
|
||||
@@ -693,6 +708,9 @@ packages:
|
||||
vitest:
|
||||
optional: true
|
||||
|
||||
'@tokenizer/token@0.3.0':
|
||||
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
|
||||
|
||||
'@types/aria-query@5.0.4':
|
||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||
|
||||
@@ -802,6 +820,9 @@ packages:
|
||||
resolution: {integrity: sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==}
|
||||
engines: {node: '>=16.20.1'}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
chai@6.2.1:
|
||||
resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -843,6 +864,9 @@ packages:
|
||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||
engines: {node: '>=12.5.0'}
|
||||
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
commondir@1.0.1:
|
||||
resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
|
||||
|
||||
@@ -984,6 +1008,10 @@ packages:
|
||||
fflate@0.8.2:
|
||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||
|
||||
file-type@19.6.0:
|
||||
resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
flatted@3.3.3:
|
||||
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
||||
|
||||
@@ -1000,6 +1028,10 @@ packages:
|
||||
function-bind@1.1.1:
|
||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||
|
||||
get-stream@9.0.1:
|
||||
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
has@1.0.3:
|
||||
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
@@ -1023,6 +1055,9 @@ packages:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
indent-string@4.0.0:
|
||||
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1052,6 +1087,10 @@ packages:
|
||||
is-reference@3.0.3:
|
||||
resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
|
||||
|
||||
is-stream@4.0.1:
|
||||
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
jose@6.1.0:
|
||||
resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==}
|
||||
|
||||
@@ -1194,6 +1233,10 @@ packages:
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
peek-readable@5.4.2:
|
||||
resolution: {integrity: sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
picocolors@1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
|
||||
@@ -1325,6 +1368,13 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map-support@0.5.21:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
|
||||
source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
sparse-bitfield@3.0.3:
|
||||
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
|
||||
|
||||
@@ -1341,6 +1391,10 @@ packages:
|
||||
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strtok3@9.1.1:
|
||||
resolution: {integrity: sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1360,6 +1414,11 @@ packages:
|
||||
symbol-tree@3.2.4:
|
||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||
|
||||
terser@5.46.0:
|
||||
resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
@@ -1385,6 +1444,10 @@ packages:
|
||||
resolution: {integrity: sha512-lCcgTAgMxQ1JKOWrVGo6E69Ukbnx4Gc1wiYLRf6J5NN4HRYJtCby1rPF8rkQ4a6qqoFBK5dvjJ1zJ0F7VfDSvw==}
|
||||
hasBin: true
|
||||
|
||||
token-types@6.1.2:
|
||||
resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
totalist@3.0.1:
|
||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1409,6 +1472,10 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
uint8array-extras@1.5.0:
|
||||
resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
@@ -1581,10 +1648,10 @@ snapshots:
|
||||
preact: 10.24.3
|
||||
preact-render-to-string: 6.5.11(preact@10.24.3)
|
||||
|
||||
'@auth/sveltekit@1.11.1(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)':
|
||||
'@auth/sveltekit@1.11.1(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)':
|
||||
dependencies:
|
||||
'@auth/core': 0.41.1
|
||||
'@sveltejs/kit': 2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))
|
||||
'@sveltejs/kit': 2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
set-cookie-parser: 2.7.1
|
||||
svelte: 5.38.6
|
||||
|
||||
@@ -1598,6 +1665,8 @@ snapshots:
|
||||
|
||||
'@babel/runtime@7.28.4': {}
|
||||
|
||||
'@borewit/text-codec@0.2.1': {}
|
||||
|
||||
'@csstools/color-helpers@5.1.0': {}
|
||||
|
||||
'@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
|
||||
@@ -1792,6 +1861,11 @@ snapshots:
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.0': {}
|
||||
|
||||
'@jridgewell/source-map@0.3.11':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.30
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.4.15': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
@@ -1922,29 +1996,31 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.50.0':
|
||||
optional: true
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1': {}
|
||||
|
||||
'@standard-schema/spec@1.0.0': {}
|
||||
|
||||
'@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)':
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
'@sveltejs/adapter-auto@6.1.0(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))':
|
||||
'@sveltejs/adapter-auto@6.1.0(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))':
|
||||
dependencies:
|
||||
'@sveltejs/kit': 2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))
|
||||
'@sveltejs/kit': 2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
|
||||
'@sveltejs/adapter-node@5.3.1(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))':
|
||||
'@sveltejs/adapter-node@5.3.1(@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))':
|
||||
dependencies:
|
||||
'@rollup/plugin-commonjs': 28.0.6(rollup@4.50.0)
|
||||
'@rollup/plugin-json': 6.1.0(rollup@4.50.0)
|
||||
'@rollup/plugin-node-resolve': 16.0.1(rollup@4.50.0)
|
||||
'@sveltejs/kit': 2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))
|
||||
'@sveltejs/kit': 2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
rollup: 4.50.0
|
||||
|
||||
'@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))':
|
||||
'@sveltejs/kit@2.37.0(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.0.0
|
||||
'@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0)
|
||||
'@sveltejs/vite-plugin-svelte': 6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))
|
||||
'@sveltejs/vite-plugin-svelte': 6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
'@types/cookie': 0.6.0
|
||||
acorn: 8.15.0
|
||||
cookie: 0.6.0
|
||||
@@ -1957,27 +2033,27 @@ snapshots:
|
||||
set-cookie-parser: 2.6.0
|
||||
sirv: 3.0.1
|
||||
svelte: 5.38.6
|
||||
vite: 7.1.3(@types/node@22.18.0)
|
||||
vite: 7.1.3(@types/node@22.18.0)(terser@5.46.0)
|
||||
|
||||
'@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))':
|
||||
'@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))
|
||||
'@sveltejs/vite-plugin-svelte': 6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
debug: 4.4.1
|
||||
svelte: 5.38.6
|
||||
vite: 7.1.3(@types/node@22.18.0)
|
||||
vite: 7.1.3(@types/node@22.18.0)(terser@5.46.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))':
|
||||
'@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)))(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
debug: 4.4.1
|
||||
deepmerge: 4.3.1
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.18
|
||||
svelte: 5.38.6
|
||||
vite: 7.1.3(@types/node@22.18.0)
|
||||
vitefu: 1.1.1(vite@7.1.3(@types/node@22.18.0))
|
||||
vite: 7.1.3(@types/node@22.18.0)(terser@5.46.0)
|
||||
vitefu: 1.1.1(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -2001,13 +2077,15 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
redent: 3.0.0
|
||||
|
||||
'@testing-library/svelte@5.2.9(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0))(vitest@4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0))':
|
||||
'@testing-library/svelte@5.2.9(svelte@5.38.6)(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))(vitest@4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0)(terser@5.46.0))':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.1
|
||||
svelte: 5.38.6
|
||||
optionalDependencies:
|
||||
vite: 7.1.3(@types/node@22.18.0)
|
||||
vitest: 4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0)
|
||||
vite: 7.1.3(@types/node@22.18.0)(terser@5.46.0)
|
||||
vitest: 4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0)(terser@5.46.0)
|
||||
|
||||
'@tokenizer/token@0.3.0': {}
|
||||
|
||||
'@types/aria-query@5.0.4': {}
|
||||
|
||||
@@ -2047,13 +2125,13 @@ snapshots:
|
||||
chai: 6.2.1
|
||||
tinyrainbow: 3.0.3
|
||||
|
||||
'@vitest/mocker@4.0.10(vite@7.1.3(@types/node@22.18.0))':
|
||||
'@vitest/mocker@4.0.10(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.0.10
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 7.1.3(@types/node@22.18.0)
|
||||
vite: 7.1.3(@types/node@22.18.0)(terser@5.46.0)
|
||||
|
||||
'@vitest/pretty-format@4.0.10':
|
||||
dependencies:
|
||||
@@ -2081,7 +2159,7 @@ snapshots:
|
||||
sirv: 3.0.2
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 3.0.3
|
||||
vitest: 4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0)
|
||||
vitest: 4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0)(terser@5.46.0)
|
||||
|
||||
'@vitest/utils@4.0.10':
|
||||
dependencies:
|
||||
@@ -2114,6 +2192,8 @@ snapshots:
|
||||
|
||||
bson@6.10.4: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
chai@6.2.1: {}
|
||||
|
||||
chart.js@4.5.0:
|
||||
@@ -2163,6 +2243,8 @@ snapshots:
|
||||
color-convert: 2.0.1
|
||||
color-string: 1.9.1
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
commondir@1.0.1: {}
|
||||
|
||||
cookie@0.6.0: {}
|
||||
@@ -2296,6 +2378,13 @@ snapshots:
|
||||
|
||||
fflate@0.8.2: {}
|
||||
|
||||
file-type@19.6.0:
|
||||
dependencies:
|
||||
get-stream: 9.0.1
|
||||
strtok3: 9.1.1
|
||||
token-types: 6.1.2
|
||||
uint8array-extras: 1.5.0
|
||||
|
||||
flatted@3.3.3: {}
|
||||
|
||||
fsevents@2.3.2:
|
||||
@@ -2306,6 +2395,11 @@ snapshots:
|
||||
|
||||
function-bind@1.1.1: {}
|
||||
|
||||
get-stream@9.0.1:
|
||||
dependencies:
|
||||
'@sec-ant/readable-stream': 0.4.1
|
||||
is-stream: 4.0.1
|
||||
|
||||
has@1.0.3:
|
||||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
@@ -2339,6 +2433,8 @@ snapshots:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
indent-string@4.0.0: {}
|
||||
|
||||
ioredis@5.9.0:
|
||||
@@ -2376,6 +2472,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
|
||||
is-stream@4.0.1: {}
|
||||
|
||||
jose@6.1.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
@@ -2510,6 +2608,8 @@ snapshots:
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
peek-readable@5.4.2: {}
|
||||
|
||||
picocolors@1.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
@@ -2669,6 +2769,13 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
source-map-support@0.5.21:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: 0.6.1
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
sparse-bitfield@3.0.3:
|
||||
dependencies:
|
||||
memory-pager: 1.5.0
|
||||
@@ -2683,6 +2790,11 @@ snapshots:
|
||||
dependencies:
|
||||
min-indent: 1.0.1
|
||||
|
||||
strtok3@9.1.1:
|
||||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
peek-readable: 5.4.2
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
svelte-check@4.3.1(picomatch@4.0.3)(svelte@5.38.6)(typescript@5.1.6):
|
||||
@@ -2716,6 +2828,13 @@ snapshots:
|
||||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
||||
terser@5.46.0:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.11
|
||||
acorn: 8.15.0
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
@@ -2738,6 +2857,12 @@ snapshots:
|
||||
dependencies:
|
||||
tldts-core: 7.0.18
|
||||
|
||||
token-types@6.1.2:
|
||||
dependencies:
|
||||
'@borewit/text-codec': 0.2.1
|
||||
'@tokenizer/token': 0.3.0
|
||||
ieee754: 1.2.1
|
||||
|
||||
totalist@3.0.1: {}
|
||||
|
||||
tough-cookie@6.0.0:
|
||||
@@ -2756,9 +2881,11 @@ snapshots:
|
||||
|
||||
typescript@5.1.6: {}
|
||||
|
||||
uint8array-extras@1.5.0: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
vite@7.1.3(@types/node@22.18.0):
|
||||
vite@7.1.3(@types/node@22.18.0)(terser@5.46.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.9
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -2769,15 +2896,16 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 22.18.0
|
||||
fsevents: 2.3.3
|
||||
terser: 5.46.0
|
||||
|
||||
vitefu@1.1.1(vite@7.1.3(@types/node@22.18.0)):
|
||||
vitefu@1.1.1(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0)):
|
||||
optionalDependencies:
|
||||
vite: 7.1.3(@types/node@22.18.0)
|
||||
vite: 7.1.3(@types/node@22.18.0)(terser@5.46.0)
|
||||
|
||||
vitest@4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0):
|
||||
vitest@4.0.10(@types/node@22.18.0)(@vitest/ui@4.0.10)(jsdom@27.2.0)(terser@5.46.0):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.0.10
|
||||
'@vitest/mocker': 4.0.10(vite@7.1.3(@types/node@22.18.0))
|
||||
'@vitest/mocker': 4.0.10(vite@7.1.3(@types/node@22.18.0)(terser@5.46.0))
|
||||
'@vitest/pretty-format': 4.0.10
|
||||
'@vitest/runner': 4.0.10
|
||||
'@vitest/snapshot': 4.0.10
|
||||
@@ -2794,7 +2922,7 @@ snapshots:
|
||||
tinyexec: 0.3.2
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 3.0.3
|
||||
vite: 7.1.3(@types/node@22.18.0)
|
||||
vite: 7.1.3(@types/node@22.18.0)(terser@5.46.0)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 22.18.0
|
||||
|
||||
@@ -8,12 +8,12 @@ import { onMount } from 'svelte'
|
||||
let {
|
||||
card_data = $bindable(),
|
||||
image_preview_url = $bindable(),
|
||||
uploaded_image_filename = $bindable(''),
|
||||
selected_image_file = $bindable(null),
|
||||
short_name = ''
|
||||
} = $props<{
|
||||
card_data: any,
|
||||
image_preview_url: string,
|
||||
uploaded_image_filename?: string,
|
||||
selected_image_file?: File | null,
|
||||
short_name: string
|
||||
}>();
|
||||
|
||||
@@ -52,12 +52,11 @@ if(!card_data.tags){
|
||||
|
||||
//locals
|
||||
let new_tag = $state("");
|
||||
let uploading = $state(false);
|
||||
let upload_error = $state("");
|
||||
|
||||
/**
|
||||
* Handles image file selection and upload
|
||||
* Now uses FormData instead of base64 encoding for better security and performance
|
||||
* Handles image file selection and preview
|
||||
* The actual upload will happen when the form is submitted
|
||||
*/
|
||||
export async function show_local_image(){
|
||||
const file = this.files[0];
|
||||
@@ -81,57 +80,15 @@ export async function show_local_image(){
|
||||
return;
|
||||
}
|
||||
|
||||
// Show preview immediately
|
||||
// Show preview and store file for later upload
|
||||
image_preview_url = URL.createObjectURL(file);
|
||||
selected_image_file = file;
|
||||
upload_error = "";
|
||||
|
||||
// Upload to server
|
||||
try {
|
||||
uploading = true;
|
||||
|
||||
// Validate short_name is provided
|
||||
if (!short_name || short_name.trim() === '') {
|
||||
upload_error = 'Please provide a short name (URL) before uploading an image.';
|
||||
alert(upload_error);
|
||||
uploading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create FormData for upload
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
formData.append('name', short_name.trim());
|
||||
|
||||
const response = await fetch('/api/rezepte/img/add', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error_data = await response.json();
|
||||
throw new Error(error_data.message || 'Upload failed');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
uploaded_image_filename = result.filename;
|
||||
upload_error = "";
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Image upload error:', error);
|
||||
upload_error = error.message || 'Failed to upload image. Please try again.';
|
||||
alert(`Upload failed: ${upload_error}`);
|
||||
|
||||
// Clear preview on error
|
||||
image_preview_url = "";
|
||||
uploaded_image_filename = "";
|
||||
} finally {
|
||||
uploading = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function remove_selected_images(){
|
||||
image_preview_url = "";
|
||||
uploaded_image_filename = "";
|
||||
selected_image_file = null;
|
||||
upload_error = "";
|
||||
}
|
||||
|
||||
@@ -430,11 +387,6 @@ input::placeholder{
|
||||
.tag_input{
|
||||
width: 12ch;
|
||||
}
|
||||
.upload-spinner {
|
||||
color: white;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -453,15 +405,11 @@ input::placeholder{
|
||||
{/if}
|
||||
|
||||
|
||||
<label class=img_label for=img_picker style={uploading ? 'opacity: 0.5; cursor: not-allowed;' : ''}>
|
||||
{#if uploading}
|
||||
<div class="upload-spinner">Uploading...</div>
|
||||
{:else}
|
||||
<label class=img_label for=img_picker>
|
||||
<svg class="upload over_img" xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path d="M288 109.3V352c0 17.7-14.3 32-32 32s-32-14.3-32-32V109.3l-73.4 73.4c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0l128 128c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L288 109.3zM64 352H192c0 35.3 28.7 64 64 64s64-28.7 64-64H448c35.3 0 64 28.7 64 64v32c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V416c0-35.3 28.7-64 64-64zM432 456a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"/></svg>
|
||||
{/if}
|
||||
</label>
|
||||
</div>
|
||||
<input type="file" id=img_picker accept="image/webp,image/jpeg,image/jpg,image/png" onchange={show_local_image} disabled={uploading}>
|
||||
<input type="file" id=img_picker accept="image/webp,image/jpeg,image/jpg,image/png" onchange={show_local_image}>
|
||||
<div class=title>
|
||||
<input class=category placeholder=Kategorie... bind:value={card_data.category}/>
|
||||
<div>
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/crosses.ttf);
|
||||
src: url(/fonts/crosses.woff2) format('woff2'),
|
||||
url(/fonts/crosses.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'LibertineMinimal';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/fonts/LinLibertine_minimal.ttf);
|
||||
src: url(/fonts/LinLibertine_minimal.woff2) format('woff2'),
|
||||
url(/fonts/LinLibertine_minimal.ttf) format('truetype');
|
||||
}
|
||||
.sbeads{
|
||||
fill: var(--nord10);
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
import { redirect, error } from '@sveltejs/kit';
|
||||
import { stripHtmlTags } from '$lib/js/stripHtmlTags';
|
||||
|
||||
export async function load({ parent }) {
|
||||
// Get data from universal load function
|
||||
const data = await parent();
|
||||
|
||||
// Strip HTML tags server-side to avoid bundling cheerio in client
|
||||
const strippedName = stripHtmlTags(data.name);
|
||||
const strippedDescription = stripHtmlTags(data.description);
|
||||
|
||||
return {
|
||||
strippedName,
|
||||
strippedDescription,
|
||||
};
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
toggleFavorite: async ({ request, locals, url, fetch }) => {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import {season} from '$lib/js/season_store';
|
||||
import RecipeNote from '$lib/components/RecipeNote.svelte';
|
||||
import {stripHtmlTags} from '$lib/js/stripHtmlTags';
|
||||
import FavoriteButton from '$lib/components/FavoriteButton.svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { recipeTranslationStore } from '$lib/stores/recipeTranslation';
|
||||
@@ -44,10 +43,8 @@
|
||||
const hero_img_src = $derived("https://bocken.org/static/rezepte/full/" + img_filename);
|
||||
const placeholder_src = $derived("https://bocken.org/static/rezepte/placeholder/" + img_filename);
|
||||
|
||||
// Get alt text from images array (with fallback to recipe name)
|
||||
const img_alt = $derived(
|
||||
data.images?.[0]?.alt || stripHtmlTags(data.name)
|
||||
);
|
||||
// Get alt text from images array
|
||||
const img_alt = $derived(data.images?.[0]?.alt || '');
|
||||
|
||||
const months = $derived(isEnglish
|
||||
? ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
||||
@@ -308,12 +305,12 @@ h2{
|
||||
|
||||
</style>
|
||||
<svelte:head>
|
||||
<title>{stripHtmlTags(data.name)} - {labels.title}</title>
|
||||
<meta name="description" content="{stripHtmlTags(data.description)}" />
|
||||
<title>{data.strippedName} - {labels.title}</title>
|
||||
<meta name="description" content="{data.strippedDescription}" />
|
||||
<meta property="og:image" content="https://bocken.org/static/rezepte/thumb/{img_filename}" />
|
||||
<meta property="og:image:secure_url" content="https://bocken.org/static/rezepte/thumb/{img_filename}" />
|
||||
<meta property="og:image:type" content="image/webp" />
|
||||
<meta property="og:image:alt" content="{stripHtmlTags(data.name)}" />
|
||||
<meta property="og:image:alt" content="{data.strippedName}" />
|
||||
{@html `<script type="application/ld+json">${JSON.stringify(data.recipeJsonLd)}</script>`}
|
||||
<!-- SEO: hreflang tags -->
|
||||
<link rel="alternate" hreflang="de" href="https://bocken.org/rezepte/{data.germanShortName}" />
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { Actions, PageServerLoad } from './$types';
|
||||
import { Recipe } from '$models/Recipe';
|
||||
import { dbConnect } from '$utils/db';
|
||||
import { invalidateRecipeCaches } from '$lib/server/cache';
|
||||
import { IMAGE_DIR } from '$env/static/private';
|
||||
import { processAndSaveRecipeImage } from '$utils/imageProcessing';
|
||||
import {
|
||||
extractRecipeFromFormData,
|
||||
validateRecipeData,
|
||||
@@ -49,14 +51,29 @@ export const actions = {
|
||||
}
|
||||
|
||||
// Handle optional image upload
|
||||
const uploadedImage = formData.get('uploaded_image_filename')?.toString();
|
||||
if (uploadedImage && uploadedImage.trim() !== '') {
|
||||
// Image was uploaded - use it
|
||||
const recipeImage = formData.get('recipe_image') as File | null;
|
||||
if (recipeImage && recipeImage.size > 0) {
|
||||
try {
|
||||
// Process and save the image
|
||||
const { filename } = await processAndSaveRecipeImage(
|
||||
recipeImage,
|
||||
recipeData.short_name,
|
||||
IMAGE_DIR
|
||||
);
|
||||
|
||||
recipeData.images = [{
|
||||
mediapath: uploadedImage,
|
||||
mediapath: filename,
|
||||
alt: '',
|
||||
caption: ''
|
||||
}];
|
||||
} catch (imageError: any) {
|
||||
console.error('Image processing error:', imageError);
|
||||
return fail(400, {
|
||||
error: `Failed to process image: ${imageError.message}`,
|
||||
errors: ['Image processing failed'],
|
||||
values: Object.fromEntries(formData)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// No image uploaded - use placeholder based on short_name
|
||||
recipeData.images = [{
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
let preamble = $state("");
|
||||
let addendum = $state("");
|
||||
let image_preview_url = $state("");
|
||||
let uploaded_image_filename = $state("");
|
||||
let selected_image_file = $state<File | null>(null);
|
||||
|
||||
// Translation workflow state
|
||||
let showTranslationWorkflow = $state(false);
|
||||
@@ -91,7 +91,7 @@
|
||||
let germanRecipeData = $derived({
|
||||
...card_data,
|
||||
...add_info,
|
||||
images: uploaded_image_filename ? [{ mediapath: uploaded_image_filename, alt: "", caption: "" }] : [],
|
||||
images: selected_image_file ? [{ mediapath: 'pending', alt: "", caption: "" }] : [],
|
||||
season: season_local,
|
||||
short_name: short_name.trim(),
|
||||
portions: portions_local,
|
||||
@@ -291,9 +291,14 @@ button.action_button {
|
||||
<form
|
||||
method="POST"
|
||||
bind:this={formElement}
|
||||
enctype="multipart/form-data"
|
||||
use:enhance={() => {
|
||||
submitting = true;
|
||||
return async ({ update }) => {
|
||||
return async ({ update, formData }) => {
|
||||
// Append the image file if one was selected
|
||||
if (selected_image_file) {
|
||||
formData.append('recipe_image', selected_image_file);
|
||||
}
|
||||
await update();
|
||||
submitting = false;
|
||||
};
|
||||
@@ -305,7 +310,6 @@ button.action_button {
|
||||
<input type="hidden" name="add_info_json" value={JSON.stringify(add_info)} />
|
||||
<input type="hidden" name="season" value={JSON.stringify(season_local)} />
|
||||
<input type="hidden" name="tags" value={JSON.stringify(card_data.tags)} />
|
||||
<input type="hidden" name="uploaded_image_filename" value={uploaded_image_filename} />
|
||||
|
||||
<!-- Translation data (added after approval) -->
|
||||
{#if translationData}
|
||||
@@ -319,7 +323,7 @@ button.action_button {
|
||||
<CardAdd
|
||||
bind:card_data
|
||||
bind:image_preview_url
|
||||
bind:uploaded_image_filename
|
||||
bind:selected_image_file
|
||||
short_name={short_name}
|
||||
/>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { IMAGE_DIR } from '$env/static/private';
|
||||
import { rename, access, unlink } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { constants } from 'fs';
|
||||
import { processAndSaveRecipeImage } from '$utils/imageProcessing';
|
||||
import {
|
||||
extractRecipeFromFormData,
|
||||
validateRecipeData,
|
||||
@@ -111,21 +112,37 @@ export const actions = {
|
||||
}
|
||||
|
||||
// Handle image scenarios
|
||||
const uploadedImage = formData.get('uploaded_image_filename')?.toString();
|
||||
const recipeImage = formData.get('recipe_image') as File | null;
|
||||
const keepExistingImage = formData.get('keep_existing_image') === 'true';
|
||||
const existingImagePath = formData.get('existing_image_path')?.toString();
|
||||
|
||||
if (uploadedImage) {
|
||||
// New image uploaded - delete old image files and use new one
|
||||
if (existingImagePath && existingImagePath !== uploadedImage) {
|
||||
if (recipeImage && recipeImage.size > 0) {
|
||||
try {
|
||||
// Process and save the new image
|
||||
const { filename } = await processAndSaveRecipeImage(
|
||||
recipeImage,
|
||||
recipeData.short_name,
|
||||
IMAGE_DIR
|
||||
);
|
||||
|
||||
// Delete old image files
|
||||
if (existingImagePath && existingImagePath !== filename) {
|
||||
await deleteRecipeImage(existingImagePath);
|
||||
}
|
||||
|
||||
recipeData.images = [{
|
||||
mediapath: uploadedImage,
|
||||
mediapath: filename,
|
||||
alt: existingImagePath ? (recipeData.images?.[0]?.alt || '') : '',
|
||||
caption: existingImagePath ? (recipeData.images?.[0]?.caption || '') : ''
|
||||
}];
|
||||
} catch (imageError: any) {
|
||||
console.error('Image processing error:', imageError);
|
||||
return fail(400, {
|
||||
error: `Failed to process image: ${imageError.message}`,
|
||||
errors: ['Image processing failed'],
|
||||
values: Object.fromEntries(formData)
|
||||
});
|
||||
}
|
||||
} else if (keepExistingImage && existingImagePath) {
|
||||
// Keep existing image
|
||||
recipeData.images = [{
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"https://bocken.org/static/rezepte/thumb/" +
|
||||
(data.recipe.images?.[0]?.mediapath || `${data.recipe.short_name}.webp`)
|
||||
);
|
||||
let uploaded_image_filename = $state("");
|
||||
let selected_image_file = $state<File | null>(null);
|
||||
|
||||
// Translation workflow state
|
||||
let showTranslationWorkflow = $state(false);
|
||||
@@ -109,10 +109,10 @@
|
||||
let currentRecipeData = $derived.by(() => {
|
||||
// Ensure we always have a valid images array with at least one item
|
||||
let recipeImages;
|
||||
if (uploaded_image_filename) {
|
||||
// New image uploaded
|
||||
if (selected_image_file) {
|
||||
// New image selected (will be uploaded on form submission)
|
||||
recipeImages = [{
|
||||
mediapath: uploaded_image_filename,
|
||||
mediapath: 'pending',
|
||||
alt: images[0]?.alt || "",
|
||||
caption: images[0]?.caption || ""
|
||||
}];
|
||||
@@ -371,9 +371,14 @@
|
||||
<form
|
||||
method="POST"
|
||||
bind:this={formElement}
|
||||
enctype="multipart/form-data"
|
||||
use:enhance={() => {
|
||||
submitting = true;
|
||||
return async ({ update }) => {
|
||||
return async ({ update, formData }) => {
|
||||
// Append the image file if one was selected
|
||||
if (selected_image_file) {
|
||||
formData.append('recipe_image', selected_image_file);
|
||||
}
|
||||
await update();
|
||||
submitting = false;
|
||||
};
|
||||
@@ -381,7 +386,7 @@
|
||||
>
|
||||
<!-- Hidden inputs for tracking -->
|
||||
<input type="hidden" name="original_short_name" value={old_short_name} />
|
||||
<input type="hidden" name="keep_existing_image" value={uploaded_image_filename ? "false" : "true"} />
|
||||
<input type="hidden" name="keep_existing_image" value={selected_image_file ? "false" : "true"} />
|
||||
<input type="hidden" name="existing_image_path" value={images[0]?.mediapath || `${old_short_name}.webp`} />
|
||||
|
||||
<!-- Hidden inputs for complex nested data -->
|
||||
@@ -390,7 +395,6 @@
|
||||
<input type="hidden" name="add_info_json" value={JSON.stringify(add_info)} />
|
||||
<input type="hidden" name="season" value={JSON.stringify(season_local)} />
|
||||
<input type="hidden" name="tags" value={JSON.stringify(card_data.tags)} />
|
||||
<input type="hidden" name="uploaded_image_filename" value={uploaded_image_filename} />
|
||||
<input type="hidden" name="datecreated" value={datecreated?.toString()} />
|
||||
|
||||
<!-- Translation data (updated after approval or marked needs_update) -->
|
||||
@@ -405,7 +409,7 @@
|
||||
<CardAdd
|
||||
bind:card_data
|
||||
bind:image_preview_url
|
||||
bind:uploaded_image_filename
|
||||
bind:selected_image_file
|
||||
short_name={short_name}
|
||||
/>
|
||||
|
||||
|
||||
79
src/utils/imageProcessing.ts
Normal file
79
src/utils/imageProcessing.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import path from 'path';
|
||||
import sharp from 'sharp';
|
||||
import { generateImageHashFromBuffer, getHashedFilename } from '$utils/imageHash';
|
||||
import { validateImageFile } from '$utils/imageValidation';
|
||||
|
||||
/**
|
||||
* Process and save recipe image with multiple versions (full, thumb, placeholder)
|
||||
* @param file - The image File object
|
||||
* @param name - The base name for the image (usually recipe short_name)
|
||||
* @param imageDir - The base directory where images are stored
|
||||
* @returns Object with hashedFilename and unhashedFilename
|
||||
*/
|
||||
export async function processAndSaveRecipeImage(
|
||||
file: File,
|
||||
name: string,
|
||||
imageDir: string
|
||||
): Promise<{ filename: string; unhashedFilename: string }> {
|
||||
// Comprehensive security validation
|
||||
const validationResult = await validateImageFile(file);
|
||||
if (!validationResult.valid) {
|
||||
throw new Error(validationResult.error || 'Invalid image file');
|
||||
}
|
||||
|
||||
// Convert File to Buffer for processing
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
// Generate content hash for cache busting
|
||||
const imageHash = generateImageHashFromBuffer(buffer);
|
||||
const hashedFilename = getHashedFilename(name, imageHash);
|
||||
const unhashedFilename = name + '.webp';
|
||||
|
||||
// Process image with Sharp - convert to WebP format
|
||||
// Save full size - both hashed and unhashed versions
|
||||
const fullBuffer = await sharp(buffer)
|
||||
.toFormat('webp')
|
||||
.webp({ quality: 90 }) // High quality for full size
|
||||
.toBuffer();
|
||||
|
||||
await sharp(fullBuffer).toFile(
|
||||
path.join(imageDir, 'rezepte', 'full', hashedFilename)
|
||||
);
|
||||
await sharp(fullBuffer).toFile(
|
||||
path.join(imageDir, 'rezepte', 'full', unhashedFilename)
|
||||
);
|
||||
|
||||
// Save thumbnail (800px width) - both hashed and unhashed versions
|
||||
const thumbBuffer = await sharp(buffer)
|
||||
.resize({ width: 800 })
|
||||
.toFormat('webp')
|
||||
.webp({ quality: 85 })
|
||||
.toBuffer();
|
||||
|
||||
await sharp(thumbBuffer).toFile(
|
||||
path.join(imageDir, 'rezepte', 'thumb', hashedFilename)
|
||||
);
|
||||
await sharp(thumbBuffer).toFile(
|
||||
path.join(imageDir, 'rezepte', 'thumb', unhashedFilename)
|
||||
);
|
||||
|
||||
// Save placeholder (20px width) - both hashed and unhashed versions
|
||||
const placeholderBuffer = await sharp(buffer)
|
||||
.resize({ width: 20 })
|
||||
.toFormat('webp')
|
||||
.webp({ quality: 60 })
|
||||
.toBuffer();
|
||||
|
||||
await sharp(placeholderBuffer).toFile(
|
||||
path.join(imageDir, 'rezepte', 'placeholder', hashedFilename)
|
||||
);
|
||||
await sharp(placeholderBuffer).toFile(
|
||||
path.join(imageDir, 'rezepte', 'placeholder', unhashedFilename)
|
||||
);
|
||||
|
||||
return {
|
||||
filename: hashedFilename,
|
||||
unhashedFilename: unhashedFilename
|
||||
};
|
||||
}
|
||||
BIN
static/fonts/LinLibertine_Rah.woff2
Normal file
BIN
static/fonts/LinLibertine_Rah.woff2
Normal file
Binary file not shown.
BIN
static/fonts/LinLibertine_minimal.woff2
Normal file
BIN
static/fonts/LinLibertine_minimal.woff2
Normal file
Binary file not shown.
BIN
static/fonts/UnifrakturMaguntia18.woff2
Normal file
BIN
static/fonts/UnifrakturMaguntia18.woff2
Normal file
Binary file not shown.
BIN
static/fonts/UnifrakturMaguntia19.woff2
Normal file
BIN
static/fonts/UnifrakturMaguntia19.woff2
Normal file
Binary file not shown.
BIN
static/fonts/UnifrakturMaguntia20.woff2
Normal file
BIN
static/fonts/UnifrakturMaguntia20.woff2
Normal file
Binary file not shown.
BIN
static/fonts/UnifrakturMaguntia21.woff2
Normal file
BIN
static/fonts/UnifrakturMaguntia21.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
static/fonts/devjavu_serif_minimal.woff2
Normal file
BIN
static/fonts/devjavu_serif_minimal.woff2
Normal file
Binary file not shown.
@@ -10,7 +10,9 @@ const config = {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter(),
|
||||
adapter: adapter({
|
||||
precompress: true // Enable brotli and gzip compression
|
||||
}),
|
||||
alias: {
|
||||
$models: 'src/models',
|
||||
$utils: 'src/utils'
|
||||
|
||||
@@ -6,4 +6,28 @@ export default defineConfig({
|
||||
allowedHosts: ["bocken.org"]
|
||||
},
|
||||
plugins: [sveltekit()],
|
||||
build: {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
drop_debugger: true
|
||||
}
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: (id) => {
|
||||
// Separate large dependencies into their own chunks
|
||||
if (id.includes('node_modules')) {
|
||||
if (id.includes('chart.js')) {
|
||||
return 'chart';
|
||||
}
|
||||
if (id.includes('@auth/sveltekit')) {
|
||||
return 'auth';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user