PageSourceSearch

https://dailynorthwestern.com/wp-content/plugins/audio-story-images/js/asi-core.js?ver=1.0.2

js dailynorthwestern.com collected 2026-10-01 10:23:29 UTC 8,945 bytes, 319 lines download raw bytes

1/*global $, jQuery, alert*/
2
3jQuery(document).ready(function ($) {
4
5	"use strict";
6
7	window.AsiController = function () {
8
9		var $images = $('.asi-img');
10
11		/*
12		/ Rename asi-audio tags into audio ones.
13		/ Param: none
14		*/
15		function renameAudioTags() {
16			$('asi-audio').each(function () {
17				var asi_id = $(this).attr('asi-id');
18				$(this).replaceWith('<audio asi-id="' + asi_id + '">' + $(this).html() + '</audio>');
19			});
20		}
21
22		/*
23		/ Set up audio progress tracking (drives the circular indicator via --progress)
24		/ Param: none
25		*/
26		function setupProgressTracking() {
27			$('audio').on('timeupdate', function () {
28				var asi_id = $(this).attr('asi-id');
29				var progress = 0;
30				if (this.duration) {
31					progress = (this.currentTime / this.duration) * 100;
32				}
33				$('.asi-player-icon[asi-id="' + asi_id + '"]').css('--progress', progress + '%');
34			});
35
36			$('audio').on('ended', function () {
37				var asi_id = $(this).attr('asi-id');
38				$('.asi-player-icon[asi-id="' + asi_id + '"]').css('--progress', '0%');
39			});
40		}
41
42		/*
43		/ Add the icons and style them (modern SVG + size/style classes).
44		/ Param: icon_style ( default )
45		*/
46		function addIcons(icon_style) {
47
48			var playSvg = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8 5v14l11-7z"/></svg>';
49			var pauseSvg = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>';
50
51			// Map the 8 historical models onto the 3 style variants
52			var styleClass = '';
53			switch (icon_style.model) {
54			case 'model-2':
55			case 'model-3':
56				styleClass = 'asi-style-solid';
57				break;
58			case 'model-4':
59			case 'model-5':
60				styleClass = 'asi-style-minimal';
61				break;
62			case 'model-6':
63			case 'model-7':
64			case 'model-8':
65				styleClass = 'asi-style-outline';
66				break;
67			default:
68				styleClass = '';
69			}
70
71			var sizeClass = '';
72			switch (icon_style.size) {
73			case 'small':
74				sizeClass = 'asi-size-small';
75				break;
76			case 'medium':
77				sizeClass = 'asi-size-medium';
78				break;
79			case 'big':
80				sizeClass = 'asi-size-big';
81				break;
82			}
83
84			var icon_paused = '<div class="asi-player-icon asi-minus-icon ' + sizeClass + ' ' + styleClass + '">' + playSvg + '</div>';
85			var icon_playing = '<div class="asi-player-icon asi-plus-icon ' + sizeClass + ' ' + styleClass + '">' + pauseSvg + '</div>';
86
87			$images.each(function (index, image) {
88				var asi_id = $(this).attr('asi-id');
89				$(this).attr('asi-id', asi_id);
90				$(icon_paused).insertAfter($(this)).attr('asi-id', asi_id);
91				$(icon_playing).insertAfter($(this)).attr('asi-id', asi_id);
92			});
93
94			if (icon_style.color) {
95				$('.asi-player-icon svg').css('fill', icon_style.color);
96			}
97		}
98
99		/*
100		/ Place the icons
101		/ Param: layout object with .position ( center, top-left, top-right, bottom-left, bottom-right )
102		*/
103		function placeIcons(layout) {
104			var $icons = $('.asi-player-icon');
105			$icons.each(function (index, icon) {
106
107				var asi_id = $(this).attr('asi-id'),
108					$linked_image = $('.asi-img[asi-id=' + asi_id + ']'),
109
110					img_height = $linked_image.height(),
111					img_width = $linked_image.width(),
112
113					img_top = $linked_image.position().top,
114					img_left = $linked_image.position().left,
115
116					padding_vertical = 15,
117					padding_horizontal = 15,
118
119					icon_size = $(this).outerWidth(),
120
121					icon_top = img_top + padding_vertical,
122					icon_bottom = img_top + img_height - padding_horizontal - icon_size,
123					icon_left = img_left + padding_horizontal,
124					icon_right = img_left + img_width + padding_vertical,
125					icon_center_top = img_top + img_height / 2 - icon_size / 2,
126					icon_center_left = img_left + img_width / 2 - icon_size / 2;
127
128				switch (layout.position) {
129				case 'center':
130					$(this).css({
131						top: icon_center_top,
132						left: icon_center_left
133					});
134					break;
135
136				case 'top-left':
137					$(this).css({
138						top: icon_top,
139						left: icon_left
140					});
141					break;
142
143				case 'top-right':
144					$(this).css({
145						top: icon_top,
146						right: icon_right
147					});
148					break;
149
150				case 'bottom-left':
151					$(this).css({
152						top: icon_bottom,
153						left: icon_left
154					});
155					break;
156
157				case 'bottom-right':
158					$(this).css({
159						top: icon_bottom,
160						right: icon_right
161					});
162					break;
163				}
164			});
165		}
166
167		/*
168		/ Play or pause a sound
169		/ Param: asi_id ( integer )
170		*/
171		this.toggleAudio = function (asi_id) {
172			var $icon_minus = $('.asi-minus-icon[asi-id=' + asi_id + ']'),
173				$icon_plus = $('.asi-plus-icon[asi-id=' + asi_id + ']'),
174				$audio = $('audio[asi-id=' + asi_id + ']');
175
176			if (!$audio.hasClass('playing')) {
177				$('audio').trigger('pause').removeClass('playing');
178				$('.asi-plus-icon').hide();
179				$('.asi-minus-icon').show().css('display', 'flex');
180				$('.asi-player-icon').css('--progress', '0%');
181
182				$audio.trigger("play");
183				$icon_minus.hide();
184				$icon_plus.show().css('display', 'flex');
185				$audio.addClass('playing');
186			} else {
187				$audio.trigger("pause");
188				$icon_minus.show().css('display', 'flex');
189				$icon_plus.hide();
190				$audio.removeClass('playing');
191			}
192		};
193
194		/*
195		/ Initialize autoplay functionality using Intersection Observer
196		/ Param: none
197		*/
198		function initAutoplay() {
199
200			if (!window.IntersectionObserver) {
201				return;
202			}
203
204			var $autoplayImages = $('.asi-img[asi-autoplay="true"]');
205
206			if ($autoplayImages.length === 0) {
207				return;
208			}
209
210			var observer = new IntersectionObserver(function (entries) {
211				entries.forEach(function (entry) {
212					if (entry.isIntersecting) {
213						var $img = $(entry.target);
214						var asi_id = $img.attr('asi-id');
215						var $audio = $('audio[asi-id=' + asi_id + ']');
216
217						if ($img.attr('asi-autoplay') === 'true' && !$audio.hasClass('playing')) {
218							setTimeout(function () {
219								$('audio').trigger('pause').removeClass('playing');
220								$('.asi-plus-icon').hide();
221								$('.asi-minus-icon').show().css('display', 'flex');
222
223								var playPromise = $audio[0].play();
224
225								if (playPromise !== undefined) {
226									playPromise.then(function () {
227										var $icon_minus = $('.asi-minus-icon[asi-id=' + asi_id + ']');
228										var $icon_plus = $('.asi-plus-icon[asi-id=' + asi_id + ']');
229										$icon_minus.hide();
230										$icon_plus.show().css('display', 'flex');
231										$audio.addClass('playing');
232									}).catch(function (error) {
233										var $img = $('.asi-img[asi-id=' + asi_id + ']');
234										var $icon = $('.asi-minus-icon[asi-id=' + asi_id + ']');
235
236										$icon.addClass('asi-autoplay-ready');
237
238										$img.attr('data-autoplay-attempted', 'true');
239
240										if (!$('body').hasClass('asi-autoplay-notice-shown')) {
241											$('body').addClass('asi-autoplay-notice-shown');
242
243											var $notice = $('<div class="asi-autoplay-notice">' +
244												'<div class="asi-autoplay-notice-content">' +
245												'<span class="asi-autoplay-notice-icon">🔊</span>' +
246												'<span class="asi-autoplay-notice-text">Click anywhere to enable audio</span>' +
247												'</div>' +
248												'</div>');
249
250											$('body').append($notice);
251
252											setTimeout(function () {
253												$notice.addClass('asi-autoplay-notice-visible');
254											}, 100);
255
256											var removeNotice = function () {
257												$notice.removeClass('asi-autoplay-notice-visible');
258												setTimeout(function () {
259													$notice.remove();
260												}, 300);
261											};
262
263											$(document).one('click touchstart', removeNotice);
264											setTimeout(removeNotice, 5000);
265										}
266									});
267								}
268							}, 200);
269						}
270					}
271				});
272			}, {
273				threshold: 0.5,
274				rootMargin: '0px 0px -50px 0px'
275			});
276
277			$autoplayImages.each(function () {
278				observer.observe(this);
279			});
280
281			// Recover from browser autoplay block: once the user interacts, play the queued ones.
282			$(document).one('click touchstart', function () {
283				$('.asi-img[data-autoplay-attempted="true"]').each(function () {
284					var $img = $(this);
285					var asi_id = $img.attr('asi-id');
286					var $audio = $('audio[asi-id=' + asi_id + ']');
287
288					if ($img[0].getBoundingClientRect().top < window.innerHeight && !$audio.hasClass('playing')) {
289						$audio[0].play().then(function () {
290							var $icon_minus = $('.asi-minus-icon[asi-id=' + asi_id + ']');
291							var $icon_plus = $('.asi-plus-icon[asi-id=' + asi_id + ']');
292							$icon_minus.hide().removeClass('asi-autoplay-ready');
293							$icon_plus.show().css('display', 'flex');
294							$audio.addClass('playing');
295							$img.removeAttr('data-autoplay-attempted');
296						}).catch(function (error) {
297							// Still blocked — give up silently.
298						});
299					}
300				});
301			});
302		}
303
304		this.init = function (options) {
305			var layout = options.layout,
306				icon_style = options.icon_style;
307
308			renameAudioTags();
309			addIcons(icon_style);
310			placeIcons(layout, icon_style);
311			setupProgressTracking();
312			initAutoplay();
313		};
314
315		this.placeIcons = placeIcons;
316
317	};
318
319});

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.