Bind tracker UI to stable insertion marker under actions
This commit is contained in:
@@ -29,6 +29,7 @@ function isOnSearchResultsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trackerElementsByPost = new WeakMap();
|
const trackerElementsByPost = new WeakMap();
|
||||||
|
const trackerAnchorsByPost = new WeakMap();
|
||||||
const postAdditionalNotes = new WeakMap();
|
const postAdditionalNotes = new WeakMap();
|
||||||
|
|
||||||
const REELS_PATH_PREFIX = '/reel/';
|
const REELS_PATH_PREFIX = '/reel/';
|
||||||
@@ -189,6 +190,52 @@ function clearTrackerElementForPost(postElement, trackerElement = null) {
|
|||||||
trackerElementsByPost.delete(postElement);
|
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) {
|
function getTrackerScopeRoot(element) {
|
||||||
return element ? element.closest(DIALOG_ROOT_SELECTOR) : null;
|
return element ? element.closest(DIALOG_ROOT_SELECTOR) : null;
|
||||||
}
|
}
|
||||||
@@ -361,6 +408,91 @@ function findAdjacentCommentSortAnchor(postElement, actionAnchor = null) {
|
|||||||
return 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 AI_CREDENTIAL_CACHE_TTL = 60 * 1000; // 1 minute cache
|
||||||
const aiCredentialCache = {
|
const aiCredentialCache = {
|
||||||
data: null,
|
data: null,
|
||||||
@@ -3223,6 +3355,10 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (existingUI) {
|
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');
|
console.log('[FB Tracker] Post #' + postNum + ' - UI already exists on normalized element');
|
||||||
postElement.setAttribute(PROCESSED_ATTR, '1');
|
postElement.setAttribute(PROCESSED_ATTR, '1');
|
||||||
return;
|
return;
|
||||||
@@ -3252,6 +3388,10 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
|
|||||||
if (existingScopedTrackers.length > 0) {
|
if (existingScopedTrackers.length > 0) {
|
||||||
const scopedTracker = dedupeTrackersForUrlInScope(encodedUrl, scopeRoot, existingScopedTrackers[0]);
|
const scopedTracker = dedupeTrackersForUrlInScope(encodedUrl, scopeRoot, existingScopedTrackers[0]);
|
||||||
if (scopedTracker) {
|
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');
|
console.log('[FB Tracker] Post #' + postNum + ' - Reusing existing tracker UI in same scope');
|
||||||
setTrackerElementForPost(postElement, scopedTracker);
|
setTrackerElementForPost(postElement, scopedTracker);
|
||||||
postElement.setAttribute(PROCESSED_ATTR, '1');
|
postElement.setAttribute(PROCESSED_ATTR, '1');
|
||||||
@@ -3907,35 +4047,19 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
|
|||||||
inserted = tryInsertBeforeReelsCommentComposer();
|
inserted = tryInsertBeforeReelsCommentComposer();
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolvedButtonBar = resolveActionButtonBar(postElement, buttonBar);
|
const insertionMarker = !inserted
|
||||||
const inPostActionAnchor = getDirectActionAnchorInPost(postElement, resolvedButtonBar);
|
? ensureTrackerInsertionMarker(postElement, buttonBar, postNum)
|
||||||
const commentSortAnchor = findAdjacentCommentSortAnchor(postElement, inPostActionAnchor);
|
: null;
|
||||||
const preferredAnchor = commentSortAnchor || inPostActionAnchor;
|
|
||||||
|
|
||||||
// Strategy 1: Insert below actions; if present, place below the sort row
|
// Strategy 1: Insert at stable marker that is locked below the action bar
|
||||||
if (!inserted && preferredAnchor && preferredAnchor.parentElement) {
|
if (!inserted && insertionMarker) {
|
||||||
preferredAnchor.parentElement.insertBefore(container, preferredAnchor.nextSibling);
|
inserted = insertTrackerAtMarker(insertionMarker, container);
|
||||||
if (commentSortAnchor) {
|
if (inserted) {
|
||||||
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted below comment sort row. ID: #' + container.id);
|
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted at stable marker. ID: #' + container.id);
|
||||||
} else {
|
|
||||||
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted directly below actions. 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)) {
|
// Strategy 2: Last fallback append to post element
|
||||||
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
|
|
||||||
if (!inserted) {
|
if (!inserted) {
|
||||||
postElement.appendChild(container);
|
postElement.appendChild(container);
|
||||||
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted into article (fallback). ID: #' + container.id);
|
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...');
|
console.log('[FB Tracker] Post #' + postNum + ' - UI was removed, re-inserting...');
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
// Try to re-insert
|
// Try to re-insert
|
||||||
const currentResolvedButtonBar = resolveActionButtonBar(postElement, buttonBar);
|
const currentMarker = ensureTrackerInsertionMarker(postElement, buttonBar, postNum);
|
||||||
const currentInPostActionAnchor = getDirectActionAnchorInPost(postElement, currentResolvedButtonBar);
|
if (!currentMarker || !insertTrackerAtMarker(currentMarker, container)) {
|
||||||
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) {
|
|
||||||
postElement.appendChild(container);
|
postElement.appendChild(container);
|
||||||
}
|
}
|
||||||
if (container.isConnected) {
|
if (container.isConnected) {
|
||||||
|
|||||||
Reference in New Issue
Block a user