11<script setup lang="ts">
2- import type { FileDiffResponse , FileChange , DiffHunk } from ' #shared/types'
3- import { createDiff , insertSkipBlocks , countDiffStats } from ' #shared/utils/diff'
2+ import type { FileDiffResponse , FileChange } from ' #shared/types'
43import { onClickOutside } from ' @vueuse/core'
54
65const props = defineProps <{
@@ -20,159 +19,26 @@ const optionsDropdownRef = useTemplateRef('optionsDropdownRef')
2019onClickOutside (optionsDropdownRef , () => {
2120 showOptions .value = false
2221})
23- const loading = ref (true )
24- const loadError = ref <Error | null >(null )
25- const diff = ref <FileDiffResponse | null >(null )
26- const fromContent = ref <string | null >(null )
27- const toContent = ref <string | null >(null )
28- const loadToken = ref (0 )
29-
30- const DIFF_TIMEOUT = 15000
31- const MAX_DIFF_FILE_SIZE = 250 * 1024
32-
33- const optionsParams = computed (() => ({
34- mergeModifiedLines: mergeModifiedLines .value ,
35- maxChangeRatio: maxChangeRatio .value ,
36- maxDiffDistance: maxDiffDistance .value ,
37- inlineMaxCharEdits: inlineMaxCharEdits .value ,
38- }))
39-
40- const status = computed (() => {
41- if (loadError .value ) return ' error'
42- if (loading .value ) return ' pending'
43- return ' success'
44- })
45-
46- async function fetchFileContent(version : string ): Promise <string | null > {
47- const controller = new AbortController ()
48- const timeoutId = setTimeout (() => controller .abort (), DIFF_TIMEOUT )
49- const url = ` https://cdn.jsdelivr.net/npm/${props .packageName }@${version }/${props .file .path } `
50- try {
51- const res = await fetch (url , { signal: controller .signal })
52- if (res .status === 404 ) return null
53- if (! res .ok ) throw new Error (` Failed to fetch file (status ${res .status }) ` )
54-
55- const length = res .headers .get (' content-length' )
56- if (length && parseInt (length , 10 ) > MAX_DIFF_FILE_SIZE ) {
57- throw new Error (
58- ` File too large to diff (${(parseInt (length , 10 ) / 1024 ).toFixed (0 )}KB). Maximum is ${
59- MAX_DIFF_FILE_SIZE / 1024
60- }KB. ` ,
61- )
62- }
63-
64- const text = await res .text ()
65- if (text .length > MAX_DIFF_FILE_SIZE ) {
66- throw new Error (
67- ` File too large to diff (${(text .length / 1024 ).toFixed (0 )}KB). Maximum is ${
68- MAX_DIFF_FILE_SIZE / 1024
69- }KB. ` ,
70- )
71- }
72-
73- return text
74- } catch (err ) {
75- // Provide specific error message for timeout
76- if (err instanceof Error && err .name === ' AbortError' ) {
77- throw new Error (` Request timed out after ${DIFF_TIMEOUT / 1000 }s ` , { cause: err })
78- }
79- throw err
80- } finally {
81- clearTimeout (timeoutId )
82- }
83- }
84-
85- function computeDiff() {
86- if (loadError .value ) return
87- if (loading .value && fromContent .value === null && toContent .value === null ) return
88-
89- const oldContent = fromContent .value ?? ' '
90- const newContent = toContent .value ?? ' '
91-
92- // Determine diff type based on content availability
93- // Note: FileDiffResponse uses 'add'/'delete'/'modify' while FileChange uses
94- // 'added'/'removed'/'modified' - this is intentional to distinguish between
95- // the file-level change info (FileChange) and the diff content type (FileDiff)
96- let type: FileDiffResponse [' type' ] = ' modify'
97- if (fromContent .value === null && toContent .value !== null ) type = ' add'
98- else if (fromContent .value !== null && toContent .value === null ) type = ' delete'
99-
100- const parsed = createDiff (oldContent , newContent , props .file .path , optionsParams .value )
101-
102- if (! parsed ) {
103- diff .value = {
104- package: props .packageName ,
105- from: props .fromVersion ,
106- to: props .toVersion ,
107- path: props .file .path ,
108- type ,
109- hunks: [],
110- stats: { additions: 0 , deletions: 0 },
111- meta: {},
112- }
113- return
114- }
11522
116- const hunksWithSkips = insertSkipBlocks (
117- parsed .hunks .filter ((h ): h is DiffHunk => h .type === ' hunk' ),
118- )
119- const stats = countDiffStats (hunksWithSkips )
120-
121- diff .value = {
122- package: props .packageName ,
123- from: props .fromVersion ,
124- to: props .toVersion ,
125- path: props .file .path ,
126- // Use the type computed from file existence (modify/add/delete) rather than
127- // parsed.type which incorrectly classifies addition-only modifications as 'add'.
128- // This matters because DiffLine skips green background highlighting when
129- // fileStatus is 'add' (since for truly new files all lines are additions).
130- type ,
131- hunks: hunksWithSkips ,
132- stats ,
133- meta: {},
134- }
135- }
136-
137- async function loadContents() {
138- const token = ++ loadToken .value
139- loading .value = true
140- loadError .value = null
141- try {
142- const [from, to] = await Promise .all ([
143- fetchFileContent (props .fromVersion ),
144- fetchFileContent (props .toVersion ),
145- ])
146-
147- if (token !== loadToken .value ) return
148-
149- fromContent .value = from
150- toContent .value = to
151-
152- if (from === null && to === null ) {
153- throw new Error (' File not found in either version' )
154- }
155-
156- computeDiff ()
157- } catch (err ) {
158- if (token !== loadToken .value ) return
159- loadError .value = err as Error
160- diff .value = null
161- } finally {
162- if (token === loadToken .value ) loading .value = false
163- }
164- }
165-
166- watch (
167- [() => props .file .path , () => props .fromVersion , () => props .toVersion ],
168- () => {
169- loadContents ()
170- },
171- { immediate: true },
23+ const apiUrl = computed (
24+ () =>
25+ ` /api/registry/compare-file/${props .packageName }/v/${props .fromVersion }...${props .toVersion }/${props .file .path } ` ,
17226)
17327
174- watch ([mergeModifiedLines , maxChangeRatio , maxDiffDistance , inlineMaxCharEdits ], () => {
175- computeDiff ()
28+ const apiQuery = computed (() => ({
29+ mergeModifiedLines: String (mergeModifiedLines .value ),
30+ maxChangeRatio: String (maxChangeRatio .value ),
31+ maxDiffDistance: String (maxDiffDistance .value ),
32+ inlineMaxCharEdits: String (inlineMaxCharEdits .value ),
33+ }))
34+
35+ const {
36+ data : diff,
37+ status,
38+ error : loadError,
39+ } = useFetch <FileDiffResponse >(apiUrl , {
40+ query: apiQuery ,
41+ timeout: 15000 ,
17642})
17743
17844function calcPercent(value : number , min : number , max : number ): number {
@@ -466,7 +332,6 @@ function getCodeUrl(version: string): string {
466332 :hunks =" diff.hunks"
467333 :type =" diff.type"
468334 :file-name =" file.path"
469- :enable-shiki =" true"
470335 :word-wrap =" wordWrap"
471336 />
472337 </div >
0 commit comments