/* ===== Add / Edit Section + Manage list ===== */ /* ---------- Add / Edit Section ---------- */ function AddSectionView({ nav, route, notify, refresh }) { const editing = !!route.editId; const existing = editing ? window.sectionById(route.editId) : null; const isBuiltinEdit = editing && window.SectionStore.isBuiltin(route.editId) && !window.SectionStore.isCustom(route.editId); // built-ins have no `raw` — derive their full source so the form can edit them const initialRaw = existing ? (existing.raw || window.buildSectionHTML(existing)) : ''; const [name, setName] = useState(existing ? existing.name : ''); const [catId, setCatId] = useState(existing ? existing.category : ''); const [raw, setRaw] = useState(initialRaw); const [fileName, setFileName] = useState(existing && existing.fileName ? existing.fileName : ''); const [thumb, setThumb] = useState(existing && existing.thumb ? existing.thumb : ''); const [thumbOver, setThumbOver] = useState(false); const [mode, setMode] = useState(editing ? 'paste' : 'upload'); const [genReact, setGenReact] = useState(existing ? existing.frameworks.includes('react') : true); const [pro, setPro] = useState(existing ? !!existing.pro : false); const [over, setOver] = useState(false); const [touched, setTouched] = useState(false); const prevFrameRef = useRef(null); const errors = { name: !name.trim() ? 'Give your section a name.' : '', cat: !catId ? 'Pick a category.' : '', raw: !raw.trim() ? 'Upload or paste the section HTML.' : '' }; const valid = !errors.name && !errors.cat && !errors.raw; const readFile = (file) => { if (!file) return; if (!/\.(html?|txt)$/i.test(file.name) && file.type && !/html|text/.test(file.type)) { notify('Please choose an .html file', true); return; } const r = new FileReader(); r.onload = () => { setRaw(String(r.result)); setFileName(file.name); if (!name.trim()) setName(file.name.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase())); }; r.readAsText(file); }; const onDrop = (e) => { e.preventDefault(); setOver(false); readFile(e.dataTransfer.files[0]); }; // Read an image file, downscale to a card-sized thumbnail, store as a compact data URL const readThumb = (file) => { if (!file) return; if (!/^image\//.test(file.type) && !/\.(png|jpe?g|webp|gif|avif)$/i.test(file.name)) { notify('Please choose an image file (PNG, JPG, WebP…)', true); return; } const r = new FileReader(); r.onload = () => { const img = new Image(); img.onload = () => { const MAXW = 800; const scale = Math.min(1, MAXW / img.width); const w = Math.round(img.width * scale), h = Math.round(img.height * scale); const c = document.createElement('canvas'); c.width = w; c.height = h; c.getContext('2d').drawImage(img, 0, 0, w, h); let url; try { url = c.toDataURL('image/jpeg', 0.82); } catch (e) { url = String(r.result); } setThumb(url); }; img.onerror = () => notify('Could not read that image', true); img.src = String(r.result); }; r.readAsDataURL(file); }; const onThumbDrop = (e) => { e.preventDefault(); setThumbOver(false); readThumb(e.dataTransfer.files[0]); }; const previewSection = raw.trim() ? { id: 'preview-live', name: name || 'Preview', category: catId || 'headers', raw, height: 600 } : null; const save = () => { setTouched(true); if (!valid) { notify('Please complete the highlighted fields', true); return; } let height = 600; try { const doc = prevFrameRef.current && prevFrameRef.current.querySelector('iframe') && prevFrameRef.current.querySelector('iframe').contentDocument; if (doc) height = Math.max(doc.body.scrollHeight, 300); } catch (e) {} const sec = { id: editing ? existing.id : window.SectionStore.newId(), name: name.trim(), category: catId, tags: (existing && existing.tags ? existing.tags.filter(function(x){return x!=='custom';}) : []).concat([catId]), frameworks: ['html'].concat(genReact ? ['react'] : []), popularity: existing ? existing.popularity : 60, downloads: existing ? existing.downloads : 0, added: editing ? existing.added : new Date().toISOString().slice(0, 10), pro: pro, custom: isBuiltinEdit ? false : true, fileName: fileName || '', thumb: thumb || '', height: height, raw: raw }; if (editing) window.SectionStore.update(sec); else window.SectionStore.add(sec); refresh(); notify(editing ? (isBuiltinEdit ? 'Built-in section updated' : 'Section updated') : 'Section added to ' + window.catById(catId).label); nav({ view: 'section', sectionId: sec.id }); }; return (
{editing ? 'Edit' : 'Create'}

{editing ? 'Edit section' : 'Add new section'}

{isBuiltinEdit ? 'Edit this section’s name, category or code. Your changes are saved as an override you can restore anytime.' : 'Name it, choose a category and drop in your HTML — we\'ll organize it and make it available everywhere.'}

setName(e.target.value)} placeholder="e.g. Glass Pricing Table" /> {touched && errors.name &&

{errors.name}

}
{touched && errors.cat &&

{errors.cat}

}
{mode === 'upload' && !raw &&
{ e.preventDefault(); setOver(true); }} onDragLeave={() => setOver(false)} onDrop={onDrop}> readFile(e.target.files[0])} />

Drop your HTML file here

or browse — .html files up to a few MB

} {mode === 'upload' && raw &&
{fileName || 'section.html'} {(new Blob([raw]).size / 1024).toFixed(1)} KB · ready
} {mode === 'paste' &&