@markupme/core 0.1.0 → 0.1.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.
@@ -0,0 +1,37 @@
1
+ # Third-party notices
2
+
3
+ `@markupme/core` contains code ported to TypeScript from these MIT-licensed projects:
4
+
5
+ | File | Ported from | Copyright |
6
+ | ---- | ----------- | --------- |
7
+ | `src/render/katex.ts` | [rehype-katex](https://github.com/remarkjs/remark-math/tree/main/packages/rehype-katex) 7.0.1 | Copyright (c) Junyoung Choi <fluke8259@gmail.com> |
8
+ | `src/plugins/math.ts` | [remark-math](https://github.com/remarkjs/remark-math/tree/main/packages/remark-math) 6.0.0 | Copyright (c) Junyoung Choi <fluke8259@gmail.com> |
9
+ | `src/plugins/math-syntax.ts` | [micromark-extension-math](https://github.com/micromark/micromark-extension-math) 3.1.0 | Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com> |
10
+
11
+ All three are distributed under the MIT License:
12
+
13
+ ```text
14
+ (The MIT License)
15
+
16
+ Copyright (c) Junyoung Choi <fluke8259@gmail.com>
17
+ Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
18
+
19
+ Permission is hereby granted, free of charge, to any person obtaining
20
+ a copy of this software and associated documentation files (the
21
+ 'Software'), to deal in the Software without restriction, including
22
+ without limitation the rights to use, copy, modify, merge, publish,
23
+ distribute, sublicense, and/or sell copies of the Software, and to
24
+ permit persons to whom the Software is furnished to do so, subject to
25
+ the following conditions:
26
+
27
+ The above copyright notice and this permission notice shall be
28
+ included in all copies or substantial portions of the Software.
29
+
30
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
31
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
32
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
33
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
34
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
35
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
36
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37
+ ```
package/dist/parse.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import remarkDirective from 'remark-directive';
2
2
  import remarkFrontmatter from 'remark-frontmatter';
3
3
  import remarkGfm from 'remark-gfm';
4
- import remarkMath from 'remark-math';
5
4
  import remarkParse from 'remark-parse';
6
5
  import { unified } from 'unified';
7
6
  import { annotateAlerts } from './plugins/alerts.js';
8
7
  import { normalizeDirectives } from './plugins/directives.js';
9
8
  import { readFrontmatter } from './plugins/frontmatter.js';
10
9
  import { addHeadingIds } from './plugins/headings.js';
10
+ import { remarkMath } from './plugins/math.js';
11
11
  import { insertToc } from './plugins/toc.js';
12
12
  import { SCHEMA_VERSION, } from './types.js';
13
13
  /** Syntax extensions shared by the parser and the serializer, so round trips keep every construct. */
@@ -0,0 +1,20 @@
1
+ import type { Extension } from 'micromark-util-types';
2
+ declare module 'micromark-util-types' {
3
+ interface TokenTypeMap {
4
+ mathFlow: 'mathFlow';
5
+ mathFlowFence: 'mathFlowFence';
6
+ mathFlowFenceMeta: 'mathFlowFenceMeta';
7
+ mathFlowFenceSequence: 'mathFlowFenceSequence';
8
+ mathFlowValue: 'mathFlowValue';
9
+ mathText: 'mathText';
10
+ mathTextData: 'mathTextData';
11
+ mathTextPadding: 'mathTextPadding';
12
+ mathTextSequence: 'mathTextSequence';
13
+ }
14
+ }
15
+ export interface MathSyntaxOptions {
16
+ /** Whether `$x$` (one dollar) is text math. Default `true`. */
17
+ singleDollarTextMath?: boolean | null | undefined;
18
+ }
19
+ /** micromark extension for math syntax. */
20
+ export declare function mathSyntax(options?: MathSyntaxOptions | null): Extension;
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Math syntax for micromark (`$$` flow math and `$` text math), ported to TypeScript from
3
+ * micromark-extension-math 3.1.0 (`lib/syntax.js`, `lib/math-flow.js`, `lib/math-text.js`).
4
+ * Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>, MIT; full notice in THIRD-PARTY-NOTICES.md.
5
+ * https://github.com/micromark/micromark-extension-math
6
+ *
7
+ * Why it lives here: micromark-extension-math (and remark-math, which depends on it) declares
8
+ * `katex ^0.16.0` for its HTML compiler, which MarkupMe never uses. npm ignores our root
9
+ * `overrides` for installed packages, so depending on it gave every consumer a vulnerable
10
+ * KaTeX (GHSA-238p-pmpm-9mq7, MKP-7). Only the tokenizers are ported; behaviour is unchanged.
11
+ */
12
+ import { factorySpace } from 'micromark-factory-space';
13
+ import { markdownLineEnding } from 'micromark-util-character';
14
+ const DOLLAR = 36;
15
+ const SPACE = 32;
16
+ /** micromark extension for math syntax. */
17
+ export function mathSyntax(options) {
18
+ return {
19
+ flow: { [DOLLAR]: mathFlow },
20
+ text: { [DOLLAR]: mathText(options) },
21
+ };
22
+ }
23
+ // ---------------------------------------------------------------------------------------------
24
+ // Flow math: $$ … $$
25
+ const mathFlow = {
26
+ tokenize: tokenizeMathFenced,
27
+ concrete: true,
28
+ name: 'mathFlow',
29
+ };
30
+ const nonLazyContinuation = {
31
+ tokenize: tokenizeNonLazyContinuation,
32
+ partial: true,
33
+ };
34
+ function tokenizeMathFenced(effects, ok, nok) {
35
+ const self = this;
36
+ const tail = self.events[self.events.length - 1];
37
+ const initialSize = tail && tail[1].type === 'linePrefix' ? tail[2].sliceSerialize(tail[1], true).length : 0;
38
+ let sizeOpen = 0;
39
+ return start;
40
+ function start(code) {
41
+ effects.enter('mathFlow');
42
+ effects.enter('mathFlowFence');
43
+ effects.enter('mathFlowFenceSequence');
44
+ return sequenceOpen(code);
45
+ }
46
+ function sequenceOpen(code) {
47
+ if (code === DOLLAR) {
48
+ effects.consume(code);
49
+ sizeOpen++;
50
+ return sequenceOpen;
51
+ }
52
+ if (sizeOpen < 2)
53
+ return nok(code);
54
+ effects.exit('mathFlowFenceSequence');
55
+ return factorySpace(effects, metaBefore, 'whitespace')(code);
56
+ }
57
+ function metaBefore(code) {
58
+ if (code === null || markdownLineEnding(code))
59
+ return metaAfter(code);
60
+ effects.enter('mathFlowFenceMeta');
61
+ effects.enter('chunkString', { contentType: 'string' });
62
+ return meta(code);
63
+ }
64
+ function meta(code) {
65
+ if (code === null || markdownLineEnding(code)) {
66
+ effects.exit('chunkString');
67
+ effects.exit('mathFlowFenceMeta');
68
+ return metaAfter(code);
69
+ }
70
+ if (code === DOLLAR)
71
+ return nok(code);
72
+ effects.consume(code);
73
+ return meta;
74
+ }
75
+ function metaAfter(code) {
76
+ effects.exit('mathFlowFence');
77
+ if (self.interrupt)
78
+ return ok(code);
79
+ return effects.attempt(nonLazyContinuation, beforeNonLazyContinuation, after)(code);
80
+ }
81
+ function beforeNonLazyContinuation(code) {
82
+ return effects.attempt({ tokenize: tokenizeClosingFence, partial: true }, after, contentStart)(code);
83
+ }
84
+ function contentStart(code) {
85
+ return (initialSize
86
+ ? factorySpace(effects, beforeContentChunk, 'linePrefix', initialSize + 1)
87
+ : beforeContentChunk)(code);
88
+ }
89
+ function beforeContentChunk(code) {
90
+ if (code === null)
91
+ return after(code);
92
+ if (markdownLineEnding(code)) {
93
+ return effects.attempt(nonLazyContinuation, beforeNonLazyContinuation, after)(code);
94
+ }
95
+ effects.enter('mathFlowValue');
96
+ return contentChunk(code);
97
+ }
98
+ function contentChunk(code) {
99
+ if (code === null || markdownLineEnding(code)) {
100
+ effects.exit('mathFlowValue');
101
+ return beforeContentChunk(code);
102
+ }
103
+ effects.consume(code);
104
+ return contentChunk;
105
+ }
106
+ function after(code) {
107
+ effects.exit('mathFlow');
108
+ return ok(code);
109
+ }
110
+ function tokenizeClosingFence(effects, ok, nok) {
111
+ let size = 0;
112
+ return factorySpace(effects, beforeSequenceClose, 'linePrefix', self.parser.constructs.disable.null?.includes('codeIndented') ? undefined : 4);
113
+ function beforeSequenceClose(code) {
114
+ effects.enter('mathFlowFence');
115
+ effects.enter('mathFlowFenceSequence');
116
+ return sequenceClose(code);
117
+ }
118
+ function sequenceClose(code) {
119
+ if (code === DOLLAR) {
120
+ size++;
121
+ effects.consume(code);
122
+ return sequenceClose;
123
+ }
124
+ if (size < sizeOpen)
125
+ return nok(code);
126
+ effects.exit('mathFlowFenceSequence');
127
+ return factorySpace(effects, afterSequenceClose, 'whitespace')(code);
128
+ }
129
+ function afterSequenceClose(code) {
130
+ if (code === null || markdownLineEnding(code)) {
131
+ effects.exit('mathFlowFence');
132
+ return ok(code);
133
+ }
134
+ return nok(code);
135
+ }
136
+ }
137
+ }
138
+ function tokenizeNonLazyContinuation(effects, ok, nok) {
139
+ const self = this;
140
+ return start;
141
+ function start(code) {
142
+ if (code === null)
143
+ return ok(code);
144
+ effects.enter('lineEnding');
145
+ effects.consume(code);
146
+ effects.exit('lineEnding');
147
+ return lineStart;
148
+ }
149
+ function lineStart(code) {
150
+ return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
151
+ }
152
+ }
153
+ // ---------------------------------------------------------------------------------------------
154
+ // Text math: $ … $ and $$ … $$ inline
155
+ function mathText(options) {
156
+ let single = options?.singleDollarTextMath;
157
+ if (single === null || single === undefined)
158
+ single = true;
159
+ return {
160
+ tokenize: tokenizeMathText,
161
+ resolve: resolveMathText,
162
+ previous,
163
+ name: 'mathText',
164
+ };
165
+ function tokenizeMathText(effects, ok, nok) {
166
+ let sizeOpen = 0;
167
+ let size = 0;
168
+ let token;
169
+ return start;
170
+ function start(code) {
171
+ effects.enter('mathText');
172
+ effects.enter('mathTextSequence');
173
+ return sequenceOpen(code);
174
+ }
175
+ function sequenceOpen(code) {
176
+ if (code === DOLLAR) {
177
+ effects.consume(code);
178
+ sizeOpen++;
179
+ return sequenceOpen;
180
+ }
181
+ if (sizeOpen < 2 && !single)
182
+ return nok(code);
183
+ effects.exit('mathTextSequence');
184
+ return between(code);
185
+ }
186
+ function between(code) {
187
+ if (code === null)
188
+ return nok(code);
189
+ if (code === DOLLAR) {
190
+ token = effects.enter('mathTextSequence');
191
+ size = 0;
192
+ return sequenceClose(code);
193
+ }
194
+ if (code === SPACE) {
195
+ effects.enter('space');
196
+ effects.consume(code);
197
+ effects.exit('space');
198
+ return between;
199
+ }
200
+ if (markdownLineEnding(code)) {
201
+ effects.enter('lineEnding');
202
+ effects.consume(code);
203
+ effects.exit('lineEnding');
204
+ return between;
205
+ }
206
+ effects.enter('mathTextData');
207
+ return data(code);
208
+ }
209
+ function data(code) {
210
+ if (code === null || code === SPACE || code === DOLLAR || markdownLineEnding(code)) {
211
+ effects.exit('mathTextData');
212
+ return between(code);
213
+ }
214
+ effects.consume(code);
215
+ return data;
216
+ }
217
+ function sequenceClose(code) {
218
+ if (code === DOLLAR) {
219
+ effects.consume(code);
220
+ size++;
221
+ return sequenceClose;
222
+ }
223
+ if (size === sizeOpen) {
224
+ effects.exit('mathTextSequence');
225
+ effects.exit('mathText');
226
+ return ok(code);
227
+ }
228
+ // More or fewer dollars than the opening sequence: it is data.
229
+ if (token)
230
+ token.type = 'mathTextData';
231
+ return data(code);
232
+ }
233
+ }
234
+ }
235
+ const resolveMathText = (events) => {
236
+ let tailExitIndex = events.length - 4;
237
+ let headEnterIndex = 3;
238
+ let index;
239
+ let enter;
240
+ const at = (i) => events[i][1];
241
+ // A space or line ending on both sides of data is padding.
242
+ if ((at(headEnterIndex).type === 'lineEnding' || at(headEnterIndex).type === 'space') &&
243
+ (at(tailExitIndex).type === 'lineEnding' || at(tailExitIndex).type === 'space')) {
244
+ index = headEnterIndex;
245
+ while (++index < tailExitIndex) {
246
+ if (at(index).type === 'mathTextData') {
247
+ at(tailExitIndex).type = 'mathTextPadding';
248
+ at(headEnterIndex).type = 'mathTextPadding';
249
+ headEnterIndex += 2;
250
+ tailExitIndex -= 2;
251
+ break;
252
+ }
253
+ }
254
+ }
255
+ // Merge adjacent spaces and data.
256
+ index = headEnterIndex - 1;
257
+ tailExitIndex++;
258
+ while (++index <= tailExitIndex) {
259
+ if (enter === undefined) {
260
+ if (index !== tailExitIndex && at(index).type !== 'lineEnding')
261
+ enter = index;
262
+ }
263
+ else if (index === tailExitIndex || at(index).type === 'lineEnding') {
264
+ at(enter).type = 'mathTextData';
265
+ if (index !== enter + 2) {
266
+ at(enter).end = at(index - 1).end;
267
+ events.splice(enter + 2, index - enter - 2);
268
+ tailExitIndex -= index - enter - 2;
269
+ index = enter + 2;
270
+ }
271
+ enter = undefined;
272
+ }
273
+ }
274
+ return events;
275
+ };
276
+ const previous = function (code) {
277
+ // If there is a previous code, there will always be a tail.
278
+ return (code !== DOLLAR || this.events[this.events.length - 1][1].type === 'characterEscape');
279
+ };
@@ -0,0 +1,2 @@
1
+ import type { Processor } from 'unified';
2
+ export declare function remarkMath(this: Processor): undefined;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * remark plugin for math syntax: the in-tree equivalent of remark-math 6.0.0 (MIT,
3
+ * https://github.com/remarkjs/remark-math), using `mdast-util-math` and the ported tokenizers in
4
+ * `math-syntax.ts`. See that file for why remark-math is no longer a dependency (MKP-7).
5
+ * Copyright (c) Junyoung Choi <fluke8259@gmail.com>, MIT; full notice in THIRD-PARTY-NOTICES.md.
6
+ */
7
+ import { mathFromMarkdown, mathToMarkdown } from 'mdast-util-math';
8
+ import { mathSyntax } from './math-syntax.js';
9
+ export function remarkMath() {
10
+ const data = this.data();
11
+ data.micromarkExtensions ??= [];
12
+ data.fromMarkdownExtensions ??= [];
13
+ data.toMarkdownExtensions ??= [];
14
+ data.micromarkExtensions.push(mathSyntax());
15
+ data.fromMarkdownExtensions.push(mathFromMarkdown());
16
+ data.toMarkdownExtensions.push(mathToMarkdown());
17
+ }
@@ -1,4 +1,3 @@
1
- import rehypeKatex from 'rehype-katex';
2
1
  import rehypeRaw from 'rehype-raw';
3
2
  import rehypeSanitize from 'rehype-sanitize';
4
3
  import rehypeStringify from 'rehype-stringify';
@@ -8,6 +7,7 @@ import { visit } from 'unist-util-visit';
8
7
  import { presentAlerts } from '../plugins/alerts.js';
9
8
  import { presentDirectives } from '../plugins/directives.js';
10
9
  import { presentEmoji } from '../plugins/emoji.js';
10
+ import { rehypeKatex } from './katex.js';
11
11
  import { markupmeSchema } from './schema.js';
12
12
  const CLOBBER = 'user-content-';
13
13
  function textOf(node) {
@@ -196,9 +196,7 @@ export async function renderHtml(doc, opts, r, diagnostics) {
196
196
  }
197
197
  });
198
198
  if (opts.math === true)
199
- out = (await unified()
200
- .use(rehypeKatex, { throwOnError: false })
201
- .run(out));
199
+ out = (await unified().use(rehypeKatex).run(out));
202
200
  // Fill slots with trusted HTML.
203
201
  visit(out, 'element', (el, i, parent) => {
204
202
  const id = el.properties.id;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * rehype plugin that renders math with KaTeX: the in-tree equivalent of rehype-katex 7.0.1 (MIT,
3
+ * https://github.com/remarkjs/remark-math), calling the `katex` version this package pins.
4
+ * Copyright (c) Junyoung Choi <fluke8259@gmail.com>, MIT; full notice in THIRD-PARTY-NOTICES.md.
5
+ * rehype-katex declares `katex ^0.16.0`, and npm ignores our root `overrides` for installed
6
+ * packages, so depending on it gave every consumer a vulnerable KaTeX (GHSA-238p-pmpm-9mq7,
7
+ * MKP-7). Behaviour is unchanged: elements with a `language-math`, `math-display` or
8
+ * `math-inline` class are replaced by KaTeX output; errors are reported on the file and
9
+ * rendered in KaTeX's error style.
10
+ */
11
+ import type { Root } from 'hast';
12
+ import type { VFile } from 'vfile';
13
+ export declare function rehypeKatex(): (tree: Root, file: VFile) => undefined;
@@ -0,0 +1,79 @@
1
+ import { fromHtmlIsomorphic } from 'hast-util-from-html-isomorphic';
2
+ import { toText } from 'hast-util-to-text';
3
+ import katex from 'katex';
4
+ import { SKIP, visitParents } from 'unist-util-visit-parents';
5
+ export function rehypeKatex() {
6
+ return (tree, file) => {
7
+ visitParents(tree, 'element', (element, parents) => {
8
+ const classes = Array.isArray(element.properties.className)
9
+ ? element.properties.className
10
+ : [];
11
+ // ```math fenced code
12
+ const languageMath = classes.includes('language-math');
13
+ // $$ flow math
14
+ const mathDisplay = classes.includes('math-display');
15
+ // $ text math
16
+ const mathInline = classes.includes('math-inline');
17
+ let displayMode = mathDisplay;
18
+ if (!languageMath && !mathDisplay && !mathInline)
19
+ return;
20
+ let parent = parents[parents.length - 1];
21
+ let scope = element;
22
+ // ```math: replace the <pre> and use display mode.
23
+ if (element.tagName === 'code' &&
24
+ languageMath &&
25
+ parent &&
26
+ parent.type === 'element' &&
27
+ parent.tagName === 'pre') {
28
+ scope = parent;
29
+ parent = parents[parents.length - 2];
30
+ displayMode = true;
31
+ }
32
+ if (!parent)
33
+ return;
34
+ const value = toText(scope, { whitespace: 'pre' });
35
+ let result;
36
+ try {
37
+ result = katex.renderToString(value, { displayMode, throwOnError: true });
38
+ }
39
+ catch (error) {
40
+ const cause = error;
41
+ file.message('Could not render math with KaTeX', {
42
+ ancestors: [...parents, element],
43
+ cause,
44
+ place: element.position,
45
+ ruleId: cause.name.toLowerCase(),
46
+ source: 'rehype-katex',
47
+ });
48
+ try {
49
+ result = katex.renderToString(value, {
50
+ displayMode,
51
+ strict: 'ignore',
52
+ throwOnError: false,
53
+ });
54
+ }
55
+ catch {
56
+ // KaTeX's error markup, for errors it does not handle itself.
57
+ result = [
58
+ {
59
+ type: 'element',
60
+ tagName: 'span',
61
+ properties: {
62
+ className: ['katex-error'],
63
+ style: 'color:#cc0000',
64
+ title: String(error),
65
+ },
66
+ children: [{ type: 'text', value }],
67
+ },
68
+ ];
69
+ }
70
+ }
71
+ if (typeof result === 'string') {
72
+ result = fromHtmlIsomorphic(result, { fragment: true }).children;
73
+ }
74
+ const index = parent.children.indexOf(scope);
75
+ parent.children.splice(index, 1, ...result);
76
+ return SKIP;
77
+ });
78
+ };
79
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.1.0";
1
+ export declare const VERSION = "0.1.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.1.0';
1
+ export const VERSION = '0.1.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markupme/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "MarkupMe core: standards-first Markdown (CommonMark + GFM) parsing, a typed document model, safe HTML rendering, Markdown serialization, linting and TOC on unified/remark.",
5
5
  "license": "MIT",
6
6
  "author": "Yarlis AI Solutions",
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "files": [
36
36
  "dist",
37
+ "THIRD-PARTY-NOTICES.md",
37
38
  "!dist/**/*.tsbuildinfo"
38
39
  ],
39
40
  "publishConfig": {
@@ -51,23 +52,30 @@
51
52
  "@types/unist": "3.0.3",
52
53
  "gemoji": "8.1.0",
53
54
  "github-slugger": "2.0.0",
55
+ "hast-util-from-html-isomorphic": "2.0.0",
54
56
  "hast-util-sanitize": "5.0.2",
57
+ "hast-util-to-text": "4.0.2",
58
+ "katex": "0.19.1",
55
59
  "mdast-util-directive": "3.2.0",
60
+ "mdast-util-math": "3.0.0",
56
61
  "mdast-util-to-string": "4.0.0",
57
- "rehype-katex": "7.0.1",
62
+ "micromark-factory-space": "2.1.0",
63
+ "micromark-util-character": "2.1.1",
64
+ "micromark-util-types": "2.0.3",
58
65
  "rehype-raw": "7.0.0",
59
66
  "rehype-sanitize": "6.0.0",
60
67
  "rehype-stringify": "10.0.1",
61
68
  "remark-directive": "4.0.0",
62
69
  "remark-frontmatter": "5.0.0",
63
70
  "remark-gfm": "4.0.1",
64
- "remark-math": "6.0.0",
65
71
  "remark-parse": "11.0.0",
66
72
  "remark-rehype": "11.1.2",
67
73
  "remark-stringify": "11.0.0",
68
74
  "smol-toml": "1.9.1",
69
75
  "unified": "11.0.5",
70
76
  "unist-util-visit": "5.2.0",
77
+ "unist-util-visit-parents": "6.0.2",
78
+ "vfile": "6.0.3",
71
79
  "yaml": "2.9.1"
72
80
  },
73
81
  "engines": {