fix(frontend): improve PDF zoom and diff readability
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
- PDF viewer: append #zoom=page-width to iframe src so A4 letters fill the panel width instead of leaving large grey gutters - Diff view: trim unchanged context to 4 words either side of each change, replacing long runs with '…' so edits are easy to spot Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,44 @@ function personLabel(p: { firstName: string; lastName: string }): string {
|
|||||||
return `${p.firstName} ${p.lastName}`.trim();
|
return `${p.firstName} ${p.lastName}`.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DIFF_CONTEXT_WORDS = 4;
|
||||||
|
|
||||||
|
type DiffPart = { value: string; added?: boolean; removed?: boolean };
|
||||||
|
|
||||||
|
function trimContextParts(parts: DiffPart[]): DiffPart[] {
|
||||||
|
return parts.flatMap((part, i) => {
|
||||||
|
if (part.added || part.removed) return [part];
|
||||||
|
const tokens = part.value.split(/(\s+)/).filter(Boolean);
|
||||||
|
const wordCount = tokens.filter((t) => /\S/.test(t)).length;
|
||||||
|
if (wordCount <= DIFF_CONTEXT_WORDS * 2) return [part];
|
||||||
|
|
||||||
|
function keepFirst(n: number): string {
|
||||||
|
let count = 0;
|
||||||
|
const out: string[] = [];
|
||||||
|
for (const t of tokens) {
|
||||||
|
out.push(t);
|
||||||
|
if (/\S/.test(t) && ++count >= n) break;
|
||||||
|
}
|
||||||
|
return out.join('');
|
||||||
|
}
|
||||||
|
function keepLast(n: number): string {
|
||||||
|
let count = 0;
|
||||||
|
const out: string[] = [];
|
||||||
|
for (const t of [...tokens].reverse()) {
|
||||||
|
out.unshift(t);
|
||||||
|
if (/\S/.test(t) && ++count >= n) break;
|
||||||
|
}
|
||||||
|
return out.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFirst = i === 0;
|
||||||
|
const isLast = i === parts.length - 1;
|
||||||
|
if (isFirst) return [{ value: '… ' + keepLast(DIFF_CONTEXT_WORDS) }];
|
||||||
|
if (isLast) return [{ value: keepFirst(DIFF_CONTEXT_WORDS) + ' …' }];
|
||||||
|
return [{ value: keepFirst(DIFF_CONTEXT_WORDS) + ' … ' + keepLast(DIFF_CONTEXT_WORDS) }];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function buildDiff(older: SnapshotDoc | null, newer: SnapshotDoc): DiffEntry[] {
|
function buildDiff(older: SnapshotDoc | null, newer: SnapshotDoc): DiffEntry[] {
|
||||||
const entries: DiffEntry[] = [];
|
const entries: DiffEntry[] = [];
|
||||||
|
|
||||||
@@ -119,7 +157,7 @@ function buildDiff(older: SnapshotDoc | null, newer: SnapshotDoc): DiffEntry[] {
|
|||||||
const a = older?.[field] ?? '';
|
const a = older?.[field] ?? '';
|
||||||
const b = newer[field] ?? '';
|
const b = newer[field] ?? '';
|
||||||
if (a === b) continue;
|
if (a === b) continue;
|
||||||
const parts = diffWords(a, b);
|
const parts = trimContextParts(diffWords(a, b));
|
||||||
entries.push({ kind: 'text', field, label: fieldLabels[field](), parts });
|
entries.push({ kind: 'text', field, label: fieldLabels[field](), parts });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,14 +882,14 @@ function versionLabel(v: VersionSummary, index: number): string {
|
|||||||
</div>
|
</div>
|
||||||
{:else if fileUrl && doc.originalFilename.toLowerCase().endsWith('.pdf')}
|
{:else if fileUrl && doc.originalFilename.toLowerCase().endsWith('.pdf')}
|
||||||
<iframe
|
<iframe
|
||||||
src={fileUrl}
|
src="{fileUrl}#zoom=page-width"
|
||||||
title={m.doc_preview_iframe_title()}
|
title={m.doc_preview_iframe_title()}
|
||||||
class="h-full w-full border-none bg-white"
|
class="h-full w-full border-none bg-white"
|
||||||
></iframe>
|
></iframe>
|
||||||
{:else if fileUrl}
|
{:else if fileUrl}
|
||||||
<div class="flex h-full w-full items-center justify-center overflow-auto p-8">
|
<div class="flex h-full w-full items-center justify-center overflow-auto p-8">
|
||||||
<img
|
<img
|
||||||
src={fileUrl}
|
src="{fileUrl}#zoom=page-width"
|
||||||
alt={m.doc_image_alt()}
|
alt={m.doc_image_alt()}
|
||||||
class="max-h-full max-w-full object-contain shadow-2xl"
|
class="max-h-full max-w-full object-contain shadow-2xl"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user