add heading anchors

This commit is contained in:
Joseph Montanaro 2023-08-20 16:12:04 -07:00
parent 6431267827
commit b1dc3ae0ea
3 changed files with 62 additions and 6 deletions

52
src/lib/Heading.svelte Normal file
View File

@ -0,0 +1,52 @@
<script>
export let level;
export let id = '';
const tag = `h${level}`;
</script>
<style>
a {
/* Works better to set the size here for line-height reasons */
font-size: 0.9em;
color: hsl(0, 0%, 50%);
}
a:hover {
border-bottom: 0.05em solid currentcolor;
}
svg {
width: 1em;
}
.before {
display: none;
margin-right: 0.5rem;
margin-left: calc(-1em - 0.5rem);
}
@media(min-width: 58rem) {
.before {
display: inline;
}
.after {
display: none;
}
}
</style>
<svelte:element this={tag} {id} class="h">
<a href="#{id}" class="before">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" />
</svg></a><span> <!-- Looks ugly but necessary to get rid of spurious whitespace -->
<slot></slot>
</span>
<!-- Icon from https://heroicons.com/ -->
<a href="#{id}" class="after">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" />
</svg>
</a>
</svelte:element>

View File

@ -1,7 +1,7 @@
<script context="module"> <script context="module">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { formatDate } from './datefmt.js'; import { formatDate } from './datefmt.js';
import { makeSlug } from '$lib/slug.js'; import { makeSlug } from '$lib/utils.js';
import Link from './Link.svelte'; import Link from './Link.svelte';
export { Link as a }; export { Link as a };

View File

@ -12,15 +12,16 @@ export function localPlugins() {
let dropcapAdded = false; let dropcapAdded = false;
let moduleScript; let moduleScript;
let imports = []; let imports = new Set();
if (needsDropcap) { if (needsDropcap) {
imports.push("import Dropcap from '$lib/Dropcap.svelte';"); imports.add("import Dropcap from '$lib/Dropcap.svelte';");
} }
visit(tree, node => { visit(tree, node => {
// add slugs to headings // add slugs to headings
if (isHeading(node)) { if (isHeading(node)) {
processHeading(node); processHeading(node);
imports.add("import Heading from '$lib/Heading.svelte';");
return SKIP; return SKIP;
} }
@ -38,7 +39,7 @@ export function localPlugins() {
}); });
// insert our imports at the top of the `<script context="module">` tag // insert our imports at the top of the `<script context="module">` tag
if (imports.length > 0) { if (imports.size > 0) {
const script = moduleScript.value; const script = moduleScript.value;
// split the script where the opening tag ends // split the script where the opening tag ends
const i = script.indexOf('>'); const i = script.indexOf('>');
@ -46,15 +47,18 @@ export function localPlugins() {
const remainder = script.slice(i + 1); const remainder = script.slice(i + 1);
// mdvsex uses tabs so we will as well // mdvsex uses tabs so we will as well
const importScript = imports.join('\n\t'); const importScript = Array.from(imports).join('\n\t');
moduleScript.value = `${openingTag}\n\t${imports}${remainder}`; moduleScript.value = `${openingTag}\n\t${importScript}${remainder}`;
} }
} }
} }
function processHeading(node) { function processHeading(node) {
const level = node.tagName.slice(1);
node.tagName = 'Heading';
node.properties.level = level;
node.properties.id = makeSlug(toText(node)); node.properties.id = makeSlug(toText(node));
} }