mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-06 02:48:25 +00:00
Replace direct Shiki usage with @pierre/diffs File component
Use the File component from @pierre/diffs/react instead of manually creating a Shiki highlighter instance. Register the custom DOT grammar via attachResolvedLanguages and drop the direct shiki dependency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
664dc91bf5
commit
615a3a2dbf
5 changed files with 48 additions and 105 deletions
|
|
@ -35,21 +35,6 @@
|
|||
radial-gradient(ellipse 80% 50% at 85% 90%, rgba(90, 200, 168, 0.07) 0%, transparent 45%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
/* ── Shiki syntax highlighting ── */
|
||||
|
||||
.shiki-container pre.shiki {
|
||||
background: transparent !important;
|
||||
margin: 0;
|
||||
padding: 1rem 1.25rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.shiki-container pre.shiki code {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* ── @pierre/diffs brand overrides ── */
|
||||
|
||||
:root {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { LanguageRegistration } from "shiki";
|
||||
import type { LanguageRegistration } from "@pierre/diffs";
|
||||
|
||||
export const dotLanguage: LanguageRegistration = {
|
||||
name: "dot",
|
||||
|
|
|
|||
17
apps/arc-web/app/data/register-dot-language.ts
Normal file
17
apps/arc-web/app/data/register-dot-language.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import type { BundledLanguage } from "@pierre/diffs";
|
||||
import {
|
||||
attachResolvedLanguages,
|
||||
getSharedHighlighter,
|
||||
} from "@pierre/diffs";
|
||||
import { dotLanguage } from "./dot-grammar";
|
||||
|
||||
export async function registerDotLanguage(): Promise<void> {
|
||||
const highlighter = await getSharedHighlighter({
|
||||
themes: ["pierre-dark"],
|
||||
langs: [],
|
||||
});
|
||||
attachResolvedLanguages(
|
||||
{ name: "dot" as BundledLanguage, data: [dotLanguage] },
|
||||
highlighter,
|
||||
);
|
||||
}
|
||||
|
|
@ -1,94 +1,24 @@
|
|||
import { ChevronRightIcon } from "@heroicons/react/20/solid";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import type { BundledLanguage } from "@pierre/diffs";
|
||||
import { File } from "@pierre/diffs/react";
|
||||
import { registerDotLanguage } from "../data/register-dot-language";
|
||||
import { workflowData } from "./workflow-detail";
|
||||
import { dotLanguage } from "../data/dot-grammar";
|
||||
|
||||
function CodeBlock({
|
||||
code,
|
||||
lang,
|
||||
filename,
|
||||
defaultOpen = true,
|
||||
}: {
|
||||
code: string;
|
||||
lang: string;
|
||||
filename: string;
|
||||
defaultOpen?: boolean;
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function highlight() {
|
||||
const { createHighlighter } = await import("shiki");
|
||||
if (cancelled) return;
|
||||
|
||||
const highlighter = await createHighlighter({
|
||||
themes: ["nord"],
|
||||
langs: lang === "dot" ? [dotLanguage] : [lang],
|
||||
});
|
||||
|
||||
if (cancelled) return;
|
||||
|
||||
const html = highlighter.codeToHtml(code, {
|
||||
lang,
|
||||
theme: "nord",
|
||||
});
|
||||
|
||||
if (cancelled || containerRef.current == null) return;
|
||||
containerRef.current.innerHTML = html;
|
||||
setReady(true);
|
||||
}
|
||||
|
||||
highlight();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [code, lang]);
|
||||
|
||||
const lines = code.split("\n");
|
||||
const lineCount = lines.length;
|
||||
const loc = lines.filter((l) => l.trim().length > 0).length;
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-white/[0.06] bg-navy-800/50 overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="flex w-full items-center gap-2 px-4 py-2.5 text-left hover:bg-white/[0.02] transition-colors"
|
||||
>
|
||||
<ChevronRightIcon
|
||||
className={`size-4 text-navy-600 transition-transform duration-150 ${open ? "rotate-90" : ""}`}
|
||||
/>
|
||||
<span className="font-mono text-xs text-navy-600">{filename}</span>
|
||||
<span className="ml-auto font-mono text-xs text-navy-600/60">
|
||||
{lineCount} lines ({loc} loc)
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div className={open ? "" : "hidden"}>
|
||||
<div className="border-t border-white/[0.06]" />
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`shiki-container overflow-x-auto transition-opacity duration-200 ${ready ? "opacity-100" : "opacity-0"}`}
|
||||
/>
|
||||
|
||||
{!ready && (
|
||||
<pre className="px-4 py-4 font-mono text-sm leading-relaxed text-navy-600">
|
||||
{code}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WorkflowDefinition() {
|
||||
const { name } = useParams();
|
||||
const workflow = workflowData[name ?? ""];
|
||||
const [dotReady, setDotReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
registerDotLanguage().then(() => {
|
||||
if (!cancelled) setDotReady(true);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (workflow == null) {
|
||||
return <p className="text-sm text-navy-600">No configuration found.</p>;
|
||||
|
|
@ -96,8 +26,20 @@ export default function WorkflowDefinition() {
|
|||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<CodeBlock code={workflow.config} lang="toml" filename="task.toml" defaultOpen={false} />
|
||||
<CodeBlock code={workflow.graph} lang="dot" filename={workflow.filename} />
|
||||
<File
|
||||
file={{ name: "task.toml", contents: workflow.config, lang: "toml" }}
|
||||
options={{ theme: "pierre-dark" }}
|
||||
/>
|
||||
{dotReady && (
|
||||
<File
|
||||
file={{
|
||||
name: workflow.filename,
|
||||
contents: workflow.graph,
|
||||
lang: "dot" as BundledLanguage,
|
||||
}}
|
||||
options={{ theme: "pierre-dark" }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@
|
|||
"isbot": "^5.1.31",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-router": "7.12.0",
|
||||
"shiki": "^4.0.0"
|
||||
"react-router": "7.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-router/dev": "7.12.0",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue