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,142 @@
1
+ 'use strict';
2
+
3
+ var DiffViewer = function DiffViewer(_ref) {
4
+ var path = _ref.path;
5
+ var original = _ref.original;
6
+ var modified = _ref.modified;
7
+ var isDark = _ref.isDark;
8
+ var onClose = _ref.onClose;
9
+ // If path is a diff:// URI (diff://baseSha..headSha/actual/file.rb), extract
10
+ // just the file path portion for display so the title bar shows a clean name.
11
+ var _rawDisplayPath = _ref.displayPath || path;
12
+ var displayPath = _rawDisplayPath;
13
+ if (!_ref.displayPath && _rawDisplayPath && _rawDisplayPath.indexOf('diff://') === 0) {
14
+ var _rest = _rawDisplayPath.slice(7); // strip 'diff://'
15
+ var _sep = _rest.indexOf('/');
16
+ if (_sep !== -1) displayPath = _rest.slice(_sep + 1);
17
+ }
18
+
19
+ var containerRef = React.useRef(null);
20
+ var editorRef = React.useRef(null);
21
+ var currentChangeRef = React.useRef(-1);
22
+
23
+ React.useEffect(function () {
24
+ if (!window.monaco || !containerRef.current) return;
25
+
26
+ var modelOriginal = window.monaco.editor.createModel(original, getLanguageForPath(displayPath));
27
+ var modelModified = window.monaco.editor.createModel(modified, getLanguageForPath(displayPath));
28
+
29
+ var diffEditor = window.monaco.editor.createDiffEditor(containerRef.current, {
30
+ theme: isDark ? 'vs-dark' : 'vs',
31
+ readOnly: true,
32
+ automaticLayout: true,
33
+ originalEditable: false,
34
+ renderSideBySide: true,
35
+ useInlineViewWhenSpaceIsLimited: false,
36
+ ignoreTrimWhitespace: false,
37
+ minimap: { enabled: false },
38
+ scrollBeyondLastLine: false,
39
+ fontFamily: "'JetBrains Mono', 'Fira Code', 'Menlo', 'Consolas', monospace",
40
+ fontSize: 13
41
+ });
42
+
43
+ diffEditor.setModel({
44
+ original: modelOriginal,
45
+ modified: modelModified
46
+ });
47
+
48
+ editorRef.current = diffEditor;
49
+ currentChangeRef.current = -1;
50
+
51
+ diffEditor.onDidUpdateDiff(function () {
52
+ currentChangeRef.current = -1;
53
+ });
54
+
55
+ return function () {
56
+ if (editorRef.current) {
57
+ editorRef.current.dispose();
58
+ editorRef.current = null;
59
+ }
60
+ modelOriginal.dispose();
61
+ modelModified.dispose();
62
+ };
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ }, [original, modified, displayPath, isDark]);
65
+
66
+ var handleNextDiff = function handleNextDiff() {
67
+ if (!editorRef.current) return;
68
+ var changes = editorRef.current.getLineChanges() || [];
69
+ if (!changes.length) return;
70
+ currentChangeRef.current = (currentChangeRef.current + 1) % changes.length;
71
+ var change = changes[currentChangeRef.current];
72
+ editorRef.current.getModifiedEditor().revealLineInCenter(change.modifiedStartLineNumber);
73
+ };
74
+
75
+ var handlePrevDiff = function handlePrevDiff() {
76
+ if (!editorRef.current) return;
77
+ var changes = editorRef.current.getLineChanges() || [];
78
+ if (!changes.length) return;
79
+ currentChangeRef.current = (currentChangeRef.current - 1 + changes.length) % changes.length;
80
+ var change = changes[currentChangeRef.current];
81
+ editorRef.current.getModifiedEditor().revealLineInCenter(change.modifiedStartLineNumber);
82
+ };
83
+
84
+ function getLanguageForPath(filePath) {
85
+ if (!filePath) return 'plaintext';
86
+ var ext = filePath.split('.').pop().toLowerCase();
87
+ var map = {
88
+ 'rb': 'ruby', 'js': 'javascript', 'jsx': 'javascript',
89
+ 'ts': 'javascript', 'tsx': 'javascript',
90
+ 'json': 'json', 'yml': 'yaml', 'yaml': 'yaml',
91
+ 'css': 'css', 'scss': 'scss', 'html': 'html',
92
+ 'xml': 'xml', 'md': 'markdown', 'sh': 'shell'
93
+ };
94
+ return map[ext] || 'plaintext';
95
+ }
96
+
97
+ var pathParts = (displayPath || '').split('/');
98
+ var fileName = pathParts.pop() || displayPath || '';
99
+ var fileDir = pathParts.join('/');
100
+
101
+ return React.createElement(
102
+ 'div',
103
+ { className: 'ide-diff-viewer' },
104
+ React.createElement(
105
+ 'div',
106
+ { className: 'ide-diff-toolbar' },
107
+ React.createElement(
108
+ 'div',
109
+ { className: 'ide-diff-title' },
110
+ React.createElement('i', { className: 'fas fa-file-contract' }),
111
+ React.createElement(
112
+ 'div',
113
+ { className: 'ide-diff-title-info', title: displayPath },
114
+ React.createElement('div', { className: 'ide-diff-title-name' }, fileName),
115
+ fileDir ? React.createElement('div', { className: 'ide-diff-title-dir' }, fileDir) : null
116
+ )
117
+ ),
118
+ React.createElement(
119
+ 'div',
120
+ { className: 'ide-diff-actions' },
121
+ onClose && React.createElement(
122
+ 'button',
123
+ { className: 'ide-diff-btn', onClick: onClose, title: 'Close diff (or click × on the tab)' },
124
+ React.createElement('i', { className: 'fas fa-times' })
125
+ ),
126
+ React.createElement(
127
+ 'button',
128
+ { className: 'ide-diff-btn', onClick: handlePrevDiff, title: 'Previous Change' },
129
+ React.createElement('i', { className: 'fas fa-arrow-up' })
130
+ ),
131
+ React.createElement(
132
+ 'button',
133
+ { className: 'ide-diff-btn', onClick: handleNextDiff, title: 'Next Change' },
134
+ React.createElement('i', { className: 'fas fa-arrow-down' })
135
+ )
136
+ )
137
+ ),
138
+ React.createElement('div', { className: 'ide-diff-editor-container', ref: containerRef })
139
+ );
140
+ };
141
+
142
+ window.DiffViewer = DiffViewer;
@@ -0,0 +1,363 @@
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 _React = React;
6
+ var useState = _React.useState;
7
+ var useEffect = _React.useEffect;
8
+ var useRef = _React.useRef;
9
+
10
+ var IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'ico', 'webp', 'bmp', 'avif'];
11
+
12
+ var EditorPanel = function EditorPanel(_ref) {
13
+ var tab = _ref.tab;
14
+ var paneId = _ref.paneId;
15
+ var onContentChange = _ref.onContentChange;
16
+ var markers = _ref.markers;
17
+ var gitAvailable = _ref.gitAvailable === true;
18
+
19
+ var editorRef = useRef(null);
20
+ var monacoRef = useRef(null);
21
+
22
+ var _useState = useState('');
23
+ var _useState2 = _slicedToArray(_useState, 2);
24
+ var markup = _useState2[0];
25
+ var setMarkup = _useState2[1];
26
+
27
+ var _useState3 = useState(false);
28
+ var _useState4 = _slicedToArray(_useState3, 2);
29
+ var isBlameVisible = _useState4[0];
30
+ var setIsBlameVisible = _useState4[1];
31
+
32
+ var _useState5 = useState(null);
33
+ var _useState6 = _slicedToArray(_useState5, 2);
34
+ var blameData = _useState6[0];
35
+ var setBlameData = _useState6[1];
36
+
37
+ var _useState7 = useState(false);
38
+ var _useState8 = _slicedToArray(_useState7, 2);
39
+ var isBlameLoading = _useState8[0];
40
+ var setIsBlameLoading = _useState8[1];
41
+
42
+ var blameDecorationsRef = useRef([]);
43
+
44
+ var findTabByPath = function findTabByPath(path) {
45
+ if (!path) return null;
46
+ var state = EditorStore.getState();
47
+ for (var i = 0; i < state.panes.length; i += 1) {
48
+ var pane = state.panes[i];
49
+ var match = pane.tabs.find(function (t) {
50
+ return t.path === path;
51
+ });
52
+ if (match) return match;
53
+ }
54
+ return null;
55
+ };
56
+
57
+ useEffect(function () {
58
+ if (tab.isPreview) return;
59
+ if (!editorRef.current || !window.monaco) return;
60
+
61
+ if (window.MbeditorEditorPlugins && window.MbeditorEditorPlugins.registerGlobalExtensions) {
62
+ window.MbeditorEditorPlugins.registerGlobalExtensions(window.monaco);
63
+ }
64
+
65
+ var parts = tab.path.split('.');
66
+ var extension = parts.length > 1 ? parts.pop().toLowerCase() : '';
67
+ var language = 'plaintext';
68
+ switch (extension) {
69
+ case 'rb':case 'ruby':case 'gemspec':case 'rakefile':
70
+ language = 'ruby';break;
71
+ case 'js':case 'jsx':
72
+ language = 'javascript';break;
73
+ case 'ts':case 'tsx':
74
+ language = 'javascript';break;
75
+ case 'css':case 'scss':case 'sass':
76
+ language = 'css';break;
77
+ case 'html':case 'erb':
78
+ language = 'html';break;
79
+ case 'haml':
80
+ language = 'plaintext';break;
81
+ case 'json':
82
+ language = 'json';break;
83
+ case 'yaml':case 'yml':
84
+ language = 'yaml';break;
85
+ case 'md':case 'markdown':
86
+ language = 'markdown';break;
87
+ case 'sh':case 'bash':case 'zsh':
88
+ language = 'shell';break;
89
+ case 'png':case 'jpg':case 'jpeg':case 'gif':case 'svg':case 'ico':case 'webp':case 'bmp':case 'avif':
90
+ language = 'image';break;
91
+ }
92
+
93
+ if (language === 'image') return;
94
+
95
+ var editor = window.monaco.editor.create(editorRef.current, {
96
+ value: tab.content,
97
+ language: language,
98
+ theme: 'vs-dark',
99
+ automaticLayout: true,
100
+ minimap: { enabled: false },
101
+ renderLineHighlight: 'none',
102
+ bracketPairColorization: { enabled: true },
103
+ fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace",
104
+ fontSize: 13,
105
+ tabSize: 4,
106
+ insertSpaces: true,
107
+ wordWrap: 'on',
108
+ linkedEditing: true, // Enables Auto-Rename Tag natively!
109
+ fixedOverflowWidgets: true,
110
+ hover: { above: false },
111
+ autoClosingBrackets: 'always',
112
+ autoClosingQuotes: 'always',
113
+ autoIndent: 'full',
114
+ formatOnPaste: true,
115
+ formatOnType: true
116
+ });
117
+
118
+ if (tab.viewState) {
119
+ editor.restoreViewState(tab.viewState);
120
+ }
121
+
122
+ monacoRef.current = editor;
123
+ window.__mbeditorActiveEditor = editor;
124
+
125
+ var modelObj = editor.getModel();
126
+
127
+ var editorPluginDisposable = null;
128
+ if (window.MbeditorEditorPlugins && window.MbeditorEditorPlugins.attachEditorFeatures) {
129
+ editorPluginDisposable = window.MbeditorEditorPlugins.attachEditorFeatures(editor, language);
130
+ }
131
+
132
+ // Change listener
133
+ var contentDisposable = modelObj.onDidChangeContent(function (e) {
134
+ var val = editor.getValue();
135
+ var currentContent = monacoRef.current._latestContent || '';
136
+
137
+ // Normalize before comparing to prevent false positive dirty edits
138
+ var vNorm = val.replace(/\r\n/g, '\n');
139
+ var cNorm = currentContent.replace(/\r\n/g, '\n');
140
+ if (vNorm !== cNorm) {
141
+ onContentChange(val);
142
+ }
143
+ });
144
+
145
+ return function () {
146
+ TabManager.saveTabViewState(tab.id, editor.saveViewState());
147
+ if (window.__mbeditorActiveEditor === editor) {
148
+ window.__mbeditorActiveEditor = null;
149
+ }
150
+ if (editorPluginDisposable) editorPluginDisposable.dispose();
151
+ contentDisposable.dispose();
152
+ editor.dispose();
153
+ };
154
+ }, [tab.id, tab.isPreview]); // re-run ONLY on tab switch, not on content change (Monaco handles its own content state)
155
+
156
+ // Listen for external content changes (e.g. after Format/Save/Load)
157
+ useEffect(function () {
158
+ var editor = monacoRef.current;
159
+ if (editor) editor._latestContent = tab.content; // update ref for closure
160
+
161
+ if (editor && editor.getValue() !== tab.content) {
162
+ if (typeof tab.content !== 'string') return;
163
+ // Normalize before comparing to prevent false positive dirty edits
164
+ var vNorm = editor.getValue().replace(/\r\n/g, '\n');
165
+ var cNorm = tab.content.replace(/\r\n/g, '\n');
166
+ if (vNorm === cNorm) return;
167
+
168
+ var model = editor.getModel();
169
+ if (model) {
170
+ if (!vNorm) {
171
+ // If the editor is currently completely empty, treat it as an initial load.
172
+ // setValue clears the undo stack which is correct for initial load.
173
+ editor.setValue(tab.content);
174
+ } else {
175
+ // Keep undo stack for formats or replaces by using executeEdits
176
+ editor.pushUndoStop();
177
+ editor.executeEdits("external", [{
178
+ range: model.getFullModelRange(),
179
+ text: tab.content
180
+ }]);
181
+ editor.pushUndoStop();
182
+ }
183
+ }
184
+ }
185
+ }, [tab.content]);
186
+
187
+ // Jump to line if specified
188
+ useEffect(function () {
189
+ if (tab.gotoLine && monacoRef.current) {
190
+ (function () {
191
+ var editor = monacoRef.current;
192
+ setTimeout(function () {
193
+ editor.revealLineInCenter(tab.gotoLine);
194
+ editor.setPosition({ lineNumber: tab.gotoLine, column: 1 });
195
+ editor.focus();
196
+
197
+ TabManager.saveTabViewState(tab.id, editor.saveViewState());
198
+ TabManager.clearGotoLine(paneId, tab.path);
199
+ }, 50);
200
+ })();
201
+ }
202
+ }, [tab.gotoLine, tab.content]); // need tab.content in dep array so if it loads asynchronously, the jump happens AFTER content loads
203
+
204
+ // Apply RuboCop markers
205
+ useEffect(function () {
206
+ if (monacoRef.current && window.monaco) {
207
+ var model = monacoRef.current.getModel();
208
+ if (model) {
209
+ var monacoMarkers = markers.map(function (m) {
210
+ return {
211
+ severity: m.severity === 'error' ? window.monaco.MarkerSeverity.Error : window.monaco.MarkerSeverity.Warning,
212
+ message: m.message,
213
+ startLineNumber: m.startLine,
214
+ startColumn: m.startCol,
215
+ endLineNumber: m.endLine,
216
+ endColumn: m.endCol
217
+ };
218
+ });
219
+ window.monaco.editor.setModelMarkers(model, 'rubocop', monacoMarkers);
220
+ }
221
+ }
222
+ }, [markers, tab.id]);
223
+
224
+ // Reset blame state when file path changes
225
+ useEffect(function () {
226
+ setBlameData(null);
227
+ setIsBlameVisible(false);
228
+ setIsBlameLoading(false);
229
+ }, [tab.path]);
230
+
231
+ // Handle Blame data fetching
232
+ useEffect(function () {
233
+ if (!isBlameVisible) {
234
+ if (monacoRef.current && monacoRef.current.getModel()) {
235
+ blameDecorationsRef.current = monacoRef.current.deltaDecorations(blameDecorationsRef.current, []);
236
+ }
237
+ return;
238
+ }
239
+
240
+ if (!blameData && !isBlameLoading) {
241
+ setIsBlameLoading(true);
242
+ GitService.fetchBlame(tab.path).then(function(data) {
243
+ setBlameData(data.lines || []);
244
+ setIsBlameLoading(false);
245
+ }).catch(function(err) {
246
+ var status = err.response && err.response.status;
247
+ var msg = status === 404
248
+ ? "File is not tracked by git"
249
+ : "Failed to load blame: " + ((err.response && err.response.data && err.response.data.error) || err.message);
250
+ EditorStore.setStatus(msg, "error");
251
+ setIsBlameLoading(false);
252
+ setIsBlameVisible(false);
253
+ });
254
+ }
255
+ }, [isBlameVisible, tab.path, blameData, isBlameLoading]);
256
+
257
+ // Render Blame decorations
258
+ useEffect(function () {
259
+ if (!monacoRef.current || !window.monaco || !isBlameVisible || !blameData) return;
260
+ var editor = monacoRef.current;
261
+
262
+ var newDecorations = blameData.map(function(lineData) {
263
+ var ln = lineData.line;
264
+ var hash = lineData.sha && lineData.sha.substring(0, 8) || '';
265
+ var author = lineData.author || '';
266
+ // Exclude uncommitted changes from noisy blame
267
+ var isUncommitted = hash === '00000000';
268
+ var text = isUncommitted ? 'Not Committed' : author + ' \xB7 ' + hash;
269
+
270
+ return {
271
+ range: new window.monaco.Range(ln, 1, ln, 1),
272
+ options: {
273
+ isWholeLine: false,
274
+ after: {
275
+ content: '\xA0\xA0\xA0\xA0' + text,
276
+ inlineClassName: isUncommitted ? 'ide-blame-annotation-uncommitted' : 'ide-blame-annotation'
277
+ }
278
+ }
279
+ };
280
+ });
281
+
282
+ blameDecorationsRef.current = editor.deltaDecorations(blameDecorationsRef.current, newDecorations);
283
+ }, [blameData, isBlameVisible, tab.id, tab.content]);
284
+
285
+ var sourceTab = tab.isPreview ? findTabByPath(tab.previewFor) : null;
286
+ var markdownContent = tab.isPreview ? sourceTab && sourceTab.content || tab.content || '' : tab.content || '';
287
+
288
+ var sourcePath = tab.isPreview ? tab.previewFor || tab.path : tab.path;
289
+ var parts = sourcePath.split('.');
290
+ var ext = parts.length > 1 ? parts.pop().toLowerCase() : '';
291
+ var isImage = tab.isImage || IMAGE_EXTENSIONS.includes(ext);
292
+ var isMarkdown = ['md', 'markdown'].includes(ext);
293
+
294
+ useEffect(function () {
295
+ if (isMarkdown && window.marked) {
296
+ (function () {
297
+ var renderer = new window.marked.Renderer();
298
+ var escapeHtml = function escapeHtml(str) {
299
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
300
+ };
301
+ renderer.html = function (token) {
302
+ return '<pre>' + escapeHtml(typeof token === 'object' ? token.text : token) + '</pre>';
303
+ };
304
+ setMarkup(window.marked.parse(markdownContent, { renderer: renderer }));
305
+ })();
306
+ }
307
+ }, [markdownContent, isMarkdown]);
308
+
309
+ if (tab.isDiff) {
310
+ var isDiffDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches || true;
311
+ return React.createElement(window.DiffViewer || DiffViewer, {
312
+ path: tab.repoPath || tab.path,
313
+ original: tab.diffOriginal || "",
314
+ modified: tab.diffModified || "",
315
+ isDark: isDiffDark
316
+ });
317
+ }
318
+
319
+ if (tab.isCombinedDiff) {
320
+ return React.createElement(window.CombinedDiffViewer, {
321
+ diffText: tab.combinedDiffText || '',
322
+ label: tab.combinedDiffLabel || 'All Changes',
323
+ isLoading: !tab.combinedDiffText && !tab.combinedDiffLoaded
324
+ });
325
+ }
326
+
327
+ if (isImage) {
328
+ var basePath = (window.MBEDITOR_BASE_PATH || '/mbeditor').replace(/\/$/, '');
329
+ return React.createElement(
330
+ 'div',
331
+ { className: 'monaco-container', style: { display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#1e1e1e' } },
332
+ React.createElement('img', { src: basePath + '/raw?path=' + encodeURIComponent(tab.path), style: { maxWidth: '90%', maxHeight: '90%', objectFit: 'contain' }, alt: tab.name })
333
+ );
334
+ }
335
+
336
+ if (tab.isPreview && isMarkdown) {
337
+ return React.createElement('div', { className: 'markdown-preview markdown-preview-full', dangerouslySetInnerHTML: { __html: markup } });
338
+ }
339
+
340
+ return React.createElement(
341
+ 'div',
342
+ { className: 'ide-editor-wrapper', style: { display: 'flex', flexDirection: 'column', height: '100%' } },
343
+ React.createElement(
344
+ 'div',
345
+ { className: 'ide-editor-toolbar', style: { display: 'flex', justifyContent: 'flex-end', padding: '4px 8px', background: '#252526', borderBottom: '1px solid #3c3c3c' } },
346
+ React.createElement(
347
+ 'button',
348
+ {
349
+ className: 'ide-icon-btn ' + (isBlameVisible ? 'active' : ''),
350
+ disabled: !gitAvailable,
351
+ onClick: gitAvailable ? function() { setIsBlameVisible(!isBlameVisible); } : undefined,
352
+ title: gitAvailable ? 'Toggle Git Blame' : 'Git not available in this workspace',
353
+ style: { fontSize: '12px', padding: '2px 6px', opacity: (gitAvailable && isBlameVisible) ? 1 : 0.6, background: isBlameVisible ? 'rgba(255,255,255,0.1)' : 'transparent', border: 'none', color: gitAvailable ? '#ccc' : '#666', cursor: gitAvailable ? 'pointer' : 'not-allowed', borderRadius: '3px' }
354
+ },
355
+ React.createElement('i', { className: 'fas fa-shoe-prints', style: { marginRight: '6px' } }),
356
+ isBlameLoading ? 'Loading...' : 'Blame'
357
+ )
358
+ ),
359
+ React.createElement('div', { ref: editorRef, className: 'monaco-container', style: { flex: 1, minHeight: 0 } })
360
+ );
361
+ };
362
+
363
+ window.EditorPanel = EditorPanel;
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ var FileHistoryPanel = function FileHistoryPanel(_ref) {
4
+ var path = _ref.path;
5
+ var onSelectCommit = _ref.onSelectCommit;
6
+ var onClose = _ref.onClose;
7
+
8
+ var _React$useState = React.useState([]),
9
+ _React$useState2 = _slicedToArray(_React$useState, 2),
10
+ commits = _React$useState2[0],
11
+ setCommits = _React$useState2[1];
12
+
13
+ var _React$useState3 = React.useState(true),
14
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
15
+ loading = _React$useState4[0],
16
+ setLoading = _React$useState4[1];
17
+
18
+ var _React$useState5 = React.useState(null),
19
+ _React$useState6 = _slicedToArray(_React$useState5, 2),
20
+ error = _React$useState6[0],
21
+ setError = _React$useState6[1];
22
+
23
+ React.useEffect(function () {
24
+ if (!path) return;
25
+ setLoading(true);
26
+ setError(null);
27
+
28
+ GitService.fetchFileHistory(path).then(function (data) {
29
+ setCommits(data.commits || []);
30
+ setLoading(false);
31
+ }).catch(function (err) {
32
+ setError(err.response && err.response.data && err.response.data.error || err.message);
33
+ setLoading(false);
34
+ });
35
+ }, [path]);
36
+
37
+ return React.createElement(
38
+ 'div',
39
+ { className: 'ide-file-history' },
40
+ React.createElement(
41
+ 'div',
42
+ { className: 'ide-file-history-header' },
43
+ React.createElement(
44
+ 'div',
45
+ { className: 'ide-file-history-title' },
46
+ React.createElement('i', { className: 'fas fa-history' }),
47
+ React.createElement(
48
+ 'span',
49
+ null,
50
+ 'History: ',
51
+ path.split('/').pop()
52
+ )
53
+ ),
54
+ React.createElement(
55
+ 'button',
56
+ { className: 'ide-icon-btn', onClick: onClose, title: 'Close History' },
57
+ React.createElement('i', { className: 'fas fa-times' })
58
+ )
59
+ ),
60
+ React.createElement(
61
+ 'div',
62
+ { className: 'ide-file-history-content' },
63
+ loading ? React.createElement(
64
+ 'div',
65
+ { className: 'ide-loading-state' },
66
+ React.createElement('i', { className: 'fas fa-spinner fa-spin' }),
67
+ ' Loading history...'
68
+ ) : error ? React.createElement(
69
+ 'div',
70
+ { className: 'ide-error-state' },
71
+ error
72
+ ) : commits.length === 0 ? React.createElement(
73
+ 'div',
74
+ { className: 'ide-empty-state' },
75
+ 'No history found for this file.'
76
+ ) : React.createElement(
77
+ 'div',
78
+ { className: 'git-list' },
79
+ commits.map(function (commit) {
80
+ var dateObj = new Date(commit.date);
81
+ var dateStr = !isNaN(dateObj) ? dateObj.toLocaleDateString() + ' ' + dateObj.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : commit.date;
82
+ return React.createElement(
83
+ 'div',
84
+ { key: commit.hash, className: 'git-commit-item hoverable', onClick: function () {
85
+ return onSelectCommit && onSelectCommit(commit.hash, path);
86
+ } },
87
+ React.createElement(
88
+ 'div',
89
+ { className: 'git-commit-title', title: commit.title },
90
+ commit.title
91
+ ),
92
+ React.createElement(
93
+ 'div',
94
+ { className: 'git-commit-meta' },
95
+ React.createElement(
96
+ 'span',
97
+ { className: 'commit-hash' },
98
+ commit.hash.slice(0, 7)
99
+ ),
100
+ ' \xB7 ',
101
+ commit.author,
102
+ ' \xB7 ',
103
+ dateStr
104
+ )
105
+ );
106
+ })
107
+ )
108
+ )
109
+ );
110
+ };
111
+
112
+ window.FileHistoryPanel = FileHistoryPanel;