@joewinke/jatui 0.1.22 → 0.1.24
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/CLAUDE.md +244 -0
- package/package.json +23 -10
- package/src/lib/actions/collapse.ts +0 -134
- package/src/lib/actions/railNav.ts +0 -473
- package/src/lib/components/AnnotationLayer.svelte +0 -108
- package/src/lib/components/AnnotationPanel.svelte +0 -319
- package/src/lib/components/AudioWaveform.svelte +0 -698
- package/src/lib/components/AvailabilityModal.svelte +0 -177
- package/src/lib/components/AvatarUpload.svelte +0 -234
- package/src/lib/components/Badge.svelte +0 -38
- package/src/lib/components/BookingForm.svelte +0 -278
- package/src/lib/components/BurndownChart.svelte +0 -778
- package/src/lib/components/Button.svelte +0 -81
- package/src/lib/components/CalendarPicker.svelte +0 -284
- package/src/lib/components/Card.svelte +0 -67
- package/src/lib/components/CharacterCounter.svelte +0 -82
- package/src/lib/components/ChipInput.svelte +0 -609
- package/src/lib/components/Collapse.svelte +0 -82
- package/src/lib/components/ColorSelector.svelte +0 -167
- package/src/lib/components/CommentThread.svelte +0 -773
- package/src/lib/components/ConfirmDialog.svelte +0 -348
- package/src/lib/components/ConfirmModal.svelte +0 -142
- package/src/lib/components/ContextMenu.svelte +0 -188
- package/src/lib/components/CountdownTimer.svelte +0 -94
- package/src/lib/components/DateRangePicker.svelte +0 -194
- package/src/lib/components/Drawer.svelte +0 -143
- package/src/lib/components/EmojiPicker.svelte +0 -159
- package/src/lib/components/EntityPreviewCard.svelte +0 -104
- package/src/lib/components/FileDropzone.svelte +0 -493
- package/src/lib/components/FilePicker.svelte +0 -488
- package/src/lib/components/FileThumbnail.svelte +0 -80
- package/src/lib/components/FilterDropdown.svelte +0 -203
- package/src/lib/components/FloatingActionBar.svelte +0 -354
- package/src/lib/components/GPSTracker.svelte +0 -202
- package/src/lib/components/HunkDiffView.svelte +0 -348
- package/src/lib/components/ImageLightbox.svelte +0 -274
- package/src/lib/components/ImageUpload.svelte +0 -147
- package/src/lib/components/InlineEdit.svelte +0 -291
- package/src/lib/components/InputDialog.svelte +0 -327
- package/src/lib/components/KeyboardShortcutsOverlay.svelte +0 -296
- package/src/lib/components/LazyImage.svelte +0 -123
- package/src/lib/components/LinkShortener.svelte +0 -274
- package/src/lib/components/LoadingSpinner.svelte +0 -106
- package/src/lib/components/LocationMap.svelte +0 -186
- package/src/lib/components/MapView.svelte +0 -341
- package/src/lib/components/MarkupEditor.svelte +0 -485
- package/src/lib/components/MarkupOverlay.svelte +0 -55
- package/src/lib/components/MediaWorkbench.svelte +0 -871
- package/src/lib/components/MilestoneCard.svelte +0 -98
- package/src/lib/components/MilestoneTimeline.svelte +0 -91
- package/src/lib/components/Modal.svelte +0 -244
- package/src/lib/components/PDFViewer.svelte +0 -105
- package/src/lib/components/PdfThumbnail.svelte +0 -63
- package/src/lib/components/PhoneInput.svelte +0 -212
- package/src/lib/components/ResizableDivider.svelte +0 -307
- package/src/lib/components/ResizablePanel.svelte +0 -305
- package/src/lib/components/SearchDropdown.svelte +0 -421
- package/src/lib/components/SelectInput.svelte +0 -237
- package/src/lib/components/SidebarUserFooter.svelte +0 -34
- package/src/lib/components/SignaturePad.svelte +0 -175
- package/src/lib/components/SmartImageEditor.svelte +0 -720
- package/src/lib/components/SortDropdown.svelte +0 -156
- package/src/lib/components/Sparkline.svelte +0 -116
- package/src/lib/components/SpeechForm.svelte +0 -114
- package/src/lib/components/StatusBadge.svelte +0 -157
- package/src/lib/components/TaskTypeIcon.svelte +0 -54
- package/src/lib/components/TextArea.svelte +0 -162
- package/src/lib/components/TextInput.svelte +0 -131
- package/src/lib/components/ThemeSelector.svelte +0 -206
- package/src/lib/components/TimeSlotPicker.svelte +0 -162
- package/src/lib/components/UserAvatar.svelte +0 -72
- package/src/lib/components/VariablePicker.svelte +0 -170
- package/src/lib/components/VoicePlayer.svelte +0 -422
- package/src/lib/components/linked-columns/LinkedColumns.svelte +0 -526
- package/src/lib/components/markup.ts +0 -287
- package/src/lib/components/messaging/Avatar.svelte +0 -81
- package/src/lib/components/messaging/ChannelInfoModal.svelte +0 -163
- package/src/lib/components/messaging/ChannelList.svelte +0 -107
- package/src/lib/components/messaging/ChannelMemberAvatarStack.svelte +0 -69
- package/src/lib/components/messaging/ChannelMembersModal.svelte +0 -182
- package/src/lib/components/messaging/CreateChannelModal.svelte +0 -190
- package/src/lib/components/messaging/DirectMessageList.svelte +0 -145
- package/src/lib/components/messaging/EmojiSelector.svelte +0 -261
- package/src/lib/components/messaging/MentionAutocomplete.svelte +0 -193
- package/src/lib/components/messaging/MessageAttachment.svelte +0 -273
- package/src/lib/components/messaging/MessageAttachmentUpload.svelte +0 -243
- package/src/lib/components/messaging/MessageInput.svelte +0 -452
- package/src/lib/components/messaging/MessageItem.svelte +0 -341
- package/src/lib/components/messaging/MessageThread.svelte +0 -306
- package/src/lib/components/messaging/NotificationSettingsModal.svelte +0 -234
- package/src/lib/components/messaging/QuotedMessageDisplay.svelte +0 -123
- package/src/lib/components/messaging/StartDMModal.svelte +0 -100
- package/src/lib/components/messaging/ThreadPanel.svelte +0 -153
- package/src/lib/components/pipeline/DESIGN.md +0 -361
- package/src/lib/components/pipeline/Pipeline.svelte +0 -391
- package/src/lib/components/pipeline/PipelineCard.svelte +0 -94
- package/src/lib/components/pipeline/PipelineColumn.svelte +0 -163
- package/src/lib/components/replay/ChapterTimeline.svelte +0 -326
- package/src/lib/components/session-nav/transcriptModel.ts +0 -352
- package/src/lib/data/emojis.ts +0 -1208
- package/src/lib/index.ts +0 -405
- package/src/lib/stores/confirmDialog.svelte.ts +0 -48
- package/src/lib/stores/inputDialog.svelte.ts +0 -51
- package/src/lib/styles/rail.css +0 -63
- package/src/lib/types/annotation.ts +0 -38
- package/src/lib/types/booking.ts +0 -143
- package/src/lib/types/comments.ts +0 -97
- package/src/lib/types/entityPreview.ts +0 -45
- package/src/lib/types/filePicker.ts +0 -16
- package/src/lib/types/googleMaps.d.ts +0 -51
- package/src/lib/types/maps.ts +0 -43
- package/src/lib/types/messaging.ts +0 -459
- package/src/lib/types/milestone.ts +0 -22
- package/src/lib/types/pipeline.ts +0 -150
- package/src/lib/types/smartImageEditor.ts +0 -39
- package/src/lib/types/templateVars.ts +0 -36
- package/src/lib/utils/currency.ts +0 -20
- package/src/lib/utils/daisyuiColors.ts +0 -243
- package/src/lib/utils/dateFormatters.ts +0 -196
- package/src/lib/utils/googleMapsLoader.ts +0 -84
- package/src/lib/utils/mentionParser.ts +0 -188
- package/src/lib/utils/phone.ts +0 -80
- package/src/lib/utils/phoneFormat.ts +0 -74
- package/src/lib/utils/taskUtils.ts +0 -75
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* GPSTracker — headless GPS tracking action component.
|
|
4
|
-
*
|
|
5
|
-
* Watches the device's geolocation and fires `onLocationUpdate` on each new
|
|
6
|
-
* fix. Uses `watchPosition` on mobile; falls back to interval polling on
|
|
7
|
-
* desktop. Renders a small fixed status indicator in the corner.
|
|
8
|
-
*
|
|
9
|
-
* The host app is responsible for sending coordinates to the server —
|
|
10
|
-
* use the `onLocationUpdate` callback to do so (no hardcoded endpoint).
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
*
|
|
14
|
-
* <GPSTracker
|
|
15
|
-
* enabled={true}
|
|
16
|
-
* onLocationUpdate={async (pos) => {
|
|
17
|
-
* await fetch('/api/location', {
|
|
18
|
-
* method: 'POST',
|
|
19
|
-
* body: JSON.stringify({ lat: pos.coords.latitude, lng: pos.coords.longitude })
|
|
20
|
-
* });
|
|
21
|
-
* }}
|
|
22
|
-
* />
|
|
23
|
-
*
|
|
24
|
-
* Ported from flush technician/GPSTracker (298L) — steelbridge fork capture (jst-e0d1u.4).
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
interface Props {
|
|
28
|
-
enabled?: boolean;
|
|
29
|
-
/** How often to poll position in seconds (mobile uses watchPosition + this; desktop uses this * 2). */
|
|
30
|
-
updateInterval?: number;
|
|
31
|
-
onLocationUpdate?: (position: GeolocationPosition) => void;
|
|
32
|
-
onError?: (error: GeolocationPositionError) => void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let { enabled = true, updateInterval = 30, onLocationUpdate, onError }: Props = $props();
|
|
36
|
-
|
|
37
|
-
let watchId: number | null = null;
|
|
38
|
-
let intervalId: number | null = null;
|
|
39
|
-
let lastPosition = $state<GeolocationPosition | null>(null);
|
|
40
|
-
let isTracking = $state(false);
|
|
41
|
-
let hasPermission = $state<boolean | null>(null);
|
|
42
|
-
let lastUpdate = $state<Date | null>(null);
|
|
43
|
-
let isMounted = false;
|
|
44
|
-
|
|
45
|
-
import { onMount, onDestroy } from 'svelte';
|
|
46
|
-
|
|
47
|
-
onMount(() => {
|
|
48
|
-
if (isMounted) return;
|
|
49
|
-
isMounted = true;
|
|
50
|
-
if (typeof navigator !== 'undefined' && enabled) {
|
|
51
|
-
checkPermission();
|
|
52
|
-
setTimeout(() => {
|
|
53
|
-
if (hasPermission !== false) startTracking();
|
|
54
|
-
}, 100);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
onDestroy(() => {
|
|
59
|
-
isMounted = false;
|
|
60
|
-
stopTracking();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
async function checkPermission() {
|
|
64
|
-
if (typeof navigator === 'undefined' || !navigator.permissions) return;
|
|
65
|
-
try {
|
|
66
|
-
const result = await navigator.permissions.query({ name: 'geolocation' });
|
|
67
|
-
hasPermission = result.state === 'granted';
|
|
68
|
-
result.addEventListener('change', () => {
|
|
69
|
-
hasPermission = result.state === 'granted';
|
|
70
|
-
if (hasPermission && enabled) startTracking();
|
|
71
|
-
else stopTracking();
|
|
72
|
-
});
|
|
73
|
-
} catch {
|
|
74
|
-
// permissions API not available — proceed optimistically
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function startTracking() {
|
|
79
|
-
if (typeof navigator === 'undefined' || !navigator.geolocation || !enabled || isTracking) return;
|
|
80
|
-
|
|
81
|
-
isTracking = true;
|
|
82
|
-
|
|
83
|
-
// Initial fix with relaxed settings (faster on desktop)
|
|
84
|
-
navigator.geolocation.getCurrentPosition(
|
|
85
|
-
(pos) => {
|
|
86
|
-
handleSuccess(pos);
|
|
87
|
-
// Try for a better fix
|
|
88
|
-
navigator.geolocation.getCurrentPosition(handleSuccess, () => {}, {
|
|
89
|
-
enableHighAccuracy: true,
|
|
90
|
-
timeout: 10000,
|
|
91
|
-
maximumAge: 0
|
|
92
|
-
});
|
|
93
|
-
},
|
|
94
|
-
(err) => {
|
|
95
|
-
if (err.code === 3) {
|
|
96
|
-
// TIMEOUT — try once more with any cached position
|
|
97
|
-
navigator.geolocation.getCurrentPosition(handleSuccess, handleError, {
|
|
98
|
-
enableHighAccuracy: false,
|
|
99
|
-
timeout: 15000,
|
|
100
|
-
maximumAge: Infinity
|
|
101
|
-
});
|
|
102
|
-
} else {
|
|
103
|
-
handleError(err);
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
{ enableHighAccuracy: false, timeout: 8000, maximumAge: 600000 }
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
// Continuous watch on mobile only
|
|
110
|
-
const isMobile =
|
|
111
|
-
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
112
|
-
if (isMobile) {
|
|
113
|
-
watchId = navigator.geolocation.watchPosition(handleSuccess, handleError, {
|
|
114
|
-
enableHighAccuracy: true,
|
|
115
|
-
timeout: 10000,
|
|
116
|
-
maximumAge: 30000
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Interval polling
|
|
121
|
-
const freq = (isMobile ? updateInterval : updateInterval * 2) * 1000;
|
|
122
|
-
intervalId = window.setInterval(() => {
|
|
123
|
-
navigator.geolocation.getCurrentPosition(
|
|
124
|
-
handleSuccess,
|
|
125
|
-
() => {}, // ignore interval failures silently
|
|
126
|
-
{ enableHighAccuracy: false, timeout: 10000, maximumAge: 300000 }
|
|
127
|
-
);
|
|
128
|
-
}, freq);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function stopTracking() {
|
|
132
|
-
isTracking = false;
|
|
133
|
-
if (watchId !== null) {
|
|
134
|
-
navigator.geolocation.clearWatch(watchId);
|
|
135
|
-
watchId = null;
|
|
136
|
-
}
|
|
137
|
-
if (intervalId !== null) {
|
|
138
|
-
clearInterval(intervalId);
|
|
139
|
-
intervalId = null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function handleSuccess(position: GeolocationPosition) {
|
|
144
|
-
lastPosition = position;
|
|
145
|
-
lastUpdate = new Date();
|
|
146
|
-
onLocationUpdate?.(position);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function handleError(error: GeolocationPositionError) {
|
|
150
|
-
if (error.code === error.PERMISSION_DENIED) {
|
|
151
|
-
hasPermission = false;
|
|
152
|
-
stopTracking();
|
|
153
|
-
}
|
|
154
|
-
onError?.(error);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
$effect(() => {
|
|
158
|
-
if (enabled && !isTracking && hasPermission !== false) startTracking();
|
|
159
|
-
else if (!enabled && isTracking) stopTracking();
|
|
160
|
-
});
|
|
161
|
-
</script>
|
|
162
|
-
|
|
163
|
-
<!-- GPS status indicator (fixed bottom-right) -->
|
|
164
|
-
{#if typeof navigator !== 'undefined'}
|
|
165
|
-
<div class="fixed bottom-20 right-4 bg-base-200 rounded-lg shadow-lg p-2 text-xs z-40">
|
|
166
|
-
<div class="flex items-center gap-2">
|
|
167
|
-
<div
|
|
168
|
-
class="w-2 h-2 rounded-full {isTracking
|
|
169
|
-
? 'bg-success'
|
|
170
|
-
: hasPermission === false
|
|
171
|
-
? 'bg-error'
|
|
172
|
-
: 'bg-warning'}"
|
|
173
|
-
></div>
|
|
174
|
-
<span>GPS {isTracking ? 'Active' : hasPermission === false ? 'Denied' : 'Inactive'}</span>
|
|
175
|
-
</div>
|
|
176
|
-
|
|
177
|
-
{#if hasPermission === false}
|
|
178
|
-
<div class="mt-2">
|
|
179
|
-
<button
|
|
180
|
-
type="button"
|
|
181
|
-
class="btn btn-xs btn-primary"
|
|
182
|
-
onclick={() => {
|
|
183
|
-
hasPermission = null;
|
|
184
|
-
isTracking = false;
|
|
185
|
-
startTracking();
|
|
186
|
-
}}
|
|
187
|
-
>
|
|
188
|
-
Enable GPS
|
|
189
|
-
</button>
|
|
190
|
-
</div>
|
|
191
|
-
<div class="text-error mt-1">Location required</div>
|
|
192
|
-
{/if}
|
|
193
|
-
|
|
194
|
-
{#if lastUpdate && isTracking}
|
|
195
|
-
<div class="text-base-content/70">Updated: {lastUpdate.toLocaleTimeString()}</div>
|
|
196
|
-
{/if}
|
|
197
|
-
|
|
198
|
-
{#if lastPosition && isTracking}
|
|
199
|
-
<div class="text-base-content/70">Accuracy: {Math.round(lastPosition.coords.accuracy)}m</div>
|
|
200
|
-
{/if}
|
|
201
|
-
</div>
|
|
202
|
-
{/if}
|
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* HunkDiffView — side-by-side line-level diff with inline word highlights.
|
|
4
|
-
*
|
|
5
|
-
* Renders the before/after of a single DiffHunk with the visual treatment of
|
|
6
|
-
* a real code-review tool:
|
|
7
|
-
* - Line numbers in gutter
|
|
8
|
-
* - "−" / "+" markers per line
|
|
9
|
-
* - Red background + strikethrough for removed lines
|
|
10
|
-
* - Green background for added lines
|
|
11
|
-
* - Inline word-level highlights when a line was modified (Myers diff)
|
|
12
|
-
* - Monospace, syntax-naive (YAML is plain enough)
|
|
13
|
-
*
|
|
14
|
-
* Pure presentation — no state, no side effects. Pairs with DiffReview's
|
|
15
|
-
* per-hunk Accept/Reject buttons.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { diffLines, diffWordsWithSpace, type Change } from 'diff'
|
|
19
|
-
|
|
20
|
-
interface Props {
|
|
21
|
-
oldText: string | null
|
|
22
|
-
newText: string | null
|
|
23
|
-
/** When false (default), shows two columns. When true, unified single column. */
|
|
24
|
-
unified?: boolean
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let { oldText, newText, unified = false }: Props = $props()
|
|
28
|
-
|
|
29
|
-
type Row = {
|
|
30
|
-
kind: 'context' | 'removed' | 'added' | 'modified'
|
|
31
|
-
oldLineNo: number | null
|
|
32
|
-
newLineNo: number | null
|
|
33
|
-
oldContent: string | null
|
|
34
|
-
newContent: string | null
|
|
35
|
-
/** When kind=='modified', word-level segments per side */
|
|
36
|
-
oldSegments?: Array<{ text: string; changed: boolean }>
|
|
37
|
-
newSegments?: Array<{ text: string; changed: boolean }>
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Build paired rows for side-by-side rendering.
|
|
42
|
-
* Adjacent removed+added blocks of equal-ish line count are zipped into
|
|
43
|
-
* 'modified' rows so the eye can compare line-for-line. Otherwise they
|
|
44
|
-
* render as separate red/green rows (with blank counterpart on the other
|
|
45
|
-
* side).
|
|
46
|
-
*/
|
|
47
|
-
function buildRows(oldT: string, newT: string): Row[] {
|
|
48
|
-
const changes = diffLines(oldT, newT)
|
|
49
|
-
const rows: Row[] = []
|
|
50
|
-
let oldLine = 1
|
|
51
|
-
let newLine = 1
|
|
52
|
-
|
|
53
|
-
for (let i = 0; i < changes.length; i++) {
|
|
54
|
-
const c = changes[i]
|
|
55
|
-
const lines = c.value.replace(/\n$/, '').split('\n')
|
|
56
|
-
|
|
57
|
-
if (!c.added && !c.removed) {
|
|
58
|
-
// context
|
|
59
|
-
for (const line of lines) {
|
|
60
|
-
rows.push({
|
|
61
|
-
kind: 'context',
|
|
62
|
-
oldLineNo: oldLine++,
|
|
63
|
-
newLineNo: newLine++,
|
|
64
|
-
oldContent: line,
|
|
65
|
-
newContent: line,
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
} else if (c.removed) {
|
|
69
|
-
// Lookahead: is the very next change an 'added' block? If so, pair them
|
|
70
|
-
const next = changes[i + 1]
|
|
71
|
-
if (next?.added) {
|
|
72
|
-
const remLines = lines
|
|
73
|
-
const addLines = next.value.replace(/\n$/, '').split('\n')
|
|
74
|
-
const pairs = Math.max(remLines.length, addLines.length)
|
|
75
|
-
for (let j = 0; j < pairs; j++) {
|
|
76
|
-
const r = remLines[j]
|
|
77
|
-
const a = addLines[j]
|
|
78
|
-
if (r !== undefined && a !== undefined) {
|
|
79
|
-
// both present — modified row with inline word diff
|
|
80
|
-
const wordChanges = diffWordsWithSpace(r, a)
|
|
81
|
-
const oldSegments: Array<{ text: string; changed: boolean }> = []
|
|
82
|
-
const newSegments: Array<{ text: string; changed: boolean }> = []
|
|
83
|
-
for (const w of wordChanges) {
|
|
84
|
-
if (w.added) newSegments.push({ text: w.value, changed: true })
|
|
85
|
-
else if (w.removed) oldSegments.push({ text: w.value, changed: true })
|
|
86
|
-
else {
|
|
87
|
-
oldSegments.push({ text: w.value, changed: false })
|
|
88
|
-
newSegments.push({ text: w.value, changed: false })
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
rows.push({
|
|
92
|
-
kind: 'modified',
|
|
93
|
-
oldLineNo: oldLine++,
|
|
94
|
-
newLineNo: newLine++,
|
|
95
|
-
oldContent: r,
|
|
96
|
-
newContent: a,
|
|
97
|
-
oldSegments,
|
|
98
|
-
newSegments,
|
|
99
|
-
})
|
|
100
|
-
} else if (r !== undefined) {
|
|
101
|
-
rows.push({
|
|
102
|
-
kind: 'removed',
|
|
103
|
-
oldLineNo: oldLine++,
|
|
104
|
-
newLineNo: null,
|
|
105
|
-
oldContent: r,
|
|
106
|
-
newContent: null,
|
|
107
|
-
})
|
|
108
|
-
} else if (a !== undefined) {
|
|
109
|
-
rows.push({
|
|
110
|
-
kind: 'added',
|
|
111
|
-
oldLineNo: null,
|
|
112
|
-
newLineNo: newLine++,
|
|
113
|
-
oldContent: null,
|
|
114
|
-
newContent: a,
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
i++ // consumed the next 'added' change
|
|
119
|
-
} else {
|
|
120
|
-
for (const line of lines) {
|
|
121
|
-
rows.push({
|
|
122
|
-
kind: 'removed',
|
|
123
|
-
oldLineNo: oldLine++,
|
|
124
|
-
newLineNo: null,
|
|
125
|
-
oldContent: line,
|
|
126
|
-
newContent: null,
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
} else if (c.added) {
|
|
131
|
-
// standalone added (not preceded by a removed block — already handled above)
|
|
132
|
-
for (const line of lines) {
|
|
133
|
-
rows.push({
|
|
134
|
-
kind: 'added',
|
|
135
|
-
oldLineNo: null,
|
|
136
|
-
newLineNo: newLine++,
|
|
137
|
-
oldContent: null,
|
|
138
|
-
newContent: line,
|
|
139
|
-
})
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return rows
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const rows = $derived.by<Row[]>(() => {
|
|
148
|
-
const oldT = oldText ?? ''
|
|
149
|
-
const newT = newText ?? ''
|
|
150
|
-
if (!oldT && !newT) return []
|
|
151
|
-
if (!oldT) {
|
|
152
|
-
// pure-add — every line is 'added'
|
|
153
|
-
return newT.replace(/\n$/, '').split('\n').map((line, i) => ({
|
|
154
|
-
kind: 'added' as const,
|
|
155
|
-
oldLineNo: null,
|
|
156
|
-
newLineNo: i + 1,
|
|
157
|
-
oldContent: null,
|
|
158
|
-
newContent: line,
|
|
159
|
-
}))
|
|
160
|
-
}
|
|
161
|
-
if (!newT) {
|
|
162
|
-
// pure-remove — every line is 'removed'
|
|
163
|
-
return oldT.replace(/\n$/, '').split('\n').map((line, i) => ({
|
|
164
|
-
kind: 'removed' as const,
|
|
165
|
-
oldLineNo: i + 1,
|
|
166
|
-
newLineNo: null,
|
|
167
|
-
oldContent: line,
|
|
168
|
-
newContent: null,
|
|
169
|
-
}))
|
|
170
|
-
}
|
|
171
|
-
return buildRows(oldT, newT)
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
function lineNoStr(n: number | null): string {
|
|
175
|
-
return n === null ? ' ' : String(n).padStart(3, ' ')
|
|
176
|
-
}
|
|
177
|
-
</script>
|
|
178
|
-
|
|
179
|
-
<div class="hunk-diff text-xs font-mono leading-relaxed rounded border border-base-300/60 overflow-hidden bg-base-200/30">
|
|
180
|
-
{#if rows.length === 0}
|
|
181
|
-
<div class="px-3 py-4 text-center text-base-content/40 italic">no content</div>
|
|
182
|
-
{:else if unified}
|
|
183
|
-
<!-- Unified column -->
|
|
184
|
-
<div class="flex flex-col">
|
|
185
|
-
{#each rows as row, i (i)}
|
|
186
|
-
{#if row.kind === 'context'}
|
|
187
|
-
<div class="diff-row diff-row-context">
|
|
188
|
-
<span class="diff-gutter">{lineNoStr(row.oldLineNo)}</span>
|
|
189
|
-
<span class="diff-gutter">{lineNoStr(row.newLineNo)}</span>
|
|
190
|
-
<span class="diff-marker"> </span>
|
|
191
|
-
<span class="diff-content">{row.oldContent}</span>
|
|
192
|
-
</div>
|
|
193
|
-
{:else if row.kind === 'removed'}
|
|
194
|
-
<div class="diff-row diff-row-removed">
|
|
195
|
-
<span class="diff-gutter">{lineNoStr(row.oldLineNo)}</span>
|
|
196
|
-
<span class="diff-gutter"> </span>
|
|
197
|
-
<span class="diff-marker">−</span>
|
|
198
|
-
<span class="diff-content line-through opacity-80">{row.oldContent}</span>
|
|
199
|
-
</div>
|
|
200
|
-
{:else if row.kind === 'added'}
|
|
201
|
-
<div class="diff-row diff-row-added">
|
|
202
|
-
<span class="diff-gutter"> </span>
|
|
203
|
-
<span class="diff-gutter">{lineNoStr(row.newLineNo)}</span>
|
|
204
|
-
<span class="diff-marker">+</span>
|
|
205
|
-
<span class="diff-content">{row.newContent}</span>
|
|
206
|
-
</div>
|
|
207
|
-
{:else if row.kind === 'modified'}
|
|
208
|
-
<div class="diff-row diff-row-removed">
|
|
209
|
-
<span class="diff-gutter">{lineNoStr(row.oldLineNo)}</span>
|
|
210
|
-
<span class="diff-gutter"> </span>
|
|
211
|
-
<span class="diff-marker">−</span>
|
|
212
|
-
<span class="diff-content">
|
|
213
|
-
{#each row.oldSegments ?? [] as seg, j (j)}{#if seg.changed}<span class="word-removed">{seg.text}</span>{:else}<span>{seg.text}</span>{/if}{/each}
|
|
214
|
-
</span>
|
|
215
|
-
</div>
|
|
216
|
-
<div class="diff-row diff-row-added">
|
|
217
|
-
<span class="diff-gutter"> </span>
|
|
218
|
-
<span class="diff-gutter">{lineNoStr(row.newLineNo)}</span>
|
|
219
|
-
<span class="diff-marker">+</span>
|
|
220
|
-
<span class="diff-content">
|
|
221
|
-
{#each row.newSegments ?? [] as seg, j (j)}{#if seg.changed}<span class="word-added">{seg.text}</span>{:else}<span>{seg.text}</span>{/if}{/each}
|
|
222
|
-
</span>
|
|
223
|
-
</div>
|
|
224
|
-
{/if}
|
|
225
|
-
{/each}
|
|
226
|
-
</div>
|
|
227
|
-
{:else}
|
|
228
|
-
<!-- Side-by-side: two synced columns -->
|
|
229
|
-
<div class="grid grid-cols-2 divide-x divide-base-300/60">
|
|
230
|
-
<!-- LEFT (current/old) -->
|
|
231
|
-
<div class="flex flex-col">
|
|
232
|
-
{#each rows as row, i (i)}
|
|
233
|
-
{#if row.kind === 'context'}
|
|
234
|
-
<div class="diff-row diff-row-context">
|
|
235
|
-
<span class="diff-gutter">{lineNoStr(row.oldLineNo)}</span>
|
|
236
|
-
<span class="diff-marker"> </span>
|
|
237
|
-
<span class="diff-content">{row.oldContent}</span>
|
|
238
|
-
</div>
|
|
239
|
-
{:else if row.kind === 'removed'}
|
|
240
|
-
<div class="diff-row diff-row-removed">
|
|
241
|
-
<span class="diff-gutter">{lineNoStr(row.oldLineNo)}</span>
|
|
242
|
-
<span class="diff-marker">−</span>
|
|
243
|
-
<span class="diff-content line-through opacity-80">{row.oldContent}</span>
|
|
244
|
-
</div>
|
|
245
|
-
{:else if row.kind === 'added'}
|
|
246
|
-
<div class="diff-row diff-row-empty">
|
|
247
|
-
<span class="diff-gutter"> </span>
|
|
248
|
-
<span class="diff-marker"> </span>
|
|
249
|
-
<span class="diff-content"> </span>
|
|
250
|
-
</div>
|
|
251
|
-
{:else if row.kind === 'modified'}
|
|
252
|
-
<div class="diff-row diff-row-removed">
|
|
253
|
-
<span class="diff-gutter">{lineNoStr(row.oldLineNo)}</span>
|
|
254
|
-
<span class="diff-marker">−</span>
|
|
255
|
-
<span class="diff-content">
|
|
256
|
-
{#each row.oldSegments ?? [] as seg, j (j)}{#if seg.changed}<span class="word-removed">{seg.text}</span>{:else}<span>{seg.text}</span>{/if}{/each}
|
|
257
|
-
</span>
|
|
258
|
-
</div>
|
|
259
|
-
{/if}
|
|
260
|
-
{/each}
|
|
261
|
-
</div>
|
|
262
|
-
|
|
263
|
-
<!-- RIGHT (proposed/new) -->
|
|
264
|
-
<div class="flex flex-col">
|
|
265
|
-
{#each rows as row, i (i)}
|
|
266
|
-
{#if row.kind === 'context'}
|
|
267
|
-
<div class="diff-row diff-row-context">
|
|
268
|
-
<span class="diff-gutter">{lineNoStr(row.newLineNo)}</span>
|
|
269
|
-
<span class="diff-marker"> </span>
|
|
270
|
-
<span class="diff-content">{row.newContent}</span>
|
|
271
|
-
</div>
|
|
272
|
-
{:else if row.kind === 'added'}
|
|
273
|
-
<div class="diff-row diff-row-added">
|
|
274
|
-
<span class="diff-gutter">{lineNoStr(row.newLineNo)}</span>
|
|
275
|
-
<span class="diff-marker">+</span>
|
|
276
|
-
<span class="diff-content">{row.newContent}</span>
|
|
277
|
-
</div>
|
|
278
|
-
{:else if row.kind === 'removed'}
|
|
279
|
-
<div class="diff-row diff-row-empty">
|
|
280
|
-
<span class="diff-gutter"> </span>
|
|
281
|
-
<span class="diff-marker"> </span>
|
|
282
|
-
<span class="diff-content"> </span>
|
|
283
|
-
</div>
|
|
284
|
-
{:else if row.kind === 'modified'}
|
|
285
|
-
<div class="diff-row diff-row-added">
|
|
286
|
-
<span class="diff-gutter">{lineNoStr(row.newLineNo)}</span>
|
|
287
|
-
<span class="diff-marker">+</span>
|
|
288
|
-
<span class="diff-content">
|
|
289
|
-
{#each row.newSegments ?? [] as seg, j (j)}{#if seg.changed}<span class="word-added">{seg.text}</span>{:else}<span>{seg.text}</span>{/if}{/each}
|
|
290
|
-
</span>
|
|
291
|
-
</div>
|
|
292
|
-
{/if}
|
|
293
|
-
{/each}
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
{/if}
|
|
297
|
-
</div>
|
|
298
|
-
|
|
299
|
-
<style>
|
|
300
|
-
.diff-row {
|
|
301
|
-
display: grid;
|
|
302
|
-
grid-template-columns: auto auto 1fr;
|
|
303
|
-
column-gap: 0.5rem;
|
|
304
|
-
padding: 0 0.5rem;
|
|
305
|
-
align-items: baseline;
|
|
306
|
-
min-height: 1.4em;
|
|
307
|
-
white-space: pre-wrap;
|
|
308
|
-
word-break: break-word;
|
|
309
|
-
}
|
|
310
|
-
.diff-gutter {
|
|
311
|
-
color: color-mix(in oklab, currentColor 35%, transparent);
|
|
312
|
-
font-variant-numeric: tabular-nums;
|
|
313
|
-
user-select: none;
|
|
314
|
-
text-align: right;
|
|
315
|
-
}
|
|
316
|
-
.diff-marker {
|
|
317
|
-
color: color-mix(in oklab, currentColor 55%, transparent);
|
|
318
|
-
width: 0.75em;
|
|
319
|
-
user-select: none;
|
|
320
|
-
}
|
|
321
|
-
.diff-row-removed {
|
|
322
|
-
background-color: color-mix(in oklab, var(--color-error, oklch(0.68 0.18 28)) 12%, transparent);
|
|
323
|
-
}
|
|
324
|
-
.diff-row-removed .diff-marker {
|
|
325
|
-
color: var(--color-error, oklch(0.68 0.18 28));
|
|
326
|
-
}
|
|
327
|
-
.diff-row-added {
|
|
328
|
-
background-color: color-mix(in oklab, var(--color-success, oklch(0.7 0.17 145)) 12%, transparent);
|
|
329
|
-
}
|
|
330
|
-
.diff-row-added .diff-marker {
|
|
331
|
-
color: var(--color-success, oklch(0.7 0.17 145));
|
|
332
|
-
}
|
|
333
|
-
.diff-row-empty {
|
|
334
|
-
background-color: color-mix(in oklab, currentColor 3%, transparent);
|
|
335
|
-
}
|
|
336
|
-
.diff-row-context {
|
|
337
|
-
background-color: transparent;
|
|
338
|
-
}
|
|
339
|
-
.word-removed {
|
|
340
|
-
background-color: color-mix(in oklab, var(--color-error, oklch(0.68 0.18 28)) 28%, transparent);
|
|
341
|
-
text-decoration: line-through;
|
|
342
|
-
text-decoration-color: color-mix(in oklab, var(--color-error, oklch(0.68 0.18 28)) 65%, transparent);
|
|
343
|
-
}
|
|
344
|
-
.word-added {
|
|
345
|
-
background-color: color-mix(in oklab, var(--color-success, oklch(0.7 0.17 145)) 28%, transparent);
|
|
346
|
-
font-weight: 600;
|
|
347
|
-
}
|
|
348
|
-
</style>
|