diff --git a/extensions/void/src/sidebar/MarkdownRender.tsx b/extensions/void/src/sidebar/MarkdownRender.tsx deleted file mode 100644 index e9cc2b96..00000000 --- a/extensions/void/src/sidebar/MarkdownRender.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import React, { JSX, useState } from 'react'; -import { MarkedToken, Token, TokensList } from 'marked'; -import { awaitVSCodeResponse, getVSCodeAPI } from './getVscodeApi'; - - -// code block with Apply button at top -export const BlockCode = ({ text, disableApplyButton = false }: { text: string, disableApplyButton?: boolean }) => { - return
- {disableApplyButton ? null :
- -
} -
-
-				{text}
-			
-
-
-} - -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; diff --git a/extensions/void/src/sidebar/Sidebar.tsx b/extensions/void/src/sidebar/Sidebar.tsx index e96006d0..cfc3e3b4 100644 --- a/extensions/void/src/sidebar/Sidebar.tsx +++ b/extensions/void/src/sidebar/Sidebar.tsx @@ -4,7 +4,8 @@ import { Command, 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' @@ -82,7 +83,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => { if (role === 'user') { chatbubbleContents = <> - {chatMessage.selection?.selectionStr && } + {chatMessage.selection?.selectionStr && } {children} } @@ -278,7 +279,7 @@ const Sidebar = () => { > X - + )} 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 diff --git a/extensions/void/src/sidebar/styles.css b/extensions/void/src/sidebar/styles.css index 8e6ff79f..6a1d2159 100644 --- a/extensions/void/src/sidebar/styles.css +++ b/extensions/void/src/sidebar/styles.css @@ -3,33 +3,37 @@ @tailwind utilities; html { - font-size: var(--vscode-font-size); + font-size: var(--vscode-font-size); } .btn { - @apply cursor-pointer transition-colors; + @apply cursor-pointer transition-colors; - &.btn-primary { - @apply bg-vscode-button-bg text-vscode-button-fg; + &.btn-primary { + @apply bg-vscode-button-bg text-vscode-button-fg; - &:not(:disabled) { - @apply hover:bg-vscode-button-hoverBg; - } - } + &:not(:disabled) { + @apply hover:bg-vscode-button-hoverBg; + } + } - &.btn-secondary { - @apply bg-vscode-button-secondary-bg text-vscode-button-secondary-fg; + &.btn-sm { + @apply px-3 py-1 text-sm; + } - &:not(:disabled) { - @apply hover:bg-vscode-button-secondary-hoverBg; - } - } + &.btn-secondary { + @apply bg-vscode-button-secondary-bg text-vscode-button-secondary-fg; - &:disabled { - @apply opacity-75 cursor-not-allowed; - } + &:not(:disabled) { + @apply hover:bg-vscode-button-secondary-hoverBg; + } + } + + &:disabled { + @apply opacity-75 cursor-not-allowed; + } } .input { - @apply bg-vscode-input-bg text-vscode-input-fg border-vscode-input-border; + @apply bg-vscode-input-bg text-vscode-input-fg border-vscode-input-border; }