Merge branch 'main' into toggle-images
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"FEATURE_TEST_TRUE": true,
|
||||
"FEATURE_TEST_FALSE": false
|
||||
"FEATURE_TEST_FALSE": false,
|
||||
"FEATURE_NEW_SITETREE": false
|
||||
}
|
||||
|
||||
@@ -68,6 +68,14 @@
|
||||
{% include featured-links %}
|
||||
{% endif %}
|
||||
{{ renderedPage }}
|
||||
|
||||
{% if tocItems and tocItems.length %}
|
||||
{% if page.documentType == "category" or page.relativePath == "github/index.md" %}
|
||||
{% include generic-toc-list %}
|
||||
{% else %}
|
||||
{% include generic-toc-items %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
{% if FEATURE_NEW_SITETREE %}
|
||||
<nav class="breadcrumbs f5" aria-label="Breadcrumb">
|
||||
{% for breadcrumb in breadcrumbs %}
|
||||
{% if breadcrumb.href == '' %}
|
||||
<span title="{{ breadcrumb.documentType }}: {{ breadcrumb.title }}">{{ breadcrumb.title }}</span>
|
||||
{% else %}
|
||||
<a title="{{ breadcrumb.documentType }}: {{ breadcrumb.title }}" href="{{ breadcrumb.href }}" class="d-inline-block {% if breadcrumb.href == currentPath %}text-gray-light{% endif %}">
|
||||
{{ breadcrumb.title }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</nav>
|
||||
{% else %}
|
||||
<nav class="breadcrumbs f5" aria-label="Breadcrumb">
|
||||
{% for breadcrumb in breadcrumbs %}
|
||||
{% if breadcrumb[1].href == '' %}
|
||||
@@ -9,3 +22,4 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
34
includes/category-articles-list.html
Normal file
34
includes/category-articles-list.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% for categoryPage in currentProductTree.childPages %}
|
||||
{% if categoryPage.href == currentPath %}{% assign currentCategory = categoryPage %}{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if currentCategory.page.shortTitle and currentCategory.page.shortTitle != '' %}{% assign currentCategoryTitle = currentCategory.page.shortTitle %}{% else %}{% assign currentCategoryTitle = currentCategory.page.title %}{% endif %}
|
||||
|
||||
{% assign maxArticles = 10 %}
|
||||
|
||||
<div class="py-6 all-articles-list">
|
||||
<h2 class="font-mktg mb-4">{{ currentCategoryTitle }} docs</h2>
|
||||
|
||||
<div class="d-flex gutter flex-wrap">
|
||||
{% for childPage in currentCategory.childPages %}
|
||||
{% unless childPage.page.hidden %}
|
||||
<div class="col-12 col-lg-4 mb-6 height-full">
|
||||
<h4 class="mb-3"><a href="{{ childPage.href }}">{{ childPage.page.title }}</a></h4>
|
||||
<ul class="list-style-none">
|
||||
{% for grandchildPage in childPage.childPages %}
|
||||
<li class="mb-3 {% if forloop.index > maxArticles %}d-none{% endif %}">
|
||||
<a href="{{ grandchildPage.href }}">
|
||||
{{ grandchildPage.page.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% assign numArticles = childPage.childPages | obj_size %}
|
||||
{% if numArticles > maxArticles %}
|
||||
<button class="js-all-articles-show-more btn-link Link--secondary">Show {{ numArticles | minus: maxArticles }} more {% octicon "chevron-up" class="v-align-text-bottom" %}</button>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endunless %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
12
includes/generic-toc-items.html
Normal file
12
includes/generic-toc-items.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% if tocItems %}
|
||||
|
||||
{% for tocItem in tocItems %}
|
||||
|
||||
{% assign title = tocItem.title %}
|
||||
{% assign fullPath = tocItem.fullPath %}
|
||||
{% assign intro = tocItem.intro %}
|
||||
{% include liquid-tags/link-with-intro %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
24
includes/generic-toc-list.html
Normal file
24
includes/generic-toc-list.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% if tocItems %}
|
||||
<ul>
|
||||
{% for tocItem in tocItems %}
|
||||
|
||||
{% assign title = tocItem.title %}
|
||||
{% assign fullPath = tocItem.fullPath %}
|
||||
{% assign intro = tocItem.intro %}
|
||||
|
||||
<li>{% include liquid-tags/link %}
|
||||
{% if tocItem.childTocItems %}
|
||||
{% unless page.relativePath == "github/index.md" %}
|
||||
<ul>
|
||||
{% for childItem in tocItem.childTocItems %}
|
||||
{% assign title = childItem.title %}
|
||||
{% assign fullPath = childItem.fullPath %}
|
||||
<li>{% include liquid-tags/link %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endunless %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
46
includes/product-articles-list.html
Normal file
46
includes/product-articles-list.html
Normal file
@@ -0,0 +1,46 @@
|
||||
{% assign maxArticles = 10 %}
|
||||
|
||||
{% if currentProductTree.page.shortTitle and currentProductTree.page.shortTitle != '' %}{% assign productTitle = currentProductTree.page.shortTitle %}{% else %}{% assign productTitle = currentProductTree.page.title %}{% endif %}
|
||||
|
||||
<div class="py-6 all-articles-list">
|
||||
<h2 class="font-mktg mb-4">All {{ productTitle }} docs</h2>
|
||||
|
||||
<div class="d-flex gutter flex-wrap">
|
||||
{% for childPage in currentProductTree.childPages %}
|
||||
{% if childPage.page.documentType == "article" %}{% assign standaloneCategory = true %}{% else %}{% assign standaloneCategory = false %}{% endif %}
|
||||
{% unless standaloneCategory %}
|
||||
<div class="col-12 col-lg-4 mb-6 height-full">
|
||||
<h4 class="mb-3"><a href="{{ childPage.href }}">{{ childPage.page.title }}</a></h4>
|
||||
|
||||
{% if childPage.childPages and childPage.childPages[0].page.documentType == "mapTopic" %}
|
||||
<ul class="list-style-none">
|
||||
{% for grandchildPage in childPage.childPages %}
|
||||
{% unless grandchildPage.page.hidden %}
|
||||
{% assign numArticles = childPage.childPages | obj_size %}
|
||||
<li class="mb-3 {% if forloop.index > maxArticles %}d-none{% endif %}">
|
||||
<a href="{{ grandchildPage.href }}">
|
||||
{{ grandchildPage.page.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% if numArticles > maxArticles %}
|
||||
<button class="js-all-articles-show-more btn-link Link--secondary">Show {{ numArticles | minus: maxArticles }} more {% octicon "chevron-up" class="v-align-text-bottom" %}</button>
|
||||
{% endif %}
|
||||
{% endunless %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<ul class="list-style-none">
|
||||
{% assign numArticles = childPage.childPages | obj_size %}
|
||||
{% for grandchildPage in childPage.childPages %}
|
||||
<li class="mb-3 {% if forloop.index > maxArticles %}d-none{% endif %}"><a href="{{ grandchildPage.href }}">{{ grandchildPage.page.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if numArticles > maxArticles %}
|
||||
<button class="js-all-articles-show-more btn-link Link--secondary">Show {{ numArticles | minus: maxArticles }} more {% octicon "chevron-up" class="v-align-text-bottom" %}</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endunless %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
74
includes/sidebar-product.html
Normal file
74
includes/sidebar-product.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!--
|
||||
Styling note:
|
||||
|
||||
Categories, Maptopics, and Articles list items get a class of `active` when they correspond to content
|
||||
hierarchy of the current page. If an item's URL is also the same as the current URL, the item
|
||||
also gets an `is-current-page` class.
|
||||
-->
|
||||
|
||||
{% include all-products-link %}
|
||||
|
||||
{% unless currentProductTree.page.hidden %}
|
||||
|
||||
{% if currentProductTree.renderedShortTitle %}{% assign productTitle = currentProductTree.renderedShortTitle %}{% else %}{% assign productTitle = currentProductTree.renderedFullTitle %}{% endif %}
|
||||
|
||||
<li title="" class="sidebar-product mb-2">
|
||||
<a href="{{currentProductTree.href}}" class="pl-4 pr-5 pb-1 f4">{{ productTitle }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<ul class="sidebar-categories list-style-none">
|
||||
{% for childPage in currentProductTree.childPages %}
|
||||
{% if childPage.page.documentType == "article" %}{% assign standaloneCategory = true %}{% else %}{% assign standaloneCategory = false %}{% endif %}
|
||||
<li class="sidebar-category py-1 {% if currentPath contains childPage.href %}active {% if currentPath == childPage.href %}is-current-page {% endif %}{% endif %}{% if standaloneCategory %}standalone-category{% endif %}">
|
||||
{% if childPage.renderedShortTitle %}{% assign childTitle = childPage.renderedShortTitle %}{% else %}{% assign childTitle = childPage.renderedFullTitle %}{% endif %}
|
||||
{% if standaloneCategory %}
|
||||
<a href="{{childPage.href}}" class="pl-4 pr-2 py-2 f6 text-uppercase d-block flex-auto mr-3">{{ childTitle }}</a>
|
||||
{% else %}
|
||||
<details class="dropdown-withArrow details details-reset" {% if currentPath contains childPage.href or forloop.index < 4 %}open{% endif %}>
|
||||
<summary>
|
||||
<div class="d-flex flex-justify-between">
|
||||
<a href="{{childPage.href}}" class="pl-4 pr-2 py-2 f6 text-uppercase d-block flex-auto mr-3">{{ childTitle }}</a>
|
||||
{% if forloop.index < 4 %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="octicon flex-shrink-0 arrow mr-3" style="margin-top:7px" viewBox="0 0 16 16" width="16" height="16"> <path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></svg>
|
||||
{% endif %}
|
||||
</div>
|
||||
</summary>
|
||||
{% endif %}
|
||||
<!-- some categories have maptopics with child articles -->
|
||||
{% if currentPath contains childPage.href or forloop.index < 4 %}
|
||||
{% if childPage.childPages[0].page.documentType == "mapTopic" %}
|
||||
<ul class="sidebar-topics list-style-none position-relative">
|
||||
{% for grandchildPage in childPage.childPages %}
|
||||
{% if grandchildPage.renderedShortTitle %}{% assign grandchildTitle = grandchildPage.renderedShortTitle %}{% else %}{% assign grandchildTitle = grandchildPage.renderedFullTitle %}{% endif %}
|
||||
<li class="sidebar-maptopic {% if currentPath contains grandchildPage.href %}active {% if currentPath == grandchildPage.href %}is-current-page{% endif %}{% endif %}">
|
||||
<a href="{{grandchildPage.href}}" class="pl-4 pr-5 py-2">{{ grandchildTitle }} </a>
|
||||
<ul class="sidebar-articles my-2">
|
||||
{% for greatgrandchildPage in grandchildPage.childPages %}
|
||||
{% if greatgrandchildPage.renderedShortTitle %}{% assign greatgrandchildTitle = greatgrandchildPage.renderedShortTitle %}{% else %}{% assign greatgrandchildTitle = greatgrandchildPage.renderedFullTitle %}{% endif %}
|
||||
<li class="sidebar-article {% if currentPath contains greatgrandchildPage.href %}active {% if currentPath == greatgrandchildPage.href %}is-current-page{% endif %}{% endif %}">
|
||||
<a href="{{greatgrandchildPage.href}}" class="pl-6 pr-5 py-1{% if forloop.last %} pb-2{% endif %}">{{ greatgrandchildTitle }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- some categories have no maptopics, only articles -->
|
||||
{% elsif childPage.childPages[0].page.documentType == "article" %}
|
||||
<ul class="sidebar-articles list-style-none">
|
||||
{% for grandchildPage in childPage.childPages %}
|
||||
{% if grandchildPage.renderedShortTitle %}{% assign grandchildTitle = grandchildPage.renderedShortTitle %}{% else %}{% assign grandchildTitle = grandchildPage.renderedFullTitle %}{% endif %}
|
||||
<li class="sidebar-article {% if currentPath contains grandchildPage.href %}active {% if currentPath == grandchildPage.href %}is-current-page{% endif %}{% endif %}">
|
||||
<a href="{{grandchildPage.href}}" class="pl-4 pr-5 py-1{% if forloop.last %} pb-2{% endif %}">{{ grandchildTitle }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</details>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{% endunless %}
|
||||
@@ -15,7 +15,11 @@
|
||||
</ul>
|
||||
{% else %}
|
||||
<ul class="sidebar-products">
|
||||
{% if FEATURE_NEW_SITETREE %}
|
||||
{% include sidebar-product %}
|
||||
{% else %}
|
||||
{% include sidebar-specific-product %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
@@ -150,11 +150,22 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="container-xl px-3 px-md-6 mt-6">
|
||||
{% if FEATURE_NEW_SITETREE %}
|
||||
{% if page.documentType == "category" %}
|
||||
{% include category-articles-list %}
|
||||
{% endif %}
|
||||
{% if page.documentType == "product" %}
|
||||
{% include product-articles-list %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% unless FEATURE_NEW_SITETREE %}
|
||||
{% if currentCategory %}
|
||||
{% include all-articles-category %}
|
||||
{% else %}
|
||||
{% include all-articles-product %}
|
||||
{% endif %}
|
||||
{% endunless %}
|
||||
</div>
|
||||
|
||||
{% include support-section %}
|
||||
|
||||
@@ -20,7 +20,7 @@ module.exports = async function createTree (originalPath, langObj) {
|
||||
const localizedBasePath = path.posix.join(__dirname, '..', langObj.dir, 'content')
|
||||
|
||||
// Initialize the Page! This is where the file reads happen.
|
||||
let page = await Page.init({
|
||||
const page = await Page.init({
|
||||
basePath: localizedBasePath,
|
||||
relativePath,
|
||||
languageCode: langObj.code
|
||||
@@ -29,17 +29,10 @@ module.exports = async function createTree (originalPath, langObj) {
|
||||
if (!page) {
|
||||
// Do not throw an error if Early Access is not available.
|
||||
if (relativePath.startsWith('early-access')) return
|
||||
// If a translated path doesn't exist, fall back to the English so there is parity between
|
||||
// the English tree and the translated trees.
|
||||
if (langObj.code !== 'en') {
|
||||
page = await Page.init({
|
||||
basePath: basePath,
|
||||
relativePath,
|
||||
languageCode: langObj.code
|
||||
})
|
||||
}
|
||||
// Do not throw an error if translated page is not available.
|
||||
if (langObj.code !== 'en') return
|
||||
|
||||
if (!page) throw Error(`Cannot initialize page for ${filepath}`)
|
||||
throw Error(`Cannot initialize page for ${filepath} in ${langObj.code}`)
|
||||
}
|
||||
|
||||
// Create the root tree object on the first run, and create children recursively.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// add a new redirect string to redirect_from frontmatter
|
||||
|
||||
module.exports = function addRedirectToFrontmatter (redirectFromData, newRedirectString) {
|
||||
if (Array.isArray(redirectFromData)) {
|
||||
if (Array.isArray(redirectFromData) && !redirectFromData.includes(newRedirectString)) {
|
||||
redirectFromData.push(newRedirectString)
|
||||
} else if (typeof redirectFromData === 'string') {
|
||||
redirectFromData = [redirectFromData]
|
||||
|
||||
55
middleware/categories-for-support.js
Normal file
55
middleware/categories-for-support.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const path = require('path')
|
||||
const renderOpts = { textOnly: true, encodeEntities: true }
|
||||
|
||||
// This middleware exposes a list of all categories and child articles at /categories.json.
|
||||
// GitHub Support uses this for internal ZenDesk search functionality.
|
||||
module.exports = async function categoriesForSupport (req, res, next) {
|
||||
const englishSiteTree = req.context.siteTree.en
|
||||
|
||||
const allCategories = []
|
||||
|
||||
await Promise.all(Object.keys(englishSiteTree).map(async (version) => {
|
||||
await Promise.all(englishSiteTree[version].childPages.map(async (productPage) => {
|
||||
if (productPage.page.relativePath.startsWith('early-access')) return
|
||||
if (!productPage.childPages) return
|
||||
|
||||
await Promise.all(productPage.childPages.map(async (categoryPage) => {
|
||||
// We can't get the rendered titles from middleware/render-tree-titles
|
||||
// here because that middleware only runs on the current version, and this
|
||||
// middleware processes all versions.
|
||||
const name = categoryPage.page.title.includes('{')
|
||||
? await categoryPage.page.renderProp('title', req.context, renderOpts)
|
||||
: categoryPage.page.title
|
||||
|
||||
allCategories.push({
|
||||
name,
|
||||
published_articles: await findArticlesPerCategory(categoryPage, [], req.context)
|
||||
})
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
|
||||
return res.json(allCategories)
|
||||
}
|
||||
|
||||
async function findArticlesPerCategory (currentPage, articlesArray, context) {
|
||||
if (currentPage.page.documentType === 'article') {
|
||||
const title = currentPage.page.title.includes('{')
|
||||
? await currentPage.page.renderProp('title', context, renderOpts)
|
||||
: currentPage.page.title
|
||||
|
||||
articlesArray.push({
|
||||
title,
|
||||
slug: path.basename(currentPage.href)
|
||||
})
|
||||
}
|
||||
|
||||
if (!currentPage.childPages) return articlesArray
|
||||
|
||||
// Run recursively to find any articles deeper in the tree.
|
||||
await Promise.all(currentPage.childPages.map(async (childPage) => {
|
||||
await findArticlesPerCategory(childPage, articlesArray, context)
|
||||
}))
|
||||
|
||||
return articlesArray
|
||||
}
|
||||
@@ -10,7 +10,7 @@ const {
|
||||
getPathWithoutLanguage
|
||||
} = require('../lib/path-utils')
|
||||
const productNames = require('../lib/product-names')
|
||||
const warmServer = require('../lib/warm-server')
|
||||
const warmServer = process.env.FEATURE_NEW_SITETREE ? require('../lib/warm-server2') : require('../lib/warm-server')
|
||||
const featureFlags = Object.keys(require('../feature-flags'))
|
||||
const builtAssets = require('../lib/built-asset-urls')
|
||||
const searchVersions = require('../lib/search/versions')
|
||||
@@ -27,7 +27,7 @@ module.exports = async function contextualize (req, res, next) {
|
||||
// make feature flag environment variables accessible in layouts
|
||||
req.context.process = { env: {} }
|
||||
featureFlags.forEach(featureFlagName => {
|
||||
req.context.process.env[featureFlagName] = process.env[featureFlagName]
|
||||
req.context[featureFlagName] = process.env[featureFlagName]
|
||||
})
|
||||
|
||||
// define each context property explicitly for code-search friendliness
|
||||
|
||||
38
middleware/contextualizers/breadcrumbs.js
Normal file
38
middleware/contextualizers/breadcrumbs.js
Normal file
@@ -0,0 +1,38 @@
|
||||
module.exports = async function breadcrumbs (req, res, next) {
|
||||
if (!req.context.page) return next()
|
||||
if (req.context.page.hidden) return next()
|
||||
|
||||
req.context.breadcrumbs = []
|
||||
|
||||
// Return an empty array on the landing page.
|
||||
if (req.context.page.documentType === 'homepage') {
|
||||
return next()
|
||||
}
|
||||
|
||||
const currentSiteTree = req.context.siteTree[req.context.currentLanguage][req.context.currentVersion]
|
||||
|
||||
await createBreadcrumb(
|
||||
// Array of child pages on the root, i.e., the product level.
|
||||
currentSiteTree.childPages,
|
||||
req.context
|
||||
)
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
async function createBreadcrumb (pageArray, context) {
|
||||
// Find each page in the siteTree's array of child pages that starts with the requested path.
|
||||
const childPage = pageArray.find(page => context.currentPath.startsWith(page.href))
|
||||
|
||||
context.breadcrumbs.push({
|
||||
documentType: childPage.page.documentType,
|
||||
href: childPage.href,
|
||||
title: childPage.renderedShortTitle || childPage.renderedFullTitle
|
||||
})
|
||||
|
||||
// Recursively loop through the siteTree and create each breadcrumb, until we reach the
|
||||
// point where the current siteTree page is the same as the requested page. Then stop.
|
||||
if (childPage.childPages && context.currentPath !== childPage.href) {
|
||||
createBreadcrumb(childPage.childPages, context)
|
||||
}
|
||||
}
|
||||
10
middleware/contextualizers/current-product-tree.js
Normal file
10
middleware/contextualizers/current-product-tree.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = function currentProductTree (req, res, next) {
|
||||
if (!req.context.page) return next()
|
||||
if (req.context.page.documentType === 'homepage') return next()
|
||||
|
||||
const currentSiteTree = req.context.siteTree[req.context.currentLanguage][req.context.currentVersion]
|
||||
|
||||
req.context.currentProductTree = currentSiteTree.childPages.find(page => req.context.currentPath.startsWith(page.href))
|
||||
|
||||
return next()
|
||||
}
|
||||
55
middleware/contextualizers/early-access-breadcrumbs.js
Normal file
55
middleware/contextualizers/early-access-breadcrumbs.js
Normal file
@@ -0,0 +1,55 @@
|
||||
module.exports = async function breadcrumbs (req, res, next) {
|
||||
if (!req.context.page) return next()
|
||||
if (!req.context.page.hidden) return next()
|
||||
|
||||
req.context.breadcrumbs = []
|
||||
|
||||
// Return an empty array on the landing page.
|
||||
if (req.context.page.documentType === 'homepage') {
|
||||
return next()
|
||||
}
|
||||
|
||||
const earlyAccessProduct = req.context.siteTree[req.language][req.context.currentVersion].childPages.find(childPage => childPage.page.relativePath === 'early-access/index.md')
|
||||
if (!earlyAccessProduct) return next()
|
||||
|
||||
// Create initial landing page breadcrumb
|
||||
req.context.breadcrumbs.push({
|
||||
documentType: earlyAccessProduct.page.documentType,
|
||||
href: '',
|
||||
title: earlyAccessProduct.page.title
|
||||
})
|
||||
|
||||
// If this is the Early Access landing page, return now
|
||||
if (req.context.currentPath === earlyAccessProduct.href) {
|
||||
return next()
|
||||
}
|
||||
|
||||
// Otherwise, create breadcrumbs
|
||||
await createBreadcrumb(
|
||||
earlyAccessProduct.childPages,
|
||||
req.context
|
||||
)
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
async function createBreadcrumb (pageArray, context) {
|
||||
// Find each page in the siteTree's array of child pages that starts with the requested path.
|
||||
const childPage = pageArray.find(page => context.currentPath.startsWith(page.href))
|
||||
|
||||
// Gray out product breadcrumb links and `Articles` categories
|
||||
const hideHref = childPage.page.documentType === 'product' ||
|
||||
(childPage.page.documentType === 'category' && childPage.page.relativePath.endsWith('/articles/index.md'))
|
||||
|
||||
context.breadcrumbs.push({
|
||||
documentType: childPage.page.documentType,
|
||||
href: hideHref ? '' : childPage.href,
|
||||
title: await childPage.page.renderTitle(context, { textOnly: true, encodeEntities: true })
|
||||
})
|
||||
|
||||
// Recursively loop through the siteTree and create each breadcrumb, until we reach the
|
||||
// point where the current siteTree page is the same as the requested page. Then stop.
|
||||
if (childPage.childPages && context.currentPath !== childPage.href) {
|
||||
createBreadcrumb(childPage.childPages, context)
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ module.exports = function earlyAccessContext (req, res, next) {
|
||||
|
||||
// Get a list of all hidden pages per version
|
||||
const earlyAccessPageLinks = uniq(Object.values(req.context.pages)
|
||||
.filter(page => page.hidden && page.relativePath.startsWith('early-access') && page.relativePath !== 'early-access/index.md')
|
||||
.filter(page => page.hidden && page.relativePath.startsWith('early-access') && !page.relativePath.endsWith('index.md'))
|
||||
.map(page => page.permalinks)
|
||||
.flat())
|
||||
// Get links for the current version
|
||||
|
||||
59
middleware/contextualizers/generic-toc.js
Normal file
59
middleware/contextualizers/generic-toc.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const { sortBy } = require('lodash')
|
||||
|
||||
module.exports = async function genericToc (req, res, next) {
|
||||
if (!req.context.page) return next()
|
||||
if (req.context.page.hidden) return next()
|
||||
if (req.context.currentLayoutName !== 'default') return next()
|
||||
if (req.context.page.documentType === 'homepage' || req.context.page.documentType === 'article') return next()
|
||||
|
||||
const currentSiteTree = req.context.siteTree[req.context.currentLanguage][req.context.currentVersion]
|
||||
|
||||
// Find the array of child pages that start with the requested path.
|
||||
const currentPageInSiteTree = findPageInSiteTree(currentSiteTree.childPages, req.context.currentPath)
|
||||
|
||||
req.context.tocItems = sortBy(
|
||||
await getUnsortedTocItems(currentPageInSiteTree.childPages, req.context),
|
||||
// Sort by the ordered array of `children` in the frontmatter.
|
||||
currentPageInSiteTree.page.children
|
||||
)
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
// Recursively loop through the siteTree until we reach the point where the
|
||||
// current siteTree page is the same as the requested page. Then stop.
|
||||
function findPageInSiteTree (pageArray, currentPath) {
|
||||
const childPage = pageArray.find(page => currentPath.startsWith(page.href))
|
||||
|
||||
if (childPage.href === currentPath) {
|
||||
return childPage
|
||||
}
|
||||
|
||||
return findPageInSiteTree(childPage.childPages, currentPath)
|
||||
}
|
||||
|
||||
async function getUnsortedTocItems (pageArray, context) {
|
||||
return Promise.all(pageArray.map(async (childPage) => {
|
||||
// return an empty string if it's a hidden link on a non-hidden page (hidden links on hidden pages are OK)
|
||||
if (childPage.page.hidden && !context.page.hidden) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const fullPath = childPage.href
|
||||
// Titles are already rendered by middleware/contextualizers/render-tree-titles.js.
|
||||
const title = childPage.renderedFullTitle
|
||||
const intro = await childPage.page.renderProp('intro', context, { unwrap: true })
|
||||
|
||||
if (!childPage.childPages) {
|
||||
return { fullPath, title, intro }
|
||||
}
|
||||
|
||||
const childTocItems = sortBy(
|
||||
await getUnsortedTocItems(childPage.childPages, context),
|
||||
// Sort by the ordered array of `children` in the frontmatter.
|
||||
childPage.page.children
|
||||
)
|
||||
|
||||
return { fullPath, title, intro, childTocItems }
|
||||
}))
|
||||
}
|
||||
@@ -3,20 +3,18 @@ const layouts = require('../../lib/layouts')
|
||||
module.exports = function layoutContext (req, res, next) {
|
||||
if (!req.context.page) return next()
|
||||
|
||||
let layoutName
|
||||
|
||||
if (req.context.page.layout) {
|
||||
const layoutOptsByType = {
|
||||
// Layouts can be specified with a `layout` frontmatter value.
|
||||
// Any invalid layout values will be caught by frontmatter schema validation.
|
||||
layoutName = req.context.page.layout
|
||||
string: req.context.page.layout,
|
||||
// A `layout: false` value means use no layout.
|
||||
} else if (req.context.page.layout === false) {
|
||||
layoutName = ''
|
||||
// If undefined, use either the default layout or the generic-toc layout.
|
||||
} else if (req.context.page.layout === undefined) {
|
||||
layoutName = 'default'
|
||||
boolean: '',
|
||||
// For all other files (like articles and the homepage), use the `default` layout.
|
||||
undefined: 'default'
|
||||
}
|
||||
|
||||
const layoutName = layoutOptsByType[typeof (req.context.page.layout)]
|
||||
|
||||
// Attach to the context object
|
||||
req.context.currentLayoutName = layoutName
|
||||
req.context.currentLayout = layouts[layoutName]
|
||||
|
||||
33
middleware/contextualizers/render-tree-titles.js
Normal file
33
middleware/contextualizers/render-tree-titles.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { sortBy } = require('lodash')
|
||||
const renderOpts = { textOnly: true, encodeEntities: true }
|
||||
|
||||
module.exports = async function renderTreeTitles (req, res, next) {
|
||||
if (!req.context.page) return next()
|
||||
if (req.context.page.documentType === 'homepage') return next()
|
||||
|
||||
await renderLiquidInTitles(req.context.siteTree[req.context.currentLanguage][req.context.currentVersion], req.context)
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
// Add new props to each siteTree page here...
|
||||
async function renderLiquidInTitles (pageInTree, context) {
|
||||
// We _only_ need to render the titles and shortTitles that contain Liquid.
|
||||
pageInTree.renderedFullTitle = pageInTree.page.title.includes('{')
|
||||
? await pageInTree.page.renderProp('title', context, renderOpts)
|
||||
: pageInTree.page.title
|
||||
|
||||
if (pageInTree.page.shortTitle) {
|
||||
pageInTree.renderedShortTitle = pageInTree.page.shortTitle.includes('{')
|
||||
? await pageInTree.page.renderProp('shortTitle', context, renderOpts)
|
||||
: pageInTree.page.shortTitle
|
||||
}
|
||||
|
||||
if (!pageInTree.childPages) return
|
||||
|
||||
pageInTree.page.childPages = sortBy(
|
||||
await Promise.all(pageInTree.childPages.map(async (childPage) => await renderLiquidInTitles(childPage, context))),
|
||||
// Sort by the ordered array of `children` in the frontmatter.
|
||||
pageInTree.page.children
|
||||
)
|
||||
}
|
||||
@@ -97,7 +97,12 @@ module.exports = function (app) {
|
||||
app.use(asyncMiddleware(instrument('./archived-enterprise-versions')))
|
||||
app.use(instrument('./robots'))
|
||||
app.use(/(\/.*)?\/early-access$/, instrument('./contextualizers/early-access-links'))
|
||||
if (!process.env.FEATURE_NEW_SITETREE) {
|
||||
app.use('/categories.json', asyncMiddleware(instrument('./categories-for-support-team')))
|
||||
}
|
||||
if (process.env.FEATURE_NEW_SITETREE) {
|
||||
app.use('/categories.json', asyncMiddleware(instrument('./categories-for-support')))
|
||||
}
|
||||
app.use(instrument('./loaderio-verification'))
|
||||
app.get('/_500', asyncMiddleware(instrument('./trigger-error')))
|
||||
|
||||
@@ -111,8 +116,20 @@ module.exports = function (app) {
|
||||
app.use(instrument('./contextualizers/webhooks'))
|
||||
app.use(asyncMiddleware(instrument('./contextualizers/whats-new-changelog')))
|
||||
app.use(instrument('./contextualizers/layout'))
|
||||
|
||||
if (!process.env.FEATURE_NEW_SITETREE) {
|
||||
app.use(asyncMiddleware(instrument('./breadcrumbs')))
|
||||
app.use(asyncMiddleware(instrument('./early-access-breadcrumbs')))
|
||||
}
|
||||
|
||||
if (process.env.FEATURE_NEW_SITETREE) {
|
||||
app.use(asyncMiddleware(instrument('./contextualizers/render-tree-titles')))
|
||||
app.use(instrument('./contextualizers/current-product-tree'))
|
||||
app.use(asyncMiddleware(instrument('./contextualizers/generic-toc')))
|
||||
app.use(asyncMiddleware(instrument('./contextualizers/breadcrumbs')))
|
||||
app.use(asyncMiddleware(instrument('./contextualizers/early-access-breadcrumbs')))
|
||||
}
|
||||
|
||||
app.use(asyncMiddleware(instrument('./enterprise-server-releases')))
|
||||
app.use(asyncMiddleware(instrument('./dev-toc')))
|
||||
app.use(asyncMiddleware(instrument('./featured-links')))
|
||||
|
||||
44
script/content-migrations/add-early-access-tocs.js
Executable file
44
script/content-migrations/add-early-access-tocs.js
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const readFrontmatter = require('../../lib/read-frontmatter')
|
||||
const earlyAccessDir = path.posix.join(process.cwd(), 'content', 'early-access')
|
||||
const { sentenceCase } = require('change-case')
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const walk = require('walk-sync')
|
||||
const stripHtmlComments = require('strip-html-comments')
|
||||
const languages = require('../../lib/languages')
|
||||
const frontmatter = require('../../lib/read-frontmatter')
|
||||
const addRedirectToFrontmatter = require('../../lib/redirects/add-redirect-to-frontmatter')
|
||||
@@ -26,6 +27,10 @@ const categoryIndexFiles = fullDirectoryPaths.map(fullDirectoryPath => walk(full
|
||||
categoryIndexFiles.forEach(categoryIndexFile => {
|
||||
let categoryIndexContent = fs.readFileSync(categoryIndexFile, 'utf8')
|
||||
|
||||
if (categoryIndexFile.endsWith('github/getting-started-with-github/index.md')) {
|
||||
categoryIndexContent = stripHtmlComments(categoryIndexContent.replace(/\n<!--/g, '<!--'))
|
||||
}
|
||||
|
||||
// find array of TOC link strings
|
||||
const rawItems = categoryIndexContent.match(linksArray)
|
||||
if (!rawItems || !rawItems[0].includes('topic_link_in_list')) return
|
||||
@@ -79,7 +84,9 @@ categoryIndexFiles.forEach(categoryIndexFile => {
|
||||
|
||||
// Read the article file so we can add a redirect from its old path
|
||||
const articleContents = frontmatter(fs.readFileSync(newArticlePath, 'utf8'))
|
||||
addRedirectToFrontmatter(articleContents.data.redirect_from, `${oldTopicDirectory}/${article}`)
|
||||
|
||||
if (!articleContents.data.redirect_from) articleContents.data.redirect_from = []
|
||||
addRedirectToFrontmatter(articleContents.data.redirect_from, `${oldTopicDirectory.replace(/^.*?\/content\//, '/')}/${article}`)
|
||||
|
||||
// Write the article with updated frontmatter
|
||||
fs.writeFileSync(newArticlePath, frontmatter.stringify(articleContents.content.trim(), articleContents.data, { lineWidth: 10000 }))
|
||||
|
||||
@@ -6,6 +6,7 @@ const walk = require('walk-sync')
|
||||
const frontmatter = require('../../lib/read-frontmatter')
|
||||
const getDocumentType = require('../../lib/get-document-type')
|
||||
const languages = require('../../lib/languages')
|
||||
const extendedMarkdownTags = Object.keys(require('../../lib/liquid-tags/extended-markdown').tags)
|
||||
|
||||
const linkString = /{% [^}]*?link.*? (\/.*?) ?%}/m
|
||||
const linksArray = new RegExp(linkString.source, 'gm')
|
||||
@@ -72,8 +73,23 @@ indexFiles
|
||||
]
|
||||
}
|
||||
|
||||
// Remove the Table of Contents section and leave any body text before it.
|
||||
let newContent = content
|
||||
.replace(/^#*? Table of contents[\s\S]*/im, '')
|
||||
.replace('<div hidden>', '')
|
||||
.replace(linksArray, '')
|
||||
|
||||
const linesArray = newContent
|
||||
.split('\n')
|
||||
|
||||
const newLinesArray = linesArray
|
||||
.filter((line, index) => /\S/.test(line) || (extendedMarkdownTags.find(tag => (linesArray[index - 1] && linesArray[index - 1].includes(tag)) || (linesArray[index + 1] && linesArray[index + 1].includes(tag)))))
|
||||
.filter(line => !/^<!--\s+?-->$/m.test(line))
|
||||
|
||||
newContent = newLinesArray.join('\n')
|
||||
|
||||
// Index files should no longer have body content, so we write an empty string
|
||||
fs.writeFileSync(indexFile, frontmatter.stringify('', data, { lineWidth: 10000 }))
|
||||
fs.writeFileSync(indexFile, frontmatter.stringify(newContent, data, { lineWidth: 10000 }))
|
||||
})
|
||||
|
||||
function getLinks (linkItemArray) {
|
||||
|
||||
Reference in New Issue
Block a user