Initial FAQ setup with structured data

structured data

auto expand working with url

better url handling for preexpand

support multiple but not quite

real multi support
This commit is contained in:
thomas.ekstrand 2024-02-12 09:15:04 -06:00
parent e674eba6c1
commit 1a5a0c7ded
4 changed files with 178 additions and 23 deletions

View file

@ -5,39 +5,51 @@ slug: /faq
sidebar_position: 3
---
## Overview
import { FaqAccordion } from "/src/components/FaqAccordion";
### Where can I get additional help, ask questions, or bond with the Meshtastic community?
This site (which has a great search function) is the preferred place for up-to-date documentation. Many of our users and developers hang out on the [Meshtastic Discord](https://discord.gg/ktMAKGBnBs) server where you may connect with like-minded people.
### How can I contribute to Meshtastic?
Everyone contributes in a different way. Join the [Meshtastic Discord](https://discord.gg/ktMAKGBnBs) and introduce yourself. We're all very friendly. If you'd like to pitch in some code, check out the [Development](/docs/developers) menu on the left.
<!-- Android Client-->
## Android Client
### What versions of Android does the Meshtastic Android App require?
Minimum requirement is Android 5 (Lollipop 2014, first BLE support), however at least Android 6 (Marshmallow 2015) is recommended as Bluetooth is more stable. While Android 5/6 are officially supported by Meshtastic, it is _not_ recommended that you purchase devices with these versions due to their limited OS support and limited battery life due to age. Many newer models exist that are very affordable. A good resource to use when researching affordable devices is the [LineageOS Supported Devices List](https://wiki.lineageos.org/devices/).
### What does the icon next to the message mean?
export const GeneralFaq = [
{
title: "Where can I get additional help, ask questions, or bond with the Meshtastic community?",
content: `This site (which has a great search function) is the preferred place for up-to-date documentation. Many of our users and developers hang out on the [Meshtastic Discord](https://discord.gg/ktMAKGBnBs) server where you may connect with like-minded people.`,
},
{
title: "How can I contribute to Meshtastic?",
content: "Everyone contributes in a different way. Join the [Meshtastic Discord](https://discord.gg/ktMAKGBnBs) and introduce yourself. We're all very friendly. If you'd like to pitch in some code, check out the [Development](/docs/developers) menu on the left.",
},
];
export const AndroidFaq = [
{
title: "What versions of Android does the Meshtastic Android App require?",
content: `Minimum requirement is Android 5 (Lollipop 2014, first BLE support), however at least Android 6 (Marshmallow 2015) is recommended as Bluetooth is more stable. While Android 5/6 are officially supported by Meshtastic, it is _not_ recommended that you purchase devices with these versions due to their limited OS support and limited battery life due to age. Many newer models exist that are very affordable. A good resource to use when researching affordable devices is the [LineageOS Supported Devices List](https://wiki.lineageos.org/devices/).`,
},
{
title: "What does the icon next to the message mean?",
content: `
- Cloud with an up arrow - Queued on the app to be sent to your device.
- Cloud only - Queued on the device to be sent over the mesh.
- Cloud with a check mark - At least one other node on the mesh acknowledged the message.
- Person with a check mark - The intended recipient of your direct message acknowledged the message.
- Cloud crossed out - Not acknowledged or message error.
- Cloud crossed out - Not acknowledged or message error.`,
},
{
title: "How can I clear the message history?",
content: `Long press any message to select and show the menu with "delete" and "select all" buttons.`,
},
{
title: "After a fresh firmware install, my node is not connecting via Bluetooth. What should I do?",
content: `Try forgetting the Bluetooth connection from the Android Bluetooth Settings menu. Re-pair and try again. This is a security measure and there is no workaround for it. It prevents apps and other accessories from spoofing an existing accessory by un-pairing and "re-pairing" themselves without the users' knowledge.`,
},
];
### How can I clear the message history?
## Overview
Long press any message to select and show the menu with "delete" and "select all" buttons.
<FaqAccordion rows={GeneralFaq} slug="general" />
### After a fresh firmware install, my node is not connecting via Bluetooth. What should I do?
<!-- Android Client-->
Try forgetting the Bluetooth connection from the Android Bluetooth Settings menu. Re-pair and try again. This is a security measure and there is no workaround for it. It prevents apps and other accessories from spoofing an existing accessory by un-pairing and "re-pairing" themselves without the users' knowledge.
## Android Client
<FaqAccordion rows={AndroidFaq} slug="android" />
<!-- Apple Clients-->

View file

@ -27,8 +27,10 @@
"dotenv": "^16.3.1",
"postcss": "^8.4.33",
"react": "^18.2.0",
"react-accessible-accordion": "^5.0.0",
"react-dom": "^18.2.0",
"react-icons": "^4.12.0",
"react-markdown": "^9.0.1",
"remark-deflist": "^1.0.0",
"swr": "^2.2.4",
"tailwindcss": "^3.4.1"

View file

@ -50,12 +50,18 @@ dependencies:
react:
specifier: ^18.2.0
version: 18.2.0
react-accessible-accordion:
specifier: ^5.0.0
version: 5.0.0(react-dom@18.2.0)(react@18.2.0)
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
react-icons:
specifier: ^4.12.0
version: 4.12.0(react@18.2.0)
react-markdown:
specifier: ^9.0.1
version: 9.0.1(@types/react@18.2.47)(react@18.2.0)
remark-deflist:
specifier: ^1.0.0
version: 1.0.0
@ -5793,6 +5799,10 @@ packages:
engines: {node: '>=8'}
dev: false
/html-url-attributes@3.0.0:
resolution: {integrity: sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==}
dev: false
/html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
dev: false
@ -8413,6 +8423,16 @@ packages:
strip-json-comments: 2.0.1
dev: false
/react-accessible-accordion@5.0.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-MT2obYpTgLIIfPr9d7hEyvPB5rg8uJcHpgA83JSRlEUHvzH48+8HJPvzSs+nM+XprTugDgLfhozO5qyJpBvYRQ==}
peerDependencies:
react: ^16.3.2 || ^17.0.0 || ^18.0.0
react-dom: ^16.3.3 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/react-dev-utils@12.0.1(typescript@5.3.3)(webpack@5.89.0):
resolution: {integrity: sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==}
engines: {node: '>=14'}
@ -8529,6 +8549,28 @@ packages:
webpack: 5.89.0
dev: false
/react-markdown@9.0.1(@types/react@18.2.47)(react@18.2.0):
resolution: {integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==}
peerDependencies:
'@types/react': '>=18'
react: '>=18'
dependencies:
'@types/hast': 3.0.3
'@types/react': 18.2.47
devlop: 1.1.0
hast-util-to-jsx-runtime: 2.3.0
html-url-attributes: 3.0.0
mdast-util-to-hast: 13.0.2
react: 18.2.0
remark-parse: 11.0.0
remark-rehype: 11.0.0
unified: 11.0.4
unist-util-visit: 5.0.0
vfile: 6.0.1
transitivePeerDependencies:
- supports-color
dev: false
/react-router-config@5.1.1(react-router@5.3.4)(react@18.2.0):
resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==}
peerDependencies:

View file

@ -0,0 +1,99 @@
import React from "react";
import ReactMarkdown from "react-markdown";
import {
Accordion,
AccordionItem,
AccordionItemHeading,
AccordionItemButton,
AccordionItemPanel,
} from "react-accessible-accordion";
import "react-accessible-accordion/dist/fancy-example.css";
export interface Faq {
title: string;
content: string;
}
/**
* Gets the query parameter `openFaqItems` which is an array of
* faq items that should be pre-opened
* @type {Function}
*/
const getOpenFaqItemsFromUrl = (slug: String): string[] => {
// Use URLSearchParams to parse the query parameters from the current URL
const searchParams = new URLSearchParams(window.location.search);
// 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(',') : [];
};
export const FaqAccordion = ({
rows,
slug
}: { rows: Faq[], slug: String }): JSX.Element => {
// Set the faq structured data
const faqStructuredData = {
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: rows.map((row) => ({
"@type": "Question",
name: row.title,
acceptedAnswer: {
"@type": "Answer",
text: row.content,
},
})),
};
// Use the getOpenFaqItemsFromUrl function to set the
// initial state of preExpanded based on URL parameters
const [preExpanded, setPreExpanded] = React.useState<string[]>(getOpenFaqItemsFromUrl(slug));
/**
* Updates query parameters in the url when items are opened
* so that a link can be shared with the faq item already opened
*/
const handleChange = (openFaqItems: (string | number)[]): void => {
// Get current url params
const searchParams = new URLSearchParams(window.location.search);
// Convert openFaqItems to a comma-separated string and update/add the parameter
searchParams.set(`openFaqItems-${slug}`, openFaqItems.map(String).join(','));
// Construct the new URL, preserve existing parameters
const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?${searchParams.toString()}`;
// Use history.pushState to change the URL without reloading the page
window.history.pushState({ path: newUrl }, '', newUrl);
};
return (
<>
<script
type="application/ld+json"
// biome-ignore lint: we need dangerouslySetInnerHTML here, and since we're the ones setting the content it's should be safe
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqStructuredData) }}
/>
<Accordion
allowMultipleExpanded={true}
allowZeroExpanded={true}
onChange={handleChange}
preExpanded={preExpanded}
>
{rows.map((row, index) => (
<AccordionItem key={index}>
<AccordionItemHeading aria-level="2">
<AccordionItemButton>{row.title}</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>
<ReactMarkdown>{row.content}</ReactMarkdown>
</AccordionItemPanel>
</AccordionItem>
))}
</Accordion>
</>
);
};