From 04eeb2d10c2bb8cac595a879446c1dcbfac4d6a6 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 1 Jun 2023 19:05:14 -0400 Subject: [PATCH] syntax higlighting --- content/index.md | 2 +- package-lock.json | 105 ++++++++++++++++++++++++ package.json | 2 + quartz.config.ts | 7 +- quartz/bootstrap.mjs | 2 +- quartz/components/Header.tsx | 14 ++++ quartz/plugins/emitters/contentPage.tsx | 17 ++-- quartz/plugins/transformers/index.ts | 1 + quartz/plugins/transformers/syntax.ts | 28 +++++++ quartz/styles/base.scss | 60 +++++++------- quartz/styles/syntax.scss | 4 +- 11 files changed, 198 insertions(+), 44 deletions(-) create mode 100644 quartz/components/Header.tsx create mode 100644 quartz/plugins/transformers/syntax.ts diff --git a/content/index.md b/content/index.md index 21b65304..8faee66c 100644 --- a/content/index.md +++ b/content/index.md @@ -1,5 +1,5 @@ --- -title: 🪴 Quartz 3.3 +title: Welcome to Quartz enableToc: false --- diff --git a/package-lock.json b/package-lock.json index cf60c68b..a9e186ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "preact-render-to-string": "^6.0.3", "pretty-time": "^1.1.0", "rehype-katex": "^6.0.3", + "rehype-pretty-code": "^0.9.6", "rehype-raw": "^6.1.1", "remark": "^14.0.2", "remark-frontmatter": "^4.0.1", @@ -35,6 +36,7 @@ "require-from-string": "^2.0.2", "rimraf": "^5.0.1", "serve-handler": "^6.1.5", + "shiki": "^0.14.2", "to-vfile": "^7.2.4", "unified": "^10.1.2", "unist-util-visit": "^4.1.2", @@ -989,6 +991,11 @@ "node": ">=8" } }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz", + "integrity": "sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==" + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1641,6 +1648,33 @@ "node": ">=8" } }, + "node_modules/hash-obj": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hash-obj/-/hash-obj-4.0.0.tgz", + "integrity": "sha512-FwO1BUVWkyHasWDW4S8o0ssQXjvyghLV2rfVhnN36b2bbcj45eGiuzdn9XOvOpjV3TKQD7Gm2BWNXdE9V4KKYg==", + "dependencies": { + "is-obj": "^3.0.0", + "sort-keys": "^5.0.0", + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hash-obj/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/hast-util-from-dom": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-4.2.0.tgz", @@ -1980,6 +2014,17 @@ "node": ">=0.12.0" } }, + "node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -2025,6 +2070,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + }, "node_modules/katex": { "version": "0.16.7", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.7.tgz", @@ -3021,6 +3071,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -3191,6 +3246,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-pretty-code": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/rehype-pretty-code/-/rehype-pretty-code-0.9.6.tgz", + "integrity": "sha512-l94QKT6w00AIJp1FsbVnbKmcEckKdYkJQfstgiSI4GTt/hSdDrnQRz6rP8r01x1rXNFC2exMG4WY0X7fOpVQGw==", + "dependencies": { + "hash-obj": "^4.0.0", + "parse-numeric-range": "^1.3.0" + }, + "engines": { + "node": "^12.16.0 || >=13.2.0" + }, + "peerDependencies": { + "shiki": "*" + } + }, "node_modules/rehype-raw": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz", @@ -3566,6 +3636,17 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.2.tgz", + "integrity": "sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A==", + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, "node_modules/signal-exit": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", @@ -3588,6 +3669,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sort-keys": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.0.0.tgz", + "integrity": "sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==", + "dependencies": { + "is-plain-obj": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -3983,6 +4078,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==" + }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", diff --git a/package.json b/package.json index 8212ce42..46c0c2a0 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "preact-render-to-string": "^6.0.3", "pretty-time": "^1.1.0", "rehype-katex": "^6.0.3", + "rehype-pretty-code": "^0.9.6", "rehype-raw": "^6.1.1", "remark": "^14.0.2", "remark-frontmatter": "^4.0.1", @@ -50,6 +51,7 @@ "require-from-string": "^2.0.2", "rimraf": "^5.0.1", "serve-handler": "^6.1.5", + "shiki": "^0.14.2", "to-vfile": "^7.2.4", "unified": "^10.1.2", "unist-util-visit": "^4.1.2", diff --git a/quartz.config.ts b/quartz.config.ts index 1208e4d1..14ef1c15 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -1,6 +1,7 @@ import { buildQuartz } from "./quartz" import Head from "./quartz/components/Head" -import { ContentPage, CreatedModifiedDate, Description, FrontMatter, GitHubFlavoredMarkdown, Katex, ObsidianFlavoredMarkdown, RemoveDrafts, ResolveLinks } from "./quartz/plugins" +import Header from "./quartz/components/Header" +import { ContentPage, CreatedModifiedDate, Description, FrontMatter, GitHubFlavoredMarkdown, Katex, ObsidianFlavoredMarkdown, RemoveDrafts, ResolveLinks, SyntaxHighlighting } from "./quartz/plugins" export default buildQuartz({ configuration: { @@ -45,6 +46,7 @@ export default buildQuartz({ new CreatedModifiedDate({ priority: ['frontmatter', 'filesystem'] // you can add 'git' here for last modified from Git but this makes the build slower }), + new SyntaxHighlighting(), new GitHubFlavoredMarkdown(), new ObsidianFlavoredMarkdown(), new ResolveLinks(), @@ -54,7 +56,8 @@ export default buildQuartz({ ], emitters: [ new ContentPage({ - Head: Head + Head: Head, + Header: Header }) ] }, diff --git a/quartz/bootstrap.mjs b/quartz/bootstrap.mjs index 8b429ca1..4886e3da 100755 --- a/quartz/bootstrap.mjs +++ b/quartz/bootstrap.mjs @@ -60,7 +60,7 @@ yargs(hideBin(process.argv)) format: "cjs", jsx: "automatic", jsxImportSource: "preact", - external: ["@napi-rs/simple-git"], + external: ["@napi-rs/simple-git", "shiki"], plugins: [sassPlugin({ type: 'css-text' })] diff --git a/quartz/components/Header.tsx b/quartz/components/Header.tsx new file mode 100644 index 00000000..7da4a8e7 --- /dev/null +++ b/quartz/components/Header.tsx @@ -0,0 +1,14 @@ +import { resolveToRoot } from "../path" + +export interface HeaderProps { + title: string + slug: string +} + +export default function({ title, slug }: HeaderProps) { + const baseDir = resolveToRoot(slug) + return
+

{title}

+
+ +} diff --git a/quartz/plugins/emitters/contentPage.tsx b/quartz/plugins/emitters/contentPage.tsx index 08d989b7..fa17a7e5 100644 --- a/quartz/plugins/emitters/contentPage.tsx +++ b/quartz/plugins/emitters/contentPage.tsx @@ -6,13 +6,15 @@ import { Fragment, jsx, jsxs } from 'preact/jsx-runtime' import { render } from "preact-render-to-string" import { ComponentType } from "preact" import { HeadProps } from "../../components/Head" - -import styles from '../../styles/base.scss' import { googleFontHref, templateThemeStyles } from "../../theme" import { GlobalConfiguration } from "../../cfg" +import { HeaderProps } from "../../components/Header" + +import styles from '../../styles/base.scss' interface Options { Head: ComponentType + Header: ComponentType } export class ContentPage extends QuartzEmitterPlugin { @@ -37,23 +39,22 @@ export class ContentPage extends QuartzEmitterPlugin { resources.css.push(googleFontHref(cfg.theme)) for (const [tree, file] of content) { - // @ts-ignore (preact makes it angry) const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: 'html' }) - const { Head } = this.opts + const title = file.data.frontmatter?.title + const { Head, Header } = this.opts const doc =
-
-

{file.data.frontmatter?.title}

-
+
+ {file.data.slug !== "index" &&

{title}

} {content}
diff --git a/quartz/plugins/transformers/index.ts b/quartz/plugins/transformers/index.ts index fb3fcf40..492a9882 100644 --- a/quartz/plugins/transformers/index.ts +++ b/quartz/plugins/transformers/index.ts @@ -5,3 +5,4 @@ export { Katex } from './latex' export { Description } from './description' export { ResolveLinks } from './links' export { ObsidianFlavoredMarkdown } from './ofm' +export { SyntaxHighlighting } from './syntax' diff --git a/quartz/plugins/transformers/syntax.ts b/quartz/plugins/transformers/syntax.ts new file mode 100644 index 00000000..f09daaa5 --- /dev/null +++ b/quartz/plugins/transformers/syntax.ts @@ -0,0 +1,28 @@ +import { PluggableList } from "unified" +import { QuartzTransformerPlugin } from "../types" +import rehypePrettyCode, { Options as CodeOptions } from "rehype-pretty-code" + +export class SyntaxHighlighting extends QuartzTransformerPlugin { + name = "SyntaxHighlighting" + + markdownPlugins(): PluggableList { + return [] + } + + htmlPlugins(): PluggableList { + return [[rehypePrettyCode, { + theme: 'css-variables', + onVisitLine(node) { + if (node.children.length === 0) { + node.children = [{ type: 'text', value: ' ' }] + } + }, + onVisitHighlightedLine(node) { + node.properties.className.push('highlighted') + }, + onVisitHighlightedWord(node) { + node.properties.className = ['word'] + }, + } satisfies Partial]] + } +} diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index bf89e5be..99613f94 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -108,41 +108,41 @@ div[data-rehype-pretty-code-fragment] { margin-bottom: -0.8rem; color: var(--darkgray); } +} - & pre { - font-family: var(--codeFont); - padding: 0.5rem 0; - border-radius: 5px; - overflow-x: scroll; - border: 1px solid var(--lightgray); +pre { + font-family: var(--codeFont); + padding: 0.5rem; + border-radius: 5px; + overflow-x: scroll; + border: 1px solid var(--lightgray); - & > code { - background: none; - padding: 0; - font-size: 0.9rem; - counter-reset: line; - counter-increment: line 0; - display: grid; + & > code { + background: none; + padding: 0; + font-size: 0.9rem; + counter-reset: line; + counter-increment: line 0; + display: grid; - & .line { - padding: 0 0.75rem; - box-sizing: border-box; - border-left: 3px solid transparent; + & .line { + padding: 0 0.25rem; + box-sizing: border-box; + border-left: 3px solid transparent; - &.highlighted { - background-color: var(--highlight); - border-left: 3px solid var(--secondary); - } + &.highlighted { + background-color: var(--highlight); + border-left: 3px solid var(--secondary); + } - &::before { - content: counter(line); - counter-increment: line; - width: 1rem; - margin-right: 1rem; - display: inline-block; - text-align: right; - color: rgba(115, 138, 148, 0.4); - } + &::before { + content: counter(line); + counter-increment: line; + width: 1rem; + margin-right: 1rem; + display: inline-block; + text-align: right; + color: rgba(115, 138, 148, 0.4); } } } diff --git a/quartz/styles/syntax.scss b/quartz/styles/syntax.scss index ca5aee5f..c53904aa 100644 --- a/quartz/styles/syntax.scss +++ b/quartz/styles/syntax.scss @@ -1,5 +1,5 @@ // npx convert-sh-theme https://raw.githubusercontent.com/shikijs/shiki/main/packages/shiki/themes/github-light.json -body { +:root { --shiki-color-text: #24292e; --shiki-color-background: #f8f8f8; --shiki-token-constant: #005cc5; @@ -14,7 +14,7 @@ body { } // npx convert-sh-theme https://raw.githubusercontent.com/shikijs/shiki/main/packages/shiki/themes/github-dark.json -:root[saved-theme="dark"] body { +:root[saved-theme="dark"] { --shiki-color-text: #e1e4e8 !important; --shiki-color-background: #24292e !important; --shiki-token-constant: #79b8ff !important;