summaryrefslogtreecommitdiff
path: root/frontend/src/components/DiffView.svelte
blob: cf0c1be005fc0841954f8de6c653211680af3faf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<script lang="ts">
  import { diffLines } from 'diff';

  interface Props {
    before: string;
    after: string;
    beforeLabel?: string;
    afterLabel?: string;
    maxHeight?: number;
  }
  let { before, after, beforeLabel = '原版本', afterLabel = '新版本', maxHeight = 480 }: Props = $props();

  const parts = $derived(diffLines(before ?? '', after ?? ''));

  const lines = $derived.by(() => {
    const out: { t: 'a' | 'd' | 'c'; text: string }[] = [];
    for (const p of parts) {
      const pieces = p.value.replace(/\n$/, '').split('\n');
      for (const piece of pieces) {
        if (p.added) out.push({ t: 'a', text: piece });
        else if (p.removed) out.push({ t: 'd', text: piece });
        else out.push({ t: 'c', text: piece });
      }
    }
    return out;
  });
</script>

<div class="diff">
  <div class="legend">
    <span><i class="sw a"></i>新增({afterLabel})</span>
    <span><i class="sw d"></i>删除({beforeLabel})</span>
  </div>
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
		<div class="body" style="max-height:{maxHeight}px" tabindex="0" aria-label="代码差异" role="region">
    {#each lines as l, i (i)}
      {#if l.t === 'a'}
        <span class="ln add" aria-hidden="true"></span><span class="code add">+{l.text}</span>
      {:else if l.t === 'd'}
        <span class="ln del" aria-hidden="true"></span><span class="code del">−{l.text}</span>
      {:else}
        <span class="ln" aria-hidden="true"> </span><span class="code">{l.text}</span>
      {/if}
    {/each}
  </div>
</div>

<style>
  .diff .legend {
    color: var(--text-3);
    font-size: 0.8rem;
    margin-bottom: 0.4rem;
    display: flex;
    gap: 1rem;
  }
  .sw { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 4px; }
  .sw.a { background: #c7f0e6; }
  .sw.d { background: #fbd4d0; }
  .body {
    overflow: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.5rem 0.7rem;
  }
  .body .code { font-family: var(--mono); font-size: 12.5px; white-space: pre-wrap; word-break: break-all; display: inline-block; width: 100%; }
  .add { background: #eafaf5; color: #14532d; }
  .del { background: #fdf0ee; color: #7f1d1d; }
  .ln { user-select: none; }
  .code.add::before, .code.del::before { opacity: 0.6; }
</style>