diff --git a/glance-system/glance-kisfenyo.yaml b/glance-system/glance-kisfenyo.yaml index 1227147..591a1c5 100644 --- a/glance-system/glance-kisfenyo.yaml +++ b/glance-system/glance-kisfenyo.yaml @@ -304,6 +304,235 @@ data: } }); })(); + + // ================================ + // Todo Widget Controller + // ================================ + (function() { + function initTodoWidgets() { + document.querySelectorAll('.todo-widget').forEach(function(widget) { + if (widget.dataset.initialized) return; + widget.dataset.initialized = 'true'; + + const API = widget.dataset.api; + const USER = widget.dataset.user; + const KEY = widget.dataset.key; + + function apiUrl(path) { + return API + path + (KEY ? '?key=' + KEY : ''); + } + + const input = widget.querySelector('.todo-add-input'); + const addBtn = widget.querySelector('.todo-add-btn'); + + async function addTodo() { + const text = input.value.trim(); + if (!text) return; + input.disabled = true; + try { + const r = await fetch(apiUrl('/userdata/' + USER + '/todos'), { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({text: text, done: false}) + }); + if (r.ok) location.reload(); + else console.error('Todo add failed:', await r.text()); + } catch(e) { console.error('Todo add error:', e); } + input.disabled = false; + } + + if (input) { + input.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { e.preventDefault(); addTodo(); } + }); + } + if (addBtn) { + addBtn.addEventListener('click', function() { + if (input.value.trim()) addTodo(); + else input.focus(); + }); + } + + widget.addEventListener('click', async function(e) { + const item = e.target.closest('.todo-item'); + if (!item) return; + + if (e.target.closest('.todo-checkbox')) { + const id = item.dataset.id; + const text = item.dataset.text; + const newDone = item.dataset.done !== 'true'; + try { + const r = await fetch(apiUrl('/userdata/' + USER + '/todos/' + id), { + method: 'PUT', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({text: text, done: newDone}) + }); + if (r.ok) location.reload(); + } catch(e) { console.error('Todo toggle error:', e); } + } + + if (e.target.closest('.todo-delete')) { + const id = item.dataset.id; + try { + const r = await fetch(apiUrl('/userdata/' + USER + '/todos/' + id), { + method: 'DELETE' + }); + if (r.ok) location.reload(); + } catch(e) { console.error('Todo delete error:', e); } + } + }); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initTodoWidgets); + } else { + initTodoWidgets(); + } + // Also run after a delay for dynamically loaded content + setTimeout(initTodoWidgets, 500); + setTimeout(initTodoWidgets, 1500); + })(); + + // ================================ + // Motivation Widget Controller + // ================================ + (function() { + function initMotivWidgets() { + document.querySelectorAll('.motiv-widget').forEach(function(widget) { + if (widget.dataset.initialized) return; + widget.dataset.initialized = 'true'; + + const API = widget.dataset.api; + const USER = widget.dataset.user; + const KEY = widget.dataset.key; + + const modal = widget.querySelector('.motiv-modal-overlay'); + const listEl = widget.querySelector('.motiv-list'); + const input = widget.querySelector('.motiv-modal-input'); + const addBtn = widget.querySelector('.motiv-modal-btn'); + const closeBtn = widget.querySelector('.motiv-modal-close'); + const gearBtn = widget.querySelector('.motiv-gear'); + + if (!modal || !listEl || !gearBtn) return; + + let quotes = []; + + function apiUrl(path) { + return API + path + (KEY ? '?key=' + KEY : ''); + } + + function esc(s) { + const d = document.createElement('div'); + d.textContent = s; + return d.innerHTML; + } + + async function loadQuotes() { + listEl.innerHTML = '
Loading...
'; + try { + const r = await fetch(API + '/userdata/' + USER + '/motivation'); + const d = await r.json(); + quotes = d.quotes || []; + renderQuotes(); + } catch(e) { + listEl.innerHTML = '
Error loading
'; + console.error('Motiv load error:', e); + } + } + + function renderQuotes() { + if (!quotes.length) { + listEl.innerHTML = '
No quotes yet
'; + return; + } + listEl.innerHTML = quotes.map(function(q, i) { + return '
' + + '' + esc(q) + '' + + '' + + '
'; + }).join(''); + } + + gearBtn.addEventListener('click', function() { + modal.classList.add('active'); + loadQuotes(); + }); + + if (closeBtn) { + closeBtn.addEventListener('click', function() { + modal.classList.remove('active'); + }); + } + + modal.addEventListener('click', function(e) { + if (e.target === modal) modal.classList.remove('active'); + }); + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape' && modal.classList.contains('active')) { + modal.classList.remove('active'); + } + }); + + async function addQuote() { + const text = input.value.trim(); + if (!text) return; + input.disabled = true; + if (addBtn) addBtn.disabled = true; + try { + const r = await fetch(apiUrl('/userdata/' + USER + '/motivation'), { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({text: text}) + }); + if (r.ok) { + input.value = ''; + const d = await r.json(); + quotes = d.quotes || []; + renderQuotes(); + } + } catch(e) { console.error('Motiv add error:', e); } + input.disabled = false; + if (addBtn) addBtn.disabled = false; + input.focus(); + } + + if (addBtn) addBtn.addEventListener('click', addQuote); + if (input) { + input.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { e.preventDefault(); addQuote(); } + }); + } + + listEl.addEventListener('click', async function(e) { + const del = e.target.closest('.motiv-item-delete'); + if (!del) return; + const item = del.closest('.motiv-item'); + const idx = item.dataset.index; + try { + const r = await fetch(apiUrl('/userdata/' + USER + '/motivation/' + idx), { + method: 'DELETE' + }); + if (r.ok) { + const d = await r.json(); + quotes = d.quotes || []; + renderQuotes(); + } + } catch(e) { console.error('Motiv delete error:', e); } + }); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initMotivWidgets); + } else { + initMotivWidgets(); + } + // Also run after a delay for dynamically loaded content + setTimeout(initMotivWidgets, 500); + setTimeout(initMotivWidgets, 1500); + })(); branding: @@ -845,74 +1074,6 @@ data: {{ end }} - - # Outline Notes iframe - type: iframe source: https://outline.dooplex.hu/collection/dooplex-server-iTAZn04AaR/recent @@ -1109,127 +1270,6 @@ data: - - - # Calendar Events Widget (Családi only) - type: custom-api title: Family Calendar