diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/customLastUpdate.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/customLastUpdate.md new file mode 100644 index 000000000000..b8e37d4f8a9e --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/customLastUpdate.md @@ -0,0 +1,8 @@ +--- +title: Custom Last Update +last_update: + author: Custom Author + date: 1/1/2000 +--- + +Custom last update \ No newline at end of file diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/lastUpdateAuthorOnly.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/lastUpdateAuthorOnly.md new file mode 100644 index 000000000000..4929ed91a9c5 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/lastUpdateAuthorOnly.md @@ -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 \ No newline at end of file diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/lastUpdateDateOnly.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/lastUpdateDateOnly.md new file mode 100644 index 000000000000..f9bfeb9759d9 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/lastUpdateDateOnly.md @@ -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 \ No newline at end of file diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap index f05c66e2aa21..69401a06a04c 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap @@ -3,13 +3,24 @@ exports[`simple site custom pagination 1`] = ` { "pagination": [ + { + "id": "customLastUpdate", + "next": { + "permalink": "/docs/doc with space", + "title": "Hoo hoo, if this path tricks you...", + }, + "prev": undefined, + }, { "id": "doc with space", "next": { "permalink": "/docs/doc-draft", "title": "doc-draft", }, - "prev": undefined, + "prev": { + "permalink": "/docs/customLastUpdate", + "title": "Custom Last Update", + }, }, { "id": "doc-draft", @@ -63,14 +74,36 @@ exports[`simple site custom pagination 1`] = ` { "id": "ipsum", "next": { - "permalink": "/docs/lorem", - "title": "lorem", + "permalink": "/docs/lastUpdateAuthorOnly", + "title": "Last Update Author Only", }, "prev": { "permalink": "/docs/", "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", "next": { @@ -78,8 +111,8 @@ exports[`simple site custom pagination 1`] = ` "title": "rootAbsoluteSlug", }, "prev": { - "permalink": "/docs/ipsum", - "title": "ipsum", + "permalink": "/docs/lastUpdateDateOnly", + "title": "Last Update Date Only", }, }, { @@ -170,6 +203,10 @@ exports[`simple site custom pagination 1`] = ` ], "sidebars": { "defaultSidebar": [ + { + "id": "customLastUpdate", + "type": "doc", + }, { "id": "doc with space", "type": "doc", @@ -208,6 +245,14 @@ exports[`simple site custom pagination 1`] = ` "id": "ipsum", "type": "doc", }, + { + "id": "lastUpdateAuthorOnly", + "type": "doc", + }, + { + "id": "lastUpdateDateOnly", + "type": "doc", + }, { "id": "lorem", "type": "doc", diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap index c8b0408c5ee1..c428a6c55e86 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap @@ -22,6 +22,7 @@ These sidebar document ids do not exist: - nonExistent Available document ids are: +- customLastUpdate - doc with space - doc-draft - foo/bar @@ -29,6 +30,8 @@ Available document ids are: - headingAsTitle - hello - ipsum +- lastUpdateAuthorOnly +- lastUpdateDateOnly - lorem - rootAbsoluteSlug - rootRelativeSlug @@ -267,6 +270,11 @@ exports[`simple website content 5`] = ` "versions": [ { "docs": [ + { + "id": "customLastUpdate", + "path": "/docs/customLastUpdate", + "sidebar": undefined, + }, { "id": "doc with space", "path": "/docs/doc with space", @@ -302,6 +310,16 @@ exports[`simple website content 5`] = ` "path": "/docs/ipsum", "sidebar": undefined, }, + { + "id": "lastUpdateAuthorOnly", + "path": "/docs/lastUpdateAuthorOnly", + "sidebar": undefined, + }, + { + "id": "lastUpdateDateOnly", + "path": "/docs/lastUpdateDateOnly", + "sidebar": undefined, + }, { "id": "lorem", "path": "/docs/lorem", @@ -390,6 +408,26 @@ exports[`simple website content: data 1`] = ` "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": "{ "unversionedId": "doc-draft", @@ -563,6 +601,44 @@ exports[`simple website content: data 1`] = ` "frontMatter": { "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": "{ "unversionedId": "lorem", @@ -924,6 +1000,11 @@ exports[`simple website content: data 1`] = ` ] }, "docs": { + "customLastUpdate": { + "id": "customLastUpdate", + "title": "Custom Last Update", + "description": "Custom last update" + }, "doc with space": { "id": "doc with space", "title": "Hoo hoo, if this path tricks you...", @@ -963,6 +1044,16 @@ exports[`simple website content: data 1`] = ` "title": "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": { "id": "lorem", "title": "lorem", @@ -1026,6 +1117,11 @@ exports[`simple website content: global data 1`] = ` "versions": [ { "docs": [ + { + "id": "customLastUpdate", + "path": "/docs/customLastUpdate", + "sidebar": undefined, + }, { "id": "doc with space", "path": "/docs/doc with space", @@ -1061,6 +1157,16 @@ exports[`simple website content: global data 1`] = ` "path": "/docs/ipsum", "sidebar": undefined, }, + { + "id": "lastUpdateAuthorOnly", + "path": "/docs/lastUpdateAuthorOnly", + "sidebar": undefined, + }, + { + "id": "lastUpdateDateOnly", + "path": "/docs/lastUpdateDateOnly", + "sidebar": undefined, + }, { "id": "lorem", "path": "/docs/lorem", @@ -1202,6 +1308,14 @@ exports[`simple website content: route config 1`] = ` "path": "/docs/category/slugs", "sidebar": "docs", }, + { + "component": "@theme/DocItem", + "exact": true, + "modules": { + "content": "@site/docs/customLastUpdate.md", + }, + "path": "/docs/customLastUpdate", + }, { "component": "@theme/DocItem", "exact": true, @@ -1262,6 +1376,22 @@ exports[`simple website content: route config 1`] = ` }, "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", "exact": true, diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts index 9a338c5bf9d2..88b74733f7ce 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts @@ -57,7 +57,6 @@ ${markdown} return { source, content, - lastUpdate: {}, contentPath: 'docs', filePath: source, }; @@ -79,7 +78,7 @@ function createTestUtils({ env = 'production', }: TestUtilsArg) { async function readDoc(docFileSource: string) { - return readDocFile(versionMetadata, docFileSource, options); + return readDocFile(versionMetadata, docFileSource); } async function processDocFile(docFileArg: DocFile | string) { const docFile: DocFile = @@ -119,7 +118,7 @@ function createTestUtils({ async function testSlug(docFileSource: string, expectedPermalink: string) { const docFile = await readDoc(docFileSource); - const metadata = processDocMetadata({ + const metadata = await processDocMetadata({ docFile, versionMetadata, context, @@ -137,14 +136,16 @@ function createTestUtils({ }[]; sidebars: Sidebars; }> { - const rawDocs = docFiles.map((docFile) => - processDocMetadata({ - docFile, - versionMetadata, - context, - options, - env: 'production', - }), + const rawDocs = await Promise.all( + docFiles.map(async (docFile) => + processDocMetadata({ + docFile, + versionMetadata, + context, + options, + env: 'production', + }), + ), ); const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, { sidebarItemsGenerator: ({defaultSidebarItemsGenerator, ...args}) => @@ -230,6 +231,9 @@ describe('simple site', () => { 'headingAsTitle.md', 'doc with space.md', 'doc-draft.md', + 'customLastUpdate.md', + 'lastUpdateAuthorOnly.md', + 'lastUpdateDateOnly.md', 'foo/bar.md', 'foo/baz.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 () => { const {defaultTestUtils} = await loadSite(); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts index 81eedbf13b9a..55da1682c725 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts @@ -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', + ], + ], + }); +}); diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index 2ea127f99fbb..580d90101740 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -37,6 +37,7 @@ import type { VersionMetadata, DocFrontMatter, LoadedVersion, + FileChange, } from '@docusaurus/plugin-content-docs'; import type {LoadContext} from '@docusaurus/types'; import type {SidebarsUtils} from './sidebars/utils'; @@ -50,9 +51,21 @@ type LastUpdateOptions = Pick< async function readLastUpdateData( filePath: string, options: LastUpdateOptions, + lastUpdateFrontMatter: FileChange | undefined, ): Promise { const {showLastUpdateAuthor, showLastUpdateTime} = options; 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. const fileLastUpdateData = process.env.NODE_ENV === 'production' @@ -61,14 +74,16 @@ async function readLastUpdateData( author: 'Author', timestamp: 1539502055, }; - - if (fileLastUpdateData) { - const {author, timestamp} = fileLastUpdateData; - return { - lastUpdatedAt: showLastUpdateTime ? timestamp : undefined, - lastUpdatedBy: showLastUpdateAuthor ? author : undefined, - }; - } + const {author, timestamp} = fileLastUpdateData ?? {}; + + return { + lastUpdatedBy: showLastUpdateAuthor + ? lastUpdateFrontMatter?.author ?? author + : undefined, + lastUpdatedAt: showLastUpdateTime + ? frontMatterTimestamp ?? timestamp + : undefined, + }; } return {}; @@ -80,7 +95,6 @@ export async function readDocFile( 'contentPath' | 'contentPathLocalized' >, source: string, - options: LastUpdateOptions, ): Promise { const contentPath = await getFolderContainingFile( getContentPathList(versionMetadata), @@ -89,11 +103,8 @@ export async function readDocFile( const filePath = path.join(contentPath, source); - const [content, lastUpdate] = await Promise.all([ - fs.readFile(filePath, 'utf-8'), - readLastUpdateData(filePath, options), - ]); - return {source, content, lastUpdate, contentPath, filePath}; + const content = await fs.readFile(filePath, 'utf-8'); + return {source, content, contentPath, filePath}; } export async function readVersionDocs( @@ -108,7 +119,7 @@ export async function readVersionDocs( ignore: options.exclude, }); 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; } -function doProcessDocMetadata({ +async function doProcessDocMetadata({ docFile, versionMetadata, context, @@ -137,8 +148,8 @@ function doProcessDocMetadata({ context: LoadContext; options: MetadataOptions; env: DocEnv; -}): DocMetadataBase { - const {source, content, lastUpdate, contentPath, filePath} = docFile; +}): Promise { + const {source, content, contentPath, filePath} = docFile; const {siteDir, i18n} = context; const { @@ -155,8 +166,15 @@ function doProcessDocMetadata({ // (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc) // but allow to disable this behavior with front matter parse_number_prefixes: parseNumberPrefixes = true, + last_update: lastUpdateFrontMatter, } = frontMatter; + const lastUpdate = await readLastUpdateData( + filePath, + options, + lastUpdateFrontMatter, + ); + // E.g. api/plugins/myDoc -> myDoc; myDoc -> myDoc const sourceFileNameWithoutExtension = path.basename( source, @@ -287,7 +305,7 @@ export function processDocMetadata(args: { context: LoadContext; options: MetadataOptions; env: DocEnv; -}): DocMetadataBase { +}): Promise { try { return doProcessDocMetadata(args); } catch (err) { diff --git a/packages/docusaurus-plugin-content-docs/src/frontMatter.ts b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts index de944a6b1716..9d210a831b25 100644 --- a/packages/docusaurus-plugin-content-docs/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts @@ -14,6 +14,9 @@ import { } from '@docusaurus/utils-validation'; 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 // We don't want default values to magically appear in doc metadata and props // While the user did not provide those values explicitly @@ -42,6 +45,15 @@ const DocFrontMatterSchema = Joi.object({ pagination_prev: Joi.string().allow(null), draft: Joi.boolean(), ...FrontMatterTOCHeadingLevels, + last_update: Joi.object({ + author: Joi.string(), + date: Joi.date().raw(), + }) + .or('author', 'date') + .messages({ + 'object.missing': FrontMatterLastUpdateErrorMessage, + 'object.base': FrontMatterLastUpdateErrorMessage, + }), }).unknown(); export function validateDocFrontMatter(frontMatter: { diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index 1d7be06b74dc..835c87204375 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -23,6 +23,14 @@ declare module '@docusaurus/plugin-content-docs' { 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. */ @@ -371,6 +379,8 @@ declare module '@docusaurus/plugin-content-docs' { pagination_prev?: string | null; /** Should this doc be excluded from production builds? */ draft?: boolean; + /** Allows overriding the last updated author and/or date. */ + last_update?: FileChange; }; export type LastUpdateData = { diff --git a/packages/docusaurus-plugin-content-docs/src/types.ts b/packages/docusaurus-plugin-content-docs/src/types.ts index cb4506b20d1a..4c4568517fbb 100644 --- a/packages/docusaurus-plugin-content-docs/src/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/types.ts @@ -8,7 +8,6 @@ import type {BrokenMarkdownLink, Tag} from '@docusaurus/utils'; import type { VersionMetadata, - LastUpdateData, LoadedVersion, CategoryGeneratedIndexMetadata, } from '@docusaurus/plugin-content-docs'; @@ -19,7 +18,6 @@ export type DocFile = { filePath: string; // /!\ may be localized source: string; content: string; - lastUpdate: LastUpdateData; }; export type SourceToPermalink = { diff --git a/website/_dogfooding/_docs tests/doc-with-last-update.md b/website/_dogfooding/_docs tests/doc-with-last-update.md new file mode 100644 index 000000000000..34c1cb0c7cb6 --- /dev/null +++ b/website/_dogfooding/_docs tests/doc-with-last-update.md @@ -0,0 +1,7 @@ +--- +last_update: + author: custom author + date: 1/1/2000 +--- + +# Doc With Last Update Front Matter diff --git a/website/_dogfooding/docs-tests-sidebars.js b/website/_dogfooding/docs-tests-sidebars.js index e7ebb55ce1a5..0c2783feeda3 100644 --- a/website/_dogfooding/docs-tests-sidebars.js +++ b/website/_dogfooding/docs-tests-sidebars.js @@ -22,6 +22,7 @@ const sidebars = { 'test-draft', 'doc-without-sidebar', 'doc-with-another-sidebar', + 'doc-with-last-update', { type: 'category', label: 'Tests', diff --git a/website/_dogfooding/dogfooding.config.js b/website/_dogfooding/dogfooding.config.js index b6239f787b42..f8a4e7007f0e 100644 --- a/website/_dogfooding/dogfooding.config.js +++ b/website/_dogfooding/dogfooding.config.js @@ -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 path: '_dogfooding/_docs tests', showLastUpdateTime: true, + showLastUpdateAuthor: true, sidebarItemsGenerator(args) { return args.defaultSidebarItemsGenerator({ ...args, diff --git a/website/docs/api/plugins/plugin-content-docs.md b/website/docs/api/plugins/plugin-content-docs.md index 4b7deaff16d6..aaec7289daf1 100644 --- a/website/docs/api/plugins/plugin-content-docs.md +++ b/website/docs/api/plugins/plugin-content-docs.md @@ -281,6 +281,7 @@ Accepted fields: | `slug` | `string` | File path | Allows to customize the document url (`//`). 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. | | `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). | @@ -288,6 +289,10 @@ Accepted fields: type Tag = string | {label: string; permalink: string}; ``` +```ts +type FileChange = {date: string; author: string}; +``` + Example: ```md @@ -306,6 +311,9 @@ keywords: - docusaurus image: https://i.imgur.com/mErPwqL.png slug: /myDoc +last_update: + date: 1/1/2000 + author: custom author name --- # Markdown Features