Files
jiang13-bbs/frontend/lib/inlineShortcodes.ts
freefire 99c0f0319d feat: 品牌裁剪弹窗/Markdown 行内短代码与详情视频块等
新增:
- BrandCropModal 品牌图裁剪弹窗 + cropImage 工具
- Markdown 行内短代码(inlineShortcodes + remark 插件)
- MdDetails 折叠块、MdVideo 视频块组件
- directiveBlocks/inlineShortcodes 测试

其它:
- 聊天后端(handler/service chat)调整
- Markdown 渲染链路、编辑器、全局样式与品牌相关面板更新
2026-10-02 01:40:06 +08:00

76 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 行内短代码解析(纯函数):
* ==高亮== / [kbd]Ctrl[/kbd] / [color=前景( bg=背景)]文字[/color] / [bg=背景]文字[/bg]
* 颜色值白名单(#hex 或 CSS 颜色名),非法值整段按普通文本回退,防 CSS 注入。
*/
export type InlineShortcodeSegment =
| { type: "text"; value: string }
| { type: "mark"; value: string }
| { type: "kbd"; value: string }
| { type: "color"; value: string; fg?: string; bg?: string };
export const COLOR_VALUE_RE = /^(#[0-9a-fA-F]{3,8}|[a-zA-Z]{1,32})$/;
export function isValidColorValue(v: string): boolean {
return COLOR_VALUE_RE.test(v);
}
/**
* 扫描顺序:[kbd] → [color] → [bg] → ==…==。
* 标签内容不含 [ 与换行,避免嵌套歧义;未闭合标记保持字面。
* 无任何短代码命中时返回 null(调用方保留原节点)。
*/
const SCAN_RE =
/\[kbd\]([^\[\]\n]+)\[\/kbd\]|\[color=([^\]\s\[]+)(?:\s+bg=([^\]\s\[]+))?\]([^\[\]\n]+)\[\/color\]|\[bg=([^\]\s\[]+)\]([^\[\]\n]+)\[\/bg\]|==([^=\n]+)==/gi;
export function splitInlineShortcodes(
text: string
): InlineShortcodeSegment[] | null {
if (!text) return null;
const re = new RegExp(SCAN_RE.source, "gi");
const out: InlineShortcodeSegment[] = [];
let last = 0;
let changed = false;
let m: RegExpExecArray | null;
while ((m = re.exec(text))) {
const [full, kbd, colorFg, colorBg, colorBody, bgVal, bgBody, markBody] = m;
const before = text.slice(last, m.index);
if (before) out.push({ type: "text", value: before });
last = m.index + full.length;
if (kbd !== undefined) {
out.push({ type: "kbd", value: kbd });
changed = true;
continue;
}
if (colorFg !== undefined) {
if (!isValidColorValue(colorFg) || (colorBg !== undefined && !isValidColorValue(colorBg))) {
out.push({ type: "text", value: full });
continue;
}
out.push(
colorBg
? { type: "color", value: colorBody, fg: colorFg, bg: colorBg }
: { type: "color", value: colorBody, fg: colorFg }
);
changed = true;
continue;
}
if (bgVal !== undefined) {
if (!isValidColorValue(bgVal)) {
out.push({ type: "text", value: full });
continue;
}
out.push({ type: "color", value: bgBody, bg: bgVal });
changed = true;
continue;
}
out.push({ type: "mark", value: markBody });
changed = true;
}
const rest = text.slice(last);
if (rest) out.push({ type: "text", value: rest });
return changed ? out : null;
}