fix type issues

This commit is contained in:
semanticdata 2024-08-14 16:28:39 -05:00
parent 16c4b476e8
commit 388ab1f228
108 changed files with 1144 additions and 2003 deletions

View File

@ -23,10 +23,6 @@
"check": "tsc --noEmit && npx prettier . --check",
"format": "npx prettier . --write"
},
"engines": {
"npm": ">=9.3.1",
"node": "20 || >=22"
},
"keywords": [
"site generator",
"ssg",
@ -108,5 +104,8 @@
"workerpool": "^9.1.3",
"ws": "^8.17.1",
"yargs": "^17.7.2"
},
"dependencies": {
"@types/mdast": "^4.0.4"
}
}

61
pnpm-lock.yaml generated
View File

@ -4,6 +4,11 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
'@types/mdast':
specifier: ^4.0.4
version: 4.0.4
devDependencies:
'@clack/prompts':
specifier: ^0.7.0
@ -1117,11 +1122,10 @@ packages:
resolution: {integrity: sha512-rHusx08LCg92WJxrsM3SPjvLTSvK5C+gealtSuhKbEOcUZfWlwigaFoPLf6Dfxhg4oryN5qP9Sj7zOQ4HYXINw==}
dev: true
/@types/mdast@4.0.3:
resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
/@types/mdast@4.0.4:
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
dependencies:
'@types/unist': 3.0.2
dev: true
/@types/ms@0.7.34:
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
@ -1155,7 +1159,6 @@ packages:
/@types/unist@3.0.2:
resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
dev: true
/@types/ws@8.5.10:
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
@ -2507,7 +2510,7 @@ packages:
/mdast-util-find-and-replace@3.0.1:
resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
escape-string-regexp: 5.0.0
unist-util-is: 6.0.0
unist-util-visit-parents: 6.0.1
@ -2516,7 +2519,7 @@ packages:
/mdast-util-from-markdown@2.0.0:
resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
'@types/unist': 3.0.2
decode-named-character-reference: 1.0.2
devlop: 1.1.0
@ -2535,7 +2538,7 @@ packages:
/mdast-util-frontmatter@2.0.1:
resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
devlop: 1.1.0
escape-string-regexp: 5.0.0
mdast-util-from-markdown: 2.0.0
@ -2548,7 +2551,7 @@ packages:
/mdast-util-gfm-autolink-literal@2.0.0:
resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
ccount: 2.0.1
devlop: 1.1.0
mdast-util-find-and-replace: 3.0.1
@ -2558,7 +2561,7 @@ packages:
/mdast-util-gfm-footnote@2.0.0:
resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
devlop: 1.1.0
mdast-util-from-markdown: 2.0.0
mdast-util-to-markdown: 2.1.0
@ -2570,7 +2573,7 @@ packages:
/mdast-util-gfm-strikethrough@2.0.0:
resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
mdast-util-from-markdown: 2.0.0
mdast-util-to-markdown: 2.1.0
transitivePeerDependencies:
@ -2580,7 +2583,7 @@ packages:
/mdast-util-gfm-table@2.0.0:
resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
devlop: 1.1.0
markdown-table: 3.0.3
mdast-util-from-markdown: 2.0.0
@ -2592,7 +2595,7 @@ packages:
/mdast-util-gfm-task-list-item@2.0.0:
resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
devlop: 1.1.0
mdast-util-from-markdown: 2.0.0
mdast-util-to-markdown: 2.1.0
@ -2618,7 +2621,7 @@ packages:
resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==}
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
devlop: 1.1.0
longest-streak: 3.1.0
mdast-util-from-markdown: 2.0.0
@ -2633,7 +2636,7 @@ packages:
dependencies:
'@types/estree-jsx': 1.0.5
'@types/hast': 3.0.4
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
devlop: 1.1.0
mdast-util-from-markdown: 2.0.0
mdast-util-to-markdown: 2.1.0
@ -2646,7 +2649,7 @@ packages:
dependencies:
'@types/estree-jsx': 1.0.5
'@types/hast': 3.0.4
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
'@types/unist': 3.0.2
ccount: 2.0.1
devlop: 1.1.0
@ -2666,7 +2669,7 @@ packages:
dependencies:
'@types/estree-jsx': 1.0.5
'@types/hast': 3.0.4
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
devlop: 1.1.0
mdast-util-from-markdown: 2.0.0
mdast-util-to-markdown: 2.1.0
@ -2677,14 +2680,14 @@ packages:
/mdast-util-newline-to-break@2.0.0:
resolution: {integrity: sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
mdast-util-find-and-replace: 3.0.1
dev: true
/mdast-util-phrasing@4.1.0:
resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
unist-util-is: 6.0.0
dev: true
@ -2692,7 +2695,7 @@ packages:
resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
'@ungap/structured-clone': 1.2.0
devlop: 1.1.0
micromark-util-sanitize-uri: 2.0.0
@ -2705,7 +2708,7 @@ packages:
/mdast-util-to-markdown@2.1.0:
resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
'@types/unist': 3.0.2
longest-streak: 3.1.0
mdast-util-phrasing: 4.1.0
@ -2718,7 +2721,7 @@ packages:
/mdast-util-to-string@4.0.0:
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
dev: true
/mdn-data@2.0.30:
@ -3348,7 +3351,7 @@ packages:
/remark-breaks@4.0.0:
resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
mdast-util-newline-to-break: 2.0.0
unified: 11.0.5
dev: true
@ -3356,7 +3359,7 @@ packages:
/remark-frontmatter@5.0.0:
resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
mdast-util-frontmatter: 2.0.1
micromark-extension-frontmatter: 2.0.0
unified: 11.0.5
@ -3367,7 +3370,7 @@ packages:
/remark-gfm@4.0.0:
resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
mdast-util-gfm: 3.0.0
micromark-extension-gfm: 3.0.0
remark-parse: 11.0.0
@ -3380,7 +3383,7 @@ packages:
/remark-math@6.0.0:
resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
mdast-util-math: 3.0.0
micromark-extension-math: 3.0.0
unified: 11.0.5
@ -3391,7 +3394,7 @@ packages:
/remark-parse@11.0.0:
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
mdast-util-from-markdown: 2.0.0
micromark-util-types: 2.0.0
unified: 11.0.5
@ -3403,7 +3406,7 @@ packages:
resolution: {integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==}
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
mdast-util-to-hast: 13.2.0
unified: 11.0.5
vfile: 6.0.1
@ -3421,7 +3424,7 @@ packages:
/remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
mdast-util-to-markdown: 2.1.0
unified: 11.0.5
dev: true
@ -3429,7 +3432,7 @@ packages:
/remark@15.0.1:
resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==}
dependencies:
'@types/mdast': 4.0.3
'@types/mdast': 4.0.4
remark-parse: 11.0.0
remark-stringify: 11.0.0
unified: 11.0.5

View File

@ -35,6 +35,7 @@ const config: QuartzConfig = {
secondary: "#284B63", // #091217
tertiary: "#84A59D", // #AA336A
highlight: "#8F9FA925",
textHighlight: "#fff23688",
},
darkMode: {
light: "#1E1E2E", // background // #161618
@ -45,6 +46,7 @@ const config: QuartzConfig = {
secondary: "#9BE895", // links, nodes // #7B97AA
tertiary: "#C072C4", // hover states, visited links // #84A59D
highlight: "#8F9FA925", // internal link background
textHighlight: "#b3aa0288",
},
},
},

View File

@ -66,24 +66,16 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
const release = await mut.acquire()
perf.addEvent("clean")
await rimraf(path.join(output, "*"), { glob: true })
console.log(
`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`,
)
console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`)
perf.addEvent("glob")
const allFiles = await glob(
"**/*.*",
argv.directory,
cfg.configuration.ignorePatterns,
)
const allFiles = await glob("**/*.*", argv.directory, cfg.configuration.ignorePatterns)
const fps = allFiles.filter((fp) => fp.endsWith(".md")).sort()
console.log(
`Found ${fps.length} input files from \`${argv.directory}\` in ${perf.timeSince("glob")}`,
)
const filePaths = fps.map(
(fp) => joinSegments(argv.directory, fp) as FilePath,
)
const filePaths = fps.map((fp) => joinSegments(argv.directory, fp) as FilePath)
ctx.allSlugs = allFiles.map((fp) => slugifyFilePath(fp as FilePath))
const parsedFiles = await parseMarkdown(ctx, filePaths)
@ -96,18 +88,12 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
const staticResources = getStaticResourcesFromPlugins(ctx)
for (const emitter of cfg.plugins.emitters) {
dependencies[emitter.name] =
(await emitter.getDependencyGraph?.(
ctx,
filteredContent,
staticResources,
)) ?? null
(await emitter.getDependencyGraph?.(ctx, filteredContent, staticResources)) ?? null
}
}
await emitContent(ctx, filteredContent)
console.log(
chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`),
)
console.log(chalk.green(`Done processing ${fps.length} files in ${perf.timeSince()}`))
release()
if (argv.serve) {
@ -151,17 +137,11 @@ async function startServing(
ignoreInitial: true,
})
const buildFromEntry = argv.fastRebuild
? partialRebuildFromEntrypoint
: rebuildFromEntrypoint
const buildFromEntry = argv.fastRebuild ? partialRebuildFromEntrypoint : rebuildFromEntrypoint
watcher
.on("add", (fp) => buildFromEntry(fp, "add", clientRefresh, buildData))
.on("change", (fp) =>
buildFromEntry(fp, "change", clientRefresh, buildData),
)
.on("unlink", (fp) =>
buildFromEntry(fp, "delete", clientRefresh, buildData),
)
.on("change", (fp) => buildFromEntry(fp, "change", clientRefresh, buildData))
.on("unlink", (fp) => buildFromEntry(fp, "delete", clientRefresh, buildData))
return async () => {
await watcher.close()
@ -204,18 +184,12 @@ async function partialRebuildFromEntrypoint(
case "add":
// add to cache when new file is added
processedFiles = await parseMarkdown(ctx, [fp])
processedFiles.forEach(([tree, vfile]) =>
contentMap.set(vfile.data.filePath!, [tree, vfile]),
)
processedFiles.forEach(([tree, vfile]) => contentMap.set(vfile.data.filePath!, [tree, vfile]))
// update the dep graph by asking all emitters whether they depend on this file
for (const emitter of cfg.plugins.emitters) {
const emitterGraph =
(await emitter.getDependencyGraph?.(
ctx,
processedFiles,
staticResources,
)) ?? null
(await emitter.getDependencyGraph?.(ctx, processedFiles, staticResources)) ?? null
if (emitterGraph) {
const existingGraph = dependencies[emitter.name]
@ -231,29 +205,20 @@ async function partialRebuildFromEntrypoint(
case "change":
// invalidate cache when file is changed
processedFiles = await parseMarkdown(ctx, [fp])
processedFiles.forEach(([tree, vfile]) =>
contentMap.set(vfile.data.filePath!, [tree, vfile]),
)
processedFiles.forEach(([tree, vfile]) => contentMap.set(vfile.data.filePath!, [tree, vfile]))
// only content files can have added/removed dependencies because of transclusions
if (path.extname(fp) === ".md") {
for (const emitter of cfg.plugins.emitters) {
// get new dependencies from all emitters for this file
const emitterGraph =
(await emitter.getDependencyGraph?.(
ctx,
processedFiles,
staticResources,
)) ?? null
(await emitter.getDependencyGraph?.(ctx, processedFiles, staticResources)) ?? null
// only update the graph if the emitter plugin uses the changed file
// eg. Assets plugin ignores md files, so we skip updating the graph
if (emitterGraph?.hasNode(fp)) {
// merge the new dependencies into the dep graph
dependencies[emitter.name]?.updateIncomingEdgesForNode(
emitterGraph,
fp,
)
dependencies[emitter.name]?.updateIncomingEdgesForNode(emitterGraph, fp)
}
}
}
@ -316,11 +281,7 @@ async function partialRebuildFromEntrypoint(
.filter((file) => !toRemove.has(file))
.map((file) => contentMap.get(file)!)
const emittedFps = await emitter.emit(
ctx,
upstreamContent,
staticResources,
)
const emittedFps = await emitter.emit(ctx, upstreamContent, staticResources)
if (ctx.argv.verbose) {
for (const file of emittedFps) {
@ -332,9 +293,7 @@ async function partialRebuildFromEntrypoint(
}
}
console.log(
`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`,
)
console.log(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`)
// CLEANUP
const destinationsToDelete = new Set<FilePath>()
@ -369,16 +328,8 @@ async function rebuildFromEntrypoint(
clientRefresh: () => void,
buildData: BuildData, // note: this function mutates buildData
) {
const {
ctx,
ignored,
mut,
initialSlugs,
contentMap,
toRebuild,
toRemove,
trackedAssets,
} = buildData
const { ctx, ignored, mut, initialSlugs, contentMap, toRebuild, toRemove, trackedAssets } =
buildData
const { argv } = ctx
@ -422,16 +373,6 @@ async function rebuildFromEntrypoint(
try {
const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp))
const trackedSlugs = [
...new Set([...contentMap.keys(), ...toRebuild, ...trackedAssets]),
]
.filter((fp) => !toRemove.has(fp))
.map((fp) =>
slugifyFilePath(path.posix.relative(argv.directory, fp) as FilePath),
)
ctx.allSlugs = [...new Set([...initialSlugs, ...trackedSlugs])]
const parsedContent = await parseMarkdown(ctx, filesToRebuild)
for (const content of parsedContent) {
const [_tree, vfile] = content
@ -458,9 +399,7 @@ async function rebuildFromEntrypoint(
await emitContent(ctx, filteredContent)
console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`))
} catch (err) {
console.log(
chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`),
)
console.log(chalk.yellow(`Rebuild failed. Waiting on a change to fix the error...`))
if (argv.verbose) {
console.log(chalk.red(err))
}

View File

@ -1,14 +1,7 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { classNames } from "../util/lang"
const ArticleTitle: QuartzComponent = ({
fileData,
displayClass,
}: QuartzComponentProps) => {
const ArticleTitle: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => {
const title = fileData.frontmatter?.title
if (title) {
return <h1 class={classNames(displayClass, "article-title")}>{title}</h1>

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/backlinks.scss"
import { resolveRelative, simplifySlug } from "../util/path"
import { i18n } from "../i18n"
@ -23,9 +19,7 @@ const Backlinks: QuartzComponent = ({
{backlinkFiles.length > 0 ? (
backlinkFiles.map((f) => (
<li>
<a
href={resolveRelative(fileData.slug!, f.slug!)}
class="internal">
<a href={resolveRelative(fileData.slug!, f.slug!)} class="internal">
{f.frontmatter?.title}
</a>
</li>

View File

@ -1,11 +1,7 @@
// @ts-ignore
import clipboardScript from "./scripts/clipboard.inline"
import clipboardStyle from "./styles/clipboard.scss"
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
const Body: QuartzComponent = ({ children }: QuartzComponentProps) => {
return <div id="quartz-body">{children}</div>

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import breadcrumbsStyle from "./styles/breadcrumbs.scss"
import { FullSlug, SimpleSlug, joinSegments, resolveRelative } from "../util/path"
import { QuartzPluginData } from "../plugins/vfile"
@ -44,11 +40,7 @@ const defaultOptions: BreadcrumbOptions = {
showCurrentPage: true,
}
function formatCrumb(
displayName: string,
baseSlug: FullSlug,
currentSlug: SimpleSlug,
): CrumbData {
function formatCrumb(displayName: string, baseSlug: FullSlug, currentSlug: SimpleSlug): CrumbData {
return {
displayName: displayName.replaceAll("-", " "),
path: resolveRelative(baseSlug, currentSlug),
@ -73,11 +65,7 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
}
// Format entry for root element
const firstEntry = formatCrumb(
options.rootName,
fileData.slug!,
"/" as SimpleSlug,
)
const firstEntry = formatCrumb(options.rootName, fileData.slug!, "/" as SimpleSlug)
const crumbs: CrumbData[] = [firstEntry]
if (!folderIndex && options.resolveFrontmatterTitle) {
@ -104,9 +92,7 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
let curPathSegment = slugParts[i]
// Try to resolve frontmatter folder title
const currentFile = folderIndex?.get(
slugParts.slice(0, i + 1).join("/"),
)
const currentFile = folderIndex?.get(slugParts.slice(0, i + 1).join("/"))
if (currentFile) {
const title = currentFile.frontmatter!.title
if (title !== "index") {
@ -137,15 +123,11 @@ export default ((opts?: Partial<BreadcrumbOptions>) => {
}
return (
<nav
class={classNames(displayClass, "breadcrumb-container")}
aria-label="breadcrumbs">
<nav class={classNames(displayClass, "breadcrumb-container")} 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>
)}
{index !== crumbs.length - 1 && <p>{` ${options.spacerSymbol} `}</p>}
</div>
))}
</nav>

View File

@ -23,11 +23,7 @@ export default ((opts?: Partial<ContentMetaOptions>) => {
// Merge options with defaults
const options: ContentMetaOptions = { ...defaultOptions, ...opts }
function ContentMetadata({
cfg,
fileData,
displayClass,
}: QuartzComponentProps) {
function ContentMetadata({ cfg, fileData, displayClass }: QuartzComponentProps) {
const text = fileData.text
if (text) {
@ -40,9 +36,7 @@ export default ((opts?: Partial<ContentMetaOptions>) => {
// Display reading time if enabled
if (options.showReadingTime) {
const { minutes, words: _words } = readingTime(text)
const displayedTime = i18n(
cfg.locale,
).components.contentMeta.readingTime({
const displayedTime = i18n(cfg.locale).components.contentMeta.readingTime({
minutes: Math.ceil(minutes),
})
segments.push(displayedTime)
@ -51,9 +45,7 @@ export default ((opts?: Partial<ContentMetaOptions>) => {
const segmentsElements = segments.map((segment) => <span>{segment}</span>)
return (
<p
show-comma={options.showComma}
class={classNames(displayClass, "content-meta")}>
<p show-comma={options.showComma} class={classNames(displayClass, "content-meta")}>
{segmentsElements}
</p>
)

View File

@ -3,26 +3,14 @@
// see: https://v8.dev/features/modules#defer
import darkmodeScript from "./scripts/darkmode.inline"
import styles from "./styles/darkmode.scss"
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { i18n } from "../i18n"
import { classNames } from "../util/lang"
const Darkmode: QuartzComponent = ({
displayClass,
cfg,
}: QuartzComponentProps) => {
const Darkmode: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
return (
<div class={classNames(displayClass, "darkmode")}>
<input
class="toggle"
id="darkmode-toggle"
type="checkbox"
tabIndex={-1}
/>
<input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex={-1} />
<label id="toggle-label-light" for="darkmode-toggle" tabIndex={-1}>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -33,7 +21,8 @@ const Darkmode: QuartzComponent = ({
y="0px"
viewBox="0 0 35 35"
style="enable-background:new 0 0 35 35"
xmlSpace="preserve">
xmlSpace="preserve"
>
<title>{i18n(cfg.locale).components.themeToggle.darkMode}</title>
<path d="M6,17.5C6,16.672,5.328,16,4.5,16h-3C0.672,16,0,16.672,0,17.5 S0.672,19,1.5,19h3C5.328,19,6,18.328,6,17.5z M7.5,26c-0.414,0-0.789,0.168-1.061,0.439l-2,2C4.168,28.711,4,29.086,4,29.5 C4,30.328,4.671,31,5.5,31c0.414,0,0.789-0.168,1.06-0.44l2-2C8.832,28.289,9,27.914,9,27.5C9,26.672,8.329,26,7.5,26z M17.5,6 C18.329,6,19,5.328,19,4.5v-3C19,0.672,18.329,0,17.5,0S16,0.672,16,1.5v3C16,5.328,16.671,6,17.5,6z M27.5,9 c0.414,0,0.789-0.168,1.06-0.439l2-2C30.832,6.289,31,5.914,31,5.5C31,4.672,30.329,4,29.5,4c-0.414,0-0.789,0.168-1.061,0.44 l-2,2C26.168,6.711,26,7.086,26,7.5C26,8.328,26.671,9,27.5,9z M6.439,8.561C6.711,8.832,7.086,9,7.5,9C8.328,9,9,8.328,9,7.5 c0-0.414-0.168-0.789-0.439-1.061l-2-2C6.289,4.168,5.914,4,5.5,4C4.672,4,4,4.672,4,5.5c0,0.414,0.168,0.789,0.439,1.06 L6.439,8.561z M33.5,16h-3c-0.828,0-1.5,0.672-1.5,1.5s0.672,1.5,1.5,1.5h3c0.828,0,1.5-0.672,1.5-1.5S34.328,16,33.5,16z M28.561,26.439C28.289,26.168,27.914,26,27.5,26c-0.828,0-1.5,0.672-1.5,1.5c0,0.414,0.168,0.789,0.439,1.06l2,2 C28.711,30.832,29.086,31,29.5,31c0.828,0,1.5-0.672,1.5-1.5c0-0.414-0.168-0.789-0.439-1.061L28.561,26.439z M17.5,29 c-0.829,0-1.5,0.672-1.5,1.5v3c0,0.828,0.671,1.5,1.5,1.5s1.5-0.672,1.5-1.5v-3C19,29.672,18.329,29,17.5,29z M17.5,7 C11.71,7,7,11.71,7,17.5S11.71,28,17.5,28S28,23.29,28,17.5S23.29,7,17.5,7z M17.5,25c-4.136,0-7.5-3.364-7.5-7.5 c0-4.136,3.364-7.5,7.5-7.5c4.136,0,7.5,3.364,7.5,7.5C25,21.636,21.636,25,17.5,25z"></path>
</svg>
@ -48,7 +37,8 @@ const Darkmode: QuartzComponent = ({
y="0px"
viewBox="0 0 100 100"
style="enable-background:new 0 0 100 100"
xmlSpace="preserve">
xmlSpace="preserve"
>
<title>{i18n(cfg.locale).components.themeToggle.lightMode}</title>
<path d="M96.76,66.458c-0.853-0.852-2.15-1.064-3.23-0.534c-6.063,2.991-12.858,4.571-19.655,4.571 C62.022,70.495,50.88,65.88,42.5,57.5C29.043,44.043,25.658,23.536,34.076,6.47c0.532-1.08,0.318-2.379-0.534-3.23 c-0.851-0.852-2.15-1.064-3.23-0.534c-4.918,2.427-9.375,5.619-13.246,9.491c-9.447,9.447-14.65,22.008-14.65,35.369 c0,13.36,5.203,25.921,14.65,35.368s22.008,14.65,35.368,14.65c13.361,0,25.921-5.203,35.369-14.65 c3.872-3.871,7.064-8.328,9.491-13.246C97.826,68.608,97.611,67.309,96.76,66.458z"></path>
</svg>

View File

@ -9,10 +9,7 @@ interface Props {
export type ValidDateType = keyof Required<QuartzPluginData>["dates"]
export function getDate(
cfg: GlobalConfiguration,
data: QuartzPluginData,
): Date | undefined {
export function getDate(cfg: GlobalConfiguration, data: QuartzPluginData): Date | undefined {
if (!cfg.defaultDateType) {
throw new Error(
`Field 'defaultDateType' was not set in the configuration object of quartz.config.ts. See https://quartz.jzhao.xyz/configuration#general-configuration for more details.`,

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
export default ((component?: QuartzComponent) => {
if (component) {

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import explorerStyle from "./styles/explorer.scss"
// @ts-ignore
@ -72,9 +68,7 @@ export default ((userOpts?: Partial<Options>) => {
// Get all folders of tree. Initialize with collapsed state
// Stringify to pass json tree as data attribute ([data-tree])
const folders = fileTree.getFolderPaths(
opts.folderDefaultState === "collapsed",
)
const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed")
jsonTree = JSON.stringify(folders)
}
@ -98,8 +92,11 @@ export default ((userOpts?: Partial<Options>) => {
data-behavior={opts.folderClickBehavior}
data-collapsed={opts.folderDefaultState}
data-savestate={opts.useSavedState}
data-tree={jsonTree}>
<h1>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h1>
data-tree={jsonTree}
aria-controls="explorer-content"
aria-expanded={opts.folderDefaultState === "open"}
>
<h2>{opts.title ?? i18n(cfg.locale).components.explorer.title}</h2>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
@ -110,7 +107,8 @@ export default ((userOpts?: Partial<Options>) => {
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="fold">
class="fold"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>

View File

@ -32,10 +32,7 @@ export type FolderState = {
collapsed: boolean
}
function getPathSegment(
fp: FilePath | undefined,
idx: number,
): string | undefined {
function getPathSegment(fp: FilePath | undefined, idx: number): string | undefined {
if (!fp) {
return undefined
}
@ -51,12 +48,7 @@ export class FileNode {
file: QuartzPluginData | null
depth: number
constructor(
slugSegment: string,
displayName?: string,
file?: QuartzPluginData,
depth?: number,
) {
constructor(slugSegment: string, displayName?: string, file?: QuartzPluginData, depth?: number) {
this.children = []
this.name = slugSegment
this.displayName = displayName ?? file?.frontmatter?.title ?? slugSegment
@ -81,9 +73,7 @@ export class FileNode {
}
} else {
// direct child
this.children.push(
new FileNode(nextSegment, undefined, fileData.file, this.depth + 1),
)
this.children.push(new FileNode(nextSegment, undefined, fileData.file, this.depth + 1))
}
return
@ -172,19 +162,13 @@ type ExplorerNodeProps = {
fullPath?: string
}
export function ExplorerNode({
node,
opts,
fullPath,
fileData,
}: ExplorerNodeProps) {
export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodeProps) {
// Get options
const folderBehavior = opts.folderClickBehavior
const isDefaultOpen = opts.folderDefaultState === "open"
// Calculate current folderPath
const folderPath =
node.name !== "" ? joinSegments(fullPath ?? "", node.name) : ""
const folderPath = node.name !== "" ? joinSegments(fullPath ?? "", node.name) : ""
const href = resolveRelative(fileData.slug!, folderPath as SimpleSlug) + "/"
return (
@ -192,9 +176,7 @@ export function ExplorerNode({
{node.file ? (
// Single file node
<li key={node.file.slug}>
<a
href={resolveRelative(fileData.slug!, node.file.slug!)}
data-for={node.file.slug}>
<a href={resolveRelative(fileData.slug!, node.file.slug!)} data-for={node.file.slug}>
{node.displayName}
</a>
</li>
@ -214,19 +196,14 @@ export function ExplorerNode({
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="folder-icon">
class="folder-icon"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
{/* render <a> tag if folderBehavior is "link", otherwise render <button> with collapse click event */}
<div key={node.name} data-folderpath={folderPath}>
{folderBehavior === "link" ? (
<a
href={resolveRelative(
fileData.slug!,
folderPath as SimpleSlug,
)}
data-for={node.name}
class="folder-title">
<a href={href} data-for={node.name} class="folder-title">
{node.displayName}
</a>
) : (
@ -238,15 +215,15 @@ export function ExplorerNode({
</div>
)}
{/* Recursively render children of folder */}
<div
class={`folder-outer ${node.depth === 0 || isDefaultOpen ? "open" : ""}`}>
<div class={`folder-outer ${node.depth === 0 || isDefaultOpen ? "open" : ""}`}>
<ul
// Inline style for left folder paddings
style={{
paddingLeft: node.name !== "" ? "1.4rem" : "0",
}}
class="content"
data-folderul={folderPath}>
data-folderul={folderPath}
>
{node.children.map((childNode, i) => (
<ExplorerNode
node={childNode}

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/footer.scss"
import { version } from "../../package.json"
import { i18n } from "../i18n"
@ -12,17 +8,14 @@ interface Options {
}
export default ((opts?: Options) => {
const Footer: QuartzComponent = ({
displayClass,
cfg,
}: QuartzComponentProps) => {
const Footer: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
const year = new Date().getFullYear()
const links = opts?.links ?? []
return (
<footer class={`${displayClass ?? ""}`}>
<p>
© {year} Miguel Pimentel · Created with{" "}
<a href="https://quartz.jzhao.xyz/">Quartz</a>.
{i18n(cfg.locale).components.footer.createdWith}{" "}
<a href="https://quartz.jzhao.xyz/">Quartz v{version}</a> © {year}
</p>
<ul>
{Object.entries(links).map(([text, link]) => (

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
// @ts-ignore
import script from "./scripts/graph.inline"
import style from "./styles/graph.scss"
@ -61,18 +57,12 @@ const defaultOptions: GraphOptions = {
}
export default ((opts?: GraphOptions) => {
const Graph: QuartzComponent = ({
displayClass,
cfg,
}: QuartzComponentProps) => {
const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
const globalGraph = {
...defaultOptions.globalGraph,
...opts?.globalGraph,
}
const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
return (
<div class={classNames(displayClass, "graph")}>
<h2>{i18n(cfg.locale).components.graph.title}</h2>
<h3>{i18n(cfg.locale).components.graph.title}</h3>
<div class="graph-outer">
<div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div>
<svg
@ -84,7 +74,8 @@ export default ((opts?: GraphOptions) => {
y="0px"
viewBox="0 0 55 55"
fill="currentColor"
xmlSpace="preserve">
xmlSpace="preserve"
>
<path
d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17
s-3.542,0.634-4.898,1.688l-7.669-7.669C16.785,10.424,17,9.74,17,9c0-2.206-1.794-4-4-4S9,6.794,9,9s1.794,4,4,4
@ -101,9 +92,7 @@ export default ((opts?: GraphOptions) => {
</svg>
</div>
<div id="global-graph-outer">
<div
id="global-graph-container"
data-cfg={JSON.stringify(globalGraph)}></div>
<div id="global-graph-container" data-cfg={JSON.stringify(globalGraph)}></div>
</div>
</div>
)

View File

@ -2,23 +2,13 @@ import {i18n} from "../i18n"
import { FullSlug, joinSegments, pathToRoot } from "../util/path"
import { JSResourceToScriptElement } from "../util/resources"
import { googleFontHref } from "../util/theme"
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
export default (() => {
const Head: QuartzComponent = ({
cfg,
fileData,
externalResources,
}: QuartzComponentProps) => {
const title =
fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
const Head: QuartzComponent = ({ cfg, fileData, externalResources }: QuartzComponentProps) => {
const title = fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
const description =
fileData.description?.trim() ??
i18n(cfg.locale).propertyDefaults.description
fileData.description?.trim() ?? i18n(cfg.locale).propertyDefaults.description
const { css, js } = externalResources
const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`)
@ -49,13 +39,7 @@ export default (() => {
<meta name="description" content={description} />
<meta name="generator" content="Quartz" />
{css.map((href) => (
<link
key={href}
href={href}
rel="stylesheet"
type="text/css"
spa-preserve
/>
<link key={href} href={href} rel="stylesheet" type="text/css" spa-preserve />
))}
{js
.filter((resource) => resource.loadTime === "beforeDOMReady")

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
const Header: QuartzComponent = ({ children }: QuartzComponentProps) => {
return children.length > 0 ? <header>{children}</header> : null

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
export default ((component?: QuartzComponent) => {
if (component) {

View File

@ -30,13 +30,9 @@ type Props = {
sort?: SortFn
} & QuartzComponentProps
export const PageList: QuartzComponent = ({
cfg,
fileData,
allFiles,
limit,
}: Props) => {
let list = allFiles.sort(byDateAndAlphabetical(cfg))
export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit, sort }: Props) => {
const sorter = sort ?? byDateAndAlphabetical(cfg)
let list = allFiles.sort(sorter)
if (limit) {
list = list.slice(0, limit)
}
@ -57,9 +53,7 @@ export const PageList: QuartzComponent = ({
)}
<div class="desc">
<h3>
<a
href={resolveRelative(fileData.slug!, page.slug!)}
class="internal">
<a href={resolveRelative(fileData.slug!, page.slug!)} class="internal">
{title}
</a>
</h3>
@ -69,10 +63,8 @@ export const PageList: QuartzComponent = ({
<li>
<a
class="internal tag-link"
href={resolveRelative(
fileData.slug!,
`tags/${tag}` as FullSlug,
)}>
href={resolveRelative(fileData.slug!, `tags/${tag}` as FullSlug)}
>
{tag}
</a>
</li>

View File

@ -1,17 +1,9 @@
import { pathToRoot } from "../util/path"
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { classNames } from "../util/lang"
import { i18n } from "../i18n"
const PageTitle: QuartzComponent = ({
fileData,
cfg,
displayClass,
}: QuartzComponentProps) => {
const PageTitle: QuartzComponent = ({ fileData, cfg, displayClass }: QuartzComponentProps) => {
const title = cfg?.pageTitle ?? i18n(cfg.locale).propertyDefaults.title
const baseDir = pathToRoot(fileData.slug!)
return (

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { FullSlug, SimpleSlug, resolveRelative } from "../util/path"
import { QuartzPluginData } from "../plugins/vfile"
import { byDateAndAlphabetical } from "./PageList"
@ -44,8 +40,7 @@ export default ((userOpts?: Partial<Options>) => {
<h3>{opts.title ?? i18n(cfg.locale).components.recentNotes.title}</h3>
<ul class="recent-ul">
{pages.slice(0, opts.limit).map((page) => {
const title =
page.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
const title = page.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
const tags = page.frontmatter?.tags ?? []
return (
@ -53,9 +48,7 @@ export default ((userOpts?: Partial<Options>) => {
<div class="section">
<div class="desc">
<h3>
<a
href={resolveRelative(fileData.slug!, page.slug!)}
class="internal">
<a href={resolveRelative(fileData.slug!, page.slug!)} class="internal">
{title}
</a>
</h3>
@ -65,20 +58,20 @@ export default ((userOpts?: Partial<Options>) => {
<Date date={getDate(cfg, page)!} locale={cfg.locale} />
</p>
)}
{opts.showTags && (
<ul class="tags">
{tags.map((tag) => (
<li>
<a
class="internal tag-link"
href={resolveRelative(
fileData.slug!,
`tags/${tag}` as FullSlug,
)}>
href={resolveRelative(fileData.slug!, `tags/${tag}` as FullSlug)}
>
{tag}
</a>
</li>
))}
</ul>
)}
</div>
</li>
)
@ -87,9 +80,7 @@ export default ((userOpts?: Partial<Options>) => {
{opts.linkToMore && remaining > 0 && (
<p>
<a href={resolveRelative(fileData.slug!, opts.linkToMore)}>
{i18n(cfg.locale).components.recentNotes.seeRemainingMore({
remaining,
})}
{i18n(cfg.locale).components.recentNotes.seeRemainingMore({ remaining })}
</a>
</p>
)}

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import style from "./styles/search.scss"
// @ts-ignore
import script from "./scripts/search.inline"
@ -18,26 +14,15 @@ const defaultOptions: SearchOptions = {
}
export default ((userOpts?: Partial<SearchOptions>) => {
const Search: QuartzComponent = ({
displayClass,
cfg,
}: QuartzComponentProps) => {
const Search: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
const opts = { ...defaultOptions, ...userOpts }
const searchPlaceholder = i18n(cfg.locale).components.search
.searchBarPlaceholder
const searchPlaceholder = i18n(cfg.locale).components.search.searchBarPlaceholder
return (
<div class={classNames(displayClass, "search")}>
<div id="search-icon">
{/* <p>Search</p> */}
<div></div>
<svg
tabIndex={0}
aria-labelledby="title desc"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 19.9 19.7">
<title id="title">Search</title>
<desc id="desc">Search</desc>
<button class="search-button" id="search-button">
<p>{i18n(cfg.locale).components.search.title}</p>
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7">
<title>Search</title>
<g class="search-path" fill="none">
<path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4" />
<circle cx="8" cy="8" r="7" />

View File

@ -1,8 +1,4 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import legacyStyle from "./styles/legacyToc.scss"
import modernStyle from "./styles/toc.scss"
import { classNames } from "../util/lang"
@ -33,7 +29,10 @@ const TableOfContents: QuartzComponent = ({
<button
type="button"
id="toc"
class={fileData.collapseToc ? "collapsed" : ""}>
class={fileData.collapseToc ? "collapsed" : ""}
aria-controls="toc-content"
aria-expanded={!fileData.collapseToc}
>
<h3>{i18n(cfg.locale).components.tableOfContents.title}</h3>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -45,7 +44,8 @@ const TableOfContents: QuartzComponent = ({
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="fold">
class="fold"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
@ -66,10 +66,7 @@ const TableOfContents: QuartzComponent = ({
TableOfContents.css = modernStyle
TableOfContents.afterDOMLoaded = script
const LegacyTableOfContents: QuartzComponent = ({
fileData,
cfg,
}: QuartzComponentProps) => {
const LegacyTableOfContents: QuartzComponent = ({ fileData, cfg }: QuartzComponentProps) => {
if (!fileData.toc) {
return null
}

View File

@ -1,15 +1,8 @@
import { pathToRoot, slugTag } from "../util/path"
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { classNames } from "../util/lang"
const TagList: QuartzComponent = ({
fileData,
displayClass,
}: QuartzComponentProps) => {
const TagList: QuartzComponent = ({ fileData, displayClass }: QuartzComponentProps) => {
const tags = fileData.frontmatter?.tags
const baseDir = pathToRoot(fileData.slug!)
if (tags && tags.length > 0) {

View File

@ -1,9 +1,5 @@
import { i18n } from "../../i18n"
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "../types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
const NotFound: QuartzComponent = ({ cfg }: QuartzComponentProps) => {
// If baseUrl contains a pathname after the domain, use this as the home link

View File

@ -1,9 +1,5 @@
import { htmlToJsx } from "../../util/jsx"
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "../types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
const Content: QuartzComponent = ({ fileData, tree }: QuartzComponentProps) => {
const content = htmlToJsx(fileData.filePath!, tree)

View File

@ -1,12 +1,8 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "../types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
import path from "path"
import style from "../styles/listPage.scss"
import {PageList} from "../PageList"
import { PageList, SortFn } from "../PageList"
import { stripSlashes, simplifySlug } from "../../util/path"
import { Root } from "hast"
import { htmlToJsx } from "../../util/jsx"
@ -32,8 +28,7 @@ export default ((opts?: Partial<FolderContentOptions>) => {
const folderSlug = stripSlashes(simplifySlug(fileData.slug!))
const allPagesInFolder = allFiles.filter((file) => {
const fileSlug = stripSlashes(simplifySlug(file.slug!))
const prefixed =
fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug
const prefixed = fileSlug.startsWith(folderSlug) && fileSlug !== folderSlug
const folderParts = folderSlug.split(path.posix.sep)
const fileParts = fileSlug.split(path.posix.sep)
const isDirectChild = fileParts.length === folderParts.length + 1

View File

@ -1,33 +1,36 @@
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "../types"
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "../types"
import style from "../styles/listPage.scss"
import {PageList} from "../PageList"
import { PageList, SortFn } from "../PageList"
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
import { QuartzPluginData } from "../../plugins/vfile"
import { Root } from "hast"
import { htmlToJsx } from "../../util/jsx"
import { i18n } from "../../i18n"
const numPages = 10
interface TagContentOptions {
sort?: SortFn
numPages: number
}
const defaultOptions: TagContentOptions = {
numPages: 10,
}
export default ((opts?: Partial<TagContentOptions>) => {
const options: TagContentOptions = { ...defaultOptions, ...opts }
const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
const { tree, fileData, allFiles, cfg } = props
const slug = fileData.slug
if (!(slug?.startsWith("tags/") || slug === "tags")) {
throw new Error(
`Component "TagContent" tried to render a non-tag page: ${slug}`,
)
throw new Error(`Component "TagContent" tried to render a non-tag page: ${slug}`)
}
const tag = simplifySlug(slug.slice("tags/".length) as FullSlug)
const allPagesWithTag = (tag: string) =>
allFiles.filter((file) =>
(file.frontmatter?.tags ?? [])
.flatMap(getAllSegmentPrefixes)
.includes(tag),
(file.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes).includes(tag),
)
const content =
@ -39,9 +42,7 @@ const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
if (tag === "/") {
const tags = [
...new Set(
allFiles
.flatMap((data) => data.frontmatter?.tags ?? [])
.flatMap(getAllSegmentPrefixes),
allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
),
].sort((a, b) => a.localeCompare(b))
const tagItemMap: Map<string, QuartzPluginData[]> = new Map()
@ -53,11 +54,7 @@ const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
<article>
<p>{content}</p>
</article>
<p>
{i18n(cfg.locale).pages.tagContent.totalTags({
count: tags.length,
})}
</p>
<p>{i18n(cfg.locale).pages.tagContent.totalTags({ count: tags.length })}</p>
<div>
{tags.map((tag) => {
const pages = tagItemMap.get(tag)!
@ -66,10 +63,14 @@ const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
allFiles: pages,
}
const contentPage = allFiles.filter(
(file) => file.slug === `tags/${tag}`,
)[0]
const content = contentPage?.description
const contentPage = allFiles.filter((file) => file.slug === `tags/${tag}`).at(0)
const root = contentPage?.htmlAst
const content =
!root || root?.children.length === 0
? contentPage?.description
: htmlToJsx(contentPage.filePath!, root)
return (
<div>
<h2>
@ -80,21 +81,20 @@ const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
{content && <p>{content}</p>}
<div class="page-listing">
<p>
{i18n(cfg.locale).pages.tagContent.itemsUnderTag({
count: pages.length,
})}
{pages.length > numPages && (
{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}
{pages.length > options.numPages && (
<>
{" "}
<span>
{i18n(cfg.locale).pages.tagContent.showingFirst({
count: numPages,
count: options.numPages,
})}
</span>
</>
)}
</p>
<PageList limit={numPages} {...listProps} />
<PageList limit={options.numPages} {...listProps} sort={opts?.sort} />
</div>
</div>
)
})}
@ -112,15 +112,12 @@ const TagContent: QuartzComponent = (props: QuartzComponentProps) => {
<div class={classes}>
<article>{content}</article>
<div class="page-listing">
<p>
{i18n(cfg.locale).pages.tagContent.itemsUnderTag({
count: pages.length,
})}
</p>
<p>{i18n(cfg.locale).pages.tagContent.itemsUnderTag({ count: pages.length })}</p>
<div>
<PageList {...listProps} />
</div>
</div>
</div>
)
}
}

View File

@ -3,13 +3,7 @@ import {QuartzComponent, QuartzComponentProps} from "./types"
import HeaderConstructor from "./Header"
import BodyConstructor from "./Body"
import { JSResourceToScriptElement, StaticResources } from "../util/resources"
import {
clone,
FullSlug,
RelativeURL,
joinSegments,
normalizeHastElement,
} from "../util/path"
import { clone, FullSlug, RelativeURL, joinSegments, normalizeHastElement } from "../util/path"
import { visit } from "unist-util-visit"
import { Root, Element, ElementContent } from "hast"
import { GlobalConfiguration } from "../cfg"
@ -77,9 +71,7 @@ export function renderPage(
if (classNames.includes("transclude")) {
const inner = node.children[0] as Element
const transcludeTarget = inner.properties["data-slug"] as FullSlug
const page = componentData.allFiles.find(
(f) => f.slug === transcludeTarget,
)
const page = componentData.allFiles.find((f) => f.slug === transcludeTarget)
if (!page) {
return
}
@ -104,16 +96,9 @@ export function renderPage(
{
type: "element",
tagName: "a",
properties: {
href: inner.properties?.href,
class: ["internal", "transclude-src"],
},
properties: { href: inner.properties?.href, class: ["internal", "transclude-src"] },
children: [
{
type: "text",
value: i18n(cfg.locale).components.transcludes
.linkToOriginal,
},
{ type: "text", value: i18n(cfg.locale).components.transcludes.linkToOriginal },
],
},
]
@ -126,8 +111,7 @@ export function renderPage(
let endIdx = undefined
for (const [i, el] of page.htmlAst.children.entries()) {
// skip non-headers
if (!(el.type === "element" && el.tagName.match(headerRegex)))
continue
if (!(el.type === "element" && el.tagName.match(headerRegex))) continue
const depth = Number(el.tagName.substring(1))
// lookin for our blockref
@ -149,23 +133,15 @@ export function renderPage(
}
node.children = [
...(
page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]
).map((child) =>
...(page.htmlAst.children.slice(startIdx, endIdx) as ElementContent[]).map((child) =>
normalizeHastElement(child as Element, slug, transcludeTarget),
),
{
type: "element",
tagName: "a",
properties: {
href: inner.properties?.href,
class: ["internal", "transclude-src"],
},
properties: { href: inner.properties?.href, class: ["internal", "transclude-src"] },
children: [
{
type: "text",
value: i18n(cfg.locale).components.transcludes.linkToOriginal,
},
{ type: "text", value: i18n(cfg.locale).components.transcludes.linkToOriginal },
],
},
]
@ -193,15 +169,9 @@ export function renderPage(
{
type: "element",
tagName: "a",
properties: {
href: inner.properties?.href,
class: ["internal", "transclude-src"],
},
properties: { href: inner.properties?.href, class: ["internal", "transclude-src"] },
children: [
{
type: "text",
value: i18n(cfg.locale).components.transcludes.linkToOriginal,
},
{ type: "text", value: i18n(cfg.locale).components.transcludes.linkToOriginal },
],
},
]
@ -242,12 +212,9 @@ export function renderPage(
</div>
)
const lang =
componentData.fileData.frontmatter?.lang ??
cfg.locale?.split("-")[0] ??
"en"
const lang = componentData.fileData.frontmatter?.lang ?? cfg.locale?.split("-")[0] ?? "en"
const doc = (
<html lang="en">
<html lang={lang}>
<Head {...componentData} />
<body data-slug={slug}>
<div id="quartz-root" class="page">

View File

@ -14,9 +14,7 @@ function toggleCallout(this: HTMLElement) {
}
const collapsed = parent.classList.contains("is-collapsed")
const height = collapsed
? parent.scrollHeight
: parent.scrollHeight + current.scrollHeight
const height = collapsed ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight
parent.style.maxHeight = height + "px"
current = parent

View File

@ -10,9 +10,7 @@ document.addEventListener("nav", () => {
const elId = checkboxId(index)
const switchState = (e: Event) => {
const newCheckboxState = (e.target as HTMLInputElement)?.checked
? "true"
: "false"
const newCheckboxState = (e.target as HTMLInputElement)?.checked ? "true" : "false"
localStorage.setItem(elId, newCheckboxState)
}

View File

@ -63,5 +63,8 @@ document.addEventListener("nav", () => {
giscusContainer.appendChild(giscusScript)
document.addEventListener("themechange", changeTheme)
window.addCleanup(() => document.removeEventListener("themechange", changeTheme))
// window.addCleanup(() => document.removeEventListener("themechange", changeTheme))
window.addCleanup(() =>
document.removeEventListener("themechange", changeTheme as EventListener)
);
})

View File

@ -1,6 +1,4 @@
const userPref = window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark"
const userPref = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"
const currentTheme = localStorage.getItem("theme") ?? userPref
document.documentElement.setAttribute("saved-theme", currentTheme)
@ -28,23 +26,15 @@ document.addEventListener("nav", () => {
}
// Darkmode toggle
const toggleSwitch = document.querySelector(
"#darkmode-toggle",
) as HTMLInputElement
const toggleSwitch = document.querySelector("#darkmode-toggle") as HTMLInputElement
toggleSwitch.addEventListener("change", switchTheme)
window.addCleanup(() =>
toggleSwitch.removeEventListener("change", switchTheme),
)
window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme))
if (currentTheme === "dark") {
toggleSwitch.checked = true
}
// Listen for changes in prefers-color-scheme
const colorSchemeMediaQuery = window.matchMedia(
"(prefers-color-scheme: dark)",
)
const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
colorSchemeMediaQuery.addEventListener("change", themeChange)
window.addCleanup(() =>
colorSchemeMediaQuery.removeEventListener("change", themeChange),
)
window.addCleanup(() => colorSchemeMediaQuery.removeEventListener("change", themeChange))
})

View File

@ -25,8 +25,7 @@ function toggleExplorer(this: HTMLElement) {
if (!content) return
content.classList.toggle("collapsed")
content.style.maxHeight =
content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
}
function toggleFolder(evt: MouseEvent) {
@ -83,26 +82,20 @@ function setupExplorer() {
const useSavedFolderState = explorer?.dataset.savestate === "true"
const oldExplorerState: FolderState[] =
storageTree && useSavedFolderState ? JSON.parse(storageTree) : []
const oldIndex = new Map(
oldExplorerState.map((entry) => [entry.path, entry.collapsed]),
)
const oldIndex = new Map(oldExplorerState.map((entry) => [entry.path, entry.collapsed]))
const newExplorerState: FolderState[] = explorer.dataset.tree
? JSON.parse(explorer.dataset.tree)
: []
currentExplorerState = []
for (const { path, collapsed } of newExplorerState) {
currentExplorerState.push({
path,
collapsed: oldIndex.get(path) ?? collapsed,
})
currentExplorerState.push({ path, collapsed: oldIndex.get(path) ?? collapsed })
}
currentExplorerState.map((folderState) => {
const folderLi = document.querySelector(
`[data-folderpath='${folderState.path}']`,
) as MaybeHTMLElement
const folderUl = folderLi?.parentElement
?.nextElementSibling as MaybeHTMLElement
const folderUl = folderLi?.parentElement?.nextElementSibling as MaybeHTMLElement
if (folderUl) {
setFolderState(folderUl, folderState.collapsed)
}
@ -127,9 +120,7 @@ document.addEventListener("nav", () => {
* @param collapsed if folder should be set to collapsed or not
*/
function setFolderState(folderElement: HTMLElement, collapsed: boolean) {
return collapsed
? folderElement.classList.remove("open")
: folderElement.classList.add("open")
return collapsed ? folderElement.classList.remove("open") : folderElement.classList.add("open")
}
/**

View File

@ -1,16 +1,7 @@
import type {
ContentDetails,
ContentIndex,
} from "../../plugins/emitters/contentIndex"
import type { ContentDetails } from "../../plugins/emitters/contentIndex"
import * as d3 from "d3"
import { registerEscapeHandler, removeAllChildren } from "./util"
import {
FullSlug,
SimpleSlug,
getFullSlug,
resolveRelative,
simplifySlug,
} from "../../util/path"
import { FullSlug, SimpleSlug, getFullSlug, resolveRelative, simplifySlug } from "../../util/path"
type NodeData = {
id: SimpleSlug
@ -101,10 +92,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
neighbourhood.add(cur)
const outgoing = links.filter((l) => l.source === cur)
const incoming = links.filter((l) => l.target === cur)
wl.push(
...outgoing.map((l) => l.target),
...incoming.map((l) => l.source),
)
wl.push(...outgoing.map((l) => l.target), ...incoming.map((l) => l.source))
}
}
} else {
@ -114,18 +102,14 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
const graphData: { nodes: NodeData[]; links: LinkData[] } = {
nodes: [...neighbourhood].map((url) => {
const text = url.startsWith("tags/")
? "#" + url.substring(5)
: data.get(url)?.title ?? url
const text = url.startsWith("tags/") ? "#" + url.substring(5) : (data.get(url)?.title ?? url)
return {
id: url,
text: text,
tags: data.get(url)?.tags ?? [],
}
}),
links: links.filter(
(l) => neighbourhood.has(l.source) && neighbourhood.has(l.target),
),
links: links.filter((l) => neighbourhood.has(l.source) && neighbourhood.has(l.target)),
}
const simulation: d3.Simulation<NodeData, LinkData> = d3
@ -148,12 +132,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [
-width / 2 / scale,
-height / 2 / scale,
width / scale,
height / scale,
])
.attr("viewBox", [-width / 2 / scale, -height / 2 / scale, width / scale, height / scale])
// draw links between nodes
const link = svg
@ -166,12 +145,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
.attr("stroke-width", 1)
// svg groups
const graphNode = svg
.append("g")
.selectAll("g")
.data(graphData.nodes)
.enter()
.append("g")
const graphNode = svg.append("g").selectAll("g").data(graphData.nodes).enter().append("g")
// calculate color
const color = (d: NodeData) => {
@ -212,9 +186,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
}
function nodeRadius(d: NodeData) {
const numLinks = links.filter(
(l: any) => l.source.id === d.id || l.target.id === d.id,
).length
const numLinks = links.filter((l: any) => l.source.id === d.id || l.target.id === d.id).length
return 2 + Math.sqrt(numLinks)
}
@ -236,15 +208,11 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
const currentId = d.id
const linkNodes = d3
.selectAll(".link")
.filter(
(d: any) => d.source.id === currentId || d.target.id === currentId,
)
.filter((d: any) => d.source.id === currentId || d.target.id === currentId)
if (focusOnHover) {
// fade out non-neighbour nodes
connectedNodes = linkNodes
.data()
.flatMap((d: any) => [d.source.id, d.target.id])
connectedNodes = linkNodes.data().flatMap((d: any) => [d.source.id, d.target.id])
d3.selectAll<HTMLElement, NodeData>(".link")
.transition()
@ -270,11 +238,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
}
// highlight links
linkNodes
.transition()
.duration(200)
.attr("stroke", "var(--gray)")
.attr("stroke-width", 1)
linkNodes.transition().duration(200).attr("stroke", "var(--gray)").attr("stroke-width", 1)
const bigFont = fontSize * 1.5
@ -291,21 +255,19 @@ async function renderGraph(container: string, fullSlug: FullSlug) {
})
.on("mouseleave", function (_, d) {
if (focusOnHover) {
d3.selectAll<HTMLElement, NodeData>(".link")
.transition()
.duration(200)
.style("opacity", 1)
d3.selectAll<HTMLElement, NodeData>(".link").transition().duration(200).style("opacity", 1)
d3.selectAll<HTMLElement, NodeData>(".node").transition().duration(200).style("opacity", 1)
d3.selectAll<HTMLElement, NodeData>(".node")
.transition()
.duration(200)
.style("opacity", 1)
.filter((d) => !connectedNodes.includes(d.id))
.nodes()
.map((it) => d3.select(it.parentNode as HTMLElement).select("text"))
.forEach((it) => it.transition().duration(200).style("opacity", it.attr("opacityOld")))
}
const currentId = d.id
const linkNodes = d3
.selectAll(".link")
.filter(
(d: any) => d.source.id === currentId || d.target.id === currentId,
)
.filter((d: any) => d.source.id === currentId || d.target.id === currentId)
linkNodes.transition().duration(200).attr("stroke", "var(--lightgray)")
@ -404,7 +366,5 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
const containerIcon = document.getElementById("global-graph-icon")
containerIcon?.addEventListener("click", renderGlobalGraph)
window.addCleanup(() =>
containerIcon?.removeEventListener("click", renderGlobalGraph),
)
window.addCleanup(() => containerIcon?.removeEventListener("click", renderGlobalGraph))
})

View File

@ -1,3 +0,0 @@
// import Plausible from "plausible-tracker"
// const { trackPageview } = Plausible()
// document.addEventListener("nav", () => trackPageview(), { passive: true })

View File

@ -3,7 +3,7 @@ import {normalizeRelativeURLs} from "../../util/path"
const p = new DOMParser()
async function mouseEnterHandler(
this: HTMLLinkElement,
this: HTMLAnchorElement,
{ clientX, clientY }: { clientX: number; clientY: number },
) {
const link = this
@ -94,22 +94,15 @@ async function mouseEnterHandler(
const heading = popoverInner.querySelector(hash) as HTMLElement | null
if (heading) {
// leave ~12px of buffer when scrolling to a heading
popoverInner.scroll({
top: heading.offsetTop - 12,
behavior: "instant",
})
popoverInner.scroll({ top: heading.offsetTop - 12, behavior: "instant" })
}
}
}
document.addEventListener("nav", () => {
const links = [
...document.getElementsByClassName("internal"),
] as HTMLLinkElement[]
const links = [...document.getElementsByClassName("internal")] as HTMLAnchorElement[]
for (const link of links) {
link.addEventListener("mouseenter", mouseEnterHandler)
window.addCleanup(() =>
link.removeEventListener("mouseenter", mouseEnterHandler),
)
window.addCleanup(() => link.removeEventListener("mouseenter", mouseEnterHandler))
}
})

View File

@ -15,8 +15,7 @@ interface Item {
type SearchType = "basic" | "tags"
let searchType: SearchType = "basic"
let currentSearchTerm: string = ""
const encoder = (str: string) =>
str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/)
const encoder = (str: string) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])/)
let index = new FlexSearch.Document<Item>({
charset: "latin:extra",
encode: encoder,
@ -66,18 +65,12 @@ function highlight(searchTerm: string, text: string, trim?: boolean) {
let endIndex = tokenizedText.length - 1
if (trim) {
const includesCheck = (tok: string) =>
tokenizedTerms.some((term) =>
tok.toLowerCase().startsWith(term.toLowerCase()),
)
tokenizedTerms.some((term) => tok.toLowerCase().startsWith(term.toLowerCase()))
const occurrencesIndices = tokenizedText.map(includesCheck)
let bestSum = 0
let bestIndex = 0
for (
let i = 0;
i < Math.max(tokenizedText.length - contextWindowWords, 0);
i++
) {
for (let i = 0; i < Math.max(tokenizedText.length - contextWindowWords, 0); i++) {
const window = occurrencesIndices.slice(i, i + contextWindowWords)
const windowSum = window.reduce((total, cur) => total + (cur ? 1 : 0), 0)
if (windowSum >= bestSum) {
@ -87,10 +80,7 @@ function highlight(searchTerm: string, text: string, trim?: boolean) {
}
startIndex = Math.max(bestIndex - contextWindowWords, 0)
endIndex = Math.min(
startIndex + 2 * contextWindowWords,
tokenizedText.length - 1,
)
endIndex = Math.min(startIndex + 2 * contextWindowWords, tokenizedText.length - 1)
tokenizedText = tokenizedText.slice(startIndex, endIndex)
}
@ -134,21 +124,15 @@ function highlightHTML(searchTerm: string, el: HTMLElement) {
let lastIndex = 0
for (const match of matches) {
const matchIndex = nodeText.indexOf(match, lastIndex)
spanContainer.appendChild(
document.createTextNode(nodeText.slice(lastIndex, matchIndex)),
)
spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex, matchIndex)))
spanContainer.appendChild(createHighlightSpan(match))
lastIndex = matchIndex + match.length
}
spanContainer.appendChild(
document.createTextNode(nodeText.slice(lastIndex)),
)
spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex)))
node.parentNode?.replaceChild(spanContainer, node)
} else if (node.nodeType === Node.ELEMENT_NODE) {
if ((node as HTMLElement).classList.contains("highlight")) return
Array.from(node.childNodes).forEach((child) =>
highlightTextNodes(child, term),
)
Array.from(node.childNodes).forEach((child) => highlightTextNodes(child, term))
}
}
@ -164,10 +148,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
const data = await fetchData
const container = document.getElementById("search-container")
const sidebar = container?.closest(".sidebar") as HTMLElement
const searchIcon = document.getElementById("search-icon")
const searchBar = document.getElementById(
"search-bar",
) as HTMLInputElement | null
const searchButton = document.getElementById("search-button")
const searchBar = document.getElementById("search-bar") as HTMLInputElement | null
const searchLayout = document.getElementById("search-layout")
const idDataMap = Object.keys(data) as FullSlug[]
@ -230,11 +212,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
const searchBarOpen = container?.classList.contains("active")
searchBarOpen ? hideSearch() : showSearch("basic")
return
} else if (
e.shiftKey &&
(e.ctrlKey || e.metaKey) &&
e.key.toLowerCase() === "k"
) {
} else if (e.shiftKey && (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
// Hotkey to open tag search
e.preventDefault()
const searchBarOpen = container?.classList.contains("active")
@ -259,9 +237,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
await displayPreview(active)
active.click()
} else {
const anchor = document.getElementsByClassName(
"result-card",
)[0] as HTMLInputElement | null
const anchor = document.getElementsByClassName("result-card")[0] as HTMLInputElement | null
if (!anchor || anchor?.classList.contains("no-match")) return
await displayPreview(anchor)
anchor.click()
@ -273,8 +249,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
const currentResult = currentHover
? currentHover
: (document.activeElement as HTMLInputElement | null)
const prevResult =
currentResult?.previousElementSibling as HTMLInputElement | null
const prevResult = currentResult?.previousElementSibling as HTMLInputElement | null
currentResult?.classList.remove("focus")
prevResult?.focus()
if (prevResult) currentHover = prevResult
@ -287,11 +262,8 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
if (document.activeElement === searchBar || currentHover !== null) {
const firstResult = currentHover
? currentHover
: (document.getElementsByClassName(
"result-card",
)[0] as HTMLInputElement | null)
const secondResult =
firstResult?.nextElementSibling as HTMLInputElement | null
: (document.getElementsByClassName("result-card")[0] as HTMLInputElement | null)
const secondResult = firstResult?.nextElementSibling as HTMLInputElement | null
firstResult?.classList.remove("focus")
secondResult?.focus()
if (secondResult) currentHover = secondResult
@ -305,10 +277,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
return {
id,
slug,
title:
searchType === "tags"
? data[slug].title
: highlight(term, data[slug].title ?? ""),
title: searchType === "tags" ? data[slug].title : highlight(term, data[slug].title ?? ""),
content: highlight(term, data[slug].content ?? "", true),
tags: highlightTags(term.substring(1), data[slug].tags),
}
@ -335,8 +304,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
}
const resultToHTML = ({ slug, title, content, tags }: Item) => {
const htmlTags =
tags.length > 0 ? `<ul class="tags">${tags.join("")}</ul>` : ``
const htmlTags = tags.length > 0 ? `<ul class="tags">${tags.join("")}</ul>` : ``
const itemTile = document.createElement("a")
itemTile.classList.add("result-card")
itemTile.id = slug
@ -345,14 +313,12 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
enablePreview && window.innerWidth > 600 ? "" : `<p>${content}</p>`
}`
itemTile.addEventListener("click", (event) => {
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
return
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return
hideSearch()
})
const handler = (event: MouseEvent) => {
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey)
return
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return
hideSearch()
}
@ -363,9 +329,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
}
itemTile.addEventListener("mouseenter", onMouseEnter)
window.addCleanup(() =>
itemTile.removeEventListener("mouseenter", onMouseEnter),
)
window.addCleanup(() => itemTile.removeEventListener("mouseenter", onMouseEnter))
itemTile.addEventListener("click", handler)
window.addCleanup(() => itemTile.removeEventListener("click", handler))
@ -422,9 +386,7 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
if (!searchLayout || !enablePreview || !el || !preview) return
const slug = el.id as FullSlug
const innerDiv = await fetchContent(slug).then((contents) =>
contents.flatMap((el) => [
...highlightHTML(currentSearchTerm, el as HTMLElement).children,
]),
contents.flatMap((el) => [...highlightHTML(currentSearchTerm, el as HTMLElement).children]),
)
previewInner = document.createElement("div")
previewInner.classList.add("preview-inner")
@ -492,20 +454,14 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
...getByField("content"),
...getByField("tags"),
])
const finalResults = [...allIds].map((id) =>
formatForDisplay(currentSearchTerm, id),
)
const finalResults = [...allIds].map((id) => formatForDisplay(currentSearchTerm, id))
await displayResults(finalResults)
}
document.addEventListener("keydown", shortcutHandler)
window.addCleanup(() =>
document.removeEventListener("keydown", shortcutHandler),
)
searchIcon?.addEventListener("click", () => showSearch("basic"))
window.addCleanup(() =>
searchIcon?.removeEventListener("click", () => showSearch("basic")),
)
window.addCleanup(() => document.removeEventListener("keydown", shortcutHandler))
searchButton?.addEventListener("click", () => showSearch("basic"))
window.addCleanup(() => searchButton?.removeEventListener("click", () => showSearch("basic")))
searchBar?.addEventListener("input", onType)
window.addCleanup(() => searchBar?.removeEventListener("input", onType))

View File

@ -1,10 +1,5 @@
import micromorph from "micromorph"
import {
FullSlug,
RelativeURL,
getFullSlug,
normalizeRelativeURLs,
} from "../../util/path"
import { FullSlug, RelativeURL, getFullSlug, normalizeRelativeURLs } from "../../util/path"
// adapted from `micromorph`
// https://github.com/natemoo-re/micromorph
@ -36,16 +31,11 @@ const getOpts = ({target}: Event): {url: URL; scroll?: boolean} | undefined => {
if ("routerIgnore" in a.dataset) return
const { href } = a
if (!isLocalUrl(href)) return
return {
url: new URL(href),
scroll: "routerNoscroll" in a.dataset ? false : undefined,
}
return { url: new URL(href), scroll: "routerNoscroll" in a.dataset ? false : undefined }
}
function notifyNav(url: FullSlug) {
const event: CustomEventMap["nav"] = new CustomEvent("nav", {
detail: {url},
})
const event: CustomEventMap["nav"] = new CustomEvent("nav", { detail: { url } })
document.dispatchEvent(event)
}
@ -96,9 +86,7 @@ async function navigate(url: URL, isBack: boolean = false) {
// scroll into place and add history
if (!isBack) {
if (url.hash) {
const el = document.getElementById(
decodeURIComponent(url.hash.substring(1)),
)
const el = document.getElementById(decodeURIComponent(url.hash.substring(1)))
el?.scrollIntoView()
} else {
window.scrollTo({ top: 0 })
@ -106,9 +94,7 @@ async function navigate(url: URL, isBack: boolean = false) {
}
// now, patch head
const elementsToRemove = document.head.querySelectorAll(
":not([spa-preserve])",
)
const elementsToRemove = document.head.querySelectorAll(":not([spa-preserve])")
elementsToRemove.forEach((el) => el.remove())
const elementsToAdd = html.head.querySelectorAll(":not([spa-preserve])")
elementsToAdd.forEach((el) => document.head.appendChild(el))
@ -133,9 +119,7 @@ function createRouter() {
event.preventDefault()
if (isSamePage(url) && url.hash) {
const el = document.getElementById(
decodeURIComponent(url.hash.substring(1)),
)
const el = document.getElementById(decodeURIComponent(url.hash.substring(1)))
el?.scrollIntoView()
history.pushState({}, "", url)
return
@ -150,8 +134,7 @@ function createRouter() {
window.addEventListener("popstate", (event) => {
const { url } = getOpts(event) ?? {}
if (window.location.hash && window.location.pathname === url?.pathname)
return
if (window.location.hash && window.location.pathname === url?.pathname) return
try {
navigate(new URL(window.location.toString()), true)
} catch (e) {
@ -184,7 +167,7 @@ if (!customElements.get("route-announcer")) {
const attrs = {
"aria-live": "assertive",
"aria-atomic": "true",
"style":
style:
"position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px",
}

View File

@ -23,8 +23,7 @@ function toggleToc(this: HTMLElement) {
const content = this.nextElementSibling as HTMLElement | undefined
if (!content) return
content.classList.toggle("collapsed")
content.style.maxHeight =
content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
}
function setupToc() {
@ -45,8 +44,6 @@ document.addEventListener("nav", () => {
// update toc entry highlighting
observer.disconnect()
const headers = document.querySelectorAll(
"h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]",
)
const headers = document.querySelectorAll("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]")
headers.forEach((header) => observer.observe(header))
})

View File

@ -1,7 +1,4 @@
export function registerEscapeHandler(
outsideContainer: HTMLElement | null,
cb: () => void,
) {
export function registerEscapeHandler(outsideContainer: HTMLElement | null, cb: () => void) {
if (!outsideContainer) return
function click(this: HTMLElement, e: HTMLElementEventMap["click"]) {
if (e.target !== this) return

View File

@ -62,9 +62,13 @@
margin-left: auto;
margin-right: auto;
@media all and (max-width: $fullPageWidth) {
width: 90%;
}
& > * {
width: 100%;
border-radius: 5px;
border-radius: 7px;
background: var(--light);
box-shadow:
0 14px 50px rgba(27, 33, 48, 0.12),
@ -86,71 +90,81 @@
}
& > #search-layout {
display: flex;
display: none;
flex-direction: row;
opacity: 0;
border: 1px solid var(--lightgray);
flex: 0 0 100%;
box-sizing: border-box;
&.display-results {
display: flex;
}
&[data-preview] > #results-container {
flex: 0 0 min(30%, 450px);
}
@media all and (min-width: $tabletBreakpoint) {
&[data-preview] {
& .result-card > p.preview {
display: none;
}
& > div {
// vh - #search-space.margin-top
height: calc(75vh - 12vh);
background: none;
&:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
border-right: 1px solid var(--lightgray);
border-top-right-radius: unset;
border-bottom-right-radius: unset;
}
&:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-top-left-radius: unset;
border-bottom-left-radius: unset;
}
}
}
}
& > div {
height: calc(75vh - 12vh);
border-radius: 5px;
}
@media all and (max-width: $tabletBreakpoint) {
display: block;
& > *:not(#results-container) {
& > #preview-container {
display: none !important;
}
& > #results-container {
&[data-preview] > #results-container {
width: 100%;
height: auto;
flex: 0 0 100%;
}
}
& .highlight {
background: color-mix(in srgb, var(--tertiary) 60%, transparent);
background: color-mix(in srgb, var(--tertiary) 60%, rgba(255, 255, 255, 0));
border-radius: 5px;
scroll-margin-top: 2rem;
}
& > #preview-container {
display: block;
box-sizing: border-box;
overflow: hidden;
& .preview-inner {
margin: 0 auto;
padding: 1em;
height: 100%;
width: 100%;
box-sizing: border-box;
overflow-y: auto;
font-family: inherit;
color: var(--dark);
line-height: 1.5em;
font-weight: 400;
background: var(--light);
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
box-shadow:
0 14px 50px rgba(27, 33, 48, 0.12),
0 10px 30px rgba(27, 33, 48, 0.16);
font-weight: $normalWeight;
overflow-y: auto;
padding: 0 2rem;
& .preview-inner {
margin: 0 auto;
width: min($pageWidth, 100%);
}
a.internal {
background-color: none;
a[role="anchor"] {
background-color: transparent;
}
}
@ -158,6 +172,7 @@
overflow-y: auto;
& .result-card {
overflow: hidden;
padding: 1em;
cursor: pointer;
transition: background 0.2s ease;
@ -173,7 +188,6 @@
margin: 0;
text-transform: none;
text-align: left;
background: var(--light);
outline: none;
font-weight: inherit;
@ -187,41 +201,23 @@
margin: 0;
}
& > ul > li {
margin: 0;
display: inline-block;
white-space: nowrap;
margin: 0;
overflow-wrap: normal;
}
& > ul {
list-style: none;
display: flex;
padding-left: 0;
gap: 0.4rem;
margin: 0;
& > ul.tags {
margin-top: 0.45rem;
box-sizing: border-box;
overflow: hidden;
background-clip: border-box;
margin-bottom: 0;
}
& > ul > li > p {
border-radius: 8px;
background-color: var(--highlight);
overflow: hidden;
background-clip: border-box;
padding: 0.03rem 0.4rem;
margin: 0;
padding: 0.2rem 0.4rem;
margin: 0 0.1rem;
line-height: 1.4rem;
font-weight: $boldWeight;
color: var(--secondary);
opacity: 0.85;
}
& > ul > li > .match-tag {
&.match-tag {
color: var(--tertiary);
font-weight: bold;
opacity: 1;
}
}
& > p {

View File

@ -24,6 +24,6 @@ export type QuartzComponent = ComponentType<QuartzComponentProps> & {
afterDOMLoaded?: string
}
export type QuartzComponentConstructor<
Options extends object | undefined = undefined,
> = (opts: Options) => QuartzComponent
export type QuartzComponentConstructor<Options extends object | undefined = undefined> = (
opts: Options,
) => QuartzComponent

View File

@ -1,5 +1,6 @@
import { Translation, CalloutTranslation } from "./locales/definition"
import en from "./locales/en-US"
import enUs from "./locales/en-US"
import enGb from "./locales/en-GB"
import fr from "./locales/fr-FR"
import it from "./locales/it-IT"
import ja from "./locales/ja-JP"
@ -64,7 +65,6 @@ export const TRANSLATIONS = {
} as const
export const defaultTranslation = "en-US"
export const i18n = (locale: ValidLocale): Translation =>
TRANSLATIONS[locale ?? defaultTranslation]
export const i18n = (locale: ValidLocale): Translation => TRANSLATIONS[locale ?? defaultTranslation]
export type ValidLocale = keyof typeof TRANSLATIONS
export type ValidCallout = keyof CalloutTranslation

View File

@ -75,17 +75,13 @@ export default {
folderContent: {
folder: "مجلد",
itemsUnderFolder: ({ count }) =>
count === 1
? "يوجد عنصر واحد فقط تحت هذا المجلد"
: `يوجد ${count} عناصر تحت هذا المجلد.`,
count === 1 ? "يوجد عنصر واحد فقط تحت هذا المجلد" : `يوجد ${count} عناصر تحت هذا المجلد.`,
},
tagContent: {
tag: "الوسم",
tagIndex: "مؤشر الوسم",
itemsUnderTag: ({ count }) =>
count === 1
? "يوجد عنصر واحد فقط تحت هذا الوسم"
: `يوجد ${count} عناصر تحت هذا الوسم.`,
count === 1 ? "يوجد عنصر واحد فقط تحت هذا الوسم" : `يوجد ${count} عناصر تحت هذا الوسم.`,
showingFirst: ({ count }) => `إظهار أول ${count} أوسمة.`,
totalTags: ({ count }) => `يوجد ${count} أوسمة.`,
},

View File

@ -64,24 +64,19 @@ export default {
},
error: {
title: "Nicht gefunden",
notFound:
"Diese Seite ist entweder nicht öffentlich oder existiert nicht.",
notFound: "Diese Seite ist entweder nicht öffentlich oder existiert nicht.",
home: "Return to Homepage",
},
folderContent: {
folder: "Ordner",
itemsUnderFolder: ({ count }) =>
count === 1
? "1 Datei in diesem Ordner."
: `${count} Dateien in diesem Ordner.`,
count === 1 ? "1 Datei in diesem Ordner." : `${count} Dateien in diesem Ordner.`,
},
tagContent: {
tag: "Tag",
tagIndex: "Tag-Übersicht",
itemsUnderTag: ({ count }) =>
count === 1
? "1 Datei mit diesem Tag."
: `${count} Dateien mit diesem Tag.`,
count === 1 ? "1 Datei mit diesem Tag." : `${count} Dateien mit diesem Tag.`,
showingFirst: ({ count }) => `Die ersten ${count} Tags werden angezeigt.`,
totalTags: ({ count }) => `${count} Tags insgesamt.`,
},

View File

@ -70,9 +70,7 @@ export default {
folderContent: {
folder: "Folder",
itemsUnderFolder: ({ count }) =>
count === 1
? "1 item under this folder."
: `${count} items under this folder.`,
count === 1 ? "1 item under this folder." : `${count} items under this folder.`,
},
tagContent: {
tag: "Tag",

View File

@ -54,13 +54,13 @@ export default {
title: "Tabla de Contenidos",
},
contentMeta: {
readingTime: ({minutes}) => `${minutes} min read`,
readingTime: ({ minutes }) => `Se lee en ${minutes} min`,
},
},
pages: {
rss: {
recentNotes: "Notas recientes",
lastFewNotes: ({count}) => `Últimás ${count} notas`,
lastFewNotes: ({ count }) => `Últimas ${count} notas`,
},
error: {
title: "No se ha encontrado.",
@ -70,19 +70,15 @@ export default {
folderContent: {
folder: "Carpeta",
itemsUnderFolder: ({ count }) =>
count === 1
? "1 artículo en esta carpeta."
: `${count} artículos en esta carpeta.`,
count === 1 ? "1 artículo en esta carpeta." : `${count} artículos en esta carpeta.`,
},
tagContent: {
tag: "Etiqueta",
tagIndex: "Índice de Etiquetas",
itemsUnderTag: ({ count }) =>
count === 1
? "1 artículo con esta etiqueta."
: `${count} artículos con esta etiqueta.`,
count === 1 ? "1 artículo con esta etiqueta." : `${count} artículos con esta etiqueta.`,
showingFirst: ({ count }) => `Mostrando las primeras ${count} etiquetas.`,
totalTags: ({count}) => `Se encontraron ${count} etiquetas en total.`,
totalTags: ({ count }) => `Se han encontrado ${count} etiquetas en total.`,
},
},
} as const satisfies Translation

View File

@ -70,9 +70,7 @@ export default {
folderContent: {
folder: "پوشه",
itemsUnderFolder: ({ count }) =>
count === 1
? ".یک مطلب در این پوشه است"
: `${count} مطلب در این پوشه است.`,
count === 1 ? ".یک مطلب در این پوشه است" : `${count} مطلب در این پوشه است.`,
},
tagContent: {
tag: "برچسب",

View File

@ -54,7 +54,7 @@ export default {
title: "Table des Matières",
},
contentMeta: {
readingTime: ({minutes}) => `${minutes} min read`,
readingTime: ({ minutes }) => `${minutes} min de lecture`,
},
},
pages: {
@ -63,24 +63,20 @@ export default {
lastFewNotes: ({ count }) => `Les dernières ${count} notes`,
},
error: {
title: "Pas trouvé",
title: "Introuvable",
notFound: "Cette page est soit privée, soit elle n'existe pas.",
home: "Retour à la page d'accueil",
},
folderContent: {
folder: "Dossier",
itemsUnderFolder: ({ count }) =>
count === 1
? "1 élément sous ce dossier."
: `${count} éléments sous ce dossier.`,
count === 1 ? "1 élément sous ce dossier." : `${count} éléments sous ce dossier.`,
},
tagContent: {
tag: "Étiquette",
tagIndex: "Index des étiquettes",
itemsUnderTag: ({ count }) =>
count === 1
? "1 élément avec cette étiquette."
: `${count} éléments avec cette étiquette.`,
count === 1 ? "1 élément avec cette étiquette." : `${count} éléments avec cette étiquette.`,
showingFirst: ({ count }) => `Affichage des premières ${count} étiquettes.`,
totalTags: ({ count }) => `Trouvé ${count} étiquettes au total.`,
},

View File

@ -69,8 +69,7 @@ export default {
},
folderContent: {
folder: "Mappa",
itemsUnderFolder: ({count}) =>
`Ebben a mappában ${count} elem található.`,
itemsUnderFolder: ({ count }) => `Ebben a mappában ${count} elem található.`,
},
tagContent: {
tag: "Címke",

View File

@ -70,17 +70,13 @@ export default {
folderContent: {
folder: "Cartella",
itemsUnderFolder: ({ count }) =>
count === 1
? "1 oggetto in questa cartella."
: `${count} oggetti in questa cartella.`,
count === 1 ? "1 oggetto in questa cartella." : `${count} oggetti in questa cartella.`,
},
tagContent: {
tag: "Etichetta",
tagIndex: "Indice etichette",
itemsUnderTag: ({ count }) =>
count === 1
? "1 oggetto con questa etichetta."
: `${count} oggetti con questa etichetta.`,
count === 1 ? "1 oggetto con questa etichetta." : `${count} oggetti con questa etichetta.`,
showingFirst: ({ count }) => `Prime ${count} etichette.`,
totalTags: ({ count }) => `Trovate ${count} etichette totali.`,
},

View File

@ -70,17 +70,13 @@ export default {
folderContent: {
folder: "Folder",
itemsUnderFolder: ({ count }) =>
count === 1
? "W tym folderze jest 1 element."
: `Elementów w folderze: ${count}.`,
count === 1 ? "W tym folderze jest 1 element." : `Elementów w folderze: ${count}.`,
},
tagContent: {
tag: "Znacznik",
tagIndex: "Spis znaczników",
itemsUnderTag: ({ count }) =>
count === 1
? "Oznaczony 1 element."
: `Elementów z tym znacznikiem: ${count}.`,
count === 1 ? "Oznaczony 1 element." : `Elementów z tym znacznikiem: ${count}.`,
showingFirst: ({ count }) => `Pokazuje ${count} pierwszych znaczników.`,
totalTags: ({ count }) => `Znalezionych wszystkich znaczników: ${count}.`,
},

View File

@ -70,9 +70,7 @@ export default {
folderContent: {
folder: "Arquivo",
itemsUnderFolder: ({ count }) =>
count === 1
? "1 item mneste arquivo."
: `${count} items neste arquivo.`,
count === 1 ? "1 item neste arquivo." : `${count} items neste arquivo.`,
},
tagContent: {
tag: "Tag",

View File

@ -71,17 +71,13 @@ export default {
folderContent: {
folder: "Dosar",
itemsUnderFolder: ({ count }) =>
count === 1
? "1 articol în acest dosar."
: `${count} elemente în acest dosar.`,
count === 1 ? "1 articol în acest dosar." : `${count} elemente în acest dosar.`,
},
tagContent: {
tag: "Etichetă",
tagIndex: "Indexul etichetelor",
itemsUnderTag: ({ count }) =>
count === 1
? "1 articol cu această etichetă."
: `${count} articole cu această etichetă.`,
count === 1 ? "1 articol cu această etichetă." : `${count} articole cu această etichetă.`,
showingFirst: ({ count }) => `Se afișează primele ${count} etichete.`,
totalTags: ({ count }) => `Au fost găsite ${count} etichete în total.`,
},

View File

@ -77,22 +77,15 @@ export default {
tagContent: {
tag: "Тег",
tagIndex: "Индекс тегов",
itemsUnderTag: ({count}) =>
`с этим тегом ${count} элемент${getForm(count, "", "а", "ов")}`,
itemsUnderTag: ({ count }) => `с этим тегом ${count} элемент${getForm(count, "", "а", "ов")}`,
showingFirst: ({ count }) =>
`Показыва${getForm(count, "ется", "ются", "ются")} ${count} тег${getForm(count, "", "а", "ов")}`,
totalTags: ({count}) =>
`Всего ${count} тег${getForm(count, "", "а", "ов")}`,
totalTags: ({ count }) => `Всего ${count} тег${getForm(count, "", "а", "ов")}`,
},
},
} as const satisfies Translation
function getForm(
number: number,
form1: string,
form2: string,
form5: string,
): string {
function getForm(number: number, form1: string, form2: string, form5: string): string {
const remainder100 = number % 100
const remainder10 = remainder100 % 10

View File

@ -54,7 +54,7 @@ export default {
title: "Зміст",
},
contentMeta: {
readingTime: ({minutes}) => `${minutes} min read`,
readingTime: ({ minutes }) => `${minutes} хв читання`,
},
},
pages: {
@ -68,21 +68,17 @@ export default {
home: "Повернутися на головну сторінку",
},
folderContent: {
folder: "Папка",
folder: "Тека",
itemsUnderFolder: ({ count }) =>
count === 1
? "У цій папці 1 елемент."
: `Елементів у цій папці: ${count}.`,
count === 1 ? "У цій теці 1 елемент." : `Елементів у цій теці: ${count}.`,
},
tagContent: {
tag: "Тег",
tagIndex: "Індекс тегу",
tag: "Мітка",
tagIndex: "Індекс мітки",
itemsUnderTag: ({ count }) =>
count === 1
? "1 елемент з цим тегом."
: `Елементів з цим тегом: ${count}.`,
showingFirst: ({count}) => `Показ перших ${count} тегів.`,
totalTags: ({count}) => `Всього знайдено тегів: ${count}.`,
count === 1 ? "1 елемент з цією міткою." : `Елементів з цією міткою: ${count}.`,
showingFirst: ({ count }) => `Показ перших ${count} міток.`,
totalTags: ({ count }) => `Всього знайдено міток: ${count}.`,
},
},
} as const satisfies Translation

View File

@ -70,9 +70,7 @@ export default {
folderContent: {
folder: "Thư Mục",
itemsUnderFolder: ({ count }) =>
count === 1
? "1 mục trong thư mục này."
: `${count} mục trong thư mục này.`,
count === 1 ? "1 mục trong thư mục này." : `${count} mục trong thư mục này.`,
},
tagContent: {
tag: "Thẻ",

View File

@ -58,13 +58,7 @@ export const NotFoundPage: QuartzEmitterPlugin = () => {
return [
await write({
ctx,
content: renderPage(
cfg,
slug,
componentData,
opts,
externalResources,
),
content: renderPage(cfg, slug, componentData, opts, externalResources),
slug,
ext: ".html",
}),

View File

@ -1,10 +1,4 @@
import {
FilePath,
FullSlug,
joinSegments,
resolveRelative,
simplifySlug,
} from "../../util/path"
import { FilePath, FullSlug, joinSegments, resolveRelative, simplifySlug } from "../../util/path"
import { QuartzEmitterPlugin } from "../types"
import path from "path"
import { write } from "./helpers"
@ -20,14 +14,9 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({
const { argv } = ctx
for (const [_tree, file] of content) {
const dir = path.posix.relative(
argv.directory,
path.dirname(file.data.filePath!),
)
const dir = path.posix.relative(argv.directory, path.dirname(file.data.filePath!))
const aliases = file.data.frontmatter?.aliases ?? []
const slugs = aliases.map(
(alias) => path.posix.join(dir, alias) as FullSlug,
)
const slugs = aliases.map((alias) => path.posix.join(dir, alias) as FullSlug)
const permalink = file.data.frontmatter?.permalink
if (typeof permalink === "string") {
slugs.push(permalink as FullSlug)
@ -39,10 +28,7 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({
slug = joinSegments(slug, "index") as FullSlug
}
graph.addEdge(
file.data.filePath!,
joinSegments(argv.output, slug + ".html") as FilePath,
)
graph.addEdge(file.data.filePath!, joinSegments(argv.output, slug + ".html") as FilePath)
}
}
@ -54,14 +40,9 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({
for (const [_tree, file] of content) {
const ogSlug = simplifySlug(file.data.slug!)
const dir = path.posix.relative(
argv.directory,
path.dirname(file.data.filePath!),
)
const dir = path.posix.relative(argv.directory, path.dirname(file.data.filePath!))
const aliases = file.data.frontmatter?.aliases ?? []
const slugs: FullSlug[] = aliases.map(
(alias) => path.posix.join(dir, alias) as FullSlug,
)
const slugs: FullSlug[] = aliases.map((alias) => path.posix.join(dir, alias) as FullSlug)
const permalink = file.data.frontmatter?.permalink
if (typeof permalink === "string") {
slugs.push(permalink as FullSlug)
@ -78,7 +59,7 @@ export const AliasRedirects: QuartzEmitterPlugin = () => ({
ctx,
content: `
<!DOCTYPE html>
<html lang="en">
<html lang="en-us">
<head>
<title>${ogSlug}</title>
<link rel="canonical" href="${redirUrl}">

View File

@ -9,10 +9,7 @@ import {QuartzConfig} from "../../cfg"
const filesToCopy = async (argv: Argv, cfg: QuartzConfig) => {
// glob all non MD files in content folder and copy it over
return await glob("**", argv.directory, [
"**/*.md",
...cfg.configuration.ignorePatterns,
])
return await glob("**", argv.directory, ["**/*.md", ...cfg.configuration.ignorePatterns])
}
export const Assets: QuartzEmitterPlugin = () => {

View File

@ -19,11 +19,7 @@ export const CNAME: QuartzEmitterPlugin = () => ({
},
async emit({ argv, cfg }, _content, _resources): Promise<FilePath[]> {
if (!cfg.configuration.baseUrl) {
console.warn(
chalk.yellow(
"CNAME emitter requires `baseUrl` to be set in your configuration",
),
)
console.warn(chalk.yellow("CNAME emitter requires `baseUrl` to be set in your configuration"))
return []
}
const path = joinSegments(argv.output, "CNAME")

View File

@ -58,9 +58,7 @@ function getComponentResources(ctx: BuildCtx): ComponentResources {
async function joinScripts(scripts: string[]): Promise<string> {
// wrap with iife to prevent scope collision
const script = scripts
.map((script) => `(function () {${script}})();`)
.join("\n")
const script = scripts.map((script) => `(function () {${script}})();`).join("\n")
// minify with esbuild
const res = await transpile(script, {
@ -70,10 +68,7 @@ async function joinScripts(scripts: string[]): Promise<string> {
return res.code
}
function addGlobalPageResources(
ctx: BuildCtx,
componentResources: ComponentResources,
) {
function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentResources) {
const cfg = ctx.cfg.configuration
// popovers
@ -190,15 +185,11 @@ export const ComponentResources: QuartzEmitterPlugin = () => {
let googleFontsStyleSheet = ""
if (cfg.theme.fontOrigin === "local") {
// let the user do it themselves in css
} else if (
cfg.theme.fontOrigin === "googleFonts" &&
!cfg.theme.cdnCaching
) {
} else if (cfg.theme.fontOrigin === "googleFonts" && !cfg.theme.cdnCaching) {
// when cdnCaching is true, we link to google fonts in Head.tsx
let match
const fontSourceRegex =
/url\((https:\/\/fonts.gstatic.com\/s\/[^)]+\.(woff2|ttf))\)/g
const fontSourceRegex = /url\((https:\/\/fonts.gstatic.com\/s\/[^)]+\.(woff2|ttf))\)/g
googleFontsStyleSheet = await (
await fetch(googleFontHref(ctx.cfg.configuration.theme))

View File

@ -2,13 +2,7 @@ import {Root} from "hast"
import { GlobalConfiguration } from "../../cfg"
import { getDate } from "../../components/Date"
import { escapeHTML } from "../../util/escape"
import {
FilePath,
FullSlug,
SimpleSlug,
joinSegments,
simplifySlug,
} from "../../util/path"
import { FilePath, FullSlug, SimpleSlug, joinSegments, simplifySlug } from "../../util/path"
import { QuartzEmitterPlugin } from "../types"
import { toHtml } from "hast-util-to-html"
import { write } from "./helpers"
@ -44,10 +38,7 @@ const defaultOptions: Options = {
function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string {
const base = cfg.baseUrl ?? ""
const createURLEntry = (
slug: SimpleSlug,
content: ContentDetails,
): string => `<url>
const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<url>
<loc>https://${joinSegments(base, encodeURI(slug))}</loc>
${content.date && `<lastmod>${content.date.toISOString()}</lastmod>`}
</url>`
@ -57,17 +48,10 @@ function generateSiteMap(cfg: GlobalConfiguration, idx: ContentIndex): string {
return `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">${urls}</urlset>`
}
function generateRSSFeed(
cfg: GlobalConfiguration,
idx: ContentIndex,
limit?: number,
): string {
function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex, limit?: number): string {
const base = cfg.baseUrl ?? ""
const createURLEntry = (
slug: SimpleSlug,
content: ContentDetails,
): string => `<item>
const createURLEntry = (slug: SimpleSlug, content: ContentDetails): string => `<item>
<title>${escapeHTML(content.title)}</title>
<link>https://${joinSegments(base, encodeURI(slug))}</link>
<guid>https://${joinSegments(base, encodeURI(slug))}</guid>
@ -120,16 +104,10 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
joinSegments(ctx.argv.output, "static/contentIndex.json") as FilePath,
)
if (opts?.enableSiteMap) {
graph.addEdge(
sourcePath,
joinSegments(ctx.argv.output, "sitemap.xml") as FilePath,
)
graph.addEdge(sourcePath, joinSegments(ctx.argv.output, "sitemap.xml") as FilePath)
}
if (opts?.enableRSS) {
graph.addEdge(
sourcePath,
joinSegments(ctx.argv.output, "index.xml") as FilePath,
)
graph.addEdge(sourcePath, joinSegments(ctx.argv.output, "index.xml") as FilePath)
}
}
@ -142,21 +120,14 @@ export const ContentIndex: QuartzEmitterPlugin<Partial<Options>> = (opts) => {
for (const [tree, file] of content) {
const slug = file.data.slug!
const date = getDate(ctx.cfg.configuration, file.data) ?? new Date()
if (
opts?.includeEmptyFiles ||
(file.data.text && file.data.text !== "")
) {
if (opts?.includeEmptyFiles || (file.data.text && file.data.text !== "")) {
linkIndex.set(slug, {
title: file.data.frontmatter?.title!,
links: file.data.links ?? [],
tags: file.data.frontmatter?.tags ?? [],
content: file.data.text ?? "",
richContent: opts?.rssFullHtml
? escapeHTML(
toHtml(tree as Root, {
allowDangerousHtml: true,
}),
)
? escapeHTML(toHtml(tree as Root, { allowDangerousHtml: true }))
: undefined,
date: date,
description: file.data.description ?? "",

View File

@ -9,16 +9,8 @@ import BodyConstructor from "../../components/Body"
import { pageResources, renderPage } from "../../components/renderPage"
import { FullPageLayout } from "../../cfg"
import { Argv } from "../../util/ctx"
import {
FilePath,
isRelativeURL,
joinSegments,
pathToRoot,
} from "../../util/path"
import {
defaultContentPageLayout,
sharedPageComponents,
} from "../../../quartz.layout"
import { FilePath, isRelativeURL, joinSegments, pathToRoot } from "../../util/path"
import { defaultContentPageLayout, sharedPageComponents } from "../../../quartz.layout"
import { Content } from "../../components"
import chalk from "chalk"
import { write } from "./helpers"
@ -33,9 +25,7 @@ const parseDependencies = (argv: Argv, hast: Root, file: VFile): string[] => {
let ref: string | null = null
if (
["script", "img", "audio", "video", "source", "iframe"].includes(
elem.tagName,
) &&
["script", "img", "audio", "video", "source", "iframe"].includes(elem.tagName) &&
elem?.properties?.src
) {
ref = elem.properties.src.toString()
@ -50,9 +40,7 @@ const parseDependencies = (argv: Argv, hast: Root, file: VFile): string[] => {
return
}
let fp = path
.join(file.data.filePath!, path.relative(argv.directory, ref))
.replace(/\\/g, "/")
let fp = path.join(file.data.filePath!, path.relative(argv.directory, ref)).replace(/\\/g, "/")
// markdown files have the .md extension stripped in hrefs, add it back here
if (!fp.split("/").pop()?.includes(".")) {
fp += ".md"
@ -63,9 +51,7 @@ const parseDependencies = (argv: Argv, hast: Root, file: VFile): string[] => {
return dependencies
}
export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
userOpts,
) => {
export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOpts) => {
const opts: FullPageLayout = {
...sharedPageComponents,
...defaultContentPageLayout,
@ -73,15 +59,7 @@ export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
...userOpts,
}
const {
head: Head,
header,
beforeBody,
pageBody,
left,
right,
footer: Footer,
} = opts
const { head: Head, header, beforeBody, pageBody, afterBody, left, right, footer: Footer } = opts
const Header = HeaderConstructor()
const Body = BodyConstructor()
@ -95,6 +73,7 @@ export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
...header,
...beforeBody,
pageBody,
...afterBody,
...left,
...right,
Footer,
@ -106,10 +85,7 @@ export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
for (const [tree, file] of content) {
const sourcePath = file.data.filePath!
const slug = file.data.slug!
graph.addEdge(
sourcePath,
joinSegments(ctx.argv.output, slug + ".html") as FilePath,
)
graph.addEdge(sourcePath, joinSegments(ctx.argv.output, slug + ".html") as FilePath)
parseDependencies(ctx.argv, tree as Root, file).forEach((dep) => {
graph.addEdge(dep as FilePath, sourcePath)
@ -141,13 +117,7 @@ export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
allFiles,
}
const content = renderPage(
cfg,
slug,
componentData,
opts,
externalResources,
)
const content = renderPage(cfg, slug, componentData, opts, externalResources)
const fp = await write({
ctx,
content,

View File

@ -3,7 +3,7 @@ import {QuartzComponentProps} from "../../components/types"
import HeaderConstructor from "../../components/Header"
import BodyConstructor from "../../components/Body"
import { pageResources, renderPage } from "../../components/renderPage"
import {ProcessedContent, defaultProcessedContent} from "../vfile"
import { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../vfile"
import { FullPageLayout } from "../../cfg"
import path from "path"
import {
@ -15,18 +15,17 @@ import {
pathToRoot,
simplifySlug,
} from "../../util/path"
import {
defaultListPageLayout,
sharedPageComponents,
} from "../../../quartz.layout"
import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
import { FolderContent } from "../../components"
import { write } from "./helpers"
import { i18n } from "../../i18n"
import DepGraph from "../../depgraph"
export const FolderPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
userOpts,
) => {
interface FolderPageOptions extends FullPageLayout {
sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number
}
export const FolderPage: QuartzEmitterPlugin<Partial<FolderPageOptions>> = (userOpts) => {
const opts: FullPageLayout = {
...sharedPageComponents,
...defaultListPageLayout,
@ -34,15 +33,7 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
...userOpts,
}
const {
head: Head,
header,
beforeBody,
pageBody,
left,
right,
footer: Footer,
} = opts
const { head: Head, header, beforeBody, pageBody, afterBody, left, right, footer: Footer } = opts
const Header = HeaderConstructor()
const Body = BodyConstructor()
@ -56,6 +47,7 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
...header,
...beforeBody,
pageBody,
...afterBody,
...left,
...right,
Footer,
@ -71,10 +63,7 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
const slug = vfile.data.slug
const folderName = path.dirname(slug ?? "") as SimpleSlug
if (slug && folderName !== "." && folderName !== "tags") {
graph.addEdge(
vfile.data.filePath!,
joinSegments(folderName, "index.html") as FilePath,
)
graph.addEdge(vfile.data.filePath!, joinSegments(folderName, "index.html") as FilePath)
}
})
@ -96,8 +85,7 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
}),
)
const folderDescriptions: Record<string, ProcessedContent> =
Object.fromEntries(
const folderDescriptions: Record<string, ProcessedContent> = Object.fromEntries(
[...folders].map((folder) => [
folder,
defaultProcessedContent({
@ -131,13 +119,7 @@ export const FolderPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
allFiles,
}
const content = renderPage(
cfg,
slug,
componentData,
opts,
externalResources,
)
const content = renderPage(cfg, slug, componentData, opts, externalResources)
const fp = await write({
ctx,
content,

View File

@ -10,12 +10,7 @@ type WriteOptions = {
content: string | Buffer
}
export const write = async ({
ctx,
slug,
ext,
content,
}: WriteOptions): Promise<FilePath> => {
export const write = async ({ ctx, slug, ext, content }: WriteOptions): Promise<FilePath> => {
const pathToPage = joinSegments(ctx.argv.output, slug + ext) as FilePath
const dir = path.dirname(pathToPage)
await fs.promises.mkdir(dir, { recursive: true })

View File

@ -30,8 +30,6 @@ export const Static: QuartzEmitterPlugin = () => ({
recursive: true,
dereference: true,
})
return fps.map((fp) =>
joinSegments(argv.output, "static", fp),
) as FilePath[]
return fps.map((fp) => joinSegments(argv.output, "static", fp)) as FilePath[]
},
})

View File

@ -3,7 +3,7 @@ import {QuartzComponentProps} from "../../components/types"
import HeaderConstructor from "../../components/Header"
import BodyConstructor from "../../components/Body"
import { pageResources, renderPage } from "../../components/renderPage"
import {ProcessedContent, defaultProcessedContent} from "../vfile"
import { ProcessedContent, QuartzPluginData, defaultProcessedContent } from "../vfile"
import { FullPageLayout } from "../../cfg"
import {
FilePath,
@ -12,18 +12,17 @@ import {
joinSegments,
pathToRoot,
} from "../../util/path"
import {
defaultListPageLayout,
sharedPageComponents,
} from "../../../quartz.layout"
import { defaultListPageLayout, sharedPageComponents } from "../../../quartz.layout"
import { TagContent } from "../../components"
import { write } from "./helpers"
import { i18n } from "../../i18n"
import DepGraph from "../../depgraph"
export const TagPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
userOpts,
) => {
interface TagPageOptions extends FullPageLayout {
sort?: (f1: QuartzPluginData, f2: QuartzPluginData) => number
}
export const TagPage: QuartzEmitterPlugin<Partial<TagPageOptions>> = (userOpts) => {
const opts: FullPageLayout = {
...sharedPageComponents,
...defaultListPageLayout,
@ -31,15 +30,7 @@ export const TagPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
...userOpts,
}
const {
head: Head,
header,
beforeBody,
pageBody,
left,
right,
footer: Footer,
} = opts
const { head: Head, header, beforeBody, pageBody, afterBody, left, right, footer: Footer } = opts
const Header = HeaderConstructor()
const Body = BodyConstructor()
@ -53,6 +44,7 @@ export const TagPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
...header,
...beforeBody,
pageBody,
...afterBody,
...left,
...right,
Footer,
@ -63,9 +55,7 @@ export const TagPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
for (const [_tree, file] of content) {
const sourcePath = file.data.filePath!
const tags = (file.data.frontmatter?.tags ?? []).flatMap(
getAllSegmentPrefixes,
)
const tags = (file.data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes)
// if the file has at least one tag, it is used in the tag index page
if (tags.length > 0) {
tags.push("index")
@ -87,16 +77,13 @@ export const TagPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
const cfg = ctx.cfg.configuration
const tags: Set<string> = new Set(
allFiles
.flatMap((data) => data.frontmatter?.tags ?? [])
.flatMap(getAllSegmentPrefixes),
allFiles.flatMap((data) => data.frontmatter?.tags ?? []).flatMap(getAllSegmentPrefixes),
)
// add base tag
tags.add("index")
const tagDescriptions: Record<string, ProcessedContent> =
Object.fromEntries(
const tagDescriptions: Record<string, ProcessedContent> = Object.fromEntries(
[...tags].map((tag) => {
const title =
tag === "index"
@ -136,13 +123,7 @@ export const TagPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (
allFiles,
}
const content = renderPage(
cfg,
slug,
componentData,
opts,
externalResources,
)
const content = renderPage(cfg, slug, componentData, opts, externalResources)
const fp = await write({
ctx,
content,

View File

@ -9,9 +9,7 @@ export function getStaticResourcesFromPlugins(ctx: BuildCtx) {
}
for (const transformer of ctx.cfg.plugins.transformers) {
const res = transformer.externalResources
? transformer.externalResources(ctx)
: {}
const res = transformer.externalResources ? transformer.externalResources(ctx) : {}
if (res?.js) {
staticResources.js.push(...res.js)
}

View File

@ -17,9 +17,7 @@ const defaultOptions: Options = {
csl: "apa",
}
export const Citations: QuartzTransformerPlugin<
Partial<Options> | undefined
> = (userOpts) => {
export const Citations: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "Citations",
@ -40,11 +38,8 @@ export const Citations: QuartzTransformerPlugin<
// using https://github.com/syntax-tree/unist-util-visit as they're just anochor links
plugins.push(() => {
return (tree, _file) => {
visit(tree, "element", (node, index, parent) => {
if (
node.tagName === "a" &&
node.properties?.href?.startsWith("#bib")
) {
visit(tree, "element", (node, _index, _parent) => {
if (node.tagName === "a" && node.properties?.href?.startsWith("#bib")) {
node.properties["data-no-popover"] = true
}
})

View File

@ -18,9 +18,7 @@ const urlRegex = new RegExp(
"g",
)
export const Description: QuartzTransformerPlugin<
Partial<Options> | undefined
> = (userOpts) => {
export const Description: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "Description",
@ -60,9 +58,7 @@ export const Description: QuartzTransformerPlugin<
while (currentDescriptionLength < len) {
const sentence = sentences[sentenceIdx]
if (!sentence) break
const currentSentence = sentence.endsWith(".")
? sentence
: sentence + "."
const currentSentence = sentence.endsWith(".") ? sentence : sentence + "."
finalDesc.push(currentSentence)
currentDescriptionLength += currentSentence.length
sentenceIdx++

View File

@ -36,15 +36,11 @@ function coerceToArray(input: string | string[]): string[] | undefined {
// remove all non-strings
return input
.filter(
(tag: unknown) => typeof tag === "string" || typeof tag === "number",
)
.filter((tag: unknown) => typeof tag === "string" || typeof tag === "number")
.map((tag: string | number) => tag.toString())
}
export const FrontMatter: QuartzTransformerPlugin<
Partial<Options> | undefined
> = (userOpts) => {
export const FrontMatter: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "FrontMatter",
@ -56,10 +52,7 @@ export const FrontMatter: QuartzTransformerPlugin<
const { data } = matter(Buffer.from(file.value), {
...opts,
engines: {
yaml: (s) =>
yaml.load(s, {
schema: yaml.JSON_SCHEMA,
}) as object,
yaml: (s) => yaml.load(s, { schema: yaml.JSON_SCHEMA }) as object,
toml: (s) => toml.parse(s) as object,
},
})
@ -67,22 +60,15 @@ export const FrontMatter: QuartzTransformerPlugin<
if (data.title != null && data.title.toString() !== "") {
data.title = data.title.toString()
} else {
data.title =
file.stem ??
i18n(cfg.configuration.locale).propertyDefaults.title
data.title = file.stem ?? i18n(cfg.configuration.locale).propertyDefaults.title
}
const tags = coerceToArray(coalesceAliases(data, ["tags", "tag"]))
if (tags)
data.tags = [...new Set(tags.map((tag: string) => slugTag(tag)))]
if (tags) data.tags = [...new Set(tags.map((tag: string) => slugTag(tag)))]
const aliases = coerceToArray(
coalesceAliases(data, ["aliases", "alias"]),
)
const aliases = coerceToArray(coalesceAliases(data, ["aliases", "alias"]))
if (aliases) data.aliases = aliases
const cssclasses = coerceToArray(
coalesceAliases(data, ["cssclasses", "cssclass"]),
)
const cssclasses = coerceToArray(coalesceAliases(data, ["cssclasses", "cssclass"]))
if (cssclasses) data.cssclasses = cssclasses
// fill in frontmatter

View File

@ -14,9 +14,7 @@ const defaultOptions: Options = {
linkHeadings: true,
}
export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<
Partial<Options> | undefined
> = (userOpts) => {
export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "GitHubFlavoredMarkdown",
@ -32,20 +30,20 @@ export const GitHubFlavoredMarkdown: QuartzTransformerPlugin<
{
behavior: "append",
properties: {
"role": "anchor",
"ariaHidden": true,
"tabIndex": -1,
role: "anchor",
ariaHidden: true,
tabIndex: -1,
"data-no-popover": true,
},
content: {
type: "element",
tagName: "svg",
properties: {
"width": 18,
"height": 18,
"viewBox": "0 0 24 24",
"fill": "none",
"stroke": "currentColor",
width: 18,
height: 18,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",

View File

@ -27,9 +27,7 @@ function coerceDate(fp: string, d: any): Date {
}
type MaybeDate = undefined | string | number
export const CreatedModifiedDate: QuartzTransformerPlugin<
Partial<Options> | undefined
> = (userOpts) => {
export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "CreatedModifiedDate",
@ -43,9 +41,7 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<
let published: MaybeDate = undefined
const fp = file.data.filePath!
const fullFp = path.isAbsolute(fp)
? fp
: path.posix.join(file.cwd, fp)
const fullFp = path.isAbsolute(fp) ? fp : path.posix.join(file.cwd, fp)
for (const source of opts.priority) {
if (source === "filesystem") {
const st = await fs.promises.stat(fullFp)
@ -66,9 +62,7 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<
}
try {
modified ||= await repo.getFileLatestModifiedDateAsync(
file.data.filePath!,
)
modified ||= await repo.getFileLatestModifiedDateAsync(file.data.filePath!)
} catch {
console.log(
chalk.yellow(

View File

@ -22,7 +22,7 @@ export const Latex: QuartzTransformerPlugin<Partial<Options>> = (opts) => {
},
htmlPlugins() {
if (engine === "katex") {
return [[rehypeKatex, {output: "html"}]]
return [[rehypeKatex, { output: "html", macros }]]
} else {
return [[rehypeMathjax, { macros }]]
}

View File

@ -32,9 +32,7 @@ const defaultOptions: Options = {
externalLinkIcon: true,
}
export const CrawlLinks: QuartzTransformerPlugin<
Partial<Options> | undefined
> = (userOpts) => {
export const CrawlLinks: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "LinkProcessing",
@ -100,9 +98,7 @@ export const CrawlLinks: QuartzTransformerPlugin<
}
// don't process external links or intra-document anchors
const isInternal = !(
isAbsoluteUrl(dest) || dest.startsWith("#")
)
const isInternal = !(isAbsoluteUrl(dest) || dest.startsWith("#"))
if (isInternal) {
dest = node.properties.href = transformLink(
file.data.slug!,
@ -112,10 +108,7 @@ export const CrawlLinks: QuartzTransformerPlugin<
// url.resolve is considered legacy
// WHATWG equivalent https://nodejs.dev/en/api/v18/url/#urlresolvefrom-to
const url = new URL(
dest,
"https://base.com/" + stripSlashes(curSlug, true),
)
const url = new URL(dest, "https://base.com/" + stripSlashes(curSlug, true))
const canonicalDest = url.pathname
let [destCanonical, _destAnchor] = splitAnchor(canonicalDest)
if (destCanonical.endsWith("/")) {
@ -123,9 +116,7 @@ export const CrawlLinks: QuartzTransformerPlugin<
}
// need to decodeURIComponent here as WHATWG URL percent-encodes everything
const full = decodeURIComponent(
stripSlashes(destCanonical, true),
) as FullSlug
const full = decodeURIComponent(stripSlashes(destCanonical, true)) as FullSlug
const simple = simplifySlug(full)
outgoing.add(simple)
node.properties["data-slug"] = full

View File

@ -1,18 +1,7 @@
import { QuartzTransformerPlugin } from "../types"
import {
Root,
Html,
BlockContent,
DefinitionContent,
Paragraph,
Code,
} from "mdast"
import { Root, Html, BlockContent, DefinitionContent, Paragraph, Code } from "mdast"
import { Element, Literal, Root as HtmlRoot } from "hast"
import {
ReplaceFunction,
findAndReplace as mdastFindReplace,
} from "mdast-util-find-and-replace"
import {slug as slugAnchor} from "github-slugger"
import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
import rehypeRaw from "rehype-raw"
import { SKIP, visit } from "unist-util-visit"
import path from "path"
@ -101,8 +90,7 @@ const arrowMapping: Record<string, string> = {
}
function canonicalizeCallout(calloutName: string): keyof typeof calloutMapping {
const normalizedCallout =
calloutName.toLowerCase() as keyof typeof calloutMapping
const normalizedCallout = calloutName.toLowerCase() as keyof typeof calloutMapping
// if callout is not recognized, make it a custom one
return calloutMapping[normalizedCallout] ?? calloutName
}
@ -140,20 +128,15 @@ const calloutLineRegex = new RegExp(/^> *\[\!\w+\|?.*?\][+-]?.*$/gm)
const tagRegex = new RegExp(
/(?:^| )#((?:[-_\p{L}\p{Emoji}\p{M}\d])+(?:\/[-_\p{L}\p{Emoji}\p{M}\d]+)*)/gu,
)
const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9]+)$/, "g")
const ytLinkRegex =
/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9]+)$/g)
const ytLinkRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
const ytPlaylistLinkRegex = /[?&]list=([^#?&]*)/
const videoExtensionRegex = new RegExp(
/\.(mp4|webm|ogg|avi|mov|flv|wmv|mkv|mpg|mpeg|3gp|m4v)$/,
)
const videoExtensionRegex = new RegExp(/\.(mp4|webm|ogg|avi|mov|flv|wmv|mkv|mpg|mpeg|3gp|m4v)$/)
const wikilinkImageEmbedRegex = new RegExp(
/^(?<alt>(?!^\d*x?\d*$).*?)?(\|?\s*?(?<width>\d+)(x(?<height>\d+))?)?$/,
)
export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
Partial<Options> | undefined
> = (userOpts) => {
export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
const mdastToHtml = (ast: PhrasingContent | Paragraph) => {
@ -210,10 +193,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
const [rawFp, rawHeader, rawAlias]: (string | undefined)[] = capture
const [fp, anchor] = splitAnchor(`${rawFp ?? ""}${rawHeader ?? ""}`)
const blockRef = Boolean(anchor?.startsWith("^")) ? "^" : ""
const displayAnchor = anchor
? `#${blockRef}${anchor.trim().replace(/^#+/, "")}`
: ""
const blockRef = Boolean(rawHeader?.match(/^#?\^/)) ? "^" : ""
const displayAnchor = anchor ? `#${blockRef}${anchor.trim().replace(/^#+/, "")}` : ""
const displayAlias = rawAlias ?? rawHeader?.replace("#", "|") ?? ""
const embedDisplay = value.startsWith("!") ? "!" : ""
@ -249,17 +230,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
if (value.startsWith("!")) {
const ext: string = path.extname(fp).toLowerCase()
const url = slugifyFilePath(fp as FilePath)
if (
[
".png",
".jpg",
".jpeg",
".gif",
".bmp",
".svg",
".webp",
].includes(ext)
) {
if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg", ".webp"].includes(ext)) {
const match = wikilinkImageEmbedRegex.exec(alias ?? "")
const alt = match?.groups?.alt ?? ""
const width = match?.groups?.width ?? "auto"
@ -275,23 +246,13 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
},
},
}
} else if (
[".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)
) {
} else if ([".mp4", ".webm", ".ogv", ".mov", ".mkv"].includes(ext)) {
return {
type: "html",
value: `<video src="${url}" controls></video>`,
}
} else if (
[
".mp3",
".webm",
".wav",
".m4a",
".ogg",
".3gp",
".flac",
].includes(ext)
[".mp3", ".webm", ".wav", ".m4a", ".ogg", ".3gp", ".flac"].includes(ext)
) {
return {
type: "html",
@ -307,7 +268,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
return {
type: "html",
data: { hProperties: { transclude: true } },
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}"><a href="${
value: `<blockquote class="transclude" data-url="${url}" data-block="${block}" data-embed-alias="${alias}"><a href="${
url + anchor
}" class="transclude-inner">Transclude of ${url}${block}</a></blockquote>`,
}
@ -399,24 +360,18 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
if (typeof replace === "string") {
node.value = node.value.replace(regex, replace)
} else {
node.value = node.value.replace(
regex,
(substring: string, ...args) => {
node.value = node.value.replace(regex, (substring: string, ...args) => {
const replaceValue = replace(substring, ...args)
if (typeof replaceValue === "string") {
return replaceValue
} else if (Array.isArray(replaceValue)) {
return replaceValue.map(mdastToHtml).join("")
} else if (
typeof replaceValue === "object" &&
replaceValue !== null
) {
} else if (typeof replaceValue === "object" && replaceValue !== null) {
return mdastToHtml(replaceValue)
} else {
return substring
}
},
)
})
}
}
})
@ -429,11 +384,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
plugins.push(() => {
return (tree: Root, _file) => {
visit(tree, "image", (node, index, parent) => {
if (
parent &&
index != undefined &&
videoExtensionRegex.test(node.url)
) {
if (parent && index != undefined && videoExtensionRegex.test(node.url)) {
const newNode: Html = {
type: "html",
value: `<video controls src="${node.url}"></video>`,
@ -457,10 +408,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
// find first line and callout content
const [firstChild, ...calloutContent] = node.children
if (
firstChild.type !== "paragraph" ||
firstChild.children[0]?.type !== "text"
) {
if (firstChild.type !== "paragraph" || firstChild.children[0]?.type !== "text") {
return
}
@ -471,31 +419,18 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
const match = firstLine.match(calloutRegex)
if (match && match.input) {
const [
calloutDirective,
typeString,
calloutMetaData,
collapseChar,
] = match
const calloutType = canonicalizeCallout(
typeString.toLowerCase(),
)
const [calloutDirective, typeString, calloutMetaData, collapseChar] = match
const calloutType = canonicalizeCallout(typeString.toLowerCase())
const collapse = collapseChar === "+" || collapseChar === "-"
const defaultState =
collapseChar === "-" ? "collapsed" : "expanded"
const titleContent = match.input
.slice(calloutDirective.length)
.trim()
const useDefaultTitle =
titleContent === "" && restOfTitle.length === 0
const defaultState = collapseChar === "-" ? "collapsed" : "expanded"
const titleContent = match.input.slice(calloutDirective.length).trim()
const useDefaultTitle = titleContent === "" && restOfTitle.length === 0
const titleNode: Paragraph = {
type: "paragraph",
children: [
{
type: "text",
value: useDefaultTitle
? capitalize(typeString)
: titleContent + " ",
value: useDefaultTitle ? capitalize(typeString) : titleContent + " ",
},
...restOfTitle,
],
@ -515,8 +450,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
</div>`,
}
const blockquoteContent: (BlockContent | DefinitionContent)[] =
[titleHtml]
const blockquoteContent: (BlockContent | DefinitionContent)[] = [titleHtml]
if (remainingText.length > 0) {
blockquoteContent.push({
type: "paragraph",
@ -544,7 +478,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
node.data = {
hProperties: {
...(node.data?.hProperties ?? {}),
"className": classNames.join(" "),
className: classNames.join(" "),
"data-callout": calloutType,
"data-callout-fold": collapse,
"data-callout-metadata": calloutMetaData,
@ -672,14 +606,10 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
plugins.push(() => {
return (tree: HtmlRoot) => {
visit(tree, "element", (node) => {
if (
node.tagName === "img" &&
typeof node.properties.src === "string"
) {
if (node.tagName === "img" && typeof node.properties.src === "string") {
const match = node.properties.src.match(ytLinkRegex)
const videoId = match && match[2].length == 11 ? match[2] : null
const playlistId =
node.properties.src.match(ytPlaylistLinkRegex)?.[1]
const playlistId = node.properties.src.match(ytPlaylistLinkRegex)?.[1]
if (videoId) {
// YouTube video (with optional playlist)
node.tagName = "iframe"
@ -713,10 +643,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<
plugins.push(() => {
return (tree: HtmlRoot, _file) => {
visit(tree, "element", (node) => {
if (
node.tagName === "input" &&
node.properties.type === "checkbox"
) {
if (node.tagName === "input" && node.properties.type === "checkbox") {
const isChecked = node.properties?.checked ?? false
node.properties = {
type: "checkbox",

View File

@ -22,10 +22,7 @@ const defaultOptions: Options = {
replaceOrgLatex: true,
}
const relrefRegex = new RegExp(
/\[([^\]]+)\]\(\{\{< relref "([^"]+)" >\}\}\)/,
"g",
)
const relrefRegex = new RegExp(/\[([^\]]+)\]\(\{\{< relref "([^"]+)" >\}\}\)/, "g")
const predefinedHeadingIdRegex = new RegExp(/(.*) {#(?:.*)}/, "g")
const hugoShortcodeRegex = new RegExp(/{{(.*)}}/, "g")
const figureTagRegex = new RegExp(/< ?figure src="(.*)" ?>/, "g")
@ -50,9 +47,7 @@ const quartzLatexRegex = new RegExp(/\$\$[\s\S]*?\$\$|\$.*?\$/, "g")
* markdown to make it compatible with quartz but the list of changes applied it
* is not exhaustive.
* */
export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin<
Partial<Options> | undefined
> = (userOpts) => {
export const OxHugoFlavouredMarkdown: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "OxHugoFlavouredMarkdown",

View File

@ -1,8 +1,5 @@
import { QuartzTransformerPlugin } from "../types"
import rehypePrettyCode, {
Options as CodeOptions,
Theme as CodeTheme,
} from "rehype-pretty-code"
import rehypePrettyCode, { Options as CodeOptions, Theme as CodeTheme } from "rehype-pretty-code"
interface Theme extends Record<string, CodeTheme> {
light: CodeTheme
@ -22,10 +19,8 @@ const defaultOptions: Options = {
keepBackground: false,
}
export const SyntaxHighlighting: QuartzTransformerPlugin<Options> = (
userOpts?: Partial<Options>,
) => {
const opts: Partial<CodeOptions> = {...defaultOptions, ...userOpts}
export const SyntaxHighlighting: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts: CodeOptions = { ...defaultOptions, ...userOpts }
return {
name: "SyntaxHighlighting",

View File

@ -12,7 +12,7 @@ export interface Options {
}
const defaultOptions: Options = {
maxDepth: 2, // 3
maxDepth: 3,
minEntries: 1,
showByDefault: true,
collapseByDefault: false,
@ -25,9 +25,7 @@ interface TocEntry {
}
const slugAnchor = new Slugger()
export const TableOfContents: QuartzTransformerPlugin<
Partial<Options> | undefined
> = (userOpts) => {
export const TableOfContents: QuartzTransformerPlugin<Partial<Options>> = (userOpts) => {
const opts = { ...defaultOptions, ...userOpts }
return {
name: "TableOfContents",
@ -35,8 +33,7 @@ export const TableOfContents: QuartzTransformerPlugin<
return [
() => {
return async (tree: Root, file) => {
const display =
file.data.frontmatter?.enableToc ?? opts.showByDefault
const display = file.data.frontmatter?.enableToc ?? opts.showByDefault
if (display) {
slugAnchor.reset()
const toc: TocEntry[] = []

View File

@ -37,11 +37,7 @@ export type QuartzEmitterPlugin<Options extends OptionType = undefined> = (
) => QuartzEmitterPluginInstance
export type QuartzEmitterPluginInstance = {
name: string
emit(
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
): Promise<FilePath[]>
emit(ctx: BuildCtx, content: ProcessedContent[], resources: StaticResources): Promise<FilePath[]>
getQuartzComponents(ctx: BuildCtx): QuartzComponent[]
getDependencyGraph?(
ctx: BuildCtx,

View File

@ -4,9 +4,7 @@ import {Data, VFile} from "vfile"
export type QuartzPluginData = Data
export type ProcessedContent = [Node, VFile]
export function defaultProcessedContent(
vfileData: Partial<QuartzPluginData>,
): ProcessedContent {
export function defaultProcessedContent(vfileData: Partial<QuartzPluginData>): ProcessedContent {
const root: Parent = { type: "root", children: [] }
const vfile = new VFile("")
vfile.data = vfileData

View File

@ -180,33 +180,15 @@ describe("link strategies", () => {
test("from a/b/c", () => {
const cur = "a/b/c" as FullSlug
assert.strictEqual(path.transformLink(cur, "a/b/d", opts), "../../a/b/d")
assert.strictEqual(
path.transformLink(cur, "a/b/index", opts),
"../../a/b/",
)
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../a/b/")
assert.strictEqual(path.transformLink(cur, "e/f", opts), "../../e/f")
assert.strictEqual(path.transformLink(cur, "e/g/h", opts), "../../e/g/h")
assert.strictEqual(path.transformLink(cur, "index", opts), "../../")
assert.strictEqual(
path.transformLink(cur, "index.png", opts),
"../../index.png",
)
assert.strictEqual(
path.transformLink(cur, "index#abc", opts),
"../../#abc",
)
assert.strictEqual(
path.transformLink(cur, "tag/test", opts),
"../../tag/test",
)
assert.strictEqual(
path.transformLink(cur, "a/b/c#test", opts),
"../../a/b/c#test",
)
assert.strictEqual(
path.transformLink(cur, "a/test.png", opts),
"../../a/test.png",
)
assert.strictEqual(path.transformLink(cur, "index.png", opts), "../../index.png")
assert.strictEqual(path.transformLink(cur, "index#abc", opts), "../../#abc")
assert.strictEqual(path.transformLink(cur, "tag/test", opts), "../../tag/test")
assert.strictEqual(path.transformLink(cur, "a/b/c#test", opts), "../../a/b/c#test")
assert.strictEqual(path.transformLink(cur, "a/test.png", opts), "../../a/test.png")
})
test("from a/b/index", () => {
@ -234,41 +216,20 @@ describe("link strategies", () => {
const cur = "a/b/c" as FullSlug
assert.strictEqual(path.transformLink(cur, "d", opts), "../../a/b/d")
assert.strictEqual(path.transformLink(cur, "h", opts), "../../e/g/h")
assert.strictEqual(
path.transformLink(cur, "a/b/index", opts),
"../../a/b/",
)
assert.strictEqual(
path.transformLink(cur, "a/b/index.png", opts),
"../../a/b/index.png",
)
assert.strictEqual(
path.transformLink(cur, "a/b/index#abc", opts),
"../../a/b/#abc",
)
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../a/b/")
assert.strictEqual(path.transformLink(cur, "a/b/index.png", opts), "../../a/b/index.png")
assert.strictEqual(path.transformLink(cur, "a/b/index#abc", opts), "../../a/b/#abc")
assert.strictEqual(path.transformLink(cur, "index", opts), "../../")
assert.strictEqual(
path.transformLink(cur, "index.png", opts),
"../../index.png",
)
assert.strictEqual(
path.transformLink(cur, "test.png", opts),
"../../a/test.png",
)
assert.strictEqual(
path.transformLink(cur, "index#abc", opts),
"../../#abc",
)
assert.strictEqual(path.transformLink(cur, "index.png", opts), "../../index.png")
assert.strictEqual(path.transformLink(cur, "test.png", opts), "../../a/test.png")
assert.strictEqual(path.transformLink(cur, "index#abc", opts), "../../#abc")
})
test("from a/b/index", () => {
const cur = "a/b/index" as FullSlug
assert.strictEqual(path.transformLink(cur, "d", opts), "../../a/b/d")
assert.strictEqual(path.transformLink(cur, "h", opts), "../../e/g/h")
assert.strictEqual(
path.transformLink(cur, "a/b/index", opts),
"../../a/b/",
)
assert.strictEqual(path.transformLink(cur, "a/b/index", opts), "../../a/b/")
assert.strictEqual(path.transformLink(cur, "index", opts), "../../")
})
@ -291,48 +252,24 @@ describe("link strategies", () => {
const cur = "a/b/c" as FullSlug
assert.strictEqual(path.transformLink(cur, "d", opts), "./d")
assert.strictEqual(path.transformLink(cur, "index", opts), "./")
assert.strictEqual(
path.transformLink(cur, "../../../index", opts),
"../../../",
)
assert.strictEqual(
path.transformLink(cur, "../../../index.png", opts),
"../../../index.png",
)
assert.strictEqual(
path.transformLink(cur, "../../../index#abc", opts),
"../../../#abc",
)
assert.strictEqual(
path.transformLink(cur, "../../../", opts),
"../../../",
)
assert.strictEqual(path.transformLink(cur, "../../../index", opts), "../../../")
assert.strictEqual(path.transformLink(cur, "../../../index.png", opts), "../../../index.png")
assert.strictEqual(path.transformLink(cur, "../../../index#abc", opts), "../../../#abc")
assert.strictEqual(path.transformLink(cur, "../../../", opts), "../../../")
assert.strictEqual(
path.transformLink(cur, "../../../a/test.png", opts),
"../../../a/test.png",
)
assert.strictEqual(
path.transformLink(cur, "../../../e/g/h", opts),
"../../../e/g/h",
)
assert.strictEqual(
path.transformLink(cur, "../../../e/g/h", opts),
"../../../e/g/h",
)
assert.strictEqual(
path.transformLink(cur, "../../../e/g/h#abc", opts),
"../../../e/g/h#abc",
)
assert.strictEqual(path.transformLink(cur, "../../../e/g/h", opts), "../../../e/g/h")
assert.strictEqual(path.transformLink(cur, "../../../e/g/h", opts), "../../../e/g/h")
assert.strictEqual(path.transformLink(cur, "../../../e/g/h#abc", opts), "../../../e/g/h#abc")
})
test("from a/b/index", () => {
const cur = "a/b/index" as FullSlug
assert.strictEqual(path.transformLink(cur, "../../index", opts), "../../")
assert.strictEqual(path.transformLink(cur, "../../", opts), "../../")
assert.strictEqual(
path.transformLink(cur, "../../e/g/h", opts),
"../../e/g/h",
)
assert.strictEqual(path.transformLink(cur, "../../e/g/h", opts), "../../e/g/h")
assert.strictEqual(path.transformLink(cur, "c", opts), "./c")
})

View File

@ -31,12 +31,7 @@ export type SimpleSlug = SlugLike<"simple">
export function isSimpleSlug(s: string): s is SimpleSlug {
const validStart = !(s.startsWith(".") || (s.length > 1 && s.startsWith("/")))
const validEnding = !endsWith(s, "index")
return (
validStart &&
!containsForbiddenCharacters(s) &&
validEnding &&
!_hasFileExtension(s)
)
return validStart && !containsForbiddenCharacters(s) && validEnding && !_hasFileExtension(s)
}
/** Can be found on `href`s but can also be constructed for client-side navigation (e.g. search and graph) */
@ -44,11 +39,7 @@ export type RelativeURL = SlugLike<"relative">
export function isRelativeURL(s: string): s is RelativeURL {
const validStart = /^\.{1,2}/.test(s)
const validEnding = !endsWith(s, "index")
return (
validStart &&
validEnding &&
![".md", ".html"].includes(_getFileExtension(s) ?? "")
)
return validStart && validEnding && ![".md", ".html"].includes(_getFileExtension(s) ?? "")
}
export function getFullSlug(window: Window): FullSlug {
@ -100,9 +91,7 @@ export function transformInternalLink(link: string): RelativeURL {
const folderPath = isFolderPath(fplike)
let segments = fplike.split("/").filter((x) => x.length > 0)
let prefix = segments.filter(isRelativeSegment).join("/")
let fp = segments
.filter((seg) => !isRelativeSegment(seg) && seg !== "")
.join("/")
let fp = segments.filter((seg) => !isRelativeSegment(seg) && seg !== "").join("/")
// manually add ext here as we want to not strip 'index' if it has an extension
const simpleSlug = simplifySlug(slugifyFilePath(fp as FilePath))
@ -114,18 +103,11 @@ 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 _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,
) {
export function normalizeRelativeURLs(el: Element | Document, destination: string | URL) {
el.querySelectorAll('[href^="./"], [href^="../"]').forEach((item) =>
_rebaseHtmlElement(item, "href", destination),
)
@ -145,20 +127,12 @@ const _rebaseHastElement = (
return
}
const rel = joinSegments(
resolveRelative(curBase, newBase),
"..",
el.properties[attr] as string,
)
const rel = joinSegments(resolveRelative(curBase, newBase), "..", el.properties[attr] as string)
el.properties[attr] = rel
}
}
export function normalizeHastElement(
rawEl: HastElement,
curBase: FullSlug,
newBase: FullSlug,
) {
export function normalizeHastElement(rawEl: HastElement, curBase: FullSlug, newBase: FullSlug) {
const el = clone(rawEl) // clone so we dont modify the original page
_rebaseHastElement(el, "src", curBase, newBase)
_rebaseHastElement(el, "href", curBase, newBase)
@ -187,14 +161,8 @@ export function pathToRoot(slug: FullSlug): RelativeURL {
return rootPath as RelativeURL
}
export function resolveRelative(
current: FullSlug,
target: FullSlug | SimpleSlug,
): RelativeURL {
const res = joinSegments(
pathToRoot(current),
simplifySlug(target as FullSlug),
) as RelativeURL
export function resolveRelative(current: FullSlug, target: FullSlug | SimpleSlug): RelativeURL {
const res = joinSegments(pathToRoot(current), simplifySlug(target as FullSlug)) as RelativeURL
return res
}
@ -235,11 +203,7 @@ export interface TransformOptions {
allSlugs: FullSlug[]
}
export function transformLink(
src: FullSlug,
target: string,
opts: TransformOptions,
): RelativeURL {
export function transformLink(src: FullSlug, target: string, opts: TransformOptions): RelativeURL {
let targetSlug = transformInternalLink(target)
if (opts.strategy === "relative") {
@ -265,8 +229,7 @@ export function transformLink(
}
// if it's not unique, then it's the absolute path from the vault root
return (joinSegments(pathToRoot(src), canonicalSlug) +
folderTail) as RelativeURL
return (joinSegments(pathToRoot(src), canonicalSlug) + folderTail) as RelativeURL
}
}
@ -292,9 +255,7 @@ function trimSuffix(s: string, suffix: string): string {
}
function containsForbiddenCharacters(s: string): boolean {
return (
s.includes(" ") || s.includes("#") || s.includes("?") || s.includes("&")
)
return s.includes(" ") || s.includes("#") || s.includes("?") || s.includes("&")
}
function _hasFileExtension(s: string): boolean {

View File

@ -16,20 +16,12 @@ export type JSResource = {
}
)
export function JSResourceToScriptElement(
resource: JSResource,
preserve?: boolean,
): JSX.Element {
export function JSResourceToScriptElement(resource: JSResource, preserve?: boolean): JSX.Element {
const scriptType = resource.moduleType ?? "application/javascript"
const spaPreserve = preserve ?? resource.spaPreserve
if (resource.contentType === "external") {
return (
<script
key={resource.src}
src={resource.src}
type={scriptType}
spa-preserve={spaPreserve}
/>
<script key={resource.src} src={resource.src} type={scriptType} spa-preserve={spaPreserve} />
)
} else {
const content = resource.script
@ -38,7 +30,8 @@ export function JSResourceToScriptElement(
key={randomUUID()}
type={scriptType}
spa-preserve={spaPreserve}
dangerouslySetInnerHTML={{__html: content}}></script>
dangerouslySetInnerHTML={{ __html: content }}
></script>
)
}
}

View File

@ -8,6 +8,7 @@ import {options} from "./util/sourcemap"
// only called from worker thread
export async function parseFiles(
buildId: string,
argv: Argv,
fps: FilePath[],
allSlugs: FullSlug[],