feat: start FAQ page
This commit is contained in:
23
src/d.ts
23
src/d.ts
@ -1,35 +1,16 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
/// <reference types="@webbtc/webln-types" />
|
/// <reference types="@webbtc/webln-types" />
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
declare const __XXX: boolean;
|
declare const __XXX: boolean;
|
||||||
declare const __XXX_HOST: string;
|
declare const __XXX_HOST: string;
|
||||||
declare const __ZAP_STREAM_VERSION__: string;
|
declare const __ZAP_STREAM_VERSION__: string;
|
||||||
|
|
||||||
declare module "*.jpg" {
|
declare module "*.md" {
|
||||||
const value: unknown;
|
|
||||||
export default value;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "*.svg" {
|
|
||||||
const value: unknown;
|
|
||||||
export default value;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "*.webp" {
|
|
||||||
const value: string;
|
const value: string;
|
||||||
export default value;
|
export default value;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "*.png" {
|
|
||||||
const value: string;
|
|
||||||
export default value;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "*.css" {
|
|
||||||
const stylesheet: CSSStyleSheet;
|
|
||||||
export default stylesheet;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "translations/*.json" {
|
declare module "translations/*.json" {
|
||||||
const value: Record<string, string>;
|
const value: Record<string, string>;
|
||||||
export default value;
|
export default value;
|
||||||
|
10
src/element/async-loader.tsx
Normal file
10
src/element/async-loader.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function Async<T>({ loader, then }: { loader: () => Promise<T>, then: (v: T) => ReactNode }) {
|
||||||
|
const [res, setResult] = useState<T>();
|
||||||
|
useEffect(() => {
|
||||||
|
loader().then(setResult);
|
||||||
|
}, []);
|
||||||
|
if (!res) return;
|
||||||
|
return then(res);
|
||||||
|
}
|
@ -1,9 +1,3 @@
|
|||||||
.markdown {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 29px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown a {
|
.markdown a {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
@ -26,7 +20,7 @@
|
|||||||
.markdown video,
|
.markdown video,
|
||||||
.markdown iframe,
|
.markdown iframe,
|
||||||
.markdown audio {
|
.markdown audio {
|
||||||
width: 100%;
|
max-width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,3 +29,12 @@
|
|||||||
width: -webkit-fill-available;
|
width: -webkit-fill-available;
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown h1,
|
||||||
|
.markdown h2,
|
||||||
|
.markdown h3,
|
||||||
|
.markdown h4,
|
||||||
|
.markdown h5,
|
||||||
|
.markdown h6 {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
@ -1,16 +1,21 @@
|
|||||||
import "./markdown.css";
|
import "./markdown.css";
|
||||||
|
|
||||||
import { ReactNode, forwardRef, useMemo } from "react";
|
import { ReactNode, forwardRef, useMemo } from "react";
|
||||||
import { Token, marked } from "marked";
|
import { Token, Tokens, marked } from "marked";
|
||||||
import { HyperText } from "./hypertext";
|
import { HyperText } from "./hypertext";
|
||||||
import { Text } from "./text";
|
import { Text } from "./text";
|
||||||
|
|
||||||
interface MarkdownProps {
|
interface MarkdownProps {
|
||||||
content: string;
|
content: string;
|
||||||
tags?: Array<Array<string>>;
|
tags?: Array<Array<string>>;
|
||||||
|
|
||||||
|
// Render plain text directly without parsing nostr/http links
|
||||||
|
plainText?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderToken(t: Token): ReactNode {
|
const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props: MarkdownProps, ref) => {
|
||||||
|
|
||||||
|
function renderToken(t: Token): ReactNode {
|
||||||
try {
|
try {
|
||||||
switch (t.type) {
|
switch (t.type) {
|
||||||
case "paragraph": {
|
case "paragraph": {
|
||||||
@ -70,19 +75,36 @@ function renderToken(t: Token): ReactNode {
|
|||||||
case "del": {
|
case "del": {
|
||||||
return <s>{t.tokens ? t.tokens.map(renderToken) : t.raw}</s>;
|
return <s>{t.tokens ? t.tokens.map(renderToken) : t.raw}</s>;
|
||||||
}
|
}
|
||||||
|
case "table": {
|
||||||
|
return <table className="table-auto border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{(t.header as Tokens.TableCell[]).map(v => <th className="border">{v.tokens ? v.tokens.map(renderToken) : v.text}</th>)}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{(t.rows as Tokens.TableCell[][]).map(v => <tr>
|
||||||
|
{v.map(d => <td className="border px-2 py-1">{d.tokens ? d.tokens.map(renderToken) : d.text}</td>)}
|
||||||
|
</tr>)}
|
||||||
|
</tbody>
|
||||||
|
</table>;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
if ("tokens" in t) {
|
if ("tokens" in t) {
|
||||||
return (t.tokens as Array<Token>).map(renderToken);
|
return (t.tokens as Array<Token>).map(renderToken);
|
||||||
}
|
}
|
||||||
|
if (props.plainText ?? false) {
|
||||||
|
return t.raw;
|
||||||
|
}
|
||||||
return <Text content={t.raw} tags={[]} />;
|
return <Text content={t.raw} tags={[]} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props: MarkdownProps, ref) => {
|
|
||||||
const parsed = useMemo(() => {
|
const parsed = useMemo(() => {
|
||||||
return marked.lexer(props.content);
|
return marked.lexer(props.content);
|
||||||
}, [props.content, props.tags]);
|
}, [props.content, props.tags]);
|
||||||
|
13
src/faq.md
Normal file
13
src/faq.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# FAQ
|
||||||
|
|
||||||
|
# Reccomended Stream Settings
|
||||||
|
| Name | Value |
|
||||||
|
| - | - |
|
||||||
|
| Video Codec | h264 |
|
||||||
|
| Audio Codec | AAC |
|
||||||
|
| Max Video Bitrate | 7000k |
|
||||||
|
| Max Audio Bitrate | 320k |
|
||||||
|
| Keyframe Interval | 2s |
|
||||||
|
|
||||||
|
### Example settings in OBS (Apple M1 Mac):
|
||||||
|

|
@ -27,8 +27,12 @@ import { WidgetsPage } from "@/pages/widgets";
|
|||||||
import { AlertsPage } from "@/pages/alerts";
|
import { AlertsPage } from "@/pages/alerts";
|
||||||
import { StreamSummaryPage } from "@/pages/summary";
|
import { StreamSummaryPage } from "@/pages/summary";
|
||||||
import { EmbededPage } from "./pages/embed";
|
import { EmbededPage } from "./pages/embed";
|
||||||
|
import Markdown from "./element/markdown";
|
||||||
const DashboardPage = lazy(() => import("./pages/dashboard"));
|
const DashboardPage = lazy(() => import("./pages/dashboard"));
|
||||||
|
|
||||||
|
import Faq from "@/faq.md";
|
||||||
|
import { Async } from "./element/async-loader";
|
||||||
|
|
||||||
const db = new SnortSystemDb();
|
const db = new SnortSystemDb();
|
||||||
const System = new NostrSystem({
|
const System = new NostrSystem({
|
||||||
db,
|
db,
|
||||||
@ -103,6 +107,13 @@ const router = createBrowserRouter([
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/faq",
|
||||||
|
element: <Async loader={async () => {
|
||||||
|
const req = await fetch(Faq);
|
||||||
|
return await req.text();
|
||||||
|
}} then={(v) => <Markdown content={v} tags={[]} plainText={true} />} />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "*",
|
path: "*",
|
||||||
element: <CatchAllRoutePage />,
|
element: <CatchAllRoutePage />,
|
||||||
|
@ -28,6 +28,7 @@ export default defineConfig({
|
|||||||
filename: "build/stats.html",
|
filename: "build/stats.html",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
assetsInclude: ["**/*.md"],
|
||||||
build: {
|
build: {
|
||||||
outDir: "build",
|
outDir: "build",
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
Reference in New Issue
Block a user