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 / general.js

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

220 lines 7.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 var abj404_whichButtonClicked = null;
2
3 jQuery(document).ready(function($) {
4 var adminOptionsPage = document.getElementById("admin-options-page");
5 if (adminOptionsPage) {
6 adminOptionsPage.addEventListener('submit', submitOptions);
7 }
8
9 var deleteDebugFileButton = document.querySelector('#deleteDebugFile');
10 if (deleteDebugFileButton) {
11 deleteDebugFileButton.addEventListener('click', function(e) {
12 abj404_whichButtonClicked = 'deleteDebugFile';
13 submitOptions(e);
14 });
15 }
16 })
17
18 function striphtml(html) {
19 // A regex strip (not a real HTML parser) so this can never load a
20 // resource or run an event-handler attribute (e.g. <img onerror=...>)
21 // that an HTML parser boundary would otherwise create, even on an
22 // element never attached to the document. The only caller uses this
23 // purely to make a JSON.stringify()'d payload readable in a console.log.
24 if (typeof html !== 'string') {
25 return '';
26 }
27 return html.replace(/<[^>]*>/g, '');
28 }
29
30 function submitOptions(e) {
31 e.preventDefault();
32
33 // Show loading overlay
34 showSaveOverlay();
35
36 // gather form data.
37 var form = document.getElementById("admin-options-page");
38 var formElements = form.elements;
39 var formData = {};
40 for (var i = 0; i < formElements.length; i++) {
41 var field = formElements[i];
42 var currentValue = field.value;
43 if (field.type == 'checkbox') {
44 currentValue = field.checked ? 1 : 0;
45 }
46
47 if (!(field.name in formData)) {
48 formData[field.name] = currentValue;
49 } else {
50 if (!Array.isArray(formData[field.name])) {
51 formData[field.name] = new Array(formData[field.name]);
52 }
53 formData[field.name].push(currentValue);
54 }
55 }
56
57 // if we should just delete the log file.
58 if (abj404_whichButtonClicked == 'deleteDebugFile') {
59 // set the action to 'updateOptions' and set deleteDebugFile to true
60 formData['action'] = 'updateOptions';
61 formData['deleteDebugFile'] = true;
62 } else {
63 formData['deleteDebugFile'] = false;
64 }
65
66 // fix checkboxes.
67 var formDataAsJson = JSON.stringify(formData);
68 var encodedData = encodeURI(formDataAsJson);
69
70 // save / send the data via an ajax request.
71 var saveOptionsURL = form.getAttribute('data-url')
72
73 jQuery.ajax({
74 url: saveOptionsURL,
75 type: 'POST',
76 // Was a bare setTimeout that showed the error but left the request
77 // running, so a late success could still submit the redirect form
78 // under an error overlay. jQuery's own deadline aborts the request and
79 // routes it to the error handler below, which is the one place the
80 // overlay is cleared.
81 timeout: 30000,
82 data: {
83 'encodedData': encodedData
84 },
85 dataType :'json',
86 success: function (data) {
87 // Support both legacy payloads ({ newURL, message, error }) and WP-shaped responses
88 // ({ success: true|false, data: { ... } }).
89 var payload = data;
90 if (data && typeof data === 'object' && typeof data.success === 'boolean' && data.data !== undefined) {
91 if (data.success === false) {
92 var serverMsg = '';
93 if (typeof data.data === 'string') {
94 serverMsg = data.data;
95 } else if (data.data && data.data.message) {
96 serverMsg = data.data.message;
97 }
98 showSaveError(serverMsg || "Error saving settings. Please try again.");
99 return;
100 }
101 payload = data.data;
102 }
103
104 // Safety: if we don't have the redirect URL, treat as failure.
105 if (!payload || payload['newURL'] === undefined) {
106 showSaveError("Error saving settings. Please try again.");
107 return;
108 }
109
110 var message = striphtml(JSON.stringify(payload, null, 2));
111 console.log("saved options: " + message);
112
113 // redirect and post a message (overlay will disappear on page reload)
114 // Use DOM methods to avoid XSS from unescaped payload values in HTML attributes.
115 var formEl = document.createElement('form');
116 formEl.method = 'post';
117 formEl.action = payload['newURL'];
118 formEl.style.display = 'none';
119 var inputEl = document.createElement('input');
120 inputEl.type = 'text';
121 inputEl.name = 'display-this-message';
122 inputEl.value = payload['message'];
123 formEl.appendChild(inputEl);
124 document.body.appendChild(formEl);
125 formEl.submit();
126 },
127 error: function (request, textStatus, errorThrown) {
128 // A deadline is the one failure the admin can act on directly, so
129 // it keeps its own wording instead of the generic save message.
130 if (textStatus === 'timeout') {
131 showSaveError('Request timed out. Please check your connection and try again.');
132 return;
133 }
134 // Shared describe-and-record seam (abj404-admin-ajax.js), guarded
135 // so a missing asset degrades to a generic message instead of
136 // throwing out of the error handler and leaving the overlay stuck.
137 var errMsg = "Error saving settings. Please try again.";
138 if (typeof abj404AdminAjaxErrorMessage === 'function') {
139 errMsg = abj404AdminAjaxErrorMessage(request, {
140 fallback: errMsg,
141 source: 'options-save',
142 errorThrown: errorThrown
143 });
144 }
145
146 showSaveError(errMsg);
147 }
148 });
149
150 // don't submit the form.
151 return false;
152 }
153
154 function showSaveOverlay() {
155 var overlay = document.getElementById('abj404-save-overlay');
156 if (overlay) {
157 overlay.style.display = 'flex';
158 overlay.classList.remove('error');
159
160 // Update message to default
161 var message = overlay.querySelector('.abj404-save-message');
162 if (message) {
163 message.textContent = abj404General.savingSettings;
164 }
165
166 // Announce to screen readers
167 abj404AnnounceToScreenReader(abj404General.savingSettings);
168 }
169 }
170
171 /**
172 * Announce a message to screen readers using a live region
173 * @param {string} message The message to announce
174 * @param {string} priority 'polite' or 'assertive' (default: 'polite')
175 */
176 function abj404AnnounceToScreenReader(message, priority) {
177 priority = priority || 'polite';
178
179 // Create or get the live region
180 var liveRegion = document.getElementById('abj404-live-region');
181 if (!liveRegion) {
182 liveRegion = document.createElement('div');
183 liveRegion.id = 'abj404-live-region';
184 liveRegion.className = 'abj404-live-region';
185 liveRegion.setAttribute('aria-live', priority);
186 liveRegion.setAttribute('aria-atomic', 'true');
187 liveRegion.setAttribute('role', 'status');
188 document.body.appendChild(liveRegion);
189 }
190
191 // Update priority if needed
192 liveRegion.setAttribute('aria-live', priority);
193
194 // Clear and set the message (clearing first ensures re-announcement)
195 liveRegion.textContent = '';
196 setTimeout(function() {
197 liveRegion.textContent = message;
198 }, 100);
199 }
200
201 function showSaveError(errorMessage) {
202 var overlay = document.getElementById('abj404-save-overlay');
203 if (overlay) {
204 overlay.classList.add('error');
205
206 var message = overlay.querySelector('.abj404-save-message');
207 if (message) {
208 message.textContent = errorMessage;
209 }
210
211 // Announce error to screen readers with assertive priority
212 abj404AnnounceToScreenReader(errorMessage, 'assertive');
213
214 // Hide overlay after 5 seconds
215 setTimeout(function() {
216 overlay.style.display = 'none';
217 }, 5000);
218 }
219 }
220