diff --git a/.gitignore b/.gitignore index 8b780e4e2e..7a5746c1df 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ coverage/ /assets/images/early-access /content/early-access /data/early-access +/script/dev-toc/static .next .eslintcache *.tsbuildinfo diff --git a/script/dev-toc/generate.js b/script/dev-toc/generate.js new file mode 100755 index 0000000000..3f27157680 --- /dev/null +++ b/script/dev-toc/generate.js @@ -0,0 +1,72 @@ +#!/usr/bin/env node + +import fs from 'fs' +import path from 'path' +import { execSync } from 'child_process' +import { program } from 'commander' +import fpt from '../../lib/non-enterprise-default-version.js' +import { allVersionKeys } from '../../lib/all-versions.js' +import { liquid } from '../../lib/render-content/index.js' +import contextualize from '../../middleware/context.js' + +const layoutFilename = path.posix.join(process.cwd(), 'script/dev-toc/layout.html') +const layout = fs.readFileSync(layoutFilename, 'utf8') + +const staticDirName = 'script/dev-toc/static' +const staticDir = path.posix.join(process.cwd(), staticDirName) +if (!fs.existsSync(staticDir)) fs.mkdirSync(staticDir) + +program + .description('Generate a local TOC of the docs website and open it in your browser') + .option( + '-o, --openSections [product-ids...]', + 'open sections for one or more product IDs by default (e.g., "-o codespaces pull-requests")' + ) + .parse(process.argv) + +const options = program.opts() + +const openSections = options.openSections || '' +const defaultOpenSections = Array.isArray(openSections) ? openSections : [openSections] + +main() + +async function main() { + const next = () => {} + const res = {} + const req = { language: 'en', cookies: {} } + + for (const version of allVersionKeys) { + req.pagePath = version === fpt ? '/' : `/${version}` + + // Create a subdir for the version if one doesn't exist yet. + const versionStaticDir = path.posix.join(staticDir, version) + if (!fs.existsSync(versionStaticDir)) fs.mkdirSync(versionStaticDir) + + // Create a versioned filename. + const filename = path.posix.join(versionStaticDir, 'index.html') + + // Create a minimal context object. + await contextualize(req, res, next) + + // Add the tree to the req.context. + req.context.currentEnglishTree = req.context.siteTree.en[req.context.currentVersion] + + // Add any defaultOpenSections to the context. + req.context.defaultOpenSections = defaultOpenSections + + // Parse the layout in script/dev-toc/layout.html with the context we created above. + const outputHtml = await liquid.parseAndRender(layout, Object.assign({}, req.context)) + + // Write a static file for each version. + fs.writeFileSync(filename, outputHtml) + } + + // Default to FPT for the file to open. + const fptFile = path.posix.join(staticDirName, fpt, 'index.html') + + execSync(`open ${fptFile}`) + + console.log(`\nCreated the TOC! If it doesn't open automatically, open the following file in your browser to view it:\n +${fptFile}`) +} diff --git a/script/dev-toc/index.js b/script/dev-toc/index.js new file mode 100644 index 0000000000..2502cf21b3 --- /dev/null +++ b/script/dev-toc/index.js @@ -0,0 +1,41 @@ +const expandText = 'Expand All' +const closeText = 'Close All' + +document.addEventListener('DOMContentLoaded', async () => { + devToc() +}) + +function devToc() { + const expandButton = document.querySelector('.js-expand') + if (!expandButton) return + + const detailsElements = document.querySelectorAll('details') + + expandButton.addEventListener('click', () => { + // on click, toggle all the details elements open or closed + const anyDetailsOpen = Array.from(detailsElements).find((details) => details.open) + + for (const detailsElement of detailsElements) { + anyDetailsOpen ? detailsElement.removeAttribute('open') : (detailsElement.open = true) + } + + // toggle the button text on click + anyDetailsOpen + ? (expandButton.textContent = expandText) + : (expandButton.textContent = closeText) + }) + + // also toggle the button text on clicking any of the details elements + for (const detailsElement of detailsElements) { + detailsElement.addEventListener('click', () => { + expandButton.textContent = closeText + + // we can only get an accurate count of the open details elements if we wait a fraction after click + setTimeout(() => { + if (!Array.from(detailsElements).find((details) => details.open)) { + expandButton.textContent = expandText + } + }, 50) + }) + } +} diff --git a/script/dev-toc/layout.html b/script/dev-toc/layout.html new file mode 100644 index 0000000000..0aa79e1971 --- /dev/null +++ b/script/dev-toc/layout.html @@ -0,0 +1,67 @@ + + + + +Docs TOC + + + + +
+ +

Versions

+ + +{% assign docsRoot = "https://docs.github.com" %} + +{% if allVersions[currentVersion] %} +

TOC for {{ allVersions[currentVersion].versionTitle }}

+ + +
+ +{% for productPage in currentEnglishTree.childPages %} +{% assign productId = productPage.page.relativePath | replace: "/index.md", "" %} +{% if defaultOpenSections contains productId %} +
{{productPage.renderedFullTitle}} +{% else %} +
{{productPage.renderedFullTitle}} +{% endif %} + +
+{% endfor %} +{% endif %} + + +
+ +