@astrojs/markdown-remark 0.10.2 → 0.11.2
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/CHANGELOG.md +26 -0
- package/dist/index.js +29 -11
- package/dist/mdxjs.d.ts +3 -0
- package/dist/mdxjs.js +17 -0
- package/dist/rehype-collect-headers.js +12 -9
- package/dist/rehype-jsx.d.ts +2 -1
- package/dist/rehype-jsx.js +17 -12
- package/dist/remark-mdxish.d.ts +1 -1
- package/dist/remark-mdxish.js +43 -5
- package/dist/remark-prism.js +2 -2
- package/dist/remark-unwrap.js +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +8 -3
- package/src/index.ts +40 -14
- package/src/mdxjs.ts +27 -0
- package/src/rehype-collect-headers.ts +15 -10
- package/src/rehype-escape.ts +1 -1
- package/src/rehype-jsx.ts +31 -14
- package/src/remark-mdxish.ts +51 -5
- package/src/remark-prism.ts +2 -2
- package/src/remark-unwrap.ts +1 -1
- package/src/types.ts +2 -2
- package/test/autolinking.test.js +92 -0
- package/test/expressions.test.js +32 -1
- package/test/strictness.test.js +99 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @astrojs/markdown-remark
|
|
2
2
|
|
|
3
|
+
## 0.11.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#3572](https://github.com/withastro/astro/pull/3572) [`5c73f614`](https://github.com/withastro/astro/commit/5c73f614e8f579e04fe61c948b69be7bc6d81d5d) Thanks [@hippotastic](https://github.com/hippotastic)! - Fix remarkMdxish performance issue on huge sites
|
|
8
|
+
|
|
9
|
+
## 0.11.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#3564](https://github.com/withastro/astro/pull/3564) [`76fb01cf`](https://github.com/withastro/astro/commit/76fb01cff1002f2a37e93869378802156c4eca7c) Thanks [@hippotastic](https://github.com/hippotastic)! - Fix autolinking of URLs inside links
|
|
14
|
+
|
|
15
|
+
* [#3554](https://github.com/withastro/astro/pull/3554) [`c549f161`](https://github.com/withastro/astro/commit/c549f161cadd76a666672556f2c2d63b5f97f00d) Thanks [@hippotastic](https://github.com/hippotastic)! - Allow AlpineJS syntax extensions in Markdown
|
|
16
|
+
|
|
17
|
+
## 0.11.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- [#3502](https://github.com/withastro/astro/pull/3502) [`939fe159`](https://github.com/withastro/astro/commit/939fe159255cecf1cab5c1b3da2670d30ac8e4a7) Thanks [@nokazn](https://github.com/nokazn)! - Fix cases for JSX-like expressions in code blocks of headings
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- [#3514](https://github.com/withastro/astro/pull/3514) [`6c955ca6`](https://github.com/withastro/astro/commit/6c955ca643a7a071609ce8a5258cc7faf5a636b2) Thanks [@hippotastic](https://github.com/hippotastic)! - Fix Markdown errors missing source filename
|
|
26
|
+
|
|
27
|
+
* [#3516](https://github.com/withastro/astro/pull/3516) [`30578015`](https://github.com/withastro/astro/commit/30578015919e019cd8dd354288a45c1fc63bd01f) Thanks [@hippotastic](https://github.com/hippotastic)! - Fix: Allow self-closing tags in Markdown
|
|
28
|
+
|
|
3
29
|
## 0.10.2
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
+
import { loadPlugins } from "./load-plugins.js";
|
|
1
2
|
import createCollectHeaders from "./rehype-collect-headers.js";
|
|
2
|
-
import
|
|
3
|
+
import rehypeEscape from "./rehype-escape.js";
|
|
3
4
|
import rehypeExpressions from "./rehype-expressions.js";
|
|
4
5
|
import rehypeIslands from "./rehype-islands.js";
|
|
5
|
-
import remarkMdxish from "./remark-mdxish.js";
|
|
6
|
-
import remarkMarkAndUnravel from "./remark-mark-and-unravel.js";
|
|
7
6
|
import rehypeJsx from "./rehype-jsx.js";
|
|
8
|
-
import
|
|
7
|
+
import remarkMarkAndUnravel from "./remark-mark-and-unravel.js";
|
|
8
|
+
import remarkMdxish from "./remark-mdxish.js";
|
|
9
9
|
import remarkPrism from "./remark-prism.js";
|
|
10
|
+
import scopedStyles from "./remark-scoped-styles.js";
|
|
10
11
|
import remarkShiki from "./remark-shiki.js";
|
|
11
12
|
import remarkUnwrap from "./remark-unwrap.js";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
13
|
+
import Slugger from "github-slugger";
|
|
14
|
+
import rehypeRaw from "rehype-raw";
|
|
15
|
+
import rehypeStringify from "rehype-stringify";
|
|
14
16
|
import markdown from "remark-parse";
|
|
15
17
|
import markdownToHtml from "remark-rehype";
|
|
16
|
-
import
|
|
17
|
-
import rehypeRaw from "rehype-raw";
|
|
18
|
-
import Slugger from "github-slugger";
|
|
18
|
+
import { unified } from "unified";
|
|
19
19
|
import { VFile } from "vfile";
|
|
20
20
|
export * from "./types.js";
|
|
21
21
|
const DEFAULT_REMARK_PLUGINS = ["remark-gfm", "remark-smartypants"];
|
|
@@ -74,12 +74,13 @@ async function renderMarkdown(content, opts = {}) {
|
|
|
74
74
|
loadedRehypePlugins.forEach(([plugin, opts2]) => {
|
|
75
75
|
parser.use([[plugin, opts2]]);
|
|
76
76
|
});
|
|
77
|
-
parser.use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw]).use(rehypeEscape).use(rehypeIslands);
|
|
77
|
+
parser.use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw]).use(rehypeEscape).use(rehypeIslands).use([rehypeCollectHeaders]).use(rehypeStringify, { allowDangerousHtml: true });
|
|
78
78
|
let result;
|
|
79
79
|
try {
|
|
80
|
-
const vfile = await parser.
|
|
80
|
+
const vfile = await parser.process(input);
|
|
81
81
|
result = vfile.toString();
|
|
82
82
|
} catch (err) {
|
|
83
|
+
err = prefixError(err, `Failed to parse Markdown file "${input.path}"`);
|
|
83
84
|
console.error(err);
|
|
84
85
|
throw err;
|
|
85
86
|
}
|
|
@@ -88,6 +89,23 @@ async function renderMarkdown(content, opts = {}) {
|
|
|
88
89
|
code: result.toString()
|
|
89
90
|
};
|
|
90
91
|
}
|
|
92
|
+
function prefixError(err, prefix) {
|
|
93
|
+
if (err && err.message) {
|
|
94
|
+
try {
|
|
95
|
+
err.message = `${prefix}:
|
|
96
|
+
${err.message}`;
|
|
97
|
+
return err;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const wrappedError = new Error(`${prefix}${err ? `: ${err}` : ""}`);
|
|
102
|
+
try {
|
|
103
|
+
wrappedError.stack = err.stack;
|
|
104
|
+
wrappedError.cause = err;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
}
|
|
107
|
+
return wrappedError;
|
|
108
|
+
}
|
|
91
109
|
export {
|
|
92
110
|
DEFAULT_REHYPE_PLUGINS,
|
|
93
111
|
DEFAULT_REMARK_PLUGINS,
|
package/dist/mdxjs.d.ts
ADDED
package/dist/mdxjs.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { mdxJsx } from "@astrojs/micromark-extension-mdx-jsx";
|
|
2
|
+
import { Parser } from "acorn";
|
|
3
|
+
import acornJsx from "acorn-jsx";
|
|
4
|
+
import { mdxExpression } from "micromark-extension-mdx-expression";
|
|
5
|
+
import { mdxMd } from "micromark-extension-mdx-md";
|
|
6
|
+
import { combineExtensions } from "micromark-util-combine-extensions";
|
|
7
|
+
function mdxjs(options) {
|
|
8
|
+
const settings = Object.assign({
|
|
9
|
+
acorn: Parser.extend(acornJsx()),
|
|
10
|
+
acornOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
11
|
+
addResult: true
|
|
12
|
+
}, options);
|
|
13
|
+
return combineExtensions([mdxExpression(settings), mdxJsx(settings), mdxMd]);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
mdxjs
|
|
17
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { visit } from "unist-util-visit";
|
|
2
1
|
import Slugger from "github-slugger";
|
|
2
|
+
import { toHtml } from "hast-util-to-html";
|
|
3
|
+
import { visit } from "unist-util-visit";
|
|
3
4
|
function createCollectHeaders() {
|
|
4
5
|
const headers = [];
|
|
5
6
|
const slugger = new Slugger();
|
|
@@ -15,29 +16,31 @@ function createCollectHeaders() {
|
|
|
15
16
|
if (!level)
|
|
16
17
|
return;
|
|
17
18
|
const depth = Number.parseInt(level);
|
|
18
|
-
let raw = "";
|
|
19
19
|
let text = "";
|
|
20
20
|
let isJSX = false;
|
|
21
|
-
visit(node, (child) => {
|
|
22
|
-
if (child.type === "element") {
|
|
21
|
+
visit(node, (child, _2, parent) => {
|
|
22
|
+
if (child.type === "element" || parent == null) {
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
if (child.type === "raw") {
|
|
26
26
|
if (child.value.startsWith("\n<") || child.value.endsWith(">\n")) {
|
|
27
|
-
raw += child.value.replace(/^\n|\n$/g, "");
|
|
28
27
|
return;
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
30
|
if (child.type === "text" || child.type === "raw") {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
if ((/* @__PURE__ */ new Set(["code", "pre"])).has(parent.tagName)) {
|
|
32
|
+
text += child.value;
|
|
33
|
+
} else {
|
|
34
|
+
text += child.value.replace(/\{/g, "${");
|
|
35
|
+
isJSX = isJSX || child.value.includes("{");
|
|
36
|
+
}
|
|
35
37
|
}
|
|
36
38
|
});
|
|
37
39
|
node.properties = node.properties || {};
|
|
38
40
|
if (typeof node.properties.id !== "string") {
|
|
39
41
|
if (isJSX) {
|
|
40
|
-
|
|
42
|
+
const raw = toHtml(node.children, { allowDangerousHtml: true }).replace(/\n(<)/g, "<").replace(/(>)\n/g, ">");
|
|
43
|
+
node.properties.id = `$$slug(\`${text}\`)`;
|
|
41
44
|
node.type = "raw";
|
|
42
45
|
node.value = `<${node.tagName} id={${node.properties.id}}>${raw}</${node.tagName}>`;
|
|
43
46
|
} else {
|
package/dist/rehype-jsx.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { RehypePlugin } from './types.js';
|
|
2
|
+
export default function rehypeJsx(): ReturnType<RehypePlugin>;
|
package/dist/rehype-jsx.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { visit } from "unist-util-visit";
|
|
2
2
|
const MDX_ELEMENTS = ["mdxJsxFlowElement", "mdxJsxTextElement"];
|
|
3
3
|
function rehypeJsx() {
|
|
4
|
-
return function(
|
|
5
|
-
visit(node,
|
|
6
|
-
child.tagName = `${child.tagName}`;
|
|
7
|
-
});
|
|
8
|
-
visit(node, MDX_ELEMENTS, (child, index, parent) => {
|
|
4
|
+
return function(tree) {
|
|
5
|
+
visit(tree, MDX_ELEMENTS, (node, index, parent) => {
|
|
9
6
|
if (index === null || !Boolean(parent))
|
|
10
7
|
return;
|
|
11
|
-
const attrs =
|
|
8
|
+
const attrs = node.attributes.reduce((acc, entry) => {
|
|
12
9
|
let attr = entry.value;
|
|
13
10
|
if (attr && typeof attr === "object") {
|
|
14
11
|
attr = `{${attr.value}}`;
|
|
@@ -24,22 +21,30 @@ function rehypeJsx() {
|
|
|
24
21
|
}
|
|
25
22
|
return acc + ` ${entry.name}${attr ? "=" : ""}${attr}`;
|
|
26
23
|
}, "");
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
if (node.children.length === 0) {
|
|
25
|
+
node.type = "raw";
|
|
26
|
+
node.value = `<${node.name}${attrs} />`;
|
|
30
27
|
return;
|
|
31
28
|
}
|
|
29
|
+
if (node.name === "a") {
|
|
30
|
+
visit(node, "element", (el, elIndex, elParent) => {
|
|
31
|
+
const isAutolink = el.tagName === "a" && el.children.length === 1 && el.children[0].type === "text" && el.children[0].value.match(/^(https?:\/\/|www\.)/i);
|
|
32
|
+
if (isAutolink) {
|
|
33
|
+
elParent.children.splice(elIndex, 1, el.children[0]);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
32
37
|
const openingTag = {
|
|
33
38
|
type: "raw",
|
|
34
39
|
value: `
|
|
35
|
-
<${
|
|
40
|
+
<${node.name}${attrs}>`
|
|
36
41
|
};
|
|
37
42
|
const closingTag = {
|
|
38
43
|
type: "raw",
|
|
39
|
-
value: `</${
|
|
44
|
+
value: `</${node.name}>
|
|
40
45
|
`
|
|
41
46
|
};
|
|
42
|
-
parent.children.splice(index, 1, openingTag, ...
|
|
47
|
+
parent.children.splice(index, 1, openingTag, ...node.children, closingTag);
|
|
43
48
|
});
|
|
44
49
|
};
|
|
45
50
|
}
|
package/dist/remark-mdxish.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default function remarkMdxish(this: any
|
|
1
|
+
export default function remarkMdxish(this: any): void;
|
package/dist/remark-mdxish.js
CHANGED
|
@@ -1,15 +1,53 @@
|
|
|
1
|
-
import { mdxjs } from "micromark-extension-mdxjs";
|
|
2
1
|
import { mdxFromMarkdown, mdxToMarkdown } from "./mdast-util-mdxish.js";
|
|
3
|
-
|
|
2
|
+
import { mdxjs } from "./mdxjs.js";
|
|
3
|
+
const extMdxJs = mdxjs({});
|
|
4
|
+
const extMdxFromMarkdown = makeFromMarkdownLessStrict(mdxFromMarkdown());
|
|
5
|
+
const extMdxToMarkdown = mdxToMarkdown();
|
|
6
|
+
function remarkMdxish() {
|
|
4
7
|
const data = this.data();
|
|
5
|
-
add("micromarkExtensions",
|
|
6
|
-
add("fromMarkdownExtensions",
|
|
7
|
-
add("toMarkdownExtensions",
|
|
8
|
+
add("micromarkExtensions", extMdxJs);
|
|
9
|
+
add("fromMarkdownExtensions", extMdxFromMarkdown);
|
|
10
|
+
add("toMarkdownExtensions", extMdxToMarkdown);
|
|
8
11
|
function add(field, value) {
|
|
9
12
|
const list = data[field] ? data[field] : data[field] = [];
|
|
10
13
|
list.push(value);
|
|
11
14
|
}
|
|
12
15
|
}
|
|
16
|
+
function makeFromMarkdownLessStrict(extensions) {
|
|
17
|
+
extensions.forEach((extension) => {
|
|
18
|
+
["mdxJsxFlowTag", "mdxJsxTextTag"].forEach((exitHandler) => {
|
|
19
|
+
if (!extension.exit || !extension.exit[exitHandler])
|
|
20
|
+
return;
|
|
21
|
+
extension.exit[exitHandler] = chainHandlers(fixSelfClosing, extension.exit[exitHandler]);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
return extensions;
|
|
25
|
+
}
|
|
26
|
+
const selfClosingTags = /* @__PURE__ */ new Set([
|
|
27
|
+
"area",
|
|
28
|
+
"base",
|
|
29
|
+
"br",
|
|
30
|
+
"col",
|
|
31
|
+
"embed",
|
|
32
|
+
"hr",
|
|
33
|
+
"img",
|
|
34
|
+
"input",
|
|
35
|
+
"link",
|
|
36
|
+
"meta",
|
|
37
|
+
"source",
|
|
38
|
+
"track",
|
|
39
|
+
"wbr"
|
|
40
|
+
]);
|
|
41
|
+
function fixSelfClosing() {
|
|
42
|
+
const tag = this.getData("mdxJsxTag");
|
|
43
|
+
if (tag.name && selfClosingTags.has(tag.name))
|
|
44
|
+
tag.selfClosing = true;
|
|
45
|
+
}
|
|
46
|
+
function chainHandlers(...handlers) {
|
|
47
|
+
return function handlerChain(token) {
|
|
48
|
+
handlers.forEach((handler) => handler.call(this, token));
|
|
49
|
+
};
|
|
50
|
+
}
|
|
13
51
|
export {
|
|
14
52
|
remarkMdxish as default
|
|
15
53
|
};
|
package/dist/remark-prism.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { visit } from "unist-util-visit";
|
|
2
|
-
import Prism from "prismjs";
|
|
3
1
|
import { addAstro } from "@astrojs/prism/internal";
|
|
2
|
+
import Prism from "prismjs";
|
|
4
3
|
import loadLanguages from "prismjs/components/index.js";
|
|
4
|
+
import { visit } from "unist-util-visit";
|
|
5
5
|
const noVisit = /* @__PURE__ */ new Set(["root", "html", "text"]);
|
|
6
6
|
const languageMap = /* @__PURE__ */ new Map([["ts", "typescript"]]);
|
|
7
7
|
function runHighlighter(lang, code) {
|
package/dist/remark-unwrap.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type * as unified from 'unified';
|
|
2
|
-
import type * as mdast from 'mdast';
|
|
3
1
|
import type * as hast from 'hast';
|
|
2
|
+
import type * as mdast from 'mdast';
|
|
4
3
|
import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
|
|
4
|
+
import type * as unified from 'unified';
|
|
5
5
|
export type { Node } from 'unist';
|
|
6
6
|
export declare type RemarkPlugin<PluginParameters extends any[] = any[]> = unified.Plugin<PluginParameters, mdast.Root>;
|
|
7
7
|
export declare type RemarkPlugins = (string | [string, any] | RemarkPlugin | [RemarkPlugin, any])[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/markdown-remark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "withastro",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,14 +16,18 @@
|
|
|
16
16
|
".": "./dist/index.js"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@astrojs/micromark-extension-mdx-jsx": "^1.0.3",
|
|
19
20
|
"@astrojs/prism": "^0.4.1",
|
|
21
|
+
"acorn": "^8.7.1",
|
|
22
|
+
"acorn-jsx": "^5.3.2",
|
|
20
23
|
"assert": "^2.0.0",
|
|
21
24
|
"github-slugger": "^1.4.0",
|
|
22
25
|
"mdast-util-mdx-expression": "^1.2.0",
|
|
23
26
|
"mdast-util-mdx-jsx": "^1.2.0",
|
|
24
27
|
"mdast-util-to-string": "^3.1.0",
|
|
25
|
-
"micromark-extension-mdx-
|
|
26
|
-
"micromark-extension-
|
|
28
|
+
"micromark-extension-mdx-expression": "^1.0.3",
|
|
29
|
+
"micromark-extension-mdx-md": "^1.0.0",
|
|
30
|
+
"micromark-util-combine-extensions": "^1.0.0",
|
|
27
31
|
"prismjs": "^1.28.0",
|
|
28
32
|
"rehype-raw": "^6.1.1",
|
|
29
33
|
"rehype-stringify": "^9.0.3",
|
|
@@ -47,6 +51,7 @@
|
|
|
47
51
|
"@types/unist": "^2.0.6",
|
|
48
52
|
"astro-scripts": "0.0.4",
|
|
49
53
|
"chai": "^4.3.6",
|
|
54
|
+
"micromark-util-types": "^1.0.2",
|
|
50
55
|
"mocha": "^9.2.2"
|
|
51
56
|
},
|
|
52
57
|
"scripts": {
|
package/src/index.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import type { MarkdownRenderingOptions, MarkdownRenderingResult } from './types';
|
|
2
2
|
|
|
3
|
+
import { loadPlugins } from './load-plugins.js';
|
|
3
4
|
import createCollectHeaders from './rehype-collect-headers.js';
|
|
4
|
-
import
|
|
5
|
+
import rehypeEscape from './rehype-escape.js';
|
|
5
6
|
import rehypeExpressions from './rehype-expressions.js';
|
|
6
7
|
import rehypeIslands from './rehype-islands.js';
|
|
7
|
-
import remarkMdxish from './remark-mdxish.js';
|
|
8
|
-
import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
|
|
9
8
|
import rehypeJsx from './rehype-jsx.js';
|
|
10
|
-
import
|
|
9
|
+
import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
|
|
10
|
+
import remarkMdxish from './remark-mdxish.js';
|
|
11
11
|
import remarkPrism from './remark-prism.js';
|
|
12
|
+
import scopedStyles from './remark-scoped-styles.js';
|
|
12
13
|
import remarkShiki from './remark-shiki.js';
|
|
13
14
|
import remarkUnwrap from './remark-unwrap.js';
|
|
14
|
-
import { loadPlugins } from './load-plugins.js';
|
|
15
15
|
|
|
16
|
-
import
|
|
16
|
+
import Slugger from 'github-slugger';
|
|
17
|
+
import rehypeRaw from 'rehype-raw';
|
|
18
|
+
import rehypeStringify from 'rehype-stringify';
|
|
17
19
|
import markdown from 'remark-parse';
|
|
18
20
|
import markdownToHtml from 'remark-rehype';
|
|
19
|
-
import
|
|
20
|
-
import rehypeRaw from 'rehype-raw';
|
|
21
|
-
import Slugger from 'github-slugger';
|
|
21
|
+
import { unified } from 'unified';
|
|
22
22
|
import { VFile } from 'vfile';
|
|
23
23
|
|
|
24
24
|
export * from './types.js';
|
|
@@ -99,16 +99,18 @@ export async function renderMarkdown(
|
|
|
99
99
|
parser
|
|
100
100
|
.use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw])
|
|
101
101
|
.use(rehypeEscape)
|
|
102
|
-
.use(rehypeIslands)
|
|
102
|
+
.use(rehypeIslands)
|
|
103
|
+
.use([rehypeCollectHeaders])
|
|
104
|
+
.use(rehypeStringify, { allowDangerousHtml: true });
|
|
103
105
|
|
|
104
106
|
let result: string;
|
|
105
107
|
try {
|
|
106
|
-
const vfile = await parser
|
|
107
|
-
.use([rehypeCollectHeaders])
|
|
108
|
-
.use(rehypeStringify, { allowDangerousHtml: true })
|
|
109
|
-
.process(input);
|
|
108
|
+
const vfile = await parser.process(input);
|
|
110
109
|
result = vfile.toString();
|
|
111
110
|
} catch (err) {
|
|
111
|
+
// Ensure that the error message contains the input filename
|
|
112
|
+
// to make it easier for the user to fix the issue
|
|
113
|
+
err = prefixError(err, `Failed to parse Markdown file "${input.path}"`);
|
|
112
114
|
console.error(err);
|
|
113
115
|
throw err;
|
|
114
116
|
}
|
|
@@ -118,3 +120,27 @@ export async function renderMarkdown(
|
|
|
118
120
|
code: result.toString(),
|
|
119
121
|
};
|
|
120
122
|
}
|
|
123
|
+
|
|
124
|
+
function prefixError(err: any, prefix: string) {
|
|
125
|
+
// If the error is an object with a `message` property, attempt to prefix the message
|
|
126
|
+
if (err && err.message) {
|
|
127
|
+
try {
|
|
128
|
+
err.message = `${prefix}:\n${err.message}`;
|
|
129
|
+
return err;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
// Any errors here are ok, there's fallback code below
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// If that failed, create a new error with the desired message and attempt to keep the stack
|
|
136
|
+
const wrappedError = new Error(`${prefix}${err ? `: ${err}` : ''}`);
|
|
137
|
+
try {
|
|
138
|
+
wrappedError.stack = err.stack;
|
|
139
|
+
// @ts-ignore
|
|
140
|
+
wrappedError.cause = err;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// It's ok if we could not set the stack or cause - the message is the most important part
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return wrappedError;
|
|
146
|
+
}
|
package/src/mdxjs.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Note: The code in this file is based on `micromark-extension-mdxjs`
|
|
2
|
+
// and was adapted to use our fork `@astrojs/micromark-extension-mdx-jsx`
|
|
3
|
+
// instead of `micromark-extension-mdx-jsx` to allow some extended syntax.
|
|
4
|
+
// See `@astrojs/micromark-extension-mdx-jsx` on NPM for more details.
|
|
5
|
+
// Also, support for ESM imports & exports in Markdown content was removed.
|
|
6
|
+
|
|
7
|
+
import { mdxJsx } from '@astrojs/micromark-extension-mdx-jsx';
|
|
8
|
+
import { Parser } from 'acorn';
|
|
9
|
+
import acornJsx from 'acorn-jsx';
|
|
10
|
+
import type { Options } from 'micromark-extension-mdx-expression';
|
|
11
|
+
import { mdxExpression } from 'micromark-extension-mdx-expression';
|
|
12
|
+
import { mdxMd } from 'micromark-extension-mdx-md';
|
|
13
|
+
import { combineExtensions } from 'micromark-util-combine-extensions';
|
|
14
|
+
import type { Extension } from 'micromark-util-types';
|
|
15
|
+
|
|
16
|
+
export function mdxjs(options: Options): Extension {
|
|
17
|
+
const settings: any = Object.assign(
|
|
18
|
+
{
|
|
19
|
+
acorn: Parser.extend(acornJsx()),
|
|
20
|
+
acornOptions: { ecmaVersion: 2020, sourceType: 'module' },
|
|
21
|
+
addResult: true,
|
|
22
|
+
},
|
|
23
|
+
options
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return combineExtensions([mdxExpression(settings), mdxJsx(settings), mdxMd]);
|
|
27
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { visit } from 'unist-util-visit';
|
|
2
1
|
import Slugger from 'github-slugger';
|
|
2
|
+
import { toHtml } from 'hast-util-to-html';
|
|
3
|
+
import { visit } from 'unist-util-visit';
|
|
3
4
|
|
|
4
5
|
import type { MarkdownHeader, RehypePlugin } from './types.js';
|
|
5
6
|
|
|
@@ -17,32 +18,36 @@ export default function createCollectHeaders() {
|
|
|
17
18
|
if (!level) return;
|
|
18
19
|
const depth = Number.parseInt(level);
|
|
19
20
|
|
|
20
|
-
let raw = '';
|
|
21
21
|
let text = '';
|
|
22
22
|
let isJSX = false;
|
|
23
|
-
visit(node, (child) => {
|
|
24
|
-
if (child.type === 'element') {
|
|
23
|
+
visit(node, (child, _, parent) => {
|
|
24
|
+
if (child.type === 'element' || parent == null) {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
if (child.type === 'raw') {
|
|
28
|
-
// HACK: serialized JSX from internal plugins, ignore these for slug
|
|
29
28
|
if (child.value.startsWith('\n<') || child.value.endsWith('>\n')) {
|
|
30
|
-
raw += child.value.replace(/^\n|\n$/g, '');
|
|
31
29
|
return;
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
if (child.type === 'text' || child.type === 'raw') {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
if (new Set(['code', 'pre']).has(parent.tagName)) {
|
|
34
|
+
text += child.value;
|
|
35
|
+
} else {
|
|
36
|
+
text += child.value.replace(/\{/g, '${');
|
|
37
|
+
isJSX = isJSX || child.value.includes('{');
|
|
38
|
+
}
|
|
38
39
|
}
|
|
39
40
|
});
|
|
40
41
|
|
|
41
42
|
node.properties = node.properties || {};
|
|
42
43
|
if (typeof node.properties.id !== 'string') {
|
|
43
44
|
if (isJSX) {
|
|
45
|
+
// HACK: serialized JSX from internal plugins, ignore these for slug
|
|
46
|
+
const raw = toHtml(node.children, { allowDangerousHtml: true })
|
|
47
|
+
.replace(/\n(<)/g, '<')
|
|
48
|
+
.replace(/(>)\n/g, '>');
|
|
44
49
|
// HACK: for ids that have JSX content, use $$slug helper to generate slug at runtime
|
|
45
|
-
node.properties.id = `$$slug(\`${text
|
|
50
|
+
node.properties.id = `$$slug(\`${text}\`)`;
|
|
46
51
|
(node as any).type = 'raw';
|
|
47
52
|
(
|
|
48
53
|
node as any
|
package/src/rehype-escape.ts
CHANGED
package/src/rehype-jsx.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { visit } from 'unist-util-visit';
|
|
2
|
+
import type { RehypePlugin } from './types.js';
|
|
2
3
|
|
|
3
4
|
const MDX_ELEMENTS = ['mdxJsxFlowElement', 'mdxJsxTextElement'];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
});
|
|
9
|
-
visit(node, MDX_ELEMENTS, (child: any, index: number | null, parent: any) => {
|
|
5
|
+
|
|
6
|
+
export default function rehypeJsx(): ReturnType<RehypePlugin> {
|
|
7
|
+
return function (tree) {
|
|
8
|
+
visit(tree, MDX_ELEMENTS, (node: any, index: number | null, parent: any) => {
|
|
10
9
|
if (index === null || !Boolean(parent)) return;
|
|
11
10
|
|
|
12
|
-
const attrs =
|
|
11
|
+
const attrs = node.attributes.reduce((acc: any[], entry: any) => {
|
|
13
12
|
let attr = entry.value;
|
|
14
13
|
if (attr && typeof attr === 'object') {
|
|
15
14
|
attr = `{${attr.value}}`;
|
|
@@ -26,23 +25,41 @@ export default function rehypeJsx(): any {
|
|
|
26
25
|
return acc + ` ${entry.name}${attr ? '=' : ''}${attr}`;
|
|
27
26
|
}, '');
|
|
28
27
|
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
if (node.children.length === 0) {
|
|
29
|
+
node.type = 'raw';
|
|
30
|
+
node.value = `<${node.name}${attrs} />`;
|
|
32
31
|
return;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
//
|
|
34
|
+
// If the current node is a JSX <a> element, remove autolinks from its children
|
|
35
|
+
// to prevent Markdown code like `<a href="/">**Go to www.example.com now!**</a>`
|
|
36
|
+
// from creating a nested link to `www.example.com`
|
|
37
|
+
if (node.name === 'a') {
|
|
38
|
+
visit(node, 'element', (el, elIndex, elParent) => {
|
|
39
|
+
const isAutolink =
|
|
40
|
+
el.tagName === 'a' &&
|
|
41
|
+
el.children.length === 1 &&
|
|
42
|
+
el.children[0].type === 'text' &&
|
|
43
|
+
el.children[0].value.match(/^(https?:\/\/|www\.)/i);
|
|
44
|
+
|
|
45
|
+
// If we found an autolink, remove it by replacing it with its text-only child
|
|
46
|
+
if (isAutolink) {
|
|
47
|
+
elParent.children.splice(elIndex, 1, el.children[0]);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Replace the current node with its children
|
|
36
53
|
// wrapped by raw opening and closing tags
|
|
37
54
|
const openingTag = {
|
|
38
55
|
type: 'raw',
|
|
39
|
-
value: `\n<${
|
|
56
|
+
value: `\n<${node.name}${attrs}>`,
|
|
40
57
|
};
|
|
41
58
|
const closingTag = {
|
|
42
59
|
type: 'raw',
|
|
43
|
-
value: `</${
|
|
60
|
+
value: `</${node.name}>\n`,
|
|
44
61
|
};
|
|
45
|
-
parent.children.splice(index, 1, openingTag, ...
|
|
62
|
+
parent.children.splice(index, 1, openingTag, ...node.children, closingTag);
|
|
46
63
|
});
|
|
47
64
|
};
|
|
48
65
|
}
|
package/src/remark-mdxish.ts
CHANGED
|
@@ -1,15 +1,61 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type * as fromMarkdown from 'mdast-util-from-markdown';
|
|
2
|
+
import type { Tag } from 'mdast-util-mdx-jsx';
|
|
2
3
|
import { mdxFromMarkdown, mdxToMarkdown } from './mdast-util-mdxish.js';
|
|
4
|
+
import { mdxjs } from './mdxjs.js';
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
// Prepare markdown extensions once to prevent performance issues
|
|
7
|
+
const extMdxJs = mdxjs({});
|
|
8
|
+
const extMdxFromMarkdown = makeFromMarkdownLessStrict(mdxFromMarkdown());
|
|
9
|
+
const extMdxToMarkdown = mdxToMarkdown();
|
|
10
|
+
|
|
11
|
+
export default function remarkMdxish(this: any) {
|
|
5
12
|
const data = this.data();
|
|
6
13
|
|
|
7
|
-
add('micromarkExtensions',
|
|
8
|
-
add('fromMarkdownExtensions',
|
|
9
|
-
add('toMarkdownExtensions',
|
|
14
|
+
add('micromarkExtensions', extMdxJs);
|
|
15
|
+
add('fromMarkdownExtensions', extMdxFromMarkdown);
|
|
16
|
+
add('toMarkdownExtensions', extMdxToMarkdown);
|
|
10
17
|
|
|
11
18
|
function add(field: string, value: unknown) {
|
|
12
19
|
const list = data[field] ? data[field] : (data[field] = []);
|
|
13
20
|
list.push(value);
|
|
14
21
|
}
|
|
15
22
|
}
|
|
23
|
+
|
|
24
|
+
function makeFromMarkdownLessStrict(extensions: fromMarkdown.Extension[]) {
|
|
25
|
+
extensions.forEach((extension) => {
|
|
26
|
+
// Fix exit handlers that are too strict
|
|
27
|
+
['mdxJsxFlowTag', 'mdxJsxTextTag'].forEach((exitHandler) => {
|
|
28
|
+
if (!extension.exit || !extension.exit[exitHandler]) return;
|
|
29
|
+
extension.exit[exitHandler] = chainHandlers(fixSelfClosing, extension.exit[exitHandler]);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return extensions;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const selfClosingTags = new Set([
|
|
37
|
+
'area',
|
|
38
|
+
'base',
|
|
39
|
+
'br',
|
|
40
|
+
'col',
|
|
41
|
+
'embed',
|
|
42
|
+
'hr',
|
|
43
|
+
'img',
|
|
44
|
+
'input',
|
|
45
|
+
'link',
|
|
46
|
+
'meta',
|
|
47
|
+
'source',
|
|
48
|
+
'track',
|
|
49
|
+
'wbr',
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
function fixSelfClosing(this: fromMarkdown.CompileContext) {
|
|
53
|
+
const tag = this.getData('mdxJsxTag') as Tag;
|
|
54
|
+
if (tag.name && selfClosingTags.has(tag.name)) tag.selfClosing = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function chainHandlers(...handlers: fromMarkdown.Handle[]) {
|
|
58
|
+
return function handlerChain(this: fromMarkdown.CompileContext, token: fromMarkdown.Token) {
|
|
59
|
+
handlers.forEach((handler) => handler.call(this, token));
|
|
60
|
+
};
|
|
61
|
+
}
|
package/src/remark-prism.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { visit } from 'unist-util-visit';
|
|
2
|
-
import Prism from 'prismjs';
|
|
3
1
|
import { addAstro } from '@astrojs/prism/internal';
|
|
2
|
+
import Prism from 'prismjs';
|
|
4
3
|
import loadLanguages from 'prismjs/components/index.js';
|
|
4
|
+
import { visit } from 'unist-util-visit';
|
|
5
5
|
const noVisit = new Set(['root', 'html', 'text']);
|
|
6
6
|
|
|
7
7
|
const languageMap = new Map([['ts', 'typescript']]);
|
package/src/remark-unwrap.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type * as unified from 'unified';
|
|
2
|
-
import type * as mdast from 'mdast';
|
|
3
1
|
import type * as hast from 'hast';
|
|
2
|
+
import type * as mdast from 'mdast';
|
|
4
3
|
import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
|
|
4
|
+
import type * as unified from 'unified';
|
|
5
5
|
|
|
6
6
|
export type { Node } from 'unist';
|
|
7
7
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { renderMarkdown } from '../dist/index.js';
|
|
2
|
+
import chai from 'chai';
|
|
3
|
+
|
|
4
|
+
describe('autolinking', () => {
|
|
5
|
+
it('autolinks URLs starting with a protocol in plain text', async () => {
|
|
6
|
+
const { code } = await renderMarkdown(`See https://example.com for more.`, {});
|
|
7
|
+
|
|
8
|
+
chai
|
|
9
|
+
.expect(code.replace(/\n/g, ''))
|
|
10
|
+
.to.equal(`<p>See <a href="https://example.com">https://example.com</a> for more.</p>`);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('autolinks URLs starting with "www." in plain text', async () => {
|
|
14
|
+
const { code } = await renderMarkdown(`See www.example.com for more.`, {});
|
|
15
|
+
|
|
16
|
+
chai
|
|
17
|
+
.expect(code.trim())
|
|
18
|
+
.to.equal(`<p>See <a href="http://www.example.com">www.example.com</a> for more.</p>`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('does not autolink URLs in code blocks', async () => {
|
|
22
|
+
const { code } = await renderMarkdown(
|
|
23
|
+
'See `https://example.com` or `www.example.com` for more.',
|
|
24
|
+
{}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
chai
|
|
28
|
+
.expect(code.trim())
|
|
29
|
+
.to.equal(
|
|
30
|
+
`<p>See <code is:raw>https://example.com</code> or ` +
|
|
31
|
+
`<code is:raw>www.example.com</code> for more.</p>`
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('does not autolink URLs in fenced code blocks', async () => {
|
|
36
|
+
const { code } = await renderMarkdown(
|
|
37
|
+
'Example:\n```\nGo to https://example.com or www.example.com now.\n```',
|
|
38
|
+
{}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
chai
|
|
42
|
+
.expect(code)
|
|
43
|
+
.to.contain(`<pre is:raw`)
|
|
44
|
+
.to.contain(`Go to https://example.com or www.example.com now.`);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('does not autolink URLs starting with a protocol when nested inside links', async () => {
|
|
48
|
+
const { code } = await renderMarkdown(
|
|
49
|
+
`See [http://example.com](http://example.com) or ` +
|
|
50
|
+
`<a test href="https://example.com">https://example.com</a>`,
|
|
51
|
+
{}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
chai
|
|
55
|
+
.expect(code.replace(/\n/g, ''))
|
|
56
|
+
.to.equal(
|
|
57
|
+
`<p>See <a href="http://example.com">http://example.com</a> or ` +
|
|
58
|
+
`<a test href="https://example.com">https://example.com</a></p>`
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('does not autolink URLs starting with "www." when nested inside links', async () => {
|
|
63
|
+
const { code } = await renderMarkdown(
|
|
64
|
+
`See [www.example.com](https://www.example.com) or ` +
|
|
65
|
+
`<a test href="https://www.example.com">www.example.com</a>`,
|
|
66
|
+
{}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
chai
|
|
70
|
+
.expect(code.replace(/\n/g, ''))
|
|
71
|
+
.to.equal(
|
|
72
|
+
`<p>See <a href="https://www.example.com">www.example.com</a> or ` +
|
|
73
|
+
`<a test href="https://www.example.com">www.example.com</a></p>`
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('does not autolink URLs when nested several layers deep inside links', async () => {
|
|
78
|
+
const { code } = await renderMarkdown(
|
|
79
|
+
`<a href="https://www.example.com">**Visit _our www.example.com or ` +
|
|
80
|
+
`http://localhost pages_ for more!**</a>`,
|
|
81
|
+
{}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
chai
|
|
85
|
+
.expect(code.replace(/\n/g, ''))
|
|
86
|
+
.to.equal(
|
|
87
|
+
`<a href="https://www.example.com"><strong>` +
|
|
88
|
+
`Visit <em>our www.example.com or http://localhost pages</em> for more!` +
|
|
89
|
+
`</strong></a>`
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
});
|
package/test/expressions.test.js
CHANGED
|
@@ -2,7 +2,7 @@ import { renderMarkdown } from '../dist/index.js';
|
|
|
2
2
|
import chai from 'chai';
|
|
3
3
|
|
|
4
4
|
describe('expressions', () => {
|
|
5
|
-
it('should be able to serialize bare
|
|
5
|
+
it('should be able to serialize bare expression', async () => {
|
|
6
6
|
const { code } = await renderMarkdown(`{a}`, {});
|
|
7
7
|
|
|
8
8
|
chai.expect(code).to.equal(`{a}`);
|
|
@@ -40,6 +40,37 @@ describe('expressions', () => {
|
|
|
40
40
|
);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
it('should be able to avoid evaluating JSX-like expressions in an inline code & generate a slug for id', async () => {
|
|
44
|
+
const { code } = await renderMarkdown(`# \`{frontmatter.title}\``, {});
|
|
45
|
+
|
|
46
|
+
chai
|
|
47
|
+
.expect(code)
|
|
48
|
+
.to.equal('<h1 id="frontmattertitle"><code is:raw>{frontmatter.title}</code></h1>');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should be able to avoid evaluating JSX-like expressions in inline codes', async () => {
|
|
52
|
+
const { code } = await renderMarkdown(`# \`{ foo }\` is a shorthand for \`{ foo: foo }\``, {});
|
|
53
|
+
|
|
54
|
+
chai
|
|
55
|
+
.expect(code)
|
|
56
|
+
.to.equal(
|
|
57
|
+
'<h1 id="-foo--is-a-shorthand-for--foo-foo-"><code is:raw>{ foo }</code> is a shorthand for <code is:raw>{ foo: foo }</code></h1>'
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should be able to avoid evaluating JSX-like expressions & escape HTML tag characters in inline codes', async () => {
|
|
62
|
+
const { code } = await renderMarkdown(
|
|
63
|
+
`###### \`{}\` is equivalent to \`Record<never, never>\` <small>(at TypeScript v{frontmatter.version})</small>`,
|
|
64
|
+
{}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
chai
|
|
68
|
+
.expect(code)
|
|
69
|
+
.to.equal(
|
|
70
|
+
`<h6 id={$$slug(\`{} is equivalent to Record<never, never> (at TypeScript v\${frontmatter.version})\`)}><code is:raw>{}</code> is equivalent to <code is:raw>Record<never, never></code> <small>(at TypeScript v{frontmatter.version})</small></h6>`
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
43
74
|
it('should be able to serialize function expression', async () => {
|
|
44
75
|
const { code } = await renderMarkdown(
|
|
45
76
|
`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { renderMarkdown } from '../dist/index.js';
|
|
2
|
+
import chai from 'chai';
|
|
3
|
+
|
|
4
|
+
describe('strictness', () => {
|
|
5
|
+
it('should allow self-closing HTML tags (void elements)', async () => {
|
|
6
|
+
const { code } = await renderMarkdown(
|
|
7
|
+
`Use self-closing void elements<br>like word<wbr>break and images: <img src="hi.jpg">`,
|
|
8
|
+
{}
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
chai
|
|
12
|
+
.expect(code)
|
|
13
|
+
.to.equal(
|
|
14
|
+
`<p>Use self-closing void elements<br />like word<wbr />break and images: ` +
|
|
15
|
+
`<img src="hi.jpg" /></p>`
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should allow attribute names starting with ":" after element names', async () => {
|
|
20
|
+
const { code } = await renderMarkdown(`<div :class="open ? '' : 'hidden'">Test</div>`, {});
|
|
21
|
+
|
|
22
|
+
chai.expect(code.trim()).to.equal(`<div :class="open ? '' : 'hidden'">Test</div>`);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should allow attribute names starting with ":" after local element names', async () => {
|
|
26
|
+
const { code } = await renderMarkdown(`<div.abc :class="open ? '' : 'hidden'">x</div.abc>`, {});
|
|
27
|
+
|
|
28
|
+
chai.expect(code.trim()).to.equal(`<div.abc :class="open ? '' : 'hidden'">x</div.abc>`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should allow attribute names starting with ":" after attribute names', async () => {
|
|
32
|
+
const { code } = await renderMarkdown(`<input type="text" disabled :placeholder="hi">`, {});
|
|
33
|
+
|
|
34
|
+
chai.expect(code.trim()).to.equal(`<input type="text" disabled :placeholder="hi" />`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should allow attribute names starting with ":" after local attribute names', async () => {
|
|
38
|
+
const { code } = await renderMarkdown(
|
|
39
|
+
`<input type="text" x-test:disabled :placeholder="hi">`,
|
|
40
|
+
{}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
chai.expect(code.trim()).to.equal(`<input type="text" x-test:disabled :placeholder="hi" />`);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should allow attribute names starting with ":" after attribute values', async () => {
|
|
47
|
+
const { code } = await renderMarkdown(`<input type="text" :placeholder="placeholder">`, {});
|
|
48
|
+
|
|
49
|
+
chai.expect(code.trim()).to.equal(`<input type="text" :placeholder="placeholder" />`);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should allow attribute names starting with "@" after element names', async () => {
|
|
53
|
+
const { code } = await renderMarkdown(`<button @click="handleClick">Test</button>`, {});
|
|
54
|
+
|
|
55
|
+
chai.expect(code.trim()).to.equal(`<button @click="handleClick">Test</button>`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should allow attribute names starting with "@" after local element names', async () => {
|
|
59
|
+
const { code } = await renderMarkdown(
|
|
60
|
+
`<button.local @click="handleClick">Test</button.local>`,
|
|
61
|
+
{}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
chai.expect(code.trim()).to.equal(`<button.local @click="handleClick">Test</button.local>`);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should allow attribute names starting with "@" after attribute names', async () => {
|
|
68
|
+
const { code } = await renderMarkdown(
|
|
69
|
+
`<button disabled @click="handleClick">Test</button>`,
|
|
70
|
+
{}
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
chai.expect(code.trim()).to.equal(`<button disabled @click="handleClick">Test</button>`);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should allow attribute names starting with "@" after local attribute names', async () => {
|
|
77
|
+
const { code } = await renderMarkdown(
|
|
78
|
+
`<button x-test:disabled @click="handleClick">Test</button>`,
|
|
79
|
+
{}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
chai.expect(code.trim()).to.equal(`<button x-test:disabled @click="handleClick">Test</button>`);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should allow attribute names starting with "@" after attribute values', async () => {
|
|
86
|
+
const { code } = await renderMarkdown(
|
|
87
|
+
`<button type="submit" @click="handleClick">Test</button>`,
|
|
88
|
+
{}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
chai.expect(code.trim()).to.equal(`<button type="submit" @click="handleClick">Test</button>`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should allow attribute names containing dots', async () => {
|
|
95
|
+
const { code } = await renderMarkdown(`<input x-on:input.debounce.500ms="fetchResults">`, {});
|
|
96
|
+
|
|
97
|
+
chai.expect(code.trim()).to.equal(`<input x-on:input.debounce.500ms="fetchResults" />`);
|
|
98
|
+
});
|
|
99
|
+
});
|