chore: rename to metadata
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
export default defineEventHandler(() => {
|
||||
})
|
||||
@@ -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"],
|
||||
},
|
||||
}
|
||||
@@ -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]),
|
||||
}]
|
||||
}
|
||||
}))
|
||||
+53
-51
@@ -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<string, OriginSource>
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -17,3 +17,7 @@ export function typeSafeObjectEntries<T extends Record<PropertyKey, unknown>>(ob
|
||||
export function typeSafeObjectKeys<T extends Record<PropertyKey, unknown>>(obj: T): (keyof T)[] {
|
||||
return Object.keys(obj) as (keyof T)[]
|
||||
}
|
||||
|
||||
export function typeSafeObjectValues<T extends Record<PropertyKey, unknown>>(obj: T): T[keyof T][] {
|
||||
return Object.values(obj) as T[keyof T][]
|
||||
}
|
||||
|
||||
+12
-11
@@ -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<keyof typeof colors, "current" | "inherit" | "transparent" | "black" | "white">
|
||||
@@ -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<string, {
|
||||
title: string
|
||||
/**
|
||||
* @default latest
|
||||
*/
|
||||
type?: "hottest" | "latest"
|
||||
type?: "hottest" | "realtime"
|
||||
active?: boolean
|
||||
interval?: number
|
||||
}>
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<T>(
|
||||
key: string,
|
||||
initialValue: T | (() => T),
|
||||
initFn?: ((stored: T) => T),
|
||||
storage?: (next: T) => void,
|
||||
): 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 {
|
||||
//
|
||||
}
|
||||
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<Record<ColumnID, SourceID[]>>("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)
|
||||
@@ -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<Record<ColumnID, SourceID[]>>("localsources", () => {
|
||||
@@ -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"
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { PrimitiveAtom } from "jotai"
|
||||
import { atom } from "jotai"
|
||||
|
||||
export function atomWithLocalStorage<T>(
|
||||
key: string,
|
||||
initialValue: T | (() => 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 {
|
||||
//
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -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")({
|
||||
|
||||
Reference in New Issue
Block a user