PageSourceSearch

https://www.craneando.es/wp-content/plugins/nextgen-gallery/static/Lightbox/ngg-tiktok-video.js?ver=4.1.3

js craneando.es collected 2026-10-02 01:02:29 UTC 17,586 bytes, 561 lines download raw bytes

1/**
2 * NextGEN Gallery TikTok Video Player
3 * @package NextGEN Gallery
4 */
5
6(function($, window) {
7    'use strict';
8
9    window.NggTikTokVideo = window.NggTikTokVideo || {};
10
11    let linkHandlersInitialized = false;
12    // Cache for gallery ID lookups to avoid repeated DOM traversals
13    const galleryIdCache = new Map();
14
15    // Constants
16    const DEFAULT_DIMENSIONS = { width: 480, height: 854 };
17    const ASPECT_RATIO = 9 / 16;
18    const MAX_DEPTH = 15;
19    const INIT_DELAY = 100;
20
21    /**
22     * Get TikTok settings for a gallery
23     * @param {string|number|null} galleryId - Gallery ID
24     * @returns {Object} Settings object with link and link_target
25     */
26    NggTikTokVideo.getTikTokSettings = (galleryId) => {
27        if (!window.ngg_tiktok_gallery_settings) {
28            window.ngg_tiktok_gallery_settings = {
29                global: {
30                    link: '0',
31                    link_target: '0'
32                }
33            };
34        }
35
36        const settings = window.ngg_tiktok_gallery_settings;
37        const galleryIdStr = galleryId ? String(galleryId) : null;
38
39        if (galleryIdStr && settings[`gallery_${galleryIdStr}`]) {
40            const gallerySettings = settings[`gallery_${galleryIdStr}`];
41            if (gallerySettings && (gallerySettings.link !== undefined || gallerySettings.link_target !== undefined)) {
42                return {
43                    link: String(gallerySettings.link ?? '0'),
44                    link_target: String(gallerySettings.link_target ?? '0')
45                };
46            }
47        }
48
49        const globalSettings = settings.global || {};
50        return {
51            link: String(globalSettings.link || '0'),
52            link_target: String(globalSettings.link_target || '0')
53        };
54    };
55
56    /**
57     * Find gallery ID from anchor element
58     * @param {jQuery} $anchor - jQuery anchor element
59     * @param {string|null} imageId - Image ID if available
60     * @returns {Object|null} Object with galleryId and $galleryContainer
61     */
62    const findGalleryId = ($anchor, imageId) => {
63        const cacheKey = imageId || $anchor[0];
64        const cachedResult = galleryIdCache.get(cacheKey);
65        
66        if (cachedResult) {
67            return cachedResult;
68        }
69
70        let $galleryContainer = null;
71        let galleryId = null;
72
73        // Try closest data-gallery-id first (most common case)
74        $galleryContainer = $anchor.closest('[data-gallery-id]');
75        if ($galleryContainer.length) {
76            galleryId = $galleryContainer.attr('data-gallery-id') || $galleryContainer.data('gallery-id');
77        }
78
79        // Try common gallery container classes
80        if (!galleryId) {
81            $galleryContainer = $anchor.closest('.ngg-galleryoverview, .ngg-imagebrowser, .ngg-slideshow');
82            if ($galleryContainer.length) {
83                galleryId = $galleryContainer.attr('data-gallery-id') ||
84                           $galleryContainer.data('gallery-id') ||
85                           $galleryContainer.attr('data-nextgen-gallery-id') ||
86                           $galleryContainer.data('nextgen-gallery-id');
87            }
88        }
89
90        // Traverse up the DOM tree
91        if (!galleryId) {
92            let $current = $anchor;
93            let depth = 0;
94            
95            while (depth < MAX_DEPTH && $current.length) {
96                if ($current.is('[data-gallery-id]')) {
97                    galleryId = $current.attr('data-gallery-id') || $current.data('gallery-id');
98                    $galleryContainer = $current;
99                    break;
100                }
101                
102                const $sibling = $current.siblings('[data-gallery-id]').first();
103                if ($sibling.length) {
104                    galleryId = $sibling.attr('data-gallery-id') || $sibling.data('gallery-id');
105                    $galleryContainer = $sibling;
106                    break;
107                }
108                
109                $current = $current.parent();
110                depth++;
111            }
112        }
113
114        // Last resort: search all containers
115        if (!galleryId && imageId) {
116            const $allContainers = $('[data-gallery-id]');
117            $allContainers.each(function() {
118                const $container = $(this);
119                if ($container.find($anchor).length > 0) {
120                    galleryId = $container.attr('data-gallery-id') || $container.data('gallery-id');
121                    $galleryContainer = $container;
122                    return false;
123                }
124            });
125        }
126
127        const result = {
128            galleryId: galleryId ? String(galleryId) : null,
129            $galleryContainer
130        };
131
132        // Cache the result (even if null) to avoid repeated traversals
133        galleryIdCache.set(cacheKey, result);
134        return result;
135    };
136
137    /**
138     * Get TikTok data from anchor element
139     * @param {jQuery} $anchor - jQuery anchor element
140     * @param {string|null} imageId - Image ID if available
141     * @returns {Object|null} TikTok data object
142     */
143    const getTikTokDataFromAnchor = ($anchor, imageId) => {
144        if (imageId && window.ngg_tiktok_images?.[imageId]) {
145            return window.ngg_tiktok_images[imageId];
146        }
147
148        const playUrl = $anchor.attr('data-tiktok-play-url');
149        const shareUrl = $anchor.attr('data-tiktok-share-url');
150        const embedUrl = $anchor.attr('data-tiktok-embed-url');
151
152        if (playUrl || shareUrl || embedUrl) {
153            return {
154                playUrl: playUrl || '',
155                shareUrl: shareUrl || '',
156                embedUrl: embedUrl || ''
157            };
158        }
159
160        return null;
161    };
162
163    /**
164     * Handle click events on TikTok image links
165     * @param {Event} e - Click event
166     */
167    const handleTikTokLinkClick = (e) => {
168        const anchor = e.target.closest('a[data-image-id]');
169        if (!anchor) {
170            return;
171        }
172
173        const $anchor = $(anchor);
174        const imageId = $anchor.attr('data-image-id');
175        const tiktokData = getTikTokDataFromAnchor($anchor, imageId);
176
177        if (!tiktokData) {
178            return;
179        }
180
181        const { galleryId } = findGalleryId($anchor, imageId);
182        const tiktokSettings = NggTikTokVideo.getTikTokSettings(galleryId);
183        const linkSetting = String(tiktokSettings.link || '0');
184        const linkTarget = String(tiktokSettings.link_target || '0');
185
186        if (linkSetting !== '1' && linkSetting !== '2') {
187            return;
188        }
189
190        if (linkSetting === '1' && !tiktokData.shareUrl) {
191            return;
192        }
193        
194        if (linkSetting === '2' && !tiktokData.embedUrl && !tiktokData.shareUrl) {
195            return;
196        }
197
198        e.preventDefault();
199        e.stopPropagation();
200
201        const openNewTab = linkTarget === '1' || linkTarget === '_blank';
202        let targetUrl = '';
203
204        if (linkSetting === '1') {
205            targetUrl = tiktokData.shareUrl || '';
206        } else if (linkSetting === '2') {
207            targetUrl = tiktokData.embedUrl || tiktokData.shareUrl || '';
208        }
209
210        if (targetUrl) {
211            if (openNewTab) {
212                window.open(targetUrl, '_blank', 'noopener,noreferrer');
213            } else {
214                window.location.href = targetUrl;
215            }
216        }
217    };
218
219    /**
220     * Initialize link handlers for TikTok images
221     */
222    NggTikTokVideo.initLinkHandlers = () => {
223        if (linkHandlersInitialized) {
224            return;
225        }
226        linkHandlersInitialized = true;
227
228        // Use capture phase to run BEFORE lightbox handlers
229        document.addEventListener('click', handleTikTokLinkClick, true);
230    };
231
232    /**
233     * Check if element is a TikTok image
234     * @param {Element|jQuery} element - Element to check
235     * @returns {boolean} True if element is a TikTok image
236     */
237    NggTikTokVideo.isTikTokImage = (element) => {
238        const $el = $(element);
239        const $anchor = $el.is('a') ? $el : $el.closest('a');
240
241        if ($anchor.attr('data-ngg-tiktok-source') === 'true') {
242            return true;
243        }
244
245        const imageId = $anchor.attr('data-image-id');
246        if (imageId && window.ngg_tiktok_images?.[imageId]) {
247            const $galleryContainer = $anchor.closest('[data-gallery-id]');
248            const galleryId = $galleryContainer.length ? $galleryContainer.attr('data-gallery-id') : null;
249            const settings = NggTikTokVideo.getTikTokSettings(galleryId);
250            return String(settings.link || '0') === '0';
251        }
252
253        return false;
254    };
255
256    /**
257     * Get TikTok data from element
258     * @param {Element|jQuery} element - Element to get data from
259     * @returns {Object|null} TikTok data object
260     */
261    NggTikTokVideo.getTikTokData = (element) => {
262        const $el = $(element);
263        const $anchor = $el.is('a') ? $el : $el.closest('a');
264
265        const playUrl = $anchor.attr('data-ngg-tiktok-play-url');
266        const shareUrl = $anchor.attr('data-ngg-tiktok-share-url');
267        const embedUrl = $anchor.attr('data-ngg-tiktok-embed-url');
268        const tiktokId = $anchor.attr('data-ngg-tiktok-id');
269
270        if (playUrl || shareUrl || embedUrl) {
271            return {
272                tiktokId: tiktokId || '',
273                playUrl: playUrl || '',
274                shareUrl: shareUrl || '',
275                embedUrl: embedUrl || '',
276                linkSetting: '0'
277            };
278        }
279
280        const imageId = $anchor.attr('data-image-id');
281        if (imageId && window.ngg_tiktok_images?.[imageId]) {
282            return window.ngg_tiktok_images[imageId];
283        }
284
285        return null;
286    };
287
288    /**
289     * Get video URL from TikTok data
290     * @param {Object|null} tiktokData - TikTok data object
291     * @returns {string} Video URL
292     */
293    NggTikTokVideo.getVideoUrl = (tiktokData) => {
294        if (!tiktokData) {
295            return '';
296        }
297
298        if (tiktokData.playUrl?.length > 0) {
299            return tiktokData.playUrl;
300        }
301
302        if (tiktokData.shareUrl?.length > 0) {
303            return tiktokData.shareUrl;
304        }
305
306        return '';
307    };
308
309    /**
310     * Get embed URL from TikTok data
311     * @param {Object|null} tiktokData - TikTok data object
312     * @returns {string} Embed URL
313     */
314    NggTikTokVideo.getEmbedUrl = (tiktokData) => {
315        if (!tiktokData) {
316            return '';
317        }
318
319        if (tiktokData.embedUrl) {
320            return tiktokData.embedUrl;
321        }
322
323        if (tiktokData.shareUrl) {
324            const match = tiktokData.shareUrl.match(/video\/(\d+)/);
325            if (match?.[1]) {
326                return `https://www.tiktok.com/embed/v2/${match[1]}`;
327            }
328        }
329
330        return '';
331    };
332
333    /**
334     * Create error message element
335     * @param {string} message - Error message
336     * @returns {jQuery} Error message jQuery element
337     */
338    const createErrorMessage = (message) => {
339        return $('<div class="ngg-tiktok-error"></div>')
340            .text(message)
341            .css({
342                color: '#fff',
343                textAlign: 'center',
344                padding: '20px'
345            });
346    };
347
348    /**
349     * Create video player element
350     * @param {string} videoUrl - Video URL
351     * @param {boolean} autoplay - Autoplay setting
352     * @param {Function} onReady - Ready callback
353     * @param {Function} onError - Error callback
354     * @returns {jQuery} Video jQuery element
355     */
356    const createVideoElement = (videoUrl, autoplay, onReady, onError) => {
357        const $video = $('<video></video>').attr({
358            src: videoUrl,
359            controls: true,
360            autoplay: autoplay,
361            playsinline: true,
362            preload: 'auto'
363        }).css({
364            width: '100%',
365            height: '100%',
366            objectFit: 'contain'
367        });
368
369        $video.on('loadeddata', () => {
370            onReady($video[0]);
371        });
372
373        $video.on('error', () => {
374            $video.remove();
375            onError(new Error('Video failed to load'));
376        });
377
378        return $video;
379    };
380
381    /**
382     * Create player container
383     * @param {Object} options - Player options
384     * @returns {jQuery} Container jQuery element
385     */
386    NggTikTokVideo.createPlayer = (options = {}) => {
387        const {
388            tiktokData,
389            width = DEFAULT_DIMENSIONS.width,
390            height = DEFAULT_DIMENSIONS.height,
391            autoplay = true,
392            onReady = () => {},
393            onError = () => {}
394        } = options;
395
396        const $container = $('<div class="ngg-tiktok-video-container"></div>').css({
397            position: 'relative',
398            width,
399            height,
400            maxWidth: '100%',
401            maxHeight: '100%',
402            display: 'flex',
403            alignItems: 'center',
404            justifyContent: 'center'
405        });
406
407        const videoUrl = NggTikTokVideo.getVideoUrl(tiktokData);
408
409        if (videoUrl && videoUrl.includes('tiktokcdn.com')) {
410            const $video = createVideoElement(videoUrl, autoplay, onReady, (error) => {
411                $container.append(createErrorMessage('Video failed to load'));
412                onError(error);
413            });
414            $container.append($video);
415        } else {
416            $container.append(createErrorMessage('Video not available'));
417            onError(new Error('No video URL available'));
418        }
419
420        return $container;
421    };
422
423    /**
424     * Create embed player
425     * @param {jQuery} $container - Container element
426     * @param {string} embedUrl - Embed URL
427     * @param {number} width - Width
428     * @param {number} height - Height
429     * @param {boolean} autoplay - Autoplay setting
430     * @param {Function} onReady - Ready callback
431     * @param {Function} onError - Error callback
432     */
433    NggTikTokVideo._createEmbedPlayer = ($container, embedUrl, width, height, autoplay, onReady, onError) => {
434        $container.empty();
435
436        if (!embedUrl) {
437            $container.append(createErrorMessage('Video not available'));
438            onError(new Error('No embed URL available'));
439            return;
440        }
441
442        const url = autoplay
443            ? `${embedUrl}${embedUrl.includes('?') ? '&' : '?'}autoplay=1`
444            : embedUrl;
445
446        const $iframe = $('<iframe></iframe>').attr({
447            src: url,
448            frameborder: '0',
449            allowfullscreen: true,
450            allow: 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'
451        }).css({
452            width: '100%',
453            height: '100%',
454            border: 'none'
455        });
456
457        $iframe.on('load', () => {
458            onReady($iframe[0]);
459        });
460
461        $iframe.on('error', () => {
462            onError(new Error('Failed to load TikTok embed'));
463        });
464
465        $container.append($iframe);
466    };
467
468    /**
469     * Replace image with video player
470     * @param {Element|jQuery} imageElement - Image element
471     * @param {Object} options - Player options
472     * @returns {jQuery|null} Player jQuery element or null
473     */
474    NggTikTokVideo.replaceImageWithVideo = (imageElement, options = {}) => {
475        const $image = $(imageElement);
476        const $anchor = $image.closest('a');
477        const tiktokData = options.tiktokData || NggTikTokVideo.getTikTokData($anchor);
478
479        if (!tiktokData) {
480            return null;
481        }
482
483        const width = options.width || $image.width() || DEFAULT_DIMENSIONS.width;
484        const height = options.height || $image.height() || DEFAULT_DIMENSIONS.height;
485
486        const playerOptions = {
487            ...options,
488            tiktokData,
489            width,
490            height
491        };
492
493        const $player = NggTikTokVideo.createPlayer(playerOptions);
494        $image.replaceWith($player);
495        return $player;
496    };
497
498    /**
499     * Create lightbox player
500     * @param {Object} tiktokData - TikTok data object
501     * @param {Object} lightboxOptions - Lightbox options
502     * @returns {jQuery} Player jQuery element
503     */
504    NggTikTokVideo.createLightboxPlayer = (tiktokData, lightboxOptions = {}) => {
505        const {
506            maxWidth = window.innerWidth * 0.8,
507            maxHeight = window.innerHeight * 0.9,
508            onReady,
509            onError
510        } = lightboxOptions;
511
512        let width, height;
513        if (maxHeight * ASPECT_RATIO <= maxWidth) {
514            height = maxHeight;
515            width = height * ASPECT_RATIO;
516        } else {
517            width = maxWidth;
518            height = width / ASPECT_RATIO;
519        }
520
521        return NggTikTokVideo.createPlayer({
522            tiktokData,
523            width: Math.round(width),
524            height: Math.round(height),
525            autoplay: true,
526            onReady,
527            onError
528        });
529    };
530
531    /**
532     * Destroy player and clean up resources
533     * @param {jQuery} $container - Container element
534     */
535    NggTikTokVideo.destroyPlayer = ($container) => {
536        if (!$container?.length) {
537            return;
538        }
539
540        const $video = $container.find('video');
541        if ($video.length) {
542            $video[0].pause();
543            $video.attr('src', '');
544        }
545
546        const $iframe = $container.find('iframe');
547        if ($iframe.length) {
548            $iframe.attr('src', '');
549        }
550
551        $container.remove();
552    };
553
554    // Initialize on DOM ready
555    $(() => {
556        setTimeout(() => {
557            NggTikTokVideo.initLinkHandlers();
558        }, INIT_DELAY);
559    });
560
561})(jQuery, window);

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.