Skip to content

Commit 9f24923

Browse files
authored
Merge pull request #18483 from github/script-to-move-toc-links-into-frontmatter
Script to move TOC links into children frontmatter
2 parents 071317d + edd9343 commit 9f24923

3 files changed

Lines changed: 144 additions & 15 deletions

File tree

lib/get-document-type.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module.exports = function getDocumentType (relativePath) {
2+
if (!relativePath.endsWith('index.md')) {
3+
return 'article'
4+
}
5+
6+
// Derive the document type from the path segment length
7+
switch (relativePath.split('/').length) {
8+
case 1:
9+
return 'homepage'
10+
case 2:
11+
return 'product'
12+
case 3:
13+
return 'category'
14+
case 4:
15+
return 'mapTopic'
16+
}
17+
}

script/content-migrations/remove-map-topics.js

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,31 @@
33
const fs = require('fs')
44
const path = require('path')
55
const walk = require('walk-sync')
6+
const languages = require('../../lib/languages')
7+
const frontmatter = require('../../lib/read-frontmatter')
8+
const addRedirectToFrontmatter = require('../../lib/redirects/add-redirect-to-frontmatter')
69

710
const relativeRefRegex = /\/[a-zA-Z0-9-]+/g
811
const linkString = /{% [^}]*?link.*? \/(.*?) ?%}/m
912
const linksArray = new RegExp(linkString.source, 'gm')
1013

11-
const fullDirectoryPath = path.join(process.cwd(), '/content')
12-
const files = walk(fullDirectoryPath, {
14+
const walkOpts = {
1315
includeBasePath: true,
1416
directories: false
15-
})
17+
}
18+
19+
// We only want category TOC files, not product TOCs.
20+
const categoryFileRegex = /content\/[^/]+?\/[^/]+?\/index.md/
1621

17-
files.forEach(file => {
18-
if (path.basename(file) !== 'index.md') return
22+
const fullDirectoryPaths = Object.values(languages).map(langObj => path.join(process.cwd(), langObj.dir, 'content'))
23+
const categoryIndexFiles = fullDirectoryPaths.map(fullDirectoryPath => walk(fullDirectoryPath, walkOpts)).flat()
24+
.filter(file => categoryFileRegex.test(file))
25+
26+
categoryIndexFiles.forEach(categoryIndexFile => {
27+
let categoryIndexContent = fs.readFileSync(categoryIndexFile, 'utf8')
1928

20-
let fileContent = fs.readFileSync(file, 'utf-8')
2129
// find array of TOC link strings
22-
const rawItems = fileContent.match(linksArray)
30+
const rawItems = categoryIndexContent.match(linksArray)
2331
if (!rawItems || !rawItems[0].includes('topic_link_in_list')) return
2432

2533
const pageToc = {}
@@ -37,24 +45,53 @@ files.forEach(file => {
3745
pageToc[currentTopic] = tmpArray
3846
}
3947
})
48+
4049
for (const topic in pageToc) {
41-
const oldTopicDirectory = path.dirname(file)
50+
const oldTopicDirectory = path.dirname(categoryIndexFile)
4251
const newTopicDirectory = path.join(oldTopicDirectory, topic)
43-
const topicFile = path.join(oldTopicDirectory, `${topic}.md`)
52+
const oldTopicFile = path.join(oldTopicDirectory, `${topic}.md`)
53+
54+
// Some translated category TOCs may be outdated and contain incorrect links
55+
if (!fs.existsSync(oldTopicFile)) continue
4456

4557
if (!fs.existsSync(newTopicDirectory)) fs.mkdirSync(newTopicDirectory)
4658

47-
let topicContent = fs.readFileSync(topicFile, 'utf-8')
48-
topicContent = topicContent.replace('mapTopic: true\n', '')
59+
const { data, content } = frontmatter(fs.readFileSync(oldTopicFile, 'utf8'))
60+
delete data.mapTopic
61+
62+
let topicContent = content
4963

5064
const articles = pageToc[topic]
5165

5266
articles.forEach(article => {
53-
fs.renameSync(`${oldTopicDirectory}/${article}.md`, `${newTopicDirectory}/${article}.md`)
67+
// Update the new map topic index file content
5468
topicContent = topicContent + `{% link_with_intro /${article} %}\n`
55-
fileContent = fileContent.replace(`/{% link_in_list /${article}`, `/{% link_in_list /${newTopicDirectory}/${article}`)
69+
70+
// Update the category index file content
71+
categoryIndexContent = categoryIndexContent.replace(`{% link_in_list /${article}`, `{% link_in_list /${topic}/${article}`)
72+
73+
// Early return if the article doesn't exist (some translated category TOCs may be outdated and contain incorrect links)
74+
if (!fs.existsSync(`${oldTopicDirectory}/${article}.md`)) return
75+
76+
// Move the file under the new map topic directory
77+
const newArticlePath = `${newTopicDirectory}/${article}.md`
78+
fs.renameSync(`${oldTopicDirectory}/${article}.md`, newArticlePath)
79+
80+
// Read the article file so we can add a redirect from its old path
81+
const articleContents = frontmatter(fs.readFileSync(newArticlePath, 'utf8'))
82+
addRedirectToFrontmatter(articleContents.data.redirect_from, `${oldTopicDirectory}/${article}`)
83+
84+
// Write the article with updated frontmatter
85+
fs.writeFileSync(newArticlePath, frontmatter.stringify(articleContents.content.trim(), articleContents.data, { lineWidth: 10000 }))
5686
})
57-
fs.writeFileSync(`${newTopicDirectory}/index.md`, topicContent)
58-
fs.unlinkSync(topicFile)
87+
88+
// Write the map topic index file
89+
fs.writeFileSync(`${newTopicDirectory}/index.md`, frontmatter.stringify(topicContent.trim(), data, { lineWidth: 10000 }))
90+
91+
// Write the category index file
92+
fs.writeFileSync(categoryIndexFile, categoryIndexContent)
93+
94+
// Delete the old map topic
95+
fs.unlinkSync(oldTopicFile)
5996
}
6097
})
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env node
2+
3+
const fs = require('fs')
4+
const path = require('path')
5+
const walk = require('walk-sync')
6+
const yaml = require('js-yaml')
7+
const frontmatter = require('../../lib/read-frontmatter')
8+
const getDocumentType = require('../../lib/get-document-type')
9+
const languages = require('../../lib/languages')
10+
11+
const linkString = /{% [^}]*?link.*? (\/.*?) ?%}/m
12+
const linksArray = new RegExp(linkString.source, 'gm')
13+
14+
// The product order is determined by data/products.yml
15+
const productsFile = path.join(process.cwd(), 'data/products.yml')
16+
const productsYml = yaml.load(fs.readFileSync(productsFile, 'utf8'))
17+
const sortedProductIds = productsYml.productsInOrder.concat('early-access')
18+
19+
// This script turns `{% link /<link> %} style content into children: [ -/<link> ] frontmatter arrays.
20+
//
21+
// It MUST be run after script/content-migrations/remove-map-topics.js.
22+
//
23+
// NOTE: The results won't work with the TOC handling currently in production, so the results must NOT
24+
// be committed until the updated handling is in place.
25+
26+
const walkOpts = {
27+
includeBasePath: true,
28+
directories: false
29+
}
30+
31+
const fullDirectoryPaths = Object.values(languages).map(langObj => path.join(process.cwd(), langObj.dir, 'content'))
32+
const indexFiles = fullDirectoryPaths.map(fullDirectoryPath => walk(fullDirectoryPath, walkOpts)).flat()
33+
.filter(file => file.endsWith('index.md'))
34+
35+
indexFiles
36+
.forEach(indexFile => {
37+
const relativePath = indexFile.replace(/^.+\/content\//, '')
38+
const documentType = getDocumentType(relativePath)
39+
40+
const { data, content } = frontmatter(fs.readFileSync(indexFile, 'utf8'))
41+
42+
if (documentType === 'homepage') {
43+
data.children = sortedProductIds
44+
}
45+
46+
const linkItems = content.match(linksArray) || []
47+
48+
// Turn the `{% link /<link> %}` list into an array of /<link> items
49+
if (documentType === 'product' || documentType === 'mapTopic') {
50+
data.children = getLinks(linkItems)
51+
}
52+
53+
if (documentType === 'category') {
54+
const childMapTopics = linkItems.filter(item => item.includes('topic_'))
55+
56+
data.children = childMapTopics.length ? getLinks(childMapTopics) : getLinks(linkItems)
57+
}
58+
59+
// Fix this one weird file
60+
if (relativePath === 'discussions/guides/index.md') {
61+
data.children = [
62+
'/best-practices-for-community-conversations-on-github',
63+
'/finding-discussions-across-multiple-repositories',
64+
'/granting-higher-permissions-to-top-contributors'
65+
]
66+
}
67+
68+
// Index files should no longer have body content, so we write an empty string
69+
fs.writeFileSync(indexFile, frontmatter.stringify('', data, { lineWidth: 10000 }))
70+
})
71+
72+
function getLinks (linkItemArray) {
73+
// do a oneoff replacement while mapping
74+
return linkItemArray.map(item => item.match(linkString)[1].replace('/discussions-guides', '/guides'))
75+
}

0 commit comments

Comments
 (0)