feat(content-docs): last_update front matter (#7461)

Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
dpang314 2022-06-01 10:27:58 -04:00 committed by GitHub
parent a469ae3d63
commit 4f26a1911a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 499 additions and 36 deletions

View file

@ -0,0 +1,8 @@
---
title: Custom Last Update
last_update:
author: Custom Author
date: 1/1/2000
---
Custom last update

View file

@ -0,0 +1,7 @@
---
title: Last Update Author Only
last_update:
author: Custom Author
---
Only custom author, so it will still use the date from Git

View file

@ -0,0 +1,7 @@
---
title: Last Update Date Only
last_update:
date: 1/1/2000
---
Only custom date, so it will still use the author from Git

View file

@ -3,13 +3,24 @@
exports[`simple site custom pagination 1`] = ` exports[`simple site custom pagination 1`] = `
{ {
"pagination": [ "pagination": [
{
"id": "customLastUpdate",
"next": {
"permalink": "/docs/doc with space",
"title": "Hoo hoo, if this path tricks you...",
},
"prev": undefined,
},
{ {
"id": "doc with space", "id": "doc with space",
"next": { "next": {
"permalink": "/docs/doc-draft", "permalink": "/docs/doc-draft",
"title": "doc-draft", "title": "doc-draft",
}, },
"prev": undefined, "prev": {
"permalink": "/docs/customLastUpdate",
"title": "Custom Last Update",
},
}, },
{ {
"id": "doc-draft", "id": "doc-draft",
@ -63,14 +74,36 @@ exports[`simple site custom pagination 1`] = `
{ {
"id": "ipsum", "id": "ipsum",
"next": { "next": {
"permalink": "/docs/lorem", "permalink": "/docs/lastUpdateAuthorOnly",
"title": "lorem", "title": "Last Update Author Only",
}, },
"prev": { "prev": {
"permalink": "/docs/", "permalink": "/docs/",
"title": "Hello sidebar_label", "title": "Hello sidebar_label",
}, },
}, },
{
"id": "lastUpdateAuthorOnly",
"next": {
"permalink": "/docs/lastUpdateDateOnly",
"title": "Last Update Date Only",
},
"prev": {
"permalink": "/docs/ipsum",
"title": "ipsum",
},
},
{
"id": "lastUpdateDateOnly",
"next": {
"permalink": "/docs/lorem",
"title": "lorem",
},
"prev": {
"permalink": "/docs/lastUpdateAuthorOnly",
"title": "Last Update Author Only",
},
},
{ {
"id": "lorem", "id": "lorem",
"next": { "next": {
@ -78,8 +111,8 @@ exports[`simple site custom pagination 1`] = `
"title": "rootAbsoluteSlug", "title": "rootAbsoluteSlug",
}, },
"prev": { "prev": {
"permalink": "/docs/ipsum", "permalink": "/docs/lastUpdateDateOnly",
"title": "ipsum", "title": "Last Update Date Only",
}, },
}, },
{ {
@ -170,6 +203,10 @@ exports[`simple site custom pagination 1`] = `
], ],
"sidebars": { "sidebars": {
"defaultSidebar": [ "defaultSidebar": [
{
"id": "customLastUpdate",
"type": "doc",
},
{ {
"id": "doc with space", "id": "doc with space",
"type": "doc", "type": "doc",
@ -208,6 +245,14 @@ exports[`simple site custom pagination 1`] = `
"id": "ipsum", "id": "ipsum",
"type": "doc", "type": "doc",
}, },
{
"id": "lastUpdateAuthorOnly",
"type": "doc",
},
{
"id": "lastUpdateDateOnly",
"type": "doc",
},
{ {
"id": "lorem", "id": "lorem",
"type": "doc", "type": "doc",

View file

@ -22,6 +22,7 @@ These sidebar document ids do not exist:
- nonExistent - nonExistent
Available document ids are: Available document ids are:
- customLastUpdate
- doc with space - doc with space
- doc-draft - doc-draft
- foo/bar - foo/bar
@ -29,6 +30,8 @@ Available document ids are:
- headingAsTitle - headingAsTitle
- hello - hello
- ipsum - ipsum
- lastUpdateAuthorOnly
- lastUpdateDateOnly
- lorem - lorem
- rootAbsoluteSlug - rootAbsoluteSlug
- rootRelativeSlug - rootRelativeSlug
@ -267,6 +270,11 @@ exports[`simple website content 5`] = `
"versions": [ "versions": [
{ {
"docs": [ "docs": [
{
"id": "customLastUpdate",
"path": "/docs/customLastUpdate",
"sidebar": undefined,
},
{ {
"id": "doc with space", "id": "doc with space",
"path": "/docs/doc with space", "path": "/docs/doc with space",
@ -302,6 +310,16 @@ exports[`simple website content 5`] = `
"path": "/docs/ipsum", "path": "/docs/ipsum",
"sidebar": undefined, "sidebar": undefined,
}, },
{
"id": "lastUpdateAuthorOnly",
"path": "/docs/lastUpdateAuthorOnly",
"sidebar": undefined,
},
{
"id": "lastUpdateDateOnly",
"path": "/docs/lastUpdateDateOnly",
"sidebar": undefined,
},
{ {
"id": "lorem", "id": "lorem",
"path": "/docs/lorem", "path": "/docs/lorem",
@ -390,6 +408,26 @@ exports[`simple website content: data 1`] = `
"permalink": "/docs/rootAbsoluteSlug" "permalink": "/docs/rootAbsoluteSlug"
} }
} }
}",
"site-docs-custom-last-update-md-b8d.json": "{
"unversionedId": "customLastUpdate",
"id": "customLastUpdate",
"title": "Custom Last Update",
"description": "Custom last update",
"source": "@site/docs/customLastUpdate.md",
"sourceDirName": ".",
"slug": "/customLastUpdate",
"permalink": "/docs/customLastUpdate",
"draft": false,
"tags": [],
"version": "current",
"frontMatter": {
"title": "Custom Last Update",
"last_update": {
"author": "Custom Author",
"date": "1/1/2000"
}
}
}", }",
"site-docs-doc-draft-md-584.json": "{ "site-docs-doc-draft-md-584.json": "{
"unversionedId": "doc-draft", "unversionedId": "doc-draft",
@ -563,6 +601,44 @@ exports[`simple website content: data 1`] = `
"frontMatter": { "frontMatter": {
"custom_edit_url": null "custom_edit_url": null
} }
}",
"site-docs-last-update-author-only-md-352.json": "{
"unversionedId": "lastUpdateAuthorOnly",
"id": "lastUpdateAuthorOnly",
"title": "Last Update Author Only",
"description": "Only custom author, so it will still use the date from Git",
"source": "@site/docs/lastUpdateAuthorOnly.md",
"sourceDirName": ".",
"slug": "/lastUpdateAuthorOnly",
"permalink": "/docs/lastUpdateAuthorOnly",
"draft": false,
"tags": [],
"version": "current",
"frontMatter": {
"title": "Last Update Author Only",
"last_update": {
"author": "Custom Author"
}
}
}",
"site-docs-last-update-date-only-md-987.json": "{
"unversionedId": "lastUpdateDateOnly",
"id": "lastUpdateDateOnly",
"title": "Last Update Date Only",
"description": "Only custom date, so it will still use the author from Git",
"source": "@site/docs/lastUpdateDateOnly.md",
"sourceDirName": ".",
"slug": "/lastUpdateDateOnly",
"permalink": "/docs/lastUpdateDateOnly",
"draft": false,
"tags": [],
"version": "current",
"frontMatter": {
"title": "Last Update Date Only",
"last_update": {
"date": "1/1/2000"
}
}
}", }",
"site-docs-lorem-md-b27.json": "{ "site-docs-lorem-md-b27.json": "{
"unversionedId": "lorem", "unversionedId": "lorem",
@ -924,6 +1000,11 @@ exports[`simple website content: data 1`] = `
] ]
}, },
"docs": { "docs": {
"customLastUpdate": {
"id": "customLastUpdate",
"title": "Custom Last Update",
"description": "Custom last update"
},
"doc with space": { "doc with space": {
"id": "doc with space", "id": "doc with space",
"title": "Hoo hoo, if this path tricks you...", "title": "Hoo hoo, if this path tricks you...",
@ -963,6 +1044,16 @@ exports[`simple website content: data 1`] = `
"title": "ipsum", "title": "ipsum",
"description": "Lorem ipsum." "description": "Lorem ipsum."
}, },
"lastUpdateAuthorOnly": {
"id": "lastUpdateAuthorOnly",
"title": "Last Update Author Only",
"description": "Only custom author, so it will still use the date from Git"
},
"lastUpdateDateOnly": {
"id": "lastUpdateDateOnly",
"title": "Last Update Date Only",
"description": "Only custom date, so it will still use the author from Git"
},
"lorem": { "lorem": {
"id": "lorem", "id": "lorem",
"title": "lorem", "title": "lorem",
@ -1026,6 +1117,11 @@ exports[`simple website content: global data 1`] = `
"versions": [ "versions": [
{ {
"docs": [ "docs": [
{
"id": "customLastUpdate",
"path": "/docs/customLastUpdate",
"sidebar": undefined,
},
{ {
"id": "doc with space", "id": "doc with space",
"path": "/docs/doc with space", "path": "/docs/doc with space",
@ -1061,6 +1157,16 @@ exports[`simple website content: global data 1`] = `
"path": "/docs/ipsum", "path": "/docs/ipsum",
"sidebar": undefined, "sidebar": undefined,
}, },
{
"id": "lastUpdateAuthorOnly",
"path": "/docs/lastUpdateAuthorOnly",
"sidebar": undefined,
},
{
"id": "lastUpdateDateOnly",
"path": "/docs/lastUpdateDateOnly",
"sidebar": undefined,
},
{ {
"id": "lorem", "id": "lorem",
"path": "/docs/lorem", "path": "/docs/lorem",
@ -1202,6 +1308,14 @@ exports[`simple website content: route config 1`] = `
"path": "/docs/category/slugs", "path": "/docs/category/slugs",
"sidebar": "docs", "sidebar": "docs",
}, },
{
"component": "@theme/DocItem",
"exact": true,
"modules": {
"content": "@site/docs/customLastUpdate.md",
},
"path": "/docs/customLastUpdate",
},
{ {
"component": "@theme/DocItem", "component": "@theme/DocItem",
"exact": true, "exact": true,
@ -1262,6 +1376,22 @@ exports[`simple website content: route config 1`] = `
}, },
"path": "/docs/ipsum", "path": "/docs/ipsum",
}, },
{
"component": "@theme/DocItem",
"exact": true,
"modules": {
"content": "@site/docs/lastUpdateAuthorOnly.md",
},
"path": "/docs/lastUpdateAuthorOnly",
},
{
"component": "@theme/DocItem",
"exact": true,
"modules": {
"content": "@site/docs/lastUpdateDateOnly.md",
},
"path": "/docs/lastUpdateDateOnly",
},
{ {
"component": "@theme/DocItem", "component": "@theme/DocItem",
"exact": true, "exact": true,

View file

@ -57,7 +57,6 @@ ${markdown}
return { return {
source, source,
content, content,
lastUpdate: {},
contentPath: 'docs', contentPath: 'docs',
filePath: source, filePath: source,
}; };
@ -79,7 +78,7 @@ function createTestUtils({
env = 'production', env = 'production',
}: TestUtilsArg) { }: TestUtilsArg) {
async function readDoc(docFileSource: string) { async function readDoc(docFileSource: string) {
return readDocFile(versionMetadata, docFileSource, options); return readDocFile(versionMetadata, docFileSource);
} }
async function processDocFile(docFileArg: DocFile | string) { async function processDocFile(docFileArg: DocFile | string) {
const docFile: DocFile = const docFile: DocFile =
@ -119,7 +118,7 @@ function createTestUtils({
async function testSlug(docFileSource: string, expectedPermalink: string) { async function testSlug(docFileSource: string, expectedPermalink: string) {
const docFile = await readDoc(docFileSource); const docFile = await readDoc(docFileSource);
const metadata = processDocMetadata({ const metadata = await processDocMetadata({
docFile, docFile,
versionMetadata, versionMetadata,
context, context,
@ -137,14 +136,16 @@ function createTestUtils({
}[]; }[];
sidebars: Sidebars; sidebars: Sidebars;
}> { }> {
const rawDocs = docFiles.map((docFile) => const rawDocs = await Promise.all(
processDocMetadata({ docFiles.map(async (docFile) =>
docFile, processDocMetadata({
versionMetadata, docFile,
context, versionMetadata,
options, context,
env: 'production', options,
}), env: 'production',
}),
),
); );
const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, { const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, {
sidebarItemsGenerator: ({defaultSidebarItemsGenerator, ...args}) => sidebarItemsGenerator: ({defaultSidebarItemsGenerator, ...args}) =>
@ -230,6 +231,9 @@ describe('simple site', () => {
'headingAsTitle.md', 'headingAsTitle.md',
'doc with space.md', 'doc with space.md',
'doc-draft.md', 'doc-draft.md',
'customLastUpdate.md',
'lastUpdateAuthorOnly.md',
'lastUpdateDateOnly.md',
'foo/bar.md', 'foo/bar.md',
'foo/baz.md', 'foo/baz.md',
'slugs/absoluteSlug.md', 'slugs/absoluteSlug.md',
@ -481,6 +485,164 @@ describe('simple site', () => {
}); });
}); });
it('docs with last_update front matter', async () => {
const {siteDir, context, options, currentVersion, createTestUtilsPartial} =
await loadSite({
options: {
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
});
const testUtilsLocal = createTestUtilsPartial({
siteDir,
context,
options,
versionMetadata: currentVersion,
});
await testUtilsLocal.testMeta('customLastUpdate.md', {
version: 'current',
id: 'customLastUpdate',
unversionedId: 'customLastUpdate',
sourceDirName: '.',
permalink: '/docs/customLastUpdate',
slug: '/customLastUpdate',
title: 'Custom Last Update',
description: 'Custom last update',
frontMatter: {
last_update: {
author: 'Custom Author',
date: '1/1/2000',
},
title: 'Custom Last Update',
},
lastUpdatedAt: new Date('1/1/2000').getTime() / 1000,
formattedLastUpdatedAt: '1/1/2000',
lastUpdatedBy: 'Custom Author',
sidebarPosition: undefined,
tags: [],
});
});
it('docs with only last_update author front matter', async () => {
const {siteDir, context, options, currentVersion, createTestUtilsPartial} =
await loadSite({
options: {
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
});
const testUtilsLocal = createTestUtilsPartial({
siteDir,
context,
options,
versionMetadata: currentVersion,
});
await testUtilsLocal.testMeta('lastUpdateAuthorOnly.md', {
version: 'current',
id: 'lastUpdateAuthorOnly',
unversionedId: 'lastUpdateAuthorOnly',
sourceDirName: '.',
permalink: '/docs/lastUpdateAuthorOnly',
slug: '/lastUpdateAuthorOnly',
title: 'Last Update Author Only',
description: 'Only custom author, so it will still use the date from Git',
frontMatter: {
last_update: {
author: 'Custom Author',
},
title: 'Last Update Author Only',
},
lastUpdatedAt: 1539502055,
formattedLastUpdatedAt: '10/14/2018',
lastUpdatedBy: 'Custom Author',
sidebarPosition: undefined,
tags: [],
});
});
it('docs with only last_update date front matter', async () => {
const {siteDir, context, options, currentVersion, createTestUtilsPartial} =
await loadSite({
options: {
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
});
const testUtilsLocal = createTestUtilsPartial({
siteDir,
context,
options,
versionMetadata: currentVersion,
});
await testUtilsLocal.testMeta('lastUpdateDateOnly.md', {
version: 'current',
id: 'lastUpdateDateOnly',
unversionedId: 'lastUpdateDateOnly',
sourceDirName: '.',
permalink: '/docs/lastUpdateDateOnly',
slug: '/lastUpdateDateOnly',
title: 'Last Update Date Only',
description: 'Only custom date, so it will still use the author from Git',
frontMatter: {
last_update: {
date: '1/1/2000',
},
title: 'Last Update Date Only',
},
lastUpdatedAt: new Date('1/1/2000').getTime() / 1000,
formattedLastUpdatedAt: '1/1/2000',
lastUpdatedBy: 'Author',
sidebarPosition: undefined,
tags: [],
});
});
it('docs with last_update front matter disabled', async () => {
const {siteDir, context, options, currentVersion, createTestUtilsPartial} =
await loadSite({
options: {
showLastUpdateAuthor: false,
showLastUpdateTime: false,
},
});
const testUtilsLocal = createTestUtilsPartial({
siteDir,
context,
options,
versionMetadata: currentVersion,
});
await testUtilsLocal.testMeta('customLastUpdate.md', {
version: 'current',
id: 'customLastUpdate',
unversionedId: 'customLastUpdate',
sourceDirName: '.',
permalink: '/docs/customLastUpdate',
slug: '/customLastUpdate',
title: 'Custom Last Update',
description: 'Custom last update',
frontMatter: {
last_update: {
author: 'Custom Author',
date: '1/1/2000',
},
title: 'Custom Last Update',
},
lastUpdatedAt: undefined,
formattedLastUpdatedAt: undefined,
lastUpdatedBy: undefined,
sidebarPosition: undefined,
tags: [],
});
});
it('docs with slugs', async () => { it('docs with slugs', async () => {
const {defaultTestUtils} = await loadSite(); const {defaultTestUtils} = await loadSite();

View file

@ -396,3 +396,52 @@ describe('validateDocFrontMatter draft', () => {
], ],
}); });
}); });
describe('validateDocFrontMatter last_update', () => {
testField({
prefix: 'last_update',
validFrontMatters: [
{last_update: undefined},
{last_update: {author: 'test author', date: undefined}},
{last_update: {author: undefined, date: '1/1/2000'}},
{last_update: {author: undefined, date: new Date('1/1/2000')}},
{last_update: {author: 'test author', date: '1/1/2000'}},
{last_update: {author: 'test author', date: '1995-12-17T03:24:00'}},
{last_update: {author: undefined, date: 'December 17, 1995 03:24:00'}},
],
invalidFrontMatters: [
[
{last_update: null},
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
],
[
{last_update: {}},
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
],
[
{last_update: ''},
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
],
[
{last_update: {invalid: 'key'}},
'does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).',
],
[
{last_update: {author: 'test author', date: 'I am not a date :('}},
'must be a valid date',
],
[
{last_update: {author: 'test author', date: '2011-10-45'}},
'must be a valid date',
],
[
{last_update: {author: 'test author', date: '2011-0-10'}},
'must be a valid date',
],
[
{last_update: {author: 'test author', date: ''}},
'must be a valid date',
],
],
});
});

View file

@ -37,6 +37,7 @@ import type {
VersionMetadata, VersionMetadata,
DocFrontMatter, DocFrontMatter,
LoadedVersion, LoadedVersion,
FileChange,
} from '@docusaurus/plugin-content-docs'; } from '@docusaurus/plugin-content-docs';
import type {LoadContext} from '@docusaurus/types'; import type {LoadContext} from '@docusaurus/types';
import type {SidebarsUtils} from './sidebars/utils'; import type {SidebarsUtils} from './sidebars/utils';
@ -50,9 +51,21 @@ type LastUpdateOptions = Pick<
async function readLastUpdateData( async function readLastUpdateData(
filePath: string, filePath: string,
options: LastUpdateOptions, options: LastUpdateOptions,
lastUpdateFrontMatter: FileChange | undefined,
): Promise<LastUpdateData> { ): Promise<LastUpdateData> {
const {showLastUpdateAuthor, showLastUpdateTime} = options; const {showLastUpdateAuthor, showLastUpdateTime} = options;
if (showLastUpdateAuthor || showLastUpdateTime) { if (showLastUpdateAuthor || showLastUpdateTime) {
const frontMatterTimestamp = lastUpdateFrontMatter?.date
? new Date(lastUpdateFrontMatter.date).getTime() / 1000
: undefined;
if (lastUpdateFrontMatter?.author && lastUpdateFrontMatter.date) {
return {
lastUpdatedAt: frontMatterTimestamp,
lastUpdatedBy: lastUpdateFrontMatter.author,
};
}
// Use fake data in dev for faster development. // Use fake data in dev for faster development.
const fileLastUpdateData = const fileLastUpdateData =
process.env.NODE_ENV === 'production' process.env.NODE_ENV === 'production'
@ -61,14 +74,16 @@ async function readLastUpdateData(
author: 'Author', author: 'Author',
timestamp: 1539502055, timestamp: 1539502055,
}; };
const {author, timestamp} = fileLastUpdateData ?? {};
if (fileLastUpdateData) { return {
const {author, timestamp} = fileLastUpdateData; lastUpdatedBy: showLastUpdateAuthor
return { ? lastUpdateFrontMatter?.author ?? author
lastUpdatedAt: showLastUpdateTime ? timestamp : undefined, : undefined,
lastUpdatedBy: showLastUpdateAuthor ? author : undefined, lastUpdatedAt: showLastUpdateTime
}; ? frontMatterTimestamp ?? timestamp
} : undefined,
};
} }
return {}; return {};
@ -80,7 +95,6 @@ export async function readDocFile(
'contentPath' | 'contentPathLocalized' 'contentPath' | 'contentPathLocalized'
>, >,
source: string, source: string,
options: LastUpdateOptions,
): Promise<DocFile> { ): Promise<DocFile> {
const contentPath = await getFolderContainingFile( const contentPath = await getFolderContainingFile(
getContentPathList(versionMetadata), getContentPathList(versionMetadata),
@ -89,11 +103,8 @@ export async function readDocFile(
const filePath = path.join(contentPath, source); const filePath = path.join(contentPath, source);
const [content, lastUpdate] = await Promise.all([ const content = await fs.readFile(filePath, 'utf-8');
fs.readFile(filePath, 'utf-8'), return {source, content, contentPath, filePath};
readLastUpdateData(filePath, options),
]);
return {source, content, lastUpdate, contentPath, filePath};
} }
export async function readVersionDocs( export async function readVersionDocs(
@ -108,7 +119,7 @@ export async function readVersionDocs(
ignore: options.exclude, ignore: options.exclude,
}); });
return Promise.all( return Promise.all(
sources.map((source) => readDocFile(versionMetadata, source, options)), sources.map((source) => readDocFile(versionMetadata, source)),
); );
} }
@ -125,7 +136,7 @@ function isDraftForEnvironment({
return (env === 'production' && frontMatter.draft) ?? false; return (env === 'production' && frontMatter.draft) ?? false;
} }
function doProcessDocMetadata({ async function doProcessDocMetadata({
docFile, docFile,
versionMetadata, versionMetadata,
context, context,
@ -137,8 +148,8 @@ function doProcessDocMetadata({
context: LoadContext; context: LoadContext;
options: MetadataOptions; options: MetadataOptions;
env: DocEnv; env: DocEnv;
}): DocMetadataBase { }): Promise<DocMetadataBase> {
const {source, content, lastUpdate, contentPath, filePath} = docFile; const {source, content, contentPath, filePath} = docFile;
const {siteDir, i18n} = context; const {siteDir, i18n} = context;
const { const {
@ -155,8 +166,15 @@ function doProcessDocMetadata({
// (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc) // (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc)
// but allow to disable this behavior with front matter // but allow to disable this behavior with front matter
parse_number_prefixes: parseNumberPrefixes = true, parse_number_prefixes: parseNumberPrefixes = true,
last_update: lastUpdateFrontMatter,
} = frontMatter; } = frontMatter;
const lastUpdate = await readLastUpdateData(
filePath,
options,
lastUpdateFrontMatter,
);
// E.g. api/plugins/myDoc -> myDoc; myDoc -> myDoc // E.g. api/plugins/myDoc -> myDoc; myDoc -> myDoc
const sourceFileNameWithoutExtension = path.basename( const sourceFileNameWithoutExtension = path.basename(
source, source,
@ -287,7 +305,7 @@ export function processDocMetadata(args: {
context: LoadContext; context: LoadContext;
options: MetadataOptions; options: MetadataOptions;
env: DocEnv; env: DocEnv;
}): DocMetadataBase { }): Promise<DocMetadataBase> {
try { try {
return doProcessDocMetadata(args); return doProcessDocMetadata(args);
} catch (err) { } catch (err) {

View file

@ -14,6 +14,9 @@ import {
} from '@docusaurus/utils-validation'; } from '@docusaurus/utils-validation';
import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; import type {DocFrontMatter} from '@docusaurus/plugin-content-docs';
const FrontMatterLastUpdateErrorMessage =
'{{#label}} does not look like a valid front matter FileChange object. Please use a FileChange object (with an author and/or date).';
// NOTE: we don't add any default value on purpose here // NOTE: we don't add any default value on purpose here
// We don't want default values to magically appear in doc metadata and props // We don't want default values to magically appear in doc metadata and props
// While the user did not provide those values explicitly // While the user did not provide those values explicitly
@ -42,6 +45,15 @@ const DocFrontMatterSchema = Joi.object<DocFrontMatter>({
pagination_prev: Joi.string().allow(null), pagination_prev: Joi.string().allow(null),
draft: Joi.boolean(), draft: Joi.boolean(),
...FrontMatterTOCHeadingLevels, ...FrontMatterTOCHeadingLevels,
last_update: Joi.object({
author: Joi.string(),
date: Joi.date().raw(),
})
.or('author', 'date')
.messages({
'object.missing': FrontMatterLastUpdateErrorMessage,
'object.base': FrontMatterLastUpdateErrorMessage,
}),
}).unknown(); }).unknown();
export function validateDocFrontMatter(frontMatter: { export function validateDocFrontMatter(frontMatter: {

View file

@ -23,6 +23,14 @@ declare module '@docusaurus/plugin-content-docs' {
image?: string; image?: string;
}; };
export type FileChange = {
author?: string;
/** Date can be any
* [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse).
*/
date?: Date | string;
};
/** /**
* Custom callback for parsing number prefixes from file/folder names. * Custom callback for parsing number prefixes from file/folder names.
*/ */
@ -371,6 +379,8 @@ declare module '@docusaurus/plugin-content-docs' {
pagination_prev?: string | null; pagination_prev?: string | null;
/** Should this doc be excluded from production builds? */ /** Should this doc be excluded from production builds? */
draft?: boolean; draft?: boolean;
/** Allows overriding the last updated author and/or date. */
last_update?: FileChange;
}; };
export type LastUpdateData = { export type LastUpdateData = {

View file

@ -8,7 +8,6 @@
import type {BrokenMarkdownLink, Tag} from '@docusaurus/utils'; import type {BrokenMarkdownLink, Tag} from '@docusaurus/utils';
import type { import type {
VersionMetadata, VersionMetadata,
LastUpdateData,
LoadedVersion, LoadedVersion,
CategoryGeneratedIndexMetadata, CategoryGeneratedIndexMetadata,
} from '@docusaurus/plugin-content-docs'; } from '@docusaurus/plugin-content-docs';
@ -19,7 +18,6 @@ export type DocFile = {
filePath: string; // /!\ may be localized filePath: string; // /!\ may be localized
source: string; source: string;
content: string; content: string;
lastUpdate: LastUpdateData;
}; };
export type SourceToPermalink = { export type SourceToPermalink = {

View file

@ -0,0 +1,7 @@
---
last_update:
author: custom author
date: 1/1/2000
---
# Doc With Last Update Front Matter

View file

@ -22,6 +22,7 @@ const sidebars = {
'test-draft', 'test-draft',
'doc-without-sidebar', 'doc-without-sidebar',
'doc-with-another-sidebar', 'doc-with-another-sidebar',
'doc-with-last-update',
{ {
type: 'category', type: 'category',
label: 'Tests', label: 'Tests',

View file

@ -30,6 +30,7 @@ const dogfoodingPluginInstances = [
// Using a _ prefix to test against an edge case regarding MDX partials: https://github.com/facebook/docusaurus/discussions/5181#discussioncomment-1018079 // Using a _ prefix to test against an edge case regarding MDX partials: https://github.com/facebook/docusaurus/discussions/5181#discussioncomment-1018079
path: '_dogfooding/_docs tests', path: '_dogfooding/_docs tests',
showLastUpdateTime: true, showLastUpdateTime: true,
showLastUpdateAuthor: true,
sidebarItemsGenerator(args) { sidebarItemsGenerator(args) {
return args.defaultSidebarItemsGenerator({ return args.defaultSidebarItemsGenerator({
...args, ...args,

View file

@ -281,6 +281,7 @@ Accepted fields:
| `slug` | `string` | File path | Allows to customize the document url (`/<routeBasePath>/<slug>`). Support multiple patterns: `slug: my-doc`, `slug: /my/path/myDoc`, `slug: /`. | | `slug` | `string` | File path | Allows to customize the document url (`/<routeBasePath>/<slug>`). Support multiple patterns: `slug: my-doc`, `slug: /my/path/myDoc`, `slug: /`. |
| `tags` | `Tag[]` | `undefined` | A list of strings or objects of two string fields `label` and `permalink` to tag to your docs. | | `tags` | `Tag[]` | `undefined` | A list of strings or objects of two string fields `label` and `permalink` to tag to your docs. |
| `draft` | `boolean` | `false` | A boolean flag to indicate that a document is a work-in-progress. Draft documents will only be displayed during development. | | `draft` | `boolean` | `false` | A boolean flag to indicate that a document is a work-in-progress. Draft documents will only be displayed during development. |
| `last_update` | `FileChange` | `undefined` | Allows overriding the last updated author and/or date. Date can be any [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). |
</APITable> </APITable>
@ -288,6 +289,10 @@ Accepted fields:
type Tag = string | {label: string; permalink: string}; type Tag = string | {label: string; permalink: string};
``` ```
```ts
type FileChange = {date: string; author: string};
```
Example: Example:
```md ```md
@ -306,6 +311,9 @@ keywords:
- docusaurus - docusaurus
image: https://i.imgur.com/mErPwqL.png image: https://i.imgur.com/mErPwqL.png
slug: /myDoc slug: /myDoc
last_update:
date: 1/1/2000
author: custom author name
--- ---
# Markdown Features # Markdown Features