diff --git a/admelist.html b/admelist.html new file mode 100644 index 0000000..747b434 --- /dev/null +++ b/admelist.html @@ -0,0 +1,53 @@ + + + + + + Edit List - GD fedl + + + +
+

fedl

+ +
+
+
+
+
+

Editor

+

Manage the live list

+

Loading list data...

+
+
+ + + +
+
+ +
+ + + + + + + + + + + +
#LevelTitleVideo URLDelete
+
+
+
+ + + diff --git a/app.js b/app.js index 9384475..bb58bdf 100644 --- a/app.js +++ b/app.js @@ -350,6 +350,190 @@ }); } + if(page==='admelist'){ + const statusEl = qs('admin-status'); + const tbody = qs('admin-list-body'); + const addBtn = qs('add-row'); + const saveBtn = qs('save-list'); + const searchEl = qs('admin-search'); + let items = []; + + function setStatus(message, isError){ + if(!statusEl) return; + statusEl.textContent = message; + statusEl.classList.toggle('error-text', !!isError); + } + + function filteredItems(){ + const query = (searchEl && searchEl.value || '').trim().toLowerCase(); + if(!query) return items; + return items.filter(item=>{ + return [item.level, item.position, item.title, item.url].some(value=> + String(value || '').toLowerCase().includes(query) + ); + }); + } + + function normalizePositions(){ + let position = 1; + items.forEach(item=>{ + if(item._isDraft){ + item.position = ''; + return; + } + item.position = String(position); + position += 1; + }); + } + + function moveItemToPosition(index, rawPosition){ + if(!items[index]) return; + const parsedPosition = Number(rawPosition); + if(!Number.isFinite(parsedPosition) || parsedPosition < 1) return; + const nextPosition = Math.max(1, parsedPosition); + const [item] = items.splice(index, 1); + item._isDraft = false; + const drafts = items.filter(entry=>entry._isDraft); + const ranked = items.filter(entry=>!entry._isDraft); + const targetIndex = Math.min(ranked.length, nextPosition - 1); + ranked.splice(targetIndex, 0, item); + items = drafts.concat(ranked); + normalizePositions(); + } + + function renderAdminTable(){ + const rows = filteredItems(); + tbody.innerHTML = ''; + rows.forEach(item=>{ + const actualIndex = items.indexOf(item); + const tr = document.createElement('tr'); + tr.innerHTML = ` + + + + + + `; + tbody.appendChild(tr); + }); + if(!rows.length){ + const tr = document.createElement('tr'); + tr.innerHTML = 'No rows match your search.'; + tbody.appendChild(tr); + } + } + + function saveItems(){ + const hasUnplacedDraft = items.some(item=>{ + const hasContent = String(item.title || '').trim() || String(item.url || '').trim() || String(item.level || '').trim(); + return item._isDraft && hasContent; + }); + if(hasUnplacedDraft){ + setStatus('Give each new row a number before saving.', true); + return Promise.resolve(); + } + items = items + .map(item=>({ + level: String(item.level || '').trim() || 'new', + position: String(item.position || '').trim(), + title: String(item.title || '').trim(), + url: String(item.url || '').trim() + })) + .filter(item=>item.title); + normalizePositions(); + return fetch(liveApiUrl, { + method:'PUT', + headers:{'Content-Type':'application/json'}, + body: JSON.stringify({text: formatData(items)}) + }).then(r=>{ + if(!r.ok) throw new Error('Save failed'); + clearItemsCache(); + renderAdminTable(); + setStatus('Saved. Live pages update automatically.'); + }).catch(err=>{ + console.error(err); + setStatus('Could not save. Check the live server endpoint.', true); + }); + } + + function loadAdmin(){ + loadItems().then(loaded=>{ + items = loaded.slice().sort((a,b)=>(Number(a.position) || 0) - (Number(b.position) || 0)).map(item=>({ + level: item.level, + position: item.position, + title: item.title, + url: item.url, + _isDraft: false + })); + normalizePositions(); + renderAdminTable(); + setStatus('Connected to live list data.'); + }).catch(err=>{ + console.error(err); + setStatus('Could not load list data.', true); + }); + } + + tbody.addEventListener('input', function(event){ + const target = event.target; + const field = target.getAttribute('data-field'); + const index = Number(target.getAttribute('data-index')); + if(!field || Number.isNaN(index) || !items[index]) return; + if(field === 'position'){ + moveItemToPosition(index, target.value); + renderAdminTable(); + }else{ + items[index][field] = target.value; + } + setStatus('Unsaved changes'); + }); + + tbody.addEventListener('click', function(event){ + const deleteButton = event.target.closest('[data-delete]'); + if(!deleteButton) return; + const deleteIndex = deleteButton.getAttribute('data-delete'); + if(deleteIndex == null) return; + const index = Number(deleteIndex); + if(Number.isNaN(index)) return; + items.splice(index, 1); + normalizePositions(); + renderAdminTable(); + setStatus('Row removed. Save when ready.'); + }); + + addBtn.addEventListener('click', function(){ + items.unshift({level:'new', position:'', title:'', url:'', _isDraft:true}); + normalizePositions(); + renderAdminTable(); + setStatus('New row added at the top. Give it a number when you want to place it.'); + }); + + saveBtn.addEventListener('click', function(){ + saveItems(); + }); + + if(searchEl){ + searchEl.addEventListener('input', renderAdminTable); + } + + bindLiveUpdates(); + onLiveUpdate(function(updatedItems){ + items = updatedItems.slice().sort((a,b)=>(Number(a.position) || 0) - (Number(b.position) || 0)).map(item=>({ + level: item.level, + position: item.position, + title: item.title, + url: item.url, + _isDraft: false + })); + normalizePositions(); + renderAdminTable(); + setStatus('List reloaded from live server.'); + }); + + loadAdmin(); + } + // Utility function escapeHtml(s){return String(s).replace(/[&<>"']/g, c=>({"&":"&","<":"<",">":">","\"":""","'":"'"})[c])} + function escapeAttr(s){return escapeHtml(String(s == null ? '' : s))} })();