minor changes
This commit is contained in:
@@ -2101,10 +2101,13 @@ function collectRegexMatches(regex, text, limit = 20) {
|
||||
return Array.from(new Set(matches)).slice(0, limit);
|
||||
}
|
||||
|
||||
function filterScorelines(candidates = []) {
|
||||
function filterScorelines(candidates = [], sourceText = '') {
|
||||
const filtered = [];
|
||||
const lowerSource = typeof sourceText === 'string' ? sourceText.toLowerCase() : '';
|
||||
for (const raw of candidates) {
|
||||
const parts = raw.split(':').map((part) => part.trim());
|
||||
const value = typeof raw === 'string' ? raw : (raw && raw.value) || '';
|
||||
const index = typeof raw === 'string' ? -1 : (raw && typeof raw.index === 'number' ? raw.index : -1);
|
||||
const parts = value.split(':').map((part) => part.trim());
|
||||
if (parts.length !== 2) {
|
||||
continue;
|
||||
}
|
||||
@@ -2118,6 +2121,17 @@ function filterScorelines(candidates = []) {
|
||||
if (a > 15 || b > 15) {
|
||||
continue;
|
||||
}
|
||||
if (index >= 0 && lowerSource) {
|
||||
const contextStart = Math.max(0, index - 12);
|
||||
const contextEnd = Math.min(lowerSource.length, index + value.length + 8);
|
||||
const context = lowerSource.slice(contextStart, contextEnd);
|
||||
const before = lowerSource.slice(Math.max(0, index - 6), index);
|
||||
const hasTimeIndicatorBefore = /\bum\s*$/.test(before);
|
||||
const hasTimeIndicatorAfter = /\buhr/.test(context);
|
||||
if (hasTimeIndicatorBefore || hasTimeIndicatorAfter) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
filtered.push(`${a}:${b}`);
|
||||
}
|
||||
return filtered;
|
||||
@@ -2184,8 +2198,12 @@ function evaluateSportsScore(text, moderationSettings = null) {
|
||||
const hitDetails = [];
|
||||
let score = 0;
|
||||
|
||||
const scorelineMatchesRaw = collectRegexMatches(/\b\d{1,2}\s*:\s*\d{1,2}\b/g, normalizedText);
|
||||
const scorelineMatches = filterScorelines(scorelineMatchesRaw);
|
||||
const scorelineMatchesRaw = Array.from(normalizedText.matchAll(/\b\d{1,2}\s*:\s*\d{1,2}\b/g))
|
||||
.map((match) => ({
|
||||
value: match[0],
|
||||
index: typeof match.index === 'number' ? match.index : -1
|
||||
}));
|
||||
const scorelineMatches = filterScorelines(scorelineMatchesRaw, normalizedText);
|
||||
applyWeight(scorelineMatches.length, weights.scoreline, 'Ergebnis', scorelineMatches);
|
||||
|
||||
const scoreEmojiMatches = collectRegexMatches(/[0-9]️⃣/g, normalizedText)
|
||||
@@ -3409,6 +3427,283 @@ document.addEventListener('contextmenu', (event) => {
|
||||
console.log('[FB Tracker] Context menu opened on:', contextMenuTarget);
|
||||
}, true);
|
||||
|
||||
// Floating AI button on text selection
|
||||
let selectionAIContainer = null;
|
||||
let selectionAIButton = null;
|
||||
let selectionAINoteButton = null;
|
||||
let selectionAIRaf = null;
|
||||
let selectionAIHideTimeout = null;
|
||||
let selectionAIEnabledCached = null;
|
||||
|
||||
const clearSelectionAIHideTimeout = () => {
|
||||
if (selectionAIHideTimeout) {
|
||||
clearTimeout(selectionAIHideTimeout);
|
||||
selectionAIHideTimeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
const hideSelectionAIButton = () => {
|
||||
clearSelectionAIHideTimeout();
|
||||
if (selectionAIContainer) {
|
||||
selectionAIContainer.style.display = 'none';
|
||||
}
|
||||
if (selectionAIButton) {
|
||||
selectionAIButton.dataset.selectionText = '';
|
||||
}
|
||||
if (selectionAINoteButton) {
|
||||
selectionAINoteButton.dataset.selectionText = '';
|
||||
}
|
||||
};
|
||||
|
||||
const ensureSelectionAIButton = () => {
|
||||
if (selectionAIContainer && selectionAIContainer.isConnected && selectionAIButton && selectionAINoteButton) {
|
||||
return selectionAIContainer;
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.style.cssText = `
|
||||
position: fixed;
|
||||
z-index: 2147483647;
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
pointer-events: auto;
|
||||
`;
|
||||
|
||||
const noteButton = document.createElement('button');
|
||||
noteButton.type = 'button';
|
||||
noteButton.textContent = '➕ Zusatzinfo';
|
||||
noteButton.title = 'Aktuelle Auswahl als Zusatzinfo speichern';
|
||||
noteButton.style.cssText = `
|
||||
padding: 7px 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: #fff;
|
||||
color: #111827;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
cursor: pointer;
|
||||
transition: transform 120ms ease, box-shadow 120ms ease, opacity 120ms ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
`;
|
||||
noteButton.addEventListener('mouseenter', () => {
|
||||
noteButton.style.transform = 'translateY(-1px)';
|
||||
noteButton.style.boxShadow = '0 8px 18px rgba(0, 0, 0, 0.18)';
|
||||
});
|
||||
noteButton.addEventListener('mouseleave', () => {
|
||||
noteButton.style.transform = 'translateY(0)';
|
||||
noteButton.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.12)';
|
||||
});
|
||||
noteButton.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const selectedText = noteButton.dataset.selectionText || '';
|
||||
if (!selectedText.trim()) {
|
||||
showToast('Keine Textauswahl gefunden', 'error');
|
||||
return;
|
||||
}
|
||||
const selection = window.getSelection();
|
||||
const anchorNode = selection ? (selection.anchorNode || selection.focusNode) : null;
|
||||
if (anchorNode && isSelectionInsideEditable(anchorNode)) {
|
||||
showToast('Textauswahl aus Eingabefeldern kann nicht genutzt werden', 'error');
|
||||
return;
|
||||
}
|
||||
const anchorElement = anchorNode && anchorNode.nodeType === Node.TEXT_NODE
|
||||
? anchorNode.parentElement
|
||||
: anchorNode;
|
||||
const postContext = anchorElement ? ensurePrimaryPostElement(anchorElement) : null;
|
||||
if (!postContext) {
|
||||
showToast('Keinen zugehörigen Beitrag gefunden', 'error');
|
||||
return;
|
||||
}
|
||||
const normalized = normalizeSelectionText(selectedText);
|
||||
if (!normalized) {
|
||||
showToast('Keine Textauswahl gefunden', 'error');
|
||||
return;
|
||||
}
|
||||
postAdditionalNotes.set(postContext, normalized);
|
||||
showToast('Auswahl als Zusatzinfo gesetzt', 'success');
|
||||
});
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.textContent = '✨ AI';
|
||||
button.title = 'Auswahl mit AI beantworten';
|
||||
button.style.cssText = `
|
||||
padding: 8px 12px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
border: none;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.22);
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: transform 120ms ease, box-shadow 120ms ease, opacity 120ms ease;
|
||||
`;
|
||||
|
||||
button.addEventListener('mouseenter', () => {
|
||||
button.style.transform = 'translateY(-1px) scale(1.02)';
|
||||
button.style.boxShadow = '0 10px 22px rgba(0, 0, 0, 0.26)';
|
||||
});
|
||||
button.addEventListener('mouseleave', () => {
|
||||
button.style.transform = 'translateY(0)';
|
||||
button.style.boxShadow = '0 8px 20px rgba(0, 0, 0, 0.22)';
|
||||
});
|
||||
|
||||
button.addEventListener('click', async (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const selectedText = button.dataset.selectionText || '';
|
||||
hideSelectionAIButton();
|
||||
if (!selectedText.trim()) {
|
||||
return;
|
||||
}
|
||||
const originalLabel = button.textContent;
|
||||
button.textContent = '⏳ AI läuft...';
|
||||
try {
|
||||
await handleSelectionAIRequest(selectedText, () => {});
|
||||
} finally {
|
||||
button.textContent = originalLabel;
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(noteButton);
|
||||
container.appendChild(button);
|
||||
document.body.appendChild(container);
|
||||
selectionAIContainer = container;
|
||||
selectionAIButton = button;
|
||||
selectionAINoteButton = noteButton;
|
||||
selectionAIButton = button;
|
||||
return container;
|
||||
};
|
||||
|
||||
const isSelectionInsideEditable = (node) => {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const el = node;
|
||||
if (el.closest('input, textarea, [contenteditable="true"]')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (node.parentElement && node.parentElement.closest('input, textarea, [contenteditable="true"]')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const positionSelectionAIButton = (rect) => {
|
||||
if (!selectionAIContainer || !rect) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewportPadding = 8;
|
||||
const containerWidth = selectionAIContainer.offsetWidth || 160;
|
||||
let left = rect.right + 8;
|
||||
let top = rect.top - (selectionAIContainer.offsetHeight || 40) - 8;
|
||||
|
||||
if (left + containerWidth + viewportPadding > window.innerWidth) {
|
||||
left = Math.max(viewportPadding, rect.right - containerWidth - 8);
|
||||
}
|
||||
|
||||
if (top < viewportPadding) {
|
||||
top = rect.bottom + 8;
|
||||
}
|
||||
|
||||
selectionAIContainer.style.left = `${Math.max(viewportPadding, left)}px`;
|
||||
selectionAIContainer.style.top = `${Math.max(viewportPadding, top)}px`;
|
||||
};
|
||||
|
||||
const updateSelectionAIButton = async () => {
|
||||
clearSelectionAIHideTimeout();
|
||||
|
||||
if (selectionAIEnabledCached === null) {
|
||||
try {
|
||||
selectionAIEnabledCached = await isAIEnabled();
|
||||
} catch (error) {
|
||||
console.warn('[FB Tracker] AI enable check failed for selection button:', error);
|
||||
selectionAIEnabledCached = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectionAIEnabledCached) {
|
||||
hideSelectionAIButton();
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = window.getSelection();
|
||||
if (!selection || selection.isCollapsed) {
|
||||
hideSelectionAIButton();
|
||||
return;
|
||||
}
|
||||
|
||||
const selectionText = (selection.toString() || '').trim();
|
||||
if (!selectionText || selectionText.length > MAX_SELECTION_LENGTH) {
|
||||
hideSelectionAIButton();
|
||||
return;
|
||||
}
|
||||
|
||||
const anchorNode = selection.anchorNode || selection.focusNode;
|
||||
if (isSelectionInsideEditable(anchorNode)) {
|
||||
hideSelectionAIButton();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selection.rangeCount) {
|
||||
hideSelectionAIButton();
|
||||
return;
|
||||
}
|
||||
|
||||
const range = selection.getRangeAt(0);
|
||||
const rect = range.getBoundingClientRect();
|
||||
if (!rect || (rect.width === 0 && rect.height === 0)) {
|
||||
hideSelectionAIButton();
|
||||
return;
|
||||
}
|
||||
|
||||
const container = ensureSelectionAIButton();
|
||||
if (!selectionAIButton || !selectionAINoteButton) {
|
||||
hideSelectionAIButton();
|
||||
return;
|
||||
}
|
||||
selectionAIButton.dataset.selectionText = selectionText;
|
||||
selectionAINoteButton.dataset.selectionText = selectionText;
|
||||
container.style.display = 'inline-flex';
|
||||
positionSelectionAIButton(rect);
|
||||
|
||||
selectionAIHideTimeout = setTimeout(() => {
|
||||
hideSelectionAIButton();
|
||||
}, 8000);
|
||||
};
|
||||
|
||||
const scheduleSelectionAIUpdate = () => {
|
||||
if (selectionAIRaf) {
|
||||
return;
|
||||
}
|
||||
selectionAIRaf = requestAnimationFrame(() => {
|
||||
selectionAIRaf = null;
|
||||
updateSelectionAIButton();
|
||||
});
|
||||
};
|
||||
|
||||
const initSelectionAIFloatingButton = () => {
|
||||
document.addEventListener('selectionchange', scheduleSelectionAIUpdate, true);
|
||||
document.addEventListener('mouseup', scheduleSelectionAIUpdate, true);
|
||||
document.addEventListener('keyup', scheduleSelectionAIUpdate, true);
|
||||
window.addEventListener('scroll', hideSelectionAIButton, true);
|
||||
window.addEventListener('blur', hideSelectionAIButton, true);
|
||||
};
|
||||
|
||||
initSelectionAIFloatingButton();
|
||||
|
||||
// Listen for manual reparse command
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message && message.type === 'generateSelectionAI') {
|
||||
@@ -4793,6 +5088,40 @@ async function addAICommentButton(container, postElement) {
|
||||
const buttonsRow = document.createElement('div');
|
||||
buttonsRow.style.cssText = 'display: flex; gap: 8px; flex-wrap: wrap;';
|
||||
|
||||
const selectionButton = document.createElement('button');
|
||||
selectionButton.type = 'button';
|
||||
selectionButton.textContent = 'Auswahl als Zusatzinfo';
|
||||
selectionButton.style.cssText = 'padding: 6px 10px; border-radius: 6px; border: 1px solid #ccd0d5; background: #fff; cursor: pointer; font-size: 12px; font-weight: 600; color: #1d2129;';
|
||||
selectionButton.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const selection = window.getSelection();
|
||||
const anchorNode = selection ? (selection.anchorNode || selection.focusNode) : null;
|
||||
if (anchorNode && isSelectionInsideEditable(anchorNode)) {
|
||||
showToast('Textauswahl aus Eingabefeldern kann nicht genutzt werden', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const context = resolvePostContext();
|
||||
let selectedText = context ? getSelectedTextFromPost(context) : '';
|
||||
if (!selectedText && selection) {
|
||||
selectedText = normalizeSelectionText(selection.toString());
|
||||
}
|
||||
if (!selectedText && lastGlobalSelection.text && Date.now() - lastGlobalSelection.timestamp < LAST_SELECTION_MAX_AGE) {
|
||||
selectedText = normalizeSelectionText(lastGlobalSelection.text);
|
||||
}
|
||||
|
||||
if (!selectedText) {
|
||||
showToast('Keine Textauswahl gefunden', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setAdditionalNote(selectedText);
|
||||
showToast('Auswahl als Zusatzinfo gesetzt', 'success');
|
||||
});
|
||||
buttonsRow.appendChild(selectionButton);
|
||||
|
||||
const editButton = document.createElement('button');
|
||||
editButton.type = 'button';
|
||||
editButton.textContent = 'Zusatzinfo bearbeiten';
|
||||
|
||||
Reference in New Issue
Block a user