├── public ├── assets │ └── 网站图片.png └── images │ └── favicon.ico ├── postcss.config.js ├── vercel.json ├── next.config.js ├── contexts ├── FontContext.js └── ThemeContext.js ├── components ├── Titles.js ├── HeaderBar.js ├── SearchBox.js ├── ThemeToggleButton.js ├── Tags.js ├── WebList.js ├── Footer.js └── FontMenu.js ├── tailwind.config.js ├── package.json ├── pages ├── _app.js ├── index.js ├── _document.js ├── api │ └── visit-count.js └── mainPage.js ├── utils └── cookies.js ├── styles └── globals.css ├── lib ├── notion.js └── dataLoader.js ├── .gitignore ├── .github └── workflows │ └── release.yml ├── README.md ├── LICENSE └── yarn.lock /public/assets/网站图片.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nowscott/IndWebIndex/HEAD/public/assets/网站图片.png -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nowscott/IndWebIndex/HEAD/public/images/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "next.config.js", 6 | "use": "@vercel/next" 7 | } 8 | ], 9 | "routes": [ 10 | { "src": "/(.*)", "dest": "/$1" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NOTION_TOKEN: process.env.NOTION_TOKEN, 4 | DATABASE_ID: process.env.DATABASE_ID, 5 | }, 6 | webpack: (config) => { 7 | config.resolve.fallback = { 8 | ...config.resolve.fallback, 9 | fs: false, 10 | }; 11 | return config; 12 | }, 13 | }; -------------------------------------------------------------------------------- /contexts/FontContext.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useContext } from 'react'; 2 | 3 | const FontContext = createContext(); 4 | 5 | export const FontProvider = ({ children }) => { 6 | const [selectedFont, setSelectedFont] = useState('font-serif'); 7 | 8 | return ( 9 | 10 | {children} 11 | 12 | ); 13 | }; 14 | 15 | export const useFont = () => useContext(FontContext); -------------------------------------------------------------------------------- /components/Titles.js: -------------------------------------------------------------------------------- 1 | //components/Titles.js 2 | const Titles = ({ title = "Individual Web Index", link = "https://github.com/NowScott/IndWebIndex" }) => ( 3 |
4 |

5 | 11 | {title} 12 | 13 |

14 |
15 | ); 16 | 17 | export default Titles; -------------------------------------------------------------------------------- /components/HeaderBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ThemeToggleButton from './ThemeToggleButton'; 3 | 4 | const HeaderBar = ({ lastFetched }) => { 5 | return ( 6 |
7 | 8 | 数据更新时间:{lastFetched ? new Date(lastFetched).toLocaleString() : '加载中...'} 9 | 10 |
11 | 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default HeaderBar; -------------------------------------------------------------------------------- /components/SearchBox.js: -------------------------------------------------------------------------------- 1 | // components/SearchBox.js 2 | const SearchBox = ({ searchQuery, setSearchQuery }) => ( 3 |
4 | setSearchQuery(e.target.value)} 13 | /> 14 |
15 | ); 16 | 17 | export default SearchBox; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx}', 5 | './components/**/*.{js,ts,jsx,tsx}', 6 | ], 7 | darkMode: 'class', 8 | theme: { 9 | extend: { 10 | fontFamily: { 11 | 'smiley': ['Smiley Sans Oblique', 'sans-serif'], 12 | 'wenkai': ['LXGW WenKai', 'sans-serif'], 13 | 'oldsong': ['KingHwa_OldSong', 'sans-serif'], 14 | 'pixel': ['MuzaiPixel', 'sans-serif'], 15 | 'marker': ['LXGW Marker Gothic', 'sans-serif'], 16 | 'default': ['sans-serif', 'Arial'] 17 | }, 18 | }, 19 | }, 20 | plugins: [], 21 | }; -------------------------------------------------------------------------------- /components/ThemeToggleButton.js: -------------------------------------------------------------------------------- 1 | //components/ThemeToggleButton.js 2 | import React from 'react'; 3 | import { useTheme } from '../contexts/ThemeContext'; 4 | import { HiOutlineMoon, HiOutlineSun } from 'react-icons/hi'; 5 | 6 | const ThemeToggleButton = () => { 7 | const { isDark, toggleTheme } = useTheme(); 8 | 9 | return ( 10 | 17 | ); 18 | }; 19 | 20 | export default ThemeToggleButton; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ind-web-index", 3 | "version": "2.3.0", 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start" 8 | }, 9 | "dependencies": { 10 | "@notionhq/client": "^0.4.11", 11 | "lodash": "^4.17.21", 12 | "mongodb": "^6.8.0", 13 | "next": "^15.1.6", 14 | "react": "^18.3.1", 15 | "react-dom": "^18.3.1", 16 | "react-icons": "^5.2.1", 17 | "tiny-pinyin": "^1.3.2" 18 | }, 19 | "devDependencies": { 20 | "autoprefixer": "^10.4.19", 21 | "postcss": "^8.4.39", 22 | "tailwindcss": "^3.4.6" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | // pages/_app.js 2 | import '../styles/globals.css'; 3 | import { FontProvider } from '../contexts/FontContext'; 4 | import { ThemeProvider } from '../contexts/ThemeContext'; 5 | import Head from 'next/head'; 6 | 7 | function MyApp({ Component, pageProps }) { 8 | return ( 9 | 10 | 11 | 12 | 13 | IndWebIndex - 网页索引 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | export default MyApp; -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | // pages/index.js 2 | import { getDatabase } from '../lib/notion'; 3 | import { randomSort,unique } from '../lib/dataLoader' 4 | import MainPage from './mainPage'; 5 | 6 | export default function Home({ initialPosts, lastFetched }) { 7 | return ; 8 | } 9 | 10 | export async function getStaticProps() { 11 | const databaseId = process.env.DATABASE_ID; 12 | const posts = await getDatabase(databaseId); 13 | const lastFetched = new Date().toISOString(); 14 | const sortedPosts = randomSort(unique(posts)); 15 | return { 16 | props: { 17 | initialPosts: sortedPosts || [], 18 | lastFetched 19 | }, 20 | revalidate: 1800, 21 | }; 22 | } -------------------------------------------------------------------------------- /utils/cookies.js: -------------------------------------------------------------------------------- 1 | // utils/cookies.js 2 | export const setCookie = (name, value, days) => { 3 | let expires = ''; 4 | if (days) { 5 | const date = new Date(); 6 | date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); 7 | expires = '; expires=' + date.toUTCString(); 8 | } 9 | document.cookie = name + '=' + (value || '') + expires + '; path=/'; 10 | }; 11 | 12 | export const getCookie = (name) => { 13 | const nameEQ = name + '='; 14 | const ca = document.cookie.split(';'); 15 | for (let i = 0; i < ca.length; i++) { 16 | let c = ca[i]; 17 | while (c.charAt(0) == ' ') c = c.substring(1, c.length); 18 | if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); 19 | } 20 | return null; 21 | }; -------------------------------------------------------------------------------- /components/Tags.js: -------------------------------------------------------------------------------- 1 | // components/Tags.js 2 | const Tags = ({ tags, onList, handleToggleTagButton }) => ( 3 |
4 |
5 | 选择标签 6 |
7 |
8 | {tags.map(tag => ( 9 | 20 | ))} 21 |
22 |
23 | ); 24 | 25 | export default Tags; -------------------------------------------------------------------------------- /components/WebList.js: -------------------------------------------------------------------------------- 1 | // components/WebList.js 2 | const WebList = ({ filteredPosts }) => ( 3 |
4 |
5 | 筛选网页 6 |
7 |
8 | {filteredPosts.length > 0 ? ( 9 | filteredPosts.map(post => ( 10 | 19 | {post.name} 20 | 21 | )) 22 | ) : ( 23 |

未找到符合条件的网页

24 | )} 25 |
26 |
27 | ); 28 | 29 | export default WebList; -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | /* styles/globals.css */ 2 | @import url('https://chinese-fonts-cdn.deno.dev/chinesefonts3/packages/dyh/dist/SmileySans-Oblique/result.css'); 3 | @import url('https://chinese-fonts-cdn.deno.dev/chinesefonts3/packages/lxgwwenkai/dist/LXGWWenKai-Regular/result.css'); 4 | @import url('https://chinese-fonts-cdn.deno.dev/chinesefonts3/packages/jhlst/dist/京華老宋体v1_007/result.css'); 5 | @import url('https://chinese-fonts-cdn.deno.dev/chinesefonts3/packages/mzxst/dist/MZPXorig/result.css'); 6 | @import url('https://chinese-fonts-cdn.deno.dev/chinesefonts3/packages/lxgwmanhei/dist/LXGWMarkerGothic/result.css'); 7 | 8 | /* Tailwind CSS 指令 */ 9 | @tailwind base; 10 | @tailwind components; 11 | @tailwind utilities; 12 | 13 | body { 14 | user-select: none; 15 | /* 所有现代浏览器 */ 16 | -webkit-user-select: none; 17 | /* Safari */ 18 | -moz-user-select: none; 19 | /* Firefox */ 20 | -ms-user-select: none; 21 | /* IE10+ */ 22 | } 23 | 24 | /* 需要黑白时专用 */ 25 | /* body { filter: grayscale(100%); } */ -------------------------------------------------------------------------------- /components/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | const Footer = () => { 4 | const [visitCount, setVisitCount] = useState(null); 5 | 6 | useEffect(() => { 7 | const fetchVisitCount = async () => { 8 | try { 9 | const response = await fetch('/api/visit-count'); 10 | const data = await response.json(); 11 | setVisitCount(data.count); 12 | } catch (error) { 13 | console.error('获取访问量失败:', error); 14 | } 15 | }; 16 | 17 | fetchVisitCount(); 18 | }, []); 19 | 20 | return ( 21 |
22 | 28 | 投稿网页 29 | 30 |

31 | {visitCount !== null ? `访问量:${visitCount}` : '访问量:加载中...'} 32 |

33 |

Copyright © 2021 - NowScott

34 |
35 | ); 36 | }; 37 | 38 | export default Footer; -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | // pages/_document.js 2 | import Document, { Html, Head, Main, NextScript } from 'next/document'; 3 | 4 | class MyDocument extends Document { 5 | render() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 |