mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-15 18:17:35 +02:00
feat(v2): docs last update timestamp and author (#1829)
* feat(v2): docs last update timestamp and author * misc(v2): changelog * misc(v2): better error messages
This commit is contained in:
parent
54e9e025d8
commit
4fe6ae3c24
12 changed files with 305 additions and 36 deletions
|
@ -7,12 +7,13 @@
|
||||||
- Docs sidebar can now be more than one level deep, theoretically up to infinity
|
- Docs sidebar can now be more than one level deep, theoretically up to infinity
|
||||||
- Collapsible docs sidebar!
|
- Collapsible docs sidebar!
|
||||||
- Make doc page title larger
|
- Make doc page title larger
|
||||||
- Add `editUrl` option (URL for editing) to docs plugin. If this field is set, there will be an "Edit this page" link for each doc page. Example: 'https://github.com/facebook/docusaurus/edit/master/docs'.
|
- Add `editUrl` option (URL for editing) to docs plugin. If this field is set, there will be an "Edit this page" link for each doc page. Example: 'https://github.com/facebook/docusaurus/edit/master/docs'
|
||||||
- More documentation ...
|
- Add `showLastUpdateTime` and `showLastUpdateAuthor` options to docs plugin to further achieve v1 parity of showing last update data for a particular doc
|
||||||
- Slight tweaks to the Blog components - blog title is larger now
|
- Slight tweaks to the Blog components - blog title is larger now
|
||||||
- Code Blocks
|
- Code Blocks
|
||||||
- Change default theme from Night Owl to Palenight
|
- Change default theme from Night Owl to Palenight
|
||||||
- Slight tweaks to playground/preview components
|
- Slight tweaks to playground/preview components
|
||||||
|
- More documentation...
|
||||||
|
|
||||||
## 2.0.0-alpha.25
|
## 2.0.0-alpha.25
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"globby": "^10.0.1",
|
"globby": "^10.0.1",
|
||||||
"import-fresh": "^3.1.0",
|
"import-fresh": "^3.1.0",
|
||||||
"loader-utils": "^1.2.3"
|
"loader-utils": "^1.2.3",
|
||||||
|
"shelljs": "^0.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@docusaurus/core": "^2.0.0",
|
"@docusaurus/core": "^2.0.0",
|
||||||
|
|
|
@ -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 fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import shell from 'shelljs';
|
||||||
|
|
||||||
|
import lastUpdate from '../lastUpdate';
|
||||||
|
|
||||||
|
describe('lastUpdate', () => {
|
||||||
|
test('existing test file in repository with Git timestamp', () => {
|
||||||
|
const existingFilePath = path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__/website/docs/hello.md',
|
||||||
|
);
|
||||||
|
const lastUpdateData = lastUpdate(existingFilePath);
|
||||||
|
expect(lastUpdateData).not.toBeNull();
|
||||||
|
|
||||||
|
const {author, timestamp} = lastUpdateData;
|
||||||
|
expect(author).not.toBeNull();
|
||||||
|
expect(typeof author).toBe('string');
|
||||||
|
|
||||||
|
expect(timestamp).not.toBeNull();
|
||||||
|
expect(typeof timestamp).toBe('number');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('non-existing file', () => {
|
||||||
|
const nonExistingFilePath = path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__',
|
||||||
|
'.nonExisting',
|
||||||
|
);
|
||||||
|
expect(lastUpdate(null)).toBeNull();
|
||||||
|
expect(lastUpdate(undefined)).toBeNull();
|
||||||
|
expect(lastUpdate(nonExistingFilePath)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('temporary created file that has no git timestamp', () => {
|
||||||
|
const tempFilePath = path.join(__dirname, '__fixtures__', '.temp');
|
||||||
|
fs.writeFileSync(tempFilePath, 'Lorem ipsum :)');
|
||||||
|
expect(lastUpdate(tempFilePath)).toBeNull();
|
||||||
|
fs.unlinkSync(tempFilePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test renaming and moving file', () => {
|
||||||
|
const mock = jest.spyOn(shell, 'exec');
|
||||||
|
mock
|
||||||
|
.mockImplementationOnce(() => ({
|
||||||
|
stdout:
|
||||||
|
'1539502055, Yangshun Tay\n' +
|
||||||
|
'\n' +
|
||||||
|
' create mode 100644 v1/lib/core/__tests__/__fixtures__/.temp2\n',
|
||||||
|
}))
|
||||||
|
.mockImplementationOnce(() => ({
|
||||||
|
stdout:
|
||||||
|
'1539502056, Joel Marcey\n' +
|
||||||
|
'\n' +
|
||||||
|
' rename v1/lib/core/__tests__/__fixtures__/{.temp2 => test/.temp3} (100%)\n' +
|
||||||
|
'1539502055, Yangshun Tay\n' +
|
||||||
|
'\n' +
|
||||||
|
' create mode 100644 v1/lib/core/__tests__/__fixtures__/.temp2\n',
|
||||||
|
}));
|
||||||
|
const tempFilePath2 = path.join(__dirname, '__fixtures__', '.temp2');
|
||||||
|
const tempFilePath3 = path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__',
|
||||||
|
'test',
|
||||||
|
'.temp3',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create new file.
|
||||||
|
const createData = lastUpdate(tempFilePath2);
|
||||||
|
expect(createData.timestamp).not.toBeNull();
|
||||||
|
|
||||||
|
// Rename/move the file.
|
||||||
|
const updateData = lastUpdate(tempFilePath3);
|
||||||
|
expect(updateData.timestamp).not.toBeNull();
|
||||||
|
// Should only consider file content change.
|
||||||
|
expect(updateData.timestamp).toEqual(createData.timestamp);
|
||||||
|
|
||||||
|
mock.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
|
@ -23,9 +23,24 @@ describe('processMetadata', () => {
|
||||||
const sourceB = path.join('hello.md');
|
const sourceB = path.join('hello.md');
|
||||||
|
|
||||||
const [dataA, dataB] = await Promise.all([
|
const [dataA, dataB] = await Promise.all([
|
||||||
processMetadata(sourceA, docsDir, {}, siteConfig, pluginPath, siteDir),
|
processMetadata({
|
||||||
processMetadata(sourceB, docsDir, {}, siteConfig, pluginPath, siteDir),
|
source: sourceA,
|
||||||
|
docsDir,
|
||||||
|
order: {},
|
||||||
|
siteConfig,
|
||||||
|
docsBasePath: pluginPath,
|
||||||
|
siteDir,
|
||||||
|
}),
|
||||||
|
processMetadata({
|
||||||
|
source: sourceB,
|
||||||
|
docsDir,
|
||||||
|
order: {},
|
||||||
|
siteConfig,
|
||||||
|
docsBasePath: pluginPath,
|
||||||
|
siteDir,
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(dataA).toEqual({
|
expect(dataA).toEqual({
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
permalink: '/docs/foo/bar',
|
permalink: '/docs/foo/bar',
|
||||||
|
@ -44,14 +59,15 @@ describe('processMetadata', () => {
|
||||||
|
|
||||||
test('docs with custom permalink', async () => {
|
test('docs with custom permalink', async () => {
|
||||||
const source = path.join('permalink.md');
|
const source = path.join('permalink.md');
|
||||||
const data = await processMetadata(
|
const data = await processMetadata({
|
||||||
source,
|
source,
|
||||||
docsDir,
|
docsDir,
|
||||||
{},
|
order: {},
|
||||||
siteConfig,
|
siteConfig,
|
||||||
pluginPath,
|
docsBasePath: pluginPath,
|
||||||
siteDir,
|
siteDir,
|
||||||
);
|
});
|
||||||
|
|
||||||
expect(data).toEqual({
|
expect(data).toEqual({
|
||||||
id: 'permalink',
|
id: 'permalink',
|
||||||
permalink: '/docs/endiliey/permalink',
|
permalink: '/docs/endiliey/permalink',
|
||||||
|
@ -65,15 +81,16 @@ describe('processMetadata', () => {
|
||||||
const editUrl =
|
const editUrl =
|
||||||
'https://github.com/facebook/docusaurus/edit/master/website/docs/';
|
'https://github.com/facebook/docusaurus/edit/master/website/docs/';
|
||||||
const source = path.join('foo', 'baz.md');
|
const source = path.join('foo', 'baz.md');
|
||||||
const data = await processMetadata(
|
const data = await processMetadata({
|
||||||
source,
|
source,
|
||||||
docsDir,
|
docsDir,
|
||||||
{},
|
order: {},
|
||||||
siteConfig,
|
siteConfig,
|
||||||
pluginPath,
|
docsBasePath: pluginPath,
|
||||||
siteDir,
|
siteDir,
|
||||||
editUrl,
|
editUrl,
|
||||||
);
|
});
|
||||||
|
|
||||||
expect(data).toEqual({
|
expect(data).toEqual({
|
||||||
id: 'foo/baz',
|
id: 'foo/baz',
|
||||||
permalink: '/docs/foo/baz',
|
permalink: '/docs/foo/baz',
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {LoadContext, Plugin, DocusaurusConfig} from '@docusaurus/types';
|
||||||
import createOrder from './order';
|
import createOrder from './order';
|
||||||
import loadSidebars from './sidebars';
|
import loadSidebars from './sidebars';
|
||||||
import processMetadata from './metadata';
|
import processMetadata from './metadata';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PluginOptions,
|
PluginOptions,
|
||||||
Sidebar,
|
Sidebar,
|
||||||
|
@ -41,6 +42,8 @@ const DEFAULT_OPTIONS: PluginOptions = {
|
||||||
docItemComponent: '@theme/DocItem',
|
docItemComponent: '@theme/DocItem',
|
||||||
remarkPlugins: [],
|
remarkPlugins: [],
|
||||||
rehypePlugins: [],
|
rehypePlugins: [],
|
||||||
|
showLastUpdateTime: false,
|
||||||
|
showLastUpdateAuthor: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function pluginContentDocs(
|
export default function pluginContentDocs(
|
||||||
|
@ -62,7 +65,14 @@ export default function pluginContentDocs(
|
||||||
|
|
||||||
// Fetches blog contents and returns metadata for the contents.
|
// Fetches blog contents and returns metadata for the contents.
|
||||||
async loadContent() {
|
async loadContent() {
|
||||||
const {include, routeBasePath, sidebarPath, editUrl} = options;
|
const {
|
||||||
|
include,
|
||||||
|
routeBasePath,
|
||||||
|
sidebarPath,
|
||||||
|
editUrl,
|
||||||
|
showLastUpdateAuthor,
|
||||||
|
showLastUpdateTime,
|
||||||
|
} = options;
|
||||||
const {siteConfig, siteDir} = context;
|
const {siteConfig, siteDir} = context;
|
||||||
const docsDir = contentPath;
|
const docsDir = contentPath;
|
||||||
|
|
||||||
|
@ -86,15 +96,17 @@ export default function pluginContentDocs(
|
||||||
});
|
});
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
docsFiles.map(async source => {
|
docsFiles.map(async source => {
|
||||||
const metadata: MetadataRaw = await processMetadata(
|
const metadata: MetadataRaw = await processMetadata({
|
||||||
source,
|
source,
|
||||||
docsDir,
|
docsDir,
|
||||||
order,
|
order,
|
||||||
siteConfig,
|
siteConfig,
|
||||||
routeBasePath,
|
docsBasePath: routeBasePath,
|
||||||
siteDir,
|
siteDir,
|
||||||
editUrl,
|
editUrl,
|
||||||
);
|
showLastUpdateAuthor,
|
||||||
|
showLastUpdateTime,
|
||||||
|
});
|
||||||
docsMetadataRaw[metadata.id] = metadata;
|
docsMetadataRaw[metadata.id] = metadata;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
73
packages/docusaurus-plugin-content-docs/src/lastUpdate.ts
Normal file
73
packages/docusaurus-plugin-content-docs/src/lastUpdate.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/**
|
||||||
|
* 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 shell from 'shelljs';
|
||||||
|
|
||||||
|
type FileLastUpdateData = {timestamp?: number; author?: string};
|
||||||
|
|
||||||
|
const GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX = /^(\d+), (.+)$/;
|
||||||
|
|
||||||
|
export default function getFileLastUpdate(
|
||||||
|
filePath: string,
|
||||||
|
): FileLastUpdateData | null {
|
||||||
|
function isTimestampAndAuthor(str: string): boolean {
|
||||||
|
return GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX.test(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimestampAndAuthor(str: string): FileLastUpdateData | null {
|
||||||
|
if (!str) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const temp = str.match(GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX);
|
||||||
|
return !temp || temp.length < 3
|
||||||
|
? null
|
||||||
|
: {timestamp: +temp[1], author: temp[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap in try/catch in case the shell commands fail (e.g. project doesn't use Git, etc).
|
||||||
|
try {
|
||||||
|
if (!shell.which('git')) {
|
||||||
|
console.log('Sorry, the docs plugin last update options require Git.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// To differentiate between content change and file renaming/moving, use --summary
|
||||||
|
// To follow the file history until before it is moved (when we create new version), use
|
||||||
|
// --follow.
|
||||||
|
const silentState = shell.config.silent; // Save old silent state.
|
||||||
|
shell.config.silent = true;
|
||||||
|
const result = shell
|
||||||
|
.exec(`git log --follow --summary --format="%ct, %an" ${filePath}`)
|
||||||
|
.stdout.trim();
|
||||||
|
shell.config.silent = silentState;
|
||||||
|
|
||||||
|
// Format the log results to be
|
||||||
|
// ['1234567890, Yangshun Tay', 'rename ...', '1234567880,
|
||||||
|
// 'Joel Marcey', 'move ...', '1234567870', '1234567860']
|
||||||
|
const records = result
|
||||||
|
.replace(/\n\s*\n/g, '\n')
|
||||||
|
.split('\n')
|
||||||
|
.filter(String);
|
||||||
|
const lastContentModifierCommit = records.find((item, index, arr) => {
|
||||||
|
const currentItemIsTimestampAndAuthor = isTimestampAndAuthor(item);
|
||||||
|
const isLastTwoItem = index + 2 >= arr.length;
|
||||||
|
const nextItemIsTimestampAndAuthor = isTimestampAndAuthor(arr[index + 1]);
|
||||||
|
return (
|
||||||
|
currentItemIsTimestampAndAuthor &&
|
||||||
|
(isLastTwoItem || nextItemIsTimestampAndAuthor)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return lastContentModifierCommit
|
||||||
|
? getTimestampAndAuthor(lastContentModifierCommit)
|
||||||
|
: null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -8,21 +8,37 @@
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {parse, normalizeUrl} from '@docusaurus/utils';
|
import {parse, normalizeUrl} from '@docusaurus/utils';
|
||||||
import {Order, MetadataRaw} from './types';
|
|
||||||
import {DocusaurusConfig} from '@docusaurus/types';
|
import {DocusaurusConfig} from '@docusaurus/types';
|
||||||
|
|
||||||
export default async function processMetadata(
|
import lastUpdate from './lastUpdate';
|
||||||
source: string,
|
import {Order, MetadataRaw} from './types';
|
||||||
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');
|
type Args = {
|
||||||
|
source: string;
|
||||||
|
docsDir: string;
|
||||||
|
order: Order;
|
||||||
|
siteConfig: Partial<DocusaurusConfig>;
|
||||||
|
docsBasePath: string;
|
||||||
|
siteDir: string;
|
||||||
|
editUrl?: string;
|
||||||
|
showLastUpdateAuthor?: boolean;
|
||||||
|
showLastUpdateTime?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function processMetadata({
|
||||||
|
source,
|
||||||
|
docsDir,
|
||||||
|
order,
|
||||||
|
siteConfig,
|
||||||
|
docsBasePath,
|
||||||
|
siteDir,
|
||||||
|
editUrl,
|
||||||
|
showLastUpdateAuthor,
|
||||||
|
showLastUpdateTime,
|
||||||
|
}: Args): Promise<MetadataRaw> {
|
||||||
|
const filePath = path.join(docsDir, source);
|
||||||
|
|
||||||
|
const fileString = await fs.readFile(filePath, 'utf-8');
|
||||||
const {frontMatter: metadata = {}, excerpt} = parse(fileString);
|
const {frontMatter: metadata = {}, excerpt} = parse(fileString);
|
||||||
|
|
||||||
// Default id is the file name.
|
// Default id is the file name.
|
||||||
|
@ -52,7 +68,7 @@ export default async function processMetadata(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it.
|
// Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it.
|
||||||
const aliasedPath = `@site/${path.relative(siteDir, filepath)}`;
|
const aliasedPath = `@site/${path.relative(siteDir, filePath)}`;
|
||||||
metadata.source = aliasedPath;
|
metadata.source = aliasedPath;
|
||||||
|
|
||||||
// Build the permalink.
|
// Build the permalink.
|
||||||
|
@ -87,5 +103,20 @@ export default async function processMetadata(
|
||||||
metadata.editUrl = normalizeUrl([editUrl, source]);
|
metadata.editUrl = normalizeUrl([editUrl, source]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showLastUpdateAuthor || showLastUpdateTime) {
|
||||||
|
const fileLastUpdateData = lastUpdate(filePath);
|
||||||
|
|
||||||
|
if (fileLastUpdateData) {
|
||||||
|
const {author, timestamp} = fileLastUpdateData;
|
||||||
|
if (showLastUpdateAuthor && author) {
|
||||||
|
metadata.lastUpdatedBy = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showLastUpdateTime && timestamp) {
|
||||||
|
metadata.lastUpdatedAt = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return metadata as MetadataRaw;
|
return metadata as MetadataRaw;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ export interface PluginOptions {
|
||||||
remarkPlugins: string[];
|
remarkPlugins: string[];
|
||||||
rehypePlugins: string[];
|
rehypePlugins: string[];
|
||||||
editUrl?: string;
|
editUrl?: string;
|
||||||
|
showLastUpdateTime?: boolean;
|
||||||
|
showLastUpdateAuthor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SidebarItemDoc = {
|
export type SidebarItemDoc = {
|
||||||
|
@ -90,6 +92,8 @@ export interface MetadataRaw extends OrderMetadata {
|
||||||
permalink: string;
|
permalink: string;
|
||||||
sidebar_label?: string;
|
sidebar_label?: string;
|
||||||
editUrl?: string;
|
editUrl?: string;
|
||||||
|
lastUpdatedAt?: number;
|
||||||
|
lastUpdatedBy?: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,15 @@ function DocItem(props) {
|
||||||
const {siteConfig = {}} = useDocusaurusContext();
|
const {siteConfig = {}} = useDocusaurusContext();
|
||||||
const {url: siteUrl} = siteConfig;
|
const {url: siteUrl} = siteConfig;
|
||||||
const {metadata, content: DocContent} = props;
|
const {metadata, content: DocContent} = props;
|
||||||
const {description, title, permalink, image: metaImage, editUrl} = metadata;
|
const {
|
||||||
|
description,
|
||||||
|
title,
|
||||||
|
permalink,
|
||||||
|
image: metaImage,
|
||||||
|
editUrl,
|
||||||
|
lastUpdatedAt,
|
||||||
|
lastUpdatedBy,
|
||||||
|
} = metadata;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -74,7 +82,7 @@ function DocItem(props) {
|
||||||
<DocContent />
|
<DocContent />
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
{editUrl && (
|
{(editUrl || lastUpdatedAt || lastUpdatedBy) && (
|
||||||
<div className="margin-vert--xl">
|
<div className="margin-vert--xl">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
|
@ -87,6 +95,29 @@ function DocItem(props) {
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{(lastUpdatedAt || lastUpdatedBy) && (
|
||||||
|
<div className="col text--right">
|
||||||
|
<em>
|
||||||
|
<small>
|
||||||
|
Last updated{' '}
|
||||||
|
{lastUpdatedAt && (
|
||||||
|
<>
|
||||||
|
on{' '}
|
||||||
|
{new Date(
|
||||||
|
lastUpdatedAt * 1000,
|
||||||
|
).toLocaleDateString()}
|
||||||
|
{lastUpdatedBy && ' '}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{lastUpdatedBy && (
|
||||||
|
<>
|
||||||
|
by <strong>{lastUpdatedBy}</strong>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</small>
|
||||||
|
</em>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -169,6 +169,14 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
remarkPlugins: [],
|
remarkPlugins: [],
|
||||||
rehypePlugins: [],
|
rehypePlugins: [],
|
||||||
|
/**
|
||||||
|
* Whether to display the author who last updated the doc.
|
||||||
|
* /
|
||||||
|
showLastUpdateAuthor: false,
|
||||||
|
/**
|
||||||
|
* Whether to display the last date the doc was updated.
|
||||||
|
* /
|
||||||
|
showLastUpdateTime: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
|
@ -265,7 +265,7 @@ module.exports = {
|
||||||
|
|
||||||
Deprecated. Create a `CNAME` file in your `static` folder instead. Files in the `static` folder will be copied into the root of the `build` folder during execution of the build command.
|
Deprecated. Create a `CNAME` file in your `static` folder instead. Files in the `static` folder will be copied into the root of the `build` folder during execution of the build command.
|
||||||
|
|
||||||
#### `customDocsPath`, `docsUrl`, `editUrl`
|
#### `customDocsPath`, `docsUrl`, `editUrl`, `enableUpdateBy`, `enableUpdateTime`
|
||||||
|
|
||||||
Deprecated. Pass it as an option to `@docusaurus/preset-classic` docs instead:
|
Deprecated. Pass it as an option to `@docusaurus/preset-classic` docs instead:
|
||||||
|
|
||||||
|
@ -286,6 +286,10 @@ module.exports = {
|
||||||
// Remark and Rehype plugins passed to MDX. Replaces `markdownOptions` and `markdownPlugins`.
|
// Remark and Rehype plugins passed to MDX. Replaces `markdownOptions` and `markdownPlugins`.
|
||||||
remarkPlugins: [],
|
remarkPlugins: [],
|
||||||
rehypePlugins: [],
|
rehypePlugins: [],
|
||||||
|
// Equivalent to `enableUpdateBy`.
|
||||||
|
showLastUpdateAuthor: true,
|
||||||
|
// Equivalent to `enableUpdateTime`.
|
||||||
|
showLastUpdateTime: true,
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
},
|
},
|
||||||
|
@ -322,8 +326,6 @@ module.exports = {
|
||||||
|
|
||||||
### Deprecated fields that may be implemented using a plugin
|
### Deprecated fields that may be implemented using a plugin
|
||||||
|
|
||||||
- `enableUpdateBy`
|
|
||||||
- `enableUpdateTime`
|
|
||||||
- `scripts`
|
- `scripts`
|
||||||
- `stylesheets`
|
- `stylesheets`
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ module.exports = {
|
||||||
sidebarPath: require.resolve('./sidebars.js'),
|
sidebarPath: require.resolve('./sidebars.js'),
|
||||||
editUrl:
|
editUrl:
|
||||||
'https://github.com/facebook/docusaurus/edit/master/website/docs/',
|
'https://github.com/facebook/docusaurus/edit/master/website/docs/',
|
||||||
|
showLastUpdateAuthor: true,
|
||||||
|
showLastUpdateTime: true,
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
path: '../website-1.x/blog',
|
path: '../website-1.x/blog',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue