@agimon-ai/doompi-web-components 0.0.1-alpha.61 → 0.0.1-alpha.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,10 +8,15 @@
8
8
  * the session has.
9
9
  */
10
10
  export type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;
11
+ type ImageLoader = (path: string) => Promise<{
12
+ url: string;
13
+ dispose(): void;
14
+ }>;
11
15
  /** GitHub-flavored Markdown using the cockpit's safe, shared presentation. */
12
- export declare function Markdown({ text, onFileLink }: {
16
+ export declare function Markdown({ text, onFileLink, loadImage }: {
13
17
  text: string;
14
18
  onFileLink?: FileLinkHandler;
19
+ loadImage?: ImageLoader;
15
20
  }): import("react").JSX.Element;
16
21
  //#endregion
17
22
  //# sourceMappingURL=Markdown.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.d.mts","names":[],"sources":["../../../../../../src/components/Markdown.tsx"],"mappings":";;;;;;;;;YAkBY,mBAAmB,cAAc;;wBAiI7B,WAAW,MAAM;EAAgB;EAAc,aAAa;oBAAiB,IAAA"}
1
+ {"version":3,"file":"Markdown.d.mts","names":[],"sources":["../../../../../../src/components/Markdown.tsx"],"mappings":";;;;;;;;;YA0BY,mBAAmB,cAAc;KAUxC,eAAe,iBAAiB;EAAU;EAAa;;;wBA+K5C,WACd,MACA,YACA;EAEA;EACA,aAAa;EACb,YAAY;oBACb,IAAA"}
@@ -1,3 +1,3 @@
1
- import{CodeBlock as e}from"./CodeBlock.mjs";import{jsx as t}from"react/jsx-runtime";import{createContext as n,isValidElement as r,useContext as i}from"react";import a from"react-markdown";import o from"remark-gfm";const s=[o],c=`rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green`,l=n(void 0);function u({className:e,children:n,...r}){let a=i(l),o=typeof n==`string`&&!n.includes(`
2
- `)?n:void 0,s=o===void 0||e!==void 0?void 0:a?.(o);return s!==void 0&&o!==void 0?/* @__PURE__ */ t(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${o}`,onClick:s,className:`${c} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`,children:o}):/* @__PURE__ */ t(`code`,{...r,className:`${e??``} ${c} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`,children:n})}function d({href:e,children:n}){let r=i(l),a=e;try{a=e===void 0?void 0:decodeURIComponent(e)}catch{a=void 0}let o=a!==void 0&&a!==``&&!/^(?:[a-z][a-z\d+.-]*:|\/\/|#|\?)/i.test(a)?r?.(a,!0):void 0;return o===void 0?/* @__PURE__ */ t(`a`,{href:e,target:`_blank`,rel:`noreferrer`,className:`text-doom-blue underline decoration-doom-blue/50`,children:n}):/* @__PURE__ */ t(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${a}`,onClick:o,className:`text-doom-blue underline decoration-doom-blue/50`,children:n})}function f(e){return typeof e==`string`?e:Array.isArray(e)?e.map(e=>f(e)).join(``):``}function p({children:n}){let i=r(n)?n:void 0;return i===void 0?/* @__PURE__ */ t(`pre`,{className:`overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed`,children:n}):/* @__PURE__ */ t(e,{text:f(i.props.children).replace(/\n$/,``),className:i.props.className})}const m={p:({children:e})=>/* @__PURE__ */ t(`p`,{className:`whitespace-pre-wrap break-words`,children:e}),strong:({children:e})=>/* @__PURE__ */ t(`strong`,{className:`font-bold text-doom-hi`,children:e}),em:({children:e})=>/* @__PURE__ */ t(`em`,{className:`italic`,children:e}),del:({children:e})=>/* @__PURE__ */ t(`del`,{className:`text-doom-dim line-through`,children:e}),a:d,code:u,pre:p,ul:({children:e})=>/* @__PURE__ */ t(`ul`,{className:`list-disc pl-5`,children:e}),ol:({children:e})=>/* @__PURE__ */ t(`ol`,{className:`list-decimal pl-5`,children:e}),li:({children:e})=>/* @__PURE__ */ t(`li`,{className:`whitespace-pre-wrap break-words`,children:e}),h1:({children:e})=>/* @__PURE__ */ t(`h1`,{className:`text-lg font-bold text-doom-hi`,children:e}),h2:({children:e})=>/* @__PURE__ */ t(`h2`,{className:`text-base font-bold text-doom-hi`,children:e}),h3:({children:e})=>/* @__PURE__ */ t(`h3`,{className:`text-base font-bold text-doom-hi`,children:e}),h4:({children:e})=>/* @__PURE__ */ t(`h4`,{className:`text-base font-bold text-doom-text`,children:e}),blockquote:({children:e})=>/* @__PURE__ */ t(`blockquote`,{className:`border-l-2 border-doom-border pl-3 text-doom-dim`,children:e}),hr:()=>/* @__PURE__ */ t(`hr`,{className:`border-doom-border-soft`}),table:({children:e})=>/* @__PURE__ */ t(`div`,{className:`overflow-x-auto`,children:/* @__PURE__ */ t(`table`,{className:`border-collapse text-sm`,children:e})}),th:({children:e})=>/* @__PURE__ */ t(`th`,{className:`border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi`,children:e}),td:({children:e})=>/* @__PURE__ */ t(`td`,{className:`border border-doom-border px-2 py-1 align-top`,children:e}),img:({src:e,alt:n})=>/* @__PURE__ */ t(`img`,{src:e,alt:n??``,className:`max-h-[480px] max-w-full rounded border border-doom-border`})};function h({text:e,onFileLink:n}){return/* @__PURE__ */ t(l.Provider,{value:n,children:/* @__PURE__ */ t(`div`,{className:`flex min-w-0 flex-col gap-2`,children:/* @__PURE__ */ t(a,{remarkPlugins:s,components:m,children:e})})})}export{h as Markdown};
1
+ import{CodeBlock as e}from"./CodeBlock.mjs";import{jsx as t,jsxs as n}from"react/jsx-runtime";import{createContext as r,isValidElement as i,useContext as a,useEffect as o,useState as s}from"react";import c from"react-markdown";import l from"remark-gfm";const u=[l],d=`rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green`,f=r(void 0),p=r(void 0);function m({src:e,alt:r}){let i=a(p),c;if(typeof e==`string`&&e!==``&&!/^(?:[a-z][a-z\d+.-]*:|\/\/|#|\?)/i.test(e))try{c=decodeURIComponent(e.split(/[?#]/,1)[0])}catch{c=``}let[l,u]=s();o(()=>{if(i===void 0||c===void 0)return;let e=!0,t;return Promise.resolve().then(()=>{if(e&&u(void 0),c===``)throw Error(`Invalid image path.`);return i(c)}).then(n=>{if(!e){n.dispose();return}t=n,u({path:c,loader:i,url:n.url})}).catch(()=>{e&&u({path:c,loader:i,failed:!0})}),()=>{e=!1,t?.dispose()}},[c,i]);let d=l?.path===c&&l?.loader===i?l:void 0;return i!==void 0&&c!==void 0&&(d?.url===void 0||d.failed)?/* @__PURE__ */ n(`span`,{role:d?.failed?`alert`:`status`,title:c,children:[r||c||`Image`,` (`,d?.failed?`unavailable`:`loading...`,`)`]}):/* @__PURE__ */ t(`img`,{src:i!==void 0&&c!==void 0?d?.url:e,alt:r??``,onError:i!==void 0&&c!==void 0?()=>u({path:c,loader:i,failed:!0}):void 0,className:`max-h-[480px] max-w-full rounded border border-doom-border`})}function h({className:e,children:n,...r}){let i=a(f),o=typeof n==`string`&&!n.includes(`
2
+ `)?n:void 0,s=o===void 0||e!==void 0?void 0:i?.(o);return s!==void 0&&o!==void 0?/* @__PURE__ */ t(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${o}`,onClick:s,className:`${d} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`,children:o}):/* @__PURE__ */ t(`code`,{...r,className:`${e??``} ${d} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`,children:n})}function g({href:e,children:n}){let r=a(f),i=e;try{i=e===void 0?void 0:decodeURIComponent(e)}catch{i=void 0}let o=i!==void 0&&i!==``&&!/^(?:[a-z][a-z\d+.-]*:|\/\/|#|\?)/i.test(i)?r?.(i,!0):void 0;return o===void 0?/* @__PURE__ */ t(`a`,{href:e,target:`_blank`,rel:`noreferrer`,className:`text-doom-blue underline decoration-doom-blue/50`,children:n}):/* @__PURE__ */ t(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${i}`,onClick:o,className:`text-doom-blue underline decoration-doom-blue/50`,children:n})}function _(e){return typeof e==`string`?e:Array.isArray(e)?e.map(e=>_(e)).join(``):``}function v({children:n}){let r=i(n)?n:void 0;return r===void 0?/* @__PURE__ */ t(`pre`,{className:`overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed`,children:n}):/* @__PURE__ */ t(e,{text:_(r.props.children).replace(/\n$/,``),className:r.props.className})}const y={p:({children:e})=>/* @__PURE__ */ t(`p`,{className:`whitespace-pre-wrap break-words`,children:e}),strong:({children:e})=>/* @__PURE__ */ t(`strong`,{className:`font-bold text-doom-hi`,children:e}),em:({children:e})=>/* @__PURE__ */ t(`em`,{className:`italic`,children:e}),del:({children:e})=>/* @__PURE__ */ t(`del`,{className:`text-doom-dim line-through`,children:e}),a:g,code:h,pre:v,ul:({children:e})=>/* @__PURE__ */ t(`ul`,{className:`list-disc pl-5`,children:e}),ol:({children:e})=>/* @__PURE__ */ t(`ol`,{className:`list-decimal pl-5`,children:e}),li:({children:e})=>/* @__PURE__ */ t(`li`,{className:`whitespace-pre-wrap break-words`,children:e}),h1:({children:e})=>/* @__PURE__ */ t(`h1`,{className:`text-lg font-bold text-doom-hi`,children:e}),h2:({children:e})=>/* @__PURE__ */ t(`h2`,{className:`text-base font-bold text-doom-hi`,children:e}),h3:({children:e})=>/* @__PURE__ */ t(`h3`,{className:`text-base font-bold text-doom-hi`,children:e}),h4:({children:e})=>/* @__PURE__ */ t(`h4`,{className:`text-base font-bold text-doom-text`,children:e}),blockquote:({children:e})=>/* @__PURE__ */ t(`blockquote`,{className:`border-l-2 border-doom-border pl-3 text-doom-dim`,children:e}),hr:()=>/* @__PURE__ */ t(`hr`,{className:`border-doom-border-soft`}),table:({children:e})=>/* @__PURE__ */ t(`div`,{className:`overflow-x-auto`,children:/* @__PURE__ */ t(`table`,{className:`border-collapse text-sm`,children:e})}),th:({children:e})=>/* @__PURE__ */ t(`th`,{className:`border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi`,children:e}),td:({children:e})=>/* @__PURE__ */ t(`td`,{className:`border border-doom-border px-2 py-1 align-top`,children:e}),img:m};function b({text:e,onFileLink:n,loadImage:r}){return/* @__PURE__ */ t(f.Provider,{value:n,children:/* @__PURE__ */ t(p.Provider,{value:r,children:/* @__PURE__ */ t(`div`,{className:`flex min-w-0 flex-col gap-2`,children:/* @__PURE__ */ t(c,{remarkPlugins:u,components:y,children:e})})})})}export{b as Markdown};
3
3
  //# sourceMappingURL=Markdown.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.mjs","names":[],"sources":["../../../../../../src/components/Markdown.tsx"],"sourcesContent":["import { createContext, type ComponentProps, isValidElement, type ReactNode, useContext } from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\n\nimport { CodeBlock } from './CodeBlock';\n\nconst REMARK_PLUGINS = [remarkGfm];\n\nconst CODE_CLASS = 'rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green';\n\n/**\n * What a piece of inline code opens, when the host knows.\n *\n * Returning undefined leaves the span as plain code, which is the answer for\n * everything a message quotes that is not a file: a class name, a flag, a\n * command. The caller decides what counts, because only it knows which files\n * the session has.\n */\nexport type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;\n\n/**\n * Passed by context rather than as a component prop so the component map stays\n * a module constant. Rebuilding that map per render gives react-markdown new\n * component identities, which remounts the whole rendered tree on every frame\n * of a streaming reply.\n */\nconst FileLinkContext = createContext<FileLinkHandler | undefined>(undefined);\n\nfunction Code({ className, children, ...rest }: ComponentProps<'code'>) {\n const onFileLink = useContext(FileLinkContext);\n // Only a single-line span with no language class is a candidate: a fenced\n // block is content, not a reference, and its own renderer owns it.\n const label = typeof children === 'string' && !children.includes('\\n') ? children : undefined;\n const open = label === undefined || className !== undefined ? undefined : onFileLink?.(label);\n if (open !== undefined && label !== undefined) {\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${label}`}\n onClick={open}\n className={`${CODE_CLASS} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`}\n >\n {label}\n </button>\n );\n }\n return (\n <code\n {...rest}\n className={`${className ?? ''} ${CODE_CLASS} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`}\n >\n {children}\n </code>\n );\n}\n\nfunction Link({ href, children }: ComponentProps<'a'>) {\n const onFileLink = useContext(FileLinkContext);\n let path = href;\n try {\n path = href === undefined ? undefined : decodeURIComponent(href);\n } catch {\n path = undefined;\n }\n const isFile = path !== undefined && path !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(path);\n const open = isFile ? onFileLink?.(path!, true) : undefined;\n if (open !== undefined)\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${path}`}\n onClick={open}\n className=\"text-doom-blue underline decoration-doom-blue/50\"\n >\n {children}\n </button>\n );\n return (\n <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"text-doom-blue underline decoration-doom-blue/50\">\n {children}\n </a>\n );\n}\n\n/** The text inside a fence, which react-markdown hands over as nested strings. */\nfunction textOf(children: ReactNode): string {\n if (typeof children === 'string') return children;\n if (Array.isArray(children)) return children.map((child) => textOf(child as ReactNode)).join('');\n return '';\n}\n\n/**\n * A fenced block, read off the <code> child that carries its language.\n *\n * The block is handed to `CodeBlock` whole rather than rendered as a styled\n * <pre> around inline code, because a fence is content with its own\n * presentation: it is coloured, it can be copied, and a mermaid fence is a\n * picture. A <pre> that came from raw HTML has no such child and keeps the\n * plain box.\n */\nfunction Pre({ children }: ComponentProps<'pre'>) {\n const code = isValidElement<{ className?: string; children?: ReactNode }>(children) ? children : undefined;\n if (code === undefined)\n return (\n <pre className=\"overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed\">\n {children}\n </pre>\n );\n // Markdown ends every fence with a newline the author did not type.\n return <CodeBlock text={textOf(code.props.children).replace(/\\n$/, '')} className={code.props.className} />;\n}\nconst COMPONENTS: Components = {\n p: ({ children }) => <p className=\"whitespace-pre-wrap break-words\">{children}</p>,\n strong: ({ children }) => <strong className=\"font-bold text-doom-hi\">{children}</strong>,\n em: ({ children }) => <em className=\"italic\">{children}</em>,\n del: ({ children }) => <del className=\"text-doom-dim line-through\">{children}</del>,\n a: Link,\n code: Code,\n pre: Pre,\n ul: ({ children }) => <ul className=\"list-disc pl-5\">{children}</ul>,\n ol: ({ children }) => <ol className=\"list-decimal pl-5\">{children}</ol>,\n li: ({ children }) => <li className=\"whitespace-pre-wrap break-words\">{children}</li>,\n h1: ({ children }) => <h1 className=\"text-lg font-bold text-doom-hi\">{children}</h1>,\n h2: ({ children }) => <h2 className=\"text-base font-bold text-doom-hi\">{children}</h2>,\n h3: ({ children }) => <h3 className=\"text-base font-bold text-doom-hi\">{children}</h3>,\n h4: ({ children }) => <h4 className=\"text-base font-bold text-doom-text\">{children}</h4>,\n blockquote: ({ children }) => (\n <blockquote className=\"border-l-2 border-doom-border pl-3 text-doom-dim\">{children}</blockquote>\n ),\n hr: () => <hr className=\"border-doom-border-soft\" />,\n table: ({ children }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"border-collapse text-sm\">{children}</table>\n </div>\n ),\n th: ({ children }) => (\n <th className=\"border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi\">{children}</th>\n ),\n td: ({ children }) => <td className=\"border border-doom-border px-2 py-1 align-top\">{children}</td>,\n img: ({ src, alt }) => (\n <img src={src} alt={alt ?? ''} className=\"max-h-[480px] max-w-full rounded border border-doom-border\" />\n ),\n};\n\n/** GitHub-flavored Markdown using the cockpit's safe, shared presentation. */\nexport function Markdown({ text, onFileLink }: { text: string; onFileLink?: FileLinkHandler }) {\n return (\n <FileLinkContext.Provider value={onFileLink}>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <ReactMarkdown remarkPlugins={REMARK_PLUGINS} components={COMPONENTS}>\n {text}\n </ReactMarkdown>\n </div>\n </FileLinkContext.Provider>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Markdown.mjs","names":[],"sources":["../../../../../../src/components/Markdown.tsx"],"sourcesContent":["import {\n createContext,\n type ComponentProps,\n isValidElement,\n type ReactNode,\n useContext,\n useEffect,\n useState,\n} from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\n\nimport { CodeBlock } from './CodeBlock';\n\nconst REMARK_PLUGINS = [remarkGfm];\n\nconst CODE_CLASS = 'rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green';\n\n/**\n * What a piece of inline code opens, when the host knows.\n *\n * Returning undefined leaves the span as plain code, which is the answer for\n * everything a message quotes that is not a file: a class name, a flag, a\n * command. The caller decides what counts, because only it knows which files\n * the session has.\n */\nexport type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;\n\n/**\n * Passed by context rather than as a component prop so the component map stays\n * a module constant. Rebuilding that map per render gives react-markdown new\n * component identities, which remounts the whole rendered tree on every frame\n * of a streaming reply.\n */\nconst FileLinkContext = createContext<FileLinkHandler | undefined>(undefined);\n\ntype ImageLoader = (path: string) => Promise<{ url: string; dispose(): void }>;\nconst ImageContext = createContext<ImageLoader | undefined>(undefined);\n\nfunction Image({ src, alt }: ComponentProps<'img'>) {\n const loader = useContext(ImageContext);\n let path: string | undefined;\n if (typeof src === 'string' && src !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(src)) {\n try {\n path = decodeURIComponent(src.split(/[?#]/, 1)[0]!);\n } catch {\n // An invalid encoded filename is shown as unavailable, never fetched.\n path = '';\n }\n }\n const [loaded, setLoaded] = useState<{ path: string; loader: ImageLoader; url?: string; failed?: boolean }>();\n useEffect(() => {\n if (loader === undefined || path === undefined) return;\n let active = true;\n let asset: Awaited<ReturnType<ImageLoader>> | undefined;\n void Promise.resolve()\n .then(() => {\n if (active) setLoaded(undefined);\n if (path === '') throw new Error('Invalid image path.');\n return loader(path);\n })\n .then((result) => {\n if (!active) {\n result.dispose();\n return;\n }\n asset = result;\n setLoaded({ path, loader, url: result.url });\n })\n .catch(() => {\n if (active) setLoaded({ path, loader, failed: true });\n });\n return () => {\n active = false;\n asset?.dispose();\n };\n }, [path, loader]);\n const current = loaded?.path === path && loaded?.loader === loader ? loaded : undefined;\n if (loader !== undefined && path !== undefined && (current?.url === undefined || current.failed))\n return (\n <span role={current?.failed ? 'alert' : 'status'} title={path}>\n {alt || path || 'Image'} ({current?.failed ? 'unavailable' : 'loading...'})\n </span>\n );\n return (\n <img\n src={loader !== undefined && path !== undefined ? current?.url : src}\n alt={alt ?? ''}\n onError={loader !== undefined && path !== undefined ? () => setLoaded({ path, loader, failed: true }) : undefined}\n className=\"max-h-[480px] max-w-full rounded border border-doom-border\"\n />\n );\n}\n\nfunction Code({ className, children, ...rest }: ComponentProps<'code'>) {\n const onFileLink = useContext(FileLinkContext);\n // Only a single-line span with no language class is a candidate: a fenced\n // block is content, not a reference, and its own renderer owns it.\n const label = typeof children === 'string' && !children.includes('\\n') ? children : undefined;\n const open = label === undefined || className !== undefined ? undefined : onFileLink?.(label);\n if (open !== undefined && label !== undefined) {\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${label}`}\n onClick={open}\n className={`${CODE_CLASS} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`}\n >\n {label}\n </button>\n );\n }\n return (\n <code\n {...rest}\n className={`${className ?? ''} ${CODE_CLASS} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`}\n >\n {children}\n </code>\n );\n}\n\nfunction Link({ href, children }: ComponentProps<'a'>) {\n const onFileLink = useContext(FileLinkContext);\n let path = href;\n try {\n path = href === undefined ? undefined : decodeURIComponent(href);\n } catch {\n path = undefined;\n }\n const isFile = path !== undefined && path !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(path);\n const open = isFile ? onFileLink?.(path!, true) : undefined;\n if (open !== undefined)\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${path}`}\n onClick={open}\n className=\"text-doom-blue underline decoration-doom-blue/50\"\n >\n {children}\n </button>\n );\n return (\n <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"text-doom-blue underline decoration-doom-blue/50\">\n {children}\n </a>\n );\n}\n\n/** The text inside a fence, which react-markdown hands over as nested strings. */\nfunction textOf(children: ReactNode): string {\n if (typeof children === 'string') return children;\n if (Array.isArray(children)) return children.map((child) => textOf(child as ReactNode)).join('');\n return '';\n}\n\n/**\n * A fenced block, read off the <code> child that carries its language.\n *\n * The block is handed to `CodeBlock` whole rather than rendered as a styled\n * <pre> around inline code, because a fence is content with its own\n * presentation: it is coloured, it can be copied, and a mermaid fence is a\n * picture. A <pre> that came from raw HTML has no such child and keeps the\n * plain box.\n */\nfunction Pre({ children }: ComponentProps<'pre'>) {\n const code = isValidElement<{ className?: string; children?: ReactNode }>(children) ? children : undefined;\n if (code === undefined)\n return (\n <pre className=\"overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed\">\n {children}\n </pre>\n );\n // Markdown ends every fence with a newline the author did not type.\n return <CodeBlock text={textOf(code.props.children).replace(/\\n$/, '')} className={code.props.className} />;\n}\nconst COMPONENTS: Components = {\n p: ({ children }) => <p className=\"whitespace-pre-wrap break-words\">{children}</p>,\n strong: ({ children }) => <strong className=\"font-bold text-doom-hi\">{children}</strong>,\n em: ({ children }) => <em className=\"italic\">{children}</em>,\n del: ({ children }) => <del className=\"text-doom-dim line-through\">{children}</del>,\n a: Link,\n code: Code,\n pre: Pre,\n ul: ({ children }) => <ul className=\"list-disc pl-5\">{children}</ul>,\n ol: ({ children }) => <ol className=\"list-decimal pl-5\">{children}</ol>,\n li: ({ children }) => <li className=\"whitespace-pre-wrap break-words\">{children}</li>,\n h1: ({ children }) => <h1 className=\"text-lg font-bold text-doom-hi\">{children}</h1>,\n h2: ({ children }) => <h2 className=\"text-base font-bold text-doom-hi\">{children}</h2>,\n h3: ({ children }) => <h3 className=\"text-base font-bold text-doom-hi\">{children}</h3>,\n h4: ({ children }) => <h4 className=\"text-base font-bold text-doom-text\">{children}</h4>,\n blockquote: ({ children }) => (\n <blockquote className=\"border-l-2 border-doom-border pl-3 text-doom-dim\">{children}</blockquote>\n ),\n hr: () => <hr className=\"border-doom-border-soft\" />,\n table: ({ children }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"border-collapse text-sm\">{children}</table>\n </div>\n ),\n th: ({ children }) => (\n <th className=\"border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi\">{children}</th>\n ),\n td: ({ children }) => <td className=\"border border-doom-border px-2 py-1 align-top\">{children}</td>,\n img: Image,\n};\n\n/** GitHub-flavored Markdown using the cockpit's safe, shared presentation. */\nexport function Markdown({\n text,\n onFileLink,\n loadImage,\n}: {\n text: string;\n onFileLink?: FileLinkHandler;\n loadImage?: ImageLoader;\n}) {\n return (\n <FileLinkContext.Provider value={onFileLink}>\n <ImageContext.Provider value={loadImage}>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <ReactMarkdown remarkPlugins={REMARK_PLUGINS} components={COMPONENTS}>\n {text}\n </ReactMarkdown>\n </div>\n </ImageContext.Provider>\n </FileLinkContext.Provider>\n );\n}\n"],"mappings":"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"}
@@ -46,6 +46,8 @@ export interface MediaPreviewProps {
46
46
  path: string;
47
47
  /** Overrides the kind inferred from the path, for a server that knows better. */
48
48
  kind?: MediaKind;
49
+ /** The fetched MIME type, required before opening image bytes as a document. */
50
+ contentType?: string;
49
51
  className?: string;
50
52
  videoControls?: boolean;
51
53
  'data-testid'?: string;
@@ -56,6 +58,6 @@ export interface MediaPreviewProps {
56
58
  /** Reports browser playback state changes for video previews. */
57
59
  onPlaybackStateChange?: (state: MediaPlaybackState) => void;
58
60
  }
59
- export declare function MediaPreview({ src, path, kind, className, controllerRef, pdfControllerRef, onPlaybackStateChange, videoControls, 'data-testid': testId }: MediaPreviewProps): import("react").JSX.Element;
61
+ export declare function MediaPreview({ src, path, kind, contentType, className, controllerRef, pdfControllerRef, onPlaybackStateChange, videoControls, 'data-testid': testId }: MediaPreviewProps): import("react").JSX.Element;
60
62
  //#endregion
61
63
  //# sourceMappingURL=MediaPreview.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MediaPreview.d.mts","names":[],"sources":["../../../../../../src/components/MediaPreview.tsx"],"mappings":";;;;;;;;;;;;iBAmBiB;EACf;EACA;EACA;;iBAGe;EACf;EACA;;iBAGe;EACf;EACA;;iBAGe;EACf,MAAM;EACN;EACA;EACA;EACA,WAAW;;iBAGI;EACf,YAAY;EACZ;EACA,OAAO;EACP,gBAAgB;EAChB;EACA,wBAAwB;EACxB,eAAe,mCAAmC,qBAAqB,QAAQ;;iBAGhE;;EAEf;;EAEA;;EAEA,OAAO;EACP;EACA;EACA;;EAEA,gCAAgC,IAAI;;EAEpC,mCAAmC,IAAI;;EAEvC,yBAAyB,OAAO;;wBA6IlB,eACd,KACA,MACA,MACA,WACA,eACA,kBACA,uBACA,8BACe,UACd,oCAAiB,IAAA"}
1
+ {"version":3,"file":"MediaPreview.d.mts","names":[],"sources":["../../../../../../src/components/MediaPreview.tsx"],"mappings":";;;;;;;;;;;;iBAmBiB;EACf;EACA;EACA;;iBAGe;EACf;EACA;;iBAGe;EACf;EACA;;iBAGe;EACf,MAAM;EACN;EACA;EACA;EACA,WAAW;;iBAGI;EACf,YAAY;EACZ;EACA,OAAO;EACP,gBAAgB;EAChB;EACA,wBAAwB;EACxB,eAAe,mCAAmC,qBAAqB,QAAQ;;iBAGhE;;EAEf;;EAEA;;EAEA,OAAO;;EAEP;EACA;EACA;EACA;;EAEA,gCAAgC,IAAI;;EAEpC,mCAAmC,IAAI;;EAEvC,yBAAyB,OAAO;;wBA6IlB,eACd,KACA,MACA,MACA,aACA,WACA,eACA,kBACA,uBACA,8BACe,UACd,oCAAiB,IAAA"}
@@ -1,2 +1,2 @@
1
- import{FileIcon as e}from"../icons/icons.mjs";import{cn as t}from"../lib/cn.mjs";import{Badge as n}from"./Badge.mjs";import{mediaKindOf as r}from"../lib/media.mjs";import{boundedMediaTime as i}from"../lib/mediaPlayback.mjs";import{PdfPreview as a}from"./PdfPreview.mjs";import{jsx as o,jsxs as s}from"react/jsx-runtime";import{useEffect as c,useImperativeHandle as l,useRef as u}from"react";const d=`max-w-full rounded border border-doom-border bg-doom-deep`;function f(e){return{playing:!e.paused&&!e.ended,currentTime:e.currentTime,duration:Number.isFinite(e.duration)?e.duration:0}}function p(e,t={current:void 0}){return{play:async()=>{let t=e.current;t!==null&&await t.play()},pause:()=>e.current?.pause(),seek:t=>{let n=e.current;n!==null&&(n.currentTime=i(t,n.duration))},getState:()=>{let t=e.current;return t===null?{playing:!1,currentTime:0,duration:0}:f(t)},isFrameReady:()=>{let n=e.current;return n!==null&&!n.seeking&&n.readyState>=2&&(n.requestVideoFrameCallback===void 0||t.current!==void 0)},getIntrinsicSize:()=>{let t=e.current;return t===null||t.videoWidth<=0||t.videoHeight<=0?null:{width:t.videoWidth,height:t.videoHeight}},captureFrame:async(n=`image/png`,r)=>{let i=e.current;if(i===null||i.videoWidth<=0||i.videoHeight<=0||i.seeking)return null;let a=t.current===void 0?void 0:{...t.current},o=a?.mediaTime??i.currentTime,s=document.createElement(`canvas`);s.width=i.videoWidth,s.height=i.videoHeight;let c=s.getContext(`2d`);if(c===null)return null;c.drawImage(i,0,0,s.width,s.height);let l=await new Promise(e=>s.toBlob(e,n,r));return l===null?null:{blob:l,width:s.width,height:s.height,timeSeconds:o,...a===void 0?{}:{metadata:a}}}}}function m({src:e,className:n,testId:r,controllerRef:i,onPlaybackStateChange:a,videoControls:s=!0}){let m=u(null),h=u(void 0),g=()=>{let e=m.current;e!==null&&a?.(f(e))},_=u(g);return c(()=>{_.current=g}),l(i,()=>p(m,h),[]),c(()=>{let e=m.current;if(e===null||e.requestVideoFrameCallback===void 0)return;let t=0,n=(r,i)=>{h.current={mediaTime:i.mediaTime,...Number.isFinite(i.presentedFrames)?{presentedFrames:i.presentedFrames}:{}},_.current(),t=e.requestVideoFrameCallback(n)};return t=e.requestVideoFrameCallback(n),()=>e.cancelVideoFrameCallback(t)},[]),/* @__PURE__ */ o(`video`,{ref:m,src:e,controls:s,playsInline:!0,preload:`metadata`,"data-testid":r,"data-kind":`video`,className:t(d,`max-h-[36rem]`,n),onPlay:g,onPause:g,onTimeUpdate:g,onDurationChange:g,onEnded:g,onLoadedMetadata:g,onLoadedData:g,onSeeking:()=>{h.current=void 0,g()},onSeeked:g,children:/* @__PURE__ */ o(`track`,{kind:`captions`})})}function h({src:i,path:c,kind:l,className:u,controllerRef:f,pdfControllerRef:p,onPlaybackStateChange:h,videoControls:g,"data-testid":_}){let v=l??r(c),y=c.split(`/`).at(-1)??c;return v===`image`?/* @__PURE__ */ o(`a`,{href:i,target:`_blank`,rel:`noreferrer`,"data-testid":_,"data-kind":v,children:/* @__PURE__ */ o(`img`,{src:i,alt:c,className:t(d,`max-h-[36rem]`,u)})}):v===`video`?/* @__PURE__ */ o(m,{src:i,className:u,controllerRef:f,onPlaybackStateChange:h,testId:_,videoControls:g}):v===`pdf`?/* @__PURE__ */ o(a,{src:i,path:c,controllerRef:p,"data-testid":_,className:u}):/* @__PURE__ */ o(n,{asChild:!0,size:`md`,"data-testid":_,"data-kind":v,className:t(`self-start bg-doom-panel text-doom-text hover:border-doom-blue/50`,u),children:/* @__PURE__ */ s(`a`,{href:i,download:y,children:[/* @__PURE__ */ o(e,{className:`h-3 w-3 shrink-0 text-doom-faint`}),`download `,y]})})}export{h as MediaPreview,f as mediaPlaybackState,p as mediaPreviewController};
1
+ import{FileIcon as e}from"../icons/icons.mjs";import{cn as t}from"../lib/cn.mjs";import{Badge as n}from"./Badge.mjs";import{mediaKindOf as r}from"../lib/media.mjs";import{boundedMediaTime as i}from"../lib/mediaPlayback.mjs";import{PdfPreview as a}from"./PdfPreview.mjs";import{jsx as o,jsxs as s}from"react/jsx-runtime";import{useEffect as c,useImperativeHandle as l,useRef as u}from"react";const d=`max-w-full rounded border border-doom-border bg-doom-deep`;function f(e){return{playing:!e.paused&&!e.ended,currentTime:e.currentTime,duration:Number.isFinite(e.duration)?e.duration:0}}function p(e,t={current:void 0}){return{play:async()=>{let t=e.current;t!==null&&await t.play()},pause:()=>e.current?.pause(),seek:t=>{let n=e.current;n!==null&&(n.currentTime=i(t,n.duration))},getState:()=>{let t=e.current;return t===null?{playing:!1,currentTime:0,duration:0}:f(t)},isFrameReady:()=>{let n=e.current;return n!==null&&!n.seeking&&n.readyState>=2&&(n.requestVideoFrameCallback===void 0||t.current!==void 0)},getIntrinsicSize:()=>{let t=e.current;return t===null||t.videoWidth<=0||t.videoHeight<=0?null:{width:t.videoWidth,height:t.videoHeight}},captureFrame:async(n=`image/png`,r)=>{let i=e.current;if(i===null||i.videoWidth<=0||i.videoHeight<=0||i.seeking)return null;let a=t.current===void 0?void 0:{...t.current},o=a?.mediaTime??i.currentTime,s=document.createElement(`canvas`);s.width=i.videoWidth,s.height=i.videoHeight;let c=s.getContext(`2d`);if(c===null)return null;c.drawImage(i,0,0,s.width,s.height);let l=await new Promise(e=>s.toBlob(e,n,r));return l===null?null:{blob:l,width:s.width,height:s.height,timeSeconds:o,...a===void 0?{}:{metadata:a}}}}}function m({src:e,className:n,testId:r,controllerRef:i,onPlaybackStateChange:a,videoControls:s=!0}){let m=u(null),h=u(void 0),g=()=>{let e=m.current;e!==null&&a?.(f(e))},_=u(g);return c(()=>{_.current=g}),l(i,()=>p(m,h),[]),c(()=>{let e=m.current;if(e===null||e.requestVideoFrameCallback===void 0)return;let t=0,n=(r,i)=>{h.current={mediaTime:i.mediaTime,...Number.isFinite(i.presentedFrames)?{presentedFrames:i.presentedFrames}:{}},_.current(),t=e.requestVideoFrameCallback(n)};return t=e.requestVideoFrameCallback(n),()=>e.cancelVideoFrameCallback(t)},[]),/* @__PURE__ */ o(`video`,{ref:m,src:e,controls:s,playsInline:!0,preload:`metadata`,"data-testid":r,"data-kind":`video`,className:t(d,`max-h-[36rem]`,n),onPlay:g,onPause:g,onTimeUpdate:g,onDurationChange:g,onEnded:g,onLoadedMetadata:g,onLoadedData:g,onSeeking:()=>{h.current=void 0,g()},onSeeked:g,children:/* @__PURE__ */ o(`track`,{kind:`captions`})})}function h({src:i,path:c,kind:l,contentType:u,className:f,controllerRef:p,pdfControllerRef:h,onPlaybackStateChange:g,videoControls:_,"data-testid":v}){let y=l??r(c),b=c.split(`/`).at(-1)??c;if(y===`image`){let e=/^image\/(?:avif|bmp|gif|jpeg|png|webp)$/i.test(u??``),n=/* @__PURE__ */ o(`img`,{src:i,alt:c,className:t(d,`max-h-[36rem]`,f)});return e?/* @__PURE__ */ o(`a`,{href:i,target:`_blank`,rel:`noreferrer`,"data-testid":v,"data-kind":y,children:n}):/* @__PURE__ */ o(`span`,{"data-testid":v,"data-kind":y,children:n})}return y===`video`?/* @__PURE__ */ o(m,{src:i,className:f,controllerRef:p,onPlaybackStateChange:g,testId:v,videoControls:_}):y===`pdf`?/* @__PURE__ */ o(a,{src:i,path:c,controllerRef:h,"data-testid":v,className:f}):/* @__PURE__ */ o(n,{asChild:!0,size:`md`,"data-testid":v,"data-kind":y,className:t(`self-start bg-doom-panel text-doom-text hover:border-doom-blue/50`,f),children:/* @__PURE__ */ s(`a`,{href:i,download:b,children:[/* @__PURE__ */ o(e,{className:`h-3 w-3 shrink-0 text-doom-faint`}),`download `,b]})})}export{h as MediaPreview,f as mediaPlaybackState,p as mediaPreviewController};
2
2
  //# sourceMappingURL=MediaPreview.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"MediaPreview.mjs","names":[],"sources":["../../../../../../src/components/MediaPreview.tsx"],"sourcesContent":["import { useEffect, useImperativeHandle, useRef } from 'react';\n\nimport { FileIcon } from '../icons/icons';\nimport { cn } from '../lib/cn';\nimport { mediaKindOf } from '../lib/media';\nimport { boundedMediaTime } from '../lib/mediaPlayback';\nimport type { MediaKind } from '../types/editor';\nimport { Badge } from './Badge';\nimport { PdfPreview, type PdfPreviewController } from './PdfPreview';\n/**\n * A file the browser can show but not edit.\n *\n * It renders bytes a server is already serving, so the caller supplies the\n * URL and this decides only which element reads it. Everything the browser\n * cannot render is a download rather than a converted approximation: a `.docx`\n * turned into HTML is a different document, and offering to edit that one\n * would be offering to overwrite the real one with it.\n */\n\nexport interface MediaPlaybackState {\n playing: boolean;\n currentTime: number;\n duration: number;\n}\n\nexport interface MediaIntrinsicSize {\n width: number;\n height: number;\n}\n\nexport interface MediaFrameMetadata {\n mediaTime: number;\n presentedFrames?: number;\n}\n\nexport interface MediaFrameCapture {\n blob: Blob;\n width: number;\n height: number;\n timeSeconds: number;\n metadata?: MediaFrameMetadata;\n}\n\nexport interface MediaPreviewController {\n play: () => Promise<void>;\n pause: () => void;\n seek: (seconds: number) => void;\n getState: () => MediaPlaybackState;\n isFrameReady: () => boolean;\n getIntrinsicSize: () => MediaIntrinsicSize | null;\n captureFrame: (type?: 'image/png' | 'image/jpeg', quality?: number) => Promise<MediaFrameCapture | null>;\n}\n\nexport interface MediaPreviewProps {\n /** Where the bytes are served from. */\n src: string;\n /** The file's path, used for the accessible name and the download name. */\n path: string;\n /** Overrides the kind inferred from the path, for a server that knows better. */\n kind?: MediaKind;\n className?: string;\n videoControls?: boolean;\n 'data-testid'?: string;\n /** Video-only controller. It is assigned while the video element is mounted. */\n controllerRef?: import('react').Ref<MediaPreviewController>;\n /** PDF-only page and geometry controller. */\n pdfControllerRef?: import('react').Ref<PdfPreviewController>;\n /** Reports browser playback state changes for video previews. */\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nconst FRAME = 'max-w-full rounded border border-doom-border bg-doom-deep';\n\ninterface VideoPreviewProps {\n src: string;\n className?: string;\n testId?: string;\n videoControls?: boolean;\n controllerRef?: import('react').Ref<MediaPreviewController>;\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nexport function mediaPlaybackState(video: HTMLVideoElement): MediaPlaybackState {\n return {\n playing: !video.paused && !video.ended,\n currentTime: video.currentTime,\n duration: Number.isFinite(video.duration) ? video.duration : 0,\n };\n}\n\nexport function mediaPreviewController(\n videoRef: { readonly current: HTMLVideoElement | null },\n frameMetadataRef: { readonly current: MediaFrameMetadata | undefined } = { current: undefined },\n): MediaPreviewController {\n return {\n play: async () => {\n const video = videoRef.current;\n if (video !== null) await video.play();\n },\n pause: () => videoRef.current?.pause(),\n seek: (seconds) => {\n const video = videoRef.current;\n if (video !== null) video.currentTime = boundedMediaTime(seconds, video.duration);\n },\n getState: () => {\n const video = videoRef.current;\n return video === null ? { playing: false, currentTime: 0, duration: 0 } : mediaPlaybackState(video);\n },\n isFrameReady: () => {\n const video = videoRef.current;\n return (\n video !== null &&\n !video.seeking &&\n video.readyState >= 2 &&\n (video.requestVideoFrameCallback === undefined || frameMetadataRef.current !== undefined)\n );\n },\n getIntrinsicSize: () => {\n const video = videoRef.current;\n return video === null || video.videoWidth <= 0 || video.videoHeight <= 0\n ? null\n : { width: video.videoWidth, height: video.videoHeight };\n },\n captureFrame: async (type = 'image/png', quality) => {\n const video = videoRef.current;\n if (video === null || video.videoWidth <= 0 || video.videoHeight <= 0 || video.seeking) return null;\n const metadata = frameMetadataRef.current === undefined ? undefined : { ...frameMetadataRef.current };\n const timeSeconds = metadata?.mediaTime ?? video.currentTime;\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n const context = canvas.getContext('2d');\n if (context === null) return null;\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (blob === null) return null;\n return {\n blob,\n width: canvas.width,\n height: canvas.height,\n timeSeconds,\n ...(metadata === undefined ? {} : { metadata }),\n };\n },\n };\n}\n\nfunction VideoPreview({\n src,\n className,\n testId,\n controllerRef,\n onPlaybackStateChange,\n videoControls = true,\n}: VideoPreviewProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const frameMetadataRef = useRef<MediaFrameMetadata | undefined>(undefined);\n const reportState = () => {\n const video = videoRef.current;\n if (video !== null) onPlaybackStateChange?.(mediaPlaybackState(video));\n };\n const reportStateRef = useRef(reportState);\n useEffect(() => {\n reportStateRef.current = reportState;\n });\n useImperativeHandle(controllerRef, () => mediaPreviewController(videoRef, frameMetadataRef), []);\n useEffect(() => {\n const video = videoRef.current;\n if (video === null || video.requestVideoFrameCallback === undefined) return;\n let request = 0;\n const report: VideoFrameRequestCallback = (_now, metadata) => {\n frameMetadataRef.current = {\n mediaTime: metadata.mediaTime,\n ...(Number.isFinite(metadata.presentedFrames) ? { presentedFrames: metadata.presentedFrames } : {}),\n };\n reportStateRef.current();\n request = video.requestVideoFrameCallback(report);\n };\n request = video.requestVideoFrameCallback(report);\n return () => video.cancelVideoFrameCallback(request);\n }, []);\n return (\n <video\n ref={videoRef}\n src={src}\n controls={videoControls}\n playsInline\n preload=\"metadata\"\n data-testid={testId}\n data-kind=\"video\"\n className={cn(FRAME, 'max-h-[36rem]', className)}\n onPlay={reportState}\n onPause={reportState}\n onTimeUpdate={reportState}\n onDurationChange={reportState}\n onEnded={reportState}\n onLoadedMetadata={reportState}\n onLoadedData={reportState}\n onSeeking={() => {\n frameMetadataRef.current = undefined;\n reportState();\n }}\n onSeeked={reportState}\n >\n <track kind=\"captions\" />\n </video>\n );\n}\n\nexport function MediaPreview({\n src,\n path,\n kind,\n className,\n controllerRef,\n pdfControllerRef,\n onPlaybackStateChange,\n videoControls,\n 'data-testid': testId,\n}: MediaPreviewProps) {\n const resolved = kind ?? mediaKindOf(path);\n const name = path.split('/').at(-1) ?? path;\n\n if (resolved === 'image') {\n return (\n <a href={src} target=\"_blank\" rel=\"noreferrer\" data-testid={testId} data-kind={resolved}>\n <img src={src} alt={path} className={cn(FRAME, 'max-h-[36rem]', className)} />\n </a>\n );\n }\n if (resolved === 'video') {\n return (\n <VideoPreview\n src={src}\n className={className}\n controllerRef={controllerRef}\n onPlaybackStateChange={onPlaybackStateChange}\n testId={testId}\n videoControls={videoControls}\n />\n );\n }\n if (resolved === 'pdf') {\n return (\n <PdfPreview src={src} path={path} controllerRef={pdfControllerRef} data-testid={testId} className={className} />\n );\n }\n return (\n <Badge\n asChild\n size=\"md\"\n data-testid={testId}\n data-kind={resolved}\n className={cn('self-start bg-doom-panel text-doom-text hover:border-doom-blue/50', className)}\n >\n <a href={src} download={name}>\n <FileIcon className=\"h-3 w-3 shrink-0 text-doom-faint\" />\n download {name}\n </a>\n </Badge>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MediaPreview.mjs","names":[],"sources":["../../../../../../src/components/MediaPreview.tsx"],"sourcesContent":["import { useEffect, useImperativeHandle, useRef } from 'react';\n\nimport { FileIcon } from '../icons/icons';\nimport { cn } from '../lib/cn';\nimport { mediaKindOf } from '../lib/media';\nimport { boundedMediaTime } from '../lib/mediaPlayback';\nimport type { MediaKind } from '../types/editor';\nimport { Badge } from './Badge';\nimport { PdfPreview, type PdfPreviewController } from './PdfPreview';\n/**\n * A file the browser can show but not edit.\n *\n * It renders bytes a server is already serving, so the caller supplies the\n * URL and this decides only which element reads it. Everything the browser\n * cannot render is a download rather than a converted approximation: a `.docx`\n * turned into HTML is a different document, and offering to edit that one\n * would be offering to overwrite the real one with it.\n */\n\nexport interface MediaPlaybackState {\n playing: boolean;\n currentTime: number;\n duration: number;\n}\n\nexport interface MediaIntrinsicSize {\n width: number;\n height: number;\n}\n\nexport interface MediaFrameMetadata {\n mediaTime: number;\n presentedFrames?: number;\n}\n\nexport interface MediaFrameCapture {\n blob: Blob;\n width: number;\n height: number;\n timeSeconds: number;\n metadata?: MediaFrameMetadata;\n}\n\nexport interface MediaPreviewController {\n play: () => Promise<void>;\n pause: () => void;\n seek: (seconds: number) => void;\n getState: () => MediaPlaybackState;\n isFrameReady: () => boolean;\n getIntrinsicSize: () => MediaIntrinsicSize | null;\n captureFrame: (type?: 'image/png' | 'image/jpeg', quality?: number) => Promise<MediaFrameCapture | null>;\n}\n\nexport interface MediaPreviewProps {\n /** Where the bytes are served from. */\n src: string;\n /** The file's path, used for the accessible name and the download name. */\n path: string;\n /** Overrides the kind inferred from the path, for a server that knows better. */\n kind?: MediaKind;\n /** The fetched MIME type, required before opening image bytes as a document. */\n contentType?: string;\n className?: string;\n videoControls?: boolean;\n 'data-testid'?: string;\n /** Video-only controller. It is assigned while the video element is mounted. */\n controllerRef?: import('react').Ref<MediaPreviewController>;\n /** PDF-only page and geometry controller. */\n pdfControllerRef?: import('react').Ref<PdfPreviewController>;\n /** Reports browser playback state changes for video previews. */\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nconst FRAME = 'max-w-full rounded border border-doom-border bg-doom-deep';\n\ninterface VideoPreviewProps {\n src: string;\n className?: string;\n testId?: string;\n videoControls?: boolean;\n controllerRef?: import('react').Ref<MediaPreviewController>;\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nexport function mediaPlaybackState(video: HTMLVideoElement): MediaPlaybackState {\n return {\n playing: !video.paused && !video.ended,\n currentTime: video.currentTime,\n duration: Number.isFinite(video.duration) ? video.duration : 0,\n };\n}\n\nexport function mediaPreviewController(\n videoRef: { readonly current: HTMLVideoElement | null },\n frameMetadataRef: { readonly current: MediaFrameMetadata | undefined } = { current: undefined },\n): MediaPreviewController {\n return {\n play: async () => {\n const video = videoRef.current;\n if (video !== null) await video.play();\n },\n pause: () => videoRef.current?.pause(),\n seek: (seconds) => {\n const video = videoRef.current;\n if (video !== null) video.currentTime = boundedMediaTime(seconds, video.duration);\n },\n getState: () => {\n const video = videoRef.current;\n return video === null ? { playing: false, currentTime: 0, duration: 0 } : mediaPlaybackState(video);\n },\n isFrameReady: () => {\n const video = videoRef.current;\n return (\n video !== null &&\n !video.seeking &&\n video.readyState >= 2 &&\n (video.requestVideoFrameCallback === undefined || frameMetadataRef.current !== undefined)\n );\n },\n getIntrinsicSize: () => {\n const video = videoRef.current;\n return video === null || video.videoWidth <= 0 || video.videoHeight <= 0\n ? null\n : { width: video.videoWidth, height: video.videoHeight };\n },\n captureFrame: async (type = 'image/png', quality) => {\n const video = videoRef.current;\n if (video === null || video.videoWidth <= 0 || video.videoHeight <= 0 || video.seeking) return null;\n const metadata = frameMetadataRef.current === undefined ? undefined : { ...frameMetadataRef.current };\n const timeSeconds = metadata?.mediaTime ?? video.currentTime;\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n const context = canvas.getContext('2d');\n if (context === null) return null;\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (blob === null) return null;\n return {\n blob,\n width: canvas.width,\n height: canvas.height,\n timeSeconds,\n ...(metadata === undefined ? {} : { metadata }),\n };\n },\n };\n}\n\nfunction VideoPreview({\n src,\n className,\n testId,\n controllerRef,\n onPlaybackStateChange,\n videoControls = true,\n}: VideoPreviewProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const frameMetadataRef = useRef<MediaFrameMetadata | undefined>(undefined);\n const reportState = () => {\n const video = videoRef.current;\n if (video !== null) onPlaybackStateChange?.(mediaPlaybackState(video));\n };\n const reportStateRef = useRef(reportState);\n useEffect(() => {\n reportStateRef.current = reportState;\n });\n useImperativeHandle(controllerRef, () => mediaPreviewController(videoRef, frameMetadataRef), []);\n useEffect(() => {\n const video = videoRef.current;\n if (video === null || video.requestVideoFrameCallback === undefined) return;\n let request = 0;\n const report: VideoFrameRequestCallback = (_now, metadata) => {\n frameMetadataRef.current = {\n mediaTime: metadata.mediaTime,\n ...(Number.isFinite(metadata.presentedFrames) ? { presentedFrames: metadata.presentedFrames } : {}),\n };\n reportStateRef.current();\n request = video.requestVideoFrameCallback(report);\n };\n request = video.requestVideoFrameCallback(report);\n return () => video.cancelVideoFrameCallback(request);\n }, []);\n return (\n <video\n ref={videoRef}\n src={src}\n controls={videoControls}\n playsInline\n preload=\"metadata\"\n data-testid={testId}\n data-kind=\"video\"\n className={cn(FRAME, 'max-h-[36rem]', className)}\n onPlay={reportState}\n onPause={reportState}\n onTimeUpdate={reportState}\n onDurationChange={reportState}\n onEnded={reportState}\n onLoadedMetadata={reportState}\n onLoadedData={reportState}\n onSeeking={() => {\n frameMetadataRef.current = undefined;\n reportState();\n }}\n onSeeked={reportState}\n >\n <track kind=\"captions\" />\n </video>\n );\n}\n\nexport function MediaPreview({\n src,\n path,\n kind,\n contentType,\n className,\n controllerRef,\n pdfControllerRef,\n onPlaybackStateChange,\n videoControls,\n 'data-testid': testId,\n}: MediaPreviewProps) {\n const resolved = kind ?? mediaKindOf(path);\n const name = path.split('/').at(-1) ?? path;\n\n if (resolved === 'image') {\n // A symlink's target determines the MIME type, so the path cannot establish SVG safety.\n const raster = /^image\\/(?:avif|bmp|gif|jpeg|png|webp)$/i.test(contentType ?? '');\n const image = <img src={src} alt={path} className={cn(FRAME, 'max-h-[36rem]', className)} />;\n return raster ? (\n <a href={src} target=\"_blank\" rel=\"noreferrer\" data-testid={testId} data-kind={resolved}>\n {image}\n </a>\n ) : (\n <span data-testid={testId} data-kind={resolved}>\n {image}\n </span>\n );\n }\n if (resolved === 'video') {\n return (\n <VideoPreview\n src={src}\n className={className}\n controllerRef={controllerRef}\n onPlaybackStateChange={onPlaybackStateChange}\n testId={testId}\n videoControls={videoControls}\n />\n );\n }\n if (resolved === 'pdf') {\n return (\n <PdfPreview src={src} path={path} controllerRef={pdfControllerRef} data-testid={testId} className={className} />\n );\n }\n return (\n <Badge\n asChild\n size=\"md\"\n data-testid={testId}\n data-kind={resolved}\n className={cn('self-start bg-doom-panel text-doom-text hover:border-doom-blue/50', className)}\n >\n <a href={src} download={name}>\n <FileIcon className=\"h-3 w-3 shrink-0 text-doom-faint\" />\n download {name}\n </a>\n </Badge>\n );\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./CodeBlock.cjs");let n=require("react/jsx-runtime"),r=require("react"),i=require("react-markdown");i=e.__toESM(i,1);let a=require("remark-gfm");a=e.__toESM(a,1);const o=[a.default],s=`rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green`,c=(0,r.createContext)(void 0);function l({className:e,children:t,...i}){let a=(0,r.useContext)(c),o=typeof t==`string`&&!t.includes(`
2
- `)?t:void 0,l=o===void 0||e!==void 0?void 0:a?.(o);return l!==void 0&&o!==void 0?/* @__PURE__ */ (0,n.jsx)(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${o}`,onClick:l,className:`${s} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`,children:o}):/* @__PURE__ */ (0,n.jsx)(`code`,{...i,className:`${e??``} ${s} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`,children:t})}function u({href:e,children:t}){let i=(0,r.useContext)(c),a=e;try{a=e===void 0?void 0:decodeURIComponent(e)}catch{a=void 0}let o=a!==void 0&&a!==``&&!/^(?:[a-z][a-z\d+.-]*:|\/\/|#|\?)/i.test(a)?i?.(a,!0):void 0;return o===void 0?/* @__PURE__ */ (0,n.jsx)(`a`,{href:e,target:`_blank`,rel:`noreferrer`,className:`text-doom-blue underline decoration-doom-blue/50`,children:t}):/* @__PURE__ */ (0,n.jsx)(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${a}`,onClick:o,className:`text-doom-blue underline decoration-doom-blue/50`,children:t})}function d(e){return typeof e==`string`?e:Array.isArray(e)?e.map(e=>d(e)).join(``):``}function f({children:e}){let i=(0,r.isValidElement)(e)?e:void 0;return i===void 0?/* @__PURE__ */ (0,n.jsx)(`pre`,{className:`overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed`,children:e}):/* @__PURE__ */ (0,n.jsx)(t.CodeBlock,{text:d(i.props.children).replace(/\n$/,``),className:i.props.className})}const p={p:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`p`,{className:`whitespace-pre-wrap break-words`,children:e}),strong:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`strong`,{className:`font-bold text-doom-hi`,children:e}),em:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`em`,{className:`italic`,children:e}),del:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`del`,{className:`text-doom-dim line-through`,children:e}),a:u,code:l,pre:f,ul:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`ul`,{className:`list-disc pl-5`,children:e}),ol:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`ol`,{className:`list-decimal pl-5`,children:e}),li:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`li`,{className:`whitespace-pre-wrap break-words`,children:e}),h1:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h1`,{className:`text-lg font-bold text-doom-hi`,children:e}),h2:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h2`,{className:`text-base font-bold text-doom-hi`,children:e}),h3:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h3`,{className:`text-base font-bold text-doom-hi`,children:e}),h4:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h4`,{className:`text-base font-bold text-doom-text`,children:e}),blockquote:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`blockquote`,{className:`border-l-2 border-doom-border pl-3 text-doom-dim`,children:e}),hr:()=>/* @__PURE__ */ (0,n.jsx)(`hr`,{className:`border-doom-border-soft`}),table:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`div`,{className:`overflow-x-auto`,children:/* @__PURE__ */ (0,n.jsx)(`table`,{className:`border-collapse text-sm`,children:e})}),th:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`th`,{className:`border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi`,children:e}),td:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`td`,{className:`border border-doom-border px-2 py-1 align-top`,children:e}),img:({src:e,alt:t})=>/* @__PURE__ */ (0,n.jsx)(`img`,{src:e,alt:t??``,className:`max-h-[480px] max-w-full rounded border border-doom-border`})};function m({text:e,onFileLink:t}){return/* @__PURE__ */ (0,n.jsx)(c.Provider,{value:t,children:/* @__PURE__ */ (0,n.jsx)(`div`,{className:`flex min-w-0 flex-col gap-2`,children:/* @__PURE__ */ (0,n.jsx)(i.default,{remarkPlugins:o,components:p,children:e})})})}exports.Markdown=m;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./CodeBlock.cjs");let n=require("react/jsx-runtime"),r=require("react"),i=require("react-markdown");i=e.__toESM(i,1);let a=require("remark-gfm");a=e.__toESM(a,1);const o=[a.default],s=`rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green`,c=(0,r.createContext)(void 0),l=(0,r.createContext)(void 0);function u({src:e,alt:t}){let i=(0,r.useContext)(l),a;if(typeof e==`string`&&e!==``&&!/^(?:[a-z][a-z\d+.-]*:|\/\/|#|\?)/i.test(e))try{a=decodeURIComponent(e.split(/[?#]/,1)[0])}catch{a=``}let[o,s]=(0,r.useState)();(0,r.useEffect)(()=>{if(i===void 0||a===void 0)return;let e=!0,t;return Promise.resolve().then(()=>{if(e&&s(void 0),a===``)throw Error(`Invalid image path.`);return i(a)}).then(n=>{if(!e){n.dispose();return}t=n,s({path:a,loader:i,url:n.url})}).catch(()=>{e&&s({path:a,loader:i,failed:!0})}),()=>{e=!1,t?.dispose()}},[a,i]);let c=o?.path===a&&o?.loader===i?o:void 0;return i!==void 0&&a!==void 0&&(c?.url===void 0||c.failed)?/* @__PURE__ */ (0,n.jsxs)(`span`,{role:c?.failed?`alert`:`status`,title:a,children:[t||a||`Image`,` (`,c?.failed?`unavailable`:`loading...`,`)`]}):/* @__PURE__ */ (0,n.jsx)(`img`,{src:i!==void 0&&a!==void 0?c?.url:e,alt:t??``,onError:i!==void 0&&a!==void 0?()=>s({path:a,loader:i,failed:!0}):void 0,className:`max-h-[480px] max-w-full rounded border border-doom-border`})}function d({className:e,children:t,...i}){let a=(0,r.useContext)(c),o=typeof t==`string`&&!t.includes(`
2
+ `)?t:void 0,l=o===void 0||e!==void 0?void 0:a?.(o);return l!==void 0&&o!==void 0?/* @__PURE__ */ (0,n.jsx)(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${o}`,onClick:l,className:`${s} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`,children:o}):/* @__PURE__ */ (0,n.jsx)(`code`,{...i,className:`${e??``} ${s} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`,children:t})}function f({href:e,children:t}){let i=(0,r.useContext)(c),a=e;try{a=e===void 0?void 0:decodeURIComponent(e)}catch{a=void 0}let o=a!==void 0&&a!==``&&!/^(?:[a-z][a-z\d+.-]*:|\/\/|#|\?)/i.test(a)?i?.(a,!0):void 0;return o===void 0?/* @__PURE__ */ (0,n.jsx)(`a`,{href:e,target:`_blank`,rel:`noreferrer`,className:`text-doom-blue underline decoration-doom-blue/50`,children:t}):/* @__PURE__ */ (0,n.jsx)(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${a}`,onClick:o,className:`text-doom-blue underline decoration-doom-blue/50`,children:t})}function p(e){return typeof e==`string`?e:Array.isArray(e)?e.map(e=>p(e)).join(``):``}function m({children:e}){let i=(0,r.isValidElement)(e)?e:void 0;return i===void 0?/* @__PURE__ */ (0,n.jsx)(`pre`,{className:`overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed`,children:e}):/* @__PURE__ */ (0,n.jsx)(t.CodeBlock,{text:p(i.props.children).replace(/\n$/,``),className:i.props.className})}const h={p:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`p`,{className:`whitespace-pre-wrap break-words`,children:e}),strong:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`strong`,{className:`font-bold text-doom-hi`,children:e}),em:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`em`,{className:`italic`,children:e}),del:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`del`,{className:`text-doom-dim line-through`,children:e}),a:f,code:d,pre:m,ul:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`ul`,{className:`list-disc pl-5`,children:e}),ol:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`ol`,{className:`list-decimal pl-5`,children:e}),li:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`li`,{className:`whitespace-pre-wrap break-words`,children:e}),h1:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h1`,{className:`text-lg font-bold text-doom-hi`,children:e}),h2:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h2`,{className:`text-base font-bold text-doom-hi`,children:e}),h3:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h3`,{className:`text-base font-bold text-doom-hi`,children:e}),h4:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h4`,{className:`text-base font-bold text-doom-text`,children:e}),blockquote:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`blockquote`,{className:`border-l-2 border-doom-border pl-3 text-doom-dim`,children:e}),hr:()=>/* @__PURE__ */ (0,n.jsx)(`hr`,{className:`border-doom-border-soft`}),table:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`div`,{className:`overflow-x-auto`,children:/* @__PURE__ */ (0,n.jsx)(`table`,{className:`border-collapse text-sm`,children:e})}),th:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`th`,{className:`border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi`,children:e}),td:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`td`,{className:`border border-doom-border px-2 py-1 align-top`,children:e}),img:u};function g({text:e,onFileLink:t,loadImage:r}){return/* @__PURE__ */ (0,n.jsx)(c.Provider,{value:t,children:/* @__PURE__ */ (0,n.jsx)(l.Provider,{value:r,children:/* @__PURE__ */ (0,n.jsx)(`div`,{className:`flex min-w-0 flex-col gap-2`,children:/* @__PURE__ */ (0,n.jsx)(i.default,{remarkPlugins:o,components:h,children:e})})})})}exports.Markdown=g;
3
3
  //# sourceMappingURL=Markdown.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.cjs","names":["remarkGfm","createContext","useContext","isValidElement","CodeBlock","ReactMarkdown"],"sources":["../../../src/components/Markdown.tsx"],"sourcesContent":["import { createContext, type ComponentProps, isValidElement, type ReactNode, useContext } from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\n\nimport { CodeBlock } from './CodeBlock';\n\nconst REMARK_PLUGINS = [remarkGfm];\n\nconst CODE_CLASS = 'rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green';\n\n/**\n * What a piece of inline code opens, when the host knows.\n *\n * Returning undefined leaves the span as plain code, which is the answer for\n * everything a message quotes that is not a file: a class name, a flag, a\n * command. The caller decides what counts, because only it knows which files\n * the session has.\n */\nexport type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;\n\n/**\n * Passed by context rather than as a component prop so the component map stays\n * a module constant. Rebuilding that map per render gives react-markdown new\n * component identities, which remounts the whole rendered tree on every frame\n * of a streaming reply.\n */\nconst FileLinkContext = createContext<FileLinkHandler | undefined>(undefined);\n\nfunction Code({ className, children, ...rest }: ComponentProps<'code'>) {\n const onFileLink = useContext(FileLinkContext);\n // Only a single-line span with no language class is a candidate: a fenced\n // block is content, not a reference, and its own renderer owns it.\n const label = typeof children === 'string' && !children.includes('\\n') ? children : undefined;\n const open = label === undefined || className !== undefined ? undefined : onFileLink?.(label);\n if (open !== undefined && label !== undefined) {\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${label}`}\n onClick={open}\n className={`${CODE_CLASS} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`}\n >\n {label}\n </button>\n );\n }\n return (\n <code\n {...rest}\n className={`${className ?? ''} ${CODE_CLASS} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`}\n >\n {children}\n </code>\n );\n}\n\nfunction Link({ href, children }: ComponentProps<'a'>) {\n const onFileLink = useContext(FileLinkContext);\n let path = href;\n try {\n path = href === undefined ? undefined : decodeURIComponent(href);\n } catch {\n path = undefined;\n }\n const isFile = path !== undefined && path !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(path);\n const open = isFile ? onFileLink?.(path!, true) : undefined;\n if (open !== undefined)\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${path}`}\n onClick={open}\n className=\"text-doom-blue underline decoration-doom-blue/50\"\n >\n {children}\n </button>\n );\n return (\n <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"text-doom-blue underline decoration-doom-blue/50\">\n {children}\n </a>\n );\n}\n\n/** The text inside a fence, which react-markdown hands over as nested strings. */\nfunction textOf(children: ReactNode): string {\n if (typeof children === 'string') return children;\n if (Array.isArray(children)) return children.map((child) => textOf(child as ReactNode)).join('');\n return '';\n}\n\n/**\n * A fenced block, read off the <code> child that carries its language.\n *\n * The block is handed to `CodeBlock` whole rather than rendered as a styled\n * <pre> around inline code, because a fence is content with its own\n * presentation: it is coloured, it can be copied, and a mermaid fence is a\n * picture. A <pre> that came from raw HTML has no such child and keeps the\n * plain box.\n */\nfunction Pre({ children }: ComponentProps<'pre'>) {\n const code = isValidElement<{ className?: string; children?: ReactNode }>(children) ? children : undefined;\n if (code === undefined)\n return (\n <pre className=\"overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed\">\n {children}\n </pre>\n );\n // Markdown ends every fence with a newline the author did not type.\n return <CodeBlock text={textOf(code.props.children).replace(/\\n$/, '')} className={code.props.className} />;\n}\nconst COMPONENTS: Components = {\n p: ({ children }) => <p className=\"whitespace-pre-wrap break-words\">{children}</p>,\n strong: ({ children }) => <strong className=\"font-bold text-doom-hi\">{children}</strong>,\n em: ({ children }) => <em className=\"italic\">{children}</em>,\n del: ({ children }) => <del className=\"text-doom-dim line-through\">{children}</del>,\n a: Link,\n code: Code,\n pre: Pre,\n ul: ({ children }) => <ul className=\"list-disc pl-5\">{children}</ul>,\n ol: ({ children }) => <ol className=\"list-decimal pl-5\">{children}</ol>,\n li: ({ children }) => <li className=\"whitespace-pre-wrap break-words\">{children}</li>,\n h1: ({ children }) => <h1 className=\"text-lg font-bold text-doom-hi\">{children}</h1>,\n h2: ({ children }) => <h2 className=\"text-base font-bold text-doom-hi\">{children}</h2>,\n h3: ({ children }) => <h3 className=\"text-base font-bold text-doom-hi\">{children}</h3>,\n h4: ({ children }) => <h4 className=\"text-base font-bold text-doom-text\">{children}</h4>,\n blockquote: ({ children }) => (\n <blockquote className=\"border-l-2 border-doom-border pl-3 text-doom-dim\">{children}</blockquote>\n ),\n hr: () => <hr className=\"border-doom-border-soft\" />,\n table: ({ children }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"border-collapse text-sm\">{children}</table>\n </div>\n ),\n th: ({ children }) => (\n <th className=\"border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi\">{children}</th>\n ),\n td: ({ children }) => <td className=\"border border-doom-border px-2 py-1 align-top\">{children}</td>,\n img: ({ src, alt }) => (\n <img src={src} alt={alt ?? ''} className=\"max-h-[480px] max-w-full rounded border border-doom-border\" />\n ),\n};\n\n/** GitHub-flavored Markdown using the cockpit's safe, shared presentation. */\nexport function Markdown({ text, onFileLink }: { text: string; onFileLink?: FileLinkHandler }) {\n return (\n <FileLinkContext.Provider value={onFileLink}>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <ReactMarkdown remarkPlugins={REMARK_PLUGINS} components={COMPONENTS}>\n {text}\n </ReactMarkdown>\n </div>\n </FileLinkContext.Provider>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Markdown.cjs","names":["remarkGfm","createContext","useContext","useState","isValidElement","CodeBlock","ReactMarkdown"],"sources":["../../../src/components/Markdown.tsx"],"sourcesContent":["import {\n createContext,\n type ComponentProps,\n isValidElement,\n type ReactNode,\n useContext,\n useEffect,\n useState,\n} from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\n\nimport { CodeBlock } from './CodeBlock';\n\nconst REMARK_PLUGINS = [remarkGfm];\n\nconst CODE_CLASS = 'rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green';\n\n/**\n * What a piece of inline code opens, when the host knows.\n *\n * Returning undefined leaves the span as plain code, which is the answer for\n * everything a message quotes that is not a file: a class name, a flag, a\n * command. The caller decides what counts, because only it knows which files\n * the session has.\n */\nexport type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;\n\n/**\n * Passed by context rather than as a component prop so the component map stays\n * a module constant. Rebuilding that map per render gives react-markdown new\n * component identities, which remounts the whole rendered tree on every frame\n * of a streaming reply.\n */\nconst FileLinkContext = createContext<FileLinkHandler | undefined>(undefined);\n\ntype ImageLoader = (path: string) => Promise<{ url: string; dispose(): void }>;\nconst ImageContext = createContext<ImageLoader | undefined>(undefined);\n\nfunction Image({ src, alt }: ComponentProps<'img'>) {\n const loader = useContext(ImageContext);\n let path: string | undefined;\n if (typeof src === 'string' && src !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(src)) {\n try {\n path = decodeURIComponent(src.split(/[?#]/, 1)[0]!);\n } catch {\n // An invalid encoded filename is shown as unavailable, never fetched.\n path = '';\n }\n }\n const [loaded, setLoaded] = useState<{ path: string; loader: ImageLoader; url?: string; failed?: boolean }>();\n useEffect(() => {\n if (loader === undefined || path === undefined) return;\n let active = true;\n let asset: Awaited<ReturnType<ImageLoader>> | undefined;\n void Promise.resolve()\n .then(() => {\n if (active) setLoaded(undefined);\n if (path === '') throw new Error('Invalid image path.');\n return loader(path);\n })\n .then((result) => {\n if (!active) {\n result.dispose();\n return;\n }\n asset = result;\n setLoaded({ path, loader, url: result.url });\n })\n .catch(() => {\n if (active) setLoaded({ path, loader, failed: true });\n });\n return () => {\n active = false;\n asset?.dispose();\n };\n }, [path, loader]);\n const current = loaded?.path === path && loaded?.loader === loader ? loaded : undefined;\n if (loader !== undefined && path !== undefined && (current?.url === undefined || current.failed))\n return (\n <span role={current?.failed ? 'alert' : 'status'} title={path}>\n {alt || path || 'Image'} ({current?.failed ? 'unavailable' : 'loading...'})\n </span>\n );\n return (\n <img\n src={loader !== undefined && path !== undefined ? current?.url : src}\n alt={alt ?? ''}\n onError={loader !== undefined && path !== undefined ? () => setLoaded({ path, loader, failed: true }) : undefined}\n className=\"max-h-[480px] max-w-full rounded border border-doom-border\"\n />\n );\n}\n\nfunction Code({ className, children, ...rest }: ComponentProps<'code'>) {\n const onFileLink = useContext(FileLinkContext);\n // Only a single-line span with no language class is a candidate: a fenced\n // block is content, not a reference, and its own renderer owns it.\n const label = typeof children === 'string' && !children.includes('\\n') ? children : undefined;\n const open = label === undefined || className !== undefined ? undefined : onFileLink?.(label);\n if (open !== undefined && label !== undefined) {\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${label}`}\n onClick={open}\n className={`${CODE_CLASS} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`}\n >\n {label}\n </button>\n );\n }\n return (\n <code\n {...rest}\n className={`${className ?? ''} ${CODE_CLASS} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`}\n >\n {children}\n </code>\n );\n}\n\nfunction Link({ href, children }: ComponentProps<'a'>) {\n const onFileLink = useContext(FileLinkContext);\n let path = href;\n try {\n path = href === undefined ? undefined : decodeURIComponent(href);\n } catch {\n path = undefined;\n }\n const isFile = path !== undefined && path !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(path);\n const open = isFile ? onFileLink?.(path!, true) : undefined;\n if (open !== undefined)\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${path}`}\n onClick={open}\n className=\"text-doom-blue underline decoration-doom-blue/50\"\n >\n {children}\n </button>\n );\n return (\n <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"text-doom-blue underline decoration-doom-blue/50\">\n {children}\n </a>\n );\n}\n\n/** The text inside a fence, which react-markdown hands over as nested strings. */\nfunction textOf(children: ReactNode): string {\n if (typeof children === 'string') return children;\n if (Array.isArray(children)) return children.map((child) => textOf(child as ReactNode)).join('');\n return '';\n}\n\n/**\n * A fenced block, read off the <code> child that carries its language.\n *\n * The block is handed to `CodeBlock` whole rather than rendered as a styled\n * <pre> around inline code, because a fence is content with its own\n * presentation: it is coloured, it can be copied, and a mermaid fence is a\n * picture. A <pre> that came from raw HTML has no such child and keeps the\n * plain box.\n */\nfunction Pre({ children }: ComponentProps<'pre'>) {\n const code = isValidElement<{ className?: string; children?: ReactNode }>(children) ? children : undefined;\n if (code === undefined)\n return (\n <pre className=\"overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed\">\n {children}\n </pre>\n );\n // Markdown ends every fence with a newline the author did not type.\n return <CodeBlock text={textOf(code.props.children).replace(/\\n$/, '')} className={code.props.className} />;\n}\nconst COMPONENTS: Components = {\n p: ({ children }) => <p className=\"whitespace-pre-wrap break-words\">{children}</p>,\n strong: ({ children }) => <strong className=\"font-bold text-doom-hi\">{children}</strong>,\n em: ({ children }) => <em className=\"italic\">{children}</em>,\n del: ({ children }) => <del className=\"text-doom-dim line-through\">{children}</del>,\n a: Link,\n code: Code,\n pre: Pre,\n ul: ({ children }) => <ul className=\"list-disc pl-5\">{children}</ul>,\n ol: ({ children }) => <ol className=\"list-decimal pl-5\">{children}</ol>,\n li: ({ children }) => <li className=\"whitespace-pre-wrap break-words\">{children}</li>,\n h1: ({ children }) => <h1 className=\"text-lg font-bold text-doom-hi\">{children}</h1>,\n h2: ({ children }) => <h2 className=\"text-base font-bold text-doom-hi\">{children}</h2>,\n h3: ({ children }) => <h3 className=\"text-base font-bold text-doom-hi\">{children}</h3>,\n h4: ({ children }) => <h4 className=\"text-base font-bold text-doom-text\">{children}</h4>,\n blockquote: ({ children }) => (\n <blockquote className=\"border-l-2 border-doom-border pl-3 text-doom-dim\">{children}</blockquote>\n ),\n hr: () => <hr className=\"border-doom-border-soft\" />,\n table: ({ children }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"border-collapse text-sm\">{children}</table>\n </div>\n ),\n th: ({ children }) => (\n <th className=\"border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi\">{children}</th>\n ),\n td: ({ children }) => <td className=\"border border-doom-border px-2 py-1 align-top\">{children}</td>,\n img: Image,\n};\n\n/** GitHub-flavored Markdown using the cockpit's safe, shared presentation. */\nexport function Markdown({\n text,\n onFileLink,\n loadImage,\n}: {\n text: string;\n onFileLink?: FileLinkHandler;\n loadImage?: ImageLoader;\n}) {\n return (\n <FileLinkContext.Provider value={onFileLink}>\n <ImageContext.Provider value={loadImage}>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <ReactMarkdown remarkPlugins={REMARK_PLUGINS} components={COMPONENTS}>\n {text}\n </ReactMarkdown>\n </div>\n </ImageContext.Provider>\n </FileLinkContext.Provider>\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- require("../icons/icons.cjs");const e=require("../lib/cn.cjs"),t=require("./Badge.cjs"),n=require("../lib/media.cjs"),r=require("../lib/mediaPlayback.cjs"),i=require("./PdfPreview.cjs");let a=require("react/jsx-runtime"),o=require("react"),s=require("lucide-react");const c=`max-w-full rounded border border-doom-border bg-doom-deep`;function l(e){return{playing:!e.paused&&!e.ended,currentTime:e.currentTime,duration:Number.isFinite(e.duration)?e.duration:0}}function u(e,t={current:void 0}){return{play:async()=>{let t=e.current;t!==null&&await t.play()},pause:()=>e.current?.pause(),seek:t=>{let n=e.current;n!==null&&(n.currentTime=r.boundedMediaTime(t,n.duration))},getState:()=>{let t=e.current;return t===null?{playing:!1,currentTime:0,duration:0}:l(t)},isFrameReady:()=>{let n=e.current;return n!==null&&!n.seeking&&n.readyState>=2&&(n.requestVideoFrameCallback===void 0||t.current!==void 0)},getIntrinsicSize:()=>{let t=e.current;return t===null||t.videoWidth<=0||t.videoHeight<=0?null:{width:t.videoWidth,height:t.videoHeight}},captureFrame:async(n=`image/png`,r)=>{let i=e.current;if(i===null||i.videoWidth<=0||i.videoHeight<=0||i.seeking)return null;let a=t.current===void 0?void 0:{...t.current},o=a?.mediaTime??i.currentTime,s=document.createElement(`canvas`);s.width=i.videoWidth,s.height=i.videoHeight;let c=s.getContext(`2d`);if(c===null)return null;c.drawImage(i,0,0,s.width,s.height);let l=await new Promise(e=>s.toBlob(e,n,r));return l===null?null:{blob:l,width:s.width,height:s.height,timeSeconds:o,...a===void 0?{}:{metadata:a}}}}}function d({src:t,className:n,testId:r,controllerRef:i,onPlaybackStateChange:s,videoControls:d=!0}){let f=(0,o.useRef)(null),p=(0,o.useRef)(void 0),m=()=>{let e=f.current;e!==null&&s?.(l(e))},h=(0,o.useRef)(m);return(0,o.useEffect)(()=>{h.current=m}),(0,o.useImperativeHandle)(i,()=>u(f,p),[]),(0,o.useEffect)(()=>{let e=f.current;if(e===null||e.requestVideoFrameCallback===void 0)return;let t=0,n=(r,i)=>{p.current={mediaTime:i.mediaTime,...Number.isFinite(i.presentedFrames)?{presentedFrames:i.presentedFrames}:{}},h.current(),t=e.requestVideoFrameCallback(n)};return t=e.requestVideoFrameCallback(n),()=>e.cancelVideoFrameCallback(t)},[]),/* @__PURE__ */ (0,a.jsx)(`video`,{ref:f,src:t,controls:d,playsInline:!0,preload:`metadata`,"data-testid":r,"data-kind":`video`,className:e.cn(c,`max-h-[36rem]`,n),onPlay:m,onPause:m,onTimeUpdate:m,onDurationChange:m,onEnded:m,onLoadedMetadata:m,onLoadedData:m,onSeeking:()=>{p.current=void 0,m()},onSeeked:m,children:/* @__PURE__ */ (0,a.jsx)(`track`,{kind:`captions`})})}function f({src:r,path:o,kind:l,className:u,controllerRef:f,pdfControllerRef:p,onPlaybackStateChange:m,videoControls:h,"data-testid":g}){let _=l??n.mediaKindOf(o),v=o.split(`/`).at(-1)??o;return _===`image`?/* @__PURE__ */ (0,a.jsx)(`a`,{href:r,target:`_blank`,rel:`noreferrer`,"data-testid":g,"data-kind":_,children:/* @__PURE__ */ (0,a.jsx)(`img`,{src:r,alt:o,className:e.cn(c,`max-h-[36rem]`,u)})}):_===`video`?/* @__PURE__ */ (0,a.jsx)(d,{src:r,className:u,controllerRef:f,onPlaybackStateChange:m,testId:g,videoControls:h}):_===`pdf`?/* @__PURE__ */ (0,a.jsx)(i.PdfPreview,{src:r,path:o,controllerRef:p,"data-testid":g,className:u}):/* @__PURE__ */ (0,a.jsx)(t.Badge,{asChild:!0,size:`md`,"data-testid":g,"data-kind":_,className:e.cn(`self-start bg-doom-panel text-doom-text hover:border-doom-blue/50`,u),children:/* @__PURE__ */ (0,a.jsxs)(`a`,{href:r,download:v,children:[/* @__PURE__ */ (0,a.jsx)(s.FileText,{className:`h-3 w-3 shrink-0 text-doom-faint`}),`download `,v]})})}exports.MediaPreview=f,exports.mediaPlaybackState=l,exports.mediaPreviewController=u;
1
+ require("../icons/icons.cjs");const e=require("../lib/cn.cjs"),t=require("./Badge.cjs"),n=require("../lib/media.cjs"),r=require("../lib/mediaPlayback.cjs"),i=require("./PdfPreview.cjs");let a=require("react/jsx-runtime"),o=require("react"),s=require("lucide-react");const c=`max-w-full rounded border border-doom-border bg-doom-deep`;function l(e){return{playing:!e.paused&&!e.ended,currentTime:e.currentTime,duration:Number.isFinite(e.duration)?e.duration:0}}function u(e,t={current:void 0}){return{play:async()=>{let t=e.current;t!==null&&await t.play()},pause:()=>e.current?.pause(),seek:t=>{let n=e.current;n!==null&&(n.currentTime=r.boundedMediaTime(t,n.duration))},getState:()=>{let t=e.current;return t===null?{playing:!1,currentTime:0,duration:0}:l(t)},isFrameReady:()=>{let n=e.current;return n!==null&&!n.seeking&&n.readyState>=2&&(n.requestVideoFrameCallback===void 0||t.current!==void 0)},getIntrinsicSize:()=>{let t=e.current;return t===null||t.videoWidth<=0||t.videoHeight<=0?null:{width:t.videoWidth,height:t.videoHeight}},captureFrame:async(n=`image/png`,r)=>{let i=e.current;if(i===null||i.videoWidth<=0||i.videoHeight<=0||i.seeking)return null;let a=t.current===void 0?void 0:{...t.current},o=a?.mediaTime??i.currentTime,s=document.createElement(`canvas`);s.width=i.videoWidth,s.height=i.videoHeight;let c=s.getContext(`2d`);if(c===null)return null;c.drawImage(i,0,0,s.width,s.height);let l=await new Promise(e=>s.toBlob(e,n,r));return l===null?null:{blob:l,width:s.width,height:s.height,timeSeconds:o,...a===void 0?{}:{metadata:a}}}}}function d({src:t,className:n,testId:r,controllerRef:i,onPlaybackStateChange:s,videoControls:d=!0}){let f=(0,o.useRef)(null),p=(0,o.useRef)(void 0),m=()=>{let e=f.current;e!==null&&s?.(l(e))},h=(0,o.useRef)(m);return(0,o.useEffect)(()=>{h.current=m}),(0,o.useImperativeHandle)(i,()=>u(f,p),[]),(0,o.useEffect)(()=>{let e=f.current;if(e===null||e.requestVideoFrameCallback===void 0)return;let t=0,n=(r,i)=>{p.current={mediaTime:i.mediaTime,...Number.isFinite(i.presentedFrames)?{presentedFrames:i.presentedFrames}:{}},h.current(),t=e.requestVideoFrameCallback(n)};return t=e.requestVideoFrameCallback(n),()=>e.cancelVideoFrameCallback(t)},[]),/* @__PURE__ */ (0,a.jsx)(`video`,{ref:f,src:t,controls:d,playsInline:!0,preload:`metadata`,"data-testid":r,"data-kind":`video`,className:e.cn(c,`max-h-[36rem]`,n),onPlay:m,onPause:m,onTimeUpdate:m,onDurationChange:m,onEnded:m,onLoadedMetadata:m,onLoadedData:m,onSeeking:()=>{p.current=void 0,m()},onSeeked:m,children:/* @__PURE__ */ (0,a.jsx)(`track`,{kind:`captions`})})}function f({src:r,path:o,kind:l,contentType:u,className:f,controllerRef:p,pdfControllerRef:m,onPlaybackStateChange:h,videoControls:g,"data-testid":_}){let v=l??n.mediaKindOf(o),y=o.split(`/`).at(-1)??o;if(v===`image`){let t=/^image\/(?:avif|bmp|gif|jpeg|png|webp)$/i.test(u??``),n=/* @__PURE__ */ (0,a.jsx)(`img`,{src:r,alt:o,className:e.cn(c,`max-h-[36rem]`,f)});return t?/* @__PURE__ */ (0,a.jsx)(`a`,{href:r,target:`_blank`,rel:`noreferrer`,"data-testid":_,"data-kind":v,children:n}):/* @__PURE__ */ (0,a.jsx)(`span`,{"data-testid":_,"data-kind":v,children:n})}return v===`video`?/* @__PURE__ */ (0,a.jsx)(d,{src:r,className:f,controllerRef:p,onPlaybackStateChange:h,testId:_,videoControls:g}):v===`pdf`?/* @__PURE__ */ (0,a.jsx)(i.PdfPreview,{src:r,path:o,controllerRef:m,"data-testid":_,className:f}):/* @__PURE__ */ (0,a.jsx)(t.Badge,{asChild:!0,size:`md`,"data-testid":_,"data-kind":v,className:e.cn(`self-start bg-doom-panel text-doom-text hover:border-doom-blue/50`,f),children:/* @__PURE__ */ (0,a.jsxs)(`a`,{href:r,download:y,children:[/* @__PURE__ */ (0,a.jsx)(s.FileText,{className:`h-3 w-3 shrink-0 text-doom-faint`}),`download `,y]})})}exports.MediaPreview=f,exports.mediaPlaybackState=l,exports.mediaPreviewController=u;
2
2
  //# sourceMappingURL=MediaPreview.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"MediaPreview.cjs","names":["boundedMediaTime","useRef","cn","mediaKindOf","PdfPreview","Badge","FileIcon"],"sources":["../../../src/components/MediaPreview.tsx"],"sourcesContent":["import { useEffect, useImperativeHandle, useRef } from 'react';\n\nimport { FileIcon } from '../icons/icons';\nimport { cn } from '../lib/cn';\nimport { mediaKindOf } from '../lib/media';\nimport { boundedMediaTime } from '../lib/mediaPlayback';\nimport type { MediaKind } from '../types/editor';\nimport { Badge } from './Badge';\nimport { PdfPreview, type PdfPreviewController } from './PdfPreview';\n/**\n * A file the browser can show but not edit.\n *\n * It renders bytes a server is already serving, so the caller supplies the\n * URL and this decides only which element reads it. Everything the browser\n * cannot render is a download rather than a converted approximation: a `.docx`\n * turned into HTML is a different document, and offering to edit that one\n * would be offering to overwrite the real one with it.\n */\n\nexport interface MediaPlaybackState {\n playing: boolean;\n currentTime: number;\n duration: number;\n}\n\nexport interface MediaIntrinsicSize {\n width: number;\n height: number;\n}\n\nexport interface MediaFrameMetadata {\n mediaTime: number;\n presentedFrames?: number;\n}\n\nexport interface MediaFrameCapture {\n blob: Blob;\n width: number;\n height: number;\n timeSeconds: number;\n metadata?: MediaFrameMetadata;\n}\n\nexport interface MediaPreviewController {\n play: () => Promise<void>;\n pause: () => void;\n seek: (seconds: number) => void;\n getState: () => MediaPlaybackState;\n isFrameReady: () => boolean;\n getIntrinsicSize: () => MediaIntrinsicSize | null;\n captureFrame: (type?: 'image/png' | 'image/jpeg', quality?: number) => Promise<MediaFrameCapture | null>;\n}\n\nexport interface MediaPreviewProps {\n /** Where the bytes are served from. */\n src: string;\n /** The file's path, used for the accessible name and the download name. */\n path: string;\n /** Overrides the kind inferred from the path, for a server that knows better. */\n kind?: MediaKind;\n className?: string;\n videoControls?: boolean;\n 'data-testid'?: string;\n /** Video-only controller. It is assigned while the video element is mounted. */\n controllerRef?: import('react').Ref<MediaPreviewController>;\n /** PDF-only page and geometry controller. */\n pdfControllerRef?: import('react').Ref<PdfPreviewController>;\n /** Reports browser playback state changes for video previews. */\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nconst FRAME = 'max-w-full rounded border border-doom-border bg-doom-deep';\n\ninterface VideoPreviewProps {\n src: string;\n className?: string;\n testId?: string;\n videoControls?: boolean;\n controllerRef?: import('react').Ref<MediaPreviewController>;\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nexport function mediaPlaybackState(video: HTMLVideoElement): MediaPlaybackState {\n return {\n playing: !video.paused && !video.ended,\n currentTime: video.currentTime,\n duration: Number.isFinite(video.duration) ? video.duration : 0,\n };\n}\n\nexport function mediaPreviewController(\n videoRef: { readonly current: HTMLVideoElement | null },\n frameMetadataRef: { readonly current: MediaFrameMetadata | undefined } = { current: undefined },\n): MediaPreviewController {\n return {\n play: async () => {\n const video = videoRef.current;\n if (video !== null) await video.play();\n },\n pause: () => videoRef.current?.pause(),\n seek: (seconds) => {\n const video = videoRef.current;\n if (video !== null) video.currentTime = boundedMediaTime(seconds, video.duration);\n },\n getState: () => {\n const video = videoRef.current;\n return video === null ? { playing: false, currentTime: 0, duration: 0 } : mediaPlaybackState(video);\n },\n isFrameReady: () => {\n const video = videoRef.current;\n return (\n video !== null &&\n !video.seeking &&\n video.readyState >= 2 &&\n (video.requestVideoFrameCallback === undefined || frameMetadataRef.current !== undefined)\n );\n },\n getIntrinsicSize: () => {\n const video = videoRef.current;\n return video === null || video.videoWidth <= 0 || video.videoHeight <= 0\n ? null\n : { width: video.videoWidth, height: video.videoHeight };\n },\n captureFrame: async (type = 'image/png', quality) => {\n const video = videoRef.current;\n if (video === null || video.videoWidth <= 0 || video.videoHeight <= 0 || video.seeking) return null;\n const metadata = frameMetadataRef.current === undefined ? undefined : { ...frameMetadataRef.current };\n const timeSeconds = metadata?.mediaTime ?? video.currentTime;\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n const context = canvas.getContext('2d');\n if (context === null) return null;\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (blob === null) return null;\n return {\n blob,\n width: canvas.width,\n height: canvas.height,\n timeSeconds,\n ...(metadata === undefined ? {} : { metadata }),\n };\n },\n };\n}\n\nfunction VideoPreview({\n src,\n className,\n testId,\n controllerRef,\n onPlaybackStateChange,\n videoControls = true,\n}: VideoPreviewProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const frameMetadataRef = useRef<MediaFrameMetadata | undefined>(undefined);\n const reportState = () => {\n const video = videoRef.current;\n if (video !== null) onPlaybackStateChange?.(mediaPlaybackState(video));\n };\n const reportStateRef = useRef(reportState);\n useEffect(() => {\n reportStateRef.current = reportState;\n });\n useImperativeHandle(controllerRef, () => mediaPreviewController(videoRef, frameMetadataRef), []);\n useEffect(() => {\n const video = videoRef.current;\n if (video === null || video.requestVideoFrameCallback === undefined) return;\n let request = 0;\n const report: VideoFrameRequestCallback = (_now, metadata) => {\n frameMetadataRef.current = {\n mediaTime: metadata.mediaTime,\n ...(Number.isFinite(metadata.presentedFrames) ? { presentedFrames: metadata.presentedFrames } : {}),\n };\n reportStateRef.current();\n request = video.requestVideoFrameCallback(report);\n };\n request = video.requestVideoFrameCallback(report);\n return () => video.cancelVideoFrameCallback(request);\n }, []);\n return (\n <video\n ref={videoRef}\n src={src}\n controls={videoControls}\n playsInline\n preload=\"metadata\"\n data-testid={testId}\n data-kind=\"video\"\n className={cn(FRAME, 'max-h-[36rem]', className)}\n onPlay={reportState}\n onPause={reportState}\n onTimeUpdate={reportState}\n onDurationChange={reportState}\n onEnded={reportState}\n onLoadedMetadata={reportState}\n onLoadedData={reportState}\n onSeeking={() => {\n frameMetadataRef.current = undefined;\n reportState();\n }}\n onSeeked={reportState}\n >\n <track kind=\"captions\" />\n </video>\n );\n}\n\nexport function MediaPreview({\n src,\n path,\n kind,\n className,\n controllerRef,\n pdfControllerRef,\n onPlaybackStateChange,\n videoControls,\n 'data-testid': testId,\n}: MediaPreviewProps) {\n const resolved = kind ?? mediaKindOf(path);\n const name = path.split('/').at(-1) ?? path;\n\n if (resolved === 'image') {\n return (\n <a href={src} target=\"_blank\" rel=\"noreferrer\" data-testid={testId} data-kind={resolved}>\n <img src={src} alt={path} className={cn(FRAME, 'max-h-[36rem]', className)} />\n </a>\n );\n }\n if (resolved === 'video') {\n return (\n <VideoPreview\n src={src}\n className={className}\n controllerRef={controllerRef}\n onPlaybackStateChange={onPlaybackStateChange}\n testId={testId}\n videoControls={videoControls}\n />\n );\n }\n if (resolved === 'pdf') {\n return (\n <PdfPreview src={src} path={path} controllerRef={pdfControllerRef} data-testid={testId} className={className} />\n );\n }\n return (\n <Badge\n asChild\n size=\"md\"\n data-testid={testId}\n data-kind={resolved}\n className={cn('self-start bg-doom-panel text-doom-text hover:border-doom-blue/50', className)}\n >\n <a href={src} download={name}>\n <FileIcon className=\"h-3 w-3 shrink-0 text-doom-faint\" />\n download {name}\n </a>\n </Badge>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MediaPreview.cjs","names":["boundedMediaTime","useRef","cn","mediaKindOf","PdfPreview","Badge","FileIcon"],"sources":["../../../src/components/MediaPreview.tsx"],"sourcesContent":["import { useEffect, useImperativeHandle, useRef } from 'react';\n\nimport { FileIcon } from '../icons/icons';\nimport { cn } from '../lib/cn';\nimport { mediaKindOf } from '../lib/media';\nimport { boundedMediaTime } from '../lib/mediaPlayback';\nimport type { MediaKind } from '../types/editor';\nimport { Badge } from './Badge';\nimport { PdfPreview, type PdfPreviewController } from './PdfPreview';\n/**\n * A file the browser can show but not edit.\n *\n * It renders bytes a server is already serving, so the caller supplies the\n * URL and this decides only which element reads it. Everything the browser\n * cannot render is a download rather than a converted approximation: a `.docx`\n * turned into HTML is a different document, and offering to edit that one\n * would be offering to overwrite the real one with it.\n */\n\nexport interface MediaPlaybackState {\n playing: boolean;\n currentTime: number;\n duration: number;\n}\n\nexport interface MediaIntrinsicSize {\n width: number;\n height: number;\n}\n\nexport interface MediaFrameMetadata {\n mediaTime: number;\n presentedFrames?: number;\n}\n\nexport interface MediaFrameCapture {\n blob: Blob;\n width: number;\n height: number;\n timeSeconds: number;\n metadata?: MediaFrameMetadata;\n}\n\nexport interface MediaPreviewController {\n play: () => Promise<void>;\n pause: () => void;\n seek: (seconds: number) => void;\n getState: () => MediaPlaybackState;\n isFrameReady: () => boolean;\n getIntrinsicSize: () => MediaIntrinsicSize | null;\n captureFrame: (type?: 'image/png' | 'image/jpeg', quality?: number) => Promise<MediaFrameCapture | null>;\n}\n\nexport interface MediaPreviewProps {\n /** Where the bytes are served from. */\n src: string;\n /** The file's path, used for the accessible name and the download name. */\n path: string;\n /** Overrides the kind inferred from the path, for a server that knows better. */\n kind?: MediaKind;\n /** The fetched MIME type, required before opening image bytes as a document. */\n contentType?: string;\n className?: string;\n videoControls?: boolean;\n 'data-testid'?: string;\n /** Video-only controller. It is assigned while the video element is mounted. */\n controllerRef?: import('react').Ref<MediaPreviewController>;\n /** PDF-only page and geometry controller. */\n pdfControllerRef?: import('react').Ref<PdfPreviewController>;\n /** Reports browser playback state changes for video previews. */\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nconst FRAME = 'max-w-full rounded border border-doom-border bg-doom-deep';\n\ninterface VideoPreviewProps {\n src: string;\n className?: string;\n testId?: string;\n videoControls?: boolean;\n controllerRef?: import('react').Ref<MediaPreviewController>;\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nexport function mediaPlaybackState(video: HTMLVideoElement): MediaPlaybackState {\n return {\n playing: !video.paused && !video.ended,\n currentTime: video.currentTime,\n duration: Number.isFinite(video.duration) ? video.duration : 0,\n };\n}\n\nexport function mediaPreviewController(\n videoRef: { readonly current: HTMLVideoElement | null },\n frameMetadataRef: { readonly current: MediaFrameMetadata | undefined } = { current: undefined },\n): MediaPreviewController {\n return {\n play: async () => {\n const video = videoRef.current;\n if (video !== null) await video.play();\n },\n pause: () => videoRef.current?.pause(),\n seek: (seconds) => {\n const video = videoRef.current;\n if (video !== null) video.currentTime = boundedMediaTime(seconds, video.duration);\n },\n getState: () => {\n const video = videoRef.current;\n return video === null ? { playing: false, currentTime: 0, duration: 0 } : mediaPlaybackState(video);\n },\n isFrameReady: () => {\n const video = videoRef.current;\n return (\n video !== null &&\n !video.seeking &&\n video.readyState >= 2 &&\n (video.requestVideoFrameCallback === undefined || frameMetadataRef.current !== undefined)\n );\n },\n getIntrinsicSize: () => {\n const video = videoRef.current;\n return video === null || video.videoWidth <= 0 || video.videoHeight <= 0\n ? null\n : { width: video.videoWidth, height: video.videoHeight };\n },\n captureFrame: async (type = 'image/png', quality) => {\n const video = videoRef.current;\n if (video === null || video.videoWidth <= 0 || video.videoHeight <= 0 || video.seeking) return null;\n const metadata = frameMetadataRef.current === undefined ? undefined : { ...frameMetadataRef.current };\n const timeSeconds = metadata?.mediaTime ?? video.currentTime;\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n const context = canvas.getContext('2d');\n if (context === null) return null;\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (blob === null) return null;\n return {\n blob,\n width: canvas.width,\n height: canvas.height,\n timeSeconds,\n ...(metadata === undefined ? {} : { metadata }),\n };\n },\n };\n}\n\nfunction VideoPreview({\n src,\n className,\n testId,\n controllerRef,\n onPlaybackStateChange,\n videoControls = true,\n}: VideoPreviewProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const frameMetadataRef = useRef<MediaFrameMetadata | undefined>(undefined);\n const reportState = () => {\n const video = videoRef.current;\n if (video !== null) onPlaybackStateChange?.(mediaPlaybackState(video));\n };\n const reportStateRef = useRef(reportState);\n useEffect(() => {\n reportStateRef.current = reportState;\n });\n useImperativeHandle(controllerRef, () => mediaPreviewController(videoRef, frameMetadataRef), []);\n useEffect(() => {\n const video = videoRef.current;\n if (video === null || video.requestVideoFrameCallback === undefined) return;\n let request = 0;\n const report: VideoFrameRequestCallback = (_now, metadata) => {\n frameMetadataRef.current = {\n mediaTime: metadata.mediaTime,\n ...(Number.isFinite(metadata.presentedFrames) ? { presentedFrames: metadata.presentedFrames } : {}),\n };\n reportStateRef.current();\n request = video.requestVideoFrameCallback(report);\n };\n request = video.requestVideoFrameCallback(report);\n return () => video.cancelVideoFrameCallback(request);\n }, []);\n return (\n <video\n ref={videoRef}\n src={src}\n controls={videoControls}\n playsInline\n preload=\"metadata\"\n data-testid={testId}\n data-kind=\"video\"\n className={cn(FRAME, 'max-h-[36rem]', className)}\n onPlay={reportState}\n onPause={reportState}\n onTimeUpdate={reportState}\n onDurationChange={reportState}\n onEnded={reportState}\n onLoadedMetadata={reportState}\n onLoadedData={reportState}\n onSeeking={() => {\n frameMetadataRef.current = undefined;\n reportState();\n }}\n onSeeked={reportState}\n >\n <track kind=\"captions\" />\n </video>\n );\n}\n\nexport function MediaPreview({\n src,\n path,\n kind,\n contentType,\n className,\n controllerRef,\n pdfControllerRef,\n onPlaybackStateChange,\n videoControls,\n 'data-testid': testId,\n}: MediaPreviewProps) {\n const resolved = kind ?? mediaKindOf(path);\n const name = path.split('/').at(-1) ?? path;\n\n if (resolved === 'image') {\n // A symlink's target determines the MIME type, so the path cannot establish SVG safety.\n const raster = /^image\\/(?:avif|bmp|gif|jpeg|png|webp)$/i.test(contentType ?? '');\n const image = <img src={src} alt={path} className={cn(FRAME, 'max-h-[36rem]', className)} />;\n return raster ? (\n <a href={src} target=\"_blank\" rel=\"noreferrer\" data-testid={testId} data-kind={resolved}>\n {image}\n </a>\n ) : (\n <span data-testid={testId} data-kind={resolved}>\n {image}\n </span>\n );\n }\n if (resolved === 'video') {\n return (\n <VideoPreview\n src={src}\n className={className}\n controllerRef={controllerRef}\n onPlaybackStateChange={onPlaybackStateChange}\n testId={testId}\n videoControls={videoControls}\n />\n );\n }\n if (resolved === 'pdf') {\n return (\n <PdfPreview src={src} path={path} controllerRef={pdfControllerRef} data-testid={testId} className={className} />\n );\n }\n return (\n <Badge\n asChild\n size=\"md\"\n data-testid={testId}\n data-kind={resolved}\n className={cn('self-start bg-doom-panel text-doom-text hover:border-doom-blue/50', className)}\n >\n <a href={src} download={name}>\n <FileIcon className=\"h-3 w-3 shrink-0 text-doom-faint\" />\n download {name}\n </a>\n </Badge>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-web-components",
3
- "version": "0.0.1-alpha.61",
3
+ "version": "0.0.1-alpha.63",
4
4
  "description": "Shared web components and theme tokens for the DoomPi cockpit and its web plugins: shadcn-style primitives on Radix, tuned to the Doom palette, with runtime theme configs.",
5
5
  "keywords": [
6
6
  "ai",
@@ -91,7 +91,7 @@
91
91
  "tailwind-merge": "3.7.0"
92
92
  },
93
93
  "devDependencies": {
94
- "@agimon-ai/doompi-core": "0.0.1-alpha.103",
94
+ "@agimon-ai/doompi-core": "0.0.1-alpha.105",
95
95
  "@agimon-ai/vibe-lint-plugin-doom-web": "0.0.1-alpha.89",
96
96
  "@types/node": "26.6.2",
97
97
  "@types/react": "19.3.0",