@astrojs/markdown-remark 0.11.0 → 0.11.3
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.d.ts +0 -1
- package/dist/index.js +13 -18
- package/dist/mdxjs.d.ts +3 -0
- package/dist/mdxjs.js +17 -0
- package/dist/rehype-collect-headers.js +3 -3
- package/dist/rehype-escape.js +1 -1
- package/dist/rehype-islands.js +1 -1
- package/dist/rehype-jsx.d.ts +2 -1
- package/dist/rehype-jsx.js +17 -14
- package/dist/remark-escape.d.ts +1 -0
- package/dist/remark-escape.js +13 -0
- package/dist/remark-mdxish.d.ts +1 -1
- package/dist/remark-mdxish.js +8 -5
- package/dist/remark-prism.js +2 -2
- package/dist/remark-unwrap.js +3 -3
- package/dist/ssr-utils.d.ts +2 -0
- package/dist/ssr-utils.js +8 -0
- package/dist/types.d.ts +2 -2
- package/package.json +10 -4
- package/src/index.ts +14 -20
- package/src/mdxjs.ts +27 -0
- package/src/rehype-collect-headers.ts +3 -3
- package/src/rehype-escape.ts +2 -2
- package/src/rehype-islands.ts +3 -3
- package/src/rehype-jsx.ts +31 -14
- package/src/remark-escape.ts +15 -0
- package/src/remark-mdxish.ts +11 -6
- package/src/remark-prism.ts +2 -2
- package/src/remark-unwrap.ts +4 -4
- package/src/ssr-utils.ts +8 -0
- package/src/types.ts +2 -2
- package/test/autolinking.test.js +92 -0
- package/test/components.test.js +3 -3
- package/test/expressions.test.js +43 -2
- package/test/strictness.test.js +81 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @astrojs/markdown-remark
|
|
2
2
|
|
|
3
|
+
## 0.11.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#3638](https://github.com/withastro/astro/pull/3638) [`80c71c7c`](https://github.com/withastro/astro/commit/80c71c7c56d15dc05ec0c5a848130aad222d7d51) Thanks [@tony-sull](https://github.com/tony-sull)! - Fix: HTML comments in markdown code blocks should not be wrapped in JS comments
|
|
8
|
+
|
|
9
|
+
* [#3612](https://github.com/withastro/astro/pull/3612) [`fca58cfd`](https://github.com/withastro/astro/commit/fca58cfd91b68501ec82350ab023170b208d8ce7) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Fix: "vpath" import error when building for netlify edge
|
|
10
|
+
|
|
11
|
+
- [#3630](https://github.com/withastro/astro/pull/3630) [`48e67fe0`](https://github.com/withastro/astro/commit/48e67fe05398dc4b1fca12db36c1b37bb341277a) Thanks [@tony-sull](https://github.com/tony-sull)! - Encodes ampersand characters in code blocks
|
|
12
|
+
|
|
13
|
+
* [#3620](https://github.com/withastro/astro/pull/3620) [`05aa7244`](https://github.com/withastro/astro/commit/05aa72442cd4512b94abdb39623e8caa2c1839b0) Thanks [@hippotastic](https://github.com/hippotastic)! - Remove extra newlines around Markdown components
|
|
14
|
+
|
|
15
|
+
## 0.11.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#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
|
|
20
|
+
|
|
21
|
+
## 0.11.1
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- [#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
|
|
26
|
+
|
|
27
|
+
* [#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
|
|
28
|
+
|
|
3
29
|
## 0.11.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,5 @@ import type { MarkdownRenderingOptions, MarkdownRenderingResult } from './types'
|
|
|
2
2
|
export * from './types.js';
|
|
3
3
|
export declare const DEFAULT_REMARK_PLUGINS: string[];
|
|
4
4
|
export declare const DEFAULT_REHYPE_PLUGINS: never[];
|
|
5
|
-
export declare function slug(value: string): string;
|
|
6
5
|
/** Shared utility for rendering markdown */
|
|
7
6
|
export declare function renderMarkdown(content: string, opts?: MarkdownRenderingOptions): Promise<MarkdownRenderingResult>;
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
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 remarkEscape from "./remark-escape.js";
|
|
8
|
+
import remarkMarkAndUnravel from "./remark-mark-and-unravel.js";
|
|
9
|
+
import remarkMdxish from "./remark-mdxish.js";
|
|
9
10
|
import remarkPrism from "./remark-prism.js";
|
|
11
|
+
import scopedStyles from "./remark-scoped-styles.js";
|
|
10
12
|
import remarkShiki from "./remark-shiki.js";
|
|
11
13
|
import remarkUnwrap from "./remark-unwrap.js";
|
|
12
|
-
import
|
|
13
|
-
import
|
|
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"];
|
|
22
22
|
const DEFAULT_REHYPE_PLUGINS = [];
|
|
23
|
-
const slugger = new Slugger();
|
|
24
|
-
function slug(value) {
|
|
25
|
-
return slugger.slug(value);
|
|
26
|
-
}
|
|
27
23
|
async function renderMarkdown(content, opts = {}) {
|
|
28
24
|
var _a;
|
|
29
25
|
let {
|
|
@@ -38,7 +34,7 @@ async function renderMarkdown(content, opts = {}) {
|
|
|
38
34
|
const scopedClassName = (_a = opts.$) == null ? void 0 : _a.scopedClassName;
|
|
39
35
|
const isMDX = mode === "mdx";
|
|
40
36
|
const { headers, rehypeCollectHeaders } = createCollectHeaders();
|
|
41
|
-
let parser = unified().use(markdown).use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : []).use([remarkUnwrap]);
|
|
37
|
+
let parser = unified().use(markdown).use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : []).use([remarkUnwrap, remarkEscape]);
|
|
42
38
|
if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
|
|
43
39
|
remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
|
|
44
40
|
rehypePlugins = [...DEFAULT_REHYPE_PLUGINS];
|
|
@@ -74,10 +70,10 @@ async function renderMarkdown(content, opts = {}) {
|
|
|
74
70
|
loadedRehypePlugins.forEach(([plugin, opts2]) => {
|
|
75
71
|
parser.use([[plugin, opts2]]);
|
|
76
72
|
});
|
|
77
|
-
parser.use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw]).use(rehypeEscape).use(rehypeIslands);
|
|
73
|
+
parser.use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw]).use(rehypeEscape).use(rehypeIslands).use([rehypeCollectHeaders]).use(rehypeStringify, { allowDangerousHtml: true });
|
|
78
74
|
let result;
|
|
79
75
|
try {
|
|
80
|
-
const vfile = await parser.
|
|
76
|
+
const vfile = await parser.process(input);
|
|
81
77
|
result = vfile.toString();
|
|
82
78
|
} catch (err) {
|
|
83
79
|
err = prefixError(err, `Failed to parse Markdown file "${input.path}"`);
|
|
@@ -109,6 +105,5 @@ ${err.message}`;
|
|
|
109
105
|
export {
|
|
110
106
|
DEFAULT_REHYPE_PLUGINS,
|
|
111
107
|
DEFAULT_REMARK_PLUGINS,
|
|
112
|
-
renderMarkdown
|
|
113
|
-
slug
|
|
108
|
+
renderMarkdown
|
|
114
109
|
};
|
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,6 +1,6 @@
|
|
|
1
|
-
import { visit } from "unist-util-visit";
|
|
2
|
-
import { toHtml } from "hast-util-to-html";
|
|
3
1
|
import Slugger from "github-slugger";
|
|
2
|
+
import { toHtml } from "hast-util-to-html";
|
|
3
|
+
import { visit } from "unist-util-visit";
|
|
4
4
|
function createCollectHeaders() {
|
|
5
5
|
const headers = [];
|
|
6
6
|
const slugger = new Slugger();
|
|
@@ -23,7 +23,7 @@ function createCollectHeaders() {
|
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
if (child.type === "raw") {
|
|
26
|
-
if (child.value.
|
|
26
|
+
if (child.value.match(/^\n?<.*>\n?$/)) {
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
}
|
package/dist/rehype-escape.js
CHANGED
|
@@ -5,7 +5,7 @@ function rehypeEscape() {
|
|
|
5
5
|
if (el.tagName === "code" || el.tagName === "pre") {
|
|
6
6
|
el.properties["is:raw"] = true;
|
|
7
7
|
visit(el, "raw", (raw) => {
|
|
8
|
-
raw.value = raw.value.replace(/</g, "<").replace(/>/g, ">");
|
|
8
|
+
raw.value = raw.value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
return el;
|
package/dist/rehype-islands.js
CHANGED
|
@@ -22,7 +22,7 @@ const visit = _visit;
|
|
|
22
22
|
function rehypeIslands() {
|
|
23
23
|
return function(node) {
|
|
24
24
|
return visit(node, "element", (el) => {
|
|
25
|
-
if (el.tagName == "astro-
|
|
25
|
+
if (el.tagName == "astro-island") {
|
|
26
26
|
visit(el, "text", (child, index, parent) => {
|
|
27
27
|
if (child.type === "text") {
|
|
28
28
|
if (parent && child.value.indexOf("<!--") > -1 && index != null) {
|
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,28 @@ 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
|
-
value:
|
|
35
|
-
<${child.name}${attrs}>`
|
|
39
|
+
value: `<${node.name}${attrs}>`
|
|
36
40
|
};
|
|
37
41
|
const closingTag = {
|
|
38
42
|
type: "raw",
|
|
39
|
-
value: `</${
|
|
40
|
-
`
|
|
43
|
+
value: `</${node.name}>`
|
|
41
44
|
};
|
|
42
|
-
parent.children.splice(index, 1, openingTag, ...
|
|
45
|
+
parent.children.splice(index, 1, openingTag, ...node.children, closingTag);
|
|
43
46
|
});
|
|
44
47
|
};
|
|
45
48
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function remarkEscape(): (tree: any) => void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { visit } from "unist-util-visit";
|
|
2
|
+
function remarkEscape() {
|
|
3
|
+
return (tree) => {
|
|
4
|
+
visit(tree, "code", removeCommentWrapper);
|
|
5
|
+
visit(tree, "inlineCode", removeCommentWrapper);
|
|
6
|
+
};
|
|
7
|
+
function removeCommentWrapper(node) {
|
|
8
|
+
node.value = node.value.replace(/{\/\*<!--/gs, "<!--").replace(/-->\*\/}/gs, "-->");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
remarkEscape as default
|
|
13
|
+
};
|
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,10 +1,13 @@
|
|
|
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);
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { visit as _visit
|
|
1
|
+
import { SKIP, visit as _visit } from "unist-util-visit";
|
|
2
2
|
const visit = _visit;
|
|
3
3
|
function remarkUnwrap() {
|
|
4
4
|
const astroRootNodes = /* @__PURE__ */ new Set();
|
|
@@ -7,10 +7,10 @@ function remarkUnwrap() {
|
|
|
7
7
|
insideAstroRoot = false;
|
|
8
8
|
astroRootNodes.clear();
|
|
9
9
|
visit(tree, "html", (node) => {
|
|
10
|
-
if (node.value.indexOf("<astro-
|
|
10
|
+
if (node.value.indexOf("<astro-island") > -1 && !insideAstroRoot) {
|
|
11
11
|
insideAstroRoot = true;
|
|
12
12
|
}
|
|
13
|
-
if (node.value.indexOf("</astro-
|
|
13
|
+
if (node.value.indexOf("</astro-island") > -1 && insideAstroRoot) {
|
|
14
14
|
insideAstroRoot = false;
|
|
15
15
|
}
|
|
16
16
|
astroRootNodes.add(node);
|
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.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "withastro",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,17 +13,22 @@
|
|
|
13
13
|
"homepage": "https://astro.build",
|
|
14
14
|
"main": "./dist/index.js",
|
|
15
15
|
"exports": {
|
|
16
|
-
".": "./dist/index.js"
|
|
16
|
+
".": "./dist/index.js",
|
|
17
|
+
"./ssr-utils": "./dist/ssr-utils.js"
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
20
|
+
"@astrojs/micromark-extension-mdx-jsx": "^1.0.3",
|
|
19
21
|
"@astrojs/prism": "^0.4.1",
|
|
22
|
+
"acorn": "^8.7.1",
|
|
23
|
+
"acorn-jsx": "^5.3.2",
|
|
20
24
|
"assert": "^2.0.0",
|
|
21
25
|
"github-slugger": "^1.4.0",
|
|
22
26
|
"mdast-util-mdx-expression": "^1.2.0",
|
|
23
27
|
"mdast-util-mdx-jsx": "^1.2.0",
|
|
24
28
|
"mdast-util-to-string": "^3.1.0",
|
|
25
|
-
"micromark-extension-mdx-
|
|
26
|
-
"micromark-extension-
|
|
29
|
+
"micromark-extension-mdx-expression": "^1.0.3",
|
|
30
|
+
"micromark-extension-mdx-md": "^1.0.0",
|
|
31
|
+
"micromark-util-combine-extensions": "^1.0.0",
|
|
27
32
|
"prismjs": "^1.28.0",
|
|
28
33
|
"rehype-raw": "^6.1.1",
|
|
29
34
|
"rehype-stringify": "^9.0.3",
|
|
@@ -47,6 +52,7 @@
|
|
|
47
52
|
"@types/unist": "^2.0.6",
|
|
48
53
|
"astro-scripts": "0.0.4",
|
|
49
54
|
"chai": "^4.3.6",
|
|
55
|
+
"micromark-util-types": "^1.0.2",
|
|
50
56
|
"mocha": "^9.2.2"
|
|
51
57
|
},
|
|
52
58
|
"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 remarkEscape from './remark-escape.js';
|
|
10
|
+
import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
|
|
11
|
+
import remarkMdxish from './remark-mdxish.js';
|
|
11
12
|
import remarkPrism from './remark-prism.js';
|
|
13
|
+
import scopedStyles from './remark-scoped-styles.js';
|
|
12
14
|
import remarkShiki from './remark-shiki.js';
|
|
13
15
|
import remarkUnwrap from './remark-unwrap.js';
|
|
14
|
-
import { loadPlugins } from './load-plugins.js';
|
|
15
16
|
|
|
16
|
-
import
|
|
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';
|
|
@@ -26,11 +26,6 @@ export * from './types.js';
|
|
|
26
26
|
export const DEFAULT_REMARK_PLUGINS = ['remark-gfm', 'remark-smartypants'];
|
|
27
27
|
export const DEFAULT_REHYPE_PLUGINS = [];
|
|
28
28
|
|
|
29
|
-
const slugger = new Slugger();
|
|
30
|
-
export function slug(value: string): string {
|
|
31
|
-
return slugger.slug(value);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
29
|
/** Shared utility for rendering markdown */
|
|
35
30
|
export async function renderMarkdown(
|
|
36
31
|
content: string,
|
|
@@ -52,7 +47,7 @@ export async function renderMarkdown(
|
|
|
52
47
|
let parser = unified()
|
|
53
48
|
.use(markdown)
|
|
54
49
|
.use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : [])
|
|
55
|
-
.use([remarkUnwrap]);
|
|
50
|
+
.use([remarkUnwrap, remarkEscape]);
|
|
56
51
|
|
|
57
52
|
if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
|
|
58
53
|
remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
|
|
@@ -99,14 +94,13 @@ export async function renderMarkdown(
|
|
|
99
94
|
parser
|
|
100
95
|
.use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw])
|
|
101
96
|
.use(rehypeEscape)
|
|
102
|
-
.use(rehypeIslands)
|
|
97
|
+
.use(rehypeIslands)
|
|
98
|
+
.use([rehypeCollectHeaders])
|
|
99
|
+
.use(rehypeStringify, { allowDangerousHtml: true });
|
|
103
100
|
|
|
104
101
|
let result: string;
|
|
105
102
|
try {
|
|
106
|
-
const vfile = await parser
|
|
107
|
-
.use([rehypeCollectHeaders])
|
|
108
|
-
.use(rehypeStringify, { allowDangerousHtml: true })
|
|
109
|
-
.process(input);
|
|
103
|
+
const vfile = await parser.process(input);
|
|
110
104
|
result = vfile.toString();
|
|
111
105
|
} catch (err) {
|
|
112
106
|
// Ensure that the error message contains the input filename
|
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,6 +1,6 @@
|
|
|
1
|
-
import { visit } from 'unist-util-visit';
|
|
2
|
-
import { toHtml } from 'hast-util-to-html';
|
|
3
1
|
import Slugger from 'github-slugger';
|
|
2
|
+
import { toHtml } from 'hast-util-to-html';
|
|
3
|
+
import { visit } from 'unist-util-visit';
|
|
4
4
|
|
|
5
5
|
import type { MarkdownHeader, RehypePlugin } from './types.js';
|
|
6
6
|
|
|
@@ -25,7 +25,7 @@ export default function createCollectHeaders() {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
if (child.type === 'raw') {
|
|
28
|
-
if (child.value.
|
|
28
|
+
if (child.value.match(/^\n?<.*>\n?$/)) {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
}
|
package/src/rehype-escape.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { visit } from 'unist-util-visit';
|
|
2
2
|
|
|
3
3
|
export default function rehypeEscape(): any {
|
|
4
4
|
return function (node: any): any {
|
|
@@ -8,7 +8,7 @@ export default function rehypeEscape(): any {
|
|
|
8
8
|
// Visit all raw children and escape HTML tags to prevent Markdown code
|
|
9
9
|
// like "This is a `<script>` tag" from actually opening a script tag
|
|
10
10
|
visit(el, 'raw', (raw) => {
|
|
11
|
-
raw.value = raw.value.replace(/</g, '<').replace(/>/g, '>');
|
|
11
|
+
raw.value = raw.value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
return el;
|
package/src/rehype-islands.ts
CHANGED
|
@@ -9,14 +9,14 @@ const visit = _visit as (
|
|
|
9
9
|
) => any;
|
|
10
10
|
|
|
11
11
|
// This fixes some confusing bugs coming from somewhere inside of our Markdown pipeline.
|
|
12
|
-
// `unist`/`remark`/`rehype` (not sure) often generate malformed HTML inside of <astro-
|
|
12
|
+
// `unist`/`remark`/`rehype` (not sure) often generate malformed HTML inside of <astro-island>
|
|
13
13
|
// For hydration to work properly, frameworks need the DOM to be the exact same on server/client.
|
|
14
14
|
// This reverts some "helpful corrections" that are applied to our perfectly valid HTML!
|
|
15
15
|
export default function rehypeIslands(): any {
|
|
16
16
|
return function (node: any): any {
|
|
17
17
|
return visit(node, 'element', (el) => {
|
|
18
|
-
// Bugs only happen inside of <astro-
|
|
19
|
-
if (el.tagName == 'astro-
|
|
18
|
+
// Bugs only happen inside of <astro-island> islands
|
|
19
|
+
if (el.tagName == 'astro-island') {
|
|
20
20
|
visit(el, 'text', (child, index, parent) => {
|
|
21
21
|
if (child.type === 'text') {
|
|
22
22
|
// Sometimes comments can be trapped as text, which causes them to be escaped
|
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:
|
|
56
|
+
value: `<${node.name}${attrs}>`,
|
|
40
57
|
};
|
|
41
58
|
const closingTag = {
|
|
42
59
|
type: 'raw',
|
|
43
|
-
value: `</${
|
|
60
|
+
value: `</${node.name}>`,
|
|
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
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Literal } from 'unist';
|
|
2
|
+
import { visit } from 'unist-util-visit';
|
|
3
|
+
|
|
4
|
+
// In code blocks, this removes the JS comment wrapper added to
|
|
5
|
+
// HTML comments by vite-plugin-markdown.
|
|
6
|
+
export default function remarkEscape() {
|
|
7
|
+
return (tree: any) => {
|
|
8
|
+
visit(tree, 'code', removeCommentWrapper);
|
|
9
|
+
visit(tree, 'inlineCode', removeCommentWrapper);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function removeCommentWrapper(node: Literal<string>) {
|
|
13
|
+
node.value = node.value.replace(/{\/\*<!--/gs, '<!--').replace(/-->\*\/}/gs, '-->');
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/remark-mdxish.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { mdxjs } from 'micromark-extension-mdxjs';
|
|
2
|
-
import { mdxFromMarkdown, mdxToMarkdown } from './mdast-util-mdxish.js';
|
|
3
1
|
import type * as fromMarkdown from 'mdast-util-from-markdown';
|
|
4
2
|
import type { Tag } from 'mdast-util-mdx-jsx';
|
|
3
|
+
import { mdxFromMarkdown, mdxToMarkdown } from './mdast-util-mdxish.js';
|
|
4
|
+
import { mdxjs } from './mdxjs.js';
|
|
5
|
+
|
|
6
|
+
// Prepare markdown extensions once to prevent performance issues
|
|
7
|
+
const extMdxJs = mdxjs({});
|
|
8
|
+
const extMdxFromMarkdown = makeFromMarkdownLessStrict(mdxFromMarkdown());
|
|
9
|
+
const extMdxToMarkdown = mdxToMarkdown();
|
|
5
10
|
|
|
6
|
-
export default function remarkMdxish(this: any
|
|
11
|
+
export default function remarkMdxish(this: any) {
|
|
7
12
|
const data = this.data();
|
|
8
13
|
|
|
9
|
-
add('micromarkExtensions',
|
|
10
|
-
add('fromMarkdownExtensions',
|
|
11
|
-
add('toMarkdownExtensions',
|
|
14
|
+
add('micromarkExtensions', extMdxJs);
|
|
15
|
+
add('fromMarkdownExtensions', extMdxFromMarkdown);
|
|
16
|
+
add('toMarkdownExtensions', extMdxToMarkdown);
|
|
12
17
|
|
|
13
18
|
function add(field: string, value: unknown) {
|
|
14
19
|
const list = data[field] ? data[field] : (data[field] = []);
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { visit as _visit
|
|
1
|
+
import { SKIP, visit as _visit } from 'unist-util-visit';
|
|
2
2
|
|
|
3
3
|
// This is a workaround.
|
|
4
4
|
// It fixes a compatibility issue between different, incompatible ASTs given by plugins to Unist
|
|
@@ -8,7 +8,7 @@ const visit = _visit as (
|
|
|
8
8
|
callback?: (node: any, index: number, parent: any) => any
|
|
9
9
|
) => any;
|
|
10
10
|
|
|
11
|
-
// Remove the wrapping paragraph for <astro-
|
|
11
|
+
// Remove the wrapping paragraph for <astro-island> islands
|
|
12
12
|
export default function remarkUnwrap() {
|
|
13
13
|
const astroRootNodes = new Set();
|
|
14
14
|
let insideAstroRoot = false;
|
|
@@ -19,10 +19,10 @@ export default function remarkUnwrap() {
|
|
|
19
19
|
astroRootNodes.clear();
|
|
20
20
|
|
|
21
21
|
visit(tree, 'html', (node) => {
|
|
22
|
-
if (node.value.indexOf('<astro-
|
|
22
|
+
if (node.value.indexOf('<astro-island') > -1 && !insideAstroRoot) {
|
|
23
23
|
insideAstroRoot = true;
|
|
24
24
|
}
|
|
25
|
-
if (node.value.indexOf('</astro-
|
|
25
|
+
if (node.value.indexOf('</astro-island') > -1 && insideAstroRoot) {
|
|
26
26
|
insideAstroRoot = false;
|
|
27
27
|
}
|
|
28
28
|
astroRootNodes.add(node);
|
package/src/ssr-utils.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Utilities used in deployment-ready SSR bundles */
|
|
2
|
+
import Slugger from 'github-slugger';
|
|
3
|
+
|
|
4
|
+
const slugger = new Slugger();
|
|
5
|
+
/** @see {@link "/packages/astro/vite-plugin-markdown"} */
|
|
6
|
+
export function slug(value: string): string {
|
|
7
|
+
return slugger.slug(value);
|
|
8
|
+
}
|
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/components.test.js
CHANGED
|
@@ -49,7 +49,7 @@ describe('components', () => {
|
|
|
49
49
|
it('should normalize children', async () => {
|
|
50
50
|
const { code } = await renderMarkdown(`<Component bool={true}>Hello world!</Component>`, {});
|
|
51
51
|
|
|
52
|
-
chai.expect(code).to.equal(
|
|
52
|
+
chai.expect(code).to.equal(`<Component bool={true}>Hello world!</Component>`);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
it('should be able to nest components', async () => {
|
|
@@ -60,7 +60,7 @@ describe('components', () => {
|
|
|
60
60
|
|
|
61
61
|
chai
|
|
62
62
|
.expect(code)
|
|
63
|
-
.to.equal(
|
|
63
|
+
.to.equal(`<Component bool={true}><Component>Hello world!</Component></Component>`);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it('should allow markdown without many spaces', async () => {
|
|
@@ -71,6 +71,6 @@ describe('components', () => {
|
|
|
71
71
|
{}
|
|
72
72
|
);
|
|
73
73
|
|
|
74
|
-
chai.expect(code).to.equal(
|
|
74
|
+
chai.expect(code).to.equal(`<Component><h1 id="hello-world">Hello world!</h1></Component>`);
|
|
75
75
|
});
|
|
76
76
|
});
|
package/test/expressions.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { renderMarkdown } from '../dist/index.js';
|
|
2
|
-
import chai from 'chai';
|
|
2
|
+
import chai, { expect } from 'chai';
|
|
3
3
|
|
|
4
4
|
describe('expressions', () => {
|
|
5
5
|
it('should be able to serialize bare expression', async () => {
|
|
@@ -11,7 +11,7 @@ describe('expressions', () => {
|
|
|
11
11
|
it('should be able to serialize expression inside component', async () => {
|
|
12
12
|
const { code } = await renderMarkdown(`<Component>{a}</Component>`, {});
|
|
13
13
|
|
|
14
|
-
chai.expect(code).to.equal(
|
|
14
|
+
chai.expect(code).to.equal(`<Component>{a}</Component>`);
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
it('should be able to serialize expression inside markdown', async () => {
|
|
@@ -71,6 +71,29 @@ describe('expressions', () => {
|
|
|
71
71
|
);
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
+
it('should be able to encode ampersand characters in code blocks', async () => {
|
|
75
|
+
const { code } = await renderMarkdown(
|
|
76
|
+
'The ampersand in ` ` must be encoded in code blocks.',
|
|
77
|
+
{}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
chai
|
|
81
|
+
.expect(code)
|
|
82
|
+
.to.equal(
|
|
83
|
+
'<p>The ampersand in <code is:raw>&nbsp;</code> must be encoded in code blocks.</p>'
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should be able to encode ampersand characters in fenced code blocks', async () => {
|
|
88
|
+
const { code } = await renderMarkdown(`
|
|
89
|
+
\`\`\`md
|
|
90
|
+
The ampersand in \` \` must be encoded in code blocks.
|
|
91
|
+
\`\`\`
|
|
92
|
+
`);
|
|
93
|
+
|
|
94
|
+
chai.expect(code).to.match(/^<pre is:raw.*<code>.*The ampersand in `&nbsp;`/);
|
|
95
|
+
});
|
|
96
|
+
|
|
74
97
|
it('should be able to serialize function expression', async () => {
|
|
75
98
|
const { code } = await renderMarkdown(
|
|
76
99
|
`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`,
|
|
@@ -79,4 +102,22 @@ describe('expressions', () => {
|
|
|
79
102
|
|
|
80
103
|
chai.expect(code).to.equal(`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`);
|
|
81
104
|
});
|
|
105
|
+
|
|
106
|
+
it('should unwrap HTML comments in inline code blocks', async () => {
|
|
107
|
+
const { code } = await renderMarkdown(`\`{/*<!-- HTML comment -->*/}\``);
|
|
108
|
+
|
|
109
|
+
chai.expect(code).to.equal('<p><code is:raw><!-- HTML comment --></code></p>');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should unwrap HTML comments in code fences', async () => {
|
|
113
|
+
const { code } = await renderMarkdown(
|
|
114
|
+
`
|
|
115
|
+
\`\`\`
|
|
116
|
+
<!-- HTML comment -->
|
|
117
|
+
\`\`\`
|
|
118
|
+
`
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
chai.expect(code).to.match(/(?<!{\/\*)<!-- HTML comment -->(?!\*\/})/);
|
|
122
|
+
});
|
|
82
123
|
});
|
package/test/strictness.test.js
CHANGED
|
@@ -15,4 +15,85 @@ describe('strictness', () => {
|
|
|
15
15
|
`<img src="hi.jpg" /></p>`
|
|
16
16
|
);
|
|
17
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
|
+
});
|
|
18
99
|
});
|