All files / src/lib/markdown-it/plugins markdown-it-alt-frontmatter.ts

97.05% Statements 33/34
90.9% Branches 10/11
100% Functions 3/3
97.05% Lines 33/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78                                        571x 571x   571x 571x 571x 571x 563x     8x 8x 8x 24x 24x   24x 24x 5x 5x   19x   2x       6x 1x     5x 5x   5x 5x 5x 5x 5x 5x     29x 29x             5x 5x 5x          
import MarkdownIt, { Options } from 'markdown-it';
import StateBlock from 'markdown-it/lib/rules_block/state_block';
import Token from 'markdown-it/lib/token';
import Renderer from 'markdown-it/lib/renderer';
 
/*
 * This file exports a MarkdownIt plugin that converts YAML-style frontmatter syntax (---)
 * into HTML-style `<frontmatter>` tags.
 * Only if there are two sets of `---` and the content inside contains key-value pairs or blank space.
 */
 
/**
 * Markdown-it plugin to parse alternative frontmatter blocks delimited by '---'.
 *
 * This plugin detects blocks anywhere in a Markdown document that begin and end with '---',
 * and contain lines in key-value format (e.g., `key: value`). Blank lines within the block are allowed.
 * If a block is detected, it is rendered as a `<frontmatter>` HTML element containing the block's content.
 */
export function altFrontmatterPlugin(md: MarkdownIt): void {
  function alt_frontmatter(state: StateBlock, startLine: number, endLine: number): boolean {
    const fmSymbol = '---';
    const keyValueRegex = /^\w+:\s+.*/;
 
    let lineStart = state.bMarks[startLine] + state.tShift[startLine];
    let lineEnd = state.eMarks[startLine];
    const marker = state.src.slice(lineStart, lineEnd);
    if (marker !== fmSymbol) {
      return false;
    }
 
    let haveEndMarker = false;
    let nextLine = startLine + 1;
    for (; nextLine < endLine; nextLine += 1) {
      lineStart = state.bMarks[nextLine] + state.tShift[nextLine];
      lineEnd = state.eMarks[nextLine];
 
      const currLine = state.src.slice(lineStart, lineEnd).trim();
      if (currLine === fmSymbol) {
        haveEndMarker = true;
        break;
      }
      if (!(keyValueRegex.test(currLine) || currLine.length === 0)) {
        // terminates if not key value or blank line.
        return false;
      }
    }
 
    if (!haveEndMarker) {
      return false;
    }
    // If a fence has heading spaces, they should be removed from its inner block
    const len = state.sCount[startLine];
    state.line = nextLine + (haveEndMarker ? 1 : 0);
 
    const token: Token = state.push('alt_frontmatter', '', 0);
    token.info = fmSymbol;
    token.content = state.getLines(startLine + 1, nextLine, len, true);
    token.markup = marker;
    token.map = [startLine, state.line];
    return true;
  }
 
  md.block.ruler.before('fence', 'alt_frontmatter', alt_frontmatter);
  md.renderer.rules.alt_frontmatter = (
    tokens: Token[],
    idx: number,
    options: Options,
    env: any,
    slf: Renderer,
  ) => {
    const token = tokens[idx];
    if (token.type === 'alt_frontmatter' && token.info === '---') {
      return `<frontmatter>\n${token.content}</frontmatter>\n`;
    }
    return slf.renderToken(tokens, idx, options);
  };
}