31 lines
1.7 KiB
TypeScript
31 lines
1.7 KiB
TypeScript
import { h } from '@ui-arch/frame';
|
|
import { copyButton } from './copyButton.js';
|
|
import type { CodeBlockProps } from './types.js';
|
|
|
|
/*** 代码块:等宽渲染多行代码,可选行号列与标题行(语言 / 文件名),右上角一键复制原文;只做展示不做高亮 ***/
|
|
export function codeBlock(props: CodeBlockProps): HTMLElement {
|
|
const lines = props.code.replace(/\r?\n$/, '').split(/\r?\n/); // 末尾换行不生成空行:复制走原文,显示不多一行
|
|
const showNo = props.lineNumbers !== false && lines.length > 1; // 单行代码给行号只是噪音
|
|
const body = h(
|
|
'div',
|
|
{ class: 'ua-code-body' },
|
|
...lines.map((text, i) => h(
|
|
'div',
|
|
{ class: 'ua-code-line' },
|
|
showNo ? h('span', { class: 'ua-code-no', 'aria-hidden': 'true' }, String(i + 1)) : null, // 行号是装饰,读屏跳过
|
|
h('span', { class: 'ua-code-text' }, text || ' '), // 空行补一个空格,撑住行高
|
|
)),
|
|
);
|
|
if (props.maxHeight !== undefined) body.style.maxHeight = `${props.maxHeight}px`; // 只在给了上限时限高,超出内部滚动
|
|
const title = props.title ?? props.lang; // 标题优先用文件名,其次语言名
|
|
const head = props.copyable === false && !title
|
|
? null // 既无标题也不可复制:整个头部省掉,不留空条
|
|
: h(
|
|
'div',
|
|
{ class: 'ua-code-head' },
|
|
title ? h('span', { class: 'ua-code-title' }, title) : h('span', null, ''),
|
|
props.copyable === false ? null : copyButton({ text: props.code, label: '复制', size: 'sm', variant: 'ghost', onCopied: props.onCopy }),
|
|
);
|
|
return h('div', { class: 'ua-code', role: 'group', 'aria-label': props.ariaLabel ?? '代码块' }, head, body);
|
|
}
|