feat(hide): 密码可见隐藏块,发帖双栏预览与门禁体验修复

增加密码解锁与游客签名 cookie;发帖页对齐 1440 并默认双栏预览;修复 locked 解析、按钮对比度与回复聚焦。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-17 05:46:42 +08:00
parent 01e2fd05ca
commit c44b0efa7d
23 changed files with 911 additions and 132 deletions

View File

@@ -1,6 +1,6 @@
/** 正文 :::hide 块解析(与 backend/markdown/hide.go 契约一致) */
export type HideKind = "login" | "reply" | "points";
export type HideKind = "login" | "reply" | "points" | "password";
export type HideSegment =
| { type: "md"; text: string }
@@ -12,26 +12,50 @@ export type HideSegment =
body: string;
};
const OPEN_RE = /^:::hide\s+(login|reply|points)(?:\s+(\d+))?(?:\s+(locked))?\s*$/;
const HIDE_KINDS = new Set<HideKind>(["login", "reply", "points", "password"]);
function isClose(line: string): boolean {
return line.trim() === ":::";
}
/** 与 Go parseOpenMarker 对齐::::hide <kind> [points|password] [locked] */
function parseOpen(trimmed: string): {
kind: HideKind;
points: number;
locked: boolean;
} | null {
const m = OPEN_RE.exec(trimmed);
if (!m) return null;
const kind = m[1] as HideKind;
const points = m[2] ? Number(m[2]) : 0;
const locked = m[3] === "locked";
if (kind === "points" && (!Number.isFinite(points) || points < 1)) {
return null;
if (!trimmed.startsWith(":::hide")) return null;
const parts = trimmed
.slice(":::hide".length)
.trim()
.split(/\s+/)
.filter(Boolean);
if (parts.length === 0) return null;
const kind = parts[0] as HideKind;
if (!HIDE_KINDS.has(kind)) return null;
let idx = 1;
let points = 0;
let locked = false;
if (kind === "points") {
if (idx >= parts.length) return null;
const n = Number(parts[idx]);
if (!Number.isFinite(n) || n < 1 || n > 100000) return null;
points = n;
idx++;
} else if (kind === "password") {
// 脱敏::::hide password locked;原文::::hide password <pwd> [locked]
if (idx < parts.length && parts[idx] !== "locked") {
idx++;
}
}
return { kind, points: kind === "points" ? points : 0, locked };
for (; idx < parts.length; idx++) {
if (parts[idx] === "locked") locked = true;
else return null;
}
return { kind, points, locked };
}
/**
@@ -87,7 +111,6 @@ export function splitHideSegments(content: string): HideSegment[] {
continue;
}
if (parseOpen(inner)) {
// 嵌套:放弃隐藏语义,整段当普通文本
buf.push(lines[i]);
i++;
found = false;
@@ -101,7 +124,6 @@ export function splitHideSegments(content: string): HideSegment[] {
j++;
}
if (!found) {
// 未闭合:开标记行起当普通文本
buf.push(lines[i]);
i++;
continue;
@@ -150,5 +172,7 @@ export function hideKindLabel(kind: HideKind): string {
return "回复可见";
case "points":
return "积分可见";
case "password":
return "密码可见";
}
}