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: d357388beb5eca70d4748d1be5d5f6770f6649b082d7e8f69c8d40cd54fcc2f5
4
- data.tar.gz: 06a02e5536cf3e89770cbf0af3f53aa02c237d7be22c8c71fcb2712298d1972f
3
+ metadata.gz: 8a5784bf33e2775f851b7fcd645014324988ae99371d934bddca53493c25d938
4
+ data.tar.gz: 65dbd9c81fb530d5719b7ff86878e3624946fb45cadf9f978a912429ea0ba6cf
5
5
  SHA512:
6
- metadata.gz: 37a30a18ccf3556534c151d7edbf96356fae333eef308813494d7b7f09d50c5a6789ec9f6835410432d8a0496d2c5e384ca2cceb0123bc82669cd45330376540
7
- data.tar.gz: 535c5c098ec325475089771e77d4ed17443a73ae3685800d185816b7bd803e383c004d0719dd38cb2565c13b6ff96e3951c1629fe4cbce83c6eac12ff42a5cf1
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: 40px solid $light-grey;
14
- padding: 15px;
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
- position: static !important;
55
+ &.del, &.ins {
56
+ &:is(li){ display: list-item; }
57
+
58
+ position: relative;
22
59
  display: block;
23
60
  text-decoration: none;
24
- border-radius: 3px;
61
+ background-color: transparent;
25
62
  }
26
63
 
27
- del {
28
- background-color: $removed-color;
29
- padding-bottom: 2px;
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
- del::before,
50
- ins::before {
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
- left: -40px;
53
- width: 40px;
54
- height: 1.5em;
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
- del::before {
62
- color: $text-colour;
63
- background-color: $diff-marker-removed-color;
64
- content: "−";
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
- ins::before {
68
- color: $text-colour;
69
- background-color: $diff-marker-added-color;
70
- content: "+";
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].name == "li" ? deleted_li(diff[:before]) : deleted_block(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].name == "li" ? added_li(diff[:after]) : added_block(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) && should_not_be_treated_as_single_change?(before_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
- before_diff, after_diff = if both_text_nodes?(before_node, after_node)
68
- diff_raw_text(before_node, after_node)
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 changes within a paragraph, heading, or list item to be treated as a single change, even if they are
92
- # structurally different, to avoid overwhelming the user with changes, and ensure any nested elements are included
93
- # within the diff, rather than being treated as added or removed content on their own.
94
- def should_not_be_treated_as_single_change?(before_node)
95
- before_node.name != "p" &&
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
- before_dup = before_html.dup
122
- after_dup = after_html.dup
117
+ before_fragment = before_html.dup
118
+ after_fragment = after_html.dup
123
119
 
124
- before_fragment, after_fragment = PublishingPlatformNokodiff::TextNodeDiffs.new(before_dup, after_dup).call
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
- if before_html.name == "li"
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 deleted_li(html)
163
- %(
164
- <li>
165
- <div class="diff">
166
- <del aria-label="removed content">#{html}</del>
167
- </div>
168
- </li>
169
- )
170
- end
171
-
172
- def deleted_block(html)
173
- %(
174
- <div class="diff">
175
- <del aria-label="removed content">#{html}</del>
176
- </div>
177
- )
178
- end
179
-
180
- def added_block(html)
181
- %(
182
- <div class="diff">
183
- <ins aria-label="added content">#{html}</ins>
184
- </div>
185
- )
186
- end
187
-
188
- def added_li(html)
189
- %(
190
- <li>
191
- <div class="diff">
192
- <ins aria-label="added content">#{html}</ins>
193
- </div>
194
- </li>
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PublishingPlatformNokodiff
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  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.2.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.15
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