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 { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { Copy, Check, Download, GitCommitVertical, Loader2, Plus, Pencil, Eye } from "lucide-react"; import { Copy, Check, Download, GitCommitHorizontal } from "lucide-react";
import UIPreview, { buildPreviewHTML } from "./UIPreview";
const PREVIEWABLE_LANGS = new Set(["html", "htm", "jsx", "tsx", "vue", "svelte"]); export default function CodeBlock({ language, filename, code, onCommitFile }) {
const PREVIEWABLE_EXTS = /\.(html?|jsx|tsx|vue|svelte)$/i;
export default React.memo(function CodeBlock({ language, filename, code, linkedRepo, onCommit }) {
const [copied, setCopied] = useState(false); 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); navigator.clipboard.writeText(code);
setCopied(true); setCopied(true);
setTimeout(() => setCopied(false), 2000); setTimeout(() => setCopied(false), 2000);
}, [code]); }
const handleDownload = useCallback(() => { function handleDownload() {
const blob = new Blob([code], { type: "text/plain" }); const blob = new Blob([code], { type: "text/plain" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement("a"); const a = document.createElement("a");
...@@ -41,81 +20,50 @@ export default React.memo(function CodeBlock({ language, filename, code, linkedR ...@@ -41,81 +20,50 @@ export default React.memo(function CodeBlock({ language, filename, code, linkedR
a.download = filename || `code.${language || "txt"}`; a.download = filename || `code.${language || "txt"}`;
a.click(); a.click();
URL.revokeObjectURL(url); 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; function handleCommit() {
const lineCount = code.split("\n").length; if (onCommitFile && filename) {
onCommitFile({ file_path: filename, content: code, language });
}
}
return ( return (
<>
<div className="my-3 rounded-xl overflow-hidden border border-anton-border bg-[#1a1b26]"> <div className="my-3 rounded-xl overflow-hidden border border-anton-border bg-[#1a1b26]">
{/* Header */} {/* Header bar */}
<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 justify-between px-3 py-1.5 bg-[#16162a] border-b border-anton-border">
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2">
{language && <span className="text-[10px] text-anton-accent font-mono uppercase shrink-0">{language}</span>} {language && (
{filename && <span className="text-[10px] text-anton-muted truncate">{filename}</span>} <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>
<div className="flex items-center gap-0.5 shrink-0"> <div className="flex items-center gap-1">
{/* Preview button */} {onCommitFile && filename && (
{canPreview && (
<button <button
onClick={() => setShowPreview(true)} onClick={handleCommit}
className="flex items-center gap-1 px-2 py-1 text-[10px] text-blue-400 hover:bg-blue-400/10 rounded transition" className="flex items-center gap-1 px-2 py-0.5 text-[10px] text-green-400 hover:bg-green-500/10 rounded transition"
title="Preview in browser" title={`Commit ${filename} to repo`}
> >
<Eye size={11} /> Preview <GitCommitHorizontal size={11} />
</button> Commit
)}
{/* 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> </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"> <button
<Download size={12} /> 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>
<button onClick={handleCopy} className="p-1.5 text-anton-muted hover:text-white transition" title="Copy"> <button
{copied ? <Check size={12} className="text-green-400" /> : <Copy size={12} />} 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> </button>
</div> </div>
</div> </div>
...@@ -124,23 +72,17 @@ export default React.memo(function CodeBlock({ language, filename, code, linkedR ...@@ -124,23 +72,17 @@ export default React.memo(function CodeBlock({ language, filename, code, linkedR
<SyntaxHighlighter <SyntaxHighlighter
language={language || "text"} language={language || "text"}
style={oneDark} style={oneDark}
customStyle={{ margin: 0, padding: "12px 16px", fontSize: "12px", lineHeight: "1.5", background: "transparent" }} customStyle={{
showLineNumbers={lineCount > 3} margin: 0,
lineNumberStyle={{ color: "#555", fontSize: "10px", paddingRight: "12px" }} padding: "12px 16px",
background: "transparent",
fontSize: "12px",
lineHeight: "1.6",
}}
wrapLongLines wrapLongLines
> >
{code} {code}
</SyntaxHighlighter> </SyntaxHighlighter>
</div> </div>
{/* Preview Modal */}
{showPreview && previewHTML && (
<UIPreview
html={previewHTML}
title={filename || `${language} preview`}
onClose={() => setShowPreview(false)}
/>
)}
</>
); );
}); }
\ 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