Skip to content

Commit 6581ab5

Browse files
authored
repo sync
2 parents 033325a + 3d4dda0 commit 6581ab5

File tree

6 files changed

+103
-48
lines changed

6 files changed

+103
-48
lines changed

contributing/redirects.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ Sometimes the canonical home of some content moves outside the help site. For th
1414

1515
We also have custom routing code that automatically creates redirects under the hood for things like moved Admin guide pages. This code lives in [/lib/redirects/get-old-paths-from-permalink.js](/lib/redirects/get-old-paths-from-permalink.js). All redirects for the site are compiled when the server starts by [/lib/redirects/precompile.js](/lib/redirects/precompile.js).
1616

17-
See [Links and image paths](content/README.md#links-and-image-paths) for info on how links and images are rewritten on the fly at page render time.
17+
See [Links and image paths](../content/README.md#links-and-image-paths) for info on how links and images are rewritten on the fly at page render time.
1818

19-
See [Troubleshooting](contribution/troubleshooting.md#debugging-locally) for info on viewing the redirects for any page.
19+
See [Troubleshooting](./troubleshooting.md#debugging-locally) for info on viewing the redirects for any page.

lib/page.js

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,18 @@ const frontmatter = require('./frontmatter')
2121
const products = require('./all-products')
2222
const slash = require('slash')
2323
const statsd = require('./statsd')
24+
const fmfromf = require('./read-frontmatter')
2425
const getLinkData = require('./get-link-data')
2526

2627
class Page {
27-
static init (opts) {
28-
opts = Page.read(opts)
28+
static async init (opts) {
29+
opts = await Page.read(opts)
2930
if (!opts) return
3031
return new Page(opts)
3132
}
3233

33-
static read (opts) {
34+
static async read (opts) {
35+
assert(opts.languageCode, 'languageCode is required')
3436
assert(opts.relativePath, 'relativePath is required')
3537
assert(opts.basePath, 'basePath is required')
3638

@@ -40,40 +42,29 @@ class Page {
4042
// Per https://nodejs.org/api/fs.html#fs_fs_exists_path_callback
4143
// its better to read and handle errors than to check access/stats first
4244
try {
43-
const raw = fs.readFileSync(fullPath, 'utf8')
44-
return { ...opts, relativePath, fullPath, raw }
45+
const { data, content, errors: frontmatterErrors } = await fmfromf(fullPath, opts.languageCode)
46+
47+
return {
48+
...opts,
49+
relativePath,
50+
fullPath,
51+
...data,
52+
markdown: content,
53+
frontmatterErrors
54+
}
4555
} catch (err) {
4656
if (err.code === 'ENOENT') return false
4757
console.error(err)
4858
}
4959
}
5060

5161
constructor (opts) {
52-
assert(opts.languageCode, 'languageCode is required')
53-
5462
Object.assign(this, { ...opts })
5563

56-
// TODO remove this when crowdin-support issue 66 has been resolved
57-
if (this.languageCode !== 'en' && this.raw.includes(': verdadero')) {
58-
this.raw = this.raw.replace(': verdadero', ': true')
59-
}
60-
61-
// parse frontmatter and save any errors for validation in the test suite
62-
const { content, data, errors: frontmatterErrors } = frontmatter(this.raw, { filepath: this.fullPath })
63-
this.frontmatterErrors = frontmatterErrors
64-
6564
if (this.frontmatterErrors.length) {
6665
throw new Error(JSON.stringify(this.frontmatterErrors, null, 2))
6766
}
6867

69-
// preserve the frontmatter-free markdown content,
70-
this.markdown = content
71-
72-
// prevent `[foo] (bar)` strings with a space between from being interpreted as markdown links
73-
this.markdown = encodeBracketedParentheses(this.markdown)
74-
75-
Object.assign(this, data)
76-
7768
// Store raw data so we can cache parsed versions
7869
this.rawIntro = this.intro
7970
this.rawTitle = this.title
@@ -94,8 +85,10 @@ class Page {
9485
// derive array of Permalink objects
9586
this.permalinks = Permalink.derive(this.languageCode, this.relativePath, this.title, this.versions)
9687

97-
// get an array of linked items in product and category TOCs
98-
this.tocItems = getTocItems(this)
88+
if (this.relativePath.endsWith('index.md')) {
89+
// get an array of linked items in product and category TOCs
90+
this.tocItems = getTocItems(this)
91+
}
9992

10093
// if this is an article and it doesn't have showMiniToc = false, set mini TOC to true
10194
if (!this.relativePath.endsWith('index.md') && !this.mapTopic) {
@@ -146,7 +139,20 @@ class Page {
146139
: this.renderProp('title', context, opts)
147140
}
148141

142+
async getMarkdown () {
143+
const raw = fs.readFileSync(this.fullPath, 'utf8')
144+
const { content } = frontmatter(raw, { filepath: this.fullPath })
145+
// prevent `[foo] (bar)` strings with a space between from being interpreted as markdown links
146+
const encodedMarkdown = encodeBracketedParentheses(content)
147+
return encodedMarkdown
148+
}
149+
149150
async _render (context) {
151+
// Get the raw markdown if we need to
152+
if (!this.markdown) {
153+
this.markdown = await this.getMarkdown()
154+
}
155+
150156
this.intro = await renderContent(this.rawIntro, context)
151157

152158
// rewrite local links in the intro to include current language code and GHE version if needed

lib/pages.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@ const walk = require('walk-sync').entries
33
const Page = require('./page')
44
const languages = require('./languages')
55

6-
function loadPageList () {
6+
async function loadPageList () {
77
// load english pages
88
const englishPath = path.join(__dirname, '..', languages.en.dir, 'content')
99
const englishPaths = walk(englishPath, {
1010
globs: ['**/*.md'],
1111
ignore: ['**/README.md']
1212
})
13-
const englishPages = englishPaths.map(
14-
opts => Page.read({
13+
const englishPages = await Promise.all(englishPaths.map(
14+
async opts => Page.init({
1515
...opts,
1616
languageCode: languages.en.code
1717
})
18-
)
18+
))
1919

2020
// load matching pages in other languages
2121
const localizedPaths = Object.values(languages)
@@ -30,16 +30,18 @@ function loadPageList () {
3030
})
3131
.flat()
3232

33-
const localizedPages = localizedPaths.map(
34-
({ basePath, relativePath, languageCode }) =>
35-
Page.read({ basePath, relativePath, languageCode })
36-
)
33+
const localizedPages = await Promise.all(localizedPaths.map(
34+
async ({ basePath, relativePath, languageCode }) => Page.init({
35+
basePath,
36+
relativePath,
37+
languageCode
38+
})
39+
))
3740

3841
// Build out the list of prepared pages
3942
return englishPages
4043
.concat(localizedPages)
4144
.filter(Boolean)
42-
.map(opts => new Page(opts))
4345
}
4446

4547
function createMapFromArray (pageList) {
@@ -58,8 +60,8 @@ function createMapFromArray (pageList) {
5860
return pageMap
5961
}
6062

61-
function loadPageMap (pageList) {
62-
const pages = pageList || loadPageList()
63+
async function loadPageMap (pageList) {
64+
const pages = pageList || await loadPageList()
6365
return createMapFromArray(pages)
6466
}
6567

lib/read-frontmatter.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const fs = require('fs')
2+
const fm = require('./frontmatter')
3+
4+
const endLine = '\n---\n'
5+
6+
/**
7+
* Reads the given filepath, but only up until `endLine`, using streams to
8+
* read each chunk and close the stream early.
9+
*/
10+
async function readFrontmatter (filepath) {
11+
const readStream = fs.createReadStream(filepath, { encoding: 'utf8', emitClose: true })
12+
return new Promise((resolve, reject) => {
13+
let frontmatter = ''
14+
readStream
15+
.on('data', function (chunk) {
16+
const endOfFrontmatterIndex = chunk.indexOf(endLine)
17+
if (endOfFrontmatterIndex !== -1) {
18+
frontmatter += chunk.slice(0, endOfFrontmatterIndex + endLine.length)
19+
// Stop early!
20+
readStream.destroy()
21+
} else {
22+
frontmatter += chunk
23+
}
24+
})
25+
.on('error', (error) => reject(error))
26+
// Stream has been destroyed and file has been closed
27+
.on('close', () => resolve(frontmatter))
28+
})
29+
}
30+
31+
/**
32+
* Read only the frontmatter from a file
33+
*/
34+
module.exports = async function fmfromf (filepath, languageCode) {
35+
let fileContent = filepath.endsWith('index.md')
36+
// For index files, we need to read the whole file because they contain ToC info
37+
? await fs.promises.readFile(filepath, 'utf8')
38+
// For everything else, only read the frontmatter
39+
: await readFrontmatter(filepath)
40+
41+
// TODO remove this when crowdin-support issue 66 has been resolved
42+
if (languageCode !== 'en' && fileContent.includes(': verdadero')) {
43+
fileContent = fileContent.replace(': verdadero', ': true')
44+
}
45+
46+
return fm(fileContent, { filepath })
47+
}

lib/warm-server.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@ async function warmServer () {
4040
}
4141

4242
if (!pageList) {
43-
pageList = dog.loadPages()
43+
pageList = await dog.loadPages()
4444
}
4545

4646
if (!site) {
4747
site = dog.loadSiteData()
4848
}
4949

5050
if (!pageMap) {
51-
pageMap = dog.loadPageMap(pageList)
51+
pageMap = await dog.loadPageMap(pageList)
5252
}
5353

5454
if (!redirects) {

tests/unit/page.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ describe('Page class', () => {
430430
})
431431

432432
describe('catches errors thrown in Page class', () => {
433-
test('frontmatter parsing error', () => {
433+
test('frontmatter parsing error', async () => {
434434
async function getPage () {
435435
return await Page.init({
436436
relativePath: 'page-with-frontmatter-error.md',
@@ -439,10 +439,10 @@ describe('catches errors thrown in Page class', () => {
439439
})
440440
}
441441

442-
expect(getPage).rejects.toThrowError('invalid frontmatter entry')
442+
await expect(getPage).rejects.toThrowError('invalid frontmatter entry')
443443
})
444444

445-
test('missing versions frontmatter', () => {
445+
test('missing versions frontmatter', async () => {
446446
async function getPage () {
447447
return await Page.init({
448448
relativePath: 'page-with-missing-product-versions.md',
@@ -451,10 +451,10 @@ describe('catches errors thrown in Page class', () => {
451451
})
452452
}
453453

454-
expect(getPage).rejects.toThrowError('versions')
454+
await expect(getPage).rejects.toThrowError('versions')
455455
})
456456

457-
test('English page with a version in frontmatter that its parent product is not available in', () => {
457+
test('English page with a version in frontmatter that its parent product is not available in', async () => {
458458
async function getPage () {
459459
return await Page.init({
460460
relativePath: 'admin/some-category/some-article-with-mismatched-versions-frontmatter.md',
@@ -466,7 +466,7 @@ describe('catches errors thrown in Page class', () => {
466466
expect(getPage).rejects.toThrowError(/`versions` frontmatter.*? product is not available in/)
467467
})
468468

469-
test('non-English page with a version in frontmatter that its parent product is not available in', () => {
469+
test('non-English page with a version in frontmatter that its parent product is not available in', async () => {
470470
async function getPage () {
471471
return await Page.init({
472472
relativePath: 'admin/some-category/some-article-with-mismatched-versions-frontmatter.md',
@@ -475,6 +475,6 @@ describe('catches errors thrown in Page class', () => {
475475
})
476476
}
477477

478-
expect(getPage).rejects.toThrowError(/`versions` frontmatter.*? product is not available in/)
478+
await expect(getPage).rejects.toThrowError(/`versions` frontmatter.*? product is not available in/)
479479
})
480480
})

0 commit comments

Comments
 (0)