misc(v2): remove legacy from docs

This commit is contained in:
Yangshun Tay 2019-10-10 19:45:50 -07:00
parent e3f25b2a45
commit 54e9e025d8
34 changed files with 24 additions and 24 deletions

View file

@ -0,0 +1,14 @@
{
"docs": {
"Test": [
"foo/bar",
"foo/baz",
{
"type": "superman"
}
],
"Guides": [
"hello"
]
}
}

View file

@ -0,0 +1,67 @@
---
id: bar
title: Bar
description: This is custom description
---
# Remarkable
> Experience real-time editing with Remarkable!
Click the `clear` link to start with a clean slate, or get the `permalink` to share or save your results.
***
# h1 Heading
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Horizontal Rules
___
***
***
## Typographic replacements
Enable typographer option to see result.
(c) (C) (r) (R) (tm) (TM) (p) (P) +-
test.. test... test..... test?..... test!....
!!!!!! ???? ,,
Remarkable -- awesome
"Smartypants, double quotes"
'Smartypants, single quotes'
## Emphasis
**This is bold text**
__This is bold text__
*This is italic text*
_This is italic text_
~~Deleted text~~
Superscript: 19^th^
Subscript: H~2~O
++Inserted text++
==Marked text==

View file

@ -0,0 +1,74 @@
---
id: baz
title: baz
---
## Images
Like links, Images also have a footnote style syntax
![Alt text][id]
With a reference later in the document defining the URL location:
[id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat"
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ "title text!")
Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
## Footnotes
Footnote 1 link[^first].
Footnote 2 link[^second].
Inline footnote^[Text of inline footnote] definition.
Duplicated footnote reference[^second].
[^first]: Footnote **can have markup**
and multiple paragraphs.
[^second]: Footnote text.
## Definition lists
Term 1
: Definition 1
with lazy continuation.
Term 2 with *inline markup*
: Definition 2
{ some code, part of Definition 2 }
Third paragraph of definition 2.
_Compact style:_
Term 1
~ Definition 1
Term 2
~ Definition 2a
~ Definition 2b
## Abbreviations
This is HTML abbreviation example.
It converts "HTML", but keep intact partial entries like "xxxHTMLyyy" and so on.
*[HTML]: Hyper Text Markup Language

View file

@ -0,0 +1,54 @@
---
id: hello
title: Hello, World !
---
Hi, Endilie here :)
## Relative links
Replace this
[foo](foo/bar.md)
Can't replace this
[file](file.md)
Do not replace below
```
[hello](hello.md)
```
## Blockquotes
> Blockquotes can also be nested...
>> ...by using additional greater-than signs right next to each other...
> > > ...or with spaces between arrows.
## Lists
Unordered
+ Create a list by starting a line with `+`, `-`, or `*`
+ Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
* Ac tristique libero volutpat at
+ Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
+ Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
1. You can use sequential numbers...
1. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar

View file

@ -0,0 +1,7 @@
---
id: permalink
title: Permalink
permalink: :baseUrl:docsUrl/endiliey/:id
---
This has a different permalink

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module.exports = {
docs: [
{
type: 'category',
label: 'level 1',
items: [
'a',
{
type: 'category',
label: 'level 2',
items: [
{
type: 'category',
label: 'level 3',
items: [
'c',
{
type: 'category',
label: 'level 4',
items: [
'd',
{
type: 'category',
label: 'deeper more more',
items: ['e'],
},
],
},
],
},
'f',
],
},
],
},
],
};

View file

@ -0,0 +1,20 @@
{
"docs": {
"Test": [
"foo/bar",
"foo/baz",
{
"type": "link",
"label": "Github",
"href": "https://github.com"
},
{
"type": "ref",
"id": "hello"
}
],
"Guides": [
"hello"
]
}
}

View file

@ -0,0 +1,98 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`loadSidebars sidebars with deep level of category 1`] = `
Object {
"docs": Array [
Object {
"items": Array [
Object {
"id": "a",
"type": "doc",
},
Object {
"items": Array [
Object {
"items": Array [
Object {
"id": "c",
"type": "doc",
},
Object {
"items": Array [
Object {
"id": "d",
"type": "doc",
},
Object {
"items": Array [
Object {
"id": "e",
"type": "doc",
},
],
"label": "deeper more more",
"type": "category",
},
],
"label": "level 4",
"type": "category",
},
],
"label": "level 3",
"type": "category",
},
Object {
"id": "f",
"type": "doc",
},
],
"label": "level 2",
"type": "category",
},
],
"label": "level 1",
"type": "category",
},
],
}
`;
exports[`loadSidebars sidebars with known sidebar item type 1`] = `
Object {
"docs": Array [
Object {
"items": Array [
Object {
"id": "foo/bar",
"type": "doc",
},
Object {
"id": "foo/baz",
"type": "doc",
},
Object {
"href": "https://github.com",
"label": "Github",
"type": "link",
},
Object {
"id": "hello",
"type": "ref",
},
],
"label": "Test",
"type": "category",
},
Object {
"items": Array [
Object {
"id": "hello",
"type": "doc",
},
],
"label": "Guides",
"type": "category",
},
],
}
`;

View file

@ -0,0 +1,59 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import pluginContentDocs from '../index';
import {LoadContext} from '@docusaurus/types';
describe('loadDocs', () => {
test('simple website', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const siteConfig = {
title: 'Hello',
baseUrl: '/',
url: 'https://docusaurus.io',
};
const context = {
siteDir,
siteConfig,
} as LoadContext;
const sidebarPath = path.join(siteDir, 'sidebars.json');
const pluginPath = 'docs';
const plugin = pluginContentDocs(context, {
path: pluginPath,
sidebarPath,
});
const {docsMetadata} = await plugin.loadContent();
expect(docsMetadata.hello).toEqual({
id: 'hello',
permalink: '/docs/hello',
previous: {
title: 'baz',
permalink: '/docs/foo/baz',
},
sidebar: 'docs',
source: path.join('@site', pluginPath, 'hello.md'),
title: 'Hello, World !',
description: 'Hi, Endilie here :)',
});
expect(docsMetadata['foo/bar']).toEqual({
id: 'foo/bar',
next: {
title: 'baz',
permalink: '/docs/foo/baz',
},
permalink: '/docs/foo/bar',
sidebar: 'docs',
source: path.join('@site', pluginPath, 'foo', 'bar.md'),
title: 'Bar',
description: 'This is custom description',
});
});
});

View file

@ -0,0 +1,87 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import processMetadata from '../metadata';
describe('processMetadata', () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const siteConfig = {
title: 'Hello',
baseUrl: '/',
url: 'https://docusaurus.io',
};
const pluginPath = 'docs';
const docsDir = path.resolve(siteDir, pluginPath);
test('normal docs', async () => {
const sourceA = path.join('foo', 'bar.md');
const sourceB = path.join('hello.md');
const [dataA, dataB] = await Promise.all([
processMetadata(sourceA, docsDir, {}, siteConfig, pluginPath, siteDir),
processMetadata(sourceB, docsDir, {}, siteConfig, pluginPath, siteDir),
]);
expect(dataA).toEqual({
id: 'foo/bar',
permalink: '/docs/foo/bar',
source: path.join('@site', pluginPath, sourceA),
title: 'Bar',
description: 'This is custom description',
});
expect(dataB).toEqual({
id: 'hello',
permalink: '/docs/hello',
source: path.join('@site', pluginPath, sourceB),
title: 'Hello, World !',
description: `Hi, Endilie here :)`,
});
});
test('docs with custom permalink', async () => {
const source = path.join('permalink.md');
const data = await processMetadata(
source,
docsDir,
{},
siteConfig,
pluginPath,
siteDir,
);
expect(data).toEqual({
id: 'permalink',
permalink: '/docs/endiliey/permalink',
source: path.join('@site', pluginPath, source),
title: 'Permalink',
description: 'This has a different permalink',
});
});
test('docs with editUrl', async () => {
const editUrl =
'https://github.com/facebook/docusaurus/edit/master/website/docs/';
const source = path.join('foo', 'baz.md');
const data = await processMetadata(
source,
docsDir,
{},
siteConfig,
pluginPath,
siteDir,
editUrl,
);
expect(data).toEqual({
id: 'foo/baz',
permalink: '/docs/foo/baz',
source: path.join('@site', pluginPath, source),
title: 'baz',
editUrl:
'https://github.com/facebook/docusaurus/edit/master/website/docs/foo/baz.md',
description: '## Images',
});
});
});

View file

@ -0,0 +1,228 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import createOrder from '../order';
describe('createOrder', () => {
test('multiple sidebars with subcategory', () => {
const result = createOrder({
docs: [
{
type: 'category',
label: 'Category1',
items: [
{
type: 'category',
label: 'Subcategory 1',
items: [{type: 'doc', id: 'doc1'}],
},
{
type: 'category',
label: 'Subcategory 2',
items: [{type: 'doc', id: 'doc2'}],
},
],
},
{
type: 'category',
label: 'Category2',
items: [{type: 'doc', id: 'doc3'}, {type: 'doc', id: 'doc4'}],
},
],
otherDocs: [
{
type: 'category',
label: 'Category1',
items: [{type: 'doc', id: 'doc5'}],
},
],
});
expect(result).toEqual({
doc1: {
next: 'doc2',
previous: undefined,
sidebar: 'docs',
},
doc2: {
next: 'doc3',
previous: 'doc1',
sidebar: 'docs',
},
doc3: {
next: 'doc4',
previous: 'doc2',
sidebar: 'docs',
},
doc4: {
next: undefined,
previous: 'doc3',
sidebar: 'docs',
},
doc5: {
next: undefined,
previous: undefined,
sidebar: 'otherDocs',
},
});
});
test('multiple sidebars without subcategory', () => {
const result = createOrder({
docs: [
{
type: 'category',
label: 'Category1',
items: [{type: 'doc', id: 'doc1'}, {type: 'doc', id: 'doc2'}],
},
{
type: 'category',
label: 'Category2',
items: [{type: 'doc', id: 'doc3'}, {type: 'doc', id: 'doc4'}],
},
],
otherDocs: [
{
type: 'category',
label: 'Category1',
items: [{type: 'doc', id: 'doc5'}],
},
],
});
expect(result).toEqual({
doc1: {
next: 'doc2',
previous: undefined,
sidebar: 'docs',
},
doc2: {
next: 'doc3',
previous: 'doc1',
sidebar: 'docs',
},
doc3: {
next: 'doc4',
previous: 'doc2',
sidebar: 'docs',
},
doc4: {
next: undefined,
previous: 'doc3',
sidebar: 'docs',
},
doc5: {
next: undefined,
previous: undefined,
sidebar: 'otherDocs',
},
});
});
test('versioned sidebars', () => {
const result = createOrder({
docs: [
{
type: 'category',
label: 'Category1',
items: [{type: 'doc', id: 'doc1'}],
},
],
'version-1.2.3-docs': [
{
type: 'category',
label: 'Category1',
items: [{type: 'doc', id: 'version-1.2.3-doc2'}],
},
{
type: 'category',
label: 'Category2',
items: [{type: 'doc', id: 'version-1.2.3-doc1'}],
},
],
});
expect(result).toEqual({
doc1: {
next: undefined,
previous: undefined,
sidebar: 'docs',
},
'version-1.2.3-doc1': {
next: undefined,
previous: 'version-1.2.3-doc2',
sidebar: 'version-1.2.3-docs',
},
'version-1.2.3-doc2': {
next: 'version-1.2.3-doc1',
previous: undefined,
sidebar: 'version-1.2.3-docs',
},
});
});
test('multiple sidebars with subcategories, refs and external links', () => {
const result = createOrder({
docs: [
{
type: 'category',
label: 'Category1',
items: [
{
type: 'category',
label: 'Subcategory 1',
items: [{type: 'link', href: '//example.com', label: 'bar'}],
},
{
type: 'category',
label: 'Subcategory 2',
items: [{type: 'doc', id: 'doc2'}],
},
{
type: 'category',
label: 'Subcategory 1',
items: [{type: 'link', href: '//example2.com', label: 'baz'}],
},
],
},
{
type: 'category',
label: 'Category2',
items: [{type: 'doc', id: 'doc3'}, {type: 'ref', id: 'doc4'}],
},
],
otherDocs: [
{
type: 'category',
label: 'Category1',
items: [{type: 'doc', id: 'doc5'}],
},
],
});
expect(result).toEqual({
doc2: {
next: 'doc3',
previous: undefined,
sidebar: 'docs',
},
doc3: {
next: undefined,
previous: 'doc2',
sidebar: 'docs',
},
doc5: {
next: undefined,
previous: undefined,
sidebar: 'otherDocs',
},
});
});
test('edge cases', () => {
expect(createOrder({})).toEqual({});
expect(createOrder(undefined)).toEqual({});
expect(() => createOrder(null)).toThrowErrorMatchingInlineSnapshot(
`"Cannot convert undefined or null to object"`,
);
});
});

View file

@ -0,0 +1,52 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import loadSidebars from '../sidebars';
/* eslint-disable global-require, import/no-dynamic-require */
describe('loadSidebars', () => {
test('sidebars with known sidebar item type', async () => {
const sidebarPath = path.join(
__dirname,
'__fixtures__',
'website',
'sidebars.json',
);
const result = loadSidebars(sidebarPath);
expect(result).toMatchSnapshot();
});
test('sidebars with deep level of category', async () => {
const sidebarPath = path.join(
__dirname,
'__fixtures__',
'website',
'sidebars-category.js',
);
const result = loadSidebars(sidebarPath);
expect(result).toMatchSnapshot();
});
test('sidebars with unknown sidebar item type', async () => {
const sidebarPath = path.join(
__dirname,
'__fixtures__',
'website',
'bad-sidebars.json',
);
expect(() => loadSidebars(sidebarPath)).toThrowErrorMatchingInlineSnapshot(
`"Unknown sidebar item type: superman"`,
);
});
test('no sidebars', () => {
const result = loadSidebars(null);
expect(result).toEqual({});
});
});

View file

@ -0,0 +1,279 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import globby from 'globby';
import fs from 'fs-extra';
import path from 'path';
import {idx, normalizeUrl, docuHash} from '@docusaurus/utils';
import {LoadContext, Plugin, DocusaurusConfig} from '@docusaurus/types';
import createOrder from './order';
import loadSidebars from './sidebars';
import processMetadata from './metadata';
import {
PluginOptions,
Sidebar,
Order,
DocsMetadata,
LoadedContent,
SourceToPermalink,
PermalinkToSidebar,
DocsSidebarItemCategory,
SidebarItemLink,
SidebarItemDoc,
SidebarItemCategory,
DocsSidebar,
DocsBaseMetadata,
MetadataRaw,
} from './types';
import {Configuration} from 'webpack';
const DEFAULT_OPTIONS: PluginOptions = {
path: 'docs', // Path to data on filesystem, relative to site dir.
routeBasePath: 'docs', // URL Route.
include: ['**/*.md', '**/*.mdx'], // Extensions to include.
sidebarPath: '', // Path to sidebar configuration for showing a list of markdown pages.
docLayoutComponent: '@theme/DocPage',
docItemComponent: '@theme/DocItem',
remarkPlugins: [],
rehypePlugins: [],
};
export default function pluginContentDocs(
context: LoadContext,
opts: Partial<PluginOptions>,
): Plugin<LoadedContent | null> {
const options = {...DEFAULT_OPTIONS, ...opts};
const contentPath = path.resolve(context.siteDir, options.path);
let sourceToPermalink: SourceToPermalink = {};
return {
name: 'docusaurus-plugin-content-docs',
getPathsToWatch() {
const {include = []} = options;
const globPattern = include.map(pattern => `${contentPath}/${pattern}`);
return [...globPattern, options.sidebarPath];
},
// Fetches blog contents and returns metadata for the contents.
async loadContent() {
const {include, routeBasePath, sidebarPath, editUrl} = options;
const {siteConfig, siteDir} = context;
const docsDir = contentPath;
if (!fs.existsSync(docsDir)) {
return null;
}
const loadedSidebars: Sidebar = loadSidebars(sidebarPath);
// Build the docs ordering such as next, previous, category and sidebar.
const order: Order = createOrder(loadedSidebars);
// Prepare metadata container.
const docsMetadataRaw: {
[id: string]: MetadataRaw;
} = {};
// Metadata for default docs files.
const docsFiles = await globby(include, {
cwd: docsDir,
});
await Promise.all(
docsFiles.map(async source => {
const metadata: MetadataRaw = await processMetadata(
source,
docsDir,
order,
siteConfig,
routeBasePath,
siteDir,
editUrl,
);
docsMetadataRaw[metadata.id] = metadata;
}),
);
// Construct docsMetadata
const docsMetadata: DocsMetadata = {};
Object.keys(docsMetadataRaw).forEach(currentID => {
let previous;
let next;
const previousID = idx(docsMetadataRaw, [currentID, 'previous']);
if (previousID) {
previous = {
title: idx(docsMetadataRaw, [previousID, 'title']) || 'Previous',
permalink: idx(docsMetadataRaw, [previousID, 'permalink']),
};
}
const nextID = idx(docsMetadataRaw, [currentID, 'next']);
if (nextID) {
next = {
title: idx(docsMetadataRaw, [nextID, 'title']) || 'Next',
permalink: idx(docsMetadataRaw, [nextID, 'permalink']),
};
}
docsMetadata[currentID] = {
...docsMetadataRaw[currentID],
previous,
next,
};
});
const permalinkToSidebar: PermalinkToSidebar = {};
Object.values(docsMetadataRaw).forEach(({source, permalink, sidebar}) => {
sourceToPermalink[source] = permalink;
if (sidebar) {
permalinkToSidebar[permalink] = sidebar;
}
});
const convertDocLink = (item: SidebarItemDoc): SidebarItemLink => {
const linkID = item.id;
const linkMetadata = docsMetadataRaw[linkID];
if (!linkMetadata) {
throw new Error(
`Improper sidebars file, document with id '${linkID}' not found.`,
);
}
return {
type: 'link',
label: linkMetadata.sidebar_label || linkMetadata.title,
href: linkMetadata.permalink,
};
};
const normalizeCategory = (
category: SidebarItemCategory,
): DocsSidebarItemCategory => {
const items = category.items.map(item => {
switch (item.type) {
case 'category':
return normalizeCategory(item as SidebarItemCategory);
case 'ref':
case 'doc':
return convertDocLink(item as SidebarItemDoc);
case 'link':
break;
default:
throw new Error(`Unknown sidebar item type: ${item.type}`);
}
return item as SidebarItemLink;
});
return {...category, items};
};
// Transform the sidebar so that all sidebar item will be in the form of 'link' or 'category' only
// This is what will be passed as props to the UI component
const docsSidebars: DocsSidebar = Object.entries(loadedSidebars).reduce(
(acc: DocsSidebar, [sidebarId, sidebarItemCategories]) => {
acc[sidebarId] = sidebarItemCategories.map(sidebarItemCategory =>
normalizeCategory(sidebarItemCategory),
);
return acc;
},
{},
);
return {
docsMetadata,
docsDir,
docsSidebars,
sourceToPermalink,
permalinkToSidebar,
};
},
async contentLoaded({content, actions}) {
if (!content) {
return;
}
const {docLayoutComponent, docItemComponent, routeBasePath} = options;
const {addRoute, createData} = actions;
const routes = await Promise.all(
Object.values(content.docsMetadata).map(async metadataItem => {
const metadataPath = await createData(
`${docuHash(metadataItem.permalink)}.json`,
JSON.stringify(metadataItem, null, 2),
);
return {
path: metadataItem.permalink,
component: docItemComponent,
exact: true,
modules: {
content: metadataItem.source,
metadata: metadataPath,
},
};
}),
);
const docsBaseMetadata: DocsBaseMetadata = {
docsSidebars: content.docsSidebars,
permalinkToSidebar: content.permalinkToSidebar,
};
const docsBaseRoute = normalizeUrl([
(context.siteConfig as DocusaurusConfig).baseUrl,
routeBasePath,
]);
const docsBaseMetadataPath = await createData(
`${docuHash(docsBaseRoute)}.json`,
JSON.stringify(docsBaseMetadata, null, 2),
);
addRoute({
path: docsBaseRoute,
component: docLayoutComponent,
routes,
modules: {
docsMetadata: docsBaseMetadataPath,
},
});
},
configureWebpack(_, isServer, utils) {
const {getBabelLoader, getCacheLoader} = utils;
const {rehypePlugins, remarkPlugins} = options;
return {
module: {
rules: [
{
test: /(\.mdx?)$/,
include: [contentPath],
use: [
getCacheLoader(isServer),
getBabelLoader(isServer),
{
loader: '@docusaurus/mdx-loader',
options: {
remarkPlugins,
rehypePlugins,
},
},
{
loader: path.resolve(__dirname, './markdown/index.js'),
options: {
siteConfig: context.siteConfig,
siteDir: context.siteDir,
docsDir: contentPath,
sourceToPermalink: sourceToPermalink,
},
},
].filter(Boolean),
},
],
},
} as Configuration;
},
};
}

View file

@ -0,0 +1,64 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import {getOptions} from 'loader-utils';
import {resolve} from 'url';
import {loader} from 'webpack';
export = function(fileString: string) {
const callback = this.async();
const options = Object.assign({}, getOptions(this), {
filepath: this.resourcePath,
});
const {docsDir, siteDir, sourceToPermalink} = options;
// Determine the source dir. e.g: /docs, /website/versioned_docs/version-1.0.0
let sourceDir: string | undefined;
const thisSource = this.resourcePath;
if (thisSource.startsWith(docsDir)) {
sourceDir = docsDir;
}
let content = fileString;
// Replace internal markdown linking (except in fenced blocks).
if (sourceDir) {
let fencedBlock = false;
const lines = content.split('\n').map(line => {
if (line.trim().startsWith('```')) {
fencedBlock = !fencedBlock;
}
if (fencedBlock) return line;
let modifiedLine = line;
// Replace inline-style links or reference-style links e.g:
// This is [Document 1](doc1.md) -> we replace this doc1.md with correct link
// [doc1]: doc1.md -> we replace this doc1.md with correct link
const mdRegex = /(?:(?:\]\()|(?:\]:\s?))(?!https)([^'")\]\s>]+\.mdx?)/g;
let mdMatch = mdRegex.exec(modifiedLine);
while (mdMatch !== null) {
// Replace it to correct html link.
const mdLink = mdMatch[1];
const targetSource = `${sourceDir}/${mdLink}`;
const aliasedSource = (source: string) =>
`@site/${path.relative(siteDir, source)}`;
const permalink =
sourceToPermalink[aliasedSource(resolve(thisSource, mdLink))] ||
sourceToPermalink[aliasedSource(targetSource)];
if (permalink) {
modifiedLine = modifiedLine.replace(mdLink, permalink);
}
mdMatch = mdRegex.exec(modifiedLine);
}
return modifiedLine;
});
content = lines.join('\n');
}
return callback && callback(null, content);
} as loader.Loader;

View file

@ -0,0 +1,91 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import fs from 'fs-extra';
import path from 'path';
import {parse, normalizeUrl} from '@docusaurus/utils';
import {Order, MetadataRaw} from './types';
import {DocusaurusConfig} from '@docusaurus/types';
export default async function processMetadata(
source: string,
docsDir: string,
order: Order,
siteConfig: Partial<DocusaurusConfig>,
docsBasePath: string,
siteDir: string,
editUrl?: string,
): Promise<MetadataRaw> {
const filepath = path.join(docsDir, source);
const fileString = await fs.readFile(filepath, 'utf-8');
const {frontMatter: metadata = {}, excerpt} = parse(fileString);
// Default id is the file name.
if (!metadata.id) {
metadata.id = path.basename(source, path.extname(source));
}
if (metadata.id.includes('/')) {
throw new Error('Document id cannot include "/".');
}
// Default title is the id.
if (!metadata.title) {
metadata.title = metadata.id;
}
if (!metadata.description) {
metadata.description = excerpt;
}
const dirName = path.dirname(source);
if (dirName !== '.') {
const prefix = dirName;
if (prefix) {
metadata.id = `${prefix}/${metadata.id}`;
}
}
// Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it.
const aliasedPath = `@site/${path.relative(siteDir, filepath)}`;
metadata.source = aliasedPath;
// Build the permalink.
const {baseUrl} = siteConfig;
// If user has own custom permalink defined in frontmatter
// e.g: :baseUrl:docsUrl/:langPart/:versionPart/endiliey/:id
if (metadata.permalink) {
metadata.permalink = path.resolve(
metadata.permalink
.replace(/:baseUrl/, baseUrl)
.replace(/:docsUrl/, docsBasePath)
.replace(/:id/, metadata.id),
);
} else {
metadata.permalink = normalizeUrl([baseUrl, docsBasePath, metadata.id]);
}
// Determine order.
const {id} = metadata;
if (order[id]) {
metadata.sidebar = order[id].sidebar;
if (order[id].next) {
metadata.next = order[id].next;
}
if (order[id].previous) {
metadata.previous = order[id].previous;
}
}
if (editUrl) {
metadata.editUrl = normalizeUrl([editUrl, source]);
}
return metadata as MetadataRaw;
}

View file

@ -0,0 +1,72 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {
Sidebar,
SidebarItem,
SidebarItemDoc,
SidebarItemCategory,
Order,
} from './types';
// Build the docs meta such as next, previous, category and sidebar.
export default function createOrder(allSidebars: Sidebar = {}): Order {
const order: Order = {};
Object.keys(allSidebars).forEach(sidebarId => {
const sidebar = allSidebars[sidebarId];
const ids: string[] = [];
const indexItems = ({items}: {items: SidebarItem[]}) => {
items.forEach(item => {
switch (item.type) {
case 'category':
indexItems({
items: (item as SidebarItemCategory).items,
});
break;
case 'ref':
case 'link':
// Refs and links should not be shown in navigation.
break;
case 'doc':
ids.push((item as SidebarItemDoc).id);
break;
default:
throw new Error(
`Unknown item type: ${item.type}. Item: ${JSON.stringify(item)}`,
);
}
});
};
indexItems({items: sidebar});
// eslint-disable-next-line
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
let previous;
let next;
if (i > 0) {
previous = ids[i - 1];
}
if (i < ids.length - 1) {
next = ids[i + 1];
}
order[id] = {
previous,
next,
sidebar: sidebarId,
};
}
});
return order;
}

View file

@ -0,0 +1,116 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import fs from 'fs';
import importFresh from 'import-fresh';
import {
SidebarItemCategory,
Sidebar,
SidebarRaw,
SidebarItem,
SidebarItemCategoryRaw,
} from './types';
/**
* Check that item contains only allowed keys
*/
function assertItem(item: Object, keys: string[]): void {
const unknownKeys = Object.keys(item).filter(
key => !keys.includes(key) && key !== 'type',
);
if (unknownKeys.length) {
throw new Error(
`Unknown sidebar item keys: ${unknownKeys}. Item: ${JSON.stringify(
item,
)}`,
);
}
}
/**
* Normalizes recursively category and all its children. Ensures, that at the end
* each item will be an object with the corresponding type
*/
function normalizeCategory(
category: SidebarItemCategoryRaw,
level = 0,
): SidebarItemCategory {
assertItem(category, ['items', 'label']);
if (!Array.isArray(category.items)) {
throw new Error(
`Error loading ${category.label} category. Category items must be array.`,
);
}
const items: SidebarItem[] = category.items.map(item => {
if (typeof item === 'string') {
return {
type: 'doc',
id: item,
};
}
switch (item.type) {
case 'category':
return normalizeCategory(item as SidebarItemCategoryRaw, level + 1);
case 'link':
assertItem(item, ['href', 'label']);
break;
case 'ref':
assertItem(item, ['id']);
break;
default:
if (item.type !== 'doc') {
throw new Error(`Unknown sidebar item type: ${item.type}`);
}
assertItem(item, ['id']);
break;
}
return item as SidebarItem;
});
return {...category, items};
}
/**
* Converts sidebars object to mapping to arrays of sidebar item objects
*/
function normalizeSidebar(sidebars: SidebarRaw): Sidebar {
return Object.entries(sidebars).reduce(
(acc: Sidebar, [sidebarId, sidebar]) => {
let normalizedSidebar: SidebarItemCategoryRaw[];
if (!Array.isArray(sidebar)) {
// convert sidebar to a more generic structure
normalizedSidebar = Object.entries(sidebar).map(([label, items]) => ({
type: 'category',
label,
items,
}));
} else {
normalizedSidebar = sidebar;
}
acc[sidebarId] = normalizedSidebar.map(item => normalizeCategory(item));
return acc;
},
{},
);
}
export default function loadSidebars(sidebarPath: string): Sidebar {
// We don't want sidebars to be cached because of hotreloading.
let allSidebars: SidebarRaw = {};
if (sidebarPath && fs.existsSync(sidebarPath)) {
allSidebars = importFresh(sidebarPath) as SidebarRaw;
}
return normalizeSidebar(allSidebars);
}

View file

@ -0,0 +1,129 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export interface PluginOptions {
path: string;
routeBasePath: string;
include: string[];
sidebarPath: string;
docLayoutComponent: string;
docItemComponent: string;
remarkPlugins: string[];
rehypePlugins: string[];
editUrl?: string;
}
export type SidebarItemDoc = {
type: string;
id: string;
};
export interface SidebarItemLink {
type: string;
href: string;
label: string;
}
export interface SidebarItemCategory {
type: string;
label: string;
items: SidebarItem[];
}
export interface SidebarItemCategoryRaw {
type: string;
label: string;
items: SidebarItemRaw[];
}
export type SidebarItem =
| SidebarItemDoc
| SidebarItemLink
| SidebarItemCategory;
export type SidebarItemRaw =
| string
| SidebarItemDoc
| SidebarItemLink
| SidebarItemCategoryRaw;
// Sidebar given by user that is not normalized yet. e.g: sidebars.json
export interface SidebarRaw {
[sidebarId: string]: {
[sidebarCategory: string]: SidebarItemRaw[];
};
}
export interface Sidebar {
[sidebarId: string]: SidebarItemCategory[];
}
export interface DocsSidebarItemCategory {
type: string;
label: string;
items: (SidebarItemLink | DocsSidebarItemCategory)[];
}
export interface DocsSidebar {
[sidebarId: string]: DocsSidebarItemCategory[];
}
export interface OrderMetadata {
previous?: string;
next?: string;
sidebar?: string;
}
export interface Order {
[id: string]: OrderMetadata;
}
export interface MetadataRaw extends OrderMetadata {
id: string;
title: string;
description: string;
source: string;
permalink: string;
sidebar_label?: string;
editUrl?: string;
[key: string]: any;
}
export interface Paginator {
title: string;
permalink: string;
}
export interface Metadata extends Omit<MetadataRaw, 'previous' | 'next'> {
previous?: Paginator;
next?: Paginator;
}
export interface DocsMetadata {
[id: string]: Metadata;
}
export interface SourceToPermalink {
[source: string]: string;
}
export interface PermalinkToSidebar {
[permalink: string]: string;
}
export interface LoadedContent {
docsMetadata: DocsMetadata;
docsDir: string;
docsSidebars: Sidebar;
sourceToPermalink: SourceToPermalink;
permalinkToSidebar: PermalinkToSidebar;
}
export type DocsBaseMetadata = Pick<
LoadedContent,
'docsSidebars' | 'permalinkToSidebar'
>;