@cobapen/markdown 0.3.1 → 0.4.1

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.
package/README.md CHANGED
@@ -14,4 +14,4 @@ https://cobapen.github.io/markdown
14
14
  - fenced code block with filename, line-number support
15
15
  - server side syntax highlighting
16
16
  - server side math rendering
17
-
17
+ - complex table rendering
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "@cobapen/markdown",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "A markdown converter for cobapen website",
5
- "keywords": ["markdown"],
5
+ "keywords": [
6
+ "markdown"
7
+ ],
6
8
  "license": "MIT",
7
9
  "author": "yamavol",
8
10
  "type": "module",
@@ -20,20 +22,24 @@
20
22
  "dist/**/*.js",
21
23
  "dist/**/*.d.ts"
22
24
  ],
25
+ "engines": {
26
+ "node": ">=20.11.0 <21 || >=21.2.0"
27
+ },
23
28
  "scripts": {
24
29
  "build": "tsc",
25
- "build:doc": "tsc && node docs/build.js",
30
+ "build:dev": "tsc --build tsconfig.dev.json",
31
+ "build:doc": "npm run build && node docs/build.js",
26
32
  "test": "vitest",
27
33
  "coverage": "vitest run --coverage",
28
34
  "lint": "eslint src",
29
- "lint:fix": "eslint src --fix",
30
- "lint:dist": "eslint dist --fix"
35
+ "lint:fix": "eslint src --fix"
31
36
  },
32
37
  "dependencies": {
33
38
  "highlight.js": "^11.11.1",
34
39
  "katex": "^0.16.22",
35
40
  "lodash-es": "^4.17.21",
36
41
  "markdown-it": "^14.1.0",
42
+ "markdown-it-adv-table": "^0.1.1",
37
43
  "markdown-it-anchor": "^9.2.0",
38
44
  "markdown-it-cjk-breaks": "^2.0.0",
39
45
  "markdown-it-deflist": "^3.0.0",
@@ -42,7 +48,7 @@
42
48
  "mathjax-full": "^3.2.2"
43
49
  },
44
50
  "devDependencies": {
45
- "@stylistic/eslint-plugin": "^4.2.0",
51
+ "@cobapen/eslint-config": "^0.4.0",
46
52
  "@types/katex": "^0.16.7",
47
53
  "@types/lodash-es": "^4.17.12",
48
54
  "@types/markdown-it": "^14.1.2",
@@ -52,7 +58,6 @@
52
58
  "eslint": "^9.24.0",
53
59
  "mustache": "^4.2.0",
54
60
  "typescript": "^5.8.3",
55
- "typescript-eslint": "^8.29.1",
56
61
  "vitest": "^3.1.1"
57
62
  }
58
63
  }
@@ -1,13 +0,0 @@
1
- import { Options } from "markdown-it/index.mjs";
2
- import Renderer from "markdown-it/lib/renderer.mjs";
3
- import Token from "markdown-it/lib/token.mjs";
4
- interface Env {
5
- showCodeTitleByDefault?: boolean;
6
- }
7
- /**
8
- * Custom fence renderer for markdown-it.
9
- *
10
- * see: markdown-it/lib/renderer.mjs
11
- */
12
- export declare function fence_custom(tokens: Token[], idx: number, options: Options, env: Env, slf: Renderer): string;
13
- export {};
@@ -1,51 +0,0 @@
1
- import { escapeHtml, unescapeAll } from "markdown-it/lib/common/utils.mjs";
2
- import { InfoString } from "./info-string.js";
3
- /**
4
- * Custom fence renderer for markdown-it.
5
- *
6
- * see: markdown-it/lib/renderer.mjs
7
- */
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]);
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
- }
51
- //# sourceMappingURL=fence-custom.js.map
@@ -1,18 +0,0 @@
1
- /**
2
- * Highlight function with line number support
3
- *
4
- * @param code
5
- * @param lang
6
- * @param linestart
7
- * @returns
8
- */
9
- export declare function highlightWithLineNumber(code: string, lang: string, linestart?: number): string;
10
- /**
11
- * Exported function for markdown-it
12
- *
13
- * @param str
14
- * @param lang
15
- * @param attrs
16
- * @returns
17
- */
18
- export declare function highlighterForMarkdownIt(str: string, lang: string, attrs: string): string;
@@ -1,91 +0,0 @@
1
- /**
2
- * highlight.ts
3
- * 2023-11-20
4
- * provides a custom highlighter for MarkdownIt
5
- */
6
- import hljs from "highlight.js";
7
- import { InfoString } from "./info-string.js";
8
- function _debuglog(..._args) {
9
- // if (_debuglog.caller.name !== "") return;
10
- // console.log(...args);
11
- }
12
- /** Get digits of the number. e.g. 1000 => 4 */
13
- function numDigits(n) {
14
- return Math.floor(n).toString().length;
15
- }
16
- /** Print a decimal nicely */
17
- function niceDec(n) {
18
- return n.toFixed(3);
19
- }
20
- /**
21
- * Highlight function with line number support
22
- *
23
- * @param code
24
- * @param lang
25
- * @param linestart
26
- * @returns
27
- */
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
- /**
35
- * Attach the line number if specified.
36
- *
37
- * The given code is rendered in <pre><code>, so each line is terminated by '\n'.
38
- * Split the input, wrap them by <div class="line">.
39
- * At the beginning of each line, <span class="line-no"> is added.
40
- *
41
- * default styles are embedded so that the text is readable
42
- * even if css was not applied.
43
- *
44
- * default-styles:
45
- * - display: inline-block
46
- * - user-select: none
47
- * - width: ${numDigits}em
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;" +
53
- "user-select:none;" +
54
- `width: ${niceDec(elWidth)}em;`;
55
- lines.forEach((line, i, lines) => {
56
- lines[i] =
57
- "<div class=\"line\">" +
58
- `<span class="line-no" style="${elStyle}">${linestart + i}</span>${line}` +
59
- "</div>";
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
- }
72
- }
73
- catch (_) {
74
- return "";
75
- }
76
- }
77
- /**
78
- * Exported function for markdown-it
79
- *
80
- * @param str
81
- * @param lang
82
- * @param attrs
83
- * @returns
84
- */
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);
90
- }
91
- //# sourceMappingURL=highlight.js.map
@@ -1,26 +0,0 @@
1
- /**
2
- * InfoString parses info_string from fenced code block.
3
- */
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;
26
- }
@@ -1,129 +0,0 @@
1
- /**
2
- * InfoString parses info_string from fenced code block.
3
- */
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);
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;
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);
72
- }
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
- };
83
- }
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();
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 "";
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());
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;
124
- }
125
- }
126
- function unescape(text) {
127
- return text.replaceAll(/\\\"/g, "\"").replaceAll(/\\\'/g, "'");
128
- }
129
- //# sourceMappingURL=info-string.js.map
package/dist/index.d.ts DELETED
@@ -1,42 +0,0 @@
1
- import markdownIt, { Options as MarkdownOptions } from "markdown-it";
2
- import { Options as MathOptions } from "./math/mathjax.js";
3
- export interface Config {
4
- /**
5
- * Set "true" to display the title (if specified) of the fenced code block.
6
- * The title is hidden by default, and user must explicitly override the style.
7
- */
8
- showCodeTitleByDefault: boolean;
9
- /**
10
- * MarkdownIt options
11
- */
12
- markdown: Partial<MarkdownOptions>;
13
- /**
14
- * MathJax options
15
- */
16
- math: Partial<MathOptions>;
17
- }
18
- export type Options = Partial<Config>;
19
- export declare class CMarkdown {
20
- private readonly _config;
21
- private readonly _mj;
22
- private readonly _md;
23
- constructor(option?: Options);
24
- /**
25
- * Install plugins and renderers to the markdown-it instance.
26
- *
27
- * @param md The instance
28
- */
29
- setup(md: markdownIt): void;
30
- /**
31
- * Render html from markdown.
32
- *
33
- * @param text markdown text
34
- * @returns html text
35
- */
36
- render(text: string): string;
37
- /**
38
- * Returns the MathJax CSS.
39
- * @returns
40
- */
41
- mathcss(): string;
42
- }
package/dist/index.js DELETED
@@ -1,79 +0,0 @@
1
- import markdownIt from "markdown-it";
2
- import { highlighterForMarkdownIt } from "./code/highlight.js";
3
- import anchor from "markdown-it-anchor";
4
- // @ts-ignore
5
- import cjkbreaks from "markdown-it-cjk-breaks";
6
- // @ts-ignore
7
- import deflist from "markdown-it-deflist";
8
- // @ts-ignore
9
- import toc from "markdown-it-table-of-contents";
10
- // @ts-ignore
11
- import footnote from "markdown-it-footnote";
12
- import { MathjaxEngine } from "./math/mathjax.js";
13
- import { mdmath } from "./math/mdmath.js";
14
- import { fence_custom } from "./code/fence-custom.js";
15
- import { replacelink } from "./link/replacelink.js";
16
- const defaultOptions = {
17
- showCodeTitleByDefault: false,
18
- markdown: {
19
- html: true,
20
- linkify: true,
21
- highlight: highlighterForMarkdownIt,
22
- },
23
- math: {
24
- tex: {
25
- macros: {
26
- bm: ["\\boldsymbol{#1}", 1],
27
- },
28
- },
29
- }
30
- };
31
- export class CMarkdown {
32
- _config;
33
- _mj;
34
- _md;
35
- constructor(option) {
36
- const config = { ...defaultOptions, ...option };
37
- const mj = new MathjaxEngine(config.math);
38
- const md = markdownIt(config.markdown);
39
- this._config = config;
40
- this._mj = mj;
41
- this._md = md;
42
- this.setup(md);
43
- }
44
- /**
45
- * Install plugins and renderers to the markdown-it instance.
46
- *
47
- * @param md The instance
48
- */
49
- setup(md) {
50
- md.renderer.rules.fence = fence_custom;
51
- md.use(anchor)
52
- .use(cjkbreaks)
53
- .use(footnote)
54
- .use(deflist)
55
- .use(replacelink)
56
- .use(mdmath(this._mj))
57
- .use(toc, {
58
- includeLevel: [2, 3],
59
- });
60
- }
61
- /**
62
- * Render html from markdown.
63
- *
64
- * @param text markdown text
65
- * @returns html text
66
- */
67
- render(text) {
68
- const env = { ...this._config }; // env object must s
69
- return this._md.render(text, env);
70
- }
71
- /**
72
- * Returns the MathJax CSS.
73
- * @returns
74
- */
75
- mathcss() {
76
- return this._mj.stylesheet();
77
- }
78
- }
79
- //# sourceMappingURL=index.js.map
@@ -1,8 +0,0 @@
1
- import { PluginWithOptions } from "markdown-it";
2
- import Token from "markdown-it/lib/token.mjs";
3
- type ReplaceHandler = (link: string, env: any, token: Token) => string;
4
- interface Options {
5
- replace: ReplaceHandler;
6
- }
7
- export declare const replacelink: PluginWithOptions<Options>;
8
- export default replacelink;
@@ -1,40 +0,0 @@
1
- function defaultHandler(link, _env, _token) {
2
- if (!link.startsWith("http") && link.endsWith(".md")) {
3
- return link.replace(/\.md$/, ".html");
4
- }
5
- else {
6
- return link;
7
- }
8
- }
9
- function replaceAttr(token, attrName, handler, env) {
10
- token.attrs?.forEach(attr => {
11
- if (attr[0] === attrName) {
12
- attr[1] = handler(attr[1], env, token);
13
- }
14
- });
15
- }
16
- function getHandler(option) {
17
- const replaceFn = option?.replace || defaultHandler;
18
- function handler(state) {
19
- state.tokens.forEach(token => {
20
- if (token.type === "inline" && token.children !== null) {
21
- token.children.forEach(childToken => {
22
- if (childToken.type == "link_open") {
23
- replaceAttr(childToken, "href", replaceFn, state.env);
24
- }
25
- else if (childToken.type == "image") {
26
- replaceAttr(childToken, "src", replaceFn, state.env);
27
- }
28
- });
29
- }
30
- });
31
- }
32
- ;
33
- return handler;
34
- }
35
- export const replacelink = (md, option) => {
36
- const handler = getHandler(option);
37
- md.core.ruler.after("linkify", "replace_link", handler);
38
- };
39
- export default replacelink;
40
- //# sourceMappingURL=replacelink.js.map
@@ -1,97 +0,0 @@
1
- /**
2
- * mathjax.ts
3
- *
4
- * Server-Side Mathjax converter from TeX input to CommonHTML.
5
- *
6
- * see official examples for more information
7
- * https://github.com/mathjax/mathjax-v3
8
- * https://github.com/mathjax/MathJax-demos-node/blob/master/direct/tex2chtml
9
- */
10
- import { type LiteAdaptor } from "mathjax-full/js/adaptors/liteAdaptor.js";
11
- import { LiteElement } from "mathjax-full/js/adaptors/lite/Element.js";
12
- import { type LiteText } from "mathjax-full/js/adaptors/lite/Text.js";
13
- import { type LiteDocument } from "mathjax-full/js/adaptors/lite/Document.js";
14
- import { type MathDocument } from "mathjax-full/js/core/MathDocument.js";
15
- import { TeX } from "mathjax-full/js/input/tex.js";
16
- import { CHTML } from "mathjax-full/js/output/chtml.js";
17
- type N = LiteElement;
18
- type T = LiteText;
19
- type D = LiteDocument;
20
- interface AnyObject {
21
- [x: string]: any;
22
- }
23
- interface TexConfig {
24
- packages: string | [string] | AnyObject;
25
- inlineMath: [[string, string]];
26
- displayMath: [[string, string]];
27
- processEscapes: boolean;
28
- processEnvironments: boolean;
29
- processRefs: boolean;
30
- digits: RegExp;
31
- tags: string;
32
- tagSide: string;
33
- tagIndent: string;
34
- useLabelIds: boolean;
35
- multlineWidth: string;
36
- maxMacros: number;
37
- maxBuffer: number;
38
- baseURL: string;
39
- formatError: (jax: object, err: Error) => void;
40
- macros: AnyObject;
41
- }
42
- interface CHTMLConfig {
43
- scale: number;
44
- minScale: number;
45
- matchFontHeight: boolean;
46
- mtextInheritFont: boolean;
47
- merrorInheritFont: boolean;
48
- mtextFont: string;
49
- merrorFont: string;
50
- mathmlspacing: boolean;
51
- skipAttributes: AnyObject;
52
- exFactor: number;
53
- displayAlign: string;
54
- displayIndent: number | string;
55
- fontURL: string;
56
- adaptiveCSS: boolean;
57
- }
58
- export interface Options {
59
- inline: boolean;
60
- em: number;
61
- ex: number;
62
- width: number;
63
- tex?: Partial<TexConfig>;
64
- chtml?: Partial<CHTMLConfig>;
65
- }
66
- /**
67
- * Initialize and encapsulates mathjax instances to generate
68
- * CommonHTML from TeX input.
69
- *
70
- * There are 2 important methods. One converts the input.
71
- * The other returns a stylesheet document. The stylesheet must be included
72
- * in your HTML document to render the equation properly.
73
- */
74
- export declare class MathjaxEngine {
75
- option: Options;
76
- adaptor: LiteAdaptor;
77
- tex: TeX<N, T, D>;
78
- chtml: CHTML<N, T, D>;
79
- html: MathDocument<N, T, D>;
80
- constructor(option?: Partial<Options>);
81
- /**
82
- * convert TeX input to CHTML.
83
- *
84
- * @param tex input string
85
- * @param override parameter to override the defaults, if you wish to
86
- * @returns
87
- */
88
- convert(tex: string, override?: Partial<Options>): string;
89
- /**
90
- * returns adaptive css (stylesheet for the processed equations only),
91
- * or the full mathjax css (if configured)
92
- *
93
- * @returns css content
94
- */
95
- stylesheet(): string;
96
- }
97
- export {};
@@ -1,110 +0,0 @@
1
- /**
2
- * mathjax.ts
3
- *
4
- * Server-Side Mathjax converter from TeX input to CommonHTML.
5
- *
6
- * see official examples for more information
7
- * https://github.com/mathjax/mathjax-v3
8
- * https://github.com/mathjax/MathJax-demos-node/blob/master/direct/tex2chtml
9
- */
10
- import { liteAdaptor } from "mathjax-full/js/adaptors/liteAdaptor.js";
11
- import { LiteElement } from "mathjax-full/js/adaptors/lite/Element.js"; /* N */
12
- import { RegisterHTMLHandler } from "mathjax-full/js/handlers/html.js";
13
- import { TeX } from "mathjax-full/js/input/tex.js";
14
- import { mathjax } from "mathjax-full/js/mathjax.js";
15
- import { CHTML } from "mathjax-full/js/output/chtml.js";
16
- import { AllPackages } from "mathjax-full/js/input/tex/AllPackages.js";
17
- import { merge } from "lodash-es";
18
- const MATHJAX_DEFAULT_FONT_URL = "https://cdn.jsdelivr.net/npm/mathjax-full@3/es5/output/chtml/fonts/woff-v2";
19
- const defaultOption = {
20
- inline: false,
21
- em: 16,
22
- ex: 8,
23
- width: 80 * 16,
24
- tex: {
25
- packages: AllPackages,
26
- },
27
- chtml: {
28
- scale: 1.21, // magic # chosen which look nice for me
29
- fontURL: MATHJAX_DEFAULT_FONT_URL,
30
- adaptiveCSS: true,
31
- exFactor: 5,
32
- },
33
- };
34
- /**
35
- * Initialize and encapsulates mathjax instances to generate
36
- * CommonHTML from TeX input.
37
- *
38
- * There are 2 important methods. One converts the input.
39
- * The other returns a stylesheet document. The stylesheet must be included
40
- * in your HTML document to render the equation properly.
41
- */
42
- export class MathjaxEngine {
43
- option;
44
- adaptor;
45
- tex;
46
- chtml;
47
- html;
48
- constructor(option) {
49
- this.option = merge({}, defaultOption, option);
50
- if (typeof this.option.tex?.packages === "string") {
51
- this.option.tex.packages = this.option.tex.packages.split(/\s*,\s*/);
52
- }
53
- this.adaptor = liteAdaptor();
54
- RegisterHTMLHandler(this.adaptor);
55
- const tex = new TeX(this.option.tex);
56
- const chtml = new CHTML(this.option.chtml);
57
- const html = mathjax.document("", {
58
- InputJax: tex,
59
- OutputJax: chtml,
60
- });
61
- html.addRenderAction("typeset", 155, renderDoc, renderMath);
62
- this.tex = tex;
63
- this.chtml = chtml;
64
- this.html = html;
65
- function renderDoc(_doc) { }
66
- function renderMath(math, doc) {
67
- const adaptor = doc.adaptor;
68
- const text = adaptor.node("mjx-copytext", { "aria-hidden": true }, [
69
- adaptor.text(math.math),
70
- ]);
71
- adaptor.setStyle(text, "position", "absolute");
72
- adaptor.setStyle(text, "display", "none");
73
- adaptor.setStyle(text, "width", "0");
74
- adaptor.setStyle(math.typesetRoot, "position", "relative");
75
- adaptor.append(math.typesetRoot, text);
76
- }
77
- }
78
- /**
79
- * convert TeX input to CHTML.
80
- *
81
- * @param tex input string
82
- * @param override parameter to override the defaults, if you wish to
83
- * @returns
84
- */
85
- convert(tex, override) {
86
- const node = this.html.convert(tex, {
87
- display: !(override?.inline ?? this.option.inline),
88
- em: override?.em ?? this.option.em,
89
- ex: override?.ex ?? this.option.ex,
90
- containerWidth: override?.width ?? this.option.width,
91
- scale: 1.0,
92
- });
93
- if (node instanceof LiteElement) {
94
- return this.adaptor.outerHTML(node);
95
- }
96
- else {
97
- return "ERROR";
98
- }
99
- }
100
- /**
101
- * returns adaptive css (stylesheet for the processed equations only),
102
- * or the full mathjax css (if configured)
103
- *
104
- * @returns css content
105
- */
106
- stylesheet() {
107
- return this.adaptor.textContent(this.chtml.styleSheet(this.html));
108
- }
109
- }
110
- //# sourceMappingURL=mathjax.js.map
@@ -1,14 +0,0 @@
1
- /**
2
- * mdmath.ts
3
- *
4
- * MarkdownIt extension for math equation processing
5
- */
6
- import { PluginSimple } from "markdown-it";
7
- import { MathjaxEngine } from "./mathjax.js";
8
- /**
9
- * returns a Markdown-It plugin
10
- *
11
- * @param math mathjax Engine to use
12
- * @returns
13
- */
14
- export declare function mdmath(math: MathjaxEngine): PluginSimple;
@@ -1,67 +0,0 @@
1
- /**
2
- * mdmath.ts
3
- *
4
- * MarkdownIt extension for math equation processing
5
- */
6
- import katex from "katex";
7
- import { math_block, math_inline } from "./mdparser.js";
8
- /**
9
- * Returns a MarkdownIt renderer to render inline/block TeX into HTML.
10
- * `KaTeX` is used for inline math, `MathJax` is used for block math.
11
- *
12
- * @param mathjax mathjax engine (used for block math processing)
13
- * @returns
14
- */
15
- function getRenderers(mathjax) {
16
- function renderInlineMath(tex) {
17
- return katex.renderToString(tex, {
18
- throwOnError: false,
19
- strict: (code, _msg, _token) => {
20
- switch (code) {
21
- case "unicodeTextInMathMode":
22
- return "ignore";
23
- default:
24
- return "warn";
25
- }
26
- },
27
- });
28
- }
29
- function renderBlockMath(tex) {
30
- try {
31
- const math = mathjax.convert(tex);
32
- return "<p>" + math + "</p>";
33
- }
34
- catch (err) {
35
- console.error(err);
36
- return tex;
37
- }
38
- }
39
- function inlineRenderer(tokens, index) {
40
- return renderInlineMath(tokens[index].content);
41
- }
42
- function blockRenderer(tokens, index) {
43
- return renderBlockMath(tokens[index].content + "\n");
44
- }
45
- return {
46
- inlineRenderer,
47
- blockRenderer,
48
- };
49
- }
50
- /**
51
- * returns a Markdown-It plugin
52
- *
53
- * @param math mathjax Engine to use
54
- * @returns
55
- */
56
- export function mdmath(math) {
57
- const renderer = getRenderers(math);
58
- return (md) => {
59
- md.inline.ruler.after("escape", "math_inline", math_inline);
60
- md.block.ruler.after("blockquote", "math_block", math_block, {
61
- alt: ["paragraph", "reference", "blockquote", "list"],
62
- });
63
- md.renderer.rules.math_inline = renderer.inlineRenderer;
64
- md.renderer.rules.math_block = renderer.blockRenderer;
65
- };
66
- }
67
- //# sourceMappingURL=mdmath.js.map
@@ -1,9 +0,0 @@
1
- /**
2
- * mdparser.ts
3
- *
4
- * provides methods to parse MarkdownIt token stream and extract math equations.
5
- */
6
- import StateBlock from "markdown-it/lib/rules_block/state_block.mjs";
7
- import StateInline from "markdown-it/lib/rules_inline/state_inline.mjs";
8
- export declare function math_inline(state: StateInline, silent: boolean): boolean;
9
- export declare function math_block(state: StateBlock, start: number, end: number, silent: boolean): boolean;
@@ -1,148 +0,0 @@
1
- /**
2
- * mdparser.ts
3
- *
4
- * provides methods to parse MarkdownIt token stream and extract math equations.
5
- */
6
- /**
7
- * Tests if potential opening or closing delimieter
8
- * Assumes that there is a "$" at state.src[pos]
9
- */
10
- function isValidDelim(state, pos) {
11
- const max = state.posMax;
12
- let can_open = true;
13
- let can_close = true;
14
- const prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
15
- const nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1;
16
- // Check non-whitespace conditions for opening and closing, and
17
- // check that closing delimeter isn't followed by a number
18
- if (prevChar === 0x20 /* " " */ ||
19
- prevChar === 0x09 /* \t */ ||
20
- (nextChar >= 0x30 /* "0" */ && nextChar <= 0x39) /* "9" */) {
21
- can_close = false;
22
- }
23
- if (nextChar === 0x20 /* " " */ || nextChar === 0x09 /* \t */) {
24
- can_open = false;
25
- }
26
- return {
27
- can_open: can_open,
28
- can_close: can_close,
29
- };
30
- }
31
- export function math_inline(state, silent) {
32
- let match, token, res, pos;
33
- if (state.src[state.pos] !== "$") {
34
- return false;
35
- }
36
- res = isValidDelim(state, state.pos);
37
- if (!res.can_open) {
38
- if (!silent) {
39
- state.pending += "$";
40
- }
41
- state.pos += 1;
42
- return true;
43
- }
44
- // First check for and bypass all properly escaped delimieters
45
- // This loop will assume that the first leading backtick can not
46
- // be the first character in state.src, which is known since
47
- // we have found an opening delimieter already.
48
- const start = state.pos + 1;
49
- match = start;
50
- while ((match = state.src.indexOf("$", match)) !== -1) {
51
- // Found potential $, look for escapes, pos will point to
52
- // first non escape when complete
53
- pos = match - 1;
54
- while (state.src[pos] === "\\") {
55
- pos -= 1;
56
- }
57
- // Even number of escapes, potential closing delimiter found
58
- if ((match - pos) % 2 == 1) {
59
- break;
60
- }
61
- match += 1;
62
- }
63
- // No closing delimter found. Consume $ and continue.
64
- if (match === -1) {
65
- if (!silent) {
66
- state.pending += "$";
67
- }
68
- state.pos = start;
69
- return true;
70
- }
71
- // Check if we have empty content, ie: $$. Do not parse.
72
- if (match - start === 0) {
73
- if (!silent) {
74
- state.pending += "$$";
75
- }
76
- state.pos = start + 1;
77
- return true;
78
- }
79
- // Check for valid closing delimiter
80
- res = isValidDelim(state, match);
81
- if (!res.can_close) {
82
- if (!silent) {
83
- state.pending += "$";
84
- }
85
- state.pos = start;
86
- return true;
87
- }
88
- if (!silent) {
89
- token = state.push("math_inline", "math", 0);
90
- token.markup = "$";
91
- token.content = state.src.slice(start, match);
92
- }
93
- state.pos = match + 1;
94
- return true;
95
- }
96
- export function math_block(state, start, end, silent) {
97
- let firstLine;
98
- let lastLine;
99
- let next;
100
- let lastPos;
101
- let found = false;
102
- let pos = state.bMarks[start] + state.tShift[start];
103
- let max = state.eMarks[start];
104
- if (pos + 2 > max) {
105
- return false;
106
- }
107
- if (state.src.slice(pos, pos + 2) !== "$$") {
108
- return false;
109
- }
110
- pos += 2;
111
- firstLine = state.src.slice(pos, max);
112
- if (silent) {
113
- return true;
114
- }
115
- if (firstLine.trim().slice(-2) === "$$") {
116
- // Single line expression
117
- firstLine = firstLine.trim().slice(0, -2);
118
- found = true;
119
- }
120
- for (next = start; !found;) {
121
- next++;
122
- if (next >= end) {
123
- break;
124
- }
125
- pos = state.bMarks[next] + state.tShift[next];
126
- max = state.eMarks[next];
127
- if (pos < max && state.tShift[next] < state.blkIndent) {
128
- // non-empty line with negative indent should stop the list:
129
- break;
130
- }
131
- if (state.src.slice(pos, max).trim().slice(-2) === "$$") {
132
- lastPos = state.src.slice(0, max).lastIndexOf("$$");
133
- lastLine = state.src.slice(pos, lastPos);
134
- found = true;
135
- }
136
- }
137
- state.line = next + 1;
138
- const token = state.push("math_block", "math", 0);
139
- token.block = true;
140
- token.content =
141
- (firstLine && firstLine.trim() ? firstLine + "\n" : "") +
142
- state.getLines(start + 1, next, state.tShift[start], true) +
143
- (lastLine && lastLine.trim() ? lastLine : "");
144
- token.map = [start, state.line];
145
- token.markup = "$$";
146
- return true;
147
- }
148
- //# sourceMappingURL=mdparser.js.map