mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
chore: auto import
This commit is contained in:
parent
cd67d52ca7
commit
0f187da2f8
94
auto-imports.app.d.ts
vendored
Normal file
94
auto-imports.app.d.ts
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const $: typeof import('clsx')['default']
|
||||||
|
const Author: typeof import('./shared/consts')['Author']
|
||||||
|
const Homepage: typeof import('./shared/consts')['Homepage']
|
||||||
|
const Interval: typeof import('./shared/consts')['Interval']
|
||||||
|
const TTL: typeof import('./shared/consts')['TTL']
|
||||||
|
const Timer: typeof import('./src/utils/index')['Timer']
|
||||||
|
const Version: typeof import('./shared/consts')['Version']
|
||||||
|
const atom: typeof import('jotai')['atom']
|
||||||
|
const atomFamily: typeof import('jotai/utils')['atomFamily']
|
||||||
|
const atomWithDefault: typeof import('jotai/utils')['atomWithDefault']
|
||||||
|
const atomWithHash: typeof import('jotai/utils')['atomWithHash']
|
||||||
|
const atomWithObservable: typeof import('jotai/utils')['atomWithObservable']
|
||||||
|
const atomWithReducer: typeof import('jotai/utils')['atomWithReducer']
|
||||||
|
const atomWithReset: typeof import('jotai/utils')['atomWithReset']
|
||||||
|
const atomWithStorage: typeof import('jotai/utils')['atomWithStorage']
|
||||||
|
const columns: typeof import('./shared/metadata')['columns']
|
||||||
|
const createJSONStorage: typeof import('jotai/utils')['createJSONStorage']
|
||||||
|
const createRef: typeof import('react')['createRef']
|
||||||
|
const currentColumnIDAtom: typeof import('./src/atoms/index')['currentColumnIDAtom']
|
||||||
|
const currentSourcesAtom: typeof import('./src/atoms/index')['currentSourcesAtom']
|
||||||
|
const delay: typeof import('./shared/utils')['delay']
|
||||||
|
const downloadMetadata: typeof import('./src/hooks/useSync')['downloadMetadata']
|
||||||
|
const fixedColumnIds: typeof import('./shared/metadata')['fixedColumnIds']
|
||||||
|
const focusSourcesAtom: typeof import('./src/atoms/index')['focusSourcesAtom']
|
||||||
|
const forwardRef: typeof import('react')['forwardRef']
|
||||||
|
const freezeAtom: typeof import('jotai/utils')['freezeAtom']
|
||||||
|
const freezeAtomCreator: typeof import('jotai/utils')['freezeAtomCreator']
|
||||||
|
const goToTopAtom: typeof import('./src/atoms/index')['goToTopAtom']
|
||||||
|
const hiddenColumns: typeof import('./shared/metadata')['hiddenColumns']
|
||||||
|
const lazy: typeof import('react')['lazy']
|
||||||
|
const loadable: typeof import('jotai/utils')['loadable']
|
||||||
|
const memo: typeof import('react')['memo']
|
||||||
|
const metadata: typeof import('./shared/metadata')['metadata']
|
||||||
|
const originSources: typeof import('./shared/sources')['originSources']
|
||||||
|
const preprocessMetadata: typeof import('./src/atoms/primitiveMetadataAtom')['preprocessMetadata']
|
||||||
|
const primitiveMetadataAtom: typeof import('./src/atoms/primitiveMetadataAtom')['primitiveMetadataAtom']
|
||||||
|
const projectDir: typeof import('./shared/dir')['projectDir']
|
||||||
|
const randomItem: typeof import('./shared/utils')['randomItem']
|
||||||
|
const randomUUID: typeof import('./shared/utils')['randomUUID']
|
||||||
|
const refetchSourcesAtom: typeof import('./src/atoms/index')['refetchSourcesAtom']
|
||||||
|
const relativeTime: typeof import('./shared/utils')['relativeTime']
|
||||||
|
const safeParseString: typeof import('./src/utils/index')['safeParseString']
|
||||||
|
const selectAtom: typeof import('jotai/utils')['selectAtom']
|
||||||
|
const sources: typeof import('./shared/sources')['sources']
|
||||||
|
const splitAtom: typeof import('jotai/utils')['splitAtom']
|
||||||
|
const startTransition: typeof import('react')['startTransition']
|
||||||
|
const toastAtom: typeof import('./src/hooks/useToast')['toastAtom']
|
||||||
|
const typeSafeObjectEntries: typeof import('./shared/type.util')['typeSafeObjectEntries']
|
||||||
|
const typeSafeObjectFromEntries: typeof import('./shared/type.util')['typeSafeObjectFromEntries']
|
||||||
|
const typeSafeObjectValues: typeof import('./shared/type.util')['typeSafeObjectValues']
|
||||||
|
const uploadMetadata: typeof import('./src/hooks/useSync')['uploadMetadata']
|
||||||
|
const useAtom: typeof import('jotai')['useAtom']
|
||||||
|
const useAtomCallback: typeof import('jotai/utils')['useAtomCallback']
|
||||||
|
const useAtomValue: typeof import('jotai')['useAtomValue']
|
||||||
|
const useCallback: typeof import('react')['useCallback']
|
||||||
|
const useContext: typeof import('react')['useContext']
|
||||||
|
const useDark: typeof import('./src/hooks/useDark')['useDark']
|
||||||
|
const useDebugValue: typeof import('react')['useDebugValue']
|
||||||
|
const useDeferredValue: typeof import('react')['useDeferredValue']
|
||||||
|
const useEffect: typeof import('react')['useEffect']
|
||||||
|
const useFocus: typeof import('./src/hooks/useFocus')['useFocus']
|
||||||
|
const useFocusWith: typeof import('./src/hooks/useFocus')['useFocusWith']
|
||||||
|
const useHydrateAtoms: typeof import('jotai/utils')['useHydrateAtoms']
|
||||||
|
const useId: typeof import('react')['useId']
|
||||||
|
const useImperativeHandle: typeof import('react')['useImperativeHandle']
|
||||||
|
const useInsertionEffect: typeof import('react')['useInsertionEffect']
|
||||||
|
const useLayoutEffect: typeof import('react')['useLayoutEffect']
|
||||||
|
const useLogin: typeof import('./src/hooks/useLogin')['useLogin']
|
||||||
|
const useMemo: typeof import('react')['useMemo']
|
||||||
|
const useOnReload: typeof import('./src/hooks/useOnReload')['useOnReload']
|
||||||
|
const usePWA: typeof import('./src/hooks/usePWA')['usePWA']
|
||||||
|
const useReducer: typeof import('react')['useReducer']
|
||||||
|
const useReducerAtom: typeof import('jotai/utils')['useReducerAtom']
|
||||||
|
const useRef: typeof import('react')['useRef']
|
||||||
|
const useRelativeTime: typeof import('./src/hooks/useRelativeTime')['useRelativeTime']
|
||||||
|
const useResetAtom: typeof import('jotai/utils')['useResetAtom']
|
||||||
|
const useSearchBar: typeof import('./src/hooks/useSearch')['useSearchBar']
|
||||||
|
const useSetAtom: typeof import('jotai')['useSetAtom']
|
||||||
|
const useState: typeof import('react')['useState']
|
||||||
|
const useSync: typeof import('./src/hooks/useSync')['useSync']
|
||||||
|
const useSyncExternalStore: typeof import('react')['useSyncExternalStore']
|
||||||
|
const useToast: typeof import('./src/hooks/useToast')['useToast']
|
||||||
|
const useTransition: typeof import('react')['useTransition']
|
||||||
|
const verifyPrimitiveMetadata: typeof import('./shared/verify')['verifyPrimitiveMetadata']
|
||||||
|
const waitForAll: typeof import('jotai/utils')['waitForAll']
|
||||||
|
}
|
55
nitro.config.ts
Normal file
55
nitro.config.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import process from "node:process"
|
||||||
|
import { join } from "node:path"
|
||||||
|
import viteNitro from "vite-plugin-with-nitro"
|
||||||
|
import { RollopGlob } from "./tools/rollup-glob"
|
||||||
|
import { projectDir } from "./shared/dir"
|
||||||
|
|
||||||
|
const nitroOption: Parameters<typeof viteNitro>[0] = {
|
||||||
|
experimental: {
|
||||||
|
database: true,
|
||||||
|
},
|
||||||
|
rollupConfig: {
|
||||||
|
plugins: [RollopGlob()],
|
||||||
|
},
|
||||||
|
sourceMap: false,
|
||||||
|
database: {
|
||||||
|
default: {
|
||||||
|
connector: "sqlite",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
imports: {
|
||||||
|
dirs: ["server/utils", "shared"],
|
||||||
|
},
|
||||||
|
preset: "node-server",
|
||||||
|
alias: {
|
||||||
|
"@shared": join(projectDir, "shared"),
|
||||||
|
"#": join(projectDir, "server"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.VERCEL) {
|
||||||
|
nitroOption.preset = "vercel-edge"
|
||||||
|
// You can use other online database, do it yourself. For more info: https://db0.unjs.io/connectors
|
||||||
|
nitroOption.database = undefined
|
||||||
|
} else if (process.env.CF_PAGES) {
|
||||||
|
nitroOption.preset = "cloudflare-pages"
|
||||||
|
nitroOption.database = {
|
||||||
|
default: {
|
||||||
|
connector: "cloudflare-d1",
|
||||||
|
options: {
|
||||||
|
bindingName: "NEWSNOW_DEV",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if (process.env.BUN) {
|
||||||
|
nitroOption.preset = "bun"
|
||||||
|
nitroOption.database = {
|
||||||
|
default: {
|
||||||
|
connector: "bun-sqlite",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return viteNitro(nitroOption)
|
||||||
|
}
|
@ -3,7 +3,6 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"commit-id": "",
|
|
||||||
"packageManager": "pnpm@9.12.1",
|
"packageManager": "pnpm@9.12.1",
|
||||||
"author": {
|
"author": {
|
||||||
"url": "https://github.com/ourongxing/",
|
"url": "https://github.com/ourongxing/",
|
||||||
@ -94,7 +93,6 @@
|
|||||||
"vite": "^5.4.8",
|
"vite": "^5.4.8",
|
||||||
"vite-plugin-pwa": "^0.20.5",
|
"vite-plugin-pwa": "^0.20.5",
|
||||||
"vite-plugin-with-nitro": "0.0.3",
|
"vite-plugin-with-nitro": "0.0.3",
|
||||||
"vite-tsconfig-paths": "^5.0.1",
|
|
||||||
"vitest": "^2.1.2",
|
"vitest": "^2.1.2",
|
||||||
"workbox-window": "^7.1.0",
|
"workbox-window": "^7.1.0",
|
||||||
"wrangler": "^3.80.3"
|
"wrangler": "^3.80.3"
|
||||||
|
41
pnpm-lock.yaml
generated
41
pnpm-lock.yaml
generated
@ -216,9 +216,6 @@ importers:
|
|||||||
vite-plugin-with-nitro:
|
vite-plugin-with-nitro:
|
||||||
specifier: 0.0.3
|
specifier: 0.0.3
|
||||||
version: 0.0.3(better-sqlite3@11.5.0)(idb-keyval@6.2.1)(typescript@5.6.3)(vite@5.4.10(@types/node@22.7.9)(terser@5.36.0))
|
version: 0.0.3(better-sqlite3@11.5.0)(idb-keyval@6.2.1)(typescript@5.6.3)(vite@5.4.10(@types/node@22.7.9)(terser@5.36.0))
|
||||||
vite-tsconfig-paths:
|
|
||||||
specifier: ^5.0.1
|
|
||||||
version: 5.0.1(typescript@5.6.3)(vite@5.4.10(@types/node@22.7.9)(terser@5.36.0))
|
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^2.1.2
|
specifier: ^2.1.2
|
||||||
version: 2.1.3(@types/node@22.7.9)(terser@5.36.0)
|
version: 2.1.3(@types/node@22.7.9)(terser@5.36.0)
|
||||||
@ -3914,9 +3911,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==}
|
resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
globrex@0.1.2:
|
|
||||||
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
|
|
||||||
|
|
||||||
goober@2.1.16:
|
goober@2.1.16:
|
||||||
resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==}
|
resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -5666,16 +5660,6 @@ packages:
|
|||||||
ts-pattern@5.5.0:
|
ts-pattern@5.5.0:
|
||||||
resolution: {integrity: sha512-jqbIpTsa/KKTJYWgPNsFNbLVpwCgzXfFJ1ukNn4I8hMwyQzHMJnk/BqWzggB0xpkILuKzaO/aMYhS0SkaJyKXg==}
|
resolution: {integrity: sha512-jqbIpTsa/KKTJYWgPNsFNbLVpwCgzXfFJ1ukNn4I8hMwyQzHMJnk/BqWzggB0xpkILuKzaO/aMYhS0SkaJyKXg==}
|
||||||
|
|
||||||
tsconfck@3.1.4:
|
|
||||||
resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==}
|
|
||||||
engines: {node: ^18 || >=20}
|
|
||||||
hasBin: true
|
|
||||||
peerDependencies:
|
|
||||||
typescript: ^5.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
typescript:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
tslib@2.8.0:
|
tslib@2.8.0:
|
||||||
resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==}
|
resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==}
|
||||||
|
|
||||||
@ -5979,14 +5963,6 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^5
|
vite: ^5
|
||||||
|
|
||||||
vite-tsconfig-paths@5.0.1:
|
|
||||||
resolution: {integrity: sha512-yqwv+LstU7NwPeNqajZzLEBVpUFU6Dugtb2P84FXuvaoYA+/70l9MHE+GYfYAycVyPSDYZ7mjOFuYBRqlEpTig==}
|
|
||||||
peerDependencies:
|
|
||||||
vite: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
vite:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
vite@5.4.10:
|
vite@5.4.10:
|
||||||
resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==}
|
resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@ -10305,8 +10281,6 @@ snapshots:
|
|||||||
slash: 5.1.0
|
slash: 5.1.0
|
||||||
unicorn-magic: 0.1.0
|
unicorn-magic: 0.1.0
|
||||||
|
|
||||||
globrex@0.1.2: {}
|
|
||||||
|
|
||||||
goober@2.1.16(csstype@3.1.3):
|
goober@2.1.16(csstype@3.1.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
@ -12184,10 +12158,6 @@ snapshots:
|
|||||||
|
|
||||||
ts-pattern@5.5.0: {}
|
ts-pattern@5.5.0: {}
|
||||||
|
|
||||||
tsconfck@3.1.4(typescript@5.6.3):
|
|
||||||
optionalDependencies:
|
|
||||||
typescript: 5.6.3
|
|
||||||
|
|
||||||
tslib@2.8.0: {}
|
tslib@2.8.0: {}
|
||||||
|
|
||||||
tsx@4.19.1:
|
tsx@4.19.1:
|
||||||
@ -12551,17 +12521,6 @@ snapshots:
|
|||||||
- webpack-sources
|
- webpack-sources
|
||||||
- xml2js
|
- xml2js
|
||||||
|
|
||||||
vite-tsconfig-paths@5.0.1(typescript@5.6.3)(vite@5.4.10(@types/node@22.7.9)(terser@5.36.0)):
|
|
||||||
dependencies:
|
|
||||||
debug: 4.3.7(supports-color@9.4.0)
|
|
||||||
globrex: 0.1.2
|
|
||||||
tsconfck: 3.1.4(typescript@5.6.3)
|
|
||||||
optionalDependencies:
|
|
||||||
vite: 5.4.10(@types/node@22.7.9)(terser@5.36.0)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
- typescript
|
|
||||||
|
|
||||||
vite@5.4.10(@types/node@22.7.9)(terser@5.36.0):
|
vite@5.4.10(@types/node@22.7.9)(terser@5.36.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.21.5
|
esbuild: 0.21.5
|
||||||
|
50
pwa.config.ts
Normal file
50
pwa.config.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import process from "node:process"
|
||||||
|
import type { VitePWAOptions } from "vite-plugin-pwa"
|
||||||
|
import { VitePWA } from "vite-plugin-pwa"
|
||||||
|
|
||||||
|
const pwaOption: Partial<VitePWAOptions> = {
|
||||||
|
includeAssets: ["icon.svg", "apple-touch-icon.png"],
|
||||||
|
manifest: {
|
||||||
|
name: "NewsNow",
|
||||||
|
short_name: "NewsNow",
|
||||||
|
description: "Elegant reading of real-time and hottest news",
|
||||||
|
theme_color: "#F14D42",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "pwa-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "pwa-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "pwa-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "any",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "pwa-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
purpose: "maskable",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
workbox: {
|
||||||
|
navigateFallbackDenylist: [/^\/api/],
|
||||||
|
},
|
||||||
|
devOptions: {
|
||||||
|
enabled: process.env.SW_DEV === "true",
|
||||||
|
type: "module",
|
||||||
|
navigateFallback: "index.html",
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function pwa() {
|
||||||
|
return VitePWA(pwaOption)
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import process from "node:process"
|
import process from "node:process"
|
||||||
import { verifyPrimitiveMetadata } from "@shared/verify"
|
|
||||||
import { UserTable } from "#/database/user"
|
import { UserTable } from "#/database/user"
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import { TTL } from "@shared/consts"
|
|
||||||
import type { SourceID, SourceResponse } from "@shared/types"
|
import type { SourceID, SourceResponse } from "@shared/types"
|
||||||
import { sources } from "@shared/sources"
|
|
||||||
import { getters } from "#/getters"
|
import { getters } from "#/getters"
|
||||||
import { getCacheTable } from "#/database/cache"
|
import { getCacheTable } from "#/database/cache"
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { typeSafeObjectEntries } from "@shared/type.util"
|
|
||||||
import type { SourceID } from "@shared/types"
|
import type { SourceID } from "@shared/types"
|
||||||
import * as x from "glob:./sources/{*.ts,**/index.ts}"
|
import * as x from "glob:./sources/{*.ts,**/index.ts}"
|
||||||
import type { SourceGetter } from "./types"
|
import type { SourceGetter } from "./types"
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { atom } from "jotai"
|
|
||||||
import type { FixedColumnID, SourceID } from "@shared/types"
|
import type { FixedColumnID, SourceID } from "@shared/types"
|
||||||
import { sources } from "@shared/sources"
|
|
||||||
import { primitiveMetadataAtom } from "./primitiveMetadataAtom"
|
|
||||||
import type { Update } from "./types"
|
import type { Update } from "./types"
|
||||||
|
|
||||||
export { primitiveMetadataAtom, preprocessMetadata } from "./primitiveMetadataAtom"
|
|
||||||
|
|
||||||
export const focusSourcesAtom = atom((get) => {
|
export const focusSourcesAtom = atom((get) => {
|
||||||
return get(primitiveMetadataAtom).data.focus
|
return get(primitiveMetadataAtom).data.focus
|
||||||
}, (get, set, update: Update<SourceID[]>) => {
|
}, (get, set, update: Update<SourceID[]>) => {
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
import { fixedColumnIds, metadata } from "@shared/metadata"
|
|
||||||
import { typeSafeObjectEntries, typeSafeObjectFromEntries } from "@shared/type.util"
|
|
||||||
import type { PrimitiveAtom } from "jotai"
|
import type { PrimitiveAtom } from "jotai"
|
||||||
import { atom } from "jotai"
|
|
||||||
import type { FixedColumnID, PrimitiveMetadata, SourceID } from "@shared/types"
|
import type { FixedColumnID, PrimitiveMetadata, SourceID } from "@shared/types"
|
||||||
import { verifyPrimitiveMetadata } from "@shared/verify"
|
|
||||||
import { sources } from "@shared/sources"
|
|
||||||
import type { Update } from "./types"
|
import type { Update } from "./types"
|
||||||
|
|
||||||
function createPrimitiveMetadataAtom(
|
function createPrimitiveMetadataAtom(
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import type { NewsItem, SourceID, SourceResponse } from "@shared/types"
|
import type { NewsItem, SourceID, SourceResponse } from "@shared/types"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import clsx from "clsx"
|
|
||||||
import { AnimatePresence, motion, useInView } from "framer-motion"
|
import { AnimatePresence, motion, useInView } from "framer-motion"
|
||||||
import { useAtom } from "jotai"
|
|
||||||
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"
|
|
||||||
import { sources } from "@shared/sources"
|
|
||||||
import type { SyntheticListenerMap } from "@dnd-kit/core/dist/hooks/utilities"
|
import type { SyntheticListenerMap } from "@dnd-kit/core/dist/hooks/utilities"
|
||||||
import { ofetch } from "ofetch"
|
import { ofetch } from "ofetch"
|
||||||
import { useWindowSize } from "react-use"
|
import { useWindowSize } from "react-use"
|
||||||
import { OverlayScrollbar } from "../common/overlay-scrollbar"
|
import { OverlayScrollbar } from "../common/overlay-scrollbar"
|
||||||
import { refetchSourcesAtom } from "~/atoms"
|
import { refetchSourcesAtom } from "~/atoms"
|
||||||
import { useRelativeTime } from "~/hooks/useRelativeTime"
|
|
||||||
import { safeParseString } from "~/utils"
|
import { safeParseString } from "~/utils"
|
||||||
import { useFocusWith } from "~/hooks/useFocus"
|
|
||||||
|
|
||||||
export interface ItemsProps extends React.HTMLAttributes<HTMLDivElement> {
|
export interface ItemsProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
id: SourceID
|
id: SourceID
|
||||||
@ -38,7 +32,7 @@ export const CardWrapper = forwardRef<HTMLDivElement, ItemsProps>(({ id, isDragg
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={clsx(
|
className={$(
|
||||||
"flex flex-col h-500px rounded-2xl p-4 cursor-default",
|
"flex flex-col h-500px rounded-2xl p-4 cursor-default",
|
||||||
"backdrop-blur-5 transition-opacity-300",
|
"backdrop-blur-5 transition-opacity-300",
|
||||||
isDragged && "op-50",
|
isDragged && "op-50",
|
||||||
@ -114,10 +108,10 @@ function NewsCard({ id, inView, handleListeners }: NewsCardProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={clsx("flex justify-between mx-2 mt-0 mb-2 items-center")}>
|
<div className={$("flex justify-between mx-2 mt-0 mb-2 items-center")}>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<a
|
<a
|
||||||
className={clsx("w-8 h-8 rounded-full bg-cover hover:animate-spin")}
|
className={$("w-8 h-8 rounded-full bg-cover hover:animate-spin")}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={sources[id].home}
|
href={sources[id].home}
|
||||||
title={sources[id].desc}
|
title={sources[id].desc}
|
||||||
@ -133,34 +127,34 @@ function NewsCard({ id, inView, handleListeners }: NewsCardProps) {
|
|||||||
>
|
>
|
||||||
{sources[id].name}
|
{sources[id].name}
|
||||||
</span>
|
</span>
|
||||||
{sources[id]?.title && <span className={clsx("text-sm", `color-${sources[id].color} bg-base op-80 bg-op-50! px-1 rounded`)}>{sources[id].title}</span>}
|
{sources[id]?.title && <span className={$("text-sm", `color-${sources[id].color} bg-base op-80 bg-op-50! px-1 rounded`)}>{sources[id].title}</span>}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs op-70"><UpdatedTime isError={isError} updatedTime={data?.updatedTime} /></span>
|
<span className="text-xs op-70"><UpdatedTime isError={isError} updatedTime={data?.updatedTime} /></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx("flex gap-2 text-lg", `color-${sources[id].color}`)}>
|
<div className={$("flex gap-2 text-lg", `color-${sources[id].color}`)}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={clsx("btn i-ph:arrow-counter-clockwise-duotone", isFetching && "animate-spin i-ph:circle-dashed-duotone")}
|
className={$("btn i-ph:arrow-counter-clockwise-duotone", isFetching && "animate-spin i-ph:circle-dashed-duotone")}
|
||||||
onClick={manualRefetch}
|
onClick={manualRefetch}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={clsx("btn", isFocused ? "i-ph:star-fill" : "i-ph:star-duotone")}
|
className={$("btn", isFocused ? "i-ph:star-fill" : "i-ph:star-duotone")}
|
||||||
onClick={toggleFocus}
|
onClick={toggleFocus}
|
||||||
/>
|
/>
|
||||||
{handleListeners && (
|
{handleListeners && (
|
||||||
<button
|
<button
|
||||||
{...handleListeners}
|
{...handleListeners}
|
||||||
type="button"
|
type="button"
|
||||||
className={clsx("btn", "i-ph:dots-six-vertical-duotone", "cursor-grab")}
|
className={$("btn", "i-ph:dots-six-vertical-duotone", "cursor-grab")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<OverlayScrollbar
|
<OverlayScrollbar
|
||||||
className={clsx([
|
className={$([
|
||||||
"h-full p-2 overflow-y-auto rounded-2xl bg-base bg-op-70!",
|
"h-full p-2 overflow-y-auto rounded-2xl bg-base bg-op-70!",
|
||||||
isFreshFetching && `animate-pulse`,
|
isFreshFetching && `animate-pulse`,
|
||||||
`sprinkle-${sources[id].color}`,
|
`sprinkle-${sources[id].color}`,
|
||||||
@ -170,7 +164,7 @@ function NewsCard({ id, inView, handleListeners }: NewsCardProps) {
|
|||||||
}}
|
}}
|
||||||
defer
|
defer
|
||||||
>
|
>
|
||||||
<div className={clsx("transition-opacity-500", isFreshFetching && "op-20")}>
|
<div className={$("transition-opacity-500", isFreshFetching && "op-20")}>
|
||||||
{!!data?.items?.length && (sources[id].type === "hottest" ? <NewsListHot items={data.items} /> : <NewsListTimeLine items={data.items} />)}
|
{!!data?.items?.length && (sources[id].type === "hottest" ? <NewsListHot items={data.items} /> : <NewsListTimeLine items={data.items} />)}
|
||||||
</div>
|
</div>
|
||||||
</OverlayScrollbar>
|
</OverlayScrollbar>
|
||||||
@ -202,7 +196,7 @@ function DiffNumber({ diff }: { diff: number }) {
|
|||||||
initial={{ opacity: 0, y: -15 }}
|
initial={{ opacity: 0, y: -15 }}
|
||||||
animate={{ opacity: 0.5, y: -7 }}
|
animate={{ opacity: 0.5, y: -7 }}
|
||||||
exit={{ opacity: 0, y: -15 }}
|
exit={{ opacity: 0, y: -15 }}
|
||||||
className={clsx("absolute left-0 text-xs", diff < 0 ? "text-green" : "text-red")}
|
className={$("absolute left-0 text-xs", diff < 0 ? "text-green" : "text-red")}
|
||||||
>
|
>
|
||||||
{diff > 0 ? `+${diff}` : diff}
|
{diff > 0 ? `+${diff}` : diff}
|
||||||
</motion.span>
|
</motion.span>
|
||||||
@ -243,12 +237,12 @@ function NewsListHot({ items }: { items: NewsItem[] }) {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
key={item.id}
|
key={item.id}
|
||||||
title={item.extra?.hover}
|
title={item.extra?.hover}
|
||||||
className={clsx(
|
className={$(
|
||||||
"flex gap-2 items-center mb-2 items-stretch relative",
|
"flex gap-2 items-center mb-2 items-stretch relative",
|
||||||
"hover:bg-neutral-400/10 rounded-md pr-1 visited:(text-neutral-400)",
|
"hover:bg-neutral-400/10 rounded-md pr-1 visited:(text-neutral-400)",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className={clsx("bg-neutral-400/10 min-w-6 flex justify-center items-center rounded-md text-sm")}>
|
<span className={$("bg-neutral-400/10 min-w-6 flex justify-center items-center rounded-md text-sm")}>
|
||||||
{i + 1}
|
{i + 1}
|
||||||
</span>
|
</span>
|
||||||
{!!item.extra?.diff && <DiffNumber diff={item.extra.diff} />}
|
{!!item.extra?.diff && <DiffNumber diff={item.extra.diff} />}
|
||||||
@ -282,7 +276,7 @@ function NewsListTimeLine({ items }: { items: NewsItem[] }) {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
className={clsx("ml-2 px-1 hover:bg-neutral-400/10 rounded-md visited:(text-neutral-400/80)")}
|
className={$("ml-2 px-1 hover:bg-neutral-400/10 rounded-md visited:(text-neutral-400/80)")}
|
||||||
href={width < 768 ? item.mobileUrl || item.url : item.url}
|
href={width < 768 ? item.mobileUrl || item.url : item.url}
|
||||||
title={item.extra?.hover}
|
title={item.extra?.hover}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -14,12 +14,9 @@ import {
|
|||||||
} from "@dnd-kit/core"
|
} from "@dnd-kit/core"
|
||||||
import type { AnimateLayoutChanges } from "@dnd-kit/sortable"
|
import type { AnimateLayoutChanges } from "@dnd-kit/sortable"
|
||||||
import { SortableContext, arrayMove, defaultAnimateLayoutChanges, rectSortingStrategy, useSortable } from "@dnd-kit/sortable"
|
import { SortableContext, arrayMove, defaultAnimateLayoutChanges, rectSortingStrategy, useSortable } from "@dnd-kit/sortable"
|
||||||
import { useAtom } from "jotai"
|
|
||||||
import type { SourceID } from "@shared/types"
|
import type { SourceID } from "@shared/types"
|
||||||
import { CSS } from "@dnd-kit/utilities"
|
import { CSS } from "@dnd-kit/utilities"
|
||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
import { sources } from "@shared/sources"
|
|
||||||
import clsx from "clsx"
|
|
||||||
import type { ItemsProps } from "./card"
|
import type { ItemsProps } from "./card"
|
||||||
import { CardWrapper } from "./card"
|
import { CardWrapper } from "./card"
|
||||||
import { currentSourcesAtom } from "~/atoms"
|
import { currentSourcesAtom } from "~/atoms"
|
||||||
@ -135,15 +132,15 @@ function DndWrapper({ items, setItems, children }: PropsWithChildren<DndProps>)
|
|||||||
|
|
||||||
function CardOverlay({ id }: { id: SourceID }) {
|
function CardOverlay({ id }: { id: SourceID }) {
|
||||||
return (
|
return (
|
||||||
<div className={clsx(
|
<div className={$(
|
||||||
"flex flex-col rounded-2xl p-4 backdrop-blur-5",
|
"flex flex-col rounded-2xl p-4 backdrop-blur-5",
|
||||||
`bg-${sources[id].color}-500 dark:bg-${sources[id].color} bg-op-40!`,
|
`bg-${sources[id].color}-500 dark:bg-${sources[id].color} bg-op-40!`,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={clsx("flex justify-between mx-2 items-center")}>
|
<div className={$("flex justify-between mx-2 items-center")}>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div
|
<div
|
||||||
className={clsx("w-8 h-8 rounded-full bg-cover")}
|
className={$("w-8 h-8 rounded-full bg-cover")}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(/icons/${id.split("-")[0]}.png)`,
|
backgroundImage: `url(/icons/${id.split("-")[0]}.png)`,
|
||||||
}}
|
}}
|
||||||
@ -153,15 +150,15 @@ function CardOverlay({ id }: { id: SourceID }) {
|
|||||||
<span className="text-xl font-bold">
|
<span className="text-xl font-bold">
|
||||||
{sources[id].name}
|
{sources[id].name}
|
||||||
</span>
|
</span>
|
||||||
{sources[id]?.title && <span className={clsx("text-sm", `color-${sources[id].color} bg-base op-80 bg-op-50! px-1 rounded`)}>{sources[id].title}</span>}
|
{sources[id]?.title && <span className={$("text-sm", `color-${sources[id].color} bg-base op-80 bg-op-50! px-1 rounded`)}>{sources[id].title}</span>}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs op-70">拖拽中</span>
|
<span className="text-xs op-70">拖拽中</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx("flex gap-2 text-lg", `color-${sources[id].color}`)}>
|
<div className={$("flex gap-2 text-lg", `color-${sources[id].color}`)}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={clsx("i-ph:dots-six-vertical-duotone", "cursor-grabbing")}
|
className={$("i-ph:dots-six-vertical-duotone", "cursor-grabbing")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import type { FixedColumnID } from "@shared/types"
|
import type { FixedColumnID } from "@shared/types"
|
||||||
import { useAtom } from "jotai"
|
|
||||||
import { useEffect } from "react"
|
|
||||||
import { useTitle } from "react-use"
|
import { useTitle } from "react-use"
|
||||||
import { metadata } from "@shared/metadata"
|
|
||||||
import { NavBar } from "../navbar"
|
import { NavBar } from "../navbar"
|
||||||
import { Dnd } from "./dnd"
|
import { Dnd } from "./dnd"
|
||||||
import { currentColumnIDAtom } from "~/atoms"
|
import { currentColumnIDAtom } from "~/atoms"
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import type { UseOverlayScrollbarsParams } from "overlayscrollbars-react"
|
import type { UseOverlayScrollbarsParams } from "overlayscrollbars-react"
|
||||||
import { useOverlayScrollbars } from "overlayscrollbars-react"
|
import { useOverlayScrollbars } from "overlayscrollbars-react"
|
||||||
import type { HTMLProps, PropsWithChildren } from "react"
|
import type { HTMLProps, PropsWithChildren } from "react"
|
||||||
import { useCallback, useMemo, useRef } from "react"
|
|
||||||
import { defu } from "defu"
|
import { defu } from "defu"
|
||||||
import { useSetAtom } from "jotai"
|
|
||||||
import { useMount } from "react-use"
|
import { useMount } from "react-use"
|
||||||
import { goToTopAtom } from "~/atoms"
|
import { goToTopAtom } from "~/atoms"
|
||||||
|
|
||||||
|
@ -2,15 +2,9 @@ import { Command } from "cmdk"
|
|||||||
import { useMount } from "react-use"
|
import { useMount } from "react-use"
|
||||||
import type { SourceID } from "@shared/types"
|
import type { SourceID } from "@shared/types"
|
||||||
import { useMemo, useRef, useState } from "react"
|
import { useMemo, useRef, useState } from "react"
|
||||||
import { sources } from "@shared/sources"
|
|
||||||
import clsx from "clsx"
|
|
||||||
import { typeSafeObjectEntries } from "@shared/type.util"
|
|
||||||
import pinyin from "@shared/pinyin.json"
|
import pinyin from "@shared/pinyin.json"
|
||||||
import { columns } from "@shared/metadata"
|
|
||||||
import { OverlayScrollbar } from "../overlay-scrollbar"
|
import { OverlayScrollbar } from "../overlay-scrollbar"
|
||||||
import { useSearchBar } from "~/hooks/useSearch"
|
|
||||||
import { CardWrapper } from "~/components/column/card"
|
import { CardWrapper } from "~/components/column/card"
|
||||||
import { useFocusWith } from "~/hooks/useFocus"
|
|
||||||
|
|
||||||
import "./cmdk.css"
|
import "./cmdk.css"
|
||||||
|
|
||||||
@ -128,7 +122,7 @@ function SourceItem({ item }: {
|
|||||||
>
|
>
|
||||||
<span className="flex gap-2 items-center">
|
<span className="flex gap-2 items-center">
|
||||||
<span
|
<span
|
||||||
className={clsx("w-4 h-4 rounded-md bg-cover")}
|
className={$("w-4 h-4 rounded-md bg-cover")}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(/icons/${item.id.split("-")[0]}.png)`,
|
backgroundImage: `url(/icons/${item.id.split("-")[0]}.png)`,
|
||||||
}}
|
}}
|
||||||
@ -136,7 +130,7 @@ function SourceItem({ item }: {
|
|||||||
<span>{item.name}</span>
|
<span>{item.name}</span>
|
||||||
<span className="text-xs text-neutral-400/80 self-end mb-3px">{item.title}</span>
|
<span className="text-xs text-neutral-400/80 self-end mb-3px">{item.title}</span>
|
||||||
</span>
|
</span>
|
||||||
<span className={clsx(isFocused ? "i-ph-star-fill" : "i-ph-star-duotone", "bg-primary op-40")}></span>
|
<span className={$(isFocused ? "i-ph-star-fill" : "i-ph-star-duotone", "bg-primary op-40")}></span>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import clsx from "clsx"
|
|
||||||
import { AnimatePresence, motion } from "framer-motion"
|
import { AnimatePresence, motion } from "framer-motion"
|
||||||
import { useAtomValue, useSetAtom } from "jotai"
|
|
||||||
import { useCallback, useMemo, useRef } from "react"
|
import { useCallback, useMemo, useRef } from "react"
|
||||||
import { useHoverDirty, useMount, useUpdateEffect, useWindowSize } from "react-use"
|
import { useHoverDirty, useMount, useUpdateEffect, useWindowSize } from "react-use"
|
||||||
import type { ToastItem } from "~/atoms/types"
|
import type { ToastItem } from "~/atoms/types"
|
||||||
import { toastAtom } from "~/hooks/useToast"
|
|
||||||
import { Timer } from "~/utils"
|
import { Timer } from "~/utils"
|
||||||
|
|
||||||
const WIDTH = 320
|
const WIDTH = 320
|
||||||
@ -91,11 +88,11 @@ function Item({ info }: { info: ToastItem }) {
|
|||||||
opacity: 1,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={$(
|
||||||
"bg-base rounded-lg shadow-xl relative",
|
"bg-base rounded-lg shadow-xl relative",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={clsx(
|
<div className={$(
|
||||||
`bg-${color}-500 dark:bg-${color} bg-op-40! p2 backdrop-blur-5 rounded-lg w-full`,
|
`bg-${color}-500 dark:bg-${color} bg-op-40! p2 backdrop-blur-5 rounded-lg w-full`,
|
||||||
"flex items-center gap-2",
|
"flex items-center gap-2",
|
||||||
)}
|
)}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { Author, Homepage } from "@shared/consts"
|
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
import { Link } from "@tanstack/react-router"
|
import { Link } from "@tanstack/react-router"
|
||||||
import { useCallback } from "react"
|
|
||||||
import { useAtomValue, useSetAtom } from "jotai"
|
|
||||||
import { useIsFetching } from "@tanstack/react-query"
|
import { useIsFetching } from "@tanstack/react-query"
|
||||||
import clsx from "clsx"
|
|
||||||
import type { SourceID } from "@shared/types"
|
import type { SourceID } from "@shared/types"
|
||||||
import { Homepage, Version } from "@shared/consts"
|
|
||||||
import { NavBar } from "../navbar"
|
import { NavBar } from "../navbar"
|
||||||
import { Menu } from "./menu"
|
import { Menu } from "./menu"
|
||||||
import { currentSourcesAtom, goToTopAtom, refetchSourcesAtom } from "~/atoms"
|
import { currentSourcesAtom, goToTopAtom, refetchSourcesAtom } from "~/atoms"
|
||||||
import { useSearchBar } from "~/hooks/useSearch"
|
|
||||||
|
|
||||||
export function Search() {
|
export function Search() {
|
||||||
const { toggle } = useSearchBar()
|
const { toggle } = useSearchBar()
|
||||||
@ -22,7 +17,7 @@ function GoTop() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title="Go To Top"
|
title="Go To Top"
|
||||||
className={clsx("i-ph:arrow-fat-up-duotone", ok ? "op-50 btn" : "op-0")}
|
className={$("i-ph:arrow-fat-up-duotone", ok ? "op-50 btn" : "op-0")}
|
||||||
onClick={goToTop}
|
onClick={goToTop}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -49,7 +44,7 @@ function Refresh() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title="Refresh"
|
title="Refresh"
|
||||||
className={clsx("i-ph:arrow-counter-clockwise-duotone btn", isFetching && "animate-spin i-ph:circle-dashed-duotone")}
|
className={$("i-ph:arrow-counter-clockwise-duotone btn", isFetching && "animate-spin i-ph:circle-dashed-duotone")}
|
||||||
onClick={refreshAll}
|
onClick={refreshAll}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
import { Homepage } from "@shared/consts"
|
|
||||||
import clsx from "clsx"
|
|
||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
import { useEffect, useRef, useState } from "react"
|
|
||||||
import { useHoverDirty } from "react-use"
|
import { useHoverDirty } from "react-use"
|
||||||
import { useDark } from "~/hooks/useDark"
|
|
||||||
import { useLogin } from "~/hooks/useLogin"
|
|
||||||
|
|
||||||
function ThemeToggle() {
|
function ThemeToggle() {
|
||||||
const { isDark, toggleDark } = useDark()
|
const { isDark, toggleDark } = useDark()
|
||||||
return (
|
return (
|
||||||
<li onClick={toggleDark}>
|
<li onClick={toggleDark}>
|
||||||
<span className={clsx("inline-block", isDark ? "i-ph-moon-stars-duotone" : "i-ph-sun-dim-duotone")} />
|
<span className={$("inline-block", isDark ? "i-ph-moon-stars-duotone" : "i-ph-sun-dim-duotone")} />
|
||||||
<span>
|
<span>
|
||||||
{isDark ? "黑暗模式" : "白天模式"}
|
{isDark ? "黑暗模式" : "白天模式"}
|
||||||
</span>
|
</span>
|
||||||
@ -50,7 +45,7 @@ export function Menu() {
|
|||||||
<div className="absolute right-0 z-99 bg-transparent pt-8 top-0">
|
<div className="absolute right-0 z-99 bg-transparent pt-8 top-0">
|
||||||
<motion.div
|
<motion.div
|
||||||
id="dropdown-menu"
|
id="dropdown-menu"
|
||||||
className={clsx([
|
className={$([
|
||||||
"w-200px",
|
"w-200px",
|
||||||
"bg-primary backdrop-blur-5 bg-op-70! rounded-lg shadow-xl",
|
"bg-primary backdrop-blur-5 bg-op-70! rounded-lg shadow-xl",
|
||||||
])}
|
])}
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import { fixedColumnIds, metadata } from "@shared/metadata"
|
import { fixedColumnIds, metadata } from "@shared/metadata"
|
||||||
import { Link } from "@tanstack/react-router"
|
import { Link } from "@tanstack/react-router"
|
||||||
import clsx from "clsx"
|
|
||||||
import { useAtomValue } from "jotai"
|
|
||||||
import { currentColumnIDAtom } from "~/atoms"
|
import { currentColumnIDAtom } from "~/atoms"
|
||||||
import { useSearchBar } from "~/hooks/useSearch"
|
|
||||||
|
|
||||||
export function NavBar() {
|
export function NavBar() {
|
||||||
const currentId = useAtomValue(currentColumnIDAtom)
|
const currentId = useAtomValue(currentColumnIDAtom)
|
||||||
const { toggle } = useSearchBar()
|
const { toggle } = useSearchBar()
|
||||||
return (
|
return (
|
||||||
<span className={clsx([
|
<span className={$([
|
||||||
"flex p-3 rounded-2xl bg-primary/1 text-sm",
|
"flex p-3 rounded-2xl bg-primary/1 text-sm",
|
||||||
"shadow shadow-primary/20 hover:shadow-primary/50 transition-shadow-500",
|
"shadow shadow-primary/20 hover:shadow-primary/50 transition-shadow-500",
|
||||||
])}
|
])}
|
||||||
@ -17,7 +14,7 @@ export function NavBar() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggle(true)}
|
onClick={() => toggle(true)}
|
||||||
className={clsx(
|
className={$(
|
||||||
"px-2 hover:(bg-primary/10 rounded-md) op-70 dark:op-90",
|
"px-2 hover:(bg-primary/10 rounded-md) op-70 dark:op-90",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -28,7 +25,7 @@ export function NavBar() {
|
|||||||
key={columnId}
|
key={columnId}
|
||||||
to="/c/$column"
|
to="/c/$column"
|
||||||
params={{ column: columnId }}
|
params={{ column: columnId }}
|
||||||
className={clsx(
|
className={$(
|
||||||
"px-2 hover:(bg-primary/10 rounded-md)",
|
"px-2 hover:(bg-primary/10 rounded-md)",
|
||||||
currentId === columnId ? "color-primary font-bold" : "op-70 dark:op-90",
|
currentId === columnId ? "color-primary font-bold" : "op-70 dark:op-90",
|
||||||
)}
|
)}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { useMedia, useUpdateEffect } from "react-use"
|
import { useMedia, useUpdateEffect } from "react-use"
|
||||||
import { atomWithStorage } from "jotai/utils"
|
|
||||||
import { useAtom } from "jotai"
|
|
||||||
|
|
||||||
export declare type ColorScheme = "dark" | "light" | "auto"
|
export declare type ColorScheme = "dark" | "light" | "auto"
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { useCallback, useMemo } from "react"
|
|
||||||
import { useAtom } from "jotai"
|
|
||||||
import type { SourceID } from "@shared/types"
|
import type { SourceID } from "@shared/types"
|
||||||
import { focusSourcesAtom } from "~/atoms"
|
import { focusSourcesAtom } from "~/atoms"
|
||||||
|
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
import { useAtomValue } from "jotai"
|
|
||||||
import { atomWithStorage } from "jotai/utils"
|
|
||||||
import { useCallback } from "react"
|
|
||||||
|
|
||||||
const userAtom = atomWithStorage<{
|
const userAtom = atomWithStorage<{
|
||||||
name?: string
|
name?: string
|
||||||
avatar?: string
|
avatar?: string
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
import { relativeTime } from "@shared/utils"
|
|
||||||
import { atom, useAtomValue } from "jotai"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { useMount } from "react-use"
|
import { useMount } from "react-use"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
import { atom, useAtom } from "jotai"
|
|
||||||
import { useCallback } from "react"
|
|
||||||
|
|
||||||
const searchBarAtom = atom(false)
|
const searchBarAtom = atom(false)
|
||||||
|
|
||||||
export function useSearchBar() {
|
export function useSearchBar() {
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import type { PrimitiveMetadata } from "@shared/types"
|
import type { PrimitiveMetadata } from "@shared/types"
|
||||||
import { useAtom } from "jotai"
|
|
||||||
import { ofetch } from "ofetch"
|
import { ofetch } from "ofetch"
|
||||||
import { useDebounce, useMount } from "react-use"
|
import { useDebounce, useMount } from "react-use"
|
||||||
import { useLogin } from "./useLogin"
|
import { useLogin } from "./useLogin"
|
||||||
import { useToast } from "./useToast"
|
import { useToast } from "./useToast"
|
||||||
import { preprocessMetadata, primitiveMetadataAtom } from "~/atoms"
|
|
||||||
import { safeParseString } from "~/utils"
|
import { safeParseString } from "~/utils"
|
||||||
|
|
||||||
export async function uploadMetadata(metadata: PrimitiveMetadata) {
|
async function uploadMetadata(metadata: PrimitiveMetadata) {
|
||||||
const jwt = safeParseString(localStorage.getItem("jwt"))
|
const jwt = safeParseString(localStorage.getItem("jwt"))
|
||||||
if (!jwt) return
|
if (!jwt) return
|
||||||
await ofetch("/api/me/sync", {
|
await ofetch("/api/me/sync", {
|
||||||
@ -22,7 +20,7 @@ export async function uploadMetadata(metadata: PrimitiveMetadata) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadMetadata(): Promise<PrimitiveMetadata | undefined> {
|
async function downloadMetadata(): Promise<PrimitiveMetadata | undefined> {
|
||||||
const jwt = safeParseString(localStorage.getItem("jwt"))
|
const jwt = safeParseString(localStorage.getItem("jwt"))
|
||||||
if (!jwt) return
|
if (!jwt) return
|
||||||
const { data, updatedTime } = await ofetch("/api/me/sync", {
|
const { data, updatedTime } = await ofetch("/api/me/sync", {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { atom, useSetAtom } from "jotai"
|
|
||||||
import { useCallback } from "react"
|
|
||||||
import type { ToastItem } from "~/atoms/types"
|
import type { ToastItem } from "~/atoms/types"
|
||||||
|
|
||||||
export const toastAtom = atom<ToastItem[]>([])
|
export const toastAtom = atom<ToastItem[]>([])
|
||||||
|
@ -4,14 +4,10 @@ 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 type { QueryClient } from "@tanstack/react-query"
|
import type { QueryClient } from "@tanstack/react-query"
|
||||||
import clsx from "clsx"
|
|
||||||
import { Header } from "~/components/header"
|
import { Header } from "~/components/header"
|
||||||
import { useOnReload } from "~/hooks/useOnReload"
|
|
||||||
import { GlobalOverlayScrollbar } from "~/components/common/overlay-scrollbar"
|
import { GlobalOverlayScrollbar } from "~/components/common/overlay-scrollbar"
|
||||||
import { useSync } from "~/hooks/useSync"
|
|
||||||
import { Footer } from "~/components/footer"
|
import { Footer } from "~/components/footer"
|
||||||
import { Toast } from "~/components/common/toast"
|
import { Toast } from "~/components/common/toast"
|
||||||
import { usePWA } from "~/hooks/usePWA"
|
|
||||||
import { SearchBar } from "~/components/common/search-bar"
|
import { SearchBar } from "~/components/common/search-bar"
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<{
|
export const Route = createRootRouteWithContext<{
|
||||||
@ -42,14 +38,14 @@ function RootComponent() {
|
|||||||
usePWA()
|
usePWA()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GlobalOverlayScrollbar className={clsx([
|
<GlobalOverlayScrollbar className={$([
|
||||||
"h-full overflow-x-auto px-4",
|
"h-full overflow-x-auto px-4",
|
||||||
"md:(px-10)",
|
"md:(px-10)",
|
||||||
"lg:(px-24)",
|
"lg:(px-24)",
|
||||||
])}
|
])}
|
||||||
>
|
>
|
||||||
<header
|
<header
|
||||||
className={clsx([
|
className={$([
|
||||||
"grid items-center py-4 px-5",
|
"grid items-center py-4 px-5",
|
||||||
"lg:(py-6)",
|
"lg:(py-6)",
|
||||||
"sticky top-0 z-10 backdrop-blur-md",
|
"sticky top-0 z-10 backdrop-blur-md",
|
||||||
@ -60,7 +56,7 @@ function RootComponent() {
|
|||||||
>
|
>
|
||||||
<Header />
|
<Header />
|
||||||
</header>
|
</header>
|
||||||
<main className={clsx([
|
<main className={$([
|
||||||
"mt-2",
|
"mt-2",
|
||||||
"min-h-[calc(100vh-180px)]",
|
"min-h-[calc(100vh-180px)]",
|
||||||
"md:(min-h-[calc(100vh-175px)])",
|
"md:(min-h-[calc(100vh-175px)])",
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { createFileRoute, redirect } from "@tanstack/react-router"
|
import { createFileRoute, redirect } from "@tanstack/react-router"
|
||||||
import { fixedColumnIds } from "@shared/metadata"
|
|
||||||
import { Column } from "~/components/column"
|
import { Column } from "~/components/column"
|
||||||
|
|
||||||
export const Route = createFileRoute("/c/$column")({
|
export const Route = createFileRoute("/c/$column")({
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { useAtomValue } from "jotai"
|
|
||||||
import { focusSourcesAtom } from "~/atoms"
|
import { focusSourcesAtom } from "~/atoms"
|
||||||
import { Column } from "~/components/column"
|
import { Column } from "~/components/column"
|
||||||
|
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
"@shared/*": ["shared/*"]
|
"@shared/*": ["shared/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src", "shared"]
|
"include": ["src", "shared", "./auto-imports.app.d.ts"]
|
||||||
}
|
}
|
||||||
|
121
vite.config.ts
121
vite.config.ts
@ -2,118 +2,49 @@ import process from "node:process"
|
|||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
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 { TanStackRouterVite } from "@tanstack/router-plugin/vite"
|
import { TanStackRouterVite } from "@tanstack/router-plugin/vite"
|
||||||
import tsconfigPath from "vite-tsconfig-paths"
|
|
||||||
import unocss from "unocss/vite"
|
import unocss from "unocss/vite"
|
||||||
import dotenv from "dotenv"
|
import dotenv from "dotenv"
|
||||||
import type { VitePWAOptions } from "vite-plugin-pwa"
|
import autoImport from "unplugin-auto-import/vite"
|
||||||
import { VitePWA } from "vite-plugin-pwa"
|
import nitro from "./nitro.config"
|
||||||
import { projectDir } from "./shared/dir"
|
import { projectDir } from "./shared/dir"
|
||||||
import { RollopGlob } from "./tools/rollup-glob"
|
import pwa from "./pwa.config"
|
||||||
|
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: join(projectDir, ".env.server"),
|
path: join(projectDir, ".env.server"),
|
||||||
})
|
})
|
||||||
|
|
||||||
const pwaOption: Partial<VitePWAOptions> = {
|
|
||||||
includeAssets: ["icon.svg", "apple-touch-icon.png"],
|
|
||||||
manifest: {
|
|
||||||
name: "NewsNow",
|
|
||||||
short_name: "NewsNow",
|
|
||||||
description: "Elegant reading of real-time and hottest news",
|
|
||||||
theme_color: "#F14D42",
|
|
||||||
icons: [
|
|
||||||
{
|
|
||||||
src: "pwa-192x192.png",
|
|
||||||
sizes: "192x192",
|
|
||||||
type: "image/png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "pwa-512x512.png",
|
|
||||||
sizes: "512x512",
|
|
||||||
type: "image/png",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "pwa-512x512.png",
|
|
||||||
sizes: "512x512",
|
|
||||||
type: "image/png",
|
|
||||||
purpose: "any",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: "pwa-512x512.png",
|
|
||||||
sizes: "512x512",
|
|
||||||
type: "image/png",
|
|
||||||
purpose: "maskable",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
workbox: {
|
|
||||||
navigateFallbackDenylist: [/^\/api/],
|
|
||||||
},
|
|
||||||
devOptions: {
|
|
||||||
enabled: process.env.SW_DEV === "true",
|
|
||||||
type: "module",
|
|
||||||
navigateFallback: "index.html",
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const nitroOption: Parameters<typeof nitro>[0] = {
|
|
||||||
experimental: {
|
|
||||||
database: true,
|
|
||||||
},
|
|
||||||
rollupConfig: {
|
|
||||||
plugins: [RollopGlob()],
|
|
||||||
},
|
|
||||||
sourceMap: false,
|
|
||||||
database: {
|
|
||||||
default: {
|
|
||||||
connector: "sqlite",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
preset: "node-server",
|
|
||||||
alias: {
|
|
||||||
"@shared": join(projectDir, "shared"),
|
|
||||||
"#": join(projectDir, "server"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.VERCEL) {
|
|
||||||
nitroOption.preset = "vercel-edge"
|
|
||||||
// You can use other online database, do it yourself. For more info: https://db0.unjs.io/connectors
|
|
||||||
nitroOption.database = undefined
|
|
||||||
} else if (process.env.CF_PAGES) {
|
|
||||||
nitroOption.preset = "cloudflare-pages"
|
|
||||||
nitroOption.database = {
|
|
||||||
default: {
|
|
||||||
connector: "cloudflare-d1",
|
|
||||||
options: {
|
|
||||||
bindingName: "NEWSNOW_DB",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else if (process.env.BUN) {
|
|
||||||
nitroOption.preset = "bun"
|
|
||||||
nitroOption.database = {
|
|
||||||
default: {
|
|
||||||
connector: "bun-sqlite",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
define: {
|
define: {
|
||||||
__LOGIN_URL__: process.env.G_CLIENT_ID ? `"https://github.com/login/oauth/authorize?client_id=${process.env.G_CLIENT_ID}"` : `"/api/login"`,
|
__LOGIN_URL__: process.env.G_CLIENT_ID ? `"https://github.com/login/oauth/authorize?client_id=${process.env.G_CLIENT_ID}"` : `"/api/login"`,
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
minify: false,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"~": join(projectDir, "src"),
|
||||||
|
"@shared": join(projectDir, "shared"),
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
tsconfigPath(),
|
|
||||||
TanStackRouterVite({
|
TanStackRouterVite({
|
||||||
autoCodeSplitting: true,
|
// error with unplugin-auto-import and vite-plugin-pwa
|
||||||
|
// autoCodeSplitting: true,
|
||||||
|
}),
|
||||||
|
autoImport({
|
||||||
|
dirs: ["src/hooks", "shared", "src/utils", "src/atoms"],
|
||||||
|
imports: ["react", "jotai", "jotai/utils", {
|
||||||
|
clsx: [
|
||||||
|
["default", "$"],
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
dts: "auto-imports.app.d.ts",
|
||||||
}),
|
}),
|
||||||
unocss(),
|
unocss(),
|
||||||
react(),
|
react(),
|
||||||
VitePWA(pwaOption),
|
pwa(),
|
||||||
nitro(nitroOption),
|
nitro(),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import { join } from "node:path"
|
||||||
import { defineConfig } from "vitest/config"
|
import { defineConfig } from "vitest/config"
|
||||||
import autoImport from "unplugin-auto-import/vite"
|
import autoImport from "unplugin-auto-import/vite"
|
||||||
import tsconfigPath from "vite-tsconfig-paths"
|
|
||||||
import { resolveModuleExportNames } from "mlly"
|
import { resolveModuleExportNames } from "mlly"
|
||||||
|
import { projectDir } from "./shared/dir"
|
||||||
|
|
||||||
const h3Exports = await resolveModuleExportNames("h3", {
|
const h3Exports = await resolveModuleExportNames("h3", {
|
||||||
url: import.meta.url,
|
url: import.meta.url,
|
||||||
@ -13,19 +14,24 @@ export default defineConfig({
|
|||||||
environment: "node",
|
environment: "node",
|
||||||
include: ["server/**/*.test.ts", "shared/**/*.test.ts", "test/**/*.test.ts"],
|
include: ["server/**/*.test.ts", "shared/**/*.test.ts", "test/**/*.test.ts"],
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@shared": join(projectDir, "shared"),
|
||||||
|
"#": join(projectDir, "server"),
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
tsconfigPath(),
|
|
||||||
// https://github.com/unjs/nitro/blob/v2/src/core/config/resolvers/imports.ts
|
// https://github.com/unjs/nitro/blob/v2/src/core/config/resolvers/imports.ts
|
||||||
autoImport({
|
autoImport({
|
||||||
imports: ["vitest", {
|
imports: [{
|
||||||
from: "h3",
|
from: "h3",
|
||||||
imports: h3Exports.filter(n => !/^[A-Z]/.test(n) && n !== "use"),
|
imports: h3Exports.filter(n => !/^[A-Z]/.test(n) && n !== "use"),
|
||||||
}, {
|
}, {
|
||||||
from: "ofetch",
|
from: "ofetch",
|
||||||
imports: ["$fetch", "ofetch"],
|
imports: ["$fetch", "ofetch"],
|
||||||
}],
|
}],
|
||||||
dirs: ["server/utils"],
|
dirs: ["server/utils", "shared"],
|
||||||
dts: "dist/.nitro/types/nitro-imports.d.ts",
|
dts: false,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user