| | <script lang="ts"> |
| | import DOMPurify from 'dompurify'; |
| | import { toast } from 'svelte-sonner'; |
| |
|
| | import type { Token } from 'marked'; |
| | import { getContext } from 'svelte'; |
| | import { goto } from '$app/navigation'; |
| |
|
| | const i18n = getContext('i18n'); |
| |
|
| | import { WEBUI_BASE_URL } from '$lib/constants'; |
| | import { copyToClipboard, unescapeHtml } from '$lib/utils'; |
| |
|
| | import Image from '$lib/components/common/Image.svelte'; |
| | import KatexRenderer from './KatexRenderer.svelte'; |
| | import Source from './Source.svelte'; |
| | import HtmlToken from './HTMLToken.svelte'; |
| | import TextToken from './MarkdownInlineTokens/TextToken.svelte'; |
| | import CodespanToken from './MarkdownInlineTokens/CodespanToken.svelte'; |
| | import MentionToken from './MarkdownInlineTokens/MentionToken.svelte'; |
| | import NoteLinkToken from './MarkdownInlineTokens/NoteLinkToken.svelte'; |
| | import SourceToken from './SourceToken.svelte'; |
| |
|
| | export let id: string; |
| | export let done = true; |
| | export let tokens: Token[]; |
| | export let sourceIds = []; |
| | export let onSourceClick: Function = () => {}; |
| |
|
| | |
| | |
| | |
| | const getNoteIdFromHref = (href: string): string | null => { |
| | try { |
| | const url = new URL(href, window.location.origin); |
| | if (url.origin === window.location.origin) { |
| | const match = url.pathname.match(/^\/notes\/([^/]+)$/); |
| | if (match) { |
| | return match[1]; |
| | } |
| | } |
| | } catch { |
| | |
| | } |
| | return null; |
| | }; |
| |
|
| | |
| | |
| | |
| | const handleLinkClick = (e: MouseEvent, href: string) => { |
| | try { |
| | const url = new URL(href, window.location.origin); |
| | |
| | if ( |
| | url.origin === window.location.origin && |
| | (url.pathname.startsWith('/notes/') || |
| | url.pathname.startsWith('/c/') || |
| | url.pathname.startsWith('/channels/')) |
| | ) { |
| | e.preventDefault(); |
| | goto(url.pathname + url.search + url.hash); |
| | } |
| | } catch { |
| | |
| | } |
| | }; |
| | </script> |
| |
|
| | {#each tokens as token, tokenIdx (tokenIdx)} |
| | {#if token.type === 'escape'} |
| | {unescapeHtml(token.text)} |
| | {:else if token.type === 'html'} |
| | <HtmlToken {id} {token} {onSourceClick} /> |
| | {:else if token.type === 'link'} |
| | {@const noteId = getNoteIdFromHref(token.href)} |
| | {#if noteId} |
| | <NoteLinkToken {noteId} href={token.href} /> |
| | {:else if token.tokens} |
| | <a |
| | href={token.href} |
| | target="_blank" |
| | rel="nofollow" |
| | title={token.title} |
| | on:click={(e) => handleLinkClick(e, token.href)} |
| | > |
| | <svelte:self id={`${id}-a`} tokens={token.tokens} {onSourceClick} {done} /> |
| | </a> |
| | {:else} |
| | <a |
| | href={token.href} |
| | target="_blank" |
| | rel="nofollow" |
| | title={token.title} |
| | on:click={(e) => handleLinkClick(e, token.href)}>{token.text}</a |
| | > |
| | {/if} |
| | {:else if token.type === 'image'} |
| | <Image src={token.href} alt={token.text} /> |
| | {:else if token.type === 'strong'} |
| | <strong><svelte:self id={`${id}-strong`} tokens={token.tokens} {onSourceClick} /></strong> |
| | {:else if token.type === 'em'} |
| | <em><svelte:self id={`${id}-em`} tokens={token.tokens} {onSourceClick} /></em> |
| | {:else if token.type === 'codespan'} |
| | <CodespanToken {token} {done} /> |
| | {:else if token.type === 'br'} |
| | <br /> |
| | {:else if token.type === 'del'} |
| | <del><svelte:self id={`${id}-del`} tokens={token.tokens} {onSourceClick} /></del> |
| | {:else if token.type === 'inlineKatex'} |
| | {#if token.text} |
| | <KatexRenderer content={token.text} displayMode={false} /> |
| | {/if} |
| | {:else if token.type === 'iframe'} |
| | <iframe |
| | src="{WEBUI_BASE_URL}/api/v1/files/{token.fileId}/content" |
| | title={token.fileId} |
| | width="100%" |
| | frameborder="0" |
| | on:load={(e) => { |
| | try { |
| | e.currentTarget.style.height = |
| | e.currentTarget.contentWindow.document.body.scrollHeight + 20 + 'px'; |
| | } catch {} |
| | }} |
| | ></iframe> |
| | {:else if token.type === 'mention'} |
| | <MentionToken {token} /> |
| | {:else if token.type === 'footnote'} |
| | {@html DOMPurify.sanitize( |
| | `<sup class="footnote-ref footnote-ref-text">${token.escapedText}</sup>` |
| | ) || ''} |
| | {:else if token.type === 'citation'} |
| | {#if (sourceIds ?? []).length > 0} |
| | <SourceToken {id} {token} {sourceIds} onClick={onSourceClick} /> |
| | {:else} |
| | <TextToken {token} {done} /> |
| | {/if} |
| | {:else if token.type === 'text'} |
| | <TextToken {token} {done} /> |
| | {/if} |
| | {/each} |
| |
|