mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-05-18 22:44:14 +02:00
fix(folders): Use real folder names instead of splicing slugs
In breadcrumbs and folder pages (both folder page titles and page lists) the name of the folder was derived from the slug unless overriden, which is.. wonky. This is much more noticeable if you change the slugify function to make all slugs lowercase - which I did, and which may be a followup PR. The patch was pretty straightforward though, we just use the real folder names from the relativePath.
This commit is contained in:
parent
04423d4931
commit
4e0c34730d
@ -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,
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user