diff --git a/lib/get-mini-toc-items.js b/lib/get-mini-toc-items.js index 73152536e6fd..d59be2de6042 100644 --- a/lib/get-mini-toc-items.js +++ b/lib/get-mini-toc-items.js @@ -35,6 +35,14 @@ export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope // Capture the anchor tag nested within the header, get its href and remove it const anchor = $('a.doctocat-link', item) const href = anchor.attr('href') + if (!href) { + // Can happen if the, for example, `

` tag was put there + // manually with HTML into the Markdown content. Then it wouldn't + // be rendered with an expected ` tags but leave content @@ -52,6 +60,7 @@ export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope return { contents, headingLevel, platform } }) + .filter(Boolean) .map((item) => { // set the indentation level for each item based on the most important // heading level in the current article diff --git a/script/content-migrations/add-early-access-tocs.js b/script/content-migrations/add-early-access-tocs.js deleted file mode 100755 index 40a96b42433b..000000000000 --- a/script/content-migrations/add-early-access-tocs.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node -import fs from 'fs' -import path from 'path' -import readFrontmatter from '../../lib/read-frontmatter.js' -import { sentenceCase } from 'change-case' - -const earlyAccessDir = path.posix.join(process.cwd(), 'content', 'early-access') - -updateOrCreateToc(earlyAccessDir) - -console.log('Updated Early Access TOCs!') - -function updateOrCreateToc(directory) { - const children = fs.readdirSync(directory).filter((subpath) => !subpath.endsWith('index.md')) - - if (!children.length) return - - const tocFile = path.posix.join(directory, 'index.md') - - let content, data - - if (fs.existsSync(tocFile)) { - const matter = readFrontmatter(fs.readFileSync(tocFile, 'utf8')) - content = matter.content - data = matter.data - } else { - content = '' - data = { - title: sentenceCase(path.basename(directory)), - versions: '*', - hidden: true, - } - } - - data.children = children.map((child) => `/${child.replace('.md', '')}`) - const newContents = readFrontmatter.stringify(content, data, { lineWidth: 10000 }) - fs.writeFileSync(tocFile, newContents) - - children.forEach((child) => { - if (child.endsWith('.md')) return - updateOrCreateToc(path.posix.join(directory, child)) - }) -} diff --git a/script/update-tocs.js b/script/update-tocs.js new file mode 100755 index 000000000000..ddf757f3acc9 --- /dev/null +++ b/script/update-tocs.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +// [start-readme] +// +// This script creates or updates an index.md file for a given directory. +// It will add `children` frontmatter in alphabetical order and create versions: '*'. +// It also prints a helpful message to update those values manually if needed. +// +// [end-readme] + +import fs from 'fs' +import path from 'path' +import { sentenceCase } from 'change-case' +import { program } from 'commander' +import readFrontmatter from '../lib/read-frontmatter.js' + +program + .description('Create or update an index.md file for a provided content directory') + .requiredOption('-d, --directory ') + .parse(process.argv) + +const directory = path.posix.join(process.cwd(), program.opts().directory) + +if (!fs.existsSync(directory)) { + console.error(`Error! ${directory} not found. Make sure directory name starts with "content/".`) + process.exit(1) +} + +// Run it! This function may run recursively. +updateOrCreateToc(directory) + +console.log( + 'Done! Review the new or updated index.md files and update the 1) order of the children 2) versions as needed' +) + +function updateOrCreateToc(directory) { + const children = fs.readdirSync(directory).filter((subpath) => !subpath.endsWith('index.md')) + if (!children.length) return + + const tocFile = path.posix.join(directory, 'index.md') + + let content, data + + // If the index.md file already exists, read it (to be updated later). + if (fs.existsSync(tocFile)) { + const parsed = readFrontmatter(fs.readFileSync(tocFile, 'utf8')) + content = parsed.content + data = parsed.data + } + // If the index.md file does not exist, create it. + else { + content = '' + data = { + title: sentenceCase(path.basename(directory)), // fake the title of the index.md from the directory name + versions: '*', // default to all versions + } + } + + // Add the children - this will default to the alphabetical list of files in the directory. + data.children = children.map((child) => `/${child.replace('.md', '')}`) + + // Write the file. + const newContents = readFrontmatter.stringify(content, data, { lineWidth: 10000 }) + fs.writeFileSync(tocFile, newContents) + + // Process any child directories recursively. + children.forEach((child) => { + if (child.endsWith('.md')) return + updateOrCreateToc(path.posix.join(directory, child)) + }) +} diff --git a/tests/unit/mini-toc-items.js b/tests/unit/mini-toc-items.js index 5344c773772e..fe4c73bbbcdf 100644 --- a/tests/unit/mini-toc-items.js +++ b/tests/unit/mini-toc-items.js @@ -1,10 +1,24 @@ import { expect } from '@jest/globals' import getMiniTocItems from '../../lib/get-mini-toc-items' +// The getMiniTocItems() function requires that every

and

+// contains a... +// +// +// +// tag within the tag. Having to manually put that into every HTML +// snippet in each test is tediuous so this function makes it convenient. +function injectDoctocatLinks(html) { + let counter = 0 + return html.replace(//g, (m) => { + return `${m}\n🔗\n` + }) +} + describe('mini toc items', () => { // Mock scenario from: /en/rest/reference/activity test('basic nested structure is created', async () => { - const html = ` + const html = injectDoctocatLinks(`

Test

Section 1

@@ -14,8 +28,9 @@ describe('mini toc items', () => {

Section 2

Section 2 A

- ` + `) const tocItems = getMiniTocItems(html, 3) + expect(tocItems.length).toBe(2) expect(tocItems[0].items.length).toBe(3) }) @@ -31,7 +46,7 @@ describe('mini toc items', () => { * 3 */ test('creates toc that starts with lower importance headers', async () => { - const html = ` + const html = injectDoctocatLinks(`

Test

Section 1 A

Section 1 B

@@ -39,7 +54,7 @@ describe('mini toc items', () => {

Section 2 A

Section 3

Section 3 A

- ` + `) const tocItems = getMiniTocItems(html, 3) expect(tocItems.length).toBe(4) expect(tocItems[3].items.length).toBe(1) @@ -56,18 +71,18 @@ describe('mini toc items', () => { // Mock scenario from: /en/repositories/creating-and-managing-repositories/about-repositories test('creates flat toc', async () => { - const html = ` + const html = injectDoctocatLinks(`

Test

Section 1

Section 2

- ` + `) const tocItems = getMiniTocItems(html, 3) expect(tocItems.length).toBe(2) expect(tocItems[0].items).toBeUndefined() }) test('handles deeply nested toc', async () => { - const html = ` + const html = injectDoctocatLinks(`

Test

Section 1

Section 2

@@ -75,7 +90,7 @@ describe('mini toc items', () => {

Section 2 A 1

Section 2 A 1 a

Section 3

- ` + `) const tocItems = getMiniTocItems(html, 5) expect(tocItems.length).toBe(3) expect(tocItems[1].items[0].items[0].items.length).toBe(1)