tabs in beiträgen gemerget

This commit is contained in:
2025-11-21 13:47:19 +01:00
parent b7a9091183
commit e55f17f0c8
4 changed files with 100 additions and 43 deletions

View File

@@ -18,6 +18,7 @@ let focusTabAdjusted = null;
let currentProfile = 1; let currentProfile = 1;
let currentTab = 'pending'; let currentTab = 'pending';
let posts = []; let posts = [];
let includeExpiredPosts = loadIncludeExpiredPreference();
let profilePollTimer = null; let profilePollTimer = null;
const UPDATES_RECONNECT_DELAY = 5000; const UPDATES_RECONNECT_DELAY = 5000;
let updatesEventSource = null; let updatesEventSource = null;
@@ -235,6 +236,7 @@ const bookmarkSearchInput = document.getElementById('bookmarkSearchInput');
const bookmarkSortSelect = document.getElementById('bookmarkSortSelect'); const bookmarkSortSelect = document.getElementById('bookmarkSortSelect');
const bookmarkSortDirectionToggle = document.getElementById('bookmarkSortDirectionToggle'); const bookmarkSortDirectionToggle = document.getElementById('bookmarkSortDirectionToggle');
const profileSelectElement = document.getElementById('profileSelect'); const profileSelectElement = document.getElementById('profileSelect');
const includeExpiredToggle = document.getElementById('includeExpiredToggle');
const REFRESH_SETTINGS_KEY = 'trackerRefreshSettings'; const REFRESH_SETTINGS_KEY = 'trackerRefreshSettings';
const SORT_SETTINGS_KEY = 'trackerSortSettings'; const SORT_SETTINGS_KEY = 'trackerSortSettings';
@@ -246,6 +248,37 @@ const BOOKMARKS_BASE_URL = 'https://www.facebook.com/search/top';
const BOOKMARK_WINDOW_DAYS = 28; const BOOKMARK_WINDOW_DAYS = 28;
const BOOKMARK_SUFFIXES = ['Gewinnspiel', 'gewinnen', 'verlosen']; const BOOKMARK_SUFFIXES = ['Gewinnspiel', 'gewinnen', 'verlosen'];
const BOOKMARK_PREFS_KEY = 'trackerBookmarkPreferences'; const BOOKMARK_PREFS_KEY = 'trackerBookmarkPreferences';
const INCLUDE_EXPIRED_STORAGE_KEY = 'trackerIncludeExpired';
function loadIncludeExpiredPreference() {
try {
const stored = localStorage.getItem(INCLUDE_EXPIRED_STORAGE_KEY);
if (stored === 'true') {
return true;
}
if (stored === 'false') {
return false;
}
} catch (error) {
console.warn('Konnte Anzeigepräferenz für abgelaufene Beiträge nicht laden:', error);
}
return false;
}
function persistIncludeExpiredPreference(value) {
try {
localStorage.setItem(INCLUDE_EXPIRED_STORAGE_KEY, value ? 'true' : 'false');
} catch (error) {
console.warn('Konnte Anzeigepräferenz für abgelaufene Beiträge nicht speichern:', error);
}
}
function updateIncludeExpiredToggleUI() {
if (!includeExpiredToggle) {
return;
}
includeExpiredToggle.checked = includeExpiredPosts;
}
function initializeFocusParams() { function initializeFocusParams() {
try { try {
@@ -333,12 +366,10 @@ const INITIAL_POST_LIMIT = 10;
const POST_LOAD_INCREMENT = 10; const POST_LOAD_INCREMENT = 10;
const tabVisibleCounts = { const tabVisibleCounts = {
pending: INITIAL_POST_LIMIT, pending: INITIAL_POST_LIMIT,
expired: INITIAL_POST_LIMIT,
all: INITIAL_POST_LIMIT all: INITIAL_POST_LIMIT
}; };
const tabFilteredCounts = { const tabFilteredCounts = {
pending: 0, pending: 0,
expired: 0,
all: 0 all: 0
}; };
let loadMoreObserver = null; let loadMoreObserver = null;
@@ -414,9 +445,6 @@ function toTimestamp(value, fallback = null) {
} }
function getSortTabKey(tab = currentTab) { function getSortTabKey(tab = currentTab) {
if (tab === 'expired') {
return 'expired';
}
if (tab === 'all') { if (tab === 'all') {
return 'all'; return 'all';
} }
@@ -1391,8 +1419,6 @@ function updateTabInUrl() {
const url = new URL(window.location.href); const url = new URL(window.location.href);
if (currentTab === 'pending') { if (currentTab === 'pending') {
url.searchParams.set('tab', 'pending'); url.searchParams.set('tab', 'pending');
} else if (currentTab === 'expired') {
url.searchParams.set('tab', 'expired');
} else { } else {
url.searchParams.set('tab', 'all'); url.searchParams.set('tab', 'all');
} }
@@ -1401,9 +1427,6 @@ function updateTabInUrl() {
} }
function getTabKey(tab = currentTab) { function getTabKey(tab = currentTab) {
if (tab === 'expired') {
return 'expired';
}
if (tab === 'all') { if (tab === 'all') {
return 'all'; return 'all';
} }
@@ -1440,9 +1463,6 @@ function cleanupLoadMoreObserver() {
} }
function getTabDisplayLabel(tab = currentTab) { function getTabDisplayLabel(tab = currentTab) {
if (tab === 'expired') {
return 'Abgelaufen/Abgeschlossen';
}
if (tab === 'all') { if (tab === 'all') {
return 'Alle Beiträge'; return 'Alle Beiträge';
} }
@@ -1535,8 +1555,6 @@ function loadMorePosts(tab = currentTab, { triggeredByScroll = false } = {}) {
function setTab(tab, { updateUrl = true } = {}) { function setTab(tab, { updateUrl = true } = {}) {
if (tab === 'all') { if (tab === 'all') {
currentTab = 'all'; currentTab = 'all';
} else if (tab === 'expired') {
currentTab = 'expired';
} else { } else {
currentTab = 'pending'; currentTab = 'pending';
} }
@@ -1553,7 +1571,7 @@ function initializeTabFromUrl() {
try { try {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const tabParam = params.get('tab'); const tabParam = params.get('tab');
if (tabParam === 'all' || tabParam === 'pending' || tabParam === 'expired') { if (tabParam === 'all' || tabParam === 'pending') {
currentTab = tabParam; currentTab = tabParam;
tabResolved = true; tabResolved = true;
} else if (initialTabOverride) { } else if (initialTabOverride) {
@@ -2773,6 +2791,16 @@ if (profileSelectElement) {
}); });
} }
if (includeExpiredToggle) {
updateIncludeExpiredToggleUI();
includeExpiredToggle.addEventListener('change', () => {
includeExpiredPosts = includeExpiredToggle.checked;
persistIncludeExpiredPreference(includeExpiredPosts);
resetVisibleCount('all');
renderPosts();
});
}
// Tab switching // Tab switching
document.querySelectorAll('.tab-btn').forEach(btn => { document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
@@ -3087,10 +3115,10 @@ function renderPosts() {
if (currentTab === 'pending') { if (currentTab === 'pending') {
filteredItems = sortedItems.filter((item) => !item.status.isExpired && item.status.canCurrentProfileCheck && !item.status.isComplete); filteredItems = sortedItems.filter((item) => !item.status.isExpired && item.status.canCurrentProfileCheck && !item.status.isComplete);
} else if (currentTab === 'expired') { } else {
filteredItems = sortedItems.filter((item) => item.status.isExpired || item.status.isComplete); filteredItems = includeExpiredPosts
} else if (currentTab === 'all') { ? sortedItems
filteredItems = sortedItems.filter((item) => !item.status.isExpired && !item.status.isComplete); : sortedItems.filter((item) => !item.status.isExpired && !item.status.isComplete);
} }
const tabTotalCount = filteredItems.length; const tabTotalCount = filteredItems.length;
@@ -3115,22 +3143,17 @@ function renderPosts() {
if (!focusHandled && focusCandidateEntry && !searchActive) { if (!focusHandled && focusCandidateEntry && !searchActive) {
const candidateVisibleInCurrentTab = filteredItems.some(({ post }) => doesPostMatchFocus(post)); const candidateVisibleInCurrentTab = filteredItems.some(({ post }) => doesPostMatchFocus(post));
if (!candidateVisibleInCurrentTab) { if (!candidateVisibleInCurrentTab) {
let desiredTab = 'all'; if (currentTab !== 'all' && focusTabAdjusted !== 'all') {
if (focusCandidateEntry.status.isExpired || focusCandidateEntry.status.isComplete) { focusTabAdjusted = 'all';
desiredTab = 'expired'; setTab('all');
} else if (focusCandidateEntry.status.canCurrentProfileCheck && !focusCandidateEntry.status.isExpired && !focusCandidateEntry.status.isComplete) {
desiredTab = 'pending';
}
if (currentTab !== desiredTab && focusTabAdjusted !== desiredTab) {
focusTabAdjusted = desiredTab;
setTab(desiredTab);
return; return;
} }
if (desiredTab !== 'all' && currentTab !== 'all' && focusTabAdjusted !== 'all') { if (!includeExpiredPosts && (focusCandidateEntry.status.isExpired || focusCandidateEntry.status.isComplete)) {
focusTabAdjusted = 'all'; includeExpiredPosts = true;
setTab('all'); persistIncludeExpiredPreference(includeExpiredPosts);
updateIncludeExpiredToggleUI();
renderPosts();
return; return;
} }
} }
@@ -3163,8 +3186,8 @@ function renderPosts() {
let emptyIcon = '🎉'; let emptyIcon = '🎉';
if (currentTab === 'pending') { if (currentTab === 'pending') {
emptyMessage = 'Keine offenen Beiträge!'; emptyMessage = 'Keine offenen Beiträge!';
} else if (currentTab === 'expired') { } else {
emptyMessage = 'Keine abgelaufenen oder abgeschlossenen Beiträge.'; emptyMessage = 'Keine Beiträge vorhanden.';
} }
if (searchActive) { if (searchActive) {

View File

@@ -20,10 +20,10 @@
<h1>📋 Post Tracker</h1> <h1>📋 Post Tracker</h1>
</div> </div>
<div class="site-nav"> <div class="site-nav">
<button type="button" class="site-nav__btn" data-view-target="posts">📝 Beiträge</button> <a class="site-nav__btn" data-view-target="posts" href="posts.html">📝 Beiträge</a>
<button type="button" class="site-nav__btn" data-view-target="dashboard">📊 Dashboard</button> <a class="site-nav__btn" data-view-target="dashboard" href="dashboard.html">📊 Dashboard</a>
<button type="button" class="site-nav__btn" data-view-target="settings">⚙️ Einstellungen</button> <a class="site-nav__btn" data-view-target="settings" href="settings.html">⚙️ Einstellungen</a>
<button type="button" class="site-nav__btn" data-view-target="bookmarks">🔖 Bookmarks</button> <a class="site-nav__btn" data-view-target="bookmarks" href="bookmarks.html">🔖 Bookmarks</a>
</div> </div>
</div> </div>
</div> </div>
@@ -82,11 +82,14 @@
<div class="tabs-section"> <div class="tabs-section">
<div class="tabs"> <div class="tabs">
<button class="tab-btn active" data-tab="pending">Offene Beiträge</button> <button class="tab-btn active" data-tab="pending">Offene Beiträge</button>
<button class="tab-btn" data-tab="expired">Abgelaufen/Abgeschlossen</button>
<button class="tab-btn" data-tab="all">Alle Beiträge</button> <button class="tab-btn" data-tab="all">Alle Beiträge</button>
</div> </div>
<div class="search-container"> <div class="search-container">
<input type="text" id="searchInput" class="search-input" placeholder="Beiträge durchsuchen..."> <input type="text" id="searchInput" class="search-input" placeholder="Beiträge durchsuchen...">
<label class="search-filter-toggle" for="includeExpiredToggle">
<input type="checkbox" id="includeExpiredToggle">
<span>Abgelaufene/abgeschlossene anzeigen</span>
</label>
</div> </div>
</div> </div>
@@ -663,7 +666,11 @@
buttons.forEach((button) => { buttons.forEach((button) => {
const isActive = button.dataset.viewTarget === view; const isActive = button.dataset.viewTarget === view;
button.classList.toggle('nav-active', isActive); button.classList.toggle('nav-active', isActive);
button.setAttribute('aria-pressed', isActive ? 'true' : 'false'); if (isActive) {
button.setAttribute('aria-current', 'page');
} else {
button.removeAttribute('aria-current');
}
}); });
if (options.updateHistory) { if (options.updateHistory) {
@@ -683,7 +690,11 @@
} }
buttons.forEach((button) => { buttons.forEach((button) => {
button.addEventListener('click', () => { button.addEventListener('click', (event) => {
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return;
}
event.preventDefault();
setView(button.dataset.viewTarget); setView(button.dataset.viewTarget);
}); });
}); });

View File

@@ -515,8 +515,10 @@ async function deleteCredential(id) {
showSuccess('Anmeldedaten gelöscht'); showSuccess('Anmeldedaten gelöscht');
} }
// Make function globally accessible // Make functions globally accessible for inline handlers
window.toggleCredentialActive = toggleCredentialActive; window.toggleCredentialActive = toggleCredentialActive;
window.editCredential = editCredential;
window.deleteCredential = deleteCredential;
// ============================================================================ // ============================================================================
// DRAG AND DROP // DRAG AND DROP

View File

@@ -113,6 +113,7 @@ header {
letter-spacing: 0.01em; letter-spacing: 0.01em;
padding: 10px 16px; padding: 10px 16px;
position: relative; position: relative;
text-decoration: none;
} }
.site-nav__btn::after { .site-nav__btn::after {
@@ -132,6 +133,7 @@ header {
.site-nav__btn:hover { .site-nav__btn:hover {
color: #0f172a; color: #0f172a;
transform: translateY(-1px); transform: translateY(-1px);
text-decoration: none;
} }
.site-nav__btn.nav-active { .site-nav__btn.nav-active {
@@ -443,6 +445,25 @@ h1 {
.search-container { .search-container {
margin-left: auto; margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
justify-content: flex-end;
}
.search-filter-toggle {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 600;
color: #1f2937;
}
.search-filter-toggle input {
width: 16px;
height: 16px;
} }
.tab-btn { .tab-btn {