tabs in beiträgen gemerget
This commit is contained in:
93
web/app.js
93
web/app.js
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user