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)
|
const userID = String(userInfo.id)
|
||||||
await userTable.addUser(userID, userInfo.notification_email || userInfo.email, "github")
|
await userTable.addUser(userID, userInfo.notification_email || userInfo.email, "github")
|
||||||
|
|
||||||
|
@ -68,12 +68,12 @@ export const originSources = {
|
|||||||
},
|
},
|
||||||
"hupu": {
|
"hupu": {
|
||||||
name: "虎扑",
|
name: "虎扑",
|
||||||
active: false,
|
disable: true,
|
||||||
home: "https://hupu.com",
|
home: "https://hupu.com",
|
||||||
},
|
},
|
||||||
"tieba": {
|
"tieba": {
|
||||||
name: "百度贴吧",
|
name: "百度贴吧",
|
||||||
active: false,
|
disable: true,
|
||||||
home: "https://tieba.baidu.com",
|
home: "https://tieba.baidu.com",
|
||||||
},
|
},
|
||||||
"toutiao": {
|
"toutiao": {
|
||||||
@ -91,13 +91,13 @@ export const originSources = {
|
|||||||
"thepaper": {
|
"thepaper": {
|
||||||
name: "澎湃新闻",
|
name: "澎湃新闻",
|
||||||
interval: Time.Common,
|
interval: Time.Common,
|
||||||
active: false,
|
disable: true,
|
||||||
home: "https://www.thepaper.cn",
|
home: "https://www.thepaper.cn",
|
||||||
},
|
},
|
||||||
"sputniknewscn": {
|
"sputniknewscn": {
|
||||||
name: "卫星通讯社",
|
name: "卫星通讯社",
|
||||||
color: "orange",
|
color: "orange",
|
||||||
active: false,
|
disable: true,
|
||||||
home: "https://sputniknews.cn",
|
home: "https://sputniknews.cn",
|
||||||
},
|
},
|
||||||
"cankaoxiaoxi": {
|
"cankaoxiaoxi": {
|
||||||
@ -116,7 +116,7 @@ function genSources() {
|
|||||||
const parent = {
|
const parent = {
|
||||||
name: source.name,
|
name: source.name,
|
||||||
type: source.type,
|
type: source.type,
|
||||||
active: source.active ?? true,
|
disable: source.disable,
|
||||||
color: source.color ?? "red",
|
color: source.color ?? "red",
|
||||||
interval: source.interval ?? Time.Default,
|
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)
|
type MainSourceID = keyof(ConstSources)
|
||||||
|
|
||||||
export type SourceID = {
|
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 } ? {
|
ConstSources[Key] extends { sub?: infer SubSource } ? {
|
||||||
// @ts-expect-error >_<
|
// @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;
|
}[keyof SubSource] | Key : Key;
|
||||||
}[MainSourceID]
|
}[MainSourceID]
|
||||||
|
|
||||||
@ -33,15 +33,15 @@ export interface OriginSource {
|
|||||||
interval?: number
|
interval?: number
|
||||||
type?: "hottest" | "realtime"
|
type?: "hottest" | "realtime"
|
||||||
/**
|
/**
|
||||||
* @default true
|
* @default false
|
||||||
*/
|
*/
|
||||||
active?: boolean
|
disable?: boolean
|
||||||
home: string
|
home: string
|
||||||
color?: Color
|
color?: Color
|
||||||
sub?: Record<string, {
|
sub?: Record<string, {
|
||||||
title: string
|
title: string
|
||||||
type?: "hottest" | "realtime"
|
type?: "hottest" | "realtime"
|
||||||
active?: boolean
|
disable?: boolean
|
||||||
interval?: number
|
interval?: number
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ export interface Source {
|
|||||||
title?: string
|
title?: string
|
||||||
type?: "hottest" | "realtime"
|
type?: "hottest" | "realtime"
|
||||||
color: Color
|
color: Color
|
||||||
active: boolean
|
disable?: boolean
|
||||||
interval: number
|
interval: number
|
||||||
redirect?: SourceID
|
redirect?: SourceID
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import type { PrimitiveAtom } from "jotai"
|
|||||||
import { atom } from "jotai"
|
import { atom } from "jotai"
|
||||||
import type { PrimitiveMetadata } from "@shared/types"
|
import type { PrimitiveMetadata } from "@shared/types"
|
||||||
import { verifyPrimitiveMetadata } from "@shared/verify"
|
import { verifyPrimitiveMetadata } from "@shared/verify"
|
||||||
|
import { sources } from "@shared/sources"
|
||||||
import type { Update } from "./types"
|
import type { Update } from "./types"
|
||||||
|
|
||||||
function createPrimitiveMetadataAtom(
|
function createPrimitiveMetadataAtom(
|
||||||
@ -17,40 +18,42 @@ function createPrimitiveMetadataAtom(
|
|||||||
if (item) {
|
if (item) {
|
||||||
const stored = JSON.parse(item) as PrimitiveMetadata
|
const stored = JSON.parse(item) as PrimitiveMetadata
|
||||||
verifyPrimitiveMetadata(stored)
|
verifyPrimitiveMetadata(stored)
|
||||||
return preprocess(stored)
|
return preprocess({
|
||||||
|
...stored,
|
||||||
|
action: "init",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch { }
|
||||||
return initialValue
|
return initialValue
|
||||||
}
|
}
|
||||||
const baseAtom = atom(getInitialValue())
|
const baseAtom = atom(getInitialValue())
|
||||||
const derivedAtom = atom(
|
const derivedAtom = atom(get => get(baseAtom), (get, set, update: Update<PrimitiveMetadata>) => {
|
||||||
get => get(baseAtom),
|
const nextValue = update instanceof Function ? update(get(baseAtom)) : update
|
||||||
(get, set, update: Update<PrimitiveMetadata>) => {
|
if (nextValue.updatedTime > get(baseAtom).updatedTime) {
|
||||||
const nextValue = update instanceof Function ? update(get(baseAtom)) : update
|
|
||||||
set(baseAtom, nextValue)
|
set(baseAtom, nextValue)
|
||||||
localStorage.setItem(key, JSON.stringify(nextValue))
|
localStorage.setItem(key, JSON.stringify(nextValue))
|
||||||
},
|
}
|
||||||
)
|
})
|
||||||
return derivedAtom
|
return derivedAtom
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialMetadata = typeSafeObjectFromEntries(typeSafeObjectEntries(metadata).map(([id, val]) => [id, val.sources]))
|
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 {
|
return {
|
||||||
data: {
|
data: {
|
||||||
...initialMetadata,
|
...initialMetadata,
|
||||||
...typeSafeObjectFromEntries(
|
...typeSafeObjectFromEntries(
|
||||||
typeSafeObjectEntries(target.data)
|
typeSafeObjectEntries(target.data)
|
||||||
.filter(([id]) => initialMetadata[id])
|
.filter(([id]) => initialMetadata[id])
|
||||||
.map(([id, sources]) => {
|
.map(([id, s]) => {
|
||||||
if (id === "focus") return [id, sources]
|
if (id === "focus") return [id, s.filter(k => sources[k])]
|
||||||
const oldS = sources.filter(k => initialMetadata[id].includes(k))
|
const oldS = s.filter(k => initialMetadata[id].includes(k))
|
||||||
const newS = initialMetadata[id].filter(k => !oldS.includes(k))
|
const newS = initialMetadata[id].filter(k => !oldS.includes(k))
|
||||||
return [id, [...oldS, ...newS]]
|
return [id, [...oldS, ...newS]]
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
action,
|
action: target.action,
|
||||||
updatedTime: target.updatedTime,
|
updatedTime: target.updatedTime,
|
||||||
} as PrimitiveMetadata
|
} as PrimitiveMetadata
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ export function Column({ id }: { id: ColumnID }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentColumnID(id)
|
setCurrentColumnID(id)
|
||||||
}, [id, setCurrentColumnID])
|
}, [id, setCurrentColumnID])
|
||||||
useTitle(`NewsNow ${metadata[id].name}`)
|
useTitle(`NewsNow | ${metadata[id].name}`)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full flex justify-center">
|
<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>>) {
|
export function GlobalOverlayScrollbar({ children, ...props }: PropsWithChildren<HTMLProps<HTMLDivElement>>) {
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
const lastScroll = useRef(0)
|
const lastTrigger = useRef(0)
|
||||||
const timer = useRef<any>()
|
const timer = useRef<any>()
|
||||||
const setGoToTop = useSetAtom(goToTopAtom)
|
const setGoToTop = useSetAtom(goToTopAtom)
|
||||||
const onScroll = useCallback((e: Event) => {
|
const onScroll = useCallback((e: Event) => {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (now - lastScroll.current > 50) {
|
if (now - lastTrigger.current > 50) {
|
||||||
lastScroll.current = now
|
lastTrigger.current = now
|
||||||
clearTimeout(timer.current)
|
clearTimeout(timer.current)
|
||||||
timer.current = setTimeout(
|
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 type { PrimitiveMetadata } from "@shared/types"
|
||||||
import { useAtom } from "jotai"
|
import { useAtom } from "jotai"
|
||||||
import { ofetch } from "ofetch"
|
import { ofetch } from "ofetch"
|
||||||
import { useCallback, useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useDebounce } from "react-use"
|
||||||
import { preprocessMetadata, primitiveMetadataAtom } from "~/atoms"
|
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() {
|
export function useSync() {
|
||||||
const [primitiveMetadata, setPrimitiveMetadata] = useAtom(primitiveMetadataAtom)
|
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 () => {
|
useDebounce(async () => {
|
||||||
if (!__ENABLE_LOGIN__) return
|
if (primitiveMetadata.action === "manual") {
|
||||||
const jwt = localStorage.getItem("user_jwt")
|
uploadMetadata(primitiveMetadata)
|
||||||
if (!jwt) return
|
}
|
||||||
try {
|
}, 10000, [primitiveMetadata])
|
||||||
const { data, updatedTime } = await ofetch("/api/me/sync", {
|
useEffect(() => {
|
||||||
headers: {
|
const fn = async () => {
|
||||||
Authorization: `Bearer ${jwt}`,
|
const metadata = await downloadMetadata()
|
||||||
},
|
if (metadata) {
|
||||||
}) as PrimitiveMetadata
|
setPrimitiveMetadata(preprocessMetadata(metadata))
|
||||||
if (data && updatedTime > primitiveMetadata.updatedTime) {
|
|
||||||
// 不用同步 action 字段
|
|
||||||
setPrimitiveMetadata(preprocessMetadata({ action: "sync", data, updatedTime }, "sync"))
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
}
|
||||||
// 只需要在初始化时执行一次
|
fn()
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [setPrimitiveMetadata])
|
}, [setPrimitiveMetadata])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
downloadMetadata()
|
|
||||||
}, [setPrimitiveMetadata, downloadMetadata])
|
|
||||||
useEffect(() => {
|
|
||||||
uploadMetadata()
|
|
||||||
}, [primitiveMetadata, uploadMetadata])
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user