From f98a21672d3e8e8318962d961cbb84a6a6379b01 Mon Sep 17 00:00:00 2001 From: jacobxu666 Date: Mon, 3 Aug 2026 22:53:10 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8B=B1=E8=AF=AD=E8=AF=AD=E6=B3=95=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=20v2:=E6=8E=A5=E5=85=A5=E5=8F=82=E8=80=83=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=E4=BD=93=E7=B3=BB,=E4=B8=AD=E6=96=87=E4=B8=BA?= =?UTF-8?q?=E4=B8=BB,=E5=AE=B6=E9=95=BF=E5=8F=AF=E7=BC=96=E8=BE=91,?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E5=8F=AF=E5=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 内容底座改为你提供的语法体系(入门/基础/进阶 69 知识点,中文讲解 + 中英对照例句 + 六步教学法),整体更适合小学生 - 内容存库(grammar_point),家长中心新增"编辑英语课程"可改标题/目标/ 课文,保存即时生效 - 取消顺序解锁,所有课程可直接学习,通过测验即点亮 - 测验引擎支持 选择/判断/填空/连词成句/连线 五种题型,达标自动联动 当日英语打卡 Co-Authored-By: Claude Opus 4.8 (1M context) --- package-lock.json | 1500 ++++++++++++++++++++- package.json | 2 + scripts/seed-grammar.ts | 55 + src/app/kid/page.tsx | 16 +- src/app/learn/[id]/page.tsx | 44 + src/app/learn/[seq]/page.tsx | 72 - src/app/learn/actions.ts | 31 +- src/app/learn/lesson-body.tsx | 19 + src/app/learn/md.tsx | 47 + src/app/learn/page.tsx | 132 +- src/app/learn/practice.tsx | 70 - src/app/learn/quiz-block.tsx | 258 ++++ src/app/learn/quiz-form.tsx | 149 -- src/app/learn/section-block.tsx | 69 - src/app/parent/dashboard.tsx | 18 +- src/app/parent/grammar/[pointId]/page.tsx | 41 + src/app/parent/grammar/actions.ts | 34 + src/app/parent/grammar/editor-form.tsx | 92 ++ src/app/parent/grammar/page.tsx | 54 + src/app/parent/page.tsx | 8 +- src/content/grammar/index.ts | 83 -- src/content/grammar/ref-data.json | 1 + src/content/grammar/types.ts | 48 - src/content/grammar/week01.ts | 307 ----- src/content/grammar/week02.ts | 440 ------ src/content/grammar/week03.ts | 430 ------ src/content/grammar/week04.ts | 374 ----- src/content/grammar/week05.ts | 341 ----- src/content/grammar/week06.ts | 358 ----- src/content/grammar/week07.ts | 361 ----- src/content/grammar/week08.ts | 375 ------ src/content/grammar/week09.ts | 361 ----- src/db/schema.ts | 28 +- src/lib/cefr.ts | 10 - src/lib/grammar.ts | 125 +- src/lib/quiz-parse.ts | 28 + 36 files changed, 2379 insertions(+), 4002 deletions(-) create mode 100644 scripts/seed-grammar.ts create mode 100644 src/app/learn/[id]/page.tsx delete mode 100644 src/app/learn/[seq]/page.tsx create mode 100644 src/app/learn/lesson-body.tsx create mode 100644 src/app/learn/md.tsx delete mode 100644 src/app/learn/practice.tsx create mode 100644 src/app/learn/quiz-block.tsx delete mode 100644 src/app/learn/quiz-form.tsx delete mode 100644 src/app/learn/section-block.tsx create mode 100644 src/app/parent/grammar/[pointId]/page.tsx create mode 100644 src/app/parent/grammar/actions.ts create mode 100644 src/app/parent/grammar/editor-form.tsx create mode 100644 src/app/parent/grammar/page.tsx delete mode 100644 src/content/grammar/index.ts create mode 100644 src/content/grammar/ref-data.json delete mode 100644 src/content/grammar/types.ts delete mode 100644 src/content/grammar/week01.ts delete mode 100644 src/content/grammar/week02.ts delete mode 100644 src/content/grammar/week03.ts delete mode 100644 src/content/grammar/week04.ts delete mode 100644 src/content/grammar/week05.ts delete mode 100644 src/content/grammar/week06.ts delete mode 100644 src/content/grammar/week07.ts delete mode 100644 src/content/grammar/week08.ts delete mode 100644 src/content/grammar/week09.ts delete mode 100644 src/lib/cefr.ts create mode 100644 src/lib/quiz-parse.ts diff --git a/package-lock.json b/package-lock.json index 27a5892..0e6eb71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "radix-ui": "^1.6.7", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "sonner": "^2.0.5", "tailwind-merge": "^3.3.1", "zod": "^3.25.67" @@ -3489,6 +3491,54 @@ "tailwindcss": "4.3.3" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.13.3", "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.13.3.tgz", @@ -3502,7 +3552,6 @@ "version": "19.2.18", "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.18.tgz", "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -3518,6 +3567,18 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "license": "ISC" + }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmmirror.com/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -3539,6 +3600,16 @@ "node": ">= 6.0.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/bcryptjs": { "version": "3.0.3", "resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-3.0.3.tgz", @@ -3575,6 +3646,56 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -3602,13 +3723,52 @@ "node": ">=6" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, "license": "MIT" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz", @@ -3618,6 +3778,15 @@ "node": ">=0.10" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", @@ -3634,6 +3803,19 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dotenv": { "version": "17.4.2", "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-17.4.2.tgz", @@ -3844,6 +4026,34 @@ "@esbuild/win32-x64": "0.25.12" } }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", @@ -3897,6 +4107,56 @@ "dev": true, "license": "ISC" }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/iconv-lite": { "version": "0.7.3", "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.3.tgz", @@ -3913,6 +4173,68 @@ "url": "https://opencollective.com/express" } }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz", @@ -4208,6 +4530,16 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru.min": { "version": "1.1.4", "resolved": "https://registry.npmmirror.com/lru.min/-/lru.min-1.1.4.tgz", @@ -4242,6 +4574,855 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/mysql2": { "version": "3.23.2", "resolved": "https://registry.npmmirror.com/mysql2/-/mysql2-3.23.2.tgz", @@ -4384,6 +5565,31 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", @@ -4419,6 +5625,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/radix-ui": { "version": "1.6.7", "resolved": "https://registry.npmmirror.com/radix-ui/-/radix-ui-1.6.7.tgz", @@ -4517,6 +5733,33 @@ "react": "^19.2.8" } }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -4586,6 +5829,72 @@ } } }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmmirror.com/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -4706,6 +6015,16 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/sql-escaper": { "version": "1.5.1", "resolved": "https://registry.npmmirror.com/sql-escaper/-/sql-escaper-1.5.1.tgz", @@ -4721,6 +6040,38 @@ "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmmirror.com/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -4775,6 +6126,26 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", @@ -5304,6 +6675,93 @@ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmmirror.com/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -5347,6 +6805,34 @@ } } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", @@ -5355,6 +6841,16 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 4bda6d7..bc9f8fa 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "radix-ui": "^1.6.7", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "sonner": "^2.0.5", "tailwind-merge": "^3.3.1", "zod": "^3.25.67" diff --git a/scripts/seed-grammar.ts b/scripts/seed-grammar.ts new file mode 100644 index 0000000..a4318ea --- /dev/null +++ b/scripts/seed-grammar.ts @@ -0,0 +1,55 @@ +// 将参考语法体系(ref-data.json)灌入 grammar_point 表;仅当表为空时执行 +import fs from "node:fs"; +import path from "node:path"; +import { sql } from "drizzle-orm"; +import { drizzle } from "drizzle-orm/mysql2"; +import mysql from "mysql2/promise"; +import { grammarPoints } from "../src/db/schema"; + +type RefPoint = { + id: string; + title: string; + difficulty: number; + prereq: string[]; + summary: string; + md: string; +}; +type RefLevel = { id: string; name: string; points: RefPoint[] }; + +async function main() { + const file = path.join(process.cwd(), "src/content/grammar/ref-data.json"); + const data = JSON.parse(fs.readFileSync(file, "utf8")) as { levels: RefLevel[] }; + const conn = await mysql.createConnection(process.env.DATABASE_URL!); + const db = drizzle(conn); + + const [cnt] = await db.select({ n: sql`COUNT(*)` }).from(grammarPoints); + if (cnt.n > 0) { + console.log("grammar_point 已有", cnt.n, "条,跳过种子"); + await conn.end(); + return; + } + + let seq = 0; + const rows = []; + for (const lvl of data.levels) { + for (const p of lvl.points) { + seq += 1; + rows.push({ + pointId: p.id, + level: lvl.id, + levelName: lvl.name, + title: p.title, + difficulty: p.difficulty, + summary: p.summary, + prereq: JSON.stringify(p.prereq ?? []), + md: p.md, + seq, + }); + } + } + await db.insert(grammarPoints).values(rows); + console.log("已写入 grammar_point:", rows.length, "条"); + await conn.end(); +} + +main(); diff --git a/src/app/kid/page.tsx b/src/app/kid/page.tsx index 631a73f..ec50438 100644 --- a/src/app/kid/page.tsx +++ b/src/app/kid/page.tsx @@ -8,8 +8,7 @@ import { todayStr } from "@/lib/dates"; import { getSessionUser } from "@/lib/auth"; import { streakOf } from "@/lib/streak"; import { dayTasks } from "@/lib/tasks"; -import { entryBySeq, TOTAL } from "@/content/grammar"; -import { nextSeq, passedSet } from "@/lib/grammar"; +import { listPoints, passedByPoint } from "@/lib/grammar"; import { TaskCard } from "./task-card"; export default async function KidPage() { @@ -25,10 +24,11 @@ export default async function KidPage() { const doneCount = tasks.filter((t) => t.status === "done").length; const allDone = tasks.length > 0 && doneCount === tasks.length; - // 英语语法入口:下一课标题 + 已完成课数 - const gNext = await nextSeq(user.id); - const gGoal = gNext <= TOTAL ? entryBySeq(gNext) : null; - const gDone = (await passedSet(user.id)).size; + // 英语语法入口:下一课标题 + 已点亮数 + const gPassed = await passedByPoint(user.id); + const gPoints = await listPoints(); + const gGoal = gPoints.find((p) => !gPassed.has(p.pointId)); + const gDone = gPassed.size; return (
@@ -53,9 +53,9 @@ export default async function KidPage() {
-

英语语法 · 已学 {gDone} 课

+

英语语法 · 已点亮 {gDone} 课

- {gGoal ? gGoal.title : "全部完成,真棒!"} + {gGoal ? gGoal.title : "全部点亮,真棒!"}

→ diff --git a/src/app/learn/[id]/page.tsx b/src/app/learn/[id]/page.tsx new file mode 100644 index 0000000..62651b0 --- /dev/null +++ b/src/app/learn/[id]/page.tsx @@ -0,0 +1,44 @@ +import Link from "next/link"; +import { redirect } from "next/navigation"; +import { BackgroundBlobs } from "@/components/background-blobs"; +import { Badge } from "@/components/ui/badge"; +import { getPoint, passedByPoint } from "@/lib/grammar"; +import { getSessionUser } from "@/lib/auth"; +import { LessonBody } from "../lesson-body"; + +export default async function LessonPage({ params }: { params: Promise<{ id: string }> }) { + const user = await getSessionUser(); + if (!user) redirect("/join"); + if (user.role !== "child") redirect("/parent"); + + const pointId = (await params).id; + const point = await getPoint(pointId); + if (!point) redirect("/learn"); + + const passed = await passedByPoint(user.id); + const alreadyPassed = passed.has(pointId); + + return ( +
+ +
+ + ← 课程地图 + +
+ {point.levelName} + 难度 {"★".repeat(point.difficulty)} + {alreadyPassed && ( + 已点亮 + )} +
+

{point.title}

+ {point.summary &&

🎯 {point.summary}

} +
+ +
+ +
+
+ ); +} diff --git a/src/app/learn/[seq]/page.tsx b/src/app/learn/[seq]/page.tsx deleted file mode 100644 index 6e2643e..0000000 --- a/src/app/learn/[seq]/page.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import Link from "next/link"; -import { redirect } from "next/navigation"; -import { BackgroundBlobs } from "@/components/background-blobs"; -import { Badge } from "@/components/ui/badge"; -import { cn } from "@/lib/utils"; -import { CEFR_STYLE } from "@/lib/cefr"; -import { TOTAL, entryBySeq } from "@/content/grammar"; -import { isUnlocked, passedSet } from "@/lib/grammar"; -import { getSessionUser } from "@/lib/auth"; -import { SectionBlock } from "../section-block"; -import { Practice } from "../practice"; -import { QuizForm } from "../quiz-form"; - -export default async function LessonPage({ params }: { params: Promise<{ seq: string }> }) { - const user = await getSessionUser(); - if (!user) redirect("/join"); - if (user.role !== "child") redirect("/parent"); - - const seq = Number((await params).seq); - const entry = entryBySeq(seq); - if (!entry) redirect("/learn"); - - const passed = await passedSet(user.id); - if (!isUnlocked(seq, passed)) redirect("/learn"); - - const alreadyPassed = passed.has(seq); - const nextSeq = seq < TOTAL ? seq + 1 : null; - const cefr = CEFR_STYLE[entry.cefr]; - - return ( -
- -
- - ← 课程地图 - -
- - {cefr.label} - - - {entry.type === "lesson" ? `第 ${seq} 课` : entry.type === "weekly" ? "周测" : "月测"} - - {alreadyPassed && ( - 已通过 - )} -
-

{entry.title}

- {entry.type === "lesson" ? ( -

🎯 {entry.goal}

- ) : ( -

{entry.intro}

- )} -
- - {entry.type === "lesson" && ( - <> -
- {entry.sections.map((s, i) => ( - - ))} -
- - - )} - -
- -
-
- ); -} diff --git a/src/app/learn/actions.ts b/src/app/learn/actions.ts index 8960258..546318d 100644 --- a/src/app/learn/actions.ts +++ b/src/app/learn/actions.ts @@ -2,37 +2,28 @@ import { revalidatePath } from "next/cache"; import { getSessionUser } from "@/lib/auth"; -import { entryBySeq } from "@/content/grammar"; -import { completeSeq, grade, isUnlocked, passedSet } from "@/lib/grammar"; +import { completePoint, extractQuiz, getPoint, gradeAnswers, type AnswerValue } from "@/lib/grammar"; export type QuizResult = - | { - score: number; - passed: boolean; - correct: number; - total: number; - detail: boolean[]; - } + | { score: number; passed: boolean; correct: number; total: number; detail: boolean[] } | { error: string }; -// 提交测验:服务端按标准答案计分(防篡改),达标即记进度并联动英语打卡 -export async function submitQuiz(_prev: QuizResult | null, formData: FormData): Promise { +// 提交结课练习:服务端按课文 quiz 判分,达标记进度并联动英语打卡 +export async function submitQuiz(pointId: string, answers: AnswerValue[]): Promise { const user = await getSessionUser(); if (!user || user.role !== "child") return { error: "会话无效,请重新激活设备" }; - const seq = Number(formData.get("seq")); - const entry = entryBySeq(seq); - if (!entry) return { error: "课程不存在" }; + const point = await getPoint(pointId); + if (!point) return { error: "知识点不存在" }; - const passed = await passedSet(user.id); - if (!isUnlocked(seq, passed)) return { error: "请先完成前面的课程" }; + const questions = extractQuiz(point.md); + if (questions.length === 0) return { error: "本课暂无练习题" }; - const answers = entry.quiz.map((_, i) => Number(formData.get(`a${i}`))); - const g = grade(entry.quiz, answers); + const g = gradeAnswers(questions, answers); + await completePoint(user.id, pointId, point.title, g.score, g.passed); - await completeSeq(user.id, seq, g.score, g.passed); revalidatePath("/learn"); - revalidatePath(`/learn/${seq}`); + revalidatePath(`/learn/${pointId}`); revalidatePath("/kid"); revalidatePath("/parent"); return g; diff --git a/src/app/learn/lesson-body.tsx b/src/app/learn/lesson-body.tsx new file mode 100644 index 0000000..81d68d4 --- /dev/null +++ b/src/app/learn/lesson-body.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { Md } from "./md"; +import { QuizBlock } from "./quiz-block"; +import { extractQuiz, type QuizQ } from "@/lib/quiz-parse"; + +// 课文 = 前段 markdown + ⑤即时练习(quiz)+ 后段 markdown(小结/知识卡) +export function LessonBody({ md, pointId }: { md: string; pointId: string }) { + const parts = md.split(/```quiz\s*\n[\s\S]*?```/); + const questions: QuizQ[] = extractQuiz(md); + + return ( +
+ + {questions.length > 0 && } + {parts.slice(1).join("\n").trim() !== "" && } +
+ ); +} diff --git a/src/app/learn/md.tsx b/src/app/learn/md.tsx new file mode 100644 index 0000000..390e72e --- /dev/null +++ b/src/app/learn/md.tsx @@ -0,0 +1,47 @@ +"use client"; + +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import type { Components } from "react-markdown"; + +// 课文 markdown 的 Tailwind 样式映射(中文为主的企业信任风) +const components: Components = { + h2: ({ children }) => ( +

{children}

+ ), + h3: ({ children }) =>

{children}

, + p: ({ children }) =>

{children}

, + ul: ({ children }) =>
    {children}
, + ol: ({ children }) =>
    {children}
, + li: ({ children }) =>
  • {children}
  • , + strong: ({ children }) => {children}, + blockquote: ({ children }) => ( +
    + {children} +
    + ), + pre: ({ children }) => ( +
    +      {children}
    +    
    + ), + code: ({ children }) => {children}, + table: ({ children }) => ( +
    + {children}
    +
    + ), + thead: ({ children }) => {children}, + th: ({ children }) => {children}, + td: ({ children }) => {children}, + hr: () =>
    , +}; + +// 渲染一段不含 quiz 块的 markdown +export function Md({ text }: { text: string }) { + return ( + + {text} + + ); +} diff --git a/src/app/learn/page.tsx b/src/app/learn/page.tsx index c4a5d97..65b7c0b 100644 --- a/src/app/learn/page.tsx +++ b/src/app/learn/page.tsx @@ -1,12 +1,10 @@ import Link from "next/link"; -import { BookOpen, Check, Lock } from "lucide-react"; +import { BookOpen, Check } from "lucide-react"; import { redirect } from "next/navigation"; import { BackgroundBlobs } from "@/components/background-blobs"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; -import { CEFR_STYLE } from "@/lib/cefr"; -import { SYLLABUS, TOTAL, entryBySeq } from "@/content/grammar"; -import { isUnlocked, nextSeq, passedSet } from "@/lib/grammar"; +import { LEVELS, listPoints, passedByPoint, totalPoints } from "@/lib/grammar"; import { getSessionUser } from "@/lib/auth"; export default async function LearnHome() { @@ -14,34 +12,33 @@ export default async function LearnHome() { if (!user) redirect("/join"); if (user.role !== "child") redirect("/parent"); - const passed = await passedSet(user.id); + const points = await listPoints(); + const passed = await passedByPoint(user.id); + const total = await totalPoints(); const done = passed.size; - const next = await nextSeq(user.id); - const current = next <= TOTAL ? entryBySeq(next) : null; - const pct = Math.round((done / TOTAL) * 100); + const pct = total ? Math.round((done / total) * 100) : 0; + const next = points.find((p) => !passed.has(p.pointId)); return ( -
    +
    ← 返回打卡 - {done} / {TOTAL} 课 + 已点亮 {done} / {total}
    - {current ? ( + {next ? (
    - 今日目标 · 第 {current.seq} 课 + 推荐下一课 · {next.levelName} -

    {current.title}

    -

    - {current.type === "lesson" ? current.goal : current.intro} -

    - +

    {next.title}

    +

    也可以直接从下面任意一课开始,全部都能学。

    + @@ -50,8 +47,7 @@ export default async function LearnHome() { ) : (

    🏆

    -

    语法课程全部完成!

    -

    太厉害了,继续保持每天复习。

    +

    全部语法点都点亮了!

    )} @@ -62,60 +58,62 @@ export default async function LearnHome() {
    -
    - {SYLLABUS.map((e) => { - const doneIt = passed.has(e.seq); - const cur = e.seq === next; - const unlocked = isUnlocked(e.seq, passed); - const isMilestone = e.type !== "lesson"; - const inner = ( -
    - {doneIt ? ( - - ) : !unlocked ? ( - - ) : isMilestone ? ( - - ) : ( - e.seq - )} + {LEVELS.map((lvl) => { + const group = points.filter((p) => p.level === lvl.id); + if (group.length === 0) return null; + const lvlDone = group.filter((p) => passed.has(p.pointId)).length; + return ( +
    +
    +
    +

    + {lvl.name} + {"★".repeat(lvl.stars)} +

    +

    {lvl.desc}

    +
    + + {lvlDone}/{group.length} + +
    +
    + {group.map((p) => { + const doneIt = passed.has(p.pointId); + return ( + + + {doneIt && } + + {p.title} + + ); + })}
    - ); - const style = e.type === "lesson" ? "" : "ring-1 ring-amber-200"; - return unlocked ? ( - - {inner} - - ) : ( -
    - {inner} -
    - ); - })} -
    -

    - 方块带书图标的是周测/月测;灰锁需先通过前一课 -

    -

    - 级别:{[...new Set(SYLLABUS.map((e) => e.cefr))].map((c) => ( - - {c} - - ))} + + ); + })} +

    + 所有课程都可以直接学习,点亮只代表你通过了该课检验

    ); diff --git a/src/app/learn/practice.tsx b/src/app/learn/practice.tsx deleted file mode 100644 index b5ba343..0000000 --- a/src/app/learn/practice.tsx +++ /dev/null @@ -1,70 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { Check, X } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; -import type { QuizQ } from "@/content/grammar/types"; - -// 课内随练:点选后立即显示对错与解析,不计分 -export function Practice({ items }: { items: QuizQ[] }) { - const [picked, setPicked] = useState<(number | null)[]>(items.map(() => null)); - - return ( -
    -

    练一练

    - {items.map((q, qi) => { - const p = picked[qi]; - const revealed = p !== null; - return ( -
    -

    - {qi + 1}. {q.q} -

    -
    - {q.options.map((opt, oi) => { - const isRight = revealed && oi === q.answer; - const isWrong = revealed && oi === p && oi !== q.answer; - return ( - - ); - })} -
    - {revealed && ( -

    - 解析:{q.explain} -

    - )} -
    - ); - })} - -
    - ); -} diff --git a/src/app/learn/quiz-block.tsx b/src/app/learn/quiz-block.tsx new file mode 100644 index 0000000..575bd89 --- /dev/null +++ b/src/app/learn/quiz-block.tsx @@ -0,0 +1,258 @@ +"use client"; + +import { useTransition, useState } from "react"; +import Link from "next/link"; +import { Check, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import type { QuizQ } from "@/lib/quiz-parse"; +import type { AnswerValue } from "@/lib/grammar"; +import { submitQuiz, type QuizResult } from "./actions"; + +type AV = AnswerValue | null; + +// 结课检验:选择/判断/填空/连词成句/连线,提交后服务端判分,达标点亮并联动打卡 +export function QuizBlock({ pointId, questions }: { pointId: string; questions: QuizQ[] }) { + const [answers, setAnswers] = useState(questions.map(() => null)); + const [result, setResult] = useState | null>(null); + const [error, setError] = useState(""); + const [pending, startTransition] = useTransition(); + + const set = (i: number, v: AV) => { + const next = [...answers]; + next[i] = v; + setAnswers(next); + }; + + const isAnswered = (q: QuizQ, a: AV): boolean => { + if (q.type === "fill") return typeof a === "string" && a.trim() !== ""; + if (q.type === "order") return Array.isArray(a) && a.length === (q.words ?? []).length; + if (q.type === "match") return Array.isArray(a) && a.length === (q.pairs ?? []).length && a.every((x) => x !== null && x !== ""); + return typeof a === "number"; + }; + const allAnswered = questions.every((q, i) => isAnswered(q, answers[i])); + + const submit = () => { + setError(""); + startTransition(async () => { + const r = await submitQuiz(pointId, answers.map((a) => a as AnswerValue)); + if ("error" in r) setError(r.error); + else setResult(r); + }); + }; + + return ( +
    +

    ⑤ 即时练习

    +

    做完提交,马上判分;每题有解说,达标即点亮本课。

    +
    + {questions.map((q, qi) => { + const a = answers[qi]; + const ok = result ? result.detail[qi] : null; + return ( +
    +

    + {qi + 1}. {q.stem} +

    + + {q.type === "fill" && ( + set(qi, e.target.value)} + placeholder="填写答案" + className="mt-2 w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200" + /> + )} + + {(q.type === "choice" || q.type === "judge") && ( +
    + {(q.type === "judge" ? ["对", "错"] : q.options ?? []).map((opt, oi) => { + const sel = a === oi; + const isRight = result && oi === (q.answer as number); + const isWrong = result && sel && oi !== (q.answer as number); + return ( + + ); + })} +
    + )} + + {q.type === "order" && set(qi, v)} />} + + {q.type === "match" && set(qi, v)} />} + + {result && ( +

    + {q.type === "fill" && ( + 参考:{(q.answers ?? []).join(" / ")} + )} + {q.type === "order" && ( + 参考:{(q.answer as string[]).join(" ")} + )} + {q.type === "match" && ( + + 参考:{(q.pairs ?? []).map((p) => `${p[0]}→${p[1]}`).join("、")} + + )} + 解说:{q.explain} +

    + )} + {result && ok === false &&

    这题答错了,看看上面的参考与解说。

    } +
    + ); + })} +
    + + {!result && ( + <> + + {error &&

    {error}

    } + + )} + + {result && ( +
    +

    {result.score} 分

    +

    + 答对 {result.correct} / {result.total} + {result.passed ? " · 本课点亮 🎉 已计入今日英语打卡" : " · 看看解说,再试一次"} +

    +
    + {!result.passed && ( + + )} + + + +
    +
    + )} +
    + ); +} + +// 连词成句:点词入槽,再点槽里的词可移回 +function OrderInput({ + q, + a, + disabled, + onChange, +}: { + q: QuizQ; + a: AV; + disabled: boolean; + onChange: (v: string[]) => void; +}) { + const seq = Array.isArray(a) ? (a as string[]) : []; + const remaining = (() => { + const pool = [...(q.words ?? [])]; + for (const w of seq) { + const idx = pool.indexOf(w); + if (idx >= 0) pool.splice(idx, 1); + } + return pool; + })(); + return ( +
    +
    + {seq.length === 0 && 按顺序点下面的词,组成句子} + {seq.map((w, i) => ( + + ))} +
    +
    + {remaining.map((w, i) => ( + + ))} +
    +
    + ); +} + +// 连线:为左边每项选择右边的类型 +function MatchInput({ + q, + a, + disabled, + onChange, +}: { + q: QuizQ; + a: AV; + disabled: boolean; + onChange: (v: string[]) => void; +}) { + const pairs = q.pairs ?? []; + const rights = [...new Set(pairs.map((p) => p[1]))]; + const sel = Array.isArray(a) ? ([...(a as string[])] as string[]) : pairs.map(() => ""); + return ( +
    + {pairs.map((p, pi) => ( +
    + {p[0]} + → + +
    + ))} +
    + ); +} diff --git a/src/app/learn/quiz-form.tsx b/src/app/learn/quiz-form.tsx deleted file mode 100644 index 47da768..0000000 --- a/src/app/learn/quiz-form.tsx +++ /dev/null @@ -1,149 +0,0 @@ -"use client"; - -import { useActionState, useState } from "react"; -import Link from "next/link"; -import { CheckCircle2, XCircle } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { cn } from "@/lib/utils"; -import type { QuizQ } from "@/content/grammar/types"; -import { submitQuiz, type QuizResult } from "./actions"; - -// 计分测验:选中后提交,服务端判分;达标才算通过并解锁下一课 -export function QuizForm({ - seq, - quiz, - nextSeq, - alreadyPassed, -}: { - seq: number; - quiz: QuizQ[]; - nextSeq: number | null; - alreadyPassed: boolean; -}) { - const [state, formAction, pending] = useActionState(submitQuiz, null); - const [picked, setPicked] = useState<(number | null)[]>(quiz.map(() => null)); - const result = state && "score" in state ? state : null; - const error = state && "error" in state ? state.error : null; - const allAnswered = picked.every((p) => p !== null); - - return ( -
    -

    - {alreadyPassed ? "再测一次" : "课后测验"} - - 满分通过线 80%,达标即完成本课并自动打卡 - -

    - -
    - - {quiz.map((q, qi) => ( -
    -

    - {qi + 1}. {q.q} -

    -
    - {q.options.map((opt, oi) => { - const sel = picked[qi] === oi; - const isRight = result && oi === q.answer; - const isWrong = result && sel && oi !== q.answer; - return ( - - ); - })} -
    - {result && ( -

    解析:{q.explain}

    - )} -
    - ))} - - {!result && ( - - )} - {error &&

    {error}

    } -
    - - {result && ( -
    -

    {result.score} 分

    -

    - 答对 {result.correct} / {result.total} - {result.passed ? " · 通过 🎉" : " · 再复习一遍吧"} -

    -
    - {!result.passed && ( - - )} - {result.passed && nextSeq && ( - - - - )} - {result.passed && !nextSeq && ( - - - - )} -
    -
    - )} -
    - ); -} diff --git a/src/app/learn/section-block.tsx b/src/app/learn/section-block.tsx deleted file mode 100644 index bc2ae73..0000000 --- a/src/app/learn/section-block.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { renderBold } from "@/lib/md"; -import type { Section } from "@/content/grammar/types"; - -// 渲染一个讲解小节:英文讲解 + 中文要点 + 例句 + 结构表 + 易错提示 -export function SectionBlock({ s }: { s: Section }) { - return ( -
    -
    -

    {s.heading}

    -

    {s.zh}

    -
    -

    - {renderBold(s.body).map((part, i) => - typeof part === "string" ? ( - {part} - ) : ( - - {part.b} - - ), - )} -

    - - {s.structure && ( -
    - - - - {s.structure.cols.map((c) => ( - - ))} - - - - {s.structure.rows.map((row, ri) => ( - - {row.map((cell, ci) => ( - - ))} - - ))} - -
    - {c} -
    - {cell} -
    -
    - )} - - {s.examples && ( -
      - {s.examples.map((ex, i) => ( -
    • - › - {ex} -
    • - ))} -
    - )} - - {s.note && ( -

    - 💡 {s.note} -

    - )} -
    - ); -} diff --git a/src/app/parent/dashboard.tsx b/src/app/parent/dashboard.tsx index 9bb5e42..d395147 100644 --- a/src/app/parent/dashboard.tsx +++ b/src/app/parent/dashboard.tsx @@ -1,5 +1,6 @@ "use client"; +import Link from "next/link"; import { logout } from "@/app/login/actions"; import { BackgroundBlobs } from "@/components/background-blobs"; import { Button } from "@/components/ui/button"; @@ -60,11 +61,18 @@ export function ParentDashboard({

    家长中心

    -
    - -
    +
    + + + +
    + +
    +
    diff --git a/src/app/parent/grammar/[pointId]/page.tsx b/src/app/parent/grammar/[pointId]/page.tsx new file mode 100644 index 0000000..f4b6579 --- /dev/null +++ b/src/app/parent/grammar/[pointId]/page.tsx @@ -0,0 +1,41 @@ +import Link from "next/link"; +import { redirect } from "next/navigation"; +import { BackgroundBlobs } from "@/components/background-blobs"; +import { Badge } from "@/components/ui/badge"; +import { getPoint } from "@/lib/grammar"; +import { getSessionUser } from "@/lib/auth"; +import { EditorForm } from "../editor-form"; + +export default async function GrammarEditPage({ params }: { params: Promise<{ pointId: string }> }) { + const parent = await getSessionUser(); + if (!parent) redirect("/login"); + if (parent.role !== "parent") redirect("/kid"); + + const pointId = (await params).pointId; + const point = await getPoint(pointId); + if (!point) redirect("/parent/grammar"); + + return ( +
    + +
    + + ← 课程列表 + +
    + {point.levelName} +

    {point.title}

    +
    +
    + +
    + +
    +
    + ); +} diff --git a/src/app/parent/grammar/actions.ts b/src/app/parent/grammar/actions.ts new file mode 100644 index 0000000..55ec8ec --- /dev/null +++ b/src/app/parent/grammar/actions.ts @@ -0,0 +1,34 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { eq } from "drizzle-orm"; +import { db } from "@/db"; +import { grammarPoints } from "@/db/schema"; +import { getSessionUser } from "@/lib/auth"; + +// 家长编辑语法知识点(标题/摘要/课文 markdown) +export async function updatePoint( + pointId: string, + data: { title: string; summary: string; md: string }, +): Promise<{ ok?: true; error?: string }> { + const parent = await getSessionUser(); + if (!parent || parent.role !== "parent") return { error: "请先以家长身份登录" }; + if (!data.title.trim()) return { error: "标题不能为空" }; + if (!data.md.trim()) return { error: "课文内容不能为空" }; + + await db + .update(grammarPoints) + .set({ + title: data.title.trim(), + summary: data.summary, + md: data.md, + updatedAt: new Date(), + }) + .where(eq(grammarPoints.pointId, pointId)); + + revalidatePath("/learn"); + revalidatePath(`/learn/${pointId}`); + revalidatePath("/parent/grammar"); + revalidatePath(`/parent/grammar/${pointId}`); + return { ok: true }; +} diff --git a/src/app/parent/grammar/editor-form.tsx b/src/app/parent/grammar/editor-form.tsx new file mode 100644 index 0000000..ed9dfe0 --- /dev/null +++ b/src/app/parent/grammar/editor-form.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { Eye, EyeOff, Save } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Md } from "@/app/learn/md"; +import { extractQuiz } from "@/lib/quiz-parse"; +import { updatePoint } from "./actions"; + +// 家长课程编辑器:改标题/摘要/课文 markdown,可实时预览,保存即生效 +export function EditorForm({ + pointId, + initialTitle, + initialSummary, + initialMd, +}: { + pointId: string; + initialTitle: string; + initialSummary: string | null; + initialMd: string; +}) { + const [title, setTitle] = useState(initialTitle); + const [summary, setSummary] = useState(initialSummary ?? ""); + const [md, setMd] = useState(initialMd); + const [preview, setPreview] = useState(false); + const [msg, setMsg] = useState<{ ok?: boolean; text: string } | null>(null); + const [pending, startTransition] = useTransition(); + + const quizCount = extractQuiz(md).length; + + const save = () => { + setMsg(null); + startTransition(async () => { + const r = await updatePoint(pointId, { title, summary, md }); + if (r.error) setMsg({ text: r.error }); + else setMsg({ ok: true, text: "已保存,孩子端立即生效" }); + }); + }; + + return ( +
    +
    +
    + + setTitle(e.target.value)} /> +
    +
    + + setSummary(e.target.value)} /> +
    +
    + +
    +
    + +
    + 检测到练习题 {quizCount} 道 + +
    +
    + {preview ? ( +
    + +
    + ) : ( +