From f1d658bcb6be95530b128a5324c52d2ac76a08fa Mon Sep 17 00:00:00 2001 From: Prajwal Dhule Date: Tue, 6 May 2025 18:04:52 +0530 Subject: [PATCH] Fix: Twenty-website docs same TOC ids #11865 (#11872) This PR fixes issue https://github.com/twentyhq/twenty/issues/11865. The highlight heading logic in TOC was checking the heading text which could be the same for multiple headings. The ids for these headings were also just the heading texts, leading to conflict in ids too. Fix: - Appended index of the heading item from the list of headings to the id of the heading. This fixed conflicting ids. - Used these unique ids to toggle the highlight style. Behaviour after the fix: https://github.com/user-attachments/assets/ab3bc205-0b0e-451d-b9cb-4fa852263efc Edit: close #11865 --------- Co-authored-by: prastoin --- .../app/(public)/user-guide/hooks/useHeadsObserver.tsx | 6 +++--- .../src/app/_components/docs/TableContent.tsx | 9 ++++----- .../src/shared-utils/wrapHeadingsWithAnchor.tsx | 10 ++++++++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/twenty-website/src/app/(public)/user-guide/hooks/useHeadsObserver.tsx b/packages/twenty-website/src/app/(public)/user-guide/hooks/useHeadsObserver.tsx index 24ee7ae26..88cbd145b 100644 --- a/packages/twenty-website/src/app/(public)/user-guide/hooks/useHeadsObserver.tsx +++ b/packages/twenty-website/src/app/(public)/user-guide/hooks/useHeadsObserver.tsx @@ -1,13 +1,13 @@ import { useEffect, useRef, useState } from 'react'; export function useHeadsObserver(location: string) { - const [activeText, setActiveText] = useState(''); + const [activeId, setActiveId] = useState(''); const observer = useRef(null); useEffect(() => { const handleObsever = (entries: any[]) => { entries.forEach((entry) => { if (entry?.isIntersecting) { - setActiveText(entry.target.innerText); + setActiveId(entry.target.id); } }); }; @@ -21,5 +21,5 @@ export function useHeadsObserver(location: string) { return () => observer.current?.disconnect(); }, [location]); - return { activeText }; + return { activeId }; } diff --git a/packages/twenty-website/src/app/_components/docs/TableContent.tsx b/packages/twenty-website/src/app/_components/docs/TableContent.tsx index 169852a3d..f23880714 100644 --- a/packages/twenty-website/src/app/_components/docs/TableContent.tsx +++ b/packages/twenty-website/src/app/_components/docs/TableContent.tsx @@ -92,7 +92,7 @@ interface HeadingType { const DocsTableContents = () => { const [headings, setHeadings] = useState([]); const pathname = usePathname(); - const { activeText } = useHeadsObserver(pathname); + const { activeId } = useHeadsObserver(pathname); useEffect(() => { const nodes: HTMLElement[] = Array.from( @@ -126,11 +126,11 @@ const DocsTableContents = () => { {headings.map((heading) => ( { e.preventDefault(); const yOffset = -70; @@ -142,8 +142,7 @@ const DocsTableContents = () => { window.scrollTo({ top: y, behavior: 'smooth' }); }} style={{ - fontWeight: - activeText === heading.text ? 'bold' : 'normal', + fontWeight: activeId === heading.id ? 'bold' : 'normal', }} > {heading.text} diff --git a/packages/twenty-website/src/shared-utils/wrapHeadingsWithAnchor.tsx b/packages/twenty-website/src/shared-utils/wrapHeadingsWithAnchor.tsx index a80a9277e..0e6a15543 100644 --- a/packages/twenty-website/src/shared-utils/wrapHeadingsWithAnchor.tsx +++ b/packages/twenty-website/src/shared-utils/wrapHeadingsWithAnchor.tsx @@ -1,4 +1,4 @@ -import React, { +import { Children, cloneElement, isValidElement, @@ -12,6 +12,7 @@ export const wrapHeadingsWithAnchor = (children: ReactNode): ReactNode => { ): element is ReactElement<{ children: ReactNode }> => { return element.props.children !== undefined; }; + const idCounts = new Map(); return Children.map(children, (child) => { if ( @@ -19,10 +20,15 @@ export const wrapHeadingsWithAnchor = (children: ReactNode): ReactNode => { typeof child.type === 'string' && ['h1', 'h2', 'h3', 'h4'].includes(child.type) ) { - const id = child.props.children + const baseId = child.props.children .toString() .replace(/\s+/g, '-') .toLowerCase(); + const idCount = idCounts.get(baseId) ?? 0; + + const id = idCount === 0 ? baseId : `${baseId}-${idCount}`; + idCounts.set(baseId, idCount + 1); + return cloneElement(child as ReactElement, { id, className: 'anchor',