Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
1779 lines
92 KiB
HTML
1779 lines
92 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||
<meta http-equiv="refresh" content="0;url=/games/aufgaben/aufgaben.html">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Aufgaben – Vanilla – xXx Sphere</title>
|
||
<link rel="stylesheet" href="/css/variables.css">
|
||
<link rel="stylesheet" href="/css/style.css">
|
||
<style>
|
||
/* ── Section ── */
|
||
.section + .section { margin-top: 2.5rem; }
|
||
.section-header {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
margin-bottom: 0.6rem; padding-bottom: 0.5rem;
|
||
border-bottom: 1px solid var(--color-secondary);
|
||
}
|
||
.section-title { font-size: 1.1rem; font-weight: 600; color: var(--color-primary); margin: 0; }
|
||
.section-actions { display: flex; align-items: center; gap: 0.5rem; }
|
||
|
||
/* ── Buttons ── */
|
||
.btn-add {
|
||
display: flex; align-items: center; gap: 0.4rem;
|
||
background: var(--color-primary); color: #fff;
|
||
border: none; border-radius: 6px; padding: 0.4rem 0.85rem;
|
||
font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: background 0.15s;
|
||
}
|
||
.btn-add:hover { background: #c73652; }
|
||
.btn-action {
|
||
background: var(--color-secondary); color: var(--color-text);
|
||
border: none; border-radius: 6px; padding: 0.4rem 0.85rem;
|
||
font-size: 0.85rem; font-weight: 600; cursor: pointer;
|
||
transition: background 0.15s, color 0.15s, opacity 0.15s;
|
||
}
|
||
.btn-action:disabled { opacity: 0.35; cursor: default; }
|
||
.btn-action:not(:disabled):hover { background: var(--color-primary); color: #fff; }
|
||
.btn-action-danger:not(:disabled):hover { background: rgba(233,69,96,0.18); color: var(--color-primary); }
|
||
.btn-sub-add {
|
||
background: none; border: 1px solid var(--color-secondary); border-radius: 5px;
|
||
color: var(--color-muted); font-size: 0.75rem; padding: 0.15rem 0.5rem;
|
||
cursor: pointer; transition: border-color 0.15s, color 0.15s;
|
||
}
|
||
.btn-sub-add:hover { border-color: var(--color-primary); color: var(--color-primary); }
|
||
|
||
.action-error { font-size: 0.82rem; color: var(--color-primary); min-height: 1.1em; margin-bottom: 0.4rem; }
|
||
|
||
/* ── Paging ── */
|
||
.paging {
|
||
display: flex; align-items: center; justify-content: center;
|
||
gap: 0.75rem; margin-top: 1rem;
|
||
}
|
||
.paging button {
|
||
background: var(--color-secondary); color: var(--color-text);
|
||
border: none; border-radius: 6px; padding: 0.4rem 0.9rem;
|
||
font-size: 0.85rem; cursor: pointer; transition: background 0.15s;
|
||
}
|
||
.paging button:hover:not(:disabled) { background: var(--color-primary); }
|
||
.paging button:disabled { opacity: 0.35; cursor: default; }
|
||
.paging .page-info { font-size: 0.85rem; color: var(--color-muted); }
|
||
|
||
.empty, .loading { color: var(--color-muted); font-size: 0.9rem; padding: 0.75rem 0; }
|
||
|
||
/* ── Gruppe card ── */
|
||
.gruppe-list { display: flex; flex-direction: column; gap: 0.75rem; }
|
||
.gruppe-card {
|
||
background: var(--color-card); border: 1px solid var(--color-secondary);
|
||
border-radius: 10px; overflow: hidden; transition: border-color 0.15s;
|
||
}
|
||
.gruppe-card.selected { border-color: var(--color-primary); background: rgba(233,69,96,0.05); }
|
||
|
||
.gruppe-header {
|
||
display: flex; align-items: center; gap: 0.9rem;
|
||
padding: 0.85rem 1rem; cursor: pointer; user-select: none;
|
||
}
|
||
.gruppe-img {
|
||
width: 48px; height: 48px; border-radius: 7px;
|
||
object-fit: cover; flex-shrink: 0;
|
||
}
|
||
.gruppe-img-placeholder {
|
||
width: 48px; height: 48px; border-radius: 7px;
|
||
background: var(--color-secondary);
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 1.3rem; flex-shrink: 0; color: var(--color-muted);
|
||
}
|
||
.gruppe-meta { flex: 1; min-width: 0; }
|
||
.gruppe-name {
|
||
font-size: 0.95rem; font-weight: 600; color: var(--color-text);
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.gruppe-info { font-size: 0.75rem; color: var(--color-muted); margin-top: 0.2rem; }
|
||
.gruppe-badges { display: flex; gap: 0.3rem; margin-top: 0.25rem; flex-wrap: wrap; }
|
||
.gruppe-badge {
|
||
font-size: 0.65rem; padding: 0.1rem 0.4rem; border-radius: 20px;
|
||
background: rgba(255,255,255,0.07); color: var(--color-muted);
|
||
}
|
||
.gruppe-badge-private { background: rgba(233,69,96,0.15); color: var(--color-primary); }
|
||
.gruppe-badge-public { background: rgba(46,204,113,0.15); color: var(--color-success); }
|
||
.gruppe-toggle { font-size: 0.75rem; color: var(--color-muted); flex-shrink: 0; transition: transform 0.2s; }
|
||
.gruppe-card.open .gruppe-toggle { transform: rotate(90deg); }
|
||
|
||
/* ── Gruppe body ── */
|
||
.gruppe-body { border-top: 1px solid var(--color-secondary); padding: 1rem 1rem 0.75rem; }
|
||
.gruppe-desc { font-size: 0.82rem; color: var(--color-muted); margin-bottom: 0.85rem; line-height: 1.5; }
|
||
|
||
.sub-section + .sub-section { margin-top: 0.85rem; }
|
||
.sub-section-header {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
margin-bottom: 0.4rem;
|
||
}
|
||
.sub-section-title {
|
||
font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em;
|
||
text-transform: uppercase; color: var(--color-primary);
|
||
}
|
||
|
||
/* ── Items ── */
|
||
.item-list { display: flex; flex-direction: column; gap: 0.3rem; }
|
||
.item {
|
||
border-radius: 6px; background: var(--color-secondary);
|
||
overflow: hidden;
|
||
}
|
||
.item-row {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
gap: 0.75rem; padding: 0.35rem 0.6rem;
|
||
cursor: pointer; user-select: none;
|
||
transition: background 0.12s;
|
||
}
|
||
.item-row:hover { background: rgba(255,255,255,0.04); }
|
||
.item.open .item-row { background: rgba(233,69,96,0.08); }
|
||
.item-text {
|
||
color: var(--color-text); flex: 1; min-width: 0; font-size: 0.82rem;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.item-badges { display: flex; gap: 0.35rem; flex-shrink: 0; }
|
||
.badge {
|
||
font-size: 0.7rem; padding: 0.1rem 0.45rem; border-radius: 20px;
|
||
background: rgba(233,69,96,0.15); color: var(--color-primary); white-space: nowrap;
|
||
}
|
||
.badge-neutral { background: rgba(255,255,255,0.07); color: var(--color-muted); }
|
||
|
||
/* ── Item detail ── */
|
||
.item-detail {
|
||
display: none; padding: 0.5rem 0.6rem 0.6rem; border-top: 1px solid rgba(255,255,255,0.06);
|
||
font-size: 0.8rem; color: var(--color-muted); line-height: 1.55;
|
||
}
|
||
.item.open .item-detail { display: block; }
|
||
.item-detail-text { margin-bottom: 0.4rem; color: var(--color-text); white-space: pre-wrap; }
|
||
.item-detail-row { display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center; margin-top: 0.25rem; }
|
||
.item-detail-label { font-size: 0.72rem; color: var(--color-muted); }
|
||
.item-detail-chip {
|
||
font-size: 0.7rem; padding: 0.1rem 0.5rem; border-radius: 20px;
|
||
background: rgba(255,255,255,0.07); color: var(--color-text);
|
||
}
|
||
.item-detail-chip-toy {
|
||
background: rgba(233,69,96,0.12); color: var(--color-primary);
|
||
}
|
||
|
||
.sub-empty { font-size: 0.78rem; color: var(--color-muted); padding: 0.2rem 0; }
|
||
|
||
/* ── Gruppe-Modal ── */
|
||
.modal-backdrop {
|
||
display: none; position: fixed; inset: 0;
|
||
background: rgba(0,0,0,0.6); z-index: 200;
|
||
align-items: center; justify-content: center;
|
||
}
|
||
.modal-backdrop.open { display: flex; }
|
||
.modal {
|
||
background: var(--color-card); border: 1px solid var(--color-secondary);
|
||
border-radius: 12px; padding: 2rem; width: 100%; max-width: 460px;
|
||
box-shadow: 0 12px 40px rgba(0,0,0,0.6); max-height: 90vh; overflow-y: auto;
|
||
}
|
||
.modal h2 { color: var(--color-primary); font-size: 1.1rem; margin-bottom: 1.25rem; }
|
||
.modal label {
|
||
display: block; font-size: 0.8rem; color: #aaa;
|
||
margin-top: 1rem; margin-bottom: 0.3rem;
|
||
}
|
||
.modal input[type="text"],
|
||
.modal input[type="number"],
|
||
.modal textarea {
|
||
width: 100%; padding: 0.6rem 0.85rem;
|
||
border: 1px solid var(--color-secondary); border-radius: 6px;
|
||
background: var(--color-secondary); color: var(--color-text);
|
||
font-size: 0.95rem; outline: none; transition: border-color 0.2s; resize: vertical;
|
||
box-sizing: border-box;
|
||
}
|
||
.modal input[type="text"]:focus,
|
||
.modal input[type="number"]:focus,
|
||
.modal textarea:focus { border-color: var(--color-primary); }
|
||
.modal input[type="file"] { font-size: 0.85rem; color: var(--color-muted); margin-top: 0.25rem; }
|
||
.modal-check {
|
||
display: flex; align-items: center; gap: 0.6rem;
|
||
margin-top: 1rem; font-size: 0.9rem; cursor: pointer;
|
||
}
|
||
.modal-check input[type="checkbox"] { accent-color: var(--color-primary); width: 16px; height: 16px; }
|
||
.modal-actions {
|
||
display: flex; justify-content: flex-end; gap: 0.75rem; margin-top: 1.5rem;
|
||
}
|
||
.modal-actions .btn-cancel {
|
||
background: var(--color-secondary); color: var(--color-text);
|
||
border: none; border-radius: 6px; padding: 0.55rem 1.1rem;
|
||
font-size: 0.9rem; cursor: pointer; transition: background 0.15s;
|
||
}
|
||
.modal-actions .btn-cancel:hover { background: #1a4a8a; }
|
||
.modal-actions .btn-save {
|
||
background: var(--color-primary); color: #fff;
|
||
border: none; border-radius: 6px; padding: 0.55rem 1.1rem;
|
||
font-size: 0.9rem; font-weight: 600; cursor: pointer; transition: background 0.15s;
|
||
}
|
||
.modal-actions .btn-save:hover { background: #c73652; }
|
||
.modal-actions .btn-save:disabled { opacity: 0.5; cursor: default; }
|
||
.modal-error { color: var(--color-primary); font-size: 0.82rem; margin-top: 0.75rem; display: none; }
|
||
|
||
/* ── Placeholder-Hint ── */
|
||
.label-with-hint { display: flex; align-items: center; gap: 0.4rem; }
|
||
.btn-hint {
|
||
background: none; border: 1px solid rgba(136,136,136,0.4); border-radius: 50%;
|
||
color: var(--color-muted); font-size: 0.7rem; font-style: italic; font-weight: 700;
|
||
width: 16px; height: 16px; line-height: 1; padding: 0;
|
||
cursor: pointer; flex-shrink: 0; transition: border-color 0.15s, color 0.15s;
|
||
}
|
||
.btn-hint:hover { border-color: var(--color-primary); color: var(--color-primary); }
|
||
.placeholder-hint {
|
||
background: rgba(255,255,255,0.04); border: 1px solid rgba(136,136,136,0.25);
|
||
border-radius: 6px; padding: 0.5rem 0.7rem; margin-bottom: 0.3rem;
|
||
font-size: 0.78rem; color: var(--color-muted); line-height: 1.6;
|
||
}
|
||
.placeholder-hint code {
|
||
background: rgba(233,69,96,0.12); color: var(--color-primary);
|
||
border-radius: 3px; padding: 0.05rem 0.3rem; font-size: 0.75rem;
|
||
}
|
||
#iTextAC { position: fixed; z-index: 9999; background: var(--color-surface, #1e1e2e);
|
||
border: 1px solid var(--color-border, #444); border-radius: 6px;
|
||
box-shadow: 0 4px 14px rgba(0,0,0,.5); display: none; overflow: hidden; min-width: 180px; max-height: 280px; overflow-y: auto; }
|
||
.ac-item { padding: 0.45rem 0.9rem; cursor: pointer; font-size: 0.88rem;
|
||
font-family: monospace; color: var(--color-text, #cdd6f4); user-select: none; }
|
||
.ac-item:hover, .ac-item-active { background: var(--color-primary, #cba6f7); color: #1e1e2e; }
|
||
.ac-separator { padding: 0.2rem 0.7rem; font-size: 0.72rem; color: var(--color-muted);
|
||
text-transform: uppercase; letter-spacing: 0.05em; background: rgba(255,255,255,0.04);
|
||
pointer-events: none; border-top: 1px solid rgba(136,136,136,0.2); margin-top: 2px; }
|
||
|
||
/* ── Item-Add-Modal extra ── */
|
||
.modal-two-col { display: flex; gap: 0.75rem; }
|
||
.modal-two-col > * { flex: 1; }
|
||
.werkzeug-checks {
|
||
display: flex; flex-wrap: nowrap;
|
||
gap: 0.25rem; margin-top: 0.5rem;
|
||
}
|
||
.werkzeug-check {
|
||
flex: 1; display: flex; flex-direction: column;
|
||
align-items: center; gap: 0.3rem;
|
||
font-size: 0.73rem; cursor: pointer;
|
||
text-align: center; line-height: 1.2;
|
||
}
|
||
.werkzeug-check input[type="checkbox"] {
|
||
accent-color: var(--color-primary);
|
||
width: 15px; height: 15px; flex-shrink: 0;
|
||
}
|
||
.item-action-btns { display: flex; gap: 0.4rem; margin-top: 0.5rem; justify-content: space-between; }
|
||
.btn-item-edit {
|
||
background: none; border: 1px solid rgba(136,136,136,0.45); border-radius: 5px;
|
||
color: var(--color-muted); font-size: 0.75rem; padding: 0.2rem 0.6rem;
|
||
cursor: pointer; transition: border-color 0.15s, color 0.15s;
|
||
}
|
||
.btn-item-edit:hover { border-color: var(--color-text); color: var(--color-text); }
|
||
.btn-item-delete {
|
||
background: none; border: 1px solid rgba(233,69,96,0.4); border-radius: 5px;
|
||
color: var(--color-primary); font-size: 0.75rem; padding: 0.2rem 0.6rem;
|
||
cursor: pointer; transition: background 0.15s;
|
||
}
|
||
.btn-item-delete:hover { background: rgba(233,69,96,0.15); }
|
||
|
||
/* ── Toy-Auswahl (item modal) ── */
|
||
.selected-toys-row {
|
||
display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.4rem; min-height: 1.2rem;
|
||
}
|
||
.sel-toy-chip {
|
||
display: inline-flex; align-items: center; gap: 0.3rem;
|
||
padding: 0.18rem 0.5rem 0.18rem 0.55rem; border-radius: 20px;
|
||
font-size: 0.78rem; border: 1px solid rgba(233,69,96,0.5);
|
||
background: rgba(233,69,96,0.1); color: var(--color-text);
|
||
}
|
||
.sel-toy-chip img { width: 16px; height: 16px; border-radius: 3px; object-fit: cover; flex-shrink: 0; }
|
||
.sel-toy-chip .toy-remove {
|
||
background: none; border: none; color: var(--color-muted); cursor: pointer;
|
||
padding: 0; font-size: 0.75rem; line-height: 1; margin-left: 0.1rem;
|
||
}
|
||
.sel-toy-chip .toy-remove:hover { color: var(--color-primary); }
|
||
.btn-toy-add {
|
||
background: none; border: 1px dashed rgba(136,136,136,0.5); border-radius: 20px;
|
||
color: var(--color-muted); font-size: 0.78rem; padding: 0.18rem 0.7rem;
|
||
cursor: pointer; margin-top: 0.4rem; transition: border-color 0.15s, color 0.15s;
|
||
}
|
||
.btn-toy-add:hover { border-color: var(--color-primary); color: var(--color-primary); }
|
||
|
||
/* ── Toy-Suche (inline) ── */
|
||
.toy-search-input {
|
||
width: 100%; box-sizing: border-box; padding: 0.45rem 0.7rem;
|
||
background: var(--color-secondary); border: 1px solid rgba(136,136,136,0.3);
|
||
border-radius: 6px; color: var(--color-text); font-size: 0.9rem; margin-bottom: 0.75rem;
|
||
}
|
||
.toy-search-input:focus { outline: none; border-color: var(--color-primary); }
|
||
.toy-search-results {
|
||
display: flex; flex-wrap: wrap; gap: 0.35rem; max-height: 240px;
|
||
overflow-y: auto; padding-right: 2px;
|
||
}
|
||
.toy-result-chip {
|
||
display: inline-flex; align-items: center; gap: 0.3rem;
|
||
padding: 0.2rem 0.6rem; border-radius: 20px; cursor: pointer;
|
||
font-size: 0.8rem; border: 1px solid var(--color-secondary);
|
||
background: var(--color-secondary); color: var(--color-muted);
|
||
transition: border-color 0.15s, background 0.15s, color 0.15s;
|
||
user-select: none;
|
||
}
|
||
.toy-result-chip:hover { border-color: var(--color-primary); color: var(--color-text); }
|
||
.toy-result-chip.selected {
|
||
border-color: var(--color-primary); background: rgba(233,69,96,0.15); color: var(--color-primary);
|
||
}
|
||
.toy-result-chip img { width: 18px; height: 18px; border-radius: 3px; object-fit: cover; flex-shrink: 0; }
|
||
|
||
/* ── Publish Modal ── */
|
||
.publish-warning {
|
||
background: rgba(233,69,96,0.08); border: 1px solid rgba(233,69,96,0.3);
|
||
border-radius: 8px; padding: 0.85rem 1rem; font-size: 0.85rem;
|
||
color: var(--color-text); line-height: 1.55; margin-bottom: 1rem;
|
||
}
|
||
.publish-warning strong { color: var(--color-primary); }
|
||
.publish-confirm-check {
|
||
display: flex; align-items: flex-start; gap: 0.65rem;
|
||
font-size: 0.85rem; line-height: 1.5; cursor: pointer; margin-top: 0.5rem;
|
||
}
|
||
.publish-confirm-check input[type="checkbox"] {
|
||
accent-color: var(--color-primary); width: 16px; height: 16px;
|
||
flex-shrink: 0; margin-top: 0.15rem;
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
<body class="app">
|
||
|
||
<!-- Gruppe-Modal -->
|
||
<div class="modal-backdrop" id="confirmModal">
|
||
<div class="modal" style="max-width:420px;">
|
||
<h2 id="confirmModalTitle">Bestätigung</h2>
|
||
<p id="confirmModalText" style="color:var(--color-text);margin-bottom:1.25rem;line-height:1.5;"></p>
|
||
<div class="modal-actions">
|
||
<button class="btn-cancel" id="confirmModalCancel">Abbrechen</button>
|
||
<button class="btn-save" id="confirmModalOk" style="background:var(--color-danger,#e74c3c);">Löschen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-backdrop" id="gruppeModal">
|
||
<div class="modal">
|
||
<h2 id="modalTitle">Neue Aufgabengruppe</h2>
|
||
<label for="gName">Name *</label>
|
||
<input type="text" id="gName" maxlength="100" placeholder="Gruppenname">
|
||
<label for="gDesc">Beschreibung</label>
|
||
<textarea id="gDesc" rows="3" maxlength="1000" placeholder="Kurze Beschreibung…"></textarea>
|
||
<label>Bild (optional)</label>
|
||
<div id="gCurrentImgWrap" style="display:none; align-items:center; gap:0.5rem; margin-bottom:0.4rem;">
|
||
<img id="gCurrentImg" style="max-width:64px; max-height:64px; border-radius:6px;" src="" alt="">
|
||
<span style="font-size:0.78rem; color:var(--color-muted);">Aktuelles Bild – neues wählen zum Ersetzen</span>
|
||
</div>
|
||
<input type="file" id="gBild" accept="image/*">
|
||
<label id="gPublicLabel" style="display:none;">
|
||
<span class="modal-check">
|
||
<input type="checkbox" id="gPublic">
|
||
Gruppe veröffentlichen (für alle sichtbar)
|
||
</span>
|
||
</label>
|
||
<div class="modal-error" id="modalError"></div>
|
||
<div class="modal-actions">
|
||
<button class="btn-cancel" id="cancelBtn">Abbrechen</button>
|
||
<button class="btn-save" id="saveBtn">Speichern</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Veröffentlichen-Modal -->
|
||
<div class="modal-backdrop" id="publishModal">
|
||
<div class="modal">
|
||
<h2 style="color:var(--color-primary);">Gruppe veröffentlichen</h2>
|
||
<div class="publish-warning">
|
||
<strong>Achtung:</strong> Wenn du diese Gruppe veröffentlichst, können <strong>alle anderen Nutzer</strong>
|
||
sie sehen und eine eigene Kopie anlegen. Dieser Vorgang kann nicht automatisch rückgängig gemacht werden –
|
||
du kannst die Gruppe danach nur noch über „Bearbeiten" wieder auf privat stellen.
|
||
</div>
|
||
<label class="publish-confirm-check" id="publishCheckLabel">
|
||
<input type="checkbox" id="publishConfirmCb">
|
||
<span>Ich bestätige, dass ich ausschließlich eigene Bilder und kein urheberrechtlich geschütztes Bildmaterial in dieser Gruppe verwende.</span>
|
||
</label>
|
||
<div class="modal-error" id="publishError" style="display:none;"></div>
|
||
<div class="modal-actions">
|
||
<button class="btn-cancel" id="publishCancelBtn">Abbrechen</button>
|
||
<button class="btn-save" id="publishConfirmBtn" disabled>Veröffentlichen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Item-Hinzufügen-Modal -->
|
||
<div class="modal-backdrop" id="itemModal">
|
||
<div class="modal">
|
||
<h2 id="itemModalTitle">Aufgabe hinzufügen</h2>
|
||
|
||
<label for="iKurzText">Kurzbezeichnung *</label>
|
||
<input type="text" id="iKurzText" maxlength="200" placeholder="Kurzer Name">
|
||
|
||
<label class="label-with-hint">
|
||
<span>Beschreibung *</span>
|
||
<button type="button" class="btn-hint" onclick="togglePlaceholderHint()" title="Platzhalter-Hilfe">i</button>
|
||
</label>
|
||
<div id="iPlaceholderHint" style="display:none;">
|
||
<div class="placeholder-hint">
|
||
In Texten können Platzhalter verwendet werden:<br>
|
||
<code>{AKTIV}</code> – Name des aktiven Parts<br>
|
||
<code>{PASSIV}</code> – Name des passiven Parts
|
||
</div>
|
||
</div>
|
||
<textarea id="iText" rows="4" maxlength="4000" placeholder="Ausführliche Beschreibung…"></textarea>
|
||
<div id="iTextAC"></div>
|
||
|
||
<!-- Finisher: Geschlecht -->
|
||
<div id="iGeschlechtRow">
|
||
<label>Geschlecht der Person die kommt *</label>
|
||
<div style="display:flex; gap:1.5rem; margin-top:0.5rem;" id="iGeschlecht">
|
||
<label style="display:flex; align-items:center; gap:0.4rem; font-size:0.85rem; cursor:pointer;">
|
||
<input type="radio" name="iGeschlechtRadio" value="WEIBLICH" style="accent-color:var(--color-primary);">
|
||
Weiblich
|
||
</label>
|
||
<label style="display:flex; align-items:center; gap:0.4rem; font-size:0.85rem; cursor:pointer;">
|
||
<input type="radio" name="iGeschlechtRadio" value="DIVERS" style="accent-color:var(--color-primary);">
|
||
Divers
|
||
</label>
|
||
<label style="display:flex; align-items:center; gap:0.4rem; font-size:0.85rem; cursor:pointer;">
|
||
<input type="radio" name="iGeschlechtRadio" value="MAENNLICH" style="accent-color:var(--color-primary);">
|
||
Männlich
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Aufgabe / Strafe: Level + Sekunden -->
|
||
<div id="iLevelRow">
|
||
<label for="iLevel">Level *</label>
|
||
<input type="number" id="iLevel" min="1" max="5" placeholder="1–5">
|
||
<label>Dauer (Sekunden)</label>
|
||
<div class="modal-two-col">
|
||
<div>
|
||
<label for="iSekVon" style="margin-top:0;">Von</label>
|
||
<input type="number" id="iSekVon" min="0" placeholder="z. B. 30">
|
||
</div>
|
||
<div>
|
||
<label for="iSekBis" style="margin-top:0;">Bis</label>
|
||
<input type="number" id="iSekBis" min="0" placeholder="z. B. 120">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Aufgabe / Strafe: Werkzeuge aktiv/passiv -->
|
||
<div id="iWerkzeugAktivRow">
|
||
<label>Benötigt (aktiv)</label>
|
||
<div class="werkzeug-checks" id="iWerkzeugAktiv">
|
||
<label class="werkzeug-check"><span>Mund</span><input type="checkbox" value="MUND"></label>
|
||
<label class="werkzeug-check"><span>Vagina</span><input type="checkbox" value="VAGINA"></label>
|
||
<label class="werkzeug-check"><span>Penis</span><input type="checkbox" value="PENIS"></label>
|
||
<label class="werkzeug-check"><span>Anus</span><input type="checkbox" value="ANUS"></label>
|
||
<label class="werkzeug-check"><span>Umschnall-Dildo</span><input type="checkbox" value="UMSCHNALLDILDO"></label>
|
||
</div>
|
||
</div>
|
||
<div id="iWerkzeugPassivRow">
|
||
<label>Benötigt (passiv)</label>
|
||
<div class="werkzeug-checks" id="iWerkzeugPassiv">
|
||
<label class="werkzeug-check"><span>Mund</span><input type="checkbox" value="MUND"></label>
|
||
<label class="werkzeug-check"><span>Vagina</span><input type="checkbox" value="VAGINA"></label>
|
||
<label class="werkzeug-check"><span>Penis</span><input type="checkbox" value="PENIS"></label>
|
||
<label class="werkzeug-check"><span>Anus</span><input type="checkbox" value="ANUS"></label>
|
||
<label class="werkzeug-check"><span>Umschnall-Dildo</span><input type="checkbox" value="UMSCHNALLDILDO"></label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Finisher: Werkzeuge (eigene Labels, kein Umschnalldildo bei aktiv) -->
|
||
<div id="iWerkzeugFinisherAktivRow">
|
||
<label>Benötigt (Person die kommt)</label>
|
||
<div class="werkzeug-checks" id="iWerkzeugFinisherAktiv">
|
||
<label class="werkzeug-check"><span>Mund</span><input type="checkbox" value="MUND"></label>
|
||
<label class="werkzeug-check"><span>Vagina</span><input type="checkbox" value="VAGINA"></label>
|
||
<label class="werkzeug-check"><span>Penis</span><input type="checkbox" value="PENIS"></label>
|
||
<label class="werkzeug-check"><span>Anus</span><input type="checkbox" value="ANUS"></label>
|
||
</div>
|
||
</div>
|
||
<div id="iWerkzeugFinisherPassivRow">
|
||
<label>Benötigt (Person die zum Kommen bringt)</label>
|
||
<div class="werkzeug-checks" id="iWerkzeugFinisherPassiv">
|
||
<label class="werkzeug-check"><span>Mund</span><input type="checkbox" value="MUND"></label>
|
||
<label class="werkzeug-check"><span>Vagina</span><input type="checkbox" value="VAGINA"></label>
|
||
<label class="werkzeug-check"><span>Penis</span><input type="checkbox" value="PENIS"></label>
|
||
<label class="werkzeug-check"><span>Anus</span><input type="checkbox" value="ANUS"></label>
|
||
<label class="werkzeug-check"><span>Umschnall-Dildo</span><input type="checkbox" value="UMSCHNALLDILDO"></label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Zeitstrafe: Minuten + SperreFuer + releaseText -->
|
||
<div id="iMinutenRow">
|
||
<label>Dauer (Minuten) *</label>
|
||
<div class="modal-two-col">
|
||
<div>
|
||
<label for="iMinVon" style="margin-top:0;">Von *</label>
|
||
<input type="number" id="iMinVon" min="0" placeholder="z. B. 5">
|
||
</div>
|
||
<div>
|
||
<label for="iMinBis" style="margin-top:0;">Bis</label>
|
||
<input type="number" id="iMinBis" min="0" placeholder="z. B. 30">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="iSperreFuerRow">
|
||
<label>Sperrt *</label>
|
||
<div class="werkzeug-checks" id="iSperreFuer">
|
||
<label class="werkzeug-check"><span>Mund</span><input type="checkbox" value="MUND"></label>
|
||
<label class="werkzeug-check"><span>Vagina</span><input type="checkbox" value="VAGINA"></label>
|
||
<label class="werkzeug-check"><span>Penis</span><input type="checkbox" value="PENIS"></label>
|
||
<label class="werkzeug-check"><span>Anus</span><input type="checkbox" value="ANUS"></label>
|
||
</div>
|
||
</div>
|
||
<div id="iReleaseTextRow">
|
||
<label for="iReleaseText">Text bei Aufhebung</label>
|
||
<textarea id="iReleaseText" rows="2" maxlength="2000" placeholder="Text der angezeigt wird, wenn die Sperre endet…"></textarea>
|
||
</div>
|
||
|
||
<!-- Toys -->
|
||
<label>Benötigte Toys (optional)</label>
|
||
<div class="selected-toys-row" id="iSelectedToys"></div>
|
||
<button class="btn-toy-add" type="button" id="iToyAddBtn">+ Toy hinzufügen</button>
|
||
<div id="iToySearchArea" style="display:none; margin-top:0.5rem;">
|
||
<input class="toy-search-input" type="text" id="toySearchInput" placeholder="Name filtern…" autocomplete="off">
|
||
<div class="toy-search-results" id="toySearchResults"></div>
|
||
<div id="toySearchEmpty" style="font-size:0.82rem; color:var(--color-muted); display:none; margin-top:0.4rem;">Keine Toys gefunden.</div>
|
||
</div>
|
||
|
||
<div class="modal-error" id="itemModalError"></div>
|
||
<div class="modal-actions">
|
||
<button class="btn-cancel" id="itemCancelBtn">Abbrechen</button>
|
||
<button class="btn-save" id="itemSaveBtn">Speichern</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="main">
|
||
<div class="content">
|
||
|
||
<!-- Meine Aufgabengruppen -->
|
||
<div class="section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">Meine Aufgabengruppen</h2>
|
||
<div class="section-actions">
|
||
<button class="btn-action" id="publishBtn" disabled>⊙ Veröffentlichen</button>
|
||
<button class="btn-action" id="editBtn" disabled>✎ Bearbeiten</button>
|
||
<button class="btn-action btn-action-danger" id="deleteBtn" disabled>✕ Löschen</button>
|
||
<button class="btn-add" id="openCreateBtn">+ Neu</button>
|
||
</div>
|
||
</div>
|
||
<div class="action-error" id="userActionError"></div>
|
||
<div id="userLoading" class="loading">Wird geladen…</div>
|
||
<div class="gruppe-list" id="userList"></div>
|
||
<div class="paging" id="userPaging" style="display:none;">
|
||
<button id="userPrev">‹ Zurück</button>
|
||
<span class="page-info" id="userPageInfo"></span>
|
||
<button id="userNext">Weiter ›</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Abonnierte Aufgabengruppen -->
|
||
<div class="section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">Abonnierte Aufgabengruppen</h2>
|
||
<div class="section-actions">
|
||
<button class="btn-action" id="unsubBtn" disabled>♥ Abonnement kündigen</button>
|
||
<button class="btn-action" id="aboCopyBtn" disabled>⊕ In meine Gruppen kopieren</button>
|
||
</div>
|
||
</div>
|
||
<div class="action-error" id="aboActionError"></div>
|
||
<div id="aboLoading" class="loading">Wird geladen…</div>
|
||
<div class="gruppe-list" id="aboList"></div>
|
||
<div class="paging" id="aboPaging" style="display:none;">
|
||
<button id="aboPrev">‹ Zurück</button>
|
||
<span class="page-info" id="aboPageInfo"></span>
|
||
<button id="aboNext">Weiter ›</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- System-Aufgabengruppen -->
|
||
<div class="section">
|
||
<div class="section-header">
|
||
<h2 class="section-title">System-Aufgabengruppen</h2>
|
||
<div class="section-actions">
|
||
<button class="btn-action" id="copyBtn" disabled>⊕ In meine Gruppen kopieren</button>
|
||
</div>
|
||
</div>
|
||
<div class="action-error" id="systemActionError"></div>
|
||
<div id="systemLoading" class="loading">Wird geladen…</div>
|
||
<div class="gruppe-list" id="systemList"></div>
|
||
<div class="paging" id="systemPaging" style="display:none;">
|
||
<button id="systemPrev">‹ Zurück</button>
|
||
<span class="page-info" id="systemPageInfo"></span>
|
||
<button id="systemNext">Weiter ›</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// ── API-URL-Hilfsfunktion ──
|
||
function apiUrl(path) {
|
||
return '/vanilla' + path;
|
||
}
|
||
|
||
const PAGE_SIZE = 5;
|
||
let userPage = 0, userTotalPages = 1;
|
||
let aboPage = 0, aboTotalPages = 1;
|
||
let systemPage = 0, systemTotalPages = 1;
|
||
|
||
// ── Pending expand after reload ──
|
||
let pendingExpandId = null;
|
||
let pendingExpandType = null;
|
||
|
||
// ── Auth ──
|
||
fetch('/login/me')
|
||
.then(r => { if (r.status === 401) { window.location.href = '/login.html'; return null; } return r.ok ? r.json() : null; })
|
||
.then(user => { if (!user) return; loadUserGruppen(); loadAboGruppen(); loadSystemGruppen(); })
|
||
.catch(() => { window.location.href = '/login.html'; });
|
||
|
||
// ── Cross-tab notification ──
|
||
let _notifyOnLoad = false;
|
||
const gruppenBc = new BroadcastChannel('vanilla-gruppen-updated');
|
||
|
||
// ── Load ──
|
||
function loadUserGruppen() {
|
||
if (_notifyOnLoad) { _notifyOnLoad = false; try { gruppenBc.postMessage(1); } catch (_) {} }
|
||
resetSelection();
|
||
document.getElementById('userLoading').style.display = 'block';
|
||
fetch(apiUrl(`/gruppe/list/user`) + `?page=${userPage}&size=${PAGE_SIZE}`)
|
||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||
.then(data => {
|
||
console.log('[aufgaben] user gruppen:', data);
|
||
userTotalPages = data.totalPages || 1;
|
||
try { renderGruppen('userList', data.content, 'user'); } catch(e) { console.error('[aufgaben] renderGruppen user Fehler:', e); throw e; }
|
||
updatePaging('userPaging', 'userPrev', 'userNext', 'userPageInfo', userPage, userTotalPages);
|
||
document.getElementById('userLoading').style.display = 'none';
|
||
reapplyPendingExpand();
|
||
})
|
||
.catch(err => { console.error('[aufgaben] Fehler user gruppen:', err); document.getElementById('userLoading').textContent = 'Fehler beim Laden: ' + err.message; });
|
||
}
|
||
|
||
function loadSystemGruppen() {
|
||
resetSelection();
|
||
document.getElementById('systemLoading').style.display = 'block';
|
||
fetch(apiUrl(`/gruppe/list/system`) + `?page=${systemPage}&size=${PAGE_SIZE}`)
|
||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||
.then(data => {
|
||
console.log('[aufgaben] system gruppen:', data);
|
||
systemTotalPages = data.totalPages || 1;
|
||
try { renderGruppen('systemList', data.content, 'system'); } catch(e) { console.error('[aufgaben] renderGruppen system Fehler:', e); throw e; }
|
||
updatePaging('systemPaging', 'systemPrev', 'systemNext', 'systemPageInfo', systemPage, systemTotalPages);
|
||
document.getElementById('systemLoading').style.display = 'none';
|
||
reapplyPendingExpand();
|
||
})
|
||
.catch(err => { console.error('[aufgaben] Fehler system gruppen:', err); document.getElementById('systemLoading').textContent = 'Fehler beim Laden: ' + err.message; });
|
||
}
|
||
|
||
function loadAboGruppen() {
|
||
document.getElementById('aboLoading').style.display = 'block';
|
||
fetch(apiUrl(`/abo/list`) + `?page=${aboPage}&size=${PAGE_SIZE}`)
|
||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||
.then(data => {
|
||
console.log('[aufgaben] abo gruppen:', data);
|
||
aboTotalPages = data.totalPages || 1;
|
||
renderGruppen('aboList', data.content, 'abo');
|
||
updatePaging('aboPaging', 'aboPrev', 'aboNext', 'aboPageInfo', aboPage, aboTotalPages);
|
||
document.getElementById('aboLoading').style.display = 'none';
|
||
reapplyPendingExpand();
|
||
})
|
||
.catch(err => { console.error('[aufgaben] Fehler abo gruppen:', err); document.getElementById('aboLoading').textContent = 'Fehler beim Laden: ' + err.message; });
|
||
}
|
||
|
||
function reapplyPendingExpand() {
|
||
if (!pendingExpandId) return;
|
||
const card = document.getElementById('gruppe-' + pendingExpandId);
|
||
if (!card) return;
|
||
const id = pendingExpandId;
|
||
const type = pendingExpandType;
|
||
pendingExpandId = pendingExpandType = null;
|
||
selectedGruppeId = id;
|
||
selectedGruppeType = type;
|
||
expandGruppe(id);
|
||
updateButtons(type);
|
||
}
|
||
|
||
// ── Render Gruppen ──
|
||
function renderGruppen(listId, gruppen, type) {
|
||
const list = document.getElementById(listId);
|
||
if (!gruppen || gruppen.length === 0) {
|
||
list.innerHTML = '<p class="empty">Keine Einträge vorhanden.</p>';
|
||
return;
|
||
}
|
||
list.innerHTML = gruppen.map(g => {
|
||
_gruppeData[g.gruppenId] = g;
|
||
const aufgabenCount = (g.aufgaben || []).length;
|
||
const strafeCount = (g.strafen || []).length;
|
||
const sperreCount = (g.sperren || []).length;
|
||
const finisherCount = (g.finisher || []).length;
|
||
const counts = [
|
||
aufgabenCount ? `${aufgabenCount} Aufgabe${aufgabenCount !== 1 ? 'n' : ''}` : '',
|
||
finisherCount ? `${finisherCount} Finisher` : ''
|
||
].filter(Boolean).join(' · ');
|
||
|
||
const badges = [];
|
||
if (g.relevanz != null) badges.push(`<span class="gruppe-badge">Relevanz ${esc(String(g.relevanz))}</span>`);
|
||
if (g.privateGruppe) badges.push(`<span class="gruppe-badge gruppe-badge-private">Privat</span>`);
|
||
else badges.push(`<span class="gruppe-badge gruppe-badge-public">Öffentlich</span>`);
|
||
if (type === 'user' && g.subscriberCount > 0) badges.push(`<span class="gruppe-badge">♥ ${g.subscriberCount} Abo${g.subscriberCount !== 1 ? 's' : ''}</span>`);
|
||
|
||
return `
|
||
<div class="gruppe-card" id="gruppe-${esc(g.gruppenId)}">
|
||
<div class="gruppe-header" onclick="selectAndToggle('${esc(g.gruppenId)}', '${type}')">
|
||
${g.bild
|
||
? `<img class="gruppe-img" src="data:image/png;base64,${g.bild}" alt="${esc(g.name)}">`
|
||
: `<div class="gruppe-img-placeholder">✓</div>`}
|
||
<div class="gruppe-meta">
|
||
<div class="gruppe-name">${esc(g.name)}</div>
|
||
<div class="gruppe-info">${g.von ? esc(g.von) + (counts ? ' · ' : '') : ''}${counts || 'Keine Einträge'}</div>
|
||
${badges.length ? `<div class="gruppe-badges">${badges.join('')}</div>` : ''}
|
||
</div>
|
||
<span class="gruppe-toggle">▶</span>
|
||
</div>
|
||
<div class="gruppe-body" id="body-${esc(g.gruppenId)}" style="display:none;">
|
||
${g.beschreibung ? `<div class="gruppe-desc">${esc(g.beschreibung)}</div>` : ''}
|
||
${renderSubSection('Aufgaben', sortByLevelThenName(g.aufgaben || []), 'aufgabe', renderAufgabe, g.gruppenId, type)}
|
||
${renderSubSection('Finisher', sortByGeschlecht(g.finisher || []), 'finisher', renderFinisher, g.gruppenId, type)}
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
|
||
// Offenes Item tracking
|
||
openItemId = null;
|
||
}
|
||
|
||
// ── Sub-sections ──
|
||
function renderSubSection(title, items, kind, renderFn, gruppenId, type) {
|
||
const showAdd = type === 'user' && (kind !== 'strafe' && kind !== 'zeitstrafe');
|
||
const addBtn = showAdd
|
||
? `<button class="btn-sub-add" onclick="openItemModal('${esc(gruppenId)}','${kind}')">+ ${title.replace('en','').replace('fen','fe')}</button>`
|
||
: '';
|
||
return `<div class="sub-section">
|
||
<div class="sub-section-header">
|
||
<span class="sub-section-title">${esc(title)} (${items.length})</span>
|
||
${addBtn}
|
||
</div>
|
||
${items.length === 0
|
||
? '<div class="sub-empty">Keine Einträge</div>'
|
||
: `<div class="item-list">${items.map(item => renderFn(item, type, gruppenId)).join('')}</div>`}
|
||
</div>`;
|
||
}
|
||
|
||
// ── Gruppen- und Item-Datencache ──
|
||
const _gruppeData = {};
|
||
const _itemData = {};
|
||
|
||
// ── Item renderers ──
|
||
const WERKZEUG_LABEL = {
|
||
MUND: 'Mund', VAGINA: 'Vagina', PENIS: 'Penis',
|
||
ANUS: 'Anus', UMSCHNALLDILDO: 'Umschnall-Dildo'
|
||
};
|
||
|
||
function werkzeugChips(list) {
|
||
if (!list || list.length === 0) return '';
|
||
return list.map(w => `<span class="item-detail-chip">${esc(WERKZEUG_LABEL[w] || w)}</span>`).join('');
|
||
}
|
||
function toyChips(list) {
|
||
if (!list || list.length === 0) return '';
|
||
return list.map(t => `<span class="item-detail-chip item-detail-chip-toy">${esc(t.name || t)}</span>`).join('');
|
||
}
|
||
|
||
function renderAufgabe(a, type, gruppenId) {
|
||
_itemData[a.aufgabeId] = { ...a, _kind: 'aufgabe', _gruppenId: gruppenId };
|
||
const badges = [];
|
||
const zeit = formatSek(a.sekundenVon, a.sekundenBis);
|
||
if (zeit) badges.push(`<span class="badge badge-neutral">${esc(zeit)}</span>`);
|
||
if (a.level != null) badges.push(`<span class="badge">Level ${esc(String(a.level))}</span>`);
|
||
|
||
const detailRows = [];
|
||
if (a.text) detailRows.push(`<div class="item-detail-text">${esc(a.text)}</div>`);
|
||
if (a.benoetigtAktiv && a.benoetigtAktiv.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Aktiv:</span>${werkzeugChips(a.benoetigtAktiv)}</div>`);
|
||
if (a.benoetigtPassiv && a.benoetigtPassiv.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Passiv:</span>${werkzeugChips(a.benoetigtPassiv)}</div>`);
|
||
if (a.benoetigteToys && a.benoetigteToys.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Toys:</span>${toyChips(a.benoetigteToys)}</div>`);
|
||
const actionBtns = type === 'user' ? `
|
||
<div class="item-action-btns">
|
||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(a.aufgabeId)}',event)">✎ Bearbeiten</button>
|
||
<button class="btn-item-delete" onclick="deleteItem('aufgabe','${esc(a.aufgabeId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||
</div>` : '';
|
||
|
||
return `<div class="item" id="item-${esc(a.aufgabeId)}">
|
||
<div class="item-row" onclick="toggleItem('${esc(a.aufgabeId)}')">
|
||
<span class="item-text">${esc(a.kurzText)}</span>
|
||
${badges.length ? `<span class="item-badges">${badges.join('')}</span>` : ''}
|
||
</div>
|
||
${(detailRows.length || actionBtns) ? `<div class="item-detail">${detailRows.join('')}${actionBtns}</div>` : ''}
|
||
</div>`;
|
||
}
|
||
|
||
function renderStrafe(s, type, gruppenId) {
|
||
_itemData[s.strafeId] = { ...s, _kind: 'strafe', _gruppenId: gruppenId };
|
||
const badges = [];
|
||
const zeit = formatSek(s.sekundenVon, s.sekundenBis);
|
||
if (zeit) badges.push(`<span class="badge badge-neutral">${esc(zeit)}</span>`);
|
||
if (s.level != null) badges.push(`<span class="badge">Level ${esc(String(s.level))}</span>`);
|
||
|
||
const detailRows = [];
|
||
if (s.text) detailRows.push(`<div class="item-detail-text">${esc(s.text)}</div>`);
|
||
if (s.benoetigtAktiv && s.benoetigtAktiv.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Aktiv:</span>${werkzeugChips(s.benoetigtAktiv)}</div>`);
|
||
if (s.benoetigtPassiv && s.benoetigtPassiv.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Passiv:</span>${werkzeugChips(s.benoetigtPassiv)}</div>`);
|
||
if (s.benoetigteToys && s.benoetigteToys.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Toys:</span>${toyChips(s.benoetigteToys)}</div>`);
|
||
const actionBtns = type === 'user' ? `
|
||
<div class="item-action-btns">
|
||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(s.strafeId)}',event)">✎ Bearbeiten</button>
|
||
<button class="btn-item-delete" onclick="deleteItem('strafe','${esc(s.strafeId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||
</div>` : '';
|
||
|
||
return `<div class="item" id="item-${esc(s.strafeId)}">
|
||
<div class="item-row" onclick="toggleItem('${esc(s.strafeId)}')">
|
||
<span class="item-text">${esc(s.kurzText)}</span>
|
||
${badges.length ? `<span class="item-badges">${badges.join('')}</span>` : ''}
|
||
</div>
|
||
${(detailRows.length || actionBtns) ? `<div class="item-detail">${detailRows.join('')}${actionBtns}</div>` : ''}
|
||
</div>`;
|
||
}
|
||
|
||
function renderZeitstrafe(z, type, gruppenId) {
|
||
_itemData[z.sperreId] = { ...z, _kind: 'zeitstrafe', _gruppenId: gruppenId };
|
||
const badges = [];
|
||
const zeit = formatMin(z.minutenVon, z.minutenBis);
|
||
if (zeit) badges.push(`<span class="badge badge-neutral">${esc(zeit)}</span>`);
|
||
|
||
const detailRows = [];
|
||
if (z.text) detailRows.push(`<div class="item-detail-text">${esc(z.text)}</div>`);
|
||
if (z.releaseText) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Bei Aufhebung:</span><span style="font-size:0.78rem; color:var(--color-text);">${esc(z.releaseText)}</span></div>`);
|
||
if (z.sperreFuer && z.sperreFuer.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Sperrt:</span>${werkzeugChips(z.sperreFuer)}</div>`);
|
||
if (z.benoetigteToys && z.benoetigteToys.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Toys:</span>${toyChips(z.benoetigteToys)}</div>`);
|
||
const actionBtns = type === 'user' ? `
|
||
<div class="item-action-btns">
|
||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(z.sperreId)}',event)">✎ Bearbeiten</button>
|
||
<button class="btn-item-delete" onclick="deleteItem('zeitstrafe','${esc(z.sperreId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||
</div>` : '';
|
||
|
||
return `<div class="item" id="item-${esc(z.sperreId)}">
|
||
<div class="item-row" onclick="toggleItem('${esc(z.sperreId)}')">
|
||
<span class="item-text">${esc(z.kurzText)}</span>
|
||
${badges.length ? `<span class="item-badges">${badges.join('')}</span>` : ''}
|
||
</div>
|
||
${(detailRows.length || actionBtns) ? `<div class="item-detail">${detailRows.join('')}${actionBtns}</div>` : ''}
|
||
</div>`;
|
||
}
|
||
|
||
const GESCHLECHT_LABEL = { WEIBLICH: 'Weiblich', DIVERS: 'Divers', MAENNLICH: 'Männlich' };
|
||
|
||
function renderFinisher(f, type, gruppenId) {
|
||
_itemData[f.finisherId] = { ...f, _kind: 'finisher', _gruppenId: gruppenId };
|
||
const badges = [];
|
||
if (f.geschlecht) badges.push(`<span class="badge badge-neutral">${esc(GESCHLECHT_LABEL[f.geschlecht] || f.geschlecht)}</span>`);
|
||
|
||
const detailRows = [];
|
||
if (f.text) detailRows.push(`<div class="item-detail-text">${esc(f.text)}</div>`);
|
||
if (f.benoetigtAktiv && f.benoetigtAktiv.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Aktiv:</span>${werkzeugChips(f.benoetigtAktiv)}</div>`);
|
||
if (f.benoetigtPassiv && f.benoetigtPassiv.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Passiv:</span>${werkzeugChips(f.benoetigtPassiv)}</div>`);
|
||
if (f.benoetigteToys && f.benoetigteToys.length) detailRows.push(`<div class="item-detail-row"><span class="item-detail-label">Toys:</span>${toyChips(f.benoetigteToys)}</div>`);
|
||
const actionBtns = type === 'user' ? `
|
||
<div class="item-action-btns">
|
||
<button class="btn-item-edit" onclick="openEditItemModal('${esc(f.finisherId)}',event)">✎ Bearbeiten</button>
|
||
<button class="btn-item-delete" onclick="deleteItem('finisher','${esc(f.finisherId)}','${esc(gruppenId)}',event)">✕ Löschen</button>
|
||
</div>` : '';
|
||
|
||
return `<div class="item" id="item-${esc(f.finisherId)}">
|
||
<div class="item-row" onclick="toggleItem('${esc(f.finisherId)}')">
|
||
<span class="item-text">${esc(f.kurzText)}</span>
|
||
${badges.length ? `<span class="item-badges">${badges.join('')}</span>` : ''}
|
||
</div>
|
||
${(detailRows.length || actionBtns) ? `<div class="item-detail">${detailRows.join('')}${actionBtns}</div>` : ''}
|
||
</div>`;
|
||
}
|
||
|
||
// ── Item toggle (detail panel) ──
|
||
let openItemId = null;
|
||
|
||
function toggleItem(itemId) {
|
||
if (openItemId === itemId) {
|
||
document.getElementById('item-' + itemId).classList.remove('open');
|
||
openItemId = null;
|
||
return;
|
||
}
|
||
if (openItemId) {
|
||
const prev = document.getElementById('item-' + openItemId);
|
||
if (prev) prev.classList.remove('open');
|
||
}
|
||
const el = document.getElementById('item-' + itemId);
|
||
if (el) el.classList.add('open');
|
||
openItemId = itemId;
|
||
}
|
||
|
||
// ── Item löschen ──
|
||
const ITEM_DELETE_URL = {
|
||
aufgabe: apiUrl('/aufgabe'),
|
||
strafe: null,
|
||
zeitstrafe: null,
|
||
finisher: apiUrl('/finisher')
|
||
};
|
||
const ITEM_DELETE_FIELD = { aufgabe: 'aufgabeId', strafe: 'strafeId', zeitstrafe: 'sperreId', finisher: 'finisherId' };
|
||
|
||
function deleteItem(kind, itemId, gruppenId, event) {
|
||
event.stopPropagation();
|
||
if (!confirm('Eintrag wirklich löschen?')) return;
|
||
const deleteUrl = ITEM_DELETE_URL[kind];
|
||
if (!deleteUrl) return;
|
||
const body = { [ITEM_DELETE_FIELD[kind]]: itemId };
|
||
fetch(deleteUrl, {
|
||
method: 'DELETE',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
}).then(r => {
|
||
if (r.ok || r.status === 202) {
|
||
openItemId = null;
|
||
pendingExpandId = gruppenId;
|
||
pendingExpandType = 'user';
|
||
_notifyOnLoad = true; loadUserGruppen();
|
||
} else {
|
||
document.getElementById('userActionError').textContent = 'Fehler beim Löschen (HTTP ' + r.status + ').';
|
||
}
|
||
}).catch(() => {
|
||
document.getElementById('userActionError').textContent = 'Verbindungsfehler.';
|
||
});
|
||
}
|
||
|
||
// ── Sort ──
|
||
function sortByLevelThenName(items) {
|
||
return items.slice().sort((a, b) => {
|
||
const la = a.level ?? 999, lb = b.level ?? 999;
|
||
if (la !== lb) return la - lb;
|
||
return (a.kurzText || '').localeCompare(b.kurzText || '', 'de');
|
||
});
|
||
}
|
||
const GESCHLECHT_ORDER = { WEIBLICH: 0, DIVERS: 1, MAENNLICH: 2 };
|
||
function sortByGeschlecht(items) {
|
||
return items.slice().sort((a, b) => {
|
||
const ga = GESCHLECHT_ORDER[a.geschlecht] ?? 99, gb = GESCHLECHT_ORDER[b.geschlecht] ?? 99;
|
||
if (ga !== gb) return ga - gb;
|
||
return (a.kurzText || '').localeCompare(b.kurzText || '', 'de');
|
||
});
|
||
}
|
||
function sortByName(items) {
|
||
return items.slice().sort((a, b) => (a.kurzText || '').localeCompare(b.kurzText || '', 'de'));
|
||
}
|
||
|
||
// ── Time formatting ──
|
||
function formatSek(von, bis) {
|
||
if (von != null && bis != null) return `${von}–${bis} s`;
|
||
if (von != null) return `ab ${von} s`;
|
||
if (bis != null) return `bis ${bis} s`;
|
||
return '';
|
||
}
|
||
function formatMin(von, bis) {
|
||
if (von != null && bis != null) return `${von}–${bis} min`;
|
||
if (von != null) return `ab ${von} min`;
|
||
if (bis != null) return `bis ${bis} min`;
|
||
return '';
|
||
}
|
||
|
||
// ── Selektion (listenübergreifend) ──
|
||
let selectedGruppeId = null;
|
||
let selectedGruppeType = null;
|
||
|
||
function selectAndToggle(gruppenId, type) {
|
||
if (selectedGruppeId === gruppenId) {
|
||
collapseGruppe(gruppenId);
|
||
selectedGruppeId = selectedGruppeType = null;
|
||
updateButtons(null);
|
||
return;
|
||
}
|
||
if (selectedGruppeId) collapseGruppe(selectedGruppeId);
|
||
selectedGruppeId = gruppenId;
|
||
selectedGruppeType = type;
|
||
expandGruppe(gruppenId);
|
||
updateButtons(type);
|
||
document.getElementById('userActionError').textContent = '';
|
||
document.getElementById('aboActionError').textContent = '';
|
||
document.getElementById('systemActionError').textContent = '';
|
||
}
|
||
|
||
function expandGruppe(id) {
|
||
const card = document.getElementById('gruppe-' + id);
|
||
const body = document.getElementById('body-' + id);
|
||
if (!card) return;
|
||
card.classList.add('selected', 'open');
|
||
body.style.display = 'block';
|
||
}
|
||
|
||
function collapseGruppe(id) {
|
||
const card = document.getElementById('gruppe-' + id);
|
||
const body = document.getElementById('body-' + id);
|
||
if (!card) return;
|
||
card.classList.remove('selected', 'open');
|
||
body.style.display = 'none';
|
||
}
|
||
|
||
function updateButtons(type) {
|
||
const isUser = type === 'user';
|
||
const isSystem = type === 'system';
|
||
const isAbo = type === 'abo';
|
||
const isPrivate = isUser && selectedGruppeId && _gruppeData[selectedGruppeId]?.privateGruppe;
|
||
document.getElementById('publishBtn').disabled = !isPrivate;
|
||
document.getElementById('editBtn').disabled = !isUser;
|
||
document.getElementById('deleteBtn').disabled = !isUser;
|
||
document.getElementById('copyBtn').disabled = !isSystem;
|
||
document.getElementById('aboCopyBtn').disabled = !isAbo;
|
||
document.getElementById('unsubBtn').disabled = !isAbo;
|
||
}
|
||
|
||
function resetSelection() {
|
||
if (selectedGruppeId) collapseGruppe(selectedGruppeId);
|
||
selectedGruppeId = selectedGruppeType = null;
|
||
updateButtons(null);
|
||
document.getElementById('userActionError').textContent = '';
|
||
document.getElementById('aboActionError').textContent = '';
|
||
document.getElementById('systemActionError').textContent = '';
|
||
}
|
||
|
||
// ── Paging ──
|
||
function updatePaging(pagingId, prevId, nextId, infoId, current, total) {
|
||
const el = document.getElementById(pagingId);
|
||
if (total <= 1) { el.style.display = 'none'; return; }
|
||
el.style.display = 'flex';
|
||
document.getElementById(prevId).disabled = current === 0;
|
||
document.getElementById(nextId).disabled = current >= total - 1;
|
||
document.getElementById(infoId).textContent = `Seite ${current + 1} von ${total}`;
|
||
}
|
||
|
||
document.getElementById('userPrev').addEventListener('click', () => { if (userPage > 0) { userPage--; loadUserGruppen(); } });
|
||
document.getElementById('userNext').addEventListener('click', () => { if (userPage < userTotalPages - 1) { userPage++; loadUserGruppen(); } });
|
||
document.getElementById('aboPrev').addEventListener('click', () => { if (aboPage > 0) { aboPage--; loadAboGruppen(); } });
|
||
document.getElementById('aboNext').addEventListener('click', () => { if (aboPage < aboTotalPages - 1) { aboPage++; loadAboGruppen(); } });
|
||
document.getElementById('systemPrev').addEventListener('click', () => { if (systemPage > 0) { systemPage--; loadSystemGruppen(); } });
|
||
document.getElementById('systemNext').addEventListener('click', () => { if (systemPage < systemTotalPages - 1) { systemPage++; loadSystemGruppen(); } });
|
||
|
||
// ── Gruppe-Modal ──
|
||
const gruppeModal = document.getElementById('gruppeModal');
|
||
const saveBtn = document.getElementById('saveBtn');
|
||
let currentEditId = null;
|
||
|
||
function openModal(editId) {
|
||
currentEditId = editId || null;
|
||
document.getElementById('modalError').style.display = 'none';
|
||
document.getElementById('gBild').value = '';
|
||
|
||
if (currentEditId) {
|
||
fetch(apiUrl(`/gruppe/${currentEditId}`))
|
||
.then(r => r.ok ? r.json() : null)
|
||
.then(g => {
|
||
if (!g) return;
|
||
document.getElementById('modalTitle').textContent = 'Aufgabengruppe bearbeiten';
|
||
document.getElementById('gName').value = g.name || '';
|
||
document.getElementById('gDesc').value = g.beschreibung || '';
|
||
const pubCb = document.getElementById('gPublic');
|
||
pubCb.checked = !g.privateGruppe;
|
||
pubCb.disabled = g.privateGruppe; // Veröffentlichen nur über den Veröffentlichen-Button
|
||
document.getElementById('gPublicLabel').style.display = 'block';
|
||
const imgWrap = document.getElementById('gCurrentImgWrap');
|
||
if (g.bild) {
|
||
document.getElementById('gCurrentImg').src = 'data:image/png;base64,' + g.bild;
|
||
imgWrap.style.display = 'flex';
|
||
} else {
|
||
imgWrap.style.display = 'none';
|
||
}
|
||
gruppeModal.classList.add('open');
|
||
document.getElementById('gName').focus();
|
||
})
|
||
.catch(() => alert('Fehler beim Laden der Gruppe.'));
|
||
} else {
|
||
document.getElementById('modalTitle').textContent = 'Neue Aufgabengruppe';
|
||
document.getElementById('gName').value = '';
|
||
document.getElementById('gDesc').value = '';
|
||
document.getElementById('gPublic').checked = false;
|
||
document.getElementById('gPublicLabel').style.display = 'none';
|
||
document.getElementById('gCurrentImgWrap').style.display = 'none';
|
||
gruppeModal.classList.add('open');
|
||
document.getElementById('gName').focus();
|
||
}
|
||
}
|
||
|
||
function closeGruppeModal() {
|
||
gruppeModal.classList.remove('open');
|
||
document.getElementById('gPublic').disabled = false;
|
||
}
|
||
|
||
document.getElementById('openCreateBtn').addEventListener('click', () => openModal(null));
|
||
document.getElementById('editBtn').addEventListener('click', () => { if (selectedGruppeId && selectedGruppeType === 'user') openModal(selectedGruppeId); });
|
||
document.getElementById('cancelBtn').addEventListener('click', closeGruppeModal);
|
||
gruppeModal.addEventListener('click', e => { if (e.target === gruppeModal) closeGruppeModal(); });
|
||
|
||
saveBtn.addEventListener('click', async () => {
|
||
const name = document.getElementById('gName').value.trim();
|
||
if (!name) { showModalError('Bitte einen Namen eingeben.'); return; }
|
||
saveBtn.disabled = true;
|
||
saveBtn.textContent = 'Speichert…';
|
||
|
||
let bildBase64 = null;
|
||
const fileInput = document.getElementById('gBild');
|
||
if (fileInput.files.length > 0) {
|
||
bildBase64 = await toBase64(fileInput.files[0]);
|
||
}
|
||
|
||
const isEdit = currentEditId != null;
|
||
const payload = {
|
||
name,
|
||
beschreibung: document.getElementById('gDesc').value.trim() || null,
|
||
privateGruppe: isEdit ? !document.getElementById('gPublic').checked : true,
|
||
bild: bildBase64
|
||
};
|
||
|
||
fetch(isEdit ? apiUrl(`/gruppe/${currentEditId}`) : apiUrl('/gruppe'), {
|
||
method: isEdit ? 'PUT' : 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
})
|
||
.then(r => {
|
||
if (r.ok || r.status === 201) {
|
||
closeGruppeModal();
|
||
if (isEdit) {
|
||
pendingExpandId = currentEditId;
|
||
pendingExpandType = 'user';
|
||
}
|
||
userPage = 0;
|
||
_notifyOnLoad = true; loadUserGruppen();
|
||
} else if (r.status === 409) {
|
||
showModalError('Limit erreicht: maximal 10 eigene Aufgabengruppen möglich.');
|
||
} else {
|
||
showModalError('Fehler beim Speichern (HTTP ' + r.status + ').');
|
||
}
|
||
})
|
||
.catch(() => showModalError('Verbindungsfehler.'))
|
||
.finally(() => { saveBtn.disabled = false; saveBtn.textContent = 'Speichern'; });
|
||
});
|
||
|
||
function showModalError(msg) {
|
||
const el = document.getElementById('modalError');
|
||
el.textContent = msg; el.style.display = 'block';
|
||
}
|
||
|
||
// ── Delete ──
|
||
document.getElementById('deleteBtn').addEventListener('click', () => {
|
||
if (!selectedGruppeId || selectedGruppeType !== 'user') return;
|
||
openConfirmModal('Aufgabengruppe und alle enthaltenen Aufgaben, Strafen und Zeitstrafen wirklich löschen?', () => {
|
||
const btn = document.getElementById('deleteBtn');
|
||
btn.disabled = true;
|
||
fetch(apiUrl(`/gruppe/${selectedGruppeId}`), { method: 'DELETE' })
|
||
.then(r => {
|
||
if (r.ok || r.status === 202) {
|
||
userPage = 0;
|
||
_notifyOnLoad = true; loadUserGruppen();
|
||
} else if (r.status === 403) {
|
||
document.getElementById('userActionError').textContent = 'Keine Berechtigung.';
|
||
btn.disabled = false;
|
||
} else {
|
||
document.getElementById('userActionError').textContent = 'Fehler beim Löschen.';
|
||
btn.disabled = false;
|
||
}
|
||
})
|
||
.catch(() => { document.getElementById('userActionError').textContent = 'Verbindungsfehler.'; btn.disabled = false; });
|
||
});
|
||
});
|
||
|
||
// ── Copy ──
|
||
document.getElementById('copyBtn').addEventListener('click', () => {
|
||
if (!selectedGruppeId || selectedGruppeType !== 'system') return;
|
||
const btn = document.getElementById('copyBtn');
|
||
btn.disabled = true;
|
||
fetch(apiUrl(`/gruppe/copy/${selectedGruppeId}`), { method: 'POST' })
|
||
.then(r => {
|
||
if (r.ok || r.status === 201) {
|
||
userPage = 0;
|
||
_notifyOnLoad = true; loadUserGruppen();
|
||
document.getElementById('systemActionError').textContent = '';
|
||
} else {
|
||
document.getElementById('systemActionError').textContent = 'Fehler beim Kopieren (HTTP ' + r.status + ').';
|
||
btn.disabled = false;
|
||
}
|
||
})
|
||
.catch(() => { document.getElementById('systemActionError').textContent = 'Verbindungsfehler.'; btn.disabled = false; });
|
||
});
|
||
|
||
// ── Copy Abo-Gruppe ──
|
||
document.getElementById('aboCopyBtn').addEventListener('click', () => {
|
||
if (!selectedGruppeId || selectedGruppeType !== 'abo') return;
|
||
const btn = document.getElementById('aboCopyBtn');
|
||
btn.disabled = true;
|
||
fetch(apiUrl(`/gruppe/copy/${selectedGruppeId}`), { method: 'POST' })
|
||
.then(r => {
|
||
if (r.ok || r.status === 201) {
|
||
userPage = 0;
|
||
_notifyOnLoad = true; loadUserGruppen();
|
||
document.getElementById('aboActionError').textContent = '';
|
||
} else if (r.status === 409) {
|
||
document.getElementById('aboActionError').textContent = 'Limit erreicht: maximal 10 eigene Aufgabengruppen möglich.';
|
||
btn.disabled = false;
|
||
} else {
|
||
document.getElementById('aboActionError').textContent = 'Fehler beim Kopieren (HTTP ' + r.status + ').';
|
||
btn.disabled = false;
|
||
}
|
||
})
|
||
.catch(() => { document.getElementById('aboActionError').textContent = 'Verbindungsfehler.'; btn.disabled = false; });
|
||
});
|
||
|
||
// ── Unsubscribe ──
|
||
document.getElementById('unsubBtn').addEventListener('click', () => {
|
||
if (!selectedGruppeId || selectedGruppeType !== 'abo') return;
|
||
if (!confirm('Abonnement dieser Gruppe wirklich kündigen?')) return;
|
||
const btn = document.getElementById('unsubBtn');
|
||
btn.disabled = true;
|
||
fetch(apiUrl(`/abo/${selectedGruppeId}`), { method: 'DELETE' })
|
||
.then(r => {
|
||
if (r.ok || r.status === 202) {
|
||
resetSelection();
|
||
aboPage = 0;
|
||
loadAboGruppen();
|
||
} else {
|
||
document.getElementById('aboActionError').textContent = 'Fehler beim Kündigen (HTTP ' + r.status + ').';
|
||
btn.disabled = false;
|
||
}
|
||
})
|
||
.catch(() => { document.getElementById('aboActionError').textContent = 'Verbindungsfehler.'; btn.disabled = false; });
|
||
});
|
||
|
||
// ── Toy-Auswahl ──
|
||
let _selectedToys = []; // [{toyId, name, bild}]
|
||
let _allAvailableToys = null; // cache
|
||
|
||
function _loadAvailableToys() {
|
||
if (_allAvailableToys !== null) return Promise.resolve(_allAvailableToys);
|
||
return fetch(apiUrl('/toy/available'))
|
||
.then(r => r.json())
|
||
.then(toys => { _allAvailableToys = toys || []; return _allAvailableToys; });
|
||
}
|
||
|
||
function renderSelectedToys() {
|
||
const container = document.getElementById('iSelectedToys');
|
||
if (_selectedToys.length === 0) { container.innerHTML = ''; return; }
|
||
container.innerHTML = _selectedToys.map(t => {
|
||
const img = t.bild ? `<img src="data:image/png;base64,${t.bild}" alt="">` : '';
|
||
return `<span class="sel-toy-chip">${img}${esc(t.name)}<button class="toy-remove" type="button" onclick="removeToy('${esc(t.toyId)}')" title="Entfernen">✕</button></span>`;
|
||
}).join('');
|
||
}
|
||
|
||
function removeToy(toyId) {
|
||
_selectedToys = _selectedToys.filter(t => t.toyId !== toyId);
|
||
renderSelectedToys();
|
||
// update search modal chips if open
|
||
const chip = document.querySelector(`#toySearchResults .toy-result-chip[data-id="${toyId}"]`);
|
||
if (chip) chip.classList.remove('selected');
|
||
}
|
||
|
||
// Toy-Suche (inline)
|
||
document.getElementById('iToyAddBtn').addEventListener('click', toggleToySearch);
|
||
document.getElementById('toySearchInput').addEventListener('input', renderToySearchResults);
|
||
|
||
function toggleToySearch() {
|
||
const area = document.getElementById('iToySearchArea');
|
||
if (area.style.display === 'none') {
|
||
area.style.display = 'block';
|
||
document.getElementById('toySearchInput').value = '';
|
||
document.getElementById('toySearchResults').innerHTML = '';
|
||
document.getElementById('toySearchEmpty').style.display = 'none';
|
||
document.getElementById('iToyAddBtn').textContent = '▲ Suche schließen';
|
||
_loadAvailableToys()
|
||
.then(() => renderToySearchResults())
|
||
.catch(() => {
|
||
document.getElementById('toySearchResults').innerHTML =
|
||
'<span style="font-size:0.82rem;color:var(--color-muted)">Fehler beim Laden.</span>';
|
||
});
|
||
document.getElementById('toySearchInput').focus();
|
||
} else {
|
||
closeToySearch();
|
||
}
|
||
}
|
||
|
||
function closeToySearch() {
|
||
document.getElementById('iToySearchArea').style.display = 'none';
|
||
document.getElementById('iToyAddBtn').textContent = '+ Toy hinzufügen';
|
||
}
|
||
|
||
function renderToySearchResults() {
|
||
const query = document.getElementById('toySearchInput').value.trim().toLowerCase();
|
||
const results = document.getElementById('toySearchResults');
|
||
const empty = document.getElementById('toySearchEmpty');
|
||
if (!_allAvailableToys) return;
|
||
const filtered = query
|
||
? _allAvailableToys.filter(t => t.name.toLowerCase().includes(query))
|
||
: _allAvailableToys;
|
||
if (filtered.length === 0) {
|
||
results.innerHTML = '';
|
||
empty.style.display = 'block';
|
||
return;
|
||
}
|
||
empty.style.display = 'none';
|
||
const selectedIds = new Set(_selectedToys.map(t => t.toyId));
|
||
results.innerHTML = filtered.map(t => {
|
||
const sel = selectedIds.has(t.toyId) ? ' selected' : '';
|
||
const img = t.bild ? `<img src="data:image/png;base64,${t.bild}" alt="">` : '';
|
||
return `<span class="toy-result-chip${sel}" data-id="${esc(t.toyId)}" onclick="toggleToyFromSearch('${esc(t.toyId)}')">${img}${esc(t.name)}</span>`;
|
||
}).join('');
|
||
}
|
||
|
||
function toggleToyFromSearch(toyId) {
|
||
const toy = (_allAvailableToys || []).find(t => t.toyId === toyId);
|
||
if (!toy) return;
|
||
const idx = _selectedToys.findIndex(t => t.toyId === toyId);
|
||
if (idx >= 0) {
|
||
_selectedToys.splice(idx, 1);
|
||
} else {
|
||
_selectedToys.push({ toyId: toy.toyId, name: toy.name, bild: toy.bild });
|
||
}
|
||
renderSelectedToys();
|
||
renderToySearchResults();
|
||
}
|
||
|
||
function _initToys(preSelected) {
|
||
// preSelected: [{toyId, name, bild}]
|
||
_selectedToys = preSelected || [];
|
||
renderSelectedToys();
|
||
}
|
||
|
||
// ── Item-Hinzufügen-Modal ──
|
||
const itemModal = document.getElementById('itemModal');
|
||
const itemSaveBtn = document.getElementById('itemSaveBtn');
|
||
let currentItemGruppeId = null;
|
||
let currentItemKind = null; // 'aufgabe' | 'strafe' | 'zeitstrafe'
|
||
let currentItemEditId = null; // null = neu, sonst ID des zu bearbeitenden Items
|
||
|
||
const ITEM_TITLES_NEW = { aufgabe: 'Aufgabe hinzufügen', strafe: 'Strafe hinzufügen', zeitstrafe: 'Zeitstrafe hinzufügen', finisher: 'Finisher hinzufügen' };
|
||
const ITEM_TITLES_EDIT = { aufgabe: 'Aufgabe bearbeiten', strafe: 'Strafe bearbeiten', zeitstrafe: 'Zeitstrafe bearbeiten', finisher: 'Finisher bearbeiten' };
|
||
|
||
function _setupItemModal(kind) {
|
||
const isZeit = kind === 'zeitstrafe';
|
||
const isFinisher = kind === 'finisher';
|
||
document.querySelector('#iPlaceholderHint .placeholder-hint').innerHTML =
|
||
isFinisher
|
||
? 'In Texten können Platzhalter verwendet werden:<br>' +
|
||
'<code>{AKTIV}</code> – Name der Person die kommt<br>' +
|
||
'<code>{PASSIV}</code> – Name der Person die zum Kommen bringt'
|
||
: 'In Texten können Platzhalter verwendet werden:<br>' +
|
||
'<code>{AKTIV}</code> – Name des aktiven Parts<br>' +
|
||
'<code>{PASSIV}</code> – Name des passiven Parts';
|
||
document.getElementById('iGeschlechtRow').style.display = isFinisher ? 'block' : 'none';
|
||
document.getElementById('iLevelRow').style.display = (!isZeit && !isFinisher) ? 'block' : 'none';
|
||
document.getElementById('iWerkzeugAktivRow').style.display = (!isZeit && !isFinisher) ? 'block' : 'none';
|
||
document.getElementById('iWerkzeugPassivRow').style.display = (!isZeit && !isFinisher) ? 'block' : 'none';
|
||
document.getElementById('iWerkzeugFinisherAktivRow').style.display = isFinisher ? 'block' : 'none';
|
||
document.getElementById('iWerkzeugFinisherPassivRow').style.display = isFinisher ? 'block' : 'none';
|
||
document.getElementById('iMinutenRow').style.display = isZeit ? 'block' : 'none';
|
||
document.getElementById('iSperreFuerRow').style.display = isZeit ? 'block' : 'none';
|
||
document.getElementById('iReleaseTextRow').style.display = isZeit ? 'block' : 'none';
|
||
}
|
||
|
||
function _resetItemFields() {
|
||
document.getElementById('iKurzText').value = '';
|
||
document.getElementById('iText').value = '';
|
||
document.getElementById('iLevel').value = '';
|
||
document.getElementById('iSekVon').value = '';
|
||
document.getElementById('iSekBis').value = '';
|
||
document.getElementById('iMinVon').value = '';
|
||
document.getElementById('iMinBis').value = '';
|
||
document.getElementById('iReleaseText').value = '';
|
||
document.querySelectorAll('#iWerkzeugAktiv input').forEach(cb => cb.checked = false);
|
||
document.querySelectorAll('#iWerkzeugPassiv input').forEach(cb => cb.checked = false);
|
||
document.querySelectorAll('#iWerkzeugFinisherAktiv input').forEach(cb => cb.checked = false);
|
||
document.querySelectorAll('#iWerkzeugFinisherPassiv input').forEach(cb => cb.checked = false);
|
||
document.querySelectorAll('#iSperreFuer input').forEach(cb => cb.checked = false);
|
||
document.querySelectorAll('#iGeschlecht input').forEach(rb => rb.checked = false);
|
||
_selectedToys = [];
|
||
renderSelectedToys();
|
||
document.getElementById('itemModalError').style.display = 'none';
|
||
}
|
||
|
||
function openItemModal(gruppenId, kind) {
|
||
currentItemGruppeId = gruppenId;
|
||
currentItemKind = kind;
|
||
currentItemEditId = null;
|
||
document.getElementById('itemModalTitle').textContent = ITEM_TITLES_NEW[kind];
|
||
_resetItemFields();
|
||
_setupItemModal(kind);
|
||
_initToys([]);
|
||
itemModal.classList.add('open');
|
||
document.getElementById('iKurzText').focus();
|
||
}
|
||
|
||
function openEditItemModal(itemId, event) {
|
||
event.stopPropagation();
|
||
const d = _itemData[itemId];
|
||
if (!d) return;
|
||
currentItemGruppeId = d._gruppenId;
|
||
currentItemKind = d._kind;
|
||
currentItemEditId = itemId;
|
||
document.getElementById('itemModalTitle').textContent = ITEM_TITLES_EDIT[d._kind];
|
||
_resetItemFields();
|
||
_setupItemModal(d._kind);
|
||
|
||
document.getElementById('iKurzText').value = d.kurzText || '';
|
||
document.getElementById('iText').value = d.text || '';
|
||
|
||
if (d._kind === 'aufgabe' || d._kind === 'strafe') {
|
||
document.getElementById('iLevel').value = d.level != null ? d.level : '';
|
||
document.getElementById('iSekVon').value = d.sekundenVon != null ? d.sekundenVon : '';
|
||
document.getElementById('iSekBis').value = d.sekundenBis != null ? d.sekundenBis : '';
|
||
(d.benoetigtAktiv || []).forEach(w => { const cb = document.querySelector(`#iWerkzeugAktiv input[value="${w}"]`); if (cb) cb.checked = true; });
|
||
(d.benoetigtPassiv || []).forEach(w => { const cb = document.querySelector(`#iWerkzeugPassiv input[value="${w}"]`); if (cb) cb.checked = true; });
|
||
} else if (d._kind === 'finisher') {
|
||
(d.benoetigtAktiv || []).forEach(w => { const cb = document.querySelector(`#iWerkzeugFinisherAktiv input[value="${w}"]`); if (cb) cb.checked = true; });
|
||
(d.benoetigtPassiv || []).forEach(w => { const cb = document.querySelector(`#iWerkzeugFinisherPassiv input[value="${w}"]`); if (cb) cb.checked = true; });
|
||
if (d.geschlecht) {
|
||
const rb = document.querySelector(`#iGeschlecht input[value="${d.geschlecht}"]`);
|
||
if (rb) rb.checked = true;
|
||
}
|
||
} else {
|
||
document.getElementById('iMinVon').value = d.minutenVon != null ? d.minutenVon : '';
|
||
document.getElementById('iMinBis').value = d.minutenBis != null ? d.minutenBis : '';
|
||
document.getElementById('iReleaseText').value = d.releaseText || '';
|
||
(d.sperreFuer || []).forEach(w => { const cb = document.querySelector(`#iSperreFuer input[value="${w}"]`); if (cb) cb.checked = true; });
|
||
}
|
||
|
||
const preSelected = (d.benoetigteToys || []).filter(t => t.toyId);
|
||
_initToys(preSelected);
|
||
itemModal.classList.add('open');
|
||
document.getElementById('iKurzText').focus();
|
||
}
|
||
|
||
function closeItemModal() { itemModal.classList.remove('open'); closeToySearch(); document.getElementById('iPlaceholderHint').style.display = 'none'; document.getElementById('iTextAC').style.display = 'none'; }
|
||
function togglePlaceholderHint() {
|
||
const el = document.getElementById('iPlaceholderHint');
|
||
el.style.display = el.style.display === 'none' ? 'block' : 'none';
|
||
}
|
||
|
||
(function() {
|
||
const STATIC = ['{AKTIV}', '{PASSIV}'];
|
||
const ta = document.getElementById('iText');
|
||
const ac = document.getElementById('iTextAC');
|
||
let _allToys = [];
|
||
let _items = [];
|
||
let _wordStart = 0;
|
||
let activeIdx = -1;
|
||
|
||
function currentWord() {
|
||
const pos = ta.selectionStart;
|
||
let start = pos;
|
||
while (start > 0 && !/\s/.test(ta.value[start - 1])) start--;
|
||
return { word: ta.value.slice(start, pos).toLowerCase(), start };
|
||
}
|
||
function buildItems(filter) {
|
||
const f = filter || '';
|
||
_items = STATIC.filter(s => !f || s.toLowerCase().includes(f)).map(s => ({ label: s, insert: s }));
|
||
const toys = _allToys.filter(t => !f || t.name.toLowerCase().includes(f));
|
||
if (toys.length) {
|
||
_items.push({ separator: true, label: 'Toys' });
|
||
toys.forEach(t => _items.push({ label: t.name, insert: t.name, toyId: t.toyId }));
|
||
}
|
||
}
|
||
function selectables() { return _items.map((it, i) => it.separator ? null : i).filter(i => i !== null); }
|
||
function renderItems() {
|
||
ac.innerHTML = '';
|
||
activeIdx = -1;
|
||
_items.forEach((item, i) => {
|
||
if (item.separator) {
|
||
const sep = document.createElement('div');
|
||
sep.className = 'ac-separator';
|
||
sep.textContent = item.label;
|
||
ac.appendChild(sep);
|
||
} else {
|
||
const div = document.createElement('div');
|
||
div.className = 'ac-item';
|
||
div.dataset.idx = String(i);
|
||
div.textContent = item.label;
|
||
div.addEventListener('mousedown', e => { e.preventDefault(); doInsert(item); });
|
||
div.addEventListener('mouseover', () => setActive(i));
|
||
ac.appendChild(div);
|
||
}
|
||
});
|
||
const s = selectables();
|
||
if (s.length) { setActive(s[0]); ac.style.display = 'block'; } else hideAC();
|
||
}
|
||
function showAC(toys) {
|
||
_allToys = toys || [];
|
||
const { word, start } = currentWord();
|
||
_wordStart = start;
|
||
buildItems(word);
|
||
const rect = ta.getBoundingClientRect();
|
||
ac.style.left = rect.left + 'px';
|
||
ac.style.top = (rect.bottom + 4) + 'px';
|
||
renderItems();
|
||
}
|
||
function hideAC() { ac.style.display = 'none'; activeIdx = -1; }
|
||
function setActive(i) {
|
||
activeIdx = i;
|
||
let activeEl = null;
|
||
ac.querySelectorAll('.ac-item').forEach(el => {
|
||
const on = parseInt(el.dataset.idx) === i;
|
||
el.classList.toggle('ac-item-active', on);
|
||
if (on) activeEl = el;
|
||
});
|
||
if (activeEl) activeEl.scrollIntoView({ block: 'nearest' });
|
||
}
|
||
function doInsert(item) {
|
||
const end = ta.selectionStart;
|
||
ta.value = ta.value.slice(0, _wordStart) + item.insert + ta.value.slice(end);
|
||
ta.selectionStart = ta.selectionEnd = _wordStart + item.insert.length;
|
||
ta.focus();
|
||
if (item.toyId) {
|
||
const already = (_selectedToys || []).find(t => t.toyId === item.toyId);
|
||
if (!already) toggleToyFromSearch(item.toyId);
|
||
}
|
||
hideAC();
|
||
}
|
||
ta.addEventListener('keydown', e => {
|
||
if (e.ctrlKey && e.code === 'Space') {
|
||
e.preventDefault();
|
||
_loadAvailableToys().then(toys => showAC(toys)).catch(() => showAC([]));
|
||
return;
|
||
}
|
||
if (ac.style.display !== 'block') return;
|
||
if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); hideAC(); return; }
|
||
const s = selectables();
|
||
if (!s.length) return;
|
||
const pos = s.indexOf(activeIdx);
|
||
if (e.key === 'ArrowDown') { e.preventDefault(); setActive(s[(pos + 1) % s.length]); }
|
||
else if (e.key === 'ArrowUp') { e.preventDefault(); setActive(s[(pos - 1 + s.length) % s.length]); }
|
||
else if (e.key === 'Enter' || e.key === 'Tab') {
|
||
e.preventDefault();
|
||
const item = _items[activeIdx];
|
||
if (item && !item.separator) doInsert(item);
|
||
}
|
||
});
|
||
ta.addEventListener('input', () => {
|
||
if (ac.style.display !== 'block') return;
|
||
const { word, start } = currentWord();
|
||
_wordStart = start;
|
||
buildItems(word);
|
||
renderItems();
|
||
});
|
||
document.addEventListener('mousedown', e => { if (!ac.contains(e.target) && e.target !== ta) hideAC(); });
|
||
})();
|
||
|
||
document.getElementById('itemCancelBtn').addEventListener('click', closeItemModal);
|
||
itemModal.addEventListener('click', e => { if (e.target === itemModal) closeItemModal(); });
|
||
|
||
itemSaveBtn.addEventListener('click', async () => {
|
||
const kurzText = document.getElementById('iKurzText').value.trim();
|
||
const text = document.getElementById('iText').value.trim();
|
||
if (!kurzText) { showItemError('Bitte eine Kurzbezeichnung eingeben.'); return; }
|
||
if (!text) { showItemError('Bitte eine Beschreibung eingeben.'); return; }
|
||
|
||
const kind = currentItemKind;
|
||
const isEdit = currentItemEditId !== null;
|
||
let url, method, payload;
|
||
|
||
if (kind === 'aufgabe' || kind === 'strafe') {
|
||
const levelVal = document.getElementById('iLevel').value.trim();
|
||
if (!levelVal) { showItemError('Bitte ein Level angeben.'); return; }
|
||
const level = parseInt(levelVal, 10);
|
||
if (isNaN(level) || level < 1 || level > 5) { showItemError('Level muss zwischen 1 und 5 liegen.'); return; }
|
||
|
||
const sekVon = document.getElementById('iSekVon').value.trim();
|
||
const sekBis = document.getElementById('iSekBis').value.trim();
|
||
payload = {
|
||
kurzText, text, level,
|
||
gruppeId: isEdit ? undefined : currentItemGruppeId,
|
||
sekundenVon: sekVon ? parseInt(sekVon, 10) : null,
|
||
sekundenBis: sekBis ? parseInt(sekBis, 10) : null,
|
||
benoetigtAktiv: checkedValues('iWerkzeugAktiv'),
|
||
benoetigtPassiv: checkedValues('iWerkzeugPassiv'),
|
||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
|
||
};
|
||
const base = kind === 'aufgabe' ? apiUrl('/aufgabe') : '/strafe';
|
||
url = isEdit ? `${base}/${currentItemEditId}` : base;
|
||
method = isEdit ? 'PUT' : 'POST';
|
||
|
||
} else if (kind === 'finisher') {
|
||
const geschlecht = document.querySelector('#iGeschlecht input:checked')?.value;
|
||
if (!geschlecht) { showItemError('Bitte ein Geschlecht auswählen.'); return; }
|
||
payload = {
|
||
kurzText, text, geschlecht,
|
||
gruppeId: isEdit ? undefined : currentItemGruppeId,
|
||
benoetigtAktiv: checkedValues('iWerkzeugFinisherAktiv'),
|
||
benoetigtPassiv: checkedValues('iWerkzeugFinisherPassiv'),
|
||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
|
||
};
|
||
url = isEdit ? apiUrl(`/finisher/${currentItemEditId}`) : apiUrl('/finisher');
|
||
method = isEdit ? 'PUT' : 'POST';
|
||
|
||
} else {
|
||
const minVon = document.getElementById('iMinVon').value.trim();
|
||
if (!minVon) { showItemError('Bitte eine Mindestdauer in Minuten angeben.'); return; }
|
||
const sperreFuer = checkedValues('iSperreFuer');
|
||
if (sperreFuer.length === 0) { showItemError('Bitte mindestens ein Werkzeug für die Sperre auswählen.'); return; }
|
||
|
||
const minBis = document.getElementById('iMinBis').value.trim();
|
||
payload = {
|
||
kurzText, text,
|
||
gruppeId: isEdit ? undefined : currentItemGruppeId,
|
||
minutenVon: parseInt(minVon, 10),
|
||
minutenBis: minBis ? parseInt(minBis, 10) : null,
|
||
releaseText: document.getElementById('iReleaseText').value.trim() || null,
|
||
sperreFuer,
|
||
benoetigteToys: _selectedToys.map(t => ({ toyId: t.toyId }))
|
||
};
|
||
url = isEdit ? `/sperre/${currentItemEditId}` : '/sperre'; // BDSM-only
|
||
method = isEdit ? 'PUT' : 'POST';
|
||
}
|
||
|
||
itemSaveBtn.disabled = true;
|
||
itemSaveBtn.textContent = 'Speichert…';
|
||
|
||
fetch(url, {
|
||
method,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
})
|
||
.then(r => {
|
||
if (r.ok || r.status === 201) {
|
||
closeItemModal();
|
||
pendingExpandId = currentItemGruppeId;
|
||
pendingExpandType = 'user';
|
||
userPage = 0;
|
||
_notifyOnLoad = true; loadUserGruppen();
|
||
} else if (r.status === 409) {
|
||
showItemError('Limit erreicht: maximal 100 Einträge pro Gruppe möglich.');
|
||
} else {
|
||
showItemError('Fehler beim Speichern (HTTP ' + r.status + ').');
|
||
}
|
||
})
|
||
.catch(() => showItemError('Verbindungsfehler.'))
|
||
.finally(() => { itemSaveBtn.disabled = false; itemSaveBtn.textContent = 'Speichern'; });
|
||
});
|
||
|
||
function showItemError(msg) {
|
||
const el = document.getElementById('itemModalError');
|
||
el.textContent = msg; el.style.display = 'block';
|
||
}
|
||
|
||
function checkedValues(containerId) {
|
||
return Array.from(document.querySelectorAll(`#${containerId} input:checked`)).map(cb => cb.value);
|
||
}
|
||
|
||
// ── Image scaling ──
|
||
function toBase64(file) {
|
||
const MAX = 128;
|
||
return new Promise((resolve, reject) => {
|
||
const img = new Image();
|
||
const url = URL.createObjectURL(file);
|
||
img.onload = () => {
|
||
URL.revokeObjectURL(url);
|
||
let w = img.naturalWidth, h = img.naturalHeight;
|
||
if (w > MAX || h > MAX) {
|
||
if (w >= h) { h = Math.max(1, Math.round(MAX * h / w)); w = MAX; }
|
||
else { w = Math.max(1, Math.round(MAX * w / h)); h = MAX; }
|
||
}
|
||
const canvas = document.createElement('canvas');
|
||
canvas.width = w; canvas.height = h;
|
||
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
|
||
resolve(canvas.toDataURL('image/png').split(',')[1]);
|
||
};
|
||
img.onerror = reject;
|
||
img.src = url;
|
||
});
|
||
}
|
||
|
||
// ── XSS ──
|
||
function esc(str) {
|
||
if (str == null) return '';
|
||
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
|
||
// ── Publish-Modal ──
|
||
const publishModal = document.getElementById('publishModal');
|
||
const publishConfirmBtn = document.getElementById('publishConfirmBtn');
|
||
const publishConfirmCb = document.getElementById('publishConfirmCb');
|
||
|
||
function openPublishModal() {
|
||
if (!selectedGruppeId || !_gruppeData[selectedGruppeId]?.privateGruppe) return;
|
||
publishConfirmCb.checked = false;
|
||
publishConfirmBtn.disabled = true;
|
||
document.getElementById('publishError').style.display = 'none';
|
||
publishModal.classList.add('open');
|
||
}
|
||
|
||
function closePublishModal() {
|
||
publishModal.classList.remove('open');
|
||
}
|
||
|
||
publishConfirmCb.addEventListener('change', () => {
|
||
publishConfirmBtn.disabled = !publishConfirmCb.checked;
|
||
});
|
||
|
||
document.getElementById('publishCancelBtn').addEventListener('click', closePublishModal);
|
||
publishModal.addEventListener('click', e => { if (e.target === publishModal) closePublishModal(); });
|
||
document.getElementById('publishBtn').addEventListener('click', openPublishModal);
|
||
|
||
publishConfirmBtn.addEventListener('click', () => {
|
||
const g = _gruppeData[selectedGruppeId];
|
||
if (!g) return;
|
||
publishConfirmBtn.disabled = true;
|
||
publishConfirmBtn.textContent = 'Wird veröffentlicht…';
|
||
|
||
fetch(apiUrl(`/gruppe/${selectedGruppeId}`), {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
name: g.name,
|
||
beschreibung: g.beschreibung || null,
|
||
privateGruppe: false,
|
||
bild: g.bild || null
|
||
})
|
||
})
|
||
.then(r => {
|
||
if (r.ok) {
|
||
closePublishModal();
|
||
pendingExpandId = selectedGruppeId;
|
||
pendingExpandType = 'user';
|
||
userPage = 0;
|
||
_notifyOnLoad = true; loadUserGruppen();
|
||
} else {
|
||
const errEl = document.getElementById('publishError');
|
||
errEl.textContent = 'Fehler beim Veröffentlichen (HTTP ' + r.status + ').';
|
||
errEl.style.display = 'block';
|
||
publishConfirmBtn.disabled = false;
|
||
}
|
||
})
|
||
.catch(() => {
|
||
const errEl = document.getElementById('publishError');
|
||
errEl.textContent = 'Verbindungsfehler.';
|
||
errEl.style.display = 'block';
|
||
publishConfirmBtn.disabled = false;
|
||
})
|
||
.finally(() => { publishConfirmBtn.textContent = 'Veröffentlichen'; });
|
||
});
|
||
|
||
// ── ESC schließt alle Modals ──
|
||
document.addEventListener('keydown', e => {
|
||
if (e.key !== 'Escape') return;
|
||
if (publishModal.classList.contains('open')) { closePublishModal(); return; }
|
||
if (gruppeModal.classList.contains('open')) { closeGruppeModal(); return; }
|
||
if (itemModal.classList.contains('open')) { closeItemModal(); return; }
|
||
if (confirmModal.classList.contains('open')) { closeConfirmModal(); return; }
|
||
});
|
||
|
||
const confirmModal = document.getElementById('confirmModal');
|
||
document.getElementById('confirmModalCancel').addEventListener('click', closeConfirmModal);
|
||
confirmModal.addEventListener('click', e => { if (e.target === confirmModal) closeConfirmModal(); });
|
||
|
||
function closeConfirmModal() { confirmModal.classList.remove('open'); }
|
||
|
||
function openConfirmModal(text, onConfirm) {
|
||
document.getElementById('confirmModalText').textContent = text;
|
||
const okBtn = document.getElementById('confirmModalOk');
|
||
const handler = () => { okBtn.removeEventListener('click', handler); closeConfirmModal(); onConfirm(); };
|
||
okBtn.removeEventListener('click', handler);
|
||
okBtn.addEventListener('click', handler);
|
||
confirmModal.classList.add('open');
|
||
}
|
||
|
||
</script>
|
||
<script src="/js/icons.js"></script>
|
||
<script src="/js/nav.js"></script>
|
||
</body>
|
||
</html>
|