PageSourceSearch

https://www.perlesandco.com/templates/template2017/js/plugins/galleryPAC.js?v=2.0.2.106

js perlesandco.com collected 2026-09-25 19:08:39 UTC 9,268 bytes, 324 lines download raw bytes

1(function ($) {
2	$.fn.galleryPAC = function () {
3		const $container = $(this);
4
5		// Conserve l'ordre d'apparition
6		const $els = $container.find('[data-src]');
7		const items = new Array($els.length);
8		let currentIndex = 0;
9		let $modal, $main, $thumbs, $arrowPrev, $arrowNext, $closeBtn, $360Folder;
10		let isOrbitvuActive = false;
11
12		// HEAD pour récupérer le MIME. Fallback silencieux si CORS/erreur.
13		const headMime = async (url) => {
14			try {
15				const res = await fetch(url, { method: 'HEAD' });
16				return res.headers.get('Content-Type');
17			} catch (_) {
18				return null;
19			}
20		};
21
22		const looksLikeVideo = (src) =>
23			/\.(mp4|webm|ogg|mov|m4v)$/i.test(src) ||
24			src.includes('youtube.com') ||
25			src.includes('youtu.be') ||
26			src.includes('vimeo.com') ||
27			src.includes('dailymotion.com') ||
28			src.includes('dai.ly');
29
30		const detectType = async (src, $el) => {
31			if (looksLikeVideo(src)) return 'video';
32
33			const mime = await headMime(src);
34			if (mime && mime.toLowerCase().includes('application/pdf')) return 'pdf';
35
36			const declared = ($el.attr('data-type') || '').toLowerCase();
37			if (declared === 'pdf') return 'pdf';
38			if (/\.pdf(\?|#|$)/i.test(src)) return 'pdf';
39
40			if (declared === '360') return '360';
41
42			return 'image';
43		};
44
45		const init = async () => {
46			const tasks = [];
47
48			$els.each(function (i) {
49				const $el = $(this);
50				const src = $el.data('src');
51				const thumb =
52					$el.find('img').attr('src') ||
53					$el.find('source').attr('src') ||
54					null;
55
56				const p = detectType(src, $el).then((type) => {
57					items[i] = { type, src, thumb };
58				});
59				tasks.push(p);
60			});
61
62			await Promise.all(tasks);
63			bindTriggersInOrder();
64		};
65
66		const buildModal = () => {
67			$modal = $(`
68				<div class="gallerypac-modal">
69					<div class="gallerypac-content">
70						<span class="gallerypac-close">&times;</span>
71						<div class="gallerypac-arrow prev">&#10094;</div>
72						<div class="gallerypac-main"></div>
73						<div class="gallerypac-arrow next">&#10095;</div>
74						<div class="gallerypac-thumbs"></div>
75					</div>
76				</div>
77			`);
78			$('body').append($modal);
79			$main = $modal.find('.gallerypac-main');
80			$thumbs = $modal.find('.gallerypac-thumbs');
81			$arrowPrev = $modal.find('.gallerypac-arrow.prev');
82			$arrowNext = $modal.find('.gallerypac-arrow.next');
83			$closeBtn = $modal.find('.gallerypac-close');
84
85			$modal.on('click', function (e) {
86				const $t = $(e.target);
87				const isInsideAllowed =
88					$t.closest('.gallerypac-arrow').length > 0 ||
89					$t.closest('.gallerypac-thumbs .thumb').length > 0 ||
90					$t.closest('.gallerypac-360-container').length > 0 ||
91					$t.closest('.gallerypac-main > *').length > 0 ||
92					$t.closest('.gallerypac-close').length > 0;
93				if (!isInsideAllowed) closeModal();
94			});
95
96			$closeBtn.on('click', () => closeModal());
97			$arrowPrev.on('click', () =>
98				showItem((currentIndex - 1 + items.length) % items.length)
99			);
100			$arrowNext.on('click', () =>
101				showItem((currentIndex + 1) % items.length)
102			);
103
104			let startX = 0;
105			$main.on('touchstart', (e) => {
106				if (isOrbitvuActive) return;
107				if ($(e.target).closest('#picture360, .ovviosautorotate, .orbitvu').length) return;
108				startX = e.originalEvent.touches[0].clientX;
109			});
110			$main.on('touchend', (e) => {
111				if (isOrbitvuActive) return;
112
113				if ($(e.target).closest('#picture360, .ovviosautorotate, .orbitvu').length) return;
114
115				const endX = e.originalEvent.changedTouches[0].clientX;
116				if (endX - startX > 50)
117					showItem((currentIndex - 1 + items.length) % items.length);
118				else if (startX - endX > 50)
119					showItem((currentIndex + 1) % items.length);
120			});
121
122			$(document).on('touchmove', '.gallerypac-360-container', function (e) {
123				e.stopPropagation();
124			});
125		};
126
127		const renderItem = (item) => {
128			if (item.type === 'video') {
129				if (item.src.includes('youtube.com') || item.src.includes('youtu.be')) {
130					let videoId = '';
131					const regexList = [
132						/\/embed\/([a-zA-Z0-9_-]+)/,
133						/watch\?v=([a-zA-Z0-9_-]+)/,
134						/shorts\/([a-zA-Z0-9_-]+)/,
135						/youtu\.be\/([a-zA-Z0-9_-]+)/
136					];
137					for (const r of regexList) {
138						const m = item.src.match(r);
139						if (m && m[1]) { videoId = m[1]; break; }
140					}
141					if (videoId) {
142						const autoplayParam =
143							(typeof window.mobileCheck === 'function' && window.mobileCheck()) ? '' : 'auto
143play=1&';
144						return `
145							<div class="gallerypac-video-container">
146							<div class="gallerypac-video-subcontainer">
147								<iframe
148									src="https://www.youtube-nocookie.com/embed/${videoId}?${autoplayParam}rel=0&controls=1&enablejsapi=1&fs=1&playsinline=1"
149									frameborder="0"
150									allow="autoplay; encrypted-media; fullscreen; picture-in-picture"
151									allowfullscreen
152									loading="eager"
153								></iframe>
154							</div></div>`;
155					}
156				}
157				if (item.src.includes('vimeo.com')) {
158					const vimeoId = item.src.split('/').pop();
159					return `
160						<div class="gallerypac-video-container">
161						<div class="gallerypac-video-subcontainer">
162							<iframe
163								src="https://player.vimeo.com/video/${vimeoId}?autoplay=1"
164								frameborder="0"
165								allow="autoplay; fullscreen"
166								allowfullscreen
167							></iframe>
168						</div></div>`;
169				}
170				return `<video controls autoplay src="${item.src}" class="gallerypac-fullmedia"></video>`;
171			}
172
173			if (item.type === 'pdf') {
174				return `
175					<div class="gallerypac-pdf-container">
176						<iframe
177							src="${item.src}#toolbar=0&navpanes=0&scrollbar=0"
178							frameborder="0"
179							class="gallerypac-pdf-frame"
180							allowfullscreen
181						></iframe>
182					</div>`;
183			}
184
185			if (item.type === '360') {
186				return `
187					<div class="gallerypac-360-container">
188						<div id="picture360"></div>
189					</div>`;
190			}
191
192			return `<img src="${item.src}" class="gallerypac-fullmedia" />`;
193		};
194
195		const showItem = (index) => {
196			currentIndex = index;
197			isOrbitvuActive = false;
198			$main.empty();
199			setTimeout(() => {
200				$main.html(renderItem(items[index]));
201
202				// INIT ORBITVU
203				if (items[index].type === '360') {
204					isOrbitvuActive = true;
205					inject_orbitvu(
206						'picture360',
207						'/templates/template2017/js/plugins/orbitvu/orbitvu.js',
208						'',
209						{
210							ovus_folder: items[index].src,
211							width: "100%",
212							content2: "yes",
213							height: "100%",
214							partial_load: "yes",
215							auto_rotate_dir: "right",
216							rotate_dir: "right",
217							teaser: "onerotationslow",
218							disable_nosize: "yes",
219							dynamic_avif_webp_ext: "yes"
220						}
221					);
222				}
223
224				$thumbs.find('.thumb').removeClass('active');
225				$thumbs.find(`[data-index="${index}"]`).addClass('active');
226
227				// Hook générique : permet à un consommateur d'injecter un overlay
228				// (ex: bouton "Acheter le matériel") en lisant le DOM source via index.
229				$container.trigger('gallerypac:show', [index, items[index], $modal]);
230			}, 10);
231		};
232
233		const closeModal = () => {
234			$modal.fadeOut(200, () => {
235				$modal.remove();
236				$('body').css('overflow', '');
237			});
238		};
239
240		const openModal = (index) => {
241			if (!$('.gallerypac-modal').length) buildModal();
242			else $modal.fadeIn();
243
244			$('body').css('overflow', 'hidden');
245			$main.empty();
246			$thumbs.empty();
247
248			const hasThumbs = items.some(it => !!it.thumb);
249			const $content = $modal.find('.gallerypac-content');
250
251			// Cacher flèches si pas de thumbs ou qu'un seul élément
252			if (!hasThumbs || items.length <= 1) {
253				$arrowPrev.hide();
254				$arrowNext.hide();
255			} else {
256				$arrowPrev.show();
257				$arrowNext.show();
258			}
259
260			if (!hasThumbs) {
261				$thumbs.hide();
262				$content.removeClass('gallerypac-has-thumbs-right');
263			} else {
264				if (window.innerWidth > 768) $content.addClass('gallerypac-has-thumbs-right');
265				else $content.removeClass('gallerypac-has-thumbs-right');
266
267				items.forEach((item, i) => {
268					if (item.thumb) {
269						let $t;
270
271						if (item.type === '360') {
272							// $t = $(`
273							// 	<div class="thumb thumb-360" data-index="${i}">
274							// 		<img class="thumb" src="${item.thumb}" />
275							// 	</div>
276							// `);
277							$t = $(`
278								<img class="thumb" src="/images/360.png" data-index="${i}" />
279							`);
280						} else {
281							$t = $(`
282								<img class="thumb" src="${item.thumb}" data-index="${i}" />
283							`);
284						}
285
286						if (i === index) $t.addClass('active');
287						$t.on('click', () => showItem(i));
288						$thumbs.append($t);
289					}
290				});
291				$thumbs.show();
292			}
293
294			showItem(index);
295			$modal.fadeIn();
296		};
297
298		const bindTriggersInOrder = () => {
299			// Si le container porte data-manual-bind, on n'attache PAS le clic
300			// automatique. Le consommateur (ex: products.js) déclenche la modale
301			// via $element.trigger('manualBindGallery') sur l'item de son choix.
302			const manualBind = !!$container.data('manualBind');
303
304			$els.each(function (i) {
305				const $el = $(this);
306
307				// Déclencheur custom toujours présent (utilisable même en mode auto)
308				$el.off('manualBindGallery.gallerypac')
309					.on('manualBindGallery.gallerypac', () => openModal(i));
310
311				if (!manualBind)
312					$el.css('cursor', 'pointer')
313						.off('click.gallerypac')
314						.on('click.gallerypac', () => openModal(i));
315			});
316		};
317
318		init();
319	};
320
321	$('.galleryPAC').each(function () {
322		$(this).galleryPAC();
323	});
324})(jQuery);

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.