重构+优化: CR-04+06 AiChat 流式渲染改进 + CR-10 useRendered DRY 抽
- CR-04: splitBlocks 正则切块改 marked.lexer() tokens 遍历(type=code 的 .raw 整块,其余按双换行切),正确处理行首空格/4+反引号嵌套/未闭合围栏——lexer 与 parse 同 marked 实例同 gfm 规则,切块边界与渲染边界一致。 - CR-06: watch(mdReady) 翻 true 时主动 scheduleStreamParse 重算,修首屏 marked 慢+暂无 delta 致末段停留 escapeFallback 纯文本。 - CR-10: useMarkdown 抽 useRendered(computed 读 mdReady+renderMd + ensureLoaded 幂等预热),4 view(TaskDetail/ProjectDetail/Ideas/Knowledge)删手写 renderedDesc computed + onMounted loadMarkdown 重复,loadMarkdown 提模块级。DRY 行为零变化。 批4 wlnazlvdc,vue-tsc 0 err
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
//! scheduleStreamParse)继续调用 useMarkdown 暴露的 mdReady/escapeFallback,
|
||||
//! 渲染结果不变。
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref, type ComputedRef } from 'vue'
|
||||
|
||||
// ═══ 模块级单例:AiChat 流式核心与历史 renderMd 共享同一份渲染器/缓存 ═══
|
||||
let _marked: typeof import('marked')['marked'] | null = null
|
||||
@@ -26,17 +26,17 @@ const mdReady = ref(false)
|
||||
// 历史消息整段缓存(文本不变,重渲染命中省去 marked+sanitize 全量解析)
|
||||
const _mdCache = new Map<string, string>()
|
||||
|
||||
export function useMarkdown() {
|
||||
/** 异步加载 marked + DOMPurify;加载完成后 mdReady 翻转触发重渲染 */
|
||||
async function loadMarkdown() {
|
||||
const [{ marked }, dp] = await Promise.all([import('marked'), import('dompurify')])
|
||||
marked.setOptions({ breaks: true, gfm: true })
|
||||
_marked = marked
|
||||
_purify = dp.default
|
||||
_mdCache.clear() // 渲染器就绪,清兜底缓存触发完整重渲染
|
||||
mdReady.value = true
|
||||
}
|
||||
/** 异步加载 marked + DOMPurify;加载完成后 mdReady 翻转触发重渲染(模块级,所有调用方共享) */
|
||||
async function loadMarkdown() {
|
||||
const [{ marked }, dp] = await Promise.all([import('marked'), import('dompurify')])
|
||||
marked.setOptions({ breaks: true, gfm: true })
|
||||
_marked = marked
|
||||
_purify = dp.default
|
||||
_mdCache.clear() // 渲染器就绪,清兜底缓存触发完整重渲染
|
||||
mdReady.value = true
|
||||
}
|
||||
|
||||
export function useMarkdown() {
|
||||
return {
|
||||
mdReady,
|
||||
loadMarkdown,
|
||||
@@ -50,6 +50,37 @@ export function useMarkdown() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情页 Markdown 渲染辅助:封装 computed(读 mdReady 触发响应式 + renderMd(getText))
|
||||
* + ensureLoaded(幂等预热 loadMarkdown)。消除 TaskDetail/ProjectDetail/Ideas/Knowledge
|
||||
* 四视图手写 computed + onMounted(loadMarkdown) 重复。
|
||||
*
|
||||
* - getText: 返回待渲染文本(通常读响应式 source.value?.description ?? ''),
|
||||
* 文本变化时 computed 重算。
|
||||
* - rendered: computed<string>,mdReady 翻转/文本变化触发重渲染,行为与抽前一致。
|
||||
* - ensureLoaded():幂等触发后台预热,首次调用执行 loadMarkdown,后续 no-op。
|
||||
*
|
||||
* 行为零变化:渲染/预热逻辑未改,仅 DRY 抽取。
|
||||
*/
|
||||
export function useRendered(getText: () => string): {
|
||||
rendered: ComputedRef<string>
|
||||
ensureLoaded: () => void
|
||||
} {
|
||||
const rendered = computed(() => {
|
||||
void mdReady.value // 触发响应式依赖:mdReady 翻转时 computed 重算
|
||||
return renderMd(getText())
|
||||
})
|
||||
|
||||
let loaded = false
|
||||
function ensureLoaded() {
|
||||
if (loaded) return
|
||||
loaded = true
|
||||
loadMarkdown()
|
||||
}
|
||||
|
||||
return { rendered, ensureLoaded }
|
||||
}
|
||||
|
||||
function escapeHtml(text: string): string {
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user