Merge branch 'v4' of https://github.com/jackyzha0/quartz into v4
This commit is contained in:
commit
3c7997983e
45 changed files with 8510 additions and 4267 deletions
|
@ -2,9 +2,9 @@ import sourceMapSupport from "source-map-support"
|
|||
sourceMapSupport.install(options)
|
||||
import path from "path"
|
||||
import { PerfTimer } from "./util/perf"
|
||||
import { rimraf } from "rimraf"
|
||||
import { rm } from "fs/promises"
|
||||
import { GlobbyFilterFunction, isGitIgnored } from "globby"
|
||||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
import { parseMarkdown } from "./processors/parse"
|
||||
import { filterContent } from "./processors/filter"
|
||||
import { emitContent } from "./processors/emit"
|
||||
|
@ -67,7 +67,7 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
|
|||
|
||||
const release = await mut.acquire()
|
||||
perf.addEvent("clean")
|
||||
await rimraf(path.join(output, "*"), { glob: true })
|
||||
await rm(output, { recursive: true, force: true })
|
||||
console.log(`Cleaned output directory \`${output}\` in ${perf.timeSince("clean")}`)
|
||||
|
||||
perf.addEvent("glob")
|
||||
|
@ -85,7 +85,9 @@ async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
|
|||
const filteredContent = filterContent(ctx, parsedFiles)
|
||||
|
||||
await emitContent(ctx, filteredContent)
|
||||
console.log(chalk.green(`Done processing ${markdownPaths.length} files in ${perf.timeSince()}`))
|
||||
console.log(
|
||||
styleText("green", `Done processing ${markdownPaths.length} files in ${perf.timeSince()}`),
|
||||
)
|
||||
release()
|
||||
|
||||
if (argv.watch) {
|
||||
|
@ -123,9 +125,10 @@ async function startWatching(
|
|||
ctx,
|
||||
mut,
|
||||
contentMap,
|
||||
ignored: (path) => {
|
||||
if (gitIgnoredMatcher(path)) return true
|
||||
const pathStr = path.toString()
|
||||
ignored: (fp) => {
|
||||
const pathStr = toPosixPath(fp.toString())
|
||||
if (pathStr.startsWith(".git/")) return true
|
||||
if (gitIgnoredMatcher(pathStr)) return true
|
||||
for (const pattern of cfg.configuration.ignorePatterns) {
|
||||
if (minimatch(pathStr, pattern)) {
|
||||
return true
|
||||
|
@ -186,7 +189,7 @@ async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildD
|
|||
|
||||
const perf = new PerfTimer()
|
||||
perf.addEvent("rebuild")
|
||||
console.log(chalk.yellow("Detected change, rebuilding..."))
|
||||
console.log(styleText("yellow", "Detected change, rebuilding..."))
|
||||
|
||||
// update changesSinceLastBuild
|
||||
for (const change of changes) {
|
||||
|
@ -248,9 +251,12 @@ async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildD
|
|||
// update allFiles and then allSlugs with the consistent view of content map
|
||||
ctx.allFiles = Array.from(contentMap.keys())
|
||||
ctx.allSlugs = ctx.allFiles.map((fp) => slugifyFilePath(fp as FilePath))
|
||||
const processedFiles = Array.from(contentMap.values())
|
||||
.filter((file) => file.type === "markdown")
|
||||
.map((file) => file.content)
|
||||
let processedFiles = filterContent(
|
||||
ctx,
|
||||
Array.from(contentMap.values())
|
||||
.filter((file) => file.type === "markdown")
|
||||
.map((file) => file.content),
|
||||
)
|
||||
|
||||
let emittedFiles = 0
|
||||
for (const emitter of cfg.plugins.emitters) {
|
||||
|
@ -281,7 +287,7 @@ async function rebuild(changes: ChangeEvent[], clientRefresh: () => void, buildD
|
|||
}
|
||||
|
||||
console.log(`Emitted ${emittedFiles} files to \`${argv.output}\` in ${perf.timeSince("rebuild")}`)
|
||||
console.log(chalk.green(`Done rebuilding in ${perf.timeSince()}`))
|
||||
console.log(styleText("green", `Done rebuilding in ${perf.timeSince()}`))
|
||||
changes.splice(0, numChangesInBuild)
|
||||
clientRefresh()
|
||||
release()
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { promises } from "fs"
|
||||
import path from "path"
|
||||
import esbuild from "esbuild"
|
||||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
import { sassPlugin } from "esbuild-sass-plugin"
|
||||
import fs from "fs"
|
||||
import { intro, outro, select, text } from "@clack/prompts"
|
||||
import { rimraf } from "rimraf"
|
||||
import { rm } from "fs/promises"
|
||||
import chokidar from "chokidar"
|
||||
import prettyBytes from "pretty-bytes"
|
||||
import { execSync, spawnSync } from "child_process"
|
||||
|
@ -48,7 +48,7 @@ function resolveContentPath(contentPath) {
|
|||
*/
|
||||
export async function handleCreate(argv) {
|
||||
console.log()
|
||||
intro(chalk.bgGreen.black(` Quartz v${version} `))
|
||||
intro(styleText(["bgGreen", "black"], ` Quartz v${version} `))
|
||||
const contentFolder = resolveContentPath(argv.directory)
|
||||
let setupStrategy = argv.strategy?.toLowerCase()
|
||||
let linkResolutionStrategy = argv.links?.toLowerCase()
|
||||
|
@ -61,12 +61,16 @@ export async function handleCreate(argv) {
|
|||
// Error handling
|
||||
if (!sourceDirectory) {
|
||||
outro(
|
||||
chalk.red(
|
||||
`Setup strategies (arg '${chalk.yellow(
|
||||
styleText(
|
||||
"red",
|
||||
`Setup strategies (arg '${styleText(
|
||||
"yellow",
|
||||
`-${CreateArgv.strategy.alias[0]}`,
|
||||
)}') other than '${chalk.yellow(
|
||||
)}') other than '${styleText(
|
||||
"yellow",
|
||||
"new",
|
||||
)}' require content folder argument ('${chalk.yellow(
|
||||
)}' require content folder argument ('${styleText(
|
||||
"yellow",
|
||||
`-${CreateArgv.source.alias[0]}`,
|
||||
)}') to be set`,
|
||||
),
|
||||
|
@ -75,19 +79,23 @@ export async function handleCreate(argv) {
|
|||
} else {
|
||||
if (!fs.existsSync(sourceDirectory)) {
|
||||
outro(
|
||||
chalk.red(
|
||||
`Input directory to copy/symlink 'content' from not found ('${chalk.yellow(
|
||||
styleText(
|
||||
"red",
|
||||
`Input directory to copy/symlink 'content' from not found ('${styleText(
|
||||
"yellow",
|
||||
sourceDirectory,
|
||||
)}', invalid argument "${chalk.yellow(`-${CreateArgv.source.alias[0]}`)})`,
|
||||
)}', invalid argument "${styleText("yellow", `-${CreateArgv.source.alias[0]}`)})`,
|
||||
),
|
||||
)
|
||||
process.exit(1)
|
||||
} else if (!fs.lstatSync(sourceDirectory).isDirectory()) {
|
||||
outro(
|
||||
chalk.red(
|
||||
`Source directory to copy/symlink 'content' from is not a directory (found file at '${chalk.yellow(
|
||||
styleText(
|
||||
"red",
|
||||
`Source directory to copy/symlink 'content' from is not a directory (found file at '${styleText(
|
||||
"yellow",
|
||||
sourceDirectory,
|
||||
)}', invalid argument ${chalk.yellow(`-${CreateArgv.source.alias[0]}`)}")`,
|
||||
)}', invalid argument ${styleText("yellow", `-${CreateArgv.source.alias[0]}`)}")`,
|
||||
),
|
||||
)
|
||||
process.exit(1)
|
||||
|
@ -119,7 +127,7 @@ export async function handleCreate(argv) {
|
|||
if (contentStat.isSymbolicLink()) {
|
||||
await fs.promises.unlink(contentFolder)
|
||||
} else {
|
||||
await rimraf(contentFolder)
|
||||
await rm(contentFolder, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,7 +237,7 @@ export async function handleBuild(argv) {
|
|||
argv.watch = true
|
||||
}
|
||||
|
||||
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
|
||||
console.log(`\n${styleText(["bgGreen", "black"], ` Quartz v${version} `)} \n`)
|
||||
const ctx = await esbuild.context({
|
||||
entryPoints: [fp],
|
||||
outfile: cacheFile,
|
||||
|
@ -304,13 +312,13 @@ export async function handleBuild(argv) {
|
|||
}
|
||||
|
||||
if (cleanupBuild) {
|
||||
console.log(chalk.yellow("Detected a source code change, doing a hard rebuild..."))
|
||||
console.log(styleText("yellow", "Detected a source code change, doing a hard rebuild..."))
|
||||
await cleanupBuild()
|
||||
}
|
||||
|
||||
const result = await ctx.rebuild().catch((err) => {
|
||||
console.error(`${chalk.red("Couldn't parse Quartz configuration:")} ${fp}`)
|
||||
console.log(`Reason: ${chalk.grey(err)}`)
|
||||
console.error(`${styleText("red", "Couldn't parse Quartz configuration:")} ${fp}`)
|
||||
console.log(`Reason: ${styleText("grey", err)}`)
|
||||
process.exit(1)
|
||||
})
|
||||
release()
|
||||
|
@ -348,7 +356,8 @@ export async function handleBuild(argv) {
|
|||
const server = http.createServer(async (req, res) => {
|
||||
if (argv.baseDir && !req.url?.startsWith(argv.baseDir)) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
styleText(
|
||||
"red",
|
||||
`[404] ${req.url} (warning: link outside of site, this is likely a Quartz bug)`,
|
||||
),
|
||||
)
|
||||
|
@ -383,8 +392,10 @@ export async function handleBuild(argv) {
|
|||
})
|
||||
const status = res.statusCode
|
||||
const statusString =
|
||||
status >= 200 && status < 300 ? chalk.green(`[${status}]`) : chalk.red(`[${status}]`)
|
||||
console.log(statusString + chalk.grey(` ${argv.baseDir}${req.url}`))
|
||||
status >= 200 && status < 300
|
||||
? styleText("green", `[${status}]`)
|
||||
: styleText("red", `[${status}]`)
|
||||
console.log(statusString + styleText("grey", ` ${argv.baseDir}${req.url}`))
|
||||
release()
|
||||
}
|
||||
|
||||
|
@ -393,7 +404,10 @@ export async function handleBuild(argv) {
|
|||
res.writeHead(302, {
|
||||
Location: newFp,
|
||||
})
|
||||
console.log(chalk.yellow("[302]") + chalk.grey(` ${argv.baseDir}${req.url} -> ${newFp}`))
|
||||
console.log(
|
||||
styleText("yellow", "[302]") +
|
||||
styleText("grey", ` ${argv.baseDir}${req.url} -> ${newFp}`),
|
||||
)
|
||||
res.end()
|
||||
}
|
||||
|
||||
|
@ -443,7 +457,8 @@ export async function handleBuild(argv) {
|
|||
const wss = new WebSocketServer({ port: argv.wsPort })
|
||||
wss.on("connection", (ws) => connections.push(ws))
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
styleText(
|
||||
"cyan",
|
||||
`Started a Quartz server listening at http://localhost:${argv.port}${argv.baseDir}`,
|
||||
),
|
||||
)
|
||||
|
@ -467,7 +482,7 @@ export async function handleBuild(argv) {
|
|||
.on("change", () => build(clientRefresh))
|
||||
.on("unlink", () => build(clientRefresh))
|
||||
|
||||
console.log(chalk.grey("hint: exit with ctrl+c"))
|
||||
console.log(styleText("grey", "hint: exit with ctrl+c"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -477,7 +492,7 @@ export async function handleBuild(argv) {
|
|||
*/
|
||||
export async function handleUpdate(argv) {
|
||||
const contentFolder = resolveContentPath(argv.directory)
|
||||
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
|
||||
console.log(`\n${styleText(["bgGreen", "black"], ` Quartz v${version} `)} \n`)
|
||||
console.log("Backing up your content")
|
||||
execSync(
|
||||
`git remote show upstream || git remote add upstream https://github.com/jackyzha0/quartz.git`,
|
||||
|
@ -490,7 +505,7 @@ export async function handleUpdate(argv) {
|
|||
try {
|
||||
gitPull(UPSTREAM_NAME, QUARTZ_SOURCE_BRANCH)
|
||||
} catch {
|
||||
console.log(chalk.red("An error occurred above while pulling updates."))
|
||||
console.log(styleText("red", "An error occurred above while pulling updates."))
|
||||
await popContentFolder(contentFolder)
|
||||
return
|
||||
}
|
||||
|
@ -517,9 +532,9 @@ export async function handleUpdate(argv) {
|
|||
|
||||
const res = spawnSync("npm", ["i"], opts)
|
||||
if (res.status === 0) {
|
||||
console.log(chalk.green("Done!"))
|
||||
console.log(styleText("green", "Done!"))
|
||||
} else {
|
||||
console.log(chalk.red("An error occurred above while installing dependencies."))
|
||||
console.log(styleText("red", "An error occurred above while installing dependencies."))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -538,14 +553,14 @@ export async function handleRestore(argv) {
|
|||
*/
|
||||
export async function handleSync(argv) {
|
||||
const contentFolder = resolveContentPath(argv.directory)
|
||||
console.log(chalk.bgGreen.black(`\n Quartz v${version} \n`))
|
||||
console.log(`\n${styleText(["bgGreen", "black"], ` Quartz v${version} `)}\n`)
|
||||
console.log("Backing up your content")
|
||||
|
||||
if (argv.commit) {
|
||||
const contentStat = await fs.promises.lstat(contentFolder)
|
||||
if (contentStat.isSymbolicLink()) {
|
||||
const linkTarg = await fs.promises.readlink(contentFolder)
|
||||
console.log(chalk.yellow("Detected symlink, trying to dereference before committing"))
|
||||
console.log(styleText("yellow", "Detected symlink, trying to dereference before committing"))
|
||||
|
||||
// stash symlink file
|
||||
await stashContentFolder(contentFolder)
|
||||
|
@ -580,7 +595,7 @@ export async function handleSync(argv) {
|
|||
try {
|
||||
gitPull(ORIGIN_NAME, QUARTZ_SOURCE_BRANCH)
|
||||
} catch {
|
||||
console.log(chalk.red("An error occurred above while pulling updates."))
|
||||
console.log(styleText("red", "An error occurred above while pulling updates."))
|
||||
await popContentFolder(contentFolder)
|
||||
return
|
||||
}
|
||||
|
@ -594,10 +609,12 @@ export async function handleSync(argv) {
|
|||
stdio: "inherit",
|
||||
})
|
||||
if (res.status !== 0) {
|
||||
console.log(chalk.red(`An error occurred above while pushing to remote ${ORIGIN_NAME}.`))
|
||||
console.log(
|
||||
styleText("red", `An error occurred above while pushing to remote ${ORIGIN_NAME}.`),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green("Done!"))
|
||||
console.log(styleText("green", "Done!"))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { isCancel, outro } from "@clack/prompts"
|
||||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
import { contentCacheFolder } from "./constants.js"
|
||||
import { spawnSync } from "child_process"
|
||||
import fs from "fs"
|
||||
|
@ -14,7 +14,7 @@ export function escapePath(fp) {
|
|||
|
||||
export function exitIfCancel(val) {
|
||||
if (isCancel(val)) {
|
||||
outro(chalk.red("Exiting"))
|
||||
outro(styleText("red", "Exiting"))
|
||||
process.exit(0)
|
||||
} else {
|
||||
return val
|
||||
|
@ -36,9 +36,9 @@ export function gitPull(origin, branch) {
|
|||
const flags = ["--no-rebase", "--autostash", "-s", "recursive", "-X", "ours", "--no-edit"]
|
||||
const out = spawnSync("git", ["pull", ...flags, origin, branch], { stdio: "inherit" })
|
||||
if (out.stderr) {
|
||||
throw new Error(chalk.red(`Error while pulling updates: ${out.stderr}`))
|
||||
throw new Error(styleText("red", `Error while pulling updates: ${out.stderr}`))
|
||||
} else if (out.status !== 0) {
|
||||
throw new Error(chalk.red("Error while pulling updates"))
|
||||
throw new Error(styleText("red", "Error while pulling updates"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ type Options = {
|
|||
strict?: boolean
|
||||
reactionsEnabled?: boolean
|
||||
inputPosition?: "top" | "bottom"
|
||||
lang?: string
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,6 +51,7 @@ export default ((opts: Options) => {
|
|||
data-theme-url={
|
||||
opts.options.themeUrl ?? `https://${cfg.baseUrl ?? "example.com"}/static/giscus`
|
||||
}
|
||||
data-lang={opts.options.lang ?? "en"}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { concatenateResources } from "../util/resources"
|
||||
import { classNames } from "../util/lang"
|
||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
|
||||
type FlexConfig = {
|
||||
|
@ -23,7 +24,10 @@ export default ((config: FlexConfig) => {
|
|||
const gap = config.gap ?? "1rem"
|
||||
|
||||
return (
|
||||
<div style={`display: flex; flex-direction: ${direction}; flex-wrap: ${wrap}; gap: ${gap};`}>
|
||||
<div
|
||||
class={classNames(props.displayClass, "flex-component")}
|
||||
style={`flex-direction: ${direction}; flex-wrap: ${wrap}; gap: ${gap};`}
|
||||
>
|
||||
{config.components.map((c) => {
|
||||
const grow = c.grow ? 1 : 0
|
||||
const shrink = (c.shrink ?? true) ? 1 : 0
|
||||
|
|
|
@ -55,6 +55,7 @@ type GiscusElement = Omit<HTMLElement, "dataset"> & {
|
|||
strict: string
|
||||
reactionsEnabled: string
|
||||
inputPosition: "top" | "bottom"
|
||||
lang: string
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +79,7 @@ document.addEventListener("nav", () => {
|
|||
giscusScript.setAttribute("data-strict", giscusContainer.dataset.strict)
|
||||
giscusScript.setAttribute("data-reactions-enabled", giscusContainer.dataset.reactionsEnabled)
|
||||
giscusScript.setAttribute("data-input-position", giscusContainer.dataset.inputPosition)
|
||||
|
||||
giscusScript.setAttribute("data-lang", giscusContainer.dataset.lang)
|
||||
const theme = document.documentElement.getAttribute("saved-theme")
|
||||
if (theme) {
|
||||
giscusScript.setAttribute("data-theme", getThemeUrl(getThemeName(theme)))
|
||||
|
|
|
@ -68,30 +68,6 @@ type TweenNode = {
|
|||
stop: () => void
|
||||
}
|
||||
|
||||
// workaround for pixijs webgpu issue: https://github.com/pixijs/pixijs/issues/11389
|
||||
async function determineGraphicsAPI(): Promise<"webgpu" | "webgl"> {
|
||||
const adapter = await navigator.gpu?.requestAdapter().catch(() => null)
|
||||
const device = adapter && (await adapter.requestDevice().catch(() => null))
|
||||
if (!device) {
|
||||
return "webgl"
|
||||
}
|
||||
|
||||
const canvas = document.createElement("canvas")
|
||||
const gl =
|
||||
(canvas.getContext("webgl2") as WebGL2RenderingContext | null) ??
|
||||
(canvas.getContext("webgl") as WebGLRenderingContext | null)
|
||||
|
||||
// we have to return webgl so pixijs automatically falls back to canvas
|
||||
if (!gl) {
|
||||
return "webgl"
|
||||
}
|
||||
|
||||
const webglMaxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)
|
||||
const webgpuMaxTextures = device.limits.maxSampledTexturesPerShaderStage
|
||||
|
||||
return webglMaxTextures === webgpuMaxTextures ? "webgpu" : "webgl"
|
||||
}
|
||||
|
||||
async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
||||
const slug = simplifySlug(fullSlug)
|
||||
const visited = getVisited()
|
||||
|
@ -373,7 +349,6 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
|||
tweens.forEach((tween) => tween.stop())
|
||||
tweens.clear()
|
||||
|
||||
const pixiPreference = await determineGraphicsAPI()
|
||||
const app = new Application()
|
||||
await app.init({
|
||||
width,
|
||||
|
@ -382,7 +357,7 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
|
|||
autoStart: false,
|
||||
autoDensity: true,
|
||||
backgroundAlpha: 0,
|
||||
preference: pixiPreference,
|
||||
preference: "webgpu",
|
||||
resolution: window.devicePixelRatio,
|
||||
eventMode: "static",
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.content-meta {
|
||||
margin-top: 0;
|
||||
color: var(--gray);
|
||||
color: var(--darkgray);
|
||||
|
||||
&[show-comma="true"] {
|
||||
> *:not(:last-child) {
|
||||
|
|
|
@ -239,7 +239,7 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
|
|||
margin-top: 0;
|
||||
background-color: var(--light);
|
||||
max-width: 100vw;
|
||||
width: 100%;
|
||||
width: 100vw;
|
||||
transform: translateX(-100vw);
|
||||
transition:
|
||||
transform 200ms ease,
|
||||
|
@ -265,6 +265,6 @@ li:has(> .folder-outer:not(.open)) > .folder-container > svg {
|
|||
|
||||
.mobile-no-scroll {
|
||||
@media all and ($mobile) {
|
||||
overflow: hidden;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
}
|
||||
|
||||
& > .search-button {
|
||||
background-color: var(--lightgray);
|
||||
background-color: color-mix(in srgb, var(--lightgray) 60%, var(--light));
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-family: inherit;
|
||||
|
|
|
@ -26,6 +26,7 @@ import th from "./locales/th-TH"
|
|||
import lt from "./locales/lt-LT"
|
||||
import fi from "./locales/fi-FI"
|
||||
import no from "./locales/nb-NO"
|
||||
import id from "./locales/id-ID"
|
||||
|
||||
export const TRANSLATIONS = {
|
||||
"en-US": enUs,
|
||||
|
@ -76,6 +77,7 @@ export const TRANSLATIONS = {
|
|||
"lt-LT": lt,
|
||||
"fi-FI": fi,
|
||||
"nb-NO": no,
|
||||
"id-ID": id,
|
||||
} as const
|
||||
|
||||
export const defaultTranslation = "en-US"
|
||||
|
|
87
quartz/i18n/locales/id-ID.ts
Normal file
87
quartz/i18n/locales/id-ID.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
import { Translation } from "./definition"
|
||||
|
||||
export default {
|
||||
propertyDefaults: {
|
||||
title: "Tanpa Judul",
|
||||
description: "Tidak ada deskripsi",
|
||||
},
|
||||
components: {
|
||||
callout: {
|
||||
note: "Catatan",
|
||||
abstract: "Abstrak",
|
||||
info: "Info",
|
||||
todo: "Daftar Tugas",
|
||||
tip: "Tips",
|
||||
success: "Berhasil",
|
||||
question: "Pertanyaan",
|
||||
warning: "Peringatan",
|
||||
failure: "Gagal",
|
||||
danger: "Bahaya",
|
||||
bug: "Bug",
|
||||
example: "Contoh",
|
||||
quote: "Kutipan",
|
||||
},
|
||||
backlinks: {
|
||||
title: "Tautan Balik",
|
||||
noBacklinksFound: "Tidak ada tautan balik ditemukan",
|
||||
},
|
||||
themeToggle: {
|
||||
lightMode: "Mode Terang",
|
||||
darkMode: "Mode Gelap",
|
||||
},
|
||||
readerMode: {
|
||||
title: "Mode Pembaca",
|
||||
},
|
||||
explorer: {
|
||||
title: "Penjelajah",
|
||||
},
|
||||
footer: {
|
||||
createdWith: "Dibuat dengan",
|
||||
},
|
||||
graph: {
|
||||
title: "Tampilan Grafik",
|
||||
},
|
||||
recentNotes: {
|
||||
title: "Catatan Terbaru",
|
||||
seeRemainingMore: ({ remaining }) => `Lihat ${remaining} lagi →`,
|
||||
},
|
||||
transcludes: {
|
||||
transcludeOf: ({ targetSlug }) => `Transklusi dari ${targetSlug}`,
|
||||
linkToOriginal: "Tautan ke asli",
|
||||
},
|
||||
search: {
|
||||
title: "Cari",
|
||||
searchBarPlaceholder: "Cari sesuatu",
|
||||
},
|
||||
tableOfContents: {
|
||||
title: "Daftar Isi",
|
||||
},
|
||||
contentMeta: {
|
||||
readingTime: ({ minutes }) => `${minutes} menit baca`,
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
rss: {
|
||||
recentNotes: "Catatan terbaru",
|
||||
lastFewNotes: ({ count }) => `${count} catatan terakhir`,
|
||||
},
|
||||
error: {
|
||||
title: "Tidak Ditemukan",
|
||||
notFound: "Halaman ini bersifat privat atau tidak ada.",
|
||||
home: "Kembali ke Beranda",
|
||||
},
|
||||
folderContent: {
|
||||
folder: "Folder",
|
||||
itemsUnderFolder: ({ count }) =>
|
||||
count === 1 ? "1 item di bawah folder ini." : `${count} item di bawah folder ini.`,
|
||||
},
|
||||
tagContent: {
|
||||
tag: "Tag",
|
||||
tagIndex: "Indeks Tag",
|
||||
itemsUnderTag: ({ count }) =>
|
||||
count === 1 ? "1 item dengan tag ini." : `${count} item dengan tag ini.`,
|
||||
showingFirst: ({ count }) => `Menampilkan ${count} tag pertama.`,
|
||||
totalTags: ({ count }) => `Ditemukan total ${count} tag.`,
|
||||
},
|
||||
},
|
||||
} as const satisfies Translation
|
|
@ -1,7 +1,7 @@
|
|||
import { FilePath, joinSegments } from "../../util/path"
|
||||
import { QuartzEmitterPlugin } from "../types"
|
||||
import fs from "fs"
|
||||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
|
||||
export function extractDomainFromBaseUrl(baseUrl: string) {
|
||||
const url = new URL(`https://${baseUrl}`)
|
||||
|
@ -12,7 +12,9 @@ export const CNAME: QuartzEmitterPlugin = () => ({
|
|||
name: "CNAME",
|
||||
async emit({ argv, cfg }) {
|
||||
if (!cfg.configuration.baseUrl) {
|
||||
console.warn(chalk.yellow("CNAME emitter requires `baseUrl` to be set in your configuration"))
|
||||
console.warn(
|
||||
styleText("yellow", "CNAME emitter requires `baseUrl` to be set in your configuration"),
|
||||
)
|
||||
return []
|
||||
}
|
||||
const path = joinSegments(argv.output, "CNAME")
|
||||
|
|
|
@ -135,15 +135,19 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso
|
|||
`)
|
||||
} else if (cfg.analytics?.provider === "goatcounter") {
|
||||
componentResources.afterDOMLoaded.push(`
|
||||
const goatcounterScriptPre = document.createElement('script');
|
||||
goatcounterScriptPre.textContent = \`
|
||||
window.goatcounter = { no_onload: true };
|
||||
\`;
|
||||
document.head.appendChild(goatcounterScriptPre);
|
||||
|
||||
const endpoint = "https://${cfg.analytics.websiteId}.${cfg.analytics.host ?? "goatcounter.com"}/count";
|
||||
const goatcounterScript = document.createElement('script');
|
||||
goatcounterScript.src = "${cfg.analytics.scriptSrc ?? "https://gc.zgo.at/count.js"}";
|
||||
goatcounterScript.defer = true;
|
||||
goatcounterScript.setAttribute(
|
||||
'data-goatcounter',
|
||||
"https://${cfg.analytics.websiteId}.${cfg.analytics.host ?? "goatcounter.com"}/count"
|
||||
);
|
||||
goatcounterScript.setAttribute('data-goatcounter', endpoint);
|
||||
goatcounterScript.onload = () => {
|
||||
window.goatcounter = { no_onload: true };
|
||||
window.goatcounter.endpoint = endpoint;
|
||||
goatcounter.count({ path: location.pathname });
|
||||
document.addEventListener('nav', () => {
|
||||
goatcounter.count({ path: location.pathname });
|
||||
|
@ -159,14 +163,10 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso
|
|||
posthog.init('${cfg.analytics.apiKey}', {
|
||||
api_host: '${cfg.analytics.host ?? "https://app.posthog.com"}',
|
||||
capture_pageview: false,
|
||||
})\`
|
||||
posthogScript.onload = () => {
|
||||
});
|
||||
document.addEventListener('nav', () => {
|
||||
posthog.capture('$pageview', { path: location.pathname });
|
||||
|
||||
document.addEventListener('nav', () => {
|
||||
posthog.capture('$pageview', { path: location.pathname });
|
||||
});
|
||||
};
|
||||
})\`
|
||||
|
||||
document.head.appendChild(posthogScript);
|
||||
`)
|
||||
|
|
|
@ -58,7 +58,7 @@ function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndexMap, limit?:
|
|||
<title>${escapeHTML(content.title)}</title>
|
||||
<link>https://${joinSegments(base, encodeURI(slug))}</link>
|
||||
<guid>https://${joinSegments(base, encodeURI(slug))}</guid>
|
||||
<description>${content.richContent ?? content.description}</description>
|
||||
<description><![CDATA[ ${content.richContent ?? content.description} ]]></description>
|
||||
<pubDate>${content.date?.toUTCString()}</pubDate>
|
||||
</item>`
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { FullPageLayout } from "../../cfg"
|
|||
import { pathToRoot } from "../../util/path"
|
||||
import { defaultContentPageLayout, sharedPageComponents } from "../../../quartz.layout"
|
||||
import { Content } from "../../components"
|
||||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
import { write } from "./helpers"
|
||||
import { BuildCtx } from "../../util/ctx"
|
||||
import { Node } from "unist"
|
||||
|
@ -90,7 +90,8 @@ export const ContentPage: QuartzEmitterPlugin<Partial<FullPageLayout>> = (userOp
|
|||
|
||||
if (!containsIndex) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
styleText(
|
||||
"yellow",
|
||||
`\nWarning: you seem to be missing an \`index.md\` home page file at the root of your \`${ctx.argv.directory}\` folder (\`${path.join(ctx.argv.directory, "index.md")} does not exist\`). This may cause errors when deploying.`,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -11,7 +11,7 @@ import { write } from "./helpers"
|
|||
import { BuildCtx } from "../../util/ctx"
|
||||
import { QuartzPluginData } from "../vfile"
|
||||
import fs from "node:fs/promises"
|
||||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
|
||||
const defaultOptions: SocialImageOptions = {
|
||||
colorScheme: "lightMode",
|
||||
|
@ -36,7 +36,7 @@ async function generateSocialImage(
|
|||
const iconData = await fs.readFile(iconPath)
|
||||
iconBase64 = `data:image/png;base64,${iconData.toString("base64")}`
|
||||
} catch (err) {
|
||||
console.warn(chalk.yellow(`Warning: Could not find icon at ${iconPath}`))
|
||||
console.warn(styleText("yellow", `Warning: Could not find icon at ${iconPath}`))
|
||||
}
|
||||
|
||||
const imageComponent = userOpts.imageStructure({
|
||||
|
|
|
@ -101,7 +101,11 @@ export const FrontMatter: QuartzTransformerPlugin<Partial<Options>> = (userOpts)
|
|||
const socialImage = coalesceAliases(data, ["socialImage", "image", "cover"])
|
||||
|
||||
const created = coalesceAliases(data, ["created", "date"])
|
||||
if (created) data.created = created
|
||||
if (created) {
|
||||
data.created = created
|
||||
data.modified ||= created // if modified is not set, use created
|
||||
}
|
||||
|
||||
const modified = coalesceAliases(data, [
|
||||
"modified",
|
||||
"lastmod",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import fs from "fs"
|
||||
import { Repository } from "@napi-rs/simple-git"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
import chalk from "chalk"
|
||||
import path from "path"
|
||||
import { styleText } from "util"
|
||||
|
||||
export interface Options {
|
||||
priority: ("frontmatter" | "git" | "filesystem")[]
|
||||
|
@ -12,12 +12,23 @@ const defaultOptions: Options = {
|
|||
priority: ["frontmatter", "git", "filesystem"],
|
||||
}
|
||||
|
||||
// YYYY-MM-DD
|
||||
const iso8601DateOnlyRegex = /^\d{4}-\d{2}-\d{2}$/
|
||||
|
||||
function coerceDate(fp: string, d: any): Date {
|
||||
// check ISO8601 date-only format
|
||||
// we treat this one as local midnight as the normal
|
||||
// js date ctor treats YYYY-MM-DD as UTC midnight
|
||||
if (typeof d === "string" && iso8601DateOnlyRegex.test(d)) {
|
||||
d = `${d}T00:00:00`
|
||||
}
|
||||
|
||||
const dt = new Date(d)
|
||||
const invalidDate = isNaN(dt.getTime()) || dt.getTime() === 0
|
||||
if (invalidDate && d !== undefined) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
styleText(
|
||||
"yellow",
|
||||
`\nWarning: found invalid date "${d}" in \`${fp}\`. Supported formats: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format`,
|
||||
),
|
||||
)
|
||||
|
@ -42,7 +53,10 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options>> = (u
|
|||
repositoryWorkdir = repo.workdir() ?? ctx.argv.directory
|
||||
} catch (e) {
|
||||
console.log(
|
||||
chalk.yellow(`\nWarning: couldn't find git repository for ${ctx.argv.directory}`),
|
||||
styleText(
|
||||
"yellow",
|
||||
`\nWarning: couldn't find git repository for ${ctx.argv.directory}`,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +83,8 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options>> = (u
|
|||
modified ||= await repo.getFileLatestModifiedDateAsync(relativePath)
|
||||
} catch {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
styleText(
|
||||
"yellow",
|
||||
`\nWarning: ${file.data.filePath!} isn't yet tracked by git, dates will be inaccurate`,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -41,6 +41,7 @@ export interface Options {
|
|||
enableYouTubeEmbed: boolean
|
||||
enableVideoEmbed: boolean
|
||||
enableCheckbox: boolean
|
||||
disableBrokenWikilinks: boolean
|
||||
}
|
||||
|
||||
const defaultOptions: Options = {
|
||||
|
@ -56,6 +57,7 @@ const defaultOptions: Options = {
|
|||
enableYouTubeEmbed: true,
|
||||
enableVideoEmbed: true,
|
||||
enableCheckbox: false,
|
||||
disableBrokenWikilinks: false,
|
||||
}
|
||||
|
||||
const calloutMapping = {
|
||||
|
@ -113,9 +115,9 @@ export const arrowRegex = new RegExp(/(-{1,2}>|={1,2}>|<-{1,2}|<={1,2})/g)
|
|||
// \[\[ -> open brace
|
||||
// ([^\[\]\|\#]+) -> one or more non-special characters ([,],|, or #) (name)
|
||||
// (#[^\[\]\|\#]+)? -> # then one or more non-special characters (heading link)
|
||||
// (\\?\|[^\[\]\#]+)? -> optional escape \ then | then one or more non-special characters (alias)
|
||||
// (\\?\|[^\[\]\#]+)? -> optional escape \ then | then zero or more non-special characters (alias)
|
||||
export const wikilinkRegex = new RegExp(
|
||||
/!?\[\[([^\[\]\|\#\\]+)?(#+[^\[\]\|\#\\]+)?(\\?\|[^\[\]\#]+)?\]\]/g,
|
||||
/!?\[\[([^\[\]\|\#\\]+)?(#+[^\[\]\|\#\\]+)?(\\?\|[^\[\]\#]*)?\]\]/g,
|
||||
)
|
||||
|
||||
// ^\|([^\n])+\|\n(\|) -> matches the header row
|
||||
|
@ -206,7 +208,7 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
|
|||
|
||||
return src
|
||||
},
|
||||
markdownPlugins(_ctx) {
|
||||
markdownPlugins(ctx) {
|
||||
const plugins: PluggableList = []
|
||||
|
||||
// regex replacements
|
||||
|
@ -275,6 +277,18 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
|
|||
// otherwise, fall through to regular link
|
||||
}
|
||||
|
||||
// treat as broken link if slug not in ctx.allSlugs
|
||||
if (opts.disableBrokenWikilinks) {
|
||||
const slug = slugifyFilePath(fp as FilePath)
|
||||
const exists = ctx.allSlugs && ctx.allSlugs.includes(slug)
|
||||
if (!exists) {
|
||||
return {
|
||||
type: "html",
|
||||
value: `<a class=\"internal broken\">${alias ?? fp}</a>`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// internal link
|
||||
const url = fp + anchor
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ProcessedContent } from "../plugins/vfile"
|
|||
import { QuartzLogger } from "../util/log"
|
||||
import { trace } from "../util/trace"
|
||||
import { BuildCtx } from "../util/ctx"
|
||||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
|
||||
export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
|
||||
const { argv, cfg } = ctx
|
||||
|
@ -26,7 +26,7 @@ export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
|
|||
if (ctx.argv.verbose) {
|
||||
console.log(`[emit:${emitter.name}] ${file}`)
|
||||
} else {
|
||||
log.updateText(`${emitter.name} -> ${chalk.gray(file)}`)
|
||||
log.updateText(`${emitter.name} -> ${styleText("gray", file)}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -36,7 +36,7 @@ export async function emitContent(ctx: BuildCtx, content: ProcessedContent[]) {
|
|||
if (ctx.argv.verbose) {
|
||||
console.log(`[emit:${emitter.name}] ${file}`)
|
||||
} else {
|
||||
log.updateText(`${emitter.name} -> ${chalk.gray(file)}`)
|
||||
log.updateText(`${emitter.name} -> ${styleText("gray", file)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import workerpool, { Promise as WorkerPromise } from "workerpool"
|
|||
import { QuartzLogger } from "../util/log"
|
||||
import { trace } from "../util/trace"
|
||||
import { BuildCtx, WorkerSerializableBuildCtx } from "../util/ctx"
|
||||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
|
||||
export type QuartzMdProcessor = Processor<MDRoot, MDRoot, MDRoot>
|
||||
export type QuartzHtmlProcessor = Processor<undefined, MDRoot, HTMLRoot>
|
||||
|
@ -194,7 +194,7 @@ export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise<Pro
|
|||
textToMarkdownPromises.map(async (promise) => {
|
||||
const result = await promise
|
||||
processedFiles += result.length
|
||||
log.updateText(`text->markdown ${chalk.gray(`${processedFiles}/${fps.length}`)}`)
|
||||
log.updateText(`text->markdown ${styleText("gray", `${processedFiles}/${fps.length}`)}`)
|
||||
return result
|
||||
}),
|
||||
).catch(errorHandler)
|
||||
|
@ -208,7 +208,7 @@ export async function parseMarkdown(ctx: BuildCtx, fps: FilePath[]): Promise<Pro
|
|||
markdownToHtmlPromises.map(async (promise) => {
|
||||
const result = await promise
|
||||
processedFiles += result.length
|
||||
log.updateText(`markdown->html ${chalk.gray(`${processedFiles}/${fps.length}`)}`)
|
||||
log.updateText(`markdown->html ${styleText("gray", `${processedFiles}/${fps.length}`)}`)
|
||||
return result
|
||||
}),
|
||||
).catch(errorHandler)
|
||||
|
|
|
@ -11,8 +11,7 @@ html {
|
|||
width: 100vw;
|
||||
}
|
||||
|
||||
body,
|
||||
section {
|
||||
body {
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--light);
|
||||
|
@ -91,7 +90,7 @@ a {
|
|||
color: var(--secondary);
|
||||
|
||||
&:hover {
|
||||
color: var(--tertiary) !important;
|
||||
color: var(--tertiary);
|
||||
}
|
||||
|
||||
&.internal {
|
||||
|
@ -101,6 +100,15 @@ a {
|
|||
border-radius: 5px;
|
||||
line-height: 1.4rem;
|
||||
|
||||
&.broken {
|
||||
color: var(--secondary);
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.2s ease;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(> img) {
|
||||
background-color: transparent;
|
||||
border-radius: 0;
|
||||
|
@ -123,16 +131,32 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
.flex-component {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.desktop-only {
|
||||
display: initial;
|
||||
&.flex-component {
|
||||
display: flex;
|
||||
}
|
||||
@media all and ($mobile) {
|
||||
&.flex-component {
|
||||
display: none;
|
||||
}
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-only {
|
||||
display: none;
|
||||
&.flex-component {
|
||||
display: none;
|
||||
}
|
||||
@media all and ($mobile) {
|
||||
&.flex-component {
|
||||
display: flex;
|
||||
}
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
@ -594,6 +618,7 @@ ol.overflow {
|
|||
}
|
||||
|
||||
.katex-display {
|
||||
display: initial;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ export async function loadEmoji(code: string) {
|
|||
emojimap = data
|
||||
}
|
||||
|
||||
const name = emojimap.codePointToName[`U+${code.toUpperCase()}`]
|
||||
const name = emojimap.codePointToName[`${code.toUpperCase()}`]
|
||||
if (!name) throw new Error(`codepoint ${code} not found in map`)
|
||||
|
||||
const b64 = emojimap.nameToBase64[name]
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -9,7 +9,7 @@ import { QUARTZ } from "./path"
|
|||
import { formatDate, getDate } from "../components/Date"
|
||||
import readingTime from "reading-time"
|
||||
import { i18n } from "../i18n"
|
||||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
|
||||
const defaultHeaderWeight = [700]
|
||||
const defaultBodyWeight = [400]
|
||||
|
@ -100,7 +100,8 @@ export async function fetchTtf(
|
|||
|
||||
if (!match) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
styleText(
|
||||
"yellow",
|
||||
`\nWarning: Failed to fetch font ${rawFontName} with weight ${weight}, got ${cssResponse.statusText}`,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import chalk from "chalk"
|
||||
import pretty from "pretty-time"
|
||||
import { styleText } from "util"
|
||||
|
||||
export class PerfTimer {
|
||||
evts: { [key: string]: [number, number] }
|
||||
|
@ -14,6 +14,6 @@ export class PerfTimer {
|
|||
}
|
||||
|
||||
timeSince(evtName?: string): string {
|
||||
return chalk.yellow(pretty(process.hrtime(this.evts[evtName ?? "start"])))
|
||||
return styleText("yellow", pretty(process.hrtime(this.evts[evtName ?? "start"])))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import chalk from "chalk"
|
||||
import { styleText } from "util"
|
||||
import process from "process"
|
||||
import { isMainThread } from "workerpool"
|
||||
|
||||
|
@ -11,9 +11,9 @@ export function trace(msg: string, err: Error) {
|
|||
lines.push("")
|
||||
lines.push(
|
||||
"\n" +
|
||||
chalk.bgRed.black.bold(" ERROR ") +
|
||||
styleText(["bgRed", "black", "bold"], " ERROR ") +
|
||||
"\n\n" +
|
||||
chalk.red(` ${msg}`) +
|
||||
styleText("red", ` ${msg}`) +
|
||||
(err.message.length > 0 ? `: ${err.message}` : ""),
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue