mbeditor 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +116 -0
- data/README.md +180 -0
- data/app/assets/javascripts/mbeditor/application.js +21 -0
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +202 -0
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +71 -0
- data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +139 -0
- data/app/assets/javascripts/mbeditor/components/CommitGraph.js +65 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +166 -0
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +1139 -0
- data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +117 -0
- data/app/assets/javascripts/mbeditor/components/FileTree.js +339 -0
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +501 -0
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +3108 -0
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +272 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +186 -0
- data/app/assets/javascripts/mbeditor/components/TabBar.js +238 -0
- data/app/assets/javascripts/mbeditor/components/TestResultsPanel.js +150 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +758 -0
- data/app/assets/javascripts/mbeditor/editor_store.js +69 -0
- data/app/assets/javascripts/mbeditor/file_icon.js +30 -0
- data/app/assets/javascripts/mbeditor/file_service.js +96 -0
- data/app/assets/javascripts/mbeditor/git_service.js +104 -0
- data/app/assets/javascripts/mbeditor/search_service.js +63 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +485 -0
- data/app/assets/stylesheets/mbeditor/application.css +848 -0
- data/app/assets/stylesheets/mbeditor/editor.css +2061 -0
- data/app/controllers/mbeditor/application_controller.rb +70 -0
- data/app/controllers/mbeditor/editors_controller.rb +996 -0
- data/app/controllers/mbeditor/git_controller.rb +234 -0
- data/app/services/mbeditor/git_blame_service.rb +98 -0
- data/app/services/mbeditor/git_commit_graph_service.rb +60 -0
- data/app/services/mbeditor/git_diff_service.rb +74 -0
- data/app/services/mbeditor/git_file_history_service.rb +42 -0
- data/app/services/mbeditor/git_service.rb +95 -0
- data/app/services/mbeditor/redmine_service.rb +86 -0
- data/app/services/mbeditor/ruby_definition_service.rb +168 -0
- data/app/services/mbeditor/test_runner_service.rb +286 -0
- data/app/views/layouts/mbeditor/application.html.erb +120 -0
- data/app/views/mbeditor/editors/index.html.erb +1 -0
- data/config/initializers/assets.rb +9 -0
- data/config/routes.rb +44 -0
- data/lib/mbeditor/configuration.rb +22 -0
- data/lib/mbeditor/engine.rb +37 -0
- data/lib/mbeditor/rack/silence_ping_request.rb +56 -0
- data/lib/mbeditor/version.rb +3 -0
- data/lib/mbeditor.rb +19 -0
- data/mbeditor.gemspec +31 -0
- data/public/mbeditor-icon.svg +4 -0
- data/public/min-maps/vs/base/worker/workerMain.js.map +1 -0
- data/public/monaco-editor/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- data/public/monaco-editor/vs/base/worker/workerMain.js +31 -0
- data/public/monaco-editor/vs/basic-languages/cameligo/cameligo.js +10 -0
- data/public/monaco-editor/vs/basic-languages/css/css.js +12 -0
- data/public/monaco-editor/vs/basic-languages/dart/dart.js +10 -0
- data/public/monaco-editor/vs/basic-languages/flow9/flow9.js +10 -0
- data/public/monaco-editor/vs/basic-languages/go/go.js +10 -0
- data/public/monaco-editor/vs/basic-languages/handlebars/handlebars.js +440 -0
- data/public/monaco-editor/vs/basic-languages/javascript/javascript.js +10 -0
- data/public/monaco-editor/vs/basic-languages/markdown/markdown.js +10 -0
- data/public/monaco-editor/vs/basic-languages/msdax/msdax.js +10 -0
- data/public/monaco-editor/vs/basic-languages/postiats/postiats.js +10 -0
- data/public/monaco-editor/vs/basic-languages/pug/pug.js +412 -0
- data/public/monaco-editor/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
- data/public/monaco-editor/vs/basic-languages/ruby/ruby.js +10 -0
- data/public/monaco-editor/vs/basic-languages/sb/sb.js +10 -0
- data/public/monaco-editor/vs/basic-languages/shell/shell.js +41 -0
- data/public/monaco-editor/vs/basic-languages/typescript/typescript.js +10 -0
- data/public/monaco-editor/vs/basic-languages/typespec/typespec.js +10 -0
- data/public/monaco-editor/vs/basic-languages/yaml/yaml.js +10 -0
- data/public/monaco-editor/vs/editor/editor.api.js +6 -0
- data/public/monaco-editor/vs/editor/editor.main.css +8 -0
- data/public/monaco-editor/vs/editor/editor.main.js +797 -0
- data/public/monaco-editor/vs/language/typescript/tsMode.js +20 -0
- data/public/monaco-editor/vs/language/typescript/tsWorker.js +51328 -0
- data/public/monaco-editor/vs/loader.js +10 -0
- data/public/monaco-editor/vs/nls.messages.de.js +20 -0
- data/public/monaco-editor/vs/nls.messages.es.js +20 -0
- data/public/monaco-editor/vs/nls.messages.fr.js +18 -0
- data/public/monaco-editor/vs/nls.messages.it.js +18 -0
- data/public/monaco-editor/vs/nls.messages.ja.js +20 -0
- data/public/monaco-editor/vs/nls.messages.ko.js +18 -0
- data/public/monaco-editor/vs/nls.messages.ru.js +20 -0
- data/public/monaco-editor/vs/nls.messages.zh-cn.js +20 -0
- data/public/monaco-editor/vs/nls.messages.zh-tw.js +18 -0
- data/public/monaco_worker.js +5 -0
- data/public/sw.js +8 -0
- data/public/ts_worker.js +5 -0
- data/vendor/assets/javascripts/axios.min.js +5 -0
- data/vendor/assets/javascripts/emmet.js +5452 -0
- data/vendor/assets/javascripts/lodash.min.js +136 -0
- data/vendor/assets/javascripts/marked.min.js +6 -0
- data/vendor/assets/javascripts/minisearch.min.js +2044 -0
- data/vendor/assets/javascripts/monaco-themes-bundle.js +10 -0
- data/vendor/assets/javascripts/monaco-vim.js +9867 -0
- data/vendor/assets/javascripts/prettier-plugin-babel.js +16 -0
- data/vendor/assets/javascripts/prettier-plugin-estree.js +35 -0
- data/vendor/assets/javascripts/prettier-plugin-html.js +19 -0
- data/vendor/assets/javascripts/prettier-plugin-markdown.js +59 -0
- data/vendor/assets/javascripts/prettier-plugin-postcss.js +52 -0
- data/vendor/assets/javascripts/prettier-standalone.js +37 -0
- data/vendor/assets/javascripts/react-dom.min.js +267 -0
- data/vendor/assets/javascripts/react.min.js +31 -0
- data/vendor/assets/stylesheets/fontawesome.min.css.erb +9 -0
- data/vendor/assets/webfonts/fa-brands-400.woff2 +0 -0
- data/vendor/assets/webfonts/fa-regular-400.woff2 +0 -0
- data/vendor/assets/webfonts/fa-solid-900.woff2 +0 -0
- metadata +188 -0
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
React.Fragment,
|
|
39
|
+
null,
|
|
40
|
+
React.createElement('div', { className: 'ide-modal-backdrop', onClick: onClose }),
|
|
41
|
+
React.createElement(
|
|
42
|
+
'div',
|
|
43
|
+
{ className: 'ide-modal-panel' },
|
|
44
|
+
React.createElement(
|
|
45
|
+
'div',
|
|
46
|
+
{ className: 'ide-file-history-header' },
|
|
47
|
+
React.createElement(
|
|
48
|
+
'div',
|
|
49
|
+
{ className: 'ide-file-history-title' },
|
|
50
|
+
React.createElement('i', { className: 'fas fa-history' }),
|
|
51
|
+
React.createElement(
|
|
52
|
+
'span',
|
|
53
|
+
null,
|
|
54
|
+
'History: ',
|
|
55
|
+
path.split('/').pop()
|
|
56
|
+
)
|
|
57
|
+
),
|
|
58
|
+
React.createElement(
|
|
59
|
+
'button',
|
|
60
|
+
{ className: 'ide-icon-btn', onClick: onClose, title: 'Close History' },
|
|
61
|
+
React.createElement('i', { className: 'fas fa-times' })
|
|
62
|
+
)
|
|
63
|
+
),
|
|
64
|
+
React.createElement(
|
|
65
|
+
'div',
|
|
66
|
+
{ className: 'ide-file-history-content' },
|
|
67
|
+
loading ? React.createElement(
|
|
68
|
+
'div',
|
|
69
|
+
{ className: 'ide-loading-state' },
|
|
70
|
+
React.createElement('i', { className: 'fas fa-spinner fa-spin' }),
|
|
71
|
+
' Loading history...'
|
|
72
|
+
) : error ? React.createElement(
|
|
73
|
+
'div',
|
|
74
|
+
{ className: 'ide-error-state' },
|
|
75
|
+
error
|
|
76
|
+
) : commits.length === 0 ? React.createElement(
|
|
77
|
+
'div',
|
|
78
|
+
{ className: 'ide-empty-state' },
|
|
79
|
+
'No history found for this file.'
|
|
80
|
+
) : React.createElement(
|
|
81
|
+
'div',
|
|
82
|
+
{ className: 'git-list' },
|
|
83
|
+
commits.map(function (commit) {
|
|
84
|
+
var dateObj = new Date(commit.date);
|
|
85
|
+
var dateStr = !isNaN(dateObj) ? dateObj.toLocaleDateString() + ' ' + dateObj.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : commit.date;
|
|
86
|
+
return React.createElement(
|
|
87
|
+
'div',
|
|
88
|
+
{ key: commit.hash, className: 'git-commit-item hoverable', onClick: function () {
|
|
89
|
+
return onSelectCommit && onSelectCommit(commit.hash, path);
|
|
90
|
+
} },
|
|
91
|
+
React.createElement(
|
|
92
|
+
'div',
|
|
93
|
+
{ className: 'git-commit-title', title: commit.title },
|
|
94
|
+
commit.title
|
|
95
|
+
),
|
|
96
|
+
React.createElement(
|
|
97
|
+
'div',
|
|
98
|
+
{ className: 'git-commit-meta' },
|
|
99
|
+
React.createElement(
|
|
100
|
+
'span',
|
|
101
|
+
{ className: 'commit-hash' },
|
|
102
|
+
commit.hash.slice(0, 7)
|
|
103
|
+
),
|
|
104
|
+
' \xB7 ',
|
|
105
|
+
commit.author,
|
|
106
|
+
' \xB7 ',
|
|
107
|
+
dateStr
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
})
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
window.FileHistoryPanel = FileHistoryPanel;
|
|
@@ -0,0 +1,339 @@
|
|
|
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
|
+
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }
|
|
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 useRef = _React.useRef;
|
|
12
|
+
var useEffect = _React.useEffect;
|
|
13
|
+
var useMemo = _React.useMemo;
|
|
14
|
+
|
|
15
|
+
var FileTree = function FileTree(_ref) {
|
|
16
|
+
var items = _ref.items;
|
|
17
|
+
var onSelect = _ref.onSelect;
|
|
18
|
+
var activePath = _ref.activePath;
|
|
19
|
+
var selectedPath = _ref.selectedPath;
|
|
20
|
+
var onNodeSelect = _ref.onNodeSelect;
|
|
21
|
+
var gitFiles = _ref.gitFiles;
|
|
22
|
+
var expandedDirs = _ref.expandedDirs;
|
|
23
|
+
var onExpandedDirsChange = _ref.onExpandedDirsChange;
|
|
24
|
+
var onFileDoubleClick = _ref.onFileDoubleClick;
|
|
25
|
+
var onContextMenu = _ref.onContextMenu;
|
|
26
|
+
var pendingCreate = _ref.pendingCreate;
|
|
27
|
+
var onCreateConfirm = _ref.onCreateConfirm;
|
|
28
|
+
var onCreateCancel = _ref.onCreateCancel;
|
|
29
|
+
var pendingRename = _ref.pendingRename;
|
|
30
|
+
var onRenameConfirm = _ref.onRenameConfirm;
|
|
31
|
+
var onRenameCancel = _ref.onRenameCancel;
|
|
32
|
+
|
|
33
|
+
var _useState = useState('');
|
|
34
|
+
|
|
35
|
+
var _useState2 = _slicedToArray(_useState, 2);
|
|
36
|
+
|
|
37
|
+
var inlineValue = _useState2[0];
|
|
38
|
+
var setInlineValue = _useState2[1];
|
|
39
|
+
|
|
40
|
+
var inlineRef = useRef(null);
|
|
41
|
+
var committedRef = useRef(false);
|
|
42
|
+
var containerRef = useRef(null);
|
|
43
|
+
|
|
44
|
+
// Scroll the highlighted node into view when selectedPath changes (e.g. Find in Explorer)
|
|
45
|
+
useEffect(function () {
|
|
46
|
+
if (!selectedPath || !containerRef.current) return;
|
|
47
|
+
var timer = setTimeout(function () {
|
|
48
|
+
var el = containerRef.current && containerRef.current.querySelector('.tree-item.selected');
|
|
49
|
+
if (el) el.scrollIntoView({ block: 'center' });
|
|
50
|
+
}, 60);
|
|
51
|
+
return function () { clearTimeout(timer); };
|
|
52
|
+
}, [selectedPath]);
|
|
53
|
+
|
|
54
|
+
// Auto-reveal: when activePath changes, expand all ancestor dirs and scroll into view
|
|
55
|
+
useEffect(function () {
|
|
56
|
+
if (!activePath) return;
|
|
57
|
+
|
|
58
|
+
// Expand every ancestor directory of the active file
|
|
59
|
+
var parts = activePath.split('/');
|
|
60
|
+
if (parts.length > 1) {
|
|
61
|
+
var ancestors = {};
|
|
62
|
+
for (var i = 1; i < parts.length; i++) {
|
|
63
|
+
ancestors[parts.slice(0, i).join('/')] = true;
|
|
64
|
+
}
|
|
65
|
+
if (onExpandedDirsChange) {
|
|
66
|
+
onExpandedDirsChange(Object.assign({}, expandedDirs || {}, ancestors));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// After the DOM updates, scroll the active item into view
|
|
71
|
+
var timer = setTimeout(function () {
|
|
72
|
+
if (!containerRef.current) return;
|
|
73
|
+
var el = containerRef.current.querySelector('.tree-item.active');
|
|
74
|
+
if (el) el.scrollIntoView({ block: 'center' });
|
|
75
|
+
}, 80);
|
|
76
|
+
return function () { clearTimeout(timer); };
|
|
77
|
+
}, [activePath]);
|
|
78
|
+
|
|
79
|
+
var renameSelectionEnd = function renameSelectionEnd(name) {
|
|
80
|
+
var value = String(name || '');
|
|
81
|
+
var dotIndex = value.indexOf('.');
|
|
82
|
+
return dotIndex > 0 ? dotIndex : value.length;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Auto-focus the inline input whenever pendingCreate is set
|
|
86
|
+
useEffect(function () {
|
|
87
|
+
if (pendingRename) {
|
|
88
|
+
setInlineValue(pendingRename.currentName || '');
|
|
89
|
+
committedRef.current = false;
|
|
90
|
+
setTimeout(function () {
|
|
91
|
+
if (!inlineRef.current) return;
|
|
92
|
+
var input = inlineRef.current;
|
|
93
|
+
input.focus();
|
|
94
|
+
var end = renameSelectionEnd(pendingRename.currentName);
|
|
95
|
+
input.setSelectionRange(0, end);
|
|
96
|
+
}, 0);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (pendingCreate) {
|
|
101
|
+
setInlineValue('');
|
|
102
|
+
committedRef.current = false;
|
|
103
|
+
setTimeout(function () {
|
|
104
|
+
if (inlineRef.current) inlineRef.current.focus();
|
|
105
|
+
}, 0);
|
|
106
|
+
}
|
|
107
|
+
}, [pendingCreate, pendingRename]);
|
|
108
|
+
|
|
109
|
+
var toggleFolder = function toggleFolder(path, e) {
|
|
110
|
+
e.stopPropagation();
|
|
111
|
+
var next = !(expandedDirs && expandedDirs[path]);
|
|
112
|
+
if (onExpandedDirsChange) onExpandedDirsChange(Object.assign({}, expandedDirs || {}, _defineProperty({}, path, next)));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
var selectNode = function selectNode(node) {
|
|
116
|
+
if (!node || !onNodeSelect) return;
|
|
117
|
+
onNodeSelect({ path: node.path, name: node.name, type: node.type });
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Build an O(1) lookup map from gitFiles so large trees don't do O(n) scans per row
|
|
121
|
+
var gitStatusMap = useMemo(function () {
|
|
122
|
+
var map = new Map();
|
|
123
|
+
(gitFiles || []).forEach(function (f) { map.set(f.path, f.status); });
|
|
124
|
+
return map;
|
|
125
|
+
}, [gitFiles]);
|
|
126
|
+
|
|
127
|
+
var getGitStatus = function getGitStatus(path) {
|
|
128
|
+
return gitStatusMap.get(path) || null;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
var getTreeStatusMeta = function getTreeStatusMeta(status) {
|
|
132
|
+
var raw = (status || '').trim();
|
|
133
|
+
if (!raw) return null;
|
|
134
|
+
if (raw === '??') return { badge: 'N', cssKey: 'A', title: 'Untracked (new file)' };
|
|
135
|
+
if (raw.startsWith('R')) return { badge: 'R', cssKey: 'R', title: 'Renamed' };
|
|
136
|
+
|
|
137
|
+
var key = raw.charAt(0);
|
|
138
|
+
var titleMap = {
|
|
139
|
+
M: 'Modified',
|
|
140
|
+
A: 'Added',
|
|
141
|
+
D: 'Deleted',
|
|
142
|
+
U: 'Unmerged'
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return { badge: key || '?', cssKey: key || 'Q', title: titleMap[key] || 'Status' };
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
var handleInlineKeyDown = function handleInlineKeyDown(e) {
|
|
149
|
+
var isRename = !!pendingRename;
|
|
150
|
+
if (e.key === 'Enter') {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
e.stopPropagation();
|
|
153
|
+
committedRef.current = true;
|
|
154
|
+
var rawValue = e && e.currentTarget ? e.currentTarget.value : inlineValue;
|
|
155
|
+
var val = String(rawValue || '').trim();
|
|
156
|
+
if (val) {
|
|
157
|
+
if (isRename && onRenameConfirm) onRenameConfirm(val, pendingRename);
|
|
158
|
+
if (!isRename && onCreateConfirm) onCreateConfirm(val);
|
|
159
|
+
} else {
|
|
160
|
+
if (isRename && onRenameCancel) onRenameCancel();
|
|
161
|
+
if (!isRename && onCreateCancel) onCreateCancel();
|
|
162
|
+
}
|
|
163
|
+
} else if (e.key === 'Escape') {
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
committedRef.current = true;
|
|
167
|
+
if (isRename && onRenameCancel) onRenameCancel();
|
|
168
|
+
if (!isRename && onCreateCancel) onCreateCancel();
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
var handleInlineBlur = function handleInlineBlur() {
|
|
173
|
+
var isRename = !!pendingRename;
|
|
174
|
+
setTimeout(function () {
|
|
175
|
+
if (!committedRef.current) {
|
|
176
|
+
if (isRename && onRenameCancel) onRenameCancel();
|
|
177
|
+
if (!isRename && onCreateCancel) onCreateCancel();
|
|
178
|
+
}
|
|
179
|
+
committedRef.current = false;
|
|
180
|
+
}, 150);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
var renderInlineRow = function renderInlineRow() {
|
|
184
|
+
var isFolder = pendingCreate.type === 'folder';
|
|
185
|
+
var iconClass = isFolder ? 'fas fa-folder tree-folder-icon' : window.getFileIcon(inlineValue || '') + ' tree-file-icon';
|
|
186
|
+
return React.createElement(
|
|
187
|
+
'div',
|
|
188
|
+
{ key: '__inline-create__', className: 'tree-item tree-item-inline-create' },
|
|
189
|
+
React.createElement(
|
|
190
|
+
'div',
|
|
191
|
+
{ className: 'tree-item-icon' },
|
|
192
|
+
React.createElement('i', { className: iconClass })
|
|
193
|
+
),
|
|
194
|
+
React.createElement('input', {
|
|
195
|
+
ref: inlineRef,
|
|
196
|
+
className: 'tree-inline-input',
|
|
197
|
+
type: 'text',
|
|
198
|
+
value: inlineValue,
|
|
199
|
+
onChange: function (e) {
|
|
200
|
+
return setInlineValue(e.target.value);
|
|
201
|
+
},
|
|
202
|
+
onKeyDown: handleInlineKeyDown,
|
|
203
|
+
onBlur: handleInlineBlur,
|
|
204
|
+
autoComplete: 'off',
|
|
205
|
+
spellCheck: false,
|
|
206
|
+
placeholder: isFolder ? 'folder name' : 'file name'
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
var renderInlineRenameRow = function renderInlineRenameRow(node) {
|
|
212
|
+
var isFolder = node.type === 'folder';
|
|
213
|
+
var iconClass = isFolder ? 'fas fa-folder tree-folder-icon' : window.getFileIcon(inlineValue || node.name || '') + ' tree-file-icon';
|
|
214
|
+
return React.createElement(
|
|
215
|
+
'div',
|
|
216
|
+
{ className: 'tree-item tree-item-inline-create' },
|
|
217
|
+
React.createElement(
|
|
218
|
+
'div',
|
|
219
|
+
{ className: 'tree-item-icon' },
|
|
220
|
+
React.createElement('i', { className: iconClass })
|
|
221
|
+
),
|
|
222
|
+
React.createElement('input', {
|
|
223
|
+
ref: inlineRef,
|
|
224
|
+
className: 'tree-inline-input',
|
|
225
|
+
type: 'text',
|
|
226
|
+
value: inlineValue,
|
|
227
|
+
onChange: function (e) {
|
|
228
|
+
return setInlineValue(e.target.value);
|
|
229
|
+
},
|
|
230
|
+
onKeyDown: handleInlineKeyDown,
|
|
231
|
+
onBlur: handleInlineBlur,
|
|
232
|
+
autoComplete: 'off',
|
|
233
|
+
spellCheck: false,
|
|
234
|
+
placeholder: 'name'
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
var renderTree = function renderTree(nodes, folderPath) {
|
|
240
|
+
var sortedNodes = [].concat(_toConsumableArray(nodes)).sort(function (a, b) {
|
|
241
|
+
if (a.type !== b.type) return a.type === "folder" ? -1 : 1;
|
|
242
|
+
return a.name.localeCompare(b.name);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
var rows = sortedNodes.map(function (node) {
|
|
246
|
+
var isFolder = node.type === "folder";
|
|
247
|
+
var isExpanded = !!(expandedDirs && expandedDirs[node.path]);
|
|
248
|
+
var isRenamingThisNode = !!(pendingRename && pendingRename.path === node.path);
|
|
249
|
+
var isOpenFile = activePath === node.path;
|
|
250
|
+
var isSelected = selectedPath === node.path;
|
|
251
|
+
var status = getGitStatus(node.path);
|
|
252
|
+
var statusMeta = getTreeStatusMeta(status);
|
|
253
|
+
var isModified = statusMeta && (statusMeta.cssKey === "M" || statusMeta.cssKey === "A");
|
|
254
|
+
|
|
255
|
+
return React.createElement(
|
|
256
|
+
'div',
|
|
257
|
+
{ key: node.path, className: 'file-tree' },
|
|
258
|
+
isRenamingThisNode ? renderInlineRenameRow(node) : React.createElement(
|
|
259
|
+
'div',
|
|
260
|
+
{
|
|
261
|
+
className: 'tree-item ' + (isOpenFile ? "active" : "") + ' ' + (isSelected ? "selected" : "") + ' ' + (isModified ? "modified" : ""),
|
|
262
|
+
onClick: function (e) {
|
|
263
|
+
selectNode(node);
|
|
264
|
+
if (isFolder) {
|
|
265
|
+
toggleFolder(node.path, e);
|
|
266
|
+
} else {
|
|
267
|
+
onSelect(node.path, node.name);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
onDoubleClick: function (e) {
|
|
271
|
+
if (!isFolder && onFileDoubleClick) {
|
|
272
|
+
e.stopPropagation();
|
|
273
|
+
onFileDoubleClick(node.path, node.name);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
onContextMenu: function (e) {
|
|
277
|
+
e.preventDefault();
|
|
278
|
+
e.stopPropagation();
|
|
279
|
+
selectNode(node);
|
|
280
|
+
if (onContextMenu) onContextMenu(e, node);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
React.createElement(
|
|
284
|
+
'div',
|
|
285
|
+
{ className: 'tree-item-icon' },
|
|
286
|
+
isFolder ? React.createElement('i', { className: 'fas fa-folder' + (isExpanded ? "-open" : "") + ' tree-folder-icon' }) : React.createElement('i', { className: window.getFileIcon(node.name) + ' tree-file-icon' })
|
|
287
|
+
),
|
|
288
|
+
React.createElement(
|
|
289
|
+
'div',
|
|
290
|
+
{ className: 'tree-item-name', title: node.path },
|
|
291
|
+
node.name
|
|
292
|
+
),
|
|
293
|
+
statusMeta && React.createElement(
|
|
294
|
+
'div',
|
|
295
|
+
{ className: 'git-status-badge git-' + statusMeta.cssKey, title: statusMeta.title },
|
|
296
|
+
statusMeta.badge
|
|
297
|
+
)
|
|
298
|
+
),
|
|
299
|
+
isFolder && isExpanded && node.children && React.createElement(
|
|
300
|
+
'div',
|
|
301
|
+
{ style: { paddingLeft: "12px" } },
|
|
302
|
+
renderTree(node.children, node.path)
|
|
303
|
+
)
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Inject inline create row at the end of this directory's list
|
|
308
|
+
if (pendingCreate && pendingCreate.parentPath === folderPath) {
|
|
309
|
+
rows.push(renderInlineRow());
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return rows;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
return React.createElement(
|
|
316
|
+
'div',
|
|
317
|
+
{ className: 'file-tree-root', ref: containerRef },
|
|
318
|
+
renderTree(items, '')
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Wrap FileTree in React.memo with a custom comparator that only checks
|
|
323
|
+
// the data props that affect what's rendered. Function prop references
|
|
324
|
+
// (event handlers) are re-created on every parent render but do not
|
|
325
|
+
// change the visual output, so we intentionally ignore them here.
|
|
326
|
+
// This prevents O(n) tree traversal on every MbeditorApp re-render
|
|
327
|
+
// caused by unrelated state changes (status messages, git polls, etc.).
|
|
328
|
+
var FileTreeMemo = React.memo(FileTree, function(prev, next) {
|
|
329
|
+
return prev.items === next.items &&
|
|
330
|
+
prev.activePath === next.activePath &&
|
|
331
|
+
prev.selectedPath === next.selectedPath &&
|
|
332
|
+
prev.gitFiles === next.gitFiles &&
|
|
333
|
+
prev.expandedDirs === next.expandedDirs &&
|
|
334
|
+
prev.pendingCreate === next.pendingCreate &&
|
|
335
|
+
prev.pendingRename === next.pendingRename;
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Expose globally for sprockets require
|
|
339
|
+
window.FileTree = FileTreeMemo;
|