paper_view 0.2.2 → 0.2.4
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/app/models/paper_view/attribute_change.rb +10 -1
- data/app/models/paper_view/leaf_change.rb +6 -0
- data/app/models/paper_view/text_diff.rb +152 -0
- data/app/views/layouts/paper_view/application.html.erb +12 -3
- data/app/views/paper_view/shared/_script.html.erb +12 -0
- data/app/views/paper_view/shared/_styles.html.erb +71 -2
- data/app/views/paper_view/shared/_theme.html.erb +8 -0
- data/app/views/paper_view/versions/_diff.html.erb +20 -0
- data/app/views/paper_view/versions/_full_value.html.erb +15 -0
- data/app/views/paper_view/versions/_panel.html.erb +22 -22
- data/app/views/paper_view/versions/_tally.html.erb +3 -0
- data/app/views/paper_view/versions/_timeline_row.html.erb +8 -3
- data/lib/paper_view/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9edde8960fedca15affc4c074a0602b55ce7d7f6bc897ecfcb667270bff1eae
|
|
4
|
+
data.tar.gz: 1bfb0849b90c525c51affda3136a2c284a3fe1eb98ae4bb4b335964c64d7f8e8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44513dfb49200317ba711a41d90395446cfbfe715b43c00d398aa98ae66b01d5682a5d458ee1bf88f2fcb6c2d09e0534407c292b6456fc22f351a1275dc0ddf9
|
|
7
|
+
data.tar.gz: 4b6285b3341e21e3685307ae903b35de96b37836faa0ec949a521ecc2cf5c7ef14a753eadec8f8f989b91f734ba641275e3a139e54f8dad525e26b5e06b2883a
|
|
@@ -79,6 +79,11 @@ module PaperView
|
|
|
79
79
|
@new_text ||= text(new_value)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
# Long texts read as a diff instead of two blocks that have to be compared by eye.
|
|
83
|
+
def diff
|
|
84
|
+
leaf.diff unless nested?
|
|
85
|
+
end
|
|
86
|
+
|
|
82
87
|
def leaf_changes
|
|
83
88
|
leaf_pairs.filter_map { |path, before, after| LeafChange.new(path, before, after) if before != after }
|
|
84
89
|
end
|
|
@@ -88,13 +93,17 @@ module PaperView
|
|
|
88
93
|
end
|
|
89
94
|
|
|
90
95
|
def leaves
|
|
91
|
-
return [
|
|
96
|
+
return [leaf] unless nested?
|
|
92
97
|
|
|
93
98
|
leaf_changes.map { |change| change.under(name) }
|
|
94
99
|
end
|
|
95
100
|
|
|
96
101
|
private
|
|
97
102
|
|
|
103
|
+
def leaf
|
|
104
|
+
@leaf ||= LeafChange.new(name, old_value, new_value)
|
|
105
|
+
end
|
|
106
|
+
|
|
98
107
|
def text(value)
|
|
99
108
|
self.class.display(self.class.structure(value) || value, pretty: true)
|
|
100
109
|
end
|
|
@@ -18,6 +18,12 @@ module PaperView
|
|
|
18
18
|
path.start_with?("[") ? "#{prefix}#{path}" : "#{prefix}.#{path}"
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
def diff
|
|
22
|
+
return @diff if defined?(@diff)
|
|
23
|
+
|
|
24
|
+
@diff = TextDiff.for(old_value, new_value)
|
|
25
|
+
end
|
|
26
|
+
|
|
21
27
|
def old_text
|
|
22
28
|
AttributeChange.display(old_value)
|
|
23
29
|
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
module PaperView
|
|
2
|
+
# A line based diff for values that are too long to read as two blocks of text.
|
|
3
|
+
class TextDiff
|
|
4
|
+
CONTEXT = 3
|
|
5
|
+
MIN_LINES = 4
|
|
6
|
+
|
|
7
|
+
# Past a couple of hundred changed lines nobody reads a diff anymore, and the search
|
|
8
|
+
# for the shortest one costs its square, so both sides become one replaced block.
|
|
9
|
+
MAX_CHANGES = 200
|
|
10
|
+
|
|
11
|
+
Line = Struct.new(:kind, :text, :old_number, :new_number) do
|
|
12
|
+
def context?
|
|
13
|
+
kind == :context
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Hunk = Struct.new(:lines) do
|
|
18
|
+
def header
|
|
19
|
+
"@@ -#{old_start},#{lines.count(&:old_number)} +#{new_start},#{lines.count(&:new_number)} @@"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def old_start
|
|
25
|
+
lines.detect(&:old_number)&.old_number || 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def new_start
|
|
29
|
+
lines.detect(&:new_number)&.new_number || 0
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.for(old_text, new_text)
|
|
34
|
+
return unless old_text.is_a?(String) && new_text.is_a?(String)
|
|
35
|
+
|
|
36
|
+
old_lines = old_text.lines.map(&:chomp)
|
|
37
|
+
new_lines = new_text.lines.map(&:chomp)
|
|
38
|
+
return if old_lines == new_lines || [old_lines.size, new_lines.size].max < MIN_LINES
|
|
39
|
+
|
|
40
|
+
new(old_lines, new_lines)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize(old_lines, new_lines)
|
|
44
|
+
@old_lines = old_lines
|
|
45
|
+
@new_lines = new_lines
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def lines
|
|
49
|
+
@lines ||= (trace = shortest_edit) ? retrace(trace) : replaced
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def hunks
|
|
53
|
+
@hunks ||= groups.map { |first, last| Hunk.new(lines[first..last]) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def added_count
|
|
57
|
+
lines.count { |line| line.kind == :added }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def removed_count
|
|
61
|
+
lines.count { |line| line.kind == :deleted }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def unchanged_count
|
|
65
|
+
lines.count(&:context?)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
attr_reader :old_lines, :new_lines
|
|
71
|
+
|
|
72
|
+
# Myers' diff, one difference at a time. Every path is tracked by its diagonal
|
|
73
|
+
# k = x - y, and ends[k] is how far into the old text the best path on that diagonal
|
|
74
|
+
# reaches. Each step moves one line sideways and then rides the diagonal of matching
|
|
75
|
+
# lines as far as it goes, so untouched stretches cost nothing.
|
|
76
|
+
def shortest_edit
|
|
77
|
+
ends = {1 => 0}
|
|
78
|
+
trace = []
|
|
79
|
+
|
|
80
|
+
(0..MAX_CHANGES).each do |differences|
|
|
81
|
+
trace << ends.dup
|
|
82
|
+
|
|
83
|
+
(-differences).step(differences, 2) do |k|
|
|
84
|
+
x = added?(ends, k, differences) ? ends[k + 1] : ends[k - 1] + 1
|
|
85
|
+
y = x - k
|
|
86
|
+
|
|
87
|
+
while x < old_lines.size && y < new_lines.size && old_lines[x] == new_lines[y]
|
|
88
|
+
x += 1
|
|
89
|
+
y += 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
ends[k] = x
|
|
93
|
+
return trace if x >= old_lines.size && y >= new_lines.size
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# A path reaches diagonal k either by moving down from k + 1, which consumes a new
|
|
101
|
+
# line, or by moving right from k - 1, which consumes an old one.
|
|
102
|
+
def added?(ends, k, differences)
|
|
103
|
+
k == -differences || (k != differences && ends[k - 1] < ends[k + 1])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Retrace the winning path backwards: every diagonal stretch is context, every
|
|
107
|
+
# sideways step is the one line that was added or deleted there.
|
|
108
|
+
def retrace(trace)
|
|
109
|
+
x = old_lines.size
|
|
110
|
+
y = new_lines.size
|
|
111
|
+
|
|
112
|
+
trace.each_with_index.reverse_each.flat_map { |ends, differences|
|
|
113
|
+
k = x - y
|
|
114
|
+
previous_k = added?(ends, k, differences) ? k + 1 : k - 1
|
|
115
|
+
previous_x = ends[previous_k]
|
|
116
|
+
previous_y = previous_x - previous_k
|
|
117
|
+
steps = []
|
|
118
|
+
|
|
119
|
+
while x > previous_x && y > previous_y
|
|
120
|
+
x -= 1
|
|
121
|
+
y -= 1
|
|
122
|
+
steps << Line.new(:context, old_lines[x], x + 1, y + 1)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
steps << step(previous_x, previous_y, added: x == previous_x) if differences > 0
|
|
126
|
+
x = previous_x
|
|
127
|
+
y = previous_y
|
|
128
|
+
steps
|
|
129
|
+
}.reverse
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def step(old_index, new_index, added:)
|
|
133
|
+
if added
|
|
134
|
+
Line.new(:added, new_lines[new_index], nil, new_index + 1)
|
|
135
|
+
else
|
|
136
|
+
Line.new(:deleted, old_lines[old_index], old_index + 1, nil)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def replaced
|
|
141
|
+
old_lines.each_with_index.map { |text, index| Line.new(:deleted, text, index + 1, nil) } +
|
|
142
|
+
new_lines.each_with_index.map { |text, index| Line.new(:added, text, nil, index + 1) }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Changed lines closer than two context blocks share their surroundings and stay in one hunk.
|
|
146
|
+
def groups
|
|
147
|
+
lines.each_index.reject { |index| lines[index].context? }
|
|
148
|
+
.slice_when { |before, after| after - before > CONTEXT * 2 }
|
|
149
|
+
.map { |run| [[run.first - CONTEXT, 0].max, [run.last + CONTEXT, lines.size - 1].min] }
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
</head>
|
|
20
20
|
|
|
21
21
|
<body>
|
|
22
|
+
<%= render "paper_view/shared/theme" %>
|
|
23
|
+
|
|
22
24
|
<header class="pv-header">
|
|
23
25
|
<%= link_to root_path, class: "pv-header__brand" do %>
|
|
24
26
|
PaperView
|
|
@@ -29,9 +31,16 @@
|
|
|
29
31
|
<%= link_to "Stats", stats_path, class: paper_view_nav_class(controller_name == "stats") %>
|
|
30
32
|
</nav>
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
<div class="pv-header__right">
|
|
35
|
+
<% if main_app.respond_to?(:root_path) %>
|
|
36
|
+
<span class="pv-header__meta"><%= link_to "← Back to app", main_app.root_path, data: {turbo: false} %></span>
|
|
37
|
+
<% end %>
|
|
38
|
+
|
|
39
|
+
<button type="button" class="pv-header__theme" data-pv-theme-toggle title="Switch between light and dark">
|
|
40
|
+
<span class="pv-theme__to-dark">☾ Dark</span>
|
|
41
|
+
<span class="pv-theme__to-light">☀ Light</span>
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
35
44
|
</header>
|
|
36
45
|
|
|
37
46
|
<main class="pv-main pv-stack">
|
|
@@ -176,6 +176,18 @@
|
|
|
176
176
|
}
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
+
document.addEventListener("click", function (event) {
|
|
180
|
+
if (!event.target.closest("[data-pv-theme-toggle]")) return;
|
|
181
|
+
|
|
182
|
+
var root = document.documentElement;
|
|
183
|
+
var stored = root.getAttribute("data-pv-theme");
|
|
184
|
+
var dark = stored ? stored === "dark" : window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
185
|
+
var next = dark ? "light" : "dark";
|
|
186
|
+
|
|
187
|
+
root.setAttribute("data-pv-theme", next);
|
|
188
|
+
try { localStorage.setItem("pv-theme", next); } catch (error) {}
|
|
189
|
+
});
|
|
190
|
+
|
|
179
191
|
document.addEventListener("change", function (event) {
|
|
180
192
|
var select = event.target.closest("select[data-pv-autosubmit]");
|
|
181
193
|
if (!select || !select.form) return;
|
|
@@ -38,8 +38,13 @@
|
|
|
38
38
|
--pv-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
:root[data-pv-theme="light"] { color-scheme: light; }
|
|
42
|
+
:root[data-pv-theme="dark"] { color-scheme: dark; }
|
|
43
|
+
.pv-theme__to-light { display: none; }
|
|
44
|
+
|
|
45
|
+
/* The dark palette is spelled out twice because a media query and the attribute override cannot share a selector. */
|
|
41
46
|
@media (prefers-color-scheme: dark) {
|
|
42
|
-
:root {
|
|
47
|
+
:root:not([data-pv-theme="light"]) {
|
|
43
48
|
--pv-bg: #0d1117;
|
|
44
49
|
--pv-surface: #161b22;
|
|
45
50
|
--pv-surface-muted: #1b212a;
|
|
@@ -68,7 +73,41 @@
|
|
|
68
73
|
--pv-warn: #e3b341;
|
|
69
74
|
--pv-warn-bg: #2b2209;
|
|
70
75
|
}
|
|
76
|
+
:root:not([data-pv-theme="light"]) .pv-theme__to-dark { display: none; }
|
|
77
|
+
:root:not([data-pv-theme="light"]) .pv-theme__to-light { display: inline; }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:root[data-pv-theme="dark"] {
|
|
81
|
+
--pv-bg: #0d1117;
|
|
82
|
+
--pv-surface: #161b22;
|
|
83
|
+
--pv-surface-muted: #1b212a;
|
|
84
|
+
--pv-row-hover: #1b212a;
|
|
85
|
+
--pv-row-selected: #16233b;
|
|
86
|
+
--pv-border: #272e38;
|
|
87
|
+
--pv-border-subtle: #21262d;
|
|
88
|
+
--pv-border-input: #39414d;
|
|
89
|
+
--pv-pill-bg: #21262d;
|
|
90
|
+
--pv-ink: #e6edf3;
|
|
91
|
+
--pv-ink-secondary: #c9d1d9;
|
|
92
|
+
--pv-ink-muted: #b1bac4;
|
|
93
|
+
--pv-ink-soft: #9198a1;
|
|
94
|
+
--pv-ink-faint: #6e7681;
|
|
95
|
+
--pv-accent: #4a8bff;
|
|
96
|
+
--pv-accent-hover: #79a9ff;
|
|
97
|
+
--pv-dot: #39414d;
|
|
98
|
+
--pv-skeleton: #232a33;
|
|
99
|
+
--pv-skeleton-sheen: #2f3945;
|
|
100
|
+
--pv-del: #ff7b72;
|
|
101
|
+
--pv-del-bg: #2d1618;
|
|
102
|
+
--pv-del-rule: #7d2a2a;
|
|
103
|
+
--pv-add: #57d364;
|
|
104
|
+
--pv-add-bg: #12261a;
|
|
105
|
+
--pv-add-rule: #2b6b36;
|
|
106
|
+
--pv-warn: #e3b341;
|
|
107
|
+
--pv-warn-bg: #2b2209;
|
|
71
108
|
}
|
|
109
|
+
:root[data-pv-theme="dark"] .pv-theme__to-dark { display: none; }
|
|
110
|
+
:root[data-pv-theme="dark"] .pv-theme__to-light { display: inline; }
|
|
72
111
|
|
|
73
112
|
body {
|
|
74
113
|
margin: 0;
|
|
@@ -99,7 +138,8 @@
|
|
|
99
138
|
}
|
|
100
139
|
.pv-header__brand { display: flex; align-items: center; gap: 8px; color: var(--pv-ink); font-size: 15px; font-weight: 650; }
|
|
101
140
|
.pv-header__brand:hover { color: var(--pv-ink); text-decoration: none; }
|
|
102
|
-
.pv-
|
|
141
|
+
.pv-header__right { display: flex; align-items: center; gap: 16px; margin-left: auto; }
|
|
142
|
+
.pv-header__meta { display: flex; align-items: center; color: var(--pv-ink-soft); font-size: 12.5px; }
|
|
103
143
|
.pv-header__nav { display: flex; align-items: stretch; gap: 20px; }
|
|
104
144
|
.pv-header__link {
|
|
105
145
|
display: flex;
|
|
@@ -111,6 +151,18 @@
|
|
|
111
151
|
}
|
|
112
152
|
.pv-header__link:hover { color: var(--pv-ink); text-decoration: none; }
|
|
113
153
|
.pv-header__link--active { color: var(--pv-ink); font-weight: 600; box-shadow: inset 0 -2px 0 var(--pv-ink); }
|
|
154
|
+
.pv-header__theme {
|
|
155
|
+
padding: 4px 11px;
|
|
156
|
+
background: transparent;
|
|
157
|
+
border: 1px solid var(--pv-border);
|
|
158
|
+
border-radius: 999px;
|
|
159
|
+
color: var(--pv-ink-soft);
|
|
160
|
+
font: inherit;
|
|
161
|
+
font-size: 12.5px;
|
|
162
|
+
line-height: 1.5;
|
|
163
|
+
cursor: pointer;
|
|
164
|
+
}
|
|
165
|
+
.pv-header__theme:hover { border-color: var(--pv-border-input); color: var(--pv-ink); }
|
|
114
166
|
|
|
115
167
|
.pv-main { max-width: 1560px; margin: 0 auto; padding: 24px; }
|
|
116
168
|
.pv-stack { display: flex; flex-direction: column; gap: 16px; }
|
|
@@ -272,6 +324,10 @@
|
|
|
272
324
|
.pv-change { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font: 400 12px/18px var(--pv-mono); }
|
|
273
325
|
.pv-change__path { color: var(--pv-ink); }
|
|
274
326
|
.pv-change__arrow { color: var(--pv-ink-faint); }
|
|
327
|
+
.pv-tally { padding: 1px 6px; border-radius: 4px; }
|
|
328
|
+
.pv-tally--add { color: var(--pv-add); background: var(--pv-add-bg); }
|
|
329
|
+
.pv-tally--del { color: var(--pv-del); background: var(--pv-del-bg); }
|
|
330
|
+
.pv-tally__unit { color: var(--pv-ink-faint); }
|
|
275
331
|
.pv-value {
|
|
276
332
|
max-width: 280px;
|
|
277
333
|
padding: 1px 6px;
|
|
@@ -331,6 +387,19 @@
|
|
|
331
387
|
.pv-keys__values { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font: 400 12px/1.5 var(--pv-mono); }
|
|
332
388
|
.pv-keys__values .pv-value { max-width: none; overflow: visible; white-space: normal; word-break: break-all; }
|
|
333
389
|
|
|
390
|
+
.pv-diff { display: flex; flex-direction: column; gap: 6px; }
|
|
391
|
+
.pv-diff__stats { display: flex; gap: 8px; font: 400 11px/1.4 var(--pv-mono); }
|
|
392
|
+
.pv-diff__hunk { border: 1px solid var(--pv-border); border-radius: 6px; overflow: hidden; }
|
|
393
|
+
.pv-diff__range { padding: 3px 8px; background: var(--pv-surface-muted); border-bottom: 1px solid var(--pv-border); font: 400 11px/1.6 var(--pv-mono); color: var(--pv-ink-faint); }
|
|
394
|
+
.pv-diff__line { display: grid; grid-template-columns: 34px 34px 10px minmax(0, 1fr); gap: 4px; font: 400 12px/1.5 var(--pv-mono); color: var(--pv-ink-secondary); }
|
|
395
|
+
.pv-diff__line--deleted { color: var(--pv-del); background: var(--pv-del-bg); }
|
|
396
|
+
.pv-diff__line--added { color: var(--pv-add); background: var(--pv-add-bg); }
|
|
397
|
+
.pv-diff__num { padding-left: 6px; text-align: right; color: var(--pv-ink-faint); user-select: none; }
|
|
398
|
+
.pv-diff__sign { text-align: center; user-select: none; }
|
|
399
|
+
.pv-diff__line--deleted .pv-diff__sign::before { content: "-"; }
|
|
400
|
+
.pv-diff__line--added .pv-diff__sign::before { content: "+"; }
|
|
401
|
+
.pv-diff__text { padding-right: 8px; white-space: pre-wrap; word-break: break-all; }
|
|
402
|
+
|
|
334
403
|
.pv-disclosures { display: grid; grid-template-columns: 1fr 1fr; column-gap: 12px; }
|
|
335
404
|
.pv-disclosure {
|
|
336
405
|
padding: 0;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<div class="pv-diff">
|
|
2
|
+
<div class="pv-diff__stats">
|
|
3
|
+
<%= render "paper_view/versions/tally", diff: diff %>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<% diff.hunks.each do |hunk| %>
|
|
7
|
+
<div class="pv-diff__hunk">
|
|
8
|
+
<div class="pv-diff__range"><%= hunk.header %></div>
|
|
9
|
+
|
|
10
|
+
<% hunk.lines.each do |line| %>
|
|
11
|
+
<div class="pv-diff__line pv-diff__line--<%= line.kind %>">
|
|
12
|
+
<span class="pv-diff__num"><%= line.old_number %></span>
|
|
13
|
+
<span class="pv-diff__num"><%= line.new_number %></span>
|
|
14
|
+
<span class="pv-diff__sign"></span>
|
|
15
|
+
<span class="pv-diff__text"><%= line.text %></span>
|
|
16
|
+
</div>
|
|
17
|
+
<% end %>
|
|
18
|
+
</div>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<button
|
|
2
|
+
type="button"
|
|
3
|
+
class="pv-attribute__unchanged"
|
|
4
|
+
aria-expanded="false"
|
|
5
|
+
aria-controls="<%= id %>"
|
|
6
|
+
data-pv-toggle="<%= id %>"
|
|
7
|
+
>
|
|
8
|
+
<span class="pv-disclosure__arrow">▸</span>
|
|
9
|
+
<%= count %> unchanged <%= unit.pluralize(count) %>
|
|
10
|
+
</button>
|
|
11
|
+
|
|
12
|
+
<div class="pv-bars" id="<%= id %>" hidden>
|
|
13
|
+
<div class="pv-bar pv-bar--old"><%= change.old_text %></div>
|
|
14
|
+
<div class="pv-bar pv-bar--new"><%= change.new_text %></div>
|
|
15
|
+
</div>
|
|
@@ -92,32 +92,32 @@
|
|
|
92
92
|
<div class="pv-keys">
|
|
93
93
|
<span class="pv-keys__name"><%= leaf.path %></span>
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
95
|
+
<% if leaf.diff %>
|
|
96
|
+
<%= render "paper_view/versions/diff", diff: leaf.diff %>
|
|
97
|
+
<% else %>
|
|
98
|
+
<div class="pv-keys__values">
|
|
99
|
+
<span class="pv-value pv-value--old"><%= leaf.old_text %></span>
|
|
100
|
+
<span class="pv-change__arrow">→</span>
|
|
101
|
+
<span class="pv-value pv-value--new"><%= leaf.new_text %></span>
|
|
102
|
+
</div>
|
|
103
|
+
<% end %>
|
|
100
104
|
</div>
|
|
101
105
|
<% end %>
|
|
102
106
|
<% if change.unchanged_count.positive? %>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
aria-controls="<%= whole_id %>"
|
|
109
|
-
data-pv-toggle="<%= whole_id %>"
|
|
110
|
-
>
|
|
111
|
-
<span class="pv-disclosure__arrow">▸</span>
|
|
112
|
-
<%= change.unchanged_count %> unchanged
|
|
113
|
-
<%= "value".pluralize(change.unchanged_count) %>
|
|
114
|
-
</button>
|
|
115
|
-
|
|
116
|
-
<div class="pv-bars" id="<%= whole_id %>" hidden>
|
|
117
|
-
<div class="pv-bar pv-bar--old"><%= change.old_text %></div>
|
|
118
|
-
<div class="pv-bar pv-bar--new"><%= change.new_text %></div>
|
|
119
|
-
</div>
|
|
107
|
+
<%= render "paper_view/versions/full_value",
|
|
108
|
+
change: change,
|
|
109
|
+
id: "pv-whole-#{row.id}-#{index}",
|
|
110
|
+
count: change.unchanged_count,
|
|
111
|
+
unit: "value" %>
|
|
120
112
|
<% end %>
|
|
113
|
+
<% elsif change.diff %>
|
|
114
|
+
<%= render "paper_view/versions/diff", diff: change.diff %>
|
|
115
|
+
|
|
116
|
+
<%= render "paper_view/versions/full_value",
|
|
117
|
+
change: change,
|
|
118
|
+
id: "pv-whole-#{row.id}-#{index}",
|
|
119
|
+
count: change.diff.unchanged_count,
|
|
120
|
+
unit: "line" %>
|
|
121
121
|
<% else %>
|
|
122
122
|
<div class="pv-bars">
|
|
123
123
|
<div class="pv-bar pv-bar--old"><%= change.old_text %></div>
|
|
@@ -35,9 +35,14 @@
|
|
|
35
35
|
<% row.lines.each do |line| %>
|
|
36
36
|
<div class="pv-change">
|
|
37
37
|
<span class="pv-change__path"><%= line.path %></span>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
|
|
39
|
+
<% if line.diff %>
|
|
40
|
+
<%= render "paper_view/versions/tally", diff: line.diff %>
|
|
41
|
+
<% else %>
|
|
42
|
+
<span class="pv-value pv-value--old" title="<%= line.old_text %>"><%= truncate(line.old_text, length: 140) %></span>
|
|
43
|
+
<span class="pv-change__arrow">→</span>
|
|
44
|
+
<span class="pv-value pv-value--new" title="<%= line.new_text %>"><%= truncate(line.new_text, length: 140) %></span>
|
|
45
|
+
<% end %>
|
|
41
46
|
</div>
|
|
42
47
|
<% end %>
|
|
43
48
|
|
data/lib/paper_view/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: paper_view
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leon Vogt
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- app/models/paper_view/payload.rb
|
|
46
46
|
- app/models/paper_view/payload_sizes.rb
|
|
47
47
|
- app/models/paper_view/table_stats.rb
|
|
48
|
+
- app/models/paper_view/text_diff.rb
|
|
48
49
|
- app/models/paper_view/timeline.rb
|
|
49
50
|
- app/models/paper_view/version_query.rb
|
|
50
51
|
- app/models/paper_view/version_row.rb
|
|
@@ -53,11 +54,15 @@ files:
|
|
|
53
54
|
- app/views/paper_view/shared/_flash.html.erb
|
|
54
55
|
- app/views/paper_view/shared/_script.html.erb
|
|
55
56
|
- app/views/paper_view/shared/_styles.html.erb
|
|
57
|
+
- app/views/paper_view/shared/_theme.html.erb
|
|
56
58
|
- app/views/paper_view/stats/_stats.html.erb
|
|
57
59
|
- app/views/paper_view/stats/show.html.erb
|
|
60
|
+
- app/views/paper_view/versions/_diff.html.erb
|
|
58
61
|
- app/views/paper_view/versions/_filters.html.erb
|
|
59
62
|
- app/views/paper_view/versions/_footer.html.erb
|
|
63
|
+
- app/views/paper_view/versions/_full_value.html.erb
|
|
60
64
|
- app/views/paper_view/versions/_panel.html.erb
|
|
65
|
+
- app/views/paper_view/versions/_tally.html.erb
|
|
61
66
|
- app/views/paper_view/versions/_timeline_row.html.erb
|
|
62
67
|
- app/views/paper_view/versions/index.html.erb
|
|
63
68
|
- config/routes.rb
|