diff --git a/extension/content.js b/extension/content.js index 9de3491..f351d9b 100644 --- a/extension/content.js +++ b/extension/content.js @@ -242,88 +242,61 @@ function getTrackerInsertionAnchorInPost(postElement, buttonBar) { return anchor && anchor.parentElement === postElement ? anchor : null; } -function isNodeAfter(node, referenceNode) { - if (!node || !referenceNode || node === referenceNode) { - return false; - } - return Boolean(referenceNode.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING); -} - -function getTopLevelAnchorWithinPost(postElement, element) { - if (!postElement || !element || !postElement.contains(element)) { - return null; - } - - let anchor = element; - while (anchor.parentElement && anchor.parentElement !== postElement) { - anchor = anchor.parentElement; - } - - return anchor && anchor.parentElement === postElement ? anchor : null; -} - -function findCommentSectionAnchorInPost(postElement, actionAnchor = null) { +function getDirectActionAnchorInPost(postElement, buttonBar) { if (!postElement) { return null; } - const selectors = [ - '[aria-label*="relevantes zuerst" i]', - '[aria-label*="most relevant" i]', - '[aria-label*="neueste" i]', - '[aria-label*="newest" i]', - '[aria-roledescription*="kommentar" i]', - '[aria-roledescription*="comment" i]', - '[data-testid*="comment" i]', - '[data-scope="comment"]', - 'div[role="textbox"][contenteditable="true"]', - 'div[contenteditable="true"][data-lexical-editor="true"]', - '[aria-placeholder*="komment" i]', - '[aria-placeholder*="comment" i]' + const seeds = []; + const seedSelectors = [ + '[data-ad-rendering-role="share_button"]', + '[data-ad-rendering-role="comment_button"]', + '[data-ad-rendering-role*="gefällt"]', + '[data-ad-rendering-role*="like_button"]', + '[data-ad-rendering-role*="reaction-like"]' ]; - const anchors = new Set(); - - postElement.querySelectorAll(selectors.join(', ')).forEach((candidate) => { - if (!candidate || candidate.closest('.fb-tracker-ui') || !isElementVisible(candidate)) { - return; + seedSelectors.forEach((selector) => { + const candidate = postElement.querySelector(selector); + if (candidate) { + seeds.push(candidate); } - - let anchorSeed = candidate; - const isTextBox = candidate.matches('div[role="textbox"][contenteditable="true"], div[contenteditable="true"][data-lexical-editor="true"]'); - if (isTextBox) { - anchorSeed = candidate.closest('form[role="presentation"]') - || candidate.closest('form') - || candidate; - } - - let anchor = getTopLevelAnchorWithinPost(postElement, anchorSeed); - if (anchor === actionAnchor) { - anchor = anchorSeed; - } - - if (!anchor || anchor === actionAnchor || !anchor.parentElement) { - return; - } - - const anchorIsInsideAction = Boolean(actionAnchor && actionAnchor.contains(anchor)); - if (actionAnchor && !anchorIsInsideAction && !isNodeAfter(anchor, actionAnchor)) { - return; - } - - anchors.add(anchor); }); - if (!anchors.size) { - return null; + if (buttonBar && postElement.contains(buttonBar)) { + seeds.push(buttonBar); } - return Array.from(anchors).sort((a, b) => { - if (a === b) { - return 0; + for (const seed of seeds) { + let current = seed.closest('[role="button"], button') || seed; + while (current && current !== postElement) { + if (!postElement.contains(current)) { + break; + } + + const hasShare = Boolean(current.querySelector('[data-ad-rendering-role="share_button"]')); + const hasComment = Boolean(current.querySelector('[data-ad-rendering-role="comment_button"]')); + const hasLike = Boolean( + current.querySelector('[data-ad-rendering-role*="gefällt"], [data-ad-rendering-role*="like_button"], [data-ad-rendering-role*="reaction-like"]') + ); + const interactionCount = [hasShare, hasComment, hasLike].filter(Boolean).length; + const hasComposer = Boolean( + current.querySelector('div[role="textbox"][contenteditable="true"], div[contenteditable="true"][data-lexical-editor="true"]') + ); + + if (interactionCount >= 2 && !hasComposer) { + return current; + } + + current = current.parentElement; } - return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1; - })[0]; + } + + if (buttonBar && buttonBar.parentElement && postElement.contains(buttonBar.parentElement)) { + return buttonBar; + } + + return getTrackerInsertionAnchorInPost(postElement, buttonBar); } const AI_CREDENTIAL_CACHE_TTL = 60 * 1000; // 1 minute cache @@ -3874,35 +3847,28 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options = inserted = tryInsertBeforeReelsCommentComposer(); } - const actionAnchor = getTrackerInsertionAnchorInPost(postElement, buttonBar); - const commentAnchor = findCommentSectionAnchorInPost(postElement, actionAnchor); + const directActionAnchor = getDirectActionAnchorInPost(postElement, buttonBar); - // Strategy 1: Insert before comment section so the bar stays above lazy-loaded comments - if (!inserted && commentAnchor && commentAnchor.parentElement) { - commentAnchor.parentElement.insertBefore(container, commentAnchor); - console.log('[FB Tracker] Post #' + postNum + ' - UI inserted before comment section. ID: #' + container.id); + // Strategy 1: Insert directly below the actions row + if (!inserted && directActionAnchor && directActionAnchor.parentElement) { + directActionAnchor.parentElement.insertBefore(container, directActionAnchor.nextSibling); + console.log('[FB Tracker] Post #' + postNum + ' - UI inserted directly below actions. ID: #' + container.id); inserted = true; } - // Strategy 2: Insert right after the action block inside the current post container - if (!inserted && actionAnchor && actionAnchor.parentElement) { - actionAnchor.parentElement.insertBefore(container, actionAnchor.nextSibling); - console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after in-post action block. ID: #' + container.id); - inserted = true; - } - // Strategy 3: Insert directly after the button bar if still inside the post container + // Strategy 2: Insert directly after the detected button bar if still inside the post container if (!inserted && buttonBar && buttonBar.parentElement && postElement.contains(buttonBar.parentElement)) { buttonBar.parentElement.insertBefore(container, buttonBar.nextSibling); console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after button bar (in-post fallback). ID: #' + container.id); inserted = true; } - // Strategy 4: Legacy fallback for old variants where the interaction bar is outside the post container + // Strategy 3: Legacy fallback for old variants where the interaction bar is outside the post container if (!inserted && buttonBar && buttonBar.parentElement && buttonBar.parentElement.parentElement && !postElement.contains(buttonBar.parentElement.parentElement)) { const grandParent = buttonBar.parentElement.parentElement; grandParent.insertBefore(container, buttonBar.parentElement.nextSibling); console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after external button bar parent (legacy). ID: #' + container.id); inserted = true; } - // Strategy 5: Append to post element + // Strategy 4: Append to post element if (!inserted) { postElement.appendChild(container); console.log('[FB Tracker] Post #' + postNum + ' - UI inserted into article (fallback). ID: #' + container.id); @@ -3933,12 +3899,9 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options = console.log('[FB Tracker] Post #' + postNum + ' - UI was removed, re-inserting...'); observer.disconnect(); // Try to re-insert - const currentActionAnchor = getTrackerInsertionAnchorInPost(postElement, buttonBar); - const currentCommentAnchor = findCommentSectionAnchorInPost(postElement, currentActionAnchor); - if (currentCommentAnchor && currentCommentAnchor.parentElement) { - currentCommentAnchor.parentElement.insertBefore(container, currentCommentAnchor); - } else if (currentActionAnchor && currentActionAnchor.parentElement) { - currentActionAnchor.parentElement.insertBefore(container, currentActionAnchor.nextSibling); + const currentDirectActionAnchor = getDirectActionAnchorInPost(postElement, buttonBar); + if (currentDirectActionAnchor && currentDirectActionAnchor.parentElement) { + currentDirectActionAnchor.parentElement.insertBefore(container, currentDirectActionAnchor.nextSibling); } else if (buttonBar && buttonBar.parentElement && postElement.contains(buttonBar.parentElement)) { buttonBar.parentElement.insertBefore(container, buttonBar.nextSibling); } else if (buttonBar && buttonBar.parentElement && buttonBar.parentElement.parentElement && !postElement.contains(buttonBar.parentElement.parentElement)) {