From d2d42e7c1d478ea7d9a9b96d7406e7aefe6c70c7 Mon Sep 17 00:00:00 2001 From: Yufan Sheng Date: Thu, 26 Sep 2024 17:07:00 +0800 Subject: [PATCH] feat: initial support for add clickable title. --- .vscode/settings.json | 1 + astro.config.ts | 8 ++- package-lock.json | 67 +++++++++++++++++++++++++ package.json | 2 + src/assets/scripts/yufan.me.js | 29 ++++++++--- src/assets/styles/globals.css | 32 +++++++----- src/assets/styles/iconfont/iconfont.css | 3 +- 7 files changed, 121 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9011bd0..9c976a8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -81,6 +81,7 @@ "luoli", "luxon", "mboker", + "microflash", "minagi", "miui", "mmwebid", diff --git a/astro.config.ts b/astro.config.ts index 45a9c29..44248d5 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -2,7 +2,9 @@ import mdx from '@astrojs/mdx'; import zeabur from '@zeabur/astro-adapter/serverless'; import { uploader } from 'astro-uploader'; import { defineConfig, envField } from 'astro/config'; +import rehypeAutolinkHeadings from 'rehype-autolink-headings'; import rehypeExternalLinks from 'rehype-external-links'; +import rehypeSlug from 'rehype-slug'; import options from './options'; import { astroImage } from './plugins/images'; @@ -38,7 +40,11 @@ export default defineConfig({ integrations: [ mdx({ remarkPlugins: [astroImage], - rehypePlugins: [[rehypeExternalLinks, { rel: 'nofollow', target: '_blank' }]], + rehypePlugins: [ + [rehypeExternalLinks, { rel: 'nofollow', target: '_blank' }], + rehypeSlug, + [rehypeAutolinkHeadings, { behavior: 'append', properties: {} }], + ], }), uploader({ paths: ['images', 'og', 'cats'], diff --git a/package-lock.json b/package-lock.json index 7a8960d..d14303e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,9 @@ "prettier-plugin-astro": "^0.14.1", "prettier-plugin-astro-organize-imports": "^0.4.9", "prettier-plugin-organize-imports": "^4.1.0", + "rehype-autolink-headings": "^7.1.0", "rehype-external-links": "^3.0.0", + "rehype-slug": "^6.0.0", "resize-sensor": "^0.0.6", "rimraf": "^6.0.1", "sharp": "^0.33.5", @@ -4663,6 +4665,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-is-element": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", @@ -4826,6 +4842,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.0.tgz", + "integrity": "sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-text": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", @@ -7556,6 +7586,25 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-autolink-headings": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz", + "integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-external-links": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/rehype-external-links/-/rehype-external-links-3.0.0.tgz", @@ -7605,6 +7654,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-stringify": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.0.tgz", diff --git a/package.json b/package.json index 91afbec..4ee1e19 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,9 @@ "prettier-plugin-astro": "^0.14.1", "prettier-plugin-astro-organize-imports": "^0.4.9", "prettier-plugin-organize-imports": "^4.1.0", + "rehype-autolink-headings": "^7.1.0", "rehype-external-links": "^3.0.0", + "rehype-slug": "^6.0.0", "resize-sensor": "^0.0.6", "rimraf": "^6.0.1", "sharp": "^0.33.5", diff --git a/src/assets/scripts/yufan.me.js b/src/assets/scripts/yufan.me.js index 837790d..0259f71 100644 --- a/src/assets/scripts/yufan.me.js +++ b/src/assets/scripts/yufan.me.js @@ -242,14 +242,14 @@ if (typeof comments !== 'undefined' && comments !== null) { } const scrollIntoView = (elem) => { - let top = 0; + if (elem === undefined || elem === null) { + return; + } const rect = elem.getBoundingClientRect(); const elemTop = rect.top + window.scrollY; - top = elemTop - (window.innerHeight / 2 - rect.height / 2); - const scrollOptions = { - top, + top: elemTop, left: 0, behavior: 'smooth', }; @@ -258,7 +258,7 @@ const scrollIntoView = (elem) => { }; // Highlighting the selected comment. -const focusComment = () => { +const focusContent = () => { if (location.hash.startsWith('#atk-comment-')) { for (const li of document.querySelectorAll('.comment-body')) { li.classList.remove('active'); @@ -269,10 +269,25 @@ const focusComment = () => { scrollIntoView(li); li.querySelector('.comment-body').classList.add('active'); } + } else { + // Try to find the ID on heading + if (location.hash.startsWith('#')) { + scrollIntoView(document.getElementById(decodeURIComponent(location.hash).substring(1))); + } } }; -window.addEventListener('hashchange', focusComment); -window.addEventListener('load', focusComment); + +window.addEventListener('load', focusContent); + +// TOC Support +for (const anchor of document.querySelectorAll('a[href^="#"]')) { + anchor.addEventListener('click', (e) => { + e.preventDefault(); + const href = anchor.getAttribute('href'); + location.hash = `${href}`; + scrollIntoView(document.querySelector(anchor.getAttribute('href'))); + }); +} // Add like button for updating likes. const likeButton = document.querySelector('button.post-like'); diff --git a/src/assets/styles/globals.css b/src/assets/styles/globals.css index bfcc5b7..9b040c1 100644 --- a/src/assets/styles/globals.css +++ b/src/assets/styles/globals.css @@ -2427,6 +2427,26 @@ a:hover .overlay { margin: 2rem 0 2rem; } +.post-content h1 a, +.post-content h2 a, +.post-content h3 a, +.post-content h4 a, +.post-content h5 a, +.post-content h6 a { + margin-left: 0.8rem; + text-decoration: none; + color: var(--color-muted); +} + +.post-content h1 a:hover, +.post-content h2 a:hover, +.post-content h3 a:hover, +.post-content h4 a:hover, +.post-content h5 a:hover, +.post-content h6 a:hover { + color: var(--color-primary); +} + .post-content h2 { position: relative; padding: 0 0 0 1.5rem; @@ -2511,12 +2531,6 @@ a:hover .overlay { .post-content dd > a, .post-content td a, .post-content th a, -.post-content h1 a, -.post-content h2 a, -.post-content h3 a, -.post-content h4 a, -.post-content h5 a, -.post-content h6 a, .post-content em a, .post-content strong a { -webkit-box-shadow: 0 -1px 0 0 var(--color-primary) inset; @@ -2531,12 +2545,6 @@ a:hover .overlay { .post-content dd > a:hover, .post-content td a:hover, .post-content th a:hover, -.post-content h1 a:hover, -.post-content h2 a:hover, -.post-content h3 a:hover, -.post-content h4 a:hover, -.post-content h5 a:hover, -.post-content h6 a:hover, .post-content em a:hover, .post-content strong a:hover { opacity: 1; diff --git a/src/assets/styles/iconfont/iconfont.css b/src/assets/styles/iconfont/iconfont.css index 7c38781..42421fd 100644 --- a/src/assets/styles/iconfont/iconfont.css +++ b/src/assets/styles/iconfont/iconfont.css @@ -6,7 +6,8 @@ url('iconfont.ttf?t=1629473213677') format('truetype'); } -.iconfont { +.iconfont, +.icon { font-family: 'iconfont' !important; font-style: normal; -webkit-font-smoothing: antialiased;