import { Change, diffLines } from 'diff' import { Platform, getIcon } from 'obsidian' import { useEffect, useState } from 'react' import ContentEditable from 'react-contenteditable' import { ApplyViewState } from '../../ApplyView' import { useApp } from '../../contexts/AppContext' export default function ApplyViewRoot({ state, close, }: { state: ApplyViewState close: () => void }) { const acceptIcon = getIcon('check') const rejectIcon = getIcon('x') const excludeIcon = getIcon('x') const getShortcutText = (shortcut: 'accept' | 'reject') => { const isMac = Platform.isMacOS if (shortcut === 'accept') { return isMac ? '(⌘⏎)' : '(Ctrl+⏎)' } return isMac ? '(⌘⌫)' : '(Ctrl+⌫)' } const app = useApp() // Track which lines have been accepted or excluded const [diffStatus, setDiffStatus] = useState>([]) const [diff] = useState(() => { const initialDiff = diffLines(state.oldContent, state.newContent) // Initialize all lines as 'active' setDiffStatus(initialDiff.map(() => 'active')) return initialDiff }) // Store edited content for each diff part const [editedContents, setEditedContents] = useState( diff.map(part => part.value) ) const handleAccept = async () => { // Filter and process content based on diffStatus const newContent = diff.reduce((result, change, index) => { // Keep unchanged content, non-excluded additions, or excluded removals if ((!change.added && !change.removed) || (change.added && diffStatus[index] !== 'excluded') || (change.removed && diffStatus[index] === 'excluded')) { return result + editedContents[index]; } return result; }, '') const file = app.vault.getFileByPath(state.file) if (!file) { throw new Error('File not found') } await app.vault.modify(file, newContent) if (state.onClose) { state.onClose(true) } close() } const handleReject = async () => { if (state.onClose) { state.onClose(false) } close() } const excludeDiffLine = (index: number) => { setDiffStatus(prevStatus => { const newStatus = [...prevStatus] // Mark line as excluded newStatus[index] = 'excluded' return newStatus }) } const acceptDiffLine = (index: number) => { setDiffStatus(prevStatus => { const newStatus = [...prevStatus] // Mark line as accepted newStatus[index] = 'accepted' return newStatus }) } const handleKeyDown = (event: KeyboardEvent) => { const modifierKey = Platform.isMacOS ? event.metaKey : event.ctrlKey; if (modifierKey) { if (event.key === 'Enter') { event.preventDefault(); event.stopPropagation(); handleAccept(); } else if (event.key === 'Backspace') { event.preventDefault(); event.stopPropagation(); handleReject(); } } } // Handle content editing changes const handleContentChange = (index: number, evt: { target: { value: string } }) => { const newEditedContents = [...editedContents]; newEditedContents[index] = evt.target.value; setEditedContents(newEditedContents); } // Add event listeners on mount and remove on unmount useEffect(() => { const handler = (e: KeyboardEvent) => handleKeyDown(e); window.addEventListener('keydown', handler, true); return () => { window.removeEventListener('keydown', handler, true); } }, [handleAccept, handleReject]) // Dependencies for the effect return (
Applying: {state?.file ?? ''}
{state?.file ? state.file.replace(/\.[^/.]+$/, '') : ''}
{diff.map((part, index) => { // Determine line display status based on diffStatus const status = diffStatus[index] const isHidden = (part.added && status === 'excluded') || (part.removed && status === 'accepted') if (isHidden) return null return (
handleContentChange(index, evt)} className="infio-editable-content" /> {(part.added || part.removed) && status === 'active' && (
)}
) })}
) }