├── .github
└── workflows
│ └── gh-pages.yml
├── .gitignore
├── .npmrc
├── README.md
├── package.json
├── pnpm-lock.yaml
├── public
├── CNAME
├── config.yaml
├── favicon.ico
└── index.html
└── src
├── index.css
├── index.js
└── pages
└── index.jsx
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: gh-pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v3
14 |
15 | - name: Pnpm
16 | uses: pnpm/action-setup@v2
17 | with:
18 | version: 7
19 |
20 | - name: Node
21 | uses: actions/setup-node@v3
22 | with:
23 | cache: pnpm
24 | node-version: 16
25 |
26 | - name: Setup
27 | run: |
28 | pnpm install
29 |
30 | - name: Build
31 | run: |
32 | pnpm build
33 |
34 | - name: Deploy
35 | uses: peaceiris/actions-gh-pages@v3
36 | with:
37 | github_token: ${{ secrets.GITHUB_TOKEN }}
38 | publish_dir: build
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # os
2 | .DS_Store
3 |
4 | # depend
5 | node_modules
6 |
7 | # build
8 | build
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers=true
2 |
3 | registry=https://registry.npmmirror.com
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📦 Tabox
2 |
3 | > 通过编写 YAML 配置文件, 快速建立属于自己的标签页
4 | >
5 | > 使用 React + Antd 开发
6 | >
7 | > [预览地址](https://tab.fzf404.art/)
8 |
9 | 
10 |
11 | ## 🚄 特性
12 |
13 | - 全功能配置
14 | - 支持多平台搜索
15 | - 网站图标自动加载
16 | - 用户 Github 仓库展示
17 |
18 | ## 🚀 快速开始
19 |
20 | ### 🖥 本机使用
21 |
22 | 1. 访问 [https://tab.fzf404.art/](https://tab.fzf404.art/)
23 | 2. 点击右上角的设置图标, 切换到编辑模式
24 | 3. 编辑 YAML 配置文件, 实时预览效果,编辑完成后点击右上角的保存
25 | 4. 可将配置文件上传至 `Github Gist`, 从 CDN 加载配置文件
26 | 5. 使用 `New Tab Override` 插件, 将 `https://tab.fzf404.art/` 设置为默认标签页
27 |
28 | > 注: 切换编辑 / 链接模式后,需要点击设置菜单右上角的保存按钮
29 |
30 | ### 🎁 Github Pages
31 |
32 | > 也可使用 Gitee Pages
33 |
34 | 1. Clone 本项目
35 | 2. **切换到 `gh-pages` 分支**
36 | 3. 按照自己的喜好, 编辑 `config.yaml` 文件
37 | 4. 将代码推送至 github,启用 Github Pages
38 | 5. 访问 Github Pages
39 |
40 | ### 💾 服务器
41 |
42 | > 推荐使用 debian / ubuntu
43 |
44 | ```bash
45 | # 1. 安装 nginx / git
46 | apt install git nginx -y
47 |
48 | # 2. 切换到 nginx 目录
49 | cd /var/www/html/
50 |
51 | # 3. clone 项目
52 | git clone https://github.com/fzf404/Tabox.git -b gh-pages --depth 1 .
53 |
54 | # 4. 访问网站
55 | http://{server_ip}/
56 | ```
57 |
58 | ### 💡 配置文件托管
59 |
60 | > 待完成...
61 |
62 | ## 🔥 提交配置文件
63 |
64 | > 欢迎提交自己的配置文件
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tabox",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "react-scripts start",
7 | "build": "react-scripts build"
8 | },
9 | "dependencies": {
10 | "@ant-design/icons": "^4.8.0",
11 | "@monaco-editor/react": "^4.4.6",
12 | "antd": "^4.24.6",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-scripts": "^5.0.1",
16 | "yaml": "^2.2.0"
17 | },
18 | "browserslist": {
19 | "production": [
20 | ">0.2%",
21 | "not dead",
22 | "not op_mini all"
23 | ],
24 | "development": [
25 | "last 1 chrome version",
26 | "last 1 firefox version",
27 | "last 1 safari version"
28 | ]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/CNAME:
--------------------------------------------------------------------------------
1 | tab.fzf404.art
--------------------------------------------------------------------------------
/public/config.yaml:
--------------------------------------------------------------------------------
1 | # 网站配置
2 | Config:
3 | # 侧栏状态
4 | hide: true
5 | # 网页标题
6 | title: Tabox
7 | # 网站图标
8 | logo: favicon.ico
9 | # 图标跳转
10 | link: https://tab.fzf404.art/
11 |
12 | # 搜索列表
13 | Search:
14 | # 搜索组
15 | Normal:
16 | Google: https://www.google.com/search?q=
17 | Baidu: https://www.baidu.com/s?wd=
18 | DuckGo: https://duckduckgo.com/?q=
19 | Translate: https://translate.google.com/?text=
20 |
21 | Code:
22 | Github: https://github.com/search?q=
23 | NPM: https://www.npmjs.com/search?q=
24 | Docker: https://hub.docker.com/search?q=
25 |
26 | Community:
27 | Bilibili: https://search.bilibili.com/all?keyword=
28 | Juejin: https://juejin.im/search?query=
29 | Zhihu: https://www.zhihu.com/search?q=
30 |
31 | Tools:
32 | Choco: https://chocolatey.org/packages?q=
33 | Greasyfork: https://greasyfork.org/zh-CN/scripts?q=
34 |
35 | # 标签列表
36 | Tabox:
37 | # 标签组
38 | Common:
39 | logo: https://cdn.worldvectorlogo.com/logos/onenote-2.svg
40 | description: 常用网站
41 |
42 | BiliBili:
43 | - https://www.bilibili.com/
44 | - 年轻人的平台
45 | # - https://www.bilibili.com/favicon.ico
46 | Github:
47 | - https://github.com/
48 | - 全球开源社区
49 | # - https://github.com/favicon.ico
50 | Zhihu:
51 | - https://www.zhihu.com/
52 | - 知乎问答社区
53 | Juejin:
54 | - https://juejin.cn/
55 | - 掘金技术社区
56 | Amap:
57 | - https://ditu.amap.com/
58 | - 高德地图
59 | TongJi:
60 | - https://tongji.baidu.com/
61 | - 百度统计
62 | Weibo:
63 | - https://weibo.com/
64 | - 微博社交平台
65 |
66 | # 标签组
67 | Website:
68 | logo: https://cdn.worldvectorlogo.com/logos/powerpoint-2.svg
69 | description: 我的网站
70 |
71 | Home:
72 | - https://www.fzf404.art/
73 | - 我的首页
74 | Monit:
75 | - https://monit.fzf404.art/
76 | - 桌面小组件
77 | Sedom:
78 | - https://sedom.fzf404.art/
79 | - 极简样式库
80 | Pixxel:
81 | - https://px.fzf404.art/
82 | - 像素画工具
83 | Blog:
84 | - https://blog.fzf404.art/
85 | - 写作博客
86 |
87 | Github: # github 配置
88 | logo: https://cdn.worldvectorlogo.com/logos/excel-4.svg
89 | description: 我的代码仓库
90 |
91 | name: fzf404 # 用户名
92 |
93 | # 仓库忽略
94 | Ignore:
95 | - nest-template
96 | - tduck-front
97 |
98 | Tools:
99 | logo: https://cdn.worldvectorlogo.com/logos/outlook-1.svg
100 | description: 实用网站
101 |
102 | Tencent:
103 | - https://console.cloud.tencent.com/
104 | - 腾讯云
105 | CDN:
106 | - https://dash.cloudflare.com/
107 | - 网站加速
108 | Colab:
109 | - https://colab.research.google.com
110 | - 免费云计算
111 | EDA:
112 | - https://lceda.cn/editor
113 | - 立创 EDA
114 | MSDN:
115 | - https://msdn.itellyou.cn
116 | - 系统镜像
117 | Gitee:
118 | - https://gitee.com
119 | - 国内开源社区
120 | Coding:
121 | - https://coding.net
122 | - 代码托管
123 | - https://help-assets.codehub.cn/enterprise/guanwang/favicon.ico
124 | NPM:
125 | - https://www.npmjs.com
126 | - Node 仓库
127 | - https://static.npmjs.com/b0f1a8318363185cc2ea6a40ac23eeb2.png
128 | Docker:
129 | - https://hub.docker.com
130 | - Docker 仓库
131 |
132 | Docs:
133 | logo: https://cdn.worldvectorlogo.com/logos/word-1.svg
134 | description: 开发工具
135 |
136 | Runoob:
137 | - https://www.runoob.com/
138 | - 菜鸟教程
139 | Electron:
140 | - https://www.electronjs.org/zh/
141 | - Electron 文档
142 | - https://www.electronjs.org/assets/img/logo.svg
143 | MDN:
144 | - https://developer.mozilla.org/
145 | - MDN 文档
146 | Vue:
147 | - https://cn.vuejs.org/
148 | - Vue 文档
149 | - https://cn.vuejs.org/logo.svg
150 | Yuque:
151 | - https://www.yuque.com
152 | - 文档平台
153 |
154 | Memo: # 备忘录
155 | logo: https://cdn.worldvectorlogo.com/logos/microsoft-teams-1.svg
156 | description: 备忘录
157 |
158 | content: |
159 | 似乎没有什么东西需要备忘!
160 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fzf404/Tabox/15d525a164939fcef8bb15dd34df40345720f313/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Tabox
7 |
8 |
9 |
10 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.css';
2 |
3 | * {
4 | transition: margin 0.2s ease;
5 | }
6 |
7 | body {
8 | min-width: 720px;
9 | }
10 |
11 | .ant-card-meta-title {
12 | margin-bottom: 0 !important;
13 | }
14 |
15 | .ant-card-meta-avatar {
16 | padding-top: 0.4rem;
17 | padding-right: 0.6rem;
18 | }
19 |
20 | .github .ant-card-meta-avatar {
21 | padding: 0 8px 0 0;
22 | }
23 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom/client'
2 |
3 | import Index from './pages/index'
4 |
5 | import './index.css'
6 |
7 | ReactDOM.createRoot(document.getElementById('root')).render()
8 |
--------------------------------------------------------------------------------
/src/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | import YAML from 'yaml'
4 | import Editor from '@monaco-editor/react'
5 |
6 | // 组件
7 | import {
8 | Alert,
9 | Avatar,
10 | BackTop,
11 | Button,
12 | Card,
13 | Col,
14 | Drawer,
15 | Empty,
16 | Input,
17 | Layout,
18 | Menu,
19 | message,
20 | PageHeader,
21 | Radio,
22 | Result,
23 | Row,
24 | Space,
25 | Spin,
26 | Switch,
27 | Typography,
28 | } from 'antd'
29 |
30 | // 图标
31 | import { GithubFilled, SettingFilled } from '@ant-design/icons'
32 |
33 | // 组件解构
34 | const { Header, Content, Footer, Sider } = Layout
35 | const { Paragraph, Title } = Typography
36 | const { Search } = Input
37 | const { Meta } = Card
38 |
39 | // 通知设置
40 | message.config({ maxCount: 3 })
41 |
42 | export default function App() {
43 | // 配置信息
44 | const [config, setConfig] = useState(() => {
45 | // 默认配置
46 | const config = {
47 | url: 'config.yaml', // 配置路径
48 | yaml: '', // yaml 格式配置文件
49 | json: {}, // json 格式配置文件
50 | edit: false, // 编辑模式
51 | error: false, // 配置异常
52 | loading: true, // 正在加载
53 | }
54 |
55 | // 编辑模式
56 | if (localStorage.getItem('tabox-edit') === 'true') {
57 | const storeConfig = localStorage.getItem('tabox-config')
58 | config.yaml = storeConfig
59 | config.json = YAML.parse(storeConfig)
60 | config.edit = true
61 | }
62 |
63 | // 链接模式
64 | if (localStorage.hasOwnProperty('tabox-url')) {
65 | config.url = localStorage.getItem('tabox-url')
66 | }
67 |
68 | return config
69 | })
70 |
71 | // 侧栏折叠
72 | const [navCollapsed, setNavCollapsed] = useState(false)
73 |
74 | // 设置折叠
75 | const [settingCollapsed, setSettingCollapsed] = useState(false)
76 |
77 | // 搜索配置
78 | const [search, setSearch] = useState({
79 | checkedMenu: '', // 当前选中的菜单
80 | checkedKeys: '', // 当前选中的配置项
81 | })
82 |
83 | // github 配置
84 | const [github, setGithub] = useState({
85 | data: [], // 个人仓库数据
86 | loading: true, // 是否加载中
87 | })
88 |
89 | // 时间数据
90 | const [time, setTime] = useState(new Date().toLocaleTimeString())
91 |
92 | // 更新时间
93 | setInterval(() => {
94 | setTime(new Date().toLocaleTimeString())
95 | }, 1000)
96 |
97 | // 加载配置
98 | useEffect(() => {
99 | if (!config.edit) {
100 | fetchConfig()
101 | }
102 | }, [])
103 |
104 | // 更新配置
105 | useEffect(() => {
106 | if (config.json.hasOwnProperty('Config')) {
107 | // 默认折叠状态
108 | setNavCollapsed(config.json.Config.hide)
109 | // 默认搜索配置
110 | const defaulCheckedMenu = Object.keys(config.json.Search)[0]
111 | const defaultCheckedKey = Object.keys(config.json.Search[defaulCheckedMenu])[0]
112 | setSearch({
113 | checkedMenu: defaulCheckedMenu,
114 | checkedKeys: defaultCheckedKey,
115 | })
116 | // 加载成功
117 | setConfig({ ...config, loading: false })
118 | // github 配置
119 | if (config.json.Tabox.hasOwnProperty('Github')) {
120 | // 加载 github 仓库信息
121 | fetch(`https://api.github.com/users/${config.json.Tabox.Github.name}/repos?per_page=100`)
122 | .then((res) => res.json())
123 | .then((data) => {
124 | if (data.length > 0) {
125 | // 按照 star 数排序
126 | data.sort((a, b) => {
127 | return b.stargazers_count - a.stargazers_count
128 | })
129 | // 设置 Github 信息
130 | setGithub({ loading: false, data: data })
131 | }
132 | })
133 | }
134 | }
135 | }, [config.json])
136 |
137 | // 请求配置文件
138 | const fetchConfig = () => {
139 | // 请求配置文件
140 | fetch(config.url)
141 | .then((res) => res.text())
142 | .then((text) => {
143 | try {
144 | // 验证合法性
145 | const parse = YAML.parse(text)
146 | // 配置文件格式错误
147 | if (!parse.hasOwnProperty('Config')) {
148 | return setConfig({ ...config, error: true })
149 | }
150 | } catch {
151 | // 解析错误
152 | return setConfig({ ...config, error: true })
153 | }
154 | // 设置配置文件
155 | setConfig({ ...config, error: false, yaml: text, json: YAML.parse(text) })
156 | })
157 | // 请求失败
158 | .catch(() => {
159 | return setConfig({ ...config, error: true })
160 | })
161 | }
162 |
163 | // 网站 logo 预解析
164 | const getICO = (logo, url) => {
165 | return logo ? logo : new URL(url).origin + '/favicon.ico'
166 | }
167 |
168 | // 重置设置
169 | const reset = () => {
170 | localStorage.clear()
171 | location.reload()
172 | }
173 |
174 | return config.loading ? (
175 | // 配置文件格式错误
176 | config.error ? (
177 |
182 |
185 |
188 |
189 | }
190 | />
191 | ) : (
192 | // 加载中
193 |
194 | )
195 | ) : (
196 |
200 | {/* 侧边导航栏 */}
201 | setNavCollapsed(!navCollapsed)}
206 | style={{ position: 'fixed', height: '100vh', zIndex: 10 }}>
207 | {/* 网站标题 */}
208 |
209 |
210 |
211 |
212 |
213 | {config.json.Config.title}
214 |
215 |
216 | {/* 侧边导航栏菜单 */}
217 |
238 |
239 | {/* 内容区 */}
240 |
244 | {/* 顶部导航 */}
245 |
246 | {/* 时间 */}
247 |
254 | {time}
255 |
256 | {/* 图标 */}
257 |
265 |
266 | {/* Github */}
267 |
268 |
269 |
270 | {/* 设置 */}
271 | setSettingCollapsed(true)} />
272 |
273 |
274 | {/* 设置菜单 */}
275 | {
281 | setSettingCollapsed(false)
282 | }}
283 | extra={
284 |
285 | {/* 保存设置 */}
286 |
306 | {/* 重置设置 */}
307 |
310 |
311 | }>
312 | {/* 设置内容 */}
313 |
314 | {/* 模式配置 */}
315 |
316 | 链接模式
317 | {
320 | setConfig({ ...config, error: false, edit: checked })
321 | }}
322 | />
323 | 编辑模式
324 |
325 | {/* 错误提醒 */}
326 | {config.error ? : null}
327 | {/* 链接配置 */}
328 | {config.edit ? null : (
329 |
330 | 配置链接
331 | {
335 | setConfig({ ...config, url: event.target.value })
336 | }}
337 | />
338 |
341 |
342 | )}
343 | {/* 配置文件编辑器 */}
344 | {config.edit ? (
345 | {
350 | try {
351 | // 验证合法性
352 | YAML.parse(text)
353 | } catch {
354 | // 配置文件格式错误
355 | return setConfig({ ...config, error: true })
356 | }
357 | // 写入配置信息
358 | setConfig({ ...config, error: false, yaml: text, json: YAML.parse(text) })
359 | }}
360 | options={{
361 | minimap: {
362 | enabled: false,
363 | },
364 | }}
365 | />
366 | ) : null}
367 |
368 |
369 |
370 |
371 | {/* 搜索栏 */}
372 |
379 | {/* 搜索菜单 */}
380 |
395 | {/* 搜索栏 */}
396 | {
402 | // 打开网页
403 | open(config.json.Search[search.checkedMenu][search.checkedKeys] + key)
404 | }}
405 | />
406 | {/* 搜索范围 */}
407 | {
411 | setSearch({ ...search, checkedKeys: event.target.value })
412 | }}>
413 |
414 | {Object.keys(config.json.Search[search.checkedMenu]).map((searchItemKey) => {
415 | return (
416 |
417 | {searchItemKey}
418 |
419 | )
420 | })}
421 |
422 |
423 |
424 | {/* 标签页内容 */}
425 |
426 | {/* 遍历标签组 */}
427 | {Object.keys(config.json.Tabox).map((tabKey) => {
428 | const menuBox = config.json.Tabox[tabKey]
429 | return (
430 |
549 | )
550 | })}
551 |
552 | {/* 回到顶部 */}
553 |
554 |
555 |
561 |
562 |
563 | )
564 | }
565 |
--------------------------------------------------------------------------------