Commit 848c5256 authored by Administrator's avatar Administrator

Update frontend/src/components/CodeBlock.jsx via Son of Anton

parent 34d5121e
import React, { useState, useCallback, useMemo } from "react";
import React, { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { Copy, Check, Download, GitCommitVertical, Loader2, Plus, Pencil, Eye } from "lucide-react";
import UIPreview, { buildPreviewHTML } from "./UIPreview";
import { Copy, Check, Download, GitCommitHorizontal } from "lucide-react";
const PREVIEWABLE_LANGS = new Set(["html", "htm", "jsx", "tsx", "vue", "svelte"]);
const PREVIEWABLE_EXTS = /\.(html?|jsx|tsx|vue|svelte)$/i;
export default React.memo(function CodeBlock({ language, filename, code, linkedRepo, onCommit }) {
export default function CodeBlock({ language, filename, code, onCommitFile }) {
const [copied, setCopied] = useState(false);
const [committing, setCommitting] = useState(false);
const [commitDone, setCommitDone] = useState(false);
const [showCommitOptions, setShowCommitOptions] = useState(false);
const [showPreview, setShowPreview] = useState(false);
const canPreview = useMemo(() => {
if (PREVIEWABLE_LANGS.has(language)) return true;
if (filename && PREVIEWABLE_EXTS.test(filename)) return true;
// Check if code looks like HTML
if (code.trim().match(/^<!DOCTYPE|^<html|^<div|^<section|^<main|^<header|^<template/i)) return true;
return false;
}, [language, filename, code]);
const previewHTML = useMemo(() => {
if (!canPreview) return null;
return buildPreviewHTML([{ language, filename, code }]);
}, [canPreview, language, filename, code]);
const handleCopy = useCallback(() => {
function handleCopy() {
navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}, [code]);
}
const handleDownload = useCallback(() => {
function handleDownload() {
const blob = new Blob([code], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
......@@ -41,106 +20,69 @@ export default React.memo(function CodeBlock({ language, filename, code, linkedR
a.download = filename || `code.${language || "txt"}`;
a.click();
URL.revokeObjectURL(url);
}, [code, filename, language]);
async function handleCommit(action) {
if (!onCommit || !filename) return;
setCommitting(true);
setShowCommitOptions(false);
try {
await onCommit(filename, code, action);
setCommitDone(true);
setTimeout(() => setCommitDone(false), 3000);
} catch { /* error handled in parent */ }
setCommitting(false);
}
const showGit = linkedRepo && filename;
const lineCount = code.split("\n").length;
function handleCommit() {
if (onCommitFile && filename) {
onCommitFile({ file_path: filename, content: code, language });
}
}
return (
<>
<div className="my-3 rounded-xl overflow-hidden border border-anton-border bg-[#1a1b26]">
{/* Header */}
<div className="flex items-center justify-between px-3 py-1.5 bg-anton-surface border-b border-anton-border gap-2">
<div className="flex items-center gap-2 min-w-0">
{language && <span className="text-[10px] text-anton-accent font-mono uppercase shrink-0">{language}</span>}
{filename && <span className="text-[10px] text-anton-muted truncate">{filename}</span>}
</div>
<div className="flex items-center gap-0.5 shrink-0">
{/* Preview button */}
{canPreview && (
<button
onClick={() => setShowPreview(true)}
className="flex items-center gap-1 px-2 py-1 text-[10px] text-blue-400 hover:bg-blue-400/10 rounded transition"
title="Preview in browser"
>
<Eye size={11} /> Preview
</button>
)}
{/* Git commit buttons */}
{showGit && !commitDone && (
<div className="relative">
{committing ? (
<span className="flex items-center gap-1 px-2 py-1 text-[10px] text-orange-400">
<Loader2 size={11} className="animate-spin" /> Committing…
</span>
) : (
<button onClick={() => setShowCommitOptions(!showCommitOptions)}
className="flex items-center gap-1 px-2 py-1 text-[10px] text-orange-400 hover:bg-orange-400/10 rounded transition"
title={`Commit to ${linkedRepo.name}`}>
<GitCommitVertical size={11} /> Commit
</button>
)}
{showCommitOptions && (
<div className="absolute right-0 top-full mt-1 z-20 bg-anton-card border border-anton-border rounded-lg shadow-xl p-1.5 min-w-[140px] animate-fade-in">
<button onClick={() => handleCommit("update")}
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-[11px] text-white hover:bg-anton-accent/10 rounded transition">
<Pencil size={11} className="text-blue-400" /> Update file
</button>
<button onClick={() => handleCommit("create")}
className="w-full flex items-center gap-2 px-2.5 py-1.5 text-[11px] text-white hover:bg-anton-accent/10 rounded transition">
<Plus size={11} className="text-green-400" /> Create new
</button>
</div>
)}
</div>
)}
{commitDone && (
<span className="flex items-center gap-1 px-2 py-1 text-[10px] text-green-400">
<Check size={11} /> Committed!
</span>
)}
<button onClick={handleDownload} className="p-1.5 text-anton-muted hover:text-white transition" title="Download">
<Download size={12} />
</button>
<button onClick={handleCopy} className="p-1.5 text-anton-muted hover:text-white transition" title="Copy">
{copied ? <Check size={12} className="text-green-400" /> : <Copy size={12} />}
<div className="my-3 rounded-xl overflow-hidden border border-anton-border bg-[#1a1b26]">
{/* Header bar */}
<div className="flex items-center justify-between px-3 py-1.5 bg-[#16162a] border-b border-anton-border">
<div className="flex items-center gap-2">
{language && (
<span className="text-[10px] font-mono text-anton-accent font-bold uppercase">{language}</span>
)}
{filename && (
<span className="text-[10px] font-mono text-anton-muted">{filename}</span>
)}
</div>
<div className="flex items-center gap-1">
{onCommitFile && filename && (
<button
onClick={handleCommit}
className="flex items-center gap-1 px-2 py-0.5 text-[10px] text-green-400 hover:bg-green-500/10 rounded transition"
title={`Commit ${filename} to repo`}
>
<GitCommitHorizontal size={11} />
Commit
</button>
</div>
)}
<button
onClick={handleDownload}
className="flex items-center gap-1 px-2 py-0.5 text-[10px] text-anton-muted hover:text-white rounded transition"
title="Download file"
>
<Download size={11} />
</button>
<button
onClick={handleCopy}
className="flex items-center gap-1 px-2 py-0.5 text-[10px] text-anton-muted hover:text-white rounded transition"
>
{copied ? <Check size={11} className="text-anton-success" /> : <Copy size={11} />}
{copied ? "Copied" : "Copy"}
</button>
</div>
{/* Code */}
<SyntaxHighlighter
language={language || "text"}
style={oneDark}
customStyle={{ margin: 0, padding: "12px 16px", fontSize: "12px", lineHeight: "1.5", background: "transparent" }}
showLineNumbers={lineCount > 3}
lineNumberStyle={{ color: "#555", fontSize: "10px", paddingRight: "12px" }}
wrapLongLines
>
{code}
</SyntaxHighlighter>
</div>
{/* Preview Modal */}
{showPreview && previewHTML && (
<UIPreview
html={previewHTML}
title={filename || `${language} preview`}
onClose={() => setShowPreview(false)}
/>
)}
</>
{/* Code */}
<SyntaxHighlighter
language={language || "text"}
style={oneDark}
customStyle={{
margin: 0,
padding: "12px 16px",
background: "transparent",
fontSize: "12px",
lineHeight: "1.6",
}}
wrapLongLines
>
{code}
</SyntaxHighlighter>
</div>
);
});
\ No newline at end of file
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment