This commit is contained in:
Ou 2024-09-29 20:57:24 +08:00
parent 51429cdd54
commit 0f8ec14ddf
36 changed files with 992 additions and 206 deletions

View File

@ -7,7 +7,7 @@
<title>NewsNow</title> <title>NewsNow</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="app"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/app.tsx"></script>
</body> </body>
</html> </html>

View File

@ -1,5 +1,5 @@
{ {
"name": "vite-project", "name": "newsnow",
"type": "module", "type": "module",
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
@ -9,25 +9,31 @@
"build": "vite build", "build": "vite build",
"nitro": "nitro build", "nitro": "nitro build",
"lint": "eslint .", "lint": "eslint .",
"test": "vitest",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@tanstack/react-query": "^5.56.2", "@tanstack/react-query": "^5.56.2",
"@tanstack/react-query-devtools": "^5.58.0", "@tanstack/react-query-devtools": "^5.58.0",
"@tanstack/react-router": "^1.58.9", "@tanstack/react-router": "^1.58.9",
"@tanstack/router-devtools": "^1.58.9", "@tanstack/router-devtools": "^1.58.9",
"@tanstack/virtual-file-routes": "^1.56.0",
"@unocss/reset": "^0.62.4", "@unocss/reset": "^0.62.4",
"array-differences": "^3.0.2",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
"framer-motion": "^11.8.0",
"h3": "^1.12.0", "h3": "^1.12.0",
"jotai": "^2.10.0", "jotai": "^2.10.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-intersection-observer": "^9.13.1",
"react-use": "^17.5.1", "react-use": "^17.5.1",
"vite-tsconfig-paths": "^5.0.1",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@analogjs/vite-plugin-nitro": "^1.8.1",
"@eslint-react/eslint-plugin": "^1.14.2", "@eslint-react/eslint-plugin": "^1.14.2",
"@iconify-json/ph": "^1.2.0", "@iconify-json/ph": "^1.2.0",
"@ourongxing/eslint-config": "3.2.3-beta.4", "@ourongxing/eslint-config": "3.2.3-beta.4",
@ -45,6 +51,8 @@
"typescript-eslint": "^8.7.0", "typescript-eslint": "^8.7.0",
"unocss": "^0.62.4", "unocss": "^0.62.4",
"vite": "^5.4.8", "vite": "^5.4.8",
"vite-plugin-with-nitro": "0.0.0-beta.4" "vite-plugin-with-nitro": "0.0.0-beta.4",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^2.1.1"
} }
} }

414
pnpm-lock.yaml generated
View File

@ -8,6 +8,15 @@ importers:
.: .:
dependencies: dependencies:
'@dnd-kit/core':
specifier: ^6.1.0
version: 6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@dnd-kit/sortable':
specifier: ^8.0.0
version: 8.0.0(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@dnd-kit/utilities':
specifier: ^3.2.2
version: 3.2.2(react@18.3.1)
'@tanstack/react-query': '@tanstack/react-query':
specifier: ^5.56.2 specifier: ^5.56.2
version: 5.56.2(react@18.3.1) version: 5.56.2(react@18.3.1)
@ -20,12 +29,21 @@ importers:
'@tanstack/router-devtools': '@tanstack/router-devtools':
specifier: ^1.58.9 specifier: ^1.58.9
version: 1.58.9(@tanstack/react-router@1.58.9(@tanstack/router-generator@1.58.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.58.9(@tanstack/react-router@1.58.9(@tanstack/router-generator@1.58.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tanstack/virtual-file-routes':
specifier: ^1.56.0
version: 1.56.0
'@unocss/reset': '@unocss/reset':
specifier: ^0.62.4 specifier: ^0.62.4
version: 0.62.4 version: 0.62.4
array-differences:
specifier: ^3.0.2
version: 3.0.2
clsx:
specifier: ^2.1.1
version: 2.1.1
dayjs:
specifier: ^1.11.13
version: 1.11.13
framer-motion:
specifier: ^11.8.0
version: 11.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
h3: h3:
specifier: ^1.12.0 specifier: ^1.12.0
version: 1.12.0 version: 1.12.0
@ -38,19 +56,16 @@ importers:
react-dom: react-dom:
specifier: ^18.3.1 specifier: ^18.3.1
version: 18.3.1(react@18.3.1) version: 18.3.1(react@18.3.1)
react-intersection-observer:
specifier: ^9.13.1
version: 9.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-use: react-use:
specifier: ^17.5.1 specifier: ^17.5.1
version: 17.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 17.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
vite-tsconfig-paths:
specifier: ^5.0.1
version: 5.0.1(typescript@5.6.2)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0))
zod: zod:
specifier: ^3.23.8 specifier: ^3.23.8
version: 3.23.8 version: 3.23.8
devDependencies: devDependencies:
'@analogjs/vite-plugin-nitro':
specifier: ^1.8.1
version: 1.8.1(magicast@0.3.5)
'@eslint-react/eslint-plugin': '@eslint-react/eslint-plugin':
specifier: ^1.14.2 specifier: ^1.14.2
version: 1.14.2(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) version: 1.14.2(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)
@ -59,7 +74,7 @@ importers:
version: 1.2.0 version: 1.2.0
'@ourongxing/eslint-config': '@ourongxing/eslint-config':
specifier: 3.2.3-beta.4 specifier: 3.2.3-beta.4
version: 3.2.3-beta.4(@eslint-react/eslint-plugin@1.14.2(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(@vue/compiler-sfc@3.5.8)(eslint-plugin-react-hooks@5.1.0-rc-fb9a90fa48-20240614(eslint@9.11.1(jiti@1.21.6)))(eslint-plugin-react-refresh@0.4.12(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) version: 3.2.3-beta.4(@eslint-react/eslint-plugin@1.14.2(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(@vue/compiler-sfc@3.5.8)(eslint-plugin-react-hooks@5.1.0-rc-fb9a90fa48-20240614(eslint@9.11.1(jiti@1.21.6)))(eslint-plugin-react-refresh@0.4.12(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.6.1)(terser@5.33.0))
'@ourongxing/tsconfig': '@ourongxing/tsconfig':
specifier: ^0.0.4 specifier: ^0.0.4
version: 0.0.4 version: 0.0.4
@ -105,6 +120,12 @@ importers:
vite-plugin-with-nitro: vite-plugin-with-nitro:
specifier: 0.0.0-beta.4 specifier: 0.0.0-beta.4
version: 0.0.0-beta.4(magicast@0.3.5)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0)) version: 0.0.0-beta.4(magicast@0.3.5)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0))
vite-tsconfig-paths:
specifier: ^5.0.1
version: 5.0.1(typescript@5.6.2)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0))
vitest:
specifier: ^2.1.1
version: 2.1.1(@types/node@22.6.1)(terser@5.33.0)
packages: packages:
@ -112,9 +133,6 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
'@analogjs/vite-plugin-nitro@1.8.1':
resolution: {integrity: sha512-jgGajnU7+Q8miEiBXCBg9cGbbjqrYa+EBStTtdshq9uEXZmIZjSe40Rv+6R7S04dnWdVZtf/O1kiZ6aToo5dRQ==}
'@antfu/install-pkg@0.4.1': '@antfu/install-pkg@0.4.1':
resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==}
@ -224,6 +242,28 @@ packages:
resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==}
engines: {node: '>=16.13'} engines: {node: '>=16.13'}
'@dnd-kit/accessibility@3.1.0':
resolution: {integrity: sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==}
peerDependencies:
react: '>=16.8.0'
'@dnd-kit/core@6.1.0':
resolution: {integrity: sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@dnd-kit/sortable@8.0.0':
resolution: {integrity: sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==}
peerDependencies:
'@dnd-kit/core': ^6.1.0
react: '>=16.8.0'
'@dnd-kit/utilities@3.2.2':
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
peerDependencies:
react: '>=16.8.0'
'@es-joy/jsdoccomment@0.48.0': '@es-joy/jsdoccomment@0.48.0':
resolution: {integrity: sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==} resolution: {integrity: sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -1628,6 +1668,36 @@ packages:
vitest: vitest:
optional: true optional: true
'@vitest/expect@2.1.1':
resolution: {integrity: sha512-YeueunS0HiHiQxk+KEOnq/QMzlUuOzbU1Go+PgAsHvvv3tUkJPm9xWt+6ITNTlzsMXUjmgm5T+U7KBPK2qQV6w==}
'@vitest/mocker@2.1.1':
resolution: {integrity: sha512-LNN5VwOEdJqCmJ/2XJBywB11DLlkbY0ooDJW3uRX5cZyYCrc4PI/ePX0iQhE3BiEGiQmK4GE7Q/PqCkkaiPnrA==}
peerDependencies:
'@vitest/spy': 2.1.1
msw: ^2.3.5
vite: ^5.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@2.1.1':
resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==}
'@vitest/runner@2.1.1':
resolution: {integrity: sha512-uTPuY6PWOYitIkLPidaY5L3t0JJITdGTSwBtwMjKzo5O6RCOEncz9PUN+0pDidX8kTHYjO0EwUIvhlGpnGpxmA==}
'@vitest/snapshot@2.1.1':
resolution: {integrity: sha512-BnSku1WFy7r4mm96ha2FzN99AZJgpZOWrAhtQfoxjUU5YMRpq1zmHRq7a5K9/NjqonebO7iVDla+VvZS8BOWMw==}
'@vitest/spy@2.1.1':
resolution: {integrity: sha512-ZM39BnZ9t/xZ/nF4UwRH5il0Sw93QnZXd9NAZGRpIgj0yvVwPpLd702s/Cx955rGaMlyBQkZJ2Ir7qyY48VZ+g==}
'@vitest/utils@2.1.1':
resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==}
'@vue/compiler-core@3.5.8': '@vue/compiler-core@3.5.8':
resolution: {integrity: sha512-Uzlxp91EPjfbpeO5KtC0KnXPkuTfGsNDeaKQJxQN718uz+RqDYarEf7UhQJGK+ZYloD2taUbHTI2J4WrUaZQNA==} resolution: {integrity: sha512-Uzlxp91EPjfbpeO5KtC0KnXPkuTfGsNDeaKQJxQN718uz+RqDYarEf7UhQJGK+ZYloD2taUbHTI2J4WrUaZQNA==}
@ -1729,6 +1799,13 @@ packages:
argparse@2.0.1: argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
array-differences@3.0.2:
resolution: {integrity: sha512-raP8rW7DyS3AypmGHlvuSxXUdTXVvQxPHDlWFyQ0DLw4BMb//1qJtUlBRRK6ePLfQV3B3L5tLSe7qix+uEf14w==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
async-sema@3.1.1: async-sema@3.1.1:
resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==}
@ -1817,6 +1894,10 @@ packages:
caniuse-lite@1.0.30001663: caniuse-lite@1.0.30001663:
resolution: {integrity: sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==} resolution: {integrity: sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==}
chai@5.1.1:
resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
engines: {node: '>=12'}
chalk@2.4.2: chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -1838,6 +1919,10 @@ packages:
character-reference-invalid@1.1.4: character-reference-invalid@1.1.4:
resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}
check-error@2.1.1:
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
engines: {node: '>= 16'}
chokidar@3.6.0: chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'} engines: {node: '>= 8.10.0'}
@ -1979,6 +2064,9 @@ packages:
csstype@3.1.3: csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
db0@0.1.4: db0@0.1.4:
resolution: {integrity: sha512-Ft6eCwONYxlwLjBXSJxw0t0RYtA5gW9mq8JfBXn9TtC0nDPlqePAhpv9v4g9aONBi6JI1OXHTKKkUYGd+BOrCA==} resolution: {integrity: sha512-Ft6eCwONYxlwLjBXSJxw0t0RYtA5gW9mq8JfBXn9TtC0nDPlqePAhpv9v4g9aONBi6JI1OXHTKKkUYGd+BOrCA==}
peerDependencies: peerDependencies:
@ -2018,6 +2106,10 @@ packages:
supports-color: supports-color:
optional: true optional: true
deep-eql@5.0.2:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
deep-is@0.1.4: deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@ -2504,6 +2596,20 @@ packages:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'} engines: {node: '>=14'}
framer-motion@11.8.0:
resolution: {integrity: sha512-q/axN/PFRdKmzPK6PO2OhbLUMWXXZuiejdM1/3FhC2hm4YIetc+qeco2EvWm4u1/UTFmevclE492wGFNfSZ4eQ==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0
react-dom: ^18.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
fresh@0.5.2: fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -2543,6 +2649,9 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*} engines: {node: 6.* || 8.* || >= 10.*}
get-func-name@2.0.2:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
get-port-please@3.1.2: get-port-please@3.1.2:
resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==} resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==}
@ -2947,6 +3056,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true hasBin: true
loupe@3.1.1:
resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==}
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@ -3258,6 +3370,10 @@ packages:
pathe@1.1.2: pathe@1.1.2:
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
pathval@2.0.0:
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
engines: {node: '>= 14.16'}
perfect-debounce@1.0.0: perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
@ -3335,6 +3451,15 @@ packages:
peerDependencies: peerDependencies:
react: ^18.3.1 react: ^18.3.1
react-intersection-observer@9.13.1:
resolution: {integrity: sha512-tSzDaTy0qwNPLJHg8XZhlyHTgGW6drFKTtvjdL+p6um12rcnp8Z5XstE+QNBJ7c64n5o0Lj4ilUleA41bmDoMw==}
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
react-dom:
optional: true
react-universal-interface@0.6.2: react-universal-interface@0.6.2:
resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==} resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==}
peerDependencies: peerDependencies:
@ -3525,6 +3650,9 @@ packages:
resolution: {integrity: sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==} resolution: {integrity: sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==}
hasBin: true hasBin: true
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
signal-exit@3.0.7: signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@ -3592,6 +3720,9 @@ packages:
stack-generator@2.0.10: stack-generator@2.0.10:
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
stackframe@1.3.4: stackframe@1.3.4:
resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}
@ -3717,6 +3848,9 @@ packages:
tiny-warning@1.0.3: tiny-warning@1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyexec@0.3.0: tinyexec@0.3.0:
resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==}
@ -3724,6 +3858,18 @@ packages:
resolution: {integrity: sha512-NbBoFBpqfcgd1tCiO8Lkfdk+xrA7mlLR9zgvZcZWQQwU63XAfUePyd6wZBaU93Hqw347lHnwFzttAkemHzzz4g==} resolution: {integrity: sha512-NbBoFBpqfcgd1tCiO8Lkfdk+xrA7mlLR9zgvZcZWQQwU63XAfUePyd6wZBaU93Hqw347lHnwFzttAkemHzzz4g==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
tinypool@1.0.1:
resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==}
engines: {node: ^18.0.0 || >=20.0.0}
tinyrainbow@1.2.0:
resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
engines: {node: '>=14.0.0'}
tinyspy@3.0.2:
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
engines: {node: '>=14.0.0'}
to-fast-properties@2.0.0: to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -3953,6 +4099,11 @@ packages:
validate-npm-package-license@3.0.4: validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
vite-node@2.1.1:
resolution: {integrity: sha512-N/mGckI1suG/5wQI35XeR9rsMsPqKXzq1CdUndzVstBj/HvyxxGctwnK6WX43NGt5L3Z5tcRf83g4TITKJhPrA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
vite-plugin-with-nitro@0.0.0-beta.4: vite-plugin-with-nitro@0.0.0-beta.4:
resolution: {integrity: sha512-w8GvLPKxE3kEhqBxiopWIJtmOoOEj9XI37cxK+9E9OjnT/1WkDxJJLfu0rr8CMSdKM5D1ZoE0yjqF3xdIn1Mdg==} resolution: {integrity: sha512-w8GvLPKxE3kEhqBxiopWIJtmOoOEj9XI37cxK+9E9OjnT/1WkDxJJLfu0rr8CMSdKM5D1ZoE0yjqF3xdIn1Mdg==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
@ -3998,6 +4149,31 @@ packages:
terser: terser:
optional: true optional: true
vitest@2.1.1:
resolution: {integrity: sha512-97We7/VC0e9X5zBVkvt7SGQMGrRtn3KtySFQG5fpaMlS+l62eeXRQO633AYhSTC3z7IMebnPPNjGXVGNRFlxBA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@types/node': ^18.0.0 || >=20.0.0
'@vitest/browser': 2.1.1
'@vitest/ui': 2.1.1
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@types/node':
optional: true
'@vitest/browser':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
vue-eslint-parser@9.4.3: vue-eslint-parser@9.4.3:
resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==}
engines: {node: ^14.17.0 || >=16.0.0} engines: {node: ^14.17.0 || >=16.0.0}
@ -4018,6 +4194,11 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
hasBin: true hasBin: true
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
wide-align@1.1.5: wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
@ -4089,34 +4270,6 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.5 '@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25 '@jridgewell/trace-mapping': 0.3.25
'@analogjs/vite-plugin-nitro@1.8.1(magicast@0.3.5)':
dependencies:
esbuild: 0.20.2
nitropack: 2.9.7(magicast@0.3.5)
xmlbuilder2: 3.1.1
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
- '@azure/data-tables'
- '@azure/identity'
- '@azure/keyvault-secrets'
- '@azure/storage-blob'
- '@capacitor/preferences'
- '@libsql/client'
- '@netlify/blobs'
- '@planetscale/database'
- '@upstash/redis'
- '@vercel/kv'
- better-sqlite3
- drizzle-orm
- encoding
- idb-keyval
- magicast
- supports-color
- uWebSockets.js
- webpack-sources
- xml2js
'@antfu/install-pkg@0.4.1': '@antfu/install-pkg@0.4.1':
dependencies: dependencies:
package-manager-detector: 0.2.0 package-manager-detector: 0.2.0
@ -4267,6 +4420,31 @@ snapshots:
dependencies: dependencies:
mime: 3.0.0 mime: 3.0.0
'@dnd-kit/accessibility@3.1.0(react@18.3.1)':
dependencies:
react: 18.3.1
tslib: 2.7.0
'@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@dnd-kit/accessibility': 3.1.0(react@18.3.1)
'@dnd-kit/utilities': 3.2.2(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tslib: 2.7.0
'@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
dependencies:
'@dnd-kit/core': 6.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@dnd-kit/utilities': 3.2.2(react@18.3.1)
react: 18.3.1
tslib: 2.7.0
'@dnd-kit/utilities@3.2.2(react@18.3.1)':
dependencies:
react: 18.3.1
tslib: 2.7.0
'@es-joy/jsdoccomment@0.48.0': '@es-joy/jsdoccomment@0.48.0':
dependencies: dependencies:
comment-parser: 1.4.1 comment-parser: 1.4.1
@ -4821,7 +4999,7 @@ snapshots:
'@oozcitak/util@8.3.8': {} '@oozcitak/util@8.3.8': {}
'@ourongxing/eslint-config@3.2.3-beta.4(@eslint-react/eslint-plugin@1.14.2(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(@vue/compiler-sfc@3.5.8)(eslint-plugin-react-hooks@5.1.0-rc-fb9a90fa48-20240614(eslint@9.11.1(jiti@1.21.6)))(eslint-plugin-react-refresh@0.4.12(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)': '@ourongxing/eslint-config@3.2.3-beta.4(@eslint-react/eslint-plugin@1.14.2(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(@vue/compiler-sfc@3.5.8)(eslint-plugin-react-hooks@5.1.0-rc-fb9a90fa48-20240614(eslint@9.11.1(jiti@1.21.6)))(eslint-plugin-react-refresh@0.4.12(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.6.1)(terser@5.33.0))':
dependencies: dependencies:
'@antfu/install-pkg': 0.4.1 '@antfu/install-pkg': 0.4.1
'@clack/prompts': 0.7.0 '@clack/prompts': 0.7.0
@ -4829,7 +5007,7 @@ snapshots:
'@stylistic/eslint-plugin': 2.8.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) '@stylistic/eslint-plugin': 2.8.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)
'@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) '@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)
'@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)
'@vitest/eslint-plugin': 1.1.4(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) '@vitest/eslint-plugin': 1.1.4(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.6.1)(terser@5.33.0))
eslint: 9.11.1(jiti@1.21.6) eslint: 9.11.1(jiti@1.21.6)
eslint-config-flat-gitignore: 0.3.0(eslint@9.11.1(jiti@1.21.6)) eslint-config-flat-gitignore: 0.3.0(eslint@9.11.1(jiti@1.21.6))
eslint-flat-config-utils: 0.4.0 eslint-flat-config-utils: 0.4.0
@ -5514,12 +5692,53 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- '@swc/helpers' - '@swc/helpers'
'@vitest/eslint-plugin@1.1.4(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)': '@vitest/eslint-plugin@1.1.4(@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.6.1)(terser@5.33.0))':
dependencies: dependencies:
eslint: 9.11.1(jiti@1.21.6) eslint: 9.11.1(jiti@1.21.6)
optionalDependencies: optionalDependencies:
'@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)
typescript: 5.6.2 typescript: 5.6.2
vitest: 2.1.1(@types/node@22.6.1)(terser@5.33.0)
'@vitest/expect@2.1.1':
dependencies:
'@vitest/spy': 2.1.1
'@vitest/utils': 2.1.1
chai: 5.1.1
tinyrainbow: 1.2.0
'@vitest/mocker@2.1.1(@vitest/spy@2.1.1)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0))':
dependencies:
'@vitest/spy': 2.1.1
estree-walker: 3.0.3
magic-string: 0.30.11
optionalDependencies:
vite: 5.4.8(@types/node@22.6.1)(terser@5.33.0)
'@vitest/pretty-format@2.1.1':
dependencies:
tinyrainbow: 1.2.0
'@vitest/runner@2.1.1':
dependencies:
'@vitest/utils': 2.1.1
pathe: 1.1.2
'@vitest/snapshot@2.1.1':
dependencies:
'@vitest/pretty-format': 2.1.1
magic-string: 0.30.11
pathe: 1.1.2
'@vitest/spy@2.1.1':
dependencies:
tinyspy: 3.0.2
'@vitest/utils@2.1.1':
dependencies:
'@vitest/pretty-format': 2.1.1
loupe: 3.1.1
tinyrainbow: 1.2.0
'@vue/compiler-core@3.5.8': '@vue/compiler-core@3.5.8':
dependencies: dependencies:
@ -5640,6 +5859,10 @@ snapshots:
argparse@2.0.1: {} argparse@2.0.1: {}
array-differences@3.0.2: {}
assertion-error@2.0.1: {}
async-sema@3.1.1: {} async-sema@3.1.1: {}
async@3.2.6: {} async@3.2.6: {}
@ -5731,6 +5954,14 @@ snapshots:
caniuse-lite@1.0.30001663: {} caniuse-lite@1.0.30001663: {}
chai@5.1.1:
dependencies:
assertion-error: 2.0.1
check-error: 2.1.1
deep-eql: 5.0.2
loupe: 3.1.1
pathval: 2.0.0
chalk@2.4.2: chalk@2.4.2:
dependencies: dependencies:
ansi-styles: 3.2.1 ansi-styles: 3.2.1
@ -5750,6 +5981,8 @@ snapshots:
character-reference-invalid@1.1.4: {} character-reference-invalid@1.1.4: {}
check-error@2.1.1: {}
chokidar@3.6.0: chokidar@3.6.0:
dependencies: dependencies:
anymatch: 3.1.3 anymatch: 3.1.3
@ -5877,6 +6110,8 @@ snapshots:
csstype@3.1.3: {} csstype@3.1.3: {}
dayjs@1.11.13: {}
db0@0.1.4: {} db0@0.1.4: {}
debug@2.6.9: debug@2.6.9:
@ -5891,6 +6126,8 @@ snapshots:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
deep-eql@5.0.2: {}
deep-is@0.1.4: {} deep-is@0.1.4: {}
deepmerge@4.3.1: {} deepmerge@4.3.1: {}
@ -6570,6 +6807,13 @@ snapshots:
cross-spawn: 7.0.3 cross-spawn: 7.0.3
signal-exit: 4.1.0 signal-exit: 4.1.0
framer-motion@11.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
tslib: 2.7.0
optionalDependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
fresh@0.5.2: {} fresh@0.5.2: {}
front-matter@4.0.2: front-matter@4.0.2:
@ -6609,6 +6853,8 @@ snapshots:
get-caller-file@2.0.5: {} get-caller-file@2.0.5: {}
get-func-name@2.0.2: {}
get-port-please@3.1.2: {} get-port-please@3.1.2: {}
get-stream@8.0.1: {} get-stream@8.0.1: {}
@ -7016,6 +7262,10 @@ snapshots:
dependencies: dependencies:
js-tokens: 4.0.0 js-tokens: 4.0.0
loupe@3.1.1:
dependencies:
get-func-name: 2.0.2
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
lru-cache@5.1.1: lru-cache@5.1.1:
@ -7401,6 +7651,8 @@ snapshots:
pathe@1.1.2: {} pathe@1.1.2: {}
pathval@2.0.0: {}
perfect-debounce@1.0.0: {} perfect-debounce@1.0.0: {}
picocolors@1.1.0: {} picocolors@1.1.0: {}
@ -7463,6 +7715,12 @@ snapshots:
react: 18.3.1 react: 18.3.1
scheduler: 0.23.2 scheduler: 0.23.2
react-intersection-observer@9.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
react-universal-interface@0.6.2(react@18.3.1)(tslib@2.7.0): react-universal-interface@0.6.2(react@18.3.1)(tslib@2.7.0):
dependencies: dependencies:
react: 18.3.1 react: 18.3.1
@ -7693,6 +7951,8 @@ snapshots:
short-unique-id@5.2.0: {} short-unique-id@5.2.0: {}
siginfo@2.0.0: {}
signal-exit@3.0.7: {} signal-exit@3.0.7: {}
signal-exit@4.1.0: {} signal-exit@4.1.0: {}
@ -7751,6 +8011,8 @@ snapshots:
dependencies: dependencies:
stackframe: 1.3.4 stackframe: 1.3.4
stackback@0.0.2: {}
stackframe@1.3.4: {} stackframe@1.3.4: {}
stacktrace-gps@3.1.2: stacktrace-gps@3.1.2:
@ -7881,6 +8143,8 @@ snapshots:
tiny-warning@1.0.3: {} tiny-warning@1.0.3: {}
tinybench@2.9.0: {}
tinyexec@0.3.0: {} tinyexec@0.3.0: {}
tinyglobby@0.2.6: tinyglobby@0.2.6:
@ -7888,6 +8152,12 @@ snapshots:
fdir: 6.3.0(picomatch@4.0.2) fdir: 6.3.0(picomatch@4.0.2)
picomatch: 4.0.2 picomatch: 4.0.2
tinypool@1.0.1: {}
tinyrainbow@1.2.0: {}
tinyspy@3.0.2: {}
to-fast-properties@2.0.0: {} to-fast-properties@2.0.0: {}
to-regex-range@5.0.1: to-regex-range@5.0.1:
@ -8109,6 +8379,23 @@ snapshots:
spdx-correct: 3.2.0 spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1 spdx-expression-parse: 3.0.1
vite-node@2.1.1(@types/node@22.6.1)(terser@5.33.0):
dependencies:
cac: 6.7.14
debug: 4.3.7
pathe: 1.1.2
vite: 5.4.8(@types/node@22.6.1)(terser@5.33.0)
transitivePeerDependencies:
- '@types/node'
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
vite-plugin-with-nitro@0.0.0-beta.4(magicast@0.3.5)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0)): vite-plugin-with-nitro@0.0.0-beta.4(magicast@0.3.5)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0)):
dependencies: dependencies:
esbuild: 0.24.0 esbuild: 0.24.0
@ -8162,6 +8449,40 @@ snapshots:
fsevents: 2.3.3 fsevents: 2.3.3
terser: 5.33.0 terser: 5.33.0
vitest@2.1.1(@types/node@22.6.1)(terser@5.33.0):
dependencies:
'@vitest/expect': 2.1.1
'@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.8(@types/node@22.6.1)(terser@5.33.0))
'@vitest/pretty-format': 2.1.1
'@vitest/runner': 2.1.1
'@vitest/snapshot': 2.1.1
'@vitest/spy': 2.1.1
'@vitest/utils': 2.1.1
chai: 5.1.1
debug: 4.3.7
magic-string: 0.30.11
pathe: 1.1.2
std-env: 3.7.0
tinybench: 2.9.0
tinyexec: 0.3.0
tinypool: 1.0.1
tinyrainbow: 1.2.0
vite: 5.4.8(@types/node@22.6.1)(terser@5.33.0)
vite-node: 2.1.1(@types/node@22.6.1)(terser@5.33.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 22.6.1
transitivePeerDependencies:
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
vue-eslint-parser@9.4.3(eslint@9.11.1(jiti@1.21.6)): vue-eslint-parser@9.4.3(eslint@9.11.1(jiti@1.21.6)):
dependencies: dependencies:
debug: 4.3.7 debug: 4.3.7
@ -8188,6 +8509,11 @@ snapshots:
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
wide-align@1.1.5: wide-align@1.1.5:
dependencies: dependencies:
string-width: 4.2.3 string-width: 4.2.3

10
server/config.ts Normal file
View File

@ -0,0 +1,10 @@
// 创建配置对象
export const config = {
PORT: 6688,
DISALLOW_ROBOT: true,
CACHE_TTL: 3600,
REQUEST_TIMEOUT: 6000,
ALLOWED_DOMAIN: "*",
USE_LOG_FILE: true,
RSS_MODE: false,
}

View File

@ -1,3 +0,0 @@
export function add(x: number, y: number) {
return x + y
}

View File

@ -0,0 +1,8 @@
import { defineEventHandler, getQuery, getRouterParam, sendProxy } from "h3"
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, "id")
const { latest } = getQuery(event)
if (latest !== undefined) return await sendProxy(event, `https://smzdk.top/api/${id}/new`)
return await sendProxy(event, `https://smzdk.top/api/${id}/new`)
})

View File

@ -1,7 +0,0 @@
import { defineEventHandler } from "h3"
export default defineEventHandler(() => {
return {
message: "Hello World",
}
})

41
server/types.ts Normal file
View File

@ -0,0 +1,41 @@
import type { NewsItem, SourceInfo } from "@shared/types"
// 榜单数据
export interface ListItem extends NewsItem { }
// 路由数据
export interface RouterData extends SourceInfo { }
// 请求类型
export interface Get {
url: string
headers?: Record<string, string | string[]>
params?: Record<string, string | number>
timeout?: number
noCache?: boolean
ttl?: number
originaInfo?: boolean
}
export interface Post {
url: string
headers?: Record<string, string | string[]>
body?: string | object | import("node:buffer").Buffer | undefined
timeout?: number
noCache?: boolean
ttl?: number
originaInfo?: boolean
}
export interface Web {
url: string
timeout?: number
noCache?: boolean
ttl?: number
userAgent?: string
}
// 参数类型
export interface Options {
[key: string]: string | number | undefined
}

View File

@ -1,24 +1,40 @@
import type { Section } from "./types" import type { Metadata } from "./types"
export const sections = [ export const sectionIds = ["focus", "social", "china", "world", "digital"] as const
{
export const sourceList = {
"36kr": "36氪",
"douyin": "抖音",
"hupu": "虎扑",
"zhihu": "知乎",
"weibo": "微博",
"tieba": "贴吧",
"zaobao": "联合早报",
"thepaper": "澎湃新闻",
"toutiao": "今日头条",
"cankaoxiaoxi": "参考消息",
"peopledaily": "人民日报",
} as const satisfies Record<string, string | false>
export const metadata: Metadata = {
focus: {
name: "关注", name: "关注",
id: "focus", sourceList: [],
}, },
{ social: {
name: "综合", name: "社交媒体",
id: "main", sourceList: ["douyin", "hupu", "tieba", "weibo"],
}, },
{ china: {
name: "国内", name: "国内",
id: "china", sourceList: ["peopledaily", "36kr", "toutiao"],
}, },
{ world: {
name: "国外", name: "国外",
id: "world", sourceList: ["zaobao"],
}, },
{ digital: {
name: "数码", name: "数码",
id: "digital", sourceList: ["36kr"],
}, },
] as const satisfies Section[] }

View File

@ -1,8 +1,34 @@
import type { sections } from "./data" import type { sectionIds, sourceList } from "./data"
export type SourceID = keyof(typeof sourceList)
export type SectionID = (typeof sectionIds)[number]
export type Metadata = Record<SectionID, Section>
export interface Section { export interface Section {
name: string name: string
id: string sourceList: SourceID[]
} }
export type SectionId = (typeof sections)[number]["id"] export interface NewsItem {
title: string
cover?: string
author?: string
desc?: string
url: string
mobileUrl?: string
}
// 路由数据
export interface SourceInfo {
name: string
title: string
subtitle?: string
type: string
description?: string
params?: Record<string, string | object>
total: number
link?: string
updateTime: string
fromCache: boolean
data: NewsItem[]
}

21
shared/utils.ts Normal file
View File

@ -0,0 +1,21 @@
export function formatTime(timestamp: string) {
const date = new Date(timestamp)
const now = new Date()
const diffInSeconds = (now.getTime() - date.getTime()) / 1000
const diffInMinutes = diffInSeconds / 60
const diffInHours = diffInMinutes / 60
if (diffInSeconds < 60) {
return "刚刚更新"
} else if (diffInMinutes < 60) {
const minutes = Math.floor(diffInMinutes)
return `${minutes}分钟前更新`
} else if (diffInHours < 24) {
const hours = Math.floor(diffInHours)
return `${hours}小时前更新`
} else {
const month = date.getMonth() + 1
const day = date.getDate()
return `${month}${day}`
}
}

15
src/app.tsx Normal file
View File

@ -0,0 +1,15 @@
import ReactDOM from "react-dom/client"
import { RouterProvider } from "@tanstack/react-router"
import { QueryClientProvider } from "@tanstack/react-query"
import { queryClient, router } from "./main"
const rootElement = document.getElementById("app")!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>,
)
}

27
src/atoms.ts Normal file
View File

@ -0,0 +1,27 @@
import { atom } from "jotai"
import type { SectionID, SourceID } from "@shared/types"
import { metadata, sourceList } from "@shared/data"
import { atomWithLocalStorage } from "./utils/atom"
export const focusSourcesAtom = atomWithLocalStorage<SourceID[]>("focusSources", [], (stored) => {
return stored.filter(item => item in sourceList)
})
const currentSectionIDAtom = atom<SectionID>("focus")
export const currentSectionAtom = atom((get) => {
const id = get(currentSectionIDAtom)
if (id === "focus") {
return {
id,
...metadata[id],
sourceList: get(focusSourcesAtom),
}
}
return {
id,
...metadata[id],
}
}, (_, set, update: SectionID) => {
set(currentSectionIDAtom, update)
})

View File

@ -1,7 +0,0 @@
import { atom } from "jotai"
import { sections } from "@shared/data"
import type { SectionId } from "@shared/types"
export const sectionsAtom = atom(sections)
export const activeSectionAtom = atom<SectionId>("focus")

View File

@ -1,6 +1,11 @@
import { Link } from "@tanstack/react-router" import { Link } from "@tanstack/react-router"
import { useCallback } from "react"
import { useAtomValue } from "jotai"
import type { SourceID } from "@shared/types"
import { queryClient } from "~/main"
import logo from "~/assets/react.svg" import logo from "~/assets/react.svg"
import { useDark } from "~/hooks/useDark" import { useDark } from "~/hooks/useDark"
import { currentSectionAtom } from "~/atoms"
function ThemeToggle() { function ThemeToggle() {
const { toggleDark } = useDark() const { toggleDark } = useDark()
@ -14,15 +19,30 @@ function ThemeToggle() {
) )
} }
function RefreshButton() {
const currentSection = useAtomValue(currentSectionAtom)
const refreshAll = useCallback(async () => {
await queryClient.refetchQueries({
predicate(query) {
return currentSection.sourceList.includes(query.queryKey[0] as SourceID)
},
})
}, [currentSection])
return (
<button type="button" className="i-ph:arrow-clockwise btn-pure" onClick={refreshAll} />
)
}
export function Header() { export function Header() {
return ( return (
<header className="flex justify-between items-center"> <header className="flex justify-between items-center">
<Link className="text-8 flex gap-2 items-center" to="/"> <Link className="text-6 flex gap-2 items-center" to="/">
<img src={logo} alt="logo" className="h-10" /> <img src={logo} alt="logo" className="h-8" />
<span>NewsNow</span> <span className="font-mono">NewsNow</span>
</Link> </Link>
<div className="flex gap-2"> <div className="flex gap-2">
<button type="button" className="i-ph:arrow-clockwise btn-pure"></button> <RefreshButton />
<ThemeToggle /> <ThemeToggle />
<Link className="i-ph:gear btn-pure" to="/setting" /> <Link className="i-ph:gear btn-pure" to="/setting" />
</div> </div>

70
src/components/Main.tsx Normal file
View File

@ -0,0 +1,70 @@
import { useCallback, useMemo, useState } from "react"
import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core"
import {
DndContext,
DragOverlay,
MouseSensor,
TouchSensor,
closestCenter,
useSensor,
useSensors,
} from "@dnd-kit/core"
import { SortableContext, rectSortingStrategy } from "@dnd-kit/sortable"
import type { SectionID } from "@shared/types"
import { metadata } from "@shared/data"
import { Item, SortableItem } from "./NewsCard"
export function Main({ sectionId }: { sectionId: SectionID }) {
// const [items, setItems] = useState(metadata?.[sectionId]?.sourceList ?? [])
const items = useMemo(() => metadata?.[sectionId]?.sourceList ?? [], [sectionId])
const [activeId, setActiveId] = useState<string | null>(null)
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor))
const handleDragStart = useCallback((event: DragStartEvent) => {
setActiveId(event.active.id as string)
}, [])
const handleDragEnd = useCallback((event: DragEndEvent) => {
const { active, over } = event
if (active.id !== over?.id) {
// setItems((items) => {
// const oldIndex = items.indexOf(active.id as any)
// const newIndex = items.indexOf(over!.id as any)
// return arrayMove(items, oldIndex, newIndex)
// })
}
setActiveId(null)
}, [])
const handleDragCancel = useCallback(() => {
setActiveId(null)
}, [])
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext items={items} strategy={rectSortingStrategy}>
<div
id="grid-container"
className="grid w-full gap-5 mt-10"
style={{
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
}}
>
{items.map(id => (
<SortableItem key={id} id={id} />
))}
</div>
</SortableContext>
<DragOverlay adjustScale style={{ transformOrigin: "0 0 " }}>
{!!activeId && <Item id={activeId} isDragging />}
</DragOverlay>
</DndContext>
)
}

View File

@ -1,21 +0,0 @@
import { Link } from "@tanstack/react-router"
import { useAtomValue } from "jotai"
import { sectionsAtom } from "~/atoms/news"
export function NavBar() {
const items = useAtomValue(sectionsAtom)
return (
<section className="flex gap-2">
{items.map(nav => (
<Link
key={nav.id}
to="/section"
search={{ n: nav.id }}
activeProps={{ className: "bg-blue-500" }}
>
{nav.name}
</Link>
))}
</section>
)
}

View File

@ -1,6 +1,84 @@
export function NewsCard() { import type { CSSProperties, HTMLAttributes, PropsWithChildren } from "react"
import { useSortable } from "@dnd-kit/sortable"
import { CSS } from "@dnd-kit/utilities"
import { forwardRef } from "react"
import clsx from "clsx"
import type { SyntheticListenerMap } from "@dnd-kit/core/dist/hooks/utilities"
type ItemProps = HTMLAttributes<HTMLDivElement> & {
id: string
withOpacity?: boolean
isDragging?: boolean
listeners?: SyntheticListenerMap
}
export function GridContainer({ children }: PropsWithChildren) {
return ( return (
<div className="card"> <div className="grid gap-2 max-w-4xl mt-20 grid-cols-4">
{children}
</div> </div>
) )
} }
export const Item = forwardRef<HTMLDivElement, ItemProps>(({ id, withOpacity, isDragging, listeners, style, ...props }, ref) => {
const inlineStyles: CSSProperties = {
transformOrigin: "50% 50%",
minHeight: "500px",
...style,
}
const css = [
"border rounded-xl",
isDragging ? "scale-105" : "",
withOpacity && "op-50",
]
return (
<div
ref={ref}
className={clsx(css)}
style={inlineStyles}
{...props}
>
<div>
<div
className={clsx("border-b", isDragging ? "cursor-grabbing" : "cursor-grab")}
{...listeners}
>
</div>
<div>
{id}
</div>
</div>
</div>
)
})
export function SortableItem(props: ItemProps) {
const { id } = props
const {
isDragging,
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id })
const style = {
transform: CSS.Transform.toString(transform),
transition: transition || undefined,
}
return (
<Item
ref={setNodeRef}
style={style}
withOpacity={isDragging}
listeners={listeners}
{...attributes}
{...props}
/>
)
}

93
src/components/Pure.tsx Normal file
View File

@ -0,0 +1,93 @@
import type { SourceID, SourceInfo } from "@shared/types"
import { useQuery } from "@tanstack/react-query"
import { formatTime } from "@shared/utils"
import clsx from "clsx"
import { useInView } from "react-intersection-observer"
import { useAtom, useAtomValue } from "jotai"
import { useCallback } from "react"
import { currentSectionAtom, focusSourcesAtom } from "~/atoms"
export function Main() {
const currentSection = useAtomValue(currentSectionAtom)
return (
<div
id="grid-container"
className="grid w-full gap-5 mt-10"
style={{
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
}}
>
{currentSection.sourceList.map(id => (
<CardWrapper key={id} id={id} />
))}
</div>
)
}
function CardWrapper({ id }: { id: SourceID }) {
const { ref, inView } = useInView({
threshold: 0,
})
return (
<div ref={ref} className="flex flex-col border rounded-md px-2 h-500px" key={id}>
<NewsCard id={id} inView={inView} />
</div>
)
}
function NewsCard({ id, inView }: { id: SourceID, inView: boolean }) {
const [focusSources, setFocusSources] = useAtom(focusSourcesAtom)
const addFocusList = useCallback(() => {
setFocusSources(focusSources.includes(id) ? focusSources.filter(i => i !== id) : [...focusSources, id])
}, [setFocusSources, focusSources, id])
const { isPending, error, isFetching, data, refetch } = useQuery({
queryKey: [id],
queryFn: async () => {
const response = await fetch(`/api/${id}?latest`)
return await response.json() as SourceInfo
},
enabled: inView,
refetchOnWindowFocus: false,
})
if (isPending || !data) {
return (
<>
{Array.from({ length: 18 }).map((_, i) => i).map(i => <div key={i} className="skeleton m1"></div>)}
</>
)
} else if (error) {
return <div>Error: </div>
} else if (data) {
return (
<>
<div className="flex justify-between py-2">
<span className="text-md font-bold">
{ data?.title }
</span>
<span>
{ data?.subtitle}
</span>
</div>
<div className="overflow-auto">
{data?.data.slice(0, 20).map((item, index) => (
<div key={item.title} className="flex gap-2 border-b border-gray-300/20">
<span>
{ index + 1}
</span>
<a href={item.url} target="_blank">
{item.title}
</a>
</div>
))}
</div>
<div className="py-2 flex items-center justify-between">
<span>
{formatTime(data!.updateTime)}
</span>
<button type="button" className={clsx(focusSources.includes(id) ? "i-ph:star-fill" : "i-ph:star")} onClick={addFocusList} />
<button type="button" className={clsx("i-ph:arrow-clockwise", isFetching && "animate-spin")} onClick={() => refetch()} />
</div>
</>
)
}
}

20
src/main.ts Normal file
View File

@ -0,0 +1,20 @@
import { QueryClient } from "@tanstack/react-query"
import { createRouter } from "@tanstack/react-router"
import { routeTree } from "./routeTree.gen"
export const queryClient = new QueryClient()
export const router = createRouter({
routeTree,
context: {
queryClient,
},
defaultPreload: "intent",
defaultPreloadStaleTime: 0,
})
declare module "@tanstack/react-router" {
interface Register {
router: typeof router
}
}

View File

@ -1,32 +0,0 @@
import ReactDOM from "react-dom/client"
import { RouterProvider, createRouter } from "@tanstack/react-router"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { routeTree } from "./routeTree.gen"
const queryClient = new QueryClient()
const router = createRouter({
routeTree,
context: {
queryClient,
},
defaultPreload: "intent",
defaultPreloadStaleTime: 0,
})
declare module "@tanstack/react-router" {
interface Register {
router: typeof router
}
}
const rootElement = document.getElementById("root")!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>,
)
}

View File

@ -12,7 +12,6 @@
import { Route as rootRoute } from './routes/__root' import { Route as rootRoute } from './routes/__root'
import { Route as SettingImport } from './routes/setting' import { Route as SettingImport } from './routes/setting'
import { Route as SectionImport } from './routes/section'
import { Route as IndexImport } from './routes/index' import { Route as IndexImport } from './routes/index'
// Create/Update Routes // Create/Update Routes
@ -22,11 +21,6 @@ const SettingRoute = SettingImport.update({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any)
const SectionRoute = SectionImport.update({
path: '/section',
getParentRoute: () => rootRoute,
} as any)
const IndexRoute = IndexImport.update({ const IndexRoute = IndexImport.update({
path: '/', path: '/',
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
@ -43,13 +37,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexImport preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/section': {
id: '/section'
path: '/section'
fullPath: '/section'
preLoaderRoute: typeof SectionImport
parentRoute: typeof rootRoute
}
'/setting': { '/setting': {
id: '/setting' id: '/setting'
path: '/setting' path: '/setting'
@ -64,41 +51,36 @@ declare module '@tanstack/react-router' {
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/section': typeof SectionRoute
'/setting': typeof SettingRoute '/setting': typeof SettingRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/section': typeof SectionRoute
'/setting': typeof SettingRoute '/setting': typeof SettingRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRoute __root__: typeof rootRoute
'/': typeof IndexRoute '/': typeof IndexRoute
'/section': typeof SectionRoute
'/setting': typeof SettingRoute '/setting': typeof SettingRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/section' | '/setting' fullPaths: '/' | '/setting'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' | '/section' | '/setting' to: '/' | '/setting'
id: '__root__' | '/' | '/section' | '/setting' id: '__root__' | '/' | '/setting'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
SectionRoute: typeof SectionRoute
SettingRoute: typeof SettingRoute SettingRoute: typeof SettingRoute
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
SectionRoute: SectionRoute,
SettingRoute: SettingRoute, SettingRoute: SettingRoute,
} }
@ -115,16 +97,12 @@ export const routeTree = rootRoute
"filePath": "__root.tsx", "filePath": "__root.tsx",
"children": [ "children": [
"/", "/",
"/section",
"/setting" "/setting"
] ]
}, },
"/": { "/": {
"filePath": "index.tsx" "filePath": "index.tsx"
}, },
"/section": {
"filePath": "section.tsx"
},
"/setting": { "/setting": {
"filePath": "setting.tsx" "filePath": "setting.tsx"
} }

View File

@ -1,24 +1,26 @@
import { Link, Outlet, createRootRouteWithContext } from "@tanstack/react-router" import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"
import { TanStackRouterDevtools } from "@tanstack/router-devtools" import { TanStackRouterDevtools } from "@tanstack/router-devtools"
import { ReactQueryDevtools } from "@tanstack/react-query-devtools" import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
import "~/styles/globals.css" import "~/styles/globals.css"
import "@unocss/reset/tailwind.css" import "@unocss/reset/tailwind.css"
import "virtual:uno.css" import "virtual:uno.css"
import type { QueryClient } from "@tanstack/react-query"
import { Header } from "~/components/Header" import { Header } from "~/components/Header"
export const Route = createRootRouteWithContext()({ export const Route = createRootRouteWithContext<{
queryClient: QueryClient
}>()({
component: RootComponent, component: RootComponent,
notFoundComponent: () => { notFoundComponent: () => {
return ( return (
<div> <div>
<p>This is the notFoundComponent configured on root route</p> <p>This is the notFoundComponent configured on root route</p>
<Link to="/">Start Over</Link>
</div> </div>
) )
}, },
}) })
function RootComponent() { export function RootComponent() {
return ( return (
<div className="p-10"> <div className="p-10">
<Header /> <Header />

View File

@ -1,9 +1,42 @@
import { metadata, sectionIds } from "@shared/data"
import type { SectionID } from "@shared/types"
import { Link, createFileRoute } from "@tanstack/react-router" import { Link, createFileRoute } from "@tanstack/react-router"
import clsx from "clsx"
import { useSetAtom } from "jotai"
import { useEffect } from "react"
import { currentSectionAtom } from "~/atoms"
// import { Main } from "~/components/Main"
import { Main } from "~/components/Pure"
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: Index, validateSearch: (search: any) => ({
section: (search.section as SectionID),
}),
component: IndexComponent,
}) })
function Index() { function IndexComponent() {
return <Link to="/section" search={{ n: "focus" }}> </Link> const { section: id = "focus" } = Route.useSearch()
const setCurrentSectionAtom = useSetAtom(currentSectionAtom)
useEffect(() => {
setCurrentSectionAtom(id)
}, [setCurrentSectionAtom, id])
return id && (
<div className="flex flex-col justify-center items-center">
<section className="flex gap-2">
{sectionIds.map(section => (
<Link
key={section}
to="/"
search={{ section }}
className={clsx("btn-action-sm", id === section && "btn-action-active")}
>
{metadata[section].name}
</Link>
))}
</section>
<Main />
</div>
)
} }

View File

@ -1,18 +0,0 @@
import { createFileRoute } from "@tanstack/react-router"
import type { SectionId } from "~/atoms/news"
import { NavBar } from "~/components/NavBar"
export const Route = createFileRoute("/section")({
validateSearch: (search: any) => ({
n: search.n as SectionId,
}),
component: Section,
})
export function Section() {
return (
<div className="p-10">
<NavBar />
</div>
)
}

View File

@ -1,13 +1,13 @@
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
export const Route = createFileRoute("/setting")({ export const Route = createFileRoute("/setting")({
component: Setting component: SettingComponent,
}) })
function Setting() { function SettingComponent() {
return ( return (
<div className="p-2"> <div className="p-2">
<h3>Setting</h3> <h3>Setting</h3>
</div> </div>
) )
} }

View File

@ -1,6 +1,7 @@
html, html,
body, body,
#root { #app {
--col-count: 5;
height: 100vh; height: 100vh;
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -21,4 +22,4 @@ body {
button:disabled { button:disabled {
cursor: not-allowed; cursor: not-allowed;
pointer-events: all !important; pointer-events: all !important;
} }

33
src/utils/atom.ts Normal file
View File

@ -0,0 +1,33 @@
import type { PrimitiveAtom } from "jotai"
import { atom } from "jotai"
export function atomWithLocalStorage<T>(
key: string,
initialValue: T,
initFn?: ((stored: T) => T),
): PrimitiveAtom<T> {
const getInitialValue = () => {
const item = localStorage.getItem(key)
try {
if (item !== null) {
const stored = JSON.parse(item)
if (initFn) return initFn(stored)
return stored
}
} catch {
//
}
return initialValue
}
const baseAtom = atom(getInitialValue())
const derivedAtom = atom(
get => get(baseAtom),
(get, set, update) => {
const nextValue
= typeof update === "function" ? update(get(baseAtom)) : update
set(baseAtom, nextValue)
localStorage.setItem(key, JSON.stringify(nextValue))
},
)
return derivedAtom
}

11
test/data.test.ts Normal file
View File

@ -0,0 +1,11 @@
import { metadata } from "@shared/data"
import { describe, expect, it } from "vitest"
// 通过两次 diff 来找出数组的差异
describe("data", () => {
it.for(Object.entries(metadata))(` "%s" source list shoule be unique`, ([, { sourceList }]) => {
if (sourceList) {
expect(new Set(sourceList).size).toBe(sourceList.length)
}
})
})

View File

@ -1,5 +1,5 @@
{ {
"extends": "@ourongxing/tsconfig", "extends": "./tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react-jsx",
"lib": ["ES2020", "DOM", "DOM.Iterable"], "lib": ["ES2020", "DOM", "DOM.Iterable"],

19
tsconfig.base.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"composite": true,
"target": "ES2020",
"moduleDetection": "force",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"strict": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noEmit": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true
}
}

View File

@ -1,5 +1,4 @@
{ {
"extends": "@ourongxing/tsconfig",
"references": [ "references": [
{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" } { "path": "./tsconfig.node.json" }

View File

@ -1,5 +1,5 @@
{ {
"extends": "@ourongxing/tsconfig", "extends": ["./tsconfig.base.json", "./dist/.nitro/types/tsconfig.json"],
"compilerOptions": { "compilerOptions": {
"lib": ["ES2020"], "lib": ["ES2020"],
"baseUrl": ".", "baseUrl": ".",
@ -8,5 +8,5 @@
"@shared/*": ["shared/*"] "@shared/*": ["shared/*"]
} }
}, },
"include": ["server", "*.config.*", "shared"] "include": ["server", "*.config.*", "shared", "test"]
} }

View File

@ -28,6 +28,7 @@ export default defineConfig({
"btn-action": "border border-base rounded flex gap-2 items-center px2 py1 op75 hover:op100 hover:bg-hover", "btn-action": "border border-base rounded flex gap-2 items-center px2 py1 op75 hover:op100 hover:bg-hover",
"btn-action-sm": "btn-action text-sm", "btn-action-sm": "btn-action text-sm",
"btn-action-active": "color-active border-active! bg-active op100!", "btn-action-active": "color-active border-active! bg-active op100!",
"skeleton": "bg-gray-400/10 rounded-md h-5 w-full animate-pulse",
}, },
theme: { theme: {
colors: { colors: {

11
vercel.json Normal file
View File

@ -0,0 +1,11 @@
{
"rewrites": [
{
"source": "/:path((?!api).*)",
"destination": "/"
}
],
"github": {
"silent": true
}
}

View File

@ -1,4 +1,5 @@
import process from "node:process" import process from "node:process"
import { fileURLToPath } from "node:url"
import { defineConfig } from "vite" import { defineConfig } from "vite"
import react from "@vitejs/plugin-react-swc" import react from "@vitejs/plugin-react-swc"
import nitro from "vite-plugin-with-nitro" import nitro from "vite-plugin-with-nitro"
@ -19,11 +20,17 @@ export default defineConfig({
react(), react(),
nitro({ ssr: false }, { nitro({ ssr: false }, {
srcDir: "server", srcDir: "server",
alias: {
"@shared": fileURLToPath(new URL("shared", import.meta.url)),
},
runtimeConfig: {
// apiPrefix: "",
},
typescript: {
generateTsConfig: true,
},
minify: false, minify: false,
preset: process.env.VERCEL ? "vercel-edge" : "node-server", preset: process.env.VERCEL ? "vercel-edge" : "node-server",
experimental: {
websocket: true,
},
}), }),
], ],
}) })