PageSourceSearch

https://mbnovels.com/_static/??-eJytzEEOwjAMRNELEQKIqnSBOItbTHDlGC…O3QdmEZtjlhsEa+vLF0+pHv53E6jcNlul1/HApJCg==

js mbnovels.com collected 2026-10-09 00:29:45 UTC 16,940 bytes, 460 lines download raw bytes

1document.addEventListener("DOMContentLoaded", function(event) {
2    function trpClearWooCartFragments(){
3
4        // clear WooCommerce cart fragments when switching language
5        var trp_language_switcher_urls = document.querySelectorAll(".trp-language-switcher-container a:not(.trp-ls-disabled-language), .trp-language-item:not(.trp-language-item__current)");
6
7        for (var i = 0; i < trp_language_switcher_urls.length; i++) {
8            trp_language_switcher_urls[i].addEventListener("click", function(){
9                if ( typeof wc_cart_fragments_params !== 'undefined' && typeof wc_cart_fragments_params.fragment_name !== 'undefined' ) {
10                    window.sessionStorage.removeItem(wc_cart_fragments_params.fragment_name);
11                }
12            });
13        }
14    }
15
16    trpClearWooCartFragments();
17});
18;
19class BaseSwitcher {
20    constructor(rootEl) {
21        this.root = rootEl;
22        this.list = rootEl?.querySelector('.trp-switcher-dropdown-list') || null;
23        this.isOpen = false;
24        this._pendingFocusOnOpen = false;
25        if (!this.root || !this.list) return;
26
27        if (!this.list.id) {
28            this.list.id = `trp-list-${Math.random().toString(36).slice(2, 9)}`;
29        }
30
31        // Single transitionend handler to drop .is-transitioning and finalize hidden/inert
32        this._onTe = (e) => {
33            if (e.target !== this.list || e.propertyName !== 'max-height') return;
34
35            this.root.classList.remove('is-transitioning');
36
37            if (!this.isOpen) {
38                this.list.hidden = true;
39                this.list.setAttribute('inert', '');
40            } else if (this._pendingFocusOnOpen) {
41                this._pendingFocusOnOpen = false;
42                const first = this.list.querySelector(
43                    '[role="option"], a, button, [tabindex]:not([tabindex="-1"])'
44                );
45                first?.focus?.({ preventScroll: true });
46            }
47        };
48        this.list.addEventListener('transitionend', this._onTe);
49
50        this.collapse();
51        this.setAutoWidth();
52        this.bindKeyboard();
53    }
54
55    /**
56     * Returns true if the list has a non-zero transition duration (for any property).
57     * We use this to decide whether to rely on `transitionend` or fall back to sync behavior.
58     */
59    _hasAnimatedTransition() {
60        if (!this.list) return false;
61
62        const cs = getComputedStyle(this.list);
63        const durationsRaw = cs.transitionDuration || '';
64
65        if (!durationsRaw) return false;
66
67        const durations = durationsRaw
68            .split(',')
69            .map(str => parseFloat(str) || 0);
70
71        return durations.some(d => d > 0);
72    }
73
74    collapse() {
75        this.list.hidden = true;
76        this.list.setAttribute('inert', '');
77        this.setExpanded(false);
78        this.root.classList.remove('is-transitioning');
79    }
80
81    /**
82     * If width is set to auto, fix it to the calculated width + 20px
83     * We do this in order to avoid width shift on hover
84     * */
85    setAutoWidth() {
86        const bonusWidth = 10;
87
88        const cs = getComputedStyle(this.root);
89        const declaredWidth = cs.getPropertyValue('--switcher-width').trim();
90
91        if (declaredWidth === 'auto' && this.root.querySelector('.trp-language-item-name')) { // In case trp-language-item-name is not present, we are in flags only mode - so no auto width sizing is needed
92            const initialWidth = this.root.getBoundingClientRect().width;
93
94            this.root.style.setProperty('--switcher-width', (initialWidth + bonusWidth) + 'px');
95        }
96    }
97
98    setExpanded(open) {
99        const trigger = this.root.querySelector('.trp-language-item__current[role="button"]');
100        const val = String( !!open );
101        trigger?.setAttribute('aria-expanded', val);
102        this.root.classList.toggle('is-open', !!open);
103    }
104
105    setOpen( open, { source = null } = {} ) {
106        if ( !this.root || !this.list || open === this.isOpen ) return;
107
108        const prefersReduced = window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches;
109        const hasTransition  = !prefersReduced && this._hasAnimatedTransition();
110
111        this.isOpen = open;
112
113        // No transitions (0s duration) OR reduced motion: do everything synchronously,
114        if ( !hasTransition ) {
115            if ( open ) {
116                this.list.hidden = false;
117                this.list.removeAttribute( 'inert' );
118                this.setExpanded( true );
119
120                this._pendingFocusOnOpen = ( source?.type === 'keydown' );
121                if ( this._pendingFocusOnOpen ) {
122                    this._pendingFocusOnOpen = false;
123                    const first = this.list.querySelector(
124                        '[role="option"], a, button, [tabindex]:not([tabindex="-1"])'
125                    );
126                    first?.focus?.({ preventScroll: true });
127                }
128            } else {
129                this.setExpanded( false );
130                this.list.hidden = true;
131                this.list.setAttribute( 'inert', '' );
132                this._pendingFocusOnOpen = false;
133            }
134            return;
135        }
136
137        // Animated path: rely on transitionend to remove .is-transitioning
138        if ( open ) {
139            // Must be visible for CSS transition to run
140            this.list.hidden = false;
141            this.list.removeAttribute( 'inert' );
142
143            this._pendingFocusOnOpen = ( source?.type === 'keydown' );
144
145            this.root.classList.add( 'is-transitioning' );
146            // Next frame so browser registers pre-open (max-height: 0) state
147            requestAnimationFrame( () => this.setExpanded( true ) );
148        } else {
149            this.root.classList.add( 'is-transitioning' );
150            this.setExpanded( false );
151        }
152    }
153
154    bindKeyboard() {
155        const trigger = this.root.querySelector('.trp-language-item__current[role="button"]');
156        if ( !trigger ) return;
157
158        trigger.addEventListener('keydown', (e) => {
159            const inList = !!e.target.closest?.('.trp-switcher-dropdown-list');
160
161            if ( e.key === 'Enter' || e.key === ' ' ) {
162                e.preventDefault();
163                this.setOpen(!this.isOpen, { source: e });
164                return;
165            }
166
167            if ( e.key === 'ArrowDown' && !this.isOpen ) {
168                e.preventDefault();
169                this.setOpen(true, { source: e });
170            }
171
172            if ( e.key === 'Escape' ) {
173                this.setOpen(false, { source: e });
174                trigger.focus?.();
175            }
176        });
177    }
178}
179
180class ShortcodeSwitcher extends BaseSwitcher {
181    constructor(wrapper) {
182        const overlay =
183                  wrapper.querySelector('.trp-language-switcher.trp-shortcode-overlay')
184                  || [...wrapper.querySelectorAll('.trp-language-switcher')]
185                      .find(el => el.classList.contains('trp-shortcode-overlay'));
186
187        // Overlay must be interactable; ensure no accidental hidden/inert from server
188        overlay.hidden = false;
189        overlay.removeAttribute('hidden');
190        overlay.removeAttribute('inert');
191        if ('inert' in overlay) overlay.inert = false;
192
193        super(overlay);
194
195        if (!this.root || !this.list) return;
196
197        const control = this.root.querySelector('.trp-language-item__current[role="button"]');
198        if (control && this.list && !control.hasAttribute('aria-controls')) {
199            control.setAttribute('aria-controls', this.list.id);
200        }
201
202        const isClickMode =
203                  this.root.classList.contains('trp-open-on-click') ||
204                  wrapper.dataset.openMode === 'click' ||
205                  wrapper.classList.contains('trp-open-on-click');
206
207        if (isClickMode) {
208            // Click anywhere on overlay EXCEPT inside the list
209            this.root.addEventListener('click', (e) => {
210                const inList = e.target.closest('.trp-switcher-dropdown-list');
211                if (!inList) {
212                    e.preventDefault();
213                    e.stopPropagation();
214                    this.setOpen(!this.isOpen, { source: e });
215                }
216            }, true);
217
218            // Outside click closes
219            this.onDocClick = (evt) => {
220                if (!wrapper.contains(evt.target)) this.setOpen(false, { source: evt });
221            };
222            document.addEventListener('click', this.onDocClick, true);
223
224            // Focus leaving wrapper closes
225            wrapper.addEventListener('focusout', () => {
226                setTimeout(() => {
227                    if (!wrapper.contains(document.activeElement)) {
228                        this.setOpen(false, { source: 'keyboard' });
229                    }
230                }, 0);
231            });
232        } else {
233            // Hover mode on overlay
234            this.root.addEventListener('mouseenter', (e) => this.setOpen(true,  { source: e }));
235            this.root.addEventListener('mouseleave', (e) => this.setOpen(false, { source: e }));
236        }
237    }
238}
239
240class FloaterSwitcher extends BaseSwitcher {
241    constructor(el) {
242        super(el);
243
244        el.addEventListener('mouseenter', (e) => this.setOpen(true,  { source: e }));
245        el.addEventListener('mouseleave', (e) => this.setOpen(false, { source: e }));
246
247        this.onDocClick = (evt) => { if (!el.contains(evt.target)) this.setOpen(false, { source: evt }); };
248        document.addEventListener('click', this.onDocClick, true);
249    }
250}
251
252document.addEventListener('DOMContentLoaded', () => {
253    // Front-end or classic admin
254    initLanguageSwitchers(document);
255
256    // If no wrapper and we are in Gutenberg, watch for async SSR insert
257    if (
258        inGutenberg() &&
259        !getEditorDoc().querySelector(WRAPPER)
260    ) {
261        observeWrapperUntilFound();
262    }
263
264    if ( !inGutenberg() )
265        observeShortcodeSwitcher();
266});
267
268/** For shortcode switcher
269 *  Mark the shortcodes that were initialized
270 * */
271const TRP_BOUND = new WeakSet();
272const mark = (el) => TRP_BOUND.add(el);
273const isMarked = (el) => TRP_BOUND.has(el);
274
275const WRAPPER = '.trp-shortcode-switcher__wrapper';
276const OVERLAY = '.trp-language-switcher:not(.trp-opposite-button):not(.trp-ald-language-selector)';
277
278// Helpers
279function inGutenberg() {
280    return document.body?.classList?.contains('block-editor-page')
281        || !!(window.wp?.data?.select?.('core/block-editor'));
282}
283
284// If editor uses an iframe canvas, work inside it
285function getEditorDoc() {
286    const ifr = document.querySelector('iframe[name="editor-canvas"], .editor-canvas__iframe');
287
288    return (ifr && ifr.contentDocument) ? ifr.contentDocument : document;
289}
290
291function initLanguageSwitchers(root = document) {
292    const floater = root.querySelector(
293        '.trp-language-switcher.trp-ls-dropdown:not(.trp-shortcode-switcher):not(.trp-opposite-language)'
294    );
295
296    if (floater)
297        new FloaterSwitcher(floater);
298
299    root.querySelectorAll(WRAPPER)
300        .forEach(wrapper => {
301            const overlay = wrapper.querySelector(OVERLAY);
302
303            if (overlay && !isMarked(overlay)) {
304                mark(overlay);
305                new ShortcodeSwitcher(wrapper);
306            }
307        });
308}
309
310/**
311 * Observes the document for dynamically inserted shortcode switchers and initializes them automatically when detected.
312 */
313function observeShortcodeSwitcher() {
314    const initWrapper = ( wrapper ) => {
315        if ( !wrapper )
316            return;
317
318        const overlay = wrapper.querySelector( OVERLAY );
319
320        if ( !overlay || isMarked( overlay ) )
321            return;
322
323        mark( overlay );
324
325        new ShortcodeSwitcher( wrapper );
326    }
327
328    const mo = new MutationObserver( ( mutations ) => {
329        for ( const m of mutations ) {
330            for ( const n of m.addedNodes ) {
331                if ( n.nodeType !== 1 )
332                    continue;
333
334                if ( n.matches?.( WRAPPER ) )
335                    initWrapper( n );
336
337                n.querySelectorAll?.( WRAPPER ).forEach( initWrapper );
338            }
339        }
340    });
341
342    mo.observe( document, { childList: true, subtree: true } );
343}
344
345/**
346 * Observe Gutenberg for the shortcode wrapper being inserted asynchronously.
347 *
348 * Supports both Blocks API v2 (no editor iframe; wrapper appears in the outer document)
349 * and Blocks API v3 (editor content rendered inside an iframe canvas).
350 *
351 * Strategy:
352 *  1) Check the current editor document for `.trp-shortcode-switcher__wrapper` and init immediately.
353 *  2) If an editor canvas iframe exists, watch its document (and reattach on iframe load) for the wrapper.
354 *  3) If no iframe yet, watch the outer document for either the iframe (v3) or the wrapper itself (v2).
355 *
356 * Initialization is performed once per context to avoid duplicate bindings.
357 */
358function observeWrapperUntilFound() {
359    // If wrapper already exists in current editor doc, init
360    const edDoc = getEditorDoc();
361    const existing = edDoc.querySelector(WRAPPER);
362
363    if ( existing ) {
364        initLanguageSwitchers( edDoc );
365        return;
366    }
367
368    // Helper to locate the editor canvas iframe in the OUTER document
369    const findCanvasIframe = () => document.querySelector('iframe[name="editor-canvas"], .editor-canvas__iframe');
370
371    // If iframe is already present in the outer doc, start watching inside it
372    const iframeNow = findCanvasIframe();
373    if ( iframeNow ) {
374        watchIframe( iframeNow );
375        return;
376    }
377
378    // Otherwise, observe the OUTER document until the iframe appears
379    const outerMO = new MutationObserver( ( mutations ) => {
380        for ( const m of mutations ) {
381            for ( const n of m.addedNodes ) {
382                if ( n.nodeType !== 1 ) continue;
383
384                const iframe =
385                          n.matches?.('iframe[name="editor-canvas"], .editor-canvas__iframe')
386                              ? n
387                              : n.querySelector?.('iframe[name="editor-canvas"], .editor-canvas__iframe');
388
389                if ( iframe ) {
390                    outerMO.disconnect();
391                    watchIframe( iframe );
392                    return;
393                }
394
395                // Also catch shortcode wrapper added directly to the outer document (API v2, no iframe)
396                const wrapper =
397                          n.matches?.(WRAPPER)
398                              ? n
399                              : n.querySelector?.(WRAPPER);
400
401                if ( wrapper ) {
402                    outerMO.disconnect();
403                    initLanguageSwitchers( document );
404                    return;
405                }
406
407            }
408        }
409    } );
410    outerMO.observe( document, { childList: true, subtree: true } );
411
412    function watchIframe( iframe ) {
413        // Try immediately (some builds inject srcdoc synchronously)
414        tryAttachInside();
415
416        // Also on load/navigate (Gutenberg may reload the canvas)
417        iframe.addEventListener( 'load', tryAttachInside );
418
419        function tryAttachInside() {
420            let doc;
421            try {
422                doc = iframe.contentDocument || iframe.contentWindow?.document;
423            } catch (e) {
424                console.warn('Cannot access iframe content due to cross-origin restrictions', e);
425                return;
426            }
427            if ( !doc ) return;
428
429            // If wrapper is already there, init once and stop.
430            const hit = doc.querySelector(WRAPPER);
431            if ( hit ) {
432                initLanguageSwitchers( doc );
433                return;
434            }
435
436            // Observe INSIDE the iframe until wrapper appears
437            const innerMO = new MutationObserver( ( muts ) => {
438                for ( const mm of muts ) {
439                    for ( const nn of mm.addedNodes ) {
440                        if ( nn.nodeType !== 1 ) continue;
441                        if (
442                            nn.matches?.(WRAPPER) ||
443                            nn.querySelector?.(WRAPPER)
444                        ) {
445                            innerMO.disconnect();
446                            initLanguageSwitchers( doc );
447                            return;
448                        }
449                    }
450                }
451                if ( doc.querySelector(WRAPPER) ) {
452                    innerMO.disconnect();
453                    initLanguageSwitchers( doc );
454                }
455            } );
456
457            innerMO.observe( doc, { childList: true, subtree: true } );
458        }
459    }
460};

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.