mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-05-18 14:34:23 +02:00
Compare commits
3 Commits
fc4a52b9a3
...
30a26c41f3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
30a26c41f3 | ||
![]() |
91189dfd2f | ||
![]() |
0bafa4c94a |
@ -27,7 +27,7 @@ export const defaultContentPageLayout: PageLayout = {
|
||||
Component.MobileOnly(Component.Spacer()),
|
||||
Component.Search(),
|
||||
Component.Darkmode(),
|
||||
Component.DesktopOnly(Component.Explorer()),
|
||||
Component.Explorer(),
|
||||
],
|
||||
right: [
|
||||
Component.Graph(),
|
||||
@ -44,7 +44,7 @@ export const defaultListPageLayout: PageLayout = {
|
||||
Component.MobileOnly(Component.Spacer()),
|
||||
Component.Search(),
|
||||
Component.Darkmode(),
|
||||
Component.DesktopOnly(Component.Explorer()),
|
||||
Component.Explorer(),
|
||||
],
|
||||
right: [],
|
||||
}
|
||||
|
@ -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<BreadcrumbOptions>) => {
|
||||
@ -65,7 +62,7 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
|
||||
}
|
||||
|
||||
// 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<BreadcrumbOptions>) => {
|
||||
|
||||
// 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<BreadcrumbOptions>) => {
|
||||
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<BreadcrumbOptions>) => {
|
||||
const includeTrailingSlash = !isTagPath || i < 1
|
||||
|
||||
// Format and add current crumb
|
||||
const crumb = formatCrumb(
|
||||
const crumb = newCrumb(
|
||||
curPathSegment,
|
||||
fileData.slug!,
|
||||
(currentPath + (includeTrailingSlash ? "/" : "")) as SimpleSlug,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
import explorerStyle from "./styles/explorer.scss"
|
||||
import style from "./styles/explorer.scss"
|
||||
|
||||
// @ts-ignore
|
||||
import script from "./scripts/explorer.inline"
|
||||
@ -83,18 +83,46 @@ export default ((userOpts?: Partial<Options>) => {
|
||||
lastBuildId = ctx.buildId
|
||||
constructFileTree(allFiles)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={classNames(displayClass, "explorer")}>
|
||||
<button
|
||||
type="button"
|
||||
id="explorer"
|
||||
id="mobile-explorer"
|
||||
class="collapsed hide-until-loaded"
|
||||
data-behavior={opts.folderClickBehavior}
|
||||
data-collapsed={opts.folderDefaultState}
|
||||
data-savestate={opts.useSavedState}
|
||||
data-tree={jsonTree}
|
||||
data-mobile={true}
|
||||
aria-controls="explorer-content"
|
||||
aria-expanded={opts.folderDefaultState === "open"}
|
||||
aria-expanded={false}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-menu"
|
||||
>
|
||||
<line x1="4" x2="20" y1="12" y2="12" />
|
||||
<line x1="4" x2="20" y1="6" y2="6" />
|
||||
<line x1="4" x2="20" y1="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="desktop-explorer"
|
||||
class="title-button"
|
||||
data-behavior={opts.folderClickBehavior}
|
||||
data-collapsed={opts.folderDefaultState}
|
||||
data-savestate={opts.useSavedState}
|
||||
data-tree={jsonTree}
|
||||
data-mobile={false}
|
||||
aria-controls="explorer-content"
|
||||
aria-expanded={true}
|
||||
>
|
||||
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
|
||||
<svg
|
||||
@ -122,7 +150,7 @@ export default ((userOpts?: Partial<Options>) => {
|
||||
)
|
||||
}
|
||||
|
||||
Explorer.css = explorerStyle
|
||||
Explorer.css = style
|
||||
Explorer.afterDOMLoaded = script
|
||||
return Explorer
|
||||
}) satisfies QuartzComponentConstructor
|
||||
|
@ -23,6 +23,11 @@ const defaultOptions: FolderContentOptions = {
|
||||
showSubfolders: true,
|
||||
}
|
||||
|
||||
type Subfolder = {
|
||||
name: string
|
||||
contents: QuartzPluginData[]
|
||||
}
|
||||
|
||||
export default ((opts?: Partial<FolderContentOptions>) => {
|
||||
const options: FolderContentOptions = { ...defaultOptions, ...opts }
|
||||
|
||||
@ -31,51 +36,56 @@ export default ((opts?: Partial<FolderContentOptions>) => {
|
||||
const folderSlug = stripSlashes(simplifySlug(fileData.slug!))
|
||||
const folderParts = folderSlug.split(path.posix.sep)
|
||||
|
||||
const allPagesInFolder: QuartzPluginData[] = []
|
||||
const allPagesInSubfolders: Map<FullSlug, QuartzPluginData[]> = new Map()
|
||||
const shownPages: QuartzPluginData[] = []
|
||||
const subfolders: Map<FullSlug, Subfolder> = new Map()
|
||||
|
||||
allFiles.forEach((file) => {
|
||||
for (const file of allFiles) {
|
||||
const fileSlug = stripSlashes(simplifySlug(file.slug!))
|
||||
const prefixed = fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug
|
||||
const fileParts = fileSlug.split(path.posix.sep)
|
||||
const isDirectChild = fileParts.length === folderParts.length + 1
|
||||
|
||||
if (!prefixed) {
|
||||
return
|
||||
// check only files in our folder or nested folders
|
||||
if (!fileSlug.startsWith(folderSlug) || fileSlug === folderSlug) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (isDirectChild) {
|
||||
allPagesInFolder.push(file)
|
||||
} else if (options.showSubfolders) {
|
||||
const fileParts = fileSlug.split(path.posix.sep)
|
||||
|
||||
// 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(
|
||||
...fileParts.slice(0, folderParts.length + 1),
|
||||
) as FullSlug
|
||||
const pagesInFolder = allPagesInSubfolders.get(subfolderSlug) || []
|
||||
allPagesInSubfolders.set(subfolderSlug, [...pagesInFolder, file])
|
||||
}
|
||||
})
|
||||
|
||||
allPagesInSubfolders.forEach((files, subfolderSlug) => {
|
||||
const hasIndex = allPagesInFolder.some(
|
||||
(file) => subfolderSlug === stripSlashes(simplifySlug(file.slug!)),
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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<FolderContentOptions>) => {
|
||||
{options.showFolderCount && (
|
||||
<p>
|
||||
{i18n(cfg.locale).pages.folderContent.itemsUnderFolder({
|
||||
count: allPagesInFolder.length,
|
||||
count: shownPages.length,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { FolderState } from "../ExplorerNode"
|
||||
|
||||
// Current state of folders
|
||||
type MaybeHTMLElement = HTMLElement | undefined
|
||||
let currentExplorerState: FolderState[]
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
// If last element is observed, remove gradient of "overflow" class so element is visible
|
||||
const explorerUl = document.getElementById("explorer-ul")
|
||||
@ -16,23 +18,43 @@ const observer = new IntersectionObserver((entries) => {
|
||||
})
|
||||
|
||||
function toggleExplorer(this: HTMLElement) {
|
||||
// Toggle collapsed state of entire explorer
|
||||
this.classList.toggle("collapsed")
|
||||
|
||||
// Toggle collapsed aria state of entire explorer
|
||||
this.setAttribute(
|
||||
"aria-expanded",
|
||||
this.getAttribute("aria-expanded") === "true" ? "false" : "true",
|
||||
)
|
||||
const content = this.nextElementSibling as MaybeHTMLElement
|
||||
if (!content) return
|
||||
|
||||
const content = (
|
||||
this.nextElementSibling?.nextElementSibling
|
||||
? this.nextElementSibling.nextElementSibling
|
||||
: this.nextElementSibling
|
||||
) as MaybeHTMLElement
|
||||
if (!content) return
|
||||
content.classList.toggle("collapsed")
|
||||
content.classList.toggle("explorer-viewmode")
|
||||
|
||||
// Prevent scroll under
|
||||
if (document.querySelector("#mobile-explorer")) {
|
||||
// Disable scrolling on the page when the explorer is opened on mobile
|
||||
const bodySelector = document.querySelector("#quartz-body")
|
||||
if (bodySelector) bodySelector.classList.toggle("lock-scroll")
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFolder(evt: MouseEvent) {
|
||||
evt.stopPropagation()
|
||||
|
||||
// Element that was clicked
|
||||
const target = evt.target as MaybeHTMLElement
|
||||
if (!target) return
|
||||
|
||||
// Check if target was svg icon or button
|
||||
const isSvg = target.nodeName === "svg"
|
||||
|
||||
// corresponding <ul> element relative to clicked button/folder
|
||||
const childFolderContainer = (
|
||||
isSvg
|
||||
? target.parentElement?.nextSibling
|
||||
@ -42,10 +64,14 @@ function toggleFolder(evt: MouseEvent) {
|
||||
isSvg ? target.nextElementSibling : target.parentElement
|
||||
) as MaybeHTMLElement
|
||||
if (!(childFolderContainer && currentFolderParent)) return
|
||||
|
||||
// <li> element of folder (stores folder-path dataset)
|
||||
childFolderContainer.classList.toggle("open")
|
||||
|
||||
// Collapse folder container
|
||||
const isCollapsed = childFolderContainer.classList.contains("open")
|
||||
setFolderState(childFolderContainer, !isCollapsed)
|
||||
|
||||
// Save folder state to localStorage
|
||||
const fullFolderPath = currentFolderParent.dataset.folderpath as string
|
||||
toggleCollapsedByPath(currentExplorerState, fullFolderPath)
|
||||
const stringifiedFileTree = JSON.stringify(currentExplorerState)
|
||||
@ -53,20 +79,34 @@ function toggleFolder(evt: MouseEvent) {
|
||||
}
|
||||
|
||||
function setupExplorer() {
|
||||
const explorer = document.getElementById("explorer")
|
||||
if (!explorer) return
|
||||
// Set click handler for collapsing entire explorer
|
||||
const allExplorers = document.querySelectorAll(".explorer > button") as NodeListOf<HTMLElement>
|
||||
|
||||
if (explorer.dataset.behavior === "collapse") {
|
||||
for (const explorer of allExplorers) {
|
||||
// Get folder state from local storage
|
||||
const storageTree = localStorage.getItem("fileTree")
|
||||
|
||||
// Convert to bool
|
||||
const useSavedFolderState = explorer?.dataset.savestate === "true"
|
||||
|
||||
if (explorer) {
|
||||
// Get config
|
||||
const collapseBehavior = explorer.dataset.behavior
|
||||
|
||||
// Add click handlers for all folders (click handler on folder "label")
|
||||
if (collapseBehavior === "collapse") {
|
||||
for (const item of document.getElementsByClassName(
|
||||
"folder-button",
|
||||
) as HTMLCollectionOf<HTMLElement>) {
|
||||
window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
|
||||
item.addEventListener("click", toggleFolder)
|
||||
window.addCleanup(() => item.removeEventListener("click", toggleFolder))
|
||||
}
|
||||
}
|
||||
|
||||
explorer.addEventListener("click", toggleExplorer)
|
||||
// Add click handler to main explorer
|
||||
window.addCleanup(() => explorer.removeEventListener("click", toggleExplorer))
|
||||
explorer.addEventListener("click", toggleExplorer)
|
||||
}
|
||||
|
||||
// Set up click handlers for each folder (click handler on folder "icon")
|
||||
for (const item of document.getElementsByClassName(
|
||||
@ -77,8 +117,6 @@ function setupExplorer() {
|
||||
}
|
||||
|
||||
// Get folder state from local storage
|
||||
const storageTree = localStorage.getItem("fileTree")
|
||||
const useSavedFolderState = explorer?.dataset.savestate === "true"
|
||||
const oldExplorerState: FolderState[] =
|
||||
storageTree && useSavedFolderState ? JSON.parse(storageTree) : []
|
||||
const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed]))
|
||||
@ -86,24 +124,61 @@ function setupExplorer() {
|
||||
? JSON.parse(explorer.dataset.tree)
|
||||
: []
|
||||
currentExplorerState = []
|
||||
|
||||
for (const { path, collapsed } of newExplorerState) {
|
||||
currentExplorerState.push({ path, collapsed: oldIndex.get(path) ?? collapsed })
|
||||
currentExplorerState.push({
|
||||
path,
|
||||
collapsed: oldIndex.get(path) ?? collapsed,
|
||||
})
|
||||
}
|
||||
|
||||
currentExplorerState.map((folderState) => {
|
||||
const folderLi = document.querySelector(
|
||||
`[data-folderpath='${folderState.path}']`,
|
||||
`[data-folderpath='${folderState.path.replace("'", "-")}']`,
|
||||
) as MaybeHTMLElement
|
||||
const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
|
||||
if (folderUl) {
|
||||
setFolderState(folderUl, folderState.collapsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function toggleExplorerFolders() {
|
||||
const currentFile = (document.querySelector("body")?.getAttribute("data-slug") ?? "").replace(
|
||||
/\/index$/g,
|
||||
"",
|
||||
)
|
||||
const allFolders = document.querySelectorAll(".folder-outer")
|
||||
|
||||
allFolders.forEach((element) => {
|
||||
const folderUl = Array.from(element.children).find((child) =>
|
||||
child.matches("ul[data-folderul]"),
|
||||
)
|
||||
if (folderUl) {
|
||||
if (currentFile.includes(folderUl.getAttribute("data-folderul") ?? "")) {
|
||||
if (!element.classList.contains("open")) {
|
||||
element.classList.add("open")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener("resize", setupExplorer)
|
||||
|
||||
document.addEventListener("nav", () => {
|
||||
const explorer = document.querySelector("#mobile-explorer")
|
||||
if (explorer) {
|
||||
explorer.classList.add("collapsed")
|
||||
const content = explorer.nextElementSibling?.nextElementSibling as HTMLElement
|
||||
if (content) {
|
||||
content.classList.add("collapsed")
|
||||
content.classList.toggle("explorer-viewmode")
|
||||
}
|
||||
}
|
||||
setupExplorer()
|
||||
|
||||
observer.disconnect()
|
||||
|
||||
// select pseudo element at end of list
|
||||
@ -111,6 +186,12 @@ document.addEventListener("nav", () => {
|
||||
if (lastItem) {
|
||||
observer.observe(lastItem)
|
||||
}
|
||||
|
||||
// Hide explorer on mobile until it is requested
|
||||
const hiddenUntilDoneLoading = document.querySelector("#mobile-explorer")
|
||||
hiddenUntilDoneLoading?.classList.remove("hide-until-loaded")
|
||||
|
||||
toggleExplorerFolders()
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -1,14 +1,70 @@
|
||||
@use "../../styles/variables.scss" as *;
|
||||
|
||||
@media all and ($mobile) {
|
||||
.page > #quartz-body {
|
||||
// Shift page position when toggling Explorer on mobile.
|
||||
& > :not(.sidebar.left:has(.explorer)) {
|
||||
transform: translateX(0);
|
||||
transition: transform 300ms ease-in-out;
|
||||
}
|
||||
&.lock-scroll > :not(.sidebar.left:has(.explorer)) {
|
||||
transform: translateX(100dvw);
|
||||
transition: transform 300ms ease-in-out;
|
||||
}
|
||||
|
||||
// Sticky top bar (stays in place when scrolling down on mobile).
|
||||
.sidebar.left:has(.explorer) {
|
||||
box-sizing: border-box;
|
||||
position: sticky;
|
||||
background-color: var(--light);
|
||||
}
|
||||
|
||||
// Hide Explorer on mobile until done loading.
|
||||
// Prevents ugly animation on page load.
|
||||
.hide-until-loaded ~ #explorer-content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explorer {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
overflow-y: hidden;
|
||||
|
||||
@media all and ($mobile) {
|
||||
order: -1;
|
||||
height: initial;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
button#mobile-explorer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button#desktop-explorer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media all and ($mobile) {
|
||||
button#mobile-explorer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
button#desktop-explorer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.desktop-only {
|
||||
@media all and not ($mobile) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
/*&:after {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
@ -23,7 +79,8 @@
|
||||
}*/
|
||||
}
|
||||
|
||||
button#explorer {
|
||||
button#mobile-explorer,
|
||||
button#desktop-explorer {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
text-align: left;
|
||||
@ -68,19 +125,19 @@ button#explorer {
|
||||
list-style: none;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: 0px;
|
||||
transition:
|
||||
max-height 0.35s ease,
|
||||
visibility 0s linear 0.35s;
|
||||
margin-top: 0.5rem;
|
||||
visibility: hidden;
|
||||
|
||||
&.collapsed {
|
||||
max-height: 100%;
|
||||
transition:
|
||||
max-height 0.35s ease,
|
||||
visibility 0s linear 0s;
|
||||
margin-top: 0.5rem;
|
||||
visibility: visible;
|
||||
|
||||
&.collapsed {
|
||||
max-height: 0;
|
||||
transition:
|
||||
max-height 0.35s ease,
|
||||
visibility 0s linear 0.35s;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
& ul {
|
||||
@ -91,12 +148,14 @@ button#explorer {
|
||||
max-height 0.35s ease,
|
||||
transform 0.35s ease,
|
||||
opacity 0.2s ease;
|
||||
|
||||
& li > a {
|
||||
color: var(--dark);
|
||||
opacity: 0.75;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
> #explorer-ul {
|
||||
max-height: none;
|
||||
}
|
||||
@ -179,3 +238,80 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
|
||||
// remove default margin from li
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.explorer {
|
||||
@media all and ($mobile) {
|
||||
#explorer-content {
|
||||
box-sizing: border-box;
|
||||
overscroll-behavior: none;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
background-color: var(--light);
|
||||
max-width: 100dvw;
|
||||
left: -100dvw;
|
||||
width: 100%;
|
||||
transition: transform 300ms ease-in-out;
|
||||
overflow: hidden;
|
||||
padding: $topSpacing 2rem 2rem;
|
||||
height: 100dvh;
|
||||
max-height: 100dvh;
|
||||
margin-top: 0;
|
||||
visibility: hidden;
|
||||
|
||||
&:not(.collapsed) {
|
||||
transform: translateX(100dvw);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
ul.overflow {
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
transform: translateX(0);
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
#mobile-explorer {
|
||||
margin: 5px;
|
||||
z-index: 101;
|
||||
|
||||
&:not(.collapsed) .lucide-menu {
|
||||
transform: rotate(-90deg);
|
||||
transition: transform 200ms ease-in-out;
|
||||
}
|
||||
|
||||
.lucide-menu {
|
||||
stroke: var(--darkgray);
|
||||
transition: transform 200ms ease;
|
||||
|
||||
&:hover {
|
||||
stroke: var(--dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-scroll {
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html:has(.no-scroll) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media all and not ($mobile) {
|
||||
.no-scroll {
|
||||
opacity: 1 !important;
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
html:has(.no-scroll) {
|
||||
overflow: auto !important;
|
||||
}
|
||||
}
|
||||
|
@ -74,13 +74,27 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (user
|
||||
const allFiles = content.map((c) => c[1].data)
|
||||
const cfg = ctx.cfg.configuration
|
||||
|
||||
const folderNames: Record<SimpleSlug, string> = {}
|
||||
|
||||
const folders: Set<SimpleSlug> = 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<Partial<FolderPageOptions>> = (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: [],
|
||||
},
|
||||
}),
|
||||
@ -100,9 +115,16 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (user
|
||||
for (const [tree, file] of content) {
|
||||
const slug = stripSlashes(simplifySlug(file.data.slug!)) as SimpleSlug
|
||||
if (folders.has(slug)) {
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const folder of folders) {
|
||||
const slug = joinSegments(folder, "index") as FullSlug
|
||||
@ -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