summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/web/src/components/Share.tsx114
-rw-r--r--packages/web/src/components/share.module.css41
2 files changed, 93 insertions, 62 deletions
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx
index ff2d4e825..3d392d886 100644
--- a/packages/web/src/components/Share.tsx
+++ b/packages/web/src/components/Share.tsx
@@ -172,13 +172,29 @@ function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> {
return entries
}
-export function getDiagnostics(
+function formatErrorString(error: string): JSX.Element {
+ const errorMarker = "Error: "
+ const startsWithError = error.startsWith(errorMarker)
+
+ return startsWithError ? (
+ <p>
+ <span data-color="red" data-marker="label" data-separator>
+ Error
+ </span>
+ <span>{error.slice(errorMarker.length)}</span>
+ </p>
+ ) : (
+ <p><span data-color="dimmed">{error}</span></p>
+ )
+}
+
+function getDiagnostics(
diagnosticsByFile: Record<string, Diagnostic[]>,
currentFile: string
-): string[] {
+): JSX.Element[] {
// Return a flat array of error diagnostics, in the format:
- // "ERROR [65:20] Property 'x' does not exist on type 'Y'"
- const result: string[] = []
+ // "Error [65:20] Property 'x' does not exist on type 'Y'"
+ const result: JSX.Element[] = []
if (
diagnosticsByFile === undefined || diagnosticsByFile[currentFile] === undefined
@@ -192,7 +208,15 @@ export function getDiagnostics(
const line = d.range.start.line + 1 // 1-based
const column = d.range.start.character + 1 // 1-based
- result.push(`\x1b[31mERROR\x1b[0m \x1b[2m[${line}:${column}]\x1b[0m ${d.message}`)
+ result.push(
+ <p>
+ <span data-color="red" data-marker="label">Error</span>
+ <span data-color="dimmed" data-separator>
+ [{line}:{column}]
+ </span>
+ <span>{d.message}</span>
+ </p>
+ )
}
}
@@ -324,46 +348,44 @@ function TextPart(props: TextPartProps) {
)
}
-interface LspPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
- text: string
+interface ErrorPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
expand?: boolean
}
-function LspPart(props: LspPartProps) {
- const [local, rest] = splitProps(props, ["text", "expand"])
+function ErrorPart(props: ErrorPartProps) {
+ const [local, rest] = splitProps(props, ["expand", "children"])
const [expanded, setExpanded] = createSignal(false)
const [overflowed, setOverflowed] = createSignal(false)
let preEl: HTMLElement | undefined
function checkOverflow() {
- if (!preEl) return
-
- const code = preEl.getElementsByTagName("code")[0]
-
- if (code && !local.expand) {
- setOverflowed(preEl.clientHeight < code.offsetHeight)
+ if (preEl && !local.expand) {
+ setOverflowed(preEl.scrollHeight > preEl.clientHeight + 1)
}
}
onMount(() => {
+ checkOverflow()
window.addEventListener("resize", checkOverflow)
})
+ createEffect(() => {
+ local.children
+ setTimeout(checkOverflow, 0)
+ })
+
onCleanup(() => {
window.removeEventListener("resize", checkOverflow)
})
return (
<div
- class={styles["message-lsp"]}
+ class={styles["message-error"]}
data-expanded={expanded() || local.expand === true}
{...rest}
>
- <CodeBlock
- lang="ansi"
- code={local.text}
- onRendered={checkOverflow}
- ref={(el) => (preEl = el)}
- />
+ <div data-section="content" ref={(el) => (preEl = el)}>
+ {local.children}
+ </div>
{((!local.expand && overflowed()) || expanded()) && (
<button
type="button"
@@ -512,6 +534,7 @@ export default function Share(props: {
info: SessionInfo
messages: Record<string, SessionMessage>
}) {
+ let hasScrolled = false
const id = props.id
const anchorId = createMemo<string | null>(() => {
@@ -586,8 +609,9 @@ export default function Share(props: {
const [, messageID] = splits
setStore("messages", messageID, reconcile(d.content))
- if (messageID === anchorId()) {
+ if (!hasScrolled && messageID === anchorId()) {
scrollToAnchor(window.location.hash.slice(1))
+ hasScrolled = true
}
}
} catch (error) {
@@ -1241,12 +1265,9 @@ export default function Share(props: {
<Switch>
<Match when={hasError()}>
<div data-part-tool-result>
- <TextPart
- expand
- text={toolData()?.result}
- data-size="sm"
- data-color="dimmed"
- />
+ <ErrorPart>
+ {formatErrorString(toolData()?.result)}
+ </ErrorPart>
</div>
</Match>
<Match when={preview()}>
@@ -1339,19 +1360,14 @@ export default function Share(props: {
<b>{filePath()}</b>
</div>
<Show when={diagnostics().length > 0}>
- <LspPart
- text={diagnostics().join("\n\n")}
- />
+ <ErrorPart>{diagnostics()}</ErrorPart>
</Show>
<Switch>
<Match when={hasError()}>
<div data-part-tool-result>
- <TextPart
- expand
- text={toolData()?.result}
- data-size="sm"
- data-color="dimmed"
- />
+ <ErrorPart>
+ {formatErrorString(toolData()?.result)}
+ </ErrorPart>
</div>
</Match>
<Match when={content()}>
@@ -1429,12 +1445,9 @@ export default function Share(props: {
<Switch>
<Match when={hasError()}>
<div data-part-tool-result>
- <TextPart
- expand
- data-size="sm"
- data-color="dimmed"
- text={message()}
- />
+ <ErrorPart>
+ {formatErrorString(message())}
+ </ErrorPart>
</div>
</Match>
<Match when={diff()}>
@@ -1448,9 +1461,7 @@ export default function Share(props: {
</Match>
</Switch>
<Show when={diagnostics().length > 0}>
- <LspPart
- text={diagnostics().join("\n\n")}
- />
+ <ErrorPart>{diagnostics()}</ErrorPart>
</Show>
</div>
<ToolFooter time={toolData()?.duration || 0} />
@@ -1601,12 +1612,9 @@ export default function Share(props: {
<Switch>
<Match when={hasError()}>
<div data-part-tool-result>
- <TextPart
- expand
- text={toolData()?.result}
- data-size="sm"
- data-color="dimmed"
- />
+ <ErrorPart>
+ {formatErrorString(toolData()?.result)}
+ </ErrorPart>
</div>
</Match>
<Match when={toolData()?.result}>
diff --git a/packages/web/src/components/share.module.css b/packages/web/src/components/share.module.css
index bcac00544..b216dbaf0 100644
--- a/packages/web/src/components/share.module.css
+++ b/packages/web/src/components/share.module.css
@@ -421,7 +421,7 @@
}
}
-.message-lsp {
+.message-error {
background-color: var(--sl-color-bg-surface);
padding: 0.5rem calc(0.5rem + 3px);
border-radius: 0.25rem;
@@ -432,24 +432,47 @@
align-self: flex-start;
max-width: var(--md-tool-width);
- padding: 0.5rem calc(0.5rem + 3px);
-
- pre {
- --shiki-dark-bg: var(--sl-color-bg-surface) !important;
- background-color: var(--sl-color-bg-surface) !important;
- line-height: 1.4;
+ [data-section="content"] {
+ p {
+ margin-bottom: 0.5rem;
+ line-height: 1.5;
font-size: 0.75rem;
white-space: pre-wrap;
word-break: break-word;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ span {
+ margin-right: 0.25rem;
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ span[data-color="red"] {
+ color: var(--sl-color-red);
+ }
+ span[data-color="dimmed"] {
+ color: var(--sl-color-text-dimmed);
+ }
+ span[data-marker="label"] {
+ text-transform: uppercase;
+ letter-spacing: -0.5px;
+ }
+ span[data-separator] {
+ margin-right: 0.375rem;
+ }
}
+ }
&[data-expanded="true"] {
- pre {
+ [data-section="content"] {
display: block;
}
}
&[data-expanded="false"] {
- pre {
+ [data-section="content"] {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 7;