paper_view 0.1.1 → 0.2.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 +23 -1
- data/app/controllers/paper_view/stats_controller.rb +12 -0
- data/app/helpers/paper_view/application_helper.rb +26 -0
- data/app/models/paper_view/metadata.rb +73 -0
- data/app/models/paper_view/payload_sizes.rb +107 -0
- data/app/models/paper_view/table_stats.rb +62 -0
- data/app/models/paper_view/timeline.rb +0 -12
- data/app/models/paper_view/version_query.rb +25 -8
- data/app/models/paper_view/version_row.rb +4 -0
- data/app/models/paper_view/versions_table.rb +60 -4
- data/app/views/layouts/paper_view/application.html.erb +11 -2
- data/app/views/paper_view/shared/_script.html.erb +173 -71
- data/app/views/paper_view/shared/_styles.html.erb +96 -9
- data/app/views/paper_view/stats/_stats.html.erb +80 -0
- data/app/views/paper_view/stats/show.html.erb +21 -0
- data/app/views/paper_view/versions/_filters.html.erb +1 -1
- data/app/views/paper_view/versions/_footer.html.erb +1 -1
- data/app/views/paper_view/versions/_panel.html.erb +35 -0
- data/app/views/paper_view/versions/index.html.erb +5 -5
- data/config/routes.rb +3 -0
- data/lib/paper_view/assets/turbo.js +7256 -0
- data/lib/paper_view/configuration.rb +4 -1
- data/lib/paper_view/turbo_asset.rb +21 -0
- data/lib/paper_view/version.rb +1 -1
- data/lib/paper_view.rb +1 -0
- metadata +9 -1
|
@@ -1,96 +1,198 @@
|
|
|
1
1
|
<%= javascript_tag nonce: true do %>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
(function () {
|
|
3
|
+
function clearBusyButtons() {
|
|
4
|
+
document.querySelectorAll(".pv-btn--busy").forEach(function (button) {
|
|
5
|
+
button.classList.remove("pv-btn--busy");
|
|
6
|
+
button.querySelectorAll(".pv-spinner").forEach(function (spinner) { spinner.remove(); });
|
|
7
|
+
});
|
|
7
8
|
}
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
document.addEventListener("change", function (event) {
|
|
11
|
-
var select = event.target.closest("select[data-pv-autosubmit]");
|
|
12
|
-
if (!select || !select.form) return;
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
// Turbo never removes stylesheets it merged into the head, so a per request CSP nonce would pile them up.
|
|
11
|
+
function dropStaleStyles() {
|
|
12
|
+
var styles = document.head.querySelectorAll("style[data-pv-styles]");
|
|
13
|
+
for (var index = 0; index < styles.length - 1; index++) styles[index].remove();
|
|
18
14
|
}
|
|
19
|
-
});
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
26
|
-
});
|
|
16
|
+
function initTimeline() {
|
|
17
|
+
var timeline = document.querySelector("[data-pv-timeline]");
|
|
18
|
+
var panels = document.querySelector("[data-pv-panels]");
|
|
19
|
+
if (!timeline || !panels) return;
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
var timeline = document.querySelector("[data-pv-timeline]");
|
|
30
|
-
var panels = document.querySelector("[data-pv-panels]");
|
|
31
|
-
if (!timeline || !panels) return;
|
|
21
|
+
var footer = timeline.parentNode.querySelector(".pv-footer");
|
|
32
22
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
function collapse(panel) {
|
|
24
|
+
panel.querySelectorAll("[data-pv-toggle]").forEach(function (toggle) { setExpanded(toggle, false); });
|
|
25
|
+
panel.querySelectorAll("[data-pv-copy]").forEach(function (button) { button.textContent = "Copy"; });
|
|
26
|
+
}
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
function setExpanded(toggle, expanded) {
|
|
29
|
+
var target = document.getElementById(toggle.getAttribute("data-pv-toggle"));
|
|
30
|
+
toggle.setAttribute("aria-expanded", expanded);
|
|
31
|
+
toggle.querySelector(".pv-disclosure__arrow").textContent = expanded ? "▾" : "▸";
|
|
32
|
+
if (target) target.hidden = !expanded;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function select(id) {
|
|
36
|
+
timeline.querySelectorAll("[data-pv-version]").forEach(function (row) {
|
|
37
|
+
var active = row.getAttribute("data-pv-version") === id;
|
|
38
|
+
row.classList.toggle("pv-version--selected", active);
|
|
39
|
+
row.setAttribute("aria-pressed", active);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
panels.querySelectorAll("[data-pv-panel]").forEach(function (panel) {
|
|
43
|
+
panel.hidden = panel.getAttribute("data-pv-panel") !== id;
|
|
44
|
+
if (panel.hidden) collapse(panel);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
panels.scrollTop = 0;
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
var url = new URL(window.location.href);
|
|
50
|
+
url.searchParams.set("version", id);
|
|
51
|
+
window.history.replaceState(window.history.state, "", url);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function rows() {
|
|
55
|
+
return Array.prototype.slice.call(timeline.querySelectorAll("[data-pv-version]"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function focusRow(row) {
|
|
59
|
+
select(row.getAttribute("data-pv-version"));
|
|
60
|
+
row.focus();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function move(row, step) {
|
|
64
|
+
var all = rows();
|
|
65
|
+
var next = all[all.indexOf(row) + step];
|
|
66
|
+
if (next) return focusRow(next);
|
|
67
|
+
|
|
68
|
+
var control = step > 0 && footer && footer.querySelector("a[href], select, button");
|
|
69
|
+
if (control) control.focus();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
timeline.addEventListener("click", function (event) {
|
|
73
|
+
if (event.target.closest("a")) return;
|
|
74
|
+
var row = event.target.closest("[data-pv-version]");
|
|
75
|
+
if (row) select(row.getAttribute("data-pv-version"));
|
|
50
76
|
});
|
|
51
77
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
78
|
+
timeline.addEventListener("keydown", function (event) {
|
|
79
|
+
var row = event.target.closest("[data-pv-version]");
|
|
80
|
+
if (!row) return;
|
|
81
|
+
|
|
82
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
move(row, event.key === "ArrowDown" ? 1 : -1);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
select(row.getAttribute("data-pv-version"));
|
|
55
91
|
});
|
|
56
92
|
|
|
57
|
-
|
|
93
|
+
if (footer) {
|
|
94
|
+
footer.addEventListener("keydown", function (event) {
|
|
95
|
+
// Inside a select or input the arrow keys belong to the control itself.
|
|
96
|
+
if (event.key !== "ArrowUp" || event.target.closest("select, input")) return;
|
|
58
97
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
var all = rows();
|
|
99
|
+
if (!all.length) return;
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
focusRow(all[all.length - 1]);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
panels.addEventListener("click", function (event) {
|
|
106
|
+
var toggle = event.target.closest("[data-pv-toggle]");
|
|
107
|
+
if (toggle) {
|
|
108
|
+
setExpanded(toggle, toggle.getAttribute("aria-expanded") !== "true");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
var copy = event.target.closest("[data-pv-copy]");
|
|
113
|
+
if (!copy || !navigator.clipboard) return;
|
|
114
|
+
navigator.clipboard.writeText(document.getElementById(copy.getAttribute("data-pv-copy")).textContent);
|
|
115
|
+
copy.textContent = "Copied";
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (new URL(window.location.href).searchParams.get("version")) {
|
|
119
|
+
var selected = timeline.querySelector(".pv-version--selected");
|
|
120
|
+
if (selected) selected.scrollIntoView({block: "center"});
|
|
121
|
+
}
|
|
62
122
|
}
|
|
63
123
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
124
|
+
function measureLayoutTop() {
|
|
125
|
+
var layout = document.querySelector(".pv-layout");
|
|
126
|
+
if (!layout) return;
|
|
127
|
+
var top = layout.getBoundingClientRect().top + window.scrollY;
|
|
128
|
+
document.documentElement.style.setProperty("--pv-layout-top", Math.round(top) + "px");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function initStats() {
|
|
132
|
+
var pending = document.querySelector("[data-pv-stats]");
|
|
133
|
+
if (!pending || !window.fetch) return;
|
|
134
|
+
|
|
135
|
+
var source = pending.getAttribute("data-pv-stats");
|
|
136
|
+
var started = Date.now();
|
|
137
|
+
pending.removeAttribute("data-pv-stats");
|
|
138
|
+
|
|
139
|
+
function show(html) {
|
|
140
|
+
var elapsed = Date.now() - started;
|
|
141
|
+
|
|
142
|
+
// Nothing is on screen before 350ms, and once the skeleton is there it stays long enough to read as a load.
|
|
143
|
+
setTimeout(function () {
|
|
144
|
+
pending.innerHTML = html;
|
|
145
|
+
pending.removeAttribute("aria-busy");
|
|
146
|
+
pending.classList.remove("pv-pending");
|
|
147
|
+
pending.classList.add("pv-loaded");
|
|
148
|
+
}, elapsed < 350 ? 0 : Math.max(0, 900 - elapsed));
|
|
149
|
+
}
|
|
69
150
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
151
|
+
fetch(source, {credentials: "same-origin", headers: {"X-Requested-With": "XMLHttpRequest"}})
|
|
152
|
+
.then(function (response) { return response.ok ? response.text() : Promise.reject(response); })
|
|
153
|
+
.then(show)
|
|
154
|
+
.catch(function () { window.location.href = source; });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function init() {
|
|
158
|
+
dropStaleStyles();
|
|
159
|
+
clearBusyButtons();
|
|
160
|
+
measureLayoutTop();
|
|
161
|
+
initTimeline();
|
|
162
|
+
initStats();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Turbo re-runs this script after every visit, the listeners below must only be registered once.
|
|
166
|
+
if (window.paperViewStarted) return init();
|
|
167
|
+
window.paperViewStarted = true;
|
|
168
|
+
|
|
169
|
+
window.addEventListener("resize", measureLayoutTop);
|
|
170
|
+
|
|
171
|
+
document.addEventListener("submit", function (event) {
|
|
172
|
+
var button = event.target.querySelector("button[type=submit]");
|
|
173
|
+
if (button && !button.querySelector(".pv-spinner")) {
|
|
174
|
+
button.classList.add("pv-btn--busy");
|
|
175
|
+
button.insertAdjacentHTML("beforeend", '<span class="pv-spinner"></span>');
|
|
176
|
+
}
|
|
76
177
|
});
|
|
77
178
|
|
|
78
|
-
|
|
79
|
-
var
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
179
|
+
document.addEventListener("change", function (event) {
|
|
180
|
+
var select = event.target.closest("select[data-pv-autosubmit]");
|
|
181
|
+
if (!select || !select.form) return;
|
|
182
|
+
|
|
183
|
+
if (select.form.requestSubmit) {
|
|
184
|
+
select.form.requestSubmit();
|
|
185
|
+
} else {
|
|
186
|
+
select.form.submit();
|
|
83
187
|
}
|
|
188
|
+
});
|
|
84
189
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
navigator.clipboard.writeText(document.getElementById(copy.getAttribute("data-pv-copy")).textContent);
|
|
88
|
-
copy.textContent = "Copied";
|
|
190
|
+
document.addEventListener("turbo:before-fetch-request", function (event) {
|
|
191
|
+
event.detail.fetchOptions.headers["X-PaperView-Turbo"] = "1";
|
|
89
192
|
});
|
|
90
193
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
194
|
+
window.addEventListener("pageshow", clearBusyButtons);
|
|
195
|
+
|
|
196
|
+
init();
|
|
95
197
|
})();
|
|
96
198
|
<% end %>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<style <%= paper_view_nonce_attribute %>>
|
|
1
|
+
<style data-pv-styles <%= paper_view_nonce_attribute %>>
|
|
2
2
|
*, *::before, *::after { box-sizing: border-box; }
|
|
3
3
|
[hidden] { display: none !important; }
|
|
4
4
|
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
--pv-accent-hover: #1d4ed8;
|
|
23
23
|
--pv-on-accent: #ffffff;
|
|
24
24
|
--pv-dot: #cbd5e1;
|
|
25
|
+
--pv-skeleton: #e4e7ec;
|
|
26
|
+
--pv-skeleton-sheen: #f2f4f7;
|
|
25
27
|
--pv-del: #b91c1c;
|
|
26
28
|
--pv-del-bg: #fef2f2;
|
|
27
29
|
--pv-del-rule: #fca5a5;
|
|
@@ -55,6 +57,8 @@
|
|
|
55
57
|
--pv-accent: #4a8bff;
|
|
56
58
|
--pv-accent-hover: #79a9ff;
|
|
57
59
|
--pv-dot: #39414d;
|
|
60
|
+
--pv-skeleton: #232a33;
|
|
61
|
+
--pv-skeleton-sheen: #2f3945;
|
|
58
62
|
--pv-del: #ff7b72;
|
|
59
63
|
--pv-del-bg: #2d1618;
|
|
60
64
|
--pv-del-rule: #7d2a2a;
|
|
@@ -86,15 +90,27 @@
|
|
|
86
90
|
top: 0;
|
|
87
91
|
z-index: 10;
|
|
88
92
|
display: flex;
|
|
89
|
-
align-items:
|
|
90
|
-
gap:
|
|
91
|
-
|
|
93
|
+
align-items: stretch;
|
|
94
|
+
gap: 20px;
|
|
95
|
+
height: var(--pv-header-h);
|
|
96
|
+
padding: 0 24px;
|
|
92
97
|
background: var(--pv-surface);
|
|
93
98
|
border-bottom: 1px solid var(--pv-border);
|
|
94
99
|
}
|
|
95
|
-
.pv-header__brand { display: flex; align-items:
|
|
100
|
+
.pv-header__brand { display: flex; align-items: center; gap: 8px; color: var(--pv-ink); font-size: 15px; font-weight: 650; }
|
|
96
101
|
.pv-header__brand:hover { color: var(--pv-ink); text-decoration: none; }
|
|
97
|
-
.pv-header__meta { margin-left: auto; color: var(--pv-ink-soft); font-size: 12.5px; }
|
|
102
|
+
.pv-header__meta { display: flex; align-items: center; margin-left: auto; color: var(--pv-ink-soft); font-size: 12.5px; }
|
|
103
|
+
.pv-header__nav { display: flex; align-items: stretch; gap: 20px; }
|
|
104
|
+
.pv-header__link {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
height: 100%;
|
|
108
|
+
color: var(--pv-ink-soft);
|
|
109
|
+
font-size: 13px;
|
|
110
|
+
font-weight: 400;
|
|
111
|
+
}
|
|
112
|
+
.pv-header__link:hover { color: var(--pv-ink); text-decoration: none; }
|
|
113
|
+
.pv-header__link--active { color: var(--pv-ink); font-weight: 600; box-shadow: inset 0 -2px 0 var(--pv-ink); }
|
|
98
114
|
|
|
99
115
|
.pv-main { max-width: 1560px; margin: 0 auto; padding: 24px; }
|
|
100
116
|
.pv-stack { display: flex; flex-direction: column; gap: 16px; }
|
|
@@ -172,6 +188,8 @@
|
|
|
172
188
|
@keyframes pv-spin { to { transform: rotate(360deg); } }
|
|
173
189
|
@media (prefers-reduced-motion: reduce) { .pv-spinner { animation-duration: 2.4s; } }
|
|
174
190
|
|
|
191
|
+
.turbo-progress-bar { height: 2px; background: var(--pv-accent); }
|
|
192
|
+
|
|
175
193
|
.pv-panel--hero { width: 100%; max-width: 880px; margin: 32px auto 0; }
|
|
176
194
|
.pv-hero { padding: 48px 40px 40px; text-align: center; }
|
|
177
195
|
.pv-hero__title { font-size: 22px; }
|
|
@@ -220,6 +238,7 @@
|
|
|
220
238
|
padding: 12px 18px;
|
|
221
239
|
border-bottom: 1px solid var(--pv-border-subtle);
|
|
222
240
|
cursor: pointer;
|
|
241
|
+
scroll-margin: calc(var(--pv-header-h) + 30px) 0 16px;
|
|
223
242
|
}
|
|
224
243
|
.pv-version--multi-item-view { grid-template-columns: 64px 22px 168px 1fr auto; }
|
|
225
244
|
.pv-version:hover { background: var(--pv-row-hover); }
|
|
@@ -270,7 +289,7 @@
|
|
|
270
289
|
top: calc(var(--pv-header-h) + 16px);
|
|
271
290
|
width: clamp(330px, 26vw, 420px);
|
|
272
291
|
flex: none;
|
|
273
|
-
max-height: calc(
|
|
292
|
+
max-height: calc(100dvh - var(--pv-layout-top, 190px) - 16px);
|
|
274
293
|
overflow-y: auto;
|
|
275
294
|
overscroll-behavior: contain;
|
|
276
295
|
}
|
|
@@ -282,13 +301,17 @@
|
|
|
282
301
|
.pv-detail__field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
283
302
|
.pv-detail__item { font: 400 13px/1.4 var(--pv-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
284
303
|
.pv-detail__who { font: 400 13px/1.4 var(--pv-sans); color: var(--pv-ink); overflow-wrap: anywhere; }
|
|
304
|
+
.pv-detail__metadata { display: flex; flex-direction: column; gap: 8px; padding: 14px 16px; border-bottom: 1px solid var(--pv-border); }
|
|
305
|
+
.pv-meta { display: grid; grid-template-columns: minmax(0, 96px) minmax(0, 1fr); gap: 4px 12px; }
|
|
306
|
+
.pv-meta__key { font: 400 12px/18px var(--pv-mono); color: var(--pv-ink-soft); overflow-wrap: anywhere; }
|
|
307
|
+
.pv-meta__value { font: 400 12px/18px var(--pv-mono); color: var(--pv-ink); overflow-wrap: anywhere; }
|
|
285
308
|
.pv-detail__body { display: flex; flex-direction: column; gap: 14px; padding: 14px 16px; }
|
|
286
309
|
.pv-detail__note { margin: 0; font: 400 12px/1.6 var(--pv-sans); color: var(--pv-ink-soft); }
|
|
287
310
|
.pv-label { font: 600 11px/1.2 var(--pv-sans); letter-spacing: .06em; text-transform: uppercase; color: var(--pv-ink-faint); }
|
|
288
311
|
|
|
289
312
|
.pv-attribute { display: flex; flex-direction: column; gap: 6px; }
|
|
290
313
|
.pv-attribute__name { font: 600 12px/1.3 var(--pv-mono); color: var(--pv-ink); word-break: break-all; }
|
|
291
|
-
.pv-attribute__unchanged {
|
|
314
|
+
.pv-attribute__unchanged, .pv-meta__more {
|
|
292
315
|
align-self: flex-start;
|
|
293
316
|
padding: 0;
|
|
294
317
|
border: 0;
|
|
@@ -346,6 +369,65 @@
|
|
|
346
369
|
.pv-snippet__copy:hover { background: var(--pv-pill-bg); }
|
|
347
370
|
.pv-snippet__code { margin: 0; padding: 10px 12px; font: 400 11px/1.7 var(--pv-mono); color: var(--pv-ink); white-space: pre-wrap; word-break: break-all; }
|
|
348
371
|
|
|
372
|
+
.pv-tiles {
|
|
373
|
+
display: grid;
|
|
374
|
+
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
|
|
375
|
+
gap: 1px;
|
|
376
|
+
background: var(--pv-border);
|
|
377
|
+
}
|
|
378
|
+
.pv-tile { display: flex; flex-direction: column; gap: 5px; padding: 16px 18px; background: var(--pv-surface); }
|
|
379
|
+
.pv-tile__value { font: 600 20px/1.2 var(--pv-sans); letter-spacing: -.01em; }
|
|
380
|
+
.pv-tile__note { font: 400 11.5px/1.3 var(--pv-sans); color: var(--pv-ink-faint); }
|
|
381
|
+
|
|
382
|
+
.pv-stat {
|
|
383
|
+
display: grid;
|
|
384
|
+
grid-template-columns: minmax(0, 1fr) 90px 130px 200px;
|
|
385
|
+
align-items: center;
|
|
386
|
+
gap: 0 14px;
|
|
387
|
+
padding: 11px 18px;
|
|
388
|
+
border-bottom: 1px solid var(--pv-border-subtle);
|
|
389
|
+
}
|
|
390
|
+
.pv-stat:last-child { border-bottom: 0; }
|
|
391
|
+
.pv-stat:not(.pv-stat--head):hover { background: var(--pv-row-hover); }
|
|
392
|
+
.pv-stat--head { padding-top: 9px; padding-bottom: 9px; background: var(--pv-surface-muted); font: 600 11px/1.2 var(--pv-sans); letter-spacing: .06em; text-transform: uppercase; color: var(--pv-ink-soft); }
|
|
393
|
+
.pv-stat--head:hover { background: var(--pv-surface-muted); }
|
|
394
|
+
.pv-stat__type { font: 600 13px/1.4 var(--pv-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
395
|
+
.pv-stat__figure { font: 400 12.5px/1.4 var(--pv-mono); text-align: right; }
|
|
396
|
+
.pv-stat--head .pv-stat__figure { font: inherit; }
|
|
397
|
+
.pv-stat__share { display: flex; align-items: center; gap: 10px; }
|
|
398
|
+
.pv-stat__percent { flex: none; width: 46px; font: 400 12px/1.4 var(--pv-mono); color: var(--pv-ink-soft); text-align: right; }
|
|
399
|
+
.pv-share { flex: 1; min-width: 0; height: 8px; border-radius: 2px; background: var(--pv-border-subtle); }
|
|
400
|
+
.pv-share__fill { fill: var(--pv-accent); }
|
|
401
|
+
|
|
402
|
+
.pv-skeleton {
|
|
403
|
+
display: inline-block;
|
|
404
|
+
width: 100%;
|
|
405
|
+
height: 11px;
|
|
406
|
+
border-radius: 4px;
|
|
407
|
+
background: linear-gradient(90deg, var(--pv-skeleton) 30%, var(--pv-skeleton-sheen) 50%, var(--pv-skeleton) 70%) 0 0 / 220% 100%;
|
|
408
|
+
animation: pv-shimmer 1.6s ease-in-out infinite;
|
|
409
|
+
}
|
|
410
|
+
.pv-skeleton--value { width: 76px; height: 14px; }
|
|
411
|
+
.pv-skeleton--note { width: 150px; }
|
|
412
|
+
.pv-skeleton--type { width: 58%; }
|
|
413
|
+
.pv-skeleton--count { width: 44px; }
|
|
414
|
+
.pv-skeleton--bytes { width: 66px; }
|
|
415
|
+
.pv-skeleton--bar { flex: 1; height: 8px; border-radius: 2px; }
|
|
416
|
+
.pv-skeleton--percent { flex: none; width: 34px; }
|
|
417
|
+
.pv-stat--pending:nth-child(3n) .pv-skeleton--type { width: 42%; }
|
|
418
|
+
.pv-stat--pending:nth-child(3n + 1) .pv-skeleton--type { width: 71%; }
|
|
419
|
+
@keyframes pv-shimmer { from { background-position: 120% 0; } to { background-position: -20% 0; } }
|
|
420
|
+
|
|
421
|
+
/* Held back long enough that a small versions table never flashes a skeleton on its way in. */
|
|
422
|
+
.pv-pending { animation: pv-fade-in .3s ease-out .35s both; }
|
|
423
|
+
.pv-loaded { animation: pv-fade-in .2s ease-out both; }
|
|
424
|
+
@keyframes pv-fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
425
|
+
|
|
426
|
+
@media (prefers-reduced-motion: reduce) {
|
|
427
|
+
.pv-skeleton { animation: none; }
|
|
428
|
+
.pv-pending, .pv-loaded { animation-duration: .01ms; }
|
|
429
|
+
}
|
|
430
|
+
|
|
349
431
|
.pv-empty { margin: 0; padding: 48px 16px; text-align: center; color: var(--pv-ink-soft); }
|
|
350
432
|
.pv-footer {
|
|
351
433
|
display: grid;
|
|
@@ -383,7 +465,12 @@
|
|
|
383
465
|
.pv-main { padding: 16px; }
|
|
384
466
|
.pv-footer { grid-template-columns: 1fr; justify-items: center; }
|
|
385
467
|
.pv-footer__end, .pv-pagination { grid-column: 1; justify-content: center; }
|
|
386
|
-
.pv-header { padding:
|
|
468
|
+
.pv-header { padding: 0 16px; }
|
|
469
|
+
.pv-stat { grid-template-columns: minmax(0, 1fr) 100px; gap: 4px 12px; }
|
|
470
|
+
.pv-stat--head { display: none; }
|
|
471
|
+
.pv-stat__type { grid-column: 1 / -1; }
|
|
472
|
+
.pv-stat__figure { text-align: left; }
|
|
473
|
+
.pv-stat__share { grid-column: 1 / -1; }
|
|
387
474
|
}
|
|
388
475
|
|
|
389
476
|
@media (max-width: 640px) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<section class="pv-panel">
|
|
2
|
+
<div class="pv-tiles">
|
|
3
|
+
<div class="pv-tile">
|
|
4
|
+
<span class="pv-label">Versions</span>
|
|
5
|
+
<span class="pv-tile__value"><%= stats ? number_with_delimiter(stats.total_count) : tag.span(class: "pv-skeleton pv-skeleton--value") %></span>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="pv-tile">
|
|
9
|
+
<span class="pv-label">Item types</span>
|
|
10
|
+
<span class="pv-tile__value"><%= stats ? number_with_delimiter(stats.rows.size) : tag.span(class: "pv-skeleton pv-skeleton--value") %></span>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="pv-tile">
|
|
14
|
+
<span class="pv-label">Table size</span>
|
|
15
|
+
<span class="pv-tile__value"><%= stats ? paper_view_bytes(stats.disk_size&.table) : tag.span(class: "pv-skeleton pv-skeleton--value") %></span>
|
|
16
|
+
<span class="pv-tile__note">
|
|
17
|
+
<% if stats %>
|
|
18
|
+
<%= paper_view_bytes(stats.estimated_total_bytes) %> payload<%= " plus #{paper_view_bytes(stats.disk_size.indexes)} of indexes" if stats.disk_size %>
|
|
19
|
+
<% else %>
|
|
20
|
+
<%= tag.span(class: "pv-skeleton pv-skeleton--note") %>
|
|
21
|
+
<% end %>
|
|
22
|
+
</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</section>
|
|
26
|
+
|
|
27
|
+
<% if stats&.empty? %>
|
|
28
|
+
<section class="pv-panel">
|
|
29
|
+
<p class="pv-empty">No versions have been recorded yet.</p>
|
|
30
|
+
</section>
|
|
31
|
+
<% else %>
|
|
32
|
+
<section class="pv-card">
|
|
33
|
+
<div class="pv-stats">
|
|
34
|
+
<div class="pv-stat pv-stat--head">
|
|
35
|
+
<span>Item type</span>
|
|
36
|
+
<span class="pv-stat__figure">Versions</span>
|
|
37
|
+
<span class="pv-stat__figure">Size</span>
|
|
38
|
+
<span>Share</span>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<% if stats %>
|
|
42
|
+
<% stats.rows.each do |row| %>
|
|
43
|
+
<% share = row.share(stats.estimated_total_bytes) %>
|
|
44
|
+
|
|
45
|
+
<div class="pv-stat">
|
|
46
|
+
<span class="pv-stat__type">
|
|
47
|
+
<% if row.item_type %>
|
|
48
|
+
<%= link_to row.item_type, versions_path(item_type: row.item_type) %>
|
|
49
|
+
<% else %>
|
|
50
|
+
<span class="pv-muted"><%= PaperView::ApplicationHelper::EMPTY %></span>
|
|
51
|
+
<% end %>
|
|
52
|
+
</span>
|
|
53
|
+
|
|
54
|
+
<span class="pv-stat__figure"><%= number_with_delimiter(row.count) %></span>
|
|
55
|
+
<span class="pv-stat__figure"><%= paper_view_bytes(row.estimated_bytes) %></span>
|
|
56
|
+
|
|
57
|
+
<span class="pv-stat__share">
|
|
58
|
+
<svg class="pv-share" viewBox="0 0 100 8" preserveAspectRatio="none" aria-hidden="true">
|
|
59
|
+
<rect class="pv-share__fill" x="0" y="0" width="<%= (share * 100).round(2) %>" height="8" rx="1.5"></rect>
|
|
60
|
+
</svg>
|
|
61
|
+
<span class="pv-stat__percent"><%= paper_view_percentage(share) %></span>
|
|
62
|
+
</span>
|
|
63
|
+
</div>
|
|
64
|
+
<% end %>
|
|
65
|
+
<% else %>
|
|
66
|
+
<% 6.times do %>
|
|
67
|
+
<div class="pv-stat pv-stat--pending">
|
|
68
|
+
<span class="pv-stat__type"><%= tag.span(class: "pv-skeleton pv-skeleton--type") %></span>
|
|
69
|
+
<span class="pv-stat__figure"><%= tag.span(class: "pv-skeleton pv-skeleton--count") %></span>
|
|
70
|
+
<span class="pv-stat__figure"><%= tag.span(class: "pv-skeleton pv-skeleton--bytes") %></span>
|
|
71
|
+
<span class="pv-stat__share">
|
|
72
|
+
<%= tag.span(class: "pv-skeleton pv-skeleton--bar") %>
|
|
73
|
+
<%= tag.span(class: "pv-skeleton pv-skeleton--percent") %>
|
|
74
|
+
</span>
|
|
75
|
+
</div>
|
|
76
|
+
<% end %>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
<% end %>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<% if @stats %>
|
|
2
|
+
<%= render "stats", stats: @stats %>
|
|
3
|
+
<% else %>
|
|
4
|
+
<div
|
|
5
|
+
class="pv-stack pv-pending"
|
|
6
|
+
aria-busy="true"
|
|
7
|
+
data-pv-stats="<%= stats_path(load_content: 1) %>"
|
|
8
|
+
>
|
|
9
|
+
<%= render "stats", stats: nil %>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<noscript>
|
|
13
|
+
<style <%= paper_view_nonce_attribute %>>.pv-pending { display: none; }</style>
|
|
14
|
+
|
|
15
|
+
<section class="pv-panel">
|
|
16
|
+
<p class="pv-empty">
|
|
17
|
+
<%= link_to "Load the statistics", stats_path(load_content: 1) %>
|
|
18
|
+
</p>
|
|
19
|
+
</section>
|
|
20
|
+
</noscript>
|
|
21
|
+
<% end %>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%= form_with url: versions_path, method: :get, local: true,
|
|
1
|
+
<%= form_with url: versions_path, method: :get, local: true, class: "pv-search" do %>
|
|
2
2
|
<%= paper_view_hidden_filters(except: %i[item_type item_id event]) %>
|
|
3
3
|
|
|
4
4
|
<div class="pv-field pv-field--grow">
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<% end %>
|
|
26
26
|
</nav>
|
|
27
27
|
|
|
28
|
-
<%= form_with url: versions_path, method: :get, local: true,
|
|
28
|
+
<%= form_with url: versions_path, method: :get, local: true, class: "pv-footer__end" do %>
|
|
29
29
|
<%= paper_view_hidden_filters(except: :per_page) %>
|
|
30
30
|
<label class="pv-muted" for="pv-per-page">Per page</label>
|
|
31
31
|
|
|
@@ -22,6 +22,41 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
</div>
|
|
24
24
|
|
|
25
|
+
<% if row.metadata.any? %>
|
|
26
|
+
<div class="pv-detail__metadata">
|
|
27
|
+
<span class="pv-label">Metadata</span>
|
|
28
|
+
|
|
29
|
+
<div class="pv-meta">
|
|
30
|
+
<% row.metadata.visible.each do |entry| %>
|
|
31
|
+
<span class="pv-meta__key"><%= entry.name %></span>
|
|
32
|
+
<span class="pv-meta__value"><%= entry.text %></span>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<% if row.metadata.hidden.any? %>
|
|
37
|
+
<% rest_id = "pv-meta-#{row.id}" %>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
class="pv-meta__more"
|
|
41
|
+
aria-expanded="false"
|
|
42
|
+
aria-controls="<%= rest_id %>"
|
|
43
|
+
data-pv-toggle="<%= rest_id %>"
|
|
44
|
+
>
|
|
45
|
+
<span class="pv-disclosure__arrow">▸</span>
|
|
46
|
+
<%= row.metadata.hidden.size %> more
|
|
47
|
+
<%= "key".pluralize(row.metadata.hidden.size) %>
|
|
48
|
+
</button>
|
|
49
|
+
|
|
50
|
+
<div class="pv-meta" id="<%= rest_id %>" hidden>
|
|
51
|
+
<% row.metadata.hidden.each do |entry| %>
|
|
52
|
+
<span class="pv-meta__key"><%= entry.name %></span>
|
|
53
|
+
<span class="pv-meta__value"><%= entry.text %></span>
|
|
54
|
+
<% end %>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
</div>
|
|
58
|
+
<% end %>
|
|
59
|
+
|
|
25
60
|
<div class="pv-detail__body">
|
|
26
61
|
<% if row.change_set.unreadable? %>
|
|
27
62
|
<p class="pv-detail__note">
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
<div class="pv-layout">
|
|
28
28
|
<section class="pv-card">
|
|
29
29
|
<div class="pv-card__head">
|
|
30
|
-
<span class="pv-card__title"><%= @
|
|
30
|
+
<span class="pv-card__title"><%= @query.title %></span>
|
|
31
31
|
|
|
32
32
|
<span class="pv-card__meta">
|
|
33
33
|
<%= number_with_delimiter(@paginator.total_count) %>
|
|
34
|
-
<%= "version".pluralize(@paginator.total_count) %><%= " · #{@
|
|
34
|
+
<%= "version".pluralize(@paginator.total_count) %><%= " · #{@query.matched_item_types.size} item types" if @query.matched_item_types.size > 1 %>
|
|
35
35
|
</span>
|
|
36
36
|
|
|
37
37
|
<span class="pv-spacer"></span>
|
|
38
38
|
|
|
39
|
-
<%= form_with url: versions_path, method: :get, local: true,
|
|
39
|
+
<%= form_with url: versions_path, method: :get, local: true, class: "pv-head-form" do %>
|
|
40
40
|
<%= paper_view_hidden_filters(except: :sort) %>
|
|
41
41
|
|
|
42
42
|
<select
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
<div class="pv-timeline__day"><%= group.label %></div>
|
|
59
59
|
|
|
60
60
|
<% group.rows.each do |row| %>
|
|
61
|
-
<%= render "paper_view/versions/timeline_row", row: row, multi_item_view:
|
|
61
|
+
<%= render "paper_view/versions/timeline_row", row: row, multi_item_view: !@query.single_item?, selected: row == @timeline.selected %>
|
|
62
62
|
<% end %>
|
|
63
63
|
</div>
|
|
64
64
|
<% end %>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
|
|
70
70
|
<aside class="pv-side" data-pv-panels>
|
|
71
71
|
<% @timeline.rows.each do |row| %>
|
|
72
|
-
<%= render "paper_view/versions/panel", row: row, multi_item_view:
|
|
72
|
+
<%= render "paper_view/versions/panel", row: row, multi_item_view: !@query.single_item?, active: row == @timeline.selected %>
|
|
73
73
|
<% end %>
|
|
74
74
|
</aside>
|
|
75
75
|
</div>
|