diff --git a/extensions/void/src/sidebar/Sidebar.tsx b/extensions/void/src/sidebar/Sidebar.tsx index b068cf12..f0ef90b5 100644 --- a/extensions/void/src/sidebar/Sidebar.tsx +++ b/extensions/void/src/sidebar/Sidebar.tsx @@ -4,7 +4,8 @@ import { File, Selection, WebviewMessage } from "../shared_types" import { awaitVSCodeResponse, getVSCodeAPI, resolveAwaitingVSCodeResponse } from "./getVscodeApi" import { marked } from 'marked'; -import { MarkdownRender, BlockCode } from "./MarkdownRender"; +import MarkdownRender from "./markdown/MarkdownRender"; +import BlockCode from "./markdown/BlockCode"; import * as vscode from 'vscode' import { FilesSelector, IncludedFiles } from "./components/Files"; @@ -232,7 +233,7 @@ const Sidebar = () => { {/* selected code */} {!!selection?.selectionStr && ( diff --git a/extensions/void/src/sidebar/markdown/BlockCode.tsx b/extensions/void/src/sidebar/markdown/BlockCode.tsx new file mode 100644 index 00000000..5c45a9f5 --- /dev/null +++ b/extensions/void/src/sidebar/markdown/BlockCode.tsx @@ -0,0 +1,72 @@ +import React, { useCallback, useEffect, useState } from "react" +import { getVSCodeAPI } from "../getVscodeApi" + +enum CopyButtonState { + Copy = "Copy", + Copied = "Copied!", + Error = "Could not copy", +} + +const COPY_FEEDBACK_TIMEOUT = 1000 + +// code block with toolbar (Apply, Copy, etc) at top +const BlockCode = ({ + text, + hideToolbar = false, +}: { + text: string + hideToolbar?: boolean +}) => { + const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy) + + useEffect(() => { + if (copyButtonState !== CopyButtonState.Copy) { + setTimeout(() => { + setCopyButtonState(CopyButtonState.Copy) + }, COPY_FEEDBACK_TIMEOUT) + } + }, [copyButtonState]) + + const onCopy = useCallback(() => { + navigator.clipboard.writeText(text).then( + () => { + setCopyButtonState(CopyButtonState.Copied) + }, + () => { + setCopyButtonState(CopyButtonState.Error) + } + ) + }, [text]) + + return ( +
+ {!hideToolbar && ( +
+
+ + +
+
+ )} +
+
{text}
+
+
+ ) +} + +export default BlockCode diff --git a/extensions/void/src/sidebar/markdown/MarkdownRender.tsx b/extensions/void/src/sidebar/markdown/MarkdownRender.tsx new file mode 100644 index 00000000..c972342e --- /dev/null +++ b/extensions/void/src/sidebar/markdown/MarkdownRender.tsx @@ -0,0 +1,164 @@ +import React, { JSX, useState } from "react" +import { MarkedToken, Token, TokensList } from "marked" +import { awaitVSCodeResponse, getVSCodeAPI } from "../getVscodeApi" +import BlockCode from "./BlockCode" + +const Render = ({ token }: { token: Token }) => { + // deal with built-in tokens first (assume marked token) + const t = token as MarkedToken + + if (t.type === "space") { + return {t.raw} + } + + if (t.type === "code") { + return + } + + if (t.type === "heading") { + const HeadingTag = `h${t.depth}` as keyof JSX.IntrinsicElements + return {t.text} + } + + if (t.type === "table") { + return ( + + + + {t.header.map((cell: any, index: number) => ( + + ))} + + + + {t.rows.map((row: any[], rowIndex: number) => ( + + {row.map((cell: any, cellIndex: number) => ( + + ))} + + ))} + +
+ {cell.raw} +
+ {cell.raw} +
+ ) + } + + if (t.type === "hr") { + return
+ } + + if (t.type === "blockquote") { + return
{t.text}
+ } + + if (t.type === "list") { + const ListTag = t.ordered ? "ol" : "ul" + return ( + + {t.items.map((item, index) => ( +
  • + {item.task && ( + + )} + {item.text} +
  • + ))} +
    + ) + } + + if (t.type === "paragraph") { + return ( +

    + {t.tokens.map((token, index) => ( + + ))} +

    + ) + } + + if (t.type === "html") { + return ( +
    +				{``}
    +				{t.raw}
    +				{``}
    +			
    + ) + } + + if (t.type === "text" || t.type === "escape") { + return {t.raw} + } + + if (t.type === "def") { + return null // Definitions are typically not rendered + } + + if (t.type === "link") { + return ( + + {t.text} + + ) + } + + if (t.type === "image") { + return {t.text} + } + + if (t.type === "strong") { + return {t.text} + } + + if (t.type === "em") { + return {t.text} + } + + // inline code + if (t.type === "codespan") { + return ( + + {t.text} + + ) + } + + if (t.type === "br") { + return
    + } + + if (t.type === "del") { + return {t.text} + } + + // default + return ( +
    + Unknown type: + {t.raw} +
    + ) +} + +const MarkdownRender = ({ tokens }: { tokens: TokensList }) => { + return ( + <> + {tokens.map((token, index) => ( + + ))} + + ) +} + +export default MarkdownRender