ui: replace HTML textarea with drag-and-drop file zone
Publish form now uses a single dashed drop area for .html uploads (click-to-browse fallback included). Removes the paste-HTML textarea since real artifacts always come from a file. Server-side multipart handling is unchanged; the `file` form field already took precedence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,9 +26,21 @@
|
|||||||
<!-- Publish form -->
|
<!-- Publish form -->
|
||||||
<section class="rounded border border-slate-800 bg-slate-900/50 p-4 mb-8">
|
<section class="rounded border border-slate-800 bg-slate-900/50 p-4 mb-8">
|
||||||
<h2 class="text-lg font-semibold mb-3">Publish</h2>
|
<h2 class="text-lg font-semibold mb-3">Publish</h2>
|
||||||
<form method="post" action="/api/publish" enctype="multipart/form-data" class="space-y-3">
|
<form method="post" action="/api/publish" enctype="multipart/form-data" class="space-y-3" id="publishForm">
|
||||||
<textarea name="html" rows="8" placeholder="Paste HTML here, or use the file picker below"
|
<label for="dropInput"
|
||||||
class="w-full bg-slate-950 border border-slate-800 rounded px-3 py-2 text-sm focus:outline-none focus:border-indigo-500"></textarea>
|
id="dropZone"
|
||||||
|
class="flex flex-col items-center justify-center text-center cursor-pointer
|
||||||
|
border-2 border-dashed border-slate-700 hover:border-indigo-500
|
||||||
|
rounded-lg bg-slate-950/50 hover:bg-slate-950 transition-colors
|
||||||
|
px-6 py-12 text-sm text-slate-400">
|
||||||
|
<svg class="w-10 h-10 mb-3 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||||
|
d="M7 16a4 4 0 01-.88-7.9A5 5 0 0115.9 6.56 4 4 0 1118 16M8 13l4-4m0 0l4 4m-4-4v10"/>
|
||||||
|
</svg>
|
||||||
|
<div><span class="text-indigo-300 font-medium">Drop HTML file here</span> or click to browse</div>
|
||||||
|
<div class="text-xs text-slate-500 mt-1" id="dropHint">.html · up to 5 MB</div>
|
||||||
|
<input id="dropInput" type="file" name="file" accept=".html,text/html" class="sr-only" required>
|
||||||
|
</label>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-3">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
<label class="block">
|
<label class="block">
|
||||||
<span class="block text-xs text-slate-400 mb-1">Custom slug (optional)</span>
|
<span class="block text-xs text-slate-400 mb-1">Custom slug (optional)</span>
|
||||||
@@ -56,15 +68,55 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-end">
|
||||||
<label class="text-xs text-slate-400">
|
|
||||||
Or upload: <input type="file" name="file" accept=".html,text/html" class="ml-2 text-xs">
|
|
||||||
</label>
|
|
||||||
<button class="bg-indigo-600 hover:bg-indigo-500 text-white rounded px-4 py-2 font-medium text-sm">Publish</button>
|
<button class="bg-indigo-600 hover:bg-indigo-500 text-white rounded px-4 py-2 font-medium text-sm">Publish</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
const zone = document.getElementById('dropZone');
|
||||||
|
const input = document.getElementById('dropInput');
|
||||||
|
const hint = document.getElementById('dropHint');
|
||||||
|
const form = document.getElementById('publishForm');
|
||||||
|
if (!zone || !input) return;
|
||||||
|
|
||||||
|
const ACTIVE = ['border-indigo-500', 'bg-indigo-500/10'];
|
||||||
|
const prevent = e => { e.preventDefault(); e.stopPropagation(); };
|
||||||
|
['dragenter', 'dragover'].forEach(ev =>
|
||||||
|
zone.addEventListener(ev, e => { prevent(e); zone.classList.add(...ACTIVE); }));
|
||||||
|
['dragleave', 'drop'].forEach(ev =>
|
||||||
|
zone.addEventListener(ev, e => { prevent(e); zone.classList.remove(...ACTIVE); }));
|
||||||
|
|
||||||
|
zone.addEventListener('drop', e => {
|
||||||
|
const f = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];
|
||||||
|
if (!f) return;
|
||||||
|
if (!/\.html?$/i.test(f.name) && f.type !== 'text/html') {
|
||||||
|
hint.textContent = 'Only .html files are accepted';
|
||||||
|
hint.classList.add('text-red-400');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// DataTransfer into the input so form submit picks it up.
|
||||||
|
const dt = new DataTransfer();
|
||||||
|
dt.items.add(f);
|
||||||
|
input.files = dt.files;
|
||||||
|
showSelection(f);
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('change', () => {
|
||||||
|
if (input.files && input.files[0]) showSelection(input.files[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
function showSelection(f) {
|
||||||
|
const kb = (f.size / 1024).toFixed(1);
|
||||||
|
hint.textContent = `${f.name} · ${kb} KB`;
|
||||||
|
hint.classList.remove('text-red-400');
|
||||||
|
hint.classList.add('text-indigo-300');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
{{ if .FlashSlug }}
|
{{ if .FlashSlug }}
|
||||||
<div class="rounded border border-emerald-800 bg-emerald-900/30 text-emerald-100 px-4 py-3 mb-6 text-sm flex items-center justify-between">
|
<div class="rounded border border-emerald-800 bg-emerald-900/30 text-emerald-100 px-4 py-3 mb-6 text-sm flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user