dsh-git-branch-manage 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +1204 -54
- package/lib/credentials.d.ts +144 -0
- package/lib/credentials.js +256 -0
- package/lib/git.d.ts +93 -4
- package/lib/git.js +147 -6
- package/lib/index.js +23 -2
- package/lib/normalize.d.ts +24 -0
- package/lib/normalize.js +47 -0
- package/lib/oauth.d.ts +109 -0
- package/lib/oauth.js +182 -0
- package/lib/routes.d.ts +82 -2
- package/lib/routes.js +413 -8
- package/lib/settings.d.ts +9 -0
- package/lib/settings.js +12 -2
- package/lib/tool.d.ts +82 -0
- package/lib/tool.js +133 -0
- package/lib/wire.d.ts +151 -0
- package/lib/wire.js +23 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -129,6 +129,25 @@ window.__ModuleLoader__.load({
|
|
|
129
129
|
const ROUTE_COMMIT_FILES = `${ROUTE_PREFIX}/commit-files`;
|
|
130
130
|
/** POST ROUTE_PREFIX/tag — create a tag at an existing commit. */
|
|
131
131
|
const ROUTE_TAG = `${ROUTE_PREFIX}/tag`;
|
|
132
|
+
/** POST ROUTE_PREFIX/tag-push — push ONE existing local tag to its remote. */
|
|
133
|
+
const ROUTE_TAG_PUSH = `${ROUTE_PREFIX}/tag-push`;
|
|
134
|
+
/** POST ROUTE_PREFIX/credentials — the global per-host credentials, secrets
|
|
135
|
+
* masked (the stored secret never leaves the host). */
|
|
136
|
+
const ROUTE_CREDENTIALS = `${ROUTE_PREFIX}/credentials`;
|
|
137
|
+
/** POST ROUTE_PREFIX/credentials-save — create or update one host credential. */
|
|
138
|
+
const ROUTE_CREDENTIALS_SAVE = `${ROUTE_PREFIX}/credentials-save`;
|
|
139
|
+
/** POST ROUTE_PREFIX/credentials-delete — forget one host credential. */
|
|
140
|
+
const ROUTE_CREDENTIALS_DELETE = `${ROUTE_PREFIX}/credentials-delete`;
|
|
141
|
+
/** POST ROUTE_PREFIX/credentials-oauth-start — begin a browser sign-in for
|
|
142
|
+
* one host (GitHub device flow); answers the one-time user code. */
|
|
143
|
+
const ROUTE_CREDENTIALS_OAUTH_START = `${ROUTE_PREFIX}/credentials-oauth-start`;
|
|
144
|
+
/** POST ROUTE_PREFIX/credentials-oauth-poll — poll a started browser sign-in;
|
|
145
|
+
* stores the token and answers the fresh masked list once the user approves. */
|
|
146
|
+
const ROUTE_CREDENTIALS_OAUTH_POLL = `${ROUTE_PREFIX}/credentials-oauth-poll`;
|
|
147
|
+
/** POST ROUTE_PREFIX/remotes — the repository's configured remotes. */
|
|
148
|
+
const ROUTE_REMOTES = `${ROUTE_PREFIX}/remotes`;
|
|
149
|
+
/** POST ROUTE_PREFIX/remote — add, re-point or remove ONE remote. */
|
|
150
|
+
const ROUTE_REMOTE = `${ROUTE_PREFIX}/remote`;
|
|
132
151
|
/** POST ROUTE_PREFIX/ai-commit-message — one-shot LLM commit message from
|
|
133
152
|
* the staged diff (mounted only where the host provides the llm service). */
|
|
134
153
|
const ROUTE_AI_COMMIT_MESSAGE = `${ROUTE_PREFIX}/ai-commit-message`;
|
|
@@ -136,6 +155,10 @@ window.__ModuleLoader__.load({
|
|
|
136
155
|
* bodies and the client caps its input at the same value, so the two ends
|
|
137
156
|
* can never disagree. */
|
|
138
157
|
const COMMIT_MESSAGE_LIMIT = 2e3;
|
|
158
|
+
/** Hosts whose browser sign-in the credential dialog offers. Mirrors the
|
|
159
|
+
* host-side device-flow support (`src/oauth.ts`); a shared constant keeps the
|
|
160
|
+
* browser list from drifting away from what the host can actually do. */
|
|
161
|
+
const BROWSER_SIGN_IN_HOSTS = ["github.com"];
|
|
139
162
|
//#endregion
|
|
140
163
|
//#region src/client/api.ts
|
|
141
164
|
/** POST one JSON body and parse the uniform envelope. */
|
|
@@ -433,6 +456,72 @@ window.__ModuleLoader__.load({
|
|
|
433
456
|
});
|
|
434
457
|
}
|
|
435
458
|
/**
|
|
459
|
+
* Push ONE local tag to its remote (the host resolves which remote). Unlike
|
|
460
|
+
* {@link requestPush}, which sends the current branch alone.
|
|
461
|
+
* @param repoPath - absolute directory inside the repository.
|
|
462
|
+
* @param name - an existing local tag name (verified host-side).
|
|
463
|
+
*/
|
|
464
|
+
function requestPushTag(repoPath, name) {
|
|
465
|
+
return post(ROUTE_TAG_PUSH, {
|
|
466
|
+
repoPath,
|
|
467
|
+
name
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Every stored GLOBAL credential, secrets masked. One row per host: a token
|
|
472
|
+
* saved for github.com serves every repository that talks to github.com.
|
|
473
|
+
*/
|
|
474
|
+
function requestCredentials() {
|
|
475
|
+
return post(ROUTE_CREDENTIALS, {});
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Create or update the credential of ONE host. A blank `secret` on an
|
|
479
|
+
* existing host keeps the stored secret.
|
|
480
|
+
* @param body - `{ host, username, method, secret? }`.
|
|
481
|
+
*/
|
|
482
|
+
function requestSaveCredential(body) {
|
|
483
|
+
return post(ROUTE_CREDENTIALS_SAVE, body);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Forget ONE host's credential.
|
|
487
|
+
* @param body - `{ host }`.
|
|
488
|
+
*/
|
|
489
|
+
function requestDeleteCredential(body) {
|
|
490
|
+
return post(ROUTE_CREDENTIALS_DELETE, body);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Start a browser sign-in for ONE host (GitHub device flow). The answer
|
|
494
|
+
* carries the one-time user code the dialog shows and the verification page
|
|
495
|
+
* it opens.
|
|
496
|
+
* @param body - `{ host }`.
|
|
497
|
+
*/
|
|
498
|
+
function requestOAuthStart(body) {
|
|
499
|
+
return post(ROUTE_CREDENTIALS_OAUTH_START, body);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Poll a started browser sign-in. Resolves `{ status: 'pending' }` until the
|
|
503
|
+
* user approves; then the token is stored host-side and the fresh masked
|
|
504
|
+
* list comes back (the token itself never reaches the browser).
|
|
505
|
+
* @param body - `{ host, deviceCode }`.
|
|
506
|
+
*/
|
|
507
|
+
function requestOAuthPoll(body) {
|
|
508
|
+
return post(ROUTE_CREDENTIALS_OAUTH_POLL, body);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The repository's configured remotes (fetch/push URLs).
|
|
512
|
+
* @param repoPath - absolute directory inside the repository.
|
|
513
|
+
*/
|
|
514
|
+
function requestRemotes(repoPath) {
|
|
515
|
+
return post(ROUTE_REMOTES, { repoPath });
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Add, re-point or remove one remote; resolves with the fresh list.
|
|
519
|
+
* @param body - `{ repoPath, action, name, url? }`.
|
|
520
|
+
*/
|
|
521
|
+
function requestMutateRemote(body) {
|
|
522
|
+
return post(ROUTE_REMOTE, body);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
436
525
|
* Generate a commit message from the checkout's diff with the requesting
|
|
437
526
|
* session's own model (a one-shot host llm call).
|
|
438
527
|
* @param body - the repo path plus the session's model selection and an
|
|
@@ -3635,6 +3724,14 @@ window.__ModuleLoader__.load({
|
|
|
3635
3724
|
return badges;
|
|
3636
3725
|
}
|
|
3637
3726
|
/**
|
|
3727
|
+
* The TAG names one commit's decoration carries, in decoration order — the
|
|
3728
|
+
* per-tag publish targets the commit menu offers. Built on
|
|
3729
|
+
* {@link classifyRefs} so "what counts as a tag" has exactly one definition.
|
|
3730
|
+
*/
|
|
3731
|
+
function commitTags(refs) {
|
|
3732
|
+
return classifyRefs(refs).filter((badge) => badge.kind === "tag").map((badge) => badge.text);
|
|
3733
|
+
}
|
|
3734
|
+
/**
|
|
3638
3735
|
* Split the working-tree rows into the two panel groups. Untracked files
|
|
3639
3736
|
* render in the unstaged group as `?` (git add still stages them); an
|
|
3640
3737
|
* unmerged conflict renders in the unstaged group as `U` unchecked (git add
|
|
@@ -3847,7 +3944,7 @@ window.__ModuleLoader__.load({
|
|
|
3847
3944
|
}
|
|
3848
3945
|
//#endregion
|
|
3849
3946
|
//#region \0git-worktree-css:/home/runner/work/dsh-git-branch/dsh-git-branch/src/client/GitHistoryPanel.module.css?inline
|
|
3850
|
-
const css$2 = "/* Git panel (sidebar global panel). Three stacked sections over the main\n * column: uncommitted changes + commit box, branches, commit history. Host\n * tokens only; the few status colors fall back to the same literals the\n * settings card uses. */\n\n.page_427795137 {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n}\n\n.topbar_427795137 {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 16px 8px;\n}\n\n.back_427795137 {\n flex: none;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n border: 0;\n background: none;\n cursor: pointer;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n padding: 4px 8px;\n border-radius: 8px;\n}\n\n.back_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.title_427795137 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.repoPill_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 999px;\n padding: 3px 10px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.repoName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.branchName_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.track_427795137 {\n font-variant-numeric: tabular-nums;\n}\n\n.topbarActions_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n\n.refreshBtn_427795137 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 0;\n border-radius: 8px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.refreshBtn_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.refreshBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n/* Cn-accelerate: the topbar 国内加速 button and its anchored popover. The\n * button's on-state is brand tint + brand text + green dot; the popover\n * mirrors the settings card's switch/input affordances over this panel's\n * tokens (popup chrome follows .menu_427795137 above). */\n\n.cnWrap_427795137 {\n position: relative;\n display: inline-flex;\n}\n\n.cnBtn_427795137,\n.cnBtnOn_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n height: 28px;\n padding: 0 10px;\n border: 0;\n border-radius: 8px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n}\n\n.cnBtn_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.cnBtnOn_427795137 {\n background: rgba(76, 107, 245, 0.16);\n color: var(--dsw-alias-brand-primary);\n}\n\n.cnBtnOn_427795137:hover {\n background: rgba(76, 107, 245, 0.22);\n color: var(--dsw-alias-brand-primary);\n}\n\n.cnDot_427795137 {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--dsw-alias-status-success, #218358);\n}\n\n.cnPop_427795137 {\n position: absolute;\n right: 0;\n top: calc(100% + 6px);\n z-index: 40;\n width: 320px;\n background: var(--dsw-alias-surface-raised, var(--dsw-alias-bg-layer-3));\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n box-shadow: var(--dsw-shadow-lv3);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.cnPopHead_427795137 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.cnPopTitle_427795137 {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.cnSwitchRow_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.cnSwitch_427795137 {\n width: 16px;\n height: 16px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.cnSwitch_427795137:disabled {\n cursor: default;\n}\n\n.cnHint_427795137 {\n margin: 0;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.cnFieldHead_427795137 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n}\n\n.cnFieldLabel_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.cnLink_427795137 {\n border: 0;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.cnLink_427795137:hover {\n text-decoration: underline;\n}\n\n.cnLink_427795137:disabled {\n opacity: 0.5;\n cursor: default;\n text-decoration: none;\n}\n\n.cnInput_427795137,\n.cnInputBad_427795137 {\n box-sizing: border-box;\n width: 100%;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n padding: 6px 8px;\n outline: none;\n}\n\n.cnInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.cnInputBad_427795137,\n.cnInputBad_427795137:focus {\n border-color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.cnError_427795137 {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.cnFooter_427795137 {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n margin-top: 2px;\n}\n\n.cnGhost_427795137,\n.cnSave_427795137 {\n border: 0;\n border-radius: 6px;\n font: inherit;\n font-size: 12px;\n padding: 5px 12px;\n cursor: pointer;\n}\n\n.cnGhost_427795137 {\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n.cnGhost_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.cnSave_427795137 {\n background: var(--dsw-alias-brand-primary);\n color: #fff;\n}\n\n.cnSave_427795137:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.scroll_427795137 {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 0 16px 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* The page column below the topbar is a definite-height flex column and the\n * three cards split it 2:1:2 (uncommitted : branches : history) — five\n * shares, the topbar and gaps taking their natural height first. Each body\n * scrolls INSIDE its share — min-height: 0 is what lets a flex child shrink\n * below its content (the old natural-height model painted overflowing rows\n * over the sections below). A floor keeps a very short panel from\n * collapsing a card to its bare head; past the floor the column itself\n * scrolls. */\n.section_427795137 {\n border: 0.5px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.sectionFill_427795137 {\n flex: 1 1 0;\n min-height: 84px;\n}\n\n/* The three flex-grow overrides of the 2:1:2 split. */\n.sectionChanges_427795137 {\n flex-grow: 2;\n}\n\n.sectionBranches_427795137 {\n flex-grow: 1;\n}\n\n.sectionHistory_427795137 {\n flex-grow: 2;\n}\n\n.sectionHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.sectionTitle_427795137 {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.countBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 999px;\n padding: 3px 7px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.sectionTools_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.sectionBody_427795137 {\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* The branch and history bodies scroll as a whole inside their share. */\n.sectionBodyScroll_427795137 {\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* Inside the uncommitted card only the file lists scroll; the commit box\n * below stays pinned however long the change tree is. */\n.changeList_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* The 全部展开/全部折叠 text chips in the branch card head. */\n.treeToggle_427795137 {\n background: none;\n border: 0;\n padding: 2px 5px;\n font: inherit;\n font-size: 11.5px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n border-radius: 5px;\n white-space: nowrap;\n}\n\n.treeToggle_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-brand-primary);\n}\n\n/* Small action chip used across the section heads and the commit row. */\n.act_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n cursor: pointer;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 4px 10px;\n white-space: nowrap;\n}\n\n.act_427795137:hover:not([disabled]) {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.act_427795137[disabled] {\n opacity: 0.45;\n cursor: default;\n}\n\n.actPrimary_427795137 {\n /* Hardcoded blue by request — the brand token does not resolve reliably\n * inside the better-sidebar panel. */\n background: #4c6bf5;\n border-color: transparent;\n color: #fff;\n}\n\n.actPrimary_427795137:hover:not([disabled]) {\n border-color: transparent;\n filter: brightness(1.06);\n}\n\n/* Uncommitted changes */\n\n.groupTitleRow_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.linkBtn_427795137 {\n margin-left: auto;\n background: none;\n border: 0;\n padding: 2px 4px;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n border-radius: 5px;\n}\n\n.linkBtn_427795137:hover:not([disabled]) {\n text-decoration: underline;\n}\n\n.linkBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.changeRow_427795137 {\n display: grid;\n grid-template-columns: 16px 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.changeRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.changeCheck_427795137 {\n width: 14px;\n height: 14px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n margin: 0;\n}\n\n.statusBadge_427795137 {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n color: #fff;\n box-sizing: border-box;\n}\n\n.statusA_427795137 { background: var(--dsw-alias-status-success, #218358); }\n.statusM_427795137 { background: var(--dsw-alias-label-warning, #b07000); }\n.statusD_427795137 { background: var(--dsw-alias-status-danger, #d5304f); }\n.statusR_427795137 { background: #8350d8; }\n.statusC_427795137 { background: #8350d8; }\n.statusT_427795137 { background: #5b7a9d; }\n.statusQ_427795137 { background: var(--dsw-alias-label-dimmed, #9aa0aa); }\n.statusU_427795137 { background: #d5502e; }\n\n.changePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.stageHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n flex: none;\n}\n\n.commitBox_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n flex: none;\n}\n\n.commitInput_427795137 {\n width: 100%;\n box-sizing: border-box;\n resize: vertical;\n min-height: 52px;\n font: inherit;\n font-size: 12.5px;\n line-height: 1.5;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.commitInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.commitActions_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.commitHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n min-height: 16px;\n}\n\n.split_427795137 {\n margin-left: auto;\n display: flex;\n position: relative;\n}\n\n.splitMain_427795137 {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 16px;\n font-size: 13px;\n}\n\n.splitCaret_427795137 {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n margin-left: 1px;\n padding: 6px 6px;\n}\n\n.menu_427795137 {\n position: absolute;\n right: 0;\n top: calc(100% + 4px);\n z-index: 40;\n width: max-content;\n min-width: 0;\n background: var(--dsw-alias-surface-raised, var(--dsw-alias-bg-layer-3));\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n box-shadow: var(--dsw-shadow-lv3);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n padding: 3px;\n display: flex;\n flex-direction: column;\n}\n\n.menuItem_427795137 {\n display: block;\n width: 100%;\n text-align: left;\n background: none;\n border: 0;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n padding: 5px 10px;\n border-radius: 5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.menuItem_427795137:hover:not([disabled]) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.menuItem_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.menuSub_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n margin-top: 1px;\n}\n\n/* Branch section — two IDEA-style trees: 本地 → branches, 远程 → per-remote → branches */\n\n.treeHead_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n\n.treeHead_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.treeChevron_427795137 {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.treeChildren_427795137 {\n display: flex;\n flex-direction: column;\n margin-left: 12px;\n}\n\n.treeHeadNest_427795137 {\n font-weight: 400;\n}\n\n.treeNest_427795137 {\n margin-left: 12px;\n}\n\n.branchRow_427795137 {\n display: grid;\n grid-template-columns: 20px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 5px 8px;\n border-radius: 7px;\n /* The action menu opens on RIGHT-CLICK (Enter/Space for keyboard) — an\n * arrow, not the pointing hand a left-click affordance would promise. */\n cursor: default;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.branchRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Whole-row highlight for the current branch: the ✓ alone read as too\n * quiet inside the tree. A translucent brand-blue wash plus the inset left\n * accent and a colored name survives both themes; #4c6bf5 is the same\n * hardcoded blue actPrimary uses (the brand token does not resolve\n * reliably inside the better-sidebar panel). */\n.branchRowCurrent_427795137 {\n background: rgba(76, 107, 245, 0.16);\n box-shadow: inset 2px 0 0 #4c6bf5;\n cursor: default;\n}\n\n.branchRowCurrent_427795137:hover {\n background: rgba(76, 107, 245, 0.16);\n}\n\n.branchRowCurrent_427795137 .branchName_427795137 {\n color: #4c6bf5;\n font-weight: 600;\n}\n\n.branchMark_427795137 {\n color: var(--dsw-alias-brand-primary);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.branchName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.branchKind_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n margin-left: 6px;\n}\n\n.branchTrack_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n flex: none;\n}\n\n/* The branch action menu is portaled; fixed positioning by row rect. */\n.floatMenu_427795137 {\n position: fixed;\n z-index: 60;\n min-width: 190px;\n}\n\n.menuHead_427795137 {\n padding: 6px 10px 5px;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n margin-bottom: 3px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.menuHeadStrong_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.menuDanger_427795137 {\n color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.formField_427795137 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.formLabel_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.formInput_427795137 {\n font: inherit;\n font-size: 13px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.formInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.formError_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-status-danger, #d5304f);\n min-height: 16px;\n}\n\n/* Commit history */\n\n.historyHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.searchBox_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1;\n max-width: 300px;\n min-width: 140px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 0 9px;\n height: 28px;\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-bg-layer-3);\n}\n\n/* The branch card's head variant: sits right of the title and grows toward\n * the tools instead of being right-aligned by `margin-left: auto`. The\n * overrides win because both selectors are single classes and this one is\n * declared later. */\n.searchBoxHead_427795137 {\n margin-left: 0;\n flex: 1 1 auto;\n max-width: 320px;\n min-width: 90px;\n}\n\n.searchInput_427795137 {\n flex: 1;\n min-width: 0;\n border: 0;\n background: none;\n outline: none;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.searchInput_427795137::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The head carries the title plus three filters (branch, author, time). The\n * row wraps at narrow widths rather than squeezing the date inputs into\n * unreadable slivers. */\n.historyFilters_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n justify-content: flex-end;\n}\n\n.filterSelect_427795137,\n.filterInput_427795137,\n.filterDate_427795137 {\n height: 26px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n padding: 0 6px;\n outline: none;\n}\n\n.filterSelect_427795137 {\n max-width: 150px;\n}\n\n.filterInput_427795137 {\n width: 120px;\n}\n\n.filterInput_427795137::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.filterDate_427795137 {\n width: 122px;\n color-scheme: light dark;\n}\n\n.filterDash_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n.commitList_427795137 {\n /* Keep the history body gap between blocks, never between graph rows.\n * A non-shrinking block also prevents a long history from compressing rows\n * inside the card's scrolling flex body. */\n flex: none;\n}\n\n.commitRow_427795137 {\n position: relative;\n display: grid;\n grid-template-columns: var(--graph-w, 22px) minmax(0, 1fr) auto;\n column-gap: 8px;\n align-items: center;\n padding: 0 8px 0 2px;\n cursor: pointer;\n border-radius: 7px;\n}\n\n.commitRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.commitRowOpen_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n.commitRowOpen_427795137:hover {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n/* The SVG fills the actual grid row, including the text cells' padding.\n * Its paths scale vertically; the separate dot keeps a constant radius. */\n.commitGraph_427795137 {\n position: relative;\n align-self: stretch;\n min-height: 44px;\n}\n\n.commitGraphSvg_427795137 {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n overflow: visible;\n}\n\n.commitDot_427795137 {\n position: absolute;\n top: 50%;\n width: 8px;\n height: 8px;\n box-sizing: border-box;\n border: 2px solid currentColor;\n border-radius: 50%;\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.commitExpandGraph_427795137 {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n display: block;\n pointer-events: none;\n}\n\n.commitMain_427795137 {\n padding: 6px 0;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.commitSubject_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.commitSubjectText_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.commitRowOpen_427795137 .commitSubjectText_427795137 {\n font-weight: 600;\n}\n\n.refBadges_427795137 {\n display: inline-flex;\n gap: 4px;\n flex: none;\n}\n\n.refBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 4px;\n padding: 3px 5px;\n font-weight: 500;\n white-space: nowrap;\n max-width: 140px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.refHead_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary-bluish);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refTag_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-warning, #b07000);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refRemote_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: #8350d8;\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refLocal_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-status-success, #218358);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.commitMeta_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n min-width: 0;\n}\n\n.commitAuthor_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.commitSide_427795137 {\n padding: 6px 0;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n flex: none;\n}\n\n/* The absolute stamp column: tabular figures so every row's digits line up\n * under the one above, which is what makes the column scannable. */\n.commitDate_427795137 {\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.commitHash_427795137 {\n font-family: ui-monospace, Consolas, monospace;\n}\n\n.commitExpand_427795137 {\n position: relative;\n grid-column: 1 / -1;\n /* Align the expanded file list with the subject column, whatever width the\n * graph band needs in this repository. */\n padding: 2px 4px 8px calc(var(--graph-w, 22px) + 8px);\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.commitExpandTitle_427795137 {\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n margin-bottom: 3px;\n}\n\n.fileRow_427795137 {\n display: grid;\n grid-template-columns: 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.fileRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.filePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.fileNums_427795137 {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n}\n\n.fileAdd_427795137 { color: var(--dsw-alias-status-success, #218358); }\n.fileDel_427795137 { color: var(--dsw-alias-status-danger, #d5304f); }\n\n.empty_427795137 {\n padding: 28px 16px;\n text-align: center;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12.5px;\n}\n\n.errorText_427795137 {\n padding: 20px 16px;\n text-align: center;\n color: var(--dsw-alias-status-danger, #d5304f);\n font-size: 12.5px;\n}\n\n.moreRow_427795137 {\n display: flex;\n justify-content: center;\n padding: 8px 0 2px;\n}\n";
|
|
3947
|
+
const css$2 = "/* Git panel (sidebar global panel). Three stacked sections over the main\n * column: uncommitted changes + commit box, branches, commit history. Host\n * tokens only; the few status colors fall back to the same literals the\n * settings card uses. */\n\n.page_427795137 {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n}\n\n.topbar_427795137 {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 16px 8px;\n}\n\n.back_427795137 {\n flex: none;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n border: 0;\n background: none;\n cursor: pointer;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n padding: 4px 8px;\n border-radius: 8px;\n}\n\n.back_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.title_427795137 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.repoPill_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 999px;\n padding: 3px 10px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.repoName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.branchName_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.track_427795137 {\n font-variant-numeric: tabular-nums;\n}\n\n.topbarActions_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n\n.refreshBtn_427795137 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 0;\n border-radius: 8px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.refreshBtn_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.refreshBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n/* Cn-accelerate: the topbar 国内加速 button and its anchored popover. The\n * button's on-state is brand tint + brand text + green dot; the popover\n * mirrors the settings card's switch/input affordances over this panel's\n * tokens (popup chrome follows .menu_427795137 above). */\n\n.cnWrap_427795137 {\n position: relative;\n display: inline-flex;\n}\n\n.cnBtn_427795137,\n.cnBtnOn_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n height: 28px;\n padding: 0 10px;\n border: 0;\n border-radius: 8px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n}\n\n.cnBtn_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.cnBtnOn_427795137 {\n background: rgba(76, 107, 245, 0.16);\n color: var(--dsw-alias-brand-primary);\n}\n\n.cnBtnOn_427795137:hover {\n background: rgba(76, 107, 245, 0.22);\n color: var(--dsw-alias-brand-primary);\n}\n\n.cnDot_427795137 {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--dsw-alias-status-success, #218358);\n}\n\n.cnPop_427795137 {\n position: absolute;\n right: 0;\n top: calc(100% + 6px);\n z-index: 40;\n width: 320px;\n background: var(--dsw-alias-surface-raised, var(--dsw-alias-bg-layer-3));\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n box-shadow: var(--dsw-shadow-lv3);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.cnPopHead_427795137 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.cnPopTitle_427795137 {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.cnSwitchRow_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.cnSwitch_427795137 {\n width: 16px;\n height: 16px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.cnSwitch_427795137:disabled {\n cursor: default;\n}\n\n.cnHint_427795137 {\n margin: 0;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.cnFieldHead_427795137 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n}\n\n.cnFieldLabel_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.cnLink_427795137 {\n border: 0;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.cnLink_427795137:hover {\n text-decoration: underline;\n}\n\n.cnLink_427795137:disabled {\n opacity: 0.5;\n cursor: default;\n text-decoration: none;\n}\n\n.cnInput_427795137,\n.cnInputBad_427795137 {\n box-sizing: border-box;\n width: 100%;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n padding: 6px 8px;\n outline: none;\n}\n\n.cnInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.cnInputBad_427795137,\n.cnInputBad_427795137:focus {\n border-color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.cnError_427795137 {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.cnFooter_427795137 {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n margin-top: 2px;\n}\n\n.cnGhost_427795137,\n.cnSave_427795137 {\n border: 0;\n border-radius: 6px;\n font: inherit;\n font-size: 12px;\n padding: 5px 12px;\n cursor: pointer;\n}\n\n.cnGhost_427795137 {\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n.cnGhost_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.cnSave_427795137 {\n background: var(--dsw-alias-brand-primary);\n color: #fff;\n}\n\n.cnSave_427795137:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.scroll_427795137 {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 0 16px 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* The page column below the topbar is a definite-height flex column and the\n * three cards split it 2:1:2 (uncommitted : branches : history) — five\n * shares, the topbar and gaps taking their natural height first. Each body\n * scrolls INSIDE its share — min-height: 0 is what lets a flex child shrink\n * below its content (the old natural-height model painted overflowing rows\n * over the sections below). A floor keeps a very short panel from\n * collapsing a card to its bare head; past the floor the column itself\n * scrolls. */\n.section_427795137 {\n border: 0.5px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.sectionFill_427795137 {\n flex: 1 1 0;\n min-height: 84px;\n}\n\n/* The three flex-grow overrides of the 2:1:2 split. */\n.sectionChanges_427795137 {\n flex-grow: 2;\n}\n\n.sectionBranches_427795137 {\n flex-grow: 1;\n}\n\n.sectionHistory_427795137 {\n flex-grow: 2;\n}\n\n.sectionHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.sectionTitle_427795137 {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.countBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 999px;\n padding: 3px 7px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.sectionTools_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.sectionBody_427795137 {\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* The branch and history bodies scroll as a whole inside their share. */\n.sectionBodyScroll_427795137 {\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* Inside the uncommitted card only the file lists scroll; the commit box\n * below stays pinned however long the change tree is. */\n.changeList_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* The 全部展开/全部折叠 text chips in the branch card head. */\n.treeToggle_427795137 {\n background: none;\n border: 0;\n padding: 2px 5px;\n font: inherit;\n font-size: 11.5px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n border-radius: 5px;\n white-space: nowrap;\n}\n\n.treeToggle_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-brand-primary);\n}\n\n/* Small action chip used across the section heads and the commit row. */\n.act_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n cursor: pointer;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 4px 10px;\n white-space: nowrap;\n}\n\n.act_427795137:hover:not([disabled]) {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.act_427795137[disabled] {\n opacity: 0.45;\n cursor: default;\n}\n\n.actPrimary_427795137 {\n /* Hardcoded blue by request — the brand token does not resolve reliably\n * inside the better-sidebar panel. */\n background: #4c6bf5;\n border-color: transparent;\n color: #fff;\n}\n\n.actPrimary_427795137:hover:not([disabled]) {\n border-color: transparent;\n filter: brightness(1.06);\n}\n\n/* Uncommitted changes */\n\n.groupTitleRow_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.linkBtn_427795137 {\n margin-left: auto;\n background: none;\n border: 0;\n padding: 2px 4px;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n border-radius: 5px;\n}\n\n.linkBtn_427795137:hover:not([disabled]) {\n text-decoration: underline;\n}\n\n.linkBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.changeRow_427795137 {\n display: grid;\n grid-template-columns: 16px 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.changeRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.changeCheck_427795137 {\n width: 14px;\n height: 14px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n margin: 0;\n}\n\n.statusBadge_427795137 {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n color: #fff;\n box-sizing: border-box;\n}\n\n.statusA_427795137 { background: var(--dsw-alias-status-success, #218358); }\n.statusM_427795137 { background: var(--dsw-alias-label-warning, #b07000); }\n.statusD_427795137 { background: var(--dsw-alias-status-danger, #d5304f); }\n.statusR_427795137 { background: #8350d8; }\n.statusC_427795137 { background: #8350d8; }\n.statusT_427795137 { background: #5b7a9d; }\n.statusQ_427795137 { background: var(--dsw-alias-label-dimmed, #9aa0aa); }\n.statusU_427795137 { background: #d5502e; }\n\n.changePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.stageHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n flex: none;\n}\n\n.commitBox_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n flex: none;\n}\n\n.commitInput_427795137 {\n width: 100%;\n box-sizing: border-box;\n resize: vertical;\n min-height: 52px;\n font: inherit;\n font-size: 12.5px;\n line-height: 1.5;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.commitInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.commitActions_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.commitHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n min-height: 16px;\n}\n\n.split_427795137 {\n margin-left: auto;\n display: flex;\n position: relative;\n}\n\n.splitMain_427795137 {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 16px;\n font-size: 13px;\n}\n\n.splitCaret_427795137 {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n margin-left: 1px;\n padding: 6px 6px;\n}\n\n.menu_427795137 {\n position: absolute;\n right: 0;\n top: calc(100% + 4px);\n z-index: 40;\n width: max-content;\n min-width: 0;\n background: var(--dsw-alias-surface-raised, var(--dsw-alias-bg-layer-3));\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n box-shadow: var(--dsw-shadow-lv3);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n padding: 3px;\n display: flex;\n flex-direction: column;\n}\n\n.menuItem_427795137 {\n display: block;\n width: 100%;\n text-align: left;\n background: none;\n border: 0;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n padding: 5px 10px;\n border-radius: 5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.menuItem_427795137:hover:not([disabled]) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.menuItem_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.menuSub_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n margin-top: 1px;\n}\n\n/* Branch section — two IDEA-style trees: 本地 → branches, 远程 → per-remote → branches */\n\n.treeHead_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n\n.treeHead_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.treeChevron_427795137 {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.treeChildren_427795137 {\n display: flex;\n flex-direction: column;\n margin-left: 12px;\n}\n\n.treeHeadNest_427795137 {\n font-weight: 400;\n}\n\n.treeNest_427795137 {\n margin-left: 12px;\n}\n\n.branchRow_427795137 {\n display: grid;\n grid-template-columns: 20px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 5px 8px;\n border-radius: 7px;\n /* The action menu opens on RIGHT-CLICK (Enter/Space for keyboard) — an\n * arrow, not the pointing hand a left-click affordance would promise. */\n cursor: default;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.branchRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Whole-row highlight for the current branch: the ✓ alone read as too\n * quiet inside the tree. A translucent brand-blue wash plus the inset left\n * accent and a colored name survives both themes; #4c6bf5 is the same\n * hardcoded blue actPrimary uses (the brand token does not resolve\n * reliably inside the better-sidebar panel). */\n.branchRowCurrent_427795137 {\n background: rgba(76, 107, 245, 0.16);\n box-shadow: inset 2px 0 0 #4c6bf5;\n cursor: default;\n}\n\n.branchRowCurrent_427795137:hover {\n background: rgba(76, 107, 245, 0.16);\n}\n\n.branchRowCurrent_427795137 .branchName_427795137 {\n color: #4c6bf5;\n font-weight: 600;\n}\n\n.branchMark_427795137 {\n color: var(--dsw-alias-brand-primary);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.branchName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.branchKind_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n margin-left: 6px;\n}\n\n.branchTrack_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n flex: none;\n}\n\n/* The branch action menu is portaled; fixed positioning by row rect. */\n.floatMenu_427795137 {\n position: fixed;\n z-index: 60;\n min-width: 190px;\n}\n\n.menuHead_427795137 {\n padding: 6px 10px 5px;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n margin-bottom: 3px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.menuHeadStrong_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.menuDanger_427795137 {\n color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.formField_427795137 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.formLabel_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.formInput_427795137 {\n font: inherit;\n font-size: 13px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.formInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.formError_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-status-danger, #d5304f);\n min-height: 16px;\n}\n\n/* Commit history */\n\n.historyHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.searchBox_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1;\n max-width: 300px;\n min-width: 140px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 0 9px;\n height: 28px;\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-bg-layer-3);\n}\n\n/* The branch card's head variant: sits right of the title and grows toward\n * the tools instead of being right-aligned by `margin-left: auto`. The\n * overrides win because both selectors are single classes and this one is\n * declared later. */\n.searchBoxHead_427795137 {\n margin-left: 0;\n flex: 1 1 auto;\n max-width: 320px;\n min-width: 90px;\n}\n\n.searchInput_427795137 {\n flex: 1;\n min-width: 0;\n border: 0;\n background: none;\n outline: none;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.searchInput_427795137::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The head carries the title plus three filters (branch, author, time). The\n * row wraps at narrow widths rather than squeezing the date inputs into\n * unreadable slivers. */\n.historyFilters_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n justify-content: flex-end;\n}\n\n.filterSelect_427795137,\n.filterInput_427795137,\n.filterDate_427795137 {\n height: 26px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n padding: 0 6px;\n outline: none;\n}\n\n.filterSelect_427795137 {\n max-width: 150px;\n}\n\n.filterInput_427795137 {\n width: 120px;\n}\n\n.filterInput_427795137::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.filterDate_427795137 {\n width: 122px;\n color-scheme: light dark;\n}\n\n.filterDash_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n.commitList_427795137 {\n /* Keep the history body gap between blocks, never between graph rows.\n * A non-shrinking block also prevents a long history from compressing rows\n * inside the card's scrolling flex body. */\n flex: none;\n}\n\n.commitRow_427795137 {\n position: relative;\n display: grid;\n grid-template-columns: var(--graph-w, 22px) minmax(0, 1fr) auto;\n column-gap: 8px;\n align-items: center;\n padding: 0 8px 0 2px;\n cursor: pointer;\n border-radius: 7px;\n}\n\n.commitRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.commitRowOpen_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n.commitRowOpen_427795137:hover {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n/* The SVG fills the actual grid row, including the text cells' padding.\n * Its paths scale vertically; the separate dot keeps a constant radius. */\n.commitGraph_427795137 {\n position: relative;\n align-self: stretch;\n min-height: 44px;\n}\n\n.commitGraphSvg_427795137 {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n overflow: visible;\n}\n\n.commitDot_427795137 {\n position: absolute;\n top: 50%;\n width: 8px;\n height: 8px;\n box-sizing: border-box;\n border: 2px solid currentColor;\n border-radius: 50%;\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.commitExpandGraph_427795137 {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n display: block;\n pointer-events: none;\n}\n\n.commitMain_427795137 {\n padding: 6px 0;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.commitSubject_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.commitSubjectText_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.commitRowOpen_427795137 .commitSubjectText_427795137 {\n font-weight: 600;\n}\n\n.refBadges_427795137 {\n display: inline-flex;\n gap: 4px;\n flex: none;\n}\n\n.refBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 4px;\n padding: 3px 5px;\n font-weight: 500;\n white-space: nowrap;\n max-width: 140px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.refHead_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary-bluish);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refTag_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-warning, #b07000);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refRemote_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: #8350d8;\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refLocal_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-status-success, #218358);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.commitMeta_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n min-width: 0;\n}\n\n.commitAuthor_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.commitSide_427795137 {\n padding: 6px 0;\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n flex: none;\n}\n\n/* The absolute stamp column: tabular figures so every row's digits line up\n * under the one above, which is what makes the column scannable. */\n.commitDate_427795137 {\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.commitHash_427795137 {\n font-family: ui-monospace, Consolas, monospace;\n}\n\n.commitExpand_427795137 {\n position: relative;\n grid-column: 1 / -1;\n /* Align the expanded file list with the subject column, whatever width the\n * graph band needs in this repository. */\n padding: 2px 4px 8px calc(var(--graph-w, 22px) + 8px);\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.commitExpandTitle_427795137 {\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n margin-bottom: 3px;\n}\n\n.fileRow_427795137 {\n display: grid;\n grid-template-columns: 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.fileRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.filePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.fileNums_427795137 {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n}\n\n.fileAdd_427795137 { color: var(--dsw-alias-status-success, #218358); }\n.fileDel_427795137 { color: var(--dsw-alias-status-danger, #d5304f); }\n\n.empty_427795137 {\n padding: 28px 16px;\n text-align: center;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12.5px;\n}\n\n.errorText_427795137 {\n padding: 20px 16px;\n text-align: center;\n color: var(--dsw-alias-status-danger, #d5304f);\n font-size: 12.5px;\n}\n\n.moreRow_427795137 {\n display: flex;\n justify-content: center;\n padding: 8px 0 2px;\n}\n\n/* Credential + remote dialogs. Both share one scaffold: a compact list of\n * stored rows above a single inline editor (the panel never stacks modals),\n * so the classes are named for the credential case and reused by remotes. */\n\n.credHint_427795137 {\n margin: 0 0 10px;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\n}\n\n.credList_427795137 {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 220px;\n overflow-y: auto;\n margin-bottom: 4px;\n}\n\n.credRow_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 8px;\n}\n\n.credRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.credMain_427795137 {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n flex: 1;\n}\n\n.credHost_427795137 {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.credMeta_427795137 {\n font-size: 11.5px;\n color: var(--dsw-alias-label-tertiary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.credMeta_427795137 code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n}\n\n.credActions_427795137 {\n display: flex;\n gap: 2px;\n flex: none;\n}\n\n.credDivider_427795137 {\n height: 0.5px;\n background: var(--dsw-alias-border-l2);\n margin: 10px 0;\n}\n\n.credFormTitle_427795137 {\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n margin-bottom: 8px;\n}\n\n/* The editor's fields: the Modal body has no gap of its own, so the stack\n * carries the rhythm instead of one margin per field. */\n.credForm_427795137 {\n display: flex;\n flex-direction: column;\n gap: 10px;\n margin-top: 2px;\n}\n\n.credPresets_427795137 {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n}\n\n.credPresetLabel_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin-right: 2px;\n}\n\n.credPreset_427795137,\n.credPresetOn_427795137 {\n font: inherit;\n font-size: 12px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 999px;\n padding: 2px 10px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.credPreset_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.credPresetOn_427795137 {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n\n.credMethodRow_427795137 {\n display: flex;\n gap: 6px;\n}\n\n.credMethod_427795137,\n.credMethodOn_427795137 {\n font: inherit;\n font-size: 12px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 4px 12px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.credMethodOn_427795137 {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n\n.credFieldHead_427795137 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n\n.credLink_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.credLink_427795137:hover {\n text-decoration: underline;\n}\n\n/* The token field's trailing 「创建」 button: the input keeps the row's width\n * and the button hugs its end, mirroring the browser token page it opens. */\n.credSecretRow_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.credSecretRow_427795137 .formInput_427795137 {\n flex: 1;\n min-width: 0;\n}\n\n/* The browser sign-in block: the only control on that authentication path —\n * no username or secret field sits above it any more. */\n.credOAuth_427795137 {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* The browser sign-in block: the one-time code the user types into the page\n * the dialog just opened, plus its waiting state. */\n.credDevice_427795137 {\n display: flex;\n flex-direction: column;\n gap: 5px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.credDeviceRow_427795137 {\n display: flex;\n align-items: baseline;\n gap: 8px;\n}\n\n.credDeviceCode_427795137 {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 15px;\n font-weight: 600;\n letter-spacing: 1px;\n color: var(--dsw-alias-label-primary);\n}\n\n.credDeviceHint_427795137 {\n font-size: 11.5px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.credDeviceWaiting_427795137 {\n font-size: 11.5px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The editor's action row: both actions sit on the right, cancel nearest the\n * form, save outermost. Only while the form itself is open. */\n.credFooter_427795137 {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: 6px;\n margin-top: 10px;\n}\n\n.credTop_427795137 {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-bottom: 10px;\n}\n\n.credTop_427795137 .credHint_427795137 {\n flex: 1;\n margin: 0;\n}\n\n.credAdd_427795137 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0.5px solid var(--dsw-alias-border-l2);\n border-radius: 7px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n}\n\n.credAdd_427795137:hover {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n";
|
|
3851
3948
|
const tagId$2 = "dsh-git-branch-manage/GitHistoryPanel.module.css";
|
|
3852
3949
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$2 + "\"]") === null) {
|
|
3853
3950
|
const tag = document.createElement("style");
|
|
@@ -3984,7 +4081,36 @@ window.__ModuleLoader__.load({
|
|
|
3984
4081
|
"fileDel": "fileDel_427795137",
|
|
3985
4082
|
"empty": "empty_427795137",
|
|
3986
4083
|
"errorText": "errorText_427795137",
|
|
3987
|
-
"moreRow": "moreRow_427795137"
|
|
4084
|
+
"moreRow": "moreRow_427795137",
|
|
4085
|
+
"credHint": "credHint_427795137",
|
|
4086
|
+
"credList": "credList_427795137",
|
|
4087
|
+
"credRow": "credRow_427795137",
|
|
4088
|
+
"credMain": "credMain_427795137",
|
|
4089
|
+
"credHost": "credHost_427795137",
|
|
4090
|
+
"credMeta": "credMeta_427795137",
|
|
4091
|
+
"credActions": "credActions_427795137",
|
|
4092
|
+
"credDivider": "credDivider_427795137",
|
|
4093
|
+
"credFormTitle": "credFormTitle_427795137",
|
|
4094
|
+
"credForm": "credForm_427795137",
|
|
4095
|
+
"credPresets": "credPresets_427795137",
|
|
4096
|
+
"credPresetLabel": "credPresetLabel_427795137",
|
|
4097
|
+
"credPreset": "credPreset_427795137",
|
|
4098
|
+
"credPresetOn": "credPresetOn_427795137",
|
|
4099
|
+
"credMethodRow": "credMethodRow_427795137",
|
|
4100
|
+
"credMethod": "credMethod_427795137",
|
|
4101
|
+
"credMethodOn": "credMethodOn_427795137",
|
|
4102
|
+
"credFieldHead": "credFieldHead_427795137",
|
|
4103
|
+
"credLink": "credLink_427795137",
|
|
4104
|
+
"credSecretRow": "credSecretRow_427795137",
|
|
4105
|
+
"credOAuth": "credOAuth_427795137",
|
|
4106
|
+
"credDevice": "credDevice_427795137",
|
|
4107
|
+
"credDeviceRow": "credDeviceRow_427795137",
|
|
4108
|
+
"credDeviceCode": "credDeviceCode_427795137",
|
|
4109
|
+
"credDeviceHint": "credDeviceHint_427795137",
|
|
4110
|
+
"credDeviceWaiting": "credDeviceWaiting_427795137",
|
|
4111
|
+
"credFooter": "credFooter_427795137",
|
|
4112
|
+
"credTop": "credTop_427795137",
|
|
4113
|
+
"credAdd": "credAdd_427795137"
|
|
3988
4114
|
};
|
|
3989
4115
|
//#endregion
|
|
3990
4116
|
//#region src/client/panel/FloatMenu.tsx
|
|
@@ -4146,7 +4272,7 @@ window.__ModuleLoader__.load({
|
|
|
4146
4272
|
}
|
|
4147
4273
|
/** The branches section. */
|
|
4148
4274
|
function BranchSection(props) {
|
|
4149
|
-
const { t, locals, remotes, busy, onCheckout, onRename, onDelete, onCreate } = props;
|
|
4275
|
+
const { t, locals, remotes, busy, onCheckout, onRename, onDelete, onCreate, onManageRemotes } = props;
|
|
4150
4276
|
const [menu, setMenu] = (0, react.useState)(null);
|
|
4151
4277
|
const [dialog, setDialog] = (0, react.useState)(null);
|
|
4152
4278
|
const [draft, setDraft] = (0, react.useState)("");
|
|
@@ -4348,6 +4474,13 @@ window.__ModuleLoader__.load({
|
|
|
4348
4474
|
onClick: collapseAll,
|
|
4349
4475
|
children: t("menuCollapseAll")
|
|
4350
4476
|
}),
|
|
4477
|
+
onManageRemotes === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4478
|
+
type: "button",
|
|
4479
|
+
className: GitHistoryPanel_module_default.treeToggle,
|
|
4480
|
+
disabled: busy,
|
|
4481
|
+
onClick: onManageRemotes,
|
|
4482
|
+
children: t("panel.remotes")
|
|
4483
|
+
}),
|
|
4351
4484
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4352
4485
|
type: "button",
|
|
4353
4486
|
className: GitHistoryPanel_module_default.act,
|
|
@@ -4588,6 +4721,528 @@ window.__ModuleLoader__.load({
|
|
|
4588
4721
|
});
|
|
4589
4722
|
}
|
|
4590
4723
|
//#endregion
|
|
4724
|
+
//#region src/client/panel/CredentialModal.tsx
|
|
4725
|
+
/**
|
|
4726
|
+
* CredentialModal: the GLOBAL git credential editor behind the topbar's
|
|
4727
|
+
* 「密钥配置」 button.
|
|
4728
|
+
*
|
|
4729
|
+
* Credentials are keyed by remote HOST, not by repository: one github.com
|
|
4730
|
+
* token serves every checkout on this machine. The list therefore never
|
|
4731
|
+
* shows a secret — the host routes return a mask only — and the editor
|
|
4732
|
+
* treats an empty secret as "keep the stored one", which is why the field's
|
|
4733
|
+
* placeholder changes once a host already has a credential.
|
|
4734
|
+
*
|
|
4735
|
+
* Validation is split: this component only rejects empty fields (instant
|
|
4736
|
+
* feedback); the host/secret shape is decided by the host half
|
|
4737
|
+
* (normalizeCredentialHost), whose message rides the error envelope back
|
|
4738
|
+
* here. Nothing in this file imports `../../credentials.ts` — that module
|
|
4739
|
+
* touches node:fs, which has no place in the browser bundle.
|
|
4740
|
+
*
|
|
4741
|
+
* Authentication has two paths and no password pair: device-flow hosts open
|
|
4742
|
+
* the browser (the token lands host-side, so the dialog only sees the mask),
|
|
4743
|
+
* every other host takes a pasted token. GitHub retired password auth in
|
|
4744
|
+
* 2021 and GitLab/Gitee expect a PAT, so a username/password form only
|
|
4745
|
+
* collected credentials the hosts reject.
|
|
4746
|
+
*
|
|
4747
|
+
* @module git-worktree/client/panel/CredentialModal
|
|
4748
|
+
*/
|
|
4749
|
+
/** The hosts the dialog offers as one-click presets (custom hosts are typed
|
|
4750
|
+
* in by hand and validated host-side). */
|
|
4751
|
+
const PRESET_HOSTS = [
|
|
4752
|
+
"github.com",
|
|
4753
|
+
"gitlab.com",
|
|
4754
|
+
"gitee.com"
|
|
4755
|
+
];
|
|
4756
|
+
/** Where each preset host mints a token. Only token-based hosts get a link:
|
|
4757
|
+
* a self-hosted GitLab or Gitee clone has no well-known settings page. */
|
|
4758
|
+
const TOKEN_PAGES = {
|
|
4759
|
+
"github.com": "https://github.com/settings/tokens/new?description=dsh-git-branch",
|
|
4760
|
+
"gitlab.com": "https://gitlab.com/-/user_settings/personal_access_tokens",
|
|
4761
|
+
"gitee.com": "https://gitee.com/profile/personal_access_tokens"
|
|
4762
|
+
};
|
|
4763
|
+
/** One polling interval, kept as a promise so the loop stays sequential. */
|
|
4764
|
+
function delay(ms) {
|
|
4765
|
+
return new Promise((resolve) => {
|
|
4766
|
+
setTimeout(resolve, ms);
|
|
4767
|
+
});
|
|
4768
|
+
}
|
|
4769
|
+
/** Consecutive unreachable polls before the dialog gives up. One TLS blip —
|
|
4770
|
+
* the reset keep-alive connection this environment sees — must not discard a
|
|
4771
|
+
* grant the user already approved in the browser. */
|
|
4772
|
+
const OAUTH_NETWORK_RETRY_LIMIT = 5;
|
|
4773
|
+
/** The credential editor dialog. Loads the global list on mount and after
|
|
4774
|
+
* every write; each write returns the fresh masked list, so no reload loop
|
|
4775
|
+
* (and no stale row) is possible. */
|
|
4776
|
+
function CredentialModal(props) {
|
|
4777
|
+
const { t, pushToast, onClose } = props;
|
|
4778
|
+
const [entries, setEntries] = (0, react.useState)([]);
|
|
4779
|
+
const [loaded, setLoaded] = (0, react.useState)(false);
|
|
4780
|
+
const [loadError, setLoadError] = (0, react.useState)();
|
|
4781
|
+
const [host, setHost] = (0, react.useState)("");
|
|
4782
|
+
const [method, setMethod] = (0, react.useState)("token");
|
|
4783
|
+
const [secret, setSecret] = (0, react.useState)("");
|
|
4784
|
+
const [seed, setSeed] = (0, react.useState)();
|
|
4785
|
+
const [formError, setFormError] = (0, react.useState)();
|
|
4786
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
4787
|
+
/** Two-step delete: the first click only arms the row. */
|
|
4788
|
+
const [confirmHost, setConfirmHost] = (0, react.useState)();
|
|
4789
|
+
/** The editor starts collapsed; the "+" icon in the header row opens it,
|
|
4790
|
+
* and the row's 编辑 button re-opens it prefilled. Keeping the form out of
|
|
4791
|
+
* the DOM until it is asked for leaves the list as the modal's default. */
|
|
4792
|
+
const [editorOpen, setEditorOpen] = (0, react.useState)(false);
|
|
4793
|
+
/** The browser sign-in currently shown under the password fields. */
|
|
4794
|
+
const [oauth, setOauth] = (0, react.useState)({ phase: "idle" });
|
|
4795
|
+
/** Generation counter: one bump abandons an in-flight poll loop (cancel,
|
|
4796
|
+
* close, unmount), so a late answer cannot revive a closed editor. */
|
|
4797
|
+
const oauthRun = (0, react.useRef)(0);
|
|
4798
|
+
(0, react.useEffect)(() => () => {
|
|
4799
|
+
oauthRun.current += 1;
|
|
4800
|
+
}, []);
|
|
4801
|
+
const load = (0, react.useCallback)(async () => {
|
|
4802
|
+
const result = await requestCredentials();
|
|
4803
|
+
if (!result.ok) {
|
|
4804
|
+
setLoadError(result.error);
|
|
4805
|
+
setLoaded(true);
|
|
4806
|
+
return;
|
|
4807
|
+
}
|
|
4808
|
+
setEntries(result.credentials);
|
|
4809
|
+
setLoadError(void 0);
|
|
4810
|
+
setLoaded(true);
|
|
4811
|
+
}, []);
|
|
4812
|
+
(0, react.useEffect)(() => {
|
|
4813
|
+
load();
|
|
4814
|
+
}, [load]);
|
|
4815
|
+
const applyEditor = (0, react.useCallback)((next) => {
|
|
4816
|
+
oauthRun.current += 1;
|
|
4817
|
+
setOauth({ phase: "idle" });
|
|
4818
|
+
setSeed(next);
|
|
4819
|
+
setHost(next === void 0 ? "" : next.host);
|
|
4820
|
+
setMethod(next === void 0 ? "token" : next.method === "token" ? "token" : "browser");
|
|
4821
|
+
setSecret("");
|
|
4822
|
+
setFormError(void 0);
|
|
4823
|
+
setConfirmHost(void 0);
|
|
4824
|
+
}, []);
|
|
4825
|
+
const openNew = (0, react.useCallback)(() => {
|
|
4826
|
+
applyEditor(void 0);
|
|
4827
|
+
setEditorOpen(true);
|
|
4828
|
+
}, [applyEditor]);
|
|
4829
|
+
/** Cancel closes the form only; the modal keeps the list it was opened for. */
|
|
4830
|
+
const closeEditor = (0, react.useCallback)(() => {
|
|
4831
|
+
applyEditor(void 0);
|
|
4832
|
+
setEditorOpen(false);
|
|
4833
|
+
}, [applyEditor]);
|
|
4834
|
+
/** Switching path abandons whatever sign-in the other path was waiting on,
|
|
4835
|
+
* so a late poll answer cannot rewrite the form the user now sees. */
|
|
4836
|
+
const chooseMode = (0, react.useCallback)((next) => {
|
|
4837
|
+
if (next === method) return;
|
|
4838
|
+
oauthRun.current += 1;
|
|
4839
|
+
setOauth({ phase: "idle" });
|
|
4840
|
+
setMethod(next);
|
|
4841
|
+
}, [method]);
|
|
4842
|
+
const applyPreset = (0, react.useCallback)((preset) => {
|
|
4843
|
+
const existing = entries.find((entry) => entry.host === preset);
|
|
4844
|
+
if (existing !== void 0) {
|
|
4845
|
+
applyEditor(existing);
|
|
4846
|
+
return;
|
|
4847
|
+
}
|
|
4848
|
+
applyEditor(void 0);
|
|
4849
|
+
setHost(preset);
|
|
4850
|
+
setMethod(BROWSER_SIGN_IN_HOSTS.includes(preset) ? "browser" : "token");
|
|
4851
|
+
}, [applyEditor, entries]);
|
|
4852
|
+
const normalizedHost = host.trim().toLowerCase();
|
|
4853
|
+
const existing = entries.find((entry) => entry.host === normalizedHost);
|
|
4854
|
+
const tokenPage = TOKEN_PAGES[normalizedHost];
|
|
4855
|
+
/** Device flow is only implemented for a few hosts; the rest get the token
|
|
4856
|
+
* field alone, with no method switch that could promise a dead button. */
|
|
4857
|
+
const browserSignIn = BROWSER_SIGN_IN_HOSTS.includes(normalizedHost);
|
|
4858
|
+
/** The mode actually rendered: without device flow the token field wins,
|
|
4859
|
+
* whatever stale selection the form was carrying. */
|
|
4860
|
+
const authMode = browserSignIn ? method : "token";
|
|
4861
|
+
const save = (0, react.useCallback)(async () => {
|
|
4862
|
+
if (busy) return;
|
|
4863
|
+
const trimmedSecret = secret.trim();
|
|
4864
|
+
if (host.trim() === "") {
|
|
4865
|
+
setFormError(t("cred.hostEmpty"));
|
|
4866
|
+
return;
|
|
4867
|
+
}
|
|
4868
|
+
if (existing === void 0 && trimmedSecret === "") {
|
|
4869
|
+
setFormError(t("cred.secretEmpty"));
|
|
4870
|
+
return;
|
|
4871
|
+
}
|
|
4872
|
+
setBusy(true);
|
|
4873
|
+
const result = await requestSaveCredential({
|
|
4874
|
+
host: host.trim(),
|
|
4875
|
+
username: "",
|
|
4876
|
+
method: "token",
|
|
4877
|
+
...trimmedSecret === "" ? {} : { secret: trimmedSecret }
|
|
4878
|
+
});
|
|
4879
|
+
setBusy(false);
|
|
4880
|
+
if (!result.ok) {
|
|
4881
|
+
setFormError(result.error);
|
|
4882
|
+
return;
|
|
4883
|
+
}
|
|
4884
|
+
setEntries(result.credentials);
|
|
4885
|
+
pushToast(t("cred.saved", { host: host.trim() }), "success");
|
|
4886
|
+
applyEditor(void 0);
|
|
4887
|
+
setEditorOpen(false);
|
|
4888
|
+
}, [
|
|
4889
|
+
applyEditor,
|
|
4890
|
+
busy,
|
|
4891
|
+
existing,
|
|
4892
|
+
host,
|
|
4893
|
+
pushToast,
|
|
4894
|
+
secret,
|
|
4895
|
+
t
|
|
4896
|
+
]);
|
|
4897
|
+
/**
|
|
4898
|
+
* IDEA-style site sign-in: start a device-flow grant, open the verification
|
|
4899
|
+
* page, then poll until the user approves. The token is stored host-side,
|
|
4900
|
+
* so the dialog only ever receives the refreshed masked list.
|
|
4901
|
+
*/
|
|
4902
|
+
const connect = (0, react.useCallback)(async () => {
|
|
4903
|
+
if (busy || oauth.phase !== "idle" || !browserSignIn) return;
|
|
4904
|
+
const target = normalizedHost;
|
|
4905
|
+
if (target === "") {
|
|
4906
|
+
setFormError(t("cred.hostEmpty"));
|
|
4907
|
+
return;
|
|
4908
|
+
}
|
|
4909
|
+
const run = ++oauthRun.current;
|
|
4910
|
+
setFormError(void 0);
|
|
4911
|
+
setOauth({ phase: "connecting" });
|
|
4912
|
+
const tab = window.open("", "_blank");
|
|
4913
|
+
const started = await requestOAuthStart({ host: target });
|
|
4914
|
+
if (oauthRun.current !== run) {
|
|
4915
|
+
tab?.close();
|
|
4916
|
+
return;
|
|
4917
|
+
}
|
|
4918
|
+
if (!started.ok) {
|
|
4919
|
+
tab?.close();
|
|
4920
|
+
setOauth({ phase: "idle" });
|
|
4921
|
+
setFormError(started.error);
|
|
4922
|
+
return;
|
|
4923
|
+
}
|
|
4924
|
+
const page = started.verificationUriComplete ?? started.verificationUri;
|
|
4925
|
+
if (tab === null || tab.closed) window.open(page, "_blank", "noopener,noreferrer");
|
|
4926
|
+
else {
|
|
4927
|
+
tab.opener = null;
|
|
4928
|
+
tab.location.href = page;
|
|
4929
|
+
}
|
|
4930
|
+
setOauth({
|
|
4931
|
+
phase: "waiting",
|
|
4932
|
+
userCode: started.userCode,
|
|
4933
|
+
verificationUri: started.verificationUri
|
|
4934
|
+
});
|
|
4935
|
+
const deadline = Date.now() + started.expiresInSeconds * 1e3;
|
|
4936
|
+
let intervalMs = Math.max(started.intervalSeconds, 1) * 1e3;
|
|
4937
|
+
let networkFailures = 0;
|
|
4938
|
+
while (Date.now() < deadline) {
|
|
4939
|
+
await delay(intervalMs);
|
|
4940
|
+
if (oauthRun.current !== run) return;
|
|
4941
|
+
const polled = await requestOAuthPoll({
|
|
4942
|
+
host: target,
|
|
4943
|
+
deviceCode: started.deviceCode
|
|
4944
|
+
});
|
|
4945
|
+
if (oauthRun.current !== run) return;
|
|
4946
|
+
if (!polled.ok) {
|
|
4947
|
+
setOauth({ phase: "idle" });
|
|
4948
|
+
setFormError(polled.error);
|
|
4949
|
+
return;
|
|
4950
|
+
}
|
|
4951
|
+
if (polled.status === "network") {
|
|
4952
|
+
networkFailures += 1;
|
|
4953
|
+
if (networkFailures >= OAUTH_NETWORK_RETRY_LIMIT) {
|
|
4954
|
+
setOauth({ phase: "idle" });
|
|
4955
|
+
setFormError(t("cred.deviceNetwork", { host: target }));
|
|
4956
|
+
return;
|
|
4957
|
+
}
|
|
4958
|
+
continue;
|
|
4959
|
+
}
|
|
4960
|
+
networkFailures = 0;
|
|
4961
|
+
if (polled.status === "pending") continue;
|
|
4962
|
+
if (polled.status === "slow_down") {
|
|
4963
|
+
intervalMs = Math.min(intervalMs * 2, 6e4);
|
|
4964
|
+
continue;
|
|
4965
|
+
}
|
|
4966
|
+
setEntries(polled.credentials);
|
|
4967
|
+
setOauth({ phase: "idle" });
|
|
4968
|
+
applyEditor(void 0);
|
|
4969
|
+
setEditorOpen(false);
|
|
4970
|
+
pushToast(t("cred.saved", { host: target }), "success");
|
|
4971
|
+
return;
|
|
4972
|
+
}
|
|
4973
|
+
if (oauthRun.current !== run) return;
|
|
4974
|
+
setOauth({ phase: "idle" });
|
|
4975
|
+
setFormError(t("cred.deviceExpired"));
|
|
4976
|
+
}, [
|
|
4977
|
+
applyEditor,
|
|
4978
|
+
browserSignIn,
|
|
4979
|
+
busy,
|
|
4980
|
+
normalizedHost,
|
|
4981
|
+
oauth.phase,
|
|
4982
|
+
pushToast,
|
|
4983
|
+
t
|
|
4984
|
+
]);
|
|
4985
|
+
const remove = (0, react.useCallback)(async (target) => {
|
|
4986
|
+
if (busy) return;
|
|
4987
|
+
setBusy(true);
|
|
4988
|
+
const result = await requestDeleteCredential({ host: target });
|
|
4989
|
+
setBusy(false);
|
|
4990
|
+
if (!result.ok) {
|
|
4991
|
+
setFormError(result.error);
|
|
4992
|
+
setConfirmHost(void 0);
|
|
4993
|
+
return;
|
|
4994
|
+
}
|
|
4995
|
+
setEntries(result.credentials);
|
|
4996
|
+
pushToast(t("cred.deleted", { host: target }), "success");
|
|
4997
|
+
setConfirmHost(void 0);
|
|
4998
|
+
if (seed?.host === target) {
|
|
4999
|
+
applyEditor(void 0);
|
|
5000
|
+
setEditorOpen(false);
|
|
5001
|
+
}
|
|
5002
|
+
}, [
|
|
5003
|
+
applyEditor,
|
|
5004
|
+
busy,
|
|
5005
|
+
pushToast,
|
|
5006
|
+
seed,
|
|
5007
|
+
t
|
|
5008
|
+
]);
|
|
5009
|
+
const title = seed === void 0 ? t("cred.add") : t("cred.edit");
|
|
5010
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
5011
|
+
open: true,
|
|
5012
|
+
onClose,
|
|
5013
|
+
title: t("cred.title"),
|
|
5014
|
+
closeLabel: t("cred.cancel"),
|
|
5015
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5016
|
+
size: "sm",
|
|
5017
|
+
variant: "ghost",
|
|
5018
|
+
onClick: onClose,
|
|
5019
|
+
children: t("close")
|
|
5020
|
+
}),
|
|
5021
|
+
children: [
|
|
5022
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5023
|
+
className: GitHistoryPanel_module_default.credTop,
|
|
5024
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5025
|
+
className: GitHistoryPanel_module_default.credHint,
|
|
5026
|
+
children: t("cred.hint")
|
|
5027
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5028
|
+
type: "button",
|
|
5029
|
+
className: GitHistoryPanel_module_default.credAdd,
|
|
5030
|
+
title: t("cred.add"),
|
|
5031
|
+
"aria-label": t("cred.add"),
|
|
5032
|
+
"aria-expanded": editorOpen,
|
|
5033
|
+
onClick: openNew,
|
|
5034
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5035
|
+
"aria-hidden": "true",
|
|
5036
|
+
children: "+"
|
|
5037
|
+
})
|
|
5038
|
+
})]
|
|
5039
|
+
}),
|
|
5040
|
+
loadError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5041
|
+
className: GitHistoryPanel_module_default.formError,
|
|
5042
|
+
children: loadError
|
|
5043
|
+
}) : null,
|
|
5044
|
+
loaded && loadError === void 0 && entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5045
|
+
className: GitHistoryPanel_module_default.empty,
|
|
5046
|
+
children: t("cred.empty")
|
|
5047
|
+
}) : null,
|
|
5048
|
+
entries.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5049
|
+
className: GitHistoryPanel_module_default.credList,
|
|
5050
|
+
children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5051
|
+
className: GitHistoryPanel_module_default.credRow,
|
|
5052
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5053
|
+
className: GitHistoryPanel_module_default.credMain,
|
|
5054
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5055
|
+
className: GitHistoryPanel_module_default.credHost,
|
|
5056
|
+
children: entry.host
|
|
5057
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5058
|
+
className: GitHistoryPanel_module_default.credMeta,
|
|
5059
|
+
children: [
|
|
5060
|
+
entry.username === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [entry.username, " · "] }),
|
|
5061
|
+
entry.method === "token" ? t("cred.methodToken") : t("cred.methodPassword"),
|
|
5062
|
+
" · ",
|
|
5063
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: entry.secretMask })
|
|
5064
|
+
]
|
|
5065
|
+
})]
|
|
5066
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5067
|
+
className: GitHistoryPanel_module_default.credActions,
|
|
5068
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5069
|
+
size: "sm",
|
|
5070
|
+
variant: "ghost",
|
|
5071
|
+
onClick: () => {
|
|
5072
|
+
applyEditor(entry);
|
|
5073
|
+
setEditorOpen(true);
|
|
5074
|
+
},
|
|
5075
|
+
children: t("cred.edit")
|
|
5076
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5077
|
+
size: "sm",
|
|
5078
|
+
variant: "ghost",
|
|
5079
|
+
disabled: busy,
|
|
5080
|
+
onClick: () => confirmHost === entry.host ? void remove(entry.host) : setConfirmHost(entry.host),
|
|
5081
|
+
children: confirmHost === entry.host ? t("cred.confirmDelete") : t("cred.delete")
|
|
5082
|
+
})]
|
|
5083
|
+
})]
|
|
5084
|
+
}, entry.host))
|
|
5085
|
+
}) : null,
|
|
5086
|
+
editorOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5087
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: GitHistoryPanel_module_default.credDivider }),
|
|
5088
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5089
|
+
className: GitHistoryPanel_module_default.credFormTitle,
|
|
5090
|
+
children: title
|
|
5091
|
+
}),
|
|
5092
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5093
|
+
className: GitHistoryPanel_module_default.credForm,
|
|
5094
|
+
children: [
|
|
5095
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5096
|
+
className: GitHistoryPanel_module_default.credPresets,
|
|
5097
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5098
|
+
className: GitHistoryPanel_module_default.credPresetLabel,
|
|
5099
|
+
children: t("cred.presets")
|
|
5100
|
+
}), PRESET_HOSTS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5101
|
+
type: "button",
|
|
5102
|
+
className: normalizedHost === preset ? GitHistoryPanel_module_default.credPresetOn : GitHistoryPanel_module_default.credPreset,
|
|
5103
|
+
onClick: () => applyPreset(preset),
|
|
5104
|
+
children: preset
|
|
5105
|
+
}, preset))]
|
|
5106
|
+
}),
|
|
5107
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5108
|
+
className: GitHistoryPanel_module_default.formField,
|
|
5109
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
5110
|
+
className: GitHistoryPanel_module_default.formLabel,
|
|
5111
|
+
htmlFor: "git-credential-host",
|
|
5112
|
+
children: t("cred.hostLabel")
|
|
5113
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5114
|
+
id: "git-credential-host",
|
|
5115
|
+
className: GitHistoryPanel_module_default.formInput,
|
|
5116
|
+
value: host,
|
|
5117
|
+
placeholder: t("cred.hostPlaceholder"),
|
|
5118
|
+
"data-modal-autofocus": seed === void 0 ? true : void 0,
|
|
5119
|
+
onChange: (event) => setHost(event.target.value)
|
|
5120
|
+
})]
|
|
5121
|
+
}),
|
|
5122
|
+
browserSignIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5123
|
+
className: GitHistoryPanel_module_default.formField,
|
|
5124
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5125
|
+
className: GitHistoryPanel_module_default.formLabel,
|
|
5126
|
+
children: t("cred.method")
|
|
5127
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5128
|
+
className: GitHistoryPanel_module_default.credMethodRow,
|
|
5129
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5130
|
+
type: "button",
|
|
5131
|
+
"aria-pressed": authMode === "browser",
|
|
5132
|
+
className: authMode === "browser" ? GitHistoryPanel_module_default.credMethodOn : GitHistoryPanel_module_default.credMethod,
|
|
5133
|
+
onClick: () => chooseMode("browser"),
|
|
5134
|
+
children: t("cred.methodBrowser")
|
|
5135
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5136
|
+
type: "button",
|
|
5137
|
+
"aria-pressed": authMode === "token",
|
|
5138
|
+
className: authMode === "token" ? GitHistoryPanel_module_default.credMethodOn : GitHistoryPanel_module_default.credMethod,
|
|
5139
|
+
onClick: () => chooseMode("token"),
|
|
5140
|
+
children: t("cred.methodToken")
|
|
5141
|
+
})]
|
|
5142
|
+
})]
|
|
5143
|
+
}) : null,
|
|
5144
|
+
authMode === "token" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5145
|
+
className: GitHistoryPanel_module_default.formField,
|
|
5146
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5147
|
+
className: GitHistoryPanel_module_default.credFieldHead,
|
|
5148
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
5149
|
+
className: GitHistoryPanel_module_default.formLabel,
|
|
5150
|
+
htmlFor: "git-credential-secret",
|
|
5151
|
+
children: t("cred.secretTokenLabel")
|
|
5152
|
+
})
|
|
5153
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5154
|
+
className: GitHistoryPanel_module_default.credSecretRow,
|
|
5155
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5156
|
+
id: "git-credential-secret",
|
|
5157
|
+
className: GitHistoryPanel_module_default.formInput,
|
|
5158
|
+
type: "password",
|
|
5159
|
+
value: secret,
|
|
5160
|
+
placeholder: existing === void 0 ? t("cred.secretPlaceholder") : t("cred.secretKeep"),
|
|
5161
|
+
onChange: (event) => setSecret(event.target.value),
|
|
5162
|
+
onKeyDown: (event) => {
|
|
5163
|
+
if (event.key !== "Enter") return;
|
|
5164
|
+
event.preventDefault();
|
|
5165
|
+
save();
|
|
5166
|
+
}
|
|
5167
|
+
}), tokenPage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5168
|
+
size: "sm",
|
|
5169
|
+
variant: "outline",
|
|
5170
|
+
onClick: () => window.open(tokenPage, "_blank", "noopener,noreferrer"),
|
|
5171
|
+
children: t("cred.createToken")
|
|
5172
|
+
}) : null]
|
|
5173
|
+
})]
|
|
5174
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5175
|
+
className: GitHistoryPanel_module_default.credOAuth,
|
|
5176
|
+
children: [
|
|
5177
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5178
|
+
className: GitHistoryPanel_module_default.credDeviceHint,
|
|
5179
|
+
children: t("cred.browserHint")
|
|
5180
|
+
}),
|
|
5181
|
+
oauth.phase === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5182
|
+
size: "sm",
|
|
5183
|
+
variant: "primary",
|
|
5184
|
+
disabled: busy,
|
|
5185
|
+
onClick: () => void connect(),
|
|
5186
|
+
children: t("cred.connect")
|
|
5187
|
+
}) : null,
|
|
5188
|
+
oauth.phase === "connecting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5189
|
+
className: GitHistoryPanel_module_default.credDeviceHint,
|
|
5190
|
+
children: t("cred.connecting")
|
|
5191
|
+
}) : null,
|
|
5192
|
+
oauth.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5193
|
+
className: GitHistoryPanel_module_default.credDevice,
|
|
5194
|
+
children: [
|
|
5195
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5196
|
+
className: GitHistoryPanel_module_default.credDeviceRow,
|
|
5197
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5198
|
+
className: GitHistoryPanel_module_default.credDeviceCode,
|
|
5199
|
+
children: oauth.userCode ?? ""
|
|
5200
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
5201
|
+
className: GitHistoryPanel_module_default.credLink,
|
|
5202
|
+
href: oauth.verificationUri,
|
|
5203
|
+
target: "_blank",
|
|
5204
|
+
rel: "noreferrer",
|
|
5205
|
+
children: oauth.verificationUri
|
|
5206
|
+
})]
|
|
5207
|
+
}),
|
|
5208
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5209
|
+
className: GitHistoryPanel_module_default.credDeviceHint,
|
|
5210
|
+
children: t("cred.deviceHint")
|
|
5211
|
+
}),
|
|
5212
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5213
|
+
className: GitHistoryPanel_module_default.credDeviceWaiting,
|
|
5214
|
+
children: t("cred.deviceWaiting")
|
|
5215
|
+
})
|
|
5216
|
+
]
|
|
5217
|
+
}) : null
|
|
5218
|
+
]
|
|
5219
|
+
}),
|
|
5220
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5221
|
+
className: GitHistoryPanel_module_default.formError,
|
|
5222
|
+
children: formError ?? ""
|
|
5223
|
+
})
|
|
5224
|
+
]
|
|
5225
|
+
}),
|
|
5226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5227
|
+
className: GitHistoryPanel_module_default.credFooter,
|
|
5228
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5229
|
+
size: "sm",
|
|
5230
|
+
variant: "outline",
|
|
5231
|
+
onClick: closeEditor,
|
|
5232
|
+
children: t("cancel")
|
|
5233
|
+
}), authMode === "token" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5234
|
+
size: "sm",
|
|
5235
|
+
variant: "primary",
|
|
5236
|
+
disabled: busy,
|
|
5237
|
+
onClick: () => void save(),
|
|
5238
|
+
children: busy ? t("cred.saving") : t("cred.save")
|
|
5239
|
+
}) : null]
|
|
5240
|
+
})
|
|
5241
|
+
] }) : null
|
|
5242
|
+
]
|
|
5243
|
+
});
|
|
5244
|
+
}
|
|
5245
|
+
//#endregion
|
|
4591
5246
|
//#region src/client/git-graph.ts
|
|
4592
5247
|
/** Lazily extend `lanes` so `index` exists. */
|
|
4593
5248
|
function ensureLane(lanes, index) {
|
|
@@ -4772,8 +5427,11 @@ window.__ModuleLoader__.load({
|
|
|
4772
5427
|
* author, and the absolute commit time. CLICKING a row expands the commit's
|
|
4773
5428
|
* changed files INLINE below it (exactly one open at a time), loading them on
|
|
4774
5429
|
* first open; RIGHT-CLICKING it opens the commit menu (copy hash, copy
|
|
4775
|
-
* subject, new branch here, tag here
|
|
4776
|
-
* writing verbs
|
|
5430
|
+
* subject, new branch here, tag here, and — for every tag the commit already
|
|
5431
|
+
* carries — push that tag), which is where the history card's writing verbs
|
|
5432
|
+
* live. Pushing a tag is offered per EXISTING tag rather than folded into the
|
|
5433
|
+
* branch push: `git push` sends the branch alone, so a tag on an
|
|
5434
|
+
* already-pushed commit would otherwise have no way out of the local repo.
|
|
4777
5435
|
*
|
|
4778
5436
|
* The head carries the three filters the card asks for, in this order —
|
|
4779
5437
|
* author, branch (a ref to pin the walk on, or every ref), and a time window
|
|
@@ -4941,7 +5599,7 @@ window.__ModuleLoader__.load({
|
|
|
4941
5599
|
return `${commit.subject} · ${commit.authorName} <${commit.authorEmail}> · ${absolute} · ${commit.short}`;
|
|
4942
5600
|
}
|
|
4943
5601
|
function HistorySection(props) {
|
|
4944
|
-
const { t, commits, loaded, loadError, loading, hasMore, loadingMore, expanded, filesByHash, filters, branches, busy, onLoadMore, onToggle, onFilters, onCreateBranch, onCreateTag, pushToast } = props;
|
|
5602
|
+
const { t, commits, loaded, loadError, loading, hasMore, loadingMore, expanded, filesByHash, filters, branches, busy, onLoadMore, onToggle, onFilters, onCreateBranch, onCreateTag, onPushTag, pushToast } = props;
|
|
4945
5603
|
const [menu, setMenu] = (0, react.useState)(null);
|
|
4946
5604
|
const [dialog, setDialog] = (0, react.useState)(null);
|
|
4947
5605
|
const [draft, setDraft] = (0, react.useState)("");
|
|
@@ -5103,6 +5761,8 @@ window.__ModuleLoader__.load({
|
|
|
5103
5761
|
})
|
|
5104
5762
|
}, commit.hash));
|
|
5105
5763
|
const dialogTitle = dialog === null ? "" : dialog.kind === "branch" ? t("createFlyAskFrom", { branch: dialog.commit.short }) : t("panel.tagTitle");
|
|
5764
|
+
/** Tags the right-clicked commit carries: one publish item each. */
|
|
5765
|
+
const menuTags = menu === null ? [] : commitTags(menu.commit.refs);
|
|
5106
5766
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5107
5767
|
className: `${GitHistoryPanel_module_default.section} ${GitHistoryPanel_module_default.sectionFill} ${GitHistoryPanel_module_default.sectionHistory}`,
|
|
5108
5768
|
"aria-label": t("panel.history"),
|
|
@@ -5177,7 +5837,18 @@ window.__ModuleLoader__.load({
|
|
|
5177
5837
|
disabled: busy,
|
|
5178
5838
|
onClick: () => openDialog("tag", menu.commit),
|
|
5179
5839
|
children: t("panel.branchTag")
|
|
5180
|
-
})
|
|
5840
|
+
}),
|
|
5841
|
+
menuTags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5842
|
+
type: "button",
|
|
5843
|
+
className: GitHistoryPanel_module_default.menuItem,
|
|
5844
|
+
role: "menuitem",
|
|
5845
|
+
disabled: busy,
|
|
5846
|
+
onClick: () => {
|
|
5847
|
+
setMenu(null);
|
|
5848
|
+
onPushTag(tag);
|
|
5849
|
+
},
|
|
5850
|
+
children: t("panel.tagPush", { tag })
|
|
5851
|
+
}, tag))
|
|
5181
5852
|
]
|
|
5182
5853
|
}) : null,
|
|
5183
5854
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
@@ -5241,6 +5912,286 @@ window.__ModuleLoader__.load({
|
|
|
5241
5912
|
});
|
|
5242
5913
|
}
|
|
5243
5914
|
//#endregion
|
|
5915
|
+
//#region src/client/panel/RemotesModal.tsx
|
|
5916
|
+
/**
|
|
5917
|
+
* RemotesModal: the repository's remote-address editor (behind the branch
|
|
5918
|
+
* section's 「管理远程」 button).
|
|
5919
|
+
*
|
|
5920
|
+
* A remote belongs to the REPOSITORY, not to a worktree, so this dialog
|
|
5921
|
+
* mutates `.git/config` through the plugin's remote routes and then reports
|
|
5922
|
+
* the fresh list back to the caller — the panel reloads its branch rows so
|
|
5923
|
+
* a new remote's branches show up without a manual refresh.
|
|
5924
|
+
*
|
|
5925
|
+
* Every mutation returns the complete list (the host half re-reads
|
|
5926
|
+
* `git remote -v` after writing), so the dialog never guesses what changed.
|
|
5927
|
+
* Names are immutable here: `git remote rename` also rewrites tracking
|
|
5928
|
+
* config, which this panel does not offer; the URL is the mutable part.
|
|
5929
|
+
*
|
|
5930
|
+
* The dialog follows the credential editor's interaction shape, so the two
|
|
5931
|
+
* modals read as one design: the list is the default view, a 「+」 in the
|
|
5932
|
+
* header row opens the editor, and the row's edit button re-opens it
|
|
5933
|
+
* prefilled. Cancel closes the editor only — the modal itself stays open.
|
|
5934
|
+
*
|
|
5935
|
+
* @module git-worktree/client/panel/RemotesModal
|
|
5936
|
+
*/
|
|
5937
|
+
/** The remote editor dialog. */
|
|
5938
|
+
function RemotesModal(props) {
|
|
5939
|
+
const { repoPath, t, pushToast, onClose, onChanged } = props;
|
|
5940
|
+
const [remotes, setRemotes] = (0, react.useState)([]);
|
|
5941
|
+
const [loaded, setLoaded] = (0, react.useState)(false);
|
|
5942
|
+
const [loadError, setLoadError] = (0, react.useState)();
|
|
5943
|
+
const [name, setName] = (0, react.useState)("");
|
|
5944
|
+
const [url, setUrl] = (0, react.useState)("");
|
|
5945
|
+
/** The remote whose URL is being rewritten (undefined = adding a new one). */
|
|
5946
|
+
const [editing, setEditing] = (0, react.useState)();
|
|
5947
|
+
const [formError, setFormError] = (0, react.useState)();
|
|
5948
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
5949
|
+
/** Two-step remove: the first click only arms the row. */
|
|
5950
|
+
const [confirmName, setConfirmName] = (0, react.useState)();
|
|
5951
|
+
/** The editor starts collapsed; the 「+」 in the header row opens it, and
|
|
5952
|
+
* the row's 修改地址 button re-opens it prefilled. Keeping the form out of
|
|
5953
|
+
* the DOM until it is asked for leaves the list as the modal's default —
|
|
5954
|
+
* the same shape the credential editor uses. */
|
|
5955
|
+
const [editorOpen, setEditorOpen] = (0, react.useState)(false);
|
|
5956
|
+
const load = (0, react.useCallback)(async () => {
|
|
5957
|
+
const result = await requestRemotes(repoPath);
|
|
5958
|
+
if (!result.ok) {
|
|
5959
|
+
setLoadError(result.error);
|
|
5960
|
+
setLoaded(true);
|
|
5961
|
+
return;
|
|
5962
|
+
}
|
|
5963
|
+
setRemotes(result.remotes);
|
|
5964
|
+
setLoadError(void 0);
|
|
5965
|
+
setLoaded(true);
|
|
5966
|
+
}, [repoPath]);
|
|
5967
|
+
(0, react.useEffect)(() => {
|
|
5968
|
+
load();
|
|
5969
|
+
}, [load]);
|
|
5970
|
+
const resetEditor = (0, react.useCallback)(() => {
|
|
5971
|
+
setEditing(void 0);
|
|
5972
|
+
setName("");
|
|
5973
|
+
setUrl("");
|
|
5974
|
+
setFormError(void 0);
|
|
5975
|
+
setConfirmName(void 0);
|
|
5976
|
+
setEditorOpen(false);
|
|
5977
|
+
}, []);
|
|
5978
|
+
/** The 「+」 button: a fresh, empty editor for a new remote. */
|
|
5979
|
+
const openNew = (0, react.useCallback)(() => {
|
|
5980
|
+
resetEditor();
|
|
5981
|
+
setEditorOpen(true);
|
|
5982
|
+
}, [resetEditor]);
|
|
5983
|
+
const startEdit = (0, react.useCallback)((entry) => {
|
|
5984
|
+
setEditing(entry.name);
|
|
5985
|
+
setName(entry.name);
|
|
5986
|
+
setUrl(entry.fetchUrl);
|
|
5987
|
+
setFormError(void 0);
|
|
5988
|
+
setConfirmName(void 0);
|
|
5989
|
+
setEditorOpen(true);
|
|
5990
|
+
}, []);
|
|
5991
|
+
const submit = (0, react.useCallback)(async () => {
|
|
5992
|
+
if (busy) return;
|
|
5993
|
+
if (name.trim() === "") {
|
|
5994
|
+
setFormError(t("remotes.nameEmpty"));
|
|
5995
|
+
return;
|
|
5996
|
+
}
|
|
5997
|
+
if (url.trim() === "") {
|
|
5998
|
+
setFormError(t("remotes.urlEmpty"));
|
|
5999
|
+
return;
|
|
6000
|
+
}
|
|
6001
|
+
setBusy(true);
|
|
6002
|
+
const result = await requestMutateRemote({
|
|
6003
|
+
repoPath,
|
|
6004
|
+
action: editing === void 0 ? "add" : "set-url",
|
|
6005
|
+
name: name.trim(),
|
|
6006
|
+
url: url.trim()
|
|
6007
|
+
});
|
|
6008
|
+
setBusy(false);
|
|
6009
|
+
if (!result.ok) {
|
|
6010
|
+
setFormError(result.error);
|
|
6011
|
+
return;
|
|
6012
|
+
}
|
|
6013
|
+
setRemotes(result.remotes);
|
|
6014
|
+
pushToast(t("remotes.saved", { name: name.trim() }), "success");
|
|
6015
|
+
resetEditor();
|
|
6016
|
+
onChanged?.();
|
|
6017
|
+
}, [
|
|
6018
|
+
busy,
|
|
6019
|
+
editing,
|
|
6020
|
+
name,
|
|
6021
|
+
onChanged,
|
|
6022
|
+
pushToast,
|
|
6023
|
+
repoPath,
|
|
6024
|
+
resetEditor,
|
|
6025
|
+
t,
|
|
6026
|
+
url
|
|
6027
|
+
]);
|
|
6028
|
+
const remove = (0, react.useCallback)(async (target) => {
|
|
6029
|
+
if (busy) return;
|
|
6030
|
+
setBusy(true);
|
|
6031
|
+
const result = await requestMutateRemote({
|
|
6032
|
+
repoPath,
|
|
6033
|
+
action: "remove",
|
|
6034
|
+
name: target
|
|
6035
|
+
});
|
|
6036
|
+
setBusy(false);
|
|
6037
|
+
if (!result.ok) {
|
|
6038
|
+
setFormError(result.error);
|
|
6039
|
+
setConfirmName(void 0);
|
|
6040
|
+
return;
|
|
6041
|
+
}
|
|
6042
|
+
setRemotes(result.remotes);
|
|
6043
|
+
pushToast(t("remotes.removed", { name: target }), "success");
|
|
6044
|
+
setConfirmName(void 0);
|
|
6045
|
+
if (editing === target) resetEditor();
|
|
6046
|
+
onChanged?.();
|
|
6047
|
+
}, [
|
|
6048
|
+
busy,
|
|
6049
|
+
editing,
|
|
6050
|
+
onChanged,
|
|
6051
|
+
pushToast,
|
|
6052
|
+
repoPath,
|
|
6053
|
+
resetEditor,
|
|
6054
|
+
t
|
|
6055
|
+
]);
|
|
6056
|
+
const editorTitle = editing === void 0 ? t("remotes.add") : `${t("remotes.edit")} · ${editing}`;
|
|
6057
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
6058
|
+
open: true,
|
|
6059
|
+
onClose,
|
|
6060
|
+
title: t("remotes.title"),
|
|
6061
|
+
closeLabel: t("remotes.cancel"),
|
|
6062
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
6063
|
+
size: "sm",
|
|
6064
|
+
variant: "ghost",
|
|
6065
|
+
onClick: onClose,
|
|
6066
|
+
children: t("close")
|
|
6067
|
+
}),
|
|
6068
|
+
children: [
|
|
6069
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6070
|
+
className: GitHistoryPanel_module_default.credTop,
|
|
6071
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6072
|
+
className: GitHistoryPanel_module_default.credHint,
|
|
6073
|
+
children: t("remotes.hint")
|
|
6074
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6075
|
+
type: "button",
|
|
6076
|
+
className: GitHistoryPanel_module_default.credAdd,
|
|
6077
|
+
title: t("remotes.add"),
|
|
6078
|
+
"aria-label": t("remotes.add"),
|
|
6079
|
+
"aria-expanded": editorOpen,
|
|
6080
|
+
onClick: openNew,
|
|
6081
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6082
|
+
"aria-hidden": "true",
|
|
6083
|
+
children: "+"
|
|
6084
|
+
})
|
|
6085
|
+
})]
|
|
6086
|
+
}),
|
|
6087
|
+
loadError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6088
|
+
className: GitHistoryPanel_module_default.formError,
|
|
6089
|
+
children: loadError
|
|
6090
|
+
}) : null,
|
|
6091
|
+
loaded && loadError === void 0 && remotes.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6092
|
+
className: GitHistoryPanel_module_default.empty,
|
|
6093
|
+
children: t("remotes.empty")
|
|
6094
|
+
}) : null,
|
|
6095
|
+
remotes.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6096
|
+
className: GitHistoryPanel_module_default.credList,
|
|
6097
|
+
children: remotes.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6098
|
+
className: GitHistoryPanel_module_default.credRow,
|
|
6099
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6100
|
+
className: GitHistoryPanel_module_default.credMain,
|
|
6101
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6102
|
+
className: GitHistoryPanel_module_default.credHost,
|
|
6103
|
+
children: entry.name
|
|
6104
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6105
|
+
className: GitHistoryPanel_module_default.credMeta,
|
|
6106
|
+
children: entry.fetchUrl === entry.pushUrl ? entry.fetchUrl : `${t("remotes.fetchUrl")}: ${entry.fetchUrl} · ${t("remotes.pushUrl")}: ${entry.pushUrl}`
|
|
6107
|
+
})]
|
|
6108
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6109
|
+
className: GitHistoryPanel_module_default.credActions,
|
|
6110
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
6111
|
+
size: "sm",
|
|
6112
|
+
variant: "ghost",
|
|
6113
|
+
onClick: () => startEdit(entry),
|
|
6114
|
+
children: t("remotes.edit")
|
|
6115
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
6116
|
+
size: "sm",
|
|
6117
|
+
variant: "ghost",
|
|
6118
|
+
disabled: busy,
|
|
6119
|
+
onClick: () => confirmName === entry.name ? void remove(entry.name) : setConfirmName(entry.name),
|
|
6120
|
+
children: confirmName === entry.name ? t("remotes.confirmRemove") : t("remotes.remove")
|
|
6121
|
+
})]
|
|
6122
|
+
})]
|
|
6123
|
+
}, entry.name))
|
|
6124
|
+
}) : null,
|
|
6125
|
+
editorOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
6126
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: GitHistoryPanel_module_default.credDivider }),
|
|
6127
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6128
|
+
className: GitHistoryPanel_module_default.credFormTitle,
|
|
6129
|
+
children: editorTitle
|
|
6130
|
+
}),
|
|
6131
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6132
|
+
className: GitHistoryPanel_module_default.credForm,
|
|
6133
|
+
children: [
|
|
6134
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6135
|
+
className: GitHistoryPanel_module_default.formField,
|
|
6136
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
6137
|
+
className: GitHistoryPanel_module_default.formLabel,
|
|
6138
|
+
htmlFor: "git-remote-name",
|
|
6139
|
+
children: t("remotes.nameLabel")
|
|
6140
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6141
|
+
id: "git-remote-name",
|
|
6142
|
+
className: GitHistoryPanel_module_default.formInput,
|
|
6143
|
+
value: name,
|
|
6144
|
+
placeholder: t("remotes.namePlaceholder"),
|
|
6145
|
+
readOnly: editing !== void 0,
|
|
6146
|
+
"data-modal-autofocus": editing === void 0 ? true : void 0,
|
|
6147
|
+
onChange: (event) => setName(event.target.value)
|
|
6148
|
+
})]
|
|
6149
|
+
}),
|
|
6150
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6151
|
+
className: GitHistoryPanel_module_default.formField,
|
|
6152
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
6153
|
+
className: GitHistoryPanel_module_default.formLabel,
|
|
6154
|
+
htmlFor: "git-remote-url",
|
|
6155
|
+
children: t("remotes.urlLabel")
|
|
6156
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6157
|
+
id: "git-remote-url",
|
|
6158
|
+
className: GitHistoryPanel_module_default.formInput,
|
|
6159
|
+
value: url,
|
|
6160
|
+
placeholder: t("remotes.urlPlaceholder"),
|
|
6161
|
+
onChange: (event) => setUrl(event.target.value),
|
|
6162
|
+
onKeyDown: (event) => {
|
|
6163
|
+
if (event.key !== "Enter") return;
|
|
6164
|
+
event.preventDefault();
|
|
6165
|
+
submit();
|
|
6166
|
+
}
|
|
6167
|
+
})]
|
|
6168
|
+
}),
|
|
6169
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6170
|
+
className: GitHistoryPanel_module_default.formError,
|
|
6171
|
+
children: formError ?? ""
|
|
6172
|
+
})
|
|
6173
|
+
]
|
|
6174
|
+
}),
|
|
6175
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6176
|
+
className: GitHistoryPanel_module_default.credFooter,
|
|
6177
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
6178
|
+
size: "sm",
|
|
6179
|
+
variant: "outline",
|
|
6180
|
+
onClick: resetEditor,
|
|
6181
|
+
children: t("cancel")
|
|
6182
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
6183
|
+
size: "sm",
|
|
6184
|
+
variant: "primary",
|
|
6185
|
+
disabled: busy,
|
|
6186
|
+
onClick: () => void submit(),
|
|
6187
|
+
children: busy ? t("remotes.saving") : t("remotes.save")
|
|
6188
|
+
})]
|
|
6189
|
+
})
|
|
6190
|
+
] }) : null
|
|
6191
|
+
]
|
|
6192
|
+
});
|
|
6193
|
+
}
|
|
6194
|
+
//#endregion
|
|
5244
6195
|
//#region src/client/panel/UncommittedSection.tsx
|
|
5245
6196
|
/**
|
|
5246
6197
|
* UncommittedSection: the panel's first block — the staged/unstaged file
|
|
@@ -5542,6 +6493,8 @@ window.__ModuleLoader__.load({
|
|
|
5542
6493
|
const [expanded, setExpanded] = (0, react.useState)(null);
|
|
5543
6494
|
const [filesByHash, setFilesByHash] = (0, react.useState)({});
|
|
5544
6495
|
const [sectionBusy, setSectionBusy] = (0, react.useState)(false);
|
|
6496
|
+
/** The remote-address editor (branch card's 「管理远程」 entry). */
|
|
6497
|
+
const [remotesOpen, setRemotesOpen] = (0, react.useState)(false);
|
|
5545
6498
|
const [syncBusy, setSyncBusy] = (0, react.useState)(false);
|
|
5546
6499
|
const [commitMenuOpen, setCommitMenuOpen] = (0, react.useState)(false);
|
|
5547
6500
|
const [commitDraft, setCommitDraft] = (0, react.useState)("");
|
|
@@ -5600,21 +6553,28 @@ window.__ModuleLoader__.load({
|
|
|
5600
6553
|
(0, react.useEffect)(() => onGitMutated(refresh), [refresh]);
|
|
5601
6554
|
(0, react.useImperativeHandle)(ref, () => ({ refresh }), [refresh]);
|
|
5602
6555
|
const repo = status !== null && status.repo ? status : null;
|
|
5603
|
-
/** Run one mutating verb with the
|
|
6556
|
+
/** Run one mutating verb with the error toast and the cross-surface notice.
|
|
5604
6557
|
* Mutations refresh through `loadAll` so their re-read bumps the request
|
|
5605
6558
|
* sequence — an older in-flight snapshot can never land over the fresh
|
|
5606
6559
|
* post-mutation state. A SUCCESS also emits the cross-surface notice so
|
|
5607
6560
|
* the composer chip drops the branch git just left (stage toggles ride
|
|
5608
|
-
* along — one extra idempotent /status beats per-verb wiring).
|
|
5609
|
-
|
|
5610
|
-
|
|
6561
|
+
* along — one extra idempotent /status beats per-verb wiring).
|
|
6562
|
+
*
|
|
6563
|
+
* `scope` names what the verb touches: `'section'` (default) claims the
|
|
6564
|
+
* working-tree busy gate — the commit box's primary button reads
|
|
6565
|
+
* 「提交中…」 while it is on. A tag verb (create or push) passes `'repo'`:
|
|
6566
|
+
* it neither reads nor writes the working tree, so lighting that gate
|
|
6567
|
+
* would report a commit that is not happening. */
|
|
6568
|
+
const runGuarded = (0, react.useCallback)(async (verb, scope = "section") => {
|
|
6569
|
+
const section = scope === "section";
|
|
6570
|
+
if (section) setSectionBusy(true);
|
|
5611
6571
|
try {
|
|
5612
6572
|
await verb();
|
|
5613
6573
|
emitGitMutated();
|
|
5614
6574
|
} catch (error) {
|
|
5615
6575
|
pushToast(t("errorGeneric", { message: failureText(error) }), "error");
|
|
5616
6576
|
} finally {
|
|
5617
|
-
setSectionBusy(false);
|
|
6577
|
+
if (section) setSectionBusy(false);
|
|
5618
6578
|
}
|
|
5619
6579
|
}, [pushToast, t]);
|
|
5620
6580
|
const onToggleRow = (0, react.useCallback)((row) => {
|
|
@@ -5856,14 +6816,16 @@ window.__ModuleLoader__.load({
|
|
|
5856
6816
|
]);
|
|
5857
6817
|
/** Tag one commit. Runs against the repository (a tag is a repository-wide
|
|
5858
6818
|
* ref), so the history card can tag a commit that no checkout has. Only
|
|
5859
|
-
* local: pushing tags is a release step, not a side effect of a right-click.
|
|
6819
|
+
* local: pushing tags is a release step, not a side effect of a right-click.
|
|
6820
|
+
* `'repo'`: a tag write leaves the working tree untouched — it must not
|
|
6821
|
+
* turn the commit box's primary button into 「提交中…」. */
|
|
5860
6822
|
const onCreateTag = (0, react.useCallback)((name, target, message) => {
|
|
5861
6823
|
runGuarded(async () => {
|
|
5862
6824
|
const result = await requestCreateTag(cwd, name, target, message);
|
|
5863
6825
|
if (!result.ok) throw new Error(result.error);
|
|
5864
6826
|
pushToast(t("panel.tagDone", { tag: result.tag }), "success");
|
|
5865
6827
|
await loadAll();
|
|
5866
|
-
});
|
|
6828
|
+
}, "repo");
|
|
5867
6829
|
}, [
|
|
5868
6830
|
cwd,
|
|
5869
6831
|
loadAll,
|
|
@@ -5871,6 +6833,28 @@ window.__ModuleLoader__.load({
|
|
|
5871
6833
|
runGuarded,
|
|
5872
6834
|
t
|
|
5873
6835
|
]);
|
|
6836
|
+
/** Publish ONE local tag. Its own verb, apart from the branch push: `git
|
|
6837
|
+
* push` sends the branch alone, so a tag made on an already-pushed commit
|
|
6838
|
+
* would otherwise never reach the remote. Deliberately no refetch: tags are
|
|
6839
|
+
* local refs and a remote tag changes nothing the panel can see. */
|
|
6840
|
+
const onPushTag = (0, react.useCallback)((name) => {
|
|
6841
|
+
runGuarded(async () => {
|
|
6842
|
+
const result = await requestPushTag(cwd, name);
|
|
6843
|
+
if (!result.ok) throw new Error(result.error);
|
|
6844
|
+
pushToast(result.pushed ? t("panel.tagPushDone", {
|
|
6845
|
+
tag: result.tag,
|
|
6846
|
+
remote: result.remote
|
|
6847
|
+
}) : t("panel.tagPushUpToDate", {
|
|
6848
|
+
tag: result.tag,
|
|
6849
|
+
remote: result.remote
|
|
6850
|
+
}), "success");
|
|
6851
|
+
}, "repo");
|
|
6852
|
+
}, [
|
|
6853
|
+
cwd,
|
|
6854
|
+
pushToast,
|
|
6855
|
+
runGuarded,
|
|
6856
|
+
t
|
|
6857
|
+
]);
|
|
5874
6858
|
const onToggleCommit = (0, react.useCallback)((hash) => {
|
|
5875
6859
|
if (expanded === hash) {
|
|
5876
6860
|
setExpanded(null);
|
|
@@ -5981,7 +6965,8 @@ window.__ModuleLoader__.load({
|
|
|
5981
6965
|
onCheckout,
|
|
5982
6966
|
onRename,
|
|
5983
6967
|
onDelete,
|
|
5984
|
-
onCreate
|
|
6968
|
+
onCreate,
|
|
6969
|
+
onManageRemotes: () => setRemotesOpen(true)
|
|
5985
6970
|
}),
|
|
5986
6971
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistorySection, {
|
|
5987
6972
|
t,
|
|
@@ -6001,8 +6986,16 @@ window.__ModuleLoader__.load({
|
|
|
6001
6986
|
onFilters: onHistoryFilters,
|
|
6002
6987
|
onCreateBranch: onCreate,
|
|
6003
6988
|
onCreateTag,
|
|
6989
|
+
onPushTag,
|
|
6004
6990
|
pushToast
|
|
6005
|
-
})
|
|
6991
|
+
}),
|
|
6992
|
+
remotesOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RemotesModal, {
|
|
6993
|
+
repoPath: cwd,
|
|
6994
|
+
t,
|
|
6995
|
+
pushToast,
|
|
6996
|
+
onClose: () => setRemotesOpen(false),
|
|
6997
|
+
onChanged: () => void loadAll()
|
|
6998
|
+
}) : null
|
|
6006
6999
|
]
|
|
6007
7000
|
});
|
|
6008
7001
|
});
|
|
@@ -6030,6 +7023,7 @@ window.__ModuleLoader__.load({
|
|
|
6030
7023
|
}, []);
|
|
6031
7024
|
const accelState = (0, react.useSyncExternalStore)(accel.store.subscribe, accel.store.getSnapshot);
|
|
6032
7025
|
const [accelOpen, setAccelOpen] = (0, react.useState)(false);
|
|
7026
|
+
const [credOpen, setCredOpen] = (0, react.useState)(false);
|
|
6033
7027
|
const accelOn = (0, react.useCallback)(() => accel.store.getSnapshot().enabled, [accel]);
|
|
6034
7028
|
const closeAccel = (0, react.useCallback)(() => {
|
|
6035
7029
|
accel.discardAccel();
|
|
@@ -6048,46 +7042,66 @@ window.__ModuleLoader__.load({
|
|
|
6048
7042
|
})
|
|
6049
7043
|
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6050
7044
|
className: GitHistoryPanel_module_default.topbarActions,
|
|
6051
|
-
children: [
|
|
6052
|
-
|
|
6053
|
-
|
|
7045
|
+
children: [
|
|
7046
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7047
|
+
className: GitHistoryPanel_module_default.cnWrap,
|
|
7048
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7049
|
+
type: "button",
|
|
7050
|
+
className: accelState.enabled ? GitHistoryPanel_module_default.cnBtnOn : GitHistoryPanel_module_default.cnBtn,
|
|
7051
|
+
title: t("panel.cnAccelerate"),
|
|
7052
|
+
"aria-label": t("panel.cnAccelerate"),
|
|
7053
|
+
"aria-expanded": accelOpen,
|
|
7054
|
+
onClick: () => accelOpen ? closeAccel() : setAccelOpen(true),
|
|
7055
|
+
children: [
|
|
7056
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7057
|
+
"aria-hidden": "true",
|
|
7058
|
+
children: "⚡"
|
|
7059
|
+
}),
|
|
7060
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("panel.cnAccelerate") }),
|
|
7061
|
+
accelState.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7062
|
+
className: GitHistoryPanel_module_default.cnDot,
|
|
7063
|
+
"aria-hidden": "true"
|
|
7064
|
+
}) : null
|
|
7065
|
+
]
|
|
7066
|
+
}), accelOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CnAcceleratePopover, {
|
|
7067
|
+
accel,
|
|
7068
|
+
t,
|
|
7069
|
+
pushToast,
|
|
7070
|
+
onClose: closeAccel
|
|
7071
|
+
}) : null]
|
|
7072
|
+
}),
|
|
7073
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6054
7074
|
type: "button",
|
|
6055
|
-
className:
|
|
6056
|
-
title: t("panel.
|
|
6057
|
-
"aria-label": t("panel.
|
|
6058
|
-
"aria-expanded":
|
|
6059
|
-
onClick: () =>
|
|
6060
|
-
children: [
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
}) : null]
|
|
6077
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6078
|
-
type: "button",
|
|
6079
|
-
className: GitHistoryPanel_module_default.refreshBtn,
|
|
6080
|
-
disabled: cwd === void 0,
|
|
6081
|
-
title: t("panel.refresh"),
|
|
6082
|
-
"aria-label": t("panel.refresh"),
|
|
6083
|
-
onClick: () => dataRef.current?.refresh(),
|
|
6084
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {
|
|
6085
|
-
size: 14,
|
|
6086
|
-
"aria-hidden": "true"
|
|
7075
|
+
className: credOpen ? GitHistoryPanel_module_default.cnBtnOn : GitHistoryPanel_module_default.cnBtn,
|
|
7076
|
+
title: t("panel.credentials"),
|
|
7077
|
+
"aria-label": t("panel.credentials"),
|
|
7078
|
+
"aria-expanded": credOpen,
|
|
7079
|
+
onClick: () => setCredOpen((open) => !open),
|
|
7080
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7081
|
+
"aria-hidden": "true",
|
|
7082
|
+
children: "🔑"
|
|
7083
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("panel.credentials") })]
|
|
7084
|
+
}),
|
|
7085
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7086
|
+
type: "button",
|
|
7087
|
+
className: GitHistoryPanel_module_default.refreshBtn,
|
|
7088
|
+
disabled: cwd === void 0,
|
|
7089
|
+
title: t("panel.refresh"),
|
|
7090
|
+
"aria-label": t("panel.refresh"),
|
|
7091
|
+
onClick: () => dataRef.current?.refresh(),
|
|
7092
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {
|
|
7093
|
+
size: 14,
|
|
7094
|
+
"aria-hidden": "true"
|
|
7095
|
+
})
|
|
6087
7096
|
})
|
|
6088
|
-
|
|
7097
|
+
]
|
|
6089
7098
|
})]
|
|
6090
7099
|
}),
|
|
7100
|
+
credOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialModal, {
|
|
7101
|
+
t,
|
|
7102
|
+
pushToast,
|
|
7103
|
+
onClose: () => setCredOpen(false)
|
|
7104
|
+
}) : null,
|
|
6091
7105
|
cwd === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6092
7106
|
className: GitHistoryPanel_module_default.empty,
|
|
6093
7107
|
children: t("panel.noSession")
|
|
@@ -7389,6 +8403,9 @@ window.__ModuleLoader__.load({
|
|
|
7389
8403
|
"panel.tagName": "Tag name",
|
|
7390
8404
|
"panel.tagMessage": "Message (optional)",
|
|
7391
8405
|
"panel.tagDone": "Tagged {tag}",
|
|
8406
|
+
"panel.tagPush": "Push tag {tag}",
|
|
8407
|
+
"panel.tagPushDone": "Pushed tag {tag} to {remote}",
|
|
8408
|
+
"panel.tagPushUpToDate": "Tag {tag} is already on {remote}",
|
|
7392
8409
|
"panel.noCommits": "This repository has no commits yet",
|
|
7393
8410
|
"panel.noMatches": "No matching commits",
|
|
7394
8411
|
"panel.loadMore": "Load more",
|
|
@@ -7412,7 +8429,72 @@ window.__ModuleLoader__.load({
|
|
|
7412
8429
|
"accel.saveFailed": "The change did not save. Try again.",
|
|
7413
8430
|
"accel.savedOn": "China acceleration enabled: {url}",
|
|
7414
8431
|
"accel.savedOff": "China acceleration disabled",
|
|
7415
|
-
"accel.mirrorDownHint": "If the mirror is down, disable acceleration or change the URL"
|
|
8432
|
+
"accel.mirrorDownHint": "If the mirror is down, disable acceleration or change the URL",
|
|
8433
|
+
"panel.credentials": "Credentials",
|
|
8434
|
+
"cred.title": "Git credentials",
|
|
8435
|
+
"cred.hint": "Global, one entry per host: a token saved for github.com serves every repository. Stored only on this machine in git-credentials.json.",
|
|
8436
|
+
"cred.empty": "No credentials saved yet",
|
|
8437
|
+
"cred.hostLabel": "Host",
|
|
8438
|
+
"cred.hostPlaceholder": "github.com",
|
|
8439
|
+
"cred.presets": "Common hosts",
|
|
8440
|
+
"cred.customPreset": "Custom host",
|
|
8441
|
+
"cred.secretTokenLabel": "Token",
|
|
8442
|
+
"cred.secretKeep": "A secret is already saved — leave blank to keep it",
|
|
8443
|
+
"cred.secretPlaceholder": "Paste a personal access token",
|
|
8444
|
+
"cred.method": "Authentication",
|
|
8445
|
+
"cred.methodToken": "Token",
|
|
8446
|
+
"cred.methodBrowser": "Browser sign-in",
|
|
8447
|
+
"cred.methodPassword": "Password",
|
|
8448
|
+
"cred.getToken": "Get a token",
|
|
8449
|
+
"cred.add": "Add credential",
|
|
8450
|
+
"cred.edit": "Edit",
|
|
8451
|
+
"cred.save": "Save",
|
|
8452
|
+
"cred.saving": "Saving…",
|
|
8453
|
+
"cred.delete": "Delete",
|
|
8454
|
+
"cred.deleting": "Deleting…",
|
|
8455
|
+
"cred.confirmDelete": "Delete for good",
|
|
8456
|
+
"cred.cancel": "Cancel",
|
|
8457
|
+
"cred.saved": "Saved the credential for {host}",
|
|
8458
|
+
"cred.deleted": "Deleted the credential for {host}",
|
|
8459
|
+
"cred.loadFailed": "Could not read the credential store",
|
|
8460
|
+
"cred.hostInvalid": "Enter a host such as github.com",
|
|
8461
|
+
"cred.hostEmpty": "Enter a host",
|
|
8462
|
+
"cred.secretEmpty": "Enter a token for a new host",
|
|
8463
|
+
"cred.connect": "Sign in with browser",
|
|
8464
|
+
"cred.connecting": "Signing in…",
|
|
8465
|
+
"cred.browserHint": "Approve access in the browser window; the token is saved for this host automatically.",
|
|
8466
|
+
"cred.deviceCode": "Verification code",
|
|
8467
|
+
"cred.deviceHint": "Enter this code in the browser window that just opened",
|
|
8468
|
+
"cred.deviceWaiting": "Waiting for approval…",
|
|
8469
|
+
"cred.deviceExpired": "The code expired — start the sign-in again",
|
|
8470
|
+
"cred.deviceDenied": "The sign-in was denied",
|
|
8471
|
+
"cred.deviceFailed": "The browser sign-in failed: {error}",
|
|
8472
|
+
"cred.deviceNetwork": "Could not reach {host} — check your network or proxy and try again",
|
|
8473
|
+
"cred.createToken": "Create",
|
|
8474
|
+
"panel.remotes": "Remotes",
|
|
8475
|
+
"remotes.title": "Remote addresses",
|
|
8476
|
+
"remotes.hint": "Remotes belong to the whole repository and apply to every worktree of it.",
|
|
8477
|
+
"remotes.empty": "No remote is configured",
|
|
8478
|
+
"remotes.add": "Add remote",
|
|
8479
|
+
"remotes.nameLabel": "Name",
|
|
8480
|
+
"remotes.namePlaceholder": "origin",
|
|
8481
|
+
"remotes.urlLabel": "Address",
|
|
8482
|
+
"remotes.urlPlaceholder": "https://github.com/user/repo.git",
|
|
8483
|
+
"remotes.fetchUrl": "Fetch",
|
|
8484
|
+
"remotes.pushUrl": "Push",
|
|
8485
|
+
"remotes.save": "Save",
|
|
8486
|
+
"remotes.saving": "Saving…",
|
|
8487
|
+
"remotes.edit": "Change address",
|
|
8488
|
+
"remotes.remove": "Remove",
|
|
8489
|
+
"remotes.confirmRemove": "Remove for good",
|
|
8490
|
+
"remotes.cancel": "Cancel",
|
|
8491
|
+
"remotes.saved": "Updated the remote {name}",
|
|
8492
|
+
"remotes.removed": "Removed the remote {name}",
|
|
8493
|
+
"remotes.loadFailed": "Could not read the remotes",
|
|
8494
|
+
"remotes.nameEmpty": "Enter a name",
|
|
8495
|
+
"remotes.nameInvalid": "Only letters, digits, \".\", \"_\" and \"-\" are allowed",
|
|
8496
|
+
"remotes.urlEmpty": "Enter an address",
|
|
8497
|
+
"remotes.urlInvalid": "Enter a valid remote address"
|
|
7416
8498
|
};
|
|
7417
8499
|
/** 中文词典。 */
|
|
7418
8500
|
const zh = {
|
|
@@ -7607,6 +8689,9 @@ window.__ModuleLoader__.load({
|
|
|
7607
8689
|
"panel.tagName": "标签名称",
|
|
7608
8690
|
"panel.tagMessage": "消息(可选)",
|
|
7609
8691
|
"panel.tagDone": "已标记 {tag}",
|
|
8692
|
+
"panel.tagPush": "推送标签 {tag}",
|
|
8693
|
+
"panel.tagPushDone": "已推送标签 {tag} 到 {remote}",
|
|
8694
|
+
"panel.tagPushUpToDate": "标签 {tag} 已存在于 {remote}",
|
|
7610
8695
|
"panel.noCommits": "仓库还没有提交",
|
|
7611
8696
|
"panel.noMatches": "没有匹配的提交",
|
|
7612
8697
|
"panel.loadMore": "加载更多",
|
|
@@ -7630,7 +8715,72 @@ window.__ModuleLoader__.load({
|
|
|
7630
8715
|
"accel.saveFailed": "保存未生效,请重试。",
|
|
7631
8716
|
"accel.savedOn": "已启用国内加速:{url}",
|
|
7632
8717
|
"accel.savedOff": "已关闭国内加速",
|
|
7633
|
-
"accel.mirrorDownHint": "若加速地址不可用,可停用国内加速或更换地址"
|
|
8718
|
+
"accel.mirrorDownHint": "若加速地址不可用,可停用国内加速或更换地址",
|
|
8719
|
+
"panel.credentials": "密钥配置",
|
|
8720
|
+
"cred.title": "Git 密钥配置",
|
|
8721
|
+
"cred.hint": "全局生效:同一域名只需配置一次,该域名的所有仓库共用。密钥只保存在本机 git-credentials.json 中。",
|
|
8722
|
+
"cred.empty": "还没有配置任何密钥",
|
|
8723
|
+
"cred.hostLabel": "域名",
|
|
8724
|
+
"cred.hostPlaceholder": "github.com",
|
|
8725
|
+
"cred.presets": "常用平台",
|
|
8726
|
+
"cred.customPreset": "自定义域名",
|
|
8727
|
+
"cred.secretTokenLabel": "令牌",
|
|
8728
|
+
"cred.secretKeep": "已保存密钥,留空表示不修改",
|
|
8729
|
+
"cred.secretPlaceholder": "粘贴个人访问令牌",
|
|
8730
|
+
"cred.method": "认证方式",
|
|
8731
|
+
"cred.methodToken": "令牌",
|
|
8732
|
+
"cred.methodBrowser": "浏览器登录",
|
|
8733
|
+
"cred.methodPassword": "密码",
|
|
8734
|
+
"cred.getToken": "获取令牌",
|
|
8735
|
+
"cred.add": "新增密钥",
|
|
8736
|
+
"cred.edit": "编辑",
|
|
8737
|
+
"cred.save": "保存",
|
|
8738
|
+
"cred.saving": "保存中…",
|
|
8739
|
+
"cred.delete": "删除",
|
|
8740
|
+
"cred.deleting": "删除中…",
|
|
8741
|
+
"cred.confirmDelete": "确认删除",
|
|
8742
|
+
"cred.cancel": "取消",
|
|
8743
|
+
"cred.saved": "已保存 {host} 的密钥",
|
|
8744
|
+
"cred.deleted": "已删除 {host} 的密钥",
|
|
8745
|
+
"cred.loadFailed": "读取密钥失败",
|
|
8746
|
+
"cred.hostInvalid": "请输入类似 github.com 的域名",
|
|
8747
|
+
"cred.hostEmpty": "请输入域名",
|
|
8748
|
+
"cred.secretEmpty": "新域名必须填写令牌",
|
|
8749
|
+
"cred.connect": "点击跳转登录",
|
|
8750
|
+
"cred.connecting": "正在跳转…",
|
|
8751
|
+
"cred.browserHint": "在浏览器窗口中完成授权,成功后会自动保存该域名的令牌。",
|
|
8752
|
+
"cred.deviceCode": "验证码",
|
|
8753
|
+
"cred.deviceHint": "在刚打开的浏览器窗口输入此验证码",
|
|
8754
|
+
"cred.deviceWaiting": "等待授权中…",
|
|
8755
|
+
"cred.deviceExpired": "验证码已过期,请重新登录",
|
|
8756
|
+
"cred.deviceDenied": "授权已被拒绝",
|
|
8757
|
+
"cred.deviceFailed": "浏览器授权失败:{error}",
|
|
8758
|
+
"cred.deviceNetwork": "无法连接 {host},请检查网络或代理后重试",
|
|
8759
|
+
"cred.createToken": "创建",
|
|
8760
|
+
"panel.remotes": "管理远程",
|
|
8761
|
+
"remotes.title": "远程地址",
|
|
8762
|
+
"remotes.hint": "远程地址属于整个仓库,对该仓库的所有工作树生效。",
|
|
8763
|
+
"remotes.empty": "还没有配置远程地址",
|
|
8764
|
+
"remotes.add": "新增远程",
|
|
8765
|
+
"remotes.nameLabel": "名称",
|
|
8766
|
+
"remotes.namePlaceholder": "origin",
|
|
8767
|
+
"remotes.urlLabel": "地址",
|
|
8768
|
+
"remotes.urlPlaceholder": "https://github.com/user/repo.git",
|
|
8769
|
+
"remotes.fetchUrl": "提取",
|
|
8770
|
+
"remotes.pushUrl": "推送",
|
|
8771
|
+
"remotes.save": "保存",
|
|
8772
|
+
"remotes.saving": "保存中…",
|
|
8773
|
+
"remotes.edit": "修改地址",
|
|
8774
|
+
"remotes.remove": "移除",
|
|
8775
|
+
"remotes.confirmRemove": "确认移除",
|
|
8776
|
+
"remotes.cancel": "取消",
|
|
8777
|
+
"remotes.saved": "已更新远程 {name}",
|
|
8778
|
+
"remotes.removed": "已移除远程 {name}",
|
|
8779
|
+
"remotes.loadFailed": "读取远程地址失败",
|
|
8780
|
+
"remotes.nameEmpty": "请输入名称",
|
|
8781
|
+
"remotes.nameInvalid": "仅允许字母、数字、\".\"、\"_\"、\"-\"",
|
|
8782
|
+
"remotes.urlEmpty": "请输入地址",
|
|
8783
|
+
"remotes.urlInvalid": "请输入有效的远程地址"
|
|
7634
8784
|
};
|
|
7635
8785
|
//#endregion
|
|
7636
8786
|
//#region src/client/index.ts
|