@docusaurus/mdx-loader 2.0.0-beta.1decd6f80 → 2.0.0-beta.20
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 +33 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +11 -0
- package/lib/index.js.map +1 -0
- package/lib/loader.d.ts +39 -0
- package/lib/loader.d.ts.map +1 -0
- package/lib/loader.js +194 -0
- package/lib/loader.js.map +1 -0
- package/lib/remark/headings/index.d.ts +9 -0
- package/lib/remark/headings/index.d.ts.map +1 -0
- package/lib/remark/headings/index.js +58 -0
- package/lib/remark/headings/index.js.map +1 -0
- package/lib/remark/toc/index.d.ts +13 -0
- package/lib/remark/toc/index.d.ts.map +1 -0
- package/lib/remark/toc/index.js +81 -0
- package/lib/remark/toc/index.js.map +1 -0
- package/lib/remark/transformImage/index.d.ts +14 -0
- package/lib/remark/transformImage/index.d.ts.map +1 -0
- package/lib/remark/transformImage/index.js +109 -0
- package/lib/remark/transformImage/index.js.map +1 -0
- package/lib/remark/transformLinks/index.d.ts +14 -0
- package/lib/remark/transformLinks/index.d.ts.map +1 -0
- package/lib/remark/transformLinks/index.js +102 -0
- package/lib/remark/transformLinks/index.js.map +1 -0
- package/lib/remark/unwrapMdxCodeBlocks/index.d.ts +9 -0
- package/lib/remark/unwrapMdxCodeBlocks/index.d.ts.map +1 -0
- package/lib/remark/unwrapMdxCodeBlocks/index.js +29 -0
- package/lib/remark/unwrapMdxCodeBlocks/index.js.map +1 -0
- package/lib/remark/utils/index.d.ts +11 -0
- package/lib/remark/utils/index.d.ts.map +1 -0
- package/lib/remark/utils/index.js +38 -0
- package/lib/remark/utils/index.js.map +1 -0
- package/package.json +23 -20
- package/src/deps.d.ts +31 -0
- package/src/index.ts +35 -0
- package/src/loader.ts +253 -0
- package/src/remark/headings/{index.js → index.ts} +22 -26
- package/src/remark/toc/index.ts +102 -0
- package/src/remark/transformImage/index.ts +167 -0
- package/src/remark/transformLinks/index.ts +152 -0
- package/src/remark/unwrapMdxCodeBlocks/{index.js → index.ts} +14 -15
- package/src/remark/utils/index.ts +36 -0
- package/src/index.d.ts +0 -19
- package/src/index.js +0 -104
- package/src/remark/headings/__tests__/index.test.js +0 -315
- package/src/remark/toc/__tests__/__snapshots__/index.test.js.snap +0 -85
- 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.js +0 -212
- package/src/remark/toc/index.js +0 -79
- package/src/remark/toc/search.js +0 -73
- package/src/remark/transformImage/__tests__/__snapshots__/index.test.js.snap +0 -31
- package/src/remark/transformImage/__tests__/fixtures/fail.md +0 -1
- package/src/remark/transformImage/__tests__/fixtures/img.md +0 -17
- package/src/remark/transformImage/__tests__/fixtures/img.png +0 -0
- 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__/index.test.js +0 -59
- package/src/remark/transformImage/index.js +0 -106
- package/src/remark/transformLinks/__tests__/__snapshots__/index.test.js.snap +0 -41
- package/src/remark/transformLinks/__tests__/fixtures/asset.md +0 -29
- 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__/index.test.js +0 -44
- package/src/remark/transformLinks/index.js +0 -143
- package/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.js.snap +0 -819
- package/src/remark/unwrapMdxCodeBlocks/__tests__/fixtures/has-mdx-code-blocks.mdx +0 -95
- package/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.js +0 -38
- package/src/remark/utils/index.js +0 -39
- package/tsconfig.json +0 -9
|
@@ -7,30 +7,30 @@
|
|
|
7
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
import {parseMarkdownHeadingId, createSlugger} from '@docusaurus/utils';
|
|
11
|
+
import visit from 'unist-util-visit';
|
|
12
|
+
import mdastToString from 'mdast-util-to-string';
|
|
13
|
+
import type {Transformer} from 'unified';
|
|
14
|
+
import type {Heading, Text} from 'mdast';
|
|
14
15
|
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
slugs
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
16
|
+
export default function plugin(): Transformer {
|
|
17
|
+
return (root) => {
|
|
18
|
+
const slugs = createSlugger();
|
|
19
|
+
visit(root, 'heading', (headingNode: Heading) => {
|
|
20
|
+
const data = headingNode.data || (headingNode.data = {});
|
|
21
|
+
const properties = (data.hProperties || (data.hProperties = {})) as {
|
|
22
|
+
id: string;
|
|
23
|
+
};
|
|
22
24
|
let {id} = properties;
|
|
23
25
|
|
|
24
26
|
if (id) {
|
|
25
|
-
id = slugs.slug(id, true);
|
|
27
|
+
id = slugs.slug(id, {maintainCase: true});
|
|
26
28
|
} else {
|
|
27
29
|
const headingTextNodes = headingNode.children.filter(
|
|
28
30
|
({type}) => !['html', 'jsx'].includes(type),
|
|
29
31
|
);
|
|
30
|
-
const heading =
|
|
31
|
-
headingTextNodes.length > 0
|
|
32
|
-
? {children: headingTextNodes}
|
|
33
|
-
: headingNode,
|
|
32
|
+
const heading = mdastToString(
|
|
33
|
+
headingTextNodes.length > 0 ? headingTextNodes : headingNode,
|
|
34
34
|
);
|
|
35
35
|
|
|
36
36
|
// Support explicit heading IDs
|
|
@@ -40,10 +40,12 @@ function headings() {
|
|
|
40
40
|
|
|
41
41
|
if (parsedHeading.id) {
|
|
42
42
|
// When there's an id, it is always in the last child node
|
|
43
|
-
// Sometimes heading is in multiple "parts" (** syntax creates a child
|
|
43
|
+
// Sometimes heading is in multiple "parts" (** syntax creates a child
|
|
44
|
+
// node):
|
|
44
45
|
// ## part1 *part2* part3 {#id}
|
|
45
|
-
const lastNode =
|
|
46
|
-
headingNode.children
|
|
46
|
+
const lastNode = headingNode.children[
|
|
47
|
+
headingNode.children.length - 1
|
|
48
|
+
] as Text;
|
|
47
49
|
|
|
48
50
|
if (headingNode.children.length > 1) {
|
|
49
51
|
const lastNodeText = parseMarkdownHeadingId(lastNode.value).text;
|
|
@@ -63,12 +65,6 @@ function headings() {
|
|
|
63
65
|
|
|
64
66
|
data.id = id;
|
|
65
67
|
properties.id = id;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
visit(ast, 'heading', visitor);
|
|
68
|
+
});
|
|
69
69
|
};
|
|
70
|
-
|
|
71
|
-
return transformer;
|
|
72
70
|
}
|
|
73
|
-
|
|
74
|
-
module.exports = headings;
|
|
@@ -0,0 +1,102 @@
|
|
|
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 {parse, type ParserOptions} from '@babel/parser';
|
|
9
|
+
import type {Identifier} from '@babel/types';
|
|
10
|
+
import traverse from '@babel/traverse';
|
|
11
|
+
import stringifyObject from 'stringify-object';
|
|
12
|
+
import toString from 'mdast-util-to-string';
|
|
13
|
+
import visit from 'unist-util-visit';
|
|
14
|
+
import {toValue} from '../utils';
|
|
15
|
+
|
|
16
|
+
import type {TOCItem} from '../..';
|
|
17
|
+
import type {Node, Parent} from 'unist';
|
|
18
|
+
import type {Heading, Literal} from 'mdast';
|
|
19
|
+
import type {Transformer} from 'unified';
|
|
20
|
+
|
|
21
|
+
const parseOptions: ParserOptions = {
|
|
22
|
+
plugins: ['jsx'],
|
|
23
|
+
sourceType: 'module',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const isImport = (child: Node): child is Literal => child.type === 'import';
|
|
27
|
+
const hasImports = (index: number) => index > -1;
|
|
28
|
+
const isExport = (child: Node): child is Literal => child.type === 'export';
|
|
29
|
+
|
|
30
|
+
type PluginOptions = {
|
|
31
|
+
name?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const isTarget = (child: Literal, name: string) => {
|
|
35
|
+
let found = false;
|
|
36
|
+
const ast = parse(child.value, parseOptions);
|
|
37
|
+
traverse(ast, {
|
|
38
|
+
VariableDeclarator: (path) => {
|
|
39
|
+
if ((path.node.id as Identifier).name === name) {
|
|
40
|
+
found = true;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
return found;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const getOrCreateExistingTargetIndex = (children: Node[], name: string) => {
|
|
48
|
+
let importsIndex = -1;
|
|
49
|
+
let targetIndex = -1;
|
|
50
|
+
|
|
51
|
+
children.forEach((child, index) => {
|
|
52
|
+
if (isImport(child)) {
|
|
53
|
+
importsIndex = index;
|
|
54
|
+
} else if (isExport(child) && isTarget(child, name)) {
|
|
55
|
+
targetIndex = index;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (targetIndex === -1) {
|
|
60
|
+
const target = {
|
|
61
|
+
default: false,
|
|
62
|
+
type: 'export',
|
|
63
|
+
value: `export const ${name} = [];`,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
targetIndex = hasImports(importsIndex) ? importsIndex + 1 : 0;
|
|
67
|
+
children.splice(targetIndex, 0, target);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return targetIndex;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default function plugin(options: PluginOptions = {}): Transformer {
|
|
74
|
+
const name = options.name || 'toc';
|
|
75
|
+
|
|
76
|
+
return (root) => {
|
|
77
|
+
const headings: TOCItem[] = [];
|
|
78
|
+
|
|
79
|
+
visit(root, 'heading', (child: Heading, index, parent) => {
|
|
80
|
+
const value = toString(child);
|
|
81
|
+
|
|
82
|
+
// depth: 1 headings are titles and not included in the TOC
|
|
83
|
+
if (parent !== root || !value || child.depth < 2) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
headings.push({
|
|
88
|
+
value: toValue(child),
|
|
89
|
+
id: child.data!.id as string,
|
|
90
|
+
level: child.depth,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
const {children} = root as Parent<Literal>;
|
|
94
|
+
const targetIndex = getOrCreateExistingTargetIndex(children, name);
|
|
95
|
+
|
|
96
|
+
if (headings.length) {
|
|
97
|
+
children[targetIndex]!.value = `export const ${name} = ${stringifyObject(
|
|
98
|
+
headings,
|
|
99
|
+
)};`;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
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 {
|
|
9
|
+
toMessageRelativeFilePath,
|
|
10
|
+
posixPath,
|
|
11
|
+
escapePath,
|
|
12
|
+
getFileLoaderUtils,
|
|
13
|
+
findAsyncSequential,
|
|
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';
|
|
22
|
+
import type {Transformer} from 'unified';
|
|
23
|
+
import type {Image, Literal} from 'mdast';
|
|
24
|
+
import logger from '@docusaurus/logger';
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
loaders: {inlineMarkdownImageFileLoader},
|
|
28
|
+
} = getFileLoaderUtils();
|
|
29
|
+
|
|
30
|
+
type PluginOptions = {
|
|
31
|
+
staticDirs: string[];
|
|
32
|
+
siteDir: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type Context = PluginOptions & {
|
|
36
|
+
filePath: string;
|
|
37
|
+
};
|
|
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 (err) {
|
|
70
|
+
// Workaround for https://github.com/yarnpkg/berry/pull/3889#issuecomment-1034469784
|
|
71
|
+
// TODO remove this check once fixed in Yarn PnP
|
|
72
|
+
if (!process.versions.pnp) {
|
|
73
|
+
logger.warn`The image at path=${imagePath} can't be read correctly. Please ensure it's a valid image.
|
|
74
|
+
${(err as Error).message}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Object.keys(jsxNode).forEach(
|
|
79
|
+
(key) => delete jsxNode[key as keyof typeof jsxNode],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
(jsxNode as Literal).type = 'jsx';
|
|
83
|
+
jsxNode.value = `<img ${alt}src={${src}}${title}${width}${height} />`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function ensureImageFileExist(imagePath: string, sourceFilePath: string) {
|
|
87
|
+
const imageExists = await fs.pathExists(imagePath);
|
|
88
|
+
if (!imageExists) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Image ${toMessageRelativeFilePath(
|
|
91
|
+
imagePath,
|
|
92
|
+
)} used in ${toMessageRelativeFilePath(sourceFilePath)} not found.`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function getImageAbsolutePath(
|
|
98
|
+
imagePath: string,
|
|
99
|
+
{siteDir, filePath, staticDirs}: Context,
|
|
100
|
+
) {
|
|
101
|
+
if (imagePath.startsWith('@site/')) {
|
|
102
|
+
const imageFilePath = path.join(siteDir, imagePath.replace('@site/', ''));
|
|
103
|
+
await ensureImageFileExist(imageFilePath, filePath);
|
|
104
|
+
return imageFilePath;
|
|
105
|
+
} else if (path.isAbsolute(imagePath)) {
|
|
106
|
+
// Absolute paths are expected to exist in the static folder.
|
|
107
|
+
const possiblePaths = staticDirs.map((dir) => path.join(dir, imagePath));
|
|
108
|
+
const imageFilePath = await findAsyncSequential(
|
|
109
|
+
possiblePaths,
|
|
110
|
+
fs.pathExists,
|
|
111
|
+
);
|
|
112
|
+
if (!imageFilePath) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Image ${possiblePaths
|
|
115
|
+
.map((p) => toMessageRelativeFilePath(p))
|
|
116
|
+
.join(' or ')} used in ${toMessageRelativeFilePath(
|
|
117
|
+
filePath,
|
|
118
|
+
)} not found.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return imageFilePath;
|
|
122
|
+
}
|
|
123
|
+
// Relative paths are resolved against the source file's folder.
|
|
124
|
+
const imageFilePath = path.join(
|
|
125
|
+
path.dirname(filePath),
|
|
126
|
+
decodeURIComponent(imagePath),
|
|
127
|
+
);
|
|
128
|
+
await ensureImageFileExist(imageFilePath, filePath);
|
|
129
|
+
return imageFilePath;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function processImageNode(node: Image, context: Context) {
|
|
133
|
+
if (!node.url) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Markdown image URL is mandatory in "${toMessageRelativeFilePath(
|
|
136
|
+
context.filePath,
|
|
137
|
+
)}" file`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const parsedUrl = url.parse(node.url);
|
|
142
|
+
if (parsedUrl.protocol || !parsedUrl.pathname) {
|
|
143
|
+
// pathname:// is an escape hatch, in case user does not want her images to
|
|
144
|
+
// be converted to require calls going through webpack loader
|
|
145
|
+
if (parsedUrl.protocol === 'pathname:') {
|
|
146
|
+
node.url = node.url.replace('pathname://', '');
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// We try to convert image urls without protocol to images with require calls
|
|
152
|
+
// going through webpack ensures that image assets exist at build time
|
|
153
|
+
const imagePath = await getImageAbsolutePath(parsedUrl.pathname, context);
|
|
154
|
+
await toImageRequireNode(node, imagePath, context.filePath);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default function plugin(options: PluginOptions): Transformer {
|
|
158
|
+
return async (root, vfile) => {
|
|
159
|
+
const promises: Promise<void>[] = [];
|
|
160
|
+
visit(root, 'image', (node: Image) => {
|
|
161
|
+
promises.push(
|
|
162
|
+
processImageNode(node, {...options, filePath: vfile.path!}),
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
await Promise.all(promises);
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
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 {
|
|
9
|
+
toMessageRelativeFilePath,
|
|
10
|
+
posixPath,
|
|
11
|
+
escapePath,
|
|
12
|
+
getFileLoaderUtils,
|
|
13
|
+
findAsyncSequential,
|
|
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 {stringifyContent} from '../utils';
|
|
21
|
+
import type {Transformer} from 'unified';
|
|
22
|
+
import type {Link, Literal} from 'mdast';
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
loaders: {inlineMarkdownLinkFileLoader},
|
|
26
|
+
} = getFileLoaderUtils();
|
|
27
|
+
|
|
28
|
+
type PluginOptions = {
|
|
29
|
+
staticDirs: string[];
|
|
30
|
+
siteDir: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type Context = PluginOptions & {
|
|
34
|
+
filePath: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Transforms the link node to a JSX `<a>` element with a `require()` call.
|
|
39
|
+
*/
|
|
40
|
+
function toAssetRequireNode(node: Link, assetPath: string, filePath: string) {
|
|
41
|
+
const jsxNode = node as Literal & Partial<Link>;
|
|
42
|
+
let relativeAssetPath = posixPath(
|
|
43
|
+
path.relative(path.dirname(filePath), assetPath),
|
|
44
|
+
);
|
|
45
|
+
// require("assets/file.pdf") means requiring from a package called assets
|
|
46
|
+
relativeAssetPath = `./${relativeAssetPath}`;
|
|
47
|
+
|
|
48
|
+
const parsedUrl = url.parse(node.url);
|
|
49
|
+
const hash = parsedUrl.hash ?? '';
|
|
50
|
+
const search = parsedUrl.search ?? '';
|
|
51
|
+
|
|
52
|
+
const href = `require('${
|
|
53
|
+
// A hack to stop Webpack from using its built-in loader to parse JSON
|
|
54
|
+
path.extname(relativeAssetPath) === '.json'
|
|
55
|
+
? `${relativeAssetPath.replace('.json', '.raw')}!=`
|
|
56
|
+
: ''
|
|
57
|
+
}${inlineMarkdownLinkFileLoader}${
|
|
58
|
+
escapePath(relativeAssetPath) + search
|
|
59
|
+
}').default${hash ? ` + '${hash}'` : ''}`;
|
|
60
|
+
const children = stringifyContent(node);
|
|
61
|
+
const title = node.title ? ` title="${escapeHtml(node.title)}"` : '';
|
|
62
|
+
|
|
63
|
+
Object.keys(jsxNode).forEach(
|
|
64
|
+
(key) => delete jsxNode[key as keyof typeof jsxNode],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
(jsxNode as Literal).type = 'jsx';
|
|
68
|
+
jsxNode.value = `<a target="_blank" href={${href}}${title}>${children}</a>`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function ensureAssetFileExist(assetPath: string, sourceFilePath: string) {
|
|
72
|
+
const assetExists = await fs.pathExists(assetPath);
|
|
73
|
+
if (!assetExists) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Asset ${toMessageRelativeFilePath(
|
|
76
|
+
assetPath,
|
|
77
|
+
)} used in ${toMessageRelativeFilePath(sourceFilePath)} not found.`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function getAssetAbsolutePath(
|
|
83
|
+
assetPath: string,
|
|
84
|
+
{siteDir, filePath, staticDirs}: Context,
|
|
85
|
+
) {
|
|
86
|
+
if (assetPath.startsWith('@site/')) {
|
|
87
|
+
const assetFilePath = path.join(siteDir, assetPath.replace('@site/', ''));
|
|
88
|
+
// The @site alias is the only way to believe that the user wants an asset.
|
|
89
|
+
// Everything else can just be a link URL
|
|
90
|
+
await ensureAssetFileExist(assetFilePath, filePath);
|
|
91
|
+
return assetFilePath;
|
|
92
|
+
} else if (path.isAbsolute(assetPath)) {
|
|
93
|
+
const assetFilePath = await findAsyncSequential(
|
|
94
|
+
staticDirs.map((dir) => path.join(dir, assetPath)),
|
|
95
|
+
fs.pathExists,
|
|
96
|
+
);
|
|
97
|
+
if (assetFilePath) {
|
|
98
|
+
return assetFilePath;
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
const assetFilePath = path.join(path.dirname(filePath), assetPath);
|
|
102
|
+
if (await fs.pathExists(assetFilePath)) {
|
|
103
|
+
return assetFilePath;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function processLinkNode(node: Link, context: Context) {
|
|
110
|
+
if (!node.url) {
|
|
111
|
+
// Try to improve error feedback
|
|
112
|
+
// see https://github.com/facebook/docusaurus/issues/3309#issuecomment-690371675
|
|
113
|
+
const title = node.title || (node.children[0] as Literal)?.value || '?';
|
|
114
|
+
const line = node?.position?.start?.line || '?';
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Markdown link URL is mandatory in "${toMessageRelativeFilePath(
|
|
117
|
+
context.filePath,
|
|
118
|
+
)}" file (title: ${title}, line: ${line}).`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const parsedUrl = url.parse(node.url);
|
|
123
|
+
if (parsedUrl.protocol || !parsedUrl.pathname) {
|
|
124
|
+
// Don't process pathname:// here, it's used by the <Link> component
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const hasSiteAlias = parsedUrl.pathname.startsWith('@site/');
|
|
128
|
+
const hasAssetLikeExtension =
|
|
129
|
+
path.extname(parsedUrl.pathname) &&
|
|
130
|
+
!parsedUrl.pathname.match(/\.(?:mdx?|html)(?:#|$)/);
|
|
131
|
+
if (!hasSiteAlias && !hasAssetLikeExtension) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const assetPath = await getAssetAbsolutePath(
|
|
136
|
+
decodeURIComponent(parsedUrl.pathname),
|
|
137
|
+
context,
|
|
138
|
+
);
|
|
139
|
+
if (assetPath) {
|
|
140
|
+
toAssetRequireNode(node, assetPath, context.filePath);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default function plugin(options: PluginOptions): Transformer {
|
|
145
|
+
return async (root, vfile) => {
|
|
146
|
+
const promises: Promise<void>[] = [];
|
|
147
|
+
visit(root, 'link', (node: Link) => {
|
|
148
|
+
promises.push(processLinkNode(node, {...options, filePath: vfile.path!}));
|
|
149
|
+
});
|
|
150
|
+
await Promise.all(promises);
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -5,30 +5,29 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import visit from 'unist-util-visit';
|
|
9
|
+
import type {Transformer, Processor} from 'unified';
|
|
10
|
+
import type {Code, Parent} from 'mdast';
|
|
9
11
|
|
|
10
12
|
// This plugin is mostly to help integrating Docusaurus with translation systems
|
|
11
|
-
// that do not support well MDX embedded JSX syntax (like Crowdin)
|
|
12
|
-
// We wrap the JSX syntax in code blocks so that translation tools don't mess
|
|
13
|
-
//
|
|
13
|
+
// that do not support well MDX embedded JSX syntax (like Crowdin).
|
|
14
|
+
// We wrap the JSX syntax in code blocks so that translation tools don't mess up
|
|
15
|
+
// with the markup, but the JSX inside such code blocks should still be
|
|
16
|
+
// evaluated as JSX
|
|
14
17
|
// See https://github.com/facebook/docusaurus/pull/4278
|
|
15
|
-
function plugin() {
|
|
16
|
-
|
|
17
|
-
visit(root, 'code', (node,
|
|
18
|
+
export default function plugin(this: Processor): Transformer {
|
|
19
|
+
return (root) => {
|
|
20
|
+
visit(root, 'code', (node: Code, index, parent) => {
|
|
18
21
|
if (node.lang === 'mdx-code-block') {
|
|
19
|
-
const
|
|
22
|
+
const newChildren = (this.parse(node.value) as Parent).children;
|
|
20
23
|
|
|
21
24
|
// Replace the mdx code block by its content, parsed
|
|
22
|
-
parent
|
|
23
|
-
parent
|
|
25
|
+
parent!.children.splice(
|
|
26
|
+
parent!.children.indexOf(node),
|
|
24
27
|
1,
|
|
25
|
-
...
|
|
28
|
+
...newChildren,
|
|
26
29
|
);
|
|
27
30
|
}
|
|
28
31
|
});
|
|
29
32
|
};
|
|
30
|
-
|
|
31
|
-
return transformer;
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
module.exports = plugin;
|
|
@@ -0,0 +1,36 @@
|
|
|
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 escapeHtml from 'escape-html';
|
|
9
|
+
import toString from 'mdast-util-to-string';
|
|
10
|
+
import type {Parent} from 'unist';
|
|
11
|
+
import type {PhrasingContent, Heading} from 'mdast';
|
|
12
|
+
|
|
13
|
+
export function stringifyContent(node: Parent): string {
|
|
14
|
+
return (node.children as PhrasingContent[]).map(toValue).join('');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function toValue(node: PhrasingContent | Heading): string {
|
|
18
|
+
switch (node?.type) {
|
|
19
|
+
case 'text':
|
|
20
|
+
return escapeHtml(node.value);
|
|
21
|
+
case 'heading':
|
|
22
|
+
return stringifyContent(node);
|
|
23
|
+
case 'inlineCode':
|
|
24
|
+
return `<code>${escapeHtml(node.value)}</code>`;
|
|
25
|
+
case 'emphasis':
|
|
26
|
+
return `<em>${stringifyContent(node)}</em>`;
|
|
27
|
+
case 'strong':
|
|
28
|
+
return `<strong>${stringifyContent(node)}</strong>`;
|
|
29
|
+
case 'delete':
|
|
30
|
+
return `<del>${stringifyContent(node)}</del>`;
|
|
31
|
+
case 'link':
|
|
32
|
+
return stringifyContent(node);
|
|
33
|
+
default:
|
|
34
|
+
return toString(node);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/index.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
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
|
-
type RemarkOrRehypePlugin =
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
10
|
-
[Function, Record<string, unknown>] | Function;
|
|
11
|
-
|
|
12
|
-
declare function docusaurusMdxLoader(fileString: string): string;
|
|
13
|
-
|
|
14
|
-
export interface RemarkAndRehypePluginOptions {
|
|
15
|
-
remarkPlugins: RemarkOrRehypePlugin[];
|
|
16
|
-
rehypePlugins: string[];
|
|
17
|
-
beforeDefaultRemarkPlugins: RemarkOrRehypePlugin[];
|
|
18
|
-
beforeDefaultRehypePlugins: RemarkOrRehypePlugin[];
|
|
19
|
-
}
|