diff --git a/.gitignore b/.gitignore index 3b254656f..94500b038 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,12 @@ dist-ssr/ .eslintcache .stylelintcache .vite-inspect +# vue-tsc incremental build cache (replaces the stale `.tsbuildinfo` +# pattern that only matched the flat-package layout; vue-tsc 2.x writes +# `*.tsbuildinfo` next to each tsconfig.json). +*.tsbuildinfo +**/tsconfig.tsbuildinfo +**/tsconfig.*.tsbuildinfo # 环境变量文件(含敏感信息,禁止进入 Git) .env .env.* diff --git a/.reports/codemap-diff.txt b/.reports/codemap-diff.txt index d32f51105..cd29b251b 100644 --- a/.reports/codemap-diff.txt +++ b/.reports/codemap-diff.txt @@ -1,153 +1,74 @@ -Codemap Update Report - 2026-06-12 -================================== - -Files Scanned : 2038 (was 1923 — ↑115) - Java 735, Vue 821, TS 482 (excl. shared), test files 46 - __tests__ dirs 21 -Backend Java : 735 (was 708, +27) -Vue (both FEs) : 821 (was 820, +1) -TS (both FEs) : 482 (was 445, +37) -Migrations : 25 (was 20, +5) -Controllers : 43 (was 41, +2 — ProblemNote + SolutionTopic) -Modules : 26 (unchanged) -Shared Packages : 4 (unchanged) -Codemaps Updated : 5 (all) - -Change Magnitude ----------------- -Cumulative delta vs 2026-06-10 baseline: - Migrations: 20 → 25 (+25.0%) - Controllers: 41 → 43 (+4.9%) - Java sources: 708 → 735 (+3.8%) - TS sources: 445 → 482 (+8.3%) -Weighted overall: ~10-15% — under 30% threshold → in-place update. - -Change Summary -------------- -[MINOR] docs/CODEMAPS/architecture.md - - Freshness header: 2026-06-10 → 2026-06-12, new file counts - - Backend purpose: appended ", notes, topics, etc." (new domain features) - - Data flow: +2 bullets (reactions / notes & topics) - - Migration listing: refreshed to 25 files; latest 5 listed - - New "Recent Architectural Themes" section (5 bullets) - -[MINOR] docs/CODEMAPS/backend.md - - Controllers: 41 → 43, with new ProblemNote + SolutionTopic rows - - User-facing routes: +3 (problem note GET/POST, solution topics) - - Edge operations: noted LIKE/DISLIKE/FAVORITE expansion - - problem/ module: noted "notes NEW 2026-06-11" - - solution/ module: noted "topics NEW 2026-06-11" - - notification: noted "logical delete added 2026-06-11" - -[MINOR] docs/CODEMAPS/frontend.md - - New "Recent Console Changes" section with full file list (10 modified, - 2 new __tests__ dirs) - - features/sider/ note: AppLayout/SidebarInset refactored 2026-06-12 - - Vue/TS counts refreshed (Console ~351/~217, Mgmt ~470/~265) - -[MINOR] docs/CODEMAPS/data.md - - Migrations: 20 → 25, appended 5 newest - - Table groups: +problem_notes, +solution_topics - - Notification group: noted is_deleted + system→system_enabled - - Edge ops: noted enum expansion - - Relationships: +users30% delta requires user approval before - overwrite; this pass at ~10-15% was in-place -- Files re-read in full via Read tool before Write (per Write-tool contract) +# Codemap Diff Report + +Generated: 2026-06-19 +Previous codemap baseline: 2026-06-18 (commit 7060279f4) +Scan scope: docs/CODEMAPS/ (6 files) +Diff threshold: <=30% → update in place (no overwrite approval needed). +Special case: a7cbe154d (2026-06-19) deleted all 6 codemaps via "drop legacy docs" chore + → full re-creation required (not a normal diff). + +## Files added/removed/modified since last scan (codebase side) + +### Commits since previous codemap sync (7060279f4..HEAD = 3 commits) + +- a7cbe154d chore: drop legacy docs and sync shared typography tokens + - **Deleted** all 6 codemaps (this report's special case) + - Deleted docs/CONTRIBUTING.md, ENV.md, PRIVACY.md, README.md, RUNBOOK.md, SHARED_TYPOGRAPHY_DESIGN.md + - Deleted docs/adr/* (12 ADRs) + - Deleted docs/DOCS-SPEC.md, docs/contest/* (R-plans + reviews) + - Synced typography tokens across both frontends +- 02fa0a919 chore: finalize shared typography migration across both apps + - console + management design-system alignment +- 14ca103ba chore: rollup prior session WIP and shared typography migration + - composite rollup of prior uncommitted typography work + +Net: **0 architecture/dependency changes** since 2026-06-18 baseline. +All 3 commits are typography/cleanup only. Codemap content drift vs 7060279f4 baseline ≈ 0–2%. + +## Codemap re-creation (2026-06-19) + +| File | Bytes | Token est. | Notes | +| ----------------------- | ------ | ---------- | -------------------------------------- | +| architecture.md | ~3700 | ~900 | ADR-008..011, 5 PM2 apps, full topology | +| backend.md | ~3300 | ~850 | 26 modules · 43 controllers · 588 Java | +| frontend.md | ~3600 | ~900 | Console + Management + shared/ layout | +| data.md | ~3600 | ~900 | 34 migrations, table groups, contest isolation | +| dependencies.md | ~3400 | ~850 | pom + both package.json + infra images | +| sandbox.md | ~3100 | ~850 | ADR-001/002 verdict codec, build pipeline | + +Total: ~20.7 KB / ~5250 tokens. All under 1000-token target per file. + +## Verified accurate (no drift) vs 2026-06-18 baseline + +- Backend: 26 modules, 43 controllers, 588 Java files (matches ecosystem + source scan) +- Frontend: console (365 files) + management (348 files) — view/store trees stable +- Migrations: still 34 (no new migrations in 3 commits) +- PM2: still 5 apps (4 long-lived + 1 one-shot ulticode-init-db) +- Arthas: still 4.2.2 STATELESS +- Sandbox image: still ulticode-sandbox:latest +- Shared packages: still 5 (auth-core, theme, design-system, badge-config, sandbox-types) +- Docker images: mysql:9.1, redis:7-alpine, nacos/nacos-server:v2.3.2 + +## New dependencies detected + +None — all changes are typography/design-system refactors of existing tokens. No new +runtime deps added. No new Docker images pulled. + +## Architecture changes + +None since 2026-06-18 baseline. Latest architecture shift (ADR-007 virtual contest +lifecycle + ADR-011 CRIT-6 shadow mode) is already captured in last sync. + +## Staleness warnings (90+ days) + +None. No `docs/*.md` file currently exceeds 30 days of age (all writes are within +the 2026-06 cycle). + +## Follow-up (not auto-fixed) + +- `.reports/codemap-diff.txt` itself — preserve last 2 versions for diff history +- If /ecc:update-docs is also invoked next, it will need fresh CONTRIB/RUNBOOK/ENV + docs (all currently missing after a7cbe154d) +- The 4 uncommitted local changes (incl. console/src/features/layout/panels/LayoutPanelHeader.vue) + are unrelated to codemap content; do not auto-include in codemaps until committed +- repo is ahead 4 of origin/main; codemaps are untracked files in working tree, not committed diff --git a/.reports/doc-update.txt b/.reports/doc-update.txt deleted file mode 100644 index 9b1d60d1e..000000000 --- a/.reports/doc-update.txt +++ /dev/null @@ -1,17 +0,0 @@ -Documentation Update Report - 2026-05-30 -=========================================== - -Updated Files -------------- -[UPDATED] docs/CONTRIBUTING.md - - Added init-db section (standalone Flyway commands) - - Added Option A/B for database migration setup - - Documented migration naming convention - -Staleness Check ---------------- -docs/ENV.md - 7 days old (OK) -docs/CONTRIBUTING.md - Updated today (OK) -docs/architecture.md - 7 days old (OK) - -No staleness warnings (>90 days) diff --git a/.reports/docs-update.txt b/.reports/docs-update.txt new file mode 100644 index 000000000..c98229253 --- /dev/null +++ b/.reports/docs-update.txt @@ -0,0 +1,71 @@ +# Documentation Update + +Generated: 2026-06-19 +Trigger: /ecc:update-docs +Previous docs baseline: 2026-06-18 (commit 7060279f4) +Special case: a7cbe154d (2026-06-19) deleted the entire docs/ tree + (CONTRIBUTING/ENV/RUNBOOK/ADR/privacy/readme) as part of + "chore: drop legacy docs" — full re-creation required. + +## Updated (re-created from source-of-truth) + +| File | Bytes | Token est. | Source-of-truth | +| ----------------------- | ------ | ---------- | ------------------------------------------------ | +| docs/ENV.md | 11935 | ~3100 | `.env.example`, `.env.test.example`, `application.yml`, `ecosystem.config.cjs`, `scripts/dev/init-env.sh` | +| docs/CONTRIBUTING.md | 13635 | ~3600 | `package.json` (console/management/shared/auth-core), `scripts/dev/`, `AGENTS.md`, `CLAUDE.md` | +| docs/RUNBOOK.md | 15942 | ~4100 | `CLAUDE.md`, `ecosystem.config.cjs`, `scripts/`, `infrastructure/`, `.github/workflows/` | + +Total new doc bytes: ~41.5 KB / ~10,800 tokens. + +## Auto-generated sections (marked in-file) + +- `docs/CONTRIBUTING.md` §5 — scripts tables (derived from each package's `package.json`) +- `docs/ENV.md` — every env var table (derived from `.env.example` + `.env.test.example`) + +Manual sections (prose / narrative) are left intact and **not** wrapped in +AUTO-GENERATED markers. + +## Flagged for manual review (90+ days) + +None. All 9 docs files (`arthas-mcp-usage.md` + `CODEMAPS/*` + new 3) were +written/modified within the 2026-06-19 session. + +## Skipped (not auto-created per skill rule) + +The following were also deleted by `a7cbe154d` but are **not** auto-created +("Only create new doc files if the command explicitly requests it"): + +- `docs/adr/ADR-000..011` (12 Architecture Decision Records) +- `docs/PRIVACY.md` (privacy policy) +- `docs/SHARED_TYPOGRAPHY_DESIGN.md` (typography design — obsolete post-migration) +- `docs/DOCS-SPEC.md` (old docs spec — obsolete) +- `docs/README.md` (docs/ index — top-level `README.md` is sufficient) +- `docs/contest/*` (R-plans + reviews — task plans, ephemeral by nature) + +If the team wants the ADRs back, they should be recreated individually as they +encode the actual decisions and can't be derived from code. + +## Other docs in tree (preserved) + +- `docs/arthas-mcp-usage.md` → moved to `docs/ops/arthas-mcp-usage.md` (11.0 KB, 2026-06-19) — Arthas MCP usage guide; `docs/ops/` is the new home for ops/tooling references +- `docs/ops/README.md` (NEW, 2026-06-19) — naming + frontmatter convention for `docs/ops/` +- `docs/CODEMAPS/{architecture,backend,frontend,data,dependencies,sandbox}.md` + (created earlier this session by /ecc:update-codemaps) + +## Verification recommendations + +After this docs pass, the team may want to spot-check that the auto-generated +script tables match the actual `package.json` `scripts` blocks. The skill rule +"Always generate from code, never manually edit generated sections" means +future /ecc:update-docs runs should re-derive these tables. + +## Staleness warnings + +None. + +## Follow-up (not auto-fixed) + +- The 4 uncommitted local changes (incl. `console/src/features/layout/panels/LayoutPanelHeader.vue`) + remain in working tree; docs are untracked and not committed +- repo is ahead 4 of origin/main +- If a future task needs the ADRs back, run a one-off generation or hand-write them diff --git a/CLAUDE.md b/CLAUDE.md index e277a0c2b..dcaccfe9f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co | **[.claude/rules/](./.claude/rules/)** | Claude Code 按 paths 触发的子规则 (backend/, frontend/, database/) | 触及对应路径时自动加载 | | **[.cursor/rules/](./.cursor/rules/)** | Cursor IDE 按 globs 触发的 `.mdc` 规则(codegraph、frontend-rules、springboot-rules) | Cursor 环境中触及 `console/`/`management/`/`backend-spring/` 时 | | **[.claude/agents/](./.claude/agents/)** | 可调用的领域子代理 | 复杂规划、代码审查、安全审查、TDD、构建修复等场景 | -| **[docs/](./docs/)** | 架构、运维、安全专题文档(`CODEMAPS/`、`ENV.md`、`CONTRIBUTING.md`、安全审查报告) | 架构梳理、运维排障、安全合规 | +| **[docs/](./docs/)** | 项目工程文档。子目录:`README.md`(索引)· `CONTRIBUTING.md`(开发)· `RUNBOOK.md`(on-call)· `ENV.md`(环境变量)· `DOCS_CONVENTIONS.md`(文档规范)· `CODEMAPS/`(架构快照)· `adr/`(决策记录)· `ops/`(运维深读)· **`theme/`**(前端主题系统,6 篇:Token / 颜色模式 / 密度 / 组件原语 / 扩展;2026-06-19 新增) | 查架构/部署/合规走 [`docs/README.md` §1–§3](./docs/README.md) 的"先读这份"表 | +| **[docs/theme/](./docs/theme/)** | 前端主题系统(2026-06-19 新增)。4 层分层(state / tokens / primitives / bootstrap)、Design Token 全集、light/dark/system + compact/comfortable 切换、组件原语清单。**项目字体 = LXGW WenKai 楷体,全站统一(包括 Monaco 编辑器 / ECharts 默认字体)** | 改前端颜色 / 字体 / 密度 / 动效 / 组件样式时先读 [`docs/theme/README.md`](./docs/theme/README.md) | 子代理简表(详见 `.claude/agents/.md`): `planner` · `architect` · `code-reviewer` · `java-reviewer` · `security-reviewer` · `tdd-guide` · `build-error-resolver` · `refactor-cleaner` · `doc-updater` · `e2e-runner` · `rust-reviewer` @@ -167,7 +168,7 @@ java -jar tools/arthas-boot.jar 4. **回退**:`./mvnw -Dtest='*IT' test -B` 跑问题单测/集成测试做对照(验证 N+1 / 重复添加逻辑时直接走这条) 5. 同步 MCP 阻塞 30s 时,**用 `mcp__plugin_context-mode_context-mode__ctx_execute` 跑 java 反射/grep 类检查**(后台子进程,无 MCP 超时) - 见 `.claude/rules/backend/09-java-runtime-diagnostics.md` 的强制约束:阻塞命令必须带 `-n N` (N ≤ 5) 限制执行次数 -- **Arthas MCP 实战手册**: `docs/arthas-mcp-usage.md` — 含 watch/trace/stack 真实调用样本(本会话 2026-06-18 验证)、OGNL 速查、降级路径 +- **Arthas MCP 实战手册**: `docs/ops/arthas-mcp-usage.md` — 含 watch/trace/stack 真实调用样本(本会话 2026-06-18 验证)、OGNL 速查、降级路径 - **增强命令并发模式**: 后台 bash `run_in_background=true` 持续 curl 触发目标端点(选没限流的 register/GET,login 是 60s/5 次窗口会被拦截),同步发 `mcp__arthas-mcp__trace/watch/stack` 配 `numberOfExecutions=1` + `timeout=12`;MCP 客户端 timeout 30s,所以 arthas 端 timeout 永远 ≤ 25 ### MCP 配置 (.mcp.json) diff --git a/README.md b/README.md index 65e6ef887..6e59b5caf 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,28 @@ **在线编程平台 (Online Judge)** — 一个功能完整的在线判题系统,支持题目练习、竞赛、社区讨论、成就系统等。 +## 项目截图 + +> 想看更多视图、对比 light/dark 主题或下载原图:见 [docs/screenshots/](docs/screenshots/README.md)。 + +### 用户端 (Console · 9002 · light) + +![论坛帖子列表](docs/screenshots/forum-list-light.png) + +### 用户端 (Console · 9002 · dark) + +![论坛平台](docs/screenshots/forum-thread-dark.png) +![论坛帖子详情](docs/screenshots/forum-detail-dark.png) +![UltiCode 竞赛](docs/screenshots/contests-dark.png) +![题库专题](docs/screenshots/problem-set-dark.png) +![题目详情 + 在线 IDE](docs/screenshots/problem-detail-dark.png) + +### 管理端 (Management · 9003 · dark) + +![管理后台 — 数据分析](docs/screenshots/analytics-dashboard-dark.png) +![管理后台 — 仪表板](docs/screenshots/admin-dashboard-dark.png) + + ## 架构概览 ``` diff --git a/backend-spring/src/main/java/com/ulticode/modules/moderation/mapper/ModerationQueueMapper.java b/backend-spring/src/main/java/com/ulticode/modules/moderation/mapper/ModerationQueueMapper.java index 3f3196c86..fc493bbac 100644 --- a/backend-spring/src/main/java/com/ulticode/modules/moderation/mapper/ModerationQueueMapper.java +++ b/backend-spring/src/main/java/com/ulticode/modules/moderation/mapper/ModerationQueueMapper.java @@ -80,6 +80,27 @@ public interface ModerationQueueMapper extends BaseMapper { @Select("SELECT COUNT(*) FROM moderation_queue WHERE status = 'UNDER_REVIEW'") long countUnderReview(); + /** + * Count items resolved by a substantive action (delete / hide / warn / ban / restore / + * appeal outcome). Excludes items dismissed as false reports so that resolvedCount and + * dismissedCount partition all RESOLVED items without overlap. + * IFNULL guards against hypothetically NULL resolution values on legacy rows. + * + * @return count of substantively resolved items + */ + @Select("SELECT COUNT(*) FROM moderation_queue " + + "WHERE status = 'RESOLVED' AND IFNULL(resolution, '') <> 'DISMISSED'") + long countResolved(); + + /** + * Count items dismissed as false reports (status = RESOLVED, resolution = DISMISSED). + * + * @return count of dismissed items + */ + @Select("SELECT COUNT(*) FROM moderation_queue " + + "WHERE status = 'RESOLVED' AND resolution = 'DISMISSED'") + long countDismissed(); + /** * Count items resolved today. * diff --git a/backend-spring/src/main/java/com/ulticode/modules/moderation/service/impl/ModerationServiceImpl.java b/backend-spring/src/main/java/com/ulticode/modules/moderation/service/impl/ModerationServiceImpl.java index 23bcbc1f3..9455731f5 100644 --- a/backend-spring/src/main/java/com/ulticode/modules/moderation/service/impl/ModerationServiceImpl.java +++ b/backend-spring/src/main/java/com/ulticode/modules/moderation/service/impl/ModerationServiceImpl.java @@ -122,6 +122,8 @@ public ModerationStatsVO getStats() { ModerationStatsVO stats = new ModerationStatsVO(); stats.setPendingCount(queueMapper.countPending()); stats.setUnderReviewCount(queueMapper.countUnderReview()); + stats.setResolvedCount(queueMapper.countResolved()); + stats.setDismissedCount(queueMapper.countDismissed()); stats.setResolvedToday(queueMapper.countResolvedToday()); stats.setPendingAppealsCount(appealMapper.countPending()); stats.setAvgResolutionTimeHours(queueMapper.avgResolutionTimeHours()); diff --git a/console/index.html b/console/index.html index e92b70f8d..00292e632 100644 --- a/console/index.html +++ b/console/index.html @@ -4,6 +4,7 @@ + UltiCode @@ -42,7 +42,7 @@ type="button" variant="ghost" size="sm" - class="h-8 px-3 font-[family-name:var(--font-mono)] text-xs uppercase tracking-wider" + class="h-8 px-3 font-mono text-xs uppercase tracking-wider" @click="onCancel" > {{ t("common.actions.cancel") }} @@ -51,7 +51,7 @@ type="submit" variant="default" size="sm" - class="h-8 px-4 font-[family-name:var(--font-mono)] text-xs uppercase tracking-wider" + class="h-8 px-4 font-mono text-xs uppercase tracking-wider" :disabled="!content.trim()" > {{ t("forum.comments.submit") }} diff --git a/console/src/components/comments/CommentNode.vue b/console/src/components/comments/CommentNode.vue index e6477f83c..b00d3a43a 100644 --- a/console/src/components/comments/CommentNode.vue +++ b/console/src/components/comments/CommentNode.vue @@ -60,7 +60,7 @@ {{ t("forum.author.op") }} @@ -73,7 +73,7 @@ ({{ t("forum.comments.edited") }}) {{ comment.replyCount }} {{ t("forum.comments.replies") }} diff --git a/console/src/components/comments/CommentThread.vue b/console/src/components/comments/CommentThread.vue index a848f6fef..9251aca77 100644 --- a/console/src/components/comments/CommentThread.vue +++ b/console/src/components/comments/CommentThread.vue @@ -63,7 +63,7 @@ const commentTree = computed(() => { /> {{ t("forum.comments.joinConversation") }} @@ -82,7 +82,7 @@ const commentTree = computed(() => {
{{ t("forum.comments.threadLocked") }}
diff --git a/console/src/components/common/data-table/DataTable.vue b/console/src/components/common/data-table/DataTable.vue index 8bff9e25f..bcb701532 100644 --- a/console/src/components/common/data-table/DataTable.vue +++ b/console/src/components/common/data-table/DataTable.vue @@ -146,7 +146,7 @@ const getItemAtIndex = (index: number): T => { v-for="col in columns" :key="col.key" :class="[ - 'font-extrabold text-[var(--primary)] text-[11px] uppercase tracking-widest py-4 px-4 bg-[var(--surface-sunken)]/60', + 'font-extrabold text-[var(--primary)] text-xxs uppercase tracking-widest py-4 px-4 bg-[var(--surface-sunken)]/60', col.headerClass, ]" > @@ -242,7 +242,7 @@ const getItemAtIndex = (index: number): T => { v-for="col in columns" :key="col.key" :class="[ - 'font-extrabold text-[var(--primary)] text-[11px] uppercase tracking-widest py-4 px-4 bg-[var(--surface-sunken)]/60', + 'font-extrabold text-[var(--primary)] text-xxs uppercase tracking-widest py-4 px-4 bg-[var(--surface-sunken)]/60', col.headerClass, ]" > diff --git a/console/src/components/common/data-table/DataTableToolbar.vue b/console/src/components/common/data-table/DataTableToolbar.vue index 253c2289b..7b1795187 100644 --- a/console/src/components/common/data-table/DataTableToolbar.vue +++ b/console/src/components/common/data-table/DataTableToolbar.vue @@ -56,7 +56,7 @@ const emit = defineEmits<{ {{ activeFilterCount }} diff --git a/console/src/components/common/data-table/TagFilter.vue b/console/src/components/common/data-table/TagFilter.vue index f0a79c9ae..78bbb3bb5 100644 --- a/console/src/components/common/data-table/TagFilter.vue +++ b/console/src/components/common/data-table/TagFilter.vue @@ -38,7 +38,7 @@ function isTagSelected(tag: string, currentTags: string[]) { v-for="tag in popularTags" :key="tag" variant="outline" - class="cursor-pointer px-2.5 py-1 text-[11px] font-medium transition-all duration-200 rounded-none" + class="cursor-pointer px-2.5 py-1 text-xxs font-medium transition-all duration-200 rounded-none" :class=" isTagSelected(tag, modelValue) ? 'border-[var(--accent-electric)]/30 bg-[var(--accent-electric)]/10 text-[var(--accent-electric)] hover:bg-[var(--accent-electric)]/18' @@ -52,7 +52,7 @@ function isTagSelected(tag: string, currentTags: string[]) { diff --git a/console/src/components/edge-operations/vote-control/VoteControl.vue b/console/src/components/edge-operations/vote-control/VoteControl.vue index be0ec3588..9a6c64c83 100644 --- a/console/src/components/edge-operations/vote-control/VoteControl.vue +++ b/console/src/components/edge-operations/vote-control/VoteControl.vue @@ -51,7 +51,7 @@ defineOptions({ class="h-3.5 w-3.5 active:scale-125 transition-transform" :class="{ 'fill-current': userVote === 1 }" /> - {{ + {{ formatCount(likes) }} @@ -77,7 +77,7 @@ defineOptions({ class="h-3.5 w-3.5 active:scale-125 transition-transform" :class="{ 'fill-current': userVote === -1 }" /> - {{ + {{ formatCount(dislikes) }} @@ -89,7 +89,7 @@ defineOptions({ class="h-8 px-3 rounded-none text-[var(--solarized-base01)] dark:text-[var(--solarized-base0)] flex items-center gap-1.5 select-none font-bold text-xs" > - {{ + {{ formatCount(likes) }} @@ -102,7 +102,7 @@ defineOptions({ class="h-8 px-3 rounded-none text-[var(--solarized-base01)] dark:text-[var(--solarized-base0)] flex items-center gap-1.5 select-none font-bold text-xs" > - {{ + {{ formatCount(dislikes) }} diff --git a/console/src/components/notification/NotificationBadge.vue b/console/src/components/notification/NotificationBadge.vue index 3448622c5..2c7538915 100644 --- a/console/src/components/notification/NotificationBadge.vue +++ b/console/src/components/notification/NotificationBadge.vue @@ -92,7 +92,7 @@ function goToNotifications() {

{{ localizedNotification(notification).title }}

{{ localizedNotification(notification).body }}

@@ -157,7 +157,7 @@ function goToNotifications() { diff --git a/console/src/features/sider/AppLayout.vue b/console/src/features/sider/AppLayout.vue index 07fd08807..ce8a99844 100644 --- a/console/src/features/sider/AppLayout.vue +++ b/console/src/features/sider/AppLayout.vue @@ -98,7 +98,7 @@ const isActiveNav = (item: NavItem) => { {{ item.label }} {{ t("common.labels.soon") }} @@ -113,7 +113,7 @@ const isActiveNav = (item: NavItem) => { {{ item.label }} {{ t("common.labels.soon") }} diff --git a/console/src/features/sider/NavUser.vue b/console/src/features/sider/NavUser.vue index 7357280c0..76b3dd2cb 100644 --- a/console/src/features/sider/NavUser.vue +++ b/console/src/features/sider/NavUser.vue @@ -261,7 +261,7 @@ async function handleLogout() { {{ unreadLabel }} diff --git a/console/src/features/sider/SidebarNav.vue b/console/src/features/sider/SidebarNav.vue index 56560eace..3ba383086 100644 --- a/console/src/features/sider/SidebarNav.vue +++ b/console/src/features/sider/SidebarNav.vue @@ -97,7 +97,7 @@ const getItemIconColorClass = (url?: string) => { > {{ t(section.name).toUpperCase() }} @@ -116,7 +116,7 @@ const getItemIconColorClass = (url?: string) => { :key="item.title" :to="item.url || '#'" :class="[ - 'group flex items-center gap-2.5 px-3 py-1.5 transition-all duration-200 select-none text-[11px] font-medium rounded-none h-8.5 mx-1', + 'group flex items-center gap-2.5 px-3 py-1.5 transition-all duration-200 select-none text-xxs font-medium rounded-none h-8.5 mx-1', isItemActive(item.url) ? 'bg-[var(--accent-electric)]/10 text-[var(--accent-electric)] font-bold' : 'text-[var(--solarized-base01)] dark:text-[var(--silver-400)] hover:bg-[var(--accent-electric)]/4 hover:text-[var(--accent-electric)] hover:translate-x-0.5', @@ -134,7 +134,7 @@ const getItemIconColorClass = (url?: string) => { {{ item.badge }} @@ -166,7 +166,7 @@ const getItemIconColorClass = (url?: string) => { {{ item.badge }} @@ -188,7 +188,7 @@ const getItemIconColorClass = (url?: string) => { :key="item.title" :to="item.url || '#'" :class="[ - 'group flex items-center gap-2.5 px-3 py-1.5 transition-all duration-200 select-none text-[11px] font-medium rounded-none h-8.5 mx-1', + 'group flex items-center gap-2.5 px-3 py-1.5 transition-all duration-200 select-none text-xxs font-medium rounded-none h-8.5 mx-1', isItemActive(item.url) ? 'bg-[var(--accent-electric)]/10 text-[var(--accent-electric)] font-bold' : 'text-[var(--solarized-base01)] dark:text-[var(--silver-400)] hover:bg-[var(--accent-electric)]/4 hover:text-[var(--accent-electric)] hover:translate-x-0.5', @@ -206,7 +206,7 @@ const getItemIconColorClass = (url?: string) => { {{ item.badge }} @@ -238,7 +238,7 @@ const getItemIconColorClass = (url?: string) => { {{ item.badge }} diff --git a/console/src/features/sider/components/SidebarListSections.vue b/console/src/features/sider/components/SidebarListSections.vue index 92f3ee154..e6c4ff4f1 100644 --- a/console/src/features/sider/components/SidebarListSections.vue +++ b/console/src/features/sider/components/SidebarListSections.vue @@ -62,7 +62,7 @@ const isListActive = (id: string | number) => {
{
  • {{ item.name }} - {{ + {{ item.problemCount }} @@ -128,7 +128,7 @@ const isListActive = (id: string | number) => {
    {
  • {{ item.name }} - {{ + {{ item.problemCount }} @@ -207,7 +207,7 @@ const isListActive = (id: string | number) => {
    { >
    {{ item.name }} - {{ + {{ item.problemCount }} @@ -259,7 +259,7 @@ const isListActive = (id: string | number) => {
    {
  • {{ item.name }} - {{ + {{ item.problemCount }} diff --git a/console/src/i18n/locales/en-US/auth.ts b/console/src/i18n/locales/en-US/auth.ts index fa3ff4055..d77ff6f53 100644 --- a/console/src/i18n/locales/en-US/auth.ts +++ b/console/src/i18n/locales/en-US/auth.ts @@ -33,6 +33,8 @@ export default { subtitle: "Enter your information to create a new account", username: "Username", usernamePlaceholder: "Enter a username", + name: "Display name", + namePlaceholder: "How should we display your name?", email: "Email", emailPlaceholder: "Enter your email address", password: "Password", @@ -96,6 +98,7 @@ export default { emailAlreadyExists: "Email already in use", usernameAlreadyExists: "Username already taken", sessionExpired: "Session expired. Please login again.", + passwordsDoNotMatch: "Passwords do not match", }, // Guest user diff --git a/console/src/i18n/locales/zh-CN/auth.ts b/console/src/i18n/locales/zh-CN/auth.ts index 1ad90af6a..90c345ddd 100644 --- a/console/src/i18n/locales/zh-CN/auth.ts +++ b/console/src/i18n/locales/zh-CN/auth.ts @@ -33,6 +33,8 @@ export default { subtitle: "输入您的信息创建新账户", username: "用户名", usernamePlaceholder: "请输入用户名", + name: "显示名称", + namePlaceholder: "您希望如何展示您的名称?", email: "邮箱", emailPlaceholder: "请输入邮箱地址", password: "密码", @@ -94,6 +96,7 @@ export default { emailAlreadyExists: "邮箱已被使用", usernameAlreadyExists: "用户名已被使用", sessionExpired: "登录已过期,请重新登录", + passwordsDoNotMatch: "两次输入的密码不一致", }, // Guest user diff --git a/console/src/lib/utils.ts b/console/src/lib/utils.ts index 88283f013..4d43cbb0a 100644 --- a/console/src/lib/utils.ts +++ b/console/src/lib/utils.ts @@ -1,7 +1,8 @@ -import type { ClassValue } from "clsx"; -import { clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} +// --------------------------------------------------------------------------- +// cn — className concatenation helper (clsx + tailwind-merge) +// +// Re-exported from `@ulticode/auth-core` so console + management + +// shared/auth-ui share one implementation. See shared/auth-core/src/utils.ts. +// --------------------------------------------------------------------------- +export { cn } from "@/shared/auth-core/src"; +export type { ClassValue } from "clsx"; \ No newline at end of file diff --git a/console/src/main.ts b/console/src/main.ts index e421008aa..55a8e40d0 100644 --- a/console/src/main.ts +++ b/console/src/main.ts @@ -10,6 +10,7 @@ import "./assets/markdown.css"; // Import PWA registration (this registers the service worker) import "@/pwa-register"; import { initTheme } from "@/composables/useTheme"; +import { applyTypographyDensity } from "@/composables/useTypographyDensity"; /** * Application Bootstrap @@ -34,6 +35,13 @@ async function bootstrap() { // call is a no-op for the initial paint but registers the OS-preference // listener for live updates. initTheme(); + // Console is the comfortable reading surface: long-form problem + // statements, markdown, forum content, and personal dashboards all + // benefit from the default density. The shared typography CSS exposes + // a `data-uc-density="comfortable"` profile (see + // shared/theme/src/typography.css + docs/SHARED_TYPOGRAPHY_DESIGN.md + // §7). The helper below is the only place this attribute is written. + applyTypographyDensity("comfortable"); const app = createApp(App); const pinia = createPinia(); diff --git a/console/src/stores/editorSettings.ts b/console/src/stores/editorSettings.ts index f89dab243..9dbeb16eb 100644 --- a/console/src/stores/editorSettings.ts +++ b/console/src/stores/editorSettings.ts @@ -26,7 +26,7 @@ const DEFAULT_SETTINGS: EditorSettings = { wordWrap: false, minimap: false, lineNumbers: "on", - fontFamily: "JetBrains Mono, Menlo, Monaco, Courier New, monospace", + fontFamily: '"LXGW WenKai", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", monospace', reduceMotion: false, highContrast: false, }; diff --git a/console/src/types/auth.ts b/console/src/types/auth.ts index e4c9b0b75..03e1e72bb 100644 --- a/console/src/types/auth.ts +++ b/console/src/types/auth.ts @@ -4,6 +4,13 @@ * Backend reference: * - LoginDTO, RegisterDTO, LoginResponse, UserVO, UserWithCsrfVO * - in backend-spring/src/main/java/com/ulticode/modules/auth/ + * + * NOTE: This file is console-specific. The User shape uses camelCase to + * match how console wires the response into Pinia stores / Vue templates. + * Management uses `shared/auth-core`'s snake_case User (see + * `shared/auth-core/src/types.ts`); the two are intentionally NOT + * unified in this refactor to avoid a wide blast radius. If a future + * PR aligns them, deprecate this file in favor of shared re-exports. */ /** @@ -77,4 +84,4 @@ export interface ForgotPasswordRequest { export interface ResetPasswordRequest { token: string; newPassword: string; -} +} \ No newline at end of file diff --git a/console/src/views/LandingView.vue b/console/src/views/LandingView.vue index 18ba5a0c4..385cffe63 100644 --- a/console/src/views/LandingView.vue +++ b/console/src/views/LandingView.vue @@ -128,7 +128,7 @@ const handleLoginRedirect = () => { {{ t("landing.brand") }} - {{ + {{ t("landing.version") }}
    @@ -188,7 +188,7 @@ const handleLoginRedirect = () => {
    {{ t("landing.coreSessionReady") }}
    @@ -229,7 +229,7 @@ const handleLoginRedirect = () => {
    {{ t("landing.systemBootStatus") }} {{ t("landing.bootOk") }} @@ -287,7 +287,7 @@ const handleLoginRedirect = () => { :key="lang" @click="selectedLang = lang" :class="[ - 'px-2 py-0.5 font-data text-[10px] uppercase border transition-colors', + 'px-2 py-0.5 font-data text-2xs uppercase border transition-colors', selectedLang === lang ? 'border-[var(--accent-electric)] bg-[color-mix(in_oklch,var(--accent-electric)_15%,transparent)] text-[var(--accent-electric)]' : 'border-transparent text-muted-foreground hover:text-foreground', @@ -299,7 +299,7 @@ const handleLoginRedirect = () => {
    {{ codeSnippets[selectedLang as keyof typeof codeSnippets] }}
    @@ -321,7 +321,7 @@ const handleLoginRedirect = () => { v-for="(log, i) in simulationTerminalText" :key="i" :class="[ - 'font-data text-[10px] leading-tight', + 'font-data text-2xs leading-tight', log.includes('[PASS]') ? 'text-[var(--terminal-green)]' : log.includes('STATUS') @@ -333,7 +333,7 @@ const handleLoginRedirect = () => {

    {{ t("landing.compileSuccess") }} @@ -365,7 +365,7 @@ const handleLoginRedirect = () => { class="precision-card precision-card--featured lg:col-span-3 border border-[var(--border)] bg-card p-6 shadow-[3px_3px_0px_0px_var(--border)] grid gap-6 md:grid-cols-12 relative overflow-hidden" >
    PRIMARY ENGINE
    @@ -381,7 +381,7 @@ const handleLoginRedirect = () => {
    HIGH PERFORMANCE

    @@ -397,7 +397,7 @@ const handleLoginRedirect = () => { >
    EXECUTION SPEED

    {

    CONTAINERIZATION

    {

    SUPPORTED LANGS

    {

    - // SANDBOX RUNNER ACTIVE {

    {{ t("landing.contestsFooter") }}
    @@ -510,7 +510,7 @@ const handleLoginRedirect = () => {

    {{ t("landing.feedFooter") }}
    @@ -534,7 +534,7 @@ const handleLoginRedirect = () => {

    {{ t("landing.badgesFooter") }}
    @@ -557,7 +557,7 @@ const handleLoginRedirect = () => {
    {{ t("landing.solverCapacity") }} 84,912 / 100,000 @@ -571,7 +571,7 @@ const handleLoginRedirect = () => {
    {{ t("landing.latencyStability") }} 99.86% @@ -608,7 +608,7 @@ const handleLoginRedirect = () => { 1,348,902

    {{ t("landing.totalSubmissionsDesc") }}

    @@ -625,7 +625,7 @@ const handleLoginRedirect = () => { 42,918

    {{ t("landing.activeSolversDesc") }}

    @@ -642,7 +642,7 @@ const handleLoginRedirect = () => { 14.2 ms

    {{ t("landing.compilationAvgDesc") }}

    @@ -659,7 +659,7 @@ const handleLoginRedirect = () => { 142 {{ t("landing.eventsUnit") }}

    {{ t("landing.contestsCompletedDesc") }}

    @@ -668,7 +668,7 @@ const handleLoginRedirect = () => {

    [INFO] diff --git a/console/src/views/auth/ForgotPasswordView.vue b/console/src/views/auth/ForgotPasswordView.vue index 9845ab656..9e54027f7 100644 --- a/console/src/views/auth/ForgotPasswordView.vue +++ b/console/src/views/auth/ForgotPasswordView.vue @@ -1,18 +1,22 @@ + \ No newline at end of file diff --git a/console/src/views/auth/LoginView.vue b/console/src/views/auth/LoginView.vue index 7403bc025..4bd77052c 100644 --- a/console/src/views/auth/LoginView.vue +++ b/console/src/views/auth/LoginView.vue @@ -1,15 +1,24 @@ - - + + + + + \ No newline at end of file diff --git a/console/src/views/auth/RegisterView.vue b/console/src/views/auth/RegisterView.vue index cd33b5726..71d6e63d9 100644 --- a/console/src/views/auth/RegisterView.vue +++ b/console/src/views/auth/RegisterView.vue @@ -1,15 +1,26 @@ - - + + + + + \ No newline at end of file diff --git a/console/src/views/auth/ResetPasswordView.vue b/console/src/views/auth/ResetPasswordView.vue index 5166eb569..efb1a74bc 100644 --- a/console/src/views/auth/ResetPasswordView.vue +++ b/console/src/views/auth/ResetPasswordView.vue @@ -1,18 +1,21 @@ + \ No newline at end of file diff --git a/console/src/views/auth/components/RegisterForm.vue b/console/src/views/auth/components/RegisterForm.vue deleted file mode 100644 index 4798ec33d..000000000 --- a/console/src/views/auth/components/RegisterForm.vue +++ /dev/null @@ -1,229 +0,0 @@ - - - - - diff --git a/console/src/views/contest/ContestBrowseView.vue b/console/src/views/contest/ContestBrowseView.vue index ead035266..ab462395c 100644 --- a/console/src/views/contest/ContestBrowseView.vue +++ b/console/src/views/contest/ContestBrowseView.vue @@ -173,7 +173,7 @@ onMounted(loadData); {{ t("contest.list.running") }} {{ runningContests.length }} diff --git a/console/src/views/contest/components/GlobalRanking.vue b/console/src/views/contest/components/GlobalRanking.vue index 651a778a4..97f30be5a 100644 --- a/console/src/views/contest/components/GlobalRanking.vue +++ b/console/src/views/contest/components/GlobalRanking.vue @@ -53,7 +53,7 @@ const { t } = useI18n(); />

    2ND
    @@ -65,7 +65,7 @@ const { t } = useI18n();

    - {{ rankings[1].contestsAttended || 0 }} {{ t("contest.ranking.title", "Contests") }} @@ -101,7 +101,7 @@ const { t } = useI18n(); />
    1ST
    @@ -113,7 +113,7 @@ const { t } = useI18n();

    - {{ rankings[0].contestsAttended || 0 }} {{ t("contest.ranking.title", "Contests") }} @@ -149,7 +149,7 @@ const { t } = useI18n(); />
    3RD
    @@ -161,7 +161,7 @@ const { t } = useI18n();

    - {{ rankings[2].contestsAttended || 0 }} {{ t("contest.ranking.title", "Contests") }} diff --git a/console/src/views/contest/components/MyContests.vue b/console/src/views/contest/components/MyContests.vue index 3d33238a3..91cf1f6fa 100644 --- a/console/src/views/contest/components/MyContests.vue +++ b/console/src/views/contest/components/MyContests.vue @@ -198,7 +198,7 @@ function navigateToContest(slug: string) { {{ t("contest.list.rated") }} diff --git a/console/src/views/contest/components/PastContests.vue b/console/src/views/contest/components/PastContests.vue index 622cd71b3..c1f9b1218 100644 --- a/console/src/views/contest/components/PastContests.vue +++ b/console/src/views/contest/components/PastContests.vue @@ -146,7 +146,7 @@ const visiblePages = computed(() => {
    {{ t("contest.list.finished") }} diff --git a/console/src/views/contest/components/RunningContests.vue b/console/src/views/contest/components/RunningContests.vue index 4a0e66344..a9e0aa317 100644 --- a/console/src/views/contest/components/RunningContests.vue +++ b/console/src/views/contest/components/RunningContests.vue @@ -113,7 +113,7 @@ onUnmounted(() => {

    {
    { >
    {{ t("contest.list.liveProgress") }}

    {{ t("contest.detail.startTime") }}

    @@ -279,7 +279,7 @@ function getErrorMessage(error: unknown, fallback: string) {

    {{ t("contest.detail.duration") }}

    @@ -302,7 +302,7 @@ function getErrorMessage(error: unknown, fallback: string) {

    {{ t("contest.detail.participants") }}

    @@ -324,7 +324,7 @@ function getErrorMessage(error: unknown, fallback: string) {

    {{ t("contest.types.title") }}

    diff --git a/console/src/views/contest/detailed/components/ContestHeader.vue b/console/src/views/contest/detailed/components/ContestHeader.vue index 1cae4289b..e45c6bba9 100644 --- a/console/src/views/contest/detailed/components/ContestHeader.vue +++ b/console/src/views/contest/detailed/components/ContestHeader.vue @@ -39,7 +39,7 @@ const { t } = useI18n(); ? 'default' : 'secondary' " - class="rounded-none px-3 h-5 font-bold uppercase text-[9px] tracking-widest border border-current/25 bg-current/5" + class="rounded-none px-3 h-5 font-bold uppercase text-2xs tracking-widest border border-current/25 bg-current/5" :class=" contest.status === 'RUNNING' ? 'text-[var(--terminal-red)] border-[var(--terminal-red)]' diff --git a/console/src/views/contest/detailed/components/ContestProblemList.vue b/console/src/views/contest/detailed/components/ContestProblemList.vue index 4a0ff5c45..04fa1b088 100644 --- a/console/src/views/contest/detailed/components/ContestProblemList.vue +++ b/console/src/views/contest/detailed/components/ContestProblemList.vue @@ -153,27 +153,27 @@ function rowAction( > # {{ t("contest.detail.problemHeaders.title") }} {{ t("contest.detail.problemHeaders.difficulty") }} {{ t("contest.detail.problemHeaders.score") }} {{ t("contest.detail.problemHeaders.acceptance") }} {{ t("contest.detail.problemHeaders.action") }} @@ -215,16 +215,16 @@ function rowAction( {{ problem.title }} - {{ + {{ problem.title }}
    @@ -255,7 +255,7 @@ function rowAction( variant="outline" :class="[ getDifficultyColor(problem.difficulty || 'Medium'), - 'font-black text-[9px] uppercase tracking-wider h-5 px-2 rounded-none border-current/25 bg-current/5', + 'font-black text-2xs uppercase tracking-wider h-5 px-2 rounded-none border-current/25 bg-current/5', ]" > {{ @@ -296,7 +296,7 @@ function rowAction( :variant="rowAction(problem.problemId).variant" :disabled="rowAction(problem.problemId).key === 'locked'" :class="[ - 'rounded-none px-3 h-8 font-black text-[10px] uppercase tracking-widest cursor-pointer', + 'rounded-none px-3 h-8 font-black text-2xs uppercase tracking-widest cursor-pointer', rowAction(problem.problemId).key === 'review' ? 'border border-border bg-[var(--silver-100)]/60 hover:bg-[var(--silver-100)] text-[var(--solarized-base01)] dark:text-[var(--solarized-base1)] dark:bg-[var(--solarized-base03)]/60' : rowAction(problem.problemId).key === 'continue' diff --git a/console/src/views/contest/detailed/components/ContestRankingTable.vue b/console/src/views/contest/detailed/components/ContestRankingTable.vue index 89f114afc..f92f45d2a 100644 --- a/console/src/views/contest/detailed/components/ContestRankingTable.vue +++ b/console/src/views/contest/detailed/components/ContestRankingTable.vue @@ -48,27 +48,27 @@ const { t } = useI18n(); > {{ t("contest.detail.rankingHeaders.rank") }} {{ t("contest.detail.rankingHeaders.user") }} {{ t("contest.detail.rankingHeaders.score") }} {{ t("contest.detail.rankingHeaders.time") }} {{ t("contest.detail.rankingHeaders.problems") }} {{ t("contest.detail.rankingHeaders.problemsSolved") }}{{ entry.username }} {{ entry.ratingTitle || "NEWBIE" }} @@ -144,7 +144,7 @@ const { t } = useI18n();
    {{ entry.problemsSolved }} diff --git a/console/src/views/contest/detailed/components/ContestRegistration.vue b/console/src/views/contest/detailed/components/ContestRegistration.vue index 1f810d337..9c9bffd1e 100644 --- a/console/src/views/contest/detailed/components/ContestRegistration.vue +++ b/console/src/views/contest/detailed/components/ContestRegistration.vue @@ -51,13 +51,13 @@ const statusColor = computed(() => { >

    {{ t("contest.detail.contestStatus") }}

    {{ t("contest.detail.details") || "CONTEST DETAILS" }}

    @@ -188,7 +188,7 @@ const statusColor = computed(() => {
    @@ -391,7 +391,7 @@ onMounted(loadData); class="flex items-center border-b bg-muted/30 px-4 h-10 shrink-0" > {{ t("forum.post.livePreview") }}
    diff --git a/console/src/views/forum/ForumFeedView.vue b/console/src/views/forum/ForumFeedView.vue index 51e5ea962..4fd450646 100644 --- a/console/src/views/forum/ForumFeedView.vue +++ b/console/src/views/forum/ForumFeedView.vue @@ -258,7 +258,7 @@ function handlePostSave(postId: string, isSaved: boolean) {
    发布于:{{ createdAgo }}
    @@ -587,7 +587,7 @@ const handleMobileTOCClick = (id: string) => { class="flex items-center gap-1.5 flex-wrap mt-1" > {{ thread.flair.text }} @@ -601,7 +601,7 @@ const handleMobileTOCClick = (id: string) => { class="bg-[var(--card)] border border-border shadow-sm p-4" >
    文章大纲
    diff --git a/console/src/views/forum/components/ForumPostCard.vue b/console/src/views/forum/components/ForumPostCard.vue index c47775397..f003806f9 100644 --- a/console/src/views/forum/components/ForumPostCard.vue +++ b/console/src/views/forum/components/ForumPostCard.vue @@ -286,7 +286,7 @@ function handleCardClick(event: MouseEvent) { v-if="post.flair" variant="secondary" :class="[ - 'ml-auto sm:ml-2 rounded-none px-2 py-0 text-[10px] h-5', + 'ml-auto sm:ml-2 rounded-none px-2 py-0 text-2xs h-5', flairClasses[post.flair.type], ]" > @@ -316,7 +316,7 @@ function handleCardClick(event: MouseEvent) { class="markdown-view" >
    @@ -386,7 +386,7 @@ function handleCardClick(event: MouseEvent) { class="markdown-view" >
    @@ -252,7 +252,7 @@ async function handleShare() { @@ -260,7 +260,7 @@ async function handleShare() { {{ t("forum.post.locked") }} @@ -357,7 +357,7 @@ async function handleShare() { >

    {{ t("forum.post.poll.title") }}

    @@ -370,7 +370,7 @@ async function handleShare() { class="space-y-1" >
    {{ option.label }} {{ @@ -387,7 +387,7 @@ async function handleShare() {
  • -

    +

    {{ media.totalVotes }} {{ t("forum.post.poll.votes") }} · {{ t("forum.post.poll.closes") }} {{ media.closesAt }} { class="absolute right-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />

    -

    +

    {{ t("personal.account.usernameUnique") }}

    @@ -234,7 +234,7 @@ onMounted(async () => { v-model="user.name" placeholder="John Doe" /> -

    +

    {{ t("personal.profile.displayName") }}

    @@ -252,7 +252,7 @@ onMounted(async () => { :placeholder="t('personal.profile.bioPlaceholder')" class="min-h-[120px] resize-none" /> -

    +

    {{ t("personal.profile.bioPlaceholder") }}

    diff --git a/console/src/views/personal/BookmarksView.vue b/console/src/views/personal/BookmarksView.vue index 392c5f5ff..02865a0f2 100644 --- a/console/src/views/personal/BookmarksView.vue +++ b/console/src/views/personal/BookmarksView.vue @@ -412,7 +412,7 @@ onMounted(() => { > {{ item.targetType.toLowerCase().replace("_", " ") diff --git a/console/src/views/personal/ForumPostsView.vue b/console/src/views/personal/ForumPostsView.vue index 57bd13ed7..2cc5088d4 100644 --- a/console/src/views/personal/ForumPostsView.vue +++ b/console/src/views/personal/ForumPostsView.vue @@ -132,7 +132,7 @@ onMounted(loadPosts); {{ post.community?.name ?? "General" }}
    {{ @@ -173,7 +173,7 @@ onMounted(loadPosts); {{ post.flair.text }} @@ -261,7 +261,7 @@ onMounted(loadPosts); post.stats?.comments ?? 0 }} {{ t("personal.forumPosts.stats.comments") }}
    @@ -273,7 +273,7 @@ onMounted(loadPosts); post.stats?.views ?? 0 }}
    {{ t("personal.forumPosts.stats.views") }} diff --git a/console/src/views/personal/ProblemListsView.vue b/console/src/views/personal/ProblemListsView.vue index 6e72500e2..3a0ed2365 100644 --- a/console/src/views/personal/ProblemListsView.vue +++ b/console/src/views/personal/ProblemListsView.vue @@ -246,7 +246,7 @@ function onCategoryUnsave(listId: string) { {{ t("personal.problemLists.tabs.myLists") }} {{ data.ownLists.length }} @@ -257,7 +257,7 @@ function onCategoryUnsave(listId: string) { {{ t("personal.problemLists.tabs.saved") }} {{ totalSavedCount }} @@ -353,13 +353,13 @@ function onCategoryUnsave(listId: string) {
    {{ t("personal.problemLists.listCard.featured") }} {{ t("personal.problemLists.listCard.saved") }} @@ -381,7 +381,7 @@ function onCategoryUnsave(listId: string) {