mbeditor 0.13.1 → 0.14.1
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 +4 -4
- data/CHANGELOG.md +132 -0
- data/app/assets/javascripts/mbeditor/application.js +5 -0
- data/app/assets/javascripts/mbeditor/audit_log.js +165 -0
- data/app/assets/javascripts/mbeditor/collaboration_service.js +248 -26
- data/app/assets/javascripts/mbeditor/components/ChangelogView.js +89 -94
- data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +6 -9
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +12 -7
- data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +20 -0
- data/app/assets/javascripts/mbeditor/components/CombinedFileView.js +214 -0
- data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +442 -248
- data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +6 -9
- data/app/assets/javascripts/mbeditor/components/FileTree.js +26 -12
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +3 -0
- data/app/assets/javascripts/mbeditor/components/Gutter.js +51 -0
- data/app/assets/javascripts/mbeditor/components/ImportConflictModal.js +60 -5
- data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1241 -1256
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
- data/app/assets/javascripts/mbeditor/components/PastePlaceModal.js +329 -0
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +47 -65
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +26 -8
- data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
- data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +18 -1
- data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
- data/app/assets/javascripts/mbeditor/editor_plugins.js +705 -323
- data/app/assets/javascripts/mbeditor/file_import.js +13 -15
- data/app/assets/javascripts/mbeditor/file_service.js +48 -39
- data/app/assets/javascripts/mbeditor/git_service.js +15 -1
- data/app/assets/javascripts/mbeditor/history_service.js +5 -13
- data/app/assets/javascripts/mbeditor/search_service.js +9 -0
- data/app/assets/javascripts/mbeditor/tab_manager.js +127 -49
- data/app/assets/javascripts/mbeditor/websocket_service.js +13 -5
- data/app/assets/stylesheets/mbeditor/application.css +6 -1
- data/app/assets/stylesheets/mbeditor/editor.css +672 -249
- data/app/assets/stylesheets/mbeditor/glass.css +174 -0
- data/app/assets/stylesheets/mbeditor/themes.css +93 -33
- data/app/channels/mbeditor/collaboration_channel.rb +17 -4
- data/app/controllers/mbeditor/application_controller.rb +26 -3
- data/app/controllers/mbeditor/editors_controller.rb +124 -439
- data/app/services/mbeditor/archive_service.rb +137 -0
- data/app/services/mbeditor/collaboration_doc_store.rb +143 -14
- data/app/services/mbeditor/editor_state_service.rb +14 -51
- data/app/services/mbeditor/file_history_service.rb +222 -0
- data/app/services/mbeditor/git_info_service.rb +6 -0
- data/app/services/mbeditor/js_syntax_check_service.rb +42 -16
- data/app/services/mbeditor/lint_service.rb +137 -0
- data/app/services/mbeditor/locked_json_file.rb +67 -0
- data/app/services/mbeditor/process_runner.rb +32 -0
- data/app/services/mbeditor/ruby_lsp_result_translator.rb +226 -0
- data/app/services/mbeditor/ruby_method_ranges_service.rb +120 -0
- data/app/services/mbeditor/search_replace_service.rb +8 -0
- data/app/views/layouts/mbeditor/application.html.erb +1 -1
- data/lib/mbeditor/audit_log.rb +203 -0
- data/lib/mbeditor/configuration.rb +6 -1
- data/lib/mbeditor/rack/pending_migration_bypass.rb +31 -10
- data/lib/mbeditor/route_map.rb +5 -0
- data/lib/mbeditor/ruby_lsp_client.rb +82 -14
- data/lib/mbeditor/version.rb +1 -1
- data/lib/mbeditor.rb +1 -0
- data/public/monaco-editor/VERSIONS +1 -1
- data/public/monaco-editor/codicon.ttf +0 -0
- data/public/monaco-editor/css.worker.js +41 -37
- data/public/monaco-editor/editor.worker.js +19 -15
- data/public/monaco-editor/html.worker.js +53 -49
- data/public/monaco-editor/json.worker.js +36 -32
- data/public/monaco-editor/monaco.css +1 -1
- data/public/monaco-editor/monaco.js +388 -301
- data/public/monaco-editor/ts.worker.js +226 -222
- data/vendor/assets/javascripts/axios.min.js +3 -3
- metadata +15 -2
|
@@ -7,100 +7,80 @@
|
|
|
7
7
|
(function() {
|
|
8
8
|
var _React = React;
|
|
9
9
|
var useEffect = _React.useEffect;
|
|
10
|
+
var useState = _React.useState;
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
var parts = text.split(/\*\*([^*]+)\*\*/g);
|
|
18
|
-
return parts.map(function(part, i) {
|
|
19
|
-
return i % 2 === 1
|
|
20
|
-
? React.createElement('strong', { key: i }, part)
|
|
21
|
-
: part;
|
|
22
|
-
});
|
|
23
|
-
}
|
|
12
|
+
var SECTION_ICONS = { Added: 'fas fa-plus-circle', Fixed: 'fas fa-wrench', Changed: 'fas fa-exchange-alt', Removed: 'fas fa-minus-circle', Performance: 'fas fa-bolt', Tests: 'fas fa-vial', Security: 'fas fa-shield-alt' };
|
|
13
|
+
// Shared with EditorPanel.js's markdown preview, which renders untrusted
|
|
14
|
+
// workspace files through the same marked renderer. One definition on
|
|
15
|
+
// purpose: this is the allow-list that keeps `javascript:` out of an href,
|
|
16
|
+
// and a second copy is how one of them silently stops matching the other.
|
|
17
|
+
var SAFE_HREF_SCHEME = /^(https?:|mailto:|#|\/)/i;
|
|
24
18
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
var listBuffer = [];
|
|
29
|
-
var key = 0;
|
|
30
|
-
|
|
31
|
-
function flushList() {
|
|
32
|
-
if (!listBuffer.length) return;
|
|
33
|
-
elements.push(React.createElement(
|
|
34
|
-
'ul', { key: 'ul' + key++, className: 'changelog-list' },
|
|
35
|
-
listBuffer.slice()
|
|
36
|
-
));
|
|
37
|
-
listBuffer = [];
|
|
38
|
-
}
|
|
19
|
+
window.mbeditorEscapeHtml = function escapeHtml(str) {
|
|
20
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
21
|
+
};
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
// H3 — section name, e.g. "### Added"
|
|
67
|
-
if (/^### /.test(line)) {
|
|
68
|
-
flushList();
|
|
69
|
-
var section = line.slice(4).trim();
|
|
70
|
-
var iconMap = { Added: 'fas fa-plus-circle', Fixed: 'fas fa-wrench', Changed: 'fas fa-exchange-alt', Removed: 'fas fa-minus-circle', Performance: 'fas fa-bolt', Tests: 'fas fa-vial', Security: 'fas fa-shield-alt' };
|
|
71
|
-
var iconClass = iconMap[section] || 'fas fa-tag';
|
|
72
|
-
elements.push(React.createElement(
|
|
73
|
-
'div', { key: key++, className: 'changelog-section changelog-section-' + section.toLowerCase() },
|
|
74
|
-
React.createElement('i', { className: iconClass }),
|
|
75
|
-
' ',
|
|
76
|
-
section
|
|
77
|
-
));
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
// Horizontal rule
|
|
81
|
-
if (/^---+$/.test(line.trim())) {
|
|
82
|
-
flushList();
|
|
83
|
-
elements.push(React.createElement('hr', { key: key++, className: 'changelog-rule' }));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
// Bullet list item
|
|
87
|
-
if (/^- /.test(line)) {
|
|
88
|
-
listBuffer.push(React.createElement('li', { key: key++ }, parseInline(line.slice(2).trim())));
|
|
89
|
-
return;
|
|
23
|
+
window.mbeditorSafeHref = function safeHref(href) {
|
|
24
|
+
if (!href) return href;
|
|
25
|
+
return SAFE_HREF_SCHEME.test(href.trim()) ? href : '#';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
var escapeHtml = window.mbeditorEscapeHtml;
|
|
29
|
+
var safeHref = window.mbeditorSafeHref;
|
|
30
|
+
|
|
31
|
+
// Custom marked.Renderer() that layers the changelog's bespoke presentation
|
|
32
|
+
// (version badges on H2, section icons on H3) over marked's own HTML, plus
|
|
33
|
+
// the same link/image href scheme allow-list EditorPanel.js uses. marked's
|
|
34
|
+
// Renderer methods here take positional args (href, title, text) — not the
|
|
35
|
+
// token objects EditorPanel.js's override checks for — so that override is
|
|
36
|
+
// effectively a no-op against the vendored marked build; this one uses the
|
|
37
|
+
// signature marked actually calls.
|
|
38
|
+
function buildRenderer() {
|
|
39
|
+
var renderer = new window.marked.Renderer();
|
|
40
|
+
|
|
41
|
+
renderer.heading = function(text, level) {
|
|
42
|
+
if (level === 2) {
|
|
43
|
+
var vMatch = text.match(/^\[([^\]]+)\](?:\s*-\s*(.+))?/);
|
|
44
|
+
var badge = vMatch ? vMatch[1] : text;
|
|
45
|
+
var date = vMatch && vMatch[2] ? vMatch[2].trim() : '';
|
|
46
|
+
return '<div class="changelog-version-row"><span class="changelog-version-badge">' + badge + '</span>' +
|
|
47
|
+
(date ? '<span class="changelog-version-date">' + date + '</span>' : '') + '</div>\n';
|
|
90
48
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return;
|
|
49
|
+
if (level === 3) {
|
|
50
|
+
var iconClass = SECTION_ICONS[text] || 'fas fa-tag';
|
|
51
|
+
return '<div class="changelog-section changelog-section-' + text.toLowerCase() + '"><i class="' + iconClass + '"></i> ' + text + '</div>\n';
|
|
95
52
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
53
|
+
return '<h1 class="changelog-h1">' + text + '</h1>\n';
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
renderer.list = function(body, ordered, start) {
|
|
57
|
+
var tag = ordered ? 'ol' : 'ul';
|
|
58
|
+
var cls = ordered ? '' : ' class="changelog-list"';
|
|
59
|
+
return '<' + tag + cls + '>\n' + body + '</' + tag + '>\n';
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
renderer.paragraph = function(text) {
|
|
63
|
+
return '<p class="changelog-p">' + text + '</p>\n';
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
renderer.hr = function() {
|
|
67
|
+
return '<hr class="changelog-rule">\n';
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
renderer.html = function(html) {
|
|
71
|
+
return '<pre>' + escapeHtml(html) + '</pre>';
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
var _origLink = renderer.link.bind(renderer);
|
|
75
|
+
var _origImage = renderer.image.bind(renderer);
|
|
76
|
+
renderer.link = function(href, title, text) {
|
|
77
|
+
return _origLink(safeHref(href), title, text);
|
|
78
|
+
};
|
|
79
|
+
renderer.image = function(href, title, text) {
|
|
80
|
+
return _origImage(safeHref(href), title, text);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return renderer;
|
|
104
84
|
}
|
|
105
85
|
|
|
106
86
|
// ---------------------------------------------------------------------------
|
|
@@ -109,11 +89,24 @@
|
|
|
109
89
|
var ChangelogView = function ChangelogView(props) {
|
|
110
90
|
var changelogState = props.changelogState;
|
|
111
91
|
var onLoad = props.onLoad;
|
|
92
|
+
var content = (changelogState && changelogState.content) || '';
|
|
93
|
+
|
|
94
|
+
var _markupState = useState('');
|
|
95
|
+
var markup = _markupState[0];
|
|
96
|
+
var setMarkup = _markupState[1];
|
|
112
97
|
|
|
113
98
|
useEffect(function() {
|
|
114
99
|
if (onLoad) onLoad();
|
|
115
100
|
}, []);
|
|
116
101
|
|
|
102
|
+
useEffect(function() {
|
|
103
|
+
if (!window.marked || !content) {
|
|
104
|
+
setMarkup('');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
setMarkup(window.marked.parse(content, { renderer: buildRenderer() }));
|
|
108
|
+
}, [content]);
|
|
109
|
+
|
|
117
110
|
if (!changelogState || changelogState.loading) {
|
|
118
111
|
return React.createElement(
|
|
119
112
|
'div', { className: 'changelog-loading' },
|
|
@@ -130,14 +123,16 @@
|
|
|
130
123
|
);
|
|
131
124
|
}
|
|
132
125
|
|
|
133
|
-
|
|
126
|
+
if (!window.marked) {
|
|
127
|
+
return React.createElement(
|
|
128
|
+
'div', { className: 'changelog-view' },
|
|
129
|
+
React.createElement('div', { className: 'changelog-body', style: { whiteSpace: 'pre-wrap' } }, content)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
134
132
|
|
|
135
133
|
return React.createElement(
|
|
136
134
|
'div', { className: 'changelog-view' },
|
|
137
|
-
React.createElement(
|
|
138
|
-
'div', { className: 'changelog-body' },
|
|
139
|
-
renderMarkdown(content)
|
|
140
|
-
)
|
|
135
|
+
React.createElement('div', { className: 'changelog-body', dangerouslySetInnerHTML: { __html: markup } })
|
|
141
136
|
);
|
|
142
137
|
};
|
|
143
138
|
|
|
@@ -5,19 +5,16 @@ var CodeReviewPanel = function CodeReviewPanel(_ref) {
|
|
|
5
5
|
var onClose = _ref.onClose;
|
|
6
6
|
|
|
7
7
|
var _React$useState = React.useState(null),
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
setRedmineIssue = _React$useState2[1];
|
|
8
|
+
redmineIssue = _React$useState[0],
|
|
9
|
+
setRedmineIssue = _React$useState[1];
|
|
11
10
|
|
|
12
11
|
var _React$useState3 = React.useState(null),
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
setRedmineError = _React$useState4[1];
|
|
12
|
+
redmineError = _React$useState3[0],
|
|
13
|
+
setRedmineError = _React$useState3[1];
|
|
16
14
|
|
|
17
15
|
var _React$useState5 = React.useState(false),
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
setIsRedmineLoading = _React$useState6[1];
|
|
16
|
+
isRedmineLoading = _React$useState5[0],
|
|
17
|
+
setIsRedmineLoading = _React$useState5[1];
|
|
21
18
|
|
|
22
19
|
var branchIdMatch = gitInfo && gitInfo.branch && gitInfo.branch.match(/(?:feature|bug|hotfix|task)\/(?:#)?(\d+)/i);
|
|
23
20
|
var issueId = branchIdMatch ? branchIdMatch[1] : null;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
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
3
|
|
|
5
4
|
var _React = React;
|
|
6
5
|
var useState = _React.useState;
|
|
@@ -17,13 +16,16 @@ var CollapsibleSection = function CollapsibleSection(_ref) {
|
|
|
17
16
|
var icon = _ref$icon === undefined ? null : _ref$icon;
|
|
18
17
|
var _ref$actions = _ref.actions;
|
|
19
18
|
var actions = _ref$actions === undefined ? null : _ref$actions;
|
|
19
|
+
// A section with nowhere to collapse to. The file tree fills the sidebar and
|
|
20
|
+
// collapsing it leaves an empty panel, so it gets no chevron and no click.
|
|
21
|
+
var _ref$collapsible = _ref.collapsible;
|
|
22
|
+
var collapsible = _ref$collapsible === undefined ? true : _ref$collapsible;
|
|
20
23
|
|
|
21
24
|
var _useState = useState(isCollapsed);
|
|
22
25
|
|
|
23
|
-
var _useState2 = _slicedToArray(_useState, 2);
|
|
24
26
|
|
|
25
|
-
var localCollapsed =
|
|
26
|
-
var setLocalCollapsed =
|
|
27
|
+
var localCollapsed = _useState[0];
|
|
28
|
+
var setLocalCollapsed = _useState[1];
|
|
27
29
|
|
|
28
30
|
// Sync parent isCollapsed prop to local state when it changes
|
|
29
31
|
useEffect(function () {
|
|
@@ -44,8 +46,11 @@ var CollapsibleSection = function CollapsibleSection(_ref) {
|
|
|
44
46
|
{ className: "collapsible-section" },
|
|
45
47
|
React.createElement(
|
|
46
48
|
"div",
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
+
{
|
|
50
|
+
className: "collapsible-header" + (collapsible ? "" : " collapsible-header-static"),
|
|
51
|
+
onClick: collapsible ? toggleCollapsed : undefined
|
|
52
|
+
},
|
|
53
|
+
collapsible && React.createElement("i", { className: "collapsible-toggle fas fa-chevron-" + (localCollapsed ? 'right' : 'down') }),
|
|
49
54
|
icon && React.createElement("i", { className: "collapsible-icon " + icon }),
|
|
50
55
|
React.createElement(
|
|
51
56
|
"span",
|
|
@@ -62,7 +67,7 @@ var CollapsibleSection = function CollapsibleSection(_ref) {
|
|
|
62
67
|
actions
|
|
63
68
|
)
|
|
64
69
|
),
|
|
65
|
-
!localCollapsed && React.createElement(
|
|
70
|
+
(!collapsible || !localCollapsed) && React.createElement(
|
|
66
71
|
"div",
|
|
67
72
|
{ className: "collapsible-content" },
|
|
68
73
|
children
|
|
@@ -32,18 +32,38 @@ var CombinedDiffViewer = function CombinedDiffViewer(_ref) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// Split the raw diff into per-file segments, each starting at "diff --git".
|
|
35
|
+
// Anything before the first such header is not a file diff — it's the
|
|
36
|
+
// explanatory "# ..." text tab_manager.js substitutes when there's no base
|
|
37
|
+
// branch to compare against, or nothing changed — so it must be shown
|
|
38
|
+
// rather than silently dropped, or the tab reads as a blank diff.
|
|
35
39
|
var segments = [];
|
|
36
40
|
var current = null;
|
|
41
|
+
var preamble = [];
|
|
37
42
|
diffText.split('\n').forEach(function (line) {
|
|
38
43
|
if (line.startsWith('diff --git ')) {
|
|
39
44
|
if (current) segments.push(current);
|
|
40
45
|
current = { header: line, lines: [line] };
|
|
41
46
|
} else if (current) {
|
|
42
47
|
current.lines.push(line);
|
|
48
|
+
} else {
|
|
49
|
+
preamble.push(line);
|
|
43
50
|
}
|
|
44
51
|
});
|
|
45
52
|
if (current) segments.push(current);
|
|
46
53
|
|
|
54
|
+
if (segments.length === 0) {
|
|
55
|
+
var message = preamble.join('\n').trim().replace(/^#\s*/, '');
|
|
56
|
+
// Info, not the green tick used for a genuinely empty diff above: this
|
|
57
|
+
// branch also carries "no base branch to compare against", which is a
|
|
58
|
+
// problem to act on rather than a clean result.
|
|
59
|
+
return React.createElement(
|
|
60
|
+
'div',
|
|
61
|
+
{ className: 'combined-diff-viewer combined-diff-empty' },
|
|
62
|
+
React.createElement('i', { className: 'fas fa-info-circle', style: { marginRight: 8 } }),
|
|
63
|
+
message || 'No changes.'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
// Extract a clean display path from the "diff --git a/foo b/foo" header
|
|
48
68
|
function extractPath(headerLine) {
|
|
49
69
|
var m = headerLine.match(/^diff --git a\/(.+) b\/(.+)$/);
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// CombinedFileView — read-only stack of several files on one scrolling page.
|
|
4
|
+
// `paths` are workspace-relative file paths (already capped by the caller);
|
|
5
|
+
// `total` is the pre-cap count so truncation is shown, never silent.
|
|
6
|
+
|
|
7
|
+
(function() {
|
|
8
|
+
var useEffect = React.useEffect;
|
|
9
|
+
var useState = React.useState;
|
|
10
|
+
var CONCURRENCY = 6;
|
|
11
|
+
|
|
12
|
+
function languageFor(path) {
|
|
13
|
+
if (!(window.monaco && monaco.editor && monaco.editor.colorize)) return null;
|
|
14
|
+
var m = /\.[^./]+$/.exec(path);
|
|
15
|
+
if (!m) return null;
|
|
16
|
+
var ext = m[0].toLowerCase();
|
|
17
|
+
var lang = monaco.languages.getLanguages().find(function (l) { return (l.extensions || []).indexOf(ext) !== -1; });
|
|
18
|
+
return lang ? lang.id : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function download(path) {
|
|
22
|
+
var link = document.createElement('a');
|
|
23
|
+
link.href = window.mbeditorBasePath() + '/raw?download=1&path=' + encodeURIComponent(path);
|
|
24
|
+
link.download = path.split('/').pop();
|
|
25
|
+
link.rel = 'noopener';
|
|
26
|
+
document.body.appendChild(link);
|
|
27
|
+
link.click();
|
|
28
|
+
document.body.removeChild(link);
|
|
29
|
+
EditorStore.setStatus('Downloading ' + link.download + '...', 'info');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function copyText(text, okMsg, failMsg) {
|
|
33
|
+
if (!navigator.clipboard) return;
|
|
34
|
+
navigator.clipboard.writeText(text).then(
|
|
35
|
+
function () { EditorStore.setStatus(okMsg, 'info'); },
|
|
36
|
+
function () { EditorStore.setStatus(failMsg, 'error'); }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function indentOf(line) { return /^\s*/.exec(line || '')[0]; }
|
|
41
|
+
|
|
42
|
+
// The selected methods (indexes into info.methods) with the class/module
|
|
43
|
+
// skeleton that holds them, in source order. Pieces that are not adjacent
|
|
44
|
+
// inside the same owner are separated by a `# ...` line.
|
|
45
|
+
function methodSlice(content, info, picked) {
|
|
46
|
+
var lines = content.split('\n');
|
|
47
|
+
var out = [];
|
|
48
|
+
var open = [];
|
|
49
|
+
var prev = null;
|
|
50
|
+
function contains(o, m) { return o.start_line <= m.start_line && o.end_line >= m.end_line; }
|
|
51
|
+
function close(n) {
|
|
52
|
+
while (open.length > n) { var o = open.pop(); out.push(indentOf(lines[o.start_line - 1]) + 'end'); }
|
|
53
|
+
}
|
|
54
|
+
info.methods.filter(function (m, i) { return picked[i]; })
|
|
55
|
+
.sort(function (a, b) { return a.start_line - b.start_line; })
|
|
56
|
+
.forEach(function (m) {
|
|
57
|
+
var chain = info.owners.filter(function (o) { return contains(o, m); });
|
|
58
|
+
var keep = 0;
|
|
59
|
+
while (keep < open.length && keep < chain.length && open[keep] === chain[keep]) keep++;
|
|
60
|
+
close(keep);
|
|
61
|
+
var reopened = open.length < chain.length;
|
|
62
|
+
for (var k = open.length; k < chain.length; k++) { out.push(lines[chain[k].start_line - 1]); open.push(chain[k]); }
|
|
63
|
+
if (prev && !reopened && m.doc_start_line > prev.end_line + 1) out.push(indentOf(lines[m.start_line - 1]) + '# ...');
|
|
64
|
+
else if (prev && !reopened) out.push('');
|
|
65
|
+
out.push.apply(out, lines.slice(m.doc_start_line - 1, m.end_line));
|
|
66
|
+
prev = m;
|
|
67
|
+
});
|
|
68
|
+
close(0);
|
|
69
|
+
return out.join('\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function fenceFor(text) {
|
|
73
|
+
var f = '```';
|
|
74
|
+
while (text.indexOf(f) !== -1) f += '`';
|
|
75
|
+
return f;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// paths: ordered file paths; files: path -> loaded entry; info: path -> {methods, owners};
|
|
79
|
+
// picked: path -> {methodIndex: true}. A file with any picked method exports only those.
|
|
80
|
+
function buildMarkdown(paths, files, info, picked) {
|
|
81
|
+
var parts = [];
|
|
82
|
+
paths.forEach(function (path) {
|
|
83
|
+
var e = files[path];
|
|
84
|
+
if (!e || e.content == null) return;
|
|
85
|
+
var sel = picked[path] || {};
|
|
86
|
+
var body = e.content;
|
|
87
|
+
if (info[path] && info[path].methods && Object.keys(sel).some(function (k) { return sel[k]; })) {
|
|
88
|
+
body = methodSlice(e.content, info[path], sel);
|
|
89
|
+
}
|
|
90
|
+
var fence = fenceFor(body);
|
|
91
|
+
var ext = (/\.([^./]+)$/.exec(path) || [])[1] || '';
|
|
92
|
+
var lang = { rb: 'ruby', js: 'javascript', jsx: 'jsx', ts: 'typescript', tsx: 'tsx', yml: 'yaml', md: 'markdown', sh: 'bash' }[ext] || ext;
|
|
93
|
+
parts.push('## ' + path + '\n\n' + fence + lang + '\n' + body.replace(/\n$/, '') + '\n' + fence + '\n');
|
|
94
|
+
});
|
|
95
|
+
return parts.join('\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function bundleName() {
|
|
99
|
+
return 'mbeditor-bundle-' + new Date().toISOString().replace(/[-:]/g, '').replace(/\..*$/, '').replace('T', '-') + '.md';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function downloadText(text, filename) {
|
|
103
|
+
var objectUrl = URL.createObjectURL(new Blob([text], { type: 'text/markdown' }));
|
|
104
|
+
var link = document.createElement('a');
|
|
105
|
+
link.href = objectUrl;
|
|
106
|
+
link.download = filename;
|
|
107
|
+
link.rel = 'noopener';
|
|
108
|
+
document.body.appendChild(link);
|
|
109
|
+
link.click();
|
|
110
|
+
document.body.removeChild(link);
|
|
111
|
+
// Revoking in the same tick can free the blob before the browser has
|
|
112
|
+
// started reading it, which cancels the download instead of saving it.
|
|
113
|
+
setTimeout(function () { URL.revokeObjectURL(objectUrl); }, 60000);
|
|
114
|
+
EditorStore.setStatus('Downloading ' + filename + '...', 'info');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
var CombinedFileView = function CombinedFileView(props) {
|
|
118
|
+
var paths = props.paths;
|
|
119
|
+
var _s = useState({});
|
|
120
|
+
var files = _s[0];
|
|
121
|
+
var setFiles = _s[1];
|
|
122
|
+
var _m = useState({});
|
|
123
|
+
var info = _m[0];
|
|
124
|
+
var setInfo = _m[1];
|
|
125
|
+
var asked = React.useRef({});
|
|
126
|
+
var _p = useState({});
|
|
127
|
+
var picked = _p[0];
|
|
128
|
+
var setPicked = _p[1];
|
|
129
|
+
|
|
130
|
+
// Method ranges for Ruby files, fetched once each file has loaded. No
|
|
131
|
+
// cancel-on-rerun: every file load re-runs this, and cancelling would drop
|
|
132
|
+
// the requests still in flight. The view is keyed per tab, so paths never
|
|
133
|
+
// change underneath it.
|
|
134
|
+
useEffect(function () {
|
|
135
|
+
paths.forEach(function (path) {
|
|
136
|
+
if (!/\.rb$/.test(path) || !files[path] || files[path].content == null || asked.current[path]) return;
|
|
137
|
+
asked.current[path] = true;
|
|
138
|
+
FileService.getRubyMethods(files[path].content).then(function (r) {
|
|
139
|
+
setInfo(function (prev) { var o = Object.assign({}, prev); o[path] = r.error ? { error: r.error } : r; return o; });
|
|
140
|
+
}, function () {
|
|
141
|
+
setInfo(function (prev) { var o = Object.assign({}, prev); o[path] = { error: 'Could not read methods' }; return o; });
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}, [paths, files]);
|
|
145
|
+
|
|
146
|
+
var toggle = function (path, i) {
|
|
147
|
+
setPicked(function (prev) {
|
|
148
|
+
var o = Object.assign({}, prev);
|
|
149
|
+
var sel = Object.assign({}, o[path] || {});
|
|
150
|
+
if (sel[i]) delete sel[i]; else sel[i] = true;
|
|
151
|
+
o[path] = sel;
|
|
152
|
+
return o;
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
useEffect(function () {
|
|
158
|
+
var cancelled = false;
|
|
159
|
+
var next = 0;
|
|
160
|
+
function put(path, entry) {
|
|
161
|
+
if (!cancelled) setFiles(function (prev) { var o = Object.assign({}, prev); o[path] = entry; return o; });
|
|
162
|
+
}
|
|
163
|
+
function worker() {
|
|
164
|
+
if (cancelled || next >= paths.length) return Promise.resolve();
|
|
165
|
+
var path = paths[next++];
|
|
166
|
+
return FileService.getFile(path).then(function (data) {
|
|
167
|
+
if (data && data.image) { put(path, { note: 'Image file, not shown' }); return; }
|
|
168
|
+
if (!data || typeof data.content !== 'string') { put(path, { note: 'Binary or unreadable file, not shown' }); return; }
|
|
169
|
+
var lang = languageFor(path);
|
|
170
|
+
if (!lang) { put(path, { content: data.content }); return; }
|
|
171
|
+
return monaco.editor.colorize(data.content, lang, { tabSize: 2 }).then(
|
|
172
|
+
function (html) { put(path, { content: data.content, html: html }); },
|
|
173
|
+
function () { put(path, { content: data.content }); }
|
|
174
|
+
);
|
|
175
|
+
})['catch'](function () { put(path, { note: 'Could not load file' }); }).then(worker);
|
|
176
|
+
}
|
|
177
|
+
for (var i = 0; i < CONCURRENCY; i++) worker();
|
|
178
|
+
return function () { cancelled = true; };
|
|
179
|
+
}, [paths]);
|
|
180
|
+
|
|
181
|
+
return React.createElement('div', { className: 'combined-view' },
|
|
182
|
+
React.createElement('div', { className: 'combined-toolbar' },
|
|
183
|
+
React.createElement('button', { type: 'button', className: 'combined-file-btn', onClick: function () { downloadText(buildMarkdown(paths, files, info, picked), bundleName()); } }, 'Download .md'),
|
|
184
|
+
React.createElement('button', { type: 'button', className: 'combined-file-btn', onClick: function () { copyText(buildMarkdown(paths, files, info, picked), 'Copied Markdown bundle', 'Could not copy Markdown bundle'); } }, 'Copy .md')
|
|
185
|
+
),
|
|
186
|
+
props.total > paths.length && React.createElement('div', { className: 'combined-note' },
|
|
187
|
+
'Showing the first ' + paths.length + ' of ' + props.total + ' files.'),
|
|
188
|
+
paths.map(function (path) {
|
|
189
|
+
var e = files[path];
|
|
190
|
+
return React.createElement('section', { key: path, className: 'combined-file' },
|
|
191
|
+
React.createElement('div', { className: 'combined-file-header' },
|
|
192
|
+
React.createElement('span', { className: 'combined-file-path' }, path),
|
|
193
|
+
React.createElement('button', { type: 'button', className: 'combined-file-btn', disabled: !e || e.content == null, onClick: function () { copyText(e.content, 'Copied ' + path, 'Could not copy ' + path); } }, 'Copy'),
|
|
194
|
+
React.createElement('button', { type: 'button', className: 'combined-file-btn', onClick: function () { download(path); } }, 'Download')
|
|
195
|
+
),
|
|
196
|
+
info[path] && info[path].methods && info[path].methods.length > 0 && React.createElement('div', { className: 'combined-methods' },
|
|
197
|
+
info[path].methods.map(function (m, i) {
|
|
198
|
+
var label = (m.owner ? m.owner + (m.singleton ? '.' : '#') : (m.singleton ? '.' : '#')) + m.name;
|
|
199
|
+
return React.createElement('label', { key: i, className: 'combined-method', title: 'lines ' + m.start_line + '-' + m.end_line },
|
|
200
|
+
React.createElement('input', { type: 'checkbox', checked: !!(picked[path] && picked[path][i]), onChange: function () { toggle(path, i); } }),
|
|
201
|
+
label);
|
|
202
|
+
})
|
|
203
|
+
),
|
|
204
|
+
!e ? React.createElement('div', { className: 'combined-note' }, 'Loading...') :
|
|
205
|
+
e.note ? React.createElement('div', { className: 'combined-note' }, e.note) :
|
|
206
|
+
e.html ? React.createElement('pre', { className: 'combined-file-body', dangerouslySetInnerHTML: { __html: e.html } }) :
|
|
207
|
+
React.createElement('pre', { className: 'combined-file-body' }, e.content)
|
|
208
|
+
);
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
window.CombinedFileView = CombinedFileView;
|
|
214
|
+
})();
|
|
@@ -110,7 +110,7 @@ var DiffViewer = function DiffViewer(_ref) {
|
|
|
110
110
|
'js': 'javascript', 'jsx': 'javascript',
|
|
111
111
|
'ts': 'typescript', 'tsx': 'typescript',
|
|
112
112
|
'json': 'json', 'yml': 'yaml', 'yaml': 'yaml',
|
|
113
|
-
'css': 'css', 'scss': '
|
|
113
|
+
'css': 'css', 'scss': 'scss', 'less': 'less', 'sass': 'css',
|
|
114
114
|
'html': 'html', 'erb': 'erb', 'haml': 'haml',
|
|
115
115
|
'xml': 'xml', 'md': 'markdown', 'markdown': 'markdown',
|
|
116
116
|
'sh': 'shell', 'bash': 'shell', 'zsh': 'shell'
|