From bbd68309668cd51405c36ef3ed2263d03a90eada Mon Sep 17 00:00:00 2001 From: "thomas.ekstrand" Date: Sat, 24 Feb 2024 09:21:42 -0600 Subject: [PATCH 1/5] FAQ accordion open sets nearest heading hash in url --- docs/about/faq.mdx | 18 ++--- src/components/FaqAccordion.tsx | 112 ++++++++++++++------------------ 2 files changed, 58 insertions(+), 72 deletions(-) diff --git a/docs/about/faq.mdx b/docs/about/faq.mdx index dffcad3b..19e8ec6b 100644 --- a/docs/about/faq.mdx +++ b/docs/about/faq.mdx @@ -190,38 +190,38 @@ If you use your ham radio license with Meshtastic, consider both the privileges ## Overview - + ## Android Client - + ## Apple Clients - + ## Channels - + ## Python CLI - + ## Devices - + ## Amateur Radio (ham) Meshtastic can be used by both unlicensed people and licensed HAM operators. - + ## Mesh - + ## Modules - + diff --git a/src/components/FaqAccordion.tsx b/src/components/FaqAccordion.tsx index 08455534..4a9a3993 100644 --- a/src/components/FaqAccordion.tsx +++ b/src/components/FaqAccordion.tsx @@ -16,83 +16,69 @@ export interface Faq { } /** - * Gets the query parameter `openFaqItems` which is an array of - * faq items that should be pre-opened - * @type {Function} + * Finds the nearest heading to an element + * @param {Element} null The element to find the nearest heading to + * @return {Element|null} The heading or null */ -const getOpenFaqItemsFromUrl = (slug: string): string[] => { - if (typeof window !== "undefined") { - // Use URLSearchParams to parse the query parameters from the current URL - const searchParams = new URLSearchParams(window.location.search); +const findNearestHeading = (element: Element): Element | null => { + const isHeading = (element: Element): boolean => /^H[1-6]$/.test(element.tagName); + let currentElement: Element | null = element; - // Get the 'openFaqItems' parameter as a comma-separated string - const openFaqItemsString = searchParams.get(`openFaqItems-${slug}`); + while (currentElement !== null) { + // Check previous siblings + let prevSibling: Element | null = currentElement.previousElementSibling; + while (prevSibling) { + if (isHeading(prevSibling)) { + return prevSibling; + } + prevSibling = prevSibling.previousElementSibling; + } - // If the parameter exists, split it by commas into an array; otherwise, return an empty array - return openFaqItemsString ? openFaqItemsString.split(",") : []; + // If no heading is found among siblings, move to the parent node + currentElement = currentElement.parentElement; } + + return null; }; /** - * Updates query parameters in the url when items are opened - * so that a link can be shared with the faq item already opened + * Takes in uuids from react-accessible-accordion onchange event + * and updates the browser url with the nearest heading's id + * @param {[type]} void [description] + * @return {[type]} [description] */ -const handleChange = ( - openFaqItems: (string | number)[], - slug: string, -): void => { - const searchParams = new URLSearchParams(window.location.search); +const updateURLWithNearestHeadingId = (targetElementUuid: string): void => { + const targetElement: HTMLElement | null = document.getElementById(`accordion__heading-${targetElementUuid}`); - if (openFaqItems.length > 0) { - // Create comma-separated string and update/add the parameter - searchParams.set( - `openFaqItems-${slug}`, - openFaqItems.map(String).join(","), - ); - } else { - // If openFaqItems is empty, remove the parameter from the URL - searchParams.delete(`openFaqItems-${slug}`); + const nearestHeading: Element | null = targetElement ? findNearestHeading(targetElement) : null; + + if (nearestHeading && nearestHeading.id) { + window.history.pushState({}, '', `#${nearestHeading.id}`); } - - // Construct the new URL, preserve existing parameters - const newUrl = `${window.location.protocol}//${window.location.host}${ - window.location.pathname - }?${searchParams.toString()}`; - - // Change the URL without reloading the page - window.history.pushState({ path: newUrl }, "", newUrl); -}; +} export const FaqAccordion = ({ rows, - slug, -}: { rows: Faq[]; slug: string }): JSX.Element => { +}: { rows: Faq[] }): JSX.Element => { return ( - Loading FAQ's...}> - {() => { - return ( - { - handleChange(itemUuids, slug); - }} - preExpanded={getOpenFaqItemsFromUrl(slug)} - > - {rows.map((row, index) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: React complains if there is no key - - - {row.title} - - - {row.content} - - - ))} - - ); + { + updateURLWithNearestHeadingId(itemUuids); }} - + > + {rows.map((row, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: React complains if there is no key + + + {row.title} + + + {row.content} + + + ))} + ); }; From 97e9485040c2f8601c3055228e13c26da847ffc3 Mon Sep 17 00:00:00 2001 From: "thomas.ekstrand" Date: Sat, 24 Feb 2024 09:27:24 -0600 Subject: [PATCH 2/5] format, lint, remove hash if all closed --- src/components/FaqAccordion.tsx | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/components/FaqAccordion.tsx b/src/components/FaqAccordion.tsx index 4a9a3993..2eb9670d 100644 --- a/src/components/FaqAccordion.tsx +++ b/src/components/FaqAccordion.tsx @@ -1,4 +1,3 @@ -import BrowserOnly from "@docusaurus/BrowserOnly"; import { Accordion, AccordionItem, @@ -21,7 +20,8 @@ export interface Faq { * @return {Element|null} The heading or null */ const findNearestHeading = (element: Element): Element | null => { - const isHeading = (element: Element): boolean => /^H[1-6]$/.test(element.tagName); + const isHeading = (element: Element): boolean => + /^H[1-6]$/.test(element.tagName); let currentElement: Element | null = element; while (currentElement !== null) { @@ -47,25 +47,33 @@ const findNearestHeading = (element: Element): Element | null => { * @param {[type]} void [description] * @return {[type]} [description] */ -const updateURLWithNearestHeadingId = (targetElementUuid: string): void => { - const targetElement: HTMLElement | null = document.getElementById(`accordion__heading-${targetElementUuid}`); +const updateUrlWithNearestHeadingId = (targetElementUuid: string): void => { + const targetElement: HTMLElement | null = document.getElementById( + `accordion__heading-${targetElementUuid}`, + ); - const nearestHeading: Element | null = targetElement ? findNearestHeading(targetElement) : null; + const nearestHeading: Element | null = targetElement + ? findNearestHeading(targetElement) + : null; - if (nearestHeading && nearestHeading.id) { - window.history.pushState({}, '', `#${nearestHeading.id}`); + // Add the hash without scrolling the page + if (nearestHeading?.id) { + window.history.pushState({}, "", `#${nearestHeading.id}`); } -} -export const FaqAccordion = ({ - rows, -}: { rows: Faq[] }): JSX.Element => { + // If they're all collapsed, remove the hash + if (!targetElement) { + window.location.hash = ''; + } +}; + +export const FaqAccordion = ({ rows }: { rows: Faq[] }): JSX.Element => { return ( { - updateURLWithNearestHeadingId(itemUuids); + updateUrlWithNearestHeadingId(itemUuids); }} > {rows.map((row, index) => ( From bed46b5c79db39f4acba99cb1d561b486040002e Mon Sep 17 00:00:00 2001 From: "thomas.ekstrand" Date: Sat, 24 Feb 2024 09:31:48 -0600 Subject: [PATCH 3/5] linting and fix bug when multiple accordions open in single section --- src/components/FaqAccordion.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/FaqAccordion.tsx b/src/components/FaqAccordion.tsx index 2eb9670d..e85d5d26 100644 --- a/src/components/FaqAccordion.tsx +++ b/src/components/FaqAccordion.tsx @@ -49,7 +49,7 @@ const findNearestHeading = (element: Element): Element | null => { */ const updateUrlWithNearestHeadingId = (targetElementUuid: string): void => { const targetElement: HTMLElement | null = document.getElementById( - `accordion__heading-${targetElementUuid}`, + `accordion__heading-${targetElementUuid[0]}`, ); const nearestHeading: Element | null = targetElement @@ -63,7 +63,7 @@ const updateUrlWithNearestHeadingId = (targetElementUuid: string): void => { // If they're all collapsed, remove the hash if (!targetElement) { - window.location.hash = ''; + window.location.hash = ""; } }; From e24ea2283f9f1c19bfdcbab549f392f1a64b861d Mon Sep 17 00:00:00 2001 From: "thomas.ekstrand" Date: Sat, 24 Feb 2024 09:32:42 -0600 Subject: [PATCH 4/5] do not scroll when all collapsed --- src/components/FaqAccordion.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/FaqAccordion.tsx b/src/components/FaqAccordion.tsx index e85d5d26..89c65cc1 100644 --- a/src/components/FaqAccordion.tsx +++ b/src/components/FaqAccordion.tsx @@ -63,7 +63,7 @@ const updateUrlWithNearestHeadingId = (targetElementUuid: string): void => { // If they're all collapsed, remove the hash if (!targetElement) { - window.location.hash = ""; + history.pushState(null, null, window.location.origin + window.location.pathname + window.location.search); } }; From 12281a8d02b0e564cb595a600ee25b097d74b153 Mon Sep 17 00:00:00 2001 From: "thomas.ekstrand" Date: Sat, 24 Feb 2024 09:36:17 -0600 Subject: [PATCH 5/5] formatting fix --- src/components/FaqAccordion.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/FaqAccordion.tsx b/src/components/FaqAccordion.tsx index 89c65cc1..05f12409 100644 --- a/src/components/FaqAccordion.tsx +++ b/src/components/FaqAccordion.tsx @@ -63,7 +63,13 @@ const updateUrlWithNearestHeadingId = (targetElementUuid: string): void => { // If they're all collapsed, remove the hash if (!targetElement) { - history.pushState(null, null, window.location.origin + window.location.pathname + window.location.search); + history.pushState( + null, + null, + window.location.origin + + window.location.pathname + + window.location.search, + ); } };