PageSourceSearch

https://majalla.org/wp-content/plugins/socialfeeds/assets/js/frontend.js?ver=1.0.9

js majalla.org collected 2026-10-02 11:46:56 UTC 14,965 bytes, 420 lines download raw bytes

1jQuery(document).ready(function ($) {
2
3	function escape_html(str) {
4		if (str === null || str === undefined) {
5			return '';
6		}
7		return $('<div>').text(String(str)).html();
8	}
9
10	function escape_attr(str) {
11		return escape_html(str).replace(/"/g, '&quot;').replace(/'/g, '&#039;');
12	}
13
14	function escape_url(url) {
15    if (!url) {
16    	return '';
17    }
18
19    let parsed = new URL(String(url), window.location.origin);
20
21    if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
22    	return '';
23    }
24
25    return parsed.href;
26	}
27
28	function sanitize_youtube_id(id) {
29		if (!id) {
30			return '';
31		}
32		let match = String(id).match(/[A-Za-z0-9_-]{11}/);
33		return match ? match[0] : '';
34	}
35
36	// ===== Video play mode: LIGHTBOX, INLINE, NEWTAB =====
37	$('.socialfeeds-youtube-feed').on('click','.socialfeeds-video-item a, .socialfeeds-media-link, .socialfeeds-carousel-item a, .socialfeeds-list-item iframe',function(e){
38  	let $link = $(this).closest('a');
39		if (!$link.length) $link = $(this);
40
41		// If it's an iframe click (list view), let it be
42		if ($link.is('iframe')) return;
43
44		let $feed = $link.closest('.socialfeeds-youtube-feed');
45		if (!$feed.length) return;
46
47		let click_action = $feed.data('click') || $feed.attr('data-click') || 'newtab';
48
49		// 1. New Tab (Default behavior)
50		if (click_action === 'newtab') {
51			return; // Let default click happen
52		}
53
54		e.preventDefault();
55		let href = $link.attr('href');
56		if (!href) return;
57
58		let vid_match = href.match(/[?&]v=([^&]+)/) || href.match(/youtu\.be\/([^?&]+)/) || href.split('/').pop().match(/^([a-zA-Z0-9_-]{11})/);
59		let vid = vid_match ? vid_match[1] : '';
60
61		if (!vid) {
62			// Fallback if video ID not found
63			window.open(href, '_blank');
64			return;
65		}
66
67		// 2. Lightbox
68		if (click_action === 'lightbox'){
69			let modal_html = `
70				<div class="socialfeeds-modal">
71					<div class="socialfeeds-modal-backdrop"></div>
72					<div class="socialfeeds-modal-inner" style="width: 900px; max-width: 90vw; background: #000;">
73						<button class="socialfeeds-modal-close" style="z-index:100; color:#fff; background:rgba(0,0,0,0.5);">×</button>
74						<div class="socialfeeds-modal-content" style="position:relative; width:100%; aspect-ratio:16/9;">
75							<iframe src="https://www.youtube.com/embed/${encodeURIComponent(vid)}?autoplay=1&rel=0" 
76								frameborder="0" 
77								allow="autoplay; encrypted-media" 
78								allowfullscreen
79								style="position:absolute; top:0; left:0; width:100%; height:100%;">
80							</iframe>
81						</div>
82					</div>
83				</div>`;
84
85			$('body').append(modal_html);
86			setTimeout(() => $('.socialfeeds-modal').addClass('active'), 10);
87			return;
88		}
89
90		// 3. Inline
91		if(click_action === 'inline'){
92			// Find the play overlay and hide it (if it exists)
93			let $overlay = $link.siblings('.socialfeeds-play-overlay');
94			if ($overlay.length) $overlay.hide();
95
96			// Should specifically target the media container to keep title/desc intact
97			$link.replaceWith(`
98				<iframe src="https://www.youtube.com/embed/${encodeURIComponent(vid)}?autoplay=1&mute=0&rel=0&playsinline=1"
99					frameborder="0"
100					allow="autoplay; encrypted-media"
101					allowfullscreen
102					style="width:100%; aspect-ratio:16/9; display:block;">
103				</iframe>
104			`);
105		}
106	});
107
108	$(document).on('click', '.socialfeeds-modal-close, .socialfeeds-modal-backdrop', function (e) {
109		$('.socialfeeds-modal').removeClass('active');
110		setTimeout(() => $('.socialfeeds-modal').remove(), 300);
111	});
112
113	// ===== LOAD MORE (AJAX) =====
114	$('.socialfeeds-load-more-btn').on('click', function (e){
115		e.preventDefault();
116		let $btn = $(this);
117		if ($btn.data('feed-type') === 'instagram') {
118			return;
119		}
120		let feed_id = $btn.data('feed-id'),
121			feed_type = $btn.data('feed-type'),
122			feed_input = $btn.data('feed-input'),
123			limit = parseInt($btn.data('limit'), 10),
124			loaded = parseInt($btn.data('loaded'), 10),
125			page_token = $btn.attr('data-page-token') || '',
126			$feed = $btn.closest('.socialfeeds-youtube-feed');
127
128
129		if (!feed_type || !feed_input) {
130			console.error('SocialFeeds: Missing required parameters for Load More');
131			return;
132		}
133
134		$btn.prop('disabled', true).addClass('socialfeeds-loading');
135		let original_text = $btn.text();
136
137		let ajax_data = {
138			action: 'socialfeeds_load_more_videos',
139			feed_id: feed_id,
140			feed_type: feed_type,
141			feed_input: feed_input,
142			limit: limit,
143			pageToken: page_token,
144			nonce: socialfeeds_ajax.nonce
145		};
146
147		$.ajax({
148			url: socialfeeds_ajax.ajax_url,
149			type: 'POST',
150			data: ajax_data,
151			dataType: 'json',
152			success: function (response) {
153				if (!response.success) {
154					$btn.prop('disabled', false).removeClass('socialfeeds-loading').text('Error loading videos');
155					return;
156				}
157
158				let data = response.data;
159
160				if (data && data.html) {
161
162					let $feed = $btn.closest('.socialfeeds-youtube-feed');
163					let $itemsContainer = $feed.find('.socialfeeds-youtube-grid, .socialfeeds-youtube-list, .socialfeeds-youtube-carou
163sel');
164
165					// Fallback if not found
166					if (!$itemsContainer.length) {
167						$itemsContainer = $btn.closest('.socialfeeds-load-more-container');
168					}
169
170					if ($itemsContainer.length && !$itemsContainer.hasClass('socialfeeds-load-more-container')) {
171						$itemsContainer.append(data.html);
172					} else {
173						$btn.closest('.socialfeeds-load-more-container').before(data.html);
174					}
175
176					let loaded_count = (data.items && data.items.length) ? data.items.length : 1;
177
178					let new_loaded = loaded + loaded_count;
179					$btn.data('loaded', new_loaded).attr('data-loaded', new_loaded);
180
181					if (data.nextPageToken) {
182						$btn.attr('data-page-token', data.nextPageToken);
183						$btn.prop('disabled', false).removeClass('socialfeeds-loading').text(original_text);
184					} else {
185						$btn.text($btn.data('no-more-text') || 'No More Videos').hide();
186					}
187
188					if ($itemsContainer.hasClass('socialfeeds-youtube-carousel')) {
189						$(window).trigger('resize');
190					}
191
192				} else if (data && data.items && data.items.length > 0) {
193					let items = data.items;
194
195					// Find the inner container where items live
196					let $itemsContainer = $feed.find('.socialfeeds-youtube-grid, .socialfeeds-youtube-list, .socialfeeds-youtube-carousel');
197
198					// Fallback if not found (shouldn't happen if structure is valid)
199					if(!$itemsContainer.length){
200						$itemsContainer = $btn.closest('.socialfeeds-load-more-container');
201					}
202
203					let is_grid = $itemsContainer.hasClass('socialfeeds-youtube-grid');
204					let is_carousel = $itemsContainer.hasClass('socialfeeds-youtube-carousel');
205					let is_list = $itemsContainer.hasClass('socialfeeds-youtube-list');
206
207					items.forEach(function (item) {
208						let vid = sanitize_youtube_id(item.videoId);
209						if (!vid) {
210							return;
211						}
212						let title = escape_html(item.title || 'Video');
213						let thumb = escape_url((item.thumbnails && item.thumbnails.medium) ? item.thumbnails.medium.url :
214							((item.thumbnails && item.thumbnails.default) ? item.thumbnails.default.url : ''));
215						let desc = item.description || '';
216						let short_desc = desc ? escape_html(desc.substring(0, 54) + '...') : '';
217
218						let item_html = '';
219						if (is_grid) {
220							item_html = `
221									<div class="socialfeeds-video-item">
222										<a href="https://www.youtube.com/watch?v=${encodeURIComponent(vid)}" target="_blank" rel="noopener">
223											<img src="${thumb}" alt="${escape_attr(item.title || 'Video')}" />
224										</a>
225										${title ? `<h5 class="socialfeeds-video-title">${title}</h5>` : ''}
226										${short_desc ? `<p class="socialfeeds-card-desc">${short_desc}</p>` : ''}
227									</div>`;
228						} else if (is_carousel) {
229							item_html = `
230									<div class="socialfeeds-video-item socialfeeds-carousel-item">
231										<a href="https://www.youtube.com/watch?v=${encodeURIComponent(vid)}" target="_blank" rel="noopener" style="display:block; position:relative;">
232											<img src="${thumb}" alt="${escape_attr(item.title || 'Video')}" />
233											<span class="socialfeeds-play-overlay"></span>
234										</a>
235										${title ? `<p class="socialfeeds-video-title">${title}</p>` : ''}
236									</div>`;
237						} else if (is_list) {
238							item_html = `
239									<div class="socialfeeds-video-item socialfeeds-list-item">
240										${title ? `<h3 class="socialfeeds-video-title">${title}</h3>` : ''}
241										<div class='socialfeeds-video-iframe-wrap'>
242											<iframe width="560" height="315" src="https://www.youtube.com/embed/${encodeURIComponent(vid)}" frameborder="0" allowfullscreen></iframe>
243										</div>
244										${short_desc ? `<p class="socialfeeds-video-desc">${short_desc}</p>` : ''}
245									</div>`;
246						} else {
247							// Default/Fallback
248							item_html = `
249									<div class="socialfeeds-video-item">
250										<a href="https://www.youtube.com/watch?v=${encodeURIComponent(vid)}" target="_blank" rel="noopener">
251											<img src="${thumb}" alt="${escape_attr(item.title || 'Video')}" />
252										</a>
253										${title ? `<h5 class="socialfeeds-video-title">${title}</h5>` : ''}
254										${short_desc ? `<p class="socialfeeds-card-desc">${short_desc}</p>` : ''}
255									</div>`;
256						}
257
258						// Append to the specific items container
259						if ($itemsContainer.length && !$itemsContainer.hasClass('socialfeeds-load-more-container')) {
260							$itemsContainer.append(item_html);
261						} else {
262							// Fallback: insert before button if we couldn't find the container
263							$btn.closest('.socialfeeds-load-more-container').before(item_html);
264						}
265					});
266
267					let new_loaded = loaded + items.length;
268					$btn.data('loaded', new_loaded).attr('data-loaded', new_loaded);
269
270					if (data.nextPageToken) {
271						$btn.attr('data-page-token', data.nextPageToken);
272						$btn.prop('disabled', false).removeClass('socialfeeds-loading').text(original_text);
273					} else {
274						$btn.text($btn.data('no-more-text') || 'No More Videos').hide();
275					}
276
277					// If we are in a carousel, trigger resize to update dots/nav
278					if(is_carousel){
279						$(window).trigger('resize');
280					}
281
282					// If items loaded are less than limit, we likely reached the end
283					if (items.length < limit) {
284						$btn.text($btn.data('no-more-text') || 'No More Videos').hide();
285					}
286
287				} else {
288					$btn.text($btn.data('no-more-text') || 'No More Videos').hide();
289				}
290			},
291			error: function (xhr, status, error) {
292				console.error('SocialFeeds AJAX Error:', error);
293				$btn.prop('disabled', false).removeClass('socialfeeds-loading').text('Error loading videos');
294			}
295		});
296	});
297
298	// ===== CAROUSEL INITIALIZATION =====
299	$('.socialfeeds-youtube-carousel').each(function () {
300		let $carousel = $(this);
301		let $feedWrapper = $carousel.closest('.socialfeeds-youtube-feed');
302
303		// Prevent double init
304		if ($feedWrapper.find('.socialfeeds-carousel-stage').length) return;
305
306		// 1. Wrap carousel in a stage for absolute positioning of arrows
307		// We want the stage to include the carousel but NOT the load more button if it's there
308		$carousel.wrap('<div class="socialfeeds-carousel-stage" style="position:relative;"></div>');
309		let $stage = $carousel.parent();
310
311		// 2. Create Nav Buttons (Inside Stage)
312		let $nav = $('<div class="socialfeeds-carousel-nav">' +
313			'<button class="socialfeeds-carousel-btn prev" aria-label="Previous" type="button"><span class="dashicons dashicons-arrow-left-alt2" style="font-family:dashicons; line-height:inherit;"></span></button>' +
314			'<button class="socialfeeds-carousel-btn next" aria-label="Next" type="button"><span class="dashicons dashicons-arrow-right-alt2" style="font-family:dashicons; line-height:inherit;"></span></button>' +
315			'</div>');
316
317		$stage.append($nav);
318
319		// 3. Create Dots (After Stage)
320		let $dots = $('<div class="socialfeeds-carousel-dots"></div>');
321		$stage.after($dots);
322
323		// If there is a load more container, ensure it is after dots
324		let $loadMore = $feedWrapper.find('.socialfeeds-load-more-container');
325		if ($loadMore.length) {
326			$feedWrapper.append($loadMore); // Move it to the very end
327		}
328
329		function updateDots() {
330			$dots.empty();
331			let scrollW = $carousel[0].scrollWidth;
332			let clientW = $carousel[0].clientWidth;
333			if (clientW >= scrollW) {
334				$dots.hide();
335				$nav.hide();
336				return;
337			}
338
339			$dots.show();
340			$nav.show();
341
342			// Simple pagination: items per page
343			let itemW = $carousel.find('.socialfeeds-carousel-item').first().outerWidth(true) || 300;
344			let itemsPerPage = Math.floor(clientW / itemW) || 1;
345			let totalItems = $carousel.find('.socialfeeds-carousel-item').length;
346			let pageCount = Math.ceil(totalItems / itemsPerPage);
347
348			// If scrolling is continuous, we approximate pages based on scrollWidth
349			pageCount = Math.ceil(scrollW / clientW);
350
351			for (let i = 0; i < pageCount; i++) {
352				let $d = $('<span class="socialfeeds-dot"></span>');
353				if (i === 0) $d.addClass('active');
354				$d.data('page', i);
355				$dots.append($d);
356			}
357		}
358
359		// Initial calc
360		setTimeout(updateDots, 500); // delay for layout settle
361		$(window).on('resize', updateDots);
362
363		// 4. Scroll Logic
364		$nav.find('.prev').on('click', function (e) {
365			e.preventDefault();
366			let itemW = $carousel.find('.socialfeeds-carousel-item').first().outerWidth(true) || 300;
367			$carousel.animate({ scrollLeft: $carousel.scrollLeft() - itemW }, 300, update_active_dot);
368		});
369
370		$nav.find('.next').on('click', function (e) {
371			e.preventDefault();
372			let itemW = $carousel.find('.socialfeeds-carousel-item').first().outerWidth(true) || 300;
373			$carousel.animate({ scrollLeft: $carousel.scrollLeft() + itemW }, 300, update_active_dot);
374		});
375
376		// Dots Click
377		$dots.on('click', '.socialfeeds-dot', function () {
378			let page = $(this).data('page');
379			let clientW = $carousel[0].clientWidth;
380			$carousel.animate({ scrollLeft: page * clientW }, 400, update_active_dot);
381		});
382
383		function update_active_dot() {
384			let scroll_left = $carousel.scrollLeft();
385			let clientW = $carousel[0].clientWidth;
386			let page = Math.round(scroll_left / clientW);
387			$dots.find('.socialfeeds-dot').removeClass('active').eq(page).addClass('active');
388		}
389
390		$carousel.on('scroll', function () {
391			clearTimeout($carousel.data('scrollTimer'));
392			$carousel.data('scrollTimer', setTimeout(update_active_dot, 100));
393		});
394
395		// 5. Autoplay
396		if ($carousel.data('autoplay')) {
397			let speed = parseInt($carousel.data('autoplay-speed')) || 3000;
398			let interval;
399
400			function start_autoplay() {
401				interval = setInterval(function () {
402					if ($carousel.scrollLeft() + $carousel.innerWidth() >= $carousel[0].scrollWidth - 10) {
403						$carousel.animate({ scrollLeft: 0 }, 600, update_active_dot);
404					} else {
405						let itemW = $carousel.find('.socialfeeds-carousel-item').first().outerWidth(true) || 300;
406						$carousel.animate({ scrollLeft: $carousel.scrollLeft() + itemW }, 600, update_active_dot);
407					}
408				}, speed);
409			}
410
411			start_autoplay();
412
413			$stage.hover(
414				function () { clearInterval(interval); },
415				function () { start_autoplay(); }
416			);
417		}
418	});
419
420});

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.