@caozeal/dsh-next-git 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +167 -2
- package/cordis.patch.yml +3 -0
- package/icon.png +0 -0
- package/icon.svg +1 -0
- package/index.js +171 -0
- package/lib/client.js +1790 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +68 -4
- package/src/ai-commit.js +194 -0
- package/src/git.js +399 -0
- package/src/operations.js +510 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1790 @@
|
|
|
1
|
+
// Browser bundle for @caozeal/dsh-next-git.
|
|
2
|
+
//
|
|
3
|
+
// Registers two contributions into the shipped Harness Web UI:
|
|
4
|
+
// - `sidebar.panellist` : the Git icon in the global panel list
|
|
5
|
+
// - `main` (keyed) : the Git panel selected by that icon
|
|
6
|
+
// All data comes from the Host half through `POST /api/git-manager`; this file
|
|
7
|
+
// has no build step and must stay a single classic script. Copy the shell
|
|
8
|
+
// pattern from the other plugins in this repository.
|
|
9
|
+
window.__ModuleLoader__.load({
|
|
10
|
+
id: '@caozeal/dsh-next-git',
|
|
11
|
+
factory: (require) => {
|
|
12
|
+
var module = { exports: {} }
|
|
13
|
+
var exports = module.exports
|
|
14
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
15
|
+
|
|
16
|
+
var React = require('react')
|
|
17
|
+
var h = React.createElement
|
|
18
|
+
var useState = React.useState
|
|
19
|
+
var useEffect = React.useEffect
|
|
20
|
+
var useRef = React.useRef
|
|
21
|
+
|
|
22
|
+
// Host settings namespace / RPC route / slot identity.
|
|
23
|
+
var NS = 'git-manager'
|
|
24
|
+
var RPC_PATH = '/api/git-manager'
|
|
25
|
+
var TAB_ID = 'git-manager'
|
|
26
|
+
var TAB_KIND = 'git-manager'
|
|
27
|
+
var POLL_MS = 5000
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------- locale
|
|
30
|
+
|
|
31
|
+
var ZH = {
|
|
32
|
+
'panel.label': 'Git 管理',
|
|
33
|
+
'panel.title': 'Git 管理',
|
|
34
|
+
'panel.open': '打开 Git 管理',
|
|
35
|
+
'panel.guide': '在当前会话仓库里查看/暂存变更、提交、切换分支、看提交图与 diff',
|
|
36
|
+
'header.refresh': '刷新',
|
|
37
|
+
'header.fetch': 'Fetch',
|
|
38
|
+
'header.pull': 'Pull',
|
|
39
|
+
'header.push': 'Push',
|
|
40
|
+
'header.newBranch': '新建分支',
|
|
41
|
+
'header.newBranchPlaceholder': '新分支名(可再填起点分支)',
|
|
42
|
+
'header.fromPlaceholder': '起点分支(可选)',
|
|
43
|
+
'header.create': '创建',
|
|
44
|
+
'header.cancel': '取消',
|
|
45
|
+
'header.repo': '仓库',
|
|
46
|
+
'header.ahead': '领先 {n}',
|
|
47
|
+
'header.behind': '落后 {n}',
|
|
48
|
+
'header.detached': '游离 HEAD',
|
|
49
|
+
'branch.local': '本地分支',
|
|
50
|
+
'branch.remote': '远程分支',
|
|
51
|
+
'branch.current': '当前',
|
|
52
|
+
'branch.gone': 'upstream 已删除',
|
|
53
|
+
'branch.switch': '切换',
|
|
54
|
+
'branch.delete': '删除',
|
|
55
|
+
'branch.empty': '没有分支',
|
|
56
|
+
'changes.title': '变更',
|
|
57
|
+
'changes.staged': '已暂存',
|
|
58
|
+
'changes.unstaged': '未暂存',
|
|
59
|
+
'changes.untracked': '未跟踪',
|
|
60
|
+
'changes.conflicted': '冲突',
|
|
61
|
+
'changes.empty': '工作区干净,没有未提交的变更',
|
|
62
|
+
'changes.emptyHint': '切到「提交图」看看历史,或者继续写代码',
|
|
63
|
+
'changes.stageAll': '全部暂存',
|
|
64
|
+
'changes.unstageAll': '全部取消暂存',
|
|
65
|
+
'changes.stage': '暂存',
|
|
66
|
+
'changes.unstage': '取消暂存',
|
|
67
|
+
'commit.title': '提交信息',
|
|
68
|
+
'commit.placeholder': '填写提交信息,或先点「AI 生成信息」',
|
|
69
|
+
'commit.hintPlaceholder': '给 AI 的补充说明(可选)',
|
|
70
|
+
'commit.cmdEnter': '⌘↵ 提交',
|
|
71
|
+
'commit.ai': 'AI 生成信息',
|
|
72
|
+
'commit.aiShort': 'AI 生成',
|
|
73
|
+
'commit.aiBusy': '生成中…',
|
|
74
|
+
'commit.commit': '提交',
|
|
75
|
+
'commit.push': '提交并推送',
|
|
76
|
+
'commit.messageRequired': '请先填写提交信息',
|
|
77
|
+
'commit.empty': '没有可提交的暂存变更',
|
|
78
|
+
'tabs.changes': '变更',
|
|
79
|
+
'tabs.graph': '提交图',
|
|
80
|
+
'tabs.branches': '分支',
|
|
81
|
+
'tabs.diff': '差异',
|
|
82
|
+
'graph.empty': '暂无提交',
|
|
83
|
+
'graph.truncated': '提交图已截断',
|
|
84
|
+
'diff.empty': '点击左侧变更文件或提交图中的提交,查看差异',
|
|
85
|
+
'diff.binary': '二进制文件,不显示文本差异',
|
|
86
|
+
'diff.truncated': '差异过大,已截断',
|
|
87
|
+
'diff.additions': '+{n}',
|
|
88
|
+
'diff.deletions': '-{n}',
|
|
89
|
+
'diff.staged': '暂存区',
|
|
90
|
+
'diff.worktree': '工作区',
|
|
91
|
+
'diff.commit': '提交 {hash}',
|
|
92
|
+
'diff.untracked': '新文件',
|
|
93
|
+
'status.loading': '加载中…',
|
|
94
|
+
'status.noSession': '请先在左侧选择一个会话,Git 面板跟随当前会话的工作目录',
|
|
95
|
+
'status.noCwd': '当前会话没有工作目录',
|
|
96
|
+
'status.notRepo': '当前目录不是 Git 仓库:{path}',
|
|
97
|
+
'status.notRepoTitle': '不是 Git 仓库',
|
|
98
|
+
'status.error': '出错了:{message}',
|
|
99
|
+
'status.busy': '执行中:{label}',
|
|
100
|
+
'status.fetching': 'Fetch 中…',
|
|
101
|
+
'status.pulling': 'Pull 中…',
|
|
102
|
+
'status.pushing': 'Push 中…',
|
|
103
|
+
'status.switching': '正在切换到 {branch}…',
|
|
104
|
+
'status.staging': '正在暂存…',
|
|
105
|
+
'status.unstaging': '正在取消暂存…',
|
|
106
|
+
'status.committing': '正在提交…',
|
|
107
|
+
'status.commitOk': '已提交 {hash}',
|
|
108
|
+
'status.pushOk': '推送完成',
|
|
109
|
+
'status.pullOk': '拉取完成',
|
|
110
|
+
'status.fetchOk': 'Fetch 完成',
|
|
111
|
+
'status.branchSwitched': '已切换到 {branch}',
|
|
112
|
+
'status.branchCreated': '已创建分支 {branch}',
|
|
113
|
+
'status.branchDeleted': '已删除分支 {branch}',
|
|
114
|
+
'status.deleteConfirm': '确定删除分支 {branch}?',
|
|
115
|
+
'status.aiModel': '由 {model} 生成',
|
|
116
|
+
'status.aiFallback': '模型输出不可用,已生成降级文案',
|
|
117
|
+
'toast.error': '失败',
|
|
118
|
+
'details.output': '最近一次操作输出',
|
|
119
|
+
'details.dismiss': '关闭',
|
|
120
|
+
'session.untitled': '未命名会话',
|
|
121
|
+
'error.transport': '无法连接 Git 服务',
|
|
122
|
+
'error.badResponse': 'Git 服务返回了非 JSON 响应',
|
|
123
|
+
'error.gitFailed': 'Git 操作失败',
|
|
124
|
+
'error.boundary': 'Git 面板渲染出错',
|
|
125
|
+
'status.cwdUnavailable': '当前会话的工作目录不存在或无法访问:{path}',
|
|
126
|
+
'status.stageOk': '已暂存',
|
|
127
|
+
'status.unstageOk': '已取消暂存',
|
|
128
|
+
'status.stageAllOk': '已暂存全部变更',
|
|
129
|
+
'status.unstageAllOk': '已取消暂存全部变更',
|
|
130
|
+
'status.aiOk': '已生成提交信息({model})',
|
|
131
|
+
'status.aiModelEdit': '由 {model} 生成,可继续编辑',
|
|
132
|
+
'status.ready': '就绪',
|
|
133
|
+
'changes.stagedCount': '已暂存 {n}',
|
|
134
|
+
'changes.conflictBanner': '存在 {n} 个冲突文件,解决后再提交',
|
|
135
|
+
'branch.selectTitle': '切换分支',
|
|
136
|
+
'header.refreshing': '刷新中…',
|
|
137
|
+
'commit.committing': '提交中…',
|
|
138
|
+
'diff.truncatedShort': '已截断',
|
|
139
|
+
'diff.noText': '没有文本差异',
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
var EN = {
|
|
143
|
+
'panel.label': 'Git',
|
|
144
|
+
'panel.title': 'Git Manager',
|
|
145
|
+
'panel.open': 'Open Git Manager',
|
|
146
|
+
'panel.guide': 'Review and stage changes, commit, switch branches, browse the graph and diffs for this session repository',
|
|
147
|
+
'header.refresh': 'Refresh',
|
|
148
|
+
'header.fetch': 'Fetch',
|
|
149
|
+
'header.pull': 'Pull',
|
|
150
|
+
'header.push': 'Push',
|
|
151
|
+
'header.newBranch': 'New branch',
|
|
152
|
+
'header.newBranchPlaceholder': 'new branch name',
|
|
153
|
+
'header.fromPlaceholder': 'start point (optional)',
|
|
154
|
+
'header.create': 'Create',
|
|
155
|
+
'header.cancel': 'Cancel',
|
|
156
|
+
'header.repo': 'Repo',
|
|
157
|
+
'header.ahead': '{n} ahead',
|
|
158
|
+
'header.behind': '{n} behind',
|
|
159
|
+
'header.detached': 'detached HEAD',
|
|
160
|
+
'branch.local': 'Local branches',
|
|
161
|
+
'branch.remote': 'Remote branches',
|
|
162
|
+
'branch.current': 'current',
|
|
163
|
+
'branch.gone': 'upstream gone',
|
|
164
|
+
'branch.switch': 'Switch',
|
|
165
|
+
'branch.delete': 'Delete',
|
|
166
|
+
'branch.empty': 'No branches',
|
|
167
|
+
'changes.title': 'Changes',
|
|
168
|
+
'changes.staged': 'Staged',
|
|
169
|
+
'changes.unstaged': 'Unstaged',
|
|
170
|
+
'changes.untracked': 'Untracked',
|
|
171
|
+
'changes.conflicted': 'Conflicts',
|
|
172
|
+
'changes.empty': 'Working tree clean',
|
|
173
|
+
'changes.emptyHint': 'Check the commit graph, or keep coding',
|
|
174
|
+
'changes.stageAll': 'Stage all',
|
|
175
|
+
'changes.unstageAll': 'Unstage all',
|
|
176
|
+
'changes.stage': 'Stage',
|
|
177
|
+
'changes.unstage': 'Unstage',
|
|
178
|
+
'commit.title': 'Commit message',
|
|
179
|
+
'commit.placeholder': 'Write a commit message, or generate one with AI',
|
|
180
|
+
'commit.hintPlaceholder': 'Optional hint for the AI',
|
|
181
|
+
'commit.cmdEnter': '⌘↵ commit',
|
|
182
|
+
'commit.ai': 'AI message',
|
|
183
|
+
'commit.aiShort': 'AI',
|
|
184
|
+
'commit.aiBusy': 'Generating…',
|
|
185
|
+
'commit.commit': 'Commit',
|
|
186
|
+
'commit.push': 'Commit & push',
|
|
187
|
+
'commit.messageRequired': 'Commit message is required',
|
|
188
|
+
'commit.empty': 'Nothing staged to commit',
|
|
189
|
+
'tabs.changes': 'Changes',
|
|
190
|
+
'tabs.graph': 'Graph',
|
|
191
|
+
'tabs.branches': 'Branches',
|
|
192
|
+
'tabs.diff': 'Diff',
|
|
193
|
+
'graph.empty': 'No commits yet',
|
|
194
|
+
'graph.truncated': 'Graph truncated',
|
|
195
|
+
'diff.empty': 'Pick a changed file or a commit to see its diff',
|
|
196
|
+
'diff.binary': 'Binary file; no textual diff',
|
|
197
|
+
'diff.truncated': 'Diff truncated',
|
|
198
|
+
'diff.additions': '+{n}',
|
|
199
|
+
'diff.deletions': '-{n}',
|
|
200
|
+
'diff.staged': 'Staged',
|
|
201
|
+
'diff.worktree': 'Working tree',
|
|
202
|
+
'diff.commit': 'Commit {hash}',
|
|
203
|
+
'diff.untracked': 'New file',
|
|
204
|
+
'status.loading': 'Loading…',
|
|
205
|
+
'status.noSession': 'Select a session on the left; the Git panel follows its working directory',
|
|
206
|
+
'status.noCwd': 'The current session has no working directory',
|
|
207
|
+
'status.notRepo': 'Not a Git repository: {path}',
|
|
208
|
+
'status.notRepoTitle': 'Not a Git repository',
|
|
209
|
+
'status.error': 'Error: {message}',
|
|
210
|
+
'status.busy': 'Running: {label}',
|
|
211
|
+
'status.fetching': 'Fetching…',
|
|
212
|
+
'status.pulling': 'Pulling…',
|
|
213
|
+
'status.pushing': 'Pushing…',
|
|
214
|
+
'status.switching': 'Switching to {branch}…',
|
|
215
|
+
'status.staging': 'Staging…',
|
|
216
|
+
'status.unstaging': 'Unstaging…',
|
|
217
|
+
'status.committing': 'Committing…',
|
|
218
|
+
'status.commitOk': 'Committed {hash}',
|
|
219
|
+
'status.pushOk': 'Push complete',
|
|
220
|
+
'status.pullOk': 'Pull complete',
|
|
221
|
+
'status.fetchOk': 'Fetch complete',
|
|
222
|
+
'status.branchSwitched': 'Switched to {branch}',
|
|
223
|
+
'status.branchCreated': 'Created branch {branch}',
|
|
224
|
+
'status.branchDeleted': 'Deleted branch {branch}',
|
|
225
|
+
'status.deleteConfirm': 'Delete branch {branch}?',
|
|
226
|
+
'status.aiModel': 'Generated by {model}',
|
|
227
|
+
'status.aiFallback': 'Model output unusable; used a fallback message',
|
|
228
|
+
'toast.error': 'Failed',
|
|
229
|
+
'details.output': 'Last command output',
|
|
230
|
+
'details.dismiss': 'Dismiss',
|
|
231
|
+
'session.untitled': 'Untitled session',
|
|
232
|
+
'error.transport': 'Cannot reach the Git service',
|
|
233
|
+
'error.badResponse': 'The Git service returned a non-JSON response',
|
|
234
|
+
'error.gitFailed': 'Git operation failed',
|
|
235
|
+
'error.boundary': 'The Git panel crashed',
|
|
236
|
+
'status.cwdUnavailable': 'The session working directory is missing or inaccessible: {path}',
|
|
237
|
+
'status.stageOk': 'Staged',
|
|
238
|
+
'status.unstageOk': 'Unstaged',
|
|
239
|
+
'status.stageAllOk': 'Staged all changes',
|
|
240
|
+
'status.unstageAllOk': 'Unstaged all changes',
|
|
241
|
+
'status.aiOk': 'Commit message generated ({model})',
|
|
242
|
+
'status.aiModelEdit': 'Generated by {model}; edit as needed',
|
|
243
|
+
'status.ready': 'Ready',
|
|
244
|
+
'changes.stagedCount': '{n} staged',
|
|
245
|
+
'changes.conflictBanner': '{n} conflicted file(s); resolve them before committing',
|
|
246
|
+
'branch.selectTitle': 'Switch branch',
|
|
247
|
+
'header.refreshing': 'Refreshing…',
|
|
248
|
+
'commit.committing': 'Committing…',
|
|
249
|
+
'diff.truncatedShort': 'truncated',
|
|
250
|
+
'diff.noText': 'No textual diff',
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------- styles
|
|
254
|
+
|
|
255
|
+
var mono = 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace'
|
|
256
|
+
|
|
257
|
+
// One component-scoped stylesheet, rendered as a <style> element inside the
|
|
258
|
+
// panel root (removed with the panel). Class names are prefixed `gm-` and all
|
|
259
|
+
// colors come from --dsw-alias-* tokens, so light/dark follow the app theme.
|
|
260
|
+
var PANEL_CSS = `
|
|
261
|
+
/* ---- DeepSeek-blue whale palette ---------------------------------- */
|
|
262
|
+
.gm-root {
|
|
263
|
+
--gm-blue: #4d6bfe;
|
|
264
|
+
--gm-blue-2: #7b93ff;
|
|
265
|
+
--gm-cyan: #43c9ff;
|
|
266
|
+
--gm-deep: #2f49d6;
|
|
267
|
+
--gm-ink: var(--dsw-alias-label-primary);
|
|
268
|
+
--gm-dim: var(--dsw-alias-label-secondary);
|
|
269
|
+
--gm-line: var(--dsw-alias-border-l1);
|
|
270
|
+
--gm-card: var(--dsw-alias-bg-layer-1);
|
|
271
|
+
--gm-card2: var(--dsw-alias-bg-layer-2);
|
|
272
|
+
--gm-base: var(--dsw-alias-bg-base);
|
|
273
|
+
--gm-blue-04: color-mix(in srgb, var(--gm-blue) 4%, transparent);
|
|
274
|
+
--gm-blue-06: color-mix(in srgb, var(--gm-blue) 6%, transparent);
|
|
275
|
+
--gm-blue-08: color-mix(in srgb, var(--gm-blue) 8%, transparent);
|
|
276
|
+
--gm-blue-12: color-mix(in srgb, var(--gm-blue) 12%, transparent);
|
|
277
|
+
--gm-blue-18: color-mix(in srgb, var(--gm-blue) 18%, transparent);
|
|
278
|
+
--gm-blue-28: color-mix(in srgb, var(--gm-blue) 28%, transparent);
|
|
279
|
+
--gm-blue-40: color-mix(in srgb, var(--gm-blue) 40%, transparent);
|
|
280
|
+
--gm-ok: var(--dsw-alias-state-success-primary);
|
|
281
|
+
--gm-err: var(--dsw-alias-state-error-primary);
|
|
282
|
+
--gm-warn: var(--dsw-alias-state-warn-primary);
|
|
283
|
+
display:flex; flex-direction:column; height:100%; min-height:0;
|
|
284
|
+
container-type:inline-size; container-name:gm;
|
|
285
|
+
background:linear-gradient(180deg, var(--gm-blue-04), transparent 180px), var(--gm-base);
|
|
286
|
+
color:var(--gm-ink); font-size:12px; line-height:1.5;
|
|
287
|
+
animation:gm-fade .18s ease;
|
|
288
|
+
}
|
|
289
|
+
.gm-root *, .gm-root *::before, .gm-root *::after { box-sizing:border-box; }
|
|
290
|
+
.gm-scroll { flex:1 1 0; min-height:0; overflow:auto; scrollbar-width:thin;
|
|
291
|
+
scrollbar-color:var(--gm-blue-28) transparent; }
|
|
292
|
+
.gm-scroll::-webkit-scrollbar { width:10px; height:10px; }
|
|
293
|
+
.gm-scroll::-webkit-scrollbar-thumb { background:var(--gm-blue-28); border-radius:99px; border:3px solid transparent; background-clip:content-box; }
|
|
294
|
+
.gm-scroll::-webkit-scrollbar-track { background:transparent; }
|
|
295
|
+
.gm-scroll::-webkit-scrollbar-thumb:hover { background:var(--gm-blue-40); background-clip:content-box; }
|
|
296
|
+
.gm-content-inner { width:100%; }
|
|
297
|
+
.gm-row { display:flex; align-items:center; gap:6px; min-width:0; }
|
|
298
|
+
.gm-spacer { flex:1 1 auto; min-width:0; }
|
|
299
|
+
.gm-mark { display:inline-flex; align-items:center; justify-content:center; flex:none;
|
|
300
|
+
background:linear-gradient(150deg, var(--gm-blue-12), var(--gm-blue-06));
|
|
301
|
+
box-shadow:inset 0 0 0 1px var(--gm-blue-18), 0 2px 6px var(--gm-blue-12); }
|
|
302
|
+
.gm-mark img { width:80%; height:80%; object-fit:contain; display:block;
|
|
303
|
+
filter:drop-shadow(0 1px 3px var(--gm-blue-28)); }
|
|
304
|
+
|
|
305
|
+
/* ---- playful top bar ---------------------------------------------- */
|
|
306
|
+
.gm-top { position:relative; flex:none; display:flex; flex-direction:column; gap:7px; padding:10px 12px 9px;
|
|
307
|
+
border-bottom:1px solid var(--gm-line); overflow:hidden;
|
|
308
|
+
background:linear-gradient(135deg, color-mix(in srgb, var(--gm-blue) 12%, var(--gm-card)), var(--gm-card) 72%); }
|
|
309
|
+
.gm-top::before { content:''; position:absolute; right:-34px; top:-46px; width:132px; height:132px; border-radius:50%;
|
|
310
|
+
background:radial-gradient(circle at 32% 32%, color-mix(in srgb, var(--gm-cyan) 42%, transparent), transparent 68%);
|
|
311
|
+
opacity:.55; pointer-events:none; }
|
|
312
|
+
.gm-top::after { content:''; position:absolute; left:-20px; bottom:-30px; width:90px; height:90px; border-radius:50%;
|
|
313
|
+
background:radial-gradient(circle, var(--gm-blue-18), transparent 70%); pointer-events:none; }
|
|
314
|
+
.gm-top > * { position:relative; z-index:1; }
|
|
315
|
+
.gm-repo { font-size:13.5px; font-weight:700; letter-spacing:-0.01em; white-space:nowrap; overflow:hidden;
|
|
316
|
+
text-overflow:ellipsis; min-width:0; }
|
|
317
|
+
.gm-repo-dot { width:7px; height:7px; border-radius:50%; flex:none;
|
|
318
|
+
background:linear-gradient(135deg, var(--gm-cyan), var(--gm-blue)); box-shadow:0 0 0 3px var(--gm-blue-12); }
|
|
319
|
+
.gm-subtle { font-size:10.5px; color:var(--gm-dim); white-space:nowrap; }
|
|
320
|
+
|
|
321
|
+
/* ---- controls ------------------------------------------------------ */
|
|
322
|
+
.gm-btn { display:inline-flex; align-items:center; justify-content:center; gap:4px; height:25px; padding:0 10px;
|
|
323
|
+
border-radius:9px; border:1px solid var(--gm-blue-12); background:var(--gm-blue-06); color:var(--gm-ink);
|
|
324
|
+
font:inherit; font-size:11.5px; line-height:1; cursor:pointer; white-space:nowrap;
|
|
325
|
+
transition:transform .12s, box-shadow .12s, background .12s, border-color .12s, color .12s, opacity .12s; }
|
|
326
|
+
.gm-btn svg { width:13px; height:13px; flex:none; }
|
|
327
|
+
.gm-btn:hover:not(:disabled) { background:var(--gm-blue-12); border-color:var(--gm-blue-28); color:var(--gm-blue); transform:translateY(-1px); }
|
|
328
|
+
.gm-btn:active:not(:disabled) { transform:translateY(0); }
|
|
329
|
+
.gm-btn:focus-visible { outline:2px solid var(--gm-blue-40); outline-offset:1px; }
|
|
330
|
+
.gm-btn:disabled { opacity:.4; cursor:default; }
|
|
331
|
+
.gm-btn.primary { border-color:transparent; color:#fff; font-weight:600;
|
|
332
|
+
background:linear-gradient(135deg, var(--gm-blue), var(--gm-deep));
|
|
333
|
+
box-shadow:0 4px 14px var(--gm-blue-28); }
|
|
334
|
+
.gm-btn.primary:hover:not(:disabled) { color:#fff; filter:brightness(1.06); box-shadow:0 6px 18px var(--gm-blue-40); }
|
|
335
|
+
.gm-btn.ghost { border-color:transparent; background:transparent; color:var(--gm-dim); }
|
|
336
|
+
.gm-btn.ghost:hover:not(:disabled) { background:var(--gm-blue-08); color:var(--gm-blue); }
|
|
337
|
+
.gm-btn.icon { width:25px; padding:0; }
|
|
338
|
+
.gm-btn.sm { height:21px; font-size:11px; border-radius:7px; }
|
|
339
|
+
.gm-btn.icon.sm { width:21px; }
|
|
340
|
+
.gm-ai-busy { position:relative; overflow:hidden; }
|
|
341
|
+
.gm-ai-busy::after { content:''; position:absolute; inset:0; background:linear-gradient(110deg, transparent 20%, rgba(255,255,255,.35) 50%, transparent 80%);
|
|
342
|
+
transform:translateX(-100%); animation:gm-shimmer 1.2s linear infinite; }
|
|
343
|
+
|
|
344
|
+
.gm-select { height:26px; max-width:174px; padding:0 26px 0 10px; border-radius:999px;
|
|
345
|
+
border:1px solid var(--gm-blue-28); background-color:var(--gm-blue-08); color:var(--gm-ink);
|
|
346
|
+
font-size:11.5px; font-weight:600; cursor:pointer; appearance:none;
|
|
347
|
+
background-image:linear-gradient(45deg,transparent 50%,var(--gm-blue) 50%),linear-gradient(135deg,var(--gm-blue) 50%,transparent 50%);
|
|
348
|
+
background-position:calc(100% - 14px) 11px, calc(100% - 10px) 11px; background-size:4px 4px,4px 4px; background-repeat:no-repeat;
|
|
349
|
+
transition:box-shadow .12s, background-color .12s; }
|
|
350
|
+
.gm-select:hover { background-color:var(--gm-blue-12); box-shadow:0 2px 10px var(--gm-blue-18); }
|
|
351
|
+
.gm-select:focus-visible { outline:2px solid var(--gm-blue-40); outline-offset:-1px; }
|
|
352
|
+
|
|
353
|
+
/* ---- badges -------------------------------------------------------- */
|
|
354
|
+
.gm-badge { display:inline-flex; align-items:center; gap:3px; height:18px; padding:0 7px; border-radius:999px;
|
|
355
|
+
border:1px solid var(--gm-blue-18); background:var(--gm-blue-06); color:var(--gm-dim);
|
|
356
|
+
font-size:10.5px; font-variant-numeric:tabular-nums; white-space:nowrap; flex:none; }
|
|
357
|
+
.gm-badge.brand { color:#fff; border-color:transparent;
|
|
358
|
+
background:linear-gradient(135deg, var(--gm-blue), var(--gm-cyan)); box-shadow:0 2px 8px var(--gm-blue-28); }
|
|
359
|
+
.gm-badge.ok { color:var(--gm-ok); border-color:color-mix(in srgb, var(--gm-ok) 35%, transparent); background:color-mix(in srgb, var(--gm-ok) 8%, transparent); }
|
|
360
|
+
.gm-badge.err { color:var(--gm-err); border-color:color-mix(in srgb, var(--gm-err) 35%, transparent); background:color-mix(in srgb, var(--gm-err) 8%, transparent); }
|
|
361
|
+
.gm-badge.warn { color:var(--gm-warn); border-color:color-mix(in srgb, var(--gm-warn) 38%, transparent); background:color-mix(in srgb, var(--gm-warn) 10%, transparent); }
|
|
362
|
+
|
|
363
|
+
/* ---- pill tabs ----------------------------------------------------- */
|
|
364
|
+
.gm-tabs { flex:none; display:flex; align-items:center; gap:4px; padding:7px 10px; border-bottom:1px solid var(--gm-line);
|
|
365
|
+
background:var(--gm-card); overflow-x:auto; scrollbar-width:none; }
|
|
366
|
+
.gm-tabs::-webkit-scrollbar { display:none; }
|
|
367
|
+
.gm-tab { display:inline-flex; align-items:center; gap:5px; height:27px; padding:0 11px; border-radius:999px;
|
|
368
|
+
border:1px solid transparent; background:transparent; color:var(--gm-dim); font:inherit; font-size:12px;
|
|
369
|
+
cursor:pointer; white-space:nowrap; transition:background .14s, color .14s, box-shadow .14s, transform .14s; }
|
|
370
|
+
.gm-tab:hover { background:var(--gm-blue-08); color:var(--gm-blue); }
|
|
371
|
+
.gm-tab.active { color:#fff; font-weight:600;
|
|
372
|
+
background:linear-gradient(135deg, var(--gm-blue), var(--gm-deep));
|
|
373
|
+
box-shadow:0 3px 12px var(--gm-blue-28); }
|
|
374
|
+
.gm-tab:focus-visible { outline:2px solid var(--gm-blue-40); outline-offset:1px; }
|
|
375
|
+
.gm-count { min-width:16px; height:16px; padding:0 4px; border-radius:999px; background:var(--gm-blue-12);
|
|
376
|
+
color:var(--gm-blue); font-size:10px; font-weight:600; display:inline-flex; align-items:center;
|
|
377
|
+
justify-content:center; font-variant-numeric:tabular-nums; }
|
|
378
|
+
.gm-tab.active .gm-count { background:rgba(255,255,255,.24); color:#fff; }
|
|
379
|
+
|
|
380
|
+
/* ---- changes ------------------------------------------------------- */
|
|
381
|
+
.gm-quick { display:flex; gap:6px; padding:9px 10px 1px; flex-wrap:wrap; }
|
|
382
|
+
.gm-section { display:flex; align-items:center; gap:6px; padding:11px 12px 3px; font-size:10.5px;
|
|
383
|
+
font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--gm-dim); }
|
|
384
|
+
.gm-section::before { content:''; width:6px; height:6px; border-radius:2px; flex:none;
|
|
385
|
+
background:linear-gradient(135deg, var(--gm-cyan), var(--gm-blue)); box-shadow:0 0 0 2px var(--gm-blue-12); }
|
|
386
|
+
.gm-section-count { font-weight:500; letter-spacing:0; font-variant-numeric:tabular-nums; color:var(--gm-blue); }
|
|
387
|
+
.gm-file { display:flex; align-items:center; gap:7px; min-height:26px; padding:3px 8px; margin:0 6px;
|
|
388
|
+
border-radius:9px; cursor:pointer; transition:background .12s; }
|
|
389
|
+
.gm-file:hover { background:var(--gm-blue-06); }
|
|
390
|
+
.gm-file.active { background:var(--gm-blue-12); box-shadow:inset 3px 0 0 var(--gm-blue); }
|
|
391
|
+
.gm-stat { width:16px; height:16px; border-radius:5px; flex:none; display:inline-flex; align-items:center;
|
|
392
|
+
justify-content:center; font-family:${mono}; font-size:10px; font-weight:700; background:var(--gm-blue-08); }
|
|
393
|
+
.gm-stat.m { color:var(--gm-warn); }
|
|
394
|
+
.gm-stat.a { color:var(--gm-ok); }
|
|
395
|
+
.gm-stat.d, .gm-stat.u { color:var(--gm-err); }
|
|
396
|
+
.gm-stat.q { color:var(--gm-dim); }
|
|
397
|
+
.gm-fpath { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
|
|
398
|
+
font-family:${mono}; font-size:11.5px; }
|
|
399
|
+
.gm-dir { color:var(--gm-dim); }
|
|
400
|
+
.gm-act { opacity:0; transition:opacity .12s, transform .12s; flex:none; }
|
|
401
|
+
.gm-file:hover .gm-act, .gm-file:focus-within .gm-act, .gm-branch:hover .gm-act { opacity:1; }
|
|
402
|
+
.gm-act:hover { transform:scale(1.12); }
|
|
403
|
+
@media (hover:none) { .gm-act { opacity:1; } }
|
|
404
|
+
.gm-conflict-bar { margin:9px 10px 2px; padding:6px 10px; border-radius:10px; font-size:11px; font-weight:600;
|
|
405
|
+
color:var(--gm-err); background:color-mix(in srgb, var(--gm-err) 8%, transparent);
|
|
406
|
+
border:1px solid color-mix(in srgb, var(--gm-err) 28%, transparent); }
|
|
407
|
+
|
|
408
|
+
/* ---- graph --------------------------------------------------------- */
|
|
409
|
+
.gm-graph { padding:6px 0 14px; font-family:${mono}; font-size:11.5px; line-height:20px; }
|
|
410
|
+
.gm-grow { display:flex; align-items:center; gap:6px; min-height:20px; padding:0 10px; margin:0 6px;
|
|
411
|
+
border-radius:9px; cursor:pointer; transition:background .12s; }
|
|
412
|
+
.gm-grow:hover { background:var(--gm-blue-06); }
|
|
413
|
+
.gm-grow.active { background:var(--gm-blue-12); box-shadow:inset 3px 0 0 var(--gm-blue); }
|
|
414
|
+
.gm-glane { flex:none; overflow:hidden;
|
|
415
|
+
-webkit-mask-image:linear-gradient(90deg, #000 88%, transparent 100%);
|
|
416
|
+
mask-image:linear-gradient(90deg, #000 88%, transparent 100%); }
|
|
417
|
+
.gm-glane line { stroke:var(--gm-blue); stroke-width:1.6; stroke-linecap:round; opacity:.65; }
|
|
418
|
+
.gm-glane circle { fill:var(--gm-blue); stroke:var(--gm-bg); stroke-width:1.4; }
|
|
419
|
+
.gm-grow.active .gm-glane line, .gm-grow:hover .gm-glane line { opacity:.95; }
|
|
420
|
+
.gm-grow.active .gm-glane circle, .gm-grow:hover .gm-glane circle { fill:var(--gm-deep); }
|
|
421
|
+
.gm-hash { flex:none; font-size:10.5px; color:var(--gm-blue); opacity:.75; }
|
|
422
|
+
.gm-subject { flex:1 1 auto; min-width:56px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-family:inherit; }
|
|
423
|
+
.gm-meta { flex:none; max-width:145px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
|
|
424
|
+
font-size:10.5px; color:var(--gm-dim); font-family:inherit; }
|
|
425
|
+
.gm-ref { height:17px; max-width:120px; overflow:hidden; text-overflow:ellipsis; display:inline-block; }
|
|
426
|
+
|
|
427
|
+
/* ---- branches ------------------------------------------------------ */
|
|
428
|
+
.gm-branch { display:flex; align-items:center; gap:6px; min-height:30px; padding:3px 10px; margin:0 6px;
|
|
429
|
+
border-radius:9px; transition:background .12s; }
|
|
430
|
+
.gm-branch:hover { background:var(--gm-blue-06); }
|
|
431
|
+
.gm-branch > svg { color:var(--gm-blue); opacity:.85; flex:none; }
|
|
432
|
+
.gm-bname { flex:0 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
|
|
433
|
+
font-family:${mono}; font-size:11.5px; }
|
|
434
|
+
.gm-bmeta { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
|
|
435
|
+
font-size:10.5px; color:var(--gm-dim); text-align:right; }
|
|
436
|
+
|
|
437
|
+
/* ---- diff ---------------------------------------------------------- */
|
|
438
|
+
.gm-diff { padding-bottom:18px; }
|
|
439
|
+
.gm-diff-head { position:sticky; top:0; z-index:2; display:flex; align-items:center; gap:6px; padding:8px 12px;
|
|
440
|
+
border-bottom:1px solid var(--gm-line);
|
|
441
|
+
background:linear-gradient(135deg, color-mix(in srgb, var(--gm-blue) 9%, var(--gm-card)), var(--gm-card) 80%); }
|
|
442
|
+
.gm-diff-title { font-size:10.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
|
|
443
|
+
color:var(--gm-blue); flex:none; }
|
|
444
|
+
.gm-hunk { margin:9px 12px 3px; padding:3px 9px; border-radius:999px; background:var(--gm-blue-08);
|
|
445
|
+
border:1px solid var(--gm-blue-18); display:inline-block; max-width:calc(100% - 24px);
|
|
446
|
+
font-family:${mono}; font-size:10.5px; color:var(--gm-blue); white-space:pre-wrap; }
|
|
447
|
+
.gm-dlines { font-family:${mono}; font-size:11.5px; line-height:1.62; margin:0 6px 6px; }
|
|
448
|
+
.gm-dline { display:flex; align-items:baseline; white-space:pre-wrap; word-break:break-all; border-radius:4px; }
|
|
449
|
+
.gm-dline.add { background:color-mix(in srgb, var(--gm-ok) 10%, transparent); }
|
|
450
|
+
.gm-dline.del { background:color-mix(in srgb, var(--gm-err) 10%, transparent); }
|
|
451
|
+
.gm-ln { width:38px; flex:none; text-align:right; padding-right:8px; color:var(--gm-dim);
|
|
452
|
+
opacity:.6; user-select:none; font-variant-numeric:tabular-nums; }
|
|
453
|
+
.gm-sign { width:14px; flex:none; text-align:center; font-weight:700; }
|
|
454
|
+
.gm-dline.add .gm-sign { color:var(--gm-ok); }
|
|
455
|
+
.gm-dline.del .gm-sign { color:var(--gm-err); }
|
|
456
|
+
.gm-dtext { flex:1 1 auto; min-width:0; padding-right:10px; }
|
|
457
|
+
.gm-meta-line { padding:2px 12px; color:var(--gm-dim); font-style:italic; }
|
|
458
|
+
|
|
459
|
+
/* ---- commit card --------------------------------------------------- */
|
|
460
|
+
.gm-commit { position:relative; flex:none; overflow:hidden; display:flex; flex-direction:column; gap:7px; margin:8px 10px 10px; padding:11px 10px 10px;
|
|
461
|
+
border-radius:14px; border:1px solid var(--gm-blue-18);
|
|
462
|
+
background:linear-gradient(180deg, color-mix(in srgb, var(--gm-blue) 6%, var(--gm-card)), var(--gm-card));
|
|
463
|
+
box-shadow:0 8px 22px color-mix(in srgb, var(--gm-blue) 10%, transparent); }
|
|
464
|
+
.gm-commit::before { content:''; position:absolute; top:0; left:0; right:0; height:3px;
|
|
465
|
+
background:linear-gradient(90deg, var(--gm-cyan), var(--gm-blue) 45%, transparent 90%); }
|
|
466
|
+
.gm-commit-head { display:flex; align-items:center; gap:6px; }
|
|
467
|
+
.gm-commit-title { font-size:10.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
|
|
468
|
+
color:var(--gm-blue); }
|
|
469
|
+
.gm-textarea { width:100%; min-height:46px; max-height:140px; padding:7px 9px; border-radius:10px;
|
|
470
|
+
border:1px solid var(--gm-line); background:var(--gm-base); color:var(--gm-ink);
|
|
471
|
+
font:inherit; font-size:12px; line-height:1.5; resize:vertical; transition:border-color .12s, box-shadow .12s; }
|
|
472
|
+
.gm-textarea::placeholder, .gm-input::placeholder { color:var(--gm-dim); opacity:.7; }
|
|
473
|
+
.gm-textarea:focus-visible { outline:none; border-color:var(--gm-blue-40);
|
|
474
|
+
box-shadow:0 0 0 3px var(--gm-blue-12); }
|
|
475
|
+
.gm-input { flex:1 1 100px; min-width:0; height:25px; padding:0 9px; border-radius:9px;
|
|
476
|
+
border:1px solid var(--gm-line); background:var(--gm-base); color:var(--gm-ink);
|
|
477
|
+
font:inherit; font-size:11.5px; transition:border-color .12s, box-shadow .12s; }
|
|
478
|
+
.gm-input:focus-visible { outline:none; border-color:var(--gm-blue-40); box-shadow:0 0 0 3px var(--gm-blue-12); }
|
|
479
|
+
.gm-actions { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
|
|
480
|
+
.gm-hint { font-size:10.5px; color:var(--gm-dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
481
|
+
.gm-ai-line { font-size:10.5px; color:var(--gm-dim); }
|
|
482
|
+
|
|
483
|
+
/* ---- status pill ---------------------------------------------------- */
|
|
484
|
+
.gm-status { flex:none; display:flex; align-items:center; gap:6px; min-height:24px; padding:3px 11px;
|
|
485
|
+
margin:7px 10px 0; border-radius:999px; border:1px solid var(--gm-blue-12); background:var(--gm-blue-06);
|
|
486
|
+
font-size:11px; color:var(--gm-dim); animation:gm-pop .18s ease; }
|
|
487
|
+
.gm-dot { width:6px; height:6px; border-radius:50%; background:var(--gm-blue); flex:none;
|
|
488
|
+
box-shadow:0 0 0 3px var(--gm-blue-12); }
|
|
489
|
+
.gm-dot.ok { background:var(--gm-ok); box-shadow:0 0 0 3px color-mix(in srgb, var(--gm-ok) 18%, transparent); }
|
|
490
|
+
.gm-dot.err { background:var(--gm-err); box-shadow:0 0 0 3px color-mix(in srgb, var(--gm-err) 18%, transparent); }
|
|
491
|
+
.gm-dot.busy { background:var(--gm-cyan); box-shadow:0 0 0 3px color-mix(in srgb, var(--gm-cyan) 22%, transparent);
|
|
492
|
+
animation:gm-pulse 1.1s ease-in-out infinite; }
|
|
493
|
+
.gm-status .gm-msg { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
494
|
+
.gm-status .gm-msg.err { color:var(--gm-err); white-space:pre-wrap; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; }
|
|
495
|
+
.gm-status .gm-msg.ok { color:var(--gm-ok); }
|
|
496
|
+
.gm-details { flex:none; margin:7px 10px 0; }
|
|
497
|
+
.gm-details > summary { list-style:none; }
|
|
498
|
+
.gm-details > summary::-webkit-details-marker { display:none; }
|
|
499
|
+
.gm-details[open] > summary { border-bottom-left-radius:10px; border-bottom-right-radius:10px; }
|
|
500
|
+
.gm-pre { margin:0; padding:7px 12px; max-height:180px; overflow:auto; font-family:${mono}; font-size:11px;
|
|
501
|
+
color:var(--gm-dim); white-space:pre-wrap; word-break:break-word;
|
|
502
|
+
background:var(--gm-blue-04); border:1px solid var(--gm-blue-12); border-top:none;
|
|
503
|
+
border-bottom-left-radius:12px; border-bottom-right-radius:12px; }
|
|
504
|
+
|
|
505
|
+
/* ---- empty states + spinner ---------------------------------------- */
|
|
506
|
+
.gm-empty { flex:1 1 auto; min-height:0; display:flex; flex-direction:column; align-items:center;
|
|
507
|
+
justify-content:center; gap:10px; padding:30px 22px; text-align:center; color:var(--gm-dim); }
|
|
508
|
+
.gm-empty-bubble { position:relative; overflow:hidden; width:108px; height:108px; border-radius:36px; display:flex; align-items:center;
|
|
509
|
+
justify-content:center; background:radial-gradient(circle at 35% 30%, var(--gm-blue-18), var(--gm-blue-04) 70%);
|
|
510
|
+
border:1px solid var(--gm-blue-12); }
|
|
511
|
+
.gm-empty-bubble::before, .gm-empty-bubble::after { content:''; position:absolute; border-radius:50%;
|
|
512
|
+
background:var(--gm-blue-18); animation:gm-rise 3.2s ease-in infinite; }
|
|
513
|
+
.gm-empty-bubble::before { width:6px; height:6px; left:22px; bottom:16px; }
|
|
514
|
+
.gm-empty-bubble::after { width:4px; height:4px; left:78px; bottom:22px; animation-delay:1.4s; }
|
|
515
|
+
.gm-empty-mascot { width:88px; height:88px; object-fit:contain; pointer-events:none;
|
|
516
|
+
animation:gm-bob 3.2s ease-in-out infinite; filter:drop-shadow(0 8px 16px var(--gm-blue-28)); }
|
|
517
|
+
.gm-empty-title { color:var(--gm-ink); font-size:13px; font-weight:650; }
|
|
518
|
+
.gm-empty-hint { font-size:11.5px; line-height:1.6; max-width:280px; }
|
|
519
|
+
.gm-spinner { width:18px; height:18px; border-radius:50%; border:2px solid var(--gm-blue-18);
|
|
520
|
+
border-top-color:var(--gm-blue); animation:gm-spin .8s linear infinite; }
|
|
521
|
+
|
|
522
|
+
/* ---- animations ----------------------------------------------------- */
|
|
523
|
+
@keyframes gm-fade { from { opacity:0; } to { opacity:1; } }
|
|
524
|
+
@keyframes gm-pop { from { opacity:0; transform:translateY(4px); } to { opacity:1; transform:none; } }
|
|
525
|
+
@keyframes gm-bob { 0%,100% { transform:translateY(0) rotate(-1.5deg); } 50% { transform:translateY(-6px) rotate(1.5deg); } }
|
|
526
|
+
@keyframes gm-pulse { 50% { opacity:.35; } }
|
|
527
|
+
@keyframes gm-shimmer { to { transform:translateX(100%); } }
|
|
528
|
+
@keyframes gm-spin { to { transform:rotate(360deg); } }
|
|
529
|
+
@keyframes gm-rise { 0% { transform:translateY(6px) scale(.7); opacity:0; } 30% { opacity:.8; }
|
|
530
|
+
100% { transform:translateY(-42px) scale(1.15); opacity:0; } }
|
|
531
|
+
|
|
532
|
+
/* ---- narrow column -------------------------------------------------- */
|
|
533
|
+
@container gm (max-width: 430px) {
|
|
534
|
+
.gm-top .gm-btn-label { display:none; }
|
|
535
|
+
.gm-top .gm-btn { width:27px; padding:0; }
|
|
536
|
+
.gm-repo { font-size:12.5px; }
|
|
537
|
+
.gm-bmeta { display:none; }
|
|
538
|
+
.gm-meta { display:none; }
|
|
539
|
+
.gm-empty-bubble { width:86px; height:86px; border-radius:28px; }
|
|
540
|
+
}
|
|
541
|
+
`
|
|
542
|
+
|
|
543
|
+
// The session header button lives outside the panel root, so it carries its own
|
|
544
|
+
// tiny stylesheet (same tokens, same look as neighbouring header utilities).
|
|
545
|
+
var HEADER_CSS = `
|
|
546
|
+
.gm-header-btn { display:inline-flex; align-items:center; justify-content:center; width:26px; height:26px; padding:0;
|
|
547
|
+
border:1px solid transparent; border-radius:9px; background:transparent; cursor:pointer;
|
|
548
|
+
transition:background .14s, transform .14s, box-shadow .14s; }
|
|
549
|
+
.gm-header-btn:hover { background:color-mix(in srgb, #4d6bfe 14%, transparent); transform:translateY(-1px); }
|
|
550
|
+
.gm-header-btn:hover svg { stroke:#4d6bfe !important; }
|
|
551
|
+
.gm-header-btn:focus-visible { outline:2px solid color-mix(in srgb, #4d6bfe 45%, transparent); outline-offset:1px; }
|
|
552
|
+
.gm-header-btn svg { display:block; }
|
|
553
|
+
`
|
|
554
|
+
|
|
555
|
+
function panelStyle() {
|
|
556
|
+
return h('style', { key: 'gm-css' }, PANEL_CSS)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Locale access: `tr` closes over the binding installed by apply(), so every
|
|
560
|
+
// visible string goes through the Client locale service (zh/en dictionaries
|
|
561
|
+
// above). Missing binding degrades to the key itself (never throws).
|
|
562
|
+
var localeT = null
|
|
563
|
+
function tr(key, params) {
|
|
564
|
+
if (!localeT) return key
|
|
565
|
+
try {
|
|
566
|
+
return localeT(key, params)
|
|
567
|
+
} catch (error) {
|
|
568
|
+
return key
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// --------------------------------------------------------------- helpers
|
|
573
|
+
|
|
574
|
+
// Icon paths are 24x24 line glyphs drawn with currentColor.
|
|
575
|
+
// Generated mascot: chibi Whale-chan (DeepSeek 鲸鱼娘), 224px webp, transparent.
|
|
576
|
+
var WHALE_IMG = 'data:image/webp;base64,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'
|
|
577
|
+
var MASCOT_IMG = 'data:image/webp;base64,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'
|
|
578
|
+
|
|
579
|
+
var ICONS = {
|
|
580
|
+
refresh: ['M20 11a8 8 0 1 0-2.3 5.7', 'M20 4v7h-7'],
|
|
581
|
+
fetch: ['M12 3v10', 'M8 9l4 4 4-4', 'M4 17h16', 'M4 21h16'],
|
|
582
|
+
pull: ['M12 3v12', 'M7 10l5 5 5-5', 'M4 21h16'],
|
|
583
|
+
push: ['M12 21V9', 'M7 14l5-5 5 5', 'M4 3h16'],
|
|
584
|
+
plus: ['M12 5v14', 'M5 12h14'],
|
|
585
|
+
minus: ['M5 12h14'],
|
|
586
|
+
close: ['M6 6l12 12', 'M18 6L6 18'],
|
|
587
|
+
ai: ['M12 3l1.7 4.6L18.5 9l-4.8 1.4L12 15l-1.7-4.6L5.5 9l4.8-1.4z', 'M18.5 15l.8 2.2L21.5 18l-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8z'],
|
|
588
|
+
check: ['M5 12l4.5 4.5L19 7'],
|
|
589
|
+
commit: ['M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16z', 'M8.5 12l2.5 2.5L16 9.5'],
|
|
590
|
+
file: ['M6 3h8l4 4v14H6z', 'M14 3v5h5'],
|
|
591
|
+
branch: ['M6 4v9', 'M6 17m-2 0a2 2 0 1 0 4 0a2 2 0 1 0-4 0', 'M18 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0-4 0', 'M18 8v1a6 6 0 0 1-6 6H8'],
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function Icon(props) {
|
|
595
|
+
var paths = ICONS[props.name]
|
|
596
|
+
if (!paths) return null
|
|
597
|
+
var size = props.size || 13
|
|
598
|
+
return h('svg', {
|
|
599
|
+
width: size, height: size, viewBox: '0 0 24 24', fill: 'none',
|
|
600
|
+
stroke: 'currentColor', strokeWidth: props.strokeWidth || 1.8,
|
|
601
|
+
strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': 'true', focusable: 'false',
|
|
602
|
+
}, paths.map(function (d, index) { return h('path', { key: index, d: d }) }))
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Small brand glyph next to the repo name / commit card: the generated chubby whale. */
|
|
606
|
+
function WhaleGlyph(props) {
|
|
607
|
+
var size = props.size || 26
|
|
608
|
+
return h('span', {
|
|
609
|
+
className: 'gm-mark',
|
|
610
|
+
style: { width: size + 'px', height: size + 'px', borderRadius: Math.round(size * 0.34) + 'px' },
|
|
611
|
+
}, h('img', { src: WHALE_IMG, alt: '', draggable: false }))
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function Spinner() {
|
|
615
|
+
return h('span', { className: 'gm-spinner', role: 'status', 'aria-label': 'loading' })
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** Guide-page glyph: the generated Whale-chan mascot, sized by the registry's IconProps. */
|
|
619
|
+
function WhaleChanIcon(props) {
|
|
620
|
+
var size = props && props.size ? props.size : 16
|
|
621
|
+
return h('img', {
|
|
622
|
+
src: MASCOT_IMG, width: size, height: size, alt: '',
|
|
623
|
+
style: { borderRadius: '4px', objectFit: 'contain', display: 'block' },
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** One panel button: variant is 'primary' | 'ghost' | undefined; icon-only when no label. */
|
|
628
|
+
function Btn(props) {
|
|
629
|
+
var className = 'gm-btn'
|
|
630
|
+
if (props.variant === 'primary') className += ' primary'
|
|
631
|
+
else if (props.variant === 'ghost' || props.ghost) className += ' ghost'
|
|
632
|
+
if (props.icon && !props.label) className += ' icon'
|
|
633
|
+
if (props.small) className += ' sm'
|
|
634
|
+
if (props.className) className += ' ' + props.className
|
|
635
|
+
return h('button', {
|
|
636
|
+
type: 'button',
|
|
637
|
+
className: className,
|
|
638
|
+
title: props.title || props.label || undefined,
|
|
639
|
+
'aria-label': props.title || props.label || undefined,
|
|
640
|
+
disabled: !!props.disabled,
|
|
641
|
+
onClick: props.disabled ? undefined : props.onClick,
|
|
642
|
+
onKeyDown: props.onKeyDown,
|
|
643
|
+
},
|
|
644
|
+
props.icon ? h(Icon, { name: props.icon, size: props.small ? 12 : 13 }) : null,
|
|
645
|
+
props.label ? h('span', { className: 'gm-btn-label' }, props.label) : null)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function statusLetter(group, entry) {
|
|
649
|
+
// 未跟踪按 git 习惯显示 `?`(`U` 留给 unmerged 状态,避免混淆)。
|
|
650
|
+
if (group === 'untracked') return '?'
|
|
651
|
+
return String(entry.status || '?').slice(0, 1)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function statusClass(group, entry) {
|
|
655
|
+
if (group === 'conflicted') return 'gm-stat u'
|
|
656
|
+
if (group === 'untracked') return 'gm-stat q'
|
|
657
|
+
var code = String(entry.status || '?').slice(0, 1)
|
|
658
|
+
if (code === 'A' || code === 'R' || code === 'C') return 'gm-stat a'
|
|
659
|
+
if (code === 'D' || code === 'U') return 'gm-stat d'
|
|
660
|
+
return 'gm-stat m'
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function pathParts(path) {
|
|
664
|
+
var value = String(path || '')
|
|
665
|
+
var slash = value.lastIndexOf('/')
|
|
666
|
+
if (slash < 0) return { dir: '', base: value }
|
|
667
|
+
return { dir: value.slice(0, slash + 1), base: value.slice(slash + 1) }
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// One RPC call to the Host half. Throws Error with a `.code` on failure.
|
|
671
|
+
async function rpc(op, params) {
|
|
672
|
+
var response
|
|
673
|
+
try {
|
|
674
|
+
response = await fetch(RPC_PATH, {
|
|
675
|
+
method: 'POST',
|
|
676
|
+
credentials: 'same-origin',
|
|
677
|
+
headers: { 'content-type': 'application/json' },
|
|
678
|
+
body: JSON.stringify(Object.assign({ op: op }, params || {})),
|
|
679
|
+
})
|
|
680
|
+
} catch (error) {
|
|
681
|
+
var transport = new Error(tr('error.transport') + ':' + (error && error.message ? error.message : error))
|
|
682
|
+
transport.code = 'transport'
|
|
683
|
+
throw transport
|
|
684
|
+
}
|
|
685
|
+
var body
|
|
686
|
+
try {
|
|
687
|
+
body = await response.json()
|
|
688
|
+
} catch (error) {
|
|
689
|
+
throw new Error(tr('error.badResponse') + ' (HTTP ' + response.status + ')')
|
|
690
|
+
}
|
|
691
|
+
if (!body || body.ok !== true) {
|
|
692
|
+
var failure = (body && body.error) || {}
|
|
693
|
+
var shaped = new Error(failure.message || tr('error.gitFailed'))
|
|
694
|
+
shaped.code = failure.code || 'internal'
|
|
695
|
+
throw shaped
|
|
696
|
+
}
|
|
697
|
+
return body.data
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// ---------------------------------------------------------------- store
|
|
701
|
+
|
|
702
|
+
function createStore(initial) {
|
|
703
|
+
var state = initial
|
|
704
|
+
var listeners = []
|
|
705
|
+
return {
|
|
706
|
+
get: function () { return state },
|
|
707
|
+
set: function (patch) {
|
|
708
|
+
var next = typeof patch === 'function' ? patch(state) : patch
|
|
709
|
+
var merged = Object.assign({}, state, next)
|
|
710
|
+
if (merged === state) return
|
|
711
|
+
state = merged
|
|
712
|
+
for (var i = 0; i < listeners.length; i += 1) listeners[i]()
|
|
713
|
+
},
|
|
714
|
+
subscribe: function (listener) {
|
|
715
|
+
listeners.push(listener)
|
|
716
|
+
return function () {
|
|
717
|
+
listeners = listeners.filter(function (item) { return item !== listener })
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function useStore(store) {
|
|
724
|
+
var pair = useState(store.get())
|
|
725
|
+
useEffect(function () {
|
|
726
|
+
var unsubscribe = store.subscribe(function () { pair[1](store.get()) })
|
|
727
|
+
pair[1](store.get())
|
|
728
|
+
return unsubscribe
|
|
729
|
+
}, [store])
|
|
730
|
+
return pair[0]
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Right-Sidebar tabs are session-scoped: the body receives `sessionId`, and the
|
|
734
|
+
// summary (with its cwd) is read from the injected sessions list.
|
|
735
|
+
function useSessionById(sessions, sessionId) {
|
|
736
|
+
var pair = useState(null)
|
|
737
|
+
useEffect(function () {
|
|
738
|
+
if (!sessions || !sessionId) {
|
|
739
|
+
pair[1](null)
|
|
740
|
+
return undefined
|
|
741
|
+
}
|
|
742
|
+
var read = function () { pair[1](sessions.list.getSnapshot().byId[sessionId] || null) }
|
|
743
|
+
read()
|
|
744
|
+
return sessions.list.subscribe(read)
|
|
745
|
+
}, [sessions, sessionId])
|
|
746
|
+
return pair[0]
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ------------------------------------------------------------- controller
|
|
750
|
+
|
|
751
|
+
function createController() {
|
|
752
|
+
var store = createStore({
|
|
753
|
+
cwd: null,
|
|
754
|
+
snapshot: null,
|
|
755
|
+
graph: null,
|
|
756
|
+
loading: false,
|
|
757
|
+
graphLoading: false,
|
|
758
|
+
error: null,
|
|
759
|
+
errorCode: null,
|
|
760
|
+
busy: null,
|
|
761
|
+
toast: null,
|
|
762
|
+
toastKind: 'ok',
|
|
763
|
+
lastOutput: null,
|
|
764
|
+
activeTab: 'changes',
|
|
765
|
+
selected: null,
|
|
766
|
+
diff: null,
|
|
767
|
+
diffLoading: false,
|
|
768
|
+
message: '',
|
|
769
|
+
aiHint: '',
|
|
770
|
+
aiModel: null,
|
|
771
|
+
aiFallback: false,
|
|
772
|
+
newBranchOpen: false,
|
|
773
|
+
newBranchName: '',
|
|
774
|
+
newBranchFrom: '',
|
|
775
|
+
})
|
|
776
|
+
var toastTimer = null
|
|
777
|
+
var loadToken = 0
|
|
778
|
+
|
|
779
|
+
function set(patch) { store.set(patch) }
|
|
780
|
+
|
|
781
|
+
function toast(kind, text, output) {
|
|
782
|
+
if (toastTimer) clearTimeout(toastTimer)
|
|
783
|
+
set({ toast: text, toastKind: kind, lastOutput: output || store.get().lastOutput })
|
|
784
|
+
toastTimer = setTimeout(function () {
|
|
785
|
+
toastTimer = null
|
|
786
|
+
set({ toast: null })
|
|
787
|
+
}, kind === 'error' ? 12000 : 6000)
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function clearToast() {
|
|
791
|
+
if (toastTimer) clearTimeout(toastTimer)
|
|
792
|
+
toastTimer = null
|
|
793
|
+
set({ toast: null, error: null, errorCode: null })
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async function loadSnapshot(cwd) {
|
|
797
|
+
var token = ++loadToken
|
|
798
|
+
// 首次加载才显示 loading;轮询刷新不闪「刷新中…」。
|
|
799
|
+
set({ loading: store.get().snapshot === null, error: null, errorCode: null })
|
|
800
|
+
try {
|
|
801
|
+
var data = await rpc('snapshot', { cwd: cwd })
|
|
802
|
+
if (token !== loadToken || cwd !== store.get().cwd) return
|
|
803
|
+
set({ snapshot: data, loading: false })
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (token !== loadToken || cwd !== store.get().cwd) return
|
|
806
|
+
set({ loading: false, error: error.message, errorCode: error.code || null })
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async function loadGraph(cwd) {
|
|
811
|
+
if (!cwd) return
|
|
812
|
+
set({ graphLoading: true })
|
|
813
|
+
try {
|
|
814
|
+
var data = await rpc('graph', { cwd: cwd, limit: 100 })
|
|
815
|
+
if (cwd !== store.get().cwd) return
|
|
816
|
+
set({ graph: data, graphLoading: false })
|
|
817
|
+
} catch (error) {
|
|
818
|
+
if (cwd !== store.get().cwd) return
|
|
819
|
+
set({ graphLoading: false, graph: { lines: [] }, error: error.message })
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async function loadDiff(target) {
|
|
824
|
+
var cwd = store.get().cwd
|
|
825
|
+
if (!cwd || !target) return
|
|
826
|
+
set({ diffLoading: true })
|
|
827
|
+
try {
|
|
828
|
+
var params = { cwd: cwd }
|
|
829
|
+
if (target.kind === 'commit') {
|
|
830
|
+
params.mode = 'commit'
|
|
831
|
+
params.hash = target.hash
|
|
832
|
+
} else {
|
|
833
|
+
params.mode = target.mode
|
|
834
|
+
params.path = target.path
|
|
835
|
+
}
|
|
836
|
+
var data = await rpc('diff', params)
|
|
837
|
+
var current = store.get().selected
|
|
838
|
+
if (!current || current.kind !== target.kind || current.path !== target.path || current.hash !== target.hash) return
|
|
839
|
+
set({ diff: data, diffLoading: false })
|
|
840
|
+
} catch (error) {
|
|
841
|
+
set({ diffLoading: false, diff: null, error: error.message, errorCode: error.code || null })
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
async function refresh() {
|
|
846
|
+
var cwd = store.get().cwd
|
|
847
|
+
if (!cwd) return
|
|
848
|
+
await Promise.all([loadSnapshot(cwd), loadGraph(cwd)])
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
async function refreshSoft() {
|
|
852
|
+
var cwd = store.get().cwd
|
|
853
|
+
if (!cwd || store.get().busy) return
|
|
854
|
+
await loadSnapshot(cwd)
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function attach(cwd) {
|
|
858
|
+
if (cwd === store.get().cwd) {
|
|
859
|
+
if (cwd && !store.get().snapshot && !store.get().loading) void refresh()
|
|
860
|
+
return
|
|
861
|
+
}
|
|
862
|
+
set({
|
|
863
|
+
cwd: cwd,
|
|
864
|
+
snapshot: null,
|
|
865
|
+
graph: null,
|
|
866
|
+
loading: !!cwd,
|
|
867
|
+
error: null,
|
|
868
|
+
errorCode: null,
|
|
869
|
+
selected: null,
|
|
870
|
+
diff: null,
|
|
871
|
+
busy: null,
|
|
872
|
+
toast: null,
|
|
873
|
+
lastOutput: null,
|
|
874
|
+
message: '',
|
|
875
|
+
aiHint: '',
|
|
876
|
+
aiModel: null,
|
|
877
|
+
aiFallback: false,
|
|
878
|
+
newBranchOpen: false,
|
|
879
|
+
newBranchName: '',
|
|
880
|
+
newBranchFrom: '',
|
|
881
|
+
})
|
|
882
|
+
if (cwd) void refresh()
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function setMessage(value) { set({ message: value }) }
|
|
886
|
+
function setAiHint(value) { set({ aiHint: value }) }
|
|
887
|
+
function setTab(tab) { set({ activeTab: tab }) }
|
|
888
|
+
|
|
889
|
+
function setNewBranch(open) {
|
|
890
|
+
set({ newBranchOpen: open, newBranchName: '', newBranchFrom: '' })
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function setNewBranchName(value) { set({ newBranchName: value }) }
|
|
894
|
+
function setNewBranchFrom(value) { set({ newBranchFrom: value }) }
|
|
895
|
+
|
|
896
|
+
async function runBusy(label, operation) {
|
|
897
|
+
if (store.get().busy) return null
|
|
898
|
+
set({ busy: label, error: null, errorCode: null })
|
|
899
|
+
try {
|
|
900
|
+
var result = await operation()
|
|
901
|
+
set({ busy: null })
|
|
902
|
+
return result
|
|
903
|
+
} catch (error) {
|
|
904
|
+
set({ busy: null, error: error.message, errorCode: error.code || null })
|
|
905
|
+
toast('error', error.message)
|
|
906
|
+
return null
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
async function selectFile(group, entry) {
|
|
911
|
+
var cwd = store.get().cwd
|
|
912
|
+
if (!cwd) return
|
|
913
|
+
var mode = group === 'staged' ? 'staged' : group === 'untracked' ? 'untracked' : 'worktree'
|
|
914
|
+
var target = { kind: 'file', group: group, path: entry.path, mode: mode }
|
|
915
|
+
set({ activeTab: 'diff', selected: target, diff: null })
|
|
916
|
+
await loadDiff(target)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
async function selectCommit(line) {
|
|
920
|
+
if (!line.full) return
|
|
921
|
+
var target = { kind: 'commit', hash: line.full, path: '', mode: 'commit' }
|
|
922
|
+
set({ activeTab: 'diff', selected: target, diff: null })
|
|
923
|
+
await loadDiff(target)
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async function stage(group, entry) {
|
|
927
|
+
var cwd = store.get().cwd
|
|
928
|
+
if (!cwd) return
|
|
929
|
+
var result = await runBusy('stage', function () { return rpc('stage', { cwd: cwd, paths: [entry.path] }) })
|
|
930
|
+
if (result !== null) {
|
|
931
|
+
toast('ok', tr('status.stageOk'), result.output)
|
|
932
|
+
await refresh()
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async function unstage(group, entry) {
|
|
937
|
+
var cwd = store.get().cwd
|
|
938
|
+
if (!cwd) return
|
|
939
|
+
var result = await runBusy('unstage', function () { return rpc('unstage', { cwd: cwd, paths: [entry.path] }) })
|
|
940
|
+
if (result !== null) {
|
|
941
|
+
toast('ok', tr('status.unstageOk'), result.output)
|
|
942
|
+
await refresh()
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
async function stageAll() {
|
|
947
|
+
var cwd = store.get().cwd
|
|
948
|
+
if (!cwd) return
|
|
949
|
+
var result = await runBusy('stage', function () { return rpc('stage', { cwd: cwd, all: true }) })
|
|
950
|
+
if (result !== null) {
|
|
951
|
+
toast('ok', tr('status.stageAllOk'), result.output)
|
|
952
|
+
await refresh()
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
async function unstageAll() {
|
|
957
|
+
var cwd = store.get().cwd
|
|
958
|
+
if (!cwd) return
|
|
959
|
+
var result = await runBusy('unstage', function () { return rpc('unstage', { cwd: cwd, all: true }) })
|
|
960
|
+
if (result !== null) {
|
|
961
|
+
toast('ok', tr('status.unstageAllOk'), result.output)
|
|
962
|
+
await refresh()
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
async function switchBranch(name) {
|
|
967
|
+
var cwd = store.get().cwd
|
|
968
|
+
if (!cwd || !name) return
|
|
969
|
+
var result = await runBusy('switch', function () { return rpc('switchBranch', { cwd: cwd, name: name }) })
|
|
970
|
+
if (result !== null) {
|
|
971
|
+
toast('ok', tr('status.branchSwitched', { branch: result.branch || name }), result.output)
|
|
972
|
+
set({ selected: null, diff: null, activeTab: 'changes' })
|
|
973
|
+
await refresh()
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
async function createBranch() {
|
|
978
|
+
var state = store.get()
|
|
979
|
+
var cwd = state.cwd
|
|
980
|
+
var name = String(state.newBranchName || '').trim()
|
|
981
|
+
if (!cwd || name === '') return
|
|
982
|
+
var params = { cwd: cwd, name: name, create: true }
|
|
983
|
+
var from = String(state.newBranchFrom || '').trim()
|
|
984
|
+
if (from !== '') params.from = from
|
|
985
|
+
var result = await runBusy('switch', function () { return rpc('switchBranch', params) })
|
|
986
|
+
if (result !== null) {
|
|
987
|
+
set({ newBranchOpen: false, newBranchName: '', newBranchFrom: '' })
|
|
988
|
+
toast('ok', tr('status.branchCreated', { branch: result.branch || name }), result.output)
|
|
989
|
+
await refresh()
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
async function deleteBranch(name) {
|
|
994
|
+
var cwd = store.get().cwd
|
|
995
|
+
if (!cwd || !name) return
|
|
996
|
+
if (typeof window !== 'undefined' && typeof window.confirm === 'function') {
|
|
997
|
+
if (!window.confirm(tr('status.deleteConfirm', { branch: name }))) return
|
|
998
|
+
}
|
|
999
|
+
var result = await runBusy('delete', function () { return rpc('deleteBranch', { cwd: cwd, name: name }) })
|
|
1000
|
+
if (result !== null) {
|
|
1001
|
+
toast('ok', tr('status.branchDeleted', { branch: name }), result.output)
|
|
1002
|
+
await refresh()
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
async function generateMessage() {
|
|
1007
|
+
var state = store.get()
|
|
1008
|
+
var cwd = state.cwd
|
|
1009
|
+
if (!cwd) return
|
|
1010
|
+
var result = await runBusy('ai', function () {
|
|
1011
|
+
return rpc('aiCommit', { cwd: cwd, hint: String(state.aiHint || '') })
|
|
1012
|
+
})
|
|
1013
|
+
if (result === null) return
|
|
1014
|
+
set({ message: result.message, aiModel: result.model || null, aiFallback: !!result.fallback })
|
|
1015
|
+
toast('ok', result.fallback ? tr('status.aiFallback') : tr('status.aiOk', { model: result.model || '' }))
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
async function commit(andPush) {
|
|
1019
|
+
var state = store.get()
|
|
1020
|
+
var cwd = state.cwd
|
|
1021
|
+
var message = String(state.message || '').trim()
|
|
1022
|
+
if (!cwd) return
|
|
1023
|
+
if (message === '') {
|
|
1024
|
+
toast('error', tr('commit.messageRequired'))
|
|
1025
|
+
return
|
|
1026
|
+
}
|
|
1027
|
+
var result = await runBusy('commit', function () { return rpc('commit', { cwd: cwd, message: message }) })
|
|
1028
|
+
if (result === null) return
|
|
1029
|
+
set({ message: '', aiModel: null, aiFallback: false })
|
|
1030
|
+
toast('ok', tr('status.commitOk', { hash: result.hash || '' }), result.output)
|
|
1031
|
+
await refresh()
|
|
1032
|
+
if (andPush) await push()
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
async function fetchRemote() {
|
|
1036
|
+
var cwd = store.get().cwd
|
|
1037
|
+
if (!cwd) return
|
|
1038
|
+
var result = await runBusy('fetch', function () { return rpc('fetch', { cwd: cwd }) })
|
|
1039
|
+
if (result !== null) {
|
|
1040
|
+
toast('ok', tr('status.fetchOk'), result.output)
|
|
1041
|
+
await refresh()
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
async function pull() {
|
|
1046
|
+
var cwd = store.get().cwd
|
|
1047
|
+
if (!cwd) return
|
|
1048
|
+
var result = await runBusy('pull', function () { return rpc('pull', { cwd: cwd }) })
|
|
1049
|
+
if (result !== null) {
|
|
1050
|
+
toast('ok', tr('status.pullOk'), result.output)
|
|
1051
|
+
await refresh()
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
async function push() {
|
|
1056
|
+
var cwd = store.get().cwd
|
|
1057
|
+
if (!cwd) return
|
|
1058
|
+
var result = await runBusy('push', function () { return rpc('push', { cwd: cwd }) })
|
|
1059
|
+
if (result !== null) {
|
|
1060
|
+
toast('ok', tr('status.pushOk'), result.output)
|
|
1061
|
+
await refresh()
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
return {
|
|
1066
|
+
store: store,
|
|
1067
|
+
attach: attach,
|
|
1068
|
+
refresh: refresh,
|
|
1069
|
+
refreshSoft: refreshSoft,
|
|
1070
|
+
selectFile: selectFile,
|
|
1071
|
+
selectCommit: selectCommit,
|
|
1072
|
+
stage: stage,
|
|
1073
|
+
unstage: unstage,
|
|
1074
|
+
stageAll: stageAll,
|
|
1075
|
+
unstageAll: unstageAll,
|
|
1076
|
+
switchBranch: switchBranch,
|
|
1077
|
+
createBranch: createBranch,
|
|
1078
|
+
deleteBranch: deleteBranch,
|
|
1079
|
+
generateMessage: generateMessage,
|
|
1080
|
+
commit: commit,
|
|
1081
|
+
fetch: fetchRemote,
|
|
1082
|
+
pull: pull,
|
|
1083
|
+
push: push,
|
|
1084
|
+
setMessage: setMessage,
|
|
1085
|
+
setAiHint: setAiHint,
|
|
1086
|
+
setTab: setTab,
|
|
1087
|
+
setNewBranch: setNewBranch,
|
|
1088
|
+
setNewBranchName: setNewBranchName,
|
|
1089
|
+
setNewBranchFrom: setNewBranchFrom,
|
|
1090
|
+
clearToast: clearToast,
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// -------------------------------------------------------------- components
|
|
1095
|
+
|
|
1096
|
+
var BUSY_KEYS = {
|
|
1097
|
+
stage: 'changes.stage',
|
|
1098
|
+
unstage: 'changes.unstage',
|
|
1099
|
+
commit: 'commit.commit',
|
|
1100
|
+
fetch: 'header.fetch',
|
|
1101
|
+
pull: 'header.pull',
|
|
1102
|
+
push: 'header.push',
|
|
1103
|
+
switch: 'branch.switch',
|
|
1104
|
+
delete: 'branch.delete',
|
|
1105
|
+
ai: 'commit.ai',
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function busyLabel(kind) {
|
|
1109
|
+
var key = BUSY_KEYS[kind]
|
|
1110
|
+
return key ? tr(key) : String(kind || '')
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function EmptyState(props) {
|
|
1114
|
+
return h('div', { className: 'gm-empty' },
|
|
1115
|
+
props.spinner
|
|
1116
|
+
? h(Spinner)
|
|
1117
|
+
: props.icon
|
|
1118
|
+
? h(Icon, { name: props.icon, size: props.iconSize || 22 })
|
|
1119
|
+
: h('div', { className: 'gm-empty-bubble' },
|
|
1120
|
+
h('img', { className: 'gm-empty-mascot', src: MASCOT_IMG, alt: '', draggable: false })),
|
|
1121
|
+
props.title ? h('div', { className: 'gm-empty-title' }, props.title) : null,
|
|
1122
|
+
props.hint ? h('div', { className: 'gm-empty-hint' }, props.hint) : null)
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function ChangesSection(props) {
|
|
1126
|
+
var entries = props.entries || []
|
|
1127
|
+
if (entries.length === 0) return null
|
|
1128
|
+
return h('div', null,
|
|
1129
|
+
h('div', { className: 'gm-section' },
|
|
1130
|
+
h('span', null, props.title),
|
|
1131
|
+
h('span', { className: 'gm-section-count' }, String(entries.length))),
|
|
1132
|
+
entries.map(function (entry) {
|
|
1133
|
+
var selected = props.selected && props.selected.kind === 'file'
|
|
1134
|
+
&& props.selected.group === props.group && props.selected.path === entry.path
|
|
1135
|
+
var parts = pathParts(entry.path)
|
|
1136
|
+
var staged = props.group === 'staged'
|
|
1137
|
+
return h('div', {
|
|
1138
|
+
key: props.group + ':' + entry.path,
|
|
1139
|
+
className: 'gm-file' + (selected ? ' active' : ''),
|
|
1140
|
+
title: entry.oldPath ? entry.oldPath + ' → ' + entry.path : entry.path,
|
|
1141
|
+
onClick: function () { props.onSelect(entry) },
|
|
1142
|
+
},
|
|
1143
|
+
h('span', { className: statusClass(props.group, entry) }, statusLetter(props.group, entry)),
|
|
1144
|
+
h('span', { className: 'gm-fpath' },
|
|
1145
|
+
parts.dir ? h('span', { className: 'gm-dir' }, parts.dir) : null,
|
|
1146
|
+
parts.base),
|
|
1147
|
+
h('button', {
|
|
1148
|
+
type: 'button',
|
|
1149
|
+
className: 'gm-btn icon sm ghost gm-act',
|
|
1150
|
+
title: staged ? tr('changes.unstage') : tr('changes.stage'),
|
|
1151
|
+
onClick: function (event) {
|
|
1152
|
+
event.stopPropagation()
|
|
1153
|
+
if (staged) props.onUnstage(entry)
|
|
1154
|
+
else props.onStage(entry)
|
|
1155
|
+
},
|
|
1156
|
+
}, h(Icon, { name: staged ? 'minus' : 'plus', size: 12 })))
|
|
1157
|
+
}))
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function ChangesPane(props) {
|
|
1161
|
+
var state = props.state
|
|
1162
|
+
var status = state.snapshot && state.snapshot.status ? state.snapshot.status : null
|
|
1163
|
+
var staged = status ? status.staged : []
|
|
1164
|
+
var unstaged = status ? status.unstaged : []
|
|
1165
|
+
var untracked = status ? status.untracked : []
|
|
1166
|
+
var conflicted = status ? status.conflicted : []
|
|
1167
|
+
var busy = state.busy
|
|
1168
|
+
function section(title, group, entries, tone) {
|
|
1169
|
+
return h(ChangesSection, {
|
|
1170
|
+
title: title,
|
|
1171
|
+
group: group,
|
|
1172
|
+
entries: entries,
|
|
1173
|
+
tone: tone,
|
|
1174
|
+
selected: state.selected,
|
|
1175
|
+
busy: busy,
|
|
1176
|
+
onSelect: function (entry) { props.actions.selectFile(group, entry) },
|
|
1177
|
+
onStage: function (entry) { props.actions.stage(group, entry) },
|
|
1178
|
+
onUnstage: function (entry) { props.actions.unstage(group, entry) },
|
|
1179
|
+
})
|
|
1180
|
+
}
|
|
1181
|
+
var empty = !conflicted.length && !staged.length && !unstaged.length && !untracked.length
|
|
1182
|
+
return h('div', { className: 'gm-scroll' },
|
|
1183
|
+
h('div', { className: 'gm-content-inner' },
|
|
1184
|
+
h('div', { className: 'gm-quick' },
|
|
1185
|
+
h(Btn, { label: tr('changes.stageAll'), icon: 'plus', disabled: !!busy || (unstaged.length + untracked.length === 0), onClick: props.actions.stageAll }),
|
|
1186
|
+
h(Btn, { label: tr('changes.unstageAll'), icon: 'minus', disabled: !!busy || staged.length === 0, onClick: props.actions.unstageAll })),
|
|
1187
|
+
conflicted.length > 0
|
|
1188
|
+
? h('div', { className: 'gm-conflict-bar' }, tr('changes.conflictBanner', { n: conflicted.length }))
|
|
1189
|
+
: null,
|
|
1190
|
+
section(tr('changes.conflicted'), 'conflicted', conflicted, 'error'),
|
|
1191
|
+
section(tr('changes.staged'), 'staged', staged),
|
|
1192
|
+
section(tr('changes.unstaged'), 'unstaged', unstaged),
|
|
1193
|
+
section(tr('changes.untracked'), 'untracked', untracked),
|
|
1194
|
+
empty
|
|
1195
|
+
? h(EmptyState, { title: tr('changes.empty'), hint: tr('changes.emptyHint') })
|
|
1196
|
+
: null))
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function CommitFooter(props) {
|
|
1200
|
+
var state = props.state
|
|
1201
|
+
var stagedCount = state.snapshot && state.snapshot.status ? state.snapshot.status.staged.length : 0
|
|
1202
|
+
var busy = state.busy
|
|
1203
|
+
var canCommit = !busy && String(state.message || '').trim() !== '' && stagedCount > 0
|
|
1204
|
+
function onKeyDown(event) {
|
|
1205
|
+
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
1206
|
+
event.preventDefault()
|
|
1207
|
+
if (canCommit) props.onCommit(false)
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return h('div', { className: 'gm-commit' },
|
|
1211
|
+
h('div', { className: 'gm-commit-head' },
|
|
1212
|
+
h(WhaleGlyph, { size: 22 }),
|
|
1213
|
+
h('span', { className: 'gm-commit-title' }, tr('commit.title')),
|
|
1214
|
+
h('span', { className: 'gm-spacer' }),
|
|
1215
|
+
stagedCount > 0 ? h('span', { className: 'gm-badge brand' }, tr('changes.stagedCount', { n: stagedCount })) : null),
|
|
1216
|
+
h('textarea', {
|
|
1217
|
+
className: 'gm-textarea',
|
|
1218
|
+
rows: 2,
|
|
1219
|
+
value: state.message,
|
|
1220
|
+
placeholder: tr('commit.placeholder'),
|
|
1221
|
+
onChange: function (event) { props.onMessage(event.target.value) },
|
|
1222
|
+
onKeyDown: onKeyDown,
|
|
1223
|
+
}),
|
|
1224
|
+
h('div', { className: 'gm-actions' },
|
|
1225
|
+
h('input', {
|
|
1226
|
+
className: 'gm-input',
|
|
1227
|
+
value: state.aiHint,
|
|
1228
|
+
placeholder: tr('commit.hintPlaceholder'),
|
|
1229
|
+
onChange: function (event) { props.onHint(event.target.value) },
|
|
1230
|
+
}),
|
|
1231
|
+
h(Btn, {
|
|
1232
|
+
label: busy === 'ai' ? tr('commit.aiBusy') : tr('commit.aiShort'),
|
|
1233
|
+
icon: 'ai',
|
|
1234
|
+
title: tr('commit.ai'),
|
|
1235
|
+
variant: 'primary',
|
|
1236
|
+
className: busy === 'ai' ? 'gm-ai-busy' : undefined,
|
|
1237
|
+
disabled: !!busy || !state.cwd,
|
|
1238
|
+
onClick: props.onGenerate,
|
|
1239
|
+
})),
|
|
1240
|
+
h('div', { className: 'gm-actions' },
|
|
1241
|
+
state.aiModel
|
|
1242
|
+
? h('span', { className: 'gm-ai-line gm-hint', title: tr('status.aiModel', { model: state.aiModel }) },
|
|
1243
|
+
state.aiFallback ? tr('status.aiFallback') : tr('status.aiModelEdit', { model: state.aiModel }))
|
|
1244
|
+
: null,
|
|
1245
|
+
h('span', { className: 'gm-spacer' }),
|
|
1246
|
+
h('span', { className: 'gm-hint' }, tr('commit.cmdEnter')),
|
|
1247
|
+
h(Btn, {
|
|
1248
|
+
label: busy === 'commit' ? tr('commit.committing') : tr('commit.commit'),
|
|
1249
|
+
variant: 'primary',
|
|
1250
|
+
disabled: !canCommit,
|
|
1251
|
+
onClick: function () { props.onCommit(false) },
|
|
1252
|
+
}),
|
|
1253
|
+
h(Btn, {
|
|
1254
|
+
label: tr('commit.push'),
|
|
1255
|
+
disabled: !canCommit,
|
|
1256
|
+
onClick: function () { props.onCommit(true) },
|
|
1257
|
+
})))
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function BranchSelect(props) {
|
|
1261
|
+
var branches = props.branches || []
|
|
1262
|
+
var repo = props.repo || {}
|
|
1263
|
+
var value = repo.branch || ''
|
|
1264
|
+
var locals = branches.filter(function (branch) { return branch.kind === 'local' })
|
|
1265
|
+
var remotes = branches.filter(function (branch) { return branch.kind === 'remote' })
|
|
1266
|
+
return h('select', {
|
|
1267
|
+
className: 'gm-select',
|
|
1268
|
+
value: value,
|
|
1269
|
+
title: tr('branch.selectTitle'),
|
|
1270
|
+
disabled: props.disabled,
|
|
1271
|
+
onChange: function (event) { if (event.target.value && event.target.value !== value) props.onSwitch(event.target.value) },
|
|
1272
|
+
},
|
|
1273
|
+
repo.detached ? h('option', { value: '' }, tr('header.detached')) : null,
|
|
1274
|
+
h('optgroup', { label: tr('branch.local') }, locals.map(function (branch) {
|
|
1275
|
+
return h('option', { key: 'l:' + branch.name, value: branch.name },
|
|
1276
|
+
branch.name + (branch.ahead ? ' ↑' + branch.ahead : '') + (branch.behind ? ' ↓' + branch.behind : ''))
|
|
1277
|
+
})),
|
|
1278
|
+
remotes.length > 0
|
|
1279
|
+
? h('optgroup', { label: tr('branch.remote') }, remotes.map(function (branch) {
|
|
1280
|
+
return h('option', { key: 'r:' + branch.name, value: branch.name }, branch.name)
|
|
1281
|
+
}))
|
|
1282
|
+
: null)
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function CompactTopBar(props) {
|
|
1286
|
+
var state = props.state
|
|
1287
|
+
var repo = state.snapshot && state.snapshot.repo ? state.snapshot.repo : null
|
|
1288
|
+
var busy = state.busy
|
|
1289
|
+
if (!repo || !repo.isRepo) {
|
|
1290
|
+
return h('div', { className: 'gm-top' },
|
|
1291
|
+
h('div', { className: 'gm-row' },
|
|
1292
|
+
h(WhaleGlyph, { size: 28 }),
|
|
1293
|
+
h('span', { className: 'gm-repo' }, tr('panel.title'))))
|
|
1294
|
+
}
|
|
1295
|
+
return h('div', { className: 'gm-top' },
|
|
1296
|
+
h('div', { className: 'gm-row' },
|
|
1297
|
+
h(WhaleGlyph, { size: 28 }),
|
|
1298
|
+
h('span', { className: 'gm-repo', title: repo.root }, repo.name || tr('panel.title')),
|
|
1299
|
+
h('span', { className: 'gm-repo-dot' }),
|
|
1300
|
+
repo.ahead ? h('span', { className: 'gm-badge' }, '↑' + repo.ahead) : null,
|
|
1301
|
+
repo.behind ? h('span', { className: 'gm-badge' }, '↓' + repo.behind) : null,
|
|
1302
|
+
h('span', { className: 'gm-spacer' }),
|
|
1303
|
+
h(BranchSelect, { branches: state.snapshot.branches, repo: repo, disabled: !!busy, onSwitch: props.onSwitch })),
|
|
1304
|
+
h('div', { className: 'gm-row' },
|
|
1305
|
+
h(Btn, { icon: 'fetch', label: 'Fetch', title: tr('header.fetch'), disabled: !!busy, onClick: props.onFetch }),
|
|
1306
|
+
h(Btn, { icon: 'pull', label: 'Pull', title: tr('header.pull'), disabled: !!busy, onClick: props.onPull }),
|
|
1307
|
+
h(Btn, { icon: 'push', label: 'Push', title: tr('header.push'), variant: 'primary', disabled: !!busy, onClick: props.onPush }),
|
|
1308
|
+
h('span', { className: 'gm-spacer' }),
|
|
1309
|
+
h(Btn, { icon: 'refresh', title: state.loading ? tr('header.refreshing') : tr('header.refresh'), disabled: !!busy, onClick: props.onRefresh }),
|
|
1310
|
+
h(Btn, { icon: 'plus', title: tr('header.newBranch'), disabled: !!busy, onClick: function () { props.onNewBranch(true) } })),
|
|
1311
|
+
state.newBranchOpen
|
|
1312
|
+
? h('div', { className: 'gm-row' },
|
|
1313
|
+
h('input', {
|
|
1314
|
+
className: 'gm-input',
|
|
1315
|
+
value: state.newBranchName,
|
|
1316
|
+
placeholder: tr('header.newBranchPlaceholder'),
|
|
1317
|
+
onChange: function (event) { props.onNewBranchName(event.target.value) },
|
|
1318
|
+
onKeyDown: function (event) {
|
|
1319
|
+
if (event.key === 'Enter') props.onCreateBranch()
|
|
1320
|
+
if (event.key === 'Escape') props.onNewBranch(false)
|
|
1321
|
+
},
|
|
1322
|
+
}),
|
|
1323
|
+
h('input', {
|
|
1324
|
+
className: 'gm-input',
|
|
1325
|
+
style: { flex: '0 1 96px' },
|
|
1326
|
+
value: state.newBranchFrom,
|
|
1327
|
+
placeholder: tr('header.fromPlaceholder'),
|
|
1328
|
+
onChange: function (event) { props.onNewBranchFrom(event.target.value) },
|
|
1329
|
+
onKeyDown: function (event) {
|
|
1330
|
+
if (event.key === 'Enter') props.onCreateBranch()
|
|
1331
|
+
if (event.key === 'Escape') props.onNewBranch(false)
|
|
1332
|
+
},
|
|
1333
|
+
}),
|
|
1334
|
+
h(Btn, { label: tr('header.create'), variant: 'primary', disabled: !!busy || !state.newBranchName.trim(), onClick: props.onCreateBranch }),
|
|
1335
|
+
h(Btn, { label: tr('header.cancel'), variant: 'ghost', onClick: function () { props.onNewBranch(false) } }))
|
|
1336
|
+
: null)
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function GraphView(props) {
|
|
1340
|
+
var state = props.state
|
|
1341
|
+
var lines = state.graph && state.graph.lines ? state.graph.lines : []
|
|
1342
|
+
if (state.graphLoading && lines.length === 0) return h(EmptyState, { spinner: true, title: tr('status.loading') })
|
|
1343
|
+
if (lines.length === 0) return h(EmptyState, { title: tr('graph.empty') })
|
|
1344
|
+
// A stable graph column keeps hash/refs/subject aligned across rows. Histories with
|
|
1345
|
+
// dozens of live lanes would otherwise push the text miles to the right, so the
|
|
1346
|
+
// column is capped and the overflowing lanes fade out instead.
|
|
1347
|
+
var GRAPH_MAX_COLS = 10
|
|
1348
|
+
var maxPrefix = 2
|
|
1349
|
+
for (var gi = 0; gi < lines.length; gi += 1) {
|
|
1350
|
+
if ((lines[gi].graph || '').length > maxPrefix) maxPrefix = (lines[gi].graph || '').length
|
|
1351
|
+
}
|
|
1352
|
+
var graphCols = Math.min(maxPrefix, GRAPH_MAX_COLS)
|
|
1353
|
+
return h('div', { className: 'gm-scroll' },
|
|
1354
|
+
h('div', { className: 'gm-content-inner gm-graph' },
|
|
1355
|
+
lines.map(function (line, index) {
|
|
1356
|
+
if (!line.full) {
|
|
1357
|
+
return h('div', { key: 'g:' + index, className: 'gm-grow' },
|
|
1358
|
+
h(GraphLane, {
|
|
1359
|
+
graph: line.graph || '',
|
|
1360
|
+
prev: index > 0 ? (lines[index - 1].graph || '') : '',
|
|
1361
|
+
next: index + 1 < lines.length ? (lines[index + 1].graph || '') : '',
|
|
1362
|
+
cols: graphCols,
|
|
1363
|
+
}))
|
|
1364
|
+
}
|
|
1365
|
+
var selected = state.selected && state.selected.kind === 'commit' && state.selected.hash === line.full
|
|
1366
|
+
return h('div', {
|
|
1367
|
+
key: 'c:' + line.full + ':' + index,
|
|
1368
|
+
className: 'gm-grow' + (selected ? ' active' : ''),
|
|
1369
|
+
title: line.full,
|
|
1370
|
+
onClick: function () { props.onSelectCommit(line) },
|
|
1371
|
+
},
|
|
1372
|
+
h(GraphLane, {
|
|
1373
|
+
graph: line.graph || '',
|
|
1374
|
+
prev: index > 0 ? (lines[index - 1].graph || '') : '',
|
|
1375
|
+
next: index + 1 < lines.length ? (lines[index + 1].graph || '') : '',
|
|
1376
|
+
cols: graphCols,
|
|
1377
|
+
}),
|
|
1378
|
+
h('span', { className: 'gm-hash' }, line.hash),
|
|
1379
|
+
line.refs && line.refs.length > 0
|
|
1380
|
+
? h('span', { className: 'gm-row', style: { gap: '4px', flex: 'none' } }, line.refs.map(function (ref, refIndex) {
|
|
1381
|
+
return h('span', {
|
|
1382
|
+
key: 'r:' + refIndex,
|
|
1383
|
+
className: 'gm-badge gm-ref' + (ref.indexOf('HEAD') === 0 ? ' brand' : ''),
|
|
1384
|
+
}, ref)
|
|
1385
|
+
}))
|
|
1386
|
+
: null,
|
|
1387
|
+
h('span', { className: 'gm-subject' }, line.subject),
|
|
1388
|
+
h('span', { className: 'gm-meta' }, line.author + ' · ' + line.date))
|
|
1389
|
+
})))
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
var GRAPH_CELL = 8
|
|
1393
|
+
var GRAPH_ROW = 20
|
|
1394
|
+
|
|
1395
|
+
function laneChar(text, lane) {
|
|
1396
|
+
return lane >= 0 && lane < text.length ? text[lane] : ' '
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/** Does lane `lane` continue into this row from the row above? */
|
|
1400
|
+
function laneFromAbove(prev, lane) {
|
|
1401
|
+
var here = laneChar(prev, lane)
|
|
1402
|
+
if (here === '|' || here === '*') return true
|
|
1403
|
+
if (laneChar(prev, lane + 1) === '/') return true
|
|
1404
|
+
if (here === '\\') return true
|
|
1405
|
+
return false
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
/** Does lane `lane` continue into the next row? */
|
|
1409
|
+
function laneToBelow(next, lane) {
|
|
1410
|
+
var here = laneChar(next, lane)
|
|
1411
|
+
if (here === '|' || here === '*') return true
|
|
1412
|
+
if (here === '/') return true
|
|
1413
|
+
if (laneChar(next, lane + 1) === '\\') return true
|
|
1414
|
+
return false
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* Draws one row of the commit graph as vector lanes, so vertical lines stay
|
|
1419
|
+
* continuous across rows (ASCII glyphs leave gaps at 20px row heights).
|
|
1420
|
+
*/
|
|
1421
|
+
function GraphLane(props) {
|
|
1422
|
+
var graph = props.graph || ''
|
|
1423
|
+
var prev = props.prev || ''
|
|
1424
|
+
var next = props.next || ''
|
|
1425
|
+
var cols = props.cols
|
|
1426
|
+
var cell = GRAPH_CELL
|
|
1427
|
+
var row = GRAPH_ROW
|
|
1428
|
+
var parts = []
|
|
1429
|
+
for (var i = 0; i < cols; i += 1) {
|
|
1430
|
+
var ch = laneChar(graph, i)
|
|
1431
|
+
var cx = (i + 0.5) * cell
|
|
1432
|
+
if (ch === '|') {
|
|
1433
|
+
parts.push(h('line', { key: 'v' + i, x1: cx, y1: 0, x2: cx, y2: row }))
|
|
1434
|
+
} else if (ch === '/') {
|
|
1435
|
+
parts.push(h('line', { key: 's' + i, x1: cx, y1: 0, x2: cx - cell, y2: row }))
|
|
1436
|
+
} else if (ch === '\\') {
|
|
1437
|
+
parts.push(h('line', { key: 'b' + i, x1: cx - cell, y1: 0, x2: cx, y2: row }))
|
|
1438
|
+
} else if (ch === '_') {
|
|
1439
|
+
parts.push(h('line', { key: 'h' + i, x1: cx, y1: row - 1, x2: cx + cell, y2: row - 1 }))
|
|
1440
|
+
} else if (ch === '*') {
|
|
1441
|
+
if (laneFromAbove(prev, i)) parts.push(h('line', { key: 'u' + i, x1: cx, y1: 0, x2: cx, y2: row / 2 }))
|
|
1442
|
+
if (laneToBelow(next, i)) parts.push(h('line', { key: 'd' + i, x1: cx, y1: row / 2, x2: cx, y2: row }))
|
|
1443
|
+
parts.push(h('circle', { key: 'c' + i, cx: cx, cy: row / 2, r: 3.2 }))
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return h('svg', {
|
|
1447
|
+
className: 'gm-glane', width: cols * cell, height: row,
|
|
1448
|
+
viewBox: '0 0 ' + (cols * cell) + ' ' + row, 'aria-hidden': 'true',
|
|
1449
|
+
}, parts)
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
function DiffView(props) {
|
|
1453
|
+
var state = props.state
|
|
1454
|
+
var diff = state.diff
|
|
1455
|
+
if (state.diffLoading) return h(EmptyState, { spinner: true, title: tr('status.loading') })
|
|
1456
|
+
if (!diff) return h(EmptyState, { title: tr('diff.empty') })
|
|
1457
|
+
var title = diff.mode === 'commit'
|
|
1458
|
+
? tr('diff.commit', { hash: state.selected && state.selected.hash ? state.selected.hash.slice(0, 8) : '' })
|
|
1459
|
+
: diff.mode === 'staged' ? tr('diff.staged') : diff.mode === 'untracked' ? tr('diff.untracked') : tr('diff.worktree')
|
|
1460
|
+
return h('div', { className: 'gm-scroll' },
|
|
1461
|
+
h('div', { className: 'gm-content-inner gm-diff' },
|
|
1462
|
+
h('div', { className: 'gm-diff-head' },
|
|
1463
|
+
h('span', { className: 'gm-diff-title' }, title),
|
|
1464
|
+
h('span', { className: 'gm-fpath', title: diff.path || '' }, diff.path || ''),
|
|
1465
|
+
h('span', { className: 'gm-spacer' }),
|
|
1466
|
+
h('span', { className: 'gm-badge ok' }, '+' + diff.additions),
|
|
1467
|
+
h('span', { className: 'gm-badge err' }, '-' + diff.deletions),
|
|
1468
|
+
diff.truncated ? h('span', { className: 'gm-badge warn' }, tr('diff.truncatedShort')) : null),
|
|
1469
|
+
diff.binary
|
|
1470
|
+
? h(EmptyState, { title: tr('diff.binary') })
|
|
1471
|
+
: diff.hunks.length === 0
|
|
1472
|
+
? h(EmptyState, { title: tr('diff.noText') })
|
|
1473
|
+
: diff.hunks.map(function (hunk, hunkIndex) {
|
|
1474
|
+
return h('div', { key: 'h:' + hunkIndex },
|
|
1475
|
+
h('div', { className: 'gm-hunk' }, hunk.header),
|
|
1476
|
+
h('div', { className: 'gm-dlines' }, hunk.lines.map(function (line, lineIndex) {
|
|
1477
|
+
var kind = line.kind === 'add' ? 'add' : line.kind === 'del' ? 'del' : ''
|
|
1478
|
+
var sign = line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : line.kind === 'meta' ? '\\' : ' '
|
|
1479
|
+
return h('div', { key: 'l:' + hunkIndex + ':' + lineIndex, className: 'gm-dline ' + kind },
|
|
1480
|
+
h('span', { className: 'gm-ln' }, line.old === undefined ? '' : String(line.old)),
|
|
1481
|
+
h('span', { className: 'gm-ln' }, line.new === undefined ? '' : String(line.new)),
|
|
1482
|
+
h('span', { className: 'gm-sign' }, sign),
|
|
1483
|
+
h('span', { className: 'gm-dtext' }, line.text))
|
|
1484
|
+
})))
|
|
1485
|
+
})))
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function BranchesView(props) {
|
|
1489
|
+
var state = props.state
|
|
1490
|
+
var branches = state.snapshot && state.snapshot.branches ? state.snapshot.branches : []
|
|
1491
|
+
var locals = branches.filter(function (branch) { return branch.kind === 'local' })
|
|
1492
|
+
var remotes = branches.filter(function (branch) { return branch.kind === 'remote' })
|
|
1493
|
+
function group(title, items, allowDelete) {
|
|
1494
|
+
if (items.length === 0) return null
|
|
1495
|
+
return h('div', null,
|
|
1496
|
+
h('div', { className: 'gm-section' },
|
|
1497
|
+
h('span', null, title),
|
|
1498
|
+
h('span', { className: 'gm-section-count' }, String(items.length))),
|
|
1499
|
+
items.map(function (branch) {
|
|
1500
|
+
return h('div', { key: branch.kind + ':' + branch.name, className: 'gm-branch' },
|
|
1501
|
+
h(Icon, { name: 'branch', size: 12 }),
|
|
1502
|
+
h('span', { className: 'gm-bname', title: branch.name }, branch.name),
|
|
1503
|
+
branch.current ? h('span', { className: 'gm-badge brand' }, tr('branch.current')) : null,
|
|
1504
|
+
branch.upstream ? h('span', { className: 'gm-badge' }, branch.upstream) : null,
|
|
1505
|
+
branch.ahead ? h('span', { className: 'gm-badge' }, '↑' + branch.ahead) : null,
|
|
1506
|
+
branch.behind ? h('span', { className: 'gm-badge' }, '↓' + branch.behind) : null,
|
|
1507
|
+
branch.gone ? h('span', { className: 'gm-badge warn' }, tr('branch.gone')) : null,
|
|
1508
|
+
h('span', { className: 'gm-bmeta' }, branch.subject || ''),
|
|
1509
|
+
h('span', { className: 'gm-row gm-act', style: { gap: '4px', flex: 'none' } },
|
|
1510
|
+
!branch.current
|
|
1511
|
+
? h(Btn, { label: tr('branch.switch'), small: true, disabled: !!state.busy, onClick: function () { props.onSwitch(branch.name) } })
|
|
1512
|
+
: null,
|
|
1513
|
+
!branch.current && allowDelete
|
|
1514
|
+
? h(Btn, { label: tr('branch.delete'), small: true, ghost: true, disabled: !!state.busy, onClick: function () { props.onDelete(branch.name) } })
|
|
1515
|
+
: null))
|
|
1516
|
+
}))
|
|
1517
|
+
}
|
|
1518
|
+
return h('div', { className: 'gm-scroll' },
|
|
1519
|
+
h('div', { className: 'gm-content-inner' },
|
|
1520
|
+
group(tr('branch.local'), locals, true),
|
|
1521
|
+
group(tr('branch.remote'), remotes, false),
|
|
1522
|
+
locals.length === 0 && remotes.length === 0
|
|
1523
|
+
? h(EmptyState, { title: tr('branch.empty') })
|
|
1524
|
+
: null))
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
function StatusBar(props) {
|
|
1528
|
+
var state = props.state
|
|
1529
|
+
var dotClass = 'gm-dot'
|
|
1530
|
+
var message = tr('status.ready')
|
|
1531
|
+
var messageClass = 'gm-msg'
|
|
1532
|
+
var dismiss = null
|
|
1533
|
+
if (state.toast) {
|
|
1534
|
+
var isError = state.toastKind === 'error'
|
|
1535
|
+
dotClass += isError ? ' err' : ' ok'
|
|
1536
|
+
messageClass += isError ? ' err' : ' ok'
|
|
1537
|
+
message = state.toast
|
|
1538
|
+
dismiss = props.onDismiss
|
|
1539
|
+
} else if (state.error) {
|
|
1540
|
+
dotClass += ' err'
|
|
1541
|
+
messageClass += ' err'
|
|
1542
|
+
message = tr('status.error', { message: state.error })
|
|
1543
|
+
dismiss = props.onDismiss
|
|
1544
|
+
} else if (state.busy) {
|
|
1545
|
+
dotClass += ' busy'
|
|
1546
|
+
message = tr('status.busy', { label: busyLabel(state.busy) })
|
|
1547
|
+
} else if (state.lastOutput) {
|
|
1548
|
+
return h('details', { className: 'gm-details' },
|
|
1549
|
+
h('summary', { className: 'gm-status' },
|
|
1550
|
+
h('span', { className: 'gm-dot ok' }),
|
|
1551
|
+
h('span', { className: 'gm-msg' }, tr('details.output'))),
|
|
1552
|
+
h('pre', { className: 'gm-pre' }, state.lastOutput))
|
|
1553
|
+
}
|
|
1554
|
+
return h('div', { className: 'gm-status' },
|
|
1555
|
+
h('span', { className: dotClass }),
|
|
1556
|
+
h('span', { className: messageClass, title: message }, message),
|
|
1557
|
+
dismiss
|
|
1558
|
+
? h('button', {
|
|
1559
|
+
type: 'button',
|
|
1560
|
+
className: 'gm-btn icon sm ghost',
|
|
1561
|
+
title: tr('details.dismiss'),
|
|
1562
|
+
onClick: dismiss,
|
|
1563
|
+
}, h(Icon, { name: 'close', size: 12 }))
|
|
1564
|
+
: null)
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
function TabbedPanel(props) {
|
|
1568
|
+
var state = props.state
|
|
1569
|
+
var repo = state.snapshot && state.snapshot.repo ? state.snapshot.repo : null
|
|
1570
|
+
var status = state.snapshot && state.snapshot.status ? state.snapshot.status : null
|
|
1571
|
+
var changeCount = status
|
|
1572
|
+
? status.staged.length + status.unstaged.length + status.untracked.length + status.conflicted.length
|
|
1573
|
+
: 0
|
|
1574
|
+
var tabs = [
|
|
1575
|
+
{ id: 'changes', label: tr('tabs.changes'), count: changeCount },
|
|
1576
|
+
{ id: 'graph', label: tr('tabs.graph'), count: 0 },
|
|
1577
|
+
{ id: 'branches', label: tr('tabs.branches'), count: 0 },
|
|
1578
|
+
{ id: 'diff', label: tr('tabs.diff'), count: 0 },
|
|
1579
|
+
]
|
|
1580
|
+
var content
|
|
1581
|
+
if (state.activeTab === 'changes') content = h(ChangesPane, { state: state, actions: props.actions })
|
|
1582
|
+
else if (state.activeTab === 'graph') content = h(GraphView, { state: state, onSelectCommit: props.actions.selectCommit })
|
|
1583
|
+
else if (state.activeTab === 'branches') content = h(BranchesView, { state: state, onSwitch: props.actions.switchBranch, onDelete: props.actions.deleteBranch })
|
|
1584
|
+
else content = h(DiffView, { state: state })
|
|
1585
|
+
return h('div', { className: 'gm-root' },
|
|
1586
|
+
panelStyle(),
|
|
1587
|
+
h(CompactTopBar, {
|
|
1588
|
+
state: state,
|
|
1589
|
+
onSwitch: props.actions.switchBranch,
|
|
1590
|
+
onRefresh: props.actions.refresh,
|
|
1591
|
+
onFetch: props.actions.fetch,
|
|
1592
|
+
onPull: props.actions.pull,
|
|
1593
|
+
onPush: props.actions.push,
|
|
1594
|
+
onNewBranch: props.actions.setNewBranch,
|
|
1595
|
+
onNewBranchName: props.actions.setNewBranchName,
|
|
1596
|
+
onNewBranchFrom: props.actions.setNewBranchFrom,
|
|
1597
|
+
onCreateBranch: props.actions.createBranch,
|
|
1598
|
+
}),
|
|
1599
|
+
h('div', { className: 'gm-tabs' },
|
|
1600
|
+
tabs.map(function (tab) {
|
|
1601
|
+
return h('button', {
|
|
1602
|
+
key: tab.id,
|
|
1603
|
+
type: 'button',
|
|
1604
|
+
className: 'gm-tab' + (state.activeTab === tab.id ? ' active' : ''),
|
|
1605
|
+
onClick: function () { props.actions.setTab(tab.id) },
|
|
1606
|
+
},
|
|
1607
|
+
h('span', null, tab.label),
|
|
1608
|
+
tab.count > 0 ? h('span', { className: 'gm-count' }, String(tab.count)) : null)
|
|
1609
|
+
})),
|
|
1610
|
+
content,
|
|
1611
|
+
h(StatusBar, { state: state, onDismiss: props.actions.clearToast }),
|
|
1612
|
+
repo && repo.isRepo
|
|
1613
|
+
? h(CommitFooter, {
|
|
1614
|
+
state: state,
|
|
1615
|
+
onMessage: props.actions.setMessage,
|
|
1616
|
+
onHint: props.actions.setAiHint,
|
|
1617
|
+
onGenerate: props.actions.generateMessage,
|
|
1618
|
+
onCommit: function (andPush) { props.actions.commit(andPush) },
|
|
1619
|
+
})
|
|
1620
|
+
: null)
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
function EmptyPanel(props) {
|
|
1624
|
+
return h('div', { className: 'gm-root' },
|
|
1625
|
+
panelStyle(),
|
|
1626
|
+
h('div', { className: 'gm-top' },
|
|
1627
|
+
h('div', { className: 'gm-row' },
|
|
1628
|
+
h(WhaleGlyph, { size: 28 }),
|
|
1629
|
+
h('span', { className: 'gm-repo' }, tr('panel.title')))),
|
|
1630
|
+
props.spinner
|
|
1631
|
+
? h(EmptyState, { spinner: true, title: props.title, hint: props.hint })
|
|
1632
|
+
: h(EmptyState, { title: props.title, hint: props.hint, whaleSize: 86 }))
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
function GitPanel(props) {
|
|
1636
|
+
var controller = props.controller
|
|
1637
|
+
var summary = props.sessionSummary
|
|
1638
|
+
var cwd = summary && summary.cwd ? summary.cwd : null
|
|
1639
|
+
var state = useStore(controller.store)
|
|
1640
|
+
|
|
1641
|
+
useEffect(function () {
|
|
1642
|
+
controller.attach(cwd)
|
|
1643
|
+
}, [cwd])
|
|
1644
|
+
|
|
1645
|
+
useEffect(function () {
|
|
1646
|
+
if (!cwd) return undefined
|
|
1647
|
+
var timer = setInterval(function () {
|
|
1648
|
+
if (typeof document !== 'undefined' && document.hidden) return
|
|
1649
|
+
void controller.refreshSoft()
|
|
1650
|
+
}, POLL_MS)
|
|
1651
|
+
return function () { clearInterval(timer) }
|
|
1652
|
+
}, [cwd])
|
|
1653
|
+
|
|
1654
|
+
useEffect(function () {
|
|
1655
|
+
if (typeof window === 'undefined') return undefined
|
|
1656
|
+
var onFocus = function () { void controller.refreshSoft() }
|
|
1657
|
+
window.addEventListener('focus', onFocus)
|
|
1658
|
+
return function () { window.removeEventListener('focus', onFocus) }
|
|
1659
|
+
}, [])
|
|
1660
|
+
|
|
1661
|
+
if (!summary) return h(EmptyPanel, { title: tr('status.noSession'), hint: tr('panel.guide') })
|
|
1662
|
+
if (!cwd) return h(EmptyPanel, { title: tr('status.noCwd') })
|
|
1663
|
+
var repo = state.snapshot && state.snapshot.repo ? state.snapshot.repo : null
|
|
1664
|
+
if (state.error && state.errorCode === 'cwd-unavailable') {
|
|
1665
|
+
return h(EmptyPanel, { title: tr('status.cwdUnavailable', { path: cwd }) })
|
|
1666
|
+
}
|
|
1667
|
+
if (state.error && (!state.snapshot || (repo && repo.isRepo === false))) {
|
|
1668
|
+
return h(EmptyPanel, { title: tr('status.error', { message: state.error }) })
|
|
1669
|
+
}
|
|
1670
|
+
if (state.snapshot && repo && repo.isRepo === false) {
|
|
1671
|
+
return h(EmptyPanel, { title: tr('status.notRepoTitle'), hint: cwd })
|
|
1672
|
+
}
|
|
1673
|
+
if (!state.snapshot && state.loading) return h(EmptyPanel, { spinner: true, title: tr('status.loading') })
|
|
1674
|
+
return h(TabbedPanel, { state: state, actions: controller })
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
function GitIcon(props) {
|
|
1678
|
+
var size = props.size || 16
|
|
1679
|
+
var color = props.active ? '#2f49d6' : '#4d6bfe'
|
|
1680
|
+
return h('svg', {
|
|
1681
|
+
width: size, height: size, viewBox: '0 0 24 24', fill: 'none',
|
|
1682
|
+
stroke: color, strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
1683
|
+
'aria-hidden': 'true', focusable: 'false',
|
|
1684
|
+
},
|
|
1685
|
+
h('line', { x1: 6, y1: 3, x2: 6, y2: 15 }),
|
|
1686
|
+
h('circle', { cx: 18, cy: 6, r: 3 }),
|
|
1687
|
+
h('circle', { cx: 6, cy: 18, r: 3 }),
|
|
1688
|
+
h('path', { d: 'M18 9a9 9 0 0 1-9 9' }))
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function makeBoundary(fallbackText) {
|
|
1692
|
+
function Boundary(props) {
|
|
1693
|
+
React.Component.call(this, props)
|
|
1694
|
+
this.state = { error: null }
|
|
1695
|
+
}
|
|
1696
|
+
Boundary.prototype = Object.create(React.Component.prototype)
|
|
1697
|
+
Boundary.prototype.constructor = Boundary
|
|
1698
|
+
Boundary.getDerivedStateFromError = function (error) { return { error: error } }
|
|
1699
|
+
Boundary.prototype.componentDidCatch = function (error) {
|
|
1700
|
+
if (typeof console !== 'undefined') console.error('[git-manager] panel crashed:', error)
|
|
1701
|
+
}
|
|
1702
|
+
Boundary.prototype.render = function () {
|
|
1703
|
+
if (this.state.error) {
|
|
1704
|
+
return h('div', { className: 'gm-root' }, panelStyle(),
|
|
1705
|
+
h(EmptyState, {
|
|
1706
|
+
title: fallbackText,
|
|
1707
|
+
hint: this.state.error && this.state.error.message ? this.state.error.message : String(this.state.error),
|
|
1708
|
+
}))
|
|
1709
|
+
}
|
|
1710
|
+
return this.props.children
|
|
1711
|
+
}
|
|
1712
|
+
return Boundary
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
// ------------------------------------------------------------------ apply
|
|
1716
|
+
|
|
1717
|
+
exports.inject = ['slots', 'locale', 'sessions', 'sidebarRight', 'sidebarRightTabs']
|
|
1718
|
+
|
|
1719
|
+
exports.apply = function (ctx) {
|
|
1720
|
+
var controller = createController()
|
|
1721
|
+
var sessions = ctx.sessions
|
|
1722
|
+
|
|
1723
|
+
ctx.effect(function () {
|
|
1724
|
+
return ctx.locale.register(NS, { zh: ZH, en: EN })
|
|
1725
|
+
}, 'git-manager: dictionaries')
|
|
1726
|
+
|
|
1727
|
+
var t = ctx.locale.bind(NS)
|
|
1728
|
+
localeT = t
|
|
1729
|
+
var Boundary = makeBoundary(t('error.boundary'))
|
|
1730
|
+
|
|
1731
|
+
// Stage one: the tab type (chip title + the guide's entry box).
|
|
1732
|
+
ctx.effect(function () {
|
|
1733
|
+
return ctx.sidebarRightTabs.register({
|
|
1734
|
+
id: TAB_ID,
|
|
1735
|
+
kind: TAB_KIND,
|
|
1736
|
+
priority: 'extension',
|
|
1737
|
+
title: function () { return t('panel.title') },
|
|
1738
|
+
guide: [{
|
|
1739
|
+
order: 30,
|
|
1740
|
+
title: function () { return t('panel.title') },
|
|
1741
|
+
description: function () { return t('panel.guide') },
|
|
1742
|
+
icon: WhaleChanIcon,
|
|
1743
|
+
}],
|
|
1744
|
+
})
|
|
1745
|
+
}, 'git-manager: right sidebar tab type')
|
|
1746
|
+
|
|
1747
|
+
// Stage two: the tab body, keyed by the definition id.
|
|
1748
|
+
function GitTabBody(props) {
|
|
1749
|
+
var summary = useSessionById(sessions, props.sessionId)
|
|
1750
|
+
return h(Boundary, null, h(GitPanel, { controller: controller, sessionSummary: summary }))
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
ctx.effect(function () {
|
|
1754
|
+
return ctx.slots.inject('sidebar.right.pane.tab', function () {
|
|
1755
|
+
return ctx.slots.register({
|
|
1756
|
+
name: 'sidebar.right.pane.tab',
|
|
1757
|
+
key: TAB_ID,
|
|
1758
|
+
locale: NS,
|
|
1759
|
+
}, GitTabBody)
|
|
1760
|
+
})
|
|
1761
|
+
}, 'git-manager: right sidebar tab body')
|
|
1762
|
+
|
|
1763
|
+
// The way in: one icon in the session header's utilities row.
|
|
1764
|
+
function HeaderGitButton() {
|
|
1765
|
+
return h(React.Fragment, null,
|
|
1766
|
+
h('style', { key: 'gm-header-css' }, HEADER_CSS),
|
|
1767
|
+
h('button', {
|
|
1768
|
+
type: 'button',
|
|
1769
|
+
className: 'gm-header-btn',
|
|
1770
|
+
title: t('panel.open'),
|
|
1771
|
+
'aria-label': t('panel.open'),
|
|
1772
|
+
onClick: function () { ctx.sidebarRight.openTab(TAB_KIND) },
|
|
1773
|
+
}, h(GitIcon, { size: 16, active: false })))
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
ctx.effect(function () {
|
|
1777
|
+
return ctx.slots.inject('conversation.session.header.utilities', function () {
|
|
1778
|
+
return ctx.slots.register({
|
|
1779
|
+
name: 'conversation.session.header.utilities',
|
|
1780
|
+
id: TAB_ID,
|
|
1781
|
+
order: 10,
|
|
1782
|
+
label: function () { return t('panel.open') },
|
|
1783
|
+
}, HeaderGitButton)
|
|
1784
|
+
})
|
|
1785
|
+
}, 'git-manager: session header button')
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
return module.exports
|
|
1789
|
+
},
|
|
1790
|
+
})
|