diff --git a/README.md b/README.md index 8155d0b..6e9c9a3 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,24 @@ END_DATE="2025-01-19 23:59" CURRENT_TERM="2024-2025年第一学期" ``` -### 1.4. ISR +### 1.4 导出笔记 + +使用下面的命令可以将当前数据的笔记导出笔记到 `archive` 目录下,导出前请确保以上环境变量已经正确设置: + +```bash +npm run archive +``` + +### 1.5. ~~ISR~~(已弃用) ISR,也就是 Next.js 的按需编译,查看笔记的页面正常情况下都是静态页面,只有在有新的提交笔记后才会重新编译,这样可以显著提高访问流畅度。 ~~但是测试发现目前 13.3.2 版本本地使用存在问题,可是部署在 Vercel 之后又可以正常使用。这应该是 Bug,后面将持续关注随时更新 Next.js 的版本。另外目前 ISR 还没有嵌入到 app 目录当中,仍然在使用 pages 中的 api。~~(更新:13.4.0 之后的稳定版本推出后,现在使用 **revalidatePath** 就可以很简单地在服务端重新编译想要的静态页面,不需要另开 API 了。) +> 注: +> +> 自 2024年10月31日起,本项目已弃用 Next.js 的 ISR功能 + ## 二、Docker部署 本项目已经提前编译好了 Docker 镜像,可以直接使用,部署的话修改一些环境变量即可,比如值班时间和当前学期名称,数据库URI不用改: diff --git a/docs/help.md b/docs/help.md index b58762f..4124cb5 100644 --- a/docs/help.md +++ b/docs/help.md @@ -67,12 +67,6 @@ Markdown 是一种轻量级**标记语言**,使用一些简单的标记符号 ![编辑器](/assets/images/editor.png) -如果你是在手机上编辑,打开会看到两个按钮,除了**退出按钮**之外,还有一个**预览按钮**,点击即可预览效果。 - -| 编辑器 | 预览 | -| :---------------------------------------------: | :--------------------------------------------: | -| ![手机编辑器](/assets/images/editor-mobile.png) | ![手机预览](/assets/images/preview-mobile.png) | - 如果你有电脑的话,建议在电脑上编辑,书写起来会更加方便。 > **小提示** 📝 diff --git a/package-lock.json b/package-lock.json index e05a658..65b67f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@codemirror/lang-markdown": "^6.3.0", "@codemirror/language-data": "^6.5.1", "@codemirror/view": "^6.34.1", - "@types/node": "^22.8.1", + "@types/node": "^22.8.5", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@uiw/codemirror-theme-vscode": "^4.23.6", @@ -23,6 +23,7 @@ "antd": "^5.21.6", "autoprefixer": "^10.4.20", "clsx": "^2.1.1", + "dotenv": "^16.4.5", "framer-motion": "^11.11.10", "postcss": "^8.4.47", "prettier": "^3.3.3", @@ -36,6 +37,7 @@ "rehype-prism-plus": "^2.0.0", "remark-gfm": "^4.0.0", "tailwindcss": "^3.4.14", + "tsx": "^4.19.2", "typescript": "^5.6.3", "zod": "^3.23.8" } @@ -609,6 +611,414 @@ "dev": true, "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1277,9 +1687,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.8.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", - "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", + "version": "22.8.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.5.tgz", + "integrity": "sha512-5iYk6AMPtsMbkZqCO1UGF9W5L38twq11S2pYWkybGHH2ogPUvXWNlQqJBzuEZWKj/WRH+QTeiv6ySWqJtvIEgA==", "dev": true, "license": "MIT", "dependencies": { @@ -2006,6 +2416,19 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2037,6 +2460,46 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2203,6 +2666,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -5080,6 +5556,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -5526,6 +6012,26 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tsx": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", + "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tween-functions": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz", diff --git a/package.json b/package.json index d5de420..1bcd3e1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dev": "next dev", "build": "next build", "start": "next start", + "archive": "tsx src/lib/notes/archiveNotes.ts", "format": "prettier -c --write ." }, "dependencies": { @@ -16,7 +17,7 @@ "@codemirror/lang-markdown": "^6.3.0", "@codemirror/language-data": "^6.5.1", "@codemirror/view": "^6.34.1", - "@types/node": "^22.8.1", + "@types/node": "^22.8.5", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@uiw/codemirror-theme-vscode": "^4.23.6", @@ -24,6 +25,7 @@ "antd": "^5.21.6", "autoprefixer": "^10.4.20", "clsx": "^2.1.1", + "dotenv": "^16.4.5", "framer-motion": "^11.11.10", "postcss": "^8.4.47", "prettier": "^3.3.3", @@ -37,6 +39,7 @@ "rehype-prism-plus": "^2.0.0", "remark-gfm": "^4.0.0", "tailwindcss": "^3.4.14", + "tsx": "^4.19.2", "typescript": "^5.6.3", "zod": "^3.23.8" }, diff --git a/public/assets/images/editor-mobile.png b/public/assets/images/editor-mobile.png deleted file mode 100644 index 2100535..0000000 Binary files a/public/assets/images/editor-mobile.png and /dev/null differ diff --git a/public/assets/images/preview-mobile.png b/public/assets/images/preview-mobile.png deleted file mode 100644 index c05232a..0000000 Binary files a/public/assets/images/preview-mobile.png and /dev/null differ diff --git a/src/app/api/notes/route.ts b/src/app/api/notes/route.ts index 7a1a14d..8c14d00 100644 --- a/src/app/api/notes/route.ts +++ b/src/app/api/notes/route.ts @@ -12,11 +12,11 @@ export async function GET(request: Request) { if (termIndex > 0) { const res = getArchivedNotes(termIndex - 1, page, pageSize, query); - return new Response(JSON.stringify(res), { headers: { "content-type": "application/json" }, status: res.code }); + return Response.json(res, { status: res.success ? 200 : res.code }); } - const result = await notes.query(page, pageSize, query); - return new Response(JSON.stringify(result), { headers: { "content-type": "application/json" }, status: result.code }); + const res = await notes.query(page, pageSize, query); + return Response.json(res, { status: res.success ? 200 : res.code }); } export async function POST(request: Request) { @@ -25,10 +25,7 @@ export async function POST(request: Request) { // validate submit date const now = new Date(); if (now < env.START_DATE || now > env.END_DATE) { - return new Response(JSON.stringify({ success: false, message: "不在值班时间,不用提交值班笔记" }), { - headers: { "content-type": "application/json" }, - status: 400 - }); + return Response.json({ success: false, message: "不在值班时间,不用提交值班笔记" }, { status: 400 }); } // get form data @@ -36,10 +33,7 @@ export async function POST(request: Request) { try { formData = await request.formData(); } catch (err) { - return new Response(JSON.stringify({ success: false, message: "表单数据解析错误" }), { - headers: { "content-type": "application/json" }, - status: 400 - }); + return Response.json({ success: false, message: "表单数据解析错误" }, { status: 400 }); } const name = formData.get("name")?.toString().trim(); @@ -48,15 +42,9 @@ export async function POST(request: Request) { // validate form data if (!name || name.length < 2 || name.length > 10) { - return new Response(JSON.stringify({ success: false, message: "姓名长度应该在2-10位之间" }), { - headers: { "content-type": "application/json" }, - status: 400 - }); + return Response.json({ success: false, message: "姓名长度应该在2-10位之间" }, { status: 400 }); } else if (!content || content.length < 4 || content.length > 1000) { - return new Response(JSON.stringify({ success: false, message: "值班笔记长度应该在10-1000位之间" }), { - headers: { "content-type": "application/json" }, - status: 400 - }); + return Response.json({ success: false, message: "值班笔记长度应该在10-1000位之间" }, { status: 400 }); } // insert into database diff --git a/src/app/form/components/Form/Form.tsx b/src/app/form/components/Form/Form.tsx index a052e4f..8629761 100644 --- a/src/app/form/components/Form/Form.tsx +++ b/src/app/form/components/Form/Form.tsx @@ -1,6 +1,5 @@ "use client"; -import z from "zod"; import clsx from "clsx"; import Link from "next/link"; import toast from "react-hot-toast"; @@ -12,6 +11,7 @@ import { FaRegUser, FaRegEdit } from "react-icons/fa"; import style from "../style.module.css"; import formatDate from "@/lib/utils/formatDate"; +import addNoteApi from "@/lib/api/notes/addNoteApi"; import usePersistantState from "@/hooks/usePersistantState"; import Message from "@/components/Message/Message"; @@ -34,25 +34,15 @@ export default function Form() { event.preventDefault(); setPending(true); - try { - const formData = new FormData(event.currentTarget); - formData.set("useMarkdown", useMarkdown.toString()); - const res = await fetch("/api/note", { method: "POST", body: formData }).then((res) => res.json()); - const { success, message } = z.object({ success: z.boolean(), message: z.string() }).parse(res); - - if (success) { - setContent(""); - setStatus("done"); - document.cookie = `${cookieName}=${new Date().toISOString()};max-age=${60 * 60 * 24};path=/;`; - } else { - console.error(message); - toast.error(message); - } - } catch (err) { - console.error(err); - } finally { - setPending(false); - } + const formData = new FormData(event.currentTarget); + const res = await addNoteApi(formData); + if (res.success) { + setContent(""); + setStatus("done"); + document.cookie = `${cookieName}=${new Date().toISOString()};max-age=${60 * 60 * 24};path=/;`; + } else toast.error(res.message); + + setPending(false); } useEffect(() => { @@ -73,7 +63,9 @@ export default function Form() { )} + {status === "duplicated" && } + 去看笔记 👉 diff --git a/src/lib/api/notes/addNoteApi.ts b/src/lib/api/notes/addNoteApi.ts new file mode 100644 index 0000000..732f975 --- /dev/null +++ b/src/lib/api/notes/addNoteApi.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; + +import { ApiResultType } from "@/types/app"; + +export default async function addNoteApi(formData: FormData): Promise { + const fallbackMessage = "提交失败"; + + try { + const res = await fetch("/api/notes", { method: "POST", body: formData }).then((res) => res.json()); + const parsed = z.object({ success: z.boolean() }).safeParse(res); + + if (!parsed.success) return { success: false, code: 400, message: res.message || fallbackMessage }; + return { success: true }; + } catch (err) { + console.error(err); + return { success: false, code: 500, message: fallbackMessage }; + } +} diff --git a/src/lib/api/notes/getNotesApi.ts b/src/lib/api/notes/getNotesApi.ts index 764c10c..e4306f0 100644 --- a/src/lib/api/notes/getNotesApi.ts +++ b/src/lib/api/notes/getNotesApi.ts @@ -24,7 +24,7 @@ export default async function getNotesApi( const res = await fetch(url).then((res) => res.json()); const parsed = z.object({ data: z.array(NoteDatabse), pagination: Pagination }).safeParse(res.data); if (!parsed.success) return { success: false, message: res.message || fallbackMessage, code: res.code || 500 }; - return { success: true, message: "获取成功", code: 200, data: parsed.data }; + return { success: true, data: parsed.data }; } catch (err) { console.error(err); return { success: false, code: 500, message: fallbackMessage }; diff --git a/src/lib/mongodb/clientPromise.ts b/src/lib/mongodb/clientPromise.ts index 09bad5a..6701178 100644 --- a/src/lib/mongodb/clientPromise.ts +++ b/src/lib/mongodb/clientPromise.ts @@ -3,12 +3,12 @@ import { MongoClient } from "mongodb"; import { ServerEnv } from "@/types/env"; const env = ServerEnv.parse(process.env); - const uri = env.MONGODB_URI; + let client: MongoClient; let clientPromise: Promise; -if (env.NODE_ENV === "development") { +if (process.env.NODE_ENV === "development") { let globalWithMongoClientPromise = global as typeof globalThis & { _mongoClientPromise: Promise; }; diff --git a/src/lib/mongodb/notes.ts b/src/lib/mongodb/notes.ts index 953af22..1dfdb33 100644 --- a/src/lib/mongodb/notes.ts +++ b/src/lib/mongodb/notes.ts @@ -17,7 +17,7 @@ async function insert(note: NoteType): Promise { } const result = await collection.insertOne({ date: new Date(), ...note }); - if (result.insertedId) return { success: true, code: 200, message: "提交成功" }; + if (result.insertedId) return { success: true }; else return { success: false, code: 500, message: "提交失败,无法写入数据库" }; } catch (error) { console.error(error); @@ -49,7 +49,7 @@ async function query(page: number, pageSize: number, query: string): Promise client.close()); +} + +main(); diff --git a/src/lib/notes/exportNotesToArchive.ts b/src/lib/notes/exportNotesToArchive.ts deleted file mode 100644 index 71400aa..0000000 --- a/src/lib/notes/exportNotesToArchive.ts +++ /dev/null @@ -1,9 +0,0 @@ -import fs from "fs"; -import path from "path"; - -import { NoteDatabseType } from "@/types/notes"; - -export default function exportNotesToArchive(term: string, notes: NoteDatabseType[]) { - const archivePath = path.join(process.cwd(), "archive", `${term}.json`); - fs.writeFileSync(archivePath, JSON.stringify(notes, null, 2)); -} diff --git a/src/lib/notes/getArchiveNotes.ts b/src/lib/notes/getArchiveNotes.ts deleted file mode 100644 index c961555..0000000 --- a/src/lib/notes/getArchiveNotes.ts +++ /dev/null @@ -1,20 +0,0 @@ -import z from "zod"; -import fs from "fs"; -import path from "path"; - -import { NoteDatabse } from "@/types/notes"; - -export default function getArchiveNotes() { - const archivePath = path.join(process.cwd(), "archive"); - const fileNames = fs.readdirSync(archivePath); - fileNames.sort((a, b) => b.localeCompare(a)); - - return fileNames.map((fileName) => { - const name = fileName.replace(".json", ""); - const filePath = path.join(archivePath, fileName); - const fileContent = fs.readFileSync(filePath, "utf8"); - const notes = z.array(NoteDatabse).parse(JSON.parse(fileContent)); - notes.sort(({ date: a }, { date: b }) => new Date(b).getTime() - new Date(a).getTime()); - return { name, notes }; - }); -} diff --git a/src/lib/notes/getArchivedNotes.ts b/src/lib/notes/getArchivedNotes.ts index 91e5d7a..4862b72 100644 --- a/src/lib/notes/getArchivedNotes.ts +++ b/src/lib/notes/getArchivedNotes.ts @@ -27,5 +27,6 @@ export default function getArchivedNotes( .filter((item) => keys.some((key) => item[key].toLowerCase().includes(query.toLowerCase()))) .sort(({ date: a }, { date: b }) => new Date(b).getTime() - new Date(a).getTime()) .slice((page - 1) * pageSize, page * pageSize); - return { success: true, code: 200, message: "获取成功", data: { data, pagination } }; + + return { success: true, data: { data, pagination } }; } diff --git a/src/types/app.ts b/src/types/app.ts index 68c75d4..e37318a 100644 --- a/src/types/app.ts +++ b/src/types/app.ts @@ -1,16 +1,14 @@ import { z } from "zod"; -export type ApiResultType = ( +export type ApiResultType = | ({ readonly success: true; } & (T extends undefined ? {} : { readonly data: NonNullable })) | { readonly success: false; - } -) & { - readonly code: number; - readonly message: string; -}; + readonly message: string; + readonly code: number; + }; export const Pagination = z.object({ page: z.number(), diff --git a/src/types/env.ts b/src/types/env.ts index 59947a6..3c53f02 100644 --- a/src/types/env.ts +++ b/src/types/env.ts @@ -1,7 +1,5 @@ import z from "zod"; -const NodeEnv = z.enum(["development", "production", "test", "preview"]); - const PublicEnv = z.object({ CURRENT_TERM: z.string(), START_DATE: z.string().transform((value) => new Date(value)), @@ -10,7 +8,6 @@ const PublicEnv = z.object({ }); const ServerEnv = z.object({ - NODE_ENV: NodeEnv, MONGODB_URI: z.string() });