diff --git a/src/d.ts b/src/d.ts index fb527e4..69604a6 100644 --- a/src/d.ts +++ b/src/d.ts @@ -1,35 +1,16 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /// +/// declare const __XXX: boolean; declare const __XXX_HOST: string; declare const __ZAP_STREAM_VERSION__: string; -declare module "*.jpg" { - const value: unknown; - export default value; -} - -declare module "*.svg" { - const value: unknown; - export default value; -} - -declare module "*.webp" { +declare module "*.md" { const value: string; 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" { const value: Record; export default value; diff --git a/src/element/async-loader.tsx b/src/element/async-loader.tsx new file mode 100644 index 0000000..0a99218 --- /dev/null +++ b/src/element/async-loader.tsx @@ -0,0 +1,10 @@ +import { ReactNode, useEffect, useState } from "react"; + +export function Async({ loader, then }: { loader: () => Promise, then: (v: T) => ReactNode }) { + const [res, setResult] = useState(); + useEffect(() => { + loader().then(setResult); + }, []); + if (!res) return; + return then(res); +} \ No newline at end of file diff --git a/src/element/markdown.css b/src/element/markdown.css index 45f7047..759770a 100644 --- a/src/element/markdown.css +++ b/src/element/markdown.css @@ -1,9 +1,3 @@ -.markdown { - font-size: 18px; - font-weight: 400; - line-height: 29px; -} - .markdown a { color: var(--primary); } @@ -26,7 +20,7 @@ .markdown video, .markdown iframe, .markdown audio { - width: 100%; + max-width: 100%; display: block; } @@ -35,3 +29,12 @@ width: -webkit-fill-available; aspect-ratio: 16 / 9; } + +.markdown h1, +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + margin: 0.5em 0; +} \ No newline at end of file diff --git a/src/element/markdown.tsx b/src/element/markdown.tsx index c7d0045..790a5f6 100644 --- a/src/element/markdown.tsx +++ b/src/element/markdown.tsx @@ -1,88 +1,110 @@ import "./markdown.css"; import { ReactNode, forwardRef, useMemo } from "react"; -import { Token, marked } from "marked"; +import { Token, Tokens, marked } from "marked"; import { HyperText } from "./hypertext"; import { Text } from "./text"; interface MarkdownProps { content: string; tags?: Array>; -} -function renderToken(t: Token): ReactNode { - try { - switch (t.type) { - case "paragraph": { - return

{t.tokens ? t.tokens.map(renderToken) : t.raw}

; - } - case "image": { - return ; - } - case "heading": { - switch (t.depth) { - case 1: - return

{t.tokens ? t.tokens.map(renderToken) : t.raw}

; - case 2: - return

{t.tokens ? t.tokens.map(renderToken) : t.raw}

; - case 3: - return

{t.tokens ? t.tokens.map(renderToken) : t.raw}

; - case 4: - return

{t.tokens ? t.tokens.map(renderToken) : t.raw}

; - case 5: - return
{t.tokens ? t.tokens.map(renderToken) : t.raw}
; - case 6: - return
{t.tokens ? t.tokens.map(renderToken) : t.raw}
; - } - throw new Error("Invalid heading"); - } - case "codespan": { - return {t.raw}; - } - case "code": { - return
{t.raw}
; - } - case "br": { - return
; - } - case "hr": { - return
; - } - case "blockquote": { - return
{t.tokens ? t.tokens.map(renderToken) : t.raw}
; - } - case "link": { - return {t.tokens ? t.tokens.map(renderToken) : t.raw}; - } - case "list": { - if (t.ordered) { - return
    {t.items.map(renderToken)}
; - } else { - return
    {t.items.map(renderToken)}
; - } - } - case "list_item": { - return
  • {t.tokens ? t.tokens.map(renderToken) : t.raw}
  • ; - } - case "em": { - return {t.tokens ? t.tokens.map(renderToken) : t.raw}; - } - case "del": { - return {t.tokens ? t.tokens.map(renderToken) : t.raw}; - } - default: { - if ("tokens" in t) { - return (t.tokens as Array).map(renderToken); - } - return ; - } - } - } catch (e) { - console.error(e); - } + // Render plain text directly without parsing nostr/http links + plainText?: boolean; } const Markdown = forwardRef((props: MarkdownProps, ref) => { + + function renderToken(t: Token): ReactNode { + try { + switch (t.type) { + case "paragraph": { + return

    {t.tokens ? t.tokens.map(renderToken) : t.raw}

    ; + } + case "image": { + return ; + } + case "heading": { + switch (t.depth) { + case 1: + return

    {t.tokens ? t.tokens.map(renderToken) : t.raw}

    ; + case 2: + return

    {t.tokens ? t.tokens.map(renderToken) : t.raw}

    ; + case 3: + return

    {t.tokens ? t.tokens.map(renderToken) : t.raw}

    ; + case 4: + return

    {t.tokens ? t.tokens.map(renderToken) : t.raw}

    ; + case 5: + return
    {t.tokens ? t.tokens.map(renderToken) : t.raw}
    ; + case 6: + return
    {t.tokens ? t.tokens.map(renderToken) : t.raw}
    ; + } + throw new Error("Invalid heading"); + } + case "codespan": { + return {t.raw}; + } + case "code": { + return
    {t.raw}
    ; + } + case "br": { + return
    ; + } + case "hr": { + return
    ; + } + case "blockquote": { + return
    {t.tokens ? t.tokens.map(renderToken) : t.raw}
    ; + } + case "link": { + return {t.tokens ? t.tokens.map(renderToken) : t.raw}; + } + case "list": { + if (t.ordered) { + return
      {t.items.map(renderToken)}
    ; + } else { + return
      {t.items.map(renderToken)}
    ; + } + } + case "list_item": { + return
  • {t.tokens ? t.tokens.map(renderToken) : t.raw}
  • ; + } + case "em": { + return {t.tokens ? t.tokens.map(renderToken) : t.raw}; + } + case "del": { + return {t.tokens ? t.tokens.map(renderToken) : t.raw}; + } + case "table": { + return + + + {(t.header as Tokens.TableCell[]).map(v => )} + + + + {(t.rows as Tokens.TableCell[][]).map(v => + {v.map(d => )} + )} + +
    {v.tokens ? v.tokens.map(renderToken) : v.text}
    {d.tokens ? d.tokens.map(renderToken) : d.text}
    ; + } + default: { + if ("tokens" in t) { + return (t.tokens as Array).map(renderToken); + } + if (props.plainText ?? false) { + return t.raw; + } + return ; + } + } + } catch (e) { + console.error(e); + } + } + + const parsed = useMemo(() => { return marked.lexer(props.content); }, [props.content, props.tags]); diff --git a/src/faq.md b/src/faq.md new file mode 100644 index 0000000..eae0d2c --- /dev/null +++ b/src/faq.md @@ -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): +![OBS Apple](https://void.cat/d/VQQ75R6tmbVQJ9eqiwJhoj.webp) \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index bd8b1b2..b2ee958 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -27,8 +27,12 @@ import { WidgetsPage } from "@/pages/widgets"; import { AlertsPage } from "@/pages/alerts"; import { StreamSummaryPage } from "@/pages/summary"; import { EmbededPage } from "./pages/embed"; +import Markdown from "./element/markdown"; const DashboardPage = lazy(() => import("./pages/dashboard")); +import Faq from "@/faq.md"; +import { Async } from "./element/async-loader"; + const db = new SnortSystemDb(); const System = new NostrSystem({ db, @@ -103,6 +107,13 @@ const router = createBrowserRouter([ ), }, + { + path: "/faq", + element: { + const req = await fetch(Faq); + return await req.text(); + }} then={(v) => } /> + }, { path: "*", element: , diff --git a/vite.config.ts b/vite.config.ts index 332ab0d..55c1262 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -28,6 +28,7 @@ export default defineConfig({ filename: "build/stats.html", }), ], + assetsInclude: ["**/*.md"], build: { outDir: "build", sourcemap: true,