2020-08-17 11:50:22 -04:00
/ * *
* Copyright ( c ) Facebook , Inc . and its affiliates .
*
* 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 fs from 'fs-extra' ;
import {
aliasedSitePath ,
getEditUrl ,
2020-11-26 06:16:46 -05:00
getFolderContainingFile ,
2020-11-30 10:42:58 -05:00
normalizeUrl ,
parseMarkdownString ,
2021-01-29 09:35:25 -05:00
posixPath ,
2020-08-17 11:50:22 -04:00
} from '@docusaurus/utils' ;
import { LoadContext } from '@docusaurus/types' ;
import { getFileLastUpdate } from './lastUpdate' ;
import {
2020-11-30 10:42:58 -05:00
DocFile ,
2020-08-17 11:50:22 -04:00
DocMetadataBase ,
LastUpdateData ,
MetadataOptions ,
PluginOptions ,
2020-11-30 10:42:58 -05:00
VersionMetadata ,
2020-08-17 11:50:22 -04:00
} from './types' ;
import getSlug from './slug' ;
import { CURRENT_VERSION_NAME } from './constants' ;
import globby from 'globby' ;
2020-11-26 06:16:46 -05:00
import { getDocsDirPaths } from './versions' ;
2021-04-15 10:20:11 -04:00
import { extractNumberPrefix , stripPathNumberPrefixes } from './numberPrefix' ;
2021-04-09 11:09:33 -04:00
import { assertDocFrontMatter } from './docFrontMatter' ;
2020-08-17 11:50:22 -04:00
type LastUpdateOptions = Pick <
PluginOptions ,
'showLastUpdateAuthor' | 'showLastUpdateTime'
> ;
async function readLastUpdateData (
filePath : string ,
options : LastUpdateOptions ,
) : Promise < LastUpdateData > {
const { showLastUpdateAuthor , showLastUpdateTime } = options ;
if ( showLastUpdateAuthor || showLastUpdateTime ) {
// Use fake data in dev for faster development.
const fileLastUpdateData =
process . env . NODE_ENV === 'production'
? await getFileLastUpdate ( filePath )
: {
author : 'Author' ,
timestamp : 1539502055 ,
} ;
if ( fileLastUpdateData ) {
const { author , timestamp } = fileLastUpdateData ;
return {
lastUpdatedAt : showLastUpdateTime ? timestamp : undefined ,
lastUpdatedBy : showLastUpdateAuthor ? author : undefined ,
} ;
}
}
return { } ;
}
export async function readDocFile (
2020-11-26 06:16:46 -05:00
versionMetadata : Pick <
VersionMetadata ,
2021-03-12 09:11:08 -05:00
'contentPath' | 'contentPathLocalized'
2020-11-26 06:16:46 -05:00
> ,
2020-08-17 11:50:22 -04:00
source : string ,
options : LastUpdateOptions ,
) : Promise < DocFile > {
2021-03-12 09:11:08 -05:00
const contentPath = await getFolderContainingFile (
2020-11-26 06:16:46 -05:00
getDocsDirPaths ( versionMetadata ) ,
source ,
) ;
2021-03-12 09:11:08 -05:00
const filePath = path . join ( contentPath , source ) ;
2020-11-26 06:16:46 -05:00
2020-08-17 11:50:22 -04:00
const [ content , lastUpdate ] = await Promise . all ( [
fs . readFile ( filePath , 'utf-8' ) ,
readLastUpdateData ( filePath , options ) ,
] ) ;
2021-03-12 09:11:08 -05:00
return { source , content , lastUpdate , contentPath , filePath } ;
2020-08-17 11:50:22 -04:00
}
export async function readVersionDocs (
versionMetadata : VersionMetadata ,
options : Pick <
PluginOptions ,
'include' | 'showLastUpdateAuthor' | 'showLastUpdateTime'
> ,
) : Promise < DocFile [ ] > {
const sources = await globby ( options . include , {
2021-03-12 09:11:08 -05:00
cwd : versionMetadata.contentPath ,
2020-08-17 11:50:22 -04:00
} ) ;
return Promise . all (
2020-11-26 06:16:46 -05:00
sources . map ( ( source ) = > readDocFile ( versionMetadata , source , options ) ) ,
2020-08-17 11:50:22 -04:00
) ;
}
export function processDocMetadata ( {
docFile ,
versionMetadata ,
context ,
options ,
} : {
docFile : DocFile ;
versionMetadata : VersionMetadata ;
context : LoadContext ;
options : MetadataOptions ;
} ) : DocMetadataBase {
2021-03-12 09:11:08 -05:00
const { source , content , lastUpdate , contentPath , filePath } = docFile ;
2020-12-28 04:25:47 -05:00
const { homePageId } = options ;
2021-03-05 09:30:09 -05:00
const { siteDir , i18n } = context ;
2020-08-17 11:50:22 -04:00
2021-04-09 11:09:33 -04:00
const { frontMatter , contentTitle , excerpt } = parseMarkdownString ( content , {
source ,
} ) ;
assertDocFrontMatter ( frontMatter ) ;
2021-03-17 12:28:42 -04:00
const {
sidebar_label : sidebarLabel ,
custom_edit_url : customEditURL ,
2021-04-15 10:20:11 -04:00
// Strip number prefixes by default (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc) by default,
// but ability to disable this behavior with frontmatterr
strip_number_prefixes : stripNumberPrefixes = true ,
2021-03-17 12:28:42 -04:00
} = frontMatter ;
2020-08-17 11:50:22 -04:00
2021-04-15 10:20:11 -04:00
// ex: api/plugins/myDoc -> myDoc
// ex: myDoc -> myDoc
const sourceFileNameWithoutExtension = path . basename (
source ,
path . extname ( source ) ,
) ;
// ex: api/plugins/myDoc -> api/plugins
// ex: myDoc -> .
const sourceDirName = path . dirname ( source ) ;
const { filename : unprefixedFileName , numberPrefix } = stripNumberPrefixes
? extractNumberPrefix ( sourceFileNameWithoutExtension )
: { filename : sourceFileNameWithoutExtension , numberPrefix : undefined } ;
const baseID : string = frontMatter . id ? ? unprefixedFileName ;
2020-08-17 11:50:22 -04:00
if ( baseID . includes ( '/' ) ) {
throw new Error ( ` Document id [ ${ baseID } ] cannot include "/". ` ) ;
}
2021-04-15 10:20:11 -04:00
// For autogenerated sidebars, sidebar position can come from filename number prefix or frontmatter
const sidebarPosition : number | undefined =
frontMatter . sidebar_position ? ? numberPrefix ;
2020-08-17 11:50:22 -04:00
// TODO legacy retrocompatibility
// The same doc in 2 distinct version could keep the same id,
// we just need to namespace the data by version
2021-04-15 10:20:11 -04:00
const versionIdPrefix =
2020-08-17 11:50:22 -04:00
versionMetadata . versionName === CURRENT_VERSION_NAME
2021-04-15 10:20:11 -04:00
? undefined
: ` version- ${ versionMetadata . versionName } ` ;
2020-08-17 11:50:22 -04:00
// TODO legacy retrocompatibility
2021-04-15 10:20:11 -04:00
// I think it's bad to affect the frontmatter id with the dirname?
function computeDirNameIdPrefix() {
if ( sourceDirName === '.' ) {
return undefined ;
}
// Eventually remove the number prefixes from intermediate directories
return stripNumberPrefixes
? stripPathNumberPrefixes ( sourceDirName )
: sourceDirName ;
}
2020-08-17 11:50:22 -04:00
2021-04-15 10:20:11 -04:00
const unversionedId = [ computeDirNameIdPrefix ( ) , baseID ]
. filter ( Boolean )
. join ( '/' ) ;
2020-08-17 11:50:22 -04:00
2021-04-15 10:20:11 -04:00
// TODO is versioning the id very useful in practice?
// legacy versioned id, requires a breaking change to modify this
const id = [ versionIdPrefix , unversionedId ] . filter ( Boolean ) . join ( '/' ) ;
2020-08-17 11:50:22 -04:00
// TODO remove soon, deprecated homePageId
const isDocsHomePage = unversionedId === ( homePageId ? ? '_index' ) ;
if ( frontMatter . slug && isDocsHomePage ) {
throw new Error (
2020-09-11 14:33:08 -04:00
` The docs homepage (homePageId= ${ homePageId } ) is not allowed to have a frontmatter slug= ${ frontMatter . slug } => you have to choose either homePageId or slug, not both ` ,
2020-08-17 11:50:22 -04:00
) ;
}
const docSlug = isDocsHomePage
? '/'
: getSlug ( {
baseID ,
2021-04-15 10:20:11 -04:00
dirName : sourceDirName ,
2020-08-17 11:50:22 -04:00
frontmatterSlug : frontMatter.slug ,
2021-04-15 10:20:11 -04:00
stripDirNumberPrefixes : stripNumberPrefixes ,
2020-08-17 11:50:22 -04:00
} ) ;
// Default title is the id.
2021-04-09 11:09:33 -04:00
const title : string = frontMatter . title ? ? contentTitle ? ? baseID ;
2020-08-17 11:50:22 -04:00
2021-04-09 11:09:33 -04:00
const description : string = frontMatter . description ? ? excerpt ? ? '' ;
2020-08-17 11:50:22 -04:00
const permalink = normalizeUrl ( [ versionMetadata . versionPath , docSlug ] ) ;
2021-02-17 05:48:33 -05:00
function getDocEditUrl() {
2021-03-12 09:11:08 -05:00
const relativeFilePath = path . relative ( contentPath , filePath ) ;
2021-02-17 05:48:33 -05:00
if ( typeof options . editUrl === 'function' ) {
return options . editUrl ( {
version : versionMetadata.versionName ,
versionDocsDirPath : posixPath (
2021-03-12 09:11:08 -05:00
path . relative ( siteDir , versionMetadata . contentPath ) ,
2021-02-17 05:48:33 -05:00
) ,
docPath : posixPath ( relativeFilePath ) ,
permalink ,
locale : context.i18n.currentLocale ,
} ) ;
} else if ( typeof options . editUrl === 'string' ) {
2021-03-12 09:11:08 -05:00
const isLocalized = contentPath === versionMetadata . contentPathLocalized ;
2021-02-17 05:48:33 -05:00
const baseVersionEditUrl =
isLocalized && options . editLocalizedFiles
? versionMetadata . versionEditUrlLocalized
: versionMetadata . versionEditUrl ;
return getEditUrl ( relativeFilePath , baseVersionEditUrl ) ;
} else {
return undefined ;
}
}
2020-08-17 11:50:22 -04:00
// Assign all of object properties during instantiation (if possible) for
// NodeJS optimization.
// Adding properties to object after instantiation will cause hidden
// class transitions.
2020-11-30 10:42:58 -05:00
return {
2020-08-17 11:50:22 -04:00
unversionedId ,
id ,
isDocsHomePage ,
title ,
description ,
source : aliasedSitePath ( filePath , siteDir ) ,
2021-04-15 10:20:11 -04:00
sourceDirName ,
2020-08-17 11:50:22 -04:00
slug : docSlug ,
permalink ,
2021-03-17 12:28:42 -04:00
editUrl : customEditURL !== undefined ? customEditURL : getDocEditUrl ( ) ,
2020-08-17 11:50:22 -04:00
version : versionMetadata.versionName ,
lastUpdatedBy : lastUpdate.lastUpdatedBy ,
lastUpdatedAt : lastUpdate.lastUpdatedAt ,
2021-03-05 09:30:09 -05:00
formattedLastUpdatedAt : lastUpdate.lastUpdatedAt
2021-03-15 13:02:53 -04:00
? new Intl . DateTimeFormat ( i18n . currentLocale ) . format (
2021-03-05 09:30:09 -05:00
lastUpdate . lastUpdatedAt * 1000 ,
)
: undefined ,
2021-03-17 12:28:42 -04:00
sidebar_label : sidebarLabel ,
2021-04-15 10:20:11 -04:00
sidebarPosition ,
2021-03-26 14:54:29 -04:00
frontMatter ,
2020-08-17 11:50:22 -04:00
} ;
}