PageSourceSearch

https://classycg.com/static/js/aten/course.js?1790861607

js classycg.com collected 2026-10-08 06:19:36 UTC 14,094 bytes, 540 lines download raw bytes

1$(function()
2{
3	// 如果目前有播放器的話
4	// 紀錄播放速度
5	var section_idx						= $('#course_video').data('section-idx');
6	var player_type						= $('#course_video').data('type');
7
8	var player							= null;
9	if(player_type=='vimeo')
10		player							= new Vimeo.Player('course_video');
11	if(player_type=='youtube'
12			|| player_type=='aten'
13	)
14	{
15		player							= videojs('course_video');
16		player.hlsQualitySelector();
17	}
18
19	if (player_type == 'aten')
20	{
21		player.ready(function () {
22			// 拿到原本的 fullscreen 按鈕
23			const fsToggle = player.controlBar.fullscreenToggle;
24
25			// 移除原本的 click handler
26			fsToggle.off('click');
27
28			// 改成自己的 fullscreen 行為
29			fsToggle.on('click', function () {
30				const container = document.getElementById('video-container');
31				if (document.fullscreenElement) {
32					// 退出 fullscreen
33					document.exitFullscreen?.();
34				} else {
35					// 進入 fullscreen(這次是真正由使用者 click 呼叫)
36					container.requestFullscreen?.();
37				}
38			});
39		});
40	}
41	if(section_idx!=undefined)
42	{
43		set_course_player_playbackrate(player, player_type);
44		set_course_player_playbackposition(player, player_type);
45		set_player_volume(player, player_type)
46	}
47
48	// 介紹頁面讀取規格內容
49	$('select[name=course_spec]').bind('change', function()
50	{
51		course_spec_on_change();
52	});
53	course_spec_on_change();
54	bind_press_to_stop_video();
55})
56
57function course_spec_on_change()
58{
59	var option							= $('select[name=course_spec] option:selected');
60	var desc							= option.data('description');
61	$('#spec_desc').html(desc);
62}
63
64function set_player_volume(player, player_type)
65{
66	const saved_volume					= localStorage.getItem('videoVolume');
67	if (saved_volume !== null)
68	{
69		if(player_type=='vimeo')
70			player.setVolume(parseFloat(saved_volume));
71		if(player_type=='youtube'
72			|| player_type=='aten'
73		)
74			player.volume(parseFloat(saved_volume));
75	}
76
77	// listen to volume change
78	if(player_type=='vimeo')
79	{
80		player.on('volumechange', function(current_volume)
81		{
82			localStorage.setItem('videoVolume', current_volume);
83		});
84	}
85	if(player_type=='youtube'
86		|| player_type=='aten'
87	)
88	{
89		player.on('volumechange', function(data)
90		{
91			const current_volume		= player.volume();
92			localStorage.setItem('videoVolume', current_volume);
93		});
94	}
95}
96
97function set_course_player_playbackposition(player, player_type)
98{
99	var section_idx						= $('#course_video').data('section-idx');
100	var playback_position				= $.cookie('course_playback_position_' + section_idx);
101	if(playback_position!=undefined && playback_position!=null)
102	{
103		if(player_type=='vimeo')
104			player.setCurrentTime(playback_position);
105		if(player_type=='youtube'
106			|| player_type=='aten'
107		)
108			player.currentTime(playback_position);
109	}
110
111	player.on('timeupdate', function(data)
112	{
113		var section_idx					= $('#course_video').data('section-idx');
114		var playback_position			= null;
115		if(player_type=='vimeo')
116			playback_position			= data.seconds;
117		if(player_type=='youtube'
118			|| player_type=='aten'
119		)
120			playback_position			= player.currentTime();
121
122		if(playback_position!=null)
123		{
124			// console.log(playback_position);
125			$.cookie('course_playback_position_' + section_idx, playback_position);
126		}
127	});
128
129	player.on('ended', function(data)
130	{
131		$.cookie('course_playback_position_' + section_idx, 0);
132
133		setTimeout(function()
134		{
135			complete_and_go_to_next_section();
136		}, 5000);
137	});
138}
139
140function set_course_player_playbackrate(player, player_type)
141{
142	var playback_rate					= $.cookie('course_playback_rate');
143	if(playback_rate==undefined)
144		playback_rate					= 1;
145	if(playback_rate!==undefined && playback_rate!==null)
146	{
147		change_video_play_speed(playback_rate, player_type, player);
148	}
149
150	if(player_type=='vimeo')
151	{
152		player.on('playbackratechange', function(data)
153		{
154			playback_rate				= data.playbackRate;
155			write_playback_rate_to_cookie(playback_rate);
156		});
157	}
158	if(player_type=='youtube' || player_type=='aten')
159	{
160		player.on('ratechange', function(data)
161		{
162			playback_rate				= player.playbackRate();
163			write_playback_rate_to_cookie(playback_rate);
164		});
165	}
166}
167
168function write_playback_rate_to_cookie(playback_rate)
169{
170	if(playback_rate==undefined || playback_rate==null)
171		playback_rate				= 1;
172
173	$.cookie('course_playback_rate', playback_rate);
174}
175
176function change_video_play_speed(playback_rate, player_type, player)
177{
178	if(player_type==undefined || player_type==null)
179	{
180		player_type						= $('#course_video').data('type');
181	}
182
183	if(player==undefined || player==null)
184	{
185		if(player_type=='vimeo')
186			player						= new Vimeo.Player('course_video');
187		if(player_type=='youtube'
188			|| player_type=='aten'
189		)
190			player						= videojs('course_video');
191	}
192
193	// vimeo
194	if(player_type=='vimeo')
195		player.setPlaybackRate(playback_rate);
196
197	// youtube/aten
198	if(player_type=='youtube'
199		|| player_type=='aten'
200	)
201		player.playbackRate(playback_rate);
202}
203
204// 觀看頁「下一個」(按鈕+影片播完自動接續):照目錄順序往後一格,不看觀看紀錄
205function complete_and_go_to_next_section()
206{
207	var url									= $('#button_complete_and_go_to_next_section').data('url');
208	$.get(url, function(res)
209	{
210		if(res.status=='ok')
211		{
212			window.location					= res.url;
213		}
214		else if(res.status=='no_section')
215		{
216			window.location					= "course";
217		}
218		else if(res.status=='no_next_section')
219		{
220			// 已是最後一個單元:全部看完才走原本的「重新觀看」完成處理,否則只提示、不跳轉
221			if(res.all_completed)
222				show_course_completed_dialog(res);
223			else
224				swal(t('This is the last lesson of the course'));
225		}
226	}, 'json');
227}
228
229// 觀看頁「上一個」:照目錄順序往前一格
230function go_to_prev_section()
231{
232	var url									= $('#button_go_to_prev_section').data('url');
233	$.get(url, function(res)
234	{
235		if(res.status=='ok')
236		{
237			window.location					= res.url;
238		}
239		else if(res.status=='no_prev_section')
240		{
241			swal(t('This is the first lesson of the course'));
242		}
243	}, 'json');
244}
245
246// 課程全部看完的處理:問要不要從第一章重看(重看不動觀看紀錄)
247function show_course_completed_dialog(res)
248{
249	if(res.first_section_url)
250	{
251		swal({
252			title: t('You have watched all chapters :D'),
253			text: t('Start watching again from the first chapter?'),
254			type: 'info',
255			showCancelButton: true,
256			confirmButtonText: t('Watch again'),
257			cancelButtonText: t('Cancel'),
258		}).then(function(result){
259			// sweetalert2 v7 預設 useRejections=false:按「取消」、ESC、點視窗外也會進 then(拿到 result.dismiss),
260			// 只有按下「重新觀看」才有 result.value。少了這道判斷,關掉對話框也會被當成按了確認。
261			if(!result || typeof result.value=='undefined')
262				return;
263			// 重新觀看只跳回第一集,不呼叫 res.reset_url——重看不需要把完成紀錄清成 0,
264			// 誤觸就再也救不回來(reset 只覆寫 completed,沒留原值)
265			window.location			= res.first_section_url;
266		});
267	}
268	else
269	{
270		swal(t('You have watched all chapters :D'));
271	}
272}
273
274function go_to_next_uncompleted_section()
275{
276	var url									= $('#button_go_to_next_uncompleted_section').data('url');
277	$.get(url, function(res)
278	{
279		if(res.status=='ok')
280		{
281			window.location					= res.url;
282		}
283		else if(res.status=='no_section')
284		{
285			window.location					= "course";
286		}
287		else if(res.status=='no_login')
288		{
289			swal(t('You need to log in to watch the next chapter'));
290		}
291		else if(res.status=='no_next_section')
292		{
293			show_course_completed_dialog(res);
294		}
295	}, 'json');
296}
297
298// 購物車專用
299function add_to_course_cart(course_spec_id)
300{
301	var obj								= '#course_spec_' + course_spec_id;
302	var url								= $(obj).attr('data-url');
303	var quantity						= 1;
304	var spec							= $(obj).attr('data-spec');
305	var sellable						= $(obj).attr('data-sellable');
306	var stock							= $(obj).attr('data-stock');
307	var deduct							= $(obj).attr('data-deduct');
308
309	if(url.length==0 || url==undefined)
310		return;
311
312	if(quantity==undefined)
313		quantity						= 1;
314
315	if(sellable=='no')
316	{
317		alert(t('No longer for sale'));
318		return;
319	}
320
321	if(deduct=='yes' && stock==0)
322	{
323		alert(t('This plan is sold out'));
324		return;
325	}
326
327	const raw_params					= {
328											quantity: quantity,
329											spec: spec,
330										};
331
332	const filtered_params				= Object.fromEntries(
333											Object.entries(raw_params).filter(([_, v]) => v !== undefined)
334										);
335	const query_string					= new URLSearchParams(filtered_params).toString();
336
337
338	var url								= url + '?' + query_string;
339	window.location						= url;
340}
341
342/*
343function add_to_course_cart()
344{
345	var url								= $('#course_add_to_cart_button').data('url');
346	var option							= $('select[name=course_spec] option:selected');
347	var type							= option.val();
348	var quantity						= option.data('number-of-people');
349	var spec							= option.data('spec');
350	var sellable						= option.data('sellable');
351	var stock							= option.data('stock');
352
353	if(url.length==0 || url==undefined)
354		return;
355	if(type.length==0 || type==undefined)
356		return;
357
358	if(quantity==undefined)
359		quantity						= 1;
360
361	if(sellable=='no')
362	{
363		alert('已停止販售');
364		return;
365	}
366
367	if(stock==0)
368	{
369		alert('已完售');
370		return;
371	}
372
373	const raw_params					= {
374											type: type,
375											quantity: quantity,
376											spec: spec,
377										};
378
379	const filtered_params				= Object.fromEntries(
380											Object.entries(raw_params).filter(([_, v]) => v !== undefined)
381										);
382	const query_string					= new URLSearchParams(filtered_params).toString();
383
384
385	var url								= url + '?' + query_string;
386	window.location						= url;
387}
388*/
389
390function course_search()
391{
392	var keyword							= $('#blog_search_keyword').val();
393	if(keyword.length==0)
394		return;
395
396	var url								= "course/search/" + encodeURIComponent(keyword).replace(/[!'()*]/g, function(c) { return '%' + c.charCodeAt(0).toString(16);});
397	window.location						= url;
398}
399
400
401var countDownDate = $('#countdown').data('deadline') * 1000;
402
403// Update the count down every 1 second
404var x = setInterval(function()
405{
406	// Get today's date and time
407	var now								= new Date().getTime();
408
409	// Find the distance between now and the count down date
410	var distance						= countDownDate - now;
411
412	// Time calculations for days, hours, minutes and seconds
413	var days = Math.floor(distance / (1000 * 60 * 60 * 24));
414	var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
415	var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
416	var seconds = Math.floor((distance % (1000 * 60)) / 1000);
417
418	$('#deadline_day').html(days);
419	$('#deadline_hour').html(hours);
420	$('#deadline_minute').html(minutes);
421	$('#deadline_second').html(seconds);
422
423	// If the count down is finished, write some text
424	if (distance < 0) {
425		clearInterval(x);
426	}
427}, 1000);
428
429// 新版課程介紹頁
430
431$(document).ready(function() {
432	// 頁籤平滑滾動點擊事件
433	$('.tab-menu .nav-link').on('click', function(e) {
434		e.preventDefault();
435
436		// 獲取目標區塊的 href 屬性
437		var targetHref = $(this).attr('href');
438		var targetId = targetHref.split('#')[1]; // 提取 # 後面的 id
439		var $targetElement = $('#' + targetId);
440
441		// 檢查目標元素是否存在
442		if ($targetElement.length > 0) {
443			// 計算滾動位置,考慮 sticky 頁籤的高度
444			var tabMenuHeight = $('#tabMenu').outerHeight() || 70;
445			var offset = $targetElement.offset().top - tabMenuHeight - 10;
446
447			// 執行平滑滾動
448			$('html, body').animate({
449				scrollTop: offset
450			}, 400);
451		}
452
453		// 更新 active 樣式
454		$('.tab-menu .nav-link').removeClass('active');
455		$(this).addClass('active');
456	});
457
458	// 手機底部立即購買按鈕滾動到購買方案區
459	$('.mobile-buy-btn').on('click', function(e) {
460		e.preventDefault();
461		var $target = $('.sticky-purchase-wrapper');
462
463		if ($target.length > 0) {
464			// 計算 sticky 頁籤的高度
465			var tabMenuHeight = $('#tabMenu').outerHeight() || 70;
466			var offset = $target.offset().top - tabMenuHeight - 10;
467
468			// 停止之前的動畫並執行新的滾動
469			$('html, body').stop(true, true).animate({
470				scrollTop: offset
471			}, 400);
472		}
473	});
474
475	// 監聽頁面滾動以更新頁籤選單 active 狀態
476	$(window).on('scroll', function() {
477		var scrollPosition = $(window).scrollTop();
478		var tabMenuHeight = $('#tabMenu').outerHeight() || 70;
479
480		// 檢查每個區塊是否在視窗範圍內
481		$('.section-container').each(function() {
482			var $section = $(this);
483			var sectionId = $section.attr('id');
484
485			if (sectionId) {
486				var sectionTop = $section.offset().top - tabMenuHeight - 50;
487				var sectionBottom = sectionTop + $section.outerHeight();
488
489				// 如果目前滾動位置在這個區塊範圍內
490				if (scrollPosition >= sectionTop && scrollPosition < sectionBottom) {
491					$('.tab-menu .nav-link').removeClass('active');
492					$('.tab-menu .nav-link[href*="#' + sectionId + '"]').addClass('active');
493				}
494			}
495		});
496
497		// 如果滾動到頁面頂部,激活第一個頁籤
498		if (scrollPosition < 100) {
499			$('.tab-menu .nav-link').removeClass('active');
500			$('.tab-menu .nav-link').first().addClass('active');
501		}
502	});
503});
504
505// 確保頁面載入完成後執行一次滾動檢查
506$(window).on('load', function() {
507	$(window).trigger('scroll');
508});
509
510function bind_press_to_stop_video()
511{
512    // 先檢查是否有 course_video 這個元素
513    if ($('#course_video').length === 0) {
514        return; // 沒有就直接跳出
515    }
516
517    // 綁定按下空白鍵暫停影片
518    $(document).on('keydown', function(event)
519    {
520        if (event.keyCode == 32) // 空白鍵
521        {
522            event.preventDefault();
523            var player_type = $('#course_video').data('type');
524            var player = null;
525
526            if (player_type == 'vimeo')
527                player = new Vimeo.Player('course_video');
528            if (player_type == 'youtube' || player_type == 'aten')
529                player = videojs('course_video');
530
531            if (player != null)
532            {
533                if (player.paused())
534                    player.play();
535                else
536                    player.pause();
537            }
538        }
539    });
540}

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.