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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5b86f03d955c56bda4a7ea3d16c1a5081a04caa64719b563665cd0a07bdf0ed1
|
|
4
|
+
data.tar.gz: 51e529ef45dee7c1d39967dba058390f3f5b9c6f40b2cf559e9060709ccfeec6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c5377856d429e5790c17239332cd691dd127eeaca4c4cfdb932955067645fa7e6e3d47a5f79687672430221a19f3e6df5d5007608f54e326f7298d2089cc4bcb
|
|
7
|
+
data.tar.gz: 3302fbf02c588bcd41669abccbc0b809d23e9cb9b9e979b6291beb070cf4c92828379d6adfbc28c5e9d084170ee58f0d1bbf79fa3f6f16985ba5e83b68c0ecef
|
data/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# mbeditor
|
|
2
|
+
|
|
3
|
+
Mbeditor (Mini Browser Editor) is a mountable Rails engine that adds a browser-based editor UI to a Rails app.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Two-pane tabbed editor with drag-to-move tabs
|
|
7
|
+
- File tree and project search
|
|
8
|
+
- Git panel with working tree changes, unpushed file changes, and branch commit titles
|
|
9
|
+
- Optional RuboCop lint and format endpoints (uses host app RuboCop)
|
|
10
|
+
|
|
11
|
+
## Security Warning
|
|
12
|
+
Mbeditor exposes read and write access to your Rails application directory over HTTP. It is intended only for local development.
|
|
13
|
+
|
|
14
|
+
- Never install mbeditor in production or staging.
|
|
15
|
+
- Never run it on infrastructure accessible to untrusted users.
|
|
16
|
+
- Always keep it in the development group in your Gemfile.
|
|
17
|
+
- The engine enforces environment restrictions at runtime, and Gemfile scoping is a second line of defense that keeps the gem out of deploy builds.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
1. Add the gem to the host app Gemfile in development only:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
gem 'mbeditor', group: :development
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2. Install dependencies:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
bundle install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. Mount the engine in host app routes:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
mount Mbeditor::Engine, at: "/mbeditor"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
4. Create `config/initializers/mbeditor.rb` using the configuration options below.
|
|
39
|
+
|
|
40
|
+
5. Boot your app and open `/mbeditor`.
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
Use a single initializer to set the engine options you need:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
MBEditor.configure do |config|
|
|
48
|
+
config.allowed_environments = [:development]
|
|
49
|
+
# config.workspace_root = Rails.root
|
|
50
|
+
config.excluded_paths = %w[.git tmp log node_modules .bundle coverage vendor/bundle]
|
|
51
|
+
config.rubocop_command = "bundle exec rubocop"
|
|
52
|
+
|
|
53
|
+
# Optional Redmine integration
|
|
54
|
+
# config.redmine_enabled = true
|
|
55
|
+
# config.redmine_url = "https://redmine.example.com/"
|
|
56
|
+
# config.redmine_api_key = "optional_api_key_override"
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Available options:
|
|
61
|
+
|
|
62
|
+
- `allowed_environments` controls which Rails environments can access the engine. Default: `[:development]`.
|
|
63
|
+
- `workspace_root` sets the root directory exposed by Mbeditor. Default: `Rails.root` from the host app.
|
|
64
|
+
- `excluded_paths` hides files and directories from the tree and path-based operations. Entries without `/` match names anywhere in the workspace path; entries with `/` match relative paths and their descendants. Default: `%w[.git tmp log node_modules .bundle coverage vendor/bundle]`.
|
|
65
|
+
- `rubocop_command` sets the command used for inline Ruby linting and formatting. Default: `"rubocop"`.
|
|
66
|
+
- `redmine_enabled` enables issue lookup integration. Default: `false`.
|
|
67
|
+
- `redmine_url` sets the Redmine base URL. Required when `redmine_enabled` is `true`.
|
|
68
|
+
- `redmine_api_key` sets the Redmine API key. Required when `redmine_enabled` is `true`.
|
|
69
|
+
|
|
70
|
+
## Host Requirements (Optional)
|
|
71
|
+
The gem keeps host/tooling responsibilities in the host app:
|
|
72
|
+
- `rubocop` and `rubocop-rails` gems (optional, required for Ruby lint/format endpoints)
|
|
73
|
+
- `haml_lint` gem (optional, required for HAML lint — add to your app's Gemfile if needed)
|
|
74
|
+
- `git` installed in environment (for Git panel data)
|
|
75
|
+
|
|
76
|
+
All lint tools are auto-detected at startup. The engine gracefully disables lint features if the tools are not available. Neither `rubocop` nor `haml_lint` are runtime dependencies of the gem itself — they are discovered from the host app's environment.
|
|
77
|
+
|
|
78
|
+
### Syntax Highlighting Support
|
|
79
|
+
Monaco runtime assets are served from the engine route namespace (`/mbeditor/monaco-editor/*` and `/mbeditor/monaco_worker.js`).
|
|
80
|
+
The gem includes syntax highlighting for common Rails and React development file types:
|
|
81
|
+
|
|
82
|
+
**Web & Template Languages:**
|
|
83
|
+
- **Ruby** (.rb, Gemfile, gemspec, Rakefile)
|
|
84
|
+
- **HTML**
|
|
85
|
+
- **ERB** (.html.erb, .erb) — Handlebars-based template syntax
|
|
86
|
+
- **HAML** (.haml) — plaintext syntax highlighting (no dedicated HAML grammar in Monaco; haml-lint provides inline error markers when available)
|
|
87
|
+
- **CSS** and **SCSS** stylesheets
|
|
88
|
+
|
|
89
|
+
**JavaScript & React:**
|
|
90
|
+
- **JavaScript** (.js, .jsx)
|
|
91
|
+
- **TypeScript** for JSX with full language server support
|
|
92
|
+
|
|
93
|
+
**Configuration & Documentation:**
|
|
94
|
+
- **YAML** (.yml, .yaml)
|
|
95
|
+
- **Markdown** (.md)
|
|
96
|
+
|
|
97
|
+
These language modules are packaged locally with the gem for true offline operation. No network fallback is needed—all highlighting works without internet connectivity.
|
|
98
|
+
|
|
99
|
+
## Development
|
|
100
|
+
|
|
101
|
+
A minimal dummy Rails app is included for local development and testing:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cd test/dummy && rails server
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Then visit http://localhost:3000/mbeditor.
|
|
108
|
+
|
|
109
|
+
## Testing
|
|
110
|
+
|
|
111
|
+
The test suite uses Minitest via the dummy Rails app. Run all tests from the project root:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
bundle exec rake test
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Run a single test file:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
bundle exec ruby -Itest test/controllers/mbeditor/editors_controller_test.rb
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Run a single test by name:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
bundle exec ruby -Itest test/controllers/mbeditor/editors_controller_test.rb -n test_ping_returns_ok
|
|
127
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//= require mbeditor/editor_plugins
|
|
2
|
+
//= require mbeditor/components/CollapsibleSection
|
|
3
|
+
//= require mbeditor/components/ShortcutHelp
|
|
4
|
+
//= require mbeditor/components/DiffViewer
|
|
5
|
+
//= require mbeditor/components/CombinedDiffViewer
|
|
6
|
+
//= require mbeditor/components/CommitGraph
|
|
7
|
+
//= require mbeditor/components/FileHistoryPanel
|
|
8
|
+
//= require mbeditor/components/CodeReviewPanel
|
|
9
|
+
//= require mbeditor/components/EditorPanel
|
|
10
|
+
//= require mbeditor/components/FileTree
|
|
11
|
+
//= require mbeditor/components/GitPanel
|
|
12
|
+
//= require mbeditor/components/QuickOpenDialog
|
|
13
|
+
//= require mbeditor/components/TabBar
|
|
14
|
+
//= require mbeditor/components/MbeditorApp
|
|
15
|
+
//= require mbeditor/editor_store
|
|
16
|
+
//= require mbeditor/file_service
|
|
17
|
+
//= require mbeditor/git_service
|
|
18
|
+
//= require mbeditor/search_service
|
|
19
|
+
//= require mbeditor/tab_manager
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var CodeReviewPanel = function CodeReviewPanel(_ref) {
|
|
4
|
+
var gitInfo = _ref.gitInfo;
|
|
5
|
+
var onClose = _ref.onClose;
|
|
6
|
+
|
|
7
|
+
var _React$useState = React.useState(null),
|
|
8
|
+
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
9
|
+
redmineIssue = _React$useState2[0],
|
|
10
|
+
setRedmineIssue = _React$useState2[1];
|
|
11
|
+
|
|
12
|
+
var _React$useState3 = React.useState(null),
|
|
13
|
+
_React$useState4 = _slicedToArray(_React$useState3, 2),
|
|
14
|
+
redmineError = _React$useState4[0],
|
|
15
|
+
setRedmineError = _React$useState4[1];
|
|
16
|
+
|
|
17
|
+
var _React$useState5 = React.useState(false),
|
|
18
|
+
_React$useState6 = _slicedToArray(_React$useState5, 2),
|
|
19
|
+
isRedmineLoading = _React$useState6[0],
|
|
20
|
+
setIsRedmineLoading = _React$useState6[1];
|
|
21
|
+
|
|
22
|
+
var branchIdMatch = gitInfo && gitInfo.branch && gitInfo.branch.match(/(?:feature|bug|hotfix|task)\/(?:#)?(\d+)/i);
|
|
23
|
+
var issueId = branchIdMatch ? branchIdMatch[1] : null;
|
|
24
|
+
|
|
25
|
+
React.useEffect(function () {
|
|
26
|
+
if (!issueId) return;
|
|
27
|
+
setIsRedmineLoading(true);
|
|
28
|
+
|
|
29
|
+
// Call the redmine endpoint. It will 503 if disabled via config.
|
|
30
|
+
axios.get((window.MBEDITOR_BASE_PATH || '/mbeditor' ) + '/redmine/issue/' + issueId)
|
|
31
|
+
.then(function (res) {
|
|
32
|
+
setRedmineIssue(res.data);
|
|
33
|
+
setIsRedmineLoading(false);
|
|
34
|
+
})
|
|
35
|
+
.catch(function (err) {
|
|
36
|
+
if (err.response && err.response.status !== 503) {
|
|
37
|
+
setRedmineError(err.response.data.error || 'Failed to load Redmine issue.');
|
|
38
|
+
}
|
|
39
|
+
setIsRedmineLoading(false);
|
|
40
|
+
});
|
|
41
|
+
}, [issueId]);
|
|
42
|
+
|
|
43
|
+
var handleOpenDiff = function handleOpenDiff(path) {
|
|
44
|
+
var baseSha = gitInfo.upstreamBranch ? gitInfo.upstreamBranch : 'HEAD';
|
|
45
|
+
TabManager.openDiffTab(path, path.split('/').pop(), baseSha, 'WORKING', null);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
var commits = gitInfo && gitInfo.branchCommits || [];
|
|
49
|
+
var unpushed = gitInfo && gitInfo.unpushedFiles || [];
|
|
50
|
+
|
|
51
|
+
return React.createElement(
|
|
52
|
+
'div',
|
|
53
|
+
{ className: 'ide-code-review' },
|
|
54
|
+
React.createElement(
|
|
55
|
+
'div',
|
|
56
|
+
{ className: 'ide-code-review-header' },
|
|
57
|
+
React.createElement(
|
|
58
|
+
'div',
|
|
59
|
+
{ className: 'ide-code-review-title' },
|
|
60
|
+
React.createElement('i', { className: 'fas fa-clipboard-check' }),
|
|
61
|
+
' Code Review: ',
|
|
62
|
+
gitInfo ? gitInfo.branch : 'Unknown Branch'
|
|
63
|
+
),
|
|
64
|
+
React.createElement(
|
|
65
|
+
'button',
|
|
66
|
+
{ className: 'ide-icon-btn', onClick: onClose, title: 'Close Review' },
|
|
67
|
+
React.createElement('i', { className: 'fas fa-times' })
|
|
68
|
+
)
|
|
69
|
+
),
|
|
70
|
+
React.createElement(
|
|
71
|
+
'div',
|
|
72
|
+
{ className: 'ide-code-review-content' },
|
|
73
|
+
|
|
74
|
+
// Redmine Issue Section
|
|
75
|
+
issueId && React.createElement(
|
|
76
|
+
'div',
|
|
77
|
+
{ className: 'review-section' },
|
|
78
|
+
React.createElement(
|
|
79
|
+
'h3',
|
|
80
|
+
{ className: 'review-section-title' },
|
|
81
|
+
'Redmine Issue #',
|
|
82
|
+
issueId
|
|
83
|
+
),
|
|
84
|
+
isRedmineLoading ? React.createElement(
|
|
85
|
+
'div',
|
|
86
|
+
{ className: 'ide-loading-state' },
|
|
87
|
+
'Loading issue details...'
|
|
88
|
+
) : redmineError ? React.createElement(
|
|
89
|
+
'div',
|
|
90
|
+
{ className: 'ide-error-state' },
|
|
91
|
+
redmineError
|
|
92
|
+
) : redmineIssue ? React.createElement(
|
|
93
|
+
'div',
|
|
94
|
+
{ className: 'redmine-card' },
|
|
95
|
+
React.createElement(
|
|
96
|
+
'div',
|
|
97
|
+
{ className: 'redmine-card-header' },
|
|
98
|
+
React.createElement('strong', null, redmineIssue.title),
|
|
99
|
+
React.createElement(
|
|
100
|
+
'span',
|
|
101
|
+
{ className: 'redmine-badge' },
|
|
102
|
+
redmineIssue.status
|
|
103
|
+
)
|
|
104
|
+
),
|
|
105
|
+
React.createElement(
|
|
106
|
+
'div',
|
|
107
|
+
{ className: 'redmine-meta' },
|
|
108
|
+
'Assigned to: ',
|
|
109
|
+
redmineIssue.author
|
|
110
|
+
),
|
|
111
|
+
React.createElement(
|
|
112
|
+
'div',
|
|
113
|
+
{ className: 'redmine-desc' },
|
|
114
|
+
redmineIssue.description
|
|
115
|
+
)
|
|
116
|
+
) : null
|
|
117
|
+
),
|
|
118
|
+
|
|
119
|
+
// Files to Review Section
|
|
120
|
+
React.createElement(
|
|
121
|
+
'div',
|
|
122
|
+
{ className: 'review-section' },
|
|
123
|
+
React.createElement(
|
|
124
|
+
'h3',
|
|
125
|
+
{ className: 'review-section-title' },
|
|
126
|
+
'Files Altered in Branch (',
|
|
127
|
+
unpushed.length,
|
|
128
|
+
')'
|
|
129
|
+
),
|
|
130
|
+
unpushed.length === 0 ? React.createElement(
|
|
131
|
+
'div',
|
|
132
|
+
{ className: 'ide-empty-state' },
|
|
133
|
+
'No unpushed files to review.'
|
|
134
|
+
) : React.createElement(
|
|
135
|
+
'div',
|
|
136
|
+
{ className: 'git-list' },
|
|
137
|
+
unpushed.map(function (item) {
|
|
138
|
+
return React.createElement(
|
|
139
|
+
'div',
|
|
140
|
+
{ key: item.path, className: 'git-commit-item hoverable', onClick: function () {
|
|
141
|
+
return handleOpenDiff(item.path);
|
|
142
|
+
} },
|
|
143
|
+
React.createElement(
|
|
144
|
+
'div',
|
|
145
|
+
{ className: 'git-commit-title' },
|
|
146
|
+
item.path
|
|
147
|
+
),
|
|
148
|
+
React.createElement(
|
|
149
|
+
'div',
|
|
150
|
+
{ className: 'git-commit-meta' },
|
|
151
|
+
'Status: ',
|
|
152
|
+
item.status,
|
|
153
|
+
' \xB7 Click to view full diff against base branch'
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
})
|
|
157
|
+
)
|
|
158
|
+
),
|
|
159
|
+
|
|
160
|
+
// Commits Section
|
|
161
|
+
React.createElement(
|
|
162
|
+
'div',
|
|
163
|
+
{ className: 'review-section' },
|
|
164
|
+
React.createElement(
|
|
165
|
+
'h3',
|
|
166
|
+
{ className: 'review-section-title' },
|
|
167
|
+
'Branch Commits (',
|
|
168
|
+
commits.length,
|
|
169
|
+
')'
|
|
170
|
+
),
|
|
171
|
+
commits.length === 0 ? React.createElement(
|
|
172
|
+
'div',
|
|
173
|
+
{ className: 'ide-empty-state' },
|
|
174
|
+
'No local commits.'
|
|
175
|
+
) : React.createElement(
|
|
176
|
+
'div',
|
|
177
|
+
{ className: 'git-list' },
|
|
178
|
+
commits.map(function (commit) {
|
|
179
|
+
return React.createElement(
|
|
180
|
+
'div',
|
|
181
|
+
{ key: commit.hash, className: 'git-commit-item' },
|
|
182
|
+
React.createElement(
|
|
183
|
+
'div',
|
|
184
|
+
{ className: 'git-commit-title' },
|
|
185
|
+
commit.title
|
|
186
|
+
),
|
|
187
|
+
React.createElement(
|
|
188
|
+
'div',
|
|
189
|
+
{ className: 'git-commit-meta' },
|
|
190
|
+
commit.hash.slice(0, 7),
|
|
191
|
+
' \xB7 ',
|
|
192
|
+
commit.author
|
|
193
|
+
)
|
|
194
|
+
);
|
|
195
|
+
})
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
window.CodeReviewPanel = CodeReviewPanel;
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
|
|
9
|
+
var CollapsibleSection = function CollapsibleSection(_ref) {
|
|
10
|
+
var title = _ref.title;
|
|
11
|
+
var children = _ref.children;
|
|
12
|
+
var _ref$isCollapsed = _ref.isCollapsed;
|
|
13
|
+
var isCollapsed = _ref$isCollapsed === undefined ? false : _ref$isCollapsed;
|
|
14
|
+
var _ref$onToggle = _ref.onToggle;
|
|
15
|
+
var onToggle = _ref$onToggle === undefined ? null : _ref$onToggle;
|
|
16
|
+
var _ref$icon = _ref.icon;
|
|
17
|
+
var icon = _ref$icon === undefined ? null : _ref$icon;
|
|
18
|
+
var _ref$actions = _ref.actions;
|
|
19
|
+
var actions = _ref$actions === undefined ? null : _ref$actions;
|
|
20
|
+
|
|
21
|
+
var _useState = useState(isCollapsed);
|
|
22
|
+
|
|
23
|
+
var _useState2 = _slicedToArray(_useState, 2);
|
|
24
|
+
|
|
25
|
+
var localCollapsed = _useState2[0];
|
|
26
|
+
var setLocalCollapsed = _useState2[1];
|
|
27
|
+
|
|
28
|
+
// Sync parent isCollapsed prop to local state when it changes
|
|
29
|
+
useEffect(function () {
|
|
30
|
+
setLocalCollapsed(isCollapsed);
|
|
31
|
+
}, [isCollapsed]);
|
|
32
|
+
|
|
33
|
+
var toggleCollapsed = function toggleCollapsed(e) {
|
|
34
|
+
e.stopPropagation();
|
|
35
|
+
var newState = !localCollapsed;
|
|
36
|
+
setLocalCollapsed(newState);
|
|
37
|
+
if (onToggle) {
|
|
38
|
+
onToggle(newState);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return React.createElement(
|
|
43
|
+
"div",
|
|
44
|
+
{ className: "collapsible-section" },
|
|
45
|
+
React.createElement(
|
|
46
|
+
"div",
|
|
47
|
+
{ className: "collapsible-header", onClick: toggleCollapsed },
|
|
48
|
+
React.createElement("i", { className: "collapsible-toggle fas fa-chevron-" + (localCollapsed ? 'right' : 'down') }),
|
|
49
|
+
icon && React.createElement("i", { className: "collapsible-icon " + icon }),
|
|
50
|
+
React.createElement(
|
|
51
|
+
"span",
|
|
52
|
+
{ className: "collapsible-title" },
|
|
53
|
+
title
|
|
54
|
+
),
|
|
55
|
+
actions && React.createElement(
|
|
56
|
+
"div",
|
|
57
|
+
{ className: "collapsible-actions", onClick: function (e) {
|
|
58
|
+
return e.stopPropagation();
|
|
59
|
+
} },
|
|
60
|
+
actions
|
|
61
|
+
)
|
|
62
|
+
),
|
|
63
|
+
!localCollapsed && React.createElement(
|
|
64
|
+
"div",
|
|
65
|
+
{ className: "collapsible-content" },
|
|
66
|
+
children
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
window.CollapsibleSection = CollapsibleSection;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Renders the raw unified diff text produced by `git diff` for multiple files.
|
|
4
|
+
// Each line is colored (green = addition, red = deletion, blue = hunk header,
|
|
5
|
+
// dimmed = file header) giving a VS Code "Open Changes" style read-only view.
|
|
6
|
+
var CombinedDiffViewer = function CombinedDiffViewer(_ref) {
|
|
7
|
+
var diffText = _ref.diffText;
|
|
8
|
+
var label = _ref.label;
|
|
9
|
+
var isLoading = _ref.isLoading;
|
|
10
|
+
|
|
11
|
+
var containerRef = React.useRef(null);
|
|
12
|
+
|
|
13
|
+
// Track which file sections are collapsed. Key = "diff --git …" header line.
|
|
14
|
+
var _us = React.useState({});
|
|
15
|
+
var collapsed = _us[0]; var setCollapsed = _us[1];
|
|
16
|
+
|
|
17
|
+
if (isLoading) {
|
|
18
|
+
return React.createElement(
|
|
19
|
+
'div',
|
|
20
|
+
{ className: 'combined-diff-viewer combined-diff-loading-wrap' },
|
|
21
|
+
React.createElement('span', { className: 'combined-diff-loading-msg' }, 'Loading\u2026')
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!diffText || !diffText.trim()) {
|
|
26
|
+
return React.createElement(
|
|
27
|
+
'div',
|
|
28
|
+
{ className: 'combined-diff-viewer combined-diff-empty' },
|
|
29
|
+
React.createElement('i', { className: 'fas fa-check-circle', style: { marginRight: 8, color: '#89d185' } }),
|
|
30
|
+
'No changes.'
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Split the raw diff into per-file segments, each starting at "diff --git".
|
|
35
|
+
var segments = [];
|
|
36
|
+
var current = null;
|
|
37
|
+
diffText.split('\n').forEach(function (line) {
|
|
38
|
+
if (line.startsWith('diff --git ')) {
|
|
39
|
+
if (current) segments.push(current);
|
|
40
|
+
current = { header: line, lines: [line] };
|
|
41
|
+
} else if (current) {
|
|
42
|
+
current.lines.push(line);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
if (current) segments.push(current);
|
|
46
|
+
|
|
47
|
+
// Extract a clean display path from the "diff --git a/foo b/foo" header
|
|
48
|
+
function extractPath(headerLine) {
|
|
49
|
+
var m = headerLine.match(/^diff --git a\/(.+) b\/(.+)$/);
|
|
50
|
+
return m ? m[2] : headerLine.replace('diff --git ', '');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function renderSegment(seg, si) {
|
|
54
|
+
var filePath = extractPath(seg.header);
|
|
55
|
+
var parts = filePath.split('/');
|
|
56
|
+
var fileName = parts.pop();
|
|
57
|
+
var fileDir = parts.join('/');
|
|
58
|
+
|
|
59
|
+
var isCollapsed = !!collapsed[si];
|
|
60
|
+
|
|
61
|
+
var lineNodes = [];
|
|
62
|
+
var inHunk = false;
|
|
63
|
+
seg.lines.forEach(function (line, li) {
|
|
64
|
+
// Skip the boring "diff --git / index / --- / +++" boilerplate at the top
|
|
65
|
+
if (li === 0) return; // "diff --git" — shown in header
|
|
66
|
+
if (line.startsWith('index ') || line.startsWith('old mode') || line.startsWith('new mode') || line.startsWith('new file') || line.startsWith('deleted file') || line.startsWith('Binary') || line.startsWith('rename ')) {
|
|
67
|
+
lineNodes.push(React.createElement('div', { key: li, className: 'cdiff-line cdiff-meta' }, line));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (line.startsWith('--- ') || line.startsWith('+++ ')) {
|
|
71
|
+
lineNodes.push(React.createElement('div', { key: li, className: 'cdiff-line cdiff-file-header' }, line));
|
|
72
|
+
inHunk = false;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (line.startsWith('@@')) {
|
|
76
|
+
inHunk = true;
|
|
77
|
+
lineNodes.push(React.createElement('div', { key: li, className: 'cdiff-line cdiff-hunk' }, line));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!inHunk) return;
|
|
81
|
+
var cls = 'cdiff-line';
|
|
82
|
+
if (line.startsWith('+')) cls += ' cdiff-add';
|
|
83
|
+
else if (line.startsWith('-')) cls += ' cdiff-del';
|
|
84
|
+
else cls += ' cdiff-ctx';
|
|
85
|
+
lineNodes.push(React.createElement('div', { key: li, className: cls }, line || '\u00a0'));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return React.createElement(
|
|
89
|
+
'div',
|
|
90
|
+
{ key: si, className: 'cdiff-file-segment' },
|
|
91
|
+
// File header bar
|
|
92
|
+
React.createElement(
|
|
93
|
+
'div',
|
|
94
|
+
{
|
|
95
|
+
className: 'cdiff-file-bar',
|
|
96
|
+
onClick: function () {
|
|
97
|
+
setCollapsed(function (prev) {
|
|
98
|
+
var next = Object.assign({}, prev);
|
|
99
|
+
next[si] = !prev[si];
|
|
100
|
+
return next;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
React.createElement('i', {
|
|
105
|
+
className: 'fas ' + (isCollapsed ? 'fa-chevron-right' : 'fa-chevron-down') + ' cdiff-chevron'
|
|
106
|
+
}),
|
|
107
|
+
React.createElement('i', { className: 'fas fa-file-code cdiff-file-icon' }),
|
|
108
|
+
React.createElement('span', { className: 'cdiff-file-name' }, fileName),
|
|
109
|
+
fileDir ? React.createElement('span', { className: 'cdiff-file-dir' }, fileDir) : null
|
|
110
|
+
),
|
|
111
|
+
!isCollapsed && React.createElement(
|
|
112
|
+
'div',
|
|
113
|
+
{ className: 'cdiff-file-body' },
|
|
114
|
+
lineNodes
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return React.createElement(
|
|
120
|
+
'div',
|
|
121
|
+
{ className: 'combined-diff-viewer' },
|
|
122
|
+
// Toolbar
|
|
123
|
+
React.createElement(
|
|
124
|
+
'div',
|
|
125
|
+
{ className: 'combined-diff-toolbar' },
|
|
126
|
+
React.createElement('i', { className: 'fas fa-exchange-alt', style: { color: '#569cd6', marginRight: 8 } }),
|
|
127
|
+
React.createElement('span', { className: 'combined-diff-label' }, label || 'All Changes'),
|
|
128
|
+
React.createElement('span', { className: 'combined-diff-file-count' }, segments.length + ' file' + (segments.length === 1 ? '' : 's'))
|
|
129
|
+
),
|
|
130
|
+
// Body
|
|
131
|
+
React.createElement(
|
|
132
|
+
'div',
|
|
133
|
+
{ className: 'combined-diff-body', ref: containerRef },
|
|
134
|
+
segments.map(function (seg, si) { return renderSegment(seg, si); })
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
window.CombinedDiffViewer = CombinedDiffViewer;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var CommitGraph = function CommitGraph(_ref) {
|
|
4
|
+
var commits = _ref.commits;
|
|
5
|
+
var onSelectCommit = _ref.onSelectCommit;
|
|
6
|
+
|
|
7
|
+
if (!commits || commits.length === 0) {
|
|
8
|
+
return React.createElement(
|
|
9
|
+
'div',
|
|
10
|
+
{ className: 'git-empty' },
|
|
11
|
+
'No commit history available.'
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Very simplified graph rendering for a single branch view.
|
|
16
|
+
// Full cross-branch line rendering in a web UI is complex;
|
|
17
|
+
// here we render a vertical line with dots for each commit.
|
|
18
|
+
return React.createElement(
|
|
19
|
+
'div',
|
|
20
|
+
{ className: 'ide-commit-graph' },
|
|
21
|
+
commits.map(function (commit, idx) {
|
|
22
|
+
var isFirst = idx === 0;
|
|
23
|
+
var isLast = idx === commits.length - 1;
|
|
24
|
+
var dateObj = new Date(commit.date);
|
|
25
|
+
var dateStr = !isNaN(dateObj) ? dateObj.toLocaleString() : commit.date;
|
|
26
|
+
var isLocal = commit.isLocal;
|
|
27
|
+
|
|
28
|
+
return React.createElement(
|
|
29
|
+
'div',
|
|
30
|
+
{
|
|
31
|
+
key: commit.hash,
|
|
32
|
+
className: 'commit-row ' + (isLocal ? 'commit-local' : ''),
|
|
33
|
+
onClick: function () { return onSelectCommit && onSelectCommit(commit); }
|
|
34
|
+
},
|
|
35
|
+
React.createElement(
|
|
36
|
+
'div',
|
|
37
|
+
{ className: 'commit-graph-col' },
|
|
38
|
+
!isFirst && React.createElement('div', { className: 'commit-line-top' }),
|
|
39
|
+
React.createElement('div', { className: 'commit-dot ' + (isLocal ? 'dot-local' : 'dot-pushed') }),
|
|
40
|
+
!isLast && React.createElement('div', { className: 'commit-line-bottom' })
|
|
41
|
+
),
|
|
42
|
+
React.createElement(
|
|
43
|
+
'div',
|
|
44
|
+
{ className: 'commit-info-col' },
|
|
45
|
+
React.createElement(
|
|
46
|
+
'div',
|
|
47
|
+
{ className: 'commit-title', title: commit.title },
|
|
48
|
+
commit.title
|
|
49
|
+
),
|
|
50
|
+
React.createElement(
|
|
51
|
+
'div',
|
|
52
|
+
{ className: 'commit-meta' },
|
|
53
|
+
React.createElement('span', { className: 'commit-hash' }, commit.hash.slice(0, 7)),
|
|
54
|
+
' \xB7 ',
|
|
55
|
+
commit.author,
|
|
56
|
+
' \xB7 ',
|
|
57
|
+
dateStr
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
window.CommitGraph = CommitGraph;
|