Merge 4e0c34730df586ed3a602d44eaf75d6ce9d73694 into dd6bd498db25344b2cccf56abfb656576a496d38

This commit is contained in:
Anton Bulakh 2025-02-24 16:59:18 +11:00 committed by GitHub
commit adc4ed4336
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 54 deletions

View File

@ -40,11 +40,8 @@ const defaultOptions: BreadcrumbOptions = {
showCurrentPage: true, showCurrentPage: true,
} }
function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData { function newCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData {
return { return { displayName, path: resolveRelative(baseSlug, currentSlug) }
displayName: displayName.replaceAll("-", " "),
path: resolveRelative(baseSlug, currentSlug),
}
} }
export default ((opts?: Partial<BreadcrumbOptions>) => { export default ((opts?: Partial<BreadcrumbOptions>) => {
@ -65,7 +62,7 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
} }
// Format entry for root element // 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] const crumbs: CrumbData[] = [firstEntry]
if (!folderIndex && options.resolveFrontmatterTitle) { if (!folderIndex && options.resolveFrontmatterTitle) {
@ -81,6 +78,7 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
// Split slug into hierarchy/parts // Split slug into hierarchy/parts
const slugParts = fileData.slug?.split("/") const slugParts = fileData.slug?.split("/")
const pathParts = fileData.relativePath?.split("/")
if (slugParts) { if (slugParts) {
// is tag breadcrumb? // is tag breadcrumb?
const isTagPath = slugParts[0] === "tags" const isTagPath = slugParts[0] === "tags"
@ -89,7 +87,7 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
let currentPath = "" let currentPath = ""
for (let i = 0; i < slugParts.length - 1; i++) { 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 // Try to resolve frontmatter folder title
const currentFile = folderIndex?.get(slugParts.slice(0, i + 1).join("/")) const currentFile = folderIndex?.get(slugParts.slice(0, i + 1).join("/"))
@ -105,7 +103,7 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
const includeTrailingSlash = !isTagPath || i < 1 const includeTrailingSlash = !isTagPath || i < 1
// Format and add current crumb // Format and add current crumb
const crumb = formatCrumb( const crumb = newCrumb(
curPathSegment, curPathSegment,
fileData.slug!, fileData.slug!,
(currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug, (currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug,

View File

@ -23,6 +23,11 @@ const defaultOptions: FolderContentOptions = {
showSubfolders: true, showSubfolders: true,
} }
type Subfolder = {
name: string
contents: QuartzPluginData[]
}
export default ((opts?: Partial<FolderContentOptions>) => { export default ((opts?: Partial<FolderContentOptions>) => {
const options: FolderContentOptions = { ...defaultOptions, ...opts } const options: FolderContentOptions = { ...defaultOptions, ...opts }
@ -31,51 +36,56 @@ export default ((opts?: Partial<FolderContentOptions>) => {
const folderSlug = stripSlashes(simplifySlug(fileData.slug!)) const folderSlug = stripSlashes(simplifySlug(fileData.slug!))
const folderParts = folderSlug.split(path.posix.sep) const folderParts = folderSlug.split(path.posix.sep)
const allPagesInFolder: QuartzPluginData[] = [] const shownPages: QuartzPluginData[] = []
const allPagesInSubfolders: Map<FullSlug, QuartzPluginData[]> = new Map() const subfolders: Map<FullSlug, Subfolder> = new Map()
allFiles.forEach((file) => { for (const file of allFiles) {
const fileSlug = stripSlashes(simplifySlug(file.slug!)) const fileSlug = stripSlashes(simplifySlug(file.slug!))
const prefixed = fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug // check only files in our folder or nested folders
const fileParts = fileSlug.split(path.posix.sep) if (!fileSlug.startsWith(folderSlug) || fileSlug === folderSlug) {
const isDirectChild = fileParts.length === folderParts.length + 1 continue
if (!prefixed) {
return
} }
if (isDirectChild) { const fileParts = fileSlug.split(path.posix.sep)
allPagesInFolder.push(file)
} else if (options.showSubfolders) { // If the file is directly in the folder we just show it
if (fileParts.length === folderParts.length + 1) {
shownPages.push(file)
continue
}
if (options.showSubfolders) {
const subfolderSlug = joinSegments( const subfolderSlug = joinSegments(
...fileParts.slice(0, folderParts.length + 1), ...fileParts.slice(0, folderParts.length + 1),
) as FullSlug ) as FullSlug
const pagesInFolder = allPagesInSubfolders.get(subfolderSlug) || []
allPagesInSubfolders.set(subfolderSlug, [...pagesInFolder, file])
}
})
allPagesInSubfolders.forEach((files, subfolderSlug) => { let subfolder = subfolders.get(subfolderSlug)
const hasIndex = allPagesInFolder.some( if (!subfolder) {
(file) => subfolderSlug === stripSlashes(simplifySlug(file.slug!)), const subfolderName = file.relativePath!.split(path.posix.sep).at(folderParts.length)!
) subfolders.set(subfolderSlug, (subfolder = { name: subfolderName, contents: [] }))
}
subfolder.contents.push(file)
}
}
for (const [slug, subfolder] of subfolders.entries()) {
const hasIndex = shownPages.some((file) => slug === stripSlashes(simplifySlug(file.slug!)))
if (!hasIndex) { if (!hasIndex) {
const subfolderDates = files.sort(byDateAndAlphabetical(cfg))[0].dates const subfolderDates = subfolder.contents.sort(byDateAndAlphabetical(cfg))[0].dates
const subfolderTitle = subfolderSlug.split(path.posix.sep).at(-1)! shownPages.push({
allPagesInFolder.push({ slug: slug,
slug: subfolderSlug,
dates: subfolderDates, dates: subfolderDates,
frontmatter: { title: subfolderTitle, tags: ["folder"] }, frontmatter: { title: subfolder.name, tags: ["folder"] },
}) })
} }
}) }
const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? [] const cssClasses: string[] = fileData.frontmatter?.cssclasses ?? []
const classes = cssClasses.join(" ") const classes = cssClasses.join(" ")
const listProps = { const listProps = {
...props, ...props,
sort: options.sort, sort: options.sort,
allFiles: allPagesInFolder, allFiles: shownPages,
} }
const content = const content =
@ -90,7 +100,7 @@ export default ((opts?: Partial<FolderContentOptions>) => {
{options.showFolderCount && ( {options.showFolderCount && (
<p> <p>
{i18n(cfg.locale).pages.folderContent.itemsUnderFolder({ {i18n(cfg.locale).pages.folderContent.itemsUnderFolder({
count: allPagesInFolder.length, count: shownPages.length,
})} })}
</p> </p>
)} )}

View File

@ -74,13 +74,27 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (user
const allFiles = content.map((c) => c[1].data) const allFiles = content.map((c) => c[1].data)
const cfg = ctx.cfg.configuration const cfg = ctx.cfg.configuration
const folderNames: Record<SimpleSlug, string> = {}
const folders: Set<SimpleSlug> = new Set( const folders: Set<SimpleSlug> = new Set(
allFiles.flatMap((data) => { allFiles.flatMap((data) => {
return data.slug if (!data.slug || !data.relativePath) {
? _getFolders(data.slug).filter( return []
(folderName) => folderName !== "." && folderName !== "tags", }
) 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<Partial<FolderPageOptions>> = (user
folder, folder,
defaultProcessedContent({ defaultProcessedContent({
slug: joinSegments(folder, "index") as FullSlug, slug: joinSegments(folder, "index") as FullSlug,
relativePath: joinSegments(folderNames[folder], "index.html") as FilePath, // this is used by breadcrumbs
frontmatter: { frontmatter: {
title: `${i18n(cfg.locale).pages.folderContent.folder}: ${folder}`, title: `${i18n(cfg.locale).pages.folderContent.folder}: ${folderNames[folder]}`,
tags: [], tags: [],
}, },
}), }),
@ -100,7 +115,14 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (user
for (const [tree, file] of content) { for (const [tree, file] of content) {
const slug = stripSlashes(simplifySlug(file.data.slug!)) as SimpleSlug const slug = stripSlashes(simplifySlug(file.data.slug!)) as SimpleSlug
if (folders.has(slug)) { if (folders.has(slug)) {
folderDescriptions[slug] = [tree, file] if (file.data.frontmatter?.title === "index") {
// sadly we need to avoid changing the original file title for things like explorer to work
const clonedFile = structuredClone(file)
clonedFile.data.frontmatter!.title = `${i18n(cfg.locale).pages.folderContent.folder}: ${folderNames[slug]}`
folderDescriptions[slug] = [tree, clonedFile]
} else {
folderDescriptions[slug] = [tree, file]
}
} }
} }
@ -132,14 +154,3 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (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
}