Skip to content

Commit

Permalink
feat(website): add autolink headings back (#1074)
Browse files Browse the repository at this point in the history
Co-authored-by: Emanuele Stoppa <[email protected]>
  • Loading branch information
yanthomasdev and ematipico authored Dec 6, 2023
1 parent 94e9d15 commit 6db0fc5
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 101 deletions.
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 28 additions & 101 deletions website/astro.config.ts
Original file line number Diff line number Diff line change
@@ -1,104 +1,40 @@
import fs from "node:fs/promises";
import path from "node:path";
import { netlifyStatic } from "@astrojs/netlify";
import react from "@astrojs/react";
import starlight from "@astrojs/starlight";
import { defineConfig } from "astro/config";
import { h } from "hastscript";
import { escape as htmlEscape } from "html-escaper";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeSlug from "rehype-slug";
import remarkToc from "remark-toc";

function resolveFile(relative: string, parent: string, root: string): string {
if (relative[0] === "/") {
return `${root}${relative}`;
}
return path.resolve(path.join(parent, relative));
}

const IMPORT_REGEX = /^import"(.*?)";?$/;

async function readFile(
loc: string,
root: string,
cache: Files,
): Promise<string> {
let content = cache.get(loc);
if (content === undefined) {
content = await fs.readFile(loc, "utf8");
content = content.trim();
cache.set(loc, content);
}

const importMatch = content.match(IMPORT_REGEX);
if (importMatch != null) {
return readFile(
resolveFile(importMatch[1], path.dirname(loc), root),
root,
cache,
);
}

return content;
}

type Files = Map<string, string>;

async function inline({
files,
root,
replacements,
}: {
files: Files;
root: string;
replacements: {
regex: RegExp;
tagBefore: string;
tagAfter: string;
}[];
}): Promise<void> {
const cache: Files = new Map();

await Promise.all(
Array.from(files.entries(), async ([htmlPath, file]) => {
if (htmlPath.includes("playground")) {
return;
}

const matches: {
key: string;
match: string;
tagBefore: string;
tagAfter: string;
}[] = [];

for (const { regex, tagBefore, tagAfter } of replacements) {
file = file.replace(regex, (match, p1) => {
const key = `{{INLINE:${matches.length - 1}}}`;
matches.push({ key, match: p1, tagBefore, tagAfter });
return key;
});
}

const sources: string[] = await Promise.all(
matches.map(async ({ match }) => {
const resolvedPath = resolveFile(match, path.dirname(htmlPath), root);
return await readFile(resolvedPath, root, cache);
}),
);

for (let i = 0; i < matches.length; i++) {
const { key, tagBefore, tagAfter } = matches[i];
const source = sources[i];
const index = file.indexOf(key);
const start = file.slice(0, index);
const end = file.slice(index + key.length);
file = `${start}${tagBefore}${source}${tagAfter}${end}`;
}

files.set(htmlPath, file);
const anchorLinkIcon = h(
"span",
{ ariaHidden: "true", class: "anchor-icon" },
h(
"svg",
{ width: 16, height: 16, viewBox: "0 0 24 24" },
h("path", {
fill: "currentcolor",
d: "m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z",
}),
),
);

const anchorLinkSRLabel = (text: string) =>
h(
"span",
{ "is:raw": true, class: "sr-only" },
`Section titled ${htmlEscape(text)}`,
);
}

const autolinkConfig = {
properties: { class: "anchor-link" },
behavior: "after",
group: ({ tagName }) =>
h("div", { tabIndex: -1, class: `heading-wrapper level-${tagName}` }),
content: ({ heading }) => [anchorLinkIcon, anchorLinkSRLabel("test")],
};

const site = "https://biomejs.dev";
// https://astro.build/config
Expand Down Expand Up @@ -256,16 +192,7 @@ export default defineConfig({
markdown: {
syntaxHighlight: "prism",
remarkPlugins: [remarkToc],
rehypePlugins: [
rehypeSlug,
[
rehypeAutolinkHeadings,
{
behavior: "append",
content: [],
},
],
],
rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, autolinkConfig]],
},

adapter: netlifyStatic(),
Expand Down
2 changes: 2 additions & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
"codemirror-lang-rome-ast": "0.0.6",
"fast-diff": "^1.3.0",
"hast-util-to-html": "^8.0.4",
"hastscript": "^8.0.0",
"html-escaper": "^3.0.3",
"lang-rome-formatter-ir": "0.0.2",
"mdast-util-to-hast": "^12.3.0",
"mermaid": "^9.4.3",
Expand Down
72 changes: 72 additions & 0 deletions website/src/styles/_markdown.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* Heading anchor link styles */
.sl-markdown-content .heading-wrapper {
--icon-size: 0.75em;
--icon-spacing: 0.25em;
line-height: var(--sl-line-height-headings);
}

/* Set font-size on wrapper element, so line-height, margins etc. match heading size. */
.sl-markdown-content {
.level-h2 {
font-size: var(--sl-text-h2);
}

.level-h3 {
font-size: var(--sl-text-h3);
}

.level-h4 {
font-size: var(--sl-text-h4);
}

.level-h5 {
font-size: var(--sl-text-h5);
}
}

.sl-markdown-content .heading-wrapper> :first-child {
margin-inline-end: calc(var(--icon-size) + var(--icon-spacing));
display: inline;
}

.sl-markdown-content .heading-wrapper svg {
display: inline;
width: var(--icon-size);
}

.sl-markdown-content .anchor-link {
margin-inline-start: calc(-1 * (var(--icon-size)));
color: var(--sl-color-gray-3);

&:hover,
&:focus {
color: var(--sl-color-text-accent);
}
}

@media (hover: hover) {
.sl-markdown-content .anchor-link {
opacity: 0;
}
}

.sl-markdown-content .heading-wrapper:hover>.anchor-link,
.sl-markdown-content .anchor-link:focus {
opacity: 1;
}

/* Float anchor links to the left of headings on larger screens. */
@media (min-width: 95em) {
.sl-markdown-content .heading-wrapper {
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
gap: var(--icon-spacing);
margin-inline-start: calc(-1 * (var(--icon-size) + var(--icon-spacing)));
}

.sl-markdown-content .heading-wrapper> :first-child,
.sl-markdown-content .anchor-link {
margin: 0;
}
}
1 change: 1 addition & 0 deletions website/src/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
@import "_credits";
@import "_pre";
@import "_sponsors";
@import "_markdown";

0 comments on commit 6db0fc5

Please sign in to comment.