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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +116 -0
- data/README.md +180 -0
- data/app/assets/javascripts/mbeditor/application.js +21 -0
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +202 -0
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +71 -0
- data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +139 -0
- data/app/assets/javascripts/mbeditor/components/CommitGraph.js +65 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +166 -0
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +1139 -0
- data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +117 -0
- data/app/assets/javascripts/mbeditor/components/FileTree.js +339 -0
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +501 -0
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +3108 -0
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +272 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +186 -0
- data/app/assets/javascripts/mbeditor/components/TabBar.js +238 -0
- data/app/assets/javascripts/mbeditor/components/TestResultsPanel.js +150 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +758 -0
- data/app/assets/javascripts/mbeditor/editor_store.js +69 -0
- data/app/assets/javascripts/mbeditor/file_icon.js +30 -0
- data/app/assets/javascripts/mbeditor/file_service.js +96 -0
- data/app/assets/javascripts/mbeditor/git_service.js +104 -0
- data/app/assets/javascripts/mbeditor/search_service.js +63 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +485 -0
- data/app/assets/stylesheets/mbeditor/application.css +848 -0
- data/app/assets/stylesheets/mbeditor/editor.css +2061 -0
- data/app/controllers/mbeditor/application_controller.rb +70 -0
- data/app/controllers/mbeditor/editors_controller.rb +996 -0
- data/app/controllers/mbeditor/git_controller.rb +234 -0
- data/app/services/mbeditor/git_blame_service.rb +98 -0
- data/app/services/mbeditor/git_commit_graph_service.rb +60 -0
- data/app/services/mbeditor/git_diff_service.rb +74 -0
- data/app/services/mbeditor/git_file_history_service.rb +42 -0
- data/app/services/mbeditor/git_service.rb +95 -0
- data/app/services/mbeditor/redmine_service.rb +86 -0
- data/app/services/mbeditor/ruby_definition_service.rb +168 -0
- data/app/services/mbeditor/test_runner_service.rb +286 -0
- data/app/views/layouts/mbeditor/application.html.erb +120 -0
- data/app/views/mbeditor/editors/index.html.erb +1 -0
- data/config/initializers/assets.rb +9 -0
- data/config/routes.rb +44 -0
- data/lib/mbeditor/configuration.rb +22 -0
- data/lib/mbeditor/engine.rb +37 -0
- data/lib/mbeditor/rack/silence_ping_request.rb +56 -0
- data/lib/mbeditor/version.rb +3 -0
- data/lib/mbeditor.rb +19 -0
- data/mbeditor.gemspec +31 -0
- data/public/mbeditor-icon.svg +4 -0
- data/public/min-maps/vs/base/worker/workerMain.js.map +1 -0
- data/public/monaco-editor/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- data/public/monaco-editor/vs/base/worker/workerMain.js +31 -0
- data/public/monaco-editor/vs/basic-languages/cameligo/cameligo.js +10 -0
- data/public/monaco-editor/vs/basic-languages/css/css.js +12 -0
- data/public/monaco-editor/vs/basic-languages/dart/dart.js +10 -0
- data/public/monaco-editor/vs/basic-languages/flow9/flow9.js +10 -0
- data/public/monaco-editor/vs/basic-languages/go/go.js +10 -0
- data/public/monaco-editor/vs/basic-languages/handlebars/handlebars.js +440 -0
- data/public/monaco-editor/vs/basic-languages/javascript/javascript.js +10 -0
- data/public/monaco-editor/vs/basic-languages/markdown/markdown.js +10 -0
- data/public/monaco-editor/vs/basic-languages/msdax/msdax.js +10 -0
- data/public/monaco-editor/vs/basic-languages/postiats/postiats.js +10 -0
- data/public/monaco-editor/vs/basic-languages/pug/pug.js +412 -0
- data/public/monaco-editor/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
- data/public/monaco-editor/vs/basic-languages/ruby/ruby.js +10 -0
- data/public/monaco-editor/vs/basic-languages/sb/sb.js +10 -0
- data/public/monaco-editor/vs/basic-languages/shell/shell.js +41 -0
- data/public/monaco-editor/vs/basic-languages/typescript/typescript.js +10 -0
- data/public/monaco-editor/vs/basic-languages/typespec/typespec.js +10 -0
- data/public/monaco-editor/vs/basic-languages/yaml/yaml.js +10 -0
- data/public/monaco-editor/vs/editor/editor.api.js +6 -0
- data/public/monaco-editor/vs/editor/editor.main.css +8 -0
- data/public/monaco-editor/vs/editor/editor.main.js +797 -0
- data/public/monaco-editor/vs/language/typescript/tsMode.js +20 -0
- data/public/monaco-editor/vs/language/typescript/tsWorker.js +51328 -0
- data/public/monaco-editor/vs/loader.js +10 -0
- data/public/monaco-editor/vs/nls.messages.de.js +20 -0
- data/public/monaco-editor/vs/nls.messages.es.js +20 -0
- data/public/monaco-editor/vs/nls.messages.fr.js +18 -0
- data/public/monaco-editor/vs/nls.messages.it.js +18 -0
- data/public/monaco-editor/vs/nls.messages.ja.js +20 -0
- data/public/monaco-editor/vs/nls.messages.ko.js +18 -0
- data/public/monaco-editor/vs/nls.messages.ru.js +20 -0
- data/public/monaco-editor/vs/nls.messages.zh-cn.js +20 -0
- data/public/monaco-editor/vs/nls.messages.zh-tw.js +18 -0
- data/public/monaco_worker.js +5 -0
- data/public/sw.js +8 -0
- data/public/ts_worker.js +5 -0
- data/vendor/assets/javascripts/axios.min.js +5 -0
- data/vendor/assets/javascripts/emmet.js +5452 -0
- data/vendor/assets/javascripts/lodash.min.js +136 -0
- data/vendor/assets/javascripts/marked.min.js +6 -0
- data/vendor/assets/javascripts/minisearch.min.js +2044 -0
- data/vendor/assets/javascripts/monaco-themes-bundle.js +10 -0
- data/vendor/assets/javascripts/monaco-vim.js +9867 -0
- data/vendor/assets/javascripts/prettier-plugin-babel.js +16 -0
- data/vendor/assets/javascripts/prettier-plugin-estree.js +35 -0
- data/vendor/assets/javascripts/prettier-plugin-html.js +19 -0
- data/vendor/assets/javascripts/prettier-plugin-markdown.js +59 -0
- data/vendor/assets/javascripts/prettier-plugin-postcss.js +52 -0
- data/vendor/assets/javascripts/prettier-standalone.js +37 -0
- data/vendor/assets/javascripts/react-dom.min.js +267 -0
- data/vendor/assets/javascripts/react.min.js +31 -0
- data/vendor/assets/stylesheets/fontawesome.min.css.erb +9 -0
- data/vendor/assets/webfonts/fa-brands-400.woff2 +0 -0
- data/vendor/assets/webfonts/fa-regular-400.woff2 +0 -0
- data/vendor/assets/webfonts/fa-solid-900.woff2 +0 -0
- 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;
|