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.
- checksums.yaml +7 -0
- data/README.md +127 -0
- data/app/assets/javascripts/mbeditor/application.js +19 -0
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +202 -0
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +71 -0
- data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +139 -0
- data/app/assets/javascripts/mbeditor/components/CommitGraph.js +65 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +142 -0
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +363 -0
- data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +112 -0
- data/app/assets/javascripts/mbeditor/components/FileTree.js +304 -0
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +416 -0
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +2335 -0
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +118 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +186 -0
- data/app/assets/javascripts/mbeditor/components/TabBar.js +123 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +282 -0
- data/app/assets/javascripts/mbeditor/editor_store.js +53 -0
- data/app/assets/javascripts/mbeditor/file_service.js +77 -0
- data/app/assets/javascripts/mbeditor/git_service.js +104 -0
- data/app/assets/javascripts/mbeditor/search_service.js +53 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +461 -0
- data/app/assets/stylesheets/mbeditor/application.css +705 -0
- data/app/assets/stylesheets/mbeditor/editor.css +1264 -0
- data/app/controllers/mbeditor/application_controller.rb +10 -0
- data/app/controllers/mbeditor/editors_controller.rb +695 -0
- data/app/controllers/mbeditor/git_controller.rb +188 -0
- data/app/services/mbeditor/git_blame_service.rb +98 -0
- data/app/services/mbeditor/git_commit_graph_service.rb +60 -0
- data/app/services/mbeditor/git_diff_service.rb +71 -0
- data/app/services/mbeditor/git_file_history_service.rb +42 -0
- data/app/services/mbeditor/git_service.rb +82 -0
- data/app/services/mbeditor/redmine_service.rb +86 -0
- data/app/views/layouts/mbeditor/application.html.erb +71 -0
- data/app/views/mbeditor/editors/index.html.erb +1 -0
- data/config/environments/development.rb +53 -0
- data/config/initializers/assets.rb +9 -0
- data/config/routes.rb +37 -0
- data/lib/mbeditor/configuration.rb +16 -0
- data/lib/mbeditor/engine.rb +28 -0
- data/lib/mbeditor/version.rb +3 -0
- data/lib/mbeditor.rb +19 -0
- data/mbeditor.gemspec +30 -0
- data/public/min-maps/vs/base/worker/workerMain.js.map +1 -0
- data/public/monaco-editor/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- data/public/monaco-editor/vs/base/worker/workerMain.js +31 -0
- data/public/monaco-editor/vs/basic-languages/cameligo/cameligo.js +10 -0
- data/public/monaco-editor/vs/basic-languages/css/css.js +12 -0
- data/public/monaco-editor/vs/basic-languages/dart/dart.js +10 -0
- data/public/monaco-editor/vs/basic-languages/flow9/flow9.js +10 -0
- data/public/monaco-editor/vs/basic-languages/go/go.js +10 -0
- data/public/monaco-editor/vs/basic-languages/handlebars/handlebars.js +440 -0
- data/public/monaco-editor/vs/basic-languages/javascript/javascript.js +10 -0
- data/public/monaco-editor/vs/basic-languages/markdown/markdown.js +10 -0
- data/public/monaco-editor/vs/basic-languages/msdax/msdax.js +10 -0
- data/public/monaco-editor/vs/basic-languages/postiats/postiats.js +10 -0
- data/public/monaco-editor/vs/basic-languages/pug/pug.js +412 -0
- data/public/monaco-editor/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
- data/public/monaco-editor/vs/basic-languages/ruby/ruby.js +10 -0
- data/public/monaco-editor/vs/basic-languages/sb/sb.js +10 -0
- data/public/monaco-editor/vs/basic-languages/typespec/typespec.js +10 -0
- data/public/monaco-editor/vs/basic-languages/yaml/yaml.js +10 -0
- data/public/monaco-editor/vs/editor/editor.main.css +8 -0
- data/public/monaco-editor/vs/editor/editor.main.js +797 -0
- data/public/monaco-editor/vs/language/typescript/tsMode.js +20 -0
- data/public/monaco-editor/vs/language/typescript/tsWorker.js +51328 -0
- data/public/monaco-editor/vs/loader.js +10 -0
- data/public/monaco-editor/vs/nls.messages.de.js +20 -0
- data/public/monaco-editor/vs/nls.messages.es.js +20 -0
- data/public/monaco-editor/vs/nls.messages.fr.js +18 -0
- data/public/monaco-editor/vs/nls.messages.it.js +18 -0
- data/public/monaco-editor/vs/nls.messages.ja.js +20 -0
- data/public/monaco-editor/vs/nls.messages.ko.js +18 -0
- data/public/monaco-editor/vs/nls.messages.ru.js +20 -0
- data/public/monaco-editor/vs/nls.messages.zh-cn.js +20 -0
- data/public/monaco-editor/vs/nls.messages.zh-tw.js +18 -0
- data/public/monaco_worker.js +5 -0
- data/vendor/assets/javascripts/axios.min.js +2 -0
- data/vendor/assets/javascripts/lodash.min.js +140 -0
- data/vendor/assets/javascripts/marked.min.js +6 -0
- data/vendor/assets/javascripts/minisearch.min.js +2044 -0
- data/vendor/assets/javascripts/prettier-plugin-babel.js +16 -0
- data/vendor/assets/javascripts/prettier-plugin-estree.js +35 -0
- data/vendor/assets/javascripts/prettier-plugin-html.js +19 -0
- data/vendor/assets/javascripts/prettier-plugin-markdown.js +59 -0
- data/vendor/assets/javascripts/prettier-plugin-postcss.js +52 -0
- data/vendor/assets/javascripts/prettier-standalone.js +37 -0
- data/vendor/assets/javascripts/react-dom.min.js +267 -0
- data/vendor/assets/javascripts/react.min.js +31 -0
- data/vendor/assets/stylesheets/fontawesome.min.css +9 -0
- data/vendor/assets/webfonts/fa-brands-400.woff2 +0 -0
- data/vendor/assets/webfonts/fa-regular-400.woff2 +0 -0
- data/vendor/assets/webfonts/fa-solid-900.woff2 +0 -0
- metadata +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
|
+
})();
|