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>
|
</script>
|
||||||
|
|
||||||
branding:
|
branding:
|
||||||
@@ -845,74 +1074,6 @@ data:
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</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
|
# Outline Notes iframe
|
||||||
- type: iframe
|
- type: iframe
|
||||||
source: https://outline.dooplex.hu/collection/dooplex-server-iTAZn04AaR/recent
|
source: https://outline.dooplex.hu/collection/dooplex-server-iTAZn04AaR/recent
|
||||||
@@ -1109,127 +1270,6 @@ data:
|
|||||||
</div>
|
</div>
|
||||||
</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)
|
# Calendar Events Widget (Családi only)
|
||||||
- type: custom-api
|
- type: custom-api
|
||||||
title: Family Calendar
|
title: Family Calendar
|
||||||
|
|||||||
Reference in New Issue
Block a user