mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
refactor: useSync
This commit is contained in:
parent
d2fcae1802
commit
3dc9c4b28c
@ -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")
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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(
|
||||
() => {
|
||||
|
@ -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 }
|
||||
}
|
@ -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])
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user