reworked site
This commit is contained in:
199
web/app.js
199
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();
|
||||
|
||||
Reference in New Issue
Block a user