feat: auto-link headings

This commit is contained in:
iCrawl
2022-10-08 15:44:00 +02:00
parent 7b76b0b7e7
commit ba90f14f9b
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,
},
);