Aktueller Stand
This commit is contained in:
@@ -457,8 +457,11 @@ async function fetchBackendProfileNumber() {
|
||||
return null;
|
||||
}
|
||||
const data = await response.json();
|
||||
if (data && data.profile_number) {
|
||||
return data.profile_number;
|
||||
if (data && typeof data.profile_number !== 'undefined') {
|
||||
const parsed = parseInt(data.profile_number, 10);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[FB Tracker] Failed to fetch profile state from backend:', error);
|
||||
@@ -499,25 +502,18 @@ function extractAuthorName(postElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function storeProfileNumberLocally(profileNumber) {
|
||||
chrome.storage.sync.set({ profileNumber });
|
||||
}
|
||||
|
||||
async function getProfileNumber() {
|
||||
const backendProfile = await fetchBackendProfileNumber();
|
||||
if (backendProfile) {
|
||||
storeProfileNumberLocally(backendProfile);
|
||||
console.log('[FB Tracker] Profile number (backend):', backendProfile);
|
||||
return backendProfile;
|
||||
try {
|
||||
const backendProfile = await fetchBackendProfileNumber();
|
||||
if (backendProfile) {
|
||||
console.log('[FB Tracker] Profile number (backend):', backendProfile);
|
||||
return backendProfile;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[FB Tracker] Failed to resolve profile number from backend:', error);
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.sync.get(['profileNumber'], (result) => {
|
||||
const profile = result.profileNumber || 1;
|
||||
console.log('[FB Tracker] Profile number (local):', profile);
|
||||
resolve(profile);
|
||||
});
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract post URL from post element
|
||||
@@ -2233,14 +2229,19 @@ function extractDeadlineFromPostText(postElement) {
|
||||
}
|
||||
|
||||
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();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
const monthNames = {
|
||||
'januar': 1, 'jan': 1,
|
||||
'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,
|
||||
'mai': 5,
|
||||
'juni': 6, 'jun': 6,
|
||||
@@ -2265,7 +2266,7 @@ function extractDeadlineFromPostText(postElement) {
|
||||
if (/^\s*(?:-|–|—|bis)\s*\d{1,2}\.\d{1,2}\./i.test(tail)) {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@@ -2291,24 +2292,98 @@ function extractDeadlineFromPostText(postElement) {
|
||||
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 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(normalizedText);
|
||||
if (rangeMatch) {
|
||||
const endDay = parseInt(rangeMatch[4], 10);
|
||||
const endMonth = parseInt(rangeMatch[5], 10);
|
||||
let endYear = parseInt(rangeMatch[6], 10);
|
||||
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);
|
||||
if (!rangeMatch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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) {
|
||||
endYear += 2000;
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
let match;
|
||||
while ((match = pattern.exec(normalizedText)) !== null) {
|
||||
@@ -2332,27 +2407,25 @@ function extractDeadlineFromPostText(postElement) {
|
||||
const hasTime = Boolean(timeInfo);
|
||||
if (timeInfo) {
|
||||
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);
|
||||
}
|
||||
|
||||
const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex);
|
||||
const recordHasTime = hasTime || hasInclusiveTime;
|
||||
const hasDeadlineTime = hasDeadlineKeywordNear(normalizedText, matchIndex);
|
||||
const recordHasTime = hasTime || hasInclusiveTime || hasDeadlineTime;
|
||||
if (hasInclusiveTime && !hasTime) {
|
||||
date.setHours(23, 59, 0, 0);
|
||||
}
|
||||
|
||||
// Only add if date is in the future
|
||||
if (date > today) {
|
||||
foundDates.push({ date, hasTime: recordHasTime });
|
||||
}
|
||||
addCandidateDate(date, recordHasTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
while ((monthMatch = monthPattern.exec(normalizedText)) !== null) {
|
||||
const day = parseInt(monthMatch[1], 10);
|
||||
@@ -2376,12 +2449,13 @@ function extractDeadlineFromPostText(postElement) {
|
||||
const hasTime = Boolean(timeInfo);
|
||||
if (timeInfo) {
|
||||
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);
|
||||
}
|
||||
|
||||
const hasInclusiveTime = hasInclusiveKeywordNear(normalizedText, matchIndex);
|
||||
const recordHasTime = hasTime || hasInclusiveTime;
|
||||
const hasDeadlineTime = hasDeadlineKeywordNear(normalizedText, matchIndex);
|
||||
const recordHasTime = hasTime || hasInclusiveTime || hasDeadlineTime;
|
||||
if (hasInclusiveTime && !hasTime) {
|
||||
date.setHours(23, 59, 0, 0);
|
||||
}
|
||||
@@ -2390,23 +2464,17 @@ function extractDeadlineFromPostText(postElement) {
|
||||
if (date <= today) {
|
||||
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) {
|
||||
foundDates.sort((a, b) => {
|
||||
const diff = a.date - b.date;
|
||||
if (diff !== 0) {
|
||||
return diff;
|
||||
}
|
||||
if (a.hasTime && !b.hasTime) return -1;
|
||||
if (!a.hasTime && b.hasTime) return 1;
|
||||
return 0;
|
||||
});
|
||||
return toDateTimeLocalString(foundDates[0].date);
|
||||
const withTime = foundDates.filter((entry) => entry.hasTime);
|
||||
const candidates = withTime.length ? withTime : foundDates;
|
||||
candidates.sort((a, b) => a.date - b.date);
|
||||
return toDateTimeLocalString(candidates[0].date);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -2735,6 +2803,9 @@ async function renderTrackedStatus({
|
||||
return { hidden: false };
|
||||
}
|
||||
|
||||
const parsedProfile = parseInt(profileNumber, 10);
|
||||
const effectiveProfileNumber = Number.isNaN(parsedProfile) ? null : parsedProfile;
|
||||
|
||||
if (postData.id) {
|
||||
container.dataset.postId = postData.id;
|
||||
}
|
||||
@@ -2760,9 +2831,12 @@ async function renderTrackedStatus({
|
||||
})
|
||||
.filter(Boolean)
|
||||
: Array.from({ length: Math.max(1, Math.min(5, parseInt(postData.target_count, 10) || 1)) }, (_, index) => index + 1);
|
||||
const isCurrentProfileRequired = requiredProfiles.includes(profileNumber);
|
||||
const canCurrentProfileCheck = isCurrentProfileRequired && postData.next_required_profile === profileNumber;
|
||||
const isCurrentProfileDone = checks.some(check => check.profile_number === profileNumber);
|
||||
const isCurrentProfileRequired = effectiveProfileNumber !== null && requiredProfiles.includes(effectiveProfileNumber);
|
||||
const parsedNextRequired = parseInt(postData.next_required_profile, 10);
|
||||
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 (isDialogContext) {
|
||||
@@ -2815,6 +2889,23 @@ async function renderTrackedStatus({
|
||||
✓ Bestätigen
|
||||
</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) {
|
||||
statusHtml += `
|
||||
<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');
|
||||
if (checkBtn) {
|
||||
checkBtn.addEventListener('click', async () => {
|
||||
if (effectiveProfileNumber === null) {
|
||||
showToast('Profilstatus nicht geladen. Bitte Tracker neu laden.', 'error');
|
||||
return;
|
||||
}
|
||||
checkBtn.disabled = true;
|
||||
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) {
|
||||
await renderTrackedStatus({
|
||||
container,
|
||||
postElement,
|
||||
postData: result,
|
||||
profileNumber,
|
||||
profileNumber: effectiveProfileNumber,
|
||||
isFeedHome,
|
||||
isDialogContext,
|
||||
manualHideInfo,
|
||||
@@ -3331,6 +3426,13 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
|
||||
const targetCount = parseInt(selectElement.value, 10);
|
||||
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.textContent = 'Wird hinzugefügt...';
|
||||
|
||||
@@ -5277,6 +5379,11 @@ async function handleSelectionAIRequest(selectionText, sendResponse) {
|
||||
showToast('AI verarbeitet Auswahl...', 'info');
|
||||
|
||||
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, {});
|
||||
|
||||
if (!comment) {
|
||||
@@ -5597,6 +5704,10 @@ async function addAICommentButton(container, postElement) {
|
||||
}
|
||||
|
||||
const effectiveProfile = profileNumber || await getProfileNumber();
|
||||
if (!effectiveProfile) {
|
||||
showToast('Profilstatus nicht geladen. Bitte Tracker neu laden.', 'error');
|
||||
return;
|
||||
}
|
||||
const decodedUrl = getDecodedPostUrl();
|
||||
const isFeedHomeFlag = container.dataset.isFeedHome === '1';
|
||||
const isDialogFlag = container.dataset.isDialogContext === '1';
|
||||
@@ -6079,22 +6190,26 @@ async function addAICommentButton(container, postElement) {
|
||||
throw new Error('Konnte Post-Text nicht extrahieren');
|
||||
}
|
||||
|
||||
selectionKeys.forEach((key) => {
|
||||
if (key) {
|
||||
postSelectionCache.delete(key);
|
||||
}
|
||||
});
|
||||
selectionKeys.forEach((key) => {
|
||||
if (key) {
|
||||
postSelectionCache.delete(key);
|
||||
}
|
||||
});
|
||||
|
||||
const additionalNote = postContext ? (postAdditionalNotes.get(postContext) || '') : '';
|
||||
if (additionalNote) {
|
||||
postText = `${postText}\n\nZusatzinfo:\n${additionalNote}`;
|
||||
}
|
||||
const additionalNote = postContext ? (postAdditionalNotes.get(postContext) || '') : '';
|
||||
if (additionalNote) {
|
||||
postText = `${postText}\n\nZusatzinfo:\n${additionalNote}`;
|
||||
}
|
||||
|
||||
throwIfCancelled();
|
||||
throwIfCancelled();
|
||||
|
||||
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();
|
||||
if (!profileNumber) {
|
||||
showToast('Profilstatus nicht geladen. Bitte Tracker neu laden.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
throwIfCancelled();
|
||||
|
||||
|
||||
86
web/app.js
86
web/app.js
@@ -1287,13 +1287,97 @@ function buildBookmarkSearchUrl(query) {
|
||||
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) {
|
||||
const trimmed = typeof baseQuery === 'string' ? baseQuery.trim() : '';
|
||||
if (!trimmed) {
|
||||
return [...BOOKMARK_SUFFIXES];
|
||||
}
|
||||
|
||||
return BOOKMARK_SUFFIXES.map((suffix) => `${trimmed} ${suffix}`.trim());
|
||||
const baseVariants = expandBookmarkQueryVariants(trimmed);
|
||||
if (!baseVariants.length) {
|
||||
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) {
|
||||
|
||||
@@ -283,24 +283,23 @@
|
||||
.automation-view .auto-table .sort-indicator {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 5px solid transparent;
|
||||
border-bottom: 0;
|
||||
border-left: 0;
|
||||
transform: rotate(45deg);
|
||||
opacity: 0.25;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: var(--automation-muted);
|
||||
-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;
|
||||
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;
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.automation-view .auto-table th.sort-asc .sort-indicator {
|
||||
border-top: 6px solid var(--automation-accent-2);
|
||||
transform: rotate(225deg);
|
||||
background-color: var(--automation-accent-2);
|
||||
transform: rotate(180deg);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.automation-view .auto-table th.sort-desc .sort-indicator {
|
||||
border-top: 6px solid var(--automation-accent-2);
|
||||
transform: rotate(45deg);
|
||||
background-color: var(--automation-accent-2);
|
||||
transform: rotate(0deg);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
|
||||
@@ -637,7 +637,7 @@
|
||||
<br><small>${formatDateTime(req.next_run_at)}</small>
|
||||
</td>
|
||||
<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>
|
||||
</td>
|
||||
<td data-sort="${lastSort}">
|
||||
|
||||
Reference in New Issue
Block a user