FAQ accordion open sets nearest heading hash in url

This commit is contained in:
thomas.ekstrand 2024-02-24 09:21:42 -06:00
parent 68ceb52407
commit bbd6830966
2 changed files with 58 additions and 72 deletions

View file

@ -190,38 +190,38 @@ If you use your ham radio license with Meshtastic, consider both the privileges
## Overview
<FaqAccordion rows={Faq.general} slug="general" />
<FaqAccordion rows={Faq.general} />
## Android Client
<FaqAccordion rows={Faq.android} slug="android" />
<FaqAccordion rows={Faq.android} />
## Apple Clients
<FaqAccordion rows={Faq.apple} slug="apple" />
<FaqAccordion rows={Faq.apple} />
## Channels
<FaqAccordion rows={Faq.channels} slug="channels" />
<FaqAccordion rows={Faq.channels} />
## Python CLI
<FaqAccordion rows={Faq.python} slug="python" />
<FaqAccordion rows={Faq.python} />
## Devices
<FaqAccordion rows={Faq.devices} slug="devices" />
<FaqAccordion rows={Faq.devices} />
## Amateur Radio (ham)
Meshtastic can be used by both unlicensed people and licensed HAM operators.
<FaqAccordion rows={Faq.ham} slug="ham" />
<FaqAccordion rows={Faq.ham} />
## Mesh
<FaqAccordion rows={Faq.mesh} slug="mesh" />
<FaqAccordion rows={Faq.mesh} />
## Modules
<FaqAccordion rows={Faq.modules} slug="modules" />
<FaqAccordion rows={Faq.modules} />

View file

@ -16,68 +16,57 @@ 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}`);
// If the parameter exists, split it by commas into an array; otherwise, return an empty array
return openFaqItemsString ? openFaqItemsString.split(",") : [];
while (currentElement !== null) {
// Check previous siblings
let prevSibling: Element | null = currentElement.previousElementSibling;
while (prevSibling) {
if (isHeading(prevSibling)) {
return prevSibling;
}
prevSibling = prevSibling.previousElementSibling;
}
// 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 => {
return (
<BrowserOnly fallback={<div>Loading FAQ's...</div>}>
{() => {
}: { rows: Faq[] }): JSX.Element => {
return (
<Accordion
allowMultipleExpanded={true}
allowZeroExpanded={true}
onChange={(itemUuids) => {
handleChange(itemUuids, slug);
updateURLWithNearestHeadingId(itemUuids);
}}
preExpanded={getOpenFaqItemsFromUrl(slug)}
>
{rows.map((row, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: React complains if there is no key
@ -92,7 +81,4 @@ export const FaqAccordion = ({
))}
</Accordion>
);
}}
</BrowserOnly>
);
};