@azerion/bunosaurus-mdx-loader 0.0.0-stage → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -2
- package/lib/createMDXLoader.d.ts +14 -0
- package/lib/createMDXLoader.d.ts.map +1 -0
- package/lib/createMDXLoader.js +52 -0
- package/lib/createMDXLoader.js.map +1 -0
- package/lib/format.d.ts +14 -0
- package/lib/format.d.ts.map +1 -0
- package/lib/format.js +41 -0
- package/lib/format.js.map +1 -0
- package/lib/frontMatter.d.ts +13 -0
- package/lib/frontMatter.d.ts.map +1 -0
- package/lib/frontMatter.js +23 -0
- package/lib/frontMatter.js.map +1 -0
- package/lib/index.d.ts +35 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +15 -0
- package/lib/index.js.map +1 -0
- package/lib/loader.d.ts +11 -0
- package/lib/loader.d.ts.map +1 -0
- package/lib/loader.js +170 -0
- package/lib/loader.js.map +1 -0
- package/lib/options.d.ts +35 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +9 -0
- package/lib/options.js.map +1 -0
- package/lib/preprocessor.d.ts +18 -0
- package/lib/preprocessor.d.ts.map +1 -0
- package/lib/preprocessor.js +34 -0
- package/lib/preprocessor.js.map +1 -0
- package/lib/processor.d.ts +49 -0
- package/lib/processor.d.ts.map +1 -0
- package/lib/processor.js +209 -0
- package/lib/processor.js.map +1 -0
- package/lib/remark/admonitions/index.d.ts +11 -0
- package/lib/remark/admonitions/index.d.ts.map +1 -0
- package/lib/remark/admonitions/index.js +107 -0
- package/lib/remark/admonitions/index.js.map +1 -0
- package/lib/remark/contentTitle/index.d.ts +13 -0
- package/lib/remark/contentTitle/index.d.ts.map +1 -0
- package/lib/remark/contentTitle/index.js +62 -0
- package/lib/remark/contentTitle/index.js.map +1 -0
- package/lib/remark/details/index.d.ts +10 -0
- package/lib/remark/details/index.d.ts.map +1 -0
- package/lib/remark/details/index.js +22 -0
- package/lib/remark/details/index.js.map +1 -0
- package/lib/remark/head/index.d.ts +10 -0
- package/lib/remark/head/index.d.ts.map +1 -0
- package/lib/remark/head/index.js +22 -0
- package/lib/remark/head/index.js.map +1 -0
- package/lib/remark/headings/index.d.ts +14 -0
- package/lib/remark/headings/index.d.ts.map +1 -0
- package/lib/remark/headings/index.js +126 -0
- package/lib/remark/headings/index.js.map +1 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts +11 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts.map +1 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.js +30 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.js.map +1 -0
- package/lib/remark/mermaid/index.d.ts +10 -0
- package/lib/remark/mermaid/index.d.ts.map +1 -0
- package/lib/remark/mermaid/index.js +34 -0
- package/lib/remark/mermaid/index.js.map +1 -0
- package/lib/remark/resolveMarkdownLinks/index.d.ts +33 -0
- package/lib/remark/resolveMarkdownLinks/index.d.ts.map +1 -0
- package/lib/remark/resolveMarkdownLinks/index.js +87 -0
- package/lib/remark/resolveMarkdownLinks/index.js.map +1 -0
- package/lib/remark/toc/index.d.ts +14 -0
- package/lib/remark/toc/index.d.ts.map +1 -0
- package/lib/remark/toc/index.js +115 -0
- package/lib/remark/toc/index.js.map +1 -0
- package/lib/remark/toc/types.d.ts +22 -0
- package/lib/remark/toc/types.d.ts.map +1 -0
- package/lib/remark/toc/types.js +9 -0
- package/lib/remark/toc/types.js.map +1 -0
- package/lib/remark/toc/utils.d.ts +26 -0
- package/lib/remark/toc/utils.d.ts.map +1 -0
- package/lib/remark/toc/utils.js +184 -0
- package/lib/remark/toc/utils.js.map +1 -0
- package/lib/remark/transformImage/index.d.ts +17 -0
- package/lib/remark/transformImage/index.d.ts.map +1 -0
- package/lib/remark/transformImage/index.js +187 -0
- package/lib/remark/transformImage/index.js.map +1 -0
- package/lib/remark/transformLinks/index.d.ts +17 -0
- package/lib/remark/transformLinks/index.d.ts.map +1 -0
- package/lib/remark/transformLinks/index.js +205 -0
- package/lib/remark/transformLinks/index.js.map +1 -0
- package/lib/remark/unusedDirectives/index.d.ts +9 -0
- package/lib/remark/unusedDirectives/index.d.ts.map +1 -0
- package/lib/remark/unusedDirectives/index.js +125 -0
- package/lib/remark/unusedDirectives/index.js.map +1 -0
- package/lib/remark/utils/index.d.ts +18 -0
- package/lib/remark/utils/index.d.ts.map +1 -0
- package/lib/remark/utils/index.js +93 -0
- package/lib/remark/utils/index.js.map +1 -0
- package/lib/serializeJS.d.ts +17 -0
- package/lib/serializeJS.d.ts.map +1 -0
- package/lib/serializeJS.js +24 -0
- package/lib/serializeJS.js.map +1 -0
- package/lib/utils.d.ts +35 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +97 -0
- package/lib/utils.js.map +1 -0
- package/package.json +65 -4
- package/src/createMDXLoader.ts +65 -0
- package/src/format.ts +50 -0
- package/src/frontMatter.ts +31 -0
- package/src/index.ts +44 -0
- package/src/loader.ts +227 -0
- package/src/options.ts +37 -0
- package/src/preprocessor.ts +47 -0
- package/src/processor.ts +306 -0
- package/src/remark/admonitions/LICENSE +45 -0
- package/src/remark/admonitions/README.md +3 -0
- package/src/remark/admonitions/index.ts +138 -0
- package/src/remark/contentTitle/index.ts +76 -0
- package/src/remark/details/index.ts +23 -0
- package/src/remark/head/index.ts +23 -0
- package/src/remark/headings/index.ts +158 -0
- package/src/remark/mdx1Compat/codeCompatPlugin.ts +35 -0
- package/src/remark/mermaid/index.ts +35 -0
- package/src/remark/resolveMarkdownLinks/index.ts +137 -0
- package/src/remark/toc/index.ts +183 -0
- package/src/remark/toc/types.ts +29 -0
- package/src/remark/toc/utils.ts +243 -0
- package/src/remark/transformImage/index.ts +268 -0
- package/src/remark/transformLinks/index.ts +291 -0
- package/src/remark/unusedDirectives/index.ts +171 -0
- package/src/remark/utils/index.ts +99 -0
- package/src/serializeJS.ts +22 -0
- package/src/types.d.mts +40 -0
- package/src/utils.ts +134 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import {visit} from 'unist-util-visit';
|
|
10
|
+
import {
|
|
11
|
+
toMessageRelativeFilePath,
|
|
12
|
+
posixPath,
|
|
13
|
+
escapePath,
|
|
14
|
+
findAsyncSequential,
|
|
15
|
+
getFileLoaderUtils,
|
|
16
|
+
parseURLOrPath,
|
|
17
|
+
parseLocalURLPath,
|
|
18
|
+
} from '@docusaurus/utils';
|
|
19
|
+
import {imageSizeFromFile} from 'image-size/fromFile';
|
|
20
|
+
import logger from '@docusaurus/logger';
|
|
21
|
+
import {pathExists} from '@docusaurus/fs';
|
|
22
|
+
import {
|
|
23
|
+
assetRequireAttributeValue,
|
|
24
|
+
formatNodePositionExtraMessage,
|
|
25
|
+
transformNode,
|
|
26
|
+
} from '../utils';
|
|
27
|
+
import type {Plugin, Transformer} from 'unified';
|
|
28
|
+
import type {MdxJsxTextElement} from 'mdast-util-mdx';
|
|
29
|
+
import type {Image, Root} from 'mdast';
|
|
30
|
+
import type {Parent} from 'unist';
|
|
31
|
+
import type {
|
|
32
|
+
MarkdownConfig,
|
|
33
|
+
OnBrokenMarkdownImagesFunction,
|
|
34
|
+
} from '@docusaurus/types';
|
|
35
|
+
|
|
36
|
+
export type PluginOptions = {
|
|
37
|
+
staticDirs: string[];
|
|
38
|
+
siteDir: string;
|
|
39
|
+
onBrokenMarkdownImages: MarkdownConfig['hooks']['onBrokenMarkdownImages'];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type Context = {
|
|
43
|
+
staticDirs: PluginOptions['staticDirs'];
|
|
44
|
+
siteDir: PluginOptions['siteDir'];
|
|
45
|
+
onBrokenMarkdownImages: OnBrokenMarkdownImagesFunction;
|
|
46
|
+
filePath: string;
|
|
47
|
+
inlineMarkdownImageFileLoader: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function asFunction(
|
|
51
|
+
onBrokenMarkdownImages: PluginOptions['onBrokenMarkdownImages'],
|
|
52
|
+
): OnBrokenMarkdownImagesFunction {
|
|
53
|
+
if (typeof onBrokenMarkdownImages === 'string') {
|
|
54
|
+
const extraHelp =
|
|
55
|
+
onBrokenMarkdownImages === 'throw'
|
|
56
|
+
? logger.interpolate`\nTo ignore this error, use the code=${'siteConfig.markdown.hooks.onBrokenMarkdownImages'} option, or apply the code=${'pathname://'} protocol to the broken image URLs.`
|
|
57
|
+
: '';
|
|
58
|
+
return ({sourceFilePath, url: imageUrl, node}) => {
|
|
59
|
+
const relativePath = toMessageRelativeFilePath(sourceFilePath);
|
|
60
|
+
if (imageUrl) {
|
|
61
|
+
logger.report(
|
|
62
|
+
onBrokenMarkdownImages,
|
|
63
|
+
)`Markdown image with URL code=${imageUrl} in source file path=${relativePath}${formatNodePositionExtraMessage(
|
|
64
|
+
node,
|
|
65
|
+
)} couldn't be resolved to an existing local image file.${extraHelp}`;
|
|
66
|
+
} else {
|
|
67
|
+
logger.report(
|
|
68
|
+
onBrokenMarkdownImages,
|
|
69
|
+
)`Markdown image with empty URL found in source file path=${relativePath}${formatNodePositionExtraMessage(
|
|
70
|
+
node,
|
|
71
|
+
)}.${extraHelp}`;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
} else {
|
|
75
|
+
return (params) =>
|
|
76
|
+
onBrokenMarkdownImages({
|
|
77
|
+
...params,
|
|
78
|
+
sourceFilePath: toMessageRelativeFilePath(params.sourceFilePath),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type Target = [node: Image, index: number, parent: Parent];
|
|
84
|
+
|
|
85
|
+
async function toImageRequireNode(
|
|
86
|
+
[node]: Target,
|
|
87
|
+
imagePath: string,
|
|
88
|
+
context: Context,
|
|
89
|
+
) {
|
|
90
|
+
// MdxJsxTextElement => see https://github.com/facebook/docusaurus/pull/8288#discussion_r1125871405
|
|
91
|
+
const jsxNode = node as unknown as MdxJsxTextElement;
|
|
92
|
+
const attributes: MdxJsxTextElement['attributes'] = [];
|
|
93
|
+
|
|
94
|
+
let relativeImagePath = posixPath(
|
|
95
|
+
path.relative(path.dirname(context.filePath), imagePath),
|
|
96
|
+
);
|
|
97
|
+
relativeImagePath = `./${relativeImagePath}`;
|
|
98
|
+
|
|
99
|
+
const parsedUrl = parseURLOrPath(node.url);
|
|
100
|
+
const hash = parsedUrl.hash ?? '';
|
|
101
|
+
const search = parsedUrl.search ?? '';
|
|
102
|
+
const requireString = `${context.inlineMarkdownImageFileLoader}${
|
|
103
|
+
escapePath(relativeImagePath) + search
|
|
104
|
+
}`;
|
|
105
|
+
if (node.alt) {
|
|
106
|
+
attributes.push({
|
|
107
|
+
type: 'mdxJsxAttribute',
|
|
108
|
+
name: 'alt',
|
|
109
|
+
value: node.alt,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
attributes.push({
|
|
114
|
+
type: 'mdxJsxAttribute',
|
|
115
|
+
name: 'src',
|
|
116
|
+
value: assetRequireAttributeValue(requireString, hash),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (node.title) {
|
|
120
|
+
attributes.push({
|
|
121
|
+
type: 'mdxJsxAttribute',
|
|
122
|
+
name: 'title',
|
|
123
|
+
value: node.title,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const size = (await imageSizeFromFile(imagePath))!;
|
|
129
|
+
if (size.width) {
|
|
130
|
+
attributes.push({
|
|
131
|
+
type: 'mdxJsxAttribute',
|
|
132
|
+
name: 'width',
|
|
133
|
+
value: String(size.width),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (size.height) {
|
|
137
|
+
attributes.push({
|
|
138
|
+
type: 'mdxJsxAttribute',
|
|
139
|
+
name: 'height',
|
|
140
|
+
value: String(size.height),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.error(err);
|
|
145
|
+
// Workaround for https://github.com/yarnpkg/berry/pull/3889#issuecomment-1034469784
|
|
146
|
+
// TODO remove this check once fixed in Yarn PnP
|
|
147
|
+
if (!process.versions.pnp) {
|
|
148
|
+
logger.warn`The image at path=${imagePath} can't be read correctly. Please ensure it's a valid image.
|
|
149
|
+
${(err as Error).message}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
transformNode(jsxNode, {
|
|
154
|
+
type: 'mdxJsxTextElement',
|
|
155
|
+
name: 'img',
|
|
156
|
+
attributes,
|
|
157
|
+
children: [],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function getLocalImageAbsolutePath(
|
|
162
|
+
originalImagePath: string,
|
|
163
|
+
{siteDir, filePath, staticDirs}: Context,
|
|
164
|
+
) {
|
|
165
|
+
if (originalImagePath.startsWith('@site/')) {
|
|
166
|
+
const imageFilePath = path.join(
|
|
167
|
+
siteDir,
|
|
168
|
+
originalImagePath.replace('@site/', ''),
|
|
169
|
+
);
|
|
170
|
+
if (!(await pathExists(imageFilePath))) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return imageFilePath;
|
|
174
|
+
} else if (path.isAbsolute(originalImagePath)) {
|
|
175
|
+
// Absolute paths are expected to exist in the static folder.
|
|
176
|
+
const possiblePaths = staticDirs.map((dir) =>
|
|
177
|
+
path.join(dir, originalImagePath),
|
|
178
|
+
);
|
|
179
|
+
const imageFilePath = await findAsyncSequential(possiblePaths, pathExists);
|
|
180
|
+
if (!imageFilePath) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return imageFilePath;
|
|
184
|
+
} else {
|
|
185
|
+
// relative paths are resolved against the source file's folder
|
|
186
|
+
const imageFilePath = path.join(path.dirname(filePath), originalImagePath);
|
|
187
|
+
if (!(await pathExists(imageFilePath))) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return imageFilePath;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function processImageNode(target: Target, context: Context) {
|
|
195
|
+
const [node] = target;
|
|
196
|
+
|
|
197
|
+
if (!node.url) {
|
|
198
|
+
node.url =
|
|
199
|
+
context.onBrokenMarkdownImages({
|
|
200
|
+
url: node.url,
|
|
201
|
+
sourceFilePath: context.filePath,
|
|
202
|
+
node,
|
|
203
|
+
}) ?? node.url;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const localUrlPath = parseLocalURLPath(node.url);
|
|
208
|
+
if (!localUrlPath) {
|
|
209
|
+
// pathname:// is an escape hatch, in case the user does not want images to
|
|
210
|
+
// be converted to require calls going through webpack loader
|
|
211
|
+
if (parseURLOrPath(node.url).protocol === 'pathname:') {
|
|
212
|
+
node.url = node.url.replace('pathname://', '');
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// We decode it first because Node Url.pathname is always encoded
|
|
218
|
+
// while the image file-system path are not.
|
|
219
|
+
// See https://github.com/facebook/docusaurus/discussions/10720
|
|
220
|
+
const decodedPathname = decodeURIComponent(localUrlPath.pathname);
|
|
221
|
+
|
|
222
|
+
// We try to convert image urls without protocol to images with require calls
|
|
223
|
+
// going through webpack ensures that image assets exist at build time
|
|
224
|
+
const localImagePath = await getLocalImageAbsolutePath(
|
|
225
|
+
decodedPathname,
|
|
226
|
+
context,
|
|
227
|
+
);
|
|
228
|
+
if (localImagePath === null) {
|
|
229
|
+
node.url =
|
|
230
|
+
context.onBrokenMarkdownImages({
|
|
231
|
+
url: node.url,
|
|
232
|
+
sourceFilePath: context.filePath,
|
|
233
|
+
node,
|
|
234
|
+
}) ?? node.url;
|
|
235
|
+
} else {
|
|
236
|
+
await toImageRequireNode(target, localImagePath, context);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
|
241
|
+
options,
|
|
242
|
+
): Transformer<Root> {
|
|
243
|
+
const onBrokenMarkdownImages = asFunction(options.onBrokenMarkdownImages);
|
|
244
|
+
|
|
245
|
+
return async (root, vfile) => {
|
|
246
|
+
const fileLoaderUtils = getFileLoaderUtils(
|
|
247
|
+
vfile.data.compilerName === 'server',
|
|
248
|
+
);
|
|
249
|
+
const context: Context = {
|
|
250
|
+
...options,
|
|
251
|
+
filePath: vfile.path!,
|
|
252
|
+
inlineMarkdownImageFileLoader:
|
|
253
|
+
fileLoaderUtils.loaders.inlineMarkdownImageFileLoader,
|
|
254
|
+
onBrokenMarkdownImages,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const promises: Promise<void>[] = [];
|
|
258
|
+
visit(root, 'image', (node, index, parent) => {
|
|
259
|
+
if (!parent || index === undefined) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
promises.push(processImageNode([node, index, parent!], context));
|
|
263
|
+
});
|
|
264
|
+
await Promise.all(promises);
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export default plugin;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import {visit} from 'unist-util-visit';
|
|
10
|
+
import {
|
|
11
|
+
toMessageRelativeFilePath,
|
|
12
|
+
posixPath,
|
|
13
|
+
escapePath,
|
|
14
|
+
findAsyncSequential,
|
|
15
|
+
getFileLoaderUtils,
|
|
16
|
+
parseURLOrPath,
|
|
17
|
+
parseLocalURLPath,
|
|
18
|
+
} from '@docusaurus/utils';
|
|
19
|
+
import logger from '@docusaurus/logger';
|
|
20
|
+
import {fs} from '@docusaurus/fs';
|
|
21
|
+
import {
|
|
22
|
+
assetRequireAttributeValue,
|
|
23
|
+
formatNodePositionExtraMessage,
|
|
24
|
+
transformNode,
|
|
25
|
+
} from '../utils';
|
|
26
|
+
import type {Plugin, Transformer} from 'unified';
|
|
27
|
+
import type {MdxJsxTextElement} from 'mdast-util-mdx';
|
|
28
|
+
import type {Parent} from 'unist';
|
|
29
|
+
import type {Link, Root} from 'mdast';
|
|
30
|
+
import type {
|
|
31
|
+
MarkdownConfig,
|
|
32
|
+
OnBrokenMarkdownLinksFunction,
|
|
33
|
+
} from '@docusaurus/types';
|
|
34
|
+
|
|
35
|
+
export type PluginOptions = {
|
|
36
|
+
staticDirs: string[];
|
|
37
|
+
siteDir: string;
|
|
38
|
+
onBrokenMarkdownLinks: MarkdownConfig['hooks']['onBrokenMarkdownLinks'];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type Context = PluginOptions & {
|
|
42
|
+
staticDirs: string[];
|
|
43
|
+
siteDir: string;
|
|
44
|
+
onBrokenMarkdownLinks: OnBrokenMarkdownLinksFunction;
|
|
45
|
+
filePath: string;
|
|
46
|
+
inlineMarkdownLinkFileLoader: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type Target = [node: Link, index: number, parent: Parent];
|
|
50
|
+
|
|
51
|
+
function asFunction(
|
|
52
|
+
onBrokenMarkdownLinks: PluginOptions['onBrokenMarkdownLinks'],
|
|
53
|
+
): OnBrokenMarkdownLinksFunction {
|
|
54
|
+
if (typeof onBrokenMarkdownLinks === 'string') {
|
|
55
|
+
const extraHelp =
|
|
56
|
+
onBrokenMarkdownLinks === 'throw'
|
|
57
|
+
? logger.interpolate`\nTo ignore this error, use the code=${'siteConfig.markdown.hooks.onBrokenMarkdownLinks'} option, or apply the code=${'pathname://'} protocol to the broken link URLs.`
|
|
58
|
+
: '';
|
|
59
|
+
|
|
60
|
+
return ({sourceFilePath, url: linkUrl, node}) => {
|
|
61
|
+
const relativePath = toMessageRelativeFilePath(sourceFilePath);
|
|
62
|
+
if (linkUrl) {
|
|
63
|
+
logger.report(
|
|
64
|
+
onBrokenMarkdownLinks,
|
|
65
|
+
)`Markdown link with URL code=${linkUrl} in source file path=${relativePath}${formatNodePositionExtraMessage(
|
|
66
|
+
node,
|
|
67
|
+
)} couldn't be resolved.
|
|
68
|
+
Make sure it references a local Markdown file that exists within the current plugin.${extraHelp}`;
|
|
69
|
+
} else {
|
|
70
|
+
logger.report(
|
|
71
|
+
onBrokenMarkdownLinks,
|
|
72
|
+
)`Markdown link with empty URL found in source file path=${relativePath}${formatNodePositionExtraMessage(
|
|
73
|
+
node,
|
|
74
|
+
)}.${extraHelp}`;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
} else {
|
|
78
|
+
return (params) =>
|
|
79
|
+
onBrokenMarkdownLinks({
|
|
80
|
+
...params,
|
|
81
|
+
sourceFilePath: toMessageRelativeFilePath(params.sourceFilePath),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Transforms the link node to a JSX `<a>` element with a `require()` call.
|
|
88
|
+
*/
|
|
89
|
+
function toAssetRequireNode(
|
|
90
|
+
[node]: Target,
|
|
91
|
+
assetPath: string,
|
|
92
|
+
context: Context,
|
|
93
|
+
) {
|
|
94
|
+
// MdxJsxTextElement => see https://github.com/facebook/docusaurus/pull/8288#discussion_r1125871405
|
|
95
|
+
const jsxNode = node as unknown as MdxJsxTextElement;
|
|
96
|
+
const attributes: MdxJsxTextElement['attributes'] = [];
|
|
97
|
+
|
|
98
|
+
// require("assets/file.pdf") means requiring from a package called assets
|
|
99
|
+
const relativeAssetPath = `./${posixPath(
|
|
100
|
+
path.relative(path.dirname(context.filePath), assetPath),
|
|
101
|
+
)}`;
|
|
102
|
+
|
|
103
|
+
const parsedUrl = parseURLOrPath(node.url);
|
|
104
|
+
const hash = parsedUrl.hash ?? '';
|
|
105
|
+
const search = parsedUrl.search ?? '';
|
|
106
|
+
|
|
107
|
+
const requireString = `${
|
|
108
|
+
// A hack to stop Webpack from using its built-in loader to parse JSON
|
|
109
|
+
path.extname(relativeAssetPath) === '.json'
|
|
110
|
+
? `${relativeAssetPath.replace('.json', '.raw')}!=`
|
|
111
|
+
: ''
|
|
112
|
+
}${context.inlineMarkdownLinkFileLoader}${
|
|
113
|
+
escapePath(relativeAssetPath) + search
|
|
114
|
+
}`;
|
|
115
|
+
|
|
116
|
+
attributes.push({
|
|
117
|
+
type: 'mdxJsxAttribute',
|
|
118
|
+
name: 'target',
|
|
119
|
+
value: '_blank',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Assets are not routes, and are required by Webpack already
|
|
123
|
+
// They should not trigger the broken link checker
|
|
124
|
+
attributes.push({
|
|
125
|
+
type: 'mdxJsxAttribute',
|
|
126
|
+
name: 'data-noBrokenLinkCheck',
|
|
127
|
+
value: {
|
|
128
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
129
|
+
value: 'true',
|
|
130
|
+
data: {
|
|
131
|
+
estree: {
|
|
132
|
+
type: 'Program',
|
|
133
|
+
body: [
|
|
134
|
+
{
|
|
135
|
+
type: 'ExpressionStatement',
|
|
136
|
+
expression: {
|
|
137
|
+
type: 'Literal',
|
|
138
|
+
value: true,
|
|
139
|
+
raw: 'true',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
sourceType: 'module',
|
|
144
|
+
comments: [],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
attributes.push({
|
|
151
|
+
type: 'mdxJsxAttribute',
|
|
152
|
+
name: 'href',
|
|
153
|
+
value: assetRequireAttributeValue(requireString, hash),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (node.title) {
|
|
157
|
+
attributes.push({
|
|
158
|
+
type: 'mdxJsxAttribute',
|
|
159
|
+
name: 'title',
|
|
160
|
+
value: node.title,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const {children} = node;
|
|
165
|
+
|
|
166
|
+
transformNode(jsxNode, {
|
|
167
|
+
type: 'mdxJsxTextElement',
|
|
168
|
+
name: 'a',
|
|
169
|
+
attributes,
|
|
170
|
+
children,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Checks if the given file path exists and is a file.
|
|
176
|
+
* Returns true if it exists, false otherwise.
|
|
177
|
+
*/
|
|
178
|
+
async function isExistingFile(filePath: string): Promise<boolean> {
|
|
179
|
+
try {
|
|
180
|
+
return (await fs.stat(filePath)).isFile();
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function getLocalFileAbsolutePath(
|
|
187
|
+
assetPath: string,
|
|
188
|
+
{siteDir, filePath, staticDirs}: Context,
|
|
189
|
+
) {
|
|
190
|
+
if (assetPath.startsWith('@site/')) {
|
|
191
|
+
const assetFilePath = path.join(siteDir, assetPath.replace('@site/', ''));
|
|
192
|
+
if (await isExistingFile(assetFilePath)) {
|
|
193
|
+
return assetFilePath;
|
|
194
|
+
}
|
|
195
|
+
} else if (path.isAbsolute(assetPath)) {
|
|
196
|
+
const assetFilePath = await findAsyncSequential(
|
|
197
|
+
staticDirs.map((dir) => path.join(dir, assetPath)),
|
|
198
|
+
isExistingFile,
|
|
199
|
+
);
|
|
200
|
+
if (assetFilePath) {
|
|
201
|
+
return assetFilePath;
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
const assetFilePath = path.join(path.dirname(filePath), assetPath);
|
|
205
|
+
if (await isExistingFile(assetFilePath)) {
|
|
206
|
+
return assetFilePath;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function processLinkNode(target: Target, context: Context) {
|
|
213
|
+
const [node] = target;
|
|
214
|
+
if (!node.url) {
|
|
215
|
+
node.url =
|
|
216
|
+
context.onBrokenMarkdownLinks({
|
|
217
|
+
url: node.url,
|
|
218
|
+
sourceFilePath: context.filePath,
|
|
219
|
+
node,
|
|
220
|
+
}) ?? node.url;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const localUrlPath = parseLocalURLPath(node.url);
|
|
225
|
+
if (!localUrlPath) {
|
|
226
|
+
// Don't process pathname:// here, it's used by the <Link> component
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const hasSiteAlias = localUrlPath.pathname.startsWith('@site/');
|
|
231
|
+
const hasAssetLikeExtension =
|
|
232
|
+
path.extname(localUrlPath.pathname) &&
|
|
233
|
+
!localUrlPath.pathname.match(/\.(?:mdx?|html)(?:#|$)/);
|
|
234
|
+
if (!hasSiteAlias && !hasAssetLikeExtension) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const localFilePath = await getLocalFileAbsolutePath(
|
|
239
|
+
decodeURIComponent(localUrlPath.pathname),
|
|
240
|
+
context,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
if (localFilePath) {
|
|
244
|
+
toAssetRequireNode(target, localFilePath, context);
|
|
245
|
+
} else {
|
|
246
|
+
// The @site alias is the only way to believe that the user wants an asset.
|
|
247
|
+
if (hasSiteAlias) {
|
|
248
|
+
node.url =
|
|
249
|
+
context.onBrokenMarkdownLinks({
|
|
250
|
+
url: node.url,
|
|
251
|
+
sourceFilePath: context.filePath,
|
|
252
|
+
node,
|
|
253
|
+
}) ?? node.url;
|
|
254
|
+
} else {
|
|
255
|
+
// Even if the url has a dot, and it looks like a file extension
|
|
256
|
+
// it can be risky to throw and fail fast by default
|
|
257
|
+
// It's perfectly valid for a route path segment to look like a filename
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
|
263
|
+
options,
|
|
264
|
+
): Transformer<Root> {
|
|
265
|
+
const onBrokenMarkdownLinks = asFunction(options.onBrokenMarkdownLinks);
|
|
266
|
+
|
|
267
|
+
return async (root, vfile) => {
|
|
268
|
+
const fileLoaderUtils = getFileLoaderUtils(
|
|
269
|
+
vfile.data.compilerName === 'server',
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const context: Context = {
|
|
273
|
+
...options,
|
|
274
|
+
filePath: vfile.path!,
|
|
275
|
+
inlineMarkdownLinkFileLoader:
|
|
276
|
+
fileLoaderUtils.loaders.inlineMarkdownLinkFileLoader,
|
|
277
|
+
onBrokenMarkdownLinks,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const promises: Promise<void>[] = [];
|
|
281
|
+
visit(root, 'link', (node, index, parent) => {
|
|
282
|
+
if (!parent || index === undefined) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
promises.push(processLinkNode([node, index, parent], context));
|
|
286
|
+
});
|
|
287
|
+
await Promise.all(promises);
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export default plugin;
|