moved JS to document head

This commit is contained in:
2026-02-01 21:20:11 +01:00
parent 08365d9997
commit 18c47b6ff7
+229 -189
View File
@@ -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 = '<div class="motiv-empty">Loading...</div>';
try {
const r = await fetch(API + '/userdata/' + USER + '/motivation');
const d = await r.json();
quotes = d.quotes || [];
renderQuotes();
} catch(e) {
listEl.innerHTML = '<div class="motiv-empty">Error loading</div>';
console.error('Motiv load error:', e);
}
}
function renderQuotes() {
if (!quotes.length) {
listEl.innerHTML = '<div class="motiv-empty">No quotes yet</div>';
return;
}
listEl.innerHTML = quotes.map(function(q, i) {
return '<div class="motiv-item" data-index="' + i + '">' +
'<span class="motiv-item-text">' + esc(q) + '</span>' +
'<button class="motiv-item-delete" type="button">🗑</button>' +
'</div>';
}).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);
})();
</script>
branding:
@@ -845,74 +1074,6 @@ data:
{{ end }}
</div>
</div>
<script>
(function() {
const widget = document.currentScript.previousElementSibling;
if (!widget || !widget.classList.contains('todo-widget')) return;
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('Add failed:', await r.text());
} catch(e) { console.error('Add error:', e); }
input.disabled = false;
}
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') addTodo();
});
addBtn.addEventListener('click', function() { 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('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('Delete error:', e); }
}
});
})();
</script>
# Outline Notes iframe
- type: iframe
source: https://outline.dooplex.hu/collection/dooplex-server-iTAZn04AaR/recent
@@ -1109,127 +1270,6 @@ data:
</div>
</div>
<script>
(function() {
const widget = document.currentScript.previousElementSibling;
if (!widget || !widget.classList.contains('motiv-widget')) return;
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');
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 = '<div class="motiv-empty">Loading...</div>';
try {
const r = await fetch(API + '/userdata/' + USER + '/motivation');
const d = await r.json();
quotes = d.quotes || [];
render();
} catch(e) {
listEl.innerHTML = '<div class="motiv-empty">Error loading</div>';
console.error(e);
}
}
function render() {
if (!quotes.length) {
listEl.innerHTML = '<div class="motiv-empty">No quotes yet</div>';
return;
}
listEl.innerHTML = quotes.map((q, i) =>
'<div class="motiv-item" data-index="' + i + '">' +
'<span class="motiv-item-text">' + esc(q) + '</span>' +
'<button class="motiv-item-delete" type="button">🗑</button>' +
'</div>'
).join('');
}
gearBtn.addEventListener('click', function() {
modal.classList.add('active');
loadQuotes();
});
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;
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 || [];
render();
}
} catch(e) { console.error(e); }
input.disabled = false;
addBtn.disabled = false;
input.focus();
}
addBtn.addEventListener('click', addQuote);
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') 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 || [];
render();
}
} catch(e) { console.error(e); }
});
})();
</script>
# Calendar Events Widget (Családi only)
- type: custom-api
title: Family Calendar