1 Commits
Author SHA1 Message Date
ZSCGRandGitHub 87ab79bc81 Update pinyin.json 2025-08-19 13:25:48 +08:00
37 changed files with 3298 additions and 5415 deletions
-3
View File
@@ -1,3 +0,0 @@
# Don't index SpecStory auto-save files, but allow explicit context inclusion via @ references
.specstory/**
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GGITHUB_TOKEN }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
+1 -2
View File
@@ -12,5 +12,4 @@ dev-dist
*.tsbuildinfo
wrangler.toml
imports.app.d.ts
package-lock.json
.specstory/
package-lock.json
+1 -1
View File
@@ -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
+2
View File
@@ -1,3 +1,5 @@
# NewsNow
![](/public/og-image.png)
[English](./README.md) | [简体中文](README.zh-CN.md) | 日本語
+2
View File
@@ -1,3 +1,5 @@
# NewsNow
![](/public/og-image.png)
English | [简体中文](README.zh-CN.md) | [日本語](README.ja-JP.md)
+4 -2
View File
@@ -1,3 +1,7 @@
# 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="FeaturedHelloGitHub" /></a>
![](/public/og-image.png)
[English](./README.md) | 简体中文 | [日本語](README.ja-JP.md)
@@ -110,5 +114,3 @@ pnpm dev
如果本项目对你有所帮助,可以给小猫买点零食。如果需要定制或者其他帮助,请通过下列方式联系备注。
![](./screenshots/reward.gif)
<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="FeaturedHelloGitHub" /></a>
-17
View File
@@ -1,17 +0,0 @@
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
+3 -9
View File
@@ -1,21 +1,15 @@
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
+14 -1
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="zh-CN" class="dark">
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
@@ -49,6 +49,19 @@
</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"))
+1 -11
View File
@@ -17,11 +17,6 @@ const nitroOption: Parameters<typeof viteNitro>[0] = {
connector: "better-sqlite3",
},
},
devDatabase: {
default: {
connector: "better-sqlite3",
},
},
imports: {
dirs: ["server/utils", "shared"],
},
@@ -43,11 +38,6 @@ 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",
@@ -67,4 +57,4 @@ if (process.env.VERCEL) {
export default function () {
return viteNitro(nitroOption)
}
}
+5 -9
View File
@@ -1,9 +1,9 @@
{
"name": "newsnow",
"type": "module",
"version": "0.0.34",
"version": "0.0.28",
"private": true,
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
"packageManager": "pnpm@10.5.2",
"author": {
"url": "https://github.com/ourongxing/",
"email": "[email protected]",
@@ -30,12 +30,11 @@
"@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.10.0",
"better-sqlite3": "^11.8.1",
"cheerio": "^1.0.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.4",
@@ -55,7 +54,6 @@
"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",
@@ -101,7 +99,7 @@
"vitest": "^3.0.7",
"workbox-build": "^7.3.0",
"workbox-window": "^7.3.0",
"wrangler": "4.14.1"
"wrangler": "^3.111.0"
},
"pnpm": {
"patchedDependencies": {
@@ -111,18 +109,16 @@
"@napi-rs/pinyin",
"@parcel/watcher",
"@swc/core",
"esbuild",
"better-sqlite3",
"esbuild",
"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",
+3038 -4831
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

-30
View File
@@ -1,30 +0,0 @@
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,
}
}
})
+1 -1
View File
@@ -82,4 +82,4 @@ export async function getCacheTable() {
} catch (e) {
logger.error("failed to init database ", e)
}
}
}
+1 -1
View File
@@ -66,4 +66,4 @@ export class UserTable {
if (!state.success) throw new Error(`delete user ${key} failed`)
logger.success(`delete user ${key}`)
}
}
}
-1
View File
@@ -22,7 +22,6 @@ 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 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')
-10
View File
@@ -1,10 +0,0 @@
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(";")
-44
View File
@@ -1,44 +0,0 @@
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
}
+1 -1
View File
@@ -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", "/api/mcp"].every(p => !url.pathname.startsWith(p)))
if (["/api/s", "/api/proxy", "/api/latest"].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))) {
+16 -33
View File
@@ -1,37 +1,20 @@
interface HotItem {
id: string
title: string
url: string
mobileUrl: string
interface Res {
data: {
title: string
hot: string
url: string
mobil_url: string
}[]
}
export default defineSource(async () => {
// 获取虎扑新热榜页面的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
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,
}
})
})
+1 -1
View File
@@ -32,7 +32,7 @@ interface Res {
}
const hot = defineSource(async () => {
const res = await myFetch<Res>("https://n8n.chgr.cc/webhook/api?name=linuxdo")
const res = await myFetch<Res>("https://linux.do/top/daily.json")
return res.topic_list.topics
.filter(k => k.visible && !k.archived && !k.pinned)
.map(k => ({
-62
View File
@@ -1,62 +0,0 @@
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,
})
+1 -1
View File
@@ -52,7 +52,7 @@ const news = defineSource(async () => {
const res: NewsRes = await myFetch(apiUrl)
return res.data.items
.filter(k => k.resource_type !== "theme" && k.resource_type !== "ad" && k.resource.type !== "live" && k.resource.uri)
.filter(k => k.resource_type !== "ad" && k.resource.type !== "live" && k.resource.uri)
.map(({ resource: h }) => {
return {
id: h.id,
+34 -130
View File
@@ -1,144 +1,48 @@
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
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 default defineSource(async () => {
const url = "https://m.weibo.cn/api/container/getIndex?containerid=106003type%3D25%26t%3D3%26disable_hot%3D1%26filter_type%3Drealtimehot"
const res: Root = await myFetch(url)
return res.data.cards[0].card_group
.filter((k, i) => i !== 0 && k.desc && !k.actionlog?.ext.includes("ads_word"))
const url = "https://weibo.com/ajax/side/hotSearch"
const res: Res = await myFetch(url)
return res.data.realtime
.filter(k => !k.is_ad)
.map((k) => {
const keyword = k.word_scheme ? k.word_scheme : `#${k.word}#`
return {
id: k.desc!,
title: k.desc!,
id: k.word,
title: k.word,
extra: {
icon: k.icon && {
url: proxyPicture(k.icon),
scale: 1.5,
},
},
url: `https://s.weibo.com/weibo?q=${encodeURIComponent(`#${k.desc}#`)}`,
mobileUrl: k.scheme,
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`,
}
})
})
+19 -34
View File
@@ -1,53 +1,38 @@
interface Res {
data: {
type: "hot_list_feed"
style_type: "1"
feed_specific: {
answer_count: 411
card_label?: {
icon: string
night_icon: string
}
target: {
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
}
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
}
}[]
}
export default defineSource({
zhihu: async () => {
const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-list-web?limit=20&desktop=true"
const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?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.link.url.match(/(\d+)$/)?.[1] ?? k.target.link.url,
title: k.target.title_area.text,
id: k.target.id,
title: k.target.title,
extra: {
info: k.target.metrics_area.text,
hover: k.target.excerpt_area.text,
icon: k.card_label?.night_icon && proxyPicture(k.card_label.night_icon),
},
url: k.target.link.url,
url: `https://www.zhihu.com/question/${urlId || k.target.id}`,
}
})
},
+8 -6
View File
@@ -4,10 +4,9 @@
"weibo": "weibo-shishiresou",
"zaobao": "lianhezaobao",
"coolapk": "kuan-jinrizuire",
"mktnews-flash": "MKTNews-kuaixun",
"wallstreetcn-quick": "huaerjiejianwen-kuaixun",
"wallstreetcn-news": "huaerjiejianwen-zuixin",
"wallstreetcn-hot": "huaerjiejianwen-zuire",
"wallstreetcn-quick": "huaerjiejianwen-shishikuaixun",
"wallstreetcn-news": "huaerjiejianwen-zuixinzixun",
"wallstreetcn-hot": "huaerjiejianwen-zuirewenzhang",
"36kr-quick": "36ke-kuaixun",
"douyin": "douyin",
"hupu": "hupu-zhugandaoretie",
@@ -17,7 +16,8 @@
"thepaper": "pengpaixinwen-rebang",
"sputniknewscn": "weixingtongxunshe",
"cankaoxiaoxi": "cankaoxiaoxi",
"pcbeta-windows11": "yuanjingluntan-Win11",
"pcbeta-windows11": "yuanjingluntan-Windows 11",
"pcbeta-windows": "yuanjingluntan-Windows ziyuan",
"cls-telegraph": "cailianshe-dianbao",
"cls-depth": "cailianshe-shendu",
"cls-hot": "cailianshe-remen",
@@ -36,8 +36,10 @@
"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",
"nowcoder": "niuke",
"sspai": "shaoshupai",
"juejin": "xitujuejin",
+24 -49
View File
@@ -57,18 +57,6 @@ 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",
@@ -78,14 +66,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,
},
@@ -171,7 +159,7 @@ export const originSources = {
home: "https://bbs.pcbeta.com",
sub: {
windows11: {
title: "Win11",
title: "Windows 11",
type: "realtime",
interval: Time.Fast,
},
@@ -179,7 +167,6 @@ export const originSources = {
title: "Windows 资源",
type: "realtime",
interval: Time.Fast,
disable: true,
},
},
},
@@ -336,6 +323,7 @@ export const originSources = {
column: "tech",
color: "slate",
home: "https://linux.do/",
disable: "cf",
sub: {
latest: {
title: "最新",
@@ -354,7 +342,6 @@ export const originSources = {
column: "china",
color: "yellow",
home: "https://www.ghxi.com/",
disable: true,
},
"smzdm": {
name: "什么值得买",
@@ -362,7 +349,6 @@ export const originSources = {
color: "red",
type: "hottest",
home: "https://www.smzdm.com",
disable: true,
},
"nowcoder": {
name: "牛客",
@@ -431,40 +417,29 @@ 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
}
}))
}
+53 -26
View File
@@ -51,23 +51,6 @@
"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": "华尔街见闻",
@@ -76,7 +59,7 @@
"home": "https://wallstreetcn.com/",
"color": "blue",
"interval": 300000,
"title": "快讯"
"title": "实时快讯"
},
"wallstreetcn-quick": {
"name": "华尔街见闻",
@@ -85,7 +68,7 @@
"home": "https://wallstreetcn.com/",
"color": "blue",
"interval": 300000,
"title": "快讯"
"title": "实时快讯"
},
"wallstreetcn-news": {
"name": "华尔街见闻",
@@ -93,7 +76,7 @@
"home": "https://wallstreetcn.com/",
"color": "blue",
"interval": 1800000,
"title": "最新"
"title": "最新资讯"
},
"wallstreetcn-hot": {
"name": "华尔街见闻",
@@ -102,7 +85,7 @@
"home": "https://wallstreetcn.com/",
"color": "blue",
"interval": 1800000,
"title": "最热"
"title": "最热文章"
},
"36kr": {
"redirect": "36kr-quick",
@@ -198,7 +181,7 @@
"home": "https://bbs.pcbeta.com",
"color": "blue",
"interval": 300000,
"title": "Win11"
"title": "Windows 11"
},
"pcbeta-windows11": {
"name": "远景论坛",
@@ -207,7 +190,16 @@
"home": "https://bbs.pcbeta.com",
"color": "blue",
"interval": 300000,
"title": "Win11"
"title": "Windows 11"
},
"pcbeta-windows": {
"name": "远景论坛",
"type": "realtime",
"column": "tech",
"home": "https://bbs.pcbeta.com",
"color": "blue",
"interval": 300000,
"title": "Windows 资源"
},
"cls": {
"redirect": "cls-telegraph",
@@ -414,15 +406,50 @@
"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",
"color": "gray",
"home": "https://linux.do/hot",
"color": "slate",
"interval": 1800000,
"title": "今日最热"
},
"ghxi": {
"name": "果核剥壳",
"column": "china",
"home": "https://www.ghxi.com/",
"color": "yellow",
"interval": 600000
},
"smzdm": {
"name": "什么值得买",
"type": "hottest",
"column": "china",
"home": "https://www.smzdm.com",
"color": "red",
"interval": 600000
},
"nowcoder": {
"name": "牛客",
"type": "hottest",
@@ -482,4 +509,4 @@
"interval": 1800000,
"title": "最热"
}
}
}
+2 -2
View File
@@ -236,11 +236,11 @@ function NewsListHot({ items }: { items: NewsItem[] }) {
key={item.id}
title={item.extra?.hover}
className={$(
"flex gap-2 items-start items-stretch relative cursor-pointer [&_*]:cursor-pointer transition-all",
"flex gap-2 items-center 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 h-6 flex-shrink-0")}>
<span className={$("bg-neutral-400/10 min-w-6 flex justify-center items-center rounded-md text-sm")}>
{i + 1}
</span>
{!!item.extra?.diff && <DiffNumber diff={item.extra.diff} />}
+45 -65
View File
@@ -8,10 +8,8 @@ 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"
@@ -28,76 +26,58 @@ export function Dnd() {
return Math.min(width - 32, WIDTH)
}, [width])
if (!items.length) return null
return (
<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,
<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,
},
visible: {
opacity: 1,
transition: {
delayChildren: 0.1,
staggerChildren: 0.1,
},
}}
>
{items.map(id => (
<motion.li
key={id}
transition={{
type: "tween",
duration: AnimationDuration / 1000,
}}
variants={{
hidden: {
y: 20,
opacity: 0,
},
},
}}
>
{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>
)}
visible: {
y: 0,
opacity: 1,
},
}}
>
<SortableCardWrapper id={id} />
</motion.li>
))}
</motion.ol>
</DndWrapper>
)
}
function DndWrapper({ items, setItems, isSingleColumn, children }: PropsWithChildren<{
function DndWrapper({ items, setItems, children }: PropsWithChildren<{
items: SourceID[]
setItems: (items: SourceID[]) => void
isSingleColumn: boolean
}>) {
const onDropTargetChange = useCallback(({ location, source }: BaseEventPayload<ElementDragType>) => {
const traget = location.current.dropTargets[0]
@@ -111,10 +91,10 @@ function DndWrapper({ items, setItems, isSingleColumn, children }: PropsWithChil
startIndex: fromIndex,
indexOfTarget: toIndex,
closestEdgeOfTarget,
axis: isSingleColumn ? "horizontal" : "vertical",
axis: "vertical",
})
setItems(update)
}, [items, setItems, isSingleColumn])
}, [items, setItems])
// 避免动画干扰
const { run } = useThrottleFn(onDropTargetChange, {
leading: true,
+1 -1
View File
@@ -19,7 +19,7 @@
}
[cmdk-list] {
--at-apply: px-3 flex flex-col gap-2 items-stretch max-h-[400px] h-[calc(100vh-100px)];
--at-apply: px-3 flex flex-col gap-2 items-stretch h-400px;
}
[cmdk-group-heading] {
-7
View File
@@ -17,12 +17,6 @@ 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()
@@ -71,7 +65,6 @@ 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>
</>
+14 -14
View File
@@ -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&style=flat&labelColor=%235e3c40&color=%23614447"
src="https://img.shields.io/github/stars/ourongxing/newsnow?logo=github"
/>
</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&style=flat&labelColor=%235e3c40&color=%23614447"
src="https://img.shields.io/github/forks/ourongxing/newsnow?logo=github"
/>
</a>
</li>
+5 -8
View File
@@ -4,7 +4,6 @@ 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"
@@ -31,13 +30,11 @@ function RootComponent() {
usePWA()
return (
<>
<GlobalOverlayScrollbar
className={$([
!isMobile && "px-4",
"h-full overflow-x-auto",
"md:(px-10)",
"lg:(px-24)",
])}
<GlobalOverlayScrollbar className={$([
"h-full overflow-x-auto px-4",
"md:(px-10)",
"lg:(px-24)",
])}
>
<header
className={$([
-1
View File
@@ -8,7 +8,6 @@
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"strict": true,
"allowJs": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,