more visual polish, adjust colours and spacing

This commit is contained in:
Jacky Zhao 2023-07-04 16:48:36 -07:00
parent 1547c8af0d
commit a90142ba2d
22 changed files with 173 additions and 140 deletions

View File

@ -22,6 +22,7 @@ const contentPageLayout: PageLayout = {
], ],
left: [ left: [
Component.PageTitle(), Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Search(), Component.Search(),
Component.Darkmode(), Component.Darkmode(),
Component.DesktopOnly(Component.TableOfContents()), Component.DesktopOnly(Component.TableOfContents()),
@ -38,6 +39,7 @@ const listPageLayout: PageLayout = {
], ],
left: [ left: [
Component.PageTitle(), Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
Component.Search(), Component.Search(),
Component.Darkmode() Component.Darkmode()
], ],
@ -63,8 +65,8 @@ const config: QuartzConfig = {
colors: { colors: {
lightMode: { lightMode: {
light: '#faf8f8', light: '#faf8f8',
lightgray: '#e8e8e8', lightgray: '#e5e5e5',
gray: '#dadada', gray: '#b8b8b8',
darkgray: '#4e4e4e', darkgray: '#4e4e4e',
dark: '#141021', dark: '#141021',
secondary: '#284b63', secondary: '#284b63',
@ -73,8 +75,8 @@ const config: QuartzConfig = {
}, },
darkMode: { darkMode: {
light: '#161618', light: '#161618',
lightgray: '#292629', lightgray: '#393639',
gray: '#343434', gray: '#646464',
darkgray: '#d4d4d4', darkgray: '#d4d4d4',
dark: '#fbfffe', dark: '#fbfffe',
secondary: '#7b97aa', secondary: '#7b97aa',

View File

@ -8,7 +8,7 @@ function Backlinks({ fileData, allFiles }: QuartzComponentProps) {
const backlinkFiles = allFiles.filter(file => file.links?.includes(slug)) const backlinkFiles = allFiles.filter(file => file.links?.includes(slug))
return <div class="backlinks"> return <div class="backlinks">
<h3>Backlinks</h3> <h3>Backlinks</h3>
<ul> <ul class="overflow">
{backlinkFiles.length > 0 ? {backlinkFiles.length > 0 ?
backlinkFiles.map(f => <li><a href={stripIndex(relativeToRoot(slug, f.slug!))} class="internal">{f.frontmatter?.title}</a></li>) backlinkFiles.map(f => <li><a href={stripIndex(relativeToRoot(slug, f.slug!))} class="internal">{f.frontmatter?.title}</a></li>)
: <li>No backlinks found</li>} : <li>No backlinks found</li>}

View File

@ -4,9 +4,7 @@ export default ((component?: QuartzComponent) => {
if (component) { if (component) {
const Component = component const Component = component
function DesktopOnly(props: QuartzComponentProps) { function DesktopOnly(props: QuartzComponentProps) {
return <div class="desktop-only"> return <Component displayClass="desktop-only" {...props} />
<Component {...props} />
</div>
} }
DesktopOnly.displayName = component.displayName DesktopOnly.displayName = component.displayName

View File

@ -11,15 +11,13 @@ export default ((opts?: Options) => {
const year = new Date().getFullYear() const year = new Date().getFullYear()
const name = opts?.authorName ?? "someone" const name = opts?.authorName ?? "someone"
const links = opts?.links ?? [] const links = opts?.links ?? []
return <> return <footer>
<hr /> <hr />
<footer> <p>Made by {name} using <a href="https://quartz.jzhao.xyz/">Quartz</a>, © {year}</p>
<p>Made by {name} using <a href="https://quartz.jzhao.xyz/">Quartz</a>, © {year}</p> <ul>{Object.entries(links).map(([text, link]) => <li>
<ul>{Object.entries(links).map(([text, link]) => <li> <a href={link}>{text}</a>
<a href={link}>{text}</a> </li>)}</ul>
</li>)}</ul> </footer>
</footer>
</>
} }
Footer.css = style Footer.css = style

View File

@ -50,7 +50,7 @@ export default ((opts?: GraphOptions) => {
const localGraph = { ...opts?.localGraph, ...defaultOptions.localGraph } const localGraph = { ...opts?.localGraph, ...defaultOptions.localGraph }
const globalGraph = { ...opts?.globalGraph, ...defaultOptions.globalGraph } const globalGraph = { ...opts?.globalGraph, ...defaultOptions.globalGraph }
return <div class="graph"> return <div class="graph">
<h3>Interactive Graph</h3> <h3>Site Graph</h3>
<div class="graph-outer"> <div class="graph-outer">
<div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div> <div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
<svg version="1.1" id="global-graph-icon" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px" <svg version="1.1" id="global-graph-icon" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px"

View File

@ -4,9 +4,7 @@ export default ((component?: QuartzComponent) => {
if (component) { if (component) {
const Component = component const Component = component
function MobileOnly(props: QuartzComponentProps) { function MobileOnly(props: QuartzComponentProps) {
return <div class="mobile-only"> return <Component displayClass="mobile-only" {...props} />
<Component {...props} />
</div>
} }
MobileOnly.displayName = component.displayName MobileOnly.displayName = component.displayName

View File

@ -14,7 +14,7 @@ function ReadingTime({ fileData }: QuartzComponentProps) {
ReadingTime.css = ` ReadingTime.css = `
.reading-time { .reading-time {
margin-top: 0; margin-top: 0;
opacity: 0.5; color: var(--gray);
} }
` `

View File

@ -1,7 +1,8 @@
import { QuartzComponentConstructor } from "./types" import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function Spacer() { function Spacer({ displayClass }: QuartzComponentProps) {
return <div class="spacer"></div> const className = displayClass ? `spacer ${displayClass}` : "spacer"
return <div class={className}></div>
} }
export default (() => Spacer) satisfies QuartzComponentConstructor export default (() => Spacer) satisfies QuartzComponentConstructor

View File

@ -26,7 +26,7 @@ function TableOfContents({ fileData }: QuartzComponentProps) {
</svg> </svg>
</button> </button>
<div id="toc-content"> <div id="toc-content">
<ul> <ul class="overflow">
{fileData.toc.map(tocEntry => <li key={tocEntry.slug} class={`depth-${tocEntry.depth}`}> {fileData.toc.map(tocEntry => <li key={tocEntry.slug} class={`depth-${tocEntry.depth}`}>
<a href={`#${tocEntry.slug}`} data-for={tocEntry.slug}>{tocEntry.text}</a> <a href={`#${tocEntry.slug}`} data-for={tocEntry.slug}>{tocEntry.text}</a>
</li>)} </li>)}

View File

@ -27,7 +27,6 @@ function FolderContent(props: QuartzComponentProps) {
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' }) const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
return <div class="popover-hint"> return <div class="popover-hint">
<article>{content}</article> <article>{content}</article>
<hr/>
<p>{allPagesInFolder.length} items under this folder.</p> <p>{allPagesInFolder.length} items under this folder.</p>
<div> <div>
<PageList {...listProps} /> <PageList {...listProps} />

View File

@ -21,7 +21,6 @@ function TagContent(props: QuartzComponentProps) {
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' }) const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' })
return <div class="popover-hint"> return <div class="popover-hint">
<article>{content}</article> <article>{content}</article>
<hr/>
<p>{allPagesWithTag.length} items with this tag.</p> <p>{allPagesWithTag.length} items with this tag.</p>
<div> <div>
<PageList {...listProps} /> <PageList {...listProps} />

View File

@ -55,22 +55,22 @@ export function renderPage(slug: string, componentData: QuartzComponentProps, co
<Head {...componentData} /> <Head {...componentData} />
<body data-slug={slug}> <body data-slug={slug}>
<div id="quartz-root" class="page"> <div id="quartz-root" class="page">
<div class="page-header">
<Header {...componentData} >
{header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
</Header>
<div class="popover-hint">
{beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
</div>
</div>
<Body {...componentData}> <Body {...componentData}>
{LeftComponent} {LeftComponent}
<div class="center"> <div class="center">
<div class="page-header">
<Header {...componentData} >
{header.map(HeaderComponent => <HeaderComponent {...componentData} />)}
</Header>
<div class="popover-hint">
{beforeBody.map(BodyComponent => <BodyComponent {...componentData} />)}
</div>
</div>
<Content {...componentData} /> <Content {...componentData} />
<Footer {...componentData} />
</div> </div>
{RightComponent} {RightComponent}
</Body> </Body>
<Footer {...componentData} />
</div> </div>
</body> </body>
{pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(res => JSResourceToScriptElement(res))} {pageResources.js.filter(resource => resource.loadTime === "afterDOMReady").map(res => JSResourceToScriptElement(res))}

View File

@ -225,7 +225,7 @@ async function renderGraph(container: string, slug: string) {
const labels = graphNode const labels = graphNode
.append("text") .append("text")
.attr("dx", 0) .attr("dx", 0)
.attr("dy", (d) => nodeRadius(d) - 8 + "px") .attr("dy", (d) => -nodeRadius(d) + "px")
.attr("text-anchor", "middle") .attr("text-anchor", "middle")
.text((d) => data[d.id]?.title || (d.id.charAt(1).toUpperCase() + d.id.slice(2)).replace("-", " ")) .text((d) => data[d.id]?.title || (d.id.charAt(1).toUpperCase() + d.id.slice(2)).replace("-", " "))
.style('opacity', (opacityScale - 1) / 3.75) .style('opacity', (opacityScale - 1) / 3.75)
@ -297,14 +297,3 @@ document.addEventListener("nav", async (e: unknown) => {
containerIcon?.addEventListener("click", renderGlobalGraph) containerIcon?.addEventListener("click", renderGlobalGraph)
}) })
let resizeEventDebounce: number | undefined = undefined
window.addEventListener('resize', () => {
if (resizeEventDebounce) {
clearTimeout(resizeEventDebounce)
}
resizeEventDebounce = window.setTimeout(async () => {
const slug = document.body.dataset["slug"]!
await renderGraph("graph-container", slug)
}, 50)
})

View File

@ -58,38 +58,7 @@ const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/
document.addEventListener("nav", async (e: unknown) => { document.addEventListener("nav", async (e: unknown) => {
const currentSlug = (e as CustomEventMap["nav"]).detail.url const currentSlug = (e as CustomEventMap["nav"]).detail.url
// setup index if it hasn't been already
const data = await fetchData const data = await fetchData
if (!index) {
index = new Document({
cache: true,
charset: 'latin:extra',
optimize: true,
encode: encoder,
document: {
id: "slug",
index: [
{
field: "title",
tokenize: "forward",
},
{
field: "content",
tokenize: "reverse",
},
]
},
})
for (const [slug, fileData] of Object.entries<ContentDetails>(data)) {
await index.addAsync(slug, {
slug,
title: fileData.title,
content: fileData.content
})
}
}
const container = document.getElementById("search-container") const container = document.getElementById("search-container")
const searchIcon = document.getElementById("search-icon") const searchIcon = document.getElementById("search-icon")
const searchBar = document.getElementById("search-bar") as HTMLInputElement | null const searchBar = document.getElementById("search-bar") as HTMLInputElement | null
@ -177,6 +146,37 @@ document.addEventListener("nav", async (e: unknown) => {
searchBar?.removeEventListener("input", onType) searchBar?.removeEventListener("input", onType)
searchBar?.addEventListener("input", onType) searchBar?.addEventListener("input", onType)
// setup index if it hasn't been already
if (!index) {
index = new Document({
cache: true,
charset: 'latin:extra',
optimize: true,
encode: encoder,
document: {
id: "slug",
index: [
{
field: "title",
tokenize: "forward",
},
{
field: "content",
tokenize: "reverse",
},
]
},
})
for (const [slug, fileData] of Object.entries<ContentDetails>(data)) {
await index.addAsync(slug, {
slug,
title: fileData.title,
content: fileData.content
})
}
}
// register handlers // register handlers
registerEscapeHandler(container, hideSearch) registerEscapeHandler(container, hideSearch)
}) })

View File

@ -2,7 +2,7 @@
position: relative; position: relative;
width: 20px; width: 20px;
height: 20px; height: 20px;
margin: 1rem; margin: 0 10px;
& > .toggle { & > .toggle {
display: none; display: none;

View File

@ -1,6 +1,5 @@
footer { footer {
text-align: left; text-align: left;
opacity: 0.8;
margin-bottom: 4rem; margin-bottom: 4rem;
& ul { & ul {

View File

@ -29,11 +29,11 @@
line-height: normal; line-height: normal;
font-size: initial; font-size: initial;
font-family: var(--bodyFont); font-family: var(--bodyFont);
border: 1px solid var(--gray); border: 1px solid var(--lightgray);
background-color: var(--light); background-color: var(--light);
border-radius: 5px; border-radius: 5px;
box-shadow: 6px 6px 36px 0 rgba(0,0,0,0.25); box-shadow: 6px 6px 36px 0 rgba(0,0,0,0.25);
overflow: scroll; overflow: auto;
} }
h1 { h1 {

View File

@ -12,6 +12,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
white-space: nowrap;
& > div { & > div {
flex-grow: 1; flex-grow: 1;
@ -38,12 +39,13 @@
& > #search-container { & > #search-container {
position: fixed; position: fixed;
contain: layout;
z-index: 999; z-index: 999;
left: 0; left: 0;
top: 0; top: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
overflow: scroll; overflow-y: auto;
display: none; display: none;
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
@ -57,7 +59,7 @@
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@media all and (max-width: $tabletBreakpoint) { @media all and (max-width: $fullPageWidth) {
width: 90%; width: 90%;
} }

View File

@ -29,7 +29,7 @@ button#toc {
list-style: none; list-style: none;
overflow: hidden; overflow: hidden;
max-height: none; max-height: none;
transition: max-height 0.3s ease; transition: max-height 0.5s ease;
& ul { & ul {
list-style: none; list-style: none;

View File

@ -11,6 +11,9 @@ export type QuartzComponentProps = {
children: (QuartzComponent | JSX.Element)[] children: (QuartzComponent | JSX.Element)[]
tree: Node<QuartzPluginData> tree: Node<QuartzPluginData>
allFiles: QuartzPluginData[] allFiles: QuartzPluginData[]
displayClass?: 'mobile-only' | 'desktop-only'
} & JSX.IntrinsicAttributes & {
[key: string]: any
} }
export type QuartzComponent = ComponentType<QuartzComponentProps> & { export type QuartzComponent = ComponentType<QuartzComponentProps> & {

View File

@ -43,71 +43,23 @@ a {
} }
} }
.page {
& > .page-header {
max-width: $pageWidth;
margin: $topSpacing auto 0 auto;
}
& > #quartz-body {
width: 100%;
display: flex;
& .left, & .right {
flex: 1;
width: calc(calc(100vw - $pageWidth) / 2);
}
& .left-inner, & .right-inner {
display: flex;
flex-direction: column;
gap: 2rem;
top: 0;
width: $sidePanelWidth;
margin-top: $topSpacing;
box-sizing: border-box;
padding: 0 4rem;
position: fixed;
}
& .left-inner {
left: calc(calc(100vw - $pageWidth) / 2 - $sidePanelWidth);
}
& .right-inner {
right: calc(calc(100vw - $pageWidth) / 2 - $sidePanelWidth);
}
& .center {
width: $pageWidth;
margin: 0 auto;
}
}
}
.desktop-only { .desktop-only {
display: initial; display: initial;
@media all and (max-width: ($pageWidth + 2 * $sidePanelWidth)) { @media all and (max-width: $fullPageWidth) {
display: none; display: none;
} }
} }
.mobile-only { .mobile-only {
display: none; display: none;
@media all and (max-width: ($pageWidth + 2 * $sidePanelWidth)) { @media all and (max-width: $fullPageWidth) {
display: initial; display: initial;
} }
} }
.page { .page {
@media all and (max-width: $tabletBreakpoint) { @media all and (max-width: $fullPageWidth) {
margin: 25px 5vw; margin: 0 5vw;
& .left, & .right {
padding: 0;
height: initial;
max-width: none;
position: initial;
}
} }
& p { & p {
@ -129,6 +81,78 @@ a {
padding-left: 0; padding-left: 0;
} }
} }
& > #quartz-body {
width: 100%;
display: flex;
@media all and (max-width: $fullPageWidth) {
flex-direction: column;
}
& .left, & .right {
flex: 1;
width: calc(calc(100vw - $pageWidth) / 2);
@media all and (max-width: $fullPageWidth) {
width: initial;
}
}
& .left-inner, & .right-inner {
display: flex;
flex-direction: column;
gap: 2rem;
top: 0;
width: $sidePanelWidth;
margin-top: $topSpacing;
box-sizing: border-box;
padding: 0 4rem;
position: fixed;
@media all and (max-width: $fullPageWidth) {
position: initial;
flex-direction: row;
padding: 0;
width: initial;
margin-top: 4rem;
}
}
& .left-inner {
left: calc(calc(100vw - $pageWidth) / 2 - $sidePanelWidth);
@media all and (max-width: $fullPageWidth) {
gap: 1rem;
align-items: center;
}
}
& .right-inner {
right: calc(calc(100vw - $pageWidth) / 2 - $sidePanelWidth);
& > * {
@media all and (max-width: $fullPageWidth) {
flex: 1;
}
}
}
}
& .page-header {
width: $pageWidth;
margin: $topSpacing auto 0 auto;
@media all and (max-width: $fullPageWidth) {
width: initial;
margin-top: 2rem;
}
}
& .center, & footer {
width: $pageWidth;
margin-left: auto;
margin-right: auto;
@media all and (max-width: $fullPageWidth) {
width: initial;
margin-left: 0;
margin-right: 0;
}
}
} }
input[type="checkbox"] { input[type="checkbox"] {
@ -200,7 +224,7 @@ pre {
font-family: var(--codeFont); font-family: var(--codeFont);
padding: 0.5rem; padding: 0.5rem;
border-radius: 5px; border-radius: 5px;
overflow-x: scroll; overflow-x: auto;
border: 1px solid var(--lightgray); border: 1px solid var(--lightgray);
& > code { & > code {
@ -301,3 +325,23 @@ audio, video {
.spacer { .spacer {
flex: 1 1 auto; flex: 1 1 auto;
} }
ul.overflow, ol.overflow {
height: 400px;
overflow-y: scroll;
& > li:last-of-type {
margin-bottom: 50px;
}
&:after {
pointer-events: none;
content: '';
width: 100%;
height: 50px;
position: absolute;
left: 0;
bottom: 0;
background: linear-gradient(transparent 0px, var(--light));
}
}

View File

@ -1,5 +1,6 @@
$pageWidth: 800px; $pageWidth: 750px;
$mobileBreakpoint: 600px; $mobileBreakpoint: 600px;
$tabletBreakpoint: 1200px; $tabletBreakpoint: 1200px;
$sidePanelWidth: 400px; $sidePanelWidth: 400px;
$topSpacing: 6rem; $topSpacing: 6rem;
$fullPageWidth: $pageWidth + 2 * $sidePanelWidth