@astrojs/markdown-remark 0.11.0 → 0.11.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @astrojs/markdown-remark
2
2
 
3
+ ## 0.11.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#3564](https://github.com/withastro/astro/pull/3564) [`76fb01cf`](https://github.com/withastro/astro/commit/76fb01cff1002f2a37e93869378802156c4eca7c) Thanks [@hippotastic](https://github.com/hippotastic)! - Fix autolinking of URLs inside links
8
+
9
+ * [#3554](https://github.com/withastro/astro/pull/3554) [`c549f161`](https://github.com/withastro/astro/commit/c549f161cadd76a666672556f2c2d63b5f97f00d) Thanks [@hippotastic](https://github.com/hippotastic)! - Allow AlpineJS syntax extensions in Markdown
10
+
3
11
  ## 0.11.0
4
12
 
5
13
  ### Minor Changes
package/dist/index.js CHANGED
@@ -1,21 +1,21 @@
1
+ import { loadPlugins } from "./load-plugins.js";
1
2
  import createCollectHeaders from "./rehype-collect-headers.js";
2
- import scopedStyles from "./remark-scoped-styles.js";
3
+ import rehypeEscape from "./rehype-escape.js";
3
4
  import rehypeExpressions from "./rehype-expressions.js";
4
5
  import rehypeIslands from "./rehype-islands.js";
5
- import remarkMdxish from "./remark-mdxish.js";
6
- import remarkMarkAndUnravel from "./remark-mark-and-unravel.js";
7
6
  import rehypeJsx from "./rehype-jsx.js";
8
- import rehypeEscape from "./rehype-escape.js";
7
+ import remarkMarkAndUnravel from "./remark-mark-and-unravel.js";
8
+ import remarkMdxish from "./remark-mdxish.js";
9
9
  import remarkPrism from "./remark-prism.js";
10
+ import scopedStyles from "./remark-scoped-styles.js";
10
11
  import remarkShiki from "./remark-shiki.js";
11
12
  import remarkUnwrap from "./remark-unwrap.js";
12
- import { loadPlugins } from "./load-plugins.js";
13
- import { unified } from "unified";
13
+ import Slugger from "github-slugger";
14
+ import rehypeRaw from "rehype-raw";
15
+ import rehypeStringify from "rehype-stringify";
14
16
  import markdown from "remark-parse";
15
17
  import markdownToHtml from "remark-rehype";
16
- import rehypeStringify from "rehype-stringify";
17
- import rehypeRaw from "rehype-raw";
18
- import Slugger from "github-slugger";
18
+ import { unified } from "unified";
19
19
  import { VFile } from "vfile";
20
20
  export * from "./types.js";
21
21
  const DEFAULT_REMARK_PLUGINS = ["remark-gfm", "remark-smartypants"];
@@ -74,10 +74,10 @@ async function renderMarkdown(content, opts = {}) {
74
74
  loadedRehypePlugins.forEach(([plugin, opts2]) => {
75
75
  parser.use([[plugin, opts2]]);
76
76
  });
77
- parser.use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw]).use(rehypeEscape).use(rehypeIslands);
77
+ parser.use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw]).use(rehypeEscape).use(rehypeIslands).use([rehypeCollectHeaders]).use(rehypeStringify, { allowDangerousHtml: true });
78
78
  let result;
79
79
  try {
80
- const vfile = await parser.use([rehypeCollectHeaders]).use(rehypeStringify, { allowDangerousHtml: true }).process(input);
80
+ const vfile = await parser.process(input);
81
81
  result = vfile.toString();
82
82
  } catch (err) {
83
83
  err = prefixError(err, `Failed to parse Markdown file "${input.path}"`);
@@ -0,0 +1,3 @@
1
+ import type { Options } from 'micromark-extension-mdx-expression';
2
+ import type { Extension } from 'micromark-util-types';
3
+ export declare function mdxjs(options: Options): Extension;
package/dist/mdxjs.js ADDED
@@ -0,0 +1,17 @@
1
+ import { mdxJsx } from "@astrojs/micromark-extension-mdx-jsx";
2
+ import { Parser } from "acorn";
3
+ import acornJsx from "acorn-jsx";
4
+ import { mdxExpression } from "micromark-extension-mdx-expression";
5
+ import { mdxMd } from "micromark-extension-mdx-md";
6
+ import { combineExtensions } from "micromark-util-combine-extensions";
7
+ function mdxjs(options) {
8
+ const settings = Object.assign({
9
+ acorn: Parser.extend(acornJsx()),
10
+ acornOptions: { ecmaVersion: 2020, sourceType: "module" },
11
+ addResult: true
12
+ }, options);
13
+ return combineExtensions([mdxExpression(settings), mdxJsx(settings), mdxMd]);
14
+ }
15
+ export {
16
+ mdxjs
17
+ };
@@ -1,6 +1,6 @@
1
- import { visit } from "unist-util-visit";
2
- import { toHtml } from "hast-util-to-html";
3
1
  import Slugger from "github-slugger";
2
+ import { toHtml } from "hast-util-to-html";
3
+ import { visit } from "unist-util-visit";
4
4
  function createCollectHeaders() {
5
5
  const headers = [];
6
6
  const slugger = new Slugger();
@@ -1 +1,2 @@
1
- export default function rehypeJsx(): any;
1
+ import type { RehypePlugin } from './types.js';
2
+ export default function rehypeJsx(): ReturnType<RehypePlugin>;
@@ -1,14 +1,11 @@
1
1
  import { visit } from "unist-util-visit";
2
2
  const MDX_ELEMENTS = ["mdxJsxFlowElement", "mdxJsxTextElement"];
3
3
  function rehypeJsx() {
4
- return function(node) {
5
- visit(node, "element", (child) => {
6
- child.tagName = `${child.tagName}`;
7
- });
8
- visit(node, MDX_ELEMENTS, (child, index, parent) => {
4
+ return function(tree) {
5
+ visit(tree, MDX_ELEMENTS, (node, index, parent) => {
9
6
  if (index === null || !Boolean(parent))
10
7
  return;
11
- const attrs = child.attributes.reduce((acc, entry) => {
8
+ const attrs = node.attributes.reduce((acc, entry) => {
12
9
  let attr = entry.value;
13
10
  if (attr && typeof attr === "object") {
14
11
  attr = `{${attr.value}}`;
@@ -24,22 +21,30 @@ function rehypeJsx() {
24
21
  }
25
22
  return acc + ` ${entry.name}${attr ? "=" : ""}${attr}`;
26
23
  }, "");
27
- if (child.children.length === 0) {
28
- child.type = "raw";
29
- child.value = `<${child.name}${attrs} />`;
24
+ if (node.children.length === 0) {
25
+ node.type = "raw";
26
+ node.value = `<${node.name}${attrs} />`;
30
27
  return;
31
28
  }
29
+ if (node.name === "a") {
30
+ visit(node, "element", (el, elIndex, elParent) => {
31
+ const isAutolink = el.tagName === "a" && el.children.length === 1 && el.children[0].type === "text" && el.children[0].value.match(/^(https?:\/\/|www\.)/i);
32
+ if (isAutolink) {
33
+ elParent.children.splice(elIndex, 1, el.children[0]);
34
+ }
35
+ });
36
+ }
32
37
  const openingTag = {
33
38
  type: "raw",
34
39
  value: `
35
- <${child.name}${attrs}>`
40
+ <${node.name}${attrs}>`
36
41
  };
37
42
  const closingTag = {
38
43
  type: "raw",
39
- value: `</${child.name}>
44
+ value: `</${node.name}>
40
45
  `
41
46
  };
42
- parent.children.splice(index, 1, openingTag, ...child.children, closingTag);
47
+ parent.children.splice(index, 1, openingTag, ...node.children, closingTag);
43
48
  });
44
49
  };
45
50
  }
@@ -1,5 +1,5 @@
1
- import { mdxjs } from "micromark-extension-mdxjs";
2
1
  import { mdxFromMarkdown, mdxToMarkdown } from "./mdast-util-mdxish.js";
2
+ import { mdxjs } from "./mdxjs.js";
3
3
  function remarkMdxish(options = {}) {
4
4
  const data = this.data();
5
5
  add("micromarkExtensions", mdxjs(options));
@@ -1,7 +1,7 @@
1
- import { visit } from "unist-util-visit";
2
- import Prism from "prismjs";
3
1
  import { addAstro } from "@astrojs/prism/internal";
2
+ import Prism from "prismjs";
4
3
  import loadLanguages from "prismjs/components/index.js";
4
+ import { visit } from "unist-util-visit";
5
5
  const noVisit = /* @__PURE__ */ new Set(["root", "html", "text"]);
6
6
  const languageMap = /* @__PURE__ */ new Map([["ts", "typescript"]]);
7
7
  function runHighlighter(lang, code) {
@@ -1,4 +1,4 @@
1
- import { visit as _visit, SKIP } from "unist-util-visit";
1
+ import { SKIP, visit as _visit } from "unist-util-visit";
2
2
  const visit = _visit;
3
3
  function remarkUnwrap() {
4
4
  const astroRootNodes = /* @__PURE__ */ new Set();
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type * as unified from 'unified';
2
- import type * as mdast from 'mdast';
3
1
  import type * as hast from 'hast';
2
+ import type * as mdast from 'mdast';
4
3
  import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
4
+ import type * as unified from 'unified';
5
5
  export type { Node } from 'unist';
6
6
  export declare type RemarkPlugin<PluginParameters extends any[] = any[]> = unified.Plugin<PluginParameters, mdast.Root>;
7
7
  export declare type RemarkPlugins = (string | [string, any] | RemarkPlugin | [RemarkPlugin, any])[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/markdown-remark",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "type": "module",
5
5
  "author": "withastro",
6
6
  "license": "MIT",
@@ -16,14 +16,18 @@
16
16
  ".": "./dist/index.js"
17
17
  },
18
18
  "dependencies": {
19
+ "@astrojs/micromark-extension-mdx-jsx": "^1.0.3",
19
20
  "@astrojs/prism": "^0.4.1",
21
+ "acorn": "^8.7.1",
22
+ "acorn-jsx": "^5.3.2",
20
23
  "assert": "^2.0.0",
21
24
  "github-slugger": "^1.4.0",
22
25
  "mdast-util-mdx-expression": "^1.2.0",
23
26
  "mdast-util-mdx-jsx": "^1.2.0",
24
27
  "mdast-util-to-string": "^3.1.0",
25
- "micromark-extension-mdx-jsx": "^1.0.3",
26
- "micromark-extension-mdxjs": "^1.0.0",
28
+ "micromark-extension-mdx-expression": "^1.0.3",
29
+ "micromark-extension-mdx-md": "^1.0.0",
30
+ "micromark-util-combine-extensions": "^1.0.0",
27
31
  "prismjs": "^1.28.0",
28
32
  "rehype-raw": "^6.1.1",
29
33
  "rehype-stringify": "^9.0.3",
@@ -47,6 +51,7 @@
47
51
  "@types/unist": "^2.0.6",
48
52
  "astro-scripts": "0.0.4",
49
53
  "chai": "^4.3.6",
54
+ "micromark-util-types": "^1.0.2",
50
55
  "mocha": "^9.2.2"
51
56
  },
52
57
  "scripts": {
package/src/index.ts CHANGED
@@ -1,24 +1,24 @@
1
1
  import type { MarkdownRenderingOptions, MarkdownRenderingResult } from './types';
2
2
 
3
+ import { loadPlugins } from './load-plugins.js';
3
4
  import createCollectHeaders from './rehype-collect-headers.js';
4
- import scopedStyles from './remark-scoped-styles.js';
5
+ import rehypeEscape from './rehype-escape.js';
5
6
  import rehypeExpressions from './rehype-expressions.js';
6
7
  import rehypeIslands from './rehype-islands.js';
7
- import remarkMdxish from './remark-mdxish.js';
8
- import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
9
8
  import rehypeJsx from './rehype-jsx.js';
10
- import rehypeEscape from './rehype-escape.js';
9
+ import remarkMarkAndUnravel from './remark-mark-and-unravel.js';
10
+ import remarkMdxish from './remark-mdxish.js';
11
11
  import remarkPrism from './remark-prism.js';
12
+ import scopedStyles from './remark-scoped-styles.js';
12
13
  import remarkShiki from './remark-shiki.js';
13
14
  import remarkUnwrap from './remark-unwrap.js';
14
- import { loadPlugins } from './load-plugins.js';
15
15
 
16
- import { unified } from 'unified';
16
+ import Slugger from 'github-slugger';
17
+ import rehypeRaw from 'rehype-raw';
18
+ import rehypeStringify from 'rehype-stringify';
17
19
  import markdown from 'remark-parse';
18
20
  import markdownToHtml from 'remark-rehype';
19
- import rehypeStringify from 'rehype-stringify';
20
- import rehypeRaw from 'rehype-raw';
21
- import Slugger from 'github-slugger';
21
+ import { unified } from 'unified';
22
22
  import { VFile } from 'vfile';
23
23
 
24
24
  export * from './types.js';
@@ -99,14 +99,13 @@ export async function renderMarkdown(
99
99
  parser
100
100
  .use(isMDX ? [rehypeJsx, rehypeExpressions] : [rehypeRaw])
101
101
  .use(rehypeEscape)
102
- .use(rehypeIslands);
102
+ .use(rehypeIslands)
103
+ .use([rehypeCollectHeaders])
104
+ .use(rehypeStringify, { allowDangerousHtml: true });
103
105
 
104
106
  let result: string;
105
107
  try {
106
- const vfile = await parser
107
- .use([rehypeCollectHeaders])
108
- .use(rehypeStringify, { allowDangerousHtml: true })
109
- .process(input);
108
+ const vfile = await parser.process(input);
110
109
  result = vfile.toString();
111
110
  } catch (err) {
112
111
  // Ensure that the error message contains the input filename
package/src/mdxjs.ts ADDED
@@ -0,0 +1,27 @@
1
+ // Note: The code in this file is based on `micromark-extension-mdxjs`
2
+ // and was adapted to use our fork `@astrojs/micromark-extension-mdx-jsx`
3
+ // instead of `micromark-extension-mdx-jsx` to allow some extended syntax.
4
+ // See `@astrojs/micromark-extension-mdx-jsx` on NPM for more details.
5
+ // Also, support for ESM imports & exports in Markdown content was removed.
6
+
7
+ import { mdxJsx } from '@astrojs/micromark-extension-mdx-jsx';
8
+ import { Parser } from 'acorn';
9
+ import acornJsx from 'acorn-jsx';
10
+ import type { Options } from 'micromark-extension-mdx-expression';
11
+ import { mdxExpression } from 'micromark-extension-mdx-expression';
12
+ import { mdxMd } from 'micromark-extension-mdx-md';
13
+ import { combineExtensions } from 'micromark-util-combine-extensions';
14
+ import type { Extension } from 'micromark-util-types';
15
+
16
+ export function mdxjs(options: Options): Extension {
17
+ const settings: any = Object.assign(
18
+ {
19
+ acorn: Parser.extend(acornJsx()),
20
+ acornOptions: { ecmaVersion: 2020, sourceType: 'module' },
21
+ addResult: true,
22
+ },
23
+ options
24
+ );
25
+
26
+ return combineExtensions([mdxExpression(settings), mdxJsx(settings), mdxMd]);
27
+ }
@@ -1,6 +1,6 @@
1
- import { visit } from 'unist-util-visit';
2
- import { toHtml } from 'hast-util-to-html';
3
1
  import Slugger from 'github-slugger';
2
+ import { toHtml } from 'hast-util-to-html';
3
+ import { visit } from 'unist-util-visit';
4
4
 
5
5
  import type { MarkdownHeader, RehypePlugin } from './types.js';
6
6
 
@@ -1,4 +1,4 @@
1
- import { SKIP, visit } from 'unist-util-visit';
1
+ import { visit } from 'unist-util-visit';
2
2
 
3
3
  export default function rehypeEscape(): any {
4
4
  return function (node: any): any {
package/src/rehype-jsx.ts CHANGED
@@ -1,15 +1,14 @@
1
1
  import { visit } from 'unist-util-visit';
2
+ import type { RehypePlugin } from './types.js';
2
3
 
3
4
  const MDX_ELEMENTS = ['mdxJsxFlowElement', 'mdxJsxTextElement'];
4
- export default function rehypeJsx(): any {
5
- return function (node: any): any {
6
- visit(node, 'element', (child: any) => {
7
- child.tagName = `${child.tagName}`;
8
- });
9
- visit(node, MDX_ELEMENTS, (child: any, index: number | null, parent: any) => {
5
+
6
+ export default function rehypeJsx(): ReturnType<RehypePlugin> {
7
+ return function (tree) {
8
+ visit(tree, MDX_ELEMENTS, (node: any, index: number | null, parent: any) => {
10
9
  if (index === null || !Boolean(parent)) return;
11
10
 
12
- const attrs = child.attributes.reduce((acc: any[], entry: any) => {
11
+ const attrs = node.attributes.reduce((acc: any[], entry: any) => {
13
12
  let attr = entry.value;
14
13
  if (attr && typeof attr === 'object') {
15
14
  attr = `{${attr.value}}`;
@@ -26,23 +25,41 @@ export default function rehypeJsx(): any {
26
25
  return acc + ` ${entry.name}${attr ? '=' : ''}${attr}`;
27
26
  }, '');
28
27
 
29
- if (child.children.length === 0) {
30
- child.type = 'raw';
31
- child.value = `<${child.name}${attrs} />`;
28
+ if (node.children.length === 0) {
29
+ node.type = 'raw';
30
+ node.value = `<${node.name}${attrs} />`;
32
31
  return;
33
32
  }
34
33
 
35
- // Replace the current child node with its children
34
+ // If the current node is a JSX <a> element, remove autolinks from its children
35
+ // to prevent Markdown code like `<a href="/">**Go to www.example.com now!**</a>`
36
+ // from creating a nested link to `www.example.com`
37
+ if (node.name === 'a') {
38
+ visit(node, 'element', (el, elIndex, elParent) => {
39
+ const isAutolink =
40
+ el.tagName === 'a' &&
41
+ el.children.length === 1 &&
42
+ el.children[0].type === 'text' &&
43
+ el.children[0].value.match(/^(https?:\/\/|www\.)/i);
44
+
45
+ // If we found an autolink, remove it by replacing it with its text-only child
46
+ if (isAutolink) {
47
+ elParent.children.splice(elIndex, 1, el.children[0]);
48
+ }
49
+ });
50
+ }
51
+
52
+ // Replace the current node with its children
36
53
  // wrapped by raw opening and closing tags
37
54
  const openingTag = {
38
55
  type: 'raw',
39
- value: `\n<${child.name}${attrs}>`,
56
+ value: `\n<${node.name}${attrs}>`,
40
57
  };
41
58
  const closingTag = {
42
59
  type: 'raw',
43
- value: `</${child.name}>\n`,
60
+ value: `</${node.name}>\n`,
44
61
  };
45
- parent.children.splice(index, 1, openingTag, ...child.children, closingTag);
62
+ parent.children.splice(index, 1, openingTag, ...node.children, closingTag);
46
63
  });
47
64
  };
48
65
  }
@@ -1,7 +1,7 @@
1
- import { mdxjs } from 'micromark-extension-mdxjs';
2
- import { mdxFromMarkdown, mdxToMarkdown } from './mdast-util-mdxish.js';
3
1
  import type * as fromMarkdown from 'mdast-util-from-markdown';
4
2
  import type { Tag } from 'mdast-util-mdx-jsx';
3
+ import { mdxFromMarkdown, mdxToMarkdown } from './mdast-util-mdxish.js';
4
+ import { mdxjs } from './mdxjs.js';
5
5
 
6
6
  export default function remarkMdxish(this: any, options = {}) {
7
7
  const data = this.data();
@@ -1,7 +1,7 @@
1
- import { visit } from 'unist-util-visit';
2
- import Prism from 'prismjs';
3
1
  import { addAstro } from '@astrojs/prism/internal';
2
+ import Prism from 'prismjs';
4
3
  import loadLanguages from 'prismjs/components/index.js';
4
+ import { visit } from 'unist-util-visit';
5
5
  const noVisit = new Set(['root', 'html', 'text']);
6
6
 
7
7
  const languageMap = new Map([['ts', 'typescript']]);
@@ -1,4 +1,4 @@
1
- import { visit as _visit, SKIP } from 'unist-util-visit';
1
+ import { SKIP, visit as _visit } from 'unist-util-visit';
2
2
 
3
3
  // This is a workaround.
4
4
  // It fixes a compatibility issue between different, incompatible ASTs given by plugins to Unist
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
- import type * as unified from 'unified';
2
- import type * as mdast from 'mdast';
3
1
  import type * as hast from 'hast';
2
+ import type * as mdast from 'mdast';
4
3
  import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
4
+ import type * as unified from 'unified';
5
5
 
6
6
  export type { Node } from 'unist';
7
7
 
@@ -0,0 +1,92 @@
1
+ import { renderMarkdown } from '../dist/index.js';
2
+ import chai from 'chai';
3
+
4
+ describe('autolinking', () => {
5
+ it('autolinks URLs starting with a protocol in plain text', async () => {
6
+ const { code } = await renderMarkdown(`See https://example.com for more.`, {});
7
+
8
+ chai
9
+ .expect(code.replace(/\n/g, ''))
10
+ .to.equal(`<p>See <a href="https://example.com">https://example.com</a> for more.</p>`);
11
+ });
12
+
13
+ it('autolinks URLs starting with "www." in plain text', async () => {
14
+ const { code } = await renderMarkdown(`See www.example.com for more.`, {});
15
+
16
+ chai
17
+ .expect(code.trim())
18
+ .to.equal(`<p>See <a href="http://www.example.com">www.example.com</a> for more.</p>`);
19
+ });
20
+
21
+ it('does not autolink URLs in code blocks', async () => {
22
+ const { code } = await renderMarkdown(
23
+ 'See `https://example.com` or `www.example.com` for more.',
24
+ {}
25
+ );
26
+
27
+ chai
28
+ .expect(code.trim())
29
+ .to.equal(
30
+ `<p>See <code is:raw>https://example.com</code> or ` +
31
+ `<code is:raw>www.example.com</code> for more.</p>`
32
+ );
33
+ });
34
+
35
+ it('does not autolink URLs in fenced code blocks', async () => {
36
+ const { code } = await renderMarkdown(
37
+ 'Example:\n```\nGo to https://example.com or www.example.com now.\n```',
38
+ {}
39
+ );
40
+
41
+ chai
42
+ .expect(code)
43
+ .to.contain(`<pre is:raw`)
44
+ .to.contain(`Go to https://example.com or www.example.com now.`);
45
+ });
46
+
47
+ it('does not autolink URLs starting with a protocol when nested inside links', async () => {
48
+ const { code } = await renderMarkdown(
49
+ `See [http://example.com](http://example.com) or ` +
50
+ `<a test href="https://example.com">https://example.com</a>`,
51
+ {}
52
+ );
53
+
54
+ chai
55
+ .expect(code.replace(/\n/g, ''))
56
+ .to.equal(
57
+ `<p>See <a href="http://example.com">http://example.com</a> or ` +
58
+ `<a test href="https://example.com">https://example.com</a></p>`
59
+ );
60
+ });
61
+
62
+ it('does not autolink URLs starting with "www." when nested inside links', async () => {
63
+ const { code } = await renderMarkdown(
64
+ `See [www.example.com](https://www.example.com) or ` +
65
+ `<a test href="https://www.example.com">www.example.com</a>`,
66
+ {}
67
+ );
68
+
69
+ chai
70
+ .expect(code.replace(/\n/g, ''))
71
+ .to.equal(
72
+ `<p>See <a href="https://www.example.com">www.example.com</a> or ` +
73
+ `<a test href="https://www.example.com">www.example.com</a></p>`
74
+ );
75
+ });
76
+
77
+ it('does not autolink URLs when nested several layers deep inside links', async () => {
78
+ const { code } = await renderMarkdown(
79
+ `<a href="https://www.example.com">**Visit _our www.example.com or ` +
80
+ `http://localhost pages_ for more!**</a>`,
81
+ {}
82
+ );
83
+
84
+ chai
85
+ .expect(code.replace(/\n/g, ''))
86
+ .to.equal(
87
+ `<a href="https://www.example.com"><strong>` +
88
+ `Visit <em>our www.example.com or http://localhost pages</em> for more!` +
89
+ `</strong></a>`
90
+ );
91
+ });
92
+ });
@@ -15,4 +15,85 @@ describe('strictness', () => {
15
15
  `<img src="hi.jpg" /></p>`
16
16
  );
17
17
  });
18
+
19
+ it('should allow attribute names starting with ":" after element names', async () => {
20
+ const { code } = await renderMarkdown(`<div :class="open ? '' : 'hidden'">Test</div>`, {});
21
+
22
+ chai.expect(code.trim()).to.equal(`<div :class="open ? '' : 'hidden'">Test</div>`);
23
+ });
24
+
25
+ it('should allow attribute names starting with ":" after local element names', async () => {
26
+ const { code } = await renderMarkdown(`<div.abc :class="open ? '' : 'hidden'">x</div.abc>`, {});
27
+
28
+ chai.expect(code.trim()).to.equal(`<div.abc :class="open ? '' : 'hidden'">x</div.abc>`);
29
+ });
30
+
31
+ it('should allow attribute names starting with ":" after attribute names', async () => {
32
+ const { code } = await renderMarkdown(`<input type="text" disabled :placeholder="hi">`, {});
33
+
34
+ chai.expect(code.trim()).to.equal(`<input type="text" disabled :placeholder="hi" />`);
35
+ });
36
+
37
+ it('should allow attribute names starting with ":" after local attribute names', async () => {
38
+ const { code } = await renderMarkdown(
39
+ `<input type="text" x-test:disabled :placeholder="hi">`,
40
+ {}
41
+ );
42
+
43
+ chai.expect(code.trim()).to.equal(`<input type="text" x-test:disabled :placeholder="hi" />`);
44
+ });
45
+
46
+ it('should allow attribute names starting with ":" after attribute values', async () => {
47
+ const { code } = await renderMarkdown(`<input type="text" :placeholder="placeholder">`, {});
48
+
49
+ chai.expect(code.trim()).to.equal(`<input type="text" :placeholder="placeholder" />`);
50
+ });
51
+
52
+ it('should allow attribute names starting with "@" after element names', async () => {
53
+ const { code } = await renderMarkdown(`<button @click="handleClick">Test</button>`, {});
54
+
55
+ chai.expect(code.trim()).to.equal(`<button @click="handleClick">Test</button>`);
56
+ });
57
+
58
+ it('should allow attribute names starting with "@" after local element names', async () => {
59
+ const { code } = await renderMarkdown(
60
+ `<button.local @click="handleClick">Test</button.local>`,
61
+ {}
62
+ );
63
+
64
+ chai.expect(code.trim()).to.equal(`<button.local @click="handleClick">Test</button.local>`);
65
+ });
66
+
67
+ it('should allow attribute names starting with "@" after attribute names', async () => {
68
+ const { code } = await renderMarkdown(
69
+ `<button disabled @click="handleClick">Test</button>`,
70
+ {}
71
+ );
72
+
73
+ chai.expect(code.trim()).to.equal(`<button disabled @click="handleClick">Test</button>`);
74
+ });
75
+
76
+ it('should allow attribute names starting with "@" after local attribute names', async () => {
77
+ const { code } = await renderMarkdown(
78
+ `<button x-test:disabled @click="handleClick">Test</button>`,
79
+ {}
80
+ );
81
+
82
+ chai.expect(code.trim()).to.equal(`<button x-test:disabled @click="handleClick">Test</button>`);
83
+ });
84
+
85
+ it('should allow attribute names starting with "@" after attribute values', async () => {
86
+ const { code } = await renderMarkdown(
87
+ `<button type="submit" @click="handleClick">Test</button>`,
88
+ {}
89
+ );
90
+
91
+ chai.expect(code.trim()).to.equal(`<button type="submit" @click="handleClick">Test</button>`);
92
+ });
93
+
94
+ it('should allow attribute names containing dots', async () => {
95
+ const { code } = await renderMarkdown(`<input x-on:input.debounce.500ms="fetchResults">`, {});
96
+
97
+ chai.expect(code.trim()).to.equal(`<input x-on:input.debounce.500ms="fetchResults" />`);
98
+ });
18
99
  });