sitediff 0.0.6 → 1.2.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.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/.eslintignore +1 -0
  3. data/.eslintrc.json +28 -0
  4. data/.project +11 -0
  5. data/.rubocop.yml +179 -0
  6. data/.rubocop_todo.yml +51 -0
  7. data/CHANGELOG.md +28 -0
  8. data/Dockerfile +33 -0
  9. data/Gemfile +11 -0
  10. data/Gemfile.lock +85 -0
  11. data/INSTALLATION.md +146 -0
  12. data/LICENSE +339 -0
  13. data/README.md +810 -0
  14. data/Rakefile +12 -0
  15. data/Thorfile +135 -0
  16. data/bin/sitediff +9 -2
  17. data/config/.gitkeep +0 -0
  18. data/config/sanitize_domains.example.yaml +8 -0
  19. data/config/sitediff.example.yaml +81 -0
  20. data/docker-compose.test.yml +3 -0
  21. data/lib/sitediff/api.rb +276 -0
  22. data/lib/sitediff/cache.rb +57 -8
  23. data/lib/sitediff/cli.rb +156 -176
  24. data/lib/sitediff/config/creator.rb +61 -77
  25. data/lib/sitediff/config/preset.rb +75 -0
  26. data/lib/sitediff/config.rb +436 -31
  27. data/lib/sitediff/crawler.rb +27 -21
  28. data/lib/sitediff/diff.rb +32 -9
  29. data/lib/sitediff/fetch.rb +10 -3
  30. data/lib/sitediff/files/diff.html.erb +20 -2
  31. data/lib/sitediff/files/jquery.min.js +2 -0
  32. data/lib/sitediff/files/normalize.css +349 -0
  33. data/lib/sitediff/files/report.html.erb +171 -0
  34. data/lib/sitediff/files/sidebyside.html.erb +5 -2
  35. data/lib/sitediff/files/sitediff.css +303 -30
  36. data/lib/sitediff/files/sitediff.js +367 -0
  37. data/lib/sitediff/presets/drupal.yaml +63 -0
  38. data/lib/sitediff/report.rb +254 -0
  39. data/lib/sitediff/result.rb +50 -20
  40. data/lib/sitediff/sanitize/dom_transform.rb +47 -8
  41. data/lib/sitediff/sanitize/regexp.rb +24 -3
  42. data/lib/sitediff/sanitize.rb +81 -12
  43. data/lib/sitediff/uriwrapper.rb +65 -23
  44. data/lib/sitediff/webserver/resultserver.rb +30 -33
  45. data/lib/sitediff/webserver.rb +15 -3
  46. data/lib/sitediff.rb +130 -83
  47. data/misc/sitediff - overview report.png +0 -0
  48. data/misc/sitediff - page report.png +0 -0
  49. data/package-lock.json +878 -0
  50. data/package.json +25 -0
  51. data/sitediff.gemspec +51 -0
  52. metadata +91 -29
  53. data/lib/sitediff/files/html_report.html.erb +0 -66
  54. data/lib/sitediff/files/rules/drupal.yaml +0 -63
  55. data/lib/sitediff/rules.rb +0 -65
@@ -1,53 +1,326 @@
1
- .sitediff {
2
- font-family: monospace;
3
- font-size: 1.2em;
1
+ * {
2
+ box-sizing: border-box;
4
3
  }
5
- .sitediff .legend {
6
- width: 95%;
7
- margin: 1em auto;
4
+
5
+ a {
6
+ color: #6E73B7;
7
+ text-decoration: none;
8
+ }
9
+
10
+ body {
11
+ font-family: sans-serif;
12
+ font-size: 16px;
13
+ padding: 3em 0;
14
+ }
15
+
16
+ .container {
17
+ overflow: hidden;
18
+ margin: 0 auto;
19
+ padding-left: 1em;
20
+ padding-right: 1em;
21
+ max-width: 1280px;
22
+ }
23
+
24
+ #footer {
25
+ font-size: 90%;
26
+ margin-top: 2em;
8
27
  text-align: center;
9
28
  }
10
- .sitediff .results thead {
11
- background: black;
12
- color: white;
29
+
30
+ .site-info {
31
+ display: flex;
32
+ flex-direction: row;
33
+ align-items: center;
34
+ justify-content: space-between;
35
+ }
36
+
37
+ .site-info .site {
38
+ background-color: #F9F9FB;
39
+ flex-basis: 49%;
40
+ flex-grow: 0;
41
+ flex-shrink: 0;
42
+ padding: 1em;
43
+ }
44
+
45
+ .site__tag {
46
+ margin-top: 0;
47
+ margin-bottom: .5em;
48
+ }
49
+
50
+ /* Statistical info */
51
+ .statistical-info {
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: space-between;
55
+ flex-direction: row;
56
+ margin-bottom: 1em;
57
+ }
58
+
59
+ .statistical-info > div {
60
+ flex-basis: 49%;
61
+ }
62
+
63
+ .statistical-info h3 {
64
+ display: inline-block;
65
+ margin-right: 1em;
13
66
  }
14
- .sitediff .results td {
67
+
68
+ .statistical-info .count {
69
+ background-color: #eeeeee;
70
+ border-radius: 50%;
71
+ display: inline-block;
72
+ font-weight: bold;
73
+ line-height: 2em;
74
+ padding: .1em .25em 0;
75
+ height: 2em;
76
+ width: 2em;
15
77
  text-align: center;
16
78
  }
17
- .sitediff .results td.path {
79
+
80
+ /* Buttons */
81
+ .button {
82
+ background-color: #EAECF3;
83
+ cursor: pointer;
84
+ display: inline-block;
85
+ font-size: .9em;
86
+ padding: .25em .5em .3em;
87
+ text-decoration: none;
88
+ }
89
+
90
+ /**
91
+ * Forms.
92
+ */
93
+ label[for] {
94
+ cursor: pointer;
95
+ }
96
+
97
+ /* Checkboxes */
98
+ .form-checkboxes .form-checkbox {
99
+ display: inline-block;
100
+ margin-right: .5em;
101
+ }
102
+
103
+ .form-checkbox input {
104
+ height: 1em;
105
+ width: 1em;
106
+ }
107
+
108
+ /* Search */
109
+ .form-search label {
110
+ display: none;
111
+ }
112
+
113
+ .form-search input {
114
+ font-size: .9em;
115
+ padding-left: .5em;
116
+ padding-right: .5em;
117
+ width: 200px;
118
+ }
119
+
120
+ /* SiteDiff Toolbar */
121
+ .sitediff-toolbar {
122
+ margin-top: 1em;
123
+ margin-bottom: 1em;
124
+ overflow: hidden;
125
+ padding-top: 5px;
126
+ padding-bottom: 5px;
127
+ }
128
+
129
+ .sitediff-toolbar .form-item {
130
+ display: inline-block;
131
+ margin-right: 1em;
132
+ }
133
+
134
+ .sitediff-toolbar .form-item:last-child {
135
+ margin-right: 0;
136
+ }
137
+
138
+ .sitediff-toolbar .toolbar__left {
139
+ float: left;
140
+ }
141
+
142
+ .sitediff-toolbar .toolbar__right {
143
+ float: right;
144
+ }
145
+
146
+ /* Table */
147
+ table {
148
+ width: 100%;
149
+ }
150
+
151
+ table thead {
152
+ background: #F9F9FB;
153
+ color: #999999;
154
+ }
155
+
156
+ td,
157
+ th {
18
158
  text-align: left;
19
- padding-left: 1em;
159
+ padding: .4em .5em;
20
160
  }
21
- .sitediff .results {
22
- padding: 1em;
23
- width: 95%;
24
- margin: 1em auto;
25
- font-size: 1em;
161
+
162
+ th {
163
+ border-bottom: 2px solid #EAECF3;
164
+ padding-top: .5em;
165
+ padding-bottom: .5em;
26
166
  }
27
- .sitediff tr.error > td.status,
28
- .sitediff tr.error > td.path {
29
- background-color: khaki;
167
+
168
+ table tbody td {
169
+ border-bottom: 2px solid #F9F9FB;
30
170
  }
31
- .sitediff tr.failure > td.status,
32
- .sitediff tr.failure > td.path {
33
- background-color: salmon;
171
+
172
+ table tbody tr:hover {
173
+ background-color: #F9F9FB;
34
174
  }
35
- .sitediff .before-col,
36
- .sitediff .both-col,
37
- .sitediff .after-col,
38
- .sitediff .diff-stat-col {
39
- width: 10%;
175
+
176
+ table .path {
177
+ margin-right: 1em;
40
178
  }
41
- .sitediff .path-col {
42
- width: 55%;
179
+
180
+ table td.description .buttons {
181
+ float: right;
182
+ }
183
+
184
+ table td.description a {
185
+ display: none;
186
+ margin-left: .4em;
187
+ }
188
+
189
+ table td.description:hover a {
190
+ display: inline-block;
191
+ }
192
+
193
+ table td.description .button-diff {
194
+ display: block;
195
+ }
196
+
197
+ table .icon-col {
198
+ width: 25px;
43
199
  }
44
200
 
201
+ table .status-col {
202
+ width: 100px;
203
+ }
204
+
205
+ /* Icons */
206
+ .icon {
207
+ display: inline-block;
208
+ height: 1em;
209
+ width: 1em;
210
+ }
211
+
212
+ .icon-result-changed {
213
+ background-color: lightcoral;
214
+ border-radius: 50%;
215
+ }
216
+
217
+ .icon-result-unchanged {
218
+ background-color: lightgreen;
219
+ border-radius: 50%;
220
+ }
221
+
222
+ .icon-result-error {
223
+ background: khaki;
224
+ border-radius: 50%;
225
+ }
226
+
227
+ /* Side-by-side view */
45
228
  #sidebyside {
46
229
  margin: 0;
47
230
  }
231
+
48
232
  #sidebyside iframe {
49
233
  float: left;
50
234
  height: 100%;
51
235
  width: 50%;
52
236
  border: 0;
53
237
  }
238
+
239
+ /* Page: Diff */
240
+ #diff-navigator {
241
+ background-color: #fefefe;
242
+ box-shadow: 0 0 .5em rgba(0, 0, 0, .25);
243
+ padding: .25em;
244
+ margin-top: 0;
245
+ position: fixed;
246
+ top: 0;
247
+ width: 100%;
248
+ }
249
+
250
+ /*** Overlay */
251
+ .overlay {
252
+ background-color: rgba(0, 0, 0, 0.25);
253
+ height: 100vh;
254
+ left: 0;
255
+ padding: 2em;
256
+ position: fixed;
257
+ top: 0;
258
+ width: 100vw;
259
+ }
260
+
261
+ .overlay__inner {
262
+ background-color: #fff;
263
+ height: 100%;
264
+ margin: 0;
265
+ position: relative;
266
+ width: 100%;
267
+ }
268
+
269
+ .overlay header {
270
+ background-color: #f9f9fb;
271
+ border-bottom: 2px solid #eaecf3;
272
+ display: flex;
273
+ min-height: 3em;
274
+ left: 0;
275
+ position: absolute;
276
+ top: 0;
277
+ width: 100%;
278
+ }
279
+
280
+ .overlay header .prev,
281
+ .overlay header .next,
282
+ .overlay header .exit {
283
+ padding: 0.5em 0;
284
+ margin: 0.5em;
285
+ }
286
+
287
+ .overlay header .prev a,
288
+ .overlay header .next a,
289
+ .overlay header .exit a {
290
+ background-color: #eaecf3;
291
+ font-size: .9em;
292
+ padding: 0.5em;
293
+ text-decoration: none;
294
+ }
295
+
296
+ .overlay header .prev a:hover,
297
+ .overlay header .next a:hover,
298
+ .overlay header .exit a:hover {
299
+ background-color: #d2d7ef;
300
+ }
301
+
302
+ .overlay header .prev.disabled a,
303
+ .overlay header .next.disabled a,
304
+ .overlay header .exit.disabled a,
305
+ .overlay header .prev.disabled a:hover,
306
+ .overlay header .next.disabled a:hover,
307
+ .overlay header .exit.disabled a:hover {
308
+ background-color: eaecf3;
309
+ color: #9b9db8;
310
+ cursor: not-allowed;
311
+ }
312
+ .overlay header .path {
313
+ flex-grow: 1;
314
+ padding: 1em;
315
+ }
316
+
317
+ .overlay article {
318
+ background-color: #fff;
319
+ height: 100%;
320
+ width: 100%;
321
+ }
322
+
323
+ .overlay article iframe {
324
+ height: 100%;
325
+ width: 100%;
326
+ }
@@ -0,0 +1,367 @@
1
+ /**
2
+ * @file
3
+ * SiteDiff report behaviors.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ /* global $ */
9
+ /**
10
+ * SiteDiff namespace.
11
+ */
12
+ var SiteDiff = SiteDiff || {};
13
+
14
+ /**
15
+ * SiteDiff global map of diffs.
16
+ */
17
+ SiteDiff.diffs = SiteDiff.diffs || {};
18
+
19
+ SiteDiff.currentDiff = -1;
20
+
21
+ /**
22
+ * Scrolls the document to the said position.
23
+ *
24
+ * @param options
25
+ * Object specifying various options.
26
+ *
27
+ * x: X position.
28
+ * y: Y position.
29
+ * animate: Whether to animate.
30
+ * callback: A function to call after scrolling.
31
+ */
32
+ SiteDiff.scrollToPosition = function (options) {
33
+ // Compute vertical and horizontal adjustments, if any.
34
+ // Example: Fixed elements, etc.
35
+ var xFix = 0;
36
+ var yFix = 0 - 100;
37
+
38
+ // Determine final x and y offsets.
39
+ var x = parseInt(options.x) + xFix;
40
+ x = Math.max(x, 0);
41
+ var y = parseInt(options.y) + yFix;
42
+ y = Math.max(y, 0);
43
+
44
+ // Perform the scroll with or without animation.
45
+ window.scrollTo(x, y);
46
+
47
+ // Trigger a callback, if any.
48
+ if (options.callback) {
49
+ options.callback();
50
+ }
51
+ };
52
+
53
+ /**
54
+ * Scrolls to a DOM element on the page.
55
+ *
56
+ * @param el
57
+ * The DOM element.
58
+ *
59
+ * @param options
60
+ * Object specifying various options.
61
+ *
62
+ * "callback" to trigger after scrolling.
63
+ */
64
+ SiteDiff.scrollToElement = function (el, options) {
65
+ options = options || {};
66
+ var callback = options.callback || function () {};
67
+
68
+ // See if the element exists.
69
+ var $el = $(el).first();
70
+ if ($el.length == 1) {
71
+ // Inject callback to focus on the element we scroll to.
72
+ options.x = 0;
73
+ options.y = $el.offset().top;
74
+ options.callback = function () {
75
+ $el.focus();
76
+ callback.call(el);
77
+ };
78
+ SiteDiff.scrollToPosition(options);
79
+ }
80
+ };
81
+
82
+ /**
83
+ * Initialize behaviors.
84
+ */
85
+ SiteDiff.init = function () {
86
+ // On the overview page.
87
+ switch ($(document.body).data('page')) {
88
+ case 'overview':
89
+ SiteDiff.initFilterForm();
90
+ break;
91
+
92
+ case 'diff':
93
+ SiteDiff.jumpToFirstDiff();
94
+ break;
95
+ }
96
+ };
97
+
98
+ /**
99
+ * Initializes report filters and overlay.
100
+ */
101
+ SiteDiff.initFilterForm = function () {
102
+ SiteDiff.initDiffArray();
103
+ SiteDiff.initStatusFilter();
104
+ SiteDiff.initSearchFilter();
105
+ SiteDiff.initOverlay();
106
+ SiteDiff.initClickHandlers();
107
+ };
108
+
109
+ /**
110
+ * Initialize global diff array
111
+ *
112
+ */
113
+ SiteDiff.initDiffArray = function() {
114
+ SiteDiff.diffs = $('.button-diff').map(function (i, element) {
115
+ var $el = $(element);
116
+ $el.data('diffindex', i);
117
+ return {
118
+ diff: $el.attr('href'),
119
+ element: $el,
120
+ index: i,
121
+ path: $el.parents('.description').find('.path').text()
122
+ };
123
+ });
124
+ };
125
+
126
+ /**
127
+ * Initializes the "status" filter.
128
+ */
129
+ SiteDiff.initStatusFilter = function () {
130
+ $('.form-item--status input')
131
+ .on('change', function () {
132
+ // Get a list of applied filters.
133
+ var appliedFilters = $('.form-item--status input:checked')
134
+ .map(function () {
135
+ return this.getAttribute('value');
136
+ // applied.push(this.getAttribute('value'));
137
+ });
138
+ // Show only matching results, hide the rest.
139
+ $('#sitediff-report')
140
+ .find('.sitediff-result')
141
+ .each(function () {
142
+ var $row = $(this);
143
+ var status = $row.data('status');
144
+ if (
145
+ // Row matches applied filters.
146
+ $.inArray(status, appliedFilters) > -1 ||
147
+ // No filters are applied.
148
+ appliedFilters.length === 0
149
+ ) {
150
+ $row.removeAttr('hidden');
151
+ }
152
+ else {
153
+ $row.attr('hidden', 'hidden');
154
+ }
155
+ });
156
+ });
157
+ };
158
+
159
+ /**
160
+ * Initializes the "search" filter.
161
+ */
162
+ SiteDiff.initSearchFilter = function () {
163
+ $('#input-search')
164
+ .on('change keyup', function () {
165
+ var keyword = $(this).val().toLowerCase();
166
+
167
+ // Filter the records.
168
+ // TODO: Trigger one event per 250ms.
169
+ $('#sitediff-report')
170
+ .find('.sitediff-result')
171
+ .each(function () {
172
+ var $row = $(this);
173
+ var path = $row.find('.path').text();
174
+
175
+ // If keyword matches, keep the row visible.
176
+ if (path.toLowerCase().indexOf(keyword) > -1) {
177
+ $row.attr('hidden', null);
178
+ }
179
+ else {
180
+ $row.attr('hidden', 'hidden');
181
+ }
182
+ });
183
+ });
184
+ };
185
+
186
+ /**
187
+ * Set up the diff overlay to be displayed.
188
+ */
189
+ SiteDiff.initOverlay = function () {
190
+ if (SiteDiff.diffs.length <= 0) return;
191
+
192
+ // add overlay
193
+ $('body').append($(
194
+ '<div class="overlay" style="display: none;"><div class="overlay__inner"><header>' +
195
+ '<div class="path"></div>' +
196
+ '<div class="prev"><a href="#" title="Previous diff (left arrow)">< Prev</a></div>' +
197
+ '<div class="next"><a href="#" title="Next diff (right arrow)">Next ></a></div>' +
198
+ '<div class="exit"><a href="#" title="Close diff display (Esc)">Close</a></div>' +
199
+ '</header><article></article></div></div>'));
200
+ // add header click handlers
201
+ $('.overlay header .exit').click(function (event) {
202
+ event.preventDefault();
203
+ SiteDiff.destroyOverlay();
204
+ });
205
+ $('.overlay header .prev').click(function (event) {
206
+ event.preventDefault();
207
+ SiteDiff.prevDiff();
208
+ });
209
+ $('.overlay header .next').click(function (event) {
210
+ event.preventDefault();
211
+ SiteDiff.nextDiff();
212
+ });
213
+
214
+ };
215
+
216
+ /**
217
+ * Set up click handlers for all diff buttons
218
+ */
219
+ SiteDiff.initClickHandlers = function () {
220
+ SiteDiff.diffs.each( function (i, diff) {
221
+ diff.element.click({index: i}, function (event) {
222
+ event.preventDefault();
223
+ SiteDiff.openOverlay(event.data.index);
224
+ });
225
+ });
226
+ };
227
+
228
+ /**
229
+ * Set up key handlers for overlay.
230
+ */
231
+ SiteDiff.initKeyHandlers = function () {
232
+ $(document).keyup(function (event) {
233
+ switch (event.which) {
234
+ case 37:
235
+ SiteDiff.prevDiff();
236
+ break;
237
+ case 39:
238
+ SiteDiff.nextDiff();
239
+ break;
240
+ case 27:
241
+ SiteDiff.destroyOverlay();
242
+ break;
243
+ }
244
+ });
245
+ };
246
+
247
+ /**
248
+ * Remove overlay key handlers.
249
+ */
250
+ SiteDiff.removeKeyHandlers = function () {
251
+ $(document).off('keyup');
252
+ };
253
+
254
+ /**
255
+ * Open overlay for the diff identified by the `index`.
256
+ *
257
+ * @param integer index
258
+ * The index of the diff to be viewed.
259
+ */
260
+ SiteDiff.openOverlay = function (index) {
261
+ SiteDiff.currentDiff = index;
262
+ SiteDiff.showDiff();
263
+ SiteDiff.initKeyHandlers();
264
+ $('.overlay').fadeIn(300);
265
+ };
266
+
267
+ /**
268
+ * Create the iframe to display the current diff.
269
+ */
270
+ SiteDiff.showDiff = function () {
271
+ var diff = SiteDiff.diffs[SiteDiff.currentDiff];
272
+ var iframe = '<iframe src="' + diff.diff + '"></iframe>';
273
+ $('.overlay header .path').text(diff.path);
274
+ SiteDiff.setPrevNext();
275
+ $('.overlay article').html(iframe);
276
+ };
277
+
278
+ /**
279
+ * Hide the overlay and clean up.
280
+ */
281
+ SiteDiff.destroyOverlay = function () {
282
+ $('.overlay article').empty();
283
+ SiteDiff.removeKeyHandlers();
284
+ $('.overlay').fadeOut(300, SiteDiff.scrollToButton);
285
+ };
286
+
287
+ /**
288
+ * Display the previous diff.
289
+ */
290
+ SiteDiff.prevDiff = function () {
291
+ if (SiteDiff.currentDiff > 0) {
292
+ SiteDiff.currentDiff--;
293
+ SiteDiff.showDiff();
294
+ }
295
+ };
296
+
297
+ /**
298
+ * Display the next diff.
299
+ */
300
+ SiteDiff.nextDiff = function () {
301
+ if (SiteDiff.currentDiff < SiteDiff.diffs.length - 1) {
302
+ SiteDiff.currentDiff++;
303
+ SiteDiff.showDiff();
304
+ }
305
+ };
306
+
307
+ /**
308
+ * Enable or disable prev and next buttons based on current diff.
309
+ */
310
+ SiteDiff.setPrevNext = function () {
311
+ if (SiteDiff.currentDiff <= 0) {
312
+ // set prev disabled
313
+ $('.overlay header .prev').addClass('disabled');
314
+ }
315
+ else {
316
+ $('.overlay header .prev.disabled').removeClass('disabled');
317
+ }
318
+ if (SiteDiff.currentDiff >= SiteDiff.diffs.length - 1) {
319
+ // set next disabled
320
+ $('.overlay header .next').addClass('disabled');
321
+ }
322
+ else {
323
+ $('.overlay header .next.disabled').removeClass('disabled');
324
+ }
325
+ };
326
+
327
+ /**
328
+ * Scroll to the button associated with the current diff.
329
+ */
330
+ SiteDiff.scrollToButton = function () {
331
+ var $diffButton = SiteDiff.diffs[SiteDiff.currentDiff].element;
332
+ if (! SiteDiff.isElementVisible($diffButton)) {
333
+ SiteDiff.scrollToElement($diffButton);
334
+ }
335
+ };
336
+
337
+ /**
338
+ * Check if an element is at least partly visible.
339
+ * @param element
340
+ */
341
+ SiteDiff.isElementVisible = function (element) {
342
+ var topVisible = $(window).scrollTop();
343
+ var bottomVisible = topVisible + $(window).height();
344
+ var elemTop = $(element).offset().top;
345
+ var elemBottom = elemTop + $(element).height();
346
+ return ((elemBottom <= bottomVisible) && (elemTop >= topVisible));
347
+ };
348
+
349
+ /**
350
+ * Jumps to the first diff on the page.
351
+ */
352
+ SiteDiff.jumpToFirstDiff = function () {
353
+ // Get the first diff hunk.
354
+ var $diff = $('#diff-container')
355
+ .find('.del, .ins')
356
+ .first();
357
+ if ($diff.length === 0) {
358
+ return;
359
+ }
360
+
361
+ // Scroll the window to it!
362
+ setTimeout(function () {
363
+ SiteDiff.scrollToElement($diff[0]);
364
+ }, 250);
365
+ };
366
+
367
+ $(document).ready(SiteDiff.init);