feat(codebase): add syntax highlighting via react-syntax-highlighter with vs-dark-plus theme to the file viewer

This commit is contained in:
2026-06-14 13:02:21 -07:00
parent 249d88f405
commit 759ad99cd8
3 changed files with 233 additions and 42 deletions

View File

@@ -1,14 +1,10 @@
"use client"; "use client";
/**
* Read-only file viewer that pulls a file's content from Gitea via
* `GET /api/projects/[projectId]/file?path=…`. No syntax highlight
* yet — just monospaced text. Phase 2 can swap in Shiki/Prism if
* the founders ever read enough code here to need it.
*/
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Loader2, AlertCircle, FileText } from "lucide-react"; import { Loader2, AlertCircle, FileText, Copy, Check } from "lucide-react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";
import { THEME } from "@/components/project/dashboard-ui";
interface GiteaFileViewerProps { interface GiteaFileViewerProps {
projectId: string; projectId: string;
@@ -29,6 +25,15 @@ export function GiteaFileViewer({ projectId, path }: GiteaFileViewerProps) {
const [content, setContent] = useState<string | null>(null); const [content, setContent] = useState<string | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const copyCode = () => {
if (content) {
navigator.clipboard.writeText(content);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
useEffect(() => { useEffect(() => {
if (!path) { if (!path) {
@@ -46,16 +51,16 @@ export function GiteaFileViewer({ projectId, path }: GiteaFileViewerProps) {
fetch(`/api/projects/${projectId}/file?path=${encodeURIComponent(path)}`, { fetch(`/api/projects/${projectId}/file?path=${encodeURIComponent(path)}`, {
credentials: "include", credentials: "include",
}) })
.then(async r => { .then(async (r) => {
const data = (await r.json()) as ApiResponse; const data = (await r.json()) as ApiResponse;
if (!r.ok) throw new Error(data.error || `HTTP ${r.status}`); if (!r.ok) throw new Error(data.error || `HTTP ${r.status}`);
if (data.type !== "file") throw new Error("Not a file"); if (data.type !== "file") throw new Error("Not a file");
return data.content ?? ""; return data.content ?? "";
}) })
.then(c => { .then((c) => {
if (!cancelled) setContent(c); if (!cancelled) setContent(c);
}) })
.catch(err => { .catch((err) => {
if (!cancelled) setError(err.message || "Failed to load file"); if (!cancelled) setError(err.message || "Failed to load file");
}) })
.finally(() => { .finally(() => {
@@ -70,7 +75,7 @@ export function GiteaFileViewer({ projectId, path }: GiteaFileViewerProps) {
if (!path) { if (!path) {
return ( return (
<Centered> <Centered>
<FileText size={18} style={{ color: INK.muted }} /> <FileText size={18} style={{ color: THEME.muted }} />
<span>Pick a file from the codebase to preview it here.</span> <span>Pick a file from the codebase to preview it here.</span>
</Centered> </Centered>
); );
@@ -79,7 +84,11 @@ export function GiteaFileViewer({ projectId, path }: GiteaFileViewerProps) {
if (loading) { if (loading) {
return ( return (
<Centered> <Centered>
<Loader2 size={14} className="animate-spin" style={{ color: INK.muted }} /> <Loader2
size={14}
className="animate-spin"
style={{ color: THEME.muted }}
/>
<span>Loading {basename(path)}</span> <span>Loading {basename(path)}</span>
</Centered> </Centered>
); );
@@ -88,17 +97,105 @@ export function GiteaFileViewer({ projectId, path }: GiteaFileViewerProps) {
if (error) { if (error) {
return ( return (
<Centered> <Centered>
<AlertCircle size={14} style={{ color: INK.muted }} /> <AlertCircle size={14} style={{ color: THEME.danger }} />
<span>{error}</span> <span style={{ color: THEME.danger }}>{error}</span>
</Centered> </Centered>
); );
} }
const extension = path.split(".").pop() || "text";
const languageMap: Record<string, string> = {
js: "javascript",
jsx: "jsx",
ts: "typescript",
tsx: "tsx",
json: "json",
html: "html",
css: "css",
scss: "scss",
md: "markdown",
sh: "bash",
yml: "yaml",
yaml: "yaml",
sql: "sql",
py: "python",
rs: "rust",
go: "go",
};
const language = languageMap[extension] || "text";
return ( return (
<div style={wrap}> <div
<pre style={pre}> style={{
<code>{content}</code> flex: 1,
</pre> display: "flex",
flexDirection: "column",
minHeight: 0,
position: "relative",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "8px 12px",
background: "#1e1e1e",
borderTopLeftRadius: THEME.radiusSm,
borderTopRightRadius: THEME.radiusSm,
borderBottom: "1px solid #333",
}}
>
<div
style={{
fontSize: "0.8rem",
color: "#a1a1aa",
fontFamily: "ui-monospace, monospace",
}}
>
{basename(path)}
</div>
<button
onClick={copyCode}
style={{
display: "flex",
alignItems: "center",
gap: 6,
background: "transparent",
border: "none",
color: copied ? "#10b981" : "#a1a1aa",
fontSize: "0.75rem",
cursor: "pointer",
}}
>
{copied ? <Check size={13} /> : <Copy size={13} />}
{copied ? "Copied" : "Copy"}
</button>
</div>
<div style={wrap}>
<SyntaxHighlighter
language={language}
style={vscDarkPlus}
customStyle={{
margin: 0,
padding: "16px",
background: "#1e1e1e",
fontSize: "0.8rem",
fontFamily:
"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
flex: 1,
}}
showLineNumbers={true}
lineNumberStyle={{
minWidth: "3em",
paddingRight: "1em",
color: "#6e7681",
textAlign: "right",
}}
>
{content || ""}
</SyntaxHighlighter>
</div>
</div> </div>
); );
} }
@@ -109,36 +206,29 @@ function basename(p: string) {
function Centered({ children }: { children: React.ReactNode }) { function Centered({ children }: { children: React.ReactNode }) {
return ( return (
<div style={{ <div
flex: 1, display: "flex", alignItems: "center", justifyContent: "center", style={{
gap: 10, color: INK.mid, fontSize: "0.85rem", padding: "32px 16px", textAlign: "center", flex: 1,
}}> display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 10,
color: THEME.mid,
fontSize: "0.85rem",
padding: "32px 16px",
textAlign: "center",
}}
>
{children} {children}
</div> </div>
); );
} }
const INK = {
ink: "#1a1a1a",
mid: "#5f5e5a",
muted: "#a09a90",
border: "#e8e4dc",
} as const;
const wrap: React.CSSProperties = { const wrap: React.CSSProperties = {
flex: 1, flex: 1,
minHeight: 0, minHeight: 0,
overflow: "auto", overflow: "auto",
margin: "-4px -10px", background: "#1e1e1e",
}; borderBottomLeftRadius: THEME.radiusSm,
borderBottomRightRadius: THEME.radiusSm,
const pre: React.CSSProperties = {
margin: 0,
padding: "8px 10px",
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
fontSize: "0.78rem",
lineHeight: 1.55,
color: INK.ink,
whiteSpace: "pre",
tabSize: 2,
}; };

View File

@@ -70,6 +70,7 @@
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.1",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"ssh2": "^1.17.0", "ssh2": "^1.17.0",
@@ -87,6 +88,7 @@
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/ssh2": "^1.15.5", "@types/ssh2": "^1.15.5",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.0.1", "eslint-config-next": "16.0.1",

View File

@@ -143,6 +143,9 @@ importers:
react-markdown: react-markdown:
specifier: ^10.1.0 specifier: ^10.1.0
version: 10.1.0(@types/react@19.2.16)(react@19.2.7) version: 10.1.0(@types/react@19.2.16)(react@19.2.7)
react-syntax-highlighter:
specifier: ^16.1.1
version: 16.1.1(react@19.2.7)
remark-gfm: remark-gfm:
specifier: ^4.0.1 specifier: ^4.0.1
version: 4.0.1 version: 4.0.1
@@ -189,6 +192,9 @@ importers:
'@types/react-dom': '@types/react-dom':
specifier: ^19 specifier: ^19
version: 19.2.3(@types/react@19.2.16) version: 19.2.3(@types/react@19.2.16)
'@types/react-syntax-highlighter':
specifier: ^15.5.13
version: 15.5.13
'@types/ssh2': '@types/ssh2':
specifier: ^1.15.5 specifier: ^1.15.5
version: 1.15.5 version: 1.15.5
@@ -2794,6 +2800,9 @@ packages:
'@types/pg@8.20.0': '@types/pg@8.20.0':
resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==}
'@types/prismjs@1.26.6':
resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==}
'@types/qs@6.15.1': '@types/qs@6.15.1':
resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==}
@@ -2805,6 +2814,9 @@ packages:
peerDependencies: peerDependencies:
'@types/react': ^19.2.0 '@types/react': ^19.2.0
'@types/react-syntax-highlighter@15.5.13':
resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==}
'@types/react@19.2.16': '@types/react@19.2.16':
resolution: {integrity: sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==} resolution: {integrity: sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==}
@@ -4080,6 +4092,9 @@ packages:
fastq@1.20.1: fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
fault@1.0.4:
resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==}
faye-websocket@0.11.4: faye-websocket@0.11.4:
resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@@ -4173,6 +4188,10 @@ packages:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
format@0.2.2:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'}
formdata-polyfill@4.0.10: formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'} engines: {node: '>=12.20.0'}
@@ -4364,18 +4383,30 @@ packages:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
hast-util-parse-selector@4.0.0:
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
hast-util-to-jsx-runtime@2.3.6: hast-util-to-jsx-runtime@2.3.6:
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
hast-util-whitespace@3.0.0: hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
hastscript@9.0.1:
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
hermes-estree@0.25.1: hermes-estree@0.25.1:
resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
hermes-parser@0.25.1: hermes-parser@0.25.1:
resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
highlight.js@10.7.3:
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
highlightjs-vue@1.0.0:
resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==}
hono@4.12.23: hono@4.12.23:
resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==} resolution: {integrity: sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==}
engines: {node: '>=16.9.0'} engines: {node: '>=16.9.0'}
@@ -4907,6 +4938,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true hasBin: true
lowlight@1.20.0:
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -5827,6 +5861,10 @@ packages:
engines: {node: '>=16.13'} engines: {node: '>=16.13'}
hasBin: true hasBin: true
prismjs@1.30.0:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
progress@2.0.3: progress@2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
@@ -5939,6 +5977,12 @@ packages:
'@types/react': '@types/react':
optional: true optional: true
react-syntax-highlighter@16.1.1:
resolution: {integrity: sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==}
engines: {node: '>= 16.20.2'}
peerDependencies:
react: '>= 0.14.0'
react-textarea-autosize@8.5.9: react-textarea-autosize@8.5.9:
resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==} resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -5961,6 +6005,9 @@ packages:
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
refractor@5.0.0:
resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==}
regexp.prototype.flags@1.5.4: regexp.prototype.flags@1.5.4:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -9603,6 +9650,8 @@ snapshots:
pg-protocol: 1.14.0 pg-protocol: 1.14.0
pg-types: 2.2.0 pg-types: 2.2.0
'@types/prismjs@1.26.6': {}
'@types/qs@6.15.1': {} '@types/qs@6.15.1': {}
'@types/range-parser@1.2.7': {} '@types/range-parser@1.2.7': {}
@@ -9611,6 +9660,10 @@ snapshots:
dependencies: dependencies:
'@types/react': 19.2.16 '@types/react': 19.2.16
'@types/react-syntax-highlighter@15.5.13':
dependencies:
'@types/react': 19.2.16
'@types/react@19.2.16': '@types/react@19.2.16':
dependencies: dependencies:
csstype: 3.2.3 csstype: 3.2.3
@@ -11169,6 +11222,10 @@ snapshots:
dependencies: dependencies:
reusify: 1.1.0 reusify: 1.1.0
fault@1.0.4:
dependencies:
format: 0.2.2
faye-websocket@0.11.4: faye-websocket@0.11.4:
dependencies: dependencies:
websocket-driver: 0.7.4 websocket-driver: 0.7.4
@@ -11315,6 +11372,8 @@ snapshots:
hasown: 2.0.4 hasown: 2.0.4
mime-types: 2.1.35 mime-types: 2.1.35
format@0.2.2: {}
formdata-polyfill@4.0.10: formdata-polyfill@4.0.10:
dependencies: dependencies:
fetch-blob: 3.2.0 fetch-blob: 3.2.0
@@ -11548,6 +11607,10 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
hast-util-parse-selector@4.0.0:
dependencies:
'@types/hast': 3.0.4
hast-util-to-jsx-runtime@2.3.6: hast-util-to-jsx-runtime@2.3.6:
dependencies: dependencies:
'@types/estree': 1.0.9 '@types/estree': 1.0.9
@@ -11572,12 +11635,24 @@ snapshots:
dependencies: dependencies:
'@types/hast': 3.0.4 '@types/hast': 3.0.4
hastscript@9.0.1:
dependencies:
'@types/hast': 3.0.4
comma-separated-tokens: 2.0.3
hast-util-parse-selector: 4.0.0
property-information: 7.2.0
space-separated-tokens: 2.0.2
hermes-estree@0.25.1: {} hermes-estree@0.25.1: {}
hermes-parser@0.25.1: hermes-parser@0.25.1:
dependencies: dependencies:
hermes-estree: 0.25.1 hermes-estree: 0.25.1
highlight.js@10.7.3: {}
highlightjs-vue@1.0.0: {}
hono@4.12.23: {} hono@4.12.23: {}
html-entities@2.6.0: {} html-entities@2.6.0: {}
@@ -12080,6 +12155,11 @@ snapshots:
dependencies: dependencies:
js-tokens: 4.0.0 js-tokens: 4.0.0
lowlight@1.20.0:
dependencies:
fault: 1.0.4
highlight.js: 10.7.3
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
lru-cache@11.5.1: {} lru-cache@11.5.1: {}
@@ -13521,6 +13601,8 @@ snapshots:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
prismjs@1.30.0: {}
progress@2.0.3: {} progress@2.0.3: {}
prop-types@15.8.1: prop-types@15.8.1:
@@ -13701,6 +13783,16 @@ snapshots:
optionalDependencies: optionalDependencies:
'@types/react': 19.2.16 '@types/react': 19.2.16
react-syntax-highlighter@16.1.1(react@19.2.7):
dependencies:
'@babel/runtime': 7.29.7
highlight.js: 10.7.3
highlightjs-vue: 1.0.0
lowlight: 1.20.0
prismjs: 1.30.0
react: 19.2.7
refractor: 5.0.0
react-textarea-autosize@8.5.9(@types/react@19.2.16)(react@19.2.7): react-textarea-autosize@8.5.9(@types/react@19.2.16)(react@19.2.7):
dependencies: dependencies:
'@babel/runtime': 7.29.7 '@babel/runtime': 7.29.7
@@ -13731,6 +13823,13 @@ snapshots:
get-proto: 1.0.1 get-proto: 1.0.1
which-builtin-type: 1.2.1 which-builtin-type: 1.2.1
refractor@5.0.0:
dependencies:
'@types/hast': 3.0.4
'@types/prismjs': 1.26.6
hastscript: 9.0.1
parse-entities: 4.0.2
regexp.prototype.flags@1.5.4: regexp.prototype.flags@1.5.4:
dependencies: dependencies:
call-bind: 1.0.9 call-bind: 1.0.9