dsh-git-branch-manage 1.0.5 → 1.0.7
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 +920 -194
- package/lib/git.d.ts +90 -1
- package/lib/git.js +114 -2
- package/lib/index.js +4 -2
- package/lib/routes.d.ts +33 -3
- package/lib/routes.js +131 -4
- package/lib/wire.d.ts +50 -0
- package/lib/wire.js +4 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -127,6 +127,10 @@ window.__ModuleLoader__.load({
|
|
|
127
127
|
const ROUTE_LOG = `${ROUTE_PREFIX}/log`;
|
|
128
128
|
/** POST ROUTE_PREFIX/commit-files — the files one commit changed. */
|
|
129
129
|
const ROUTE_COMMIT_FILES = `${ROUTE_PREFIX}/commit-files`;
|
|
130
|
+
/** POST ROUTE_PREFIX/tag — create a tag at an existing commit. */
|
|
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`;
|
|
130
134
|
/** POST ROUTE_PREFIX/ai-commit-message — one-shot LLM commit message from
|
|
131
135
|
* the staged diff (mounted only where the host provides the llm service). */
|
|
132
136
|
const ROUTE_AI_COMMIT_MESSAGE = `${ROUTE_PREFIX}/ai-commit-message`;
|
|
@@ -388,16 +392,19 @@ window.__ModuleLoader__.load({
|
|
|
388
392
|
/**
|
|
389
393
|
* One page of commit history of the checkout.
|
|
390
394
|
* @param repoPath - absolute directory inside the repository.
|
|
391
|
-
* @param
|
|
392
|
-
* @param skip - offset into the history.
|
|
393
|
-
* @param ref - optional starting rev (a branch name); absent = HEAD.
|
|
395
|
+
* @param query - paging and the branch/author/time narrowing.
|
|
394
396
|
*/
|
|
395
|
-
function requestLog(repoPath,
|
|
397
|
+
function requestLog(repoPath, query = {}) {
|
|
398
|
+
const { limit, skip, ref, all, author, since, until } = query;
|
|
396
399
|
return post(ROUTE_LOG, {
|
|
397
400
|
repoPath,
|
|
398
401
|
...limit === void 0 ? {} : { limit },
|
|
399
402
|
...skip === void 0 ? {} : { skip },
|
|
400
|
-
...ref === void 0 || ref === "" ? {} : { ref }
|
|
403
|
+
...ref === void 0 || ref === "" ? {} : { ref },
|
|
404
|
+
...all === true ? { all } : {},
|
|
405
|
+
...author === void 0 || author === "" ? {} : { author },
|
|
406
|
+
...since === void 0 ? {} : { since },
|
|
407
|
+
...until === void 0 ? {} : { until }
|
|
401
408
|
});
|
|
402
409
|
}
|
|
403
410
|
/**
|
|
@@ -412,6 +419,34 @@ window.__ModuleLoader__.load({
|
|
|
412
419
|
});
|
|
413
420
|
}
|
|
414
421
|
/**
|
|
422
|
+
* Create a tag pointing at one commit (annotated when a message is given,
|
|
423
|
+
* lightweight otherwise). Local only: pushing tags is a separate release step.
|
|
424
|
+
* @param repoPath - absolute directory inside the repository.
|
|
425
|
+
* @param name - the new tag name.
|
|
426
|
+
* @param target - the commit to tag; absent = HEAD.
|
|
427
|
+
* @param message - annotation message; absent = lightweight tag.
|
|
428
|
+
*/
|
|
429
|
+
function requestCreateTag(repoPath, name, target, message) {
|
|
430
|
+
return post(ROUTE_TAG, {
|
|
431
|
+
repoPath,
|
|
432
|
+
name,
|
|
433
|
+
...target === void 0 || target === "" ? {} : { target },
|
|
434
|
+
...message === void 0 || message.trim() === "" ? {} : { message }
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Push ONE local tag to its remote (the host resolves which remote). Unlike
|
|
439
|
+
* {@link requestPush}, which sends the current branch alone.
|
|
440
|
+
* @param repoPath - absolute directory inside the repository.
|
|
441
|
+
* @param name - an existing local tag name (verified host-side).
|
|
442
|
+
*/
|
|
443
|
+
function requestPushTag(repoPath, name) {
|
|
444
|
+
return post(ROUTE_TAG_PUSH, {
|
|
445
|
+
repoPath,
|
|
446
|
+
name
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
415
450
|
* Generate a commit message from the checkout's diff with the requesting
|
|
416
451
|
* session's own model (a one-shot host llm call).
|
|
417
452
|
* @param body - the repo path plus the session's model selection and an
|
|
@@ -3614,6 +3649,14 @@ window.__ModuleLoader__.load({
|
|
|
3614
3649
|
return badges;
|
|
3615
3650
|
}
|
|
3616
3651
|
/**
|
|
3652
|
+
* The TAG names one commit's decoration carries, in decoration order — the
|
|
3653
|
+
* per-tag publish targets the commit menu offers. Built on
|
|
3654
|
+
* {@link classifyRefs} so "what counts as a tag" has exactly one definition.
|
|
3655
|
+
*/
|
|
3656
|
+
function commitTags(refs) {
|
|
3657
|
+
return classifyRefs(refs).filter((badge) => badge.kind === "tag").map((badge) => badge.text);
|
|
3658
|
+
}
|
|
3659
|
+
/**
|
|
3617
3660
|
* Split the working-tree rows into the two panel groups. Untracked files
|
|
3618
3661
|
* render in the unstaged group as `?` (git add still stages them); an
|
|
3619
3662
|
* unmerged conflict renders in the unstaged group as `U` unchecked (git add
|
|
@@ -3650,11 +3693,97 @@ window.__ModuleLoader__.load({
|
|
|
3650
3693
|
unstaged
|
|
3651
3694
|
};
|
|
3652
3695
|
}
|
|
3653
|
-
/**
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3696
|
+
/** Day and week windows, in ms (7 days, not a calendar week). */
|
|
3697
|
+
const DAY_MS = 864e5;
|
|
3698
|
+
const WEEK_MS = 7 * DAY_MS;
|
|
3699
|
+
/**
|
|
3700
|
+
* Turn the picker's choice into the `since`/`until` bounds the history call
|
|
3701
|
+
* ships. `day`/`week` are relative to `now` — resolved per request, so a
|
|
3702
|
+
* panel left open an hour does not keep asking for the window it opened
|
|
3703
|
+
* with. `custom` carries the two bounds the date inputs produced (either may
|
|
3704
|
+
* be absent, meaning "open on that side").
|
|
3705
|
+
*/
|
|
3706
|
+
function historyRangeBound(range, now, custom = {}) {
|
|
3707
|
+
if (range === "day") return { since: now - DAY_MS };
|
|
3708
|
+
if (range === "week") return { since: now - WEEK_MS };
|
|
3709
|
+
if (range === "custom") return {
|
|
3710
|
+
...custom.since === void 0 ? {} : { since: custom.since },
|
|
3711
|
+
...custom.until === void 0 ? {} : { until: custom.until }
|
|
3712
|
+
};
|
|
3713
|
+
return {};
|
|
3714
|
+
}
|
|
3715
|
+
/** No filter: every ref, every author, every date. */
|
|
3716
|
+
const EMPTY_HISTORY_FILTERS = {
|
|
3717
|
+
branch: "",
|
|
3718
|
+
author: "",
|
|
3719
|
+
range: "all",
|
|
3720
|
+
customSince: "",
|
|
3721
|
+
customUntil: ""
|
|
3722
|
+
};
|
|
3723
|
+
/** 'YYYY-MM-DD' as a date input emits it. */
|
|
3724
|
+
const DAY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
3725
|
+
/** Midnight of a 'YYYY-MM-DD' day in the BROWSER's zone, or undefined. Read
|
|
3726
|
+
* field by field on purpose: `new Date('2026-10-15')` is parsed as UTC
|
|
3727
|
+
* midnight, which is the previous day west of Greenwich — a custom window
|
|
3728
|
+
* would then start a day early for half the world. */
|
|
3729
|
+
function parseDayStart(value) {
|
|
3730
|
+
const match = DAY_PATTERN.exec(value.trim());
|
|
3731
|
+
if (match === null) return void 0;
|
|
3732
|
+
const [, year, month, day] = match;
|
|
3733
|
+
const date = new Date(Number(year), Number(month) - 1, Number(day));
|
|
3734
|
+
return Number.isFinite(date.getTime()) ? date.getTime() : void 0;
|
|
3735
|
+
}
|
|
3736
|
+
/** Last millisecond of a 'YYYY-MM-DD' day in the browser's zone, or undefined.
|
|
3737
|
+
* The end is inclusive: a custom window ending today must cover commits made
|
|
3738
|
+
* earlier today. */
|
|
3739
|
+
function parseDayEnd(value) {
|
|
3740
|
+
const start = parseDayStart(value);
|
|
3741
|
+
return start === void 0 ? void 0 : start + DAY_MS - 1;
|
|
3742
|
+
}
|
|
3743
|
+
/** The window the filters currently describe, relative to `now`. */
|
|
3744
|
+
function historyFilterBound(filters, now) {
|
|
3745
|
+
if (filters.range !== "custom") return historyRangeBound(filters.range, now);
|
|
3746
|
+
const since = parseDayStart(filters.customSince);
|
|
3747
|
+
const until = parseDayEnd(filters.customUntil);
|
|
3748
|
+
return {
|
|
3749
|
+
...since === void 0 ? {} : { since },
|
|
3750
|
+
...until === void 0 ? {} : { until }
|
|
3751
|
+
};
|
|
3752
|
+
}
|
|
3753
|
+
/**
|
|
3754
|
+
* The history request for one filter state. A chosen branch pins `ref` (and
|
|
3755
|
+
* cancels `all`, which would otherwise widen the walk to every ref); leaving
|
|
3756
|
+
* it blank asks for `all` — the card is meant to show the whole repository,
|
|
3757
|
+
* not just the checked-out branch, and merge edges only exist when the
|
|
3758
|
+
* branches on both sides of a merge are in the page.
|
|
3759
|
+
*/
|
|
3760
|
+
function buildHistoryQuery(state) {
|
|
3761
|
+
const author = state.author.trim();
|
|
3762
|
+
const base = {
|
|
3763
|
+
...author === "" ? {} : { author },
|
|
3764
|
+
...state.bound
|
|
3765
|
+
};
|
|
3766
|
+
if (state.branch !== "") return {
|
|
3767
|
+
...base,
|
|
3768
|
+
ref: state.branch
|
|
3769
|
+
};
|
|
3770
|
+
return {
|
|
3771
|
+
...base,
|
|
3772
|
+
all: true
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
/** The full paged request for one filter state, resolved against `now`. */
|
|
3776
|
+
function historyQueryFor(filters, now, limit, skip) {
|
|
3777
|
+
return {
|
|
3778
|
+
...buildHistoryQuery({
|
|
3779
|
+
branch: filters.branch,
|
|
3780
|
+
author: filters.author,
|
|
3781
|
+
now,
|
|
3782
|
+
bound: historyFilterBound(filters, now)
|
|
3783
|
+
}),
|
|
3784
|
+
limit,
|
|
3785
|
+
skip
|
|
3786
|
+
};
|
|
3658
3787
|
}
|
|
3659
3788
|
/**
|
|
3660
3789
|
* Case-insensitive substring filter over the branch name — the branch card's
|
|
@@ -3740,7 +3869,7 @@ window.__ModuleLoader__.load({
|
|
|
3740
3869
|
}
|
|
3741
3870
|
//#endregion
|
|
3742
3871
|
//#region \0git-worktree-css:/home/runner/work/dsh-git-branch/dsh-git-branch/src/client/GitHistoryPanel.module.css?inline
|
|
3743
|
-
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.commitRow_427795137 {\n position: relative;\n display: grid;\n grid-template-columns: 22px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 6px 8px 6px 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.commitGraph_427795137 {\n position: relative;\n align-self: stretch;\n min-height: 34px;\n}\n\n.commitGraph_427795137::before {\n content: \"\";\n position: absolute;\n left: 10px;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--dsw-alias-border-l2);\n}\n\n.commitRowFirst_427795137 .commitGraph_427795137::before {\n top: 50%;\n}\n\n.commitRowLast_427795137 .commitGraph_427795137::before {\n bottom: 50%;\n}\n\n/* A small plain dot on the rail — the old 10px dot with a fat card-colored\n * ring read as a heavy black blob. #4c6bf5 for the same reason actPrimary\n * hardcodes it (the brand token does not resolve reliably in this panel). */\n.commitDot_427795137 {\n position: absolute;\n left: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #4c6bf5;\n}\n\n.commitDotMerge_427795137 {\n background: #8350d8;\n}\n\n.commitMain_427795137 {\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}\n\n.commitSide_427795137 {\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.commitHash_427795137 {\n font-family: ui-monospace, Consolas, monospace;\n}\n\n.commitExpand_427795137 {\n grid-column: 1 / -1;\n padding: 2px 4px 8px 28px;\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";
|
|
3872
|
+
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";
|
|
3744
3873
|
const tagId$2 = "dsh-git-branch-manage/GitHistoryPanel.module.css";
|
|
3745
3874
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$2 + "\"]") === null) {
|
|
3746
3875
|
const tag = document.createElement("style");
|
|
@@ -3842,13 +3971,18 @@ window.__ModuleLoader__.load({
|
|
|
3842
3971
|
"searchBox": "searchBox_427795137",
|
|
3843
3972
|
"searchBoxHead": "searchBoxHead_427795137",
|
|
3844
3973
|
"searchInput": "searchInput_427795137",
|
|
3974
|
+
"historyFilters": "historyFilters_427795137",
|
|
3975
|
+
"filterSelect": "filterSelect_427795137",
|
|
3976
|
+
"filterInput": "filterInput_427795137",
|
|
3977
|
+
"filterDate": "filterDate_427795137",
|
|
3978
|
+
"filterDash": "filterDash_427795137",
|
|
3979
|
+
"commitList": "commitList_427795137",
|
|
3845
3980
|
"commitRow": "commitRow_427795137",
|
|
3846
3981
|
"commitRowOpen": "commitRowOpen_427795137",
|
|
3847
3982
|
"commitGraph": "commitGraph_427795137",
|
|
3848
|
-
"
|
|
3849
|
-
"commitRowLast": "commitRowLast_427795137",
|
|
3983
|
+
"commitGraphSvg": "commitGraphSvg_427795137",
|
|
3850
3984
|
"commitDot": "commitDot_427795137",
|
|
3851
|
-
"
|
|
3985
|
+
"commitExpandGraph": "commitExpandGraph_427795137",
|
|
3852
3986
|
"commitMain": "commitMain_427795137",
|
|
3853
3987
|
"commitSubject": "commitSubject_427795137",
|
|
3854
3988
|
"commitSubjectText": "commitSubjectText_427795137",
|
|
@@ -3859,7 +3993,9 @@ window.__ModuleLoader__.load({
|
|
|
3859
3993
|
"refRemote": "refRemote_427795137",
|
|
3860
3994
|
"refLocal": "refLocal_427795137",
|
|
3861
3995
|
"commitMeta": "commitMeta_427795137",
|
|
3996
|
+
"commitAuthor": "commitAuthor_427795137",
|
|
3862
3997
|
"commitSide": "commitSide_427795137",
|
|
3998
|
+
"commitDate": "commitDate_427795137",
|
|
3863
3999
|
"commitHash": "commitHash_427795137",
|
|
3864
4000
|
"commitExpand": "commitExpand_427795137",
|
|
3865
4001
|
"commitExpandTitle": "commitExpandTitle_427795137",
|
|
@@ -3873,6 +4009,67 @@ window.__ModuleLoader__.load({
|
|
|
3873
4009
|
"moreRow": "moreRow_427795137"
|
|
3874
4010
|
};
|
|
3875
4011
|
//#endregion
|
|
4012
|
+
//#region src/client/panel/FloatMenu.tsx
|
|
4013
|
+
/**
|
|
4014
|
+
* FloatMenu: a body-portalled, fixed-positioned popup menu anchored to a
|
|
4015
|
+
* viewport point — the shared shell behind the branch list's context menu and
|
|
4016
|
+
* the history rows' commit menu.
|
|
4017
|
+
*
|
|
4018
|
+
* Owns the three things every such menu needs and neither caller should
|
|
4019
|
+
* re-implement: the portal (a scrolling card body would otherwise clip it),
|
|
4020
|
+
* dismissal (pointer-down outside + Escape), and viewport clamping in a
|
|
4021
|
+
* LAYOUT effect (measuring after paint would flash a menu off-screen, and the
|
|
4022
|
+
* clamp must run before the browser shows the frame).
|
|
4023
|
+
*
|
|
4024
|
+
* Position arrives as the anchor point in client coordinates — the caller
|
|
4025
|
+
* derives it from the row it was opened on, so the menu opens where the user
|
|
4026
|
+
* clicked rather than where the row happens to be after a re-render.
|
|
4027
|
+
*
|
|
4028
|
+
* @module git-worktree/client/panel/FloatMenu
|
|
4029
|
+
*/
|
|
4030
|
+
/** Distance kept from the viewport edges, in px. */
|
|
4031
|
+
const VIEWPORT_MARGIN = 8;
|
|
4032
|
+
/**
|
|
4033
|
+
* One floating menu. Rendered into `document.body`, positioned on the layout
|
|
4034
|
+
* pass and clamped into the viewport: flip above the anchor when the menu
|
|
4035
|
+
* would overflow the bottom, clamp the left edge otherwise.
|
|
4036
|
+
*/
|
|
4037
|
+
function FloatMenu(props) {
|
|
4038
|
+
const { anchor, role = "menu", label, onClose, children } = props;
|
|
4039
|
+
const ref = (0, react.useRef)(null);
|
|
4040
|
+
(0, react.useEffect)(() => {
|
|
4041
|
+
const onPointerDown = (event) => {
|
|
4042
|
+
if (ref.current !== null && !ref.current.contains(event.target)) onClose();
|
|
4043
|
+
};
|
|
4044
|
+
const onKeyDown = (event) => {
|
|
4045
|
+
if (event.key === "Escape") onClose();
|
|
4046
|
+
};
|
|
4047
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
4048
|
+
document.addEventListener("keydown", onKeyDown);
|
|
4049
|
+
return () => {
|
|
4050
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
4051
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
4052
|
+
};
|
|
4053
|
+
}, [onClose]);
|
|
4054
|
+
(0, react.useLayoutEffect)(() => {
|
|
4055
|
+
const el = ref.current;
|
|
4056
|
+
if (el === null) return;
|
|
4057
|
+
const rect = el.getBoundingClientRect();
|
|
4058
|
+
const left = Math.max(VIEWPORT_MARGIN, Math.min(anchor.left, window.innerWidth - rect.width - VIEWPORT_MARGIN));
|
|
4059
|
+
let top = anchor.top;
|
|
4060
|
+
if (top + rect.height > window.innerHeight - VIEWPORT_MARGIN) top = Math.max(VIEWPORT_MARGIN, anchor.top - rect.height - VIEWPORT_MARGIN);
|
|
4061
|
+
el.style.left = `${String(Math.round(left))}px`;
|
|
4062
|
+
el.style.top = `${String(Math.round(top))}px`;
|
|
4063
|
+
}, [anchor]);
|
|
4064
|
+
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4065
|
+
ref,
|
|
4066
|
+
className: `${GitHistoryPanel_module_default.menu} ${GitHistoryPanel_module_default.floatMenu}`,
|
|
4067
|
+
role,
|
|
4068
|
+
...label === void 0 ? {} : { "aria-label": label },
|
|
4069
|
+
children
|
|
4070
|
+
}), document.body);
|
|
4071
|
+
}
|
|
4072
|
+
//#endregion
|
|
3876
4073
|
//#region src/client/panel/BranchSection.tsx
|
|
3877
4074
|
/**
|
|
3878
4075
|
* BranchSection: the panel's middle block — the branch list with an IDEA-style
|
|
@@ -3910,35 +4107,9 @@ window.__ModuleLoader__.load({
|
|
|
3910
4107
|
function BranchFloatMenu(props) {
|
|
3911
4108
|
const { state, t, busy, onAction, onClose } = props;
|
|
3912
4109
|
const { branch } = state;
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
if (ref.current !== null && !ref.current.contains(event.target)) onClose();
|
|
3917
|
-
};
|
|
3918
|
-
const onKeyDown = (event) => {
|
|
3919
|
-
if (event.key === "Escape") onClose();
|
|
3920
|
-
};
|
|
3921
|
-
document.addEventListener("pointerdown", onPointerDown, true);
|
|
3922
|
-
document.addEventListener("keydown", onKeyDown);
|
|
3923
|
-
return () => {
|
|
3924
|
-
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
3925
|
-
document.removeEventListener("keydown", onKeyDown);
|
|
3926
|
-
};
|
|
3927
|
-
}, [onClose]);
|
|
3928
|
-
(0, react.useLayoutEffect)(() => {
|
|
3929
|
-
const el = ref.current;
|
|
3930
|
-
if (el === null) return;
|
|
3931
|
-
const rect = el.getBoundingClientRect();
|
|
3932
|
-
const left = Math.max(8, Math.min(state.rect.left, window.innerWidth - rect.width - 8));
|
|
3933
|
-
let top = state.rect.top;
|
|
3934
|
-
if (top + rect.height > window.innerHeight - 8) top = Math.max(8, state.rect.top - rect.height - 8);
|
|
3935
|
-
el.style.left = `${String(Math.round(left))}px`;
|
|
3936
|
-
el.style.top = `${String(Math.round(top))}px`;
|
|
3937
|
-
}, [state]);
|
|
3938
|
-
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3939
|
-
ref,
|
|
3940
|
-
className: `${GitHistoryPanel_module_default.menu} ${GitHistoryPanel_module_default.floatMenu}`,
|
|
3941
|
-
role: "menu",
|
|
4110
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FloatMenu, {
|
|
4111
|
+
anchor: state.rect,
|
|
4112
|
+
onClose,
|
|
3942
4113
|
children: [
|
|
3943
4114
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3944
4115
|
className: GitHistoryPanel_module_default.menuHead,
|
|
@@ -3993,7 +4164,7 @@ window.__ModuleLoader__.load({
|
|
|
3993
4164
|
children: t("panel.branchCopy")
|
|
3994
4165
|
})
|
|
3995
4166
|
]
|
|
3996
|
-
})
|
|
4167
|
+
});
|
|
3997
4168
|
}
|
|
3998
4169
|
/** The branches section. */
|
|
3999
4170
|
function BranchSection(props) {
|
|
@@ -4439,18 +4610,215 @@ window.__ModuleLoader__.load({
|
|
|
4439
4610
|
});
|
|
4440
4611
|
}
|
|
4441
4612
|
//#endregion
|
|
4613
|
+
//#region src/client/git-graph.ts
|
|
4614
|
+
/** Lazily extend `lanes` so `index` exists. */
|
|
4615
|
+
function ensureLane(lanes, index) {
|
|
4616
|
+
while (lanes.length <= index) lanes.push(null);
|
|
4617
|
+
}
|
|
4618
|
+
/** First free lane, appended when every existing lane is busy. */
|
|
4619
|
+
function takeLane(lanes) {
|
|
4620
|
+
const free = lanes.indexOf(null);
|
|
4621
|
+
if (free >= 0) return free;
|
|
4622
|
+
lanes.push(null);
|
|
4623
|
+
return lanes.length - 1;
|
|
4624
|
+
}
|
|
4625
|
+
/**
|
|
4626
|
+
* Compute one row per commit, in the order given.
|
|
4627
|
+
* @param commits - newest-first commits (git log order).
|
|
4628
|
+
* @returns one {@link GraphRow} per input commit, same order.
|
|
4629
|
+
*/
|
|
4630
|
+
function buildGraph(commits) {
|
|
4631
|
+
/** Lane state at the TOP edge of the current row. */
|
|
4632
|
+
const lanes = [];
|
|
4633
|
+
const rows = [];
|
|
4634
|
+
for (const commit of commits) {
|
|
4635
|
+
const incoming = [];
|
|
4636
|
+
for (let index = 0; index < lanes.length; index += 1) if (lanes[index] === commit.hash) incoming.push(index);
|
|
4637
|
+
const converge = incoming[0];
|
|
4638
|
+
const lane = converge === void 0 ? takeLane(lanes) : converge;
|
|
4639
|
+
ensureLane(lanes, lane);
|
|
4640
|
+
if (converge === void 0) incoming.push(lane);
|
|
4641
|
+
const top = lanes.slice();
|
|
4642
|
+
for (const index of incoming) lanes[index] = null;
|
|
4643
|
+
const parentLanes = [];
|
|
4644
|
+
for (let position = 0; position < commit.parents.length; position += 1) {
|
|
4645
|
+
const parent = commit.parents[position];
|
|
4646
|
+
let target;
|
|
4647
|
+
if (position === 0) {
|
|
4648
|
+
target = lane;
|
|
4649
|
+
ensureLane(lanes, target);
|
|
4650
|
+
} else {
|
|
4651
|
+
const waiting = lanes.indexOf(parent);
|
|
4652
|
+
target = waiting >= 0 ? waiting : takeLane(lanes);
|
|
4653
|
+
}
|
|
4654
|
+
lanes[target] = parent;
|
|
4655
|
+
if (!parentLanes.includes(target)) parentLanes.push(target);
|
|
4656
|
+
}
|
|
4657
|
+
const edges = [];
|
|
4658
|
+
for (let index = 0; index < Math.max(top.length, lanes.length); index += 1) {
|
|
4659
|
+
const above = top[index] ?? null;
|
|
4660
|
+
const below = lanes[index] ?? null;
|
|
4661
|
+
if (above === null) continue;
|
|
4662
|
+
if (below === above && index !== lane) {
|
|
4663
|
+
edges.push({
|
|
4664
|
+
kind: "through",
|
|
4665
|
+
from: index,
|
|
4666
|
+
to: index
|
|
4667
|
+
});
|
|
4668
|
+
continue;
|
|
4669
|
+
}
|
|
4670
|
+
edges.push({
|
|
4671
|
+
kind: "in",
|
|
4672
|
+
from: index,
|
|
4673
|
+
to: lane
|
|
4674
|
+
});
|
|
4675
|
+
}
|
|
4676
|
+
for (const target of parentLanes) edges.push({
|
|
4677
|
+
kind: "out",
|
|
4678
|
+
from: lane,
|
|
4679
|
+
to: target
|
|
4680
|
+
});
|
|
4681
|
+
rows.push({
|
|
4682
|
+
lane,
|
|
4683
|
+
lanes: Math.max(top.length, lanes.length),
|
|
4684
|
+
edges
|
|
4685
|
+
});
|
|
4686
|
+
}
|
|
4687
|
+
return trimWidths(rows);
|
|
4688
|
+
}
|
|
4689
|
+
/**
|
|
4690
|
+
* Narrow each row to the lanes it actually uses: a lane released midway (or
|
|
4691
|
+
* one opened only by a later commit) would otherwise reserve dead space in
|
|
4692
|
+
* every row above it.
|
|
4693
|
+
*/
|
|
4694
|
+
function trimWidths(rows) {
|
|
4695
|
+
const widths = rows.map((row) => {
|
|
4696
|
+
let width = 0;
|
|
4697
|
+
for (const edge of row.edges) width = Math.max(width, edge.from + 1, edge.to + 1);
|
|
4698
|
+
return Math.max(width, row.lane + 1);
|
|
4699
|
+
});
|
|
4700
|
+
return rows.map((row, index) => ({
|
|
4701
|
+
...row,
|
|
4702
|
+
lanes: widths[index] ?? 1
|
|
4703
|
+
}));
|
|
4704
|
+
}
|
|
4705
|
+
/** The widest row of a graph — how many lane columns the gutter needs. */
|
|
4706
|
+
function graphWidth(rows) {
|
|
4707
|
+
return rows.reduce((widest, row) => Math.max(widest, row.lanes), 1);
|
|
4708
|
+
}
|
|
4709
|
+
/** Vertical coordinate system for curves, scaled to the actual row height. */
|
|
4710
|
+
const GRAPH_BAND = 44;
|
|
4711
|
+
const LINE_WIDTH = 1.6;
|
|
4712
|
+
const LANE_COLORS = [
|
|
4713
|
+
"#4c6bf5",
|
|
4714
|
+
"#2f9e44",
|
|
4715
|
+
"#e8590c",
|
|
4716
|
+
"#9c36b5",
|
|
4717
|
+
"#0c8599",
|
|
4718
|
+
"#d6336c",
|
|
4719
|
+
"#f08c00",
|
|
4720
|
+
"#5f3dc4"
|
|
4721
|
+
];
|
|
4722
|
+
function laneColor(lane) {
|
|
4723
|
+
return LANE_COLORS[(lane % LANE_COLORS.length + LANE_COLORS.length) % LANE_COLORS.length];
|
|
4724
|
+
}
|
|
4725
|
+
function cx(lane) {
|
|
4726
|
+
return lane * 14 + 7 + 2;
|
|
4727
|
+
}
|
|
4728
|
+
function bend(from, fromY, to, toY) {
|
|
4729
|
+
const x0 = cx(from);
|
|
4730
|
+
const x1 = cx(to);
|
|
4731
|
+
if (x0 === x1) return `M ${String(x0)} ${String(fromY)} L ${String(x1)} ${String(toY)}`;
|
|
4732
|
+
const mid = (toY - fromY) / 2;
|
|
4733
|
+
return `M ${String(x0)} ${String(fromY)} C ${String(x0)} ${String(fromY + mid)} ${String(x1)} ${String(toY - mid)} ${String(x1)} ${String(toY)}`;
|
|
4734
|
+
}
|
|
4735
|
+
function CommitGraph({ row, width, merge }) {
|
|
4736
|
+
const mid = GRAPH_BAND / 2;
|
|
4737
|
+
const dotColor = laneColor(row.lane);
|
|
4738
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4739
|
+
className: GitHistoryPanel_module_default.commitGraphSvg,
|
|
4740
|
+
width,
|
|
4741
|
+
height: "100%",
|
|
4742
|
+
viewBox: `0 0 ${String(width)} ${String(GRAPH_BAND)}`,
|
|
4743
|
+
preserveAspectRatio: "none",
|
|
4744
|
+
"aria-hidden": "true",
|
|
4745
|
+
focusable: "false",
|
|
4746
|
+
children: row.edges.map((edge, index) => {
|
|
4747
|
+
const [fromY, toY] = edge.kind === "through" ? [0, GRAPH_BAND] : edge.kind === "in" ? [0, mid] : [mid, GRAPH_BAND];
|
|
4748
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
4749
|
+
d: bend(edge.from, fromY, edge.to, toY),
|
|
4750
|
+
fill: "none",
|
|
4751
|
+
stroke: laneColor(edge.kind === "out" ? edge.to : edge.from),
|
|
4752
|
+
strokeWidth: LINE_WIDTH,
|
|
4753
|
+
strokeLinecap: "round",
|
|
4754
|
+
vectorEffect: "non-scaling-stroke"
|
|
4755
|
+
}, `${edge.kind}:${String(edge.from)}:${String(edge.to)}:${String(index)}`);
|
|
4756
|
+
})
|
|
4757
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4758
|
+
className: GitHistoryPanel_module_default.commitDot,
|
|
4759
|
+
style: {
|
|
4760
|
+
left: cx(row.lane),
|
|
4761
|
+
color: dotColor,
|
|
4762
|
+
backgroundColor: merge ? "transparent" : dotColor
|
|
4763
|
+
},
|
|
4764
|
+
"aria-hidden": "true"
|
|
4765
|
+
})] });
|
|
4766
|
+
}
|
|
4767
|
+
/** Only bottom-edge lanes continue through an expanded file list. Incoming
|
|
4768
|
+
* lanes already terminate at the dot and must not be resurrected below it. */
|
|
4769
|
+
function CommitGraphContinuation({ row, width }) {
|
|
4770
|
+
const lanes = [...new Set(row.edges.filter((edge) => edge.kind !== "in").map((edge) => edge.to))];
|
|
4771
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
4772
|
+
className: GitHistoryPanel_module_default.commitExpandGraph,
|
|
4773
|
+
width,
|
|
4774
|
+
height: "100%",
|
|
4775
|
+
"aria-hidden": "true",
|
|
4776
|
+
focusable: "false",
|
|
4777
|
+
children: lanes.map((lane) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
|
|
4778
|
+
x1: cx(lane),
|
|
4779
|
+
x2: cx(lane),
|
|
4780
|
+
y1: "0",
|
|
4781
|
+
y2: "100%",
|
|
4782
|
+
stroke: laneColor(lane),
|
|
4783
|
+
strokeWidth: LINE_WIDTH,
|
|
4784
|
+
strokeLinecap: "round"
|
|
4785
|
+
}, lane))
|
|
4786
|
+
});
|
|
4787
|
+
}
|
|
4788
|
+
//#endregion
|
|
4442
4789
|
//#region src/client/panel/HistorySection.tsx
|
|
4443
4790
|
/**
|
|
4444
|
-
* HistorySection: the panel's third block — the commit
|
|
4445
|
-
*
|
|
4446
|
-
*
|
|
4447
|
-
*
|
|
4448
|
-
*
|
|
4449
|
-
*
|
|
4450
|
-
*
|
|
4791
|
+
* HistorySection: the panel's third block — the commit graph. Every row
|
|
4792
|
+
* draws its lane picture (vertical rails, curved merges, hollow dots for
|
|
4793
|
+
* merge commits) from `buildGraph`, then the subject, its refs badges, the
|
|
4794
|
+
* author, and the absolute commit time. CLICKING a row expands the commit's
|
|
4795
|
+
* changed files INLINE below it (exactly one open at a time), loading them on
|
|
4796
|
+
* first open; RIGHT-CLICKING it opens the commit menu (copy hash, copy
|
|
4797
|
+
* subject, new branch here, tag here, and — for every tag the commit already
|
|
4798
|
+
* carries — push that tag), which is where the history card's writing verbs
|
|
4799
|
+
* live. Pushing a tag is offered per EXISTING tag rather than folded into the
|
|
4800
|
+
* branch push: `git push` sends the branch alone, so a tag on an
|
|
4801
|
+
* already-pushed commit would otherwise have no way out of the local repo.
|
|
4802
|
+
*
|
|
4803
|
+
* The head carries the three filters the card asks for, in this order —
|
|
4804
|
+
* author, branch (a ref to pin the walk on, or every ref), and a time window
|
|
4805
|
+
* (all / last 24h / last 7 days / a custom day range). They are NOT applied
|
|
4806
|
+
* here: this section is presentational, so it hands each change up and the
|
|
4807
|
+
* data owner refetches.
|
|
4808
|
+
* Filtering client-side would have been wrong for a paged list — the page
|
|
4809
|
+
* holds 50 commits, and a needle outside it would silently report "no match".
|
|
4451
4810
|
*
|
|
4452
4811
|
* @module git-worktree/client/panel/HistorySection
|
|
4453
4812
|
*/
|
|
4813
|
+
/** `YYYY/MM/DD HH:mm` in the browser's zone — the reference screenshot's
|
|
4814
|
+
* right-hand time column. Field-by-field on purpose: `toLocaleString` varies
|
|
4815
|
+
* with the user's locale and would break the column's alignment. */
|
|
4816
|
+
function formatStamp(date) {
|
|
4817
|
+
if (!Number.isFinite(date) || date <= 0) return "";
|
|
4818
|
+
const at = new Date(date);
|
|
4819
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
4820
|
+
return `${String(at.getFullYear())}/${pad(at.getMonth() + 1)}/${pad(at.getDate())} ${pad(at.getHours())}:${pad(at.getMinutes())}`;
|
|
4821
|
+
}
|
|
4454
4822
|
function statusTitle$1(status, t) {
|
|
4455
4823
|
if (status === "A") return t("panel.stAdd");
|
|
4456
4824
|
if (status === "D") return t("panel.stDel");
|
|
@@ -4494,18 +4862,12 @@ window.__ModuleLoader__.load({
|
|
|
4494
4862
|
/** One history row; `ref` and the tooltip's hover handlers arrive through
|
|
4495
4863
|
* the anchor share when the row is wrapped in a Tooltip. */
|
|
4496
4864
|
const CommitRow = (0, react.forwardRef)(function CommitRow(props, ref) {
|
|
4497
|
-
const { commit,
|
|
4865
|
+
const { commit, graph, graphWidth, open, files, t, onToggle, onMenu } = props;
|
|
4498
4866
|
const badges = classifyRefs(commit.refs);
|
|
4499
|
-
const
|
|
4500
|
-
const absolute = Number.isFinite(commit.date) && commit.date > 0 ? `${when.toLocaleDateString()} ${when.toLocaleTimeString()}` : "";
|
|
4867
|
+
const stamp = formatStamp(commit.date);
|
|
4501
4868
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4502
4869
|
ref,
|
|
4503
|
-
className: [
|
|
4504
|
-
GitHistoryPanel_module_default.commitRow,
|
|
4505
|
-
first ? GitHistoryPanel_module_default.commitRowFirst : "",
|
|
4506
|
-
last ? GitHistoryPanel_module_default.commitRowLast : "",
|
|
4507
|
-
open ? GitHistoryPanel_module_default.commitRowOpen : ""
|
|
4508
|
-
].filter((name) => name !== "").join(" "),
|
|
4870
|
+
className: [GitHistoryPanel_module_default.commitRow, open ? GitHistoryPanel_module_default.commitRowOpen : ""].filter((name) => name !== "").join(" "),
|
|
4509
4871
|
role: "button",
|
|
4510
4872
|
tabIndex: 0,
|
|
4511
4873
|
"aria-expanded": open,
|
|
@@ -4515,6 +4877,13 @@ window.__ModuleLoader__.load({
|
|
|
4515
4877
|
onFocus: props.onFocus,
|
|
4516
4878
|
onBlur: props.onBlur,
|
|
4517
4879
|
onClick: () => onToggle(commit.hash),
|
|
4880
|
+
onContextMenu: (event) => {
|
|
4881
|
+
event.preventDefault();
|
|
4882
|
+
onMenu(commit, {
|
|
4883
|
+
left: event.clientX,
|
|
4884
|
+
top: event.clientY
|
|
4885
|
+
});
|
|
4886
|
+
},
|
|
4518
4887
|
onKeyDown: (event) => {
|
|
4519
4888
|
if (event.key === "Enter" || event.key === " ") {
|
|
4520
4889
|
event.preventDefault();
|
|
@@ -4524,7 +4893,11 @@ window.__ModuleLoader__.load({
|
|
|
4524
4893
|
children: [
|
|
4525
4894
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4526
4895
|
className: GitHistoryPanel_module_default.commitGraph,
|
|
4527
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
4896
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommitGraph, {
|
|
4897
|
+
row: graph,
|
|
4898
|
+
width: graphWidth,
|
|
4899
|
+
merge: commit.parents.length > 1
|
|
4900
|
+
})
|
|
4528
4901
|
}),
|
|
4529
4902
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4530
4903
|
className: GitHistoryPanel_module_default.commitMain,
|
|
@@ -4540,37 +4913,48 @@ window.__ModuleLoader__.load({
|
|
|
4540
4913
|
children: badge.text
|
|
4541
4914
|
}, `${badge.kind}:${badge.text}`))
|
|
4542
4915
|
})]
|
|
4543
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
4916
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4544
4917
|
className: GitHistoryPanel_module_default.commitMeta,
|
|
4545
|
-
children:
|
|
4918
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4919
|
+
className: GitHistoryPanel_module_default.commitAuthor,
|
|
4920
|
+
children: commit.authorName
|
|
4921
|
+
})
|
|
4546
4922
|
})]
|
|
4547
4923
|
}),
|
|
4548
4924
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4549
4925
|
className: GitHistoryPanel_module_default.commitSide,
|
|
4550
4926
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4551
|
-
className: GitHistoryPanel_module_default.
|
|
4552
|
-
|
|
4927
|
+
className: GitHistoryPanel_module_default.commitDate,
|
|
4928
|
+
title: stamp,
|
|
4929
|
+
children: stamp
|
|
4553
4930
|
})
|
|
4554
4931
|
}),
|
|
4555
4932
|
open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4556
4933
|
className: GitHistoryPanel_module_default.commitExpand,
|
|
4557
|
-
children: [
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4934
|
+
children: [
|
|
4935
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommitGraphContinuation, {
|
|
4936
|
+
row: graph,
|
|
4937
|
+
width: graphWidth
|
|
4938
|
+
}),
|
|
4939
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4940
|
+
className: GitHistoryPanel_module_default.commitExpandTitle,
|
|
4941
|
+
children: t("panel.filesTitle", {
|
|
4942
|
+
n: files?.kind === "loaded" ? files.files.length : 0,
|
|
4943
|
+
author: commit.authorName,
|
|
4944
|
+
date: stamp
|
|
4945
|
+
})
|
|
4946
|
+
}),
|
|
4947
|
+
files === void 0 || files.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4948
|
+
className: GitHistoryPanel_module_default.commitExpandTitle,
|
|
4949
|
+
children: t("manager.loading")
|
|
4950
|
+
}) : files.kind === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4951
|
+
className: GitHistoryPanel_module_default.errorText,
|
|
4952
|
+
children: files.message
|
|
4953
|
+
}) : files.files.map((file) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileRow, {
|
|
4954
|
+
file,
|
|
4955
|
+
t
|
|
4956
|
+
}, `${file.status}:${file.path}`))
|
|
4957
|
+
]
|
|
4574
4958
|
}) : null
|
|
4575
4959
|
]
|
|
4576
4960
|
});
|
|
@@ -4582,70 +4966,316 @@ window.__ModuleLoader__.load({
|
|
|
4582
4966
|
return `${commit.subject} · ${commit.authorName} <${commit.authorEmail}> · ${absolute} · ${commit.short}`;
|
|
4583
4967
|
}
|
|
4584
4968
|
function HistorySection(props) {
|
|
4585
|
-
const { t, commits, loaded, loadError, loading, hasMore, loadingMore, expanded, filesByHash, onLoadMore, onToggle,
|
|
4586
|
-
const [
|
|
4587
|
-
const
|
|
4588
|
-
const
|
|
4969
|
+
const { t, commits, loaded, loadError, loading, hasMore, loadingMore, expanded, filesByHash, filters, branches, busy, onLoadMore, onToggle, onFilters, onCreateBranch, onCreateTag, onPushTag, pushToast } = props;
|
|
4970
|
+
const [menu, setMenu] = (0, react.useState)(null);
|
|
4971
|
+
const [dialog, setDialog] = (0, react.useState)(null);
|
|
4972
|
+
const [draft, setDraft] = (0, react.useState)("");
|
|
4973
|
+
const [message, setMessage] = (0, react.useState)("");
|
|
4974
|
+
const [authorDraft, setAuthorDraft] = (0, react.useState)(filters.author);
|
|
4975
|
+
const graph = (0, react.useMemo)(() => buildGraph(commits), [commits]);
|
|
4976
|
+
const bandWidth = (0, react.useMemo)(() => graphWidth(graph) * 14 + 4, [graph]);
|
|
4977
|
+
const openMenu = (commit, rect) => {
|
|
4978
|
+
setMenu({
|
|
4979
|
+
commit,
|
|
4980
|
+
rect
|
|
4981
|
+
});
|
|
4982
|
+
};
|
|
4983
|
+
const openDialog = (kind, commit) => {
|
|
4984
|
+
setMenu(null);
|
|
4985
|
+
setDraft("");
|
|
4986
|
+
setMessage("");
|
|
4987
|
+
setDialog({
|
|
4988
|
+
kind,
|
|
4989
|
+
commit
|
|
4990
|
+
});
|
|
4991
|
+
};
|
|
4992
|
+
const copy = (text) => {
|
|
4993
|
+
setMenu(null);
|
|
4994
|
+
navigator.clipboard.writeText(text).then(() => pushToast(t("hover.copied"), "success"), () => pushToast(t("errorGeneric", { message: t("hover.copied") }), "error"));
|
|
4995
|
+
};
|
|
4996
|
+
const nameIssue = draft.trim() === "" ? "empty" : branchNameIssue(draft.trim());
|
|
4997
|
+
const duplicateBranch = dialog !== null && dialog.kind === "branch" && branches.includes(draft.trim());
|
|
4998
|
+
const dialogError = nameIssue === "empty" ? t("menuNewBranchPlaceholder") : nameIssue !== null ? t("menuNewBranchBad") : duplicateBranch ? t("menuNewBranchExists") : void 0;
|
|
4999
|
+
const submitDialog = () => {
|
|
5000
|
+
if (dialog === null || nameIssue !== null || duplicateBranch) return;
|
|
5001
|
+
const name = draft.trim();
|
|
5002
|
+
if (dialog.kind === "branch") onCreateBranch(name, dialog.commit.hash);
|
|
5003
|
+
else onCreateTag(name, dialog.commit.hash, message.trim());
|
|
5004
|
+
setDialog(null);
|
|
5005
|
+
setDraft("");
|
|
5006
|
+
setMessage("");
|
|
5007
|
+
};
|
|
5008
|
+
/** Commit a pending author needle — Enter or blur, never per keystroke: the
|
|
5009
|
+
* filter is a server-side walk, and one request per character would queue
|
|
5010
|
+
* dozens of `git log` runs behind a single word. */
|
|
5011
|
+
const commitAuthor = () => {
|
|
5012
|
+
const author = authorDraft.trim();
|
|
5013
|
+
if (author === filters.author) return;
|
|
5014
|
+
onFilters({
|
|
5015
|
+
...filters,
|
|
5016
|
+
author
|
|
5017
|
+
});
|
|
5018
|
+
};
|
|
5019
|
+
const filterBar = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5020
|
+
className: GitHistoryPanel_module_default.historyFilters,
|
|
5021
|
+
children: [
|
|
5022
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5023
|
+
type: "text",
|
|
5024
|
+
className: GitHistoryPanel_module_default.filterInput,
|
|
5025
|
+
value: authorDraft,
|
|
5026
|
+
placeholder: t("panel.historyAuthor"),
|
|
5027
|
+
"aria-label": t("panel.historyAuthor"),
|
|
5028
|
+
onChange: (event) => setAuthorDraft(event.target.value),
|
|
5029
|
+
onBlur: commitAuthor,
|
|
5030
|
+
onKeyDown: (event) => {
|
|
5031
|
+
if (event.key === "Enter") {
|
|
5032
|
+
event.preventDefault();
|
|
5033
|
+
commitAuthor();
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
}),
|
|
5037
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
5038
|
+
className: GitHistoryPanel_module_default.filterSelect,
|
|
5039
|
+
value: filters.branch,
|
|
5040
|
+
"aria-label": t("panel.historyBranch"),
|
|
5041
|
+
onChange: (event) => onFilters({
|
|
5042
|
+
...filters,
|
|
5043
|
+
branch: event.target.value
|
|
5044
|
+
}),
|
|
5045
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5046
|
+
value: "",
|
|
5047
|
+
children: t("panel.historyAllRefs")
|
|
5048
|
+
}), branches.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5049
|
+
value: name,
|
|
5050
|
+
children: name
|
|
5051
|
+
}, name))]
|
|
5052
|
+
}),
|
|
5053
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
5054
|
+
className: GitHistoryPanel_module_default.filterSelect,
|
|
5055
|
+
value: filters.range,
|
|
5056
|
+
"aria-label": t("panel.historyTime"),
|
|
5057
|
+
onChange: (event) => onFilters({
|
|
5058
|
+
...filters,
|
|
5059
|
+
range: event.target.value
|
|
5060
|
+
}),
|
|
5061
|
+
children: [
|
|
5062
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5063
|
+
value: "all",
|
|
5064
|
+
children: t("panel.rangeAll")
|
|
5065
|
+
}),
|
|
5066
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5067
|
+
value: "day",
|
|
5068
|
+
children: t("panel.rangeDay")
|
|
5069
|
+
}),
|
|
5070
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5071
|
+
value: "week",
|
|
5072
|
+
children: t("panel.rangeWeek")
|
|
5073
|
+
}),
|
|
5074
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
5075
|
+
value: "custom",
|
|
5076
|
+
children: t("panel.rangeCustom")
|
|
5077
|
+
})
|
|
5078
|
+
]
|
|
5079
|
+
}),
|
|
5080
|
+
filters.range === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5081
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5082
|
+
type: "date",
|
|
5083
|
+
className: GitHistoryPanel_module_default.filterDate,
|
|
5084
|
+
value: filters.customSince,
|
|
5085
|
+
"aria-label": t("panel.rangeSince"),
|
|
5086
|
+
onChange: (event) => onFilters({
|
|
5087
|
+
...filters,
|
|
5088
|
+
customSince: event.target.value
|
|
5089
|
+
})
|
|
5090
|
+
}),
|
|
5091
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5092
|
+
className: GitHistoryPanel_module_default.filterDash,
|
|
5093
|
+
"aria-hidden": "true",
|
|
5094
|
+
children: "–"
|
|
5095
|
+
}),
|
|
5096
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5097
|
+
type: "date",
|
|
5098
|
+
className: GitHistoryPanel_module_default.filterDate,
|
|
5099
|
+
value: filters.customUntil,
|
|
5100
|
+
"aria-label": t("panel.rangeUntil"),
|
|
5101
|
+
onChange: (event) => onFilters({
|
|
5102
|
+
...filters,
|
|
5103
|
+
customUntil: event.target.value
|
|
5104
|
+
})
|
|
5105
|
+
})
|
|
5106
|
+
] }) : null
|
|
5107
|
+
]
|
|
5108
|
+
});
|
|
5109
|
+
const rows = commits.map((commit, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
5110
|
+
label: () => tooltipLabel(commit),
|
|
5111
|
+
side: "bottom",
|
|
5112
|
+
portal: true,
|
|
5113
|
+
maxWidth: 380,
|
|
5114
|
+
delayMs: 250,
|
|
5115
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommitRow, {
|
|
5116
|
+
commit,
|
|
5117
|
+
graph: graph[index] ?? {
|
|
5118
|
+
lane: 0,
|
|
5119
|
+
lanes: 1,
|
|
5120
|
+
edges: []
|
|
5121
|
+
},
|
|
5122
|
+
graphWidth: bandWidth,
|
|
5123
|
+
open: expanded === commit.hash,
|
|
5124
|
+
files: filesByHash[commit.hash],
|
|
5125
|
+
t,
|
|
5126
|
+
onToggle,
|
|
5127
|
+
onMenu: openMenu
|
|
5128
|
+
})
|
|
5129
|
+
}, commit.hash));
|
|
5130
|
+
const dialogTitle = dialog === null ? "" : dialog.kind === "branch" ? t("createFlyAskFrom", { branch: dialog.commit.short }) : t("panel.tagTitle");
|
|
5131
|
+
/** Tags the right-clicked commit carries: one publish item each. */
|
|
5132
|
+
const menuTags = menu === null ? [] : commitTags(menu.commit.refs);
|
|
4589
5133
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4590
5134
|
className: `${GitHistoryPanel_module_default.section} ${GitHistoryPanel_module_default.sectionFill} ${GitHistoryPanel_module_default.sectionHistory}`,
|
|
4591
5135
|
"aria-label": t("panel.history"),
|
|
4592
|
-
children: [
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
className: GitHistoryPanel_module_default.
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
})
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
5136
|
+
children: [
|
|
5137
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5138
|
+
className: GitHistoryPanel_module_default.historyHead,
|
|
5139
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
5140
|
+
className: GitHistoryPanel_module_default.sectionTitle,
|
|
5141
|
+
children: t("panel.history")
|
|
5142
|
+
}), filterBar]
|
|
5143
|
+
}),
|
|
5144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5145
|
+
className: `${GitHistoryPanel_module_default.sectionBody} ${GitHistoryPanel_module_default.sectionBodyScroll}`,
|
|
5146
|
+
style: { "--graph-w": `${String(bandWidth)}px` },
|
|
5147
|
+
children: [loadError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5148
|
+
className: GitHistoryPanel_module_default.errorText,
|
|
5149
|
+
children: loadError
|
|
5150
|
+
}) : !loaded && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5151
|
+
className: GitHistoryPanel_module_default.empty,
|
|
5152
|
+
children: t("manager.loading")
|
|
5153
|
+
}) : commits.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5154
|
+
className: GitHistoryPanel_module_default.empty,
|
|
5155
|
+
children: t("panel.noCommits")
|
|
5156
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5157
|
+
className: GitHistoryPanel_module_default.commitList,
|
|
5158
|
+
children: rows
|
|
5159
|
+
}), hasMore ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5160
|
+
className: GitHistoryPanel_module_default.moreRow,
|
|
5161
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5162
|
+
type: "button",
|
|
5163
|
+
className: GitHistoryPanel_module_default.act,
|
|
5164
|
+
disabled: loadingMore,
|
|
5165
|
+
onClick: onLoadMore,
|
|
5166
|
+
children: t("panel.loadMore")
|
|
5167
|
+
})
|
|
5168
|
+
}) : null]
|
|
5169
|
+
}),
|
|
5170
|
+
menu !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FloatMenu, {
|
|
5171
|
+
anchor: menu.rect,
|
|
5172
|
+
onClose: () => setMenu(null),
|
|
5173
|
+
children: [
|
|
5174
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5175
|
+
className: GitHistoryPanel_module_default.menuHead,
|
|
5176
|
+
children: menu.commit.subject
|
|
5177
|
+
}),
|
|
5178
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5179
|
+
type: "button",
|
|
5180
|
+
className: GitHistoryPanel_module_default.menuItem,
|
|
5181
|
+
role: "menuitem",
|
|
5182
|
+
onClick: () => copy(menu.commit.hash),
|
|
5183
|
+
children: t("panel.commitCopyHash")
|
|
5184
|
+
}),
|
|
5185
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5186
|
+
type: "button",
|
|
5187
|
+
className: GitHistoryPanel_module_default.menuItem,
|
|
5188
|
+
role: "menuitem",
|
|
5189
|
+
onClick: () => copy(menu.commit.subject),
|
|
5190
|
+
children: t("panel.commitCopySubject")
|
|
5191
|
+
}),
|
|
5192
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5193
|
+
type: "button",
|
|
5194
|
+
className: GitHistoryPanel_module_default.menuItem,
|
|
5195
|
+
role: "menuitem",
|
|
5196
|
+
disabled: busy,
|
|
5197
|
+
onClick: () => openDialog("branch", menu.commit),
|
|
5198
|
+
children: t("panel.branchNewFrom")
|
|
5199
|
+
}),
|
|
5200
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5201
|
+
type: "button",
|
|
5202
|
+
className: GitHistoryPanel_module_default.menuItem,
|
|
5203
|
+
role: "menuitem",
|
|
5204
|
+
disabled: busy,
|
|
5205
|
+
onClick: () => openDialog("tag", menu.commit),
|
|
5206
|
+
children: t("panel.branchTag")
|
|
5207
|
+
}),
|
|
5208
|
+
menuTags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5209
|
+
type: "button",
|
|
5210
|
+
className: GitHistoryPanel_module_default.menuItem,
|
|
5211
|
+
role: "menuitem",
|
|
5212
|
+
disabled: busy,
|
|
5213
|
+
onClick: () => {
|
|
5214
|
+
setMenu(null);
|
|
5215
|
+
onPushTag(tag);
|
|
5216
|
+
},
|
|
5217
|
+
children: t("panel.tagPush", { tag })
|
|
5218
|
+
}, tag))
|
|
5219
|
+
]
|
|
5220
|
+
}) : null,
|
|
5221
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
5222
|
+
open: dialog !== null,
|
|
5223
|
+
onClose: () => setDialog(null),
|
|
5224
|
+
title: dialogTitle,
|
|
5225
|
+
closeLabel: t("close"),
|
|
5226
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5227
|
+
size: "sm",
|
|
5228
|
+
variant: "ghost",
|
|
5229
|
+
onClick: () => setDialog(null),
|
|
5230
|
+
children: t("cancel")
|
|
5231
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5232
|
+
size: "sm",
|
|
5233
|
+
variant: "primary",
|
|
5234
|
+
disabled: busy || nameIssue !== null || duplicateBranch,
|
|
5235
|
+
onClick: submitDialog,
|
|
5236
|
+
children: t("actionConfirm")
|
|
5237
|
+
})] }),
|
|
5238
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5239
|
+
className: GitHistoryPanel_module_default.formField,
|
|
5240
|
+
children: [
|
|
5241
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
5242
|
+
className: GitHistoryPanel_module_default.formLabel,
|
|
5243
|
+
htmlFor: "git-panel-commit-name",
|
|
5244
|
+
children: dialog !== null && dialog.kind === "tag" ? t("panel.tagName") : t("panel.branchNew")
|
|
5245
|
+
}),
|
|
5246
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5247
|
+
id: "git-panel-commit-name",
|
|
5248
|
+
className: GitHistoryPanel_module_default.formInput,
|
|
5249
|
+
"data-modal-autofocus": true,
|
|
5250
|
+
value: draft,
|
|
5251
|
+
onChange: (event) => setDraft(event.target.value),
|
|
5252
|
+
onKeyDown: (event) => {
|
|
5253
|
+
if (event.key === "Enter") submitDialog();
|
|
5254
|
+
}
|
|
5255
|
+
}),
|
|
5256
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5257
|
+
className: GitHistoryPanel_module_default.formError,
|
|
5258
|
+
children: dialogError ?? ""
|
|
5259
|
+
})
|
|
5260
|
+
]
|
|
5261
|
+
}), dialog !== null && dialog.kind === "tag" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5262
|
+
className: GitHistoryPanel_module_default.formField,
|
|
5263
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
5264
|
+
className: GitHistoryPanel_module_default.formLabel,
|
|
5265
|
+
htmlFor: "git-panel-tag-message",
|
|
5266
|
+
children: t("panel.tagMessage")
|
|
5267
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5268
|
+
id: "git-panel-tag-message",
|
|
5269
|
+
className: GitHistoryPanel_module_default.formInput,
|
|
5270
|
+
value: message,
|
|
5271
|
+
onChange: (event) => setMessage(event.target.value),
|
|
5272
|
+
onKeyDown: (event) => {
|
|
5273
|
+
if (event.key === "Enter") submitDialog();
|
|
5274
|
+
}
|
|
5275
|
+
})]
|
|
5276
|
+
}) : null]
|
|
5277
|
+
})
|
|
5278
|
+
]
|
|
4649
5279
|
});
|
|
4650
5280
|
}
|
|
4651
5281
|
//#endregion
|
|
@@ -4907,52 +5537,6 @@ window.__ModuleLoader__.load({
|
|
|
4907
5537
|
});
|
|
4908
5538
|
}
|
|
4909
5539
|
//#endregion
|
|
4910
|
-
//#region src/client/relative-time.ts
|
|
4911
|
-
/**
|
|
4912
|
-
* Compact relative time for trailing labels, as a structured bucket the
|
|
4913
|
-
* renderer localizes ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
|
|
4914
|
-
*/
|
|
4915
|
-
function relativeTime(updatedAt, now) {
|
|
4916
|
-
const MIN = 6e4;
|
|
4917
|
-
const HOUR = 36e5;
|
|
4918
|
-
const DAY = 864e5;
|
|
4919
|
-
const diff = Math.max(0, now - updatedAt);
|
|
4920
|
-
if (diff < MIN) return {
|
|
4921
|
-
unit: "now",
|
|
4922
|
-
n: 0
|
|
4923
|
-
};
|
|
4924
|
-
if (diff < HOUR) return {
|
|
4925
|
-
unit: "minutes",
|
|
4926
|
-
n: Math.floor(diff / MIN)
|
|
4927
|
-
};
|
|
4928
|
-
if (diff < DAY) return {
|
|
4929
|
-
unit: "hours",
|
|
4930
|
-
n: Math.floor(diff / HOUR)
|
|
4931
|
-
};
|
|
4932
|
-
if (diff < 30 * DAY) return {
|
|
4933
|
-
unit: "days",
|
|
4934
|
-
n: Math.floor(diff / DAY)
|
|
4935
|
-
};
|
|
4936
|
-
if (diff < 365 * DAY) return {
|
|
4937
|
-
unit: "months",
|
|
4938
|
-
n: Math.floor(diff / (30 * DAY))
|
|
4939
|
-
};
|
|
4940
|
-
return {
|
|
4941
|
-
unit: "years",
|
|
4942
|
-
n: Math.floor(diff / (365 * DAY))
|
|
4943
|
-
};
|
|
4944
|
-
}
|
|
4945
|
-
/** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
|
|
4946
|
-
function timeLabel(updatedAt, now, t) {
|
|
4947
|
-
const { unit, n } = relativeTime(updatedAt, now);
|
|
4948
|
-
if (unit === "now") return t("time.now");
|
|
4949
|
-
if (unit === "minutes") return t("time.minutes", { n });
|
|
4950
|
-
if (unit === "hours") return t("time.hours", { n });
|
|
4951
|
-
if (unit === "days") return t("time.days", { n });
|
|
4952
|
-
if (unit === "months") return t("time.months", { n });
|
|
4953
|
-
return t("time.years", { n });
|
|
4954
|
-
}
|
|
4955
|
-
//#endregion
|
|
4956
5540
|
//#region src/client/GitHistoryPanel.tsx
|
|
4957
5541
|
/**
|
|
4958
5542
|
* GitHistoryPanel: the git-management tab for the dsh-better-sidebar right
|
|
@@ -4972,6 +5556,9 @@ window.__ModuleLoader__.load({
|
|
|
4972
5556
|
*
|
|
4973
5557
|
* @module git-worktree/client/GitHistoryPanel
|
|
4974
5558
|
*/
|
|
5559
|
+
/** One history page. Kept next to the loader: `hasMore` is decided by
|
|
5560
|
+
* comparing the returned length against it. */
|
|
5561
|
+
const HISTORY_PAGE = 50;
|
|
4975
5562
|
/** One git failure → its message text (the git stderr rides error.message). */
|
|
4976
5563
|
function failureText(error) {
|
|
4977
5564
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -4997,11 +5584,15 @@ window.__ModuleLoader__.load({
|
|
|
4997
5584
|
const [commitMenuOpen, setCommitMenuOpen] = (0, react.useState)(false);
|
|
4998
5585
|
const [commitDraft, setCommitDraft] = (0, react.useState)("");
|
|
4999
5586
|
const [aiBusy, setAiBusy] = (0, react.useState)(false);
|
|
5587
|
+
/** The history card's filters. They live HERE, not in the section, because
|
|
5588
|
+
* they are request parameters: the section renders them and hands every
|
|
5589
|
+
* change up, and the loader below re-walks the log with them. */
|
|
5590
|
+
const [historyFilters, setHistoryFilters] = (0, react.useState)(EMPTY_HISTORY_FILTERS);
|
|
5000
5591
|
/** Response-sequence guard: a refresh raced against a mutation or a cwd
|
|
5001
5592
|
* change must never land an older snapshot over a newer one. */
|
|
5002
5593
|
const seqRef = (0, react.useRef)(0);
|
|
5003
5594
|
const loadHistoryPage = (0, react.useCallback)(async (skip) => {
|
|
5004
|
-
const result = await requestLog(cwd,
|
|
5595
|
+
const result = await requestLog(cwd, historyQueryFor(historyFilters, Date.now(), HISTORY_PAGE, skip));
|
|
5005
5596
|
if (!result.ok) {
|
|
5006
5597
|
setHistoryError(result.error);
|
|
5007
5598
|
return;
|
|
@@ -5009,9 +5600,9 @@ window.__ModuleLoader__.load({
|
|
|
5009
5600
|
setHistoryError(void 0);
|
|
5010
5601
|
return {
|
|
5011
5602
|
commits: result.commits,
|
|
5012
|
-
complete: result.commits.length <
|
|
5603
|
+
complete: result.commits.length < HISTORY_PAGE
|
|
5013
5604
|
};
|
|
5014
|
-
}, [cwd]);
|
|
5605
|
+
}, [cwd, historyFilters]);
|
|
5015
5606
|
const loadAll = (0, react.useCallback)(async () => {
|
|
5016
5607
|
const seq = seqRef.current + 1;
|
|
5017
5608
|
seqRef.current = seq;
|
|
@@ -5301,6 +5892,45 @@ window.__ModuleLoader__.load({
|
|
|
5301
5892
|
runGuarded,
|
|
5302
5893
|
t
|
|
5303
5894
|
]);
|
|
5895
|
+
/** Tag one commit. Runs against the repository (a tag is a repository-wide
|
|
5896
|
+
* ref), so the history card can tag a commit that no checkout has. Only
|
|
5897
|
+
* local: pushing tags is a release step, not a side effect of a right-click. */
|
|
5898
|
+
const onCreateTag = (0, react.useCallback)((name, target, message) => {
|
|
5899
|
+
runGuarded(async () => {
|
|
5900
|
+
const result = await requestCreateTag(cwd, name, target, message);
|
|
5901
|
+
if (!result.ok) throw new Error(result.error);
|
|
5902
|
+
pushToast(t("panel.tagDone", { tag: result.tag }), "success");
|
|
5903
|
+
await loadAll();
|
|
5904
|
+
});
|
|
5905
|
+
}, [
|
|
5906
|
+
cwd,
|
|
5907
|
+
loadAll,
|
|
5908
|
+
pushToast,
|
|
5909
|
+
runGuarded,
|
|
5910
|
+
t
|
|
5911
|
+
]);
|
|
5912
|
+
/** Publish ONE local tag. Its own verb, apart from the branch push: `git
|
|
5913
|
+
* push` sends the branch alone, so a tag made on an already-pushed commit
|
|
5914
|
+
* would otherwise never reach the remote. Deliberately no refetch: tags are
|
|
5915
|
+
* local refs and a remote tag changes nothing the panel can see. */
|
|
5916
|
+
const onPushTag = (0, react.useCallback)((name) => {
|
|
5917
|
+
runGuarded(async () => {
|
|
5918
|
+
const result = await requestPushTag(cwd, name);
|
|
5919
|
+
if (!result.ok) throw new Error(result.error);
|
|
5920
|
+
pushToast(result.pushed ? t("panel.tagPushDone", {
|
|
5921
|
+
tag: result.tag,
|
|
5922
|
+
remote: result.remote
|
|
5923
|
+
}) : t("panel.tagPushUpToDate", {
|
|
5924
|
+
tag: result.tag,
|
|
5925
|
+
remote: result.remote
|
|
5926
|
+
}), "success");
|
|
5927
|
+
});
|
|
5928
|
+
}, [
|
|
5929
|
+
cwd,
|
|
5930
|
+
pushToast,
|
|
5931
|
+
runGuarded,
|
|
5932
|
+
t
|
|
5933
|
+
]);
|
|
5304
5934
|
const onToggleCommit = (0, react.useCallback)((hash) => {
|
|
5305
5935
|
if (expanded === hash) {
|
|
5306
5936
|
setExpanded(null);
|
|
@@ -5351,7 +5981,10 @@ window.__ModuleLoader__.load({
|
|
|
5351
5981
|
loadHistoryPage,
|
|
5352
5982
|
loadingMore
|
|
5353
5983
|
]);
|
|
5354
|
-
const
|
|
5984
|
+
const onHistoryFilters = (0, react.useCallback)((next) => {
|
|
5985
|
+
setHistoryFilters(next);
|
|
5986
|
+
setExpanded(null);
|
|
5987
|
+
}, []);
|
|
5355
5988
|
if (statusError !== void 0 && repo === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5356
5989
|
className: GitHistoryPanel_module_default.errorText,
|
|
5357
5990
|
children: t("panel.loadFailed", { message: statusError })
|
|
@@ -5420,9 +6053,16 @@ window.__ModuleLoader__.load({
|
|
|
5420
6053
|
loadingMore,
|
|
5421
6054
|
expanded,
|
|
5422
6055
|
filesByHash,
|
|
6056
|
+
filters: historyFilters,
|
|
6057
|
+
branches: rows.locals.map((row) => row.name),
|
|
6058
|
+
busy: sectionBusy,
|
|
5423
6059
|
onLoadMore,
|
|
5424
6060
|
onToggle: onToggleCommit,
|
|
5425
|
-
|
|
6061
|
+
onFilters: onHistoryFilters,
|
|
6062
|
+
onCreateBranch: onCreate,
|
|
6063
|
+
onCreateTag,
|
|
6064
|
+
onPushTag,
|
|
6065
|
+
pushToast
|
|
5426
6066
|
})
|
|
5427
6067
|
]
|
|
5428
6068
|
});
|
|
@@ -5569,6 +6209,52 @@ window.__ModuleLoader__.load({
|
|
|
5569
6209
|
}
|
|
5570
6210
|
}
|
|
5571
6211
|
//#endregion
|
|
6212
|
+
//#region src/client/relative-time.ts
|
|
6213
|
+
/**
|
|
6214
|
+
* Compact relative time for trailing labels, as a structured bucket the
|
|
6215
|
+
* renderer localizes ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
|
|
6216
|
+
*/
|
|
6217
|
+
function relativeTime(updatedAt, now) {
|
|
6218
|
+
const MIN = 6e4;
|
|
6219
|
+
const HOUR = 36e5;
|
|
6220
|
+
const DAY = 864e5;
|
|
6221
|
+
const diff = Math.max(0, now - updatedAt);
|
|
6222
|
+
if (diff < MIN) return {
|
|
6223
|
+
unit: "now",
|
|
6224
|
+
n: 0
|
|
6225
|
+
};
|
|
6226
|
+
if (diff < HOUR) return {
|
|
6227
|
+
unit: "minutes",
|
|
6228
|
+
n: Math.floor(diff / MIN)
|
|
6229
|
+
};
|
|
6230
|
+
if (diff < DAY) return {
|
|
6231
|
+
unit: "hours",
|
|
6232
|
+
n: Math.floor(diff / HOUR)
|
|
6233
|
+
};
|
|
6234
|
+
if (diff < 30 * DAY) return {
|
|
6235
|
+
unit: "days",
|
|
6236
|
+
n: Math.floor(diff / DAY)
|
|
6237
|
+
};
|
|
6238
|
+
if (diff < 365 * DAY) return {
|
|
6239
|
+
unit: "months",
|
|
6240
|
+
n: Math.floor(diff / (30 * DAY))
|
|
6241
|
+
};
|
|
6242
|
+
return {
|
|
6243
|
+
unit: "years",
|
|
6244
|
+
n: Math.floor(diff / (365 * DAY))
|
|
6245
|
+
};
|
|
6246
|
+
}
|
|
6247
|
+
/** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
|
|
6248
|
+
function timeLabel(updatedAt, now, t) {
|
|
6249
|
+
const { unit, n } = relativeTime(updatedAt, now);
|
|
6250
|
+
if (unit === "now") return t("time.now");
|
|
6251
|
+
if (unit === "minutes") return t("time.minutes", { n });
|
|
6252
|
+
if (unit === "hours") return t("time.hours", { n });
|
|
6253
|
+
if (unit === "days") return t("time.days", { n });
|
|
6254
|
+
if (unit === "months") return t("time.months", { n });
|
|
6255
|
+
return t("time.years", { n });
|
|
6256
|
+
}
|
|
6257
|
+
//#endregion
|
|
5572
6258
|
//#region src/client/concurrency.ts
|
|
5573
6259
|
/**
|
|
5574
6260
|
* Bounded-concurrency map for the browser half's fan-out calls.
|
|
@@ -6747,6 +7433,26 @@ window.__ModuleLoader__.load({
|
|
|
6747
7433
|
"panel.createDone": "Created branch {branch}",
|
|
6748
7434
|
"panel.history": "Commit history",
|
|
6749
7435
|
"panel.historySearch": "Search commits / authors",
|
|
7436
|
+
"panel.historyBranch": "Branch",
|
|
7437
|
+
"panel.historyAllRefs": "All branches",
|
|
7438
|
+
"panel.historyAuthor": "Author",
|
|
7439
|
+
"panel.historyTime": "Time",
|
|
7440
|
+
"panel.rangeAll": "All time",
|
|
7441
|
+
"panel.rangeDay": "Last 24 hours",
|
|
7442
|
+
"panel.rangeWeek": "Last 7 days",
|
|
7443
|
+
"panel.rangeCustom": "Custom range",
|
|
7444
|
+
"panel.rangeSince": "From",
|
|
7445
|
+
"panel.rangeUntil": "To",
|
|
7446
|
+
"panel.commitCopyHash": "Copy hash",
|
|
7447
|
+
"panel.commitCopySubject": "Copy message",
|
|
7448
|
+
"panel.branchTag": "Tag this commit…",
|
|
7449
|
+
"panel.tagTitle": "New tag at {branch}",
|
|
7450
|
+
"panel.tagName": "Tag name",
|
|
7451
|
+
"panel.tagMessage": "Message (optional)",
|
|
7452
|
+
"panel.tagDone": "Tagged {tag}",
|
|
7453
|
+
"panel.tagPush": "Push tag {tag}",
|
|
7454
|
+
"panel.tagPushDone": "Pushed tag {tag} to {remote}",
|
|
7455
|
+
"panel.tagPushUpToDate": "Tag {tag} is already on {remote}",
|
|
6750
7456
|
"panel.noCommits": "This repository has no commits yet",
|
|
6751
7457
|
"panel.noMatches": "No matching commits",
|
|
6752
7458
|
"panel.loadMore": "Load more",
|
|
@@ -6948,6 +7654,26 @@ window.__ModuleLoader__.load({
|
|
|
6948
7654
|
"panel.createDone": "已创建分支 {branch}",
|
|
6949
7655
|
"panel.history": "提交历史",
|
|
6950
7656
|
"panel.historySearch": "搜索提交 / 作者",
|
|
7657
|
+
"panel.historyBranch": "分支",
|
|
7658
|
+
"panel.historyAllRefs": "所有分支",
|
|
7659
|
+
"panel.historyAuthor": "提交人",
|
|
7660
|
+
"panel.historyTime": "时间",
|
|
7661
|
+
"panel.rangeAll": "全部",
|
|
7662
|
+
"panel.rangeDay": "近 24 小时",
|
|
7663
|
+
"panel.rangeWeek": "近 7 天",
|
|
7664
|
+
"panel.rangeCustom": "自定义范围",
|
|
7665
|
+
"panel.rangeSince": "从",
|
|
7666
|
+
"panel.rangeUntil": "到",
|
|
7667
|
+
"panel.commitCopyHash": "复制哈希",
|
|
7668
|
+
"panel.commitCopySubject": "复制消息",
|
|
7669
|
+
"panel.branchTag": "添加标签…",
|
|
7670
|
+
"panel.tagTitle": "在 {branch} 上新标签",
|
|
7671
|
+
"panel.tagName": "标签名称",
|
|
7672
|
+
"panel.tagMessage": "消息(可选)",
|
|
7673
|
+
"panel.tagDone": "已标记 {tag}",
|
|
7674
|
+
"panel.tagPush": "推送标签 {tag}",
|
|
7675
|
+
"panel.tagPushDone": "已推送标签 {tag} 到 {remote}",
|
|
7676
|
+
"panel.tagPushUpToDate": "标签 {tag} 已存在于 {remote}",
|
|
6951
7677
|
"panel.noCommits": "仓库还没有提交",
|
|
6952
7678
|
"panel.noMatches": "没有匹配的提交",
|
|
6953
7679
|
"panel.loadMore": "加载更多",
|