PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / js / diagnosticDataCard.js

diagnosticDataCard.js in 404 Solution trunk, at includes/js/diagnosticDataCard.js

286 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Browser behavior for the Options-tab "Your Diagnostic Data" card.
3 *
4 * Owns the two user-facing one-shot actions on the card:
5 * - Download: POSTs to WordPress admin-ajax, then creates a local Blob
6 * download from the returned JSON. No server-generated URL is used.
7 * - Delete: opens the Restore-Defaults-style modal, POSTs confirm=1, and
8 * surfaces success/failure with inline native notices.
9 */
10
11 (function (window, document, $) {
12 'use strict';
13
14 var SELECTOR = '.abj404-diagnostic-data-card';
15
16 function ajaxUrl() {
17 if (typeof window.ajaxurl === 'string' && window.ajaxurl) {
18 return window.ajaxurl;
19 }
20 if (window.ABJ404 && window.ABJ404.ajaxurl) {
21 return String(window.ABJ404.ajaxurl);
22 }
23 return '/wp-admin/admin-ajax.php';
24 }
25
26 function text(key) {
27 var strings = {
28 download: 'Download my data',
29 preparing: 'Preparing...',
30 deleteConfirm: 'Yes, delete permanently',
31 deleting: 'Deleting...',
32 tryAgain: 'Try again',
33 downloaded: 'Downloaded {count} records to your browser.',
34 deleted: 'Deleted {count} stored reports for this site.',
35 unexpected: 'Unexpected diagnostics response. Try again shortly.',
36 genericFailure: "Couldn't reach the diagnostics server. Try again shortly.",
37 nothingStored: 'Nothing currently stored.'
38 };
39 return strings[key] || key;
40 }
41
42 function countRows(data) {
43 if (!data || !Array.isArray(data.rows)) {
44 return null;
45 }
46 return data.rows.length;
47 }
48
49 function deleteCount(data) {
50 if (!data || typeof data.deleted !== 'number' || !isFinite(data.deleted)) {
51 return null;
52 }
53 return data.deleted;
54 }
55
56 function clearNotice(container) {
57 if (container) {
58 container.innerHTML = '';
59 }
60 }
61
62 function showNotice(container, type, message) {
63 if (!container) {
64 return;
65 }
66 container.innerHTML = '';
67 var notice = document.createElement('div');
68 notice.className = 'notice notice-' + type;
69 notice.setAttribute('role', type === 'success' ? 'status' : 'alert');
70 var paragraph = document.createElement('p');
71 paragraph.textContent = message;
72 notice.appendChild(paragraph);
73 container.appendChild(notice);
74 }
75
76 function responseMessage(jqXHR) {
77 var body = jqXHR && jqXHR.responseJSON ? jqXHR.responseJSON : null;
78 if (body && body.data && typeof body.data.message === 'string') {
79 return body.data.message;
80 }
81 if (body && typeof body.message === 'string') {
82 return body.message;
83 }
84 return text('genericFailure');
85 }
86
87 function setButton(button, disabled, label) {
88 if (!button) {
89 return;
90 }
91 button.disabled = !!disabled;
92 if (label) {
93 button.textContent = label;
94 }
95 }
96
97 function createDownload(data, downloadDate) {
98 var filenameDate = downloadDate || 'today';
99 var blob = new window.Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
100 var url = window.URL.createObjectURL(blob);
101 var anchor = document.createElement('a');
102 anchor.href = url;
103 anchor.download = '404-solution-diagnostic-data-' + filenameDate + '.json';
104 document.body.appendChild(anchor);
105 anchor.click();
106 if (anchor.parentNode) {
107 anchor.parentNode.removeChild(anchor);
108 }
109 window.URL.revokeObjectURL(url);
110 }
111
112 function mount(card) {
113 if (!card || card.getAttribute('data-abj404-diagnostic-mounted') === '1') {
114 return;
115 }
116 card.setAttribute('data-abj404-diagnostic-mounted', '1');
117
118 var downloadButton = card.querySelector('.abj404-diagnostic-data-download');
119 var deleteButton = card.querySelector('.abj404-diagnostic-data-delete');
120 var noticeContainer = card.querySelector('.abj404-diagnostic-data-notice');
121 var modal = card.querySelector('.abj404-diagnostic-data-modal');
122 var modalNotice = card.querySelector('.abj404-diagnostic-data-modal-notice');
123 var cancelButtons = Array.prototype.slice.call(card.querySelectorAll('.abj404-diagnostic-data-cancel'));
124 var confirmButton = card.querySelector('.abj404-diagnostic-data-confirm');
125 var emptyNote = card.querySelector('.abj404-diagnostic-data-empty-note');
126 var lastFocus = null;
127
128 if (downloadButton) {
129 downloadButton.addEventListener('click', function () {
130 clearNotice(noticeContainer);
131 setButton(downloadButton, true, text('preparing'));
132 window.abj404AdminAjax({
133 url: ajaxUrl(),
134 type: 'POST',
135 data: {
136 action: 'abj404_privacy_export',
137 nonce: downloadButton.getAttribute('data-nonce') || card.getAttribute('data-export-nonce') || ''
138 },
139 success: function (response) {
140 var data = response && response.success === true ? response.data : null;
141 var rowCount = countRows(data);
142 if (rowCount === null) {
143 showNotice(noticeContainer, 'error', text('unexpected'));
144 return;
145 }
146 createDownload(data, card.getAttribute('data-download-date') || '');
147 showNotice(noticeContainer, 'success', text('downloaded').replace('{count}', String(rowCount)));
148 },
149 error: function (jqXHR) {
150 showNotice(noticeContainer, 'error', responseMessage(jqXHR));
151 },
152 complete: function () {
153 setButton(downloadButton, false, text('download'));
154 }
155 });
156 });
157 }
158
159 function openModal() {
160 if (!modal) {
161 return;
162 }
163 lastFocus = document.activeElement;
164 clearNotice(modalNotice);
165 modal.hidden = false;
166 modal.classList.add('active');
167 if (cancelButtons[0]) {
168 cancelButtons[0].focus();
169 }
170 }
171
172 function closeModal() {
173 if (!modal) {
174 return;
175 }
176 modal.hidden = true;
177 modal.classList.remove('active');
178 setModalLoading(false, text('deleteConfirm'));
179 if (lastFocus && typeof lastFocus.focus === 'function') {
180 lastFocus.focus();
181 }
182 }
183
184 function setModalLoading(disabled, confirmLabel) {
185 cancelButtons.forEach(function (button) {
186 button.disabled = !!disabled;
187 });
188 if (confirmButton) {
189 confirmButton.disabled = !!disabled;
190 confirmButton.textContent = confirmLabel;
191 if (disabled) {
192 confirmButton.classList.add('loading');
193 } else {
194 confirmButton.classList.remove('loading');
195 }
196 }
197 }
198
199 function markDeleted(count) {
200 closeModal();
201 showNotice(noticeContainer, 'success', text('deleted').replace('{count}', String(count)));
202 setButton(downloadButton, true, text('download'));
203 setButton(deleteButton, true, 'Delete my data');
204 if (emptyNote) {
205 emptyNote.hidden = false;
206 emptyNote.textContent = text('nothingStored');
207 }
208 }
209
210 if (deleteButton) {
211 deleteButton.addEventListener('click', function () {
212 openModal();
213 });
214 }
215
216 cancelButtons.forEach(function (button) {
217 button.addEventListener('click', function () {
218 closeModal();
219 });
220 });
221
222 if (modal) {
223 modal.addEventListener('click', function (event) {
224 if (event.target === modal) {
225 closeModal();
226 }
227 });
228 // Scoped to the modal element itself (not document): the listener's
229 // lifetime is tied to the modal node's own lifetime, so if the card's
230 // DOM node is ever discarded and replaced (admin page re-render / AJAX
231 // content swap), the old listener is discarded along with it instead
232 // of leaking on document forever.
233 modal.addEventListener('keydown', function (event) {
234 if (event.key === 'Escape' && !modal.hidden) {
235 closeModal();
236 }
237 });
238 }
239
240 if (confirmButton) {
241 confirmButton.addEventListener('click', function () {
242 clearNotice(modalNotice);
243 setModalLoading(true, text('deleting'));
244 window.abj404AdminAjax({
245 url: ajaxUrl(),
246 type: 'POST',
247 data: {
248 action: 'abj404_privacy_delete',
249 nonce: deleteButton ? (deleteButton.getAttribute('data-nonce') || card.getAttribute('data-delete-nonce') || '') : '',
250 confirm: '1'
251 },
252 success: function (response) {
253 var data = response && response.success === true ? response.data : null;
254 var count = deleteCount(data);
255 if (count === null) {
256 showNotice(modalNotice, 'error', text('unexpected'));
257 setModalLoading(false, text('tryAgain'));
258 return;
259 }
260 markDeleted(count);
261 },
262 error: function (jqXHR) {
263 showNotice(modalNotice, 'error', responseMessage(jqXHR));
264 setModalLoading(false, text('tryAgain'));
265 }
266 });
267 });
268 }
269 }
270
271 function mountAll() {
272 Array.prototype.forEach.call(document.querySelectorAll(SELECTOR), mount);
273 }
274
275 window.ABJ404 = window.ABJ404 || {};
276 window.ABJ404.DiagnosticDataCard = {
277 mount: mount,
278 mountAll: mountAll
279 };
280
281 if ($ && typeof $ === 'function') {
282 $(mountAll);
283 }
284
285 })(window, document, window.jQuery);
286