You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
6.0 KiB
239 lines
6.0 KiB
<script setup lang="ts">
|
|
import { marked, Renderer } from "marked";
|
|
|
|
const props = defineProps<{
|
|
text: string;
|
|
}>();
|
|
|
|
marked.setOptions({ breaks: true, gfm: true });
|
|
|
|
const renderer = new Renderer();
|
|
const originalCode = renderer.code.bind(renderer);
|
|
renderer.code = (code: string, infostring: string | undefined, escaped: boolean) => {
|
|
const lang = (infostring || "").trim();
|
|
const langLabel = lang ? lang.split(/\s+/)[0] : "text";
|
|
const inner = originalCode(code, infostring, escaped);
|
|
return `<div class="md-code-block"><div class="md-code-toolbar"><span class="md-code-lang">${langLabel}</span><button type="button" class="md-code-copy" data-code="${encodeURIComponent(code)}" aria-label="复制代码"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><span class="md-code-copy-label">复制</span></button></div>${inner}</div>`;
|
|
};
|
|
|
|
const html = computed(() => {
|
|
if (!props.text) return "";
|
|
try {
|
|
return marked.parse(props.text, { async: false, renderer } as any) as unknown as string;
|
|
} catch {
|
|
return props.text;
|
|
}
|
|
});
|
|
|
|
const rootRef = ref<HTMLElement | null>(null);
|
|
|
|
const handleCopy = async (e: Event) => {
|
|
const target = (e.target as HTMLElement)?.closest(".md-code-copy") as HTMLElement | null;
|
|
if (!target) return;
|
|
const raw = target.getAttribute("data-code");
|
|
if (raw == null) return;
|
|
const code = decodeURIComponent(raw);
|
|
try {
|
|
await navigator.clipboard.writeText(code);
|
|
const label = target.querySelector(".md-code-copy-label");
|
|
if (label) label.textContent = "已复制";
|
|
target.classList.add("is-copied");
|
|
const { $toast } = useNuxtApp();
|
|
$toast?.success?.("代码已复制");
|
|
setTimeout(() => {
|
|
const l = target.querySelector(".md-code-copy-label");
|
|
if (l) l.textContent = "复制";
|
|
target.classList.remove("is-copied");
|
|
}, 1500);
|
|
} catch {
|
|
const { $toast } = useNuxtApp();
|
|
$toast?.error?.("复制失败");
|
|
}
|
|
};
|
|
|
|
onMounted(() => {
|
|
rootRef.value?.addEventListener("click", handleCopy);
|
|
});
|
|
onBeforeUnmount(() => {
|
|
rootRef.value?.removeEventListener("click", handleCopy);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="rootRef" class="agent-markdown markdown-body" v-html="html" />
|
|
</template>
|
|
|
|
<style scoped>
|
|
.agent-markdown {
|
|
font-size: 15px;
|
|
line-height: 1.7;
|
|
color: var(--color-ink);
|
|
word-break: break-word;
|
|
}
|
|
|
|
.agent-markdown :deep(p) {
|
|
margin: 0 0 12px;
|
|
}
|
|
|
|
.agent-markdown :deep(p:last-child) {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.agent-markdown :deep(h1),
|
|
.agent-markdown :deep(h2),
|
|
.agent-markdown :deep(h3),
|
|
.agent-markdown :deep(h4) {
|
|
font-weight: 600;
|
|
margin: 20px 0 8px;
|
|
color: var(--color-ink);
|
|
}
|
|
|
|
.agent-markdown :deep(h1) { font-size: 1.5em; }
|
|
.agent-markdown :deep(h2) { font-size: 1.3em; }
|
|
.agent-markdown :deep(h3) { font-size: 1.15em; }
|
|
.agent-markdown :deep(h4) { font-size: 1em; }
|
|
|
|
.agent-markdown :deep(ul),
|
|
.agent-markdown :deep(ol) {
|
|
margin: 0 0 12px;
|
|
padding-left: 24px;
|
|
}
|
|
|
|
.agent-markdown :deep(li) {
|
|
margin: 4px 0;
|
|
}
|
|
|
|
.agent-markdown :deep(code) {
|
|
font-family: "JetBrains Mono", "SF Mono", "Fira Code", ui-monospace, monospace;
|
|
font-size: 0.88em;
|
|
background: var(--color-surface-soft);
|
|
border: 1px solid var(--color-hairline);
|
|
border-radius: 4px;
|
|
padding: 1px 5px;
|
|
color: var(--color-ink);
|
|
}
|
|
|
|
.agent-markdown :deep(.md-code-block) {
|
|
position: relative;
|
|
margin: 0 0 12px;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
background: var(--color-surface-dark);
|
|
}
|
|
|
|
.agent-markdown :deep(.md-code-toolbar) {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 6px 12px;
|
|
background: var(--color-surface-dark-soft, #1f1e1b);
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
}
|
|
|
|
.agent-markdown :deep(.md-code-lang) {
|
|
font-family: "JetBrains Mono", "SF Mono", ui-monospace, monospace;
|
|
font-size: 12px;
|
|
color: rgba(255, 255, 255, 0.5);
|
|
text-transform: lowercase;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
.agent-markdown :deep(.md-code-copy) {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 3px 8px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
background: transparent;
|
|
color: rgba(255, 255, 255, 0.6);
|
|
font-size: 12px;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
transition: background 0.15s, color 0.15s;
|
|
}
|
|
|
|
.agent-markdown :deep(.md-code-copy:hover) {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
color: rgba(255, 255, 255, 0.9);
|
|
}
|
|
|
|
.agent-markdown :deep(.md-code-copy.is-copied) {
|
|
color: #6fcf97;
|
|
}
|
|
|
|
.agent-markdown :deep(.md-code-copy-label) {
|
|
line-height: 1;
|
|
}
|
|
|
|
.agent-markdown :deep(.md-code-block pre) {
|
|
background: var(--color-surface-dark);
|
|
border-radius: 0;
|
|
padding: 16px;
|
|
margin: 0;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.agent-markdown :deep(pre) {
|
|
background: var(--color-surface-dark);
|
|
border-radius: 10px;
|
|
padding: 16px;
|
|
overflow-x: auto;
|
|
margin: 0 0 12px;
|
|
}
|
|
|
|
.agent-markdown :deep(pre code) {
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
color: var(--color-on-dark);
|
|
font-family: "JetBrains Mono", "SF Mono", "Fira Code", ui-monospace, monospace;
|
|
font-size: 13px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.agent-markdown :deep(blockquote) {
|
|
border-left: 3px solid var(--color-primary);
|
|
margin: 0 0 12px;
|
|
padding: 4px 16px;
|
|
color: var(--color-body);
|
|
background: var(--color-surface-soft);
|
|
border-radius: 0 8px 8px 0;
|
|
}
|
|
|
|
.agent-markdown :deep(a) {
|
|
color: var(--color-primary);
|
|
text-decoration: none;
|
|
border-bottom: 1px solid transparent;
|
|
transition: border-color 0.15s;
|
|
}
|
|
|
|
.agent-markdown :deep(a:hover) {
|
|
border-bottom-color: var(--color-primary);
|
|
}
|
|
|
|
.agent-markdown :deep(table) {
|
|
border-collapse: collapse;
|
|
margin: 0 0 12px;
|
|
width: 100%;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.agent-markdown :deep(th),
|
|
.agent-markdown :deep(td) {
|
|
border: 1px solid var(--color-hairline);
|
|
padding: 8px 12px;
|
|
text-align: left;
|
|
}
|
|
|
|
.agent-markdown :deep(th) {
|
|
background: var(--color-surface-soft);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.agent-markdown :deep(hr) {
|
|
border: none;
|
|
border-top: 1px solid var(--color-hairline);
|
|
margin: 16px 0;
|
|
}
|
|
</style>
|
|
|