@astrojs/markdown-remark 0.12.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- @astrojs/markdown-remark:build: cache hit, replaying output 1546942a008a83f6
1
+ @astrojs/markdown-remark:build: cache hit, replaying output f2df71f430c1c421
2
2
  @astrojs/markdown-remark:build: 
3
- @astrojs/markdown-remark:build: > @astrojs/markdown-remark@0.12.0 build /home/runner/work/astro/astro/packages/markdown/remark
3
+ @astrojs/markdown-remark:build: > @astrojs/markdown-remark@0.14.1 build /home/runner/work/astro/astro/packages/markdown/remark
4
4
  @astrojs/markdown-remark:build: > astro-scripts build "src/**/*.ts" && tsc -p tsconfig.json
5
5
  @astrojs/markdown-remark:build: 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # @astrojs/markdown-remark
2
2
 
3
+ ## 0.14.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4176](https://github.com/withastro/astro/pull/4176) [`2675b8633`](https://github.com/withastro/astro/commit/2675b8633c5d5c45b237ec87940d5eaf1bfb1b4b) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Support frontmatter injection for MD and MDX using remark and rehype plugins
8
+
9
+ * [#4137](https://github.com/withastro/astro/pull/4137) [`471c6f784`](https://github.com/withastro/astro/commit/471c6f784e21399676c8b2002665ffdf83a1c59e) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Speed up internal markdown builds with new vite-plugin markdown
10
+
11
+ - [#4169](https://github.com/withastro/astro/pull/4169) [`16034f0dd`](https://github.com/withastro/astro/commit/16034f0dd5b3683e9e022dbd413e85bd18d2b031) Thanks [@hippotastic](https://github.com/hippotastic)! - Fix double-escaping of non-highlighted code blocks in Astro-flavored markdown
12
+
13
+ ## 0.14.0
14
+
15
+ ### Minor Changes
16
+
17
+ - [#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
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [[`64432bcb8`](https://github.com/withastro/astro/commit/64432bcb873efd0e4297c00fc9583a1fe516dfe7)]:
22
+ - @astrojs/prism@0.7.0
23
+
24
+ ## 0.13.0
25
+
26
+ ### Minor Changes
27
+
28
+ - [`ba11b3399`](https://github.com/withastro/astro/commit/ba11b33996d79c32da947986edb0f32dbcc04aaf) Thanks [@RafidMuhymin](https://github.com/RafidMuhymin)! - fixed generated slugs in markdown that ends with a dash
29
+
30
+ * [#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.
31
+
32
+ For long term support, migrate to the `@astrojs/mdx` integration for MDX support (including `.mdx` pages!).
33
+
34
+ Not ready to migrate to MDX? Add the legacy flag to your Astro config to re-enable the previous Markdown support.
35
+
36
+ ```js
37
+ // https://astro.build/config
38
+ export default defineConfig({
39
+ legacy: {
40
+ astroFlavoredMarkdown: true,
41
+ },
42
+ });
43
+ ```
44
+
45
+ - [#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()`.
46
+
47
+ ### Patch Changes
48
+
49
+ - [#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.
50
+
3
51
  ## 0.12.0
4
52
 
5
53
  ### Minor 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?: MarkdownRenderingOptions): Promise<MarkdownRenderingResult>;
6
+ export declare function renderMarkdown(content: string, opts: MarkdownRenderingOptions): Promise<MarkdownRenderingResult>;
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { loadPlugins } from "./load-plugins.js";
2
- import createCollectHeaders from "./rehype-collect-headers.js";
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";
6
6
  import rehypeJsx from "./rehype-jsx.js";
7
7
  import remarkEscape from "./remark-escape.js";
8
+ import { remarkInitializeAstroData } from "./remark-initialize-astro-data.js";
8
9
  import remarkMarkAndUnravel from "./remark-mark-and-unravel.js";
9
10
  import remarkMdxish from "./remark-mdxish.js";
10
11
  import remarkPrism from "./remark-prism.js";
@@ -20,21 +21,20 @@ import { VFile } from "vfile";
20
21
  export * from "./types.js";
21
22
  const DEFAULT_REMARK_PLUGINS = ["remark-gfm", "remark-smartypants"];
22
23
  const DEFAULT_REHYPE_PLUGINS = [];
23
- async function renderMarkdown(content, opts = {}) {
24
+ async function renderMarkdown(content, opts) {
24
25
  var _a;
25
26
  let {
26
27
  fileURL,
27
- mode = "mdx",
28
28
  syntaxHighlight = "shiki",
29
29
  shikiConfig = {},
30
30
  remarkPlugins = [],
31
- rehypePlugins = []
31
+ rehypePlugins = [],
32
+ isAstroFlavoredMd = false
32
33
  } = opts;
33
34
  const input = new VFile({ value: content, path: fileURL });
34
35
  const scopedClassName = (_a = opts.$) == null ? void 0 : _a.scopedClassName;
35
- const isMDX = mode === "mdx";
36
- const { headers, rehypeCollectHeaders } = createCollectHeaders();
37
- let parser = unified().use(markdown).use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : []).use([remarkUnwrap, remarkEscape]);
36
+ const { headings, rehypeCollectHeadings } = createCollectHeadings();
37
+ let parser = unified().use(markdown).use(remarkInitializeAstroData).use(isAstroFlavoredMd ? [remarkMdxish, remarkMarkAndUnravel, remarkUnwrap, remarkEscape] : []);
38
38
  if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
39
39
  remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
40
40
  rehypePlugins = [...DEFAULT_REHYPE_PLUGINS];
@@ -57,32 +57,34 @@ async function renderMarkdown(content, opts = {}) {
57
57
  markdownToHtml,
58
58
  {
59
59
  allowDangerousHtml: true,
60
- passThrough: [
60
+ passThrough: isAstroFlavoredMd ? [
61
61
  "raw",
62
62
  "mdxFlowExpression",
63
63
  "mdxJsxFlowElement",
64
64
  "mdxJsxTextElement",
65
65
  "mdxTextExpression"
66
- ]
66
+ ] : []
67
67
  }
68
68
  ]
69
69
  ]);
70
70
  loadedRehypePlugins.forEach(([plugin, pluginOpts]) => {
71
71
  parser.use([[plugin, pluginOpts]]);
72
72
  });
73
- parser.use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw]).use(rehypeEscape).use(rehypeIslands).use([rehypeCollectHeaders]).use(rehypeStringify, { allowDangerousHtml: true });
74
- let result;
73
+ parser.use(
74
+ isAstroFlavoredMd ? [rehypeJsx, rehypeExpressions, rehypeEscape, rehypeIslands, rehypeCollectHeadings] : [rehypeCollectHeadings, rehypeRaw]
75
+ ).use(rehypeStringify, { allowDangerousHtml: true });
76
+ let vfile;
75
77
  try {
76
- const vfile = await parser.process(input);
77
- result = vfile.toString();
78
+ vfile = await parser.process(input);
78
79
  } catch (err) {
79
80
  err = prefixError(err, `Failed to parse Markdown file "${input.path}"`);
80
81
  console.error(err);
81
82
  throw err;
82
83
  }
83
84
  return {
84
- metadata: { headers, source: content, html: result.toString() },
85
- code: result.toString()
85
+ metadata: { headings, source: content, html: String(vfile.value) },
86
+ code: String(vfile.value),
87
+ vfile
86
88
  };
87
89
  }
88
90
  function prefixError(err, prefix) {
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
- acorn: Parser.extend(acornJsx()),
10
- acornOptions: { ecmaVersion: 2020, sourceType: "module" },
11
- addResult: true
12
- }, options);
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 {
@@ -0,0 +1,5 @@
1
+ import type { MarkdownHeading, RehypePlugin } from './types.js';
2
+ export default function createCollectHeadings(): {
3
+ headings: MarkdownHeading[];
4
+ rehypeCollectHeadings: () => ReturnType<RehypePlugin>;
5
+ };
@@ -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 createCollectHeaders() {
5
- const headers = [];
4
+ function createCollectHeadings() {
5
+ const headings = [];
6
6
  const slugger = new Slugger();
7
- function rehypeCollectHeaders() {
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
- node.properties.id = slugger.slug(text);
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
- headers.push({ depth, slug: node.properties.id, text });
53
+ headings.push({ depth, slug: node.properties.id, text });
51
54
  });
52
55
  };
53
56
  }
54
57
  return {
55
- headers,
56
- rehypeCollectHeaders
58
+ headings,
59
+ rehypeCollectHeadings
57
60
  };
58
61
  }
59
62
  export {
60
- createCollectHeaders as default
63
+ createCollectHeadings as default
61
64
  };
@@ -1 +1,2 @@
1
+ export declare function escapeEntities(value: string): string;
1
2
  export default function rehypeEscape(): any;
@@ -1,17 +1,21 @@
1
- import { visit } from "unist-util-visit";
1
+ import { SKIP, visit } from "unist-util-visit";
2
+ function escapeEntities(value) {
3
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
11
+ raw.value = escapeEntities(raw.value);
9
12
  });
13
+ return SKIP;
10
14
  }
11
- return el;
12
15
  });
13
16
  };
14
17
  }
15
18
  export {
16
- rehypeEscape as default
19
+ rehypeEscape as default,
20
+ escapeEntities
17
21
  };
@@ -0,0 +1,2 @@
1
+ import type { VFile } from 'vfile';
2
+ export declare function remarkInitializeAstroData(): (tree: any, vfile: VFile) => void;
@@ -0,0 +1,10 @@
1
+ function remarkInitializeAstroData() {
2
+ return function(tree, vfile) {
3
+ if (!vfile.data.astro) {
4
+ vfile.data.astro = { frontmatter: {} };
5
+ }
6
+ };
7
+ }
8
+ export {
9
+ remarkInitializeAstroData
10
+ };
@@ -1,49 +1,19 @@
1
- import { addAstro } from "@astrojs/prism/internal";
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 } = runHighlighter(lang, value);
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(" ")}"><code is:raw class="${classLanguage}">${html}</code></pre>`;
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);
@@ -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('<pre class="shiki"', `<pre is:raw class="astro-code${scopedClassName ? " " + scopedClassName : ""}"`);
31
- html = html.replace(/style="(background-)?color: var\(--shiki-/g, 'style="$1color: var(--astro-code-');
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(/<span class="line"><span style="(.*?)">([\+|\-])/g, '<span class="line"><span style="$1"><span style="user-select: none;">$2</span>');
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(/style="(.*?)"/, 'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"');
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
@@ -2,6 +2,7 @@ import type * as hast from 'hast';
2
2
  import type * as mdast from 'mdast';
3
3
  import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
4
4
  import type * as unified from 'unified';
5
+ import type { VFile } from 'vfile';
5
6
  export type { Node } from 'unist';
6
7
  export declare type RemarkPlugin<PluginParameters extends any[] = any[]> = unified.Plugin<PluginParameters, mdast.Root>;
7
8
  export declare type RemarkPlugins = (string | [string, any] | RemarkPlugin | [RemarkPlugin, any])[];
@@ -27,18 +28,20 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
27
28
  $?: {
28
29
  scopedClassName: string | null;
29
30
  };
31
+ isAstroFlavoredMd?: boolean;
30
32
  }
31
- export interface MarkdownHeader {
33
+ export interface MarkdownHeading {
32
34
  depth: number;
33
35
  slug: string;
34
36
  text: string;
35
37
  }
36
38
  export interface MarkdownMetadata {
37
- headers: MarkdownHeader[];
39
+ headings: MarkdownHeading[];
38
40
  source: string;
39
41
  html: string;
40
42
  }
41
43
  export interface MarkdownRenderingResult {
42
44
  metadata: MarkdownMetadata;
45
+ vfile: VFile;
43
46
  code: string;
44
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/markdown-remark",
3
- "version": "0.12.0",
3
+ "version": "0.14.1",
4
4
  "type": "module",
5
5
  "author": "withastro",
6
6
  "license": "MIT",
@@ -17,18 +17,15 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@astrojs/micromark-extension-mdx-jsx": "^1.0.3",
20
- "@astrojs/prism": "^0.6.1",
20
+ "@astrojs/prism": "^0.7.0",
21
21
  "acorn": "^8.7.1",
22
22
  "acorn-jsx": "^5.3.2",
23
- "assert": "^2.0.0",
24
23
  "github-slugger": "^1.4.0",
25
24
  "mdast-util-mdx-expression": "^1.2.1",
26
25
  "mdast-util-mdx-jsx": "^1.2.0",
27
- "mdast-util-to-string": "^3.1.0",
28
26
  "micromark-extension-mdx-expression": "^1.0.3",
29
27
  "micromark-extension-mdx-md": "^1.0.0",
30
28
  "micromark-util-combine-extensions": "^1.0.0",
31
- "prismjs": "^1.28.0",
32
29
  "rehype-raw": "^6.1.1",
33
30
  "rehype-stringify": "^9.0.3",
34
31
  "remark-gfm": "^3.0.1",
@@ -47,7 +44,6 @@
47
44
  "@types/hast": "^2.3.4",
48
45
  "@types/mdast": "^3.0.10",
49
46
  "@types/mocha": "^9.1.1",
50
- "@types/prismjs": "^1.26.0",
51
47
  "@types/unist": "^2.0.6",
52
48
  "astro-scripts": "0.0.6",
53
49
  "chai": "^4.3.6",
package/src/index.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import type { MarkdownRenderingOptions, MarkdownRenderingResult } from './types';
2
2
 
3
3
  import { loadPlugins } from './load-plugins.js';
4
- import createCollectHeaders from './rehype-collect-headers.js';
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';
8
8
  import rehypeJsx from './rehype-jsx.js';
9
9
  import remarkEscape from './remark-escape.js';
10
+ import { remarkInitializeAstroData } from './remark-initialize-astro-data.js';
10
11
  import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
11
12
  import remarkMdxish from './remark-mdxish.js';
12
13
  import remarkPrism from './remark-prism.js';
@@ -29,25 +30,24 @@ export const DEFAULT_REHYPE_PLUGINS = [];
29
30
  /** Shared utility for rendering markdown */
30
31
  export async function renderMarkdown(
31
32
  content: string,
32
- opts: MarkdownRenderingOptions = {}
33
+ opts: MarkdownRenderingOptions
33
34
  ): Promise<MarkdownRenderingResult> {
34
35
  let {
35
36
  fileURL,
36
- mode = 'mdx',
37
37
  syntaxHighlight = 'shiki',
38
38
  shikiConfig = {},
39
39
  remarkPlugins = [],
40
40
  rehypePlugins = [],
41
+ isAstroFlavoredMd = false,
41
42
  } = opts;
42
43
  const input = new VFile({ value: content, path: fileURL });
43
44
  const scopedClassName = opts.$?.scopedClassName;
44
- const isMDX = mode === 'mdx';
45
- const { headers, rehypeCollectHeaders } = createCollectHeaders();
45
+ const { headings, rehypeCollectHeadings } = createCollectHeadings();
46
46
 
47
47
  let parser = unified()
48
48
  .use(markdown)
49
- .use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : [])
50
- .use([remarkUnwrap, remarkEscape]);
49
+ .use(remarkInitializeAstroData)
50
+ .use(isAstroFlavoredMd ? [remarkMdxish, remarkMarkAndUnravel, remarkUnwrap, remarkEscape] : []);
51
51
 
52
52
  if (remarkPlugins.length === 0 && rehypePlugins.length === 0) {
53
53
  remarkPlugins = [...DEFAULT_REMARK_PLUGINS];
@@ -76,13 +76,15 @@ export async function renderMarkdown(
76
76
  markdownToHtml as any,
77
77
  {
78
78
  allowDangerousHtml: true,
79
- passThrough: [
80
- 'raw',
81
- 'mdxFlowExpression',
82
- 'mdxJsxFlowElement',
83
- 'mdxJsxTextElement',
84
- 'mdxTextExpression',
85
- ],
79
+ passThrough: isAstroFlavoredMd
80
+ ? [
81
+ 'raw',
82
+ 'mdxFlowExpression',
83
+ 'mdxJsxFlowElement',
84
+ 'mdxJsxTextElement',
85
+ 'mdxTextExpression',
86
+ ]
87
+ : [],
86
88
  },
87
89
  ],
88
90
  ]);
@@ -92,16 +94,16 @@ export async function renderMarkdown(
92
94
  });
93
95
 
94
96
  parser
95
- .use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw])
96
- .use(rehypeEscape)
97
- .use(rehypeIslands)
98
- .use([rehypeCollectHeaders])
97
+ .use(
98
+ isAstroFlavoredMd
99
+ ? [rehypeJsx, rehypeExpressions, rehypeEscape, rehypeIslands, rehypeCollectHeadings]
100
+ : [rehypeCollectHeadings, rehypeRaw]
101
+ )
99
102
  .use(rehypeStringify, { allowDangerousHtml: true });
100
103
 
101
- let result: string;
104
+ let vfile: VFile;
102
105
  try {
103
- const vfile = await parser.process(input);
104
- result = vfile.toString();
106
+ vfile = await parser.process(input);
105
107
  } catch (err) {
106
108
  // Ensure that the error message contains the input filename
107
109
  // to make it easier for the user to fix the issue
@@ -112,8 +114,9 @@ export async function renderMarkdown(
112
114
  }
113
115
 
114
116
  return {
115
- metadata: { headers, source: content, html: result.toString() },
116
- code: result.toString(),
117
+ metadata: { headings, source: content, html: String(vfile.value) },
118
+ code: String(vfile.value),
119
+ vfile,
117
120
  };
118
121
  }
119
122
 
@@ -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 { MarkdownHeader, RehypePlugin } from './types.js';
5
+ import type { MarkdownHeading, RehypePlugin } from './types.js';
6
6
 
7
- export default function createCollectHeaders() {
8
- const headers: MarkdownHeader[] = [];
7
+ export default function createCollectHeadings() {
8
+ const headings: MarkdownHeading[] = [];
9
9
  const slugger = new Slugger();
10
10
 
11
- function rehypeCollectHeaders(): ReturnType<RehypePlugin> {
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
- node.properties.id = slugger.slug(text);
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
- headers.push({ depth, slug: node.properties.id, text });
64
+ headings.push({ depth, slug: node.properties.id, text });
61
65
  });
62
66
  };
63
67
  }
64
68
 
65
69
  return {
66
- headers,
67
- rehypeCollectHeaders,
70
+ headings,
71
+ rehypeCollectHeadings,
68
72
  };
69
73
  }
@@ -1,4 +1,8 @@
1
- import { visit } from 'unist-util-visit';
1
+ import { SKIP, visit } from 'unist-util-visit';
2
+
3
+ export function escapeEntities(value: string): string {
4
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
5
+ }
2
6
 
3
7
  export default function rehypeEscape(): any {
4
8
  return function (node: any): any {
@@ -8,10 +12,11 @@ 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.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
15
+ raw.value = escapeEntities(raw.value);
12
16
  });
17
+ // Do not visit children to prevent double escaping
18
+ return SKIP;
13
19
  }
14
- return el;
15
20
  });
16
21
  };
17
22
  }
@@ -2,7 +2,7 @@ import type { Literal } from 'unist';
2
2
  import { visit } from 'unist-util-visit';
3
3
 
4
4
  // In code blocks, this removes the JS comment wrapper added to
5
- // HTML comments by vite-plugin-markdown.
5
+ // HTML comments by vite-plugin-markdown-legacy.
6
6
  export default function remarkEscape() {
7
7
  return (tree: any) => {
8
8
  visit(tree, 'code', removeCommentWrapper);
@@ -0,0 +1,9 @@
1
+ import type { VFile } from 'vfile';
2
+
3
+ export function remarkInitializeAstroData() {
4
+ return function (tree: any, vfile: VFile) {
5
+ if (!vfile.data.astro) {
6
+ vfile.data.astro = { frontmatter: {} };
7
+ }
8
+ };
9
+ }
@@ -1,48 +1,7 @@
1
- import { addAstro } from '@astrojs/prism/internal';
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 } = runHighlighter(lang, value);
14
+ let { html, classLanguage } = runHighlighterWithAstro(lang, value);
56
15
  let classes = [classLanguage];
57
16
  if (className) {
58
17
  classes.push(className);
package/src/types.ts CHANGED
@@ -2,6 +2,7 @@ import type * as hast from 'hast';
2
2
  import type * as mdast from 'mdast';
3
3
  import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
4
4
  import type * as unified from 'unified';
5
+ import type { VFile } from 'vfile';
5
6
 
6
7
  export type { Node } from 'unist';
7
8
 
@@ -41,21 +42,23 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions {
41
42
  $?: {
42
43
  scopedClassName: string | null;
43
44
  };
45
+ isAstroFlavoredMd?: boolean;
44
46
  }
45
47
 
46
- export interface MarkdownHeader {
48
+ export interface MarkdownHeading {
47
49
  depth: number;
48
50
  slug: string;
49
51
  text: string;
50
52
  }
51
53
 
52
54
  export interface MarkdownMetadata {
53
- headers: MarkdownHeader[];
55
+ headings: MarkdownHeading[];
54
56
  source: string;
55
57
  html: string;
56
58
  }
57
59
 
58
60
  export interface MarkdownRenderingResult {
59
61
  metadata: MarkdownMetadata;
62
+ vfile: VFile;
60
63
  code: string;
61
64
  }
@@ -2,91 +2,107 @@ import { renderMarkdown } from '../dist/index.js';
2
2
  import chai from 'chai';
3
3
 
4
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.`, {});
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
- 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
- });
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
- it('autolinks URLs starting with "www." in plain text', async () => {
14
- const { code } = await renderMarkdown(`See www.example.com for more.`, {});
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
- chai
17
- .expect(code.trim())
18
- .to.equal(`<p>See <a href="http://www.example.com">www.example.com</a> for more.</p>`);
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
- 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>`
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
- 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
- );
37
+ describe('astro-flavored md', () => {
38
+ const renderAstroMd = (text) => renderMarkdown(text, { isAstroFlavoredMd: true });
40
39
 
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
- });
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
- 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>`
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
- 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>`
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
- 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>`
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
  });
@@ -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 renderMarkdown(`<Component str="cool!" />`, {});
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 renderMarkdown(`<Component bool={true} />`, {});
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 renderMarkdown(`<Component prop={["a", "b", "c"]} />`, {});
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 renderMarkdown(`<Component prop={{ a: 0, b: 1, c: 2 }} />`, {});
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 renderMarkdown(`<Component empty />`, {});
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 renderMarkdown(`<Component {...spread} />`, {});
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 renderMarkdown(`<Component client:load />`, {});
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 renderMarkdown(`<Component bool={true}>Hello world!</Component>`, {});
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 renderMarkdown(
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 renderMarkdown(
69
+ const { code } = await renderAstroMd(
68
70
  `<Component>
69
71
  # Hello world!
70
72
  </Component>`,
@@ -0,0 +1,23 @@
1
+ import { renderMarkdown } from '../dist/index.js';
2
+ import { expect } from 'chai';
3
+
4
+ describe('entities', () => {
5
+ it('should not unescape entities in regular Markdown', async () => {
6
+ const { code } = await renderMarkdown(`&lt;i&gt;This should NOT be italic&lt;/i&gt;`, {
7
+ isAstroFlavoredMd: false,
8
+ });
9
+
10
+ expect(code).to.equal(`<p>&#x3C;i>This should NOT be italic&#x3C;/i></p>`);
11
+ });
12
+
13
+ it('should not escape entities in code blocks twice in Astro-flavored markdown', async () => {
14
+ const { code } = await renderMarkdown(`\`\`\`astro\n<h1>{x && x.name || ''}!</h1>\n\`\`\``, {
15
+ isAstroFlavoredMd: true,
16
+ syntaxHighlight: false,
17
+ });
18
+
19
+ expect(code).to.equal(
20
+ `<pre is:raw><code class="language-astro">&lt;h1&gt;{x &amp;&amp; x.name || ''}!&lt;/h1&gt;\n</code></pre>`
21
+ );
22
+ });
23
+ });
@@ -1,21 +1,23 @@
1
1
  import { renderMarkdown } from '../dist/index.js';
2
- import chai, { expect } from '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 renderMarkdown(`{a}`, {});
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 renderMarkdown(`<Component>{a}</Component>`, {});
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 renderMarkdown(`# {frontmatter.title}`, {});
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 renderMarkdown(`# Hello {frontmatter.name}`, {});
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 renderMarkdown(`# Hello <span>{frontmatter.name}</span>`, {});
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 renderMarkdown(`# \`{frontmatter.title}\``, {});
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 renderMarkdown(`# \`{ foo }\` is a shorthand for \`{ foo: foo }\``, {});
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-"><code is:raw>{ foo }</code> is a shorthand for <code is:raw>{ foo: foo }</code></h1>'
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 renderMarkdown(
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 renderMarkdown(
77
+ const { code } = await renderAstroMd(
76
78
  'The ampersand in `&nbsp;` 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 renderMarkdown(`
90
+ const { code } = await renderAstroMd(`
89
91
  \`\`\`md
90
92
  The ampersand in \`&nbsp;\` 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 renderMarkdown(
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 renderMarkdown(`\`{/*<!-- HTML comment -->*/}\``);
109
+ const { code } = await renderAstroMd(`\`{/*<!-- HTML comment -->*/}\``);
108
110
 
109
111
  chai.expect(code).to.equal('<p><code is:raw>&lt;!-- HTML comment --&gt;</code></p>');
110
112
  });
111
113
 
112
114
  it('should unwrap HTML comments in code fences', async () => {
113
- const { code } = await renderMarkdown(
115
+ const { code } = await renderAstroMd(
114
116
  `
115
117
  \`\`\`
116
118
  <!-- HTML comment -->
@@ -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 renderMarkdown(
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 renderMarkdown(`<div :class="open ? '' : 'hidden'">Test</div>`, {});
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 renderMarkdown(`<div.abc :class="open ? '' : 'hidden'">x</div.abc>`, {});
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 renderMarkdown(`<input type="text" disabled :placeholder="hi">`, {});
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 renderMarkdown(
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 renderMarkdown(`<input type="text" :placeholder="placeholder">`, {});
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 renderMarkdown(`<button @click="handleClick">Test</button>`, {});
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 renderMarkdown(
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 renderMarkdown(
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 renderMarkdown(
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 renderMarkdown(
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 renderMarkdown(`<input x-on:input.debounce.500ms="fetchResults">`, {});
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
  });
@@ -1,5 +0,0 @@
1
- import type { MarkdownHeader, RehypePlugin } from './types.js';
2
- export default function createCollectHeaders(): {
3
- headers: MarkdownHeader[];
4
- rehypeCollectHeaders: () => ReturnType<RehypePlugin>;
5
- };