{"id":17257766,"url":"https://github.com/rss1102/cherry-markdown-monorepo","last_synced_at":"2025-07-30T13:04:30.981Z","repository":{"id":182289501,"uuid":"667554546","full_name":"RSS1102/cherry-markdown-monorepo","owner":"RSS1102","description":null,"archived":false,"fork":false,"pushed_at":"2023-12-11T03:14:52.000Z","size":21718,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"dev","last_synced_at":"2025-02-26T10:09:09.578Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RSS1102.png","metadata":{"files":{"readme":"README.CN.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-07-17T19:19:20.000Z","updated_at":"2023-07-18T09:24:04.000Z","dependencies_parsed_at":"2023-12-11T07:04:57.767Z","dependency_job_id":null,"html_url":"https://github.com/RSS1102/cherry-markdown-monorepo","commit_stats":null,"previous_names":["rss1102/cherry-markdown-monorepo"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RSS1102%2Fcherry-markdown-monorepo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RSS1102%2Fcherry-markdown-monorepo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RSS1102%2Fcherry-markdown-monorepo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RSS1102%2Fcherry-markdown-monorepo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RSS1102","download_url":"https://codeload.github.com/RSS1102/cherry-markdown-monorepo/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245622857,"owners_count":20645676,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-15T07:18:30.700Z","updated_at":"2025-03-26T08:42:38.780Z","avatar_url":"https://github.com/RSS1102.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"logo/logo--color.svg\" alt=\"cherry logo\" width=\"50%\"/\u003e\u003c/p\u003e\n\n# Cherry Markdown Editor\n\n## 介绍\n\nCherry Markdown Editor 是一款 Javascript Markdown 编辑器，具有开箱即用、轻量简洁、易于扩展等特点. 它可以运行在浏览器或服务端(NodeJs).\n\n### **开箱即用**\n\n开发者可以使用非常简单的方式调用并实例化 Cherry Markdown 编辑器，实例化的编辑器默认支持大部分常用的 markdown 语法（如标题、目录、流程图、公式等）。\n\n### **易于拓展**\n\n当 Cherry Markdown 编辑器支持的语法不满足开发者需求时，可以快速的进行二次开发或功能扩展。同时，CherryMarkdown 编辑器应该由纯 JavaScript 实现，不应该依赖 angular、vue、react 等框架技术，框架只提供容器环境即可。\n\n## 特性\n\n### 语法特性\n\n1. 图片缩放、对齐、引用\n2. 根据表格内容生成图表\n3. 字体颜色、字体大小\n4. 字体背景颜色、上标、下标\n5. checklist\n6. 音视频\n\n### 多种模式\n\n1. 双栏编辑预览模式（支持同步滚动）\n2. 纯预览模式\n3. 无工具栏模式（极简编辑模式）\n4. 移动端预览模式\n\n### 功能特性\n\n1. 复制 Html 粘贴成 MD 语法\n2. 经典换行\u0026常规换行\n3. 多光标编辑\n4. 图片尺寸\n5. 导出长图、pdf\n6. float toolbar 在新行行首支持快速工具栏\n7. bubble toolbar 选中文字时联想出快速工具栏\n\n### 性能特性\n\n1. 局部渲染\n2. 局部更新\n\n### 安全\n\nCherry Markdown 有内置的安全 Hook，通过过滤白名单以及 DomPurify 进行扫描过滤.\n\n### 样式主题\n\nCherry Markdown 有多种样式主题选择\n\n### 特性展示\n\n这里可以看到特性的简短的演示 [screenshot](./docs/features.md)\n\n### 在线体验\n\n- [basic](https://tencent.github.io/cherry-markdown/examples/index.html)\n- [H5](https://tencent.github.io/cherry-markdown/examples/h5.html)\n- [多实例](https://tencent.github.io/cherry-markdown/examples/multiple.html)\n- [无 toolbar](https://tencent.github.io/cherry-markdown/examples/notoolbar.html)\n- [纯预览模式](https://tencent.github.io/cherry-markdown/examples/preview_only.html)\n- [注入](https://tencent.github.io/cherry-markdown/examples/xss.html)（默认防注入，需要配置才允许注入）\n- [API](https://tencent.github.io/cherry-markdown/examples/api.html)\n- [图片所见即所得编辑尺寸](https://tencent.github.io/cherry-markdown/examples/img.html)\n- [表格编辑](https://tencent.github.io/cherry-markdown/examples/table.html)\n- [标题自动序号](https://tencent.github.io/cherry-markdown/examples/head_num.html)\n\n## 安装\n\n通过 yarn\n\n```bash\nyarn add cherry-markdown\n```\n\n通过 npm\n\n```bash\nnpm install cherry-markdown --save\n```\n\n如果需要开启 `mermaid` 画图、表格自动转图表功能，需要同时添加`mermaid` 与`echarts`包。\n\n目前**Cherry**推荐的插件版本为`echarts@4.6.0`、`mermaid@8.11.1`\n\n```bash\n# 安装mermaid依赖开启mermaid画图功能\nyarn add mermaid@8.11.1\n# 安装echarts依赖开启表格自动转图表功能\nyarn add echarts@4.6.0\n```\n\n## Quick start\n\n### Browser\n\n#### UMD\n\n```html\n\u003clink href=\"cherry-editor.min.css\" /\u003e\n\u003cdiv id=\"markdown-container\"\u003e\u003c/div\u003e\n\u003cscript src=\"cherry-editor.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  new Cherry({\n    id: 'markdown-container',\n    value: '# welcome to cherry editor!',\n  });\n\u003c/script\u003e\n```\n\n#### ESM\n\n```javascript\nimport Cherry from 'cherry-markdown';\nconst cherryInstance = new Cherry({\n  id: 'markdown-container',\n  value: '# welcome to cherry editor!',\n});\n```\n\n### Node\n\n```javascript\nconst { default: CherryEngine } = require('cherry-markdown/dist/cherry-markdown.engine.core.common');\nconst cherryEngineInstance = new CherryEngine();\nconst htmlContent = cherryEngineInstance.makeHtml('# welcome to cherry editor!');\n```\n\n## 使用轻量版本\n\n因 mermaid 库尺寸非常大，Cherry 构建产物中包含了不内置 mermaid 的核心构建包，可按以下方式引入核心构建。\n\n### 完整模式 (图形界面)\n\n```javascript\nimport Cherry from 'cherry-markdown/dist/cherry-markdown.core';\nconst cherryInstance = new Cherry({\n  id: 'markdown-container',\n  value: '# welcome to cherry editor!',\n});\n```\n\n### 引擎模式 (语法编译)\n\n```javascript\n// 引入Cherry引擎核心构建\n// 引擎配置项与Cherry通用，以下文档内容仅介绍Cherry核心包\nimport CherryEngine from 'cherry-markdown/dist/cherry-markdown.engine.core';\nconst cherryEngineInstance = new CherryEngine();\nconst htmlContent = cherryEngineInstance.makeHtml('# welcome to cherry editor!');\n\n// --\u003e \u003ch1\u003ewelcome to cherry editor!\u003c/h1\u003e\n```\n\n### ⚠️ 关于 mermaid\n\n核心构建包不包含 mermaid 依赖，需要手动引入相关插件。\n\n```javascript\nimport Cherry from 'cherry-markdown/dist/cherry-markdown.core';\nimport CherryMermaidPlugin from 'cherry-markdown/dist/addons/cherry-code-block-mermaid-plugin';\nimport mermaid from 'mermaid';\n\n// 插件注册必须在Cherry实例化之前完成\nCherry.usePlugin(CherryMermaidPlugin, {\n  mermaid, // 传入mermaid引用\n  // mermaidAPI: mermaid.mermaidAPI, // 也可以传入mermaid API\n  // 同时可以在这里配置mermaid的行为，可参考mermaid官方文档\n  // theme: 'neutral',\n  // sequence: { useMaxWidth: false, showSequenceNumbers: true }\n});\n\nconst cherryInstance = new Cherry({\n  id: 'markdown-container',\n  value: '# welcome to cherry editor!',\n});\n```\n\n### 异步加载\n\n**recommend** 使用异步引入，以下为 webpack 异步引入样例。\n\n```javascript\nimport Cherry from 'cherry-markdown/dist/cherry-markdown.core';\n\nconst registerPlugin = async () =\u003e {\n  const [{ default: CherryMermaidPlugin }, mermaid] = await Promise.all([\n    import('cherry-markdown/src/addons/cherry-code-block-mermaid-plugin'),\n    import('mermaid'),\n  ]);\n  Cherry.usePlugin(CherryMermaidPlugin, {\n    mermaid, // 传入mermaid引用\n  });\n};\n\nregisterPlugin().then(() =\u003e {\n  // 插件注册必须在Cherry实例化之前完成\n  const cherryInstance = new Cherry({\n    id: 'markdown-container',\n    value: '# welcome to cherry editor!',\n  });\n});\n```\n\n## 配置\n\n### 默认配置\n\n````javascript\n  {\n  // 第三方包\n  externals: {\n    // externals\n  },\n  // 解析引擎配置\n  engine: {\n    // 全局配置\n    global: {\n      // 是否启用经典换行逻辑\n      // true：一个换行会被忽略，两个以上连续换行会分割成段落，\n      // false： 一个换行会转成\u003cbr\u003e，两个连续换行会分割成段落，三个以上连续换行会转成\u003cbr\u003e并分割段落\n      classicBr: false,\n      /**\n       * 全局的URL处理器\n       * @param {string} url 来源url\n       * @param {'image'|'audio'|'video'|'autolink'|'link'} srcType 来源类型\n       * @returns\n       */\n      urlProcessor: callbacks.urlProcessor,\n      /**\n       * 额外允许渲染的html标签\n       * 标签以英文竖线分隔，如：htmlWhiteList: 'iframe|script|style'\n       * 默认为空，默认允许渲染的html见src/utils/sanitize.js whiteList 属性\n       * 需要注意：\n       *    - 启用iframe、script等标签后，会产生xss注入，请根据实际场景判断是否需要启用\n       *    - 一般编辑权限可控的场景（如api文档系统）可以允许iframe、script等标签\n       */\n      htmlWhiteList: '',\n    },\n    // 内置语法配置\n    syntax: {\n      // 语法开关\n      // 'hookName': false,\n      // 语法配置\n      // 'hookName': {\n      //\n      // }\n      autoLink: {\n        /** 是否开启短链接 */\n        enableShortLink: true,\n        /** 短链接长度 */\n        shortLinkLength: 20,\n      },\n      list: {\n        listNested: false, // 同级列表类型转换后变为子级\n        indentSpace: 2, // 默认2个空格缩进\n      },\n      table: {\n        enableChart: false,\n        // chartRenderEngine: EChartsTableEngine,\n        // externals: ['echarts'],\n      },\n      inlineCode: {\n        theme: 'red',\n      },\n      codeBlock: {\n        theme: 'dark', // 默认为深色主题\n        wrap: true, // 超出长度是否换行，false则显示滚动条\n        lineNumber: true, // 默认显示行号\n        customRenderer: {\n          // 自定义语法渲染器\n        },\n        /**\n         * indentedCodeBlock是缩进代码块是否启用的开关\n         *\n         *    在6.X之前的版本中默认不支持该语法。\n         *    因为cherry的开发团队认为该语法太丑了（容易误触）\n         *    开发团队希望用```代码块语法来彻底取代该语法\n         *    但在后续的沟通中，开发团队发现在某些场景下该语法有更好的显示效果\n         *    因此开发团队在6.X版本中才引入了该语法\n         *    已经引用6.x以下版本的业务如果想做到用户无感知升级，可以去掉该语法：\n         *        indentedCodeBlock：false\n         */\n        indentedCodeBlock: true,\n      },\n      emoji: {\n        useUnicode: true, // 是否使用unicode进行渲染\n      },\n      fontEmphasis: {\n        allowWhitespace: false, // 是否允许首尾空格\n      },\n      strikethrough: {\n        needWhitespace: false, // 是否必须有首位空格\n      },\n      mathBlock: {\n        engine: 'MathJax', // katex或MathJax\n        src: '',\n        plugins: true, // 默认加载插件\n      },\n      inlineMath: {\n        engine: 'MathJax', // katex或MathJax\n        src: '',\n      },\n      toc: {\n        /** 默认只渲染一个目录 */\n        allowMultiToc: false,\n      },\n      header: {\n        /**\n         * 标题的样式：\n         *  - default       默认样式，标题前面有锚点\n         *  - autonumber    标题前面有自增序号锚点\n         *  - none          标题没有锚点\n         */\n        anchorStyle: 'default',\n      },\n    },\n  },\n  editor: {\n    codemirror: {\n      // depend on codemirror theme name: https://codemirror.net/demo/theme.html\n      // 自行导入主题文件: `import 'codemirror/theme/\u003ctheme-name\u003e.css';`\n      theme: 'default',\n    },\n    // 编辑器的高度，默认100%，如果挂载点存在内联设置的height则以内联样式为主\n    height: '100%',\n    // defaultModel 编辑器初始化后的默认模式，一共有三种模式：1、双栏编辑预览模式；2、纯编辑模式；3、预览模式\n    // edit\u0026preview: 双栏编辑预览模式\n    // editOnly: 纯编辑模式（没有预览，可通过toolbar切换成双栏或预览模式）\n    // previewOnly: 预览模式（没有编辑框，toolbar只显示“返回编辑”按钮，可通过toolbar切换成编辑模式）\n    defaultModel: 'edit\u0026preview',\n    // 粘贴时是否自动将html转成markdown\n    convertWhenPaste: true,\n  },\n  toolbars: {\n    theme: 'dark', // light or dark\n    showToolbar: true, // false：不展示顶部工具栏； true：展示工具栏; toolbars.showToolbar=false 与 toolbars.toolbar=false 等效\n    toolbar: [\n      'bold',\n      'italic',\n      'strikethrough',\n      '|',\n      'color',\n      'header',\n      '|',\n      'list',\n      {\n        insert: [\n          'image',\n          'audio',\n          'video',\n          'link',\n          'hr',\n          'br',\n          'code',\n          'formula',\n          'toc',\n          'table',\n          'line-table',\n          'bar-table',\n          'pdf',\n          'word',\n        ],\n      },\n      'graph',\n      'settings',\n    ],\n    sidebar: [],\n    bubble: ['bold', 'italic', 'underline', 'strikethrough', 'sub', 'sup', 'quote', '|', 'size', 'color'], // array or false\n    float: ['h1', 'h2', 'h3', '|', 'checklist', 'quote', 'quickTable', 'code'], // array or false\n  },\n  fileUpload: callbacks.fileUpload,\n  callback: {\n    afterChange: callbacks.afterChange,\n    afterInit: callbacks.afterInit,\n    beforeImageMounted: callbacks.beforeImageMounted,\n  },\n  previewer: {\n    dom: false,\n    className: 'cherry-markdown',\n    // 是否启用预览区域编辑能力（目前支持编辑图片尺寸、编辑表格内容）\n    enablePreviewerBubble: true,\n  },\n  // 预览页面不需要绑定事件\n  isPreviewOnly: false,\n  // 预览区域跟随编辑器光标自动滚动\n  autoScrollByCursor: true,\n  // 外层容器不存在时，是否强制输出到body上\n  forceAppend: true,\n}\n````\n\n### 关闭浮动菜单和气泡菜单\n\n```javascript\n  toolbars: {\n    ... // other config\n    bubble: false, // array or false\n    float: false, // array or false\n  },\n\n```\n\n### 更多信息\n\n如果你想自定义配置信息，可以看这里 [Configuration](./docs/configuration.CN.md)\n\n## 例子\n\n点击查看 [examples](./examples/)\n\n### 客户端\n\n[cherry-markdown 桌面客户端](./docs/cherry_editor_client.CN.md)\n\n## 拓展\n\n### 自定义语法\n\n#### sentence Syntax\n\n如果编译内容没有额外特殊要求，使用普通语法\n\n```javascript\n/*\n * 生成一个屏蔽敏感词汇的hook\n * 名字叫blockSensitiveWords\n * 匹配规则会挂载到实例的RULE属性上\n */\nlet BlockSensitiveWordsHook = Cherry.createSyntaxHook('blockSensitiveWords', Cherry.constants.HOOKS_TYPE_LIST.SEN, {\n  makeHtml(str) {\n    return str.replace(this.RULE.reg, '***');\n  },\n  rule(str) {\n    return {\n      reg: /敏感词汇/g,\n    };\n  },\n});\nnew Cherry({\n  id: 'markdown-container',\n  value: '# welcome to cherry editor!',\n  engine: {\n    customSyntax: {\n      // 注入编辑器的自定义语法中\n      BlockSensitiveWordsHook: {\n        syntaxClass: BlockSensitiveWordsHook,\n        // 有同名hook则强制覆盖\n        force: true,\n        // 在处理图片的hook之前调用\n        // before: 'image',\n      },\n    },\n  },\n});\n```\n\n#### paragraph Syntax\n\n如果编译内容要求不受外界影响，则使用段落语法\n\n```javascript\n/*\n * 生成一个屏蔽敏感词汇的hook\n * 名字叫blockSensitiveWords\n * 匹配规则会挂载到实例的RULE属性上\n */\nlet BlockSensitiveWordsHook = Cherry.createSyntaxHook('blockSensitiveWords', Cherry.constants.HOOKS_TYPE_LIST.PAR, {\n  // 开启缓存，用于保护内容\n  needCache: true,\n  // 预处理文本，避免受影响\n  beforeMakeHtml(str) {\n    return str.replace(this.RULE.reg, (match, code) =\u003e {\n      const lineCount = (match.match(/\\n/g) || []).length;\n      const sign = this.$engine.md5(match);\n      const html = `\u003cdiv data-sign=\"${sign}\" data-lines=\"${lineCount + 1}\" \u003e***\u003c/div\u003e`;\n      return this.pushCache(html, sign, lineCount);\n    });\n  },\n  makeHtml(str, sentenceMakeFunc) {\n    return str;\n  },\n  rule(str) {\n    return {\n      reg: /sensitive words/g,\n    };\n  },\n});\nnew Cherry({\n  id: 'markdown-container',\n  value: '# welcome to cherry editor!',\n  engine: {\n    customSyntax: {\n      // 注入编辑器的自定义语法中\n      BlockSensitiveWordsHook: {\n        syntaxClass: BlockSensitiveWordsHook,\n        // 有同名hook则强制覆盖\n        force: true,\n        // 在处理图片的hook之前调用\n        // before: 'image',\n      },\n    },\n  },\n});\n```\n\n### 自定义工具栏\n\n```javascript\n/*\n * 生成一个添加前缀模板的hook\n * 名字叫AddPrefixTemplate\n * 图标类名icon-add-prefix\n */\nlet AddPrefixTemplate = Cherry.createMenuHook('AddPrefixTemplate', 'icon-add-prefix', {\n  onClick(selection) {\n    return 'Prefix-' + selection;\n  },\n});\nnew Cherry({\n  id: 'markdown-container',\n  value: '# welcome to cherry editor!',\n  toolbars: {\n    toolbar: [\n      'bold',\n      // ...其他菜单项\n      // @see src/Cherry.config.js\n      'settings',\n      'addPrefix', // 在顶部菜单栏的尾部添加自定义菜单项\n    ],\n    bubble: [\n      'bold' /** ...其他菜单项 */,\n      ,\n      'color',\n      'addPrefix', // 在气泡菜单（选中文本时出现）的尾部添加自定义菜单项\n    ], // array or false\n    float: [\n      'h1' /** ...其他菜单项 */,\n      ,\n      'code',\n      'addPrefix', // 在浮动菜单（在新的空行出现）的尾部添加自定义菜单项\n    ], // array or false\n    customMenu: {\n      // 注入编辑器的菜单中\n      // 对象 key 可以作为菜单项的名字（需要保证唯一），在上方的配置中使用\n      addPrefix: AddPrefixTemplate,\n    },\n  },\n});\n```\n\n如果你想看更多有关 cherry markdown 的拓展信息，可以看这里 [extensions](./docs/extensions.CN.md).\n\n## 单元测试\n\n选用 Jest 作为单元测试工具，主要看好其断言、支持异步和快照测试等功能。单元测试分为两个部分，CommonMark 用例测试和快照测试。\n\n### CommonMark 测试用例\n\n运行 `yarn run test:commonmark` 测试 CommonMark 官方用例，运行速度较快。\n\n用例位于 `test/suites/commonmark.spec.json`， 比如：\n\n```json\n{\n  \"markdown\": \"  \\tfoo\\tbaz\\t\\tbim\\n\",\n  \"html\": \"\u003cpre\u003e\u003ccode\u003efoo\\tbaz\\t\\tbim\\n\u003c/code\u003e\u003c/pre\u003e\\n\",\n  \"example\": 2,\n  \"start_line\": 363,\n  \"end_line\": 368,\n  \"section\": \"Tabs\"\n},\n```\n\n对于这个测试点，Jest 会比对 `Cherry.makeHtml(\" \\tfoo\\tbaz\\t\\tbim\\n\")` 生成的 html 与用例中的预期结果 `\"\u003cpre\u003e\u003ccode\u003efoo\\tbaz\\t\\tbim\\n\u003c/code\u003e\u003c/pre\u003e\\n\"`。Cherry Markdown 的匹配器已忽略 `data-line` 等私有属性。\n\nCommonMark 规范及测试用例可参考：https://spec.commonmark.org/ 。\n\n### 快照测试\n\n调用 `yarn run test:snapshot` 运行快照测试，书写用例可参考 `test/core/hooks/List.spec.ts`，新用例第一次运行会自动生成快照，第二次会将生成内容与快照进行比对。如果需要重新生成快照，将 `test/core/hooks/__snapshots__` 下对应的旧快照删除再运行一次即可。\n\n快照测试运行速度较慢，仅在易出错且包含 Cherry 特色语法的 Hook 上使用。\n\n## Contributing\n\n欢迎加入我们打造强大的 Markdown 编辑器。当然你也可以给我们提交特性需求的 issue。 在写特性功能之前，你需要了解 [extensions](./docs/extensions.CN.md) 以及 [commit_convention](./docs/commit_convention.CN.md).\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frss1102%2Fcherry-markdown-monorepo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frss1102%2Fcherry-markdown-monorepo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frss1102%2Fcherry-markdown-monorepo/lists"}