mbeditor 0.12.9 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +216 -1
- data/app/assets/javascripts/mbeditor/application.js +6 -1
- data/app/assets/javascripts/mbeditor/collaboration_service.js +52 -1
- data/app/assets/javascripts/mbeditor/color_provider.js +5 -0
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +3 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +104 -103
- data/app/assets/javascripts/mbeditor/components/FileTree.js +6 -1
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +10 -1
- data/app/assets/javascripts/mbeditor/components/ImportConflictModal.js +15 -8
- data/app/assets/javascripts/mbeditor/components/ImportDialog.js +224 -0
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +704 -270
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +25 -6
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +193 -9
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +105 -77
- data/app/assets/javascripts/mbeditor/components/TabBar.js +11 -2
- data/app/assets/javascripts/mbeditor/editor_plugins.js +434 -112
- data/app/assets/javascripts/mbeditor/file_import.js +78 -0
- data/app/assets/javascripts/mbeditor/file_service.js +112 -18
- data/app/assets/javascripts/mbeditor/git_service.js +130 -8
- data/app/assets/javascripts/mbeditor/history_service.js +33 -38
- data/app/assets/javascripts/mbeditor/log_service.js +4 -0
- data/app/assets/javascripts/mbeditor/search_service.js +50 -4
- data/app/assets/javascripts/mbeditor/tab_manager.js +66 -10
- data/app/assets/javascripts/mbeditor/websocket_service.js +88 -4
- data/app/assets/stylesheets/mbeditor/editor.css +389 -103
- data/app/channels/mbeditor/channel_authentication.rb +7 -0
- data/app/channels/mbeditor/collaboration_channel.rb +8 -2
- data/app/channels/mbeditor/editor_channel.rb +6 -3
- data/app/controllers/mbeditor/application_controller.rb +5 -0
- data/app/controllers/mbeditor/editors_controller.rb +155 -29
- data/app/controllers/mbeditor/git_controller.rb +3 -3
- data/app/controllers/mbeditor/logs_controller.rb +3 -1
- data/app/services/mbeditor/collaboration_doc_store.rb +49 -3
- data/app/services/mbeditor/duplicate_content_scanner.rb +105 -0
- data/app/services/mbeditor/editor_state_service.rb +36 -26
- data/app/services/mbeditor/exclusion_matcher.rb +12 -10
- data/app/services/mbeditor/file_operation_service.rb +38 -3
- data/app/services/mbeditor/file_tree_service.rb +30 -2
- data/app/services/mbeditor/git_combined_diff_service.rb +13 -3
- data/app/services/mbeditor/git_commit_detail_service.rb +20 -16
- data/app/services/mbeditor/git_diff_service.rb +5 -1
- data/app/services/mbeditor/git_info_service.rb +20 -8
- data/app/services/mbeditor/git_line_diff_service.rb +6 -2
- data/app/services/mbeditor/git_service.rb +65 -15
- data/app/services/mbeditor/js_definition_service.rb +3 -1
- data/app/services/mbeditor/js_globals_service.rb +18 -4
- data/app/services/mbeditor/js_members_service.rb +3 -2
- data/app/services/mbeditor/js_program_service.rb +15 -5
- data/app/services/mbeditor/js_syntax_check_service.rb +15 -4
- data/app/services/mbeditor/process_runner.rb +42 -12
- data/app/services/mbeditor/ri_definition_service.rb +8 -1
- data/app/services/mbeditor/route_service.rb +45 -8
- data/app/services/mbeditor/rubocop_run_service.rb +98 -0
- data/app/services/mbeditor/ruby_definition_service.rb +23 -4
- data/app/services/mbeditor/schema_service.rb +73 -66
- data/app/services/mbeditor/search_replace_service.rb +42 -7
- data/app/services/mbeditor/test_runner_service.rb +45 -10
- data/lib/mbeditor/cable_log_filter.rb +8 -2
- data/lib/mbeditor/configuration.rb +4 -1
- data/lib/mbeditor/editor_bootstrap.rb +18 -12
- data/lib/mbeditor/engine.rb +22 -3
- data/lib/mbeditor/exception_log.rb +2 -2
- data/lib/mbeditor/pending_migrations.rb +33 -0
- data/lib/mbeditor/rack/handle_pending_migrations.rb +25 -15
- data/lib/mbeditor/rack/pending_migration_bypass.rb +104 -0
- data/lib/mbeditor/rack/silence_ping_request.rb +10 -2
- data/lib/mbeditor/route_map.rb +1 -0
- data/lib/mbeditor/ruby_lsp_client.rb +71 -12
- data/lib/mbeditor/version.rb +1 -1
- data/lib/tasks/mbeditor.rake +23 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe5dce956e01ff66387b9ccf99fc8b163ce131a226158eda7b6cf2c8513eaa09
|
|
4
|
+
data.tar.gz: 37e4f12f4ccd1ca7d209e7fe134a25d813bbe175862d53a470a5042aff9d7797
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 60c74bb23b92719d4273d720af42813e2ecd3d639281901d47037e332697819cbe58c338eea9aeff334f3533d48d2e62c994b2122fb36fbded4bef94bd225b36
|
|
7
|
+
data.tar.gz: 1451f3b2effb5d9cffd9811b41ea72b07d174702af9e6dc9993d5fbc92d990109dbf834e957c1eced22e8d77b815173d14bf6465b51072002a0e1c482745dc69
|
data/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,222 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.13.1] - 2026-08-26
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
- **The whole-suite test run is gone** — the status-bar "Tests" button, the
|
|
12
|
+
Test Run drawer, `POST /test_all`, and the `test_all_command` /
|
|
13
|
+
`test_all_timeout` settings. It was one blocking request holding a
|
|
14
|
+
development-server thread for the length of the suite, with no way to cancel
|
|
15
|
+
it and the runner's output buffered whole in the server process; on a real
|
|
16
|
+
host app that is twenty minutes of a frozen-looking button for a result the
|
|
17
|
+
terminal gives you faster and interruptibly. The per-file (and per-line) test
|
|
18
|
+
run stays — that one is scoped, finishes in seconds and is why the editor
|
|
19
|
+
runs tests at all. **A host app setting `test_all_command` or
|
|
20
|
+
`test_all_timeout` in its initializer must drop those lines.**
|
|
21
|
+
- **The ruby-lsp status-bar chip is gone**, along with its 10-second health
|
|
22
|
+
poll. ruby-lsp itself is untouched — definitions, hover, completion,
|
|
23
|
+
diagnostics and the rest all still run through it.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- `rake mbeditor:scan_duplicates` — scans the workspace for files that contain a
|
|
27
|
+
duplicated copy of themselves, for finding files damaged before the seeding fix below.
|
|
28
|
+
Set `MBEDITOR_WORKSPACE_ROOT` to scan a tree other than the resolved workspace.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **Data loss: a saved file could contain two copies of itself.** When two editor
|
|
32
|
+
clients attached to a collaborative room the server had no state for — a fresh
|
|
33
|
+
room, or one evicted while both sat idle — each one seeded the shared document
|
|
34
|
+
from disk, and Yjs merged both inserts at offset 0 into two concatenated copies.
|
|
35
|
+
Saving then wrote the doubled buffer. The server now grants the right to seed a
|
|
36
|
+
room to exactly one client (`CollaborationDocStore.claim_seed`), never evicts a
|
|
37
|
+
room that still has subscribers, and a client whose cable reconnects re-publishes
|
|
38
|
+
its snapshot so a server that lost its state cannot hand out a second seed.
|
|
39
|
+
- **Status bar alignment.** Every item now sits in the same 8px cell instead of
|
|
40
|
+
carrying its own padding on top of the bar's (the branch, the offline chip
|
|
41
|
+
and the version each had a different one), the branch drops from 13px to the
|
|
42
|
+
bar's 11px, and `line-height: 1` on the bar *and* on its buttons puts an icon
|
|
43
|
+
and the number beside it in identical line boxes — Pico gives `<button>` its
|
|
44
|
+
own 1.5, so the problem counts sat in a 16.5px box next to an 11px icon.
|
|
45
|
+
- **The Problems panel's Fix button did nothing visible.** `rubocop -a` writes
|
|
46
|
+
its corrections to disk, but reports the offenses it just fixed alongside the
|
|
47
|
+
ones it left — each flagged both `corrected` and `correctable` — so the list
|
|
48
|
+
after a fix was byte-identical to the list before it and the button stayed
|
|
49
|
+
lit. Corrected offenses are now dropped from the response, open buffers are
|
|
50
|
+
re-read after the run, and clearing stale diagnostics now clears the React
|
|
51
|
+
marker map as well as Monaco's, which is what had left the panel, the tab
|
|
52
|
+
badges and the status-bar tallies disagreeing.
|
|
53
|
+
- **Quick Open matches loosely again.** MiniSearch was configured `fuzzy: false`,
|
|
54
|
+
so a single typo found nothing; fuzziness is now scaled per term. Folders sort
|
|
55
|
+
after files instead of ahead of every match, and the dialog and its text are
|
|
56
|
+
larger.
|
|
57
|
+
- **Search results read like VS Code's.** Hits group under a collapsible row per
|
|
58
|
+
file with a match count, the matched text is tinted with the accent colour and
|
|
59
|
+
is kept on screen at any panel width (eliding left and right around it), and
|
|
60
|
+
clicking a hit selects the match and leaves the cursor at its end.
|
|
61
|
+
- **Bulk import no longer opens on the previous import's folder** — the folder
|
|
62
|
+
you actually acted on is offered first — and it now reports what it wrote and
|
|
63
|
+
reveals the destination in the tree.
|
|
64
|
+
- `ReactRailsUJS` no longer reports as undefined; it ships inside the react-rails
|
|
65
|
+
gem, outside the workspace, so no static scan of the tree could ever find it.
|
|
66
|
+
- `<<~JSX` heredocs highlight as JavaScript (as do `<<~TSX`/`<<~TS`).
|
|
67
|
+
- The Open Editors list no longer runs its filenames underneath the split and
|
|
68
|
+
close buttons.
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
- **The activity bar is a third narrower** (60px to 40px), and the editor
|
|
72
|
+
toolbar's per-file Test button is gone; running the test under the cursor is
|
|
73
|
+
unaffected.
|
|
74
|
+
|
|
75
|
+
## [0.13.0] - 2026-08-20
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
- **Whole-suite test run, in a drawer.** `POST /test_all` runs the suite with
|
|
79
|
+
no file argument under its own `test_all_timeout` (30 min) — a suite run is
|
|
80
|
+
minutes, not seconds, and forcing `test_timeout` up to cover it would let one
|
|
81
|
+
hung example block for half an hour. It renders as a bottom drawer beside
|
|
82
|
+
Problems and the log rather than a modal: a backdrop that blocks the editor
|
|
83
|
+
for the length of a suite run turns the wait into dead time. Failures group
|
|
84
|
+
by file, each row jumps to its line, and "Open failing" opens them all at
|
|
85
|
+
once. The result survives closing the drawer and a reload, and a branch
|
|
86
|
+
switch drops it.
|
|
87
|
+
- **Whole-workspace RuboCop run in the Problems panel.** The panel could only
|
|
88
|
+
ever show diagnostics for files Monaco had open, because markers are
|
|
89
|
+
per-model. Three buttons now cover the rest of the project: RuboCop (a full
|
|
90
|
+
`rubocop --format json` run), Fix (`-a`, safe corrections only), and Open all.
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
- **Activity bar and explorer are larger.** Activity buttons go 36px to 60px
|
|
94
|
+
with a 25px glyph and fill the bar edge to edge, so the active indicator sits
|
|
95
|
+
flush at the screen edge; state is carried by glyph colour alone and the
|
|
96
|
+
hover box is gone. Explorer rows and icons go 12px to 14px.
|
|
97
|
+
- **Per-file test timeout raised 60s to 180s**, and the client now derives its
|
|
98
|
+
request timeout from the server's (`/workspace` reports both ceilings)
|
|
99
|
+
instead of a hard-coded 120s. Raising the server timeout past the client's
|
|
100
|
+
used to make the browser abort a run the server was still executing.
|
|
101
|
+
|
|
102
|
+
### Security
|
|
103
|
+
- **Every entry point is gated on `allowed_environments`.** The check moved to
|
|
104
|
+
`ApplicationController` ahead of `run_authentication`. `LogsController`
|
|
105
|
+
declared no gate at all, so `GET /mbeditor/logs/tail` returned the tail of
|
|
106
|
+
`log/<env>.log` in any environment the engine was mounted in; both cable
|
|
107
|
+
channels checked only the (nil by default) authentication hook, so anything
|
|
108
|
+
reaching the host's `/cable` could subscribe and start a log tail or write
|
|
109
|
+
state. Running the host's auth proc now happens after the environment check,
|
|
110
|
+
so a disallowed environment 404s without it.
|
|
111
|
+
- **`model_schema` validates its parameter.** The model name flowed into a file
|
|
112
|
+
path without a format check, so `..` segments escaped the workspace.
|
|
113
|
+
- **Git ref patterns reject a leading dash**, and `git show` takes
|
|
114
|
+
`--end-of-options` — a ref beginning with `-` was argument injection into a
|
|
115
|
+
documented git option.
|
|
116
|
+
|
|
117
|
+
### Performance
|
|
118
|
+
- **Typing no longer re-renders the whole application.** Every keystroke wrote
|
|
119
|
+
the full document into the store, which re-rendered an app subscribed to all
|
|
120
|
+
of it. Content writes are now throttled (250 ms, trailing) with a documented
|
|
121
|
+
flush-on-save contract, and the dirty flag is written only on a clean/dirty
|
|
122
|
+
transition. On top of that: the file tree is no longer deep-copied per render,
|
|
123
|
+
the model-graph scene (~10k SVG elements) no longer rebuilds when unrelated
|
|
124
|
+
state moves, and blame/test view zones, the tab strip's smooth scroll, and the
|
|
125
|
+
problem counts no longer churn per keypress.
|
|
126
|
+
- **Monaco's ruby-lsp providers honour cancellation.** `documentHighlight`
|
|
127
|
+
fires per cursor move and was POSTing the entire buffer with no way to cancel
|
|
128
|
+
it, so superseded requests stayed in flight; all eight raw providers now
|
|
129
|
+
thread Monaco's `CancellationToken` through one shared path, and highlights
|
|
130
|
+
memoise per word occurrence.
|
|
131
|
+
- **ruby-lsp requests pipeline again.** The document mutex was held across the
|
|
132
|
+
whole request/response round-trip, so a single diagnostics call (10 s budget)
|
|
133
|
+
or rename (30 s) blocked every hover and highlight in the process. It is now
|
|
134
|
+
held only across the write, which is what actually needs ordering. Abandoned
|
|
135
|
+
requests send `$/cancelRequest` rather than leaving a single-threaded server
|
|
136
|
+
computing a result nobody will read.
|
|
137
|
+
- **Conditional GETs on the polled and static routes.** Monaco's 14 MB of
|
|
138
|
+
assets carried no validators and were re-fetched on every page load; `/files`
|
|
139
|
+
shipped roughly 1 MB of JSON on a 10 s poll and now answers 304 from a digest
|
|
140
|
+
memoised with the existing tree cache. `/git_status`, hit every 5 s, is TTL
|
|
141
|
+
cached instead of spawning two git subprocesses per poll.
|
|
142
|
+
- **`find_branch_base` costs 2 git spawns instead of up to 12**, resolving the
|
|
143
|
+
candidate list with one `for-each-ref`. Commit detail drops from 3 spawns to
|
|
144
|
+
2, and the line-diff probe only asks whether a file is untracked when the
|
|
145
|
+
diff came back empty.
|
|
146
|
+
- **A JS save no longer rebuilds the whole workspace JS program behind a global
|
|
147
|
+
lock**, and a `.rb` save no longer invalidates the JS program and globals
|
|
148
|
+
caches at all. Go-to-definition no longer rewrites the entire on-disk cache
|
|
149
|
+
(tens of MB of JSON) on any lookup that parsed something new.
|
|
150
|
+
- Smaller: one `lstat` per tree entry instead of three stats, unbounded
|
|
151
|
+
subprocess output capped, and the exclusion matcher splits each path once
|
|
152
|
+
rather than once per pattern.
|
|
153
|
+
|
|
154
|
+
### Fixed
|
|
155
|
+
- **Pending migrations no longer lock the editor.** `CheckPending` is swapped
|
|
156
|
+
for a wrapper that runs the identical check for host traffic but records
|
|
157
|
+
rather than raises for the editor's, so the tree, opening a file and saving
|
|
158
|
+
one keep working — including saving the migration causing it. The warning
|
|
159
|
+
rides a response header, so it appears for a migration created mid-session
|
|
160
|
+
and clears once migrated without a reload. Host traffic is still blocked
|
|
161
|
+
exactly as Rails intends. The fallback shell also requested its Sprockets
|
|
162
|
+
assets under the engine mount, where they 404, and hardcoded `/mbeditor`
|
|
163
|
+
instead of the configured mount path.
|
|
164
|
+
- **Saves no longer degrade under a burst.** A timeout was counted as an
|
|
165
|
+
unreachable server, which suppressed the polls that would have proven it up;
|
|
166
|
+
`files_changed` is coalesced once at the WebSocket dispatch (15 writes: 15
|
|
167
|
+
dispatches to 1); panes re-ran `git diff` on every save anywhere in the
|
|
168
|
+
workspace; and the lite git poll escalated to the full fan-out twice per save
|
|
169
|
+
(12 saves: 12 fan-outs to 1).
|
|
170
|
+
- **Replace All left open tabs showing pre-replace text.** The refresh treated
|
|
171
|
+
a model-registry entry as a Monaco model, so the update silently no-opped
|
|
172
|
+
into a bare `catch` — and saving that tab afterwards wrote the stale content
|
|
173
|
+
back over the replacement.
|
|
174
|
+
- **A dropped cable connection disconnected the host application's own
|
|
175
|
+
ActionCable consumer** when the host provided one, taking its channels down
|
|
176
|
+
with it, and left collaboration rooms bound to the dead consumer with no
|
|
177
|
+
indication they were dead. Consumers are disconnected only when mbeditor
|
|
178
|
+
created them, and rooms rejoin on reconnect.
|
|
179
|
+
- **ruby-lsp failed entirely for any workspace path containing a space** or
|
|
180
|
+
other character needing escaping: the `file://` URI was built by
|
|
181
|
+
interpolation, so ruby-lsp's own `URI` parse raised and every request fell
|
|
182
|
+
back silently. Return-path comparisons unescape before matching the
|
|
183
|
+
workspace root.
|
|
184
|
+
- **Git status mangled paths with spaces, non-ASCII characters, or renames.**
|
|
185
|
+
Porcelain output was read verbatim, so quoted and octal-escaped paths arrived
|
|
186
|
+
unopenable and missed their numstat enrichment; both sides now use `-z`.
|
|
187
|
+
- **Saves and editor state are written atomically.** Both truncated in place,
|
|
188
|
+
so a reader landing mid-write parsed an empty file and silently reported no
|
|
189
|
+
open tabs, and a crash mid-write left the file empty for good. Writes go to a
|
|
190
|
+
temporary file and rename, resolving through symlinks so a linked file stays
|
|
191
|
+
a link.
|
|
192
|
+
- **Literal (non-regex) replace corrupted replacements containing
|
|
193
|
+
backslashes** — `\1`, `\&` and `\\` were interpreted as backreferences across
|
|
194
|
+
up to 500 files.
|
|
195
|
+
- **A user-supplied search regex could pin a worker indefinitely.** Patterns
|
|
196
|
+
are compiled with `Regexp.timeout` where the Ruby version supports it;
|
|
197
|
+
`Timeout.timeout` cannot interrupt a regex match, which is what the previous
|
|
198
|
+
guard relied on.
|
|
199
|
+
- **A failed ruby-lsp handshake orphaned the child process**, leaving it
|
|
200
|
+
indexing and holding memory, and up to three could accumulate.
|
|
201
|
+
- **Branch switches left stale buffers and diagnostics.** A checkout rewrites
|
|
202
|
+
the working tree and nothing told the editor; the git poll now announces it,
|
|
203
|
+
the app re-reads the tree and every open tab (dirty ones still prompt rather
|
|
204
|
+
than being clobbered), and `/files?refresh=1` bypasses the 15 s tree cache.
|
|
205
|
+
- **The Problems panel filled with vendored gem source** when a host
|
|
206
|
+
`.rubocop.yml` replaced RuboCop's default excludes, and reported TypeScript's
|
|
207
|
+
implicit-any advice on plain JS/JSX, where there are no annotations to add.
|
|
208
|
+
- **`HistoryService` rejected an empty base**, so the first history entry for a
|
|
209
|
+
file 400'd.
|
|
210
|
+
- Also: a V8 isolate leak in the JS syntax checker, unbounded growth in the
|
|
211
|
+
collaboration delta log and several per-path caches, JS member completions
|
|
212
|
+
cached with a stale insert range, `quick_fix`/format/haml-lint running with
|
|
213
|
+
no timeout, a negative log offset returning a 500, and configured JS globals
|
|
214
|
+
being dropped when the symbol cap was reached.
|
|
215
|
+
|
|
216
|
+
### Tests
|
|
217
|
+
- The pending-migration middleware tests built `ActiveRecord::PendingMigrationError`
|
|
218
|
+
with no arguments, whose initializer lists the pending migrations and so needs
|
|
219
|
+
a database connection; every CI runner without one failed all six.
|
|
220
|
+
- Two stubs restored `AvailabilityProbe.rg` by rebinding a captured `Method`,
|
|
221
|
+
which raises `can't bind singleton method to a different class` if the class
|
|
222
|
+
is reloaded in between — leaving `rg` undefined and failing every later search
|
|
223
|
+
and workspace test in that process. They alias instead.
|
|
9
224
|
|
|
10
225
|
## [0.12.9] - 2026-08-07
|
|
11
226
|
|
|
@@ -33,6 +33,11 @@
|
|
|
33
33
|
//= require mbeditor/components/GitPanel
|
|
34
34
|
//= require mbeditor/components/QuickOpenDialog
|
|
35
35
|
//= require mbeditor/components/TabBar
|
|
36
|
+
// Both import modals must be required *inside* the IIFE: React is an IIFE
|
|
37
|
+
// parameter (window.React is deliberately never set), so a component file
|
|
38
|
+
// placed after the tail resolves a bare `React` to undefined and takes the
|
|
39
|
+
// whole app down the first time it renders.
|
|
40
|
+
//= require mbeditor/components/ImportConflictModal
|
|
41
|
+
//= require mbeditor/components/ImportDialog
|
|
36
42
|
//= require mbeditor/components/MbeditorApp
|
|
37
43
|
//= require mbeditor/application_iife_tail
|
|
38
|
-
//= require mbeditor/components/ImportConflictModal
|
|
@@ -225,6 +225,9 @@ var CollaborationService = (function () {
|
|
|
225
225
|
binding: null,
|
|
226
226
|
undoManager: null,
|
|
227
227
|
lateJoin: false,
|
|
228
|
+
// Set from the sync handshake: the server grants the right to seed an empty
|
|
229
|
+
// room's shared doc from disk to exactly one client (see claim_seed).
|
|
230
|
+
seedGranted: false,
|
|
228
231
|
onSeeded: null,
|
|
229
232
|
attachRequested: false,
|
|
230
233
|
degraded: false,
|
|
@@ -280,10 +283,14 @@ var CollaborationService = (function () {
|
|
|
280
283
|
if (d) window.Y.applyUpdate(room.doc, _b64ToU8(d), REMOTE);
|
|
281
284
|
});
|
|
282
285
|
}
|
|
286
|
+
room.seedGranted = !!data.seed;
|
|
283
287
|
room.synced = true;
|
|
284
288
|
_maybeAttach(room);
|
|
285
289
|
} else if (data.type === 'doc_update') {
|
|
286
290
|
if (data.update) window.Y.applyUpdate(room.doc, _b64ToU8(data.update), REMOTE);
|
|
291
|
+
// A client that deferred attaching (empty room, seed granted to someone
|
|
292
|
+
// else) is waiting for exactly this: the seeder's content has landed.
|
|
293
|
+
_maybeAttach(room);
|
|
287
294
|
} else if (data.type === 'awareness') {
|
|
288
295
|
if (data.awareness && room.awareness && window.awarenessProtocol) {
|
|
289
296
|
// REMOTE origin keeps the outbound handler from echoing peer state back.
|
|
@@ -295,6 +302,35 @@ var CollaborationService = (function () {
|
|
|
295
302
|
}
|
|
296
303
|
}
|
|
297
304
|
|
|
305
|
+
// Re-open every room's channel subscription on the current consumer.
|
|
306
|
+
//
|
|
307
|
+
// A cable drop tears the consumer down and takes every CollaborationChannel
|
|
308
|
+
// subscription with it — silently, because isAvailable() reports cable
|
|
309
|
+
// *support*, not liveness. Nothing here noticed: the rooms stayed in _rooms
|
|
310
|
+
// with dead subscriptions, so edits stopped reaching peers until the file was
|
|
311
|
+
// closed and reopened. WebSocketService calls this when the EditorChannel
|
|
312
|
+
// reconnects. Only the wire is replaced; the Yjs doc, the binding and the undo
|
|
313
|
+
// history all survive, and the re-sent sync snapshot re-applies harmlessly
|
|
314
|
+
// (Y.applyUpdate is idempotent).
|
|
315
|
+
function rejoinRooms() {
|
|
316
|
+
Object.keys(_rooms).forEach(function (path) {
|
|
317
|
+
var room = _rooms[path];
|
|
318
|
+
try { if (room.subscription) room.subscription.unsubscribe(); } catch (e) { /* already dead */ }
|
|
319
|
+
room.subscription = WebSocketService.subscribeCollaboration(path, {
|
|
320
|
+
// Awareness is never server-persisted, so peers lost our caret with the
|
|
321
|
+
// connection and would not see it again until we happened to move.
|
|
322
|
+
// The server may have lost the room entirely (restart): re-publish our
|
|
323
|
+
// content so the next opener late-joins instead of being granted a seed
|
|
324
|
+
// that would merge a second copy into what we still hold.
|
|
325
|
+
connected: function () {
|
|
326
|
+
_sendLocalAwareness(room);
|
|
327
|
+
if (room.binding) pushSnapshot(path);
|
|
328
|
+
},
|
|
329
|
+
received: function (data) { _onMessage(room, data); }
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
298
334
|
// Register the current editor/model for a room and request a binding. Called on
|
|
299
335
|
// every editor (re)creation (tab switch recreates the editor; the model and the
|
|
300
336
|
// Yjs doc persist). opts.onSeeded is invoked once the binding has set the model,
|
|
@@ -323,6 +359,11 @@ var CollaborationService = (function () {
|
|
|
323
359
|
|
|
324
360
|
function _maybeAttach(room) {
|
|
325
361
|
if (room.binding || !room.synced || !room.attachRequested || !room.model) return;
|
|
362
|
+
// Shared doc still empty and the seed was granted to another client: its
|
|
363
|
+
// content is on its way. Binding now would hand the model to an empty Y.Text
|
|
364
|
+
// and wipe the buffer, so wait for the first doc_update (or the fallback
|
|
365
|
+
// timer, which lets the natively-loaded content stand).
|
|
366
|
+
if (room.text.length === 0 && !room.seedGranted && room.model.getValue().length > 0) return;
|
|
326
367
|
_doAttach(room);
|
|
327
368
|
}
|
|
328
369
|
|
|
@@ -330,10 +371,12 @@ var CollaborationService = (function () {
|
|
|
330
371
|
var model = room.model;
|
|
331
372
|
room.lateJoin = room.text.length > 0; // server already had shared state
|
|
332
373
|
|
|
333
|
-
if (!room.lateJoin && model.getValue().length > 0) {
|
|
374
|
+
if (!room.lateJoin && room.seedGranted && model.getValue().length > 0) {
|
|
334
375
|
// First opener whose disk content is already in the model: seed the shared
|
|
335
376
|
// doc from it before constructing the binding, otherwise the binding would
|
|
336
377
|
// overwrite the model to match the (empty) Y.Text and wipe the content.
|
|
378
|
+
// Only ever the client the SERVER picked — two clients each seeding an
|
|
379
|
+
// empty room merge into two concatenated copies of the file.
|
|
337
380
|
room.doc.transact(function () { room.text.insert(0, model.getValue()); });
|
|
338
381
|
}
|
|
339
382
|
|
|
@@ -561,6 +604,7 @@ var CollaborationService = (function () {
|
|
|
561
604
|
// Client IDs are globally unique, so one shared <style> element covers all open
|
|
562
605
|
// files. Re-runs on any awareness change (cheap; small participant counts).
|
|
563
606
|
var _styleEl = null;
|
|
607
|
+
var _cursorCss = null;
|
|
564
608
|
function _renderCursorStyles() {
|
|
565
609
|
if (typeof document === 'undefined') return;
|
|
566
610
|
if (!_styleEl) {
|
|
@@ -593,6 +637,12 @@ var CollaborationService = (function () {
|
|
|
593
637
|
'font-family:sans-serif;z-index:10;pointer-events:none;}';
|
|
594
638
|
});
|
|
595
639
|
});
|
|
640
|
+
// Awareness changes on every caret move and every scroll frame, but the
|
|
641
|
+
// styles only depend on who is here and what colour they chose — which
|
|
642
|
+
// almost never changes. Re-assigning textContent invalidates the document's
|
|
643
|
+
// style rules, so skip the write when the CSS is identical.
|
|
644
|
+
if (css === _cursorCss) return;
|
|
645
|
+
_cursorCss = css;
|
|
596
646
|
_styleEl.textContent = css;
|
|
597
647
|
}
|
|
598
648
|
|
|
@@ -739,6 +789,7 @@ var CollaborationService = (function () {
|
|
|
739
789
|
onAvailabilityChange: onAvailabilityChange,
|
|
740
790
|
isEnabledFor: isEnabledFor,
|
|
741
791
|
ensureRoom: ensureRoom,
|
|
792
|
+
rejoinRooms: rejoinRooms,
|
|
742
793
|
bindEditor: bindEditor,
|
|
743
794
|
unbindEditor: unbindEditor,
|
|
744
795
|
leaveRoom: leaveRoom,
|
|
@@ -87,6 +87,11 @@
|
|
|
87
87
|
function provideDocumentColors(model) {
|
|
88
88
|
var colors = [];
|
|
89
89
|
var lineCount = model.getLineCount();
|
|
90
|
+
// Registered for every language and re-run on every change, so a big file
|
|
91
|
+
// pays two regex passes per line for every keystroke. Same threshold
|
|
92
|
+
// EditorPanel's applyLargeFileTuning uses to switch off the other
|
|
93
|
+
// whole-document decorations.
|
|
94
|
+
if (lineCount > 2500) return [];
|
|
90
95
|
for (var ln = 1; ln <= lineCount; ln++) {
|
|
91
96
|
var line = model.getLineContent(ln);
|
|
92
97
|
if (line.length > 2000) continue; // skip pathological lines
|
|
@@ -49,7 +49,9 @@ var CollapsibleSection = function CollapsibleSection(_ref) {
|
|
|
49
49
|
icon && React.createElement("i", { className: "collapsible-icon " + icon }),
|
|
50
50
|
React.createElement(
|
|
51
51
|
"span",
|
|
52
|
-
|
|
52
|
+
// The title truncates away entirely on a narrow sidebar, so it carries
|
|
53
|
+
// its own tooltip — otherwise the section becomes unidentifiable.
|
|
54
|
+
{ className: "collapsible-title", title: typeof title === 'string' ? title : undefined },
|
|
53
55
|
title
|
|
54
56
|
),
|
|
55
57
|
actions && React.createElement(
|