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
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;