fix: [lock:] 已解锁块渲染为锁块容器(组件层切段,同款背景边框+🔓标识)

This commit is contained in:
2026-08-13 00:12:25 +08:00
parent 914ad4239b
commit 1ba9542479
2 changed files with 96 additions and 21 deletions
+64 -21
View File
@@ -21,15 +21,67 @@ function fileTag(filename) {
/**
* 剥离内联 [lock:] 成对标签,保留块内内容(只去标签不去内容)。
* - 已解锁块:后端在剥离后的 content 里原样保留 [lock:type]...[/lock] 标签,
* 读者视角不该看到标签本身 → 去标签留内容
* - 未解锁块:后端已替换为 @@LOCK<n>@@ 占位符(不匹配此正则),不受影响
* - 支持 [lock](无参数)与 [lock:type]、[lock:type:参数] 形态;幂等,可重复调用
* 保留导出供外部/预览使用;renderContent 不再调用——组件层已用切段方案接管(保留锁块容器)。
*/
export function stripLockTags(content) {
return String(content).replace(/\[lock(?::[^\]]*)?\]([\s\S]*?)\[\/lock\]/g, '$1');
}
/** 从整块标签提取 lock 类型参数:'login' / 'reply' / 'password'(无参数返回 '' */
function extractLockType(block) {
const m = String(block).match(/^\[lock(?::([^\]]*))?\]/);
const param = (m && m[1]) || '';
return param.split(':')[0] || '';
}
/**
* 按内联 [lock:] 成对标签切段(split 带捕获组):
* 交替产出 [文本, 整块(含标签), 块内内容, 文本, …](i%3:0=文本 / 1=整块 / 2=块内内容)
*/
const INLINE_LOCK_RE = /(\[lock(?::[^\]]*)?\]([\s\S]*?)\[\/lock\])/g;
/**
* 渲染一段文本:文本段走 renderContent;内联 [lock:] 段渲染「已解锁内容」容器。
* 已解锁块(admin/作者/解锁后)后端在 content 里原样保留 [lock:...]...[/lock] 标签,
* 这里按标签切成锁块容器(同款背景+边框 + 🔓 已解锁头部),内容递归 MarkdownRenderer 渲染。
*/
function renderSegments(text, useMarkdown, rest) {
const parts = String(text).split(INLINE_LOCK_RE);
const nodes = [];
let textIdx = 0;
let lockIdx = 0;
for (let i = 0; i < parts.length; i += 1) {
const p = parts[i];
if (i % 3 === 1) {
// 整块(含标签),紧随其后 i+1 是对应块内内容
const inner = parts[i + 1] || '';
const type = extractLockType(p);
nodes.push(
<div key={'reveal' + lockIdx++} className="lock-reveal">
<div className="lock-placeholder lock-revealed">
<div className="lock-revealed-head">
<span className="material-icons" style={{ fontSize: 16 }} aria-hidden="true">lock_open</span>
已解锁内容
{type ? <span className="lock-revealed-type">{type}</span> : null}
</div>
<div className="lock-revealed-body">
<MarkdownRenderer
content={inner}
useMarkdown={useMarkdown}
{...rest}
/>
</div>
</div>
</div>
);
i += 1; // 跳过内容段(已作为 inner 渲染)
} else if (p) {
nodes.push(<RawBlock key={'text' + textIdx++} html={renderContent(p, useMarkdown)} />);
}
}
return nodes;
}
/**
* 内容渲染:useMarkdown=false 走纯文本(转义 + 标签替换 + <br>);
* useMarkdown=true 先抽取 [image:]/[file:] 标签,marked 渲染后再还原(照搬 render.js)。
@@ -37,9 +89,6 @@ export function stripLockTags(content) {
*/
export function renderContent(content, useMarkdown) {
if (content == null) return '';
// 最前段剥离内联 [lock:] 成对标签:已解锁块的标签透出问题(保留块内内容)。
// 未解锁块是 @@LOCK<n>@@ 占位(不匹配),先后顺序与占位分段无冲突
content = stripLockTags(content);
if (!useMarkdown) {
let html = escapeHtml(content);
@@ -74,11 +123,10 @@ function RawBlock({ html }) {
}
/**
* 构建渲染节点:
* 1. 详情页(locks 非空):按服务端剥离的 @@LOCK<n>@@ 占位分段——
* 已解锁且有内容(lockContent)的块递归渲染(.lock-reveal 淡入),否则渲染 LockBlock 占位
* 2. 编辑器作者预览(previewMode):剥离 [lock:type]...[/lock] 配对标签,直接展开内容
* 3. 普通:整段原样渲染
* 构建渲染节点(两层分段)
* 1. @@LOCK<n>@@ 占位分段(详情页 locks 非空)——lock 段:已解锁+有内容 → 递归渲染(.lock-reveal 淡入),
* 否则 LockBlock 锁定卡片;文本段 → 再按内联 [lock:] 切段(已解锁容器 / 文本)
* 2. locks 为空(含 previewMode 编辑预览):整段按内联 [lock:] 切段,作者视角同样显示已解锁容器
*/
function buildNodes({ content, useMarkdown, locks, unlocked, lockContent, onUnlock, onGoComment, previewMode }) {
if (content == null) return null;
@@ -89,7 +137,6 @@ function buildNodes({ content, useMarkdown, locks, unlocked, lockContent, onUnlo
// 服务端已把未解锁块替换为 @@LOCK<n>@@split 交替产出 [文本, 索引, 文本, 索引…]
const parts = str.split(/@@LOCK(\d+)@@/);
const nodes = [];
let textIdx = 0;
for (let i = 0; i < parts.length; i += 1) {
const p = parts[i];
if (i % 2 === 1) {
@@ -115,19 +162,15 @@ function buildNodes({ content, useMarkdown, locks, unlocked, lockContent, onUnlo
nodes.push(<LockBlock key={'lock' + n} index={n} type={type} onUnlock={onUnlock} onGoComment={onGoComment} />);
}
} else if (p) {
nodes.push(<RawBlock key={'text' + textIdx++} html={renderContent(p, useMarkdown)} />);
// 文本段:可能仍含内联 [lock:](已解锁块)→ 切段渲染已解锁容器
nodes.push(...renderSegments(p, useMarkdown, { locks, unlocked, lockContent, onUnlock, onGoComment }));
}
}
return nodes;
}
// 编辑器作者预览:锁定块直接展开内容(作者视角;stripLockTags 幂等,renderContent 内还会再处理一次
if (previewMode) {
const stripped = stripLockTags(str);
return [<RawBlock key="text0" html={renderContent(stripped, useMarkdown)} />];
}
return [<RawBlock key="text0" html={renderContent(content, useMarkdown)} />];
// locks 为空:整段按内联 [lock:] 切段(previewMode 作者预览同样显示「已解锁内容」容器
return renderSegments(str, useMarkdown, { locks, unlocked, lockContent, onUnlock, onGoComment });
}
/**