diff --git a/server/routes/me.ts b/server/routes/me.ts deleted file mode 100644 index 073260f..0000000 --- a/server/routes/me.ts +++ /dev/null @@ -1,2 +0,0 @@ -export default defineEventHandler(() => { -}) diff --git a/shared/data.ts b/shared/data.ts deleted file mode 100644 index e496b8b..0000000 --- a/shared/data.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Metadata } from "./types" - -export const columnIds = ["focus", "realtime", "hottest", "china", "world", "tech", "finance"] as const - -export const metadata: Metadata = { - focus: { - name: "关注", - sources: [], - }, - realtime: { - name: "实时", - sources: ["weibo", "wallstreetcn", "ithome", "36kr", "zaobao"], - }, - hottest: { - name: "最热", - sources: ["weibo", "douyin", "zhihu", "toutiao"], - }, - china: { - name: "国内", - sources: ["weibo", "douyin", "toutiao", "zhihu"], - }, - world: { - name: "国际", - sources: ["sputniknewscn", "zaobao", "cankaoxiaoxi"], - }, - tech: { - name: "科技", - sources: ["ithome", "v2ex", "coolapk", "36kr-quick"], - }, - finance: { - name: "财经", - sources: ["wallstreetcn", "36kr-quick"], - }, -} diff --git a/shared/metadata.ts b/shared/metadata.ts new file mode 100644 index 0000000..812b810 --- /dev/null +++ b/shared/metadata.ts @@ -0,0 +1,58 @@ +import { sources } from "./sources" +import { typeSafeObjectEntries, typeSafeObjectFromEntries } from "./type.util" +import type { Metadata } from "./types" + +export const columnIds = ["focus", "realtime", "hottest", "china", "world", "tech", "finance"] as const + +const originMetadata: Metadata = { + china: { + name: "国内", + sources: ["weibo", "douyin", "toutiao", "zhihu"], + }, + world: { + name: "国际", + sources: ["zaobao", "cankaoxiaoxi"], + }, + tech: { + name: "科技", + sources: ["ithome", "v2ex", "coolapk", "36kr-quick"], + }, + finance: { + name: "财经", + sources: ["wallstreetcn", "36kr-quick"], + }, + focus: { + name: "关注", + sources: [], + }, + realtime: { + name: "实时", + sources: [], + }, + hottest: { + name: "最热", + sources: [], + }, +} + +export const metadata = typeSafeObjectFromEntries(typeSafeObjectEntries(originMetadata).map(([k, v]) => { + switch (k) { + case "focus": + return [k, v] + case "hottest": + return [k, { + ...v, + sources: typeSafeObjectEntries(sources).filter(([, v]) => v.type === "hottest" && !v.redirect).map(([k]) => k), + }] + case "realtime": + return [k, { + ...v, + sources: typeSafeObjectEntries(sources).filter(([, v]) => v.type === "realtime" && !v.redirect).map(([k]) => k), + }] + default: + return [k, { + ...v, + sources: v.sources.filter(s => sources[s]), + }] + } +})) diff --git a/shared/sources.ts b/shared/sources.ts index 65212d7..14f8db9 100644 --- a/shared/sources.ts +++ b/shared/sources.ts @@ -17,6 +17,25 @@ export const originSources = { color: "slate", home: "https://v2ex.com/", }, + "zhihu": { + name: "知乎", + type: "hottest", + home: "https://www.zhihu.com", + }, + "weibo": { + name: "微博", + title: "实时热搜", + type: "hottest", + color: "red", + interval: Time.Realtime, + home: "https://weibo.com", + }, + "zaobao": { + name: "联合早报", + interval: Time.Common, + color: "red", + home: "https://www.zaobao.com", + }, "coolapk": { name: "酷安", type: "hottest", @@ -27,22 +46,13 @@ export const originSources = { "wallstreetcn": { name: "华尔街见闻", interval: Time.Fast, + type: "realtime", home: "https://wallstreetcn.com/", title: "快讯", }, - "sputniknewscn": { - name: "卫星通讯社", - color: "orange", - home: "https://sputniknews.cn", - }, - "cankaoxiaoxi": { - name: "参考消息", - color: "red", - interval: Time.Common, - home: "https://china.cankaoxiaoxi.com", - }, "36kr": { name: "36氪", + type: "realtime", home: "https://36kr.com", sub: { quick: { @@ -58,36 +68,14 @@ export const originSources = { }, "hupu": { name: "虎扑", + active: false, home: "https://hupu.com", }, - "zhihu": { - name: "知乎", - type: "hottest", - home: "https://www.zhihu.com", - }, - "weibo": { - name: "微博", - title: "实时热搜", - type: "hottest", - color: "red", - interval: Time.Realtime, - home: "https://weibo.com", - }, "tieba": { name: "百度贴吧", + active: false, home: "https://tieba.baidu.com", }, - "zaobao": { - name: "联合早报", - interval: Time.Common, - color: "red", - home: "https://www.zaobao.com", - }, - "thepaper": { - name: "澎湃新闻", - interval: Time.Common, - home: "https://www.thepaper.cn", - }, "toutiao": { name: "今日头条", type: "hottest", @@ -97,8 +85,27 @@ export const originSources = { "ithome": { name: "IT之家", color: "red", + type: "realtime", home: "https://www.ithome.com", }, + "thepaper": { + name: "澎湃新闻", + interval: Time.Common, + active: false, + home: "https://www.thepaper.cn", + }, + "sputniknewscn": { + name: "卫星通讯社", + color: "orange", + active: false, + home: "https://sputniknews.cn", + }, + "cankaoxiaoxi": { + name: "参考消息", + color: "red", + interval: Time.Common, + home: "https://china.cankaoxiaoxi.com", + }, } as const satisfies Record export const sources = genSources() @@ -106,36 +113,31 @@ function genSources() { const _: [SourceID, Source][] = [] Object.entries(originSources).forEach(([id, source]: [any, OriginSource]) => { + const parent = { + name: source.name, + type: source.type, + active: source.active ?? true, + color: source.color ?? "red", + interval: source.interval ?? Time.Default, + } if (source.sub && Object.keys(source.sub).length) { Object.entries(source.sub).forEach(([subId, subSource], i) => { if (i === 0) { _.push([id, { redirect: `${id}-${subId}`, - name: source.name, - type: source.type, - color: source.color ?? "blue", - interval: source.interval ?? Time.Default, + ...parent, ...subSource, }] as [any, Source]) } - _.push([`${id}-${subId}`, { - name: source.name, - type: source.type, - color: source.color ?? "blue", - interval: source.interval ?? Time.Default, - ...subSource, - }] as [any, Source]) + _.push([`${id}-${subId}`, { ...parent, ...subSource }] as [any, Source]) }) } else { _.push([id, { - name: source.name, - type: source.type, - color: source.color ?? "blue", - interval: source.interval ?? Time.Default, title: source.title, + ...parent, }]) } }) - return typeSafeObjectFromEntries(_) + return typeSafeObjectFromEntries(_.filter(([_, v]) => v.active)) } diff --git a/shared/type.util.ts b/shared/type.util.ts index 0257517..f67c520 100644 --- a/shared/type.util.ts +++ b/shared/type.util.ts @@ -17,3 +17,7 @@ export function typeSafeObjectEntries>(ob export function typeSafeObjectKeys>(obj: T): (keyof T)[] { return Object.keys(obj) as (keyof T)[] } + +export function typeSafeObjectValues>(obj: T): T[keyof T][] { + return Object.values(obj) as T[keyof T][] +} diff --git a/shared/types.ts b/shared/types.ts index bf6d32c..8165261 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -1,5 +1,5 @@ import type { colors } from "unocss/preset-mini" -import type { columnIds } from "./data" +import type { columnIds } from "./metadata" import type { originSources } from "./sources" export type Color = Exclude @@ -8,10 +8,11 @@ type ConstSources = typeof originSources type MainSourceID = keyof(ConstSources) export type SourceID = { - [Key in MainSourceID]: ConstSources[Key] extends { sub?: infer SubType } ? keyof { + [Key in MainSourceID]: ConstSources[Key] extends { active?: false } ? never : + ConstSources[Key] extends { sub?: infer SubSource } ? { // @ts-expect-error >_< - [K in keyof SubType as `${Key}-${K}` ]: never - } | Key : Key; + [SubKey in keyof SubSource ]: SubSource[SubKey] extends { active?: false } ? never : `${Key}-${SubKey}` + }[keyof SubSource] | Key : Key; }[MainSourceID] export type ColumnID = (typeof columnIds)[number] @@ -24,18 +25,17 @@ export interface OriginSource { * 刷新的间隔时间,复用缓存 */ interval?: number + type?: "hottest" | "realtime" /** - * @default latest + * @default true */ - type?: "hottest" | "latest" + active?: boolean home: string color?: Color sub?: Record } @@ -43,8 +43,9 @@ export interface OriginSource { export interface Source { name: string title?: string - type?: "hottest" | "latest" + type?: "hottest" | "realtime" color: Color + active: boolean interval: number redirect?: SourceID } diff --git a/src/atoms/atomWithLocalStorage.ts b/src/atoms/atomWithLocalStorage.ts new file mode 100644 index 0000000..766bba7 --- /dev/null +++ b/src/atoms/atomWithLocalStorage.ts @@ -0,0 +1,67 @@ +import { metadata } from "@shared/metadata" +import { typeSafeObjectEntries, typeSafeObjectFromEntries } from "@shared/type.util" +import type { ColumnID, SourceID } from "@shared/types" +import type { PrimitiveAtom } from "jotai" +import { atom } from "jotai" +import { ofetch } from "ofetch" + +function sync(nextValue: any) { + if (__ENABLE_LOGIN__) { + const jwt = localStorage.getItem("user_jwt") + if (jwt) { + ofetch("/me/sync", { + method: "POST", + body: { data: nextValue }, + headers: { + Authorization: `Bearer ${localStorage.getItem("user_jwt")}`, + }, + }).then(console.log).catch(e => console.error(e)) + } + } +} + +export function atomWithLocalStorage( + key: string, + initialValue: T | (() => T), + initFn?: ((stored: T) => T), + storage?: (next: T) => void, +): PrimitiveAtom { + const getInitialValue = () => { + const item = localStorage.getItem(key) + try { + if (item !== null) { + const stored = JSON.parse(item) + if (initFn) return initFn(stored) + return stored + } + } catch { + // + } + if (initialValue instanceof Function) return initialValue() + else return initialValue + } + const baseAtom = atom(getInitialValue()) + const derivedAtom = atom( + get => get(baseAtom), + (get, set, update: any) => { + const nextValue = typeof update === "function" ? update(get(baseAtom)) : update + set(baseAtom, nextValue) + localStorage.setItem(key, JSON.stringify(nextValue)) + storage?.(nextValue) + }, + ) + return derivedAtom +} + +const initialSources = typeSafeObjectFromEntries(typeSafeObjectEntries(metadata).map(([id, val]) => [id, val.sources])) +export const localSourcesAtom = atomWithLocalStorage>("localsources", initialSources, (stored) => { + return typeSafeObjectFromEntries(typeSafeObjectEntries({ + ...initialSources, + ...stored, + }).filter(([id]) => initialSources[id]).map(([id, val]) => { + if (id === "focus") return [id, val] + const oldS = val.filter(k => initialSources[id].includes(k)) + const newS = initialSources[id].filter(k => !oldS.includes(k)) + return [id, [...oldS, ...newS]] + })) +}, sync) diff --git a/src/atoms.ts b/src/atoms/index.ts similarity index 95% rename from src/atoms.ts rename to src/atoms/index.ts index d6f67a9..dd3b8c1 100644 --- a/src/atoms.ts +++ b/src/atoms/index.ts @@ -1,9 +1,9 @@ import { atom } from "jotai" import type { ColumnID, SourceID } from "@shared/types" -import { metadata } from "@shared/data" +import { metadata } from "@shared/metadata" import { sources } from "@shared/sources" import { typeSafeObjectEntries, typeSafeObjectFromEntries } from "@shared/type.util" -import { atomWithLocalStorage } from "./hooks/atomWithLocalStorage" +import { atomWithLocalStorage } from "./atomWithLocalStorage" const initialSources = typeSafeObjectFromEntries(typeSafeObjectEntries(metadata).map(([id, val]) => [id, val.sources])) export const localSourcesAtom = atomWithLocalStorage>("localsources", () => { diff --git a/src/components/column/index.tsx b/src/components/column/index.tsx index a7f4543..00f7de7 100644 --- a/src/components/column/index.tsx +++ b/src/components/column/index.tsx @@ -1,4 +1,4 @@ -import { columnIds, metadata } from "@shared/data" +import { columnIds, metadata } from "@shared/metadata" import type { ColumnID } from "@shared/types" import { Link } from "@tanstack/react-router" import clsx from "clsx" diff --git a/src/hooks/atomWithLocalStorage.ts b/src/hooks/atomWithLocalStorage.ts deleted file mode 100644 index a2cac0a..0000000 --- a/src/hooks/atomWithLocalStorage.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { PrimitiveAtom } from "jotai" -import { atom } from "jotai" - -export function atomWithLocalStorage( - key: string, - initialValue: T | (() => T), - initFn?: ((stored: T) => T), -): PrimitiveAtom { - const getInitialValue = () => { - const item = localStorage.getItem(key) - try { - if (item !== null) { - const stored = JSON.parse(item) - if (initFn) return initFn(stored) - return stored - } - } catch { - // - } - if (initialValue instanceof Function) return initialValue() - else 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 -} diff --git a/src/routes/s.$column.tsx b/src/routes/s.$column.tsx index 2360262..ae70a43 100644 --- a/src/routes/s.$column.tsx +++ b/src/routes/s.$column.tsx @@ -1,5 +1,5 @@ import { createFileRoute, redirect } from "@tanstack/react-router" -import { columnIds } from "@shared/data" +import { columnIds } from "@shared/metadata" import { Column } from "~/components/column" export const Route = createFileRoute("/s/$column")({