mirror of
https://github.com/meshtastic/meshtastic.git
synced 2024-12-25 21:54:20 -08:00
FAQ accordion open sets nearest heading hash in url
This commit is contained in:
parent
68ceb52407
commit
bbd6830966
|
@ -190,38 +190,38 @@ If you use your ham radio license with Meshtastic, consider both the privileges
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
<FaqAccordion rows={Faq.general} slug="general" />
|
<FaqAccordion rows={Faq.general} />
|
||||||
|
|
||||||
## Android Client
|
## Android Client
|
||||||
|
|
||||||
<FaqAccordion rows={Faq.android} slug="android" />
|
<FaqAccordion rows={Faq.android} />
|
||||||
|
|
||||||
## Apple Clients
|
## Apple Clients
|
||||||
|
|
||||||
<FaqAccordion rows={Faq.apple} slug="apple" />
|
<FaqAccordion rows={Faq.apple} />
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
<FaqAccordion rows={Faq.channels} slug="channels" />
|
<FaqAccordion rows={Faq.channels} />
|
||||||
|
|
||||||
## Python CLI
|
## Python CLI
|
||||||
|
|
||||||
<FaqAccordion rows={Faq.python} slug="python" />
|
<FaqAccordion rows={Faq.python} />
|
||||||
|
|
||||||
## Devices
|
## Devices
|
||||||
|
|
||||||
<FaqAccordion rows={Faq.devices} slug="devices" />
|
<FaqAccordion rows={Faq.devices} />
|
||||||
|
|
||||||
## Amateur Radio (ham)
|
## Amateur Radio (ham)
|
||||||
|
|
||||||
Meshtastic can be used by both unlicensed people and licensed HAM operators.
|
Meshtastic can be used by both unlicensed people and licensed HAM operators.
|
||||||
|
|
||||||
<FaqAccordion rows={Faq.ham} slug="ham" />
|
<FaqAccordion rows={Faq.ham} />
|
||||||
|
|
||||||
## Mesh
|
## Mesh
|
||||||
|
|
||||||
<FaqAccordion rows={Faq.mesh} slug="mesh" />
|
<FaqAccordion rows={Faq.mesh} />
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
<FaqAccordion rows={Faq.modules} slug="modules" />
|
<FaqAccordion rows={Faq.modules} />
|
||||||
|
|
|
@ -16,83 +16,69 @@ export interface Faq {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the query parameter `openFaqItems` which is an array of
|
* Finds the nearest heading to an element
|
||||||
* faq items that should be pre-opened
|
* @param {Element} null The element to find the nearest heading to
|
||||||
* @type {Function}
|
* @return {Element|null} The heading or null
|
||||||
*/
|
*/
|
||||||
const getOpenFaqItemsFromUrl = (slug: string): string[] => {
|
const findNearestHeading = (element: Element): Element | null => {
|
||||||
if (typeof window !== "undefined") {
|
const isHeading = (element: Element): boolean => /^H[1-6]$/.test(element.tagName);
|
||||||
// Use URLSearchParams to parse the query parameters from the current URL
|
let currentElement: Element | null = element;
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
// Get the 'openFaqItems' parameter as a comma-separated string
|
while (currentElement !== null) {
|
||||||
const openFaqItemsString = searchParams.get(`openFaqItems-${slug}`);
|
// 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
|
// If no heading is found among siblings, move to the parent node
|
||||||
return openFaqItemsString ? openFaqItemsString.split(",") : [];
|
currentElement = currentElement.parentElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates query parameters in the url when items are opened
|
* Takes in uuids from react-accessible-accordion onchange event
|
||||||
* so that a link can be shared with the faq item already opened
|
* and updates the browser url with the nearest heading's id
|
||||||
|
* @param {[type]} void [description]
|
||||||
|
* @return {[type]} [description]
|
||||||
*/
|
*/
|
||||||
const handleChange = (
|
const updateURLWithNearestHeadingId = (targetElementUuid: string): void => {
|
||||||
openFaqItems: (string | number)[],
|
const targetElement: HTMLElement | null = document.getElementById(`accordion__heading-${targetElementUuid}`);
|
||||||
slug: string,
|
|
||||||
): void => {
|
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
if (openFaqItems.length > 0) {
|
const nearestHeading: Element | null = targetElement ? findNearestHeading(targetElement) : null;
|
||||||
// Create comma-separated string and update/add the parameter
|
|
||||||
searchParams.set(
|
if (nearestHeading && nearestHeading.id) {
|
||||||
`openFaqItems-${slug}`,
|
window.history.pushState({}, '', `#${nearestHeading.id}`);
|
||||||
openFaqItems.map(String).join(","),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// If openFaqItems is empty, remove the parameter from the URL
|
|
||||||
searchParams.delete(`openFaqItems-${slug}`);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 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 = ({
|
export const FaqAccordion = ({
|
||||||
rows,
|
rows,
|
||||||
slug,
|
}: { rows: Faq[] }): JSX.Element => {
|
||||||
}: { rows: Faq[]; slug: string }): JSX.Element => {
|
|
||||||
return (
|
return (
|
||||||
<BrowserOnly fallback={<div>Loading FAQ's...</div>}>
|
<Accordion
|
||||||
{() => {
|
allowMultipleExpanded={true}
|
||||||
return (
|
allowZeroExpanded={true}
|
||||||
<Accordion
|
onChange={(itemUuids) => {
|
||||||
allowMultipleExpanded={true}
|
updateURLWithNearestHeadingId(itemUuids);
|
||||||
allowZeroExpanded={true}
|
|
||||||
onChange={(itemUuids) => {
|
|
||||||
handleChange(itemUuids, slug);
|
|
||||||
}}
|
|
||||||
preExpanded={getOpenFaqItemsFromUrl(slug)}
|
|
||||||
>
|
|
||||||
{rows.map((row, index) => (
|
|
||||||
// biome-ignore lint/suspicious/noArrayIndexKey: React complains if there is no key
|
|
||||||
<AccordionItem key={index}>
|
|
||||||
<AccordionItemHeading aria-level="3">
|
|
||||||
<AccordionItemButton>{row.title}</AccordionItemButton>
|
|
||||||
</AccordionItemHeading>
|
|
||||||
<AccordionItemPanel>
|
|
||||||
<ReactMarkdown>{row.content}</ReactMarkdown>
|
|
||||||
</AccordionItemPanel>
|
|
||||||
</AccordionItem>
|
|
||||||
))}
|
|
||||||
</Accordion>
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
</BrowserOnly>
|
>
|
||||||
|
{rows.map((row, index) => (
|
||||||
|
// biome-ignore lint/suspicious/noArrayIndexKey: React complains if there is no key
|
||||||
|
<AccordionItem key={index}>
|
||||||
|
<AccordionItemHeading aria-level="3">
|
||||||
|
<AccordionItemButton>{row.title}</AccordionItemButton>
|
||||||
|
</AccordionItemHeading>
|
||||||
|
<AccordionItemPanel>
|
||||||
|
<ReactMarkdown>{row.content}</ReactMarkdown>
|
||||||
|
</AccordionItemPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue