PageSourceSearch

https://staatstheater-hannover.de/media/scripts/ks-lightbox.js

js staatstheater-hannover.de collected 2026-09-24 09:43:05 UTC 34,669 bytes, 991 lines download raw bytes

1
2(function ($) {
3    'use strict';
4
5    /* =========================================
6     KS LIGHTBOX – SHARED CORE
7     Gemeinsamer Stand für Rhein-Kreis Kultur und
8     Staatstheater Hannover. Projektspezifische DOM-
9     Strukturen werden ausschließlich über Selektoren
10     angebunden; die Lightbox-Logik bleibt gemeinsam.
11     ========================================= */
12    if (!$) {
13        return;
14    }
15
16    /* =========================================
17     CONFIG
18     ========================================= */
19
20    var SELECTOR_CONTAINER =
21        '.view_dynamic_media_container, ' +
22        '.media-container-container, ' +
23        '.ensemble-detail-portrait';
24    
25    var SELECTOR_ITEM =
26        '.image-container[data-media-type="picture"], ' +
27        '.owl-media-container-item[data-media-type="picture"], ' +
28        '.ensemble-detail-portraits[data-media-type="picture"]';
29    
30    var SELECTOR_IMAGE = '.media_wrapper img, .image-wrapper img';
31    var SELECTOR_EDITOR_CONTROL =
32            '.cb-flex-editor-button, ' +
33            '.pix-edit-button';
34    var SELECTOR_TINYMCE_IMAGE_IN_ARTICLE =
35            '.cb-tex-image-container img, ' +
36            '.cb-media-tinymce img';
37    var SELECTOR_TINYMCE_IMAGE =
38            '.dynamic-page-article ' + SELECTOR_TINYMCE_IMAGE_IN_ARTICLE.split(', ').join(', .dynamic-page-article ');
39    var DRAG_THRESHOLD = 10;
40    var LIGHTBOX_SWIPE_THRESHOLD = 40;
41    var LIGHTBOX_CLOSE_SWIPE_THRESHOLD = 60;
42    /* =========================================
43     HELPERS
44     ========================================= */
45
46    function escapeHtml(str) {
47        return String(str || '')
48                .replace(/&/g, '&')
49                .replace(/</g, '&lt;')
50                .replace(/>/g, '&gt;')
51                .replace(/"/g, '&quot;')
52                .replace(/'/g, '&#039;');
53    }
54
55    function normalizeText(text) {
56        return String(text || '').replace(/\s+/g, ' ').trim();
57    }
58
59    /* =========================================
60     IMAGE SOURCE
61     ========================================= */
62
63    function getBestImageSrc($img) {
64        if (!$img || !$img.length) {
65            return '';
66        }
67
68        return $img.prop('currentSrc') ||
69                $img.attr('src') ||
70                $img.attr('data-src') ||
71                '';
72    }
73
74    /*
75     * LIGHTBOX IMAGE SOURCE
76     *
77     * Die normale Seite darf fuer ihren jeweiligen Bildplatz eigene
78     * Crop-/Rescale-Parameter und 1x/2x-Groessen verwenden. Die Lightbox
79     * ist davon bewusst entkoppelt und baut aus der Originaldatei eine
80     * eigene, unbeschnittene High-Quality-Variante.
81     *
82     * min_length=1000 verhindert, dass kleine Ausgangsbilder unnoetig
83     * auf die Lightbox-Zielgroesse hochgerechnet werden.
84     */
85    function getLightboxImageSrc($img) {
86        var source = getBestImageSrc($img);
87        var sourceUrl;
88        var originalFile;
89
90        if (!source) {
91            return '';
92        }
93
94        try {
95            sourceUrl = new URL(source, window.location.href);
96        } catch (e) {
97            return source;
98        }
99
100        /*
101         * Nur vorhandene Image-Toolkit-URLs neu aufbauen. Andere Quellen
102         * bleiben unveraendert und funktionieren damit wie bisher.
103         */
104        if (sourceUrl.hostname !== 'imgtoolkit.culturebase.org') {
105            return source;
106        }
107
108        originalFile = sourceUrl.searchParams.get('file');
109        if (!originalFile) {
110            return source;
111        }
112
113        return 'https://imgtoolkit.culturebase.org/' +
114                '?quality=9' +
115                '&file=' + encodeURIComponent(originalFile) +
116                '&do=rescaleIn' +
117                '&width=0' +
118                '&height=2000' +
119                '&min_length=1000';
120    }
121
122    /* =========================================
123     IMAGE LOOKUP
124     Unterstützt unterschiedliche Media-Markups,
125     ohne seitenspezifische Lightbox-Logik.
126     ========================================= */
127
128    function getItemImage($item) {
129        var $img;
130        if (!$item || !$item.length) {
131            return $();
132        }
133
134        /* Bestehende CMS-Strukturen */
135        $img = $item.find(SELECTOR_IMAGE).first();
136        if ($img.length) {
137            return $img;
138        }
139
140        /* Event-Detail: Bild direkt im Picture-Item */
141        $img = $item.children('img').first();
142        if ($img.length) {
143            return $img;
144        }
145
146        /* Letzter generischer Fallback */
147        return $item.find('img').first();
148    }
149
150    function buildCaptionData($item) {
151        var $img = getItemImage($item);
152
153        /*
154         * Oeffentliche Bildinformationen bewusst eng lesen.
155         * Der title-Tag an image-wrapper / img enthaelt fuer eingeloggte
156         * Redakteure interne PixMeta-Kontrolldaten und ist keine Quelle
157         * fuer die oeffentliche Lightbox.
158         */
159        var title = normalizeText(
160                $item.find('.media-container__title .title').first().text()
161                );
162        var copyright = normalizeText(
163                $item.find('.copyright').first().text()
164                );
165
166        var subtitle = normalizeText($item.find('.media-container__subtitle, [data-subtitle], .subtitle').first().text()) ||
167                normalizeText($item.attr('data-subtitle')) ||
168                normalizeText($img.attr('data-subtitle'));
169        var description = normalizeText($item.find('.media-container__description, [data-description], .description').first().text()) ||
170                normalizeText($item.attr('data-description')) ||
171                normalizeText($img.attr('data-description'));
172
173        return {
174            title: title,
175            subtitle: subtitle,
176            description: description,
177            copyright: copyright
178        };
179    }
180
181    function buildCaptionHtml(data) {
182        var parts = [];
183        if (data.title) {
184            parts.push('<div class="ks-lightbox__caption-title">' + escapeHtml(data.title) + '</div>');
185        }
186
187        if (data.subtitle) {
188            parts.push('<div class="ks-lightbox__caption-subtitle">' + escapeHtml(data.subtitle) + '</div>');
189        }
190
191        if (data.description) {
192            parts.push('<div class="ks-lightbox__caption-description">' + escapeHtml(data.description) + '</div>');
193        }
194
195        return parts.join('');
196    }
197
198    function getPictureItems($container) {
199        return $container.find(SELECTOR_ITEM).filter(function () {
200            return !$(this).closest('.owl-item.cloned').length;
201        });
202    }
203
204    function collectItems($container) {
205        var items = [];
206        getPictureItems($container).each(function () {
207            var $item = $(this);
208            var $img = getItemImage($item);
209            var src = getLightboxImageSrc($img);
210            var captionData;
211            if (!src) {
212                return;
213            }
214
215            captionData = buildCaptionData($item);
216            items.push({
217                src: src,
218                alt: $img.attr('alt') || '',
219                captionHtml: buildCaptionHtml(captionData),
220                copyright: captionData.copyright || ''
221            });
222        });
223        return items;
224    }
225
226
227    function buildTinyMceItem(imgEl) {
228        var $img = $(imgEl);
229        var src = getLightboxImageSrc($img);
230        var title = normalizeText($img.attr('title'));
231        var alt = normalizeText($img.attr('alt'));
232        if (!src) {
233            return null;
234        }
235
236        return {
237            src: src,
238            alt: alt,
239            captionHtml: title
240                    ? buildCaptionHtml({
241                        title: title,
242                        subtitle: '',
243                        description: '',
244                        copyright: ''
245                    })
246                    : '',
247            copyright: ''
248        };
249    }
250
251    function collectTinyMceItems(clickedImgEl) {
252        var articleEl;
253        var imageEls;
254        var items = [];
255        var index = 0;
256        if (!clickedImgEl) {
257            return {
258                items: [],
259                index: 0
260            };
261        }
262
263        /*
264         * Nur Bilder desselben CMS-Artikels sammeln.
265         * Mediencontainer / Header / Logos usw. bleiben ausgeschlossen.
266         */
267        articleEl = clickedImgEl.closest('.dynamic-page-article');
268        if (!articleEl) {
269            return {
270                items: [],
271                index: 0
272            };
273        }
274
275        imageEls = Array.from(
276                articleEl.querySelectorAll(SELECTOR_TINYMCE_IMAGE_IN_ARTICLE)
277                );
278        imageEls.forEach(function (imgEl) {
279            var item = buildTinyMceItem(imgEl);
280            if (!item) {
281                return;
282            }
283
284            if (imgEl === clickedImgEl) {
285                index = items.length;
286            }
287
288            items.push(item);
289        });
290        return {
291            items: items,
292            index: index
293        };
294    }
295
296
297    function closestTinyMceImageFromTarget(target) {
298        if (!target || !target.closest) {
299            return null;
300        }
301
302        var imgEl = target.closest('img');
303        if (!imgEl) {
304            return null;
305        }
306
307        if (!imgEl.matches(SELECTOR_TINYMCE_IMAGE)) {
308            return null;
309        }
310
311        return imgEl;
312    }
313
314
315    function closestItemFromTarget(target) {
316        if (!target || !target.closest) {
317            return null;
318        }
319
320        return target.closest(SELECTOR_ITEM);
321    }
322
323    function isMobileLandscape() {
324        return window.matchMedia(
325                '(hover: none) and (pointer: coarse) and (orientation: landscape)'
326                ).matches;
327    }
328
329    /* =========================================
330     MARKUP
331     ========================================= */
332
333    function ensureLightbox() {
334        var $existing = $('#ks-lightbox');
335        if ($existing.length) {
336            return $existing;
337        }
338
339        var html = `
340            <div class="ks-lightbox" id="ks-lightbox" aria-hidden="true">
341                <div class="ks-lightbox__backdrop" data-lightbox-close="true"></div>
342
343                <div class="ks-lightbox__dialog" role="dialog" aria-modal="true" aria-label="Bildansicht">
344                    <button type="button" class="ks-lightbox__close" aria-label="Schließen" data-lightbox-close="true">×</button>
345
346                    <div class="ks-lightbox__counter"></div>
347
348                    <button type="button" class="ks-lightbox__nav ks-lightbox__nav--prev" aria-label="Vorheriges Bild">‹</button>
349                    <button type="button" class="ks-lightbox__nav ks-lightbox__nav--next" aria-label="Nächstes Bild">›</button>
350
351                    <div class="ks-lightbox__stage">
352                        <div class="ks-lightbox__frame">
353                            <div class="ks-lightbox__image-wrap">
354                                <img class="ks-lightbox__image" src="" alt="">
355                                <div class="ks-lightbox__copyright"></div>
356                            </div>
357                        </div>
358                    </div>
359
360                    <div class="ks-lightbox__caption"></div>
361                </div>
362            </div>
363        `;
364        $('body').append(html);
365        return $('#ks-lightbox');
366    }
367
368    /* =========================================
369     API
370     ========================================= */
371
372    var api = {
373        $lightbox: null,
374        items: [],
375        index: 0,
376        lastTrigger: null,
377        touchStartX: 0,
378        touchEndX: 0,
379        touchStartY: 0,
380        touchEndY: 0,
381        lightboxMouseDown: false,
382        lightboxMouseStartX: 0,
383        lightboxMouseStartY: 0,
384        _originalThemeColor: undefined,
385        _frozenScrollY: 0,
386        pointerState: {
387            isDown: false,
388            moved: false,
389            startX: 0,
390            startY: 0,
391            itemEl: null
392        },
393        /* =========================================
394         SCROLL / VIEWPORT
395         ========================================= */
396
397        getMinimumOpenScrollY: function () {
398            var isLandscape = window.innerWidth > window.innerHeight;
399            if (isLandscape) {
400                return Math.max(180, Math.round(window.innerHeight * 0.55));
401            }
402
403            return Math.max(110, Math.round(window.innerHeight * 0.28));
404        },
405        getScrollY: function () {
406            return window.pageYOffset ||
407                    document.documentElement.scrollTop ||
408                    document.body.scrollTop ||
409                    0;
410        },
411        ensureMinimumScrollForLightbox: function (callback) {
412            var self = this;
413            var minY = this.getMinimumOpenScrollY();
414            var attempts = 0;
415            var maxAttempts = 8;
416            var tolerance = 8;
417            function ensure() {
418                var currentY = self.getScrollY();
419                if (currentY >= (minY - tolerance) || attempts >= maxAttempts) {
420                    if (typeof callback === 'function') {
421                        callback();
422                    }
423                    return;
424                }
425
426                attempts += 1;
427                window.scrollTo(0, minY);
428                setTimeout(ensure, 80);
429            }
430
431            ensure();
432        },
433        freezeBackgroundScroll: function () {
434            this._frozenScrollY = this.getScrollY();
435        },
436        unfreezeBackgroundScroll: function () {
437            var scrollY = typeof this._frozenScrollY === 'number' ? this._frozenScrollY : 0;
438            window.scrollTo(0, scrollY);
439        },
440        /* =========================================
441         INIT
442         ========================================= */
443
444        init: function () {
445            this.$lightbox = ensureLightbox();
446            document.querySelectorAll(SELECTOR_TINYMCE_IMAGE).forEach(function (img) {
447                if (!img.hasAttribute('tabindex')) {
448                    img.setAttribute('tabindex', '0');
449                }
450
451                if (!img.hasAttribute('role')) {
452                    img.setAttribute('role', 'button');
453                }
454
455                if (!img.hasAttribute('aria-label')) {
456                    img.setAttribute('aria-label', 'Bild vergrößern');
457                }
458            });
459            this.bindGlobalEvents();
460        },
461        /* =========================================
462         OPEN / CLOSE
463         ========================================= */
464
465
466        openFromTinyMceImage: function (imgEl) {
467            var collection = collectTinyMceItems(imgEl);
468            if (!collection.items.length) {
469                return;
470            }
471
472            this.lastTrigger = imgEl;
473            this.items = collection.items;
474            this.index = collection.index;
475            this.openPreparedItems();
476        },
477        openPreparedItems: function () {
478            if (!this.items.length) {
479                return;
480            }
481
482            document.body.classList.add('ks-lightbox-active');
483            document.documentElement.classList.add('ks-lightbox-active');
484            document.documentElement.style.backgroundColor = '#000000';
485            document.body.style.backgroundColor = '#000000';
486            $('body').addClass('ks-lightbox-open');
487            this.freezeBackgroundScroll();
488            this.$lightbox
489                    .attr('aria-hidden', 'false')
490                    .addClass('is-open');
491            this.render();
492            this.$lightbox.find('.ks-lightbox__close').trigger('focus');
493            this.setThemeColor('#000000');
494        },
495        openFromItem: function (itemEl) {
496            var $item = $(itemEl);
497            var $container = $item.closest(SELECTOR_CONTAINER);
498            var $items = getPictureItems($container);
499            var index = $items.index($item);
500            this.lastTrigger = itemEl;
501            if (index < 0) {
502                index = 0;
503            }
504
505            this.openFromContainer($container, index);
506        },
507        openFromContainer: function ($container, index) {
508            this.items = collectItems($container);
509            this.index = index || 0;
510            this.openPreparedItems();
511        },
512        close: function () {
513            if (document.activeElement) {
514                document.activeElement.blur();
515            }
516
517            $('body').removeClass('ks-lightbox-open');
518            document.body.classList.remove('ks-lightbox-active');
519            document.documentElement.classList.remove('ks-lightbox-active');
520            document.documentElement.style.backgroundColor = '';
521            document.body.style.backgroundColor = '';
522            this.$lightbox
523                    .attr('aria-hidden', 'true')
524                    .removeClass('is-open');
525            this.unfreezeBackgroundScroll();
526            this.restoreThemeColor();
527            if (this.lastTrigger && typeof this.lastTrigger.focus === 'function') {
528                this.lastTrigger.focus();
529            }
530        },
531        setThemeColor: function (color) {
532            var themeMeta = document.querySelector('meta[name="theme-color"]');
533            if (!themeMeta)
534                return;
535            // Originalwert für das spätere Wiederherstellen merken.
536            if (typeof this._originalThemeColor === 'undefined') {
537                this._originalThemeColor = themeMeta.getAttribute('content');
538            }
539
540            // Meta-Element ersetzen; iOS/Safari reagiert darauf zuverlässiger als auf reine Attributänderung.
541            var newMeta = themeMeta.cloneNode();
542            newMeta.setAttribute('content', color);
543            themeMeta.parentNode.replaceChild(newMeta, themeMeta);
544        },
545        restoreThemeColor: function () {
546            var themeMeta = document.querySelector('meta[name="theme-color"]');
547            if (!themeMeta || typeof this._originalThemeColor === 'undefined') {
548                return;
549            }
550
551            var newMeta = themeMeta.cloneNode();
552            newMeta.setAttribute('content', this._originalThemeColor);
553            themeMeta.parentNode.replaceChild(newMeta, themeMeta);
554        },
555        /* =========================================
556         NAVIGATION
557         ========================================= */
558
559        prev: function () {
560            if (!this.items.length) {
561                return;
562            }
563
564            this.index = (this.index - 1 + this.items.length) % this.items.length;
565            this.render();
566        },
567        next: function () {
568            if (!this.items.length) {
569                return;
570            }
571
572            this.index = (this.index + 1) % this.items.length;
573            this.render();
574        },
575        /* =========================================
576         IMAGE WRAP SIZE
577         ========================================= */
578
579        updateImageWrapSize: function () {
580            var $frame = this.$lightbox.find('.ks-lightbox__frame');
581            var $wrap = this.$lightbox.find('.ks-lightbox__image-wrap');
582            var img = this.$lightbox.find('.ks-lightbox__image').get(0);
583            var frameWidth;
584            var frameHeight;
585            var imageRatio;
586            var frameRatio;
587            var width;
588            var height;
589            if (!$frame.length || !$wrap.length || !img || !img.naturalWi
589dth || !img.naturalHeight) {
590                return;
591            }
592
593            frameWidth = $frame.width();
594            frameHeight = $frame.height();
595            if (!frameWidth || !frameHeight) {
596                return;
597            }
598
599            imageRatio = img.naturalWidth / img.naturalHeight;
600            frameRatio = frameWidth / frameHeight;
601            if (imageRatio > frameRatio) {
602                width = frameWidth;
603                height = Math.round(frameWidth / imageRatio);
604            } else {
605                height = frameHeight;
606                width = Math.round(frameHeight * imageRatio);
607            }
608
609            /*
610             * Kleine Bildressourcen niemals über ihre natürliche Pixelgröße
611             * hinaus vergrößern. Das Image Toolkit verhindert mit min_length
612             * bereits das serverseitige Hochskalieren; diese Begrenzung schützt
613             * zusätzlich vor clientseitiger Vergrößerung durch die Lightbox.
614             */
615            if (width > img.naturalWidth || height > img.naturalHeight) {
616                width = img.naturalWidth;
617                height = img.naturalHeight;
618            }
619
620            $wrap.css({
621                width: width + 'px',
622                height: height + 'px'
623            });
624        },
625        render: function () {
626            var self = this;
627            var item = this.items[this.index];
628            var $image = this.$lightbox.find('.ks-lightbox__image');
629            var $wrap = this.$lightbox.find('.ks-lightbox__image-wrap');
630            $wrap.addClass('is-loading');
631            if (!item) {
632                return;
633            }
634
635            this.$lightbox.find('.ks-lightbox__image-wrap').css({
636                width: '',
637                height: ''
638            });
639            $image
640                    .off('load.ksLightbox')
641                    .on('load.ksLightbox', function () {
642                        self.updateImageWrapSize();
643                        window.requestAnimationFrame(function () {
644                            $wrap.removeClass('is-loading');
645                        });
646                    })
647
648                    .attr('src', item.src)
649                    .attr('alt', item.alt || '');
650            if ($image.get(0) && $image.get(0).complete) {
651                self.updateImageWrapSize();
652                window.requestAnimationFrame(function () {
653                    $wrap.removeClass('is-loading');
654                });
655            }
656
657            this.$lightbox.find('.ks-lightbox__counter')
658                    .text((this.index + 1) + '/' + this.items.length);
659            this.$lightbox.find('.ks-lightbox__caption')
660                    .html(item.captionHtml || '');
661            this.$lightbox.find('.ks-lightbox__copyright')
662                    .text(item.copyright || '');
663            this.$lightbox.toggleClass('is-single', this.items.length < 2);
664        },
665        /* =========================================
666         POINTER STATE: THUMBNAIL STRIP
667         ========================================= */
668
669        resetPointerState: function () {
670            if (this.pointerState.itemEl) {
671                $(this.pointerState.itemEl).removeClass('is-dragging');
672            }
673
674            this.pointerState.isDown = false;
675            this.pointerState.moved = false;
676            this.pointerState.startX = 0;
677            this.pointerState.startY = 0;
678            this.pointerState.itemEl = null;
679        },
680        /* =========================================
681         EVENTS
682         ========================================= */
683
684        bindGlobalEvents: function () {
685            var self = this;
686
687            /* =========================================
688             TINYMCE ARTICLE IMAGES
689             ========================================= */
690
691            document.addEventListener('click', function (event) {
692                var imgEl = closestTinyMceImageFromTarget(event.target);
693
694                if (!imgEl || $(event.target).closest('#ks-lightbox').length) {
695                    return;
696                }
697
698                event.preventDefault();
699                event.stopPropagation();
700
701                self.openFromTinyMceImage(imgEl);
702            }, true);
703
704            /* =========================================
705             THUMBNAIL STRIP: CLICK VS DRAG
706             ========================================= */
707
708            document.addEventListener('mousedown', function (event) {
709                var itemEl = closestItemFromTarget(event.target);
710
711                /*
712                 * Nur primaeren Mausklick fuer die Lightbox auswerten.
713                 * Rechtsklick, Mittelklick und Ctrl+Klick (macOS-Kontextmenue)
714                 * muessen fuer Browser-/Inspector-Kontextmenues frei bleiben.
715                 */
716                if (event.button !== 0 || event.ctrlKey) {
717                    self.resetPointerState();
718                    return;
719                }
720
721                if (
722                        !itemEl ||
723                        $(event.target).closest('#ks-lightbox').length ||
724                        $(event.target).closest(SELECTOR_EDITOR_CONTROL).length
725                        ) {
726                    return;
727                }
728
729                self.pointerState.isDown = true;
730                self.pointerState.moved = false;
731                self.pointerState.startX = event.clientX;
732                self.pointerState.startY = event.clientY;
733                self.pointerState.itemEl = itemEl;
734            }, true);
735            document.addEventListener('mousemove', function (event) {
736                var state = self.pointerState;
737                var dx;
738                var dy;
739                if (!state.isDown || !state.itemEl) {
740                    return;
741                }
742
743                dx = Math.abs(event.clientX - state.startX);
744                dy = Math.abs(event.clientY - state.startY);
745                if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
746                    state.moved = true;
747                    $(state.itemEl).addClass('is-dragging');
748                }
749            }, true);
750            document.addEventListener('mouseup', function (event) {
751                var state = self.pointerState;
752                var itemEl = closestItemFromTarget(event.target);
753
754                if (event.button !== 0 || event.ctrlKey) {
755                    self.resetPointerState();
756                    return;
757                }
758
759                if ($(event.target).closest(SELECTOR_EDITOR_CONTROL).length) {
760                    self.resetPointerState();
761                    return;
762                }
763
764                if (!state.isDown || !state.itemEl) {
765                    return;
766                }
767
768                if (!itemEl || itemEl !== state.itemEl || state.moved) {
769                    self.resetPointerState();
770                    return;
771                }
772
773                event.preventDefault();
774                event.stopPropagation();
775                self.openFromItem(state.itemEl);
776                self.resetPointerState();
777            }, true);
778            document.addEventListener('dragstart', function (event) {
779                if ($(event.target).closest('#ks-lightbox').length) {
780                    event.preventDefault();
781                }
782
783                self.resetPointerState();
784            }, true);
785            document.addEventListener('mouseleave', function () {
786                self.resetPointerState();
787            }, true);
788            /* =========================================
789             KEYBOARD
790             ========================================= */
791
792            document.addEventListener('keydown', function (event) {
793                var active = document.activeElement;
794                var tinyMceImg = active && active.matches
795                        ? active.matches(SELECTOR_TINYMCE_IMAGE)
796                        : false;
797                if (tinyMceImg && (event.key === 'Enter' || event.key === ' ')) {
798                    event.preventDefault();
799                    self.openFromTinyMceImage(active);
800                    return;
801                }
802                if (active && $(active).closest(SELECTOR_EDITOR_CONTROL).length) {
803                    return;
804                }
805
806                var itemEl = closestItemFromTarget(active);
807                if (itemEl && (event.key === 'Enter' || event.key === ' ')) {
808                    event.preventDefault();
809                    self.openFromItem(itemEl);
810                    return;
811                }
812
813                if (!self.$lightbox || !self.$lightbox.hasClass('is-open')) {
814                    return;
815                }
816
817                if (event.key === 'Escape') {
818                    self.close();
819                }
820
821                if (event.key === 'ArrowLeft') {
822                    self.prev();
823                }
824
825                if (event.key === 'ArrowRight') {
826                    self.next();
827                }
828            }, true);
829            /* =========================================
830             ORIENTATION CHANGE
831             ========================================= */
832
833            window.addEventListener('orientationchange', function () {
834                if (!self.$lightbox || !self.$lightbox.hasClass('is-open')) {
835                    return;
836                }
837
838                setTimeout(function () {
839                    self.ensureMinimumScrollForLightbox(function () {
840                        self._frozenScrollY = self.getScrollY();
841                        self.updateImageWrapSize();
842                        setTimeout(function () {
843                            self.ensureMinimumScrollForLightbox(function () {
844                                self._frozenScrollY = self.getScrollY();
845                                self.updateImageWrapSize();
846                            });
847                        }, 220);
848                    });
849                }, 160);
850            }, true);
851            window.addEventListener('resize', function () {
852                if (!self.$lightbox || !self.$lightbox.hasClass('is-open')) {
853                    return;
854                }
855
856                self.updateImageWrapSize();
857            }, true);
858            /* =========================================
859             LIGHTBOX BUTTONS
860             ========================================= */
861
862            self.$lightbox.on('click', '[data-lightbox-close="true"]', function (event) {
863                event.preventDefault();
864                event.stopPropagation();
865                self.close();
866            });
867            self.$lightbox.on('click', '.ks-lightbox__nav--prev', function (event) {
868                event.preventDefault();
869                event.stopPropagation();
870                self.prev();
871            });
872            self.$lightbox.on('click', '.ks-lightbox__nav--next', function (event) {
873                event.preventDefault();
874                event.stopPropagation();
875                self.next();
876            });
877            /* =========================================
878             TOUCH LIGHTBOX SWIPE
879             ========================================= */
880
881            self.$lightbox.on('touchstart', '.ks-lightbox__stage', function (event) {
882                var touch = event.originalEvent.touches[0];
883                self.touchStartX = touch.clientX;
884                self.touchStartY = touch.clientY;
885            });
886            self.$lightbox.on('touchend', '.ks-lightbox__stage', function (event) {
887                var touch = event.originalEvent.changedTouches[0];
888                var deltaX;
889                var deltaY;
890                self.touchEndX = touch.clientX;
891                self.touchEndY = touch.clientY;
892                deltaX = self.touchEndX - self.touchStartX;
893                deltaY = self.touchEndY - self.touchStartY;
894                /* =========================================
895                 VERTICAL SWIPE / CLOSE
896                 ========================================= */
897
898                if (
899                        Math.abs(deltaY) >= LIGHTBOX_CLOSE_SWIPE_THRESHOLD &&
900                        Math.abs(deltaY) > Math.abs(deltaX)
901                        ) {
902                    /*
903                     * MOBILE LANDSCAPE:
904                     * Nur Swipe nach unten schließt.
905                     * Swipe nach oben bleibt frei, damit Safari seine
906                     * Browser-UI durch normalen Dokument-Scroll ausblenden kann.
907                     *
908                     * deltaY > 0 = Finger bewegt sich nach unten
909                     * deltaY < 0 = Finger bewegt sich nach oben
910                     */
911                    if (isMobileLandscape()) {
912                        if (deltaY > 0) {
913                            self.close();
914                        }
915
916                        return;
917                    }
918
919                    /*
920                     * MOBILE PORTRAIT / sonstige Touch-Zustände:
921                     * Swipe nach oben ODER unten schließt weiterhin.
922                     */
923                    self.close();
924                    return;
925                }
926
927
928                if (Math.abs(deltaX) >= LIGHTBOX_SWIPE_THRESHOLD && Math.abs(deltaX) > Math.abs(deltaY)) {
929                    if (deltaX > 0) {
930                        self.prev();
931                    } else {
932                        self.next();
933                    }
934                }
935            });
936            /* =========================================
937             DESKTOP LIGHTBOX SWIPE / DRAG
938             ========================================= */
939
940            self.$lightbox.on('mousedown', '.ks-lightbox__stage', function (event) {
941                if (event.button !== 0) {
942                    return;
943                }
944
945                if ($(event.target).closest('.ks-lightbox__close, .ks-lightbox__nav').length) {
946                    return;
947                }
948
949                event.preventDefault();
950                self.lightboxMouseDown = true;
951                self.lightboxMouseStartX = event.clientX;
952                self.lightboxMouseStartY = event.clientY;
953            });
954            document.addEventListener('mousemove', function (event) {
955                if (!self.lightboxMouseDown) {
956                    return;
957                }
958
959                event.preventDefault();
960            }, true);
961            document.addEventListener('mouseup', function (event) {
962                var deltaX;
963                var deltaY;
964                if (!self.lightboxMouseDown) {
965                    return;
966                }
967
968                self.lightboxMouseDown = false;
969                deltaX = event.clientX - self.lightboxMouseStartX;
970                deltaY = event.clientY - self.lightboxMouseStartY;
971                if (Math.abs(deltaX) >= LIGHTBOX_SWIPE_THRESHOLD && Math.abs(deltaX) > Math.abs(deltaY)) {
972                    event.preventDefault();
973                    if (deltaX > 0) {
974                        self.prev();
975                    } else {
976                        self.next();
977                    }
978                }
979            }, true);
980        }
981    };
982
983    /* =========================================
984     START
985     ========================================= */
986
987    $(document).ready(function () {
988        api.init();
989    });
990}
991)(window.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.