Skip to content

自定义语法

sunsonliu edited this page Nov 6, 2023 · 10 revisions

简单例子

通过一个例子来了解cherry的自定义语法机制,如下:

定义一个自定义语法

/**
 * 自定义一个语法,识别形如 ***ABC*** 的内容,并将其替换成 <span style="color: red"><strong>ABC</strong></span>
 */
var CustomHookA = Cherry.createSyntaxHook('important', Cherry.constants.HOOKS_TYPE_LIST.SEN, {
    makeHtml(str) {
        return str.replace(this.RULE.reg, function(whole, m1, m2) {
            return `<span style="color: red;"><strong>${m2}</strong></span>`;
        });
    },
    rule(str) {
        return { reg: /(\*\*\*)([^\*]+)\1/g };
    },
});

...
/**
 * @param {string} hookName 语法名
 * @param {string} type 语法类型,行内语法为Cherry.constants.HOOKS_TYPE_LIST.SEN,段落语法为Cherry.constants.HOOKS_TYPE_LIST.PAR
 * @param {object} options 自定义语法的主体逻辑
 */
Cherry.createSyntaxHook(hookName, type, options)

将这个语法配置到cherry配置中

new Cherry({
    id: 'markdown-container', 
    value: '## hello world', 
    fileUpload: myFileUpload,
    customSyntax: {
        importHook: {
            syntaxClass: CustomHookA, // 将自定义语法对象挂载到 importHook.syntaxClass上
            force: false, // true: 当cherry自带的语法中也有一个“importHook”时,用自定义的语法覆盖默认语法; false:不覆盖
            before: 'fontEmphasis', // 定义该自定义语法的执行顺序,当前例子表明在加粗/斜体语法前执行该自定义语法
        },
    },
    toolbars: {
        ...
    },
});

效果如下图: image

详细解释

原理: 一言以蔽之,cherry的语法解析引擎就是将一堆正则按一定顺序依次执行,将markdown字符串替换为html字符串的工具。

语法分两类

  1. 行内语法,即类似加粗、斜体、上下标等主要对文字样式进行控制的语法,最大的特点是可以相互嵌套
  2. 段落语法,即类似表格、代码块、列表等主要对整段文本样式进行控制的语法,有两个特点:
    1. 可以在内部执行行内语法
    2. 可以声明与其他段落语法互斥

语法的组成

  1. 语法名,唯一的作用就是用来定义语法的执行顺序的时候按语法名排序
  2. beforeMakeHtml(),engine会最先按语法正序依次调用beforeMakeHtml()
  3. makeHtml(),engine会在调用完所有语法的beforeMakeHtml()后,再按语法正序依次调用makeHtml()
  4. afterMakeHtml(),engine会在调用完所有语法的makeHtml()后,再按语法逆序依次调用afterMakeHtml()
  5. rule(),用来定义语法的正则
  6. needCache,用来声明是否需要“缓存”,只有段落语法支持这个变量,true:段落语法可以在beforeMakeHtml()、makeHtml()的时候利用this.pushCache()this.popCache()实现排它的能力

自带的语法

  • 行内Hook
    引擎会按当前顺序执行makeHtml方法
    • emoji 表情
    • image 图片
    • link 超链接
    • autoLink 自动超链接(自动将符合超链接格式的字符串转换成标签)
    • fontEmphasis 加粗和斜体
    • bgColor 字体背景色
    • fontColor 字体颜色
    • fontSize 字体大小
    • sub 下标
    • sup 上标
    • ruby 一种表明上下排列的排版方式,典型应用就是文字上面加拼音
    • strikethrough 删除线
    • underline 下划线
    • highLight 高亮(就是在文字外层包一个标签)
  • 段落级 Hook
    引擎会按当前排序顺序执行beforeMake、makeHtml方法
    引擎会按当前排序逆序执行afterMake方法
    • codeBlock 代码块
    • inlineCode 行内代码(因要实现排它特性,所以归类为段落语法)
    • mathBlock 块级公式
    • inlineMath 行内公式(理由同行内代码)
    • htmlBlock html标签,主要作用为过滤白名单外的html标签
    • footnote 脚注
    • commentReference 超链接引用
    • br 换行
    • table 表格
    • blockquote 引用
    • toc 目录
    • header 标题
    • hr 分割线
    • list 有序列表、无序列表、checklist
    • detail 手风琴
    • panel 信息面板
    • normalParagraph 普通段落

具体介绍

  • 如果要实现一个行内语法,只需要了解以下三个概念
    1. 定义正则 rule()
    2. 定义具体的正则替换逻辑 makeHtml()
    3. 确定自定义语法名,并确定执行顺序
  • 如果要实现一个段落语法,则需要在了解行内语法相关概念后再了解以下概念:
    1. 排它机制
    2. 编辑区和预览区同步滚动机制
    3. 局部渲染机制

由于上面已有自定义行内语法的实现例子,接下来我们将通过实现一个自定义段落语法的例子来了解各个机制

一例胜千言

最简单段落语法

/**
 * 把 \n++\n XXX \n++\n 渲染成 <div>XXX</div>
 */
var myBlockHook = Cherry.createSyntaxHook('myBlock', Cherry.constants.HOOKS_TYPE_LIST.PAR, {
    makeHtml(str) {
        return str.replace(this.RULE.reg, function(whole, m1, m2) {
            return `<div style="border: 1px solid;border-radius: 15px;background: gold;">${m2}</div>`;
        });
    },
    rule(str) {
        return { reg: /\n\+\+(\n[\s\S]+?\n)\+\+\n/g };
    },
});
...
new Cherry({
    ...
    customSyntax: {
        myBlock: {
            syntaxClass: myBlockHook,
            before: 'blockquote',
        },
    },
    ...
});

效果如下: image

当我们尝试进行段逻语法嵌套时,就会发现这样的问题: image

为什么会有这样的问题,则需要先理解cherry的排他机制

理解排它: 一言以蔽之,排他就是某个语法利用自己的“先发优势(如beforeMakeHtmlmakeHtml)”把符合自己语法规则的内容先替换成占位符,再利用自己的“后发优势(afterMakeHtml)”将占位符替换回html内容

未完待续...

Clone this wiki locally