🛠️ Update copyBtn styles for rehypeCopyBtn plugin + create generateToC() + return createdAt, updatedAt & tableOfContents

This commit is contained in:
pheralb
2025-09-14 19:00:20 +01:00
parent e7bbd32b56
commit 1c11725e01
3 changed files with 69 additions and 15 deletions
+19 -5
View File
@@ -1,16 +1,21 @@
import { z } from "zod";
import path from "node:path";
import fs from "node:fs/promises";
// Content Collections:
import { compileMarkdown } from "@content-collections/markdown";
import { defineCollection, defineConfig } from "@content-collections/core";
// Plugings:
// Plugins:
import rehypeSlug from "rehype-slug";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
import rehypeShiki from "@shikijs/rehype/core";
import { shikiHighlighter, rehypeShikiOptions } from "./src/utils/shiki";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
// Custom Plugins:
import { rehypeCopyBtn } from "./src/markdown/rehypeCopyBtn";
import { getTableOfContents } from "./src/markdown/generateToC";
import { rehypeExternalLinks } from "./src/markdown/rehypeExternalLinks";
import { shikiHighlighter, rehypeShikiOptions } from "./src/utils/shiki";
const docs = defineCollection({
name: "docs",
@@ -22,18 +27,27 @@ const docs = defineCollection({
}),
transform: async (document, context) => {
const highlighter = await shikiHighlighter();
const filePath = path.join(
context.collection.directory,
document._meta.filePath,
);
const { mtimeMs, birthtimeMs } = await fs.stat(filePath);
const html = await compileMarkdown(context, document, {
rehypePlugins: [
rehypeSlug,
rehypeAutolinkHeadings,
[rehypeShiki, highlighter, rehypeShikiOptions],
rehypeExternalLinks,
rehypeSlug,
rehypeAutolinkHeadings,
rehypeCopyBtn,
],
});
const tableOfContents = getTableOfContents(document.content);
return {
...document,
html,
createdAt: new Date(birthtimeMs),
updatedAt: new Date(mtimeMs),
tableOfContents,
};
},
});
+39
View File
@@ -0,0 +1,39 @@
import GithubSlugger from "github-slugger";
type ToCItem = {
id: number;
level: number;
text: string;
slug: string;
};
const getTableOfContents = (markdown: string): ToCItem[] => {
const slugger = new GithubSlugger();
const regXHeader = /(?:^|\n)(?<flag>#+)\s+(?<content>.+)/g;
// Delete # from code blocks and inline code:
let clean = markdown.replace(/<pre[\s\S]*?<\/pre>/gi, "");
clean = clean.replace(/<code[\s\S]*?<\/code>/gi, "");
return Array.from(clean.matchAll(regXHeader))
.map((match, idx): ToCItem | null => {
const groups = match.groups;
if (
groups &&
typeof groups.flag === "string" &&
typeof groups.content === "string" &&
groups.flag.length > 1
) {
return {
id: idx,
level: groups.flag.length,
text: groups.content,
slug: slugger.slug(groups.content),
};
}
return null;
})
.filter((x): x is ToCItem => x !== null);
};
export { getTableOfContents, type ToCItem };
+11 -10
View File
@@ -7,13 +7,14 @@ export const rehypeCopyBtn = () => {
return (tree: UnistTree) => {
visit(tree, "element", (node: UnistNode, index, parent) => {
if (node.tagName === "pre" && parent && typeof index === "number") {
const iconSize = 14;
const copyIcon = {
type: "element",
tagName: "svg",
properties: {
xmlns: "http://www.w3.org/2000/svg",
width: "14",
height: "14",
width: iconSize,
height: iconSize,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
@@ -27,8 +28,8 @@ export const rehypeCopyBtn = () => {
type: "element",
tagName: "rect",
properties: {
width: "14",
height: "14",
width: iconSize,
height: iconSize,
x: "8",
y: "8",
rx: "2",
@@ -51,15 +52,15 @@ export const rehypeCopyBtn = () => {
tagName: "svg",
properties: {
xmlns: "http://www.w3.org/2000/svg",
width: "14",
height: "14",
width: iconSize,
height: iconSize,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
style: "display: none;",
class: "hidden",
},
children: [
{
@@ -89,9 +90,9 @@ export const rehypeCopyBtn = () => {
type: "button",
title: "Copy code to clipboard",
class: cn(
"absolute top-2 right-2 px-1.5 py-0.5 rounded-md",
"bg-transparent hover:bg-neutral-200 dark:hover:bg-neutral-800",
"transition-colors",
"cursor-pointer absolute top-0 right-0 px-1.5 py-0.5 rounded-bl-md",
"border-b border-l border-neutral-200 dark:border-neutral-800",
"transition-colors hover:text-neutral-700 dark:hover:text-neutral-300",
),
onclick: `
const button = this;