summaryrefslogtreecommitdiffhomepage
path: root/packages/web/src
diff options
context:
space:
mode:
authorJay V <[email protected]>2025-07-04 13:33:23 -0400
committerJay V <[email protected]>2025-07-04 13:53:25 -0400
commit143fd8e07635274403874479a53f0b124ac5f433 (patch)
tree1078e5c7b5aec24aefedbc5cede0ae110718f348 /packages/web/src
parent06dba28bd69134535ad4a1482b7bbda9f26f96d6 (diff)
downloadopencode-143fd8e07635274403874479a53f0b124ac5f433.tar.gz
opencode-143fd8e07635274403874479a53f0b124ac5f433.zip
docs: share improve markdown rendering of ai responses
Diffstat (limited to 'packages/web/src')
-rw-r--r--packages/web/src/components/MarkdownView.tsx32
-rw-r--r--packages/web/src/components/Share.tsx9
-rw-r--r--packages/web/src/components/markdownview.module.css48
-rw-r--r--packages/web/src/components/share.module.css7
4 files changed, 74 insertions, 22 deletions
diff --git a/packages/web/src/components/MarkdownView.tsx b/packages/web/src/components/MarkdownView.tsx
index 5e21c0d7e..7a63bc0cb 100644
--- a/packages/web/src/components/MarkdownView.tsx
+++ b/packages/web/src/components/MarkdownView.tsx
@@ -1,21 +1,39 @@
import { type JSX, splitProps, createResource } from "solid-js"
import { marked } from "marked"
+import markedShiki from "marked-shiki"
+import { codeToHtml } from "shiki"
+import { transformerNotationDiff } from "@shikijs/transformers"
import styles from "./markdownview.module.css"
interface MarkdownViewProps extends JSX.HTMLAttributes<HTMLDivElement> {
markdown: string
}
+const markedWithShiki = marked.use(
+ markedShiki({
+ highlight(code, lang) {
+ return codeToHtml(code, {
+ lang: lang || "text",
+ themes: {
+ light: "github-light",
+ dark: "github-dark",
+ },
+ transformers: [transformerNotationDiff()],
+ })
+ },
+ }),
+)
+
function MarkdownView(props: MarkdownViewProps) {
const [local, rest] = splitProps(props, ["markdown"])
- const [html] = createResource(() => local.markdown, async (markdown) => {
- return marked.parse(markdown)
- })
-
- return (
- <div innerHTML={html()} class={styles["markdown-body"]} {...rest} />
+ const [html] = createResource(
+ () => local.markdown,
+ async (markdown) => {
+ return markedWithShiki.parse(markdown)
+ },
)
+
+ return <div innerHTML={html()} class={styles["markdown-body"]} {...rest} />
}
export default MarkdownView
-
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx
index ff838dabe..7f2c45b18 100644
--- a/packages/web/src/components/Share.tsx
+++ b/packages/web/src/components/Share.tsx
@@ -294,15 +294,11 @@ function ResultsButton(props: ResultsButtonProps) {
interface TextPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
text: string
expand?: boolean
- invert?: boolean
- highlight?: boolean
}
function TextPart(props: TextPartProps) {
const [local, rest] = splitProps(props, [
"text",
"expand",
- "invert",
- "highlight",
])
const [expanded, setExpanded] = createSignal(false)
const [overflowed, setOverflowed] = createSignal(false)
@@ -332,8 +328,6 @@ function TextPart(props: TextPartProps) {
return (
<div
class={styles["message-text"]}
- data-invert={local.invert}
- data-highlight={local.highlight}
data-expanded={expanded() || local.expand === true}
{...rest}
>
@@ -991,9 +985,9 @@ export default function Share(props: {
</div>
<div data-section="content">
<TextPart
- invert
text={part().text}
expand={isLastPart()}
+ data-background="blue"
/>
</div>
</div>
@@ -1021,7 +1015,6 @@ export default function Share(props: {
</div>
<div data-section="content">
<MarkdownPart
- highlight
expand={isLastPart()}
text={stripEnclosingTag(part().text)}
/>
diff --git a/packages/web/src/components/markdownview.module.css b/packages/web/src/components/markdownview.module.css
index a4360bde9..9524c5cd7 100644
--- a/packages/web/src/components/markdownview.module.css
+++ b/packages/web/src/components/markdownview.module.css
@@ -40,11 +40,17 @@
}
pre {
- white-space: pre-wrap;
- border-radius: 0.25rem;
- border: 1px solid rgba(0, 0, 0, 0.2);
+ --shiki-dark-bg: var(--sl-color-bg-surface) !important;
+ background-color: var(--sl-color-bg-surface) !important;
padding: 0.5rem 0.75rem;
+ line-height: 1.6;
font-size: 0.75rem;
+ white-space: pre-wrap;
+ word-break: break-word;
+
+ span {
+ white-space: break-spaces;
+ }
}
code {
@@ -61,4 +67,40 @@
}
}
}
+
+ table {
+ border-collapse: collapse;
+ width: 100%;
+ }
+
+ th,
+ td {
+ border: 1px solid var(--sl-color-border);
+ padding: 0.5rem 0.75rem;
+ text-align: left;
+ }
+
+ th {
+ border-bottom: 1px solid var(--sl-color-border);
+ }
+
+ /* Remove outer borders */
+ table tr:first-child th,
+ table tr:first-child td {
+ border-top: none;
+ }
+
+ table tr:last-child td {
+ border-bottom: none;
+ }
+
+ table th:first-child,
+ table td:first-child {
+ border-left: none;
+ }
+
+ table th:last-child,
+ table td:last-child {
+ border-right: none;
+ }
}
diff --git a/packages/web/src/components/share.module.css b/packages/web/src/components/share.module.css
index dafbdd8a2..d8eac0e58 100644
--- a/packages/web/src/components/share.module.css
+++ b/packages/web/src/components/share.module.css
@@ -493,9 +493,8 @@
}
}
- &[data-highlight="true"] {
- background-color: var(--sl-color-blue-low);
- }
+ &[data-background="none"] { background-color: transparent; }
+ &[data-background="blue"] { background-color: var(--sl-color-blue-low); }
&[data-expanded="true"] {
pre {
@@ -669,7 +668,7 @@
}
.message-markdown {
- background-color: var(--sl-color-bg-surface);
+ border: 1px solid var(--sl-color-blue-high);
padding: 0.5rem calc(0.5rem + 3px);
border-radius: 0.25rem;
display: flex;