This commit is contained in:
Corey 2023-10-25 14:14:44 -04:00
commit e66f1edb83
41 changed files with 275 additions and 85 deletions

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM node:20-slim as builder
WORKDIR /usr/src/app
COPY package.json .
COPY package-lock.json* .
RUN npm ci
FROM node:20-slim
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app/ /usr/src/app/
COPY . .
CMD ["npx", "quartz", "build", "--serve"]

0
content/.gitkeep Normal file
View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

View File

@ -1,22 +0,0 @@
---
title: ""
---
This site attempts to inform the previous, current, and future work required to fulfill the requirements of the projects under the Logos Collective, a complete tech stack that provides infrastructure for the self-sovereign network state. To learn more about the motivation, please visit the [Logos Collective Site](https://logos.co).
This site is an ongoing work in progress. The links within are an attempt to capture a lot of moving targets. This means that the information here may or may not be the bleeding edge of what is true with respect to the development within the Logos Collective projects. Your use of this Website is subject to the following [[terms-of-use|terms of use]] which we ask you to read carefully prior to your use of the Website.
## Navigation
- [Monthly Project Reports](tags/monthly-report)
### Projects
- [Waku](waku/index.md)
- [Codex](codex/overview.md)
- [[nomos/index|Nomos]]
### Services
- [Vac](vac/index.md)
- [Comms (Acid Info)](acid/index.md)
- [[insight/index|Insight]]
### Skunk works
- [Innovation Lab](innovation_lab/index.md)

View File

@ -1,5 +0,0 @@
---
title: Components
---
Want to create your own custom component? Check out the advanced guide on [[creating components]] for more information.

View File

@ -247,7 +247,7 @@ If you are creating an emitter plugin that needs to render components, there are
- Your component should use `getQuartzComponents` to declare a list of `QuartzComponents` that it uses to construct the page. See the page on [[creating components]] for more information.
- You can use the `renderPage` function defined in `quartz/components/renderPage.tsx` to render Quartz components into HTML.
- If you need to render an HTML AST to JSX, you can use the `toJsxRuntime` function from `hast-util-to-jsx-runtime` library. An example of this can be found in `quartz/components/pages/Content.tsx`.
- If you need to render an HTML AST to JSX, you can use the `htmlToJsx` function from `quartz/util/jsx.ts`. An example of this can be found in `quartz/components/pages/Content.tsx`.
For example, the following is a simplified version of the content page plugin that renders every single page.

View File

@ -0,0 +1,7 @@
Quartz comes shipped with a Docker image that will allow you to preview your Quartz locally without installing Node.
You can run the below one-liner to run Quartz in Docker.
```sh
docker run --rm -itp 8080:8080 $(docker build -q .)
```

View File

@ -0,0 +1,35 @@
---
title: "Breadcrumbs"
tags:
- component
---
Breadcrumbs provide a way to navigate a hierarchy of pages within your site using a list of its parent folders.
By default, the element at the very top of your page is the breadcrumb navigation bar (can also be seen at the top on this page!).
## Customization
Most configuration can be done by passing in options to `Component.Breadcrumbs()`.
For example, here's what the default configuration looks like:
```typescript title="quartz.layout.ts"
Component.Breadcrumbs({
spacerSymbol: ">", // symbol between crumbs
rootName: "Home", // name of first/root element
resolveFrontmatterTitle: false, // wether to resolve folder names through frontmatter titles (more computationally expensive)
hideOnRoot: true, // wether to hide breadcrumbs on root `index.md` page
})
```
When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field.
You can also adjust where the breadcrumbs will be displayed by adjusting the [[layout]] (moving `Component.Breadcrumbs()` up or down)
Want to customize it even more?
- Removing breadcrumbs: delete all usages of `Component.Breadcrumbs()` from `quartz.layout.ts`.
- Component: `quartz/components/Breadcrumbs.tsx`
- Style: `quartz/components/styles/breadcrumbs.scss`
- Script: inline at `quartz/components/Breadcrumbs.tsx`

View File

@ -19,6 +19,7 @@
"profile": "0x -D prof ./quartz/bootstrap-cli.mjs build --concurrency=1"
},
"engines": {
"npm": ">=9.3.1",
"node": ">=18.14"
},
"keywords": [

View File

@ -15,7 +15,12 @@ export const sharedPageComponents: SharedLayout = {
// components for pages that display a single page (e.g. a single note)
export const defaultContentPageLayout: PageLayout = {
beforeBody: [Component.ArticleTitle(), Component.ContentMeta(), Component.TagList()],
beforeBody: [
Component.Breadcrumbs(),
Component.ArticleTitle(),
Component.ContentMeta(),
Component.TagList(),
],
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),

View File

@ -1,9 +1,9 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function ArticleTitle({ fileData }: QuartzComponentProps) {
function ArticleTitle({ fileData, displayClass }: QuartzComponentProps) {
const title = fileData.frontmatter?.title
if (title) {
return <h1 class="article-title">{title}</h1>
return <h1 class={`article-title ${displayClass ?? ""}`}>{title}</h1>
} else {
return null
}

View File

@ -2,11 +2,11 @@ import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/backlinks.scss"
import { resolveRelative, simplifySlug } from "../util/path"
function Backlinks({ fileData, allFiles }: QuartzComponentProps) {
function Backlinks({ fileData, allFiles, displayClass }: QuartzComponentProps) {
const slug = simplifySlug(fileData.slug!)
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
return (
<div class="backlinks">
<div class={`backlinks ${displayClass ?? ""}`}>
<h3>Backlinks</h3>
<ul class="overflow">
{backlinkFiles.length > 0 ? (

View File

@ -0,0 +1,117 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import breadcrumbsStyle from "./styles/breadcrumbs.scss"
import { FullSlug, SimpleSlug, resolveRelative } from "../util/path"
import { QuartzPluginData } from "../plugins/vfile"
type CrumbData = {
displayName: string
path: string
}
interface BreadcrumbOptions {
/**
* Symbol between crumbs
*/
spacerSymbol: string
/**
* Name of first crumb
*/
rootName: string
/**
* wether to look up frontmatter title for folders (could cause performance problems with big vaults)
*/
resolveFrontmatterTitle: boolean
/**
* Wether to display breadcrumbs on root `index.md`
*/
hideOnRoot: boolean
}
const defaultOptions: BreadcrumbOptions = {
spacerSymbol: ">",
rootName: "Home",
resolveFrontmatterTitle: false,
hideOnRoot: true,
}
function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData {
return {
displayName: displayName.replaceAll("-", " "),
path: resolveRelative(baseSlug, currentSlug),
}
}
// given a folderName (e.g. "features"), search for the corresponding `index.md` file
function findCurrentFile(allFiles: QuartzPluginData[], folderName: string) {
return allFiles.find((file) => {
if (file.slug?.endsWith("index")) {
const folderParts = file.filePath?.split("/")
if (folderParts) {
const name = folderParts[folderParts?.length - 2]
if (name === folderName) {
return true
}
}
}
})
}
export default ((opts?: Partial<BreadcrumbOptions>) => {
// Merge options with defaults
const options: BreadcrumbOptions = { ...defaultOptions, ...opts }
function Breadcrumbs({ fileData, allFiles, displayClass }: QuartzComponentProps) {
// Hide crumbs on root if enabled
if (options.hideOnRoot && fileData.slug === "index") {
return <></>
}
// Format entry for root element
const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug)
const crumbs: CrumbData[] = [firstEntry]
// Split slug into hierarchy/parts
const slugParts = fileData.slug?.split("/")
if (slugParts) {
// full path until current part
let currentPath = ""
for (let i = 0; i < slugParts.length - 1; i++) {
let currentTitle = slugParts[i]
// TODO: performance optimizations/memoizing
// Try to resolve frontmatter folder title
if (options?.resolveFrontmatterTitle) {
// try to find file for current path
const currentFile = findCurrentFile(allFiles, currentTitle)
if (currentFile) {
currentTitle = currentFile.frontmatter!.title
}
}
// Add current slug to full path
currentPath += slugParts[i] + "/"
// Format and add current crumb
const crumb = formatCrumb(currentTitle, fileData.slug!, currentPath as SimpleSlug)
crumbs.push(crumb)
}
// Add current file to crumb (can directly use frontmatter title)
crumbs.push({
displayName: fileData.frontmatter!.title,
path: "",
})
}
return (
<nav class={`breadcrumb-container ${displayClass ?? ""}`} aria-label="breadcrumbs">
{crumbs.map((crumb, index) => (
<div class="breadcrumb-element">
<a href={crumb.path}>{crumb.displayName}</a>
{index !== crumbs.length - 1 && <p>{` ${options.spacerSymbol} `}</p>}
</div>
))}
</nav>
)
}
Breadcrumbs.css = breadcrumbsStyle
return Breadcrumbs
}) satisfies QuartzComponentConstructor

View File

@ -14,7 +14,7 @@ export default (() => {
}
segments.push(timeTaken)
return <p class="content-meta">{segments.join(", ")}</p>
return <p class={`content-meta ${displayClass ?? ""}`}>{segments.join(", ")}</p>
} else {
return null
}

View File

@ -3,11 +3,11 @@
// see: https://v8.dev/features/modules#defer
import darkmodeScript from "./scripts/darkmode.inline"
import styles from "./styles/darkmode.scss"
import { QuartzComponentConstructor } from "./types"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function Darkmode() {
function Darkmode({ displayClass }: QuartzComponentProps) {
return (
<div class="darkmode">
<div class={`darkmode ${displayClass ?? ""}`}>
<input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex={-1} />
<label id="toggle-label-light" for="darkmode-toggle" tabIndex={-1}>
<svg

View File

@ -1,4 +1,4 @@
import { QuartzComponentConstructor } from "./types"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/footer.scss"
import { version } from "../../package.json"
@ -7,11 +7,11 @@ interface Options {
}
export default ((opts?: Options) => {
function Footer() {
function Footer({ displayClass }: QuartzComponentProps) {
const year = new Date().getFullYear()
const links = opts?.links ?? []
return (
<footer>
<footer class={`${displayClass ?? ""}`}>
<hr />
<p>
Created by Logos with <a href="https://quartz.jzhao.xyz/">Quartz v{version}</a>, © {year}

View File

@ -1,4 +1,4 @@
import { QuartzComponentConstructor } from "./types"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
// @ts-ignore
import script from "./scripts/graph.inline"
import style from "./styles/graph.scss"
@ -52,11 +52,11 @@ const defaultOptions: GraphOptions = {
}
export default ((opts?: GraphOptions) => {
function Graph() {
function Graph({ displayClass }: QuartzComponentProps) {
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
return (
<div class="graph">
<div class={`graph ${displayClass ?? ""}`}>
<h3>Graph View</h3>
<div class="graph-outer">
<div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>

View File

@ -1,11 +1,11 @@
import { pathToRoot } from "../util/path"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function PageTitle({ fileData, cfg }: QuartzComponentProps) {
function PageTitle({ fileData, cfg, displayClass }: QuartzComponentProps) {
const title = cfg?.pageTitle ?? "Untitled Quartz"
const baseDir = pathToRoot(fileData.slug!)
return (
<h1 class="page-title">
<h1 class={`page-title ${displayClass ?? ""}`}>
<a href={baseDir}>{title}</a>
</h1>
)

View File

@ -29,7 +29,7 @@ export default ((userOpts?: Partial<Options>) => {
const pages = allFiles.filter(opts.filter).sort(opts.sort)
const remaining = Math.max(0, pages.length - opts.limit)
return (
<div class={`recent-notes ${displayClass}`}>
<div class={`recent-notes ${displayClass ?? ""}`}>
<h3>{opts.title}</h3>
<ul class="recent-ul">
{pages.slice(0, opts.limit).map((page) => {

View File

@ -1,12 +1,12 @@
import { QuartzComponentConstructor } from "./types"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/search.scss"
// @ts-ignore
import script from "./scripts/search.inline"
export default (() => {
function Search() {
function Search({ displayClass }: QuartzComponentProps) {
return (
<div class="search">
<div class={`search ${displayClass ?? ""}`}>
<div id="search-icon">
<p>Search</p>
<div></div>

View File

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

View File

@ -19,7 +19,7 @@ function TableOfContents({ fileData, displayClass }: QuartzComponentProps) {
}
return (
<div class={`toc ${displayClass}`}>
<div class={`toc ${displayClass ?? ""}`}>
<button type="button" id="toc">
<h3>Table of Contents</h3>
<svg

View File

@ -1,12 +1,12 @@
import { pathToRoot, slugTag } from "../util/path"
import { QuartzComponentConstructor, QuartzComponentProps } from "./types"
function TagList({ fileData }: QuartzComponentProps) {
function TagList({ fileData, displayClass }: QuartzComponentProps) {
const tags = fileData.frontmatter?.tags
const baseDir = pathToRoot(fileData.slug!)
if (tags && tags.length > 0) {
return (
<ul class="tags">
<ul class={`tags ${displayClass ?? ""}`}>
{tags.map((tag) => {
const display = `#${tag}`
const linkDest = baseDir + `/tags/${slugTag(tag)}`
@ -32,6 +32,12 @@ TagList.css = `
padding-left: 0;
gap: 0.4rem;
margin: 1rem 0;
flex-wrap: wrap;
justify-self: end;
}
.section-li > .section > .tags {
justify-content: flex-end;
}
.tags > li {
@ -41,7 +47,7 @@ TagList.css = `
overflow-wrap: normal;
}
a.tag-link {
a.internal.tag-link {
border-radius: 8px;
background-color: var(--highlight);
padding: 0.2rem 0.4rem;

View File

@ -18,6 +18,7 @@ import Footer from "./Footer"
import DesktopOnly from "./DesktopOnly"
import MobileOnly from "./MobileOnly"
import RecentNotes from "./RecentNotes"
import Breadcrumbs from "./Breadcrumbs"
export {
ArticleTitle,

View File

@ -1,10 +1,8 @@
import { htmlToJsx } from "../../util/jsx"
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
function Content({ tree }: QuartzComponentProps) {
// @ts-ignore (preact makes it angry)
const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
function Content({ fileData, tree }: QuartzComponentProps) {
const content = htmlToJsx(fileData.filePath!, tree)
return <article class="popover-hint">{content}</article>
}

View File

@ -1,6 +1,4 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import path from "path"
import style from "../styles/listPage.scss"
@ -29,8 +27,7 @@ function FolderContent(props: QuartzComponentProps) {
const content =
(tree as Root).children.length === 0
? fileData.description
: // @ts-ignore
toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
: htmlToJsx(fileData.filePath!, tree)
return (
<div class="popover-hint">

View File

@ -1,6 +1,4 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import style from "../styles/listPage.scss"
import { PageList } from "../PageList"
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
@ -26,8 +24,7 @@ function TagContent(props: QuartzComponentProps) {
const content =
(tree as Root).children.length === 0
? fileData.description
: // @ts-ignore
toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
: htmlToJsx(fileData.filePath!, tree)
if (tag === "") {
const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]

View File

@ -303,7 +303,6 @@ document.addEventListener("nav", async (e: unknown) => {
// setup index if it hasn't been already
if (!index) {
index = new Document({
cache: true,
charset: "latin:extra",
optimize: true,
encode: encoder,

View File

@ -20,6 +20,7 @@ const isLocalUrl = (href: string) => {
const getOpts = ({ target }: Event): { url: URL; scroll?: boolean } | undefined => {
if (!isElement(target)) return
if (target.attributes.getNamedItem("target")?.value === "_blank") return
const a = target.closest("a")
if (!a) return
if ("routerIgnore" in a.dataset) return
@ -92,7 +93,7 @@ function createRouter() {
if (typeof window !== "undefined") {
window.addEventListener("click", async (event) => {
const { url } = getOpts(event) ?? {}
if (!url) return
if (!url || event.ctrlKey || event.metaKey) return
event.preventDefault()
try {
navigate(url, false)

View File

@ -0,0 +1,22 @@
.breadcrumb-container {
margin: 0;
margin-top: 0.75rem;
padding: 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 0.5rem;
}
.breadcrumb-element {
p {
margin: 0;
margin-left: 0.5rem;
padding: 0;
line-height: normal;
}
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}

View File

@ -19,11 +19,6 @@ li.section-li {
}
}
& > .tags {
justify-self: end;
margin-left: 1rem;
}
& > .desc > h3 > a {
background-color: transparent;
}

View File

@ -7,7 +7,7 @@ import spaRouterScript from "../../components/scripts/spa.inline"
import plausibleScript from "../../components/scripts/plausible.inline"
// @ts-ignore
import popoverScript from "../../components/scripts/popover.inline"
import styles from "../../styles/base.scss"
import styles from "../../styles/custom.scss"
import popoverStyle from "../../components/styles/popover.scss"
import { BuildCtx } from "../../util/ctx"
import { StaticResources } from "../../util/resources"
@ -164,7 +164,7 @@ export const ComponentResources: QuartzEmitterPlugin<Options> = (opts?: Partial<
addGlobalPageResources(ctx, resources, componentResources)
const stylesheet = joinStyles(ctx.cfg.configuration.theme, styles, ...componentResources.css)
const stylesheet = joinStyles(ctx.cfg.configuration.theme, ...componentResources.css, styles)
const prescript = joinScripts(componentResources.beforeDOMLoaded)
const postscript = joinScripts(componentResources.afterDOMLoaded)
const fps = await Promise.all([

View File

@ -37,6 +37,11 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options> | undefined>
data.tags = data.tag
}
// coerce title to string
if (data.title) {
data.title = data.title.toString()
}
if (data.tags && !Array.isArray(data.tags)) {
data.tags = data.tags
.toString()

View File

@ -18,11 +18,13 @@ interface Options {
markdownLinkResolution: TransformOptions["strategy"]
/** Strips folders from a link so that it looks nice */
prettyLinks: boolean
openLinksInNewTab: boolean
}
const defaultOptions: Options = {
markdownLinkResolution: "absolute",
prettyLinks: true,
openLinksInNewTab: false,
}
export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
@ -52,6 +54,10 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
node.properties.className ??= []
node.properties.className.push(isAbsoluteUrl(dest) ? "external" : "internal")
if (opts.openLinksInNewTab) {
node.properties.target = "_blank"
}
// don't process external links or intra-document anchors
const isInternal = !(isAbsoluteUrl(dest) || dest.startsWith("#"))
if (isInternal) {

View File

@ -14,6 +14,7 @@ import { FilePath, pathToRoot, slugTag, slugifyFilePath } from "../../util/path"
import { toHast } from "mdast-util-to-hast"
import { toHtml } from "hast-util-to-html"
import { PhrasingContent } from "mdast-util-find-and-replace/lib"
import { capitalize } from "../../util/lang"
export interface Options {
comments: boolean

View File

@ -1,7 +1,6 @@
@use "./custom.scss";
@use "./variables.scss" as *;
@use "./syntax.scss";
@use "./callouts.scss";
@use "./variables.scss" as *;
html {
scroll-behavior: smooth;
@ -95,6 +94,8 @@ a {
}
& article {
position: relative;
& > h1 {
font-size: 2rem;
}

View File

@ -1,2 +1,4 @@
@use "./base.scss";
// put your custom CSS here!

15
quartz/util/jsx.ts Normal file
View File

@ -0,0 +1,15 @@
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import { QuartzPluginData } from "../plugins/vfile"
import { Node, Root } from "hast"
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { trace } from "./trace"
import { type FilePath } from "./path"
export function htmlToJsx(fp: FilePath, tree: Node<QuartzPluginData>) {
try {
// @ts-ignore (preact makes it angry)
return toJsxRuntime(tree as Root, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
} catch (e) {
trace(`Failed to parse Markdown in \`${fp}\` into JSX`, e as Error)
}
}

View File

@ -4,7 +4,7 @@ import { isMainThread } from "workerpool"
const rootFile = /.*at file:/
export function trace(msg: string, err: Error) {
const stack = err.stack
let stack = err.stack ?? ""
const lines: string[] = []
@ -12,15 +12,11 @@ export function trace(msg: string, err: Error) {
lines.push(
"\n" +
chalk.bgRed.black.bold(" ERROR ") +
"\n" +
"\n\n" +
chalk.red(` ${msg}`) +
(err.message.length > 0 ? `: ${err.message}` : ""),
)
if (!stack) {
return
}
let reachedEndOfLegibleTrace = false
for (const line of stack.split("\n").slice(1)) {
if (reachedEndOfLegibleTrace) {