mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-12 16:47:26 +02:00
fix(v2): fix encoding of markdown image/file inline file-loaders (#4736)
* try to reproduce windows edge case due to file encoding * mdx loader => required file paths should be escaped * revert bad change * try to fix posix path issues * try to fix posix path issues * attempt to fix the file-loader edge cases with non-ascii chars * Add more example image edge-cases
This commit is contained in:
parent
39105f03d4
commit
a79c70c954
12 changed files with 148 additions and 40 deletions
|
@ -11,7 +11,11 @@ const url = require('url');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const escapeHtml = require('escape-html');
|
const escapeHtml = require('escape-html');
|
||||||
const {getFileLoaderUtils} = require('@docusaurus/core/lib/webpack/utils');
|
const {getFileLoaderUtils} = require('@docusaurus/core/lib/webpack/utils');
|
||||||
const {posixPath, toMessageRelativeFilePath} = require('@docusaurus/utils');
|
const {
|
||||||
|
posixPath,
|
||||||
|
escapePath,
|
||||||
|
toMessageRelativeFilePath,
|
||||||
|
} = require('@docusaurus/utils');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loaders: {inlineMarkdownImageFileLoader},
|
loaders: {inlineMarkdownImageFileLoader},
|
||||||
|
@ -22,7 +26,9 @@ const createJSX = (node, pathUrl) => {
|
||||||
jsxNode.type = 'jsx';
|
jsxNode.type = 'jsx';
|
||||||
jsxNode.value = `<img ${node.alt ? `alt={"${escapeHtml(node.alt)}"} ` : ''}${
|
jsxNode.value = `<img ${node.alt ? `alt={"${escapeHtml(node.alt)}"} ` : ''}${
|
||||||
node.url
|
node.url
|
||||||
? `src={require("${inlineMarkdownImageFileLoader}${pathUrl}").default}`
|
? `src={require("${inlineMarkdownImageFileLoader}${escapePath(
|
||||||
|
pathUrl,
|
||||||
|
)}").default}`
|
||||||
: ''
|
: ''
|
||||||
}${node.title ? ` title="${escapeHtml(node.title)}"` : ''} />`;
|
}${node.title ? ` title="${escapeHtml(node.title)}"` : ''} />`;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,11 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const {toMessageRelativeFilePath, posixPath} = require('@docusaurus/utils');
|
const {
|
||||||
|
toMessageRelativeFilePath,
|
||||||
|
posixPath,
|
||||||
|
escapePath,
|
||||||
|
} = require('@docusaurus/utils');
|
||||||
|
|
||||||
const visit = require('unist-util-visit');
|
const visit = require('unist-util-visit');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
@ -43,7 +47,9 @@ function toAssetRequireNode({node, filePath, requireAssetPath}) {
|
||||||
? relativeRequireAssetPath
|
? relativeRequireAssetPath
|
||||||
: `./${relativeRequireAssetPath}`;
|
: `./${relativeRequireAssetPath}`;
|
||||||
|
|
||||||
const href = `require('${inlineMarkdownLinkFileLoader}${relativeRequireAssetPath}').default`;
|
const href = `require('${inlineMarkdownLinkFileLoader}${escapePath(
|
||||||
|
relativeRequireAssetPath,
|
||||||
|
)}').default`;
|
||||||
const children = (node.children || []).map((n) => toValue(n)).join('');
|
const children = (node.children || []).map((n) => toValue(n)).join('');
|
||||||
const title = node.title ? `title="${escapeHtml(node.title)}"` : '';
|
const title = node.title ? `title="${escapeHtml(node.title)}"` : '';
|
||||||
|
|
||||||
|
|
25
packages/docusaurus-utils/src/__tests__/escapePath.test.ts
Normal file
25
packages/docusaurus-utils/src/__tests__/escapePath.test.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* 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 {escapePath} from '../escapePath';
|
||||||
|
|
||||||
|
describe('escapePath', () => {
|
||||||
|
test('escapePath works', () => {
|
||||||
|
const asserts: Record<string, string> = {
|
||||||
|
'c:/aaaa\\bbbb': 'c:/aaaa\\\\bbbb',
|
||||||
|
'c:\\aaaa\\bbbb\\★': 'c:\\\\aaaa\\\\bbbb\\\\★',
|
||||||
|
'\\\\?\\c:\\aaaa\\bbbb': '\\\\\\\\?\\\\c:\\\\aaaa\\\\bbbb',
|
||||||
|
'c:\\aaaa\\bbbb': 'c:\\\\aaaa\\\\bbbb',
|
||||||
|
'foo\\bar': 'foo\\\\bar',
|
||||||
|
'foo\\bar/lol': 'foo\\\\bar/lol',
|
||||||
|
'website\\docs/**/*.{md,mdx}': 'website\\\\docs/**/*.{md,mdx}',
|
||||||
|
};
|
||||||
|
Object.keys(asserts).forEach((file) => {
|
||||||
|
expect(escapePath(file)).toBe(asserts[file]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -38,7 +38,7 @@ import {sum} from 'lodash';
|
||||||
|
|
||||||
describe('load utils', () => {
|
describe('load utils', () => {
|
||||||
test('aliasedSitePath', () => {
|
test('aliasedSitePath', () => {
|
||||||
const asserts = {
|
const asserts: Record<string, string> = {
|
||||||
'user/website/docs/asd.md': '@site/docs/asd.md',
|
'user/website/docs/asd.md': '@site/docs/asd.md',
|
||||||
'user/website/versioned_docs/foo/bar.md':
|
'user/website/versioned_docs/foo/bar.md':
|
||||||
'@site/versioned_docs/foo/bar.md',
|
'@site/versioned_docs/foo/bar.md',
|
||||||
|
@ -51,23 +51,8 @@ describe('load utils', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('posixPath', () => {
|
|
||||||
const asserts = {
|
|
||||||
'c:/aaaa\\bbbb': 'c:/aaaa/bbbb',
|
|
||||||
'c:\\aaaa\\bbbb\\★': 'c:\\aaaa\\bbbb\\★',
|
|
||||||
'\\\\?\\c:\\aaaa\\bbbb': '\\\\?\\c:\\aaaa\\bbbb',
|
|
||||||
'c:\\aaaa\\bbbb': 'c:/aaaa/bbbb',
|
|
||||||
'foo\\bar': 'foo/bar',
|
|
||||||
'foo\\bar/lol': 'foo/bar/lol',
|
|
||||||
'website\\docs/**/*.{md,mdx}': 'website/docs/**/*.{md,mdx}',
|
|
||||||
};
|
|
||||||
Object.keys(asserts).forEach((file) => {
|
|
||||||
expect(posixPath(file)).toBe(asserts[file]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('genComponentName', () => {
|
test('genComponentName', () => {
|
||||||
const asserts = {
|
const asserts: Record<string, string> = {
|
||||||
'/': 'index',
|
'/': 'index',
|
||||||
'/foo-bar': 'FooBar096',
|
'/foo-bar': 'FooBar096',
|
||||||
'/foo/bar': 'FooBar1Df',
|
'/foo/bar': 'FooBar1Df',
|
||||||
|
@ -99,7 +84,7 @@ describe('load utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docuHash', () => {
|
test('docuHash', () => {
|
||||||
const asserts = {
|
const asserts: Record<string, string> = {
|
||||||
'': '-d41',
|
'': '-d41',
|
||||||
'/': 'index',
|
'/': 'index',
|
||||||
'/foo-bar': 'foo-bar-096',
|
'/foo-bar': 'foo-bar-096',
|
||||||
|
@ -115,7 +100,7 @@ describe('load utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fileToPath', () => {
|
test('fileToPath', () => {
|
||||||
const asserts = {
|
const asserts: Record<string, string> = {
|
||||||
'index.md': '/',
|
'index.md': '/',
|
||||||
'hello/index.md': '/hello/',
|
'hello/index.md': '/hello/',
|
||||||
'foo.md': '/foo',
|
'foo.md': '/foo',
|
||||||
|
@ -166,7 +151,7 @@ describe('load utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('genChunkName', () => {
|
test('genChunkName', () => {
|
||||||
const firstAssert = {
|
const firstAssert: Record<string, string> = {
|
||||||
'/docs/adding-blog': 'docs-adding-blog-062',
|
'/docs/adding-blog': 'docs-adding-blog-062',
|
||||||
'/docs/versioning': 'docs-versioning-8a8',
|
'/docs/versioning': 'docs-versioning-8a8',
|
||||||
'/': 'index',
|
'/': 'index',
|
||||||
|
@ -186,7 +171,7 @@ describe('load utils', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Even with same preferred name, still different chunk name for different path
|
// Even with same preferred name, still different chunk name for different path
|
||||||
const secondAssert = {
|
const secondAssert: Record<string, string> = {
|
||||||
'/blog/1': 'blog-85-f-089',
|
'/blog/1': 'blog-85-f-089',
|
||||||
'/blog/2': 'blog-353-489',
|
'/blog/2': 'blog-353-489',
|
||||||
};
|
};
|
||||||
|
@ -195,7 +180,7 @@ describe('load utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only generate short unique id
|
// Only generate short unique id
|
||||||
const thirdAssert = {
|
const thirdAssert: Record<string, string> = {
|
||||||
a: '0cc175b9',
|
a: '0cc175b9',
|
||||||
b: '92eb5ffe',
|
b: '92eb5ffe',
|
||||||
c: '4a8a08f0',
|
c: '4a8a08f0',
|
||||||
|
|
25
packages/docusaurus-utils/src/__tests__/posixPath.test.ts
Normal file
25
packages/docusaurus-utils/src/__tests__/posixPath.test.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* 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 {posixPath} from '../posixPath';
|
||||||
|
|
||||||
|
describe('posixPath', () => {
|
||||||
|
test('posixPath works', () => {
|
||||||
|
const asserts: Record<string, string> = {
|
||||||
|
'c:/aaaa\\bbbb': 'c:/aaaa/bbbb',
|
||||||
|
'c:\\aaaa\\bbbb\\★': 'c:\\aaaa\\bbbb\\★',
|
||||||
|
'\\\\?\\c:\\aaaa\\bbbb': '\\\\?\\c:\\aaaa\\bbbb',
|
||||||
|
'c:\\aaaa\\bbbb': 'c:/aaaa/bbbb',
|
||||||
|
'foo\\bar': 'foo/bar',
|
||||||
|
'foo\\bar/lol': 'foo/bar/lol',
|
||||||
|
'website\\docs/**/*.{md,mdx}': 'website/docs/**/*.{md,mdx}',
|
||||||
|
};
|
||||||
|
Object.keys(asserts).forEach((file) => {
|
||||||
|
expect(posixPath(file)).toBe(asserts[file]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
23
packages/docusaurus-utils/src/escapePath.ts
Normal file
23
packages/docusaurus-utils/src/escapePath.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When you have a path like C:\X\Y
|
||||||
|
* It is not safe to use directly when generating code
|
||||||
|
* For example, this would fail due to unescaped \: `<img src={require('${filePath}')} />`
|
||||||
|
* But this would work: `<img src={require('${escapePath(filePath)}')} />`
|
||||||
|
*
|
||||||
|
* Workaround for issue in posixPath, maybe we won't need it anymore soon?
|
||||||
|
* https://github.com/facebook/docusaurus/issues/4730#issuecomment-833530370
|
||||||
|
* https://github.com/sindresorhus/slash/pull/16#issuecomment-833528479
|
||||||
|
*/
|
||||||
|
export function escapePath(str: string): string {
|
||||||
|
const escaped = JSON.stringify(str);
|
||||||
|
|
||||||
|
// Remove the " around the json string;
|
||||||
|
return escaped.substring(1, escaped.length - 1);
|
||||||
|
}
|
|
@ -21,9 +21,14 @@ import {
|
||||||
// @ts-expect-error: no typedefs :s
|
// @ts-expect-error: no typedefs :s
|
||||||
import resolvePathnameUnsafe from 'resolve-pathname';
|
import resolvePathnameUnsafe from 'resolve-pathname';
|
||||||
|
|
||||||
|
import {posixPath as posixPathImport} from './posixPath';
|
||||||
|
|
||||||
|
export const posixPath = posixPathImport;
|
||||||
|
|
||||||
export * from './codeTranslationsUtils';
|
export * from './codeTranslationsUtils';
|
||||||
export * from './markdownParser';
|
export * from './markdownParser';
|
||||||
export * from './markdownLinks';
|
export * from './markdownLinks';
|
||||||
|
export * from './escapePath';
|
||||||
|
|
||||||
const fileHash = new Map();
|
const fileHash = new Map();
|
||||||
export async function generate(
|
export async function generate(
|
||||||
|
@ -129,20 +134,6 @@ export function genComponentName(pagePath: string): string {
|
||||||
return upperFirst(camelCase(pageHash));
|
return upperFirst(camelCase(pageHash));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert Windows backslash paths to posix style paths.
|
|
||||||
* E.g: endi\\lie -> endi/lie
|
|
||||||
*/
|
|
||||||
export function posixPath(str: string): string {
|
|
||||||
const isExtendedLengthPath = /^\\\\\?\\/.test(str);
|
|
||||||
const hasNonAscii = /[^\u0000-\u0080]+/.test(str); // eslint-disable-line
|
|
||||||
|
|
||||||
if (isExtendedLengthPath || hasNonAscii) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
return str.replace(/\\/g, '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
// When you want to display a path in a message/warning/error,
|
// When you want to display a path in a message/warning/error,
|
||||||
// it's more convenient to:
|
// it's more convenient to:
|
||||||
// - make it relative to cwd()
|
// - make it relative to cwd()
|
||||||
|
|
27
packages/docusaurus-utils/src/posixPath.ts
Normal file
27
packages/docusaurus-utils/src/posixPath.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Windows backslash paths to posix style paths.
|
||||||
|
* E.g: endi\\lie -> endi/lie
|
||||||
|
*
|
||||||
|
* Looks like this code was originally copied from https://github.com/sindresorhus/slash/blob/main/index.js
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function posixPath(str: string): string {
|
||||||
|
const isExtendedLengthPath = /^\\\\\?\\/.test(str);
|
||||||
|
|
||||||
|
// TODO not sure why we need this
|
||||||
|
// See https://github.com/sindresorhus/slash/pull/16#issuecomment-833528479
|
||||||
|
// See https://github.com/facebook/docusaurus/issues/4730#issuecomment-833530370
|
||||||
|
const hasNonAscii = /[^\u0000-\u0080]+/.test(str); // eslint-disable-line
|
||||||
|
|
||||||
|
if (isExtendedLengthPath || hasNonAscii) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
return str.replace(/\\/g, '/');
|
||||||
|
}
|
|
@ -222,3 +222,23 @@ See https://github.com/facebook/docusaurus/pull/1584
|
||||||
Code tag + double pipe: <code>||</code>
|
Code tag + double pipe: <code>||</code>
|
||||||
|
|
||||||
Code tag + double pipe: <code>||</code>
|
Code tag + double pipe: <code>||</code>
|
||||||
|
|
||||||
|
## Images edge cases
|
||||||
|
|
||||||
|

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

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

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

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

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

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

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

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

|
||||||
|
|
BIN
website/static/dogfooding/4/docu.png
Normal file
BIN
website/static/dogfooding/4/docu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5 KiB |
BIN
website/static/dogfooding/4/图片.png
Normal file
BIN
website/static/dogfooding/4/图片.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 301 KiB |
BIN
website/static/dogfooding/新控制器空间/图片.png
Normal file
BIN
website/static/dogfooding/新控制器空间/图片.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5 KiB |
Loading…
Add table
Add a link
Reference in a new issue