@docusaurus/mdx-loader 2.0.0-beta.13 → 2.0.0-beta.15
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/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +11 -5
- package/lib/index.js.map +1 -0
- package/lib/remark/headings/index.d.ts +1 -0
- package/lib/remark/headings/index.d.ts.map +1 -0
- package/lib/remark/headings/index.js +1 -0
- package/lib/remark/headings/index.js.map +1 -0
- package/lib/remark/toc/index.d.ts +1 -0
- package/lib/remark/toc/index.d.ts.map +1 -0
- package/lib/remark/toc/index.js +1 -0
- package/lib/remark/toc/index.js.map +1 -0
- package/lib/remark/toc/search.d.ts +1 -0
- package/lib/remark/toc/search.d.ts.map +1 -0
- package/lib/remark/toc/search.js +4 -4
- package/lib/remark/toc/search.js.map +1 -0
- package/lib/remark/transformImage/index.d.ts +4 -3
- package/lib/remark/transformImage/index.d.ts.map +1 -0
- package/lib/remark/transformImage/index.js +63 -42
- package/lib/remark/transformImage/index.js.map +1 -0
- package/lib/remark/transformLinks/index.d.ts +3 -3
- package/lib/remark/transformLinks/index.d.ts.map +1 -0
- package/lib/remark/transformLinks/index.js +52 -55
- package/lib/remark/transformLinks/index.js.map +1 -0
- package/lib/remark/unwrapMdxCodeBlocks/index.d.ts +1 -0
- package/lib/remark/unwrapMdxCodeBlocks/index.d.ts.map +1 -0
- package/lib/remark/unwrapMdxCodeBlocks/index.js +1 -0
- package/lib/remark/unwrapMdxCodeBlocks/index.js.map +1 -0
- package/lib/remark/utils/index.d.ts +1 -0
- package/lib/remark/utils/index.d.ts.map +1 -0
- package/lib/remark/utils/index.js +1 -0
- package/lib/remark/utils/index.js.map +1 -0
- package/package.json +6 -7
- package/src/index.ts +10 -5
- package/src/remark/headings/index.ts +1 -1
- package/src/remark/toc/index.ts +1 -1
- package/src/remark/toc/search.ts +4 -6
- package/src/remark/transformImage/index.ts +101 -68
- package/src/remark/transformLinks/index.ts +76 -91
- package/lib/.tsbuildinfo +0 -1
- package/src/remark/headings/__tests__/index.test.ts +0 -313
- package/src/remark/toc/__tests__/__snapshots__/index.test.ts.snap +0 -95
- package/src/remark/toc/__tests__/fixtures/empty-headings.md +0 -5
- package/src/remark/toc/__tests__/fixtures/inline-code.md +0 -9
- package/src/remark/toc/__tests__/fixtures/insert-below-imports.md +0 -11
- package/src/remark/toc/__tests__/fixtures/just-content.md +0 -15
- package/src/remark/toc/__tests__/fixtures/name-exist.md +0 -7
- package/src/remark/toc/__tests__/fixtures/non-text-content.md +0 -9
- package/src/remark/toc/__tests__/index.test.ts +0 -226
- package/src/remark/toc/__tests__/search.test.ts +0 -182
- package/src/remark/transformImage/__tests__/__fixtures__/fail.md +0 -1
- package/src/remark/transformImage/__tests__/__fixtures__/img.md +0 -19
- package/src/remark/transformImage/__tests__/__fixtures__/noUrl.md +0 -1
- package/src/remark/transformImage/__tests__/__fixtures__/pathname.md +0 -1
- package/src/remark/transformImage/__tests__/__fixtures__/static/img.png +0 -0
- package/src/remark/transformImage/__tests__/__fixtures__/static2/img2.png +0 -0
- package/src/remark/transformImage/__tests__/__snapshots__/index.test.ts.snap +0 -33
- package/src/remark/transformImage/__tests__/index.test.ts +0 -63
- package/src/remark/transformLinks/__tests__/__fixtures__/asset.md +0 -35
- package/src/remark/transformLinks/__tests__/__fixtures__/asset.pdf +0 -0
- package/src/remark/transformLinks/__tests__/__fixtures__/noUrl.md +0 -1
- package/src/remark/transformLinks/__tests__/__fixtures__/pathname.md +0 -1
- package/src/remark/transformLinks/__tests__/__fixtures__/static/staticAsset.pdf +0 -0
- package/src/remark/transformLinks/__tests__/__fixtures__/static/staticAssetImage.png +0 -0
- package/src/remark/transformLinks/__tests__/__fixtures__/static2/asset2.pdf +0 -0
- package/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap +0 -47
- package/src/remark/transformLinks/__tests__/index.test.ts +0 -55
- package/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.ts.snap +0 -819
- package/src/remark/unwrapMdxCodeBlocks/__tests__/fixtures/has-mdx-code-blocks.mdx +0 -95
- package/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.ts +0 -38
- package/tsconfig.json +0 -9
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {readFile} from 'fs-extra';
|
|
9
9
|
import mdx from '@mdx-js/mdx';
|
|
10
|
-
import
|
|
10
|
+
import logger from '@docusaurus/logger';
|
|
11
11
|
import emoji from 'remark-emoji';
|
|
12
12
|
import {
|
|
13
13
|
parseFrontMatter,
|
|
@@ -124,12 +124,17 @@ export default async function mdxLoader(
|
|
|
124
124
|
remarkPlugins: [
|
|
125
125
|
...(reqOptions.beforeDefaultRemarkPlugins || []),
|
|
126
126
|
...DEFAULT_OPTIONS.remarkPlugins,
|
|
127
|
-
[
|
|
127
|
+
[
|
|
128
|
+
transformImage,
|
|
129
|
+
{
|
|
130
|
+
staticDirs: reqOptions.staticDirs,
|
|
131
|
+
siteDir: reqOptions.siteDir,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
128
134
|
[
|
|
129
135
|
transformLinks,
|
|
130
136
|
{
|
|
131
137
|
staticDirs: reqOptions.staticDirs,
|
|
132
|
-
filePath,
|
|
133
138
|
siteDir: reqOptions.siteDir,
|
|
134
139
|
},
|
|
135
140
|
],
|
|
@@ -151,7 +156,7 @@ export default async function mdxLoader(
|
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
// MDX partials are MDX files starting with _ or in a folder starting with _
|
|
154
|
-
// Partial are not expected to have an associated metadata file or
|
|
159
|
+
// Partial are not expected to have an associated metadata file or front matter
|
|
155
160
|
const isMDXPartial = options.isMDXPartial && options.isMDXPartial(filePath);
|
|
156
161
|
if (isMDXPartial && hasFrontMatter) {
|
|
157
162
|
const errorMessage = `Docusaurus MDX partial files should not contain FrontMatter.
|
|
@@ -164,7 +169,7 @@ ${JSON.stringify(frontMatter, null, 2)}`;
|
|
|
164
169
|
if (shouldError) {
|
|
165
170
|
return callback(new Error(errorMessage));
|
|
166
171
|
} else {
|
|
167
|
-
|
|
172
|
+
logger.warn(errorMessage);
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/* Based on remark-slug (https://github.com/remarkjs/remark-slug) and gatsby-remark-autolink-headers (https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-autolink-headers) */
|
|
9
9
|
|
|
10
10
|
import {parseMarkdownHeadingId, createSlugger} from '@docusaurus/utils';
|
|
11
|
-
import visit, {Visitor} from 'unist-util-visit';
|
|
11
|
+
import visit, {type Visitor} from 'unist-util-visit';
|
|
12
12
|
import toString from 'mdast-util-to-string';
|
|
13
13
|
import type {Transformer} from 'unified';
|
|
14
14
|
import type {Parent} from 'unist';
|
package/src/remark/toc/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {parse, ParserOptions} from '@babel/parser';
|
|
8
|
+
import {parse, type ParserOptions} from '@babel/parser';
|
|
9
9
|
import type {Identifier} from '@babel/types';
|
|
10
10
|
import traverse from '@babel/traverse';
|
|
11
11
|
import stringifyObject from 'stringify-object';
|
package/src/remark/toc/search.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import toString from 'mdast-util-to-string';
|
|
9
|
-
import visit
|
|
9
|
+
import visit from 'unist-util-visit';
|
|
10
10
|
import {toValue} from '../utils';
|
|
11
11
|
import type {TOCItem} from '@docusaurus/types';
|
|
12
12
|
import type {Node} from 'unist';
|
|
@@ -26,7 +26,7 @@ interface SearchItem {
|
|
|
26
26
|
export default function search(node: Node): TOCItem[] {
|
|
27
27
|
const headings: SearchItem[] = [];
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
visit(node, 'heading', (child: Heading, _index, parent) => {
|
|
30
30
|
const value = toString(child);
|
|
31
31
|
|
|
32
32
|
// depth:1 headings are titles and not included in the TOC
|
|
@@ -44,11 +44,9 @@ export default function search(node: Node): TOCItem[] {
|
|
|
44
44
|
level: child.depth,
|
|
45
45
|
parentIndex: -1,
|
|
46
46
|
});
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
visit(node, 'heading', visitor);
|
|
47
|
+
});
|
|
50
48
|
|
|
51
|
-
// Keep track of which previous index would be the current heading's
|
|
49
|
+
// Keep track of which previous index would be the current heading's direct parent.
|
|
52
50
|
// Each entry <i> is the last index of the `headings` array at heading level <i>.
|
|
53
51
|
// We will modify these indices as we iterate through all headings.
|
|
54
52
|
// e.g. if an ### H3 was last seen at index 2, then prevIndexForLevel[3] === 2
|
|
@@ -5,53 +5,80 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import visit from 'unist-util-visit';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import url from 'url';
|
|
11
|
-
import fs from 'fs-extra';
|
|
12
|
-
import escapeHtml from 'escape-html';
|
|
13
8
|
import {
|
|
9
|
+
toMessageRelativeFilePath,
|
|
14
10
|
posixPath,
|
|
15
11
|
escapePath,
|
|
16
|
-
toMessageRelativeFilePath,
|
|
17
12
|
getFileLoaderUtils,
|
|
13
|
+
findAsyncSequential,
|
|
18
14
|
} from '@docusaurus/utils';
|
|
15
|
+
import visit from 'unist-util-visit';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import url from 'url';
|
|
18
|
+
import fs from 'fs-extra';
|
|
19
|
+
import escapeHtml from 'escape-html';
|
|
20
|
+
import sizeOf from 'image-size';
|
|
21
|
+
import {promisify} from 'util';
|
|
19
22
|
import type {Plugin, Transformer} from 'unified';
|
|
20
23
|
import type {Image, Literal} from 'mdast';
|
|
24
|
+
import logger from '@docusaurus/logger';
|
|
21
25
|
|
|
22
26
|
const {
|
|
23
27
|
loaders: {inlineMarkdownImageFileLoader},
|
|
24
28
|
} = getFileLoaderUtils();
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
filePath: string;
|
|
30
|
+
type PluginOptions = {
|
|
28
31
|
staticDirs: string[];
|
|
29
|
-
|
|
32
|
+
siteDir: string;
|
|
33
|
+
};
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
(jsxNode as unknown as Literal).type = 'jsx';
|
|
34
|
-
(jsxNode as unknown as Literal).value = `<img ${
|
|
35
|
-
node.alt ? `alt={"${escapeHtml(node.alt)}"} ` : ''
|
|
36
|
-
}${
|
|
37
|
-
node.url
|
|
38
|
-
? `src={require("${inlineMarkdownImageFileLoader}${escapePath(
|
|
39
|
-
pathUrl,
|
|
40
|
-
)}").default}`
|
|
41
|
-
: ''
|
|
42
|
-
}${node.title ? ` title="${escapeHtml(node.title)}"` : ''} />`;
|
|
43
|
-
|
|
44
|
-
if (jsxNode.url) {
|
|
45
|
-
delete (jsxNode as Partial<Image>).url;
|
|
46
|
-
}
|
|
47
|
-
if (jsxNode.alt) {
|
|
48
|
-
delete jsxNode.alt;
|
|
49
|
-
}
|
|
50
|
-
if (jsxNode.title) {
|
|
51
|
-
delete jsxNode.title;
|
|
52
|
-
}
|
|
35
|
+
type Context = PluginOptions & {
|
|
36
|
+
filePath: string;
|
|
53
37
|
};
|
|
54
38
|
|
|
39
|
+
async function toImageRequireNode(
|
|
40
|
+
node: Image,
|
|
41
|
+
imagePath: string,
|
|
42
|
+
filePath: string,
|
|
43
|
+
) {
|
|
44
|
+
const jsxNode = node as Literal & Partial<Image>;
|
|
45
|
+
let relativeImagePath = posixPath(
|
|
46
|
+
path.relative(path.dirname(filePath), imagePath),
|
|
47
|
+
);
|
|
48
|
+
relativeImagePath = `./${relativeImagePath}`;
|
|
49
|
+
|
|
50
|
+
const parsedUrl = url.parse(node.url);
|
|
51
|
+
const hash = parsedUrl.hash ?? '';
|
|
52
|
+
const search = parsedUrl.search ?? '';
|
|
53
|
+
|
|
54
|
+
const alt = node.alt ? `alt={"${escapeHtml(node.alt)}"} ` : '';
|
|
55
|
+
const src = `require("${inlineMarkdownImageFileLoader}${
|
|
56
|
+
escapePath(relativeImagePath) + search
|
|
57
|
+
}").default${hash ? ` + '${hash}'` : ''}`;
|
|
58
|
+
const title = node.title ? ` title="${escapeHtml(node.title)}"` : '';
|
|
59
|
+
let width = '';
|
|
60
|
+
let height = '';
|
|
61
|
+
try {
|
|
62
|
+
const size = (await promisify(sizeOf)(imagePath))!;
|
|
63
|
+
if (size.width) {
|
|
64
|
+
width = ` width="${size.width}"`;
|
|
65
|
+
}
|
|
66
|
+
if (size.height) {
|
|
67
|
+
height = ` height="${size.height}"`;
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
logger.error`The image at path=${imagePath} can't be read correctly. Please ensure it's a valid image.
|
|
71
|
+
${(e as Error).message}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Object.keys(jsxNode).forEach(
|
|
75
|
+
(key) => delete jsxNode[key as keyof typeof jsxNode],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
(jsxNode as Literal).type = 'jsx';
|
|
79
|
+
jsxNode.value = `<img ${alt}src={${src}}${title}${width}${height} />`;
|
|
80
|
+
}
|
|
81
|
+
|
|
55
82
|
async function ensureImageFileExist(imagePath: string, sourceFilePath: string) {
|
|
56
83
|
const imageExists = await fs.pathExists(imagePath);
|
|
57
84
|
if (!imageExists) {
|
|
@@ -63,36 +90,53 @@ async function ensureImageFileExist(imagePath: string, sourceFilePath: string) {
|
|
|
63
90
|
}
|
|
64
91
|
}
|
|
65
92
|
|
|
66
|
-
async function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
async function getImageAbsolutePath(
|
|
94
|
+
imagePath: string,
|
|
95
|
+
{siteDir, filePath, staticDirs}: Context,
|
|
96
|
+
) {
|
|
97
|
+
if (imagePath.startsWith('@site/')) {
|
|
98
|
+
const imageFilePath = path.join(siteDir, imagePath.replace('@site/', ''));
|
|
99
|
+
await ensureImageFileExist(imageFilePath, filePath);
|
|
100
|
+
return imageFilePath;
|
|
101
|
+
} else if (path.isAbsolute(imagePath)) {
|
|
102
|
+
// absolute paths are expected to exist in the static folder
|
|
103
|
+
const possiblePaths = staticDirs.map((dir) => path.join(dir, imagePath));
|
|
104
|
+
const imageFilePath = await findAsyncSequential(
|
|
105
|
+
possiblePaths,
|
|
106
|
+
fs.pathExists,
|
|
107
|
+
);
|
|
108
|
+
if (!imageFilePath) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Image ${possiblePaths
|
|
111
|
+
.map((p) => toMessageRelativeFilePath(p))
|
|
112
|
+
.join(' or ')} used in ${toMessageRelativeFilePath(
|
|
113
|
+
filePath,
|
|
114
|
+
)} not found.`,
|
|
115
|
+
);
|
|
71
116
|
}
|
|
117
|
+
return imageFilePath;
|
|
118
|
+
}
|
|
119
|
+
// We try to convert image urls without protocol to images with require calls
|
|
120
|
+
// going through webpack ensures that image assets exist at build time
|
|
121
|
+
else {
|
|
122
|
+
// relative paths are resolved against the source file's folder
|
|
123
|
+
const imageFilePath = path.join(path.dirname(filePath), imagePath);
|
|
124
|
+
await ensureImageFileExist(imageFilePath, filePath);
|
|
125
|
+
return imageFilePath;
|
|
72
126
|
}
|
|
73
|
-
throw new Error(
|
|
74
|
-
`Image ${possiblePaths
|
|
75
|
-
.map((p) => toMessageRelativeFilePath(p))
|
|
76
|
-
.join(' or ')} used in ${toMessageRelativeFilePath(
|
|
77
|
-
sourceFilePath,
|
|
78
|
-
)} not found.`,
|
|
79
|
-
);
|
|
80
127
|
}
|
|
81
128
|
|
|
82
|
-
async function processImageNode(
|
|
83
|
-
node: Image,
|
|
84
|
-
{filePath, staticDirs}: PluginOptions,
|
|
85
|
-
) {
|
|
129
|
+
async function processImageNode(node: Image, context: Context) {
|
|
86
130
|
if (!node.url) {
|
|
87
131
|
throw new Error(
|
|
88
132
|
`Markdown image URL is mandatory in "${toMessageRelativeFilePath(
|
|
89
|
-
filePath,
|
|
133
|
+
context.filePath,
|
|
90
134
|
)}" file`,
|
|
91
135
|
);
|
|
92
136
|
}
|
|
93
137
|
|
|
94
138
|
const parsedUrl = url.parse(node.url);
|
|
95
|
-
if (parsedUrl.protocol) {
|
|
139
|
+
if (parsedUrl.protocol || !parsedUrl.pathname) {
|
|
96
140
|
// pathname:// is an escape hatch,
|
|
97
141
|
// in case user does not want his images to be converted to require calls going through webpack loader
|
|
98
142
|
// we don't have to document this for now,
|
|
@@ -100,31 +144,20 @@ async function processImageNode(
|
|
|
100
144
|
if (parsedUrl.protocol === 'pathname:') {
|
|
101
145
|
node.url = node.url.replace('pathname://', '');
|
|
102
146
|
}
|
|
147
|
+
return;
|
|
103
148
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const possibleImagePaths = staticDirs.map((dir) =>
|
|
108
|
-
path.join(dir, node.url),
|
|
109
|
-
);
|
|
110
|
-
const imagePath = await findImage(possibleImagePaths, filePath);
|
|
111
|
-
createJSX(node, posixPath(imagePath));
|
|
112
|
-
}
|
|
113
|
-
// We try to convert image urls without protocol to images with require calls
|
|
114
|
-
// going through webpack ensures that image assets exist at build time
|
|
115
|
-
else {
|
|
116
|
-
// relative paths are resolved against the source file's folder
|
|
117
|
-
const expectedImagePath = path.join(path.dirname(filePath), node.url);
|
|
118
|
-
await ensureImageFileExist(expectedImagePath, filePath);
|
|
119
|
-
createJSX(node, node.url.startsWith('./') ? node.url : `./${node.url}`);
|
|
120
|
-
}
|
|
149
|
+
|
|
150
|
+
const imagePath = await getImageAbsolutePath(parsedUrl.pathname, context);
|
|
151
|
+
await toImageRequireNode(node, imagePath, context.filePath);
|
|
121
152
|
}
|
|
122
153
|
|
|
123
154
|
const plugin: Plugin<[PluginOptions]> = (options) => {
|
|
124
|
-
const transformer: Transformer = async (root) => {
|
|
155
|
+
const transformer: Transformer = async (root, vfile) => {
|
|
125
156
|
const promises: Promise<void>[] = [];
|
|
126
157
|
visit(root, 'image', (node: Image) => {
|
|
127
|
-
promises.push(
|
|
158
|
+
promises.push(
|
|
159
|
+
processImageNode(node, {...options, filePath: vfile.path!}),
|
|
160
|
+
);
|
|
128
161
|
});
|
|
129
162
|
await Promise.all(promises);
|
|
130
163
|
};
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
posixPath,
|
|
11
11
|
escapePath,
|
|
12
12
|
getFileLoaderUtils,
|
|
13
|
+
findAsyncSequential,
|
|
13
14
|
} from '@docusaurus/utils';
|
|
14
15
|
import visit from 'unist-util-visit';
|
|
15
16
|
import path from 'path';
|
|
@@ -23,114 +24,87 @@ import type {Link, Literal} from 'mdast';
|
|
|
23
24
|
const {
|
|
24
25
|
loaders: {inlineMarkdownLinkFileLoader},
|
|
25
26
|
} = getFileLoaderUtils();
|
|
26
|
-
const hashRegex = /#.*$/;
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
filePath: string;
|
|
28
|
+
type PluginOptions = {
|
|
30
29
|
staticDirs: string[];
|
|
31
30
|
siteDir: string;
|
|
32
|
-
}
|
|
31
|
+
};
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
) {
|
|
38
|
-
const assetExists = await fs.pathExists(fileSystemAssetPath);
|
|
39
|
-
if (!assetExists) {
|
|
40
|
-
throw new Error(
|
|
41
|
-
`Asset ${toMessageRelativeFilePath(
|
|
42
|
-
fileSystemAssetPath,
|
|
43
|
-
)} used in ${toMessageRelativeFilePath(sourceFilePath)} not found.`,
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
33
|
+
type Context = PluginOptions & {
|
|
34
|
+
filePath: string;
|
|
35
|
+
};
|
|
47
36
|
|
|
48
37
|
// transform the link node to a jsx link with a require() call
|
|
49
|
-
function toAssetRequireNode({
|
|
50
|
-
node
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}: {
|
|
54
|
-
node: Link;
|
|
55
|
-
filePath: string;
|
|
56
|
-
requireAssetPath: string;
|
|
57
|
-
}) {
|
|
58
|
-
let relativeRequireAssetPath = posixPath(
|
|
59
|
-
path.relative(path.dirname(filePath), requireAssetPath),
|
|
38
|
+
function toAssetRequireNode(node: Link, assetPath: string, filePath: string) {
|
|
39
|
+
const jsxNode = node as Literal & Partial<Link>;
|
|
40
|
+
let relativeAssetPath = posixPath(
|
|
41
|
+
path.relative(path.dirname(filePath), assetPath),
|
|
60
42
|
);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
43
|
+
// require("assets/file.pdf") means requiring from a package called assets
|
|
44
|
+
relativeAssetPath = `./${relativeAssetPath}`;
|
|
45
|
+
|
|
46
|
+
const parsedUrl = url.parse(node.url);
|
|
47
|
+
const hash = parsedUrl.hash ?? '';
|
|
48
|
+
const search = parsedUrl.search ?? '';
|
|
49
|
+
|
|
50
|
+
const href = `require('${
|
|
51
|
+
// A hack to stop Webpack from using its built-in loader to parse JSON
|
|
52
|
+
path.extname(relativeAssetPath) === '.json'
|
|
53
|
+
? `${relativeAssetPath.replace('.json', '.raw')}!=`
|
|
54
|
+
: ''
|
|
55
|
+
}${inlineMarkdownLinkFileLoader}${
|
|
56
|
+
escapePath(relativeAssetPath) + search
|
|
57
|
+
}').default${hash ? ` + '${hash}'` : ''}`;
|
|
73
58
|
const children = stringifyContent(node);
|
|
74
59
|
const title = node.title ? ` title="${escapeHtml(node.title)}"` : '';
|
|
75
60
|
|
|
76
|
-
(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
).value = `<a target="_blank" href={${href}}${title}>${children}</a>`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// If the link looks like an asset link, we'll link to the asset,
|
|
83
|
-
// and use a require("assetUrl") (using webpack url-loader/file-loader)
|
|
84
|
-
// instead of navigating to such link
|
|
85
|
-
async function convertToAssetLinkIfNeeded(
|
|
86
|
-
node: Link,
|
|
87
|
-
{filePath, siteDir, staticDirs}: PluginOptions,
|
|
88
|
-
) {
|
|
89
|
-
const assetPath = node.url.replace(hashRegex, '');
|
|
90
|
-
|
|
91
|
-
const hasSiteAlias = assetPath.startsWith('@site/');
|
|
92
|
-
const hasAssetLikeExtension =
|
|
93
|
-
path.extname(assetPath) && !assetPath.match(/#|.md|.mdx|.html/);
|
|
94
|
-
|
|
95
|
-
const looksLikeAssetLink = hasSiteAlias || hasAssetLikeExtension;
|
|
61
|
+
Object.keys(jsxNode).forEach(
|
|
62
|
+
(key) => delete jsxNode[key as keyof typeof jsxNode],
|
|
63
|
+
);
|
|
96
64
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
65
|
+
(jsxNode as Literal).type = 'jsx';
|
|
66
|
+
jsxNode.value = `<a target="_blank" href={${href}}${title}>${children}</a>`;
|
|
67
|
+
}
|
|
100
68
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
69
|
+
async function ensureAssetFileExist(assetPath: string, sourceFilePath: string) {
|
|
70
|
+
const assetExists = await fs.pathExists(assetPath);
|
|
71
|
+
if (!assetExists) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Asset ${toMessageRelativeFilePath(
|
|
74
|
+
assetPath,
|
|
75
|
+
)} used in ${toMessageRelativeFilePath(sourceFilePath)} not found.`,
|
|
76
|
+
);
|
|
107
77
|
}
|
|
78
|
+
}
|
|
108
79
|
|
|
80
|
+
async function getAssetAbsolutePath(
|
|
81
|
+
assetPath: string,
|
|
82
|
+
{siteDir, filePath, staticDirs}: Context,
|
|
83
|
+
) {
|
|
109
84
|
if (assetPath.startsWith('@site/')) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
toAssetLinkNode(fileSystemAssetPath);
|
|
85
|
+
const assetFilePath = path.join(siteDir, assetPath.replace('@site/', ''));
|
|
86
|
+
// The @site alias is the only way to believe that the user wants an asset.
|
|
87
|
+
// Everything else can just be a link URL
|
|
88
|
+
await ensureAssetFileExist(assetFilePath, filePath);
|
|
89
|
+
return assetFilePath;
|
|
116
90
|
} else if (path.isAbsolute(assetPath)) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
91
|
+
const assetFilePath = await findAsyncSequential(
|
|
92
|
+
staticDirs.map((dir) => path.join(dir, assetPath)),
|
|
93
|
+
fs.pathExists,
|
|
94
|
+
);
|
|
95
|
+
if (assetFilePath) {
|
|
96
|
+
return assetFilePath;
|
|
124
97
|
}
|
|
125
98
|
} else {
|
|
126
|
-
const
|
|
127
|
-
if (await fs.pathExists(
|
|
128
|
-
|
|
99
|
+
const assetFilePath = path.join(path.dirname(filePath), assetPath);
|
|
100
|
+
if (await fs.pathExists(assetFilePath)) {
|
|
101
|
+
return assetFilePath;
|
|
129
102
|
}
|
|
130
103
|
}
|
|
104
|
+
return null;
|
|
131
105
|
}
|
|
132
106
|
|
|
133
|
-
async function processLinkNode(node: Link,
|
|
107
|
+
async function processLinkNode(node: Link, context: Context) {
|
|
134
108
|
if (!node.url) {
|
|
135
109
|
// try to improve error feedback
|
|
136
110
|
// see https://github.com/facebook/docusaurus/issues/3309#issuecomment-690371675
|
|
@@ -138,24 +112,35 @@ async function processLinkNode(node: Link, options: PluginOptions) {
|
|
|
138
112
|
const line = node?.position?.start?.line || '?';
|
|
139
113
|
throw new Error(
|
|
140
114
|
`Markdown link URL is mandatory in "${toMessageRelativeFilePath(
|
|
141
|
-
|
|
115
|
+
context.filePath,
|
|
142
116
|
)}" file (title: ${title}, line: ${line}).`,
|
|
143
117
|
);
|
|
144
118
|
}
|
|
145
119
|
|
|
146
120
|
const parsedUrl = url.parse(node.url);
|
|
147
|
-
if (parsedUrl.protocol) {
|
|
121
|
+
if (parsedUrl.protocol || !parsedUrl.pathname) {
|
|
122
|
+
// Don't process pathname:// here, it's used by the <Link> component
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const hasSiteAlias = parsedUrl.pathname.startsWith('@site/');
|
|
126
|
+
const hasAssetLikeExtension =
|
|
127
|
+
path.extname(parsedUrl.pathname) &&
|
|
128
|
+
!parsedUrl.pathname.match(/\.(?:mdx?|html)(?:#|$)/);
|
|
129
|
+
if (!hasSiteAlias && !hasAssetLikeExtension) {
|
|
148
130
|
return;
|
|
149
131
|
}
|
|
150
132
|
|
|
151
|
-
await
|
|
133
|
+
const assetPath = await getAssetAbsolutePath(parsedUrl.pathname, context);
|
|
134
|
+
if (assetPath) {
|
|
135
|
+
toAssetRequireNode(node, assetPath, context.filePath);
|
|
136
|
+
}
|
|
152
137
|
}
|
|
153
138
|
|
|
154
139
|
const plugin: Plugin<[PluginOptions]> = (options) => {
|
|
155
|
-
const transformer: Transformer = async (root) => {
|
|
140
|
+
const transformer: Transformer = async (root, vfile) => {
|
|
156
141
|
const promises: Promise<void>[] = [];
|
|
157
142
|
visit(root, 'link', (node: Link) => {
|
|
158
|
-
promises.push(processLinkNode(node, options));
|
|
143
|
+
promises.push(processLinkNode(node, {...options, filePath: vfile.path!}));
|
|
159
144
|
});
|
|
160
145
|
await Promise.all(promises);
|
|
161
146
|
};
|