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,461 @@
1
+ var TabManager = (function () {
2
+ function _isImagePath(path) {
3
+ return /\.(png|jpe?g|gif|svg|ico|webp|bmp|avif)$/i.test(path || "");
4
+ }
5
+
6
+ function _isMarkdownPath(path) {
7
+ return /\.(md|markdown)$/i.test(path || "");
8
+ }
9
+
10
+ function _previewPath(path) {
11
+ return path + "::preview";
12
+ }
13
+
14
+ function _previewName(name) {
15
+ return (name || "preview") + "-preview";
16
+ }
17
+
18
+ function _ensureMarkdownPreview(sourcePaneId, sourcePath, sourceName, sourceContent) {
19
+ var state = EditorStore.getState();
20
+ var targetPaneId = sourcePaneId === 1 ? 2 : 1;
21
+ var previewPath = _previewPath(sourcePath);
22
+ var targetPane = state.panes.find(function(p) { return p.id === targetPaneId; });
23
+ if (!targetPane) return;
24
+
25
+ var existing = targetPane.tabs.find(function(t) { return t.path === previewPath; });
26
+ if (existing) {
27
+ _updateTab(targetPaneId, previewPath, {
28
+ name: _previewName(sourceName),
29
+ isPreview: true,
30
+ previewFor: sourcePath,
31
+ content: typeof sourceContent === 'string' ? sourceContent : existing.content
32
+ });
33
+ return;
34
+ }
35
+
36
+ var previewTab = {
37
+ id: previewPath,
38
+ path: previewPath,
39
+ name: _previewName(sourceName),
40
+ dirty: false,
41
+ content: typeof sourceContent === 'string' ? sourceContent : "",
42
+ viewState: null,
43
+ isPreview: true,
44
+ previewFor: sourcePath
45
+ };
46
+
47
+ var newPanes = state.panes.map(function(p) {
48
+ if (p.id === targetPaneId) {
49
+ return Object.assign({}, p, {
50
+ tabs: p.tabs.concat(previewTab),
51
+ activeTabId: previewPath
52
+ });
53
+ }
54
+ return p;
55
+ });
56
+
57
+ EditorStore.setState({ panes: newPanes, focusedPaneId: sourcePaneId, activeTabId: sourcePath });
58
+ }
59
+
60
+ function _syncMarkdownPreviewContent(sourcePath, sourceContent) {
61
+ var state = EditorStore.getState();
62
+ var previewPath = _previewPath(sourcePath);
63
+
64
+ state.panes.forEach(function(pane) {
65
+ var previewTab = pane.tabs.find(function(tab) {
66
+ return tab.path === previewPath || (tab.isPreview && tab.previewFor === sourcePath);
67
+ });
68
+
69
+ if (previewTab) {
70
+ _updateTab(pane.id, previewTab.path, {
71
+ content: sourceContent,
72
+ previewFor: sourcePath,
73
+ isPreview: true
74
+ });
75
+ }
76
+ });
77
+ }
78
+
79
+ function openTab(path, name, line, forcePaneId, isSoftOpen) {
80
+ var state = EditorStore.getState();
81
+ var paneId = forcePaneId || state.focusedPaneId;
82
+ var pane = state.panes.find(function(p) { return p.id === paneId; });
83
+
84
+ // If pane 2 is currently empty/hidden, prefer opening new tabs in pane 1.
85
+ if (!forcePaneId && paneId === 2 && (!pane || pane.tabs.length === 0)) {
86
+ var primaryPane = state.panes.find(function(p) { return p.id === 1; });
87
+ if (primaryPane && primaryPane.tabs.length > 0) {
88
+ paneId = 1;
89
+ pane = primaryPane;
90
+ }
91
+ }
92
+
93
+ if (!pane) return;
94
+
95
+ var existing = pane.tabs.find(function(t) { return t.path === path; });
96
+
97
+ if (existing) {
98
+ if (line) _updateTab(paneId, path, { gotoLine: line });
99
+ switchTab(paneId, path);
100
+ if (_isMarkdownPath(path)) {
101
+ _ensureMarkdownPreview(paneId, path, existing.name || name, existing.content || "");
102
+ }
103
+ return;
104
+ }
105
+
106
+ // For soft-opens, discard any existing non-dirty soft tab so this one replaces it.
107
+ var tabsList = pane.tabs;
108
+ if (isSoftOpen) {
109
+ tabsList = pane.tabs.filter(function(t) { return !t.isSoftOpen || t.dirty; });
110
+ }
111
+
112
+ // Create placeholder then load
113
+ var newTab = {
114
+ id: path,
115
+ path: path,
116
+ name: name,
117
+ dirty: false,
118
+ content: "",
119
+ viewState: null,
120
+ isImage: _isImagePath(path),
121
+ isSoftOpen: isSoftOpen ? true : false
122
+ };
123
+ if (line) newTab.gotoLine = line;
124
+
125
+ var newPanes = state.panes.map(function(p) {
126
+ if (p.id === paneId) {
127
+ return Object.assign({}, p, { tabs: tabsList.concat(newTab), activeTabId: path });
128
+ }
129
+ return p;
130
+ });
131
+
132
+ EditorStore.setState({ panes: newPanes, focusedPaneId: paneId, activeTabId: path });
133
+
134
+ if (_isMarkdownPath(path)) {
135
+ _ensureMarkdownPreview(paneId, path, name, "");
136
+ }
137
+
138
+ // Virtual paths (diff://, combined-diff://) should never hit the file endpoint
139
+ if (path.indexOf('diff://') === 0 || path.indexOf('combined-diff://') === 0) {
140
+ return;
141
+ }
142
+
143
+ FileService.getFile(path).then(function(data) {
144
+ var loadedContent = typeof data.content === 'string' ? data.content : "";
145
+ _updateTab(paneId, path, {
146
+ content: loadedContent,
147
+ cleanContent: loadedContent,
148
+ isImage: data.image === true ? true : _isImagePath(path)
149
+ });
150
+ if (_isMarkdownPath(path)) {
151
+ _syncMarkdownPreviewContent(path, typeof data.content === 'string' ? data.content : "");
152
+ }
153
+ }).catch(function(err) {
154
+ if (path.startsWith('diff://')) return; // diff tabs handle their own loading
155
+ EditorStore.setStatus("Failed to load file: " + ((err.response && err.response.data && err.response.data.error) || err.message), "error");
156
+ closeTab(paneId, path);
157
+ });
158
+ }
159
+
160
+ function openDiffTab(repoPath, name, baseSha, headSha, paneIdOverride) {
161
+ var state = EditorStore.getState();
162
+ var paneId = paneIdOverride || state.focusedPaneId;
163
+ var pane = state.panes.find(function(p) { return p.id === paneId; });
164
+ if (!pane) return;
165
+
166
+ // diff://[base]..[head]/path
167
+ var baseStr = baseSha || 'HEAD';
168
+ var headStr = headSha || 'WORKING';
169
+ var diffId = 'diff://' + baseStr + '..' + headStr + '/' + repoPath;
170
+
171
+ var existing = pane.tabs.find(function(t) { return t.id === diffId; });
172
+ if (existing) {
173
+ switchTab(paneId, diffId);
174
+ return;
175
+ }
176
+
177
+ var newTab = {
178
+ id: diffId,
179
+ path: diffId, // use diffId as path so markdown-preview logic never fires
180
+ repoPath: repoPath, // original file path kept for reference
181
+ name: name + ' \u2195 Diff',
182
+ dirty: false,
183
+ content: "Loading diff...",
184
+ viewState: null,
185
+ isImage: false,
186
+ isSoftOpen: false,
187
+ isDiff: true,
188
+ diffOriginal: "",
189
+ diffModified: "",
190
+ diffBaseSha: baseSha,
191
+ diffHeadSha: headSha
192
+ };
193
+
194
+ var newPanes = state.panes.map(function(p) {
195
+ if (p.id === paneId) {
196
+ return Object.assign({}, p, { tabs: p.tabs.concat(newTab), activeTabId: diffId });
197
+ }
198
+ return p;
199
+ });
200
+
201
+ EditorStore.setState({ panes: newPanes, focusedPaneId: paneId, activeTabId: diffId });
202
+
203
+ GitService.fetchDiff(repoPath, baseSha, headSha).then(function(data) {
204
+ _updateTab(paneId, diffId, {
205
+ content: "Diff loaded",
206
+ diffOriginal: data.original || "",
207
+ diffModified: data.modified || ""
208
+ });
209
+ }).catch(function(err) {
210
+ var msg = err.response && err.response.data && err.response.data.error || err.message;
211
+ EditorStore.setStatus("Failed to load diff: " + msg, "error");
212
+ _updateTab(paneId, diffId, {
213
+ content: "Error: " + msg,
214
+ diffOriginal: "Error loading diff",
215
+ diffModified: "Error loading diff"
216
+ });
217
+ });
218
+ }
219
+
220
+ function openCombinedDiffTab(scope, label) {
221
+ var tabId = 'combined-diff://' + (scope || 'local');
222
+ var state = EditorStore.getState();
223
+ var paneId = state.focusedPaneId;
224
+ var pane = state.panes.find(function(p) { return p.id === paneId; });
225
+ if (!pane) return;
226
+
227
+ // Re-activate existing tab if present
228
+ var existing = pane.tabs.find(function(t) { return t.id === tabId; });
229
+ if (existing) {
230
+ switchTab(paneId, tabId);
231
+ return;
232
+ }
233
+
234
+ var newTab = {
235
+ id: tabId,
236
+ path: tabId,
237
+ name: label || 'All Changes',
238
+ dirty: false,
239
+ content: '',
240
+ viewState: null,
241
+ isImage: false,
242
+ isSoftOpen: false,
243
+ isCombinedDiff: true,
244
+ combinedDiffText: '',
245
+ combinedDiffLabel: label || 'All Changes',
246
+ combinedDiffLoaded: false
247
+ };
248
+
249
+ var newPanes = state.panes.map(function(p) {
250
+ if (p.id === paneId) {
251
+ return Object.assign({}, p, { tabs: p.tabs.concat(newTab), activeTabId: tabId });
252
+ }
253
+ return p;
254
+ });
255
+ EditorStore.setState({ panes: newPanes, focusedPaneId: paneId, activeTabId: tabId });
256
+
257
+ var basePath = (window.MBEDITOR_BASE_PATH || '/mbeditor').replace(/\/$/, '');
258
+ axios.get(basePath + '/git/combined_diff', { params: { scope: scope || 'local' } })
259
+ .then(function(res) {
260
+ _updateTab(paneId, tabId, {
261
+ combinedDiffText: typeof res.data === 'string' ? res.data : '',
262
+ combinedDiffLoaded: true
263
+ });
264
+ })
265
+ .catch(function(err) {
266
+ _updateTab(paneId, tabId, {
267
+ combinedDiffText: '# Error loading diff: ' + (err.message || 'unknown error'),
268
+ combinedDiffLoaded: true
269
+ });
270
+ });
271
+ }
272
+
273
+ function closeTab(paneId, path) {
274
+ var state = EditorStore.getState();
275
+ var newPanes = state.panes.map(function(pane) {
276
+ if (pane.id === paneId) {
277
+ // Match by t.id so diff tabs (id = 'diff://...') are closed correctly
278
+ var newTabs = pane.tabs.filter(function(t) { return t.id !== path; });
279
+ var newActive = pane.activeTabId;
280
+ if (pane.activeTabId === path) {
281
+ newActive = newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
282
+ }
283
+ return Object.assign({}, pane, { tabs: newTabs, activeTabId: newActive });
284
+ }
285
+ return pane;
286
+ });
287
+
288
+ var nextFocusedPaneId = state.focusedPaneId;
289
+ var focusedPane = newPanes.find(function(p) { return p.id === nextFocusedPaneId; });
290
+
291
+ // If the currently focused pane became empty, move focus to a pane that still has tabs.
292
+ if (!focusedPane || focusedPane.tabs.length === 0) {
293
+ var paneWithTabs = newPanes.find(function(p) { return p.tabs.length > 0; });
294
+ nextFocusedPaneId = paneWithTabs ? paneWithTabs.id : 1;
295
+ focusedPane = newPanes.find(function(p) { return p.id === nextFocusedPaneId; });
296
+ }
297
+
298
+ // If pane 1 is now empty and pane 2 has tabs, migrate pane 2 tabs into pane 1.
299
+ var pane1AfterClose = newPanes.find(function(p) { return p.id === 1; });
300
+ var pane2AfterClose = newPanes.find(function(p) { return p.id === 2; });
301
+ if (pane1AfterClose && pane1AfterClose.tabs.length === 0 && pane2AfterClose && pane2AfterClose.tabs.length > 0) {
302
+ newPanes = newPanes.map(function(p) {
303
+ if (p.id === 1) return Object.assign({}, p, { tabs: pane2AfterClose.tabs, activeTabId: pane2AfterClose.activeTabId });
304
+ if (p.id === 2) return Object.assign({}, p, { tabs: [], activeTabId: null });
305
+ return p;
306
+ });
307
+ nextFocusedPaneId = 1;
308
+ focusedPane = newPanes.find(function(p) { return p.id === 1; });
309
+ }
310
+
311
+ var maybeNewGlobalActiveTab = focusedPane ? focusedPane.activeTabId : null;
312
+
313
+ EditorStore.setState({
314
+ panes: newPanes,
315
+ focusedPaneId: nextFocusedPaneId,
316
+ activeTabId: maybeNewGlobalActiveTab
317
+ });
318
+ }
319
+
320
+ function closeAllTabsInPane(paneId) {
321
+ var state = EditorStore.getState();
322
+ var pane = state.panes.find(function(p) { return p.id === paneId; });
323
+ if (!pane || pane.tabs.length === 0) return;
324
+
325
+ pane.tabs.slice().forEach(function(tab) {
326
+ closeTab(paneId, tab.path);
327
+ });
328
+ }
329
+
330
+ function closeAllTabs() {
331
+ var state = EditorStore.getState();
332
+ var paneIds = state.panes.map(function(p) { return p.id; }).sort(function(a, b) { return b - a; });
333
+
334
+ paneIds.forEach(function(paneId) {
335
+ closeAllTabsInPane(paneId);
336
+ });
337
+ }
338
+
339
+ function switchTab(paneId, path) {
340
+ var state = EditorStore.getState();
341
+ var newPanes = state.panes.map(function(p) {
342
+ if (p.id === paneId) return Object.assign({}, p, { activeTabId: path });
343
+ return p;
344
+ });
345
+ EditorStore.setState({ panes: newPanes, focusedPaneId: paneId, activeTabId: path });
346
+ }
347
+
348
+ function focusPane(paneId) {
349
+ var state = EditorStore.getState();
350
+ var pane = state.panes.find(function(p) { return p.id === paneId; });
351
+ var newActive = pane ? pane.activeTabId : null;
352
+ EditorStore.setState({ focusedPaneId: paneId, activeTabId: newActive });
353
+ }
354
+
355
+ function markDirty(paneId, path, content) {
356
+ // Auto-harden any soft-open tab on first edit.
357
+ var st = EditorStore.getState();
358
+ var paneForTab = st.panes.find(function(p) { return p.id === paneId; });
359
+ var existingTab = paneForTab && paneForTab.tabs.find(function(t) { return t.path === path; });
360
+ var updates = { content: content, dirty: true };
361
+ if (existingTab && existingTab.isSoftOpen) {
362
+ updates.isSoftOpen = false;
363
+ }
364
+ _updateTab(paneId, path, updates);
365
+ if (_isMarkdownPath(path)) {
366
+ _syncMarkdownPreviewContent(path, content);
367
+ }
368
+ }
369
+
370
+ function markClean(paneId, path, content) {
371
+ _updateTab(paneId, path, { content: content, dirty: false });
372
+ }
373
+
374
+ function hardenTab(paneId, path) {
375
+ _updateTab(paneId, path, { isSoftOpen: false });
376
+ }
377
+
378
+ function saveTabViewState(paneIdOrPath, pathOrViewState, maybeViewState) {
379
+ var paneId = paneIdOrPath;
380
+ var path = pathOrViewState;
381
+ var viewState = maybeViewState;
382
+
383
+ // Backward-compatible signature: saveTabViewState(path, viewState)
384
+ if (typeof maybeViewState === 'undefined') {
385
+ var state = EditorStore.getState();
386
+ path = paneIdOrPath;
387
+ viewState = pathOrViewState;
388
+ var containingPane = state.panes.find(function(p) {
389
+ return p.tabs.some(function(t) { return t.path === path; });
390
+ });
391
+ if (!containingPane) return;
392
+ paneId = containingPane.id;
393
+ }
394
+
395
+ _updateTab(paneId, path, { viewState: viewState });
396
+ }
397
+
398
+ function moveTabToPane(sourcePaneId, targetPaneId, path) {
399
+ if (sourcePaneId === targetPaneId) return;
400
+
401
+ var state = EditorStore.getState();
402
+ var sourcePane = state.panes.find(function(p) { return p.id === sourcePaneId; });
403
+ var targetPane = state.panes.find(function(p) { return p.id === targetPaneId; });
404
+ if (!sourcePane || !targetPane) return;
405
+
406
+ var tabToMove = sourcePane.tabs.find(function(t) { return t.path === path; });
407
+ if (!tabToMove) return;
408
+
409
+ var targetHasTab = targetPane.tabs.some(function(t) { return t.path === path; });
410
+
411
+ var newPanes = state.panes.map(function(p) {
412
+ if (p.id === sourcePaneId) {
413
+ var newTabs = p.tabs.filter(function(t) { return t.path !== path; });
414
+ var newActive = p.activeTabId === path ? (newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null) : p.activeTabId;
415
+ return Object.assign({}, p, { tabs: newTabs, activeTabId: newActive });
416
+ }
417
+ if (p.id === targetPaneId) {
418
+ return Object.assign({}, p, { tabs: targetHasTab ? p.tabs : p.tabs.concat(tabToMove), activeTabId: path });
419
+ }
420
+ return p;
421
+ });
422
+
423
+ EditorStore.setState({ panes: newPanes, focusedPaneId: targetPaneId, activeTabId: path });
424
+ }
425
+
426
+ function _updateTab(paneId, path, updates) {
427
+ var state = EditorStore.getState();
428
+ var newPanes = state.panes.map(function(pane) {
429
+ if (pane.id === paneId) {
430
+ var newTabs = pane.tabs.map(function(t) {
431
+ // Match by t.id so diff tabs (where id !== path) are found correctly
432
+ return t.id === path ? Object.assign({}, t, updates) : t;
433
+ });
434
+ return Object.assign({}, pane, { tabs: newTabs });
435
+ }
436
+ return pane;
437
+ });
438
+ EditorStore.setState({ panes: newPanes });
439
+ }
440
+
441
+ function clearGotoLine(paneId, path) {
442
+ _updateTab(paneId, path, { gotoLine: null });
443
+ }
444
+
445
+ return {
446
+ openTab: openTab,
447
+ openDiffTab: openDiffTab,
448
+ openCombinedDiffTab: openCombinedDiffTab,
449
+ closeTab: closeTab,
450
+ switchTab: switchTab,
451
+ focusPane: focusPane,
452
+ markDirty: markDirty,
453
+ markClean: markClean,
454
+ hardenTab: hardenTab,
455
+ saveTabViewState: saveTabViewState,
456
+ moveTabToPane: moveTabToPane,
457
+ clearGotoLine: clearGotoLine,
458
+ closeAllTabsInPane: closeAllTabsInPane,
459
+ closeAllTabs: closeAllTabs
460
+ };
461
+ })();