183 lines
6.8 KiB
JavaScript
183 lines
6.8 KiB
JavaScript
import { useState, useRef, useEffect } from 'react';
|
|
import { useRouter } from 'next/router';
|
|
import Head from 'next/head';
|
|
import Editor, { loader } from "@monaco-editor/react";
|
|
import { SITE_CONFIG } from '../config';
|
|
import { nanoid } from 'nanoid';
|
|
|
|
export default function Home() {
|
|
const [filename, setFilename] = useState('script.js');
|
|
const [code, setCode] = useState('');
|
|
const [lang, setLang] = useState('javascript');
|
|
const [indentMode, setIndentMode] = useState('spaces');
|
|
const [indentSize, setIndentSize] = useState(4);
|
|
const [expiry, setExpiry] = useState(SITE_CONFIG.expiryOptions[0].value);
|
|
const [allowDiscussions, setAllowDiscussions] = useState(false);
|
|
const [availableLanguages, setAvailableLanguages] = useState([]);
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
const editorRef = useRef(null);
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
loader.init().then((monaco) => {
|
|
const languages = monaco.languages.getLanguages();
|
|
const sorted = languages.map(l => l.id).sort((a, b) => a.localeCompare(b));
|
|
setAvailableLanguages(sorted);
|
|
});
|
|
}, []);
|
|
|
|
const handleIndentSizeChange = (newSize) => {
|
|
const oldSize = indentSize;
|
|
setIndentSize(newSize);
|
|
if (editorRef.current && indentMode === 'spaces') {
|
|
const model = editorRef.current.getModel();
|
|
const currentCode = model.getValue();
|
|
const updatedLines = currentCode.split('\n').map(line => {
|
|
const match = line.match(/^ +/);
|
|
if (match) {
|
|
const spaceCount = match[0].length;
|
|
const newSpaceCount = Math.round((spaceCount / oldSize) * newSize);
|
|
return ' '.repeat(newSpaceCount) + line.slice(spaceCount);
|
|
}
|
|
return line;
|
|
});
|
|
model.setValue(updatedLines.join('\n'));
|
|
}
|
|
};
|
|
|
|
const save = async () => {
|
|
if (!code.trim()) return alert("Paste cannot be empty!");
|
|
|
|
const deleteToken = nanoid(32);
|
|
const res = await fetch('/api/pastes', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
content: code,
|
|
language: lang,
|
|
filename,
|
|
expiry,
|
|
allowDiscussions,
|
|
deleteToken
|
|
})
|
|
});
|
|
const data = await res.json();
|
|
|
|
localStorage.setItem(`delete_token_${data.id}`, deleteToken);
|
|
router.push(`/paste/${data.id}`);
|
|
};
|
|
|
|
if (!mounted) return null;
|
|
|
|
return (
|
|
<div className="min-h-screen bg-zinc-950 text-zinc-200 p-8 font-sans">
|
|
<Head>
|
|
<title>{SITE_CONFIG.pageTitle}</title>
|
|
<link rel="icon" href="/favicon.ico" />
|
|
</Head>
|
|
|
|
<div className="max-w-6xl mx-auto">
|
|
<div className="mb-6 flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<img src="/logo.png" alt="DevBin Logo" className="h-12 w-auto" />
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight text-white">DEVBIN</h1>
|
|
<p className="text-zinc-500 text-xs uppercase tracking-widest">Private Code Sharing</p>
|
|
</div>
|
|
</div>
|
|
<p className="text-zinc-500 text-sm font-mono">{SITE_CONFIG.editorTitle}</p>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-4 mb-0 bg-zinc-900 p-2 rounded-t-lg border border-zinc-800 border-b-0">
|
|
<input
|
|
className="bg-zinc-950 border border-zinc-800 p-1.5 px-3 rounded text-sm focus:outline-none focus:border-blue-500 w-64 text-white font-mono"
|
|
placeholder="filename.txt"
|
|
value={filename}
|
|
onChange={(e) => setFilename(e.target.value)}
|
|
/>
|
|
|
|
<div className="flex items-center gap-4">
|
|
<select
|
|
className="bg-zinc-800 border border-zinc-700 p-1.5 rounded text-xs font-semibold focus:outline-none cursor-pointer"
|
|
value={expiry}
|
|
onChange={(e) => setExpiry(e.target.value)}
|
|
>
|
|
{SITE_CONFIG.expiryOptions.map(opt => (
|
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
))}
|
|
</select>
|
|
|
|
<label className="flex items-center gap-2 px-2 cursor-pointer hover:bg-zinc-800 rounded py-1 transition-colors">
|
|
<input
|
|
type="checkbox"
|
|
className="accent-blue-600"
|
|
checked={allowDiscussions}
|
|
onChange={(e) => setAllowDiscussions(e.target.checked)}
|
|
/>
|
|
<span className="text-xs font-semibold text-zinc-400 uppercase tracking-tight">Discussions</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="flex-grow"></div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<select
|
|
className="bg-zinc-800 border border-zinc-700 p-1.5 rounded text-xs font-semibold focus:outline-none cursor-pointer"
|
|
value={indentMode}
|
|
onChange={(e) => setIndentMode(e.target.value)}
|
|
>
|
|
<option value="spaces">Spaces</option>
|
|
<option value="tabs">Tabs</option>
|
|
</select>
|
|
|
|
<select
|
|
className={`bg-zinc-800 border border-zinc-700 p-1.5 rounded text-xs font-semibold focus:outline-none cursor-pointer ${indentMode === 'tabs' ? 'opacity-50' : ''}`}
|
|
value={indentSize}
|
|
disabled={indentMode === 'tabs'}
|
|
onChange={(e) => handleIndentSizeChange(Number(e.target.value))}
|
|
>
|
|
<option value="2">2</option>
|
|
<option value="4">4</option>
|
|
<option value="8">8</option>
|
|
</select>
|
|
|
|
<select
|
|
className="bg-zinc-800 border border-zinc-700 p-1.5 rounded text-xs font-semibold focus:outline-none cursor-pointer capitalize"
|
|
value={lang}
|
|
onChange={e => setLang(e.target.value)}
|
|
>
|
|
{availableLanguages.map(id => <option key={id} value={id}>{id}</option>)}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border border-zinc-800 rounded-b-lg overflow-hidden shadow-2xl">
|
|
<Editor
|
|
height="70vh"
|
|
theme="vs-dark"
|
|
language={lang}
|
|
value={code}
|
|
onChange={setCode}
|
|
onMount={(ed) => { editorRef.current = ed }}
|
|
options={{
|
|
tabSize: indentSize,
|
|
insertSpaces: indentMode === 'spaces',
|
|
fontSize: 14,
|
|
minimap: { enabled: false },
|
|
padding: { top: 16 },
|
|
automaticLayout: true
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex justify-end mt-4">
|
|
<button onClick={save} className="bg-blue-600 px-8 py-2.5 rounded font-bold hover:bg-blue-700 text-white shadow-lg transition-all active:scale-95">
|
|
Create Paste
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |