rails-wayback 0.1.0 → 0.1.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/README.md +4 -0
- data/lib/rails_wayback/bar_renderer.css +55 -0
- data/lib/rails_wayback/bar_renderer.html.erb +22 -0
- data/lib/rails_wayback/bar_renderer.js +132 -0
- data/lib/rails_wayback/bar_renderer.rb +8 -213
- data/lib/rails_wayback/cli.rb +1 -1
- data/lib/rails_wayback/engine.rb +7 -0
- data/lib/rails_wayback/git.rb +3 -0
- data/lib/rails_wayback/toggle.rb +10 -2
- data/lib/rails_wayback/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d994f992691b844562d34eb5e06afaf69be7502d98c2107313a7fabcfe99209
|
|
4
|
+
data.tar.gz: d8175d1cd12a74f6db23b6a58c4d26acd11a36825a98b6c49da50515c154d945
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c90265c6459015b3c684fe0f3446f52bda36d1fae2e54cdbba0beb7748d912add4da80946b5d431c2e0f174975d9ceb1591ec24bb3163b5087bb72fc46f6994
|
|
7
|
+
data.tar.gz: ba0e7095c8ebc4e2ea1d8034b9b3c9f49ed28a4b26608977c9cc9fc2a83c4594ec9e59476abb73b7c6856fec107ff85e8ae45b0a4b1818e0b6f8388f80ca204d
|
data/README.md
CHANGED
|
@@ -56,6 +56,10 @@ bundle exec rails-wayback clean # drop the tmp/rails_wayback ref cache
|
|
|
56
56
|
Same commands as rake tasks: `rake wayback:on`, `wayback:off`,
|
|
57
57
|
`wayback:status`, `wayback:clean`.
|
|
58
58
|
|
|
59
|
+
Activation lasts for the current Rails server session. Every new server start
|
|
60
|
+
begins with `rails-wayback` disabled, while `on` and `off` can still be used at
|
|
61
|
+
any time without restarting Rails.
|
|
62
|
+
|
|
59
63
|
With the gem enabled, load any page of your app. A dark bar appears at the
|
|
60
64
|
bottom with:
|
|
61
65
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#rails-wayback-bar {
|
|
2
|
+
position: fixed; left: 0; right: 0; bottom: 0; z-index: 2147483000;
|
|
3
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
4
|
+
font-size: 12px; color: #e6e9ef;
|
|
5
|
+
background: rgba(15, 17, 21, 0.96);
|
|
6
|
+
border-top: 1px solid #2a2f3a;
|
|
7
|
+
box-shadow: 0 -8px 20px rgba(0,0,0,.35);
|
|
8
|
+
}
|
|
9
|
+
#rails-wayback-bar * { box-sizing: border-box; }
|
|
10
|
+
#rails-wayback-bar .rw-bar-inner {
|
|
11
|
+
display: flex; align-items: end; gap: 12px; padding: 8px 12px;
|
|
12
|
+
}
|
|
13
|
+
#rails-wayback-bar .rw-field { display: flex; flex-direction: column; gap: 3px; min-width: 180px; }
|
|
14
|
+
#rails-wayback-bar .rw-flex { flex: 1; }
|
|
15
|
+
#rails-wayback-bar label {
|
|
16
|
+
color: #8a93a6; text-transform: uppercase; letter-spacing: .06em; font-size: 10px;
|
|
17
|
+
}
|
|
18
|
+
#rails-wayback-bar select {
|
|
19
|
+
background: #0b0d12; color: #e6e9ef;
|
|
20
|
+
border: 1px solid #2a2f3a; border-radius: 4px;
|
|
21
|
+
padding: 5px 8px; font-family: inherit; font-size: 12px; width: 100%;
|
|
22
|
+
}
|
|
23
|
+
#rails-wayback-bar .rw-state { color: #8a93a6; font-size: 11px; margin: 0 4px; }
|
|
24
|
+
#rails-wayback-bar .rw-state.rw-active { color: #ffd479; }
|
|
25
|
+
#rails-wayback-bar .rw-btn {
|
|
26
|
+
border: 0; border-radius: 4px; padding: 8px 14px; cursor: pointer;
|
|
27
|
+
font-family: inherit; font-size: 12px; font-weight: 600;
|
|
28
|
+
}
|
|
29
|
+
#rails-wayback-bar .rw-btn-primary { background: #6ea8fe; color: #0b0d12; }
|
|
30
|
+
#rails-wayback-bar .rw-btn-ghost { background: transparent; color: #8a93a6; border: 1px solid #2a2f3a; }
|
|
31
|
+
#rails-wayback-bar .rw-btn:disabled { opacity: .5; cursor: not-allowed; }
|
|
32
|
+
#rails-wayback-bar .rw-diff {
|
|
33
|
+
border-top: 1px solid #2a2f3a;
|
|
34
|
+
padding: 6px 12px; font-size: 11px; color: #8a93a6;
|
|
35
|
+
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
|
36
|
+
}
|
|
37
|
+
#rails-wayback-bar .rw-diff-match .rw-diff-msg { color: #7ee787; }
|
|
38
|
+
#rails-wayback-bar .rw-diff-miss .rw-diff-msg { color: #ffd479; }
|
|
39
|
+
#rails-wayback-bar .rw-diff-neutral .rw-diff-msg { color: #8a93a6; }
|
|
40
|
+
#rails-wayback-bar .rw-diff-details { color: #8a93a6; }
|
|
41
|
+
#rails-wayback-bar .rw-diff-details summary {
|
|
42
|
+
cursor: pointer; user-select: none;
|
|
43
|
+
}
|
|
44
|
+
#rails-wayback-bar .rw-diff-details[open] {
|
|
45
|
+
flex-basis: 100%;
|
|
46
|
+
}
|
|
47
|
+
#rails-wayback-bar .rw-diff-details ul {
|
|
48
|
+
margin: 6px 0 0; padding: 6px 10px; list-style: none;
|
|
49
|
+
background: #0b0d12; border: 1px solid #2a2f3a; border-radius: 4px;
|
|
50
|
+
max-height: 200px; overflow: auto;
|
|
51
|
+
font-family: inherit; font-size: 11px;
|
|
52
|
+
}
|
|
53
|
+
#rails-wayback-bar .rw-diff-details li { padding: 2px 0; color: #c8cfdc; }
|
|
54
|
+
body { padding-bottom: 56px !important; }
|
|
55
|
+
body:has(#rails-wayback-bar .rw-diff) { padding-bottom: 88px !important; }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<style data-rails-wayback><%= styles %></style>
|
|
2
|
+
<div id="rails-wayback-bar" data-mount="<%= escape(@engine_mount) %>"
|
|
3
|
+
data-branch="<%= escape(@current_branch) %>"
|
|
4
|
+
data-commit="<%= escape(@current_commit) %>"
|
|
5
|
+
data-active-ref="<%= escape(@active_ref.to_s) %>"
|
|
6
|
+
data-active-branch="<%= escape(@active_branch.to_s) %>">
|
|
7
|
+
<div class="rw-bar-inner">
|
|
8
|
+
<div class="rw-field">
|
|
9
|
+
<label><%= escape(branch_label) %></label>
|
|
10
|
+
<select data-rw-branch></select>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="rw-field rw-flex">
|
|
13
|
+
<label><%= escape(commit_label) %></label>
|
|
14
|
+
<select data-rw-commit></select>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="rw-state" data-rw-state></div>
|
|
17
|
+
<button type="button" data-rw-travel class="rw-btn rw-btn-primary">Travel</button>
|
|
18
|
+
<button type="button" data-rw-reset class="rw-btn rw-btn-ghost" hidden>Return to HEAD</button>
|
|
19
|
+
</div>
|
|
20
|
+
<%= diff_summary_html %>
|
|
21
|
+
</div>
|
|
22
|
+
<script data-rails-wayback><%= script %></script>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const bar = document.getElementById("rails-wayback-bar");
|
|
3
|
+
if (!bar) return;
|
|
4
|
+
|
|
5
|
+
const mount = bar.dataset.mount;
|
|
6
|
+
const headBranch = bar.dataset.branch;
|
|
7
|
+
const headCommit = bar.dataset.commit;
|
|
8
|
+
const activeRef = bar.dataset.activeRef || "";
|
|
9
|
+
const activeBranch = bar.dataset.activeBranch || "";
|
|
10
|
+
|
|
11
|
+
// While traveling, reflect the ref's branch/commit in the
|
|
12
|
+
// dropdowns instead of the developer's local HEAD, so the bar
|
|
13
|
+
// matches what is actually being rendered.
|
|
14
|
+
const selectedBranch = activeBranch || headBranch;
|
|
15
|
+
const selectedCommit = activeRef || headCommit;
|
|
16
|
+
|
|
17
|
+
const branchSel = bar.querySelector("[data-rw-branch]");
|
|
18
|
+
const commitSel = bar.querySelector("[data-rw-commit]");
|
|
19
|
+
const stateEl = bar.querySelector("[data-rw-state]");
|
|
20
|
+
const travelBtn = bar.querySelector("[data-rw-travel]");
|
|
21
|
+
const resetBtn = bar.querySelector("[data-rw-reset]");
|
|
22
|
+
|
|
23
|
+
function setState() {
|
|
24
|
+
if (activeRef) {
|
|
25
|
+
const label = activeBranch
|
|
26
|
+
? activeBranch + "@" + activeRef.slice(0, 7)
|
|
27
|
+
: activeRef.slice(0, 7);
|
|
28
|
+
stateEl.textContent = "Traveling: " + label;
|
|
29
|
+
stateEl.classList.add("rw-active");
|
|
30
|
+
resetBtn.hidden = false;
|
|
31
|
+
} else {
|
|
32
|
+
stateEl.textContent = "Live";
|
|
33
|
+
stateEl.classList.remove("rw-active");
|
|
34
|
+
resetBtn.hidden = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function showError(msg) {
|
|
39
|
+
stateEl.textContent = msg;
|
|
40
|
+
stateEl.classList.add("rw-active");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fetchBranches() {
|
|
44
|
+
fetch(mount + "/branches", { headers: { Accept: "application/json" } })
|
|
45
|
+
.then(function (r) {
|
|
46
|
+
if (!r.ok) throw new Error("HTTP " + r.status + " on /branches");
|
|
47
|
+
return r.json();
|
|
48
|
+
})
|
|
49
|
+
.then(function (data) {
|
|
50
|
+
branchSel.innerHTML = "";
|
|
51
|
+
(data.branches || []).forEach(function (b) {
|
|
52
|
+
const opt = document.createElement("option");
|
|
53
|
+
opt.value = b;
|
|
54
|
+
opt.textContent = b;
|
|
55
|
+
if (b === selectedBranch) opt.selected = true;
|
|
56
|
+
branchSel.appendChild(opt);
|
|
57
|
+
});
|
|
58
|
+
if ((data.errors || []).length > 0) showError(data.errors.join(" | "));
|
|
59
|
+
if (branchSel.value) fetchCommits(branchSel.value);
|
|
60
|
+
})
|
|
61
|
+
.catch(function (err) {
|
|
62
|
+
showError("branches: " + err.message);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function fetchCommits(branch) {
|
|
67
|
+
travelBtn.disabled = true;
|
|
68
|
+
fetch(mount + "/commits/" + encodeURI(branch), { headers: { Accept: "application/json" } })
|
|
69
|
+
.then(function (r) {
|
|
70
|
+
if (!r.ok) throw new Error("HTTP " + r.status + " on /commits/" + branch);
|
|
71
|
+
return r.json();
|
|
72
|
+
})
|
|
73
|
+
.then(function (data) {
|
|
74
|
+
commitSel.innerHTML = "";
|
|
75
|
+
(data.commits || []).forEach(function (c) {
|
|
76
|
+
const opt = document.createElement("option");
|
|
77
|
+
opt.value = c.sha;
|
|
78
|
+
opt.textContent = c.short_sha + " — " + (c.subject || "").slice(0, 80);
|
|
79
|
+
if (c.sha === selectedCommit) opt.selected = true;
|
|
80
|
+
commitSel.appendChild(opt);
|
|
81
|
+
});
|
|
82
|
+
if (data.error) {
|
|
83
|
+
showError("commits: " + data.error);
|
|
84
|
+
} else if (!activeRef) {
|
|
85
|
+
setState();
|
|
86
|
+
}
|
|
87
|
+
travelBtn.disabled = !commitSel.value;
|
|
88
|
+
})
|
|
89
|
+
.catch(function (err) {
|
|
90
|
+
showError("commits: " + err.message);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setCookie(name, value) {
|
|
95
|
+
document.cookie = name + "=" + encodeURIComponent(value) + "; Path=/; SameSite=Lax";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function clearCookie(name) {
|
|
99
|
+
document.cookie = name + "=; Path=/; Max-Age=0; SameSite=Lax";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Travel is a *session*, not a one-off request: the ref lives
|
|
103
|
+
// in a cookie so it survives every subsequent link click. The
|
|
104
|
+
// URL params are added just so a first-time landing (or a
|
|
105
|
+
// shared link) still works even before the cookie is set.
|
|
106
|
+
function travel() {
|
|
107
|
+
const sha = commitSel.value;
|
|
108
|
+
if (!sha) return;
|
|
109
|
+
setCookie("rails_wayback_ref", sha);
|
|
110
|
+
if (branchSel.value) setCookie("rails_wayback_branch", branchSel.value);
|
|
111
|
+
const url = new URL(window.location.href);
|
|
112
|
+
url.searchParams.set("_wayback_ref", sha);
|
|
113
|
+
if (branchSel.value) url.searchParams.set("_wayback_branch", branchSel.value);
|
|
114
|
+
window.location.assign(url.toString());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function reset() {
|
|
118
|
+
clearCookie("rails_wayback_ref");
|
|
119
|
+
clearCookie("rails_wayback_branch");
|
|
120
|
+
const url = new URL(window.location.href);
|
|
121
|
+
url.searchParams.delete("_wayback_ref");
|
|
122
|
+
url.searchParams.delete("_wayback_branch");
|
|
123
|
+
window.location.assign(url.toString());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
branchSel.addEventListener("change", function (e) { fetchCommits(e.target.value); });
|
|
127
|
+
travelBtn.addEventListener("click", travel);
|
|
128
|
+
resetBtn.addEventListener("click", reset);
|
|
129
|
+
|
|
130
|
+
setState();
|
|
131
|
+
fetchBranches();
|
|
132
|
+
})();
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "cgi"
|
|
5
|
+
require "erb"
|
|
5
6
|
|
|
6
7
|
module RailsWayback
|
|
7
8
|
# Builds the HTML/CSS/JS payload that the middleware injects into
|
|
8
9
|
# every HTML response so the developer can travel from any page of
|
|
9
10
|
# their app without visiting a dedicated route.
|
|
10
11
|
class BarRenderer
|
|
12
|
+
TEMPLATE = ERB.new(File.read(File.expand_path("bar_renderer.html.erb", __dir__))).freeze
|
|
13
|
+
STYLES = File.read(File.expand_path("bar_renderer.css", __dir__)).freeze
|
|
14
|
+
SCRIPT = File.read(File.expand_path("bar_renderer.js", __dir__)).freeze
|
|
15
|
+
|
|
11
16
|
def initialize(current_branch:, current_commit:, active_ref: nil, active_branch: nil,
|
|
12
17
|
engine_mount: "/rails-wayback", diff_info: nil)
|
|
13
18
|
@current_branch = current_branch
|
|
@@ -19,30 +24,7 @@ module RailsWayback
|
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
def render
|
|
22
|
-
|
|
23
|
-
<style data-rails-wayback>#{styles}</style>
|
|
24
|
-
<div id="rails-wayback-bar" data-mount="#{escape(@engine_mount)}"
|
|
25
|
-
data-branch="#{escape(@current_branch)}"
|
|
26
|
-
data-commit="#{escape(@current_commit)}"
|
|
27
|
-
data-active-ref="#{escape(@active_ref.to_s)}"
|
|
28
|
-
data-active-branch="#{escape(@active_branch.to_s)}">
|
|
29
|
-
<div class="rw-bar-inner">
|
|
30
|
-
<div class="rw-field">
|
|
31
|
-
<label>#{escape(branch_label)}</label>
|
|
32
|
-
<select data-rw-branch></select>
|
|
33
|
-
</div>
|
|
34
|
-
<div class="rw-field rw-flex">
|
|
35
|
-
<label>#{escape(commit_label)}</label>
|
|
36
|
-
<select data-rw-commit></select>
|
|
37
|
-
</div>
|
|
38
|
-
<div class="rw-state" data-rw-state></div>
|
|
39
|
-
<button type="button" data-rw-travel class="rw-btn rw-btn-primary">Travel</button>
|
|
40
|
-
<button type="button" data-rw-reset class="rw-btn rw-btn-ghost" hidden>Return to HEAD</button>
|
|
41
|
-
</div>
|
|
42
|
-
#{diff_summary_html}
|
|
43
|
-
</div>
|
|
44
|
-
<script data-rails-wayback>#{script}</script>
|
|
45
|
-
HTML
|
|
27
|
+
TEMPLATE.result(binding)
|
|
46
28
|
end
|
|
47
29
|
|
|
48
30
|
private
|
|
@@ -104,198 +86,11 @@ module RailsWayback
|
|
|
104
86
|
end
|
|
105
87
|
|
|
106
88
|
def styles
|
|
107
|
-
|
|
108
|
-
#rails-wayback-bar {
|
|
109
|
-
position: fixed; left: 0; right: 0; bottom: 0; z-index: 2147483000;
|
|
110
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
111
|
-
font-size: 12px; color: #e6e9ef;
|
|
112
|
-
background: rgba(15, 17, 21, 0.96);
|
|
113
|
-
border-top: 1px solid #2a2f3a;
|
|
114
|
-
box-shadow: 0 -8px 20px rgba(0,0,0,.35);
|
|
115
|
-
}
|
|
116
|
-
#rails-wayback-bar * { box-sizing: border-box; }
|
|
117
|
-
#rails-wayback-bar .rw-bar-inner {
|
|
118
|
-
display: flex; align-items: end; gap: 12px; padding: 8px 12px;
|
|
119
|
-
}
|
|
120
|
-
#rails-wayback-bar .rw-field { display: flex; flex-direction: column; gap: 3px; min-width: 180px; }
|
|
121
|
-
#rails-wayback-bar .rw-flex { flex: 1; }
|
|
122
|
-
#rails-wayback-bar label {
|
|
123
|
-
color: #8a93a6; text-transform: uppercase; letter-spacing: .06em; font-size: 10px;
|
|
124
|
-
}
|
|
125
|
-
#rails-wayback-bar select {
|
|
126
|
-
background: #0b0d12; color: #e6e9ef;
|
|
127
|
-
border: 1px solid #2a2f3a; border-radius: 4px;
|
|
128
|
-
padding: 5px 8px; font-family: inherit; font-size: 12px; width: 100%;
|
|
129
|
-
}
|
|
130
|
-
#rails-wayback-bar .rw-state { color: #8a93a6; font-size: 11px; margin: 0 4px; }
|
|
131
|
-
#rails-wayback-bar .rw-state.rw-active { color: #ffd479; }
|
|
132
|
-
#rails-wayback-bar .rw-btn {
|
|
133
|
-
border: 0; border-radius: 4px; padding: 8px 14px; cursor: pointer;
|
|
134
|
-
font-family: inherit; font-size: 12px; font-weight: 600;
|
|
135
|
-
}
|
|
136
|
-
#rails-wayback-bar .rw-btn-primary { background: #6ea8fe; color: #0b0d12; }
|
|
137
|
-
#rails-wayback-bar .rw-btn-ghost { background: transparent; color: #8a93a6; border: 1px solid #2a2f3a; }
|
|
138
|
-
#rails-wayback-bar .rw-btn:disabled { opacity: .5; cursor: not-allowed; }
|
|
139
|
-
#rails-wayback-bar .rw-diff {
|
|
140
|
-
border-top: 1px solid #2a2f3a;
|
|
141
|
-
padding: 6px 12px; font-size: 11px; color: #8a93a6;
|
|
142
|
-
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
|
143
|
-
}
|
|
144
|
-
#rails-wayback-bar .rw-diff-match .rw-diff-msg { color: #7ee787; }
|
|
145
|
-
#rails-wayback-bar .rw-diff-miss .rw-diff-msg { color: #ffd479; }
|
|
146
|
-
#rails-wayback-bar .rw-diff-neutral .rw-diff-msg { color: #8a93a6; }
|
|
147
|
-
#rails-wayback-bar .rw-diff-details { color: #8a93a6; }
|
|
148
|
-
#rails-wayback-bar .rw-diff-details summary {
|
|
149
|
-
cursor: pointer; user-select: none;
|
|
150
|
-
}
|
|
151
|
-
#rails-wayback-bar .rw-diff-details[open] {
|
|
152
|
-
flex-basis: 100%;
|
|
153
|
-
}
|
|
154
|
-
#rails-wayback-bar .rw-diff-details ul {
|
|
155
|
-
margin: 6px 0 0; padding: 6px 10px; list-style: none;
|
|
156
|
-
background: #0b0d12; border: 1px solid #2a2f3a; border-radius: 4px;
|
|
157
|
-
max-height: 200px; overflow: auto;
|
|
158
|
-
font-family: inherit; font-size: 11px;
|
|
159
|
-
}
|
|
160
|
-
#rails-wayback-bar .rw-diff-details li { padding: 2px 0; color: #c8cfdc; }
|
|
161
|
-
body { padding-bottom: 56px !important; }
|
|
162
|
-
body:has(#rails-wayback-bar .rw-diff) { padding-bottom: 88px !important; }
|
|
163
|
-
CSS
|
|
89
|
+
STYLES
|
|
164
90
|
end
|
|
165
91
|
|
|
166
92
|
def script
|
|
167
|
-
|
|
168
|
-
(function () {
|
|
169
|
-
var bar = document.getElementById("rails-wayback-bar");
|
|
170
|
-
if (!bar) return;
|
|
171
|
-
|
|
172
|
-
var mount = bar.dataset.mount;
|
|
173
|
-
var headBranch = bar.dataset.branch;
|
|
174
|
-
var headCommit = bar.dataset.commit;
|
|
175
|
-
var activeRef = bar.dataset.activeRef || "";
|
|
176
|
-
var activeBranch = bar.dataset.activeBranch || "";
|
|
177
|
-
// While traveling, reflect the ref's branch/commit in the
|
|
178
|
-
// dropdowns instead of the developer's local HEAD, so the bar
|
|
179
|
-
// matches what is actually being rendered.
|
|
180
|
-
var selectedBranch = activeBranch || headBranch;
|
|
181
|
-
var selectedCommit = activeRef || headCommit;
|
|
182
|
-
|
|
183
|
-
var branchSel = bar.querySelector("[data-rw-branch]");
|
|
184
|
-
var commitSel = bar.querySelector("[data-rw-commit]");
|
|
185
|
-
var stateEl = bar.querySelector("[data-rw-state]");
|
|
186
|
-
var travelBtn = bar.querySelector("[data-rw-travel]");
|
|
187
|
-
var resetBtn = bar.querySelector("[data-rw-reset]");
|
|
188
|
-
|
|
189
|
-
function setState() {
|
|
190
|
-
if (activeRef) {
|
|
191
|
-
var label = activeBranch
|
|
192
|
-
? activeBranch + "@" + activeRef.slice(0, 7)
|
|
193
|
-
: activeRef.slice(0, 7);
|
|
194
|
-
stateEl.textContent = "Traveling: " + label;
|
|
195
|
-
stateEl.classList.add("rw-active");
|
|
196
|
-
resetBtn.hidden = false;
|
|
197
|
-
} else {
|
|
198
|
-
stateEl.textContent = "Live";
|
|
199
|
-
stateEl.classList.remove("rw-active");
|
|
200
|
-
resetBtn.hidden = true;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function showError(msg) {
|
|
205
|
-
stateEl.textContent = msg;
|
|
206
|
-
stateEl.classList.add("rw-active");
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function loadBranches() {
|
|
210
|
-
fetch(mount + "/branches", { headers: { Accept: "application/json" } })
|
|
211
|
-
.then(function (r) {
|
|
212
|
-
if (!r.ok) throw new Error("HTTP " + r.status + " on /branches");
|
|
213
|
-
return r.json();
|
|
214
|
-
})
|
|
215
|
-
.then(function (data) {
|
|
216
|
-
branchSel.innerHTML = "";
|
|
217
|
-
(data.branches || []).forEach(function (b) {
|
|
218
|
-
var opt = document.createElement("option");
|
|
219
|
-
opt.value = b; opt.textContent = b;
|
|
220
|
-
if (b === selectedBranch) opt.selected = true;
|
|
221
|
-
branchSel.appendChild(opt);
|
|
222
|
-
});
|
|
223
|
-
if ((data.errors || []).length > 0) showError(data.errors.join(" | "));
|
|
224
|
-
if (branchSel.value) loadCommits(branchSel.value);
|
|
225
|
-
})
|
|
226
|
-
.catch(function (err) {
|
|
227
|
-
showError("branches: " + err.message);
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function loadCommits(branch) {
|
|
232
|
-
travelBtn.disabled = true;
|
|
233
|
-
fetch(mount + "/commits/" + encodeURI(branch), { headers: { Accept: "application/json" } })
|
|
234
|
-
.then(function (r) {
|
|
235
|
-
if (!r.ok) throw new Error("HTTP " + r.status + " on /commits/" + branch);
|
|
236
|
-
return r.json();
|
|
237
|
-
})
|
|
238
|
-
.then(function (data) {
|
|
239
|
-
commitSel.innerHTML = "";
|
|
240
|
-
(data.commits || []).forEach(function (c) {
|
|
241
|
-
var opt = document.createElement("option");
|
|
242
|
-
opt.value = c.sha;
|
|
243
|
-
opt.textContent = c.short_sha + " — " + (c.subject || "").slice(0, 80);
|
|
244
|
-
if (c.sha === selectedCommit) opt.selected = true;
|
|
245
|
-
commitSel.appendChild(opt);
|
|
246
|
-
});
|
|
247
|
-
if (data.error) {
|
|
248
|
-
showError("commits: " + data.error);
|
|
249
|
-
} else if (!activeRef) {
|
|
250
|
-
setState();
|
|
251
|
-
}
|
|
252
|
-
travelBtn.disabled = !commitSel.value;
|
|
253
|
-
})
|
|
254
|
-
.catch(function (err) {
|
|
255
|
-
showError("commits: " + err.message);
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function setCookie(name, value) {
|
|
260
|
-
document.cookie = name + "=" + encodeURIComponent(value) + "; Path=/; SameSite=Lax";
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function clearCookie(name) {
|
|
264
|
-
document.cookie = name + "=; Path=/; Max-Age=0; SameSite=Lax";
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Travel is a *session*, not a one-off request: the ref lives
|
|
268
|
-
// in a cookie so it survives every subsequent link click. The
|
|
269
|
-
// URL params are added just so a first-time landing (or a
|
|
270
|
-
// shared link) still works even before the cookie is set.
|
|
271
|
-
function travel() {
|
|
272
|
-
var sha = commitSel.value;
|
|
273
|
-
if (!sha) return;
|
|
274
|
-
setCookie("rails_wayback_ref", sha);
|
|
275
|
-
if (branchSel.value) setCookie("rails_wayback_branch", branchSel.value);
|
|
276
|
-
var url = new URL(window.location.href);
|
|
277
|
-
url.searchParams.set("_wayback_ref", sha);
|
|
278
|
-
if (branchSel.value) url.searchParams.set("_wayback_branch", branchSel.value);
|
|
279
|
-
window.location.assign(url.toString());
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function reset() {
|
|
283
|
-
clearCookie("rails_wayback_ref");
|
|
284
|
-
clearCookie("rails_wayback_branch");
|
|
285
|
-
var url = new URL(window.location.href);
|
|
286
|
-
url.searchParams.delete("_wayback_ref");
|
|
287
|
-
url.searchParams.delete("_wayback_branch");
|
|
288
|
-
window.location.assign(url.toString());
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
branchSel.addEventListener("change", function (e) { loadCommits(e.target.value); });
|
|
292
|
-
travelBtn.addEventListener("click", travel);
|
|
293
|
-
resetBtn.addEventListener("click", reset);
|
|
294
|
-
|
|
295
|
-
setState();
|
|
296
|
-
loadBranches();
|
|
297
|
-
})();
|
|
298
|
-
JS
|
|
93
|
+
SCRIPT
|
|
299
94
|
end
|
|
300
95
|
end
|
|
301
96
|
end
|
data/lib/rails_wayback/cli.rb
CHANGED
|
@@ -40,7 +40,7 @@ module RailsWayback
|
|
|
40
40
|
|
|
41
41
|
def cmd_on
|
|
42
42
|
RailsWayback.enable!
|
|
43
|
-
@stdout.puts "rails-wayback: enabled (mounted at /rails-wayback on the next request)"
|
|
43
|
+
@stdout.puts "rails-wayback: enabled for this server session (mounted at /rails-wayback on the next request)"
|
|
44
44
|
0
|
|
45
45
|
end
|
|
46
46
|
|
data/lib/rails_wayback/engine.rb
CHANGED
|
@@ -9,6 +9,13 @@ module RailsWayback
|
|
|
9
9
|
class Engine < ::Rails::Engine
|
|
10
10
|
isolate_namespace RailsWayback
|
|
11
11
|
|
|
12
|
+
# Activation is scoped to a Rails server session. The marker still lets
|
|
13
|
+
# the standalone CLI toggle a running server, but a later server boot
|
|
14
|
+
# always starts with Wayback disabled.
|
|
15
|
+
server do
|
|
16
|
+
RailsWayback.toggle.reset!
|
|
17
|
+
end
|
|
18
|
+
|
|
12
19
|
initializer "rails_wayback.controller_extensions" do
|
|
13
20
|
ActiveSupport.on_load(:action_controller_base) do
|
|
14
21
|
include RailsWayback::ControllerExtensions
|
data/lib/rails_wayback/git.rb
CHANGED
|
@@ -9,6 +9,9 @@ module RailsWayback
|
|
|
9
9
|
# We shell out to git instead of pulling in a dependency to keep the
|
|
10
10
|
# gem light. All commands are scoped to the host app root and return
|
|
11
11
|
# plain Ruby data structures.
|
|
12
|
+
#
|
|
13
|
+
# NOTE: Maybe we should need to use a git library instead of shelling out to git.
|
|
14
|
+
# Only if this functionality is not enough for the gem on the future.
|
|
12
15
|
class Git
|
|
13
16
|
Commit = Struct.new(:sha, :short_sha, :subject, :author, :date, keyword_init: true)
|
|
14
17
|
|
data/lib/rails_wayback/toggle.rb
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
|
|
5
5
|
module RailsWayback
|
|
6
|
-
#
|
|
6
|
+
# Shares the current server session's state between the CLI and Rails.
|
|
7
7
|
#
|
|
8
8
|
# The flag is a plain file inside `tmp/rails_wayback/enabled` (ignored by
|
|
9
9
|
# git). We keep it as a file so the CLI, rake tasks and running Rails
|
|
10
|
-
# process all agree on the same state without booting extra services.
|
|
10
|
+
# process all agree on the same state without booting extra services. The
|
|
11
|
+
# engine clears stale state whenever a new Rails server session starts.
|
|
11
12
|
class Toggle
|
|
12
13
|
def initialize(configuration = RailsWayback.configuration)
|
|
13
14
|
@configuration = configuration
|
|
@@ -30,6 +31,13 @@ module RailsWayback
|
|
|
30
31
|
true
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
# Start a new server session disabled. Unlike `disable!`, this does not
|
|
35
|
+
# touch routes: Rails is already loading them as part of server boot.
|
|
36
|
+
def reset!
|
|
37
|
+
FileUtils.rm_f(configuration.toggle_file_path)
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
33
41
|
def status
|
|
34
42
|
enabled? ? :on : :off
|
|
35
43
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-wayback
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- MrCesar107
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -78,6 +78,9 @@ files:
|
|
|
78
78
|
- lib/rails-wayback.rb
|
|
79
79
|
- lib/rails_wayback.rb
|
|
80
80
|
- lib/rails_wayback/bar_middleware.rb
|
|
81
|
+
- lib/rails_wayback/bar_renderer.css
|
|
82
|
+
- lib/rails_wayback/bar_renderer.html.erb
|
|
83
|
+
- lib/rails_wayback/bar_renderer.js
|
|
81
84
|
- lib/rails_wayback/bar_renderer.rb
|
|
82
85
|
- lib/rails_wayback/cli.rb
|
|
83
86
|
- lib/rails_wayback/configuration.rb
|