/** * Shared utility functions used across the app layout. * Previously defined inline in layout.html. */ /** * Confirm and execute a resource delete (soft or hard). * Called from the delete confirmation modal. */ async function confirmResourceDelete(mode) { const resourceDeleteModal = bootstrap.Modal.getInstance( document.getElementById('resourceDeleteModal')); resourceDeleteModal.hide(); try { const res = await fetch(`/resource/${_rdmCategory}/${_rdmSlug}/delete`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({mode}), }); const data = await res.json(); if (data.status === 'ok') { const card = document.getElementById(`card-${_rdmSlug}`); if (card) card.remove(); } else { alert('Delete failed: ' + (data.error || 'unknown error')); } } catch (e) { alert('Delete failed: ' + e); } } /** * Regenerate tags for a single resource via the LLM queue. * Called from detail page tag regeneration buttons. */ function regenerateTags(category, slug) { const btn = document.getElementById('regenerate-tags-btn'); if (!btn) return; const origText = btn.innerHTML; btn.disabled = true; btn.innerHTML = 'Regenerating\u2026'; fetch(`/api/${category}/${slug}/regenerate_tags`, { method: 'POST', headers: {'X-Requested-With': 'XMLHttpRequest'} }) .then(r => r.json().then(d => ({ok: r.ok, data: d}))) .then(({ok, data}) => { if (ok && data.success) { location.reload(); } else { alert('Regeneration failed: ' + (data.error || 'Unknown error')); btn.disabled = false; btn.innerHTML = origText; } }) .catch(err => { alert('Regeneration failed: ' + err); btn.disabled = false; btn.innerHTML = origText; }); } /** * Initialize the JSON editor modal for a resource detail page. * Provides simple form view and raw JSON (advanced) editing. */ function initJsonEditor(saveUrl) { const jsonModal = document.getElementById('jsonEditorModal'); if (!jsonModal) return; const textarea = document.getElementById('json-editor-textarea'); const errBox = document.getElementById('json-editor-error'); const simplePanel = document.getElementById('json-simple-panel'); const advancedPanel = document.getElementById('json-advanced-panel'); const simpleTab = document.getElementById('json-simple-tab'); const advancedTab = document.getElementById('json-advanced-tab'); let activeTab = 'simple'; function buildSimpleForm(data) { simplePanel.innerHTML = ''; for (const [key, value] of Object.entries(data)) { const row = document.createElement('div'); row.className = 'row mb-2 align-items-start'; const labelCol = document.createElement('div'); labelCol.className = 'col-sm-3 col-form-label fw-semibold text-capitalize small pt-1'; labelCol.textContent = key.replace(/_/g, ' '); const inputCol = document.createElement('div'); inputCol.className = 'col-sm-9'; let el; if (typeof value === 'boolean') { const wrap = document.createElement('div'); wrap.className = 'form-check mt-2'; el = document.createElement('input'); el.type = 'checkbox'; el.className = 'form-check-input'; el.checked = value; el.dataset.dtype = 'boolean'; wrap.appendChild(el); inputCol.appendChild(wrap); } else if (typeof value === 'number') { el = document.createElement('input'); el.type = 'number'; el.step = 'any'; el.className = 'form-control form-control-sm'; el.value = value; el.dataset.dtype = 'number'; inputCol.appendChild(el); } else if (typeof value === 'string') { if (value.length > 80) { el = document.createElement('textarea'); el.className = 'form-control form-control-sm'; el.rows = 3; } else { el = document.createElement('input'); el.type = 'text'; el.className = 'form-control form-control-sm'; } el.value = value; el.dataset.dtype = 'string'; inputCol.appendChild(el); } else { el = document.createElement('textarea'); el.className = 'form-control form-control-sm font-monospace'; const lines = JSON.stringify(value, null, 2).split('\n'); el.rows = Math.min(10, lines.length + 1); el.value = JSON.stringify(value, null, 2); el.dataset.dtype = 'json'; inputCol.appendChild(el); } el.dataset.key = key; row.appendChild(labelCol); row.appendChild(inputCol); simplePanel.appendChild(row); } } function readSimpleForm() { const result = {}; simplePanel.querySelectorAll('[data-key]').forEach(el => { const key = el.dataset.key; const dtype = el.dataset.dtype; if (dtype === 'boolean') result[key] = el.checked; else if (dtype === 'number') { const n = parseFloat(el.value); result[key] = isNaN(n) ? el.value : n; } else if (dtype === 'json') { try { result[key] = JSON.parse(el.value); } catch { result[key] = el.value; } } else result[key] = el.value; }); return result; } simpleTab.addEventListener('click', () => { errBox.classList.add('d-none'); let data; try { data = JSON.parse(textarea.value); } catch (e) { errBox.textContent = 'Cannot switch: invalid JSON \u2014 ' + e.message; errBox.classList.remove('d-none'); return; } buildSimpleForm(data); simplePanel.classList.remove('d-none'); advancedPanel.classList.add('d-none'); simpleTab.classList.add('active'); advancedTab.classList.remove('active'); activeTab = 'simple'; }); advancedTab.addEventListener('click', () => { textarea.value = JSON.stringify(readSimpleForm(), null, 2); advancedPanel.classList.remove('d-none'); simplePanel.classList.add('d-none'); advancedTab.classList.add('active'); simpleTab.classList.remove('active'); activeTab = 'advanced'; }); jsonModal.addEventListener('show.bs.modal', () => { const raw = document.getElementById('json-raw-data').textContent; let data; try { data = JSON.parse(raw); } catch { data = {}; } buildSimpleForm(data); textarea.value = JSON.stringify(data, null, 2); simplePanel.classList.remove('d-none'); advancedPanel.classList.add('d-none'); simpleTab.classList.add('active'); advancedTab.classList.remove('active'); activeTab = 'simple'; errBox.classList.add('d-none'); }); document.getElementById('json-save-btn').addEventListener('click', async () => { errBox.classList.add('d-none'); let parsed; if (activeTab === 'simple') { parsed = readSimpleForm(); } else { try { parsed = JSON.parse(textarea.value); } catch (e) { errBox.textContent = 'Invalid JSON: ' + e.message; errBox.classList.remove('d-none'); return; } } const fd = new FormData(); fd.append('json_data', JSON.stringify(parsed)); const resp = await fetch(saveUrl, { method: 'POST', body: fd }); const result = await resp.json(); if (result.success) { bootstrap.Modal.getInstance(jsonModal).hide(); location.reload(); } else { errBox.textContent = result.error || 'Save failed.'; errBox.classList.remove('d-none'); } }); }