Auth for submodule ops, revision checkout, central confirm dialog, unified sidebar sizing #46
+47
-14
@@ -438,8 +438,9 @@
|
||||
let externalToolsDetectionUnavailable = false;
|
||||
let errorMessage = "";
|
||||
let operation = "";
|
||||
type ConfirmAnswer = { confirmed: boolean; value: string; checked: boolean };
|
||||
let confirmDialogRequest: ConfirmRequest | null = null;
|
||||
let confirmDialogResolve: ((confirmed: boolean) => void) | null = null;
|
||||
let confirmDialogResolve: ((answer: ConfirmAnswer) => void) | null = null;
|
||||
let compareFrom = "";
|
||||
let compareTo = "";
|
||||
let comparison: GitCommitComparison | null = null;
|
||||
@@ -1289,15 +1290,20 @@
|
||||
setLanguage(next);
|
||||
}
|
||||
|
||||
/** Show the confirmation dialog and resolve once the user answers. */
|
||||
function requestConfirmation(request: ConfirmRequest): Promise<boolean> {
|
||||
confirmDialogResolve?.(false);
|
||||
/** Show the dialog and resolve with the answer, including input and checkbox. */
|
||||
function askConfirmation(request: ConfirmRequest): Promise<ConfirmAnswer> {
|
||||
confirmDialogResolve?.({ confirmed: false, value: "", checked: false });
|
||||
confirmDialogRequest = request;
|
||||
return new Promise<boolean>((resolve) => {
|
||||
return new Promise<ConfirmAnswer>((resolve) => {
|
||||
confirmDialogResolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
/** Yes/no only, for the many confirmations that need nothing else. */
|
||||
async function requestConfirmation(request: ConfirmRequest): Promise<boolean> {
|
||||
return (await askConfirmation(request)).confirmed;
|
||||
}
|
||||
|
||||
/** Confirmation shown before deleting a local or remote branch. */
|
||||
function branchDeleteConfirmRequest(branch: GitBranchInfo, force: boolean): ConfirmRequest {
|
||||
const remoteName = branch.remote ? branch.name.split("/")[0] : "";
|
||||
@@ -1356,11 +1362,11 @@
|
||||
};
|
||||
}
|
||||
|
||||
function answerConfirmation(confirmed: boolean) {
|
||||
function answerConfirmation(confirmed: boolean, value = "", checked = false) {
|
||||
const resolve = confirmDialogResolve;
|
||||
confirmDialogRequest = null;
|
||||
confirmDialogResolve = null;
|
||||
resolve?.(confirmed);
|
||||
resolve?.({ confirmed, value, checked });
|
||||
}
|
||||
|
||||
function applyThemePreference(next: AppTheme) {
|
||||
@@ -4797,8 +4803,34 @@
|
||||
}
|
||||
|
||||
async function stashStatusFiles(files: GitFileStatus[], label: string) {
|
||||
const suffix = files.length === 1 ? files[0].path : `${label} (${files.length} files)`;
|
||||
await saveStash(`Gitty: ${suffix}`, true, files);
|
||||
if (files.length === 0) return;
|
||||
const suffix = files.length === 1
|
||||
? files[0].path
|
||||
: label ? `${label} (${files.length} files)` : `${files.length} files`;
|
||||
const fallbackMessage = `Gitty: ${suffix}`;
|
||||
|
||||
const answer = await askConfirmation({
|
||||
eyebrow: t("confirm.stashFiles.eyebrow"),
|
||||
title: files.length === 1 ? t("confirm.stashFiles.titleOne") : t("confirm.stashFiles.title", { count: files.length }),
|
||||
message: t("confirm.stashFiles.message"),
|
||||
items: files.map((file) => file.path),
|
||||
input: {
|
||||
label: t("confirm.stashFiles.inputLabel"),
|
||||
placeholder: fallbackMessage,
|
||||
value: fallbackMessage,
|
||||
optional: true,
|
||||
},
|
||||
checkbox: {
|
||||
label: t("confirm.stashFiles.untracked"),
|
||||
note: t("confirm.stashFiles.untrackedNote"),
|
||||
defaultChecked: true,
|
||||
},
|
||||
confirmLabel: t("stashes.save"),
|
||||
danger: false,
|
||||
});
|
||||
|
||||
if (!answer.confirmed) return;
|
||||
await saveStash(answer.value.trim() || fallbackMessage, answer.checked, files);
|
||||
}
|
||||
|
||||
async function applyStashEntry(stash: GitStash) {
|
||||
@@ -4884,12 +4916,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function stopTrackingTarget(target: string, kind: "file" | "folder") {
|
||||
if (!activeRepoPath || !target) return;
|
||||
async function stopTrackingTarget(targets: string[], kind: "file" | "folder" | "selection") {
|
||||
const paths = targets.filter(Boolean);
|
||||
if (!activeRepoPath || paths.length === 0) return;
|
||||
await runOperation(`Stopping tracking for ${kind}`, async () => {
|
||||
applyStatus(await untrackPaths(activeRepoPath, [target]));
|
||||
applyStatus(await untrackPaths(activeRepoPath, paths));
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
trackEvent("git_paths_untracked", { kind });
|
||||
trackEvent("git_paths_untracked", { kind, paths: paths.length });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6608,7 +6641,7 @@
|
||||
{#if confirmDialogRequest}
|
||||
<ConfirmDialog
|
||||
request={confirmDialogRequest}
|
||||
onConfirm={() => answerConfirmation(true)}
|
||||
onConfirm={(result) => answerConfirmation(true, result.value, result.checked)}
|
||||
onCancel={() => answerConfirmation(false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
note?: string;
|
||||
confirmLabel?: string;
|
||||
cancelLabel?: string;
|
||||
/** Optional opt-in the user must tick before confirming, e.g. "delete anyway". */
|
||||
checkbox?: { label: string; note?: string; required?: boolean };
|
||||
/** Optional opt-in, e.g. "delete anyway" (required) or "include untracked". */
|
||||
checkbox?: { label: string; note?: string; required?: boolean; defaultChecked?: boolean };
|
||||
/** Optional single-line input, e.g. a stash message. */
|
||||
input?: { label: string; placeholder?: string; value?: string; optional?: boolean };
|
||||
/** Destructive actions get the red confirm button and warning icon. */
|
||||
danger?: boolean;
|
||||
}
|
||||
@@ -28,8 +30,8 @@
|
||||
interface Props {
|
||||
request: ConfirmRequest;
|
||||
isBusy?: boolean;
|
||||
/** `checked` is the state of the optional checkbox. */
|
||||
onConfirm: (checked: boolean) => void;
|
||||
/** Carries the state of the optional checkbox and input. */
|
||||
onConfirm: (result: { checked: boolean; value: string }) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@@ -42,18 +44,28 @@
|
||||
let danger = $derived(request.danger !== false);
|
||||
let items = $derived(request.items ?? []);
|
||||
let checked = $state(false);
|
||||
let blocked = $derived(Boolean(request.checkbox?.required) && !checked);
|
||||
let value = $state("");
|
||||
let inputElement = $state<HTMLInputElement | null>(null);
|
||||
let missingInput = $derived(Boolean(request.input) && request.input?.optional !== true && value.trim().length === 0);
|
||||
let blocked = $derived((Boolean(request.checkbox?.required) && !checked) || missingInput);
|
||||
|
||||
$effect(() => {
|
||||
// Reset the opt-in whenever a different confirmation is shown.
|
||||
// Start from the defaults again whenever a different confirmation is shown.
|
||||
request.title;
|
||||
checked = false;
|
||||
checked = request.checkbox?.defaultChecked ?? false;
|
||||
value = request.input?.value ?? "";
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
confirmButton?.focus();
|
||||
// The input is the first thing to fill in when there is one.
|
||||
if (inputElement) inputElement.select();
|
||||
else confirmButton?.focus();
|
||||
});
|
||||
|
||||
function submit() {
|
||||
if (!isBusy && !blocked) onConfirm({ checked, value });
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === "Escape") {
|
||||
event.stopPropagation();
|
||||
@@ -116,6 +128,22 @@
|
||||
</ul>
|
||||
{/if}
|
||||
|
||||
{#if request.input}
|
||||
<label class="confirm-input">
|
||||
<span>{request.input.label}</span>
|
||||
<input
|
||||
bind:this={inputElement}
|
||||
bind:value
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
placeholder={request.input.placeholder ?? ""}
|
||||
disabled={isBusy}
|
||||
onkeydown={(event) => { if (event.key === "Enter") { event.preventDefault(); submit(); } }}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#if request.checkbox}
|
||||
<label class="confirm-check">
|
||||
<input type="checkbox" bind:checked disabled={isBusy} />
|
||||
@@ -140,7 +168,7 @@
|
||||
bind:this={confirmButton}
|
||||
class={`confirm-action ${danger ? "btn-danger" : "btn-primary"}`}
|
||||
type="button"
|
||||
onclick={() => onConfirm(checked)}
|
||||
onclick={submit}
|
||||
disabled={isBusy || blocked}
|
||||
>
|
||||
{#if isBusy}
|
||||
@@ -206,6 +234,13 @@
|
||||
background: color-mix(in srgb, var(--color-accent) 9%, transparent);
|
||||
}
|
||||
.confirm-dialog .confirm-action { min-width: 116px; }
|
||||
.confirm-dialog .confirm-input { display: grid; gap: 5px; }
|
||||
.confirm-dialog .confirm-input span {
|
||||
color: var(--color-ink-muted);
|
||||
font-size: 11.5px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.confirm-dialog .confirm-input input { height: 32px; font-size: 12.5px; }
|
||||
.confirm-dialog .confirm-check {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
@@ -217,6 +252,11 @@
|
||||
background: rgba(255, 90, 103, 0.05);
|
||||
cursor: pointer;
|
||||
}
|
||||
.confirm-dialog:not(.danger) .confirm-check {
|
||||
border-color: var(--color-border-subtle);
|
||||
background: color-mix(in srgb, var(--color-accent) 5%, transparent);
|
||||
}
|
||||
.confirm-dialog:not(.danger) .confirm-check input { accent-color: var(--color-accent); }
|
||||
.confirm-dialog .confirm-check input { width: 15px; height: 15px; margin-top: 1px; accent-color: #e86060; }
|
||||
.confirm-dialog .confirm-check span { display: grid; gap: 2px; min-width: 0; }
|
||||
.confirm-dialog .confirm-check strong { color: var(--color-ink); font-size: 12.5px; font-weight: 650; }
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
onFileHistory: (node: ExplorerNode) => void;
|
||||
onBlame: (node: ExplorerNode) => void;
|
||||
onIgnore: (target: string, kind: GitIgnoreKind) => void;
|
||||
onStopTracking: (target: string, kind: "file" | "folder") => void;
|
||||
onStopTracking: (targets: string[], kind: "file" | "folder") => void;
|
||||
collapsed?: boolean;
|
||||
onToggleCollapsed?: () => void;
|
||||
}
|
||||
@@ -268,7 +268,7 @@
|
||||
const node = contextNode;
|
||||
if (!node) return;
|
||||
closeFileContextMenu();
|
||||
onStopTracking(node.path, node.kind);
|
||||
onStopTracking([node.path], node.kind);
|
||||
}
|
||||
|
||||
function handleWindowKeydown(event: KeyboardEvent) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Archive,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
FileDiff,
|
||||
CopyCheck, FileDiff,
|
||||
FileMinus2,
|
||||
FileType,
|
||||
FileX,
|
||||
@@ -34,7 +34,7 @@
|
||||
onDiscardMany: (files: GitFileStatus[]) => void;
|
||||
onStash: (files: GitFileStatus[], label: string) => void;
|
||||
onIgnore: (target: string, kind: GitIgnoreKind) => void;
|
||||
onStopTracking: (target: string, kind: "file" | "folder") => void;
|
||||
onStopTracking: (targets: string[], kind: "file" | "folder" | "selection") => void;
|
||||
onPatch: (file: GitFileStatus, staged: boolean) => void;
|
||||
onStageAll: () => void;
|
||||
onUnstageAll: () => void;
|
||||
@@ -64,7 +64,8 @@
|
||||
|
||||
interface StatusContextTarget {
|
||||
lane: StatusLaneKind;
|
||||
kind: "file" | "folder";
|
||||
/** "selection" is a right-click on one row of a multi-selection. */
|
||||
kind: "file" | "folder" | "selection";
|
||||
label: string;
|
||||
files: GitFileStatus[];
|
||||
}
|
||||
@@ -228,9 +229,22 @@
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (files.length === 0) return;
|
||||
|
||||
// Right-clicking a row that belongs to the current multi-selection acts on
|
||||
// the whole selection, the same way the row buttons already do.
|
||||
let targetKind: StatusContextTarget["kind"] = kind;
|
||||
let targetFiles = files;
|
||||
if (kind === "file" && files.length === 1 && isStatusSelected(files[0])) {
|
||||
const laneFiles = selectedFiles().filter((file) => (lane === "unstaged" ? file.unstaged !== null : file.staged !== null));
|
||||
if (laneFiles.length > 1) {
|
||||
targetKind = "selection";
|
||||
targetFiles = laneFiles;
|
||||
}
|
||||
}
|
||||
|
||||
statusContextMenuX = Math.max(8, Math.min(event.clientX, window.innerWidth - 288));
|
||||
statusContextMenuY = Math.max(8, Math.min(event.clientY, window.innerHeight - 174));
|
||||
statusContextTarget = { lane, kind, label, files };
|
||||
statusContextTarget = { lane, kind: targetKind, label, files: targetFiles };
|
||||
requestAnimationFrame(() => {
|
||||
if (!statusContextMenuElement) return;
|
||||
const bounds = statusContextMenuElement.getBoundingClientRect();
|
||||
@@ -266,7 +280,7 @@
|
||||
const target = statusContextTarget;
|
||||
if (!target) return;
|
||||
closeStatusContextMenu();
|
||||
onStash(target.files, target.label);
|
||||
onStash(target.files, target.kind === "selection" ? "" : target.label);
|
||||
}
|
||||
|
||||
function isIgnoreableNewFile(file: GitFileStatus): boolean {
|
||||
@@ -305,7 +319,8 @@
|
||||
const target = statusContextTarget;
|
||||
if (!target) return;
|
||||
closeStatusContextMenu();
|
||||
onStopTracking(target.label, target.kind);
|
||||
const targets = target.kind === "selection" ? target.files.map((file) => file.path) : [target.label];
|
||||
onStopTracking(targets, target.kind);
|
||||
}
|
||||
|
||||
function handleStatusWindowKeydown(event: KeyboardEvent) {
|
||||
@@ -396,10 +411,10 @@
|
||||
let visibleStagedRows = $derived(statusView === "tree" ? flattenStatusTree(stagedTree, "staged") : listStatusRows(stagedFiles));
|
||||
let selectedUnstagedCount = $derived(changedFiles.filter((f) => selectedStatusPaths.has(fileKey(f)) && f.unstaged !== null).length);
|
||||
let selectedStagedCount = $derived(changedFiles.filter((f) => selectedStatusPaths.has(fileKey(f)) && f.staged !== null).length);
|
||||
let statusContextCanIgnore = $derived(statusContextTarget?.files.some(isIgnoreableNewFile) ?? false);
|
||||
let statusContextCanIgnore = $derived(statusContextTarget?.kind !== "selection" && (statusContextTarget?.files.some(isIgnoreableNewFile) ?? false));
|
||||
let statusContextCanStopTracking = $derived(statusContextTarget?.files.some(isTrackedStatusFile) ?? false);
|
||||
let statusContextIgnoreExtension = $derived(statusContextTarget?.kind === "file" ? statusContextExtension(statusContextTarget.label) : "");
|
||||
let statusContextIgnoreFolder = $derived(statusContextTarget ? statusContextFolder(statusContextTarget) : "");
|
||||
let statusContextIgnoreFolder = $derived(statusContextTarget && statusContextTarget.kind !== "selection" ? statusContextFolder(statusContextTarget) : "");
|
||||
|
||||
$effect(() => {
|
||||
const validKeys = new Set(changedFiles.map(fileKey));
|
||||
@@ -580,12 +595,18 @@
|
||||
<div bind:this={statusContextMenuElement} class="status-context-menu" style={`left: ${statusContextMenuX}px; top: ${statusContextMenuY}px;`} role="menu" tabindex="-1" aria-label={t("status.menuActionsFor", { name: statusContextTarget.label })} onclick={(event) => event.stopPropagation()} onkeydown={(event) => { if (event.key === "Escape") closeStatusContextMenu(); event.stopPropagation(); }}>
|
||||
<div class="status-context-label">
|
||||
<span class="status-context-object-icon" aria-hidden="true">
|
||||
{#if statusContextTarget.kind === "folder"}<FolderOpen size={16} />{:else}<FileDiff size={16} />{/if}
|
||||
{#if statusContextTarget.kind === "folder"}<FolderOpen size={16} />{:else if statusContextTarget.kind === "selection"}<CopyCheck size={16} />{:else}<FileDiff size={16} />{/if}
|
||||
</span>
|
||||
<span class="status-context-object-copy">
|
||||
<span class="status-context-kind">{statusContextTarget.lane === "unstaged" ? (statusContextTarget.kind === "folder" ? t("status.menuKindUnstagedFolder") : t("status.menuKindUnstagedFile")) : (statusContextTarget.kind === "folder" ? t("status.menuKindStagedFolder") : t("status.menuKindStagedFile"))}</span>
|
||||
<strong title={statusContextTarget.label}>{statusContextName(statusContextTarget.label)}</strong>
|
||||
<span class="status-context-path" title={statusContextTarget.label}><Folder size={10} aria-hidden="true" />{statusContextParent(statusContextTarget.label)}</span>
|
||||
<span class="status-context-kind">{statusContextTarget.lane === "unstaged"
|
||||
? (statusContextTarget.kind === "folder" ? t("status.menuKindUnstagedFolder") : statusContextTarget.kind === "selection" ? t("status.menuKindUnstagedSelection") : t("status.menuKindUnstagedFile"))
|
||||
: (statusContextTarget.kind === "folder" ? t("status.menuKindStagedFolder") : statusContextTarget.kind === "selection" ? t("status.menuKindStagedSelection") : t("status.menuKindStagedFile"))}</span>
|
||||
{#if statusContextTarget.kind === "selection"}
|
||||
<strong>{t("status.menuFileCount", { count: statusContextTarget.files.length })}</strong>
|
||||
{:else}
|
||||
<strong title={statusContextTarget.label}>{statusContextName(statusContextTarget.label)}</strong>
|
||||
<span class="status-context-path" title={statusContextTarget.label}><Folder size={10} aria-hidden="true" />{statusContextParent(statusContextTarget.label)}</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="status-context-count" title={statusContextTarget.files.length === 1 ? t("status.menuFileCountOne") : t("status.menuFileCount", { count: statusContextTarget.files.length })}>
|
||||
{statusContextTarget.files.length}
|
||||
@@ -596,14 +617,16 @@
|
||||
{#if statusContextTarget.lane === "unstaged"}<ArrowRight size={15} />{:else}<ArrowLeft size={15} />{/if}
|
||||
</span>
|
||||
<span class="status-context-action-copy">
|
||||
<strong>{statusContextTarget.lane === "unstaged" ? (statusContextTarget.kind === "folder" ? t("status.menuStageFolder") : t("status.menuStageFile")) : (statusContextTarget.kind === "folder" ? t("status.menuUnstageFolder") : t("status.menuUnstageFile"))}</strong>
|
||||
<strong>{statusContextTarget.lane === "unstaged"
|
||||
? (statusContextTarget.kind === "folder" ? t("status.menuStageFolder") : statusContextTarget.kind === "selection" ? t("status.menuStageSelection", { count: statusContextTarget.files.length }) : t("status.menuStageFile"))
|
||||
: (statusContextTarget.kind === "folder" ? t("status.menuUnstageFolder") : statusContextTarget.kind === "selection" ? t("status.menuUnstageSelection", { count: statusContextTarget.files.length }) : t("status.menuUnstageFile"))}</strong>
|
||||
<span>{statusContextTarget.lane === "unstaged" ? t("status.menuStageHint") : t("status.menuUnstageHint")}</span>
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem" onclick={runStatusContextStashAction} disabled={isBusy}>
|
||||
<span class="status-context-action-icon" aria-hidden="true"><Archive size={15} /></span>
|
||||
<span class="status-context-action-copy">
|
||||
<strong>{statusContextTarget.kind === "folder" ? t("status.menuStashFolder") : t("status.menuStashFile")}</strong>
|
||||
<strong>{statusContextTarget.kind === "folder" ? t("status.menuStashFolder") : statusContextTarget.kind === "selection" ? t("status.menuStashSelection", { count: statusContextTarget.files.length }) : t("status.menuStashFile")}</strong>
|
||||
<span>{statusContextTarget.files.length === 1 ? t("status.menuStashHintOne") : t("status.menuStashHint", { count: statusContextTarget.files.length })}</span>
|
||||
</span>
|
||||
</button>
|
||||
@@ -616,7 +639,7 @@
|
||||
{#if statusContextTarget.kind === "folder"}<FolderMinus size={15} />{:else}<FileMinus2 size={15} />{/if}
|
||||
</span>
|
||||
<span class="status-context-action-copy">
|
||||
<strong>{statusContextTarget.kind === "folder" ? t("status.menuStopTrackingFolder") : t("status.menuStopTrackingFile")}</strong>
|
||||
<strong>{statusContextTarget.kind === "folder" ? t("status.menuStopTrackingFolder") : statusContextTarget.kind === "selection" ? t("status.menuStopTrackingSelection", { count: statusContextTarget.files.length }) : t("status.menuStopTrackingFile")}</strong>
|
||||
<span>{t("status.menuStopTrackingNote")}</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -430,7 +430,7 @@
|
||||
<ConfirmDialog
|
||||
request={removalConfirmRequest(pendingRemoval)}
|
||||
{isBusy}
|
||||
onConfirm={(force) => { void confirmRemoval(force); }}
|
||||
onConfirm={(result) => { void confirmRemoval(result.checked); }}
|
||||
onCancel={() => { pendingRemoval = null; forceRemoval = false; }}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -479,6 +479,21 @@ export const messages = {
|
||||
"confirm.worktreeRemove.title": { en: "Remove {name}?", de: "{name} entfernen?" },
|
||||
"confirm.worktreeRemove.message": { en: "This removes the worktree folder and its Git registration. The branch itself is kept.", de: "Das entfernt den Worktree-Ordner und seine Git-Registrierung. Der Branch selbst bleibt erhalten." },
|
||||
"confirm.worktreeRemove.action": { en: "Remove worktree", de: "Worktree entfernen" },
|
||||
|
||||
// ── Stash from the changes context menu ────────────────────────────────────
|
||||
"confirm.stashFiles.eyebrow": { en: "Save for later", de: "Für später sichern" },
|
||||
"confirm.stashFiles.titleOne": { en: "Stash 1 file?", de: "1 Datei stashen?" },
|
||||
"confirm.stashFiles.title": { en: "Stash {count} files?", de: "{count} Dateien stashen?" },
|
||||
"confirm.stashFiles.message": { en: "These changes are saved to a stash and removed from your working tree.", de: "Diese Änderungen werden in einem Stash gesichert und aus dem Arbeitsverzeichnis entfernt." },
|
||||
"confirm.stashFiles.inputLabel": { en: "Message (optional)", de: "Nachricht (optional)" },
|
||||
"confirm.stashFiles.untracked": { en: "Include untracked files", de: "Unverfolgte Dateien einbeziehen" },
|
||||
"confirm.stashFiles.untrackedNote": { en: "Files Git does not track yet are stashed as well.", de: "Dateien, die Git noch nicht verfolgt, werden mitgesichert." },
|
||||
"status.menuKindUnstagedSelection": { en: "Unstaged selection", de: "Ungestagte Auswahl" },
|
||||
"status.menuKindStagedSelection": { en: "Staged selection", de: "Gestagte Auswahl" },
|
||||
"status.menuStageSelection": { en: "Stage {count} files", de: "{count} Dateien stagen" },
|
||||
"status.menuUnstageSelection": { en: "Unstage {count} files", de: "{count} Dateien entstagen" },
|
||||
"status.menuStashSelection": { en: "Stash {count} files", de: "{count} Dateien stashen" },
|
||||
"status.menuStopTrackingSelection": { en: "Stop tracking {count} files", de: "{count} Dateien nicht mehr verfolgen" },
|
||||
} as const;
|
||||
|
||||
export type MessageKey = keyof typeof messages;
|
||||
|
||||
Reference in New Issue
Block a user