aboutsummaryrefslogtreecommitdiff
path: root/markup.js
blob: 69adf9c0a6371887be4260816fd4fa63283f5421 (plain)
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// Tweaked from https://github.com/developit/snarkdown

const markup = (() => {

const TAGS = {
  '' : ['<em>','</em>'],
  _ : ['<strong>','</strong>'],
  '~' : ['<s>','</s>'],
  '\n' : ['</p><p>'],
  ' ' : ['</p><p>'],
  '-': ['<hr>']
};

const outdent = str =>
  str.replace(RegExp('^'+(str.match(/^(\t| )+/) || '')[0], 'gm'), '');

const encodeAttr = str =>
  (str+'').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

return function parse(md, prevLinks) {
  let tokenizer = new RegExp(
      "((?:^|\\n+)(?:\\n---+|\\* \\*(?: \\*)+)\\n)|" +
      "(?:^``` *(\\w*)\\n([\\s\\S]*?)\\n```$)|" +
      "((?:(?:^|\\n+)(?:\\t|  {2,}).+)+\\n*)|" +
      "((?:(?:^|\\n)([>*+-]|\\d+\\.)\\s+.*)+)|" +
      "(?:\\!\\[([^\\]]*?)\\]\\(([^\\)]+?)\\))|" +
      "(\\[)|" + "(\\](?:\\(([^\\)]+?)\\))?)|" +
      "(?:(?:^|\\n+)([^\\s].*)\\n(\\-{3,}|={3,})(?:\\n+|$))|" +
      "(?:(?:^|\\n+)(#{1,6})\\s*(.+)(?:\\n+|$))|" +
      "(?:`([^`].*?)`)|(  \\n\\n*|\\n{2,}|__|\\*\\*|[_*]|~~)", "gm"),
    context = [],
    out = '',
    links = prevLinks || {},
    last = 0,
    chunk, prev, token, inner, t;

  function tag(token) {
    const desc = TAGS[token.replace(/\*/g,'_')[1] || ''],
      end = context[context.length-1]==token;
    if (!desc) return token;
    if (!desc[1]) return desc[0];
    context[end?'pop':'push'](token);
    return desc[end|0];
  }

  function flush() {
    let str = '';
    while (context.length) str += tag(context[context.length-1]);
    return str;
  }

  md = md.replace(/^\[(.+?)\]:\s*(.+)$/gm, (s, name, url) => {
    links[name.toLowerCase()] = url;
    return '';
  }).replace(/^\n+|\n+$/g, '');

  while ((token = tokenizer.exec(md))) {
    prev = md.substring(last, token.index);
    last = tokenizer.lastIndex;
    chunk = token[0];
    if (prev.match(/[^\\](\\\\)*\\$/)) {
      // escaped
    }
    // Code/Indent blocks:
    else if (token[3] || token[4]) {
      chunk = '<pre class="code '+(token[4]?'poetry':token[2].toLowerCase())+'">'+
        outdent(encodeAttr(token[3] || token[4]).replace(/^\n+|\n+$/g, ''))+'</pre>';
    }
    // > Quotes, -* lists:
    else if (token[6]) {
      t = token[6];
      if (t.match(/\./)) {
        token[5] = token[5].replace(/^\d+/gm, '');
      }
      inner = parse(outdent(token[5].replace(/^\s*[>*+.-]/gm, '')));
      if (t==='>') t = 'blockquote';
      else {
        t = t.match(/\./) ? 'ol' : 'ul';
        inner = inner.replace(/^(.*)(\n|$)/gm, '<li>$1</li>');
      }
      chunk = '<'+t+'>' + inner + '</'+t+'>';
    }
    // Images:
    else if (token[8]) {
      chunk = `<img src="${encodeAttr(token[8])}" alt="${encodeAttr(token[7])}">`;
    }
    // Links:
    else if (token[10]) {
      out = out.replace('<a>', `<a href="${encodeAttr(token[11] || links[prev.toLowerCase()])}">`);
      chunk = flush() + '</a>';
    }
    else if (token[9]) {
      chunk = '<a>';
    }
    // Headings:
    else if (token[12] || token[14]) {
      t = 'h' + (token[14] ? token[14].length : (token[13][0]==='='?1:2));
      chunk = '<'+t+'>' + parse(token[12] || token[15], links) + '</'+t+'>';
    }
    // `code`:
    else if (token[16]) {
      chunk = '<code>'+encodeAttr(token[16])+'</code>';
    }
    // Inline formatting: *em*, **strong** & friends
    else if (token[17] || token[1]) {
      chunk = tag(token[17] || '--');
    }
    out += prev;
    out += chunk;
  }

  return "<p>" + (out + md.substring(last) + flush()).trim() + "</p>";
};

})();