feat: auto-link headings

This commit is contained in:
iCrawl 2022-10-08 15:44:00 +02:00
parent 7b76b0b7e7
commit ba90f14f9b
No known key found for this signature in database
GPG key ID: 1AB888B16355FBB2
5 changed files with 105 additions and 4 deletions

View file

@ -4,9 +4,42 @@ import mdx from '@astrojs/mdx';
import react from '@astrojs/react';
import { remarkCodeHike } from '@code-hike/mdx';
import { defineConfig } from 'astro/config';
import { toString } from 'hast-util-to-string';
import { h } from 'hastscript';
import { escape } from 'html-escaper';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypeSlug from 'rehype-slug';
import shikiThemeDarkPlus from 'shiki/themes/dark-plus.json' assert { type: 'json' };
import Unocss from 'unocss/astro';
const LinkIcon = h(
'svg',
{
width: '1rem',
height: '1rem',
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
},
h('path', {
// eslint-disable-next-line id-length
d: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',
}),
h('path', {
// eslint-disable-next-line id-length
d: 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',
}),
);
const createSROnlyLabel = (text: string) => {
const node = h('span.sr-only', `Section titled ${escape(text)}`);
node.properties!['is:raw'] = true;
return node;
};
export default defineConfig({
integrations: [
react(),
@ -20,7 +53,34 @@ export default defineConfig({
],
markdown: {
remarkPlugins: [[remarkCodeHike, { autoImport: false, theme: shikiThemeDarkPlus, lineNumbers: true }]],
rehypePlugins: [],
rehypePlugins: [
rehypeSlug,
[
rehypeAutolinkHeadings,
{
properties: {
class:
'relative inline-flex w-6 h-6 place-items-center place-content-center outline-0 text-black dark:text-white ml-2',
},
behavior: 'after',
group: ({ tagName }) =>
h('div', {
class: `[&>*]:inline-block [&>h1]:m-0 [&>h2]:m-0 [&>h3]:m-0 [&>h4]:m-0 level-${tagName}`,
tabIndex: -1,
}),
content: (heading) => [
h(
`span.anchor-icon`,
{
ariaHidden: 'true',
},
LinkIcon,
),
createSROnlyLabel(toString(heading)),
],
},
],
],
extendDefaultPlugins: true,
syntaxHighlight: false,
},

View file

@ -72,9 +72,14 @@
"eslint": "^8.24.0",
"eslint-config-neon": "^0.1.35",
"happy-dom": "^7.4.0",
"hast-util-to-string": "^2.0.0",
"hastscript": "^7.0.2",
"html-escaper": "^3.0.3",
"prettier": "^2.7.1",
"prettier-plugin-astro": "^0.5.5",
"prettier-plugin-tailwindcss": "^0.1.13",
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.0.1",
"typescript": "^4.8.4",
"unocss": "^0.45.26",
"vercel": "^28.4.8",

View file

@ -13,7 +13,7 @@ const { headings } = Astro.props;
<script is:inline>
window.addEventListener('load', () => {
const headings = document.querySelectorAll(
'div.prose > h1, div.prose > h2, div.prose > h3, div.prose > h4, div.prose > h5',
'div.level-h1 > h1, div.level-h2 > h2, div.level-h3 > h3, div.level-h4 > h4',
);
const headingsObserver = new IntersectionObserver(
@ -27,7 +27,7 @@ const { headings } = Astro.props;
},
{
root: null,
rootMargin: '0px 0px -450px 0px',
rootMargin: '-100px 0% -66%',
threshold: 1.0,
},
);

View file

@ -52,14 +52,30 @@ export default defineConfig({
h1: {
'scroll-margin-top': '6.5rem',
},
'.level-h1': {
margin: '1rem 0',
},
h2: {
'margin-top': '1.25em',
'scroll-margin-top': '6.5rem',
},
'.level-h2': {
margin: '1.25em 0',
},
h3: {
'margin-top': '1.25em',
'scroll-margin-top': '6.5rem',
},
'.level-h3': {
margin: '1.25em 0',
},
h4: {
'margin-top': '1.25em',
'scroll-margin-top': '6.5rem',
},
'.level-h4': {
margin: '1.25em 0',
},
// eslint-disable-next-line id-length
p: {
margin: '.5em 0',

View file

@ -2130,6 +2130,9 @@ __metadata:
eslint: ^8.24.0
eslint-config-neon: ^0.1.35
happy-dom: ^7.4.0
hast-util-to-string: ^2.0.0
hastscript: ^7.0.2
html-escaper: ^3.0.3
meilisearch: ^0.28.0
prettier: ^2.7.1
prettier-plugin-astro: ^0.5.5
@ -2140,9 +2143,11 @@ __metadata:
react-icons: ^4.4.0
react-syntax-highlighter: ^15.5.0
react-use: ^17.4.0
rehype-autolink-headings: ^6.1.1
rehype-ignore: ^1.0.1
rehype-pretty-code: ^0.3.2
rehype-raw: ^6.1.1
rehype-slug: ^5.0.1
sharp: ^0.31.1
shiki: ^0.11.1
typescript: ^4.8.4
@ -11173,7 +11178,7 @@ __metadata:
languageName: node
linkType: hard
"hastscript@npm:^7.0.0":
"hastscript@npm:^7.0.0, hastscript@npm:^7.0.2":
version: 7.0.2
resolution: "hastscript@npm:7.0.2"
dependencies:
@ -16706,6 +16711,21 @@ __metadata:
languageName: node
linkType: hard
"rehype-autolink-headings@npm:^6.1.1":
version: 6.1.1
resolution: "rehype-autolink-headings@npm:6.1.1"
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
checksum: 60782fb2e505e70f75b59a3ac43cb5abfeb328485da07b604fcc73d6bc3626e9c3657d536a77c6c6c435b7b3086bae5c7f93d5ded510d74e553fd80fd91c8f97
languageName: node
linkType: hard
"rehype-ignore@npm:^1.0.1":
version: 1.0.1
resolution: "rehype-ignore@npm:1.0.1"