1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2026-06-05 19:15:30 -04:00

add language indicator to code blocks

This commit is contained in:
2025-05-07 20:01:12 -04:00
parent 6fd7c9fc4a
commit fff705f1e8
15 changed files with 90 additions and 59 deletions
+47 -9
View File
@@ -1,16 +1,19 @@
import { codeToHtml } from "shiki";
import reactToText from "react-to-text";
import { CodeIcon } from "lucide-react";
import CopyButton from "@/components/copy-button";
import { cn } from "@/lib/utils";
import type { ComponentProps, ComponentPropsWithoutRef } from "react";
const CodeBlock = async ({
lineNumbers = false,
showLineNumbers = false,
showCopyButton = true,
className,
children,
...rest
}: ComponentPropsWithoutRef<"pre"> & {
lineNumbers?: boolean;
showLineNumbers?: boolean;
showCopyButton?: boolean;
}) => {
// escape hatch if this code wasn't meant to be highlighted
if (!children || typeof children !== "object" || !("props" in children)) {
@@ -35,22 +38,57 @@ const CodeBlock = async ({
},
});
const getFullLang = (lang: string) => {
// replace the file extension with the full language name when it makes sense to
switch (lang.toLowerCase()) {
case "js":
return "JavaScript";
case "jsx":
return "JavaScript (JSX)";
case "ts":
return "TypeScript";
case "tsx":
return "TypeScript (JSX)";
case "sh":
case "bash":
case "zsh":
return "Shell";
case "py":
return "Python";
case "md":
return "Markdown";
case "mdx":
return "Markdown (MDX)";
default:
return lang;
}
};
const fullLang = getFullLang(lang);
return (
<div className={cn("bg-muted/35 relative isolate rounded-lg border-2 font-mono", className)}>
<div
className={cn(
"grid max-h-[500px] w-full overflow-x-auto p-4 **:bg-transparent! md:max-h-[650px] dark:**:text-[var(--shiki-dark)]! [&_pre]:whitespace-normal",
"grid max-h-[500px] w-full overflow-x-auto p-4 **:bg-transparent! data-language:pt-9 md:max-h-[650px] dark:**:text-[var(--shiki-dark)]! [&_pre]:whitespace-normal",
"[&_.line]:inline-block [&_.line]:min-w-full [&_.line]:py-1 [&_.line]:leading-none [&_.line]:whitespace-pre [&_.line]:after:hidden",
"data-line-numbers:[&_.line]:before:text-muted-foreground data-line-numbers:[counter-reset:line] data-line-numbers:[&_.line]:[counter-increment:line] data-line-numbers:[&_.line]:before:mr-5 data-line-numbers:[&_.line]:before:inline-block data-line-numbers:[&_.line]:before:w-5 data-line-numbers:[&_.line]:before:text-right data-line-numbers:[&_.line]:before:content-[counter(line)]"
)}
data-language={lang}
data-line-numbers={lineNumbers || undefined}
data-language={lang || undefined}
data-line-numbers={showLineNumbers || undefined}
dangerouslySetInnerHTML={{ __html: codeHighlighted }}
/>
<CopyButton
source={codeString}
className="text-foreground/85 hover:text-primary bg-muted/10 absolute top-0 right-0 size-10 rounded-tr-lg rounded-bl-lg border-b-2 border-l-2 p-0 backdrop-blur-xs [&_svg]:my-auto [&_svg]:inline-block [&_svg]:size-4.5 [&_svg]:align-text-bottom"
/>
{fullLang && (
<span className="text-foreground/75 bg-muted/40 absolute top-0 left-0 flex items-center rounded-tl-md rounded-br-lg border-r-2 border-b-2 py-[5px] pr-[10px] font-mono text-xs font-medium tracking-wide uppercase backdrop-blur-xs select-none">
<CodeIcon className="stroke-primary/90 mr-[8px] ml-[10px] inline-block size-[14px]" /> <span>{fullLang}</span>
</span>
)}
{showCopyButton && (
<CopyButton
source={codeString}
className="text-foreground/75 hover:text-primary bg-muted/40 absolute top-0 right-0 size-10 rounded-tr-md rounded-bl-lg border-b-2 border-l-2 p-0 backdrop-blur-xs select-none [&_svg]:my-auto [&_svg]:inline-block [&_svg]:size-4.5 [&_svg]:align-text-bottom"
/>
)}
</div>
);
};
+3 -2
View File
@@ -3,13 +3,14 @@
import { forwardRef, useState, useEffect } from "react";
import copy from "copy-to-clipboard";
import { ClipboardIcon, CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import type { Ref, ComponentPropsWithoutRef, ComponentRef, MouseEventHandler } from "react";
const CopyButton = (
{
source,
timeout = 2000,
style,
className,
...rest
}: ComponentPropsWithoutRef<"button"> & {
source: string;
@@ -51,7 +52,7 @@ const CopyButton = (
ref={ref}
onClick={handleCopy}
disabled={copied}
style={{ cursor: copied ? "default" : "pointer", ...style }}
className={cn("cursor-pointer disabled:cursor-default", className)}
{...rest}
>
{copied ? <CheckIcon className="stroke-success" /> : <ClipboardIcon />}
+3 -8
View File
@@ -3,15 +3,10 @@
import YouTubeEmbed from "react-lite-youtube-embed";
import type { ComponentPropsWithoutRef } from "react";
// lite-youtube-embed CSS is imported in app/global.css to save a request
const YouTube = ({ ...rest }: Omit<ComponentPropsWithoutRef<typeof YouTubeEmbed>, "title">) => {
return (
<div
// lite-youtube-embed CSS is imported in app/global.css to save a request
className="youtube-embed"
>
<YouTubeEmbed cookie={false} containerElement="div" title="" {...rest} />
</div>
);
return <YouTubeEmbed cookie={false} containerElement="div" title="" {...rest} />;
};
export default YouTube;