feat: 站点单页、时间线导入与行级 BBCode 短代码
首页右栏接入单页入口与公告置顶/全部页;Markdown 编辑器支持可视化时间线与 Git 导入;隐藏块改为 [hide]/[timeline] 命名闭合,并修复单页/公告 GORM 更新只改 updated_at 的问题。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
/** 正文 :::hide 块解析(与 backend/markdown/hide.go 契约一致) */
|
||||
/** 正文 [hide]…[/hide] 块解析(与 backend/markdown/hide.go 契约一致) */
|
||||
|
||||
export type HideKind = "login" | "reply" | "points" | "password";
|
||||
|
||||
@@ -13,25 +13,34 @@ export type HideSegment =
|
||||
};
|
||||
|
||||
const HIDE_KINDS = new Set<HideKind>(["login", "reply", "points", "password"]);
|
||||
const HIDE_OPEN_RE = /^\[hide(?:\s+(.+))?\]$/i;
|
||||
|
||||
function isClose(line: string): boolean {
|
||||
return line.trim() === ":::";
|
||||
return line.trim().toLowerCase() === "[/hide]";
|
||||
}
|
||||
|
||||
/** 与 Go parseOpenMarker 对齐::::hide <kind> [points|password] [locked] */
|
||||
/** 与 Go parseOpenMarker 对齐:[hide <kind>[=arg] [locked]] */
|
||||
function parseOpen(trimmed: string): {
|
||||
kind: HideKind;
|
||||
points: number;
|
||||
locked: boolean;
|
||||
} | null {
|
||||
if (!trimmed.startsWith(":::hide")) return null;
|
||||
const parts = trimmed
|
||||
.slice(":::hide".length)
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean);
|
||||
const m = trimmed.match(HIDE_OPEN_RE);
|
||||
if (!m) return null;
|
||||
const rest = (m[1] || "").trim();
|
||||
if (!rest) return null;
|
||||
const parts = rest.split(/\s+/).filter(Boolean);
|
||||
if (parts.length === 0) return null;
|
||||
const kind = parts[0] as HideKind;
|
||||
|
||||
let head = parts[0];
|
||||
let kind = head;
|
||||
let arg = "";
|
||||
const eq = head.indexOf("=");
|
||||
if (eq >= 0) {
|
||||
kind = head.slice(0, eq);
|
||||
arg = head.slice(eq + 1);
|
||||
}
|
||||
kind = kind.toLowerCase() as HideKind;
|
||||
if (!HIDE_KINDS.has(kind)) return null;
|
||||
|
||||
let idx = 1;
|
||||
@@ -39,20 +48,21 @@ function parseOpen(trimmed: string): {
|
||||
let locked = false;
|
||||
|
||||
if (kind === "points") {
|
||||
if (idx >= parts.length) return null;
|
||||
const n = Number(parts[idx]);
|
||||
if (!arg) return null;
|
||||
const n = Number(arg);
|
||||
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++;
|
||||
// 脱敏:[hide password locked];原文密码不在前端解析保留
|
||||
if (arg) {
|
||||
/* skip plaintext */
|
||||
}
|
||||
} else if (arg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (; idx < parts.length; idx++) {
|
||||
if (parts[idx] === "locked") locked = true;
|
||||
if (parts[idx].toLowerCase() === "locked") locked = true;
|
||||
else return null;
|
||||
}
|
||||
return { kind, points, locked };
|
||||
|
||||
Reference in New Issue
Block a user