From fcd81353f88b613e5e93c089e10e530d08695b3f Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Thu, 1 Jun 2023 19:48:38 -0400 Subject: [PATCH] heading linking --- package-lock.json | 65 ++++++++++++++++++++++++++++++ package.json | 3 ++ quartz/components/Head.tsx | 1 - quartz/path.ts | 5 ++- quartz/plugins/transformers/gfm.ts | 15 ++++++- quartz/plugins/transformers/ofm.ts | 4 +- quartz/styles/base.scss | 22 +++++++--- 7 files changed, 104 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index a9e186edf..0b292c076 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "chalk": "^4.1.2", "cli-spinner": "^0.2.10", "esbuild-sass-plugin": "^2.9.0", + "github-slugger": "^2.0.0", "globby": "^13.1.4", "gray-matter": "^4.0.3", "hast-util-to-jsx-runtime": "^1.2.0", @@ -23,9 +24,11 @@ "preact": "^10.14.1", "preact-render-to-string": "^6.0.3", "pretty-time": "^1.1.0", + "rehype-autolink-headings": "^6.1.1", "rehype-katex": "^6.0.3", "rehype-pretty-code": "^0.9.6", "rehype-raw": "^6.1.1", + "rehype-slug": "^5.1.0", "remark": "^14.0.2", "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1", @@ -1565,6 +1568,11 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, "node_modules/glob": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.6.tgz", @@ -1737,6 +1745,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-has-property": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz", + "integrity": "sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-heading-rank": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.1.tgz", + "integrity": "sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-is-element": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.3.tgz", @@ -3229,6 +3258,24 @@ "node": ">=8.10.0" } }, + "node_modules/rehype-autolink-headings": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-6.1.1.tgz", + "integrity": "sha512-NMYzZIsHM3sA14nC5rAFuUPIOfg+DFmf9EY1YMhaNlB7+3kK/ZlE6kqPfuxr1tsJ1XWkTrMtMoyHosU70d35mA==", + "dependencies": { + "@types/hast": "^2.0.0", + "extend": "^3.0.0", + "hast-util-has-property": "^2.0.0", + "hast-util-heading-rank": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-katex": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-6.0.3.tgz", @@ -3275,6 +3322,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-slug": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz", + "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==", + "dependencies": { + "@types/hast": "^2.0.0", + "github-slugger": "^2.0.0", + "hast-util-has-property": "^2.0.0", + "hast-util-heading-rank": "^2.0.0", + "hast-util-to-string": "^2.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.3.tgz", diff --git a/package.json b/package.json index 46c0c2a05..32175204c 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "chalk": "^4.1.2", "cli-spinner": "^0.2.10", "esbuild-sass-plugin": "^2.9.0", + "github-slugger": "^2.0.0", "globby": "^13.1.4", "gray-matter": "^4.0.3", "hast-util-to-jsx-runtime": "^1.2.0", @@ -38,9 +39,11 @@ "preact": "^10.14.1", "preact-render-to-string": "^6.0.3", "pretty-time": "^1.1.0", + "rehype-autolink-headings": "^6.1.1", "rehype-katex": "^6.0.3", "rehype-pretty-code": "^0.9.6", "rehype-raw": "^6.1.1", + "rehype-slug": "^5.1.0", "remark": "^14.0.2", "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1", diff --git a/quartz/components/Head.tsx b/quartz/components/Head.tsx index c4deb8b6b..868294dde 100644 --- a/quartz/components/Head.tsx +++ b/quartz/components/Head.tsx @@ -26,7 +26,6 @@ export default function({ title, description, slug, externalResources }: HeadPro - diff --git a/quartz/path.ts b/quartz/path.ts index 4127fc008..4f3bfa7f7 100644 --- a/quartz/path.ts +++ b/quartz/path.ts @@ -1,4 +1,7 @@ import path from 'path' +import SlugAnchor from 'github-slugger' + +const slugAnchor = new SlugAnchor() function slugSegment(s: string): string { return s.replace(/\s/g, '-') @@ -6,7 +9,7 @@ function slugSegment(s: string): string { export function slugify(s: string): string { const [fp, anchor] = s.split("#", 2) - const sluggedAnchor = anchor === undefined ? "" : "#" + slugSegment(anchor) + const sluggedAnchor = anchor === undefined ? "" : "#" + slugAnchor.slug(anchor) const withoutFileExt = fp.replace(new RegExp(path.extname(fp) + '$'), '') const rawSlugSegments = withoutFileExt.split(path.sep) const slugParts: string = rawSlugSegments diff --git a/quartz/plugins/transformers/gfm.ts b/quartz/plugins/transformers/gfm.ts index ef3a94432..55dbda2e1 100644 --- a/quartz/plugins/transformers/gfm.ts +++ b/quartz/plugins/transformers/gfm.ts @@ -2,13 +2,17 @@ import { PluggableList } from "unified" import remarkGfm from "remark-gfm" import smartypants from 'remark-smartypants' import { QuartzTransformerPlugin } from "../types" +import rehypeSlug from "rehype-slug" +import rehypeAutolinkHeadings from "rehype-autolink-headings/lib" export interface Options { enableSmartyPants: boolean + linkHeadings: boolean } const defaultOptions: Options = { - enableSmartyPants: true + enableSmartyPants: true, + linkHeadings: true } export class GitHubFlavoredMarkdown extends QuartzTransformerPlugin { @@ -25,6 +29,13 @@ export class GitHubFlavoredMarkdown extends QuartzTransformerPlugin { } htmlPlugins(): PluggableList { - return [] + return this.opts.linkHeadings + ? [rehypeSlug, [rehypeAutolinkHeadings, { + behavior: 'append', content: { + type: 'text', + value: 'ยง' + } + }]] + : [] } } diff --git a/quartz/plugins/transformers/ofm.ts b/quartz/plugins/transformers/ofm.ts index 9215e2c50..7569797be 100644 --- a/quartz/plugins/transformers/ofm.ts +++ b/quartz/plugins/transformers/ofm.ts @@ -42,9 +42,9 @@ export class ObsidianFlavoredMarkdown extends QuartzTransformerPlugin { } else { const [path, rawHeader, rawAlias] = capture - const header = rawHeader?.slice(1).trim() ?? "" + const anchor = rawHeader?.slice(1).trim() ?? "" const alias = rawAlias?.slice(1).trim() ?? path - const url = slugify(path.trim() + header) + const url = slugify(path.trim() + anchor) return { type: 'link', url, diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index 99613f94c..5f56b0f4a 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -85,12 +85,24 @@ thead { font-weight: revert; margin: 2rem 0 0; - &:hover > .hanchor { - color: var(--secondary); - } - article > & > a { - color: var(--dark) + color: var(--dark); + &.internal { + background-color: transparent; + } + } +} + +h1, h2, h3, h4, h5, h6 { + &[id] > a { + margin: 0 0.5rem; + opacity: 0; + transition: opacity 0.2s ease; + font-family: var(--codeFont); + user-select: none; + } + &[id]:hover > a { + opacity: 1; } }