├── .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 | ![Tabox](https://img.fzf404.art/tabox/v1.0.0.webp) 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 | <span style={{ position: 'absolute' }}> {config.json.Config.title}</span> 214 | 215 | 216 | {/* 侧边导航栏菜单 */} 217 | { 222 | // 侧边栏点击滚动 223 | document.getElementById(event.key).scrollIntoView({ block: 'center', behavior: 'smooth' }) 224 | }}> 225 | {Object.keys(config.json.Tabox).map((menuKey) => { 226 | // 菜单项 227 | const menuItem = config.json.Tabox[menuKey] 228 | return ( 229 | 230 | 231 | 232 | {navCollapsed ? null : menuKey} 233 | 234 | 235 | ) 236 | })} 237 | 238 | 239 | {/* 内容区 */} 240 | 244 | {/* 顶部导航 */} 245 |
246 | {/* 时间 */} 247 | 254 | {time} 255 | 256 | {/* 图标 */} 257 | 265 | <Space size="middle"> 266 | {/* Github */} 267 | <a href="https://github.com/fzf404/Tabox" target="_blank" rel="noreferrer"> 268 | <GithubFilled style={{ color: '#fff' }} /> 269 | </a> 270 | {/* 设置 */} 271 | <SettingFilled onClick={() => setSettingCollapsed(true)} /> 272 | </Space> 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 | { 385 | setSearch({ 386 | ...search, 387 | checkedMenu: event.key, 388 | checkedKeys: Object.keys(config.json.Search[event.key])[0], 389 | }) 390 | }}> 391 | {Object.keys(config.json.Search).map((searchMenuKey) => { 392 | return {searchMenuKey} 393 | })} 394 | 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 |
442 | {/* 标签组标题 */} 443 | 450 | 454 | {/* 标签组内容 */} 455 | 456 | {Object.keys(menuBox).map((boxKey) => { 457 | // 说明内容不渲染 458 | if ( 459 | boxKey === 'url' || 460 | boxKey === 'logo' || 461 | boxKey === 'description' || 462 | boxKey === 'Ignore' 463 | ) { 464 | return null 465 | } 466 | // 标签内容 467 | const tabItem = config.json.Tabox[tabKey][boxKey] 468 | // Github 渲染 469 | if (tabKey === 'Github' && boxKey === 'name') { 470 | // 获取忽略的仓库 471 | const ignoreItems = config.json.Tabox.Github.Ignore ? config.json.Tabox.Github.Ignore : [] 472 | 473 | // 判断 Github 仓库是否加载成功 474 | return github.loading ? ( 475 | 476 | ) : ( 477 | github.data.map((githubItem) => { 478 | if (ignoreItems.some((name) => name === githubItem.name)) { 479 | return null 480 | } 481 | return ( 482 | 483 | {/* 仓库信息 */} 484 | 485 | 486 | {githubItem.name}} 490 | // 仓库 star 数 491 | avatar={ 492 | 493 | {githubItem.stargazers_count} 494 | 495 | } 496 | // 仓库描述 497 | description={ 498 | githubItem.description 499 | ? githubItem.description.length > 26 500 | ? githubItem.description.substring(0, 24) + '..' 501 | : githubItem.description 502 | : null 503 | } 504 | /> 505 | 506 | 507 | 508 | ) 509 | }) 510 | ) 511 | } 512 | // 备忘录渲染 513 | if (tabKey === 'Memo' && boxKey === 'content') { 514 | return ( 515 |
522 |                                 {tabItem}
523 |                               
524 | ) 525 | } 526 | // 默认渲染 527 | return tabItem ? ( 528 | 529 | {/* 标签内容 */} 530 | 531 | 532 | } 537 | // 标签描述 538 | description={tabItem[1]} 539 | /> 540 | 541 | 542 | 543 | ) : null 544 | })} 545 |
546 |
547 |
548 |
549 | ) 550 | })} 551 |
552 | {/* 回到顶部 */} 553 | 554 |
555 | 561 |
562 |
563 | ) 564 | } 565 | --------------------------------------------------------------------------------