mirror of
https://github.com/Dokploy/website
synced 2025-06-26 18:16:01 +00:00
feat: add clickable heading links with copy-to-clipboard functionality
This commit is contained in:
parent
064924316b
commit
b7e7d50f32
100
apps/website/app/[locale]/blog/[slug]/components/Headings.tsx
Normal file
100
apps/website/app/[locale]/blog/[slug]/components/Headings.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import slugify from "slugify";
|
||||
|
||||
type HeadingProps = DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLHeadingElement>,
|
||||
HTMLHeadingElement
|
||||
>;
|
||||
|
||||
function LinkIcon() {
|
||||
return (
|
||||
<svg
|
||||
className="inline-block w-4 h-4 ml-2 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function H1({ children, ...props }: HeadingProps) {
|
||||
const router = useRouter();
|
||||
const id = slugify(children?.toString() || "", {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`#${id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<h1
|
||||
id={id}
|
||||
onClick={handleClick}
|
||||
className="group text-xl md:text-2xl xl:text-3xl text-primary font-bold mt-8 mb-4 cursor-pointer hover:text-primary/80 transition-colors"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<LinkIcon />
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
export function H2({ children, ...props }: HeadingProps) {
|
||||
const router = useRouter();
|
||||
const id = slugify(children?.toString() || "", {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`#${id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<h2
|
||||
id={id}
|
||||
onClick={handleClick}
|
||||
className="group text-2xl text-primary/90 font-semibold mt-6 mb-3 cursor-pointer hover:text-primary/80 transition-colors"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<LinkIcon />
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
|
||||
export function H3({ children, ...props }: HeadingProps) {
|
||||
const router = useRouter();
|
||||
const id = slugify(children?.toString() || "", {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`#${id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<h3
|
||||
id={id}
|
||||
onClick={handleClick}
|
||||
className="group text-xl text-primary/90 font-semibold mt-4 mb-2 cursor-pointer hover:text-primary/80 transition-colors"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<LinkIcon />
|
||||
</h3>
|
||||
);
|
||||
}
|
@ -19,6 +19,7 @@ import slugify from "slugify";
|
||||
import TurndownService from "turndown";
|
||||
// @ts-ignore
|
||||
import * as turndownPluginGfm from "turndown-plugin-gfm";
|
||||
import { H1, H2, H3 } from "./components/Headings";
|
||||
import { TableOfContents } from "./components/TableOfContents";
|
||||
import { ZoomableImage } from "./components/ZoomableImage";
|
||||
|
||||
@ -172,55 +173,17 @@ export default async function BlogPostPage({ params }: Props) {
|
||||
});
|
||||
|
||||
const components: Partial<Components> = {
|
||||
h1: ({ node, ...props }) => {
|
||||
const id = slugify(props.children?.toString() || "", {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
return (
|
||||
<h1
|
||||
id={id}
|
||||
className="text-xl md:text-2xl xl:text-3xl text-primary font-bold mt-8 mb-4"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
h2: ({ node, ...props }) => {
|
||||
const id = slugify(props.children?.toString() || "", {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
return (
|
||||
<h2
|
||||
id={id}
|
||||
className="text-2xl text-primary/90 font-semibold mt-6 mb-3"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
h3: ({ node, ...props }) => {
|
||||
const id = slugify(props.children?.toString() || "", {
|
||||
lower: true,
|
||||
strict: true,
|
||||
});
|
||||
return (
|
||||
<h3
|
||||
id={id}
|
||||
className="text-xl text-primary/90 font-semibold mt-4 mb-2"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
p: ({ node, children, ...props }) => {
|
||||
return (
|
||||
h1: H1,
|
||||
h2: H2,
|
||||
h3: H3,
|
||||
p: ({ node, children, ...props }) => (
|
||||
<p
|
||||
className="text-base text-muted-foreground leading-relaxed mb-4"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
},
|
||||
),
|
||||
a: ({ node, href, ...props }) => (
|
||||
<a
|
||||
href={href}
|
||||
|
Loading…
Reference in New Issue
Block a user