mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-01 03:08:17 +02:00
fix(mdx-loader): the table-of-contents should display toc/headings of imported MDX partials (#9684)
Co-authored-by: Titus <tituswormer@gmail.com> Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
bed11f62bc
commit
3c982127d7
33 changed files with 1145 additions and 408 deletions
|
@ -18,8 +18,6 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.22.7",
|
|
||||||
"@babel/traverse": "^7.22.8",
|
|
||||||
"@docusaurus/logger": "3.0.0",
|
"@docusaurus/logger": "3.0.0",
|
||||||
"@docusaurus/utils": "3.0.0",
|
"@docusaurus/utils": "3.0.0",
|
||||||
"@docusaurus/utils-validation": "3.0.0",
|
"@docusaurus/utils-validation": "3.0.0",
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import {mdxLoader} from './loader';
|
import {mdxLoader} from './loader';
|
||||||
|
|
||||||
import type {TOCItem as TOCItemImported} from './remark/toc';
|
import type {TOCItem as TOCItemImported} from './remark/toc/types';
|
||||||
|
|
||||||
export default mdxLoader;
|
export default mdxLoader;
|
||||||
|
|
||||||
|
|
5
packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/partials/SomeComponent.js
generated
Normal file
5
packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/partials/SomeComponent.js
generated
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function SomeComponent() {
|
||||||
|
return <div>Some component</div>;
|
||||||
|
}
|
7
packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/partials/_partial1.md
generated
Normal file
7
packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/partials/_partial1.md
generated
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
## Partial 1
|
||||||
|
|
||||||
|
Partial 1
|
||||||
|
|
||||||
|
### Partial 1 Sub Heading
|
||||||
|
|
||||||
|
Content
|
|
@ -0,0 +1,3 @@
|
||||||
|
## Partial 2 Nested
|
||||||
|
|
||||||
|
Partial 2 Nested
|
11
packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/partials/_partial2.mdx
generated
Normal file
11
packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/partials/_partial2.mdx
generated
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
## Partial 2
|
||||||
|
|
||||||
|
Partial 2
|
||||||
|
|
||||||
|
### Partial 2 Sub Heading
|
||||||
|
|
||||||
|
Content
|
||||||
|
|
||||||
|
import Partial2Nested from './partial2-nested.md';
|
||||||
|
|
||||||
|
<Partial2Nested />
|
|
@ -0,0 +1,7 @@
|
||||||
|
## Partial 3
|
||||||
|
|
||||||
|
Partial 3
|
||||||
|
|
||||||
|
### Partial 3 Sub Heading
|
||||||
|
|
||||||
|
Content
|
49
packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/partials/index.mdx
generated
Normal file
49
packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/partials/index.mdx
generated
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import Partial1 from './_partial1.md';
|
||||||
|
|
||||||
|
import SomeComponent from './SomeComponent';
|
||||||
|
|
||||||
|
# Index
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
import Partial2 from './_partial2.md';
|
||||||
|
|
||||||
|
## Index section 1
|
||||||
|
|
||||||
|
Foo
|
||||||
|
|
||||||
|
<Partial1 />
|
||||||
|
|
||||||
|
Some text
|
||||||
|
|
||||||
|
<SomeComponent />
|
||||||
|
|
||||||
|
## Index section 2
|
||||||
|
|
||||||
|
<Partial2 />
|
||||||
|
|
||||||
|
## Unused partials
|
||||||
|
|
||||||
|
Unused partials (that are only imported but not rendered) shouldn't alter the TOC
|
||||||
|
|
||||||
|
import UnusedPartialImport from './_partial3.md';
|
||||||
|
|
||||||
|
## NonExisting Partials
|
||||||
|
|
||||||
|
Partials that do not exist should alter the TOC
|
||||||
|
|
||||||
|
It's not the responsibility of the Remark plugin to check for their existence
|
||||||
|
|
||||||
|
import DoesNotExist from './_doesNotExist.md';
|
||||||
|
|
||||||
|
<DoesNotExist />
|
||||||
|
|
||||||
|
## Duplicate partials
|
||||||
|
|
||||||
|
It's fine if we use partials at the end
|
||||||
|
|
||||||
|
<Partial1 />
|
||||||
|
|
||||||
|
And we can use the partial multiple times!
|
||||||
|
|
||||||
|
<Partial1 />
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Partial used before import
|
||||||
|
|
||||||
|
While it looks weird to import after usage, this remains valid MDX usage.
|
||||||
|
|
||||||
|
<Partial />
|
||||||
|
|
||||||
|
import Partial from './_partial.md';
|
|
@ -1,238 +1,601 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`toc remark plugin does not overwrite TOC var if no TOC 1`] = `
|
exports[`toc remark plugin does not overwrite TOC var if no TOC 1`] = `
|
||||||
"foo
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
|
|
||||||
\`bar\`
|
|
||||||
|
|
||||||
\`\`\`js
|
|
||||||
baz
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
export const toc = 1;
|
export const toc = 1;
|
||||||
|
function _createMdxContent(props) {
|
||||||
|
const _components = {
|
||||||
|
code: "code",
|
||||||
|
p: "p",
|
||||||
|
pre: "pre",
|
||||||
|
...props.components
|
||||||
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
|
children: [_jsx(_components.p, {
|
||||||
|
children: "foo"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
children: "bar"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.pre, {
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
className: "language-js",
|
||||||
|
children: "baz/n"
|
||||||
|
})
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`toc remark plugin escapes inline code 1`] = `
|
exports[`toc remark plugin escapes inline code 1`] = `
|
||||||
"export const toc = [
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
{
|
export const toc = [{
|
||||||
value: '<code><Head /></code>',
|
"value": "<code><Head /></code>",
|
||||||
id: 'head-',
|
"id": "head-",
|
||||||
level: 2
|
"level": 2
|
||||||
},
|
}, {
|
||||||
{
|
"value": "<code><Head>Test</Head></code>",
|
||||||
value: '<code><Head>Test</Head></code>',
|
"id": "headtesthead",
|
||||||
id: 'headtesthead',
|
"level": 3
|
||||||
level: 3
|
}, {
|
||||||
},
|
"value": "<code><div /></code>",
|
||||||
{
|
"id": "div-",
|
||||||
value: '<code><div /></code>',
|
"level": 2
|
||||||
id: 'div-',
|
}, {
|
||||||
level: 2
|
"value": "<code><div> Test </div></code>",
|
||||||
},
|
"id": "div-test-div",
|
||||||
{
|
"level": 2
|
||||||
value: '<code><div> Test </div></code>',
|
}, {
|
||||||
id: 'div-test-div',
|
"value": "<code><div><i>Test</i></div></code>",
|
||||||
level: 2
|
"id": "divitestidiv",
|
||||||
},
|
"level": 2
|
||||||
{
|
}, {
|
||||||
value: '<code><div><i>Test</i></div></code>',
|
"value": "<code><div><i>Test</i></div></code>",
|
||||||
id: 'divitestidiv',
|
"id": "divitestidiv-1",
|
||||||
level: 2
|
"level": 2
|
||||||
},
|
}];
|
||||||
{
|
function _createMdxContent(props) {
|
||||||
value: '<code><div><i>Test</i></div></code>',
|
const _components = {
|
||||||
id: 'divitestidiv-1',
|
a: "a",
|
||||||
level: 2
|
code: "code",
|
||||||
}
|
h2: "h2",
|
||||||
]
|
h3: "h3",
|
||||||
|
...props.components
|
||||||
## \`<Head />\`
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
### \`<Head>Test</Head>\`
|
children: [_jsx(_components.h2, {
|
||||||
|
id: "head-",
|
||||||
## \`<div />\`
|
children: _jsx(_components.code, {
|
||||||
|
children: "<Head />"
|
||||||
## \`<div> Test </div>\`
|
})
|
||||||
|
}), "/n", _jsx(_components.h3, {
|
||||||
## \`<div><i>Test</i></div>\`
|
id: "headtesthead",
|
||||||
|
children: _jsx(_components.code, {
|
||||||
## [\`<div><i>Test</i></div>\`](/some/link)
|
children: "<Head>Test</Head>"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "div-",
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
children: "<div />"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "div-test-div",
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
children: "<div> Test </div>"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "divitestidiv",
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
children: "<div><i>Test</i></div>"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "divitestidiv-1",
|
||||||
|
children: _jsx(_components.a, {
|
||||||
|
href: "/some/link",
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
children: "<div><i>Test</i></div>"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`toc remark plugin exports even with existing name 1`] = `
|
exports[`toc remark plugin exports even with existing name 1`] = `
|
||||||
"export const toc = [
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
{
|
export const toc = ['replaceMe'];
|
||||||
value: 'Thanos',
|
function _createMdxContent(props) {
|
||||||
id: 'thanos',
|
const _components = {
|
||||||
level: 2
|
h2: "h2",
|
||||||
},
|
h3: "h3",
|
||||||
{
|
...props.components
|
||||||
value: 'Tony Stark',
|
};
|
||||||
id: 'tony-stark',
|
return _jsxs(_Fragment, {
|
||||||
level: 2
|
children: [_jsx(_components.h2, {
|
||||||
},
|
id: "thanos",
|
||||||
{
|
children: "Thanos"
|
||||||
value: 'Avengers',
|
}), "/n", _jsx(_components.h2, {
|
||||||
id: 'avengers',
|
id: "tony-stark",
|
||||||
level: 3
|
children: "Tony Stark"
|
||||||
}
|
}), "/n", _jsx(_components.h3, {
|
||||||
]
|
id: "avengers",
|
||||||
|
children: "Avengers"
|
||||||
## Thanos
|
})]
|
||||||
|
});
|
||||||
## Tony Stark
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
### Avengers
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`toc remark plugin handles empty headings 1`] = `
|
exports[`toc remark plugin handles empty headings 1`] = `
|
||||||
"export const toc = []
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
|
export const toc = [];
|
||||||
# Ignore this
|
function _createMdxContent(props) {
|
||||||
|
const _components = {
|
||||||
##
|
h1: "h1",
|
||||||
|
h2: "h2",
|
||||||
## 
|
img: "img",
|
||||||
|
...props.components
|
||||||
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
|
children: [_jsx(_components.h1, {
|
||||||
|
id: "ignore-this",
|
||||||
|
children: "Ignore this"
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: ""
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "-1",
|
||||||
|
children: _jsx(_components.img, {
|
||||||
|
src: "an-image.svg",
|
||||||
|
alt: ""
|
||||||
|
})
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`toc remark plugin inserts below imports 1`] = `
|
exports[`toc remark plugin inserts below imports 1`] = `
|
||||||
"import something from 'something';
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
|
import something from 'something';
|
||||||
import somethingElse from 'something-else';
|
import somethingElse from 'something-else';
|
||||||
|
export const toc = [{
|
||||||
export const toc = [
|
"value": "Title",
|
||||||
{
|
"id": "title",
|
||||||
value: 'Title',
|
"level": 2
|
||||||
id: 'title',
|
}, {
|
||||||
level: 2
|
"value": "Test",
|
||||||
},
|
"id": "test",
|
||||||
{
|
"level": 2
|
||||||
value: 'Test',
|
}, {
|
||||||
id: 'test',
|
"value": "Again",
|
||||||
level: 2
|
"id": "again",
|
||||||
},
|
"level": 3
|
||||||
{
|
}];
|
||||||
value: 'Again',
|
function _createMdxContent(props) {
|
||||||
id: 'again',
|
const _components = {
|
||||||
level: 3
|
h2: "h2",
|
||||||
}
|
h3: "h3",
|
||||||
]
|
p: "p",
|
||||||
|
...props.components
|
||||||
## Title
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
## Test
|
children: [_jsx(_components.h2, {
|
||||||
|
id: "title",
|
||||||
### Again
|
children: "Title"
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
Content.
|
id: "test",
|
||||||
|
children: "Test"
|
||||||
|
}), "/n", _jsx(_components.h3, {
|
||||||
|
id: "again",
|
||||||
|
children: "Again"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Content."
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`toc remark plugin outputs empty array for no TOC 1`] = `
|
exports[`toc remark plugin outputs empty array for no TOC 1`] = `
|
||||||
"export const toc = []
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
|
export const toc = [];
|
||||||
foo
|
function _createMdxContent(props) {
|
||||||
|
const _components = {
|
||||||
\`bar\`
|
code: "code",
|
||||||
|
p: "p",
|
||||||
\`\`\`js
|
pre: "pre",
|
||||||
baz
|
...props.components
|
||||||
\`\`\`
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
|
children: [_jsx(_components.p, {
|
||||||
|
children: "foo"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
children: "bar"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.pre, {
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
className: "language-js",
|
||||||
|
children: "baz/n"
|
||||||
|
})
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`toc remark plugin works on non text phrasing content 1`] = `
|
exports[`toc remark plugin works on non text phrasing content 1`] = `
|
||||||
"export const toc = [
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
{
|
export const toc = [{
|
||||||
value: '<em>Emphasis</em>',
|
"value": "<em>Emphasis</em>",
|
||||||
id: 'emphasis',
|
"id": "emphasis",
|
||||||
level: 2
|
"level": 2
|
||||||
},
|
}, {
|
||||||
{
|
"value": "<strong>Importance</strong>",
|
||||||
value: '<strong>Importance</strong>',
|
"id": "importance",
|
||||||
id: 'importance',
|
"level": 3
|
||||||
level: 3
|
}, {
|
||||||
},
|
"value": "<del>Strikethrough</del>",
|
||||||
{
|
"id": "strikethrough",
|
||||||
value: '<del>Strikethrough</del>',
|
"level": 2
|
||||||
id: 'strikethrough',
|
}, {
|
||||||
level: 2
|
"value": "<i>HTML</i>",
|
||||||
},
|
"id": "html",
|
||||||
{
|
"level": 2
|
||||||
value: '<i>HTML</i>',
|
}, {
|
||||||
id: 'html',
|
"value": "<code>inline.code()</code>",
|
||||||
level: 2
|
"id": "inlinecode",
|
||||||
},
|
"level": 2
|
||||||
{
|
}, {
|
||||||
value: '<code>inline.code()</code>',
|
"value": "some <span class=\\"some-class\\">styled</span> <strong>heading</strong> <span class=\\"myClassName <> weird char\\"></span> test",
|
||||||
id: 'inlinecode',
|
"id": "some-styled-heading--test",
|
||||||
level: 2
|
"level": 2
|
||||||
},
|
}];
|
||||||
{
|
function _createMdxContent(props) {
|
||||||
value: 'some <span class="some-class">styled</span> <strong>heading</strong> <span class="myClassName <> weird char"></span> test',
|
const _components = {
|
||||||
id: 'some-styled-heading--test',
|
code: "code",
|
||||||
level: 2
|
del: "del",
|
||||||
}
|
em: "em",
|
||||||
]
|
h2: "h2",
|
||||||
|
h3: "h3",
|
||||||
## *Emphasis*
|
strong: "strong",
|
||||||
|
...props.components
|
||||||
### **Importance**
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
## ~~Strikethrough~~
|
children: [_jsx(_components.h2, {
|
||||||
|
id: "emphasis",
|
||||||
## <i>HTML</i>
|
children: _jsx(_components.em, {
|
||||||
|
children: "Emphasis"
|
||||||
## \`inline.code()\`
|
})
|
||||||
|
}), "/n", _jsx(_components.h3, {
|
||||||
## some <span className="some-class" style={{border: "solid"}}>styled</span> <strong>heading</strong> <span class="myClass" className="myClassName <> weird char" data-random-attr="456" /> test
|
id: "importance",
|
||||||
|
children: _jsx(_components.strong, {
|
||||||
|
children: "Importance"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "strikethrough",
|
||||||
|
children: _jsx(_components.del, {
|
||||||
|
children: "Strikethrough"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "html",
|
||||||
|
children: _jsx("i", {
|
||||||
|
children: "HTML"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "inlinecode",
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
children: "inline.code()"
|
||||||
|
})
|
||||||
|
}), "/n", _jsxs(_components.h2, {
|
||||||
|
id: "some-styled-heading--test",
|
||||||
|
children: ["some ", _jsx("span", {
|
||||||
|
className: "some-class",
|
||||||
|
style: {
|
||||||
|
border: "solid"
|
||||||
|
},
|
||||||
|
children: "styled"
|
||||||
|
}), " ", _jsx("strong", {
|
||||||
|
children: "heading"
|
||||||
|
}), " ", _jsx("span", {
|
||||||
|
class: "myClass",
|
||||||
|
className: "myClassName <> weird char",
|
||||||
|
"data-random-attr": "456"
|
||||||
|
}), " test"]
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`toc remark plugin works on text content 1`] = `
|
exports[`toc remark plugin works on text content 1`] = `
|
||||||
"export const toc = [
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
{
|
|
||||||
value: 'Endi',
|
|
||||||
id: 'endi',
|
|
||||||
level: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'Endi',
|
|
||||||
id: 'endi-1',
|
|
||||||
level: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'Yangshun',
|
|
||||||
id: 'yangshun',
|
|
||||||
level: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'I ♥ unicode.',
|
|
||||||
id: 'i--unicode',
|
|
||||||
level: 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
### Endi
|
|
||||||
|
|
||||||
\`\`\`md
|
|
||||||
## This is ignored
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
## Endi
|
|
||||||
|
|
||||||
Lorem ipsum
|
|
||||||
|
|
||||||
### Yangshun
|
|
||||||
|
|
||||||
Some content here
|
|
||||||
|
|
||||||
## I ♥ unicode.
|
|
||||||
|
|
||||||
export const c = 1;
|
export const c = 1;
|
||||||
|
export const toc = [{
|
||||||
|
"value": "Endi",
|
||||||
|
"id": "endi",
|
||||||
|
"level": 3
|
||||||
|
}, {
|
||||||
|
"value": "Endi",
|
||||||
|
"id": "endi-1",
|
||||||
|
"level": 2
|
||||||
|
}, {
|
||||||
|
"value": "Yangshun",
|
||||||
|
"id": "yangshun",
|
||||||
|
"level": 3
|
||||||
|
}, {
|
||||||
|
"value": "I ♥ unicode.",
|
||||||
|
"id": "i--unicode",
|
||||||
|
"level": 2
|
||||||
|
}];
|
||||||
|
function _createMdxContent(props) {
|
||||||
|
const _components = {
|
||||||
|
code: "code",
|
||||||
|
h2: "h2",
|
||||||
|
h3: "h3",
|
||||||
|
p: "p",
|
||||||
|
pre: "pre",
|
||||||
|
...props.components
|
||||||
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
|
children: [_jsx(_components.h3, {
|
||||||
|
id: "endi",
|
||||||
|
children: "Endi"
|
||||||
|
}), "/n", _jsx(_components.pre, {
|
||||||
|
children: _jsx(_components.code, {
|
||||||
|
className: "language-md",
|
||||||
|
children: "## This is ignored/n"
|
||||||
|
})
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "endi-1",
|
||||||
|
children: "Endi"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Lorem ipsum"
|
||||||
|
}), "/n", _jsx(_components.h3, {
|
||||||
|
id: "yangshun",
|
||||||
|
children: "Yangshun"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Some content here"
|
||||||
|
}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "i--unicode",
|
||||||
|
children: "I ♥ unicode."
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`toc remark plugin works with imported markdown 1`] = `
|
||||||
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
|
import Partial1, {toc as __tocPartial1} from './_partial1.md';
|
||||||
|
import SomeComponent from './SomeComponent';
|
||||||
|
import Partial2, {toc as __tocPartial2} from './_partial2.md';
|
||||||
|
import UnusedPartialImport from './_partial3.md';
|
||||||
|
import DoesNotExist, {toc as __tocDoesNotExist} from './_doesNotExist.md';
|
||||||
|
export const toc = [{
|
||||||
|
"value": "Index section 1",
|
||||||
|
"id": "index-section-1",
|
||||||
|
"level": 2
|
||||||
|
}, ...__tocPartial1, {
|
||||||
|
"value": "Index section 2",
|
||||||
|
"id": "index-section-2",
|
||||||
|
"level": 2
|
||||||
|
}, ...__tocPartial2, {
|
||||||
|
"value": "Unused partials",
|
||||||
|
"id": "unused-partials",
|
||||||
|
"level": 2
|
||||||
|
}, {
|
||||||
|
"value": "NonExisting Partials",
|
||||||
|
"id": "nonexisting-partials",
|
||||||
|
"level": 2
|
||||||
|
}, ...__tocDoesNotExist, {
|
||||||
|
"value": "Duplicate partials",
|
||||||
|
"id": "duplicate-partials",
|
||||||
|
"level": 2
|
||||||
|
}, ...__tocPartial1, ...__tocPartial1];
|
||||||
|
function _createMdxContent(props) {
|
||||||
|
const _components = {
|
||||||
|
h1: "h1",
|
||||||
|
h2: "h2",
|
||||||
|
p: "p",
|
||||||
|
...props.components
|
||||||
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
|
children: [_jsx(_components.h1, {
|
||||||
|
id: "index",
|
||||||
|
children: "Index"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Some text"
|
||||||
|
}), "/n", "/n", _jsx(_components.h2, {
|
||||||
|
id: "index-section-1",
|
||||||
|
children: "Index section 1"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Foo"
|
||||||
|
}), "/n", _jsx(Partial1, {}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Some text"
|
||||||
|
}), "/n", _jsx(SomeComponent, {}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "index-section-2",
|
||||||
|
children: "Index section 2"
|
||||||
|
}), "/n", _jsx(Partial2, {}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "unused-partials",
|
||||||
|
children: "Unused partials"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Unused partials (that are only imported but not rendered) shouldn't alter the TOC"
|
||||||
|
}), "/n", "/n", _jsx(_components.h2, {
|
||||||
|
id: "nonexisting-partials",
|
||||||
|
children: "NonExisting Partials"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Partials that do not exist should alter the TOC"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "It's not the responsibility of the Remark plugin to check for their existence"
|
||||||
|
}), "/n", "/n", _jsx(DoesNotExist, {}), "/n", _jsx(_components.h2, {
|
||||||
|
id: "duplicate-partials",
|
||||||
|
children: "Duplicate partials"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "It's fine if we use partials at the end"
|
||||||
|
}), "/n", _jsx(Partial1, {}), "/n", _jsx(_components.p, {
|
||||||
|
children: "And we can use the partial multiple times!"
|
||||||
|
}), "/n", _jsx(Partial1, {})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`toc remark plugin works with partial imported after its usage 1`] = `
|
||||||
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
|
import Partial, {toc as __tocPartial} from './_partial.md';
|
||||||
|
export const toc = [...__tocPartial];
|
||||||
|
function _createMdxContent(props) {
|
||||||
|
const _components = {
|
||||||
|
h1: "h1",
|
||||||
|
p: "p",
|
||||||
|
...props.components
|
||||||
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
|
children: [_jsx(_components.h1, {
|
||||||
|
id: "partial-used-before-import",
|
||||||
|
children: "Partial used before import"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "While it looks weird to import after usage, this remains valid MDX usage."
|
||||||
|
}), "/n", _jsx(Partial, {})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`toc remark plugin works with partials importing other partials 1`] = `
|
||||||
|
"import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
|
||||||
|
import Partial2Nested, {toc as __tocPartial2Nested} from './partial2-nested.md';
|
||||||
|
export const toc = [{
|
||||||
|
"value": "Partial 2",
|
||||||
|
"id": "partial-2",
|
||||||
|
"level": 2
|
||||||
|
}, {
|
||||||
|
"value": "Partial 2 Sub Heading",
|
||||||
|
"id": "partial-2-sub-heading",
|
||||||
|
"level": 3
|
||||||
|
}, ...__tocPartial2Nested];
|
||||||
|
function _createMdxContent(props) {
|
||||||
|
const _components = {
|
||||||
|
h2: "h2",
|
||||||
|
h3: "h3",
|
||||||
|
p: "p",
|
||||||
|
...props.components
|
||||||
|
};
|
||||||
|
return _jsxs(_Fragment, {
|
||||||
|
children: [_jsx(_components.h2, {
|
||||||
|
id: "partial-2",
|
||||||
|
children: "Partial 2"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Partial 2"
|
||||||
|
}), "/n", _jsx(_components.h3, {
|
||||||
|
id: "partial-2-sub-heading",
|
||||||
|
children: "Partial 2 Sub Heading"
|
||||||
|
}), "/n", _jsx(_components.p, {
|
||||||
|
children: "Content"
|
||||||
|
}), "/n", "/n", _jsx(Partial2Nested, {})]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export default function MDXContent(props = {}) {
|
||||||
|
const {wrapper: MDXLayout} = props.components || ({});
|
||||||
|
return MDXLayout ? _jsx(MDXLayout, {
|
||||||
|
...props,
|
||||||
|
children: _jsx(_createMdxContent, {
|
||||||
|
...props
|
||||||
|
})
|
||||||
|
}) : _createMdxContent(props);
|
||||||
|
}
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -11,18 +11,23 @@ import plugin from '../index';
|
||||||
import headings from '../../headings/index';
|
import headings from '../../headings/index';
|
||||||
|
|
||||||
const processFixture = async (name: string) => {
|
const processFixture = async (name: string) => {
|
||||||
const {remark} = await import('remark');
|
|
||||||
const {default: gfm} = await import('remark-gfm');
|
const {default: gfm} = await import('remark-gfm');
|
||||||
const {default: mdx} = await import('remark-mdx');
|
|
||||||
|
|
||||||
const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
|
const {compile} = await import('@mdx-js/mdx');
|
||||||
|
|
||||||
|
const filePath = path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__',
|
||||||
|
name.endsWith('.mdx') ? name : `${name}.md`,
|
||||||
|
);
|
||||||
|
|
||||||
const file = await vfile.read(filePath);
|
const file = await vfile.read(filePath);
|
||||||
const result = await remark()
|
|
||||||
.use(headings)
|
const result = await compile(file, {
|
||||||
.use(gfm)
|
format: 'mdx',
|
||||||
.use(mdx)
|
remarkPlugins: [headings, gfm, plugin],
|
||||||
.use(plugin)
|
rehypePlugins: [],
|
||||||
.process(file);
|
});
|
||||||
|
|
||||||
return result.value;
|
return result.value;
|
||||||
};
|
};
|
||||||
|
@ -70,4 +75,21 @@ describe('toc remark plugin', () => {
|
||||||
const result = await processFixture('empty-headings');
|
const result = await processFixture('empty-headings');
|
||||||
expect(result).toMatchSnapshot();
|
expect(result).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('works with imported markdown', async () => {
|
||||||
|
const result = await processFixture('partials/index.mdx');
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with partials importing other partials', async () => {
|
||||||
|
const result = await processFixture('partials/_partial2.mdx');
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with partial imported after its usage', async () => {
|
||||||
|
const result = await processFixture(
|
||||||
|
'partials/partial-used-before-import.mdx',
|
||||||
|
);
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,154 +5,183 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {parse, type ParserOptions} from '@babel/parser';
|
import {
|
||||||
import traverse from '@babel/traverse';
|
addTocSliceImportIfNeeded,
|
||||||
import stringifyObject from 'stringify-object';
|
createTOCExportNodeAST,
|
||||||
import {toValue} from '../utils';
|
findDefaultImportName,
|
||||||
import type {Identifier} from '@babel/types';
|
getImportDeclarations,
|
||||||
import type {Node, Parent} from 'unist';
|
isMarkdownImport,
|
||||||
import type {Heading, Literal} from 'mdast';
|
isNamedExport,
|
||||||
|
} from './utils';
|
||||||
|
import type {Heading, Root} from 'mdast';
|
||||||
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
||||||
import type {Transformer} from 'unified';
|
import type {Transformer} from 'unified';
|
||||||
import type {
|
import type {
|
||||||
MdxjsEsm,
|
MdxjsEsm,
|
||||||
|
MdxJsxFlowElement,
|
||||||
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
||||||
} from 'mdast-util-mdx';
|
} from 'mdast-util-mdx';
|
||||||
|
import type {TOCItems} from './types';
|
||||||
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
|
import type {ImportDeclaration} from 'estree';
|
||||||
// TODO upgrade to TS 5.3
|
|
||||||
// See https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1517839391
|
|
||||||
// import type {Plugin} from 'unified';
|
|
||||||
type Plugin = any; // TODO fix this asap
|
|
||||||
|
|
||||||
export type TOCItem = {
|
|
||||||
readonly value: string;
|
|
||||||
readonly id: string;
|
|
||||||
readonly level: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseOptions: ParserOptions = {
|
|
||||||
plugins: ['jsx'],
|
|
||||||
sourceType: 'module',
|
|
||||||
};
|
|
||||||
|
|
||||||
const isImport = (child: any): child is Literal =>
|
|
||||||
child.type === 'mdxjsEsm' && child.value.startsWith('import');
|
|
||||||
const hasImports = (index: number) => index > -1;
|
|
||||||
const isExport = (child: any): child is Literal =>
|
|
||||||
child.type === 'mdxjsEsm' && child.value.startsWith('export');
|
|
||||||
|
|
||||||
interface PluginOptions {
|
interface PluginOptions {
|
||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTarget = (child: Literal, name: string) => {
|
// ComponentName (default export) => ImportDeclaration mapping
|
||||||
let found = false;
|
type MarkdownImports = Map<string, {declaration: ImportDeclaration}>;
|
||||||
const ast = parse(child.value, parseOptions);
|
|
||||||
traverse(ast, {
|
|
||||||
VariableDeclarator: (path) => {
|
|
||||||
if ((path.node.id as Identifier).name === name) {
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return found;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOrCreateExistingTargetIndex = async (
|
// MdxjsEsm node representing an already existing "export const toc" declaration
|
||||||
children: Node[],
|
type ExistingTOCExport = MdxjsEsm | null;
|
||||||
name: string,
|
|
||||||
) => {
|
|
||||||
let importsIndex = -1;
|
|
||||||
let targetIndex = -1;
|
|
||||||
|
|
||||||
children.forEach((child, index) => {
|
function createTocSliceImportName({
|
||||||
if (isImport(child)) {
|
tocExportName,
|
||||||
importsIndex = index;
|
componentName,
|
||||||
} else if (isExport(child) && isTarget(child, name)) {
|
}: {
|
||||||
targetIndex = index;
|
tocExportName: string;
|
||||||
|
componentName: string;
|
||||||
|
}) {
|
||||||
|
// The name of the toc slice import alias doesn't matter much
|
||||||
|
// We just need to ensure it's valid and won't conflict with other names
|
||||||
|
return `__${tocExportName}${componentName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectImportsExports({
|
||||||
|
root,
|
||||||
|
tocExportName,
|
||||||
|
}: {
|
||||||
|
root: Root;
|
||||||
|
tocExportName: string;
|
||||||
|
}): Promise<{
|
||||||
|
markdownImports: MarkdownImports;
|
||||||
|
existingTocExport: ExistingTOCExport;
|
||||||
|
}> {
|
||||||
|
const {visit} = await import('unist-util-visit');
|
||||||
|
|
||||||
|
const markdownImports = new Map<string, {declaration: ImportDeclaration}>();
|
||||||
|
let existingTocExport: MdxjsEsm | null = null;
|
||||||
|
|
||||||
|
visit(root, 'mdxjsEsm', (node) => {
|
||||||
|
if (!node.data?.estree) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isNamedExport(node, tocExportName)) {
|
||||||
|
existingTocExport = node;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (targetIndex === -1) {
|
getImportDeclarations(node.data.estree).forEach((declaration) => {
|
||||||
const target = await createExportNode(name, []);
|
if (!isMarkdownImport(declaration)) {
|
||||||
|
|
||||||
targetIndex = hasImports(importsIndex) ? importsIndex + 1 : 0;
|
|
||||||
children.splice(targetIndex, 0, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
return targetIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
const plugin: Plugin = function plugin(
|
|
||||||
options: PluginOptions = {},
|
|
||||||
): Transformer {
|
|
||||||
const name = options.name || 'toc';
|
|
||||||
|
|
||||||
return async (root) => {
|
|
||||||
const {toString} = await import('mdast-util-to-string');
|
|
||||||
const {visit} = await import('unist-util-visit');
|
|
||||||
|
|
||||||
const headings: TOCItem[] = [];
|
|
||||||
|
|
||||||
visit(root, 'heading', (child: Heading) => {
|
|
||||||
const value = toString(child);
|
|
||||||
|
|
||||||
// depth:1 headings are titles and not included in the TOC
|
|
||||||
if (!value || child.depth < 2) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const componentName = findDefaultImportName(declaration);
|
||||||
headings.push({
|
if (!componentName) {
|
||||||
value: toValue(child, toString),
|
return;
|
||||||
id: child.data!.id!,
|
}
|
||||||
level: child.depth,
|
markdownImports.set(componentName, {
|
||||||
|
declaration,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const {children} = root as Parent;
|
return {markdownImports, existingTocExport};
|
||||||
const targetIndex = await getOrCreateExistingTargetIndex(children, name);
|
}
|
||||||
|
|
||||||
if (headings?.length) {
|
async function collectTOCItems({
|
||||||
children[targetIndex] = await createExportNode(name, headings);
|
root,
|
||||||
|
tocExportName,
|
||||||
|
markdownImports,
|
||||||
|
}: {
|
||||||
|
root: Root;
|
||||||
|
tocExportName: string;
|
||||||
|
markdownImports: MarkdownImports;
|
||||||
|
}): Promise<{
|
||||||
|
// The toc items we collected in the tree
|
||||||
|
tocItems: TOCItems;
|
||||||
|
}> {
|
||||||
|
const {toString} = await import('mdast-util-to-string');
|
||||||
|
const {visit} = await import('unist-util-visit');
|
||||||
|
|
||||||
|
const tocItems: TOCItems = [];
|
||||||
|
|
||||||
|
visit(root, (child) => {
|
||||||
|
if (child.type === 'heading') {
|
||||||
|
visitHeading(child);
|
||||||
|
} else if (child.type === 'mdxJsxFlowElement') {
|
||||||
|
visitJSXElement(child);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export default plugin;
|
return {tocItems};
|
||||||
|
|
||||||
async function createExportNode(name: string, object: any): Promise<MdxjsEsm> {
|
// Visit Markdown headings
|
||||||
const {valueToEstree} = await import('estree-util-value-to-estree');
|
function visitHeading(node: Heading) {
|
||||||
|
const value = toString(node);
|
||||||
|
// depth:1 headings are titles and not included in the TOC
|
||||||
|
if (!value || node.depth < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tocItems.push({
|
||||||
|
type: 'heading',
|
||||||
|
heading: node,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
// Visit JSX elements, such as <Partial/>
|
||||||
type: 'mdxjsEsm',
|
function visitJSXElement(node: MdxJsxFlowElement) {
|
||||||
value: `export const ${name} = ${stringifyObject(object)}`,
|
const componentName = node.name;
|
||||||
data: {
|
if (!componentName) {
|
||||||
estree: {
|
return;
|
||||||
type: 'Program',
|
}
|
||||||
body: [
|
const importDeclaration = markdownImports.get(componentName)?.declaration;
|
||||||
{
|
if (!importDeclaration) {
|
||||||
type: 'ExportNamedDeclaration',
|
return;
|
||||||
declaration: {
|
}
|
||||||
type: 'VariableDeclaration',
|
|
||||||
declarations: [
|
const tocSliceImportName = createTocSliceImportName({
|
||||||
{
|
tocExportName,
|
||||||
type: 'VariableDeclarator',
|
componentName,
|
||||||
id: {
|
});
|
||||||
type: 'Identifier',
|
|
||||||
name,
|
tocItems.push({
|
||||||
},
|
type: 'slice',
|
||||||
init: valueToEstree(object),
|
importName: tocSliceImportName,
|
||||||
},
|
});
|
||||||
],
|
|
||||||
kind: 'const',
|
addTocSliceImportIfNeeded({
|
||||||
},
|
importDeclaration,
|
||||||
specifiers: [],
|
tocExportName,
|
||||||
source: null,
|
tocSliceImportName,
|
||||||
},
|
});
|
||||||
],
|
}
|
||||||
sourceType: 'module',
|
}
|
||||||
},
|
|
||||||
},
|
export default function plugin(options: PluginOptions = {}): Transformer<Root> {
|
||||||
|
const tocExportName = options.name || 'toc';
|
||||||
|
|
||||||
|
return async (root) => {
|
||||||
|
const {markdownImports, existingTocExport} = await collectImportsExports({
|
||||||
|
root,
|
||||||
|
tocExportName,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If user explicitly writes "export const toc" in his mdx file
|
||||||
|
// We keep it as is do not override their explicit toc structure
|
||||||
|
// See https://github.com/facebook/docusaurus/pull/7530#discussion_r1458087876
|
||||||
|
if (existingTocExport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {tocItems} = await collectTOCItems({
|
||||||
|
root,
|
||||||
|
tocExportName,
|
||||||
|
markdownImports,
|
||||||
|
});
|
||||||
|
|
||||||
|
root.children.push(
|
||||||
|
await createTOCExportNodeAST({
|
||||||
|
tocExportName,
|
||||||
|
tocItems,
|
||||||
|
}),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
29
packages/docusaurus-mdx-loader/src/remark/toc/types.ts
Normal file
29
packages/docusaurus-mdx-loader/src/remark/toc/types.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {Heading} from 'mdast';
|
||||||
|
|
||||||
|
// Note: this type is exported from mdx-loader and used in theme
|
||||||
|
// Need to keep it retro compatible
|
||||||
|
export type TOCItem = {
|
||||||
|
readonly value: string;
|
||||||
|
readonly id: string;
|
||||||
|
readonly level: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TOCHeading = {
|
||||||
|
readonly type: 'heading';
|
||||||
|
readonly heading: Heading;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A TOC slice represents a TOCItem[] imported from a partial
|
||||||
|
export type TOCSlice = {
|
||||||
|
readonly type: 'slice';
|
||||||
|
readonly importName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TOCItems = (TOCHeading | TOCSlice)[];
|
177
packages/docusaurus-mdx-loader/src/remark/toc/utils.ts
Normal file
177
packages/docusaurus-mdx-loader/src/remark/toc/utils.ts
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {toValue} from '../utils';
|
||||||
|
import type {Node} from 'unist';
|
||||||
|
import type {
|
||||||
|
MdxjsEsm,
|
||||||
|
// @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
|
||||||
|
} from 'mdast-util-mdx';
|
||||||
|
import type {TOCHeading, TOCItem, TOCItems, TOCSlice} from './types';
|
||||||
|
import type {
|
||||||
|
Program,
|
||||||
|
SpreadElement,
|
||||||
|
ImportDeclaration,
|
||||||
|
ImportSpecifier,
|
||||||
|
} from 'estree';
|
||||||
|
|
||||||
|
export function getImportDeclarations(program: Program): ImportDeclaration[] {
|
||||||
|
return program.body.filter(
|
||||||
|
(item): item is ImportDeclaration => item.type === 'ImportDeclaration',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMarkdownImport(node: Node): node is ImportDeclaration {
|
||||||
|
if (node.type !== 'ImportDeclaration') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const importPath = (node as ImportDeclaration).source.value;
|
||||||
|
return typeof importPath === 'string' && /\.mdx?$/.test(importPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findDefaultImportName(
|
||||||
|
importDeclaration: ImportDeclaration,
|
||||||
|
): string | undefined {
|
||||||
|
return importDeclaration.specifiers.find(
|
||||||
|
(o: Node) => o.type === 'ImportDefaultSpecifier',
|
||||||
|
)?.local.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findNamedImportSpecifier(
|
||||||
|
importDeclaration: ImportDeclaration,
|
||||||
|
localName: string,
|
||||||
|
): ImportSpecifier | undefined {
|
||||||
|
return importDeclaration?.specifiers.find(
|
||||||
|
(specifier): specifier is ImportSpecifier =>
|
||||||
|
specifier.type === 'ImportSpecifier' &&
|
||||||
|
specifier.local.name === localName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before: import Partial from "partial"
|
||||||
|
// After: import Partial, {toc as __tocPartial} from "partial"
|
||||||
|
export function addTocSliceImportIfNeeded({
|
||||||
|
importDeclaration,
|
||||||
|
tocExportName,
|
||||||
|
tocSliceImportName,
|
||||||
|
}: {
|
||||||
|
importDeclaration: ImportDeclaration;
|
||||||
|
tocExportName: string;
|
||||||
|
tocSliceImportName: string;
|
||||||
|
}): void {
|
||||||
|
// We only add the toc slice named import if it doesn't exist already
|
||||||
|
if (!findNamedImportSpecifier(importDeclaration, tocSliceImportName)) {
|
||||||
|
importDeclaration.specifiers.push({
|
||||||
|
type: 'ImportSpecifier',
|
||||||
|
imported: {type: 'Identifier', name: tocExportName},
|
||||||
|
local: {type: 'Identifier', name: tocSliceImportName},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNamedExport(
|
||||||
|
node: Node,
|
||||||
|
exportName: string,
|
||||||
|
): node is MdxjsEsm {
|
||||||
|
if (node.type !== 'mdxjsEsm') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const program = (node as MdxjsEsm).data?.estree;
|
||||||
|
if (!program) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (program.body.length !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const exportDeclaration = program.body[0]!;
|
||||||
|
if (exportDeclaration.type !== 'ExportNamedDeclaration') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const variableDeclaration = exportDeclaration.declaration;
|
||||||
|
if (variableDeclaration?.type !== 'VariableDeclaration') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const {id} = variableDeclaration.declarations[0]!;
|
||||||
|
if (id.type !== 'Identifier') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return id.name === exportName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTOCExportNodeAST({
|
||||||
|
tocExportName,
|
||||||
|
tocItems,
|
||||||
|
}: {
|
||||||
|
tocExportName: string;
|
||||||
|
tocItems: TOCItems;
|
||||||
|
}): Promise<MdxjsEsm> {
|
||||||
|
function createTOCSliceAST(tocSlice: TOCSlice): SpreadElement {
|
||||||
|
return {
|
||||||
|
type: 'SpreadElement',
|
||||||
|
argument: {type: 'Identifier', name: tocSlice.importName},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTOCHeadingAST({heading}: TOCHeading) {
|
||||||
|
const {toString} = await import('mdast-util-to-string');
|
||||||
|
const {valueToEstree} = await import('estree-util-value-to-estree');
|
||||||
|
const value: TOCItem = {
|
||||||
|
value: toValue(heading, toString),
|
||||||
|
id: heading.data!.id!,
|
||||||
|
level: heading.depth,
|
||||||
|
};
|
||||||
|
return valueToEstree(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createTOCItemAST(tocItem: TOCItems[number]) {
|
||||||
|
switch (tocItem.type) {
|
||||||
|
case 'slice':
|
||||||
|
return createTOCSliceAST(tocItem);
|
||||||
|
case 'heading':
|
||||||
|
return createTOCHeadingAST(tocItem);
|
||||||
|
default: {
|
||||||
|
throw new Error(`unexpected toc item type`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'mdxjsEsm',
|
||||||
|
value: '', // See https://github.com/facebook/docusaurus/pull/9684#discussion_r1457595181
|
||||||
|
data: {
|
||||||
|
estree: {
|
||||||
|
type: 'Program',
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
type: 'ExportNamedDeclaration',
|
||||||
|
declaration: {
|
||||||
|
type: 'VariableDeclaration',
|
||||||
|
declarations: [
|
||||||
|
{
|
||||||
|
type: 'VariableDeclarator',
|
||||||
|
id: {
|
||||||
|
type: 'Identifier',
|
||||||
|
name: tocExportName,
|
||||||
|
},
|
||||||
|
init: {
|
||||||
|
type: 'ArrayExpression',
|
||||||
|
elements: await Promise.all(tocItems.map(createTOCItemAST)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
kind: 'const',
|
||||||
|
},
|
||||||
|
specifiers: [],
|
||||||
|
source: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -9,10 +9,6 @@ tags: [paginated-tag]
|
||||||
|
|
||||||
{/* truncate */}
|
{/* truncate */}
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import SecondLevelPartial from './_second-level-partial.mdx';
|
||||||
|
|
||||||
|
## 1st level partial
|
||||||
|
|
||||||
|
I'm 1 level deep.
|
||||||
|
|
||||||
|
<SecondLevelPartial />
|
|
@ -0,0 +1,19 @@
|
||||||
|
## Partial
|
||||||
|
|
||||||
|
Partial intro
|
||||||
|
|
||||||
|
### Partial Sub Heading 1
|
||||||
|
|
||||||
|
Partial Sub Heading 1 content
|
||||||
|
|
||||||
|
#### Partial Sub Sub Heading 1
|
||||||
|
|
||||||
|
Partial Sub Sub Heading 1 content
|
||||||
|
|
||||||
|
### Partial Sub Heading 2
|
||||||
|
|
||||||
|
Partial Sub Heading 2 content
|
||||||
|
|
||||||
|
#### Partial Sub Sub Heading 2
|
||||||
|
|
||||||
|
Partial Sub Sub Heading 2 content
|
|
@ -0,0 +1,3 @@
|
||||||
|
### 2nd level partial
|
||||||
|
|
||||||
|
I'm 2 levels deep.
|
46
website/_dogfooding/_docs tests/tests/toc-partials/index.mdx
Normal file
46
website/_dogfooding/_docs tests/tests/toc-partials/index.mdx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import Partial from './_partial.mdx';
|
||||||
|
|
||||||
|
# TOC partial test
|
||||||
|
|
||||||
|
This page tests that MDX-imported content appears correctly in the table-of-contents
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
- https://github.com/facebook/docusaurus/issues/3915
|
||||||
|
- https://github.com/facebook/docusaurus/pull/9684
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**The table of contents should include headings of this partial**:
|
||||||
|
|
||||||
|
<Partial />
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**We can import the same partial using a different name and it still works**:
|
||||||
|
|
||||||
|
import WeirdLocalName from './_partial.mdx';
|
||||||
|
|
||||||
|
<WeirdLocalName />
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**We can import a partial and not use it, the TOC remains unaffected**:
|
||||||
|
|
||||||
|
import UnusedPartial from './_partial.mdx';
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
import FirstLevelPartial from './_first-level-partial.mdx';
|
||||||
|
|
||||||
|
**It also works for partials importing other partials**
|
||||||
|
|
||||||
|
<FirstLevelPartial />
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**And we can even use the same partial twice!**
|
||||||
|
|
||||||
|
**(although it's useless and not particularly recommended because headings will have the same ids)**
|
||||||
|
|
||||||
|
<FirstLevelPartial />
|
|
@ -3,10 +3,6 @@ toc_min_heading_level: 2
|
||||||
toc_max_heading_level: 2
|
toc_max_heading_level: 2
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@ toc_min_heading_level: 2
|
||||||
toc_max_heading_level: 3
|
toc_max_heading_level: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@ toc_min_heading_level: 2
|
||||||
toc_max_heading_level: 4
|
toc_max_heading_level: 4
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@ toc_min_heading_level: 2
|
||||||
toc_max_heading_level: 5
|
toc_max_heading_level: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@ toc_min_heading_level: 3
|
||||||
toc_max_heading_level: 5
|
toc_max_heading_level: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@ toc_min_heading_level: 3
|
||||||
# toc_max_heading_level:
|
# toc_max_heading_level:
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@ toc_min_heading_level: 4
|
||||||
toc_max_heading_level: 5
|
toc_max_heading_level: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@ toc_min_heading_level: 5
|
||||||
toc_max_heading_level: 5
|
toc_max_heading_level: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
toc_max_heading_level: 5
|
toc_max_heading_level: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
# toc_max_heading_level:
|
# toc_max_heading_level:
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -3,10 +3,6 @@ toc_min_heading_level: 2
|
||||||
toc_max_heading_level: 4
|
toc_max_heading_level: 4
|
||||||
---
|
---
|
||||||
|
|
||||||
import Content, {
|
import Content from '@site/_dogfooding/_partials/toc-tests.mdx';
|
||||||
toc as ContentToc,
|
|
||||||
} from '@site/_dogfooding/_partials/toc-tests.mdx';
|
|
||||||
|
|
||||||
<Content />
|
<Content />
|
||||||
|
|
||||||
export const toc = ContentToc;
|
|
||||||
|
|
|
@ -5,9 +5,7 @@ sidebar_label: Contributing
|
||||||
---
|
---
|
||||||
|
|
||||||
```mdx-code-block
|
```mdx-code-block
|
||||||
import Contributing, {toc as ContributingTOC} from "@site/../CONTRIBUTING.md"
|
import Contributing from "@site/../CONTRIBUTING.md"
|
||||||
|
|
||||||
<Contributing />
|
<Contributing />
|
||||||
|
|
||||||
export const toc = ContributingTOC;
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -190,16 +190,21 @@ export default async function createConfigAsync() {
|
||||||
preprocessor: ({filePath, fileContent}) => {
|
preprocessor: ({filePath, fileContent}) => {
|
||||||
let result = fileContent;
|
let result = fileContent;
|
||||||
|
|
||||||
|
// This fixes Crowdin bug altering MDX comments on i18n sites...
|
||||||
|
// https://github.com/facebook/docusaurus/pull/9220
|
||||||
result = result.replaceAll('{/_', '{/*');
|
result = result.replaceAll('{/_', '{/*');
|
||||||
result = result.replaceAll('_/}', '*/}');
|
result = result.replaceAll('_/}', '*/}');
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
// "vscode://file/${projectPath}${filePath}:${line}:${column}",
|
const isPartial = path.basename(filePath).startsWith('_');
|
||||||
// "webstorm://open?file=${projectPath}${filePath}&line=${line}&column=${column}",
|
if (!isPartial) {
|
||||||
const vscodeLink = `vscode://file/${filePath}`;
|
// "vscode://file/${projectPath}${filePath}:${line}:${column}",
|
||||||
const webstormLink = `webstorm://open?file=${filePath}`;
|
// "webstorm://open?file=${projectPath}${filePath}&line=${line}&column=${column}",
|
||||||
const intellijLink = `idea://open?file=${filePath}`;
|
const vscodeLink = `vscode://file/${filePath}`;
|
||||||
result = `${result}\n\n---\n\n**DEV**: open this file in [VSCode](<${vscodeLink}>) | [WebStorm](<${webstormLink}>) | [IntelliJ](<${intellijLink}>)\n`;
|
const webstormLink = `webstorm://open?file=${filePath}`;
|
||||||
|
const intellijLink = `idea://open?file=${filePath}`;
|
||||||
|
result = `${result}\n\n---\n\n**DEV**: open this file in [VSCode](<${vscodeLink}>) | [WebStorm](<${webstormLink}>) | [IntelliJ](<${intellijLink}>)\n`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -434,7 +434,7 @@
|
||||||
chalk "^2.4.2"
|
chalk "^2.4.2"
|
||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.7", "@babel/parser@^7.23.3":
|
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.3":
|
||||||
version "7.23.3"
|
version "7.23.3"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.3.tgz#0ce0be31a4ca4f1884b5786057cadcb6c3be58f9"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.3.tgz#0ce0be31a4ca4f1884b5786057cadcb6c3be58f9"
|
||||||
integrity sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==
|
integrity sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==
|
||||||
|
|
Loading…
Add table
Reference in a new issue