Compare commits

...

8 Commits

18 changed files with 282 additions and 736 deletions

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -8,3 +8,6 @@ sentry*
package.json package.json
node_modules node_modules
/public/docs /public/docs
Dockerfile
docker-compose.yml
nginx.conf

View File

@ -1,6 +1,7 @@
# 新聞解析 / News Analyze # 新聞解析 / News Analyze
![](https://hackatime-badge.hackclub.com/U087ATD163V/news-analyize) ![](https://hackatime-badge.hackclub.com/U087ATD163V/news-analyize) ![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/hpware/news-analyze?utm_source=oss&utm_medium=github&utm_campaign=hpware%2Fnews-analyze&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews) ![LICENSE](https://img.shields.io/github/license/hpware/news-analyze?style=flat) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/hpware/news-analyze/build_docker_image.yml)
App Design: [PDF Document](/design.pdf) App Design: [PDF Document](/design.pdf)
@ -95,4 +96,4 @@ https://news.yuanhau.com/api/home/lt?query=domestic
https://news.yuanhau.com/api/news/get/lt/${article url hash} https://news.yuanhau.com/api/news/get/lt/${article url hash}
or you can self host the API on your own server with a basic docker compose command. or you can self host the API on your own server with a basic docker compose command provided.

110
bun.lock
View File

@ -175,55 +175,55 @@
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="], "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.5", "", { "os": "android", "cpu": "arm" }, "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.5", "", { "os": "android", "cpu": "arm64" }, "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], "@esbuild/android-x64": ["@esbuild/android-x64@0.25.5", "", { "os": "android", "cpu": "x64" }, "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.5", "", { "os": "linux", "cpu": "arm" }, "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.5", "", { "os": "linux", "cpu": "x64" }, "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.5", "", { "os": "none", "cpu": "arm64" }, "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.5", "", { "os": "none", "cpu": "x64" }, "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.5", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
@ -1225,7 +1225,7 @@
"effect": ["effect@3.14.21", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-TKR7zfWcuZgEdWd+oIGA8LdREj/c+1Q0wz4pWqQtYT7VHnkW/QQEYCXgrDI5dT6lJgRTgyQAC1bAnpAf6MdjIA=="], "effect": ["effect@3.14.21", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-TKR7zfWcuZgEdWd+oIGA8LdREj/c+1Q0wz4pWqQtYT7VHnkW/QQEYCXgrDI5dT6lJgRTgyQAC1bAnpAf6MdjIA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.157", "", {}, "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w=="], "electron-to-chromium": ["electron-to-chromium@1.5.158", "", {}, "sha512-9vcp2xHhkvraY6AHw2WMi+GDSLPX42qe2xjYaVoZqFRJiOcilVQFq9mZmpuHEQpzlgGDelKlV7ZiGcmMsc8WxQ=="],
"emoji-datasource": ["emoji-datasource@15.0.1", "", {}, "sha512-aF5Q6LCKXzJzpG4K0ETiItuzz0xLYxNexR9qWw45/shuuEDWZkOIbeGHA23uopOSYA/LmeZIXIFsySCx+YKg2g=="], "emoji-datasource": ["emoji-datasource@15.0.1", "", {}, "sha512-aF5Q6LCKXzJzpG4K0ETiItuzz0xLYxNexR9qWw45/shuuEDWZkOIbeGHA23uopOSYA/LmeZIXIFsySCx+YKg2g=="],
@ -1263,7 +1263,7 @@
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], "esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@ -1299,7 +1299,7 @@
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
"execa": ["execa@9.5.3", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.0.0" } }, "sha512-QFNnTvU3UjgWFy8Ef9iDHvIdcgZ344ebkwYx4/KLbR+CKQA4xBaHzv+iRpp86QfMHP8faFQLh8iOc57215y4Rg=="], "execa": ["execa@9.6.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw=="],
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
@ -2545,7 +2545,7 @@
"zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="], "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="],
"zod": ["zod@3.25.28", "", {}, "sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q=="], "zod": ["zod@3.25.30", "", {}, "sha512-VolhdEtu6TJr/fzGuHA/SZ5ixvXqA6ADOG9VRcQ3rdOKmF5hkmcJbyaQjUH5BgmpA9gej++zYRX7zjSmdReIwA=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
@ -2599,6 +2599,8 @@
"@netlify/dev-utils/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="], "@netlify/dev-utils/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="],
"@netlify/zip-it-and-ship-it/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
"@netlify/zip-it-and-ship-it/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], "@netlify/zip-it-and-ship-it/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
"@netlify/zip-it-and-ship-it/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="], "@netlify/zip-it-and-ship-it/find-up": ["find-up@7.0.0", "", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="],
@ -2939,6 +2941,56 @@
"@netlify/dev-utils/find-up/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], "@netlify/dev-utils/find-up/unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="],
"@netlify/zip-it-and-ship-it/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
"@netlify/zip-it-and-ship-it/execa/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], "@netlify/zip-it-and-ship-it/execa/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="],
"@netlify/zip-it-and-ship-it/execa/human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], "@netlify/zip-it-and-ship-it/execa/human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],

View File

@ -70,12 +70,35 @@ watch(
}, },
{ immediate: true }, { immediate: true },
); );
const findRel = (title: string) => {
return tf(title);
}
const tf = (text: string) => {
const words = text.toLowerCase().split('');
// const words = text.toLowerCase().match(/[\u4e00-\u9fff]|[a-zA-Z0-9]+/g) || [];
const freqMap = new Map();
for (const word of words) {
if (word.trim()) {
freqMap.set(word, (freqMap.get(word) || 0) + 1);
}
}
const tfVector = <any>{};
for (const [term, count] of freqMap) {
tfVector[term] = count / words.length;
}
return tfVector;
}
</script> </script>
<template> <template>
<div class="justify-center align-center text-center"> <div class="justify-center align-center text-center">
<!--Tabs--> <!--Tabs-->
<div <div
class="sticky inset-x-0 top-0 bg-gray-300/90 backdrop-blur-xl border shadow-lg rounded-xl p-1 m-1 mt-0 justify-center align-center text-center z-[50] overflow-x-auto scrollbar-hide min-w-min whitespace-nowrap px-2" class="sticky inset-x-0 top-0 bg-gray-300/90 backdrop-blur-xl border shadow-lg rounded-xl p-1 m-1 mt-0 justify-center align-center text-center z-[50] overflow-x-auto scrollbar-xl min-w-min whitespace-nowrap px-2"
> >
<div class="gap-2 flex flex-row justify-center align-center text-center"> <div class="gap-2 flex flex-row justify-center align-center text-center">
<button <button
@ -88,6 +111,14 @@ watch(
</button> </button>
</div> </div>
</div> </div>
<Transition
enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__fadeOut"
>
<div v-if="switchTabs" class="absolute inset-x-0 top-12 p-2 m-12 z-[50]">
Loading...
</div>
</Transition>
<Transition <Transition
enter-active-class="animate__animated animate__fadeIn" enter-active-class="animate__animated animate__fadeIn"
leave-active-class="animate__animated animate__fadeOut" leave-active-class="animate__animated animate__fadeOut"
@ -119,10 +150,16 @@ watch(
}) })
}} }}
</p> </p>
<p :class="getCheckResult(item.title) ? 'hidden' : ''"> <div>
<h3 class="text-lg">類似文章</h3>
<div>{{ findRel(item.title) }}</div>
<!--<div v-for="item in findRel(item.title)">
{{ item }}
</div>-->
</div>
<!--<p :class="getCheckResult(item.title) ? 'hidden' : ''">
{{ item.shortDescription }} {{ item.shortDescription }}
</p> </p>-->
<!--ADD 類似 NEWS ARTICLES.-->
</div> </div>
</button> </button>
</div> </div>

View File

@ -9,6 +9,7 @@ const activateAiSummary = ref(false);
const isGenerating = ref(false); const isGenerating = ref(false);
const summaryText = ref(""); const summaryText = ref("");
const { locale } = useI18n(); const { locale } = useI18n();
const likeart = ref([]);
const aiSummary = async () => { const aiSummary = async () => {
activateAiSummary.value = true; activateAiSummary.value = true;
isGenerating.value = true; isGenerating.value = true;
@ -57,6 +58,15 @@ const aiSummary = async () => {
</button> </button>
<div v-else>{{ summaryText }}</div> <div v-else>{{ summaryText }}</div>
</div> </div>
<div class="flex flex-col bg-gray-500">
<div class="flex flex-row" v-for="item in likeart">
<img /><!--Image-->
<div class="flex flex-col">
<h2>title</h2>
<span>description</span>
</div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -2,9 +2,15 @@
// Great, there are now no errors ig // Great, there are now no errors ig
const emit = defineEmits(["windowopener", "error", "loadValue"]); const emit = defineEmits(["windowopener", "error", "loadValue"]);
import sha512 from "crypto-js/sha512"; import sha512 from "crypto-js/sha512";
import Input from "~/components/ui/input/Input.vue";
const userAccount = ref(""); const userAccount = ref("");
const userPassword = ref(""); const userPassword = ref("");
const error = ref(false);
const errormsg = ref("");
const success = ref(false);
const submitUserPassword = async () => { const submitUserPassword = async () => {
error.value = false;
errormsg.value = "";
// Encrypt password during transit // Encrypt password during transit
const password = sha512(userPassword.value).toString(); const password = sha512(userPassword.value).toString();
@ -21,46 +27,53 @@ const submitUserPassword = async () => {
}); });
const res = await sendData.json(); const res = await sendData.json();
if (res.status === "ok") { if (!res.error) {
// Store the token in local storage error.value = false;
localStorage.setItem("token", res.token); localStorage.setItem("token", res.token);
// Redirect to the home page
window.location.href = "/";
success.value = true; success.value = true;
console.log(res);
userAccount.value = "";
} else { } else {
alert("Login failed");
error.value = true; error.value = true;
errormsg.value = res.error;
} }
// Clear the input fields
userAccount.value = "";
userPassword.value = ""; userPassword.value = "";
}; };
</script> </script>
<template> <template>
<div class="flex flex-col items-center justify-center h-full"> <div class="">
<form <form
class="flex flex-col items-center justify-center h-full" class="flex flex-col items-center justify-center h-full"
@submit.prevent="submitUserPassword" @submit.prevent="submitUserPassword"
v-if="!success" v-if="!success"
> >
<div class="text-xl mb-4 text-bold">Login / Register</div> <span class="text-2xl text-bold mb-0">Login / Register</span>
<span class="mb-4 text-sm mt-0"
<input >We will create a account for you if you don't have one.</span
type="text" >
placeholder="Username" <div class="">
class="mb-2 p-2 border rounded" <Input
v-model="userAccount" type="text"
/> placeholder="Username"
<input class="mb-2 p-2 border rounded"
type="password" v-model="userAccount"
placeholder="Password" required
class="p-2 border rounded mb-2" />
v-model="userPassword" <Input
/> type="password"
placeholder="Password"
class="p-2 border rounded mb-2"
v-model="userPassword"
required
/>
</div>
<span v-if="error" class="text-red-600 text-xs m-2"
>Error: {{ errormsg }}</span
>
<button class="bg-black text-white p-2 rounded transition duration-200"> <button class="bg-black text-white p-2 rounded transition duration-200">
Log In Log In
</button> </button>
</form> </form>
<div v-else></div> <div v-else>Hi! ${user}</div>
</div> </div>
</template> </template>

View File

@ -47,7 +47,7 @@
"interface": "Use a desktop like interface, the one that you already got used to.", "interface": "Use a desktop like interface, the one that you already got used to.",
"documentation": "We provide a documentation for you to learn how to use the app.", "documentation": "We provide a documentation for you to learn how to use the app.",
"opensource": "This platform is open source! minus the database part, but I'm going to try!", "opensource": "This platform is open source! minus the database part, but I'm going to try!",
"apis": "We develop apis that does not exist before, like the line today one, but it just needs some revese engineering to find the apis behind it." "apis": "We use api's that are made via reverse engineering."
} }
} }
}, },
@ -73,5 +73,16 @@
"terminal": "Terminal", "terminal": "Terminal",
"aboutNewsOrg": "About this news organization", "aboutNewsOrg": "About this news organization",
"newsview": "News View" "newsview": "News View"
},
"tools": {
"title": "Tools",
"name": {
"checkweirdkeywords": "Check Sensivite Keywords",
"noadlinetoday": "LINE Today but no ads"
},
"content": {
"checkweirdkeywords": "This tool check if the news title has weird 18+ content.",
"noadlinetoday": "Provides free, no ads Line today."
}
} }
} }

View File

@ -73,5 +73,16 @@
"terminal": "終端機", "terminal": "終端機",
"aboutNewsOrg": "關於這個新聞來源", "aboutNewsOrg": "關於這個新聞來源",
"newsview": "新聞" "newsview": "新聞"
},
"tools":{
"title": "工具",
"name": {
"checkweirdkeywords": "檢查偏色情標體",
"noadlinetoday": "無廣告新聞"
},
"content": {
"checkweirdkeywords": "這個工具檢查新聞記者是不是使用偏色情的標體 (台灣的很愛用)",
"noadlinetoday": "提供無廣告的LINE Today 新聞"
}
} }
} }

View File

@ -1,211 +0,0 @@
<script setup lang="ts">
// No layout
// interfaces
interface currentNavBarInterface {
name: string;
icon: string;
action: any;
flash: boolean;
windowAssociated: string;
}
// Import plugins
import { gsap } from "gsap";
import { TextPlugin } from "gsap/TextPlugin";
import { createApp } from "vue";
gsap.registerPlugin(TextPlugin);
// Import Windows
import SignIn from "~/components/app/windows/login.vue";
// Import Shadcn/UI components
import AlertComponent from "~/components/ui/alert/Alert.vue";
import ButtonComponent from "~/components/ui/button/Button.vue";
import DialogComponent from "~/components/ui/dialog/Dialog.vue";
import ProgressComponent from "~/components/ui/progress/Progress.vue";
import HoverCardComponent from "~/components/ui/hover-card/HoverCard.vue";
// Icons
import {
ComputerDesktopIcon,
UserIcon,
LanguageIcon,
ChevronRightIcon,
} from "@heroicons/vue/24/outline";
// i18n
const { t, locale, locales } = useI18n();
const switchLocalePath = useSwitchLocalePath();
const localePath = useLocalePath();
// Router
const router = useRouter();
const route = useRoute();
// values
const popMessage = ref(null);
const menuOpen = ref(false);
const langMenuOpen = ref(false);
const lang = ref(locale.value);
const alertOpen = ref(false);
const currentNavBar = ref<currentNavBarInterface[]>([]);
// Date
const currentDate = ref(
new Date().toLocaleDateString("zh-TW", {
month: "2-digit",
day: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}),
);
onMounted(() => {
setInterval(() => {
currentDate.value = new Date().toLocaleDateString("zh-TW", {
month: "2-digit",
day: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
});
}, 1000);
});
// functions
const openWindow = (windowName?: string) => {
if (windowName === "leave") {
router.push(localePath("/home"));
}
console.log(windowName);
menuOpen.value = false;
};
const unMinWindow = (windowName?: string) => {
console.log(windowName);
};
// menus
const menuItems = [
{ name: "Hot News", windowName: "hotnews" },
{ name: "News", windowName: "news" },
{ name: "Sources", windowName: "sources" },
{ name: "About This Website", windowName: "about" },
{ name: "Settings", windowName: "settings" },
{ name: "Leave", windowName: "leave" },
];
const toggleMenu = () => {
menuOpen.value = !menuOpen.value;
};
// Lang Menu
const toggleLangMenu = () => {
langMenuOpen.value = !langMenuOpen.value;
};
</script>
<template>
<div
class="absolute inset-x-0 flex flex-row px-2 py-1 bg-[#7D7C7C]/70 text-white justify-between align-center text-center z-50"
>
<!--Menu container-->
<div class="flex flex-row g-2 text-gray-400 text-white z-9999">
<button
@click="toggleMenu"
class="w-8 h-8 text-white hover:text-blue-500 transition-all duration-100 flex flex-row"
>
<ComputerDesktopIcon />
</button>
<span class="ml-1 mr-2 text-[20px]">|</span>
<!--navbar icons for min and max application window-->
<button
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
></button>
<div
v-for="item in currentNavBar"
:key="item.name"
class="flex flex-row items-center gap-x-2 hover:bg-gray-100 transition-all duration-100 px-4 py-2 cursor-pointer"
>
<button
@click="unMinWindow(item.windowAssociated)"
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
>
<span>{{ item.name }}</span>
<span
v-if="item.flash"
class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"
></span>
<span v-if="item.icon" :class="item.icon"> </span>
</button>
</div>
</div>
<div class="text-center align-middle justify-center text-white">
{{ currentDate }}
</div>
</div>
<div class="w-full h-[2.5em]"></div>
<!--Menu-->
<Transition
enter-active-class="animate__animated animate__fadeInDown animate_fast03"
leave-active-class="animate__animated animate__fadeOutUp animate_fast03"
>
<div
class="m-2 p-2 bg-gray-800 shadow-lg w-fit rounded-[10px] v-9998"
v-if="menuOpen"
>
<div v-for="item in menuItems" :key="item.name" class="">
<button
@click="openWindow(item.windowName)"
class="flex flex-row items-center gap-x-2 text-gray-400 hover:text-gray-600 transition-all duration-100"
>
<span>{{ item.name }}</span>
<ChevronRightIcon class="w-4 h-4 justify-center align-center" />
</button>
</div>
</div>
</Transition>
<!--Main desktop contents-->
<div
class="flex flex-col justify-center align-center text-center absolute w-full h-screen inset-x-0 inset-y-0 z-[-1]"
id="desktop"
></div>
<slot />
<!--Footer-->
<div
class="absolute w-[calc(100% - 5px)] inset-x-0 bottom-0 mx-[1.5px] p-3 justify-between align-center flex flex-row"
>
<div class="">
<!--Lang-->
<span>Lang: </span>
<span class="text-lg">{{ t("localeflag") }}</span
>&nbsp;
<button
class="w-4 h-4 hover:text-blue-200 transition-all duration-100"
@click="toggleLangMenu"
>
<LanguageIcon />
</button>
</div>
<div class="gap-2 flex flex-row">
<!--版權資訊-->
<span class="text-sm">1.0.0</span>
<span class="text-sm">|</span>
<span class="text-sm">MIT License</span>
<span class="text-sm">|</span>
<span class="text-sm">{{ new Date().getFullYear() }} &copy yh</span>
</div>
<div class="">
<button
@click="openWindow('login')"
class="w-8 h-8 text-gray-400 flex flex-row"
>
<UserIcon
class="w-8 h-8 text-gray-400 hover:text-blue-500 transition-all duration-100"
/>
</button>
</div>
</div>
</template>

View File

@ -1,14 +1,27 @@
# Run :%s/news.yuanhau.com/your.domain.here/ in vim to replace news.yuanhau.com to your domain.
# Caddy is currently not supported.
# Before getting the SSL cert for your website comment the the few with the lines of #commentbeforessl
#
#
#
# If you want to change the log dir use :%s/news_analyze/{your-file-here}
#
#
#
#
#
#
server { server {
listen 80; listen 80;
listen 443 ssl; listen 443 ssl; #commentbeforessl
ssl_certificate /etc/letsencrypt/live/news.yuanhau.com/fullchain.pem; ssl_certificate /etc/letsencrypt/live/news.yuanhau.com/fullchain.pem; #commentbeforessl
ssl_certificate_key /etc/letsencrypt/live/news.yuanhau.com/privkey.pem; ssl_certificate_key /etc/letsencrypt/live/news.yuanhau.com/privkey.pem; #commentbeforessl
server_name news.yuanhau.com; server_name news.yuanhau.com;
access_log /var/log/nginx/news_analyze.access.log; access_log /var/log/nginx/news_analyze.access.log;
error_log /var/log/nginx/news_analyze.error.log; error_log /var/log/nginx/news_analyze.error.log;
location / { location / {
proxy_pass http://127.0.0.1:36694; proxy_pass http://127.0.0.1:36694; # Traefik port.
proxy_set_header X-real-IP $remote_addr; proxy_set_header X-real-IP $remote_addr;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for; proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;

View File

@ -32,7 +32,7 @@ import { TextPlugin } from "gsap/TextPlugin";
gsap.registerPlugin(TextPlugin); gsap.registerPlugin(TextPlugin);
// Import Windows // Import Windows
import LoginWindow from "~/components/app/windows/login.vue"; import UserWindow from "~/components/app/windows/user.vue";
import HotNewsWindow from "~/components/app/windows/hotnews.vue"; import HotNewsWindow from "~/components/app/windows/hotnews.vue";
import SourcesWindow from "~/components/app/windows/sources.vue"; import SourcesWindow from "~/components/app/windows/sources.vue";
import AboutWindow from "~/components/app/windows/about.vue"; import AboutWindow from "~/components/app/windows/about.vue";
@ -92,7 +92,7 @@ const menuItems = [
{ name: t("app.about"), windowName: "about" }, { name: t("app.about"), windowName: "about" },
{ name: t("app.terminal"), windowName: "tty" }, { name: t("app.terminal"), windowName: "tty" },
{ name: t("app.settings"), windowName: "settings" }, { name: t("app.settings"), windowName: "settings" },
{ name: t("app.login"), windowName: "login" }, { name: t("app.login"), windowName: "user" },
{ name: t("app.leave"), windowName: "leave" }, { name: t("app.leave"), windowName: "leave" },
]; ];
@ -106,10 +106,10 @@ const associAppWindow = [
height: "500px", height: "500px",
}, },
{ {
name: "login", name: "user",
id: "2", id: "2",
title: t("app.login"), title: t("app.login"),
component: LoginWindow, component: UserWindow,
}, },
{ {
name: "sources", name: "sources",
@ -340,6 +340,7 @@ const toggleMinWindow = (windowUUId: string) => {
useSeoMeta({ useSeoMeta({
title: "Desktop", title: "Desktop",
}); });
watchEffect(() => { watchEffect(() => {
useSeoMeta({ useSeoMeta({
title: titleAppName.value + " - Desktop", title: titleAppName.value + " - Desktop",
@ -396,9 +397,9 @@ const openArticles = async (slug: string) => {
}; };
const getStaticArticleId = () => { const getStaticArticleId = () => {
storeStaticArticleId.value += 1 storeStaticArticleId.value += 1;
return storeStaticArticleId.value return storeStaticArticleId.value;
} };
</script> </script>
<template> <template>
<div v-if="changeLangAnimation"> <div v-if="changeLangAnimation">

View File

@ -1,428 +0,0 @@
<script setup lang="ts">
// Make the Desktop funner // TODO
// No layout
definePageMeta({
layout: false,
});
// interfaces
interface currentNavBarInterface {
name: string;
icon: string;
action: any;
flash: boolean;
windowAssociated: string;
minimized: boolean;
}
interface associAppWindowInterface {
name: string;
id: string;
absoluteId: string;
title: string;
component: any;
width: string;
height: string;
black: boolean;
}
// Import plugins
import { v4 as uuidv4 } from "uuid";
import { gsap } from "gsap";
import { TextPlugin } from "gsap/TextPlugin";
gsap.registerPlugin(TextPlugin);
// Import Windows
import LoginWindow from "~/components/app/windows/login.vue";
import HotNewsWindow from "~/components/app/windows/hotnews.vue";
import SourcesWindow from "~/components/app/windows/sources.vue";
import AboutWindow from "~/components/app/windows/about.vue";
import ChatbotWindow from "~/components/app/windows/chatbot.vue";
import AboutNewsOrgWindow from "~/components/app/windows/aboutNewsOrg.vue";
import TTYWindow from "~/components/app/windows/tty.vue";
import FavStaredWindow from "~/components/app/windows/fav.vue";
import Error404Window from "~/components/app/windows/error404.vue";
import NewsViewWindow from "~/components/app/windows/newsView.vue";
// Import Icons
import {
ComputerDesktopIcon,
UserIcon,
LanguageIcon,
ChevronRightIcon,
} from "@heroicons/vue/24/outline";
// i18n
const { t, locale, locales } = useI18n();
const switchLocalePath = useSwitchLocalePath();
const localePath = useLocalePath();
// Router
const router = useRouter();
const route = useRoute();
// values
const popMessage = ref(null);
const menuOpen = ref(false);
const langMenuOpen = ref(false);
const lang = ref(locale.value);
const alertOpen = ref(false);
const currentNavBar = ref<currentNavBarInterface[]>([]);
const bootingAnimation = ref(true);
const activeWindows = ref<associAppWindowInterface[]>([]);
const openApp = ref();
const openAppId = ref();
const openAppNameQuery = ref();
const currentOpenAppId = ref(0);
const progress = ref(0);
const titleAppName = ref("Desktop");
const openingAppViaAnApp = ref(false);
const passedValues = ref();
const globalWindowVal = ref(new Map());
const changeLangAnimation = ref(false);
// Key Data
const menuItems = [
{ name: t("app.hotnews"), windowName: "hotnews" },
{ name: t("app.news"), windowName: "news" },
{ name: t("app.sources"), windowName: "sources" },
{ name: t("app.starred"), windowName: "starred" },
{ name: t("app.chatbot"), windowName: "chatbot" },
{ name: t("app.about"), windowName: "about" },
{ name: t("app.terminal"), windowName: "tty" },
{ name: t("app.settings"), windowName: "settings" },
{ name: t("app.login"), windowName: "login" },
{ name: t("app.leave"), windowName: "leave" },
];
const associAppWindow = [
{
name: "hotnews",
id: "1",
title: t("app.hotnews"),
component: HotNewsWindow,
width: "700px",
height: "500px",
},
{
name: "login",
id: "2",
title: t("app.login"),
component: LoginWindow,
},
{
name: "sources",
id: "3",
title: t("app.sources"),
component: SourcesWindow,
width: "700px",
height: "500px",
},
{
name: "about",
id: "4",
title: t("app.about"),
component: AboutWindow,
},
{
name: "settings",
id: "5",
title: t("app.settings"),
component: Error404Window,
},
{
name: "news",
id: "6",
title: t("app.news"),
component: Error404Window,
},
{
name: "starred",
id: "7",
title: t("app.starred"),
component: FavStaredWindow,
},
{
name: "chatbot",
id: "8",
title: t("app.chatbot"),
component: ChatbotWindow,
width: "400px",
height: "600px",
},
{
name: "error404",
id: "9",
title: t("app.error404"),
component: Error404Window,
},
{
name: "aboutNewsOrg",
id: "10",
title: t("app.aboutNewsOrg"),
component: AboutNewsOrgWindow,
},
{
name: "tty",
id: "11",
title: t("app.terminal"),
component: TTYWindow,
black: true,
},
{
name: "newsView",
id: "12",
title: t("app.newsview"),
component: NewsViewWindow,
},
];
// Date
const currentDate = ref(
new Date().toLocaleDateString("zh-TW", {
month: "2-digit",
day: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}),
);
onMounted(() => {
setInterval(() => {
currentDate.value = new Date().toLocaleDateString("zh-TW", {
month: "2-digit",
day: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
});
}, 1000);
});
// functions
onMounted(() => {
associAppWindow.forEach((window) => {
globalWindowVal.value.set(window.name, {
id: window.id,
title: window.title,
windowCount: 1,
});
});
});
const openWindow = (windowName?: string) => {
if (windowName === "leave") {
if (confirm("Are you sure?")) {
router.push(localePath("/home"));
} else {
return;
}
} else {
if (windowName) findAndOpenWindow(windowName);
}
menuOpen.value = false;
};
const unMinWindow = (windowName?: string) => {
console.log(windowName);
};
// menus
const toggleMenu = () => {
menuOpen.value = !menuOpen.value;
};
// Lang Menu
const toggleLangMenu = () => {
langMenuOpen.value = !langMenuOpen.value;
};
// ?openapp= component
onMounted(async () => {
openApp.value = route.query.openapp;
openAppId.value = route.query.id;
if (openApp.value) {
// DO NOT REMOVE THIS FUNCTION!! IT WILL ABSOLUTELY 100% FAIL!
if (openApp.value === "newsView") {
return;
}
openWindow(openApp.value);
}
});
const findAndOpenWindow = (windowName: string) => {
const app = associAppWindow.find((app) => app.name === windowName);
// Prevent dual logins
if (
windowName === "login" &&
activeWindows.value.some((window) => window.name === "login")
) {
return;
}
// Prevent dual about
if (
windowName === "about" &&
activeWindows.value.some((window) => window.name === "about")
) {
return;
}
if (app) {
// Use shallowRef for better performance with components
const windowComponent = shallowRef(app.component);
titleAppName.value = app.title;
const abosluteId = uuidv4();
activeWindows.value.push({
id: currentOpenAppId.value,
absoluteId: abosluteId,
component: windowComponent,
name: windowName,
title: app.title,
width: app.width || "600px",
height: app.height || "400px",
black: app.black || false,
});
currentOpenAppId.value++;
// Add to navbar
const windowNameVal2 =
globalWindowVal.value.get(windowName).windowCount === 1
? windowName
: windowName +
"(" +
globalWindowVal.value.get(windowName).windowCount +
")";
currentNavBar.value.push({
name: windowNameVal2,
icon: "anything",
action: "idk",
flash: true,
windowAssociated: abosluteId,
minimized: false,
});
globalWindowVal.value.get(windowName).windowCount++;
}
};
const obtainTopWindowPosition = (windowId: string) => {
if (!openingAppViaAnApp.value) {
const windowIndex = activeWindows.value.findIndex(
(window) => window.id === windowId,
);
if (windowIndex !== -1) {
const [window] = activeWindows.value.splice(windowIndex, 1);
titleAppName.value = window.title;
activeWindows.value.push(window);
}
}
};
const closeWindow = (windowId: string, windowAID: string) => {
activeWindows.value = activeWindows.value.filter(
(window) => window.id !== windowId,
);
currentNavBar.value = currentNavBar.value.filter(
(window) => window.windowAssociated !== windowAID,
);
console.log("activeWindows.value", activeWindows.value);
};
const openNewWindowViaApp = (windowId: string) => {
openingAppViaAnApp.value = true;
findAndOpenWindow(windowId);
setTimeout(() => {
openingAppViaAnApp.value = false;
}, 1000);
};
const maxWindow = (windowUUId: string) => {
const windowIndex = activeWindows.value.findIndex(
(window) => window.absoluteId === windowUUId,
);
console.log(windowIndex);
};
const toggleMinWindow = (windowUUId: string) => {
const windowIndex = "";
};
// Title
useSeoMeta({
title: "Desktop",
});
watchEffect(() => {
useSeoMeta({
title: titleAppName.value + " - Desktop",
});
});
// Booting animation
onMounted(() => {
const changeLang = route.query.changelang;
if (changeLang) {
bootingAnimation.value = false;
changeLangAnimation.value = true;
}
setTimeout(() => {
changeLangAnimation.value = false;
}, 4000);
});
onMounted(() => {
// booting animation bypass
const bootingHeaderParams = route.query.bypass;
if (bootingHeaderParams) {
bootingAnimation.value = false;
}
});
onMounted(() => {
if (bootingAnimation.value) {
gsap.to(popMessage.value, {
duration: 0.5,
text: t("app.booting"),
ease: "none",
});
}
setTimeout(() => {
bootingAnimation.value = false;
}, 2000);
});
watchEffect((cleanupFn) => {
const tier = setTimeout(() => (progress.value = 10), Math.random() * 50);
const timer = setTimeout(() => (progress.value = 30), Math.random() * 100);
const timmer = setTimeout(() => (progress.value = 70), Math.random() * 150);
const timmmer = setTimeout(() => (progress.value = 100), 1800);
cleanupFn(() => clearTimeout(tier));
cleanupFn(() => clearTimeout(timer));
cleanupFn(() => clearTimeout(timmer));
cleanupFn(() => clearTimeout(timmmer));
});
</script>
<template>
<div v-if="changeLangAnimation">
<div
class="flex flex-col justify-center align-center text-center absolute w-full h-screen inset-0 overscroll-none bg-gray-600/50 z-[999999]"
>
<span>{{ t("app.changelangmessage") }}</span>
</div>
</div>
<div v-if="bootingAnimation">
<div
class="flex flex-col justify-center align-center text-center absolute w-full h-screen inset-0 overscroll-none"
>
<Progress
v-model="progress"
class="w-3/5 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gradient-to-r from-[#2a7b9b] then-[#8d57c7] to-[#ed4242]"
/>
<br />
<span class="text-xl text-bold mt-3 text-sky-200">{{
t("app.launchtext")
}}</span>
</div>
</div>
</template>

View File

@ -2,27 +2,31 @@
const localePath = useLocalePath(); const localePath = useLocalePath();
// Import Icons // Import Icons
import { SearchXIcon, CircleSlash2Icon } from "lucide-vue-next"; import { SearchXIcon, CircleSlash2Icon } from "lucide-vue-next";
const { t } = useI18n();
// Array // Array
const tools = [ const tools = [
{ {
name: "檢查偏色情標體", name: t("tools.name.checkweirdkeywords"),
content: "這個工具檢查新聞記者是不是使用偏色情的標體 (台灣的很愛用)", content: t("tools.content.checkweirdkeywords"),
icon: SearchXIcon, icon: SearchXIcon,
go: localePath("/tools/checkweirdkeywords"), go: localePath("/tools/checkweirdkeywords"),
}, },
{ {
name: "無廣告新聞", name: t("tools.name.noadlinetoday"),
content: "提供無廣告的LINE Today 新聞", content: t("tools.content.noadlinetoday"),
icon: CircleSlash2Icon, icon: CircleSlash2Icon,
go: localePath("/tools/freelinetoday"), go: localePath("/tools/freelinetoday"),
}, },
]; ];
useSeoMeta({
title: `${t("tools.title")}`
})
</script> </script>
<template> <template>
<div <div
class="justify-center align-center absolute inset-0 flex flex-col w-full h-screen" class="justify-center align-center absolute inset-0 flex flex-col w-full h-screen"
> >
<h1 class="text-5xl text-bold m-4 text-center">Tools</h1> <h1 class="text-5xl text-bold m-4 text-center">{{ t("tools.title") }}</h1>
<div <div
class="justify-center align-center gap-2 p-2 w-full flex flex-row flex-wrap relative" class="justify-center align-center gap-2 p-2 w-full flex flex-row flex-wrap relative"
> >

View File

@ -13,6 +13,7 @@ export default defineEventHandler(async (event) => {
} }
const body = await readBody(event); const body = await readBody(event);
const { username, password } = body; const { username, password } = body;
console.log(password);
if (!username || !password) { if (!username || !password) {
return { return {
error: "NO_USER_AND_PASSWORD_SUBMITED", error: "NO_USER_AND_PASSWORD_SUBMITED",
@ -25,45 +26,58 @@ export default defineEventHandler(async (event) => {
}; };
} }
// Server side hashing // Server side hashing
const hashedPassword = await argon2.hash(salt, password);
// Check if user exists, if not, create a user // Check if user exists, if not, create a user
try { try {
console.log(username);
const fetchUserInfo = await sql` const fetchUserInfo = await sql`
select * from users select * from users
where user = ${username}`; where username = ${username}`;
if (!fetchUserInfo) { console.log(fetchUserInfo[0]);
if (fetchUserInfo.length === 0) {
const hashedPassword = await argon2.hash(salt + password);
const createNewUser = await sql` const createNewUser = await sql`
insert into users (uuid, username, passwordhash) insert into users (uuid, username, passwordhash)
values (${uuidv4()}, ${username}, ${hashedPassword}) values (${uuidv4()}, ${username}, ${hashedPassword})
`; `;
if (!createNewUser) { console.log(createNewUser);
if (fetchUserInfo.length !== 0) {
return { return {
error: "CANNOT_CREATE_NEW_USER", error: "CANNOT_CREATE_NEW_USER",
}; };
} }
const newToken = uuidv4();
//const newToken64 = atob(newToken);
return {
user: fetchUserInfo,
token: newToken,
};
} else { } else {
if (fetchUserInfo.password !== hashedPassword) { const isValid = await argon2.verify(
fetchUserInfo[0].passwordhash,
salt + password,
);
if (!isValid) {
return { return {
error: "PASSWORD_NO_MATCH", error: "PASSWORD_NO_MATCH",
}; };
} }
const newToken = uuidv4();
const newToken64 = atob(newToken);
const saveNewToken = await sql`
insert into usertokens
`;
if (!saveNewToken) {
return {
error: "CANNOT_CREATE_NEW_TOKEN",
};
}
return {
user: fetchUserInfo.user,
token: newToken,
};
} }
const newToken = uuidv4();
const newToken64 = btoa(newToken);
const fetchUserInfoAgain = await sql`
select * from users
where username = ${username}`;
/*await sql`
INSERT INTO usertokens (user, token)
VALUES (${fetchUserInfo[0].username}, ${newToken64})
`;*/
return {
user: fetchUserInfoAgain,
token: newToken,
};
} catch (e) { } catch (e) {
console.log(e);
return { return {
error: "UNABLE_TO_PROCESS", error: "UNABLE_TO_PROCESS",
}; };

View File

@ -0,0 +1,14 @@
export default defineEventHandler((event) => {
const query = getQuery(event);
const toolCall = query.tool;
const forwardCall = query.forward;
if (toolCall) {
const buildUrl = "/desktop?openapp=" + toolCall;
return sendRedirect(event, buildUrl, 302);
}
if (forwardCall) {
const buildUrl = "/" + forwardCall;
return sendRedirect(event, buildUrl, 302);
}
return sendRedirect(event, "/", 302)
})

View File

@ -25,8 +25,8 @@ async function lineToday(slug: string) {
.text() .text()
.replaceAll("\n", "") .replaceAll("\n", "")
.replace(" ", ""); .replace(" ", "");
const paragraph = []; const paragraph = <any[]>[];
const images = []; const images = <any[]>[];
html("article.news-content") html("article.news-content")
.contents() .contents()
.each((i, element) => { .each((i, element) => {

View File

@ -46,3 +46,4 @@
--chart-5: 340 75% 55%; --chart-5: 340 75% 55%;
} }
} }
@plugin 'tailwind-scrollbar';