publishing_platform_nokodiff 0.2.0 → 0.3.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a5784bf33e2775f851b7fcd645014324988ae99371d934bddca53493c25d938
|
|
4
|
+
data.tar.gz: 65dbd9c81fb530d5719b7ff86878e3624946fb45cadf9f978a912429ea0ba6cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6bc58874e20ee3fa14f7d485e49eed2e451dfa1f129696017e27410ddd475a79275f5ca0704ee609a007faf0ea575ccb32ed7055de80f2efc02d5b0b63eedd3
|
|
7
|
+
data.tar.gz: 9e041f0e47b18841366496c8a263cce6173b293d46d51e1eb269f9df174b26dd2fca6cf40b31e0fd6b103757744712651ee79b14cb4548d49509ec0adb536f50
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// stylelint-disable selector-no-qualifying-type, max-nesting-depth
|
|
2
|
-
// Diff of two editions
|
|
3
1
|
$added-color: #e6fff3;
|
|
4
2
|
$diff-marker-added-color: #99ffcf;
|
|
5
3
|
$removed-color: #fadede;
|
|
@@ -7,66 +5,188 @@ $diff-marker-removed-color: #f3aeac;
|
|
|
7
5
|
$text-colour: #0b0c0c;
|
|
8
6
|
$light-grey: #f3f2f1;
|
|
9
7
|
$black: #0b0c0c;
|
|
8
|
+
$comparison-sidebar-width: 40px;
|
|
9
|
+
$comparison-sidebar-padding: 15px;
|
|
10
|
+
$comparison-sidebar-offset: $comparison-sidebar-width + $comparison-sidebar-padding;
|
|
10
11
|
|
|
11
12
|
.compare-editions {
|
|
12
13
|
position: relative;
|
|
13
|
-
border-left:
|
|
14
|
-
padding:
|
|
14
|
+
border-left: $comparison-sidebar-width solid $light-grey;
|
|
15
|
+
padding: $comparison-sidebar-padding;
|
|
16
|
+
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
|
|
17
|
+
|
|
18
|
+
.visually-hidden {
|
|
19
|
+
position: absolute !important;
|
|
20
|
+
width: 1px !important;
|
|
21
|
+
height: 1px !important;
|
|
22
|
+
// If margin is set to a negative value it can cause text to be announced in
|
|
23
|
+
// the wrong order in VoiceOver for OSX
|
|
24
|
+
margin: 0 !important;
|
|
25
|
+
padding: 0 !important;
|
|
26
|
+
overflow: hidden !important;
|
|
27
|
+
|
|
28
|
+
clip: rect(0 0 0 0) !important;
|
|
29
|
+
clip-path: inset(50%) !important;
|
|
30
|
+
|
|
31
|
+
border: 0 !important;
|
|
32
|
+
|
|
33
|
+
// For long content, line feeds are not interpreted as spaces and small width
|
|
34
|
+
// causes content to wrap 1 word per line:
|
|
35
|
+
// https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe
|
|
36
|
+
white-space: nowrap !important;
|
|
37
|
+
|
|
38
|
+
// Prevent users from selecting or copying visually-hidden text. This prevents
|
|
39
|
+
// a user unintentionally copying more text than they intended and needing to
|
|
40
|
+
// manually trim it down again.
|
|
41
|
+
user-select: none !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
td::before {
|
|
45
|
+
// Backup to handle any inputted classes which make use of a ::before attached to a td element
|
|
46
|
+
// unsetting all styles for it will prevent it interfering with the ::before that we need to use
|
|
47
|
+
// for the diff marker
|
|
48
|
+
all: unset;
|
|
49
|
+
}
|
|
15
50
|
|
|
16
51
|
.diff {
|
|
17
52
|
position: static;
|
|
18
53
|
margin-bottom: 10px;
|
|
19
54
|
|
|
20
|
-
del, ins {
|
|
21
|
-
|
|
55
|
+
&.del, &.ins {
|
|
56
|
+
&:is(li){ display: list-item; }
|
|
57
|
+
|
|
58
|
+
position: relative;
|
|
22
59
|
display: block;
|
|
23
60
|
text-decoration: none;
|
|
24
|
-
|
|
61
|
+
background-color: transparent;
|
|
25
62
|
}
|
|
26
63
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
64
|
+
&::after {
|
|
65
|
+
content: "";
|
|
66
|
+
position: absolute;
|
|
67
|
+
top: 0;
|
|
68
|
+
bottom: 0;
|
|
69
|
+
left: -$comparison-sidebar-padding;
|
|
70
|
+
min-height: 1.5em;
|
|
71
|
+
width: 100vw;
|
|
72
|
+
display: block;
|
|
73
|
+
z-index: -1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&.del {
|
|
77
|
+
&::after { background-color: $removed-color; }
|
|
30
78
|
|
|
31
79
|
span.diff-marker {
|
|
32
|
-
font-weight: normal;
|
|
33
80
|
background-color: $diff-marker-removed-color;
|
|
34
81
|
border-bottom: 2px dashed $black;
|
|
35
82
|
}
|
|
36
83
|
}
|
|
37
84
|
|
|
38
|
-
ins {
|
|
39
|
-
background-color: $added-color;
|
|
40
|
-
padding-bottom: 2px;
|
|
85
|
+
&.ins {
|
|
86
|
+
&::after { background-color: $added-color; }
|
|
41
87
|
|
|
42
88
|
span.diff-marker {
|
|
43
|
-
font-weight: normal;
|
|
44
89
|
background-color: $diff-marker-added-color;
|
|
45
90
|
border-bottom: 2px dashed $black;
|
|
46
91
|
}
|
|
47
92
|
}
|
|
48
93
|
|
|
49
|
-
|
|
50
|
-
|
|
94
|
+
&::before {
|
|
95
|
+
// Some styles use ::before in ways we need to override to make this work
|
|
96
|
+
border-radius: unset !important;
|
|
97
|
+
border: unset !important;
|
|
98
|
+
|
|
51
99
|
position: absolute;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
100
|
+
top: 0;
|
|
101
|
+
left: -$comparison-sidebar-offset;
|
|
102
|
+
width: $comparison-sidebar-width;
|
|
103
|
+
height: 100%;
|
|
104
|
+
min-height: 1.5em;
|
|
55
105
|
line-height: 1.5em;
|
|
56
106
|
text-align: center;
|
|
107
|
+
font-weight: normal;
|
|
57
108
|
z-index: 10;
|
|
58
109
|
}
|
|
110
|
+
|
|
111
|
+
&.del::before {
|
|
112
|
+
color: $text-colour;
|
|
113
|
+
background-color: $diff-marker-removed-color;
|
|
114
|
+
content: "−" !important;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&.ins::before {
|
|
118
|
+
color: $text-colour;
|
|
119
|
+
background-color: $diff-marker-added-color;
|
|
120
|
+
content: "+" !important;
|
|
121
|
+
}
|
|
59
122
|
}
|
|
60
123
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
// List-specific overrides for non-govuk lists
|
|
127
|
+
$list-offset: -40px;
|
|
128
|
+
|
|
129
|
+
li span.diff, ul span.diff{
|
|
130
|
+
&::after {
|
|
131
|
+
left: -$comparison-sidebar-padding + $list-offset;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&::before {
|
|
135
|
+
left: -$comparison-sidebar-offset + $list-offset;
|
|
136
|
+
}
|
|
65
137
|
}
|
|
66
138
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
139
|
+
// Table-specific overrides
|
|
140
|
+
$table-row-offset: -2px;
|
|
141
|
+
|
|
142
|
+
table {
|
|
143
|
+
overflow-x: visible;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
tr.diff {
|
|
147
|
+
// bootstrap styles add a background color to td, th
|
|
148
|
+
td, th {
|
|
149
|
+
background-color: transparent;
|
|
150
|
+
}
|
|
151
|
+
position: static;
|
|
152
|
+
display: table-row !important;
|
|
153
|
+
transform: scale(1);
|
|
154
|
+
|
|
155
|
+
&.del::after, &.ins::after {
|
|
156
|
+
left: -$comparison-sidebar-padding + $table-row-offset;
|
|
157
|
+
// Need to account for border in row element
|
|
158
|
+
height: 100%;
|
|
159
|
+
top: 1px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Attaching a ::before psuedoelement to a <tr> creates a phantom column so we need to hide it
|
|
163
|
+
// and instead attach one to the first-child <td>
|
|
164
|
+
&::before {
|
|
165
|
+
display: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
td:first-child::before, th:first-child::before {
|
|
169
|
+
position: absolute;
|
|
170
|
+
left: -$comparison-sidebar-offset;
|
|
171
|
+
width: $comparison-sidebar-width;
|
|
172
|
+
height: 100%;
|
|
173
|
+
min-height: 1.5em;
|
|
174
|
+
top: 1px;
|
|
175
|
+
line-height: 1.5em;
|
|
176
|
+
text-align: center;
|
|
177
|
+
z-index: 10;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
&.del td:first-child::before, &.del th:first-child::before {
|
|
181
|
+
color: $text-colour;
|
|
182
|
+
background-color: $diff-marker-removed-color;
|
|
183
|
+
content: "−";
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
&.ins td:first-child::before, &.ins th:first-child::before {
|
|
187
|
+
color: $text-colour;
|
|
188
|
+
background-color: $diff-marker-added-color;
|
|
189
|
+
content: "+";
|
|
190
|
+
}
|
|
71
191
|
}
|
|
72
|
-
}
|
|
192
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
module PublishingPlatformNokodiff
|
|
2
2
|
class Differ
|
|
3
|
+
include FormattingHelpers
|
|
4
|
+
|
|
5
|
+
BOTTOM_LEVEL_ELEMENTS = %w[p li tr h1 h2 h3 h4 h5 h6].freeze
|
|
6
|
+
|
|
3
7
|
def initialize(before, after)
|
|
4
8
|
@before = before
|
|
5
9
|
@after = after
|
|
@@ -13,9 +17,9 @@ module PublishingPlatformNokodiff
|
|
|
13
17
|
when :changed
|
|
14
18
|
changed_block(diff[:before], diff[:after])
|
|
15
19
|
when :deleted
|
|
16
|
-
diff[:before].
|
|
20
|
+
diff[:before].text? ? build_deleted_text_html(diff[:before]) : build_deleted_element_html(diff[:before])
|
|
17
21
|
when :added
|
|
18
|
-
diff[:after].
|
|
22
|
+
diff[:after].text? ? build_added_text_html(diff[:after]) : build_added_element_html(diff[:after])
|
|
19
23
|
end
|
|
20
24
|
}.join("\n")
|
|
21
25
|
end
|
|
@@ -60,21 +64,15 @@ module PublishingPlatformNokodiff
|
|
|
60
64
|
end
|
|
61
65
|
|
|
62
66
|
def changed_block(before_node, after_node)
|
|
63
|
-
if structurally_similar?(before_node, after_node) &&
|
|
67
|
+
if structurally_similar?(before_node, after_node) && !treat_element_as_single_change?(before_node)
|
|
64
68
|
inner_diff = Differ.new(before_node, after_node).to_html
|
|
65
69
|
rebuild_element(after_node, inner_diff)
|
|
70
|
+
elsif both_text_nodes?(before_node, after_node)
|
|
71
|
+
before_node, after_node = diff_raw_text(before_node, after_node)
|
|
72
|
+
build_deleted_text_html(before_node) + build_added_text_html(after_node)
|
|
66
73
|
else
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
else
|
|
70
|
-
diff_sub_elements(before_node, after_node)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
if before_node.name == "li"
|
|
74
|
-
deleted_li(before_diff) + added_li(after_diff)
|
|
75
|
-
else
|
|
76
|
-
deleted_block(before_diff) + added_block(after_diff)
|
|
77
|
-
end
|
|
74
|
+
before_node, after_node = diff_sub_elements(before_node, after_node)
|
|
75
|
+
build_deleted_element_html(before_node) + build_added_element_html(after_node)
|
|
78
76
|
end
|
|
79
77
|
end
|
|
80
78
|
|
|
@@ -88,13 +86,11 @@ module PublishingPlatformNokodiff
|
|
|
88
86
|
before_node.name == after_node.name
|
|
89
87
|
end
|
|
90
88
|
|
|
91
|
-
# We want all
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
def
|
|
95
|
-
before_node.name
|
|
96
|
-
!before_node.name.match(/^h[1-6]$/) &&
|
|
97
|
-
before_node.name != "li"
|
|
89
|
+
# We want to treat all content within certain elements as single changes, even if they are structurally different,
|
|
90
|
+
# to avoid overwhelming the user with changes, and ensure any nested elements are included within the diff,
|
|
91
|
+
# rather than being treated as added or removed content on their own.
|
|
92
|
+
def treat_element_as_single_change?(before_node)
|
|
93
|
+
BOTTOM_LEVEL_ELEMENTS.include?(before_node.name)
|
|
98
94
|
end
|
|
99
95
|
|
|
100
96
|
def rebuild_element(template_node, inner_html)
|
|
@@ -118,19 +114,15 @@ module PublishingPlatformNokodiff
|
|
|
118
114
|
end
|
|
119
115
|
|
|
120
116
|
def diff_sub_elements(before_html, after_html)
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
before_fragment = before_html.dup
|
|
118
|
+
after_fragment = after_html.dup
|
|
123
119
|
|
|
124
|
-
|
|
120
|
+
PublishingPlatformNokodiff::TextNodeDiffs.new(before_fragment, after_fragment).call
|
|
125
121
|
|
|
126
122
|
merge_adjacent_highlighted_changes(before_fragment)
|
|
127
123
|
merge_adjacent_highlighted_changes(after_fragment)
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
[before_fragment.inner_html, after_fragment.inner_html]
|
|
131
|
-
else
|
|
132
|
-
[before_fragment.to_html, after_fragment.to_html]
|
|
133
|
-
end
|
|
125
|
+
[before_fragment, after_fragment]
|
|
134
126
|
end
|
|
135
127
|
|
|
136
128
|
def merge_adjacent_highlighted_changes(node)
|
|
@@ -159,40 +151,76 @@ module PublishingPlatformNokodiff
|
|
|
159
151
|
node.to_html
|
|
160
152
|
end
|
|
161
153
|
|
|
162
|
-
def
|
|
163
|
-
|
|
164
|
-
<
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
</
|
|
177
|
-
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
154
|
+
def build_deleted_text_html(html)
|
|
155
|
+
<<~HTML
|
|
156
|
+
<span class="diff del">
|
|
157
|
+
<span class="visually-hidden">Removed content </span>
|
|
158
|
+
#{html}
|
|
159
|
+
</span>
|
|
160
|
+
HTML
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def build_added_text_html(html)
|
|
164
|
+
<<~HTML
|
|
165
|
+
<span class="diff ins">
|
|
166
|
+
<span class="visually-hidden">Added content </span>
|
|
167
|
+
#{html}
|
|
168
|
+
</span>
|
|
169
|
+
HTML
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def build_added_element_html(element)
|
|
173
|
+
if element.name == "tr"
|
|
174
|
+
insert_table_row_change_marker(element, "Added row")
|
|
175
|
+
class_string = "#{element['class']&.gsub('del', '')} diff ins".strip
|
|
176
|
+
|
|
177
|
+
<<~HTML
|
|
178
|
+
<#{element.name} class="#{class_string}">
|
|
179
|
+
#{element.inner_html}
|
|
180
|
+
</#{element.name}>
|
|
181
|
+
HTML
|
|
182
|
+
|
|
183
|
+
else
|
|
184
|
+
class_string = element["class"].nil? ? nil : " class= \"#{element['class']}\""
|
|
185
|
+
span_class_string = "diff ins"
|
|
186
|
+
|
|
187
|
+
<<~HTML
|
|
188
|
+
<#{element.name}#{class_string}>
|
|
189
|
+
<span class="#{span_class_string}">
|
|
190
|
+
<span class="visually-hidden">Added content </span>
|
|
191
|
+
#{element.inner_html}
|
|
192
|
+
</span>
|
|
193
|
+
</#{element.name}>
|
|
194
|
+
HTML
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def build_deleted_element_html(element)
|
|
199
|
+
if element.name == "tr"
|
|
200
|
+
insert_table_row_change_marker(element, "Removed row")
|
|
201
|
+
class_string = "#{element['class']&.gsub('ins', '')} diff del".strip
|
|
202
|
+
|
|
203
|
+
<<~HTML
|
|
204
|
+
<#{element.name} class="#{class_string}">
|
|
205
|
+
#{element.inner_html}
|
|
206
|
+
</#{element.name}>
|
|
207
|
+
HTML
|
|
208
|
+
|
|
209
|
+
else
|
|
210
|
+
# Entirely removed steps OLs require that the span also have the .steps class
|
|
211
|
+
# This is either a bug or weakness in the actual govuk CSS for that class
|
|
212
|
+
class_string = element["class"].nil? ? nil : " class= \"#{element['class']}\""
|
|
213
|
+
span_class_string = "diff del"
|
|
214
|
+
|
|
215
|
+
<<~HTML
|
|
216
|
+
<#{element.name}#{class_string}>
|
|
217
|
+
<span class="#{span_class_string}">
|
|
218
|
+
<span class="visually-hidden">Removed content </span>
|
|
219
|
+
#{element.inner_html}
|
|
220
|
+
</span>
|
|
221
|
+
</#{element.name}>
|
|
222
|
+
HTML
|
|
223
|
+
end
|
|
196
224
|
end
|
|
197
225
|
end
|
|
198
226
|
end
|
|
@@ -6,5 +6,13 @@ module PublishingPlatformNokodiff
|
|
|
6
6
|
n["class"] = "diff-marker"
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
|
+
|
|
10
|
+
def insert_table_row_change_marker(element, message)
|
|
11
|
+
marker = Nokogiri::XML::Node.new("span", element.document)
|
|
12
|
+
marker.content = "#{message} "
|
|
13
|
+
marker["class"] = "visually-hidden"
|
|
14
|
+
|
|
15
|
+
element.children.first.prepend_child(marker)
|
|
16
|
+
end
|
|
9
17
|
end
|
|
10
18
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: publishing_platform_nokodiff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Publishing Platform
|
|
@@ -207,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
207
207
|
- !ruby/object:Gem::Version
|
|
208
208
|
version: '0'
|
|
209
209
|
requirements: []
|
|
210
|
-
rubygems_version: 4.0.
|
|
210
|
+
rubygems_version: 4.0.17
|
|
211
211
|
specification_version: 4
|
|
212
212
|
summary: A Ruby Gem to highlight additions, deletions and character level changes
|
|
213
213
|
while preserving original HTML
|