PageSourceSearch

https://businesselitesafrica.com/wp-content/plugins/video-gallery-…/js/wpgpyt-features.js?ver=1786933611

js businesselitesafrica.com collected 2026-10-02 06:26:44 UTC 11,602 bytes, 415 lines download raw bytes

1( function () {
2	'use strict';
3
4	var STORAGE_KEY = 'wpgpyt_watch_later';
5
6	function init() {
7		// Videos load dynamically, so watch for them
8		observeVideoList();
9	}
10
11	// Watch for dynamically loaded videos
12	function observeVideoList() {
13		var attempts = 0;
14		var maxAttempts = 30;
15
16		// Poll for the list instead of observing (safer)
17		var checkInterval = setInterval( function () {
18			attempts++;
19
20			var list = document.querySelector( '#spidochetube_list' );
21			if ( list && list.querySelectorAll( 'li' ).length ) {
22				setupWatchLater( list );
23				clearInterval( checkInterval );
24			}
25
26			// Stop after max attempts (prevent infinite loop)
27			if ( attempts >= maxAttempts ) {
28				clearInterval( checkInterval );
29			}
30		}, 500 );
31	}
32
33	function setupWatchLater( list ) {
34		var items = list.querySelectorAll( 'li' );
35
36		items.forEach( function ( li ) {
37			var link = li.querySelector( 'a[data-youtubeid]' );
38			if ( ! link ) {
39				return;
40			}
41
42			var videoId = link.getAttribute( 'data-youtubeid' );
43			if ( ! videoId ) {
44				return;
45			}
46
47			// Avoid duplicate button
48			if ( li.querySelector( '.wpgpyt-save-btn' ) ) {
49				return;
50			}
51
52			var titleEl = li.querySelector( 'span' );
53			var imgEl = li.querySelector( 'img' );
54			var video = {
55				id: videoId,
56				title: titleEl ? titleEl.textContent : '',
57				thumb: imgEl ? imgEl.getAttribute( 'src' ) : ''
58			};
59
60			var btn = document.createElement( 'button' );
61			btn.className = 'wpgpyt-save-btn';
62			btn.type = 'button';
63			btn.setAttribute( 'aria-label', 'Save to Watch Later' );
64			btn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z"/></svg>';
65
66			if ( isSaved( videoId ) ) {
67				btn.classList.add( 'wpgpyt-saved' );
68			}
69
70			li.appendChild( btn );
71
72			btn.addEventListener( 'click', function ( e ) {
73				e.preventDefault();
74				e.stopPropagation();
75				toggleSave( video, btn );
76			} );
77		} );
78
79		injectSavedBar();
80		updateCount();
81	}
82
83	function getSaved() {
84		try {
85			return JSON.parse( localStorage.getItem( STORAGE_KEY ) ) || [];
86		} catch ( err ) {
87			return [];
88		}
89	}
90
91	function saveList( list ) {
92		try {
93			localStorage.setItem( STORAGE_KEY, JSON.stringify( list ) );
94		} catch ( err ) {}
95	}
96
97	function isSaved( id ) {
98		return getSaved().some( function ( item ) {
99			return item.id === id;
100		} );
101	}
102
103	function toggleSave( video, btn ) {
104		var list = getSaved();
105		var index = -1;
106		for ( var i = 0; i < list.length; i++ ) {
107			if ( list[ i ].id === video.id ) {
108				index = i;
109				break;
110			}
111		}
112
113		if ( index === -1 ) {
114			list.push( video );
115			btn.classList.add( 'wpgpyt-saved' );
116			showToast( 'Saved to Watch Later' );
117		} else {
118			list.splice( index, 1 );
119			btn.classList.remove( 'wpgpyt-saved' );
120			showToast( 'Removed from Watch Later' );
121		}
122
123		saveList( list );
124		updateCount();
125	}
126
127	// === Saved Bar ===
128	function injectSavedBar() {
129		var wrapper = document.querySelector( '#wpgpyt-wrapper' );
130		if ( ! wrapper ) {
131			return;
132		}
133
134		if ( document.querySelector( '.wpgpyt-saved-bar' ) ) {
135			return;
136		}
137
138		var bar = document.createElement( 'div' );
139		bar.className = 'wpgpyt-saved-bar';
140		bar.innerHTML =
141			'<button class="wpgpyt-saved-count-btn" type="button">' +
142				'<svg viewBox="0 0 24 24"><path d="M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2z"/></svg>' +
143				'<span class="wpgpyt-saved-count-text">Saved (0)</span>' +
144			'</button>';
145
146		wrapper.parentNode.insertBefore( bar, wrapper );
147
148		bar.querySelector( '.wpgpyt-saved-count-btn' ).addEventListener( 'click', function () {
149			var count = getSaved().length;
150			if ( count === 0 ) {
151				showToast( 'No saved videos yet' );
152			} else {
153				showToast( 'You have ' + count + ' saved video' + ( count > 1 ? 's' : '' ) );
154			}
155		} );
156	}
157
158	function updateCount() {
159		var text = document.querySelector( '.wpgpyt-saved-count-text' );
160		if ( text ) {
161			text.textContent = 'Saved (' + getSaved().length + ')';
162		}
163	}
164
165	// === Toast ===
166	var toastTimer = null;
167	function showToast( message ) {
168		var toast = document.querySelector( '.wpgpyt-toast' );
169		if ( ! toast ) {
170			toast = document.createElement( 'div' );
171			toast.className = 'wpgpyt-toast';
172			document.body.appendChild( toast );
173		}
174		toast.textContent = message;
175		void toast.offsetWidth;
176		toast.classList.add( 'wpgpyt-toast-show' );
177		clearTimeout( toastTimer );
178		toastTimer = setTimeout( function () {
179			toast.classList.remove( 'wpgpyt-toast-show' );
180		}, 2500 );
181	}
182
183	// === DOM Ready ===
184	if ( document.readyState === 'loading' ) {
185		document.addEventListener( 'DOMContentLoaded', init );
186	} else {
187		init();
188	}
189} )();
190
191
192// ===== Video Search Feature =====
193( function () {
194	'use strict';
195
196	function initSearch() {
197		var attempts = 0;
198		var maxAttempts = 30;
199
200		var checkInterval = setInterval( function () {
201			attempts++;
202
203			var list = document.querySelector( '#spidochetube_list' );
204			if ( list && list.querySelectorAll( 'li' ).length ) {
205				setupSearch( list );
206				clearInterval( checkInterval );
207			}
208
209			if ( attempts >= maxAttempts ) {
210				clearInterval( checkInterval );
211			}
212		}, 500 );
213	}
214
215	function setupSearch( list ) {
216		// Avoid duplicate search bar
217		if ( document.querySelector( '.wpgpyt-search-bar' ) ) {
218			return;
219		}
220
221		var wrapper = document.querySelector( '#wpgpyt-wrapper' );
222		if ( ! wrapper ) {
223			return;
224		}
225
226		// Create search bar
227		var searchBar = document.createElement( 'div' );
228		searchBar.className = 'wpgpyt-search-bar';
229		searchBar.innerHTML =
230			'<input type="text" class="wpgpyt-search-input" placeholder="Search videos..." aria-label="Search videos">' +
231			'<svg class="wpgpyt-search-icon" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>' +
232			'<button class="wpgpyt-search-clear" type="button" aria-label="Clear search">&times;</button>';
233
234		// Insert before wrapper (or after saved bar if exists)
235		var savedBar = document.querySelector( '.wpgpyt-saved-bar' );
236		if ( savedBar ) {
237			savedBar.parentNode.insertBefore( searchBar, savedBar.nextSibling );
238		} else {
239			wrapper.parentNode.insertBefore( searchBar, wrapper );
240		}
241
242		// Create "no results" message
243		var noResults = document.createElement( 'div' );
244		noResults.className = 'wpgpyt-no-results';
245		noResults.textContent = 'No videos found';
246		wrapper.parentNode.insertBefore( noResults, wrapper.nextSibling );
247
248		var input = searchBar.querySelector( '.wpgpyt-search-input' );
249		var clearBtn = searchBar.querySelector( '.wpgpyt-search-clear' );
250
251		// Search on input
252		input.addEventListener( 'input', function () {
253			var query = input.value.toLowerCase().trim();
254
255			if ( query ) {
256				searchBar.classList.add( 'wpgpyt-has-text' );
257			} else {
258				searchBar.classList.remove( 'wpgpyt-has-text' );
259			}
260
261			filterVideos( list, query, noResults );
262		} );
263
264		// Clear button
265		clearBtn.addEventListener( 'click', function () {
266			input.value = '';
267			searchBar.classList.remove( 'wpgpyt-has-text' );
268			filterVideos( list, '', noResults );
269			input.focus();
270		} );
271	}
272
273	function filterVideos( list, query, noResults ) {
274		var items = list.querySelectorAll( 'li' );
275		var visibleCount = 0;
276
277		items.forEach( function ( li ) {
278			var titleEl = li.querySelector( 'span' );
279			var title = titleEl ? titleEl.textContent.toLowerCase() : '';
280
281			if ( ! query || title.indexOf( query ) !== -1 ) {
282				li.classList.remove( 'wpgpyt-filtered' );
283				visibleCount++;
284			} else {
285				li.classList.add( 'wpgpyt-filtered' );
286			}
287		} );
288
289		// Show/hide no results message
290		if ( visibleCount === 0 && query ) {
291			noResults.classList.add( 'wpgpyt-show' );
292		} else {
293			noResults.classList.remove( 'wpgpyt-show' );
294		}
295	}
296
297	if ( document.readyState === 'loading' ) {
298		document.addEventListener( 'DOMContentLoaded', initSearch );
299	} else {
300		initSearch();
301	}
302} )();
303
304
305// ===== Sticky Player Feature =====
306( function () {
307	'use strict';
308
309	function initSticky() {
310		var attempts = 0;
311		var maxAttempts = 30;
312
313		var checkInterval = setInterval( function () {
314			attempts++;
315
316			var player = document.querySelector( '#spidochetube_player' );
317			if ( player && player.querySelector( 'iframe' ) ) {
318				setupSticky( player );
319				clearInterval( checkInterval );
320			}
321
322			if ( attempts >= maxAttempts ) {
323				clearInterval( checkInterval );
324			}
325		}, 500 );
326	}
327
328	function setupSticky( player ) {
329		// Avoid duplicate setup
330		if ( player.getAttribute( 'data-wpgpyt-sticky-ready' ) ) {
331			return;
332		}
333		player.setAttribute( 'data-wpgpyt-sticky-ready', '1' );
334
335		// Add close button
336		var closeBtn = document.createElement( 'button' );
337		closeBtn.className = 'wpgpyt-sticky-close';
338		closeBtn.type = 'button';
339		closeBtn.setAttribute( 'aria-label', 'Close sticky player' );
340		closeBtn.innerHTML = '&times;';
341		player.appendChild( closeBtn );
342
343		// Add LIVE label
344		var label = document.createElement( 'span' );
345		label.className = 'wpgpyt-sticky-label';
346		label.textContent = 'Playing';
347		player.appendChild( label );
348
349		// Create placeholder (prevents layout jump when sticky)
350		var placeholder = document.createElement( 'div' );
351		placeholder.className = 'wpgpyt-player-placeholder';
352		player.parentNode.insertBefore( placeholder, player );
353
354		var stickyDismissed = false;
355
356		// Close button handler
357		closeBtn.addEventListener( 'click', function () {
358			player.classList.remove( 'wpgpyt-sticky' );
359			placeholder.classList.remove( 'wpgpyt-active' );
360			stickyDismissed = true;
361		} );
362
363		// Scroll handler
364		var ticking = false;
365		window.addEventListener( 'scroll', function () {
366			if ( ! ticking ) {
367				window.requestAnimationFrame( function () {
368					handleScroll( player, placeholder, stickyDismissed );
369					ticking = false;
370				} );
371				ticking = true;
372			}
373		} );
374
375		// Reset dismiss when a new video is clicked
376		var list = document.querySelector( '#spidochetube_list' );
377		if ( list ) {
378			list.addEventListener( 'click', function () {
379				stickyDismissed = false;
380			} );
381		}
382	}
383
384	function handleScroll( player, placeholder, stickyDismissed ) {
385		if ( stickyDismissed ) {
386			return;
387		}
388
389		// Use placeholder position (since player may be fixed)
390		var reference = placeholder.classList.contains( 'wpgpyt-active' ) ? placeholder : player;
391		var rect = reference.getBoundingClientRect();
392
393		// When player scrolled above viewport top
394		if ( rect.top < -50 && ! player.classList.contains( 'wpgpyt-sticky' ) ) {
395			// Set placeholder height to match player (prevent jump)
396			placeholder.style.height = player.offsetHeight + 'px';
397			placeholder.classList.add( 'wpgpyt-active' );
398			player.classList.add( 'wpgpyt-sticky' );
399		} else if ( player.classList.contains( 'wpgpyt-sticky' ) ) {
400			// Check placeholder position to decide un-sticky
401			var phRect = placeholder.getBoundingClientRect();
402			if ( phRect.top >= -50 ) {
403				player.classList.remove( 'wpgpyt-sticky' );
404				placeholder.classList.remove( 'wpgpyt-active' );
405				placeholder.style.height = '';
406			}
407		}
408	}
409
410	if ( document.readyState === 'loading' ) {
411		document.addEventListener( 'DOMContentLoaded', initSticky );
412	} else {
413		initSticky();
414	}
415} )();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.