Open code test #26
+70
-2
@@ -17,6 +17,7 @@
|
|||||||
import BranchDeleteConfirmDialog from "./lib/components/BranchDeleteConfirmDialog.svelte";
|
import BranchDeleteConfirmDialog from "./lib/components/BranchDeleteConfirmDialog.svelte";
|
||||||
import BranchPanel from "./lib/components/BranchPanel.svelte";
|
import BranchPanel from "./lib/components/BranchPanel.svelte";
|
||||||
import CloneRepositoryDialog from "./lib/components/CloneRepositoryDialog.svelte";
|
import CloneRepositoryDialog from "./lib/components/CloneRepositoryDialog.svelte";
|
||||||
|
import CommandPalette from "./lib/components/CommandPalette.svelte";
|
||||||
import CommitPanel from "./lib/components/CommitPanel.svelte";
|
import CommitPanel from "./lib/components/CommitPanel.svelte";
|
||||||
import CompareSelectDialog from "./lib/components/CompareSelectDialog.svelte";
|
import CompareSelectDialog from "./lib/components/CompareSelectDialog.svelte";
|
||||||
import CredentialDialog from "./lib/components/CredentialDialog.svelte";
|
import CredentialDialog from "./lib/components/CredentialDialog.svelte";
|
||||||
@@ -287,6 +288,7 @@
|
|||||||
let selectedExplorerKind: ExplorerNodeKind = "file";
|
let selectedExplorerKind: ExplorerNodeKind = "file";
|
||||||
let expandedExplorerPaths = new Set<string>();
|
let expandedExplorerPaths = new Set<string>();
|
||||||
let expandedCommitHashes = new Set<string>();
|
let expandedCommitHashes = new Set<string>();
|
||||||
|
let selectedCommitHash = "";
|
||||||
let fileHistory: GitCommit[] = [];
|
let fileHistory: GitCommit[] = [];
|
||||||
let fileHistoryLoading = false;
|
let fileHistoryLoading = false;
|
||||||
let fileHistoryError = "";
|
let fileHistoryError = "";
|
||||||
@@ -312,6 +314,7 @@
|
|||||||
let aiSettingsOpen = false;
|
let aiSettingsOpen = false;
|
||||||
let appSettingsOpen = false;
|
let appSettingsOpen = false;
|
||||||
let helpOpen = false;
|
let helpOpen = false;
|
||||||
|
let commandPaletteOpen = false;
|
||||||
let analyticsNoticeOpen = false;
|
let analyticsNoticeOpen = false;
|
||||||
let analyticsSettings: AnalyticsSettings = defaultAnalyticsSettings();
|
let analyticsSettings: AnalyticsSettings = defaultAnalyticsSettings();
|
||||||
let appTheme: AppTheme = loadThemePreference();
|
let appTheme: AppTheme = loadThemePreference();
|
||||||
@@ -3749,6 +3752,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openFileFromCommandPalette(file: GitRepositoryFile) {
|
||||||
|
if (!activeRepoPath) return;
|
||||||
|
selectedExplorerPath = file.path;
|
||||||
|
selectedExplorerKind = "file";
|
||||||
|
expandedExplorerPaths = new Set([...expandedExplorerPaths, ...explorerParentFolders(file.path)]);
|
||||||
|
try {
|
||||||
|
await openRepositoryFile(activeRepoPath, file.path);
|
||||||
|
trackEvent("explorer_file_opened", { source: "command_palette", tracked: file.tracked ? 1 : 0 });
|
||||||
|
} catch (error) {
|
||||||
|
errorMessage = errorToMessage(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectCommitFromCommandPalette(target: GitCommit) {
|
||||||
|
selectedCommitHash = target.hash;
|
||||||
|
trackEvent("commit_selected", { source: "command_palette" });
|
||||||
|
}
|
||||||
|
|
||||||
async function restoreSelectedFileFromCommit(target: GitCommit) {
|
async function restoreSelectedFileFromCommit(target: GitCommit) {
|
||||||
if (!activeRepoPath || !selectedExplorerPath) return;
|
if (!activeRepoPath || !selectedExplorerPath) return;
|
||||||
const kind = selectedExplorerKind === "folder" ? "folder" : "file";
|
const kind = selectedExplorerKind === "folder" ? "folder" : "file";
|
||||||
@@ -3781,6 +3802,14 @@
|
|||||||
trackEvent("help_opened");
|
trackEvent("help_opened");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openAppSettings() {
|
||||||
|
appSettingsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAiSettings() {
|
||||||
|
aiSettingsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
async function compareSelectedCommits() {
|
async function compareSelectedCommits() {
|
||||||
if (!canCompare) return;
|
if (!canCompare) return;
|
||||||
await runOperation("Comparing commits", async () => {
|
await runOperation("Comparing commits", async () => {
|
||||||
@@ -3970,11 +3999,20 @@
|
|||||||
// ── Event handlers ─────────────────────────────────────────────────────────
|
// ── Event handlers ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function handleWindowKeydown(event: KeyboardEvent) {
|
function handleWindowKeydown(event: KeyboardEvent) {
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!event.repeat) commandPaletteOpen = !commandPaletteOpen;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === "/") {
|
if ((event.ctrlKey || event.metaKey) && event.key === "/") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
openHelp();
|
openHelp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (event.key === "Escape" && commandPaletteOpen) {
|
||||||
|
commandPaletteOpen = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.key === "Escape" && helpOpen) {
|
if (event.key === "Escape" && helpOpen) {
|
||||||
helpOpen = false;
|
helpOpen = false;
|
||||||
return;
|
return;
|
||||||
@@ -4012,7 +4050,7 @@
|
|||||||
|
|
||||||
<main class="shell">
|
<main class="shell">
|
||||||
<TitleBar
|
<TitleBar
|
||||||
onOpenSettings={() => { appSettingsOpen = true; }}
|
onOpenSettings={openAppSettings}
|
||||||
onOpenHelp={openHelp}
|
onOpenHelp={openHelp}
|
||||||
language={appLanguage}
|
language={appLanguage}
|
||||||
/>
|
/>
|
||||||
@@ -4544,7 +4582,7 @@
|
|||||||
onGenerateCommitMessage={generateCommitMessageWithAi}
|
onGenerateCommitMessage={generateCommitMessageWithAi}
|
||||||
onReviewStaged={reviewStagedWithAi}
|
onReviewStaged={reviewStagedWithAi}
|
||||||
onSplitStaged={splitStagedWithAi}
|
onSplitStaged={splitStagedWithAi}
|
||||||
onOpenAiSettings={() => { aiSettingsOpen = true; }}
|
onOpenAiSettings={openAiSettings}
|
||||||
onToggleAmend={toggleAmendMode}
|
onToggleAmend={toggleAmendMode}
|
||||||
onUndoLastCommit={undoLastCommitChange}
|
onUndoLastCommit={undoLastCommitChange}
|
||||||
/>
|
/>
|
||||||
@@ -4574,6 +4612,7 @@
|
|||||||
<aside class="history-aside" aria-label="Commit history">
|
<aside class="history-aside" aria-label="Commit history">
|
||||||
<HistoryPanel
|
<HistoryPanel
|
||||||
{commits}
|
{commits}
|
||||||
|
{selectedCommitHash}
|
||||||
{localBranchNames}
|
{localBranchNames}
|
||||||
activeBranch={status?.current_branch ?? ""}
|
activeBranch={status?.current_branch ?? ""}
|
||||||
activeUpstream={status?.upstream ?? ""}
|
activeUpstream={status?.upstream ?? ""}
|
||||||
@@ -4590,6 +4629,7 @@
|
|||||||
onCreateBranchFromCommit={openNewBranchDialog}
|
onCreateBranchFromCommit={openNewBranchDialog}
|
||||||
onCherryPickCommit={cherryPickFromCommit}
|
onCherryPickCommit={cherryPickFromCommit}
|
||||||
onRevertCommit={revertHistoryCommit}
|
onRevertCommit={revertHistoryCommit}
|
||||||
|
onSelectCommit={(commit) => { selectedCommitHash = commit.hash; }}
|
||||||
onToggleCommitFiles={(hash) => {
|
onToggleCommitFiles={(hash) => {
|
||||||
const next = new Set(expandedCommitHashes);
|
const next = new Set(expandedCommitHashes);
|
||||||
if (next.has(hash)) next.delete(hash); else next.add(hash);
|
if (next.has(hash)) next.delete(hash); else next.add(hash);
|
||||||
@@ -4615,6 +4655,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{#if commandPaletteOpen}
|
||||||
|
<CommandPalette
|
||||||
|
language={appLanguage}
|
||||||
|
{hasRepository}
|
||||||
|
{isBusy}
|
||||||
|
{branches}
|
||||||
|
files={repoFiles}
|
||||||
|
{commits}
|
||||||
|
onClose={() => { commandPaletteOpen = false; }}
|
||||||
|
onCheckoutBranch={checkout}
|
||||||
|
onOpenFile={openFileFromCommandPalette}
|
||||||
|
onSelectCommit={selectCommitFromCommandPalette}
|
||||||
|
onFetch={fetchRepo}
|
||||||
|
onPull={pullRepo}
|
||||||
|
onPush={pushRepo}
|
||||||
|
onRefresh={refreshRepo}
|
||||||
|
onOpenSearch={openGlobalSearchDialog}
|
||||||
|
onOpenCompare={openCompareSelect}
|
||||||
|
onOpenReflog={openReflog}
|
||||||
|
onOpenInteractiveRebase={openInteractiveRebase}
|
||||||
|
onOpenWorktrees={openWorktreeDialog}
|
||||||
|
onOpenSyncSettings={openSyncOptions}
|
||||||
|
onOpenSettings={openAppSettings}
|
||||||
|
onOpenAiSettings={openAiSettings}
|
||||||
|
onOpenHelp={openHelp}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if updateToastOpen}
|
{#if updateToastOpen}
|
||||||
<UpdateToast
|
<UpdateToast
|
||||||
state={updateToastState}
|
state={updateToastState}
|
||||||
|
|||||||
@@ -2408,6 +2408,7 @@
|
|||||||
.commit-row { display: grid; min-width: 0; gap: 8px; padding: 10px; border: 1px solid var(--color-border-subtle); border-radius: 8px; background: var(--color-surface-raised); transition: border-color 120ms; }
|
.commit-row { display: grid; min-width: 0; gap: 8px; padding: 10px; border: 1px solid var(--color-border-subtle); border-radius: 8px; background: var(--color-surface-raised); transition: border-color 120ms; }
|
||||||
.commit-row + .commit-row { margin-top: 5px; }
|
.commit-row + .commit-row { margin-top: 5px; }
|
||||||
.commit-row:hover { border-color: var(--color-border); }
|
.commit-row:hover { border-color: var(--color-border); }
|
||||||
|
.commit-row.selected { border-color: color-mix(in srgb, var(--color-primary) 58%, var(--color-border)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary) 18%, transparent) inset; }
|
||||||
.commit-row.compact { padding: 8px; }
|
.commit-row.compact { padding: 8px; }
|
||||||
|
|
||||||
.commit-line { display: flex; align-items: flex-start; min-width: 0; gap: 8px; }
|
.commit-line { display: flex; align-items: flex-start; min-width: 0; gap: 8px; }
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import {
|
||||||
|
ArrowDownToLine, ArrowUpFromLine, Boxes, CircleHelp, FileCode, GitBranch,
|
||||||
|
GitCompare, History, RefreshCw, Search, Settings, SlidersHorizontal, Sparkles,
|
||||||
|
} from "@lucide/svelte";
|
||||||
|
import type { AppLanguage, GitBranch as GitBranchInfo, GitCommit, GitRepositoryFile } from "../types";
|
||||||
|
|
||||||
|
type ItemKind = "fetch" | "pull" | "push" | "refresh" | "search" | "compare" | "reflog" | "rebase" | "worktrees" | "sync" | "settings" | "ai-settings" | "help" | "branch" | "file" | "commit";
|
||||||
|
interface Item { id: string; group: string; kind: ItemKind; title: string; subtitle: string; search: string; disabled?: boolean; run: () => void | Promise<void>; }
|
||||||
|
interface Props {
|
||||||
|
language: AppLanguage; hasRepository: boolean; isBusy: boolean;
|
||||||
|
branches: GitBranchInfo[]; files: GitRepositoryFile[]; commits: GitCommit[];
|
||||||
|
onClose: () => void;
|
||||||
|
onCheckoutBranch: (branch: GitBranchInfo) => void | Promise<void>;
|
||||||
|
onOpenFile: (file: GitRepositoryFile) => void | Promise<void>;
|
||||||
|
onSelectCommit: (commit: GitCommit) => void | Promise<void>;
|
||||||
|
onFetch: () => void | Promise<void>; onPull: () => void | Promise<void>; onPush: () => void | Promise<void>;
|
||||||
|
onRefresh: () => void | Promise<void>; onOpenSearch: () => void; onOpenCompare: () => void;
|
||||||
|
onOpenReflog: () => void | Promise<void>; onOpenInteractiveRebase: () => void;
|
||||||
|
onOpenWorktrees: () => void | Promise<void>; onOpenSyncSettings: () => void | Promise<void>;
|
||||||
|
onOpenSettings: () => void; onOpenAiSettings: () => void; onOpenHelp: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
language = "en", hasRepository = false, isBusy = false, branches = [], files = [], commits = [], onClose = () => {},
|
||||||
|
onCheckoutBranch = () => {}, onOpenFile = () => {}, onSelectCommit = () => {}, onFetch = () => {}, onPull = () => {},
|
||||||
|
onPush = () => {}, onRefresh = () => {}, onOpenSearch = () => {}, onOpenCompare = () => {}, onOpenReflog = () => {},
|
||||||
|
onOpenInteractiveRebase = () => {}, onOpenWorktrees = () => {}, onOpenSyncSettings = () => {}, onOpenSettings = () => {},
|
||||||
|
onOpenAiSettings = () => {}, onOpenHelp = () => {},
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let query = $state("");
|
||||||
|
let activeIndex = $state(0);
|
||||||
|
let inputElement = $state<HTMLInputElement | null>(null);
|
||||||
|
let listElement = $state<HTMLElement | null>(null);
|
||||||
|
const isGerman = $derived(language === "de");
|
||||||
|
const repositoryActionDisabled = $derived(!hasRepository || isBusy);
|
||||||
|
|
||||||
|
function action(id: string, kind: ItemKind, title: string, subtitle: string, run: () => void | Promise<void>, requiresRepository = true): Item {
|
||||||
|
return { id, group: isGerman ? "Aktionen" : "Actions", kind, title, subtitle, search: `${title} ${subtitle}`.toLowerCase(), disabled: requiresRepository ? repositoryActionDisabled : false, run };
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionItems = $derived([
|
||||||
|
action("fetch", "fetch", "Fetch", isGerman ? "Remote-Änderungen abrufen" : "Download remote changes", onFetch),
|
||||||
|
action("pull", "pull", "Pull", isGerman ? "Änderungen abrufen und integrieren" : "Download and integrate changes", onPull),
|
||||||
|
action("push", "push", "Push", isGerman ? "Lokale Commits veröffentlichen" : "Publish local commits", onPush),
|
||||||
|
action("refresh", "refresh", isGerman ? "Repository aktualisieren" : "Refresh repository", isGerman ? "Status und Historie neu laden" : "Reload status and history", onRefresh),
|
||||||
|
action("search", "search", isGerman ? "Globale Codesuche" : "Global code search", isGerman ? "Code und Dateihistorie durchsuchen" : "Search code and file history", onOpenSearch),
|
||||||
|
action("compare", "compare", isGerman ? "Commits vergleichen" : "Compare commits", isGerman ? "Unterschiede zwischen zwei Revisionen" : "Diff two revisions", onOpenCompare),
|
||||||
|
action("reflog", "reflog", "Reflog", isGerman ? "Verlorene Commits finden und wiederherstellen" : "Find and recover lost commits", onOpenReflog),
|
||||||
|
action("rebase", "rebase", "Interactive Rebase", isGerman ? "Commit-Historie bearbeiten" : "Edit commit history", onOpenInteractiveRebase),
|
||||||
|
action("worktrees", "worktrees", "Worktrees", isGerman ? "Arbeitsverzeichnisse verwalten" : "Manage linked working trees", onOpenWorktrees),
|
||||||
|
action("sync", "sync", isGerman ? "Synchronisierung konfigurieren" : "Configure synchronization", isGerman ? "Remote, Upstream und Pull-Strategie" : "Remote, upstream and pull strategy", onOpenSyncSettings),
|
||||||
|
action("settings", "settings", isGerman ? "Einstellungen" : "Settings", isGerman ? "Darstellung, Sprache und Verhalten" : "Appearance, language and behavior", onOpenSettings, false),
|
||||||
|
action("ai-settings", "ai-settings", "AI Settings", isGerman ? "Provider und Modell konfigurieren" : "Configure provider and model", onOpenAiSettings, false),
|
||||||
|
action("help", "help", isGerman ? "Hilfe öffnen" : "Open help", isGerman ? "Git-Dokumentation und Tastenkürzel" : "Git documentation and keyboard shortcuts", onOpenHelp, false),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dynamicItems = $derived.by(() => {
|
||||||
|
if (!hasRepository) return [];
|
||||||
|
const branchItems: Item[] = branches.map((branch) => ({
|
||||||
|
id: `branch:${branch.remote ? "remote" : "local"}:${branch.name}`, group: "Branches", kind: "branch", title: branch.name,
|
||||||
|
subtitle: branch.current ? (isGerman ? "Aktueller Branch" : "Current branch") : branch.remote ? (isGerman ? "Remote-Branch auschecken" : "Check out remote branch") : (isGerman ? "Branch auschecken" : "Check out branch"),
|
||||||
|
search: `${branch.name} branch ${branch.remote ? "remote" : "local"}`.toLowerCase(), disabled: isBusy || branch.current, run: () => onCheckoutBranch(branch),
|
||||||
|
}));
|
||||||
|
const fileItems: Item[] = files.map((file) => ({
|
||||||
|
id: `file:${file.path}`, group: isGerman ? "Dateien" : "Files", kind: "file", title: file.path.split(/[\\/]/).pop() ?? file.path,
|
||||||
|
subtitle: file.path, search: `${file.path} file datei`.toLowerCase(), disabled: isBusy, run: () => onOpenFile(file),
|
||||||
|
}));
|
||||||
|
const commitItems: Item[] = commits.map((commit) => ({
|
||||||
|
id: `commit:${commit.hash}`, group: isGerman ? "Geladene Commits" : "Loaded commits", kind: "commit", title: commit.summary || (isGerman ? "Ohne Commit-Nachricht" : "No commit message"),
|
||||||
|
subtitle: `${commit.short_hash} · ${commit.author_name}`, search: `${commit.hash} ${commit.short_hash} ${commit.summary} ${commit.author_name} ${commit.author_email} ${commit.refs.join(" ")}`.toLowerCase(), run: () => onSelectCommit(commit),
|
||||||
|
}));
|
||||||
|
return [...branchItems, ...fileItems, ...commitItems];
|
||||||
|
});
|
||||||
|
|
||||||
|
const visibleItems = $derived.by(() => {
|
||||||
|
const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
||||||
|
const allItems = [...actionItems, ...dynamicItems];
|
||||||
|
if (terms.length === 0) return allItems.slice(0, 35);
|
||||||
|
return allItems.filter((item) => terms.every((term) => item.search.includes(term))).slice(0, 80);
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => { query; activeIndex = 0; });
|
||||||
|
$effect(() => { if (activeIndex >= visibleItems.length) activeIndex = Math.max(0, visibleItems.length - 1); });
|
||||||
|
$effect(() => { activeIndex; queueMicrotask(() => listElement?.querySelector<HTMLElement>("[data-active='true']")?.scrollIntoView({ block: "nearest" })); });
|
||||||
|
onMount(() => inputElement?.focus());
|
||||||
|
|
||||||
|
function execute(item: Item | undefined) { if (!item || item.disabled) return; onClose(); queueMicrotask(() => { void item.run(); }); }
|
||||||
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); onClose(); return; }
|
||||||
|
if (event.key === "ArrowDown") { event.preventDefault(); activeIndex = Math.min(activeIndex + 1, visibleItems.length - 1); return; }
|
||||||
|
if (event.key === "ArrowUp") { event.preventDefault(); activeIndex = Math.max(activeIndex - 1, 0); return; }
|
||||||
|
if (event.key === "Enter") { event.preventDefault(); execute(visibleItems[activeIndex]); }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="command-palette-backdrop" role="presentation" onclick={(event) => { if (event.target === event.currentTarget) onClose(); }}>
|
||||||
|
<div class="command-palette" role="dialog" aria-modal="true" aria-label={isGerman ? "Befehlspalette" : "Command palette"}>
|
||||||
|
<div class="command-palette-search">
|
||||||
|
<Search size={19} aria-hidden="true" />
|
||||||
|
<input bind:this={inputElement} bind:value={query} onkeydown={handleKeydown} placeholder={isGerman ? "Aktion, Branch, Datei oder Commit suchen…" : "Search actions, branches, files, or commits…"} aria-label={isGerman ? "Befehl suchen" : "Search commands"} autocomplete="off" spellcheck="false" />
|
||||||
|
<kbd>ESC</kbd>
|
||||||
|
</div>
|
||||||
|
<div class="command-palette-results" bind:this={listElement} role="listbox" aria-label={isGerman ? "Ergebnisse" : "Results"}>
|
||||||
|
{#if visibleItems.length === 0}
|
||||||
|
<div class="command-palette-empty"><Search size={24} aria-hidden="true" /><strong>{isGerman ? "Keine Treffer" : "No results"}</strong><span>{isGerman ? "Versuche einen anderen Suchbegriff." : "Try a different search term."}</span></div>
|
||||||
|
{:else}
|
||||||
|
{#each visibleItems as item, index (item.id)}
|
||||||
|
{#if index === 0 || visibleItems[index - 1].group !== item.group}<div class="command-palette-group">{item.group}</div>{/if}
|
||||||
|
<button class="command-palette-item" class:active={index === activeIndex} type="button" role="option" aria-selected={index === activeIndex} data-active={index === activeIndex} disabled={item.disabled} onmouseenter={() => { activeIndex = index; }} onclick={() => execute(item)}>
|
||||||
|
<span class={`command-palette-icon ${item.kind}`}>
|
||||||
|
{#if item.kind === "fetch" || item.kind === "pull"}<ArrowDownToLine size={16} />
|
||||||
|
{:else if item.kind === "push"}<ArrowUpFromLine size={16} />
|
||||||
|
{:else if item.kind === "refresh"}<RefreshCw size={16} />
|
||||||
|
{:else if item.kind === "search"}<Search size={16} />
|
||||||
|
{:else if item.kind === "compare"}<GitCompare size={16} />
|
||||||
|
{:else if item.kind === "reflog" || item.kind === "commit"}<History size={16} />
|
||||||
|
{:else if item.kind === "rebase" || item.kind === "branch"}<GitBranch size={16} />
|
||||||
|
{:else if item.kind === "worktrees"}<Boxes size={16} />
|
||||||
|
{:else if item.kind === "sync"}<SlidersHorizontal size={16} />
|
||||||
|
{:else if item.kind === "settings"}<Settings size={16} />
|
||||||
|
{:else if item.kind === "ai-settings"}<Sparkles size={16} />
|
||||||
|
{:else if item.kind === "help"}<CircleHelp size={16} />
|
||||||
|
{:else}<FileCode size={16} />{/if}
|
||||||
|
</span>
|
||||||
|
<span class="command-palette-copy"><strong>{item.title}</strong><small>{item.subtitle}</small></span>
|
||||||
|
{#if item.kind === "branch" && item.disabled && !isBusy}<span class="command-palette-current">{isGerman ? "AKTUELL" : "CURRENT"}</span>{/if}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<footer class="command-palette-footer"><span><kbd>↑</kbd><kbd>↓</kbd>{isGerman ? "Navigieren" : "Navigate"}</span><span><kbd>↵</kbd>{isGerman ? "Öffnen" : "Open"}</span>{#if commits.length > 0}<span class="command-palette-hint">{isGerman ? `${commits.length} geladene Commits` : `${commits.length} loaded commits`}</span>{/if}</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.command-palette-backdrop { position: fixed; inset: 0; z-index: 100; display: grid; place-items: start center; padding: min(14vh, 120px) 20px 20px; background: color-mix(in srgb, var(--app-dialog-backdrop) 76%, transparent); backdrop-filter: blur(7px) saturate(.82); }
|
||||||
|
.command-palette { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; width: min(720px, 100%); max-height: min(650px, 76vh); overflow: hidden; border: 1px solid color-mix(in srgb, var(--color-primary) 22%, var(--color-border)); border-radius: 14px; background: var(--app-dialog-bg); box-shadow: 0 28px 90px rgba(0,0,0,.42); }
|
||||||
|
.command-palette-search { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 11px; padding: 15px 17px; border-bottom: 1px solid var(--color-border); color: var(--color-primary); }
|
||||||
|
.command-palette-search input { min-width: 0; border: 0; outline: 0; color: var(--color-ink); background: transparent; font: inherit; font-size: 15px; }
|
||||||
|
.command-palette-search input::placeholder { color: var(--color-ink-dim); }
|
||||||
|
kbd { display: inline-grid; place-items: center; min-width: 23px; height: 21px; padding: 0 5px; border: 1px solid var(--color-border); border-radius: 5px; color: var(--color-ink-dim); background: var(--color-surface-raised); font-family: var(--font-mono); font-size: 9px; font-weight: 700; }
|
||||||
|
.command-palette-results { min-height: 120px; overflow-y: auto; padding: 7px; }
|
||||||
|
.command-palette-group { padding: 10px 9px 5px; color: var(--color-ink-dim); font-size: 10px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; }
|
||||||
|
.command-palette-item { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; width: 100%; gap: 10px; padding: 8px 10px; border: 1px solid transparent; border-radius: 8px; text-align: left; color: var(--color-ink); background: transparent; cursor: pointer; }
|
||||||
|
.command-palette-item.active:not(:disabled) { border-color: color-mix(in srgb, var(--color-primary) 24%, transparent); background: color-mix(in srgb, var(--color-primary) 11%, var(--color-surface-raised)); }
|
||||||
|
.command-palette-item:disabled { cursor: default; opacity: .48; }
|
||||||
|
.command-palette-icon { display: grid; place-items: center; width: 31px; height: 31px; border: 1px solid var(--color-border-subtle); border-radius: 8px; color: var(--color-ink-muted); background: var(--color-surface-raised); }
|
||||||
|
.command-palette-icon.branch { color: #65c98b; } .command-palette-icon.file { color: #69a7ff; } .command-palette-icon.commit { color: #ba82ff; }
|
||||||
|
.command-palette-copy { display: grid; min-width: 0; gap: 2px; } .command-palette-copy strong, .command-palette-copy small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.command-palette-copy strong { font-size: 12.5px; font-weight: 700; } .command-palette-copy small { color: var(--color-ink-dim); font-size: 10.5px; }
|
||||||
|
.command-palette-current { color: var(--color-ink-dim); font-family: var(--font-mono); font-size: 9px; font-weight: 700; }
|
||||||
|
.command-palette-empty { display: grid; place-items: center; gap: 5px; padding: 54px 20px; color: var(--color-ink-dim); } .command-palette-empty strong { margin-top: 5px; color: var(--color-ink); font-size: 13px; } .command-palette-empty span { font-size: 11px; }
|
||||||
|
.command-palette-footer { display: flex; align-items: center; gap: 16px; min-height: 38px; padding: 7px 12px; border-top: 1px solid var(--color-border); color: var(--color-ink-dim); font-size: 10px; }
|
||||||
|
.command-palette-footer span { display: flex; align-items: center; gap: 5px; } .command-palette-footer span kbd + kbd { margin-left: -3px; } .command-palette-hint { margin-left: auto; }
|
||||||
|
@media (max-width: 640px) { .command-palette-backdrop { padding: 60px 10px 10px; } .command-palette { max-height: calc(100vh - 80px); } .command-palette-hint { display: none !important; } }
|
||||||
|
</style>
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
isLoadingMore: boolean;
|
isLoadingMore: boolean;
|
||||||
loadMoreError: string;
|
loadMoreError: string;
|
||||||
expandedCommitHashes: Set<string>;
|
expandedCommitHashes: Set<string>;
|
||||||
|
selectedCommitHash: string;
|
||||||
onLoadMore: () => void | Promise<void>;
|
onLoadMore: () => void | Promise<void>;
|
||||||
onRestoreCommit: (commit: GitCommit) => void;
|
onRestoreCommit: (commit: GitCommit) => void;
|
||||||
onPreviewCommitFile: (commit: GitCommit, file: GitCommitFile) => void;
|
onPreviewCommitFile: (commit: GitCommit, file: GitCommitFile) => void;
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
onCreateBranchFromCommit: (commit: GitCommit) => void;
|
onCreateBranchFromCommit: (commit: GitCommit) => void;
|
||||||
onCherryPickCommit: (commit: GitCommit) => void;
|
onCherryPickCommit: (commit: GitCommit) => void;
|
||||||
onRevertCommit: (commit: GitCommit) => void;
|
onRevertCommit: (commit: GitCommit) => void;
|
||||||
|
onSelectCommit: (commit: GitCommit) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -65,6 +67,7 @@
|
|||||||
isLoadingMore = false,
|
isLoadingMore = false,
|
||||||
loadMoreError = "",
|
loadMoreError = "",
|
||||||
expandedCommitHashes = new Set(),
|
expandedCommitHashes = new Set(),
|
||||||
|
selectedCommitHash = "",
|
||||||
onLoadMore = () => {},
|
onLoadMore = () => {},
|
||||||
onRestoreCommit = () => {},
|
onRestoreCommit = () => {},
|
||||||
onPreviewCommitFile = () => {},
|
onPreviewCommitFile = () => {},
|
||||||
@@ -72,6 +75,7 @@
|
|||||||
onCreateBranchFromCommit = () => {},
|
onCreateBranchFromCommit = () => {},
|
||||||
onCherryPickCommit = () => {},
|
onCherryPickCommit = () => {},
|
||||||
onRevertCommit = () => {},
|
onRevertCommit = () => {},
|
||||||
|
onSelectCommit = () => {},
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let hiddenGraphBranches = $state<Set<string>>(new Set());
|
let hiddenGraphBranches = $state<Set<string>>(new Set());
|
||||||
@@ -501,6 +505,15 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!selectedCommitHash) return;
|
||||||
|
queueMicrotask(() => {
|
||||||
|
panelElement
|
||||||
|
?.querySelector<HTMLElement>(`[data-commit-hash="${CSS.escape(selectedCommitHash)}"]`)
|
||||||
|
?.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const defaultBranch = activeBranch && localBranchNames.includes(activeBranch)
|
const defaultBranch = activeBranch && localBranchNames.includes(activeBranch)
|
||||||
? activeBranch
|
? activeBranch
|
||||||
@@ -577,6 +590,8 @@
|
|||||||
{@const rowSyncClass = syncClassForBranches(row?.branchLabels ?? [])}
|
{@const rowSyncClass = syncClassForBranches(row?.branchLabels ?? [])}
|
||||||
<article
|
<article
|
||||||
class="commit-row graph-row"
|
class="commit-row graph-row"
|
||||||
|
class:selected={selectedCommitHash === item.hash}
|
||||||
|
data-commit-hash={item.hash}
|
||||||
class:graph-ahead-row={rowSyncClass === "ahead"}
|
class:graph-ahead-row={rowSyncClass === "ahead"}
|
||||||
class:graph-behind-row={rowSyncClass === "behind"}
|
class:graph-behind-row={rowSyncClass === "behind"}
|
||||||
class:merge-row={item.parents.length > 1}
|
class:merge-row={item.parents.length > 1}
|
||||||
|
|||||||
Reference in New Issue
Block a user