xDocxDoc
AI
前端
后端
iOS
Android
Flutter
AI
前端
后端
iOS
Android
Flutter
    • Marked是什么
    • 高级用法
    • 扩展
    • 代码示例

代码示例

自定义 markdown 渲染。

diviceMarkdownConfig.js

const defaultConfig = {
  name: 'default',
  textstyle() {
    return [
      'color: rgba(255, 255, 255, 0.90)',
      'font-size: 30px',
      'font-weight: 400',
      'line-height: 50px',
    ].join(';');
  },
  margin() {
    return [
      'height: 20px',
    ].join(';');
  },
  paragraphMargin() {
    return [
      'height: 20px',
    ].join(';');
  },
  latex() {
    return [
      'color: rgba(255, 255, 255, 0.90)',
      'font-size: 30px',
      'line-height: 50px',
      'lineSpacing: 16px',
    ].join(';');
  },
  code() {
    return [
      'background-color: #333',
      'padding: 6px',
      'color: rgba(255,255,255,0.6)',
    ].join(';');
  },
  heading: {
    h(index) {
      const fontSize = 48 - (index - 1) * 6;
      const lineHeight = 58 - (index - 1) * 6;
      const fontWeight = 900 - (index - 1) * 100;
      return [
        `font-size: ${fontSize}px`,
        `line-height: ${lineHeight}px`,
        `font-weight: ${fontWeight}`,
      ].join(';');
    },
    hNum(index) {
      const fontSize = 48 - (index - 1) * 6;
      return [
        'color:#578FFF',
        `font-size: ${fontSize}px`,
      ].join(';');
    },
    margin(index) {
      const marginBottom = 28 - (index - 1) * 4;
      return [
        `height: ${marginBottom}px`,
      ].join(';');
    },
  },
  hr() {
    return [
      'height: 1px',
      'background-color: rgba(255, 255, 255, 0.16)',
    ].join(';')
  },
  blockquote() {
    return [
      'background-color: #666',
      'padding: 6px',
      'border-left: 4px solid #333',
      'color: rgba(255,255,255,0.6)',
    ].join(';')
  },
  list(depth) {
    return [
      `padding-left:${(depth - 1) * 16}px`,
    ].join(';');
  },
  listitem: {
    liNum() {
      return [
        'color: rgba(255,255,255,0.9)',
      ].join(';');
    },
    olNum() {
      return [].join(';');
    },
  },
  table: {
    table() {
      return [].join(';');
    },
    tableCell() {
      return [
        'border:1px solid red',
        'padding: 0 10px',
      ].join(';');
    },
    headerCell() {
      return [].join(';');
    },
    bodyCell() {
      return [].join(';');
    },
  },
  paragraph() {
    return [].join('');
  },
  strong() {
    return [
      'font-weight: 700',
    ].join(';');
  },
  em() {
    return [
      'font-style: italic'
    ].join(';');
  },
  codespan() {
    return [
      'color: rgba(255,255,255,0.6)',
    ].join(';');
  },
  br() {
    return [
      'height: 20px',
    ].join(';');
  }
};

const styleConfig = {
  a: {
    name: 'a',
  },
  b: {
    name: 'b',
  },
};

const getConfigByName = (name) => {
  let config = { ...defaultConfig };
  if (config[name]) {
    config = {
      ...styleConfig[name],
    };
  }
  return config;
};

export {
  getConfigBySku,
};

hassmarked.js

import { marked } from "./marked.esm.js";
import { getConfigBySku } from "./diviceMarkdownConfig.js";

const skuConfig = getConfigByName();

const options = {
  gfm: true, // 允许 Git Hub标准的markdown
  breaks: true, // 单个\n换行,要求 gfm 为true
  tables: true, // 允许支持表格语法,要求 gfm 为true
  pedantic: false, // 非严格模式:支持表格等扩展语法
  sanitize: false, // 会保留并渲染输入中包含的所有 HTML 标签,true 时过滤掉html标签
};
let listDepth = 0;

const renderer = {
  // block markdown
  code(options) {
    const { text } = options;
    const style = skuConfig?.code();
    const marginStyle = skuConfig?.margin();
    return [
      `<div class="code" style="${style}">`,
      `<pre class="code_text">${text}</pre>`,
      '</div>',
      `<div class="codeMarginBottom" style="${marginStyle}"></div>`,
    ].join('');
  },
  heading(options) {
    const { depth, tokens } = options;
    const parsedContent = this.parser.parseInline(tokens);
    const headingStyle = skuConfig?.heading?.h(depth);
    const hNumStyle = skuConfig?.heading?.hNum(depth);
    const marginStyle = skuConfig?.heading?.margin(depth);
    return [
      `<div class="heading${depth}" style="${headingStyle}">`,
      `<span style="${hNumStyle}">•</span>`,
      `${parsedContent}</div>`,
      `<div class="headingMarginBottom" style="${marginStyle}"></div>`,
    ].join('');
  },
  hr() {
    const marginBottom = skuConfig?.margin();
    const marginTop = skuConfig?.paragraphMargin();
    const style = skuConfig?.hr();
    return [
      `<div class="hrMarginTop" style="${marginTop}"></div>`,
      `<div class="hr" style="${style}"></div>`,
      `<div class="hrMarginBottom" style="${marginBottom}"></div>`,
    ].join('');
  },
  blockquote(options) {
    const parsedContent = this.parser.parse(options.tokens);
    const style = skuConfig?.blockquote();
    const marginStyle = skuConfig?.margin();
    return [
      `<div class="blockquote" style="${style}">${parsedContent}</div>`,
      `<div class="blockquoteMarginBottom" style="${marginStyle}"></div>`,
    ].join('');
  },
  list(options) {
    listDepth++;    
    // 为有序列表计算起始序号,默认为1
    const startNumber = options.ordered ? (options.start || 1) : null;
    // 处理列表项,为有序列表项添加序号
    const itemsHtml = options.items.map((item, index) => {
      const parsedContent = this.parser.parse(item.tokens);
      // 计算当前列表项的序号
      const itemNumber = options.ordered ? (startNumber + index) : null;
      return this.listitem({
          ...item, 
          text: parsedContent,
          ordered: options.ordered,
          number: itemNumber // 传递序号到列表项
      });
    }).join('');
    // 根据列表类型和深度应用样式
    const listTypeClass = options.ordered ? 'ol' : 'ul';
    const style = skuConfig?.list(listDepth);
    const marginStyle = skuConfig?.margin();
    const result = [
      `<div style="${style}" class="${listTypeClass}${listDepth - 1}">${itemsHtml}</div>`,
      // `<div class="listMarginBottom" style="${marginStyle}"></div>`,
    ].join('');
    listDepth--;
    return result;
  },
  listitem(options) {
    const liNumStyle = skuConfig?.listitem?.liNum();
    const olNumStyle = skuConfig?.listitem?.olNum();
    // 有序列表项添加序号前缀
    const numberPrefix = options.ordered && options.number 
    ? `<span class="olNum" style="${olNumStyle}">${options.number}.</span>` 
    : `<span class="liNum" style="${liNumStyle}">・</span>`;
    return `<div class="li">${numberPrefix}${options.text}</div>`;
  },
  table(options) {
    const headerCells = options.header.map((cell, index) => {
      const align = options.align[index] || '';
      return this.tablecell({
        cell,
        isHeader: true,
        align
      });
    }).join('');
    const headerRow = this.tablerow({
      cells: headerCells,
      isHeader: true
    });
    const bodyRows = options.rows.map((row, rowIndex) => {
      const cells = row.map((cell, cellIndex) => {
        const align = options.align[cellIndex] || '';
        return this.tablecell({
          cell,
          isHeader: false,
          align
        });
      }).join('');
      return this.tablerow({
        cells,
        isHeader: false,
        rowIndex
      });
    }).join('');
    const style = skuConfig?.table?.table();
    const marginStyle = skuConfig?.margin();
    return [
      `<div class="table" style="${style}" data-type="${options.type}">`,
      `<div class="tableHeader">${headerRow}</div>`,
      `<div class="tableBody">${bodyRows}</div>`,
      '</div>',
      `<div class="tableMarginBottom" style="${marginStyle}"></div>`,
    ].join('');
  },
  // 表格行渲染
  tablerow(options) {
    // options包含:cells(单元格HTML)、isHeader(是否表头行)、rowIndex(行索引)
    const rowType = options.isHeader ? 'headerRow' : 'bodyRow';
    return `<div class="tableRow ${rowType}" data-index="${options.rowIndex || 0}">${options.cells}</div>`;
  },
  tablecell(options) {
    // options包含:cell(单元格数据)、isHeader(是否表头)、align(对齐方式)
    const content = this.parser.parseInline(options.cell.tokens);
    const cellType = options.isHeader ? 'headerCell' : 'bodyCell';
    const alignClass = options.align ? `align${options.align}` : '';
    const tableCellStyle = skuConfig?.table?.tableCell();
    const headerCellStyle = skuConfig?.table?.headerCell();
    const bodyCellStyle = skuConfig?.table?.bodyCell();
    if (options.isHeader) {
      return `<span style="${tableCellStyle};${headerCellStyle}" class="tableCell ${cellType} ${alignClass}">${content}</span>`;
    } else {
      return `<span style="${tableCellStyle};${bodyCellStyle}" class="tableCell ${cellType} ${alignClass}">${content}</span>`;
    }
  },
  paragraph(options) {
    const parsedContent = this.parser.parseInline(options.tokens);
    const style = skuConfig?.paragraph();
    const marginStyle = skuConfig?.paragraphMargin();
    return [
      `<span class="paragraph" style="${style}">${parsedContent}</span>`,
      `<div style="${marginStyle}" class="paragraphMargin"></div>`
    ].join('');
  },
  // inline markdown
  strong(options) { // 加粗
    const { tokens } = options;
    const style = skuConfig?.strong();
    const parsedContent = this.parser.parseInline(tokens);
    return `<span class="strong" style="${style}">${parsedContent}</span>`;
  },
  em(options) { // 斜体
    const { tokens } = options;
    const style = skuConfig?.em();
    const parsedContent = this.parser.parseInline(tokens);
    return `<span class="em" style="${style}">${parsedContent}</span>`;
  },
  codespan(options) {
    const { text } = options;
    const style = skuConfig?.codespan();
    return `<span class="codespan" style="${style}">${text}</span>`;
  },
  br() {
    const style = skuConfig?.br();
    return `<div style="${style}"></div>`;
  },
};

marked.use(options);
marked.use({ renderer });

const renderMarkdown = (markdownStr) => {
  const latexStyle = skuConfig.latex();
  markdownStr = markdownStr.replace(/(<latex\s+style=)("[^"]*")/g, `$1"${latexStyle}"`);
  console.error('renderMarkdown markdownStr=', markdownStr);
  const markdownStrParsed = marked.parse(markdownStr, options);
  console.error('renderMarkdown markdownStrParsed=', markdownStrParsed);
  return markdownStrParsed;
}

export {
  renderMarkdown,
};
最后更新: 2025/9/14 08:47
Prev
扩展