mbeditor 0.12.0 → 0.12.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 +65 -3
- data/README.md +38 -1
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +6 -1
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +590 -221
- data/app/assets/stylesheets/mbeditor/editor.css +94 -2
- data/app/controllers/mbeditor/editors_controller.rb +31 -4
- data/lib/mbeditor/configuration.rb +6 -2
- data/lib/mbeditor/version.rb +1 -1
- metadata +2 -2
|
@@ -3120,8 +3120,16 @@ button:not(.pico-btn) { margin-bottom: 0; }
|
|
|
3120
3120
|
margin-left: auto;
|
|
3121
3121
|
}
|
|
3122
3122
|
.ide-model-graph-search {
|
|
3123
|
-
width:
|
|
3124
|
-
|
|
3123
|
+
width: 170px;
|
|
3124
|
+
/* Two separate things drew over the text here. WebKit renders its own
|
|
3125
|
+
magnifier and clear button inside type="search"; and the vendored Pico
|
|
3126
|
+
stylesheet paints a magnifier as a background-image on every [type=search],
|
|
3127
|
+
positioned at the left, which the 8px padding put the text straight on top
|
|
3128
|
+
of. Disabling the native decorations alone fixed neither — the visible icon
|
|
3129
|
+
was Pico's. Both are off; the placeholder already says what the field is. */
|
|
3130
|
+
-webkit-appearance: none;
|
|
3131
|
+
appearance: none;
|
|
3132
|
+
padding: 3px 8px;
|
|
3125
3133
|
background: var(--ide-input-bg, #3c3c3c);
|
|
3126
3134
|
border: 1px solid var(--ide-border, #3c3c3c);
|
|
3127
3135
|
border-radius: 3px;
|
|
@@ -3243,3 +3251,87 @@ button:not(.pico-btn) { margin-bottom: 0; }
|
|
|
3243
3251
|
.statusbar-problems-error-icon { color: var(--ide-danger); }
|
|
3244
3252
|
.statusbar-problems-warning-icon { color: var(--ide-warning); }
|
|
3245
3253
|
.statusbar-problems-count { font-variant-numeric: tabular-nums; }
|
|
3254
|
+
|
|
3255
|
+
/* Search decorations off across engines — Chrome/Safari draw their own
|
|
3256
|
+
magnifier and clear button inside type="search". */
|
|
3257
|
+
.ide-model-graph-search::-webkit-search-decoration,
|
|
3258
|
+
.ide-model-graph-search::-webkit-search-cancel-button,
|
|
3259
|
+
.ide-model-graph-search::-webkit-search-results-button,
|
|
3260
|
+
.ide-model-graph-search::-webkit-search-results-decoration {
|
|
3261
|
+
-webkit-appearance: none;
|
|
3262
|
+
appearance: none;
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
/* Cluster areas — a connected part of the schema, drawn behind its models so
|
|
3266
|
+
the grouping is visible rather than implied by proximity alone. */
|
|
3267
|
+
.mg-region-box {
|
|
3268
|
+
fill: color-mix(in srgb, var(--ide-fg, #d4d4d4) 4%, transparent);
|
|
3269
|
+
stroke: color-mix(in srgb, var(--ide-fg, #d4d4d4) 12%, transparent);
|
|
3270
|
+
stroke-width: 1;
|
|
3271
|
+
}
|
|
3272
|
+
.mg-region-label {
|
|
3273
|
+
fill: color-mix(in srgb, var(--ide-fg, #d4d4d4) 45%, transparent);
|
|
3274
|
+
font-size: 11px;
|
|
3275
|
+
font-weight: 500;
|
|
3276
|
+
pointer-events: none;
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
/* Hovering a model dims every association it is not part of. With 500+ edges on
|
|
3280
|
+
screen the lines are otherwise indistinguishable from each other, and picking
|
|
3281
|
+
out one model's relationships is the whole reason to look at the diagram.
|
|
3282
|
+
Applied by toggling classes on the DOM rather than through React — re-rendering
|
|
3283
|
+
thousands of SVG nodes on mouseenter is what made this view unusable before. */
|
|
3284
|
+
/* The ~30 ms this costs on a 300-model graph is the browser restyling every
|
|
3285
|
+
edge and box, not anything in our code — toggling the classes themselves
|
|
3286
|
+
measures 1.6 ms. Switching to stroke-opacity/fill-opacity was tried on the
|
|
3287
|
+
theory that opacity stacking contexts were the cost; measured slower (38 ms
|
|
3288
|
+
vs 32 ms), so plain opacity stays. */
|
|
3289
|
+
.mg-focus-mode .mg-edge { opacity: 0.08; }
|
|
3290
|
+
.mg-focus-mode .mg-lit .mg-edge { opacity: 1; stroke-width: 2; }
|
|
3291
|
+
.mg-focus-mode .mg-node { opacity: 0.55; }
|
|
3292
|
+
.mg-focus-mode .mg-node:hover { opacity: 1; }
|
|
3293
|
+
|
|
3294
|
+
/* The association list shown while a model is hovered. Kept mounted and toggled
|
|
3295
|
+
with a class so moving between boxes doesn't thrash the DOM. */
|
|
3296
|
+
.mg-assoc-card {
|
|
3297
|
+
position: absolute;
|
|
3298
|
+
z-index: 30;
|
|
3299
|
+
max-width: 290px;
|
|
3300
|
+
padding: 8px 10px;
|
|
3301
|
+
background: var(--ide-panel-bg, #252526);
|
|
3302
|
+
border: 1px solid var(--ide-border, #3c3c3c);
|
|
3303
|
+
border-radius: 6px;
|
|
3304
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
|
|
3305
|
+
font-size: 11px;
|
|
3306
|
+
line-height: 1.5;
|
|
3307
|
+
color: var(--ide-text, #d4d4d4);
|
|
3308
|
+
pointer-events: none;
|
|
3309
|
+
}
|
|
3310
|
+
.mg-assoc-card-hidden { display: none; }
|
|
3311
|
+
.mg-assoc-title { font-weight: 600; margin-bottom: 4px; }
|
|
3312
|
+
.mg-assoc-count { color: var(--ide-text-muted, #858585); margin-bottom: 4px; }
|
|
3313
|
+
.mg-assoc-row { display: flex; align-items: baseline; gap: 5px; white-space: nowrap; }
|
|
3314
|
+
.mg-assoc-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
|
|
3315
|
+
.mg-assoc-macro { color: var(--ide-accent-fg, #4ec9b0); }
|
|
3316
|
+
.mg-assoc-name { color: var(--ide-text, #d4d4d4); }
|
|
3317
|
+
.mg-assoc-dir { color: var(--ide-text-muted, #858585); overflow: hidden; text-overflow: ellipsis; }
|
|
3318
|
+
.mg-assoc-through { color: var(--ide-text-muted, #858585); font-style: italic; }
|
|
3319
|
+
.mg-assoc-more, .mg-assoc-empty { color: var(--ide-text-muted, #858585); margin-top: 3px; }
|
|
3320
|
+
|
|
3321
|
+
/* Explicit "open the schema" target in each model's header. */
|
|
3322
|
+
.mg-open-btn { cursor: pointer; }
|
|
3323
|
+
.mg-open-btn-bg { fill: transparent; }
|
|
3324
|
+
.mg-open-btn-bar { fill: var(--ide-text-muted, #858585); }
|
|
3325
|
+
.mg-open-btn:hover .mg-open-btn-bg { fill: color-mix(in srgb, var(--ide-fg, #d4d4d4) 16%, transparent); }
|
|
3326
|
+
.mg-open-btn:hover .mg-open-btn-bar { fill: var(--ide-fg, #d4d4d4); }
|
|
3327
|
+
.mg-node { cursor: zoom-in; }
|
|
3328
|
+
|
|
3329
|
+
/* Pico paints a magnifier as a background-image on every [type=search], at the
|
|
3330
|
+
left of the field, and our 8px padding put the text straight on top of it.
|
|
3331
|
+
Its selector is input:not([type=checkbox],...)[type=search] — specificity
|
|
3332
|
+
(0,2,1) — so a plain .class rule (0,1,0) loses and the icon stayed. Matching
|
|
3333
|
+
that specificity, and loading later, is what actually removes it. */
|
|
3334
|
+
input.ide-model-graph-search[type="search"] {
|
|
3335
|
+
background-image: none;
|
|
3336
|
+
padding-inline-start: 8px;
|
|
3337
|
+
}
|
|
@@ -1145,14 +1145,41 @@ module Mbeditor
|
|
|
1145
1145
|
# authenticate_with) so the host app can supply a collaboration display name.
|
|
1146
1146
|
# nil — including any failure or a blank result — falls through to the
|
|
1147
1147
|
# client-generated name.
|
|
1148
|
+
# Display name for the collaboration caret. An explicit user_name_callback
|
|
1149
|
+
# wins; otherwise fall back to current_user, when the host's auth library put
|
|
1150
|
+
# it within reach of an ActionController::Base subclass (Devise, Sorcery and
|
|
1151
|
+
# friends do; a current_user hand-written on the host's own
|
|
1152
|
+
# ApplicationController does not — see default_user_name).
|
|
1148
1153
|
def resolved_user_name
|
|
1149
1154
|
cb = Mbeditor.configuration.user_name_callback
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
name = instance_exec(&cb)
|
|
1155
|
+
name = cb ? instance_exec(&cb) : default_user_name
|
|
1153
1156
|
name = name.to_s.strip
|
|
1154
1157
|
name.empty? ? nil : name
|
|
1155
|
-
rescue StandardError
|
|
1158
|
+
rescue StandardError => e
|
|
1159
|
+
# Was a bare `nil`, which made a broken callback indistinguishable from an
|
|
1160
|
+
# unconfigured one: you got the generated name and no clue why. The usual
|
|
1161
|
+
# cause is a NameError on current_user, and silently swallowing it sent
|
|
1162
|
+
# people looking in the wrong place.
|
|
1163
|
+
Rails.logger.warn("[mbeditor] user name lookup failed: #{e.class}: #{e.message}")
|
|
1164
|
+
nil
|
|
1165
|
+
end
|
|
1166
|
+
|
|
1167
|
+
# Read the display name straight off current_user, trying each configured
|
|
1168
|
+
# attribute in turn. This is what makes an authenticated editor show real
|
|
1169
|
+
# names with no extra wiring; user_name_methods exists so an app whose column
|
|
1170
|
+
# isn't in the default list can name it instead of writing a callback.
|
|
1171
|
+
def default_user_name
|
|
1172
|
+
return nil unless respond_to?(:current_user, true)
|
|
1173
|
+
|
|
1174
|
+
user = current_user
|
|
1175
|
+
return nil unless user
|
|
1176
|
+
|
|
1177
|
+
Array(Mbeditor.configuration.user_name_methods).each do |m|
|
|
1178
|
+
next unless user.respond_to?(m)
|
|
1179
|
+
|
|
1180
|
+
value = user.public_send(m).to_s.strip
|
|
1181
|
+
return value unless value.empty?
|
|
1182
|
+
end
|
|
1156
1183
|
nil
|
|
1157
1184
|
end
|
|
1158
1185
|
|
|
@@ -5,7 +5,7 @@ module Mbeditor
|
|
|
5
5
|
attr_accessor :allowed_environments, :workspace_root, :excluded_paths, :rubocop_command, :rubocop_server,
|
|
6
6
|
:redmine_enabled, :redmine_url, :redmine_api_key, :redmine_ticket_source,
|
|
7
7
|
:test_framework, :test_command, :test_timeout,
|
|
8
|
-
:authenticate_with, :authentication_cache_ttl, :user_name_callback,
|
|
8
|
+
:authenticate_with, :authentication_cache_ttl, :user_name_callback, :user_name_methods,
|
|
9
9
|
:lint_timeout, :base_branch_candidates, :git_timeout, :search_timeout,
|
|
10
10
|
:ruby_def_include_dirs, :related_files_custom_paths,
|
|
11
11
|
:mount_path, :resilient_routing, :js_global_identifiers,
|
|
@@ -58,7 +58,11 @@ module Mbeditor
|
|
|
58
58
|
@ruby_def_include_dirs = %w[app/models app/controllers app/helpers app/concerns]
|
|
59
59
|
@related_files_custom_paths = []
|
|
60
60
|
@authentication_cache_ttl = 0
|
|
61
|
-
@user_name_callback = nil # proc resolved in controller context (instance_exec) → collaboration display name; nil falls through to the client-generated name
|
|
61
|
+
@user_name_callback = nil # proc resolved in controller context (instance_exec) → collaboration display name; nil falls through to current_user, then to the client-generated name
|
|
62
|
+
# Attributes tried on current_user, in order, when no user_name_callback
|
|
63
|
+
# is set. First non-blank one wins. Name your own column here rather than
|
|
64
|
+
# writing a callback for the common case.
|
|
65
|
+
@user_name_methods = %w[name full_name display_name username login email]
|
|
62
66
|
@js_global_identifiers = [] # extra ambient JS globals for the editor (runtime-only names invisible to static scan, e.g. %w[Routes I18n])
|
|
63
67
|
# Load the workspace's own JS source into Monaco's TypeScript program, so
|
|
64
68
|
# cross-file references get real inferred types instead of ambient `any`.
|
data/lib/mbeditor/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mbeditor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.12.
|
|
4
|
+
version: 0.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oliver Noonan
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|