docs(core): fix relative image paths (#5635)

This commit is contained in:
Jack Hsu 2021-05-12 11:04:55 -04:00 committed by GitHub
parent 0bd6deaa65
commit 6db97832e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 87 additions and 13 deletions

View File

@ -1,5 +1,5 @@
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join, relative } from 'path';
import matter from 'gray-matter'; import matter from 'gray-matter';
import { readJsonFile } from '@nrwl/workspace'; import { readJsonFile } from '@nrwl/workspace';
import { import {
@ -29,7 +29,10 @@ export function getDocument(
} }
return { return {
filePath: docPath, filePath: relative(
version === 'preview' ? previewRootPath : archiveRootPath,
docPath
),
data: file.data, data: file.data,
content: file.content, content: file.content,
excerpt: file.excerpt, excerpt: file.excerpt,

View File

@ -5,7 +5,18 @@ import Content from './content';
describe('Content', () => { describe('Content', () => {
it('should render successfully', () => { it('should render successfully', () => {
const { baseElement } = render(<Content data="hello" />); const { baseElement } = render(
<Content
version="1.0.0"
flavor="react"
document={{
content: '',
data: {},
filePath: 'a/b/test.md',
excerpt: '',
}}
/>
);
expect(baseElement).toBeTruthy(); expect(baseElement).toBeTruthy();
}); });
}); });

View File

@ -3,6 +3,7 @@ import ReactMarkdown from 'react-markdown';
import autolinkHeadings from 'rehype-autolink-headings'; import autolinkHeadings from 'rehype-autolink-headings';
import gfm from 'remark-gfm'; import gfm from 'remark-gfm';
import slug from 'rehype-slug'; import slug from 'rehype-slug';
import { DocumentData } from '@nrwl/nx-dev/data-access-documents';
import { transformLinkPath } from './renderers/transform-link-path'; import { transformLinkPath } from './renderers/transform-link-path';
import { transformImagePath } from './renderers/transform-image-path'; import { transformImagePath } from './renderers/transform-image-path';
@ -10,7 +11,7 @@ import { renderIframes } from './renderers/render-iframe';
import { CodeBlock } from './code-block'; import { CodeBlock } from './code-block';
export interface ContentProps { export interface ContentProps {
data: string; document: DocumentData;
flavor: string; flavor: string;
version: string; version: string;
} }
@ -41,12 +42,15 @@ export function Content(props: ContentProps) {
<ReactMarkdown <ReactMarkdown
remarkPlugins={[gfm]} remarkPlugins={[gfm]}
rehypePlugins={[slug, autolinkHeadings, renderIframes]} rehypePlugins={[slug, autolinkHeadings, renderIframes]}
children={props.data} children={props.document.content}
transformLinkUri={transformLinkPath({ transformLinkUri={transformLinkPath({
flavor: props.flavor, flavor: props.flavor,
version: props.version, version: props.version,
})} })}
transformImageUri={transformImagePath(props.version)} transformImageUri={transformImagePath({
version: props.version,
document: props.document,
})}
className="prose max-w-none" className="prose max-w-none"
components={components} components={components}
/> />

View File

@ -48,7 +48,7 @@ export function DocViewer({
className="min-w-0 w-full flex-auto lg:static lg:max-h-full lg:overflow-visible" className="min-w-0 w-full flex-auto lg:static lg:max-h-full lg:overflow-visible"
> >
<Content <Content
data={document.content} document={document}
flavor={flavor.value} flavor={flavor.value}
version={version.path} version={version.path}
/> />

View File

@ -0,0 +1,39 @@
import { transformImagePath } from './transform-image-path';
describe('transformImagePath', () => {
it('should transform relative paths', () => {
const opts = {
version: '1.0.0',
document: { content: '', excerpt: '', filePath: 'a/b/test.md', data: {} },
};
const transform = transformImagePath(opts);
expect(transform('./test.png')).toEqual('/documentation/a/b/test.png');
expect(transform('../test.png')).toEqual('/documentation/a/test.png');
expect(transform('../../test.png')).toEqual('/documentation/test.png');
});
it('should transform absolute paths', () => {
const opts = {
version: '1.0.0',
document: { content: '', excerpt: '', filePath: 'a/b/test.md', data: {} },
};
const transform = transformImagePath(opts);
expect(transform('/shared/test.png')).toEqual(
'/documentation/1.0.0/shared/test.png'
);
});
it('should support preview links', () => {
const opts = {
version: 'preview',
document: { content: '', excerpt: '', filePath: 'a/b/test.md', data: {} },
};
const transform = transformImagePath(opts);
expect(transform('/shared/test.png')).toEqual(
'/api/preview-asset?uri=%2Fshared%2Ftest.png&document=a%2Fb%2Ftest.md'
);
});
});

View File

@ -1,12 +1,26 @@
import { uriTransformer } from 'react-markdown'; import { uriTransformer } from 'react-markdown';
import { DocumentData } from '@nrwl/nx-dev/data-access-documents';
import { join } from 'path';
export function transformImagePath(version: string): (src: string) => string { export function transformImagePath({
version,
document,
}: {
version: string;
document: DocumentData;
}): (src: string) => string {
return (src) => { return (src) => {
if (/\.(gif|jpe?g|tiff?|png|webp|bmp)$/i.test(src)) { if (/\.(gif|jpe?g|tiff?|png|webp|bmp)$/i.test(src)) {
if (version === 'preview') { if (version === 'preview') {
src = `/api/preview-asset?uri=${encodeURIComponent(src)}`; src = `/api/preview-asset?uri=${encodeURIComponent(
src
)}&document=${encodeURIComponent(document.filePath)}`;
} else { } else {
src = `/documentation/${version}`.concat(src); if (src.startsWith('.')) {
src = join('/documentation', document.filePath, '..', src);
} else {
src = `/documentation/${version}`.concat(src);
}
} }
} }
return uriTransformer(src); return uriTransformer(src);

View File

@ -8,12 +8,15 @@ import * as send from 'send';
const previewRootPath = join(appRootPath, 'docs'); const previewRootPath = join(appRootPath, 'docs');
export default function (req: NextApiRequest, res: NextApiResponse) { export default function (req: NextApiRequest, res: NextApiResponse) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve) => {
if (Array.isArray(req.query.uri)) { if (Array.isArray(req.query.uri) || Array.isArray(req.query.document)) {
res.writeHead(422, { 'Content-Type': 'text/plain' }); res.writeHead(422, { 'Content-Type': 'text/plain' });
res.end('Invalid URI'); res.end('Invalid URI');
} else { } else {
const stream = send(req, decodeURIComponent(req.query.uri), { const uri = decodeURIComponent(req.query.uri);
const document = decodeURIComponent(req.query.document);
const src = uri.startsWith('.') ? join(document, '..', uri) : uri;
const stream = send(req, src, {
// Files outside of the root are forbidden and will result in 404. // Files outside of the root are forbidden and will result in 404.
root: previewRootPath, root: previewRootPath,
}); });