1579 lines
78 KiB
HTML
1579 lines
78 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Post Tracker</title>
|
||
<link rel="icon" type="image/png" sizes="32x32" href="assets/app-icon-64.png">
|
||
<link rel="icon" type="image/png" sizes="192x192" href="assets/app-icon-192.png">
|
||
<link rel="apple-touch-icon" href="assets/app-icon-192.png">
|
||
<style>
|
||
body.auth-pending {
|
||
visibility: hidden;
|
||
overflow: hidden;
|
||
}
|
||
</style>
|
||
<script>
|
||
(function gateAssets() {
|
||
const API_URL = 'https://fb.srv.medeba-media.de/api';
|
||
const LOGIN_PAGE = 'login.html';
|
||
const ASSET_VERSION = '__ASSET_VERSION__';
|
||
const cssFiles = [
|
||
{ href: 'style.css' },
|
||
{ href: 'dashboard.css' },
|
||
{ href: 'settings.css' },
|
||
{ href: 'automation.css' },
|
||
{ href: 'daily-bookmarks.css', id: 'dailyBookmarksCss', disabled: true }
|
||
];
|
||
const jsFiles = [
|
||
'app.js',
|
||
'dashboard.js',
|
||
'settings.js',
|
||
'vendor/list.min.js',
|
||
'automation.js',
|
||
'daily-bookmarks.js'
|
||
];
|
||
|
||
function withVersion(value) {
|
||
if (!ASSET_VERSION) {
|
||
return value;
|
||
}
|
||
const joiner = value.includes('?') ? '&' : '?';
|
||
return `${value}${joiner}v=${ASSET_VERSION}`;
|
||
}
|
||
|
||
function redirectToLogin() {
|
||
try {
|
||
const redirect = encodeURIComponent(window.location.href);
|
||
window.location.replace(`${LOGIN_PAGE}?redirect=${redirect}`);
|
||
} catch (_error) {
|
||
window.location.replace(LOGIN_PAGE);
|
||
}
|
||
}
|
||
|
||
function loadStyles() {
|
||
cssFiles.forEach(({ href, id, disabled }) => {
|
||
const link = document.createElement('link');
|
||
link.rel = 'stylesheet';
|
||
link.href = withVersion(href);
|
||
if (id) link.id = id;
|
||
if (disabled) link.disabled = true;
|
||
document.head.appendChild(link);
|
||
});
|
||
}
|
||
|
||
function loadScriptsSequentially(list, index = 0) {
|
||
return new Promise((resolve, reject) => {
|
||
if (index >= list.length) {
|
||
resolve();
|
||
return;
|
||
}
|
||
const script = document.createElement('script');
|
||
script.src = withVersion(list[index]);
|
||
script.onload = () => loadScriptsSequentially(list, index + 1).then(resolve).catch(reject);
|
||
script.onerror = reject;
|
||
document.body.appendChild(script);
|
||
});
|
||
}
|
||
|
||
async function bootstrap() {
|
||
try {
|
||
const res = await fetch(`${API_URL}/session`, { credentials: 'include' });
|
||
if (res.status === 401) {
|
||
redirectToLogin();
|
||
return;
|
||
}
|
||
loadStyles();
|
||
await loadScriptsSequentially(jsFiles);
|
||
} catch (_error) {
|
||
redirectToLogin();
|
||
}
|
||
}
|
||
|
||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||
bootstrap();
|
||
} else {
|
||
document.addEventListener('DOMContentLoaded', bootstrap);
|
||
}
|
||
})();
|
||
</script>
|
||
</head>
|
||
<body class="auth-pending">
|
||
<div class="shell">
|
||
<header class="site-header site-header--global">
|
||
<div class="container">
|
||
<div class="header-main">
|
||
<div>
|
||
<h1>📋 Post Tracker</h1>
|
||
</div>
|
||
<div class="site-nav">
|
||
<a class="site-nav__btn" data-view-target="posts" href="posts.html">📝 Beiträge</a>
|
||
<a class="site-nav__btn" data-view-target="dashboard" href="dashboard.html">📊 Dashboard</a>
|
||
<a class="site-nav__btn" data-view-target="settings" href="settings.html">⚙️ Einstellungen</a>
|
||
<a class="site-nav__btn" data-view-target="bookmarks" href="bookmarks.html">🔖 Bookmarks</a>
|
||
<a class="site-nav__btn" data-view-target="automation" href="index.html?view=automation">⚡️ Automationen</a>
|
||
<a class="site-nav__btn" data-view-target="daily-bookmarks" href="index.html?view=daily-bookmarks">✅ Daily Bookmarks</a>
|
||
<button class="site-nav__btn site-nav__btn--icon" id="logoutBtn" type="button" aria-label="Abmelden">🚪</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<main class="shell-main">
|
||
<section id="view-posts" class="app-view app-view--active" data-view="posts">
|
||
<div class="container">
|
||
<div class="page-toolbar">
|
||
<div class="page-toolbar__title">
|
||
<h2>Beiträge</h2>
|
||
<button type="button" class="btn btn-primary" id="openManualPostModalBtn">Beitrag hinzufügen</button>
|
||
</div>
|
||
<div class="header-controls">
|
||
<div class="control-group">
|
||
<label for="profileSelect">Dein Profil:</label>
|
||
<select id="profileSelect" class="control-select">
|
||
<option value="1">Profil 1</option>
|
||
<option value="2">Profil 2</option>
|
||
<option value="3">Profil 3</option>
|
||
<option value="4">Profil 4</option>
|
||
<option value="5">Profil 5</option>
|
||
</select>
|
||
</div>
|
||
<div class="control-group">
|
||
<label class="switch">
|
||
<input type="checkbox" id="autoRefreshToggle" checked>
|
||
<span>Auto-Refresh</span>
|
||
</label>
|
||
<select id="autoRefreshInterval" class="control-select">
|
||
<option value="15000">15 s</option>
|
||
<option value="30000">30 s</option>
|
||
<option value="60000">1 min</option>
|
||
<option value="120000">2 min</option>
|
||
<option value="300000">5 min</option>
|
||
</select>
|
||
<button type="button" id="manualRefreshBtn" class="refresh-btn" aria-label="Aktualisieren" title="Aktualisieren">🔄</button>
|
||
</div>
|
||
<div class="control-group">
|
||
<label for="sortMode">Sortierung:</label>
|
||
<div class="sort-controls">
|
||
<select id="sortMode" class="control-select">
|
||
<option value="created">Erstelldatum</option>
|
||
<option value="deadline">Deadline</option>
|
||
<option value="lastCheck">Letzte Teilnahme</option>
|
||
<option value="lastChange">Letzte Änderung</option>
|
||
<option value="smart">Smart (Dringlichkeit)</option>
|
||
</select>
|
||
<button type="button" id="sortDirectionToggle" class="sort-direction-toggle" aria-label="Absteigend" aria-pressed="false" title="Absteigend">
|
||
<span class="sort-direction-toggle__icon" aria-hidden="true">▼</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tabs-section">
|
||
<div class="tabs">
|
||
<button class="tab-btn" data-tab="all">Alle Beiträge</button>
|
||
<button class="tab-btn active" data-tab="pending">Offene Beiträge</button>
|
||
</div>
|
||
<div class="merge-controls" id="mergeControls" hidden>
|
||
<div class="merge-actions">
|
||
<button type="button" class="btn btn-secondary" id="mergeModeToggle">Merge-Modus</button>
|
||
<button type="button" class="btn btn-primary" id="mergeSubmitBtn" disabled>Beiträge mergen</button>
|
||
</div>
|
||
</div>
|
||
<div class="search-container">
|
||
<label class="search-filter-toggle" for="includeExpiredToggle">
|
||
<input type="checkbox" id="includeExpiredToggle">
|
||
<span>Abgelaufene/abgeschlossene anzeigen</span>
|
||
</label>
|
||
<input type="text" id="searchInput" class="search-input" placeholder="Beiträge durchsuchen...">
|
||
</div>
|
||
<div class="posts-bulk-controls" id="pendingBulkControls" hidden>
|
||
<div class="bulk-actions">
|
||
<label for="pendingBulkCountSelect">Anzahl</label>
|
||
<select id="pendingBulkCountSelect">
|
||
<option value="1">1</option>
|
||
<option value="5">5</option>
|
||
<option value="10">10</option>
|
||
<option value="15">15</option>
|
||
<option value="20">20</option>
|
||
</select>
|
||
<label class="auto-open-toggle">
|
||
<input type="checkbox" id="pendingAutoOpenToggle">
|
||
<span>Auto öffnen</span>
|
||
</label>
|
||
<button type="button" class="btn btn-secondary" id="pendingBulkOpenBtn">Links öffnen</button>
|
||
</div>
|
||
<div id="pendingBulkStatus" class="bulk-status" role="status" aria-live="polite"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="loading" class="loading">Lade Beiträge...</div>
|
||
<div id="error" class="error" style="display: none;"></div>
|
||
|
||
<div id="pendingAutoOpenOverlay" class="auto-open-overlay" hidden>
|
||
<div class="auto-open-overlay__panel" id="pendingAutoOpenOverlayPanel">
|
||
<div class="auto-open-overlay__badge">Auto-Öffnen startet gleich</div>
|
||
<div class="auto-open-overlay__timer">
|
||
<span id="pendingAutoOpenCountdown" class="auto-open-overlay__count">0.0</span>
|
||
<span class="auto-open-overlay__unit">Sek.</span>
|
||
</div>
|
||
<p class="auto-open-overlay__text">
|
||
Die nächsten offenen Beiträge werden automatisch geöffnet. Abbrechen, falls du noch warten willst.
|
||
</p>
|
||
<p class="auto-open-overlay__hint">Klicke irgendwo in dieses Panel, um abzubrechen.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="postsContainer" class="posts-container"></div>
|
||
</div>
|
||
|
||
<div id="screenshotModal" class="screenshot-modal" hidden>
|
||
<div id="screenshotModalBackdrop" class="screenshot-modal__backdrop" aria-hidden="true"></div>
|
||
<div id="screenshotModalContent" class="screenshot-modal__content" role="dialog" aria-modal="true">
|
||
<button type="button" id="screenshotModalClose" class="screenshot-modal__close" aria-label="Schließen">×</button>
|
||
<img id="screenshotModalImage" alt="Screenshot zum Beitrag" />
|
||
</div>
|
||
</div>
|
||
|
||
<div id="manualPostModal" class="modal" hidden>
|
||
<div id="manualPostModalBackdrop" class="modal__backdrop" aria-hidden="true"></div>
|
||
<div id="manualPostModalContent" class="modal__content" role="dialog" aria-modal="true" aria-labelledby="manualPostModalTitle" tabindex="-1">
|
||
<button type="button" id="manualPostModalClose" class="modal__close" aria-label="Schließen">×</button>
|
||
<h2 id="manualPostModalTitle">Beitrag hinzufügen</h2>
|
||
<form id="manualPostForm" novalidate>
|
||
<div class="form-grid">
|
||
<label class="form-field">
|
||
<span>Direktlink *</span>
|
||
<input type="url" id="manualPostUrl" placeholder="https://www.facebook.com/..." required>
|
||
</label>
|
||
<label class="form-field">
|
||
<span>Titel</span>
|
||
<input type="text" id="manualPostTitle" placeholder="Kurzbeschreibung" maxlength="200">
|
||
</label>
|
||
<label class="form-field">
|
||
<span>Benötigte Profile *</span>
|
||
<select id="manualPostTarget" required>
|
||
<option value="1">1</option>
|
||
<option value="2">2</option>
|
||
<option value="3">3</option>
|
||
<option value="4">4</option>
|
||
<option value="5">5</option>
|
||
</select>
|
||
</label>
|
||
<label class="form-field">
|
||
<span>Erstellt von (Facebook-Name)</span>
|
||
<input type="text" id="manualPostCreatorName" placeholder="z.B. Max Mustermann">
|
||
</label>
|
||
<label class="form-field">
|
||
<span>Deadline</span>
|
||
<input type="datetime-local" id="manualPostDeadline">
|
||
</label>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary" id="manualPostSubmitBtn">Speichern</button>
|
||
<button type="button" class="btn btn-secondary" id="manualPostReset">Zurücksetzen</button>
|
||
</div>
|
||
<div id="manualPostMessage" class="form-message" role="status" aria-live="polite"></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section id="view-automation" class="app-view automation-view" data-view="automation">
|
||
<div class="container">
|
||
<div class="auto-shell">
|
||
<header class="auto-hero">
|
||
<div class="hero-head">
|
||
<div class="hero-text">
|
||
<h1>Request Automationen</h1>
|
||
</div>
|
||
<div class="hero-actions">
|
||
<button class="ghost-btn" id="openImportBtn" type="button">📥 Vorlage importieren</button>
|
||
<button class="primary-btn" id="newAutomationBtn" type="button">+ Neue Automation</button>
|
||
</div>
|
||
</div>
|
||
<div class="hero-stats" id="heroStats"></div>
|
||
</header>
|
||
|
||
<section class="panel list-panel">
|
||
<div class="panel-header">
|
||
<div>
|
||
<p class="panel-eyebrow">Geplante Requests</p>
|
||
<h2>Automationen</h2>
|
||
</div>
|
||
<div class="panel-actions">
|
||
<input id="tableFilterInput" class="filter-input" type="search" placeholder="Filtern nach Name/Typ…" />
|
||
<button class="ghost-btn" id="refreshBtn" type="button">Aktualisieren</button>
|
||
</div>
|
||
</div>
|
||
<div id="listStatus" class="list-status" aria-live="polite"></div>
|
||
<div class="table-wrap" id="automationTable">
|
||
<table class="auto-table">
|
||
<thead>
|
||
<tr>
|
||
<th data-sort-column="name">Name<span class="sort-indicator"></span></th>
|
||
<th data-sort-column="next">Nächster Lauf<span class="sort-indicator"></span></th>
|
||
<th data-sort-column="runUntil">Läuft bis<span class="sort-indicator"></span></th>
|
||
<th data-sort-column="last">Letzter Lauf<span class="sort-indicator"></span></th>
|
||
<th data-sort-column="status">Status<span class="sort-indicator"></span></th>
|
||
<th data-sort-column="runs">#Läufe<span class="sort-indicator"></span></th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
<tr class="table-filter-row">
|
||
<th><input id="filterName" type="search" placeholder="Name/Typ/E-Mail/URL"></th>
|
||
<th><input id="filterNext" type="search" placeholder="z.B. heute"></th>
|
||
<th><input id="filterRunUntil" type="search" placeholder="z.B. morgen"></th>
|
||
<th><input id="filterLast" type="search" placeholder="z.B. HTTP 200"></th>
|
||
<th>
|
||
<select id="filterStatus">
|
||
<option value="">Alle</option>
|
||
<option value="success">OK</option>
|
||
<option value="error">Fehler</option>
|
||
</select>
|
||
</th>
|
||
<th><input id="filterRuns" type="number" min="0" placeholder="≥"></th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="requestTableBody" class="list"></tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel runs-panel">
|
||
<div class="panel-header">
|
||
<div>
|
||
<p class="panel-eyebrow">Verlauf</p>
|
||
<h2>Run-Historie</h2>
|
||
</div>
|
||
<p class="runs-hint">Letzte Läufe der ausgewählten Automation.</p>
|
||
</div>
|
||
<div id="runsStatus" class="runs-status" aria-live="polite"></div>
|
||
<ul id="runsList" class="runs-list"></ul>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="formModal" class="modal" hidden>
|
||
<div class="modal__backdrop" id="formModalBackdrop"></div>
|
||
<div class="modal__content" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
|
||
<div class="modal__header">
|
||
<div>
|
||
<p class="panel-eyebrow" id="formModeLabel">Neue Automation</p>
|
||
<h2 id="modalTitle">Request & Zeitplan</h2>
|
||
</div>
|
||
<button class="ghost-btn" id="modalCloseBtn" type="button">×</button>
|
||
</div>
|
||
<form id="automationForm" class="form-grid" novalidate>
|
||
<div class="field">
|
||
<label for="typeSelect">Typ</label>
|
||
<select id="typeSelect">
|
||
<option value="request">HTTP Request</option>
|
||
<option value="email">E-Mail</option>
|
||
<option value="flow">Flow (bis 3 Schritte)</option>
|
||
</select>
|
||
</div>
|
||
<div class="field">
|
||
<label for="nameInput">Name *</label>
|
||
<input id="nameInput" type="text" placeholder="API Ping (stündlich)" required maxlength="160">
|
||
</div>
|
||
<div class="field">
|
||
<label for="descriptionInput">Notizen</label>
|
||
<textarea id="descriptionInput" rows="2" placeholder="Kurzbeschreibung oder Zweck"></textarea>
|
||
</div>
|
||
<div class="field" data-section="http">
|
||
<label for="urlInput">URL-Template *</label>
|
||
<input id="urlInput" type="url" placeholder="https://api.example.com/{{date}}/trigger" required>
|
||
</div>
|
||
<div class="field inline" data-section="http">
|
||
<label for="methodSelect">Methode</label>
|
||
<select id="methodSelect">
|
||
<option value="GET">GET</option>
|
||
<option value="POST">POST</option>
|
||
<option value="PUT">PUT</option>
|
||
<option value="PATCH">PATCH</option>
|
||
<option value="DELETE">DELETE</option>
|
||
</select>
|
||
</div>
|
||
<div class="field inline">
|
||
<label for="activeToggle">Aktiv</label>
|
||
<label class="switch">
|
||
<input type="checkbox" id="activeToggle" checked>
|
||
<span class="switch-slider"></span>
|
||
<span class="switch-label">Plan aktiv</span>
|
||
</label>
|
||
</div>
|
||
<div class="field" data-section="http">
|
||
<label for="headersInput">Headers (Key: Value pro Zeile oder JSON)</label>
|
||
<textarea id="headersInput" rows="3" placeholder="Authorization: Bearer {{token}}\nContent-Type: application/json"></textarea>
|
||
</div>
|
||
<div class="field" data-section="http">
|
||
<label for="bodyInput">Body (optional, Templates möglich)</label>
|
||
<textarea id="bodyInput" rows="5" placeholder='{"date":"{{date}}","id":"{{uuid}}"}'></textarea>
|
||
</div>
|
||
<div class="field full" data-section="email">
|
||
<label for="emailToInput">E-Mail Empfänger *</label>
|
||
<input id="emailToInput" type="text" placeholder="max@example.com, lisa@example.com">
|
||
</div>
|
||
<div class="field full" data-section="email">
|
||
<label for="emailSubjectInput">Betreff *</label>
|
||
<input id="emailSubjectInput" type="text" placeholder="Status Update {{date}}">
|
||
</div>
|
||
<div class="field full" data-section="email">
|
||
<label for="emailBodyInput">Body *</label>
|
||
<textarea id="emailBodyInput" rows="6" placeholder="Hallo,\nheutiger Status: {{uuid}}"></textarea>
|
||
</div>
|
||
<div class="field full" data-section="flow">
|
||
<div class="template-hint">
|
||
<p class="template-title">Flow Schritte</p>
|
||
<p class="template-copy">Max. 3 Schritte, Kontext steht als {{step1_json}}, {{step1_text}}, {{step1_status_code}} usw. im nächsten Schritt zur Verfügung.</p>
|
||
</div>
|
||
</div>
|
||
<div class="field" data-section="flow">
|
||
<label for="flowStep1Url">Step 1 URL *</label>
|
||
<input id="flowStep1Url" type="url" placeholder="https://api.example.com/first">
|
||
</div>
|
||
<div class="field inline" data-section="flow">
|
||
<label for="flowStep1Method">Step 1 Methode</label>
|
||
<select id="flowStep1Method">
|
||
<option value="GET">GET</option>
|
||
<option value="POST">POST</option>
|
||
<option value="PUT">PUT</option>
|
||
<option value="PATCH">PATCH</option>
|
||
<option value="DELETE">DELETE</option>
|
||
</select>
|
||
</div>
|
||
<div class="field" data-section="flow">
|
||
<label for="flowStep1Headers">Step 1 Headers</label>
|
||
<textarea id="flowStep1Headers" rows="2" placeholder="Authorization: Bearer {{token}}"></textarea>
|
||
</div>
|
||
<div class="field" data-section="flow">
|
||
<label for="flowStep1Body">Step 1 Body</label>
|
||
<textarea id="flowStep1Body" rows="3" placeholder='{"id":"{{uuid}}"}'></textarea>
|
||
</div>
|
||
<div class="field" data-section="flow">
|
||
<label for="flowStep2Url">Step 2 URL (optional)</label>
|
||
<input id="flowStep2Url" type="url" placeholder="https://api.example.com/second">
|
||
</div>
|
||
<div class="field inline" data-section="flow">
|
||
<label for="flowStep2Method">Step 2 Methode</label>
|
||
<select id="flowStep2Method">
|
||
<option value="GET">GET</option>
|
||
<option value="POST">POST</option>
|
||
<option value="PUT">PUT</option>
|
||
<option value="PATCH">PATCH</option>
|
||
<option value="DELETE">DELETE</option>
|
||
</select>
|
||
</div>
|
||
<div class="field" data-section="flow">
|
||
<label for="flowStep2Headers">Step 2 Headers</label>
|
||
<textarea id="flowStep2Headers" rows="2" placeholder="Authorization: Bearer {{token}}"></textarea>
|
||
</div>
|
||
<div class="field" data-section="flow">
|
||
<label for="flowStep2Body">Step 2 Body</label>
|
||
<textarea id="flowStep2Body" rows="3" placeholder='{"fromStep1":"{{step1_json.id}}"}'></textarea>
|
||
</div>
|
||
<div class="field inline">
|
||
<label for="intervalPreset">Intervall</label>
|
||
<select id="intervalPreset">
|
||
<option value="hourly">Jede Stunde</option>
|
||
<option value="daily">Jeden Tag</option>
|
||
<option value="custom">Eigene Minuten</option>
|
||
</select>
|
||
</div>
|
||
<div class="field inline">
|
||
<label for="intervalMinutesInput">Intervall (Minuten)</label>
|
||
<input id="intervalMinutesInput" type="number" min="5" max="20160" step="5" value="60">
|
||
</div>
|
||
<div class="field inline">
|
||
<label for="jitterInput">Varianz (Minuten)</label>
|
||
<input id="jitterInput" type="number" min="0" max="120" step="5" value="10">
|
||
<small>Auslösung erfolgt zufällig +0…Varianz Min nach dem Intervall.</small>
|
||
</div>
|
||
<div class="field inline">
|
||
<label for="startAtInput">Start ab</label>
|
||
<input id="startAtInput" type="datetime-local">
|
||
</div>
|
||
<div class="field inline">
|
||
<label for="runUntilInput">Läuft bis</label>
|
||
<input id="runUntilInput" type="datetime-local">
|
||
</div>
|
||
<div class="field full">
|
||
<label>Ausschlusszeiten (täglich, Serverzeit)</label>
|
||
<div class="exclusion-controls">
|
||
<input id="excludeStartInput" type="time" aria-label="Ausschluss von">
|
||
<span class="exclusion-sep">bis</span>
|
||
<input id="excludeEndInput" type="time" aria-label="Ausschluss bis">
|
||
<button class="secondary-btn" id="addExclusionBtn" type="button">Hinzufügen</button>
|
||
</div>
|
||
<div id="exclusionList" class="exclusion-list"></div>
|
||
<p class="placeholder-hint">Beispiel: 00:00–07:00, um nächtliche Ausführungen zu vermeiden.</p>
|
||
</div>
|
||
<div class="field full">
|
||
<div class="template-hint">
|
||
<p class="template-title">Platzhalter</p>
|
||
<table class="placeholder-table">
|
||
<tbody id="placeholderTableBody"></tbody>
|
||
</table>
|
||
<p class="placeholder-hint">Beispiele beziehen sich auf den aktuellen Zeitpunkt.</p>
|
||
</div>
|
||
</div>
|
||
<div class="field full">
|
||
<div class="preview-panel">
|
||
<div class="preview-header">
|
||
<div>
|
||
<p class="panel-eyebrow">Vorschau</p>
|
||
<h3>Aufgelöste Werte</h3>
|
||
</div>
|
||
<button class="secondary-btn" type="button" id="refreshPreviewBtn">Aktualisieren</button>
|
||
</div>
|
||
<div class="preview-grid">
|
||
<div class="preview-block">
|
||
<p class="preview-label">URL</p>
|
||
<pre id="previewUrl" class="preview-value">—</pre>
|
||
</div>
|
||
<div class="preview-block">
|
||
<p class="preview-label">Headers</p>
|
||
<pre id="previewHeaders" class="preview-value">—</pre>
|
||
</div>
|
||
<div class="preview-block">
|
||
<p class="preview-label">Body</p>
|
||
<pre id="previewBody" class="preview-value">—</pre>
|
||
</div>
|
||
</div>
|
||
<p class="preview-hint">Die Vorschau nutzt die aktuellen Formularwerte und füllt Platzhalter ({{date}}, {{uuid}}, …) mit Beispielwerten.</p>
|
||
</div>
|
||
</div>
|
||
<div class="field full modal-actions">
|
||
<div id="formStatus" class="form-status" aria-live="polite"></div>
|
||
<div class="panel-actions">
|
||
<button class="ghost-btn" id="resetFormBtn" type="button">Zurücksetzen</button>
|
||
<button class="primary-btn" id="saveBtn" type="submit">Speichern</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="importModal" class="modal" hidden>
|
||
<div class="modal__backdrop" id="importModalBackdrop"></div>
|
||
<div class="modal__content" role="dialog" aria-modal="true" aria-labelledby="importTitle">
|
||
<div class="modal__header">
|
||
<div>
|
||
<p class="panel-eyebrow">Import</p>
|
||
<h2 id="importTitle">Vorlage einfügen</h2>
|
||
</div>
|
||
<button class="ghost-btn" id="importCloseBtn" type="button">×</button>
|
||
</div>
|
||
<p class="import-hint">Füge hier "Copy as cURL", "Copy as fetch" oder Powershell ein. Header, Methode, Body und URL werden übernommen.</p>
|
||
<textarea id="importInput" rows="7" placeholder="curl https://api.example.com -X POST -H 'Authorization: Bearer token' --data '{\"hello\":\"world\"}'"></textarea>
|
||
<div class="modal-actions">
|
||
<div id="importStatus" class="import-status" aria-live="polite"></div>
|
||
<button class="secondary-btn" id="applyImportBtn" type="button">Vorlage übernehmen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section id="view-daily-bookmarks" class="app-view daily-bookmarks-view" data-view="daily-bookmarks">
|
||
<div class="container">
|
||
<div class="daily-shell">
|
||
<header class="hero">
|
||
<h1 class="hero__title">Daily Bookmarks</h1>
|
||
<div class="hero__controls">
|
||
<div class="day-switch">
|
||
<button class="ghost-btn" id="dailyPrevDayBtn" aria-label="Vorheriger Tag">◀</button>
|
||
<div class="day-switch__label">
|
||
<div id="dailyDayLabel" class="day-switch__day">Heute</div>
|
||
<div id="dailyDaySubLabel" class="day-switch__sub"></div>
|
||
</div>
|
||
<button class="ghost-btn" id="dailyNextDayBtn" aria-label="Nächster Tag">▶</button>
|
||
<button class="ghost-btn ghost-btn--today" id="dailyTodayBtn">Heute</button>
|
||
</div>
|
||
<div class="hero__actions">
|
||
<div class="bulk-actions">
|
||
<label for="dailyBulkCountSelect">Anzahl</label>
|
||
<select id="dailyBulkCountSelect">
|
||
<option value="1">1</option>
|
||
<option value="5">5</option>
|
||
<option value="10">10</option>
|
||
<option value="15">15</option>
|
||
<option value="20">20</option>
|
||
</select>
|
||
<label class="auto-open-toggle">
|
||
<input type="checkbox" id="dailyAutoOpenToggle">
|
||
<span>Auto öffnen</span>
|
||
</label>
|
||
<button class="secondary-btn" id="dailyBulkOpenBtn" type="button">Öffnen & abhaken</button>
|
||
</div>
|
||
<button class="primary-btn" id="dailyOpenCreateBtn" type="button">+ Bookmark</button>
|
||
<button class="secondary-btn" id="dailyOpenImportBtn" type="button">Liste importieren</button>
|
||
<button class="ghost-btn" id="dailyRefreshBtn" type="button">Aktualisieren</button>
|
||
<span id="dailyHeroStats" class="hero__stats"></span>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div id="dailyAutoOpenOverlay" class="auto-open-overlay" hidden>
|
||
<div class="auto-open-overlay__panel" id="dailyAutoOpenOverlayPanel">
|
||
<div class="auto-open-overlay__badge">Auto-Öffnen startet gleich</div>
|
||
<div class="auto-open-overlay__timer">
|
||
<span id="dailyAutoOpenCountdown" class="auto-open-overlay__count">0.0</span>
|
||
<span class="auto-open-overlay__unit">Sek.</span>
|
||
</div>
|
||
<p class="auto-open-overlay__text">
|
||
Die nächste Runde deiner gefilterten Bookmarks öffnet automatisch. Abbrechen, falls du noch warten willst.
|
||
</p>
|
||
<p class="auto-open-overlay__hint">Klicke irgendwo in dieses Panel, um abzubrechen.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<main class="panel list-panel">
|
||
<header class="panel__header panel__header--row">
|
||
<div>
|
||
<p class="panel__eyebrow">Tägliche Liste</p>
|
||
<h2 class="panel__title">Alle Bookmarks</h2>
|
||
</div>
|
||
<div class="list-summary" id="dailyListSummary"></div>
|
||
</header>
|
||
<div id="dailyListStatus" class="list-status" role="status" aria-live="polite"></div>
|
||
<div class="table-wrapper">
|
||
<table class="bookmark-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="col-url">
|
||
<button type="button" class="sort-btn" data-sort-key="url_template">URL (aufgelöst)</button>
|
||
</th>
|
||
<th class="col-marker">
|
||
<button type="button" class="sort-btn" data-sort-key="marker">Marker</button>
|
||
</th>
|
||
<th class="col-created">
|
||
<button type="button" class="sort-btn" data-sort-key="created_at">Erstelldatum</button>
|
||
</th>
|
||
<th class="col-last">
|
||
<button type="button" class="sort-btn" data-sort-key="last_completed_at">Letzte Erledigung</button>
|
||
</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
<tr class="table-filter-row">
|
||
<th>
|
||
<label class="visually-hidden" for="dailyUrlFilter">Nach URL filtern</label>
|
||
<input id="dailyUrlFilter" type="search" placeholder="URL filtern">
|
||
</th>
|
||
<th>
|
||
<label class="visually-hidden" for="dailyMarkerFilter">Nach Marker filtern</label>
|
||
<select id="dailyMarkerFilter">
|
||
<option value="">Alle Marker</option>
|
||
<option value="__none">Ohne Marker</option>
|
||
</select>
|
||
</th>
|
||
<th></th>
|
||
<th></th>
|
||
<th class="filter-hint">
|
||
<button type="button" class="ghost-btn ghost-btn--tiny" id="dailyResetViewBtn" aria-label="Filter und Sortierung zurücksetzen">↺</button>
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="dailyTableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="dailyBookmarkModal" class="modal" hidden>
|
||
<div class="modal__backdrop" id="dailyModalBackdrop"></div>
|
||
<div class="modal__content" role="dialog" aria-modal="true" aria-labelledby="dailyModalTitle">
|
||
<header class="modal__header">
|
||
<div>
|
||
<p class="panel__eyebrow" id="dailyFormModeLabel">Neues Bookmark</p>
|
||
<h2 id="dailyModalTitle" class="panel__title">Bookmark pflegen</h2>
|
||
<p class="panel__subtitle">Titel optional, URL-Template erforderlich. Platzhalter werden für den gewählten Tag aufgelöst.</p>
|
||
</div>
|
||
<button class="ghost-btn modal__close" type="button" id="dailyModalCloseBtn" aria-label="Schließen">×</button>
|
||
</header>
|
||
<form id="dailyBookmarkForm" class="bookmark-form" autocomplete="off">
|
||
<label class="field">
|
||
<span>Titel (optional)</span>
|
||
<input id="dailyTitleInput" type="text" name="title" maxlength="160" placeholder="z.B. Daily Gewinnspielrunde">
|
||
</label>
|
||
<label class="field">
|
||
<span>URL-Template *</span>
|
||
<input id="dailyUrlInput" type="url" name="url_template" maxlength="800" placeholder="https://www.test.de/tag-{{day}}/" required>
|
||
</label>
|
||
<div id="dailyUrlSuggestionBox" class="suggestion-box" hidden></div>
|
||
<label class="field">
|
||
<span>Notiz (optional)</span>
|
||
<textarea id="dailyNotesInput" name="notes" maxlength="800" rows="3" placeholder="Kurze Hinweise oder To-do für diesen Link"></textarea>
|
||
</label>
|
||
<label class="field">
|
||
<span>Marker (optional)</span>
|
||
<input id="dailyMarkerInput" type="text" name="marker" maxlength="120" placeholder="z.B. März-Import oder Kampagne A">
|
||
</label>
|
||
<div class="field field--switch">
|
||
<span>Status</span>
|
||
<label class="switch-control">
|
||
<input id="dailyActiveInput" type="checkbox" name="is_active" checked>
|
||
<span class="switch-control__label">Bookmark ist aktiv</span>
|
||
</label>
|
||
<p class="field__hint">Deaktivierte Bookmarks bleiben erhalten, werden aber beim Auto-Öffnen und Abhaken übersprungen.</p>
|
||
</div>
|
||
<div class="form-preview">
|
||
<div>
|
||
<p class="form-preview__label">Aufgelöste URL für den gewählten Tag:</p>
|
||
<a id="dailyPreviewLink" class="form-preview__link" href="#" target="_blank" rel="noopener">–</a>
|
||
</div>
|
||
<div class="form-preview__actions">
|
||
<button class="secondary-btn" type="button" id="dailyResetBtn">Zurücksetzen</button>
|
||
<button class="primary-btn" type="submit" id="dailySubmitBtn">Speichern</button>
|
||
</div>
|
||
</div>
|
||
<div class="placeholder-help">
|
||
<p class="placeholder-help__title">Dynamische Platzhalter</p>
|
||
<ul class="placeholder-help__list">
|
||
<li><code>{{day}}</code> Tag des Monats (1–31), <code>{{dd}}</code> zweistellig</li>
|
||
<li><code>{{date}}</code> liefert <code>YYYY-MM-DD</code></li>
|
||
<li><code>{{mm}}</code> Monat zweistellig, <code>{{yyyy}}</code> Jahr</li>
|
||
<li><code>{{day+1}}</code> oder <code>{{date-2}}</code> verschieben um Tage</li>
|
||
<li><code>{{counter:477}}</code> Basiswert + aktueller Tag, z.B. <code>https://www.test.de/sweepstakes/{{counter:477}}/</code></li>
|
||
</ul>
|
||
</div>
|
||
<div id="dailyFormStatus" class="form-status" role="status" aria-live="polite"></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="dailyImportModal" class="modal" hidden>
|
||
<div class="modal__backdrop" id="dailyImportBackdrop"></div>
|
||
<div class="modal__content" role="dialog" aria-modal="true" aria-labelledby="dailyImportModalTitle">
|
||
<header class="modal__header">
|
||
<div>
|
||
<p class="panel__eyebrow">Masseneingabe</p>
|
||
<h2 id="dailyImportModalTitle" class="panel__title">Viele Bookmarks importieren</h2>
|
||
<p class="panel__subtitle">Füge hunderte Links gleichzeitig hinzu und vergebe einen gemeinsamen Marker.</p>
|
||
</div>
|
||
<button class="ghost-btn modal__close" type="button" id="dailyImportCloseBtn" aria-label="Schließen">×</button>
|
||
</header>
|
||
<form id="dailyImportForm" class="bookmark-form" autocomplete="off">
|
||
<label class="field">
|
||
<span>Liste der URL-Templates *</span>
|
||
<textarea id="dailyImportInput" name="import_urls" maxlength="120000" rows="8" placeholder="Jeder Link in eine neue Zeile, z.B. https://example.com/{{date}}/"></textarea>
|
||
</label>
|
||
<label class="field">
|
||
<span>Marker für alle Einträge (optional)</span>
|
||
<input id="dailyImportMarkerInput" type="text" name="import_marker" maxlength="120" placeholder="z.B. Batch März 2024">
|
||
</label>
|
||
<p class="import-hint">Doppelte oder ungültige Zeilen werden automatisch übersprungen.</p>
|
||
<div class="import-actions">
|
||
<button class="ghost-btn" type="button" id="dailyImportResetBtn">Zurücksetzen</button>
|
||
<button class="primary-btn" type="submit" id="dailyImportSubmitBtn">Importieren</button>
|
||
</div>
|
||
<div id="dailyImportStatus" class="import-status" role="status" aria-live="polite"></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section id="view-dashboard" class="app-view" data-view="dashboard">
|
||
<div class="container">
|
||
<div class="page-toolbar page-toolbar--dashboard">
|
||
<div class="header-controls">
|
||
<div class="control-group">
|
||
<label for="timeFilter">Zeitraum:</label>
|
||
<select id="timeFilter" class="control-select">
|
||
<option value="all">Alle</option>
|
||
<option value="today">Heute</option>
|
||
<option value="week" selected>Diese Woche</option>
|
||
<option value="month">Dieser Monat</option>
|
||
<option value="year">Dieses Jahr</option>
|
||
</select>
|
||
</div>
|
||
<div class="control-group">
|
||
<label for="profileFilter">Profil-Filter:</label>
|
||
<select id="profileFilter" class="control-select">
|
||
<option value="all">Alle Profile</option>
|
||
<option value="1">Profil 1</option>
|
||
<option value="2">Profil 2</option>
|
||
<option value="3">Profil 3</option>
|
||
<option value="4">Profil 4</option>
|
||
<option value="5">Profil 5</option>
|
||
</select>
|
||
</div>
|
||
<button type="button" class="btn btn-primary" id="dailyRefreshBtn">
|
||
🔄 Aktualisieren
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="dashboardLoading" class="loading">Lade Statistiken...</div>
|
||
<div id="dashboardError" class="error" style="display: none;"></div>
|
||
|
||
<div id="dashboardContainer" class="dashboard-container" style="display: none;">
|
||
|
||
<!-- SECTION 1: OVERVIEW -->
|
||
<section class="dashboard-section">
|
||
<h2 class="section-title">Übersicht</h2>
|
||
|
||
<!-- Primary Stats -->
|
||
<div class="stats-grid">
|
||
<div class="stat-card stat-card--primary">
|
||
<div class="stat-card__icon">📋</div>
|
||
<div class="stat-card__content">
|
||
<div class="stat-card__label">Gesamt Beiträge</div>
|
||
<div class="stat-card__value" id="totalPosts">0</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card--success">
|
||
<div class="stat-card__icon">✓</div>
|
||
<div class="stat-card__content">
|
||
<div class="stat-card__label">Abgeschlossen</div>
|
||
<div class="stat-card__value" id="completedPosts">0</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card--warning">
|
||
<div class="stat-card__icon">⏳</div>
|
||
<div class="stat-card__content">
|
||
<div class="stat-card__label">In Bearbeitung</div>
|
||
<div class="stat-card__value" id="activePosts">0</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card--danger">
|
||
<div class="stat-card__icon">⚠️</div>
|
||
<div class="stat-card__content">
|
||
<div class="stat-card__label">Abgelaufen</div>
|
||
<div class="stat-card__value" id="expiredPosts">0</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card stat-card--info">
|
||
<div class="stat-card__icon">🏆</div>
|
||
<div class="stat-card__content">
|
||
<div class="stat-card__label">Erfolgreich</div>
|
||
<div class="stat-card__value" id="successfulPosts">0</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Key Metrics -->
|
||
<div class="metrics-grid">
|
||
<div class="metric-card">
|
||
<div class="metric-card__label">Erfolgsquote</div>
|
||
<div class="metric-card__value" id="successRateMetric">0%</div>
|
||
<div class="metric-card__change" id="successRateChange"></div>
|
||
</div>
|
||
|
||
<div class="metric-card">
|
||
<div class="metric-card__label">Ø Bearbeitungszeit</div>
|
||
<div class="metric-card__value" id="avgCompletionTime">-</div>
|
||
<div class="metric-card__subtext">bis Abschluss</div>
|
||
</div>
|
||
|
||
<div class="metric-card">
|
||
<div class="metric-card__label">Teilnahmen heute</div>
|
||
<div class="metric-card__value" id="checksToday">0</div>
|
||
<div class="metric-card__change" id="checksTodayChange"></div>
|
||
</div>
|
||
|
||
<div class="metric-card">
|
||
<div class="metric-card__label">Deadline-Risiko</div>
|
||
<div class="metric-card__value" id="deadlineRiskValue">0</div>
|
||
<div class="metric-card__subtext" id="deadlineRiskText">keine Risiken</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- SECTION 2: ANALYTICS -->
|
||
<section class="dashboard-section">
|
||
<h2 class="section-title">Analyse</h2>
|
||
|
||
<div class="charts-row">
|
||
<!-- Activity Timeline -->
|
||
<div class="chart-card chart-card--full">
|
||
<div class="chart-card__header">
|
||
<h3 class="chart-card__title">Aktivitätsverlauf</h3>
|
||
<span class="chart-card__subtitle" id="timelineSubtitle"></span>
|
||
</div>
|
||
<div class="chart-card__body">
|
||
<canvas id="timelineChart" width="1200" height="280"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="charts-row">
|
||
<!-- Profile Performance -->
|
||
<div class="chart-card">
|
||
<div class="chart-card__header">
|
||
<h3 class="chart-card__title">Teilnahmen pro Profil</h3>
|
||
<span class="chart-card__subtitle" id="profileChartSubtitle"></span>
|
||
</div>
|
||
<div class="chart-card__body">
|
||
<div id="profileChart" class="bar-chart"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Progress Overview -->
|
||
<div class="chart-card">
|
||
<div class="chart-card__header">
|
||
<h3 class="chart-card__title">Status-Verteilung</h3>
|
||
<span class="chart-card__subtitle" id="progressChartSubtitle"></span>
|
||
</div>
|
||
<div class="chart-card__body">
|
||
<canvas id="progressChart" width="400" height="300"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="charts-row">
|
||
<!-- Period Trend -->
|
||
<div class="chart-card">
|
||
<div class="chart-card__header">
|
||
<h3 class="chart-card__title">Teilnahmen-Trend</h3>
|
||
<span class="chart-card__subtitle" id="trendChartSubtitle"></span>
|
||
</div>
|
||
<div class="chart-card__body">
|
||
<canvas id="periodTrendChart" width="500" height="300"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Profile Comparison -->
|
||
<div class="chart-card">
|
||
<div class="chart-card__header">
|
||
<h3 class="chart-card__title">Profilvergleich</h3>
|
||
<span class="chart-card__subtitle" id="profileComparisonSubtitle"></span>
|
||
</div>
|
||
<div class="chart-card__body">
|
||
<canvas id="profileComparisonChart" width="500" height="300"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- SECTION 3: PERFORMANCE COMPARISONS -->
|
||
<section class="dashboard-section">
|
||
<h2 class="section-title">Performance-Vergleiche</h2>
|
||
|
||
<div class="comparison-grid">
|
||
<div class="comparison-card">
|
||
<h3 class="comparison-card__title">Wochenvergleich</h3>
|
||
<div class="comparison-card__content" id="weeklyComparison"></div>
|
||
</div>
|
||
<div class="comparison-card">
|
||
<h3 class="comparison-card__title">Monatsvergleich</h3>
|
||
<div class="comparison-card__content" id="monthlyComparison"></div>
|
||
</div>
|
||
<div class="comparison-card">
|
||
<h3 class="comparison-card__title">Jahresvergleich</h3>
|
||
<div class="comparison-card__content" id="yearlyComparison"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="success-comparison-grid">
|
||
<div class="success-comparison-card">
|
||
<h3 class="success-comparison-card__title">Erfolgsanalyse: Woche</h3>
|
||
<div class="success-comparison-card__content" id="weeklySuccessComparison"></div>
|
||
</div>
|
||
<div class="success-comparison-card">
|
||
<h3 class="success-comparison-card__title">Erfolgsanalyse: Monat</h3>
|
||
<div class="success-comparison-card__content" id="monthlySuccessComparison"></div>
|
||
</div>
|
||
<div class="success-comparison-card">
|
||
<h3 class="success-comparison-card__title">Erfolgsanalyse: Jahr</h3>
|
||
<div class="success-comparison-card__content" id="yearlySuccessComparison"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- SECTION 4: DETAILS -->
|
||
<section class="dashboard-section">
|
||
<h2 class="section-title">Details</h2>
|
||
|
||
<div class="details-grid">
|
||
<!-- Top Performers -->
|
||
<div class="detail-card">
|
||
<div class="detail-card__header">
|
||
<h3 class="detail-card__title">Top Performers</h3>
|
||
<span class="detail-card__badge" id="performersCount">0</span>
|
||
</div>
|
||
<div class="detail-card__body">
|
||
<div id="topPerformers" class="performers-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Upcoming Deadlines -->
|
||
<div class="detail-card">
|
||
<div class="detail-card__header">
|
||
<h3 class="detail-card__title">Anstehende Deadlines</h3>
|
||
<span class="detail-card__badge" id="deadlinesCount">0</span>
|
||
</div>
|
||
<div class="detail-card__body">
|
||
<div id="upcomingDeadlines" class="deadline-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Recent Activity -->
|
||
<div class="detail-card detail-card--full">
|
||
<div class="detail-card__header">
|
||
<h3 class="detail-card__title">Letzte Aktivitäten</h3>
|
||
<span class="detail-card__badge" id="activityCount">0</span>
|
||
</div>
|
||
<div class="detail-card__body">
|
||
<div id="recentActivity" class="activity-list"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section id="view-settings" class="app-view" data-view="settings">
|
||
<div class="container">
|
||
|
||
<div id="settingsLoading" class="loading" style="display: none;">Lade Einstellungen...</div>
|
||
<div id="settingsError" class="error" style="display: none;"></div>
|
||
<div id="settingsSuccess" class="success" style="display: none;"></div>
|
||
|
||
<div class="settings-container">
|
||
|
||
<!-- AI Credentials Section -->
|
||
<section class="settings-section">
|
||
<h2 class="section-title">AI-Anmeldedaten</h2>
|
||
<p class="section-description">
|
||
Verwalte deine API-Schlüssel für verschiedene AI-Provider. Du kannst mehrere Credentials speichern und schnell zwischen ihnen wechseln.
|
||
</p>
|
||
|
||
<div id="credentialsList" class="credentials-list"></div>
|
||
|
||
<button type="button" class="btn btn-primary" id="addCredentialBtn">
|
||
➕ Neue Anmeldedaten hinzufügen
|
||
</button>
|
||
</section>
|
||
|
||
<!-- Profile Friends Section -->
|
||
<section class="settings-section">
|
||
<h2 class="section-title">👥 Freundesnamen pro Profil</h2>
|
||
<p class="section-description">
|
||
Gib für jedes Profil eine Liste von Freundesnamen an, die im Prompt verwendet werden können.
|
||
</p>
|
||
|
||
<div id="profileFriendsList"></div>
|
||
</section>
|
||
|
||
<!-- AI Settings Section -->
|
||
<section class="settings-section">
|
||
<h2 class="section-title">AI-Kommentar-Generator</h2>
|
||
<p class="section-description">
|
||
Konfiguriere die automatische Generierung von Kommentaren durch KI.
|
||
</p>
|
||
|
||
<form id="aiSettingsForm">
|
||
<div class="form-group">
|
||
<label class="form-label">
|
||
<input type="checkbox" id="aiEnabled" class="form-checkbox">
|
||
<span>AI-Kommentar-Generator aktivieren</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="activeCredential" class="form-label">Aktive Anmeldedaten</label>
|
||
<select id="activeCredential" class="form-select">
|
||
<option value="">-- Bitte wählen --</option>
|
||
</select>
|
||
<p class="form-help">
|
||
Wähle welche API-Anmeldedaten verwendet werden sollen
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="aiPromptPrefix" class="form-label">Prompt-Präfix</label>
|
||
<textarea id="aiPromptPrefix" class="form-textarea" rows="4"
|
||
placeholder="Anweisungen für die KI vor dem Post-Text..."></textarea>
|
||
<p class="form-help">
|
||
Dieser Text wird vor dem eigentlichen Post-Text an die KI gesendet. Verwende <code>{FREUNDE}</code> als Platzhalter für Freundesnamen.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="button" class="btn btn-secondary" id="testBtn">
|
||
🧪 Kommentar testen
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Sports scoring Section -->
|
||
<section class="settings-section">
|
||
<h2 class="section-title">🏷️ Sport-Post-Scoring</h2>
|
||
<p class="section-description">
|
||
Analysiert Beitragstexte nach Sport-Begriffen (z.B. Fußball, Volleyball) und weist einen Score zu.
|
||
Beiträge oberhalb des Schwellwerts würden später automatisch ausgeblendet – aktuell wird nur markiert.
|
||
</p>
|
||
|
||
<form id="moderationSettingsForm">
|
||
<div class="form-group">
|
||
<label class="form-label">
|
||
<input type="checkbox" id="sportsScoringEnabled" class="form-checkbox">
|
||
<span>Scoring aktivieren</span>
|
||
</label>
|
||
<p class="form-help">
|
||
Nutze das heuristische Punktesystem, um offensichtliche Sport-/Spiel-Posts zu erkennen.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="sportsScoreThreshold" class="form-label">Schwellwert für Sport-Posts</label>
|
||
<input type="number" id="sportsScoreThreshold" class="form-input" min="0" max="50" step="0.5" value="5">
|
||
<p class="form-help">
|
||
Ab diesem Score kann der Post automatisch versteckt werden.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label">
|
||
<input type="checkbox" id="sportsAutoHideEnabled" class="form-checkbox">
|
||
<span>Automatisch verstecken bei Überschreitung</span>
|
||
</label>
|
||
<p class="form-help">
|
||
Wenn aktiviert, blendet die Extension Posts mit Score ≥ Schwellwert automatisch aus (Feed & Suche).
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label">Gewichte (0–10)</label>
|
||
<div class="grid-weights">
|
||
<label class="form-field-inline">
|
||
<span>Ergebnis (1:0)</span>
|
||
<input type="number" id="sportWeightScoreline" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Score-Emojis (+3️⃣)</span>
|
||
<input type="number" id="sportWeightScoreEmoji" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Sport-Emojis (⚽️)</span>
|
||
<input type="number" id="sportWeightSportEmoji" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Verben (gewinnen)</span>
|
||
<input type="number" id="sportWeightSportVerb" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Nomen (Liga, Tor)</span>
|
||
<input type="number" id="sportWeightSportNoun" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Hashtags (#auswärtssieg)</span>
|
||
<input type="number" id="sportWeightHashtag" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Team-Kürzel (FC…)</span>
|
||
<input type="number" id="sportWeightTeamToken" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Wettbewerbe (Cup)</span>
|
||
<input type="number" id="sportWeightCompetition" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Ergebnisbezug (Sieg)</span>
|
||
<input type="number" id="sportWeightCelebration" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Ort (Auswärts)</span>
|
||
<input type="number" id="sportWeightLocation" class="form-input" min="0" max="10" step="0.5">
|
||
</label>
|
||
</div>
|
||
<p class="form-help">
|
||
Je höher das Gewicht, desto stärker zahlt der jeweilige Treffer auf den Sport-Score ein.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="form-label">Stichwort-Listen (kommagetrennt)</label>
|
||
<div class="grid-weights">
|
||
<label class="form-field-inline">
|
||
<span>Nomen</span>
|
||
<textarea id="sportTermsNouns" class="form-textarea" rows="2" placeholder="auswärtssieg, liga, tor ..."></textarea>
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Verben</span>
|
||
<textarea id="sportTermsVerbs" class="form-textarea" rows="2" placeholder="gewinnen, punkten ..."></textarea>
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Wettbewerbe</span>
|
||
<textarea id="sportTermsCompetitions" class="form-textarea" rows="2" placeholder="bundesliga, cup ..."></textarea>
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Ergebnisbezug</span>
|
||
<textarea id="sportTermsCelebrations" class="form-textarea" rows="2" placeholder="sieg, tabellenführung ..."></textarea>
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Orte</span>
|
||
<textarea id="sportTermsLocations" class="form-textarea" rows="2" placeholder="auswärts, stadion ..."></textarea>
|
||
</label>
|
||
<label class="form-field-inline">
|
||
<span>Negativliste</span>
|
||
<textarea id="sportTermsNegatives" class="form-textarea" rows="2" placeholder="rezept, politik ..."></textarea>
|
||
</label>
|
||
</div>
|
||
<p class="form-help">
|
||
Leere Felder nutzen die Standardliste. Negativliste senkt den Score bei Treffern.
|
||
</p>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Similarity settings -->
|
||
<section class="settings-section">
|
||
<h2 class="section-title">Ähnlichkeits-Erkennung</h2>
|
||
<p class="section-description">
|
||
Steuert, ab wann Posts als ähnlich gelten (Text-Ähnlichkeit oder Bild-Ähnlichkeit).
|
||
</p>
|
||
|
||
<form id="similaritySettingsForm">
|
||
<div class="form-group">
|
||
<label for="similarityTextThreshold" class="form-label">Text-Ähnlichkeit (0.50–0.99)</label>
|
||
<input type="number" id="similarityTextThreshold" class="form-input" min="0.5" max="0.99" step="0.01" value="0.85">
|
||
<p class="form-help">
|
||
Je höher der Wert, desto strenger wird Text-Ähnlichkeit bewertet.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="similarityImageThreshold" class="form-label">Bild-Distanz (0–64)</label>
|
||
<input type="number" id="similarityImageThreshold" class="form-input" min="0" max="64" step="1" value="6">
|
||
<p class="form-help">
|
||
Kleiner Wert = strenger (0 = identischer Hash, 64 = komplett unterschiedlich).
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">Ähnlichkeit speichern</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Hidden posts / purge settings -->
|
||
<section class="settings-section">
|
||
<h2 class="section-title">Versteckte Beiträge bereinigen</h2>
|
||
<p class="section-description">
|
||
Steuere die automatische Bereinigung versteckter/ausgeblendeter Beiträge aus der Suche und starte bei Bedarf eine manuelle Bereinigung.
|
||
</p>
|
||
|
||
<form id="hiddenSettingsForm">
|
||
<div class="form-group">
|
||
<label class="form-label">
|
||
<input type="checkbox" id="autoPurgeHiddenToggle" class="form-checkbox">
|
||
<span>Automatisches Purging aktivieren</span>
|
||
</label>
|
||
<p class="form-help">
|
||
Löscht versteckte Beiträge nach Ablauf der Aufbewahrungsdauer automatisch.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="hiddenRetentionDays" class="form-label">Aufbewahrungsdauer (Tage)</label>
|
||
<input type="number" id="hiddenRetentionDays" class="form-input" min="1" max="365" step="1" value="90">
|
||
<p class="form-help">
|
||
Ältere versteckte Beiträge werden beim Auto-Purge entfernt. Min 1 Tag, max 365 Tage.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="button" class="btn btn-secondary" id="purgeHiddenNowBtn">
|
||
🧹 Jetzt bereinigen
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<button type="button" class="floating-save-btn" id="saveAllFloatingBtn" aria-label="Einstellungen speichern">
|
||
<span class="spinner" aria-hidden="true" style="display: none;">⏳</span>
|
||
<span class="label">Einstellungen speichern</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Add/Edit Credential Modal -->
|
||
<div id="credentialModal" class="modal" hidden>
|
||
<div class="modal__backdrop"></div>
|
||
<div class="modal__content" role="dialog" aria-modal="true">
|
||
<button type="button" id="credentialModalClose" class="modal__close">×</button>
|
||
<h2 class="modal__title" id="credentialModalTitle">Anmeldedaten hinzufügen</h2>
|
||
|
||
<form id="credentialForm">
|
||
<input type="hidden" id="credentialId">
|
||
|
||
<div class="form-group">
|
||
<label for="credentialName" class="form-label">Name *</label>
|
||
<input type="text" id="credentialName" class="form-input" placeholder="z.B. Mein Gemini Key" required>
|
||
<p class="form-help">Gib einen eindeutigen Namen für diese Anmeldedaten an</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="credentialProvider" class="form-label">Provider *</label>
|
||
<select id="credentialProvider" class="form-select" required>
|
||
<option value="gemini">Google Gemini</option>
|
||
<option value="claude">Anthropic Claude</option>
|
||
<option value="openai">OpenAI / ChatGPT</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="credentialApiKey" class="form-label">API-Schlüssel</label>
|
||
<input type="password" id="credentialApiKey" class="form-input" placeholder="sk-...">
|
||
<p class="form-help" id="credentialApiKeyHelp"></p>
|
||
</div>
|
||
|
||
<div class="form-group" id="credentialBaseUrlGroup" style="display: none;">
|
||
<label for="credentialBaseUrl" class="form-label">Basis-URL</label>
|
||
<input type="text" id="credentialBaseUrl" class="form-input" placeholder="https://api.openai.com/v1">
|
||
<p class="form-help" id="credentialBaseUrlHelp"></p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="credentialModel" class="form-label">Modell</label>
|
||
<input type="text" id="credentialModel" class="form-input" list="credentialModelOptions"
|
||
placeholder="z.B. gpt-4o-mini">
|
||
<datalist id="credentialModelOptions"></datalist>
|
||
<p class="form-help" id="credentialModelHelp"></p>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||
<button type="button" class="btn btn-secondary" id="credentialCancelBtn">Abbrechen</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Test Modal -->
|
||
<div id="testModal" class="modal" hidden>
|
||
<div class="modal__backdrop"></div>
|
||
<div class="modal__content" role="dialog" aria-modal="true">
|
||
<button type="button" id="testModalClose" class="modal__close">×</button>
|
||
<h2 class="modal__title">Kommentar-Generator testen</h2>
|
||
|
||
<div class="modal__body">
|
||
<div class="form-group">
|
||
<label for="testProfileNumber" class="form-label">Test mit Profil</label>
|
||
<select id="testProfileNumber" class="form-select">
|
||
<option value="1">Profil 1</option>
|
||
<option value="2">Profil 2</option>
|
||
<option value="3">Profil 3</option>
|
||
<option value="4">Profil 4</option>
|
||
<option value="5">Profil 5</option>
|
||
</select>
|
||
<p class="form-help">Wähle ein Profil, um die Freundesnamen zu testen</p>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="testPostText" class="form-label">Test Post-Text</label>
|
||
<textarea id="testPostText" class="form-textarea" rows="4"
|
||
placeholder="Füge hier einen Beispiel-Post-Text ein..."></textarea>
|
||
</div>
|
||
|
||
<button type="button" class="btn btn-primary" id="generateTestComment">
|
||
✨ Kommentar generieren
|
||
</button>
|
||
|
||
<div id="testLoading" class="test-loading" style="display: none;">
|
||
Generiere Kommentar...
|
||
</div>
|
||
|
||
<div id="testResult" class="test-result" style="display: none;">
|
||
<h3>Generierter Kommentar:</h3>
|
||
<div id="testComment" class="test-comment"></div>
|
||
</div>
|
||
|
||
<div id="testError" class="test-error" style="display: none;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section id="view-bookmarks" class="app-view" data-view="bookmarks">
|
||
<div class="container">
|
||
<main class="bookmark-page">
|
||
<section class="bookmark-page__panel">
|
||
<div class="bookmark-panel__header">
|
||
<h2 class="bookmark-panel__title">🔖 Bookmarks</h2>
|
||
</div>
|
||
<p class="bookmark-page__lead">Über die Bookmarks kannst du auf einen Schlag mehrere relevante Suchanfragen öffnen.</p>
|
||
<div class="bookmark-panel__toolbar">
|
||
<label class="bookmark-panel__search">
|
||
<span>Suche</span>
|
||
<input type="search" id="bookmarkSearchInput" placeholder="Keyword oder Titel durchsuchen">
|
||
</label>
|
||
<div class="bookmark-panel__sort">
|
||
<label>
|
||
<span>Sortierung</span>
|
||
<select id="bookmarkSortSelect">
|
||
<option value="recent">Zuletzt verwendet</option>
|
||
<option value="label">Alphabetisch</option>
|
||
</select>
|
||
</label>
|
||
<button type="button" class="bookmark-sort__direction" id="bookmarkSortDirectionToggle" aria-pressed="false" title="Sortierreihenfolge umkehren">
|
||
<span class="bookmark-sort__direction-icon" aria-hidden="true">▼</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<form id="bookmarkQuickForm" class="bookmark-quicksearch" autocomplete="off">
|
||
<div class="bookmark-quicksearch__fields">
|
||
<label class="bookmark-quicksearch__field">
|
||
<span>Einmalige Suche</span>
|
||
<input type="text" id="bookmarkQuickQuery" placeholder="Suchbegriff nur öffnen, nicht speichern">
|
||
</label>
|
||
<button type="submit" class="btn btn-secondary">Sofort suchen</button>
|
||
</div>
|
||
<p class="bookmark-quicksearch__hint">Öffnet die drei Varianten ohne ein Bookmark anzulegen.</p>
|
||
<div id="bookmarkQuickStatus" class="bookmark-status" aria-live="polite" hidden></div>
|
||
</form>
|
||
<div id="bookmarksList" class="bookmark-list" role="list" aria-live="polite"></div>
|
||
<form id="bookmarkForm" class="bookmark-form" autocomplete="off">
|
||
<div class="bookmark-form__fields">
|
||
<label class="bookmark-form__field">
|
||
<span>Titel</span>
|
||
<input type="text" id="bookmarkName" maxlength="40" placeholder="Optionaler Titel">
|
||
</label>
|
||
<label class="bookmark-form__field">
|
||
<span>Keyword *</span>
|
||
<input type="text" id="bookmarkQuery" required placeholder="z.B. gewinnspiel">
|
||
</label>
|
||
</div>
|
||
<div class="bookmark-form__actions">
|
||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||
<button type="button" class="btn btn-secondary" id="bookmarkCancelBtn">Zurücksetzen</button>
|
||
</div>
|
||
<p class="bookmark-form__hint">Öffnet drei Varianten (… Gewinnspiel / … gewinnen / … verlosen) mit Filter auf die letzten vier Wochen.</p>
|
||
</form>
|
||
</section>
|
||
</main>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
const buttons = Array.from(document.querySelectorAll('[data-view-target]'));
|
||
const views = Array.from(document.querySelectorAll('.app-view'));
|
||
const viewMap = Object.fromEntries(views.map((section) => [section.dataset.view, section]));
|
||
const params = new URLSearchParams(window.location.search);
|
||
const initialParam = params.get('view');
|
||
const defaultView = viewMap[initialParam] ? initialParam : 'posts';
|
||
const VIEW_CHANGE_EVENT = 'app:view-change';
|
||
const BASE_TITLE = 'Post Tracker';
|
||
const VIEW_TITLES = {
|
||
posts: 'Beiträge',
|
||
dashboard: 'Dashboard',
|
||
settings: 'Einstellungen',
|
||
bookmarks: 'Bookmarks',
|
||
automation: 'Automationen',
|
||
'daily-bookmarks': 'Daily Bookmarks'
|
||
};
|
||
|
||
function dispatchViewChange(view) {
|
||
window.dispatchEvent(new CustomEvent(VIEW_CHANGE_EVENT, { detail: { view } }));
|
||
}
|
||
|
||
function updateDocumentTitle(view) {
|
||
const title = VIEW_TITLES[view];
|
||
document.title = title ? `${title} – ${BASE_TITLE}` : BASE_TITLE;
|
||
}
|
||
|
||
function setView(view, options = { updateHistory: true }) {
|
||
if (!viewMap[view]) {
|
||
view = 'posts';
|
||
}
|
||
|
||
views.forEach((section) => {
|
||
section.classList.toggle('app-view--active', section.dataset.view === view);
|
||
});
|
||
|
||
buttons.forEach((button) => {
|
||
const isActive = button.dataset.viewTarget === view;
|
||
button.classList.toggle('nav-active', isActive);
|
||
if (isActive) {
|
||
button.setAttribute('aria-current', 'page');
|
||
} else {
|
||
button.removeAttribute('aria-current');
|
||
}
|
||
});
|
||
|
||
if (options.updateHistory) {
|
||
const nextParams = new URLSearchParams(window.location.search);
|
||
if (view === 'posts') {
|
||
nextParams.delete('view');
|
||
} else {
|
||
nextParams.set('view', view);
|
||
nextParams.delete('tab');
|
||
}
|
||
const query = nextParams.toString();
|
||
const newUrl = query ? `${window.location.pathname}?${query}` : window.location.pathname;
|
||
window.history.pushState({ view }, '', newUrl);
|
||
}
|
||
|
||
updateDocumentTitle(view);
|
||
dispatchViewChange(view);
|
||
}
|
||
|
||
buttons.forEach((button) => {
|
||
button.addEventListener('click', (event) => {
|
||
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
setView(button.dataset.viewTarget);
|
||
});
|
||
});
|
||
|
||
window.addEventListener('popstate', (event) => {
|
||
const view = (event.state && event.state.view) || new URLSearchParams(window.location.search).get('view') || 'posts';
|
||
setView(view, { updateHistory: false });
|
||
});
|
||
|
||
setView(defaultView, { updateHistory: false });
|
||
const initParams = new URLSearchParams(window.location.search);
|
||
if (defaultView === 'posts') {
|
||
initParams.delete('view');
|
||
} else {
|
||
initParams.set('view', defaultView);
|
||
initParams.delete('tab');
|
||
}
|
||
const initQuery = initParams.toString();
|
||
window.history.replaceState({ view: defaultView }, '', initQuery ? `${window.location.pathname}?${initQuery}` : window.location.pathname);
|
||
})();
|
||
</script>
|
||
<script>
|
||
(function() {
|
||
const AUTOMATION_VIEW = 'automation';
|
||
const DAILY_VIEW = 'daily-bookmarks';
|
||
function handleViewChange(event) {
|
||
const view = event?.detail?.view;
|
||
if (view === AUTOMATION_VIEW) {
|
||
window.AutomationPage?.activate?.();
|
||
} else {
|
||
window.AutomationPage?.deactivate?.();
|
||
}
|
||
if (view === DAILY_VIEW) {
|
||
window.DailyBookmarksPage?.activate?.();
|
||
} else {
|
||
window.DailyBookmarksPage?.deactivate?.();
|
||
}
|
||
}
|
||
|
||
window.addEventListener('app:view-change', handleViewChange);
|
||
|
||
const automationSection = document.querySelector('[data-view="automation"]');
|
||
if (automationSection && automationSection.classList.contains('app-view--active')) {
|
||
window.AutomationPage?.activate?.();
|
||
}
|
||
const dailySection = document.querySelector('[data-view="daily-bookmarks"]');
|
||
if (dailySection && dailySection.classList.contains('app-view--active')) {
|
||
window.DailyBookmarksPage?.activate?.();
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|