mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-01 03:08:17 +02:00
feat(v2): support for adding relative images and handling broken image links (#3069)
* all relative path in image url * throw error if file doesn't present * better error * add @docusaurus/core to deps * fix test
This commit is contained in:
parent
15e73daae7
commit
3155dc30dc
15 changed files with 162 additions and 4 deletions
|
@ -89,6 +89,11 @@ Reference-style: ![alt text][logo]
|
||||||
|
|
||||||
[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 2'
|
[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 2'
|
||||||
|
|
||||||
|
Images from any folder can be used by providing path to file. Path should be relative to markdown file.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Code
|
## Code
|
||||||
|
|
|
@ -89,6 +89,10 @@ Reference-style: ![alt text][logo]
|
||||||
|
|
||||||
[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 2'
|
[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 2'
|
||||||
|
|
||||||
|
Images from any folder can be used by providing path to file. Path should be relative to markdown file.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Code
|
## Code
|
||||||
|
|
|
@ -89,6 +89,10 @@ Reference-style: ![alt text][logo]
|
||||||
|
|
||||||
[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 2'
|
[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 2'
|
||||||
|
|
||||||
|
Images from any folder can be used by providing path to file. Path should be relative to markdown file.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Code
|
## Code
|
||||||
|
|
|
@ -13,6 +13,7 @@ const matter = require('gray-matter');
|
||||||
const stringifyObject = require('stringify-object');
|
const stringifyObject = require('stringify-object');
|
||||||
const slug = require('./remark/slug');
|
const slug = require('./remark/slug');
|
||||||
const rightToc = require('./remark/rightToc');
|
const rightToc = require('./remark/rightToc');
|
||||||
|
const relativePath = require('./remark/transformImage');
|
||||||
|
|
||||||
const DEFAULT_OPTIONS = {
|
const DEFAULT_OPTIONS = {
|
||||||
rehypePlugins: [],
|
rehypePlugins: [],
|
||||||
|
@ -29,6 +30,10 @@ module.exports = async function (fileString) {
|
||||||
remarkPlugins: [
|
remarkPlugins: [
|
||||||
...(reqOptions.beforeDefaultRemarkPlugins || []),
|
...(reqOptions.beforeDefaultRemarkPlugins || []),
|
||||||
...DEFAULT_OPTIONS.remarkPlugins,
|
...DEFAULT_OPTIONS.remarkPlugins,
|
||||||
|
[
|
||||||
|
relativePath,
|
||||||
|
{staticDir: reqOptions.staticDir, filePath: this.resourcePath},
|
||||||
|
],
|
||||||
...(reqOptions.remarkPlugins || []),
|
...(reqOptions.remarkPlugins || []),
|
||||||
],
|
],
|
||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`transform md images to <img /> 1`] = `
|
||||||
|
"
|
||||||
|
|
||||||
|
<img src={require(\\"!url-loader!./img.png\\").default} />
|
||||||
|
|
||||||
|
<img alt={\\"img\\"} src={require(\\"!url-loader!./img.png\\").default} />
|
||||||
|
|
||||||
|
<img alt={\\"img\\"} src={require(\\"!url-loader!./img.png\\").default} title={\\"Title\\"} /> 
|
||||||
|
|
||||||
|
## Heading
|
||||||
|
|
||||||
|
\`\`\`md
|
||||||
|

|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
<img alt={\\"img\\"} src={require(\\"!url-loader!./img.png\\").default} />
|
||||||
|
"
|
||||||
|
`;
|
|
@ -0,0 +1 @@
|
||||||
|

|
|
@ -0,0 +1,15 @@
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
 
|
||||||
|
|
||||||
|
## Heading
|
||||||
|
|
||||||
|
```md
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|

|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* 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 {join} from 'path';
|
||||||
|
import remark from 'remark';
|
||||||
|
import mdx from 'remark-mdx';
|
||||||
|
import vfile from 'to-vfile';
|
||||||
|
import plugin from '../index';
|
||||||
|
import slug from '../../slug/index';
|
||||||
|
|
||||||
|
const processFixture = async (name, options) => {
|
||||||
|
const path = join(__dirname, 'fixtures', `${name}.md`);
|
||||||
|
const file = await vfile.read(path);
|
||||||
|
const result = await remark()
|
||||||
|
.use(slug)
|
||||||
|
.use(mdx)
|
||||||
|
.use(plugin, {...options, filePath: path})
|
||||||
|
.process(file);
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
test('fail if image donot exists', async () => {
|
||||||
|
expect(
|
||||||
|
processFixture('fail', {staticDir: join(__dirname, 'fixtures')}),
|
||||||
|
).rejects.toBeInstanceOf(Error);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('transform md images to <img />', async () => {
|
||||||
|
const result = await processFixture('img', {
|
||||||
|
staticDir: join(__dirname, 'fixtures'),
|
||||||
|
});
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const visit = require('unist-util-visit');
|
||||||
|
const path = require('path');
|
||||||
|
const url = require('url');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
|
||||||
|
const plugin = (options) => {
|
||||||
|
const transformer = (root) => {
|
||||||
|
visit(root, 'image', (node) => {
|
||||||
|
if (!url.parse(node.url).protocol) {
|
||||||
|
if (!path.isAbsolute(node.url)) {
|
||||||
|
if (
|
||||||
|
!fs.existsSync(path.join(path.dirname(options.filePath), node.url))
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`Image ${path.join(
|
||||||
|
path.dirname(options.filePath),
|
||||||
|
node.url,
|
||||||
|
)} used in ${options.filePath} not found.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
node.type = 'jsx';
|
||||||
|
node.value = `<img ${node.alt ? `alt={"${node.alt}"}` : ''} ${
|
||||||
|
node.url
|
||||||
|
? `src={require("!url-loader!${
|
||||||
|
node.url.startsWith('./') ? node.url : `./${node.url}`
|
||||||
|
}").default}`
|
||||||
|
: ''
|
||||||
|
} ${node.title ? `title={"${node.title}"}` : ''} />`;
|
||||||
|
if (node.url) {
|
||||||
|
delete node.url;
|
||||||
|
}
|
||||||
|
if (node.alt) {
|
||||||
|
delete node.alt;
|
||||||
|
}
|
||||||
|
if (node.title) {
|
||||||
|
delete node.title;
|
||||||
|
}
|
||||||
|
} else if (!fs.existsSync(path.join(options.staticDir, node.url))) {
|
||||||
|
throw new Error(
|
||||||
|
`Image ${path.join(options.staticDir, node.url)} used in ${
|
||||||
|
options.filePath
|
||||||
|
} not found.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformer;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = plugin;
|
|
@ -18,6 +18,7 @@
|
||||||
"@docusaurus/mdx-loader": "^2.0.0-alpha.58",
|
"@docusaurus/mdx-loader": "^2.0.0-alpha.58",
|
||||||
"@docusaurus/types": "^2.0.0-alpha.58",
|
"@docusaurus/types": "^2.0.0-alpha.58",
|
||||||
"@docusaurus/utils": "^2.0.0-alpha.58",
|
"@docusaurus/utils": "^2.0.0-alpha.58",
|
||||||
|
"@docusaurus/core": "2.0.0-alpha.58",
|
||||||
"@hapi/joi": "^17.1.1",
|
"@hapi/joi": "^17.1.1",
|
||||||
"feed": "^4.1.0",
|
"feed": "^4.1.0",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
|
@ -28,7 +29,6 @@
|
||||||
"remark-admonitions": "^1.2.1"
|
"remark-admonitions": "^1.2.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@docusaurus/core": "^2.0.0",
|
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.4",
|
||||||
"react-dom": "^16.8.4"
|
"react-dom": "^16.8.4"
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,7 @@ import kebabCase from 'lodash.kebabcase';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import admonitions from 'remark-admonitions';
|
import admonitions from 'remark-admonitions';
|
||||||
import {normalizeUrl, docuHash, aliasedSitePath} from '@docusaurus/utils';
|
import {normalizeUrl, docuHash, aliasedSitePath} from '@docusaurus/utils';
|
||||||
|
import {STATIC_DIR_NAME} from '@docusaurus/core/lib/constants';
|
||||||
import {ValidationError} from '@hapi/joi';
|
import {ValidationError} from '@hapi/joi';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -363,6 +364,7 @@ export default function pluginContentBlog(
|
||||||
options: {
|
options: {
|
||||||
remarkPlugins,
|
remarkPlugins,
|
||||||
rehypePlugins,
|
rehypePlugins,
|
||||||
|
staticDir: path.join(siteDir, STATIC_DIR_NAME),
|
||||||
// Note that metadataPath must be the same/in-sync as
|
// Note that metadataPath must be the same/in-sync as
|
||||||
// the path from createData for each MDX.
|
// the path from createData for each MDX.
|
||||||
metadataPath: (mdxPath: string) => {
|
metadataPath: (mdxPath: string) => {
|
||||||
|
|
|
@ -33,10 +33,10 @@
|
||||||
"lodash.pickby": "^4.6.0",
|
"lodash.pickby": "^4.6.0",
|
||||||
"lodash.sortby": "^4.6.0",
|
"lodash.sortby": "^4.6.0",
|
||||||
"remark-admonitions": "^1.2.1",
|
"remark-admonitions": "^1.2.1",
|
||||||
"shelljs": "^0.8.4"
|
"shelljs": "^0.8.4",
|
||||||
|
"@docusaurus/core": "^2.0.0-alpha.58"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@docusaurus/core": "^2.0.0",
|
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.4",
|
||||||
"react-dom": "^16.8.4"
|
"react-dom": "^16.8.4"
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,6 +13,7 @@ import globby from 'globby';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import admonitions from 'remark-admonitions';
|
import admonitions from 'remark-admonitions';
|
||||||
|
import {STATIC_DIR_NAME} from '@docusaurus/core/lib/constants';
|
||||||
import {
|
import {
|
||||||
normalizeUrl,
|
normalizeUrl,
|
||||||
docuHash,
|
docuHash,
|
||||||
|
@ -524,6 +525,7 @@ Available document ids=
|
||||||
options: {
|
options: {
|
||||||
remarkPlugins,
|
remarkPlugins,
|
||||||
rehypePlugins,
|
rehypePlugins,
|
||||||
|
staticDir: path.join(siteDir, STATIC_DIR_NAME),
|
||||||
metadataPath: (mdxPath: string) => {
|
metadataPath: (mdxPath: string) => {
|
||||||
// Note that metadataPath must be the same/in-sync as
|
// Note that metadataPath must be the same/in-sync as
|
||||||
// the path from createData for each MDX.
|
// the path from createData for each MDX.
|
||||||
|
|
|
@ -959,8 +959,11 @@ You can use images by requiring them and using an image tag through MDX:
|
||||||
# My markdown page
|
# My markdown page
|
||||||
|
|
||||||
<img src={require('./assets/docusaurus-asset-example-banner.png').default} />
|
<img src={require('./assets/docusaurus-asset-example-banner.png').default} />
|
||||||
```
|
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|

|
||||||
|
```
|
||||||
The ES imports syntax also works:
|
The ES imports syntax also works:
|
||||||
|
|
||||||
```mdx
|
```mdx
|
||||||
|
|
Loading…
Add table
Reference in a new issue