feat(web): enhanced file panel — side-by-side diff, hide whitespace, inline review
Adds DiffSplitView component for side-by-side diff mode, whitespace-only change filtering, inline review comments with thread/gutter cell UI, diff preferences persistence, and write-file API support for in-browser editing. Backend: hideWhitespace param on git diff endpoint, write_file route.
This commit is contained in:
92
apps/web/src/stores/useDiffCommentStore.ts
Normal file
92
apps/web/src/stores/useDiffCommentStore.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
export interface DiffComment {
|
||||
id: string;
|
||||
body: string;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface DiffCommentTarget {
|
||||
filePath: string;
|
||||
side: 'old' | 'new';
|
||||
lineNumber: number;
|
||||
}
|
||||
|
||||
function loadFromStorage(key: string): Map<string, DiffComment[]> {
|
||||
try {
|
||||
const raw = localStorage.getItem(key);
|
||||
if (!raw) return new Map();
|
||||
const parsed = JSON.parse(raw);
|
||||
return new Map(Object.entries(parsed));
|
||||
} catch {
|
||||
return new Map();
|
||||
}
|
||||
}
|
||||
|
||||
function saveToStorage(key: string, map: Map<string, DiffComment[]>) {
|
||||
const obj: Record<string, DiffComment[]> = {};
|
||||
for (const [k, v] of map) obj[k] = v;
|
||||
localStorage.setItem(key, JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export function useDiffComments(sessionId: string, mode: string) {
|
||||
const storageKey = `boocode.diff.comments.${sessionId}.${mode}`;
|
||||
const [comments, setComments] = useState<Map<string, DiffComment[]>>(() =>
|
||||
loadFromStorage(storageKey)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
saveToStorage(storageKey, comments);
|
||||
}, [storageKey, comments]);
|
||||
|
||||
const addComment = useCallback(
|
||||
(key: string, comment: DiffComment) => {
|
||||
setComments((prev) => {
|
||||
const next = new Map(prev);
|
||||
const list = next.get(key) ?? [];
|
||||
next.set(key, [...list, comment]);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const updateComment = useCallback(
|
||||
(key: string, id: string, body: string) => {
|
||||
setComments((prev) => {
|
||||
const next = new Map(prev);
|
||||
const list = next.get(key);
|
||||
if (!list) return prev;
|
||||
next.set(
|
||||
key,
|
||||
list.map((c) =>
|
||||
c.id === id ? { ...c, body, updatedAt: Date.now() } : c
|
||||
)
|
||||
);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const deleteComment = useCallback(
|
||||
(key: string, id: string) => {
|
||||
setComments((prev) => {
|
||||
const next = new Map(prev);
|
||||
const list = next.get(key);
|
||||
if (!list) return prev;
|
||||
const filtered = list.filter((c) => c.id !== id);
|
||||
if (filtered.length === 0) {
|
||||
next.delete(key);
|
||||
} else {
|
||||
next.set(key, filtered);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return { comments, addComment, updateComment, deleteComment };
|
||||
}
|
||||
Reference in New Issue
Block a user