Skip to content

Commit a787b16

Browse files
committed
fix: handle file-directory transitions in compare
WIP
1 parent bfb124f commit a787b16

11 files changed

Lines changed: 162 additions & 95 deletions

File tree

app/components/diff/Line.vue

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ const lineNumberOld = computed(() => {
2626
if (props.line.type === 'normal') {
2727
return props.line.oldLineNumber
2828
}
29-
return props.line.type === 'delete' ? props.line.lineNumber ?? props.line.oldLineNumber : undefined
29+
return props.line.type === 'delete'
30+
? (props.line.lineNumber ?? props.line.oldLineNumber)
31+
: undefined
3032
})
3133
3234
const rowClasses = computed(() => {
@@ -74,15 +76,13 @@ function normalizeLanguage(raw?: string): 'javascript' | 'typescript' | 'json' |
7476
const lang = raw.toLowerCase()
7577
if (lang.includes('json')) return 'json'
7678
if (lang === 'ts' || lang.includes('typescript') || lang.includes('tsx')) return 'typescript'
77-
if (lang === 'js' || lang.includes('javascript') || lang.includes('mjs') || lang.includes('cjs')) return 'javascript'
79+
if (lang === 'js' || lang.includes('javascript') || lang.includes('mjs') || lang.includes('cjs'))
80+
return 'javascript'
7881
return 'plaintext'
7982
}
8083
8184
function escapeHtml(str: string): string {
82-
return str
83-
.replace(/&/g, '&')
84-
.replace(/</g, '&lt;')
85-
.replace(/>/g, '&gt;')
85+
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
8686
}
8787
8888
async function highlightSegments() {
@@ -91,7 +91,10 @@ async function highlightSegments() {
9191
const lang = normalizeLanguage(diffContext?.language?.value)
9292
// If language unsupported, keep escaped plain text
9393
if (lang === 'plaintext') {
94-
renderedSegments.value = props.line.content.map(seg => ({ html: escapeHtml(seg.value), type: seg.type }))
94+
renderedSegments.value = props.line.content.map(seg => ({
95+
html: escapeHtml(seg.value),
96+
type: seg.type,
97+
}))
9598
return
9699
}
97100

app/components/diff/Table.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const props = defineProps<{
1010
wordWrap?: boolean
1111
}>()
1212
13-
1413
const language = computed(() => {
1514
if (!props.fileName) return 'text'
1615
return guessLanguageFromPath(props.fileName)

app/components/diff/ViewerPanel.vue

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ function computeDiff() {
102102
}
103103
104104
const hunksWithSkips = insertSkipBlocks(
105-
parsed.hunks.filter((h): h is typeof parsed.hunks[number] & { type: 'hunk' } => h.type === 'hunk'),
105+
parsed.hunks.filter(
106+
(h): h is (typeof parsed.hunks)[number] & { type: 'hunk' } => h.type === 'hunk',
107+
),
106108
)
107109
const stats = countDiffStats(hunksWithSkips)
108110
@@ -187,7 +189,6 @@ const changeRatioPercent = computed(() => calcPercent(maxChangeRatio.value, 0, 1
187189
const diffDistancePercent = computed(() => calcPercent(maxDiffDistance.value, 1, 60))
188190
const charEditPercent = computed(() => calcPercent(inlineMaxCharEdits.value, 0, 10))
189191
190-
191192
function formatBytes(bytes: number | undefined): string {
192193
if (bytes === undefined) return ''
193194
if (bytes < 1024) return `${bytes} B`
@@ -225,10 +226,7 @@ function getCodeUrl(version: string): string {
225226

226227
<!-- Stats -->
227228
<template v-if="diff?.stats">
228-
<span
229-
v-if="diff.stats.additions > 0"
230-
class="text-xs text-green-500 font-mono shrink-0"
231-
>
229+
<span v-if="diff.stats.additions > 0" class="text-xs text-green-500 font-mono shrink-0">
232230
+{{ diff.stats.additions }}
233231
</span>
234232
<span v-if="diff.stats.deletions > 0" class="text-xs text-red-500 font-mono shrink-0">
@@ -261,7 +259,10 @@ function getCodeUrl(version: string): string {
261259
>
262260
<span class="i-carbon-settings w-3.5 h-3.5" />
263261
Options
264-
<span class="i-carbon-chevron-down w-3 h-3 transition-transform" :class="{ 'rotate-180': showOptions }" />
262+
<span
263+
class="i-carbon-chevron-down w-3 h-3 transition-transform"
264+
:class="{ 'rotate-180': showOptions }"
265+
/>
265266
</button>
266267

267268
<!-- Dropdown menu -->
@@ -316,12 +317,18 @@ function getCodeUrl(version: string): string {
316317
</div>
317318

318319
<!-- Sliders -->
319-
<motion.div class="flex flex-col gap-2" :animate="{ opacity: mergeModifiedLines ? 1 : 0 }">
320+
<motion.div
321+
class="flex flex-col gap-2"
322+
:animate="{ opacity: mergeModifiedLines ? 1 : 0 }"
323+
>
320324
<!-- Change ratio slider -->
321325
<div class="sr-only">
322326
<label for="change-ratio">Change ratio</label>
323327
</div>
324-
<div class="slider-shell min-w-[360px]" :class="{ 'is-disabled': !mergeModifiedLines }">
328+
<div
329+
class="slider-shell min-w-[360px]"
330+
:class="{ 'is-disabled': !mergeModifiedLines }"
331+
>
325332
<div class="slider-labels">
326333
<span class="slider-label">Change ratio</span>
327334
<span class="slider-value tabular-nums">{{ maxChangeRatio.toFixed(2) }}</span>
@@ -351,7 +358,10 @@ function getCodeUrl(version: string): string {
351358
<div class="sr-only">
352359
<label for="diff-distance">Diff distance</label>
353360
</div>
354-
<div class="slider-shell min-w-[360px]" :class="{ 'is-disabled': !mergeModifiedLines }">
361+
<div
362+
class="slider-shell min-w-[360px]"
363+
:class="{ 'is-disabled': !mergeModifiedLines }"
364+
>
355365
<div class="slider-labels">
356366
<span class="slider-label">Diff distance</span>
357367
<span class="slider-value tabular-nums">{{ maxDiffDistance }}</span>
@@ -381,7 +391,10 @@ function getCodeUrl(version: string): string {
381391
<div class="sr-only">
382392
<label for="char-edits">Char edits</label>
383393
</div>
384-
<div class="slider-shell min-w-[360px]" :class="{ 'is-disabled': !mergeModifiedLines }">
394+
<div
395+
class="slider-shell min-w-[360px]"
396+
:class="{ 'is-disabled': !mergeModifiedLines }"
397+
>
385398
<div class="slider-labels">
386399
<span class="slider-label">Char edits</span>
387400
<span class="slider-value tabular-nums">{{ inlineMaxCharEdits }}</span>
@@ -465,7 +478,6 @@ function getCodeUrl(version: string): string {
465478
:enable-shiki="true"
466479
:word-wrap="wordWrap"
467480
/>
468-
469481
</div>
470482
</div>
471483
</template>
@@ -494,7 +506,10 @@ function getCodeUrl(version: string): string {
494506
border-radius: 12px;
495507
overflow: hidden;
496508
cursor: grab;
497-
transition: background-color 150ms ease, border-color 150ms ease, opacity 150ms ease;
509+
transition:
510+
background-color 150ms ease,
511+
border-color 150ms ease,
512+
opacity 150ms ease;
498513
}
499514
500515
.slider-shell:hover {
@@ -569,7 +584,9 @@ function getCodeUrl(version: string): string {
569584
inset: 0 auto 0 0;
570585
background: var(--bg-subtle);
571586
border-radius: 10px;
572-
transition: width 150ms ease-out, background-color 150ms ease-out;
587+
transition:
588+
width 150ms ease-out,
589+
background-color 150ms ease-out;
573590
z-index: 2;
574591
pointer-events: none;
575592
}

app/pages/compare/[...path].vue

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ function getChangeIcon(type: FileChange['type']): string {
121121
}
122122
}
123123
124-
125124
const fromVersionUrlPattern = computed(() => {
126125
return `/compare/${packageName.value}/v/{version}...${toVersion.value}`
127126
})
@@ -204,7 +203,9 @@ useSeoMeta({
204203

205204
<!-- Error: invalid route -->
206205
<div v-if="!parsedRoute.range" class="container py-20 text-center">
207-
<p class="text-fg-muted mb-4">Invalid comparison URL. Use format: /compare/package/v/from...to</p>
206+
<p class="text-fg-muted mb-4">
207+
Invalid comparison URL. Use format: /compare/package/v/from...to
208+
</p>
208209
<NuxtLink :to="packageRoute()" class="btn">Go to package</NuxtLink>
209210
</div>
210211

@@ -239,7 +240,9 @@ useSeoMeta({
239240
<span class="text-yellow-500">~{{ compare.stats.filesModified }}</span>
240241
</span>
241242
<span v-if="compare.dependencyChanges.length > 0" class="text-fg-muted">
242-
{{ compare.dependencyChanges.length }} dep{{ compare.dependencyChanges.length !== 1 ? 's' : '' }}
243+
{{ compare.dependencyChanges.length }} dep{{
244+
compare.dependencyChanges.length !== 1 ? 's' : ''
245+
}}
243246
</span>
244247
</div>
245248
</div>
@@ -272,11 +275,10 @@ useSeoMeta({
272275
</summary>
273276

274277
<div class="space-y-2 ml-5 max-h-40 overflow-y-auto">
275-
<div
276-
v-for="[section, changes] in groupedDeps"
277-
:key="section"
278-
>
279-
<div class="text-[10px] font-medium text-fg-subtle uppercase tracking-wide mb-1.5">
278+
<div v-for="[section, changes] in groupedDeps" :key="section">
279+
<div
280+
class="text-[10px] font-medium text-fg-subtle uppercase tracking-wide mb-1.5"
281+
>
280282
{{ formatSection(section) }}
281283
</div>
282284
<div class="space-y-1">
@@ -306,11 +308,19 @@ useSeoMeta({
306308
</NuxtLink>
307309

308310
<!-- Version change -->
309-
<div class="flex items-center gap-1.5 text-fg-muted font-mono text-[10px] ml-auto shrink-0">
310-
<span v-if="dep.from" :class="{ 'line-through opacity-50': dep.type === 'updated' }">
311+
<div
312+
class="flex items-center gap-1.5 text-fg-muted font-mono text-[10px] ml-auto shrink-0"
313+
>
314+
<span
315+
v-if="dep.from"
316+
:class="{ 'line-through opacity-50': dep.type === 'updated' }"
317+
>
311318
{{ dep.from }}
312319
</span>
313-
<span v-if="dep.type === 'updated'" class="i-carbon-arrow-right w-2.5 h-2.5" />
320+
<span
321+
v-if="dep.type === 'updated'"
322+
class="i-carbon-arrow-right w-2.5 h-2.5"
323+
/>
314324
<span v-if="dep.to">{{ dep.to }}</span>
315325
</div>
316326

@@ -345,7 +355,7 @@ useSeoMeta({
345355
<span class="i-carbon-document w-3.5 h-3.5" />
346356
Changed Files
347357
</h2>
348-
358+
349359
<!-- Filter dropdown -->
350360
<select
351361
v-model="fileFilter"
@@ -371,7 +381,9 @@ useSeoMeta({
371381
:key="file.path"
372382
type="button"
373383
class="w-full px-3 py-2 flex items-center gap-2 text-sm text-left hover:bg-bg-muted transition-colors group"
374-
:class="{ 'bg-bg-muted border-l-3 border-l-blue-500': selectedFile?.path === file.path }"
384+
:class="{
385+
'bg-bg-muted border-l-3 border-l-blue-500': selectedFile?.path === file.path,
386+
}"
375387
@click="selectedFile = file"
376388
>
377389
<!-- File icon -->
@@ -391,7 +403,6 @@ useSeoMeta({
391403

392404
<!-- Right side: Diff viewer -->
393405
<div class="flex-1 flex flex-col min-w-0 overflow-hidden">
394-
395406
<!-- Diff viewer panel -->
396407
<div class="flex-1 overflow-hidden bg-bg-subtle">
397408
<DiffViewerPanel
@@ -403,7 +414,9 @@ useSeoMeta({
403414
/>
404415
<div v-else class="h-full flex items-center justify-center text-center p-8">
405416
<div>
406-
<span class="i-carbon-document-blank w-16 h-16 mx-auto text-fg-subtle/50 block mb-4" />
417+
<span
418+
class="i-carbon-document-blank w-16 h-16 mx-auto text-fg-subtle/50 block mb-4"
419+
/>
407420
<p class="text-fg-muted">Select a file from the sidebar to view its diff</p>
408421
</div>
409422
</div>

app/utils/cn.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { clsx, type ClassValue } from "clsx"
2-
import { twMerge } from "tailwind-merge"
1+
import { clsx, type ClassValue } from 'clsx'
2+
import { twMerge } from 'tailwind-merge'
33

44
export function cn(...inputs: ClassValue[]) {
55
return twMerge(clsx(inputs))

app/utils/language-detection.ts

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,62 +9,62 @@ export function guessLanguageFromPath(filePath: string): string {
99
// Extension-based mapping
1010
const extMap: Record<string, string> = {
1111
// JavaScript/TypeScript
12-
'js': 'javascript',
13-
'mjs': 'javascript',
14-
'cjs': 'javascript',
15-
'jsx': 'jsx',
16-
'ts': 'typescript',
17-
'mts': 'typescript',
18-
'cts': 'typescript',
19-
'tsx': 'tsx',
12+
js: 'javascript',
13+
mjs: 'javascript',
14+
cjs: 'javascript',
15+
jsx: 'jsx',
16+
ts: 'typescript',
17+
mts: 'typescript',
18+
cts: 'typescript',
19+
tsx: 'tsx',
2020

2121
// Web
22-
'html': 'html',
23-
'htm': 'html',
24-
'css': 'css',
25-
'scss': 'scss',
26-
'sass': 'scss',
27-
'less': 'less',
28-
'vue': 'vue',
29-
'svelte': 'svelte',
30-
'astro': 'astro',
22+
html: 'html',
23+
htm: 'html',
24+
css: 'css',
25+
scss: 'scss',
26+
sass: 'scss',
27+
less: 'less',
28+
vue: 'vue',
29+
svelte: 'svelte',
30+
astro: 'astro',
3131

3232
// Data/Config
33-
'json': 'json',
34-
'jsonc': 'jsonc',
35-
'json5': 'json',
36-
'yaml': 'yaml',
37-
'yml': 'yaml',
38-
'toml': 'toml',
39-
'xml': 'xml',
40-
'svg': 'xml',
33+
json: 'json',
34+
jsonc: 'jsonc',
35+
json5: 'json',
36+
yaml: 'yaml',
37+
yml: 'yaml',
38+
toml: 'toml',
39+
xml: 'xml',
40+
svg: 'xml',
4141

4242
// Documentation
43-
'md': 'markdown',
44-
'mdx': 'markdown',
45-
'txt': 'text',
43+
md: 'markdown',
44+
mdx: 'markdown',
45+
txt: 'text',
4646

4747
// Shell
48-
'sh': 'bash',
49-
'bash': 'bash',
50-
'zsh': 'bash',
51-
'fish': 'bash',
48+
sh: 'bash',
49+
bash: 'bash',
50+
zsh: 'bash',
51+
fish: 'bash',
5252

5353
// Other languages
54-
'py': 'python',
55-
'rs': 'rust',
56-
'go': 'go',
57-
'sql': 'sql',
58-
'graphql': 'graphql',
59-
'gql': 'graphql',
60-
'diff': 'diff',
61-
'patch': 'diff',
54+
py: 'python',
55+
rs: 'rust',
56+
go: 'go',
57+
sql: 'sql',
58+
graphql: 'graphql',
59+
gql: 'graphql',
60+
diff: 'diff',
61+
patch: 'diff',
6262
}
6363

6464
// Special filename mapping
6565
const filenameMap: Record<string, string> = {
66-
'Dockerfile': 'dockerfile',
67-
'Makefile': 'makefile',
66+
Dockerfile: 'dockerfile',
67+
Makefile: 'makefile',
6868
}
6969

7070
if (filenameMap[fileName]) {

lunaria/files/en-US.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,4 +642,4 @@
642642
"lines_hidden": "{count} lines hidden",
643643
"compare_versions": "Compare versions"
644644
}
645-
}
645+
}

0 commit comments

Comments
 (0)