@astrojs/markdown-remark 0.11.7 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +49 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +12 -11
- package/dist/mdxjs.js +8 -5
- package/dist/rehype-collect-headings.d.ts +5 -0
- package/dist/{rehype-collect-headers.js → rehype-collect-headings.js} +11 -8
- package/dist/rehype-escape.d.ts +1 -0
- package/dist/rehype-escape.js +6 -2
- package/dist/remark-prism.js +5 -35
- package/dist/remark-shiki.js +16 -4
- package/dist/types.d.ts +3 -2
- package/package.json +3 -8
- package/src/index.ts +20 -19
- package/src/{rehype-collect-headers.ts → rehype-collect-headings.ts} +12 -8
- package/src/rehype-escape.ts +5 -1
- package/src/remark-prism.ts +2 -43
- package/src/remark-shiki.ts +1 -0
- package/src/types.ts +3 -2
- package/test/autolinking.test.js +88 -72
- package/test/components.test.js +12 -10
- package/test/entities.test.js +12 -0
- package/test/expressions.test.js +17 -15
- package/test/strictness.test.js +15 -16
- package/dist/rehype-collect-headers.d.ts +0 -5
- package/dist/ssr-utils.d.ts +0 -2
- package/dist/ssr-utils.js +0 -8
- package/src/ssr-utils.ts +0 -8
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
[
|
|
2
|
-
[
|
|
3
|
-
[
|
|
4
|
-
[
|
|
5
|
-
[
|
|
1
|
+
[35m@astrojs/markdown-remark:build: [0mcache hit, replaying output [2m6f4b2d3bfc1168f3[0m
|
|
2
|
+
[35m@astrojs/markdown-remark:build: [0m
|
|
3
|
+
[35m@astrojs/markdown-remark:build: [0m> @astrojs/markdown-remark@0.14.0 build /home/runner/work/astro/astro/packages/markdown/remark
|
|
4
|
+
[35m@astrojs/markdown-remark:build: [0m> astro-scripts build "src/**/*.ts" && tsc -p tsconfig.json
|
|
5
|
+
[35m@astrojs/markdown-remark:build: [0m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# @astrojs/markdown-remark
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#4114](https://github.com/withastro/astro/pull/4114) [`64432bcb8`](https://github.com/withastro/astro/commit/64432bcb873efd0e4297c00fc9583a1fe516dfe7) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Refactor `@astrojs/mdx` and `@astrojs/markdown-remark` to use `@astrojs/prism` instead of duplicating the code
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`64432bcb8`](https://github.com/withastro/astro/commit/64432bcb873efd0e4297c00fc9583a1fe516dfe7)]:
|
|
12
|
+
- @astrojs/prism@0.7.0
|
|
13
|
+
|
|
14
|
+
## 0.13.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- [`ba11b3399`](https://github.com/withastro/astro/commit/ba11b33996d79c32da947986edb0f32dbcc04aaf) Thanks [@RafidMuhymin](https://github.com/RafidMuhymin)! - fixed generated slugs in markdown that ends with a dash
|
|
19
|
+
|
|
20
|
+
* [#4016](https://github.com/withastro/astro/pull/4016) [`00fab4ce1`](https://github.com/withastro/astro/commit/00fab4ce135eb799cac69140403d7724686733d6) Thanks [@bholmesdev](https://github.com/bholmesdev)! - The use of components and JSX expressions in Markdown are no longer supported by default.
|
|
21
|
+
|
|
22
|
+
For long term support, migrate to the `@astrojs/mdx` integration for MDX support (including `.mdx` pages!).
|
|
23
|
+
|
|
24
|
+
Not ready to migrate to MDX? Add the legacy flag to your Astro config to re-enable the previous Markdown support.
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
// https://astro.build/config
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
legacy: {
|
|
30
|
+
astroFlavoredMarkdown: true,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- [#4031](https://github.com/withastro/astro/pull/4031) [`6e27a5fdc`](https://github.com/withastro/astro/commit/6e27a5fdc21276cad26cd50e16a2709a40a7cbac) Thanks [@natemoo-re](https://github.com/natemoo-re)! - **BREAKING** Renamed Markdown utility function `getHeaders()` to `getHeadings()`.
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- [#4008](https://github.com/withastro/astro/pull/4008) [`399d7e269`](https://github.com/withastro/astro/commit/399d7e269834d11c046b390705a9a53d3738f3cf) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Avoid parsing JSX, components, and Astro islands when using "plain" md mode. This brings `markdown.mode: 'md'` in-line with our docs description.
|
|
40
|
+
|
|
41
|
+
## 0.12.0
|
|
42
|
+
|
|
43
|
+
### Minor Changes
|
|
44
|
+
|
|
45
|
+
- [#3924](https://github.com/withastro/astro/pull/3924) [`07fb544da`](https://github.com/withastro/astro/commit/07fb544dab142a3d4bb9d0d878aab34eaea447b2) Thanks [@FredKSchott](https://github.com/FredKSchott)! - Remove unused ssr-utils file
|
|
46
|
+
|
|
47
|
+
### Patch Changes
|
|
48
|
+
|
|
49
|
+
- Updated dependencies [[`31f9c0bf0`](https://github.com/withastro/astro/commit/31f9c0bf029ffa4b470e620f2c32e1370643e81e)]:
|
|
50
|
+
- @astrojs/prism@0.6.1
|
|
51
|
+
|
|
3
52
|
## 0.11.7
|
|
4
53
|
|
|
5
54
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export * from './types.js';
|
|
|
3
3
|
export declare const DEFAULT_REMARK_PLUGINS: string[];
|
|
4
4
|
export declare const DEFAULT_REHYPE_PLUGINS: never[];
|
|
5
5
|
/** Shared utility for rendering markdown */
|
|
6
|
-
export declare function renderMarkdown(content: string, opts
|
|
6
|
+
export declare function renderMarkdown(content: string, opts: MarkdownRenderingOptions): Promise<MarkdownRenderingResult>;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadPlugins } from "./load-plugins.js";
|
|
2
|
-
import
|
|
2
|
+
import createCollectHeadings from "./rehype-collect-headings.js";
|
|
3
3
|
import rehypeEscape from "./rehype-escape.js";
|
|
4
4
|
import rehypeExpressions from "./rehype-expressions.js";
|
|
5
5
|
import rehypeIslands from "./rehype-islands.js";
|
|
@@ -20,21 +20,20 @@ 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
|
-
async function renderMarkdown(content, opts
|
|
23
|
+
async function renderMarkdown(content, opts) {
|
|
24
24
|
var _a;
|
|
25
25
|
let {
|
|
26
26
|
fileURL,
|
|
27
|
-
mode = "mdx",
|
|
28
27
|
syntaxHighlight = "shiki",
|
|
29
28
|
shikiConfig = {},
|
|
30
29
|
remarkPlugins = [],
|
|
31
|
-
rehypePlugins = []
|
|
30
|
+
rehypePlugins = [],
|
|
31
|
+
isAstroFlavoredMd = false
|
|
32
32
|
} = opts;
|
|
33
33
|
const input = new VFile({ value: content, path: fileURL });
|
|
34
34
|
const scopedClassName = (_a = opts.$) == null ? void 0 : _a.scopedClassName;
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
let parser = unified().use(markdown).use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : []).use([remarkUnwrap, remarkEscape]);
|
|
35
|
+
const { headings, rehypeCollectHeadings } = createCollectHeadings();
|
|
36
|
+
let parser = unified().use(markdown).use(isAstroFlavoredMd ? [remarkMdxish, remarkMarkAndUnravel, remarkUnwrap, remarkEscape] : []);
|
|
38
37
|
if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
|
|
39
38
|
remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
|
|
40
39
|
rehypePlugins = [...DEFAULT_REHYPE_PLUGINS];
|
|
@@ -57,20 +56,22 @@ async function renderMarkdown(content, opts = {}) {
|
|
|
57
56
|
markdownToHtml,
|
|
58
57
|
{
|
|
59
58
|
allowDangerousHtml: true,
|
|
60
|
-
passThrough: [
|
|
59
|
+
passThrough: isAstroFlavoredMd ? [
|
|
61
60
|
"raw",
|
|
62
61
|
"mdxFlowExpression",
|
|
63
62
|
"mdxJsxFlowElement",
|
|
64
63
|
"mdxJsxTextElement",
|
|
65
64
|
"mdxTextExpression"
|
|
66
|
-
]
|
|
65
|
+
] : []
|
|
67
66
|
}
|
|
68
67
|
]
|
|
69
68
|
]);
|
|
70
69
|
loadedRehypePlugins.forEach(([plugin, pluginOpts]) => {
|
|
71
70
|
parser.use([[plugin, pluginOpts]]);
|
|
72
71
|
});
|
|
73
|
-
parser.use(
|
|
72
|
+
parser.use(
|
|
73
|
+
isAstroFlavoredMd ? [rehypeJsx, rehypeExpressions, rehypeEscape, rehypeIslands, rehypeCollectHeadings] : [rehypeCollectHeadings, rehypeRaw]
|
|
74
|
+
).use(rehypeStringify, { allowDangerousHtml: true });
|
|
74
75
|
let result;
|
|
75
76
|
try {
|
|
76
77
|
const vfile = await parser.process(input);
|
|
@@ -81,7 +82,7 @@ async function renderMarkdown(content, opts = {}) {
|
|
|
81
82
|
throw err;
|
|
82
83
|
}
|
|
83
84
|
return {
|
|
84
|
-
metadata: {
|
|
85
|
+
metadata: { headings, source: content, html: result.toString() },
|
|
85
86
|
code: result.toString()
|
|
86
87
|
};
|
|
87
88
|
}
|
package/dist/mdxjs.js
CHANGED
|
@@ -5,11 +5,14 @@ import { mdxExpression } from "micromark-extension-mdx-expression";
|
|
|
5
5
|
import { mdxMd } from "micromark-extension-mdx-md";
|
|
6
6
|
import { combineExtensions } from "micromark-util-combine-extensions";
|
|
7
7
|
function mdxjs(options) {
|
|
8
|
-
const settings = Object.assign(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const settings = Object.assign(
|
|
9
|
+
{
|
|
10
|
+
acorn: Parser.extend(acornJsx()),
|
|
11
|
+
acornOptions: { ecmaVersion: 2020, sourceType: "module" },
|
|
12
|
+
addResult: true
|
|
13
|
+
},
|
|
14
|
+
options
|
|
15
|
+
);
|
|
13
16
|
return combineExtensions([mdxExpression(settings), mdxJsx(settings), mdxMd]);
|
|
14
17
|
}
|
|
15
18
|
export {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import Slugger from "github-slugger";
|
|
2
2
|
import { toHtml } from "hast-util-to-html";
|
|
3
3
|
import { visit } from "unist-util-visit";
|
|
4
|
-
function
|
|
5
|
-
const
|
|
4
|
+
function createCollectHeadings() {
|
|
5
|
+
const headings = [];
|
|
6
6
|
const slugger = new Slugger();
|
|
7
|
-
function
|
|
7
|
+
function rehypeCollectHeadings() {
|
|
8
8
|
return function(tree) {
|
|
9
9
|
visit(tree, (node) => {
|
|
10
10
|
if (node.type !== "element")
|
|
@@ -44,18 +44,21 @@ function createCollectHeaders() {
|
|
|
44
44
|
node.type = "raw";
|
|
45
45
|
node.value = `<${node.tagName} id={${node.properties.id}}>${raw}</${node.tagName}>`;
|
|
46
46
|
} else {
|
|
47
|
-
|
|
47
|
+
let slug = slugger.slug(text);
|
|
48
|
+
if (slug.endsWith("-"))
|
|
49
|
+
slug = slug.slice(0, -1);
|
|
50
|
+
node.properties.id = slug;
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
|
-
|
|
53
|
+
headings.push({ depth, slug: node.properties.id, text });
|
|
51
54
|
});
|
|
52
55
|
};
|
|
53
56
|
}
|
|
54
57
|
return {
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
headings,
|
|
59
|
+
rehypeCollectHeadings
|
|
57
60
|
};
|
|
58
61
|
}
|
|
59
62
|
export {
|
|
60
|
-
|
|
63
|
+
createCollectHeadings as default
|
|
61
64
|
};
|
package/dist/rehype-escape.d.ts
CHANGED
package/dist/rehype-escape.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { visit } from "unist-util-visit";
|
|
2
|
+
function escapeEntities(value) {
|
|
3
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4
|
+
}
|
|
2
5
|
function rehypeEscape() {
|
|
3
6
|
return function(node) {
|
|
4
7
|
return visit(node, "element", (el) => {
|
|
5
8
|
if (el.tagName === "code" || el.tagName === "pre") {
|
|
6
9
|
el.properties["is:raw"] = true;
|
|
7
10
|
visit(el, "raw", (raw) => {
|
|
8
|
-
raw.value = raw.value
|
|
11
|
+
raw.value = escapeEntities(raw.value);
|
|
9
12
|
});
|
|
10
13
|
}
|
|
11
14
|
return el;
|
|
@@ -13,5 +16,6 @@ function rehypeEscape() {
|
|
|
13
16
|
};
|
|
14
17
|
}
|
|
15
18
|
export {
|
|
16
|
-
rehypeEscape as default
|
|
19
|
+
rehypeEscape as default,
|
|
20
|
+
escapeEntities
|
|
17
21
|
};
|
package/dist/remark-prism.js
CHANGED
|
@@ -1,49 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import Prism from "prismjs";
|
|
3
|
-
import loadLanguages from "prismjs/components/index.js";
|
|
1
|
+
import { runHighlighterWithAstro } from "@astrojs/prism/dist/highlighter";
|
|
4
2
|
import { visit } from "unist-util-visit";
|
|
5
3
|
const noVisit = /* @__PURE__ */ new Set(["root", "html", "text"]);
|
|
6
|
-
const languageMap = /* @__PURE__ */ new Map([["ts", "typescript"]]);
|
|
7
|
-
function runHighlighter(lang, code) {
|
|
8
|
-
let classLanguage = `language-${lang}`;
|
|
9
|
-
if (lang == null) {
|
|
10
|
-
lang = "plaintext";
|
|
11
|
-
}
|
|
12
|
-
const ensureLoaded = (language) => {
|
|
13
|
-
if (language && !Prism.languages[language]) {
|
|
14
|
-
loadLanguages([language]);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
if (languageMap.has(lang)) {
|
|
18
|
-
ensureLoaded(languageMap.get(lang));
|
|
19
|
-
} else if (lang === "astro") {
|
|
20
|
-
ensureLoaded("typescript");
|
|
21
|
-
addAstro(Prism);
|
|
22
|
-
} else {
|
|
23
|
-
ensureLoaded("markup-templating");
|
|
24
|
-
ensureLoaded(lang);
|
|
25
|
-
}
|
|
26
|
-
if (lang && !Prism.languages[lang]) {
|
|
27
|
-
console.warn(`Unable to load the language: ${lang}`);
|
|
28
|
-
}
|
|
29
|
-
const grammar = Prism.languages[lang];
|
|
30
|
-
let html = code;
|
|
31
|
-
if (grammar) {
|
|
32
|
-
html = Prism.highlight(code, grammar, lang);
|
|
33
|
-
}
|
|
34
|
-
return { classLanguage, html };
|
|
35
|
-
}
|
|
36
4
|
function transformer(className) {
|
|
37
5
|
return function(tree) {
|
|
38
6
|
const visitor = (node) => {
|
|
39
7
|
let { lang, value } = node;
|
|
40
8
|
node.type = "html";
|
|
41
|
-
let { html, classLanguage } =
|
|
9
|
+
let { html, classLanguage } = runHighlighterWithAstro(lang, value);
|
|
42
10
|
let classes = [classLanguage];
|
|
43
11
|
if (className) {
|
|
44
12
|
classes.push(className);
|
|
45
13
|
}
|
|
46
|
-
node.value = `<pre class="${classes.join(
|
|
14
|
+
node.value = `<pre class="${classes.join(
|
|
15
|
+
" "
|
|
16
|
+
)}"><code is:raw class="${classLanguage}">${html}</code></pre>`;
|
|
47
17
|
return node;
|
|
48
18
|
};
|
|
49
19
|
return visit(tree, "code", visitor);
|
package/dist/remark-shiki.js
CHANGED
|
@@ -27,15 +27,27 @@ const remarkShiki = async ({ langs = [], theme = "github-dark", wrap = false },
|
|
|
27
27
|
lang = "plaintext";
|
|
28
28
|
}
|
|
29
29
|
let html = highlighter.codeToHtml(node.value, { lang });
|
|
30
|
-
html = html.replace(
|
|
31
|
-
|
|
30
|
+
html = html.replace(
|
|
31
|
+
'<pre class="shiki"',
|
|
32
|
+
`<pre is:raw class="astro-code${scopedClassName ? " " + scopedClassName : ""}"`
|
|
33
|
+
);
|
|
34
|
+
html = html.replace(
|
|
35
|
+
/style="(background-)?color: var\(--shiki-/g,
|
|
36
|
+
'style="$1color: var(--astro-code-'
|
|
37
|
+
);
|
|
32
38
|
if (node.lang === "diff") {
|
|
33
|
-
html = html.replace(
|
|
39
|
+
html = html.replace(
|
|
40
|
+
/<span class="line"><span style="(.*?)">([\+|\-])/g,
|
|
41
|
+
'<span class="line"><span style="$1"><span style="user-select: none;">$2</span>'
|
|
42
|
+
);
|
|
34
43
|
}
|
|
35
44
|
if (wrap === false) {
|
|
36
45
|
html = html.replace(/style="(.*?)"/, 'style="$1; overflow-x: auto;"');
|
|
37
46
|
} else if (wrap === true) {
|
|
38
|
-
html = html.replace(
|
|
47
|
+
html = html.replace(
|
|
48
|
+
/style="(.*?)"/,
|
|
49
|
+
'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
|
|
50
|
+
);
|
|
39
51
|
}
|
|
40
52
|
if (scopedClassName) {
|
|
41
53
|
html = html.replace(/\<span class="line"\>/g, `<span class="line ${scopedClassName}"`);
|
package/dist/types.d.ts
CHANGED
|
@@ -27,14 +27,15 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
|
|
|
27
27
|
$?: {
|
|
28
28
|
scopedClassName: string | null;
|
|
29
29
|
};
|
|
30
|
+
isAstroFlavoredMd?: boolean;
|
|
30
31
|
}
|
|
31
|
-
export interface
|
|
32
|
+
export interface MarkdownHeading {
|
|
32
33
|
depth: number;
|
|
33
34
|
slug: string;
|
|
34
35
|
text: string;
|
|
35
36
|
}
|
|
36
37
|
export interface MarkdownMetadata {
|
|
37
|
-
|
|
38
|
+
headings: MarkdownHeading[];
|
|
38
39
|
source: string;
|
|
39
40
|
html: string;
|
|
40
41
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/markdown-remark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "withastro",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,23 +13,19 @@
|
|
|
13
13
|
"homepage": "https://astro.build",
|
|
14
14
|
"main": "./dist/index.js",
|
|
15
15
|
"exports": {
|
|
16
|
-
".": "./dist/index.js"
|
|
17
|
-
"./ssr-utils": "./dist/ssr-utils.js"
|
|
16
|
+
".": "./dist/index.js"
|
|
18
17
|
},
|
|
19
18
|
"dependencies": {
|
|
20
19
|
"@astrojs/micromark-extension-mdx-jsx": "^1.0.3",
|
|
21
|
-
"@astrojs/prism": "^0.
|
|
20
|
+
"@astrojs/prism": "^0.7.0",
|
|
22
21
|
"acorn": "^8.7.1",
|
|
23
22
|
"acorn-jsx": "^5.3.2",
|
|
24
|
-
"assert": "^2.0.0",
|
|
25
23
|
"github-slugger": "^1.4.0",
|
|
26
24
|
"mdast-util-mdx-expression": "^1.2.1",
|
|
27
25
|
"mdast-util-mdx-jsx": "^1.2.0",
|
|
28
|
-
"mdast-util-to-string": "^3.1.0",
|
|
29
26
|
"micromark-extension-mdx-expression": "^1.0.3",
|
|
30
27
|
"micromark-extension-mdx-md": "^1.0.0",
|
|
31
28
|
"micromark-util-combine-extensions": "^1.0.0",
|
|
32
|
-
"prismjs": "^1.28.0",
|
|
33
29
|
"rehype-raw": "^6.1.1",
|
|
34
30
|
"rehype-stringify": "^9.0.3",
|
|
35
31
|
"remark-gfm": "^3.0.1",
|
|
@@ -48,7 +44,6 @@
|
|
|
48
44
|
"@types/hast": "^2.3.4",
|
|
49
45
|
"@types/mdast": "^3.0.10",
|
|
50
46
|
"@types/mocha": "^9.1.1",
|
|
51
|
-
"@types/prismjs": "^1.26.0",
|
|
52
47
|
"@types/unist": "^2.0.6",
|
|
53
48
|
"astro-scripts": "0.0.6",
|
|
54
49
|
"chai": "^4.3.6",
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MarkdownRenderingOptions, MarkdownRenderingResult } from './types';
|
|
2
2
|
|
|
3
3
|
import { loadPlugins } from './load-plugins.js';
|
|
4
|
-
import
|
|
4
|
+
import createCollectHeadings from './rehype-collect-headings.js';
|
|
5
5
|
import rehypeEscape from './rehype-escape.js';
|
|
6
6
|
import rehypeExpressions from './rehype-expressions.js';
|
|
7
7
|
import rehypeIslands from './rehype-islands.js';
|
|
@@ -29,25 +29,23 @@ export const DEFAULT_REHYPE_PLUGINS = [];
|
|
|
29
29
|
/** Shared utility for rendering markdown */
|
|
30
30
|
export async function renderMarkdown(
|
|
31
31
|
content: string,
|
|
32
|
-
opts: MarkdownRenderingOptions
|
|
32
|
+
opts: MarkdownRenderingOptions
|
|
33
33
|
): Promise<MarkdownRenderingResult> {
|
|
34
34
|
let {
|
|
35
35
|
fileURL,
|
|
36
|
-
mode = 'mdx',
|
|
37
36
|
syntaxHighlight = 'shiki',
|
|
38
37
|
shikiConfig = {},
|
|
39
38
|
remarkPlugins = [],
|
|
40
39
|
rehypePlugins = [],
|
|
40
|
+
isAstroFlavoredMd = false,
|
|
41
41
|
} = opts;
|
|
42
42
|
const input = new VFile({ value: content, path: fileURL });
|
|
43
43
|
const scopedClassName = opts.$?.scopedClassName;
|
|
44
|
-
const
|
|
45
|
-
const { headers, rehypeCollectHeaders } = createCollectHeaders();
|
|
44
|
+
const { headings, rehypeCollectHeadings } = createCollectHeadings();
|
|
46
45
|
|
|
47
46
|
let parser = unified()
|
|
48
47
|
.use(markdown)
|
|
49
|
-
.use(
|
|
50
|
-
.use([remarkUnwrap, remarkEscape]);
|
|
48
|
+
.use(isAstroFlavoredMd ? [remarkMdxish, remarkMarkAndUnravel, remarkUnwrap, remarkEscape] : []);
|
|
51
49
|
|
|
52
50
|
if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
|
|
53
51
|
remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
|
|
@@ -76,13 +74,15 @@ export async function renderMarkdown(
|
|
|
76
74
|
markdownToHtml as any,
|
|
77
75
|
{
|
|
78
76
|
allowDangerousHtml: true,
|
|
79
|
-
passThrough:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
passThrough: isAstroFlavoredMd
|
|
78
|
+
? [
|
|
79
|
+
'raw',
|
|
80
|
+
'mdxFlowExpression',
|
|
81
|
+
'mdxJsxFlowElement',
|
|
82
|
+
'mdxJsxTextElement',
|
|
83
|
+
'mdxTextExpression',
|
|
84
|
+
]
|
|
85
|
+
: [],
|
|
86
86
|
},
|
|
87
87
|
],
|
|
88
88
|
]);
|
|
@@ -92,10 +92,11 @@ export async function renderMarkdown(
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
parser
|
|
95
|
-
.use(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
.use(
|
|
96
|
+
isAstroFlavoredMd
|
|
97
|
+
? [rehypeJsx, rehypeExpressions, rehypeEscape, rehypeIslands, rehypeCollectHeadings]
|
|
98
|
+
: [rehypeCollectHeadings, rehypeRaw]
|
|
99
|
+
)
|
|
99
100
|
.use(rehypeStringify, { allowDangerousHtml: true });
|
|
100
101
|
|
|
101
102
|
let result: string;
|
|
@@ -112,7 +113,7 @@ export async function renderMarkdown(
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
return {
|
|
115
|
-
metadata: {
|
|
116
|
+
metadata: { headings, source: content, html: result.toString() },
|
|
116
117
|
code: result.toString(),
|
|
117
118
|
};
|
|
118
119
|
}
|
|
@@ -2,13 +2,13 @@ import Slugger from 'github-slugger';
|
|
|
2
2
|
import { toHtml } from 'hast-util-to-html';
|
|
3
3
|
import { visit } from 'unist-util-visit';
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { MarkdownHeading, RehypePlugin } from './types.js';
|
|
6
6
|
|
|
7
|
-
export default function
|
|
8
|
-
const
|
|
7
|
+
export default function createCollectHeadings() {
|
|
8
|
+
const headings: MarkdownHeading[] = [];
|
|
9
9
|
const slugger = new Slugger();
|
|
10
10
|
|
|
11
|
-
function
|
|
11
|
+
function rehypeCollectHeadings(): ReturnType<RehypePlugin> {
|
|
12
12
|
return function (tree) {
|
|
13
13
|
visit(tree, (node) => {
|
|
14
14
|
if (node.type !== 'element') return;
|
|
@@ -53,17 +53,21 @@ export default function createCollectHeaders() {
|
|
|
53
53
|
node as any
|
|
54
54
|
).value = `<${node.tagName} id={${node.properties.id}}>${raw}</${node.tagName}>`;
|
|
55
55
|
} else {
|
|
56
|
-
|
|
56
|
+
let slug = slugger.slug(text);
|
|
57
|
+
|
|
58
|
+
if (slug.endsWith('-')) slug = slug.slice(0, -1);
|
|
59
|
+
|
|
60
|
+
node.properties.id = slug;
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
headings.push({ depth, slug: node.properties.id, text });
|
|
61
65
|
});
|
|
62
66
|
};
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
return {
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
headings,
|
|
71
|
+
rehypeCollectHeadings,
|
|
68
72
|
};
|
|
69
73
|
}
|
package/src/rehype-escape.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { visit } from 'unist-util-visit';
|
|
2
2
|
|
|
3
|
+
export function escapeEntities(value: string): string {
|
|
4
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
5
|
+
}
|
|
6
|
+
|
|
3
7
|
export default function rehypeEscape(): any {
|
|
4
8
|
return function (node: any): any {
|
|
5
9
|
return visit(node, 'element', (el) => {
|
|
@@ -8,7 +12,7 @@ export default function rehypeEscape(): any {
|
|
|
8
12
|
// Visit all raw children and escape HTML tags to prevent Markdown code
|
|
9
13
|
// like "This is a `<script>` tag" from actually opening a script tag
|
|
10
14
|
visit(el, 'raw', (raw) => {
|
|
11
|
-
raw.value = raw.value
|
|
15
|
+
raw.value = escapeEntities(raw.value);
|
|
12
16
|
});
|
|
13
17
|
}
|
|
14
18
|
return el;
|
package/src/remark-prism.ts
CHANGED
|
@@ -1,48 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import Prism from 'prismjs';
|
|
3
|
-
import loadLanguages from 'prismjs/components/index.js';
|
|
1
|
+
import { runHighlighterWithAstro } from '@astrojs/prism/dist/highlighter';
|
|
4
2
|
import { visit } from 'unist-util-visit';
|
|
5
3
|
const noVisit = new Set(['root', 'html', 'text']);
|
|
6
4
|
|
|
7
|
-
const languageMap = new Map([['ts', 'typescript']]);
|
|
8
|
-
|
|
9
|
-
function runHighlighter(lang: string, code: string) {
|
|
10
|
-
let classLanguage = `language-${lang}`;
|
|
11
|
-
|
|
12
|
-
if (lang == null) {
|
|
13
|
-
lang = 'plaintext';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const ensureLoaded = (language: string) => {
|
|
17
|
-
if (language && !Prism.languages[language]) {
|
|
18
|
-
loadLanguages([language]);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
if (languageMap.has(lang)) {
|
|
23
|
-
ensureLoaded(languageMap.get(lang)!);
|
|
24
|
-
} else if (lang === 'astro') {
|
|
25
|
-
ensureLoaded('typescript');
|
|
26
|
-
addAstro(Prism);
|
|
27
|
-
} else {
|
|
28
|
-
ensureLoaded('markup-templating'); // Prism expects this to exist for a number of other langs
|
|
29
|
-
ensureLoaded(lang);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (lang && !Prism.languages[lang]) {
|
|
33
|
-
// eslint-disable-next-line no-console
|
|
34
|
-
console.warn(`Unable to load the language: ${lang}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const grammar = Prism.languages[lang];
|
|
38
|
-
let html = code;
|
|
39
|
-
if (grammar) {
|
|
40
|
-
html = Prism.highlight(code, grammar, lang);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return { classLanguage, html };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
5
|
type MaybeString = string | null | undefined;
|
|
47
6
|
|
|
48
7
|
/** */
|
|
@@ -52,7 +11,7 @@ function transformer(className: MaybeString) {
|
|
|
52
11
|
let { lang, value } = node;
|
|
53
12
|
node.type = 'html';
|
|
54
13
|
|
|
55
|
-
let { html, classLanguage } =
|
|
14
|
+
let { html, classLanguage } = runHighlighterWithAstro(lang, value);
|
|
56
15
|
let classes = [classLanguage];
|
|
57
16
|
if (className) {
|
|
58
17
|
classes.push(className);
|
package/src/remark-shiki.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -41,16 +41,17 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
|
|
|
41
41
|
$?: {
|
|
42
42
|
scopedClassName: string | null;
|
|
43
43
|
};
|
|
44
|
+
isAstroFlavoredMd?: boolean;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
export interface
|
|
47
|
+
export interface MarkdownHeading {
|
|
47
48
|
depth: number;
|
|
48
49
|
slug: string;
|
|
49
50
|
text: string;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export interface MarkdownMetadata {
|
|
53
|
-
|
|
54
|
+
headings: MarkdownHeading[];
|
|
54
55
|
source: string;
|
|
55
56
|
html: string;
|
|
56
57
|
}
|
package/test/autolinking.test.js
CHANGED
|
@@ -2,91 +2,107 @@ import { renderMarkdown } from '../dist/index.js';
|
|
|
2
2
|
import chai from 'chai';
|
|
3
3
|
|
|
4
4
|
describe('autolinking', () => {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
describe('plain md', () => {
|
|
6
|
+
it('autolinks URLs starting with a protocol in plain text', async () => {
|
|
7
|
+
const { code } = await renderMarkdown(`See https://example.com for more.`, {});
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
chai
|
|
10
|
+
.expect(code.replace(/\n/g, ''))
|
|
11
|
+
.to.equal(`<p>See <a href="https://example.com">https://example.com</a> for more.</p>`);
|
|
12
|
+
});
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
it('autolinks URLs starting with "www." in plain text', async () => {
|
|
15
|
+
const { code } = await renderMarkdown(`See www.example.com for more.`, {});
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
chai
|
|
18
|
+
.expect(code.trim())
|
|
19
|
+
.to.equal(`<p>See <a href="http://www.example.com">www.example.com</a> for more.</p>`);
|
|
20
|
+
});
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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>`
|
|
22
|
+
it('does not autolink URLs in code blocks', async () => {
|
|
23
|
+
const { code } = await renderMarkdown(
|
|
24
|
+
'See `https://example.com` or `www.example.com` for more.',
|
|
25
|
+
{}
|
|
32
26
|
);
|
|
27
|
+
|
|
28
|
+
chai
|
|
29
|
+
.expect(code.trim())
|
|
30
|
+
.to.equal(
|
|
31
|
+
`<p>See <code>https://example.com</code> or ` +
|
|
32
|
+
`<code>www.example.com</code> for more.</p>`
|
|
33
|
+
);
|
|
34
|
+
});
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
'Example:\n```\nGo to https://example.com or www.example.com now.\n```',
|
|
38
|
-
{}
|
|
39
|
-
);
|
|
37
|
+
describe('astro-flavored md', () => {
|
|
38
|
+
const renderAstroMd = (text) => renderMarkdown(text, { isAstroFlavoredMd: true });
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
it('does not autolink URLs in code blocks', async () => {
|
|
41
|
+
const { code } = await renderAstroMd(
|
|
42
|
+
'See `https://example.com` or `www.example.com` for more.',
|
|
43
|
+
{}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
chai
|
|
47
|
+
.expect(code.trim())
|
|
48
|
+
.to.equal(
|
|
49
|
+
`<p>See <code is:raw>https://example.com</code> or ` +
|
|
50
|
+
`<code is:raw>www.example.com</code> for more.</p>`
|
|
51
|
+
);
|
|
52
|
+
});
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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>`
|
|
54
|
+
it('does not autolink URLs in fenced code blocks', async () => {
|
|
55
|
+
const { code } = await renderAstroMd(
|
|
56
|
+
'Example:\n```\nGo to https://example.com or www.example.com now.\n```'
|
|
59
57
|
);
|
|
60
|
-
});
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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>`
|
|
59
|
+
chai
|
|
60
|
+
.expect(code)
|
|
61
|
+
.to.contain(`<pre is:raw`)
|
|
62
|
+
.to.contain(`Go to https://example.com or www.example.com now.`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('does not autolink URLs starting with a protocol when nested inside links', async () => {
|
|
66
|
+
const { code } = await renderAstroMd(
|
|
67
|
+
`See [http://example.com](http://example.com) or ` +
|
|
68
|
+
`<a test href="https://example.com">https://example.com</a>`
|
|
74
69
|
);
|
|
75
|
-
});
|
|
76
70
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`</strong></a>`
|
|
71
|
+
chai
|
|
72
|
+
.expect(code.replace(/\n/g, ''))
|
|
73
|
+
.to.equal(
|
|
74
|
+
`<p>See <a href="http://example.com">http://example.com</a> or ` +
|
|
75
|
+
`<a test href="https://example.com">https://example.com</a></p>`
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('does not autolink URLs starting with "www." when nested inside links', async () => {
|
|
80
|
+
const { code } = await renderAstroMd(
|
|
81
|
+
`See [www.example.com](https://www.example.com) or ` +
|
|
82
|
+
`<a test href="https://www.example.com">www.example.com</a>`
|
|
90
83
|
);
|
|
84
|
+
|
|
85
|
+
chai
|
|
86
|
+
.expect(code.replace(/\n/g, ''))
|
|
87
|
+
.to.equal(
|
|
88
|
+
`<p>See <a href="https://www.example.com">www.example.com</a> or ` +
|
|
89
|
+
`<a test href="https://www.example.com">www.example.com</a></p>`
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('does not autolink URLs when nested several layers deep inside links', async () => {
|
|
94
|
+
const { code } = await renderAstroMd(
|
|
95
|
+
`<a href="https://www.example.com">**Visit _our www.example.com or ` +
|
|
96
|
+
`http://localhost pages_ for more!**</a>`
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
chai
|
|
100
|
+
.expect(code.replace(/\n/g, ''))
|
|
101
|
+
.to.equal(
|
|
102
|
+
`<a href="https://www.example.com"><strong>` +
|
|
103
|
+
`Visit <em>our www.example.com or http://localhost pages</em> for more!` +
|
|
104
|
+
`</strong></a>`
|
|
105
|
+
);
|
|
106
|
+
});
|
|
91
107
|
});
|
|
92
108
|
});
|
package/test/components.test.js
CHANGED
|
@@ -2,32 +2,34 @@ import { renderMarkdown } from '../dist/index.js';
|
|
|
2
2
|
import chai from 'chai';
|
|
3
3
|
|
|
4
4
|
describe('components', () => {
|
|
5
|
+
const renderAstroMd = (text) => renderMarkdown(text, { isAstroFlavoredMd: true });
|
|
6
|
+
|
|
5
7
|
it('should be able to serialize string', async () => {
|
|
6
|
-
const { code } = await
|
|
8
|
+
const { code } = await renderAstroMd(`<Component str="cool!" />`);
|
|
7
9
|
|
|
8
10
|
chai.expect(code).to.equal(`<Component str="cool!" />`);
|
|
9
11
|
});
|
|
10
12
|
|
|
11
13
|
it('should be able to serialize boolean attribute', async () => {
|
|
12
|
-
const { code } = await
|
|
14
|
+
const { code } = await renderAstroMd(`<Component bool={true} />`);
|
|
13
15
|
|
|
14
16
|
chai.expect(code).to.equal(`<Component bool={true} />`);
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
it('should be able to serialize array', async () => {
|
|
18
|
-
const { code } = await
|
|
20
|
+
const { code } = await renderAstroMd(`<Component prop={["a", "b", "c"]} />`);
|
|
19
21
|
|
|
20
22
|
chai.expect(code).to.equal(`<Component prop={["a", "b", "c"]} />`);
|
|
21
23
|
});
|
|
22
24
|
|
|
23
25
|
it('should be able to serialize object', async () => {
|
|
24
|
-
const { code } = await
|
|
26
|
+
const { code } = await renderAstroMd(`<Component prop={{ a: 0, b: 1, c: 2 }} />`);
|
|
25
27
|
|
|
26
28
|
chai.expect(code).to.equal(`<Component prop={{ a: 0, b: 1, c: 2 }} />`);
|
|
27
29
|
});
|
|
28
30
|
|
|
29
31
|
it('should be able to serialize empty attribute', async () => {
|
|
30
|
-
const { code } = await
|
|
32
|
+
const { code } = await renderAstroMd(`<Component empty />`);
|
|
31
33
|
|
|
32
34
|
chai.expect(code).to.equal(`<Component empty />`);
|
|
33
35
|
});
|
|
@@ -35,25 +37,25 @@ describe('components', () => {
|
|
|
35
37
|
// Notable omission: shorthand attribute
|
|
36
38
|
|
|
37
39
|
it('should be able to serialize spread attribute', async () => {
|
|
38
|
-
const { code } = await
|
|
40
|
+
const { code } = await renderAstroMd(`<Component {...spread} />`);
|
|
39
41
|
|
|
40
42
|
chai.expect(code).to.equal(`<Component {...spread} />`);
|
|
41
43
|
});
|
|
42
44
|
|
|
43
45
|
it('should allow client:* directives', async () => {
|
|
44
|
-
const { code } = await
|
|
46
|
+
const { code } = await renderAstroMd(`<Component client:load />`);
|
|
45
47
|
|
|
46
48
|
chai.expect(code).to.equal(`<Component client:load />`);
|
|
47
49
|
});
|
|
48
50
|
|
|
49
51
|
it('should normalize children', async () => {
|
|
50
|
-
const { code } = await
|
|
52
|
+
const { code } = await renderAstroMd(`<Component bool={true}>Hello world!</Component>`);
|
|
51
53
|
|
|
52
54
|
chai.expect(code).to.equal(`<Component bool={true}>Hello world!</Component>`);
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
it('should be able to nest components', async () => {
|
|
56
|
-
const { code } = await
|
|
58
|
+
const { code } = await renderAstroMd(
|
|
57
59
|
`<Component bool={true}><Component>Hello world!</Component></Component>`,
|
|
58
60
|
{}
|
|
59
61
|
);
|
|
@@ -64,7 +66,7 @@ describe('components', () => {
|
|
|
64
66
|
});
|
|
65
67
|
|
|
66
68
|
it('should allow markdown without many spaces', async () => {
|
|
67
|
-
const { code } = await
|
|
69
|
+
const { code } = await renderAstroMd(
|
|
68
70
|
`<Component>
|
|
69
71
|
# Hello world!
|
|
70
72
|
</Component>`,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { renderMarkdown } from '../dist/index.js';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
|
|
4
|
+
describe('entities', () => {
|
|
5
|
+
const renderAstroMd = (text) => renderMarkdown(text, { isAstroFlavoredMd: false });
|
|
6
|
+
|
|
7
|
+
it('should not unescape entities', async () => {
|
|
8
|
+
const { code } = await renderAstroMd(`<i>This should NOT be italic</i>`);
|
|
9
|
+
|
|
10
|
+
expect(code).to.equal(`<p><i>This should NOT be italic</i></p>`);
|
|
11
|
+
});
|
|
12
|
+
});
|
package/test/expressions.test.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { renderMarkdown } from '../dist/index.js';
|
|
2
|
-
import chai
|
|
2
|
+
import chai from 'chai';
|
|
3
3
|
|
|
4
4
|
describe('expressions', () => {
|
|
5
|
+
const renderAstroMd = (text, opts) => renderMarkdown(text, { isAstroFlavoredMd: true, ...opts });
|
|
6
|
+
|
|
5
7
|
it('should be able to serialize bare expression', async () => {
|
|
6
|
-
const { code } = await
|
|
8
|
+
const { code } = await renderAstroMd(`{a}`, {});
|
|
7
9
|
|
|
8
10
|
chai.expect(code).to.equal(`{a}`);
|
|
9
11
|
});
|
|
10
12
|
|
|
11
13
|
it('should be able to serialize expression inside component', async () => {
|
|
12
|
-
const { code } = await
|
|
14
|
+
const { code } = await renderAstroMd(`<Component>{a}</Component>`, {});
|
|
13
15
|
|
|
14
16
|
chai.expect(code).to.equal(`<Component>{a}</Component>`);
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
it('should be able to serialize expression inside markdown', async () => {
|
|
18
|
-
const { code } = await
|
|
20
|
+
const { code } = await renderAstroMd(`# {frontmatter.title}`, {});
|
|
19
21
|
|
|
20
22
|
chai
|
|
21
23
|
.expect(code)
|
|
@@ -23,7 +25,7 @@ describe('expressions', () => {
|
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
it('should be able to serialize complex expression inside markdown', async () => {
|
|
26
|
-
const { code } = await
|
|
28
|
+
const { code } = await renderAstroMd(`# Hello {frontmatter.name}`, {});
|
|
27
29
|
|
|
28
30
|
chai
|
|
29
31
|
.expect(code)
|
|
@@ -31,7 +33,7 @@ describe('expressions', () => {
|
|
|
31
33
|
});
|
|
32
34
|
|
|
33
35
|
it('should be able to serialize complex expression with markup inside markdown', async () => {
|
|
34
|
-
const { code } = await
|
|
36
|
+
const { code } = await renderAstroMd(`# Hello <span>{frontmatter.name}</span>`, {});
|
|
35
37
|
|
|
36
38
|
chai
|
|
37
39
|
.expect(code)
|
|
@@ -41,7 +43,7 @@ describe('expressions', () => {
|
|
|
41
43
|
});
|
|
42
44
|
|
|
43
45
|
it('should be able to avoid evaluating JSX-like expressions in an inline code & generate a slug for id', async () => {
|
|
44
|
-
const { code } = await
|
|
46
|
+
const { code } = await renderAstroMd(`# \`{frontmatter.title}\``, {});
|
|
45
47
|
|
|
46
48
|
chai
|
|
47
49
|
.expect(code)
|
|
@@ -49,17 +51,17 @@ describe('expressions', () => {
|
|
|
49
51
|
});
|
|
50
52
|
|
|
51
53
|
it('should be able to avoid evaluating JSX-like expressions in inline codes', async () => {
|
|
52
|
-
const { code } = await
|
|
54
|
+
const { code } = await renderAstroMd(`# \`{ foo }\` is a shorthand for \`{ foo: foo }\``, {});
|
|
53
55
|
|
|
54
56
|
chai
|
|
55
57
|
.expect(code)
|
|
56
58
|
.to.equal(
|
|
57
|
-
'<h1 id="-foo--is-a-shorthand-for--foo-foo
|
|
59
|
+
'<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
60
|
);
|
|
59
61
|
});
|
|
60
62
|
|
|
61
63
|
it('should be able to avoid evaluating JSX-like expressions & escape HTML tag characters in inline codes', async () => {
|
|
62
|
-
const { code } = await
|
|
64
|
+
const { code } = await renderAstroMd(
|
|
63
65
|
`###### \`{}\` is equivalent to \`Record<never, never>\` <small>(at TypeScript v{frontmatter.version})</small>`,
|
|
64
66
|
{}
|
|
65
67
|
);
|
|
@@ -72,7 +74,7 @@ describe('expressions', () => {
|
|
|
72
74
|
});
|
|
73
75
|
|
|
74
76
|
it('should be able to encode ampersand characters in code blocks', async () => {
|
|
75
|
-
const { code } = await
|
|
77
|
+
const { code } = await renderAstroMd(
|
|
76
78
|
'The ampersand in ` ` must be encoded in code blocks.',
|
|
77
79
|
{}
|
|
78
80
|
);
|
|
@@ -85,7 +87,7 @@ describe('expressions', () => {
|
|
|
85
87
|
});
|
|
86
88
|
|
|
87
89
|
it('should be able to encode ampersand characters in fenced code blocks', async () => {
|
|
88
|
-
const { code } = await
|
|
90
|
+
const { code } = await renderAstroMd(`
|
|
89
91
|
\`\`\`md
|
|
90
92
|
The ampersand in \` \` must be encoded in code blocks.
|
|
91
93
|
\`\`\`
|
|
@@ -95,7 +97,7 @@ describe('expressions', () => {
|
|
|
95
97
|
});
|
|
96
98
|
|
|
97
99
|
it('should be able to serialize function expression', async () => {
|
|
98
|
-
const { code } = await
|
|
100
|
+
const { code } = await renderAstroMd(
|
|
99
101
|
`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`,
|
|
100
102
|
{}
|
|
101
103
|
);
|
|
@@ -104,13 +106,13 @@ describe('expressions', () => {
|
|
|
104
106
|
});
|
|
105
107
|
|
|
106
108
|
it('should unwrap HTML comments in inline code blocks', async () => {
|
|
107
|
-
const { code } = await
|
|
109
|
+
const { code } = await renderAstroMd(`\`{/*<!-- HTML comment -->*/}\``);
|
|
108
110
|
|
|
109
111
|
chai.expect(code).to.equal('<p><code is:raw><!-- HTML comment --></code></p>');
|
|
110
112
|
});
|
|
111
113
|
|
|
112
114
|
it('should unwrap HTML comments in code fences', async () => {
|
|
113
|
-
const { code } = await
|
|
115
|
+
const { code } = await renderAstroMd(
|
|
114
116
|
`
|
|
115
117
|
\`\`\`
|
|
116
118
|
<!-- HTML comment -->
|
package/test/strictness.test.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { renderMarkdown } from '../dist/index.js';
|
|
2
2
|
import chai from 'chai';
|
|
3
3
|
|
|
4
|
-
describe('strictness', () => {
|
|
4
|
+
describe('strictness in Astro-flavored markdown', () => {
|
|
5
|
+
const renderAstroMd = (text, opts) => renderMarkdown(text, { isAstroFlavoredMd: true, ...opts });
|
|
6
|
+
|
|
5
7
|
it('should allow self-closing HTML tags (void elements)', async () => {
|
|
6
|
-
const { code } = await
|
|
8
|
+
const { code } = await renderAstroMd(
|
|
7
9
|
`Use self-closing void elements<br>like word<wbr>break and images: <img src="hi.jpg">`,
|
|
8
10
|
{}
|
|
9
11
|
);
|
|
@@ -17,25 +19,25 @@ describe('strictness', () => {
|
|
|
17
19
|
});
|
|
18
20
|
|
|
19
21
|
it('should allow attribute names starting with ":" after element names', async () => {
|
|
20
|
-
const { code } = await
|
|
22
|
+
const { code } = await renderAstroMd(`<div :class="open ? '' : 'hidden'">Test</div>`, {});
|
|
21
23
|
|
|
22
24
|
chai.expect(code.trim()).to.equal(`<div :class="open ? '' : 'hidden'">Test</div>`);
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
it('should allow attribute names starting with ":" after local element names', async () => {
|
|
26
|
-
const { code } = await
|
|
28
|
+
const { code } = await renderAstroMd(`<div.abc :class="open ? '' : 'hidden'">x</div.abc>`, {});
|
|
27
29
|
|
|
28
30
|
chai.expect(code.trim()).to.equal(`<div.abc :class="open ? '' : 'hidden'">x</div.abc>`);
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
it('should allow attribute names starting with ":" after attribute names', async () => {
|
|
32
|
-
const { code } = await
|
|
34
|
+
const { code } = await renderAstroMd(`<input type="text" disabled :placeholder="hi">`, {});
|
|
33
35
|
|
|
34
36
|
chai.expect(code.trim()).to.equal(`<input type="text" disabled :placeholder="hi" />`);
|
|
35
37
|
});
|
|
36
38
|
|
|
37
39
|
it('should allow attribute names starting with ":" after local attribute names', async () => {
|
|
38
|
-
const { code } = await
|
|
40
|
+
const { code } = await renderAstroMd(
|
|
39
41
|
`<input type="text" x-test:disabled :placeholder="hi">`,
|
|
40
42
|
{}
|
|
41
43
|
);
|
|
@@ -44,19 +46,19 @@ describe('strictness', () => {
|
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
it('should allow attribute names starting with ":" after attribute values', async () => {
|
|
47
|
-
const { code } = await
|
|
49
|
+
const { code } = await renderAstroMd(`<input type="text" :placeholder="placeholder">`, {});
|
|
48
50
|
|
|
49
51
|
chai.expect(code.trim()).to.equal(`<input type="text" :placeholder="placeholder" />`);
|
|
50
52
|
});
|
|
51
53
|
|
|
52
54
|
it('should allow attribute names starting with "@" after element names', async () => {
|
|
53
|
-
const { code } = await
|
|
55
|
+
const { code } = await renderAstroMd(`<button @click="handleClick">Test</button>`, {});
|
|
54
56
|
|
|
55
57
|
chai.expect(code.trim()).to.equal(`<button @click="handleClick">Test</button>`);
|
|
56
58
|
});
|
|
57
59
|
|
|
58
60
|
it('should allow attribute names starting with "@" after local element names', async () => {
|
|
59
|
-
const { code } = await
|
|
61
|
+
const { code } = await renderAstroMd(
|
|
60
62
|
`<button.local @click="handleClick">Test</button.local>`,
|
|
61
63
|
{}
|
|
62
64
|
);
|
|
@@ -65,16 +67,13 @@ describe('strictness', () => {
|
|
|
65
67
|
});
|
|
66
68
|
|
|
67
69
|
it('should allow attribute names starting with "@" after attribute names', async () => {
|
|
68
|
-
const { code } = await
|
|
69
|
-
`<button disabled @click="handleClick">Test</button>`,
|
|
70
|
-
{}
|
|
71
|
-
);
|
|
70
|
+
const { code } = await renderAstroMd(`<button disabled @click="handleClick">Test</button>`, {});
|
|
72
71
|
|
|
73
72
|
chai.expect(code.trim()).to.equal(`<button disabled @click="handleClick">Test</button>`);
|
|
74
73
|
});
|
|
75
74
|
|
|
76
75
|
it('should allow attribute names starting with "@" after local attribute names', async () => {
|
|
77
|
-
const { code } = await
|
|
76
|
+
const { code } = await renderAstroMd(
|
|
78
77
|
`<button x-test:disabled @click="handleClick">Test</button>`,
|
|
79
78
|
{}
|
|
80
79
|
);
|
|
@@ -83,7 +82,7 @@ describe('strictness', () => {
|
|
|
83
82
|
});
|
|
84
83
|
|
|
85
84
|
it('should allow attribute names starting with "@" after attribute values', async () => {
|
|
86
|
-
const { code } = await
|
|
85
|
+
const { code } = await renderAstroMd(
|
|
87
86
|
`<button type="submit" @click="handleClick">Test</button>`,
|
|
88
87
|
{}
|
|
89
88
|
);
|
|
@@ -92,7 +91,7 @@ describe('strictness', () => {
|
|
|
92
91
|
});
|
|
93
92
|
|
|
94
93
|
it('should allow attribute names containing dots', async () => {
|
|
95
|
-
const { code } = await
|
|
94
|
+
const { code } = await renderAstroMd(`<input x-on:input.debounce.500ms="fetchResults">`, {});
|
|
96
95
|
|
|
97
96
|
chai.expect(code.trim()).to.equal(`<input x-on:input.debounce.500ms="fetchResults" />`);
|
|
98
97
|
});
|
package/dist/ssr-utils.d.ts
DELETED
package/dist/ssr-utils.js
DELETED
package/src/ssr-utils.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
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
|
-
}
|