From a15982bc37f904a2e0de93bdc5b02d126cae9598 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Sun, 15 Feb 2026 13:00:10 +0100 Subject: [PATCH] Add commit and push functionality with dialog Sidebar shows "Commit & Push" button when uncommitted changes exist. Clicking opens a commit message dialog. After committing, auto-pushes and shows success/error toast. Modified files count refreshes after commit. Co-Authored-By: Claude Opus 4.6 --- src/App.tsx | 37 +++++++++- src/components/CommitDialog.css | 119 ++++++++++++++++++++++++++++++++ src/components/CommitDialog.tsx | 74 ++++++++++++++++++++ src/components/Sidebar.css | 33 +++++++++ src/components/Sidebar.tsx | 11 ++- 5 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 src/components/CommitDialog.css create mode 100644 src/components/CommitDialog.tsx diff --git a/src/App.tsx b/src/App.tsx index 1ba2b3c9..d0684d98 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { ResizeHandle } from './components/ResizeHandle' import { CreateNoteDialog, type NoteType } from './components/CreateNoteDialog' import { QuickOpenPalette } from './components/QuickOpenPalette' import { Toast } from './components/Toast' +import { CommitDialog } from './components/CommitDialog' import { isTauri, mockInvoke, addMockEntry, updateMockContent } from './mock-tauri' import type { VaultEntry, SidebarSelection, GitCommit, ModifiedFile } from './types' import './App.css' @@ -140,6 +141,7 @@ function App() { const [modifiedFiles, setModifiedFiles] = useState([]) const [showCreateDialog, setShowCreateDialog] = useState(false) const [showQuickOpen, setShowQuickOpen] = useState(false) + const [showCommitDialog, setShowCommitDialog] = useState(false) const [toastMessage, setToastMessage] = useState(null) // Refs for keyboard shortcuts (to avoid stale closures) @@ -500,10 +502,37 @@ function App() { return modifiedFiles.some((f) => f.path === path) }, [modifiedFiles]) + const handleCommitPush = useCallback(async (message: string) => { + setShowCommitDialog(false) + try { + const vaultPath = TEST_VAULT_PATH.replace('~', '/Users/luca') + if (isTauri()) { + await invoke('git_commit', { vaultPath, message }) + setToastMessage('Changes committed') + try { + await invoke('git_push', { vaultPath }) + setToastMessage('Committed and pushed') + } catch (pushErr) { + console.warn('Push failed:', pushErr) + setToastMessage('Committed (push failed)') + } + } else { + await mockInvoke('git_commit', { message }) + await mockInvoke('git_push', {}) + setToastMessage('Committed and pushed') + } + // Refresh modified files + loadModifiedFiles() + } catch (err) { + console.error('Commit failed:', err) + setToastMessage(`Commit failed: ${err}`) + } + }, [loadModifiedFiles]) + return (
- + setShowCommitDialog(true)} />
@@ -552,6 +581,12 @@ function App() { onClose={() => setShowCreateDialog(false)} onCreate={handleCreateNote} /> + setShowCommitDialog(false)} + />
) } diff --git a/src/components/CommitDialog.css b/src/components/CommitDialog.css new file mode 100644 index 00000000..577926b5 --- /dev/null +++ b/src/components/CommitDialog.css @@ -0,0 +1,119 @@ +.commit-dialog__overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +.commit-dialog { + background: var(--bg-card); + border: 1px solid var(--border-primary); + border-radius: 12px; + width: 420px; + padding: 20px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); +} + +.commit-dialog__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 14px; +} + +.commit-dialog__header h3 { + margin: 0; + font-size: 15px; + font-weight: 600; + color: var(--text-heading); +} + +.commit-dialog__count { + font-size: 12px; + color: var(--text-tertiary); + background: var(--bg-button); + padding: 2px 8px; + border-radius: 10px; +} + +.commit-dialog__input { + width: 100%; + padding: 10px 12px; + background: var(--bg-input); + border: 1px solid var(--border-input); + border-radius: 8px; + color: var(--text-primary); + font-size: 13px; + font-family: inherit; + resize: vertical; + outline: none; + box-sizing: border-box; + transition: border-color 0.15s; +} + +.commit-dialog__input::placeholder { + color: var(--text-faint); +} + +.commit-dialog__input:focus { + border-color: var(--accent-blue); +} + +.commit-dialog__footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 14px; +} + +.commit-dialog__hint { + font-size: 11px; + color: var(--text-faint); +} + +.commit-dialog__actions { + display: flex; + gap: 8px; +} + +.commit-dialog__cancel { + padding: 6px 14px; + font-size: 13px; + border: 1px solid var(--border-primary); + border-radius: 6px; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.15s; +} + +.commit-dialog__cancel:hover { + background: var(--bg-button); +} + +.commit-dialog__submit { + padding: 6px 14px; + font-size: 13px; + border: none; + border-radius: 6px; + background: var(--accent-blue, #2196f3); + color: #fff; + cursor: pointer; + font-weight: 500; + transition: all 0.15s; +} + +.commit-dialog__submit:hover { + opacity: 0.9; +} + +.commit-dialog__submit:disabled { + opacity: 0.4; + cursor: default; +} diff --git a/src/components/CommitDialog.tsx b/src/components/CommitDialog.tsx new file mode 100644 index 00000000..8bf2a69e --- /dev/null +++ b/src/components/CommitDialog.tsx @@ -0,0 +1,74 @@ +import { useState, useEffect, useRef } from 'react' +import './CommitDialog.css' + +interface CommitDialogProps { + open: boolean + modifiedCount: number + onCommit: (message: string) => void + onClose: () => void +} + +export function CommitDialog({ open, modifiedCount, onCommit, onClose }: CommitDialogProps) { + const [message, setMessage] = useState('') + const inputRef = useRef(null) + + useEffect(() => { + if (open) { + setMessage('') + // Focus with a small delay to ensure the dialog is rendered + setTimeout(() => inputRef.current?.focus(), 50) + } + }, [open]) + + if (!open) return null + + const handleSubmit = () => { + const trimmed = message.trim() + if (!trimmed) return + onCommit(trimmed) + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + handleSubmit() + } else if (e.key === 'Escape') { + onClose() + } + } + + return ( +
+
e.stopPropagation()}> +
+

Commit & Push

+ {modifiedCount} file{modifiedCount !== 1 ? 's' : ''} changed +
+