From ae1fb329b5522dbecd21379a7ef23c8959d98de7 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Thu, 6 Aug 2026 21:18:16 -0600 Subject: [PATCH] =?UTF-8?q?feat(=E9=98=B6=E6=AE=B51):=20=E6=90=AC=E5=85=A5?= =?UTF-8?q?=20ojnext=20=E4=B8=BA=20apps/web=EF=BC=8C=E6=9C=AA=E6=94=B9?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + apps/web/.browserslistrc | 1 + apps/web/.env.production | 8 + apps/web/.env.staging | 8 + apps/web/.env.test | 8 + apps/web/.github/workflows/deploy.yml | 46 + apps/web/.gitignore | 30 + apps/web/.prettierrc.toml | 1 + apps/web/CLAUDE.md | 114 ++ apps/web/README.md | 1 + apps/web/docs/API接口对比分析.md | 548 ++++++ apps/web/docs/FlowchartEditor.md | 311 ++++ ...26-06-14-submit-formatting-button-state.md | 259 +++ .../plans/2026-07-26-demo-student-view.md | 296 ++++ ...4-submit-formatting-button-state-design.md | 34 + ...2026-07-05-sql-min-two-testcases-design.md | 28 + ...-07-05-sql-schema-autocompletion-design.md | 50 + .../2026-07-26-demo-student-view-design.md | 102 ++ apps/web/docs/图表.md | 216 +++ apps/web/index.html | 17 + apps/web/package.json | 67 + apps/web/public/A.png | Bin 0 -> 8228 bytes apps/web/public/B.png | Bin 0 -> 9034 bytes apps/web/public/C.png | Bin 0 -> 7776 bytes apps/web/public/Monaco.ttf | Bin 0 -> 48116 bytes apps/web/public/S.png | Bin 0 -> 11789 bytes apps/web/public/badge-1.png | Bin 0 -> 2214 bytes apps/web/public/badge-2.png | Bin 0 -> 2250 bytes apps/web/public/badge-3.png | Bin 0 -> 2894 bytes apps/web/public/badge-4.png | Bin 0 -> 2749 bytes apps/web/public/badge-5.png | Bin 0 -> 3318 bytes apps/web/public/badge-6.png | Bin 0 -> 3194 bytes apps/web/public/favicon.ico | Bin 0 -> 2393 bytes apps/web/public/noto--dog-face.ico | Bin 0 -> 192545 bytes apps/web/public/style.css | 20 + apps/web/src/App.vue | 77 + .../components/AchievementModal.vue | 185 +++ apps/web/src/admin/achievement/list.vue | 144 ++ apps/web/src/admin/ai/list.vue | 206 +++ .../admin/announcement/components/Actions.vue | 39 + apps/web/src/admin/announcement/detail.vue | 117 ++ apps/web/src/admin/announcement/list.vue | 113 ++ apps/web/src/admin/api.ts | 600 +++++++ apps/web/src/admin/communication/messages.vue | 2 + .../src/admin/contest/components/Actions.vue | 68 + apps/web/src/admin/contest/detail.vue | 196 +++ apps/web/src/admin/contest/helper.vue | 326 ++++ apps/web/src/admin/contest/list.vue | 132 ++ apps/web/src/admin/problem/Stuck.vue | 56 + apps/web/src/admin/problem/TopACTrend.vue | 202 +++ .../src/admin/problem/components/Actions.vue | 160 ++ .../admin/problem/components/AddButton.vue | 47 + .../problem/components/AstRulesEditor.vue | 397 +++++ .../problem/components/BatchTagModal.vue | 109 ++ .../src/admin/problem/components/Modal.vue | 94 ++ .../problem/components/SQLTestcaseEditor.vue | 345 ++++ .../problem/components/TagProblemsModal.vue | 147 ++ .../problem/components/TestcaseGenerator.vue | 263 +++ apps/web/src/admin/problem/detail.vue | 920 ++++++++++ apps/web/src/admin/problem/list.vue | 335 ++++ apps/web/src/admin/problem/tags.vue | 186 +++ .../admin/problemset/components/Actions.vue | 105 ++ .../problemset/components/AddBadgeModal.vue | 162 ++ .../problemset/components/AddProblemModal.vue | 103 ++ .../problemset/components/BadgeManagement.vue | 96 ++ .../problemset/components/EditBadgeModal.vue | 167 ++ .../components/EditProblemModal.vue | 100 ++ .../components/ProblemManagement.vue | 73 + .../problemset/components/ProblemSetInfo.vue | 70 + .../components/ProgressManagement.vue | 69 + apps/web/src/admin/problemset/detail.vue | 270 +++ apps/web/src/admin/problemset/edit.vue | 169 ++ apps/web/src/admin/problemset/list.vue | 212 +++ apps/web/src/admin/setting/config.vue | 314 ++++ apps/web/src/admin/setting/home.vue | 244 +++ apps/web/src/admin/transforms.ts | 20 + .../src/admin/tutorial/components/Actions.vue | 43 + .../tutorial/components/ExerciseManager.vue | 652 ++++++++ apps/web/src/admin/tutorial/detail.vue | 133 ++ apps/web/src/admin/tutorial/list.vue | 107 ++ .../web/src/admin/user/components/Actions.vue | 55 + apps/web/src/admin/user/components/Name.vue | 42 + apps/web/src/admin/user/generate.vue | 107 ++ apps/web/src/admin/user/list.vue | 359 ++++ apps/web/src/env.d.ts | 16 + apps/web/src/index.css | 29 + apps/web/src/main.ts | 92 + apps/web/src/mermaid-legacy.d.ts | 1 + apps/web/src/oj/achievement/api.ts | 27 + .../components/AchievementCard.vue | 134 ++ apps/web/src/oj/achievement/index.vue | 227 +++ apps/web/src/oj/ai/analysis.vue | 122 ++ apps/web/src/oj/ai/components/AI.vue | 91 + .../oj/ai/components/DifficultyGradeChart.vue | 144 ++ .../src/oj/ai/components/DurationChart.vue | 204 +++ .../src/oj/ai/components/EfficiencyChart.vue | 234 +++ apps/web/src/oj/ai/components/Grade.vue | 53 + apps/web/src/oj/ai/components/Heatmap.vue | 251 +++ apps/web/src/oj/ai/components/Overview.vue | 63 + .../src/oj/ai/components/ProgressChart.vue | 271 +++ .../ai/components/RankDistributionChart.vue | 132 ++ apps/web/src/oj/ai/components/SolvedTable.vue | 154 ++ apps/web/src/oj/ai/components/StreakStats.vue | 177 ++ apps/web/src/oj/ai/components/TagTitle.vue | 21 + .../src/oj/ai/components/TagsRadarChart.vue | 154 ++ .../oj/ai/components/TimeActivityHeatmap.vue | 154 ++ .../announcement/components/TitleWithTag.vue | 14 + apps/web/src/oj/announcement/list.vue | 94 ++ apps/web/src/oj/api.ts | 435 +++++ apps/web/src/oj/class/pk.vue | 1260 ++++++++++++++ apps/web/src/oj/composables/syncStatus.ts | 73 + .../oj/contest/components/AcAndSubmission.vue | 25 + .../src/oj/contest/components/ContestInfo.vue | 42 + .../src/oj/contest/components/ContestMenu.vue | 56 + .../src/oj/contest/components/LineChart.vue | 231 +++ apps/web/src/oj/contest/detail.vue | 93 ++ apps/web/src/oj/contest/list.vue | 180 ++ apps/web/src/oj/contest/pages/problems.vue | 61 + apps/web/src/oj/contest/pages/rank.vue | 341 ++++ apps/web/src/oj/flowchart/index.vue | 7 + .../src/oj/learn/components/ExerciseDebug.vue | 121 ++ .../src/oj/learn/components/ExerciseFill.vue | 135 ++ .../src/oj/learn/components/ExerciseGroup.vue | 183 ++ .../src/oj/learn/components/ExerciseMatch.vue | 184 ++ .../src/oj/learn/components/ExerciseMcq.vue | 123 ++ .../oj/learn/components/ExercisePredict.vue | 92 + .../src/oj/learn/components/ExerciseSort.vue | 130 ++ .../oj/learn/components/ExerciseWidget.vue | 41 + .../learn/components/exercise-highlight.css | 50 + .../oj/learn/composables/useCodeHighlight.ts | 41 + .../oj/learn/composables/useExerciseParse.ts | 36 + .../src/oj/learn/composables/useShuffle.ts | 9 + apps/web/src/oj/learn/index.vue | 255 +++ .../oj/problem/components/ContestEditor.vue | 69 + .../oj/problem/components/EditorForTest.vue | 161 ++ apps/web/src/oj/problem/components/Form.vue | 291 ++++ .../oj/problem/components/MyFlowchartTab.vue | 42 + .../oj/problem/components/ProblemContent.vue | 550 ++++++ .../oj/problem/components/ProblemEditor.vue | 123 ++ .../problem/components/ProblemFlowchart.vue | 43 + .../src/oj/problem/components/ProblemInfo.vue | 177 ++ .../problem/components/ProblemListTitle.vue | 28 + .../oj/problem/components/ProblemReaction.vue | 698 ++++++++ .../oj/problem/components/ProblemStatus.vue | 24 + .../problem/components/ProblemSubmission.vue | 343 ++++ .../problem/components/ProblemYearlyChart.vue | 85 + .../oj/problem/components/SQLDataTable.vue | 60 + .../problem/components/SubmissionResult.vue | 237 +++ .../src/oj/problem/components/SubmitCode.vue | 275 +++ .../oj/problem/components/SubmitFlowchart.vue | 422 +++++ .../problem/components/submitButtonState.ts | 53 + .../oj/problem/composables/useFireworks.ts | 391 +++++ .../composables/useMermaidConverter.ts | 125 ++ .../composables/useSubmissionMonitor.ts | 190 +++ apps/web/src/oj/problem/detail.vue | 284 ++++ apps/web/src/oj/problem/list.vue | 342 ++++ .../src/oj/problem/utils/pythonSyntaxCheck.ts | 53 + .../components/ProblemSetHeader.vue | 109 ++ .../components/ProblemSetProblemsList.vue | 81 + .../components/UserProgressView.vue | 268 +++ apps/web/src/oj/problemset/detail.vue | 139 ++ apps/web/src/oj/problemset/list.vue | 274 +++ apps/web/src/oj/rank/components/Chart.vue | 114 ++ apps/web/src/oj/rank/components/Index.vue | 31 + apps/web/src/oj/rank/list.vue | 880 ++++++++++ apps/web/src/oj/store/ai.ts | 206 +++ apps/web/src/oj/store/code.ts | 94 ++ apps/web/src/oj/store/contest.ts | 116 ++ apps/web/src/oj/store/problem.ts | 34 + .../components/ButtonWithSearch.vue | 53 + .../submission/components/FlowchartLink.vue | 35 + .../components/FlowchartScoreDetail.vue | 226 +++ .../src/oj/submission/components/Grade.vue | 25 + .../submission/components/SubmissionLink.vue | 52 + apps/web/src/oj/submission/detail.vue | 168 ++ apps/web/src/oj/submission/list.vue | 573 +++++++ apps/web/src/oj/transforms.ts | 29 + apps/web/src/oj/user/index.vue | 331 ++++ apps/web/src/oj/user/message.vue | 71 + apps/web/src/oj/user/setting.vue | 76 + apps/web/src/routes.ts | 339 ++++ apps/web/src/shared/api.ts | 34 + .../src/shared/components/AchievementIcon.vue | 50 + .../shared/components/AchievementToast.vue | 101 ++ .../src/shared/components/AuthorSelect.vue | 35 + apps/web/src/shared/components/Beian.vue | 69 + apps/web/src/shared/components/CodeEditor.vue | 65 + .../src/shared/components/ContestTitle.vue | 18 + .../web/src/shared/components/ContestType.vue | 21 + apps/web/src/shared/components/Copy.vue | 35 + .../components/FlowchartEditor/CustomNode.vue | 275 +++ .../FlowchartEditor/NodeActions.vue | 60 + .../FlowchartEditor/NodeHandles.vue | 238 +++ .../components/FlowchartEditor/Toolbar.vue | 413 +++++ .../components/FlowchartEditor/index.vue | 325 ++++ .../components/FlowchartEditor/useCache.ts | 89 + .../components/FlowchartEditor/useDnD.ts | 84 + .../FlowchartEditor/useFlowOperations.ts | 142 ++ .../components/FlowchartEditor/useHistory.ts | 72 + .../FlowchartEditor/useNodeStyles.ts | 137 ++ .../components/FlowchartStatisticsPanel.vue | 577 +++++++ apps/web/src/shared/components/Header.vue | 356 ++++ apps/web/src/shared/components/Hitokoto.vue | 69 + apps/web/src/shared/components/IconButton.vue | 29 + apps/web/src/shared/components/Login.vue | 201 +++ .../shared/components/LoginSummaryModal.vue | 83 + .../src/shared/components/MarkdownEditor.vue | 40 + .../src/shared/components/MermaidEditor.vue | 93 ++ apps/web/src/shared/components/Pagination.vue | 61 + apps/web/src/shared/components/Signup.vue | 131 ++ .../src/shared/components/StatisticsPanel.vue | 470 ++++++ .../shared/components/SubmissionResultTag.vue | 17 + .../src/shared/components/SyncCodeEditor.vue | 153 ++ apps/web/src/shared/components/TextCopy.vue | 33 + apps/web/src/shared/components/TextEditor.vue | 144 ++ apps/web/src/shared/components/UserBadge.vue | 87 + .../web/src/shared/composables/breakpoints.ts | 20 + .../src/shared/composables/configUpdate.ts | 41 + .../src/shared/composables/learnProgress.ts | 15 + apps/web/src/shared/composables/maxkb.ts | 132 ++ apps/web/src/shared/composables/pagination.ts | 154 ++ apps/web/src/shared/composables/rarity.ts | 15 + apps/web/src/shared/composables/sync.ts | 412 +++++ apps/web/src/shared/composables/useMermaid.ts | 334 ++++ apps/web/src/shared/composables/websocket.ts | 544 ++++++ .../src/shared/extensions/autocompletion.ts | 107 ++ apps/web/src/shared/extensions/baseTheme.ts | 9 + apps/web/src/shared/extensions/c.ts | 257 +++ apps/web/src/shared/extensions/python.ts | 608 +++++++ apps/web/src/shared/extensions/sql.ts | 404 +++++ apps/web/src/shared/layout/admin.vue | 204 +++ apps/web/src/shared/layout/default.vue | 50 + apps/web/src/shared/store/achievement.ts | 64 + apps/web/src/shared/store/authModal.ts | 155 ++ apps/web/src/shared/store/config.ts | 24 + apps/web/src/shared/store/loginSummary.ts | 74 + apps/web/src/shared/store/myFlowchart.ts | 18 + apps/web/src/shared/store/screenMode.ts | 34 + apps/web/src/shared/store/user.ts | 94 ++ apps/web/src/shared/themes/createTheme.ts | 110 ++ apps/web/src/shared/themes/oneDark.ts | 149 ++ apps/web/src/shared/themes/smoothy.ts | 100 ++ apps/web/src/utils/constants.ts | 370 +++++ apps/web/src/utils/download.ts | 23 + apps/web/src/utils/functions.ts | 600 +++++++ apps/web/src/utils/http.ts | 79 + apps/web/src/utils/judge.ts | 37 + apps/web/src/utils/permissions.ts | 108 ++ apps/web/src/utils/renders.ts | 9 + apps/web/src/utils/storage.ts | 21 + apps/web/src/utils/stream.ts | 94 ++ apps/web/src/utils/types.ts | 790 +++++++++ apps/web/tests/submitButtonState.test.ts | 51 + apps/web/tsconfig.app.json | 23 + apps/web/tsconfig.json | 7 + apps/web/tsconfig.node.json | 24 + apps/web/vite.config.ts | 175 ++ bun.lock | 1476 ++++++++++++++++- 258 files changed, 40490 insertions(+), 91 deletions(-) create mode 100644 apps/web/.browserslistrc create mode 100644 apps/web/.env.production create mode 100644 apps/web/.env.staging create mode 100644 apps/web/.env.test create mode 100644 apps/web/.github/workflows/deploy.yml create mode 100644 apps/web/.gitignore create mode 100644 apps/web/.prettierrc.toml create mode 100644 apps/web/CLAUDE.md create mode 100644 apps/web/README.md create mode 100644 apps/web/docs/API接口对比分析.md create mode 100644 apps/web/docs/FlowchartEditor.md create mode 100644 apps/web/docs/superpowers/plans/2026-06-14-submit-formatting-button-state.md create mode 100644 apps/web/docs/superpowers/plans/2026-07-26-demo-student-view.md create mode 100644 apps/web/docs/superpowers/specs/2026-06-14-submit-formatting-button-state-design.md create mode 100644 apps/web/docs/superpowers/specs/2026-07-05-sql-min-two-testcases-design.md create mode 100644 apps/web/docs/superpowers/specs/2026-07-05-sql-schema-autocompletion-design.md create mode 100644 apps/web/docs/superpowers/specs/2026-07-26-demo-student-view-design.md create mode 100644 apps/web/docs/图表.md create mode 100644 apps/web/index.html create mode 100644 apps/web/package.json create mode 100644 apps/web/public/A.png create mode 100644 apps/web/public/B.png create mode 100644 apps/web/public/C.png create mode 100644 apps/web/public/Monaco.ttf create mode 100644 apps/web/public/S.png create mode 100644 apps/web/public/badge-1.png create mode 100644 apps/web/public/badge-2.png create mode 100644 apps/web/public/badge-3.png create mode 100644 apps/web/public/badge-4.png create mode 100644 apps/web/public/badge-5.png create mode 100644 apps/web/public/badge-6.png create mode 100644 apps/web/public/favicon.ico create mode 100644 apps/web/public/noto--dog-face.ico create mode 100644 apps/web/public/style.css create mode 100644 apps/web/src/App.vue create mode 100644 apps/web/src/admin/achievement/components/AchievementModal.vue create mode 100644 apps/web/src/admin/achievement/list.vue create mode 100644 apps/web/src/admin/ai/list.vue create mode 100644 apps/web/src/admin/announcement/components/Actions.vue create mode 100644 apps/web/src/admin/announcement/detail.vue create mode 100644 apps/web/src/admin/announcement/list.vue create mode 100644 apps/web/src/admin/api.ts create mode 100644 apps/web/src/admin/communication/messages.vue create mode 100644 apps/web/src/admin/contest/components/Actions.vue create mode 100644 apps/web/src/admin/contest/detail.vue create mode 100644 apps/web/src/admin/contest/helper.vue create mode 100644 apps/web/src/admin/contest/list.vue create mode 100644 apps/web/src/admin/problem/Stuck.vue create mode 100644 apps/web/src/admin/problem/TopACTrend.vue create mode 100644 apps/web/src/admin/problem/components/Actions.vue create mode 100644 apps/web/src/admin/problem/components/AddButton.vue create mode 100644 apps/web/src/admin/problem/components/AstRulesEditor.vue create mode 100644 apps/web/src/admin/problem/components/BatchTagModal.vue create mode 100644 apps/web/src/admin/problem/components/Modal.vue create mode 100644 apps/web/src/admin/problem/components/SQLTestcaseEditor.vue create mode 100644 apps/web/src/admin/problem/components/TagProblemsModal.vue create mode 100644 apps/web/src/admin/problem/components/TestcaseGenerator.vue create mode 100644 apps/web/src/admin/problem/detail.vue create mode 100644 apps/web/src/admin/problem/list.vue create mode 100644 apps/web/src/admin/problem/tags.vue create mode 100644 apps/web/src/admin/problemset/components/Actions.vue create mode 100644 apps/web/src/admin/problemset/components/AddBadgeModal.vue create mode 100644 apps/web/src/admin/problemset/components/AddProblemModal.vue create mode 100644 apps/web/src/admin/problemset/components/BadgeManagement.vue create mode 100644 apps/web/src/admin/problemset/components/EditBadgeModal.vue create mode 100644 apps/web/src/admin/problemset/components/EditProblemModal.vue create mode 100644 apps/web/src/admin/problemset/components/ProblemManagement.vue create mode 100644 apps/web/src/admin/problemset/components/ProblemSetInfo.vue create mode 100644 apps/web/src/admin/problemset/components/ProgressManagement.vue create mode 100644 apps/web/src/admin/problemset/detail.vue create mode 100644 apps/web/src/admin/problemset/edit.vue create mode 100644 apps/web/src/admin/problemset/list.vue create mode 100644 apps/web/src/admin/setting/config.vue create mode 100644 apps/web/src/admin/setting/home.vue create mode 100644 apps/web/src/admin/transforms.ts create mode 100644 apps/web/src/admin/tutorial/components/Actions.vue create mode 100644 apps/web/src/admin/tutorial/components/ExerciseManager.vue create mode 100644 apps/web/src/admin/tutorial/detail.vue create mode 100644 apps/web/src/admin/tutorial/list.vue create mode 100644 apps/web/src/admin/user/components/Actions.vue create mode 100644 apps/web/src/admin/user/components/Name.vue create mode 100644 apps/web/src/admin/user/generate.vue create mode 100644 apps/web/src/admin/user/list.vue create mode 100644 apps/web/src/env.d.ts create mode 100644 apps/web/src/index.css create mode 100644 apps/web/src/main.ts create mode 100644 apps/web/src/mermaid-legacy.d.ts create mode 100644 apps/web/src/oj/achievement/api.ts create mode 100644 apps/web/src/oj/achievement/components/AchievementCard.vue create mode 100644 apps/web/src/oj/achievement/index.vue create mode 100644 apps/web/src/oj/ai/analysis.vue create mode 100644 apps/web/src/oj/ai/components/AI.vue create mode 100644 apps/web/src/oj/ai/components/DifficultyGradeChart.vue create mode 100644 apps/web/src/oj/ai/components/DurationChart.vue create mode 100644 apps/web/src/oj/ai/components/EfficiencyChart.vue create mode 100644 apps/web/src/oj/ai/components/Grade.vue create mode 100644 apps/web/src/oj/ai/components/Heatmap.vue create mode 100644 apps/web/src/oj/ai/components/Overview.vue create mode 100644 apps/web/src/oj/ai/components/ProgressChart.vue create mode 100644 apps/web/src/oj/ai/components/RankDistributionChart.vue create mode 100644 apps/web/src/oj/ai/components/SolvedTable.vue create mode 100644 apps/web/src/oj/ai/components/StreakStats.vue create mode 100644 apps/web/src/oj/ai/components/TagTitle.vue create mode 100644 apps/web/src/oj/ai/components/TagsRadarChart.vue create mode 100644 apps/web/src/oj/ai/components/TimeActivityHeatmap.vue create mode 100644 apps/web/src/oj/announcement/components/TitleWithTag.vue create mode 100644 apps/web/src/oj/announcement/list.vue create mode 100644 apps/web/src/oj/api.ts create mode 100644 apps/web/src/oj/class/pk.vue create mode 100644 apps/web/src/oj/composables/syncStatus.ts create mode 100644 apps/web/src/oj/contest/components/AcAndSubmission.vue create mode 100644 apps/web/src/oj/contest/components/ContestInfo.vue create mode 100644 apps/web/src/oj/contest/components/ContestMenu.vue create mode 100644 apps/web/src/oj/contest/components/LineChart.vue create mode 100644 apps/web/src/oj/contest/detail.vue create mode 100644 apps/web/src/oj/contest/list.vue create mode 100644 apps/web/src/oj/contest/pages/problems.vue create mode 100644 apps/web/src/oj/contest/pages/rank.vue create mode 100644 apps/web/src/oj/flowchart/index.vue create mode 100644 apps/web/src/oj/learn/components/ExerciseDebug.vue create mode 100644 apps/web/src/oj/learn/components/ExerciseFill.vue create mode 100644 apps/web/src/oj/learn/components/ExerciseGroup.vue create mode 100644 apps/web/src/oj/learn/components/ExerciseMatch.vue create mode 100644 apps/web/src/oj/learn/components/ExerciseMcq.vue create mode 100644 apps/web/src/oj/learn/components/ExercisePredict.vue create mode 100644 apps/web/src/oj/learn/components/ExerciseSort.vue create mode 100644 apps/web/src/oj/learn/components/ExerciseWidget.vue create mode 100644 apps/web/src/oj/learn/components/exercise-highlight.css create mode 100644 apps/web/src/oj/learn/composables/useCodeHighlight.ts create mode 100644 apps/web/src/oj/learn/composables/useExerciseParse.ts create mode 100644 apps/web/src/oj/learn/composables/useShuffle.ts create mode 100644 apps/web/src/oj/learn/index.vue create mode 100644 apps/web/src/oj/problem/components/ContestEditor.vue create mode 100644 apps/web/src/oj/problem/components/EditorForTest.vue create mode 100644 apps/web/src/oj/problem/components/Form.vue create mode 100644 apps/web/src/oj/problem/components/MyFlowchartTab.vue create mode 100644 apps/web/src/oj/problem/components/ProblemContent.vue create mode 100644 apps/web/src/oj/problem/components/ProblemEditor.vue create mode 100644 apps/web/src/oj/problem/components/ProblemFlowchart.vue create mode 100644 apps/web/src/oj/problem/components/ProblemInfo.vue create mode 100644 apps/web/src/oj/problem/components/ProblemListTitle.vue create mode 100644 apps/web/src/oj/problem/components/ProblemReaction.vue create mode 100644 apps/web/src/oj/problem/components/ProblemStatus.vue create mode 100644 apps/web/src/oj/problem/components/ProblemSubmission.vue create mode 100644 apps/web/src/oj/problem/components/ProblemYearlyChart.vue create mode 100644 apps/web/src/oj/problem/components/SQLDataTable.vue create mode 100644 apps/web/src/oj/problem/components/SubmissionResult.vue create mode 100644 apps/web/src/oj/problem/components/SubmitCode.vue create mode 100644 apps/web/src/oj/problem/components/SubmitFlowchart.vue create mode 100644 apps/web/src/oj/problem/components/submitButtonState.ts create mode 100644 apps/web/src/oj/problem/composables/useFireworks.ts create mode 100644 apps/web/src/oj/problem/composables/useMermaidConverter.ts create mode 100644 apps/web/src/oj/problem/composables/useSubmissionMonitor.ts create mode 100644 apps/web/src/oj/problem/detail.vue create mode 100644 apps/web/src/oj/problem/list.vue create mode 100644 apps/web/src/oj/problem/utils/pythonSyntaxCheck.ts create mode 100644 apps/web/src/oj/problemset/components/ProblemSetHeader.vue create mode 100644 apps/web/src/oj/problemset/components/ProblemSetProblemsList.vue create mode 100644 apps/web/src/oj/problemset/components/UserProgressView.vue create mode 100644 apps/web/src/oj/problemset/detail.vue create mode 100644 apps/web/src/oj/problemset/list.vue create mode 100644 apps/web/src/oj/rank/components/Chart.vue create mode 100644 apps/web/src/oj/rank/components/Index.vue create mode 100644 apps/web/src/oj/rank/list.vue create mode 100644 apps/web/src/oj/store/ai.ts create mode 100644 apps/web/src/oj/store/code.ts create mode 100644 apps/web/src/oj/store/contest.ts create mode 100644 apps/web/src/oj/store/problem.ts create mode 100644 apps/web/src/oj/submission/components/ButtonWithSearch.vue create mode 100644 apps/web/src/oj/submission/components/FlowchartLink.vue create mode 100644 apps/web/src/oj/submission/components/FlowchartScoreDetail.vue create mode 100644 apps/web/src/oj/submission/components/Grade.vue create mode 100644 apps/web/src/oj/submission/components/SubmissionLink.vue create mode 100644 apps/web/src/oj/submission/detail.vue create mode 100644 apps/web/src/oj/submission/list.vue create mode 100644 apps/web/src/oj/transforms.ts create mode 100644 apps/web/src/oj/user/index.vue create mode 100644 apps/web/src/oj/user/message.vue create mode 100644 apps/web/src/oj/user/setting.vue create mode 100644 apps/web/src/routes.ts create mode 100644 apps/web/src/shared/api.ts create mode 100644 apps/web/src/shared/components/AchievementIcon.vue create mode 100644 apps/web/src/shared/components/AchievementToast.vue create mode 100644 apps/web/src/shared/components/AuthorSelect.vue create mode 100644 apps/web/src/shared/components/Beian.vue create mode 100644 apps/web/src/shared/components/CodeEditor.vue create mode 100644 apps/web/src/shared/components/ContestTitle.vue create mode 100644 apps/web/src/shared/components/ContestType.vue create mode 100644 apps/web/src/shared/components/Copy.vue create mode 100644 apps/web/src/shared/components/FlowchartEditor/CustomNode.vue create mode 100644 apps/web/src/shared/components/FlowchartEditor/NodeActions.vue create mode 100644 apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue create mode 100644 apps/web/src/shared/components/FlowchartEditor/Toolbar.vue create mode 100644 apps/web/src/shared/components/FlowchartEditor/index.vue create mode 100644 apps/web/src/shared/components/FlowchartEditor/useCache.ts create mode 100644 apps/web/src/shared/components/FlowchartEditor/useDnD.ts create mode 100644 apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts create mode 100644 apps/web/src/shared/components/FlowchartEditor/useHistory.ts create mode 100644 apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts create mode 100644 apps/web/src/shared/components/FlowchartStatisticsPanel.vue create mode 100644 apps/web/src/shared/components/Header.vue create mode 100644 apps/web/src/shared/components/Hitokoto.vue create mode 100644 apps/web/src/shared/components/IconButton.vue create mode 100644 apps/web/src/shared/components/Login.vue create mode 100644 apps/web/src/shared/components/LoginSummaryModal.vue create mode 100644 apps/web/src/shared/components/MarkdownEditor.vue create mode 100644 apps/web/src/shared/components/MermaidEditor.vue create mode 100644 apps/web/src/shared/components/Pagination.vue create mode 100644 apps/web/src/shared/components/Signup.vue create mode 100644 apps/web/src/shared/components/StatisticsPanel.vue create mode 100644 apps/web/src/shared/components/SubmissionResultTag.vue create mode 100644 apps/web/src/shared/components/SyncCodeEditor.vue create mode 100644 apps/web/src/shared/components/TextCopy.vue create mode 100644 apps/web/src/shared/components/TextEditor.vue create mode 100644 apps/web/src/shared/components/UserBadge.vue create mode 100644 apps/web/src/shared/composables/breakpoints.ts create mode 100644 apps/web/src/shared/composables/configUpdate.ts create mode 100644 apps/web/src/shared/composables/learnProgress.ts create mode 100644 apps/web/src/shared/composables/maxkb.ts create mode 100644 apps/web/src/shared/composables/pagination.ts create mode 100644 apps/web/src/shared/composables/rarity.ts create mode 100644 apps/web/src/shared/composables/sync.ts create mode 100644 apps/web/src/shared/composables/useMermaid.ts create mode 100644 apps/web/src/shared/composables/websocket.ts create mode 100644 apps/web/src/shared/extensions/autocompletion.ts create mode 100644 apps/web/src/shared/extensions/baseTheme.ts create mode 100644 apps/web/src/shared/extensions/c.ts create mode 100644 apps/web/src/shared/extensions/python.ts create mode 100644 apps/web/src/shared/extensions/sql.ts create mode 100644 apps/web/src/shared/layout/admin.vue create mode 100644 apps/web/src/shared/layout/default.vue create mode 100644 apps/web/src/shared/store/achievement.ts create mode 100644 apps/web/src/shared/store/authModal.ts create mode 100644 apps/web/src/shared/store/config.ts create mode 100644 apps/web/src/shared/store/loginSummary.ts create mode 100644 apps/web/src/shared/store/myFlowchart.ts create mode 100644 apps/web/src/shared/store/screenMode.ts create mode 100644 apps/web/src/shared/store/user.ts create mode 100644 apps/web/src/shared/themes/createTheme.ts create mode 100644 apps/web/src/shared/themes/oneDark.ts create mode 100644 apps/web/src/shared/themes/smoothy.ts create mode 100644 apps/web/src/utils/constants.ts create mode 100644 apps/web/src/utils/download.ts create mode 100644 apps/web/src/utils/functions.ts create mode 100644 apps/web/src/utils/http.ts create mode 100644 apps/web/src/utils/judge.ts create mode 100644 apps/web/src/utils/permissions.ts create mode 100644 apps/web/src/utils/renders.ts create mode 100644 apps/web/src/utils/storage.ts create mode 100644 apps/web/src/utils/stream.ts create mode 100644 apps/web/src/utils/types.ts create mode 100644 apps/web/tests/submitButtonState.test.ts create mode 100644 apps/web/tsconfig.app.json create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/tsconfig.node.json create mode 100644 apps/web/vite.config.ts diff --git a/.gitignore b/.gitignore index 274dc5b..2561b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ build/ .env .env.* !.env.example +!apps/web/.env.production +!apps/web/.env.staging +!apps/web/.env.test .DS_Store .vscode/ diff --git a/apps/web/.browserslistrc b/apps/web/.browserslistrc new file mode 100644 index 0000000..21e47a5 --- /dev/null +++ b/apps/web/.browserslistrc @@ -0,0 +1 @@ +chrome >= 90 diff --git a/apps/web/.env.production b/apps/web/.env.production new file mode 100644 index 0000000..c19eedf --- /dev/null +++ b/apps/web/.env.production @@ -0,0 +1,8 @@ +PUBLIC_ENV=xuyue.cc +PUBLIC_MAXKB_URL=https://maxkb.xuyue.cc/chat/api/embed?protocol=https&host=maxkb.xuyue.cc&token=dd37457027c40b39 +PUBLIC_OJ_URL=https://oj.xuyue.cc +PUBLIC_CODE_URL=https://code.xuyue.cc +PUBLIC_JUDGE0_URL=https://judge0api.xuyue.cc +PUBLIC_SIGNALING_URL=wss://signaling.xuyue.cc +PUBLIC_WS_URL=wss://oj.xuyue.cc/ws +PUBLIC_ICONIFY_URL=https://icon.xuyue.cc \ No newline at end of file diff --git a/apps/web/.env.staging b/apps/web/.env.staging new file mode 100644 index 0000000..7916890 --- /dev/null +++ b/apps/web/.env.staging @@ -0,0 +1,8 @@ +PUBLIC_ENV=school +PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13.114.114:92&token=dd37457027c40b39 +PUBLIC_OJ_URL=http://10.13.114.114:81 +PUBLIC_CODE_URL=http://10.13.114.114:82 +PUBLIC_JUDGE0_URL=http://10.13.114.114:8082 +PUBLIC_ICONIFY_URL=http://10.13.114.114:8098 +PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085 +PUBLIC_WS_URL=ws://10.13.114.114:81/ws \ No newline at end of file diff --git a/apps/web/.env.test b/apps/web/.env.test new file mode 100644 index 0000000..cf7dc87 --- /dev/null +++ b/apps/web/.env.test @@ -0,0 +1,8 @@ +PUBLIC_ENV=test +PUBLIC_MAXKB_URL=http://10.13.114.114:92/chat/api/embed?protocol=http&host=10.13.114.114:92&token=dd37457027c40b39 +PUBLIC_OJ_URL=http://10.13.114.114:93 +PUBLIC_CODE_URL=http://10.13.114.114:82 +PUBLIC_JUDGE0_URL=http://10.13.114.114:8082 +PUBLIC_ICONIFY_URL=http://10.13.114.114:8098 +PUBLIC_SIGNALING_URL=ws://10.13.114.114:8085 +PUBLIC_WS_URL=ws://10.13.114.114:93/ws \ No newline at end of file diff --git a/apps/web/.github/workflows/deploy.yml b/apps/web/.github/workflows/deploy.yml new file mode 100644 index 0000000..79cfa2c --- /dev/null +++ b/apps/web/.github/workflows/deploy.yml @@ -0,0 +1,46 @@ +name: Deploy + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: debian + build_command: build + remote_port: 22 + target: /root/OJDeploy/data/clientnext + - name: school + build_command: build:staging + remote_port: 8822 + target: /root/OJ/data/dist + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + - run: npm install + - run: npm run ${{ matrix.build_command }} + env: + CI: false + + - uses: easingthemes/ssh-deploy@main + with: + SSH_PRIVATE_KEY: ${{ secrets.KEY }} + REMOTE_HOST: ${{ secrets.HOST }} + REMOTE_PORT: ${{ matrix.remote_port }} + ARGS: "-avzr --delete" + SOURCE: dist/ + REMOTE_USER: root + TARGET: ${{ matrix.target }} \ No newline at end of file diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 0000000..e0280c2 --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +src/components.d.ts +src/auto-imports.d.ts + +.claude +.worktrees \ No newline at end of file diff --git a/apps/web/.prettierrc.toml b/apps/web/.prettierrc.toml new file mode 100644 index 0000000..d165b8d --- /dev/null +++ b/apps/web/.prettierrc.toml @@ -0,0 +1 @@ +semi=false \ No newline at end of file diff --git a/apps/web/CLAUDE.md b/apps/web/CLAUDE.md new file mode 100644 index 0000000..7efe1f5 --- /dev/null +++ b/apps/web/CLAUDE.md @@ -0,0 +1,114 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**ojnext** is the frontend for an Online Judge platform. Built with Vue 3 + TypeScript using Vite (Rolldown-based bundler), Naive UI component library, Pinia for state management, and Vue Router. + +## Commands + +```bash +npm start # Start dev server on port 5173 +npm run build # Production build +npm run build:staging # Staging build +npm run build:test # Test build +npm fmt # Format with Prettier +``` + +No test suite is configured. Linting is via Prettier only. + +## Architecture + +### Directory Structure + +``` +src/ +├── shared/ # Cross-cutting concerns: layout, stores, composables, API +├── oj/ # User-facing features (problems, submissions, contests, etc.) +├── admin/ # Admin panel features +├── utils/ # Constants, types, HTTP client, helpers +├── routes.ts # Route definitions (two top-level: ojs, admins) +├── main.ts # App entry point +└── App.vue # Root component with Naive UI theme setup +``` + +### Module Pattern + +Each feature module (under `oj/` or `admin/`) typically has: +- `views/` — page-level Vue components +- `components/` — feature-specific components +- `api.ts` — API calls specific to the feature + +Shared logic lives in `shared/`: +- `store/` — Pinia stores: `user` (auth/roles), `config` (site-wide settings), `authModal` (login/signup form state), `screenMode` (problem split-screen layout), `loginSummary` (AI activity summary) +- `composables/` — `pagination` (URL-synced), `websocket` (reconnect + heartbeat), `sync` (Yjs/y-webrtc for collaborative editing), `configUpdate` (WS-pushed config sync), `useMermaid` (lazy Mermaid render), `breakpoints`, `maxkb` +- `layout/` — `default.vue` and `admin.vue` layout wrappers +- `api.ts` — shared API calls (auth, profile, tags, captcha) + +### Auto-Imports + +Configured via `unplugin-auto-import` and `unplugin-vue-components`. You do **not** need to manually import: +- Vue APIs (`ref`, `computed`, `watch`, etc.) +- Vue Router (`useRouter`, `useRoute`) +- Pinia (`defineStore`, `storeToRefs`) +- VueUse composables +- Naive UI composables (`useDialog`, `useMessage`, `useNotification`, `useLoadingBar`) +- Naive UI components (all `N*` components) +- Naive UI types (`DataTableColumn`, `FormRules`, `FormItemRule`, `SelectOption`, `UploadCustomRequestOptions`, `UploadFileInfo`, `MenuOption`, `DropdownOption`) + +Generated type declaration files: `src/auto-imports.d.ts`, `src/components.d.ts`. + +### Path Aliases + +``` +utils → ./src/utils +oj → ./src/oj +admin → ./src/admin +shared → ./src/shared +``` + +### HTTP Client + +`utils/http.ts` — Axios instance with interceptors. All API calls proxy through the dev server: +- `/api` and `/public` → `PUBLIC_OJ_URL` (backend) +- `/ws` → `PUBLIC_WS_URL` (WebSocket backend) + +### Key Utilities + +- `utils/constants.ts` — Judge status codes, language IDs, difficulty levels, contest types +- `utils/types.ts` — TypeScript interfaces for all domain models +- `utils/permissions.ts` — Permission check helpers +- `utils/judge.ts` — Judge-related utilities +- `utils/renders.ts` — Table column render helpers for Naive UI DataTable + +### Environment Variables + +Variables prefixed with `PUBLIC_` are injected at build time. Env files: `.env`, `.env.staging`, `.env.test`. + +| Variable | Purpose | +|---|---| +| `PUBLIC_OJ_URL` | Backend REST API base URL | +| `PUBLIC_WS_URL` | WebSocket server URL | +| `PUBLIC_ENV` | Environment name (dev/staging/production) | +| `PUBLIC_CODE_URL` | Code execution service | +| `PUBLIC_JUDGE0_URL` | Judge0 API | +| `PUBLIC_MAXKB_URL` | Knowledge base service | +| `PUBLIC_SIGNALING_URL` | WebRTC signaling server | +| `PUBLIC_ICONIFY_URL` | Iconify icon CDN | + +### Routing + +Routes are defined in `src/routes.ts` with two root routes: `ojs` (user-facing) and `admins` (admin panel). Route meta fields used: +- `requiresAuth` — redirect to login if not authenticated +- `requiresSuperAdmin` — super admin only +- `requiresProblemPermission` — problem management access + +### Real-time Features + +- WebSocket via composable in `shared/composables/` for submission status updates +- Yjs + y-webrtc for collaborative editing in the flowchart editor + +## Related Repository + +The backend is at `../OnlineJudge` — a Django 5 + DRF project. See its CLAUDE.md for backend details. diff --git a/apps/web/README.md b/apps/web/README.md new file mode 100644 index 0000000..b18748c --- /dev/null +++ b/apps/web/README.md @@ -0,0 +1 @@ +oj.xuyue.cc \ No newline at end of file diff --git a/apps/web/docs/API接口对比分析.md b/apps/web/docs/API接口对比分析.md new file mode 100644 index 0000000..2a0cb71 --- /dev/null +++ b/apps/web/docs/API接口对比分析.md @@ -0,0 +1,548 @@ +# API接口对比分析 + +## 更新日志 + +### 最近更新(2025-01-15) +- ✨ **FlowchartEditor 流程图编辑器**:新增完整的流程图编辑功能 + - 基于 Vue Flow 构建的流程图编辑器组件 + - 支持7种节点类型:开始、输入、处理、判断、循环、输出、结束 + - 完整的拖拽创建、节点连接、编辑功能 + - 撤销重做、自动保存、键盘快捷键支持 + - 模块化设计,包含9个独立的功能模块 +- 🎨 **前端组件优化**:完善了组件文档和项目结构说明 + - 更新了 README.md 中的技术栈和项目结构 + - 创建了详细的 FlowchartEditor 使用文档 + - 优化了开发指南和构建流程说明 +- 🔧 **构建工具升级**:从 Vite 迁移到 Rsbuild + - 使用 Rsbuild 作为新的构建工具 + - 支持多环境构建(test、staging、production) + - 优化了构建性能和开发体验 + +### 历史更新(2025-10) +- ✨ **AI分析功能增强**:完善了AI智能分析模块的文档说明 + - 详细说明了4个AI相关接口的功能和参数 + - 新增等级系统说明(S/A/B/C),包含特殊规则 + - 补充了时间范围选择功能 + - 说明了流式响应的实现方式 + - 前端组件从 `WeeklyChart.vue` 升级为 `DurationChart.vue`(混合图表) +- 🔧 **数据缓存优化**:后端AI接口增加了缓存机制,提升性能 +- 🐛 **修正等级系统说明**:更正了等级阈值(A级:前35%,B级:前75%),并补充了小规模参与惩罚规则 + +--- + +## 一、前端已使用的API接口 + +### 1. 用户认证相关(shared/api.ts) +- `POST /api/login` - 用户登录 +- `POST /api/register` - 用户注册 +- `GET /api/logout` - 用户登出 +- `GET /api/profile` - 获取用户资料 +- `GET /api/captcha` - 获取验证码 + +### 2. OJ普通用户API(oj/api.ts) +#### 2.1 网站配置 +- `GET /api/website` - 获取网站配置 +- `GET /api/hitokoto` - 获取一言 + +#### 2.2 题目相关 +- `GET /api/problem` - 获取题目列表 +- `GET /api/problem` - 获取单个题目 +- `GET /api/problem/tags` - 获取题目标签列表 +- `GET /api/problem/author` - 获取题目作者列表 +- `GET /api/problem/beat_count` - 获取题目击败率 +- `GET /api/pickone` - 随机获取题目 +- `GET /api/contest/problem` - 获取竞赛题目 + +#### 2.3 提交相关 +- `GET /api/submission` - 获取单个提交 +- `POST /api/submission` - 提交代码 +- `GET /api/submissions` - 获取提交列表 +- `GET /api/submissions/today_count` - 获取今日提交数 +- `GET /api/contest_submissions` - 获取竞赛提交列表 + +#### 2.4 排名相关 +- `GET /api/user_rank` - 获取用户排名 +- `GET /api/user_activity_rank` - 获取活跃度排名 +- `GET /api/user_problem_rank` - 获取题目排名 +- `GET /api/contest_rank` - 获取竞赛排名 + +#### 2.5 竞赛相关 +- `GET /api/contests` - 获取竞赛列表 +- `GET /api/contest` - 获取单个竞赛 +- `GET /api/contest/access` - 获取竞赛访问权限 +- `POST /api/contest/password` - 验证竞赛密码 + +#### 2.6 公告相关 +- `GET /api/announcement` - 获取公告列表/单个公告 + +#### 2.7 消息相关 +- `POST /api/message` - 创建消息 +- `GET /api/message` - 获取消息列表 + +#### 2.8 评论相关 +- `POST /api/comment` - 创建评论 +- `GET /api/comment` - 获取评论 +- `GET /api/comment/statistics` - 获取评论统计 + +#### 2.9 用户相关 +- `POST /api/upload_avatar` - 上传头像 +- `PUT /api/profile` - 更新用户资料 +- `GET /api/profile/fresh_display_id` - 刷新用户题目显示ID +- `GET /api/metrics` - 获取用户统计数据 + +#### 2.10 教程相关 +- `GET /api/tutorial` - 获取单个教程 +- `GET /api/tutorials` - 获取教程列表 + +#### 2.11 AI分析相关 +- `GET /api/ai/detail` - 获取用户详细数据 + - **参数**: start, end(时间范围) + - **返回**: 用户等级(S/A/B/C)、已解决题目列表、标签统计、难度统计、参赛次数等 + - **特点**: 包含班级排名对比,计算每道题的解题排名和等级 +- `GET /api/ai/duration` - 获取时段数据 + - **参数**: end(结束时间), duration(时间单位,如 "months:6", "weeks:1") + - **返回**: 每周/每月的综合情况(题目数、提交数、等级) + - **用途**: 用于绘制时间趋势图,展示学习进度变化 +- `GET /api/ai/heatmap` - 获取热力图数据 + - **返回**: 用户的提交热力图数据(按日期统计提交数) + - **用途**: 可视化用户活跃度分布 +- `POST /api/ai/analysis` - AI智能分析生成 + - **请求体**: details(详细数据), duration(时段数据) + - **响应方式**: 流式响应(Server-Sent Events) + - **AI提供商**: DeepSeek + - **功能**: 根据用户学习数据生成个性化学习建议和鼓励 + - **实现**: 使用fetch直接调用,在 `oj/store/ai.ts` 中处理流式输出 + +### 3. 管理员API(admin/api.ts) +#### 3.1 仪表板 +- `GET /api/admin/dashboard_info` - 获取仪表板信息 +- `GET /api/admin/random_user` - 随机获取用户 + +#### 3.2 题目管理 +- `GET /api/admin/problem` - 获取题目列表/单个题目 +- `POST /api/admin/problem` - 创建题目 +- `PUT /api/admin/problem` - 编辑题目 +- `PUT /api/admin/problem/visible` - 切换题目可见性 +- `DELETE /api/admin/problem` - 删除题目 +- `GET /api/admin/contest/problem` - 获取竞赛题目 +- `POST /api/admin/contest/problem` - 创建竞赛题目 +- `PUT /api/admin/contest/problem` - 编辑竞赛题目 +- `DELETE /api/admin/contest/problem` - 删除竞赛题目 +- `POST /api/admin/contest/add_problem_from_public` - 从公开题库添加题目到竞赛 + +#### 3.3 用户管理 +- `GET /api/admin/user` - 获取用户列表 +- `POST /api/admin/user` - 导入用户 +- `PUT /api/admin/user` - 编辑用户 +- `DELETE /api/admin/user` - 删除用户 +- `POST /api/admin/reset_password` - 重置用户密码 + +#### 3.4 竞赛管理 +- `GET /api/admin/contest` - 获取竞赛列表/单个竞赛 +- `POST /api/admin/contest` - 创建竞赛 +- `PUT /api/admin/contest` - 编辑竞赛 +- `GET /api/admin/contest/acm_helper` - 获取ACM比赛辅助检查列表 +- `PUT /api/admin/contest/acm_helper` - 更新ACM比赛辅助检查状态 + +#### 3.5 测试用例管理 +- `POST /api/admin/test_case` - 上传测试用例 +- `GET /api/admin/prune_test_case` - 列出无效测试用例 +- `DELETE /api/admin/prune_test_case` - 清理无效测试用例 + +#### 3.6 题目管理扩展 +- `POST /api/admin/contest_problem/make_public` - 将竞赛题目转为公开题目 + +#### 3.7 判题服务器管理 +- `GET /api/admin/judge_server` - 获取判题服务器列表 +- `DELETE /api/admin/judge_server` - 删除判题服务器 + +#### 3.8 公告管理 +- `GET /api/admin/announcement` - 获取公告列表/单个公告 +- `POST /api/admin/announcement` - 创建公告 +- `PUT /api/admin/announcement` - 编辑公告 +- `DELETE /api/admin/announcement` - 删除公告 + +#### 3.9 评论管理 +- `GET /api/admin/comment` - 获取评论列表 +- `DELETE /api/admin/comment` - 删除评论 + +#### 3.10 网站配置 +- `GET /api/admin/website` - 获取网站配置 +- `POST /api/admin/website` - 更新网站配置 + +#### 3.11 文件上传 +- `POST /api/admin/upload_image` - 上传图片(富文本编辑器、Markdown编辑器使用) + +#### 3.12 提交管理 +- `GET /api/admin/submission/rejudge` - 重新判题 +- `GET /api/admin/submission/statistics` - 获取提交统计 + +#### 3.13 教程管理 +- `GET /api/admin/tutorial` - 获取教程列表/单个教程 +- `POST /api/admin/tutorial` - 创建教程 +- `PUT /api/admin/tutorial` - 更新教程 +- `DELETE /api/admin/tutorial` - 删除教程 +- `PUT /api/admin/tutorial/visibility` - 设置教程可见性 + +--- + +## 二、后端提供但前端未使用的API接口 + +### 1. 用户认证相关(account) +- `POST /api/change_password` - 修改密码 +- `POST /api/change_email` - 修改邮箱 +- `POST /api/apply_reset_password` - 申请重置密码 +- `POST /api/reset_password` - 重置密码 +- `GET /api/check_username_or_email` - 检查用户名或邮箱是否存在 +- `GET /api/tfa_required` - 检查是否需要双因素认证 +- `POST /api/two_factor_auth` - 双因素认证 +- `GET /api/sessions` - 会话管理 +- `GET /api/open_api_appkey` - OpenAPI密钥管理 +- `GET /api/sso` - 单点登录 + +### 2. 用户管理(admin) +- `GET /api/admin/generate_user` - 生成用户 + +### 3. 网站配置相关 +- `GET /api/languages` - 获取支持的编程语言列表 + +### 4. 判题服务器内部接口(不需要前端实现) +- ❌ `POST /api/judge_server_heartbeat/` - 判题服务器心跳 + - **标记为不需要** + - **原因**: 此接口由 JudgeServer Docker 容器调用,用于向后端报告服务器状态 + - **使用方**: 判题服务器(非前端) + - **数据内容**: hostname, judger_version, CPU/内存使用率等 + - **认证方式**: 使用特殊的 judge_server_token,非用户认证 + +### 5. 管理员配置相关 +- `GET /api/admin/smtp` - SMTP配置 +- `POST /api/admin/smtp_test` - SMTP测试 +- `GET /api/admin/versions` - 版本信息 + +### 6. 题目管理相关(admin) +- `POST /api/admin/export_problem` - 导出题目 +- `POST /api/admin/import_problem` - 导入题目 +- `POST /api/admin/import_fps` - 导入FPS格式题目 + +### 7. 竞赛相关(admin) +- `GET /api/contest/announcement` - 获取竞赛公告列表(OJ端) +- `GET /api/admin/contest/announcement` - 获取竞赛公告(管理端) +- `GET /api/admin/download_submissions` - 下载竞赛提交 + +### 8. 提交相关(不必要的接口) +- ❌ `GET /api/submission_exists` - 检查提交是否存在 + - **标记为不需要** + - **原因**: 题目接口返回的 `my_status` 字段已完整包含此信息 + - **替代方案**: 直接判断 `my_status` 的值(0=已通过,非零=已尝试未通过,null=未尝试) + - **优势**: 零额外请求,性能更优 + +### 9. 文件上传相关(admin) +- `POST /api/admin/upload_file` - 上传文件(任意格式) + - **说明**: 前端已使用 `upload_image`(仅图片),`upload_file` 可上传任意文件 + - **当前状态**: 未使用(前端暂无上传非图片文件的需求) + - **潜在场景**: 题目附件、教程资料、作业提交等 + +--- + +## 三、统计总结 + +### 前端已使用接口统计 +- **OJ普通用户接口**: 38个(包括1个直接用fetch调用的流式接口) +- **管理员接口**: 35个 +- **共享接口**: 5个 +- **总计**: 78个API调用 + +### 后端未被使用接口统计 +- **用户认证相关**: 10个 +- **网站配置相关**: 2个(languages) +- **题目管理相关**: 3个 +- **竞赛管理相关**: 3个 +- **其他**: 1个 +- **标记为不需要**: 2个 + - submission_exists(数据冗余) + - judge_server_heartbeat(内部接口) +- **总计**: 21个API端点(其中2个不需要前端实现) + +### 未使用接口占比 +约 **21%** 的后端API接口前端尚未使用 +- **需要考虑实现**: 19个接口 +- **不必要实现**: 2个接口(submission_exists, judge_server_heartbeat) + +**备注**: 本次更新新增了 ACM 比赛辅助检查功能(2个接口),用于赛后人工审核代码。 + +--- + +## 四、建议 + +### 1. 高优先级需要实现的功能 +- **密码管理**: change_password, apply_reset_password, reset_password +- **邮箱管理**: change_email +- **用户名/邮箱检查**: check_username_or_email(注册时实时验证) +- **编程语言列表**: languages(显示支持的编程语言) +- **题目导入导出**: export_problem, import_problem(方便题库管理) + +### 2. 中等优先级功能 +- **双因素认证**: tfa_required, two_factor_auth(增强安全性) +- **会话管理**: sessions(多设备登录管理) +- **竞赛公告**: contest/announcement(OJ端,增强竞赛体验) + +### 3. 低优先级功能 +- **SSO单点登录**: sso(如需要集成其他系统) +- **OpenAPI**: open_api_appkey(如需要开放API) +- **SMTP配置**: smtp, smtp_test(管理员配置) +- **版本信息**: versions(显示系统版本) +- **FPS导入**: import_fps(特定格式题目导入) +- **文件上传**: upload_file(目前只有图片上传) + +### 4. 可选功能 +- **生成用户**: generate_user(批量生成测试用户) +- **下载提交**: download_submissions(下载竞赛所有提交) + +--- + +## 五、接口使用率分析 + +| 模块 | 后端提供 | 前端使用 | 使用率 | +|------|---------|---------|--------| +| 用户认证 | 17 | 7 | 41% | +| 题目管理 | 14 | 11 | 79% | +| 提交管理 | 7 | 6 | 86% | +| 竞赛管理 | 12 | 9 | 75% | +| 公告管理 | 4 | 4 | 100% | +| 评论管理 | 4 | 4 | 100% | +| 消息管理 | 1 | 1 | 100% | +| 教程管理 | 4 | 4 | 100% | +| AI分析 | 4 | 4 | 100% ✅ | +| 配置管理 | 9 | 3 | 33% ⚠️(1个为内部接口) | + +**总体使用率约为 79%**(已实现ACM比赛辅助检查功能,竞赛管理使用率提升至75%) + +### 特殊说明 + +#### 1. AI智能分析功能 ✨ + +**功能概述**: 基于用户的学习数据,使用DeepSeek AI生成个性化的学习分析报告和建议。 + +**涉及接口**: +- `GET /api/ai/detail` - 获取详细学习数据 +- `GET /api/ai/duration` - 获取时段趋势数据 +- `GET /api/ai/heatmap` - 获取活跃度热力图 +- `POST /api/ai/analysis` - 生成AI分析(流式响应) + +**前端实现**: +- **页面**: `src/oj/ai/analysis.vue` +- **Store**: `src/oj/store/ai.ts` +- **组件**: + - `DurationChart.vue` - 混合图表(柱状图+折线图),展示题目数、提交数、等级变化 + - `Heatmap.vue` - 提交热力图 + - `Details.vue` - 详细数据展示 + - `AI.vue` - AI分析结果展示(Markdown格式) + +**时间范围选择**: +支持多种时间范围:一节课(1小时)、两节课(2小时)、一天、一周、一个月、两个月、半年、一年 + +**等级系统**: +- **S级**: 排名前10%(卓越水平,约10%的人) +- **A级**: 排名前35%(优秀水平,约25%的人) +- **B级**: 排名前75%(良好水平,约40%的人) +- **C级**: 75%之后(及格水平,约25%的人) +- **特殊规则**: 参与人数少于10人时,S级降为A级,A级降为B级(避免因人少而评级虚高) + +**流式接口实现**: +`POST /api/ai/analysis` 使用了**流式响应(Server-Sent Events)**,在 `oj/store/ai.ts` 中直接使用 `fetch` API调用,配合 `consumeJSONEventStream` 工具函数处理流式数据,实现AI内容的实时流式输出。 + +**数据缓存**: +为提升性能,后端对 `ai/detail` 和 `ai/duration` 接口的返回数据进行了缓存,相同参数的请求会直接返回缓存结果。 + +#### 2. ACM 比赛辅助检查功能 ✨ + +**功能说明**: 用于赛后人工审核 ACM 模式比赛的代码,检查是否存在抄袭、作弊等行为。 + +**涉及接口**: +- `GET /api/admin/contest/acm_helper` - 获取比赛中所有 AC 的提交记录 + - **返回数据**: 用户名、题目ID、AC时间、错误次数、检查状态等 +- `PUT /api/admin/contest/acm_helper` - 更新提交的检查状态 + - **参数**: contest_id, rank_id, problem_id, checked + +**使用场景**: +1. 管理员进入比赛详情页,点击"审核"按钮进入辅助检查页面 +2. 系统展示所有 AC 提交的列表(按用户和题目分组) +3. 管理员可以: + - 查看每个提交的代码详情 + - 标记已检查的提交 + - 批量标记所有提交为已检查 + - 按用户名、题目、检查状态筛选 +4. 实时显示检查进度统计(总计/已检查/未检查) + +**实现位置**: +- 页面: `src/admin/contest/helper.vue` +- 路由: `/admin/contest/:contestID/helper` +- API: `src/admin/api.ts` (getACMHelperList, updateACMHelperChecked) + +#### 3. 前端不需要的接口 ❌ + +##### 3.1 数据冗余接口 +**`GET /api/submission_exists`** - 此接口**无需实现** + +**分析结论**: +- 后端题目接口(`GET /api/problem`)已返回 `my_status` 字段 +- `my_status` 完整记录了用户的做题状态: + - `0` (JudgeStatus.ACCEPTED) = 已通过 ✅ + - `-1` (JudgeStatus.WRONG_ANSWER) = 答案错误 ❌ + - `-2` (JudgeStatus.COMPILE_ERROR) = 编译错误 ❌ + - `1` (JudgeStatus.TIME_LIMIT_EXCEEDED) = 超时 ❌ + - 其他非零值 = 其他失败原因 ❌ + - `null/undefined` = 从未提交 ⭕ + +**前端实现**: +```typescript +// 仅需一个计算属性即可判断 +const hasTriedButNotPassed = computed(() => { + return problem.value?.my_status !== undefined && + problem.value?.my_status !== null && + problem.value?.my_status !== 0 +}) +``` + +**优势对比**: +| 方案 | API请求 | 代码复杂度 | 性能 | +|------|---------|-----------|------| +| ❌ 使用 submission_exists | +1 | 高(需异步+状态管理) | 慢(额外网络请求) | +| ✅ 使用 my_status | 0 | 低(3行计算属性) | 快(本地计算) | + +**经验教训**: +- 实现新功能前应充分了解后端现有数据结构 +- 避免创建冗余接口,优先利用现有数据 +- 简单的解决方案往往是最好的 + +--- + +##### 3.2 内部系统接口 +**`POST /api/judge_server_heartbeat/`** - 此接口**无需前端实现** + +**接口说明**: +- **用途**: 判题服务器(JudgeServer)向后端报告健康状态 +- **调用方**: JudgeServer Docker 容器(非前端) +- **调用频率**: 每隔几秒自动调用一次 +- **认证方式**: 使用 `judge_server_token`(特殊令牌,非用户认证) + +**报告的数据**: +```python +{ + "hostname": "judge-server-1", + "judger_version": "2.0.4", + "cpu_core": 4, + "cpu": 45.2, # CPU使用率 + "memory": 60.5, # 内存使用率 + "service_url": "http://judger:8080" +} +``` + +**后端使用场景**: +- 监控判题服务器在线状态 +- 显示服务器资源使用情况(仅在管理后台显示) +- 负载均衡分配判题任务 + +**前端已有接口**: +前端查看判题服务器状态使用的是: +- `GET /api/admin/judge_server` - 获取所有判题服务器列表及状态 + +**结论**: +此接口是系统内部通信接口,前端完全不需要调用。类似的内部接口还可能存在于分布式系统的其他服务间通信中。 + +--- + +## 六、新增功能模块 + +### FlowchartEditor 流程图编辑器 ✨ + +**功能概述**: 基于 Vue Flow 构建的完整流程图编辑器,支持拖拽创建、节点连接、编辑等核心功能。 + +**技术实现**: +- **核心库**: Vue Flow (@vue-flow/core) +- **组件架构**: 模块化设计,9个独立功能模块 +- **状态管理**: 基于 Vue 3 Composition API +- **数据持久化**: localStorage 自动缓存 +- **交互体验**: 拖拽、键盘快捷键、撤销重做 + +**组件结构**: +``` +FlowchartEditor/ +├── index.vue # 主组件 - 整合所有功能 +├── CustomNode.vue # 自定义节点 - 7种节点类型 +├── Toolbar.vue # 工具栏 - 节点创建和操作 +├── NodeHandles.vue # 节点操作手柄 - 连接点管理 +├── NodeActions.vue # 节点动作 - 删除、编辑按钮 +├── useCache.ts # 缓存管理 - 自动保存/恢复 +├── useDnD.ts # 拖拽处理 - 节点创建逻辑 +├── useFlowOperations.ts # 流程操作 - 增删改查 +├── useHistory.ts # 历史记录 - 撤销重做 +└── useNodeStyles.ts # 节点样式 - 类型配置 +``` + +**节点类型支持**: +- **开始节点** (start) - 流程开始,绿色主题 +- **输入节点** (input) - 数据输入,蓝色主题 +- **处理节点** (default) - 数据处理,紫色主题 +- **判断节点** (decision) - 条件判断,橙色主题 +- **循环节点** (loop) - 循环处理,青色主题 +- **输出节点** (output) - 数据输出,粉色主题 +- **结束节点** (end) - 流程结束,红色主题 + +**核心功能**: +1. **拖拽创建**: 从工具栏拖拽节点到画布 +2. **节点连接**: 支持节点间的连线操作 +3. **节点编辑**: 双击节点进行文本编辑 +4. **撤销重做**: 完整的历史记录管理 +5. **自动保存**: 本地缓存,防止数据丢失 +6. **键盘快捷键**: Ctrl+Z/Ctrl+Y 撤销重做,Delete 删除 +7. **批量操作**: 支持多选和批量删除 + +**数据格式**: +```typescript +// 节点数据 +interface Node { + id: string + type: string + position: { x: number, y: number } + data: { + customLabel: string + [key: string]: any + } +} + +// 边数据 +interface Edge { + id: string + source: string + target: string + sourceHandle?: string + targetHandle?: string + type?: string +} +``` + +**使用场景**: +- 算法流程图绘制 +- 业务流程设计 +- 教学演示工具 +- 问题分析图表 + +**性能优化**: +- 基于 Vue Flow 的高性能渲染 +- 模块化设计,按需加载 +- 本地缓存减少重复计算 +- 响应式状态管理 + +**扩展性**: +- 支持自定义节点类型 +- 可扩展工具栏功能 +- 支持主题定制 +- 支持数据导入导出 + +**文档支持**: +- 详细的使用文档: `docs/FlowchartEditor.md` +- 完整的 API 说明 +- 最佳实践指南 +- 故障排除手册 + diff --git a/apps/web/docs/FlowchartEditor.md b/apps/web/docs/FlowchartEditor.md new file mode 100644 index 0000000..e026d3a --- /dev/null +++ b/apps/web/docs/FlowchartEditor.md @@ -0,0 +1,311 @@ +# FlowchartEditor 流程图编辑器 + +一个基于 Vue 3 + Vue Flow 构建的功能完整的流程图编辑器组件。 + +## 功能特性 + +### 🎯 核心功能 +- **拖拽创建节点** - 从工具栏拖拽节点到画布 +- **节点连接** - 支持节点间的连线操作 +- **节点编辑** - 双击节点进行文本编辑 +- **撤销重做** - 完整的历史记录管理 +- **自动保存** - 本地缓存,防止数据丢失 +- **键盘快捷键** - 支持常用快捷键操作 + +### 🎨 节点类型 +- **开始节点** (start) - 流程开始 +- **输入节点** (input) - 数据输入 +- **处理节点** (default) - 数据处理 +- **判断节点** (decision) - 条件判断 +- **循环节点** (loop) - 循环处理 +- **输出节点** (output) - 数据输出 +- **结束节点** (end) - 流程结束 + +### ⌨️ 快捷键 +- `Ctrl+Z` / `Cmd+Z` - 撤销 +- `Ctrl+Y` / `Cmd+Shift+Z` - 重做 +- `Delete` / `Backspace` - 删除选中节点 + +## 组件结构 + +``` +FlowchartEditor/ +├── index.vue # 主组件 +├── CustomNode.vue # 自定义节点组件 +├── Toolbar.vue # 工具栏组件 +├── NodeHandles.vue # 节点操作手柄 +├── NodeActions.vue # 节点动作按钮 +├── useCache.ts # 缓存管理 +├── useDnD.ts # 拖拽处理 +├── useFlowOperations.ts # 流程操作 +├── useHistory.ts # 历史记录 +└── useNodeStyles.ts # 节点样式 +``` + +## 使用方法 + +### 基本用法 + +```vue + + + +``` + +### 获取流程图数据 + +```javascript +// 获取当前流程图数据 +const data = flowchartRef.value.getFlowchartData() +console.log(data.nodes) // 节点数组 +console.log(data.edges) // 边数组 +``` + +## 组件详解 + +### 主组件 (index.vue) + +主组件负责整合所有功能模块,提供完整的流程图编辑体验。 + +**主要功能**: +- 整合 Vue Flow 核心功能 +- 管理节点和边的状态 +- 处理用户交互事件 +- 提供数据暴露接口 + +**暴露的方法**: +- `getFlowchartData()` - 获取当前流程图数据 + +### 自定义节点 (CustomNode.vue) + +基于 Vue Flow 的自定义节点组件,支持多种节点类型。 + +**节点类型配置**: +- 每种节点类型都有独特的样式和图标 +- 支持自定义标签文本 +- 提供删除和编辑功能 + +### 工具栏 (Toolbar.vue) + +提供节点创建和操作的工具集合。 + +**功能**: +- 节点类型选择 +- 撤销/重做操作 +- 清空画布 +- 保存状态显示 + +### 缓存管理 (useCache.ts) + +提供本地存储功能,防止数据丢失。 + +**功能**: +- 自动保存到 localStorage +- 页面刷新后恢复数据 +- 保存状态提示 +- 清空缓存功能 + +### 拖拽处理 (useDnD.ts) + +处理节点拖拽创建的逻辑。 + +**功能**: +- 拖拽开始处理 +- 拖拽悬停效果 +- 拖拽放置处理 +- 节点位置计算 + +### 流程操作 (useFlowOperations.ts) + +处理流程图的增删改操作。 + +**功能**: +- 节点连接处理 +- 边删除处理 +- 节点删除处理 +- 节点更新处理 +- 清空画布 + +### 历史记录 (useHistory.ts) + +提供撤销重做功能。 + +**功能**: +- 状态快照保存 +- 撤销操作 +- 重做操作 +- 历史记录管理 + +### 节点样式 (useNodeStyles.ts) + +定义各种节点类型的样式配置。 + +**功能**: +- 节点类型配置 +- 样式定义 +- 图标配置 +- 颜色主题 + +## 样式定制 + +### 节点样式 + +可以通过修改 `useNodeStyles.ts` 来自定义节点样式: + +```typescript +export function getNodeTypeConfig(type: string) { + const configs = { + start: { + label: '开始', + icon: 'play-circle', + color: '#10b981', + // 自定义样式 + }, + // 其他节点类型... + } + + return configs[type] || configs.default +} +``` + +### 边样式 + +边的样式在主组件中定义: + +```vue +:default-edge-options="{ + type: 'step', + style: { + stroke: '#6366f1', + strokeWidth: 2.5, + cursor: 'pointer', + filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))' + }, + markerEnd: { + type: MarkerType.ArrowClosed, + color: '#6366f1', + width: 16, + height: 16, + }, +}" +``` + +## 数据格式 + +### 节点数据格式 + +```typescript +interface Node { + id: string + type: string + position: { x: number, y: number } + data: { + customLabel: string + [key: string]: any + } +} +``` + +### 边数据格式 + +```typescript +interface Edge { + id: string + source: string + target: string + sourceHandle?: string + targetHandle?: string + type?: string +} +``` + +## 最佳实践 + +### 1. 性能优化 +- 大量节点时考虑虚拟化 +- 合理使用缓存机制 +- 避免频繁的状态更新 + +### 2. 用户体验 +- 提供清晰的操作反馈 +- 支持键盘快捷键 +- 保持操作的直观性 + +### 3. 数据管理 +- 定期保存到服务器 +- 提供数据导入导出功能 +- 支持版本控制机制 + +## 扩展开发 + +### 添加新节点类型 + +1. 在 `useNodeStyles.ts` 中添加新类型配置 +2. 在 `Toolbar.vue` 中添加新节点按钮 +3. 在 `CustomNode.vue` 中添加新节点渲染逻辑 + +### 添加新功能 + +1. 创建新的 composable 函数 +2. 在主组件中集成新功能 +3. 更新工具栏和用户界面 + +## 故障排除 + +### 常见问题 + +1. **节点无法拖拽** + - 检查拖拽事件处理 + - 确认节点类型配置正确 + +2. **撤销重做不工作** + - 检查历史记录状态 + - 确认状态保存时机 + +3. **数据丢失** + - 检查缓存配置 + - 确认 localStorage 可用 + +### 调试技巧 + +1. 使用 Vue DevTools 查看组件状态 +2. 检查浏览器控制台错误 +3. 验证数据格式正确性 + +## 更新日志 + +### v1.0.0 (2024-01-15) +- ✨ 初始版本发布 +- 🎯 基础流程图编辑功能 +- 🔧 拖拽创建节点 +- 🔗 节点连接功能 +- ↩️ 撤销重做支持 +- 💾 本地缓存功能 + +### v1.1.0 (2024-01-20) +- 🎨 新增多种节点类型 +- ⌨️ 键盘快捷键支持 +- 🖱️ 优化拖拽体验 +- 📱 响应式布局改进 + +### v1.2.0 (2024-01-25) +- 🔧 模块化重构 +- 📦 组合式函数优化 +- 🎯 性能提升 +- 🐛 修复已知问题 + +## 许可证 + +MIT License diff --git a/apps/web/docs/superpowers/plans/2026-06-14-submit-formatting-button-state.md b/apps/web/docs/superpowers/plans/2026-06-14-submit-formatting-button-state.md new file mode 100644 index 0000000..13ba1f7 --- /dev/null +++ b/apps/web/docs/superpowers/plans/2026-06-14-submit-formatting-button-state.md @@ -0,0 +1,259 @@ +# Submit Formatting Button State Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Show `格式化中` on the submit button during automatic formatting, then show `正在提交` continuously while the submission request is pending. + +**Architecture:** Extract the button presentation rules into a small pure TypeScript function so the state priority can be tested without adding a frontend test framework. Keep formatter and submission-request flags local to `SubmitCode.vue`, with `finally` blocks ensuring both flags clear on every outcome. + +**Tech Stack:** Vue 3 Composition API, TypeScript, Node.js built-in test runner, Rsbuild + +--- + +### Task 1: Define and test submit button presentation rules + +**Files:** +- Create: `tests/submitButtonState.test.ts` +- Create: `src/oj/problem/components/submitButtonState.ts` + +- [ ] **Step 1: Write the failing test** + +Create `tests/submitButtonState.test.ts`: + +```ts +import assert from "node:assert/strict" +import test from "node:test" +import { getSubmitButtonState } from "../src/oj/problem/components/submitButtonState.ts" + +const idleInput = { + isAuthed: true, + hasCode: true, + isFormatting: false, + isSubmitting: false, + isJudging: false, + isCooldown: false, +} + +test("shows a disabled loading state while formatting", () => { + assert.deepEqual( + getSubmitButtonState({ ...idleInput, isFormatting: true }), + { + disabled: true, + label: "格式化中", + icon: "eos-icons:loading", + }, + ) +}) + +test("shows submitting immediately after formatting", () => { + assert.deepEqual( + getSubmitButtonState({ ...idleInput, isSubmitting: true }), + { + disabled: true, + label: "正在提交", + icon: "eos-icons:loading", + }, + ) +}) + +test("preserves existing login, judging, cooldown, and idle states", () => { + assert.deepEqual( + getSubmitButtonState({ ...idleInput, isAuthed: false }), + { + disabled: true, + label: "请先登录", + icon: "ph:play-fill", + }, + ) + assert.deepEqual(getSubmitButtonState({ ...idleInput, isJudging: true }), { + disabled: true, + label: "正在评分", + icon: "eos-icons:loading", + }) + assert.deepEqual(getSubmitButtonState({ ...idleInput, isCooldown: true }), { + disabled: true, + label: "正在冷却", + icon: "ph:lightbulb-fill", + }) + assert.deepEqual(getSubmitButtonState(idleInput), { + disabled: false, + label: "提交代码", + icon: "ph:play-fill", + }) +}) +``` + +- [ ] **Step 2: Run the test to verify it fails** + +Run: + +```bash +node --test tests/submitButtonState.test.ts +``` + +Expected: FAIL with `ERR_MODULE_NOT_FOUND` for `submitButtonState.ts`. + +- [ ] **Step 3: Implement the pure state function** + +Create `src/oj/problem/components/submitButtonState.ts`: + +```ts +export interface SubmitButtonStateInput { + isAuthed: boolean + hasCode: boolean + isFormatting: boolean + isSubmitting: boolean + isJudging: boolean + isCooldown: boolean +} + +export interface SubmitButtonState { + disabled: boolean + label: string + icon: string +} + +export function getSubmitButtonState({ + isAuthed, + hasCode, + isFormatting, + isSubmitting, + isJudging, + isCooldown, +}: SubmitButtonStateInput): SubmitButtonState { + const disabled = + !isAuthed || + !hasCode || + isFormatting || + isSubmitting || + isJudging || + isCooldown + + let label = "提交代码" + if (!isAuthed) { + label = "请先登录" + } else if (isFormatting) { + label = "格式化中" + } else if (isSubmitting) { + label = "正在提交" + } else if (isJudging) { + label = "正在评分" + } else if (isCooldown) { + label = "正在冷却" + } + + const icon = + isFormatting || isSubmitting || isJudging + ? "eos-icons:loading" + : isCooldown + ? "ph:lightbulb-fill" + : "ph:play-fill" + + return { disabled, label, icon } +} +``` + +- [ ] **Step 4: Run the test to verify it passes** + +Run: + +```bash +node --test tests/submitButtonState.test.ts +``` + +Expected: 3 tests pass. + +### Task 2: Connect formatting and submission request lifecycle to the button + +**Files:** +- Modify: `src/oj/problem/components/SubmitCode.vue` + +- [ ] **Step 1: Add local request states and computed presentation** + +Import `getSubmitButtonState`, add `isFormatting` and `isSubmittingRequest` refs, and replace the three existing button computed properties with: + +```ts +const buttonState = computed(() => + getSubmitButtonState({ + isAuthed: userStore.isAuthed, + hasCode: codeStore.code.value.trim() !== "", + isFormatting: isFormatting.value, + isSubmitting: isSubmittingRequest.value || submitting.value, + isJudging: judging.value || pending.value, + isCooldown: isCooldown.value, + }), +) +``` + +Use `buttonState.disabled`, `buttonState.icon`, and `buttonState.label` in the template. + +- [ ] **Step 2: Guard and track the formatting request** + +At the start of `submit`, return when `buttonState.value.disabled` is true. Around `formatCode`, set `isFormatting.value = true` before the request and clear it in `finally`: + +```ts +isFormatting.value = true +try { + const res = await formatCode({ + code: codeStore.code.value, + language: formatLang, + }) + codeStore.setCode(res.data.code) +} catch (e: any) { + if (e?.error === "format-error") { + message.warning(`代码格式化失败:${e.data},请检查代码后重试`) + return + } +} finally { + isFormatting.value = false +} +``` + +- [ ] **Step 3: Track the submission API request** + +Set `isSubmittingRequest.value = true` immediately before `submitCode`, keep the existing success flow inside the `try`, and clear the request state in `finally`: + +```ts +isSubmittingRequest.value = true +try { + const res = await submitCode(data) + console.log(`[Submit] 代码已提交: ID=${res.data.submission_id}`) + + startCooldown() + startMonitoring(res.data.submission_id) + showResult.value = true +} finally { + isSubmittingRequest.value = false +} +``` + +- [ ] **Step 4: Run focused tests** + +Run: + +```bash +node --test tests/submitButtonState.test.ts +``` + +Expected: 3 tests pass. + +- [ ] **Step 5: Run the production build** + +Run: + +```bash +npm run build +``` + +Expected: Rsbuild exits with status 0. + +- [ ] **Step 6: Check the final diff** + +Run: + +```bash +git diff --check +git diff -- src/oj/problem/components/SubmitCode.vue src/oj/problem/components/submitButtonState.ts tests/submitButtonState.test.ts +``` + +Expected: no whitespace errors; diff is limited to the button state feature and its test. diff --git a/apps/web/docs/superpowers/plans/2026-07-26-demo-student-view.md b/apps/web/docs/superpowers/plans/2026-07-26-demo-student-view.md new file mode 100644 index 0000000..d035447 --- /dev/null +++ b/apps/web/docs/superpowers/plans/2026-07-26-demo-student-view.md @@ -0,0 +1,296 @@ +# 学生视角(演示模式)Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 超级管理员在右上角下拉菜单点一下,整站界面变成普通学生看到的样子,再点一下恢复。 + +**Architecture:** 纯前端伪装。全站 40 多处权限判断读的都是 `shared/store/user.ts` 里的 6 个角色 getter,没有一处直接读 `user.admin_type`。在 store 里加一个 `demoMode` 开关,给每个 getter 加 `!demoMode.value &&` 前缀,全站自动跟随。路由守卫(`src/main.ts`)、权限工具(`src/utils/permissions.ts`)、各页面 `v-if` 零改动。 + +**Tech Stack:** Vue 3 ` +``` + +### 图表样式定制 + +```typescript +// 主题配置 +const theme = { + colors: { + primary: '#6366f1', + secondary: '#8b5cf6', + success: '#10b981', + warning: '#f59e0b', + error: '#ef4444' + }, + gradients: { + primary: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + success: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)' + } +} +``` + +## 性能优化 + +### 1. 数据懒加载 +- 按需加载图表数据 +- 使用虚拟滚动处理大量数据 + +### 2. 图表缓存 +- 缓存计算结果 +- 避免重复渲染 + +### 3. 响应式设计 +- 自适应容器大小 +- 移动端优化 + +## 最佳实践 + +### 1. 数据预处理 +- 在组件外部处理数据 +- 使用 computed 属性缓存计算结果 + +### 2. 错误处理 +- 添加数据验证 +- 提供降级方案 + +### 3. 用户体验 +- 添加加载状态 +- 提供交互反馈 + +## 更新日志 + +### v1.0.0 (2024-01-15) +- ✨ 初始版本发布 +- 📊 基础图表组件 +- 🎨 主题样式支持 +- 📱 响应式设计 + +### v1.1.0 (2024-01-20) +- 🔧 性能优化 +- 📈 新增混合图表 +- 🎯 交互体验改进 + +## 许可证 + +MIT License \ No newline at end of file diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..3737e63 --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,17 @@ + + + + + + + 判题狗 + + + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..1776ccb --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,67 @@ +{ + "name": "@oj2/web", + "version": "1.9.0", + "type": "module", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "vite build", + "build:staging": "vite build --mode staging", + "build:test": "vite build --mode test", + "fmt": "prettier --write src *.ts" + }, + "dependencies": { + "@oj2/contract": "workspace:*", + "@codemirror/autocomplete": "^6.20.3", + "@codemirror/lang-cpp": "^6.0.3", + "@codemirror/lang-python": "^6.2.1", + "@codemirror/lang-sql": "^6.10.0", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.2", + "@vue-flow/minimap": "^1.5.4", + "@vue-flow/node-resizer": "^1.5.1", + "@vue-flow/node-toolbar": "^1.1.1", + "@vueuse/core": "^14.4.0", + "@vueuse/router": "^14.4.0", + "@wangeditor-next/editor": "^6.2.0", + "@wangeditor-next/editor-for-vue": "^6.2.0", + "axios": "^1.19.0", + "canvas-confetti": "^1.9.4", + "chart.js": "^4.5.1", + "chartjs-chart-wordcloud": "^4.4.5", + "codemirror": "^6.0.2", + "copy-text-to-clipboard": "^3.2.2", + "date-fns": "^4.4.0", + "fflate": "^0.8.3", + "highlight.js": "^11.11.1", + "md-editor-v3": "^6.5.6", + "mermaid": "^11.16.1", + "mermaid-legacy": "npm:mermaid@^9.4.3", + "naive-ui": "^2.44.1", + "nanoid": "^6.0.1", + "normalize.css": "^8.0.1", + "pinia": "^4.0.2", + "skulpt": "^1.2.0", + "vue": "^3.5.41", + "vue-chartjs": "^5.3.4", + "vue-codemirror": "^6.1.1", + "vue-router": "^5.2.0", + "y-codemirror.next": "^0.3.5", + "y-webrtc": "^10.3.0", + "yjs": "^13.6.32" + }, + "devDependencies": { + "@iconify/vue": "^5.0.1", + "@types/canvas-confetti": "^1.9.0", + "@types/node": "^26.1.2", + "@vitejs/plugin-legacy": "^8.2.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "prettier": "^3.9.6", + "typescript": "^7.0.2", + "unplugin-auto-import": "^21.1.0", + "unplugin-vue-components": "^32.1.0", + "vite": "^8.2.1" + } +} diff --git a/apps/web/public/A.png b/apps/web/public/A.png new file mode 100644 index 0000000000000000000000000000000000000000..01c828e4b770543a0f454eaa3924f918468ed96a GIT binary patch literal 8228 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 W88tzA=Y+BV0000^L`0~$mGCuayYf&(#v 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

{_5u4p?N1)jV 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 literal 0 HcmV?d00001 diff --git a/apps/web/public/badge-1.png b/apps/web/public/badge-1.png new file mode 100644 index 0000000000000000000000000000000000000000..5bb14aa763f914eb2a4f5e6af5348a1725e20a35 GIT binary patch literal 2214 zcmV;X2wC@uP)^ zO>7la6vyv$g=`T`+|X{s4-72W5ef|piHojuL1F@hMvXCX1tgMCYJv%{GBL)O(sp4& zjIO#curO2zyDmyJDsF5yT_|j^8yU~juAp8Ijm`QMK@_s$z} z|MS0X1z_ia)0d#_9D{8G;JN*0Zl`H71MS>@W?{QP*cJdXhmWe$sdJ~cYbJCK0FL6Y zIWjcM{!i0yE%8({r1BZ%{s~eC>L~)=nMdyPtx?Z`h@f22lu)? zFYR()e>$aNKsebY2%P{BI*-FRuU@}9dB}n=nx@G~+Gu&3yfFYf4xBzEJ9^rW?8smE zVAA1m=oB)_GWjTN`t>Pgf5w<8jR8o~r1C2)a5D4tDFXoLsibep&y>D3Dik&bKy|Q| zaj=YVjH$2n%Q)?+2QMRZW$i~ZJ~WF4z~O_h_;rl&MFap#xd`-yiZ^ExE{ z`umZCNJAbZspIPkSjZqi1PIcQ)g?A8G>Hg@0)X&W6z2yU-rEqyh2)(Pr$YIUKizZF zAD#Df+TC~3L zuvUGoToHgL%Sn>X(r8^``S&l**N)WK(0Cov2gGX)BJ$(4i43v<5&0@az(veq>Mn1t z&M%sLX|fUko2!d!k5}g}Sr9NB2TFS`C?F09K?t2EAS!5DD#8xyeB#{VKliUslr{(o zDgmHvLJ$A|(()=i1VP&{@^zg@w*`;^dVK%-(;HUpybrGh0R31H$YTUVx`@KHPn{5i zGUtoh(jLNbt2a&3DQu&x4%G&L&QXLy(h!C)6-h&OQ~PB0st(|wka}hPAV=4reyWx?27vgxsxlmd%IZ^wgJtER+(x5eS=D@F z00@ia^p#jr<*aR9X{>XV;UuJayZ(_!? zt8(4xwQlGv=Xs{SmgBsBE!y|`UKO{(qXRIKCP&m#uCF!CY~XcAo;3^zI1btKymg+s z_I79_O-~|=Nznn2i_`q>kde2>0)hrQfWXa?b)L%-9eHiXVF@B{`z9ZQq5~jC`Co0| ztfZfL005!@5a19;Vvfk9Lo&0#_Z^lu$wvjCX#E!%2LO@Upn7T{u}urLtXfGFlZAa)0uhRvqQ9&I|FU5IKv7RKz)ddP_D-Sf_xFN5w@`pL;;}6nq<`Z-H?s> z#nH8l{7+>7BD@@x^89IIABX~gb5HX1Kb{?ca;qi5Xfx_}R~|@i>){wJC%ZBEwlG0@ zOX?gKq-C?AjeQ^r0A&G?jyeFSgTmK1)^d!NwBy?XX#s+dEdB!r`vJ3o{ed*~pf>h_ zC;-U%&!aPa1>$tssBad0%m4@hVam=1oVT-~jeQ^r05Up$39tcjyv1<<5V=0f;@lV` zt%4#B2-Kl2pSh_62s#^B9Ov*|0i)Nq}IJ^s_-bEYPh*m3daHHaV%+=m7`;p=UNohq-P^ z5A`$#KwfFHtX1I?Y+yrKQLzyqphJKd18m{7ehyQlrZE7FHft4Lq@WF%0s(K@|9=1g o0RR6nS*{)c000I_L_t&o05)5By-4N&%m4rY07*qoM6N<$f;IOE)&Kwi literal 0 HcmV?d00001 diff --git a/apps/web/public/badge-2.png b/apps/web/public/badge-2.png new file mode 100644 index 0000000000000000000000000000000000000000..7610c6bfa46083387c5b83e612ebb3812e1b0bb2 GIT binary patch literal 2250 zcmV;*2sQVKP)9*~6vwBoWT}Y>6rO2ur7Q&zLZn@|$IuiOCX}5C3v0SlNilxJg$;GVy139FX`_1< zZ43cX+(}%R+9riUqKWP_#!&yi)0z9`-FM%8bKCcoD7ojInKLtI&iT)o&+>%(kN^80 zfPMzR-o1NAL}UH5p)V0IJUqOxZ{NPHp`oEQ(X!HO0^(9Xfaoaza&kn@E^lpZP34>& z5fHN~1Bj7aSy!_?1wc+N$Quj!+JR@>%=BqD|H&JUwB&pMu}q>@)H47yx;SReAA9+E zcjxA(2EgRRD{k@HyAGLmdW0$B(_Lph0YIV8{5Izq?WJoM8yrIxM*>sCw`r)?jZD`8 zkRxLX{n=prWrci5#~cudgdkI!bF>ZlZt)!fAj8OpXxxR=lW@Kd$h% zOu-Kz5DC7B^tpPyarnIGNcso~Hs zQDD&;1g&@y0BW28z^EucIG>z%f8R8FYWtrwUiOnOO9fy(G~BrVYe@E=N6ur+#)H3L zfijzepopgekhL8c0CLV_;KqYLVU;KefJcuWWoQlr00qwHy^d|pm&N%c0DJ=j;r{*K ze6~-l#QEFb{~nAoTSKOdR|cR=S=Y--oX_>$c>uJxn$o^afduEn8W3#Ffl`=FjsP8R zZ7b}{HkjZ%-qrj;DArev4un~Jh^jOtTLO^3`r093i* z>6-g|ufrZAr{B2;Gn&!dCcG_^_%kCA?%X($U6Iv{bs?M1%G=if0MAa6+Q{?hz}>qa z&NJ^r=pTEDd3{i3RsV>F#j7tmbokayPwou>X#ccT6X%!oz~LKppZTz-qj=uqz|z&j zh<8lrnE&ul27-LF0S*c?05m_cf-`;C>M$rj?=OFJ2=T6n#(;4A$kQaDhBHD2fJ*GB zEABdR+?|_Wa;ML}U#=?}dHUV$yhleXs?`rkRLOje0ieL>`r0C4-;pWMXR z4~jv^Ip3*y-^%7MzuwG>;;V8707C5mSE+>qM^C(2Y&aF#5ch={{o#{p=xPEX=X|H< zJ^cn^TE*Z7A47Qt0D{MlAHOHQA~NTj45uKGCiO?pLJ)G!V_>}BcWT~ars+)^f zr!&?708$UaxGED376||VQC@|bKzQ(no6v{^qvFtHujcSlMSrWhb60+Gaa5_^%EN~b zNAXSRhOY?#N~tKi6hJ@*01)XaWFWluY<4uoc|e$c`(75Bwf%eCZuk3O6VA_FSq)i- z4|2X~03c~96!&RfXiLz%Iq z0MJkhOc80;gt_@el4&I8>wmP^qrcqLK|lc`zxdfj6w5l1W@@1)*h;z`B?F+48fmA@%G)?^b-MhMY^&@@20(QU09JLSi@!cQ&sORU zGh2PSlvM_Rt8$^nK09A%Q-ZJx0Ik&xi1W4K?f)PlL%H%MML`k(@moRD6c`Za<+MG2 z!98^j0I$$k0{{c$JjT@;@BE0`Qc?s+i*148^=)v#oHyHg@gF2O4!Z$#9suIwwGKkr zpi$O}k|F?Q+FQ9MTLc_E`LW~DL5FaNn;kU#!VL#;6(vudR-Lz#bGAnZx`Iy9$fk9f zqF?mGDGvuB*=jf0cKRt#0)UDlj+T(4BQ71=Myv{qRlC_m4-Zy{JjOw4U+JR_S6Xz? zAC2J|@$1_L>iRpC1Vph^cabVZKXqQv}tON$pguLBunK~5XmhJ3ep*8#wAIl{Eb zK;TIXc}zTx0)m$@s>utPZ?hxT69Bwr4GSaN8EXPWnw8zx;{mhf8=kMzG1fBx6a}`6 z$a0&nj5doML|SsbbuDr|bDj=+1^}IT5LmTsIw143&ekdCD{W!E?XFl)0l-o~5V0n( zg+Xj{qimg&_nLYRKyXkXu!TW|+gzyEZQeHkI_21I_52F}0RR8l>6s}2000I_L_t&o Y0GdsZz1U1K761SM07*qoM6N<$g84%_+5i9m literal 0 HcmV?d00001 diff --git a/apps/web/public/badge-3.png b/apps/web/public/badge-3.png new file mode 100644 index 0000000000000000000000000000000000000000..73247e252510fc0d4ade1b9b2db92e3d36a3d6d8 GIT binary patch literal 2894 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 literal 0 HcmV?d00001 diff --git a/apps/web/public/badge-4.png b/apps/web/public/badge-4.png new file mode 100644 index 0000000000000000000000000000000000000000..dae57cc30dadda29d0c3bcdf473dcc44d567fb8f GIT binary patch literal 2749 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 ALjV8( literal 0 HcmV?d00001 diff --git a/apps/web/public/badge-6.png b/apps/web/public/badge-6.png new file mode 100644 index 0000000000000000000000000000000000000000..06d4b4d78a6e48ebe88c2d93463084f1a8bec7bd GIT binary patch literal 3194 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 literal 0 HcmV?d00001 diff --git a/apps/web/public/favicon.ico b/apps/web/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..127922312f03ad54155f965742cd35ed4ee894c4 GIT binary patch literal 2393 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 literal 0 HcmV?d00001 diff --git a/apps/web/public/noto--dog-face.ico b/apps/web/public/noto--dog-face.ico new file mode 100644 index 0000000000000000000000000000000000000000..a49602af12c42bc0d727922ec3a506c7b8199812 GIT binary patch literal 192545 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 +import { darkTheme, dateZhCN, zhCN } from "naive-ui" +import "normalize.css" +import "./index.css" +import { useConfigStore } from "shared/store/config" +import { useConfigUpdate } from "shared/composables/configUpdate" +import { useMaxKB } from "shared/composables/maxkb" +import { useUserStore } from "shared/store/user" + +const isDark = useDark() +const configStore = useConfigStore() +const userStore = useUserStore() + +// 初始化配置和实时更新 +onMounted(() => { + configStore.getConfig() + userStore.getMyProfile() +}) + +// 使用配置更新和 MaxKB 功能 +useConfigUpdate() +useMaxKB() + +// 延迟加载 highlight.js,避免阻塞首屏 +const hljsInstance = ref(null) +const loadHighlightJS = async () => { + if (hljsInstance.value) return hljsInstance.value + + const [hljs, c, cpp, python, java, javascript, go, sql] = await Promise.all([ + import("highlight.js/lib/core"), + import("highlight.js/lib/languages/c"), + import("highlight.js/lib/languages/cpp"), + import("highlight.js/lib/languages/python"), + import("highlight.js/lib/languages/java"), + import("highlight.js/lib/languages/javascript"), + import("highlight.js/lib/languages/go"), + import("highlight.js/lib/languages/sql"), + ]).then((modules) => modules.map((m) => m.default)) + + hljs.registerLanguage("c", c) + hljs.registerLanguage("python", python) + hljs.registerLanguage("cpp", cpp) + hljs.registerLanguage("java", java) + hljs.registerLanguage("javascript", javascript) + hljs.registerLanguage("go", go) + hljs.registerLanguage("sql", sql) + + hljsInstance.value = hljs + return hljs +} + +// 在空闲时预加载 +onMounted(() => { + if ("requestIdleCallback" in window) { + requestIdleCallback(() => loadHighlightJS()) + } else { + setTimeout(() => loadHighlightJS(), 1000) + } +}) + +provide("hljs", hljsInstance) + + + diff --git a/apps/web/src/admin/achievement/components/AchievementModal.vue b/apps/web/src/admin/achievement/components/AchievementModal.vue new file mode 100644 index 0000000..676ba19 --- /dev/null +++ b/apps/web/src/admin/achievement/components/AchievementModal.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/apps/web/src/admin/achievement/list.vue b/apps/web/src/admin/achievement/list.vue new file mode 100644 index 0000000..db6d265 --- /dev/null +++ b/apps/web/src/admin/achievement/list.vue @@ -0,0 +1,144 @@ + + + diff --git a/apps/web/src/admin/ai/list.vue b/apps/web/src/admin/ai/list.vue new file mode 100644 index 0000000..c0c7e5e --- /dev/null +++ b/apps/web/src/admin/ai/list.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/apps/web/src/admin/announcement/components/Actions.vue b/apps/web/src/admin/announcement/components/Actions.vue new file mode 100644 index 0000000..bd0856a --- /dev/null +++ b/apps/web/src/admin/announcement/components/Actions.vue @@ -0,0 +1,39 @@ + + + diff --git a/apps/web/src/admin/announcement/detail.vue b/apps/web/src/admin/announcement/detail.vue new file mode 100644 index 0000000..258f594 --- /dev/null +++ b/apps/web/src/admin/announcement/detail.vue @@ -0,0 +1,117 @@ + + + diff --git a/apps/web/src/admin/announcement/list.vue b/apps/web/src/admin/announcement/list.vue new file mode 100644 index 0000000..a4ca45f --- /dev/null +++ b/apps/web/src/admin/announcement/list.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/apps/web/src/admin/api.ts b/apps/web/src/admin/api.ts new file mode 100644 index 0000000..9a32961 --- /dev/null +++ b/apps/web/src/admin/api.ts @@ -0,0 +1,600 @@ +import http from "utils/http" +import { toProblemListItem } from "admin/transforms" +import type { + AdminProblem, + AdminTag, + Announcement, + AnnouncementEdit, + BlankContest, + BlankProblem, + Contest, + Exercise, + ExerciseType, + Server, + SQLDisplay, + TestcaseUploadedReturns, + Tutorial, + User, + WebsiteConfig, +} from "utils/types" + +export function getBaseInfo() { + return http.get("admin/dashboard_info") +} + +export function randomUser10(classroom: string) { + return http.get("admin/random_user", { params: { classroom } }) +} + +export async function getProblemList( + offset = 0, + limit = 10, + keyword: string, + author?: string, + contestID?: string, + tagId?: number, +) { + const endpoint = !!contestID ? "admin/contest/problem" : "admin/problem" + const res = await http.get<{ results: AdminProblem[]; total: number }>( + endpoint, + { + params: { + paging: true, + offset, + limit, + keyword, + author, + contest_id: contestID, + tag_id: tagId, + }, + }, + ) + return { + results: res.data.results.map(toProblemListItem), + total: res.data.total, + } +} + +export function deleteProblem(id: number) { + return http.delete("admin/problem", { params: { id } }) +} + +export function deleteContestProblem(id: number) { + return http.delete("admin/contest/problem", { params: { id } }) +} + +export function editProblem(problem: AdminProblem | BlankProblem) { + return http.put("admin/problem", problem) +} + +export function toggleProblemVisible(problemID: number) { + return http.put("admin/problem/visible", { id: problemID }) +} + +export function generateFlowchartFromPythonCode(python: string) { + return http.post("admin/problem/flowchart", { python }) +} + +export function editContestProblem(problem: AdminProblem | BlankProblem) { + return http.put("admin/contest/problem", problem) +} + +export function getProblem(id: string | number) { + return http.get("admin/problem", { params: { id } }) +} + +export function getContestProblem(id: number) { + return http.get("admin/contest/problem", { params: { id } }) +} + +// 标签管理 +export function getTagAdminList(keyword = "") { + return http.get("admin/problem/tag", { params: { keyword } }) +} + +export function renameTag(id: number, name: string) { + return http.put<{ + merged: boolean + id: number + name: string + affected_count: number + }>("admin/problem/tag", { id, name }) +} + +export function deleteTag(id: number) { + return http.delete("admin/problem/tag", { params: { id } }) +} + +export function batchTagProblems( + problemIds: number[], + tagNames: string[], + action: "add" | "remove", +) { + return http.post<{ problem_count: number; tag_count: number }>( + "admin/problem/batch_tag", + { problem_ids: problemIds, tag_names: tagNames, action }, + ) +} + +// 用户列表 +export function getUserList( + offset = 0, + limit = 10, + type = "", + keyword: string, + orderBy = "", +) { + return http.get("admin/user", { + params: { paging: true, offset, limit, keyword, type, order_by: orderBy }, + }) +} + +// 编辑用户 +export function editUser(user: User) { + return http.put("admin/user", user) +} + +// 重置用户密码 +export function resetPassword(userID: number) { + return http.post("admin/reset_password", { id: userID }) +} + +// 导入用户 +export function importUsers(users: string[][]) { + return http.post("admin/user", { users }) +} + +// 批量删除用户 +export function deleteUsers(userIDs: number[]) { + return http.delete("admin/user", { params: { id: userIDs.join(",") } }) +} + +export function getContestList(offset = 0, limit = 10, keyword: string) { + return http.get("admin/contest", { + params: { paging: true, offset, limit, keyword }, + }) +} + +// 上传图片 +export async function uploadImage(file: File): Promise { + const form = new window.FormData() + form.append("image", file) + // 该端点不走 { error, data } 信封,直接返回上传结果 + const res = (await http.post("admin/upload_image", form, { + headers: { "content-type": "multipart/form-data" }, + })) as unknown as { success: boolean; file_path: string; msg: "Success" } + return res.success ? res.file_path : "" +} + +// 上传测试用例;SQL 题的压缩包是 1.sql..N.sql(每个文件一个测试点的建表+数据脚本) +export function uploadTestcases(file: File, options: { sql?: boolean } = {}) { + const form = new window.FormData() + form.append("file", file) + if (options.sql) { + form.append("sql", "1") + } + return http.post("admin/test_case", form, { + headers: { "content-type": "multipart/form-data" }, + }) +} + +// SQL 题测试点预览:后端跑一遍初始化脚本+标准答案,返回数据表和期望结果展示数据 +export function previewSQLTestcase(data: { + init_sql: string + ref_sql: string + mode: "query" | "modify" +}) { + return http.post("admin/sql_test_case_preview", data) +} + +// 回显已上传的 SQL 测试点脚本内容(按 1.sql, 2.sql... 排序) +export function getSQLTestcaseScripts(problemId: number) { + return http.get<{ name: string; content: string }[]>( + "admin/sql_test_case_scripts", + { params: { problem_id: problemId } }, + ) +} + +// AI 根据标准答案生成一个 SQL 测试点初始化脚本 +export function generateSQLTestcase(data: { + ref_sql: string + mode: "query" | "modify" +}) { + return http.post<{ sql: string }>("admin/sql_test_case_ai_gen", data) +} + +export function createProblem(problem: BlankProblem) { + return http.post("admin/problem", problem) +} + +export function createContestProblem(problem: BlankProblem) { + return http.post("admin/contest/problem", problem) +} + +export function createContest(contest: BlankContest) { + return http.post("admin/contest", contest) +} + +export function editContest(contest: Contest | BlankContest) { + return http.put("admin/contest", contest) +} + +export function cloneContest(contest_id: number) { + return http.post("admin/contest/clone", { contest_id }) +} + +export function getContest(id: string) { + return http.get("admin/contest", { + params: { id }, + }) +} + +export function addProblemForContest( + contestID: string, + problemID: number, + displayID: string, +) { + return http.post("admin/contest/add_problem_from_public", { + contest_id: contestID, + problem_id: problemID, + display_id: displayID, + }) +} + +export function getWebsite() { + return http.get("admin/website") +} + +export function editWebsite(data: WebsiteConfig) { + return http.post("admin/website", data) +} + +export function listInvalidTestcases() { + return http.get("admin/prune_test_case") +} + +export function pruneInvalidTestcases(id?: string) { + return http.delete("admin/prune_test_case", { params: { id } }) +} + +export function getJudgeServer() { + return http.get<{ token: string; servers: Server[] }>("admin/judge_server") +} + +export function deleteJudgeServer(hostname: string) { + return http.delete("admin/judge_server", { params: { hostname } }) +} + +export function getAnnouncementList(offset = 0, limit = 10) { + return http.get("admin/announcement", { + params: { paging: true, offset, limit }, + }) +} + +export function getAnnouncement(id: number) { + return http.get("admin/announcement", { params: { id } }) +} + +export function deleteAnnouncement(id: number) { + return http.delete("admin/announcement", { params: { id } }) +} + +export function editAnnouncement(announcement: AnnouncementEdit) { + return http.put("admin/announcement", announcement) +} + +export function createAnnouncement(announcement: AnnouncementEdit) { + return http.post("admin/announcement", announcement) +} + +export async function getTutorialList() { + const res = await http.get("admin/tutorial") + return res.data +} + +export async function getTutorial(id: number) { + const res = await http.get("admin/tutorial", { params: { id } }) + return res.data +} + +export async function createTutorial(data: Partial) { + const res = await http.post("admin/tutorial", data) + return res.data +} + +export async function updateTutorial(data: Partial) { + const res = await http.put("admin/tutorial", data) + return res.data +} + +export function deleteTutorial(id: number) { + return http.delete("admin/tutorial", { params: { id } }) +} + +export function setTutorialVisibility(id: number, is_public: boolean) { + return http.put("admin/tutorial/visibility", { id, is_public }) +} + +export async function getAdminExercises(tutorialId: number) { + const res = await http.get("admin/exercise", { + params: { tutorial_id: tutorialId }, + }) + return res.data +} + +export async function createExercise(data: { + tutorial_id: number + type: ExerciseType + data: object + order: number +}) { + const res = await http.post("admin/exercise", data) + return res.data +} + +export async function updateExercise(data: { + id: number + type: ExerciseType + data: object + order: number +}) { + const res = await http.put("admin/exercise", data) + return res.data as Exercise +} + +export function deleteExercise(id: number) { + return http.delete("admin/exercise", { params: { id } }) +} + +// 将竞赛题目转为公开题目 +export function makeProblemPublic(id: number, display_id: string) { + return http.post("admin/contest_problem/make_public", { + id, + display_id, + }) +} + +// 比赛辅助检查 +export function getACMHelperList(contest_id: number) { + return http.get("admin/contest/acm_helper", { + params: { contest_id }, + }) +} + +export function updateACMHelperChecked( + contest_id: number, + rank_id: number, + problem_id: string, + checked: boolean, +) { + return http.put("admin/contest/acm_helper", { + contest_id, + rank_id, + problem_id, + checked, + }) +} + +// 题单管理 API +export function getProblemSetList( + offset = 0, + limit = 10, + keyword = "", + difficulty = "", + status = "", +) { + return http.get("admin/problemset", { + params: { + offset, + limit, + keyword, + difficulty, + status, + }, + }) +} + +export function getProblemSetDetail(id: number) { + return http.get(`admin/problemset/${id}`) +} + +export function createProblemSet(data: { + title: string + description: string + difficulty: string + status: string + end_time?: Date | null +}) { + return http.post("admin/problemset", data) +} + +export function editProblemSet(data: { + id: number + title?: string + description?: string + difficulty?: string + status?: string + end_time?: Date | null + visible?: boolean +}) { + return http.put("admin/problemset", data) +} + +export function deleteProblemSet(id: number) { + return http.delete("admin/problemset", { params: { id } }) +} + +export function toggleProblemSetVisible(id: number) { + return http.put("admin/problemset/visible", { id }) +} + +export function updateProblemSetStatus(id: number, status: string) { + return http.put("admin/problemset/status", { id, status }) +} + +// 题单题目管理 API +export function getProblemSetProblems(problemSetId: number) { + return http.get(`admin/problemset/${problemSetId}/problems`) +} + +export function addProblemToSet( + problemSetId: number, + data: { + problem_id: string + order?: number + is_required?: boolean + score?: number + hint?: string + }, +) { + return http.post(`admin/problemset/${problemSetId}/problems`, data) +} + +export function editProblemInSet( + problemSetId: number, + problemSetProblemId: number, + data: { + order?: number + is_required?: boolean + score?: number + hint?: string + }, +) { + return http.put( + `admin/problemset/${problemSetId}/problems/${problemSetProblemId}`, + data, + ) +} + +export function removeProblemFromSet( + problemSetId: number, + problemSetProblemId: number, +) { + return http.delete( + `admin/problemset/${problemSetId}/problems/${problemSetProblemId}`, + ) +} + +// 题单奖章管理 API +export function getProblemSetBadges(problemSetId: number) { + return http.get(`admin/problemset/${problemSetId}/badges`) +} + +export function createProblemSetBadge( + problemSetId: number, + data: { + name: string + description: string + icon: string + condition_type: string + condition_value: number + level?: number + }, +) { + return http.post(`admin/problemset/${problemSetId}/badges`, data) +} + +export function editProblemSetBadge( + problemSetId: number, + badgeId: number, + data: { + name?: string + description?: string + icon?: string + condition_type?: string + condition_value?: number + level?: number + }, +) { + return http.put(`admin/problemset/${problemSetId}/badges/${badgeId}`, data) +} + +export function deleteProblemSetBadge(problemSetId: number, badgeId: number) { + return http.delete(`admin/problemset/${problemSetId}/badges/${badgeId}`) +} + +// 题单进度管理 API +export function getProblemSetProgress(problemSetId: number) { + return http.get(`admin/problemset/${problemSetId}/progress`) +} + +export function removeUserFromProblemSet(problemSetId: number, userId: number) { + return http.delete(`admin/problemset/${problemSetId}/progress/${userId}`) +} + +// 学生卡点分析 +export function getStuckProblems() { + return http.get("admin/problem/stuck") +} + +export function getTopACTrend(params: { + since_year: number + until_year: number + min_per_year: number +}) { + return http.get("admin/problem/top_ac_trend", { params }) +} + +// AI 学习分析报告 +export function getAIReportList(offset = 0, limit = 10, username = "") { + return http.get("admin/ai/reports", { + params: { paging: true, offset, limit, username: username || undefined }, + }) +} + +export function getAIReportDetail(id: number) { + return http.get("admin/ai/reports", { params: { id } }) +} + +export function pinAIReport(id: number) { + return http.post("admin/ai/reports", { id }) +} + +export function getPinnedAIReports() { + return http.get("admin/ai/reports", { params: { pinned_only: "true" } }) +} + +// ==================== 成就 ==================== + +export interface AdminAchievement { + id: number + name: string + description: string + icon: string + rarity: string + hidden: boolean + metric: string + metric_name: string + operator: "gte" | "lte" + threshold: number + visible: boolean + unlock_count: number + order: number + create_time: string +} + +export interface MetricOption { + key: string + name: string + help_text: string +} + +export function getAdminAchievements() { + return http.get("admin/achievement") +} + +export function getMetricOptions() { + return http.get("admin/achievement/metrics") +} + +export function createAchievement(data: Partial) { + return http.post("admin/achievement", data) +} + +export function updateAchievement(data: Partial) { + return http.put("admin/achievement", data) +} + +export function deleteAchievement(id: number) { + return http.delete("admin/achievement", { params: { id } }) +} diff --git a/apps/web/src/admin/communication/messages.vue b/apps/web/src/admin/communication/messages.vue new file mode 100644 index 0000000..84e81e4 --- /dev/null +++ b/apps/web/src/admin/communication/messages.vue @@ -0,0 +1,2 @@ + + diff --git a/apps/web/src/admin/contest/components/Actions.vue b/apps/web/src/admin/contest/components/Actions.vue new file mode 100644 index 0000000..5f1a141 --- /dev/null +++ b/apps/web/src/admin/contest/components/Actions.vue @@ -0,0 +1,68 @@ + + + diff --git a/apps/web/src/admin/contest/detail.vue b/apps/web/src/admin/contest/detail.vue new file mode 100644 index 0000000..4aa66f2 --- /dev/null +++ b/apps/web/src/admin/contest/detail.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/apps/web/src/admin/contest/helper.vue b/apps/web/src/admin/contest/helper.vue new file mode 100644 index 0000000..0dbd966 --- /dev/null +++ b/apps/web/src/admin/contest/helper.vue @@ -0,0 +1,326 @@ + + + + + diff --git a/apps/web/src/admin/contest/list.vue b/apps/web/src/admin/contest/list.vue new file mode 100644 index 0000000..4d16ae0 --- /dev/null +++ b/apps/web/src/admin/contest/list.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/apps/web/src/admin/problem/Stuck.vue b/apps/web/src/admin/problem/Stuck.vue new file mode 100644 index 0000000..37824f2 --- /dev/null +++ b/apps/web/src/admin/problem/Stuck.vue @@ -0,0 +1,56 @@ + + + diff --git a/apps/web/src/admin/problem/TopACTrend.vue b/apps/web/src/admin/problem/TopACTrend.vue new file mode 100644 index 0000000..d9f3046 --- /dev/null +++ b/apps/web/src/admin/problem/TopACTrend.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/apps/web/src/admin/problem/components/Actions.vue b/apps/web/src/admin/problem/components/Actions.vue new file mode 100644 index 0000000..d958a82 --- /dev/null +++ b/apps/web/src/admin/problem/components/Actions.vue @@ -0,0 +1,160 @@ + + diff --git a/apps/web/src/admin/problem/components/AddButton.vue b/apps/web/src/admin/problem/components/AddButton.vue new file mode 100644 index 0000000..f80e089 --- /dev/null +++ b/apps/web/src/admin/problem/components/AddButton.vue @@ -0,0 +1,47 @@ + + + diff --git a/apps/web/src/admin/problem/components/AstRulesEditor.vue b/apps/web/src/admin/problem/components/AstRulesEditor.vue new file mode 100644 index 0000000..3c5aedf --- /dev/null +++ b/apps/web/src/admin/problem/components/AstRulesEditor.vue @@ -0,0 +1,397 @@ + + + diff --git a/apps/web/src/admin/problem/components/BatchTagModal.vue b/apps/web/src/admin/problem/components/BatchTagModal.vue new file mode 100644 index 0000000..1e780db --- /dev/null +++ b/apps/web/src/admin/problem/components/BatchTagModal.vue @@ -0,0 +1,109 @@ + + + diff --git a/apps/web/src/admin/problem/components/Modal.vue b/apps/web/src/admin/problem/components/Modal.vue new file mode 100644 index 0000000..7475af7 --- /dev/null +++ b/apps/web/src/admin/problem/components/Modal.vue @@ -0,0 +1,94 @@ + + + diff --git a/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue b/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue new file mode 100644 index 0000000..f1aa2ba --- /dev/null +++ b/apps/web/src/admin/problem/components/SQLTestcaseEditor.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/apps/web/src/admin/problem/components/TagProblemsModal.vue b/apps/web/src/admin/problem/components/TagProblemsModal.vue new file mode 100644 index 0000000..d5558ea --- /dev/null +++ b/apps/web/src/admin/problem/components/TagProblemsModal.vue @@ -0,0 +1,147 @@ + + + diff --git a/apps/web/src/admin/problem/components/TestcaseGenerator.vue b/apps/web/src/admin/problem/components/TestcaseGenerator.vue new file mode 100644 index 0000000..589ce29 --- /dev/null +++ b/apps/web/src/admin/problem/components/TestcaseGenerator.vue @@ -0,0 +1,263 @@ + + + diff --git a/apps/web/src/admin/problem/detail.vue b/apps/web/src/admin/problem/detail.vue new file mode 100644 index 0000000..568e016 --- /dev/null +++ b/apps/web/src/admin/problem/detail.vue @@ -0,0 +1,920 @@ + + + + + diff --git a/apps/web/src/admin/problem/list.vue b/apps/web/src/admin/problem/list.vue new file mode 100644 index 0000000..94161a8 --- /dev/null +++ b/apps/web/src/admin/problem/list.vue @@ -0,0 +1,335 @@ + + + + + diff --git a/apps/web/src/admin/problem/tags.vue b/apps/web/src/admin/problem/tags.vue new file mode 100644 index 0000000..9b0befe --- /dev/null +++ b/apps/web/src/admin/problem/tags.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/apps/web/src/admin/problemset/components/Actions.vue b/apps/web/src/admin/problemset/components/Actions.vue new file mode 100644 index 0000000..97d1b7b --- /dev/null +++ b/apps/web/src/admin/problemset/components/Actions.vue @@ -0,0 +1,105 @@ + + + diff --git a/apps/web/src/admin/problemset/components/AddBadgeModal.vue b/apps/web/src/admin/problemset/components/AddBadgeModal.vue new file mode 100644 index 0000000..a7a0f60 --- /dev/null +++ b/apps/web/src/admin/problemset/components/AddBadgeModal.vue @@ -0,0 +1,162 @@ + + + diff --git a/apps/web/src/admin/problemset/components/AddProblemModal.vue b/apps/web/src/admin/problemset/components/AddProblemModal.vue new file mode 100644 index 0000000..90cfece --- /dev/null +++ b/apps/web/src/admin/problemset/components/AddProblemModal.vue @@ -0,0 +1,103 @@ + + + diff --git a/apps/web/src/admin/problemset/components/BadgeManagement.vue b/apps/web/src/admin/problemset/components/BadgeManagement.vue new file mode 100644 index 0000000..91e4677 --- /dev/null +++ b/apps/web/src/admin/problemset/components/BadgeManagement.vue @@ -0,0 +1,96 @@ + + + diff --git a/apps/web/src/admin/problemset/components/EditBadgeModal.vue b/apps/web/src/admin/problemset/components/EditBadgeModal.vue new file mode 100644 index 0000000..7a0a938 --- /dev/null +++ b/apps/web/src/admin/problemset/components/EditBadgeModal.vue @@ -0,0 +1,167 @@ + + + diff --git a/apps/web/src/admin/problemset/components/EditProblemModal.vue b/apps/web/src/admin/problemset/components/EditProblemModal.vue new file mode 100644 index 0000000..6709334 --- /dev/null +++ b/apps/web/src/admin/problemset/components/EditProblemModal.vue @@ -0,0 +1,100 @@ + + + diff --git a/apps/web/src/admin/problemset/components/ProblemManagement.vue b/apps/web/src/admin/problemset/components/ProblemManagement.vue new file mode 100644 index 0000000..2c337a8 --- /dev/null +++ b/apps/web/src/admin/problemset/components/ProblemManagement.vue @@ -0,0 +1,73 @@ + + + diff --git a/apps/web/src/admin/problemset/components/ProblemSetInfo.vue b/apps/web/src/admin/problemset/components/ProblemSetInfo.vue new file mode 100644 index 0000000..ccbc88a --- /dev/null +++ b/apps/web/src/admin/problemset/components/ProblemSetInfo.vue @@ -0,0 +1,70 @@ + + + diff --git a/apps/web/src/admin/problemset/components/ProgressManagement.vue b/apps/web/src/admin/problemset/components/ProgressManagement.vue new file mode 100644 index 0000000..4db070a --- /dev/null +++ b/apps/web/src/admin/problemset/components/ProgressManagement.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web/src/admin/problemset/detail.vue b/apps/web/src/admin/problemset/detail.vue new file mode 100644 index 0000000..7b2c883 --- /dev/null +++ b/apps/web/src/admin/problemset/detail.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/apps/web/src/admin/problemset/edit.vue b/apps/web/src/admin/problemset/edit.vue new file mode 100644 index 0000000..157046b --- /dev/null +++ b/apps/web/src/admin/problemset/edit.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/apps/web/src/admin/problemset/list.vue b/apps/web/src/admin/problemset/list.vue new file mode 100644 index 0000000..736ed6b --- /dev/null +++ b/apps/web/src/admin/problemset/list.vue @@ -0,0 +1,212 @@ + + + + + diff --git a/apps/web/src/admin/setting/config.vue b/apps/web/src/admin/setting/config.vue new file mode 100644 index 0000000..23fdc42 --- /dev/null +++ b/apps/web/src/admin/setting/config.vue @@ -0,0 +1,314 @@ + + + + + diff --git a/apps/web/src/admin/setting/home.vue b/apps/web/src/admin/setting/home.vue new file mode 100644 index 0000000..5a8dbc6 --- /dev/null +++ b/apps/web/src/admin/setting/home.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/apps/web/src/admin/transforms.ts b/apps/web/src/admin/transforms.ts new file mode 100644 index 0000000..63e99b6 --- /dev/null +++ b/apps/web/src/admin/transforms.ts @@ -0,0 +1,20 @@ +import type { AdminProblem } from "utils/types" + +// 把后端的 AdminProblem 塑形成管理端列表项,与请求逻辑解耦。 +export function toProblemListItem(result: AdminProblem) { + return { + id: result.id, + _id: result._id, + title: result.title, + username: result.created_by.username, + create_time: result.create_time, + visible: result.visible, + difficulty: result.difficulty, + tags: result.tags, + has_ast_rules: result.has_ast_rules, + allow_flowchart: result.allow_flowchart, + show_flowchart: result.show_flowchart, + // 比赛题目列表接口不返回这个字段 + top_reaction: result.top_reaction ?? null, + } +} diff --git a/apps/web/src/admin/tutorial/components/Actions.vue b/apps/web/src/admin/tutorial/components/Actions.vue new file mode 100644 index 0000000..e2e535b --- /dev/null +++ b/apps/web/src/admin/tutorial/components/Actions.vue @@ -0,0 +1,43 @@ + + + diff --git a/apps/web/src/admin/tutorial/components/ExerciseManager.vue b/apps/web/src/admin/tutorial/components/ExerciseManager.vue new file mode 100644 index 0000000..9c572bd --- /dev/null +++ b/apps/web/src/admin/tutorial/components/ExerciseManager.vue @@ -0,0 +1,652 @@ + + + diff --git a/apps/web/src/admin/tutorial/detail.vue b/apps/web/src/admin/tutorial/detail.vue new file mode 100644 index 0000000..a60b058 --- /dev/null +++ b/apps/web/src/admin/tutorial/detail.vue @@ -0,0 +1,133 @@ + + + diff --git a/apps/web/src/admin/tutorial/list.vue b/apps/web/src/admin/tutorial/list.vue new file mode 100644 index 0000000..1b1581a --- /dev/null +++ b/apps/web/src/admin/tutorial/list.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/apps/web/src/admin/user/components/Actions.vue b/apps/web/src/admin/user/components/Actions.vue new file mode 100644 index 0000000..e64fe7d --- /dev/null +++ b/apps/web/src/admin/user/components/Actions.vue @@ -0,0 +1,55 @@ + + diff --git a/apps/web/src/admin/user/components/Name.vue b/apps/web/src/admin/user/components/Name.vue new file mode 100644 index 0000000..72462ac --- /dev/null +++ b/apps/web/src/admin/user/components/Name.vue @@ -0,0 +1,42 @@ + + diff --git a/apps/web/src/admin/user/generate.vue b/apps/web/src/admin/user/generate.vue new file mode 100644 index 0000000..d613f3b --- /dev/null +++ b/apps/web/src/admin/user/generate.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/apps/web/src/admin/user/list.vue b/apps/web/src/admin/user/list.vue new file mode 100644 index 0000000..bc49bae --- /dev/null +++ b/apps/web/src/admin/user/list.vue @@ -0,0 +1,359 @@ + + + + + diff --git a/apps/web/src/env.d.ts b/apps/web/src/env.d.ts new file mode 100644 index 0000000..671f9d4 --- /dev/null +++ b/apps/web/src/env.d.ts @@ -0,0 +1,16 @@ +/// + +interface ImportMetaEnv { + readonly PUBLIC_ENV: string + readonly PUBLIC_MAXKB_URL: string + readonly PUBLIC_OJ_URL: string + readonly PUBLIC_CODE_URL: string + readonly PUBLIC_JUDGE0_URL: string + readonly PUBLIC_ICONIFY_URL: string + readonly PUBLIC_SIGNALING_URL: string + readonly PUBLIC_WS_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/apps/web/src/index.css b/apps/web/src/index.css new file mode 100644 index 0000000..fcb8334 --- /dev/null +++ b/apps/web/src/index.css @@ -0,0 +1,29 @@ +body { + height: 100vh; +} + +.md-editor-dark { + --md-bk-color: var(--n-body-color) !important; +} + +.md-editor-dark div.vuepress-theme { + --md-theme-color: var(--n-text-color) !important; +} + +.oj-mermaid-surface { + box-sizing: border-box; + padding: 18px; + overflow: auto; + border: 1px solid rgba(148, 163, 184, 0.24); + border-radius: 8px; +} + +.oj-mermaid-surface > svg { + max-width: 100%; +} + +::view-transition-old(root), +::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; +} diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts new file mode 100644 index 0000000..e84d4f2 --- /dev/null +++ b/apps/web/src/main.ts @@ -0,0 +1,92 @@ +import { addAPIProvider } from "@iconify/vue" +import { createPinia } from "pinia" +import { createRouter, createWebHistory } from "vue-router" + +import { STORAGE_KEY } from "utils/constants" +import storage from "utils/storage" + +import App from "./App.vue" +import { admins, ojs } from "./routes" + +const router = createRouter({ + history: createWebHistory(), + routes: [ojs, admins], +}) + +const pinia = createPinia() + +// 创建 app 并安装插件 +const app = createApp(App) +app.use(pinia) +app.use(router) + +// 现在可以安全地使用 Store +import { useAuthModalStore } from "./shared/store/authModal" +import { useUserStore } from "./shared/store/user" + +const authStore = useAuthModalStore() + +router.beforeEach(async (to, from, next) => { + // 检查是否需要认证 + if (to.matched.some((record) => record.meta.requiresAuth)) { + if (!storage.get(STORAGE_KEY.AUTHED)) { + authStore.openLoginModal() + next("/") + return + } + } + + // 检查权限 + if ( + to.matched.some( + (record) => + record.meta.requiresSuperAdmin || + record.meta.requiresTeacherAdmin || + record.meta.requiresProblemPermission, + ) + ) { + if (!storage.get(STORAGE_KEY.AUTHED)) { + authStore.openLoginModal() + next("/") + return + } + + const userStore = useUserStore() + if (!userStore.user) { + try { + await userStore.getMyProfile() + } catch (error) { + next("/") + return + } + } + if (to.matched.some((record) => record.meta.requiresSuperAdmin)) { + if (!userStore.isSuperAdmin) { + next("/") + return + } + } else if (to.matched.some((record) => record.meta.requiresTeacherAdmin)) { + if (!userStore.isTeacherOrAbove) { + next("/") + return + } + } else if ( + to.matched.some((record) => record.meta.requiresProblemPermission) + ) { + if (!userStore.hasProblemPermission) { + next("/") + return + } + } + } + + next() +}) + +app.mount("#app") + +if (!!import.meta.env.PUBLIC_ICONIFY_URL) { + addAPIProvider("", { + resources: [import.meta.env.PUBLIC_ICONIFY_URL], + }) +} diff --git a/apps/web/src/mermaid-legacy.d.ts b/apps/web/src/mermaid-legacy.d.ts new file mode 100644 index 0000000..a0c9382 --- /dev/null +++ b/apps/web/src/mermaid-legacy.d.ts @@ -0,0 +1 @@ +declare module "mermaid-legacy" diff --git a/apps/web/src/oj/achievement/api.ts b/apps/web/src/oj/achievement/api.ts new file mode 100644 index 0000000..8d682f5 --- /dev/null +++ b/apps/web/src/oj/achievement/api.ts @@ -0,0 +1,27 @@ +import http from "utils/http" +import type { + Achievement, + AchievementSummary, + PendingAchievement, +} from "utils/types" + +export function getAchievements(name?: string) { + return http.get<{ username: string; achievements: Achievement[] }>( + "achievements", + { params: name ? { name } : {} }, + ) +} + +export function getAchievementSummary(name?: string) { + return http.get("achievements/summary", { + params: name ? { name } : {}, + }) +} + +export function getPendingAchievements() { + return http.get("achievements/pending") +} + +export function markAchievementsRead(ids: number[]) { + return http.post("achievements/pending", { ids }) +} diff --git a/apps/web/src/oj/achievement/components/AchievementCard.vue b/apps/web/src/oj/achievement/components/AchievementCard.vue new file mode 100644 index 0000000..05b846a --- /dev/null +++ b/apps/web/src/oj/achievement/components/AchievementCard.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/apps/web/src/oj/achievement/index.vue b/apps/web/src/oj/achievement/index.vue new file mode 100644 index 0000000..4b54628 --- /dev/null +++ b/apps/web/src/oj/achievement/index.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/apps/web/src/oj/ai/analysis.vue b/apps/web/src/oj/ai/analysis.vue new file mode 100644 index 0000000..4eee1c7 --- /dev/null +++ b/apps/web/src/oj/ai/analysis.vue @@ -0,0 +1,122 @@ + + diff --git a/apps/web/src/oj/ai/components/AI.vue b/apps/web/src/oj/ai/components/AI.vue new file mode 100644 index 0000000..a6865fa --- /dev/null +++ b/apps/web/src/oj/ai/components/AI.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web/src/oj/ai/components/DifficultyGradeChart.vue b/apps/web/src/oj/ai/components/DifficultyGradeChart.vue new file mode 100644 index 0000000..6add8be --- /dev/null +++ b/apps/web/src/oj/ai/components/DifficultyGradeChart.vue @@ -0,0 +1,144 @@ + + + diff --git a/apps/web/src/oj/ai/components/DurationChart.vue b/apps/web/src/oj/ai/components/DurationChart.vue new file mode 100644 index 0000000..502d7ca --- /dev/null +++ b/apps/web/src/oj/ai/components/DurationChart.vue @@ -0,0 +1,204 @@ + + + diff --git a/apps/web/src/oj/ai/components/EfficiencyChart.vue b/apps/web/src/oj/ai/components/EfficiencyChart.vue new file mode 100644 index 0000000..8b74bf4 --- /dev/null +++ b/apps/web/src/oj/ai/components/EfficiencyChart.vue @@ -0,0 +1,234 @@ + + + diff --git a/apps/web/src/oj/ai/components/Grade.vue b/apps/web/src/oj/ai/components/Grade.vue new file mode 100644 index 0000000..b5096d1 --- /dev/null +++ b/apps/web/src/oj/ai/components/Grade.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web/src/oj/ai/components/Heatmap.vue b/apps/web/src/oj/ai/components/Heatmap.vue new file mode 100644 index 0000000..2c4f4a8 --- /dev/null +++ b/apps/web/src/oj/ai/components/Heatmap.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/apps/web/src/oj/ai/components/Overview.vue b/apps/web/src/oj/ai/components/Overview.vue new file mode 100644 index 0000000..4355f4d --- /dev/null +++ b/apps/web/src/oj/ai/components/Overview.vue @@ -0,0 +1,63 @@ + + + diff --git a/apps/web/src/oj/ai/components/ProgressChart.vue b/apps/web/src/oj/ai/components/ProgressChart.vue new file mode 100644 index 0000000..3f55eed --- /dev/null +++ b/apps/web/src/oj/ai/components/ProgressChart.vue @@ -0,0 +1,271 @@ + + + diff --git a/apps/web/src/oj/ai/components/RankDistributionChart.vue b/apps/web/src/oj/ai/components/RankDistributionChart.vue new file mode 100644 index 0000000..db51802 --- /dev/null +++ b/apps/web/src/oj/ai/components/RankDistributionChart.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web/src/oj/ai/components/SolvedTable.vue b/apps/web/src/oj/ai/components/SolvedTable.vue new file mode 100644 index 0000000..60ef450 --- /dev/null +++ b/apps/web/src/oj/ai/components/SolvedTable.vue @@ -0,0 +1,154 @@ + + + diff --git a/apps/web/src/oj/ai/components/StreakStats.vue b/apps/web/src/oj/ai/components/StreakStats.vue new file mode 100644 index 0000000..4e05a32 --- /dev/null +++ b/apps/web/src/oj/ai/components/StreakStats.vue @@ -0,0 +1,177 @@ + + + diff --git a/apps/web/src/oj/ai/components/TagTitle.vue b/apps/web/src/oj/ai/components/TagTitle.vue new file mode 100644 index 0000000..48d91e3 --- /dev/null +++ b/apps/web/src/oj/ai/components/TagTitle.vue @@ -0,0 +1,21 @@ + + diff --git a/apps/web/src/oj/ai/components/TagsRadarChart.vue b/apps/web/src/oj/ai/components/TagsRadarChart.vue new file mode 100644 index 0000000..42b6e37 --- /dev/null +++ b/apps/web/src/oj/ai/components/TagsRadarChart.vue @@ -0,0 +1,154 @@ + + + diff --git a/apps/web/src/oj/ai/components/TimeActivityHeatmap.vue b/apps/web/src/oj/ai/components/TimeActivityHeatmap.vue new file mode 100644 index 0000000..9711864 --- /dev/null +++ b/apps/web/src/oj/ai/components/TimeActivityHeatmap.vue @@ -0,0 +1,154 @@ + + + diff --git a/apps/web/src/oj/announcement/components/TitleWithTag.vue b/apps/web/src/oj/announcement/components/TitleWithTag.vue new file mode 100644 index 0000000..dbb7ec0 --- /dev/null +++ b/apps/web/src/oj/announcement/components/TitleWithTag.vue @@ -0,0 +1,14 @@ + + diff --git a/apps/web/src/oj/announcement/list.vue b/apps/web/src/oj/announcement/list.vue new file mode 100644 index 0000000..a849691 --- /dev/null +++ b/apps/web/src/oj/announcement/list.vue @@ -0,0 +1,94 @@ + + diff --git a/apps/web/src/oj/api.ts b/apps/web/src/oj/api.ts new file mode 100644 index 0000000..efe618d --- /dev/null +++ b/apps/web/src/oj/api.ts @@ -0,0 +1,435 @@ +import http from "utils/http" +import { filterResult } from "oj/transforms" +import type { + Exercise, + Problem, + ReactionKey, + ReactionState, + Submission, + SubmissionListPayload, + SubmitCodePayload, +} from "utils/types" + +export function getWebsiteConfig() { + return http.get("website") +} + +export async function getProblemList( + offset = 0, + limit = 10, + searchParams: any = {}, +) { + const res = await http.get<{ results: Problem[]; total: number }>("problem", { + params: { paging: true, offset, limit, ...searchParams }, + }) + return { + results: res.data.results.map(filterResult), + total: res.data.total, + } +} + +export function getAuthors(all = false) { + return http.get("problem/author", { + params: { + all: all ? "1" : "0", + }, + }) +} + +export function getRandomProblemID() { + return http.get("pickone") +} + +export function getProblem(problemID: string, contestID: string) { + const endpoint = !!contestID ? "contest/problem" : "problem" + return http.get(endpoint, { + params: { + problem_id: problemID, + contest_id: contestID, + }, + }) +} + +export function getProblemBeatRate(problemID: number) { + return http.get("problem/beat_count", { params: { problem_id: problemID } }) +} + +export function getSubmission(id: string) { + return http.get("submission", { + params: { id }, + }) +} + +export function submitCode(data: SubmitCodePayload) { + return http.post("submission", data) +} + +export function formatCode(data: { code: string; language: string }) { + return http.post<{ code: string }>("format_code", data) +} + +export function getSubmissions(params: Partial) { + const endpoint = !!params.contest_id ? "contest_submissions" : "submissions" + return http.get(endpoint, { params }) +} + +export function getRankOfProblem(problem_id: string) { + return http.get("user_problem_rank", { params: { problem_id: problem_id } }) +} + +export function getTodaySubmissionCount(language?: string) { + return http.get("submissions/today_count", { params: { language } }) +} + +export function adminRejudge(id: string) { + return http.get("admin/submission/rejudge", { + params: { id }, + }) +} + +export function getSubmissionStatistics( + duration: { start?: string; end: string }, + problemID?: string, + username?: string, +) { + return http.get("admin/submission/statistics", { + params: { + ...duration, + problem_id: problemID, + username, + }, + }) +} + +export function getRank( + offset: number, + limit: number, + n: number, + username?: string, +) { + return http.get("user_rank", { + params: { offset, limit, rule: "acm", username, n }, + }) +} + +export function getActivityRank(start: string) { + return http.get("user_activity_rank", { + params: { start }, + }) +} + +export function getClassRank(grade?: number | null) { + return http.get("class_rank", { + params: { grade }, + }) +} + +export function getUserClassRank( + scope?: "all" | "window", + offset?: number, + limit?: number, +) { + return http.get("user_class_rank", { params: { scope, offset, limit } }) +} + +export function getClassPK( + classNames: string[], + startTime?: string, + endTime?: string, +) { + const payload: any = { + class_name: classNames, + } + if (startTime) { + payload.start_time = startTime + } + if (endTime) { + payload.end_time = endTime + } + return http.post("class_pk", payload) +} + +export function getContestList(query: { + offset: number + limit: number + keyword: string + status: string + tag: string +}) { + return http.get("contests", { params: query }) +} + +export function getContest(id: string) { + return http.get("contest", { params: { id } }) +} + +export function getContestAccess(id: string) { + return http.get("contest/access", { params: { contest_id: id } }) +} + +export function checkContestPassword(contestID: string, password: string) { + return http.post("contest/password", { + contest_id: contestID, + password, + }) +} + +export async function getContestProblems(contestID: string) { + const res = await http.get("contest/problem", { + params: { contest_id: contestID }, + }) + return res.data.map(filterResult) +} + +export function getContestRank( + contestID: string, + query: { limit: number; offset: number }, +) { + return http.get("contest_rank", { + params: { + contest_id: contestID, + ...query, + }, + }) +} + +export function uploadAvatar(file: File) { + const form = new window.FormData() + form.append("image", file) + return http.post("upload_avatar", form, { + headers: { "content-type": "multipart/form-data" }, + }) +} + +export function updateProfile(data: { real_name: string; mood: string }) { + return http.put("profile", data) +} + +export function getAnnouncementList(offset = 0, limit = 10) { + return http.get("announcement", { params: { limit, offset } }) +} + +export function getAnnouncement(id: number) { + return http.get("announcement", { params: { id } }) +} + +export function createMessage(data: { + recipient: number + message: string + submission: string +}) { + return http.post("message", data) +} + +export function getMessageList(offset = 0, limit = 10) { + return http.get("message", { params: { limit, offset } }) +} + +export function getReaction(problemID: number) { + return http.get("reaction", { + params: { problem_id: problemID }, + }) +} + +export function setReaction(problemID: number, type: ReactionKey) { + return http.post("reaction", { + problem_id: problemID, + type, + }) +} + +// TODO: 这个API有问题 +export function refreshUserProblemDisplayIds() { + return http.get("profile/fresh_display_id") +} + +export function getMetrics(userid: number) { + return http.get("metrics", { params: { userid } }) +} + +export function getTutorial(id: number) { + return http.get("tutorial", { params: { id } }) +} + +export function getTutorials(type: "python" | "c") { + return http.get("tutorials", { params: { type } }) +} + +export function getAIDetailData(start: string, end: string, username?: string) { + return http.get("ai/detail", { params: { start, end, username } }) +} + +export function getAIDurationData( + end: string, + duration: string, + username?: string, +) { + return http.get("ai/duration", { params: { end, duration, username } }) +} + +export function getAIHeatmapData(username?: string) { + return http.get("ai/heatmap", { params: username ? { username } : {} }) +} + +export function getAILoginSummary() { + return http.get("ai/login_summary") +} + +export function getAIPinnedReport() { + return http.get("ai/pinned") +} + +// ==================== 相似题目推荐 ==================== + +export function getSimilarProblems(problemId: string) { + return http.get("problem/similar", { params: { problem_id: problemId } }) +} + +export interface YearlyACData { + year: number + total: number + accepted: number + ac_rate: number +} + +export function getProblemYearlyAC(problemId: string) { + return http.get("problem/yearly_ac", { + params: { problem_id: problemId }, + }) +} + +// ==================== 流程图相关API ==================== + +export function submitFlowchart(data: { + problem_id: number + mermaid_code: string + flowchart_data: any // 这个是压缩之后的,元数据太长了 +}) { + return http.post("flowchart/submission", data) +} + +export function getFlowchartSubmission(id: string) { + return http.get("flowchart/submission", { + params: { id }, + }) +} + +export function getFlowchartSubmissions(params: { + username?: string + problem_id?: string + myself?: string + offset?: number + limit?: number + today?: string + grade?: string +}) { + return http.get("flowchart/submissions", { params }) +} + +export function getFlowchartStatistics( + duration: { start?: string; end: string }, + problemID?: string, + username?: string, +) { + return http.get("admin/flowchart/statistics", { + params: { + ...duration, + problem_id: problemID, + username, + }, + }) +} + +export function retryFlowchartSubmission(submissionId: string) { + return http.post("flowchart/submission/retry", { + submission_id: submissionId, + }) +} + +export function getCurrentProblemFlowchartSubmission(problemId: number) { + return http.get("flowchart/submission/current", { + params: { problem_id: problemId }, + }) +} + +export function getFlowchartSubmissionDetail(problemId: number, page = 0) { + return http.get("flowchart/submission/detail", { + params: { problem_id: problemId, page }, + }) +} + +// ==================== 题单相关API ==================== + +export function getProblemSetList( + offset = 0, + limit = 10, + keyword = "", + difficulty = "", + status = "", +) { + return http.get("problemset", { + params: { + offset, + limit, + keyword, + difficulty, + status, + }, + }) +} + +export function getProblemSetDetail(id: number) { + return http.get(`problemset/${id}`) +} + +export function getProblemSetProblems(problemSetId: number) { + return http.get(`problemset/${problemSetId}/problems`) +} + +export function joinProblemSet(problemSetId: number) { + return http.post("problemset/progress", { + problemset_id: problemSetId, + }) +} + +export function updateProblemSetProgress( + problemSetId: number, + problemId: number, + submissionId: string, +) { + return http.put("problemset/progress", { + problemset_id: problemSetId, + problem_id: problemId, + submission_id: submissionId, + }) +} + +// 获取用户徽章列表 +export function getUserBadges(username?: string) { + return http.get("user/badges", { params: username ? { username } : {} }) +} + +// 获取题单徽章列表 +export function getProblemSetBadges(problemSetId: number) { + return http.get(`problemset/${problemSetId}/badges`) +} + +// 获取题单用户进度列表 +export function getProblemSetUserProgress( + problemSetId: number, + params?: { + limit?: number + offset?: number + class_name?: string + completion_status?: "" | "completed" | "in_progress" | "not_started" + }, +) { + return http.get(`problemset/${problemSetId}/users_progress`, { params }) +} + +export async function getExercises(tutorialId: number): Promise { + const res = await http.get("exercises", { + params: { tutorial_id: tutorialId }, + }) + return res.data +} diff --git a/apps/web/src/oj/class/pk.vue b/apps/web/src/oj/class/pk.vue new file mode 100644 index 0000000..264608b --- /dev/null +++ b/apps/web/src/oj/class/pk.vue @@ -0,0 +1,1260 @@ + + + + + diff --git a/apps/web/src/oj/composables/syncStatus.ts b/apps/web/src/oj/composables/syncStatus.ts new file mode 100644 index 0000000..fcddcb2 --- /dev/null +++ b/apps/web/src/oj/composables/syncStatus.ts @@ -0,0 +1,73 @@ +import { ref, provide, inject } from "vue" + +/** + * 同步状态管理 composable + * 使用 provide/inject 模式在组件树中共享状态 + */ + +export interface SyncStatusState { + hadConnection: boolean + otherUser?: { name: string; isSuperAdmin: boolean } + lastLeftUser?: { name: string; isSuperAdmin: boolean } // 保存离开之人的信息 +} + +// 提供/注入的 key +export const SYNC_STATUS_KEY = Symbol("syncStatus") + +/** + * 创建同步状态实例 + * 每次调用创建新的状态实例 + */ +export function createSyncStatus() { + const otherUser = ref<{ name: string; isSuperAdmin: boolean }>() + const hadConnection = ref(false) + const lastLeftUser = ref<{ name: string; isSuperAdmin: boolean }>() + + const setOtherUser = (user?: { name: string; isSuperAdmin: boolean }) => { + // 如果之前有其他用户,现在没有了,说明用户离开了 + if (otherUser.value && !user) { + lastLeftUser.value = otherUser.value + } + otherUser.value = user + if (user) { + hadConnection.value = true + } + } + + const reset = () => { + otherUser.value = undefined + hadConnection.value = false + lastLeftUser.value = undefined + } + + return { + otherUser, + hadConnection, + lastLeftUser, + setOtherUser, + reset, + } +} + +/** + * 提供同步状态到子组件 + * 在父组件中调用 + */ +export function provideSyncStatus() { + const syncStatus = createSyncStatus() + provide(SYNC_STATUS_KEY, syncStatus) + return syncStatus +} + +/** + * 注入同步状态 + * 在子组件中调用,获取父组件提供的状态 + */ +export function injectSyncStatus() { + const syncStatus = + inject>(SYNC_STATUS_KEY) + if (!syncStatus) { + throw new Error("syncStatus must be provided by a parent component") + } + return syncStatus +} diff --git a/apps/web/src/oj/contest/components/AcAndSubmission.vue b/apps/web/src/oj/contest/components/AcAndSubmission.vue new file mode 100644 index 0000000..4f99a36 --- /dev/null +++ b/apps/web/src/oj/contest/components/AcAndSubmission.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/apps/web/src/oj/contest/components/ContestInfo.vue b/apps/web/src/oj/contest/components/ContestInfo.vue new file mode 100644 index 0000000..4f85d38 --- /dev/null +++ b/apps/web/src/oj/contest/components/ContestInfo.vue @@ -0,0 +1,42 @@ + + + diff --git a/apps/web/src/oj/contest/components/ContestMenu.vue b/apps/web/src/oj/contest/components/ContestMenu.vue new file mode 100644 index 0000000..3eece0b --- /dev/null +++ b/apps/web/src/oj/contest/components/ContestMenu.vue @@ -0,0 +1,56 @@ + + + diff --git a/apps/web/src/oj/contest/components/LineChart.vue b/apps/web/src/oj/contest/components/LineChart.vue new file mode 100644 index 0000000..ca65dd9 --- /dev/null +++ b/apps/web/src/oj/contest/components/LineChart.vue @@ -0,0 +1,231 @@ + + + + diff --git a/apps/web/src/oj/contest/detail.vue b/apps/web/src/oj/contest/detail.vue new file mode 100644 index 0000000..15ae6de --- /dev/null +++ b/apps/web/src/oj/contest/detail.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/apps/web/src/oj/contest/list.vue b/apps/web/src/oj/contest/list.vue new file mode 100644 index 0000000..f71a0ef --- /dev/null +++ b/apps/web/src/oj/contest/list.vue @@ -0,0 +1,180 @@ + + diff --git a/apps/web/src/oj/contest/pages/problems.vue b/apps/web/src/oj/contest/pages/problems.vue new file mode 100644 index 0000000..c3bd47f --- /dev/null +++ b/apps/web/src/oj/contest/pages/problems.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/apps/web/src/oj/contest/pages/rank.vue b/apps/web/src/oj/contest/pages/rank.vue new file mode 100644 index 0000000..46d0748 --- /dev/null +++ b/apps/web/src/oj/contest/pages/rank.vue @@ -0,0 +1,341 @@ + + + + diff --git a/apps/web/src/oj/flowchart/index.vue b/apps/web/src/oj/flowchart/index.vue new file mode 100644 index 0000000..cd8329d --- /dev/null +++ b/apps/web/src/oj/flowchart/index.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseDebug.vue b/apps/web/src/oj/learn/components/ExerciseDebug.vue new file mode 100644 index 0000000..b1ee170 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseDebug.vue @@ -0,0 +1,121 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseFill.vue b/apps/web/src/oj/learn/components/ExerciseFill.vue new file mode 100644 index 0000000..9979ec3 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseFill.vue @@ -0,0 +1,135 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseGroup.vue b/apps/web/src/oj/learn/components/ExerciseGroup.vue new file mode 100644 index 0000000..70c4cc0 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseGroup.vue @@ -0,0 +1,183 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseMatch.vue b/apps/web/src/oj/learn/components/ExerciseMatch.vue new file mode 100644 index 0000000..6dbc673 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseMatch.vue @@ -0,0 +1,184 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseMcq.vue b/apps/web/src/oj/learn/components/ExerciseMcq.vue new file mode 100644 index 0000000..71925c1 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseMcq.vue @@ -0,0 +1,123 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExercisePredict.vue b/apps/web/src/oj/learn/components/ExercisePredict.vue new file mode 100644 index 0000000..48d4c29 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExercisePredict.vue @@ -0,0 +1,92 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseSort.vue b/apps/web/src/oj/learn/components/ExerciseSort.vue new file mode 100644 index 0000000..cfa1d88 --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseSort.vue @@ -0,0 +1,130 @@ + + + diff --git a/apps/web/src/oj/learn/components/ExerciseWidget.vue b/apps/web/src/oj/learn/components/ExerciseWidget.vue new file mode 100644 index 0000000..ab479ff --- /dev/null +++ b/apps/web/src/oj/learn/components/ExerciseWidget.vue @@ -0,0 +1,41 @@ + + + diff --git a/apps/web/src/oj/learn/components/exercise-highlight.css b/apps/web/src/oj/learn/components/exercise-highlight.css new file mode 100644 index 0000000..5493423 --- /dev/null +++ b/apps/web/src/oj/learn/components/exercise-highlight.css @@ -0,0 +1,50 @@ +/* 练一练代码高亮配色(明 / 暗),由涉及代码高亮的题型组件统一引入 */ +.hljs-keyword, +.hljs-operator, +.hljs-selector-tag { + color: #d73a49; +} +.hljs-string, +.hljs-regexp, +.hljs-template-literal { + color: #032f62; +} +.hljs-comment, +.hljs-quote { + color: #6a737d; + font-style: italic; +} +.hljs-number, +.hljs-literal { + color: #005cc5; +} +.hljs-built_in, +.hljs-title.function_, +.hljs-class .hljs-title { + color: #6f42c1; +} + +.dark .hljs-keyword, +.dark .hljs-operator, +.dark .hljs-selector-tag { + color: #c678dd; +} +.dark .hljs-string, +.dark .hljs-regexp, +.dark .hljs-template-literal { + color: #98c379; +} +.dark .hljs-comment, +.dark .hljs-quote { + color: #7f848e; + font-style: italic; +} +.dark .hljs-number, +.dark .hljs-literal { + color: #e5c07b; +} +.dark .hljs-built_in, +.dark .hljs-title.function_, +.dark .hljs-class .hljs-title { + color: #61afef; +} diff --git a/apps/web/src/oj/learn/composables/useCodeHighlight.ts b/apps/web/src/oj/learn/composables/useCodeHighlight.ts new file mode 100644 index 0000000..3ac8261 --- /dev/null +++ b/apps/web/src/oj/learn/composables/useCodeHighlight.ts @@ -0,0 +1,41 @@ +import hljs from "highlight.js/lib/core" +import python from "highlight.js/lib/languages/python" +import c from "highlight.js/lib/languages/c" + +hljs.registerLanguage("python", python) +hljs.registerLanguage("c", c) + +export function escapeHtml(text: string): string { + return text.replace(/&/g, "&").replace(//g, ">") +} + +function normalizeLang(lang?: string): "python" | "c" | null { + return lang === "python" ? "python" : lang === "c" ? "c" : null +} + +// 把整段代码高亮为 HTML;不支持的语言或异常时回退为转义文本 +export function highlight(code: string, lang?: string): string { + const language = normalizeLang(lang) + if (language) { + try { + return hljs.highlight(code, { language }).value + } catch { + // fall through + } + } + return escapeHtml(code) +} + +// 按行高亮:整体高亮后再按行切分,保证跨行 token 着色正确,返回逐行 HTML 数组 +export function highlightLines(lines: string[], lang?: string): string[] { + const language = normalizeLang(lang) + if (language) { + try { + const html = hljs.highlight(lines.join("\n"), { language }).value + return html.split("\n") + } catch { + // fall through + } + } + return lines.map((line) => escapeHtml(line)) +} diff --git a/apps/web/src/oj/learn/composables/useExerciseParse.ts b/apps/web/src/oj/learn/composables/useExerciseParse.ts new file mode 100644 index 0000000..aa483ba --- /dev/null +++ b/apps/web/src/oj/learn/composables/useExerciseParse.ts @@ -0,0 +1,36 @@ +import type { Exercise } from "utils/types" + +type Segment = + { type: "md"; content: string } | { type: "exercise"; exercise: Exercise } + +export function parseExercises( + content: string, + exercises: Exercise[], +): Segment[] { + const exerciseMap = new Map(exercises.map((e) => [e.id, e])) + const segments: Segment[] = [] + const regex = /\[\[exercise:(\d+)\]\]/g + let lastIndex = 0 + let match: RegExpExecArray | null + + while ((match = regex.exec(content)) !== null) { + if (match.index > lastIndex) { + segments.push({ + type: "md", + content: content.slice(lastIndex, match.index), + }) + } + const id = parseInt(match[1]) + const exercise = exerciseMap.get(id) + if (exercise) { + segments.push({ type: "exercise", exercise }) + } + lastIndex = regex.lastIndex + } + + if (lastIndex < content.length) { + segments.push({ type: "md", content: content.slice(lastIndex) }) + } + + return segments +} diff --git a/apps/web/src/oj/learn/composables/useShuffle.ts b/apps/web/src/oj/learn/composables/useShuffle.ts new file mode 100644 index 0000000..9b77a45 --- /dev/null +++ b/apps/web/src/oj/learn/composables/useShuffle.ts @@ -0,0 +1,9 @@ +// Fisher–Yates 洗牌,返回新数组,不修改原数组 +export function shuffle(arr: T[]): T[] { + const a = [...arr] + for (let i = a.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[a[i], a[j]] = [a[j], a[i]] + } + return a +} diff --git a/apps/web/src/oj/learn/index.vue b/apps/web/src/oj/learn/index.vue new file mode 100644 index 0000000..6dc880f --- /dev/null +++ b/apps/web/src/oj/learn/index.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ContestEditor.vue b/apps/web/src/oj/problem/components/ContestEditor.vue new file mode 100644 index 0000000..316b250 --- /dev/null +++ b/apps/web/src/oj/problem/components/ContestEditor.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web/src/oj/problem/components/EditorForTest.vue b/apps/web/src/oj/problem/components/EditorForTest.vue new file mode 100644 index 0000000..62ac570 --- /dev/null +++ b/apps/web/src/oj/problem/components/EditorForTest.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/Form.vue b/apps/web/src/oj/problem/components/Form.vue new file mode 100644 index 0000000..7cecaef --- /dev/null +++ b/apps/web/src/oj/problem/components/Form.vue @@ -0,0 +1,291 @@ + + + diff --git a/apps/web/src/oj/problem/components/MyFlowchartTab.vue b/apps/web/src/oj/problem/components/MyFlowchartTab.vue new file mode 100644 index 0000000..10987f4 --- /dev/null +++ b/apps/web/src/oj/problem/components/MyFlowchartTab.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemContent.vue b/apps/web/src/oj/problem/components/ProblemContent.vue new file mode 100644 index 0000000..afcafe7 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemContent.vue @@ -0,0 +1,550 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemEditor.vue b/apps/web/src/oj/problem/components/ProblemEditor.vue new file mode 100644 index 0000000..ed6d7f1 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemEditor.vue @@ -0,0 +1,123 @@ + + + diff --git a/apps/web/src/oj/problem/components/ProblemFlowchart.vue b/apps/web/src/oj/problem/components/ProblemFlowchart.vue new file mode 100644 index 0000000..0ec970d --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemFlowchart.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemInfo.vue b/apps/web/src/oj/problem/components/ProblemInfo.vue new file mode 100644 index 0000000..dc7d3b2 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemInfo.vue @@ -0,0 +1,177 @@ + + + + diff --git a/apps/web/src/oj/problem/components/ProblemListTitle.vue b/apps/web/src/oj/problem/components/ProblemListTitle.vue new file mode 100644 index 0000000..345b7c4 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemListTitle.vue @@ -0,0 +1,28 @@ + + diff --git a/apps/web/src/oj/problem/components/ProblemReaction.vue b/apps/web/src/oj/problem/components/ProblemReaction.vue new file mode 100644 index 0000000..23a4344 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemReaction.vue @@ -0,0 +1,698 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemStatus.vue b/apps/web/src/oj/problem/components/ProblemStatus.vue new file mode 100644 index 0000000..2440b9f --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemStatus.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/ProblemSubmission.vue b/apps/web/src/oj/problem/components/ProblemSubmission.vue new file mode 100644 index 0000000..1a70976 --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemSubmission.vue @@ -0,0 +1,343 @@ + + + + diff --git a/apps/web/src/oj/problem/components/ProblemYearlyChart.vue b/apps/web/src/oj/problem/components/ProblemYearlyChart.vue new file mode 100644 index 0000000..240f49f --- /dev/null +++ b/apps/web/src/oj/problem/components/ProblemYearlyChart.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/SQLDataTable.vue b/apps/web/src/oj/problem/components/SQLDataTable.vue new file mode 100644 index 0000000..8330664 --- /dev/null +++ b/apps/web/src/oj/problem/components/SQLDataTable.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/SubmissionResult.vue b/apps/web/src/oj/problem/components/SubmissionResult.vue new file mode 100644 index 0000000..cce77ba --- /dev/null +++ b/apps/web/src/oj/problem/components/SubmissionResult.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/apps/web/src/oj/problem/components/SubmitCode.vue b/apps/web/src/oj/problem/components/SubmitCode.vue new file mode 100644 index 0000000..0140d81 --- /dev/null +++ b/apps/web/src/oj/problem/components/SubmitCode.vue @@ -0,0 +1,275 @@ + + + diff --git a/apps/web/src/oj/problem/components/SubmitFlowchart.vue b/apps/web/src/oj/problem/components/SubmitFlowchart.vue new file mode 100644 index 0000000..94910db --- /dev/null +++ b/apps/web/src/oj/problem/components/SubmitFlowchart.vue @@ -0,0 +1,422 @@ + + + + diff --git a/apps/web/src/oj/problem/components/submitButtonState.ts b/apps/web/src/oj/problem/components/submitButtonState.ts new file mode 100644 index 0000000..5d843cd --- /dev/null +++ b/apps/web/src/oj/problem/components/submitButtonState.ts @@ -0,0 +1,53 @@ +export interface SubmitButtonStateInput { + isAuthed: boolean + hasCode: boolean + isFormatting: boolean + isSubmitting: boolean + isJudging: boolean + isCooldown: boolean +} + +export interface SubmitButtonState { + disabled: boolean + label: string + icon: string +} + +export function getSubmitButtonState({ + isAuthed, + hasCode, + isFormatting, + isSubmitting, + isJudging, + isCooldown, +}: SubmitButtonStateInput): SubmitButtonState { + const disabled = + !isAuthed || + !hasCode || + isFormatting || + isSubmitting || + isJudging || + isCooldown + + let label = "提交代码" + if (!isAuthed) { + label = "请先登录" + } else if (isFormatting) { + label = "格式化中" + } else if (isSubmitting) { + label = "正在提交" + } else if (isJudging) { + label = "正在评分" + } else if (isCooldown) { + label = "正在冷却" + } + + const icon = + isFormatting || isSubmitting || isJudging + ? "eos-icons:loading" + : isCooldown + ? "ph:lightbulb-fill" + : "ph:play-fill" + + return { disabled, label, icon } +} diff --git a/apps/web/src/oj/problem/composables/useFireworks.ts b/apps/web/src/oj/problem/composables/useFireworks.ts new file mode 100644 index 0000000..8da2e86 --- /dev/null +++ b/apps/web/src/oj/problem/composables/useFireworks.ts @@ -0,0 +1,391 @@ +import confetti from "canvas-confetti" +import { getCurrentScope, onScopeDispose } from "vue" + +// 表情图标取自 @iconify 的 streamline-emojis 图标集,与项目内 用法保持一致 +const EMOJI_SVGS = [ + // 星星眼 + '', + // 点赞 + '', + // 火箭 + '', +] + +// 将 emoji SVG 转成 confetti 可用的彩色位图 Shape,缓存后供重复调用复用 +let emojiShapesPromise: Promise | null = null + +function svgToBitmapShape(svg: string): Promise { + return new Promise((resolve, reject) => { + const blob = new Blob([svg], { type: "image/svg+xml" }) + const url = URL.createObjectURL(blob) + const img = new Image() + img.onload = async () => { + const renderSize = 64 + const baseUnit = 10 + const canvas = document.createElement("canvas") + canvas.width = renderSize + canvas.height = renderSize + const ctx = canvas.getContext("2d")! + ctx.drawImage(img, 0, 0, renderSize, renderSize) + URL.revokeObjectURL(url) + + try { + const bitmap = await createImageBitmap(canvas) + const scale = baseUnit / renderSize + resolve({ + type: "bitmap", + bitmap, + matrix: [ + scale, + 0, + 0, + scale, + (-renderSize * scale) / 2, + (-renderSize * scale) / 2, + ], + } as unknown as confetti.Shape) + } catch (e) { + reject(e) + } + } + img.onerror = reject + img.src = url + }) +} + +function loadEmojiShapes(): Promise { + if (!emojiShapesPromise) { + emojiShapesPromise = Promise.all(EMOJI_SVGS.map(svgToBitmapShape)) + } + return emojiShapesPromise +} + +/** + * 随机烟花效果 Composable + * 提供10种不同风格的烟花庆祝效果 + */ +export function useFireworks() { + // 提前预加载表情包位图,避免首次触发效果8时出现解码延迟 + loadEmojiShapes() + + // 动画期间创建的定时器,组件卸载时统一清理,避免离开页面后动画继续 + const timers = new Set>() + let disposed = false + + function stopTimer(id: ReturnType) { + clearInterval(id) + timers.delete(id) + } + + function runLater(fn: () => void, ms: number) { + const id = setTimeout(() => { + timers.delete(id) + fn() + }, ms) + timers.add(id) + } + + if (getCurrentScope()) { + onScopeDispose(() => { + disposed = true + timers.forEach((id) => clearInterval(id)) + timers.clear() + }) + } + + /** + * 触发随机烟花效果 + */ + function celebrate() { + const fireworkTypes = [ + // 效果1: 经典烟花秀 + () => { + const duration = 3000 + const animationEnd = Date.now() + duration + const defaults = { + startVelocity: 30, + spread: 360, + ticks: 60, + } + + const interval = setInterval(() => { + const timeLeft = animationEnd - Date.now() + if (timeLeft <= 0) return stopTimer(interval) + + const particleCount = 50 * (timeLeft / duration) + confetti({ + ...defaults, + particleCount, + origin: { x: Math.random() * 0.3 + 0.1, y: Math.random() - 0.2 }, + colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"], + }) + confetti({ + ...defaults, + particleCount, + origin: { x: Math.random() * 0.3 + 0.7, y: Math.random() - 0.2 }, + colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"], + }) + }, 250) + timers.add(interval) + }, + + // 效果2: 星星雨 + () => { + const animationEnd = Date.now() + 2000 + + const interval = setInterval(() => { + if (Date.now() >= animationEnd) return stopTimer(interval) + + confetti({ + particleCount: 12, + angle: 270, + spread: 100, + startVelocity: 12, + gravity: 0.6, + decay: 0.96, + ticks: 200, + scalar: 1.2, + shapes: ["star"], + origin: { x: Math.random(), y: 0 }, + colors: ["#FFD700", "#FFA500", "#FFFF00", "#FF69B4", "#00CED1"], + }) + }, 150) + timers.add(interval) + }, + + // 效果3: 爆炸波浪 + () => { + function randomInRange(min: number, max: number) { + return Math.random() * (max - min) + min + } + + for (let i = 0; i < 5; i++) { + runLater(() => { + confetti({ + angle: randomInRange(55, 125), + spread: randomInRange(50, 70), + particleCount: randomInRange(50, 100), + origin: { y: 0.6 }, + colors: ["#26ccff", "#a25afd", "#ff5e7e", "#88ff5a", "#fcff42"], + }) + }, i * 200) + } + }, + + // 效果4: 彩虹喷泉 + () => { + const end = Date.now() + 2000 + + const colors = [ + "#ff595e", + "#ff924c", + "#ffca3a", + "#8ac926", + "#1982c4", + "#6a4c93", + ] + + const frame = () => { + confetti({ + particleCount: 2, + angle: 60, + spread: 55, + origin: { x: 0 }, + colors: colors, + }) + confetti({ + particleCount: 2, + angle: 120, + spread: 55, + origin: { x: 1 }, + colors: colors, + }) + + if (Date.now() < end && !disposed) { + requestAnimationFrame(frame) + } + } + + frame() + }, + + // 效果5: 烟花雨 + () => { + const duration = 2500 + const animationEnd = Date.now() + duration + + const interval = setInterval(() => { + const timeLeft = animationEnd - Date.now() + if (timeLeft <= 0) return stopTimer(interval) + + const particleCount = 50 + confetti({ + particleCount, + startVelocity: 30, + spread: 360, + ticks: 60, + origin: { + x: Math.random(), + y: Math.random() - 0.2, + }, + colors: [ + "#ff0000", + "#00ff00", + "#0000ff", + "#ffff00", + "#ff00ff", + "#00ffff", + ], + }) + }, 200) + timers.add(interval) + }, + + // 效果6: 炮竹齐鸣 + () => { + const count = 200 + const defaults = { + origin: { y: 0.7 }, + } + + function fire(particleRatio: number, opts: any) { + confetti({ + ...defaults, + ...opts, + particleCount: Math.floor(count * particleRatio), + }) + } + + fire(0.25, { + spread: 26, + startVelocity: 55, + }) + + fire(0.2, { + spread: 60, + }) + + fire(0.35, { + spread: 100, + decay: 0.91, + scalar: 0.8, + }) + + fire(0.1, { + spread: 120, + startVelocity: 25, + decay: 0.92, + scalar: 1.2, + }) + + fire(0.1, { + spread: 120, + startVelocity: 45, + }) + }, + + // 效果7: 螺旋上升 + () => { + const steps = 16 + const colors = ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"] + + for (let step = 0; step < steps; step++) { + runLater(() => { + const progress = step / (steps - 1) + const phase = step * (Math.PI / 4) + + confetti({ + particleCount: 7, + angle: 90 + Math.cos(phase) * 18, + spread: 25, + startVelocity: 16, + gravity: 0.25, + decay: 0.94, + ticks: 100, + scalar: 0.9, + shapes: ["circle", "square"], + origin: { + x: 0.5 + Math.sin(phase) * 0.14, + y: 0.85 - progress * 0.55, + }, + colors, + }) + }, step * 65) + } + }, + + // 效果8: 表情包烟花 + () => { + loadEmojiShapes().then((emojiShapes) => { + confetti({ + shapes: emojiShapes, + flat: true, + scalar: 6, + particleCount: 18, + spread: 360, + startVelocity: 45, + gravity: 0.7, + ticks: 150, + origin: { y: 0.6 }, + }) + }) + }, + + // 效果9: 风车螺旋 + () => { + const colors = ["#ff9a9e", "#fad0c4", "#fbc2eb", "#a18cd1", "#fad961"] + + for (let angle = 0; angle < 360; angle += 30) { + runLater( + () => { + confetti({ + particleCount: 15, + angle, + spread: 20, + startVelocity: 40, + origin: { x: 0.5, y: 0.5 }, + colors, + }) + }, + (angle / 30) * 80, + ) + } + }, + + // 效果10: 礼花瀑布 + () => { + const animationEnd = Date.now() + 1800 + const colors = ["#f6d365", "#fda085", "#fbc2eb", "#a6c1ee", "#84fab0"] + + const interval = setInterval(() => { + if (Date.now() >= animationEnd) return stopTimer(interval) + + for (let column = 0; column < 6; column++) { + confetti({ + particleCount: 2, + angle: 270, + spread: 30, + startVelocity: 5, + gravity: 1, + decay: 0.96, + ticks: 240, + scalar: 1.1, + origin: { x: (column + Math.random()) / 6, y: 0 }, + colors, + }) + } + }, 120) + timers.add(interval) + }, + ] + + // 随机选择一种效果 + const randomEffect = + fireworkTypes[Math.floor(Math.random() * fireworkTypes.length)] + randomEffect() + } + + return { + celebrate, + } +} diff --git a/apps/web/src/oj/problem/composables/useMermaidConverter.ts b/apps/web/src/oj/problem/composables/useMermaidConverter.ts new file mode 100644 index 0000000..0cd2eb2 --- /dev/null +++ b/apps/web/src/oj/problem/composables/useMermaidConverter.ts @@ -0,0 +1,125 @@ +/** + * 将流程图JSON数据转换为Mermaid格式 + */ +export function useMermaidConverter() { + const convertToMermaid = (flowchartData: any) => { + const { nodes, edges } = flowchartData + + if (!nodes || nodes.length === 0) { + return "graph TD\n A[空流程图]" + } + + let mermaid = "graph TD\n" + + // Build safe ID mapping to prevent Mermaid syntax errors from special characters + const idMap = new Map() + nodes.forEach((node: any, index: number) => { + idMap.set(node.id, `node_${index}`) + }) + const safeId = (id: string) => + idMap.get(id) || id.replace(/[^a-zA-Z0-9_]/g, "_") + + // 处理节点 - 根据原始类型和自定义标签 + nodes.forEach((node: any) => { + const nodeId = safeId(node.id) + const label = node.data?.customLabel || node.data?.label || "节点" + const originalType = node.data?.originalType || node.type + + // 根据节点原始类型确定Mermaid语法 + switch (originalType) { + case "start": + mermaid += ` ${nodeId}(("${label}"))\n` + break + case "end": + mermaid += ` ${nodeId}(("${label}"))\n` + break + case "input": + // 输入框使用平行四边形 + mermaid += ` ${nodeId}[/"${label}"/]\n` + break + case "output": + // 输出框使用平行四边形 + mermaid += ` ${nodeId}[/"${label}"/]\n` + break + case "default": + mermaid += ` ${nodeId}["${label}"]\n` + break + case "decision": + mermaid += ` ${nodeId}{"${label}"}\n` + break + case "loop": + // 循环使用菱形 + mermaid += ` ${nodeId}{"${label}"}\n` + break + default: + mermaid += ` ${nodeId}["${label}"]\n` + } + }) + + // 处理边 + edges.forEach((edge: any) => { + const source = safeId(edge.source) + const target = safeId(edge.target) + const label = edge.label ?? "" + + if (label && label.trim() !== "") { + mermaid += ` ${source} -->|"${label}"| ${target}\n` + } else { + mermaid += ` ${source} --> ${target}\n` + } + }) + + // 添加样式定义来区分不同类型的节点 + mermaid += "\n" + mermaid += + " classDef startNode fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef endNode fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef input fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef output fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef process fill:#f0f9ff,stroke:#0284c7,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef decision fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#0f172a\n" + mermaid += + " classDef loop fill:#fae8ff,stroke:#c026d3,stroke-width:2px,color:#0f172a\n" + mermaid += "\n" + + // 为节点应用样式 + nodes.forEach((node: any) => { + const nodeId = safeId(node.id) + const originalType = node.data?.originalType || node.type + + switch (originalType) { + case "start": + mermaid += ` class ${nodeId} startNode\n` + break + case "end": + mermaid += ` class ${nodeId} endNode\n` + break + case "input": + mermaid += ` class ${nodeId} input\n` + break + case "output": + mermaid += ` class ${nodeId} output\n` + break + case "decision": + mermaid += ` class ${nodeId} decision\n` + break + case "loop": + mermaid += ` class ${nodeId} loop\n` + break + default: + mermaid += ` class ${nodeId} process\n` + } + }) + + return mermaid + } + + return { + convertToMermaid, + } +} diff --git a/apps/web/src/oj/problem/composables/useSubmissionMonitor.ts b/apps/web/src/oj/problem/composables/useSubmissionMonitor.ts new file mode 100644 index 0000000..832b380 --- /dev/null +++ b/apps/web/src/oj/problem/composables/useSubmissionMonitor.ts @@ -0,0 +1,190 @@ +import { ref, computed, watch, onUnmounted } from "vue" +import { useIntervalFn, useTimeoutFn } from "@vueuse/core" +import { getSubmission } from "oj/api" +import { SubmissionStatus } from "utils/constants" +import type { PendingAchievement, Submission } from "utils/types" +import { useAchievementStore } from "shared/store/achievement" +import { + useSubmissionWebSocket, + type SubmissionUpdate, +} from "shared/composables/websocket" + +/** + * 判题监控 Composable + * 负责通过 WebSocket + 轮询双保险机制监控判题结果 + */ +export function useSubmissionMonitor() { + // ==================== 状态 ==================== + const submissionId = ref("") + const submission = ref() + + // ==================== 轮询机制 ==================== + const { pause: pausePolling, resume: resumePolling } = useIntervalFn( + async () => { + if (!submissionId.value) return + + try { + const res = await getSubmission(submissionId.value) + submission.value = res.data + + const result = res.data.result + // 判题完成,停止轮询 + if ( + result !== SubmissionStatus.judging && + result !== SubmissionStatus.pending + ) { + pausePolling() + } + } catch (error) { + console.error("[SubmissionMonitor] 轮询失败:", error) + pausePolling() + } + }, + 2000, + { immediate: false }, + ) + + // ==================== WebSocket 处理 ==================== + const handleSubmissionUpdate = (data: SubmissionUpdate) => { + // push_to_user 复用了 submission_update 这个 channel handler, + // 其他类型的消息会走同一条 WebSocket 帧进来,必须先分流 + const frame = data as unknown as { + type: string + achievements?: PendingAchievement[] + } + if (frame.type === "achievement_unlocked") { + useAchievementStore().enqueue(frame.achievements ?? []) + return + } + if (frame.type !== "submission_update") { + return + } + + console.log("[SubmissionMonitor] 收到WebSocket更新:", data) + + if (data.submission_id !== submissionId.value) { + console.log("[SubmissionMonitor] 提交ID不匹配,忽略") + return + } + + if (!submission.value) { + submission.value = {} as Submission + } + + submission.value.result = data.result as Submission["result"] + + // 判题完成或出错,获取完整详情 + if (data.status === "finished" || data.status === "error") { + console.log( + `[SubmissionMonitor] 判题${data.status === "finished" ? "完成" : "出错"}`, + ) + + // 停止轮询(WebSocket已成功) + pausePolling() + + getSubmission(submissionId.value).then((res) => { + submission.value = res.data + // 15分钟无新提交则断开WebSocket(节省资源) + scheduleDisconnect(15 * 60 * 1000) + }) + } + } + + // 初始化 WebSocket + const { + connect, + subscribe, + scheduleDisconnect, + cancelScheduledDisconnect, + status: wsStatus, + } = useSubmissionWebSocket(handleSubmissionUpdate) + + // ==================== 轮询保底启动 ==================== + const { start: startPollingFallback } = useTimeoutFn( + () => { + if ( + submission.value && + (submission.value.result === SubmissionStatus.judging || + submission.value.result === SubmissionStatus.pending || + submission.value.result === 9) // 9 = submitting + ) { + console.log("[SubmissionMonitor] WebSocket未及时响应,启动轮询保底") + resumePolling() + } + }, + 5000, + { immediate: false }, + ) + + // ==================== 启动监控 ==================== + const startMonitoring = (id: string) => { + submissionId.value = id + submission.value = { id, result: 9 } as Submission // 9 = submitting + + // 取消之前的断开计划 + cancelScheduledDisconnect() + + // 如果WebSocket未连接,先连接 + if (wsStatus.value !== "connected") { + console.log("[SubmissionMonitor] 启动WebSocket连接...") + connect() + } + + // 等待WebSocket连接并订阅 + let unwatch: (() => void) | null = null + unwatch = watch( + wsStatus, + (status) => { + if (status === "connected") { + console.log("[SubmissionMonitor] WebSocket已连接,订阅提交:", id) + subscribe(id) + if (unwatch) { + unwatch() // 订阅成功后停止监听 + } + } + }, + { immediate: true }, + ) + + // 5秒后启动轮询保底(防止WebSocket失败) + startPollingFallback() + } + + // ==================== 计算属性 ==================== + const judging = computed( + () => submission.value?.result === SubmissionStatus.judging, + ) + + const pending = computed( + () => submission.value?.result === SubmissionStatus.pending, + ) + + const submitting = computed( + () => submission.value?.result === SubmissionStatus.submitting, + ) + + const isProcessing = computed(() => { + return judging.value || pending.value || submitting.value + }) + + // ==================== 清理 ==================== + onUnmounted(() => { + pausePolling() + }) + + return { + // 状态 + submissionId, + submission, + + // 计算属性 + judging, + pending, + submitting, + isProcessing, + + // 方法 + startMonitoring, + pausePolling, + } +} diff --git a/apps/web/src/oj/problem/detail.vue b/apps/web/src/oj/problem/detail.vue new file mode 100644 index 0000000..3b1a667 --- /dev/null +++ b/apps/web/src/oj/problem/detail.vue @@ -0,0 +1,284 @@ + + + diff --git a/apps/web/src/oj/problem/list.vue b/apps/web/src/oj/problem/list.vue new file mode 100644 index 0000000..ff72393 --- /dev/null +++ b/apps/web/src/oj/problem/list.vue @@ -0,0 +1,342 @@ + + + + + diff --git a/apps/web/src/oj/problem/utils/pythonSyntaxCheck.ts b/apps/web/src/oj/problem/utils/pythonSyntaxCheck.ts new file mode 100644 index 0000000..3b5bf9d --- /dev/null +++ b/apps/web/src/oj/problem/utils/pythonSyntaxCheck.ts @@ -0,0 +1,53 @@ +export interface PythonSyntaxError { + line: number +} + +let skulptPromise: Promise | null = null + +/** + * 按需加载 Skulpt(约 233KB gzip),只在提交 Python3 代码时才下载。 + * 结果缓存,同一页面只加载一次。 + */ +function loadSkulpt(): Promise { + if (!skulptPromise) { + // @ts-ignore - skulpt has no type definitions + skulptPromise = import("skulpt").then((m) => m.default ?? m) + } + return skulptPromise +} + +/** + * 提前把 Skulpt 拉下来,避免点提交时才开始下载。 + * 失败不影响功能,提交时会再试一次。 + */ +export function prefetchPythonSyntaxChecker() { + loadSkulpt().catch(() => { + skulptPromise = null + }) +} + +/** + * 用 Skulpt 检测 Python 代码中的语法错误。 + * 只编译不执行,不受 input() 等 IO 调用影响。 + * 加载失败时返回 null(放行提交),交给后端判题兜底。 + */ +export async function checkPythonSyntax( + code: string, +): Promise { + let Sk: any + try { + Sk = await loadSkulpt() + } catch { + skulptPromise = null + return null + } + + Sk.configure({ output: () => {} }) + try { + Sk.compile(code, "prog.py", "exec") + return null + } catch (e: any) { + const line: number = e?.traceback?.[0]?.lineno ?? 1 + return { line } + } +} diff --git a/apps/web/src/oj/problemset/components/ProblemSetHeader.vue b/apps/web/src/oj/problemset/components/ProblemSetHeader.vue new file mode 100644 index 0000000..7d1762f --- /dev/null +++ b/apps/web/src/oj/problemset/components/ProblemSetHeader.vue @@ -0,0 +1,109 @@ + + + diff --git a/apps/web/src/oj/problemset/components/ProblemSetProblemsList.vue b/apps/web/src/oj/problemset/components/ProblemSetProblemsList.vue new file mode 100644 index 0000000..c720b2b --- /dev/null +++ b/apps/web/src/oj/problemset/components/ProblemSetProblemsList.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/apps/web/src/oj/problemset/components/UserProgressView.vue b/apps/web/src/oj/problemset/components/UserProgressView.vue new file mode 100644 index 0000000..6acd4fb --- /dev/null +++ b/apps/web/src/oj/problemset/components/UserProgressView.vue @@ -0,0 +1,268 @@ + + + diff --git a/apps/web/src/oj/problemset/detail.vue b/apps/web/src/oj/problemset/detail.vue new file mode 100644 index 0000000..ab0d1c0 --- /dev/null +++ b/apps/web/src/oj/problemset/detail.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/apps/web/src/oj/problemset/list.vue b/apps/web/src/oj/problemset/list.vue new file mode 100644 index 0000000..b1e21f7 --- /dev/null +++ b/apps/web/src/oj/problemset/list.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/apps/web/src/oj/rank/components/Chart.vue b/apps/web/src/oj/rank/components/Chart.vue new file mode 100644 index 0000000..73f33cd --- /dev/null +++ b/apps/web/src/oj/rank/components/Chart.vue @@ -0,0 +1,114 @@ + + + diff --git a/apps/web/src/oj/rank/components/Index.vue b/apps/web/src/oj/rank/components/Index.vue new file mode 100644 index 0000000..477cf58 --- /dev/null +++ b/apps/web/src/oj/rank/components/Index.vue @@ -0,0 +1,31 @@ + + diff --git a/apps/web/src/oj/rank/list.vue b/apps/web/src/oj/rank/list.vue new file mode 100644 index 0000000..0b94a7e --- /dev/null +++ b/apps/web/src/oj/rank/list.vue @@ -0,0 +1,880 @@ + + + + + diff --git a/apps/web/src/oj/store/ai.ts b/apps/web/src/oj/store/ai.ts new file mode 100644 index 0000000..2e66c95 --- /dev/null +++ b/apps/web/src/oj/store/ai.ts @@ -0,0 +1,206 @@ +import type { DetailsData, DurationData } from "utils/types" +import { consumeJSONEventStream } from "utils/stream" +import { + getAIDetailData, + getAIDurationData, + getAIHeatmapData, + getAIPinnedReport, +} from "../api" +import { getCSRFToken } from "utils/functions" + +export const useAIStore = defineStore("ai", () => { + const duration = ref("months:6") + const targetUsername = ref("") + const durationData = ref([]) + const detailsData = reactive({ + start: "", + end: "", + grade: "B", + class_name: "", + tags: {}, + difficulty: {}, + contest_count: 0, + solved: [], + flowcharts: [], + }) + const heatmapData = ref<{ timestamp: number; value: number }[]>([]) + + const loading = reactive({ + fetching: false, // 合并 details 和 duration 的 loading + ai: false, + heatmap: false, + }) + + const mdContent = ref("") + const pinnedReport = ref<{ analysis: string } | null>(null) + + async function fetchDetailsData(start: string, end: string) { + const res = await getAIDetailData( + start, + end, + targetUsername.value || undefined, + ) + detailsData.start = res.data.start + detailsData.end = res.data.end + detailsData.solved = res.data.solved + detailsData.grade = res.data.grade + detailsData.class_name = res.data.class_name + detailsData.tags = res.data.tags + detailsData.difficulty = res.data.difficulty + detailsData.contest_count = res.data.contest_count + detailsData.flowcharts = res.data.flowcharts + } + + async function fetchDurationData(end: string, duration: string) { + const res = await getAIDurationData( + end, + duration, + targetUsername.value || undefined, + ) + durationData.value = res.data + } + + async function fetchHeatmapData() { + loading.heatmap = true + const res = await getAIHeatmapData(targetUsername.value || undefined) + heatmapData.value = res.data + loading.heatmap = false + } + + async function fetchAnalysisData( + start: string, + end: string, + duration: string, + ) { + loading.fetching = true + try { + await Promise.all([ + fetchDetailsData(start, end), + fetchDurationData(end, duration), + ]) + } finally { + loading.fetching = false + } + } + + let aiController: AbortController | null = null + + async function fetchAIAnalysis() { + if (aiController) { + aiController.abort() + } + const controller = new AbortController() + aiController = controller + + loading.ai = true + mdContent.value = "" + + const headers: Record = { + "Content-Type": "application/json", + } + const csrfToken = getCSRFToken() + if (csrfToken) { + headers["X-CSRFToken"] = csrfToken + } + + try { + const response = await fetch("/api/ai/analysis", { + method: "POST", + headers, + body: JSON.stringify({ + details: detailsData, + duration: durationData.value, + }), + signal: controller.signal, + }) + + if (!response.ok) { + throw new Error("AI 分析生成失败") + } + + let hasStarted = false + + await consumeJSONEventStream(response, { + signal: controller.signal, + onEvent(event) { + if (event === "end" && !hasStarted) { + loading.ai = false + } + }, + onMessage(payload) { + const parsed = payload as { + type?: string + content?: string + message?: string + } + + if (parsed.type === "delta" && parsed.content) { + if (!hasStarted) { + hasStarted = true + loading.ai = false + } + mdContent.value += parsed.content + } else if (parsed.type === "error") { + throw new Error(parsed.message || "AI 服务异常") + } else if (parsed.type === "done" && !hasStarted) { + loading.ai = false + } + }, + }) + } catch (error: any) { + if (controller.signal.aborted) { + return + } + console.error("生成 AI 分析失败", error) + const message = error?.message || "生成失败,请稍后再试" + mdContent.value = `生成失败:${message}` + } finally { + if (aiController === controller) { + aiController = null + loading.ai = false + } + } + } + + async function fetchPinnedReport() { + const res = await getAIPinnedReport() + pinnedReport.value = res.data + } + + async function simulatePinnedStream() { + if (!pinnedReport.value) return + const text = pinnedReport.value.analysis + mdContent.value = "" + const CHUNK = 6 + const DELAY = 18 + await new Promise((resolve) => { + let i = 0 + function step() { + if (i >= text.length) { + resolve() + return + } + mdContent.value += text.slice(i, i + CHUNK) + i += CHUNK + setTimeout(step, DELAY) + } + step() + }) + } + + return { + fetchAnalysisData, + fetchHeatmapData, + fetchAIAnalysis, + fetchPinnedReport, + simulatePinnedStream, + durationData, + detailsData, + heatmapData, + duration, + targetUsername, + loading, + mdContent, + pinnedReport, + } +}) diff --git a/apps/web/src/oj/store/code.ts b/apps/web/src/oj/store/code.ts new file mode 100644 index 0000000..1b24ac2 --- /dev/null +++ b/apps/web/src/oj/store/code.ts @@ -0,0 +1,94 @@ +import { defineStore } from "pinia" +import { STORAGE_KEY } from "utils/constants" +import storage from "utils/storage" +import type { Code, LANGUAGE } from "utils/types" + +/** + * 代码编辑器状态管理 Store + * 管理全局的代码、输入、输出状态 + */ +export const useCodeStore = defineStore("code", () => { + // ==================== 状态 ==================== + const code = reactive({ + value: "", + language: storage.get(STORAGE_KEY.LANGUAGE) || "Python3", + }) + + const input = ref("") + const output = ref("") + + // ==================== 计算属性 ==================== + const isEmpty = computed(() => code.value.trim() === "") + + // ==================== 操作 ==================== + /** + * 设置代码内容 + */ + function setCode(value: string) { + code.value = value + } + + /** + * 设置编程语言 + */ + function setLanguage(language: LANGUAGE) { + code.language = language + storage.set(STORAGE_KEY.LANGUAGE, language) + } + + /** + * 设置输入 + */ + function setInput(value: string) { + input.value = value + } + + /** + * 设置输出 + */ + function setOutput(value: string) { + output.value = value + } + + /** + * 重置所有状态 + */ + function reset() { + code.value = "" + input.value = "" + output.value = "" + } + + /** + * 清空输出 + */ + function clearOutput() { + output.value = "" + } + + // 监听语言变化,保存到本地存储 + watch( + () => code.language, + (newLanguage) => { + storage.set(STORAGE_KEY.LANGUAGE, newLanguage) + }, + ) + + return { + // 状态 + code, + input, + output, + + // 计算属性 + isEmpty, + + // 操作 + setCode, + setLanguage, + setInput, + setOutput, + reset, + clearOutput, + } +}) diff --git a/apps/web/src/oj/store/contest.ts b/apps/web/src/oj/store/contest.ts new file mode 100644 index 0000000..bd58561 --- /dev/null +++ b/apps/web/src/oj/store/contest.ts @@ -0,0 +1,116 @@ +import { formatISO, getTime, parseISO } from "date-fns" +import { useUserStore } from "shared/store/user" +import { ContestStatus, ContestType } from "utils/constants" +import { duration } from "utils/functions" +import type { Contest, Problem } from "utils/types" +import { + checkContestPassword, + getContest, + getContestAccess, + getContestProblems, +} from "../api" + +export const useContestStore = defineStore("contest", () => { + const userStore = useUserStore() + const [access, toggleAccess] = useToggle(false) + const contest = ref(null) + const problems = ref([]) + const now = ref(0) + + let timer = 0 + + const contestStatus = computed(() => { + if (!contest.value) return ContestStatus.initial + const start = getTime(parseISO(contest.value.start_time.toString())) + const end = getTime(parseISO(contest.value.end_time.toString())) + if (start > now.value) { + return ContestStatus.not_started + } else if (end < now.value) { + return ContestStatus.finished + } else { + return ContestStatus.underway + } + }) + + const countdown = computed(() => { + if (contestStatus.value === ContestStatus.finished) { + return "已结束" + } else if (contestStatus.value === ContestStatus.not_started) { + const d = duration(formatISO(now.value), contest.value!.start_time, true) + return "距离比赛开始 " + d + } else { + const d = duration(formatISO(now.value), contest.value!.end_time, true) + return "距离比赛结束 " + d + } + }) + + const isContestAdmin = computed( + () => + userStore.isSuperAdmin || + (userStore.isAuthed && + contest.value?.created_by.id === userStore.user!.id), + ) + + const isPrivate = computed( + () => contest.value!.contest_type === ContestType.private, + ) + + async function init(contestID: string) { + problems.value = [] + const res = await getContest(contestID) + contest.value = res.data + now.value = getTime(parseISO(res.data.now)) + if (contestStatus.value !== ContestStatus.finished) { + timer = setInterval(() => { + now.value = now.value + 1000 + }, 1000) + } + if (contest.value?.contest_type === ContestType.private) { + const res = await getContestAccess(contestID) + toggleAccess(res.data.access) + } + _getProblems(contestID) + } + + function clear() { + contest.value = null + problems.value = [] + toggleAccess(false) + now.value = 0 + if (timer) clearInterval(timer) + } + + async function checkPassword(contestID: string, password: string) { + try { + const res = await checkContestPassword(contestID, password) + toggleAccess(res.data) + if (res.data) { + _getProblems(contestID) + } + } catch (err) { + toggleAccess(false) + } + } + + async function _getProblems(contestID: string) { + try { + problems.value = await getContestProblems(contestID) + } catch (err) { + problems.value = [] + toggleAccess(false) + } + } + + return { + contest, + contestStatus, + isContestAdmin, + access, + problems, + isPrivate, + countdown, + init, + clear, + checkPassword, + } +}) diff --git a/apps/web/src/oj/store/problem.ts b/apps/web/src/oj/store/problem.ts new file mode 100644 index 0000000..ead60ec --- /dev/null +++ b/apps/web/src/oj/store/problem.ts @@ -0,0 +1,34 @@ +import { defineStore } from "pinia" +import type { LANGUAGE, Problem } from "utils/types" + +export const useProblemStore = defineStore("problem", () => { + const problem = ref(null) + const route = useRoute() + + const failCount = ref(0) + + const languages = computed(() => { + if (route.name === "problem" && problem.value?.allow_flowchart) { + return ["Flowchart", ...problem.value?.languages] + } + return problem.value?.languages ?? [] + }) + + function incrementFailCount() { + failCount.value++ + } + + watch( + () => problem.value?.id, + () => { + failCount.value = 0 + }, + ) + + return { + problem, + failCount, + languages, + incrementFailCount, + } +}) diff --git a/apps/web/src/oj/submission/components/ButtonWithSearch.vue b/apps/web/src/oj/submission/components/ButtonWithSearch.vue new file mode 100644 index 0000000..6e70507 --- /dev/null +++ b/apps/web/src/oj/submission/components/ButtonWithSearch.vue @@ -0,0 +1,53 @@ + + diff --git a/apps/web/src/oj/submission/components/FlowchartLink.vue b/apps/web/src/oj/submission/components/FlowchartLink.vue new file mode 100644 index 0000000..34a1cdb --- /dev/null +++ b/apps/web/src/oj/submission/components/FlowchartLink.vue @@ -0,0 +1,35 @@ + + + + diff --git a/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue new file mode 100644 index 0000000..9303ae4 --- /dev/null +++ b/apps/web/src/oj/submission/components/FlowchartScoreDetail.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/apps/web/src/oj/submission/components/Grade.vue b/apps/web/src/oj/submission/components/Grade.vue new file mode 100644 index 0000000..3e89ad1 --- /dev/null +++ b/apps/web/src/oj/submission/components/Grade.vue @@ -0,0 +1,25 @@ + + + diff --git a/apps/web/src/oj/submission/components/SubmissionLink.vue b/apps/web/src/oj/submission/components/SubmissionLink.vue new file mode 100644 index 0000000..ced3704 --- /dev/null +++ b/apps/web/src/oj/submission/components/SubmissionLink.vue @@ -0,0 +1,52 @@ + + + diff --git a/apps/web/src/oj/submission/detail.vue b/apps/web/src/oj/submission/detail.vue new file mode 100644 index 0000000..db7ef00 --- /dev/null +++ b/apps/web/src/oj/submission/detail.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/apps/web/src/oj/submission/list.vue b/apps/web/src/oj/submission/list.vue new file mode 100644 index 0000000..d15f77d --- /dev/null +++ b/apps/web/src/oj/submission/list.vue @@ -0,0 +1,573 @@ + + + diff --git a/apps/web/src/oj/transforms.ts b/apps/web/src/oj/transforms.ts new file mode 100644 index 0000000..80f8ac7 --- /dev/null +++ b/apps/web/src/oj/transforms.ts @@ -0,0 +1,29 @@ +import { DIFFICULTY } from "utils/constants" +import { getACRate } from "utils/functions" +import type { Problem } from "utils/types" + +// 把后端的 Problem 塑形成列表项需要的形状,与请求逻辑解耦。 +export function filterResult(result: Problem) { + const newResult = { + id: result.id, + _id: result._id, + title: result.title, + difficulty: DIFFICULTY[result.difficulty], + tags: result.tags, + submission: result.submission_number, + rate: getACRate(result.accepted_number, result.submission_number), + status: "", + author: result.created_by.username, + allow_flowchart: result.allow_flowchart, + show_flowchart: result.show_flowchart, + has_ast_rules: result.has_ast_rules, + } + if (result.my_status === null || result.my_status === undefined) { + newResult.status = "not_test" + } else if (result.my_status === 0) { + newResult.status = "passed" + } else { + newResult.status = "failed" + } + return newResult +} diff --git a/apps/web/src/oj/user/index.vue b/apps/web/src/oj/user/index.vue new file mode 100644 index 0000000..6898961 --- /dev/null +++ b/apps/web/src/oj/user/index.vue @@ -0,0 +1,331 @@ + + + diff --git a/apps/web/src/oj/user/message.vue b/apps/web/src/oj/user/message.vue new file mode 100644 index 0000000..02997b6 --- /dev/null +++ b/apps/web/src/oj/user/message.vue @@ -0,0 +1,71 @@ + + diff --git a/apps/web/src/oj/user/setting.vue b/apps/web/src/oj/user/setting.vue new file mode 100644 index 0000000..06be145 --- /dev/null +++ b/apps/web/src/oj/user/setting.vue @@ -0,0 +1,76 @@ + + + diff --git a/apps/web/src/routes.ts b/apps/web/src/routes.ts new file mode 100644 index 0000000..b815626 --- /dev/null +++ b/apps/web/src/routes.ts @@ -0,0 +1,339 @@ +import type { RouteRecordRaw } from "vue-router" + +export const ojs: RouteRecordRaw = { + path: "/", + component: () => import("shared/layout/default.vue"), + children: [ + { path: "", component: () => import("oj/problem/list.vue") }, + { + path: "problem/:problemID", + component: () => import("oj/problem/detail.vue"), + props: true, + name: "problem", + }, + { + path: "submission", + component: () => import("oj/submission/list.vue"), + name: "submissions", + }, + { + path: "submission/:submissionID", + component: () => import("oj/submission/detail.vue"), + props: true, + meta: { requiresAuth: true }, + }, + { + path: "contest", + component: () => import("oj/contest/list.vue"), + name: "contests", + }, + { + path: "contest/:contestID", + component: () => import("oj/contest/detail.vue"), + props: true, + meta: { requiresAuth: true }, + children: [ + { + path: "", + component: () => import("oj/contest/pages/problems.vue"), + props: true, + meta: { requiresAuth: true }, + name: "contest problems", + }, + { + path: "submission", + component: () => import("oj/submission/list.vue"), + meta: { requiresAuth: true }, + name: "contest submissions", + }, + { + path: "rank", + component: () => import("oj/contest/pages/rank.vue"), + props: true, + meta: { requiresAuth: true }, + name: "contest rank", + }, + ], + }, + { + path: "contest/:contestID/problem/:problemID", + component: () => import("oj/problem/detail.vue"), + props: true, + name: "contest problem", + meta: { requiresAuth: true }, + }, + { + path: "rank", + name: "rank", + component: () => import("oj/rank/list.vue"), + }, + { + path: "class", + name: "class", + component: () => import("oj/class/pk.vue"), + }, + { + path: "announcement", + name: "announcements", + component: () => import("oj/announcement/list.vue"), + }, + { + path: "user", + component: () => import("oj/user/index.vue"), + meta: { requiresAuth: true }, + }, + { + path: "achievement", + name: "achievement", + component: () => import("oj/achievement/index.vue"), + meta: { requiresAuth: true }, + }, + { + path: "setting", + component: () => import("oj/user/setting.vue"), + meta: { requiresAuth: true }, + }, + { + path: "message", + component: () => import("oj/user/message.vue"), + meta: { requiresAuth: true }, + }, + { + path: "learn/:type/:step", + name: "learn", + component: () => import("oj/learn/index.vue"), + props: true, + }, + { + path: "ai-analysis", + name: "ai", + component: () => import("oj/ai/analysis.vue"), + meta: { requiresAuth: true }, + }, + { + path: "flowchart", + name: "flowchart", + component: () => import("oj/flowchart/index.vue"), + }, + { + path: "problemset", + component: () => import("oj/problemset/list.vue"), + name: "problemsets", + }, + { + path: "problemset/:problemSetId", + component: () => import("oj/problemset/detail.vue"), + props: true, + name: "problemset", + }, + { + path: "problemset/:problemSetId/problem/:problemID", + component: () => import("oj/problem/detail.vue"), + props: true, + name: "problemset problem", + meta: { requiresAuth: true }, + }, + ], +} + +export const admins: RouteRecordRaw = { + path: "/admin", + component: () => import("shared/layout/admin.vue"), + children: [ + { + path: "", + name: "admin home", + component: () => import("admin/setting/home.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "config", + name: "admin config", + component: () => import("admin/setting/config.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "user/list", + name: "admin user list", + component: () => import("admin/user/list.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "user/generate", + name: "admin user generate", + component: () => import("admin/user/generate.vue"), + meta: { requiresSuperAdmin: true }, + }, + // admin和super_admin都可以访问的路由 (需要题目权限) + { + path: "problem/list", + name: "admin problem list", + component: () => import("admin/problem/list.vue"), + meta: { requiresProblemPermission: true }, + }, + { + path: "problem/tags", + name: "admin tag list", + component: () => import("admin/problem/tags.vue"), + meta: { requiresProblemPermission: true }, + }, + { + path: "problem/create", + name: "admin problem create", + component: () => import("admin/problem/detail.vue"), + meta: { requiresProblemPermission: true }, + }, + { + path: "problem/edit/:problemID", + name: "admin problem edit", + component: () => import("admin/problem/detail.vue"), + props: true, + meta: { requiresProblemPermission: true }, + }, + { + path: "contest/list", + name: "admin contest list", + component: () => import("admin/contest/list.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/create", + name: "admin contest create", + component: () => import("admin/contest/detail.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/edit/:contestID", + name: "admin contest edit", + component: () => import("admin/contest/detail.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/:contestID/problem/list", + name: "admin contest problem list", + component: () => import("admin/problem/list.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/:contestID/problem/create", + name: "admin contest problem create", + component: () => import("admin/problem/detail.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/:contestID/problem/edit/:problemID", + name: "admin contest problem edit", + component: () => import("admin/problem/detail.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "contest/:contestID/helper", + name: "admin contest helper", + component: () => import("admin/contest/helper.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + // 只有super_admin可以访问的路由 + { + path: "announcement/list", + name: "admin announcement list", + component: () => import("admin/announcement/list.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "announcement/create", + name: "admin announcement create", + component: () => import("admin/announcement/detail.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "announcement/edit/:announcementID", + name: "admin announcement edit", + component: () => import("admin/announcement/detail.vue"), + props: true, + meta: { requiresSuperAdmin: true }, + }, + { + path: "message/list", + name: "admin message list", + component: () => import("admin/communication/messages.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "tutorial/list", + name: "admin tutorial list", + component: () => import("admin/tutorial/list.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "tutorial/create", + name: "admin tutorial create", + component: () => import("admin/tutorial/detail.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "tutorial/edit/:tutorialID", + name: "admin tutorial edit", + component: () => import("admin/tutorial/detail.vue"), + props: true, + meta: { requiresSuperAdmin: true }, + }, + { + path: "problem/stuck", + name: "admin stuck problems", + component: () => import("admin/problem/Stuck.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "problem/top_ac_trend", + name: "admin top ac trend", + component: () => import("admin/problem/TopACTrend.vue"), + meta: { requiresTeacherAdmin: true }, + }, + // 题单管理路由 + { + path: "problemset/list", + name: "admin problemset list", + component: () => import("admin/problemset/list.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "achievement/list", + name: "admin achievement list", + // 后端 admin 接口是 super_admin_required,这里必须跟着用 + // requiresSuperAdmin,否则教师能进页面但每个请求都 403 + component: () => import("admin/achievement/list.vue"), + meta: { requiresSuperAdmin: true }, + }, + { + path: "problemset/create", + name: "admin problemset create", + component: () => import("admin/problemset/edit.vue"), + meta: { requiresTeacherAdmin: true }, + }, + { + path: "problemset/edit/:problemSetId", + name: "admin problemset edit", + component: () => import("admin/problemset/edit.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "problemset/:problemSetId", + name: "admin problemset detail", + component: () => import("admin/problemset/detail.vue"), + props: true, + meta: { requiresTeacherAdmin: true }, + }, + { + path: "ai/reports", + name: "admin ai reports", + component: () => import("admin/ai/list.vue"), + meta: { requiresTeacherAdmin: true }, + }, + ], +} diff --git a/apps/web/src/shared/api.ts b/apps/web/src/shared/api.ts new file mode 100644 index 0000000..939a4cb --- /dev/null +++ b/apps/web/src/shared/api.ts @@ -0,0 +1,34 @@ +import http from "utils/http" +import type { Profile, Tag } from "utils/types" + +export function login(data: { username: string; password: string }) { + return http.post("login", data) +} + +export function signup(data: { + username: string + email: string + password: string +}) { + return http.post("register", data) +} + +export function logout() { + return http.get("logout") +} + +export function getProfile(username: string = "") { + return http.get("profile", { params: { username } }) +} + +export function getProblemTagList() { + return http.get("problem/tags") +} + +export function getHitokoto() { + return http.get("hitokoto") +} + +export function getClassUsernames(classroom: string) { + return http.get("class_usernames", { params: { classroom: classroom } }) +} diff --git a/apps/web/src/shared/components/AchievementIcon.vue b/apps/web/src/shared/components/AchievementIcon.vue new file mode 100644 index 0000000..b8fb970 --- /dev/null +++ b/apps/web/src/shared/components/AchievementIcon.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/apps/web/src/shared/components/AchievementToast.vue b/apps/web/src/shared/components/AchievementToast.vue new file mode 100644 index 0000000..1d83dc0 --- /dev/null +++ b/apps/web/src/shared/components/AchievementToast.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/apps/web/src/shared/components/AuthorSelect.vue b/apps/web/src/shared/components/AuthorSelect.vue new file mode 100644 index 0000000..e2b2345 --- /dev/null +++ b/apps/web/src/shared/components/AuthorSelect.vue @@ -0,0 +1,35 @@ + + + diff --git a/apps/web/src/shared/components/Beian.vue b/apps/web/src/shared/components/Beian.vue new file mode 100644 index 0000000..3b1aa0a --- /dev/null +++ b/apps/web/src/shared/components/Beian.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web/src/shared/components/CodeEditor.vue b/apps/web/src/shared/components/CodeEditor.vue new file mode 100644 index 0000000..91ab92d --- /dev/null +++ b/apps/web/src/shared/components/CodeEditor.vue @@ -0,0 +1,65 @@ + + + diff --git a/apps/web/src/shared/components/ContestTitle.vue b/apps/web/src/shared/components/ContestTitle.vue new file mode 100644 index 0000000..ba70df9 --- /dev/null +++ b/apps/web/src/shared/components/ContestTitle.vue @@ -0,0 +1,18 @@ + + + diff --git a/apps/web/src/shared/components/ContestType.vue b/apps/web/src/shared/components/ContestType.vue new file mode 100644 index 0000000..c2a98dc --- /dev/null +++ b/apps/web/src/shared/components/ContestType.vue @@ -0,0 +1,21 @@ + + + diff --git a/apps/web/src/shared/components/Copy.vue b/apps/web/src/shared/components/Copy.vue new file mode 100644 index 0000000..e83330d --- /dev/null +++ b/apps/web/src/shared/components/Copy.vue @@ -0,0 +1,35 @@ + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/CustomNode.vue b/apps/web/src/shared/components/FlowchartEditor/CustomNode.vue new file mode 100644 index 0000000..8cd3cf1 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/CustomNode.vue @@ -0,0 +1,275 @@ + + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/NodeActions.vue b/apps/web/src/shared/components/FlowchartEditor/NodeActions.vue new file mode 100644 index 0000000..c2695cc --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/NodeActions.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue b/apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue new file mode 100644 index 0000000..07e9977 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/NodeHandles.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/Toolbar.vue b/apps/web/src/shared/components/FlowchartEditor/Toolbar.vue new file mode 100644 index 0000000..df95c7e --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/Toolbar.vue @@ -0,0 +1,413 @@ + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/index.vue b/apps/web/src/shared/components/FlowchartEditor/index.vue new file mode 100644 index 0000000..c4a665a --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/index.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/apps/web/src/shared/components/FlowchartEditor/useCache.ts b/apps/web/src/shared/components/FlowchartEditor/useCache.ts new file mode 100644 index 0000000..bc332fd --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useCache.ts @@ -0,0 +1,89 @@ +import { ref, watch, type Ref, type MaybeRefOrGetter } from "vue" +import { useStorage, useDebounceFn } from "@vueuse/core" +import type { Node, Edge } from "@vue-flow/core" + +/** + * 缓存管理 - 使用 @vueuse 的 useStorage + */ +export function useCache( + nodes: Ref, + edges: Ref, + storageKey: MaybeRefOrGetter = "flowchart-editor-data", +) { + const isSaving = ref(false) + const lastSaved = ref(null) + const hasUnsavedChanges = ref(false) + + // 使用 useStorage 管理数据存储,支持响应式 key(题目 ID 异步加载时自动切换) + const storedData = useStorage<{ + nodes: Node[] + edges: Edge[] + timestamp: string + }>(storageKey, { + nodes: [], + edges: [], + timestamp: "", + }) + + // 防抖保存:isSaving 在 watch 中置 true,保存完成后置 false,使 UI 能感知保存中状态 + const debouncedSave = useDebounceFn(() => { + storedData.value.nodes = nodes.value + storedData.value.edges = edges.value + storedData.value.timestamp = new Date().toISOString() + lastSaved.value = new Date() + hasUnsavedChanges.value = false + isSaving.value = false + }, 500) + + // 立即保存 + const saveToCache = () => { + isSaving.value = true + storedData.value.nodes = nodes.value + storedData.value.edges = edges.value + storedData.value.timestamp = new Date().toISOString() + lastSaved.value = new Date() + hasUnsavedChanges.value = false + isSaving.value = false + } + + // 从缓存加载数据 + const loadFromCache = () => { + if (storedData.value.nodes?.length || storedData.value.edges?.length) { + nodes.value = storedData.value.nodes + edges.value = storedData.value.edges + lastSaved.value = storedData.value.timestamp + ? new Date(storedData.value.timestamp) + : null + hasUnsavedChanges.value = false + return true + } + return false + } + + // 清除缓存数据 + const clearCache = () => { + storedData.value = { nodes: [], edges: [], timestamp: "" } + lastSaved.value = null + hasUnsavedChanges.value = false + } + + // 监听节点和边的变化,isSaving 在此置 true 以覆盖防抖等待窗口 + watch( + [nodes, edges], + () => { + hasUnsavedChanges.value = true + isSaving.value = true + debouncedSave() + }, + { deep: true }, + ) + + return { + isSaving, + lastSaved, + hasUnsavedChanges, + saveToCache, + loadFromCache, + clearCache, + } +} diff --git a/apps/web/src/shared/components/FlowchartEditor/useDnD.ts b/apps/web/src/shared/components/FlowchartEditor/useDnD.ts new file mode 100644 index 0000000..da6b761 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useDnD.ts @@ -0,0 +1,84 @@ +import { ref } from "vue" +import { useVueFlow } from "@vue-flow/core" +import { + getNodeTypeConfig, + createNodeStyle, + getNodeDimensions, +} from "./useNodeStyles" +import { getRandomId } from "utils/functions" + +// 模块级共享:当前拖拽的节点类型(Toolbar 写入,canvas 读取) +export const currentDragNodeType = ref(null) + +/** + * 简化的拖拽处理 + */ +export function useDnD() { + const { addNodes, screenToFlowCoordinate } = useVueFlow() + const isDragOver = ref(false) + const screenDragPos = ref<{ x: number; y: number } | null>(null) + + // 拖拽悬停处理 + const onDragOver = (event: DragEvent) => { + event.preventDefault() + isDragOver.value = true + screenDragPos.value = { x: event.clientX, y: event.clientY } + } + + // 拖拽离开处理 + const onDragLeave = () => { + isDragOver.value = false + screenDragPos.value = null + } + + // 拖拽放置处理 + const onDrop = (event: DragEvent) => { + event.preventDefault() + isDragOver.value = false + screenDragPos.value = null + currentDragNodeType.value = null + + const type = event.dataTransfer?.getData("application/vueflow") + if (!type) return + + // 获取鼠标在画布中的坐标 + const position = screenToFlowCoordinate({ + x: event.clientX, + y: event.clientY, + }) + + // 根据节点类型获取实际尺寸 + const dimensions = getNodeDimensions(type) + + // 调整位置,使节点中心点对齐到鼠标位置 + const adjustedPosition = { + x: position.x - dimensions.width / 2, + y: position.y - dimensions.height / 2, + } + + const nodeId = `node-${getRandomId()}` + const config = getNodeTypeConfig(type) + const newNode = { + id: nodeId, + type: "custom", + position: adjustedPosition, + data: { + label: config.label, + color: config.color, + originalType: type, + }, + style: createNodeStyle(type), + } + + addNodes([newNode]) + return newNode + } + + return { + isDragOver, + screenDragPos, + onDragOver, + onDragLeave, + onDrop, + } +} diff --git a/apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts b/apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts new file mode 100644 index 0000000..e9d41d4 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useFlowOperations.ts @@ -0,0 +1,142 @@ +import type { Ref } from "vue" +import type { Node, Edge, Connection } from "@vue-flow/core" +import { useVueFlow } from "@vue-flow/core" +import { getRandomId } from "utils/functions" + +export function useFlowOperations( + nodes: Ref, + edges: Ref, + addEdges: (edges: Edge[]) => void, + removeNodes: (nodeIds: string[]) => void, + removeEdges: (edgeIds: string[]) => void, + saveState: (nodes: Node[], edges: Edge[]) => void, +) { + const { findNode, getSelectedNodes, getSelectedEdges } = useVueFlow() + const getAutoLabel = ( + sourceNode: Node | undefined, + targetNode: Node | undefined, + sourceHandle: string | null | undefined, + targetHandle: string | null | undefined, + ) => { + const sourceType = sourceNode?.data?.originalType || sourceNode?.type + const targetType = targetNode?.data?.originalType || targetNode?.type + + // 如果是判断节点 + if (sourceType === "decision") { + // 根据handle ID推断标签 + if (sourceHandle === "yes") { + return "是" + } else if (sourceHandle === "no") { + return "否" + } + } + + // 如果是循环节点 + if (sourceType === "loop") { + // 根据handle ID推断标签 + if (sourceHandle === "continue") { + return "继续" + } else if (sourceHandle === "exit") { + return "退出" + } + } + + // 如果是循环体回到循环节点 + if (targetType === "loop") { + if (targetHandle === "return") { + return "返回" + } + } + // 默认情况 + return "" + } + + const handleConnect = (params: Connection) => { + const sourceNode = nodes.value.find((node) => node.id === params.source) + const targetNode = nodes.value.find((node) => node.id === params.target) + + // 自动推断标签 + const autoLabel = getAutoLabel( + sourceNode, + targetNode, + params.sourceHandle, + params.targetHandle, + ) + + const newEdge: Edge = { + id: `edge-${getRandomId()}`, + source: params.source, + target: params.target, + sourceHandle: params.sourceHandle, + targetHandle: params.targetHandle, + type: "default", + label: autoLabel, + } + + addEdges([newEdge]) + saveState(nodes.value, edges.value) + } + + const handleEdgeClick = ({ edge }: { edge: Edge }) => { + removeEdges([edge.id]) + saveState(nodes.value, edges.value) + } + + // 节点删除 + const handleNodeDelete = (nodeId: string) => { + // 删除相关边 + const relatedEdges = edges.value.filter( + (edge) => edge.source === nodeId || edge.target === nodeId, + ) + if (relatedEdges.length > 0) { + removeEdges(relatedEdges.map((edge) => edge.id)) + } + + removeNodes([nodeId]) + saveState(nodes.value, edges.value) + } + + // 节点更新,空标签时清除自定义标签(恢复默认类型名称) + const handleNodeUpdate = (nodeId: string, newLabel: string) => { + const node = findNode(nodeId) + if (node) { + if (newLabel) { + node.data = { ...node.data, customLabel: newLabel } + } else { + const { customLabel: _, ...rest } = node.data + node.data = rest + } + saveState(nodes.value, edges.value) + } + } + + // 清空画布 + const clearCanvas = () => { + nodes.value = [] + edges.value = [] + saveState(nodes.value, edges.value) + } + + // 删除选中的节点和边 + const deleteSelected = () => { + const selectedNodes = getSelectedNodes.value + const selectedEdges = getSelectedEdges.value + + if (selectedNodes.length > 0) { + removeNodes(selectedNodes.map((node) => node.id)) + } + if (selectedEdges.length > 0) { + removeEdges(selectedEdges.map((edge) => edge.id)) + } + saveState(nodes.value, edges.value) + } + + return { + handleConnect, + handleEdgeClick, + handleNodeDelete, + handleNodeUpdate, + clearCanvas, + deleteSelected, + } +} diff --git a/apps/web/src/shared/components/FlowchartEditor/useHistory.ts b/apps/web/src/shared/components/FlowchartEditor/useHistory.ts new file mode 100644 index 0000000..8ec9a50 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useHistory.ts @@ -0,0 +1,72 @@ +import { shallowRef, computed } from "vue" +import type { Node, Edge } from "@vue-flow/core" + +/** + * 简化的历史记录管理 + */ +export function useHistory() { + const history = shallowRef<{ nodes: Node[]; edges: Edge[] }[]>([]) + const historyIndex = ref(-1) + + // 是否可以撤销 + const canUndo = computed(() => historyIndex.value > 0) + + // 是否可以重做 + const canRedo = computed(() => historyIndex.value < history.value.length - 1) + + const deepCopyState = ( + nodes: Node[], + edges: Edge[], + ): { nodes: Node[]; edges: Edge[] } => + JSON.parse(JSON.stringify({ nodes, edges })) as { + nodes: Node[] + edges: Edge[] + } + + // 保存状态到历史记录 + const saveState = (nodes: Node[], edges: Edge[]) => { + const currentState = deepCopyState(nodes, edges) + + // 如果当前不在历史记录的末尾,删除后面的记录 + if (historyIndex.value < history.value.length - 1) { + history.value = history.value.slice(0, historyIndex.value + 1) + } + + history.value = [...history.value, currentState] + historyIndex.value = history.value.length - 1 + + // 限制历史记录数量 + if (history.value.length > 20) { + history.value = history.value.slice(1) + historyIndex.value-- + } + } + + // 撤销 + const undo = () => { + if (canUndo.value) { + historyIndex.value-- + const state = history.value[historyIndex.value] + return deepCopyState(state.nodes, state.edges) + } + return null + } + + // 重做 + const redo = () => { + if (canRedo.value) { + historyIndex.value++ + const state = history.value[historyIndex.value] + return deepCopyState(state.nodes, state.edges) + } + return null + } + + return { + canUndo, + canRedo, + saveState, + undo, + redo, + } +} diff --git a/apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts b/apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts new file mode 100644 index 0000000..45e2114 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartEditor/useNodeStyles.ts @@ -0,0 +1,137 @@ +/** + * 节点样式管理 + */ + +/** + * 获取节点类型配置 + */ +export function getNodeTypeConfig(type: string) { + const configs: Record< + string, + { label: string; color: string; icon: string; description: string } + > = { + start: { + label: "开始", + color: "#10b981", + icon: "▶", + description: "流程开始", + }, + input: { + label: "输入", + color: "#06b6d4", + icon: "📥", + description: "数据输入", + }, + default: { + label: "赋值", + color: "#3b82f6", + icon: "⚙", + description: "赋值语句", + }, + decision: { + label: "判断", + color: "#f59e0b", + icon: "❓", + description: "条件语句", + }, + loop: { + label: "循环", + color: "#8b5cf6", + icon: "🔄", + description: "循环语句", + }, + output: { + label: "输出", + color: "#84cc16", + icon: "📤", + description: "数据输出", + }, + end: { + label: "结束", + color: "#ef4444", + icon: "⏹", + description: "流程结束", + }, + } + return ( + configs[type] || { + label: "节点", + color: "#6b7280", + icon: "⚪", + description: "未知节点", + } + ) +} + +/** + * 获取节点样式 + */ +export function getNodeStyle(type: string, color: string) { + const baseStyle = { + background: color, + color: "white", + border: `2px solid ${color}`, + borderRadius: "10px", + fontSize: "16px", + fontWeight: "500", + width: "auto", // 自动宽度 + height: "auto", // 自动高度 + minWidth: "100px", + minHeight: "40px", + maxWidth: "400px", + maxHeight: "160px", + display: "flex", + alignItems: "center", + justifyContent: "center", + boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)", + } + + // 根据节点类型调整样式 + switch (type) { + case "start": + case "end": + return { + ...baseStyle, + } + case "decision": + return { + ...baseStyle, + borderRadius: "8px", + minWidth: "140px", + minHeight: "50px", + } + case "loop": + return { + ...baseStyle, + borderRadius: "8px", + minWidth: "140px", + minHeight: "50px", + } + default: + return baseStyle + } +} + +/** + * 创建节点样式 + */ +export function createNodeStyle(type: string) { + const config = getNodeTypeConfig(type) + return getNodeStyle(type, config.color) +} + +/** + * 获取节点尺寸 + */ +export function getNodeDimensions(type: string) { + switch (type) { + case "start": + case "end": + return { width: 100, height: 40 } + case "decision": + case "loop": + return { width: 140, height: 50 } + default: + return { width: 120, height: 40 } + } +} diff --git a/apps/web/src/shared/components/FlowchartStatisticsPanel.vue b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue new file mode 100644 index 0000000..fcbdaf4 --- /dev/null +++ b/apps/web/src/shared/components/FlowchartStatisticsPanel.vue @@ -0,0 +1,577 @@ + + + + + diff --git a/apps/web/src/shared/components/Header.vue b/apps/web/src/shared/components/Header.vue new file mode 100644 index 0000000..4e4db69 --- /dev/null +++ b/apps/web/src/shared/components/Header.vue @@ -0,0 +1,356 @@ + + + + + diff --git a/apps/web/src/shared/components/Hitokoto.vue b/apps/web/src/shared/components/Hitokoto.vue new file mode 100644 index 0000000..f8ae727 --- /dev/null +++ b/apps/web/src/shared/components/Hitokoto.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web/src/shared/components/IconButton.vue b/apps/web/src/shared/components/IconButton.vue new file mode 100644 index 0000000..8fbe0b1 --- /dev/null +++ b/apps/web/src/shared/components/IconButton.vue @@ -0,0 +1,29 @@ + + diff --git a/apps/web/src/shared/components/Login.vue b/apps/web/src/shared/components/Login.vue new file mode 100644 index 0000000..25e565d --- /dev/null +++ b/apps/web/src/shared/components/Login.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/apps/web/src/shared/components/LoginSummaryModal.vue b/apps/web/src/shared/components/LoginSummaryModal.vue new file mode 100644 index 0000000..be3083b --- /dev/null +++ b/apps/web/src/shared/components/LoginSummaryModal.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/web/src/shared/components/MarkdownEditor.vue b/apps/web/src/shared/components/MarkdownEditor.vue new file mode 100644 index 0000000..c0ee0f7 --- /dev/null +++ b/apps/web/src/shared/components/MarkdownEditor.vue @@ -0,0 +1,40 @@ + + + diff --git a/apps/web/src/shared/components/MermaidEditor.vue b/apps/web/src/shared/components/MermaidEditor.vue new file mode 100644 index 0000000..238e5ac --- /dev/null +++ b/apps/web/src/shared/components/MermaidEditor.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/apps/web/src/shared/components/Pagination.vue b/apps/web/src/shared/components/Pagination.vue new file mode 100644 index 0000000..478d30d --- /dev/null +++ b/apps/web/src/shared/components/Pagination.vue @@ -0,0 +1,61 @@ + + + + diff --git a/apps/web/src/shared/components/Signup.vue b/apps/web/src/shared/components/Signup.vue new file mode 100644 index 0000000..1e22389 --- /dev/null +++ b/apps/web/src/shared/components/Signup.vue @@ -0,0 +1,131 @@ + + + diff --git a/apps/web/src/shared/components/StatisticsPanel.vue b/apps/web/src/shared/components/StatisticsPanel.vue new file mode 100644 index 0000000..52bd0b4 --- /dev/null +++ b/apps/web/src/shared/components/StatisticsPanel.vue @@ -0,0 +1,470 @@ + + + diff --git a/apps/web/src/shared/components/SubmissionResultTag.vue b/apps/web/src/shared/components/SubmissionResultTag.vue new file mode 100644 index 0000000..9bf859f --- /dev/null +++ b/apps/web/src/shared/components/SubmissionResultTag.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/apps/web/src/shared/components/SyncCodeEditor.vue b/apps/web/src/shared/components/SyncCodeEditor.vue new file mode 100644 index 0000000..9c18670 --- /dev/null +++ b/apps/web/src/shared/components/SyncCodeEditor.vue @@ -0,0 +1,153 @@ + + + diff --git a/apps/web/src/shared/components/TextCopy.vue b/apps/web/src/shared/components/TextCopy.vue new file mode 100644 index 0000000..551c561 --- /dev/null +++ b/apps/web/src/shared/components/TextCopy.vue @@ -0,0 +1,33 @@ + + diff --git a/apps/web/src/shared/components/TextEditor.vue b/apps/web/src/shared/components/TextEditor.vue new file mode 100644 index 0000000..2bc8097 --- /dev/null +++ b/apps/web/src/shared/components/TextEditor.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/apps/web/src/shared/components/UserBadge.vue b/apps/web/src/shared/components/UserBadge.vue new file mode 100644 index 0000000..9688acb --- /dev/null +++ b/apps/web/src/shared/components/UserBadge.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/apps/web/src/shared/composables/breakpoints.ts b/apps/web/src/shared/composables/breakpoints.ts new file mode 100644 index 0000000..71cc669 --- /dev/null +++ b/apps/web/src/shared/composables/breakpoints.ts @@ -0,0 +1,20 @@ +import { + breakpointsTailwind, + useBreakpoints as useVueUseBreakpoints, +} from "@vueuse/core" + +/** + * 响应式断点检测 composable + * 每次调用创建新的断点检测实例 + */ +export function useBreakpoints() { + const breakpoints = useVueUseBreakpoints(breakpointsTailwind) + + const isMobile = breakpoints.smallerOrEqual("md") + const isDesktop = breakpoints.greater("md") + + return { + isMobile, + isDesktop, + } +} diff --git a/apps/web/src/shared/composables/configUpdate.ts b/apps/web/src/shared/composables/configUpdate.ts new file mode 100644 index 0000000..4974f36 --- /dev/null +++ b/apps/web/src/shared/composables/configUpdate.ts @@ -0,0 +1,41 @@ +import { useConfigStore } from "shared/store/config" +import { useUserStore } from "shared/store/user" +import { + useConfigWebSocket, + type ConfigUpdate, +} from "shared/composables/websocket" + +export function useConfigUpdate() { + const configStore = useConfigStore() + const userStore = useUserStore() + + // 处理 WebSocket 配置更新 + const handleConfigUpdate = (data: ConfigUpdate) => { + // 更新全局配置 - 使用响应式方式 + if (data.key in configStore.config) { + // 直接修改 ref 的值来触发响应式更新 + ;(configStore.config as any)[data.key] = data.value + } + } + + // 初始化 WebSocket - handler 会在 onMounted 时自动添加 + const { connect, disconnect } = useConfigWebSocket(handleConfigUpdate) + + // 监听登录状态变化 + watch( + () => userStore.isAuthed, + (isAuthed) => { + if (isAuthed) { + connect() + } else { + disconnect() + } + }, + { immediate: true }, + ) + + return { + connect, + disconnect, + } +} diff --git a/apps/web/src/shared/composables/learnProgress.ts b/apps/web/src/shared/composables/learnProgress.ts new file mode 100644 index 0000000..8a638e6 --- /dev/null +++ b/apps/web/src/shared/composables/learnProgress.ts @@ -0,0 +1,15 @@ +import { useStorage } from "@vueuse/core" +import { STORAGE_KEY } from "utils/constants" + +export type TutorialType = "python" | "c" + +// 模块级单例:学习页写入、导航栏读取,必须共用同一个响应式引用, +// 否则导航栏的链接不会随学习进度更新 +const learnStep = useStorage>( + STORAGE_KEY.LEARN_CURRENT_STEP, + { python: 1, c: 1 }, +) + +export function useLearnProgress() { + return { learnStep } +} diff --git a/apps/web/src/shared/composables/maxkb.ts b/apps/web/src/shared/composables/maxkb.ts new file mode 100644 index 0000000..a84f909 --- /dev/null +++ b/apps/web/src/shared/composables/maxkb.ts @@ -0,0 +1,132 @@ +import { useConfigStore } from "shared/store/config" +import { useUserStore } from "shared/store/user" +import { + useConfigWebSocket, + type ConfigUpdate, +} from "shared/composables/websocket" + +export function useMaxKB() { + const configStore = useConfigStore() + const userStore = useUserStore() + const isLoaded = ref(false) + + // 处理 WebSocket 配置更新 - 只处理 MaxKB 相关 + const handleConfigUpdate = (data: ConfigUpdate) => { + if (data.key === "enable_maxkb") { + if (data.value) { + loadMaxKBScript() + } else { + removeMaxKBScript() + } + } + } + + // 初始化 WebSocket + const { connect, disconnect } = useConfigWebSocket(handleConfigUpdate) + + // 监听登录状态变化 + watch( + () => userStore.isAuthed, + (isAuthed) => { + if (isAuthed) { + connect() + } else { + disconnect() + } + }, + { immediate: true }, + ) + + const loadMaxKBScript = () => { + const { enable_maxkb } = configStore.config + + if (!enable_maxkb) { + return + } + + const existingScript = document.querySelector( + `script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`, + ) + if (existingScript) { + isLoaded.value = true + return + } + + // 创建并插入脚本标签 + const script = document.createElement("script") + script.src = import.meta.env.PUBLIC_MAXKB_URL + script.async = true + script.defer = true + + script.onload = () => { + isLoaded.value = true + } + + script.onerror = () => { + console.error("Failed to load MaxKB script") + } + + document.head.appendChild(script) + } + + const removeMaxKBScript = () => { + // 把 script 也删除 + const script = document.querySelector( + `script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`, + ) + if (script) { + script.remove() + } + // 等待DOM加载完成后删除所有id以"maxkb-"开头的元素 + const removeMaxKBElements = () => { + // 查找所有id以"maxkb-"开头的元素 + const elements = document.querySelectorAll('[id^="maxkb-"]') + + elements.forEach((element) => { + element.remove() + }) + } + + // 如果DOM已经加载完成,直接执行删除 + if (document.readyState === "complete") { + removeMaxKBElements() + } else { + // 等待DOM加载完成 + window.addEventListener("load", removeMaxKBElements, { once: true }) + } + + // 移除MaxKB脚本标签 + const existingScript = document.querySelector( + `script[src="${import.meta.env.PUBLIC_MAXKB_URL}"]`, + ) + if (existingScript) { + existingScript.remove() + } + + // 重置加载状态 + isLoaded.value = false + } + + // 连接 WebSocket + onMounted(() => { + connect() + }) + + watch( + () => configStore.config.enable_maxkb, + (enabled) => { + if (enabled) { + loadMaxKBScript() + } else { + removeMaxKBScript() + } + }, + { immediate: true }, + ) + + return { + loadMaxKBScript, + removeMaxKBScript, + isLoaded: readonly(isLoaded), + } +} diff --git a/apps/web/src/shared/composables/pagination.ts b/apps/web/src/shared/composables/pagination.ts new file mode 100644 index 0000000..f0204e9 --- /dev/null +++ b/apps/web/src/shared/composables/pagination.ts @@ -0,0 +1,154 @@ +import { reactive, watch } from "vue" +import { useRoute, useRouter } from "vue-router" +import { filterEmptyValue } from "utils/functions" + +export interface PaginationQuery { + page: number + limit: number + [key: string]: any +} + +export interface UsePaginationOptions { + /** 默认每页条数 */ + defaultLimit?: number + /** 默认页码 */ + defaultPage?: number + /** 当其他查询条件变化时是否重置页码 */ + resetPageOnChange?: boolean +} + +/** + * 分页相关的 composable,处理分页状态和 URL 同步 + * 每次调用创建新的分页状态实例 + * @param initialQuery 初始查询参数对象 + * @param options 配置选项 + */ +export function usePagination>( + initialQuery: Omit = {} as Omit, + options: UsePaginationOptions = {}, +) { + const { + defaultLimit = 10, + defaultPage = 1, + resetPageOnChange = true, + } = options + + const route = useRoute() + const router = useRouter() + + // 从 URL 查询参数初始化状态 + const query = reactive({ + page: parseInt(route.query.page) || defaultPage, + limit: parseInt(route.query.limit) || defaultLimit, + ...initialQuery, + }) as unknown as T & PaginationQuery + + // 同步 URL 查询参数到本地状态 + function syncFromRoute() { + ;(query as any).page = parseInt(route.query.page) || defaultPage + ;(query as any).limit = parseInt(route.query.limit) || defaultLimit + + // 同步其他查询参数 + Object.keys(initialQuery).forEach((key) => { + const value = route.query[key] + if (value !== undefined) { + // 处理不同类型的参数 + if (typeof initialQuery[key] === "boolean") { + ;(query as any)[key] = value === "1" || value === "true" + } else if (typeof initialQuery[key] === "number") { + ;(query as any)[key] = parseInt(value) || initialQuery[key] + } else { + ;(query as any)[key] = value || initialQuery[key] + } + } + }) + } + + // 更新 URL + function updateRoute() { + const newQuery = filterEmptyValue(query) + router.push({ + path: route.path, + query: newQuery, + }) + } + + // 重置页码到第一页 + function resetPage() { + ;(query as any).page = defaultPage + } + + // 清空所有查询条件(除了分页参数) + function clearQuery() { + Object.keys(initialQuery).forEach((key) => { + const initialValue = initialQuery[key] + if (typeof initialValue === "string") { + ;(query as any)[key] = "" + } else if (typeof initialValue === "boolean") { + ;(query as any)[key] = false + } else if (typeof initialValue === "number") { + ;(query as any)[key] = 0 + } else { + ;(query as any)[key] = initialValue + } + }) + resetPage() + } + + // 监听页码变化,同步到 URL + watch(() => query.page, updateRoute) + + // 监听每页条数变化,重置页码并同步到 URL + watch( + () => query.limit, + () => { + if (resetPageOnChange) { + resetPage() + } + updateRoute() + }, + ) + + // 监听其他查询条件变化,重置页码并同步到 URL + if (resetPageOnChange && Object.keys(initialQuery).length > 0) { + const otherQueryKeys = Object.keys(initialQuery) + watch( + () => otherQueryKeys.map((key) => query[key]), + () => { + resetPage() + updateRoute() + }, + { deep: true }, + ) + } + + // 监听路由变化,同步到本地状态 + watch( + () => route.query, + () => { + syncFromRoute() + }, + { deep: true }, + ) + + return { + query, + updateRoute, + resetPage, + clearQuery, + syncFromRoute, + } +} + +/** + * 简化版本的分页 composable,只处理基本的分页逻辑 + * 每次调用创建新的分页状态实例 + * @param defaultLimit 默认每页条数 + * @param defaultPage 默认页码 + */ +export function useSimplePagination(defaultLimit = 10, defaultPage = 1) { + return usePagination( + {}, + { defaultLimit, defaultPage, resetPageOnChange: false }, + ) +} diff --git a/apps/web/src/shared/composables/rarity.ts b/apps/web/src/shared/composables/rarity.ts new file mode 100644 index 0000000..237170c --- /dev/null +++ b/apps/web/src/shared/composables/rarity.ts @@ -0,0 +1,15 @@ +import { useDark } from "@vueuse/core" +import { computed } from "vue" +import { RARITY_TEXT_COLOR } from "utils/constants" + +/** + * 成就稀有度的文字配色,跟随明暗主题切换。 + * 两套色值都压到 4.5:1 以上,不然浅色模式下白金和黄金的小字看不清。 + * 边框和色块不用这个,直接用 RARITY_COLOR 的原色。 + */ +export function useRarityColor() { + const isDark = useDark() + return computed(() => + isDark.value ? RARITY_TEXT_COLOR.dark : RARITY_TEXT_COLOR.light, + ) +} diff --git a/apps/web/src/shared/composables/sync.ts b/apps/web/src/shared/composables/sync.ts new file mode 100644 index 0000000..7dfa0f0 --- /dev/null +++ b/apps/web/src/shared/composables/sync.ts @@ -0,0 +1,412 @@ +import { useMessage } from "naive-ui" +import { useUserStore } from "../store/user" +import type { EditorView } from "@codemirror/view" +import { Compartment } from "@codemirror/state" +import type { WebrtcProvider } from "y-webrtc" +import type { Doc, Text } from "yjs" + +// 常量定义 +const SYNC_CONSTANTS = { + MAX_ROOM_USERS: 2, + AWARENESS_SYNC_DELAY: 500, + INIT_SYNC_TIMEOUT: 500, + SUPER_ADMIN_COLOR: "#ff6b6b", + REGULAR_USER_COLOR: "#4dabf7", +} as const + +// 错误类型码 +export const SYNC_ERROR_CODES = { + SUPER_ADMIN_LEFT: "SUPER_ADMIN_LEFT", + MISSING_SUPER_ADMIN: "MISSING_SUPER_ADMIN", +} as const + +// 界面和通知文案 +export const SYNC_MESSAGES = { + // 超管离开 + SUPER_ADMIN_LEFT: (name: string) => `超管 ${name} 已离开`, + + // 缺少超管 + MISSING_SUPER_ADMIN: "协同编辑需要超管", + + // 连接成功 + SYNC_ACTIVE: "协同编辑已激活!", + + // 连接断开 + CONNECTION_LOST: "协同编辑已断开", + + // 等待相关 + WAITING_STUDENT: "正在等待学生加入...", + WAITING_ADMIN: "正在等待超管加入...", + + // Form.vue 界面文案 + SYNC_ON: "断开同步", + SYNC_OFF: "开启同步", + SYNCING_WITH: (name: string) => `与 ${name} 同步中`, + STUDENT_LEFT: (name?: string) => (name ? `${name}已离开` : "可以关闭同步"), +} as const + +// 类型定义 +type SyncState = "waiting" | "active" | "error" +type SyncErrorCode = (typeof SYNC_ERROR_CODES)[keyof typeof SYNC_ERROR_CODES] + +interface UserInfo { + name: string + isSuperAdmin: boolean +} + +interface PeersEvent { + webrtcPeers: string[] +} + +interface StatusEvent { + connected: boolean +} + +interface SyncedEvent { + synced: boolean +} + +interface SyncOptions { + problemId: string + editorView: EditorView + onStatusChange?: (status: SyncStatus) => void +} + +export interface SyncStatus { + connected: boolean + roomUsers: number + canSync: boolean + message: string + error?: string + errorCode?: SyncErrorCode + otherUser?: UserInfo +} + +/** + * 代码同步 composable + * 每次调用创建新的同步实例 + */ +export function useCodeSync() { + const userStore = useUserStore() + const message = useMessage() + + // 每次调用创建新的实例变量 + let ydoc: Doc | null = null + let provider: WebrtcProvider | null = null + let ytext: Text | null = null + const collabCompartment = new Compartment() + let currentEditorView: EditorView | null = null + let lastSyncState: SyncState | null = null + let roomUserInfo = new Map() + let hasShownSuperAdminLeftMessage = false + + const updateStatus = ( + status: SyncStatus, + onStatusChange?: (status: SyncStatus) => void, + ) => { + onStatusChange?.(status) + } + + const normalizeClientId = (clientId: number | string): number => { + return typeof clientId === "string" ? parseInt(clientId, 10) : clientId + } + + const checkHasSuperAdmin = (awarenessStates: Map): boolean => { + if (userStore.isSuperAdmin) return true + return Array.from(awarenessStates.values()).some( + (state) => state.user?.isSuperAdmin, + ) + } + + const getOtherUserInfo = ( + awarenessStates: Map, + ): UserInfo | undefined => { + if (!provider) return undefined + + const localClientId = provider.awareness.clientID + for (const [clientId, state] of awarenessStates) { + if (clientId !== localClientId && state.user) { + return { + name: state.user.name, + isSuperAdmin: state.user.isSuperAdmin, + } + } + } + return undefined + } + + const checkIfSuperAdminLeft = ( + removedClientIds: number[], + onStatusChange?: (status: SyncStatus) => void, + ) => { + if (userStore.isSuperAdmin || hasShownSuperAdminLeftMessage) return + + const superAdminInfo = removedClientIds + .map((id) => roomUserInfo.get(id)) + .find((info) => info?.isSuperAdmin) + + if (superAdminInfo) { + hasShownSuperAdminLeftMessage = true + const leftMessage = SYNC_MESSAGES.SUPER_ADMIN_LEFT(superAdminInfo.name) + updateStatus( + { + connected: false, + roomUsers: 0, + canSync: false, + message: leftMessage, + error: leftMessage, + errorCode: SYNC_ERROR_CODES.SUPER_ADMIN_LEFT, + }, + onStatusChange, + ) + message.warning(leftMessage) + stopSync() + } + } + + const checkRoomPermissions = ( + roomUsers: number, + onStatusChange?: (status: SyncStatus) => void, + ) => { + const awarenessStates = provider?.awareness.getStates() + if (!awarenessStates) return + + const hasSuperAdmin = checkHasSuperAdmin(awarenessStates) + const canSync = roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && hasSuperAdmin + const otherUser = getOtherUserInfo(awarenessStates) + + if (roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && !hasSuperAdmin) { + if (lastSyncState === "error") return + + updateStatus( + { + connected: false, + roomUsers, + canSync: false, + message: SYNC_MESSAGES.MISSING_SUPER_ADMIN, + error: SYNC_MESSAGES.MISSING_SUPER_ADMIN, + errorCode: SYNC_ERROR_CODES.MISSING_SUPER_ADMIN, + otherUser, + }, + onStatusChange, + ) + message.error(SYNC_MESSAGES.MISSING_SUPER_ADMIN) + lastSyncState = "error" + stopSync() + return + } else if (canSync) { + updateStatus( + { + connected: true, + roomUsers, + canSync: true, + message: SYNC_MESSAGES.SYNC_ACTIVE, + otherUser, + }, + onStatusChange, + ) + if (lastSyncState !== "active") { + message.success(SYNC_MESSAGES.SYNC_ACTIVE) + lastSyncState = "active" + } + } else { + updateStatus( + { + connected: true, + roomUsers, + canSync: false, + message: + roomUsers === 1 + ? SYNC_MESSAGES.WAITING_STUDENT + : SYNC_MESSAGES.WAITING_ADMIN, + otherUser, + }, + onStatusChange, + ) + lastSyncState = "waiting" + } + } + + const setupContentSync = ( + ytext: Text, + provider: WebrtcProvider, + savedContent: string, + ) => { + let hasInitialized = false + + const initTimeout = setTimeout(() => { + if (!hasInitialized && ytext.length === 0 && savedContent) { + ytext.insert(0, savedContent) + } + hasInitialized = true + }, SYNC_CONSTANTS.INIT_SYNC_TIMEOUT) + + provider.on("synced", (event: SyncedEvent) => { + if (!event.synced || hasInitialized) return + + clearTimeout(initTimeout) + if (ytext.length === 0 && savedContent) { + ytext.insert(0, savedContent) + } + hasInitialized = true + }) + } + + async function startSync(options: SyncOptions): Promise<() => void> { + const { problemId, editorView, onStatusChange } = options + + if (!userStore.isAuthed) { + return () => {} + } + + // 动态导入 yjs 相关模块 + const [Y, { WebrtcProvider }, { yCollab }] = await Promise.all([ + import("yjs"), + import("y-webrtc"), + import("y-codemirror.next"), + ]) + + // 初始化文档和提供者 + ydoc = new Y.Doc() + ytext = ydoc.getText("codemirror") + const roomName = `problem-${problemId}` + + provider = new WebrtcProvider(roomName, ydoc, { + signaling: [import.meta.env.PUBLIC_SIGNALING_URL], + maxConns: 1, + filterBcConns: true, + }) + + // 监听连接状态 + provider.on("status", (event: StatusEvent) => { + if (!event.connected) { + updateStatus( + { + connected: false, + roomUsers: 0, + canSync: false, + message: SYNC_MESSAGES.CONNECTION_LOST, + error: SYNC_MESSAGES.CONNECTION_LOST, + }, + onStatusChange, + ) + message.warning(SYNC_MESSAGES.CONNECTION_LOST) + } + }) + + // 监听用户加入/离开 + provider.on("peers", (event: PeersEvent) => { + const roomUsers = event.webrtcPeers.length + 1 + setTimeout(() => { + checkRoomPermissions(roomUsers, onStatusChange) + }, SYNC_CONSTANTS.AWARENESS_SYNC_DELAY) + }) + + // 监听 awareness 变化 + provider.awareness.on("change", (changes: any) => { + if (!provider) return + + const awarenessStates = provider.awareness.getStates() + + if (changes.removed?.length > 0) { + checkIfSuperAdminLeft(changes.removed, onStatusChange) + } + + awarenessStates.forEach((state, clientId) => { + if (state.user) { + const normalizedId = normalizeClientId(clientId) + roomUserInfo.set(normalizedId, { + name: state.user.name, + isSuperAdmin: state.user.isSuperAdmin, + }) + } + }) + + checkRoomPermissions(awarenessStates.size, onStatusChange) + }) + + // 配置编辑器扩展 + if (editorView && ytext) { + currentEditorView = editorView + const userColor = userStore.isSuperAdmin + ? SYNC_CONSTANTS.SUPER_ADMIN_COLOR + : SYNC_CONSTANTS.REGULAR_USER_COLOR + const userName = userStore.user?.username || "匿名用户" + const savedContent = editorView.state.doc.toString() + + // 设置用户信息 + provider.awareness.setLocalStateField("user", { + name: userName, + color: userColor, + isSuperAdmin: userStore.isSuperAdmin, + }) + + // 清空编辑器并应用协同扩展 + editorView.dispatch({ + changes: { from: 0, to: editorView.state.doc.length, insert: "" }, + }) + + const collabExt = yCollab(ytext, provider.awareness) + editorView.dispatch({ + effects: collabCompartment.reconfigure(collabExt), + }) + + // 设置内容同步 + setupContentSync(ytext, provider, savedContent) + + // 设置初始状态 + const waitingMessage = userStore.isSuperAdmin + ? SYNC_MESSAGES.WAITING_STUDENT + : SYNC_MESSAGES.WAITING_ADMIN + + updateStatus( + { + connected: true, + roomUsers: 1, + canSync: false, + message: waitingMessage, + }, + onStatusChange, + ) + + message.info(waitingMessage) + lastSyncState = "waiting" + } + + return () => stopSync() + } + + function stopSync() { + if (currentEditorView) { + try { + currentEditorView.dispatch({ + effects: collabCompartment.reconfigure([]), + }) + } catch (error) { + console.warn("移除协同编辑扩展失败:", error) + } + currentEditorView = null + } + + provider?.disconnect() + provider?.destroy() + ydoc?.destroy() + + provider = null + ydoc = null + ytext = null + lastSyncState = null + roomUserInfo.clear() + hasShownSuperAdminLeftMessage = false + } + + function getInitialExtension() { + return collabCompartment.of([]) + } + + return { + startSync, + stopSync, + getInitialExtension, + } +} diff --git a/apps/web/src/shared/composables/useMermaid.ts b/apps/web/src/shared/composables/useMermaid.ts new file mode 100644 index 0000000..0925627 --- /dev/null +++ b/apps/web/src/shared/composables/useMermaid.ts @@ -0,0 +1,334 @@ +import { getRandomId } from "utils/functions" + +const mermaidThemeVariables = { + primaryColor: "#e0f2fe", + primaryTextColor: "#0f172a", + primaryBorderColor: "#0284c7", + lineColor: "#64748b", + arrowheadColor: "#64748b", + secondaryColor: "#f5f3ff", + tertiaryColor: "#ecfdf5", + background: "#ffffff", + mainBkg: "#f8fafc", + secondBkg: "#eef2ff", + tertiaryBkg: "#f0fdfa", + nodeBorder: "#2563eb", + clusterBkg: "#f8fafc", + clusterBorder: "#cbd5e1", + edgeLabelBackground: "#ffffff", + fontFamily: + 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif', +} + +const semanticNodeClasses = [ + "startNode", + "endNode", + "startEnd", + "input", + "output", + "process", + "decision", + "loop", +] + +const displayStyleId = "oj-mermaid-display-style" + +const mermaidDisplayStyle = ` +.oj-mermaid-flowchart { + max-width: 100%; + height: auto; +} + +.oj-mermaid-flowchart g.node rect, +.oj-mermaid-flowchart g.node polygon, +.oj-mermaid-flowchart g.node ellipse, +.oj-mermaid-flowchart g.node circle, +.oj-mermaid-flowchart g.node path { + stroke-width: 2px !important; + filter: drop-shadow(0 6px 12px rgba(15, 23, 42, 0.12)); +} + +.oj-mermaid-flowchart g.node.startNode rect, +.oj-mermaid-flowchart g.node.startNode polygon, +.oj-mermaid-flowchart g.node.startNode ellipse, +.oj-mermaid-flowchart g.node.startNode circle, +.oj-mermaid-flowchart g.node.startNode path, +.oj-mermaid-flowchart g.node.startEnd rect, +.oj-mermaid-flowchart g.node.startEnd polygon, +.oj-mermaid-flowchart g.node.startEnd ellipse, +.oj-mermaid-flowchart g.node.startEnd circle, +.oj-mermaid-flowchart g.node.startEnd path { + fill: #dcfce7 !important; + stroke: #16a34a !important; +} + +.oj-mermaid-flowchart g.node.endNode rect, +.oj-mermaid-flowchart g.node.endNode polygon, +.oj-mermaid-flowchart g.node.endNode ellipse, +.oj-mermaid-flowchart g.node.endNode circle, +.oj-mermaid-flowchart g.node.endNode path { + fill: #fee2e2 !important; + stroke: #dc2626 !important; +} + +.oj-mermaid-flowchart g.node.input rect, +.oj-mermaid-flowchart g.node.input polygon, +.oj-mermaid-flowchart g.node.input ellipse, +.oj-mermaid-flowchart g.node.input circle, +.oj-mermaid-flowchart g.node.input path { + fill: #dbeafe !important; + stroke: #2563eb !important; +} + +.oj-mermaid-flowchart g.node.output rect, +.oj-mermaid-flowchart g.node.output polygon, +.oj-mermaid-flowchart g.node.output ellipse, +.oj-mermaid-flowchart g.node.output circle, +.oj-mermaid-flowchart g.node.output path { + fill: #ede9fe !important; + stroke: #7c3aed !important; +} + +.oj-mermaid-flowchart g.node.process rect, +.oj-mermaid-flowchart g.node.process polygon, +.oj-mermaid-flowchart g.node.process ellipse, +.oj-mermaid-flowchart g.node.process circle, +.oj-mermaid-flowchart g.node.process path { + fill: #f0f9ff !important; + stroke: #0284c7 !important; +} + +.oj-mermaid-flowchart g.node.decision rect, +.oj-mermaid-flowchart g.node.decision polygon, +.oj-mermaid-flowchart g.node.decision ellipse, +.oj-mermaid-flowchart g.node.decision circle, +.oj-mermaid-flowchart g.node.decision path { + fill: #fef3c7 !important; + stroke: #d97706 !important; +} + +.oj-mermaid-flowchart g.node.loop rect, +.oj-mermaid-flowchart g.node.loop polygon, +.oj-mermaid-flowchart g.node.loop ellipse, +.oj-mermaid-flowchart g.node.loop circle, +.oj-mermaid-flowchart g.node.loop path { + fill: #fae8ff !important; + stroke: #c026d3 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-0 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-0 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-0 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-0 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-0 path { + fill: #dbeafe !important; + stroke: #2563eb !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-1 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-1 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-1 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-1 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-1 path { + fill: #ccfbf1 !important; + stroke: #0d9488 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-2 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-2 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-2 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-2 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-2 path { + fill: #ede9fe !important; + stroke: #7c3aed !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-3 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-3 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-3 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-3 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-3 path { + fill: #ffe4e6 !important; + stroke: #e11d48 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-4 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-4 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-4 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-4 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-4 path { + fill: #fef3c7 !important; + stroke: #d97706 !important; +} + +.oj-mermaid-flowchart g.node.oj-node-palette-5 rect, +.oj-mermaid-flowchart g.node.oj-node-palette-5 polygon, +.oj-mermaid-flowchart g.node.oj-node-palette-5 ellipse, +.oj-mermaid-flowchart g.node.oj-node-palette-5 circle, +.oj-mermaid-flowchart g.node.oj-node-palette-5 path { + fill: #dcfce7 !important; + stroke: #16a34a !important; +} + +.oj-mermaid-flowchart g.node .label, +.oj-mermaid-flowchart g.node .nodeLabel, +.oj-mermaid-flowchart g.node .nodeLabel p, +.oj-mermaid-flowchart g.node .label span { + color: #0f172a !important; + fill: #0f172a !important; + font-weight: 650 !important; +} + +.oj-mermaid-flowchart .edgePaths path.path, +.oj-mermaid-flowchart .flowchart-link { + stroke: #64748b !important; + stroke-width: 2.4px !important; +} + +.oj-mermaid-flowchart marker path, +.oj-mermaid-flowchart .marker { + fill: #64748b !important; + stroke: #64748b !important; +} + +.oj-mermaid-flowchart .edgeLabel rect, +.oj-mermaid-flowchart .edgeLabel .labelBkg { + fill: rgba(255, 255, 255, 0.94) !important; + stroke: #cbd5e1 !important; +} + +.oj-mermaid-flowchart .edgeLabel, +.oj-mermaid-flowchart .edgeLabel span, +.oj-mermaid-flowchart .edgeLabel p { + color: #334155 !important; + font-weight: 600 !important; +} +` + +const svgNamespace = "http://www.w3.org/2000/svg" + +function getNodeLabel(node: SVGGElement): string { + const el = + node.querySelector(".nodeLabel p") || + node.querySelector(".nodeLabel") || + node.querySelector(".label span") || + node.querySelector(".label") + return el?.textContent?.trim() ?? "" +} + +function applyFlowchartDisplayStyle(container: HTMLElement) { + container.classList.add("oj-mermaid-surface") + + const svg = container.querySelector("svg") + if (!svg) return + + svg.classList.add("oj-mermaid-flowchart") + + const nodes = Array.from(svg.querySelectorAll("g.node")) + + // Assign palette indices by label so same label → same color, different labels → different colors + const labelPaletteMap = new Map() + let paletteCounter = 0 + + nodes.forEach((node) => { + const hasSemanticClass = semanticNodeClasses.some((className) => + node.classList.contains(className), + ) + if (!hasSemanticClass) { + const label = getNodeLabel(node) + if (!labelPaletteMap.has(label)) { + labelPaletteMap.set(label, paletteCounter % 6) + paletteCounter++ + } + node.classList.add(`oj-node-palette-${labelPaletteMap.get(label)}`) + } + }) + + svg.querySelector(`#${displayStyleId}`)?.remove() + const style = document.createElementNS(svgNamespace, "style") + style.setAttribute("id", displayStyleId) + style.textContent = mermaidDisplayStyle + svg.insertBefore(style, svg.firstChild) +} + +function getChromeVersion(): number { + const match = navigator.userAgent.match(/Chrome\/(\d+)/) + return match ? parseInt(match[1]) : Infinity +} + +let mermaidInstance: any = null +let mermaidIsLegacy = false + +async function loadMermaid() { + if (!mermaidInstance) { + if (getChromeVersion() < 94) { + mermaidInstance = (await import("mermaid-legacy")).default + mermaidIsLegacy = true + } else { + mermaidInstance = (await import("mermaid")).default + } + mermaidInstance.initialize({ + startOnLoad: false, + securityLevel: "strict", + theme: "base", + themeVariables: mermaidThemeVariables, + }) + } + return mermaidInstance +} + +export function useMermaid() { + const renderError = ref(null) + const renderSuccess = ref(false) + let renderGeneration = 0 + + const renderFlowchart = async ( + container: HTMLElement | null, + mermaidCode: string, + ) => { + renderError.value = null + renderSuccess.value = false + + if (container) container.innerHTML = "" + + if (!container || !mermaidCode?.trim()) return + + const gen = ++renderGeneration + try { + const m = await loadMermaid() + const id = `mermaid-${getRandomId()}` + // v9 (mermaid-legacy): callback-based render(id, code, cb) + // v10+: Promise-based render(id, code) → { svg } + const svg = mermaidIsLegacy + ? await new Promise((resolve, reject) => { + try { + m.render(id, mermaidCode, resolve) + } catch (e) { + reject(e) + } + }) + : (await m.render(id, mermaidCode)).svg + if (gen !== renderGeneration) return + container.innerHTML = svg + applyFlowchartDisplayStyle(container) + renderSuccess.value = true + } catch (error) { + if (gen !== renderGeneration) return + renderError.value = + error instanceof Error + ? error.message + : "流程图渲染失败,请检查代码格式" + } + } + + const clearError = () => { + renderError.value = null + } + + return { + renderError: readonly(renderError), + renderSuccess: readonly(renderSuccess), + renderFlowchart, + clearError, + } +} diff --git a/apps/web/src/shared/composables/websocket.ts b/apps/web/src/shared/composables/websocket.ts new file mode 100644 index 0000000..e267b3f --- /dev/null +++ b/apps/web/src/shared/composables/websocket.ts @@ -0,0 +1,544 @@ +import { ref, onUnmounted, type Ref } from "vue" + +/** + * WebSocket 连接状态 + */ +export type ConnectionStatus = + "disconnected" | "connecting" | "connected" | "error" + +/** + * WebSocket 消息类型 + */ +export interface WebSocketMessage { + type: string + [key: string]: any +} + +/** + * WebSocket 配置 + */ +export interface WebSocketConfig { + /** WebSocket 路径(如 '/ws/submission/') */ + path: string + /** 最大重连次数,默认 5 */ + maxReconnectAttempts?: number + /** 重连延迟(毫秒),默认 1000 */ + reconnectDelay?: number + /** 心跳间隔(毫秒),默认 30000(30秒) */ + heartbeatTime?: number + /** 是否启用心跳,默认 true */ + enableHeartbeat?: boolean + /** 是否启用自动重连,默认 true */ + enableAutoReconnect?: boolean +} + +/** + * WebSocket 消息处理器 + */ +export type MessageHandler = ( + data: T, +) => void + +/** + * WebSocket 基础连接管理类 + * 提供连接、重连、心跳等通用功能 + */ +export class BaseWebSocket { + protected ws: WebSocket | null = null + protected url: string + protected handlers: Set> = new Set() + protected reconnectAttempts = 0 + protected maxReconnectAttempts: number + protected reconnectDelay: number + protected heartbeatInterval: number | null = null + protected heartbeatTime: number + protected enableHeartbeat: boolean + protected enableAutoReconnect: boolean + protected disconnectTimer: number | null = null + + public status: Ref = ref("disconnected") + + constructor(config: WebSocketConfig) { + this.url = `${import.meta.env.PUBLIC_WS_URL}/${config.path}/` + + this.maxReconnectAttempts = config.maxReconnectAttempts ?? 5 + this.reconnectDelay = config.reconnectDelay ?? 1000 + this.heartbeatTime = config.heartbeatTime ?? 30000 + this.enableHeartbeat = config.enableHeartbeat ?? true + this.enableAutoReconnect = config.enableAutoReconnect ?? true + } + + /** + * 连接 WebSocket + */ + connect() { + if ( + this.ws && + (this.ws.readyState === WebSocket.OPEN || + this.ws.readyState === WebSocket.CONNECTING) + ) { + return + } + + this.status.value = "connecting" + + try { + this.ws = new WebSocket(this.url) + + this.ws.onopen = () => { + this.status.value = "connected" + this.reconnectAttempts = 0 + console.log(`[WebSocket] 连接成功: ${this.url}`) + if (this.enableHeartbeat) { + this.startHeartbeat() + } + this.onConnected() + } + + this.ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data) as T + console.log(`[WebSocket] 收到消息:`, data) + + // 处理心跳响应 + if (data.type === "pong") { + return + } + + // 调用消息处理钩子 + this.onMessage(data) + } catch (error) { + console.error("[WebSocket] 解析消息失败:", error) + } + } + + this.ws.onerror = (error) => { + console.error("[WebSocket] 连接错误:", error) + this.status.value = "error" + this.onError(error) + } + + this.ws.onclose = (event) => { + console.log( + `[WebSocket] 连接关闭: code=${event.code}, reason=${event.reason}`, + ) + this.status.value = "disconnected" + this.stopHeartbeat() + this.onDisconnected(event) + + // 自动重连 + if ( + this.enableAutoReconnect && + this.reconnectAttempts < this.maxReconnectAttempts + ) { + this.reconnectAttempts++ + const delay = this.reconnectDelay * this.reconnectAttempts + console.log( + `[WebSocket] 将在 ${delay}ms 后重连 (尝试 ${this.reconnectAttempts}/${this.maxReconnectAttempts})`, + ) + setTimeout(() => this.connect(), delay) + } + } + } catch (error) { + console.error("Failed to create WebSocket connection:", error) + this.status.value = "error" + } + } + + /** + * 断开连接 + */ + disconnect() { + this.cancelScheduledDisconnect() + this.stopHeartbeat() + this.enableAutoReconnect = false // 停止自动重连 + if (this.ws) { + this.ws.close() + this.ws = null + } + this.status.value = "disconnected" + } + + /** + * 安排延迟断开连接 + * @param delay 延迟时间(毫秒),默认 900000(15分钟) + */ + scheduleDisconnect(delay: number = 15 * 60 * 1000) { + // 取消之前的定时器 + this.cancelScheduledDisconnect() + + // 设置新的定时器 + this.disconnectTimer = window.setTimeout(() => { + const minutes = Math.floor(delay / 60000) + console.log(`WebSocket idle for ${minutes} minutes, disconnecting...`) + this.disconnect() + // 断开后需要重新允许自动重连 + this.enableAutoReconnect = true + }, delay) + } + + /** + * 取消已安排的断开连接 + */ + cancelScheduledDisconnect() { + if (this.disconnectTimer !== null) { + clearTimeout(this.disconnectTimer) + this.disconnectTimer = null + } + } + + /** + * 发送消息 + */ + send(data: any) { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(data)) + return true + } + return false + } + + /** + * 添加消息处理器 + */ + addHandler(handler: MessageHandler) { + this.handlers.add(handler) + } + + /** + * 移除消息处理器 + */ + removeHandler(handler: MessageHandler) { + this.handlers.delete(handler) + } + + /** + * 清除所有处理器 + */ + clearHandlers() { + this.handlers.clear() + } + + /** + * 发送心跳包 + */ + protected sendHeartbeat() { + this.send({ type: "ping", timestamp: Date.now() }) + } + + /** + * 开始心跳 + */ + protected startHeartbeat() { + this.stopHeartbeat() + this.heartbeatInterval = window.setInterval(() => { + this.sendHeartbeat() + }, this.heartbeatTime) + } + + /** + * 停止心跳 + */ + protected stopHeartbeat() { + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval) + this.heartbeatInterval = null + } + } + + /** + * 连接成功钩子(子类可重写) + */ + protected onConnected() { + // 子类实现 + } + + /** + * 断开连接钩子(子类可重写) + */ + protected onDisconnected(event: CloseEvent) { + // 子类实现 + } + + /** + * 错误钩子(子类可重写) + */ + protected onError(error: Event) { + // 子类实现 + } + + /** + * 消息处理钩子(子类可重写) + */ + protected onMessage(data: T) { + // 通知所有处理器 + this.handlers.forEach((handler) => { + try { + handler(data) + } catch (error) { + console.error("Error in message handler:", error) + } + }) + } +} + +/** + * 提交状态更新的数据类型 + */ +export interface SubmissionUpdate extends WebSocketMessage { + type: "submission_update" + submission_id: string + result: number + status: "pending" | "judging" | "finished" | "error" + time_cost?: number + memory_cost?: number + score?: number + err_info?: string +} + +/** + * 提交 WebSocket 连接管理类 + */ +class SubmissionWebSocket extends BaseWebSocket { + constructor() { + super({ + path: "submission", + }) + } + + /** + * 订阅特定提交的更新 + */ + subscribe(submissionId: string) { + const success = this.send({ + type: "subscribe", + submission_id: submissionId, + }) + if (!success) { + console.error("[WebSocket] 订阅失败: 连接未就绪") + } + } +} + +/** + * 用于组件中使用 WebSocket 的 Composable + * 每次调用创建新的 WebSocket 实例 + */ +export function useSubmissionWebSocket( + handler?: MessageHandler, +) { + const ws = new SubmissionWebSocket() + + // 如果提供了处理器,添加到实例中 + if (handler) { + ws.addHandler(handler) + } + + // 组件卸载时清理资源 + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + ws.disconnect() + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + subscribe: (submissionId: string) => ws.subscribe(submissionId), + scheduleDisconnect: (delay?: number) => ws.scheduleDisconnect(delay), + cancelScheduledDisconnect: () => ws.cancelScheduledDisconnect(), + status: ws.status, + addHandler: (h: MessageHandler) => ws.addHandler(h), + removeHandler: (h: MessageHandler) => ws.removeHandler(h), + } +} + +/** + * 通用 WebSocket Composable 工厂函数 + * 用于创建自定义的 WebSocket composable + * + * @example + * ```ts + * // 创建通知 WebSocket + * interface NotificationMessage extends WebSocketMessage { + * type: 'notification' + * title: string + * content: string + * } + * + * class NotificationWebSocket extends BaseWebSocket { + * constructor() { + * super({ path: 'notification' }) + * } + * } + * + * let notificationWs: NotificationWebSocket | null = null + * + * export function useNotificationWebSocket(handler?: MessageHandler) { + * if (!notificationWs) { + * notificationWs = new NotificationWebSocket() + * } + * return createWebSocketComposable(notificationWs, handler) + * } + * ``` + */ +export function createWebSocketComposable( + ws: BaseWebSocket, + handler?: MessageHandler, +) { + if (handler) { + ws.addHandler(handler) + } + + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + send: (data: any) => ws.send(data), + status: ws.status, + addHandler: (h: MessageHandler) => ws.addHandler(h), + removeHandler: (h: MessageHandler) => ws.removeHandler(h), + } +} + +/** + * 流程图评分更新消息类型 + */ +export interface FlowchartEvaluationUpdate extends WebSocketMessage { + type: + | "flowchart_evaluation_completed" + | "flowchart_evaluation_failed" + | "flowchart_evaluation_update" + submission_id: string + score?: number + grade?: string + feedback?: string + suggestions?: string + criteriaDetails?: any + error?: string +} + +/** + * 流程图 WebSocket 连接管理类 + */ +class FlowchartWebSocket extends BaseWebSocket { + constructor() { + super({ + path: "flowchart", // 使用专门的 flowchart WebSocket 路径 + }) + } + + /** + * 订阅特定流程图提交的更新 + */ + subscribe(submissionId: string) { + const success = this.send({ + type: "subscribe", + submission_id: submissionId, + }) + if (!success) { + console.error("[Flowchart WebSocket] 订阅失败: 连接未就绪") + } + } +} + +/** + * 用于组件中使用流程图 WebSocket 的 Composable + */ +export function useFlowchartWebSocket( + handler?: MessageHandler, +) { + const ws = new FlowchartWebSocket() + + // 如果提供了处理器,添加到实例中 + if (handler) { + ws.addHandler(handler) + } + + // 组件卸载时清理资源 + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + ws.disconnect() + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + subscribe: (submissionId: string) => ws.subscribe(submissionId), + scheduleDisconnect: (delay?: number) => ws.scheduleDisconnect(delay), + cancelScheduledDisconnect: () => ws.cancelScheduledDisconnect(), + status: ws.status, + addHandler: (h: MessageHandler) => + ws.addHandler(h), + removeHandler: (h: MessageHandler) => + ws.removeHandler(h), + } +} + +/** + * 配置更新消息类型 + */ +export interface ConfigUpdate extends WebSocketMessage { + type: "config_update" + key: string + value: any +} + +/** + * 配置 WebSocket 连接管理类 + */ +class ConfigWebSocket extends BaseWebSocket { + constructor() { + super({ + path: "config", + }) + } + + /** + * 发送配置更新 + */ + updateConfig(key: string, value: any) { + this.send({ + type: "config_update", + key, + value, + }) + } +} + +/** + * 用于组件中使用配置 WebSocket 的 Composable + */ +export function useConfigWebSocket(handler?: MessageHandler) { + const ws = new ConfigWebSocket() + + onMounted(() => { + if (handler) { + ws.addHandler(handler) + } + }) + + onUnmounted(() => { + if (handler) { + ws.removeHandler(handler) + } + ws.disconnect() + }) + + return { + connect: () => ws.connect(), + disconnect: () => ws.disconnect(), + updateConfig: (key: string, value: any) => ws.updateConfig(key, value), + status: ws.status, + addHandler: (h: MessageHandler) => ws.addHandler(h), + removeHandler: (h: MessageHandler) => ws.removeHandler(h), + } +} diff --git a/apps/web/src/shared/extensions/autocompletion.ts b/apps/web/src/shared/extensions/autocompletion.ts new file mode 100644 index 0000000..ba77d68 --- /dev/null +++ b/apps/web/src/shared/extensions/autocompletion.ts @@ -0,0 +1,107 @@ +import type { + Completion, + CompletionContext, + CompletionResult, + CompletionSource, +} from "@codemirror/autocomplete" +import type { EditorView } from "@codemirror/view" +import { useProblemStore } from "oj/store/problem" +import type { LANGUAGE } from "utils/types" +import { c } from "./c" +import { python } from "./python" +import { sql } from "./sql" + +type ChineseCompletion = Pick< + Completion, + "label" | "detail" | "type" | "info" | "boost" | "apply" +> & { apply?: string | Completion["apply"] } + +// 中文注释提示 +const chineseAnnotations: Record = { + python, + c, + sql, +} + +// SQL 题:当前题目的表名和字段名补全,数据来自 sql_display +function sqlSchemaCompletions(): Completion[] { + const tables = useProblemStore().problem?.sql_display?.tables ?? [] + return tables.flatMap((table) => [ + { + label: table.name, + detail: "数据表", + type: "class", + info: `字段:${table.columns + .map((col) => (col.type ? `${col.name} ${col.type}` : col.name)) + .join(", ")}`, + boost: 110, + }, + ...table.columns.map((col) => ({ + label: col.name, + detail: col.type + ? `${table.name} 的字段 · ${col.type}` + : `${table.name} 的字段`, + type: "property", + boost: 105, + })), + ]) +} + +export function enhanceCompletion(language: LANGUAGE): CompletionSource { + return async function ( + context: CompletionContext, + ): Promise { + const word = context.matchBefore(/\w+/) + if (!word && !context.explicit) return null + + const trulyLanguage = + language === "SQL" + ? "sql" + : language.startsWith("Python") + ? "python" + : "c" + const completions: Completion[] = ( + chineseAnnotations[trulyLanguage] || [] + ).map((completion) => { + const insertText = + typeof completion.apply === "string" + ? completion.apply + : completion.label + const cursorOffset = insertText.includes("(") + ? insertText.indexOf("(") + 1 + : insertText.length + + if ( + (completion.type === "function" || completion.type === "method") && + insertText.includes(")") + ) { + return { + ...completion, + apply: ( + view: EditorView, + _c: Completion, + from: number, + to: number, + ) => { + view.dispatch({ + changes: { from, to, insert: insertText }, + selection: { anchor: from + cursorOffset }, + }) + }, + } + } + + return completion + }) + + if (trulyLanguage === "sql") { + completions.push(...sqlSchemaCompletions()) + } + + return { + from: word ? word.from : context.pos, + options: completions, + validFor: /^\w+$/, + } + } +} diff --git a/apps/web/src/shared/extensions/baseTheme.ts b/apps/web/src/shared/extensions/baseTheme.ts new file mode 100644 index 0000000..f663018 --- /dev/null +++ b/apps/web/src/shared/extensions/baseTheme.ts @@ -0,0 +1,9 @@ +import { EditorView } from "@codemirror/view" + +export const styleTheme = EditorView.baseTheme({ + "& .cm-scroller": { "font-family": "Monaco" }, + "&.cm-editor.cm-focused": { outline: "none" }, + "&.cm-editor .cm-tooltip.cm-tooltip-autocomplete ul": { + "font-family": "Monaco", + }, +}) diff --git a/apps/web/src/shared/extensions/c.ts b/apps/web/src/shared/extensions/c.ts new file mode 100644 index 0000000..3b1236b --- /dev/null +++ b/apps/web/src/shared/extensions/c.ts @@ -0,0 +1,257 @@ +export const c = [ + { + label: "printf", + detail: "格式化输出", + type: "function", + info: "标准输出函数,按格式打印,搭配 %d/%s 等占位符", + boost: 90, + apply: "printf();", + }, + { + label: "scanf", + detail: "格式化输入", + type: "function", + info: "标准输入函数,按格式读取,用 & 取地址接收变量", + boost: 88, + apply: "scanf();", + }, + { + label: "puts", + detail: "输出字符串", + type: "function", + info: "输出以 \\0 结尾的字符串并自动换行,比 printf 简洁", + boost: 84, + apply: "puts();", + }, + { + label: "gets", + detail: "读取字符串", + type: "function", + info: "读取一行字符串到缓冲区(不安全,建议使用 fgets)", + boost: 60, + apply: "gets();", + }, + { + label: "fgets", + detail: "安全读行", + type: "function", + info: "从文件流读取一行到缓冲区,限制长度,避免溢出", + boost: 82, + apply: "fgets();", + }, + { + label: "memset", + detail: "内存填充", + type: "function", + info: "按字节把内存填为指定值,常用于初始化数组或结构体", + boost: 80, + apply: "memset();", + }, + { + label: "memcpy", + detail: "内存拷贝", + type: "function", + info: "从源地址复制指定字节到目标地址,避免重叠", + boost: 78, + apply: "memcpy();", + }, + { + label: "strlen", + detail: "字符串长度", + type: "function", + info: "计算以 \\0 结尾的字符串长度(不含终止符)", + boost: 76, + apply: "strlen();", + }, + { + label: "strcmp", + detail: "字符串比较", + type: "function", + info: "按字典序比较两个字符串,相等为 0,小于返回负数", + boost: 74, + apply: "strcmp();", + }, + { + label: "strcpy", + detail: "字符串拷贝", + type: "function", + info: "把源字符串复制到目标(含终止符),目标要有足够空间", + boost: 72, + apply: "strcpy();", + }, + { + label: "int main", + detail: "程序入口", + type: "keyword", + info: "C 程序入口,通常返回 0 表示正常退出", + boost: 70, + }, + { + label: "for", + detail: "循环语句", + type: "keyword", + info: "for (init; condition; step) 结构,用于固定次数循环", + boost: 68, + }, + { + label: "while", + detail: "条件循环", + type: "keyword", + info: "while (condition) 条件循环,条件真则执行", + boost: 66, + }, + { + label: "if", + detail: "条件判断", + type: "keyword", + info: "if (condition) 条件分支,可配 else/else if", + boost: 64, + }, + { + label: "struct", + detail: "结构体定义", + type: "keyword", + info: "定义结构体,可组合不同类型的成员", + boost: 62, + }, + { + label: "typedef", + detail: "类型别名", + type: "keyword", + info: "为已有类型起别名,提升可读性", + boost: 60, + }, + { + label: "const", + detail: "只读限定", + type: "keyword", + info: "声明常量或只读指针,防止被修改", + boost: 58, + }, + { + label: "return", + detail: "返回值", + type: "keyword", + info: "结束函数并返回值,main 返回 0 表示成功", + boost: 56, + }, + { + label: "break", + detail: "终止循环", + type: "keyword", + info: "立即退出最近的 for/while/do 循环体", + boost: 54, + }, + { + label: "continue", + detail: "继续下一次循环", + type: "keyword", + info: "跳过当前循环余下语句,开始下一轮迭代", + boost: 52, + }, + { + label: "do", + detail: "do-while 循环", + type: "keyword", + info: "do { ... } while(condition); 后置条件循环,至少执行一次", + boost: 50, + }, + { + label: "else", + detail: "分支兜底", + type: "keyword", + info: "if/else if/else 结构中的兜底分支", + boost: 48, + }, + { + label: "switch", + detail: "多分支选择", + type: "keyword", + info: "switch (expr) { case ... } 多分支选择结构,常配合 break", + boost: 46, + }, + { + label: "case", + detail: "分支标签", + type: "keyword", + info: "switch 中的具体匹配分支,常在末尾使用 break", + boost: 44, + }, + { + label: "default", + detail: "默认分支", + type: "keyword", + info: "switch 中未匹配任何 case 时执行的分支", + boost: 42, + }, + { + label: "goto", + detail: "无条件跳转", + type: "keyword", + info: "跳转到指定标签位置,需谨慎使用以避免可读性问题", + boost: 40, + }, + { + label: "sizeof", + detail: "求字节大小", + type: "keyword", + info: "返回类型或表达式所占字节数,编译期求值", + boost: 38, + }, + { + label: "int", + detail: "整数类型", + type: "keyword", + info: "声明整型变量或返回值,通常占 4 字节", + boost: 36, + }, + { + label: "char", + detail: "字符类型", + type: "keyword", + info: "声明字符或字节型变量,通常占 1 字节", + boost: 34, + }, + { + label: "double", + detail: "双精度类型", + type: "keyword", + info: "声明双精度浮点变量,通常占 8 字节", + boost: 32, + }, + { + label: "float", + detail: "单精度类型", + type: "keyword", + info: "声明单精度浮点变量,通常占 4 字节", + boost: 30, + }, + { + label: "void", + detail: "空类型", + type: "keyword", + info: "表示无返回值或无类型指针(void*)", + boost: 28, + }, + { + label: "unsigned", + detail: "无符号修饰", + type: "keyword", + info: "与整型/字符型组合表示无符号数,扩大正数范围", + boost: 26, + }, + { + label: "long", + detail: "长整型修饰", + type: "keyword", + info: "与整型组合表示更大范围(long/long long)", + boost: 24, + }, + { + label: "short", + detail: "短整型修饰", + type: "keyword", + info: "与整型组合表示较小范围的整数类型", + boost: 22, + }, +] diff --git a/apps/web/src/shared/extensions/python.ts b/apps/web/src/shared/extensions/python.ts new file mode 100644 index 0000000..e93182d --- /dev/null +++ b/apps/web/src/shared/extensions/python.ts @@ -0,0 +1,608 @@ +export const python = [ + { + label: "print", + detail: "打印输出", + type: "function", + info: "最常用的输出函数,把内容打印到屏幕;sep 控制分隔符,end 控制行尾。", + boost: 100, + apply: "print()", + }, + { + label: "input", + detail: "读取输入", + type: "function", + info: "读取一行输入并返回字符串,可以传入提示文字。", + boost: 99, + apply: "input()", + }, + { + label: "len", + detail: "获取长度", + type: "function", + info: "返回序列或集合的长度,常用来统计列表、字符串中有多少个元素。", + boost: 90, + apply: "len()", + }, + { + label: "range", + detail: "生成整数序列", + type: "function", + info: "生成整数序列,支持起点、终点和步长,常配合 for 循环遍历次数。", + boost: 85, + apply: "range()", + }, + { + label: "enumerate", + detail: "枚举索引与值", + type: "function", + info: "遍历时同时得到索引和值,可用 start 指定起始编号,适合需要编号输出的场景。", + boost: 82, + apply: "enumerate()", + }, + { + label: "zip", + detail: "并行遍历", + type: "function", + info: "把多个可迭代对象按位置打包成元组并行遍历,长度取最短的序列。", + boost: 80, + apply: "zip()", + }, + { + label: "map", + detail: "映射函数", + type: "function", + info: "把函数作用到序列每个元素,返回惰性迭代器,需要 list() 展开后才能看到结果。", + boost: 78, + apply: "map()", + }, + { + label: "filter", + detail: "过滤元素", + type: "function", + info: "保留函数返回真值的元素,返回惰性迭代器,常用于筛选出符合条件的数据。", + boost: 76, + apply: "filter()", + }, + { + label: "sorted", + detail: "排序", + type: "function", + info: "返回排好序的新列表,支持 key 排序函数和 reverse 逆序,不会修改原序列。", + boost: 74, + apply: "sorted()", + }, + { + label: "sum", + detail: "求和", + type: "function", + info: "对可迭代对象求和,可设置初始值,常用于数字累计与前缀和。", + boost: 72, + apply: "sum()", + }, + { + label: "open", + detail: "文件读写", + type: "function", + info: "打开文件获得文件对象,常配合 with 自动关闭,支持读写追加等多种模式。", + boost: 70, + apply: "open()", + }, + { + label: "abs", + detail: "绝对值", + type: "function", + info: "返回数字的绝对值,把负数变成正数,常用于距离或差值计算。", + boost: 68, + apply: "abs()", + }, + { + label: "round", + detail: "四舍五入", + type: "function", + info: "按指定小数位四舍五入,默认保留到整数,适合处理成绩或金额保留位数。", + boost: 66, + apply: "round()", + }, + { + label: "isinstance", + detail: "类型检查", + type: "function", + info: "判断对象是否属于某个类型或类型元组,常用于分支处理不同数据。", + boost: 64, + apply: "isinstance()", + }, + { + label: "type", + detail: "获取类型", + type: "function", + info: "返回对象的类型,或用三个参数动态创建类型,用来了解变量真实类型。", + boost: 62, + apply: "type()", + }, + { + label: "list", + detail: "列表构造", + type: "function", + info: "把可迭代对象转换为列表,或创建空列表存放数据,支持列表推导式。", + boost: 60, + apply: "list()", + }, + { + label: "dict", + detail: "字典构造", + type: "function", + info: "根据映射或键值对序列创建字典,用来存储键值对信息。", + boost: 58, + apply: "dict()", + }, + { + label: "set", + detail: "集合构造", + type: "function", + info: "把可迭代对象转换为集合,自动去重,适合判重和集合运算。", + boost: 56, + apply: "set()", + }, + { + label: "tuple", + detail: "元组构造", + type: "function", + info: "把可迭代对象转换为元组,或创建不可变的序列,用作安全的组合数据。", + boost: 54, + apply: "tuple()", + }, + { + label: "int", + detail: "转整数", + type: "function", + info: "把参数转为整数,支持进制转换,如 int('101', 2) 表示二进制转十进制。", + boost: 74, + apply: "int()", + }, + { + label: "float", + detail: "转浮点数", + type: "function", + info: "把参数转为浮点数,接受字符串或数字,常用于保留小数的计算。", + boost: 72, + apply: "float()", + }, + { + label: "str", + detail: "转字符串", + type: "function", + info: "把对象转为字符串,常用于输出、拼接或写入文件。", + boost: 70, + apply: "str()", + }, + { + label: "bool", + detail: "转布尔值", + type: "function", + info: "按真值规则转为 True/False,空对象一般为 False,常用于条件判断。", + boost: 68, + apply: "bool()", + }, + { + label: "def", + detail: "定义函数", + type: "keyword", + info: "定义函数,可写位置参数、关键字参数和默认值,是封装和复用代码的基础。", + boost: 52, + }, + { + label: "class", + detail: "定义类", + type: "keyword", + info: "定义类,支持继承和魔术方法,用来创建自定义数据类型。", + boost: 50, + }, + { + label: "with", + detail: "上下文管理", + type: "keyword", + info: "进入上下文管理器,自动处理进入与退出,常用于文件、锁等需要收尾的资源。", + boost: 48, + }, + { + label: "try", + detail: "异常捕获", + type: "keyword", + info: "开始异常处理代码块,后面接 except/finally/else,防止程序因错误直接退出。", + boost: 46, + }, + { + label: "except", + detail: "处理异常", + type: "keyword", + info: "捕获并处理指定异常,与 try 连用,让程序能优雅处理输入或运行时错误。", + boost: 44, + }, + { + label: "finally", + detail: "收尾", + type: "keyword", + info: "无论是否出现异常都会执行的收尾代码块,常用于关闭文件或释放资源。", + boost: 42, + }, + { + label: "import", + detail: "导入模块", + type: "keyword", + info: "导入模块或包中的名称,可用 as 取别名,便于使用标准库或第三方库。", + boost: 40, + }, + { + label: "from", + detail: "按需导入", + type: "keyword", + info: "从模块中按名称导入对象,可与 import/as 组合,减少书写模块前缀。", + boost: 38, + }, + { + label: "return", + detail: "返回值", + type: "keyword", + info: "结束函数并返回一个值,不写时默认返回 None。", + boost: 36, + }, + { + label: "for", + detail: "for 循环", + type: "keyword", + info: "逐项遍历,可以配合 range 使用。", + boost: 45, + }, + { + label: "while", + detail: "while 循环", + type: "keyword", + info: "条件循环,条件为真时执行,支持 break/continue 和 else 分支。", + boost: 43, + }, + { + label: "if", + detail: "条件分支", + type: "keyword", + info: "if 判断,条件成立执行对应语句,可与 elif/else 组成多分支。", + boost: 41, + apply: "if ", + }, + { + label: "elif", + detail: "多分支判断", + type: "keyword", + info: "否则如果,用来依次判断多个条件,让逻辑更清晰。", + boost: 39, + apply: "elif ", + }, + { + label: "else", + detail: "否则", + type: "keyword", + info: "否则,在前面条件不满足或循环未提前退出时执行。", + boost: 37, + apply: "else:", + }, + { + label: "break", + detail: "退出循环", + type: "keyword", + info: "立即终止最近的循环(for 或 while),跳出当前循环体。", + boost: 35, + }, + { + label: "continue", + detail: "跳过本次", + type: "keyword", + info: "结束本轮循环迭代,直接开始下一轮条件判断。", + boost: 33, + }, + { + label: "pass", + detail: "空操作占位", + type: "keyword", + info: "占位语句,不执行任何操作,常用于占位或编写空的函数/类体。", + boost: 31, + }, + { + label: "lambda", + detail: "匿名函数", + type: "keyword", + info: "定义轻量级匿名函数,语法 lambda 参数: 表达式,返回表达式结果,适合简短逻辑。", + boost: 29, + }, + { + label: "yield", + detail: "生成器产出", + type: "keyword", + info: "在函数中产出一个值并暂停状态,把函数变成生成器,可配合 yield from 继续产出。", + boost: 27, + }, + { + label: "global", + detail: "声明全局变量", + type: "keyword", + info: "在函数内部声明写入模块级变量的权限,用于修改外层的全局变量。", + boost: 25, + }, + { + label: "nonlocal", + detail: "声明外层变量", + type: "keyword", + info: "在嵌套函数中声明使用最近外层作用域的变量,便于修改闭包变量。", + boost: 23, + }, + { + label: "True", + detail: "布尔真", + type: "keyword", + info: "布尔常量 True,与 False/None 一起常用于条件表达式和逻辑判断。", + boost: 21, + }, + { + label: "False", + detail: "布尔假", + type: "keyword", + info: "布尔常量 False,在逻辑判断中表示假值,常作为条件的否定结果。", + boost: 19, + }, + { + label: "None", + detail: "空值对象", + type: "keyword", + info: "表示空值或缺失值的单例对象,常用于默认参数或占位。", + boost: 17, + }, + { + label: "and", + detail: "逻辑与", + type: "keyword", + info: "逻辑与运算符,短路求值,返回最后被求值的操作数,常用于多条件同时成立。", + boost: 15, + }, + { + label: "or", + detail: "逻辑或", + type: "keyword", + info: "逻辑或运算符,短路求值,返回第一个真值或最后一个操作数,常用于设默认值。", + boost: 14, + }, + { + label: "not", + detail: "逻辑非", + type: "keyword", + info: "逻辑非运算符,返回布尔取反结果,把真变假、假变真。", + boost: 13, + }, + { + label: "in", + detail: "成员测试", + type: "keyword", + info: "判断元素是否在序列、集合或字典中,返回布尔结果,常用于查找。", + boost: 12, + }, + { + label: "is", + detail: "身份比较", + type: "keyword", + info: "比较两个对象是否是同一个对象,常用于和 None 比较以避免误判。", + boost: 11, + }, + { + label: "append", + detail: "列表追加", + type: "method", + info: "在列表尾部添加一个新元素,等价于 list.append(value)。", + boost: 48, + apply: "append()", + }, + { + label: "insert", + detail: "列表插入", + type: "method", + info: "在指定位置插入元素,语法 list.insert(index, value)。", + boost: 46, + apply: "insert()", + }, + { + label: "remove", + detail: "删除匹配值", + type: "method", + info: "删除列表中第一次出现的指定值,不存在会抛出异常 ValueError。", + boost: 44, + apply: "remove()", + }, + { + label: "pop", + detail: "弹出元素", + type: "method", + info: "移除并返回列表指定位置(默认尾部)的元素,用于栈或队列操作。", + boost: 42, + apply: "pop()", + }, + { + label: "count", + detail: "统计次数", + type: "method", + info: "返回某个对象在列表中出现的次数,常用于频次统计。", + boost: 40, + apply: "count()", + }, + { + label: "reverse", + detail: "反转列表", + type: "method", + info: "原地反转列表中元素的顺序,常与切片 [::-1] 效果类似。", + boost: 38, + apply: "reverse()", + }, + { + label: "sort", + detail: "列表排序", + type: "method", + info: "对列表进行原地排序,可指定 key 排序函数和 reverse 是否倒序。", + boost: 36, + apply: "sort()", + }, + { + label: "add", + detail: "集合添加", + type: "method", + info: "向集合添加单个元素,若元素已存在则忽略,保持集合去重特性。", + boost: 50, + apply: "add()", + }, + { + label: "clear", + detail: "清空集合", + type: "method", + info: "移除集合中所有元素,变成一个空集合。", + boost: 48, + apply: "clear()", + }, + { + label: "keys", + detail: "字典键", + type: "method", + info: "返回字典键的可迭代视图,用于遍历所有键。", + boost: 42, + apply: "keys()", + }, + { + label: "values", + detail: "字典值", + type: "method", + info: "返回字典值的可迭代视图,用于遍历所有值。", + boost: 40, + apply: "values()", + }, + { + label: "split", + detail: "字符串切分", + type: "method", + info: "按分隔符切分字符串,返回列表,默认按空白字符分割。", + boost: 52, + apply: "split()", + }, + { + label: "replace", + detail: "字符串替换", + type: "method", + info: "把字符串中的子串替换为新内容,可限制替换次数。", + boost: 50, + apply: "replace()", + }, + { + label: "format", + detail: "格式化字符串", + type: "method", + info: "使用占位符或命名参数进行字符串格式化,便于拼接变量输出。", + boost: 48, + apply: "format()", + }, + { + label: "strip", + detail: "去首尾字符", + type: "method", + info: "移除字符串首尾指定字符,默认移除空白符,常用于清理输入。", + boost: 46, + apply: "strip()", + }, + { + label: "lower", + detail: "转小写", + type: "method", + info: "把字符串中的字母转换为小写形式,常用于不区分大小写比较。", + boost: 44, + apply: "lower()", + }, + { + label: "upper", + detail: "转大写", + type: "method", + info: "把字符串中的字母转换为大写形式。", + boost: 42, + apply: "upper()", + }, + { + label: "swapcase", + detail: "大小写互换", + type: "method", + info: "把字符串中的大小写字母互换,便于切换展示风格。", + boost: 40, + apply: "swapcase()", + }, + { + label: "find", + detail: "查找子串位置", + type: "method", + info: "返回子串首次出现的索引,未找到返回 -1,适合安全查找。", + boost: 38, + apply: "find()", + }, + { + label: "index", + detail: "查找子串索引", + type: "method", + info: "返回子串首次出现的索引,未找到会抛出异常 ValueError。", + boost: 36, + apply: "index()", + }, + { + label: "startswith", + detail: "前缀判断", + type: "method", + info: "判断字符串是否以指定前缀开头,可指定检查的范围切片。", + boost: 34, + apply: "startswith()", + }, + { + label: "endswith", + detail: "后缀判断", + type: "method", + info: "判断字符串是否以指定后缀结尾,可指定范围,常用于文件名处理。", + boost: 32, + apply: "endswith()", + }, + { + label: "isalnum", + detail: "是否字母数字", + type: "method", + info: "检测字符串是否只由字母和数字组成,常用于基础输入校验。", + boost: 30, + apply: "isalnum()", + }, + { + label: "isalpha", + detail: "是否字母", + type: "method", + info: "检测字符串是否只由字母组成,用来判断名字等只含字母的场景。", + boost: 28, + apply: "isalpha()", + }, + { + label: "isdigit", + detail: "是否数字", + type: "method", + info: "检测字符串是否只由数字组成,用来判断输入是否为纯数字。", + boost: 26, + apply: "isdigit()", + }, + { + label: "islower", + detail: "是否全小写", + type: "method", + info: "检测字符串是否全部由小写字母组成且至少有一个字母。", + boost: 24, + apply: "islower()", + }, + { + label: "isupper", + detail: "是否全大写", + type: "method", + info: "检测字符串中所有字母是否都是大写且至少有一个字母。", + boost: 22, + apply: "isupper()", + }, +] diff --git a/apps/web/src/shared/extensions/sql.ts b/apps/web/src/shared/extensions/sql.ts new file mode 100644 index 0000000..dfc79ac --- /dev/null +++ b/apps/web/src/shared/extensions/sql.ts @@ -0,0 +1,404 @@ +export const sql = [ + { + label: "SELECT", + detail: "查询数据", + type: "keyword", + info: "从表中查询数据,后面接要查询的列名,用 * 表示所有列。", + boost: 100, + apply: "SELECT ", + }, + { + label: "FROM", + detail: "指定表", + type: "keyword", + info: "指定要查询的表,和 SELECT 搭配使用,如 SELECT * FROM 表名。", + boost: 98, + apply: "FROM ", + }, + { + label: "WHERE", + detail: "筛选条件", + type: "keyword", + info: "按条件筛选行,只保留满足条件的数据,如 WHERE age > 18。", + boost: 96, + apply: "WHERE ", + }, + { + label: "ORDER BY", + detail: "排序", + type: "keyword", + info: "按指定列排序,默认从小到大(ASC),加 DESC 表示从大到小。", + boost: 90, + apply: "ORDER BY ", + }, + { + label: "GROUP BY", + detail: "分组", + type: "keyword", + info: "按指定列分组,常配合 COUNT、SUM 等聚合函数统计每组数据。", + boost: 88, + apply: "GROUP BY ", + }, + { + label: "HAVING", + detail: "分组后筛选", + type: "keyword", + info: "对分组后的结果再筛选,WHERE 筛选行,HAVING 筛选组。", + boost: 86, + apply: "HAVING ", + }, + { + label: "LIMIT", + detail: "限制条数", + type: "keyword", + info: "限制返回的行数,如 LIMIT 5 只取前 5 条,常和 ORDER BY 搭配。", + boost: 84, + apply: "LIMIT ", + }, + { + label: "DISTINCT", + detail: "去重", + type: "keyword", + info: "去掉查询结果中的重复值,如 SELECT DISTINCT city FROM users。", + boost: 82, + apply: "DISTINCT ", + }, + { + label: "AS", + detail: "起别名", + type: "keyword", + info: "给列或表起别名,让结果更易读,如 SELECT name AS 姓名。", + boost: 80, + apply: "AS ", + }, + { + label: "JOIN", + detail: "连接表", + type: "keyword", + info: "把两张表按条件连接起来查询,需要用 ON 指定连接条件。", + boost: 78, + apply: "JOIN ", + }, + { + label: "LEFT JOIN", + detail: "左连接", + type: "keyword", + info: "以左表为主连接右表,左表的行都保留,右表没匹配的补 NULL。", + boost: 76, + apply: "LEFT JOIN ", + }, + { + label: "ON", + detail: "连接条件", + type: "keyword", + info: "指定两张表的连接条件,如 ON a.id = b.user_id,和 JOIN 搭配。", + boost: 74, + apply: "ON ", + }, + { + label: "INSERT INTO", + detail: "插入数据", + type: "keyword", + info: "向表中插入新行,如 INSERT INTO 表名 (列1, 列2) VALUES (值1, 值2)。", + boost: 72, + apply: "INSERT INTO ", + }, + { + label: "VALUES", + detail: "插入的值", + type: "keyword", + info: "和 INSERT INTO 搭配,写具体要插入的值,顺序要和列名对应。", + boost: 70, + apply: "VALUES ", + }, + { + label: "UPDATE", + detail: "更新数据", + type: "keyword", + info: "修改表中已有的数据,配合 SET 设置新值,别忘了加 WHERE 限定范围。", + boost: 68, + apply: "UPDATE ", + }, + { + label: "SET", + detail: "设置新值", + type: "keyword", + info: "和 UPDATE 搭配,指定要修改的列和新值,如 SET score = 90。", + boost: 66, + apply: "SET ", + }, + { + label: "DELETE FROM", + detail: "删除数据", + type: "keyword", + info: "删除表中的行,一定要配合 WHERE 使用,否则会删掉整张表的数据。", + boost: 64, + apply: "DELETE FROM ", + }, + { + label: "CREATE TABLE", + detail: "创建表", + type: "keyword", + info: "新建一张表,需要定义列名和类型,如 CREATE TABLE users (id INTEGER, name TEXT)。", + boost: 62, + apply: "CREATE TABLE ", + }, + { + label: "DROP TABLE", + detail: "删除表", + type: "keyword", + info: "删除整张表(包括结构和数据),操作不可恢复,要谨慎使用。", + boost: 60, + apply: "DROP TABLE ", + }, + { + label: "ALTER TABLE", + detail: "修改表结构", + type: "keyword", + info: "修改已有表的结构,比如添加列:ALTER TABLE 表名 ADD COLUMN 列名 类型。", + boost: 58, + apply: "ALTER TABLE ", + }, + { + label: "AND", + detail: "并且", + type: "keyword", + info: "连接多个条件,全部成立才算满足,如 WHERE age > 18 AND city = '上海'。", + boost: 56, + apply: "AND ", + }, + { + label: "OR", + detail: "或者", + type: "keyword", + info: "连接多个条件,任意一个成立就算满足。", + boost: 54, + apply: "OR ", + }, + { + label: "NOT", + detail: "取反", + type: "keyword", + info: "对条件取反,如 NOT IN、NOT LIKE、IS NOT NULL。", + boost: 52, + apply: "NOT ", + }, + { + label: "IN", + detail: "在列表中", + type: "keyword", + info: "判断值是否在给定列表中,如 WHERE city IN ('北京', '上海')。", + boost: 50, + apply: "IN ", + }, + { + label: "BETWEEN", + detail: "在区间内", + type: "keyword", + info: "判断值是否在某个范围内(包含两端),如 BETWEEN 60 AND 100。", + boost: 48, + apply: "BETWEEN ", + }, + { + label: "LIKE", + detail: "模糊匹配", + type: "keyword", + info: "模糊查询,% 匹配任意多个字符,_ 匹配单个字符,如 LIKE '张%'。", + boost: 46, + apply: "LIKE ", + }, + { + label: "IS NULL", + detail: "是否为空", + type: "keyword", + info: "判断值是否为 NULL(空值),不能写 = NULL,要用 IS NULL。", + boost: 44, + }, + { + label: "IS NOT NULL", + detail: "是否非空", + type: "keyword", + info: "判断值不为 NULL,常用于过滤掉缺失数据的行。", + boost: 42, + }, + { + label: "ASC", + detail: "升序", + type: "keyword", + info: "排序时从小到大排列,是 ORDER BY 的默认方式,可以省略。", + boost: 40, + }, + { + label: "DESC", + detail: "降序", + type: "keyword", + info: "排序时从大到小排列,如 ORDER BY score DESC 按分数从高到低。", + boost: 41, + }, + { + label: "UNION", + detail: "合并结果", + type: "keyword", + info: "合并两个查询的结果并去重,两个查询的列数和类型要一致。", + boost: 38, + apply: "UNION ", + }, + { + label: "CASE", + detail: "条件表达式", + type: "keyword", + info: "类似 if/else 的条件判断,语法 CASE WHEN 条件 THEN 值 ELSE 值 END。", + boost: 36, + apply: "CASE ", + }, + { + label: "WHEN", + detail: "当条件成立", + type: "keyword", + info: "和 CASE 搭配,写判断条件,成立时返回 THEN 后面的值。", + boost: 34, + apply: "WHEN ", + }, + { + label: "THEN", + detail: "返回值", + type: "keyword", + info: "和 WHEN 搭配,条件成立时返回的结果。", + boost: 32, + apply: "THEN ", + }, + { + label: "ELSE", + detail: "否则", + type: "keyword", + info: "CASE 中所有 WHEN 都不成立时返回的默认值。", + boost: 30, + apply: "ELSE ", + }, + { + label: "END", + detail: "结束 CASE", + type: "keyword", + info: "标记 CASE 表达式的结束,写 CASE 时不要漏掉。", + boost: 28, + }, + { + label: "NULL", + detail: "空值", + type: "keyword", + info: "表示没有值(缺失),判断时要用 IS NULL / IS NOT NULL。", + boost: 26, + }, + { + label: "COUNT", + detail: "统计行数", + type: "function", + info: "统计行数,COUNT(*) 统计所有行,COUNT(列名) 不统计 NULL。", + boost: 92, + apply: "COUNT()", + }, + { + label: "SUM", + detail: "求和", + type: "function", + info: "对某一列求和,只能用于数字列,如 SUM(score)。", + boost: 87, + apply: "SUM()", + }, + { + label: "AVG", + detail: "平均值", + type: "function", + info: "计算某一列的平均值,会自动忽略 NULL,如 AVG(score)。", + boost: 85, + apply: "AVG()", + }, + { + label: "MAX", + detail: "最大值", + type: "function", + info: "求某一列的最大值,如 MAX(score) 找最高分。", + boost: 83, + apply: "MAX()", + }, + { + label: "MIN", + detail: "最小值", + type: "function", + info: "求某一列的最小值,如 MIN(score) 找最低分。", + boost: 81, + apply: "MIN()", + }, + { + label: "LENGTH", + detail: "字符串长度", + type: "function", + info: "返回字符串的字符个数,如 LENGTH(name)。", + boost: 79, + apply: "LENGTH()", + }, + { + label: "UPPER", + detail: "转大写", + type: "function", + info: "把字符串中的字母转成大写,如 UPPER('abc') 得到 'ABC'。", + boost: 77, + apply: "UPPER()", + }, + { + label: "LOWER", + detail: "转小写", + type: "function", + info: "把字符串中的字母转成小写,如 LOWER('ABC') 得到 'abc'。", + boost: 75, + apply: "LOWER()", + }, + { + label: "SUBSTR", + detail: "截取子串", + type: "function", + info: "截取字符串的一部分,语法 SUBSTR(字符串, 起始位置, 长度),位置从 1 开始。", + boost: 73, + apply: "SUBSTR()", + }, + { + label: "REPLACE", + detail: "替换字符串", + type: "function", + info: "把字符串中的内容替换成新内容,语法 REPLACE(字符串, 旧内容, 新内容)。", + boost: 71, + apply: "REPLACE()", + }, + { + label: "ROUND", + detail: "四舍五入", + type: "function", + info: "按指定小数位四舍五入,如 ROUND(3.456, 2) 得到 3.46。", + boost: 69, + apply: "ROUND()", + }, + { + label: "ABS", + detail: "绝对值", + type: "function", + info: "返回数字的绝对值,把负数变成正数,如 ABS(-5) 得到 5。", + boost: 67, + apply: "ABS()", + }, + { + label: "IFNULL", + detail: "空值替代", + type: "function", + info: "如果第一个值是 NULL 就返回第二个值,如 IFNULL(score, 0) 把空分数当 0。", + boost: 65, + apply: "IFNULL()", + }, + { + label: "TRIM", + detail: "去首尾空格", + type: "function", + info: "去掉字符串首尾的空格,常用于清理输入数据。", + boost: 63, + apply: "TRIM()", + }, +] diff --git a/apps/web/src/shared/layout/admin.vue b/apps/web/src/shared/layout/admin.vue new file mode 100644 index 0000000..e83042b --- /dev/null +++ b/apps/web/src/shared/layout/admin.vue @@ -0,0 +1,204 @@ + + + diff --git a/apps/web/src/shared/layout/default.vue b/apps/web/src/shared/layout/default.vue new file mode 100644 index 0000000..7a01f47 --- /dev/null +++ b/apps/web/src/shared/layout/default.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/apps/web/src/shared/store/achievement.ts b/apps/web/src/shared/store/achievement.ts new file mode 100644 index 0000000..75086d5 --- /dev/null +++ b/apps/web/src/shared/store/achievement.ts @@ -0,0 +1,64 @@ +import { + getPendingAchievements, + markAchievementsRead, +} from "oj/achievement/api" +import type { PendingAchievement } from "utils/types" + +/** + * 成就解锁弹窗队列。 + * + * 通知走推拉结合,后端的 UserAchievement.notified 是唯一真相来源: + * - 拉(主):布局层每次路由切换拉一次 pending,覆盖全部场景,绝不丢 + * - 推(增强):WebSocket 只在用户当场停留在问题页时把延迟压到几百毫秒 + * + * 之所以不能只靠推:前端 WebSocket 不是常驻连接,只在问题页且有提交监听时 + * 才建连,纯推会丢消息(尤其是题单奖章,那些页面根本没建连接)。 + */ +export const useAchievementStore = defineStore("achievement", () => { + const queue = ref([]) + const current = ref(null) + + // 成就和题单奖章的 id 来自两张不同的表,数值会重叠, + // 只按 id 去重会让奖章 5 把成就 5 挤掉 + function keyOf(item: PendingAchievement) { + return `${item.kind ?? "achievement"}:${item.id}` + } + + function enqueue(items: PendingAchievement[]) { + if (!items?.length) return + // 去重:WebSocket 推来的和 pending 拉来的可能是同一批 + const known = new Set([ + ...queue.value.map(keyOf), + ...(current.value ? [keyOf(current.value)] : []), + ]) + queue.value.push(...items.filter((i) => !known.has(keyOf(i)))) + } + + async function fetchPending() { + try { + // http 客户端返回 ApiResponse,真实载荷在 .data 里 + const res = await getPendingAchievements() + enqueue(res.data ?? []) + } catch { + // 拉取失败静默处理,下次路由切换会再拉 + } + } + + function next() { + current.value = queue.value.shift() ?? null + return current.value + } + + async function markRead(item: PendingAchievement) { + // 奖章不在 UserAchievement 表里,它的 id 传给标记接口会被当成成就 id, + // 把一个恰好同号、还没弹过的成就静默标记为已弹——那个奖杯就再也不会出现 + if (item.kind === "badge") return + try { + await markAchievementsRead([item.id]) + } catch { + // 标记失败下次会重复弹一次,可接受 + } + } + + return { queue, current, enqueue, fetchPending, next, markRead } +}) diff --git a/apps/web/src/shared/store/authModal.ts b/apps/web/src/shared/store/authModal.ts new file mode 100644 index 0000000..5a8f42b --- /dev/null +++ b/apps/web/src/shared/store/authModal.ts @@ -0,0 +1,155 @@ +import { defineStore } from "pinia" + +/** + * 认证状态管理 Store + * 统一管理登录、注册相关的模态框状态和表单状态 + */ +export const useAuthModalStore = defineStore("authModal", () => { + // ==================== 模态框状态 ==================== + const loginModalOpen = ref(false) + const signupModalOpen = ref(false) + + // ==================== 登录表单状态 ==================== + const loginForm = reactive({ + class: "", + username: "", + password: "", + }) + + const loginLoading = ref(false) + const loginError = ref("") + + // ==================== 注册表单状态 ==================== + const signupForm = reactive({ + username: "", + email: "", + password: "", + passwordAgain: "", + }) + + const signupLoading = ref(false) + const signupError = ref("") + + // ==================== 模态框操作 ==================== + /** + * 打开登录模态框 + */ + function openLoginModal() { + loginModalOpen.value = true + } + + /** + * 关闭登录模态框 + */ + function closeLoginModal() { + loginModalOpen.value = false + } + + /** + * 打开注册模态框 + */ + function openSignupModal() { + signupModalOpen.value = true + } + + /** + * 关闭注册模态框 + */ + function closeSignupModal() { + signupModalOpen.value = false + } + + /** + * 从登录切换到注册 + */ + function switchToSignup() { + closeLoginModal() + openSignupModal() + } + + /** + * 从注册切换到登录 + */ + function switchToLogin() { + closeSignupModal() + openLoginModal() + } + + // ==================== 登录表单操作 ==================== + /** + * 设置登录加载状态 + */ + function setLoginLoading(loading: boolean) { + loginLoading.value = loading + } + + /** + * 设置登录错误信息 + */ + function setLoginError(error: string) { + loginError.value = error + } + + /** + * 清空登录错误 + */ + function clearLoginError() { + loginError.value = "" + } + + // ==================== 注册表单操作 ==================== + /** + * 设置注册加载状态 + */ + function setSignupLoading(loading: boolean) { + signupLoading.value = loading + } + + /** + * 设置注册错误信息 + */ + function setSignupError(error: string) { + signupError.value = error + } + + /** + * 清空注册错误 + */ + function clearSignupError() { + signupError.value = "" + } + + return { + // 模态框状态 + loginModalOpen, + signupModalOpen, + + // 登录表单状态 + loginForm, + loginLoading, + loginError, + + // 注册表单状态 + signupForm, + signupLoading, + signupError, + + // 模态框操作 + openLoginModal, + closeLoginModal, + openSignupModal, + closeSignupModal, + switchToSignup, + switchToLogin, + + // 登录表单操作 + setLoginLoading, + setLoginError, + clearLoginError, + + // 注册表单操作 + setSignupLoading, + setSignupError, + clearSignupError, + } +}) diff --git a/apps/web/src/shared/store/config.ts b/apps/web/src/shared/store/config.ts new file mode 100644 index 0000000..b317895 --- /dev/null +++ b/apps/web/src/shared/store/config.ts @@ -0,0 +1,24 @@ +import { getWebsiteConfig } from "oj/api" +import type { WebsiteConfig } from "utils/types" + +export const useConfigStore = defineStore("config", () => { + const config = ref({ + website_base_url: "", + website_name: "", + website_name_shortcut: "", + website_footer: "", + submission_list_show_all: true, + allow_register: false, + class_list: [], + enable_maxkb: true, + }) + async function getConfig() { + const res = await getWebsiteConfig() + config.value = res.data + document.title = res.data.website_name + } + return { + config, + getConfig, + } +}) diff --git a/apps/web/src/shared/store/loginSummary.ts b/apps/web/src/shared/store/loginSummary.ts new file mode 100644 index 0000000..36c1cad --- /dev/null +++ b/apps/web/src/shared/store/loginSummary.ts @@ -0,0 +1,74 @@ +import { getAILoginSummary } from "oj/api" + +interface LoginSummary { + start: string + end: string + new_problem_count: number + submission_count: number + accepted_count: number + solved_count: number + flowchart_submission_count: number +} + +export const useLoginSummaryStore = defineStore("loginSummary", () => { + const show = ref(false) + const loading = ref(false) + const summary = ref(null) + const analysis = ref("") + const analysisError = ref("") + + function shouldShowSummary(nextSummary: LoginSummary | null) { + if (!nextSummary) { + return false + } + const values = [ + nextSummary.new_problem_count, + nextSummary.submission_count, + nextSummary.accepted_count, + nextSummary.solved_count, + nextSummary.flowchart_submission_count, + ] + const zeroCount = values.filter((value) => value === 0).length + return zeroCount < Math.floor(values.length / 2) + 1 + } + + async function fetchSummary() { + loading.value = true + analysis.value = "" + analysisError.value = "" + try { + const res = await getAILoginSummary() + summary.value = res.data.summary + analysis.value = res.data.analysis || "" + analysisError.value = res.data.analysis_error || "" + } catch (err) { + analysisError.value = "获取登录统计失败,请稍后再试" + } finally { + loading.value = false + } + } + + async function open() { + await fetchSummary() + if (!summary.value && analysisError.value) { + show.value = true + return + } + show.value = shouldShowSummary(summary.value) + } + + function close() { + show.value = false + } + + return { + show, + loading, + summary, + analysis, + analysisError, + fetchSummary, + open, + close, + } +}) diff --git a/apps/web/src/shared/store/myFlowchart.ts b/apps/web/src/shared/store/myFlowchart.ts new file mode 100644 index 0000000..411bb31 --- /dev/null +++ b/apps/web/src/shared/store/myFlowchart.ts @@ -0,0 +1,18 @@ +import { defineStore } from "pinia" + +export const useMyFlowchartStore = defineStore("myFlowchart", () => { + const showing = ref(false) + const mermaidCode = ref("") + + function show(code: string) { + mermaidCode.value = code + showing.value = true + } + + function hide() { + showing.value = false + mermaidCode.value = "" + } + + return { showing, mermaidCode, show, hide } +}) diff --git a/apps/web/src/shared/store/screenMode.ts b/apps/web/src/shared/store/screenMode.ts new file mode 100644 index 0000000..29b25c6 --- /dev/null +++ b/apps/web/src/shared/store/screenMode.ts @@ -0,0 +1,34 @@ +import { defineStore } from "pinia" +import { ScreenMode } from "utils/constants" + +export const useScreenModeStore = defineStore("screenMode", () => { + const { state: screenMode, next: switchScreenMode } = useCycleList( + Object.values(ScreenMode), + { + initialValue: ScreenMode.both, + }, + ) + + // 计算属性 + const isBothMode = computed(() => screenMode.value === ScreenMode.both) + const isCodeOnlyMode = computed(() => screenMode.value === ScreenMode.code) + + const shouldShowProblem = computed( + () => + screenMode.value === ScreenMode.both || + screenMode.value === ScreenMode.problem, + ) + + function resetScreenMode() { + screenMode.value = ScreenMode.both + } + + return { + screenMode, + isBothMode, + isCodeOnlyMode, + shouldShowProblem, + switchScreenMode, + resetScreenMode, + } +}) diff --git a/apps/web/src/shared/store/user.ts b/apps/web/src/shared/store/user.ts new file mode 100644 index 0000000..f19c39d --- /dev/null +++ b/apps/web/src/shared/store/user.ts @@ -0,0 +1,94 @@ +import { PROBLEM_PERMISSION, STORAGE_KEY, USER_TYPE } from "utils/constants" +import storage from "utils/storage" +import type { Profile, User } from "utils/types" +import { getProfile } from "../api" +import { useConfigStore } from "./config" + +export const useUserStore = defineStore("user", () => { + const configStore = useConfigStore() + + const profile = ref(null) + const [isFinished] = useToggle(false) + const user = computed(() => profile.value?.user ?? null) + const isAuthed = computed(() => !!user.value?.email) + + // 演示模式:超管临时把界面伪装成普通学生,方便上课投屏 + const demoMode = ref(storage.get(STORAGE_KEY.DEMO_MODE) ?? false) + + // 不受伪装影响的真实身份,只用于判断能否切换演示模式。 + // 若这里用被伪装后的 isSuperAdmin,一进入演示模式入口就消失了,退不出来。 + const realIsSuperAdmin = computed( + () => user.value?.admin_type === USER_TYPE.SUPER_ADMIN, + ) + + const isAdminRole = computed( + () => + !demoMode.value && + (user.value?.admin_type === USER_TYPE.STUDENT_ADMIN || + user.value?.admin_type === USER_TYPE.TEACHER_ADMIN || + user.value?.admin_type === USER_TYPE.SUPER_ADMIN), + ) + const isStudentAdmin = computed( + () => !demoMode.value && user.value?.admin_type === USER_TYPE.STUDENT_ADMIN, + ) + const isTeacherAdmin = computed( + () => !demoMode.value && user.value?.admin_type === USER_TYPE.TEACHER_ADMIN, + ) + const isTeacherOrAbove = computed( + () => + !demoMode.value && + (user.value?.admin_type === USER_TYPE.TEACHER_ADMIN || + user.value?.admin_type === USER_TYPE.SUPER_ADMIN), + ) + const isSuperAdmin = computed(() => !demoMode.value && realIsSuperAdmin.value) + const hasProblemPermission = computed( + () => + !demoMode.value && + user.value?.problem_permission !== PROBLEM_PERMISSION.NONE, + ) + + const canToggleDemoMode = computed(() => realIsSuperAdmin.value) + + function toggleDemoMode() { + demoMode.value = !demoMode.value + storage.set(STORAGE_KEY.DEMO_MODE, demoMode.value) + } + + const showSubmissions = computed(() => { + let flag = configStore.config.submission_list_show_all + if (isAdminRole.value) flag = true + return flag + }) + + async function getMyProfile() { + isFinished.value = false + const res = await getProfile() + profile.value = res.data + isFinished.value = true + storage.set(STORAGE_KEY.AUTHED, !!user.value?.email) + } + + function clearProfile() { + profile.value = null + demoMode.value = false + storage.clear() + } + return { + profile, + isFinished, + user, + isAdminRole, + isStudentAdmin, + isTeacherAdmin, + isTeacherOrAbove, + isSuperAdmin, + hasProblemPermission, + demoMode, + canToggleDemoMode, + toggleDemoMode, + isAuthed, + showSubmissions, + getMyProfile, + clearProfile, + } +}) diff --git a/apps/web/src/shared/themes/createTheme.ts b/apps/web/src/shared/themes/createTheme.ts new file mode 100644 index 0000000..8190216 --- /dev/null +++ b/apps/web/src/shared/themes/createTheme.ts @@ -0,0 +1,110 @@ +import { + HighlightStyle, + TagStyle, + syntaxHighlighting, +} from "@codemirror/language" +import { Extension } from "@codemirror/state" +import { EditorView } from "@codemirror/view" + +interface Options { + /** + * Theme variant. Determines which styles CodeMirror will apply by default. + */ + variant: Variant + + /** + * Settings to customize the look of the editor, like background, gutter, selection and others. + */ + settings: Settings + + /** + * Syntax highlighting styles. + */ + styles: TagStyle[] +} + +type Variant = "light" | "dark" + +interface Settings { + /** + * Editor background. + */ + background: string + + /** + * Default text color. + */ + foreground: string + + /** + * Caret color. + */ + caret: string + + /** + * Selection background. + */ + selection: string + + /** + * Background of highlighted lines. + */ + lineHighlight: string + + /** + * Gutter background. + */ + gutterBackground: string + + /** + * Text color inside gutter. + */ + gutterForeground: string + + gutterBorderRight: string +} + +export const createTheme = ({ + variant, + settings, + styles, +}: Options): Extension => { + const theme = EditorView.theme( + { + // eslint-disable-next-line @typescript-eslint/naming-convention + "&": { + backgroundColor: settings.background, + color: settings.foreground, + }, + ".cm-content": { + caretColor: settings.caret, + }, + ".cm-cursor, .cm-dropCursor": { + borderLeftColor: settings.caret, + }, + "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": + { + backgroundColor: settings.selection, + }, + ".cm-activeLine": { + backgroundColor: settings.lineHighlight, + }, + ".cm-gutters": { + backgroundColor: settings.gutterBackground, + borderRight: settings.gutterBorderRight, + color: settings.gutterForeground, + }, + ".cm-activeLineGutter": { + backgroundColor: settings.lineHighlight, + }, + }, + { + dark: variant === "dark", + }, + ) + + const highlightStyle = HighlightStyle.define(styles) + const extension = [theme, syntaxHighlighting(highlightStyle)] + + return extension +} diff --git a/apps/web/src/shared/themes/oneDark.ts b/apps/web/src/shared/themes/oneDark.ts new file mode 100644 index 0000000..2a848c9 --- /dev/null +++ b/apps/web/src/shared/themes/oneDark.ts @@ -0,0 +1,149 @@ +import { HighlightStyle, syntaxHighlighting } from "@codemirror/language" +import { Extension } from "@codemirror/state" +import { EditorView } from "@codemirror/view" +import { tags as t } from "@lezer/highlight" + +// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors + +const chalky = "#e5c07b", + coral = "#e06c75", + cyan = "#56b6c2", + invalid = "#ffffff", + ivory = "#abb2bf", + stone = "#7d8799", // Brightened compared to original to increase contrast + malibu = "#61afef", + sage = "#98c379", + whiskey = "#d19a66", + violet = "#c678dd", + darkBackground = "#26262a", + highlightBackground = "#2c313a", + background = "#101014", // naive-ui + tooltipBackground = "#353a42", + selection = "#3E4451", + cursor = "#528bff" + +/// The editor theme styles for One Dark. +const oneDarkTheme = EditorView.theme( + { + "&": { + color: ivory, + backgroundColor: background, + }, + + ".cm-content": { + caretColor: cursor, + }, + + ".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor }, + "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": + { backgroundColor: selection }, + + ".cm-panels": { backgroundColor: darkBackground, color: ivory }, + ".cm-panels.cm-panels-top": { borderBottom: "2px solid black" }, + ".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" }, + + ".cm-searchMatch": { + backgroundColor: "#72a1ff59", + outline: "1px solid #457dff", + }, + ".cm-searchMatch.cm-searchMatch-selected": { + backgroundColor: "#6199ff2f", + }, + + ".cm-activeLine": { backgroundColor: "#6699ff0b" }, + ".cm-selectionMatch": { backgroundColor: "#aafe661a" }, + + "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": { + backgroundColor: "#bad0f847", + }, + + ".cm-gutters": { + backgroundColor: background, + color: stone, + border: "none", + }, + + ".cm-activeLineGutter": { + backgroundColor: highlightBackground, + }, + + ".cm-foldPlaceholder": { + backgroundColor: "transparent", + border: "none", + color: "#ddd", + }, + + ".cm-tooltip": { + border: "none", + backgroundColor: tooltipBackground, + }, + ".cm-tooltip .cm-tooltip-arrow:before": { + borderTopColor: "transparent", + borderBottomColor: "transparent", + }, + ".cm-tooltip .cm-tooltip-arrow:after": { + borderTopColor: tooltipBackground, + borderBottomColor: tooltipBackground, + }, + ".cm-tooltip-autocomplete": { + "& > ul > li[aria-selected]": { + backgroundColor: highlightBackground, + color: ivory, + }, + }, + }, + { dark: true }, +) + +/// The highlighting style for code in the One Dark theme. +const oneDarkHighlightStyle = HighlightStyle.define([ + { tag: t.keyword, color: violet }, + { + tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], + color: coral, + }, + { tag: [t.function(t.variableName), t.labelName], color: malibu }, + { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey }, + { tag: [t.definition(t.name), t.separator], color: ivory }, + { + tag: [ + t.typeName, + t.className, + t.number, + t.changed, + t.annotation, + t.modifier, + t.self, + t.namespace, + ], + color: chalky, + }, + { + tag: [ + t.operator, + t.operatorKeyword, + t.url, + t.escape, + t.regexp, + t.link, + t.special(t.string), + ], + color: cyan, + }, + { tag: [t.meta, t.comment], color: stone }, + { tag: t.strong, fontWeight: "bold" }, + { tag: t.emphasis, fontStyle: "italic" }, + { tag: t.strikethrough, textDecoration: "line-through" }, + { tag: t.link, color: stone, textDecoration: "underline" }, + { tag: t.heading, fontWeight: "bold", color: coral }, + { tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey }, + { tag: [t.processingInstruction, t.string, t.inserted], color: sage }, + { tag: t.invalid, color: invalid }, +]) + +/// Extension to enable the One Dark theme (both the editor theme and +/// the highlight style). +export const oneDark: Extension = [ + oneDarkTheme, + syntaxHighlighting(oneDarkHighlightStyle), +] diff --git a/apps/web/src/shared/themes/smoothy.ts b/apps/web/src/shared/themes/smoothy.ts new file mode 100644 index 0000000..bb64ee1 --- /dev/null +++ b/apps/web/src/shared/themes/smoothy.ts @@ -0,0 +1,100 @@ +import { tags as t } from "@lezer/highlight" +import { EditorView } from "@codemirror/view" +import type { Extension } from "@codemirror/state" +import { createTheme } from "./createTheme" + +const tooltipTheme = EditorView.theme({ + ".cm-tooltip": { + border: "1px solid #e0e0e0", + backgroundColor: "#f8f8f8", + }, + ".cm-tooltip-autocomplete": { + "& > ul > li[aria-selected]": { + backgroundColor: "#e8e8e8", + color: "#000000", + }, + }, +}) + +// Author: Kenneth Reitz +const smoothyBase = createTheme({ + variant: "light", + settings: { + background: "#FFFFFF", + foreground: "#000000", + caret: "#000000", + selection: "#FFFD0054", + gutterBackground: "#FFFFFF", + gutterForeground: "#00000070", + gutterBorderRight: "none", + lineHighlight: "#00000008", + }, + styles: [ + { + tag: t.comment, + color: "#CFCFCF", + }, + { + tag: [t.number, t.bool, t.null], + color: "#E66C29", + }, + { + tag: [ + t.className, + t.definition(t.propertyName), + t.function(t.variableName), + t.labelName, + t.definition(t.typeName), + ], + color: "#2EB43B", + }, + { + tag: t.keyword, + color: "#D8B229", + }, + { + tag: t.operator, + color: "#4EA44E", + fontWeight: "bold", + }, + { + tag: [t.definitionKeyword, t.modifier], + color: "#925A47", + }, + { + tag: t.string, + color: "#704D3D", + }, + { + tag: t.typeName, + color: "#2F8996", + }, + { + tag: [t.variableName, t.propertyName], + color: "#77ACB0", + }, + { + tag: t.self, + color: "#77ACB0", + fontWeight: "bold", + }, + { + tag: t.regexp, + color: "#E3965E", + }, + { + tag: [t.tagName, t.angleBracket], + color: "#BAA827", + }, + { + tag: t.attributeName, + color: "#B06520", + }, + { + tag: t.derefOperator, + color: "#000", + }, + ], +}) + +export const smoothy: Extension = [smoothyBase, tooltipTheme] diff --git a/apps/web/src/utils/constants.ts b/apps/web/src/utils/constants.ts new file mode 100644 index 0000000..fe0b84c --- /dev/null +++ b/apps/web/src/utils/constants.ts @@ -0,0 +1,370 @@ +import type { AchievementRarity, SUBMISSION_RESULT, ReactionKey } from "./types" + +export enum SubmissionStatus { + compile_error = -2, + wrong_answer = -1, + accepted = 0, + time_limit_exceeded = 1 | 2, + memory_limit_exceeded = 3, + runtime_error = 4, + system_error = 5, + pending = 6, + judging = 7, + partial_accepted = 8, + submitting = 9, + ast_check_failed = 10, +} + +export enum ContestStatus { + initial = "2", // 这里不需要传入到后端,只是为了一开始加载数据的时候,做一个初始位 + not_started = "1", + underway = "0", + finished = "-1", +} + +export enum ContestType { + public = "Public", + private = "Password Protected", +} + +export const JUDGE_STATUS: { + [key in SUBMISSION_RESULT]: { + name: string + title: string + type: "error" | "success" | "warning" | "info" + } +} = { + "-2": { + name: "编译失败", + title: "编译失败", + type: "warning", + }, + "-1": { + name: "答案错误", + title: "答案错误", + type: "error", + }, + "0": { + name: "答案正确", + title: "答案正确", + type: "success", + }, + "1": { + name: "运行超时", + title: "运行超时", + type: "error", + }, + "2": { + name: "运行超时", + title: "运行超时", + type: "error", + }, + "3": { + name: "内存超限", + title: "内存超限", + type: "error", + }, + "4": { + name: "运行时错误", + title: "运行时错误", + type: "warning", + }, + "5": { + name: "系统错误", + title: "系统错误", + type: "error", + }, + "6": { + name: "等待评分", + title: "等待评分", + type: "warning", + }, + "7": { + name: "正在评分", + title: "正在评分", + type: "warning", + }, + "8": { + name: "部分正确", + title: "部分正确", + type: "warning", + }, + "9": { + name: "正在提交", + title: "正在提交", + type: "info", + }, + "10": { + name: "语法未通过", + title: "答案正确,但语法未通过", + type: "success", + }, +} + +export const CONTEST_STATUS: { + [key in ContestStatus]: { + name: string + type: "error" | "success" | "warning" + } +} = { + // 这里不需要传入到后端,只是为了一开始加载数据的时候,做一个初始位 + "2": { + name: "未开始", + type: "warning", + }, + "1": { + name: "未开始", + type: "warning", + }, + "0": { + name: "进行中", + type: "success", + }, + "-1": { + name: "已结束", + type: "error", + }, +} + +export const CONTEST_TYPE = { + PUBLIC: "Public", + PRIVATE: "Password Protected", +} + +export const USER_TYPE = { + REGULAR_USER: "Regular User", + STUDENT_ADMIN: "Student Admin", + TEACHER_ADMIN: "Teacher Admin", + SUPER_ADMIN: "Super Admin", +} + +export const PROBLEM_PERMISSION = { + NONE: "None", + OWN: "Own", + ALL: "All", +} + +export const STORAGE_KEY = { + AUTHED: "authed", + LANGUAGE: "problemLanguage", + LEARN_CURRENT_STEP: "learnStep", + ADMIN_PROBLEM: "adminProblem", + ADMIN_PROBLEM_TAGS: "adminProblemTags", + DEMO_MODE: "demoMode", +} + +export const DIFFICULTY = { + Low: "简单", + Mid: "中等", + High: "困难", +} + +const cSource = + "#include\r\n\r\nint main()\r\n{\r\n \r\n return 0;\r\n}" +const cppSource = + "#include\r\n\r\nusing namespace std;\r\n\r\nint main()\r\n{\r\n \r\n return 0;\r\n}" +const pythonSource = "" +const javaSource = + 'public class Main {\r\n public static void main(String[] args) {\r\n System.out.println("黄岩一职");\r\n }\r\n}' + +export const SOURCES = { + C: cSource, + "C++": cppSource, + Java: javaSource, + Python3: pythonSource, + Python2: "", + JavaScript: "", + Golang: "", + Flowchart: "", + SQL: "", +} as const + +export const LANGUAGE_ID = { + C: 50, + "C++": 54, + Java: 62, + Python3: 71, + Python2: 0, + JavaScript: 0, + Golang: 0, + Flowchart: 0, + SQL: 0, +} as const + +export const LANGUAGE_FORMAT_VALUE = { + C: "c", + "C++": "cpp", + Java: "java", + Python2: "python", + Python3: "python", + JavaScript: "javascript", + Golang: "go", + Flowchart: "flowchart", + SQL: "sql", +} as const + +export const LANGUAGE_SHOW_VALUE = { + Flowchart: "流程图", + C: "C语言", + "C++": "C++", + Java: "Java", + Python2: "Python", + Python3: "Python", + JavaScript: "JS", + Golang: "Go", + SQL: "SQL", +} as const + +export const ICON_SET = { + Flowchart: "vscode-icons:file-type-drawio", + Python2: "devicon:python", + Python3: "devicon:python", + C: "devicon:c", + "C++": "devicon:cplusplus", + Java: "devicon:java", + JavaScript: "devicon:javascript", + Golang: "devicon:go", + SQL: "devicon:sqlite", +} as const + +const cTemplate = `//TEMPLATE BEGIN +#include + +int main() { + printf("黄岩一职"); + return 0; +} +//TEMPLATE END` + +const cppTemplate = `//TEMPLATE BEGIN +#include + +int main() { + return 0; +} +//TEMPLATE END` + +const blankTemplate = `//PREPEND BEGIN +//PREPEND END + +//TEMPLATE BEGIN +//TEMPLATE END + +//APPEND BEGIN +//APPEND END` + +export const CODE_TEMPLATES = { + C: cTemplate, + "C++": cppTemplate, + Python2: blankTemplate, + Python3: blankTemplate, + Java: blankTemplate, + JavaScript: blankTemplate, + Golang: blankTemplate, + Flowchart: blankTemplate, + SQL: blankTemplate, +} as const + +export enum ScreenMode { + both = "双栏", + code = "自测", + problem = "题目", +} + +export enum ChartType { + Rank, + Activity, +} + +// 成就稀有度 +export const RARITY_LABEL: Record = { + bronze: "青铜", + silver: "白银", + gold: "黄金", + platinum: "白金", +} + +// 边框、色块用的原色:饱和度够高,明暗底上都醒目。 +// 青铜往红偏(色相 23),黄金往纯黄推(色相 47),中间隔开两档亮度, +// 不然两个都落在橙棕区,扫一眼分不出来 +export const RARITY_COLOR: Record = { + bronze: "#c2703d", + silver: "#9fa6b2", + gold: "#f2c012", + platinum: "#7dd3fc", +} + +// 文字要另配一套。上面那组是照深色底调的,搬到白底上白金只有 1.7:1、 +// 黄金 2.2:1,12px 的稀有度标签根本看不清。取色见 useRarityColor +export const RARITY_TEXT_COLOR: Record< + "dark" | "light", + Record +> = { + dark: { + bronze: "#e08d63", + silver: "#b6bcc7", + gold: "#f2c012", + platinum: "#7dd3fc", + }, + light: { + bronze: "#a13d1e", + silver: "#6b7280", + gold: "#8f6f00", + platinum: "#0369a1", + }, +} + +// 时间范围配置 +export const DURATION_OPTIONS = [ + { label: "本节课内", value: "hours:1" }, + { label: "两节课内", value: "hours:2" }, + { label: "一天内", value: "days:1" }, + { label: "一周内", value: "weeks:1" }, + { label: "一个月内", value: "months:1" }, + { label: "两个月内", value: "months:2" }, + { label: "半年内", value: "months:6" }, + { label: "一年内", value: "years:1" }, +] as const + +// 班级号的位数范围。学生用户名形如 ks<班级号><姓名>,班级号还要跟 +// 网站配置里的班级列表对得上。 +// 后端 OnlineJudge/utils/shortcuts.py 的 CLASS_NAME_MIN/MAX_DIGITS +// 是同一条规则的另一份,改这里必须同时改那边。 +export const CLASS_NAME_MIN_DIGITS = 3 +export const CLASS_NAME_MAX_DIGITS = 4 + +/** 合法班级号:3~4 位纯数字 */ +export const CLASS_NAME_RE = new RegExp( + `^\\d{${CLASS_NAME_MIN_DIGITS},${CLASS_NAME_MAX_DIGITS}}$`, +) + +/** 用户名开头的 ks<班级号>,用于从用户名里认出班级 */ +export const USERNAME_CLASS_RE = new RegExp( + `^ks\\d{${CLASS_NAME_MIN_DIGITS},${CLASS_NAME_MAX_DIGITS}}`, +) + +/** 班级号作为数字时的上下界,给 n-input-number 用 */ +export const CLASS_NAME_MIN_VALUE = 10 ** (CLASS_NAME_MIN_DIGITS - 1) +export const CLASS_NAME_MAX_VALUE = 10 ** CLASS_NAME_MAX_DIGITS - 1 + +export const REACTIONS: { + key: ReactionKey + label: string + icon: string +}[] = [ + { + key: "too_easy", + label: "太简单", + icon: "fluent-emoji:smiling-face-with-sunglasses", + }, + { key: "too_hard", label: "太难了", icon: "fluent-emoji:exploding-head" }, + { + key: "confusing", + label: "没看懂", + icon: "fluent-emoji:face-with-spiral-eyes", + }, + { key: "buggy", label: "题目有错", icon: "fluent-emoji:bug" }, + { key: "learned", label: "学到了", icon: "fluent-emoji:light-bulb" }, + { key: "interesting", label: "有意思", icon: "fluent-emoji:star-struck" }, + { key: "want_explain", label: "想听讲解", icon: "fluent-emoji:books" }, +] diff --git a/apps/web/src/utils/download.ts b/apps/web/src/utils/download.ts new file mode 100644 index 0000000..327e17a --- /dev/null +++ b/apps/web/src/utils/download.ts @@ -0,0 +1,23 @@ +import axios from "axios" + +const http = axios.create({ + baseURL: "/api/admin", + responseType: "blob", + xsrfHeaderName: "X-CSRFToken", + xsrfCookieName: "csrftoken", +}) + +async function download(url: string) { + const res = await http.get(url) + const headers = res.headers + const link = document.createElement("a") + link.href = window.URL.createObjectURL( + new window.Blob([res.data], { type: headers["content-type"] }), + ) + link.download = (headers["content-disposition"] || "").split("filename=")[1] + document.body.appendChild(link) + link.click() + link.remove() +} + +export default download diff --git a/apps/web/src/utils/functions.ts b/apps/web/src/utils/functions.ts new file mode 100644 index 0000000..3ec9c7d --- /dev/null +++ b/apps/web/src/utils/functions.ts @@ -0,0 +1,600 @@ +import { getTime, intervalToDuration, parseISO, type Duration } from "date-fns" +import { User } from "./types" +import { USER_TYPE } from "./constants" +import { + strFromU8, + strToU8, + unzlibSync, + zipSync, + zlibSync, + type Zippable, +} from "fflate" +import copyTextFallback from "copy-text-to-clipboard" +import { customAlphabet } from "nanoid" + +function calculateACRate(acCount: number, totalCount: number): string { + if (totalCount === 0) return "0.00" + if (acCount >= totalCount) return "100.00" + return ((acCount / totalCount) * 100).toFixed(2) +} + +export function getACRate(acCount: number, totalCount: number): string { + return `${calculateACRate(acCount, totalCount)}%` +} + +export function getACRateNumber(acCount: number, totalCount: number): number { + return parseFloat(calculateACRate(acCount, totalCount)) +} + +export function filterEmptyValue>( + object: T, +): Partial { + return Object.entries(object).reduce((query, [key, value]) => { + if (value != null && value !== "" && value !== undefined) { + query[key as keyof T] = value + } + return query + }, {} as Partial) +} + +export function getTagColor( + tag: "Low" | "Mid" | "High" | "简单" | "中等" | "困难", +) { + return <"success" | "info" | "error">{ + Low: "success", + Mid: "info", + High: "error", + 简单: "success", + 中等: "info", + 困难: "error", + }[tag] +} + +// 2023-04-03T02:43:28.673156Z +export function parseTime(utc: Date | string, format = "YYYY年M月D日") { + const time = useDateFormat(utc, format, { locales: "zh-CN" }) + return time.value +} + +function getDurationObject(start: Date | string, end: Date | string) { + return intervalToDuration({ + start: getTime(parseISO(start.toString())), + end: getTime(parseISO(end.toString())), + }) +} + +function formatDurationUnits( + duration: Duration, + units: Array<{ key: keyof Duration; suffix: string }>, +): string { + return units + .filter(({ key }) => duration[key]) + .map(({ key, suffix }) => duration[key] + suffix) + .join("") +} + +export function duration( + start: Date | string, + end: Date | string, + showSeconds = false, +): string { + const durationObj = getDurationObject(start, end) + const units = [ + { key: "years" as const, suffix: "年" }, + { key: "months" as const, suffix: "月" }, + { key: "days" as const, suffix: "天" }, + { key: "hours" as const, suffix: "小时" }, + { key: "minutes" as const, suffix: "分钟" }, + ...(showSeconds ? [{ key: "seconds" as const, suffix: "秒" }] : []), + ] + return formatDurationUnits(durationObj, units) +} + +export function durationToDays( + start: Date | string, + end: Date | string, +): string { + const durationObj = getDurationObject(start, end) + const units = [ + { key: "years" as const, suffix: "年" }, + { key: "months" as const, suffix: "月" }, + { key: "days" as const, suffix: "天" }, + ] + const result = formatDurationUnits(durationObj, units) + return result || "一天以内" +} + +export function secondsToDuration(seconds: number): string { + const duration = intervalToDuration({ + start: 0, + end: seconds * 1000, + }) + const hours = (duration.days ?? 0) * 24 + (duration.hours ?? 0) + const pad = (n: number) => String(n).padStart(2, "0") + return [hours, pad(duration.minutes ?? 0), pad(duration.seconds ?? 0)].join( + ":", + ) +} + +export function submissionMemoryFormat(memory: number | string | undefined) { + if (memory === undefined) return "--" + // 1048576 = 1024 * 1024 + let t = parseInt(memory + "") / 1048576 + return String(t.toFixed(0)) + "MB" +} + +export function submissionTimeFormat(time: number | string | undefined) { + if (time === undefined) return "--" + return time + "ms" +} + +export function debounce any>( + fn: T, + delay = 100, +): (...args: Parameters) => void { + let timeoutId: ReturnType + return (...args: Parameters) => { + clearTimeout(timeoutId) + timeoutId = setTimeout(() => fn(...args), delay) + } +} + +export function getUserRole(role: User["admin_type"]): { + type: "default" | "info" | "warning" | "error" + label: "普通" | "学生管理员" | "教师管理员" | "超管" +} { + const roleMap = { + [USER_TYPE.REGULAR_USER]: { + type: "default" as const, + label: "普通" as const, + }, + [USER_TYPE.STUDENT_ADMIN]: { + type: "info" as const, + label: "学生管理员" as const, + }, + [USER_TYPE.TEACHER_ADMIN]: { + type: "warning" as const, + label: "教师管理员" as const, + }, + [USER_TYPE.SUPER_ADMIN]: { + type: "error" as const, + label: "超管" as const, + }, + } + + return roleMap[role] || roleMap[USER_TYPE.REGULAR_USER] +} + +export function unique(arr: T[]): T[] { + return [...new Set(arr)] +} + +export function encode(string?: string): string { + try { + return btoa(String.fromCharCode(...new TextEncoder().encode(string ?? ""))) + } catch (error) { + console.error("编码失败:", error) + return "" + } +} + +export function decode(bytes?: string): string { + try { + if (!bytes) return "" + const latin = atob(bytes) + return new TextDecoder("utf-8").decode( + Uint8Array.from({ length: latin.length }, (_, index) => + latin.charCodeAt(index), + ), + ) + } catch (error) { + console.error("解码失败:", error) + return "" + } +} + +export function getCSRFToken(): string { + if (typeof document === "undefined") { + return "" + } + const match = document.cookie.match(/(?:^|;\s*)csrftoken=([^;]+)/) + return match ? decodeURIComponent(match[1]) : "" +} + +export function utoa(data: string): string { + const buffer = strToU8(data) + const zipped = zlibSync(buffer, { level: 9 }) + const binary = strFromU8(zipped, true) + return btoa(binary) +} + +export function atou(base64: string): string { + const binary = atob(base64) + const buffer = strToU8(binary, true) + const unzipped = unzlibSync(buffer) + return strFromU8(unzipped) +} + +/** + * 把若干文本文件打包成 zip Blob + * @param files 文件名和文本内容 + * @param mtime 归档内的修改时间,默认当前时间 + */ +export function createZipBlob( + files: { name: string; content: string }[], + mtime: Date = new Date(), +): Blob { + const entries: Zippable = {} + for (const f of files) { + entries[f.name] = strToU8(f.content) + } + return new Blob([zipSync(entries, { mtime })], { type: "application/zip" }) +} + +/** + * 复制文本到剪贴板 + * 优先使用 Clipboard API(支持在 modal 中使用),失败时回退到 copy-text-to-clipboard + * @param text 要复制的文本 + * @returns Promise 复制是否成功 + */ +export async function copyToClipboard(text: string): Promise { + // 优先使用现代 Clipboard API + if (navigator.clipboard && window.isSecureContext) { + try { + await navigator.clipboard.writeText(text) + return true + } catch (error) { + console.warn("Clipboard API 复制失败,尝试使用回退方法:", error) + } + } + + // 回退到 copy-text-to-clipboard + try { + const success = copyTextFallback(text) + return success + } catch (error) { + console.error("复制失败:", error) + return false + } +} + +export function getRandomId() { + const nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz") + return nanoid() +} + +/** + * 恶搞效果函数 - 随机触发不同的页面恶搞效果 + */ +export function trickOrTreat() { + const effects = [ + // 效果1: 中文乱码 + () => { + document.body.innerHTML = document.body.innerHTML.replace( + /[\u4e00-\u9fa5]/g, + function (c) { + return String.fromCharCode(c.charCodeAt(0) ^ 0xa5) // 将中文字符转为乱码 + }, + ) + }, + // 效果2: 页面一直缩放 + () => { + const style = document.createElement("style") + style.id = "trick-scale-style" + style.textContent = ` + body { + animation: trickScale 0.5s ease-in-out infinite alternate; + } + @keyframes trickScale { + from { transform: scale(0.8); } + to { transform: scale(1.2); } + } + ` + document.head.appendChild(style) + }, + // 效果3: 页面左右颠倒 + () => { + document.body.style.transform = "scaleX(-1)" + }, + // 效果4: 去掉鼠标 + () => { + const style = document.createElement("style") + style.id = "trick-cursor-none-style" + style.textContent = ` + * { + cursor: none !important; + } + ` + document.head.appendChild(style) + }, + // 效果5: 页面一直旋转 + () => { + const style = document.createElement("style") + style.id = "trick-rotate-style" + style.textContent = ` + body { + animation: trickRotate 2s linear infinite; + } + @keyframes trickRotate { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + ` + document.head.appendChild(style) + }, + // 效果6: 页面上下颠倒 + () => { + document.body.style.transform = "scaleY(-1)" + }, + // 效果7: 页面颜色反转(反色) + () => { + const style = document.createElement("style") + style.id = "trick-invert-style" + style.textContent = ` + body { + filter: invert(1) !important; + } + ` + document.head.appendChild(style) + }, + // 效果8: 页面抖动/震动 + () => { + const style = document.createElement("style") + style.id = "trick-shake-style" + style.textContent = ` + body { + animation: trickShake 0.1s ease-in-out infinite; + } + @keyframes trickShake { + 0%, 100% { transform: translate(0, 0); } + 25% { transform: translate(-5px, -5px); } + 50% { transform: translate(5px, 5px); } + 75% { transform: translate(-5px, 5px); } + } + ` + document.head.appendChild(style) + }, + // 效果9: 页面模糊 + () => { + const style = document.createElement("style") + style.id = "trick-blur-style" + style.textContent = ` + body { + filter: blur(5px) !important; + } + ` + document.head.appendChild(style) + }, + // 效果10: 点击哪里,哪里的DOM就飞掉 + () => { + // 添加CSS动画样式 + const style = document.createElement("style") + style.id = "trick-flyaway-style" + style.textContent = ` + .trick-flyaway { + animation: trickFlyAway 0.5s ease-out forwards !important; + pointer-events: none !important; + } + @keyframes trickFlyAway { + 0% { + transform: translate(0, 0) rotate(0deg); + opacity: 1; + } + 100% { + transform: translate(var(--fly-x, 500px), var(--fly-y, -500px)) rotate(720deg); + opacity: 0; + } + } + ` + document.head.appendChild(style) + + // 添加全局点击事件监听器 + const clickHandler = (e: MouseEvent) => { + const target = e.target as HTMLElement + // 跳过body和html元素,以及已经飞走的元素 + if ( + target === document.body || + target === document.documentElement || + target.classList.contains("trick-flyaway") + ) { + return + } + + // 获取点击位置相对于视口的位置 + const rect = target.getBoundingClientRect() + const clickX = e.clientX - rect.left - rect.width / 2 + const clickY = e.clientY - rect.top - rect.height / 2 + + // 计算飞出方向(随机方向) + const angle = Math.random() * Math.PI * 2 + const distance = 1000 + Math.random() * 500 + const flyX = Math.cos(angle) * distance + const flyY = Math.sin(angle) * distance + + // 设置CSS变量 + target.style.setProperty("--fly-x", `${flyX}px`) + target.style.setProperty("--fly-y", `${flyY}px`) + + // 添加飞走动画类 + target.classList.add("trick-flyaway") + + // 动画结束后移除元素或隐藏 + setTimeout(() => { + target.style.display = "none" + }, 500) + } + + document.addEventListener("click", clickHandler, true) + }, + // 效果11: 页面元素随机位置 + () => { + const style = document.createElement("style") + style.id = "trick-random-position-style" + style.textContent = ` + * { + position: relative !important; + } + ` + document.head.appendChild(style) + + // 随机移动所有元素 + const allElements = document.querySelectorAll("*") + allElements.forEach((el) => { + if (el === document.body || el === document.documentElement) return + const element = el as HTMLElement + const randomX = (Math.random() - 0.5) * 200 + const randomY = (Math.random() - 0.5) * 200 + element.style.transform = `translate(${randomX}px, ${randomY}px)` + }) + }, + // 效果12: 页面变成3D倾斜效果 + () => { + const style = document.createElement("style") + style.id = "trick-3d-tilt-style" + style.textContent = ` + body { + perspective: 1000px; + transform-style: preserve-3d; + animation: trick3DTilt 3s ease-in-out infinite alternate; + } + @keyframes trick3DTilt { + 0% { + transform: perspective(1000px) rotateX(0deg) rotateY(0deg); + } + 25% { + transform: perspective(1000px) rotateX(15deg) rotateY(-15deg); + } + 50% { + transform: perspective(1000px) rotateX(-15deg) rotateY(15deg); + } + 75% { + transform: perspective(1000px) rotateX(15deg) rotateY(15deg); + } + 100% { + transform: perspective(1000px) rotateX(-15deg) rotateY(-15deg); + } + } + ` + document.head.appendChild(style) + }, + // 效果13: 页面所有链接失效 + () => { + const clickHandler = (e: MouseEvent) => { + const target = e.target as HTMLElement + // 检查是否是链接或按钮 + if ( + target.tagName === "A" || + target.closest("a") || + (target.tagName === "BUTTON" && !target.closest("n-button")) + ) { + e.preventDefault() + e.stopPropagation() + e.stopImmediatePropagation() + return false + } + } + document.addEventListener("click", clickHandler, true) + }, + // 效果14: 页面变成波浪效果 + () => { + const style = document.createElement("style") + style.id = "trick-wave-style" + style.textContent = ` + body { + animation: trickWave 2s ease-in-out infinite; + } + @keyframes trickWave { + 0%, 100% { + transform: translateY(0) scaleY(1); + } + 25% { + transform: translateY(-10px) scaleY(1.02); + } + 50% { + transform: translateY(0) scaleY(1); + } + 75% { + transform: translateY(10px) scaleY(0.98); + } + } + ` + document.head.appendChild(style) + }, + // 效果15: 页面变成故障效果(Glitch) + () => { + const style = document.createElement("style") + style.id = "trick-glitch-style" + style.textContent = ` + body { + animation: trickGlitch 0.3s infinite; + } + @keyframes trickGlitch { + 0% { + transform: translate(0); + filter: hue-rotate(0deg); + } + 20% { + transform: translate(-2px, 2px); + filter: hue-rotate(90deg); + } + 40% { + transform: translate(-2px, -2px); + filter: hue-rotate(180deg); + } + 60% { + transform: translate(2px, 2px); + filter: hue-rotate(270deg); + } + 80% { + transform: translate(2px, -2px); + filter: hue-rotate(360deg); + } + 100% { + transform: translate(0); + filter: hue-rotate(0deg); + } + } + body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: + repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(255, 0, 0, 0.03) 2px, + rgba(255, 0, 0, 0.03) 4px + ), + repeating-linear-gradient( + 90deg, + transparent, + transparent 2px, + rgba(0, 255, 255, 0.03) 2px, + rgba(0, 255, 255, 0.03) 4px + ); + pointer-events: none; + z-index: 9999; + mix-blend-mode: difference; + } + ` + document.head.appendChild(style) + }, + ] + + // 随机选择一种效果 + const randomEffect = effects[Math.floor(Math.random() * effects.length)] + randomEffect() +} + +// function getChromeVersion() { +// var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) +// return raw ? parseInt(raw[2], 10) : 0 +// } + +// export const isLowVersion = getChromeVersion() < 80 + +// export const protocol = isLowVersion ? "http" : "https" diff --git a/apps/web/src/utils/http.ts b/apps/web/src/utils/http.ts new file mode 100644 index 0000000..40d90be --- /dev/null +++ b/apps/web/src/utils/http.ts @@ -0,0 +1,79 @@ +import axios, { type AxiosRequestConfig } from "axios" +import { createDiscreteApi } from "naive-ui" +import { useAuthModalStore } from "shared/store/authModal" +import storage from "./storage" +import { STORAGE_KEY } from "./constants" + +const { message } = createDiscreteApi(["message"]) + +// 后端统一返回 { error, data } 信封;拦截器剥掉 axios 外层后, +// 调用方拿到的就是这个信封,data 才是真正的业务数据。 +export interface ApiResponse { + error: string | null + data: T +} + +// 让 http.get() 的类型真实反映"解包后返回信封"这件事, +// 调用方 res.data 直接拿到带类型的 T,不再依赖 axios 的 AxiosResponse 巧合对齐。 +interface Http { + get( + url: string, + config?: AxiosRequestConfig, + ): Promise> + delete( + url: string, + config?: AxiosRequestConfig, + ): Promise> + post( + url: string, + data?: unknown, + config?: AxiosRequestConfig, + ): Promise> + put( + url: string, + data?: unknown, + config?: AxiosRequestConfig, + ): Promise> +} + +const instance = axios.create({ + baseURL: "/api", + xsrfHeaderName: "X-CSRFToken", + xsrfCookieName: "csrftoken", +}) + +// 统一剥掉空字符串 / null / undefined 的 query 参数, +// 各 api 函数不必再手写过滤逻辑(保留 0、false)。 +instance.interceptors.request.use((config) => { + if (config.params) { + config.params = Object.fromEntries( + Object.entries(config.params).filter( + ([, v]) => v !== "" && v !== null && v !== undefined, + ), + ) + } + return config +}) + +instance.interceptors.response.use( + (res) => { + if (res.data.error) { + if (res.data.error === "login-required") { + storage.remove(STORAGE_KEY.AUTHED) + useAuthModalStore().openLoginModal() + } else if (res.data.error === "permission-denied") { + message.error(res.data.data || "权限不足") + } + return Promise.reject(res.data) + } else { + return Promise.resolve(res.data) + } + }, + (err) => { + return Promise.reject(err) + }, +) + +const http = instance as unknown as Http + +export default http diff --git a/apps/web/src/utils/judge.ts b/apps/web/src/utils/judge.ts new file mode 100644 index 0000000..6015d21 --- /dev/null +++ b/apps/web/src/utils/judge.ts @@ -0,0 +1,37 @@ +import axios from "axios" +import { decode, encode } from "./functions" +import { Code } from "./types" + +const http = axios.create({ baseURL: import.meta.env.PUBLIC_JUDGE0_URL }) + +export async function createTestSubmission(code: Code, input: string) { + const encodedCode = encode(code.value) + const id = { + C: 50, + "C++": 54, + Java: 62, + Golang: 60, + JavaScript: 63, + Python2: 70, + Python3: 71, + }[code.language] + let compilerOptions = "" + if (id === 50) compilerOptions = "-lm" // 解决 GCC 的链接问题 + const payload = { + source_code: encodedCode, + language_id: id, + stdin: encode(input), + redirect_stderr_to_stdout: true, + compiler_options: compilerOptions, + } + const response = await http.post("/submissions", payload, { + params: { base64_encoded: true, wait: true }, + }) + const data = response.data + return { + status: data.status && data.status.id, + output: [decode(data.compile_output), decode(data.stdout)] + .join("\n") + .trim(), + } +} diff --git a/apps/web/src/utils/permissions.ts b/apps/web/src/utils/permissions.ts new file mode 100644 index 0000000..861529d --- /dev/null +++ b/apps/web/src/utils/permissions.ts @@ -0,0 +1,108 @@ +import { useUserStore } from "shared/store/user" + +export function usePermissions() { + const userStore = useUserStore() + + return { + isAuthenticated: computed(() => userStore.isAuthed), + isAdminRole: computed(() => userStore.isAdminRole), + isTeacherOrAbove: computed(() => userStore.isTeacherOrAbove), + isSuperAdmin: computed(() => userStore.isSuperAdmin), + hasProblemPermission: computed(() => userStore.hasProblemPermission), + + canManageUsers: computed(() => userStore.isSuperAdmin), + canManageAnnouncements: computed(() => userStore.isSuperAdmin), + canManageTutorials: computed(() => userStore.isSuperAdmin), + canManageSystemConfig: computed(() => userStore.isSuperAdmin), + canSendMessages: computed(() => userStore.isSuperAdmin), + + canManageProblems: computed(() => userStore.hasProblemPermission), + canManageContests: computed(() => userStore.isTeacherOrAbove), + canManageProblemsets: computed(() => userStore.isTeacherOrAbove), + canViewClassroomData: computed(() => userStore.isTeacherOrAbove), + + canManageAllProblems: computed( + () => + userStore.user?.problem_permission === "All" || userStore.isSuperAdmin, + ), + canManageOwnProblems: computed( + () => + userStore.user?.problem_permission === "Own" && !userStore.isSuperAdmin, + ), + + getUserPermissionLevel: computed(() => { + if (userStore.isSuperAdmin) return "超级管理员" + if (userStore.isTeacherAdmin) return "教师管理员" + if (userStore.isStudentAdmin) return "学生管理员" + return "普通用户" + }), + + getProblemPermissionLevel: computed(() => { + if (!userStore.user) return "无权限" + switch (userStore.user.problem_permission) { + case "All": + return "管理所有题目" + case "Own": + return "管理自己的题目" + case "None": + return "无题目权限" + default: + return "无权限" + } + }), + } +} + +export function checkRoutePermission(routeName: string): boolean { + const userStore = useUserStore() + + const superAdminRoutes = [ + "admin home", + "admin config", + "admin user list", + "admin user generate", + "admin announcement list", + "admin announcement create", + "admin announcement edit", + "admin message list", + "admin tutorial list", + "admin tutorial create", + "admin tutorial edit", + ] + + const teacherAdminRoutes = [ + "admin contest list", + "admin contest create", + "admin contest edit", + "admin contest problem list", + "admin contest problem create", + "admin contest problem edit", + "admin contest helper", + "admin problemset list", + "admin problemset create", + "admin problemset edit", + "admin problemset detail", + "admin stuck problems", + "admin top ac trend", + ] + + const problemPermissionRoutes = [ + "admin problem list", + "admin problem create", + "admin problem edit", + ] + + if (superAdminRoutes.includes(routeName)) { + return userStore.isSuperAdmin + } + + if (teacherAdminRoutes.includes(routeName)) { + return userStore.isTeacherOrAbove + } + + if (problemPermissionRoutes.includes(routeName)) { + return userStore.hasProblemPermission + } + + return true +} diff --git a/apps/web/src/utils/renders.ts b/apps/web/src/utils/renders.ts new file mode 100644 index 0000000..02f4be1 --- /dev/null +++ b/apps/web/src/utils/renders.ts @@ -0,0 +1,9 @@ +import { Icon } from "@iconify/vue" +import { NFlex } from "naive-ui" + +export function renderTableTitle(title: string, icon: string): any { + return h(NFlex, { align: "center", size: 4 }, () => [ + h(Icon, { icon: icon, width: 20, height: 20 }), + title, + ]) +} diff --git a/apps/web/src/utils/storage.ts b/apps/web/src/utils/storage.ts new file mode 100644 index 0000000..feb1ca5 --- /dev/null +++ b/apps/web/src/utils/storage.ts @@ -0,0 +1,21 @@ +const localStorage = window.localStorage + +export default { + set(key: string, value: any) { + localStorage.setItem(key, JSON.stringify(value)) + }, + get(key: string) { + const content = localStorage.getItem(key) + if (content) { + return JSON.parse(content) + } else { + return null + } + }, + remove(key: string) { + localStorage.removeItem(key) + }, + clear() { + localStorage.clear() + }, +} diff --git a/apps/web/src/utils/stream.ts b/apps/web/src/utils/stream.ts new file mode 100644 index 0000000..fc6b6ef --- /dev/null +++ b/apps/web/src/utils/stream.ts @@ -0,0 +1,94 @@ +export interface JSONEventStreamHandlers { + onMessage: (data: T, event?: string) => void + onEvent?: (event: string) => void + signal?: AbortSignal | null +} + +export async function consumeJSONEventStream( + response: Response, + handlers: JSONEventStreamHandlers, +) { + if (!response.body) { + throw new Error("当前环境不支持可读流") + } + + const reader = response.body.getReader() + const decoder = new TextDecoder("utf-8") + let buffer = "" + + const { onMessage, onEvent, signal } = handlers + + const handleEvent = (raw: string) => { + const lines = raw.split("\n") + let eventName: string | undefined + const dataLines: string[] = [] + + for (const line of lines) { + const trimmed = line.trim() + if (!trimmed) continue + if (trimmed.startsWith("event:")) { + eventName = trimmed.slice(6).trim() + } else if (trimmed.startsWith("data:")) { + dataLines.push(trimmed.slice(5).trim()) + } + } + + if (dataLines.length === 0) { + if (eventName && onEvent) { + onEvent(eventName) + } + return + } + + const payloadStr = dataLines.join("\n") + + let parsed: T + try { + parsed = JSON.parse(payloadStr) + } catch (error) { + throw new Error(`无法解析服务端事件数据: ${payloadStr}`) + } + + onMessage(parsed, eventName) + } + + const processBuffer = (flush = false) => { + let idx = buffer.indexOf("\n\n") + while (idx !== -1) { + const rawEvent = buffer.slice(0, idx) + buffer = buffer.slice(idx + 2) + if (rawEvent.trim()) { + handleEvent(rawEvent) + } + idx = buffer.indexOf("\n\n") + } + + if (flush && buffer.trim()) { + handleEvent(buffer.trim()) + buffer = "" + } + } + + try { + while (true) { + if (signal?.aborted) { + await reader.cancel() + break + } + + const { value, done } = await reader.read() + if (value) { + buffer += decoder.decode(value, { stream: true }) + processBuffer() + } + + if (done) { + buffer += decoder.decode() + processBuffer(true) + break + } + } + } finally { + reader.releaseLock() + } +} diff --git a/apps/web/src/utils/types.ts b/apps/web/src/utils/types.ts new file mode 100644 index 0000000..4ab9426 --- /dev/null +++ b/apps/web/src/utils/types.ts @@ -0,0 +1,790 @@ +import { ContestStatus, ContestType, LANGUAGE_SHOW_VALUE } from "./constants" + +export interface Profile { + id: number + user: User + real_name: string + acm_problems_status: { + problems: { + [key: string]: { + _id: string + status: number + } + } + } + avatar: string + blog: null + mood: string + github: string + school: string + major: string + language: string + accepted_number: number + submission_number: number +} + +export type UserAdminType = + "Regular User" | "Student Admin" | "Teacher Admin" | "Super Admin" + +export interface User { + id: number + username: string + real_name: string + email: string + admin_type: UserAdminType + problem_permission: string + create_time: Date + last_login: Date + open_api: boolean + is_disabled: boolean + password?: string + raw_password?: string + class_name?: string | null +} + +export type LANGUAGE = + | "C" + | "C++" + | "Python2" + | "Python3" + | "Java" + | "JavaScript" + | "Golang" + | "Flowchart" + | "SQL" + +export interface SQLConfig { + mode: "query" | "modify" + order_sensitive: boolean +} + +export interface SQLDisplayColumn { + name: string + type?: string +} + +export interface SQLDisplayTable { + name: string + columns: SQLDisplayColumn[] + rows: (string | number | null)[][] + total_rows: number + truncated: boolean + dropped?: boolean +} + +export interface SQLDisplay { + tables: SQLDisplayTable[] + expected: + | { + columns: SQLDisplayColumn[] + rows: (string | number | null)[][] + total_rows: number + truncated: boolean + } + | { changed_tables: SQLDisplayTable[] } +} + +export type LANGUAGE_SHOW_LABEL = + (typeof LANGUAGE_SHOW_VALUE)[keyof typeof LANGUAGE_SHOW_VALUE] + +export type SUBMISSION_RESULT = + -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 + +export type ProblemStatus = "passed" | "failed" | "not_test" + +interface SampleUser { + id: number + username: string + real_name: string | null +} + +export interface Tag { + id: number + name: string +} + +export interface AdminTag { + id: number + name: string + problem_count: number +} + +export interface TestcaseUploadedReturns { + id: string + info: Testcase[] +} + +export interface Testcase { + input_name: string + output_name: string + score: string +} + +export interface Problem { + _id: string + id: number + tags: string[] + created_by: SampleUser + template: { [key in LANGUAGE]?: string } + title: string + description: string + input_description: string + output_description: string + samples: { + input: string + output: string + }[] + hint: string + languages: Array + create_time: Date + last_update_time: null + time_limit: number + memory_limit: number + difficulty: "Low" | "Mid" | "High" + source: string + prompt: string + answers: { language: LANGUAGE; code: string }[] + submission_number: number + accepted_number: number + statistic_info: { [key: string]: number } + share_submission: boolean + contest: number + my_status: number + my_failed_count?: number + visible: boolean + + // 流程图相关字段 + allow_flowchart: boolean + mermaid_code?: string + flowchart_data?: Record + flowchart_hint?: string + show_flowchart?: boolean + ast_rules?: { + [key: string]: { + engine: string + target?: string + min?: number + max?: number + message: string + }[] + } | null + has_ast_rules?: boolean + + // SQL 题配置(非 SQL 题为 null) + sql_config?: SQLConfig | null + + // SQL 题展示数据(后端保存题目时自动生成) + sql_display?: SQLDisplay | null +} + +export type AdminProblem = Problem & + AlterProblem & { + // 后台题目列表接口附带的最高票评价,比赛题目列表不返回 + top_reaction?: { type: ReactionKey; count: number } | null + } + +interface AlterProblem { + test_case_id: string + test_case_score: Testcase[] + contest_id?: string +} + +type ExcludeKeys = + | "id" + | "created_by" + | "create_time" + | "last_update_time" + | "my_status" + | "contest" + | "statistic_info" + | "accepted_number" + | "submission_number" + +export type BlankProblem = Omit & + AlterProblem & { id?: number } + +export interface ProblemFiltered { + _id: string + id: number + title: string + difficulty: "简单" | "中等" | "困难" + tags: string[] + submission: number + rate: string + status: "not_test" | "passed" | "failed" + author: string + allow_flowchart: boolean + show_flowchart: boolean + has_ast_rules: boolean +} + +export interface AdminProblemFiltered { + _id: string + id: number + title: string + visible: boolean + username: string + create_time: string + difficulty: "Low" | "Mid" | "High" + tags: string[] + has_ast_rules: boolean + allow_flowchart: boolean + show_flowchart: boolean + // 比赛题目列表接口不返回这个字段 + top_reaction?: { type: ReactionKey; count: number } | null +} + +// 题单相关类型 +export interface ProblemSet { + id: number + title: string + description: string + created_by: SampleUser + create_time: Date + difficulty: "Easy" | "Medium" | "Hard" + status: "active" | "archived" | "draft" + end_time: Date | null + visible: boolean + problems_count: number + completed_count: number + user_progress: { + is_joined: boolean + progress_percentage: number + completed_count: number + total_count: number + is_completed: boolean + } +} + +export interface ProblemSetList { + id: number + title: string + description: string + created_by: SampleUser + create_time: Date + difficulty: "Easy" | "Medium" | "Hard" + status: "active" | "archived" | "draft" + end_time: Date | null + problems_count: number + visible: boolean + user_progress: { + is_joined: boolean + progress_percentage: number + completed_count: number + total_count: number + is_completed: boolean + } + badges: ProblemSetBadge[] +} + +export interface ProblemSetProblem { + id: number + problemset: number + problem: Problem + order: number + is_required: boolean + score: number + hint: string + is_completed: boolean +} + +export interface ProblemSetBadge { + id: number + problemset: number + name: string + description: string + icon: string + condition_type: "all_problems" | "problem_count" | "score" + condition_value: number + is_earned?: boolean +} + +export interface UserBadge { + id: number + user: number + badge: ProblemSetBadge + earned_time: Date +} + +export interface CompletedProblem { + id: number + _id: string + title: string +} + +export interface ProblemSetProgress { + id: number + problemset: ProblemSetList + user: SampleUser + join_time: Date + completed_problems_count: number + total_problems_count: number + progress_percentage: number + is_completed: boolean + completed_problems: CompletedProblem[] +} + +export interface CreateProblemSetData { + title: string + description: string + difficulty: "Easy" | "Medium" | "Hard" + status: "active" | "archived" | "draft" + end_time?: Date | null +} + +export interface EditProblemSetData { + id: number + title?: string + description?: string + difficulty?: "Easy" | "Medium" | "Hard" + status?: "active" | "archived" | "draft" + end_time?: Date | null + visible?: boolean +} + +export interface Code { + language: LANGUAGE + value: string +} + +export interface SubmitCodePayload { + problem_id: number + language: LANGUAGE + code: string + contest_id?: number +} + +// ==================== 流程图相关类型 ==================== + +export const FlowchartSubmissionStatus = { + PENDING: 0, // 等待AI评分 + PROCESSING: 1, // AI评分中 + COMPLETED: 2, // 评分完成 + FAILED: 3, // 评分失败 +} as const + +export interface FlowchartSubmission { + id: string + user: number + problem: number + mermaid_code: string + flowchart_data: Record + status: number + create_time: string + ai_score?: number + ai_grade?: string + ai_feedback?: string + ai_suggestions?: string + ai_criteria_details: Record + ai_provider?: string + ai_model?: string + processing_time?: number + evaluation_time?: string +} + +// 列表接口返回的字段(包含 username 和 problem_title) +export interface FlowchartSubmissionListItem { + id: string + create_time: string + evaluation_time: string + ai_score: number + ai_grade: Grade + ai_model: string + ai_provider: string + processing_time: number + status: number + username: string + problem_title: string + problem: string + show_link: boolean +} +export interface SubmitFlowchartPayload { + problem_id: number + mermaid_code: string + flowchart_data?: Record +} + +interface Info { + err: string | null + data: { + error: number + memory: number + output: null + result: SUBMISSION_RESULT + signal: number + cpu_time: number + exit_code: number + real_time: number + test_case: string + output_md5: string + }[] +} + +export interface Submission { + id: string + create_time: Date + user_id: number + username: string + code: string + result: SUBMISSION_RESULT + info: Info + language: LANGUAGE + shared: boolean + show_link: boolean + statistic_info: { + score?: number + err_info?: string + time_cost?: number + memory_cost?: number + ast_results?: Array<{ description: string; passed: boolean }> + } + ip: string + contest: number + problem: number // 不是 display_id + can_unshare: boolean +} + +export interface SubmissionListItem { + id: string + problem: string + problem_title: string + show_link: boolean + create_time: string + user_id: number + username: string + result: SUBMISSION_RESULT + language: LANGUAGE + shared: boolean + statistic_info: { + time_cost: number + memory_cost: number + } +} + +export interface SubmissionListPayload { + myself?: "1" | "0" + result?: string + username?: string + contest_id?: string + problem_id?: string + language: LANGUAGE | "" + today?: "1" | "0" + page: number + limit: number + offset: number +} + +export interface Rank { + id: number + user: SampleUser + acm_problems_status: { + problems: { + [key: string]: { + _id: string + status: number + } + } + contest_problems?: { + [key: string]: { + [key: string]: { + _id: string + status: number + } + } + } + } + real_name: null | string + avatar: string + blog: null + mood: null | string + github: null + school: null | string + major: null | string + language: null | string + accepted_number: number + submission_number: number +} + +export interface Contest extends BlankContest { + id: number + created_by: SampleUser + status: ContestStatus + contest_type: ContestType + create_time: string + now: string + last_update_time: string +} + +export interface BlankContest { + title: string + description: string + tag: string + start_time: string + end_time: string + password: string + visible: boolean + allowed_ip_ranges: { value: string }[] +} + +interface SubmissionInfo { + is_ac: boolean + ac_time: number + is_first_ac: boolean + error_number: number + checked?: boolean +} + +export interface ContestRank { + id: number + user: SampleUser + submission_number: number + accepted_number: number + total_time: number + submission_info: { [key: string]: SubmissionInfo } + contest: number +} + +export interface WebsiteConfig { + website_base_url: string + website_name: string + website_name_shortcut: string + website_footer: string + allow_register: boolean + submission_list_show_all: boolean + class_list: string[] & never[] + enable_maxkb: boolean +} + +export interface Server { + id: number + status: "abnormal" | "normal" + hostname: string + ip: string + judger_version: string + cpu_core: number + memory_usage: number + cpu_usage: number + last_heartbeat: Date + create_time: Date + task_number: number + service_url: string + is_disabled: boolean +} + +export interface AnnouncementEdit { + id: number + title: string + tag: string + content: string + visible: boolean + top: boolean +} + +export interface Announcement extends AnnouncementEdit { + created_by: SampleUser + create_time: Date + last_update_time: Date +} + +export interface Message { + sender: User + create_time: Date + message: string + submission: Submission +} + +export interface CreateMessage { + sender: string + recipient: string + submission: string + message: string +} + +export type ReactionKey = + | "too_easy" + | "too_hard" + | "confusing" + | "buggy" + | "learned" + | "interesting" + | "want_explain" + +export type ReactionCounts = Record + +export interface ReactionState { + mine: ReactionKey | null + counts: ReactionCounts | null +} + +export interface Tutorial { + id: number + title: string + content: string + code: string + is_public: boolean + order: number + type: "python" | "c" + created_by?: User + updated_at?: Date + created_at?: Date +} + +export interface ExerciseMcqData { + question: string + options: string[] + answer: number[] +} + +export interface ExerciseSortData { + question: string + lines: string[] +} + +export interface ExerciseFillData { + question: string + code: string +} + +export interface ExerciseMatchData { + question: string + left: string[] + right: string[] + answer: number[] +} + +export interface ExercisePredictData { + question: string + code: string + answer: string[] +} + +export interface ExerciseDebugData { + question: string + lines: string[] + answer: number[] + explanation?: string +} + +export interface ExerciseGroupData { + question: string + buckets: string[] + items: string[] + answer: number[] +} + +export type ExerciseType = + "mcq" | "sort" | "fill" | "match" | "predict" | "debug" | "group" + +export interface Exercise { + id: number + type: ExerciseType + data: + | ExerciseMcqData + | ExerciseSortData + | ExerciseFillData + | ExerciseMatchData + | ExercisePredictData + | ExerciseDebugData + | ExerciseGroupData + order: number +} + +export interface DurationData { + unit: string + index: number + start: string + end: string + grade: Grade + problem_count: number + submission_count: number +} + +export interface SolvedProblem { + problem: { + title: string + display_id: string + contest_title: string + contest_id: number + } + ac_time: string + rank: number + ac_count: number + grade: Grade + period_rank: number + period_ac_count: number + difficulty: string +} + +export interface FlowchartSummary { + problem__id: string + problem_title: string + submission_count: number + best_score: number + best_grade: string + latest_submission_time: string + avg_score: number +} + +export interface DetailsData { + start: string + end: string + grade: Grade + class_name: string + tags: { [key: string]: number } + difficulty: { [key: string]: number } + contest_count: number + solved: SolvedProblem[] + flowcharts: FlowchartSummary[] +} + +export type Grade = "S" | "A" | "B" | "C" + +// ==================== 成就相关类型 ==================== + +export type AchievementRarity = "bronze" | "silver" | "gold" | "platinum" + +export interface Achievement { + id: number + name: string + description: string + icon: string + rarity: AchievementRarity + hidden: boolean + // 隐藏成就未解锁时,后端已做掩码处理,以下四个字段为 null + metric: string | null + operator: "gte" | "lte" | null + threshold: number | null + unlocked: boolean + unlock_time: string | null + backfilled: boolean + progress: number | null + unlock_rate: number +} + +export interface AchievementRarityStat { + rarity: AchievementRarity + label: string + total: number + unlocked: number +} + +export interface PendingAchievement { + id: number + name: string + description: string + icon: string + rarity: AchievementRarity + // 弹窗队列里混着两种东西:全站成就和题单奖章。它们的 id 来自两张不同的表, + // 数值会重叠,所以去重和标记已读都必须带上 kind 一起判断。 + // pending 接口只返回成就,不带这个字段,缺省按 achievement 处理。 + kind?: "achievement" | "badge" +} + +export interface AchievementSummary { + username: string + total: number + unlocked: number + percent: number + rarity: AchievementRarityStat[] + recent: PendingAchievement[] +} diff --git a/apps/web/tests/submitButtonState.test.ts b/apps/web/tests/submitButtonState.test.ts new file mode 100644 index 0000000..d3f3735 --- /dev/null +++ b/apps/web/tests/submitButtonState.test.ts @@ -0,0 +1,51 @@ +import assert from "node:assert/strict" +import test from "node:test" +import { getSubmitButtonState } from "../src/oj/problem/components/submitButtonState.ts" + +const idleInput = { + isAuthed: true, + hasCode: true, + isFormatting: false, + isSubmitting: false, + isJudging: false, + isCooldown: false, +} + +test("shows a disabled loading state while formatting", () => { + assert.deepEqual(getSubmitButtonState({ ...idleInput, isFormatting: true }), { + disabled: true, + label: "格式化中", + icon: "eos-icons:loading", + }) +}) + +test("shows submitting immediately after formatting", () => { + assert.deepEqual(getSubmitButtonState({ ...idleInput, isSubmitting: true }), { + disabled: true, + label: "正在提交", + icon: "eos-icons:loading", + }) +}) + +test("preserves existing login, judging, cooldown, and idle states", () => { + assert.deepEqual(getSubmitButtonState({ ...idleInput, isAuthed: false }), { + disabled: true, + label: "请先登录", + icon: "ph:play-fill", + }) + assert.deepEqual(getSubmitButtonState({ ...idleInput, isJudging: true }), { + disabled: true, + label: "正在评分", + icon: "eos-icons:loading", + }) + assert.deepEqual(getSubmitButtonState({ ...idleInput, isCooldown: true }), { + disabled: true, + label: "正在冷却", + icon: "ph:lightbulb-fill", + }) + assert.deepEqual(getSubmitButtonState(idleInput), { + disabled: false, + label: "提交代码", + icon: "ph:play-fill", + }) +}) diff --git a/apps/web/tsconfig.app.json b/apps/web/tsconfig.app.json new file mode 100644 index 0000000..47335e5 --- /dev/null +++ b/apps/web/tsconfig.app.json @@ -0,0 +1,23 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + // Vite(oxc) 遵守 verbatimModuleSyntax,会保留未写 type 的纯类型导入导致构建报错; + // 关掉以让转换器自动消除类型导入(与原 Rsbuild 行为一致) + "verbatimModuleSyntax": false, + "paths": { + "utils/*": ["./src/utils/*"], + "oj/*": ["./src/oj/*"], + "admin/*": ["./src/admin/*"], + "shared/*": ["./src/shared/*"] + }, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.vue"] +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/apps/web/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..50db440 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,175 @@ +import { fileURLToPath, URL } from "node:url" +import { defineConfig, loadEnv, type Plugin } from "vite" +import vue from "@vitejs/plugin-vue" +import legacy from "@vitejs/plugin-legacy" +import AutoImport from "unplugin-auto-import/vite" +import Components from "unplugin-vue-components/vite" +import { NaiveUiResolver } from "unplugin-vue-components/resolvers" + +// 显式保留 Chrome 90 所需的运行时兼容项,避免 plugin-legacy 对每个产物执行 Babel 扫描。 +// 升级前端依赖后需要重新审计此列表。 +const polyfills = [ + "es.aggregate-error.cause", + "es.array-buffer.detached", + "es.array-buffer.transfer-to-fixed-length", + "es.array-buffer.transfer", + "es.array.at", + "es.array.find-last-index", + "es.array.push", + "es.array.to-reversed", + "es.array.to-sorted", + "es.array.to-spliced", + "es.array.with", + "es.error.cause", + "es.iterator.constructor", + "es.iterator.drop", + "es.iterator.every", + "es.iterator.filter", + "es.iterator.find", + "es.iterator.flat-map", + "es.iterator.for-each", + "es.iterator.map", + "es.iterator.reduce", + "es.iterator.some", + "es.iterator.to-array", + "es.json.parse", + "es.json.stringify", + "es.map.get-or-insert-computed", + "es.map.get-or-insert", + "es.object.has-own", + "es.regexp.flags", + "es.set.difference.v2", + "es.set.intersection.v2", + "es.set.is-disjoint-from.v2", + "es.set.is-subset-of.v2", + "es.set.is-superset-of.v2", + "es.set.symmetric-difference.v2", + "es.set.union.v2", + "es.string.at-alternative", + "es.typed-array.at", + "es.typed-array.find-last-index", + "es.typed-array.find-last", + "es.typed-array.set", + "es.typed-array.to-reversed", + "es.typed-array.to-sorted", + "es.typed-array.with", + "es.uint8-array.set-from-base64", + "es.uint8-array.set-from-hex", + "es.uint8-array.to-base64", + "es.uint8-array.to-hex", + "es.weak-map.get-or-insert-computed", + "es.weak-map.get-or-insert", + "esnext.array.group", + "web.dom-exception.stack", + "web.immediate", + "web.structured-clone", + "web.url-search-params.delete", + "web.url-search-params.has", + "web.url-search-params.size", + "web.url.can-parse", +] + +// index.html 里按需注入 MaxKB 脚本(原 Rsbuild EJS 模板的等价实现) +function injectMaxkb(maxkbUrl: string | undefined): Plugin { + return { + name: "inject-maxkb", + transformIndexHtml(html) { + if (!maxkbUrl) return html + return { + html, + tags: [ + { + tag: "script", + attrs: { async: true, defer: true, src: maxkbUrl }, + injectTo: "head", + }, + ], + } + }, + } +} + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), "PUBLIC_") + + const proxyConfig = { + target: env["PUBLIC_OJ_URL"], + changeOrigin: true, + } + + const wsProxyConfig = { + target: env["PUBLIC_WS_URL"], + ws: true, + changeOrigin: true, + } + + return { + plugins: [ + vue(), + // 机房存在 Chrome 91 等旧浏览器:不做 SystemJS 双构建, + // 只按使用情况给现代产物注入 core-js API polyfill(Array.at 等) + legacy({ + renderLegacyChunks: false, + modernTargets: "chrome>=90", + modernPolyfills: polyfills, + }), + AutoImport({ + imports: [ + "vue", + "vue-router", + "@vueuse/core", + "pinia", + { + "naive-ui": [ + "useDialog", + "useMessage", + "useNotification", + "useLoadingBar", + ], + }, + { + from: "naive-ui", + imports: [ + "DataTableColumn", + "FormRules", + "FormItemRule", + "SelectOption", + "UploadCustomRequestOptions", + "UploadFileInfo", + "MenuOption", + "DropdownDividerOption", + "DropdownOption", + ], + type: true, + }, + ], + dts: "./src/auto-imports.d.ts", + }), + Components({ + resolvers: [NaiveUiResolver()], + dts: "./src/components.d.ts", + }), + injectMaxkb(env["PUBLIC_MAXKB_URL"]), + ], + envPrefix: "PUBLIC_", + resolve: { + alias: { + // mermaid-legacy (mermaid@9) 写死了 UMD 路径,新版 cytoscape 的 exports + // 不允许 import 条件访问它,转到 ESM 产物 + "cytoscape/dist/cytoscape.umd.js": "cytoscape/dist/cytoscape.esm.mjs", + utils: fileURLToPath(new URL("./src/utils", import.meta.url)), + oj: fileURLToPath(new URL("./src/oj", import.meta.url)), + admin: fileURLToPath(new URL("./src/admin", import.meta.url)), + shared: fileURLToPath(new URL("./src/shared", import.meta.url)), + }, + }, + server: { + port: 5173, + proxy: { + "/api": proxyConfig, + "/public": proxyConfig, + "/ws": wsProxyConfig, + }, + }, + } +}) diff --git a/bun.lock b/bun.lock index f31ddda..1d518e3 100644 --- a/bun.lock +++ b/bun.lock @@ -23,6 +23,64 @@ "drizzle-kit": "^0.31.10", }, }, + "apps/web": { + "name": "@oj2/web", + "version": "1.9.0", + "dependencies": { + "@codemirror/autocomplete": "^6.20.3", + "@codemirror/lang-cpp": "^6.0.3", + "@codemirror/lang-python": "^6.2.1", + "@codemirror/lang-sql": "^6.10.0", + "@oj2/contract": "workspace:*", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.2", + "@vue-flow/minimap": "^1.5.4", + "@vue-flow/node-resizer": "^1.5.1", + "@vue-flow/node-toolbar": "^1.1.1", + "@vueuse/core": "^14.4.0", + "@vueuse/router": "^14.4.0", + "@wangeditor-next/editor": "^6.2.0", + "@wangeditor-next/editor-for-vue": "^6.2.0", + "axios": "^1.19.0", + "canvas-confetti": "^1.9.4", + "chart.js": "^4.5.1", + "chartjs-chart-wordcloud": "^4.4.5", + "codemirror": "^6.0.2", + "copy-text-to-clipboard": "^3.2.2", + "date-fns": "^4.4.0", + "fflate": "^0.8.3", + "highlight.js": "^11.11.1", + "md-editor-v3": "^6.5.6", + "mermaid": "^11.16.1", + "mermaid-legacy": "npm:mermaid@^9.4.3", + "naive-ui": "^2.44.1", + "nanoid": "^6.0.1", + "normalize.css": "^8.0.1", + "pinia": "^4.0.2", + "skulpt": "^1.2.0", + "vue": "^3.5.41", + "vue-chartjs": "^5.3.4", + "vue-codemirror": "^6.1.1", + "vue-router": "^5.2.0", + "y-codemirror.next": "^0.3.5", + "y-webrtc": "^10.3.0", + "yjs": "^13.6.32", + }, + "devDependencies": { + "@iconify/vue": "^5.0.1", + "@types/canvas-confetti": "^1.9.0", + "@types/node": "^26.1.2", + "@vitejs/plugin-legacy": "^8.2.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "prettier": "^3.9.6", + "typescript": "^7.0.2", + "unplugin-auto-import": "^21.1.0", + "unplugin-vue-components": "^32.1.0", + "vite": "^8.2.1", + }, + }, "packages/contract": { "name": "@oj2/contract", "version": "0.0.0", @@ -32,202 +90,1438 @@ }, }, "packages": { - "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], - "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], - "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + "@babel/generator": ["@babel/generator@8.0.0", "", { "dependencies": { "@babel/parser": "^8.0.0", "@babel/types": "^8.0.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "@types/jsesc": "^2.5.0", "jsesc": "^3.0.2" } }, "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-wrap-function": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.29.7", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ["@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + "@babel/plugin-bugfix-safari-class-field-initializer-scope": ["@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": ["@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA=="], + + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ["@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.13.0" } }, "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw=="], + + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ["@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw=="], + + "@babel/plugin-proposal-private-property-in-object": ["@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2", "", { "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w=="], + + "@babel/plugin-syntax-import-assertions": ["@babel/plugin-syntax-import-assertions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw=="], + + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg=="], + + "@babel/plugin-syntax-unicode-sets-regex": ["@babel/plugin-syntax-unicode-sets-regex@7.18.6", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg=="], + + "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ=="], + + "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA=="], + + "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-remap-async-to-generator": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w=="], + + "@babel/plugin-transform-block-scoped-functions": ["@babel/plugin-transform-block-scoped-functions@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA=="], + + "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ=="], + + "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA=="], + + "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A=="], + + "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g=="], + + "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/template": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA=="], + + "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg=="], + + "@babel/plugin-transform-dotall-regex": ["@babel/plugin-transform-dotall-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ=="], + + "@babel/plugin-transform-duplicate-keys": ["@babel/plugin-transform-duplicate-keys@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ=="], + + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ["@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA=="], + + "@babel/plugin-transform-dynamic-import": ["@babel/plugin-transform-dynamic-import@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg=="], + + "@babel/plugin-transform-explicit-resource-management": ["@babel/plugin-transform-explicit-resource-management@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw=="], + + "@babel/plugin-transform-exponentiation-operator": ["@babel/plugin-transform-exponentiation-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ=="], + + "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA=="], + + "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ=="], + + "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg=="], + + "@babel/plugin-transform-json-strings": ["@babel/plugin-transform-json-strings@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg=="], + + "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw=="], + + "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q=="], + + "@babel/plugin-transform-member-expression-literals": ["@babel/plugin-transform-member-expression-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg=="], + + "@babel/plugin-transform-modules-amd": ["@babel/plugin-transform-modules-amd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew=="], + + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ=="], + + "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.8", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.8" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA=="], + + "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.29.7", "", { "dependencies": { "@babel/helper-module-transforms": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA=="], + + "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ=="], + + "@babel/plugin-transform-new-target": ["@babel/plugin-transform-new-target@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A=="], + + "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg=="], + + "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw=="], + + "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.29.7", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A=="], + + "@babel/plugin-transform-object-super": ["@babel/plugin-transform-object-super@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA=="], + + "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng=="], + + "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ=="], + + "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g=="], + + "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.29.7", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug=="], + + "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-create-class-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA=="], + + "@babel/plugin-transform-property-literals": ["@babel/plugin-transform-property-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA=="], + + "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg=="], + + "@babel/plugin-transform-regexp-modifiers": ["@babel/plugin-transform-regexp-modifiers@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ=="], + + "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA=="], + + "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg=="], + + "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.29.8", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA=="], + + "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA=="], + + "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA=="], + + "@babel/plugin-transform-typeof-symbol": ["@babel/plugin-transform-typeof-symbol@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A=="], + + "@babel/plugin-transform-unicode-escapes": ["@babel/plugin-transform-unicode-escapes@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA=="], + + "@babel/plugin-transform-unicode-property-regex": ["@babel/plugin-transform-unicode-property-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw=="], + + "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA=="], + + "@babel/plugin-transform-unicode-sets-regex": ["@babel/plugin-transform-unicode-sets-regex@7.29.7", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg=="], + + "@babel/preset-env": ["@babel/preset-env@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.29.7", "@babel/plugin-syntax-import-attributes": "^7.29.7", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.29.7", "@babel/plugin-transform-async-generator-functions": "^7.29.7", "@babel/plugin-transform-async-to-generator": "^7.29.7", "@babel/plugin-transform-block-scoped-functions": "^7.29.7", "@babel/plugin-transform-block-scoping": "^7.29.7", "@babel/plugin-transform-class-properties": "^7.29.7", "@babel/plugin-transform-class-static-block": "^7.29.7", "@babel/plugin-transform-classes": "^7.29.7", "@babel/plugin-transform-computed-properties": "^7.29.7", "@babel/plugin-transform-destructuring": "^7.29.7", "@babel/plugin-transform-dotall-regex": "^7.29.7", "@babel/plugin-transform-duplicate-keys": "^7.29.7", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-dynamic-import": "^7.29.7", "@babel/plugin-transform-explicit-resource-management": "^7.29.7", "@babel/plugin-transform-exponentiation-operator": "^7.29.7", "@babel/plugin-transform-export-namespace-from": "^7.29.7", "@babel/plugin-transform-for-of": "^7.29.7", "@babel/plugin-transform-function-name": "^7.29.7", "@babel/plugin-transform-json-strings": "^7.29.7", "@babel/plugin-transform-literals": "^7.29.7", "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", "@babel/plugin-transform-member-expression-literals": "^7.29.7", "@babel/plugin-transform-modules-amd": "^7.29.7", "@babel/plugin-transform-modules-commonjs": "^7.29.7", "@babel/plugin-transform-modules-systemjs": "^7.29.7", "@babel/plugin-transform-modules-umd": "^7.29.7", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", "@babel/plugin-transform-new-target": "^7.29.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", "@babel/plugin-transform-numeric-separator": "^7.29.7", "@babel/plugin-transform-object-rest-spread": "^7.29.7", "@babel/plugin-transform-object-super": "^7.29.7", "@babel/plugin-transform-optional-catch-binding": "^7.29.7", "@babel/plugin-transform-optional-chaining": "^7.29.7", "@babel/plugin-transform-parameters": "^7.29.7", "@babel/plugin-transform-private-methods": "^7.29.7", "@babel/plugin-transform-private-property-in-object": "^7.29.7", "@babel/plugin-transform-property-literals": "^7.29.7", "@babel/plugin-transform-regenerator": "^7.29.7", "@babel/plugin-transform-regexp-modifiers": "^7.29.7", "@babel/plugin-transform-reserved-words": "^7.29.7", "@babel/plugin-transform-shorthand-properties": "^7.29.7", "@babel/plugin-transform-spread": "^7.29.7", "@babel/plugin-transform-sticky-regex": "^7.29.7", "@babel/plugin-transform-template-literals": "^7.29.7", "@babel/plugin-transform-typeof-symbol": "^7.29.7", "@babel/plugin-transform-unicode-escapes": "^7.29.7", "@babel/plugin-transform-unicode-property-regex": "^7.29.7", "@babel/plugin-transform-unicode-regex": "^7.29.7", "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA=="], + + "@babel/preset-modules": ["@babel/preset-modules@0.1.6-no-external-plugins", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", "esutils": "^2.0.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], + + "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], + + "@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="], + + "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g=="], + + "@codemirror/commands": ["@codemirror/commands@6.10.4", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.7.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg=="], + + "@codemirror/lang-angular": ["@codemirror/lang-angular@0.1.4", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-javascript": "^6.1.2", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.3" } }, "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g=="], + + "@codemirror/lang-cpp": ["@codemirror/lang-cpp@6.0.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/cpp": "^1.0.0" } }, "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA=="], + + "@codemirror/lang-css": ["@codemirror/lang-css@6.3.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/css": "^1.1.7" } }, "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg=="], + + "@codemirror/lang-go": ["@codemirror/lang-go@6.0.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/go": "^1.0.0" } }, "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg=="], + + "@codemirror/lang-html": ["@codemirror/lang-html@6.4.12", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/css": "^1.1.0", "@lezer/html": "^1.3.12" } }, "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w=="], + + "@codemirror/lang-java": ["@codemirror/lang-java@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/java": "^1.0.0" } }, "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ=="], + + "@codemirror/lang-javascript": ["@codemirror/lang-javascript@6.2.5", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/javascript": "^1.0.0" } }, "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A=="], + + "@codemirror/lang-jinja": ["@codemirror/lang-jinja@6.0.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.4.0" } }, "sha512-P5kyHLObzjtbGj16h+hyvZTxJhSjBEeSx4wMjbnAf3b0uwTy2+F0zGjMZL4PQOm/mh2eGZ5xUDVZXgwP783Nsw=="], + + "@codemirror/lang-json": ["@codemirror/lang-json@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ=="], + + "@codemirror/lang-less": ["@codemirror/lang-less@6.0.2", "", { "dependencies": { "@codemirror/lang-css": "^6.2.0", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ=="], + + "@codemirror/lang-liquid": ["@codemirror/lang-liquid@6.3.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.1" } }, "sha512-6PDVU3ZnfeYyz1at1E/ttorErZvZFXXt1OPhtfe1EZJ2V2iDFa0CwPqPgG5F7NXN0yONGoBogKmFAafKTqlwIw=="], + + "@codemirror/lang-markdown": ["@codemirror/lang-markdown@6.5.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.7.1", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.3.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.2.1", "@lezer/markdown": "^1.0.0" } }, "sha512-AwBOdkWYuA//WcM0xO5PfHPUcmz/O2i5o0Nsg1U69SII/loCJlFI1Romd9xp2HYb1kYJRGZotyqRghuHH5n8Kw=="], + + "@codemirror/lang-php": ["@codemirror/lang-php@6.0.2", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/php": "^1.0.0" } }, "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA=="], + + "@codemirror/lang-python": ["@codemirror/lang-python@6.2.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.3.2", "@codemirror/language": "^6.8.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.1", "@lezer/python": "^1.1.4" } }, "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw=="], + + "@codemirror/lang-rust": ["@codemirror/lang-rust@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/rust": "^1.0.0" } }, "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA=="], + + "@codemirror/lang-sass": ["@codemirror/lang-sass@6.0.2", "", { "dependencies": { "@codemirror/lang-css": "^6.2.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/sass": "^1.0.0" } }, "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q=="], + + "@codemirror/lang-sql": ["@codemirror/lang-sql@6.10.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w=="], + + "@codemirror/lang-vue": ["@codemirror/lang-vue@0.1.3", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-javascript": "^6.1.2", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.1" } }, "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug=="], + + "@codemirror/lang-wast": ["@codemirror/lang-wast@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q=="], + + "@codemirror/lang-xml": ["@codemirror/lang-xml@6.1.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/xml": "^1.0.0" } }, "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg=="], + + "@codemirror/lang-yaml": ["@codemirror/lang-yaml@6.1.3", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.0.0", "@lezer/yaml": "^1.0.0" } }, "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ=="], + + "@codemirror/language": ["@codemirror/language@6.12.4", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A=="], + + "@codemirror/language-data": ["@codemirror/language-data@6.5.2", "", { "dependencies": { "@codemirror/lang-angular": "^0.1.0", "@codemirror/lang-cpp": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-go": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-java": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/lang-jinja": "^6.0.0", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-less": "^6.0.0", "@codemirror/lang-liquid": "^6.0.0", "@codemirror/lang-markdown": "^6.0.0", "@codemirror/lang-php": "^6.0.0", "@codemirror/lang-python": "^6.0.0", "@codemirror/lang-rust": "^6.0.0", "@codemirror/lang-sass": "^6.0.0", "@codemirror/lang-sql": "^6.0.0", "@codemirror/lang-vue": "^0.1.1", "@codemirror/lang-wast": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.4.0" } }, "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg=="], + + "@codemirror/legacy-modes": ["@codemirror/legacy-modes@6.5.3", "", { "dependencies": { "@codemirror/language": "^6.0.0" } }, "sha512-xCsmIzH78MyWkib9jlPaaun57XNkfbMIhagfaZVd0iLTqlpw3jXaIcbZm72MTmmn64eTZpBVNjbyYh+QXnxRsg=="], + + "@codemirror/lint": ["@codemirror/lint@6.9.7", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.42.0", "crelt": "^1.0.5" } }, "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg=="], + + "@codemirror/search": ["@codemirror/search@6.7.1", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA=="], + + "@codemirror/state": ["@codemirror/state@6.7.1", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A=="], + + "@codemirror/view": ["@codemirror/view@6.43.8", "", { "dependencies": { "@codemirror/state": "^6.7.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-qtItTDssZ/5GFfi94hrILu9j/VUeFPDPkhovEfmWFj2ipTxnzPB8DdHgfbb8HYTzLTYhrndKmyQxXUz/PDLenw=="], + + "@css-render/plugin-bem": ["@css-render/plugin-bem@0.15.14", "", { "peerDependencies": { "css-render": "~0.15.14" } }, "sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg=="], + + "@css-render/vue3-ssr": ["@css-render/vue3-ssr@0.15.14", "", { "peerDependencies": { "vue": "^3.0.11" } }, "sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g=="], + + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "https://registry.npmjs.com/@drizzle-team/brocli/-/brocli-0.10.2.tgz", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + + "@emotion/hash": ["@emotion/hash@0.8.0", "", {}, "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "https://registry.npmjs.com/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "https://registry.npmjs.com/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "https://registry.npmjs.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "https://registry.npmjs.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "https://registry.npmjs.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "https://registry.npmjs.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "https://registry.npmjs.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "https://registry.npmjs.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "https://registry.npmjs.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "https://registry.npmjs.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "https://registry.npmjs.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "https://registry.npmjs.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "https://registry.npmjs.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "https://registry.npmjs.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "https://registry.npmjs.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "https://registry.npmjs.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "https://registry.npmjs.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "https://registry.npmjs.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.1.4", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw=="], + + "@iconify/vue": ["@iconify/vue@5.0.1", "", { "dependencies": { "@iconify/types": "^2.0.0" }, "peerDependencies": { "vue": ">=3.0.0" } }, "sha512-aumwwooJlFJ5H5qYWB6ZTAyM0C8hpfcSVLB9/a3qnH1GGvIJ+FEbpEs4s/HfErYe/M5qZeLjwmESR5fFm3lXEw=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@juggle/resize-observer": ["@juggle/resize-observer@3.4.0", "", {}, "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="], + + "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="], + + "@lezer/common": ["@lezer/common@1.5.2", "", {}, "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ=="], + + "@lezer/cpp": ["@lezer/cpp@1.1.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-vh9gWWJOXFVY8HBHK3Twzq8MgwG2iN4GSyzBP9sCGTe37P15x2R14VaBQk0VA0ezTRN1KHYBBsHhvpGZ2Xy/pA=="], + + "@lezer/css": ["@lezer/css@1.3.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-YJE78Wcg+zX8f10hiHWQ4Az48Qr/c13eId0VtRQYLBpxHDmDeSrXIlkbl+fJGW42rWC/uoUco9mhBZeVWP/A1g=="], + + "@lezer/go": ["@lezer/go@1.0.1", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ=="], + + "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="], + + "@lezer/html": ["@lezer/html@1.3.13", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg=="], + + "@lezer/java": ["@lezer/java@1.1.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw=="], + + "@lezer/javascript": ["@lezer/javascript@1.5.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.1.3", "@lezer/lr": "^1.3.0" } }, "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA=="], + + "@lezer/json": ["@lezer/json@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ=="], + + "@lezer/lr": ["@lezer/lr@1.4.10", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A=="], + + "@lezer/markdown": ["@lezer/markdown@1.7.2", "", { "dependencies": { "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0" } }, "sha512-iTkYvoVcKt3WkeL7qUDyXHONZEwLio4wj8KTNi2dnjQEXBZKMV63BpQrPqfsM+OkvuRbiSTAcycYAsQzLhRNoQ=="], + + "@lezer/php": ["@lezer/php@1.0.5", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.1.0" } }, "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA=="], + + "@lezer/python": ["@lezer/python@1.1.19", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ=="], + + "@lezer/rust": ["@lezer/rust@1.0.2", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg=="], + + "@lezer/sass": ["@lezer/sass@1.1.0", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ=="], + + "@lezer/xml": ["@lezer/xml@1.0.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww=="], + + "@lezer/yaml": ["@lezer/yaml@1.0.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.4.0" } }, "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw=="], + + "@lucide/vue": ["@lucide/vue@1.29.0", "", { "peerDependencies": { "vue": ">=3.0.1" } }, "sha512-UnbPPAdxWGqABrjcQJZFqawSgZTBmeKOrNFPI9sqKaDkgLoEbYyx+ivIdiYRR1YoYhWWg7dCdvjtCwK6zBxWDQ=="], + + "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.3", "", {}, "sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA=="], + + "@mermaid-js/parser": ["@mermaid-js/parser@1.2.0", "", { "dependencies": { "@chevrotain/types": "~11.1.2" } }, "sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA=="], "@oj2/api": ["@oj2/api@workspace:apps/api"], "@oj2/contract": ["@oj2/contract@workspace:packages/contract"], + "@oj2/web": ["@oj2/web@workspace:apps/web"], + + "@oxc-project/types": ["@oxc-project/types@0.143.0", "", {}, "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.3", "", { "os": "android", "cpu": "arm64" }, "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.3", "", { "os": "none", "cpu": "arm64" }, "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.3", "", { "os": "win32", "cpu": "x64" }, "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@transloadit/prettier-bytes": ["@transloadit/prettier-bytes@0.3.5", "", {}, "sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA=="], + "@types/bun": ["@types/bun@1.3.14", "https://registry.npmjs.com/@types/bun/-/bun-1.3.14.tgz", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + "@types/canvas-confetti": ["@types/canvas-confetti@1.9.0", "", {}, "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg=="], + + "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], + + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="], + + "@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="], + + "@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="], + + "@types/d3-cloud": ["@types/d3-cloud@1.2.9", "", { "dependencies": { "@types/d3": "^3" } }, "sha512-5EWJvnlCrqTThGp8lYHx+DL00sOjx2HTlXH1WRe93k5pfOIhPQaL63NttaKYIbT7bTXp/USiunjNS/N4ipttIQ=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], + + "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], + + "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="], + + "@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="], + + "@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], + + "@types/d3-geo": ["@types/d3-geo@3.1.1", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w=="], + + "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="], + + "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="], + + "@types/d3-random": ["@types/d3-random@3.0.4", "", {}, "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/event-emitter": ["@types/event-emitter@0.3.5", "", {}, "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ=="], + + "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], + + "@types/jsesc": ["@types/jsesc@2.5.1", "", {}, "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw=="], + + "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], + + "@types/lodash": ["@types/lodash@4.17.25", "", {}, "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ=="], + + "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="], + + "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], + + "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], + "@types/node": ["@types/node@26.1.2", "https://registry.npmjs.com/@types/node/-/node-26.1.2.tgz", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], - "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + + "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="], + + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + + "@uppy/companion-client": ["@uppy/companion-client@5.1.1", "", { "dependencies": { "@uppy/utils": "^7.1.1", "namespace-emitter": "^2.0.1", "p-retry": "^6.1.0" }, "peerDependencies": { "@uppy/core": "^5.1.1" } }, "sha512-DzrOWTbIZHvtgAFXBMYHk2wD27NjpBSVhY2tEiEIUhPd2CxbFRZjHM/N3HOt3VwZEAP471QWFLlJRWPcIY3A2Q=="], + + "@uppy/core": ["@uppy/core@5.2.0", "", { "dependencies": { "@transloadit/prettier-bytes": "^0.3.4", "@uppy/store-default": "^5.0.0", "@uppy/utils": "^7.1.4", "lodash": "^4.17.21", "mime-match": "^1.0.2", "namespace-emitter": "^2.0.1", "nanoid": "^5.0.9", "preact": "^10.5.13" } }, "sha512-uvfNyz4cnaplt7LYJmEZHuqOuav0tKp4a9WKJIaH6iIj7XiqYvS2J5SEByexAlUFlzefOAyjzj4Ja2dd/8aMrw=="], + + "@uppy/store-default": ["@uppy/store-default@5.0.0", "", {}, "sha512-hQtCSQ1yGiaval/wVYUWquYGDJ+bpQ7e4FhUUAsRQz1x1K+o7NBtjfp63O9I4Ks1WRoKunpkarZ+as09l02cPw=="], + + "@uppy/utils": ["@uppy/utils@7.2.0", "", { "dependencies": { "lodash": "^4.17.23", "preact": "^10.26.10" } }, "sha512-6lC246qszMv6bTyl/+QyHwrudgeguWkA94ME1wHn+a6uRAvmtAEaUManIfGqTJfoKvWAiCJqdJPl5xRJjhAloQ=="], + + "@uppy/xhr-upload": ["@uppy/xhr-upload@5.2.0", "", { "dependencies": { "@uppy/companion-client": "^5.1.1", "@uppy/utils": "^7.2.0" }, "peerDependencies": { "@uppy/core": "^5.2.0" } }, "sha512-3LV/X5Of6BINnKplP+CwUJ0a4/7cRFfzxwGyXnW+uCrNQHoo09dttcz3begWHejGvzenQHuUnMO3Fxyc71Pryg=="], + + "@upsetjs/venn.js": ["@upsetjs/venn.js@2.0.0", "", { "optionalDependencies": { "d3-selection": "^3.0.0", "d3-transition": "^3.0.1" } }, "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw=="], + + "@vavt/copy2clipboard": ["@vavt/copy2clipboard@1.0.3", "", {}, "sha512-HtG48r2FBYp9eRvGB3QGmtRBH1zzRRAVvFbGgFstOwz4/DDaNiX0uZc3YVKPydqgOav26pibr9MtoCaWxn7aeA=="], + + "@vavt/markdown-theme": ["@vavt/markdown-theme@5.0.2", "", {}, "sha512-cGYEV2VIUmhT9p8Wua9yXh7sKN1f3cbztTFHlkYyH9SzbpiKUnQrcbDts/0fP6cmvZPOe9SWWsDujmyuHepJdQ=="], + + "@vavt/util": ["@vavt/util@2.1.2", "", {}, "sha512-L3UbSJthJwr3wq0x93O5TrCepimrmVZaIl2ciZbeL18G5++gBhJXNhcH7RcVk/6rr3SavWOvwhig0mqRLoR7dw=="], + + "@vitejs/plugin-legacy": ["@vitejs/plugin-legacy@8.2.3", "", { "dependencies": { "@babel/core": "^7.29.7", "@babel/plugin-transform-dynamic-import": "^7.29.7", "@babel/plugin-transform-modules-systemjs": "^7.29.8", "@babel/preset-env": "^7.29.7", "babel-plugin-polyfill-corejs3": "^0.14.2", "babel-plugin-polyfill-regenerator": "^0.6.8", "browserslist": "^4.28.7", "browserslist-to-esbuild": "^2.1.1", "core-js": "^3.49.0", "magic-string": "^1.1.0", "regenerator-runtime": "^0.14.1", "systemjs": "^6.15.1" }, "peerDependencies": { "terser": "^5.16.0", "vite": "^8.0.0" } }, "sha512-MMHLsn/0FrBkCQyb1x44cPYGwkfqcGr2FblcEP2ne79aXe7d5tFKoZ09xs5C4wuh8fOu2JRaNhgAS3K3uvUeJA=="], + + "@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.8", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vue": "^3.2.25" } }, "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew=="], + + "@vue-flow/background": ["@vue-flow/background@1.3.2", "", { "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w=="], + + "@vue-flow/controls": ["@vue-flow/controls@1.1.3", "", { "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw=="], + + "@vue-flow/core": ["@vue-flow/core@1.48.2", "", { "dependencies": { "@vueuse/core": "^10.5.0", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" }, "peerDependencies": { "vue": "^3.3.0" } }, "sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA=="], + + "@vue-flow/minimap": ["@vue-flow/minimap@1.5.4", "", { "dependencies": { "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" }, "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g=="], + + "@vue-flow/node-resizer": ["@vue-flow/node-resizer@1.5.1", "", { "dependencies": { "d3-drag": "^3.0.0", "d3-selection": "^3.0.0" }, "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-DNYgXTF39GXPAygVMFmgn5azgFztaWfqbWvv8D/X0HGgeunRrexJ0W94DyXH1rnndGyYAJ0KexEjyelsb+Nlbg=="], + + "@vue-flow/node-toolbar": ["@vue-flow/node-toolbar@1.1.1", "", { "peerDependencies": { "@vue-flow/core": "^1.23.0", "vue": "^3.3.0" } }, "sha512-vgSnGMLd3FXstdpvC721qcBDzGkPsudmyA8urEP55/EMikCp59klgzPvVULTDxxsew5MdXtTBCumsqOi+PXJdg=="], + + "@vue-macros/common": ["@vue-macros/common@3.1.4", "", { "dependencies": { "@vue/compiler-sfc": "^3.5.22", "ast-kit": "^2.1.2", "local-pkg": "^1.1.2", "magic-string-ast": "^1.0.2", "unplugin-utils": "^0.3.0" }, "peerDependencies": { "vue": "^2.7.0 || ^3.2.25" }, "optionalPeers": ["vue"] }, "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw=="], + + "@vue/compiler-core": ["@vue/compiler-core@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg=="], + + "@vue/compiler-dom": ["@vue/compiler-dom@3.5.41", "", { "dependencies": { "@vue/compiler-core": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw=="], + + "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.41", "", { "dependencies": { "@babel/parser": "^7.29.8", "@vue/compiler-core": "3.5.41", "@vue/compiler-dom": "3.5.41", "@vue/compiler-ssr": "3.5.41", "@vue/shared": "3.5.41", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ=="], + + "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A=="], + + "@vue/devtools-api": ["@vue/devtools-api@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1" } }, "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A=="], + + "@vue/devtools-kit": ["@vue/devtools-kit@8.2.1", "", { "dependencies": { "@vue/devtools-shared": "^8.2.1", "birpc": "^2.6.1", "hookable": "^5.5.3", "perfect-debounce": "^2.0.0" } }, "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ=="], + + "@vue/devtools-shared": ["@vue/devtools-shared@8.2.1", "", {}, "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g=="], + + "@vue/reactivity": ["@vue/reactivity@3.5.41", "", { "dependencies": { "@vue/shared": "3.5.41" } }, "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA=="], + + "@vue/runtime-core": ["@vue/runtime-core@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg=="], + + "@vue/runtime-dom": ["@vue/runtime-dom@3.5.41", "", { "dependencies": { "@vue/reactivity": "3.5.41", "@vue/runtime-core": "3.5.41", "@vue/shared": "3.5.41", "csstype": "^3.2.3" } }, "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw=="], + + "@vue/server-renderer": ["@vue/server-renderer@3.5.41", "", { "dependencies": { "@vue/compiler-ssr": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/shared": "3.5.41" } }, "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ=="], + + "@vue/shared": ["@vue/shared@3.5.41", "", {}, "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA=="], + + "@vue/tsconfig": ["@vue/tsconfig@0.9.1", "", { "peerDependencies": { "typescript": ">= 5.8", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w=="], + + "@vueuse/core": ["@vueuse/core@14.4.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.4.0", "@vueuse/shared": "14.4.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ=="], + + "@vueuse/metadata": ["@vueuse/metadata@14.4.0", "", {}, "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g=="], + + "@vueuse/router": ["@vueuse/router@14.4.0", "", { "dependencies": { "@vueuse/shared": "14.4.0" }, "peerDependencies": { "vue": "^3.5.0", "vue-router": "^4.0.0 || ^5.0.0" } }, "sha512-q+dRUKI6xYGT4vNPq2t3fujrXPw2KBFV5Jxhw38liTFzwzGXBYcf8Qdjk7AiQpjEH2xLSin6WVTvj85EEIpo9w=="], + + "@vueuse/shared": ["@vueuse/shared@14.4.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g=="], + + "@wangeditor-next/basic-modules": ["@wangeditor-next/basic-modules@6.2.0", "", { "dependencies": { "is-url": "^1.2.4" }, "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "lodash.throttle": "^4.1.1", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-TZF9qtiMq1go5gRMTFicq4mQmaBYsO6QOCe3Z8/A7DHbDDBHNvFKe5BnXhRBBS3kp3ukQK5ZIsdeR8kBrtWY7A=="], + + "@wangeditor-next/code-highlight": ["@wangeditor-next/code-highlight@6.2.0", "", { "dependencies": { "prismjs": "^1.23.0" }, "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-7vWVazX+1uc72AG+bH/OS5UwGTYc0xLgErVEv/UbANnxP1d7eje67X2XY+tS7UH9UmirySmZoTKFtyduJSRCyg=="], + + "@wangeditor-next/core": ["@wangeditor-next/core@6.2.0", "", { "dependencies": { "@types/event-emitter": "^0.3.3", "event-emitter": "^0.3.5", "html-void-elements": "^3.0.0", "i18next": "^23.0.0", "scroll-into-view-if-needed": "^3.0.0", "slate-history": "^0.115.0" }, "peerDependencies": { "@uppy/core": "^2.1.1 || ^5.0.0", "@uppy/xhr-upload": "^2.0.3 || ^5.0.0", "dom7": "^3.0.0 || ^4.0.0", "is-hotkey": "^0.2.0", "lodash.camelcase": "^4.3.0", "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", "lodash.foreach": "^4.5.0", "lodash.throttle": "^4.1.1", "lodash.toarray": "^4.4.0", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-uHPgMUGJGMhV9LUhH9ZgjbfmTGowU/3G9KFHT+PdPvM7CDhdIq1gBGVbGapEWS1nLlfbx9JeOcDhFq1yBKwmaw=="], + + "@wangeditor-next/editor": ["@wangeditor-next/editor@6.2.0", "", { "dependencies": { "@uppy/core": "^5.0.0", "@uppy/xhr-upload": "^5.0.0", "@wangeditor-next/basic-modules": "^6.2.0", "@wangeditor-next/code-highlight": "^6.2.0", "@wangeditor-next/core": "^6.2.0", "@wangeditor-next/list-module": "^6.2.0", "@wangeditor-next/table-module": "^6.2.0", "@wangeditor-next/upload-image-module": "^6.2.0", "@wangeditor-next/video-module": "^6.2.0", "dom7": "^4.0.0", "is-hotkey": "^0.2.0", "lodash.camelcase": "^4.3.0", "lodash.clonedeep": "^4.5.0", "lodash.debounce": "^4.0.8", "lodash.foreach": "^4.5.0", "lodash.throttle": "^4.1.1", "lodash.toarray": "^4.4.0", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-8x/JdD9NWAw/gd+LPtnEEVU8ef2y2/Db7DGpVnYENITyvet1tjcObBZxyG5edCe6KACgseaqklhVnpfG8xayJA=="], + + "@wangeditor-next/editor-for-vue": ["@wangeditor-next/editor-for-vue@6.2.0", "", { "peerDependencies": { "@wangeditor-next/editor": "^6.2.0", "vue": "^3.0.5" } }, "sha512-c8QDYnioeaySIIsSdRwM9GL+HyvbTuy2WyaxfvSf+GHoYGe3Hi5NeA8AL8OQnJIJRSkkU0dS7CZlnjnWMD+7AA=="], + + "@wangeditor-next/list-module": ["@wangeditor-next/list-module@6.2.0", "", { "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-rmefXe7HYY0o+treOWdhq9xDLilcoQJzBZ33V1f9vp6mvlBNXvpdPfFwnNK3TrFVtUF6ELtiGA4dFe/CeajiQQ=="], + + "@wangeditor-next/table-module": ["@wangeditor-next/table-module@6.2.0", "", { "peerDependencies": { "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-XAipfwV5/aYwwxQaz4Ty1EZbyOr9ssYq8NL7oqzUrQB6Atpt2MZLeS88+npEhu8V5xntqunvijIx1ohl8/uRCg=="], + + "@wangeditor-next/upload-image-module": ["@wangeditor-next/upload-image-module@6.2.0", "", { "peerDependencies": { "@uppy/core": "^2.0.3 || ^5.0.0", "@uppy/xhr-upload": "^2.0.3 || ^5.0.0", "@wangeditor-next/basic-modules": "^6.2.0", "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "lodash.foreach": "^4.5.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-w65xUavqDi62kIwItnyKBjmMeBrUCKZkerACQKNU7MeLj1w71q7IPGmvi5It6dwA4Pk64sl+l0eKCsLI6dLRFA=="], + + "@wangeditor-next/video-module": ["@wangeditor-next/video-module@6.2.0", "", { "peerDependencies": { "@uppy/core": "^2.1.4 || ^5.0.0", "@uppy/xhr-upload": "^2.0.7 || ^5.0.0", "@wangeditor-next/core": "^6.2.0", "dom7": "^3.0.0 || ^4.0.0", "nanoid": "^5.0.0", "slate": "^0.124.0", "snabbdom": "^3.6.0" } }, "sha512-4SDqjHb+VwOSmyV+XlCBrR9SLWU0svnN7TnfDmgZHO/wUGxK6uVY0biKsn6GDpQjGL77XqiQNE/PK8YT4noY2Q=="], + + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="], + + "ast-walker-scope": ["ast-walker-scope@0.9.0", "", { "dependencies": { "@babel/parser": "^7.29.2", "@babel/types": "^7.29.0", "ast-kit": "^2.2.0" } }, "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A=="], + + "async-validator": ["async-validator@4.2.5", "", {}, "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.19.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw=="], + + "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="], + + "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.14.2", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g=="], + + "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.12", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA=="], + + "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], + + "browserslist": ["browserslist@4.28.7", "", { "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw=="], + + "browserslist-to-esbuild": ["browserslist-to-esbuild@2.1.1", "", { "dependencies": { "meow": "^13.0.0" }, "peerDependencies": { "browserslist": "*" }, "bin": { "browserslist-to-esbuild": "cli/index.js" } }, "sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-from": ["buffer-from@1.1.2", "https://registry.npmjs.com/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "bun-types": ["bun-types@1.3.14", "https://registry.npmjs.com/bun-types/-/bun-types-1.3.14.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], - "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - "drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], + "caniuse-lite": ["caniuse-lite@1.0.30001807", "", {}, "sha512-daRXJ9EB/rdRgu7kV+TTl1YUKtlsMWblPl2sLnpg9DZae16QCegol6A1SmCE31Lm9mXC1sRWGt/krouH+/dl7Q=="], - "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "canvas-confetti": ["canvas-confetti@1.9.4", "", {}, "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw=="], - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="], - "get-tsconfig": ["get-tsconfig@4.14.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A=="], + "chartjs-chart-wordcloud": ["chartjs-chart-wordcloud@4.4.5", "", { "dependencies": { "@types/d3-cloud": "^1.2.9", "d3-cloud": "^1.2.7" }, "peerDependencies": { "chart.js": "^4.1.0" } }, "sha512-x7gdE5BZyj31+bjHbZ/0tX4hB6un7TQhvwdO5qHhqpmJCS6bONHjMuDzSjL/Qw2uI6CT2/U04LGqBREHwBiK3g=="], - "hono": ["hono@4.13.0", "", {}, "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ=="], + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], - "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], + "codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="], - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], - "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], - "tsx": ["tsx@4.23.9", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-6q8uTORRGauQVjqMQnKUucLFoeXZAfw6zKvG35GLbdKWbLdeOtZ3H4mhyA5mxuUd2o2cRTskhj59nLLQseUvUw=="], + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "copy-text-to-clipboard": ["copy-text-to-clipboard@3.2.2", "", {}, "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A=="], + + "core-js": ["core-js@3.50.0", "", {}, "sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw=="], + + "core-js-compat": ["core-js-compat@3.50.0", "", { "dependencies": { "browserslist": "^4.28.7" } }, "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q=="], + + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + + "crelt": ["crelt@1.0.7", "", {}, "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA=="], + + "css-render": ["css-render@0.15.14", "", { "dependencies": { "@emotion/hash": "~0.8.0", "csstype": "~3.0.5" } }, "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg=="], + + "cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "cytoscape": ["cytoscape@3.34.0", "", {}, "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg=="], + + "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], + + "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="], + + "d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="], + + "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], + + "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], + + "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], + + "d3-cloud": ["d3-cloud@1.2.9", "", { "dependencies": { "d3-dispatch": "^1.0.3" } }, "sha512-leL1GLneC9ZQtnV+6TGWrNlGfI1WX7S2arcTv2vae12DaXo5wjm6GBCkskXbrDlyOymd/A75Pyj1H37MW4BZ/Q=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], + + "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], + + "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "dagre-d3-es": ["dagre-d3-es@7.0.14", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg=="], + + "date-fns": ["date-fns@4.4.0", "", {}, "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w=="], + + "date-fns-tz": ["date-fns-tz@3.2.0", "", { "peerDependencies": { "date-fns": "^3.0.0 || ^4.0.0" } }, "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ=="], + + "dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dom7": ["dom7@4.0.6", "", { "dependencies": { "ssr-window": "^4.0.0" } }, "sha512-emjdpPLhpNubapLFdjNL9tP06Sr+GZkrIHEXLWvOGsytACUrkbeIdjO5g77m00BrHTznnlcNqgmn7pCN192TBA=="], + + "dompurify": ["dompurify@3.4.13", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ=="], + + "drizzle-kit": ["drizzle-kit@0.31.10", "https://registry.npmjs.com/drizzle-kit/-/drizzle-kit-0.31.10.tgz", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], + + "drizzle-orm": ["drizzle-orm@0.45.2", "https://registry.npmjs.com/drizzle-orm/-/drizzle-orm-0.45.2.tgz", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.402", "", {}, "sha512-/oOpMaPT6Yg+6/1XQhyIPlzgj7Ye9zf+nNM2Uh6OcE2G2oNptWazFa+qB2Pdqqbsc9KnIDzgAntoYN0dbwOXwA=="], + + "elkjs": ["elkjs@0.8.2", "", {}, "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "err-code": ["err-code@3.0.1", "", {}, "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "es-toolkit": ["es-toolkit@1.50.0", "", {}, "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w=="], + + "es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="], + + "es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="], + + "es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="], + + "esbuild": ["esbuild@0.25.12", "https://registry.npmjs.com/esbuild/-/esbuild-0.25.12.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="], + + "evtd": ["evtd@0.2.4", "", {}, "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw=="], + + "exsolve": ["exsolve@1.1.1", "", {}, "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g=="], + + "ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], + + "fsevents": ["fsevents@2.3.3", "https://registry.npmjs.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-browser-rtc": ["get-browser-rtc@1.1.0", "", {}, "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-tsconfig": ["get-tsconfig@4.14.1", "https://registry.npmjs.com/get-tsconfig/-/get-tsconfig-4.14.1.tgz", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + + "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], + + "hono": ["hono@4.13.0", "https://registry.npmjs.com/hono/-/hono-4.13.0.tgz", {}, "sha512-jhunvfHWxd7J5EFfSgH4xsYJzSe/lfqbUCxiyyeaQasUsXeEHXtzVid+7EOGByc5JnFa23SSFL3Y2RV/z1T+eQ=="], + + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-hotkey": ["is-hotkey@0.2.0", "", {}, "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw=="], + + "is-network-error": ["is-network-error@1.3.2", "", {}, "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA=="], + + "is-url": ["is-url@1.2.4", "", {}, "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="], + + "isomorphic.js": ["isomorphic.js@0.2.5", "", {}, "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsbi": ["jsbi@3.2.5", "", {}, "sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "katex": ["katex@0.16.47", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="], + + "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + + "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], + + "lib0": ["lib0@0.2.117", "", { "dependencies": { "isomorphic.js": "^0.2.4" }, "bin": { "0serve": "bin/0serve.js", "0gentesthtml": "bin/gentesthtml.js", "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js" } }, "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw=="], + + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], + + "linkify-it": ["linkify-it@5.0.2", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q=="], + + "local-pkg": ["local-pkg@1.2.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q=="], + + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + + "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "lodash.clonedeep": ["lodash.clonedeep@4.5.0", "", {}, "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="], + + "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + + "lodash.foreach": ["lodash.foreach@4.5.0", "", {}, "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ=="], + + "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], + + "lodash.toarray": ["lodash.toarray@4.4.0", "", {}, "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "magic-string": ["magic-string@1.1.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g=="], + + "magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="], + + "markdown-it": ["markdown-it@14.3.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.5.0", "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw=="], + + "markdown-it-image-figures": ["markdown-it-image-figures@2.1.1", "", { "peerDependencies": { "markdown-it": "*" } }, "sha512-mwXSQ2nPeVUzCMIE3HlLvjRioopiqyJLNph0pyx38yf9mpqFDhNGnMpAXF9/A2Xv0oiF2cVyg9xwfF0HNAz05g=="], + + "markdown-it-sub": ["markdown-it-sub@2.0.0", "", {}, "sha512-iCBKgwCkfQBRg2vApy9vx1C1Tu6D8XYo8NvevI3OlwzBRmiMtsJ2sXupBgEA7PPxiDwNni3qIUkhZ6j5wofDUA=="], + + "markdown-it-sup": ["markdown-it-sup@2.0.0", "", {}, "sha512-5VgmdKlkBd8sgXuoDoxMpiU+BiEt3I49GItBzzw7Mxq9CxvnhE/k09HFli09zgfFDRixDQDfDxi0mgBCXtaTvA=="], + + "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "md-editor-v3": ["md-editor-v3@6.5.6", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.7", "@codemirror/commands": "^6.8.1", "@codemirror/lang-markdown": "^6.3.4", "@codemirror/language": "^6.11.3", "@codemirror/language-data": "^6.5.1", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.38.2", "@lezer/highlight": "^1.2.1", "@lucide/vue": "^1.16.0", "@types/markdown-it": "^14.0.1", "@vavt/copy2clipboard": "^1.0.1", "@vavt/markdown-theme": "^5.0.2", "@vavt/util": "^2.1.2", "codemirror": "^6.0.2", "markdown-it": "^14.0.0", "markdown-it-image-figures": "^2.1.1", "markdown-it-sub": "^2.0.0", "markdown-it-sup": "^2.0.0", "medium-zoom": "^1.1.0", "xss": "^1.0.15" }, "peerDependencies": { "vue": "^3.5.3" } }, "sha512-bD+xKTpaC9mGDE7W+9x25Y5mEsv21Y735J2Q0dxV+lWQTXH21/sKNdwaUYQ5dhXU2fl+oqrOJsEjSd58FLyNSA=="], + + "mdurl": ["mdurl@2.1.0", "", {}, "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg=="], + + "medium-zoom": ["medium-zoom@1.1.0", "", {}, "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ=="], + + "meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="], + + "mermaid": ["mermaid@11.16.1", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.20", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", "katex": "^0.16.45", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g=="], + + "mermaid-legacy": ["mermaid@9.4.3", "", { "dependencies": { "@braintree/sanitize-url": "^6.0.0", "cytoscape": "^3.23.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.1.0", "d3": "^7.4.0", "dagre-d3-es": "7.0.9", "dayjs": "^1.11.7", "dompurify": "2.4.3", "elkjs": "^0.8.2", "khroma": "^2.0.0", "lodash-es": "^4.17.21", "non-layered-tidy-tree-layout": "^2.0.2", "stylis": "^4.1.2", "ts-dedent": "^2.2.0", "uuid": "^9.0.0", "web-worker": "^1.2.0" } }, "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-match": ["mime-match@1.0.2", "", { "dependencies": { "wildcard": "^1.1.0" } }, "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "naive-ui": ["naive-ui@2.44.1", "", { "dependencies": { "@css-render/plugin-bem": "^0.15.14", "@css-render/vue3-ssr": "^0.15.14", "@types/lodash": "^4.17.20", "@types/lodash-es": "^4.17.12", "async-validator": "^4.2.5", "css-render": "^0.15.14", "csstype": "^3.1.3", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "evtd": "^0.2.4", "highlight.js": "^11.8.0", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "seemly": "^0.3.10", "treemate": "^0.3.11", "vdirs": "^0.1.8", "vooks": "^0.2.12", "vueuc": "^0.4.65" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-reo8Esw0p58liZwbUutC7meW24Xbn3EwNv91zReWKm2W4JPu+zfgJRn/F7aO0BFmvN+h2brA2M5lRvYqLq4kuA=="], + + "namespace-emitter": ["namespace-emitter@2.0.1", "", {}, "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g=="], + + "nanoid": ["nanoid@6.0.1", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-3wVS3i51pE2pi1k5FFL/95BGfVS0kSsvDVuGXHOtxox/TywUmtgq+3qiTOTbs9J7KfHaXPiN171k/A6dBnaXFw=="], + + "next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="], + + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], + + "non-layered-tidy-tree-layout": ["non-layered-tidy-tree-layout@2.0.2", "", {}, "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="], + + "normalize.css": ["normalize.css@8.0.1", "", {}, "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg=="], + + "nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="], + + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], + + "p-retry": ["p-retry@6.2.1", "", { "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" } }, "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ=="], + + "package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], + + "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "pinia": ["pinia@4.0.2", "", { "dependencies": { "nostics": "^1.1.4" }, "peerDependencies": { "@vue/devtools-api": "^8.1.5", "typescript": ">=5.6.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-yKVVA7bSj5oRZFp/Ab9wLlmyb5gPUYEiIm4ryiWTe/xe7PtkRdMVOp1X1ggvq0c6Uj7Q0Du1HnV2mtAwM0Ks1g=="], + + "pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="], + + "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], + + "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], + + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], + + "postgres": ["postgres@3.4.9", "https://registry.npmjs.com/postgres/-/postgres-3.4.9.tgz", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], + + "preact": ["preact@10.29.8", "", { "peerDependencies": { "preact-render-to-string": ">=5" }, "optionalPeers": ["preact-render-to-string"] }, "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q=="], + + "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], + + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], + + "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="], + + "regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="], + + "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + + "regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="], + + "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="], + + "regjsparser": ["regjsparser@0.13.2", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "https://registry.npmjs.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], + + "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], + + "rolldown": ["rolldown@1.2.3", "", { "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.3", "@rolldown/binding-darwin-arm64": "1.2.3", "@rolldown/binding-darwin-x64": "1.2.3", "@rolldown/binding-freebsd-x64": "1.2.3", "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", "@rolldown/binding-linux-arm64-gnu": "1.2.3", "@rolldown/binding-linux-arm64-musl": "1.2.3", "@rolldown/binding-linux-ppc64-gnu": "1.2.3", "@rolldown/binding-linux-s390x-gnu": "1.2.3", "@rolldown/binding-linux-x64-gnu": "1.2.3", "@rolldown/binding-linux-x64-musl": "1.2.3", "@rolldown/binding-openharmony-arm64": "1.2.3", "@rolldown/binding-win32-arm64-msvc": "1.2.3", "@rolldown/binding-win32-x64-msvc": "1.2.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A=="], + + "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], + + "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], + + "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], + + "seemly": ["seemly@0.3.10", "", {}, "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "simple-peer": ["simple-peer@9.11.1", "", { "dependencies": { "buffer": "^6.0.3", "debug": "^4.3.2", "err-code": "^3.0.1", "get-browser-rtc": "^1.1.0", "queue-microtask": "^1.2.3", "randombytes": "^2.1.0", "readable-stream": "^3.6.0" } }, "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw=="], + + "skulpt": ["skulpt@1.2.0", "", { "dependencies": { "jsbi": "^3.1.4" } }, "sha512-T0cv0sdSOXLlIJTuyXSeYJ3TFdWYSZfX2PcBLpRKrKZ3dTbVQXRMiYudSuko2xzcyMif47DS2ShatCwjCbsSsA=="], + + "slate": ["slate@0.124.1", "", {}, "sha512-ii7DwezgvbLAyKtHBIunjTR1kzbNfYLCUKLMzJELlbTZkvHzX4DzN7HKIwcakf6dPxO6AoeT/P7kHOcyTym/hA=="], + + "slate-history": ["slate-history@0.115.0", "", { "peerDependencies": { "slate": ">=0.114.3" } }, "sha512-QdUm9aVyQFz6JG4a84Z6Um+tJpZJmsh9bjXwTVTvYiN4rdKbqL6+/4HT94on1WYxe10Q4vY6mA6BCpoYxgF3tQ=="], + + "snabbdom": ["snabbdom@3.6.4", "", {}, "sha512-VmxEfuw1/Y/eFj5VtMhYnukExpYiPkNzoo3+N3qwAOUDMl8wXgbli5ebR+j0knE3lZ/0eYskLxNcX64uy10N9w=="], + + "source-map": ["source-map@0.6.1", "https://registry.npmjs.com/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "https://registry.npmjs.com/source-map-support/-/source-map-support-0.5.21.tgz", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "ssr-window": ["ssr-window@4.0.2", "", {}, "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-literal": ["strip-literal@4.0.0", "", { "dependencies": { "js-tokens": "^10.0.0" } }, "sha512-PaqAvfUZKBwc/SLmNZtHmzK+v19Z4O4eS3cKPeGvbIv/U3pnyEq4Tuw3/4v/FwfM8VQaEawsyCcOQ0P+kpwWWw=="], + + "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], + + "stylis": ["stylis@4.4.0", "", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "systemjs": ["systemjs@6.15.1", "", {}, "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA=="], + + "terser": ["terser@5.49.2", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-rGbJiKeQ4WDe3EXlDAIaQcwftVfv2Q8o1awFNfvXolJYKkb1AuZY1RTOmqx4LJXZENbWZA7eIsYGHuEzHsi1nQ=="], + + "tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "treemate": ["treemate@0.3.11", "", {}, "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg=="], + + "ts-dedent": ["ts-dedent@2.3.0", "", {}, "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg=="], + + "tsx": ["tsx@4.23.9", "https://registry.npmjs.com/tsx/-/tsx-4.23.9.tgz", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-6q8uTORRGauQVjqMQnKUucLFoeXZAfw6zKvG35GLbdKWbLdeOtZ3H4mhyA5mxuUd2o2cRTskhj59nLLQseUvUw=="], + + "type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="], "typescript": ["typescript@5.9.3", "https://registry.npmjs.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + "undici-types": ["undici-types@8.3.0", "https://registry.npmjs.com/undici-types/-/undici-types-8.3.0.tgz", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="], + + "unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="], + + "unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="], + + "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="], + + "unimport": ["unimport@6.4.0", "", { "dependencies": { "acorn": "^8.18.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.2.1", "magic-string": "^1.1.0", "mlly": "^1.8.2", "pathe": "^2.0.3", "picomatch": "^4.0.5", "pkg-types": "^2.3.1", "scule": "^1.3.0", "strip-literal": "^4.0.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2" }, "peerDependencies": { "oxc-parser": "*", "rolldown": "^1.0.0" }, "optionalPeers": ["oxc-parser", "rolldown"] }, "sha512-JJOOuNMFq8b4ZPBKwQUxEcba4MplskDzYI1Lvrf8rJfWphZTWvPNXWa493qsPngHUmub89w6C7j+SeLWTE/UIQ=="], + + "unplugin": ["unplugin@3.3.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "picomatch": "^4.0.4", "webpack-virtual-modules": "^0.6.2" }, "peerDependencies": { "@farmfe/core": "*", "@rspack/core": "*", "bun-types-no-globals": "*", "esbuild": "*", "rolldown": "*", "rollup": "*", "unloader": "*", "vite": "*", "webpack": "*" }, "optionalPeers": ["@farmfe/core", "@rspack/core", "bun-types-no-globals", "esbuild", "rolldown", "rollup", "unloader", "vite", "webpack"] }, "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg=="], + + "unplugin-auto-import": ["unplugin-auto-import@21.1.0", "", { "dependencies": { "local-pkg": "^1.2.1", "magic-string": "^1.1.0", "picomatch": "^4.0.5", "unimport": "^6.4.0", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2" }, "peerDependencies": { "@nuxt/kit": "^4.0.0", "@vueuse/core": "*" }, "optionalPeers": ["@nuxt/kit", "@vueuse/core"] }, "sha512-EzrSqWIBulEqCuP7idADXH+tVKYrbwKlR5+r/lOWik0o+Ksny1kitmtryHGNSv7puzzORlcIwT/x2JStiszlHA=="], + + "unplugin-utils": ["unplugin-utils@0.3.2", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.4" } }, "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA=="], + + "unplugin-vue-components": ["unplugin-vue-components@32.1.0", "", { "dependencies": { "chokidar": "^5.0.0", "local-pkg": "^1.2.0", "magic-string": "^0.30.21", "mlly": "^1.8.2", "obug": "^2.1.1", "picomatch": "^4.0.4", "tinyglobby": "^0.2.16", "unplugin": "^3.0.0", "unplugin-utils": "^0.3.1" }, "peerDependencies": { "@nuxt/kit": "^3.2.2 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@nuxt/kit"] }, "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg=="], + + "update-browserslist-db": ["update-browserslist-db@1.3.0", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="], + + "vdirs": ["vdirs@0.1.8", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw=="], + + "vite": ["vite@8.2.1", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", "postcss": "^8.5.25", "rolldown": "~1.2.1", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw=="], + + "vooks": ["vooks@0.2.12", "", { "dependencies": { "evtd": "^0.2.2" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q=="], + + "vue": ["vue@3.5.41", "", { "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/compiler-sfc": "3.5.41", "@vue/runtime-dom": "3.5.41", "@vue/server-renderer": "3.5.41", "@vue/shared": "3.5.41" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg=="], + + "vue-chartjs": ["vue-chartjs@5.3.4", "", { "peerDependencies": { "chart.js": "^4.1.1", "vue": "^3.0.0-0 || ^2.7.0" } }, "sha512-x3Fqob8RQvrTdssfi9ecsCzEkFOd8JPmNwSkSQzdfKj/uBsRJs/Y88cZcZIEcPsTVfMGwMo4MOoihoDG2DoE/g=="], + + "vue-codemirror": ["vue-codemirror@6.1.1", "", { "dependencies": { "@codemirror/commands": "6.x", "@codemirror/language": "6.x", "@codemirror/state": "6.x", "@codemirror/view": "6.x" }, "peerDependencies": { "codemirror": "6.x", "vue": "3.x" } }, "sha512-rTAYo44owd282yVxKtJtnOi7ERAcXTeviwoPXjIc6K/IQYUsoDkzPvw/JDFtSP6T7Cz/2g3EHaEyeyaQCKoDMg=="], + + "vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="], + + "vue-router": ["vue-router@5.2.0", "", { "dependencies": { "@babel/generator": "^8.0.0", "@vue-macros/common": "^3.1.3", "@vue/devtools-api": "^8.1.5", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "json5": "^2.2.3", "local-pkg": "^1.2.1", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "nostics": "^1.1.4", "pathe": "^2.0.3", "picomatch": "^4.0.5", "scule": "^1.3.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2", "yaml": "^2.9.0" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", "pinia": "^3.0.4 || ^4.0.2", "vite": "^7.3.0 || ^8.0.0", "vue": "^3.5.34 || ^4.0.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw=="], + + "vueuc": ["vueuc@0.4.65", "", { "dependencies": { "@css-render/vue3-ssr": "^0.15.10", "@juggle/resize-observer": "^3.3.1", "css-render": "^0.15.10", "evtd": "^0.2.4", "seemly": "^0.3.6", "vdirs": "^0.1.4", "vooks": "^0.2.4" }, "peerDependencies": { "vue": "^3.0.11" } }, "sha512-lXuMl+8gsBmruudfxnMF9HW4be8rFziylXFu1VHVNbLVhRTXXV4njvpRuJapD/8q+oFEMSfQMH16E/85VoWRyQ=="], + + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + + "web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="], + + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], + + "wildcard": ["wildcard@1.1.2", "", {}, "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng=="], + + "ws": ["ws@8.21.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw=="], + + "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="], + + "y-codemirror.next": ["y-codemirror.next@0.3.5", "", { "dependencies": { "lib0": "^0.2.42" }, "peerDependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "yjs": "^13.5.6" } }, "sha512-VluNu3e5HfEXybnypnsGwKAj+fKLd4iAnR7JuX1Sfyydmn1jCBS5wwEL/uS04Ch2ib0DnMAOF6ZRR/8kK3wyGw=="], + + "y-protocols": ["y-protocols@1.0.7", "", { "dependencies": { "lib0": "^0.2.85" }, "peerDependencies": { "yjs": "^13.0.0" } }, "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw=="], + + "y-webrtc": ["y-webrtc@10.3.0", "", { "dependencies": { "lib0": "^0.2.42", "simple-peer": "^9.11.0", "y-protocols": "^1.0.6" }, "optionalDependencies": { "ws": "^8.14.2" }, "peerDependencies": { "yjs": "^13.6.8" }, "bin": { "y-webrtc-signaling": "bin/server.js" } }, "sha512-KalJr7dCgUgyVFxoG3CQYbpS0O2qybegD0vI4bYnYHI0MOwoVbucED3RZ5f2o1a5HZb1qEssUKS0H/Upc6p1lA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "yjs": ["yjs@13.6.32", "", { "dependencies": { "lib0": "^0.2.99" } }, "sha512-lfiJIIC4Xayt5ItynE407ehlE03pCjeOc4hkR4yxxvvNJ4kuiN25B0g+Qp8XagYz361LLL7DCzR5bvFJ81QKtQ=="], + "zod": ["zod@4.4.3", "https://registry.npmjs.com/zod/-/zod-4.4.3.tgz", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + "@babel/core/@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], - "tsx/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "@babel/generator/@babel/parser": ["@babel/parser@8.0.4", "", { "dependencies": { "@babel/types": "^8.0.4" }, "bin": "./bin/babel-parser.js" }, "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + "@babel/generator/@babel/types": ["@babel/types@8.0.4", "", { "dependencies": { "@babel/helper-string-parser": "^8.0.0", "@babel/helper-validator-identifier": "^8.0.4" } }, "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + "@babel/traverse/@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "https://registry.npmjs.com/esbuild/-/esbuild-0.18.20.tgz", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + "@oj2/web/typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + "@types/d3-cloud/@types/d3": ["@types/d3@3.5.53", "", {}, "sha512-8yKQA9cAS6+wGsJpBysmnhlaaxlN42Qizqkw+h2nILSlS+MAG2z4JdO6p+PJrJ+ACvimkmLJL281h157e52psQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + "@uppy/core/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + "@vue-flow/core/@vueuse/core": ["@vueuse/core@10.11.1", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.11.1", "@vueuse/shared": "10.11.1", "vue-demi": ">=0.14.8" } }, "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + "@vue/compiler-sfc/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + "@wangeditor-next/basic-modules/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + "@wangeditor-next/core/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + "@wangeditor-next/editor/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + "@wangeditor-next/table-module/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + "@wangeditor-next/video-module/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + "css-render/csstype": ["csstype@3.0.11", "", {}, "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + "d3-cloud/d3-dispatch": ["d3-dispatch@1.0.6", "", {}, "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + "magic-string-ast/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + "mermaid-legacy/@braintree/sanitize-url": ["@braintree/sanitize-url@6.0.4", "", {}, "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="], - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + "mermaid-legacy/dagre-d3-es": ["dagre-d3-es@7.0.9", "", { "dependencies": { "d3": "^7.8.2", "lodash-es": "^4.17.21" } }, "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w=="], - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + "mermaid-legacy/dompurify": ["dompurify@2.4.3", "", {}, "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="], - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + "mermaid-legacy/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + "postcss/nanoid": ["nanoid@3.3.17", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g=="], - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + "strip-literal/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + "tsx/esbuild": ["esbuild@0.28.1", "https://registry.npmjs.com/esbuild/-/esbuild-0.28.1.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + "unplugin-vue-components/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + "vue-router/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + "@babel/generator/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@8.0.0", "", {}, "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg=="], - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + "@babel/generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@8.0.4", "", {}, "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg=="], - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "https://registry.npmjs.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "https://registry.npmjs.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "https://registry.npmjs.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "https://registry.npmjs.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "https://registry.npmjs.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "https://registry.npmjs.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "https://registry.npmjs.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "https://registry.npmjs.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "https://registry.npmjs.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "https://registry.npmjs.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "https://registry.npmjs.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "https://registry.npmjs.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "https://registry.npmjs.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "https://registry.npmjs.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "https://registry.npmjs.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + + "@vue-flow/core/@vueuse/core/@types/web-bluetooth": ["@types/web-bluetooth@0.0.20", "", {}, "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="], + + "@vue-flow/core/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@10.11.1", "", {}, "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw=="], + + "@vue-flow/core/@vueuse/core/@vueuse/shared": ["@vueuse/shared@10.11.1", "", { "dependencies": { "vue-demi": ">=0.14.8" } }, "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA=="], + + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], + + "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "https://registry.npmjs.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "https://registry.npmjs.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "https://registry.npmjs.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "https://registry.npmjs.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "https://registry.npmjs.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "https://registry.npmjs.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "https://registry.npmjs.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "https://registry.npmjs.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "https://registry.npmjs.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "https://registry.npmjs.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "https://registry.npmjs.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "https://registry.npmjs.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "https://registry.npmjs.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "https://registry.npmjs.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "https://registry.npmjs.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "https://registry.npmjs.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], } }