@cobapen/markdown 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { Options } from "markdown-it/index.mjs";
2
2
  import Renderer from "markdown-it/lib/renderer.mjs";
3
3
  import Token from "markdown-it/lib/token.mjs";
4
4
  interface Env {
5
- showCodeTitleByDefault?: boolean;
5
+ showCodeTitleByDefault?: boolean;
6
6
  }
7
7
  /**
8
8
  * Custom fence renderer for markdown-it.
@@ -6,46 +6,45 @@ import { InfoString } from "./info-string.js";
6
6
  * see: markdown-it/lib/renderer.mjs
7
7
  */
8
8
  export function fence_custom(tokens, idx, options, env, slf) {
9
- const token = tokens[idx];
10
- const info_str = token.info ? unescapeAll(token.info).trim() : "";
11
- const info = new InfoString(info_str);
12
- const langName = info.lang;
13
- const langAttrs = info.attrs.join(" ");
14
- let highlighted;
15
- if (options.highlight) {
16
- highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content);
17
- }
18
- else {
19
- highlighted = escapeHtml(token.content);
20
- }
21
- if (highlighted.indexOf("<pre") === 0) {
22
- return highlighted + "\n";
23
- }
24
- // If language exists, inject class gently, without modifying original token.
25
- // May be, one day we will add .deepClone() for token and simplify this part, but
26
- // now we prefer to keep things local.
27
- if (info.hasLang) {
28
- const i = token.attrIndex("class");
29
- const tmpAttrs = token.attrs ? token.attrs.slice() : [];
30
- if (i < 0) {
31
- tmpAttrs.push(["class", options.langPrefix + langName]);
9
+ const token = tokens[idx];
10
+ const info_str = token.info ? unescapeAll(token.info).trim() : "";
11
+ const info = new InfoString(info_str);
12
+ const langName = info.lang;
13
+ const langAttrs = info.attrs.join(" ");
14
+ let highlighted;
15
+ if (options.highlight) {
16
+ highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content);
32
17
  }
33
18
  else {
34
- tmpAttrs[i] = tmpAttrs[i].slice();
35
- tmpAttrs[i][1] += " " + options.langPrefix + langName;
19
+ highlighted = escapeHtml(token.content);
36
20
  }
37
- // Fake token just to render attributes
38
- const tmpToken = {
39
- attrs: tmpAttrs
40
- };
41
- if (info.title.length > 0) {
42
- const style = (env.showCodeTitleByDefault === true)
43
- ? ""
44
- : " style=\"visibility:hidden;\"";
45
- return `<pre><code${slf.renderAttrs(tmpToken)}>${highlighted}</code><span class="title"${style}>${info.title}</span></pre>\n`;
21
+ if (highlighted.indexOf("<pre") === 0) {
22
+ return highlighted + "\n";
46
23
  }
47
- return `<pre><code${slf.renderAttrs(tmpToken)}>${highlighted}</code></pre>\n`;
48
- }
49
- return `<pre><code${slf.renderAttrs(token)}>${highlighted}</code></pre>\n`;
24
+ // If language exists, inject class gently, without modifying original token.
25
+ // May be, one day we will add .deepClone() for token and simplify this part, but
26
+ // now we prefer to keep things local.
27
+ if (info.hasLang) {
28
+ const i = token.attrIndex("class");
29
+ const tmpAttrs = token.attrs ? token.attrs.slice() : [];
30
+ if (i < 0) {
31
+ tmpAttrs.push(["class", options.langPrefix + langName]);
32
+ }
33
+ else {
34
+ tmpAttrs[i] = tmpAttrs[i].slice();
35
+ tmpAttrs[i][1] += " " + options.langPrefix + langName;
36
+ }
37
+ // Fake token just to render attributes
38
+ const tmpToken = {
39
+ attrs: tmpAttrs
40
+ };
41
+ if (info.title.length > 0) {
42
+ const style = (env.showCodeTitleByDefault === true)
43
+ ? ""
44
+ : " style=\"visibility:hidden;\"";
45
+ return `<pre><code${slf.renderAttrs(tmpToken)}>${highlighted}</code><span class="title"${style}>${info.title}</span></pre>\n`;
46
+ }
47
+ return `<pre><code${slf.renderAttrs(tmpToken)}>${highlighted}</code></pre>\n`;
48
+ }
49
+ return `<pre><code${slf.renderAttrs(token)}>${highlighted}</code></pre>\n`;
50
50
  }
51
- //# sourceMappingURL=fence-custom.js.map
@@ -6,16 +6,16 @@
6
6
  import hljs from "highlight.js";
7
7
  import { InfoString } from "./info-string.js";
8
8
  function _debuglog(..._args) {
9
- // if (_debuglog.caller.name !== "") return;
10
- // console.log(...args);
9
+ // if (_debuglog.caller.name !== "") return;
10
+ // console.log(...args);
11
11
  }
12
12
  /** Get digits of the number. e.g. 1000 => 4 */
13
13
  function numDigits(n) {
14
- return Math.floor(n).toString().length;
14
+ return Math.floor(n).toString().length;
15
15
  }
16
16
  /** Print a decimal nicely */
17
17
  function niceDec(n) {
18
- return n.toFixed(3);
18
+ return n.toFixed(3);
19
19
  }
20
20
  /**
21
21
  * Highlight function with line number support
@@ -26,12 +26,12 @@ function niceDec(n) {
26
26
  * @returns
27
27
  */
28
28
  export function highlightWithLineNumber(code, lang, linestart) {
29
- try {
30
- /** convert if lang is specified + supported by highlight.js */
31
- if (lang && hljs.getLanguage(lang)) {
32
- /** do conversion */
33
- let htmlLines = hljs.highlight(code, { language: lang }).value;
34
- /**
29
+ try {
30
+ /** convert if lang is specified + supported by highlight.js */
31
+ if (lang && hljs.getLanguage(lang)) {
32
+ /** do conversion */
33
+ let htmlLines = hljs.highlight(code, { language: lang }).value;
34
+ /**
35
35
  * Attach the line number if specified.
36
36
  *
37
37
  * The given code is rendered in <pre><code>, so each line is terminated by '\n'.
@@ -46,33 +46,33 @@ export function highlightWithLineNumber(code, lang, linestart) {
46
46
  * - user-select: none
47
47
  * - width: ${numDigits}em
48
48
  */
49
- if (linestart !== undefined) {
50
- const lines = htmlLines.split("\n");
51
- const elWidth = numDigits(linestart + lines.length) * 0.8;
52
- const elStyle = "display:inline-block;" +
49
+ if (linestart !== undefined) {
50
+ const lines = htmlLines.split("\n");
51
+ const elWidth = numDigits(linestart + lines.length) * 0.8;
52
+ const elStyle = "display:inline-block;" +
53
53
  "user-select:none;" +
54
54
  `width: ${niceDec(elWidth)}em;`;
55
- lines.forEach((line, i, lines) => {
56
- lines[i] =
55
+ lines.forEach((line, i, lines) => {
56
+ lines[i] =
57
57
  "<div class=\"line\">" +
58
58
  `<span class="line-no" style="${elStyle}">${linestart + i}</span>${line}` +
59
59
  "</div>";
60
- });
61
- _debuglog(lines);
62
- htmlLines = lines.join("");
63
- }
64
- return htmlLines;
60
+ });
61
+ _debuglog(lines);
62
+ htmlLines = lines.join("");
63
+ }
64
+ return htmlLines;
65
+ }
66
+ else {
67
+ // no language , no highlighting.
68
+ // If you want line numbers without highlighting, set language to
69
+ // "nohighlight" or "text"
70
+ return "";
71
+ }
65
72
  }
66
- else {
67
- // no language , no highlighting.
68
- // If you want line numbers without highlighting, set language to
69
- // "nohighlight" or "text"
70
- return "";
73
+ catch (_) {
74
+ return "";
71
75
  }
72
- }
73
- catch (_) {
74
- return "";
75
- }
76
76
  }
77
77
  /**
78
78
  * Exported function for markdown-it
@@ -83,9 +83,8 @@ export function highlightWithLineNumber(code, lang, linestart) {
83
83
  * @returns
84
84
  */
85
85
  export function highlighterForMarkdownIt(str, lang, attrs) {
86
- _debuglog(lang ? lang : "(lang is empty or undefined)");
87
- const info = new InfoString(lang + " " + attrs);
88
- _debuglog(info);
89
- return highlightWithLineNumber(str, info.lang, info.linestart);
86
+ _debuglog(lang ? lang : "(lang is empty or undefined)");
87
+ const info = new InfoString(lang + " " + attrs);
88
+ _debuglog(info);
89
+ return highlightWithLineNumber(str, info.lang, info.linestart);
90
90
  }
91
- //# sourceMappingURL=highlight.js.map
@@ -2,25 +2,25 @@
2
2
  * InfoString parses info_string from fenced code block.
3
3
  */
4
4
  export declare class InfoString {
5
- private readonly _lang;
6
- private readonly _attrs;
7
- private readonly _title;
8
- private readonly _linestart;
9
- constructor(info: string);
10
- get lang(): string;
11
- get attrs(): string[];
12
- get title(): string;
13
- get linestart(): number | undefined;
14
- get hasLang(): boolean;
15
- /** Parse info_string into an array of strings. All quotes are removed*/
16
- static parseInfoString(info: string): string[];
17
- /** Parse metadata notation "{filename:line, ...}"" */
18
- static parseMetaNotation(text: string): {
19
- filename: string;
20
- line: number;
21
- } | undefined;
22
- /** From attributes list, return title metadata */
23
- static getTitle(attr: string[]): string;
24
- /** From attributes list, return line number if defined */
25
- static getLineStart(attr: string[]): number | undefined;
5
+ private readonly _lang;
6
+ private readonly _attrs;
7
+ private readonly _title;
8
+ private readonly _linestart;
9
+ constructor(info: string);
10
+ get lang(): string;
11
+ get attrs(): string[];
12
+ get title(): string;
13
+ get linestart(): number | undefined;
14
+ get hasLang(): boolean;
15
+ /** Parse info_string into an array of strings. All quotes are removed*/
16
+ static parseInfoString(info: string): string[];
17
+ /** Parse metadata notation "{filename:line, ...}"" */
18
+ static parseMetaNotation(text: string): {
19
+ filename: string;
20
+ line: number;
21
+ } | undefined;
22
+ /** From attributes list, return title metadata */
23
+ static getTitle(attr: string[]): string;
24
+ /** From attributes list, return line number if defined */
25
+ static getLineStart(attr: string[]): number | undefined;
26
26
  }
@@ -2,128 +2,127 @@
2
2
  * InfoString parses info_string from fenced code block.
3
3
  */
4
4
  export class InfoString {
5
- _lang;
6
- _attrs;
7
- _title;
8
- _linestart;
9
- constructor(info) {
10
- if (info.length > 0) {
11
- const arr = InfoString.parseInfoString(info);
12
- this._lang = arr[0];
13
- this._attrs = arr.slice(1);
14
- this._title = InfoString.getTitle(this._attrs);
15
- this._linestart = InfoString.getLineStart(this._attrs);
5
+ _lang;
6
+ _attrs;
7
+ _title;
8
+ _linestart;
9
+ constructor(info) {
10
+ if (info.length > 0) {
11
+ const arr = InfoString.parseInfoString(info);
12
+ this._lang = arr[0];
13
+ this._attrs = arr.slice(1);
14
+ this._title = InfoString.getTitle(this._attrs);
15
+ this._linestart = InfoString.getLineStart(this._attrs);
16
+ }
17
+ else {
18
+ this._lang = "";
19
+ this._attrs = [];
20
+ this._title = "";
21
+ this._linestart = undefined;
22
+ }
23
+ }
24
+ get lang() {
25
+ return this._lang;
16
26
  }
17
- else {
18
- this._lang = "";
19
- this._attrs = [];
20
- this._title = "";
21
- this._linestart = undefined;
27
+ get attrs() {
28
+ return this._attrs;
22
29
  }
23
- }
24
- get lang() {
25
- return this._lang;
26
- }
27
- get attrs() {
28
- return this._attrs;
29
- }
30
- get title() {
31
- return this._title;
32
- }
33
- get linestart() {
34
- return this._linestart;
35
- }
36
- get hasLang() {
37
- return this._lang.length > 0;
38
- }
39
- /** Parse info_string into an array of strings. All quotes are removed*/
40
- static parseInfoString(info) {
41
- // There are 4 possible tokens, but it can be reduced to 2 patterns.
42
- // arg, "arg quoted", key=value, key="value quoted"
43
- //
44
- // This function returns a quote-removed string.
45
- //
46
- // const regex = /([^\s]+=)?(?:"([^"]*)"|'([^']*)'|([^\s]+))/g;
47
- // ~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48
- // key= "value" 'value' value
49
- //
50
- // The regex above does not support escape letters. The next regex
51
- // is escape char aware.
52
- //
53
- // NOTE: This class is designed to handle escape letters. However, tools
54
- // might decide to unescape info_string before used (e.g. markdown-it).
55
- // In such case, you might need to use double-escaped quotes.
56
- //
57
- const dq = "[^\"\\\\]*(?:\\\\.|[^\"\\\\]*)*"; // [^"]*
58
- const sq = "[^\'\\\\]*(?:\\\\.|[^\'\\\\]*)*"; // [^']*
59
- // ~~~~~~~~~~ ~~~~~ ~~~~~~~~~~
60
- // char seq ( escape char seq )*
61
- //
62
- const ptn = `([^\\s]+=)?(?:"(${dq})"|'(${sq})'|([^\\s]+))`;
63
- // ~~~~~~~~~~ ~~~~~~~~~ ~~~~~~~~~ ~~~~~~~~~
64
- // key= "value" 'value' value
65
- const regex = new RegExp(ptn, "g");
66
- const result = [];
67
- let match;
68
- while ((match = regex.exec(info)) !== null) {
69
- let text = (match[1] || "") + (match[2] || match[3] || match[4] || "");
70
- text = unescape(text).trim();
71
- result.push(text);
30
+ get title() {
31
+ return this._title;
72
32
  }
73
- return result;
74
- }
75
- /** Parse metadata notation "{filename:line, ...}"" */
76
- static parseMetaNotation(text) {
77
- const match = text.match(/^\{\s*([^:]+):(\d+)\s*\}$/);
78
- if (match) {
79
- return {
80
- filename: match[1],
81
- line: parseInt(match[2], 10),
82
- };
33
+ get linestart() {
34
+ return this._linestart;
83
35
  }
84
- return undefined;
85
- }
86
- /** From attributes list, return title metadata */
87
- static getTitle(attr) {
88
- const titleAttr = attr.find(x => x.startsWith("title=")) ?? "";
89
- if (titleAttr.length > 0) {
90
- const match = titleAttr.match(/^title=(.*)$/);
91
- if (match) {
92
- let value = match[1].trim();
93
- if (value.startsWith("\"") && value.endsWith("\"")) {
94
- value = value.slice(1, -1);
95
- value = unescape(value).trim();
36
+ get hasLang() {
37
+ return this._lang.length > 0;
38
+ }
39
+ /** Parse info_string into an array of strings. All quotes are removed*/
40
+ static parseInfoString(info) {
41
+ // There are 4 possible tokens, but it can be reduced to 2 patterns.
42
+ // arg, "arg quoted", key=value, key="value quoted"
43
+ //
44
+ // This function returns a quote-removed string.
45
+ //
46
+ // const regex = /([^\s]+=)?(?:"([^"]*)"|'([^']*)'|([^\s]+))/g;
47
+ // ~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48
+ // key= "value" 'value' value
49
+ //
50
+ // The regex above does not support escape letters. The next regex
51
+ // is escape char aware.
52
+ //
53
+ // NOTE: This class is designed to handle escape letters. However, tools
54
+ // might decide to unescape info_string before used (e.g. markdown-it).
55
+ // In such case, you might need to use double-escaped quotes.
56
+ //
57
+ const dq = "[^\"\\\\]*(?:\\\\.|[^\"\\\\]*)*"; // [^"]*
58
+ const sq = "[^\'\\\\]*(?:\\\\.|[^\'\\\\]*)*"; // [^']*
59
+ // ~~~~~~~~~~ ~~~~~ ~~~~~~~~~~
60
+ // char seq ( escape char seq )*
61
+ //
62
+ const ptn = `([^\\s]+=)?(?:"(${dq})"|'(${sq})'|([^\\s]+))`;
63
+ // ~~~~~~~~~~ ~~~~~~~~~ ~~~~~~~~~ ~~~~~~~~~
64
+ // key= "value" 'value' value
65
+ const regex = new RegExp(ptn, "g");
66
+ const result = [];
67
+ let match;
68
+ while ((match = regex.exec(info)) !== null) {
69
+ let text = (match[1] || "") + (match[2] || match[3] || match[4] || "");
70
+ text = unescape(text).trim();
71
+ result.push(text);
96
72
  }
97
- return value;
98
- }
99
- else {
100
- throw new Error("Must not fail. Check impl.");
101
- }
73
+ return result;
102
74
  }
103
- if (attr.length > 0) {
104
- const meta = InfoString.parseMetaNotation(attr[0]);
105
- if (meta && meta.filename.length > 0) {
106
- return meta.filename;
107
- }
75
+ /** Parse metadata notation "{filename:line, ...}"" */
76
+ static parseMetaNotation(text) {
77
+ const match = text.match(/^\{\s*([^:]+):(\d+)\s*\}$/);
78
+ if (match) {
79
+ return {
80
+ filename: match[1],
81
+ line: parseInt(match[2], 10),
82
+ };
83
+ }
84
+ return undefined;
108
85
  }
109
- return "";
110
- }
111
- /** From attributes list, return line number if defined */
112
- static getLineStart(attr) {
113
- const lineAttr = attr.find(x => x.startsWith("linestart=")) ?? "";
114
- if (lineAttr.length > 0) {
115
- return parseInt(lineAttr.split("=")[1].trim());
86
+ /** From attributes list, return title metadata */
87
+ static getTitle(attr) {
88
+ const titleAttr = attr.find(x => x.startsWith("title=")) ?? "";
89
+ if (titleAttr.length > 0) {
90
+ const match = titleAttr.match(/^title=(.*)$/);
91
+ if (match) {
92
+ let value = match[1].trim();
93
+ if (value.startsWith("\"") && value.endsWith("\"")) {
94
+ value = value.slice(1, -1);
95
+ value = unescape(value).trim();
96
+ }
97
+ return value;
98
+ }
99
+ else {
100
+ throw new Error("Must not fail. Check impl.");
101
+ }
102
+ }
103
+ if (attr.length > 0) {
104
+ const meta = InfoString.parseMetaNotation(attr[0]);
105
+ if (meta && meta.filename.length > 0) {
106
+ return meta.filename;
107
+ }
108
+ }
109
+ return "";
116
110
  }
117
- if (attr.length > 0) {
118
- const meta = InfoString.parseMetaNotation(attr[0]);
119
- if (meta && meta.line > 0) {
120
- return meta.line;
121
- }
111
+ /** From attributes list, return line number if defined */
112
+ static getLineStart(attr) {
113
+ const lineAttr = attr.find(x => x.startsWith("linestart=")) ?? "";
114
+ if (lineAttr.length > 0) {
115
+ return parseInt(lineAttr.split("=")[1].trim());
116
+ }
117
+ if (attr.length > 0) {
118
+ const meta = InfoString.parseMetaNotation(attr[0]);
119
+ if (meta && meta.line > 0) {
120
+ return meta.line;
121
+ }
122
+ }
123
+ return undefined;
122
124
  }
123
- return undefined;
124
- }
125
125
  }
126
126
  function unescape(text) {
127
- return text.replaceAll(/\\\"/g, "\"").replaceAll(/\\\'/g, "'");
127
+ return text.replaceAll(/\\\"/g, "\"").replaceAll(/\\\'/g, "'");
128
128
  }
129
- //# sourceMappingURL=info-string.js.map
package/dist/index.d.ts CHANGED
@@ -1,42 +1,42 @@
1
1
  import markdownIt, { Options as MarkdownOptions } from "markdown-it";
2
2
  import { Options as MathOptions } from "./math/mathjax.js";
3
3
  export interface Config {
4
- /**
4
+ /**
5
5
  * Set "true" to display the title (if specified) of the fenced code block.
6
6
  * The title is hidden by default, and user must explicitly override the style.
7
7
  */
8
- showCodeTitleByDefault: boolean;
9
- /**
8
+ showCodeTitleByDefault: boolean;
9
+ /**
10
10
  * MarkdownIt options
11
11
  */
12
- markdown: Partial<MarkdownOptions>;
13
- /**
12
+ markdown: Partial<MarkdownOptions>;
13
+ /**
14
14
  * MathJax options
15
15
  */
16
- math: Partial<MathOptions>;
16
+ math: Partial<MathOptions>;
17
17
  }
18
18
  export type Options = Partial<Config>;
19
19
  export declare class CMarkdown {
20
- private readonly _config;
21
- private readonly _mj;
22
- private readonly _md;
23
- constructor(option?: Options);
24
- /**
20
+ private readonly _config;
21
+ private readonly _mj;
22
+ private readonly _md;
23
+ constructor(option?: Options);
24
+ /**
25
25
  * Install plugins and renderers to the markdown-it instance.
26
26
  *
27
27
  * @param md The instance
28
28
  */
29
- setup(md: markdownIt): void;
30
- /**
29
+ setup(md: markdownIt): void;
30
+ /**
31
31
  * Render html from markdown.
32
32
  *
33
33
  * @param text markdown text
34
34
  * @returns html text
35
35
  */
36
- render(text: string): string;
37
- /**
36
+ render(text: string): string;
37
+ /**
38
38
  * Returns the MathJax CSS.
39
39
  * @returns
40
40
  */
41
- mathcss(): string;
41
+ mathcss(): string;
42
42
  }