@gakr-gakr/diffs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -0
- package/api.ts +10 -0
- package/assets/viewer-runtime.js +1888 -0
- package/autobot.plugin.json +218 -0
- package/index.ts +11 -0
- package/package.json +50 -0
- package/runtime-api.ts +1 -0
- package/skills/diffs/SKILL.md +23 -0
- package/src/browser.ts +564 -0
- package/src/config.ts +443 -0
- package/src/http.ts +324 -0
- package/src/language-hints.ts +117 -0
- package/src/pierre-themes.ts +59 -0
- package/src/plugin.ts +73 -0
- package/src/prompt-guidance.ts +7 -0
- package/src/render.ts +557 -0
- package/src/store.ts +387 -0
- package/src/tool.ts +547 -0
- package/src/types.ts +127 -0
- package/src/url.ts +60 -0
- package/src/viewer-assets.ts +103 -0
- package/src/viewer-client.ts +353 -0
- package/src/viewer-payload.ts +94 -0
- package/tsconfig.json +16 -0
package/src/render.ts
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import type { FileContents, FileDiffMetadata, SupportedLanguages } from "@pierre/diffs";
|
|
2
|
+
import { parsePatchFiles } from "@pierre/diffs";
|
|
3
|
+
import { preloadFileDiff, preloadMultiFileDiff } from "@pierre/diffs/ssr";
|
|
4
|
+
import {
|
|
5
|
+
collectDiffPayloadLanguageHints,
|
|
6
|
+
normalizeDiffViewerPayloadLanguages,
|
|
7
|
+
normalizeSupportedLanguageHint,
|
|
8
|
+
} from "./language-hints.js";
|
|
9
|
+
import { ensurePierreThemesRegistered } from "./pierre-themes.js";
|
|
10
|
+
import type {
|
|
11
|
+
DiffInput,
|
|
12
|
+
DiffRenderOptions,
|
|
13
|
+
DiffRenderTarget,
|
|
14
|
+
DiffViewerOptions,
|
|
15
|
+
DiffViewerPayload,
|
|
16
|
+
RenderedDiffDocument,
|
|
17
|
+
} from "./types.js";
|
|
18
|
+
|
|
19
|
+
const DEFAULT_FILE_NAME = "diff.txt";
|
|
20
|
+
const MAX_PATCH_FILE_COUNT = 128;
|
|
21
|
+
const MAX_PATCH_TOTAL_LINES = 120_000;
|
|
22
|
+
const VIEWER_LOADER_DOCUMENT_PATH = "../../assets/viewer.js";
|
|
23
|
+
|
|
24
|
+
function escapeCssString(value: string): string {
|
|
25
|
+
return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function escapeHtml(value: string): string {
|
|
29
|
+
return value
|
|
30
|
+
.replaceAll("&", "&")
|
|
31
|
+
.replaceAll("<", "<")
|
|
32
|
+
.replaceAll(">", ">")
|
|
33
|
+
.replaceAll('"', """)
|
|
34
|
+
.replaceAll("'", "'");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function escapeJsonScript(value: unknown): string {
|
|
38
|
+
return JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildDiffTitle(input: DiffInput): string {
|
|
42
|
+
if (input.title?.trim()) {
|
|
43
|
+
return input.title.trim();
|
|
44
|
+
}
|
|
45
|
+
if (input.kind === "before_after") {
|
|
46
|
+
return input.path?.trim() || "Text diff";
|
|
47
|
+
}
|
|
48
|
+
return "Patch diff";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveBeforeAfterFileName(params: {
|
|
52
|
+
input: Extract<DiffInput, { kind: "before_after" }>;
|
|
53
|
+
lang?: SupportedLanguages;
|
|
54
|
+
}): string {
|
|
55
|
+
const { input, lang } = params;
|
|
56
|
+
if (input.path?.trim()) {
|
|
57
|
+
return input.path.trim();
|
|
58
|
+
}
|
|
59
|
+
if (lang && lang !== "text") {
|
|
60
|
+
return `diff.${lang.replace(/^\.+/, "")}`;
|
|
61
|
+
}
|
|
62
|
+
return DEFAULT_FILE_NAME;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildDiffOptions(options: DiffRenderOptions): DiffViewerOptions {
|
|
66
|
+
const fontFamily = escapeCssString(options.presentation.fontFamily);
|
|
67
|
+
const fontSize = Math.max(10, Math.floor(options.presentation.fontSize));
|
|
68
|
+
const lineHeight = Math.max(20, Math.round(fontSize * options.presentation.lineSpacing));
|
|
69
|
+
return {
|
|
70
|
+
theme: {
|
|
71
|
+
light: "pierre-light",
|
|
72
|
+
dark: "pierre-dark",
|
|
73
|
+
},
|
|
74
|
+
diffStyle: options.presentation.layout,
|
|
75
|
+
diffIndicators: options.presentation.diffIndicators,
|
|
76
|
+
disableLineNumbers: !options.presentation.showLineNumbers,
|
|
77
|
+
expandUnchanged: options.expandUnchanged,
|
|
78
|
+
themeType: options.presentation.theme,
|
|
79
|
+
backgroundEnabled: options.presentation.background,
|
|
80
|
+
overflow: options.presentation.wordWrap ? "wrap" : "scroll",
|
|
81
|
+
unsafeCSS: `
|
|
82
|
+
:host {
|
|
83
|
+
--diffs-font-family: "${fontFamily}", "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
84
|
+
--diffs-header-font-family: "${fontFamily}", "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
85
|
+
--diffs-font-size: ${fontSize}px;
|
|
86
|
+
--diffs-line-height: ${lineHeight}px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[data-diffs-header] {
|
|
90
|
+
min-height: 64px;
|
|
91
|
+
padding-inline: 18px 14px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
[data-header-content] {
|
|
95
|
+
gap: 10px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
[data-metadata] {
|
|
99
|
+
gap: 10px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.oc-diff-toolbar {
|
|
103
|
+
display: inline-flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
gap: 6px;
|
|
106
|
+
margin-inline-start: 6px;
|
|
107
|
+
flex: 0 0 auto;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.oc-diff-toolbar-button {
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
width: 24px;
|
|
115
|
+
height: 24px;
|
|
116
|
+
padding: 0;
|
|
117
|
+
margin: 0;
|
|
118
|
+
border: 0;
|
|
119
|
+
border-radius: 0;
|
|
120
|
+
background: transparent;
|
|
121
|
+
color: inherit;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
opacity: 0.6;
|
|
124
|
+
line-height: 0;
|
|
125
|
+
overflow: visible;
|
|
126
|
+
transition: opacity 120ms ease;
|
|
127
|
+
flex: 0 0 auto;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.oc-diff-toolbar-button:hover {
|
|
131
|
+
opacity: 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.oc-diff-toolbar-button[data-active="true"] {
|
|
135
|
+
opacity: 0.92;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.oc-diff-toolbar-button svg {
|
|
139
|
+
display: block;
|
|
140
|
+
width: 16px;
|
|
141
|
+
height: 16px;
|
|
142
|
+
min-width: 16px;
|
|
143
|
+
min-height: 16px;
|
|
144
|
+
overflow: visible;
|
|
145
|
+
flex: 0 0 auto;
|
|
146
|
+
color: inherit;
|
|
147
|
+
fill: currentColor;
|
|
148
|
+
pointer-events: none;
|
|
149
|
+
}
|
|
150
|
+
`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function buildImageRenderOptions(options: DiffRenderOptions): DiffRenderOptions {
|
|
155
|
+
return {
|
|
156
|
+
...options,
|
|
157
|
+
presentation: {
|
|
158
|
+
...options.presentation,
|
|
159
|
+
fontSize: Math.max(16, options.presentation.fontSize),
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function shouldRenderViewer(target: DiffRenderTarget): boolean {
|
|
165
|
+
return target === "viewer" || target === "both";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function shouldRenderImage(target: DiffRenderTarget): boolean {
|
|
169
|
+
return target === "image" || target === "both";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function buildRenderVariants(params: { options: DiffRenderOptions; target: DiffRenderTarget }): {
|
|
173
|
+
viewerOptions?: DiffViewerOptions;
|
|
174
|
+
imageOptions?: DiffViewerOptions;
|
|
175
|
+
} {
|
|
176
|
+
return {
|
|
177
|
+
...(shouldRenderViewer(params.target)
|
|
178
|
+
? { viewerOptions: buildDiffOptions(params.options) }
|
|
179
|
+
: {}),
|
|
180
|
+
...(shouldRenderImage(params.target)
|
|
181
|
+
? { imageOptions: buildDiffOptions(buildImageRenderOptions(params.options)) }
|
|
182
|
+
: {}),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function renderDiffCard(payload: DiffViewerPayload): string {
|
|
187
|
+
return `<section class="oc-diff-card">
|
|
188
|
+
<diffs-container class="oc-diff-host" data-autobot-diff-host>
|
|
189
|
+
<template shadowrootmode="open">${payload.prerenderedHTML}</template>
|
|
190
|
+
</diffs-container>
|
|
191
|
+
<script type="application/json" data-autobot-diff-payload>${escapeJsonScript(payload)}</script>
|
|
192
|
+
</section>`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function buildHtmlDocument(params: {
|
|
196
|
+
title: string;
|
|
197
|
+
bodyHtml: string;
|
|
198
|
+
theme: DiffRenderOptions["presentation"]["theme"];
|
|
199
|
+
imageMaxWidth: number;
|
|
200
|
+
runtimeMode: "viewer" | "image";
|
|
201
|
+
}): string {
|
|
202
|
+
return `<!doctype html>
|
|
203
|
+
<html lang="en">
|
|
204
|
+
<head>
|
|
205
|
+
<meta charset="utf-8" />
|
|
206
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
207
|
+
<meta name="color-scheme" content="dark light" />
|
|
208
|
+
<title>${escapeHtml(params.title)}</title>
|
|
209
|
+
<style>
|
|
210
|
+
* {
|
|
211
|
+
box-sizing: border-box;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
html,
|
|
215
|
+
body {
|
|
216
|
+
min-height: 100%;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
html {
|
|
220
|
+
background: #05070b;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
body {
|
|
224
|
+
margin: 0;
|
|
225
|
+
min-height: 100vh;
|
|
226
|
+
padding: 22px;
|
|
227
|
+
font-family:
|
|
228
|
+
"Fira Code",
|
|
229
|
+
"SF Mono",
|
|
230
|
+
Monaco,
|
|
231
|
+
Consolas,
|
|
232
|
+
monospace;
|
|
233
|
+
background: #05070b;
|
|
234
|
+
color: #f8fafc;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
body[data-theme="light"] {
|
|
238
|
+
background: #f3f5f8;
|
|
239
|
+
color: #0f172a;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.oc-frame {
|
|
243
|
+
max-width: 1560px;
|
|
244
|
+
margin: 0 auto;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.oc-frame[data-render-mode="image"] {
|
|
248
|
+
max-width: ${Math.max(640, Math.round(params.imageMaxWidth))}px;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
[data-autobot-diff-root] {
|
|
252
|
+
display: grid;
|
|
253
|
+
gap: 18px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.oc-diff-card {
|
|
257
|
+
overflow: hidden;
|
|
258
|
+
border-radius: 18px;
|
|
259
|
+
border: 1px solid rgba(148, 163, 184, 0.16);
|
|
260
|
+
background: rgba(15, 23, 42, 0.14);
|
|
261
|
+
box-shadow: 0 18px 48px rgba(2, 6, 23, 0.22);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
body[data-theme="light"] .oc-diff-card {
|
|
265
|
+
border-color: rgba(148, 163, 184, 0.22);
|
|
266
|
+
background: rgba(255, 255, 255, 0.92);
|
|
267
|
+
box-shadow: 0 14px 32px rgba(15, 23, 42, 0.08);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.oc-diff-host {
|
|
271
|
+
display: block;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.oc-frame[data-render-mode="image"] .oc-diff-card {
|
|
275
|
+
min-height: 240px;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@media (max-width: 720px) {
|
|
279
|
+
body {
|
|
280
|
+
padding: 12px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
[data-autobot-diff-root] {
|
|
284
|
+
gap: 12px;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
</style>
|
|
288
|
+
</head>
|
|
289
|
+
<body data-theme="${params.theme}">
|
|
290
|
+
<main class="oc-frame" data-render-mode="${params.runtimeMode}">
|
|
291
|
+
<div data-autobot-diff-root>
|
|
292
|
+
${params.bodyHtml}
|
|
293
|
+
</div>
|
|
294
|
+
</main>
|
|
295
|
+
<script type="module" src="${VIEWER_LOADER_DOCUMENT_PATH}"></script>
|
|
296
|
+
</body>
|
|
297
|
+
</html>`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
type RenderedSection = {
|
|
301
|
+
viewer?: string;
|
|
302
|
+
image?: string;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
function buildRenderedSection(params: {
|
|
306
|
+
viewerPayload?: DiffViewerPayload;
|
|
307
|
+
imagePayload?: DiffViewerPayload;
|
|
308
|
+
}): RenderedSection {
|
|
309
|
+
return {
|
|
310
|
+
...(params.viewerPayload ? { viewer: renderDiffCard(params.viewerPayload) } : {}),
|
|
311
|
+
...(params.imagePayload ? { image: renderDiffCard(params.imagePayload) } : {}),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function buildRenderedBodies(sections: ReadonlyArray<RenderedSection>): {
|
|
316
|
+
viewerBodyHtml?: string;
|
|
317
|
+
imageBodyHtml?: string;
|
|
318
|
+
} {
|
|
319
|
+
const viewerSections = sections.flatMap((section) => (section.viewer ? [section.viewer] : []));
|
|
320
|
+
const imageSections = sections.flatMap((section) => (section.image ? [section.image] : []));
|
|
321
|
+
return {
|
|
322
|
+
...(viewerSections.length > 0 ? { viewerBodyHtml: viewerSections.join("\n") } : {}),
|
|
323
|
+
...(imageSections.length > 0 ? { imageBodyHtml: imageSections.join("\n") } : {}),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function renderBeforeAfterDiff(
|
|
328
|
+
input: Extract<DiffInput, { kind: "before_after" }>,
|
|
329
|
+
options: DiffRenderOptions,
|
|
330
|
+
target: DiffRenderTarget,
|
|
331
|
+
): Promise<{ viewerBodyHtml?: string; imageBodyHtml?: string; fileCount: number }> {
|
|
332
|
+
ensurePierreThemesRegistered();
|
|
333
|
+
|
|
334
|
+
const lang = await normalizeSupportedLanguageHint(input.lang);
|
|
335
|
+
const fileName = resolveBeforeAfterFileName({ input, lang });
|
|
336
|
+
const oldFile: FileContents = {
|
|
337
|
+
name: fileName,
|
|
338
|
+
contents: input.before,
|
|
339
|
+
...(lang ? { lang } : {}),
|
|
340
|
+
};
|
|
341
|
+
const newFile: FileContents = {
|
|
342
|
+
name: fileName,
|
|
343
|
+
contents: input.after,
|
|
344
|
+
...(lang ? { lang } : {}),
|
|
345
|
+
};
|
|
346
|
+
const { viewerOptions, imageOptions } = buildRenderVariants({ options, target });
|
|
347
|
+
const [viewerResult, imageResult] = await Promise.all([
|
|
348
|
+
viewerOptions
|
|
349
|
+
? preloadMultiFileDiffWithFallback({
|
|
350
|
+
oldFile,
|
|
351
|
+
newFile,
|
|
352
|
+
options: viewerOptions,
|
|
353
|
+
})
|
|
354
|
+
: Promise.resolve(undefined),
|
|
355
|
+
imageOptions
|
|
356
|
+
? preloadMultiFileDiffWithFallback({
|
|
357
|
+
oldFile,
|
|
358
|
+
newFile,
|
|
359
|
+
options: imageOptions,
|
|
360
|
+
})
|
|
361
|
+
: Promise.resolve(undefined),
|
|
362
|
+
]);
|
|
363
|
+
const [viewerPayload, imagePayload] = await Promise.all([
|
|
364
|
+
viewerResult && viewerOptions
|
|
365
|
+
? normalizeDiffViewerPayloadLanguages({
|
|
366
|
+
prerenderedHTML: viewerResult.prerenderedHTML,
|
|
367
|
+
oldFile: viewerResult.oldFile,
|
|
368
|
+
newFile: viewerResult.newFile,
|
|
369
|
+
options: viewerOptions,
|
|
370
|
+
langs: collectDiffPayloadLanguageHints({
|
|
371
|
+
oldFile: viewerResult.oldFile,
|
|
372
|
+
newFile: viewerResult.newFile,
|
|
373
|
+
}),
|
|
374
|
+
})
|
|
375
|
+
: Promise.resolve(undefined),
|
|
376
|
+
imageResult && imageOptions
|
|
377
|
+
? normalizeDiffViewerPayloadLanguages({
|
|
378
|
+
prerenderedHTML: imageResult.prerenderedHTML,
|
|
379
|
+
oldFile: imageResult.oldFile,
|
|
380
|
+
newFile: imageResult.newFile,
|
|
381
|
+
options: imageOptions,
|
|
382
|
+
langs: collectDiffPayloadLanguageHints({
|
|
383
|
+
oldFile: imageResult.oldFile,
|
|
384
|
+
newFile: imageResult.newFile,
|
|
385
|
+
}),
|
|
386
|
+
})
|
|
387
|
+
: Promise.resolve(undefined),
|
|
388
|
+
]);
|
|
389
|
+
const section = buildRenderedSection({
|
|
390
|
+
...(viewerPayload ? { viewerPayload } : {}),
|
|
391
|
+
...(imagePayload ? { imagePayload } : {}),
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
...buildRenderedBodies([section]),
|
|
396
|
+
fileCount: 1,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function renderPatchDiff(
|
|
401
|
+
input: Extract<DiffInput, { kind: "patch" }>,
|
|
402
|
+
options: DiffRenderOptions,
|
|
403
|
+
target: DiffRenderTarget,
|
|
404
|
+
): Promise<{ viewerBodyHtml?: string; imageBodyHtml?: string; fileCount: number }> {
|
|
405
|
+
ensurePierreThemesRegistered();
|
|
406
|
+
|
|
407
|
+
const files = parsePatchFiles(input.patch).flatMap((entry) => entry.files ?? []);
|
|
408
|
+
if (files.length === 0) {
|
|
409
|
+
throw new Error("Patch input did not contain any file diffs.");
|
|
410
|
+
}
|
|
411
|
+
if (files.length > MAX_PATCH_FILE_COUNT) {
|
|
412
|
+
throw new Error(`Patch input contains too many files (max ${MAX_PATCH_FILE_COUNT}).`);
|
|
413
|
+
}
|
|
414
|
+
const totalLines = files.reduce((sum, fileDiff) => {
|
|
415
|
+
const splitLines = Number.isFinite(fileDiff.splitLineCount) ? fileDiff.splitLineCount : 0;
|
|
416
|
+
const unifiedLines = Number.isFinite(fileDiff.unifiedLineCount) ? fileDiff.unifiedLineCount : 0;
|
|
417
|
+
return sum + Math.max(splitLines, unifiedLines, 0);
|
|
418
|
+
}, 0);
|
|
419
|
+
if (totalLines > MAX_PATCH_TOTAL_LINES) {
|
|
420
|
+
throw new Error(`Patch input is too large to render (max ${MAX_PATCH_TOTAL_LINES} lines).`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const { viewerOptions, imageOptions } = buildRenderVariants({ options, target });
|
|
424
|
+
const sections = await Promise.all(
|
|
425
|
+
files.map(async (fileDiff) => {
|
|
426
|
+
const [viewerResult, imageResult] = await Promise.all([
|
|
427
|
+
viewerOptions
|
|
428
|
+
? preloadFileDiffWithFallback({
|
|
429
|
+
fileDiff,
|
|
430
|
+
options: viewerOptions,
|
|
431
|
+
})
|
|
432
|
+
: Promise.resolve(undefined),
|
|
433
|
+
imageOptions
|
|
434
|
+
? preloadFileDiffWithFallback({
|
|
435
|
+
fileDiff,
|
|
436
|
+
options: imageOptions,
|
|
437
|
+
})
|
|
438
|
+
: Promise.resolve(undefined),
|
|
439
|
+
]);
|
|
440
|
+
|
|
441
|
+
const [viewerPayload, imagePayload] = await Promise.all([
|
|
442
|
+
viewerResult && viewerOptions
|
|
443
|
+
? normalizeDiffViewerPayloadLanguages({
|
|
444
|
+
prerenderedHTML: viewerResult.prerenderedHTML,
|
|
445
|
+
fileDiff: viewerResult.fileDiff,
|
|
446
|
+
options: viewerOptions,
|
|
447
|
+
langs: collectDiffPayloadLanguageHints({ fileDiff: viewerResult.fileDiff }),
|
|
448
|
+
})
|
|
449
|
+
: Promise.resolve(undefined),
|
|
450
|
+
imageResult && imageOptions
|
|
451
|
+
? normalizeDiffViewerPayloadLanguages({
|
|
452
|
+
prerenderedHTML: imageResult.prerenderedHTML,
|
|
453
|
+
fileDiff: imageResult.fileDiff,
|
|
454
|
+
options: imageOptions,
|
|
455
|
+
langs: collectDiffPayloadLanguageHints({ fileDiff: imageResult.fileDiff }),
|
|
456
|
+
})
|
|
457
|
+
: Promise.resolve(undefined),
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
return buildRenderedSection({
|
|
461
|
+
...(viewerPayload ? { viewerPayload } : {}),
|
|
462
|
+
...(imagePayload ? { imagePayload } : {}),
|
|
463
|
+
});
|
|
464
|
+
}),
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
...buildRenderedBodies(sections),
|
|
469
|
+
fileCount: files.length,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export async function renderDiffDocument(
|
|
474
|
+
input: DiffInput,
|
|
475
|
+
options: DiffRenderOptions,
|
|
476
|
+
target: DiffRenderTarget = "both",
|
|
477
|
+
): Promise<RenderedDiffDocument> {
|
|
478
|
+
const title = buildDiffTitle(input);
|
|
479
|
+
const rendered =
|
|
480
|
+
input.kind === "before_after"
|
|
481
|
+
? await renderBeforeAfterDiff(input, options, target)
|
|
482
|
+
: await renderPatchDiff(input, options, target);
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
...(rendered.viewerBodyHtml
|
|
486
|
+
? {
|
|
487
|
+
html: buildHtmlDocument({
|
|
488
|
+
title,
|
|
489
|
+
bodyHtml: rendered.viewerBodyHtml,
|
|
490
|
+
theme: options.presentation.theme,
|
|
491
|
+
imageMaxWidth: options.image.maxWidth,
|
|
492
|
+
runtimeMode: "viewer",
|
|
493
|
+
}),
|
|
494
|
+
}
|
|
495
|
+
: {}),
|
|
496
|
+
...(rendered.imageBodyHtml
|
|
497
|
+
? {
|
|
498
|
+
imageHtml: buildHtmlDocument({
|
|
499
|
+
title,
|
|
500
|
+
bodyHtml: rendered.imageBodyHtml,
|
|
501
|
+
theme: options.presentation.theme,
|
|
502
|
+
imageMaxWidth: options.image.maxWidth,
|
|
503
|
+
runtimeMode: "image",
|
|
504
|
+
}),
|
|
505
|
+
}
|
|
506
|
+
: {}),
|
|
507
|
+
title,
|
|
508
|
+
fileCount: rendered.fileCount,
|
|
509
|
+
inputKind: input.kind,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
type PreloadedFileDiffResult = Awaited<ReturnType<typeof preloadFileDiff>>;
|
|
514
|
+
type PreloadedMultiFileDiffResult = Awaited<ReturnType<typeof preloadMultiFileDiff>>;
|
|
515
|
+
|
|
516
|
+
function shouldFallbackToClientHydration(error: unknown): boolean {
|
|
517
|
+
return (
|
|
518
|
+
error instanceof TypeError &&
|
|
519
|
+
error.message.includes('needs an import attribute of "type: json"')
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function preloadFileDiffWithFallback(params: {
|
|
524
|
+
fileDiff: FileDiffMetadata;
|
|
525
|
+
options: DiffViewerOptions;
|
|
526
|
+
}): Promise<PreloadedFileDiffResult> {
|
|
527
|
+
try {
|
|
528
|
+
return await preloadFileDiff(params);
|
|
529
|
+
} catch (error) {
|
|
530
|
+
if (!shouldFallbackToClientHydration(error)) {
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
fileDiff: params.fileDiff,
|
|
535
|
+
prerenderedHTML: "",
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async function preloadMultiFileDiffWithFallback(params: {
|
|
541
|
+
oldFile: FileContents;
|
|
542
|
+
newFile: FileContents;
|
|
543
|
+
options: DiffViewerOptions;
|
|
544
|
+
}): Promise<PreloadedMultiFileDiffResult> {
|
|
545
|
+
try {
|
|
546
|
+
return await preloadMultiFileDiff(params);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (!shouldFallbackToClientHydration(error)) {
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
oldFile: params.oldFile,
|
|
553
|
+
newFile: params.newFile,
|
|
554
|
+
prerenderedHTML: "",
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
}
|