PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.2
WebberZone Top 10 — Popular Posts v4.5.2
4.5.2 4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 All 118 releases
top-10 / includes / admin / settings / js / settings-admin-scripts.js

settings-admin-scripts.js in WebberZone Top 10 — Popular Posts 4.5.2, at includes/admin/settings/js/settings-admin-scripts.js

467 lines 14.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 jQuery(document).ready(function ($) {
2
3 const prefix = WebberSettingsAdmin.prefix || 'wz';
4
5 // File browser.
6 $('.file-browser').on('click', function (event) {
7 event.preventDefault();
8
9 var self = $(this);
10
11 // Create the media frame.
12 var file_frame = wp.media.frames.file_frame = wp.media({
13 title: self.data('uploader_title'),
14 button: {
15 text: self.data('uploader_button_text'),
16 },
17 multiple: false
18 });
19
20 file_frame.on('select', function () {
21 attachment = file_frame.state().get('selection').first().toJSON();
22 self.prev('.file-url').val(attachment.url).change();
23 });
24
25 // Finally, open the modal
26 file_frame.open();
27 });
28
29 $(function () {
30 $("#post-body-content").tabs({
31 create: function (event, ui) {
32 $(ui.tab.find("a")).addClass("nav-tab-active");
33 },
34 activate: function (event, ui) {
35 $(ui.oldTab.find("a")).removeClass("nav-tab-active");
36 $(ui.newTab.find("a")).addClass("nav-tab-active");
37 window.scrollTo({ top: 0, behavior: 'smooth' });
38 }
39 });
40 });
41
42 // Settings search: live-filters setting rows across all tabs.
43 (function () {
44 var $container = $('#post-body-content');
45 var $searchInput = $('#' + prefix + '-settings-search');
46 var $form = $('#' + prefix + '-settings-form');
47
48 if (!$searchInput.length || !$form.length || !$container.length) {
49 return;
50 }
51
52 var $panels = $form.children('div[id]');
53 var $navLinks = $container.find('> ul.nav-tab-wrapper a.nav-tab');
54 var $clearButton = $container.find('.wz-settings-search-clear');
55 var $status = $container.find('.wz-settings-search-status');
56 var strings = (typeof WebberSettingsAdmin !== 'undefined' && WebberSettingsAdmin.strings) || {};
57 var $noResults = null;
58 var $actionsBar = null;
59 var indexBuilt = false;
60 var debounceTimer = null;
61
62 function getRows($panel) {
63 return $panel.children('table.form-table').find('> tbody > tr, > tr');
64 }
65
66 // Build a searchable text string per row once, on first search.
67 // Strips noise (select options, dropdown widgets, code editors) and adds input ids/names.
68 function buildIndex() {
69 $panels.each(function () {
70 getRows($(this)).each(function () {
71 var $row = $(this);
72 var $clone = $row.clone();
73 $clone.find('option, script, style, template, .ts-dropdown, .CodeMirror').remove();
74 var text = $clone.text() + ' ';
75 $row.find(':input[id], :input[name]').each(function () {
76 text += ' ' + (this.id || '') + ' ' + (this.name || '');
77 });
78 $row.data('wzSearchText', text.toLowerCase());
79 });
80 });
81 indexBuilt = true;
82 }
83
84 function clearHighlights() {
85 $form.find('mark.wz-search-mark').each(function () {
86 var parent = this.parentNode;
87 $(this).replaceWith(document.createTextNode($(this).text()));
88 parent.normalize();
89 });
90 }
91
92 // Elements the highlighter must never descend into.
93 var highlightSkip = 'input, textarea, select, button, script, style, template, mark, .CodeMirror, .ts-wrapper, .wp-picker-container, .wz-repeater-wrapper';
94
95 // Wrap the first occurrence of query in each matching text node with <mark>.
96 function highlightTerm($el, query) {
97 $el.contents().each(function () {
98 if (3 === this.nodeType) {
99 var text = this.nodeValue;
100 var idx = text.toLowerCase().indexOf(query);
101 if (-1 !== idx) {
102 var matched = text.slice(idx, idx + query.length);
103 var afterNode = document.createTextNode(text.slice(idx + query.length));
104 var mark = $('<mark class="wz-search-mark"></mark>').text(matched);
105 this.nodeValue = text.slice(0, idx);
106 $(this).after(afterNode).after(mark);
107 }
108 } else if (1 === this.nodeType && !$(this).is(highlightSkip)) {
109 highlightTerm($(this), query);
110 }
111 });
112 }
113
114 function updateTabBadge(panelId, count) {
115 var $link = $navLinks.filter('[href="#' + panelId + '"]');
116 if (!$link.length) {
117 return;
118 }
119 var $badge = $link.find('.wz-tab-count');
120 if (0 === count) {
121 $badge.remove();
122 } else {
123 if (!$badge.length) {
124 $badge = $('<span class="wz-tab-count"><span class="wz-tab-count-number"></span><span class="screen-reader-text"></span></span>').appendTo($link);
125 $badge.find('.screen-reader-text').text(' ' + (strings.search_matches_label || 'matching settings'));
126 }
127 $badge.find('.wz-tab-count-number').text(count);
128 }
129 $link.toggleClass('wz-tab-no-matches', 0 === count);
130 }
131
132 // Announce the result count to screen readers via the polite live region.
133 function announceResults(total) {
134 var template;
135 if (0 === total) {
136 template = strings.search_no_results || 'No settings found.';
137 } else if (1 === total) {
138 template = strings.search_results_single || '%d setting found.';
139 } else {
140 template = strings.search_results_plural || '%d settings found.';
141 }
142 $status.text(template.replace('%d', total));
143 }
144
145 // Single Save Changes bar shown while searching, replacing the hidden per-tab button rows.
146 function toggleActionsBar(show) {
147 if (show && !$actionsBar) {
148 var $save = $form.find('input[type="submit"][name="submit"]').first().clone().removeAttr('id');
149 if (!$save.length) {
150 return;
151 }
152 $actionsBar = $('<p class="wz-search-actions"></p>').append($save).appendTo($form);
153 }
154 if ($actionsBar) {
155 $actionsBar.toggle(show);
156 }
157 }
158
159 function toggleNoResults(show) {
160 if (show && !$noResults) {
161 $noResults = $('<p class="wz-search-no-results"></p>')
162 .text(strings.search_no_results || 'No settings found.')
163 .appendTo($form);
164 }
165 if ($noResults) {
166 $noResults.toggle(show);
167 }
168 }
169
170 function resetSearch() {
171 $container.removeClass('wz-searching');
172 clearHighlights();
173 $form.find('.wz-search-hidden').removeClass('wz-search-hidden');
174 $form.find('.wz-search-match').removeClass('wz-search-match');
175 $panels.removeClass('wz-has-matches');
176 $navLinks.removeClass('wz-tab-no-matches').find('.wz-tab-count').remove();
177 toggleNoResults(false);
178 toggleActionsBar(false);
179 $clearButton.prop('hidden', true);
180 $status.text('');
181 }
182
183 function applySearch() {
184 var query = $.trim($searchInput.val()).toLowerCase();
185
186 if (!query) {
187 resetSearch();
188 return;
189 }
190
191 if (!indexBuilt) {
192 buildIndex();
193 }
194
195 clearHighlights();
196 $container.addClass('wz-searching');
197
198 var total = 0;
199
200 $panels.each(function () {
201 var $panel = $(this);
202 var $rows = getRows($panel);
203 var count = 0;
204
205 $rows.each(function () {
206 var $row = $(this);
207 var matched = -1 !== ($row.data('wzSearchText') || '').indexOf(query);
208 $row.toggleClass('wz-search-match', matched).toggleClass('wz-search-hidden', !matched);
209 if (matched) {
210 count++;
211 }
212 });
213
214 // Keep a section header visible when any row in its group matched.
215 $rows.filter('.wz-settings-header-row').each(function () {
216 var $header = $(this);
217 if ($header.hasClass('wz-search-match')) {
218 return;
219 }
220 var groupHasMatch = $header.nextUntil('.wz-settings-header-row').filter('.wz-search-match').length > 0;
221 if (groupHasMatch) {
222 $header.removeClass('wz-search-hidden');
223 }
224 });
225
226 $panel.toggleClass('wz-has-matches', count > 0);
227 updateTabBadge($panel.attr('id'), count);
228 total += count;
229 });
230
231 // Highlight the matched term in the visible rows (labels and descriptions).
232 $form.find('tr.wz-search-match').children('th, td').each(function () {
233 highlightTerm($(this), query);
234 });
235
236 toggleNoResults(0 === total);
237 toggleActionsBar(total > 0);
238 $clearButton.prop('hidden', false);
239 announceResults(total);
240 }
241
242 // 'search' also fires when the native clear (x) button is used.
243 $searchInput.on('input search', function () {
244 clearTimeout(debounceTimer);
245 debounceTimer = setTimeout(applySearch, 200);
246 });
247
248 $searchInput.on('keydown', function (e) {
249 if ('Escape' === e.key) {
250 $(this).val('');
251 applySearch();
252 }
253 });
254
255 // Clear button: reset the search and return focus to the input.
256 $clearButton.on('click', function () {
257 $searchInput.val('');
258 applySearch();
259 $searchInput.trigger('focus');
260 });
261
262 // While searching, tab clicks scroll to that tab's results instead of switching tabs.
263 // Capture-phase listener so it runs before, and blocks, the jQuery UI Tabs handlers.
264 var navWrapper = $container.find('> ul.nav-tab-wrapper').get(0);
265 if (navWrapper) {
266 navWrapper.addEventListener(
267 'click',
268 function (e) {
269 if (!$container.hasClass('wz-searching')) {
270 return;
271 }
272 var link = e.target.closest ? e.target.closest('a.nav-tab') : null;
273 if (!link) {
274 return;
275 }
276 e.preventDefault();
277 e.stopPropagation();
278 var $panel = $(link.getAttribute('href'));
279 if ($panel.hasClass('wz-has-matches')) {
280 // Move focus to the section heading for keyboard/screen-reader users.
281 var title = $panel.find('.wz-section-title').get(0);
282 if (title) {
283 title.focus({ preventScroll: true });
284 }
285 window.scrollTo({ top: $panel.offset().top - 80, behavior: 'smooth' });
286 }
287 },
288 true
289 );
290 }
291 })();
292
293 // Initialize ColorPicker.
294 $('.color-field').each(function (i, element) {
295 $(element).wpColorPicker();
296 });
297
298 // Reset default thumbnail - uses plugin-specific localized data.
299 $('.reset-default-thumb').on('click', function () {
300 var settingsKey = WebberSettingsAdmin.settings_key || '';
301 var thumbDefault = (typeof window[WebberSettingsAdmin.prefix + '_admin'] !== 'undefined')
302 ? window[WebberSettingsAdmin.prefix + '_admin'].thumb_default
303 : '';
304 $('#' + settingsKey + '-thumb_default').val(thumbDefault);
305 });
306
307 // Reset formmodified on submit.
308 $('#' + prefix + '-settings-form').on('submit', function () {
309 formmodified = 0;
310 });
311
312 // Initialize Repeater Fields.
313 $('.wz-repeater-wrapper').each(function () {
314 var wrapper = $(this);
315 var itemsContainer = wrapper.find('.wz-repeater-items');
316 var index = parseInt(wrapper.data('index'), 10) || 0;
317 var liveUpdateField = wrapper.data('live-update-field') || 'name';
318 var fallbackTitle = wrapper.data('fallback-title') || '';
319 var liveUpdateOptions = wrapper.data('live-update-field-options') || {};
320
321 function reindexItems() {
322 itemsContainer.find('.wz-repeater-item').each(function (idx) {
323 $(this).find(':input').each(function () {
324 var name = $(this).attr('name');
325 if (name) {
326 name = name.replace(/\[\d+\](?=\[(?:fields|row_id)\])/, '[' + idx + ']');
327 $(this).attr('name', name);
328 }
329 });
330 });
331 }
332
333 // Add Item.
334 wrapper.on('click', '.add-item', function () {
335 var templateEl = wrapper.find('.repeater-template').get(0);
336 if (!templateEl) {
337 return;
338 }
339 var template = templateEl.innerHTML;
340 var uniqueId = 'row_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
341 template = template.replace(/{{INDEX}}/g, index);
342 template = template.replace(/{{ROW_ID}}/g, uniqueId);
343 itemsContainer.append(template);
344 index++;
345 var newItem = itemsContainer.find('.wz-repeater-item:last');
346 itemsContainer.find('.repeater-item-header:last')
347 .attr('aria-expanded', 'true')
348 .find('.toggle-icon').text('\u25b2');
349 itemsContainer.find('.repeater-item-content:last')
350 .attr('aria-hidden', 'false')
351 .css('display', 'block');
352 if (window.WebberInitTomSelect) {
353 window.WebberInitTomSelect(newItem.get(0));
354 }
355 document.dispatchEvent(new CustomEvent('wz:repeater-item-added', { detail: { container: newItem.get(0) } }));
356 });
357
358 // Remove Item.
359 wrapper.on('click', '.remove-item', function () {
360 $(this).closest('.wz-repeater-item').remove();
361 reindexItems();
362 });
363
364 // Move Up.
365 wrapper.on('click', '.move-up', function () {
366 var item = $(this).closest('.wz-repeater-item');
367 var prev = item.prev();
368 if (prev.length) {
369 item.insertBefore(prev);
370 reindexItems();
371 }
372 });
373
374 // Move Down.
375 wrapper.on('click', '.move-down', function () {
376 var item = $(this).closest('.wz-repeater-item');
377 var next = item.next();
378 if (next.length) {
379 item.insertAfter(next);
380 reindexItems();
381 }
382 });
383
384 // Toggle Accordion.
385 wrapper.on('click', '.repeater-item-header', function (e) {
386 e.preventDefault();
387 e.stopImmediatePropagation();
388 var $button = $(this);
389 var $toggleIcon = $button.find('.toggle-icon');
390 var $content = $button.next('.repeater-item-content');
391 var isExpanded = 'true' === $button.attr('aria-expanded');
392 if (isExpanded) {
393 $button.attr('aria-expanded', 'false');
394 $content.attr('aria-hidden', 'true').stop(true, true).hide();
395 $toggleIcon.text('\u25bc');
396 } else {
397 $button.attr('aria-expanded', 'true');
398 $content.attr('aria-hidden', 'false').stop(true, true).show();
399 $toggleIcon.text('\u25b2');
400 }
401 });
402
403 // Enforce unique selection across rows for the field named in data-unique-field.
404 var uniqueField = wrapper.data('unique-field') || '';
405 function syncUniqueSelects() {
406 if (!uniqueField) {
407 return;
408 }
409 var $selects = itemsContainer.find('.wz-repeater-item select[name$="[fields][' + uniqueField + ']"]');
410 var usedValues = {};
411 $selects.each(function () {
412 var val = $(this).val();
413 if (val) {
414 usedValues[val] = true;
415 }
416 });
417 $selects.each(function () {
418 var $sel = $(this);
419 var ownVal = $sel.val();
420 $sel.find('option').each(function () {
421 var optVal = $(this).val();
422 if (!optVal) {
423 return;
424 }
425 $(this).prop('disabled', optVal !== ownVal && usedValues[optVal]);
426 });
427 });
428 }
429
430 if (uniqueField) {
431 syncUniqueSelects();
432 wrapper.on('change', '.wz-repeater-item select[name$="[fields][' + uniqueField + ']"]', function () {
433 syncUniqueSelects();
434 });
435 wrapper.on('click', '.remove-item', function () {
436 // Sync after DOM removal; removal handler fires before remove, so defer.
437 setTimeout(syncUniqueSelects, 0);
438 });
439 document.addEventListener('wz:repeater-item-added', function (e) {
440 if (wrapper.get(0).contains(e.detail.container)) {
441 syncUniqueSelects();
442 }
443 });
444 }
445
446 // Live update repeater title when the specified field changes.
447 // Handles text inputs (input event), selects (change event, uses option text),
448 // and TomSelect-enhanced inputs (change event, uses displayed text or value).
449 function updateRepeaterTitle($field) {
450 var newName;
451 var val = $field.val();
452 if ($field.is('select')) {
453 // Ignore placeholder options (empty value).
454 newName = val ? $field.find('option:selected').text().trim() : '';
455 } else {
456 newName = val ? (liveUpdateOptions[val] || val) : '';
457 }
458 $field.closest('.wz-repeater-item').find('.repeater-title').text(newName || fallbackTitle);
459 }
460
461 wrapper.on('input change', '.wz-repeater-item :input[name$="[fields][' + liveUpdateField + ']"]', function () {
462 updateRepeaterTitle($(this));
463 });
464 });
465
466 });
467