mbeditor 0.2.2

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