diff --git a/quartz/components/Breadcrumbs.tsx b/quartz/components/Breadcrumbs.tsx index 9ccfb9a6a2728..3f1b13a05d59f 100644 --- a/quartz/components/Breadcrumbs.tsx +++ b/quartz/components/Breadcrumbs.tsx @@ -40,11 +40,8 @@ const defaultOptions: BreadcrumbOptions = { showCurrentPage: true, } -function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData { - return { - displayName: displayName.replaceAll("-", " "), - path: resolveRelative(baseSlug, currentSlug), - } +function newCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData { + return { displayName, path: resolveRelative(baseSlug, currentSlug) } } export default ((opts?: Partial) => { @@ -65,7 +62,7 @@ export default ((opts?: Partial) => { } // Format entry for root element - const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug) + const firstEntry = newCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug) const crumbs: CrumbData[] = [firstEntry] if (!folderIndex && options.resolveFrontmatterTitle) { @@ -81,6 +78,7 @@ export default ((opts?: Partial) => { // Split slug into hierarchy/parts const slugParts = fileData.slug?.split("/") + const pathParts = fileData.relativePath?.split("/") if (slugParts) { // is tag breadcrumb? const isTagPath = slugParts[0] === "tags" @@ -89,7 +87,7 @@ export default ((opts?: Partial) => { let currentPath = "" for (let i = 0; i < slugParts.length - 1; i++) { - let curPathSegment = slugParts[i] + let curPathSegment = pathParts?.[i] ?? slugParts[i] // Try to resolve frontmatter folder title const currentFile = folderIndex?.get(slugParts.slice(0, i + 1).join("/")) @@ -105,7 +103,7 @@ export default ((opts?: Partial) => { const includeTrailingSlash = !isTagPath || i < 1 // Format and add current crumb - const crumb = formatCrumb( + const crumb = newCrumb( curPathSegment, fileData.slug!, (currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug, diff --git a/quartz/components/pages/FolderContent.tsx b/quartz/components/pages/FolderContent.tsx index 593073b962adb..bc81423f37bfa 100644 --- a/quartz/components/pages/FolderContent.tsx +++ b/quartz/components/pages/FolderContent.tsx @@ -23,6 +23,11 @@ const defaultOptions: FolderContentOptions = { showSubfolders: true, } +type Subfolder = { + name: string + contents: QuartzPluginData[] +} + export default ((opts?: Partial) => { const options: FolderContentOptions = { ...defaultOptions, ...opts } @@ -31,51 +36,56 @@ export default ((opts?: Partial) => { const folderSlug = stripSlashes(simplifySlug(fileData.slug!)) const folderParts = folderSlug.split(path.posix.sep) - const allPagesInFolder: QuartzPluginData[] = [] - const allPagesInSubfolders: Map = new Map() + const shownPages: QuartzPluginData[] = [] + const subfolders: Map = new Map() - allFiles.forEach((file) => { + for (const file of allFiles) { const fileSlug = stripSlashes(simplifySlug(file.slug!)) - const prefixed = fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug + // check only files in our folder or nested folders + if (!fileSlug.startsWith(folderSlug) || fileSlug === folderSlug) { + continue + } + const fileParts = fileSlug.split(path.posix.sep) - const isDirectChild = fileParts.length === folderParts.length + 1 - if (!prefixed) { - return + // If the file is directly in the folder we just show it + if (fileParts.length === folderParts.length + 1) { + shownPages.push(file) + continue } - if (isDirectChild) { - allPagesInFolder.push(file) - } else if (options.showSubfolders) { + if (options.showSubfolders) { const subfolderSlug = joinSegments( ...fileParts.slice(0, folderParts.length + 1), ) as FullSlug - const pagesInFolder = allPagesInSubfolders.get(subfolderSlug) || [] - allPagesInSubfolders.set(subfolderSlug, [...pagesInFolder, file]) + + let subfolder = subfolders.get(subfolderSlug) + if (!subfolder) { + const subfolderName = file.relativePath!.split(path.posix.sep).at(folderParts.length)! + subfolders.set(subfolderSlug, (subfolder = { name: subfolderName, contents: [] })) + } + subfolder.contents.push(file) } - }) + } - allPagesInSubfolders.forEach((files, subfolderSlug) => { - const hasIndex = allPagesInFolder.some( - (file) => subfolderSlug === stripSlashes(simplifySlug(file.slug!)), - ) + for (const [slug, subfolder] of subfolders.entries()) { + const hasIndex = shownPages.some((file) => slug === stripSlashes(simplifySlug(file.slug!))) if (!hasIndex) { - const subfolderDates = files.sort(byDateAndAlphabetical(cfg))[0].dates - const subfolderTitle = subfolderSlug.split(path.posix.sep).at(-1)! - allPagesInFolder.push({ - slug: subfolderSlug, + const subfolderDates = subfolder.contents.sort(byDateAndAlphabetical(cfg))[0].dates + shownPages.push({ + slug: slug, dates: subfolderDates, - frontmatter: { title: subfolderTitle, tags: ["folder"] }, + frontmatter: { title: subfolder.name, tags: ["folder"] }, }) } - }) + } const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] const classes = cssClasses.join(" ") const listProps = { ...props, sort: options.sort, - allFiles: allPagesInFolder, + allFiles: shownPages, } const content = @@ -90,7 +100,7 @@ export default ((opts?: Partial) => { {options.showFolderCount && (

{i18n(cfg.locale).pages.folderContent.itemsUnderFolder({ - count: allPagesInFolder.length, + count: shownPages.length, })}

)} diff --git a/quartz/plugins/emitters/folderPage.tsx b/quartz/plugins/emitters/folderPage.tsx index bafaec916727f..d390a85c827a7 100644 --- a/quartz/plugins/emitters/folderPage.tsx +++ b/quartz/plugins/emitters/folderPage.tsx @@ -74,13 +74,27 @@ export const FolderPage: QuartzEmitterPlugin> = (user const allFiles = content.map((c) => c[1].data) const cfg = ctx.cfg.configuration + const folderNames: Record = {} + const folders: Set = new Set( allFiles.flatMap((data) => { - return data.slug - ? _getFolders(data.slug).filter( - (folderName) => folderName !== "." && folderName !== "tags", - ) - : [] + if (!data.slug || !data.relativePath) { + return [] + } + let folderSlug = path.dirname(data.slug) as SimpleSlug + let folderFs = path.dirname(data.relativePath) as SimpleSlug + folderNames[folderSlug] = folderFs + + const folders = [folderSlug] + while (folderSlug !== ".") { + folderSlug = path.dirname(folderSlug) as SimpleSlug + folders.push(folderSlug) + + folderFs = path.dirname(folderFs) as SimpleSlug + folderNames[folderSlug] = folderFs + } + + return folders.filter((f) => f !== "." && f !== "tags") }), ) @@ -89,8 +103,9 @@ export const FolderPage: QuartzEmitterPlugin> = (user folder, defaultProcessedContent({ slug: joinSegments(folder, "index") as FullSlug, + relativePath: joinSegments(folderNames[folder], "index.html") as FilePath, // this is used by breadcrumbs frontmatter: { - title: `${i18n(cfg.locale).pages.folderContent.folder}: ${folder}`, + title: `${i18n(cfg.locale).pages.folderContent.folder}: ${folderNames[folder]}`, tags: [], }, }), @@ -132,14 +147,3 @@ export const FolderPage: QuartzEmitterPlugin> = (user }, } } - -function _getFolders(slug: FullSlug): SimpleSlug[] { - var folderName = path.dirname(slug ?? "") as SimpleSlug - const parentFolderNames = [folderName] - - while (folderName !== ".") { - folderName = path.dirname(folderName ?? "") as SimpleSlug - parentFolderNames.push(folderName) - } - return parentFolderNames -}