Add embedded PTY terminal, keyboard shortcuts, and assignee/label integrations #53
+62
-1
@@ -5940,6 +5940,67 @@
|
||||
|
||||
// ── Event handlers ─────────────────────────────────────────────────────────
|
||||
|
||||
// ── Main view shortcuts ────────────────────────────────────────────────────
|
||||
// Ctrl/Cmd + 1…4 jump to the four main views in the order of the tab bar.
|
||||
const mainViewShortcuts: AppView[] = ["management", "repository", "review-center", "issues"];
|
||||
|
||||
function mainViewShortcutIndex(event: KeyboardEvent): number {
|
||||
if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) return 0;
|
||||
const code = event.code ?? "";
|
||||
const digit = code.startsWith("Digit")
|
||||
? Number(code.slice(5))
|
||||
: code.startsWith("Numpad")
|
||||
? Number(code.slice(6))
|
||||
: Number(event.key);
|
||||
if (!Number.isInteger(digit) || digit < 1 || digit > mainViewShortcuts.length) return 0;
|
||||
return digit;
|
||||
}
|
||||
|
||||
// Capture phase: dialogs and editors trap keys on their own nodes, and the
|
||||
// view shortcut must not depend on the event reaching window afterwards.
|
||||
function handleViewShortcutKeydown(event: KeyboardEvent) {
|
||||
const index = mainViewShortcutIndex(event);
|
||||
if (index === 0) return;
|
||||
if (import.meta.env.DEV) {
|
||||
console.debug("[gitty] view shortcut", { key: event.key, code: event.code, index, blocked: mainViewSwitchBlocked(), activeView });
|
||||
}
|
||||
event.preventDefault();
|
||||
if (!event.repeat) switchMainView(index);
|
||||
}
|
||||
|
||||
// Anything modal owns the keyboard, so the view stays where it is.
|
||||
function mainViewSwitchBlocked(): boolean {
|
||||
return isBusy
|
||||
|| commandPaletteOpen || helpOpen || globalSearchOpen || appSettingsOpen
|
||||
|| cloneDialogOpen || initRepositoryDialogOpen || credDialogOpen
|
||||
|| compareDialogOpen || compareSelectOpen || fileHistoryDialogOpen
|
||||
|| resolveDialogOpen || interactiveRebaseOpen || reflogOpen || bisectOpen
|
||||
|| worktreeDialogOpen || submoduleDialogOpen || gitLfsDialogOpen
|
||||
|| linePatchOpen || aiReviewOpen
|
||||
|| pendingSubmoduleInitialization !== null || pendingDiscard !== null
|
||||
|| newBranchCommit !== null || renameBranchTarget !== null
|
||||
|| deleteBranchTarget !== null || commitNoteTarget !== null;
|
||||
}
|
||||
|
||||
function switchMainView(index: number) {
|
||||
if (mainViewSwitchBlocked()) return;
|
||||
const view = mainViewShortcuts[index - 1];
|
||||
if (!view || view === activeView) return;
|
||||
closeRepoTabContextMenu();
|
||||
if (view === "management") {
|
||||
openRepoManagement();
|
||||
} else if (view === "review-center") {
|
||||
openReviewCenter();
|
||||
} else if (view === "issues") {
|
||||
activeView = "issues";
|
||||
} else {
|
||||
// Without an open repository the tab bar would ask for a folder; a
|
||||
// keyboard shortcut should never pop up a native file dialog.
|
||||
const path = repoTabs.find((tab) => sameRepoPath(tab.path, activeRepoPath))?.path ?? repoTabs[0]?.path;
|
||||
if (path) void selectRepoTab(path);
|
||||
}
|
||||
}
|
||||
|
||||
function handleWindowKeydown(event: KeyboardEvent) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
|
||||
event.preventDefault();
|
||||
@@ -5992,7 +6053,7 @@
|
||||
<title>Gitty</title>
|
||||
</svelte:head>
|
||||
|
||||
<svelte:window on:click={handleWindowClick} on:keydown={handleWindowKeydown} on:contextmenu|capture={handleWindowContextMenu} />
|
||||
<svelte:window on:click={handleWindowClick} on:keydown|capture={handleViewShortcutKeydown} on:keydown={handleWindowKeydown} on:contextmenu|capture={handleWindowContextMenu} />
|
||||
|
||||
{#if sidebarSectionMenu && activeView === "repository"}
|
||||
<SidebarSectionMenu x={sidebarSectionMenu.x} y={sidebarSectionMenu.y} language={appLanguage}
|
||||
|
||||
@@ -89,10 +89,10 @@
|
||||
|
||||
<header class="workspace-navigation">
|
||||
<nav class="global-navigation" aria-label={language === "de" ? "Hauptnavigation" : "Main navigation"}>
|
||||
<button type="button" class:active={activeView === "management"} aria-current={activeView === "management" ? "page" : undefined} disabled={isBusy} onclick={onOpenManagement}><House size={17}/><span>Dashboard</span></button>
|
||||
<button type="button" class:active={activeView === "repository"} aria-current={activeView === "repository" ? "page" : undefined} disabled={isBusy} onclick={onOpenRepositories}><Database size={17}/><span>Repositories</span></button>
|
||||
<button type="button" class:active={activeView === "review-center"} aria-current={activeView === "review-center" ? "page" : undefined} disabled={isBusy} onclick={onOpenReviewCenter}><GitPullRequest size={17}/><span>Pull Requests</span></button>
|
||||
<button type="button" class:active={activeView === "issues"} aria-current={activeView === "issues" ? "page" : undefined} disabled={isBusy} onclick={onOpenIssues}><Columns3 size={17}/><span>Issues & Boards</span></button>
|
||||
<button type="button" class:active={activeView === "management"} aria-current={activeView === "management" ? "page" : undefined} disabled={isBusy} onclick={onOpenManagement} title="Dashboard (Ctrl + 1)"><House size={17}/><span>Dashboard</span></button>
|
||||
<button type="button" class:active={activeView === "repository"} aria-current={activeView === "repository" ? "page" : undefined} disabled={isBusy} onclick={onOpenRepositories} title="Repositories (Ctrl + 2)"><Database size={17}/><span>Repositories</span></button>
|
||||
<button type="button" class:active={activeView === "review-center"} aria-current={activeView === "review-center" ? "page" : undefined} disabled={isBusy} onclick={onOpenReviewCenter} title="Pull Requests (Ctrl + 3)"><GitPullRequest size={17}/><span>Pull Requests</span></button>
|
||||
<button type="button" class:active={activeView === "issues"} aria-current={activeView === "issues" ? "page" : undefined} disabled={isBusy} onclick={onOpenIssues} title="Issues & Boards (Ctrl + 4)"><Columns3 size={17}/><span>Issues & Boards</span></button>
|
||||
</nav>
|
||||
{#if activeView === "repository"}
|
||||
<div class="repository-row">
|
||||
|
||||
@@ -249,6 +249,7 @@
|
||||
summary: "Die Hilfe ist überall erreichbar. Dialoge lassen sich konsistent schließen und Suchfelder direkt fokussieren.",
|
||||
commands: [
|
||||
{ command: "Ctrl + /", description: "Diese Hilfe öffnen" },
|
||||
{ command: "Ctrl + 1 … 4", description: "Zwischen Dashboard, Repositories, Pull Requests und Issues & Boards wechseln" },
|
||||
{ command: "Ctrl + A", description: "Alle Dateien in der aktiven Statusliste („Ungestaged“ oder „Gestaged“) auswählen" },
|
||||
{ command: "Escape", description: "Aktuelles Overlay oder Dialogfenster schließen – in der Statusliste die aktuelle Auswahl aufheben" },
|
||||
{ command: "Tab / Shift + Tab", description: "Zwischen Bedienelementen wechseln" },
|
||||
@@ -460,6 +461,7 @@
|
||||
summary: "Help is available everywhere. Dialogs close consistently and search fields receive focus automatically.",
|
||||
commands: [
|
||||
{ command: "Ctrl + /", description: "Open this help center" },
|
||||
{ command: "Ctrl + 1 … 4", description: "Switch between Dashboard, Repositories, Pull Requests and Issues & Boards" },
|
||||
{ command: "Ctrl + A", description: "Select every file in the focused status list (Unstaged or Staged)" },
|
||||
{ command: "Escape", description: "Close the current overlay or dialog – in the status list, clear the current selection" },
|
||||
{ command: "Tab / Shift + Tab", description: "Move between controls" },
|
||||
|
||||
Reference in New Issue
Block a user