rails-markup 1.4.2 → 1.4.3
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/README.md +18 -0
- data/app/assets/javascripts/rails_markup/toolbar.js +338 -43
- data/lib/rails_markup/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 385e2a5ce158bd1832873c6edb4ec22f689209b7e5c7ceb33ac500c3a5e4e4fc
|
|
4
|
+
data.tar.gz: be53ae296ff60727333058950f374ebf80a57e2e85a953db216614aeb2837c1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33bd8549180fb872d8a5cb07d68ce154e9df3fd82a2e6f36e16f36c434d2f2c82f6583977ca0e623b4c705b0054ed9e60d523db2fcfbab2f601fa5cb4548a690
|
|
7
|
+
data.tar.gz: 78cf0596ec74242a08857c23b1cc27deb2594616a497157b60cb2f561726f628f5fd5c4ed5be40d9e0aa16074300ad024f09caa4a09c86f734a7df83975ee34a
|
data/README.md
CHANGED
|
@@ -33,6 +33,24 @@ bin/rails railties:install:migrations FROM=rails_markup
|
|
|
33
33
|
bin/rails db:migrate
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
### Compatibility: `connection_pool` 3.x
|
|
37
|
+
|
|
38
|
+
Rails Markup does **not** use Redis or `connection_pool`. However, Rails 7.2 and
|
|
39
|
+
Rails 8.0 apps configured with `:redis_cache_store` fail to boot against
|
|
40
|
+
`connection_pool` 3.x (released Dec 2025): Active Support passes pool options as
|
|
41
|
+
a positional hash, while `connection_pool` 3 requires keyword arguments, raising
|
|
42
|
+
`ArgumentError: wrong number of arguments (given 1, expected 0)` during
|
|
43
|
+
`Rails.application.initialize!`. This is an upstream Rails/`connection_pool`
|
|
44
|
+
incompatibility (fixed in Rails 8.1), unrelated to this gem — but because it
|
|
45
|
+
breaks boot, it will also break `bin/dev`, `db:migrate`, and the migration-copy
|
|
46
|
+
step above whenever this gem happens to be installed.
|
|
47
|
+
|
|
48
|
+
If you hit it, pin the dependency in your app's Gemfile until you're on Rails 8.1+:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
gem "connection_pool", "< 3.0"
|
|
52
|
+
```
|
|
53
|
+
|
|
36
54
|
### Rails Markup 1.2 rolling UUID upgrade
|
|
37
55
|
|
|
38
56
|
The 1.2 upgrade migration adds, repairs, and uniquely indexes `client_uuid`, but
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
},
|
|
107
107
|
|
|
108
108
|
destroy() {
|
|
109
|
+
this._closeAllMenus();
|
|
109
110
|
this._deactivateMode();
|
|
110
111
|
if (this.sseSource) { this.sseSource.close(); this.sseSource = null; }
|
|
111
112
|
if (this.healthInterval) { clearInterval(this.healthInterval); this.healthInterval = null; }
|
|
@@ -120,12 +121,27 @@
|
|
|
120
121
|
window.removeEventListener("online", this._boundOnline);
|
|
121
122
|
this._boundOnline = null;
|
|
122
123
|
}
|
|
124
|
+
if (this._boundKeyDown) {
|
|
125
|
+
document.removeEventListener("keydown", this._boundKeyDown, true);
|
|
126
|
+
this._boundKeyDown = null;
|
|
127
|
+
}
|
|
123
128
|
if (this._onResize) window.removeEventListener("resize", this._onResize);
|
|
124
129
|
if (this._onScroll) window.removeEventListener("scroll", this._onScroll);
|
|
125
130
|
if (this._boundTurboFrame) {
|
|
126
131
|
document.removeEventListener("turbo:frame-render", this._boundTurboFrame);
|
|
127
132
|
this._boundTurboFrame = null;
|
|
128
133
|
}
|
|
134
|
+
if (this._boundMenuDocClick) {
|
|
135
|
+
document.removeEventListener("click", this._boundMenuDocClick);
|
|
136
|
+
this._boundMenuDocClick = null;
|
|
137
|
+
}
|
|
138
|
+
if (this._boundMenuViewportChange) {
|
|
139
|
+
document.getElementById("rm-panel-list")
|
|
140
|
+
?.removeEventListener("scroll", this._boundMenuViewportChange);
|
|
141
|
+
window.removeEventListener("resize", this._boundMenuViewportChange);
|
|
142
|
+
window.removeEventListener("scroll", this._boundMenuViewportChange);
|
|
143
|
+
this._boundMenuViewportChange = null;
|
|
144
|
+
}
|
|
129
145
|
this._onResize = null;
|
|
130
146
|
this._onScroll = null;
|
|
131
147
|
const root = document.getElementById("rm-toolbar-root");
|
|
@@ -159,19 +175,30 @@
|
|
|
159
175
|
.rm-pins-container { position:absolute; top:0; left:0; width:100%; z-index:9979; pointer-events:none; }
|
|
160
176
|
.rm-pin { pointer-events:auto; }
|
|
161
177
|
.rm-popup { display:none; position:fixed; z-index:9982; width:360px; max-width:calc(100vw - 24px); background:rgba(255,255,255,0.95); backdrop-filter:blur(12px); border-radius:16px; box-shadow:0 25px 50px rgba(0,0,0,0.1); border:1px solid rgba(229,231,235,0.8); padding:16px; }
|
|
162
|
-
.rm-popup textarea { width:100%; font-size:13px; border:1px solid #e5e7eb; border-radius:12px; padding:12px; resize:none; outline:none; font-family:inherit; transition:border-color 0.15s,box-shadow 0.15s; }
|
|
163
|
-
.rm-popup textarea:focus { border
|
|
164
|
-
#rm-toolbar-root .rm-
|
|
165
|
-
#rm-toolbar-root .rm-
|
|
166
|
-
#rm-toolbar-root .rm-
|
|
178
|
+
#rm-toolbar-root .rm-popup textarea { display:block; width:100%; max-width:100%; font-size:13px; font-weight:400; line-height:1.4; color:#1f2937; height:auto; margin:0; border:1px solid #e5e7eb; border-radius:12px; padding:12px; resize:none; outline:none; font-family:inherit; background:#fff; background-image:none; box-shadow:none; appearance:none; -webkit-appearance:none; transition:border-color 0.15s,box-shadow 0.15s; }
|
|
179
|
+
#rm-toolbar-root .rm-popup textarea:focus { border:1px solid #818cf8; box-shadow:0 0 0 3px rgba(99,102,241,0.1); }
|
|
180
|
+
#rm-toolbar-root .rm-menu { position:relative; display:inline-block; vertical-align:middle; }
|
|
181
|
+
#rm-toolbar-root .rm-menu-btn { display:inline-flex; align-items:center; gap:4px; width:auto; height:auto; margin:0; font-size:11px; font-weight:500; line-height:1.4; color:#374151; border:1px solid #e5e7eb; border-radius:8px; padding:6px 8px; background:#fff; background-image:none; box-shadow:none; outline:none; text-transform:none; appearance:none; -webkit-appearance:none; cursor:pointer; }
|
|
182
|
+
#rm-toolbar-root .rm-menu-btn:hover { border-color:#d1d5db; background:#fff; }
|
|
183
|
+
#rm-toolbar-root .rm-menu-btn:focus { outline:none; border-color:#818cf8; box-shadow:0 0 0 3px rgba(99,102,241,0.1); }
|
|
184
|
+
#rm-toolbar-root .rm-menu-btn[aria-expanded="true"] { border-color:#818cf8; }
|
|
185
|
+
#rm-toolbar-root .rm-menu-chevron { width:10px; height:10px; flex-shrink:0; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; opacity:0.55; }
|
|
186
|
+
#rm-toolbar-root .rm-menu-list { display:none; position:absolute; top:calc(100% + 4px); left:0; z-index:9984; min-width:100%; padding:4px; margin:0; list-style:none; background:#fff; border:1px solid #e5e7eb; border-radius:10px; box-shadow:0 10px 24px rgba(0,0,0,0.12); }
|
|
187
|
+
#rm-toolbar-root .rm-menu-list.rm-menu-open { display:block; }
|
|
188
|
+
#rm-toolbar-root .rm-menu-option { display:block; width:100%; margin:0; padding:6px 10px; font-size:11px; font-weight:500; line-height:1.4; color:#374151; text-align:left; border:none; border-radius:6px; background:transparent; background-image:none; box-shadow:none; cursor:pointer; appearance:none; -webkit-appearance:none; }
|
|
189
|
+
#rm-toolbar-root .rm-menu-option:hover, #rm-toolbar-root .rm-menu-option:focus { background:#f3f4f6; outline:none; }
|
|
190
|
+
#rm-toolbar-root .rm-menu-option-active { background:#eef2ff; color:#4338ca; }
|
|
191
|
+
#rm-toolbar-root .rm-menu-compact .rm-menu-btn { font-size:10px; padding:2px 6px; border-radius:4px; color:#6b7280; }
|
|
192
|
+
#rm-toolbar-root .rm-menu-compact .rm-menu-list, #rm-toolbar-root .rm-menu-list-compact { min-width:120px; right:0; left:auto; }
|
|
193
|
+
#rm-toolbar-root .rm-menu-compact .rm-menu-option, #rm-toolbar-root .rm-menu-list-compact .rm-menu-option { font-size:10px; padding:5px 8px; }
|
|
167
194
|
.rm-popup-el { font-size:11px; color:#9ca3af; font-family:monospace; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; line-height:1.4; }
|
|
168
195
|
.rm-popup-text { font-size:12px; color:#6b7280; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:2px; line-height:1.4; }
|
|
169
196
|
.rm-popup-actions { display:flex; align-items:center; gap:8px; margin-top:8px; }
|
|
170
197
|
.rm-popup-actions .rm-count { font-size:10px; color:#d1d5db; margin-left:auto; font-variant-numeric:tabular-nums; }
|
|
171
|
-
.rm-btn-cancel { padding:6px 12px; font-size:12px; color:#9ca3af; background:none; border:none; cursor:pointer; border-radius:8px; }
|
|
172
|
-
.rm-btn-cancel:hover { color:#6b7280; }
|
|
173
|
-
.rm-btn-submit { padding:6px 16px; font-size:12px; font-weight:500; color:#fff; border:none; border-radius:8px; cursor:pointer; display:inline-flex; align-items:center; gap:6px; }
|
|
174
|
-
.rm-btn-submit kbd { font-size:9px; opacity:0.6; font-family:sans-serif; }
|
|
198
|
+
#rm-toolbar-root .rm-btn-cancel { padding:6px 12px; font-size:12px; color:#9ca3af; background:none; background-image:none; border:none; box-shadow:none; cursor:pointer; border-radius:8px; appearance:none; -webkit-appearance:none; }
|
|
199
|
+
#rm-toolbar-root .rm-btn-cancel:hover { color:#6b7280; }
|
|
200
|
+
#rm-toolbar-root .rm-btn-submit { padding:6px 16px; font-size:12px; font-weight:500; color:#fff; border:none; border-radius:8px; cursor:pointer; display:inline-flex; align-items:center; gap:6px; box-shadow:none; appearance:none; -webkit-appearance:none; }
|
|
201
|
+
#rm-toolbar-root .rm-btn-submit kbd { font-size:9px; opacity:0.6; font-family:sans-serif; }
|
|
175
202
|
.rm-panel { display:none; position:fixed; z-index:9981; width:380px; max-width:calc(100vw - 48px); max-height:60vh; background:rgba(255,255,255,0.95); backdrop-filter:blur(12px); border-radius:16px; box-shadow:0 25px 50px rgba(0,0,0,0.1); border:1px solid rgba(229,231,235,0.8); flex-direction:column; }
|
|
176
203
|
.rm-panel-header { display:flex; align-items:center; justify-content:space-between; padding:12px 16px; border-bottom:1px solid #f3f4f6; }
|
|
177
204
|
.rm-panel-header h3 { font-size:14px; font-weight:600; color:#1f2937; }
|
|
@@ -252,17 +279,8 @@
|
|
|
252
279
|
</div>
|
|
253
280
|
<textarea id="rm-popup-input" rows="3" placeholder="What should change?"></textarea>
|
|
254
281
|
<div style="display:flex;align-items:center;gap:8px;margin-top:12px">
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
<option value="change" selected>Change</option>
|
|
258
|
-
<option value="question">Question</option>
|
|
259
|
-
<option value="approve">Approve</option>
|
|
260
|
-
</select>
|
|
261
|
-
<select id="rm-severity-select">
|
|
262
|
-
<option value="suggestion" selected>Suggestion</option>
|
|
263
|
-
<option value="important">Important</option>
|
|
264
|
-
<option value="blocking">Blocking</option>
|
|
265
|
-
</select>
|
|
282
|
+
${this._menuMarkup({ inputId: "rm-intent-select", label: "Intent", value: "change", options: this._intentOptions() })}
|
|
283
|
+
${this._menuMarkup({ inputId: "rm-severity-select", label: "Severity", value: "suggestion", options: this._severityOptions() })}
|
|
266
284
|
<span class="rm-count" id="rm-char-count"></span>
|
|
267
285
|
</div>
|
|
268
286
|
<div class="rm-popup-actions">
|
|
@@ -324,15 +342,43 @@
|
|
|
324
342
|
if (chip) self._filterAnnotations(chip.dataset.filter);
|
|
325
343
|
});
|
|
326
344
|
|
|
345
|
+
// Custom menus (intent/severity/status) — button+menu, never native selects
|
|
346
|
+
// so host FormSelect/Select2 enhancers cannot rewrite our DOM (#4).
|
|
347
|
+
this.root.addEventListener("click", (e) => {
|
|
348
|
+
const option = e.target.closest(".rm-menu-option");
|
|
349
|
+
if (option) {
|
|
350
|
+
e.preventDefault();
|
|
351
|
+
e.stopPropagation();
|
|
352
|
+
this._selectMenuOption(option);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const btn = e.target.closest(".rm-menu-btn");
|
|
356
|
+
if (btn) {
|
|
357
|
+
e.preventDefault();
|
|
358
|
+
e.stopPropagation();
|
|
359
|
+
const menu = this._menuForElement(btn);
|
|
360
|
+
if (!menu || !this.root.contains(menu)) return;
|
|
361
|
+
this._toggleMenu(menu);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (!this._menuForElement(e.target)) this._closeAllMenus();
|
|
365
|
+
});
|
|
366
|
+
if (!this._boundMenuDocClick) {
|
|
367
|
+
this._boundMenuDocClick = (e) => {
|
|
368
|
+
if (!this.root || this._menuForElement(e.target)) return;
|
|
369
|
+
this._closeAllMenus();
|
|
370
|
+
};
|
|
371
|
+
document.addEventListener("click", this._boundMenuDocClick);
|
|
372
|
+
}
|
|
373
|
+
|
|
327
374
|
// Event delegation for cards (status change, edit, delete, or click scrolls to element)
|
|
328
375
|
const panelList = document.getElementById("rm-panel-list");
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
});
|
|
376
|
+
if (!this._boundMenuViewportChange) {
|
|
377
|
+
this._boundMenuViewportChange = () => self._closeAllMenus();
|
|
378
|
+
window.addEventListener("resize", this._boundMenuViewportChange);
|
|
379
|
+
window.addEventListener("scroll", this._boundMenuViewportChange, { passive: true });
|
|
380
|
+
}
|
|
381
|
+
panelList.addEventListener("scroll", this._boundMenuViewportChange, { passive: true });
|
|
336
382
|
panelList.addEventListener("click", (e) => {
|
|
337
383
|
const retryBtn = e.target.closest("[data-retry-client-id]");
|
|
338
384
|
if (retryBtn) {
|
|
@@ -340,8 +386,8 @@
|
|
|
340
386
|
self._retrySync(retryBtn.dataset.retryClientId);
|
|
341
387
|
return;
|
|
342
388
|
}
|
|
343
|
-
// Status
|
|
344
|
-
if (e.target.closest("
|
|
389
|
+
// Status menu — leave bubbling for the root menu handler; don't scroll.
|
|
390
|
+
if (e.target.closest(".rm-menu")) return;
|
|
345
391
|
// Edit button
|
|
346
392
|
const editBtn = e.target.closest("[data-edit-id]");
|
|
347
393
|
if (editBtn) {
|
|
@@ -382,7 +428,10 @@
|
|
|
382
428
|
this._boundMouseDown = (e) => self._handleMouseDown(e);
|
|
383
429
|
this._boundMouseUp = (e) => self._handleMouseUp(e);
|
|
384
430
|
this._boundClick = (e) => self._handleClick(e);
|
|
385
|
-
this._boundKeyDown
|
|
431
|
+
if (!this._boundKeyDown) {
|
|
432
|
+
this._boundKeyDown = (e) => self._handleKeyDown(e);
|
|
433
|
+
document.addEventListener("keydown", this._boundKeyDown, true);
|
|
434
|
+
}
|
|
386
435
|
this._boundTouchStart = (e) => { if (self.active && e.touches[0]) { const t = e.touches[0]; const el = document.elementFromPoint(t.clientX, t.clientY); if (el && !self._isToolbar(el) && e.cancelable) e.preventDefault(); self._handleMouseDown({ clientX: t.clientX, clientY: t.clientY }); } };
|
|
387
436
|
this._boundTouchEnd = (e) => { if (self.active && e.changedTouches[0]) { const t = e.changedTouches[0]; const el = document.elementFromPoint(t.clientX, t.clientY); if (el && !self._isToolbar(el)) { e.preventDefault(); self._handleMouseUp({ clientX: t.clientX, clientY: t.clientY, preventDefault(){}, stopPropagation(){} }); } } };
|
|
388
437
|
|
|
@@ -458,7 +507,6 @@
|
|
|
458
507
|
document.addEventListener("mousedown", this._boundMouseDown, true);
|
|
459
508
|
document.addEventListener("mouseup", this._boundMouseUp, true);
|
|
460
509
|
document.addEventListener("click", this._boundClick, true);
|
|
461
|
-
document.addEventListener("keydown", this._boundKeyDown, true);
|
|
462
510
|
document.addEventListener("touchstart", this._boundTouchStart, true);
|
|
463
511
|
document.addEventListener("touchend", this._boundTouchEnd, true);
|
|
464
512
|
},
|
|
@@ -478,7 +526,6 @@
|
|
|
478
526
|
document.removeEventListener("mousedown", this._boundMouseDown, true);
|
|
479
527
|
document.removeEventListener("mouseup", this._boundMouseUp, true);
|
|
480
528
|
document.removeEventListener("click", this._boundClick, true);
|
|
481
|
-
document.removeEventListener("keydown", this._boundKeyDown, true);
|
|
482
529
|
document.removeEventListener("touchstart", this._boundTouchStart, true);
|
|
483
530
|
document.removeEventListener("touchend", this._boundTouchEnd, true);
|
|
484
531
|
this._removeHighlight();
|
|
@@ -551,12 +598,69 @@
|
|
|
551
598
|
},
|
|
552
599
|
|
|
553
600
|
_handleKeyDown(event) {
|
|
601
|
+
const openList = this.root?.querySelector(".rm-menu-list.rm-menu-open");
|
|
602
|
+
const openMenu = this._menuForElement(openList);
|
|
603
|
+
if (openMenu) {
|
|
604
|
+
const options = Array.from(openList.querySelectorAll(".rm-menu-option"));
|
|
605
|
+
const focusedIndex = options.indexOf(document.activeElement);
|
|
606
|
+
|
|
607
|
+
if (event.key === "Escape") {
|
|
608
|
+
this._closeMenu(openMenu, true);
|
|
609
|
+
event.preventDefault();
|
|
610
|
+
event.stopPropagation();
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (event.key === "Tab") {
|
|
614
|
+
this._closeMenu(openMenu);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
618
|
+
const direction = event.key === "ArrowDown" ? 1 : -1;
|
|
619
|
+
const start = focusedIndex === -1 ? 0 : focusedIndex;
|
|
620
|
+
const next = (start + direction + options.length) % options.length;
|
|
621
|
+
options[next]?.focus();
|
|
622
|
+
event.preventDefault();
|
|
623
|
+
event.stopPropagation();
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (event.key === "Home" || event.key === "End") {
|
|
627
|
+
const option = event.key === "Home" ? options[0] : options[options.length - 1];
|
|
628
|
+
option?.focus();
|
|
629
|
+
event.preventDefault();
|
|
630
|
+
event.stopPropagation();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if ((event.key === "Enter" || event.key === " ") && focusedIndex !== -1) {
|
|
634
|
+
this._selectMenuOption(options[focusedIndex]);
|
|
635
|
+
event.preventDefault();
|
|
636
|
+
event.stopPropagation();
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const trigger = event.target.closest?.(".rm-menu-btn");
|
|
642
|
+
if (trigger && this.root?.contains(trigger) &&
|
|
643
|
+
["Enter", " ", "ArrowDown", "ArrowUp"].includes(event.key)) {
|
|
644
|
+
const menu = this._menuForElement(trigger);
|
|
645
|
+
if (menu) this._openMenu(menu);
|
|
646
|
+
event.preventDefault();
|
|
647
|
+
event.stopPropagation();
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
554
651
|
if (event.key === "Escape") {
|
|
555
652
|
const popup = document.getElementById("rm-popup");
|
|
556
653
|
if (popup && popup.style.display === "block") {
|
|
557
654
|
this._closePopup();
|
|
558
|
-
|
|
655
|
+
event.preventDefault();
|
|
656
|
+
event.stopPropagation();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (this.active) {
|
|
559
660
|
this._deactivateMode();
|
|
661
|
+
event.preventDefault();
|
|
662
|
+
event.stopPropagation();
|
|
663
|
+
return;
|
|
560
664
|
}
|
|
561
665
|
}
|
|
562
666
|
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
@@ -602,6 +706,192 @@
|
|
|
602
706
|
|
|
603
707
|
// ---- Popup ----
|
|
604
708
|
|
|
709
|
+
_intentOptions() {
|
|
710
|
+
return [
|
|
711
|
+
["fix", "Fix"],
|
|
712
|
+
["change", "Change"],
|
|
713
|
+
["question", "Question"],
|
|
714
|
+
["approve", "Approve"]
|
|
715
|
+
];
|
|
716
|
+
},
|
|
717
|
+
|
|
718
|
+
_severityOptions() {
|
|
719
|
+
return [
|
|
720
|
+
["suggestion", "Suggestion"],
|
|
721
|
+
["important", "Important"],
|
|
722
|
+
["blocking", "Blocking"]
|
|
723
|
+
];
|
|
724
|
+
},
|
|
725
|
+
|
|
726
|
+
_statusOptions() {
|
|
727
|
+
return [
|
|
728
|
+
["pending", "Pending"],
|
|
729
|
+
["acknowledged", "Acknowledged"],
|
|
730
|
+
["resolved", "Resolved"],
|
|
731
|
+
["dismissed", "Dismissed"]
|
|
732
|
+
];
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
_menuMarkup({ inputId, statusId, label, value, options, compact }) {
|
|
736
|
+
const current = options.find(([v]) => v === value) || options[0];
|
|
737
|
+
const currentValue = current[0];
|
|
738
|
+
const currentLabel = current[1];
|
|
739
|
+
const menuId = inputId ? `rm-menu-${inputId}` : `rm-menu-status-${statusId}`;
|
|
740
|
+
const listId = `${menuId}-list`;
|
|
741
|
+
const inputAttrs = inputId
|
|
742
|
+
? `id="${inputId}"`
|
|
743
|
+
: `data-status-id="${statusId}"`;
|
|
744
|
+
const optionsHtml = options.map(([v, text]) => {
|
|
745
|
+
const active = v === currentValue;
|
|
746
|
+
return `<button type="button" class="rm-menu-option${active ? " rm-menu-option-active" : ""}" role="option" data-menu-owner="${menuId}" data-value="${v}" aria-selected="${active ? "true" : "false"}">${text}</button>`;
|
|
747
|
+
}).join("");
|
|
748
|
+
return `<div class="rm-menu${compact ? " rm-menu-compact" : ""}" id="${menuId}">` +
|
|
749
|
+
`<input type="hidden" ${inputAttrs} value="${currentValue}">` +
|
|
750
|
+
`<button type="button" class="rm-menu-btn" aria-haspopup="listbox" aria-controls="${listId}" aria-expanded="false" title="${this._esc(label)}" aria-label="${this._esc(label)}">` +
|
|
751
|
+
`<span class="rm-menu-label">${currentLabel}</span>` +
|
|
752
|
+
`<svg class="rm-menu-chevron" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>` +
|
|
753
|
+
`</button>` +
|
|
754
|
+
`<div class="rm-menu-list${compact ? " rm-menu-list-compact" : ""}" id="${listId}" role="listbox" data-menu-owner="${menuId}" aria-label="${this._esc(label)}">${optionsHtml}</div>` +
|
|
755
|
+
`</div>`;
|
|
756
|
+
},
|
|
757
|
+
|
|
758
|
+
_menuForElement(element) {
|
|
759
|
+
if (!element) return null;
|
|
760
|
+
const nestedMenu = element.closest?.(".rm-menu");
|
|
761
|
+
if (nestedMenu) return nestedMenu;
|
|
762
|
+
const ownerId = element.closest?.("[data-menu-owner]")?.dataset.menuOwner;
|
|
763
|
+
return ownerId ? document.getElementById(ownerId) : null;
|
|
764
|
+
},
|
|
765
|
+
|
|
766
|
+
_menuList(menu) {
|
|
767
|
+
if (!menu) return null;
|
|
768
|
+
return menu.querySelector(".rm-menu-list") ||
|
|
769
|
+
document.getElementById(`${menu.id}-list`);
|
|
770
|
+
},
|
|
771
|
+
|
|
772
|
+
_selectMenuOption(option) {
|
|
773
|
+
const menu = this._menuForElement(option);
|
|
774
|
+
if (!menu || !this.root?.contains(menu)) return;
|
|
775
|
+
const value = option.dataset.value;
|
|
776
|
+
const statusInput = menu.querySelector("[data-status-id]");
|
|
777
|
+
const statusId = statusInput?.dataset.statusId;
|
|
778
|
+
this._setMenuValue(menu, value);
|
|
779
|
+
if (!statusId) return;
|
|
780
|
+
const id = parseInt(statusId, 10);
|
|
781
|
+
if (Number.isNaN(id)) return;
|
|
782
|
+
this._changeStatus(id, value);
|
|
783
|
+
this.root.querySelector(`[data-status-id="${statusId}"]`)
|
|
784
|
+
?.closest(".rm-menu")
|
|
785
|
+
?.querySelector(".rm-menu-btn")
|
|
786
|
+
?.focus();
|
|
787
|
+
},
|
|
788
|
+
|
|
789
|
+
_setMenuValue(inputOrMenu, value, restoreFocus = true) {
|
|
790
|
+
if (!inputOrMenu) return;
|
|
791
|
+
const menu = inputOrMenu.classList?.contains("rm-menu")
|
|
792
|
+
? inputOrMenu
|
|
793
|
+
: inputOrMenu.closest?.(".rm-menu");
|
|
794
|
+
const input = menu
|
|
795
|
+
? menu.querySelector('input[type="hidden"]')
|
|
796
|
+
: inputOrMenu;
|
|
797
|
+
if (!input) return;
|
|
798
|
+
const resolvedMenu = menu || input.closest(".rm-menu");
|
|
799
|
+
input.value = value;
|
|
800
|
+
if (!resolvedMenu) return;
|
|
801
|
+
const list = this._menuList(resolvedMenu);
|
|
802
|
+
const option = list?.querySelector(`.rm-menu-option[data-value="${value}"]`);
|
|
803
|
+
const label = resolvedMenu.querySelector(".rm-menu-label");
|
|
804
|
+
if (label && option) label.textContent = option.textContent;
|
|
805
|
+
list?.querySelectorAll(".rm-menu-option").forEach(opt => {
|
|
806
|
+
const active = opt.dataset.value === value;
|
|
807
|
+
opt.classList.toggle("rm-menu-option-active", active);
|
|
808
|
+
opt.setAttribute("aria-selected", active ? "true" : "false");
|
|
809
|
+
});
|
|
810
|
+
this._closeMenu(resolvedMenu, restoreFocus);
|
|
811
|
+
},
|
|
812
|
+
|
|
813
|
+
_toggleMenu(menu) {
|
|
814
|
+
const open = this._menuList(menu)?.classList.contains("rm-menu-open");
|
|
815
|
+
this._closeAllMenus();
|
|
816
|
+
if (!open) this._openMenu(menu);
|
|
817
|
+
},
|
|
818
|
+
|
|
819
|
+
_openMenu(menu) {
|
|
820
|
+
const list = this._menuList(menu);
|
|
821
|
+
const btn = menu.querySelector(".rm-menu-btn");
|
|
822
|
+
if (!list || !btn) return;
|
|
823
|
+
this._closeAllMenus();
|
|
824
|
+
const rect = btn.getBoundingClientRect();
|
|
825
|
+
const gap = 4;
|
|
826
|
+
const viewportPadding = 8;
|
|
827
|
+
const compactWidth = menu.classList.contains("rm-menu-compact") ? 120 : 0;
|
|
828
|
+
const minimumWidth = Math.max(rect.width || 0, compactWidth);
|
|
829
|
+
|
|
830
|
+
list._rmMenuPortalOrigin = {
|
|
831
|
+
parent: list.parentNode,
|
|
832
|
+
nextSibling: list.nextSibling
|
|
833
|
+
};
|
|
834
|
+
this.root.appendChild(list);
|
|
835
|
+
list.style.pointerEvents = "auto";
|
|
836
|
+
list.style.position = "fixed";
|
|
837
|
+
list.style.top = "0px";
|
|
838
|
+
list.style.bottom = "";
|
|
839
|
+
list.style.left = "0px";
|
|
840
|
+
list.style.right = "auto";
|
|
841
|
+
list.style.minWidth = `${minimumWidth}px`;
|
|
842
|
+
list.classList.add("rm-menu-open");
|
|
843
|
+
btn.setAttribute("aria-expanded", "true");
|
|
844
|
+
|
|
845
|
+
const menuWidth = Math.max(list.offsetWidth || 0, rect.width || 0, compactWidth);
|
|
846
|
+
const menuHeight = list.offsetHeight || 0;
|
|
847
|
+
const spaceBelow = window.innerHeight - rect.bottom - viewportPadding;
|
|
848
|
+
const spaceAbove = rect.top - viewportPadding;
|
|
849
|
+
const openUp = menuHeight > spaceBelow && spaceAbove > spaceBelow;
|
|
850
|
+
const preferredLeft = compactWidth ? rect.right - menuWidth : rect.left;
|
|
851
|
+
const maxLeft = Math.max(viewportPadding, window.innerWidth - menuWidth - viewportPadding);
|
|
852
|
+
const left = Math.min(Math.max(viewportPadding, preferredLeft), maxLeft);
|
|
853
|
+
|
|
854
|
+
list.style.position = "fixed";
|
|
855
|
+
list.style.top = openUp ? "" : `${rect.bottom + gap}px`;
|
|
856
|
+
list.style.bottom = openUp ? `${window.innerHeight - rect.top + gap}px` : "";
|
|
857
|
+
list.style.left = `${left}px`;
|
|
858
|
+
list.style.right = "auto";
|
|
859
|
+
list.style.minWidth = `${minimumWidth}px`;
|
|
860
|
+
const availableHeight = Math.max(0, (openUp ? spaceAbove : spaceBelow) - gap);
|
|
861
|
+
list.style.maxHeight = availableHeight ? `${availableHeight}px` : "";
|
|
862
|
+
list.style.overflowY = menuHeight > availableHeight && availableHeight > 0 ? "auto" : "";
|
|
863
|
+
|
|
864
|
+
const selected = list.querySelector('.rm-menu-option[aria-selected="true"]') ||
|
|
865
|
+
list.querySelector(".rm-menu-option");
|
|
866
|
+
selected?.focus();
|
|
867
|
+
},
|
|
868
|
+
|
|
869
|
+
_closeMenu(menu, restoreFocus = false) {
|
|
870
|
+
const list = this._menuList(menu);
|
|
871
|
+
const btn = menu.querySelector(".rm-menu-btn");
|
|
872
|
+
if (list) {
|
|
873
|
+
list.classList.remove("rm-menu-open");
|
|
874
|
+
["position", "top", "bottom", "left", "right", "minWidth", "maxHeight", "overflowY", "pointerEvents"]
|
|
875
|
+
.forEach(property => { list.style[property] = ""; });
|
|
876
|
+
const origin = list._rmMenuPortalOrigin;
|
|
877
|
+
if (origin?.parent) {
|
|
878
|
+
if (origin.nextSibling?.parentNode === origin.parent) {
|
|
879
|
+
origin.parent.insertBefore(list, origin.nextSibling);
|
|
880
|
+
} else {
|
|
881
|
+
origin.parent.appendChild(list);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
delete list._rmMenuPortalOrigin;
|
|
885
|
+
}
|
|
886
|
+
if (btn) btn.setAttribute("aria-expanded", "false");
|
|
887
|
+
if (restoreFocus) btn?.focus();
|
|
888
|
+
},
|
|
889
|
+
|
|
890
|
+
_closeAllMenus() {
|
|
891
|
+
if (!this.root) return;
|
|
892
|
+
this.root.querySelectorAll(".rm-menu").forEach(menu => this._closeMenu(menu));
|
|
893
|
+
},
|
|
894
|
+
|
|
605
895
|
_showPopup(x, y) {
|
|
606
896
|
const popup = document.getElementById("rm-popup");
|
|
607
897
|
// Clean up previous drawing elements
|
|
@@ -638,10 +928,11 @@
|
|
|
638
928
|
: this._currentElement.nearbyText.slice(0, 60);
|
|
639
929
|
const input = document.getElementById("rm-popup-input");
|
|
640
930
|
input.value = "";
|
|
641
|
-
document.getElementById("rm-intent-select")
|
|
642
|
-
document.getElementById("rm-severity-select")
|
|
931
|
+
this._setMenuValue(document.getElementById("rm-intent-select"), "change", false);
|
|
932
|
+
this._setMenuValue(document.getElementById("rm-severity-select"), "suggestion", false);
|
|
643
933
|
document.getElementById("rm-char-count").textContent = "";
|
|
644
934
|
document.getElementById("rm-submit-label").textContent = "Add";
|
|
935
|
+
this._closeAllMenus();
|
|
645
936
|
|
|
646
937
|
// Show screenshot preview with drawing tools
|
|
647
938
|
if (this._currentScreenshot) {
|
|
@@ -652,6 +943,7 @@
|
|
|
652
943
|
},
|
|
653
944
|
|
|
654
945
|
_closePopup() {
|
|
946
|
+
this._closeAllMenus();
|
|
655
947
|
const popup = document.getElementById("rm-popup");
|
|
656
948
|
popup.style.transition = "opacity 0.15s ease";
|
|
657
949
|
popup.style.opacity = "0";
|
|
@@ -806,12 +1098,13 @@
|
|
|
806
1098
|
<span class="rm-card-badge" style="background:${ic.bg};color:${ic.text}">${annotation.intent}</span>
|
|
807
1099
|
${annotation.severity !== "suggestion" ? '<span class="rm-card-badge" style="background:#fff7ed;color:#9a3412">' + annotation.severity + '</span>' : ''}
|
|
808
1100
|
<span style="margin-left:auto;display:flex;gap:2px;align-items:center;">
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1101
|
+
${this._menuMarkup({
|
|
1102
|
+
statusId: annotation.id,
|
|
1103
|
+
label: "Change status",
|
|
1104
|
+
value: annotation.status,
|
|
1105
|
+
options: this._statusOptions(),
|
|
1106
|
+
compact: true
|
|
1107
|
+
})}
|
|
815
1108
|
<button data-edit-id="${annotation.id}" title="Edit" style="padding:2px 4px;background:none;border:none;cursor:pointer;color:#d1d5db;border-radius:4px;display:flex;align-items:center;" onmouseover="this.style.color='#6b7280'" onmouseout="this.style.color='#d1d5db'">
|
|
816
1109
|
<svg viewBox="0 0 24 24" style="width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;"><path d="M17 3a2.85 2.85 0 114 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
|
|
817
1110
|
</button>
|
|
@@ -832,6 +1125,7 @@
|
|
|
832
1125
|
_rebuildList() {
|
|
833
1126
|
const list = document.getElementById("rm-panel-list");
|
|
834
1127
|
if (!list) return;
|
|
1128
|
+
this._closeAllMenus();
|
|
835
1129
|
list.innerHTML = "";
|
|
836
1130
|
this._renderStorageError(list);
|
|
837
1131
|
this._renderFailedSync(list);
|
|
@@ -886,10 +1180,11 @@
|
|
|
886
1180
|
? '"' + annotation.selectedText.slice(0, 60) + '"'
|
|
887
1181
|
: (annotation.element?.nearbyText || "").slice(0, 60);
|
|
888
1182
|
document.getElementById("rm-popup-input").value = annotation.comment;
|
|
889
|
-
document.getElementById("rm-intent-select")
|
|
890
|
-
document.getElementById("rm-severity-select")
|
|
1183
|
+
this._setMenuValue(document.getElementById("rm-intent-select"), annotation.intent, false);
|
|
1184
|
+
this._setMenuValue(document.getElementById("rm-severity-select"), annotation.severity, false);
|
|
891
1185
|
document.getElementById("rm-submit-label").textContent = "Save";
|
|
892
1186
|
this._updateCharCount();
|
|
1187
|
+
this._closeAllMenus();
|
|
893
1188
|
|
|
894
1189
|
// Clean up previous drawing elements
|
|
895
1190
|
const popup = document.getElementById("rm-popup");
|
data/lib/rails_markup/version.rb
CHANGED