Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50711a91f2 | ||
|
|
1b5fedd508 | ||
|
|
02e0616155 | ||
|
|
06a16c2e03 | ||
|
|
55582bb3f3 | ||
|
|
a7e5540122 | ||
|
|
7bd07463a3 | ||
|
|
5608f3e1f2 | ||
|
|
c5853938ff | ||
|
|
ee00b5db5d | ||
|
|
b660dc4613 | ||
|
|
8a6aacc70d | ||
|
|
9fefe96f7b | ||
|
|
819640c683 | ||
|
|
fcdf837435 | ||
|
|
fa9f552d73 | ||
|
|
7e88e142b2 | ||
|
|
b60bb219ec | ||
|
|
3e870e1e37 | ||
|
|
3721fd383a | ||
|
|
8142074b3c | ||
|
|
5a2e2a5e9c | ||
|
|
cb9b320996 | ||
|
|
3b845ce6b5 | ||
|
|
a0941eff63 | ||
|
|
7e6805ca87 | ||
|
|
4b89a57889 | ||
|
|
5627cbef60 | ||
|
|
57dc2c319c | ||
|
|
84543b12b6 | ||
|
|
3584efb09b | ||
|
|
f357e920b7 | ||
|
|
dca03c4f0c | ||
|
|
576137a62f | ||
|
|
a896f15c44 | ||
|
|
f78eead5fd | ||
|
|
6fe8f03f16 | ||
|
|
8e7b35385b | ||
|
|
f2698ba2f0 | ||
|
|
5a07c02ad0 | ||
|
|
ce0baddc78 | ||
|
|
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/**
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
name: Auto Refresh
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '*/20 0-17,22,23 * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
|
|
||||||
- name: Refresh
|
|
||||||
env:
|
|
||||||
JWT_TOKEN: ${{ secrets.JWT_TOKEN }}
|
|
||||||
run: npx tsx ./scripts/refresh.ts
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Publish Docker image
|
name: Publish Docker image to Gitea
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -8,11 +8,12 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-docker:
|
build-docker:
|
||||||
name: Push Docker image to multiple registries
|
name: Build and push to Gitea Registry (Internal)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write # 必须:允许写入软件包
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -22,19 +23,29 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
# 👇 告诉 Buildx 这个地址允许 HTTP (insecure)
|
||||||
|
driver-opts: network=1panel-network
|
||||||
|
config-inline: |
|
||||||
|
[registry."172.18.0.21:3000"]
|
||||||
|
http = true
|
||||||
|
insecure = true
|
||||||
|
|
||||||
- name: Log in to the Container registry
|
- name: Log in to Gitea Registry (Internal)
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
# ✅ 修改点1: 使用内网地址,避开 Cloudflare 和 WAF
|
||||||
|
registry: 172.18.0.21:3000
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GGITEA_PAT }}
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
# ✅ 修改点2: 镜像名必须以 gitea:3000 开头,才能推送到内网注册中心
|
||||||
|
# Gitea 会自动处理映射,你在外网依然可以用 git.chgr.cc 拉取
|
||||||
|
images: 172.18.0.21:3000/${{ github.repository }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branch: main
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: lts/*
|
|
||||||
|
|
||||||
- run: npx changelogithub
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
||||||
@@ -13,3 +13,4 @@ dev-dist
|
|||||||
wrangler.toml
|
wrangler.toml
|
||||||
imports.app.d.ts
|
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
|
WORKDIR /usr/src
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
# NewsNow
|
|
||||||
|
|
||||||

|

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

|

|
||||||
|
|
||||||
English | [简体中文](README.zh-CN.md) | [日本語](README.ja-JP.md)
|
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)
|
[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
|
||||||
+10
-3
@@ -1,15 +1,22 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
newsnow:
|
newsnow:
|
||||||
image: ghcr.io/ourongxing/newsnow:latest
|
image: ghcr.io/ourongxing/newsnow:latest
|
||||||
container_name: newsnow
|
container_name: newsnow
|
||||||
restart: always
|
|
||||||
ports:
|
ports:
|
||||||
- '4444:4444'
|
- '4444:4444'
|
||||||
|
volumes:
|
||||||
|
- newsnow_data:/usr/app/.data
|
||||||
environment:
|
environment:
|
||||||
|
- HOST=0.0.0.0
|
||||||
|
- PORT=4444
|
||||||
|
- NODE_ENV=production
|
||||||
- G_CLIENT_ID=
|
- G_CLIENT_ID=
|
||||||
- G_CLIENT_SECRET=
|
- G_CLIENT_SECRET=
|
||||||
- JWT_SECRET=
|
- JWT_SECRET=
|
||||||
- INIT_TABLE=true
|
- INIT_TABLE=true
|
||||||
- ENABLE_CACHE=true
|
- ENABLE_CACHE=true
|
||||||
|
- PRODUCTHUNT_API_TOKEN=
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
newsnow_data:
|
||||||
|
name: newsnow_data
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ G_CLIENT_SECRET=
|
|||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
INIT_TABLE=true
|
INIT_TABLE=true
|
||||||
ENABLE_CACHE=true
|
ENABLE_CACHE=true
|
||||||
|
PRODUCTHUNT_API_TOKEN=
|
||||||
+6
-19
@@ -1,5 +1,5 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN" class="dark">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
@@ -15,14 +15,14 @@
|
|||||||
<meta property="og:title" content="NewsNow - 优雅的新闻聚合阅读器" />
|
<meta property="og:title" content="NewsNow - 优雅的新闻聚合阅读器" />
|
||||||
<meta property="og:description" content="实时新闻聚合阅读器,汇集全球热点新闻,提供优雅的阅读体验" />
|
<meta property="og:description" content="实时新闻聚合阅读器,汇集全球热点新闻,提供优雅的阅读体验" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="https://newsnow.busiyi.world" />
|
<meta property="og:url" content="https://hot.chgr.cc" />
|
||||||
<meta property="og:image" content="https://newsnow.busiyi.world/og-image.png" />
|
<meta property="og:image" content="https://hot.chgr.cc/og-image.png" />
|
||||||
|
|
||||||
<!-- Twitter Card Meta Tags -->
|
<!-- Twitter Card Meta Tags -->
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:title" content="NewsNow - 优雅的新闻聚合阅读器" />
|
<meta name="twitter:title" content="NewsNow - 优雅的新闻聚合阅读器" />
|
||||||
<meta name="twitter:description" content="实时新闻聚合阅读器,汇集全球热点新闻,提供优雅的阅读体验" />
|
<meta name="twitter:description" content="实时新闻聚合阅读器,汇集全球热点新闻,提供优雅的阅读体验" />
|
||||||
<meta name="twitter:image" content="https://newsnow.busiyi.world/og-image.svg" />
|
<meta name="twitter:image" content="https://hot.chgr.cc/og-image.svg" />
|
||||||
|
|
||||||
<meta name="theme-color" content="#F14D42" />
|
<meta name="theme-color" content="#F14D42" />
|
||||||
<link rel="preload" href="/Baloo2-Bold.subset.ttf" as="font" type="font/ttf" crossorigin>
|
<link rel="preload" href="/Baloo2-Bold.subset.ttf" as="font" type="font/ttf" crossorigin>
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "WebSite",
|
"@type": "WebSite",
|
||||||
"name": "NewsNow",
|
"name": "NewsNow",
|
||||||
"url": "https://newsnow.busiyi.world",
|
"url": "https://hot.chgr.cc",
|
||||||
"description": "实时新闻聚合阅读器,汇集全球热点新闻,提供优雅的阅读体验",
|
"description": "实时新闻聚合阅读器,汇集全球热点新闻,提供优雅的阅读体验"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -49,19 +49,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<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)
|
const query = new URLSearchParams(window.location.search)
|
||||||
if (query.has("login") && query.has("user") && query.has("jwt")) {
|
if (query.has("login") && query.has("user") && query.has("jwt")) {
|
||||||
localStorage.setItem("user", query.get("user"))
|
localStorage.setItem("user", query.get("user"))
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ const nitroOption: Parameters<typeof viteNitro>[0] = {
|
|||||||
connector: "better-sqlite3",
|
connector: "better-sqlite3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
devDatabase: {
|
||||||
|
default: {
|
||||||
|
connector: "better-sqlite3",
|
||||||
|
},
|
||||||
|
},
|
||||||
imports: {
|
imports: {
|
||||||
dirs: ["server/utils", "shared"],
|
dirs: ["server/utils", "shared"],
|
||||||
},
|
},
|
||||||
@@ -38,6 +43,11 @@ if (process.env.VERCEL) {
|
|||||||
// }
|
// }
|
||||||
} else if (process.env.CF_PAGES) {
|
} else if (process.env.CF_PAGES) {
|
||||||
nitroOption.preset = "cloudflare-pages"
|
nitroOption.preset = "cloudflare-pages"
|
||||||
|
nitroOption.unenv = {
|
||||||
|
alias: {
|
||||||
|
"safer-buffer": "node:buffer",
|
||||||
|
},
|
||||||
|
}
|
||||||
nitroOption.database = {
|
nitroOption.database = {
|
||||||
default: {
|
default: {
|
||||||
connector: "cloudflare-d1",
|
connector: "cloudflare-d1",
|
||||||
|
|||||||
+9
-5
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "newsnow",
|
"name": "newsnow",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.0.28",
|
"version": "0.0.37",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@10.5.2",
|
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
|
||||||
"author": {
|
"author": {
|
||||||
"url": "https://github.com/ourongxing/",
|
"url": "https://github.com/ourongxing/",
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
@@ -30,11 +30,12 @@
|
|||||||
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
||||||
"@formkit/auto-animate": "^0.8.2",
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@iconify-json/si": "^1.2.3",
|
"@iconify-json/si": "^1.2.3",
|
||||||
|
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||||
"@tanstack/react-query-devtools": "^5.66.11",
|
"@tanstack/react-query-devtools": "^5.66.11",
|
||||||
"@tanstack/react-router": "^1.112.0",
|
"@tanstack/react-router": "^1.112.0",
|
||||||
"@unocss/reset": "^66.0.0",
|
"@unocss/reset": "^66.0.0",
|
||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
"better-sqlite3": "^11.8.1",
|
"better-sqlite3": "^11.10.0",
|
||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.4",
|
"cmdk": "^1.0.4",
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
"overlayscrollbars": "^2.11.1",
|
"overlayscrollbars": "^2.11.1",
|
||||||
"pnpm": "^10.5.2",
|
"pnpm": "^10.5.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-use": "^17.6.0",
|
"react-use": "^17.6.0",
|
||||||
"uncrypto": "^0.1.3",
|
"uncrypto": "^0.1.3",
|
||||||
@@ -99,7 +101,7 @@
|
|||||||
"vitest": "^3.0.7",
|
"vitest": "^3.0.7",
|
||||||
"workbox-build": "^7.3.0",
|
"workbox-build": "^7.3.0",
|
||||||
"workbox-window": "^7.3.0",
|
"workbox-window": "^7.3.0",
|
||||||
"wrangler": "^3.111.0"
|
"wrangler": "4.14.1"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
@@ -109,16 +111,18 @@
|
|||||||
"@napi-rs/pinyin",
|
"@napi-rs/pinyin",
|
||||||
"@parcel/watcher",
|
"@parcel/watcher",
|
||||||
"@swc/core",
|
"@swc/core",
|
||||||
"better-sqlite3",
|
|
||||||
"esbuild",
|
"esbuild",
|
||||||
|
"better-sqlite3",
|
||||||
"sharp",
|
"sharp",
|
||||||
"simple-git-hooks",
|
"simple-git-hooks",
|
||||||
|
"unrs-resolver",
|
||||||
"workerd"
|
"workerd"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"cross-spawn": ">=7.0.6",
|
"cross-spawn": ">=7.0.6",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
|
"nitropack": "npm:[email protected]",
|
||||||
"picomatch": "^4.0.2",
|
"picomatch": "^4.0.2",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"db0": "^0.3.1",
|
"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: 5.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
<url>
|
<url>
|
||||||
<loc>https://newsnow.busiyi.world/</loc>
|
<loc>https://hot.chgr.cc/</loc>
|
||||||
<lastmod>2025-01-18</lastmod>
|
<lastmod>2025-01-18</lastmod>
|
||||||
<changefreq>always</changefreq>
|
<changefreq>always</changefreq>
|
||||||
<priority>1.0</priority>
|
<priority>1.0</priority>
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import sources from "../shared/sources.json"
|
|
||||||
|
|
||||||
Promise.all(Object.keys(sources).map(id =>
|
|
||||||
fetch(`https://newsnow.busiyi.world/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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Vendored
+5
@@ -8,6 +8,7 @@ declare module 'glob:./sources/{*.ts,**/index.ts}' {
|
|||||||
export const chongbuluo: typeof import('./sources/chongbuluo')
|
export const chongbuluo: typeof import('./sources/chongbuluo')
|
||||||
export const cls: typeof import('./sources/cls/index')
|
export const cls: typeof import('./sources/cls/index')
|
||||||
export const coolapk: typeof import('./sources/coolapk/index')
|
export const coolapk: typeof import('./sources/coolapk/index')
|
||||||
|
export const douban: typeof import('./sources/douban')
|
||||||
export const douyin: typeof import('./sources/douyin')
|
export const douyin: typeof import('./sources/douyin')
|
||||||
export const fastbull: typeof import('./sources/fastbull')
|
export const fastbull: typeof import('./sources/fastbull')
|
||||||
export const gelonghui: typeof import('./sources/gelonghui')
|
export const gelonghui: typeof import('./sources/gelonghui')
|
||||||
@@ -22,6 +23,8 @@ declare module 'glob:./sources/{*.ts,**/index.ts}' {
|
|||||||
export const kaopu: typeof import('./sources/kaopu')
|
export const kaopu: typeof import('./sources/kaopu')
|
||||||
export const kuaishou: typeof import('./sources/kuaishou')
|
export const kuaishou: typeof import('./sources/kuaishou')
|
||||||
export const linuxdo: typeof import('./sources/linuxdo')
|
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 nowcoder: typeof import('./sources/nowcoder')
|
||||||
export const pcbeta: typeof import('./sources/pcbeta')
|
export const pcbeta: typeof import('./sources/pcbeta')
|
||||||
export const producthunt: typeof import('./sources/producthunt')
|
export const producthunt: typeof import('./sources/producthunt')
|
||||||
@@ -29,6 +32,8 @@ declare module 'glob:./sources/{*.ts,**/index.ts}' {
|
|||||||
export const solidot: typeof import('./sources/solidot')
|
export const solidot: typeof import('./sources/solidot')
|
||||||
export const sputniknewscn: typeof import('./sources/sputniknewscn')
|
export const sputniknewscn: typeof import('./sources/sputniknewscn')
|
||||||
export const sspai: typeof import('./sources/sspai')
|
export const sspai: typeof import('./sources/sspai')
|
||||||
|
export const steam: typeof import('./sources/steam')
|
||||||
|
export const tencent: typeof import('./sources/tencent')
|
||||||
export const thepaper: typeof import('./sources/thepaper')
|
export const thepaper: typeof import('./sources/thepaper')
|
||||||
export const tieba: typeof import('./sources/tieba')
|
export const tieba: typeof import('./sources/tieba')
|
||||||
export const toutiao: typeof import('./sources/toutiao')
|
export const toutiao: typeof import('./sources/toutiao')
|
||||||
|
|||||||
@@ -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 (!url.pathname.startsWith("/api")) return
|
||||||
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
|
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
|
||||||
event.context.disabledLogin = true
|
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" })
|
throw createError({ statusCode: 506, message: "Server not configured, disable login" })
|
||||||
} else {
|
} else {
|
||||||
if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) {
|
if (["/api/s", "/api/me"].find(p => url.pathname.startsWith(p))) {
|
||||||
|
|||||||
@@ -1,29 +1,3 @@
|
|||||||
interface NewApiVideoItem {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
desc: string;
|
|
||||||
cover: string;
|
|
||||||
author: string;
|
|
||||||
timestamp: number; // 注意这是毫秒时间戳
|
|
||||||
hot: number;
|
|
||||||
url: string;
|
|
||||||
mobileUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NewApiResponse {
|
|
||||||
code: number;
|
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
type: string;
|
|
||||||
description: string;
|
|
||||||
parameData: any; // 可以根据需要细化
|
|
||||||
link: string;
|
|
||||||
total: number;
|
|
||||||
updateTime: string;
|
|
||||||
fromCache: boolean;
|
|
||||||
data: NewApiVideoItem[]; // 核心数据在这里
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WapRes {
|
interface WapRes {
|
||||||
code: number
|
code: number
|
||||||
exp_str: string
|
exp_str: string
|
||||||
@@ -146,56 +120,18 @@ const hotVideo = defineSource(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const ranking = defineSource(async () => {
|
const ranking = defineSource(async () => {
|
||||||
// 1. 使用新接口的 URL
|
const url = "https://api.bilibili.com/x/web-interface/ranking/v2"
|
||||||
const newUrl = "https://api-hot.chgr.cc/bilibili?limit=20"; // <--- 替换成新接口的实际地址
|
const res: HotVideoRes = await myFetch(url)
|
||||||
// 假设 myFetch 可以正确获取并解析新接口的 JSON
|
|
||||||
const newRes: NewApiResponse = await myFetch(newUrl);
|
|
||||||
|
|
||||||
// 2. 数据转换适配层:将 NewApiResponse 转换为符合 HotVideoRes['data']['list'] 结构的数组
|
return res.data.list.map(video => ({
|
||||||
// 注意:我们只需要构建 map 函数需要的部分即可,不需要严格创建完整的 HotVideoRes
|
|
||||||
const adaptedList = newRes.data.map(newItem => {
|
|
||||||
// 创建一个符合 map 函数内部访问 video 对象结构的临时对象
|
|
||||||
return {
|
|
||||||
bvid: newItem.id,
|
|
||||||
title: newItem.title,
|
|
||||||
pubdate: Math.floor(newItem.timestamp / 1000), // 将毫秒转为秒 (整数)
|
|
||||||
owner: {
|
|
||||||
name: newItem.author,
|
|
||||||
// 如果 map 中需要其他 owner 字段,需提供默认值
|
|
||||||
mid: 0,
|
|
||||||
face: '',
|
|
||||||
},
|
|
||||||
stat: {
|
|
||||||
view: newItem.hot, // 使用 hot 作为观看数 (近似值)
|
|
||||||
like: 0, // <--- 新接口没有点赞数,这里设置为 0
|
|
||||||
// 如果 map 中需要其他 stat 字段,需提供默认值
|
|
||||||
danmaku: 0, reply: 0, favorite: 0, coin: 0, share: 0, now_rank: 0, his_rank: 0, dislike: 0,
|
|
||||||
},
|
|
||||||
desc: newItem.desc,
|
|
||||||
pic: newItem.cover,
|
|
||||||
// --- 以下字段如果 map 中没有用到,可以不映射 ---
|
|
||||||
aid: 0, videos: 1, tid: 0, tname: 'N/A', copyright: 1, ctime: Math.floor(newItem.timestamp / 1000),
|
|
||||||
state: 0, duration: 0, dynamic: '', cid: 0, dimension: { width: 0, height: 0, rotate: 0 },
|
|
||||||
short_link: newItem.url, short_link_v2: newItem.url, rcmd_reason: { content: '', corner_mark: 0 },
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 使用转换后的 adaptedList 进行 map 操作,这部分代码保持不变
|
|
||||||
// map 函数现在操作的是我们手动适配过的对象数组
|
|
||||||
return adaptedList.map(video => ({
|
|
||||||
id: video.bvid,
|
id: video.bvid,
|
||||||
title: video.title,
|
title: video.title,
|
||||||
url: `https://www.bilibili.com/video/${video.bvid}`, // 保持原来的 URL 拼接方式
|
url: `https://www.bilibili.com/video/${video.bvid}`,
|
||||||
// 或者,如果你想用新接口提供的 URL:
|
pubDate: video.pubdate * 1000,
|
||||||
// url: video.short_link, // short_link 在适配层映射自 newItem.url
|
|
||||||
pubDate: video.pubdate * 1000, // 将秒转换回毫秒
|
|
||||||
extra: {
|
extra: {
|
||||||
// 注意:因为 like 设置为 0,这里的点赞数会显示为 0
|
|
||||||
info: `${video.owner.name} · ${formatNumber(video.stat.view)}观看 · ${formatNumber(video.stat.like)}点赞`,
|
info: `${video.owner.name} · ${formatNumber(video.stat.view)}观看 · ${formatNumber(video.stat.like)}点赞`,
|
||||||
// 如果不想显示点赞数,可以修改 info 的格式:
|
|
||||||
// info: `${video.owner.name} · ${formatNumber(video.stat.view)}观看`,
|
|
||||||
hover: video.desc,
|
hover: video.desc,
|
||||||
icon: proxyPicture(video.pic), // pic 来源于 newItem.cover
|
icon: proxyPicture(video.pic),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
interface HotMoviesRes {
|
||||||
|
category: string
|
||||||
|
tags: []
|
||||||
|
items: MovieItem[]
|
||||||
|
recommend_tags: []
|
||||||
|
total: number
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MovieItem {
|
||||||
|
rating: {
|
||||||
|
count: number
|
||||||
|
max: number
|
||||||
|
star_count: number
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
title: string
|
||||||
|
pic: {
|
||||||
|
large: string
|
||||||
|
normal: string
|
||||||
|
}
|
||||||
|
is_new: boolean
|
||||||
|
uri: string
|
||||||
|
episodes_info: string
|
||||||
|
card_subtitle: string
|
||||||
|
type: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineSource(async () => {
|
||||||
|
const baseURL = "https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie"
|
||||||
|
const res: HotMoviesRes = await myFetch(baseURL, {
|
||||||
|
headers: {
|
||||||
|
Referer: "https://movie.douban.com/",
|
||||||
|
Accept: "application/json, text/plain, */*",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return res.items.map(movie => ({
|
||||||
|
id: movie.id,
|
||||||
|
title: movie.title,
|
||||||
|
url: `https://movie.douban.com/subject/${movie.id}`,
|
||||||
|
extra: {
|
||||||
|
info: movie.card_subtitle.split(" / ").slice(0, 3).join(" / "),
|
||||||
|
hover: movie.card_subtitle,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
+33
-16
@@ -1,20 +1,37 @@
|
|||||||
interface Res {
|
interface HotItem {
|
||||||
data: {
|
id: string
|
||||||
title: string
|
title: string
|
||||||
hot: string
|
url: string
|
||||||
url: string
|
mobileUrl: string
|
||||||
mobil_url: string
|
|
||||||
}[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineSource(async () => {
|
export default defineSource(async () => {
|
||||||
const r: Res = await myFetch(`https://api.vvhan.com/api/hotlist/huPu`)
|
// 获取虎扑新热榜页面的HTML内容
|
||||||
return r.data.map((k) => {
|
const html = await myFetch(`https://bbs.hupu.com/topic-daily-hot`)
|
||||||
return {
|
|
||||||
id: k.url,
|
// 正则表达式匹配新的热榜项结构
|
||||||
title: k.title,
|
const regex = /<li class="bbs-sl-web-post-body">[\s\S]*?<a href="(\/[^"]+?\.html)"[^>]*?class="p-title"[^>]*>([^<]+)<\/a>/g
|
||||||
url: k.url,
|
|
||||||
mobileUrl: k.mobil_url,
|
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://idcflare.com/t/topic/${k.id}`,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const latest = defineSource(async () => {
|
||||||
|
const res = await myFetch<Res>("https://idcflare.com/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://idcflare.com/t/topic/${k.id}`,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineSource({
|
||||||
|
"idcflare": latest,
|
||||||
|
"idcflare-latest": latest,
|
||||||
|
"idcflare-hot": hot,
|
||||||
|
})
|
||||||
@@ -2,17 +2,17 @@ type Res = {
|
|||||||
description: string
|
description: string
|
||||||
link: string
|
link: string
|
||||||
// Date
|
// Date
|
||||||
pubDate: string
|
pub_date: string
|
||||||
publisher: string
|
publisher: string
|
||||||
title: string
|
title: string
|
||||||
}[]
|
}[]
|
||||||
export default defineSource(async () => {
|
export default defineSource(async () => {
|
||||||
const res = await Promise.all(["https://kaopucdn.azureedge.net/jsondata/news_list_beta_hans_0.json", "https://kaopucdn.azureedge.net/jsondata/news_list_beta_hans_1.json"].map(url => myFetch(url) as Promise<Res>))
|
const res: Res = await $fetch("https://kaopustorage.blob.core.windows.net/news-prod/news_list_hans_0.json")
|
||||||
return res.flat().filter(k => ["财新", "公视"].every(h => k.publisher !== h)).map((k) => {
|
return res.filter(k => ["财新", "公视"].every(h => k.publisher !== h)).map((k) => {
|
||||||
return {
|
return {
|
||||||
id: k.link,
|
id: k.link,
|
||||||
title: k.title,
|
title: k.title,
|
||||||
pubDate: k.pubDate,
|
pubDate: k.pub_date,
|
||||||
extra: {
|
extra: {
|
||||||
hover: k.description,
|
hover: k.description,
|
||||||
info: k.publisher,
|
info: k.publisher,
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
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 categoryData = res.data.find(item => item.name === category)
|
||||||
|
if (!categoryData?.child) return []
|
||||||
|
|
||||||
|
return categoryData.child.flatMap(subCategory =>
|
||||||
|
(subCategory.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.match(/^【([^】]*)】(.*)$/)?.[1] || item.data.content,
|
||||||
|
pubDate: item.time,
|
||||||
|
extra: { info: item.type, hover: item.data.content },
|
||||||
|
url: `https://mktnews.net/flashDetail.html?id=${item.id}`,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineSource({
|
||||||
|
"mktnews": flash,
|
||||||
|
"mktnews-flash": flash,
|
||||||
|
})
|
||||||
@@ -1,28 +1,56 @@
|
|||||||
import * as cheerio from "cheerio"
|
import process from "node:process"
|
||||||
import type { NewsItem } from "@shared/types"
|
import type { NewsItem } from "@shared/types"
|
||||||
|
|
||||||
export default defineSource(async () => {
|
export default defineSource(async () => {
|
||||||
const baseURL = "https://www.producthunt.com"
|
const apiToken = process.env.PRODUCTHUNT_API_TOKEN
|
||||||
const html: any = await myFetch(baseURL)
|
const token = `Bearer ${apiToken}`
|
||||||
const $ = cheerio.load(html)
|
if (!apiToken) {
|
||||||
const $main = $("[data-test=homepage-section-0] [data-test^=post-item]")
|
throw new Error("PRODUCTHUNT_API_TOKEN is not set")
|
||||||
|
}
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
posts(first: 30, order: VOTES) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
tagline
|
||||||
|
votesCount
|
||||||
|
url
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const response: any = await myFetch("https://api.producthunt.com/v2/api/graphql", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Authorization": token,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ query }),
|
||||||
|
})
|
||||||
|
|
||||||
const news: NewsItem[] = []
|
const news: NewsItem[] = []
|
||||||
$main.each((_, el) => {
|
const posts = response?.data?.posts?.edges || []
|
||||||
const a = $(el).find("a").first()
|
|
||||||
const url = a.attr("href")
|
for (const edge of posts) {
|
||||||
const title = $(el).find("a[data-test^=post-name]").text().replace(/^\d+\.\s*/, "")
|
const post = edge.node
|
||||||
const id = $(el).attr("data-test")?.replace("post-item-", "")
|
if (post.id && post.name) {
|
||||||
const vote = $(el).find("[data-test=vote-button]").text()
|
|
||||||
if (url && id && title) {
|
|
||||||
news.push({
|
news.push({
|
||||||
url: `${baseURL}${url}`,
|
id: post.id,
|
||||||
title,
|
title: post.name,
|
||||||
id,
|
url: post.url || `https://www.producthunt.com/posts/${post.slug}`,
|
||||||
extra: {
|
extra: {
|
||||||
info: `△︎ ${vote}`,
|
info: ` △︎ ${post.votesCount || 0}`,
|
||||||
|
hover: post.tagline,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
return news
|
return news
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import * as cheerio from "cheerio"
|
||||||
|
import type { NewsItem } from "@shared/types"
|
||||||
|
|
||||||
|
export default defineSource(async () => {
|
||||||
|
const response: any = await myFetch("https://store.steampowered.com/stats/stats/")
|
||||||
|
const $ = cheerio.load(response)
|
||||||
|
const $rows = $("#detailStats tr.player_count_row")
|
||||||
|
const news: NewsItem[] = []
|
||||||
|
|
||||||
|
$rows.each((_, el) => {
|
||||||
|
const $el = $(el)
|
||||||
|
const $a = $el.find("a.gameLink")
|
||||||
|
const url = $a.attr("href")
|
||||||
|
const gameName = $a.text().trim()
|
||||||
|
const currentPlayers = $el.find("td:first-child .currentServers").text().trim()
|
||||||
|
|
||||||
|
if (url && gameName && currentPlayers) {
|
||||||
|
const title = gameName
|
||||||
|
news.push({
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
id: url,
|
||||||
|
pubDate: Date.now(),
|
||||||
|
extra: {
|
||||||
|
info: currentPlayers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return news
|
||||||
|
})
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { myFetch } from "#/utils/fetch"
|
||||||
|
import { defineSource } from "#/utils/source"
|
||||||
|
|
||||||
|
interface WapRes {
|
||||||
|
ret: number
|
||||||
|
msg: string
|
||||||
|
data: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
lead: string
|
||||||
|
cover?: string
|
||||||
|
shareTitle: string
|
||||||
|
shareAbstract: string
|
||||||
|
sharePic: string
|
||||||
|
is724: boolean
|
||||||
|
is724Paper: boolean
|
||||||
|
head_cmsid: string
|
||||||
|
feed_style: number
|
||||||
|
head_article: {
|
||||||
|
live_info: string
|
||||||
|
title: string
|
||||||
|
img: string
|
||||||
|
pub_time: string
|
||||||
|
media_name: string
|
||||||
|
}
|
||||||
|
paperInfo: any
|
||||||
|
tabs: {
|
||||||
|
id: string
|
||||||
|
channel_id: string
|
||||||
|
name: string
|
||||||
|
source: string
|
||||||
|
type: string
|
||||||
|
articleList: any[]
|
||||||
|
article_count: number
|
||||||
|
sub_tab: string
|
||||||
|
}[]
|
||||||
|
banner: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 综合早报
|
||||||
|
*/
|
||||||
|
const comprehensiveNews = defineSource(async () => {
|
||||||
|
const url = "https://i.news.qq.com/web_backend/v2/getTagInfo?tagId=aEWqxLtdgmQ%3D"
|
||||||
|
const res = await myFetch<WapRes>(url, {
|
||||||
|
headers: {
|
||||||
|
Referer: "https://news.qq.com/",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return res.data.tabs[0].articleList.map(news => ({
|
||||||
|
id: news.id,
|
||||||
|
title: news.title,
|
||||||
|
url: news.link_info.url,
|
||||||
|
extra: {
|
||||||
|
hover: news.desc,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineSource({
|
||||||
|
"tencent-hot": comprehensiveNews,
|
||||||
|
})
|
||||||
@@ -52,7 +52,7 @@ const news = defineSource(async () => {
|
|||||||
|
|
||||||
const res: NewsRes = await myFetch(apiUrl)
|
const res: NewsRes = await myFetch(apiUrl)
|
||||||
return res.data.items
|
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 }) => {
|
.map(({ resource: h }) => {
|
||||||
return {
|
return {
|
||||||
id: h.id,
|
id: h.id,
|
||||||
|
|||||||
+47
-43
@@ -1,48 +1,52 @@
|
|||||||
interface Res {
|
import * as cheerio from "cheerio"
|
||||||
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 () => {
|
export default defineSource(async () => {
|
||||||
const url = "https://weibo.com/ajax/side/hotSearch"
|
const baseurl = "https://s.weibo.com"
|
||||||
const res: Res = await myFetch(url)
|
const url = `${baseurl}/top/summary?cate=realtimehot`
|
||||||
return res.data.realtime
|
|
||||||
.filter(k => !k.is_ad)
|
const html = await myFetch(url, {
|
||||||
.map((k) => {
|
headers: {
|
||||||
const keyword = k.word_scheme ? k.word_scheme : `#${k.word}#`
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
||||||
return {
|
// https://github.com/v5tech/weibo-trending-hot-search
|
||||||
id: k.word,
|
"Cookie": "SUB=_2AkMWIuNSf8NxqwJRmP8dy2rhaoV2ygrEieKgfhKJJRMxHRl-yT9jqk86tRB6PaLNvQZR6zYUcYVT1zSjoSreQHidcUq7",
|
||||||
title: k.word,
|
"referer": url,
|
||||||
extra: {
|
},
|
||||||
icon: k.icon && {
|
})
|
||||||
url: proxyPicture(k.icon),
|
|
||||||
scale: 1.5,
|
const $ = cheerio.load(html)
|
||||||
|
|
||||||
|
const rows = $("#pl_top_realtimehot table tbody tr").slice(1)
|
||||||
|
|
||||||
|
const hotNews: NewsItem[] = []
|
||||||
|
|
||||||
|
rows.each((_, row) => {
|
||||||
|
const $row = $(row)
|
||||||
|
const $link = $row.find("td.td-02 a").filter((_, el) => {
|
||||||
|
const href = $(el).attr("href")
|
||||||
|
return !!(href && !href.includes("javascript:void(0);"))
|
||||||
|
}).first()
|
||||||
|
|
||||||
|
if ($link.length) {
|
||||||
|
const title = $link.text().trim()
|
||||||
|
const href = $link.attr("href")
|
||||||
|
|
||||||
|
if (title && href) {
|
||||||
|
const $flag = $row.find("td.td-03").text().trim()
|
||||||
|
const flagUrl = {
|
||||||
|
新: "https://simg.s.weibo.com/moter/flags/1_0.png",
|
||||||
|
热: "https://simg.s.weibo.com/moter/flags/2_0.png",
|
||||||
|
}[$flag]
|
||||||
|
hotNews.push({
|
||||||
|
id: title,
|
||||||
|
title,
|
||||||
|
url: `${baseurl}${href}`,
|
||||||
|
mobileUrl: `${baseurl}${href}`,
|
||||||
|
extra: {
|
||||||
|
icon: flagUrl ? { url: proxyPicture(flagUrl), scale: 1.5 } : undefined,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
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`,
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
return hotNews
|
||||||
})
|
})
|
||||||
|
|||||||
+34
-19
@@ -1,38 +1,53 @@
|
|||||||
interface Res {
|
interface Res {
|
||||||
data: {
|
data: {
|
||||||
card_label?: {
|
type: "hot_list_feed"
|
||||||
icon: string
|
style_type: "1"
|
||||||
night_icon: string
|
feed_specific: {
|
||||||
|
answer_count: 411
|
||||||
}
|
}
|
||||||
target: {
|
target: {
|
||||||
id: number
|
title_area: {
|
||||||
title: string
|
text: string
|
||||||
url: string
|
}
|
||||||
created: number
|
excerpt_area: {
|
||||||
answer_count: number
|
text: string
|
||||||
follower_count: number
|
}
|
||||||
bound_topic_ids: number[]
|
image_area: {
|
||||||
comment_count: number
|
url: string
|
||||||
is_following: boolean
|
}
|
||||||
excerpt: 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({
|
export default defineSource({
|
||||||
zhihu: async () => {
|
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)
|
const res: Res = await myFetch(url)
|
||||||
return res.data
|
return res.data
|
||||||
.map((k) => {
|
.map((k) => {
|
||||||
const urlId = k.target.url?.match(/(\d+)$/)?.[1]
|
|
||||||
return {
|
return {
|
||||||
id: k.target.id,
|
id: k.target.link.url.match(/(\d+)$/)?.[1] ?? k.target.link.url,
|
||||||
title: k.target.title,
|
title: k.target.title_area.text,
|
||||||
extra: {
|
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,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
+13
-9
@@ -4,9 +4,10 @@
|
|||||||
"weibo": "weibo-shishiresou",
|
"weibo": "weibo-shishiresou",
|
||||||
"zaobao": "lianhezaobao",
|
"zaobao": "lianhezaobao",
|
||||||
"coolapk": "kuan-jinrizuire",
|
"coolapk": "kuan-jinrizuire",
|
||||||
"wallstreetcn-quick": "huaerjiejianwen-shishikuaixun",
|
"mktnews-flash": "MKTNews-kuaixun",
|
||||||
"wallstreetcn-news": "huaerjiejianwen-zuixinzixun",
|
"wallstreetcn-quick": "huaerjiejianwen-kuaixun",
|
||||||
"wallstreetcn-hot": "huaerjiejianwen-zuirewenzhang",
|
"wallstreetcn-news": "huaerjiejianwen-zuixin",
|
||||||
|
"wallstreetcn-hot": "huaerjiejianwen-zuire",
|
||||||
"36kr-quick": "36ke-kuaixun",
|
"36kr-quick": "36ke-kuaixun",
|
||||||
"douyin": "douyin",
|
"douyin": "douyin",
|
||||||
"hupu": "hupu-zhugandaoretie",
|
"hupu": "hupu-zhugandaoretie",
|
||||||
@@ -16,8 +17,7 @@
|
|||||||
"thepaper": "pengpaixinwen-rebang",
|
"thepaper": "pengpaixinwen-rebang",
|
||||||
"sputniknewscn": "weixingtongxunshe",
|
"sputniknewscn": "weixingtongxunshe",
|
||||||
"cankaoxiaoxi": "cankaoxiaoxi",
|
"cankaoxiaoxi": "cankaoxiaoxi",
|
||||||
"pcbeta-windows11": "yuanjingluntan-Windows 11",
|
"pcbeta-windows11": "yuanjingluntan-Win11",
|
||||||
"pcbeta-windows": "yuanjingluntan-Windows ziyuan",
|
|
||||||
"cls-telegraph": "cailianshe-dianbao",
|
"cls-telegraph": "cailianshe-dianbao",
|
||||||
"cls-depth": "cailianshe-shendu",
|
"cls-depth": "cailianshe-shendu",
|
||||||
"cls-hot": "cailianshe-remen",
|
"cls-hot": "cailianshe-remen",
|
||||||
@@ -36,14 +36,18 @@
|
|||||||
"kaopu": "kaopuxinwen",
|
"kaopu": "kaopuxinwen",
|
||||||
"jin10": "jinshishuju",
|
"jin10": "jinshishuju",
|
||||||
"baidu": "baiduresou",
|
"baidu": "baiduresou",
|
||||||
"linuxdo-latest": "LINUX DO-zuixin",
|
"linuxdo-latest": "LINUX DO-zuixin",
|
||||||
"linuxdo-hot": "LINUX DO-jinrizuire",
|
"linuxdo-hot": "LINUX DO-jinrizuire",
|
||||||
"ghxi": "guoheboke",
|
"idcflare-latest": "IDC Flare-zuixin",
|
||||||
"smzdm": "shenmezhidemai",
|
"idcflare-hot": "IDC Flare-jinrizuire",
|
||||||
"nowcoder": "niuke",
|
"nowcoder": "niuke",
|
||||||
"sspai": "shaoshupai",
|
"sspai": "shaoshupai",
|
||||||
"juejin": "xitujuejin",
|
"juejin": "xitujuejin",
|
||||||
"ifeng": "fenghuangwang-redianzixun",
|
"ifeng": "fenghuangwang-redianzixun",
|
||||||
"chongbuluo-latest": "chongbuluo-zuixin",
|
"chongbuluo-latest": "chongbuluo-zuixin",
|
||||||
"chongbuluo-hot": "chongbuluo-zuire"
|
"chongbuluo-hot": "chongbuluo-zuire",
|
||||||
|
"douban": "douban-remendianying",
|
||||||
|
"steam": "Steam-zaixianrenshu",
|
||||||
|
"tencent-hot": "tengxunxinwen-zonghezaobao"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+200
-116
@@ -13,81 +13,6 @@ const Time = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const originSources = {
|
export const originSources = {
|
||||||
"bilibili": {
|
|
||||||
name: "哔哩哔哩",
|
|
||||||
color: "blue",
|
|
||||||
home: "https://www.bilibili.com",
|
|
||||||
sub: {
|
|
||||||
"hot-search": {
|
|
||||||
title: "热搜",
|
|
||||||
column: "china",
|
|
||||||
type: "hottest",
|
|
||||||
},
|
|
||||||
"ranking": {
|
|
||||||
title: "排行榜",
|
|
||||||
column: "china",
|
|
||||||
disable: "cf",
|
|
||||||
type: "hottest",
|
|
||||||
interval: Time.Common,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"linuxdo": {
|
|
||||||
name: "LINUX DO",
|
|
||||||
column: "tech",
|
|
||||||
color: "slate",
|
|
||||||
home: "https://linux.do/",
|
|
||||||
disable: "cf",
|
|
||||||
sub: {
|
|
||||||
latest: {
|
|
||||||
title: "最新",
|
|
||||||
home: "https://linux.do/latest",
|
|
||||||
},
|
|
||||||
hot: {
|
|
||||||
title: "今日最热",
|
|
||||||
type: "hottest",
|
|
||||||
interval: Time.Common,
|
|
||||||
home: "https://linux.do/hot",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"github": {
|
|
||||||
name: "Github",
|
|
||||||
color: "gray",
|
|
||||||
home: "https://github.com/",
|
|
||||||
column: "tech",
|
|
||||||
sub: {
|
|
||||||
"trending-today": {
|
|
||||||
title: "Today",
|
|
||||||
type: "hottest",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"douyin": {
|
|
||||||
name: "抖音",
|
|
||||||
type: "hottest",
|
|
||||||
column: "china",
|
|
||||||
color: "gray",
|
|
||||||
home: "https://www.douyin.com",
|
|
||||||
},
|
|
||||||
"kuaishou": {
|
|
||||||
name: "快手",
|
|
||||||
type: "hottest",
|
|
||||||
column: "china",
|
|
||||||
color: "orange",
|
|
||||||
// cloudflare pages cannot access
|
|
||||||
disable: "cf",
|
|
||||||
home: "https://www.kuaishou.com",
|
|
||||||
},
|
|
||||||
"weibo": {
|
|
||||||
name: "微博",
|
|
||||||
title: "实时热搜",
|
|
||||||
type: "hottest",
|
|
||||||
column: "china",
|
|
||||||
color: "red",
|
|
||||||
interval: Time.Realtime,
|
|
||||||
home: "https://weibo.com",
|
|
||||||
},
|
|
||||||
"v2ex": {
|
"v2ex": {
|
||||||
name: "V2EX",
|
name: "V2EX",
|
||||||
color: "slate",
|
color: "slate",
|
||||||
@@ -106,27 +31,14 @@ export const originSources = {
|
|||||||
color: "blue",
|
color: "blue",
|
||||||
home: "https://www.zhihu.com",
|
home: "https://www.zhihu.com",
|
||||||
},
|
},
|
||||||
"juejin": {
|
"weibo": {
|
||||||
name: "稀土掘金",
|
name: "微博",
|
||||||
column: "tech",
|
title: "实时热搜",
|
||||||
color: "blue",
|
|
||||||
type: "hottest",
|
type: "hottest",
|
||||||
home: "https://juejin.cn",
|
|
||||||
},
|
|
||||||
"baidu": {
|
|
||||||
name: "百度热搜",
|
|
||||||
column: "china",
|
column: "china",
|
||||||
color: "blue",
|
|
||||||
type: "hottest",
|
|
||||||
home: "https://www.baidu.com",
|
|
||||||
},
|
|
||||||
"hupu": {
|
|
||||||
name: "虎扑",
|
|
||||||
home: "https://hupu.com",
|
|
||||||
column: "china",
|
|
||||||
title: "主干道热帖",
|
|
||||||
type: "hottest",
|
|
||||||
color: "red",
|
color: "red",
|
||||||
|
interval: Time.Realtime,
|
||||||
|
home: "https://weibo.com",
|
||||||
},
|
},
|
||||||
"zaobao": {
|
"zaobao": {
|
||||||
name: "联合早报",
|
name: "联合早报",
|
||||||
@@ -145,6 +57,18 @@ export const originSources = {
|
|||||||
title: "今日最热",
|
title: "今日最热",
|
||||||
home: "https://coolapk.com",
|
home: "https://coolapk.com",
|
||||||
},
|
},
|
||||||
|
"mktnews": {
|
||||||
|
name: "MKTNews",
|
||||||
|
column: "finance",
|
||||||
|
home: "https://mktnews.net",
|
||||||
|
color: "indigo",
|
||||||
|
interval: Time.Realtime,
|
||||||
|
sub: {
|
||||||
|
flash: {
|
||||||
|
title: "快讯",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"wallstreetcn": {
|
"wallstreetcn": {
|
||||||
name: "华尔街见闻",
|
name: "华尔街见闻",
|
||||||
color: "blue",
|
color: "blue",
|
||||||
@@ -154,14 +78,14 @@ export const originSources = {
|
|||||||
quick: {
|
quick: {
|
||||||
type: "realtime",
|
type: "realtime",
|
||||||
interval: Time.Fast,
|
interval: Time.Fast,
|
||||||
title: "实时快讯",
|
title: "快讯",
|
||||||
},
|
},
|
||||||
news: {
|
news: {
|
||||||
title: "最新资讯",
|
title: "最新",
|
||||||
interval: Time.Common,
|
interval: Time.Common,
|
||||||
},
|
},
|
||||||
hot: {
|
hot: {
|
||||||
title: "最热文章",
|
title: "最热",
|
||||||
type: "hottest",
|
type: "hottest",
|
||||||
interval: Time.Common,
|
interval: Time.Common,
|
||||||
},
|
},
|
||||||
@@ -181,6 +105,21 @@ export const originSources = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"douyin": {
|
||||||
|
name: "抖音",
|
||||||
|
type: "hottest",
|
||||||
|
column: "china",
|
||||||
|
color: "gray",
|
||||||
|
home: "https://www.douyin.com",
|
||||||
|
},
|
||||||
|
"hupu": {
|
||||||
|
name: "虎扑",
|
||||||
|
home: "https://hupu.com",
|
||||||
|
column: "china",
|
||||||
|
title: "主干道热帖",
|
||||||
|
type: "hottest",
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
"tieba": {
|
"tieba": {
|
||||||
name: "百度贴吧",
|
name: "百度贴吧",
|
||||||
title: "热议",
|
title: "热议",
|
||||||
@@ -232,7 +171,7 @@ export const originSources = {
|
|||||||
home: "https://bbs.pcbeta.com",
|
home: "https://bbs.pcbeta.com",
|
||||||
sub: {
|
sub: {
|
||||||
windows11: {
|
windows11: {
|
||||||
title: "Windows 11",
|
title: "Win11",
|
||||||
type: "realtime",
|
type: "realtime",
|
||||||
interval: Time.Fast,
|
interval: Time.Fast,
|
||||||
},
|
},
|
||||||
@@ -240,6 +179,7 @@ export const originSources = {
|
|||||||
title: "Windows 资源",
|
title: "Windows 资源",
|
||||||
type: "realtime",
|
type: "realtime",
|
||||||
interval: Time.Fast,
|
interval: Time.Fast,
|
||||||
|
disable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -323,6 +263,52 @@ export const originSources = {
|
|||||||
type: "hottest",
|
type: "hottest",
|
||||||
home: "https://www.producthunt.com/",
|
home: "https://www.producthunt.com/",
|
||||||
},
|
},
|
||||||
|
"github": {
|
||||||
|
name: "Github",
|
||||||
|
color: "gray",
|
||||||
|
home: "https://github.com/",
|
||||||
|
column: "tech",
|
||||||
|
sub: {
|
||||||
|
"trending-today": {
|
||||||
|
title: "Today",
|
||||||
|
type: "hottest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bilibili": {
|
||||||
|
name: "哔哩哔哩",
|
||||||
|
color: "blue",
|
||||||
|
home: "https://www.bilibili.com",
|
||||||
|
sub: {
|
||||||
|
"hot-search": {
|
||||||
|
title: "热搜",
|
||||||
|
column: "china",
|
||||||
|
type: "hottest",
|
||||||
|
},
|
||||||
|
"hot-video": {
|
||||||
|
title: "热门视频",
|
||||||
|
disable: "cf",
|
||||||
|
column: "china",
|
||||||
|
type: "hottest",
|
||||||
|
},
|
||||||
|
"ranking": {
|
||||||
|
title: "排行榜",
|
||||||
|
column: "china",
|
||||||
|
disable: "cf",
|
||||||
|
type: "hottest",
|
||||||
|
interval: Time.Common,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"kuaishou": {
|
||||||
|
name: "快手",
|
||||||
|
type: "hottest",
|
||||||
|
column: "china",
|
||||||
|
color: "orange",
|
||||||
|
// cloudflare pages cannot access
|
||||||
|
disable: "cf",
|
||||||
|
home: "https://www.kuaishou.com",
|
||||||
|
},
|
||||||
"kaopu": {
|
"kaopu": {
|
||||||
name: "靠谱新闻",
|
name: "靠谱新闻",
|
||||||
column: "world",
|
column: "world",
|
||||||
@@ -338,11 +324,63 @@ export const originSources = {
|
|||||||
type: "realtime",
|
type: "realtime",
|
||||||
home: "https://www.jin10.com",
|
home: "https://www.jin10.com",
|
||||||
},
|
},
|
||||||
|
"baidu": {
|
||||||
|
name: "百度热搜",
|
||||||
|
column: "china",
|
||||||
|
color: "blue",
|
||||||
|
type: "hottest",
|
||||||
|
home: "https://www.baidu.com",
|
||||||
|
},
|
||||||
|
"linuxdo": {
|
||||||
|
name: "LINUX DO",
|
||||||
|
column: "tech",
|
||||||
|
color: "slate",
|
||||||
|
home: "https://linux.do/",
|
||||||
|
sub: {
|
||||||
|
latest: {
|
||||||
|
title: "最新",
|
||||||
|
home: "https://linux.do/latest",
|
||||||
|
},
|
||||||
|
hot: {
|
||||||
|
title: "今日最热",
|
||||||
|
type: "hottest",
|
||||||
|
interval: Time.Common,
|
||||||
|
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": {
|
"ghxi": {
|
||||||
name: "果核剥壳",
|
name: "果核剥壳",
|
||||||
column: "china",
|
column: "china",
|
||||||
color: "yellow",
|
color: "yellow",
|
||||||
home: "https://www.ghxi.com/",
|
home: "https://www.ghxi.com/",
|
||||||
|
disable: true,
|
||||||
|
},
|
||||||
|
"smzdm": {
|
||||||
|
name: "什么值得买",
|
||||||
|
column: "china",
|
||||||
|
color: "red",
|
||||||
|
type: "hottest",
|
||||||
|
home: "https://www.smzdm.com",
|
||||||
|
disable: true,
|
||||||
},
|
},
|
||||||
"nowcoder": {
|
"nowcoder": {
|
||||||
name: "牛客",
|
name: "牛客",
|
||||||
@@ -358,6 +396,13 @@ export const originSources = {
|
|||||||
type: "hottest",
|
type: "hottest",
|
||||||
home: "https://sspai.com",
|
home: "https://sspai.com",
|
||||||
},
|
},
|
||||||
|
"juejin": {
|
||||||
|
name: "稀土掘金",
|
||||||
|
column: "tech",
|
||||||
|
color: "blue",
|
||||||
|
type: "hottest",
|
||||||
|
home: "https://juejin.cn",
|
||||||
|
},
|
||||||
"ifeng": {
|
"ifeng": {
|
||||||
name: "凤凰网",
|
name: "凤凰网",
|
||||||
column: "china",
|
column: "china",
|
||||||
@@ -385,6 +430,36 @@ export const originSources = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"douban": {
|
||||||
|
name: "豆瓣",
|
||||||
|
column: "china",
|
||||||
|
title: "热门电影",
|
||||||
|
color: "green",
|
||||||
|
type: "hottest",
|
||||||
|
home: "https://www.douban.com",
|
||||||
|
},
|
||||||
|
"steam": {
|
||||||
|
name: "Steam",
|
||||||
|
column: "world",
|
||||||
|
title: "在线人数",
|
||||||
|
color: "blue",
|
||||||
|
type: "hottest",
|
||||||
|
home: "https://store.steampowered.com",
|
||||||
|
},
|
||||||
|
"tencent": {
|
||||||
|
name: "腾讯新闻",
|
||||||
|
column: "china",
|
||||||
|
color: "blue",
|
||||||
|
home: "https://news.qq.com",
|
||||||
|
sub: {
|
||||||
|
hot: {
|
||||||
|
title: "综合早报",
|
||||||
|
type: "hottest",
|
||||||
|
interval: Time.Common,
|
||||||
|
home: "https://news.qq.com/tag/aEWqxLtdgmQ=",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const satisfies Record<string, OriginSource>
|
} as const satisfies Record<string, OriginSource>
|
||||||
|
|
||||||
export function genSources() {
|
export function genSources() {
|
||||||
@@ -404,29 +479,38 @@ export function genSources() {
|
|||||||
if (source.sub && Object.keys(source.sub).length) {
|
if (source.sub && Object.keys(source.sub).length) {
|
||||||
Object.entries(source.sub).forEach(([subId, subSource], i) => {
|
Object.entries(source.sub).forEach(([subId, subSource], i) => {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
_.push([id, {
|
_.push([
|
||||||
redirect: `${id}-${subId}`,
|
id,
|
||||||
...parent,
|
{
|
||||||
...subSource,
|
redirect: `${id}-${subId}`,
|
||||||
}] as [any, Source])
|
...parent,
|
||||||
|
...subSource,
|
||||||
|
},
|
||||||
|
] as [any, Source])
|
||||||
}
|
}
|
||||||
_.push([`${id}-${subId}`, { ...parent, ...subSource }] as [any, Source])
|
_.push([`${id}-${subId}`, { ...parent, ...subSource }] as [
|
||||||
|
any,
|
||||||
|
Source,
|
||||||
|
])
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
_.push([id, {
|
_.push([
|
||||||
title: source.title,
|
id,
|
||||||
...parent,
|
{
|
||||||
}])
|
title: source.title,
|
||||||
|
...parent,
|
||||||
|
},
|
||||||
|
])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return typeSafeObjectFromEntries(_.filter(([_, v]) => {
|
return typeSafeObjectFromEntries(
|
||||||
if (v.disable === "cf" && process.env.CF_PAGES) {
|
_.filter(([_, v]) => {
|
||||||
return false
|
if (v.disable === "cf" && process.env.CF_PAGES) {
|
||||||
} else if (v.disable === true) {
|
return false
|
||||||
return false
|
} else {
|
||||||
} else {
|
return v.disable !== true
|
||||||
return true
|
}
|
||||||
}
|
}),
|
||||||
}))
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-44
@@ -51,6 +51,23 @@
|
|||||||
"color": "green",
|
"color": "green",
|
||||||
"interval": 600000
|
"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": {
|
"wallstreetcn": {
|
||||||
"redirect": "wallstreetcn-quick",
|
"redirect": "wallstreetcn-quick",
|
||||||
"name": "华尔街见闻",
|
"name": "华尔街见闻",
|
||||||
@@ -59,7 +76,7 @@
|
|||||||
"home": "https://wallstreetcn.com/",
|
"home": "https://wallstreetcn.com/",
|
||||||
"color": "blue",
|
"color": "blue",
|
||||||
"interval": 300000,
|
"interval": 300000,
|
||||||
"title": "实时快讯"
|
"title": "快讯"
|
||||||
},
|
},
|
||||||
"wallstreetcn-quick": {
|
"wallstreetcn-quick": {
|
||||||
"name": "华尔街见闻",
|
"name": "华尔街见闻",
|
||||||
@@ -68,7 +85,7 @@
|
|||||||
"home": "https://wallstreetcn.com/",
|
"home": "https://wallstreetcn.com/",
|
||||||
"color": "blue",
|
"color": "blue",
|
||||||
"interval": 300000,
|
"interval": 300000,
|
||||||
"title": "实时快讯"
|
"title": "快讯"
|
||||||
},
|
},
|
||||||
"wallstreetcn-news": {
|
"wallstreetcn-news": {
|
||||||
"name": "华尔街见闻",
|
"name": "华尔街见闻",
|
||||||
@@ -76,7 +93,7 @@
|
|||||||
"home": "https://wallstreetcn.com/",
|
"home": "https://wallstreetcn.com/",
|
||||||
"color": "blue",
|
"color": "blue",
|
||||||
"interval": 1800000,
|
"interval": 1800000,
|
||||||
"title": "最新资讯"
|
"title": "最新"
|
||||||
},
|
},
|
||||||
"wallstreetcn-hot": {
|
"wallstreetcn-hot": {
|
||||||
"name": "华尔街见闻",
|
"name": "华尔街见闻",
|
||||||
@@ -85,7 +102,7 @@
|
|||||||
"home": "https://wallstreetcn.com/",
|
"home": "https://wallstreetcn.com/",
|
||||||
"color": "blue",
|
"color": "blue",
|
||||||
"interval": 1800000,
|
"interval": 1800000,
|
||||||
"title": "最热文章"
|
"title": "最热"
|
||||||
},
|
},
|
||||||
"36kr": {
|
"36kr": {
|
||||||
"redirect": "36kr-quick",
|
"redirect": "36kr-quick",
|
||||||
@@ -181,7 +198,7 @@
|
|||||||
"home": "https://bbs.pcbeta.com",
|
"home": "https://bbs.pcbeta.com",
|
||||||
"color": "blue",
|
"color": "blue",
|
||||||
"interval": 300000,
|
"interval": 300000,
|
||||||
"title": "Windows 11"
|
"title": "Win11"
|
||||||
},
|
},
|
||||||
"pcbeta-windows11": {
|
"pcbeta-windows11": {
|
||||||
"name": "远景论坛",
|
"name": "远景论坛",
|
||||||
@@ -190,16 +207,7 @@
|
|||||||
"home": "https://bbs.pcbeta.com",
|
"home": "https://bbs.pcbeta.com",
|
||||||
"color": "blue",
|
"color": "blue",
|
||||||
"interval": 300000,
|
"interval": 300000,
|
||||||
"title": "Windows 11"
|
"title": "Win11"
|
||||||
},
|
|
||||||
"pcbeta-windows": {
|
|
||||||
"name": "远景论坛",
|
|
||||||
"type": "realtime",
|
|
||||||
"column": "tech",
|
|
||||||
"home": "https://bbs.pcbeta.com",
|
|
||||||
"color": "blue",
|
|
||||||
"interval": 300000,
|
|
||||||
"title": "Windows 资源"
|
|
||||||
},
|
},
|
||||||
"cls": {
|
"cls": {
|
||||||
"redirect": "cls-telegraph",
|
"redirect": "cls-telegraph",
|
||||||
@@ -353,6 +361,16 @@
|
|||||||
"interval": 600000,
|
"interval": 600000,
|
||||||
"title": "热搜"
|
"title": "热搜"
|
||||||
},
|
},
|
||||||
|
"bilibili-hot-video": {
|
||||||
|
"name": "哔哩哔哩",
|
||||||
|
"type": "hottest",
|
||||||
|
"disable": "cf",
|
||||||
|
"column": "china",
|
||||||
|
"home": "https://www.bilibili.com",
|
||||||
|
"color": "blue",
|
||||||
|
"interval": 600000,
|
||||||
|
"title": "热门视频"
|
||||||
|
},
|
||||||
"bilibili-ranking": {
|
"bilibili-ranking": {
|
||||||
"name": "哔哩哔哩",
|
"name": "哔哩哔哩",
|
||||||
"type": "hottest",
|
"type": "hottest",
|
||||||
@@ -396,41 +414,23 @@
|
|||||||
"color": "blue",
|
"color": "blue",
|
||||||
"interval": 600000
|
"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": {
|
"linuxdo-hot": {
|
||||||
"name": "LINUX DO",
|
"name": "LINUX DO",
|
||||||
"type": "hottest",
|
"type": "hottest",
|
||||||
"disable": "cf",
|
|
||||||
"column": "tech",
|
"column": "tech",
|
||||||
"home": "https://linux.do/hot",
|
"home": "https://linux.do",
|
||||||
"color": "slate",
|
"color": "gray",
|
||||||
"interval": 1800000,
|
"interval": 600000,
|
||||||
"title": "今日最热"
|
"title": "今日最热"
|
||||||
},
|
},
|
||||||
"ghxi": {
|
"idcflare-hot": {
|
||||||
"name": "果核剥壳",
|
"name": "IDC Flare",
|
||||||
"column": "china",
|
"type": "hottest",
|
||||||
"home": "https://www.ghxi.com/",
|
"column": "tech",
|
||||||
"color": "yellow",
|
"home": "https://idcflare.com",
|
||||||
"interval": 600000
|
"color": "gray",
|
||||||
|
"interval": 600000,
|
||||||
|
"title": "今日最热"
|
||||||
},
|
},
|
||||||
"nowcoder": {
|
"nowcoder": {
|
||||||
"name": "牛客",
|
"name": "牛客",
|
||||||
@@ -490,5 +490,42 @@
|
|||||||
"color": "green",
|
"color": "green",
|
||||||
"interval": 1800000,
|
"interval": 1800000,
|
||||||
"title": "最热"
|
"title": "最热"
|
||||||
|
},
|
||||||
|
"douban": {
|
||||||
|
"title": "热门电影",
|
||||||
|
"name": "豆瓣",
|
||||||
|
"type": "hottest",
|
||||||
|
"column": "china",
|
||||||
|
"home": "https://www.douban.com",
|
||||||
|
"color": "green",
|
||||||
|
"interval": 600000
|
||||||
|
},
|
||||||
|
"steam": {
|
||||||
|
"title": "在线人数",
|
||||||
|
"name": "Steam",
|
||||||
|
"type": "hottest",
|
||||||
|
"column": "world",
|
||||||
|
"home": "https://store.steampowered.com",
|
||||||
|
"color": "blue",
|
||||||
|
"interval": 600000
|
||||||
|
},
|
||||||
|
"tencent": {
|
||||||
|
"redirect": "tencent-hot",
|
||||||
|
"name": "腾讯新闻",
|
||||||
|
"type": "hottest",
|
||||||
|
"column": "china",
|
||||||
|
"home": "https://news.qq.com/tag/aEWqxLtdgmQ=",
|
||||||
|
"color": "blue",
|
||||||
|
"interval": 1800000,
|
||||||
|
"title": "综合早报"
|
||||||
|
},
|
||||||
|
"tencent-hot": {
|
||||||
|
"name": "腾讯新闻",
|
||||||
|
"type": "hottest",
|
||||||
|
"column": "china",
|
||||||
|
"home": "https://news.qq.com/tag/aEWqxLtdgmQ=",
|
||||||
|
"color": "blue",
|
||||||
|
"interval": 1800000,
|
||||||
|
"title": "综合早报"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import { useThrottleFn } from "ahooks"
|
|||||||
import { useAutoAnimate } from "@formkit/auto-animate/react"
|
import { useAutoAnimate } from "@formkit/auto-animate/react"
|
||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
import { useWindowSize } from "react-use"
|
import { useWindowSize } from "react-use"
|
||||||
|
import { isMobile } from "react-device-detect"
|
||||||
import { DndContext } from "../common/dnd"
|
import { DndContext } from "../common/dnd"
|
||||||
import { useSortable } from "../common/dnd/useSortable"
|
import { useSortable } from "../common/dnd/useSortable"
|
||||||
|
import { OverlayScrollbar } from "../common/overlay-scrollbar"
|
||||||
import type { ItemsProps } from "./card"
|
import type { ItemsProps } from "./card"
|
||||||
import { CardWrapper } from "./card"
|
import { CardWrapper } from "./card"
|
||||||
import { currentSourcesAtom } from "~/atoms"
|
import { currentSourcesAtom } from "~/atoms"
|
||||||
@@ -26,58 +28,76 @@ export function Dnd() {
|
|||||||
return Math.min(width - 32, WIDTH)
|
return Math.min(width - 32, WIDTH)
|
||||||
}, [width])
|
}, [width])
|
||||||
|
|
||||||
|
if (!items.length) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndWrapper items={items} setItems={setItems}>
|
<DndWrapper items={items} setItems={setItems} isSingleColumn={isMobile}>
|
||||||
<motion.ol
|
<OverlayScrollbar defer className="overflow-x-auto">
|
||||||
className="grid w-full gap-6"
|
<motion.ol
|
||||||
ref={parent}
|
className={isMobile
|
||||||
style={{
|
? "flex px-2 gap-6 pb-4 scroll-smooth"
|
||||||
gridTemplateColumns: `repeat(auto-fill, minmax(${minWidth}px, 1fr))`,
|
: "grid w-full gap-6"}
|
||||||
}}
|
ref={parent}
|
||||||
initial="hidden"
|
style={isMobile
|
||||||
animate="visible"
|
? {
|
||||||
variants={{
|
// 横向滚动布局
|
||||||
hidden: {
|
}
|
||||||
opacity: 0,
|
: {
|
||||||
},
|
gridTemplateColumns: `repeat(auto-fill, minmax(${minWidth}px, 1fr))`,
|
||||||
visible: {
|
}}
|
||||||
opacity: 1,
|
initial="hidden"
|
||||||
transition: {
|
animate="visible"
|
||||||
delayChildren: 0.1,
|
variants={{
|
||||||
staggerChildren: 0.1,
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
},
|
},
|
||||||
},
|
visible: {
|
||||||
}}
|
opacity: 1,
|
||||||
>
|
transition: {
|
||||||
{items.map(id => (
|
delayChildren: 0.1,
|
||||||
<motion.li
|
staggerChildren: 0.1,
|
||||||
key={id}
|
|
||||||
transition={{
|
|
||||||
type: "tween",
|
|
||||||
duration: AnimationDuration / 1000,
|
|
||||||
}}
|
|
||||||
variants={{
|
|
||||||
hidden: {
|
|
||||||
y: 20,
|
|
||||||
opacity: 0,
|
|
||||||
},
|
},
|
||||||
visible: {
|
},
|
||||||
y: 0,
|
}}
|
||||||
opacity: 1,
|
>
|
||||||
},
|
{items.map((id, index) => (
|
||||||
}}
|
<motion.li
|
||||||
>
|
key={id}
|
||||||
<SortableCardWrapper id={id} />
|
className={$(isMobile && "flex-shrink-0", isMobile && index === items.length - 1 && "mr-2")}
|
||||||
</motion.li>
|
style={isMobile ? { width: `${width - 16 > WIDTH ? WIDTH : width - 16}px` } : undefined}
|
||||||
))}
|
transition={{
|
||||||
</motion.ol>
|
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>
|
</DndWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DndWrapper({ items, setItems, children }: PropsWithChildren<{
|
function DndWrapper({ items, setItems, isSingleColumn, children }: PropsWithChildren<{
|
||||||
items: SourceID[]
|
items: SourceID[]
|
||||||
setItems: (items: SourceID[]) => void
|
setItems: (items: SourceID[]) => void
|
||||||
|
isSingleColumn: boolean
|
||||||
}>) {
|
}>) {
|
||||||
const onDropTargetChange = useCallback(({ location, source }: BaseEventPayload<ElementDragType>) => {
|
const onDropTargetChange = useCallback(({ location, source }: BaseEventPayload<ElementDragType>) => {
|
||||||
const traget = location.current.dropTargets[0]
|
const traget = location.current.dropTargets[0]
|
||||||
@@ -91,10 +111,10 @@ function DndWrapper({ items, setItems, children }: PropsWithChildren<{
|
|||||||
startIndex: fromIndex,
|
startIndex: fromIndex,
|
||||||
indexOfTarget: toIndex,
|
indexOfTarget: toIndex,
|
||||||
closestEdgeOfTarget,
|
closestEdgeOfTarget,
|
||||||
axis: "vertical",
|
axis: isSingleColumn ? "horizontal" : "vertical",
|
||||||
})
|
})
|
||||||
setItems(update)
|
setItems(update)
|
||||||
}, [items, setItems])
|
}, [items, setItems, isSingleColumn])
|
||||||
// 避免动画干扰
|
// 避免动画干扰
|
||||||
const { run } = useThrottleFn(onDropTargetChange, {
|
const { run } = useThrottleFn(onDropTargetChange, {
|
||||||
leading: true,
|
leading: true,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[cmdk-list] {
|
[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] {
|
[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() {
|
function Refresh() {
|
||||||
const currentSources = useAtomValue(currentSourcesAtom)
|
const currentSources = useAtomValue(currentSourcesAtom)
|
||||||
const { refresh } = useRefetch()
|
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">
|
<span className="justify-self-end flex gap-2 items-center text-xl text-primary-600 dark:text-primary">
|
||||||
<GoTop />
|
<GoTop />
|
||||||
<Refresh />
|
<Refresh />
|
||||||
|
<Github />
|
||||||
<Menu />
|
<Menu />
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
|
|
||||||
function ThemeToggle() {
|
// function ThemeToggle() {
|
||||||
const { isDark, toggleDark } = useDark()
|
// const { isDark, toggleDark } = useDark()
|
||||||
return (
|
// return (
|
||||||
<li onClick={toggleDark} className="cursor-pointer [&_*]:cursor-pointer transition-all">
|
// <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 className={$("inline-block", isDark ? "i-ph-moon-stars-duotone" : "i-ph-sun-dim-duotone")} />
|
||||||
<span>
|
// <span>
|
||||||
{isDark ? "浅色模式" : "深色模式"}
|
// {isDark ? "浅色模式" : "深色模式"}
|
||||||
</span>
|
// </span>
|
||||||
</li>
|
// </li>
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function Menu() {
|
export function Menu() {
|
||||||
const { loggedIn, login, logout, userInfo, enableLogin } = useLogin()
|
const { loggedIn, login, logout, userInfo, enableLogin } = useLogin()
|
||||||
@@ -64,7 +64,7 @@ export function Menu() {
|
|||||||
<span>Github 账号登录</span>
|
<span>Github 账号登录</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
<ThemeToggle />
|
{/* <ThemeToggle /> */}
|
||||||
<li onClick={() => window.open(Homepage)} className="cursor-pointer [&_*]:cursor-pointer transition-all">
|
<li onClick={() => window.open(Homepage)} className="cursor-pointer [&_*]:cursor-pointer transition-all">
|
||||||
<span className="i-ph:github-logo-duotone inline-block" />
|
<span className="i-ph:github-logo-duotone inline-block" />
|
||||||
<span>Star on Github </span>
|
<span>Star on Github </span>
|
||||||
@@ -75,7 +75,7 @@ export function Menu() {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="GitHub stars badge"
|
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>
|
||||||
<a
|
<a
|
||||||
@@ -83,7 +83,7 @@ export function Menu() {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="GitHub forks badge"
|
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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Outlet, createRootRouteWithContext } from "@tanstack/react-router"
|
|||||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools"
|
import { TanStackRouterDevtools } from "@tanstack/router-devtools"
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
|
||||||
import type { QueryClient } from "@tanstack/react-query"
|
import type { QueryClient } from "@tanstack/react-query"
|
||||||
|
import { isMobile } from "react-device-detect"
|
||||||
import { Header } from "~/components/header"
|
import { Header } from "~/components/header"
|
||||||
import { GlobalOverlayScrollbar } from "~/components/common/overlay-scrollbar"
|
import { GlobalOverlayScrollbar } from "~/components/common/overlay-scrollbar"
|
||||||
import { Footer } from "~/components/footer"
|
import { Footer } from "~/components/footer"
|
||||||
@@ -30,11 +31,13 @@ function RootComponent() {
|
|||||||
usePWA()
|
usePWA()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GlobalOverlayScrollbar className={$([
|
<GlobalOverlayScrollbar
|
||||||
"h-full overflow-x-auto px-4",
|
className={$([
|
||||||
"md:(px-10)",
|
!isMobile && "px-4",
|
||||||
"lg:(px-24)",
|
"h-full overflow-x-auto",
|
||||||
])}
|
"md:(px-10)",
|
||||||
|
"lg:(px-24)",
|
||||||
|
])}
|
||||||
>
|
>
|
||||||
<header
|
<header
|
||||||
className={$([
|
className={$([
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"allowJs": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user