Merge pull request #11699 from shaun-gallagher-octane/markdown-alert-renderer

feat: Markdown alert renderer
This commit is contained in:
Timothy Jaeryang Baek 2025-03-15 02:12:16 +00:00 committed by GitHub
commit f1a84f45c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 126 additions and 3 deletions

View File

@ -0,0 +1,116 @@
<script lang="ts" context="module">
import { marked, type Token } from 'marked';
type AlertType = "NOTE" | "TIP" | "IMPORTANT" | "WARNING" | "CAUTION";
interface AlertTheme {
border: string;
text: string;
icon: ComponentType;
}
export interface AlertData {
type: AlertType;
text: string;
tokens: Token[];
}
const alertStyles: Record<AlertType, AlertTheme> = {
"NOTE": {
border: "border-sky-500",
text: "text-sky-500",
icon: Info
},
"TIP": {
border: "border-emerald-500",
text: "text-emerald-500",
icon: LightBlub
},
"IMPORTANT": {
border: "border-purple-500",
text: "text-purple-500",
icon: Star
},
"WARNING": {
border: "border-yellow-500",
text: "text-yellow-500",
icon: ArrowRightCircle
},
"CAUTION": {
border: "border-rose-500",
text: "text-rose-500",
icon: Bolt
}
};
export function alertComponent(token: Token): AlertData | false {
const regExpStr = `^(?:\\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\])\\s*?\n*`;
const regExp = new RegExp(regExpStr);
const matches = token.text?.match(regExp);
if (matches && matches.length) {
const alertType = matches[1] as AlertType;
const newText = token.text.replace(regExp, '');
const newTokens = marked.lexer(newText);
return {
type: alertType,
text: newText,
tokens: newTokens
};
}
return false;
}
</script>
<script lang="ts">
import Info from '$lib/components/icons/Info.svelte';
import Star from '$lib/components/icons/Star.svelte';
import LightBlub from '$lib/components/icons/LightBlub.svelte';
import Bolt from '$lib/components/icons/Bolt.svelte';
import ArrowRightCircle from '$lib/components/icons/ArrowRightCircle.svelte';
import MarkdownTokens from './MarkdownTokens.svelte';
import type { ComponentType } from 'svelte';
export let token: Token;
export let alert: AlertData;
export let id = '';
export let tokenIdx = 0;
export let onTaskClick: ((event: MouseEvent) => void) | undefined = undefined;
export let onSourceClick: ((event: MouseEvent) => void) | undefined = undefined;
</script>
<!--
Renders the following Markdown as alerts:
> [!NOTE]
> Example note
> [!TIP]
> Example tip
> [!IMPORTANT]
> Example important
> [!CAUTION]
> Example caution
> [!WARNING]
> Example warning
-->
<div class={`border-l-2 pl-2 ${alertStyles[alert.type].border}`}>
<p class={alertStyles[alert.type].text}>
<svelte:component
this={alertStyles[alert.type].icon}
className="inline-block size-4"
/>
<b>{alert.type}</b>
</p>
<MarkdownTokens
id={`${id}-${tokenIdx}`}
tokens={alert.tokens}
{onTaskClick}
{onSourceClick}
/>
</div>

View File

@ -14,9 +14,11 @@
import CodeBlock from '$lib/components/chat/Messages/CodeBlock.svelte';
import MarkdownInlineTokens from '$lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte';
import KatexRenderer from './KatexRenderer.svelte';
import AlertRenderer, { alertComponent } from './AlertRenderer.svelte';
import Collapsible from '$lib/components/common/Collapsible.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
import Source from './Source.svelte';
import { settings } from '$lib/stores';
@ -172,9 +174,14 @@
</div>
</div>
{:else if token.type === 'blockquote'}
<blockquote dir="auto">
<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} {onTaskClick} {onSourceClick} />
</blockquote>
{@const alert = alertComponent(token)}
{#if alert}
<AlertRenderer token={token} alert={alert} />
{:else}
<blockquote dir="auto">
<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} {onTaskClick} {onSourceClick} />
</blockquote>
{/if}
{:else if token.type === 'list'}
{#if token.ordered}
<ol start={token.start || 1}>