Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] v4 from jackyzha0:v4 #33

Merged
merged 4 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@jackyzha0/quartz",
"description": "🌱 publish your digital garden and notes as a website",
"private": true,
"version": "4.1.1",
"version": "4.1.2",
"type": "module",
"author": "jackyzha0 <[email protected]>",
"license": "MIT",
Expand Down
33 changes: 24 additions & 9 deletions quartz/components/renderPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { QuartzComponent, QuartzComponentProps } from "./types"
import HeaderConstructor from "./Header"
import BodyConstructor from "./Body"
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
import { FullSlug, RelativeURL, joinSegments } from "../util/path"
import { FullSlug, RelativeURL, joinSegments, normalizeHastElement } from "../util/path"
import { visit } from "unist-util-visit"
import { Root, Element, ElementContent } from "hast"
import { QuartzPluginData } from "../plugins/vfile"

interface RenderComponents {
head: QuartzComponent
Expand Down Expand Up @@ -49,6 +50,18 @@ export function pageResources(
}
}

let pageIndex: Map<FullSlug, QuartzPluginData> | undefined = undefined
function getOrComputeFileIndex(allFiles: QuartzPluginData[]): Map<FullSlug, QuartzPluginData> {
if (!pageIndex) {
pageIndex = new Map()
for (const file of allFiles) {
pageIndex.set(file.slug!, file)
}
}

return pageIndex
}

export function renderPage(
slug: FullSlug,
componentData: QuartzComponentProps,
Expand All @@ -62,17 +75,15 @@ export function renderPage(
if (classNames.includes("transclude")) {
const inner = node.children[0] as Element
const transcludeTarget = inner.properties?.["data-slug"] as FullSlug

// TODO: avoid this expensive find operation and construct an index ahead of time
const page = componentData.allFiles.find((f) => f.slug === transcludeTarget)
const page = getOrComputeFileIndex(componentData.allFiles).get(transcludeTarget)
if (!page) {
return
}

let blockRef = node.properties?.dataBlock as string | undefined
if (blockRef?.startsWith("^")) {
if (blockRef?.startsWith("#^")) {
// block transclude
blockRef = blockRef.slice(1)
blockRef = blockRef.slice("#^".length)
let blockNode = page.blocks?.[blockRef]
if (blockNode) {
if (blockNode.tagName === "li") {
Expand All @@ -84,7 +95,7 @@ export function renderPage(
}

node.children = [
blockNode,
normalizeHastElement(blockNode, slug, transcludeTarget),
{
type: "element",
tagName: "a",
Expand Down Expand Up @@ -117,7 +128,9 @@ export function renderPage(
}

node.children = [
...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]),
...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]).map((child) =>
normalizeHastElement(child as Element, slug, transcludeTarget),
),
{
type: "element",
tagName: "a",
Expand All @@ -135,7 +148,9 @@ export function renderPage(
{ type: "text", value: page.frontmatter?.title ?? `Transclude of ${page.slug}` },
],
},
...(page.htmlAst.children as ElementContent[]),
...(page.htmlAst.children as ElementContent[]).map((child) =>
normalizeHastElement(child as Element, slug, transcludeTarget),
),
{
type: "element",
tagName: "a",
Expand Down
28 changes: 15 additions & 13 deletions quartz/components/scripts/graph.inline.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ContentDetails } from "../../plugins/emitters/contentIndex"
import type { ContentDetails, ContentIndex } from "../../plugins/emitters/contentIndex"
import * as d3 from "d3"
import { registerEscapeHandler, removeAllChildren } from "./util"
import { FullSlug, SimpleSlug, getFullSlug, resolveRelative, simplifySlug } from "../../util/path"
Expand Down Expand Up @@ -46,20 +46,22 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
showTags,
} = JSON.parse(graph.dataset["cfg"]!)

const data = await fetchData

const data: Map<SimpleSlug, ContentDetails> = new Map(
Object.entries<ContentDetails>(await fetchData).map(([k, v]) => [
simplifySlug(k as FullSlug),
v,
]),
)
const links: LinkData[] = []
const tags: SimpleSlug[] = []

const validLinks = new Set(Object.keys(data).map((slug) => simplifySlug(slug as FullSlug)))

for (const [src, details] of Object.entries<ContentDetails>(data)) {
const source = simplifySlug(src as FullSlug)
const validLinks = new Set(data.keys())
for (const [source, details] of data.entries()) {
const outgoing = details.links ?? []

for (const dest of outgoing) {
if (validLinks.has(dest)) {
links.push({ source, target: dest })
links.push({ source: source, target: dest })
}
}

Expand All @@ -71,7 +73,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
tags.push(...localTags.filter((tag) => !tags.includes(tag)))

for (const tag of localTags) {
links.push({ source, target: tag })
links.push({ source: source, target: tag })
}
}
}
Expand All @@ -93,17 +95,17 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
}
}
} else {
Object.keys(data).forEach((id) => neighbourhood.add(simplifySlug(id as FullSlug)))
validLinks.forEach((id) => neighbourhood.add(id))
if (showTags) tags.forEach((tag) => neighbourhood.add(tag))
}

const graphData: { nodes: NodeData[]; links: LinkData[] } = {
nodes: [...neighbourhood].map((url) => {
const text = url.startsWith("tags/") ? "#" + url.substring(5) : data[url]?.title ?? url
const text = url.startsWith("tags/") ? "#" + url.substring(5) : data.get(url)?.title ?? url
return {
id: url,
text: text,
tags: data[url]?.tags ?? [],
tags: data.get(url)?.tags ?? [],
}
}),
links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)),
Expand Down Expand Up @@ -200,7 +202,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
window.spaNavigate(new URL(targ, window.location.toString()))
})
.on("mouseover", function (_, d) {
const neighbours: SimpleSlug[] = data[fullSlug].links ?? []
const neighbours: SimpleSlug[] = data.get(slug)?.links ?? []
const neighbourNodes = d3
.selectAll<HTMLElement, NodeData>(".node")
.filter((d) => neighbours.includes(d.id))
Expand Down
12 changes: 7 additions & 5 deletions quartz/plugins/transformers/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,16 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options> | undefined> =
// WHATWG equivalent https://nodejs.dev/en/api/v18/url/#urlresolvefrom-to
const url = new URL(dest, `https://base.com/${curSlug}`)
const canonicalDest = url.pathname
const [destCanonical, _destAnchor] = splitAnchor(canonicalDest)
let [destCanonical, _destAnchor] = splitAnchor(canonicalDest)
if (destCanonical.endsWith("/")) {
destCanonical += "index"
}

// need to decodeURIComponent here as WHATWG URL percent-encodes everything
const simple = decodeURIComponent(
simplifySlug(destCanonical as FullSlug),
) as SimpleSlug
const full = decodeURIComponent(_stripSlashes(destCanonical, true)) as FullSlug
const simple = simplifySlug(full)
outgoing.add(simple)
node.properties["data-slug"] = simple
node.properties["data-slug"] = full
}

// rewrite link internals if prettylinks is on
Expand Down
3 changes: 2 additions & 1 deletion quartz/plugins/transformers/ofm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
const [rawFp, rawHeader, rawAlias] = capture
const fp = rawFp ?? ""
const anchor = rawHeader?.trim().replace(/^#+/, "")
const displayAnchor = anchor ? `#${slugAnchor(anchor)}` : ""
const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : ""
const displayAnchor = anchor ? `#${blockRef}${slugAnchor(anchor)}` : ""
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
const embedDisplay = value.startsWith("!") ? "!" : ""
return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]`
Expand Down
2 changes: 1 addition & 1 deletion quartz/util/path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe("transforms", () => {
test("simplifySlug", () => {
asserts(
[
["index", ""],
["index", "/"],
["abc", "abc"],
["abc/index", "abc/"],
["abc/def", "abc/def"],
Expand Down
47 changes: 38 additions & 9 deletions quartz/util/path.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { slug } from "github-slugger"
import type { Element as HastElement } from "hast"
// this file must be isomorphic so it can't use node libs (e.g. path)

export const QUARTZ = "quartz"
Expand All @@ -24,7 +25,7 @@ export function isFullSlug(s: string): s is FullSlug {
/** Shouldn't be a relative path and shouldn't have `/index` as an ending or a file extension. It _can_ however have a trailing slash to indicate a folder path. */
export type SimpleSlug = SlugLike<"simple">
export function isSimpleSlug(s: string): s is SimpleSlug {
const validStart = !(s.startsWith(".") || s.startsWith("/"))
const validStart = !(s.startsWith(".") || (s.length > 1 && s.startsWith("/")))
const validEnding = !(s.endsWith("/index") || s === "index")
return validStart && !_containsForbiddenCharacters(s) && validEnding && !_hasFileExtension(s)
}
Expand Down Expand Up @@ -65,7 +66,8 @@ export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug {
}

export function simplifySlug(fp: FullSlug): SimpleSlug {
return _stripSlashes(_trimSuffix(fp, "index"), true) as SimpleSlug
const res = _stripSlashes(_trimSuffix(fp, "index"), true)
return (res.length === 0 ? "/" : res) as SimpleSlug
}

export function transformInternalLink(link: string): RelativeURL {
Expand All @@ -86,20 +88,47 @@ export function transformInternalLink(link: string): RelativeURL {

// from micromorph/src/utils.ts
// https://github.com/natemoo-re/micromorph/blob/main/src/utils.ts#L5
const _rebaseHtmlElement = (el: Element, attr: string, newBase: string | URL) => {
const rebased = new URL(el.getAttribute(attr)!, newBase)
el.setAttribute(attr, rebased.pathname + rebased.hash)
}
export function normalizeRelativeURLs(el: Element | Document, destination: string | URL) {
const rebase = (el: Element, attr: string, newBase: string | URL) => {
const rebased = new URL(el.getAttribute(attr)!, newBase)
el.setAttribute(attr, rebased.pathname + rebased.hash)
}

el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) =>
rebase(item, "href", destination),
_rebaseHtmlElement(item, "href", destination),
)
el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) =>
rebase(item, "src", destination),
_rebaseHtmlElement(item, "src", destination),
)
}

const _rebaseHastElement = (
el: HastElement,
attr: string,
curBase: FullSlug,
newBase: FullSlug,
) => {
if (el.properties?.[attr]) {
if (!isRelativeURL(String(el.properties[attr]))) {
return
}

const rel = joinSegments(resolveRelative(curBase, newBase), "..", el.properties[attr] as string)
el.properties[attr] = rel
}
}

export function normalizeHastElement(el: HastElement, curBase: FullSlug, newBase: FullSlug) {
_rebaseHastElement(el, "src", curBase, newBase)
_rebaseHastElement(el, "href", curBase, newBase)
if (el.children) {
el.children = el.children.map((child) =>
normalizeHastElement(child as HastElement, curBase, newBase),
)
}

return el
}

// resolve /a/b/c to ../..
export function pathToRoot(slug: FullSlug): RelativeURL {
let rootPath = slug
Expand Down