refactor: useSync

This commit is contained in:
Ou 2024-10-17 15:33:33 +08:00
parent d2fcae1802
commit 3dc9c4b28c
8 changed files with 87 additions and 92 deletions

View File

@ -43,7 +43,6 @@ export default defineEventHandler(async (event) => {
},
})
console.log(userInfo)
const userID = String(userInfo.id)
await userTable.addUser(userID, userInfo.notification_email || userInfo.email, "github")

View File

@ -68,12 +68,12 @@ export const originSources = {
},
"hupu": {
name: "虎扑",
active: false,
disable: true,
home: "https://hupu.com",
},
"tieba": {
name: "百度贴吧",
active: false,
disable: true,
home: "https://tieba.baidu.com",
},
"toutiao": {
@ -91,13 +91,13 @@ export const originSources = {
"thepaper": {
name: "澎湃新闻",
interval: Time.Common,
active: false,
disable: true,
home: "https://www.thepaper.cn",
},
"sputniknewscn": {
name: "卫星通讯社",
color: "orange",
active: false,
disable: true,
home: "https://sputniknews.cn",
},
"cankaoxiaoxi": {
@ -116,7 +116,7 @@ function genSources() {
const parent = {
name: source.name,
type: source.type,
active: source.active ?? true,
disable: source.disable,
color: source.color ?? "red",
interval: source.interval ?? Time.Default,
}
@ -139,5 +139,5 @@ function genSources() {
}
})
return typeSafeObjectFromEntries(_.filter(([_, v]) => v.active))
return typeSafeObjectFromEntries(_.filter(([_, v]) => !v.disable))
}

View File

@ -8,10 +8,10 @@ type ConstSources = typeof originSources
type MainSourceID = keyof(ConstSources)
export type SourceID = {
[Key in MainSourceID]: ConstSources[Key] extends { active?: false } ? never :
[Key in MainSourceID]: ConstSources[Key] extends { disable?: true } ? never :
ConstSources[Key] extends { sub?: infer SubSource } ? {
// @ts-expect-error >_<
[SubKey in keyof SubSource ]: SubSource[SubKey] extends { active?: false } ? never : `${Key}-${SubKey}`
[SubKey in keyof SubSource ]: SubSource[SubKey] extends { disable?: true } ? never : `${Key}-${SubKey}`
}[keyof SubSource] | Key : Key;
}[MainSourceID]
@ -33,15 +33,15 @@ export interface OriginSource {
interval?: number
type?: "hottest" | "realtime"
/**
* @default true
* @default false
*/
active?: boolean
disable?: boolean
home: string
color?: Color
sub?: Record<string, {
title: string
type?: "hottest" | "realtime"
active?: boolean
disable?: boolean
interval?: number
}>
}
@ -51,7 +51,7 @@ export interface Source {
title?: string
type?: "hottest" | "realtime"
color: Color
active: boolean
disable?: boolean
interval: number
redirect?: SourceID
}

View File

@ -4,6 +4,7 @@ import type { PrimitiveAtom } from "jotai"
import { atom } from "jotai"
import type { PrimitiveMetadata } from "@shared/types"
import { verifyPrimitiveMetadata } from "@shared/verify"
import { sources } from "@shared/sources"
import type { Update } from "./types"
function createPrimitiveMetadataAtom(
@ -17,40 +18,42 @@ function createPrimitiveMetadataAtom(
if (item) {
const stored = JSON.parse(item) as PrimitiveMetadata
verifyPrimitiveMetadata(stored)
return preprocess(stored)
return preprocess({
...stored,
action: "init",
})
}
} catch { }
return initialValue
}
const baseAtom = atom(getInitialValue())
const derivedAtom = atom(
get => get(baseAtom),
(get, set, update: Update<PrimitiveMetadata>) => {
const nextValue = update instanceof Function ? update(get(baseAtom)) : update
const derivedAtom = atom(get => get(baseAtom), (get, set, update: Update<PrimitiveMetadata>) => {
const nextValue = update instanceof Function ? update(get(baseAtom)) : update
if (nextValue.updatedTime > get(baseAtom).updatedTime) {
set(baseAtom, nextValue)
localStorage.setItem(key, JSON.stringify(nextValue))
},
)
}
})
return derivedAtom
}
const initialMetadata = typeSafeObjectFromEntries(typeSafeObjectEntries(metadata).map(([id, val]) => [id, val.sources]))
export function preprocessMetadata(target: PrimitiveMetadata, action: PrimitiveMetadata["action"] = "init") {
export function preprocessMetadata(target: PrimitiveMetadata) {
return {
data: {
...initialMetadata,
...typeSafeObjectFromEntries(
typeSafeObjectEntries(target.data)
.filter(([id]) => initialMetadata[id])
.map(([id, sources]) => {
if (id === "focus") return [id, sources]
const oldS = sources.filter(k => initialMetadata[id].includes(k))
.map(([id, s]) => {
if (id === "focus") return [id, s.filter(k => sources[k])]
const oldS = s.filter(k => initialMetadata[id].includes(k))
const newS = initialMetadata[id].filter(k => !oldS.includes(k))
return [id, [...oldS, ...newS]]
}),
),
},
action,
action: target.action,
updatedTime: target.updatedTime,
} as PrimitiveMetadata
}

View File

@ -13,7 +13,7 @@ export function Column({ id }: { id: ColumnID }) {
useEffect(() => {
setCurrentColumnID(id)
}, [id, setCurrentColumnID])
useTitle(`NewsNow ${metadata[id].name}`)
useTitle(`NewsNow | ${metadata[id].name}`)
return (
<>
<div className="w-full flex justify-center">

View File

@ -46,13 +46,13 @@ export function OverlayScrollbar({ children, options, events, defer, ...props }:
export function GlobalOverlayScrollbar({ children, ...props }: PropsWithChildren<HTMLProps<HTMLDivElement>>) {
const ref = useRef<HTMLDivElement>(null)
const lastScroll = useRef(0)
const lastTrigger = useRef(0)
const timer = useRef<any>()
const setGoToTop = useSetAtom(goToTopAtom)
const onScroll = useCallback((e: Event) => {
const now = Date.now()
if (now - lastScroll.current > 50) {
lastScroll.current = now
if (now - lastTrigger.current > 50) {
lastTrigger.current = now
clearTimeout(timer.current)
timer.current = setTimeout(
() => {

View File

@ -1,18 +0,0 @@
import { useEffect, useRef, useState } from "react"
export function useSticky() {
const ref = useRef<HTMLDivElement>(null)
const [isSticky, setIsSticky] = useState(false)
useEffect(() => {
const observer = new IntersectionObserver(
([event]) => setIsSticky(event.intersectionRatio < 1),
{ threshold: [1], rootMargin: "-1px 0px 0px 0px" },
)
observer.observe(ref.current!)
return () => observer.disconnect()
}, [])
return { ref, isSticky }
}

View File

@ -1,57 +1,68 @@
import type { PrimitiveMetadata } from "@shared/types"
import { useAtom } from "jotai"
import { ofetch } from "ofetch"
import { useCallback, useEffect } from "react"
import { useEffect } from "react"
import { useDebounce } from "react-use"
import { preprocessMetadata, primitiveMetadataAtom } from "~/atoms"
export async function uploadMetadata(metadata: PrimitiveMetadata) {
if (!__ENABLE_LOGIN__) return
const jwt = localStorage.getItem("user_jwt")
if (!jwt) return
try {
await ofetch("/api/me/sync", {
method: "POST",
headers: {
Authorization: `Bearer ${jwt}`,
},
body: {
data: metadata.data,
updatedTime: metadata.updatedTime,
},
})
} catch (e) {
console.error(e)
}
}
export async function downloadMetadata(): Promise<PrimitiveMetadata | undefined> {
if (!__ENABLE_LOGIN__) return
const jwt = localStorage.getItem("user_jwt")
if (!jwt) return
try {
const { data, updatedTime } = await ofetch("/api/me/sync", {
headers: {
Authorization: `Bearer ${jwt}`,
},
}) as PrimitiveMetadata
// 不用同步 action 字段
if (data) {
return {
action: "sync",
data,
updatedTime,
}
}
} catch (e) {
console.error(e)
}
}
export function useSync() {
const [primitiveMetadata, setPrimitiveMetadata] = useAtom(primitiveMetadataAtom)
const uploadMetadata = useCallback(async () => {
if (!__ENABLE_LOGIN__) return
const jwt = localStorage.getItem("user_jwt")
if (!jwt) return
if (primitiveMetadata.action !== "manual") return
try {
await ofetch("/api/me/sync", {
method: "POST",
headers: {
Authorization: `Bearer ${jwt}`,
},
body: {
data: primitiveMetadata.data,
updatedTime: primitiveMetadata.updatedTime,
},
})
} catch (e) {
console.error(e)
}
}, [primitiveMetadata])
const downloadMetadata = useCallback(async () => {
if (!__ENABLE_LOGIN__) return
const jwt = localStorage.getItem("user_jwt")
if (!jwt) return
try {
const { data, updatedTime } = await ofetch("/api/me/sync", {
headers: {
Authorization: `Bearer ${jwt}`,
},
}) as PrimitiveMetadata
if (data && updatedTime > primitiveMetadata.updatedTime) {
// 不用同步 action 字段
setPrimitiveMetadata(preprocessMetadata({ action: "sync", data, updatedTime }, "sync"))
useDebounce(async () => {
if (primitiveMetadata.action === "manual") {
uploadMetadata(primitiveMetadata)
}
}, 10000, [primitiveMetadata])
useEffect(() => {
const fn = async () => {
const metadata = await downloadMetadata()
if (metadata) {
setPrimitiveMetadata(preprocessMetadata(metadata))
}
} catch (e) {
console.error(e)
}
// 只需要在初始化时执行一次
// eslint-disable-next-line react-hooks/exhaustive-deps
fn()
}, [setPrimitiveMetadata])
useEffect(() => {
downloadMetadata()
}, [setPrimitiveMetadata, downloadMetadata])
useEffect(() => {
uploadMetadata()
}, [primitiveMetadata, uploadMetadata])
}