diff --git a/lib/create-tree.js b/lib/create-tree.js
new file mode 100644
index 000000000000..eb2053c2803a
--- /dev/null
+++ b/lib/create-tree.js
@@ -0,0 +1,60 @@
+const fs = require('fs')
+const path = require('path')
+const Page = require('./page')
+const { sortBy } = require('lodash')
+
+let basePath
+
+module.exports = async function createTree (originalPath, langObj) {
+ // Do not reset this value on recursive runs
+ if (!basePath) basePath = originalPath
+
+ // On recursive runs, this is processing page.children items in `/` format.
+ // If the path exists as is, assume this is a directory with a child index.md.
+ // Otherwise, assume it's a child .md file and add `.md` to the path.
+ let filepath
+ try {
+ await fs.promises.access(originalPath)
+ filepath = `${originalPath}/index.md`
+ } catch {
+ filepath = `${originalPath}.md`
+ }
+
+ const relativePath = filepath.replace(`${basePath}/`, '')
+ const localizedBasePath = path.join(__dirname, '..', langObj.dir, 'content')
+
+ // Initialize the Page! This is where the magic happens (sorry).
+ const page = await Page.init({
+ basePath: localizedBasePath,
+ relativePath,
+ languageCode: langObj.code
+ })
+
+ if (!page) {
+ // Do not throw an error if early access is not available
+ if (relativePath.startsWith('early-access')) return
+
+ throw Error(`Cannot initialize page for ${filepath}`)
+ }
+
+ // Create the root tree object on the first run, and create children recursively
+ const item = {
+ relativePath,
+ title: page.shortTitle || page.title,
+ // parentPath: parentPath || null,
+ page
+ }
+
+ // Process frontmatter children recursively
+ if (item.page.children) {
+ item.childPages = sortBy(
+ (await Promise.all(item.page.children
+ .map(async (child) => await createTree(path.join(originalPath, child), langObj))))
+ .filter(Boolean),
+ // Sort by the ordered array of children in the frontmatter
+ item.page.children
+ )
+ }
+
+ return item
+}
diff --git a/lib/frontmatter.js b/lib/frontmatter.js
index c875db25152b..918fa26d7400 100644
--- a/lib/frontmatter.js
+++ b/lib/frontmatter.js
@@ -36,6 +36,9 @@ const schema = {
minimum: 2,
maximum: 4
},
+ children: {
+ type: 'array'
+ },
mapTopic: {
type: 'boolean'
},
diff --git a/lib/get-document-type.js b/lib/get-document-type.js
new file mode 100644
index 000000000000..8c3fab956ed1
--- /dev/null
+++ b/lib/get-document-type.js
@@ -0,0 +1,17 @@
+module.exports = function getDocumentType (relativePath) {
+ if (!relativePath.endsWith('index.md')) {
+ return 'article'
+ }
+
+ // Derive the document type from the path segment length
+ switch (relativePath.split('/').length) {
+ case 1:
+ return 'homepage'
+ case 2:
+ return 'product'
+ case 3:
+ return 'category'
+ case 4:
+ return 'mapTopic'
+ }
+}
diff --git a/lib/page.js b/lib/page.js
index f9e2a43a17e3..7c7edd0af460 100644
--- a/lib/page.js
+++ b/lib/page.js
@@ -18,6 +18,7 @@ const statsd = require('./statsd')
const readFileContents = require('./read-file-contents')
const getLinkData = require('./get-link-data')
const union = require('lodash/union')
+// const getDocumentType = require('./get-document-type')
class Page {
static async init (opts) {
@@ -69,8 +70,14 @@ class Page {
this.rawLearningTracks = this.learningTracks
this.rawIncludeGuides = this.includeGuides
+ // Is this a Product, Category, Topic, or Article?
+ // this.documentType = getDocumentType(this.relativePath)
+
+ // Get array of versions that the page is available in for fast lookup
+ this.applicableVersions = getApplicableVersions(this.versions, this.fullPath)
+
// a page should only be available in versions that its parent product is available in
- const versionsParentProductIsNotAvailableIn = getApplicableVersions(this.versions, this.fullPath)
+ const versionsParentProductIsNotAvailableIn = this.applicableVersions
// only the homepage will not have this.parentProduct
.filter(availableVersion => this.parentProduct && !this.parentProduct.versions.includes(availableVersion))
diff --git a/lib/pages.js b/lib/pages.js
index c037c2213cbb..c4dd05bf5a24 100644
--- a/lib/pages.js
+++ b/lib/pages.js
@@ -1,7 +1,82 @@
const path = require('path')
+const languages = require('./languages')
+const versions = Object.keys(require('./all-versions'))
+const createTree = require('./create-tree')
+const nonEnterpriseDefaultVersion = require('./non-enterprise-default-version')
+const englishPath = path.join(__dirname, '..', 'content')
const walk = require('walk-sync').entries
const Page = require('./page')
-const languages = require('./languages')
+
+// This function creates a nested object that can be accessed like this:
+// siteTree[languageCode][version].childPages[].childPages[] (etc...)
+async function loadTree () {
+ // We only need to initialize pages once per language (since pages don't change per version), so we do that
+ // first since it's the most expensive work. This gets us a nested object with pages attached that we can use
+ // as the basis for the siteTree after we do some versioning.
+ const rawTree = {}
+ await Promise.all(Object.values(languages)
+ .map(async (langObj) => {
+ rawTree[langObj.code] = await createTree(englishPath, langObj)
+ }))
+
+ // Now that we have the paged tree, we can walk it for every version and do two operations:
+ // 1. Add a versioned href to every node (we can get this easily from the permalinks array).
+ // 2. Recurisvely drop any child pages that are not available in the current version.
+ // Note that order of languages and versions doesn't matter, but order of child page arrays DOES matter (for nav).
+ const siteTree = {}
+ await Promise.all(Object.keys(languages).map(async (langCode) => {
+ const treePerVersion = {}
+
+ await Promise.all(versions.map(async (version) => {
+ // Yes, we are mutating the rawTree object here.
+ versionPages(rawTree[langCode])
+
+ // This step can't be asynchronous because the order of child pages matters.
+ function versionPages (item) {
+ // Add a versioned href as a convenience for use in layouts.
+ item.href = item.page.permalinks
+ .find(pl => pl.pageVersion === version || (pl.pageVersion === 'homepage' && version === nonEnterpriseDefaultVersion))
+
+ if (!item.childPages) return item
+
+ // Drop child pages that do not apply to the current version
+ item.childPages = item.childPages.filter(childPage => childPage.page.applicableVersions.includes(version))
+ item.childPages.forEach(childPage => versionPages(childPage))
+ }
+
+ treePerVersion[version] = rawTree[langCode]
+ }))
+
+ siteTree[langCode] = treePerVersion
+ }))
+
+ console.log(siteTree.en[nonEnterpriseDefaultVersion])
+
+ return siteTree
+}
+
+async function loadPageListFromTree (tree) {
+ const siteTree = tree || await loadTree()
+
+ // Traverse the tree of pages and create a simple array of pages.
+ // (We don't care about the language code or version here, so we can start two levels in.)
+ const collection = Object.values(siteTree).map(v => Object.values(v)).flat()
+ const result = []
+ collectPages(collection, result)
+
+ function collectPages (arr, result) {
+ arr.forEach(item => {
+ if (item.page) {
+ result.push(item.page)
+ }
+ if (item.childPages) {
+ collectPages(item.childPages, result)
+ }
+ })
+ }
+
+ return result
+}
async function loadPageList () {
// load english pages
@@ -66,6 +141,8 @@ async function loadPageMap (pageList) {
}
module.exports = {
+ loadTree,
+ // loadPages: loadPageListFromTree,
loadPages: loadPageList,
loadPageMap
}