42 lines
2.0 KiB
TypeScript
42 lines
2.0 KiB
TypeScript
import { h } from '@ui-arch/frame';
|
||
import { diffLines } from './diffKit.js';
|
||
import type { DiffViewProps } from './types.js';
|
||
|
||
/*** 文本差异对比:行级对齐 + 增删着色 + 双行号列,超限时退化为整段替换(只影响精度不影响语义) ***/
|
||
export function diffView(props: DiffViewProps): HTMLElement {
|
||
const ignore = props.ignoreWhitespace === true; // 只在显式开启时归一行内空白
|
||
const ops = diffLines(props.before, props.after, ignore); // 纯逻辑在 diffKit:这里只负责把它画出来
|
||
const added = ops.filter((o) => o.type === 'add').length; // 新增行数:统计条与读屏共用
|
||
const removed = ops.filter((o) => o.type === 'del').length; // 删除行数
|
||
let oldNo = 0;
|
||
let newNo = 0;
|
||
const rows = ops.map((op) => {
|
||
if (op.type !== 'add') oldNo += 1; // same 与 del 占旧行号
|
||
if (op.type !== 'del') newNo += 1; // same 与 add 占新行号
|
||
const sign = op.type === 'add' ? '+' : op.type === 'del' ? '−' : ' ';
|
||
return h(
|
||
'div',
|
||
{ class: `ua-diff-row is-${op.type}` },
|
||
h('span', { class: 'ua-diff-no' }, op.type === 'add' ? '' : String(oldNo)),
|
||
h('span', { class: 'ua-diff-no' }, op.type === 'del' ? '' : String(newNo)),
|
||
h('span', { class: 'ua-diff-sign', 'aria-hidden': 'true' }, sign),
|
||
h('span', { class: 'ua-diff-text' }, op.text || ' '),
|
||
);
|
||
});
|
||
const stats = props.showStats === false
|
||
? null
|
||
: h(
|
||
'div',
|
||
{ class: 'ua-diff-stats' },
|
||
h('span', { class: 'ua-diff-add' }, `+${added}`),
|
||
h('span', { class: 'ua-diff-del' }, `−${removed}`),
|
||
ignore ? h('span', { class: 'ua-diff-note' }, '已忽略行内空白差异') : null, // 归一口径必须写在脸上,否则「为什么不报这几行」说不清
|
||
);
|
||
return h(
|
||
'div',
|
||
{ class: 'ua-diff', role: 'group', 'aria-label': props.ariaLabel ?? `文本差异:新增 ${added} 行,删除 ${removed} 行` },
|
||
stats,
|
||
h('div', { class: 'ua-diff-body' }, ...rows),
|
||
);
|
||
}
|