- Add help docs for most sections
- Add translations documentation
- Fix up todos
- Remove german translation
This commit is contained in:
Jamie Curnow
2025-10-27 23:59:00 +10:00
parent 0f718570d6
commit 5d6916dcf0
38 changed files with 686 additions and 115 deletions

View File

@@ -8,14 +8,11 @@
const allLocales = [
["en", "en-US"],
["de", "de-DE"],
["fa", "fa-IR"],
];
const ignoreUnused = [
/^capability\..*$/,
/^status\..*$/,
/^type\..*$/,
/^.*$/,
];
const { spawnSync } = require("child_process");
@@ -119,20 +116,10 @@ const compareLocale = (locale) => {
const checkForMissing = (locale) => {
allKeys.forEach((key) => {
if (typeof locale.data[key] === "undefined") {
let ignored = false;
ignoreMissing.map((regex) => {
if (key.match(regex)) {
ignored = true;
}
return null;
});
if (!ignored) {
allWarnings.push(
"WARN: `" + locale[0] + "` does not contain item: `" + key + "`",
);
}
}
return null;
});
};

View File

@@ -33,6 +33,7 @@
"react-bootstrap": "^2.10.10",
"react-dom": "^19.2.0",
"react-intl": "^7.1.14",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.9.4",
"react-select": "^5.10.2",
"react-toastify": "^11.0.5",

View File

@@ -4,7 +4,6 @@ import type { AccessList } from "./models";
export async function createAccessList(item: AccessList): Promise<AccessList> {
return await api.post({
url: "/nginx/access-lists",
// todo: only use whitelist of fields for this data
data: item,
});
}

View File

@@ -4,7 +4,6 @@ import type { DeadHost } from "./models";
export async function createDeadHost(item: DeadHost): Promise<DeadHost> {
return await api.post({
url: "/nginx/dead-hosts",
// todo: only use whitelist of fields for this data
data: item,
});
}

View File

@@ -4,7 +4,6 @@ import type { ProxyHost } from "./models";
export async function createProxyHost(item: ProxyHost): Promise<ProxyHost> {
return await api.post({
url: "/nginx/proxy-hosts",
// todo: only use whitelist of fields for this data
data: item,
});
}

View File

@@ -4,7 +4,6 @@ import type { RedirectionHost } from "./models";
export async function createRedirectionHost(item: RedirectionHost): Promise<RedirectionHost> {
return await api.post({
url: "/nginx/redirection-hosts",
// todo: only use whitelist of fields for this data
data: item,
});
}

View File

@@ -4,7 +4,6 @@ import type { Stream } from "./models";
export async function createStream(item: Stream): Promise<Stream> {
return await api.post({
url: "/nginx/streams",
// todo: only use whitelist of fields for this data
data: item,
});
}

View File

@@ -18,7 +18,6 @@ export interface NewUser {
export async function createUser(item: NewUser, noAuth?: boolean): Promise<User> {
return await api.post({
url: "/users",
// todo: only use whitelist of fields for this data
data: item,
noAuth,
});

View File

@@ -1,5 +1,4 @@
import { createIntl, createIntlCache } from "react-intl";
import langDe from "./lang/de.json";
import langEn from "./lang/en.json";
import langFa from "./lang/fa.json";
import langList from "./lang/lang-list.json";
@@ -9,15 +8,12 @@ import langList from "./lang/lang-list.json";
// Remember when adding to this list, also update check-locales.js script
const localeOptions = [
["en", "en-US"],
["de", "de-DE"],
["fa", "fa-IR"],
];
const loadMessages = (locale?: string): typeof langList & typeof langEn => {
const thisLocale = locale || "en";
switch (thisLocale.slice(0, 2)) {
case "de":
return Object.assign({}, langList, langEn, langDe);
case "fa":
return Object.assign({}, langList, langEn, langFa);
default:
@@ -27,9 +23,6 @@ const loadMessages = (locale?: string): typeof langList & typeof langEn => {
const getFlagCodeForLocale = (locale?: string) => {
switch (locale) {
case "de-DE":
case "de":
return "DE";
case "fa-IR":
case "fa":
return "IR";

View File

@@ -1,23 +1,48 @@
# Internationalisation support
## Before you start
It's highly recommended that you spin up a development instance of this project
on your docker capable server. It's pretty easy:
```bash
git clone https://github.com/NginxProxyManager/nginx-proxy-manager.git
cd nginx-proxy-manager
./scripts/start-dev -f
```
Then after a while, you can access http://yourserverip:3081
This stack will watch the file system for changes, especially to language files,
and reload the site you have open in the browser.
## Adding new translations
Modify the files in the `src` folder. Follow the conventions already there.
When the development stack is running, it will sort the locale lang files
for you when you save.
## After making changes
You will need to run `yarn locale-compile` in this frontend folder for
If you're NOT running the development stack, you will need to run
`yarn locale-compile` in the `frontend` folder for
the new translations to be compiled into the `lang` folder.
When running in dev mode, this should automatically happen within Vite.
## Adding a whole new language
There's a fair bit you'll need to touch. Here's a list that may
not be complete by the time you're reading this:
- frontend/src/locale/src/[yourlang].json
- frontend/src/locale/src/lang-list.json
- frontend/src/locale/src/HelpDoc/*
- frontend/src/locale/IntlProvider.tsx
## Checking for missing translations in other languages
## Checking for missing translations in languages
Run `node check-locales.cjs` in this frontend folder.
## Adding new languages
todo

View File

@@ -1,3 +0,0 @@
{
"dashboard": "Armaturenbrett"
}

View File

@@ -100,7 +100,11 @@
"error.invalid-auth": "Invalid email or password",
"error.invalid-domain": "Invalid domain: {domain}",
"error.invalid-email": "Invalid email address",
"error.max-character-length": "Maximum length is {max} character{max, plural, one {} other {s}}",
"error.max-domains": "Too many domains, max is {max}",
"error.maximum": "Maximum is {max}",
"error.min-character-length": "Minimum length is {min} character{min, plural, one {} other {s}}",
"error.minimum": "Minimum is {min}",
"error.passwords-must-match": "Passwords must match",
"error.required": "This is required",
"expires.on": "Expires: {date}",

View File

@@ -0,0 +1,7 @@
## What is an Access List?
Access Lists provide a blacklist or whitelist of specific client IP addresses along with authentication for the Proxy Hosts via Basic HTTP Authentication.
You can configure multiple client rules, usernames and passwords for a single Access List and then apply that to one or more _Proxy Hosts_.
This is most useful for forwarded web services that do not have authentication mechanisms built in or when you want to protect from unknown clients.

View File

@@ -0,0 +1,32 @@
## Certificates Help
### HTTP Certificate
A HTTP validated certificate means Let's Encrypt servers will
attempt to reach your domains over HTTP (not HTTPS!) and if successful, they
will issue your certificate.
For this method, you will have to have a _Proxy Host_ created for your domains(s) that
is accessible with HTTP and pointing to this Nginx installation. After a certificate
has been given, you can modify the _Proxy Host_ to also use this certificate for HTTPS
connections. However, the _Proxy Host_ will still need to be configured for HTTP access
in order for the certificate to renew.
This process _does not_ support wildcard domains.
### DNS Certificate
A DNS validated certificate requires you to use a DNS Provider plugin. This DNS
Provider will be used to create temporary records on your domain and then Let's
Encrypt will query those records to be sure you're the owner and if successful, they
will issue your certificate.
You do not need a _Proxy Host_ to be created prior to requesting this type of
certificate. Nor do you need to have your _Proxy Host_ configured for HTTP access.
This process _does_ support wildcard domains.
### Custom Certificate
Use this option to upload your own SSL Certificate, as provided by your own
Certificate Authority.

View File

@@ -0,0 +1,10 @@
## What is a 404 Host?
A 404 Host is simply a host setup that shows a 404 page.
This can be useful when your domain is listed in search engines and you want
to provide a nicer error page or specifically to tell the search indexers that
the domain pages no longer exist.
Another benefit of having this host is to track the logs for hits to it and
view the referrers.

View File

@@ -0,0 +1,7 @@
## What is a Proxy Host?
A Proxy Host is the incoming endpoint for a web service that you want to forward.
It provides optional SSL termination for your service that might not have SSL support built in.
Proxy Hosts are the most common use for the Nginx Proxy Manager.

View File

@@ -0,0 +1,7 @@
## What is a Redirection Host?
A Redirection Host will redirect requests from the incoming domain and push the
viewer to another domain.
The most common reason to use this type of host is when your website changes
domains but you still have search engine or referrer links pointing to the old domain.

View File

@@ -0,0 +1,6 @@
## What is a Stream?
A relatively new feature for Nginx, a Stream will serve to forward TCP/UDP
traffic directly to another computer on the network.
If you're running game servers, FTP or SSH servers this can come in handy.

View File

@@ -0,0 +1,6 @@
export * as AccessLists from "./AccessLists.md";
export * as Certificates from "./Certificates.md";
export * as DeadHosts from "./DeadHosts.md";
export * as ProxyHosts from "./ProxyHosts.md";
export * as RedirectionHosts from "./RedirectionHosts.md";
export * as Streams from "./Streams.md";

View File

@@ -0,0 +1,20 @@
// import * as de from "./de/index";
// import * as fa from "./fa/index";
import * as en from "./en/index";
const items: any = { en };
const fallbackLang = "en";
export const getHelpFile = (lang: string, section: string): string => {
if (typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined") {
return items[lang][section].default;
}
// Fallback to English
if (typeof items[fallbackLang] !== "undefined" && typeof items[fallbackLang][section] !== "undefined") {
return items[fallbackLang][section].default;
}
throw new Error(`Cannot load help doc for ${lang}-${section}`);
};
export default items;

View File

@@ -1,5 +0,0 @@
{
"dashboard": {
"defaultMessage": "Armaturenbrett"
}
}

View File

@@ -302,9 +302,21 @@
"error.invalid-email": {
"defaultMessage": "Invalid email address"
},
"error.max-character-length": {
"defaultMessage": "Maximum length is {max} character{max, plural, one {} other {s}}"
},
"error.max-domains": {
"defaultMessage": "Too many domains, max is {max}"
},
"error.maximum": {
"defaultMessage": "Maximum is {max}"
},
"error.min-character-length": {
"defaultMessage": "Minimum length is {min} character{min, plural, one {} other {s}}"
},
"error.minimum": {
"defaultMessage": "Minimum is {min}"
},
"error.passwords-must-match": {
"defaultMessage": "Passwords must match"
},

View File

@@ -52,7 +52,27 @@ const DeleteConfirmModal = EasyModal.create(
<Alert variant="danger" show={!!error} onClose={() => setError(null)} dismissible>
{error}
</Alert>
{children}
<div className="text-center mb-3">
<svg
role="img"
aria-label="warning icon"
xmlns="http://www.w3.org/2000/svg"
className="icon mb-2 text-danger icon-lg"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 9v2m0 4v.01" />
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75" />
</svg>
</div>
<div className="text-center mb-3">{children}</div>
</Modal.Body>
<Modal.Footer>
<Button data-bs-dismiss="modal" onClick={remove} disabled={isSubmitting}>

View File

@@ -0,0 +1,54 @@
import cn from "classnames";
import EasyModal, { type InnerModalProps } from "ez-modal-react";
import { useEffect, useState } from "react";
import Modal from "react-bootstrap/Modal";
import ReactMarkdown from "react-markdown";
import { Button } from "src/components";
import { getLocale, T } from "src/locale";
import { getHelpFile } from "src/locale/src/HelpDoc";
interface Props extends InnerModalProps {
section: string;
color?: string;
}
const showHelpModal = (section: string, color?: string) => {
EasyModal.show(HelpModal, { section, color });
};
const HelpModal = EasyModal.create(({ section, color, visible, remove }: Props) => {
const [markdownText, setMarkdownText] = useState("");
const lang = getLocale(true);
useEffect(() => {
try {
const docFile = getHelpFile(lang, section) as any;
fetch(docFile)
.then((response) => response.text())
.then(setMarkdownText);
} catch (ex: any) {
setMarkdownText(`**ERROR:** ${ex.message}`);
}
}, [lang, section]);
return (
<Modal show={visible} onHide={remove}>
<Modal.Body>
<ReactMarkdown>{markdownText}</ReactMarkdown>
</Modal.Body>
<Modal.Footer>
<Button
type="button"
actionType="primary"
className={cn("ms-auto", color ? `btn-${color}` : null)}
data-bs-dismiss="modal"
onClick={remove}
>
<T id="action.close" />
</Button>
</Modal.Footer>
</Modal>
);
});
export { showHelpModal };

View File

@@ -5,6 +5,7 @@ export * from "./DeadHostModal";
export * from "./DeleteConfirmModal";
export * from "./DNSCertificateModal";
export * from "./EventDetailsModal";
export * from "./HelpModal";
export * from "./HTTPCertificateModal";
export * from "./PermissionsModal";
export * from "./ProxyHostModal";

View File

@@ -11,12 +11,10 @@ const validateString = (minLength = 0, maxLength = 0) => {
return intl.formatMessage({ id: "error.required" });
}
if (minLength && value.length < minLength) {
// TODO: i18n
return `Minimum length is ${minLength} character${minLength === 1 ? "" : "s"}`;
return intl.formatMessage({ id: "error.min-character-length" }, { min: minLength });
}
if (maxLength && (typeof value === "undefined" || value.length > maxLength)) {
// TODO: i18n
return `Maximum length is ${maxLength} character${maxLength === 1 ? "" : "s"}`;
return intl.formatMessage({ id: "error.max-character-length" }, { max: maxLength });
}
};
};
@@ -33,12 +31,10 @@ const validateNumber = (min = -1, max = -1) => {
return intl.formatMessage({ id: "error.required" });
}
if (min > -1 && int < min) {
// TODO: i18n
return `Minimum is ${min}`;
return intl.formatMessage({ id: "error.minimum" }, { min });
}
if (max > -1 && int > max) {
// TODO: i18n
return `Maximum is ${max}`;
return intl.formatMessage({ id: "error.maximum" }, { max });
}
};
};

View File

@@ -53,7 +53,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
cell: (info: any) => <T id="proxy-hosts.count" data={{ count: info.getValue() }} />,
}),
columnHelper.display({
id: "id", // todo: not needed for a display?
id: "id",
cell: (info: any) => {
return (
<span className="dropdown">

View File

@@ -1,11 +1,11 @@
import { IconSearch } from "@tabler/icons-react";
import { IconHelp, IconSearch } from "@tabler/icons-react";
import { useState } from "react";
import Alert from "react-bootstrap/Alert";
import { deleteAccessList } from "src/api/backend";
import { Button, LoadingPage } from "src/components";
import { useAccessLists } from "src/hooks";
import { T } from "src/locale";
import { showAccessListModal, showDeleteConfirmModal } from "src/modals";
import { showAccessListModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
import { showObjectSuccess } from "src/notifications";
import Table from "./Table";
@@ -47,9 +47,10 @@ export default function TableWrapper() {
<T id="access-lists" />
</h2>
</div>
{data?.length ? (
<div className="col-md-auto col-sm-12">
<div className="ms-auto d-flex flex-wrap btn-list">
{data?.length ? (
<div className="input-group input-group-flat w-auto">
<span className="input-group-text input-group-text-sm">
<IconSearch size={16} />
@@ -62,14 +63,19 @@ export default function TableWrapper() {
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
/>
</div>
) : null}
<Button size="sm" onClick={() => showHelpModal("AccessLists", "cyan")}>
<IconHelp size={20} />
</Button>
{data?.length ? (
<Button size="sm" className="btn-cyan" onClick={() => showAccessListModal("new")}>
<T id="object.add" tData={{ object: "access-list" }} />
</Button>
</div>
</div>
) : null}
</div>
</div>
</div>
</div>
<Table
data={filtered ?? data ?? []}
isFetching={isFetching}
@@ -77,7 +83,7 @@ export default function TableWrapper() {
onEdit={(id: number) => showAccessListModal(id)}
onDelete={(id: number) =>
showDeleteConfirmModal({
title: "access.delete.title",
title: <T id="object.delete" tData={{ object: "access-list" }} />,
onConfirm: () => handleDelete(id),
invalidations: [["access-lists"], ["access-list", id]],
children: <T id="object.delete.content" tData={{ object: "access-list" }} />,

View File

@@ -1,14 +1,15 @@
import { IconSearch } from "@tabler/icons-react";
import { IconHelp, IconSearch } from "@tabler/icons-react";
import { useState } from "react";
import Alert from "react-bootstrap/Alert";
import { deleteCertificate, downloadCertificate } from "src/api/backend";
import { LoadingPage } from "src/components";
import { Button, LoadingPage } from "src/components";
import { useCertificates } from "src/hooks";
import { T } from "src/locale";
import {
showCustomCertificateModal,
showDeleteConfirmModal,
showDNSCertificateModal,
showHelpModal,
showHTTPCertificateModal,
showRenewCertificateModal,
} from "src/modals";
@@ -69,9 +70,10 @@ export default function TableWrapper() {
<T id="certificates" />
</h2>
</div>
{data?.length ? (
<div className="col-md-auto col-sm-12">
<div className="ms-auto d-flex flex-wrap btn-list">
{data?.length ? (
<div className="input-group input-group-flat w-auto">
<span className="input-group-text input-group-text-sm">
<IconSearch size={16} />
@@ -84,6 +86,11 @@ export default function TableWrapper() {
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
/>
</div>
) : null}
<Button size="sm" onClick={() => showHelpModal("Certificates", "pink")}>
<IconHelp size={20} />
</Button>
{data?.length ? (
<div className="dropdown">
<button
type="button"
@@ -126,11 +133,11 @@ export default function TableWrapper() {
</a>
</div>
</div>
</div>
</div>
) : null}
</div>
</div>
</div>
</div>
<Table
data={filtered ?? data ?? []}
isFiltered={!!search}

View File

@@ -116,12 +116,7 @@ const Dashboard = () => {
<code>{`Todo:
- check mobile
- use statuses for table formatters where applicable: https://docs.tabler.io/ui/components/statuses
- add help docs for host types
- REDO SCREENSHOTS in docs folder
- search codebase for "TODO"
- update documentation to add development notes for translations
- double check output of access field selection on proxy host dialog, after access lists are completed
- check permissions in all places
More for api, then implement here:

View File

@@ -58,7 +58,7 @@ export default function Table({ data, isFetching, onEdit, onDelete, onDisableTog
},
}),
columnHelper.display({
id: "id", // todo: not needed for a display?
id: "id",
cell: (info: any) => {
return (
<span className="dropdown">

View File

@@ -1,4 +1,4 @@
import { IconSearch } from "@tabler/icons-react";
import { IconHelp, IconSearch } from "@tabler/icons-react";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import Alert from "react-bootstrap/Alert";
@@ -6,7 +6,7 @@ import { deleteDeadHost, toggleDeadHost } from "src/api/backend";
import { Button, LoadingPage } from "src/components";
import { useDeadHosts } from "src/hooks";
import { T } from "src/locale";
import { showDeadHostModal, showDeleteConfirmModal } from "src/modals";
import { showDeadHostModal, showDeleteConfirmModal, showHelpModal } from "src/modals";
import { showObjectSuccess } from "src/notifications";
import Table from "./Table";
@@ -56,9 +56,10 @@ export default function TableWrapper() {
<T id="dead-hosts" />
</h2>
</div>
{data?.length ? (
<div className="col-md-auto col-sm-12">
<div className="ms-auto d-flex flex-wrap btn-list">
{data?.length ? (
<div className="input-group input-group-flat w-auto">
<span className="input-group-text input-group-text-sm">
<IconSearch size={16} />
@@ -71,14 +72,19 @@ export default function TableWrapper() {
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
/>
</div>
) : null}
<Button size="sm" onClick={() => showHelpModal("DeadHosts", "red")}>
<IconHelp size={20} />
</Button>
{data?.length ? (
<Button size="sm" className="btn-red" onClick={() => showDeadHostModal("new")}>
<T id="object.add" tData={{ object: "dead-host" }} />
</Button>
</div>
</div>
) : null}
</div>
</div>
</div>
</div>
<Table
data={filtered ?? data ?? []}
isFiltered={!!search}

View File

@@ -1,4 +1,4 @@
import { IconSearch } from "@tabler/icons-react";
import { IconHelp, IconSearch } from "@tabler/icons-react";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import Alert from "react-bootstrap/Alert";
@@ -6,7 +6,7 @@ import { deleteProxyHost, toggleProxyHost } from "src/api/backend";
import { Button, LoadingPage } from "src/components";
import { useProxyHosts } from "src/hooks";
import { T } from "src/locale";
import { showDeleteConfirmModal, showProxyHostModal } from "src/modals";
import { showDeleteConfirmModal, showHelpModal, showProxyHostModal } from "src/modals";
import { showObjectSuccess } from "src/notifications";
import Table from "./Table";
@@ -59,9 +59,10 @@ export default function TableWrapper() {
<T id="proxy-hosts" />
</h2>
</div>
{data?.length ? (
<div className="col-md-auto col-sm-12">
<div className="ms-auto d-flex flex-wrap btn-list">
{data?.length ? (
<div className="input-group input-group-flat w-auto">
<span className="input-group-text input-group-text-sm">
<IconSearch size={16} />
@@ -74,14 +75,19 @@ export default function TableWrapper() {
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
/>
</div>
) : null}
<Button size="sm" onClick={() => showHelpModal("ProxyHosts", "lime")}>
<IconHelp size={20} />
</Button>
{data?.length ? (
<Button size="sm" className="btn-lime" onClick={() => showProxyHostModal("new")}>
<T id="object.add" tData={{ object: "proxy-host" }} />
</Button>
</div>
</div>
) : null}
</div>
</div>
</div>
</div>
<Table
data={filtered ?? data ?? []}
isFiltered={!!search}

View File

@@ -1,4 +1,4 @@
import { IconSearch } from "@tabler/icons-react";
import { IconHelp, IconSearch } from "@tabler/icons-react";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import Alert from "react-bootstrap/Alert";
@@ -6,7 +6,7 @@ import { deleteRedirectionHost, toggleRedirectionHost } from "src/api/backend";
import { Button, LoadingPage } from "src/components";
import { useRedirectionHosts } from "src/hooks";
import { T } from "src/locale";
import { showDeleteConfirmModal, showRedirectionHostModal } from "src/modals";
import { showDeleteConfirmModal, showHelpModal, showRedirectionHostModal } from "src/modals";
import { showObjectSuccess } from "src/notifications";
import Table from "./Table";
@@ -59,9 +59,10 @@ export default function TableWrapper() {
<T id="redirection-hosts" />
</h2>
</div>
{data?.length ? (
<div className="col-md-auto col-sm-12">
<div className="ms-auto d-flex flex-wrap btn-list">
{data?.length ? (
<div className="input-group input-group-flat w-auto">
<span className="input-group-text input-group-text-sm">
<IconSearch size={16} />
@@ -74,6 +75,11 @@ export default function TableWrapper() {
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
/>
</div>
) : null}
<Button size="sm" onClick={() => showHelpModal("RedirectionHosts", "yellow")}>
<IconHelp size={20} />
</Button>
{data?.length ? (
<Button
size="sm"
className="btn-yellow"
@@ -81,11 +87,11 @@ export default function TableWrapper() {
>
<T id="object.add" tData={{ object: "redirection-host" }} />
</Button>
</div>
</div>
) : null}
</div>
</div>
</div>
</div>
<Table
data={filtered ?? data ?? []}
isFiltered={!!search}

View File

@@ -87,7 +87,7 @@ export default function Table({ data, isFetching, isFiltered, onEdit, onDelete,
},
}),
columnHelper.display({
id: "id", // todo: not needed for a display?
id: "id",
cell: (info: any) => {
return (
<span className="dropdown">

View File

@@ -1,4 +1,4 @@
import { IconSearch } from "@tabler/icons-react";
import { IconHelp, IconSearch } from "@tabler/icons-react";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import Alert from "react-bootstrap/Alert";
@@ -6,7 +6,7 @@ import { deleteStream, toggleStream } from "src/api/backend";
import { Button, LoadingPage } from "src/components";
import { useStreams } from "src/hooks";
import { T } from "src/locale";
import { showDeleteConfirmModal, showStreamModal } from "src/modals";
import { showDeleteConfirmModal, showHelpModal, showStreamModal } from "src/modals";
import { showObjectSuccess } from "src/notifications";
import Table from "./Table";
@@ -61,9 +61,10 @@ export default function TableWrapper() {
<T id="streams" />
</h2>
</div>
{data?.length ? (
<div className="col-md-auto col-sm-12">
<div className="ms-auto d-flex flex-wrap btn-list">
{data?.length ? (
<div className="input-group input-group-flat w-auto">
<span className="input-group-text input-group-text-sm">
<IconSearch size={16} />
@@ -76,14 +77,19 @@ export default function TableWrapper() {
onChange={(e: any) => setSearch(e.target.value.toLowerCase().trim())}
/>
</div>
) : null}
<Button size="sm" onClick={() => showHelpModal("Streams", "blue")}>
<IconHelp size={20} />
</Button>
{data?.length ? (
<Button size="sm" className="btn-blue" onClick={() => showStreamModal("new")}>
<T id="object.add" tData={{ object: "stream" }} />
</Button>
</div>
</div>
) : null}
</div>
</div>
</div>
</div>
<Table
data={filtered ?? data ?? []}
isFetching={isFetching}

View File

@@ -47,4 +47,5 @@ export default defineConfig({
environment: "happy-dom",
setupFiles: ["./vitest-setup.js"],
},
assetsInclude: ["**/*.md", "**/*.png", "**/*.svg"],
});

View File

@@ -977,12 +977,26 @@
resolved "https://registry.yarnpkg.com/@types/country-flag-icons/-/country-flag-icons-1.2.2.tgz#8f51089cab857f0f700feabd38b3960d006d64f2"
integrity sha512-CefEn/J336TBDp7NX8JqzlDtCBOsm8M3r1Li0gEOt0HOMHF1XemNyrx9lSHjsafcb1yYWybU0N8ZAXuyCaND0w==
"@types/debug@^4.0.0":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==
dependencies:
"@types/ms" "*"
"@types/deep-eql@*":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd"
integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==
"@types/estree@1.0.8", "@types/estree@^1.0.0":
"@types/estree-jsx@^1.0.0":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18"
integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==
dependencies:
"@types/estree" "*"
"@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
@@ -1020,6 +1034,11 @@
dependencies:
"@types/unist" "*"
"@types/ms@*":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
"@types/node@^20.0.0":
version "20.19.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.11.tgz#728cab53092bd5f143beed7fbba7ba99de3c16c4"
@@ -1356,6 +1375,13 @@ date-fns@^4.1.0:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14"
integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==
debug@^4.0.0, debug@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
@@ -1363,13 +1389,6 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
dependencies:
ms "^2.1.3"
debug@^4.4.3:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
decimal.js@^10.4.3:
version "10.6.0"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a"
@@ -1491,6 +1510,11 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
estree-util-is-identifier-name@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd"
integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==
estree-walker@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d"
@@ -1655,6 +1679,27 @@ hast-util-to-html@^9.0.0:
stringify-entities "^4.0.0"
zwitch "^2.0.4"
hast-util-to-jsx-runtime@^2.0.0:
version "2.3.6"
resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98"
integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==
dependencies:
"@types/estree" "^1.0.0"
"@types/hast" "^3.0.0"
"@types/unist" "^3.0.0"
comma-separated-tokens "^2.0.0"
devlop "^1.0.0"
estree-util-is-identifier-name "^3.0.0"
hast-util-whitespace "^3.0.0"
mdast-util-mdx-expression "^2.0.0"
mdast-util-mdx-jsx "^3.0.0"
mdast-util-mdxjs-esm "^2.0.0"
property-information "^7.0.0"
space-separated-tokens "^2.0.0"
style-to-js "^1.0.0"
unist-util-position "^5.0.0"
vfile-message "^4.0.0"
hast-util-to-string@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz#a4f15e682849326dd211c97129c94b0c3e76527c"
@@ -1698,6 +1743,11 @@ hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-
dependencies:
react-is "^16.7.0"
html-url-attributes@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87"
integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==
html-void-elements@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7"
@@ -1731,6 +1781,11 @@ indent-string@^4.0.0:
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
inline-style-parser@0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22"
integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==
intl-messageformat@10.7.18:
version "10.7.18"
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.7.18.tgz#51a6f387afbca9b0f881b2ec081566db8c540b0d"
@@ -1845,6 +1900,11 @@ lodash@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
longest-streak@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
loose-envify@^1.0.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -1871,6 +1931,74 @@ magic-string@^0.30.19:
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.5"
mdast-util-from-markdown@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a"
integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==
dependencies:
"@types/mdast" "^4.0.0"
"@types/unist" "^3.0.0"
decode-named-character-reference "^1.0.0"
devlop "^1.0.0"
mdast-util-to-string "^4.0.0"
micromark "^4.0.0"
micromark-util-decode-numeric-character-reference "^2.0.0"
micromark-util-decode-string "^2.0.0"
micromark-util-normalize-identifier "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
unist-util-stringify-position "^4.0.0"
mdast-util-mdx-expression@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096"
integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==
dependencies:
"@types/estree-jsx" "^1.0.0"
"@types/hast" "^3.0.0"
"@types/mdast" "^4.0.0"
devlop "^1.0.0"
mdast-util-from-markdown "^2.0.0"
mdast-util-to-markdown "^2.0.0"
mdast-util-mdx-jsx@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d"
integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==
dependencies:
"@types/estree-jsx" "^1.0.0"
"@types/hast" "^3.0.0"
"@types/mdast" "^4.0.0"
"@types/unist" "^3.0.0"
ccount "^2.0.0"
devlop "^1.1.0"
mdast-util-from-markdown "^2.0.0"
mdast-util-to-markdown "^2.0.0"
parse-entities "^4.0.0"
stringify-entities "^4.0.0"
unist-util-stringify-position "^4.0.0"
vfile-message "^4.0.0"
mdast-util-mdxjs-esm@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97"
integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==
dependencies:
"@types/estree-jsx" "^1.0.0"
"@types/hast" "^3.0.0"
"@types/mdast" "^4.0.0"
devlop "^1.0.0"
mdast-util-from-markdown "^2.0.0"
mdast-util-to-markdown "^2.0.0"
mdast-util-phrasing@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3"
integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==
dependencies:
"@types/mdast" "^4.0.0"
unist-util-is "^6.0.0"
mdast-util-to-hast@^13.0.0:
version "13.2.0"
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4"
@@ -1886,11 +2014,102 @@ mdast-util-to-hast@^13.0.0:
unist-util-visit "^5.0.0"
vfile "^6.0.0"
mdast-util-to-markdown@^2.0.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b"
integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==
dependencies:
"@types/mdast" "^4.0.0"
"@types/unist" "^3.0.0"
longest-streak "^3.0.0"
mdast-util-phrasing "^4.0.0"
mdast-util-to-string "^4.0.0"
micromark-util-classify-character "^2.0.0"
micromark-util-decode-string "^2.0.0"
unist-util-visit "^5.0.0"
zwitch "^2.0.0"
mdast-util-to-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814"
integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==
dependencies:
"@types/mdast" "^4.0.0"
memoize-one@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
micromark-core-commonmark@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4"
integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==
dependencies:
decode-named-character-reference "^1.0.0"
devlop "^1.0.0"
micromark-factory-destination "^2.0.0"
micromark-factory-label "^2.0.0"
micromark-factory-space "^2.0.0"
micromark-factory-title "^2.0.0"
micromark-factory-whitespace "^2.0.0"
micromark-util-character "^2.0.0"
micromark-util-chunked "^2.0.0"
micromark-util-classify-character "^2.0.0"
micromark-util-html-tag-name "^2.0.0"
micromark-util-normalize-identifier "^2.0.0"
micromark-util-resolve-all "^2.0.0"
micromark-util-subtokenize "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromark-factory-destination@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639"
integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==
dependencies:
micromark-util-character "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromark-factory-label@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1"
integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==
dependencies:
devlop "^1.0.0"
micromark-util-character "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromark-factory-space@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc"
integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==
dependencies:
micromark-util-character "^2.0.0"
micromark-util-types "^2.0.0"
micromark-factory-title@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94"
integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==
dependencies:
micromark-factory-space "^2.0.0"
micromark-util-character "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromark-factory-whitespace@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1"
integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==
dependencies:
micromark-factory-space "^2.0.0"
micromark-util-character "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromark-util-character@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6"
@@ -1899,11 +2118,71 @@ micromark-util-character@^2.0.0:
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromark-util-chunked@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051"
integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==
dependencies:
micromark-util-symbol "^2.0.0"
micromark-util-classify-character@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629"
integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==
dependencies:
micromark-util-character "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromark-util-combine-extensions@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9"
integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==
dependencies:
micromark-util-chunked "^2.0.0"
micromark-util-types "^2.0.0"
micromark-util-decode-numeric-character-reference@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5"
integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==
dependencies:
micromark-util-symbol "^2.0.0"
micromark-util-decode-string@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2"
integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==
dependencies:
decode-named-character-reference "^1.0.0"
micromark-util-character "^2.0.0"
micromark-util-decode-numeric-character-reference "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-encode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8"
integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==
micromark-util-html-tag-name@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825"
integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==
micromark-util-normalize-identifier@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d"
integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==
dependencies:
micromark-util-symbol "^2.0.0"
micromark-util-resolve-all@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b"
integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==
dependencies:
micromark-util-types "^2.0.0"
micromark-util-sanitize-uri@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7"
@@ -1913,6 +2192,16 @@ micromark-util-sanitize-uri@^2.0.0:
micromark-util-encode "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-subtokenize@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee"
integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==
dependencies:
devlop "^1.0.0"
micromark-util-chunked "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromark-util-symbol@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8"
@@ -1923,6 +2212,29 @@ micromark-util-types@^2.0.0:
resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e"
integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==
micromark@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb"
integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==
dependencies:
"@types/debug" "^4.0.0"
debug "^4.0.0"
decode-named-character-reference "^1.0.0"
devlop "^1.0.0"
micromark-core-commonmark "^2.0.0"
micromark-factory-space "^2.0.0"
micromark-util-character "^2.0.0"
micromark-util-chunked "^2.0.0"
micromark-util-combine-extensions "^2.0.0"
micromark-util-decode-numeric-character-reference "^2.0.0"
micromark-util-encode "^2.0.0"
micromark-util-normalize-identifier "^2.0.0"
micromark-util-resolve-all "^2.0.0"
micromark-util-sanitize-uri "^2.0.0"
micromark-util-subtokenize "^2.0.0"
micromark-util-symbol "^2.0.0"
micromark-util-types "^2.0.0"
micromatch@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
@@ -2184,6 +2496,23 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-markdown@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca"
integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==
dependencies:
"@types/hast" "^3.0.0"
"@types/mdast" "^4.0.0"
devlop "^1.0.0"
hast-util-to-jsx-runtime "^2.0.0"
html-url-attributes "^3.0.0"
mdast-util-to-hast "^13.0.0"
remark-parse "^11.0.0"
remark-rehype "^11.0.0"
unified "^11.0.0"
unist-util-visit "^5.0.0"
vfile "^6.0.0"
react-refresh@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062"
@@ -2304,6 +2633,27 @@ rehype@~13.0.0:
rehype-stringify "^10.0.0"
unified "^11.0.0"
remark-parse@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1"
integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==
dependencies:
"@types/mdast" "^4.0.0"
mdast-util-from-markdown "^2.0.0"
micromark-util-types "^2.0.0"
unified "^11.0.0"
remark-rehype@^11.0.0:
version "11.1.2"
resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37"
integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==
dependencies:
"@types/hast" "^3.0.0"
"@types/mdast" "^4.0.0"
mdast-util-to-hast "^13.0.0"
unified "^11.0.0"
vfile "^6.0.0"
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -2439,6 +2789,20 @@ strip-indent@^3.0.0:
dependencies:
min-indent "^1.0.0"
style-to-js@^1.0.0:
version "1.1.18"
resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.18.tgz#3e6c13bd4c4db079bd2c2c94571cce5c758bc2ff"
integrity sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==
dependencies:
style-to-object "1.0.11"
style-to-object@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.11.tgz#cf252c4051758b7acb18a5efb296f91fb79bb9c4"
integrity sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow==
dependencies:
inline-style-parser "0.2.4"
stylis@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
@@ -2757,7 +3121,7 @@ yaml@^1.10.0:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
zwitch@^2.0.4:
zwitch@^2.0.0, zwitch@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==