Spaces:
Running
Running
const queryArg = "section"; | |
function syncHFSpacesURLHash() { | |
// Handle explicit section requests (don't update hash automatically on load) | |
const hasExplicitRequest = handleExplicitSectionRequest(); | |
// Set up hash change monitoring | |
updateHashBasedOnHashChange(); | |
// Always set up scroll monitoring to update hash during scrolling | |
setupScrollMonitoring(); | |
// If no explicit request, we don't update the hash on initial load | |
// The hash will only start updating when the user scrolls | |
} | |
function handleExplicitSectionRequest() { | |
// Check for section parameter in URL | |
const urlParams = new URLSearchParams(window.location.search); | |
const sectionId = urlParams.get(queryArg); | |
// If we have an explicit section request | |
if (sectionId) { | |
const targetElement = document.getElementById(sectionId); | |
if (targetElement) { | |
// Slight delay to ensure the browser doesn't try to do its own scrolling first | |
setTimeout(() => { | |
targetElement.scrollIntoView(); | |
history.replaceState(null, null, `#${sectionId}`); | |
}, 100); | |
} | |
return true; | |
} | |
// No explicit section parameter found | |
return false; | |
} | |
function setupScrollMonitoring() { | |
// Variables to manage throttling | |
let isScrolling = false; | |
let lastKnownScrollPosition = 0; | |
let initialScroll = true; | |
// Add the scroll event listener | |
window.addEventListener('scroll', function() { | |
lastKnownScrollPosition = window.scrollY; | |
if (!isScrolling) { | |
window.requestAnimationFrame(function() { | |
// Skip the first scroll event which might be browser's automatic scroll | |
// to a hash on page load | |
if (initialScroll) { | |
initialScroll = false; | |
} else { | |
updateHashBasedOnScroll(lastKnownScrollPosition); | |
} | |
isScrolling = false; | |
}); | |
} | |
isScrolling = true; | |
}); | |
} | |
// Function to update the URL hash based on scroll position | |
function updateHashBasedOnScroll(scrollPosition) { | |
const closestHeading = findClosestHeading(scrollPosition); | |
// Update the URL hash if we found a closest element | |
if (closestHeading && closestHeading.id) { | |
// Only update if the hash is different to avoid unnecessary operations | |
if (window.location.hash !== `#${closestHeading.id}`) { | |
silentlyUpdateHash(closestHeading.id); | |
postMessageToHFSpaces(closestHeading.id); | |
} | |
} | |
} | |
// Find the closest heading to the current scroll position | |
function findClosestHeading(scrollPosition) { | |
// Get only heading elements with IDs that we want to track | |
const headingsWithIds = Array.from(document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]')); | |
// Skip if there are no headings with IDs | |
if (headingsWithIds.length === 0) return null; | |
// Find the element closest to the middle of the viewport | |
let closestHeading = null; | |
let closestDistance = Infinity; | |
const viewportMiddle = scrollPosition + window.innerHeight / 2; | |
// Iterate through all headings to find the closest one | |
headingsWithIds.forEach(heading => { | |
const headingTop = heading.getBoundingClientRect().top + scrollPosition; | |
const distance = Math.abs(headingTop - viewportMiddle); | |
if (distance < closestDistance) { | |
closestDistance = distance; | |
closestHeading = heading; | |
} | |
}); | |
return closestHeading; | |
} | |
// Update hash without triggering scroll or other side effects | |
function silentlyUpdateHash(id) { | |
history.replaceState(null, null, `#${id}`); | |
} | |
function updateHashBasedOnHashChange() { | |
window.addEventListener('hashchange', () => { | |
const elementId = window.location.hash.slice(1); | |
postMessageToHFSpaces(elementId); | |
}); | |
} | |
function postMessageToHFSpaces(elementId) { | |
const parentOrigin = "https://huggingface.co"; | |
window.parent.postMessage({ queryString: `${queryArg}=${elementId}` }, parentOrigin); | |
} | |
export { syncHFSpacesURLHash }; | |