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,2335 @@
1
+ "use strict";
2
+
3
+ var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })();
4
+
5
+ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
6
+
7
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
8
+
9
+ var _React = React;
10
+ var useState = _React.useState;
11
+ var useEffect = _React.useEffect;
12
+ var useRef = _React.useRef;
13
+
14
+ var SIDEBAR_MIN_WIDTH = 240;
15
+ var SIDEBAR_MAX_WIDTH = 560;
16
+ var EDITOR_MIN_WIDTH = 320;
17
+ var GIT_PANEL_MIN_WIDTH = 280;
18
+ var PANE_MIN_WIDTH_PERCENT = 20;
19
+ var PANE_MAX_WIDTH_PERCENT = 80;
20
+
21
+ var SidebarActionButton = function SidebarActionButton(_ref) {
22
+ var title = _ref.title;
23
+ var iconClass = _ref.iconClass;
24
+ var onClick = _ref.onClick;
25
+ var _ref$disabled = _ref.disabled;
26
+ var disabled = _ref$disabled === undefined ? false : _ref$disabled;
27
+ var _ref$danger = _ref.danger;
28
+ var danger = _ref$danger === undefined ? false : _ref$danger;
29
+ var _ref$ariaLabel = _ref.ariaLabel;
30
+ var ariaLabel = _ref$ariaLabel === undefined ? null : _ref$ariaLabel;
31
+
32
+ return React.createElement(
33
+ "button",
34
+ {
35
+ type: "button",
36
+ className: "project-action-btn" + (danger ? " danger" : ""),
37
+ title: title,
38
+ "aria-label": ariaLabel || title,
39
+ onClick: onClick,
40
+ disabled: !!disabled
41
+ },
42
+ React.createElement("i", { className: iconClass })
43
+ );
44
+ };
45
+
46
+ var SectionActionGroup = function SectionActionGroup(_ref2) {
47
+ var ariaLabel = _ref2.ariaLabel;
48
+ var children = _ref2.children;
49
+ var _ref2$className = _ref2.className;
50
+ var className = _ref2$className === undefined ? "" : _ref2$className;
51
+
52
+ return React.createElement(
53
+ "div",
54
+ {
55
+ className: "project-actions" + (className ? " " + className : ""),
56
+ role: "toolbar",
57
+ "aria-label": ariaLabel
58
+ },
59
+ children
60
+ );
61
+ };
62
+
63
+ var MbeditorApp = function MbeditorApp() {
64
+ var _useState = useState(EditorStore.getState());
65
+
66
+ var _useState2 = _slicedToArray(_useState, 2);
67
+
68
+ var state = _useState2[0];
69
+ var setState = _useState2[1];
70
+
71
+ var _useState21 = useState(null);
72
+ var _useState22 = _slicedToArray(_useState21, 2);
73
+ var historyPanelPath = _useState22[0];
74
+ var setHistoryPanelPath = _useState22[1];
75
+
76
+ var _useState23 = useState(false);
77
+ var _useState24 = _slicedToArray(_useState23, 2);
78
+ var isNavigating = _useState24[0];
79
+ var setIsNavigating = _useState24[1];
80
+
81
+ var _useState25 = useState(false);
82
+ var _useState26 = _slicedToArray(_useState25, 2);
83
+ var isReviewOpen = _useState26[0];
84
+ var setIsReviewOpen = _useState26[1];
85
+
86
+ var _useState27 = useState(null);
87
+ var _useState28 = _slicedToArray(_useState27, 2);
88
+ var selectedCommit = _useState28[0];
89
+ var setSelectedCommit = _useState28[1];
90
+
91
+ var _useState29 = useState(null);
92
+ var _useState30 = _slicedToArray(_useState29, 2);
93
+ var commitDetailFiles = _useState30[0];
94
+ var setCommitDetailFiles = _useState30[1];
95
+
96
+ var _useState4 = useState([]);
97
+
98
+ var _useState42 = _slicedToArray(_useState4, 2);
99
+
100
+ var treeData = _useState42[0];
101
+ var setTreeData = _useState42[1];
102
+
103
+ var _useState5 = useState("");
104
+
105
+ var _useState52 = _slicedToArray(_useState5, 2);
106
+
107
+ var projectRootName = _useState52[0];
108
+ var setProjectRootName = _useState52[1];
109
+
110
+ var _useState6 = useState(null);
111
+
112
+ var _useState62 = _slicedToArray(_useState6, 2);
113
+
114
+ var selectedTreeNode = _useState62[0];
115
+ var setSelectedTreeNode = _useState62[1];
116
+
117
+ var _useState7 = useState("");
118
+
119
+ var _useState72 = _slicedToArray(_useState7, 2);
120
+
121
+ var searchQuery = _useState72[0];
122
+ var setSearchQuery = _useState72[1];
123
+
124
+ var _useState8 = useState("explorer");
125
+
126
+ var _useState82 = _slicedToArray(_useState8, 2);
127
+
128
+ var activeSidebarTab = _useState82[0];
129
+ var setActiveSidebarTab = _useState82[1];
130
+
131
+ var _useState9 = useState({});
132
+
133
+ var _useState92 = _slicedToArray(_useState9, 2);
134
+
135
+ var markers = _useState92[0];
136
+ var setMarkers = _useState92[1];
137
+ // { tabId: [] }
138
+
139
+ var _useState10 = useState({});
140
+
141
+ var _useState102 = _slicedToArray(_useState10, 2);
142
+
143
+ var loading = _useState102[0];
144
+ var setLoading = _useState102[1];
145
+
146
+ var _useState11 = useState(null);
147
+
148
+ var _useState112 = _slicedToArray(_useState11, 2);
149
+
150
+ var closingTabId = _useState112[0];
151
+ var setClosingTabId = _useState112[1];
152
+
153
+ var _useState12 = useState(null);
154
+
155
+ var _useState122 = _slicedToArray(_useState12, 2);
156
+
157
+ var closingPaneId = _useState122[0];
158
+ var setClosingPaneId = _useState122[1];
159
+
160
+ var _useState13 = useState(SIDEBAR_MIN_WIDTH);
161
+
162
+ var _useState132 = _slicedToArray(_useState13, 2);
163
+
164
+ var sidebarWidth = _useState132[0];
165
+ var setSidebarWidth = _useState132[1];
166
+
167
+ var _useState14 = useState(50);
168
+
169
+ var _useState142 = _slicedToArray(_useState14, 2);
170
+
171
+ var pane1Width = _useState142[0];
172
+ var setPane1Width = _useState142[1];
173
+ // percentage
174
+
175
+ var _useState15 = useState(null);
176
+
177
+ var _useState152 = _slicedToArray(_useState15, 2);
178
+
179
+ var activeResizeMode = _useState152[0];
180
+ var setActiveResizeMode = _useState152[1];
181
+
182
+ var _useState16 = useState(null);
183
+
184
+ var _useState162 = _slicedToArray(_useState16, 2);
185
+
186
+ var draggedTab = _useState162[0];
187
+ var setDraggedTab = _useState162[1];
188
+
189
+ var _useState17 = useState(null);
190
+
191
+ var _useState172 = _slicedToArray(_useState17, 2);
192
+
193
+ var dragOverPaneId = _useState172[0];
194
+ var setDragOverPaneId = _useState172[1];
195
+
196
+ var _useState18 = useState(false);
197
+ var _useState182 = _slicedToArray(_useState18, 2);
198
+ var showGitPanel = _useState182[0];
199
+ var setShowGitPanel = _useState182[1];
200
+
201
+ var _useState18g = useState(320);
202
+ var _useState18g2 = _slicedToArray(_useState18g, 2);
203
+ var gitPanelWidth = _useState18g2[0];
204
+ var setGitPanelWidth = _useState18g2[1];
205
+
206
+ var _useState18h = useState(false);
207
+
208
+ var _useState18h2 = _slicedToArray(_useState18h, 2);
209
+
210
+ var showHelp = _useState18h2[0];
211
+ var setShowHelp = _useState18h2[1];
212
+
213
+ var _useState18b = useState(true);
214
+
215
+ var _useState18b2 = _slicedToArray(_useState18b, 2);
216
+
217
+ var serverOnline = _useState18b2[0];
218
+ var setServerOnline = _useState18b2[1];
219
+
220
+ var _useState18c = useState(false);
221
+
222
+ var _useState18c2 = _slicedToArray(_useState18c, 2);
223
+
224
+ var rubocopAvailable = _useState18c2[0];
225
+ var setRubocopAvailable = _useState18c2[1];
226
+
227
+ var _useState18d = useState(false);
228
+
229
+ var _useState18d2 = _slicedToArray(_useState18d, 2);
230
+
231
+ var hamlLintAvailable = _useState18d2[0];
232
+ var setHamlLintAvailable = _useState18d2[1];
233
+
234
+ var _useState18e = useState(false);
235
+ var _useState18e2 = _slicedToArray(_useState18e, 2);
236
+ var gitAvailable = _useState18e2[0];
237
+ var setGitAvailable = _useState18e2[1];
238
+
239
+ var _useState19 = useState({
240
+ openEditors: false,
241
+ projects: false
242
+ });
243
+
244
+ var _useState192 = _slicedToArray(_useState19, 2);
245
+
246
+ var collapsedSections = _useState192[0];
247
+ var setCollapsedSections = _useState192[1];
248
+
249
+ var _useState20 = useState({});
250
+
251
+ var _useState202 = _slicedToArray(_useState20, 2);
252
+
253
+ var expandedDirs = _useState202[0];
254
+ var setExpandedDirs = _useState202[1];
255
+
256
+ var _useState21 = useState(null);
257
+
258
+ var _useState212 = _slicedToArray(_useState21, 2);
259
+
260
+ var pendingCreate = _useState212[0];
261
+ var setPendingCreate = _useState212[1];
262
+
263
+ var _useState22 = useState(null);
264
+
265
+ var _useState222 = _slicedToArray(_useState22, 2);
266
+
267
+ var pendingRename = _useState222[0];
268
+ var setPendingRename = _useState222[1];
269
+
270
+ var _useState23 = useState(null);
271
+
272
+ var _useState232 = _slicedToArray(_useState23, 2);
273
+
274
+ var contextMenu = _useState232[0];
275
+ var setContextMenu = _useState232[1];
276
+
277
+ var resizeSessionRef = useRef(null);
278
+ var resizeRafRef = useRef(null);
279
+
280
+ var clamp = function clamp(value, min, max) {
281
+ return Math.min(max, Math.max(min, value));
282
+ };
283
+
284
+ var normalizeRelativePath = function normalizeRelativePath(input) {
285
+ return (input || "").replace(/\\/g, "/").trim().replace(/^\/+/, "").replace(/\/+$/, "").replace(/\/+/g, "/");
286
+ };
287
+
288
+ var parentDir = function parentDir(path) {
289
+ if (!path) return "";
290
+ var idx = path.lastIndexOf("/");
291
+ return idx > 0 ? path.slice(0, idx) : "";
292
+ };
293
+
294
+ var deriveProjectRootName = function deriveProjectRootName() {
295
+ if (projectRootName) return projectRootName;
296
+ var railsRoot = document && document.body && document.body.dataset ? document.body.dataset.railsRoot : "";
297
+ if (!railsRoot) return "PROJECT";
298
+ var parts = railsRoot.split("/").filter(Boolean);
299
+ return parts.length ? parts[parts.length - 1] : "PROJECT";
300
+ };
301
+
302
+ var refreshProjectTree = function refreshProjectTree() {
303
+ return FileService.getTree().then(function (data) {
304
+ setTreeData(data || []);
305
+ SearchService.buildIndex(data || []);
306
+ return data || [];
307
+ })["catch"](function (err) {
308
+ EditorStore.setStatus("Failed to refresh files: " + (err && err.message || "Unknown error"), "error");
309
+ return [];
310
+ });
311
+ };
312
+
313
+ var isRubyPath = function isRubyPath(path) {
314
+ return path && (path.endsWith('.rb') || path.endsWith('.gemspec') || path.endsWith('Rakefile') || path.endsWith('Gemfile'));
315
+ };
316
+
317
+ var applyMarkersForTab = function applyMarkersForTab(paneId, tabId, nextMarkers) {
318
+ var currentPane = EditorStore.getState().panes.find(function (p) {
319
+ return p.id === paneId;
320
+ });
321
+ var current = currentPane ? currentPane.tabs.find(function (t) {
322
+ return t.id === tabId;
323
+ }) : null;
324
+ if (current) current.markers = nextMarkers;
325
+ setMarkers(function (prev) {
326
+ return _extends({}, prev, _defineProperty({}, tabId, nextMarkers));
327
+ });
328
+ };
329
+
330
+ var runRubyLint = function runRubyLint(tab, paneId) {
331
+ var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
332
+
333
+ if (!tab || (!isRubyPath(tab.path) && !tab.path.endsWith('.haml'))) return Promise.resolve(null);
334
+
335
+ if (options.showLoading) {
336
+ setLoading(function (prev) {
337
+ return _extends({}, prev, { lint: true });
338
+ });
339
+ }
340
+ if (options.showStatus) {
341
+ EditorStore.setStatus('Linting...', 'info');
342
+ }
343
+
344
+ return FileService.lintFile(tab.path, tab.content).then(function (res) {
345
+ var nextMarkers = res.markers || [];
346
+ applyMarkersForTab(paneId, tab.id, nextMarkers);
347
+
348
+ if (options.showStatus) {
349
+ var count = res.summary && res.summary.offense_count || 0;
350
+ EditorStore.setStatus(count === 0 ? 'No RuboCop offenses!' : "Found " + count + " offenses", count === 0 ? 'success' : 'warning');
351
+ }
352
+
353
+ return res;
354
+ })["catch"](function (err) {
355
+ if (options.showStatus) {
356
+ EditorStore.setStatus('Lint failed: ' + err.message, 'error');
357
+ }
358
+ return null;
359
+ })["finally"](function () {
360
+ if (options.showLoading) {
361
+ setLoading(function (prev) {
362
+ return _extends({}, prev, { lint: false });
363
+ });
364
+ }
365
+ });
366
+ };
367
+
368
+ var _debouncedAutoLint = useRef(window._.debounce(function (tab, paneId) {
369
+ if (!tab) return;
370
+ if (isRubyPath(tab.path)) {
371
+ runRubyLint(tab, paneId);
372
+ return;
373
+ }
374
+ if (tab.path.endsWith('.haml')) {
375
+ runRubyLint(tab, paneId);
376
+ return;
377
+ }
378
+
379
+ var ext = tab.path.split('.').pop().toLowerCase();
380
+ var formatMap = {
381
+ 'js': 'babel', 'jsx': 'babel',
382
+ 'json': 'json',
383
+ 'css': 'css', 'scss': 'scss',
384
+ 'html': 'html', 'md': 'markdown'
385
+ };
386
+ var parserName = formatMap[ext];
387
+
388
+ if (parserName && window.prettier && window.prettierPlugins) {
389
+ window.prettier.format(tab.content, {
390
+ parser: parserName,
391
+ plugins: Object.values(window.prettierPlugins),
392
+ tabWidth: 4,
393
+ useTabs: false
394
+ }).then(function () {}).then(function () {
395
+ var currentPane = EditorStore.getState().panes.find(function (p) {
396
+ return p.id === paneId;
397
+ });
398
+ var current = currentPane ? currentPane.tabs.find(function (t) {
399
+ return t.id === tab.id;
400
+ }) : null;
401
+ if (current) current.markers = [];
402
+ setMarkers(function (prev) {
403
+ return _extends({}, prev, _defineProperty({}, tab.id, []));
404
+ });
405
+ })["catch"](function (err) {
406
+ var newMarkers = [];
407
+ if (err && err.loc && err.loc.start) {
408
+ newMarkers.push({
409
+ severity: "error",
410
+ message: err.message.split("\n")[0] || "Syntax error",
411
+ startLine: err.loc.start.line,
412
+ startCol: err.loc.start.column,
413
+ endLine: err.loc.end ? err.loc.end.line : err.loc.start.line,
414
+ endCol: err.loc.end ? err.loc.end.column : err.loc.start.column + 1
415
+ });
416
+ }
417
+ var currentPane = EditorStore.getState().panes.find(function (p) {
418
+ return p.id === paneId;
419
+ });
420
+ var current = currentPane ? currentPane.tabs.find(function (t) {
421
+ return t.id === tab.id;
422
+ }) : null;
423
+ if (current) current.markers = newMarkers;
424
+ setMarkers(function (prev) {
425
+ return _extends({}, prev, _defineProperty({}, tab.id, newMarkers));
426
+ });
427
+ });
428
+ }
429
+ }, 600)).current;
430
+
431
+ var setQuickOpen = function setQuickOpen(visible) {
432
+ EditorStore.setState({ isQuickOpenVisible: visible });
433
+ };
434
+
435
+ // Persist state when openTabs or activeTabId changes
436
+ useEffect(function () {
437
+ // Subscribe to EditorStore
438
+ var unsubscribe = EditorStore.subscribe(setState);
439
+
440
+ // Initial load
441
+ Promise.all([FileService.getWorkspace()["catch"](function () {
442
+ return null;
443
+ }), refreshProjectTree()]).then(function (_ref) {
444
+ var _ref2 = _slicedToArray(_ref, 1);
445
+
446
+ var workspace = _ref2[0];
447
+
448
+ if (workspace && workspace.rootName) {
449
+ setProjectRootName(workspace.rootName);
450
+ }
451
+ if (workspace && typeof workspace.rubocopAvailable === 'boolean') {
452
+ setRubocopAvailable(workspace.rubocopAvailable);
453
+ }
454
+ if (workspace && typeof workspace.hamlLintAvailable === 'boolean') {
455
+ setHamlLintAvailable(workspace.hamlLintAvailable);
456
+ }
457
+ if (workspace && typeof workspace.gitAvailable === 'boolean') {
458
+ setGitAvailable(workspace.gitAvailable);
459
+ }
460
+ });
461
+ GitService.fetchStatus();
462
+
463
+ // Load persisted state
464
+ FileService.getState().then(function (savedState) {
465
+ var panesToLoad = savedState && savedState.panes;
466
+ if (savedState && savedState.openTabs) {
467
+ panesToLoad = [{ id: 1, tabs: savedState.openTabs, activeTabId: savedState.activeTabId }, { id: 2, tabs: [], activeTabId: null }];
468
+ }
469
+ if (panesToLoad && panesToLoad.length > 0) {
470
+ var allTabs = panesToLoad.flatMap(function (p) {
471
+ return p.tabs;
472
+ });
473
+ Promise.all(allTabs.map(function (t) {
474
+ if (t.isDiff && t.repoPath) {
475
+ return GitService.fetchDiff(t.repoPath, t.diffBaseSha, t.diffHeadSha)
476
+ .then(function (d) { return { content: 'Diff loaded', diffOriginal: d.original || '', diffModified: d.modified || '', _isDiffResult: true }; })
477
+ ["catch"](function () { return { content: '', diffOriginal: '', diffModified: '', _isDiffResult: true }; });
478
+ }
479
+ if (t.isCombinedDiff || (t.path || '').startsWith('combined-diff://') || (t.path || '').startsWith('diff://')) {
480
+ return Promise.resolve({ content: '' });
481
+ }
482
+ var sourcePath = t.isPreview || /::preview$/.test(t.path || '') ? t.previewFor || (t.path || '').replace(/::preview$/, '') : t.path;
483
+ return FileService.getFile(sourcePath)["catch"](function () {
484
+ return { content: '' };
485
+ });
486
+ })).then(function (results) {
487
+ var resIdx = 0;
488
+ var restoredPanes = panesToLoad.map(function (p) {
489
+ return _extends({}, p, {
490
+ tabs: p.tabs.map(function (t) {
491
+ var res = results[resIdx++];
492
+ return _extends({}, t, { content: res.content }, res._isDiffResult ? { diffOriginal: res.diffOriginal, diffModified: res.diffModified } : {});
493
+ })
494
+ });
495
+ });
496
+ EditorStore.setState(_extends({}, savedState, { panes: restoredPanes, openTabs: undefined }));
497
+ // Restore collapsedSections UI state
498
+ if (savedState.collapsedSections) {
499
+ setCollapsedSections(savedState.collapsedSections);
500
+ }
501
+ if (savedState.expandedDirs) {
502
+ setExpandedDirs(savedState.expandedDirs);
503
+ }
504
+ if (typeof savedState.showGitPanel === 'boolean') {
505
+ setShowGitPanel(savedState.showGitPanel);
506
+ if (savedState.showGitPanel) {
507
+ GitService.fetchInfo();
508
+ }
509
+ }
510
+ if (typeof savedState.gitPanelWidth === 'number') {
511
+ setGitPanelWidth(savedState.gitPanelWidth);
512
+ }
513
+ });
514
+ }
515
+ });
516
+
517
+ // Hotkeys setup
518
+ var onKeyDown = function onKeyDown(e) {
519
+ if ((e.ctrlKey || e.metaKey) && e.key === 'p') {
520
+ e.preventDefault();
521
+ setQuickOpen(true);
522
+ }
523
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
524
+ (function () {
525
+ e.preventDefault();
526
+ var st = EditorStore.getState();
527
+ var focusedPane = st.panes.find(function (p) {
528
+ return p.id === st.focusedPaneId;
529
+ }) || st.panes[0];
530
+ if (focusedPane && focusedPane.activeTabId) {
531
+ var tab = focusedPane.tabs.find(function (t) {
532
+ return t.id === focusedPane.activeTabId;
533
+ });
534
+ if (tab && tab.dirty) handleSave(focusedPane.id, tab);
535
+ }
536
+ })();
537
+ }
538
+ if (e.key === 'Escape') {
539
+ setContextMenu(null);
540
+ setShowHelp(false);
541
+ }
542
+ };
543
+
544
+ var handleMouseMove = function handleMouseMove(e) {
545
+ var session = resizeSessionRef.current;
546
+ if (!session) return;
547
+
548
+ // Throttle via rAF — skip if a frame is already queued to avoid paint thrashing
549
+ if (resizeRafRef.current) return;
550
+ var clientX = e.clientX;
551
+ resizeRafRef.current = requestAnimationFrame(function () {
552
+ resizeRafRef.current = null;
553
+ var s = resizeSessionRef.current;
554
+ if (!s) return;
555
+
556
+ if (s.mode === 'pane') {
557
+ var container = document.getElementById('ide-main-split-container');
558
+ if (!container) return;
559
+
560
+ var rect = container.getBoundingClientRect();
561
+ var nextWidth = (clientX - rect.left) / rect.width * 100;
562
+ setPane1Width(clamp(nextWidth, PANE_MIN_WIDTH_PERCENT, PANE_MAX_WIDTH_PERCENT));
563
+ }
564
+
565
+ if (s.mode === 'sidebar') {
566
+ var body = document.getElementById('ide-body-container');
567
+ if (!body) return;
568
+
569
+ var rect = body.getBoundingClientRect();
570
+ var reservedRight = EDITOR_MIN_WIDTH + (showGitPanel ? gitPanelWidth : 0);
571
+ var maxSidebarWidth = Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, rect.width - reservedRight));
572
+ var nextWidth = clientX - rect.left;
573
+ setSidebarWidth(clamp(nextWidth, SIDEBAR_MIN_WIDTH, maxSidebarWidth));
574
+ }
575
+
576
+ if (s.mode === 'gitpanel') {
577
+ var body = document.getElementById('ide-body-container');
578
+ if (!body) return;
579
+
580
+ var rect = body.getBoundingClientRect();
581
+ var nextWidth = rect.right - clientX;
582
+ setGitPanelWidth(clamp(nextWidth, GIT_PANEL_MIN_WIDTH, 600));
583
+ }
584
+ });
585
+ };
586
+
587
+ var handleMouseUp = function handleMouseUp() {
588
+ if (!resizeSessionRef.current) return;
589
+
590
+ if (resizeRafRef.current) {
591
+ cancelAnimationFrame(resizeRafRef.current);
592
+ resizeRafRef.current = null;
593
+ }
594
+ resizeSessionRef.current = null;
595
+ setActiveResizeMode(null);
596
+ document.body.style.cursor = '';
597
+ document.body.style.userSelect = '';
598
+ };
599
+
600
+ window.addEventListener('keydown', onKeyDown);
601
+ window.addEventListener('mousemove', handleMouseMove);
602
+ window.addEventListener('mouseup', handleMouseUp);
603
+ return function () {
604
+ unsubscribe();
605
+ if (resizeRafRef.current) {
606
+ cancelAnimationFrame(resizeRafRef.current);
607
+ resizeRafRef.current = null;
608
+ }
609
+ window.removeEventListener('keydown', onKeyDown);
610
+ window.removeEventListener('mousemove', handleMouseMove);
611
+ window.removeEventListener('mouseup', handleMouseUp);
612
+ document.body.style.cursor = '';
613
+ document.body.style.userSelect = '';
614
+ };
615
+ }, []);
616
+
617
+ // Heartbeat — poll /ping every 5s and reflect connectivity in the status bar
618
+ useEffect(function () {
619
+ var wasOnline = true;
620
+ var interval = setInterval(function () {
621
+ FileService.ping().then(function () {
622
+ if (!wasOnline) {
623
+ wasOnline = true;
624
+ setServerOnline(true);
625
+ }
626
+ }).catch(function () {
627
+ if (wasOnline) {
628
+ wasOnline = false;
629
+ setServerOnline(false);
630
+ }
631
+ });
632
+ }, 5000);
633
+ return function () { clearInterval(interval); };
634
+ }, []);
635
+
636
+ var handleSelectFile = function handleSelectFile(path, name, line) {
637
+ TabManager.openTab(path, name, line);
638
+ setSelectedTreeNode({ path: path, name: name || path.split('/').pop(), type: 'file' });
639
+ setQuickOpen(false);
640
+ };
641
+
642
+ // Single-click in explorer: soft (preview) open — replaces any existing soft tab
643
+ var handleSoftOpenFile = function handleSoftOpenFile(path, name) {
644
+ TabManager.openTab(path, name, null, null, true);
645
+ setSelectedTreeNode({ path: path, name: name || path.split('/').pop(), type: 'file' });
646
+ };
647
+
648
+ // Double-click in explorer or on tab: harden the tab (remove italic/preview)
649
+ var handleHardOpenFile = function handleHardOpenFile(path, name) {
650
+ var st = EditorStore.getState();
651
+ var targetPane = st.panes.find(function (p) {
652
+ return p.tabs.some(function (t) {
653
+ return t.path === path;
654
+ });
655
+ });
656
+ if (targetPane) {
657
+ TabManager.hardenTab(targetPane.id, path);
658
+ TabManager.switchTab(targetPane.id, path);
659
+ } else {
660
+ TabManager.openTab(path, name, null, null, false);
661
+ }
662
+ setSelectedTreeNode({ path: path, name: name || path.split('/').pop(), type: 'file' });
663
+ };
664
+
665
+ var requestCloseTab = function requestCloseTab(paneId, id) {
666
+ var pane = state.panes.find(function (p) {
667
+ return p.id === paneId;
668
+ }) || state.panes[0];
669
+ var tab = pane.tabs.find(function (t) {
670
+ return t.id === id;
671
+ });
672
+ if (tab && tab.dirty) {
673
+ setClosingPaneId(paneId);
674
+ setClosingTabId(id);
675
+ } else {
676
+ TabManager.closeTab(paneId, id);
677
+ }
678
+ };
679
+
680
+ var confirmCloseTab = function confirmCloseTab(save) {
681
+ var pane = state.panes.find(function (p) {
682
+ return p.id === closingPaneId;
683
+ });
684
+ var tab = pane ? pane.tabs.find(function (t) {
685
+ return t.id === closingTabId;
686
+ }) : null;
687
+ if (!tab) {
688
+ setClosingTabId(null);
689
+ setClosingPaneId(null);
690
+ return;
691
+ }
692
+
693
+ if (save) {
694
+ setLoading(function (prev) {
695
+ return _extends({}, prev, { save: true });
696
+ });
697
+ EditorStore.setStatus("Saving " + tab.name + "...", "info");
698
+ FileService.saveFile(tab.path, tab.content).then(function () {
699
+ EditorStore.setStatus("Saved", "success");
700
+ GitService.fetchStatus();
701
+ TabManager.closeTab(closingPaneId, tab.id);
702
+ })["catch"](function (err) {
703
+ EditorStore.setStatus("Save failed: " + err.message, "error");
704
+ })["finally"](function () {
705
+ setLoading(function (prev) {
706
+ return _extends({}, prev, { save: false });
707
+ });
708
+ setClosingTabId(null);
709
+ setClosingPaneId(null);
710
+ });
711
+ } else {
712
+ TabManager.closeTab(closingPaneId, tab.id);
713
+ setClosingTabId(null);
714
+ setClosingPaneId(null);
715
+ }
716
+ };
717
+
718
+ var confirmBulkClose = function confirmBulkClose(tabs, scopeLabel) {
719
+ var dirtyCount = tabs.filter(function (tab) {
720
+ return tab.dirty;
721
+ }).length;
722
+
723
+ if (dirtyCount === 0) return true;
724
+
725
+ var dirtyLabel = dirtyCount === 1 ? "1 unsaved editor has changes." : dirtyCount + " unsaved editors have changes.";
726
+ return window.confirm(dirtyLabel + " Close " + scopeLabel + " without saving?");
727
+ };
728
+
729
+ var handleCloseAllEditors = function handleCloseAllEditors() {
730
+ var allTabs = state.panes.flatMap(function (pane) {
731
+ return pane.tabs;
732
+ });
733
+ if (allTabs.length === 0) return;
734
+ if (!confirmBulkClose(allTabs, "all editors")) return;
735
+
736
+ TabManager.closeAllTabs();
737
+ setClosingTabId(null);
738
+ setClosingPaneId(null);
739
+ EditorStore.setStatus("Closed " + allTabs.length + " editor" + (allTabs.length === 1 ? "" : "s"), "info");
740
+ };
741
+
742
+ var handleCloseEditorsInGroup = function handleCloseEditorsInGroup(paneId) {
743
+ var pane = state.panes.find(function (p) {
744
+ return p.id === paneId;
745
+ });
746
+ if (!pane || pane.tabs.length === 0) return;
747
+ if (!confirmBulkClose(pane.tabs, "all editors in Group " + paneId)) return;
748
+
749
+ TabManager.closeAllTabsInPane(paneId);
750
+ setClosingTabId(null);
751
+ setClosingPaneId(null);
752
+ EditorStore.setStatus("Closed " + pane.tabs.length + " editor" + (pane.tabs.length === 1 ? "" : "s") + " in Group " + paneId, "info");
753
+ };
754
+
755
+ // Persist state when panes, focusedPaneId, or collapsedSections changes
756
+ useEffect(function () {
757
+ // debounce explicitly using setTimeout to avoid spamming the backend
758
+ var timeoutId = setTimeout(function () {
759
+ var st = EditorStore.getState();
760
+ var lightweightPanes = st.panes.map(function (p) {
761
+ return {
762
+ id: p.id,
763
+ activeTabId: p.activeTabId,
764
+ tabs: p.tabs.filter(function(t) { return !t.isCombinedDiff; }).map(function (t) {
765
+ return {
766
+ id: t.id,
767
+ path: t.path,
768
+ name: t.name,
769
+ dirty: t.dirty,
770
+ viewState: t.viewState,
771
+ isPreview: !!t.isPreview,
772
+ previewFor: t.previewFor || null,
773
+ isDiff: !!t.isDiff,
774
+ diffBaseSha: t.diffBaseSha || null,
775
+ diffHeadSha: t.diffHeadSha || null,
776
+ repoPath: t.repoPath || null
777
+ };
778
+ })
779
+ };
780
+ });
781
+ FileService.saveState({ panes: lightweightPanes, focusedPaneId: st.focusedPaneId, collapsedSections: collapsedSections, expandedDirs: expandedDirs, showGitPanel: showGitPanel, gitPanelWidth: gitPanelWidth });
782
+ }, 1000);
783
+ return function () {
784
+ return clearTimeout(timeoutId);
785
+ };
786
+ }, [state.panes, state.focusedPaneId, collapsedSections, expandedDirs, showGitPanel, gitPanelWidth]);
787
+
788
+ var focusedPane = state.panes.find(function (p) {
789
+ return p.id === state.focusedPaneId;
790
+ }) || state.panes[0];
791
+ var activeTab = focusedPane.tabs.find(function (t) {
792
+ return t.id === focusedPane.activeTabId;
793
+ });
794
+
795
+ // Phase 7: Per-file last-commit info shown in the status bar
796
+ var _useState31 = useState(null);
797
+ var _useState32 = _slicedToArray(_useState31, 2);
798
+ var activeFileCommit = _useState32[0];
799
+ var setActiveFileCommit = _useState32[1];
800
+
801
+ useEffect(function () {
802
+ if (!gitAvailable || !activeTab || activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph || !activeTab.path || activeTab.path.indexOf('diff://') === 0 || activeTab.path.indexOf('combined-diff://') === 0) {
803
+ setActiveFileCommit(null);
804
+ return;
805
+ }
806
+ var currentPath = activeTab.path;
807
+ GitService.fetchFileHistory(currentPath).then(function(data) {
808
+ var first = data && data.history && data.history[0];
809
+ if (first) {
810
+ setActiveFileCommit({ hash: first.hash, title: first.title, author: first.author, date: first.date });
811
+ } else {
812
+ setActiveFileCommit(null);
813
+ }
814
+ }).catch(function() {
815
+ setActiveFileCommit(null);
816
+ });
817
+ }, [activeTab ? activeTab.id : null, gitAvailable]);
818
+
819
+ useEffect(function () {
820
+ if (!activeTab || typeof activeTab.content !== 'string') return;
821
+ if (activeTab.isDiff || activeTab.isCombinedDiff || activeTab.isCommitGraph) return;
822
+ if (isRubyPath(activeTab.path) && !rubocopAvailable) return;
823
+ if (activeTab.path.endsWith('.haml') && !hamlLintAvailable) return;
824
+
825
+ _debouncedAutoLint(activeTab, focusedPane.id);
826
+
827
+ return function () {
828
+ _debouncedAutoLint.cancel();
829
+ };
830
+ }, [focusedPane.id, activeTab ? activeTab.id : null, activeTab ? activeTab.content : null, rubocopAvailable, hamlLintAvailable]);
831
+
832
+ var handleOpenCommitGraph = function handleOpenCommitGraph() {
833
+ var paneId = state.focusedPaneId || 1;
834
+ var tabId = 'mbeditor://commit-graph';
835
+
836
+ var pane = state.panes.find(function(p) { return p.id === paneId; });
837
+ var existing = pane && pane.tabs.find(function(t) { return t.id === tabId; });
838
+ if (existing) {
839
+ TabManager.switchTab(paneId, tabId);
840
+ return;
841
+ }
842
+
843
+ var newTab = {
844
+ id: tabId,
845
+ path: tabId,
846
+ name: 'Commit Graph',
847
+ dirty: false,
848
+ content: '', // not used
849
+ isCommitGraph: true
850
+ };
851
+
852
+ var newPanes = state.panes.map(function(p) {
853
+ if (p.id === paneId) {
854
+ return Object.assign({}, p, { tabs: p.tabs.concat(newTab), activeTabId: tabId });
855
+ }
856
+ return p;
857
+ });
858
+
859
+ EditorStore.setState({ panes: newPanes, focusedPaneId: paneId, activeTabId: tabId });
860
+
861
+ // Fetch data asynchronously
862
+ GitService.fetchCommitGraph().then(function(data) {
863
+ var s = EditorStore.getState();
864
+ var p = s.panes.find(function(p) { return p.id === paneId; });
865
+ if (!p) return;
866
+ var t = p.tabs.find(function(t) { return t.id === tabId; });
867
+ if (t) {
868
+ var newPanes2 = s.panes.map(function(p2) {
869
+ if (p2.id === paneId) {
870
+ var newTabs = p2.tabs.map(function(t2) {
871
+ return t2.id === tabId ? Object.assign({}, t2, { commits: data.commits }) : t2;
872
+ });
873
+ return Object.assign({}, p2, { tabs: newTabs });
874
+ }
875
+ return p2;
876
+ });
877
+ EditorStore.setState({ panes: newPanes2 });
878
+ }
879
+ });
880
+ };
881
+
882
+ var handleSave = function handleSave(paneId, tab) {
883
+ setLoading(function (prev) {
884
+ return _extends({}, prev, { save: true });
885
+ });
886
+ EditorStore.setStatus("Saving " + tab.name + "...", "info");
887
+ FileService.saveFile(tab.path, tab.content).then(function () {
888
+ var newPanes = EditorStore.getState().panes.map(function (p) {
889
+ if (p.id === paneId) {
890
+ return _extends({}, p, { tabs: p.tabs.map(function (t) {
891
+ return t.id === tab.id ? _extends({}, t, { dirty: false, cleanContent: tab.content }) : t;
892
+ }) });
893
+ }
894
+ return p;
895
+ });
896
+ EditorStore.setState({ panes: newPanes });
897
+ EditorStore.setStatus("Saved", "success");
898
+ GitService.fetchStatus();
899
+ })["catch"](function (err) {
900
+ EditorStore.setStatus("Save failed: " + err.message, "error");
901
+ })["finally"](function () {
902
+ return setLoading(function (prev) {
903
+ return _extends({}, prev, { save: false });
904
+ });
905
+ });
906
+ };
907
+
908
+ var handleSaveAll = function handleSaveAll() {
909
+ var dirtyTabs = state.panes.flatMap(function (p) {
910
+ return p.tabs;
911
+ }).filter(function (t) {
912
+ return t.dirty;
913
+ });
914
+ if (dirtyTabs.length === 0) return;
915
+
916
+ setLoading(function (prev) {
917
+ return _extends({}, prev, { saveAll: true });
918
+ });
919
+ EditorStore.setStatus("Saving " + dirtyTabs.length + " files...", "info");
920
+ var promises = dirtyTabs.map(function (tab) {
921
+ return FileService.saveFile(tab.path, tab.content);
922
+ });
923
+ Promise.all(promises).then(function () {
924
+ var newPanes = EditorStore.getState().panes.map(function (p) {
925
+ return _extends({}, p, { tabs: p.tabs.map(function (t) {
926
+ return _extends({}, t, { dirty: false, cleanContent: t.content });
927
+ })
928
+ });
929
+ });
930
+ EditorStore.setState({ panes: newPanes });
931
+ EditorStore.setStatus("All files saved", "success");
932
+ GitService.fetchStatus();
933
+ })["catch"](function (err) {
934
+ EditorStore.setStatus("Failed to save some files", "error");
935
+ })["finally"](function () {
936
+ return setLoading(function (prev) {
937
+ return _extends({}, prev, { saveAll: false });
938
+ });
939
+ });
940
+ };
941
+
942
+ var handleTabDragStart = function handleTabDragStart(sourcePaneId, tabId) {
943
+ var pane2 = EditorStore.getState().panes.find(function (p) {
944
+ return p.id === 2;
945
+ });
946
+ if (!pane2 || pane2.tabs.length === 0) {
947
+ setPane1Width(50);
948
+ }
949
+ setDraggedTab({ sourcePaneId: sourcePaneId, tabId: tabId });
950
+ };
951
+
952
+ var clearDragState = function clearDragState() {
953
+ setDraggedTab(null);
954
+ setDragOverPaneId(null);
955
+ };
956
+
957
+ var moveDraggedTabToPane = function moveDraggedTabToPane(targetPaneId) {
958
+ if (!draggedTab) return;
959
+ TabManager.moveTabToPane(draggedTab.sourcePaneId, targetPaneId, draggedTab.tabId);
960
+ clearDragState();
961
+ };
962
+
963
+ var handleFormat = function handleFormat() {
964
+ if (!activeTab) return;
965
+
966
+ var isRubyLang = activeTab.path.endsWith('.rb') || activeTab.path.endsWith('.gemspec') || activeTab.path.endsWith("Rakefile") || activeTab.path.endsWith("Gemfile");
967
+
968
+ if (isRubyLang && !rubocopAvailable) {
969
+ EditorStore.setStatus("RuboCop is not available for this workspace.", "warning");
970
+ return;
971
+ }
972
+
973
+ if (activeTab.dirty) handleSave(focusedPane.id, activeTab); // save first
974
+
975
+ if (isRubyLang) {
976
+ setLoading(function (prev) {
977
+ return _extends({}, prev, { format: true });
978
+ });
979
+ EditorStore.setStatus("Formatting...", "info");
980
+ FileService.formatFile(activeTab.path).then(function (res) {
981
+ if (res.content) {
982
+ // Update content without marking dirty
983
+ var newPanes = EditorStore.getState().panes.map(function (p) {
984
+ if (p.id === focusedPane.id) return _extends({}, p, { tabs: p.tabs.map(function (t) {
985
+ return t.id === activeTab.id ? _extends({}, t, { content: res.content, dirty: false }) : t;
986
+ }) });
987
+ return p;
988
+ });
989
+ EditorStore.setState({ panes: newPanes });
990
+ }
991
+ EditorStore.setStatus("Formatted successfully", "success");
992
+ setMarkers(function (prev) {
993
+ return _extends({}, prev, _defineProperty({}, activeTab.id, []));
994
+ }); // clear lint markers
995
+ GitService.fetchStatus();
996
+ })["catch"](function (err) {
997
+ return EditorStore.setStatus("Format failed: " + err.message, "error");
998
+ })["finally"](function () {
999
+ return setLoading(function (prev) {
1000
+ return _extends({}, prev, { format: false });
1001
+ });
1002
+ });
1003
+ return;
1004
+ }
1005
+
1006
+ // Attempt Prettier Formatting
1007
+ var ext = activeTab.path.split('.').pop().toLowerCase();
1008
+ var formatMap = {
1009
+ 'js': 'babel', 'jsx': 'babel',
1010
+ 'json': 'json',
1011
+ 'css': 'css', 'scss': 'scss',
1012
+ 'html': 'html', 'md': 'markdown'
1013
+ };
1014
+ var parserName = formatMap[ext];
1015
+
1016
+ if (parserName && window.prettier && window.prettierPlugins) {
1017
+ setLoading(function (prev) {
1018
+ return _extends({}, prev, { format: true });
1019
+ });
1020
+ EditorStore.setStatus("Formatting with Prettier...", "info");
1021
+ window.prettier.format(activeTab.content, {
1022
+ parser: parserName,
1023
+ plugins: Object.values(window.prettierPlugins),
1024
+ tabWidth: 4,
1025
+ useTabs: false
1026
+ }).then(function (formatted) {
1027
+ var newPanes = EditorStore.getState().panes.map(function (p) {
1028
+ if (p.id === focusedPane.id) return _extends({}, p, { tabs: p.tabs.map(function (t) {
1029
+ return t.id === activeTab.id ? _extends({}, t, { content: formatted, dirty: true }) : t;
1030
+ }) });
1031
+ return p;
1032
+ });
1033
+ EditorStore.setState({ panes: newPanes });
1034
+ EditorStore.setStatus("Formatted (Unsaved)", "success");
1035
+ GitService.fetchStatus();
1036
+ })["catch"](function (err) {
1037
+ EditorStore.setStatus("Prettier Formatter failed: " + err.message, "error");
1038
+ })["finally"](function () {
1039
+ setLoading(function (prev) {
1040
+ return _extends({}, prev, { format: false });
1041
+ });
1042
+ });
1043
+ }
1044
+ };
1045
+
1046
+ var _debouncedSearch = useRef(window._.debounce(function (q) {
1047
+ if (!q.trim()) {
1048
+ EditorStore.setState({ searchResults: [] });
1049
+ return;
1050
+ }
1051
+ EditorStore.setStatus("Searching project...", "info");
1052
+ SearchService.projectSearch(q).then(function (res) {
1053
+ EditorStore.setStatus("Found " + res.length + " results", "success");
1054
+ });
1055
+ }, 400)).current;
1056
+
1057
+ var handleSearchChange = function handleSearchChange(e) {
1058
+ var val = e.target.value;
1059
+ setSearchQuery(val);
1060
+ _debouncedSearch(val);
1061
+ };
1062
+
1063
+ var execSearch = function execSearch(e) {
1064
+ e.preventDefault();
1065
+ _debouncedSearch(searchQuery);
1066
+ };
1067
+
1068
+ var toggleGitPanel = function toggleGitPanel() {
1069
+ setShowGitPanel(function (prev) {
1070
+ if (!prev) GitService.fetchInfo();
1071
+ return !prev;
1072
+ });
1073
+ };
1074
+
1075
+ var startGitPanelResize = function startGitPanelResize(e) {
1076
+ e.preventDefault();
1077
+ resizeSessionRef.current = { mode: 'gitpanel' };
1078
+ setActiveResizeMode('gitpanel');
1079
+ document.body.style.cursor = 'col-resize';
1080
+ document.body.style.userSelect = 'none';
1081
+ };
1082
+
1083
+ var handleSelectCommit = function handleSelectCommit(commit) {
1084
+ setSelectedCommit(commit);
1085
+ setCommitDetailFiles(null);
1086
+ GitService.fetchCommitDetail(commit.hash).then(function (data) {
1087
+ setCommitDetailFiles(data.files || []);
1088
+ }).catch(function () {
1089
+ setCommitDetailFiles([]);
1090
+ });
1091
+ };
1092
+
1093
+ var handleToggleSection = function handleToggleSection(sectionKey, isCollapsed) {
1094
+ setCollapsedSections(function (prev) {
1095
+ return _extends({}, prev, _defineProperty({}, sectionKey, isCollapsed));
1096
+ });
1097
+ };
1098
+
1099
+ var handleCollapseAll = function handleCollapseAll() {
1100
+ return setExpandedDirs({});
1101
+ };
1102
+
1103
+ var openContextMenu = function openContextMenu(e, node) {
1104
+ setContextMenu({ x: e.clientX, y: e.clientY, node: node });
1105
+ setSelectedTreeNode(node);
1106
+ };
1107
+
1108
+ var closeContextMenu = function closeContextMenu() {
1109
+ return setContextMenu(null);
1110
+ };
1111
+
1112
+ var handleContextMenuAction = function handleContextMenuAction(action) {
1113
+ var node = contextMenu && contextMenu.node;
1114
+ closeContextMenu();
1115
+ if (action === 'open' && node) {
1116
+ handleHardOpenFile(node.path, node.name);return;
1117
+ }
1118
+ if (action === 'newFile') {
1119
+ handleCreateFile(node);return;
1120
+ }
1121
+ if (action === 'newFolder') {
1122
+ handleCreateDir(node);return;
1123
+ }
1124
+ if (action === 'rename') {
1125
+ handleRenamePath(node);return;
1126
+ }
1127
+ if (action === 'delete') {
1128
+ handleDeletePath(node);return;
1129
+ }
1130
+ if (action === 'copyPath' && node) {
1131
+ if (navigator.clipboard) {
1132
+ navigator.clipboard.writeText(node.path)["catch"](function () {});
1133
+ }
1134
+ EditorStore.setStatus('Copied: ' + node.path, 'info');
1135
+ }
1136
+ };
1137
+
1138
+ var startPaneResize = function startPaneResize(e) {
1139
+ e.preventDefault();
1140
+ resizeSessionRef.current = { mode: 'pane' };
1141
+ setActiveResizeMode('pane');
1142
+ document.body.style.cursor = 'col-resize';
1143
+ document.body.style.userSelect = 'none';
1144
+ };
1145
+
1146
+ var startSidebarResize = function startSidebarResize(e) {
1147
+ e.preventDefault();
1148
+ resizeSessionRef.current = { mode: 'sidebar' };
1149
+ setActiveResizeMode('sidebar');
1150
+ document.body.style.cursor = 'col-resize';
1151
+ document.body.style.userSelect = 'none';
1152
+ };
1153
+
1154
+ var openFileFromGitPanel = function openFileFromGitPanel(path, name) {
1155
+ if (!path) return;
1156
+ handleSelectFile(path, name || path.split('/').pop());
1157
+ };
1158
+
1159
+ var pathMatchesNodeOrDescendant = function pathMatchesNodeOrDescendant(value, targetPath) {
1160
+ if (!value || !targetPath) return false;
1161
+ return value === targetPath || value.indexOf(targetPath + '/') === 0 || value.indexOf(targetPath + '::preview') === 0;
1162
+ };
1163
+
1164
+ var rewritePathAfterRename = function rewritePathAfterRename(value, oldPath, newPath) {
1165
+ if (!value || !oldPath || !newPath) return value;
1166
+ if (value === oldPath) return newPath;
1167
+ if (value === oldPath + '::preview') return newPath + '::preview';
1168
+ if (value.indexOf(oldPath + '/') === 0) return newPath + value.slice(oldPath.length);
1169
+ return value;
1170
+ };
1171
+
1172
+ var applyRenameToOpenTabs = function applyRenameToOpenTabs(oldPath, newPath) {
1173
+ var currentState = EditorStore.getState();
1174
+ var newPanes = currentState.panes.map(function (pane) {
1175
+ var renamedTabs = pane.tabs.map(function (tab) {
1176
+ var nextPath = rewritePathAfterRename(tab.path, oldPath, newPath);
1177
+ var nextPreviewFor = rewritePathAfterRename(tab.previewFor, oldPath, newPath);
1178
+ if (nextPath === tab.path && nextPreviewFor === tab.previewFor) return tab;
1179
+
1180
+ var defaultName = nextPath.split('/').pop();
1181
+ var previewSourceName = nextPreviewFor ? nextPreviewFor.split('/').pop() : defaultName;
1182
+ return _extends({}, tab, {
1183
+ id: nextPath,
1184
+ path: nextPath,
1185
+ name: tab.isPreview ? previewSourceName + '-preview' : defaultName,
1186
+ previewFor: nextPreviewFor
1187
+ });
1188
+ });
1189
+
1190
+ return _extends({}, pane, {
1191
+ tabs: renamedTabs,
1192
+ activeTabId: rewritePathAfterRename(pane.activeTabId, oldPath, newPath)
1193
+ });
1194
+ });
1195
+
1196
+ EditorStore.setState({
1197
+ panes: newPanes,
1198
+ activeTabId: rewritePathAfterRename(currentState.activeTabId, oldPath, newPath)
1199
+ });
1200
+ };
1201
+
1202
+ var removeDeletedPathFromOpenTabs = function removeDeletedPathFromOpenTabs(targetPath) {
1203
+ var currentState = EditorStore.getState();
1204
+ var removedTabIds = [];
1205
+
1206
+ var newPanes = currentState.panes.map(function (pane) {
1207
+ var keptTabs = pane.tabs.filter(function (tab) {
1208
+ var removeTab = pathMatchesNodeOrDescendant(tab.path, targetPath) || pathMatchesNodeOrDescendant(tab.previewFor, targetPath);
1209
+ if (removeTab) {
1210
+ removedTabIds.push(tab.id);
1211
+ }
1212
+ return !removeTab;
1213
+ });
1214
+
1215
+ var nextActiveTabId = pane.activeTabId;
1216
+ var activeStillExists = keptTabs.some(function (tab) {
1217
+ return tab.id === nextActiveTabId;
1218
+ });
1219
+ if (!activeStillExists) {
1220
+ nextActiveTabId = keptTabs.length ? keptTabs[keptTabs.length - 1].id : null;
1221
+ }
1222
+
1223
+ return _extends({}, pane, {
1224
+ tabs: keptTabs,
1225
+ activeTabId: nextActiveTabId
1226
+ });
1227
+ });
1228
+
1229
+ var nextFocusedPaneId = currentState.focusedPaneId;
1230
+ var focusedPane = newPanes.find(function (pane) {
1231
+ return pane.id === nextFocusedPaneId;
1232
+ });
1233
+ if (!focusedPane || focusedPane.tabs.length === 0) {
1234
+ var paneWithTabs = newPanes.find(function (pane) {
1235
+ return pane.tabs.length > 0;
1236
+ });
1237
+ nextFocusedPaneId = paneWithTabs ? paneWithTabs.id : 1;
1238
+ }
1239
+
1240
+ var activePane = newPanes.find(function (pane) {
1241
+ return pane.id === nextFocusedPaneId;
1242
+ });
1243
+ EditorStore.setState({
1244
+ panes: newPanes,
1245
+ focusedPaneId: nextFocusedPaneId,
1246
+ activeTabId: activePane ? activePane.activeTabId : null
1247
+ });
1248
+
1249
+ if (removedTabIds.length) {
1250
+ setMarkers(function (prev) {
1251
+ var next = _extends({}, prev);
1252
+ removedTabIds.forEach(function (tabId) {
1253
+ return delete next[tabId];
1254
+ });
1255
+ return next;
1256
+ });
1257
+ }
1258
+ };
1259
+
1260
+ var handleCreateFile = function handleCreateFile(targetNode) {
1261
+ var node = targetNode !== undefined ? targetNode : selectedTreeNode;
1262
+ var baseDir = node ? node.type === 'folder' ? node.path : parentDir(node.path) : '';
1263
+ // Ensure the target folder is expanded so the inline row is visible
1264
+ if (baseDir) setExpandedDirs(function (prev) {
1265
+ return Object.assign({}, prev, _defineProperty({}, baseDir, true));
1266
+ });
1267
+ setPendingRename(null);
1268
+ setPendingCreate({ type: 'file', parentPath: baseDir });
1269
+ };
1270
+
1271
+ var handleCreateDir = function handleCreateDir(targetNode) {
1272
+ var node = targetNode !== undefined ? targetNode : selectedTreeNode;
1273
+ var baseDir = node ? node.type === 'folder' ? node.path : parentDir(node.path) : '';
1274
+ if (baseDir) setExpandedDirs(function (prev) {
1275
+ return Object.assign({}, prev, _defineProperty({}, baseDir, true));
1276
+ });
1277
+ setPendingRename(null);
1278
+ setPendingCreate({ type: 'folder', parentPath: baseDir });
1279
+ };
1280
+
1281
+ var handleCreateConfirm = function handleCreateConfirm(name) {
1282
+ if (!pendingCreate || !name) return;
1283
+ var type = pendingCreate.type;
1284
+ var parentPath = pendingCreate.parentPath;
1285
+
1286
+ var path = normalizeRelativePath(parentPath ? parentPath + '/' + name : name);
1287
+ setPendingCreate(null);
1288
+
1289
+ if (type === 'file') {
1290
+ setLoading(function (prev) {
1291
+ return _extends({}, prev, { createFile: true });
1292
+ });
1293
+ FileService.createFile(path, '').then(function (res) {
1294
+ var createdPath = res && res.path || path;
1295
+ var createdName = createdPath.split('/').pop();
1296
+ setSelectedTreeNode({ path: createdPath, name: createdName, type: 'file' });
1297
+ EditorStore.setStatus('Created file: ' + createdName, 'success');
1298
+ return refreshProjectTree().then(function () {
1299
+ handleSelectFile(createdPath, createdName);
1300
+ GitService.fetchStatus();
1301
+ });
1302
+ })["catch"](function (err) {
1303
+ var message = err && err.response && err.response.data && err.response.data.error || err.message;
1304
+ EditorStore.setStatus('Create file failed: ' + message, 'error');
1305
+ })["finally"](function () {
1306
+ return setLoading(function (prev) {
1307
+ return _extends({}, prev, { createFile: false });
1308
+ });
1309
+ });
1310
+ } else {
1311
+ setLoading(function (prev) {
1312
+ return _extends({}, prev, { createDir: true });
1313
+ });
1314
+ FileService.createDir(path).then(function (res) {
1315
+ var createdPath = res && res.path || path;
1316
+ setSelectedTreeNode({ path: createdPath, name: createdPath.split('/').pop(), type: 'folder' });
1317
+ EditorStore.setStatus('Created folder: ' + createdPath, 'success');
1318
+ return refreshProjectTree().then(function () {
1319
+ return GitService.fetchStatus();
1320
+ });
1321
+ })["catch"](function (err) {
1322
+ var message = err && err.response && err.response.data && err.response.data.error || err.message;
1323
+ EditorStore.setStatus('Create folder failed: ' + message, 'error');
1324
+ })["finally"](function () {
1325
+ return setLoading(function (prev) {
1326
+ return _extends({}, prev, { createDir: false });
1327
+ });
1328
+ });
1329
+ }
1330
+ };
1331
+
1332
+ var handleCreateCancel = function handleCreateCancel() {
1333
+ return setPendingCreate(null);
1334
+ };
1335
+
1336
+ var handleRenamePath = function handleRenamePath(targetNode) {
1337
+ var node = targetNode !== undefined ? targetNode : selectedTreeNode;
1338
+ if (!node || !node.path) {
1339
+ EditorStore.setStatus('Select a file or folder to rename first.', 'warning');
1340
+ return;
1341
+ }
1342
+
1343
+ var itemPath = node.path;
1344
+
1345
+ // Expand all ancestor folders so the rename inline input is always visible
1346
+ var parts = itemPath.split('/');
1347
+ if (parts.length > 1) {
1348
+ var ancestors = {};
1349
+ for (var i = 1; i < parts.length; i++) {
1350
+ ancestors[parts.slice(0, i).join('/')] = true;
1351
+ }
1352
+ setExpandedDirs(function (prev) {
1353
+ return Object.assign({}, prev, ancestors);
1354
+ });
1355
+ }
1356
+
1357
+ setPendingCreate(null);
1358
+ setPendingRename({
1359
+ path: itemPath,
1360
+ parentPath: parentDir(itemPath),
1361
+ type: node.type,
1362
+ currentName: node.name || itemPath.split('/').pop()
1363
+ });
1364
+ };
1365
+
1366
+ var handleRenameConfirm = function handleRenameConfirm(name, renameTarget) {
1367
+ var target = renameTarget || pendingRename;
1368
+ if (!target || !name) return;
1369
+
1370
+ var oldPath = target.path;
1371
+ var currentName = target.currentName || oldPath.split('/').pop();
1372
+ var nextName = name.trim();
1373
+ setPendingRename(null);
1374
+
1375
+ if (!nextName || nextName === currentName) return;
1376
+
1377
+ var nextPath = normalizeRelativePath(parentDir(oldPath) ? parentDir(oldPath) + '/' + nextName : nextName);
1378
+ if (!nextPath || nextPath === oldPath) return;
1379
+
1380
+ setLoading(function (prev) {
1381
+ return _extends({}, prev, { renamePath: true });
1382
+ });
1383
+ FileService.renamePath(oldPath, nextPath).then(function (res) {
1384
+ var renamedPath = res && res.path || nextPath;
1385
+ applyRenameToOpenTabs(oldPath, renamedPath);
1386
+ setSelectedTreeNode(function (prev) {
1387
+ return prev ? _extends({}, prev, { path: renamedPath, name: renamedPath.split('/').pop() }) : prev;
1388
+ });
1389
+ EditorStore.setStatus('Renamed to: ' + renamedPath, 'success');
1390
+ return refreshProjectTree().then(function () {
1391
+ GitService.fetchStatus();
1392
+ });
1393
+ })["catch"](function (err) {
1394
+ var message = err && err.response && err.response.data && err.response.data.error || err.message;
1395
+ EditorStore.setStatus('Rename failed: ' + message, 'error');
1396
+ })["finally"](function () {
1397
+ setLoading(function (prev) {
1398
+ return _extends({}, prev, { renamePath: false });
1399
+ });
1400
+ });
1401
+ };
1402
+
1403
+ var handleRenameCancel = function handleRenameCancel() {
1404
+ return setPendingRename(null);
1405
+ };
1406
+
1407
+ var handleDeletePath = function handleDeletePath(targetNode) {
1408
+ var node = targetNode !== undefined ? targetNode : selectedTreeNode;
1409
+ if (!node || !node.path) {
1410
+ EditorStore.setStatus('Select a file or folder to delete first.', 'warning');
1411
+ return;
1412
+ }
1413
+
1414
+ var targetPath = node.path;
1415
+ var confirmed = window.confirm('Delete ' + targetPath + '? This cannot be undone.');
1416
+ if (!confirmed) return;
1417
+
1418
+ setLoading(function (prev) {
1419
+ return _extends({}, prev, { deletePath: true });
1420
+ });
1421
+ FileService.deletePath(targetPath).then(function () {
1422
+ removeDeletedPathFromOpenTabs(targetPath);
1423
+ setSelectedTreeNode(null);
1424
+ EditorStore.setStatus('Deleted: ' + targetPath, 'success');
1425
+ return refreshProjectTree().then(function () {
1426
+ GitService.fetchStatus();
1427
+ });
1428
+ })["catch"](function (err) {
1429
+ var message = err && err.response && err.response.data && err.response.data.error || err.message;
1430
+ EditorStore.setStatus('Delete failed: ' + message, 'error');
1431
+ })["finally"](function () {
1432
+ setLoading(function (prev) {
1433
+ return _extends({}, prev, { deletePath: false });
1434
+ });
1435
+ });
1436
+ };
1437
+
1438
+ var projectSectionTitle = deriveProjectRootName().toUpperCase();
1439
+ var selectedTreePath = selectedTreeNode ? selectedTreeNode.path : null;
1440
+ var isRuby = activeTab && isRubyPath(activeTab.path);
1441
+ var isHaml = activeTab && activeTab.path.endsWith('.haml');
1442
+ var supportedPrettierExts = ['js', 'jsx', 'json', 'css', 'scss', 'html', 'md'];
1443
+ var isPrettierable = activeTab && supportedPrettierExts.includes(activeTab.path.split('.').pop().toLowerCase());
1444
+ var canLintAndFormat = activeTab && (isPrettierable || isRuby && rubocopAvailable || isHaml && hamlLintAvailable);
1445
+ var hasGitBranch = !!(state.gitBranch && state.gitBranch.trim());
1446
+
1447
+ var renderTabBar = function renderTabBar(paneId, tabs, activeId) {
1448
+ return React.createElement(TabBar, {
1449
+ tabs: tabs,
1450
+ activeId: activeId,
1451
+ onSelect: function (id) {
1452
+ return TabManager.switchTab(paneId, id);
1453
+ },
1454
+ onClose: function (id) {
1455
+ return requestCloseTab(paneId, id);
1456
+ },
1457
+ onTabDragStart: function (id) {
1458
+ return handleTabDragStart(paneId, id);
1459
+ },
1460
+ onTabDragEnd: clearDragState,
1461
+ onHardenTab: function (tabId) {
1462
+ return TabManager.hardenTab(paneId, tabId);
1463
+ },
1464
+ onShowHistory: function (path) {
1465
+ setHistoryPanelPath(path);
1466
+ }
1467
+ });
1468
+ };
1469
+
1470
+ return React.createElement(
1471
+ "div",
1472
+ { className: "ide-shell" },
1473
+ React.createElement(
1474
+ "div",
1475
+ { className: "ide-titlebar" },
1476
+ React.createElement("i", { className: "fas fa-layer-group ide-titlebar-icon" }),
1477
+ React.createElement(
1478
+ "div",
1479
+ { className: "ide-titlebar-title" },
1480
+ "Mini Browser Editor — ",
1481
+ window.location.host
1482
+ ),
1483
+ React.createElement(
1484
+ "div",
1485
+ { style: { marginLeft: "auto", display: "flex", gap: "4px", height: "100%", alignItems: "center" } },
1486
+ React.createElement(
1487
+ "button",
1488
+ { className: "statusbar-btn", onClick: function () {
1489
+ return activeTab && handleSave(focusedPane.id, activeTab);
1490
+ }, disabled: loading.save || !activeTab || !activeTab.dirty },
1491
+ React.createElement("i", { className: loading.save ? "fas fa-spinner fa-spin" : "fas fa-save" }),
1492
+ " Save ",
1493
+ activeTab && activeTab.dirty ? "●" : ""
1494
+ ),
1495
+ React.createElement(
1496
+ "button",
1497
+ { className: "statusbar-btn", onClick: handleSaveAll, disabled: loading.saveAll || !state.panes.flatMap(function (p) {
1498
+ return p.tabs;
1499
+ }).some(function (t) {
1500
+ return t.dirty;
1501
+ }) },
1502
+ React.createElement(
1503
+ "i",
1504
+ { className: loading.saveAll ? "fas fa-spinner fa-spin" : "fas fa-save", style: loading.saveAll ? {} : { position: 'relative' } },
1505
+ !loading.saveAll && React.createElement("i", { className: "fas fa-save", style: { position: 'absolute', top: '-2px', left: '3px', fontSize: '9px', opacity: 0.8 } })
1506
+ ),
1507
+ " Save All"
1508
+ ),
1509
+ React.createElement("div", { className: "statusbar-sep" }),
1510
+ React.createElement(
1511
+ "button",
1512
+ { className: "statusbar-btn", onClick: handleFormat, disabled: loading.format || !canLintAndFormat },
1513
+ React.createElement("i", { className: loading.format ? "fas fa-spinner fa-spin" : "fas fa-magic" }),
1514
+ " Format"
1515
+ ),
1516
+ hasGitBranch && React.createElement(
1517
+ React.Fragment,
1518
+ null,
1519
+ React.createElement("div", { className: "statusbar-sep" }),
1520
+ React.createElement(
1521
+ "button",
1522
+ { type: "button", className: "statusbar-btn titlebar-git-btn", onClick: toggleGitPanel },
1523
+ React.createElement("i", { className: "fas fa-code-branch" }),
1524
+ " Git"
1525
+ )
1526
+ ),
1527
+ React.createElement("div", { className: "statusbar-sep" }),
1528
+ React.createElement(
1529
+ "button",
1530
+ { type: "button", className: "statusbar-btn", onClick: function () { return setShowHelp(true); }, title: "Keyboard shortcuts & help" },
1531
+ React.createElement("i", { className: "fas fa-keyboard" }),
1532
+ " Help"
1533
+ )
1534
+ )
1535
+ ),
1536
+ showHelp && React.createElement(ShortcutHelp, { onClose: function () { return setShowHelp(false); } }),
1537
+ React.createElement(
1538
+ "div",
1539
+ { className: "ide-body", id: "ide-body-container" },
1540
+ React.createElement(
1541
+ "div",
1542
+ { className: "ide-sidebar", style: { width: sidebarWidth + "px" } },
1543
+ React.createElement(
1544
+ "div",
1545
+ { className: "ide-sidebar-tabs" },
1546
+ React.createElement(
1547
+ "button",
1548
+ { type: "button", className: "ide-sidebar-tab " + (activeSidebarTab === 'explorer' ? 'active' : ''), onClick: function () {
1549
+ return setActiveSidebarTab('explorer');
1550
+ } },
1551
+ "EXPLORER"
1552
+ ),
1553
+ React.createElement(
1554
+ "button",
1555
+ { type: "button", className: "ide-sidebar-tab " + (activeSidebarTab === 'search' ? 'active' : ''), onClick: function () {
1556
+ return setActiveSidebarTab('search');
1557
+ } },
1558
+ "SEARCH"
1559
+ )
1560
+ ),
1561
+ activeSidebarTab === 'explorer' && React.createElement(
1562
+ "div",
1563
+ { className: "ide-sidebar-content" },
1564
+ state.panes.flatMap(function (p) {
1565
+ return p.tabs;
1566
+ }).length > 0 && React.createElement(
1567
+ CollapsibleSection,
1568
+ {
1569
+ title: "OPEN EDITORS",
1570
+ isCollapsed: collapsedSections.openEditors,
1571
+ onToggle: function (isCollapsed) {
1572
+ return handleToggleSection('openEditors', isCollapsed);
1573
+ },
1574
+ actions: React.createElement(
1575
+ SectionActionGroup,
1576
+ { ariaLabel: "Open editor actions" },
1577
+ React.createElement(SidebarActionButton, {
1578
+ title: "Close all editors",
1579
+ ariaLabel: "Close all open editors",
1580
+ iconClass: "far fa-window-close",
1581
+ onClick: handleCloseAllEditors
1582
+ })
1583
+ )
1584
+ },
1585
+ React.createElement(
1586
+ "div",
1587
+ { style: { marginBottom: "12px" } },
1588
+ state.panes.map(function (pane) {
1589
+ if (pane.tabs.length === 0) return null;
1590
+ var isPane2 = pane.id === 2;
1591
+ return React.createElement(
1592
+ "div",
1593
+ {
1594
+ key: pane.id,
1595
+ className: "open-editors-group",
1596
+ style: { marginBottom: pane.id === 1 && state.panes[1].tabs.length > 0 ? "10px" : "0" }
1597
+ },
1598
+ React.createElement(
1599
+ "div",
1600
+ { className: "ide-sidebar-header open-editors-group-header" },
1601
+ React.createElement(
1602
+ "span",
1603
+ { className: "open-editors-group-title" },
1604
+ "GROUP ",
1605
+ pane.id
1606
+ ),
1607
+ React.createElement(
1608
+ SectionActionGroup,
1609
+ { ariaLabel: "Group " + pane.id + " actions", className: "collapsible-actions open-editors-group-actions" },
1610
+ React.createElement(SidebarActionButton, {
1611
+ title: "Close all editors in Group " + pane.id,
1612
+ ariaLabel: "Close all editors in Group " + pane.id,
1613
+ iconClass: "far fa-window-close",
1614
+ onClick: function (e) {
1615
+ e.stopPropagation();handleCloseEditorsInGroup(pane.id);
1616
+ }
1617
+ })
1618
+ )
1619
+ ),
1620
+ React.createElement(
1621
+ "div",
1622
+ { className: "file-tree" },
1623
+ pane.tabs.map(function (tab) {
1624
+ return React.createElement(
1625
+ "div",
1626
+ {
1627
+ key: tab.id,
1628
+ className: "tree-item " + (pane.activeTabId === tab.id && state.focusedPaneId === pane.id ? "active" : ""),
1629
+ onClick: function () {
1630
+ TabManager.focusPane(pane.id);TabManager.switchTab(pane.id, tab.id);
1631
+ }
1632
+ },
1633
+ React.createElement("i", { className: "tree-item-icon " + (window.getFileIcon ? window.getFileIcon(tab.name) : 'far fa-file-code') + " tree-file-icon" }),
1634
+ React.createElement(
1635
+ "div",
1636
+ { className: "tree-item-name", style: { display: 'flex', alignItems: 'center' } },
1637
+ React.createElement(
1638
+ "span",
1639
+ { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
1640
+ tab.name
1641
+ ),
1642
+ tab.dirty && React.createElement("i", { className: "fas fa-circle", style: { fontSize: '5px', color: '#e3d286', marginLeft: '6px', marginTop: '1px' } })
1643
+ ),
1644
+ React.createElement(
1645
+ "div",
1646
+ { className: "tab-actions", style: { display: 'flex', position: 'absolute', right: '4px', top: 0, height: '100%', alignItems: 'center' } },
1647
+ React.createElement(
1648
+ "div",
1649
+ { className: "tab-split", onClick: function (e) {
1650
+ e.stopPropagation();TabManager.moveTabToPane(pane.id, pane.id === 1 ? 2 : 1, tab.id);
1651
+ }, style: { padding: '0 4px', cursor: 'pointer', opacity: 0.6 }, title: "Move to Group " + (pane.id === 1 ? 2 : 1) },
1652
+ React.createElement("i", { className: isPane2 ? "fas fa-chevron-left" : "fas fa-chevron-right" })
1653
+ ),
1654
+ React.createElement(
1655
+ "div",
1656
+ { className: "tab-close", onClick: function (e) {
1657
+ e.stopPropagation();requestCloseTab(pane.id, tab.id);
1658
+ }, style: { padding: '0 4px', cursor: 'pointer', opacity: 0.6 } },
1659
+ React.createElement("i", { className: "fas fa-times" })
1660
+ )
1661
+ )
1662
+ );
1663
+ })
1664
+ )
1665
+ );
1666
+ })
1667
+ )
1668
+ ),
1669
+ React.createElement(
1670
+ CollapsibleSection,
1671
+ {
1672
+ title: projectSectionTitle,
1673
+ isCollapsed: collapsedSections.projects,
1674
+ onToggle: function (isCollapsed) {
1675
+ return handleToggleSection('projects', isCollapsed);
1676
+ },
1677
+ actions: React.createElement(
1678
+ SectionActionGroup,
1679
+ { ariaLabel: "Project actions" },
1680
+ React.createElement(SidebarActionButton, {
1681
+ title: "Collapse all folders",
1682
+ iconClass: "fas fa-compress-alt",
1683
+ onClick: handleCollapseAll
1684
+ }),
1685
+ React.createElement(SidebarActionButton, {
1686
+ title: "New file",
1687
+ iconClass: loading.createFile ? 'fas fa-spinner fa-spin' : 'far fa-file',
1688
+ onClick: function () {
1689
+ return handleCreateFile();
1690
+ },
1691
+ disabled: !!loading.createFile
1692
+ }),
1693
+ React.createElement(SidebarActionButton, {
1694
+ title: "New folder",
1695
+ iconClass: loading.createDir ? 'fas fa-spinner fa-spin' : 'far fa-folder',
1696
+ onClick: function () {
1697
+ return handleCreateDir();
1698
+ },
1699
+ disabled: !!loading.createDir
1700
+ }),
1701
+ React.createElement(SidebarActionButton, {
1702
+ title: "Rename selected",
1703
+ iconClass: loading.renamePath ? 'fas fa-spinner fa-spin' : 'fas fa-pen',
1704
+ onClick: function () {
1705
+ return handleRenamePath();
1706
+ },
1707
+ disabled: !!loading.renamePath || !selectedTreePath
1708
+ }),
1709
+ React.createElement(SidebarActionButton, {
1710
+ title: "Delete selected",
1711
+ iconClass: loading.deletePath ? 'fas fa-spinner fa-spin' : 'far fa-trash-alt',
1712
+ onClick: function () {
1713
+ return handleDeletePath();
1714
+ },
1715
+ disabled: !!loading.deletePath || !selectedTreePath,
1716
+ danger: true
1717
+ })
1718
+ )
1719
+ },
1720
+ React.createElement(FileTree, {
1721
+ items: treeData,
1722
+ onSelect: handleSoftOpenFile,
1723
+ activePath: activeTab && activeTab.path,
1724
+ selectedPath: selectedTreePath,
1725
+ onNodeSelect: setSelectedTreeNode,
1726
+ gitFiles: state.gitFiles,
1727
+ expandedDirs: expandedDirs,
1728
+ onExpandedDirsChange: setExpandedDirs,
1729
+ onFileDoubleClick: handleHardOpenFile,
1730
+ onContextMenu: openContextMenu,
1731
+ pendingCreate: pendingCreate,
1732
+ onCreateConfirm: handleCreateConfirm,
1733
+ onCreateCancel: handleCreateCancel,
1734
+ pendingRename: pendingRename,
1735
+ onRenameConfirm: handleRenameConfirm,
1736
+ onRenameCancel: handleRenameCancel
1737
+ })
1738
+ )
1739
+ ),
1740
+ activeSidebarTab === 'search' && React.createElement(
1741
+ "form",
1742
+ { className: "search-panel", onSubmit: execSearch },
1743
+ React.createElement(
1744
+ "div",
1745
+ { className: "search-input-wrap" },
1746
+ React.createElement("input", {
1747
+ className: "search-input",
1748
+ placeholder: "Find in files...",
1749
+ value: searchQuery,
1750
+ onChange: handleSearchChange
1751
+ }),
1752
+ React.createElement(
1753
+ "button",
1754
+ { type: "submit", className: "search-btn" },
1755
+ React.createElement("i", { className: "fas fa-search" })
1756
+ )
1757
+ ),
1758
+ React.createElement(
1759
+ "div",
1760
+ { className: "search-results" },
1761
+ searchQuery && state.searchResults.length > 0 && React.createElement(
1762
+ "div",
1763
+ { className: "search-results-meta" },
1764
+ state.searchResults.length,
1765
+ " result" + (state.searchResults.length !== 1 ? "s" : ""),
1766
+ state.searchResults.length >= 30 && React.createElement(
1767
+ "span",
1768
+ { className: "search-results-capped" },
1769
+ " — refine query to see more"
1770
+ )
1771
+ ),
1772
+ searchQuery && state.searchResults.length === 0 && React.createElement(
1773
+ "div",
1774
+ { className: "search-results-empty" },
1775
+ "No results"
1776
+ ),
1777
+ state.searchResults.map(function (res, i) {
1778
+ return React.createElement(
1779
+ "div",
1780
+ { key: i, className: "search-result-item", onClick: function () {
1781
+ return handleSelectFile(res.file, res.file.split('/').pop(), res.line);
1782
+ } },
1783
+ React.createElement(
1784
+ "div",
1785
+ { className: "search-result-file" },
1786
+ res.file,
1787
+ " ",
1788
+ React.createElement(
1789
+ "span",
1790
+ { className: "search-result-line-num" },
1791
+ ":",
1792
+ res.line
1793
+ )
1794
+ ),
1795
+ React.createElement(
1796
+ "div",
1797
+ { className: "search-result-text" },
1798
+ res.text
1799
+ )
1800
+ );
1801
+ })
1802
+ )
1803
+ )
1804
+ ),
1805
+ React.createElement("div", {
1806
+ className: "panel-divider sidebar-divider " + (activeResizeMode === 'sidebar' ? 'active' : ''),
1807
+ onMouseDown: startSidebarResize,
1808
+ role: "separator",
1809
+ "aria-orientation": "vertical",
1810
+ "aria-label": "Resize explorer panel"
1811
+ }),
1812
+ React.createElement(
1813
+ "div",
1814
+ {
1815
+ id: "ide-main-split-container",
1816
+ className: "ide-main",
1817
+ style: { display: 'flex', flexDirection: 'row', width: '100%', height: '100%', cursor: activeResizeMode === 'pane' ? 'col-resize' : 'default', userSelect: activeResizeMode ? 'none' : 'auto' },
1818
+ onDragOverCapture: function (e) {
1819
+ if (!draggedTab) return;
1820
+ e.preventDefault();
1821
+
1822
+ var rect = e.currentTarget.getBoundingClientRect();
1823
+ var splitAtX = rect.left + rect.width * (pane1Width / 100);
1824
+ var hoverPaneId = e.clientX >= splitAtX ? 2 : 1;
1825
+ var nextDropPane = hoverPaneId === draggedTab.sourcePaneId ? null : hoverPaneId;
1826
+
1827
+ e.dataTransfer.dropEffect = nextDropPane ? 'move' : 'none';
1828
+ if (dragOverPaneId !== nextDropPane) setDragOverPaneId(nextDropPane);
1829
+ },
1830
+ onDropCapture: function (e) {
1831
+ if (!draggedTab) return;
1832
+ e.preventDefault();
1833
+
1834
+ var rect = e.currentTarget.getBoundingClientRect();
1835
+ var splitAtX = rect.left + rect.width * (pane1Width / 100);
1836
+ var targetPaneId = e.clientX >= splitAtX ? 2 : 1;
1837
+
1838
+ if (targetPaneId !== draggedTab.sourcePaneId) {
1839
+ moveDraggedTabToPane(targetPaneId);
1840
+ } else {
1841
+ clearDragState();
1842
+ }
1843
+ }
1844
+ },
1845
+ state.panes.map(function (pane, idx) {
1846
+ if (pane.id === 2 && pane.tabs.length === 0 && !draggedTab) return null; // Show pane 2 while dragging to allow drop-to-split
1847
+
1848
+ // Dynamic width distribution
1849
+ var isSplit = state.panes[1].tabs.length > 0 || !!draggedTab;
1850
+ var flexBasis = '100%';
1851
+ if (isSplit) flexBasis = pane.id === 1 ? pane1Width + "%" : 100 - pane1Width + "%";
1852
+
1853
+ var isFocused = state.focusedPaneId === pane.id;
1854
+ var pActiveTab = pane.tabs.find(function (t) {
1855
+ return t.id === pane.activeTabId;
1856
+ });
1857
+ var canAcceptDrop = !!draggedTab && draggedTab.sourcePaneId !== pane.id;
1858
+ var isDropTarget = canAcceptDrop && dragOverPaneId === pane.id;
1859
+
1860
+ var content;
1861
+ if (pane.tabs.length === 0) {
1862
+ content = React.createElement(
1863
+ 'div',
1864
+ { className: 'ide-empty-pane' },
1865
+ React.createElement('i', { className: 'fas fa-code ide-empty-icon' }),
1866
+ React.createElement(
1867
+ 'p',
1868
+ null,
1869
+ 'Ctrl+P to open files'
1870
+ )
1871
+ );
1872
+ } else if (pActiveTab) {
1873
+ if (pActiveTab.isCommitGraph) {
1874
+ content = React.createElement(window.CommitGraph || CommitGraph, {
1875
+ commits: pActiveTab.commits || [],
1876
+ onSelectCommit: handleSelectCommit
1877
+ });
1878
+ } else if (pActiveTab.isDiff) {
1879
+ content = React.createElement(window.DiffViewer || DiffViewer, {
1880
+ key: pActiveTab.id,
1881
+ path: pActiveTab.path,
1882
+ original: pActiveTab.diffOriginal || '',
1883
+ modified: pActiveTab.diffModified || '',
1884
+ isDark: true,
1885
+ onClose: function() { requestCloseTab(pane.id, pActiveTab.id); }
1886
+ });
1887
+ } else {
1888
+ content = React.createElement(window.EditorPanel || EditorPanel, {
1889
+ key: pActiveTab.id,
1890
+ tab: pActiveTab,
1891
+ paneId: pane.id,
1892
+ markers: markers[pActiveTab.id] || [],
1893
+ gitAvailable: gitAvailable,
1894
+ onContentChange: function onContentChange(val) {
1895
+ var st = EditorStore.getState();
1896
+ var cp = st.panes.find(function(p) { return p.id === pane.id; });
1897
+ var ct = cp && cp.tabs.find(function(t) { return t.id === pActiveTab.id; });
1898
+ var cleanNorm = ((ct && ct.cleanContent) || '').replace(/\r\n/g, '\n');
1899
+ var valNorm = val.replace(/\r\n/g, '\n');
1900
+ if (valNorm === cleanNorm) {
1901
+ TabManager.markClean(pane.id, pActiveTab.id, val);
1902
+ } else {
1903
+ TabManager.markDirty(pane.id, pActiveTab.id, val);
1904
+ }
1905
+ }
1906
+ });
1907
+ }
1908
+ }
1909
+
1910
+ return React.createElement(
1911
+ React.Fragment,
1912
+ { key: pane.id },
1913
+ idx === 1 && isSplit && React.createElement("div", {
1914
+ className: "panel-divider pane-divider " + (activeResizeMode === 'pane' ? 'active' : ''),
1915
+ onMouseDown: startPaneResize
1916
+ }),
1917
+ React.createElement(
1918
+ "div",
1919
+ {
1920
+ className: "ide-pane " + (isFocused ? 'focused' : '') + " " + (isDropTarget ? 'drop-target' : ''),
1921
+ style: { flexBasis: flexBasis, flexShrink: 0, flexGrow: 0, display: 'flex', flexDirection: 'column', minWidth: 0 },
1922
+ onClickCapture: function () {
1923
+ return TabManager.focusPane(pane.id);
1924
+ },
1925
+ onDragOver: function (e) {
1926
+ if (!canAcceptDrop) return;
1927
+ e.preventDefault();
1928
+ e.dataTransfer.dropEffect = 'move';
1929
+ if (dragOverPaneId !== pane.id) setDragOverPaneId(pane.id);
1930
+ },
1931
+ onDragEnter: function (e) {
1932
+ if (!canAcceptDrop) return;
1933
+ e.preventDefault();
1934
+ if (dragOverPaneId !== pane.id) setDragOverPaneId(pane.id);
1935
+ },
1936
+ onDragLeave: function (e) {
1937
+ if (dragOverPaneId !== pane.id) return;
1938
+ if (!e.currentTarget.contains(e.relatedTarget)) {
1939
+ setDragOverPaneId(null);
1940
+ }
1941
+ },
1942
+ onDrop: function (e) {
1943
+ if (!canAcceptDrop) return;
1944
+ e.preventDefault();
1945
+ moveDraggedTabToPane(pane.id);
1946
+ }
1947
+ },
1948
+ pane.tabs.length > 0 ? React.createElement(
1949
+ React.Fragment,
1950
+ null,
1951
+ renderTabBar(pane.id, pane.tabs, pane.activeTabId),
1952
+ React.createElement(
1953
+ "div",
1954
+ { style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', visibility: activeResizeMode === 'pane' ? 'hidden' : 'visible' } },
1955
+ content
1956
+ )
1957
+ ) : React.createElement(
1958
+ "div",
1959
+ { className: "tab-welcome" },
1960
+ canAcceptDrop ? React.createElement(
1961
+ React.Fragment,
1962
+ null,
1963
+ React.createElement("i", { className: "fas fa-columns" }),
1964
+ React.createElement(
1965
+ "h2",
1966
+ null,
1967
+ "Drop Tab Here"
1968
+ ),
1969
+ React.createElement(
1970
+ "p",
1971
+ null,
1972
+ "Release to move this file into Group ",
1973
+ pane.id,
1974
+ "."
1975
+ )
1976
+ ) : pane.id === 1 ? React.createElement(
1977
+ React.Fragment,
1978
+ null,
1979
+ React.createElement("i", { className: "fas fa-code" }),
1980
+ React.createElement("h2", null, "Mini Browser Editor"),
1981
+ React.createElement("p", { className: "welcome-intro" }, "Open a file from the explorer to start editing."),
1982
+ React.createElement(
1983
+ "div",
1984
+ { className: "welcome-shortcuts" },
1985
+ React.createElement(
1986
+ "div",
1987
+ { className: "welcome-section" },
1988
+ React.createElement("h3", null, "Keyboard shortcuts"),
1989
+ React.createElement(
1990
+ "table",
1991
+ { className: "shortcut-table" },
1992
+ React.createElement(
1993
+ "tbody",
1994
+ null,
1995
+ React.createElement("tr", null,
1996
+ React.createElement("td", null, React.createElement("kbd", null, "Ctrl+P")),
1997
+ React.createElement("td", null, "Quick-open any file by name")
1998
+ ),
1999
+ React.createElement("tr", null,
2000
+ React.createElement("td", null, React.createElement("kbd", null, "Ctrl+S")),
2001
+ React.createElement("td", null, "Save the active file")
2002
+ ),
2003
+ React.createElement("tr", null,
2004
+ React.createElement("td", null, React.createElement("kbd", null, "Ctrl+Z\u00a0/\u00a0Ctrl+Y")),
2005
+ React.createElement("td", null, "Undo / Redo")
2006
+ )
2007
+ )
2008
+ )
2009
+ ),
2010
+ React.createElement(
2011
+ "div",
2012
+ { className: "welcome-section" },
2013
+ React.createElement("h3", null, "Sidebar panels"),
2014
+ React.createElement(
2015
+ "ul",
2016
+ { className: "welcome-tips" },
2017
+ React.createElement("li", null, React.createElement("i", { className: "fas fa-folder-open" }), "\u00a0Explorer \u2014 browse and manage project files"),
2018
+ React.createElement("li", null, React.createElement("i", { className: "fas fa-search" }), "\u00a0Search \u2014 full-text search across all files"),
2019
+ React.createElement("li", null, React.createElement("i", { className: "fas fa-code-branch" }), "\u00a0Git panel \u2014 branch status and changed files (top-right icon)")
2020
+ )
2021
+ ),
2022
+ React.createElement(
2023
+ "div",
2024
+ { className: "welcome-section" },
2025
+ React.createElement("h3", null, "Editor tips"),
2026
+ React.createElement(
2027
+ "ul",
2028
+ { className: "welcome-tips" },
2029
+ React.createElement("li", null, "Drag any tab to the right half to open a split pane"),
2030
+ React.createElement("li", null, "Right-click a file in the explorer to rename or delete it"),
2031
+ React.createElement("li", null, "Ruby files auto-lint with RuboCop when installed"),
2032
+ React.createElement("li", null, "JS, CSS, HTML and Markdown auto-format with Prettier")
2033
+ )
2034
+ )
2035
+ )
2036
+ ) : null
2037
+ )
2038
+ )
2039
+ );
2040
+ })
2041
+ ),
2042
+
2043
+ // Right-side Git panel (children of ide-body, alongside sidebar and ide-main)
2044
+ showGitPanel && React.createElement("div", {
2045
+ className: "panel-divider gitpanel-divider " + (activeResizeMode === 'gitpanel' ? 'active' : ''),
2046
+ onMouseDown: startGitPanelResize,
2047
+ role: "separator",
2048
+ "aria-orientation": "vertical",
2049
+ "aria-label": "Resize git panel"
2050
+ }),
2051
+ showGitPanel && React.createElement(
2052
+ "div",
2053
+ { className: "ide-git-right-panel", style: { width: gitPanelWidth + "px" } },
2054
+ React.createElement(window.GitPanel || GitPanel, {
2055
+ gitInfo: state.gitInfo,
2056
+ error: state.gitInfoError,
2057
+ onRefresh: function () { return GitService.fetchInfo(); },
2058
+ onClose: function () { return setShowGitPanel(false); },
2059
+ onOpenFile: openFileFromGitPanel,
2060
+ onOpenDiff: TabManager.openDiffTab,
2061
+ onOpenAllChanges: function(scope, label) { TabManager.openCombinedDiffTab(scope, label); },
2062
+ onSelectCommit: handleSelectCommit
2063
+ })
2064
+ )
2065
+ ),
2066
+ React.createElement(
2067
+ "div",
2068
+ { className: "ide-statusbar" },
2069
+ hasGitBranch && React.createElement(
2070
+ "div",
2071
+ { className: "statusbar-branch" },
2072
+ React.createElement("i", { className: "fas fa-code-branch" }),
2073
+ " ",
2074
+ state.gitBranch,
2075
+ state.gitInfo && state.gitInfo.ahead > 0 && React.createElement(
2076
+ "span",
2077
+ { className: "statusbar-aheadbehind", title: state.gitInfo.ahead + " commit(s) ahead of upstream" },
2078
+ " \u2191",
2079
+ state.gitInfo.ahead
2080
+ ),
2081
+ state.gitInfo && state.gitInfo.behind > 0 && React.createElement(
2082
+ "span",
2083
+ { className: "statusbar-aheadbehind statusbar-behind", title: state.gitInfo.behind + " commit(s) behind upstream" },
2084
+ " \u2193",
2085
+ state.gitInfo.behind
2086
+ )
2087
+ ),
2088
+ !serverOnline && React.createElement(
2089
+ "div",
2090
+ { className: "statusbar-offline" },
2091
+ React.createElement("i", { className: "fas fa-exclamation-triangle" }),
2092
+ " Server offline"
2093
+ ),
2094
+ activeFileCommit && React.createElement(
2095
+ "div",
2096
+ { className: "statusbar-file-commit", title: activeFileCommit.title + " — " + activeFileCommit.author },
2097
+ React.createElement("i", { className: "fas fa-history", style: { marginRight: "4px", opacity: 0.7 } }),
2098
+ React.createElement("span", { className: "commit-hash" }, activeFileCommit.hash.slice(0, 7)),
2099
+ " ",
2100
+ activeFileCommit.author
2101
+ ),
2102
+ React.createElement(
2103
+ "div",
2104
+ { className: "statusbar-msg " + state.statusMessage.kind },
2105
+ state.statusMessage.text
2106
+ )
2107
+ ),
2108
+
2109
+ // File History Panel overlay
2110
+ historyPanelPath && React.createElement(window.FileHistoryPanel || FileHistoryPanel, {
2111
+ path: historyPanelPath,
2112
+ onClose: function () { return setHistoryPanelPath(null); },
2113
+ onSelectCommit: function (hash, path) {
2114
+ TabManager.openDiffTab(path, path.split('/').pop(), hash + '^', hash, null);
2115
+ }
2116
+ }),
2117
+
2118
+ // Commit Detail overlay (shown when a commit row is clicked in CommitGraph)
2119
+ selectedCommit && React.createElement(
2120
+ React.Fragment,
2121
+ null,
2122
+ React.createElement("div", {
2123
+ style: { position: 'fixed', inset: 0, zIndex: 9000, background: 'rgba(0,0,0,0.45)' },
2124
+ onClick: function() { setSelectedCommit(null); setCommitDetailFiles(null); }
2125
+ }),
2126
+ React.createElement(
2127
+ "div",
2128
+ { className: "ide-commit-detail-panel" },
2129
+ React.createElement(
2130
+ "div",
2131
+ { className: "ide-commit-detail-header" },
2132
+ React.createElement(
2133
+ "div",
2134
+ null,
2135
+ React.createElement("div", { className: "ide-commit-detail-title" }, selectedCommit.title),
2136
+ React.createElement(
2137
+ "div",
2138
+ { className: "ide-commit-detail-meta" },
2139
+ React.createElement("span", { className: "commit-hash" }, selectedCommit.hash.slice(0, 7)),
2140
+ " \xB7 ",
2141
+ selectedCommit.author,
2142
+ " \xB7 ",
2143
+ selectedCommit.date ? new Date(selectedCommit.date).toLocaleString() : ""
2144
+ )
2145
+ ),
2146
+ React.createElement(
2147
+ "button",
2148
+ { className: "git-header-btn", onClick: function() { setSelectedCommit(null); setCommitDetailFiles(null); }, title: "Close" },
2149
+ React.createElement("i", { className: "fas fa-times" })
2150
+ )
2151
+ ),
2152
+ commitDetailFiles === null
2153
+ ? React.createElement("div", { className: "git-empty" }, React.createElement("i", { className: "fas fa-spinner fa-spin" }), " Loading...")
2154
+ : commitDetailFiles.length === 0
2155
+ ? React.createElement("div", { className: "git-empty" }, "No file changes found.")
2156
+ : React.createElement(
2157
+ "div",
2158
+ { className: "git-list" },
2159
+ commitDetailFiles.map(function(f, i) {
2160
+ var name = (f.path || '').split('/').pop() || f.path;
2161
+ return React.createElement(
2162
+ "div",
2163
+ { key: i, className: "git-file-item" },
2164
+ React.createElement(
2165
+ "div",
2166
+ { className: "git-file-info", onClick: function() { openFileFromGitPanel(f.path, name); } },
2167
+ React.createElement("span", { className: "git-status-badge git-" + (f.status || 'M'), title: f.status }, f.status),
2168
+ React.createElement("span", { className: "git-file-path", title: f.path }, f.path)
2169
+ ),
2170
+ React.createElement(
2171
+ "div",
2172
+ { className: "git-file-actions" },
2173
+ React.createElement(
2174
+ "button",
2175
+ {
2176
+ className: "git-action-btn",
2177
+ title: "View Diff",
2178
+ onClick: function(e) {
2179
+ e.stopPropagation();
2180
+ TabManager.openDiffTab(f.path, name, selectedCommit.hash + '^', selectedCommit.hash, null);
2181
+ }
2182
+ },
2183
+ React.createElement("i", { className: "fas fa-exchange-alt" })
2184
+ )
2185
+ )
2186
+ );
2187
+ })
2188
+ )
2189
+ )
2190
+ ),
2191
+
2192
+ // Modals & Panels
2193
+ state.isQuickOpenVisible && React.createElement(window.QuickOpenDialog || QuickOpenDialog, { onSelect: handleSelectFile, onClose: function () {
2194
+ return setQuickOpen(false);
2195
+ } }),
2196
+ contextMenu && React.createElement(
2197
+ React.Fragment,
2198
+ null,
2199
+ React.createElement("div", {
2200
+ style: { position: 'fixed', inset: 0, zIndex: 9998 },
2201
+ onClick: closeContextMenu,
2202
+ onContextMenu: function (e) {
2203
+ e.preventDefault();closeContextMenu();
2204
+ }
2205
+ }),
2206
+ React.createElement(
2207
+ "div",
2208
+ {
2209
+ className: "context-menu",
2210
+ style: { left: contextMenu.x, top: contextMenu.y },
2211
+ onClick: function (e) {
2212
+ return e.stopPropagation();
2213
+ }
2214
+ },
2215
+ contextMenu.node && contextMenu.node.type === 'file' && React.createElement(
2216
+ "div",
2217
+ { className: "context-menu-item", onClick: function () {
2218
+ return handleContextMenuAction('open');
2219
+ } },
2220
+ React.createElement("i", { className: "far fa-file-code context-menu-icon" }),
2221
+ " Open"
2222
+ ),
2223
+ React.createElement(
2224
+ "div",
2225
+ { className: "context-menu-item", onClick: function () {
2226
+ return handleContextMenuAction('newFile');
2227
+ } },
2228
+ React.createElement("i", { className: "far fa-file context-menu-icon" }),
2229
+ " New File"
2230
+ ),
2231
+ React.createElement(
2232
+ "div",
2233
+ { className: "context-menu-item", onClick: function () {
2234
+ return handleContextMenuAction('newFolder');
2235
+ } },
2236
+ React.createElement("i", { className: "far fa-folder context-menu-icon" }),
2237
+ " New Folder"
2238
+ ),
2239
+ React.createElement("div", { className: "context-menu-divider" }),
2240
+ React.createElement(
2241
+ "div",
2242
+ { className: "context-menu-item", onClick: function () {
2243
+ return handleContextMenuAction('rename');
2244
+ } },
2245
+ React.createElement("i", { className: "fas fa-pen context-menu-icon" }),
2246
+ " Rename"
2247
+ ),
2248
+ React.createElement(
2249
+ "div",
2250
+ { className: "context-menu-item context-menu-item-danger", onClick: function () {
2251
+ return handleContextMenuAction('delete');
2252
+ } },
2253
+ React.createElement("i", { className: "far fa-trash-alt context-menu-icon" }),
2254
+ " Delete"
2255
+ ),
2256
+ React.createElement("div", { className: "context-menu-divider" }),
2257
+ React.createElement(
2258
+ "div",
2259
+ { className: "context-menu-item", onClick: function () {
2260
+ return handleContextMenuAction('copyPath');
2261
+ } },
2262
+ React.createElement("i", { className: "fas fa-copy context-menu-icon" }),
2263
+ " Copy Path"
2264
+ )
2265
+ )
2266
+ ),
2267
+ closingTabId && React.createElement(
2268
+ "div",
2269
+ { className: "quick-open-overlay", style: { zIndex: 10001 } },
2270
+ React.createElement(
2271
+ "div",
2272
+ { className: "quick-open-box", style: { width: '400px', padding: '20px', background: '#252526', border: '1px solid #454545' } },
2273
+ React.createElement(
2274
+ "h3",
2275
+ { style: { marginTop: 0, fontSize: '14px', color: '#fff' } },
2276
+ "Unsaved Changes"
2277
+ ),
2278
+ React.createElement(
2279
+ "p",
2280
+ { style: { color: '#ccc', margin: '16px 0', fontSize: '13px' } },
2281
+ "Do you want to save the changes you made to ",
2282
+ React.createElement(
2283
+ "strong",
2284
+ null,
2285
+ (state.panes.flatMap(function (p) {
2286
+ return p.tabs;
2287
+ }).find(function (t) {
2288
+ return t.id === closingTabId;
2289
+ }) || {}).name
2290
+ ),
2291
+ "?"
2292
+ ),
2293
+ React.createElement(
2294
+ "p",
2295
+ { style: { color: '#888', marginBottom: '24px', fontSize: '12px' } },
2296
+ "Your changes will be lost if you don't save them."
2297
+ ),
2298
+ React.createElement(
2299
+ "div",
2300
+ { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' } },
2301
+ React.createElement(
2302
+ "button",
2303
+ {
2304
+ onClick: function () {
2305
+ return confirmCloseTab(true);
2306
+ },
2307
+ style: { padding: '6px 16px', background: '#0e639c', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' } },
2308
+ "Save"
2309
+ ),
2310
+ React.createElement(
2311
+ "button",
2312
+ {
2313
+ onClick: function () {
2314
+ return confirmCloseTab(false);
2315
+ },
2316
+ style: { padding: '6px 16px', background: 'transparent', color: '#ccc', border: '1px solid #666', borderRadius: '4px', cursor: 'pointer' } },
2317
+ "Don't Save"
2318
+ ),
2319
+ React.createElement(
2320
+ "button",
2321
+ {
2322
+ onClick: function () {
2323
+ return setClosingTabId(null);
2324
+ },
2325
+ style: { padding: '6px 16px', background: 'transparent', color: '#888', border: 'none', cursor: 'pointer' } },
2326
+ "Cancel"
2327
+ )
2328
+ )
2329
+ )
2330
+ )
2331
+ );
2332
+ };
2333
+
2334
+ window.MbeditorApp = MbeditorApp;
2335
+ /* TITLE BAR */ /* SIDEBAR */ /* EDITOR AREA */ /* STATUS BAR */ /* Right-click context menu */