@astrojs/markdown-remark 0.12.0 → 0.13.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.
@@ -1,5 +1,5 @@
1
- @astrojs/markdown-remark:build: cache hit, replaying output 1546942a008a83f6
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
4
- @astrojs/markdown-remark:build: > astro-scripts build "src/**/*.ts" && tsc -p tsconfig.json
5
- @astrojs/markdown-remark:build: 
1
+ @astrojs/markdown-remark:build: cache hit, replaying output a1025d081834f1c0
2
+ @astrojs/markdown-remark:build: 
3
+ @astrojs/markdown-remark:build: > @astrojs/markdown-remark@0.13.0 build /home/runner/work/astro/astro/packages/markdown/remark
4
+ @astrojs/markdown-remark:build: > astro-scripts build "src/**/*.ts" && tsc -p tsconfig.json
5
+ @astrojs/markdown-remark:build: 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @astrojs/markdown-remark
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`ba11b3399`](https://github.com/withastro/astro/commit/ba11b33996d79c32da947986edb0f32dbcc04aaf) Thanks [@RafidMuhymin](https://github.com/RafidMuhymin)! - fixed generated slugs in markdown that ends with a dash
8
+
9
+ * [#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.
10
+
11
+ For long term support, migrate to the `@astrojs/mdx` integration for MDX support (including `.mdx` pages!).
12
+
13
+ Not ready to migrate to MDX? Add the legacy flag to your Astro config to re-enable the previous Markdown support.
14
+
15
+ ```js
16
+ // https://astro.build/config
17
+ export default defineConfig({
18
+ legacy: {
19
+ astroFlavoredMarkdown: true,
20
+ },
21
+ });
22
+ ```
23
+
24
+ - [#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()`.
25
+
26
+ ### Patch Changes
27
+
28
+ - [#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.
29
+
3
30
  ## 0.12.0
4
31
 
5
32
  ### 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,5 +1,5 @@
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";
@@ -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 isMDX = mode === "mdx";
36
- const { headers, rehypeCollectHeaders } = createCollectHeaders();
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(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw]).use(rehypeEscape).use(rehypeIslands).use([rehypeCollectHeaders]).use(rehypeStringify, { allowDangerousHtml: true });
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: { headers, source: content, html: result.toString() },
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
- 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
  };
@@ -43,7 +43,9 @@ function transformer(className) {
43
43
  if (className) {
44
44
  classes.push(className);
45
45
  }
46
- node.value = `<pre class="${classes.join(" ")}"><code is:raw class="${classLanguage}">${html}</code></pre>`;
46
+ node.value = `<pre class="${classes.join(
47
+ " "
48
+ )}"><code is:raw class="${classLanguage}">${html}</code></pre>`;
47
49
  return node;
48
50
  };
49
51
  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
@@ -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 MarkdownHeader {
32
+ export interface MarkdownHeading {
32
33
  depth: number;
33
34
  slug: string;
34
35
  text: string;
35
36
  }
36
37
  export interface MarkdownMetadata {
37
- headers: MarkdownHeader[];
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.12.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "author": "withastro",
6
6
  "license": "MIT",
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 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';
@@ -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 isMDX = mode === 'mdx';
45
- const { headers, rehypeCollectHeaders } = createCollectHeaders();
44
+ const { headings, rehypeCollectHeadings } = createCollectHeadings();
46
45
 
47
46
  let parser = unified()
48
47
  .use(markdown)
49
- .use(isMDX ? [remarkMdxish, remarkMarkAndUnravel] : [])
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
- 'raw',
81
- 'mdxFlowExpression',
82
- 'mdxJsxFlowElement',
83
- 'mdxJsxTextElement',
84
- 'mdxTextExpression',
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(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw])
96
- .use(rehypeEscape)
97
- .use(rehypeIslands)
98
- .use([rehypeCollectHeaders])
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: { headers, source: content, html: result.toString() },
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 { 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
  }
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 MarkdownHeader {
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
- headers: MarkdownHeader[];
54
+ headings: MarkdownHeading[];
54
55
  source: string;
55
56
  html: string;
56
57
  }
@@ -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>`,
@@ -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
- };