Bind tracker UI to stable insertion marker under actions

This commit is contained in:
2026-02-26 00:27:12 +01:00
parent 2fc5dbfacc
commit f10d1f5a8d

View File

@@ -29,6 +29,7 @@ function isOnSearchResultsPage() {
}
const trackerElementsByPost = new WeakMap();
const trackerAnchorsByPost = new WeakMap();
const postAdditionalNotes = new WeakMap();
const REELS_PATH_PREFIX = '/reel/';
@@ -189,6 +190,52 @@ function clearTrackerElementForPost(postElement, trackerElement = null) {
trackerElementsByPost.delete(postElement);
}
function getTrackerAnchorForPost(postElement) {
if (!postElement) {
return null;
}
const anchor = trackerAnchorsByPost.get(postElement);
if (anchor && anchor.isConnected) {
return anchor;
}
if (anchor) {
trackerAnchorsByPost.delete(postElement);
}
return null;
}
function setTrackerAnchorForPost(postElement, anchorElement) {
if (!postElement) {
return;
}
if (anchorElement && anchorElement.isConnected) {
trackerAnchorsByPost.set(postElement, anchorElement);
} else {
trackerAnchorsByPost.delete(postElement);
}
}
function clearTrackerAnchorForPost(postElement, anchorElement = null) {
if (!postElement) {
return;
}
if (!trackerAnchorsByPost.has(postElement)) {
return;
}
const current = trackerAnchorsByPost.get(postElement);
if (anchorElement && current && current !== anchorElement) {
return;
}
trackerAnchorsByPost.delete(postElement);
}
function getTrackerScopeRoot(element) {
return element ? element.closest(DIALOG_ROOT_SELECTOR) : null;
}
@@ -361,6 +408,91 @@ function findAdjacentCommentSortAnchor(postElement, actionAnchor = null) {
return null;
}
function createTrackerInsertionMarker() {
const marker = document.createElement('div');
marker.className = 'fb-tracker-insertion-anchor';
marker.setAttribute('aria-hidden', 'true');
marker.style.cssText = `
display: block;
width: 100%;
height: 0;
margin: 0;
padding: 0;
border: 0;
pointer-events: none;
`;
return marker;
}
function positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum = '?') {
if (!postElement || !marker) {
return false;
}
const resolvedButtonBar = resolveActionButtonBar(postElement, buttonBar);
const actionAnchor = getDirectActionAnchorInPost(postElement, resolvedButtonBar);
const sortAnchor = findAdjacentCommentSortAnchor(postElement, actionAnchor);
const preferredAnchor = sortAnchor || actionAnchor;
if (preferredAnchor && preferredAnchor.parentElement) {
preferredAnchor.parentElement.insertBefore(marker, preferredAnchor.nextSibling);
return true;
}
if (resolvedButtonBar && resolvedButtonBar.parentElement && postElement.contains(resolvedButtonBar.parentElement)) {
resolvedButtonBar.parentElement.insertBefore(marker, resolvedButtonBar.nextSibling);
return true;
}
if (buttonBar && buttonBar.parentElement && buttonBar.parentElement.parentElement && !postElement.contains(buttonBar.parentElement.parentElement)) {
buttonBar.parentElement.parentElement.insertBefore(marker, buttonBar.parentElement.nextSibling);
return true;
}
postElement.appendChild(marker);
console.log('[FB Tracker] Post #' + postNum + ' - Marker fallback appended to post');
return true;
}
function ensureTrackerInsertionMarker(postElement, buttonBar, postNum = '?') {
if (!postElement) {
return null;
}
let marker = getTrackerAnchorForPost(postElement);
if (!marker) {
const existingMarkers = Array.from(postElement.querySelectorAll('.fb-tracker-insertion-anchor'));
if (existingMarkers.length > 0) {
marker = existingMarkers[0];
existingMarkers.slice(1).forEach((duplicate) => duplicate.remove());
setTrackerAnchorForPost(postElement, marker);
}
}
if (!marker || !marker.isConnected) {
marker = createTrackerInsertionMarker();
}
const positioned = positionTrackerInsertionMarker(postElement, marker, buttonBar, postNum);
if (!positioned || !marker.isConnected) {
clearTrackerAnchorForPost(postElement, marker);
return null;
}
setTrackerAnchorForPost(postElement, marker);
return marker;
}
function insertTrackerAtMarker(marker, trackerElement) {
if (!marker || !trackerElement || !marker.parentElement) {
return false;
}
marker.parentElement.insertBefore(trackerElement, marker.nextSibling);
return true;
}
const AI_CREDENTIAL_CACHE_TTL = 60 * 1000; // 1 minute cache
const aiCredentialCache = {
data: null,
@@ -3223,6 +3355,10 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
}
if (existingUI) {
const marker = ensureTrackerInsertionMarker(postElement, buttonBar, postNum);
if (marker) {
insertTrackerAtMarker(marker, existingUI);
}
console.log('[FB Tracker] Post #' + postNum + ' - UI already exists on normalized element');
postElement.setAttribute(PROCESSED_ATTR, '1');
return;
@@ -3252,6 +3388,10 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
if (existingScopedTrackers.length > 0) {
const scopedTracker = dedupeTrackersForUrlInScope(encodedUrl, scopeRoot, existingScopedTrackers[0]);
if (scopedTracker) {
const marker = ensureTrackerInsertionMarker(postElement, buttonBar, postNum);
if (marker) {
insertTrackerAtMarker(marker, scopedTracker);
}
console.log('[FB Tracker] Post #' + postNum + ' - Reusing existing tracker UI in same scope');
setTrackerElementForPost(postElement, scopedTracker);
postElement.setAttribute(PROCESSED_ATTR, '1');
@@ -3907,35 +4047,19 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
inserted = tryInsertBeforeReelsCommentComposer();
}
const resolvedButtonBar = resolveActionButtonBar(postElement, buttonBar);
const inPostActionAnchor = getDirectActionAnchorInPost(postElement, resolvedButtonBar);
const commentSortAnchor = findAdjacentCommentSortAnchor(postElement, inPostActionAnchor);
const preferredAnchor = commentSortAnchor || inPostActionAnchor;
const insertionMarker = !inserted
? ensureTrackerInsertionMarker(postElement, buttonBar, postNum)
: null;
// Strategy 1: Insert below actions; if present, place below the sort row
if (!inserted && preferredAnchor && preferredAnchor.parentElement) {
preferredAnchor.parentElement.insertBefore(container, preferredAnchor.nextSibling);
if (commentSortAnchor) {
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted below comment sort row. ID: #' + container.id);
} else {
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted directly below actions. ID: #' + container.id);
// Strategy 1: Insert at stable marker that is locked below the action bar
if (!inserted && insertionMarker) {
inserted = insertTrackerAtMarker(insertionMarker, container);
if (inserted) {
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted at stable marker. ID: #' + container.id);
}
inserted = true;
}
// Strategy 2: Insert directly after the detected button bar if still inside the post container
if (!inserted && resolvedButtonBar && resolvedButtonBar.parentElement && postElement.contains(resolvedButtonBar.parentElement)) {
resolvedButtonBar.parentElement.insertBefore(container, resolvedButtonBar.nextSibling);
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after button bar (in-post fallback). ID: #' + container.id);
inserted = true;
}
// 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 4: Append to post element
// Strategy 2: Last fallback append to post element
if (!inserted) {
postElement.appendChild(container);
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted into article (fallback). ID: #' + container.id);
@@ -3966,17 +4090,8 @@ 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 currentResolvedButtonBar = resolveActionButtonBar(postElement, buttonBar);
const currentInPostActionAnchor = getDirectActionAnchorInPost(postElement, currentResolvedButtonBar);
const currentCommentSortAnchor = findAdjacentCommentSortAnchor(postElement, currentInPostActionAnchor);
const currentPreferredAnchor = currentCommentSortAnchor || currentInPostActionAnchor;
if (currentPreferredAnchor && currentPreferredAnchor.parentElement) {
currentPreferredAnchor.parentElement.insertBefore(container, currentPreferredAnchor.nextSibling);
} else if (currentResolvedButtonBar && currentResolvedButtonBar.parentElement && postElement.contains(currentResolvedButtonBar.parentElement)) {
currentResolvedButtonBar.parentElement.insertBefore(container, currentResolvedButtonBar.nextSibling);
} else if (buttonBar && buttonBar.parentElement && buttonBar.parentElement.parentElement && !postElement.contains(buttonBar.parentElement.parentElement)) {
buttonBar.parentElement.parentElement.insertBefore(container, buttonBar.parentElement.nextSibling);
} else if (postElement) {
const currentMarker = ensureTrackerInsertionMarker(postElement, buttonBar, postNum);
if (!currentMarker || !insertTrackerAtMarker(currentMarker, container)) {
postElement.appendChild(container);
}
if (container.isConnected) {