mbeditor 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.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +127 -0
  3. data/app/assets/javascripts/mbeditor/application.js +19 -0
  4. data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +202 -0
  5. data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +71 -0
  6. data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +139 -0
  7. data/app/assets/javascripts/mbeditor/components/CommitGraph.js +65 -0
  8. data/app/assets/javascripts/mbeditor/components/DiffViewer.js +142 -0
  9. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +363 -0
  10. data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +112 -0
  11. data/app/assets/javascripts/mbeditor/components/FileTree.js +304 -0
  12. data/app/assets/javascripts/mbeditor/components/GitPanel.js +416 -0
  13. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +2335 -0
  14. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +118 -0
  15. data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +186 -0
  16. data/app/assets/javascripts/mbeditor/components/TabBar.js +123 -0
  17. data/app/assets/javascripts/mbeditor/editor_plugins.js +282 -0
  18. data/app/assets/javascripts/mbeditor/editor_store.js +53 -0
  19. data/app/assets/javascripts/mbeditor/file_service.js +77 -0
  20. data/app/assets/javascripts/mbeditor/git_service.js +104 -0
  21. data/app/assets/javascripts/mbeditor/search_service.js +53 -0
  22. data/app/assets/javascripts/mbeditor/tab_manager.js +461 -0
  23. data/app/assets/stylesheets/mbeditor/application.css +705 -0
  24. data/app/assets/stylesheets/mbeditor/editor.css +1264 -0
  25. data/app/controllers/mbeditor/application_controller.rb +10 -0
  26. data/app/controllers/mbeditor/editors_controller.rb +695 -0
  27. data/app/controllers/mbeditor/git_controller.rb +188 -0
  28. data/app/services/mbeditor/git_blame_service.rb +98 -0
  29. data/app/services/mbeditor/git_commit_graph_service.rb +60 -0
  30. data/app/services/mbeditor/git_diff_service.rb +71 -0
  31. data/app/services/mbeditor/git_file_history_service.rb +42 -0
  32. data/app/services/mbeditor/git_service.rb +82 -0
  33. data/app/services/mbeditor/redmine_service.rb +86 -0
  34. data/app/views/layouts/mbeditor/application.html.erb +71 -0
  35. data/app/views/mbeditor/editors/index.html.erb +1 -0
  36. data/config/environments/development.rb +53 -0
  37. data/config/initializers/assets.rb +9 -0
  38. data/config/routes.rb +37 -0
  39. data/lib/mbeditor/configuration.rb +16 -0
  40. data/lib/mbeditor/engine.rb +28 -0
  41. data/lib/mbeditor/version.rb +3 -0
  42. data/lib/mbeditor.rb +19 -0
  43. data/mbeditor.gemspec +30 -0
  44. data/public/min-maps/vs/base/worker/workerMain.js.map +1 -0
  45. data/public/monaco-editor/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  46. data/public/monaco-editor/vs/base/worker/workerMain.js +31 -0
  47. data/public/monaco-editor/vs/basic-languages/cameligo/cameligo.js +10 -0
  48. data/public/monaco-editor/vs/basic-languages/css/css.js +12 -0
  49. data/public/monaco-editor/vs/basic-languages/dart/dart.js +10 -0
  50. data/public/monaco-editor/vs/basic-languages/flow9/flow9.js +10 -0
  51. data/public/monaco-editor/vs/basic-languages/go/go.js +10 -0
  52. data/public/monaco-editor/vs/basic-languages/handlebars/handlebars.js +440 -0
  53. data/public/monaco-editor/vs/basic-languages/javascript/javascript.js +10 -0
  54. data/public/monaco-editor/vs/basic-languages/markdown/markdown.js +10 -0
  55. data/public/monaco-editor/vs/basic-languages/msdax/msdax.js +10 -0
  56. data/public/monaco-editor/vs/basic-languages/postiats/postiats.js +10 -0
  57. data/public/monaco-editor/vs/basic-languages/pug/pug.js +412 -0
  58. data/public/monaco-editor/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
  59. data/public/monaco-editor/vs/basic-languages/ruby/ruby.js +10 -0
  60. data/public/monaco-editor/vs/basic-languages/sb/sb.js +10 -0
  61. data/public/monaco-editor/vs/basic-languages/typespec/typespec.js +10 -0
  62. data/public/monaco-editor/vs/basic-languages/yaml/yaml.js +10 -0
  63. data/public/monaco-editor/vs/editor/editor.main.css +8 -0
  64. data/public/monaco-editor/vs/editor/editor.main.js +797 -0
  65. data/public/monaco-editor/vs/language/typescript/tsMode.js +20 -0
  66. data/public/monaco-editor/vs/language/typescript/tsWorker.js +51328 -0
  67. data/public/monaco-editor/vs/loader.js +10 -0
  68. data/public/monaco-editor/vs/nls.messages.de.js +20 -0
  69. data/public/monaco-editor/vs/nls.messages.es.js +20 -0
  70. data/public/monaco-editor/vs/nls.messages.fr.js +18 -0
  71. data/public/monaco-editor/vs/nls.messages.it.js +18 -0
  72. data/public/monaco-editor/vs/nls.messages.ja.js +20 -0
  73. data/public/monaco-editor/vs/nls.messages.ko.js +18 -0
  74. data/public/monaco-editor/vs/nls.messages.ru.js +20 -0
  75. data/public/monaco-editor/vs/nls.messages.zh-cn.js +20 -0
  76. data/public/monaco-editor/vs/nls.messages.zh-tw.js +18 -0
  77. data/public/monaco_worker.js +5 -0
  78. data/vendor/assets/javascripts/axios.min.js +2 -0
  79. data/vendor/assets/javascripts/lodash.min.js +140 -0
  80. data/vendor/assets/javascripts/marked.min.js +6 -0
  81. data/vendor/assets/javascripts/minisearch.min.js +2044 -0
  82. data/vendor/assets/javascripts/prettier-plugin-babel.js +16 -0
  83. data/vendor/assets/javascripts/prettier-plugin-estree.js +35 -0
  84. data/vendor/assets/javascripts/prettier-plugin-html.js +19 -0
  85. data/vendor/assets/javascripts/prettier-plugin-markdown.js +59 -0
  86. data/vendor/assets/javascripts/prettier-plugin-postcss.js +52 -0
  87. data/vendor/assets/javascripts/prettier-standalone.js +37 -0
  88. data/vendor/assets/javascripts/react-dom.min.js +267 -0
  89. data/vendor/assets/javascripts/react.min.js +31 -0
  90. data/vendor/assets/stylesheets/fontawesome.min.css +9 -0
  91. data/vendor/assets/webfonts/fa-brands-400.woff2 +0 -0
  92. data/vendor/assets/webfonts/fa-regular-400.woff2 +0 -0
  93. data/vendor/assets/webfonts/fa-solid-900.woff2 +0 -0
  94. metadata +173 -0
@@ -0,0 +1,416 @@
1
+ var GitPanel = function GitPanel(_ref) {
2
+ var gitInfo = _ref.gitInfo;
3
+ var error = _ref.error;
4
+ var onOpenFile = _ref.onOpenFile;
5
+ var onOpenDiff = _ref.onOpenDiff;
6
+ var onOpenAllChanges = _ref.onOpenAllChanges;
7
+ var onRefresh = _ref.onRefresh;
8
+ var onClose = _ref.onClose;
9
+
10
+ var useState = React.useState;
11
+
12
+ var _s1 = useState(true); var localExpanded = _s1[0]; var setLocalExpanded = _s1[1];
13
+ var _s2 = useState(true); var branchExpanded = _s2[0]; var setBranchExpanded = _s2[1];
14
+ var _s3 = useState(true); var historyExpanded= _s3[0]; var setHistoryExpanded= _s3[1];
15
+ // { [hash]: true|false }
16
+ var _s4 = useState({}); var expandedCommits = _s4[0]; var setExpandedCommits = _s4[1];
17
+ // { [hash]: { loading, files: [{status,path}], error } }
18
+ var _s5 = useState({}); var commitFiles = _s5[0]; var setCommitFiles = _s5[1];
19
+
20
+ var workingTree = gitInfo && gitInfo.workingTree || [];
21
+ var unpushedFiles = gitInfo && gitInfo.unpushedFiles || [];
22
+ var unpushedCommits = gitInfo && gitInfo.unpushedCommits || [];
23
+ var upstreamBranch = gitInfo && gitInfo.upstreamBranch;
24
+
25
+ // Build a set of unpushed (local) commit hashes for quick lookup
26
+ var localHashes = {};
27
+ unpushedCommits.forEach(function (c) { if (c && c.hash) localHashes[c.hash] = true; });
28
+
29
+ var rawBranchCommits = gitInfo && gitInfo.branchCommits || [];
30
+ var branchCommits = rawBranchCommits.map(function (c) {
31
+ return Object.assign({}, c, { isLocal: !!localHashes[c.hash] });
32
+ });
33
+
34
+ var statusMeta = function statusMeta(rawStatus) {
35
+ var raw = (rawStatus || '').trim();
36
+ if (raw === '??') return { badge: 'NEW', cssKey: 'A', description: 'Untracked' };
37
+ if (raw.startsWith('R')) return { badge: 'R', cssKey: 'R', description: 'Renamed' };
38
+ switch (raw.charAt(0)) {
39
+ case 'M': return { badge: 'M', cssKey: 'M', description: 'Modified' };
40
+ case 'A': return { badge: 'A', cssKey: 'A', description: 'Added' };
41
+ case 'D': return { badge: 'D', cssKey: 'D', description: 'Deleted' };
42
+ case 'U': return { badge: 'U', cssKey: 'Q', description: 'Conflict' };
43
+ case 'C': return { badge: 'C', cssKey: 'R', description: 'Copied' };
44
+ default: return { badge: raw || '?', cssKey: 'Q', description: 'Unknown' };
45
+ }
46
+ };
47
+
48
+ var fileIcon = function fileIcon(filename) {
49
+ var ext = (filename || '').split('.').pop().toLowerCase();
50
+ var iconMap = {
51
+ 'rb': { cls: 'fas fa-gem', color: '#cc342d' },
52
+ 'gemspec': { cls: 'fas fa-gem', color: '#cc342d' },
53
+ 'js': { cls: 'fab fa-js', color: '#f0db4f' },
54
+ 'ts': { cls: 'fas fa-code', color: '#3178c6' },
55
+ 'jsx': { cls: 'fab fa-js', color: '#61dafb' },
56
+ 'tsx': { cls: 'fas fa-code', color: '#61dafb' },
57
+ 'html': { cls: 'fab fa-html5', color: '#e34f26' },
58
+ 'htm': { cls: 'fab fa-html5', color: '#e34f26' },
59
+ 'erb': { cls: 'fab fa-html5', color: '#cc342d' },
60
+ 'css': { cls: 'fab fa-css3-alt', color: '#1572b6' },
61
+ 'scss': { cls: 'fab fa-sass', color: '#cc6699' },
62
+ 'sass': { cls: 'fab fa-sass', color: '#cc6699' },
63
+ 'md': { cls: 'fab fa-markdown', color: '#7f8b97' },
64
+ 'json': { cls: 'fas fa-code', color: '#ffe082' },
65
+ 'yml': { cls: 'fas fa-cog', color: '#888' },
66
+ 'yaml': { cls: 'fas fa-cog', color: '#888' },
67
+ 'png': { cls: 'fas fa-image', color: '#aaa' },
68
+ 'jpg': { cls: 'fas fa-image', color: '#aaa' },
69
+ 'jpeg': { cls: 'fas fa-image', color: '#aaa' },
70
+ 'gif': { cls: 'fas fa-image', color: '#aaa' },
71
+ 'svg': { cls: 'fas fa-image', color: '#aaa' },
72
+ 'txt': { cls: 'fas fa-file-alt', color: '#888' },
73
+ 'sh': { cls: 'fas fa-terminal', color: '#89d185' },
74
+ 'lock': { cls: 'fas fa-lock', color: '#888' },
75
+ 'pdf': { cls: 'fas fa-file-pdf', color: '#f48771' },
76
+ };
77
+ var icon = iconMap[ext] || { cls: 'fas fa-file-code', color: '#7f8b97' };
78
+ return React.createElement('i', { className: icon.cls + ' git-file-type-icon', style: { color: icon.color } });
79
+ };
80
+
81
+ // Renders a file row used in Local Changes and Changes in Branch sections
82
+ var renderFileRow = function renderFileRow(item, baseSha, headSha, showOpen) {
83
+ var parts = (item.path || '').split('/');
84
+ var name = parts.pop() || item.path;
85
+ var dir = parts.join('/');
86
+ var meta = statusMeta(item.status);
87
+ var statusClass = 'git-' + meta.cssKey;
88
+ return React.createElement(
89
+ 'div',
90
+ { key: item.path, className: 'git-file-item', style: { cursor: 'pointer' }, onClick: function () { onOpenFile && onOpenFile(item.path, name); } },
91
+ fileIcon(name),
92
+ React.createElement(
93
+ 'div',
94
+ { className: 'git-file-info', title: item.path },
95
+ React.createElement('span', { className: 'git-file-name' }, name),
96
+ dir ? React.createElement('span', { className: 'git-file-dir' }, dir) : null
97
+ ),
98
+ (item.added !== undefined || item.removed !== undefined) ? React.createElement(
99
+ 'span',
100
+ { className: 'git-diff-counts' },
101
+ item.added !== undefined ? React.createElement('span', { className: 'git-stat-add' }, '+' + item.added) : null,
102
+ item.removed !== undefined ? React.createElement('span', { className: 'git-stat-del' }, '-' + item.removed) : null
103
+ ) : null,
104
+ React.createElement('span', { className: 'git-status-badge ' + statusClass, title: meta.description }, meta.badge),
105
+ React.createElement(
106
+ 'div',
107
+ { className: 'git-file-actions' },
108
+ showOpen ? React.createElement(
109
+ 'button',
110
+ { className: 'git-action-btn', title: 'Open file', onClick: function (e) { e.stopPropagation(); onOpenFile && onOpenFile(item.path, name); } },
111
+ React.createElement('i', { className: 'fas fa-file-code' })
112
+ ) : null,
113
+ React.createElement(
114
+ 'button',
115
+ { className: 'git-action-btn', title: 'View diff', onClick: function (e) { e.stopPropagation(); onOpenDiff && onOpenDiff(item.path, name, baseSha, headSha); } },
116
+ React.createElement('i', { className: 'fas fa-exchange-alt' })
117
+ )
118
+ )
119
+ );
120
+ };
121
+
122
+ var toggleCommit = function toggleCommit(hash) {
123
+ var nowExpanded = !expandedCommits[hash];
124
+ setExpandedCommits(function (prev) {
125
+ var next = Object.assign({}, prev);
126
+ next[hash] = nowExpanded;
127
+ return next;
128
+ });
129
+ if (nowExpanded && !commitFiles[hash]) {
130
+ setCommitFiles(function (prev) {
131
+ var next = Object.assign({}, prev);
132
+ next[hash] = { loading: true, files: [], error: null };
133
+ return next;
134
+ });
135
+ GitService.fetchCommitDetail(hash).then(function (data) {
136
+ setCommitFiles(function (prev) {
137
+ var next = Object.assign({}, prev);
138
+ next[hash] = { loading: false, files: data.files || [], error: null };
139
+ return next;
140
+ });
141
+ }).catch(function (err) {
142
+ setCommitFiles(function (prev) {
143
+ var next = Object.assign({}, prev);
144
+ next[hash] = { loading: false, files: [], error: err.message || 'Failed to load files' };
145
+ return next;
146
+ });
147
+ });
148
+ }
149
+ };
150
+
151
+ var renderCommit = function renderCommit(commit, idx) {
152
+ var isFirst = idx === 0;
153
+ var isLast = idx === branchCommits.length - 1;
154
+ var isExpanded = !!expandedCommits[commit.hash];
155
+ var fd = commitFiles[commit.hash];
156
+ var isLocal = commit.isLocal;
157
+ var colorClass = isLocal ? 'commit-local' : 'commit-pushed';
158
+ var dateObj = new Date(commit.date);
159
+ var dateStr = !isNaN(dateObj) ? dateObj.toLocaleString() : (commit.date || '');
160
+
161
+ // Main commit header row
162
+ var headerRow = React.createElement(
163
+ 'div',
164
+ { className: 'commit-row ' + colorClass + (isExpanded ? ' commit-expanded' : ''), onClick: function () { toggleCommit(commit.hash); } },
165
+ React.createElement(
166
+ 'div',
167
+ { className: 'commit-graph-col' },
168
+ !isFirst && React.createElement('div', { className: 'commit-line-top' }),
169
+ React.createElement('div', { className: 'commit-dot ' + (isLocal ? 'dot-local' : 'dot-pushed') }),
170
+ // Draw bottom line if: not the last commit, OR this commit is expanded (sub-items appear in same wrapper)
171
+ (!isLast || isExpanded) && React.createElement('div', { className: 'commit-line-bottom' })
172
+ ),
173
+ React.createElement(
174
+ 'div',
175
+ { className: 'commit-info-col' },
176
+ React.createElement(
177
+ 'div',
178
+ { className: 'commit-title', title: commit.title || ('no message \u2014 ' + commit.hash.slice(0, 7)) },
179
+ commit.title
180
+ ? commit.title
181
+ : React.createElement('span', { className: 'commit-title-empty' }, commit.hash.slice(0, 7) + ' \u2014 no message')
182
+ ),
183
+ React.createElement(
184
+ 'div',
185
+ { className: 'commit-meta' },
186
+ React.createElement('span', { className: 'commit-hash' }, commit.hash.slice(0, 7)),
187
+ ' \xB7 ',
188
+ React.createElement('span', { className: 'commit-meta-author' }, commit.author),
189
+ ' \xB7 ',
190
+ dateStr
191
+ )
192
+ )
193
+ );
194
+
195
+ // Expanded: nested file rows with continued graph line on the left.
196
+ // These are siblings of headerRow inside a wrapper flex-column, so bottom:0 on
197
+ // the header's spine and top:0 on the sub-row's spine connect without any pixel tricks.
198
+ var subItems = [];
199
+ if (isExpanded) {
200
+ var makeSubRow = function makeSubRow(key, extra, children) {
201
+ return React.createElement(
202
+ 'div',
203
+ Object.assign({ key: key, className: 'commit-subfile-row git-file-item ' + colorClass }, extra),
204
+ React.createElement('div', { className: 'commit-graph-col' },
205
+ React.createElement('div', { className: 'commit-line-full' })
206
+ ),
207
+ children
208
+ );
209
+ };
210
+
211
+ if (!fd || fd.loading) {
212
+ subItems.push(makeSubRow(commit.hash + '-load', {},
213
+ React.createElement('span', { className: 'git-commit-loading' }, 'Loading\u2026')
214
+ ));
215
+ } else if (fd.error) {
216
+ subItems.push(makeSubRow(commit.hash + '-err', {},
217
+ React.createElement('span', { className: 'git-commit-loading' }, fd.error)
218
+ ));
219
+ } else if (fd.files.length === 0) {
220
+ subItems.push(makeSubRow(commit.hash + '-empty', {},
221
+ React.createElement('span', { className: 'git-commit-loading' }, 'No files changed.')
222
+ ));
223
+ } else {
224
+ fd.files.forEach(function (f, fi) {
225
+ var fParts = (f.path || '').split('/');
226
+ var fName = fParts.pop() || f.path;
227
+ var fDir = fParts.join('/');
228
+ var fStatus = (f.status || '').trim();
229
+ var fCssKey = fStatus === 'M' ? 'M' : fStatus === 'A' ? 'A' : fStatus === 'D' ? 'D' : fStatus.startsWith('R') ? 'R' : 'Q';
230
+ subItems.push(makeSubRow(commit.hash + '-f-' + fi, { style: { cursor: 'pointer' }, onClick: function () { onOpenFile && onOpenFile(f.path, fName); } },
231
+ [
232
+ fileIcon(fName),
233
+ React.createElement(
234
+ 'div',
235
+ { key: 'info', className: 'git-file-info', title: f.path },
236
+ React.createElement('span', { className: 'git-file-name' }, fName),
237
+ fDir ? React.createElement('span', { className: 'git-file-dir' }, fDir) : null
238
+ ),
239
+ (f.added !== undefined || f.removed !== undefined) ? React.createElement(
240
+ 'span',
241
+ { key: 'counts', className: 'git-diff-counts' },
242
+ f.added !== undefined ? React.createElement('span', { key: 'a', className: 'git-stat-add' }, '+' + f.added) : null,
243
+ f.removed !== undefined ? React.createElement('span', { key: 'r', className: 'git-stat-del' }, '-' + f.removed) : null
244
+ ) : null,
245
+ React.createElement('span', { key: 'badge', className: 'git-status-badge git-' + fCssKey }, fStatus || '?'),
246
+ React.createElement(
247
+ 'div',
248
+ { key: 'actions', className: 'git-file-actions' },
249
+ React.createElement(
250
+ 'button',
251
+ { className: 'git-action-btn', title: 'View diff for this commit', onClick: function (e) { e.stopPropagation(); onOpenDiff && onOpenDiff(f.path, fName, commit.hash + '^', commit.hash); } },
252
+ React.createElement('i', { className: 'fas fa-exchange-alt' })
253
+ )
254
+ )
255
+ ]
256
+ ));
257
+ });
258
+ }
259
+ }
260
+
261
+ // Wrap the header row + sub-items in a single flex-column group.
262
+ // A 6px spacer element at the end of every non-last group draws the
263
+ // spine line through the visual gap between groups.
264
+ return React.createElement(
265
+ 'div',
266
+ { key: commit.hash, className: 'commit-group ' + colorClass + (isExpanded ? ' is-expanded' : '') + (isLast ? ' is-last' : '') },
267
+ headerRow,
268
+ subItems,
269
+ !isLast && React.createElement('div', { className: 'commit-spacer ' + colorClass },
270
+ React.createElement('div', { className: 'commit-graph-col' },
271
+ React.createElement('div', { className: 'commit-line-full' })
272
+ )
273
+ )
274
+ );
275
+ };
276
+
277
+ // One element per commit (wrapper div)
278
+ var historyRows = branchCommits.map(function (commit, idx) {
279
+ return renderCommit(commit, idx);
280
+ });
281
+
282
+ return React.createElement(
283
+ 'aside',
284
+ { className: 'ide-git-panel', 'aria-label': 'Git panel' },
285
+
286
+ // Header
287
+ React.createElement(
288
+ 'div',
289
+ { className: 'ide-git-panel-header' },
290
+ React.createElement(
291
+ 'div',
292
+ { className: 'ide-git-panel-header-info' },
293
+ React.createElement('div', { className: 'ide-git-panel-title' }, 'Source Control'),
294
+ React.createElement(
295
+ 'div',
296
+ { className: 'ide-git-panel-branch' },
297
+ React.createElement('i', { className: 'fas fa-code-branch', style: { marginRight: '5px', fontSize: '11px', opacity: 0.7 } }),
298
+ gitInfo && gitInfo.branch || 'unknown branch',
299
+ gitInfo && gitInfo.ahead > 0 && React.createElement('span', { className: 'git-ahead-chip', title: gitInfo.ahead + ' ahead' }, '\u2191' + gitInfo.ahead),
300
+ gitInfo && gitInfo.behind > 0 && React.createElement('span', { className: 'git-behind-chip', title: gitInfo.behind + ' behind' }, '\u2193' + gitInfo.behind)
301
+ )
302
+ ),
303
+ React.createElement(
304
+ 'div',
305
+ { className: 'ide-git-panel-actions' },
306
+ onRefresh && React.createElement(
307
+ 'button',
308
+ { className: 'git-header-btn', onClick: onRefresh, title: 'Refresh' },
309
+ React.createElement('i', { className: 'fas fa-sync-alt' })
310
+ ),
311
+ onClose && React.createElement(
312
+ 'button',
313
+ { className: 'git-header-btn', onClick: onClose, title: 'Close panel' },
314
+ React.createElement('i', { className: 'fas fa-times' })
315
+ )
316
+ )
317
+ ),
318
+
319
+ error && React.createElement('div', { className: 'git-error' }, error),
320
+
321
+ // ── Section 1: Local Changes ────────────────────────────────────────────
322
+ React.createElement(
323
+ 'div',
324
+ { className: 'git-section ' + (localExpanded ? 'expanded' : '') },
325
+ React.createElement(
326
+ 'div',
327
+ { className: 'git-section-title' },
328
+ React.createElement(
329
+ 'div',
330
+ { className: 'git-section-title-main hoverable', onClick: function () { setLocalExpanded(!localExpanded); } },
331
+ React.createElement('i', { className: 'fas ' + (localExpanded ? 'fa-chevron-down' : 'fa-chevron-right'), style: { width: '14px', fontSize: '10px' } }),
332
+ ' Local Changes\u2002',
333
+ React.createElement('span', { className: 'git-section-count' }, workingTree.length)
334
+ ),
335
+ workingTree.length > 0 && React.createElement(
336
+ 'button',
337
+ {
338
+ className: 'git-section-action-btn',
339
+ title: 'View all local changes',
340
+ onClick: function (e) { e.stopPropagation(); onOpenAllChanges && onOpenAllChanges('local', 'Local Changes'); }
341
+ },
342
+ React.createElement('i', { className: 'fas fa-layer-group' })
343
+ )
344
+ ),
345
+ localExpanded && React.createElement(
346
+ 'div',
347
+ { className: 'git-list' },
348
+ workingTree.length === 0
349
+ ? React.createElement('div', { className: 'git-empty' }, 'No local changes.')
350
+ : workingTree.map(function (item, idx) { return renderFileRow(item, 'HEAD', null, false); })
351
+ )
352
+ ),
353
+
354
+ // ── Section 2: Changes in Branch ───────────────────────────────────────
355
+ React.createElement(
356
+ 'div',
357
+ { className: 'git-section ' + (branchExpanded ? 'expanded' : '') },
358
+ React.createElement(
359
+ 'div',
360
+ { className: 'git-section-title' },
361
+ React.createElement(
362
+ 'div',
363
+ { className: 'git-section-title-main hoverable', onClick: function () { setBranchExpanded(!branchExpanded); } },
364
+ React.createElement('i', { className: 'fas ' + (branchExpanded ? 'fa-chevron-down' : 'fa-chevron-right'), style: { width: '14px', fontSize: '10px' } }),
365
+ ' Changes in Branch\u2002',
366
+ React.createElement('span', { className: 'git-section-count' }, unpushedFiles.length)
367
+ ),
368
+ unpushedFiles.length > 0 && React.createElement(
369
+ 'button',
370
+ {
371
+ className: 'git-section-action-btn',
372
+ title: 'View all branch changes',
373
+ onClick: function (e) { e.stopPropagation(); onOpenAllChanges && onOpenAllChanges('branch', 'Changes in Branch'); }
374
+ },
375
+ React.createElement('i', { className: 'fas fa-layer-group' })
376
+ )
377
+ ),
378
+ branchExpanded && React.createElement(
379
+ React.Fragment,
380
+ null,
381
+ upstreamBranch
382
+ ? React.createElement('div', { className: 'git-hint' }, 'All files changed vs ', React.createElement('code', null, upstreamBranch))
383
+ : React.createElement('div', { className: 'git-hint' }, 'No upstream branch tracked.'),
384
+ React.createElement(
385
+ 'div',
386
+ { className: 'git-list' },
387
+ unpushedFiles.length === 0
388
+ ? React.createElement('div', { className: 'git-empty' }, 'No changes vs upstream.')
389
+ : unpushedFiles.map(function (item) { return renderFileRow(item, upstreamBranch, 'HEAD', true); })
390
+ )
391
+ )
392
+ ),
393
+
394
+ // ── Section 3: History ─────────────────────────────────────────────────
395
+ React.createElement(
396
+ 'div',
397
+ { className: 'git-section git-history-section ' + (historyExpanded ? 'expanded' : '') },
398
+ React.createElement(
399
+ 'div',
400
+ { className: 'git-section-title hoverable', onClick: function () { setHistoryExpanded(!historyExpanded); } },
401
+ React.createElement('i', { className: 'fas ' + (historyExpanded ? 'fa-chevron-down' : 'fa-chevron-right'), style: { width: '14px', fontSize: '10px' } }),
402
+ ' History\u2002',
403
+ React.createElement('span', { className: 'git-section-count' }, branchCommits.length)
404
+ ),
405
+ historyExpanded && React.createElement(
406
+ 'div',
407
+ { className: 'git-history-graph-wrap' },
408
+ branchCommits.length === 0
409
+ ? React.createElement('div', { className: 'git-empty' }, 'No commit history.')
410
+ : historyRows
411
+ )
412
+ )
413
+ );
414
+ };
415
+
416
+ window.GitPanel = GitPanel;