From b81c2042e939ea386097666812b6ff6469338b00 Mon Sep 17 00:00:00 2001 From: Meik Date: Mon, 5 Jan 2026 19:58:07 +0100 Subject: [PATCH] aktueller stand --- extension/content.js | 30 ++++++++++----- web/app.js | 90 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/extension/content.js b/extension/content.js index aa0a1ec..fddad97 100644 --- a/extension/content.js +++ b/extension/content.js @@ -1044,6 +1044,7 @@ async function markPostChecked(postId, profileNumber, options = {}) { try { const ignoreOrder = options && options.ignoreOrder === true; const returnError = options && options.returnError === true; + const allowOutOfOrderOnConflict = options && options.allowOutOfOrderOnConflict !== false; console.log('[FB Tracker] Marking post as checked:', postId, 'Profile:', profileNumber); const response = await backendFetch(`${API_URL}/posts/${postId}/check`, { method: 'POST', @@ -1066,6 +1067,14 @@ async function markPostChecked(postId, profileNumber, options = {}) { const payload = await response.json().catch(() => ({})); const message = payload && payload.error ? payload.error : 'Beitrag kann aktuell nicht bestätigt werden.'; console.log('[FB Tracker] Post check blocked:', message); + if (!ignoreOrder && allowOutOfOrderOnConflict && Array.isArray(payload.missing_profiles) && payload.missing_profiles.length) { + return markPostChecked(postId, profileNumber, { + ...options, + ignoreOrder: true, + returnError: true, + allowOutOfOrderOnConflict: false + }); + } return returnError ? { error: message, status: response.status } : null; } @@ -2224,6 +2233,7 @@ function extractDeadlineFromPostText(postElement) { } const fullText = textNodes.join(' '); + const normalizedText = fullText.replace(/(\d)\s*([.:])\s*(\d)/g, '$1$2$3'); const today = new Date(); today.setHours(0, 0, 0, 0); @@ -2278,12 +2288,12 @@ function extractDeadlineFromPostText(postElement) { const hasInclusiveKeywordNear = (text, index) => { const windowStart = Math.max(0, index - 40); const windowText = text.slice(windowStart, index).toLowerCase(); - return /\b(einschlie(?:ß|ss)lich|inklusive|inkl\.)\b/.test(windowText); + return /\b(einschlie(?:ß|ss)lich|einschl\.?|inklusive|inkl\.)\b/.test(windowText); }; const foundDates = []; const rangePattern = /\b(\d{1,2})\.(\d{1,2})\.(\d{2,4})\s*(?:-|–|—|bis)\s*(\d{1,2})\.(\d{1,2})\.(\d{2,4})\b/i; - const rangeMatch = rangePattern.exec(fullText); + const rangeMatch = rangePattern.exec(normalizedText); if (rangeMatch) { const endDay = parseInt(rangeMatch[4], 10); const endMonth = parseInt(rangeMatch[5], 10); @@ -2301,7 +2311,7 @@ function extractDeadlineFromPostText(postElement) { for (const pattern of patterns) { let match; - while ((match = pattern.exec(fullText)) !== null) { + while ((match = pattern.exec(normalizedText)) !== null) { const day = parseInt(match[1], 10); const month = parseInt(match[2], 10); let year = match[3] ? parseInt(match[3], 10) : today.getFullYear(); @@ -2318,15 +2328,15 @@ function extractDeadlineFromPostText(postElement) { // Check if date is valid (e.g., not 31.02.) if (date.getMonth() === month - 1 && date.getDate() === day) { - const timeInfo = extractTimeAfterIndex(fullText, pattern.lastIndex); + const timeInfo = extractTimeAfterIndex(normalizedText, pattern.lastIndex); const hasTime = Boolean(timeInfo); if (timeInfo) { date.setHours(timeInfo.hour, timeInfo.minute, 0, 0); - } else if (hasInclusiveKeywordNear(fullText, matchIndex)) { + } else if (hasInclusiveKeywordNear(normalizedText, matchIndex)) { date.setHours(23, 59, 0, 0); } - const hasInclusiveTime = hasInclusiveKeywordNear(fullText, matchIndex); + const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex); const recordHasTime = hasTime || hasInclusiveTime; if (hasInclusiveTime && !hasTime) { date.setHours(23, 59, 0, 0); @@ -2344,7 +2354,7 @@ function extractDeadlineFromPostText(postElement) { // Pattern for "12. Oktober" or "12 Oktober" const monthPattern = /\b(\d{1,2})\.?\s*(januar|jan|februar|feb|märz|mär|maerz|april|apr|mai|juni|jun|juli|jul|august|aug|september|sep|sept|oktober|okt|november|nov|dezember|dez)\s*(\d{2,4})?\b/gi; let monthMatch; - while ((monthMatch = monthPattern.exec(fullText)) !== null) { + while ((monthMatch = monthPattern.exec(normalizedText)) !== null) { const day = parseInt(monthMatch[1], 10); const monthStr = monthMatch[2].toLowerCase(); const month = monthNames[monthStr]; @@ -2362,15 +2372,15 @@ function extractDeadlineFromPostText(postElement) { // Check if date is valid if (date.getMonth() === month - 1 && date.getDate() === day) { - const timeInfo = extractTimeAfterIndex(fullText, monthPattern.lastIndex); + const timeInfo = extractTimeAfterIndex(normalizedText, monthPattern.lastIndex); const hasTime = Boolean(timeInfo); if (timeInfo) { date.setHours(timeInfo.hour, timeInfo.minute, 0, 0); - } else if (hasInclusiveKeywordNear(fullText, matchIndex)) { + } else if (hasInclusiveKeywordNear(normalizedText, matchIndex)) { date.setHours(23, 59, 0, 0); } - const hasInclusiveTime = hasInclusiveKeywordNear(fullText, matchIndex); + const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex); const recordHasTime = hasTime || hasInclusiveTime; if (hasInclusiveTime && !hasTime) { date.setHours(23, 59, 0, 0); diff --git a/web/app.js b/web/app.js index 36c1dec..a335106 100644 --- a/web/app.js +++ b/web/app.js @@ -82,6 +82,62 @@ function apiFetch(url, options = {}) { }); } +function showToast(message, type = 'info') { + const toast = document.createElement('div'); + toast.style.cssText = ` + position: fixed; + bottom: 84px; + right: 24px; + background: ${type === 'error' ? '#e74c3c' : type === 'success' ? '#42b72a' : '#1877f2'}; + color: white; + padding: 12px 20px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + font-size: 14px; + z-index: 999999; + max-width: 350px; + animation: appToastSlideIn 0.3s ease-out; + pointer-events: none; + `; + toast.textContent = message; + + if (!document.getElementById('app-toast-styles')) { + const style = document.createElement('style'); + style.id = 'app-toast-styles'; + style.textContent = ` + @keyframes appToastSlideIn { + from { + transform: translateX(400px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + @keyframes appToastSlideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(400px); + opacity: 0; + } + } + `; + document.head.appendChild(style); + } + + document.body.appendChild(toast); + + setTimeout(() => { + toast.style.animation = 'appToastSlideOut 0.3s ease-out'; + setTimeout(() => toast.remove(), 300); + }, 3000); +} + function markAppReady() { if (document && document.body) { document.body.classList.remove('auth-pending'); @@ -529,8 +585,11 @@ function setPendingBulkStatus(message = '', isError = false) { if (!pendingBulkStatus) { return; } - pendingBulkStatus.textContent = message || ''; - pendingBulkStatus.classList.toggle('bulk-status--error', !!isError); + pendingBulkStatus.textContent = ''; + pendingBulkStatus.classList.remove('bulk-status--error'); + if (message) { + showToast(message, isError ? 'error' : 'info'); + } } function initializeFocusParams() { @@ -1130,18 +1189,20 @@ function formatFacebookDateParts(date) { } function buildBookmarkFiltersParam() { -const y = String(new Date().getFullYear()); // "2025" + const now = new Date(); + const endYear = now.getFullYear(); // start_month = Monat von (heute - BOOKMARK_WINDOW_DAYS), auf Monatsanfang (ohne Padding) const windowAgo = new Date(); windowAgo.setDate(windowAgo.getDate() - BOOKMARK_WINDOW_DAYS); + const startYear = windowAgo.getFullYear(); const startMonthNum = windowAgo.getMonth() + 1; // 1..12 - const startMonthLabel = `${y}-${startMonthNum}`; // z.B. "2025-9" + const startMonthLabel = `${startYear}-${startMonthNum}`; // z.B. "2025-9" const startDayLabel = `${startMonthLabel}-1`; // z.B. "2025-9-1" // Ende = Jahresende (ohne Padding), Jahre immer aktuelles Jahr als String - const endMonthLabel = `${y}-12`; - const endDayLabel = `${y}-12-31`; + const endMonthLabel = `${endYear}-12`; + const endDayLabel = `${endYear}-12-31`; // Reihenfolge wie gewünscht: top_tab zuerst, dann rp_creation_time const filtersPayload = { @@ -1152,9 +1213,9 @@ const y = String(new Date().getFullYear()); // "2025" 'rp_creation_time:0': JSON.stringify({ name: 'creation_time', args: JSON.stringify({ - start_year: y, // als String + start_year: String(startYear), // als String start_month: startMonthLabel, - end_year: y, // als String + end_year: String(endYear), // als String end_month: endMonthLabel, start_day: startDayLabel, end_day: endDayLabel @@ -2469,7 +2530,7 @@ function openNativeDeadlinePicker(post, triggerElement) {
Uhrzeit optional – Standard ist 00:00 Uhr.
@@ -4476,20 +4537,25 @@ async function openPost(postId) { if (!status.canCurrentProfileCheck) { if (status.waitingForNames.length) { - alert(`Wartet auf: ${status.waitingForNames.join(', ')}`); + const proceed = confirm(`Vorherige Profile müssen zuerst bestätigen (${status.waitingForNames.join(', ')}). Trotzdem bestätigen?`); + if (!proceed) { + return; + } } else { alert('Der Beitrag kann aktuell nicht abgehakt werden.'); + return; } - return; } try { + const ignoreOrder = !status.canCurrentProfileCheck && status.waitingForNames.length > 0; const response = await apiFetch(`${API_URL}/check-by-url`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: post.url, - profile_number: currentProfile + profile_number: currentProfile, + ignore_order: ignoreOrder }) });