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()
});