markdown-it-echarts.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import MarkdownIt from "markdown-it";
  2. import type { RenderRule } from "markdown-it/lib/renderer.mjs";
  3. import type Token from "markdown-it/lib/token.mjs";
  4. import type { Options } from "markdown-it/lib/index.mjs";
  5. import type Renderer from "markdown-it/lib/renderer.mjs";
  6. import * as echarts from 'echarts'
  7. class EChartsElement extends HTMLDivElement {
  8. instance!: echarts.ECharts
  9. private resizeHandler!: () => void
  10. connectedCallback() {
  11. const config = decodeURIComponent(this.getAttribute('config') ?? '') ?? '';
  12. let data: echarts.EChartsOption
  13. try {
  14. data = JSON.parse(config)
  15. } catch (e) {
  16. console.error(e)
  17. return
  18. }
  19. this.instance = echarts.init(this)
  20. this.instance.setOption(data)
  21. // 创建绑定了正确上下文的 resize 处理器
  22. this.resizeHandler = () => {
  23. this.instance?.resize()
  24. }
  25. window.addEventListener('resize', this.resizeHandler)
  26. }
  27. disconnectedCallback() {
  28. // 清理 resize 事件监听器
  29. if (this.resizeHandler) {
  30. window.removeEventListener('resize', this.resizeHandler)
  31. }
  32. // 销毁 ECharts 实例
  33. if (this.instance) {
  34. this.instance.dispose()
  35. }
  36. }
  37. }
  38. export type EchartsPluginOptions = {
  39. }
  40. // 生成唯一ID
  41. function generateId(): string {
  42. return 'echarts-' + Math.random().toString(36).substring(2, 9)
  43. }
  44. // 验证JSON格式
  45. function isValidJSON(str: string): boolean {
  46. try {
  47. JSON.parse(str)
  48. return true
  49. } catch {
  50. return false
  51. }
  52. }
  53. // 渲染echarts代码块
  54. const renderEcharts: RenderRule = (tokens: Token[], idx: number, _options: Options, env: any, _self: Renderer) => {
  55. const token = tokens[idx]
  56. const content = token.content.trim()
  57. if (!content) {
  58. return '<div style="padding: 16px;background-color: #fff5f5;border: 1px solid #fed7d7;border-radius: 6px;color: #c53030;margin: 16px 0;">ECharts配置不能为空</div>'
  59. }
  60. const id = generateId()
  61. const className = env.echartsClassName || 'echarts-container'
  62. // 生成完整HTML
  63. return `<div is="echarts-container" style="width: 100%;height: 350px;margin: 16px 0; border-radius: 6px" class="${className}" id="${id}" config="${encodeURIComponent(content)}"></div>`
  64. }
  65. // markdown-it插件
  66. //@ts-ignore
  67. // eslint-disable-next-line no-unused-vars
  68. function echartsPlugin(md: MarkdownIt, options: EchartsPluginOptions = {}) {
  69. // 保存原始的fence渲染器
  70. const defaultRender = md.renderer.rules.fence ?? function(tokens, idx, options, _env, renderer) {
  71. return renderer.renderToken(tokens, idx, options)
  72. }
  73. if (customElements.get('echarts-container') === undefined) {
  74. customElements.define('echarts-container', EChartsElement, { extends: 'div' })
  75. }
  76. // 重写fence渲染器
  77. md.renderer.rules.fence = function(tokens, idx, options, env, renderer) {
  78. const token = tokens[idx]
  79. const info = token.info ? token.info.trim() : ''
  80. // 检查是否是echarts代码块
  81. if (info === 'echarts' && isValidJSON(token.content.trim())) {
  82. console.log(`prepare renderer`,tokens,idx,options,env,renderer,tokens[idx])
  83. return renderEcharts(tokens, idx, options, env, renderer)
  84. }
  85. // 其他代码块使用默认渲染器
  86. return defaultRender(tokens, idx, options, env, renderer)
  87. }
  88. }
  89. export default echartsPlugin