Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3584efb09b | ||
|
|
f357e920b7 | ||
|
|
dca03c4f0c | ||
|
|
576137a62f | ||
|
|
a896f15c44 | ||
|
|
f78eead5fd | ||
|
|
6fe8f03f16 | ||
|
|
8e7b35385b | ||
|
|
0271066e27 | ||
|
|
f2e4ce36de | ||
|
|
cce09de022 | ||
|
|
9e37d4449f | ||
|
|
aff39c1e1b | ||
|
|
6c22566079 | ||
|
|
cfb48a8c25 | ||
|
|
0580495208 | ||
|
|
a914396685 | ||
|
|
51fa15a4e4 | ||
|
|
3968f658ee | ||
|
|
c4c287a40d | ||
|
|
ae37735391 | ||
|
|
c16797cb59 | ||
|
|
3aa2b91b05 | ||
|
|
1eda9d52b6 | ||
|
|
f8579bbe4f | ||
|
|
d1fc4f1410 | ||
|
|
e9804b9ee2 | ||
|
|
640a4550d6 | ||
|
|
fc57acf9cc | ||
|
|
681d137975 | ||
|
|
ca7d6475f9 | ||
|
|
27c623d779 | ||
|
|
3d7026611b | ||
|
|
bb5fad47cb | ||
|
|
0ee5ef316f | ||
|
|
da3dabcba4 | ||
|
|
332ed38540 | ||
|
|
1d522f320c | ||
|
|
735a0bca82 | ||
|
|
0331caf1a6 | ||
|
|
c738c4c4cf | ||
|
|
9f1be628e0 | ||
|
|
10f5d9a53d | ||
|
|
67f33961d2 | ||
|
|
1fb665be18 | ||
|
|
7f9591f465 | ||
|
|
b784389021 | ||
|
|
2e32ca13a7 |
@@ -0,0 +1,3 @@
|
||||
|
||||
# Don't index SpecStory auto-save files, but allow explicit context inclusion via @ references
|
||||
.specstory/**
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
password: ${{ secrets.GGITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
|
||||
+2
-1
@@ -12,4 +12,5 @@ dev-dist
|
||||
*.tsbuildinfo
|
||||
wrangler.toml
|
||||
imports.app.d.ts
|
||||
package-lock.json
|
||||
package-lock.json
|
||||
.specstory/
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:20.12.2-alpine as builder
|
||||
FROM node:20.12.2-alpine AS builder
|
||||
WORKDIR /usr/src
|
||||
COPY . .
|
||||
RUN corepack enable
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# NewsNow
|
||||
|
||||

|
||||
|
||||
[English](./README.md) | [简体中文](README.zh-CN.md) | 日本語
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# NewsNow
|
||||
|
||||

|
||||
|
||||
English | [简体中文](README.zh-CN.md) | [日本語](README.ja-JP.md)
|
||||
|
||||
+2
-4
@@ -1,7 +1,3 @@
|
||||
# NewsNow
|
||||
|
||||
<a href="https://hellogithub.com/repository/c2978695e74a423189e9ca2543ab3b36" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=c2978695e74a423189e9ca2543ab3b36&claim_uid=SMJiFwlsKCkWf89&theme=small" alt="Featured|HelloGitHub" /></a>
|
||||
|
||||

|
||||
|
||||
[English](./README.md) | 简体中文 | [日本語](README.ja-JP.md)
|
||||
@@ -114,3 +110,5 @@ pnpm dev
|
||||
如果本项目对你有所帮助,可以给小猫买点零食。如果需要定制或者其他帮助,请通过下列方式联系备注。
|
||||
|
||||

|
||||
|
||||
<a href="https://hellogithub.com/repository/c2978695e74a423189e9ca2543ab3b36" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=c2978695e74a423189e9ca2543ab3b36&claim_uid=SMJiFwlsKCkWf89&theme=small" alt="Featured|HelloGitHub" /></a>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
newsnow:
|
||||
build: .
|
||||
ports:
|
||||
- '4444:4444'
|
||||
volumes:
|
||||
- newsnow_data:/usr/app/.data
|
||||
environment:
|
||||
- G_CLIENT_ID=
|
||||
- G_CLIENT_SECRET=
|
||||
- JWT_SECRET=
|
||||
- INIT_TABLE=true
|
||||
- ENABLE_CACHE=true
|
||||
|
||||
volumes:
|
||||
newsnow_data:
|
||||
name: newsnow_data
|
||||
+9
-3
@@ -1,15 +1,21 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
newsnow:
|
||||
image: ghcr.io/ourongxing/newsnow:latest
|
||||
container_name: newsnow
|
||||
restart: always
|
||||
ports:
|
||||
- '4444:4444'
|
||||
volumes:
|
||||
- newsnow_data:/usr/app/.data
|
||||
environment:
|
||||
- HOST=0.0.0.0
|
||||
- PORT=4444
|
||||
- NODE_ENV=production
|
||||
- G_CLIENT_ID=
|
||||
- G_CLIENT_SECRET=
|
||||
- JWT_SECRET=
|
||||
- INIT_TABLE=true
|
||||
- ENABLE_CACHE=true
|
||||
|
||||
volumes:
|
||||
newsnow_data:
|
||||
name: newsnow_data
|
||||
|
||||
+1
-14
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<html lang="zh-CN" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -49,19 +49,6 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function safeParseString(str) {
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
} catch {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
const theme = safeParseString(localStorage.getItem("color-scheme")) || "dark"
|
||||
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
if (theme !== "light") {
|
||||
document.documentElement.classList.add("dark")
|
||||
}
|
||||
|
||||
const query = new URLSearchParams(window.location.search)
|
||||
if (query.has("login") && query.has("user") && query.has("jwt")) {
|
||||
localStorage.setItem("user", query.get("user"))
|
||||
|
||||
+11
-1
@@ -17,6 +17,11 @@ const nitroOption: Parameters<typeof viteNitro>[0] = {
|
||||
connector: "better-sqlite3",
|
||||
},
|
||||
},
|
||||
devDatabase: {
|
||||
default: {
|
||||
connector: "better-sqlite3",
|
||||
},
|
||||
},
|
||||
imports: {
|
||||
dirs: ["server/utils", "shared"],
|
||||
},
|
||||
@@ -38,6 +43,11 @@ if (process.env.VERCEL) {
|
||||
// }
|
||||
} else if (process.env.CF_PAGES) {
|
||||
nitroOption.preset = "cloudflare-pages"
|
||||
nitroOption.unenv = {
|
||||
alias: {
|
||||
"safer-buffer": "node:buffer",
|
||||
},
|
||||
}
|
||||
nitroOption.database = {
|
||||
default: {
|
||||
connector: "cloudflare-d1",
|
||||
@@ -57,4 +67,4 @@ if (process.env.VERCEL) {
|
||||
|
||||
export default function () {
|
||||
return viteNitro(nitroOption)
|
||||
}
|
||||
}
|
||||
+9
-5
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "newsnow",
|
||||
"type": "module",
|
||||
"version": "0.0.28",
|
||||
"version": "0.0.34",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.5.2",
|
||||
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
|
||||
"author": {
|
||||
"url": "https://github.com/ourongxing/",
|
||||
"email": "[email protected]",
|
||||
@@ -30,11 +30,12 @@
|
||||
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@iconify-json/si": "^1.2.3",
|
||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||
"@tanstack/react-query-devtools": "^5.66.11",
|
||||
"@tanstack/react-router": "^1.112.0",
|
||||
"@unocss/reset": "^66.0.0",
|
||||
"ahooks": "^3.8.4",
|
||||
"better-sqlite3": "^11.8.1",
|
||||
"better-sqlite3": "^11.10.0",
|
||||
"cheerio": "^1.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.4",
|
||||
@@ -54,6 +55,7 @@
|
||||
"overlayscrollbars": "^2.11.1",
|
||||
"pnpm": "^10.5.2",
|
||||
"react": "^19.0.0",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-use": "^17.6.0",
|
||||
"uncrypto": "^0.1.3",
|
||||
@@ -99,7 +101,7 @@
|
||||
"vitest": "^3.0.7",
|
||||
"workbox-build": "^7.3.0",
|
||||
"workbox-window": "^7.3.0",
|
||||
"wrangler": "^3.111.0"
|
||||
"wrangler": "4.14.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
@@ -109,16 +111,18 @@
|
||||
"@napi-rs/pinyin",
|
||||
"@parcel/watcher",
|
||||
"@swc/core",
|
||||
"better-sqlite3",
|
||||
"esbuild",
|
||||
"better-sqlite3",
|
||||
"sharp",
|
||||
"simple-git-hooks",
|
||||
"unrs-resolver",
|
||||
"workerd"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"cross-spawn": ">=7.0.6",
|
||||
"dayjs": "1.11.13",
|
||||
"nitropack": "npm:[email protected]",
|
||||
"picomatch": "^4.0.2",
|
||||
"react": "^19",
|
||||
"db0": "^0.3.1",
|
||||
|
||||
Generated
+4833
-3040
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
+1
-1
@@ -1,5 +1,5 @@
|
||||
import sources from "../shared/sources.json"
|
||||
|
||||
Promise.all(Object.keys(sources).map(id =>
|
||||
fetch(`https://newsnow.busiyi.world/api/s?id=${id}`),
|
||||
fetch(`https://hot.chgr.cc/api/s?id=${id}`),
|
||||
)).catch(console.error)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
|
||||
import { getServer } from "#/mcp/server"
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const req = event.node.req
|
||||
const res = event.node.res
|
||||
const server = getServer()
|
||||
try {
|
||||
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined })
|
||||
transport.onerror = console.error.bind(console)
|
||||
await server.connect(transport)
|
||||
await transport.handleRequest(req, res, await readBody(event))
|
||||
res.on("close", () => {
|
||||
// console.log("Request closed")
|
||||
transport.close()
|
||||
server.close()
|
||||
})
|
||||
return res
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return {
|
||||
jsonrpc: "2.0",
|
||||
error: {
|
||||
code: -32603,
|
||||
message: "Internal server error",
|
||||
},
|
||||
id: null,
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -82,4 +82,4 @@ export async function getCacheTable() {
|
||||
} catch (e) {
|
||||
logger.error("failed to init database ", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,4 +66,4 @@ export class UserTable {
|
||||
if (!state.success) throw new Error(`delete user ${key} failed`)
|
||||
logger.success(`delete user ${key}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+2
@@ -22,6 +22,8 @@ declare module 'glob:./sources/{*.ts,**/index.ts}' {
|
||||
export const kaopu: typeof import('./sources/kaopu')
|
||||
export const kuaishou: typeof import('./sources/kuaishou')
|
||||
export const linuxdo: typeof import('./sources/linuxdo')
|
||||
export const idcflare: typeof import('./sources/idcflare')
|
||||
export const mktnews: typeof import('./sources/mktnews')
|
||||
export const nowcoder: typeof import('./sources/nowcoder')
|
||||
export const pcbeta: typeof import('./sources/pcbeta')
|
||||
export const producthunt: typeof import('./sources/producthunt')
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import sources from "../../shared/sources.json"
|
||||
|
||||
export const description = Object.entries(sources).filter(([_, source]) => {
|
||||
if (source.redirect) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}).map(([id, source]) => {
|
||||
return source.title ? `${source.name}-${source.title} id is ${id}` : `${source.name} id is ${id}`
|
||||
}).join(";")
|
||||
@@ -0,0 +1,44 @@
|
||||
import { z } from "zod"
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"
|
||||
import packageJSON from "../../package.json"
|
||||
import { description } from "./desc.js"
|
||||
|
||||
export function getServer() {
|
||||
const server = new McpServer(
|
||||
{
|
||||
name: "NewsNow",
|
||||
version: packageJSON.version,
|
||||
},
|
||||
{ capabilities: { logging: {} } },
|
||||
)
|
||||
|
||||
server.tool(
|
||||
"get_hotest_latest_news",
|
||||
`get hotest or latest news from source by {id}, return {count: 10} news.`,
|
||||
{
|
||||
id: z.string().describe(`source id. e.g. ${description}`),
|
||||
count: z.any().default(10).describe("count of news to return."),
|
||||
},
|
||||
async ({ id, count }): Promise<CallToolResult> => {
|
||||
let n = Number(count)
|
||||
if (Number.isNaN(n) || n < 1) {
|
||||
n = 10
|
||||
}
|
||||
|
||||
const res: SourceResponse = await $fetch(`/api/s?id=${id}`)
|
||||
return {
|
||||
content: res.items.slice(0, count).map((item) => {
|
||||
return {
|
||||
text: `[${item.title}](${item.url})`,
|
||||
type: "text",
|
||||
}
|
||||
}),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
server.server.onerror = console.error.bind(console)
|
||||
|
||||
return server
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => {
|
||||
if (!url.pathname.startsWith("/api")) return
|
||||
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
|
||||
event.context.disabledLogin = true
|
||||
if (["/api/s", "/api/proxy", "/api/latest"].every(p => !url.pathname.startsWith(p)))
|
||||
if (["/api/s", "/api/proxy", "/api/latest", "/api/mcp"].every(p => !url.pathname.startsWith(p)))
|
||||
throw createError({ statusCode: 506, message: "Server not configured, disable login" })
|
||||
} else {
|
||||
if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) {
|
||||
|
||||
+33
-16
@@ -1,20 +1,37 @@
|
||||
interface Res {
|
||||
data: {
|
||||
title: string
|
||||
hot: string
|
||||
url: string
|
||||
mobil_url: string
|
||||
}[]
|
||||
interface HotItem {
|
||||
id: string
|
||||
title: string
|
||||
url: string
|
||||
mobileUrl: string
|
||||
}
|
||||
|
||||
export default defineSource(async () => {
|
||||
const r: Res = await myFetch(`https://api.vvhan.com/api/hotlist/huPu`)
|
||||
return r.data.map((k) => {
|
||||
return {
|
||||
id: k.url,
|
||||
title: k.title,
|
||||
url: k.url,
|
||||
mobileUrl: k.mobil_url,
|
||||
}
|
||||
})
|
||||
// 获取虎扑新热榜页面的HTML内容
|
||||
const html = await myFetch(`https://bbs.hupu.com/topic-daily-hot`)
|
||||
|
||||
// 正则表达式匹配新的热榜项结构
|
||||
const regex = /<li class="bbs-sl-web-post-body">[\s\S]*?<a href="(\/[^"]+?\.html)"[^>]*?class="p-title"[^>]*>([^<]+)<\/a>/g
|
||||
|
||||
const result: HotItem[] = []
|
||||
let match
|
||||
|
||||
// 将赋值操作移到循环内部,修复no-cond-assign警告
|
||||
while (true) {
|
||||
match = regex.exec(html)
|
||||
if (!match) break
|
||||
|
||||
const [, path, title] = match
|
||||
|
||||
// 构建完整URL
|
||||
const url = `https://bbs.hupu.com${path}`
|
||||
|
||||
result.push({
|
||||
id: path,
|
||||
title: title.trim(),
|
||||
url,
|
||||
mobileUrl: url,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
interface Res {
|
||||
topic_list: {
|
||||
can_create_topic: boolean
|
||||
more_topics_url: string
|
||||
per_page: number
|
||||
top_tags: string[]
|
||||
topics: {
|
||||
id: number
|
||||
title: string
|
||||
fancy_title: string
|
||||
posts_count: number
|
||||
reply_count: number
|
||||
highest_post_number: number
|
||||
image_url: null | string
|
||||
created_at: Date
|
||||
last_posted_at: Date
|
||||
bumped: boolean
|
||||
bumped_at: Date
|
||||
unseen: boolean
|
||||
pinned: boolean
|
||||
excerpt?: string
|
||||
visible: boolean
|
||||
closed: boolean
|
||||
archived: boolean
|
||||
like_count: number
|
||||
has_summary: boolean
|
||||
last_poster_username: string
|
||||
category_id: number
|
||||
pinned_globally: boolean
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
const hot = defineSource(async () => {
|
||||
const res = await myFetch<Res>("https://n8n.chgr.cc/webhook/api?name=idcflare")
|
||||
return res.topic_list.topics
|
||||
.filter(k => k.visible && !k.archived && !k.pinned)
|
||||
.map(k => ({
|
||||
id: k.id,
|
||||
title: k.title,
|
||||
url: `https://linux.do/t/topic/${k.id}`,
|
||||
}))
|
||||
})
|
||||
|
||||
const latest = defineSource(async () => {
|
||||
const res = await myFetch<Res>("https://linux.do/latest.json?order=created")
|
||||
return res.topic_list.topics
|
||||
.filter(k => k.visible && !k.archived && !k.pinned)
|
||||
.map(k => ({
|
||||
id: k.id,
|
||||
title: k.title,
|
||||
pubDate: new Date(k.created_at).valueOf(),
|
||||
url: `https://linux.do/t/topic/${k.id}`,
|
||||
}))
|
||||
})
|
||||
|
||||
export default defineSource({
|
||||
"idcflare": latest,
|
||||
"idcflare-latest": latest,
|
||||
"idcflare-hot": hot,
|
||||
})
|
||||
@@ -32,7 +32,7 @@ interface Res {
|
||||
}
|
||||
|
||||
const hot = defineSource(async () => {
|
||||
const res = await myFetch<Res>("https://linux.do/top/daily.json")
|
||||
const res = await myFetch<Res>("https://n8n.chgr.cc/webhook/api?name=linuxdo")
|
||||
return res.topic_list.topics
|
||||
.filter(k => k.visible && !k.archived && !k.pinned)
|
||||
.map(k => ({
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
interface Report {
|
||||
id: string
|
||||
type: number
|
||||
time: string
|
||||
important: number
|
||||
data: {
|
||||
content: string
|
||||
pic: string
|
||||
title: string
|
||||
}
|
||||
remark: string[]
|
||||
hot: boolean
|
||||
hot_start: string
|
||||
hot_end: string
|
||||
classify: {
|
||||
id: number
|
||||
pid: number
|
||||
name: string
|
||||
parent: string
|
||||
}[]
|
||||
}
|
||||
|
||||
interface Res {
|
||||
data: {
|
||||
id: number
|
||||
name: string
|
||||
pid: number
|
||||
child: {
|
||||
id: number
|
||||
name: string
|
||||
pid: number
|
||||
flash_list: Report[]
|
||||
}[]
|
||||
}[]
|
||||
}
|
||||
|
||||
const flash = defineSource(async () => {
|
||||
const res: Res = await myFetch("https://api.mktnews.net/api/flash/host")
|
||||
|
||||
const categories = ["policy", "AI", "financial"] as const
|
||||
const typeMap = { policy: "Policy", AI: "AI", financial: "Financial" } as const
|
||||
|
||||
const allReports = categories.flatMap((category) => {
|
||||
const flash_list = res.data.find(item => item.name === category)?.child[0]?.flash_list || []
|
||||
return flash_list.map(item => ({ ...item, type: typeMap[category] }))
|
||||
})
|
||||
|
||||
return allReports
|
||||
.sort((a, b) => b.time.localeCompare(a.time))
|
||||
.map(item => ({
|
||||
id: item.id,
|
||||
title: item.data.title || item.data.content,
|
||||
pubDate: item.time,
|
||||
extra: { info: item.type },
|
||||
url: `https://mktnews.net/flashDetail.html?id=${item.id}`,
|
||||
}))
|
||||
})
|
||||
|
||||
export default defineSource({
|
||||
"mktnews": flash,
|
||||
"mktnews-flash": flash,
|
||||
})
|
||||
@@ -52,7 +52,7 @@ const news = defineSource(async () => {
|
||||
|
||||
const res: NewsRes = await myFetch(apiUrl)
|
||||
return res.data.items
|
||||
.filter(k => k.resource_type !== "ad" && k.resource.type !== "live" && k.resource.uri)
|
||||
.filter(k => k.resource_type !== "theme" && k.resource_type !== "ad" && k.resource.type !== "live" && k.resource.uri)
|
||||
.map(({ resource: h }) => {
|
||||
return {
|
||||
id: h.id,
|
||||
|
||||
+136
-34
@@ -1,48 +1,150 @@
|
||||
interface Res {
|
||||
ok: number // 1 is ok
|
||||
data: {
|
||||
realtime:
|
||||
{
|
||||
num: number // 看上去是个 id
|
||||
emoticon: string
|
||||
icon?: string // 热,新 icon url
|
||||
icon_width: number
|
||||
icon_height: number
|
||||
is_ad?: number // 1
|
||||
note: string
|
||||
small_icon_desc: string
|
||||
icon_desc?: string // 如果是 荐 ,就是广告
|
||||
topic_flag: number
|
||||
icon_desc_color: string
|
||||
flag: number
|
||||
word_scheme: string
|
||||
small_icon_desc_color: string
|
||||
realpos: number
|
||||
label_name: string
|
||||
word: string // 热搜词
|
||||
rank: number
|
||||
}[]
|
||||
}
|
||||
export interface Root {
|
||||
ok: number
|
||||
data: Data
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
mapi: any[]
|
||||
cards: Card[]
|
||||
cardlistInfo: CardlistInfo
|
||||
scheme: string
|
||||
showAppTips: number
|
||||
}
|
||||
|
||||
export interface Card {
|
||||
card_group: CardGroup[]
|
||||
card_type: number
|
||||
show_type: number
|
||||
itemid?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
export interface CardGroup {
|
||||
actionlog?: Actionlog
|
||||
card_type: number
|
||||
pic?: string
|
||||
itemid?: string
|
||||
icon?: string
|
||||
icon_width?: number
|
||||
icon_height?: number
|
||||
desc?: string
|
||||
scheme: string
|
||||
desc_extr: any
|
||||
promotion?: Promotion
|
||||
display_arrow?: number
|
||||
show_type?: number
|
||||
title?: string
|
||||
is_show_arrow?: number
|
||||
left_tag_img?: string
|
||||
sub_title?: string
|
||||
}
|
||||
|
||||
export interface Actionlog {
|
||||
luicode: string
|
||||
lfid: string
|
||||
uicode: string
|
||||
act_code: number
|
||||
act_type: number
|
||||
fid: string
|
||||
ext: string
|
||||
}
|
||||
|
||||
export interface Promotion {
|
||||
monitor_url: MonitorUrl[]
|
||||
}
|
||||
|
||||
export interface MonitorUrl {
|
||||
third_party_click: string
|
||||
third_party_show: string
|
||||
type: string
|
||||
monitor_name?: string
|
||||
}
|
||||
|
||||
export interface CardlistInfo {
|
||||
select_id: string
|
||||
title_top: string
|
||||
show_style: number
|
||||
starttime: number
|
||||
can_shared: number
|
||||
v_p: string
|
||||
containerid: string
|
||||
page_type: string
|
||||
nick: string
|
||||
cardlist_head_cards: CardlistHeadCard[]
|
||||
enable_load_imge_scrolling: number
|
||||
page_title: string
|
||||
search_request_id: string
|
||||
load_more_threshold: number
|
||||
pagesize: number
|
||||
page_common_ext: string
|
||||
cardlist_menus: any[]
|
||||
total: number
|
||||
headbg_animation: string
|
||||
page_size: number
|
||||
page: any
|
||||
}
|
||||
|
||||
export interface CardlistHeadCard {
|
||||
title_top?: string
|
||||
show_menu: boolean
|
||||
head_type_name: string
|
||||
head_type: number
|
||||
head_data?: HeadData
|
||||
channel_list?: ChannelList[]
|
||||
}
|
||||
|
||||
export interface HeadData {
|
||||
cover_url: string
|
||||
show_navi_mask: boolean
|
||||
show_title: boolean
|
||||
data_type: number
|
||||
scheme: string
|
||||
}
|
||||
|
||||
export interface ChannelList {
|
||||
actionlog: Actionlog2
|
||||
scheme: string
|
||||
containerid: string
|
||||
name: string
|
||||
default_add: number
|
||||
id: number
|
||||
must_show: number
|
||||
replaced_name?: string
|
||||
}
|
||||
|
||||
export interface Actionlog2 {
|
||||
luicode: string
|
||||
lfid: string
|
||||
uicode: string
|
||||
act_code: number
|
||||
act_type: number
|
||||
fid: string
|
||||
ext: string
|
||||
}
|
||||
|
||||
export default defineSource(async () => {
|
||||
const url = "https://weibo.com/ajax/side/hotSearch"
|
||||
const res: Res = await myFetch(url)
|
||||
return res.data.realtime
|
||||
.filter(k => !k.is_ad)
|
||||
const url = "https://m.weibo.cn/api/container/getIndex?containerid=106003type%3D25%26t%3D3%26disable_hot%3D1%26filter_type%3Drealtimehot&title=%E5%BE%AE%E5%8D%9A%E7%83%AD%E6%90%9C&extparam=filter_type%3Drealtimehot%26mi_cid%3D100103%26pos%3D0_0%26c_type%3D30%26display_time%3D1540538388&luicode=10000011&lfid=231583"
|
||||
const res: Root = await myFetch(url, {
|
||||
headers: {
|
||||
"referer": "https://s.weibo.com/top/summary?cate=realtimehot",
|
||||
"mweibo-pwa": "1",
|
||||
"x-requested-with": "XMLHttpRequest",
|
||||
},
|
||||
})
|
||||
return res.data.cards[0].card_group
|
||||
.filter((k, i) => i !== 0 && k.desc && !k.actionlog?.ext.includes("ads_word"))
|
||||
.map((k) => {
|
||||
const keyword = k.word_scheme ? k.word_scheme : `#${k.word}#`
|
||||
return {
|
||||
id: k.word,
|
||||
title: k.word,
|
||||
id: k.desc!,
|
||||
title: k.desc!,
|
||||
extra: {
|
||||
icon: k.icon && {
|
||||
url: proxyPicture(k.icon),
|
||||
scale: 1.5,
|
||||
},
|
||||
},
|
||||
url: `https://s.weibo.com/weibo?q=${encodeURIComponent(keyword)}`,
|
||||
mobileUrl: `https://m.weibo.cn/search?containerid=231522type%3D1%26q%3D${encodeURIComponent(keyword)}&_T_WM=16922097837&v_p=42`,
|
||||
url: `https://s.weibo.com/weibo?q=${encodeURIComponent(`#${k.desc}#`)}`,
|
||||
mobileUrl: k.scheme,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
+34
-19
@@ -1,38 +1,53 @@
|
||||
interface Res {
|
||||
data: {
|
||||
card_label?: {
|
||||
icon: string
|
||||
night_icon: string
|
||||
type: "hot_list_feed"
|
||||
style_type: "1"
|
||||
feed_specific: {
|
||||
answer_count: 411
|
||||
}
|
||||
target: {
|
||||
id: number
|
||||
title: string
|
||||
url: string
|
||||
created: number
|
||||
answer_count: number
|
||||
follower_count: number
|
||||
bound_topic_ids: number[]
|
||||
comment_count: number
|
||||
is_following: boolean
|
||||
excerpt: string
|
||||
title_area: {
|
||||
text: string
|
||||
}
|
||||
excerpt_area: {
|
||||
text: string
|
||||
}
|
||||
image_area: {
|
||||
url: string
|
||||
}
|
||||
metrics_area: {
|
||||
text: string
|
||||
font_color: string
|
||||
background: string
|
||||
weight: string
|
||||
}
|
||||
label_area: {
|
||||
type: "trend"
|
||||
trend: number
|
||||
night_color: string
|
||||
normal_color: string
|
||||
}
|
||||
link: {
|
||||
url: string
|
||||
}
|
||||
}
|
||||
}[]
|
||||
}
|
||||
|
||||
export default defineSource({
|
||||
zhihu: async () => {
|
||||
const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=20&desktop=true"
|
||||
const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-list-web?limit=20&desktop=true"
|
||||
const res: Res = await myFetch(url)
|
||||
return res.data
|
||||
.map((k) => {
|
||||
const urlId = k.target.url?.match(/(\d+)$/)?.[1]
|
||||
return {
|
||||
id: k.target.id,
|
||||
title: k.target.title,
|
||||
id: k.target.link.url.match(/(\d+)$/)?.[1] ?? k.target.link.url,
|
||||
title: k.target.title_area.text,
|
||||
extra: {
|
||||
icon: k.card_label?.night_icon && proxyPicture(k.card_label.night_icon),
|
||||
info: k.target.metrics_area.text,
|
||||
hover: k.target.excerpt_area.text,
|
||||
},
|
||||
url: `https://www.zhihu.com/question/${urlId || k.target.id}`,
|
||||
url: k.target.link.url,
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
+9
-9
@@ -4,9 +4,10 @@
|
||||
"weibo": "weibo-shishiresou",
|
||||
"zaobao": "lianhezaobao",
|
||||
"coolapk": "kuan-jinrizuire",
|
||||
"wallstreetcn-quick": "huaerjiejianwen-shishikuaixun",
|
||||
"wallstreetcn-news": "huaerjiejianwen-zuixinzixun",
|
||||
"wallstreetcn-hot": "huaerjiejianwen-zuirewenzhang",
|
||||
"mktnews-flash": "MKTNews-kuaixun",
|
||||
"wallstreetcn-quick": "huaerjiejianwen-kuaixun",
|
||||
"wallstreetcn-news": "huaerjiejianwen-zuixin",
|
||||
"wallstreetcn-hot": "huaerjiejianwen-zuire",
|
||||
"36kr-quick": "36ke-kuaixun",
|
||||
"douyin": "douyin",
|
||||
"hupu": "hupu-zhugandaoretie",
|
||||
@@ -16,8 +17,7 @@
|
||||
"thepaper": "pengpaixinwen-rebang",
|
||||
"sputniknewscn": "weixingtongxunshe",
|
||||
"cankaoxiaoxi": "cankaoxiaoxi",
|
||||
"pcbeta-windows11": "yuanjingluntan-Windows 11",
|
||||
"pcbeta-windows": "yuanjingluntan-Windows ziyuan",
|
||||
"pcbeta-windows11": "yuanjingluntan-Win11",
|
||||
"cls-telegraph": "cailianshe-dianbao",
|
||||
"cls-depth": "cailianshe-shendu",
|
||||
"cls-hot": "cailianshe-remen",
|
||||
@@ -36,14 +36,14 @@
|
||||
"kaopu": "kaopuxinwen",
|
||||
"jin10": "jinshishuju",
|
||||
"baidu": "baiduresou",
|
||||
"linuxdo-latest": "LINUX DO-zuixin",
|
||||
"linuxdo-latest": "LINUX DO-zuixin",
|
||||
"linuxdo-hot": "LINUX DO-jinrizuire",
|
||||
"ghxi": "guoheboke",
|
||||
"smzdm": "shenmezhidemai",
|
||||
"idcflare-latest": "IDC Flare-zuixin",
|
||||
"idcflare-hot": "IDC Flare-jinrizuire",
|
||||
"nowcoder": "niuke",
|
||||
"sspai": "shaoshupai",
|
||||
"juejin": "xitujuejin",
|
||||
"ifeng": "fenghuangwang-redianzixun",
|
||||
"chongbuluo-latest": "chongbuluo-zuixin",
|
||||
"chongbuluo-hot": "chongbuluo-zuire"
|
||||
}
|
||||
}
|
||||
|
||||
+67
-24
@@ -57,6 +57,18 @@ export const originSources = {
|
||||
title: "今日最热",
|
||||
home: "https://coolapk.com",
|
||||
},
|
||||
"mktnews": {
|
||||
name: "MKTNews",
|
||||
column: "finance",
|
||||
home: "https://mktnews.net",
|
||||
color: "indigo",
|
||||
interval: Time.Realtime,
|
||||
sub: {
|
||||
flash: {
|
||||
title: "快讯",
|
||||
},
|
||||
},
|
||||
},
|
||||
"wallstreetcn": {
|
||||
name: "华尔街见闻",
|
||||
color: "blue",
|
||||
@@ -66,14 +78,14 @@ export const originSources = {
|
||||
quick: {
|
||||
type: "realtime",
|
||||
interval: Time.Fast,
|
||||
title: "实时快讯",
|
||||
title: "快讯",
|
||||
},
|
||||
news: {
|
||||
title: "最新资讯",
|
||||
title: "最新",
|
||||
interval: Time.Common,
|
||||
},
|
||||
hot: {
|
||||
title: "最热文章",
|
||||
title: "最热",
|
||||
type: "hottest",
|
||||
interval: Time.Common,
|
||||
},
|
||||
@@ -159,7 +171,7 @@ export const originSources = {
|
||||
home: "https://bbs.pcbeta.com",
|
||||
sub: {
|
||||
windows11: {
|
||||
title: "Windows 11",
|
||||
title: "Win11",
|
||||
type: "realtime",
|
||||
interval: Time.Fast,
|
||||
},
|
||||
@@ -167,6 +179,7 @@ export const originSources = {
|
||||
title: "Windows 资源",
|
||||
type: "realtime",
|
||||
interval: Time.Fast,
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -323,7 +336,6 @@ export const originSources = {
|
||||
column: "tech",
|
||||
color: "slate",
|
||||
home: "https://linux.do/",
|
||||
disable: "cf",
|
||||
sub: {
|
||||
latest: {
|
||||
title: "最新",
|
||||
@@ -336,12 +348,31 @@ export const originSources = {
|
||||
home: "https://linux.do/hot",
|
||||
},
|
||||
},
|
||||
},
|
||||
"idcflare": {
|
||||
name: "IDC Flare",
|
||||
column: "tech",
|
||||
color: "slate",
|
||||
home: "https://idcflare.com/",
|
||||
sub: {
|
||||
latest: {
|
||||
title: "最新",
|
||||
home: "https://idcflare.com/latest",
|
||||
},
|
||||
hot: {
|
||||
title: "今日最热",
|
||||
type: "hottest",
|
||||
interval: Time.Common,
|
||||
home: "https://idcflare.com/hot",
|
||||
},
|
||||
},
|
||||
},
|
||||
"ghxi": {
|
||||
name: "果核剥壳",
|
||||
column: "china",
|
||||
color: "yellow",
|
||||
home: "https://www.ghxi.com/",
|
||||
disable: true,
|
||||
},
|
||||
"smzdm": {
|
||||
name: "什么值得买",
|
||||
@@ -349,6 +380,7 @@ export const originSources = {
|
||||
color: "red",
|
||||
type: "hottest",
|
||||
home: "https://www.smzdm.com",
|
||||
disable: true,
|
||||
},
|
||||
"nowcoder": {
|
||||
name: "牛客",
|
||||
@@ -417,29 +449,40 @@ export function genSources() {
|
||||
if (source.sub && Object.keys(source.sub).length) {
|
||||
Object.entries(source.sub).forEach(([subId, subSource], i) => {
|
||||
if (i === 0) {
|
||||
_.push([id, {
|
||||
redirect: `${id}-${subId}`,
|
||||
...parent,
|
||||
...subSource,
|
||||
}] as [any, Source])
|
||||
_.push([
|
||||
id,
|
||||
{
|
||||
redirect: `${id}-${subId}`,
|
||||
...parent,
|
||||
...subSource,
|
||||
},
|
||||
] as [any, Source])
|
||||
}
|
||||
_.push([`${id}-${subId}`, { ...parent, ...subSource }] as [any, Source])
|
||||
_.push([`${id}-${subId}`, { ...parent, ...subSource }] as [
|
||||
any,
|
||||
Source,
|
||||
])
|
||||
})
|
||||
} else {
|
||||
_.push([id, {
|
||||
title: source.title,
|
||||
...parent,
|
||||
}])
|
||||
_.push([
|
||||
id,
|
||||
{
|
||||
title: source.title,
|
||||
...parent,
|
||||
},
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
return typeSafeObjectFromEntries(_.filter(([_, v]) => {
|
||||
if (v.disable === "cf" && process.env.CF_PAGES) {
|
||||
return false
|
||||
} else if (v.disable === true) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}))
|
||||
return typeSafeObjectFromEntries(
|
||||
_.filter(([_, v]) => {
|
||||
if (v.disable === "cf" && process.env.CF_PAGES) {
|
||||
return false
|
||||
} else if (v.disable === true) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
+33
-51
@@ -51,6 +51,23 @@
|
||||
"color": "green",
|
||||
"interval": 600000
|
||||
},
|
||||
"mktnews": {
|
||||
"redirect": "mktnews-flash",
|
||||
"name": "MKTNews",
|
||||
"column": "finance",
|
||||
"home": "https://mktnews.net",
|
||||
"color": "indigo",
|
||||
"interval": 120000,
|
||||
"title": "快讯"
|
||||
},
|
||||
"mktnews-flash": {
|
||||
"name": "MKTNews",
|
||||
"column": "finance",
|
||||
"home": "https://mktnews.net",
|
||||
"color": "indigo",
|
||||
"interval": 120000,
|
||||
"title": "快讯"
|
||||
},
|
||||
"wallstreetcn": {
|
||||
"redirect": "wallstreetcn-quick",
|
||||
"name": "华尔街见闻",
|
||||
@@ -59,7 +76,7 @@
|
||||
"home": "https://wallstreetcn.com/",
|
||||
"color": "blue",
|
||||
"interval": 300000,
|
||||
"title": "实时快讯"
|
||||
"title": "快讯"
|
||||
},
|
||||
"wallstreetcn-quick": {
|
||||
"name": "华尔街见闻",
|
||||
@@ -68,7 +85,7 @@
|
||||
"home": "https://wallstreetcn.com/",
|
||||
"color": "blue",
|
||||
"interval": 300000,
|
||||
"title": "实时快讯"
|
||||
"title": "快讯"
|
||||
},
|
||||
"wallstreetcn-news": {
|
||||
"name": "华尔街见闻",
|
||||
@@ -76,7 +93,7 @@
|
||||
"home": "https://wallstreetcn.com/",
|
||||
"color": "blue",
|
||||
"interval": 1800000,
|
||||
"title": "最新资讯"
|
||||
"title": "最新"
|
||||
},
|
||||
"wallstreetcn-hot": {
|
||||
"name": "华尔街见闻",
|
||||
@@ -85,7 +102,7 @@
|
||||
"home": "https://wallstreetcn.com/",
|
||||
"color": "blue",
|
||||
"interval": 1800000,
|
||||
"title": "最热文章"
|
||||
"title": "最热"
|
||||
},
|
||||
"36kr": {
|
||||
"redirect": "36kr-quick",
|
||||
@@ -181,7 +198,7 @@
|
||||
"home": "https://bbs.pcbeta.com",
|
||||
"color": "blue",
|
||||
"interval": 300000,
|
||||
"title": "Windows 11"
|
||||
"title": "Win11"
|
||||
},
|
||||
"pcbeta-windows11": {
|
||||
"name": "远景论坛",
|
||||
@@ -190,16 +207,7 @@
|
||||
"home": "https://bbs.pcbeta.com",
|
||||
"color": "blue",
|
||||
"interval": 300000,
|
||||
"title": "Windows 11"
|
||||
},
|
||||
"pcbeta-windows": {
|
||||
"name": "远景论坛",
|
||||
"type": "realtime",
|
||||
"column": "tech",
|
||||
"home": "https://bbs.pcbeta.com",
|
||||
"color": "blue",
|
||||
"interval": 300000,
|
||||
"title": "Windows 资源"
|
||||
"title": "Win11"
|
||||
},
|
||||
"cls": {
|
||||
"redirect": "cls-telegraph",
|
||||
@@ -406,49 +414,23 @@
|
||||
"color": "blue",
|
||||
"interval": 600000
|
||||
},
|
||||
"linuxdo": {
|
||||
"redirect": "linuxdo-latest",
|
||||
"name": "LINUX DO",
|
||||
"disable": "cf",
|
||||
"column": "tech",
|
||||
"home": "https://linux.do/latest",
|
||||
"color": "slate",
|
||||
"interval": 600000,
|
||||
"title": "最新"
|
||||
},
|
||||
"linuxdo-latest": {
|
||||
"name": "LINUX DO",
|
||||
"disable": "cf",
|
||||
"column": "tech",
|
||||
"home": "https://linux.do/latest",
|
||||
"color": "slate",
|
||||
"interval": 600000,
|
||||
"title": "最新"
|
||||
},
|
||||
"linuxdo-hot": {
|
||||
"name": "LINUX DO",
|
||||
"type": "hottest",
|
||||
"disable": "cf",
|
||||
"column": "tech",
|
||||
"home": "https://linux.do/hot",
|
||||
"color": "slate",
|
||||
"home": "https://linux.do",
|
||||
"color": "gray",
|
||||
"interval": 1800000,
|
||||
"title": "今日最热"
|
||||
},
|
||||
"ghxi": {
|
||||
"name": "果核剥壳",
|
||||
"column": "china",
|
||||
"home": "https://www.ghxi.com/",
|
||||
"color": "yellow",
|
||||
"interval": 600000
|
||||
},
|
||||
"smzdm": {
|
||||
"name": "什么值得买",
|
||||
"idcflare-hot": {
|
||||
"name": "IDC Flare",
|
||||
"type": "hottest",
|
||||
"column": "china",
|
||||
"home": "https://www.smzdm.com",
|
||||
"color": "red",
|
||||
"interval": 600000
|
||||
"column": "tech",
|
||||
"home": "https://idcflare.com",
|
||||
"color": "gray",
|
||||
"interval": 1800000,
|
||||
"title": "今日最热"
|
||||
},
|
||||
"nowcoder": {
|
||||
"name": "牛客",
|
||||
@@ -509,4 +491,4 @@
|
||||
"interval": 1800000,
|
||||
"title": "最热"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,11 +236,11 @@ function NewsListHot({ items }: { items: NewsItem[] }) {
|
||||
key={item.id}
|
||||
title={item.extra?.hover}
|
||||
className={$(
|
||||
"flex gap-2 items-center items-stretch relative cursor-pointer [&_*]:cursor-pointer transition-all",
|
||||
"flex gap-2 items-start items-stretch relative cursor-pointer [&_*]:cursor-pointer transition-all",
|
||||
"hover:bg-neutral-400/10 rounded-md pr-1 visited:(text-neutral-400)",
|
||||
)}
|
||||
>
|
||||
<span className={$("bg-neutral-400/10 min-w-6 flex justify-center items-center rounded-md text-sm")}>
|
||||
<span className={$("bg-neutral-400/10 min-w-6 flex justify-center items-center rounded-md text-sm h-6 flex-shrink-0")}>
|
||||
{i + 1}
|
||||
</span>
|
||||
{!!item.extra?.diff && <DiffNumber diff={item.extra.diff} />}
|
||||
|
||||
@@ -8,8 +8,10 @@ import { useThrottleFn } from "ahooks"
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react"
|
||||
import { motion } from "framer-motion"
|
||||
import { useWindowSize } from "react-use"
|
||||
import { isMobile } from "react-device-detect"
|
||||
import { DndContext } from "../common/dnd"
|
||||
import { useSortable } from "../common/dnd/useSortable"
|
||||
import { OverlayScrollbar } from "../common/overlay-scrollbar"
|
||||
import type { ItemsProps } from "./card"
|
||||
import { CardWrapper } from "./card"
|
||||
import { currentSourcesAtom } from "~/atoms"
|
||||
@@ -26,58 +28,76 @@ export function Dnd() {
|
||||
return Math.min(width - 32, WIDTH)
|
||||
}, [width])
|
||||
|
||||
if (!items.length) return null
|
||||
|
||||
return (
|
||||
<DndWrapper items={items} setItems={setItems}>
|
||||
<motion.ol
|
||||
className="grid w-full gap-6"
|
||||
ref={parent}
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(auto-fill, minmax(${minWidth}px, 1fr))`,
|
||||
}}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
variants={{
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
delayChildren: 0.1,
|
||||
staggerChildren: 0.1,
|
||||
<DndWrapper items={items} setItems={setItems} isSingleColumn={isMobile}>
|
||||
<OverlayScrollbar defer className="overflow-x-auto">
|
||||
<motion.ol
|
||||
className={isMobile
|
||||
? "flex px-2 gap-6 pb-4 scroll-smooth"
|
||||
: "grid w-full gap-6"}
|
||||
ref={parent}
|
||||
style={isMobile
|
||||
? {
|
||||
// 横向滚动布局
|
||||
}
|
||||
: {
|
||||
gridTemplateColumns: `repeat(auto-fill, minmax(${minWidth}px, 1fr))`,
|
||||
}}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
variants={{
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{items.map(id => (
|
||||
<motion.li
|
||||
key={id}
|
||||
transition={{
|
||||
type: "tween",
|
||||
duration: AnimationDuration / 1000,
|
||||
}}
|
||||
variants={{
|
||||
hidden: {
|
||||
y: 20,
|
||||
opacity: 0,
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
delayChildren: 0.1,
|
||||
staggerChildren: 0.1,
|
||||
},
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SortableCardWrapper id={id} />
|
||||
</motion.li>
|
||||
))}
|
||||
</motion.ol>
|
||||
},
|
||||
}}
|
||||
>
|
||||
{items.map((id, index) => (
|
||||
<motion.li
|
||||
key={id}
|
||||
className={$(isMobile && "flex-shrink-0", isMobile && index === items.length - 1 && "mr-2")}
|
||||
style={isMobile ? { width: `${width - 16 > WIDTH ? WIDTH : width - 16}px` } : undefined}
|
||||
transition={{
|
||||
type: "tween",
|
||||
duration: AnimationDuration / 1000,
|
||||
}}
|
||||
variants={{
|
||||
hidden: {
|
||||
y: 20,
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SortableCardWrapper id={id} />
|
||||
</motion.li>
|
||||
))}
|
||||
</motion.ol>
|
||||
</OverlayScrollbar>
|
||||
{isMobile && (
|
||||
<div className="flex justify-center">
|
||||
<span className="text-sm text-gray-500 text-center">左右滑动查看更多</span>
|
||||
</div>
|
||||
)}
|
||||
</DndWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
function DndWrapper({ items, setItems, children }: PropsWithChildren<{
|
||||
function DndWrapper({ items, setItems, isSingleColumn, children }: PropsWithChildren<{
|
||||
items: SourceID[]
|
||||
setItems: (items: SourceID[]) => void
|
||||
isSingleColumn: boolean
|
||||
}>) {
|
||||
const onDropTargetChange = useCallback(({ location, source }: BaseEventPayload<ElementDragType>) => {
|
||||
const traget = location.current.dropTargets[0]
|
||||
@@ -91,10 +111,10 @@ function DndWrapper({ items, setItems, children }: PropsWithChildren<{
|
||||
startIndex: fromIndex,
|
||||
indexOfTarget: toIndex,
|
||||
closestEdgeOfTarget,
|
||||
axis: "vertical",
|
||||
axis: isSingleColumn ? "horizontal" : "vertical",
|
||||
})
|
||||
setItems(update)
|
||||
}, [items, setItems])
|
||||
}, [items, setItems, isSingleColumn])
|
||||
// 避免动画干扰
|
||||
const { run } = useThrottleFn(onDropTargetChange, {
|
||||
leading: true,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
}
|
||||
|
||||
[cmdk-list] {
|
||||
--at-apply: px-3 flex flex-col gap-2 items-stretch h-400px;
|
||||
--at-apply: px-3 flex flex-col gap-2 items-stretch max-h-[400px] h-[calc(100vh-100px)];
|
||||
}
|
||||
|
||||
[cmdk-group-heading] {
|
||||
|
||||
@@ -17,6 +17,12 @@ function GoTop() {
|
||||
)
|
||||
}
|
||||
|
||||
function Github() {
|
||||
return (
|
||||
<button type="button" title="Github" className="i-ph:github-logo-duotone btn" onClick={() => window.open(Homepage)} />
|
||||
)
|
||||
}
|
||||
|
||||
function Refresh() {
|
||||
const currentSources = useAtomValue(currentSourcesAtom)
|
||||
const { refresh } = useRefetch()
|
||||
@@ -65,6 +71,7 @@ export function Header() {
|
||||
<span className="justify-self-end flex gap-2 items-center text-xl text-primary-600 dark:text-primary">
|
||||
<GoTop />
|
||||
<Refresh />
|
||||
<Github />
|
||||
<Menu />
|
||||
</span>
|
||||
</>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
function ThemeToggle() {
|
||||
const { isDark, toggleDark } = useDark()
|
||||
return (
|
||||
<li onClick={toggleDark} className="cursor-pointer [&_*]:cursor-pointer transition-all">
|
||||
<span className={$("inline-block", isDark ? "i-ph-moon-stars-duotone" : "i-ph-sun-dim-duotone")} />
|
||||
<span>
|
||||
{isDark ? "浅色模式" : "深色模式"}
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
// function ThemeToggle() {
|
||||
// const { isDark, toggleDark } = useDark()
|
||||
// return (
|
||||
// <li onClick={toggleDark} className="cursor-pointer [&_*]:cursor-pointer transition-all">
|
||||
// <span className={$("inline-block", isDark ? "i-ph-moon-stars-duotone" : "i-ph-sun-dim-duotone")} />
|
||||
// <span>
|
||||
// {isDark ? "浅色模式" : "深色模式"}
|
||||
// </span>
|
||||
// </li>
|
||||
// )
|
||||
// }
|
||||
|
||||
export function Menu() {
|
||||
const { loggedIn, login, logout, userInfo, enableLogin } = useLogin()
|
||||
@@ -64,7 +64,7 @@ export function Menu() {
|
||||
<span>Github 账号登录</span>
|
||||
</li>
|
||||
))}
|
||||
<ThemeToggle />
|
||||
{/* <ThemeToggle /> */}
|
||||
<li onClick={() => window.open(Homepage)} className="cursor-pointer [&_*]:cursor-pointer transition-all">
|
||||
<span className="i-ph:github-logo-duotone inline-block" />
|
||||
<span>Star on Github </span>
|
||||
@@ -75,7 +75,7 @@ export function Menu() {
|
||||
>
|
||||
<img
|
||||
alt="GitHub stars badge"
|
||||
src="https://img.shields.io/github/stars/ourongxing/newsnow?logo=github"
|
||||
src="https://img.shields.io/github/stars/ourongxing/newsnow?logo=github&style=flat&labelColor=%235e3c40&color=%23614447"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
@@ -83,7 +83,7 @@ export function Menu() {
|
||||
>
|
||||
<img
|
||||
alt="GitHub forks badge"
|
||||
src="https://img.shields.io/github/forks/ourongxing/newsnow?logo=github"
|
||||
src="https://img.shields.io/github/forks/ourongxing/newsnow?logo=github&style=flat&labelColor=%235e3c40&color=%23614447"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"
|
||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools"
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
|
||||
import type { QueryClient } from "@tanstack/react-query"
|
||||
import { isMobile } from "react-device-detect"
|
||||
import { Header } from "~/components/header"
|
||||
import { GlobalOverlayScrollbar } from "~/components/common/overlay-scrollbar"
|
||||
import { Footer } from "~/components/footer"
|
||||
@@ -30,11 +31,13 @@ function RootComponent() {
|
||||
usePWA()
|
||||
return (
|
||||
<>
|
||||
<GlobalOverlayScrollbar className={$([
|
||||
"h-full overflow-x-auto px-4",
|
||||
"md:(px-10)",
|
||||
"lg:(px-24)",
|
||||
])}
|
||||
<GlobalOverlayScrollbar
|
||||
className={$([
|
||||
!isMobile && "px-4",
|
||||
"h-full overflow-x-auto",
|
||||
"md:(px-10)",
|
||||
"lg:(px-24)",
|
||||
])}
|
||||
>
|
||||
<header
|
||||
className={$([
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
Reference in New Issue
Block a user