From f9d8fc5b82cba8d14c6e6f6c118bb96ba9c0e399 Mon Sep 17 00:00:00 2001 From: Meik Date: Sun, 16 Nov 2025 13:02:05 +0100 Subject: [PATCH] reworked site --- web/Dockerfile | 2 + web/app.js | 199 ++++++++++++++++++++++++++----- web/bookmarks.html | 62 ++++++++++ web/dashboard.html | 7 +- web/index.html | 284 ++++++++++++++++++++++----------------------- web/posts.html | 132 +++++++++++++++++++++ web/settings.html | 6 - web/style.css | 117 +++++++++++++++++++ 8 files changed, 628 insertions(+), 181 deletions(-) create mode 100644 web/bookmarks.html create mode 100644 web/posts.html diff --git a/web/Dockerfile b/web/Dockerfile index d9d248c..a062301 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,8 +1,10 @@ FROM nginx:alpine COPY index.html /usr/share/nginx/html/ +COPY posts.html /usr/share/nginx/html/ COPY dashboard.html /usr/share/nginx/html/ COPY settings.html /usr/share/nginx/html/ +COPY bookmarks.html /usr/share/nginx/html/ COPY style.css /usr/share/nginx/html/ COPY dashboard.css /usr/share/nginx/html/ COPY settings.css /usr/share/nginx/html/ diff --git a/web/app.js b/web/app.js index 6a82c24..4119280 100644 --- a/web/app.js +++ b/web/app.js @@ -145,6 +145,9 @@ function scheduleUpdatesReconnect() { } function startUpdatesStream() { + if (isBookmarksPage) { + return; + } if (typeof EventSource === 'undefined') { console.warn('EventSource wird von diesem Browser nicht unterstützt. Fallback auf Polling.'); return; @@ -246,6 +249,11 @@ const bookmarkForm = document.getElementById('bookmarkForm'); const bookmarkNameInput = document.getElementById('bookmarkName'); const bookmarkQueryInput = document.getElementById('bookmarkQuery'); const bookmarkCancelBtn = document.getElementById('bookmarkCancelBtn'); +const bookmarkSearchInput = document.getElementById('bookmarkSearchInput'); +const bookmarkSortSelect = document.getElementById('bookmarkSortSelect'); +const bookmarkSortDirectionToggle = document.getElementById('bookmarkSortDirectionToggle'); +const profileSelectElement = document.getElementById('profileSelect'); +const isBookmarksPage = document.body.classList.contains('bookmarks-page'); const REFRESH_SETTINGS_KEY = 'trackerRefreshSettings'; const SORT_SETTINGS_KEY = 'trackerSortSettings'; @@ -296,6 +304,9 @@ let manualPostModalPreviousOverflow = ''; let activeDeadlinePicker = null; let bookmarkPanelVisible = false; let bookmarkOutsideHandler = null; +let bookmarkSearchTerm = ''; +let bookmarkSortMode = 'recent'; +let bookmarkSortDirection = 'desc'; const INITIAL_POST_LIMIT = 10; const POST_LOAD_INCREMENT = 10; @@ -501,6 +512,83 @@ function sortBookmarksByRecency(list) { }); } +function getBookmarkLabelForComparison(bookmark = {}) { + const label = typeof bookmark.label === 'string' ? bookmark.label.trim() : ''; + const query = typeof bookmark.query === 'string' ? bookmark.query.trim() : ''; + return label || query || ''; +} + +function getBookmarkClickTimestamp(bookmark = {}) { + if (bookmark.last_clicked_at) { + const ts = new Date(bookmark.last_clicked_at).getTime(); + if (!Number.isNaN(ts)) { + return ts; + } + } + return 0; +} + +function sortBookmarksForDisplay(list) { + const items = [...list]; + if (bookmarkSortMode === 'label') { + items.sort((a, b) => { + const labelA = getBookmarkLabelForComparison(a); + const labelB = getBookmarkLabelForComparison(b); + const result = labelA.localeCompare(labelB, 'de', { sensitivity: 'base' }); + return bookmarkSortDirection === 'desc' ? -result : result; + }); + return items; + } + + items.sort((a, b) => { + const diff = getBookmarkClickTimestamp(b) - getBookmarkClickTimestamp(a); + if (diff !== 0) { + return bookmarkSortDirection === 'desc' ? diff : -diff; + } + const fallback = getBookmarkLabelForComparison(a).localeCompare( + getBookmarkLabelForComparison(b), + 'de', + { sensitivity: 'base' } + ); + return bookmarkSortDirection === 'desc' ? fallback : -fallback; + }); + + return items; +} + +function filterBookmarksBySearch(list) { + if (!bookmarkSearchTerm) { + return [...list]; + } + const term = bookmarkSearchTerm.toLowerCase(); + return list.filter((bookmark) => { + const label = (bookmark.label || bookmark.query || '').toLowerCase(); + const query = (bookmark.query || '').toLowerCase(); + return label.includes(term) || query.includes(term); + }); +} + +function getRecentBookmarks(list) { + const recent = sortBookmarksByRecency(list); + const RECENT_LIMIT = 5; + return recent.filter((bookmark) => bookmark.last_clicked_at).slice(0, RECENT_LIMIT); +} + +function updateBookmarkSortDirectionUI() { + if (!bookmarkSortDirectionToggle) { + return; + } + const isAsc = bookmarkSortDirection === 'asc'; + bookmarkSortDirectionToggle.setAttribute('aria-pressed', isAsc ? 'true' : 'false'); + bookmarkSortDirectionToggle.setAttribute('title', isAsc ? 'Älteste zuerst' : 'Neueste zuerst'); + const icon = bookmarkSortDirectionToggle.querySelector('.bookmark-sort__direction-icon'); + if (icon) { + icon.textContent = isAsc ? '▲' : '▼'; + } else { + bookmarkSortDirectionToggle.textContent = isAsc ? '▲' : '▼'; + } +} + const DEFAULT_BOOKMARK_LAST_CLICK_KEY = 'trackerDefaultBookmarkLastClickedAt'; const bookmarkState = { @@ -954,18 +1042,9 @@ function renderBookmarks() { isDefault: true }; - const sorted = sortBookmarksByRecency(dynamicBookmarks); - const recent = []; - const RECENT_LIMIT = 5; - - sorted.forEach((bookmark) => { - if (bookmark.last_clicked_at && recent.length < RECENT_LIMIT) { - recent.push(bookmark); - } - }); - - const alphabeticalAll = [...dynamicBookmarks] - .sort((a, b) => a.label.localeCompare(b.label, 'de', { sensitivity: 'base' })); + const filteredBookmarks = filterBookmarksBySearch(dynamicBookmarks); + const sortedForAll = sortBookmarksForDisplay(filteredBookmarks); + const recent = bookmarkSearchTerm ? [] : getRecentBookmarks(filteredBookmarks); const sections = []; @@ -977,17 +1056,25 @@ function renderBookmarks() { }); } + const allItems = bookmarkSearchTerm ? sortedForAll : [staticDefault, ...sortedForAll]; + const allTitle = bookmarkSearchTerm + ? `Suchergebnisse${filteredBookmarks.length ? ` (${filteredBookmarks.length})` : ''}` + : 'Alle Bookmarks'; + sections.push({ - id: 'all', - title: 'Alle Bookmarks', - items: [staticDefault, ...alphabeticalAll] + id: bookmarkSearchTerm ? 'search' : 'all', + title: allTitle, + items: allItems }); + let renderedAnySection = false; + sections.forEach((section) => { if (!section.items.length) { return; } + renderedAnySection = true; const sectionElement = document.createElement('section'); sectionElement.className = 'bookmark-section'; sectionElement.dataset.section = section.id; @@ -1012,6 +1099,17 @@ function renderBookmarks() { sectionElement.appendChild(list); bookmarksList.appendChild(sectionElement); }); + + if (!renderedAnySection) { + const empty = document.createElement('div'); + empty.className = 'bookmark-empty'; + if (bookmarkSearchTerm) { + empty.textContent = `Keine Bookmarks gefunden für „${bookmarkSearchTerm}“.`; + } else { + empty.textContent = 'Noch keine Bookmarks gespeichert.'; + } + bookmarksList.appendChild(empty); + } } function resetBookmarkForm() { @@ -1197,6 +1295,34 @@ function initializeBookmarks() { if (bookmarkForm) { bookmarkForm.addEventListener('submit', handleBookmarkSubmit); } + + if (bookmarkSearchInput) { + bookmarkSearchInput.addEventListener('input', () => { + bookmarkSearchTerm = typeof bookmarkSearchInput.value === 'string' + ? bookmarkSearchInput.value.trim() + : ''; + renderBookmarks(); + }); + } + + if (bookmarkSortSelect) { + bookmarkSortSelect.addEventListener('change', () => { + const value = bookmarkSortSelect.value; + if (value === 'label' || value === 'recent') { + bookmarkSortMode = value; + renderBookmarks(); + } + }); + } + + if (bookmarkSortDirectionToggle) { + bookmarkSortDirectionToggle.addEventListener('click', () => { + bookmarkSortDirection = bookmarkSortDirection === 'desc' ? 'asc' : 'desc'; + updateBookmarkSortDirectionUI(); + renderBookmarks(); + }); + updateBookmarkSortDirectionUI(); + } } function getSortSettingsPageKey() { @@ -2288,6 +2414,10 @@ function applyAutoRefreshSettings() { : ''; } + if (isBookmarksPage) { + return; + } + if (!autoRefreshSettings.enabled) { return; } @@ -2582,7 +2712,9 @@ function applyProfileNumber(profileNumber, { fromBackend = false } = {}) { return; } - document.getElementById('profileSelect').value = String(profileNumber); + if (profileSelectElement) { + profileSelectElement.value = String(profileNumber); + } if (currentProfile === profileNumber) { if (!fromBackend) { @@ -2598,8 +2730,10 @@ function applyProfileNumber(profileNumber, { fromBackend = false } = {}) { pushProfileState(currentProfile); } - resetVisibleCount(); - renderPosts(); + if (!isBookmarksPage) { + resetVisibleCount(); + renderPosts(); + } } // Load profile from localStorage @@ -2637,9 +2771,11 @@ function startProfilePolling() { } // Profile selector change handler -document.getElementById('profileSelect').addEventListener('change', (e) => { - saveProfile(parseInt(e.target.value, 10)); -}); +if (profileSelectElement) { + profileSelectElement.addEventListener('change', (e) => { + saveProfile(parseInt(e.target.value, 10)); + }); +} // Tab switching document.querySelectorAll('.tab-btn').forEach(btn => { @@ -2673,6 +2809,9 @@ if (autoRefreshToggle) { autoRefreshSettings.enabled = !!autoRefreshToggle.checked; saveAutoRefreshSettings(); applyAutoRefreshSettings(); + if (isBookmarksPage) { + return; + } if (autoRefreshSettings.enabled && updatesStreamHealthy) { console.info('Live-Updates sind aktiv; automatisches Refresh bleibt pausiert.'); } @@ -3980,7 +4119,9 @@ function checkAutoCheck() { } catch (error) { console.warn('Konnte check-Parameter nicht entfernen:', error); } - fetchPosts({ showLoader: false }); + if (!isBookmarksPage) { + fetchPosts({ showLoader: false }); + } }).catch(console.error); } } @@ -4049,10 +4190,12 @@ loadAutoRefreshSettings(); initializeFocusParams(); initializeTabFromUrl(); loadSortMode(); -resetManualPostForm(); -loadProfile(); -startProfilePolling(); -fetchPosts(); -checkAutoCheck(); -startUpdatesStream(); +if (!isBookmarksPage) { + resetManualPostForm(); + loadProfile(); + startProfilePolling(); + fetchPosts(); + checkAutoCheck(); + startUpdatesStream(); +} applyAutoRefreshSettings(); diff --git a/web/bookmarks.html b/web/bookmarks.html new file mode 100644 index 0000000..69f1ccb --- /dev/null +++ b/web/bookmarks.html @@ -0,0 +1,62 @@ + + + + + + Post Tracker – Bookmarks + + + + + + +
+
+
+
+

🔖 Bookmarks

+ Zurück zum Dashboard +
+

Über die Bookmarks kannst du auf einen Schlag mehrere relevante Suchanfragen öffnen.

+
+ +
+ + +
+
+
+
+
+ + +
+
+ + +
+

Öffnet drei Varianten (… Gewinnspiel / … gewinnen / … verlosen) mit Filter auf die letzten vier Wochen.

+
+
+
+
+ + + diff --git a/web/dashboard.html b/web/dashboard.html index cfb786d..0ee833c 100644 --- a/web/dashboard.html +++ b/web/dashboard.html @@ -12,10 +12,9 @@
-
-
+
+

📊 Dashboard

- Zurück zu Beiträgen
@@ -43,7 +42,7 @@ 🔄 Aktualisieren
-
+
Lade Statistiken...
diff --git a/web/index.html b/web/index.html index dd2295f..15cf5cd 100644 --- a/web/index.html +++ b/web/index.html @@ -3,161 +3,159 @@ - Post Tracker - Web Interface + Post Tracker + -
-
-
+
+ - -
-
- - - -
-
- -
-
- -
Lade Beiträge...
- - -
+
+ +
- + + function setView(view) { + buttons.forEach((button) => { + button.classList.toggle('active', button.dataset.viewTarget === view); + }); + + const targetUrl = viewUrls[view] || viewUrls.posts; + if (frame.src !== targetUrl) { + frame.src = targetUrl; + } + + const params = new URLSearchParams(window.location.search); + if (view === 'posts') { + params.delete('view'); + } else { + params.set('view', view); + } + const url = params.toString() ? `?${params.toString()}` : window.location.pathname; + window.history.replaceState({}, document.title, url); + } + + buttons.forEach((button) => { + button.addEventListener('click', () => { + setView(button.dataset.viewTarget); + }); + }); + + setView(defaultView); + })(); + diff --git a/web/posts.html b/web/posts.html new file mode 100644 index 0000000..5a04f1f --- /dev/null +++ b/web/posts.html @@ -0,0 +1,132 @@ + + + + + + Post Tracker - Web Interface + + + + + + +
+
+
+

📋 Post Tracker

+ +
+
+
+ + +
+
+ + + +
+
+ +
+ + +
+
+
+
+ +
+
+ + + +
+
+ +
+
+ +
Lade Beiträge...
+ + +
+
+ + + + + + + + diff --git a/web/settings.html b/web/settings.html index 84404ec..d2ca8b8 100644 --- a/web/settings.html +++ b/web/settings.html @@ -12,12 +12,6 @@
-
-
-

⚙️ Einstellungen

- Zurück zu Beiträgen -
-
diff --git a/web/style.css b/web/style.css index 9244fce..021e0a0 100644 --- a/web/style.css +++ b/web/style.css @@ -28,6 +28,24 @@ header { gap: 12px; } +.page-toolbar { + background: white; + padding: 16px 18px; + border-radius: 10px; + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08); + margin-bottom: 18px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.page-toolbar__title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + .header-main { display: flex; align-items: center; @@ -1333,6 +1351,105 @@ h1 { text-align: center; } +.bookmarks-page { + padding-bottom: 40px; +} + +.bookmarks-page__intro { + margin-top: 12px; + color: #4b5563; + font-size: 14px; +} + +.bookmark-page { + margin-top: 32px; + display: flex; + justify-content: center; +} + +.bookmark-page__panel { + width: min(960px, 100%); + background: #ffffff; + border-radius: 20px; + border: 1px solid #e5e7eb; + padding: 24px 28px; + box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12); + display: flex; + flex-direction: column; + gap: 16px; +} + +.bookmark-page__panel .bookmark-list { + max-height: none; +} + +.bookmark-page__lead { + margin: 0; + color: #4b5563; + font-size: 14px; +} + +.bookmark-panel__toolbar { + display: flex; + flex-wrap: wrap; + gap: 16px; + align-items: flex-end; + margin-bottom: 12px; +} + +.bookmark-panel__search { + display: flex; + flex-direction: column; + gap: 6px; + flex: 1 1 220px; +} + +.bookmark-panel__search input { + border: 1px solid #d0d3d9; + border-radius: 8px; + padding: 8px 10px; + font-size: 14px; +} + +.bookmark-panel__sort { + display: flex; + align-items: center; + gap: 8px; +} + +.bookmark-panel__sort label { + display: flex; + flex-direction: column; + gap: 6px; + font-size: 13px; + color: #4b5563; +} + +.bookmark-panel__sort select { + border: 1px solid #d0d3d9; + border-radius: 8px; + padding: 8px 10px; + font-size: 14px; + min-width: 180px; +} + +.bookmark-sort__direction { + border: 1px solid #d0d3d9; + border-radius: 8px; + background: #f3f4f6; + width: 40px; + height: 40px; + cursor: pointer; + display: inline-flex; + justify-content: center; + align-items: center; + padding: 0; +} + +.bookmark-sort__direction:hover { + border-color: #a5b4fc; +} + @media (max-width: 640px) { .bookmark-panel { width: min(480px, 94vw);