mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-24 23:15:46 -05:00
Merge branch 'v4' of https://github.com/jackyzha0/quartz
This commit is contained in:
commit
cc70f949ee
@ -2,7 +2,7 @@
|
||||
"name": "@jackyzha0/quartz",
|
||||
"description": "🌱 publish your digital garden and notes as a website",
|
||||
"private": true,
|
||||
"version": "4.1.0",
|
||||
"version": "4.1.1",
|
||||
"type": "module",
|
||||
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
|
||||
"license": "MIT",
|
||||
|
||||
@ -5,7 +5,7 @@ import BodyConstructor from "./Body"
|
||||
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
|
||||
import { FullSlug, RelativeURL, joinSegments } from "../util/path"
|
||||
import { visit } from "unist-util-visit"
|
||||
import { Root, Element } from "hast"
|
||||
import { Root, Element, ElementContent } from "hast"
|
||||
|
||||
interface RenderComponents {
|
||||
head: QuartzComponent
|
||||
@ -61,22 +61,81 @@ export function renderPage(
|
||||
const classNames = (node.properties?.className ?? []) as string[]
|
||||
if (classNames.includes("transclude")) {
|
||||
const inner = node.children[0] as Element
|
||||
const blockSlug = inner.properties?.["data-slug"] as FullSlug
|
||||
const blockRef = node.properties!.dataBlock as string
|
||||
const transcludeTarget = inner.properties?.["data-slug"] as FullSlug
|
||||
|
||||
// TODO: avoid this expensive find operation and construct an index ahead of time
|
||||
let blockNode = componentData.allFiles.find((f) => f.slug === blockSlug)?.blocks?.[blockRef]
|
||||
if (blockNode) {
|
||||
if (blockNode.tagName === "li") {
|
||||
blockNode = {
|
||||
type: "element",
|
||||
tagName: "ul",
|
||||
children: [blockNode],
|
||||
const page = componentData.allFiles.find((f) => f.slug === transcludeTarget)
|
||||
if (!page) {
|
||||
return
|
||||
}
|
||||
|
||||
let blockRef = node.properties?.dataBlock as string | undefined
|
||||
if (blockRef?.startsWith("^")) {
|
||||
// block transclude
|
||||
blockRef = blockRef.slice(1)
|
||||
let blockNode = page.blocks?.[blockRef]
|
||||
if (blockNode) {
|
||||
if (blockNode.tagName === "li") {
|
||||
blockNode = {
|
||||
type: "element",
|
||||
tagName: "ul",
|
||||
children: [blockNode],
|
||||
}
|
||||
}
|
||||
|
||||
node.children = [
|
||||
blockNode,
|
||||
{
|
||||
type: "element",
|
||||
tagName: "a",
|
||||
properties: { href: inner.properties?.href, class: ["internal"] },
|
||||
children: [{ type: "text", value: `Link to original` }],
|
||||
},
|
||||
]
|
||||
}
|
||||
} else if (blockRef?.startsWith("#") && page.htmlAst) {
|
||||
// header transclude
|
||||
blockRef = blockRef.slice(1)
|
||||
let startIdx = undefined
|
||||
let endIdx = undefined
|
||||
for (const [i, el] of page.htmlAst.children.entries()) {
|
||||
if (el.type === "element" && el.tagName.match(/h[1-6]/)) {
|
||||
if (endIdx) {
|
||||
break
|
||||
}
|
||||
|
||||
if (startIdx) {
|
||||
endIdx = i
|
||||
} else if (el.properties?.id === blockRef) {
|
||||
startIdx = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!startIdx) {
|
||||
return
|
||||
}
|
||||
|
||||
node.children = [
|
||||
blockNode,
|
||||
...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]),
|
||||
{
|
||||
type: "element",
|
||||
tagName: "a",
|
||||
properties: { href: inner.properties?.href, class: ["internal"] },
|
||||
children: [{ type: "text", value: `Link to original` }],
|
||||
},
|
||||
]
|
||||
} else if (page.htmlAst) {
|
||||
// page transclude
|
||||
node.children = [
|
||||
{
|
||||
type: "element",
|
||||
tagName: "h1",
|
||||
children: [
|
||||
{ type: "text", value: page.frontmatter?.title ?? `Transclude of ${page.slug}` },
|
||||
],
|
||||
},
|
||||
...(page.htmlAst.children as ElementContent[]),
|
||||
{
|
||||
type: "element",
|
||||
tagName: "a",
|
||||
|
||||
@ -2,14 +2,18 @@ import { computePosition, flip, inline, shift } from "@floating-ui/dom"
|
||||
|
||||
// from micromorph/src/utils.ts
|
||||
// https://github.com/natemoo-re/micromorph/blob/main/src/utils.ts#L5
|
||||
export function normalizeRelativeURLs(el: Element | Document, base: string | URL) {
|
||||
const update = (el: Element, attr: string, base: string | URL) => {
|
||||
el.setAttribute(attr, new URL(el.getAttribute(attr)!, base).pathname)
|
||||
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) => update(item, "href", base))
|
||||
|
||||
el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) => update(item, "src", base))
|
||||
el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) =>
|
||||
rebase(item, "href", destination),
|
||||
)
|
||||
el.querySelectorAll('[src^="./"], [src^="../"]').forEach((item) =>
|
||||
rebase(item, "src", destination),
|
||||
)
|
||||
}
|
||||
|
||||
const p = new DOMParser()
|
||||
|
||||
@ -109,6 +109,7 @@ function createRouter() {
|
||||
if (isSamePage(url) && url.hash) {
|
||||
const el = document.getElementById(decodeURIComponent(url.hash.substring(1)))
|
||||
el?.scrollIntoView()
|
||||
history.pushState({}, "", url)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
29
quartz/plugins/emitters/cname.ts
Normal file
29
quartz/plugins/emitters/cname.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { FilePath, joinSegments } from "../../util/path"
|
||||
import { QuartzEmitterPlugin } from "../types"
|
||||
import fs from "fs"
|
||||
import chalk from "chalk"
|
||||
|
||||
export function extractDomainFromBaseUrl(baseUrl: string) {
|
||||
const url = new URL(`https://${baseUrl}`)
|
||||
return url.hostname
|
||||
}
|
||||
|
||||
export const CNAME: QuartzEmitterPlugin = () => ({
|
||||
name: "CNAME",
|
||||
getQuartzComponents() {
|
||||
return []
|
||||
},
|
||||
async emit({ argv, cfg }, _content, _resources, _emit): Promise<FilePath[]> {
|
||||
if (!cfg.configuration.baseUrl) {
|
||||
console.warn(chalk.yellow("CNAME emitter requires `baseUrl` to be set in your configuration"))
|
||||
return []
|
||||
}
|
||||
const path = joinSegments(argv.output, "CNAME")
|
||||
const content = extractDomainFromBaseUrl(cfg.configuration.baseUrl)
|
||||
if (!content) {
|
||||
return []
|
||||
}
|
||||
fs.writeFileSync(path, content)
|
||||
return [path] as FilePath[]
|
||||
},
|
||||
})
|
||||
@ -7,3 +7,4 @@ export { Assets } from "./assets"
|
||||
export { Static } from "./static"
|
||||
export { ComponentResources } from "./componentResources"
|
||||
export { NotFoundPage } from "./404"
|
||||
export { CNAME } from "./cname"
|
||||
|
||||
@ -11,7 +11,10 @@ export const Static: QuartzEmitterPlugin = () => ({
|
||||
async emit({ argv, cfg }, _content, _resources, _emit): Promise<FilePath[]> {
|
||||
const staticPath = joinSegments(QUARTZ, "static")
|
||||
const fps = await glob("**", staticPath, cfg.configuration.ignorePatterns)
|
||||
await fs.promises.cp(staticPath, joinSegments(argv.output, "static"), { recursive: true })
|
||||
await fs.promises.cp(staticPath, joinSegments(argv.output, "static"), {
|
||||
recursive: true,
|
||||
dereference: true,
|
||||
})
|
||||
return fps.map((fp) => joinSegments(argv.output, "static", fp)) as FilePath[]
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { PluggableList } from "unified"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
import { Root, HTML, BlockContent, DefinitionContent, Code, Paragraph } from "mdast"
|
||||
import { Element, Literal } from "hast"
|
||||
import { Element, Literal, Root as HtmlRoot } from "hast"
|
||||
import { Replace, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
|
||||
import { slug as slugAnchor } from "github-slugger"
|
||||
import rehypeRaw from "rehype-raw"
|
||||
@ -110,7 +110,10 @@ function canonicalizeCallout(calloutName: string): keyof typeof callouts {
|
||||
// ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name)
|
||||
// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link)
|
||||
// (|[^\[\]\|\#]+)? -> | then one or more non-special characters (alias)
|
||||
const wikilinkRegex = new RegExp(/!?\[\[([^\[\]\|\#]+)?(#[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/, "g")
|
||||
export const wikilinkRegex = new RegExp(
|
||||
/!?\[\[([^\[\]\|\#]+)?(#+[^\[\]\|\#]+)?(\|[^\[\]\|\#]+)?\]\]/,
|
||||
"g",
|
||||
)
|
||||
const highlightRegex = new RegExp(/==([^=]+)==/, "g")
|
||||
const commentRegex = new RegExp(/%%(.+)%%/, "g")
|
||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
||||
@ -178,9 +181,9 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
src = src.replaceAll(wikilinkRegex, (value, ...capture) => {
|
||||
const [rawFp, rawHeader, rawAlias] = capture
|
||||
const fp = rawFp ?? ""
|
||||
const anchor = rawHeader?.trim().slice(1)
|
||||
const anchor = rawHeader?.trim().replace(/^#+/, "")
|
||||
const displayAnchor = anchor ? `#${slugAnchor(anchor)}` : ""
|
||||
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
|
||||
const displayAlias = rawAlias ?? anchor ?? ""
|
||||
const embedDisplay = value.startsWith("!") ? "!" : ""
|
||||
return `${embedDisplay}[[${fp}${displayAnchor}${displayAlias}]]`
|
||||
})
|
||||
@ -236,13 +239,13 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
value: `<iframe src="${url}"></iframe>`,
|
||||
}
|
||||
} else if (ext === "") {
|
||||
const block = anchor.slice(1)
|
||||
const block = anchor
|
||||
return {
|
||||
type: "html",
|
||||
data: { hProperties: { transclude: true } },
|
||||
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}"><a href="${
|
||||
url + anchor
|
||||
}" class="transclude-inner">Transclude of block ${block}</a></blockquote>`,
|
||||
}" class="transclude-inner">Transclude of ${url}${block}</a></blockquote>`,
|
||||
}
|
||||
}
|
||||
|
||||
@ -477,6 +480,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
file.data.htmlAst = tree
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -524,5 +529,6 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>
|
||||
declare module "vfile" {
|
||||
interface DataMap {
|
||||
blocks: Record<string, Element>
|
||||
htmlAst: HtmlRoot
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { Root } from "mdast"
|
||||
import { visit } from "unist-util-visit"
|
||||
import { toString } from "mdast-util-to-string"
|
||||
import Slugger from "github-slugger"
|
||||
import { wikilinkRegex } from "./ofm"
|
||||
|
||||
export interface Options {
|
||||
maxDepth: 1 | 2 | 3 | 4 | 5 | 6
|
||||
@ -24,6 +25,7 @@ interface TocEntry {
|
||||
slug: string // this is just the anchor (#some-slug), not the canonical slug
|
||||
}
|
||||
|
||||
const regexMdLinks = new RegExp(/\[([^\[]+)\](\(.*\))/, "g")
|
||||
export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefined> = (
|
||||
userOpts,
|
||||
) => {
|
||||
@ -41,7 +43,16 @@ export const TableOfContents: QuartzTransformerPlugin<Partial<Options> | undefin
|
||||
let highestDepth: number = opts.maxDepth
|
||||
visit(tree, "heading", (node) => {
|
||||
if (node.depth <= opts.maxDepth) {
|
||||
const text = toString(node)
|
||||
let text = toString(node)
|
||||
|
||||
// strip link formatting from toc entries
|
||||
text = text.replace(wikilinkRegex, (_, rawFp, __, rawAlias) => {
|
||||
const fp = rawFp?.trim() ?? ""
|
||||
const alias = rawAlias?.slice(1).trim()
|
||||
return alias ?? fp
|
||||
})
|
||||
text = text.replace(regexMdLinks, "$1")
|
||||
|
||||
highestDepth = Math.min(highestDepth, node.depth)
|
||||
toc.push({
|
||||
depth: node.depth,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user