aktueller stand
This commit is contained in:
@@ -1044,6 +1044,7 @@ async function markPostChecked(postId, profileNumber, options = {}) {
|
|||||||
try {
|
try {
|
||||||
const ignoreOrder = options && options.ignoreOrder === true;
|
const ignoreOrder = options && options.ignoreOrder === true;
|
||||||
const returnError = options && options.returnError === true;
|
const returnError = options && options.returnError === true;
|
||||||
|
const allowOutOfOrderOnConflict = options && options.allowOutOfOrderOnConflict !== false;
|
||||||
console.log('[FB Tracker] Marking post as checked:', postId, 'Profile:', profileNumber);
|
console.log('[FB Tracker] Marking post as checked:', postId, 'Profile:', profileNumber);
|
||||||
const response = await backendFetch(`${API_URL}/posts/${postId}/check`, {
|
const response = await backendFetch(`${API_URL}/posts/${postId}/check`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -1066,6 +1067,14 @@ async function markPostChecked(postId, profileNumber, options = {}) {
|
|||||||
const payload = await response.json().catch(() => ({}));
|
const payload = await response.json().catch(() => ({}));
|
||||||
const message = payload && payload.error ? payload.error : 'Beitrag kann aktuell nicht bestätigt werden.';
|
const message = payload && payload.error ? payload.error : 'Beitrag kann aktuell nicht bestätigt werden.';
|
||||||
console.log('[FB Tracker] Post check blocked:', message);
|
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;
|
return returnError ? { error: message, status: response.status } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2224,6 +2233,7 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fullText = textNodes.join(' ');
|
const fullText = textNodes.join(' ');
|
||||||
|
const normalizedText = fullText.replace(/(\d)\s*([.:])\s*(\d)/g, '$1$2$3');
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
@@ -2278,12 +2288,12 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
const hasInclusiveKeywordNear = (text, index) => {
|
const hasInclusiveKeywordNear = (text, index) => {
|
||||||
const windowStart = Math.max(0, index - 40);
|
const windowStart = Math.max(0, index - 40);
|
||||||
const windowText = text.slice(windowStart, index).toLowerCase();
|
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 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 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) {
|
if (rangeMatch) {
|
||||||
const endDay = parseInt(rangeMatch[4], 10);
|
const endDay = parseInt(rangeMatch[4], 10);
|
||||||
const endMonth = parseInt(rangeMatch[5], 10);
|
const endMonth = parseInt(rangeMatch[5], 10);
|
||||||
@@ -2301,7 +2311,7 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
|
|
||||||
for (const pattern of patterns) {
|
for (const pattern of patterns) {
|
||||||
let match;
|
let match;
|
||||||
while ((match = pattern.exec(fullText)) !== null) {
|
while ((match = pattern.exec(normalizedText)) !== null) {
|
||||||
const day = parseInt(match[1], 10);
|
const day = parseInt(match[1], 10);
|
||||||
const month = parseInt(match[2], 10);
|
const month = parseInt(match[2], 10);
|
||||||
let year = match[3] ? parseInt(match[3], 10) : today.getFullYear();
|
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.)
|
// Check if date is valid (e.g., not 31.02.)
|
||||||
if (date.getMonth() === month - 1 && date.getDate() === day) {
|
if (date.getMonth() === month - 1 && date.getDate() === day) {
|
||||||
const timeInfo = extractTimeAfterIndex(fullText, pattern.lastIndex);
|
const timeInfo = extractTimeAfterIndex(normalizedText, pattern.lastIndex);
|
||||||
const hasTime = Boolean(timeInfo);
|
const hasTime = Boolean(timeInfo);
|
||||||
if (timeInfo) {
|
if (timeInfo) {
|
||||||
date.setHours(timeInfo.hour, timeInfo.minute, 0, 0);
|
date.setHours(timeInfo.hour, timeInfo.minute, 0, 0);
|
||||||
} else if (hasInclusiveKeywordNear(fullText, matchIndex)) {
|
} else if (hasInclusiveKeywordNear(normalizedText, matchIndex)) {
|
||||||
date.setHours(23, 59, 0, 0);
|
date.setHours(23, 59, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasInclusiveTime = hasInclusiveKeywordNear(fullText, matchIndex);
|
const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex);
|
||||||
const recordHasTime = hasTime || hasInclusiveTime;
|
const recordHasTime = hasTime || hasInclusiveTime;
|
||||||
if (hasInclusiveTime && !hasTime) {
|
if (hasInclusiveTime && !hasTime) {
|
||||||
date.setHours(23, 59, 0, 0);
|
date.setHours(23, 59, 0, 0);
|
||||||
@@ -2344,7 +2354,7 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
// Pattern for "12. Oktober" or "12 Oktober"
|
// 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;
|
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;
|
let monthMatch;
|
||||||
while ((monthMatch = monthPattern.exec(fullText)) !== null) {
|
while ((monthMatch = monthPattern.exec(normalizedText)) !== null) {
|
||||||
const day = parseInt(monthMatch[1], 10);
|
const day = parseInt(monthMatch[1], 10);
|
||||||
const monthStr = monthMatch[2].toLowerCase();
|
const monthStr = monthMatch[2].toLowerCase();
|
||||||
const month = monthNames[monthStr];
|
const month = monthNames[monthStr];
|
||||||
@@ -2362,15 +2372,15 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
|
|
||||||
// Check if date is valid
|
// Check if date is valid
|
||||||
if (date.getMonth() === month - 1 && date.getDate() === day) {
|
if (date.getMonth() === month - 1 && date.getDate() === day) {
|
||||||
const timeInfo = extractTimeAfterIndex(fullText, monthPattern.lastIndex);
|
const timeInfo = extractTimeAfterIndex(normalizedText, monthPattern.lastIndex);
|
||||||
const hasTime = Boolean(timeInfo);
|
const hasTime = Boolean(timeInfo);
|
||||||
if (timeInfo) {
|
if (timeInfo) {
|
||||||
date.setHours(timeInfo.hour, timeInfo.minute, 0, 0);
|
date.setHours(timeInfo.hour, timeInfo.minute, 0, 0);
|
||||||
} else if (hasInclusiveKeywordNear(fullText, matchIndex)) {
|
} else if (hasInclusiveKeywordNear(normalizedText, matchIndex)) {
|
||||||
date.setHours(23, 59, 0, 0);
|
date.setHours(23, 59, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasInclusiveTime = hasInclusiveKeywordNear(fullText, matchIndex);
|
const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex);
|
||||||
const recordHasTime = hasTime || hasInclusiveTime;
|
const recordHasTime = hasTime || hasInclusiveTime;
|
||||||
if (hasInclusiveTime && !hasTime) {
|
if (hasInclusiveTime && !hasTime) {
|
||||||
date.setHours(23, 59, 0, 0);
|
date.setHours(23, 59, 0, 0);
|
||||||
|
|||||||
90
web/app.js
90
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() {
|
function markAppReady() {
|
||||||
if (document && document.body) {
|
if (document && document.body) {
|
||||||
document.body.classList.remove('auth-pending');
|
document.body.classList.remove('auth-pending');
|
||||||
@@ -529,8 +585,11 @@ function setPendingBulkStatus(message = '', isError = false) {
|
|||||||
if (!pendingBulkStatus) {
|
if (!pendingBulkStatus) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pendingBulkStatus.textContent = message || '';
|
pendingBulkStatus.textContent = '';
|
||||||
pendingBulkStatus.classList.toggle('bulk-status--error', !!isError);
|
pendingBulkStatus.classList.remove('bulk-status--error');
|
||||||
|
if (message) {
|
||||||
|
showToast(message, isError ? 'error' : 'info');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeFocusParams() {
|
function initializeFocusParams() {
|
||||||
@@ -1130,18 +1189,20 @@ function formatFacebookDateParts(date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildBookmarkFiltersParam() {
|
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)
|
// start_month = Monat von (heute - BOOKMARK_WINDOW_DAYS), auf Monatsanfang (ohne Padding)
|
||||||
const windowAgo = new Date();
|
const windowAgo = new Date();
|
||||||
windowAgo.setDate(windowAgo.getDate() - BOOKMARK_WINDOW_DAYS);
|
windowAgo.setDate(windowAgo.getDate() - BOOKMARK_WINDOW_DAYS);
|
||||||
|
const startYear = windowAgo.getFullYear();
|
||||||
const startMonthNum = windowAgo.getMonth() + 1; // 1..12
|
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"
|
const startDayLabel = `${startMonthLabel}-1`; // z.B. "2025-9-1"
|
||||||
|
|
||||||
// Ende = Jahresende (ohne Padding), Jahre immer aktuelles Jahr als String
|
// Ende = Jahresende (ohne Padding), Jahre immer aktuelles Jahr als String
|
||||||
const endMonthLabel = `${y}-12`;
|
const endMonthLabel = `${endYear}-12`;
|
||||||
const endDayLabel = `${y}-12-31`;
|
const endDayLabel = `${endYear}-12-31`;
|
||||||
|
|
||||||
// Reihenfolge wie gewünscht: top_tab zuerst, dann rp_creation_time
|
// Reihenfolge wie gewünscht: top_tab zuerst, dann rp_creation_time
|
||||||
const filtersPayload = {
|
const filtersPayload = {
|
||||||
@@ -1152,9 +1213,9 @@ const y = String(new Date().getFullYear()); // "2025"
|
|||||||
'rp_creation_time:0': JSON.stringify({
|
'rp_creation_time:0': JSON.stringify({
|
||||||
name: 'creation_time',
|
name: 'creation_time',
|
||||||
args: JSON.stringify({
|
args: JSON.stringify({
|
||||||
start_year: y, // als String
|
start_year: String(startYear), // als String
|
||||||
start_month: startMonthLabel,
|
start_month: startMonthLabel,
|
||||||
end_year: y, // als String
|
end_year: String(endYear), // als String
|
||||||
end_month: endMonthLabel,
|
end_month: endMonthLabel,
|
||||||
start_day: startDayLabel,
|
start_day: startDayLabel,
|
||||||
end_day: endDayLabel
|
end_day: endDayLabel
|
||||||
@@ -2469,7 +2530,7 @@ function openNativeDeadlinePicker(post, triggerElement) {
|
|||||||
<div class="deadline-picker__field">
|
<div class="deadline-picker__field">
|
||||||
<label>
|
<label>
|
||||||
<span>Uhrzeit</span>
|
<span>Uhrzeit</span>
|
||||||
<input type="time" class="deadline-picker__time" step="900" placeholder="hh:mm">
|
<input type="time" class="deadline-picker__time" step="60" placeholder="hh:mm">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="deadline-picker__hint">Uhrzeit optional – Standard ist 00:00 Uhr.</div>
|
<div class="deadline-picker__hint">Uhrzeit optional – Standard ist 00:00 Uhr.</div>
|
||||||
@@ -4476,20 +4537,25 @@ async function openPost(postId) {
|
|||||||
|
|
||||||
if (!status.canCurrentProfileCheck) {
|
if (!status.canCurrentProfileCheck) {
|
||||||
if (status.waitingForNames.length) {
|
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 {
|
} else {
|
||||||
alert('Der Beitrag kann aktuell nicht abgehakt werden.');
|
alert('Der Beitrag kann aktuell nicht abgehakt werden.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const ignoreOrder = !status.canCurrentProfileCheck && status.waitingForNames.length > 0;
|
||||||
const response = await apiFetch(`${API_URL}/check-by-url`, {
|
const response = await apiFetch(`${API_URL}/check-by-url`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
url: post.url,
|
url: post.url,
|
||||||
profile_number: currentProfile
|
profile_number: currentProfile,
|
||||||
|
ignore_order: ignoreOrder
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user