feat: add heading anchor links

This commit is contained in:
radishzzz 2025-05-13 03:09:23 +01:00
parent 328104ed1a
commit e5ecbe1341
8 changed files with 117 additions and 29 deletions

View file

@ -3,8 +3,10 @@ import partytown from '@astrojs/partytown'
import sitemap from '@astrojs/sitemap'
import robotsTxt from 'astro-robots-txt'
import { defineConfig } from 'astro/config'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypeExternalLinks from 'rehype-external-links'
import rehypeKatex from 'rehype-katex'
import rehypeSlug from 'rehype-slug'
import remarkDirective from 'remark-directive'
import remarkMath from 'remark-math'
import UnoCSS from 'unocss/astro'
@ -19,15 +21,10 @@ const url = themeConfig.site.url
const locale = themeConfig.global.locale
const linkPrefetch = themeConfig.preload.linkPrefetch
const imageHostURL = themeConfig.preload.imageHostURL
// Configure domains and remotePatterns to optimize remote images in Markdown files using ![alt](src) syntax
// Docs: https://docs.astro.build/en/guides/images/#authorizing-remote-images
const imageConfig = imageHostURL
? {
// Configure domains and remotePatterns to optimize remote images in Markdown files using ![alt](src) syntax
// Docs: https://docs.astro.build/en/guides/images/#authorizing-remote-images
image: {
domains: [imageHostURL],
remotePatterns: [{ protocol: 'https' }],
},
}
? { image: { domains: [imageHostURL], remotePatterns: [{ protocol: 'https' }] } }
: {}
export default defineConfig({
@ -69,7 +66,44 @@ export default defineConfig({
],
rehypePlugins: [
rehypeKatex,
rehypeSlug,
rehypeImgToFigure,
[
rehypeAutolinkHeadings,
{
behavior: 'append',
test: ['h1', 'h2', 'h3', 'h4'],
content: {
type: 'element',
tagName: 'svg',
properties: {
'viewBox': '0 0 24 24',
'aria-hidden': 'true',
'fill': 'currentColor',
},
children: [
{
type: 'element',
tagName: 'path',
properties: {
d: 'm7.374 15.182 7.85-7.849 1.413 1.414-7.848 7.85z',
},
},
{
type: 'element',
tagName: 'path',
properties: {
d: 'M10.6 20c-1.8 1.8-4.7 1.8-6.5 0-.9-.9-1.4-2-1.4-3.3s.5-2.4 1.4-3.3l3.8-3.8-1.4-1.4-4.2 4.2c-2.5 2.5-2.5 6.7 0 9.3 1.3 1.3 3 1.9 4.6 1.9s3.4-.6 4.6-1.9l4.2-4.2-1.4-1.4-3.8 3.8ZM21.7 2.3C20.5 1.1 18.8.4 17.1.4s-3.4.7-4.6 1.9L8.3 6.5l1.4 1.4 3.8-3.8c1.8-1.8 4.7-1.8 6.5 0 .9.9 1.4 2 1.4 3.3s-.5 2.4-1.4 3.3l-3.8 3.8 1.4 1.4 4.2-4.2c1.2-1.2 1.9-2.9 1.9-4.6s-.7-3.4-1.9-4.6Z',
},
},
],
},
properties: {
className: ['heading-anchor-link'],
ariaLabel: 'Link to this section',
},
},
],
[
rehypeExternalLinks,
{

View file

@ -29,8 +29,10 @@
"overlayscrollbars": "^2.11.2",
"photoswipe": "^5.4.4",
"reading-time": "^1.5.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-external-links": "^3.0.0",
"rehype-katex": "^7.0.1",
"rehype-slug": "^6.0.0",
"remark-directive": "^4.0.0",
"remark-math": "^6.0.0",
"sanitize-html": "^2.16.0",

43
pnpm-lock.yaml generated
View file

@ -56,12 +56,18 @@ importers:
reading-time:
specifier: ^1.5.0
version: 1.5.0
rehype-autolink-headings:
specifier: ^7.1.0
version: 7.1.0
rehype-external-links:
specifier: ^3.0.0
version: 3.0.0
rehype-katex:
specifier: ^7.0.1
version: 7.0.1
rehype-slug:
specifier: ^6.0.0
version: 6.0.0
remark-directive:
specifier: ^4.0.0
version: 4.0.0
@ -2366,6 +2372,9 @@ packages:
hast-util-from-parse5@8.0.3:
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
hast-util-heading-rank@3.0.0:
resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==}
hast-util-is-element@3.0.0:
resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
@ -2387,6 +2396,9 @@ packages:
hast-util-to-parse5@8.0.0:
resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
hast-util-to-string@3.0.1:
resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==}
hast-util-to-text@4.0.2:
resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
@ -3317,6 +3329,9 @@ packages:
resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==}
hasBin: true
rehype-autolink-headings@7.1.0:
resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==}
rehype-external-links@3.0.0:
resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==}
@ -3332,6 +3347,9 @@ packages:
rehype-recma@1.0.0:
resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==}
rehype-slug@6.0.0:
resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==}
rehype-stringify@10.0.1:
resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==}
@ -6833,6 +6851,10 @@ snapshots:
vfile-location: 5.0.3
web-namespaces: 2.0.1
hast-util-heading-rank@3.0.0:
dependencies:
'@types/hast': 3.0.4
hast-util-is-element@3.0.0:
dependencies:
'@types/hast': 3.0.4
@ -6922,6 +6944,10 @@ snapshots:
web-namespaces: 2.0.1
zwitch: 2.0.4
hast-util-to-string@3.0.1:
dependencies:
'@types/hast': 3.0.4
hast-util-to-text@4.0.2:
dependencies:
'@types/hast': 3.0.4
@ -8101,6 +8127,15 @@ snapshots:
dependencies:
jsesc: 3.0.2
rehype-autolink-headings@7.1.0:
dependencies:
'@types/hast': 3.0.4
'@ungap/structured-clone': 1.3.0
hast-util-heading-rank: 3.0.0
hast-util-is-element: 3.0.0
unified: 11.0.5
unist-util-visit: 5.0.0
rehype-external-links@3.0.0:
dependencies:
'@types/hast': 3.0.4
@ -8140,6 +8175,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
rehype-slug@6.0.0:
dependencies:
'@types/hast': 3.0.4
github-slugger: 2.0.0
hast-util-heading-rank: 3.0.0
hast-util-to-string: 3.0.1
unist-util-visit: 5.0.0
rehype-stringify@10.0.1:
dependencies:
'@types/hast': 3.0.4

View file

@ -17,7 +17,7 @@ const SubtitleTag = isPost ? 'div' : 'h2'
<header
class:list={[
isPost ? 'mb-10.8' : 'mb-10.5',
'cjk:tracking-0.02em lg:(uno-desktop-column top-20)',
'lg:(uno-desktop-column top-20) cjk:tracking-0.02em',
]}
>
<TitleTag

View file

@ -37,7 +37,7 @@ const navItems = [
class:list={[
isPost ? 'hidden lg:block' : '',
'mb-10.5 text-3.6 font-semibold leading-8.75 font-navbar',
'cjk:tracking-0.02em lg:(uno-desktop-column text-4 leading-9.72 bottom-50)',
'lg:(uno-desktop-column text-4 leading-9.72 bottom-50) cjk:tracking-0.02em',
]}
>
<ul>

View file

@ -40,7 +40,7 @@ function getPostPath(post: Post) {
{/* post title */}
<h3 class="inline">
<a
class="cjk:tracking-0.02em hover:c-primary"
class="hover:c-primary cjk:tracking-0.02em"
lg={isHome ? 'font-medium text-4.5' : ''}
href={getPostPath(post)}
transition:name={`post-${post.data.abbrlink || post.id}-${lang}`}
@ -54,12 +54,12 @@ function getPostPath(post: Post) {
<>
<PinIcon
aria-hidden="true"
class="ml-0.25em inline-block aspect-square w-3.7 translate-y--0.45 lg:hidden"
class="ml-0.25em inline-block aspect-square w-0.92em translate-y--0.1em lg:hidden"
fill="currentColor"
/>
<PinIconBold
aria-hidden="true"
class="hidden lg:(ml-0.25em inline-block aspect-square w-4.1 translate-y--0.45)"
class="hidden lg:(ml-0.25em inline-block aspect-square w-1.05em translate-y--0.14em)"
fill="currentColor"
/>
</>

View file

@ -1,10 +1,4 @@
/*!
* Markdown Extended Features
* 1. Admonition
* 2. GitHub Card
*/
/* KaTeX Formula Overflow Fix */
/* KaTeX Overflow Fix */
.katex-display {
--at-apply: 'overflow-x-auto overflow-y-hidden scrollbar-hidden';
}
@ -12,6 +6,25 @@
display: none;
}
/* Heading Anchor Link */
.heading-anchor-link {
--at-apply: 'inline-block translate-y-0.04em c-secondary/0';
}
h1:hover .heading-anchor-link,
h2:hover .heading-anchor-link,
h3:hover .heading-anchor-link,
h4:hover .heading-anchor-link {
--at-apply: 'c-secondary/40';
}
.heading-anchor-link svg {
--at-apply: 'ml-0.4em aspect-square w-0.8em transition-colors active:scale-90 hover:c-secondary/80';
}
/* Video */
iframe {
--at-apply: 'mb-4 w-full aspect-video';
}
/* Admonition >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
.admonition-title {
--at-apply: 'flex items-center mb-4 font-semibold';
@ -129,10 +142,6 @@
}
/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
iframe {
--at-apply: 'mb-4 w-full aspect-video';
}
/* :where(details) {
--at-apply: 'my-4 px-4 py-3 border border-solid border-secondary/25 rounded cursor-pointer';
}

View file

@ -7,7 +7,7 @@
/* Global Styles */
.heti {
--at-apply: 'cjk:tracking-0.02em break-words leading-1.5em hyphens-auto';
--at-apply: 'break-words leading-1.5em hyphens-auto cjk:tracking-0.02em';
}
/* Customized Post Title */
@ -25,7 +25,7 @@
--at-apply: 'mt-6 mb-3 font-semibold';
}
.heti :where(h1) {
--at-apply: 'text-8 leading-12';
--at-apply: 'text-7 leading-12';
}
.heti :where(h2) {
--at-apply: 'text-6 leading-9';
@ -45,7 +45,7 @@
.heti :where(h1),
.heti :where(h2),
.heti :where(h3) {
--at-apply: 'cjk:text-pretty cjk:tracking-0.05em text-balance';
--at-apply: 'text-balance cjk:text-pretty cjk:tracking-0.05em';
}
.heti :where(h1 + h2),
.heti :where(h2 + h3),
@ -57,7 +57,7 @@
/* Paragraphs */
.heti :where(p) {
--at-apply: 'cjk:text-justify mb-4 text-pretty';
--at-apply: 'mb-4 text-pretty cjk:text-justify';
}
/* Links */
@ -92,7 +92,7 @@ html.dark .heti pre :where(span) {
/* Inline Code */
.heti :where(code) {
--at-apply: 'cjk:break-all px-0.4em py-0.2em text-0.85em tracking-0 uno-round-border bg-secondary/5';
--at-apply: 'px-0.4em py-0.2em text-0.85em tracking-0 uno-round-border bg-secondary/5 cjk:break-all';
counter-reset: line;
}
.heti :where(code) span.line {