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 <paul@twenty.com>
This commit is contained in:
@ -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<IntersectionObserver | null>(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 };
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ interface HeadingType {
|
||||
const DocsTableContents = () => {
|
||||
const [headings, setHeadings] = useState<HeadingType[]>([]);
|
||||
const pathname = usePathname();
|
||||
const { activeText } = useHeadsObserver(pathname);
|
||||
const { activeId } = useHeadsObserver(pathname);
|
||||
|
||||
useEffect(() => {
|
||||
const nodes: HTMLElement[] = Array.from(
|
||||
@ -126,11 +126,11 @@ const DocsTableContents = () => {
|
||||
<StyledUnorderedList>
|
||||
{headings.map((heading) => (
|
||||
<StyledList
|
||||
key={heading.text}
|
||||
key={heading.id}
|
||||
style={getStyledHeading(heading.level)}
|
||||
>
|
||||
<StyledLink
|
||||
href={`#${heading.text}`}
|
||||
href={`#${heading.id}`}
|
||||
onClick={(e) => {
|
||||
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}
|
||||
|
||||
@ -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<string, number>();
|
||||
|
||||
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<any>, {
|
||||
id,
|
||||
className: 'anchor',
|
||||
|
||||
Reference in New Issue
Block a user