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);
|
return Array.from(new Set(matches)).slice(0, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterScorelines(candidates = []) {
|
function filterScorelines(candidates = [], sourceText = '') {
|
||||||
const filtered = [];
|
const filtered = [];
|
||||||
|
const lowerSource = typeof sourceText === 'string' ? sourceText.toLowerCase() : '';
|
||||||
for (const raw of candidates) {
|
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) {
|
if (parts.length !== 2) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2118,6 +2121,17 @@ function filterScorelines(candidates = []) {
|
|||||||
if (a > 15 || b > 15) {
|
if (a > 15 || b > 15) {
|
||||||
continue;
|
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}`);
|
filtered.push(`${a}:${b}`);
|
||||||
}
|
}
|
||||||
return filtered;
|
return filtered;
|
||||||
@@ -2184,8 +2198,12 @@ function evaluateSportsScore(text, moderationSettings = null) {
|
|||||||
const hitDetails = [];
|
const hitDetails = [];
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
|
||||||
const scorelineMatchesRaw = collectRegexMatches(/\b\d{1,2}\s*:\s*\d{1,2}\b/g, normalizedText);
|
const scorelineMatchesRaw = Array.from(normalizedText.matchAll(/\b\d{1,2}\s*:\s*\d{1,2}\b/g))
|
||||||
const scorelineMatches = filterScorelines(scorelineMatchesRaw);
|
.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);
|
applyWeight(scorelineMatches.length, weights.scoreline, 'Ergebnis', scorelineMatches);
|
||||||
|
|
||||||
const scoreEmojiMatches = collectRegexMatches(/[0-9]️⃣/g, normalizedText)
|
const scoreEmojiMatches = collectRegexMatches(/[0-9]️⃣/g, normalizedText)
|
||||||
@@ -3409,6 +3427,283 @@ document.addEventListener('contextmenu', (event) => {
|
|||||||
console.log('[FB Tracker] Context menu opened on:', contextMenuTarget);
|
console.log('[FB Tracker] Context menu opened on:', contextMenuTarget);
|
||||||
}, true);
|
}, 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
|
// Listen for manual reparse command
|
||||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
if (message && message.type === 'generateSelectionAI') {
|
if (message && message.type === 'generateSelectionAI') {
|
||||||
@@ -4793,6 +5088,40 @@ async function addAICommentButton(container, postElement) {
|
|||||||
const buttonsRow = document.createElement('div');
|
const buttonsRow = document.createElement('div');
|
||||||
buttonsRow.style.cssText = 'display: flex; gap: 8px; flex-wrap: wrap;';
|
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');
|
const editButton = document.createElement('button');
|
||||||
editButton.type = 'button';
|
editButton.type = 'button';
|
||||||
editButton.textContent = 'Zusatzinfo bearbeiten';
|
editButton.textContent = 'Zusatzinfo bearbeiten';
|
||||||
|
|||||||
Reference in New Issue
Block a user