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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 141c77f551fd195ca9003f28b527f22f29d735fa70e1a1f80f638ad5739edf2d
|
|
4
|
+
data.tar.gz: 479f09a85f6c11c76f014ec85f269d5597bee72eb9e10135e65df275046a4fac
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d1015d5ca772fd35e20c83837ca54d4751ff3b09f0c1978a0d5e55bbb7ba392107e461bb7656ae7ac8158babb55289579afe15d9772f07024229ce6f77b1cb62
|
|
7
|
+
data.tar.gz: 4194a9575055264e59b6f1922bfb7fa225c64242e9de8c406ca43a4c1995397b711204471994651d9b94da6b4ae0a02958f9d48833436af7e5f4dddb083990c9
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.2] - 2026-04-02
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Bumped the gem version for the next tagged release.
|
|
12
|
+
|
|
13
|
+
## [0.2.1] - 2026-04-01
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- **Missing files in restored tabs** — file loads can now opt into a structured missing-file response so restored editor tabs stay stable instead of erroring when a file has been deleted.
|
|
17
|
+
- **Markdown preview restore** — markdown preview tabs are only recreated after a real file load, which avoids blank preview panes for missing files.
|
|
18
|
+
- **Fallback search exclusions** — the non-ripgrep search path now excludes configured nested directories without accidentally excluding unrelated sibling directories with the same basename.
|
|
19
|
+
- **Branch combined diff baseline** — branch-wide combined diffs now prefer the same merge-base style baseline used elsewhere in the git panel instead of relying only on the upstream ref.
|
|
20
|
+
- **PWA assets** — the web manifest is emitted with explicit manifest content, and the service worker now uses install and activate handlers instead of a no-op fetch listener.
|
|
21
|
+
|
|
22
|
+
## [0.1.9] - 2026-03-27
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- **Authentication hook** — new `authenticate_with` configuration option accepts a proc/lambda that runs as a `before_action` in all engine controllers. Use it to plug in the host app's auth system (e.g. Authlogic's `UserSession.find`, Devise's `authenticate_user!`). The proc executes via `instance_exec` so `session`, `cookies`, `redirect_to`, and auth library class methods are all accessible. Default: `nil` (no auth, existing behaviour preserved).
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- **HTTP status code** — all 422 responses now use `:unprocessable_content` (the correct Rails symbol for HTTP 422 since RFC 9110) instead of the deprecated `:unprocessable_entity`.
|
|
29
|
+
- **Search exclusion** — search command construction refactored for clearer exclusion handling.
|
|
30
|
+
|
|
31
|
+
## [0.1.8] - 2026-03-27
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- **Stray engine config** — removed `config/environments/development.rb` from the engine root; environment configs belong only in the dummy app.
|
|
35
|
+
- **CI workflow** — corrected ref format in the GitHub Actions checkout step.
|
|
36
|
+
- **Test reliability** — refactored HTTP stubbing in `RedmineServiceTest` for improved clarity.
|
|
37
|
+
|
|
38
|
+
## [0.1.7] - 2026-03-26
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- **Test results panel** — a dedicated panel shows pass/fail counts, per-test status icons, and error messages after a test run. Inline failure markers overlay the Monaco editor and can be toggled independently of RuboCop markers.
|
|
42
|
+
- **File history context menu** — the tab bar context menu now includes a file history option that opens the per-file commit log.
|
|
43
|
+
|
|
44
|
+
## [0.1.6] - 2026-03-25
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- **Editor preferences** — added a settings tab for theme, font size, font family, tab size, and insert-spaces preferences.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- **Theme support** — Monaco now initializes with the saved editor theme and other user preferences.
|
|
51
|
+
- **Language tooling** — JavaScript and TypeScript use the dedicated worker setup for better editor support.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
- **Search results** — capped workspace search results now surface the cap state in the UI.
|
|
55
|
+
- **File size validation** — the 5 MB file-size limit now applies on write as well as read.
|
|
56
|
+
- **System test teardown** — Cuprite sessions now reset before deleting temporary workspaces, preventing background git requests from hitting removed paths in CI.
|
|
57
|
+
|
|
58
|
+
## [0.1.5] - 2026-03-24
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
- **Shared file icons** — explorer, git panel, tabs, and quick-open now use the same icon mapping.
|
|
62
|
+
- **Quick-open polish** — Ctrl+P shows file icons beside each result and includes a clear button.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
- **Search UX** — the sidebar search now shows a loading spinner while the backend search request is in flight, and it includes a clear button.
|
|
66
|
+
- **Git refresh UX** — the git panel refresh button now spins while refresh data is loading.
|
|
67
|
+
|
|
68
|
+
## [0.1.4] - 2026-03-24
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
- **Blame workspace root** — git blame now resolves paths through the shared workspace-root helper, which keeps dummy-app and auto-detected repo roots consistent.
|
|
72
|
+
- **Heartbeat log noise** — `/ping` requests are silenced at the middleware layer before Rails request logging runs.
|
|
73
|
+
- **Blame presentation** — blame is rendered as grouped headers showing author and commit message above contiguous code blocks.
|
|
74
|
+
|
|
75
|
+
## [0.1.3] - 2026-03-24
|
|
76
|
+
|
|
77
|
+
### Performance
|
|
78
|
+
- **Heartbeat log spam** — `ping` action now uses `Rails.logger.silence`; the frontend switches to a self-rescheduling `setTimeout` (30 s online / 5 s offline) and skips polls entirely while the browser tab is hidden (`document.hidden`).
|
|
79
|
+
- **FileTree re-renders** — `FileTree` is now wrapped in `React.memo` with a data-only comparator. Event handler references (which are re-created on every `MbeditorApp` render) are intentionally ignored, preventing the entire O(n) tree traversal on every keypress, git poll, or status-bar update.
|
|
80
|
+
- **Search index blocking** — `SearchService.buildIndex` is now deferred to idle time via `requestIdleCallback` (with a 50 ms `setTimeout` fallback for Safari). The main thread is no longer blocked during the synchronous MiniSearch rebuild that runs after every project-tree refresh.
|
|
81
|
+
- **Blame decoration churn** — removed `tab.content` from the blame-decoration `useEffect` dependency array in `EditorPanel`. Decorations are derived from `blameData` (fetched once on toggle); re-applying the same decorations via `deltaDecorations` on every keystroke was unnecessary.
|
|
82
|
+
- **EditorStore slice subscriptions** — added `EditorStore.subscribeToSlice(keys, fn)` so future sub-components can subscribe only to the store keys they care about, avoiding re-renders for unrelated state changes.
|
|
83
|
+
|
|
84
|
+
## [0.1.2] - 2026-03-24
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
- **Webfonts 404** — Font Awesome CSS used relative `../webfonts/` paths that resolved to `/webfonts/fa-*` at the host-app root, where no route existed. The vendor stylesheet is now processed by Sprockets ERB so font URLs are rendered as fingerprint-correct `asset_path` calls, and `.ttf` fallback references (which were never bundled) have been removed.
|
|
88
|
+
- **Git compatibility** — `git branch --show-current` is only available in Git ≥ 2.22. All three call sites have been replaced with `git rev-parse --abbrev-ref HEAD` (works on any modern Git), centralised in `GitService.current_branch`. The git panel no longer reports an error on older Git installations.
|
|
89
|
+
- **Slow initial load** — `workspace_root` (when not explicitly configured) now caches the `git rev-parse --show-toplevel` subprocess result at the class level so the subprocess runs at most once per process. `rubocop_available?`, `haml_lint_available?`, and `git_available?` are similarly cached, keyed by their respective configuration values so tests and reconfiguration still get fresh results.
|
|
90
|
+
|
|
91
|
+
## [0.1.1] - 2026-03-24
|
|
92
|
+
|
|
93
|
+
### Changed
|
|
94
|
+
- Bumped the release to avoid republishing the already-pushed 0.1.0 gem and to include the latest GitHub Actions publish workflow updates
|
|
95
|
+
|
|
96
|
+
## [0.1.0] - 2026-03-20
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
- Mountable Rails engine providing a browser-based code editor (Monaco Editor)
|
|
100
|
+
- File explorer with recursive tree, git status badges, inline create/rename/delete
|
|
101
|
+
- Split-pane editor layout with draggable tabs
|
|
102
|
+
- Ctrl+P quick-open file finder (MiniSearch)
|
|
103
|
+
- Full-text workspace search (`rg` with `grep` fallback)
|
|
104
|
+
- Ctrl+S save with dirty-state tracking
|
|
105
|
+
- RuboCop lint and auto-format for Ruby files
|
|
106
|
+
- Haml-Lint support for `.haml` files
|
|
107
|
+
- Prettier auto-format for JS, JSX, JSON, CSS, SCSS, HTML, and Markdown
|
|
108
|
+
- Markdown live preview
|
|
109
|
+
- Git branch/status panel with ahead/behind counts
|
|
110
|
+
- Real-time collaborative editing via Action Cable + Y.js CRDT
|
|
111
|
+
- Remote cursor and selection display during collaboration
|
|
112
|
+
- Configurable `workspace_root`, `allowed_environments`, `excluded_paths`, and `rubocop_command`
|
|
113
|
+
- Path traversal protection — all file access validated within `workspace_root`
|
|
114
|
+
- File size cap (5 MB) on read
|
|
115
|
+
- CSRF guard on all write endpoints via `X-Mbeditor-Client` request header
|
|
116
|
+
- MIT licence
|
data/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# mbeditor
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/mbeditor)
|
|
4
|
+
[](https://github.com/ojnoonan/mbeditor/actions/workflows/test.yml)
|
|
5
|
+
|
|
6
|
+
Mbeditor (Mini Browser Editor) is a mountable Rails engine that adds a browser-based editor UI to a Rails app.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
- Two-pane tabbed editor with drag-to-move tabs
|
|
10
|
+
- File tree and project search
|
|
11
|
+
- Git panel with working tree changes, unpushed file changes, and branch commit titles
|
|
12
|
+
- Optional RuboCop lint and format endpoints (uses host app RuboCop)
|
|
13
|
+
- Optional test runner with inline failure markers and a dedicated results panel (Minitest and RSpec)
|
|
14
|
+
|
|
15
|
+
## Security Warning
|
|
16
|
+
Mbeditor exposes read and write access to your Rails application directory over HTTP. It is intended only for local development.
|
|
17
|
+
|
|
18
|
+
- Never install mbeditor in production or staging.
|
|
19
|
+
- Never run it on infrastructure accessible to untrusted users.
|
|
20
|
+
- Always keep it in the development group in your Gemfile.
|
|
21
|
+
- The engine enforces environment restrictions at runtime, and Gemfile scoping is a second line of defense that keeps the gem out of deploy builds.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
1. Add the gem to the host app Gemfile in development only:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
gem 'mbeditor', group: :development
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. Install dependencies:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bundle install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
3. Mount the engine in host app routes:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
mount Mbeditor::Engine, at: "/mbeditor"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
4. Create `config/initializers/mbeditor.rb` using the configuration options below.
|
|
43
|
+
|
|
44
|
+
5. Boot your app and open `/mbeditor`.
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
Use a single initializer to set the engine options you need:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
Mbeditor.configure do |config|
|
|
52
|
+
config.allowed_environments = [:development]
|
|
53
|
+
# config.workspace_root = Rails.root
|
|
54
|
+
config.excluded_paths = %w[.git tmp log node_modules .bundle coverage vendor/bundle]
|
|
55
|
+
config.rubocop_command = "bundle exec rubocop"
|
|
56
|
+
|
|
57
|
+
# Optional authentication (runs as a before_action in the engine controllers)
|
|
58
|
+
# config.authenticate_with = proc { redirect_to login_path unless UserSession.find }
|
|
59
|
+
|
|
60
|
+
# Optional test runner (Minitest or RSpec)
|
|
61
|
+
# config.test_framework = :minitest # :minitest or :rspec — auto-detected when nil
|
|
62
|
+
# config.test_command = "bundle exec rails test" # defaults to bin/rails test or bundle exec ruby -Itest
|
|
63
|
+
# config.test_timeout = 60
|
|
64
|
+
|
|
65
|
+
# Optional Redmine integration
|
|
66
|
+
# config.redmine_enabled = true
|
|
67
|
+
# config.redmine_url = "https://redmine.example.com/"
|
|
68
|
+
# config.redmine_api_key = "optional_api_key_override"
|
|
69
|
+
# config.redmine_ticket_source = :commit # :commit (scan recent commit messages for #123) or :branch (leading digits of branch name)
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Available options:
|
|
74
|
+
|
|
75
|
+
- `allowed_environments` controls which Rails environments can access the engine. Default: `[:development]`.
|
|
76
|
+
- `authenticate_with` accepts a proc that runs as a `before_action` in all engine controllers. Use it to plug in the host app's authentication. The proc executes via `instance_exec` inside the engine controller, so it has access to `session`, `cookies`, `redirect_to`, and auth library class methods (e.g. Authlogic's `UserSession.find`), but not helper methods defined in the host app's `ApplicationController`. Default: `nil` (no authentication).
|
|
77
|
+
- `workspace_root` sets the root directory exposed by Mbeditor. Default: `Rails.root` from the host app.
|
|
78
|
+
- `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]`.
|
|
79
|
+
- `rubocop_command` sets the command used for inline Ruby linting and formatting. Default: `"rubocop"`.
|
|
80
|
+
- `test_framework` sets the test framework. `:minitest` or `:rspec`. Auto-detected from file suffix, `.rspec`, or `test`/`spec` directory when `nil`. Default: `nil`.
|
|
81
|
+
- `test_command` overrides the full command used to run a test file. When `nil`, the engine picks `bin/rails test` (Minitest) or `bin/rspec` / `bundle exec rspec` (RSpec). Default: `nil`.
|
|
82
|
+
- `test_timeout` sets the maximum seconds a test run may take before being killed. Default: `60`.
|
|
83
|
+
- `redmine_enabled` enables issue lookup integration. Default: `false`.
|
|
84
|
+
- `redmine_url` sets the Redmine base URL. Required when `redmine_enabled` is `true`.
|
|
85
|
+
- `redmine_api_key` sets the Redmine API key. Required when `redmine_enabled` is `true`.
|
|
86
|
+
- `redmine_ticket_source` controls how the current Redmine ticket is identified. `:commit` scans the 100 most recent branch commits for a `#123` reference in the commit message. `:branch` reads the leading digits from the branch name (e.g. `123-my-feature` → ticket 123). Default: `:commit`.
|
|
87
|
+
|
|
88
|
+
## Test Runner
|
|
89
|
+
|
|
90
|
+
The Test button appears in the editor toolbar for any `.rb` file when a `test/` or `spec/` directory exists in the workspace root. Clicking it:
|
|
91
|
+
|
|
92
|
+
1. Resolves the active source file to its matching test file using standard Rails conventions (`app/models/user.rb` → `test/models/user_test.rb`). If the open file is already a test file, it runs that file directly.
|
|
93
|
+
2. Runs the test file using the configured command in a subprocess with a timeout.
|
|
94
|
+
3. Shows a **Test Results** panel with pass/fail counts, per-test status icons, and error messages.
|
|
95
|
+
4. Optionally overlays inline failure markers in the Monaco editor (separate from RuboCop markers — the two never interfere). Use the marker icon in the panel header to toggle them.
|
|
96
|
+
|
|
97
|
+
**Framework auto-detection order:**
|
|
98
|
+
1. File suffix: `_spec.rb` → RSpec, `_test.rb` → Minitest
|
|
99
|
+
2. `.rspec` file present → RSpec
|
|
100
|
+
3. `spec/` directory present → RSpec
|
|
101
|
+
4. `test/` directory present → Minitest
|
|
102
|
+
|
|
103
|
+
**Default commands** (when `test_command` is not set):
|
|
104
|
+
- Minitest: `bin/rails test <file>` if `bin/rails` exists, otherwise `bundle exec ruby -Itest <file>`
|
|
105
|
+
- RSpec: `bin/rspec <file>` if `bin/rspec` exists, otherwise `bundle exec rspec --format json <file>`
|
|
106
|
+
|
|
107
|
+
## Keyboard Shortcuts
|
|
108
|
+
|
|
109
|
+
| Shortcut | Action |
|
|
110
|
+
|----------|--------|
|
|
111
|
+
| `Ctrl+P` | Quick-open file by name |
|
|
112
|
+
| `Ctrl+S` | Save the active file |
|
|
113
|
+
| `Ctrl+Shift+S` | Save all dirty files |
|
|
114
|
+
| `Alt+Shift+F` | Format the active file |
|
|
115
|
+
| `Ctrl+Shift+G` | Toggle the git panel |
|
|
116
|
+
| `Ctrl+Z` / `Ctrl+Y` | Undo / Redo (Monaco built-in) |
|
|
117
|
+
|
|
118
|
+
## Host Requirements (Optional)
|
|
119
|
+
The gem keeps host/tooling responsibilities in the host app:
|
|
120
|
+
- `rubocop` and `rubocop-rails` gems (optional, required for Ruby lint/format endpoints)
|
|
121
|
+
- `haml_lint` gem (optional, required for HAML lint — add to your app's Gemfile if needed)
|
|
122
|
+
- `git` installed in environment (for Git panel data)
|
|
123
|
+
- `minitest` or `rspec` in the host app's bundle (required for the test runner)
|
|
124
|
+
|
|
125
|
+
All lint and test tools are auto-detected at runtime. The engine gracefully disables features if the tools are not available. Neither `rubocop`, `haml_lint`, nor any test framework are runtime dependencies of the gem itself — they are discovered from the host app's environment.
|
|
126
|
+
|
|
127
|
+
### Syntax Highlighting Support
|
|
128
|
+
Monaco runtime assets are served from the engine route namespace (`/mbeditor/monaco-editor/*` and `/mbeditor/monaco_worker.js`).
|
|
129
|
+
The gem includes syntax highlighting for common Rails and React development file types:
|
|
130
|
+
|
|
131
|
+
**Web & Template Languages:**
|
|
132
|
+
- **Ruby** (.rb, Gemfile, gemspec, Rakefile)
|
|
133
|
+
- **HTML**
|
|
134
|
+
- **ERB** (.html.erb, .erb) — Handlebars-based template syntax
|
|
135
|
+
- **HAML** (.haml) — plaintext syntax highlighting (no dedicated HAML grammar in Monaco; haml-lint provides inline error markers when available)
|
|
136
|
+
- **CSS** and **SCSS** stylesheets
|
|
137
|
+
|
|
138
|
+
**JavaScript & React:**
|
|
139
|
+
- **JavaScript** (.js, .jsx)
|
|
140
|
+
- **TypeScript** for JSX with full language server support
|
|
141
|
+
|
|
142
|
+
**Configuration & Documentation:**
|
|
143
|
+
- **YAML** (.yml, .yaml)
|
|
144
|
+
- **Markdown** (.md)
|
|
145
|
+
|
|
146
|
+
These language modules are packaged locally with the gem for true offline operation. No network fallback is needed—all highlighting works without internet connectivity.
|
|
147
|
+
|
|
148
|
+
## Asset Pipeline
|
|
149
|
+
|
|
150
|
+
Mbeditor requires **Sprockets** (`sprockets-rails >= 3.4`). Host apps using **Propshaft** as their asset pipeline are not supported — the engine depends on Sprockets directives to load its JavaScript and CSS assets.
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
A minimal dummy Rails app is included for local development and testing:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
cd test/dummy && rails server
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Then visit http://localhost:3000/mbeditor.
|
|
161
|
+
|
|
162
|
+
## Testing
|
|
163
|
+
|
|
164
|
+
The test suite uses Minitest via the dummy Rails app. Run all tests from the project root:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
bundle exec rake test
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Run a single test file:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
bundle exec ruby -Itest test/controllers/mbeditor/editors_controller_test.rb
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Run a single test by name:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
bundle exec ruby -Itest test/controllers/mbeditor/editors_controller_test.rb -n test_ping_returns_ok
|
|
180
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//= require mbeditor/editor_store
|
|
2
|
+
//= require mbeditor/file_icon
|
|
3
|
+
//= require mbeditor/file_service
|
|
4
|
+
//= require mbeditor/git_service
|
|
5
|
+
//= require mbeditor/search_service
|
|
6
|
+
//= require mbeditor/tab_manager
|
|
7
|
+
//= require mbeditor/editor_plugins
|
|
8
|
+
//= require mbeditor/components/CollapsibleSection
|
|
9
|
+
//= require mbeditor/components/ShortcutHelp
|
|
10
|
+
//= require mbeditor/components/DiffViewer
|
|
11
|
+
//= require mbeditor/components/CombinedDiffViewer
|
|
12
|
+
//= require mbeditor/components/CommitGraph
|
|
13
|
+
//= require mbeditor/components/FileHistoryPanel
|
|
14
|
+
//= require mbeditor/components/TestResultsPanel
|
|
15
|
+
//= require mbeditor/components/CodeReviewPanel
|
|
16
|
+
//= require mbeditor/components/EditorPanel
|
|
17
|
+
//= require mbeditor/components/FileTree
|
|
18
|
+
//= require mbeditor/components/GitPanel
|
|
19
|
+
//= require mbeditor/components/QuickOpenDialog
|
|
20
|
+
//= require mbeditor/components/TabBar
|
|
21
|
+
//= require mbeditor/components/MbeditorApp
|
|
@@ -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;
|