PageSourceSearch

https://tri-mag.de/wp-content/plugins/tri-mag-newsticker/assets/js/ticker-pulse.js?ver=2.51.0

js tri-mag.de collected 2026-09-24 09:36:21 UTC 5,748 bytes, 163 lines download raw bytes

1/**
2 * tri-mag Newsticker – anonyme Statistik-Beacons.
3 *
4 * (Seit 2.18.3 unter neutralem Datei-/Endpoint-Namen "pulse": Adblocker-
5 * Filterlisten wie EasyPrivacy blockieren URLs mit "track"/"stats" –
6 * dadurch gingen Zählungen von Besuchern mit Adblocker verloren.)
7 *
8 * Views: Karte ausreichend im Viewport (IntersectionObserver), einmal pro
9 * Seitenaufruf, gesammelt und gebündelt per sendBeacon verschickt. Für
10 * hohe Karten (Bild!) gilt statt der 50-%-Regel eine Mindest-Sichthöhe,
11 * da 50 % einer sehr hohen Karte auf kleinen Displays nie erreicht werden.
12 * Klicks: Links in der Karte (Headline, „Zum Artikel", Quelle) sowie
13 * Video-Poster-Klick und Podcast-Play. Keine Cookies, keine
14 * personenbezogenen Daten – nur Meldungs-IDs.
15 */
16( function () {
17	'use strict';
18
19	var cfg = window.tmnStats || {};
20	if ( ! cfg.endpoint ) {
21		return;
22	}
23
24	var seen  = {};
25	var queue = [];
26
27	function send( type, ids ) {
28		if ( ! ids.length ) {
29			return;
30		}
31		var payload = JSON.stringify( { type: type, ids: ids } );
32		if ( navigator.sendBeacon ) {
33			navigator.sendBeacon( cfg.endpoint, new Blob( [ payload ], { type: 'application/json' } ) );
34		} else {
35			fetch( cfg.endpoint, {
36				method: 'POST',
37				headers: { 'Content-Type': 'application/json' },
38				body: payload,
39				keepalive: true,
40				credentials: 'omit'
41			} ).catch( function () {} );
42		}
43	}
44
45	function flushViews() {
46		if ( queue.length ) {
47			send( 'view', queue.splice( 0 ) );
48		}
49	}
50
51	// ── Views ────────────────────────────────────────────────────────────
52	if ( 'IntersectionObserver' in window ) {
53		var observer = new IntersectionObserver( function ( entries ) {
54			entries.forEach( function ( entry ) {
55				if ( ! entry.isIntersecting ) {
56					return;
57				}
58				// Gesehen = mind. 50 % des Elements sichtbar ODER es
59				// füllt mind. 60 % des Viewports (hohe Karten mit Bild
60				// erreichen auf kleinen Displays nie 50 % Sichtbarkeit)
61				var visible = entry.intersectionRect ? entry.intersectionRect.height : 0;
62				if ( entry.intersectionRatio < 0.5 && visible < 0.6 * window.innerHeight ) {
63					return;
64				}
65				var id = entry.target.getAttribute( 'data-stat' );
66				if ( id && ! seen[ id ] ) {
67					seen[ id ] = true;
68					queue.push( parseInt( id, 10 ) );
69					observer.unobserve( entry.target );
70				}
71			} );
72		}, { threshold: [ 0.1, 0.25, 0.5, 0.75 ] } );
73
74		document.querySelectorAll( '[data-stat]' ).forEach( function ( el ) {
75			observer.observe( el );
76		} );
77
78		// Dynamisch nachgeladene Elemente mitbeobachten – die
79		// Schlagzeilen-Leiste baut ihre Einträge alle 90 s per Poll neu
80		// auf; ohne diesen MutationObserver fielen die neuen Knoten aus
81		// der View-Zählung (bereits gezählte IDs bleiben über `seen`
82		// dedupliziert, es entstehen keine Doppel-Views)
83		if ( 'MutationObserver' in window ) {
84			new MutationObserver( function ( mutations ) {
85				mutations.forEach( function ( m ) {
86					m.addedNodes.forEach( function ( node ) {
87						if ( 1 !== node.nodeType ) {
88							return;
89						}
90						if ( node.hasAttribute && node.hasAttribute( 'data-stat' ) ) {
91							observer.observe( node );
92						}
93						if ( node.querySelectorAll ) {
94							node.querySelectorAll( '[data-stat]' ).forEach( function ( el ) {
95								observer.observe( el );
96							} );
97						}
98					} );
99				} );
100			} ).observe( document.body, { childList: true, subtree: true } );
101		}
102	}
103
104	setInterval( flushViews, 10000 );
105	document.addEventListener( 'visibilitychange', function () {
106		if ( 'hidden' === document.visibilityState ) {
107			flushViews();
108		}
109	} );
110	// pagehide zusätzlich: iOS Safari feuert visibilitychange beim
111	// Schließen/Wegnavigieren nicht zuverlässig – ohne pagehide gingen
112	// dort die Views des letzten Sichtfensters verloren
113	window.addEventListener( 'pagehide', flushViews );
114
115	// ── Klicks ───────────────────────────────────────────────────────────
116	document.addEventListener( 'click', function ( event ) {
117		var wrap = event.target.closest( '[data-stat]' );
118		if ( ! wrap ) {
119			return;
120		}
121		var isLink   = event.target.closest( 'a' );
122		var isPoster = event.target.closest( '.tmn-yt-poster' );
123		// Redaktions-Buttons (Inline-Editor) nicht mitzählen
124		if ( event.target.closest( '.tmn-edit-btn, .tmn-edit-form' ) ) {
125			return;
126		}
127		if ( isLink || isPoster ) {
128			send( 'click', [ parseInt( wrap.getAttribute( 'data-stat' ), 10 ) ] );
129		}
130	}, true );
131
132	// ── News-Show-Plays ──────────────────────────────────────────────────
133	// Player mit data-show="<Attachment-ID>" (Voll- und Kurzfassung sind
134	// getrennte Dateien → getrennte Zählung). Media-Events bubbeln nicht,
135	// deshalb Capture-Listener auf document; einmal pro Element.
136	document.addEventListener( 'play', function ( event ) {
137		var el = event.target;
138		if ( ! el || 'AUDIO' !== el.tagName ) {
139			return;
140		}
141		var id = el.getAttribute( 'data-show' );
142		if ( ! id || el.dataset.tmnCounted ) {
143			return;
144		}
145		el.dataset.tmnCounted = '1';
146		send( 'play', [ parseInt( id, 10 ) ] );
147	}, true );
148
149	// Podcast-Play (einmal pro Element)
150	document.querySelectorAll( '[data-stat] audio' ).forEach( function ( audio ) {
151		var counted = false;
152		audio.addEventListener( 'play', function () {
153			if ( counted ) {
154				return;
155			}
156			counted = true;
157			var wrap = audio.closest( '[data-stat]' );
158			if ( wrap ) {
159				send( 'click', [ parseInt( wrap.getAttribute( 'data-stat' ), 10 ) ] );
160			}
161		} );
162	} );
163} )();

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.