From 6b3825a8820662768f5bdbd354cb25bd4664f6e7 Mon Sep 17 00:00:00 2001 From: okadurin Date: Thu, 14 Dec 2023 15:11:45 +0100 Subject: [PATCH] chore: create a common file for rendering [...slug] and [component] --- src/pages/[top]/[...slug].astro | 221 +----------------------------- src/utils/pages/render-entries.ts | 193 ++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 214 deletions(-) create mode 100644 src/utils/pages/render-entries.ts diff --git a/src/pages/[top]/[...slug].astro b/src/pages/[top]/[...slug].astro index 51c476799b..3033f96a90 100644 --- a/src/pages/[top]/[...slug].astro +++ b/src/pages/[top]/[...slug].astro @@ -9,12 +9,7 @@ import { fundamentalsEntries, allPages } from '../../content'; import { blogEntries } from '../../content'; import { guideEntries } from '../../content'; import { maxDepthForNonComponentsNavigation } from '../../../config.mjs'; - -let pages = []; -const inPageNavData = []; -let mdjsStoriesJsPath = ''; -let dirPath; - +import { getPages, getInPageNavData, getPagesByDir, getPathForMdjsStroriesFile } from '../../utils/pages/render-entries.ts'; export async function getStaticPaths() { const fundamentalsArr = fundamentalsEntries.map(entry => ({ @@ -75,221 +70,19 @@ export async function getStaticPaths() { const { entry } = Astro.props; -const renderDir = async (directoryPath) => { - const entries = getEntriesByDir(directoryPath); - return await concatenateEntries(entries); -}; - -const convertHeadingsToInPageNavData = (headings, componentSlug) => { - const arePagesConcatenated = !entry; - let mainPathPart = arePagesConcatenated ? dirPath : componentSlug; - return headings.map(header => { - const anchor = header.slug; - return { - name: header.text, - url: `/${mainPathPart}#${anchor}` - }; - }); -}; - -const parseEntries = async (entries) => { - const contents = []; - for (const componentEntry of entries) { - const { Content, headings, remarkPluginFrontmatter } = await componentEntry.render(); - const order = remarkPluginFrontmatter.order; - const slug = componentEntry.slug; - const content = {Content, headings, order, slug}; - contents.push(content); - } - return contents; -}; - -const updateHeadings = (contentItems) => { - const parentDirToNavDataMap = new Map(); - for (const contentItem of contentItems) { - const headersH2 = contentItem.headings.filter(header => header.depth === 2); - const parentDirName = path.dirname(contentItem.slug); - if (headersH2.length !== 0) { - const entryInPageNavData = convertHeadingsToInPageNavData(headersH2, contentItem.slug)[0]; - const headersH3 = contentItem.headings.filter(header => header.depth === 3); - if (headersH3.length !== 0) { - entryInPageNavData.children = convertHeadingsToInPageNavData(headersH3, contentItem.slug); - } - inPageNavData.push(entryInPageNavData); - parentDirToNavDataMap.set(parentDirName, entryInPageNavData); - } else { - const headersH3 = contentItem.headings.filter(header => header.depth === 3); - if (headersH3.length !== 0) { - const entryInPageNavData = parentDirToNavDataMap.get(parentDirName); - if (entryInPageNavData) { - entryInPageNavData.children = entryInPageNavData.children || []; - entryInPageNavData.children = [...entryInPageNavData.children, ...convertHeadingsToInPageNavData(headersH3, contentItem.slug)]; - } - } - } - } -}; - -const directoriesOrder = new Map(); -function getOrder(content, contents) { - const dir = path.dirname(content.slug); - const dirIndex = path.join(dir, 'dir-index'); - let dirOrder; - if (directoriesOrder.has(dir)) { - dirOrder = directoriesOrder.get(dir); - } else { - dirOrder = contents.find(item => item.slug === dirIndex).order; - directoriesOrder.set(dir, dirOrder); - } - return dirOrder; -} - -function getContentsWithParentDepth(contents, parentDepth) { - return contents.filter(content => { - const dirDepth = path.dirname(content.slug).split('/').length; - return dirDepth >= parentDepth; - }); -} - -function getSlugForParentDepth(slug, depth) { - const slugParts = slug.split('/'); - const slugPartsForParentDepth = slugParts.slice(0, depth); - return slugPartsForParentDepth.join('/'); -} - -function getUniqueParentDirs(contents, parentDepth) { - const dirs = new Set(); - contents.forEach(content => { - dirs.add(getSlugForParentDepth(content.slug, parentDepth)); - }); - return [...dirs]; -} - -function sortDirs(dirs, contents) { - dirs.sort((a, b) => { - const aDirOrder = contents.find(content => content.slug === path.join(a, 'dir-index')).order; - const bDirOrder = contents.find(content => content.slug === path.join(b, 'dir-index')).order; - return aDirOrder < bDirOrder ? -1 : 1; - }); -} - -function sortDirectoriesForParentDepth(contents, parentDepth) { - - const reducedContents = getContentsWithParentDepth(contents, parentDepth); - - const uniqueParentDirs = getUniqueParentDirs(reducedContents, parentDepth); - - sortDirs(uniqueParentDirs, reducedContents); - contents.sort((a, b) => { - const aParentDir = getSlugForParentDepth(a.slug, parentDepth); - const bParentDir = getSlugForParentDepth(b.slug, parentDepth); - if (uniqueParentDirs.indexOf(aParentDir) > uniqueParentDirs.indexOf(bParentDir)) { - return 1; - } else if (uniqueParentDirs.indexOf(aParentDir) < uniqueParentDirs.indexOf(bParentDir)) { - return -1; - } else { - return 0; - } - }); -} - -function sortDirectories(contents) { - let parentDepth = maxDepthForNonComponentsNavigation + 1; - let hasParentWithDepth = contents.some(content => path.dirname(content.slug).split('/').length === parentDepth); - - while(hasParentWithDepth) { - sortDirectoriesForParentDepth(contents, parentDepth); - parentDepth++; - hasParentWithDepth = contents.some(content => path.dirname(content.slug).split('/').length === parentDepth); - } -} - -function sort(contents) { - contents.sort((a, b) => { - // Get paths with fewer depth first - if (a.slug.split('/').length < b.slug.split('/').length) { - return -1; - } else if (a.slug.split('/').length > b.slug.split('/').length) { - return 1; - } - // same depth - else { - // same parent - if (path.dirname(a.slug) === path.dirname(b.slug)) { - if (path.basename(a.slug) === 'dir-index') { - return -1; - } else if (path.basename(b.slug) === 'dir-index') { - return 1; - } else { - return a.order < b.order ? -1 : 1; - } - } - } - }); -} - -async function concatenateEntries(entries) { - const contents = await parseEntries(entries); - sortDirectories(contents); - sort(contents); - updateHeadings(contents); - return contents; -} - -const getEntriesByDir = (dirname) => { - //return allPages.filter(childEntry => childEntry.slug.startsWith(dirname)); - return allPages.filter(childEntry => { - return childEntry.slug.startsWith(dirname)}); -}; - -const getMdjsStories = (fullDirPath) => { - return new Promise((resolve, reject) => { - glob(fullDirPath + '/**/__mdjs-stories--*.js', {}, (err, files)=>{ - const relativePaths = files.map(file => { - return path.relative(fullDirPath, file); - }); - resolve(relativePaths); - }) - }); -}; - -if (entry) { - pages = await concatenateEntries([entry]); -} else { - dirPath = path.join(Astro.params.top, Astro.params.slug); - pages = await renderDir(dirPath); - const fullDirPath = path.join(process.cwd(), 'public/docs', dirPath); - const files = await getMdjsStories(fullDirPath); - let imports = ''; - files.forEach(file => { - imports += `import('./${file}');\n` - }); - if (imports) { - mdjsStoriesJsPath = path.join(fullDirPath, '__mdjs-stories.js'); - fs.writeFileSync(mdjsStoriesJsPath, imports, 'utf8'); - } -} - -const getPathMdjsStroriesFile = () => { - if (entry) { - const mdjsStroriesFileDirectory = path.dirname(entry.slug); - return `/docs/${mdjsStroriesFileDirectory}/__mdjs-stories--${path.basename(entry.slug)}.js`; - } - if (mdjsStoriesJsPath) { - return '/docs' + mdjsStoriesJsPath.split('/docs')[1]; - } - return null; -} - +// URL path as a base for in-page navigation. It is used to add anchors to. F.e. "host://urlPath#header1" +const urlPath = entry ? entry.slug : path.join(Astro.params.top, Astro.params.slug); +const pages = entry ? await getPages([entry]) : await getPagesByDir(urlPath); +const pathForMdjsStroriesFile = await getPathForMdjsStroriesFile(entry?.slug, urlPath); --- - + { pages.map((page) => ( )) } - {getPathMdjsStroriesFile() && } + {pathForMdjsStroriesFile && } diff --git a/src/utils/pages/render-entries.ts b/src/utils/pages/render-entries.ts new file mode 100644 index 0000000000..931b75477a --- /dev/null +++ b/src/utils/pages/render-entries.ts @@ -0,0 +1,193 @@ +import { glob } from 'glob' +import * as fs from 'fs'; +import * as process from 'process'; +import * as path from 'path'; +import { fundamentalsEntries, allPages } from '../../content'; +import { maxDepthForNonComponentsNavigation } from '../../../config.mjs'; + +const convertHeadingsToInPageNavData = (headings, urlPath) => { + return headings.map(header => { + const anchor = header.slug; + return { + name: header.text, + url: `/${urlPath}#${anchor}` + }; + }); +}; + +const parseEntries = async (entries) => { + const contents = []; + for (const componentEntry of entries) { + const { Content, headings, remarkPluginFrontmatter } = await componentEntry.render(); + const order = remarkPluginFrontmatter.order; + const slug = componentEntry.slug; + const content = {Content, headings, order, slug}; + contents.push(content); + } + return contents; +}; + +export const getInPageNavData = (contentItems, urlPath) => { + const inPageNavData = []; + const parentDirToNavDataMap = new Map(); + const arePagesConcatenated = contentItems.length > 1; + for (const contentItem of contentItems) { + const headersH2 = contentItem.headings.filter(header => header.depth === 2); + const parentDirName = path.dirname(contentItem.slug); + if (headersH2.length !== 0) { + const entryInPageNavData = convertHeadingsToInPageNavData(headersH2, urlPath)[0]; + const headersH3 = contentItem.headings.filter(header => header.depth === 3); + if (headersH3.length !== 0) { + entryInPageNavData.children = convertHeadingsToInPageNavData(headersH3, urlPath); + } + inPageNavData.push(entryInPageNavData); + parentDirToNavDataMap.set(parentDirName, entryInPageNavData); + } else { + const headersH3 = contentItem.headings.filter(header => header.depth === 3); + if (headersH3.length !== 0) { + const entryInPageNavData = parentDirToNavDataMap.get(parentDirName); + if (entryInPageNavData) { + entryInPageNavData.children = entryInPageNavData.children || []; + entryInPageNavData.children = [...entryInPageNavData.children, ...convertHeadingsToInPageNavData(headersH3, urlPath)]; + } + } + } + } + return inPageNavData; +}; + +function getContentsWithParentDepth(contents, parentDepth) { + return contents.filter(content => { + const dirDepth = path.dirname(content.slug).split('/').length; + return dirDepth >= parentDepth; + }); +} + +function getSlugForParentDepth(slug, depth) { + const slugParts = slug.split('/'); + const slugPartsForParentDepth = slugParts.slice(0, depth); + return slugPartsForParentDepth.join('/'); +} + +function getUniqueParentDirs(contents, parentDepth) { + const dirs = new Set(); + contents.forEach(content => { + dirs.add(getSlugForParentDepth(content.slug, parentDepth)); + }); + return [...dirs]; +} + +function sortDirs(dirs, contents) { + dirs.sort((a, b) => { + const aDirOrder = contents.find(content => content.slug === path.join(a, 'dir-index')).order; + const bDirOrder = contents.find(content => content.slug === path.join(b, 'dir-index')).order; + return aDirOrder < bDirOrder ? -1 : 1; + }); +} + +function sortDirectoriesForParentDepth(contents, parentDepth) { + + const reducedContents = getContentsWithParentDepth(contents, parentDepth); + + const uniqueParentDirs = getUniqueParentDirs(reducedContents, parentDepth); + + sortDirs(uniqueParentDirs, reducedContents); + contents.sort((a, b) => { + const aParentDir = getSlugForParentDepth(a.slug, parentDepth); + const bParentDir = getSlugForParentDepth(b.slug, parentDepth); + if (uniqueParentDirs.indexOf(aParentDir) > uniqueParentDirs.indexOf(bParentDir)) { + return 1; + } else if (uniqueParentDirs.indexOf(aParentDir) < uniqueParentDirs.indexOf(bParentDir)) { + return -1; + } else { + return 0; + } + }); +} + +function sortDirectories(contents) { + let parentDepth = maxDepthForNonComponentsNavigation + 1; + let hasParentWithDepth = contents.some(content => path.dirname(content.slug).split('/').length === parentDepth); + + while(hasParentWithDepth) { + sortDirectoriesForParentDepth(contents, parentDepth); + parentDepth++; + hasParentWithDepth = contents.some(content => path.dirname(content.slug).split('/').length === parentDepth); + } +} + +function sort(contents) { + contents.sort((a, b) => { + // Get paths with fewer depth first + if (a.slug.split('/').length < b.slug.split('/').length) { + return -1; + } else if (a.slug.split('/').length > b.slug.split('/').length) { + return 1; + } + // same depth + else { + // same parent + if (path.dirname(a.slug) === path.dirname(b.slug)) { + if (path.basename(a.slug) === 'dir-index') { + return -1; + } else if (path.basename(b.slug) === 'dir-index') { + return 1; + } else { + return a.order < b.order ? -1 : 1; + } + } + } + }); +} + +export async function getPages(entries) { + const contents = await parseEntries(entries); + sortDirectories(contents); + sort(contents); + return contents; +} + +export const getPagesByDir = async (directoryPath) => { + const entries = getEntriesByDir(directoryPath); + return await getPages(entries); +}; + +const getEntriesByDir = (dirname) => { + return allPages.filter(childEntry => { + return childEntry.slug.startsWith(dirname)}); +}; + +const getMdjsStories = (fullDirPath) => { + return new Promise((resolve, reject) => { + glob(fullDirPath + '/**/__mdjs-stories--*.js', {}, (err, files)=>{ + const relativePaths = files.map(file => { + return path.relative(fullDirPath, file); + }); + resolve(relativePaths); + }) + }); +}; + +export async function getPathForMdjsStroriesFile(entrySlug, urlPath) { + if (entrySlug) { + const mdjsStroriesFileDirectory = path.dirname(entrySlug); + return `/docs/${mdjsStroriesFileDirectory}/__mdjs-stories--${path.basename(entrySlug)}.js`; + } + if (urlPath) { + let mdjsStoriesJsPath = ''; + const fullDirPath = path.join(process.cwd(), 'public/docs', urlPath); + const files = await getMdjsStories(fullDirPath); + let imports = ''; + files.forEach(file => { + imports += `import('./${file}');\n` + }); + if (imports) { + mdjsStoriesJsPath = path.join(fullDirPath, '__mdjs-stories.js'); + fs.writeFileSync(mdjsStoriesJsPath, imports, 'utf8'); + } + if (mdjsStoriesJsPath) { + return '/docs' + mdjsStoriesJsPath.split('/docs')[1]; + } + } + return null; +} \ No newline at end of file