Skip to content

Commit 8fb046b

Browse files
committed
Fix: anchor navigation by updating section IDs, heading links and TOC references
1 parent eafad9c commit 8fb046b

File tree

1 file changed

+133
-16
lines changed

1 file changed

+133
-16
lines changed

src/features/feeds/components/FeedList.tsx

Lines changed: 133 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import useQueryString from "~/hooks/useQueryString.ts"
1111
import { RefObject } from "preact"
1212
import SectionWrapper from "~/components/SectionWrapper/SectionWrapper.tsx"
1313
import button from "@chainlink/design-system/button.module.css"
14+
import { updateTableOfContents } from "~/components/TableOfContents/tocStore.ts"
1415

1516
export type DataFeedType = "default" | "smartdata" | "rates" | "streamsCrypto" | "streamsRwa"
1617
export const FeedList = ({
@@ -136,23 +137,119 @@ export const FeedList = ({
136137
const wrapperRef = useRef(null)
137138
const [showOnlySVR, setShowOnlySVR] = useState(false)
138139

139-
// Scroll restoration handler - re-scroll to hash target after content loads
140+
// scroll handler
140141
useEffect(() => {
141-
if (typeof window === "undefined" || !window.location.hash) return
142-
143-
// Check if chainMetadata has loaded (content is ready)
144142
if (!chainMetadata.loading && chainMetadata.processedData) {
145-
// Wait for any DOM updates to finish
143+
if (typeof window === "undefined") return
144+
145+
// Get the anchor from URL if present
146+
const hash = window.location.hash.substring(1) // Remove the # character
147+
148+
// Force a delay to ensure DOM elements are rendered before updating
146149
setTimeout(() => {
147-
const targetId = window.location.hash.substring(1)
148-
const targetElement = document.getElementById(targetId)
149-
if (targetElement) {
150-
// Use scrollIntoView with behavior: auto to ensure proper positioning
151-
targetElement.scrollIntoView({ behavior: "auto" })
150+
let hasUpdatedAnyId = false
151+
152+
// Find all section elements that need their IDs updated
153+
chainMetadata.processedData?.networks.forEach((network) => {
154+
const sectionId = network.name.toLowerCase().replace(/\s+/g, "-")
155+
const existingSection = document.getElementById(sectionId)
156+
157+
// If section exists with correct ID, no need to update
158+
if (existingSection) return
159+
160+
// Find section with network name title and update its ID
161+
document.querySelectorAll("h3").forEach((heading) => {
162+
if (heading.textContent === network.name) {
163+
const section = heading.closest("section")
164+
if (section) {
165+
const oldId = section.id
166+
section.id = sectionId
167+
heading.id = sectionId
168+
hasUpdatedAnyId = true
169+
170+
// Update anchor links inside the heading
171+
const anchor = heading.querySelector("a")
172+
if (anchor) {
173+
anchor.href = `#${sectionId}`
174+
}
175+
176+
// If we're updating the ID that matches our hash, we need to scroll to it
177+
if (hash && (hash === oldId || hash === sectionId)) {
178+
setTimeout(() => section.scrollIntoView({ behavior: "auto" }), 100)
179+
}
180+
}
181+
}
182+
})
183+
})
184+
185+
// Also update testnet section if it exists
186+
if (chainMetadata.processedData?.testnetNetwork) {
187+
const testnetId =
188+
chainMetadata.processedData.testnetNetwork.name.toLowerCase().replace(/\s+/g, "-") || "testnet-feeds"
189+
document.querySelectorAll("h2").forEach((heading) => {
190+
if (heading.textContent === "Testnet Feeds" || heading.textContent?.includes("Testnet")) {
191+
const section = heading.closest("section")
192+
if (section) {
193+
const oldId = section.id
194+
section.id = testnetId
195+
heading.id = testnetId
196+
hasUpdatedAnyId = true
197+
198+
// Update anchor links inside the heading
199+
const anchor = heading.querySelector("a")
200+
if (anchor) {
201+
anchor.href = `#${testnetId}`
202+
}
203+
204+
// If we're updating the ID that matches our hash, we need to scroll to it
205+
if (hash && (hash === oldId || hash === testnetId)) {
206+
setTimeout(() => section.scrollIntoView({ behavior: "auto" }), 100)
207+
}
208+
}
209+
}
210+
})
211+
}
212+
213+
// If we have a hash but haven't scrolled yet, try to find the element with that ID
214+
if (hash && hasUpdatedAnyId) {
215+
const targetElement = document.getElementById(hash)
216+
if (targetElement) {
217+
setTimeout(() => targetElement.scrollIntoView({ behavior: "auto" }), 100)
218+
}
219+
} else if (hash) {
220+
// Basic fallback if we didnt update any IDs but still have a hash
221+
const targetElement = document.getElementById(hash)
222+
if (targetElement) {
223+
setTimeout(() => targetElement.scrollIntoView({ behavior: "auto" }), 200)
224+
}
152225
}
153-
}, 200)
226+
227+
// Update TOC links if we made any ID changes
228+
if (hasUpdatedAnyId) {
229+
// Find the TOC container and update its links
230+
const tocLinks = document.querySelectorAll(".toc-item a")
231+
tocLinks.forEach((link) => {
232+
const href = link.getAttribute("href")
233+
if (href) {
234+
const currentHash = href.split("#")[1]
235+
if (currentHash) {
236+
// Try to find element with this ID
237+
const targetHeading = document.getElementById(currentHash)
238+
if (targetHeading) {
239+
// Update the TOC link to point to the correct ID
240+
const updatedHref = window.location.pathname + window.location.search + "#" + currentHash
241+
link.setAttribute("href", updatedHref)
242+
}
243+
}
244+
}
245+
})
246+
247+
// Trigger a TOC update
248+
updateTableOfContents()
249+
}
250+
}, 300)
154251
}
155-
}, [chainMetadata.loading, chainMetadata.processedData])
252+
}, [chainMetadata.loading, chainMetadata.processedData, currentNetwork])
156253

157254
// Network selection handler
158255
function handleNetworkSelect(chain: Chain) {
@@ -304,7 +401,11 @@ export const FeedList = ({
304401
<StreamsNetworkAddressesTable />
305402
</SectionWrapper>
306403

307-
<SectionWrapper title={streamsMainnetSectionTitle} depth={2}>
404+
<SectionWrapper
405+
title={streamsMainnetSectionTitle}
406+
depth={2}
407+
idOverride={streamsMainnetSectionTitle.toLowerCase().replace(/\s+/g, "-")}
408+
>
308409
<div className={feedList.tableFilters}>
309410
<form class={feedList.filterDropdown_search}>
310411
<input
@@ -363,7 +464,11 @@ export const FeedList = ({
363464
)}
364465
</SectionWrapper>
365466

366-
<SectionWrapper title={streamsTestnetSectionTitle} depth={2}>
467+
<SectionWrapper
468+
title={streamsTestnetSectionTitle}
469+
depth={2}
470+
idOverride={streamsTestnetSectionTitle.toLowerCase().replace(/\s+/g, "-")}
471+
>
367472
<div className={feedList.tableFilters}>
368473
<form class={feedList.filterDropdown_search}>
369474
<input
@@ -493,7 +598,12 @@ export const FeedList = ({
493598
.map((network: ChainNetwork) => {
494599
return (
495600
<>
496-
<SectionWrapper title={network.name} depth={3} key={network.name}>
601+
<SectionWrapper
602+
title={network.name}
603+
depth={3}
604+
key={network.name}
605+
idOverride={network.name.toLowerCase().replace(/\s+/g, "-")}
606+
>
497607
{network.networkType === "mainnet" ? (
498608
<>
499609
{!isStreams && chain.l2SequencerFeed && (
@@ -780,7 +890,14 @@ export const FeedList = ({
780890
{!isDeprecating &&
781891
chainMetadata.processedData?.testnetProcessedData &&
782892
chainMetadata.processedData?.testnetProcessedData.length > 0 && (
783-
<SectionWrapper title={isStreams ? streamsTestnetSectionTitle : "Testnet Feeds"} depth={2} updateTOC={true}>
893+
<SectionWrapper
894+
title={isStreams ? streamsTestnetSectionTitle : "Testnet Feeds"}
895+
depth={2}
896+
updateTOC={true}
897+
idOverride={
898+
chainMetadata.processedData.testnetNetwork?.name?.toLowerCase().replace(/\s+/g, "-") || "testnet-feeds"
899+
}
900+
>
784901
<div className={feedList.tableFilters}>
785902
<form class={feedList.filterDropdown_search}>
786903
<input

0 commit comments

Comments
 (0)