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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +116 -0
  3. data/README.md +180 -0
  4. data/app/assets/javascripts/mbeditor/application.js +21 -0
  5. data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +202 -0
  6. data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +71 -0
  7. data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +139 -0
  8. data/app/assets/javascripts/mbeditor/components/CommitGraph.js +65 -0
  9. data/app/assets/javascripts/mbeditor/components/DiffViewer.js +166 -0
  10. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +1139 -0
  11. data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +117 -0
  12. data/app/assets/javascripts/mbeditor/components/FileTree.js +339 -0
  13. data/app/assets/javascripts/mbeditor/components/GitPanel.js +501 -0
  14. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +3108 -0
  15. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +272 -0
  16. data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +186 -0
  17. data/app/assets/javascripts/mbeditor/components/TabBar.js +238 -0
  18. data/app/assets/javascripts/mbeditor/components/TestResultsPanel.js +150 -0
  19. data/app/assets/javascripts/mbeditor/editor_plugins.js +758 -0
  20. data/app/assets/javascripts/mbeditor/editor_store.js +69 -0
  21. data/app/assets/javascripts/mbeditor/file_icon.js +30 -0
  22. data/app/assets/javascripts/mbeditor/file_service.js +96 -0
  23. data/app/assets/javascripts/mbeditor/git_service.js +104 -0
  24. data/app/assets/javascripts/mbeditor/search_service.js +63 -0
  25. data/app/assets/javascripts/mbeditor/tab_manager.js +485 -0
  26. data/app/assets/stylesheets/mbeditor/application.css +848 -0
  27. data/app/assets/stylesheets/mbeditor/editor.css +2061 -0
  28. data/app/controllers/mbeditor/application_controller.rb +70 -0
  29. data/app/controllers/mbeditor/editors_controller.rb +996 -0
  30. data/app/controllers/mbeditor/git_controller.rb +234 -0
  31. data/app/services/mbeditor/git_blame_service.rb +98 -0
  32. data/app/services/mbeditor/git_commit_graph_service.rb +60 -0
  33. data/app/services/mbeditor/git_diff_service.rb +74 -0
  34. data/app/services/mbeditor/git_file_history_service.rb +42 -0
  35. data/app/services/mbeditor/git_service.rb +95 -0
  36. data/app/services/mbeditor/redmine_service.rb +86 -0
  37. data/app/services/mbeditor/ruby_definition_service.rb +168 -0
  38. data/app/services/mbeditor/test_runner_service.rb +286 -0
  39. data/app/views/layouts/mbeditor/application.html.erb +120 -0
  40. data/app/views/mbeditor/editors/index.html.erb +1 -0
  41. data/config/initializers/assets.rb +9 -0
  42. data/config/routes.rb +44 -0
  43. data/lib/mbeditor/configuration.rb +22 -0
  44. data/lib/mbeditor/engine.rb +37 -0
  45. data/lib/mbeditor/rack/silence_ping_request.rb +56 -0
  46. data/lib/mbeditor/version.rb +3 -0
  47. data/lib/mbeditor.rb +19 -0
  48. data/mbeditor.gemspec +31 -0
  49. data/public/mbeditor-icon.svg +4 -0
  50. data/public/min-maps/vs/base/worker/workerMain.js.map +1 -0
  51. data/public/monaco-editor/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
  52. data/public/monaco-editor/vs/base/worker/workerMain.js +31 -0
  53. data/public/monaco-editor/vs/basic-languages/cameligo/cameligo.js +10 -0
  54. data/public/monaco-editor/vs/basic-languages/css/css.js +12 -0
  55. data/public/monaco-editor/vs/basic-languages/dart/dart.js +10 -0
  56. data/public/monaco-editor/vs/basic-languages/flow9/flow9.js +10 -0
  57. data/public/monaco-editor/vs/basic-languages/go/go.js +10 -0
  58. data/public/monaco-editor/vs/basic-languages/handlebars/handlebars.js +440 -0
  59. data/public/monaco-editor/vs/basic-languages/javascript/javascript.js +10 -0
  60. data/public/monaco-editor/vs/basic-languages/markdown/markdown.js +10 -0
  61. data/public/monaco-editor/vs/basic-languages/msdax/msdax.js +10 -0
  62. data/public/monaco-editor/vs/basic-languages/postiats/postiats.js +10 -0
  63. data/public/monaco-editor/vs/basic-languages/pug/pug.js +412 -0
  64. data/public/monaco-editor/vs/basic-languages/restructuredtext/restructuredtext.js +10 -0
  65. data/public/monaco-editor/vs/basic-languages/ruby/ruby.js +10 -0
  66. data/public/monaco-editor/vs/basic-languages/sb/sb.js +10 -0
  67. data/public/monaco-editor/vs/basic-languages/shell/shell.js +41 -0
  68. data/public/monaco-editor/vs/basic-languages/typescript/typescript.js +10 -0
  69. data/public/monaco-editor/vs/basic-languages/typespec/typespec.js +10 -0
  70. data/public/monaco-editor/vs/basic-languages/yaml/yaml.js +10 -0
  71. data/public/monaco-editor/vs/editor/editor.api.js +6 -0
  72. data/public/monaco-editor/vs/editor/editor.main.css +8 -0
  73. data/public/monaco-editor/vs/editor/editor.main.js +797 -0
  74. data/public/monaco-editor/vs/language/typescript/tsMode.js +20 -0
  75. data/public/monaco-editor/vs/language/typescript/tsWorker.js +51328 -0
  76. data/public/monaco-editor/vs/loader.js +10 -0
  77. data/public/monaco-editor/vs/nls.messages.de.js +20 -0
  78. data/public/monaco-editor/vs/nls.messages.es.js +20 -0
  79. data/public/monaco-editor/vs/nls.messages.fr.js +18 -0
  80. data/public/monaco-editor/vs/nls.messages.it.js +18 -0
  81. data/public/monaco-editor/vs/nls.messages.ja.js +20 -0
  82. data/public/monaco-editor/vs/nls.messages.ko.js +18 -0
  83. data/public/monaco-editor/vs/nls.messages.ru.js +20 -0
  84. data/public/monaco-editor/vs/nls.messages.zh-cn.js +20 -0
  85. data/public/monaco-editor/vs/nls.messages.zh-tw.js +18 -0
  86. data/public/monaco_worker.js +5 -0
  87. data/public/sw.js +8 -0
  88. data/public/ts_worker.js +5 -0
  89. data/vendor/assets/javascripts/axios.min.js +5 -0
  90. data/vendor/assets/javascripts/emmet.js +5452 -0
  91. data/vendor/assets/javascripts/lodash.min.js +136 -0
  92. data/vendor/assets/javascripts/marked.min.js +6 -0
  93. data/vendor/assets/javascripts/minisearch.min.js +2044 -0
  94. data/vendor/assets/javascripts/monaco-themes-bundle.js +10 -0
  95. data/vendor/assets/javascripts/monaco-vim.js +9867 -0
  96. data/vendor/assets/javascripts/prettier-plugin-babel.js +16 -0
  97. data/vendor/assets/javascripts/prettier-plugin-estree.js +35 -0
  98. data/vendor/assets/javascripts/prettier-plugin-html.js +19 -0
  99. data/vendor/assets/javascripts/prettier-plugin-markdown.js +59 -0
  100. data/vendor/assets/javascripts/prettier-plugin-postcss.js +52 -0
  101. data/vendor/assets/javascripts/prettier-standalone.js +37 -0
  102. data/vendor/assets/javascripts/react-dom.min.js +267 -0
  103. data/vendor/assets/javascripts/react.min.js +31 -0
  104. data/vendor/assets/stylesheets/fontawesome.min.css.erb +9 -0
  105. data/vendor/assets/webfonts/fa-brands-400.woff2 +0 -0
  106. data/vendor/assets/webfonts/fa-regular-400.woff2 +0 -0
  107. data/vendor/assets/webfonts/fa-solid-900.woff2 +0 -0
  108. 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
+ [![Gem Version](https://badge.fury.io/rb/mbeditor.svg)](https://rubygems.org/gems/mbeditor)
4
+ [![Test](https://github.com/ojnoonan/mbeditor/actions/workflows/test.yml/badge.svg)](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;