1/** 2 * MOM Soft-Navigation (Player V3, Stage 2) 3 * --------------------------------------------------------------------------- 4 * Lückenlose Wiedergabe beim Seitenwechsel: solange Musik läuft, werden 5 * interne Link-Klicks abgefangen, die Zielseite per fetch geladen und nur 6 * #totalcontent getauscht â das <audio>-Element (und der Mini-Player im 7 * Header) leben weiter. JEDER Fehler fällt auf normale Navigation zurück. 8 * 9 * Kill-Switch: Cookie mom_softnav=0. 10 */ 11(function (window, $) { 12 'use strict'; 13 if (!window.MOM || !window.MOM.audio) return; 14 if (window.MOM.softnav) return; 15 16 var busy = false; 17 18 function enabled() { 19 return document.cookie.indexOf('mom_softnav=0') === -1; 20 } 21 22 function playing() { 23 try { return !window.MOM.audio.element().paused; } catch (e) { return false; } 24 } 25 26 /* ------------------------------------------------------------ Klick-Filter */ 27 var SKIP_PATH = /(\/account\/logout|\/player\/(download|deliver)|\/register\/login_fb)/; 28 var SKIP_EXT = /\.(pdf|zip|mp3|jpe?g|png|gif|svg|xml|rss|css|js)(\?|$)/i; 29 30 function interceptable(a, e) { 31 if (e.defaultPrevented) return false; // andere Handler (Popups etc.) haben übernommen 32 if (e.button !== 0 || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return false; 33 var href = a.getAttribute('href'); 34 if (!href || href.charAt(0) === '#') return false; 35 if (/^(javascript:|mailto:|tel:)/i.test(href)) return false; 36 if (a.hasAttribute('download')) return false; 37 var target = a.getAttribute('target'); 38 if (target && target !== '_self') return false; 39 if (a.getAttribute('onclick')) return false; // Inline-Handler (openPlayer/openInlinebox â¦) 40 var url; 41 try { url = new URL(a.href, window.location.href); } catch (err) { return false; } 42 if (url.origin !== window.location.origin) return false; 43 if (SKIP_PATH.test(url.pathname)) return false; 44 if (SKIP_EXT.test(url.pathname)) return false; 45 if (url.search.indexOf('ajax_load') !== -1) return false; 46 if (url.href === window.location.href) return false; 47 return true; 48 } 49 50 document.addEventListener('click', function (e) { 51 if (!enabled() || !playing() || busy) return; 52 var a = e.target && e.target.closest ? e.target.closest('a[href]') : null; 53 if (!a || !interceptable(a, e)) return; 54 e.preventDefault(); 55 softLoad(a.href, true); 56 }, false); 57 58 window.addEventListener('popstate', function () { 59 if (!enabled()) return; 60 if (playing() && !busy) softLoad(window.location.href, false); 61 else window.location.reload(); 62 }); 63 64 /* ------------------------------------------------------- Assets nachladen */ 65 function loadedUrls(selector, attr) { 66 var set = {}; 67 document.querySelectorAll(selector).forEach(function (el) { 68 try { set[new URL(el.getAttribute(attr), window.location.href).href] = true; } catch (e) {} 69 }); 70 return set; 71 } 72 73 function injectAssets(doc) { 74 // Stylesheets: fehlende einfach anhängen (Reihenfolge unkritisch für Zusatz-Styles) 75 var haveCss = loadedUrls('link[rel="stylesheet"]', 'href'); 76 doc.querySelectorAll('link[rel="stylesheet"][href]').forEach(function (l) { 77 var abs; 78 try { abs = new URL(l.getAttribute('href'), window.location.href).href; } catch (e) { return; } 79 if (!haveCss[abs]) { 80 var el = document.createElement('link'); 81 el.rel = 'stylesheet'; el.href = abs; 82 document.head.appendChild(el); 83 haveCss[abs] = true; 84 } 85 }); 86 87 // Skripte (head + body, z. B. js_library/jcarousel, Play-Page-Module): 88 // fehlende sequenziell laden, damit Abhängigkeiten stimmen 89 var haveJs = loadedUrls('script[src]', 'src'); 90 var queue = []; 91 doc.querySelectorAll('script[src]').forEach(function (s) { 92 var abs; 93 try { abs = new URL(s.getAttribute('src'), window.location.href).href; } catch (e) { return; } 94 if (!haveJs[abs]) { queue.push(abs); haveJs[abs] = true; } 95 }); 96 97 return queue.reduce(function (p, src) { 98 return p.then(function () { 99 return new Promise(function (resolve, reject) { 100 var el = document.createElement('script'); 101 el.src = src; 102 el.onload = resolve; 103 el.onerror = reject; 104 document.head.appendChild(el); 105 }); 106 }); 107 }, Promise.resolve()); 108 } 109 110 /* ------------------------------------- Inline-Skripte im Content ausführen */ 111 function execScriptsIn(container) { 112 var scripts = container.querySelectorAll('script'); 113 return Array.prototype.reduce.call(scripts, function (p, old) { 114 return p.then(function () { 115 return new Promise(function (resolve, reject) { 116 var el = document.createElement('script'); 117 for (var i = 0; i < old.attributes.length; i++) 118 el.setAttribute(old.attributes[i].name, old.attributes[i].value); 119 if (old.src) { 120 el.onload = resolve; el.onerror = reject; 121 old.parentNode.replaceChild(el, old); 122 } else { 123 el.textContent = old.textContent; 124 old.parentNode.replaceChild(el, old); 125 resolve(); 126 } 127 }); 128 }); 129 }, Promise.resolve()); 130 } 131 132 /* -------------------------------------------------------------- Ladebalken */ 133 function progress(on) { 134 document.documentElement.classList.toggle('mom-softnav-loading', !!on); 135 } 136 137 /* --------------------------------------------------------------- Kernflow */ 138 function softLoad(url, push) { 139 busy = true; 140 progress(true); 141 142 fetch(url, { credentials: 'same-origin', redirect: 'follow', headers: { 'X-MOM-SoftNav': '1' } }) 143 .then(function (r) { 144 if (!r.ok) throw new Error('http ' + r.status); 145 var ct = r.headers.get('content-type') || ''; 146 if (ct.indexOf('text/html') === -1) throw new Error('not html'); 147 // Redirects übernehmen (z. B. forward()) â aber NIE cross-origin weiterverarbeiten 148 if (r.url) { 149 if (new URL(r.url, window.location.href).origin !== window.location.origin) 150 throw new Error('cross-origin redirect'); 151 url = r.url; 152 } 153 return r.text(); 154 }) 155 .then(function (html) { 156 var doc = new DOMParser().parseFromString(html, 'text/html'); 157 var fresh = doc.getElementById('totalcontent'); 158 if (!fresh) throw new Error('no totalcontent'); 159 160 return injectAssets(doc).then(function () { 161 var cur = document.getElementById('totalcontent'); 162 if (!cur) throw new Error('no local totalcontent'); 163 164 // altes Play-Page-Modul sauber lösen (Waveform-Listener etc.) 165 if (window.MOM.playPage && window.MOM.playPage.teardown) { 166 try { window.MOM.playPage.teardown(); } catch (e) {} 167 } 168 169 // Vertrauensgrenze: fresh stammt aus einem same-origin-Fetch unserer 170 // EIGENEN serverseitig gerenderten Seite (Origin + Redirect geprüft) â 171 // identisches Vertrauensniveau wie eine normale Voll-Navigation. 172 cur.innerHTML = fresh.innerHTML; 173 document.title = doc.title; 174 175 // Meta-Description/Canonical bewusst nicht synchronisiert (SPA-Zwischenzustand) 176 177 return execScriptsIn(cur).then(function () { 178 // Seiten-Init der NEUEN Seite ausführen (definiert __momPageInit neu 179 // und ruft es via jQuery(fn) sofort auf, da DOM bereits ready ist). 180 // Vertrauensgrenze: das Skript ist der vom eigenen Server emittierte 181 // #mom-page-init-Block der Zielseite (same-origin geprüft) â bei einer 182 // normalen Navigation würde der Browser exakt denselben Code ausführen. 183 // Es wird nichts in den Skripttext interpoliert. 184 var init = doc.getElementById('mom-page-init'); 185 if (init && init.textContent) { 186 (new Function(init.textContent))(); 187 } else if (window.__momPageInit) { 188 window.__momPageInit = function () {}; 189 } 190 }); 191 }).then(function () { 192 if (push) { 193 try { window.history.pushState({ momSoft: 1 }, '', url); } catch (e) {} 194 } 195 window.scrollTo(0, 0); 196 // Quick-Messenger sofort füllen: die Box kam frisch mit dem Swap und 197 // zeigt sonst bis zum nächsten 15s-Cron-Tick nur den Lade-Spinner 198 try { if (window.__momCron && typeof window.browserCron_ajax === 'function') window.browserCron_ajax(); } catch (e) {} 199 progress(false); 200 busy = false;
201 }); 202 }) 203 .catch(function () { 204 progress(false); 205 busy = false; 206 window.location.href = url; // Fallback: harte Navigation 207 }); 208 } 209 210 window.MOM.softnav = { load: softLoad, enabled: enabled }; 211 212})(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.