PluginProbe
WebberZone Top 10 — Popular Posts / trunk
WebberZone Top 10 — Popular Posts vtrunk
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 1.6.1 1.6.2 All 116 releases
top-10 / includes / admin / settings / js / settings-admin-scripts.js

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

458 lines 14.1 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 .toggle-icon').text('\u25b2');
347 itemsContainer.find('.repeater-item-content:last').css('display', 'block');
348 if (window.WebberInitTomSelect) {
349 window.WebberInitTomSelect(newItem.get(0));
350 }
351 document.dispatchEvent(new CustomEvent('wz:repeater-item-added', { detail: { container: newItem.get(0) } }));
352 });
353
354 // Remove Item.
355 wrapper.on('click', '.remove-item', function () {
356 $(this).closest('.wz-repeater-item').remove();
357 reindexItems();
358 });
359
360 // Move Up.
361 wrapper.on('click', '.move-up', function () {
362 var item = $(this).closest('.wz-repeater-item');
363 var prev = item.prev();
364 if (prev.length) {
365 item.insertBefore(prev);
366 reindexItems();
367 }
368 });
369
370 // Move Down.
371 wrapper.on('click', '.move-down', function () {
372 var item = $(this).closest('.wz-repeater-item');
373 var next = item.next();
374 if (next.length) {
375 item.insertAfter(next);
376 reindexItems();
377 }
378 });
379
380 // Toggle Accordion.
381 wrapper.on('click', '.repeater-item-header', function () {
382 var $this = $(this);
383 var $toggleIcon = $this.find('.toggle-icon');
384 var $content = $this.next('.repeater-item-content');
385 if ($content.is(':visible')) {
386 $content.slideUp();
387 $toggleIcon.text('\u25bc');
388 } else {
389 $content.slideDown();
390 $toggleIcon.text('\u25b2');
391 }
392 });
393
394 // Enforce unique selection across rows for the field named in data-unique-field.
395 var uniqueField = wrapper.data('unique-field') || '';
396 function syncUniqueSelects() {
397 if (!uniqueField) {
398 return;
399 }
400 var $selects = itemsContainer.find('.wz-repeater-item select[name$="[fields][' + uniqueField + ']"]');
401 var usedValues = {};
402 $selects.each(function () {
403 var val = $(this).val();
404 if (val) {
405 usedValues[val] = true;
406 }
407 });
408 $selects.each(function () {
409 var $sel = $(this);
410 var ownVal = $sel.val();
411 $sel.find('option').each(function () {
412 var optVal = $(this).val();
413 if (!optVal) {
414 return;
415 }
416 $(this).prop('disabled', optVal !== ownVal && usedValues[optVal]);
417 });
418 });
419 }
420
421 if (uniqueField) {
422 syncUniqueSelects();
423 wrapper.on('change', '.wz-repeater-item select[name$="[fields][' + uniqueField + ']"]', function () {
424 syncUniqueSelects();
425 });
426 wrapper.on('click', '.remove-item', function () {
427 // Sync after DOM removal; removal handler fires before remove, so defer.
428 setTimeout(syncUniqueSelects, 0);
429 });
430 document.addEventListener('wz:repeater-item-added', function (e) {
431 if (wrapper.get(0).contains(e.detail.container)) {
432 syncUniqueSelects();
433 }
434 });
435 }
436
437 // Live update repeater title when the specified field changes.
438 // Handles text inputs (input event), selects (change event, uses option text),
439 // and TomSelect-enhanced inputs (change event, uses displayed text or value).
440 function updateRepeaterTitle($field) {
441 var newName;
442 var val = $field.val();
443 if ($field.is('select')) {
444 // Ignore placeholder options (empty value).
445 newName = val ? $field.find('option:selected').text().trim() : '';
446 } else {
447 newName = val ? (liveUpdateOptions[val] || val) : '';
448 }
449 $field.closest('.wz-repeater-item').find('.repeater-title').text(newName || fallbackTitle);
450 }
451
452 wrapper.on('input change', '.wz-repeater-item :input[name$="[fields][' + liveUpdateField + ']"]', function () {
453 updateRepeaterTitle($(this));
454 });
455 });
456
457 });
458