PageSourceSearch

https://keziahjones.com/js/app-tagging.js

js keziahjones.com collected 2026-09-26 20:32:36 UTC 36,252 bytes, 1,186 lines download raw bytes

1
2function TagLink( tag, language )
3{
4	var url = 'https://store.steampowered.com/tags/';
5	if ( language )
6		url += language + '/';
7	else
8		url += 'th/';
9
10	url += encodeURIComponent( tag ).replace( /%2F/g, '/' ) + '/';
11	return url;
12}
13
14
15function AppTaggingMatchTags( rgTerms, rgTags, rgSuggestions )
16{
17	if ( rgTags && rgTags.length )
18	{
19		for ( var i = 0; i < rgTags.length; i++ )
20		{
21			var strNameNormalized = rgTags[i].name_normalized;
22			if ( !strNameNormalized )
23			{
24				strNameNormalized = rgTags[i].name_normalized = rgTags[i].name.toLowerCase(); 	// @todo: We should use .normalize() when browsers actually support it.																			// For now just return the normal value as \W actually just removes entire tags.
25			}
26
27			var bMatch = true;
28			for ( var iTerm = 0; iTerm < rgTerms.length; iTerm++ )
29			{
30				if ( strNameNormalized.indexOf( rgTerms[iTerm] ) == -1 )
31				{
32					bMatch = false;
33					break;
34				}
35			}
36			if ( bMatch )
37			{
38				rgSuggestions.push( rgTags[i].name );
39				if ( rgSuggestions.length >= 10 )
40					break;
41			}
42		}
43	}
44}
45
46function GetTagSuggestFunc( rgTagList )
47{
48	return function( value, fnSetSuggestions )
49	{
50		var rgSuggestions = [];
51		if ( value )
52		{
53			var rgTerms = value.toLowerCase().split( ' ' );
54			AppTaggingMatchTags( rgTerms, rgTagList, rgSuggestions );
55
56			rgSuggestions.sort();
57		}
58
59		fnSetSuggestions( rgSuggestions );
60	};
61}
62
63function InitAppTagModal( appid, rgAppTags, rgUserTags, strTagLinkSNR, strYourTagSNR, bShowBanOption, bIsDLC )
64{
65	var $AppTagModal = $J('#app_tagging_modal');
66	var $AppTagForm = $J('#app_tag_form');
67	var $AppTagInput = $AppTagForm.find('input[name=tag]');
68	var $AppTagButton = $AppTagForm.find('button');
69	var TextSuggest = null;
70
71	var $PopularTags = $AppTagModal.find( '.app_tags.popular_tags' );
72	var $YourTags = $AppTagModal.find( '.app_tags.your_tags' );
73	var $FrequentTags = $AppTagModal.find( '.app_tags.previous_tags' );
74	var $FrequentTagCtn = $AppTagModal.find( '.previous_tags_ctn' ).hide();
75
76	var rgUserTagsByTagID = {};
77	for ( var i= 0; i < rgUserTags.length; i++ )
78	{
79		rgUserTagsByTagID[ rgUserTags[i].tagid ] = rgUserTags[i];
80	}
81
82	var rgYourPopularTags = null;
83	var rgGlobalPopularTags = [];
84	var bYourPopularTagsRequested = false;
85	var bBannedTag = false;
86	var bAppliedTag = false;
87	var bAppliedTagToChildren = false;
88
89	// setup "advanced" admin mode - internal functions
90	var $AdvCheckbox = $J('#app_tagging_show_advanced');
91	$AdvCheckbox.prop( 'checked', !!WebStorage.GetLocal( 'ShowAdvancedTaggingOptions' ) );
92	$AdvCheckbox.on( 'change', function() {
93		if ( $AdvCheckbox.prop('checked') )
94		{
95			WebStorage.SetLocal( 'ShowAdvancedTaggingOptions', 1 );
96			$AppTagModal.addClass( 'advanced_enabled' );
97		}
98		else
99		{
100			WebStorage.RemoveLocal( 'ShowAdvancedTaggingOptions' );
101			$AppTagModal.removeClass( 'advanced_enabled' );
102		}
103	} ).trigger('change');
104
105	var fnUpdateTagButtonState = function() {
106		if ( $AppTagInput.val().length > 0 )
107			$AppTagButton.removeClass( 'btn_disabled' );
108		else
109			$AppTagButton.addClass( 'btn_disabled' );
110	};
111
112	var fnSetCheckboxTooltip = function( $Checkbox )
113	{
114		if ( $Checkbox.hasClass('checked') )
115			$Checkbox.data('tooltip-text', 'คุณนำแท็กนี้ไปใช้แล้ว คลิกเพื่อลบออก' );
116		else
117			$Checkbox.data('tooltip-text', 'คลิกที่นี่เพื่อนำแท็กนี้ไปใช้' );
118	};
119
120	var fnSetReportTooltip = function( $ReportFlag )
121	{
122		if ( $ReportFlag.hasClass('reported') )
123			$ReportFlag.data('tooltip-text', 'คุณได้รายงานแท็กนี้แล้วว่า นำมาใช้งานไม่ตรงกับผลิตภัณฑ์นี้ คลิกอีกครั้งเพื่อลบรายงานนี้' );
124		else
125			$ReportFlag.data('tooltip-text', 'คลิกเพื่อรายงานแท็กนี้ว่า นำมาใช้งานไม่ตรงกับผลิตภัณฑ์นี้' );
126	};
127
128	var fnCheckboxClick = function( checkbox, tag, bPopular, tagid )
129	{
130		var bWithdraw = $J(checkbox).hasClass('checked');
131		fnApplyTag( tag, bWithdraw, bPopular, 0, tagid );
132	};
133
134	var fnAdjustClick = function( checkbox, tag, bPopular, tagid )
135	{
136		console.log( rgUserTagsByTagID[ tagid ] );
137		var nCurrentWeight = ( rgUserTagsByTagID[ tagid ] && rgUserTagsByTagID[ tagid ].weight ) || 0;
138		ShowPromptDialog( 'Adjust Tag Weight', 'Enter a new weight for your vote.  Current weight: ' + nCurrentWeight, 'Tag' )
139			.done( function( value ) {
140				var weight = parseInt( value );
141				if ( weight )
142					fnApplyTag( tag, weight < 0, bPopular, 0, tagid, weight );
143			});
144	}
145
146	var fnMakeTag = function( tagid, tagdata, checked, bPopular, bReported, bBrowsable )
147	{
148		var tag = tagdata.name;
149		var $Tag = $J('<div/>', {'class': 'app_tag_control', 'data-tagid': tagid } );
150
151		var $Checkbox = $J('<div/>', {'data-tooltip-text': '', 'class': 'app_tag_checkbox' + ( checked && !bReported ? ' checked' : '' ) }).click( function() { fnCheckboxClick( this, tag, bPopular, tagid ); });
152
153		fnSetCheckboxTooltip( $Checkbox );
154
155		$Tag.append( $Checkbox );
156
157		if ( bPopular )
158		{
159			$Tag.addClass( 'popular' );
160
161			// only if logged in
162			if ( $YourTags.length )
163			{
164				if ( bShowBanOption )
165				{
166					var $Ban = $J('<div/>', {'class': 'app_tag_ban'} );
167					$Ban.data('tooltip-text', 'คลิกที่นี่เพื่อลบแท็กนี้ออกจากผลิตภัณฑ์นี้ ตัวเลือกนี้จำกัดเฉพาะผู้พัฒนาและเจ้าหน้าที่ Steam เท่านั้น' );
168
169					$Tag.append( $Ban );
170					var content = $J( '<div/>' );
171					content.append( $J( '<div/>', { 'class': 'app_tag_ban_prompt' } ).text( 'คุณแน่ใจหรือไม่ว่าต้องการลบแท็ก "%s" ออกจากผลิตภัณฑ์นี้?'.replace( /%s/, tag ) ) );
172					if ( !bIsDLC ) {
173						content.append( $J( '<div/>', { 'class': 'app_tag_ban_checkbox' } ).append( $J( '<input/>', {'type': 'checkbox', 'name':'ban_child_tags', 'checked': 1, 'id': 'ban_child_tags' } ) ) );
174						content.append( $J( '<div/>', { 'class': 'app_tag_ban_label' } ).append( $J( '<label/>', {'for': 'ban_child_tags' } ).text( 'แบนแท็กสำหรับเนื้อหาดาวน์โหลดย่อยด้วย' ) ) );
175						content.append( $J( '<div/>
175', { 'class': 'app_tag_ban_note' } ).text( 'โปรดทราบ: การดำเนินการดังกล่าวจะส่งผลกระทบต่อเนื้อหาดาวน์โหลดทั้งหมดที่เกี่ยวข้องกับเกมนี้ แม้แต่เนื้อหาดาวน์โหลดที่คุณสร้างหรือวางจำหน่ายในอนาคตด้วย แต่คุณจะยังสามารถเลิกแบนแท็กสำหรับเนื้อหาดาวน์โหลดแต่ละรายการได้' ) );
176					}
177
178					$Ban.click( function() {
179						ShowConfirmDialog( 'แบนแท็ก', content ).done( function()
180						{
181							$Ban.trigger('mouseleave');	// to clear the tooltip
182							var checkbox = content.find( '.app_tag_ban_checkbox > input' );
183							var bApplyToChildren = false;
184
185							if ( checkbox.is( ':checked' ) )
186							{
187								// Ban all children
188								bApplyToChildren = true;
189							}
190							fnApplyTag( tag, false, true, -1, 0 /* no tag id */, 0 /* no weight */, bApplyToChildren );
191						});
192					});
193				}
194				else
195				{
196					var $Report = $J('<div/>', {'class': 'app_tag_report' + ( bReported ? ' reported' : '' )});
197
198					fnSetReportTooltip($Report);
199					$Tag.append($Report);
200
201					$Report.click(function () {
202						fnApplyTag(tag, $Report.hasClass('reported'), true, 2);
203					});
204				}
205			}
206		}
207
208		if ( bShowBanOption )
209		{
210			var $AdjCheckbox = $J('<div/>', {'data-tooltip-text': 'Adjust your applied weight', 'class': 'app_tag_advanced app_tag_adjust' }).click( function() { fnAdjustClick( this, tag, bPopular, tagid ); }).text( "±" );
211			$Tag.append( $AdjCheckbox );
212
213			$Tag.append( $J('<div/>', {'class': 'app_tag_advanced app_tag_count' } ).text( tagdata.count ) );
214		}
215
216		if ( bBrowsable )
217		{
218			var $Link = $J('<a/>', {'class': 'app_tag', 'href': TagLink(tag) + '?snr=' + strTagLinkSNR}).text(tag);
219			$Link.InstrumentLinks();
220			$Tag.append($Link);
221		}
222		else
223		{
224			var $Link = $J('<div/>', {'class': 'app_tag not_browseable' }).text(tag);
225			$Link.data('tooltip-text', 'รายการที่ถูกแท็กด้วย "%s" ยังมีไม่มากพอ'.replace( /%s/, $Link.html() ) );
226
227			$Tag.append( $Link );
228		}
229
230		return $Tag;
231	};
232
233	var fnMakeFrequentTag = function( tagid, tag )
234	{
235		var $Tag = $J('<div/>', {'class': 'app_tag previous_tag', 'data-tagid': tagid, 'data-tooltip-text': 'คลิกที่นี่เพื่อนำแท็กนี้ไปใช้' }).text( tag ).click( function() { fnApplyTag( tag ) } );
236
237		return $Tag;
238	};
239
240	var fnCheckTagBox = function( $Elements, withdraw, reported )
241	{
242		var $Checkbox = $Elements.find( '.app_tag_checkbox' );
243
244		if ( withdraw || reported )
245		{
246			$Checkbox.removeClass( 'checked' );
247		}
248		else
249		{
250			$Checkbox.addClass( 'checked' );
251		}
252		fnSetCheckboxTooltip( $Checkbox );
253
254		var $ReportFlag = $Elements.find( '.app_tag_report' );
255		if ( reported )
256			$ReportFlag.addClass( 'reported' );
257		else
258			$ReportFlag.removeClass( 'reported' );
259		fnSetReportTooltip( $ReportFlag );
260	};
261
262	var fnFlashTag = function( $Tag )
263	{
264		$Tag.addClass('tag_control_flash');
265		window.setTimeout( function() { $Tag.removeClass( 'tag_control_flash' ) }, 1100 );
266	};
267
268	var fnBuildTagDisplay = function( Modal )
269	{
270		$PopularTags.empty();
271		$YourTags.empty();
272		$FrequentTags.empty();
273
274		for ( var i = 0; i < rgAppTags.length; i++ )
275		{
276			var tagid = rgAppTags[i].tagid;
277			var $AppTag = fnMakeTag( tagid, rgAppTags[i], rgUserTagsByTagID[tagid], true, rgUserTagsByTagID[tagid] && rgUserTagsByTagID[tagid].is_reported, true );
278
279			$PopularTags.append( $AppTag );
280		}
281
282		Modal.AdjustSizing();
283
284		if ( !$YourTags.length )
285		{
286			return;	//not logged in case
287		}
288
289		if ( rgYourPopularTags === null )
290		{
291			$YourTags.html( '<div class="yourtags_throbber"><img src="https://store.fastly.steamstatic.com/public/images/login/throbber.gif"></div>' );
292			if ( !bYourPopularTagsRequested )
293			{
294				bYourPopularTagsRequested = true;
295
296				var url = 'https://store.steampowered.com/tagdata/myfrequenttags';
297				var mytag_version = WebStorage.GetLocal( 'mytag_version', true );
298				if ( mytag_version )
299					url += '?v=' + parseInt( mytag_version );
300
301				$J.get( url ).done( function( data ) {
302					rgYourPopularTags = data || [];
303					fnBuildTagDisplay( Modal );
304					Modal.AdjustSizing();
305				}).fail( function() {
306					rgYourPopularTags = [];
307					fnBuildTagDisplay( Modal );
308					Modal.AdjustSizing();
309				});
310
311				// also load the global popular tags, to populate the autocomplete with
312				$J.get( 'https://store.steampowered.com/tagdata/populartags/thai').done( function ( data ) {
313					for ( var i = 0; i < data.length; i++ )
314						rgGlobalPopularTags.push( data[i] );	// don't assign, we've got references to this guy
315				});
316			}
317		}
318		else
319		{
320			var rgYourTagsSeen = {};
321			for ( var i = 0; i < rgUserTags.length; i++ )
322			{
323				var tagid = rgUserTags[i].tagid;
324				rgYourTagsSeen[tagid] = true;
325
326				if ( rgUserTagsByTagID[tagid] && rgUserTagsByTagID[tagid].is_reported )
327					continue;
328
329				var $AppTag = fnMakeTag( tagid, rgUserTags[i], rgUserTagsByTagID[tagid], false, false, rgUserTags[i].browseable );
330
331				$YourTags.append( $AppTag );
332			}
333
334			if ( $YourTags.children().length == 0 )
335			{
336				$YourTags.append( $J('<div/>', {'class': 'app_tag_control no_tags_yet'}).text('ยังไม่มีแท็ก' ) );
337			}
338
339			var cPopularTagsToDisplay = 16;
340			for ( var i = 0; i < rgYourPopularTags.length; i++ )
341			{
342				var tagid = rgYourPopularTags[i].tagid;
343
344				if ( rgYourTagsSeen[tagid] )
345					continue;
346
347				var $AppTag = fnMakeFrequentTag( tagid, rgYourPopularTags[i].name );
348
349				$FrequentTags.append( $AppTag );
350				$FrequentTagCtn.show();
351
352				if ( --cPopularTagsToDisplay <= 0 )
353					break;
354			}
355		}
356	};
357
358	var fnUpdateUserTagsOnPage = function()
359	{
360		var $YourTagsOnPage = $J('.your_tags[data-appid=' + appid + ']');
361		var $AddYourOwn = $YourTagsOnPage.children( '.app_tag.add_your_own');
362		var $AddMore = $YourTagsOnPage.children( '.app_tag.add_more');
363		if ( rgUserTags.length )
364		{
365			$AddYourOwn.hide();
366			$AddMore.show();
367			$YourTagsOnPage.empty();
368			for ( var i = 0; i < rgUserTags.length; i++ )
369			{
370				if ( !rgUserTags[i].is_reported && rgUserTags[i].browseable )
371				{
372					var $AppTag = $J('<a/>', { 'class': 'app_tag', 'href': TagLink(rgUserTags[i].name) + '?snr=' + strYourTagSNR }).text(rgUserTags[i].name);
373					$AppTag.InstrumentLinks();
374					$YourTagsOnPage.append($AppTag);
375				}
376			}
377
378			// always put non-browseable after all browseable tags, so they are less likely to display
379			for( var i = 0; i < rgUserTags.length; i++ )
380			{
381				if ( !rgUserTags[i].is_reported &&  !rgUserTags[i].browseable )
382				{
383					var $AppTag = $J('<div/>', {'class': 'app_tag not_browseable' }).text( rgUserTags[i].name );
384					$AppTag.data('tooltip-text', 'รายการที่ถูกแท็กด้วย "%s" ยังมีไม่มากพอ'.replace( /%s/, $AppTag.html() ) );
385
386					$YourTagsOnPage.append( $AppTag );
387				}
388			}
389		}
390		else
391		{
392			$AddYourOwn.show();
393			$AddMore.hide();
394			$YourTagsOnPage.empty();
395		}
396		$YourTagsOnPage.append( $AddYourOwn, $AddMore );
397		AdjustVisibleAppTags( $YourTagsOnPage );
398	};
399
400		var fnIsTagBrowseable = function( tagid, name )
401	{
402		for ( var i = 0; i < rgGlobalPopularTags.length; i++ )
403		{
404							if ( rgGlobalPopularTags[i].name == name )
405					return true;
406					}
407		return false;
408	};
409
410	var fnApplyTag = function( tag, withdraw, bPopularClick, eReportType, unTagID, nWeight, bApplyToChildren )
411	{
412		var rgParams = {
413			appid: appid,
414			sessionid: g_sessionID,
415			tag: tag
416		};
417		if ( unTagID )
418			rgParams['tagid'] = unTagID;
419
420		if ( nWeight )
421		{
422			rgParams['weight'] = Math.abs( nWeight );
423			if ( nWeight < 0 )
424				rgParams['withdraw'] = 1;
425		}
426		else if ( withdraw )
427			rgParams['withdraw'] = 1;
428		else if ( eReportType )
429			rgParams['reporttype'] = eReportType;
430
431		if ( bApplyToChildren )
432			rgParams['applytochildren'] = bApplyToChildren;
433
434		$J.post( 'https://store.steampowered.com/tagdata/tagapp', rgParams ).done( function( data ) {
435
436			bAppliedTag = true;
437			bAppliedTagToChildren = bAppliedTagToChildren || bApplyToChildren;
438
439			var strControlSelector = '.app_tag_control[data-tagid=' + data.tagid + ']';
440			var $AppTag = $PopularTags.find( strControlSelector );
441			var $YourTag = $YourTags.find( strControlSelector );
442
443			if ( typeof data.browseable == 'undefined' )
444			{
445				data.browseable = fnIsTagBrowseable( data.tagid, data.name );
446			}
447
448			if ( !$YourTag.length && !data.withdraw && !data.reported )
449			{
450				$YourTag = fnMakeTag( data.tagid, data, false, false, false, data.browseable );
451				$YourTags.append( $YourTag );
452				$YourTags.children('.no_tags_yet').hide();
453			}
454
455			var iExistingTag;
456			for ( iExistingTag = 0; iExistingTag < rgUserTags.length; iExistingTag++ )
457			{
458				if ( rgUserTags[iExistingTag].tagid == data.tagid )
459				{
460					break;
461				}
462			}
463
464			if ( data.withdraw )
465			{
466				delete rgUserTagsByTagID[ data.tagid ];
467				if ( iExistingTag < rgUserTags.length )
468					rgUserTags.splice( iExistingTag, 1 );
469
470				fnCheckTagBox( $YourTag, true );
471			}
472			else
473			{
474				rgUserTagsByTagID[ data.tagid ] = data;
475				if ( iExistingTag >= rgUserTags.length )
476				{
477					rgUserTags.push( data );
478				}
479
480				// remove from the frequent tag box
481				$FrequentTags.find( '[data-tagid=' + data.tagid + ']' ).hide();
482				if ( $FrequentTags.children().length == 0 )
483					$FrequentTagCtn.hide();
484
485				// make sure it's checked
486				if ( data.is_reported )
487				{
488					$YourTag.remove();
489				}
490				else
491				{
492					fnCheckTagBox( $YourTag );
493				}
494			}
495
496			fnCheckTagBox( $AppTag, data.withdraw, data.is_reported );
497
498			// highlight the popular tag to show it has changed
499			if ( !bPopularClick && !data.withdraw && $AppTag.length )
500			{
501				fnFlashTag( $AppTag );
502			}
503
504			if ( !data.withdraw && $YourTag.length )
505				fnFlashTag( $YourTag );
506
507			// roll "mytag_version" variable which will force a reload of user's frequent tags
508			WebStorage.SetLocal( 'mytag_version', parseInt( WebStorage.GetLocal( 'mytag_version', true ) || 0 ) + 1, true );
509
510			if ( eReportType > 0 && !withdraw )
511			{
512				ShowAlertDialog( 'รายงานแท็ก', 'ขอบคุณสำหรับรายงานของคุณ แท็กที่มีจำนวนรายงานถึงจำนวนที่กำหนดไว้จะถูกลบออกจากผลิตภัณฑ์นี้');
513			}
514			else if ( eReportType == -1 )
515			{
516				$AppTag.hide( 'fast' );
517				bBannedTag = true;
518			}
519
520		}).fail( function() {
521			ShowAlertDialog( 'ดูและแก้ไขแท็กสำหรับผลิตภัณฑ์นี้', 'There was a problem adding your tag.' );
522		});
523	};
524
525	var fnOnSubmit = function(event) {
526		if ( event )
527			event.preventDefault();
528
529		if ( $AppTagInput.val().length == 0 )
530			return;
531
532		fnApplyTag( $AppTagInput.val(), false );
533		$AppTagInput.val('').change().focus();
534	};
535
536	var changeUrl = function(url, removeParam="", addParam="") {
537
538		if(url.indexOf("?") === -1) return url;
539		var bits = url.split("?");
540		if(bits.length < 2) return url;
541		var firstBit = bits[0];
542		var secondBit = bits[1];
543		var params = [];
544		if(secondBit.indexOf("&") === -1){
545			params.push(secondBit);
546		}else{
547			var nextBits = secondBit.split("&");
548			for(var i = 0; i < nextBits.length; i++){
549				params.push(nextBits[i]);
550			}
551		}
552
553		var result = firstBit;
554
555		for(var i = params.length-1; i >= 0; i--){
556			var param = params[i];
557			if(param === removeParam){
558				params.splice(i,1);
559			}
560		}
561
562		if(addParam !== "" && addParam !== null && params.indexOf(addParam) === -1){
563			params.push(addParam);
564		}
565
566		var extraBits = params.join("&");
567		if(extraBits === "&") extraBits = "";
568
569		if(extraBits !== ""){
570			result += "?" + extraBits;
571		}
572
573		return result;
574	}
575
576	window.ShowAppTagModal = function( appid )
577	{
578		if ( typeof GetUsabilityTracker !== 'undefined' )
579			GetUsabilityTracker().AddEvent( 1001 );
580
581		$AppTagModal.show();
582		var Modal = ShowAlertDialog( 'ดูและแก้ไขแท็กสำหรับผลิตภัณฑ์นี้', $AppTagModal, 'ปิด' );
583		Modal.GetContent().addClass( 'app_tag_modal_frame');
584		fnBuildTagDisplay( Modal );
585		Modal.AdjustSizing();
586
587		Modal.always( function() {
588			if ( TextSuggest )
589			{
590				TextSuggest.Destroy();
591				TextSuggest = null;
592			}
593
594			fnUpdateUserTagsOnPage();
595
596			if ( bAppliedTag && bShowBanOption && typeof FlushApp != 'undefined' )
597				FlushApp( appid, bAppliedTagToChildren );
598			else if ( bBannedTag )
599				window.location.reload();
600
601			window.history.pushState({}, document.title, changeUrl(window.location.href, "tags", ""));
602
603		} );
604
605		// in the "you need to log in" case, we don't have a form to display
606		if ( $AppTagForm.length )
607		{
608			$AppTagInput.val('');
609			$AppTagInput.change( fnUpdateTagButtonState );
610			$AppTagInput.keyup( fnUpdateTagButtonState );
611			fnUpdateTagButtonState();
612			$AppTagInput.focus();
613
614			$AppTagForm.submit( fnOnSubmit );
615			$AppTagForm.keyup( function( event ) { if ( event.which == 13 ) event.stopPropagation() } );
616
617			TextSuggest = new CTextInputSuggest( $AppTagInput, GetTagSuggestFunc( rgGlobalPopularTags ), function( suggestion ) { fnOnSubmit(); } );
618
619			// hack to size the input control correctly
620			$AppTagForm.find( '.gray_bevel').css( 'margin-right', ( $AppTagButton.width() + 12 ) + 'px' );
621		}
622
623		//add "tags" param to the URL
624		window.history.pushState({}, document.title, changeUrl(window.location.href, "", "tags"));
625
626	};
627
628
629
630	var $PopularTagsOnPage = $J('.popular_tags[data-appid=' + appid + ']');
631	var $YourTagsOnPage = $J('.your_tags[data-appid=' + appid + ']');
632	AdjustVisibleAppTags( $PopularTagsOnPage );
633	fnUpdateUserTagsOnPage();
634
635	$J(window ).on('resize.VisibleAppTags', function() {
636		AdjustVisibleAppTags( $PopularTagsOnPage );
637		AdjustVisibleAppTags( $YourTagsOnPage );
638	});
639}
640
641function AdjustVisibleAppTags( $TagCtn )
642{
643	var bPopularTags = $TagCtn.hasClass( 'popular_tags' );
644	var $AddButton = $TagCtn.children('.app_tag.add_button');
645	if ( !bPopularTags )
646		$AddButton = $AddButton.filter(':visible');
647
648
649	var k_nSpacing = 5;
650	var nSpaceRemaining = $TagCtn.width() - $AddButton.outerWidth();
651
652	var bAnyHidden = false;
653	$TagCtn.children( '.app_tag:not(.add_button)').each( function() {
654		var $Element = $J(this);
655		var nWidth = $Element.outerWidth() + k_nSpacing;
656		if ( nWidth > nSpaceRemaining )
657		{
658			$Element.hide();
659			bAnyHidden = true;
660		}
661		else
662		{
663			nSpaceRemaining -= nWidth;
664			$Element.show();
665		}
666	});
667}
668
669function InitTagTabs( strURL, cc, rgTabNames, rgInitialParams )
670{
671	var g_rgTabs = {};
672	var g_strTabRequestURL = strURL;
673	var g_rgTabBaseParams = {
674		cc: cc,
675		l: 'thai',
676		v: 4
677	};
678	var g_rgTabParams = {};
679	var g_strActiveTab = null;
680
681
682	var fnOnTabFilterChange = function()
683	{
684		for ( var strTab in g_rgTabs )
685		{
686			var Tab = g_rgTabs[strTab];
687			Tab.m_iStart = 0;
688			Tab.m_nLoaded = 0;
689			Tab.m_$Items.css( 'top', '0px' );
690		}
691		UpdateTabDisplay();
692	};
693
694	var fnAddFilter = function( strParam, value, $Checkbox )
695	{
696		if ( !$Checkbox )
697			$Checkbox = $J('.tab_filter_control[data-param=' + strParam + '][data-value=' + value + ']');
698
699		$Checkbox.addClass( 'checked' );
700
701		if ( !g_rgTabParams[strParam] )
702			g_rgTabParams[strParam] = [];
703		g_rgTabParams[strParam].push( value );
704
705		var $Filter = $J('<div/>', {'class': 'tab_filter' } );
706		$Filter.attr( 'data-param', strParam );
707		$Filter.attr( 'data-value', value );
708		$Filter.text( $Checkbox.text() );
709		$Filter.append( $J('<div/>', {'class': 'tab_filter_remove' }).html('&nbsp;').click( function() { fnRemoveFilter( strParam, value, $Checkbox, $Filter ); } ) );
710		$J('.tab_filters').append( $Filter );
711		$J('.tab_filter_ctn').show( 'fast' );
712
713		fnOnTabFilterChange();
714	};
715
716	var fnRemoveFilter = function( strParam, value, $Checkbox, $Filter )
717	{
718		if ( !$Checkbox )
719			$Checkbox = $J('.tab_filter_control[data-param=' + strParam + '][data-value=' + value + ']');
720		if ( !$Filter )
721			$Filter = $J('.tab_filter[data-param=' + strParam + '][data-value=' + value + ']');
722
723		$Checkbox.removeClass('checked');
724
725		if ( g_rgTabParams[strParam] )
726			g_rgTabParams[strParam] = g_rgTabParams[strParam].filter( function( val ) { return val != value; } );
727
728		$Filter.remove();
729		if ( !$J('.tab_filters').children().length )
730			$J('.tab_filter_ctn').hide( 'fast' );
731
732		fnOnTabFilterChange();
733	};
734
735
736	var InitTabEvents = function( Tab )
737	{
738		Tab.m_$Select.click( function() {
739			UpdateTabDisplay( Tab.m_strName );
740		} );
741
742		Tab.m_$Next.css( 'visibility', 'visible' );
743		Tab.m_$NextLink.attr( 'href', '#' );
744		Tab.m_$NextLink.click( function( event ) {
745			event.preventDefault();
746			if ( !Tab.m_bRequestInFlight && !Tab.m_bAnimating )
747				DoTabScroll( Tab, 10 );
748		});
749
750
751		Tab.m_$Prev.css( 'visibility', 'visible' );
752		Tab.m_$PrevLink.attr( 'href', '#' );
753		Tab.m_$PrevLink.click( function( event ) {
754			event.preventDefault();
755			if ( !Tab.m_bRequestInFlight && !Tab.m_bAnimating )
756				DoTabScroll( Tab, -10 );
757		});
758	};
759
760	var UpdateTabDisplay = function( strActiveTab )
761	{
762		if ( !strActiveTab )
763			strActiveTab = g_strActiveTab;
764		else
765			g_strActiveTab = strActiveTab;
766
767		for ( var strTabName in g_rgTabs )
768		{
769			var Tab = g_rgTabs[strTabName];
770			if ( strTabName == strActiveTab )
771			{
772				Tab.m_$Select.addClass( 'active' );
773				Tab.m_$Content.show();
774				if ( Tab.m_nLoaded == 0 )
775				{
776					Tab.m_$Items.empty();
777					LoadTab( Tab );
778				}
779			}
780			else
781			{
782				Tab.m_$Select.removeClass( 'active' );
783				Tab.m_$Content.hide();
784			}
785		}
786	};
787
788	var DoTabScroll = function( Tab, delta )
789	{
790		if ( Tab.m_iStart + delta + 10 > Tab.m_nLoaded )
791			LoadTab( Tab, delta );
792		else
793			TagScrollTabTo( Tab, Tab.m_iStart + delta );
794	};
795
796	var LoadTab = function( Tab, delta )
797	{
798		if ( Tab.m_bRequestInFlight )
799			return;
800
801		if ( !delta )
802			delta = 0;
803
804		Tab.m_bNeedsReload = false;
805		var url = g_strTabRequestURL + Tab.m_strName;
806		var rgParams = $J.extend( {
807			'start': Tab.m_iStart + delta
808		}, g_rgTabBaseParams );
809
810		for ( var filter in g_rgTabParams )
811		{
812			var rgParamValues = g_rgTabParams[filter];
813			if ( rgParamValues && rgParamValues.length )
814			{
815				rgParamValues.sort();
816				for ( var i = 0; i < rgParamValues.length; i++ )
817				{
818					rgParams[ filter + '[' + i + ']' ] = rgParamValues[i];
819				}
820			}
821		}
822
823		$J.getJSON( url, rgParams).done( function( data ) {
824			Tab.m_bRequestInFlight = false;
825
826			Tab.m_$Items.append( data.html );
827			Tab.m_nTotal = data.total;
828			Tab.m_nLoaded = data.start + data.count;
829
830			TagScrollTabTo( Tab, data.start );
831		});
832	};
833
834	var UpdateTabPagingDisplay = function( Tab )
835	{
836		if ( Tab.m_iStart > 0 )
837			Tab.m_$Prev.show();
838		else
839			Tab.m_$Prev.hide();
840
841		if ( Tab.m_iStart + 10 < Math.min( Tab.m_nTotal, 100 ) )
842			Tab.m_$Next.show();
843		else
844			Tab.m_$Next.hide();
845
846		Tab.m_$CountStart.text( Tab.m_iStart + 1 );
847		Tab.m_$CountEnd.text( Math.min( Tab.m_iStart + 10, Tab.m_nTotal ) );
848		Tab.m_$CountTotal.text( Tab.m_nTotal );
849	};
850
851	var TagScrollTabTo = function( Tab, iStart )
852	{
853		var delta = iStart - Tab.m_iStart;
854		Tab.m_iStart = iStart;
855		UpdateTabPagingDisplay( Tab );
856
857		if ( delta != 0 )
858		{
859			var nItemHeight = Tab.m_$Items.children(':first-child').height() + 1;
860
861			Tab.m_bAnimating = true;
862			Tab.m_$Items.css( 'position', 'relative' );
863			var nTop = parseInt( Tab.m_$Items.css('top') ) || 0;
864			Tab.m_$Items.animate( {top: (nTop - (delta * nItemHeight)) + 'px' }, 750, function() {
865				Tab.m_bAnimating = false;
866			} );
867		}
868	};
869
870	// perform initialization
871
872	for ( var i = 0 ; i < rgTabNames.length; i++ )
873	{
874		var strName = rgTabNames[i];
875		var Tab = {
876			m_strName: strName,
877			m_$Select: $J('#tab_select_' + strName ),
878			m_$Content: $J('#tab_' + strName + '_content' ),
879			m_$Items: $J('#tab_' + strName + '_items' ),
880			m_$Prev: $J('#tab_' + strName + '_prev' ),
881			m_$PrevLink: $J('#tab_' + strName + '_prev > a' ),
882			m_$Next: $J('#tab_' + strName + '_next' ),
883			m_$NextLink: $J('#tab_' + strName + '_next > a' ),
884			m_$CountStart: $J('#tab_' + strName + '_count_start' ),
885			m_$CountEnd: $J('#tab_' + strName + '_count_end' ),
886			m_$CountTotal: $J('#tab_' + strName + '_count_total' ),
887			m_iStart: 0,
888			m_nTotal: parseInt( $J('#tab_' + strName + '_count_total').text() ) || 0,
889			m_nLoaded: 10,
890
891			m_bAnimating: false,
892			m_bRequestInFlight: false
893		};
894
895		UpdateTabPagingDisplay( Tab );
896		InitTabEvents( Tab );
897
898		g_rgTabs[ strName ] = Tab;
899	}
900
901	UpdateTabDisplay( rgTabNames[0] );
902
903	$J('.tab_filter_control').each( function() {
904		var $Control = $J(this);
905		var strParam = $Control.data('param');
906		var value = $Control.data('value');
907		$Control.click( function() {
908			if ( !$Control.hasClass( 'checked' ) )
909			{
910				fnAddFilter( strParam, value, $Control );
911			}
912			else
913			{
914				fnRemoveFilter( strParam, value, $Control );
915			}
916		});
917	});
918
919	$J('#related_tags_more_toggle').click( function() {
920		$J('#related_tags_more_toggle').hide();
921		$J('#related_tags_more').show('fast');
922	});
923
924	// due to php, this might be an empty array, or an object.  on an object, length will be undefined
925	if ( rgInitialParams.length !== 0 )
926	{
927		for ( var param in rgInitialParams )
928		{
929			var rgValues = rgInitialParams[param];
930			for ( var i = 0; i < rgValues.length; i++ )
931			{
932				$J('.tab_filter_ctn').show();	// when showing initially, skip the animation that fnAddFilter would run
933				fnAddFilter( param, rgValues[i] );
934			}
935		}
936	}
937}
938
939
940function InitTagBrowsePage( strTagLanguage, rgDefaultGetParams )
941{
942	var rgTagResults = {};
943
944	var rgActiveTagByOption = { global: null, yours: null, friends: null, recommended: null };
945	var strActiveOption;
946
947	var fnDisplayTag = function( unTagID, strTagName ) {
948		if ( !rgTagResults[unTagID] )
949		{
950			var $Element = $J('<div/>', {'class': 'tag_browse_games_results'} );
951			// some pending info
952			var $GamesElement = $J('<div/>', {'class':'browse_tag_games'});
953			$GamesElement.append($J('<h3/>').text( strTagName ) );
954			$GamesElement.append( $J('<div/>', {'class': 'browse_tag_loading' }).html( '<img src="https://store.fastly.steamstatic.com/public/images/login/throbber.gif">' ) );
955
956			$Element.append( $GamesElement );
957			$Element.append( $J('<div/>', {'class':'browse_tag_game_total'}).html( '&nbsp;' ) );
958			var rgParams = $J.extend( { name: strTagName }, rgDefaultGetParams );
959
960			$J.get( 'https://store.steampowered.com/tagdata/gettaggames/' + strTagLanguage + '/' + unTagID + '/', rgParams ).done( function ( html ) {
961				$Element.html( html );
962				$Element.InstrumentLinks();
963				if ( typeof GDynamicStore != 'undefined' )
964					GDynamicStore.DecorateDynamicItems( $Element );
965			}).fail( function () {
966				$GamesElement.find( '.browse_tag_loading').text( 'ขออภัย ตรวจพบปัญหาในการโหลดผลิตภัณฑ์ โปรดลองใหม่อีกครั้งภายหลัง' );
967			});
968
969			rgTagResults[unTagID] = $Element;
970		}
971
972		$J('#tag_browse_games_ctn').children().detach();
973		$J('#tag_browse_games_ctn').append( rgTagResults[unTagID] );
974	};
975
976	var fnSelectOption = function( strOption, unTagID )
977	{
978		strActiveOption = strOption;
979		var $Tags =	$J('#tag_browse_' + strOption);
980		$Tags.siblings('.tag_browse_tags').hide();
981		$Tags.show();
982
983		var $Desc = $J('#tag_browse_description_' + strOption );
984		$Desc.siblings().hide();
985		$Desc.show();
986
987		$J('.tag_browse_option').removeClass('active');
988		$J('.tag_browse_option[data-browseoption=' + strOption + ']').addClass('active');
989
990		if ( unTagID )
991		{
992			var $Tag = $Tags.children('[data-tagid=' + unTagID + ']');
993			if ( $Tag.length )
994				rgActiveTagByOption[strActiveOption] = $Tag;
995		}
996
997		// select the first tag if none selected
998		if ( !rgActiveTagByOption[strActiveOption] && $Tags.children().length )
999			rgActiveTagByOption[strActiveOption]  = $J($Tags.children()[0]);
1000
1001		if ( rgActiveTagByOption[strActiveOption] )
1002			fnSelectTag( rgActiveTagByOption[strActiveOption] );
1003	};
1004
1005	var fnSelectTag = function( $Tag )
1006	{
1007		if ( rgActiveTagByOption[ strActiveOption ] )
1008			rgActiveTagByOption[ strActiveOption ].removeClass('active');
1009
1010		$Tag.addClass( 'active' );
1011		rgActiveTagByOption[ strActiveOption ] = $Tag;
1012
1013		fnDisplayTag( $Tag.data('tagid'), $Tag.text() );
1014		window.history.replaceState( {}, '', '#' + strActiveOption + '_' + $Tag.data('tagid') );
1015	};
1016
1017	$J('.tag_browse_option').click( function () {
1018		fnSelectOption( $J(this).data('browseoption') );
1019	});
1020
1021	//TODO: for testing only
1022	var strEvent = window.location.href.indexOf( 'test_mouse' ) == -1 ? 'click' : 'mouseenter';
1023	$J('.tag_browse_tag').on( strEvent, function() {
1024		fnSelectTag( $J(this) );
1025	});
1026
1027	var fnGoToHash = function() {
1028		var hash = window.location.hash;
1029		if ( hash && hash.length > 2 )
1030		{
1031			var rgHashComponents = hash.substr(1).split('_');
1032			if ( rgHashComponents.length >= 1 && rgHashComponents.length <= 2 )
1033			{
1034				var option = rgHashComponents[0];
1035				var tagid;
1036				if ( rgHashComponents.length >= 2 )
1037					tagid = parseInt( rgHashComponents[1] );
1038				if ( rgActiveTagByOption[option] || rgActiveTagByOption[option] === null )
1039				{
1040					fnSelectOption( option, tagid );
1041					return;
1042				}
1043			}
1044		}
1045
1046		fnSelectOption('global');
1047	};
1048
1049	if ( window.location.hash.length )
1050		fnGoToHash();
1051	else
1052		fnSelectOption( 'global' );
1053}
1054
1055function InitBannedTagModal( appid, $BanModal, bIsDLC )
1056{
1057	var bBansChanged = false;
1058	var bAppliedToChildren = false;
1059	window.ShowAppTagBanModal = function() {
1060		$BanModal.show();
1061		ShowAlertDialog( 'แท็กที่ถูกแบน', $BanModal).always( function() {
1062			if ( bBansChanged && typeof FlushApp != 'undefined' )
1063				FlushApp( appid, bAppliedToChildren );
1064			else if ( bBansChanged )
1065				window.location.reload();
1066		} );
1067
1068		$BanModal.find( '.app_tag_report').click( function() {
1069			var $Flag = $J(this);
1070			var $Row = $Flag.parent( '.app_tag_control' );
1071			var tag = $Row.data('tag');
1072
1073			var content = $J( '<div/>' );
1074			content.append( $J( '<div/>', { 'class': 'app_tag_ban_prompt' } ).text( 'คุณแน่ใจหรือไม่ว่าต้องการเลิกแบนแท็ก "%s"?'.replace( /%s/, tag ) ) );
1075			if ( !bIsDLC ) {
1076				content.append( $J( '<div/>', { 'class': 'app_tag_ban_checkbox' } ).append( $J( '<input/>', {'type': 'checkbox', 'name':'unban_child_tags', 'checked': 1, 'id': 'unban_child_tags' } ) ) );
1077				content.append( $J( '<div/>', { 'class': 'app_tag_ban_label' } ).append( $J( '<label/>', {'for': 'unban_child_tags' } ).text( 'เลิกแบนแท็กสำหรับเนื้อหาดาวน์โหลดย่อยด้วย' ) ) );
1078			}
1079
1080			ShowConfirmDialog( 'เลิกแบนแท็ก',
1081				content,
1082				'เลิกแบนแท็ก')
1083				.done( function() {
1084					var checkbox = content.find( '.app_tag_ban_checkbox > input' );
1085					var bApplyToChildren = false;
1086
1087					if ( checkbox.is( ':checked' ) )
1088					{
1089						// unban all children DLCs as well
1090						bApplyToChildren = true;
1091					}
1092
1093					var rgParams = {
1094						appid: appid,
1095						sessionid: g_sessionID,
1096						tag: tag,
1097						reporttype: -1,
1098						unban: 1,
1099						applytochildren: bApplyToChildren
1100					};
1101					$J.post( 'https://store.steampowered.com/tagdata/tagapp', rgParams ).done( function( data ) {
1102						$Row.hide( 'fast' );
1103					}).fail( function () {
1104						ShowAlertDialog( 'เลิกแบนแท็ก', 'ตรวจพบปัญหาในการแบนหรือเลิกแบนแท็กนี้ โปรดลองใหม่อีกครั้งภายหลัง' );
1105					});
1106					bBansChanged = true;
1107					bAppliedToChildren = bAppliedToChildren || bApplyToChildren;
1108				});
1109		});
1110	};
1111}
1112
1113
1114var g_bLoadingPopularTags = false;
1115var g_rgGlobalPopularTags = [];
1116
1117// A modular tag auto-complete widget.
1118var CTagAutoComplete = function( $elContainer, fnOnTagSelected, bNoFocus )
1119{
1120	var $AppTagForm = $elContainer;
1121	var $AppTagInput = $AppTagForm.find('input[name=tag]');
1122	var $AppTagButton = $AppTagForm.find('button');
1123
1124	var TextSuggest;
1125
1126	if ( $AppTagForm.length )
1127	{
1128		var fnOnSubmit = function(event) {
1129			if ( event )
1130				event.preventDefault();
1131
1132			if ( $AppTagInput.val().length == 0 )
1133				return;
1134
1135			fnOnTagSelected( $AppTagInput.val() );
1136			if ( !bNoFocus )
1137				$AppTagInput.val('').change().focus();
1138		};
1139
1140		var fnUpdateTagButtonState = function() {
1141			if ( $AppTagInput.val().length > 0 )
1142				$AppTagButton.removeClass( 'btn_disabled' );
1143			else
1144				$AppTagButton.addClass( 'btn_disabled' );
1145		};
1146
1147		$AppTagInput.val('');
1148		$AppTagInput.change( fnUpdateTagButtonState );
1149		$AppTagInput.keyup( fnUpdateTagButtonState );
1150		fnUpdateTagButtonState();
1151		if ( !bNoFocus )
1152			$AppTagInput.focus();
1153
1154		$AppTagForm.submit( fnOnSubmit );
1155		$AppTagForm.keyup( function( event ) { if ( event.which == 13 ) event.stopPropagation() } );
1156
1157		TextSuggest = new CTextInputSuggest( $AppTagInput, GetTagSuggestFunc( g_rgGlobalPopularTags ), function( suggestion ) { fnOnSubmit(); } );
1158
1159		// hack to size the input control correctly
1160		//$AppTagForm.find( '.gray_bevel').css( 'margin-right', ( $AppTagButton.width() + 12 ) + 'px' );
1161
1162		this.LoadPopularTags( false );
1163	}
1164}
1165
1166//
1167CTagAutoComplete.prototype.LoadPopularTags = function( bForce )
1168{
1169	if ( g_bLoadingPopularTags )
1170		return;
1171
1172	g_bLoadingPopularTags = true;
1173	if( !bForce && g_rgGlobalPopularTags.length > 0 )
1174		return;
1175
1176	$J.get( 'https://store.steampowered.com/tagdata/populartags/thai').done( function ( data ) {
1177		for ( var i = 0; i < data.length; i++ )
1178		{
1179			g_rgGlobalPopularTags.push( data[i] );	// don't assign, we've got references to this guy
1180		}
1181
1182		g_bLoadingPopularTags = false;
1183	});
1184}
1185
1186

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.