feat(review): auto-continue merge and poll provider after push
Coordinate local conflict resolutions with automated push and provider- side merge status checks. When a local resolution completes the app advances the merge workflow, pushes the updated branch, and attempts to continue the merge. The UI shows a checking state and disables relevant actions while the provider rechecks to prevent duplicate operations. This streamlines finishing conflict resolution and keeps PR status in sync with remote providers. - Automatically push and continue merge when local resolutions finish - Poll provider for updated mergeability and refresh request details - Add guards to disable UI actions while waiting for remote status
This commit is contained in:
+23
-2
@@ -2700,7 +2700,6 @@
|
|||||||
const sourceBranch = localSource?.name ?? remoteSource?.name;
|
const sourceBranch = localSource?.name ?? remoteSource?.name;
|
||||||
if (!sourceBranch) throw new Error(appLanguage === "de" ? `Der Quellbranch „${request.sourceBranch}“ wurde lokal und auf ${match.remote.name} nicht gefunden.` : `The source branch “${request.sourceBranch}” was not found locally or on ${match.remote.name}.`);
|
if (!sourceBranch) throw new Error(appLanguage === "de" ? `Der Quellbranch „${request.sourceBranch}“ wurde lokal und auf ${match.remote.name} nicht gefunden.` : `The source branch “${request.sourceBranch}” was not found locally or on ${match.remote.name}.`);
|
||||||
if (status?.current_branch !== request.sourceBranch) applyStatus(await checkoutBranch(match.path, sourceBranch));
|
if (status?.current_branch !== request.sourceBranch) applyStatus(await checkoutBranch(match.path, sourceBranch));
|
||||||
if ((status?.ahead ?? 0) > 0) throw new Error(appLanguage === "de" ? `Der lokale Branch „${request.sourceBranch}“ enthält noch nicht gepushte Commits. Pushe oder sichere sie zuerst.` : `The local branch “${request.sourceBranch}” contains unpushed commits. Push or preserve them first.`);
|
|
||||||
if (remoteSource) applyStatus(await mergeBranch(match.path, remoteSource.name, "ff-only"));
|
if (remoteSource) applyStatus(await mergeBranch(match.path, remoteSource.name, "ff-only"));
|
||||||
const targetBranch = availableBranches.some((branch) => branch.remote && branch.name === `${match.remote.name}/${request.targetBranch}`)
|
const targetBranch = availableBranches.some((branch) => branch.remote && branch.name === `${match.remote.name}/${request.targetBranch}`)
|
||||||
? `${match.remote.name}/${request.targetBranch}`
|
? `${match.remote.name}/${request.targetBranch}`
|
||||||
@@ -2717,6 +2716,8 @@
|
|||||||
} else {
|
} else {
|
||||||
reviewConflictPhase = "ready-to-push";
|
reviewConflictPhase = "ready-to-push";
|
||||||
reviewConflictMessage = appLanguage === "de" ? "Der Zielbranch wurde ohne Dateikonflikte übernommen. Der Quellbranch kann jetzt gepusht werden." : "The target branch was merged without file conflicts. The source branch is ready to push.";
|
reviewConflictMessage = appLanguage === "de" ? "Der Zielbranch wurde ohne Dateikonflikte übernommen. Der Quellbranch kann jetzt gepusht werden." : "The target branch was merged without file conflicts. The source branch is ready to push.";
|
||||||
|
await tick();
|
||||||
|
await pushReviewConflictResolution();
|
||||||
}
|
}
|
||||||
trackEvent("review_conflict_resolution_started");
|
trackEvent("review_conflict_resolution_started");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -2731,12 +2732,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function continueReviewConflictMerge() {
|
async function continueReviewConflictMerge() {
|
||||||
if (reviewConflictPhase !== "ready-to-continue" || hasConflicts || !mergeInProgress) return;
|
if (!reviewConflictRequestId || hasConflicts || !mergeInProgress || isBusy) return;
|
||||||
|
reviewConflictPhase = "ready-to-continue";
|
||||||
|
reviewConflictMessage = appLanguage === "de" ? "Der Merge-Commit wird erstellt …" : "Creating the merge commit …";
|
||||||
await continueMerge();
|
await continueMerge();
|
||||||
activeView = "review-center";
|
activeView = "review-center";
|
||||||
|
await tick();
|
||||||
if (!errorMessage && !mergeInProgress) {
|
if (!errorMessage && !mergeInProgress) {
|
||||||
reviewConflictPhase = "ready-to-push";
|
reviewConflictPhase = "ready-to-push";
|
||||||
reviewConflictMessage = appLanguage === "de" ? "Der Merge ist abgeschlossen. Pushe den Quellbranch, damit der PR aktualisiert wird." : "The merge is complete. Push the source branch to update the pull request.";
|
reviewConflictMessage = appLanguage === "de" ? "Der Merge ist abgeschlossen. Pushe den Quellbranch, damit der PR aktualisiert wird." : "The merge is complete. Push the source branch to update the pull request.";
|
||||||
|
await tick();
|
||||||
|
await pushReviewConflictResolution();
|
||||||
|
} else if (errorMessage) {
|
||||||
|
reviewConflictMessage = appLanguage === "de" ? `Der Merge-Commit konnte nicht erstellt werden: ${errorMessage}` : `The merge commit could not be created: ${errorMessage}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5273,6 +5281,7 @@
|
|||||||
async function applyPreparedResolutions() {
|
async function applyPreparedResolutions() {
|
||||||
if (!activeRepoPath || isBusy || Object.keys(preparedResolutions).length === 0) return;
|
if (!activeRepoPath || isBusy || Object.keys(preparedResolutions).length === 0) return;
|
||||||
const entries = Object.entries(preparedResolutions);
|
const entries = Object.entries(preparedResolutions);
|
||||||
|
let completeReviewMerge = false;
|
||||||
await runOperation(`Resolving ${entries.length} ${entries.length === 1 ? "file" : "files"}`, async () => {
|
await runOperation(`Resolving ${entries.length} ${entries.length === 1 ? "file" : "files"}`, async () => {
|
||||||
let nextStatus: GitStatus | null = null;
|
let nextStatus: GitStatus | null = null;
|
||||||
for (const [file, prepared] of entries) {
|
for (const [file, prepared] of entries) {
|
||||||
@@ -5291,6 +5300,12 @@
|
|||||||
resolveDialogOpen = false;
|
resolveDialogOpen = false;
|
||||||
conflict = null;
|
conflict = null;
|
||||||
conflictTarget = "";
|
conflictTarget = "";
|
||||||
|
completeReviewMerge = Boolean(
|
||||||
|
reviewConflictRequestId
|
||||||
|
&& reviewConflictRepoPath
|
||||||
|
&& sameRepoPath(activeRepoPath, reviewConflictRepoPath)
|
||||||
|
&& nextStatus?.merge_in_progress,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await loadConflict(remaining[0].path);
|
await loadConflict(remaining[0].path);
|
||||||
}
|
}
|
||||||
@@ -5299,6 +5314,12 @@
|
|||||||
remaining: remaining.length,
|
remaining: remaining.length,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (completeReviewMerge && !errorMessage) {
|
||||||
|
reviewConflictPhase = "ready-to-continue";
|
||||||
|
activeView = "review-center";
|
||||||
|
await tick();
|
||||||
|
await continueReviewConflictMerge();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Event handlers ─────────────────────────────────────────────────────────
|
// ── Event handlers ─────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
let collapsedRepositories = $state(new Set<string>());
|
let collapsedRepositories = $state(new Set<string>());
|
||||||
let loadGeneration = 0;
|
let loadGeneration = 0;
|
||||||
let loadedStates = $state(new Set<"open" | "merged" | "closed">());
|
let loadedStates = $state(new Set<"open" | "merged" | "closed">());
|
||||||
|
let completedResolutionRefreshKey = "";
|
||||||
|
|
||||||
const SOURCE_TIMEOUT_MS = 46_000;
|
const SOURCE_TIMEOUT_MS = 46_000;
|
||||||
const de = $derived(language === "de");
|
const de = $derived(language === "de");
|
||||||
@@ -81,6 +82,15 @@
|
|||||||
return () => window.removeEventListener("click", closeActionMenu);
|
return () => window.removeEventListener("click", closeActionMenu);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const key = localResolutionPhase === "complete" && localResolutionRequestId && selectedSourceId
|
||||||
|
? `${selectedSourceId}:${localResolutionRequestId}`
|
||||||
|
: "";
|
||||||
|
if (!key || key === completedResolutionRefreshKey) return;
|
||||||
|
completedResolutionRefreshKey = key;
|
||||||
|
void refreshCompletedResolution(key, localResolutionRequestId, selectedSourceId);
|
||||||
|
});
|
||||||
|
|
||||||
function withTimeout<T>(promise: Promise<T>, source: string, timeoutMs = SOURCE_TIMEOUT_MS): Promise<T> {
|
function withTimeout<T>(promise: Promise<T>, source: string, timeoutMs = SOURCE_TIMEOUT_MS): Promise<T> {
|
||||||
return new Promise<T>((resolve, reject) => {
|
return new Promise<T>((resolve, reject) => {
|
||||||
const seconds = Math.round(timeoutMs / 1_000);
|
const seconds = Math.round(timeoutMs / 1_000);
|
||||||
@@ -246,12 +256,43 @@
|
|||||||
} finally { detailLoadingId = ""; }
|
} finally { detailLoadingId = ""; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshCompletedResolution(key: string, requestId: string, sourceId: string) {
|
||||||
|
const source = sources.find((candidate) => candidate.id === sourceId);
|
||||||
|
const initialRequest = requests.find((candidate) => candidate.id === requestId);
|
||||||
|
if (!source || !initialRequest) return;
|
||||||
|
let request: IntegrationReviewRequest = initialRequest;
|
||||||
|
actionNotice = de ? "Branch wurde gepusht. Der Merge-Status wird geprüft …" : "Branch pushed. Checking the merge status …";
|
||||||
|
try {
|
||||||
|
const credential = await withTimeout(loadCredential(integrationCredentialKey(source.provider, source.accountId)), `${source.label} keychain`, 15_000);
|
||||||
|
if (!credential?.password) return;
|
||||||
|
for (const delay of [700, 1_500, 3_000, 5_000]) {
|
||||||
|
await new Promise((resolve) => window.setTimeout(resolve, delay));
|
||||||
|
if (completedResolutionRefreshKey !== key || selectedSourceId !== sourceId) return;
|
||||||
|
const details: IntegrationReviewRequest = await withTimeout(getIntegrationReviewDetails(source.provider, source.baseUrl, credential.username, credential.password, request), source.label, 20_000);
|
||||||
|
requests = requests.map((candidate) => candidate.id === details.id ? details : candidate);
|
||||||
|
request = details;
|
||||||
|
if (details.mergeStatus === "mergeable") {
|
||||||
|
actionNotice = de ? "Der Konflikt ist gelöst. Der Request kann jetzt zusammengeführt werden." : "The conflict is resolved. The request can now be merged.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (details.mergeStatus !== "conflicts" && details.mergeStatus !== "checking") return;
|
||||||
|
}
|
||||||
|
actionNotice = de ? "Der Anbieter prüft den neuen Branch noch. Aktualisiere den Status in einigen Sekunden erneut." : "The provider is still checking the updated branch. Refresh the status again in a few seconds.";
|
||||||
|
} catch (error) {
|
||||||
|
errors = [{ source: source.label, message: error instanceof Error ? error.message : String(error) }];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hasConflicts(request: IntegrationReviewRequest): boolean { return request.mergeStatus === "conflicts"; }
|
function hasConflicts(request: IntegrationReviewRequest): boolean { return request.mergeStatus === "conflicts"; }
|
||||||
|
|
||||||
function isLocalResolutionActive(request: IntegrationReviewRequest): boolean {
|
function isLocalResolutionActive(request: IntegrationReviewRequest): boolean {
|
||||||
return request.id === localResolutionRequestId && localResolutionPhase !== "idle";
|
return request.id === localResolutionRequestId && localResolutionPhase !== "idle";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isWaitingForResolvedStatus(request: IntegrationReviewRequest): boolean {
|
||||||
|
return isLocalResolutionActive(request) && localResolutionPhase === "complete" && hasConflicts(request);
|
||||||
|
}
|
||||||
|
|
||||||
async function startLocalResolution(request: IntegrationReviewRequest) {
|
async function startLocalResolution(request: IntegrationReviewRequest) {
|
||||||
const source = activeSource;
|
const source = activeSource;
|
||||||
if (!source || !request.sourceBranch || !request.targetBranch) return;
|
if (!source || !request.sourceBranch || !request.targetBranch) return;
|
||||||
@@ -262,6 +303,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runPrimaryAction(request: IntegrationReviewRequest) {
|
async function runPrimaryAction(request: IntegrationReviewRequest) {
|
||||||
|
if (isWaitingForResolvedStatus(request)) return;
|
||||||
if (hasConflicts(request)) await startLocalResolution(request);
|
if (hasConflicts(request)) await startLocalResolution(request);
|
||||||
else if (request.state === "open" || request.state === "draft") await performReviewAction(request, "merge");
|
else if (request.state === "open" || request.state === "draft") await performReviewAction(request, "merge");
|
||||||
else if (request.state === "closed") await performReviewAction(request, "reopen");
|
else if (request.state === "closed") await performReviewAction(request, "reopen");
|
||||||
@@ -269,6 +311,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function primaryActionLabel(request: IntegrationReviewRequest): string {
|
function primaryActionLabel(request: IntegrationReviewRequest): string {
|
||||||
|
if (isWaitingForResolvedStatus(request)) return de ? "Status wird geprüft" : "Checking status";
|
||||||
if (hasConflicts(request)) return de ? "Konflikt lösen" : "Resolve conflict";
|
if (hasConflicts(request)) return de ? "Konflikt lösen" : "Resolve conflict";
|
||||||
if (request.state === "open" || request.state === "draft") return de ? "Zusammenführen" : "Merge";
|
if (request.state === "open" || request.state === "draft") return de ? "Zusammenführen" : "Merge";
|
||||||
if (request.state === "closed") return de ? "Wieder öffnen" : "Reopen";
|
if (request.state === "closed") return de ? "Wieder öffnen" : "Reopen";
|
||||||
@@ -402,13 +445,13 @@
|
|||||||
<span class="repo-branch"><strong>{request.repositoryName}</strong>{#if request.sourceBranch && request.targetBranch}<span class="branch-route"><GitBranch size={11} /><code title={request.sourceBranch}>{request.sourceBranch}</code><b>→</b><code title={request.targetBranch}>{request.targetBranch}</code></span>{:else}<span class="branch-loading"><LoaderCircle class="spin" size={11} />{de ? "Branches werden geladen …" : "Loading branches …"}</span>{/if}</span>
|
<span class="repo-branch"><strong>{request.repositoryName}</strong>{#if request.sourceBranch && request.targetBranch}<span class="branch-route"><GitBranch size={11} /><code title={request.sourceBranch}>{request.sourceBranch}</code><b>→</b><code title={request.targetBranch}>{request.targetBranch}</code></span>{:else}<span class="branch-loading"><LoaderCircle class="spin" size={11} />{de ? "Branches werden geladen …" : "Loading branches …"}</span>{/if}</span>
|
||||||
<span class="row-actions">
|
<span class="row-actions">
|
||||||
<span class:merge-action={request.state === "open" || request.state === "draft"} class:conflict-action={hasConflicts(request)} class="provider-action">
|
<span class:merge-action={request.state === "open" || request.state === "draft"} class:conflict-action={hasConflicts(request)} class="provider-action">
|
||||||
<button class="provider-button" class:merge-primary={request.state === "open" || request.state === "draft"} class:conflict={hasConflicts(request)} disabled={!!actionBusyId || (hasConflicts(request) && (!request.sourceBranch || !request.targetBranch || (isLocalResolutionActive(request) && localResolutionPhase === "preparing")))} type="button" onclick={(event) => { event.stopPropagation(); void runPrimaryAction(request); }}>{#if actionBusyId === request.id || (isLocalResolutionActive(request) && localResolutionPhase === "preparing")}<LoaderCircle class="spin" size={12} />{:else if request.state === "open" || request.state === "draft"}<GitMerge size={12} />{:else}<ExternalLink size={12} />{/if}{primaryActionLabel(request)}</button>
|
<button class="provider-button" class:merge-primary={request.state === "open" || request.state === "draft"} class:conflict={hasConflicts(request)} disabled={!!actionBusyId || isWaitingForResolvedStatus(request) || (hasConflicts(request) && (!request.sourceBranch || !request.targetBranch || (isLocalResolutionActive(request) && localResolutionPhase === "preparing")))} type="button" onclick={(event) => { event.stopPropagation(); void runPrimaryAction(request); }}>{#if actionBusyId === request.id || isWaitingForResolvedStatus(request) || (isLocalResolutionActive(request) && localResolutionPhase === "preparing")}<LoaderCircle class="spin" size={12} />{:else if request.state === "open" || request.state === "draft"}<GitMerge size={12} />{:else}<ExternalLink size={12} />{/if}{primaryActionLabel(request)}</button>
|
||||||
<button class="action-toggle" class:active={actionMenuId === request.id} type="button" aria-label={de ? "Weitere Aktionen" : "More actions"} onclick={(event) => { event.stopPropagation(); actionMenuId = actionMenuId === request.id ? "" : request.id; }}><ChevronDown size={13} /></button>
|
<button class="action-toggle" class:active={actionMenuId === request.id} type="button" aria-label={de ? "Weitere Aktionen" : "More actions"} onclick={(event) => { event.stopPropagation(); actionMenuId = actionMenuId === request.id ? "" : request.id; }}><ChevronDown size={13} /></button>
|
||||||
{#if actionMenuId === request.id}
|
{#if actionMenuId === request.id}
|
||||||
<span class="action-menu" role="menu" tabindex="-1">
|
<span class="action-menu" role="menu" tabindex="-1">
|
||||||
<button type="button" role="menuitem" onclick={() => openDetail(request)}><PanelRightOpen size={13} />{de ? "Request prüfen" : "Review request"}</button>
|
<button type="button" role="menuitem" onclick={() => openDetail(request)}><PanelRightOpen size={13} />{de ? "Request prüfen" : "Review request"}</button>
|
||||||
{#if request.state === "open" || request.state === "draft"}
|
{#if request.state === "open" || request.state === "draft"}
|
||||||
{#if hasConflicts(request)}<button type="button" role="menuitem" disabled={!request.sourceBranch || !request.targetBranch || (isLocalResolutionActive(request) && localResolutionPhase === "preparing")} onclick={() => void startLocalResolution(request)}><GitMerge size={13} />{de ? "Konflikt lokal lösen" : "Resolve conflict locally"}</button>{:else}<button type="button" role="menuitem" disabled={actionBusyId === request.id} onclick={() => void performReviewAction(request, "merge")}><GitMerge size={13} />{reviewActionLabel("merge")}</button>{/if}
|
{#if isWaitingForResolvedStatus(request)}<button type="button" role="menuitem" disabled><LoaderCircle class="spin" size={13} />{de ? "Merge-Status wird geprüft" : "Checking merge status"}</button>{:else if hasConflicts(request)}<button type="button" role="menuitem" disabled={!request.sourceBranch || !request.targetBranch || (isLocalResolutionActive(request) && localResolutionPhase === "preparing")} onclick={() => void startLocalResolution(request)}><GitMerge size={13} />{de ? "Konflikt lokal lösen" : "Resolve conflict locally"}</button>{:else}<button type="button" role="menuitem" disabled={actionBusyId === request.id} onclick={() => void performReviewAction(request, "merge")}><GitMerge size={13} />{reviewActionLabel("merge")}</button>{/if}
|
||||||
<button type="button" role="menuitem" disabled={actionBusyId === request.id} onclick={() => void performReviewAction(request, "approve")}><Check size={13} />{reviewActionLabel("approve")}</button>
|
<button type="button" role="menuitem" disabled={actionBusyId === request.id} onclick={() => void performReviewAction(request, "approve")}><Check size={13} />{reviewActionLabel("approve")}</button>
|
||||||
<button class="danger" type="button" role="menuitem" disabled={actionBusyId === request.id} onclick={() => void performReviewAction(request, "close")}><XCircle size={13} />{reviewActionLabel("close")}</button>
|
<button class="danger" type="button" role="menuitem" disabled={actionBusyId === request.id} onclick={() => void performReviewAction(request, "close")}><XCircle size={13} />{reviewActionLabel("close")}</button>
|
||||||
{:else if request.state === "closed"}
|
{:else if request.state === "closed"}
|
||||||
@@ -440,7 +483,7 @@
|
|||||||
<section class="description"><h3>{de ? "Beschreibung" : "Description"}</h3><p class:muted={!selected.description}>{selected.description || (de ? "Keine Beschreibung vorhanden." : "No description provided.")}</p></section>
|
<section class="description"><h3>{de ? "Beschreibung" : "Description"}</h3><p class:muted={!selected.description}>{selected.description || (de ? "Keine Beschreibung vorhanden." : "No description provided.")}</p></section>
|
||||||
<section class="comments-section">
|
<section class="comments-section">
|
||||||
<header><h3>{de ? "Kommentare" : "Comments"}</h3><span>{selected.comments?.length ?? 0}</span></header>
|
<header><h3>{de ? "Kommentare" : "Comments"}</h3><span>{selected.comments?.length ?? 0}</span></header>
|
||||||
<div class:conflict={hasConflicts(selected)} class="merge-summary">{#if detailLoadingId === selected.id}<LoaderCircle class="spin" size={14} /><span>{de ? "Merge-Status wird geladen …" : "Loading merge status …"}</span>{:else if hasConflicts(selected)}<AlertTriangle size={14} /><strong>{de ? "Dieser Request hat Merge-Konflikte." : "This request has merge conflicts."}</strong>{:else}<Check size={14} /><span>{de ? "Dieser Branch hat keine erkannten Konflikte mit dem Zielbranch." : "This branch has no detected conflicts with the base branch."}</span>{/if}</div>
|
<div class:conflict={hasConflicts(selected) && !isWaitingForResolvedStatus(selected)} class="merge-summary">{#if detailLoadingId === selected.id || isWaitingForResolvedStatus(selected)}<LoaderCircle class="spin" size={14} /><span>{de ? "Der Anbieter prüft den gelösten Branch …" : "The provider is checking the resolved branch …"}</span>{:else if hasConflicts(selected)}<AlertTriangle size={14} /><strong>{de ? "Dieser Request hat Merge-Konflikte." : "This request has merge conflicts."}</strong>{:else}<Check size={14} /><span>{de ? "Dieser Branch hat keine erkannten Konflikte mit dem Zielbranch." : "This branch has no detected conflicts with the base branch."}</span>{/if}</div>
|
||||||
{#if hasConflicts(selected) || isLocalResolutionActive(selected)}
|
{#if hasConflicts(selected) || isLocalResolutionActive(selected)}
|
||||||
<div class:error={localResolutionPhase === "error"} class:complete={localResolutionPhase === "complete"} class="local-resolution">
|
<div class:error={localResolutionPhase === "error"} class:complete={localResolutionPhase === "complete"} class="local-resolution">
|
||||||
<div>{#if localResolutionPhase === "preparing"}<LoaderCircle class="spin" size={14} />{:else if localResolutionPhase === "complete"}<Check size={14} />{:else}<GitMerge size={14} />{/if}<span>{isLocalResolutionActive(selected) && localResolutionMessage ? localResolutionMessage : (de ? "Löse den Konflikt mit dem lokalen Repository und Gittys Konflikt-Editor." : "Resolve this conflict with the local repository and Gitty's conflict editor.")}</span></div>
|
<div>{#if localResolutionPhase === "preparing"}<LoaderCircle class="spin" size={14} />{:else if localResolutionPhase === "complete"}<Check size={14} />{:else}<GitMerge size={14} />{/if}<span>{isLocalResolutionActive(selected) && localResolutionMessage ? localResolutionMessage : (de ? "Löse den Konflikt mit dem lokalen Repository und Gittys Konflikt-Editor." : "Resolve this conflict with the local repository and Gitty's conflict editor.")}</span></div>
|
||||||
@@ -457,7 +500,7 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<aside class="detail-sidebar">
|
<aside class="detail-sidebar">
|
||||||
<div class="detail-actions">{#if selected.state === "open" || selected.state === "draft"}{#if hasConflicts(selected)}<button class="detail-action danger-action" type="button" disabled={isLocalResolutionActive(selected) && localResolutionPhase === "preparing"} onclick={() => void startLocalResolution(selected)}><GitMerge size={14} />{de ? "Konflikt lösen" : "Resolve conflict"}</button>{:else}<button class="detail-action primary-action" type="button" disabled={!!actionBusyId} onclick={() => void performReviewAction(selected, "merge")}><GitMerge size={14} />{reviewActionLabel("merge")}</button>{/if}<button class="detail-action" type="button" disabled={!!actionBusyId} onclick={() => void performReviewAction(selected, "approve")}><Check size={14} />{reviewActionLabel("approve")}</button><button class="detail-action danger-action" type="button" disabled={!!actionBusyId} onclick={() => void performReviewAction(selected, "close")}><XCircle size={14} />{reviewActionLabel("close")}</button>{:else if selected.state === "closed"}<button class="detail-action primary-action" type="button" disabled={!!actionBusyId} onclick={() => void performReviewAction(selected, "reopen")}><RotateCcw size={14} />{reviewActionLabel("reopen")}</button>{/if}<button class="detail-action" type="button" onclick={() => void openRequest()} disabled={!selected.webUrl}><ExternalLink size={14} />{actionLabel(selected.provider)}</button></div>
|
<div class="detail-actions">{#if selected.state === "open" || selected.state === "draft"}{#if isWaitingForResolvedStatus(selected)}<button class="detail-action" type="button" disabled><LoaderCircle class="spin" size={14} />{de ? "Status wird geprüft" : "Checking status"}</button>{:else if hasConflicts(selected)}<button class="detail-action danger-action" type="button" disabled={isLocalResolutionActive(selected) && localResolutionPhase === "preparing"} onclick={() => void startLocalResolution(selected)}><GitMerge size={14} />{de ? "Konflikt lösen" : "Resolve conflict"}</button>{:else}<button class="detail-action primary-action" type="button" disabled={!!actionBusyId} onclick={() => void performReviewAction(selected, "merge")}><GitMerge size={14} />{reviewActionLabel("merge")}</button>{/if}<button class="detail-action" type="button" disabled={!!actionBusyId} onclick={() => void performReviewAction(selected, "approve")}><Check size={14} />{reviewActionLabel("approve")}</button><button class="detail-action danger-action" type="button" disabled={!!actionBusyId} onclick={() => void performReviewAction(selected, "close")}><XCircle size={14} />{reviewActionLabel("close")}</button>{:else if selected.state === "closed"}<button class="detail-action primary-action" type="button" disabled={!!actionBusyId} onclick={() => void performReviewAction(selected, "reopen")}><RotateCcw size={14} />{reviewActionLabel("reopen")}</button>{/if}<button class="detail-action" type="button" onclick={() => void openRequest()} disabled={!selected.webUrl}><ExternalLink size={14} />{actionLabel(selected.provider)}</button></div>
|
||||||
<section class="people-section"><header><h3>{de ? "Teilnehmer" : "Participants"}</h3></header><div class="people compact"><span class="avatar">{initials(selected.author)}</span></div></section>
|
<section class="people-section"><header><h3>{de ? "Teilnehmer" : "Participants"}</h3></header><div class="people compact"><span class="avatar">{initials(selected.author)}</span></div></section>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user