mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-17 00:35:50 +02:00
feat: add last contributor to each document (#980)
* add contributor list to each document * handle case where there is no github repo * Move to v1 * Revert "handle case where there is no github repo" This reverts commita76a887901
. * Revert "add contributor list to each document" This reverts commitc0cc79f995
. * Add last updated by field * -Include enableUpdateBy in the config - Rename css class to be more general * Fix lint * Refactor : - s/getGitlastupdated/getGitLastUpdatedTime - refactor part in getGitLastUpdated[Time|By] that overlaps - remove getAuthorInformation * -Modify the display logic -Refactor the utils, combine lastupdatedtime and lastupdatedby -Replace the test * Modify docs based on the display change * Update docs for url and baseurl For more clarity and to make relationship more clear * Update support for Web App Manifest (#1046) * misc: update nits * misc: update nits
This commit is contained in:
parent
4a7e1ea189
commit
8cf9afe4ac
7 changed files with 158 additions and 62 deletions
|
@ -17,7 +17,7 @@ const OnPageNav = require('./nav/OnPageNav.js');
|
|||
const Site = require('./Site.js');
|
||||
const translation = require('../server/translation.js');
|
||||
const docs = require('../server/docs.js');
|
||||
const {idx, getGitLastUpdated} = require('./utils.js');
|
||||
const {idx, getGitLastUpdatedTime, getGitLastUpdatedBy} = require('./utils.js');
|
||||
|
||||
// component used to generate whole webpage for docs, including sidebar/header/footer
|
||||
class DocsLayout extends React.Component {
|
||||
|
@ -45,16 +45,19 @@ class DocsLayout extends React.Component {
|
|||
if (this.props.Doc) {
|
||||
DocComponent = this.props.Doc;
|
||||
}
|
||||
const filepath = docs.getFilePath(metadata);
|
||||
|
||||
let updateTime;
|
||||
if (this.props.config.enableUpdateTime) {
|
||||
const filepath = docs.getFilePath(metadata);
|
||||
updateTime = getGitLastUpdated(filepath);
|
||||
}
|
||||
const updateTime = this.props.config.enableUpdateTime
|
||||
? getGitLastUpdatedTime(filepath)
|
||||
: null;
|
||||
const updateAuthor = this.props.config.enableUpdateBy
|
||||
? getGitLastUpdatedBy(filepath)
|
||||
: null;
|
||||
|
||||
const title =
|
||||
idx(i18n, ['localized-strings', 'docs', id, 'title']) || defaultTitle;
|
||||
const hasOnPageNav = this.props.config.onPageNav === 'separate';
|
||||
|
||||
const previousTitle =
|
||||
idx(i18n, ['localized-strings', metadata.previous_id]) ||
|
||||
idx(i18n, ['localized-strings', 'previous']) ||
|
||||
|
@ -90,15 +93,16 @@ class DocsLayout extends React.Component {
|
|||
version={metadata.version}
|
||||
language={metadata.language}
|
||||
/>
|
||||
{this.props.config.enableUpdateTime &&
|
||||
updateTime && (
|
||||
<div className="docLastUpdateTimestamp">
|
||||
<em>
|
||||
<strong>Last updated: </strong>
|
||||
{updateTime}
|
||||
</em>
|
||||
</div>
|
||||
)}
|
||||
{(updateTime || updateAuthor) && (
|
||||
<div className="docLastUpdate">
|
||||
<em>
|
||||
Last updated
|
||||
{updateTime && ` on ${updateTime}`}
|
||||
{updateAuthor && ` by ${updateAuthor}`}
|
||||
</em>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="docs-prevnext">
|
||||
{metadata.previous_id && (
|
||||
<a
|
||||
|
|
|
@ -74,13 +74,13 @@ describe('utils', () => {
|
|||
expect(utils.removeExtension('pages.js')).toBe('pages');
|
||||
});
|
||||
|
||||
test('getGitLastUpdated', () => {
|
||||
test('getGitLastUpdatedTime', () => {
|
||||
// existing test file in repository with git timestamp
|
||||
const existingFilePath = path.join(__dirname, '__fixtures__', 'test.md');
|
||||
const gitLastUpdated = utils.getGitLastUpdated(existingFilePath);
|
||||
expect(typeof gitLastUpdated).toBe('string');
|
||||
expect(Date.parse(gitLastUpdated)).not.toBeNaN();
|
||||
expect(gitLastUpdated).not.toBeNull();
|
||||
const gitLastUpdatedTime = utils.getGitLastUpdatedTime(existingFilePath);
|
||||
expect(typeof gitLastUpdatedTime).toBe('string');
|
||||
expect(Date.parse(gitLastUpdatedTime)).not.toBeNaN();
|
||||
expect(gitLastUpdatedTime).not.toBeNull();
|
||||
|
||||
// non existing file
|
||||
const nonExistingFilePath = path.join(
|
||||
|
@ -88,14 +88,14 @@ describe('utils', () => {
|
|||
'__fixtures__',
|
||||
'.nonExisting',
|
||||
);
|
||||
expect(utils.getGitLastUpdated(null)).toBeNull();
|
||||
expect(utils.getGitLastUpdated(undefined)).toBeNull();
|
||||
expect(utils.getGitLastUpdated(nonExistingFilePath)).toBeNull();
|
||||
expect(utils.getGitLastUpdatedTime(null)).toBeNull();
|
||||
expect(utils.getGitLastUpdatedTime(undefined)).toBeNull();
|
||||
expect(utils.getGitLastUpdatedTime(nonExistingFilePath)).toBeNull();
|
||||
|
||||
// temporary created file that has no git timestamp
|
||||
const tempFilePath = path.join(__dirname, '__fixtures__', '.temp');
|
||||
fs.writeFileSync(tempFilePath, 'Lorem ipsum :)');
|
||||
expect(utils.getGitLastUpdated(tempFilePath)).toBeNull();
|
||||
expect(utils.getGitLastUpdatedTime(tempFilePath)).toBeNull();
|
||||
fs.unlinkSync(tempFilePath);
|
||||
|
||||
// test renaming and moving file
|
||||
|
@ -111,24 +111,24 @@ describe('utils', () => {
|
|||
// create new file
|
||||
shell.exec = jest.fn(() => ({
|
||||
stdout:
|
||||
'1539502055\n' +
|
||||
'1539502055, Yangshun Tay\n' +
|
||||
'\n' +
|
||||
' create mode 100644 v1/lib/core/__tests__/__fixtures__/.temp2\n',
|
||||
}));
|
||||
const createTime = utils.getGitLastUpdated(tempFilePath2);
|
||||
const createTime = utils.getGitLastUpdatedTime(tempFilePath2);
|
||||
expect(typeof createTime).toBe('string');
|
||||
|
||||
// rename / move the file
|
||||
shell.exec = jest.fn(() => ({
|
||||
stdout:
|
||||
'1539502056\n' +
|
||||
'1539502056, Joel Marcey\n' +
|
||||
'\n' +
|
||||
' rename v1/lib/core/__tests__/__fixtures__/{.temp2 => test/.temp3} (100%)\n' +
|
||||
'1539502055\n' +
|
||||
'1539502055, Yangshun Tay\n' +
|
||||
'\n' +
|
||||
' create mode 100644 v1/lib/core/__tests__/__fixtures__/.temp2\n',
|
||||
}));
|
||||
const lastUpdateTime = utils.getGitLastUpdated(tempFilePath3);
|
||||
const lastUpdateTime = utils.getGitLastUpdatedTime(tempFilePath3);
|
||||
// should only consider file content change
|
||||
expect(lastUpdateTime).toEqual(createTime);
|
||||
});
|
||||
|
|
|
@ -39,8 +39,21 @@ function idx(target, keyPaths) {
|
|||
}
|
||||
|
||||
function getGitLastUpdated(filepath) {
|
||||
function isTimestamp(str) {
|
||||
return /^\d+$/.test(str);
|
||||
const timestampAndAuthorRegex = /^(\d+), (.+)$/;
|
||||
|
||||
function isTimestampAndAuthor(str) {
|
||||
return timestampAndAuthorRegex.test(str);
|
||||
}
|
||||
|
||||
function getTimestampAndAuthor(str) {
|
||||
if (!str) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const temp = str.match(timestampAndAuthorRegex);
|
||||
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).
|
||||
|
@ -51,40 +64,58 @@ function getGitLastUpdated(filepath) {
|
|||
const silentState = shell.config.silent; // Save old silent state.
|
||||
shell.config.silent = true;
|
||||
const result = shell
|
||||
.exec(`git log --follow --summary --format=%ct ${filepath}`)
|
||||
.exec(`git log --follow --summary --format="%ct, %an" ${filepath}`)
|
||||
.stdout.trim();
|
||||
shell.config.silent = silentState;
|
||||
|
||||
// Format the log results to be
|
||||
// ['1234567', 'rename ...', '1234566', 'move ...', '1234565', '1234564']
|
||||
// ['1234567890, Yangshun Tay', 'rename ...', '1234567880,
|
||||
// 'Joel Marcey', 'move ...', '1234567870', '1234567860']
|
||||
const records = result
|
||||
.toString('utf-8')
|
||||
.replace(/\n\s*\n/g, '\n')
|
||||
.split('\n')
|
||||
.filter(String);
|
||||
|
||||
const timeSpan = records.find((item, index, arr) => {
|
||||
const currentItemIsTimestamp = isTimestamp(item);
|
||||
const lastContentModifierCommit = records.find((item, index, arr) => {
|
||||
const currentItemIsTimestampAndAuthor = isTimestampAndAuthor(item);
|
||||
const isLastTwoItem = index + 2 >= arr.length;
|
||||
const nextItemIsTimestamp = isTimestamp(arr[index + 1]);
|
||||
return currentItemIsTimestamp && (isLastTwoItem || nextItemIsTimestamp);
|
||||
const nextItemIsTimestampAndAuthor = isTimestampAndAuthor(arr[index + 1]);
|
||||
return (
|
||||
currentItemIsTimestampAndAuthor &&
|
||||
(isLastTwoItem || nextItemIsTimestampAndAuthor)
|
||||
);
|
||||
});
|
||||
|
||||
if (timeSpan) {
|
||||
const date = new Date(parseInt(timeSpan, 10) * 1000);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
return lastContentModifierCommit
|
||||
? getTimestampAndAuthor(lastContentModifierCommit)
|
||||
: null;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getGitLastUpdatedTime(filepath) {
|
||||
const commit = getGitLastUpdated(filepath);
|
||||
|
||||
if (commit && commit.timestamp) {
|
||||
const date = new Date(parseInt(commit.timestamp, 10) * 1000);
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getGitLastUpdatedBy(filepath) {
|
||||
const commit = getGitLastUpdated(filepath);
|
||||
return commit ? commit.author : null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
blogPostHasTruncateMarker,
|
||||
extractBlogPostBeforeTruncate,
|
||||
getGitLastUpdated,
|
||||
getGitLastUpdatedTime,
|
||||
getGitLastUpdatedBy,
|
||||
getPath,
|
||||
removeExtension,
|
||||
idx,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue