updated
This commit is contained in:
@@ -24,9 +24,19 @@ data:
|
|||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from fastapi import FastAPI, Response
|
from fastapi import FastAPI, Response
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from prometheus_client import Counter, Histogram, Gauge, generate_latest, CONTENT_TYPE_LATEST
|
from prometheus_client import Counter, Histogram, Gauge, generate_latest, CONTENT_TYPE_LATEST
|
||||||
|
|
||||||
APP = FastAPI()
|
APP = FastAPI()
|
||||||
|
|
||||||
|
# Add CORS middleware to allow cross-origin requests from Glance dashboards
|
||||||
|
APP.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["https://kisfenyo.dooplex.hu", "https://orsi.dooplex.hu", "https://glance-helper.dooplex.hu"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
# ================================
|
# ================================
|
||||||
# Időkép configuration
|
# Időkép configuration
|
||||||
@@ -1246,7 +1256,6 @@ data:
|
|||||||
}, ensure_ascii=False),
|
}, ensure_ascii=False),
|
||||||
media_type="application/json; charset=utf-8"
|
media_type="application/json; charset=utf-8"
|
||||||
)
|
)
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
|||||||
+182
-217
@@ -777,14 +777,13 @@ data:
|
|||||||
{{ $user := .Options.StringOr "user" "kisfenyo" }}
|
{{ $user := .Options.StringOr "user" "kisfenyo" }}
|
||||||
{{ $apiKey := .Options.StringOr "api_key" "" }}
|
{{ $apiKey := .Options.StringOr "api_key" "" }}
|
||||||
{{ $todos := .JSON.Array "todos" }}
|
{{ $todos := .JSON.Array "todos" }}
|
||||||
{{ $widgetId := "todo-kisfenyo" }}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.todo-widget { display: flex; flex-direction: column; gap: 8px; }
|
.todo-widget { display: flex; flex-direction: column; gap: 8px; }
|
||||||
.todo-add { display: flex; gap: 8px; align-items: center; }
|
.todo-add { display: flex; gap: 8px; align-items: center; }
|
||||||
.todo-add-btn {
|
.todo-add-btn {
|
||||||
background: none; border: none; color: inherit; opacity: 0.6;
|
background: none; border: none; color: inherit; opacity: 0.6;
|
||||||
cursor: pointer; font-size: 16px; padding: 4px 8px;
|
cursor: pointer; font-size: 18px; padding: 4px 8px;
|
||||||
transition: opacity 0.15s;
|
transition: opacity 0.15s;
|
||||||
}
|
}
|
||||||
.todo-add-btn:hover { opacity: 1; }
|
.todo-add-btn:hover { opacity: 1; }
|
||||||
@@ -825,29 +824,22 @@ data:
|
|||||||
.todo-item:hover .todo-delete { opacity: 0.5; }
|
.todo-item:hover .todo-delete { opacity: 0.5; }
|
||||||
.todo-delete:hover { opacity: 1 !important; color: #ef4444; }
|
.todo-delete:hover { opacity: 1 !important; color: #ef4444; }
|
||||||
.todo-empty { opacity: 0.5; font-size: 13px; padding: 8px 0; text-align: center; }
|
.todo-empty { opacity: 0.5; font-size: 13px; padding: 8px 0; text-align: center; }
|
||||||
.todo-loading { opacity: 0.5; font-size: 12px; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="todo-widget" id="{{ $widgetId }}">
|
<div class="todo-widget" data-api="{{ $apiBase }}" data-user="{{ $user }}" data-key="{{ $apiKey }}">
|
||||||
<div class="todo-add">
|
<div class="todo-add">
|
||||||
<span class="todo-add-btn" onclick="todoAddFocus_{{ $widgetId }}()">+</span>
|
<button class="todo-add-btn" type="button">+</button>
|
||||||
<input type="text" class="todo-add-input" id="{{ $widgetId }}-input"
|
<input type="text" class="todo-add-input" placeholder="Add a task">
|
||||||
placeholder="Add a task"
|
|
||||||
onkeydown="if(event.key==='Enter')todoAdd_{{ $widgetId }}(this.value)">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="todo-list" id="{{ $widgetId }}-list">
|
<div class="todo-list">
|
||||||
{{ if eq (len $todos) 0 }}
|
{{ if eq (len $todos) 0 }}
|
||||||
<div class="todo-empty">No tasks yet</div>
|
<div class="todo-empty">No tasks yet</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
{{ range $todos }}
|
{{ range $todos }}
|
||||||
{{ $id := .String "id" }}
|
<div class="todo-item" data-id="{{ .String "id" }}" data-text="{{ .String "text" }}" data-done="{{ .Bool "done" }}">
|
||||||
{{ $text := .String "text" }}
|
<div class="todo-checkbox {{ if .Bool "done" }}done{{ end }}"></div>
|
||||||
{{ $done := .Bool "done" }}
|
<span class="todo-text {{ if .Bool "done" }}done{{ end }}">{{ .String "text" }}</span>
|
||||||
<div class="todo-item" data-id="{{ $id }}" data-text="{{ $text }}" data-done="{{ $done }}">
|
<button class="todo-delete" type="button" title="Delete">🗑</button>
|
||||||
<div class="todo-checkbox {{ if $done }}done{{ end }}"
|
|
||||||
onclick="todoToggle_{{ $widgetId }}(this.parentElement)"></div>
|
|
||||||
<span class="todo-text {{ if $done }}done{{ end }}">{{ $text }}</span>
|
|
||||||
<button class="todo-delete" onclick="todoDelete_{{ $widgetId }}('{{ $id }}')" title="Delete">🗑</button>
|
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -856,76 +848,69 @@ data:
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
const API_BASE = '{{ $apiBase }}';
|
const widget = document.currentScript.previousElementSibling;
|
||||||
const USER = '{{ $user }}';
|
if (!widget || !widget.classList.contains('todo-widget')) return;
|
||||||
const API_KEY = '{{ $apiKey }}';
|
|
||||||
const WIDGET_ID = '{{ $widgetId }}';
|
const API = widget.dataset.api;
|
||||||
|
const USER = widget.dataset.user;
|
||||||
function getUrl(path) {
|
const KEY = widget.dataset.key;
|
||||||
return API_BASE + path + (API_KEY ? '?key=' + API_KEY : '');
|
|
||||||
|
function apiUrl(path) {
|
||||||
|
return API + path + (KEY ? '?key=' + KEY : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
window['todoAddFocus_' + WIDGET_ID] = function() {
|
const input = widget.querySelector('.todo-add-input');
|
||||||
document.getElementById(WIDGET_ID + '-input').focus();
|
const addBtn = widget.querySelector('.todo-add-btn');
|
||||||
};
|
|
||||||
|
async function addTodo() {
|
||||||
window['todoAdd_' + WIDGET_ID] = async function(text) {
|
const text = input.value.trim();
|
||||||
text = text.trim();
|
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
|
||||||
const input = document.getElementById(WIDGET_ID + '-input');
|
|
||||||
input.disabled = true;
|
input.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(getUrl('/userdata/' + USER + '/todos'), {
|
const r = await fetch(apiUrl('/userdata/' + USER + '/todos'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({ text: text, done: false })
|
body: JSON.stringify({text: text, done: false})
|
||||||
});
|
});
|
||||||
if (resp.ok) {
|
if (r.ok) location.reload();
|
||||||
input.value = '';
|
else console.error('Add failed:', await r.text());
|
||||||
location.reload();
|
} catch(e) { console.error('Add error:', e); }
|
||||||
} else {
|
input.disabled = false;
|
||||||
console.error('Failed to add todo:', await resp.text());
|
}
|
||||||
}
|
|
||||||
} catch (e) {
|
input.addEventListener('keydown', function(e) {
|
||||||
console.error('Error adding todo:', e);
|
if (e.key === 'Enter') addTodo();
|
||||||
} finally {
|
});
|
||||||
input.disabled = false;
|
addBtn.addEventListener('click', function() { input.focus(); });
|
||||||
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')) {
|
||||||
window['todoToggle_' + WIDGET_ID] = async function(itemEl) {
|
const id = item.dataset.id;
|
||||||
const id = itemEl.dataset.id;
|
try {
|
||||||
const text = itemEl.dataset.text;
|
const r = await fetch(apiUrl('/userdata/' + USER + '/todos/' + id), {
|
||||||
const newDone = itemEl.dataset.done !== 'true';
|
method: 'DELETE'
|
||||||
try {
|
});
|
||||||
const resp = await fetch(getUrl('/userdata/' + USER + '/todos/' + id), {
|
if (r.ok) location.reload();
|
||||||
method: 'PUT',
|
} catch(e) { console.error('Delete error:', e); }
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ text: text, done: newDone })
|
|
||||||
});
|
|
||||||
if (resp.ok) {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error toggling todo:', e);
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
window['todoDelete_' + WIDGET_ID] = async function(id) {
|
|
||||||
try {
|
|
||||||
const resp = await fetch(getUrl('/userdata/' + USER + '/todos/' + id), {
|
|
||||||
method: 'DELETE'
|
|
||||||
});
|
|
||||||
if (resp.ok) {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error deleting todo:', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
# Outline Notes iframe
|
# Outline Notes iframe
|
||||||
@@ -1026,7 +1011,6 @@ data:
|
|||||||
{{ $apiKey := .Options.StringOr "api_key" "" }}
|
{{ $apiKey := .Options.StringOr "api_key" "" }}
|
||||||
{{ $quote := .JSON.String "quote" }}
|
{{ $quote := .JSON.String "quote" }}
|
||||||
{{ $total := .JSON.Int "total" }}
|
{{ $total := .JSON.Int "total" }}
|
||||||
{{ $widgetId := "motiv-kisfenyo" }}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.motiv-widget { position: relative; }
|
.motiv-widget { position: relative; }
|
||||||
@@ -1042,11 +1026,11 @@ data:
|
|||||||
}
|
}
|
||||||
.motiv-gear {
|
.motiv-gear {
|
||||||
cursor: pointer; padding: 2px 6px; border-radius: 4px;
|
cursor: pointer; padding: 2px 6px; border-radius: 4px;
|
||||||
transition: opacity 0.15s, background 0.15s;
|
transition: opacity 0.15s, background 0.15s; border: none; background: none;
|
||||||
|
color: inherit; font-size: 11px;
|
||||||
}
|
}
|
||||||
.motiv-gear:hover { opacity: 1; background: rgba(255,255,255,0.1); }
|
.motiv-gear:hover { opacity: 1; background: rgba(255,255,255,0.1); }
|
||||||
|
|
||||||
/* Management Modal */
|
|
||||||
.motiv-modal-overlay {
|
.motiv-modal-overlay {
|
||||||
display: none; position: fixed; inset: 0;
|
display: none; position: fixed; inset: 0;
|
||||||
background: rgba(0,0,0,0.6); z-index: 99999;
|
background: rgba(0,0,0,0.6); z-index: 99999;
|
||||||
@@ -1068,87 +1052,58 @@ data:
|
|||||||
.motiv-modal-close {
|
.motiv-modal-close {
|
||||||
background: none; border: none; color: inherit; cursor: pointer;
|
background: none; border: none; color: inherit; cursor: pointer;
|
||||||
font-size: 18px; opacity: 0.6; padding: 4px 8px;
|
font-size: 18px; opacity: 0.6; padding: 4px 8px;
|
||||||
transition: opacity 0.15s;
|
|
||||||
}
|
}
|
||||||
.motiv-modal-close:hover { opacity: 1; }
|
.motiv-modal-close:hover { opacity: 1; }
|
||||||
.motiv-modal-body {
|
.motiv-modal-body {
|
||||||
flex: 1; overflow-y: auto; padding: 12px 16px;
|
flex: 1; overflow-y: auto; padding: 12px 16px;
|
||||||
display: flex; flex-direction: column; gap: 8px;
|
display: flex; flex-direction: column; gap: 8px;
|
||||||
}
|
}
|
||||||
.motiv-modal-add {
|
.motiv-modal-add { display: flex; gap: 8px; padding-bottom: 12px; border-bottom: 1px solid rgba(255,255,255,0.08); }
|
||||||
display: flex; gap: 8px; padding-bottom: 12px;
|
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
|
||||||
}
|
|
||||||
.motiv-modal-input {
|
.motiv-modal-input {
|
||||||
flex: 1; background: rgba(255,255,255,0.08);
|
flex: 1; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.1);
|
||||||
border: 1px solid rgba(255,255,255,0.1);
|
border-radius: 6px; padding: 10px 12px; color: inherit; font-size: 13px; outline: none;
|
||||||
border-radius: 6px; padding: 10px 12px;
|
|
||||||
color: inherit; font-size: 13px; outline: none;
|
|
||||||
}
|
|
||||||
.motiv-modal-input:focus {
|
|
||||||
background: rgba(255,255,255,0.12);
|
|
||||||
border-color: rgba(255,255,255,0.25);
|
|
||||||
}
|
}
|
||||||
|
.motiv-modal-input:focus { background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.25); }
|
||||||
.motiv-modal-btn {
|
.motiv-modal-btn {
|
||||||
background: rgba(96, 165, 250, 0.3); border: none;
|
background: rgba(96, 165, 250, 0.3); border: none; color: inherit; padding: 10px 16px;
|
||||||
color: inherit; padding: 10px 16px; border-radius: 6px;
|
border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500;
|
||||||
cursor: pointer; font-size: 13px; font-weight: 500;
|
|
||||||
transition: background 0.15s;
|
|
||||||
}
|
}
|
||||||
.motiv-modal-btn:hover { background: rgba(96, 165, 250, 0.5); }
|
.motiv-modal-btn:hover { background: rgba(96, 165, 250, 0.5); }
|
||||||
.motiv-modal-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
||||||
.motiv-list { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
|
.motiv-list { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
|
||||||
.motiv-list-title {
|
.motiv-list-title { font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; opacity: 0.5; margin-bottom: 4px; }
|
||||||
font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;
|
|
||||||
opacity: 0.5; margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.motiv-item {
|
.motiv-item {
|
||||||
display: flex; align-items: flex-start; gap: 10px;
|
display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px;
|
||||||
padding: 10px 12px; background: rgba(255,255,255,0.04);
|
background: rgba(255,255,255,0.04); border-radius: 8px;
|
||||||
border-radius: 8px; transition: background 0.15s;
|
|
||||||
}
|
}
|
||||||
.motiv-item:hover { background: rgba(255,255,255,0.08); }
|
.motiv-item:hover { background: rgba(255,255,255,0.08); }
|
||||||
.motiv-item-text {
|
.motiv-item-text { flex: 1; font-size: 13px; line-height: 1.4; opacity: 0.9; }
|
||||||
flex: 1; font-size: 13px; line-height: 1.4; opacity: 0.9;
|
|
||||||
}
|
|
||||||
.motiv-item-delete {
|
.motiv-item-delete {
|
||||||
background: none; border: none; color: inherit;
|
background: none; border: none; color: inherit; cursor: pointer;
|
||||||
cursor: pointer; opacity: 0.4; padding: 2px 6px;
|
opacity: 0.4; padding: 2px 6px; font-size: 13px;
|
||||||
font-size: 13px; transition: opacity 0.15s, color 0.15s;
|
|
||||||
}
|
}
|
||||||
.motiv-item-delete:hover { opacity: 1; color: #ef4444; }
|
.motiv-item-delete:hover { opacity: 1; color: #ef4444; }
|
||||||
.motiv-empty {
|
.motiv-empty { text-align: center; opacity: 0.5; padding: 20px; font-size: 13px; }
|
||||||
text-align: center; opacity: 0.5; padding: 20px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.motiv-loading { text-align: center; padding: 20px; opacity: 0.6; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="motiv-widget" id="{{ $widgetId }}">
|
<div class="motiv-widget" data-api="{{ $apiBase }}" data-user="{{ $user }}" data-key="{{ $apiKey }}">
|
||||||
<div class="motiv-quote">{{ $quote }}</div>
|
<div class="motiv-quote">{{ $quote }}</div>
|
||||||
<div class="motiv-meta">
|
<div class="motiv-meta">
|
||||||
<span>{{ $total }} quotes</span>
|
<span>{{ $total }} quotes</span>
|
||||||
<span class="motiv-gear" onclick="motivOpenModal_{{ $widgetId }}()" title="Manage quotes">⚙️ Manage</span>
|
<button class="motiv-gear" type="button" title="Manage quotes">⚙️ Manage</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="motiv-modal-overlay">
|
||||||
|
<div class="motiv-modal">
|
||||||
<!-- Management Modal (appended to body) -->
|
<div class="motiv-modal-header">
|
||||||
<div class="motiv-modal-overlay" id="{{ $widgetId }}-modal">
|
<span class="motiv-modal-title">Manage Motivation Quotes</span>
|
||||||
<div class="motiv-modal">
|
<button class="motiv-modal-close" type="button">×</button>
|
||||||
<div class="motiv-modal-header">
|
|
||||||
<span class="motiv-modal-title">Manage Motivation Quotes</span>
|
|
||||||
<button class="motiv-modal-close" onclick="motivCloseModal_{{ $widgetId }}()">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="motiv-modal-body">
|
|
||||||
<div class="motiv-modal-add">
|
|
||||||
<input type="text" class="motiv-modal-input" id="{{ $widgetId }}-new"
|
|
||||||
placeholder="Add a new motivational quote..."
|
|
||||||
onkeydown="if(event.key==='Enter')motivAdd_{{ $widgetId }}()">
|
|
||||||
<button class="motiv-modal-btn" onclick="motivAdd_{{ $widgetId }}()">Add</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="motiv-list-title">Your Quotes</div>
|
<div class="motiv-modal-body">
|
||||||
<div class="motiv-list" id="{{ $widgetId }}-list">
|
<div class="motiv-modal-add">
|
||||||
<div class="motiv-loading">Loading quotes...</div>
|
<input type="text" class="motiv-modal-input" placeholder="Add a new motivational quote...">
|
||||||
|
<button class="motiv-modal-btn" type="button">Add</button>
|
||||||
|
</div>
|
||||||
|
<div class="motiv-list-title">Your Quotes</div>
|
||||||
|
<div class="motiv-list"><div class="motiv-empty">Click Manage to load quotes</div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1156,111 +1111,121 @@ data:
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
const API_BASE = '{{ $apiBase }}';
|
const widget = document.currentScript.previousElementSibling;
|
||||||
const USER = '{{ $user }}';
|
if (!widget || !widget.classList.contains('motiv-widget')) return;
|
||||||
const API_KEY = '{{ $apiKey }}';
|
|
||||||
const WIDGET_ID = '{{ $widgetId }}';
|
|
||||||
|
|
||||||
let quotesCache = [];
|
const API = widget.dataset.api;
|
||||||
|
const USER = widget.dataset.user;
|
||||||
function getUrl(path) {
|
const KEY = widget.dataset.key;
|
||||||
return API_BASE + path + (API_KEY ? '?key=' + API_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 escapeHtml(str) {
|
function esc(s) {
|
||||||
const div = document.createElement('div');
|
const d = document.createElement('div');
|
||||||
div.textContent = str;
|
d.textContent = s;
|
||||||
return div.innerHTML;
|
return d.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadQuotes() {
|
async function loadQuotes() {
|
||||||
const listEl = document.getElementById(WIDGET_ID + '-list');
|
listEl.innerHTML = '<div class="motiv-empty">Loading...</div>';
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(getUrl('/userdata/' + USER + '/motivation').replace('?key=', ''));
|
const r = await fetch(API + '/userdata/' + USER + '/motivation');
|
||||||
const data = await resp.json();
|
const d = await r.json();
|
||||||
quotesCache = data.quotes || [];
|
quotes = d.quotes || [];
|
||||||
renderQuotes();
|
render();
|
||||||
} catch (e) {
|
} catch(e) {
|
||||||
listEl.innerHTML = '<div class="motiv-empty">Error loading quotes</div>';
|
listEl.innerHTML = '<div class="motiv-empty">Error loading</div>';
|
||||||
console.error('Error loading quotes:', e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderQuotes() {
|
function render() {
|
||||||
const listEl = document.getElementById(WIDGET_ID + '-list');
|
if (!quotes.length) {
|
||||||
if (quotesCache.length === 0) {
|
listEl.innerHTML = '<div class="motiv-empty">No quotes yet</div>';
|
||||||
listEl.innerHTML = '<div class="motiv-empty">No quotes yet. Add some above!</div>';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listEl.innerHTML = quotesCache.map((q, i) =>
|
listEl.innerHTML = quotes.map((q, i) =>
|
||||||
'<div class="motiv-item">' +
|
'<div class="motiv-item" data-index="' + i + '">' +
|
||||||
'<span class="motiv-item-text">' + escapeHtml(q) + '</span>' +
|
'<span class="motiv-item-text">' + esc(q) + '</span>' +
|
||||||
'<button class="motiv-item-delete" onclick="motivDelete_' + WIDGET_ID + '(' + i + ')" title="Delete">🗑</button>' +
|
'<button class="motiv-item-delete" type="button">🗑</button>' +
|
||||||
'</div>'
|
'</div>'
|
||||||
).join('');
|
).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
window['motivOpenModal_' + WIDGET_ID] = function() {
|
gearBtn.addEventListener('click', function() {
|
||||||
const modal = document.getElementById(WIDGET_ID + '-modal');
|
|
||||||
modal.classList.add('active');
|
modal.classList.add('active');
|
||||||
loadQuotes();
|
loadQuotes();
|
||||||
// Close on overlay click
|
});
|
||||||
modal.onclick = function(e) {
|
|
||||||
if (e.target === modal) window['motivCloseModal_' + WIDGET_ID]();
|
closeBtn.addEventListener('click', function() {
|
||||||
};
|
modal.classList.remove('active');
|
||||||
// Close on Escape
|
});
|
||||||
document.addEventListener('keydown', function handler(e) {
|
|
||||||
if (e.key === 'Escape') {
|
modal.addEventListener('click', function(e) {
|
||||||
window['motivCloseModal_' + WIDGET_ID]();
|
if (e.target === modal) modal.classList.remove('active');
|
||||||
document.removeEventListener('keydown', handler);
|
});
|
||||||
}
|
|
||||||
});
|
document.addEventListener('keydown', function(e) {
|
||||||
};
|
if (e.key === 'Escape' && modal.classList.contains('active')) {
|
||||||
|
modal.classList.remove('active');
|
||||||
window['motivCloseModal_' + WIDGET_ID] = function() {
|
}
|
||||||
document.getElementById(WIDGET_ID + '-modal').classList.remove('active');
|
});
|
||||||
};
|
|
||||||
|
async function addQuote() {
|
||||||
window['motivAdd_' + WIDGET_ID] = async function() {
|
|
||||||
const input = document.getElementById(WIDGET_ID + '-new');
|
|
||||||
const text = input.value.trim();
|
const text = input.value.trim();
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
|
||||||
input.disabled = true;
|
input.disabled = true;
|
||||||
|
addBtn.disabled = true;
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(getUrl('/userdata/' + USER + '/motivation'), {
|
const r = await fetch(apiUrl('/userdata/' + USER + '/motivation'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({ text: text })
|
body: JSON.stringify({text: text})
|
||||||
});
|
});
|
||||||
if (resp.ok) {
|
if (r.ok) {
|
||||||
input.value = '';
|
input.value = '';
|
||||||
const data = await resp.json();
|
const d = await r.json();
|
||||||
quotesCache = data.quotes || [];
|
quotes = d.quotes || [];
|
||||||
renderQuotes();
|
render();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch(e) { console.error(e); }
|
||||||
console.error('Error adding quote:', e);
|
input.disabled = false;
|
||||||
} finally {
|
addBtn.disabled = false;
|
||||||
input.disabled = false;
|
input.focus();
|
||||||
input.focus();
|
}
|
||||||
}
|
|
||||||
};
|
addBtn.addEventListener('click', addQuote);
|
||||||
|
input.addEventListener('keydown', function(e) {
|
||||||
window['motivDelete_' + WIDGET_ID] = async function(index) {
|
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 {
|
try {
|
||||||
const resp = await fetch(getUrl('/userdata/' + USER + '/motivation/' + index), {
|
const r = await fetch(apiUrl('/userdata/' + USER + '/motivation/' + idx), {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
if (resp.ok) {
|
if (r.ok) {
|
||||||
const data = await resp.json();
|
const d = await r.json();
|
||||||
quotesCache = data.quotes || [];
|
quotes = d.quotes || [];
|
||||||
renderQuotes();
|
render();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch(e) { console.error(e); }
|
||||||
console.error('Error deleting quote:', e);
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user