/* eslint-disable react-hooks/rules-of-hooks */ import React, { Fragment, useMemo, useState } from 'react'; import styled from 'styled-components'; import Fuse from 'fuse.js'; import { Link, useLocation } from 'react-router-dom'; import { useMarkdownTree } from 'react-static-plugin-md-pages'; import { SidebarNavItem, SidebarNavSubItem, SidebarNavSubItemWrapper, SidebarContainer, SidebarWrapper, SideBarStripes, ChevronItem, } from './navigation'; import SidebarSearchInput from './sidebar-search-input'; import logoSidebar from '../assets/sidebar-badge.svg'; const HeroLogoLink = styled(Link)` display: none; flex-direction: row; justify-content: center; margin-bottom: ${p => p.theme.spacing.sm}; align-self: center; @media ${p => p.theme.media.sm} { display: flex; } `; const HeroLogo = styled.img.attrs(() => ({ src: logoSidebar, alt: 'urql', }))` width: ${p => p.theme.layout.logo}; height: ${p => p.theme.layout.logo}; `; const ContentWrapper = styled.div` display: flex; flex-direction: column; padding-bottom: ${p => p.theme.spacing.lg}; `; export const SidebarStyling = ({ children, sidebarOpen }) => ( <> ); const getMatchTree = (() => { const sortByRefIndex = (a, b) => a.refIndex - b.refIndex; const options = { distance: 100, findAllMatches: true, includeMatches: true, keys: [ 'frontmatter.title', `children.frontmatter.title`, 'children.headings.value', ], threshold: 0.2, }; return (children, pattern) => { // Filter any nested heading with a depth greater than 2 const childrenMaxH3 = children.map(child => ({ ...child, children: child.children && child.children.map(child => ({ ...child, headings: child.headings.filter(heading => heading.depth == 2), })), })); const fuse = new Fuse(childrenMaxH3, options); let matches = fuse.search(pattern); // For every matching section, include only matching headers return matches .reduce((matches, match) => { const matchesMap = new Map(); match.matches.forEach(individualMatch => { matchesMap.set(individualMatch.value, { indices: individualMatch.indices, }); }); // Add the top level heading but don't add subheadings unless they match const currentItem = { ...match.item, matchedIndices: match.indices, refIndex: match.refIndex, }; // For every child of the currently matched section, add all appplicable // H2 and H3 headers plus their indices if (currentItem.children) { currentItem.children = currentItem.children.reduce( (children, child) => { const newChild = { ...child }; newChild.headings = newChild.headings.reduce( (headings, header) => { const match = matchesMap.get(header.value); if (match) { headings.push({ ...header, matchedIndices: match.indices, }); } return headings; }, [] ); const match = matchesMap.get(newChild.frontmatter.title); if (match) { newChild.matchedIndices = match.indices; } if (match || newChild.headings.length > 0) { children.push(newChild); } return children; }, [] ); } return [...matches, currentItem]; }, []) .sort(sortByRefIndex); }; })(); // Wrap matching substrings in const highlightText = (text, indices) => ( <> {indices.map(([startIndex, endIndex], i) => { const isLastIndex = !indices[i + 1]; const prevEndIndex = indices[i - 1] ? indices[i - 1][1] : -1; return ( <> {startIndex != 0 ? text.slice(prevEndIndex + 1, startIndex) : ''} {text.slice(startIndex, endIndex + 1)} {isLastIndex && endIndex < text.length ? text.slice(endIndex + 1, text.length) : ''} ); })} ); const Sidebar = ({ closeSidebar, ...props }) => { const [filterTerm, setFilterTerm] = useState(''); const location = useLocation(); const tree = useMarkdownTree(); const sidebarItems = useMemo(() => { let pathname = location.pathname.match(/docs\/?(.+)?/); if (!pathname || !tree || !tree.children || !location) { return null; } pathname = pathname[0]; const trimmedPathname = pathname.replace(/(\/$)|(\/#.+)/, ''); let children = tree.children; if (tree.frontmatter && tree.originalPath) { children = [{ ...tree, children: undefined }, ...children]; } if (filterTerm) { children = getMatchTree(children, filterTerm); } return children.map(page => { const pageChildren = page.children || []; const isActive = pageChildren.length ? trimmedPathname.startsWith(page.path) : !!page.path.match(new RegExp(`${trimmedPathname}$`, 'g')); const showSubItems = !!filterTerm || (pageChildren.length && isActive); return ( isActive} onClick={closeSidebar} > {page.matchedIndices ? highlightText(page.frontmatter.title, page.matchedIndices) : page.frontmatter.title} {pageChildren.length ? : null} {showSubItems ? ( {pageChildren.map(childPage => ( !!childPage.path.match( new RegExp(`${trimmedPathname}$`, 'g') ) } to={`/${childPage.path}/`} > {childPage.matchedIndices ? highlightText( childPage.frontmatter.title, childPage.matchedIndices ) : childPage.frontmatter.title} {/* Only Show H3 items if there is a search applied */} {filterTerm ? childPage.headings.map(heading => ( {highlightText(heading.value, heading.matchedIndices)} )) : null} ))} ) : null} ); }); }, [location, tree, filterTerm, closeSidebar]); return ( setFilterTerm(e.target.value)} value={filterTerm} /> {sidebarItems} ); }; export default Sidebar;