Aktueller Stand
This commit is contained in:
@@ -457,8 +457,11 @@ async function fetchBackendProfileNumber() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data && data.profile_number) {
|
if (data && typeof data.profile_number !== 'undefined') {
|
||||||
return data.profile_number;
|
const parsed = parseInt(data.profile_number, 10);
|
||||||
|
if (!Number.isNaN(parsed)) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[FB Tracker] Failed to fetch profile state from backend:', error);
|
console.warn('[FB Tracker] Failed to fetch profile state from backend:', error);
|
||||||
@@ -499,25 +502,18 @@ function extractAuthorName(postElement) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeProfileNumberLocally(profileNumber) {
|
|
||||||
chrome.storage.sync.set({ profileNumber });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getProfileNumber() {
|
async function getProfileNumber() {
|
||||||
|
try {
|
||||||
const backendProfile = await fetchBackendProfileNumber();
|
const backendProfile = await fetchBackendProfileNumber();
|
||||||
if (backendProfile) {
|
if (backendProfile) {
|
||||||
storeProfileNumberLocally(backendProfile);
|
|
||||||
console.log('[FB Tracker] Profile number (backend):', backendProfile);
|
console.log('[FB Tracker] Profile number (backend):', backendProfile);
|
||||||
return backendProfile;
|
return backendProfile;
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[FB Tracker] Failed to resolve profile number from backend:', error);
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return null;
|
||||||
chrome.storage.sync.get(['profileNumber'], (result) => {
|
|
||||||
const profile = result.profileNumber || 1;
|
|
||||||
console.log('[FB Tracker] Profile number (local):', profile);
|
|
||||||
resolve(profile);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract post URL from post element
|
// Extract post URL from post element
|
||||||
@@ -2233,14 +2229,19 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fullText = textNodes.join(' ');
|
const fullText = textNodes.join(' ');
|
||||||
const normalizedText = fullText.replace(/(\d)\s*([.:])\s*(\d)/g, '$1$2$3');
|
const normalizedText = fullText
|
||||||
|
.normalize('NFKD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.replace(/[^\S\r\n]+/g, ' ')
|
||||||
|
.replace(/(\d)\s*([.:])\s*(\d)/g, '$1$2$3');
|
||||||
|
const normalizedLower = normalizedText.toLowerCase();
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
const monthNames = {
|
const monthNames = {
|
||||||
'januar': 1, 'jan': 1,
|
'januar': 1, 'jan': 1,
|
||||||
'februar': 2, 'feb': 2,
|
'februar': 2, 'feb': 2,
|
||||||
'märz': 3, 'mär': 3, 'maerz': 3,
|
'märz': 3, 'mär': 3, 'maerz': 3, 'marz': 3,
|
||||||
'april': 4, 'apr': 4,
|
'april': 4, 'apr': 4,
|
||||||
'mai': 5,
|
'mai': 5,
|
||||||
'juni': 6, 'jun': 6,
|
'juni': 6, 'jun': 6,
|
||||||
@@ -2265,7 +2266,7 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
if (/^\s*(?:-|–|—|bis)\s*\d{1,2}\.\d{1,2}\./i.test(tail)) {
|
if (/^\s*(?:-|–|—|bis)\s*\d{1,2}\.\d{1,2}\./i.test(tail)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const timeMatch = /^\s*(?:\(|\[)?\s*(?:[,;:-]|\b)?\s*(?:um|ab|bis|gegen|spätestens)?\s*(?:den|dem|am)?\s*(?:ca\.?)?\s*(\d{1,2})(?:[:.](\d{2}))?\s*(?:uhr|h)?\s*(?:\)|\])?/i.exec(tail);
|
const timeMatch = /^\s*(?:\(|\[)?\s*(?:[,;:/|-]|\b)?\s*(?:um|ab|bis|gegen|spaetestens|spatestens|spätestens)?\s*(?:den|dem|am)?\s*(?:ca\.?)?\s*(\d{1,2})(?:[:.](\d{2}))?\s*(?:uhr|h)?\s*(?:\)|\])?/i.exec(tail);
|
||||||
if (!timeMatch) {
|
if (!timeMatch) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -2291,24 +2292,98 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
return /\b(einschlie(?:ß|ss)lich|einschl\.?|inklusive|inkl\.)\b/.test(windowText);
|
return /\b(einschlie(?:ß|ss)lich|einschl\.?|inklusive|inkl\.)\b/.test(windowText);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasDeadlineKeywordNear = (text, index) => {
|
||||||
|
const windowStart = Math.max(0, index - 50);
|
||||||
|
const windowText = text.slice(windowStart, index).toLowerCase();
|
||||||
|
return /\b(bis|spaetestens|spatestens|spätestens|teilnahmeschluss|einsendeschluss|anmeldeschluss|anmeldefrist|abgabeschluss|stichtag)\b/.test(windowText);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addCandidateDate = (date, hasTime) => {
|
||||||
|
if (date > today) {
|
||||||
|
foundDates.push({ date, hasTime });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const weekdayNames = {
|
||||||
|
montag: 1,
|
||||||
|
dienstag: 2,
|
||||||
|
mittwoch: 3,
|
||||||
|
donnerstag: 4,
|
||||||
|
freitag: 5,
|
||||||
|
samstag: 6,
|
||||||
|
sonntag: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNextWeekdayDate = (weekday, preferNext) => {
|
||||||
|
const base = new Date(today);
|
||||||
|
let delta = (weekday - base.getDay() + 7) % 7;
|
||||||
|
if (delta === 0 && preferNext) {
|
||||||
|
delta = 7;
|
||||||
|
}
|
||||||
|
base.setDate(base.getDate() + delta);
|
||||||
|
return base;
|
||||||
|
};
|
||||||
|
|
||||||
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 rangePatterns = [
|
||||||
|
// DD.MM.YYYY - DD.MM.YYYY
|
||||||
|
/\b(\d{1,2})\.(\d{1,2})\.(\d{2,4})\s*(?:-|–|—|bis)\s*(\d{1,2})\.(\d{1,2})\.(\d{2,4})\b/i,
|
||||||
|
// DD.–DD.MM.YYYY (start day only)
|
||||||
|
/\b(\d{1,2})\.\s*(?:-|–|—|bis)\s*(\d{1,2})\.(\d{1,2})\.(\d{2,4})\b/i,
|
||||||
|
// vom DD.MM bis DD.MM.YYYY
|
||||||
|
/\bvom\s*(\d{1,2})\.(\d{1,2})\.?\s*(?:-|–|—|bis)\s*(\d{1,2})\.(\d{1,2})\.(\d{2,4})\b/i
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const rangePattern of rangePatterns) {
|
||||||
const rangeMatch = rangePattern.exec(normalizedText);
|
const rangeMatch = rangePattern.exec(normalizedText);
|
||||||
if (rangeMatch) {
|
if (!rangeMatch) {
|
||||||
const endDay = parseInt(rangeMatch[4], 10);
|
continue;
|
||||||
const endMonth = parseInt(rangeMatch[5], 10);
|
}
|
||||||
let endYear = parseInt(rangeMatch[6], 10);
|
|
||||||
|
const endDay = parseInt(rangeMatch[rangeMatch.length - 3], 10);
|
||||||
|
const endMonth = parseInt(rangeMatch[rangeMatch.length - 2], 10);
|
||||||
|
let endYear = parseInt(rangeMatch[rangeMatch.length - 1], 10);
|
||||||
|
|
||||||
if (endYear < 100) {
|
if (endYear < 100) {
|
||||||
endYear += 2000;
|
endYear += 2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endMonth >= 1 && endMonth <= 12 && endDay >= 1 && endDay <= 31) {
|
if (endMonth >= 1 && endMonth <= 12 && endDay >= 1 && endDay <= 31) {
|
||||||
const endDate = new Date(endYear, endMonth - 1, endDay, 0, 0, 0, 0);
|
const endDate = new Date(endYear, endMonth - 1, endDay, 23, 59, 0, 0);
|
||||||
if (endDate.getMonth() === endMonth - 1 && endDate.getDate() === endDay && endDate > today) {
|
if (endDate.getMonth() === endMonth - 1 && endDate.getDate() === endDay && endDate > today) {
|
||||||
return toDateTimeLocalString(endDate);
|
return toDateTimeLocalString(endDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Weekday-only patterns like "bis nächsten Mittwoch" or "Sonntag, 23:59 Uhr"
|
||||||
|
const weekdayPattern = /\b(montag|dienstag|mittwoch|donnerstag|freitag|samstag|sonntag)\b/gi;
|
||||||
|
let weekdayMatch;
|
||||||
|
while ((weekdayMatch = weekdayPattern.exec(normalizedLower)) !== null) {
|
||||||
|
const weekdayKey = weekdayMatch[1].toLowerCase();
|
||||||
|
const weekday = weekdayNames[weekdayKey];
|
||||||
|
if (typeof weekday !== 'number') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchIndex = weekdayMatch.index;
|
||||||
|
const windowStart = Math.max(0, matchIndex - 30);
|
||||||
|
const windowText = normalizedLower.slice(windowStart, matchIndex);
|
||||||
|
const preferNext = /\b(naechst|nachst|kommend)\b/.test(windowText);
|
||||||
|
const date = getNextWeekdayDate(weekday, preferNext);
|
||||||
|
const timeInfo = extractTimeAfterIndex(normalizedText, weekdayPattern.lastIndex);
|
||||||
|
const hasTime = Boolean(timeInfo);
|
||||||
|
|
||||||
|
if (timeInfo) {
|
||||||
|
date.setHours(timeInfo.hour, timeInfo.minute, 0, 0);
|
||||||
|
} else if (hasInclusiveKeywordNear(normalizedText, matchIndex) || hasDeadlineKeywordNear(normalizedText, matchIndex)) {
|
||||||
|
date.setHours(23, 59, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordHasTime = hasTime || hasInclusiveKeywordNear(normalizedText, matchIndex) || hasDeadlineKeywordNear(normalizedText, matchIndex);
|
||||||
|
addCandidateDate(date, recordHasTime);
|
||||||
|
}
|
||||||
|
|
||||||
for (const pattern of patterns) {
|
for (const pattern of patterns) {
|
||||||
let match;
|
let match;
|
||||||
while ((match = pattern.exec(normalizedText)) !== null) {
|
while ((match = pattern.exec(normalizedText)) !== null) {
|
||||||
@@ -2332,27 +2407,25 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
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(normalizedText, matchIndex)) {
|
} else if (hasInclusiveKeywordNear(normalizedText, matchIndex) || hasDeadlineKeywordNear(normalizedText, matchIndex)) {
|
||||||
date.setHours(23, 59, 0, 0);
|
date.setHours(23, 59, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex);
|
const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex);
|
||||||
const recordHasTime = hasTime || hasInclusiveTime;
|
const hasDeadlineTime = hasDeadlineKeywordNear(normalizedText, matchIndex);
|
||||||
|
const recordHasTime = hasTime || hasInclusiveTime || hasDeadlineTime;
|
||||||
if (hasInclusiveTime && !hasTime) {
|
if (hasInclusiveTime && !hasTime) {
|
||||||
date.setHours(23, 59, 0, 0);
|
date.setHours(23, 59, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only add if date is in the future
|
addCandidateDate(date, recordHasTime);
|
||||||
if (date > today) {
|
|
||||||
foundDates.push({ date, hasTime: recordHasTime });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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|marz|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(normalizedText)) !== null) {
|
while ((monthMatch = monthPattern.exec(normalizedText)) !== null) {
|
||||||
const day = parseInt(monthMatch[1], 10);
|
const day = parseInt(monthMatch[1], 10);
|
||||||
@@ -2376,12 +2449,13 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
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(normalizedText, matchIndex)) {
|
} else if (hasInclusiveKeywordNear(normalizedText, matchIndex) || hasDeadlineKeywordNear(normalizedText, matchIndex)) {
|
||||||
date.setHours(23, 59, 0, 0);
|
date.setHours(23, 59, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex);
|
const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex);
|
||||||
const recordHasTime = hasTime || hasInclusiveTime;
|
const hasDeadlineTime = hasDeadlineKeywordNear(normalizedText, matchIndex);
|
||||||
|
const recordHasTime = hasTime || hasInclusiveTime || hasDeadlineTime;
|
||||||
if (hasInclusiveTime && !hasTime) {
|
if (hasInclusiveTime && !hasTime) {
|
||||||
date.setHours(23, 59, 0, 0);
|
date.setHours(23, 59, 0, 0);
|
||||||
}
|
}
|
||||||
@@ -2390,23 +2464,17 @@ function extractDeadlineFromPostText(postElement) {
|
|||||||
if (date <= today) {
|
if (date <= today) {
|
||||||
date.setFullYear(year + 1);
|
date.setFullYear(year + 1);
|
||||||
}
|
}
|
||||||
foundDates.push({ date, hasTime: recordHasTime });
|
addCandidateDate(date, recordHasTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the earliest future date
|
// Prefer dates with explicit time; otherwise fall back to earliest date.
|
||||||
if (foundDates.length > 0) {
|
if (foundDates.length > 0) {
|
||||||
foundDates.sort((a, b) => {
|
const withTime = foundDates.filter((entry) => entry.hasTime);
|
||||||
const diff = a.date - b.date;
|
const candidates = withTime.length ? withTime : foundDates;
|
||||||
if (diff !== 0) {
|
candidates.sort((a, b) => a.date - b.date);
|
||||||
return diff;
|
return toDateTimeLocalString(candidates[0].date);
|
||||||
}
|
|
||||||
if (a.hasTime && !b.hasTime) return -1;
|
|
||||||
if (!a.hasTime && b.hasTime) return 1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
return toDateTimeLocalString(foundDates[0].date);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -2735,6 +2803,9 @@ async function renderTrackedStatus({
|
|||||||
return { hidden: false };
|
return { hidden: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parsedProfile = parseInt(profileNumber, 10);
|
||||||
|
const effectiveProfileNumber = Number.isNaN(parsedProfile) ? null : parsedProfile;
|
||||||
|
|
||||||
if (postData.id) {
|
if (postData.id) {
|
||||||
container.dataset.postId = postData.id;
|
container.dataset.postId = postData.id;
|
||||||
}
|
}
|
||||||
@@ -2760,9 +2831,12 @@ async function renderTrackedStatus({
|
|||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
: Array.from({ length: Math.max(1, Math.min(5, parseInt(postData.target_count, 10) || 1)) }, (_, index) => index + 1);
|
: Array.from({ length: Math.max(1, Math.min(5, parseInt(postData.target_count, 10) || 1)) }, (_, index) => index + 1);
|
||||||
const isCurrentProfileRequired = requiredProfiles.includes(profileNumber);
|
const isCurrentProfileRequired = effectiveProfileNumber !== null && requiredProfiles.includes(effectiveProfileNumber);
|
||||||
const canCurrentProfileCheck = isCurrentProfileRequired && postData.next_required_profile === profileNumber;
|
const parsedNextRequired = parseInt(postData.next_required_profile, 10);
|
||||||
const isCurrentProfileDone = checks.some(check => check.profile_number === profileNumber);
|
const nextRequiredProfile = Number.isNaN(parsedNextRequired) ? null : parsedNextRequired;
|
||||||
|
const canCurrentProfileCheck = isCurrentProfileRequired && nextRequiredProfile === effectiveProfileNumber;
|
||||||
|
const isCurrentProfileDone = effectiveProfileNumber !== null
|
||||||
|
&& checks.some(check => check.profile_number === effectiveProfileNumber);
|
||||||
|
|
||||||
if (isFeedHome && isCurrentProfileDone) {
|
if (isFeedHome && isCurrentProfileDone) {
|
||||||
if (isDialogContext) {
|
if (isDialogContext) {
|
||||||
@@ -2815,6 +2889,23 @@ async function renderTrackedStatus({
|
|||||||
✓ Bestätigen
|
✓ Bestätigen
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
} else if (!isExpired && !completed && !isCurrentProfileDone && effectiveProfileNumber === null) {
|
||||||
|
statusHtml += `
|
||||||
|
<button class="fb-tracker-check-btn" title="Profilstatus nicht geladen" style="
|
||||||
|
padding: 4px 12px;
|
||||||
|
background-color: #9ca3af;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: not-allowed;
|
||||||
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 8px;
|
||||||
|
" disabled>
|
||||||
|
✓ Bestätigen
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
} else if (isCurrentProfileDone) {
|
} else if (isCurrentProfileDone) {
|
||||||
statusHtml += `
|
statusHtml += `
|
||||||
<div style="color: #42b72a; font-size: 12px; white-space: nowrap; margin-left: 8px;">
|
<div style="color: #42b72a; font-size: 12px; white-space: nowrap; margin-left: 8px;">
|
||||||
@@ -2885,17 +2976,21 @@ async function renderTrackedStatus({
|
|||||||
const checkBtn = container.querySelector('.fb-tracker-check-btn');
|
const checkBtn = container.querySelector('.fb-tracker-check-btn');
|
||||||
if (checkBtn) {
|
if (checkBtn) {
|
||||||
checkBtn.addEventListener('click', async () => {
|
checkBtn.addEventListener('click', async () => {
|
||||||
|
if (effectiveProfileNumber === null) {
|
||||||
|
showToast('Profilstatus nicht geladen. Bitte Tracker neu laden.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
checkBtn.disabled = true;
|
checkBtn.disabled = true;
|
||||||
checkBtn.textContent = 'Wird bestätigt...';
|
checkBtn.textContent = 'Wird bestätigt...';
|
||||||
|
|
||||||
const result = await markPostChecked(postData.id, profileNumber, { returnError: true });
|
const result = await markPostChecked(postData.id, effectiveProfileNumber, { returnError: true });
|
||||||
|
|
||||||
if (result && !result.error) {
|
if (result && !result.error) {
|
||||||
await renderTrackedStatus({
|
await renderTrackedStatus({
|
||||||
container,
|
container,
|
||||||
postElement,
|
postElement,
|
||||||
postData: result,
|
postData: result,
|
||||||
profileNumber,
|
profileNumber: effectiveProfileNumber,
|
||||||
isFeedHome,
|
isFeedHome,
|
||||||
isDialogContext,
|
isDialogContext,
|
||||||
manualHideInfo,
|
manualHideInfo,
|
||||||
@@ -3331,6 +3426,13 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
|
|||||||
const targetCount = parseInt(selectElement.value, 10);
|
const targetCount = parseInt(selectElement.value, 10);
|
||||||
console.log('[FB Tracker] Add button clicked, target:', targetCount);
|
console.log('[FB Tracker] Add button clicked, target:', targetCount);
|
||||||
|
|
||||||
|
if (!profileNumber) {
|
||||||
|
showToast('Profilstatus nicht geladen. Bitte Tracker neu laden.', 'error');
|
||||||
|
addButton.disabled = false;
|
||||||
|
addButton.textContent = 'Erneut versuchen';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
addButton.disabled = true;
|
addButton.disabled = true;
|
||||||
addButton.textContent = 'Wird hinzugefügt...';
|
addButton.textContent = 'Wird hinzugefügt...';
|
||||||
|
|
||||||
@@ -5277,6 +5379,11 @@ async function handleSelectionAIRequest(selectionText, sendResponse) {
|
|||||||
showToast('AI verarbeitet Auswahl...', 'info');
|
showToast('AI verarbeitet Auswahl...', 'info');
|
||||||
|
|
||||||
const profileNumber = await getProfileNumber();
|
const profileNumber = await getProfileNumber();
|
||||||
|
if (!profileNumber) {
|
||||||
|
showToast('Profilstatus nicht geladen. Bitte Tracker neu laden.', 'error');
|
||||||
|
sendResponse({ error: 'profile-missing' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
const comment = await generateAIComment(normalizedSelection, profileNumber, {});
|
const comment = await generateAIComment(normalizedSelection, profileNumber, {});
|
||||||
|
|
||||||
if (!comment) {
|
if (!comment) {
|
||||||
@@ -5597,6 +5704,10 @@ async function addAICommentButton(container, postElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const effectiveProfile = profileNumber || await getProfileNumber();
|
const effectiveProfile = profileNumber || await getProfileNumber();
|
||||||
|
if (!effectiveProfile) {
|
||||||
|
showToast('Profilstatus nicht geladen. Bitte Tracker neu laden.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const decodedUrl = getDecodedPostUrl();
|
const decodedUrl = getDecodedPostUrl();
|
||||||
const isFeedHomeFlag = container.dataset.isFeedHome === '1';
|
const isFeedHomeFlag = container.dataset.isFeedHome === '1';
|
||||||
const isDialogFlag = container.dataset.isDialogContext === '1';
|
const isDialogFlag = container.dataset.isDialogContext === '1';
|
||||||
@@ -6095,6 +6206,10 @@ async function addAICommentButton(container, postElement) {
|
|||||||
console.log('[FB Tracker] Generating AI comment for:', postText.substring(0, 100));
|
console.log('[FB Tracker] Generating AI comment for:', postText.substring(0, 100));
|
||||||
|
|
||||||
const profileNumber = await getProfileNumber();
|
const profileNumber = await getProfileNumber();
|
||||||
|
if (!profileNumber) {
|
||||||
|
showToast('Profilstatus nicht geladen. Bitte Tracker neu laden.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
throwIfCancelled();
|
throwIfCancelled();
|
||||||
|
|
||||||
|
|||||||
84
web/app.js
84
web/app.js
@@ -1287,13 +1287,97 @@ function buildBookmarkSearchUrl(query) {
|
|||||||
return searchUrl.toString();
|
return searchUrl.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function splitBookmarkGroupOptions(rawGroup) {
|
||||||
|
if (typeof rawGroup !== 'string') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawGroup
|
||||||
|
.split(',')
|
||||||
|
.map((entry) => entry.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandBookmarkQueryVariants(baseQuery) {
|
||||||
|
const trimmed = typeof baseQuery === 'string' ? baseQuery.trim() : '';
|
||||||
|
if (!trimmed) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const variants = [''];
|
||||||
|
let cursor = 0;
|
||||||
|
|
||||||
|
while (cursor < trimmed.length) {
|
||||||
|
const openIndex = trimmed.indexOf('(', cursor);
|
||||||
|
if (openIndex === -1) {
|
||||||
|
const tail = trimmed.slice(cursor);
|
||||||
|
for (let i = 0; i < variants.length; i += 1) {
|
||||||
|
variants[i] = `${variants[i]}${tail}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeIndex = trimmed.indexOf(')', openIndex + 1);
|
||||||
|
if (closeIndex === -1) {
|
||||||
|
const tail = trimmed.slice(cursor);
|
||||||
|
for (let i = 0; i < variants.length; i += 1) {
|
||||||
|
variants[i] = `${variants[i]}${tail}`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const before = trimmed.slice(cursor, openIndex);
|
||||||
|
if (before) {
|
||||||
|
for (let i = 0; i < variants.length; i += 1) {
|
||||||
|
variants[i] = `${variants[i]}${before}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupRaw = trimmed.slice(openIndex + 1, closeIndex);
|
||||||
|
const options = splitBookmarkGroupOptions(groupRaw);
|
||||||
|
|
||||||
|
if (options.length) {
|
||||||
|
const expanded = [];
|
||||||
|
variants.forEach((prefix) => {
|
||||||
|
options.forEach((option) => {
|
||||||
|
expanded.push(`${prefix}${option}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
variants.splice(0, variants.length, ...expanded);
|
||||||
|
} else {
|
||||||
|
const literalGroup = trimmed.slice(openIndex, closeIndex + 1);
|
||||||
|
for (let i = 0; i < variants.length; i += 1) {
|
||||||
|
variants[i] = `${variants[i]}${literalGroup}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = closeIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return variants
|
||||||
|
.map((variant) => variant.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
function buildBookmarkSearchQueries(baseQuery) {
|
function buildBookmarkSearchQueries(baseQuery) {
|
||||||
const trimmed = typeof baseQuery === 'string' ? baseQuery.trim() : '';
|
const trimmed = typeof baseQuery === 'string' ? baseQuery.trim() : '';
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
return [...BOOKMARK_SUFFIXES];
|
return [...BOOKMARK_SUFFIXES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const baseVariants = expandBookmarkQueryVariants(trimmed);
|
||||||
|
if (!baseVariants.length) {
|
||||||
return BOOKMARK_SUFFIXES.map((suffix) => `${trimmed} ${suffix}`.trim());
|
return BOOKMARK_SUFFIXES.map((suffix) => `${trimmed} ${suffix}`.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
const queries = [];
|
||||||
|
baseVariants.forEach((variant) => {
|
||||||
|
BOOKMARK_SUFFIXES.forEach((suffix) => {
|
||||||
|
queries.push(`${variant} ${suffix}`.trim());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...new Set(queries)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function openBookmarkQueries(baseQuery) {
|
function openBookmarkQueries(baseQuery) {
|
||||||
|
|||||||
@@ -283,24 +283,23 @@
|
|||||||
.automation-view .auto-table .sort-indicator {
|
.automation-view .auto-table .sort-indicator {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
width: 10px;
|
width: 18px;
|
||||||
height: 10px;
|
height: 18px;
|
||||||
border: 5px solid transparent;
|
background-color: var(--automation-muted);
|
||||||
border-bottom: 0;
|
-webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='black' d='M4 6l4 4 4-4z'/></svg>") no-repeat center / contain;
|
||||||
border-left: 0;
|
mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='black' d='M4 6l4 4 4-4z'/></svg>") no-repeat center / contain;
|
||||||
transform: rotate(45deg);
|
opacity: 0.35;
|
||||||
opacity: 0.25;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.automation-view .auto-table th.sort-asc .sort-indicator {
|
.automation-view .auto-table th.sort-asc .sort-indicator {
|
||||||
border-top: 6px solid var(--automation-accent-2);
|
background-color: var(--automation-accent-2);
|
||||||
transform: rotate(225deg);
|
transform: rotate(180deg);
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.automation-view .auto-table th.sort-desc .sort-indicator {
|
.automation-view .auto-table th.sort-desc .sort-indicator {
|
||||||
border-top: 6px solid var(--automation-accent-2);
|
background-color: var(--automation-accent-2);
|
||||||
transform: rotate(45deg);
|
transform: rotate(0deg);
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -637,7 +637,7 @@
|
|||||||
<br><small>${formatDateTime(req.next_run_at)}</small>
|
<br><small>${formatDateTime(req.next_run_at)}</small>
|
||||||
</td>
|
</td>
|
||||||
<td data-sort="${untilSort}">
|
<td data-sort="${untilSort}">
|
||||||
<span class="until" data-sort="${untilSort}">${req.run_until ? formatRelative(req.run_until) : '—'}</span>
|
<span class="runUntil until" data-sort="${untilSort}">${req.run_until ? formatRelative(req.run_until) : '—'}</span>
|
||||||
<br><small>${formatDateTime(req.run_until)}</small>
|
<br><small>${formatDateTime(req.run_until)}</small>
|
||||||
</td>
|
</td>
|
||||||
<td data-sort="${lastSort}">
|
<td data-sort="${lastSort}">
|
||||||
|
|||||||
Reference in New Issue
Block a user