moved JS to document head
This commit is contained in:
+229
-189
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user