mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-05-18 14:34:23 +02:00
Compare commits
6 Commits
26ff78f81f
...
d02bb0f044
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d02bb0f044 | ||
![]() |
e320324cf2 | ||
![]() |
9f8a8c83e5 | ||
![]() |
1176deab7e | ||
![]() |
2802aa91f4 | ||
![]() |
50efa14bd2 |
@ -161,6 +161,18 @@ document.addEventListener("nav", () => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also add the equivalent of a `beforeunload` event for [[SPA Routing]] via the `prenav` event.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
document.addEventListener("prenav", () => {
|
||||||
|
// executed after an SPA navigation is triggered but
|
||||||
|
// before the page is replaced
|
||||||
|
// one usage pattern is to store things in sessionStorage
|
||||||
|
// in the prenav and then conditionally load then in the consequent
|
||||||
|
// nav
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks.
|
It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks.
|
||||||
This will get called on page navigation.
|
This will get called on page navigation.
|
||||||
|
|
||||||
|
1
index.d.ts
vendored
1
index.d.ts
vendored
@ -5,6 +5,7 @@ declare module "*.scss" {
|
|||||||
|
|
||||||
// dom custom event
|
// dom custom event
|
||||||
interface CustomEventMap {
|
interface CustomEventMap {
|
||||||
|
prenav: CustomEvent<{}>
|
||||||
nav: CustomEvent<{ url: FullSlug }>
|
nav: CustomEvent<{ url: FullSlug }>
|
||||||
themechange: CustomEvent<{ theme: "light" | "dark" }>
|
themechange: CustomEvent<{ theme: "light" | "dark" }>
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import * as Plugin from "./quartz/plugins"
|
|||||||
*/
|
*/
|
||||||
const config: QuartzConfig = {
|
const config: QuartzConfig = {
|
||||||
configuration: {
|
configuration: {
|
||||||
pageTitle: "🪴 Quartz 4",
|
pageTitle: "Quartz 4",
|
||||||
pageTitleSuffix: "",
|
pageTitleSuffix: "",
|
||||||
enableSPA: true,
|
enableSPA: true,
|
||||||
enablePopovers: true,
|
enablePopovers: true,
|
||||||
|
@ -26,9 +26,8 @@ export const defaultContentPageLayout: PageLayout = {
|
|||||||
Component.PageTitle(),
|
Component.PageTitle(),
|
||||||
Component.MobileOnly(Component.Spacer()),
|
Component.MobileOnly(Component.Spacer()),
|
||||||
Component.Search(),
|
Component.Search(),
|
||||||
|
|
||||||
Component.Explorer(),
|
|
||||||
Component.Darkmode(),
|
Component.Darkmode(),
|
||||||
|
Component.Explorer(),
|
||||||
],
|
],
|
||||||
right: [
|
right: [
|
||||||
Component.Graph(),
|
Component.Graph(),
|
||||||
|
@ -22,8 +22,8 @@ export interface Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: Options = {
|
const defaultOptions: Options = {
|
||||||
folderClickBehavior: "collapse",
|
|
||||||
folderDefaultState: "collapsed",
|
folderDefaultState: "collapsed",
|
||||||
|
folderClickBehavior: "collapse",
|
||||||
useSavedState: true,
|
useSavedState: true,
|
||||||
mapFn: (node) => {
|
mapFn: (node) => {
|
||||||
return node
|
return node
|
||||||
@ -74,7 +74,7 @@ export default ((userOpts?: Partial<Options>) => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="mobile-explorer"
|
id="mobile-explorer"
|
||||||
class="collapsed hide-until-loaded explorer-toggle"
|
class="explorer-toggle hide-until-loaded"
|
||||||
data-mobile={true}
|
data-mobile={true}
|
||||||
aria-controls="explorer-content"
|
aria-controls="explorer-content"
|
||||||
>
|
>
|
||||||
|
@ -17,7 +17,6 @@ document.addEventListener("nav", (e) => {
|
|||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = new IntersectionObserver((entries) => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const parentUl = entry.target.parentElement
|
const parentUl = entry.target.parentElement
|
||||||
console.log(parentUl)
|
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
parentUl.classList.remove("gradient-active")
|
parentUl.classList.remove("gradient-active")
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FileTrieNode } from "../../util/fileTrie"
|
import { FileTrieNode } from "../../util/fileTrie"
|
||||||
import { FullSlug, resolveRelative } from "../../util/path"
|
import { FullSlug, resolveRelative, simplifySlug } from "../../util/path"
|
||||||
import { ContentDetails } from "../../plugins/emitters/contentIndex"
|
import { ContentDetails } from "../../plugins/emitters/contentIndex"
|
||||||
|
|
||||||
type MaybeHTMLElement = HTMLElement | undefined
|
type MaybeHTMLElement = HTMLElement | undefined
|
||||||
@ -81,6 +81,11 @@ function createFileNode(currentSlug: FullSlug, node: FileTrieNode): HTMLLIElemen
|
|||||||
a.href = resolveRelative(currentSlug, node.data?.slug!)
|
a.href = resolveRelative(currentSlug, node.data?.slug!)
|
||||||
a.dataset.for = node.data?.slug
|
a.dataset.for = node.data?.slug
|
||||||
a.textContent = node.displayName
|
a.textContent = node.displayName
|
||||||
|
|
||||||
|
if (currentSlug === node.data?.slug) {
|
||||||
|
a.classList.add("active")
|
||||||
|
}
|
||||||
|
|
||||||
return li
|
return li
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,10 +119,18 @@ function createFolderNode(
|
|||||||
span.textContent = node.displayName
|
span.textContent = node.displayName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the saved state is collapsed or the default state is collapsed
|
||||||
const isCollapsed =
|
const isCollapsed =
|
||||||
currentExplorerState.find((item) => item.path === folderPath)?.collapsed ??
|
currentExplorerState.find((item) => item.path === folderPath)?.collapsed ??
|
||||||
opts.folderDefaultState === "collapsed"
|
opts.folderDefaultState === "collapsed"
|
||||||
if (!isCollapsed) {
|
|
||||||
|
// if this folder is a prefix of the current path we
|
||||||
|
// want to open it anyways
|
||||||
|
const simpleFolderPath = simplifySlug(folderPath)
|
||||||
|
const folderIsPrefixOfCurrentSlug =
|
||||||
|
simpleFolderPath === currentSlug.slice(0, simpleFolderPath.length)
|
||||||
|
|
||||||
|
if (!isCollapsed || folderIsPrefixOfCurrentSlug) {
|
||||||
folderOuter.classList.add("open")
|
folderOuter.classList.add("open")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,6 +206,18 @@ async function setupExplorer(currentSlug: FullSlug) {
|
|||||||
}
|
}
|
||||||
explorerUl.insertBefore(fragment, explorerUl.firstChild)
|
explorerUl.insertBefore(fragment, explorerUl.firstChild)
|
||||||
|
|
||||||
|
// restore explorer scrollTop position if it exists
|
||||||
|
const scrollTop = sessionStorage.getItem("explorerScrollTop")
|
||||||
|
if (scrollTop) {
|
||||||
|
explorerUl.scrollTop = parseInt(scrollTop)
|
||||||
|
} else {
|
||||||
|
// try to scroll to the active element if it exists
|
||||||
|
const activeElement = explorerUl.querySelector(".active")
|
||||||
|
if (activeElement) {
|
||||||
|
activeElement.scrollIntoView({ behavior: "smooth" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set up event handlers
|
// Set up event handlers
|
||||||
const explorerButtons = explorer.querySelectorAll(
|
const explorerButtons = explorer.querySelectorAll(
|
||||||
"button.explorer-toggle",
|
"button.explorer-toggle",
|
||||||
@ -225,15 +250,26 @@ async function setupExplorer(currentSlug: FullSlug) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener("prenav", async (e: CustomEventMap["prenav"]) => {
|
||||||
|
// save explorer scrollTop position
|
||||||
|
const explorer = document.getElementById("explorer-ul")
|
||||||
|
if (!explorer) return
|
||||||
|
sessionStorage.setItem("explorerScrollTop", explorer.scrollTop.toString())
|
||||||
|
})
|
||||||
|
|
||||||
document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
|
document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
|
||||||
const currentSlug = e.detail.url
|
const currentSlug = e.detail.url
|
||||||
const mobileExplorer = document.querySelector("#mobile-explorer")
|
|
||||||
if (mobileExplorer) {
|
|
||||||
mobileExplorer.classList.add("collapsed")
|
|
||||||
}
|
|
||||||
await setupExplorer(currentSlug)
|
await setupExplorer(currentSlug)
|
||||||
|
|
||||||
// Hide explorer on mobile until it is requested
|
// if mobile hamburger is visible, collapse by default
|
||||||
|
const mobileExplorer = document.getElementById("mobile-explorer")
|
||||||
|
if (mobileExplorer && mobileExplorer.checkVisibility()) {
|
||||||
|
for (const explorer of document.querySelectorAll(".explorer")) {
|
||||||
|
explorer.classList.add("collapsed")
|
||||||
|
explorer.setAttribute("aria-expanded", "false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const hiddenUntilDoneLoading = document.querySelector("#mobile-explorer")
|
const hiddenUntilDoneLoading = document.querySelector("#mobile-explorer")
|
||||||
hiddenUntilDoneLoading?.classList.remove("hide-until-loaded")
|
hiddenUntilDoneLoading?.classList.remove("hide-until-loaded")
|
||||||
})
|
})
|
||||||
|
@ -75,6 +75,10 @@ async function navigate(url: URL, isBack: boolean = false) {
|
|||||||
|
|
||||||
if (!contents) return
|
if (!contents) return
|
||||||
|
|
||||||
|
// notify about to nav
|
||||||
|
const event: CustomEventMap["prenav"] = new CustomEvent("prenav", { detail: {} })
|
||||||
|
document.dispatchEvent(event)
|
||||||
|
|
||||||
// cleanup old
|
// cleanup old
|
||||||
cleanupFns.forEach((fn) => fn())
|
cleanupFns.forEach((fn) => fn())
|
||||||
cleanupFns.clear()
|
cleanupFns.clear()
|
||||||
@ -108,7 +112,7 @@ async function navigate(url: URL, isBack: boolean = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// now, patch head
|
// now, patch head, re-executing scripts
|
||||||
const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])")
|
const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])")
|
||||||
elementsToRemove.forEach((el) => el.remove())
|
elementsToRemove.forEach((el) => el.remove())
|
||||||
const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])")
|
const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])")
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
text-align: inherit;
|
text-align: inherit;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
& svg {
|
& svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
background-color: var(--light);
|
background-color: var(--light);
|
||||||
|
padding: 1rem 0 1rem 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide Explorer on mobile until done loading.
|
|
||||||
// Prevents ugly animation on page load.
|
|
||||||
.hide-until-loaded ~ #explorer-content {
|
.hide-until-loaded ~ #explorer-content {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -30,6 +30,19 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
&.collapsed {
|
||||||
|
flex: 0 1 1.2rem;
|
||||||
|
& .fold {
|
||||||
|
transform: rotateZ(-90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .fold {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
@media all and ($mobile) {
|
@media all and ($mobile) {
|
||||||
order: -1;
|
order: -1;
|
||||||
@ -62,6 +75,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
pointer-events: all;
|
||||||
|
transition: transform 0.35s ease;
|
||||||
|
|
||||||
|
& > polyline {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button#mobile-explorer,
|
button#mobile-explorer,
|
||||||
@ -80,49 +102,29 @@ button#desktop-explorer {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .fold {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.explorer.collapsed > & .fold {
|
|
||||||
transform: rotateZ(-90deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#explorer-content {
|
#explorer-content {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 100%;
|
|
||||||
transition: max-height 0.35s ease;
|
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
|
|
||||||
.explorer.collapsed > & {
|
|
||||||
max-height: 0px;
|
|
||||||
transition: max-height 0.35s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
& ul {
|
& ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0.08rem 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
transition:
|
|
||||||
max-height 0.35s ease,
|
|
||||||
transform 0.35s ease,
|
|
||||||
opacity 0.2s ease;
|
|
||||||
|
|
||||||
& li > a {
|
& li > a {
|
||||||
color: var(--dark);
|
color: var(--dark);
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> #explorer-ul {
|
&.active {
|
||||||
max-height: none;
|
opacity: 1;
|
||||||
|
color: var(--tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-outer {
|
.folder-outer {
|
||||||
@ -143,14 +145,6 @@ button#desktop-explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
|
||||||
pointer-events: all;
|
|
||||||
|
|
||||||
& > polyline {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.folder-container {
|
.folder-container {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -212,51 +206,52 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
|
|||||||
|
|
||||||
.explorer {
|
.explorer {
|
||||||
@media all and ($mobile) {
|
@media all and ($mobile) {
|
||||||
#explorer-content {
|
&.collapsed {
|
||||||
box-sizing: border-box;
|
flex: 0 0 34px;
|
||||||
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) {
|
& > #explorer-content {
|
||||||
transform: translateX(100dvw);
|
transform: translateX(-100vw);
|
||||||
visibility: visible;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.collapsed {
|
&:not(.collapsed) {
|
||||||
|
flex: 0 0 34px;
|
||||||
|
|
||||||
|
& > #explorer-content {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#mobile-explorer {
|
#explorer-content {
|
||||||
margin: 5px;
|
box-sizing: border-box;
|
||||||
z-index: 101;
|
z-index: 100;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
background-color: var(--light);
|
||||||
|
max-width: 100vw;
|
||||||
|
width: 100%;
|
||||||
|
transform: translateX(-100vw);
|
||||||
|
transition:
|
||||||
|
transform 200ms ease,
|
||||||
|
visibility 200ms ease;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 4rem 0 2rem 0;
|
||||||
|
height: 100dvh;
|
||||||
|
max-height: 100dvh;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.collapsed) .lucide-menu {
|
#mobile-explorer {
|
||||||
transform: rotate(-90deg);
|
margin: 0;
|
||||||
transition: transform 200ms ease-in-out;
|
padding: 5px;
|
||||||
}
|
z-index: 101;
|
||||||
|
|
||||||
.lucide-menu {
|
.lucide-menu {
|
||||||
stroke: var(--darkgray);
|
stroke: var(--darkgray);
|
||||||
transition: transform 200ms ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
stroke: var(--dark);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
&.desktop-only {
|
overflow-y: hidden;
|
||||||
max-height: 40%;
|
flex: 0 1 auto;
|
||||||
|
&:has(button#toc.collapsed) {
|
||||||
|
flex: 0 1 1.2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,22 +46,7 @@ button#toc {
|
|||||||
|
|
||||||
#toc-content {
|
#toc-content {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
overflow: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
transition:
|
|
||||||
max-height 0.35s ease,
|
|
||||||
visibility 0s linear 0s;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
visibility: visible;
|
|
||||||
|
|
||||||
&.collapsed {
|
|
||||||
max-height: 0;
|
|
||||||
transition:
|
|
||||||
max-height 0.35s ease,
|
|
||||||
visibility 0s linear 0.35s;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
& ul {
|
& ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
@ -543,7 +543,6 @@ video {
|
|||||||
|
|
||||||
div:has(> .overflow) {
|
div:has(> .overflow) {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user