@cobapen/markdown 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Yamavol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # cp-markdown
2
+
3
+ A markdown converter for the cobapen website.
4
+
5
+ ## Features
6
+
7
+ - toc
8
+ - anchor & footnote
9
+ - deflist
10
+ - fenced code block with filename, line-number
11
+ - server side math rendering
12
+
@@ -0,0 +1,13 @@
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 {};
@@ -0,0 +1,51 @@
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
+ var token = tokens[idx];
10
+ var info_str = token.info ? unescapeAll(token.info).trim() : "";
11
+ var info = new InfoString(info_str);
12
+ var langName = info.lang;
13
+ var langAttrs = info.attrs.join(" ");
14
+ var 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
+ var i = token.attrIndex("class");
29
+ var 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
+ var tmpToken = {
39
+ attrs: tmpAttrs
40
+ };
41
+ if (info.title.length > 0) {
42
+ var style = (env.showCodeTitleByDefault === true)
43
+ ? ""
44
+ : " style=\"visibility:hidden;\"";
45
+ return "<pre><code".concat(slf.renderAttrs(tmpToken), ">").concat(highlighted, "</code><span class=\"title\"").concat(style, ">").concat(info.title, "</span></pre>\n");
46
+ }
47
+ return "<pre><code".concat(slf.renderAttrs(tmpToken), ">").concat(highlighted, "</code></pre>\n");
48
+ }
49
+ return "<pre><code".concat(slf.renderAttrs(token), ">").concat(highlighted, "</code></pre>\n");
50
+ }
51
+ //# sourceMappingURL=fence-custom.js.map
@@ -0,0 +1,18 @@
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;
@@ -0,0 +1,95 @@
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() {
9
+ var _args = [];
10
+ for (var _i = 0; _i < arguments.length; _i++) {
11
+ _args[_i] = arguments[_i];
12
+ }
13
+ // if (_debuglog.caller.name !== "") return;
14
+ // console.log(...args);
15
+ }
16
+ /** Get digits of the number. e.g. 1000 => 4 */
17
+ function numDigits(n) {
18
+ return Math.floor(n).toString().length;
19
+ }
20
+ /** Print a decimal nicely */
21
+ function niceDec(n) {
22
+ return n.toFixed(3);
23
+ }
24
+ /**
25
+ * Highlight function with line number support
26
+ *
27
+ * @param code
28
+ * @param lang
29
+ * @param linestart
30
+ * @returns
31
+ */
32
+ export function highlightWithLineNumber(code, lang, linestart) {
33
+ try {
34
+ /** convert if lang is specified + supported by highlight.js */
35
+ if (lang && hljs.getLanguage(lang)) {
36
+ /** do conversion */
37
+ var htmlLines = hljs.highlight(code, { language: lang }).value;
38
+ /**
39
+ * Attach the line number if specified.
40
+ *
41
+ * The given code is rendered in <pre><code>, so each line is terminated by '\n'.
42
+ * Split the input, wrap them by <div class="line">.
43
+ * At the beginning of each line, <span class="line-no"> is added.
44
+ *
45
+ * default styles are embedded so that the text is readable
46
+ * even if css was not applied.
47
+ *
48
+ * default-styles:
49
+ * - display: inline-block
50
+ * - user-select: none
51
+ * - width: ${numDigits}em
52
+ */
53
+ if (linestart !== undefined) {
54
+ var lines = htmlLines.split("\n");
55
+ var elWidth = numDigits(linestart + lines.length) * 0.8;
56
+ var elStyle_1 = "display:inline-block;" +
57
+ "user-select:none;" +
58
+ "width: ".concat(niceDec(elWidth), "em;");
59
+ lines.forEach(function (line, i, lines) {
60
+ lines[i] =
61
+ "<div class=\"line\">" +
62
+ "<span class=\"line-no\" style=\"".concat(elStyle_1, "\">").concat(linestart + i, "</span>").concat(line) +
63
+ "</div>";
64
+ });
65
+ _debuglog(lines);
66
+ htmlLines = lines.join("");
67
+ }
68
+ return htmlLines;
69
+ }
70
+ else {
71
+ // no language , no highlighting.
72
+ // If you want line numbers without highlighting, set language to
73
+ // "nohighlight" or "text"
74
+ return "";
75
+ }
76
+ }
77
+ catch (_) {
78
+ return "";
79
+ }
80
+ }
81
+ /**
82
+ * Exported function for markdown-it
83
+ *
84
+ * @param str
85
+ * @param lang
86
+ * @param attrs
87
+ * @returns
88
+ */
89
+ export function highlighterForMarkdownIt(str, lang, attrs) {
90
+ _debuglog(lang ? lang : "(lang is empty or undefined)");
91
+ var info = new InfoString(lang + " " + attrs);
92
+ _debuglog(info);
93
+ return highlightWithLineNumber(str, info.lang, info.linestart);
94
+ }
95
+ //# sourceMappingURL=highlight.js.map
@@ -0,0 +1,26 @@
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
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * InfoString parses info_string from fenced code block.
3
+ */
4
+ var InfoString = /** @class */ (function () {
5
+ function InfoString(info) {
6
+ if (info.length > 0) {
7
+ var arr = InfoString.parseInfoString(info);
8
+ this._lang = arr[0];
9
+ this._attrs = arr.slice(1);
10
+ this._title = InfoString.getTitle(this._attrs);
11
+ this._linestart = InfoString.getLineStart(this._attrs);
12
+ }
13
+ else {
14
+ this._lang = "";
15
+ this._attrs = [];
16
+ this._title = "";
17
+ this._linestart = undefined;
18
+ }
19
+ }
20
+ Object.defineProperty(InfoString.prototype, "lang", {
21
+ get: function () {
22
+ return this._lang;
23
+ },
24
+ enumerable: false,
25
+ configurable: true
26
+ });
27
+ Object.defineProperty(InfoString.prototype, "attrs", {
28
+ get: function () {
29
+ return this._attrs;
30
+ },
31
+ enumerable: false,
32
+ configurable: true
33
+ });
34
+ Object.defineProperty(InfoString.prototype, "title", {
35
+ get: function () {
36
+ return this._title;
37
+ },
38
+ enumerable: false,
39
+ configurable: true
40
+ });
41
+ Object.defineProperty(InfoString.prototype, "linestart", {
42
+ get: function () {
43
+ return this._linestart;
44
+ },
45
+ enumerable: false,
46
+ configurable: true
47
+ });
48
+ Object.defineProperty(InfoString.prototype, "hasLang", {
49
+ get: function () {
50
+ return this._lang.length > 0;
51
+ },
52
+ enumerable: false,
53
+ configurable: true
54
+ });
55
+ /** Parse info_string into an array of strings. All quotes are removed*/
56
+ InfoString.parseInfoString = function (info) {
57
+ // There are 4 possible tokens, but it can be reduced to 2 patterns.
58
+ // arg, "arg quoted", key=value, key="value quoted"
59
+ //
60
+ // This function returns a quote-removed string.
61
+ //
62
+ // const regex = /([^\s]+=)?(?:"([^"]*)"|'([^']*)'|([^\s]+))/g;
63
+ // ~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
64
+ // key= "value" 'value' value
65
+ //
66
+ // The regex above does not support escape letters. The next regex
67
+ // is escape char aware.
68
+ //
69
+ // NOTE: This class is designed to handle escape letters. However, tools
70
+ // might decide to unescape info_string before used (e.g. markdown-it).
71
+ // In such case, you might need to use double-escaped quotes.
72
+ //
73
+ var dq = "[^\"\\\\]*(?:\\\\.|[^\"\\\\]*)*"; // [^"]*
74
+ var sq = "[^\'\\\\]*(?:\\\\.|[^\'\\\\]*)*"; // [^']*
75
+ // ~~~~~~~~~~ ~~~~~ ~~~~~~~~~~
76
+ // char seq ( escape char seq )*
77
+ //
78
+ var ptn = "([^\\s]+=)?(?:\"(".concat(dq, ")\"|'(").concat(sq, ")'|([^\\s]+))");
79
+ // ~~~~~~~~~~ ~~~~~~~~~ ~~~~~~~~~ ~~~~~~~~~
80
+ // key= "value" 'value' value
81
+ var regex = new RegExp(ptn, "g");
82
+ var result = [];
83
+ var match;
84
+ while ((match = regex.exec(info)) !== null) {
85
+ var text = (match[1] || "") + (match[2] || match[3] || match[4] || "");
86
+ text = unescape(text).trim();
87
+ result.push(text);
88
+ }
89
+ return result;
90
+ };
91
+ /** Parse metadata notation "{filename:line, ...}"" */
92
+ InfoString.parseMetaNotation = function (text) {
93
+ var match = text.match(/^\{\s*([^:]+):(\d+)\s*\}$/);
94
+ if (match) {
95
+ return {
96
+ filename: match[1],
97
+ line: parseInt(match[2], 10),
98
+ };
99
+ }
100
+ return undefined;
101
+ };
102
+ /** From attributes list, return title metadata */
103
+ InfoString.getTitle = function (attr) {
104
+ var _a;
105
+ var titleAttr = (_a = attr.find(function (x) { return x.startsWith("title="); })) !== null && _a !== void 0 ? _a : "";
106
+ if (titleAttr.length > 0) {
107
+ var match = titleAttr.match(/^title=(.*)$/);
108
+ if (match) {
109
+ var value = match[1].trim();
110
+ if (value.startsWith("\"") && value.endsWith("\"")) {
111
+ value = value.slice(1, -1);
112
+ value = unescape(value).trim();
113
+ }
114
+ return value;
115
+ }
116
+ else {
117
+ throw new Error("Must not fail. Check impl.");
118
+ }
119
+ }
120
+ if (attr.length > 0) {
121
+ var meta = InfoString.parseMetaNotation(attr[0]);
122
+ if (meta && meta.filename.length > 0) {
123
+ return meta.filename;
124
+ }
125
+ }
126
+ return "";
127
+ };
128
+ /** From attributes list, return line number if defined */
129
+ InfoString.getLineStart = function (attr) {
130
+ var _a;
131
+ var lineAttr = (_a = attr.find(function (x) { return x.startsWith("linestart="); })) !== null && _a !== void 0 ? _a : "";
132
+ if (lineAttr.length > 0) {
133
+ return parseInt(lineAttr.split("=")[1].trim());
134
+ }
135
+ if (attr.length > 0) {
136
+ var meta = InfoString.parseMetaNotation(attr[0]);
137
+ if (meta && meta.line > 0) {
138
+ return meta.line;
139
+ }
140
+ }
141
+ return undefined;
142
+ };
143
+ return InfoString;
144
+ }());
145
+ export { InfoString };
146
+ function unescape(text) {
147
+ return text.replaceAll(/\\\"/g, "\"").replaceAll(/\\\'/g, "'");
148
+ }
149
+ //# sourceMappingURL=info-string.js.map
@@ -0,0 +1,16 @@
1
+ export interface Configuration {
2
+ /**
3
+ * Set "true" to display the title (if specified) of the fenced code block.
4
+ * The title is hidden by default, and user must explicitly override the style.
5
+ */
6
+ showCodeTitleByDefault: boolean;
7
+ }
8
+ export type Options = Partial<Configuration>;
9
+ export declare class MarkdownConverter {
10
+ private readonly _config;
11
+ private readonly _mj;
12
+ private readonly _md;
13
+ constructor(option?: Options);
14
+ render(text: string): string;
15
+ mathcss(): string;
16
+ }
package/dist/index.js ADDED
@@ -0,0 +1,67 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import markdownIt from "markdown-it";
13
+ import { highlighterForMarkdownIt } from "./code/highlight.js";
14
+ import anchor from "markdown-it-anchor";
15
+ // @ts-ignore
16
+ import cjkbreaks from "markdown-it-cjk-breaks";
17
+ // @ts-ignore
18
+ import deflist from "markdown-it-deflist";
19
+ // @ts-ignore
20
+ import toc from "markdown-it-table-of-contents";
21
+ // @ts-ignore
22
+ import footnote from "markdown-it-footnote";
23
+ import { MathjaxEngine } from "./math/mathjax.js";
24
+ import { mdmath } from "./math/mdmath.js";
25
+ import { fence_custom } from "./code/fence-custom.js";
26
+ var defaultOptions = {
27
+ showCodeTitleByDefault: false,
28
+ };
29
+ var MarkdownConverter = /** @class */ (function () {
30
+ function MarkdownConverter(option) {
31
+ var config = __assign(__assign({}, defaultOptions), option);
32
+ var mj = new MathjaxEngine({
33
+ tex: {
34
+ macros: {
35
+ bm: ["\\boldsymbol{#1}", 1],
36
+ },
37
+ },
38
+ });
39
+ var md = markdownIt({
40
+ html: true,
41
+ linkify: true,
42
+ highlight: highlighterForMarkdownIt,
43
+ });
44
+ md.renderer.rules.fence = fence_custom;
45
+ md.use(anchor)
46
+ .use(cjkbreaks)
47
+ .use(footnote)
48
+ .use(deflist)
49
+ .use(mdmath(mj))
50
+ .use(toc, {
51
+ includeLevel: [2, 3],
52
+ });
53
+ this._config = config;
54
+ this._mj = mj;
55
+ this._md = md;
56
+ }
57
+ MarkdownConverter.prototype.render = function (text) {
58
+ var env = __assign({}, this._config); // env object must s
59
+ return this._md.render(text, env);
60
+ };
61
+ MarkdownConverter.prototype.mathcss = function () {
62
+ return this._mj.stylesheet();
63
+ };
64
+ return MarkdownConverter;
65
+ }());
66
+ export { MarkdownConverter };
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,97 @@
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
+ 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 {};
@@ -0,0 +1,108 @@
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
+ var MATHJAX_DEFAULT_FONT_URL = "https://cdn.jsdelivr.net/npm/mathjax-full@3/es5/output/chtml/fonts/woff-v2";
19
+ var 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
+ var MathjaxEngine = /** @class */ (function () {
43
+ function MathjaxEngine(option) {
44
+ var _a;
45
+ this.option = merge({}, defaultOption, option);
46
+ if (typeof ((_a = this.option.tex) === null || _a === void 0 ? void 0 : _a.packages) === "string") {
47
+ this.option.tex.packages = this.option.tex.packages.split(/\s*,\s*/);
48
+ }
49
+ this.adaptor = liteAdaptor();
50
+ RegisterHTMLHandler(this.adaptor);
51
+ var tex = new TeX(this.option.tex);
52
+ var chtml = new CHTML(this.option.chtml);
53
+ var html = mathjax.document("", {
54
+ InputJax: tex,
55
+ OutputJax: chtml,
56
+ });
57
+ html.addRenderAction("typeset", 155, renderDoc, renderMath);
58
+ this.tex = tex;
59
+ this.chtml = chtml;
60
+ this.html = html;
61
+ function renderDoc(_doc) { }
62
+ function renderMath(math, doc) {
63
+ var adaptor = doc.adaptor;
64
+ var text = adaptor.node("mjx-copytext", { "aria-hidden": true }, [
65
+ adaptor.text(math.math),
66
+ ]);
67
+ adaptor.setStyle(text, "position", "absolute");
68
+ adaptor.setStyle(text, "visibility", "hidden");
69
+ adaptor.setStyle(math.typesetRoot, "position", "relative");
70
+ adaptor.append(math.typesetRoot, text);
71
+ }
72
+ }
73
+ /**
74
+ * convert TeX input to CHTML.
75
+ *
76
+ * @param tex input string
77
+ * @param override parameter to override the defaults, if you wish to
78
+ * @returns
79
+ */
80
+ MathjaxEngine.prototype.convert = function (tex, override) {
81
+ var _a, _b, _c, _d;
82
+ var node = this.html.convert(tex, {
83
+ display: !((_a = override === null || override === void 0 ? void 0 : override.inline) !== null && _a !== void 0 ? _a : this.option.inline),
84
+ em: (_b = override === null || override === void 0 ? void 0 : override.em) !== null && _b !== void 0 ? _b : this.option.em,
85
+ ex: (_c = override === null || override === void 0 ? void 0 : override.ex) !== null && _c !== void 0 ? _c : this.option.ex,
86
+ containerWidth: (_d = override === null || override === void 0 ? void 0 : override.width) !== null && _d !== void 0 ? _d : this.option.width,
87
+ scale: 1.0,
88
+ });
89
+ if (node instanceof LiteElement) {
90
+ return this.adaptor.outerHTML(node);
91
+ }
92
+ else {
93
+ return "ERROR";
94
+ }
95
+ };
96
+ /**
97
+ * returns adaptive css (stylesheet for the processed equations only),
98
+ * or the full mathjax css (if configured)
99
+ *
100
+ * @returns css content
101
+ */
102
+ MathjaxEngine.prototype.stylesheet = function () {
103
+ return this.adaptor.textContent(this.chtml.styleSheet(this.html));
104
+ };
105
+ return MathjaxEngine;
106
+ }());
107
+ export { MathjaxEngine };
108
+ //# sourceMappingURL=mathjax.js.map
@@ -0,0 +1,14 @@
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;
@@ -0,0 +1,67 @@
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: function (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
+ var 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: inlineRenderer,
47
+ blockRenderer: 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
+ var renderer = getRenderers(math);
58
+ return function (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
@@ -0,0 +1,9 @@
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;
@@ -0,0 +1,148 @@
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
+ var max = state.posMax;
12
+ var can_open = true;
13
+ var can_close = true;
14
+ var prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
15
+ var 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
+ var 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
+ var 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
+ var firstLine;
98
+ var lastLine;
99
+ var next;
100
+ var lastPos;
101
+ var found = false;
102
+ var pos = state.bMarks[start] + state.tShift[start];
103
+ var 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
+ var 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
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@cobapen/markdown",
3
+ "version": "0.1.0",
4
+ "description": "A markdown converter for cobapen website",
5
+ "license": "MIT",
6
+ "author": "yamavol",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "directories": {
11
+ "doc": "docs",
12
+ "test": "tests"
13
+ },
14
+ "keywords": [
15
+ "markdown"
16
+ ],
17
+ "homepage": "https://github.com/cobapen/markdown#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/cobapen/markdown/issues"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/cobapen/markdown.git"
24
+ },
25
+ "files": [
26
+ "dist/**/*.js",
27
+ "dist/**/*.d.ts"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "build:doc": "tsc && node docs/build.js",
32
+ "test": "vitest",
33
+ "coverage": "vitest run --coverage"
34
+ },
35
+ "dependencies": {
36
+ "highlight.js": "^11.11.1",
37
+ "katex": "^0.16.22",
38
+ "lodash-es": "^4.17.21",
39
+ "markdown-it": "^14.1.0",
40
+ "markdown-it-anchor": "^9.2.0",
41
+ "markdown-it-cjk-breaks": "^2.0.0",
42
+ "markdown-it-deflist": "^3.0.0",
43
+ "markdown-it-footnote": "^4.0.0",
44
+ "markdown-it-table-of-contents": "^0.9.0",
45
+ "mathjax-full": "^3.2.2"
46
+ },
47
+ "devDependencies": {
48
+ "@stylistic/eslint-plugin": "^4.2.0",
49
+ "@types/katex": "^0.16.7",
50
+ "@types/lodash-es": "^4.17.12",
51
+ "@types/markdown-it": "^14.1.2",
52
+ "@types/mustache": "^4.2.5",
53
+ "@types/node": "^22.14.1",
54
+ "@vitest/coverage-v8": "^3.1.1",
55
+ "eslint": "^9.24.0",
56
+ "mustache": "^4.2.0",
57
+ "typescript": "^5.8.3",
58
+ "typescript-eslint": "^8.29.1",
59
+ "vitest": "^3.1.1"
60
+ }
61
+ }