plumbo 0.1.0 → 0.2.0
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 +11 -0
- data/lib/plumbo/configuration.rb +8 -0
- data/lib/plumbo/panel.rb +78 -7
- data/lib/plumbo/railtie.rb +11 -0
- data/lib/plumbo/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9d835f22037aca3f59bd7784b5b53f0df96d6f357058f7fee0feb2960fdd2dc
|
|
4
|
+
data.tar.gz: 046c9e808514912b46a1db902f5b4eaee0cc24505161461bf2f8a70559ce15c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b7e38789548920e3de8e6a02bb461cba040a68440e71b133049ca6761df508a00d4aa6287f59dc73cb19e7064a1ac14b9ed1db7cb821055338559caccf4364a4
|
|
7
|
+
data.tar.gz: c4ec8aa1b0181cef27a1955dc220aa9051bdd59c71cfc8fb131d6c08bda3147392eec9bd3da1a7018aa60092636cd79f2caa7b475a2bdefeb0fe376b23af80f0
|
data/README.md
CHANGED
|
@@ -31,6 +31,9 @@ showing how many files rendered the current page.
|
|
|
31
31
|
and Stimulus controllers nested (and color-coded) under their parent.
|
|
32
32
|
- **Collapse or expand** a parent by clicking its row; **copy** a single path with
|
|
33
33
|
its copy icon, or **Copy All** for the whole (filtered) list.
|
|
34
|
+
- **Hover a row** to highlight the part of the page that file rendered — a partial
|
|
35
|
+
used in a loop lights up every instance. Rows with no on-page output (controllers,
|
|
36
|
+
helpers, JavaScript) simply don't highlight.
|
|
34
37
|
- **Filter** by typing in the search box, or click a type chip — Controllers,
|
|
35
38
|
Views, Partials, Stimulus, … — to show just one kind.
|
|
36
39
|
- **Clear All** empties the list so you can watch fresh files appear as you click
|
|
@@ -49,6 +52,7 @@ Plumbo.configure do |c|
|
|
|
49
52
|
c.max_files = 500 # safety cap on listed files
|
|
50
53
|
c.include_stimulus = true # list Stimulus controllers
|
|
51
54
|
c.javascript_root = "app/javascript" # source dir Stimulus paths map into
|
|
55
|
+
c.highlight = true # highlight a file's region on hover
|
|
52
56
|
end
|
|
53
57
|
```
|
|
54
58
|
|
|
@@ -65,6 +69,13 @@ every response carries an `X-Plumbo-Files` header that the panel reads on each
|
|
|
65
69
|
> detected — those emitted by helpers or ViewComponents, and other JavaScript,
|
|
66
70
|
> aren't listed.
|
|
67
71
|
|
|
72
|
+
Hover highlighting reuses Rails' built-in
|
|
73
|
+
`annotate_rendered_view_with_filenames`, which Plumbo enables in development so each
|
|
74
|
+
rendered template and partial is wrapped in `<!-- BEGIN … -->`/`<!-- END … -->`
|
|
75
|
+
comments. The panel reads those markers to locate a file's output on the page. This
|
|
76
|
+
adds the comments to your development HTML; set `c.highlight = false` to leave the
|
|
77
|
+
markup untouched (the panel still lists files, just without hover highlighting).
|
|
78
|
+
|
|
68
79
|
## Notes
|
|
69
80
|
|
|
70
81
|
- Production-safe: disabled outside development by default.
|
data/lib/plumbo/configuration.rb
CHANGED
|
@@ -18,6 +18,13 @@ module Plumbo
|
|
|
18
18
|
# in the rendered HTML. Defaults to true.
|
|
19
19
|
attr_accessor :include_stimulus
|
|
20
20
|
|
|
21
|
+
# Whether hovering a view/partial/layout row highlights its rendered region
|
|
22
|
+
# on the page. Defaults to true. When on, Plumbo enables Rails' built-in
|
|
23
|
+
# annotate_rendered_view_with_filenames so the rendered HTML carries the
|
|
24
|
+
# BEGIN/END comment markers the highlight reads. Turn off to leave the host
|
|
25
|
+
# app's HTML untouched (highlighting then does nothing).
|
|
26
|
+
attr_accessor :highlight
|
|
27
|
+
|
|
21
28
|
# Source directory Stimulus controllers are mapped into. Combined with the
|
|
22
29
|
# "controllers/" subdirectory and the Stimulus identifier to form the path.
|
|
23
30
|
attr_accessor :javascript_root
|
|
@@ -29,6 +36,7 @@ module Plumbo
|
|
|
29
36
|
@max_files = 500
|
|
30
37
|
@path_prefix = "@"
|
|
31
38
|
@include_stimulus = true
|
|
39
|
+
@highlight = true
|
|
32
40
|
@javascript_root = "app/javascript"
|
|
33
41
|
@root = nil
|
|
34
42
|
end
|
data/lib/plumbo/panel.rb
CHANGED
|
@@ -94,8 +94,9 @@ module Plumbo
|
|
|
94
94
|
#plumbo .plumbo-chip{flex:none;white-space:nowrap;color:#9ca3af;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);border-radius:9999px;padding:2px 8px;font-size:10px}
|
|
95
95
|
#plumbo .plumbo-chip:hover{background:rgba(255,255,255,.1);color:#fff}
|
|
96
96
|
#plumbo .plumbo-chip[aria-pressed="true"]{background:#2563eb;border-color:#2563eb;color:#fff}
|
|
97
|
-
#plumbo .plumbo-list{flex:1;min-height:0;list-style:none;margin:0;padding:0;overflow
|
|
98
|
-
#plumbo .plumbo-
|
|
97
|
+
#plumbo .plumbo-list{flex:1;min-height:0;list-style:none;margin:0;padding:0;overflow:auto;background:#0b1220;scrollbar-width:none}
|
|
98
|
+
#plumbo .plumbo-list::-webkit-scrollbar{display:none}
|
|
99
|
+
#plumbo .plumbo-row{--d:0;display:flex;align-items:center;gap:8px;width:max-content;min-width:100%;padding:7px 16px;padding-left:calc(16px + var(--d) * 14px);color:#d1d5db;text-align:left;cursor:default;background-image:repeating-linear-gradient(to right,rgba(255,255,255,.09) 0,rgba(255,255,255,.09) 1px,transparent 1px,transparent 14px);background-repeat:no-repeat;background-position:20px 0;background-size:calc(var(--d) * 14px) 100%}
|
|
99
100
|
#plumbo .plumbo-row:hover{background-color:rgba(255,255,255,.05);color:#fff}
|
|
100
101
|
#plumbo .plumbo-row.plumbo-parent{cursor:pointer}
|
|
101
102
|
#plumbo .plumbo-caret{flex:none;width:12px;display:flex;color:#6b7280}
|
|
@@ -105,9 +106,9 @@ module Plumbo
|
|
|
105
106
|
#plumbo .plumbo-row.plumbo-parent:hover .plumbo-caret{color:#fff}
|
|
106
107
|
#plumbo .plumbo-type{flex:none;display:flex;color:#6b7280}
|
|
107
108
|
#plumbo .plumbo-row:hover .plumbo-type{color:#9ca3af}
|
|
108
|
-
#plumbo .plumbo-path{flex:
|
|
109
|
-
#plumbo .plumbo-copy{margin-left:auto;flex:none;color:#4b5563;display:flex;cursor:pointer}
|
|
110
|
-
#plumbo .plumbo-row:hover .plumbo-copy{color:#9ca3af}
|
|
109
|
+
#plumbo .plumbo-path{flex:none;white-space:nowrap}
|
|
110
|
+
#plumbo .plumbo-copy{position:sticky;right:16px;margin-left:auto;flex:none;color:#4b5563;display:flex;cursor:pointer;padding-left:20px;background:linear-gradient(to right,rgba(11,18,32,0),#0b1220 16px)}
|
|
111
|
+
#plumbo .plumbo-row:hover .plumbo-copy{color:#9ca3af;background:linear-gradient(to right,rgba(23,30,43,0),#171e2b 16px)}
|
|
111
112
|
#plumbo .plumbo-row[data-depth="1"] .plumbo-path{color:#93c5fd}
|
|
112
113
|
#plumbo .plumbo-row[data-depth="1"] .plumbo-type{color:#60a5fa}
|
|
113
114
|
#plumbo .plumbo-row[data-depth="2"] .plumbo-path{color:#c4b5fd}
|
|
@@ -120,6 +121,7 @@ module Plumbo
|
|
|
120
121
|
#plumbo .plumbo-row[data-depth="5"] .plumbo-type{color:#f472b6}
|
|
121
122
|
#plumbo .plumbo-flash{color:#4ade80}
|
|
122
123
|
#plumbo svg{display:block}
|
|
124
|
+
.plumbo-hl{position:fixed;z-index:2147482000;pointer-events:none;background:rgba(59,130,246,.18);outline:2px solid #3b82f6;outline-offset:-1px;border-radius:2px}
|
|
123
125
|
CSS
|
|
124
126
|
|
|
125
127
|
JS = <<~JS.freeze
|
|
@@ -163,7 +165,7 @@ module Plumbo
|
|
|
163
165
|
}
|
|
164
166
|
else if (hit.hasAttribute("data-plumbo-clear")) {
|
|
165
167
|
var emptied = root.querySelector("#plumbo-list");
|
|
166
|
-
if (emptied) { emptied.innerHTML = ""; refresh(); }
|
|
168
|
+
if (emptied) { emptied.innerHTML = ""; clearHighlight(); refresh(); }
|
|
167
169
|
}
|
|
168
170
|
else if (hit.hasAttribute("data-plumbo-chip")) {
|
|
169
171
|
var cat = hit.getAttribute("data-category");
|
|
@@ -290,6 +292,75 @@ module Plumbo
|
|
|
290
292
|
if (html) { list.insertAdjacentHTML("beforeend", html); }
|
|
291
293
|
refresh();
|
|
292
294
|
}
|
|
295
|
+
// Hover-to-highlight: map a panel row back to the region(s) it rendered.
|
|
296
|
+
// Relies on Rails' annotate_rendered_view_with_filenames, which wraps each
|
|
297
|
+
// template/partial in `<!-- BEGIN path -->`/`<!-- END path -->` comments
|
|
298
|
+
// (path relative to Rails.root). Overlays are throwaway, pointer-events:none.
|
|
299
|
+
var overlays = [];
|
|
300
|
+
function clearHighlight(){
|
|
301
|
+
for (var i = 0; i < overlays.length; i++){ if (overlays[i].parentNode) overlays[i].parentNode.removeChild(overlays[i]); }
|
|
302
|
+
overlays = [];
|
|
303
|
+
}
|
|
304
|
+
// Walk every comment node and pair BEGIN/END markers by path using a stack
|
|
305
|
+
// (markers nest cleanly), returning {path, begin, end} for each pair.
|
|
306
|
+
function commentPairs(){
|
|
307
|
+
var pairs = [], stack = [];
|
|
308
|
+
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null, false);
|
|
309
|
+
var node;
|
|
310
|
+
while ((node = walker.nextNode())){
|
|
311
|
+
var m = /^\\s*(BEGIN|END)\\s+(.+?)\\s*$/.exec(node.nodeValue || "");
|
|
312
|
+
if (!m) continue;
|
|
313
|
+
if (m[1] === "BEGIN"){ stack.push({ path: m[2], begin: node }); }
|
|
314
|
+
else {
|
|
315
|
+
for (var i = stack.length - 1; i >= 0; i--){
|
|
316
|
+
if (stack[i].path === m[2]){ pairs.push({ path: m[2], begin: stack[i].begin, end: node }); stack.splice(i, 1); break; }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return pairs;
|
|
321
|
+
}
|
|
322
|
+
// Draw one fixed overlay around the whole region a row rendered — a single
|
|
323
|
+
// bounding box, not one per line/inline fragment, so nested links and labels
|
|
324
|
+
// don't each get their own border. A looped partial still yields one box per
|
|
325
|
+
// instance (one comment pair each). A row's data-path carries the configured
|
|
326
|
+
// prefix (e.g. "@app/..."); the comment path doesn't — endsWith matches
|
|
327
|
+
// regardless of prefix. Rows with no rendered region simply match nothing.
|
|
328
|
+
function highlight(path){
|
|
329
|
+
clearHighlight();
|
|
330
|
+
if (!path) return;
|
|
331
|
+
var pairs = commentPairs();
|
|
332
|
+
for (var i = 0; i < pairs.length; i++){
|
|
333
|
+
if (!path.endsWith(pairs[i].path)) continue;
|
|
334
|
+
var range = document.createRange();
|
|
335
|
+
try { range.setStartAfter(pairs[i].begin); range.setEndBefore(pairs[i].end); } catch (e) { continue; }
|
|
336
|
+
var rect = range.getBoundingClientRect();
|
|
337
|
+
if (rect.width <= 0 && rect.height <= 0) continue;
|
|
338
|
+
var box = document.createElement("div");
|
|
339
|
+
box.className = "plumbo-hl";
|
|
340
|
+
box.style.cssText = "top:" + rect.top + "px;left:" + rect.left + "px;width:" + rect.width + "px;height:" + rect.height + "px";
|
|
341
|
+
document.body.appendChild(box);
|
|
342
|
+
overlays.push(box);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function rowFor(target, root){
|
|
346
|
+
if (!root || !target || !target.closest) return null;
|
|
347
|
+
var row = target.closest(".plumbo-row");
|
|
348
|
+
return (row && root.contains(row)) ? row : null;
|
|
349
|
+
}
|
|
350
|
+
// Delegate hover like the click/input handlers. mouseover bubbles, so it
|
|
351
|
+
// fires for every move; track the row under the cursor and only re-highlight
|
|
352
|
+
// when it changes (re-paint on row→row, clear when leaving rows). A null
|
|
353
|
+
// relatedTarget on mouseout means the pointer left the window — clear then.
|
|
354
|
+
var hoveredRow = null;
|
|
355
|
+
document.addEventListener("mouseover", function(event){
|
|
356
|
+
var row = rowFor(event.target, document.getElementById("plumbo"));
|
|
357
|
+
if (row === hoveredRow) return;
|
|
358
|
+
hoveredRow = row;
|
|
359
|
+
highlight(row ? row.getAttribute("data-path") : null);
|
|
360
|
+
});
|
|
361
|
+
document.addEventListener("mouseout", function(event){
|
|
362
|
+
if (!event.relatedTarget && hoveredRow) { hoveredRow = null; clearHighlight(); }
|
|
363
|
+
});
|
|
293
364
|
refresh();
|
|
294
365
|
// Read the file list off every fetch response (Turbo Drive/Frame/Stream
|
|
295
366
|
// and custom fetch all go through fetch) so the panel keeps up without a
|
|
@@ -305,7 +376,7 @@ module Plumbo
|
|
|
305
376
|
}
|
|
306
377
|
// A full Turbo Drive visit swaps in a fresh panel; reset the filter to
|
|
307
378
|
// match the new page, then rebuild.
|
|
308
|
-
document.addEventListener("turbo:load", function(){ query = ""; activeCategory = null; refresh(); });
|
|
379
|
+
document.addEventListener("turbo:load", function(){ query = ""; activeCategory = null; clearHighlight(); refresh(); });
|
|
309
380
|
})();
|
|
310
381
|
JS
|
|
311
382
|
|
data/lib/plumbo/railtie.rb
CHANGED
|
@@ -10,5 +10,16 @@ module Plumbo
|
|
|
10
10
|
initializer "plumbo.middleware" do |app|
|
|
11
11
|
app.middleware.use Plumbo::Middleware
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
# Enable Rails' filename annotations so each rendered template/partial is
|
|
15
|
+
# wrapped in BEGIN/END HTML comments — the markers the panel's hover-to-
|
|
16
|
+
# highlight reads to map a row back to its region on the page. Templates bake
|
|
17
|
+
# the comments in at compile time, so this must be set before Action View is
|
|
18
|
+
# configured rather than per-request. Only ever switched on (never off) so a
|
|
19
|
+
# host that enabled it independently keeps it.
|
|
20
|
+
initializer "plumbo.annotate_views", before: "action_view.setup" do |app|
|
|
21
|
+
config = Plumbo.config
|
|
22
|
+
app.config.action_view.annotate_rendered_view_with_filenames = true if config.enabled && config.highlight
|
|
23
|
+
end
|
|
13
24
|
end
|
|
14
25
|
end
|
data/lib/plumbo/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: plumbo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt Sears
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-06-
|
|
10
|
+
date: 2026-06-29 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|