|
|
|
@@ -18,6 +18,7 @@ const sessionSearchRecordedUrls = new Set();
|
|
|
|
const sessionSearchInfoCache = new Map();
|
|
|
|
const sessionSearchInfoCache = new Map();
|
|
|
|
|
|
|
|
|
|
|
|
const trackerElementsByPost = new WeakMap();
|
|
|
|
const trackerElementsByPost = new WeakMap();
|
|
|
|
|
|
|
|
const postAdditionalNotes = new WeakMap();
|
|
|
|
|
|
|
|
|
|
|
|
const REELS_PATH_PREFIX = '/reel/';
|
|
|
|
const REELS_PATH_PREFIX = '/reel/';
|
|
|
|
|
|
|
|
|
|
|
|
@@ -2614,6 +2615,11 @@ document.addEventListener('contextmenu', (event) => {
|
|
|
|
|
|
|
|
|
|
|
|
// 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') {
|
|
|
|
|
|
|
|
handleSelectionAIRequest(message.selectionText || '', sendResponse);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (message && message.type === 'reparsePost') {
|
|
|
|
if (message && message.type === 'reparsePost') {
|
|
|
|
console.log('[FB Tracker] Manual reparse triggered');
|
|
|
|
console.log('[FB Tracker] Manual reparse triggered');
|
|
|
|
|
|
|
|
|
|
|
|
@@ -2752,6 +2758,38 @@ function showToast(message, type = 'info') {
|
|
|
|
}, 3000);
|
|
|
|
}, 3000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function copyTextToClipboard(text) {
|
|
|
|
|
|
|
|
if (typeof text !== 'string') {
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
await navigator.clipboard.writeText(text);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.warn('[FB Tracker] navigator.clipboard.writeText failed:', error);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const textarea = document.createElement('textarea');
|
|
|
|
|
|
|
|
textarea.value = text;
|
|
|
|
|
|
|
|
textarea.style.position = 'fixed';
|
|
|
|
|
|
|
|
textarea.style.top = '-999px';
|
|
|
|
|
|
|
|
textarea.style.left = '-999px';
|
|
|
|
|
|
|
|
document.body.appendChild(textarea);
|
|
|
|
|
|
|
|
textarea.focus();
|
|
|
|
|
|
|
|
textarea.select();
|
|
|
|
|
|
|
|
const success = document.execCommand('copy');
|
|
|
|
|
|
|
|
document.body.removeChild(textarea);
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.warn('[FB Tracker] execCommand copy fallback failed:', error);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Extract post text from a Facebook post element
|
|
|
|
* Extract post text from a Facebook post element
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
@@ -3328,6 +3366,38 @@ async function generateAIComment(postText, profileNumber, options = {}) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function handleSelectionAIRequest(selectionText, sendResponse) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const normalizedSelection = normalizeSelectionText(selectionText);
|
|
|
|
|
|
|
|
if (!normalizedSelection) {
|
|
|
|
|
|
|
|
showToast('Keine gültige Auswahl gefunden', 'error');
|
|
|
|
|
|
|
|
sendResponse({ success: false, error: 'Keine gültige Auswahl gefunden' });
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
showToast('AI verarbeitet Auswahl...', 'info');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const profileNumber = await getProfileNumber();
|
|
|
|
|
|
|
|
const comment = await generateAIComment(normalizedSelection, profileNumber, {});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!comment) {
|
|
|
|
|
|
|
|
throw new Error('Keine Antwort vom AI-Dienst erhalten');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const copied = await copyTextToClipboard(comment);
|
|
|
|
|
|
|
|
if (!copied) {
|
|
|
|
|
|
|
|
throw new Error('Antwort konnte nicht in die Zwischenablage kopiert werden');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
showToast('AI-Antwort in die Zwischenablage kopiert', 'success');
|
|
|
|
|
|
|
|
sendResponse({ success: true, comment });
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('[FB Tracker] Selection AI error:', error);
|
|
|
|
|
|
|
|
showToast(`❌ ${error.message || 'Fehler bei AI-Anfrage'}`, 'error');
|
|
|
|
|
|
|
|
sendResponse({ success: false, error: error.message || 'Unbekannter Fehler' });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Check if AI is enabled
|
|
|
|
* Check if AI is enabled
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
@@ -3419,9 +3489,6 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
dropdown.className = 'fb-tracker-ai-dropdown';
|
|
|
|
dropdown.className = 'fb-tracker-ai-dropdown';
|
|
|
|
dropdown.style.cssText = `
|
|
|
|
dropdown.style.cssText = `
|
|
|
|
display: none;
|
|
|
|
display: none;
|
|
|
|
position: absolute;
|
|
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
|
|
top: calc(100% + 6px);
|
|
|
|
|
|
|
|
min-width: 220px;
|
|
|
|
min-width: 220px;
|
|
|
|
background: #ffffff;
|
|
|
|
background: #ffffff;
|
|
|
|
border-radius: 8px;
|
|
|
|
border-radius: 8px;
|
|
|
|
@@ -3435,6 +3502,75 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
wrapper.appendChild(dropdown);
|
|
|
|
wrapper.appendChild(dropdown);
|
|
|
|
container.appendChild(wrapper);
|
|
|
|
container.appendChild(wrapper);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const baseButtonText = button.textContent;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const resolvePostContexts = () => {
|
|
|
|
|
|
|
|
const contextCandidate = container.closest('div[aria-posinset], article[role="article"], article, div[role="complementary"]');
|
|
|
|
|
|
|
|
const normalizedContext = contextCandidate ? ensurePrimaryPostElement(contextCandidate) : null;
|
|
|
|
|
|
|
|
const fallbackContext = postElement ? ensurePrimaryPostElement(postElement) : null;
|
|
|
|
|
|
|
|
const postContext = normalizedContext || fallbackContext || contextCandidate || postElement || container;
|
|
|
|
|
|
|
|
return { postContext, contextCandidate, fallbackContext, normalizedContext };
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const resolvePostContext = () => resolvePostContexts().postContext;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getAdditionalNote = () => {
|
|
|
|
|
|
|
|
const context = resolvePostContext();
|
|
|
|
|
|
|
|
return context ? (postAdditionalNotes.get(context) || '') : '';
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let notePreviewElement = null;
|
|
|
|
|
|
|
|
let noteClearButton = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const truncateNoteForPreview = (note) => {
|
|
|
|
|
|
|
|
if (!note) {
|
|
|
|
|
|
|
|
return '';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return note.length > 120 ? `${note.slice(0, 117)}…` : note;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const updateNoteIndicator = () => {
|
|
|
|
|
|
|
|
const note = getAdditionalNote();
|
|
|
|
|
|
|
|
const hasNote = note.trim().length > 0;
|
|
|
|
|
|
|
|
button.dataset.aiOriginalText = hasNote ? `${baseButtonText} ✎` : baseButtonText;
|
|
|
|
|
|
|
|
if ((button.dataset.aiState || 'idle') === 'idle' && !button._aiContext) {
|
|
|
|
|
|
|
|
button.textContent = button.dataset.aiOriginalText;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
button.title = hasNote
|
|
|
|
|
|
|
|
? 'Generiere automatisch einen passenden Kommentar (mit Zusatzinfo)'
|
|
|
|
|
|
|
|
: 'Generiere automatisch einen passenden Kommentar';
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const updateNotePreview = () => {
|
|
|
|
|
|
|
|
updateNoteIndicator();
|
|
|
|
|
|
|
|
if (notePreviewElement) {
|
|
|
|
|
|
|
|
const note = getAdditionalNote();
|
|
|
|
|
|
|
|
notePreviewElement.textContent = note
|
|
|
|
|
|
|
|
? `Aktuelle Zusatzinfo: ${truncateNoteForPreview(note)}`
|
|
|
|
|
|
|
|
: 'Keine Zusatzinfo gesetzt';
|
|
|
|
|
|
|
|
if (noteClearButton) {
|
|
|
|
|
|
|
|
const hasNote = note.trim().length > 0;
|
|
|
|
|
|
|
|
noteClearButton.disabled = !hasNote;
|
|
|
|
|
|
|
|
noteClearButton.style.opacity = hasNote ? '1' : '0.6';
|
|
|
|
|
|
|
|
noteClearButton.style.cursor = hasNote ? 'pointer' : 'default';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const setAdditionalNote = (value) => {
|
|
|
|
|
|
|
|
const context = resolvePostContext();
|
|
|
|
|
|
|
|
if (!context) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const trimmed = (value || '').trim();
|
|
|
|
|
|
|
|
if (trimmed) {
|
|
|
|
|
|
|
|
postAdditionalNotes.set(context, trimmed);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
postAdditionalNotes.delete(context);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
updateNotePreview();
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
button.addEventListener('mouseenter', () => {
|
|
|
|
button.addEventListener('mouseenter', () => {
|
|
|
|
if ((button.dataset.aiState || 'idle') === 'idle') {
|
|
|
|
if ((button.dataset.aiState || 'idle') === 'idle') {
|
|
|
|
button.style.transform = 'translateY(-2px)';
|
|
|
|
button.style.transform = 'translateY(-2px)';
|
|
|
|
@@ -3470,10 +3606,8 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
button.addEventListener('pointerdown', () => {
|
|
|
|
button.addEventListener('pointerdown', () => {
|
|
|
|
const contextElement = container.closest('div[aria-posinset], article[role="article"], article, div[role="complementary"]');
|
|
|
|
const context = resolvePostContext();
|
|
|
|
const normalized = contextElement ? ensurePrimaryPostElement(contextElement) : null;
|
|
|
|
const target = context || postElement || container;
|
|
|
|
const fallbackNormalized = postElement ? ensurePrimaryPostElement(postElement) : null;
|
|
|
|
|
|
|
|
const target = normalized || fallbackNormalized || contextElement || postElement || container;
|
|
|
|
|
|
|
|
cacheSelectionForPost(target);
|
|
|
|
cacheSelectionForPost(target);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
@@ -3481,6 +3615,32 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
button.dataset.aiOriginalText = button.textContent;
|
|
|
|
button.dataset.aiOriginalText = button.textContent;
|
|
|
|
|
|
|
|
|
|
|
|
let dropdownOpen = false;
|
|
|
|
let dropdownOpen = false;
|
|
|
|
|
|
|
|
let dropdownPortalParent = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const resolveDropdownPortalParent = () => {
|
|
|
|
|
|
|
|
if (dropdownPortalParent && dropdownPortalParent.isConnected) {
|
|
|
|
|
|
|
|
return dropdownPortalParent;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const candidate = document.body || document.documentElement;
|
|
|
|
|
|
|
|
dropdownPortalParent = candidate;
|
|
|
|
|
|
|
|
return dropdownPortalParent;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const mountDropdownInPortal = () => {
|
|
|
|
|
|
|
|
const portalParent = resolveDropdownPortalParent();
|
|
|
|
|
|
|
|
if (!portalParent) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dropdown.parentElement !== portalParent) {
|
|
|
|
|
|
|
|
portalParent.appendChild(dropdown);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const restoreDropdownToWrapper = () => {
|
|
|
|
|
|
|
|
if (dropdown.parentElement !== wrapper) {
|
|
|
|
|
|
|
|
wrapper.appendChild(dropdown);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const closeDropdown = () => {
|
|
|
|
const closeDropdown = () => {
|
|
|
|
if (!dropdownOpen) {
|
|
|
|
if (!dropdownOpen) {
|
|
|
|
@@ -3497,10 +3657,18 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
dropdownButton.style.transform = 'translateY(0)';
|
|
|
|
dropdownButton.style.transform = 'translateY(0)';
|
|
|
|
button.style.boxShadow = 'none';
|
|
|
|
button.style.boxShadow = 'none';
|
|
|
|
dropdownButton.style.boxShadow = 'none';
|
|
|
|
dropdownButton.style.boxShadow = 'none';
|
|
|
|
|
|
|
|
dropdown.style.position = '';
|
|
|
|
|
|
|
|
dropdown.style.top = '';
|
|
|
|
|
|
|
|
dropdown.style.left = '';
|
|
|
|
|
|
|
|
dropdown.style.maxHeight = '';
|
|
|
|
|
|
|
|
dropdown.style.overflowY = '';
|
|
|
|
|
|
|
|
restoreDropdownToWrapper();
|
|
|
|
|
|
|
|
window.removeEventListener('scroll', repositionDropdown, true);
|
|
|
|
|
|
|
|
window.removeEventListener('resize', repositionDropdown);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleOutsideClick = (event) => {
|
|
|
|
const handleOutsideClick = (event) => {
|
|
|
|
if (!wrapper.contains(event.target)) {
|
|
|
|
if (!wrapper.contains(event.target) && !dropdown.contains(event.target)) {
|
|
|
|
closeDropdown();
|
|
|
|
closeDropdown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
@@ -3518,17 +3686,76 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
loading.style.cssText = 'padding: 8px 14px; font-size: 13px; color: #555;';
|
|
|
|
loading.style.cssText = 'padding: 8px 14px; font-size: 13px; color: #555;';
|
|
|
|
dropdown.appendChild(loading);
|
|
|
|
dropdown.appendChild(loading);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const appendNoteUI = () => {
|
|
|
|
|
|
|
|
noteClearButton = null;
|
|
|
|
|
|
|
|
const noteSection = document.createElement('div');
|
|
|
|
|
|
|
|
noteSection.style.cssText = 'padding: 10px 14px; display: flex; flex-direction: column; gap: 6px;';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
notePreviewElement = document.createElement('div');
|
|
|
|
|
|
|
|
notePreviewElement.style.cssText = 'font-size: 12px; color: #65676b; white-space: normal;';
|
|
|
|
|
|
|
|
noteSection.appendChild(notePreviewElement);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const buttonsRow = document.createElement('div');
|
|
|
|
|
|
|
|
buttonsRow.style.cssText = 'display: flex; gap: 8px; flex-wrap: wrap;';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const editButton = document.createElement('button');
|
|
|
|
|
|
|
|
editButton.type = 'button';
|
|
|
|
|
|
|
|
editButton.textContent = 'Zusatzinfo bearbeiten';
|
|
|
|
|
|
|
|
editButton.style.cssText = 'padding: 6px 10px; border-radius: 6px; border: 1px solid #ccd0d5; background: #fff; cursor: pointer; font-size: 12px; font-weight: 600; color: #1d2129;';
|
|
|
|
|
|
|
|
editButton.addEventListener('click', (event) => {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
|
|
const existingNote = getAdditionalNote();
|
|
|
|
|
|
|
|
const input = window.prompt('Zusatzinfo für den AI-Prompt eingeben:', existingNote);
|
|
|
|
|
|
|
|
if (input === null) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const trimmed = (input || '').trim();
|
|
|
|
|
|
|
|
setAdditionalNote(trimmed);
|
|
|
|
|
|
|
|
if (trimmed) {
|
|
|
|
|
|
|
|
showToast('Zusatzinfo gespeichert', 'success');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
showToast('Zusatzinfo entfernt', 'success');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
buttonsRow.appendChild(editButton);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const clearButton = document.createElement('button');
|
|
|
|
|
|
|
|
clearButton.type = 'button';
|
|
|
|
|
|
|
|
clearButton.textContent = 'Zurücksetzen';
|
|
|
|
|
|
|
|
clearButton.style.cssText = 'padding: 6px 10px; border-radius: 6px; border: 1px solid #ccd0d5; background: #fff; cursor: pointer; font-size: 12px; font-weight: 600; color: #a11900;';
|
|
|
|
|
|
|
|
clearButton.addEventListener('click', (event) => {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
|
|
if (!getAdditionalNote()) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
setAdditionalNote('');
|
|
|
|
|
|
|
|
showToast('Zusatzinfo entfernt', 'success');
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
buttonsRow.appendChild(clearButton);
|
|
|
|
|
|
|
|
noteClearButton = clearButton;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
noteSection.appendChild(buttonsRow);
|
|
|
|
|
|
|
|
dropdown.appendChild(noteSection);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateNotePreview();
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const credentials = await fetchActiveAICredentials();
|
|
|
|
const credentials = await fetchActiveAICredentials();
|
|
|
|
dropdown.innerHTML = '';
|
|
|
|
dropdown.innerHTML = '';
|
|
|
|
|
|
|
|
appendNoteUI();
|
|
|
|
|
|
|
|
|
|
|
|
if (!credentials || credentials.length === 0) {
|
|
|
|
if (!credentials || credentials.length === 0) {
|
|
|
|
const empty = document.createElement('div');
|
|
|
|
const empty = document.createElement('div');
|
|
|
|
empty.textContent = 'Keine aktiven AI-Anbieter gefunden';
|
|
|
|
empty.textContent = 'Keine aktiven AI-Anbieter gefunden';
|
|
|
|
empty.style.cssText = 'padding: 8px 14px; font-size: 13px; color: #888;';
|
|
|
|
empty.style.cssText = 'padding: 8px 14px; font-size: 13px; color: #888;';
|
|
|
|
dropdown.appendChild(empty);
|
|
|
|
dropdown.appendChild(empty);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
}
|
|
|
|
const divider = document.createElement('div');
|
|
|
|
|
|
|
|
divider.style.cssText = 'border-top: 1px solid #e4e6eb; margin: 6px 0;';
|
|
|
|
|
|
|
|
dropdown.appendChild(divider);
|
|
|
|
|
|
|
|
|
|
|
|
credentials.forEach((credential) => {
|
|
|
|
credentials.forEach((credential) => {
|
|
|
|
const option = document.createElement('button');
|
|
|
|
const option = document.createElement('button');
|
|
|
|
@@ -3587,8 +3814,10 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
|
|
|
|
|
|
|
|
dropdown.appendChild(option);
|
|
|
|
dropdown.appendChild(option);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
dropdown.innerHTML = '';
|
|
|
|
dropdown.innerHTML = '';
|
|
|
|
|
|
|
|
appendNoteUI();
|
|
|
|
const errorItem = document.createElement('div');
|
|
|
|
const errorItem = document.createElement('div');
|
|
|
|
errorItem.textContent = error.message || 'AI-Anbieter konnten nicht geladen werden';
|
|
|
|
errorItem.textContent = error.message || 'AI-Anbieter konnten nicht geladen werden';
|
|
|
|
errorItem.style.cssText = 'padding: 8px 14px; font-size: 13px; color: #c0392b; white-space: normal;';
|
|
|
|
errorItem.style.cssText = 'padding: 8px 14px; font-size: 13px; color: #c0392b; white-space: normal;';
|
|
|
|
@@ -3596,6 +3825,50 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const positionDropdown = () => {
|
|
|
|
|
|
|
|
if (!dropdownOpen) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mountDropdownInPortal();
|
|
|
|
|
|
|
|
dropdown.style.position = 'fixed';
|
|
|
|
|
|
|
|
dropdown.style.maxHeight = `${Math.max(200, Math.floor(window.innerHeight * 0.6))}px`;
|
|
|
|
|
|
|
|
dropdown.style.overflowY = 'auto';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const rect = wrapper.getBoundingClientRect();
|
|
|
|
|
|
|
|
const dropdownRect = dropdown.getBoundingClientRect();
|
|
|
|
|
|
|
|
const margin = 8;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let top = rect.top - dropdownRect.height - margin;
|
|
|
|
|
|
|
|
if (top < margin) {
|
|
|
|
|
|
|
|
top = rect.bottom + margin;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const viewportPadding = 8;
|
|
|
|
|
|
|
|
let left = rect.right - dropdownRect.width;
|
|
|
|
|
|
|
|
if (left < viewportPadding) {
|
|
|
|
|
|
|
|
left = viewportPadding;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const maxLeft = window.innerWidth - dropdownRect.width - viewportPadding;
|
|
|
|
|
|
|
|
if (left > maxLeft) {
|
|
|
|
|
|
|
|
left = Math.max(viewportPadding, maxLeft);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const maxTop = window.innerHeight - dropdownRect.height - margin;
|
|
|
|
|
|
|
|
if (top > maxTop) {
|
|
|
|
|
|
|
|
top = Math.max(viewportPadding, maxTop);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dropdown.style.top = `${top}px`;
|
|
|
|
|
|
|
|
dropdown.style.left = `${left}px`;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const repositionDropdown = () => {
|
|
|
|
|
|
|
|
if (dropdownOpen) {
|
|
|
|
|
|
|
|
positionDropdown();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const toggleDropdown = async () => {
|
|
|
|
const toggleDropdown = async () => {
|
|
|
|
if ((button.dataset.aiState || 'idle') !== 'idle') {
|
|
|
|
if ((button.dataset.aiState || 'idle') !== 'idle') {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
@@ -3609,12 +3882,16 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
dropdownOpen = true;
|
|
|
|
dropdownOpen = true;
|
|
|
|
wrapper.classList.add('fb-tracker-ai-wrapper--open');
|
|
|
|
wrapper.classList.add('fb-tracker-ai-wrapper--open');
|
|
|
|
dropdownButton.textContent = '▴';
|
|
|
|
dropdownButton.textContent = '▴';
|
|
|
|
|
|
|
|
mountDropdownInPortal();
|
|
|
|
dropdown.style.display = 'block';
|
|
|
|
dropdown.style.display = 'block';
|
|
|
|
dropdownButton.setAttribute('aria-expanded', 'true');
|
|
|
|
dropdownButton.setAttribute('aria-expanded', 'true');
|
|
|
|
document.addEventListener('click', handleOutsideClick, true);
|
|
|
|
document.addEventListener('click', handleOutsideClick, true);
|
|
|
|
document.addEventListener('keydown', handleKeydown, true);
|
|
|
|
document.addEventListener('keydown', handleKeydown, true);
|
|
|
|
|
|
|
|
|
|
|
|
await renderDropdownItems();
|
|
|
|
await renderDropdownItems();
|
|
|
|
|
|
|
|
positionDropdown();
|
|
|
|
|
|
|
|
window.addEventListener('scroll', repositionDropdown, true);
|
|
|
|
|
|
|
|
window.addEventListener('resize', repositionDropdown);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
dropdownButton.addEventListener('click', (event) => {
|
|
|
|
dropdownButton.addEventListener('click', (event) => {
|
|
|
|
@@ -3708,10 +3985,8 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
button.textContent = '⏳ Generiere...';
|
|
|
|
button.textContent = '⏳ Generiere...';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const contextCandidate = container.closest('div[aria-posinset], article[role="article"], article, div[role="complementary"]');
|
|
|
|
const contexts = resolvePostContexts();
|
|
|
|
const normalizedContext = contextCandidate ? ensurePrimaryPostElement(contextCandidate) : null;
|
|
|
|
const { postContext, contextCandidate, fallbackContext } = contexts;
|
|
|
|
const fallbackContext = postElement ? ensurePrimaryPostElement(postElement) : null;
|
|
|
|
|
|
|
|
const postContext = normalizedContext || fallbackContext || contextCandidate || postElement || container;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const selectionKeys = [];
|
|
|
|
const selectionKeys = [];
|
|
|
|
if (postContext) {
|
|
|
|
if (postContext) {
|
|
|
|
@@ -3723,6 +3998,12 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
if (contextCandidate && contextCandidate !== postContext && contextCandidate !== postElement) {
|
|
|
|
if (contextCandidate && contextCandidate !== postContext && contextCandidate !== postElement) {
|
|
|
|
selectionKeys.push(contextCandidate);
|
|
|
|
selectionKeys.push(contextCandidate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fallbackContext
|
|
|
|
|
|
|
|
&& fallbackContext !== postContext
|
|
|
|
|
|
|
|
&& fallbackContext !== postElement
|
|
|
|
|
|
|
|
&& fallbackContext !== contextCandidate) {
|
|
|
|
|
|
|
|
selectionKeys.push(fallbackContext);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const resolveRecentSelection = () => {
|
|
|
|
const resolveRecentSelection = () => {
|
|
|
|
for (const key of selectionKeys) {
|
|
|
|
for (const key of selectionKeys) {
|
|
|
|
@@ -3781,6 +4062,11 @@ async function addAICommentButton(container, postElement) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|