Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
import mergeWith from 'lodash/mergeWith';
import { isBrowser } from '@/utils/env';
import Logger from '@/Logger';

const CHART_TYPES = [
'flowchart',
Expand Down Expand Up @@ -85,7 +86,7 @@ export default class MermaidCodeEngine {
* @param {Object} mermaidOptions - Mermaid 配置选项
* @param {Object} [mermaidOptions.mermaid] - mermaid 实例对象,如果未提供会尝试从 window.mermaid 获取
* @param {Object} [mermaidOptions.mermaidAPI] - mermaidAPI 实例对象,如果未提供会尝试从 window.mermaidAPI 获取
* @param {string} [mermaidOptions.theme='default'] - 主题,可选值: 'default', 'dark', 'forest', 'neutral' 等
* @param {string} [mermaidOptions.theme='default'] - 主题,可选值'default', 'dark', 'forest', 'neutral' 等
* @param {string} [mermaidOptions.altFontFamily='sans-serif'] - 备用字体
* @param {string} [mermaidOptions.fontFamily='sans-serif'] - 主字体
* @param {string} [mermaidOptions.themeCSS] - 自定义主题 CSS 样式
Expand Down Expand Up @@ -151,7 +152,7 @@ export default class MermaidCodeEngine {
}

/**
* 转换svg为img,如果出错则直出svg
* 转换 svg 为 img,如果出错则直出 svg
* @param {string} svgCode
* @param {string} graphId
* @returns {string}
Expand All @@ -164,7 +165,7 @@ export default class MermaidCodeEngine {
try {
const svgDoc = /** @type {XMLDocument} */ (domParser.parseFromString(svgCode, 'image/svg+xml'));
const svgDom = /** @type {SVGSVGElement} */ (/** @type {any} */ (svgDoc.documentElement));
// tagName不是svg时,说明存在parse error
// tagName 不是 svg 时,说明存在 parse error
if (svgDom.tagName.toLowerCase() === 'svg') {
svgDom.style.maxWidth = '100%';
svgDom.style.height = 'auto';
Expand All @@ -180,7 +181,7 @@ export default class MermaidCodeEngine {
svgDom.getAttribute('height') === '100%' && svgDom.setAttribute('height', `${svgBox.height}`);
// fix end
svgHtml = svgDoc.documentElement.outerHTML;
// 屏蔽转img标签功能,如需要转换为img解除屏蔽即可
// 屏蔽转 img 标签功能,如需要转换为 img 解除屏蔽即可
if (this.svg2img) {
const dataUrl = `data:image/svg+xml,${encodeURIComponent(svgDoc.documentElement.outerHTML)}`;
svgHtml = `<img class="svg-img" src="${dataUrl}" alt="${graphId}" />`;
Expand Down Expand Up @@ -219,8 +220,8 @@ export default class MermaidCodeEngine {
/**
* 如果开启了流式渲染,当前有上次渲染结果时,使用上次渲染结果
* 这里有赌的成分
* 流式输出场景,只有最后一个mermaid代码块在流式输出,随着最后一个mermaid流式输出,mermaid的渲染有概率会失败
* 这里赌的是只有一个mermaid代码块需要渲染
* 流式输出场景,只有最后一个 mermaid 代码块在流式输出,随着最后一个 mermaid 流式输出,mermaid 的渲染有概率会失败
* 这里赌的是只有一个 mermaid 代码块需要渲染
*/
if ($engine.$cherry.options.engine.global.flowSessionContext && this.lastRenderedCode) {
return this.lastRenderedCode;
Expand Down Expand Up @@ -249,6 +250,7 @@ export default class MermaidCodeEngine {

asyncRender(graphId, src, sign, $engine, props) {
$engine.asyncRenderHandler.add(graphId);

this.mermaidAPIRefs
.render(graphId, src, this.mermaidCanvas)
.then(({ svg: svgCode }) => {
Expand All @@ -260,10 +262,10 @@ export default class MermaidCodeEngine {
.catch(() => {
/**
* 如果开启了流式渲染,当前有上次渲染结果时,使用上次渲染结果
* 这里有赌的成分,流式输出场景,只有最后一个mermaid代码块在流式输出,随着最后一个mermaid流式输出,mermaid的渲染有概率会失败
* 这里有赌的成分流式输出场景,只有最后一个 mermaid 代码块在流式输出,随着最后一个 mermaid 流式输出,mermaid 的渲染有概率会失败
* 这里赌的是:
* 1、只有一个mermaid代码块需要渲染
* 2、纯预览模式,且流式输出场景,所有mermaid都正常输出
* 1、只有一个 mermaid 代码块需要渲染
* 2、纯预览模式,且流式输出场景,所有 mermaid 都正常输出
*/
if (
$engine.$cherry.options.engine.global.flowSessionContext &&
Expand All @@ -278,22 +280,31 @@ export default class MermaidCodeEngine {
this.handleAsyncRenderDone(graphId, sign, $engine, props, html);
}
});
Logger.log('Mermaid async render started:', { graphId, sign });
if (this.needReturnLastRenderedCode) {
return this.lastRenderedCode;
}
// 先渲染源码
Logger.log('Mermaid async render done:', { graphId, sign });

// 【关键修改】在流式渲染模式下,不显示代码块,而是显示占位符
if ($engine.$cherry.options.engine.global.flowSessionContext) {
return `<div data-sign="${sign}" data-type="codeBlock" class="mermaid-loading">
<div class="mermaid-placeholder">Mermaid 图表渲染中...</div>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

流式输出mermaid图

不建议这样实现哈,目前对mermaid图形已经支持流式动态绘制了,#1549 提出的优化目标是解决在流式绘制mermaid图形的时候,会先渲染成代码块,再渲染成mermaid图(如下图),可以在这里体验
image

</div>`;
}

// 非流式模式下,先渲染源码
return props.fallback();
}

render(src, sign, $engine, props = {}) {
let $sign = sign;
if (!$sign) {
$sign = Math.round(Math.random() * 100000000);
}
this.mountMermaidCanvas($engine);
// 多实例的情况下相同的内容ID相同会导致mermaid渲染异常
// 需要通过添加时间戳使得多次渲染相同内容的图像ID唯一
// 图像渲染节流在CodeBlock Hook内部控制
// 多实例的情况下相同的内容 ID 相同会导致 mermaid 渲染异常
// 需要通过添加时间戳使得多次渲染相同内容的图像 ID 唯一
// 图像渲染节流在 CodeBlock Hook 内部控制
const graphId = `mermaid-${sign}-${new Date().getTime()}`;
this.svg2img = props.mermaidConfig?.svg2img ?? false;
return this.isAsyncRenderVersion()
Expand Down
73 changes: 37 additions & 36 deletions packages/cherry-markdown/src/core/hooks/CodeBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Prism from 'prismjs';
import { escapeHTMLSpecialChar } from '@/utils/sanitize';
import { getTableRule, getCodeBlockRule } from '@/utils/regexp';
import { prependLineFeedForParagraph } from '@/utils/lineFeed';

import Logger from '@/Logger';
Prism.manual = true;

const CUSTOM_WRAPPER = {
Expand All @@ -41,8 +41,8 @@ export default class CodeBlock extends ParagraphBase {
this.expandCode = config.expandCode; // 是否显示“展开”按钮
this.editCode = config.editCode; // 是否显示“编辑”按钮
this.changeLang = config.changeLang; // 是否显示“切换语言”按钮
this.selfClosing = config.selfClosing; // 自动闭合,为true时,当md中有奇数个```时,会自动在md末尾追加一个```
this.mermaid = config.mermaid; // mermaid的配置,目前仅支持格式设置,svg2img=true 展示成图片,false 展示成svg
this.selfClosing = config.selfClosing; // 自动闭合,为 true 时,当 md 中有奇数个```时,会自动在 md 末尾追加一个```
this.mermaid = config.mermaid; // mermaid 的配置,目前仅支持格式设置,svg2img=true 展示成图片,false 展示成 svg
this.indentedCodeBlock = typeof config.indentedCodeBlock === 'undefined' ? true : config.indentedCodeBlock; // 是否支持缩进代码块
this.INLINE_CODE_REGEX = /(`+)(.+?(?:\n.+?)*?)\1/g;
if (config && config.customRenderer) {
Expand Down Expand Up @@ -83,7 +83,7 @@ export default class CodeBlock extends ParagraphBase {

$resetCache() {
if (this.codeCacheList.length > 100) {
// 如果缓存超过100条,则清空最早的缓存
// 如果缓存超过 100 条,则清空最早的缓存
for (let i = 0; i < this.codeCacheList.length - 100; i++) {
delete this.codeCache[this.codeCacheList[i]];
}
Expand Down Expand Up @@ -218,7 +218,7 @@ export default class CodeBlock extends ParagraphBase {
}

/**
* 补齐用codeBlock承载的mermaid
* 补齐用 codeBlock 承载的 mermaid
* @param {string} $code
* @param {string} $lang
*/
Expand All @@ -235,7 +235,7 @@ export default class CodeBlock extends ParagraphBase {
lang = 'mermaid';
}
if (lang === 'mermaid') {
// 8.4.8版本兼容8.5.2版本的语法
// 8.4.8 版本兼容 8.5.2 版本的语法
code = code.replace(/(^[\s]*)stateDiagram-v2\n/, '$1stateDiagram\n');
// code = code.replace(/(^[\s]*)sequenceDiagram[ \t]*\n[\s]*autonumber[ \t]*\n/, '$1sequenceDiagram\n');
}
Expand Down Expand Up @@ -271,33 +271,32 @@ export default class CodeBlock extends ParagraphBase {
// 平台自定义代码块样式
cacheCode = this.customHighlighter(cacheCode, lang);
} else {
// 默认使用prism渲染代码块
if (!lang || !Prism.languages[lang]) lang = 'javascript'; // 如果没有写语言,默认用js样式渲染
if (!lang || !Prism.languages[lang]) lang = 'javascript'; // 如果没有写语言,默认用 js 样式渲染
cacheCode = Prism.highlight(cacheCode, Prism.languages[lang], lang);
cacheCode = this.renderLineNumber(cacheCode);
}
const needUnExpand = this.expandCode && $code.match(/\n/g)?.length > 10; // 是否需要收起代码块
const codeHtml = `<pre class="language-${lang}">${this.wrapCode(cacheCode, lang)}</pre>`;
cacheCode = `<div
data-sign="${sign}"
data-type="codeBlock"
data-lines="${lines}"
data-edit-code="${this.editCode}"
data-copy-code="${this.copyCode}"
data-expand-code="${this.expandCode}"
data-change-lang="${this.changeLang}"
data-lang="${lang}"
style="position:relative"
class="${needUnExpand ? 'cherry-code-unExpand' : 'cherry-code-expand'}"
>
${this.customWrapperRender(oldLang, cacheCode, codeHtml)}
`;
data-sign="${sign}"
data-type="codeBlock"
data-lines="${lines}"
data-edit-code="${this.editCode}"
data-copy-code="${this.copyCode}"
data-expand-code="${this.expandCode}"
data-change-lang="${this.changeLang}"
data-lang="${lang}"
style="position:relative"
class="${needUnExpand ? 'cherry-code-unExpand' : 'cherry-code-expand'}"
>
${this.customWrapperRender(oldLang, cacheCode, codeHtml)}
`;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不太建议通过工具批量修lint,这样会导致真正 commit 信息被覆盖(如下图)
image

if (needUnExpand) {
cacheCode += `<div class="cherry-mask-code-block">
<div class="expand-btn ">
<i class="ch-icon ch-icon-expand"></i>
</div>
</div>`;
<div class="expand-btn ">
<i class="ch-icon ch-icon-expand"></i>
</div>
</div>`;
}
cacheCode += '</div>';
return cacheCode;
Expand Down Expand Up @@ -366,6 +365,7 @@ export default class CodeBlock extends ParagraphBase {
});
}

//现在这里要做的只是把 mermaid 的排除逻辑放进来 ? r 然后其他的就不要管了,尽量不要影响其他功能
$dealUnclosingCode(str) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议保留这段逻辑哈,我们的目标不是输出占位,而是在确定是代码块的时候输出代码块,在确定是mermaid图的时候输出mermaid图,在不确定的时候什么都不输出

const codes = str.match(
/(?:^|\n)(\n*((?:>[\t ]*)*)(?:[^\S\n]*))(`{3,})([^`]*?)(?=CHERRY_FLOW_SESSION_CURSOR|$|\n)/g,
Expand All @@ -386,8 +386,11 @@ export default class CodeBlock extends ParagraphBase {
codeBegin = false;
return true;
});
// 如果有奇数个代码块关键字,则进行自动闭合
if ($codes.length % 2 === 1) {
// 在流式输出模式下,如果有未闭合的代码块,不输出任何内容
if (this.$cherry.options.engine.global.flowSessionContext) {
return str;
}
const lastCode = $codes[$codes.length - 1].replace(/(`)[^`]+$/, '$1').replace(/\n+/, '');
const $str = str.replace(/\n+$/, '').replace(/\n`{1,2}$/, '');
return `${$str}\n${lastCode}\n`;
Expand All @@ -397,12 +400,10 @@ export default class CodeBlock extends ParagraphBase {

beforeMakeHtml(str, sentenceMakeFunc, markdownParams) {
let $str = str;

// 处理段落代码块自动闭合
if (this.selfClosing || this.$cherry.options.engine.global.flowSessionContext) {
$str = this.$dealUnclosingCode($str);
}

// 预处理缩进代码块
$str = this.$replaceCodeInIndent($str);

Expand All @@ -418,7 +419,7 @@ export default class CodeBlock extends ParagraphBase {
}
let $code = code;
const { sign, lines } = this.computeLines(match, leadingContent, code);
// 从缓存中获取html
// 从缓存中获取 html
let cacheCode = this.$codeCache(sign);
if (cacheCode && cacheCode !== '') {
// 别忘了把 ">"(引用块)加回来
Expand Down Expand Up @@ -448,7 +449,7 @@ export default class CodeBlock extends ParagraphBase {
// 如果是公式关键字,则直接返回
if (/^(math|katex|latex)$/i.test($lang) && !this.isInternalCustomLangCovered($lang)) {
const prefix = match.match(/^\s*/g);
// ~D为经编辑器中间转义后的$,code结尾包含结束```前的所有换行符,所以不需要补换行
// ~D 为经编辑器中间转义后的$,code 结尾包含结束```前的所有换行符,所以不需要补换行
return `${prefix}~D~D\n${$code}~D~D`; // 提供公式语法供公式钩子解析
}
[$code, $lang] = this.appendMermaid($code, $lang);
Expand All @@ -467,14 +468,14 @@ export default class CodeBlock extends ParagraphBase {
this.$codeCache(sign, cacheCode);
return this.getCacheWithSpace(this.pushCache(cacheCode, sign, lines), match);
}
// 渲染出错则按正常code进行渲染
// 渲染出错则按正常 code 进行渲染
}
// $code = this.$replaceSpecialChar($code);
cacheCode = this.$codeReplace($code, $lang, sign, lines);
const result = this.getCacheWithSpace(this.pushCache(cacheCode, sign, lines), match);
return addBlockQuoteSignToResult(result);
});
// 表格里处理行内代码,让一个td里的行内代码语法生效,让跨td的行内代码语法失效
// 表格里处理行内代码,让一个 td 里的行内代码语法生效,让跨 td 的行内代码语法失效
$str = $str.replace(getTableRule(true), (whole, ...args) => {
return whole
.replace(/\\\|/g, '~CHERRYNormalLine')
Expand All @@ -485,8 +486,8 @@ export default class CodeBlock extends ParagraphBase {
.join('|')
.replace(/`/g, '\\`');
});
// 为了避免InlineCode被HtmlBlock转义,需要在这里提前缓存
// InlineBlock只需要在afterMakeHtml还原即可
// 为了避免 InlineCode 被 HtmlBlock 转义,需要在这里提前缓存
// InlineBlock 只需要在 afterMakeHtml 还原即可
$str = this.makeInlineCode($str, true);

// 处理缩进代码块
Expand All @@ -501,7 +502,7 @@ export default class CodeBlock extends ParagraphBase {
* @returns {string} 格式化后的语言
*/
formatLang(lang) {
// 增加一个潜规则,即便配置了all,也不处理mermaid
// 增加一个潜规则,即便配置了 all,也不处理 mermaid
if (this.customLang.indexOf('all') !== -1 && lang !== 'mermaid') {
return 'all';
}
Expand All @@ -521,7 +522,7 @@ export default class CodeBlock extends ParagraphBase {
$code = $code.replace(/~CHERRYNormalLine/g, '|');
$code = $code.replace(/\\/g, '\\\\');

// 如果行内代码只有一个颜色值,则在code末尾追加一个颜色圆点
// 如果行内代码只有一个颜色值,则在 code 末尾追加一个颜色圆点
const trimmed = $code.trim();
const isHex = /^#([0-9a-fA-F]{6})$/i.test(trimmed);
const isRgb = /^rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)$/i.test(trimmed);
Expand Down
Loading