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 commit a76a887901.

* Revert "add contributor list to each document"

This reverts commit c0cc79f995.

* 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:
Fienny Angelina 2018-10-18 15:59:17 +08:00 committed by Yangshun Tay
parent 4a7e1ea189
commit 8cf9afe4ac
7 changed files with 158 additions and 62 deletions

View file

@ -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

View file

@ -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);
});

View file

@ -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,