提交 5be56ac7 编写于 作者: P pc-ls

2023-05-21--协议实现网页登录

上级 c732d1c0
...@@ -56,3 +56,5 @@ Release/ ...@@ -56,3 +56,5 @@ Release/
obj/ obj/
AutoLogin_VS2012/bin/bin.rar AutoLogin_VS2012/bin/bin.rar
Heart/Tangram/SHARE Heart/Tangram/SHARE
node_modules
{
"npm-scripts.showStartNotification": false
}
\ No newline at end of file
{
"name": "steam-login",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "steam-login",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"node-fetch": "^3.3.1"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"engines": {
"node": ">= 12"
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
"integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"engines": {
"node": ">= 8"
}
}
},
"dependencies": {
"data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
},
"fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"requires": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
}
},
"formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"requires": {
"fetch-blob": "^3.1.2"
}
},
"node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
},
"node-fetch": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz",
"integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==",
"requires": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
}
},
"web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q=="
}
}
}
/**** (c) Valve Corporation. Use is governed by the terms of the Steam Subscriber Agreement http://store.steampowered.com/subscriber_agreement/.
****/
(self.webpackChunkstore=self.webpackChunkstore||[]).push([[4535],{76456:e=>{e.exports={LoginContainer:"login_LoginContainer_2kLRm"}},86401:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(89526),o=n(44839),a=n(19094),i=n(32765),s=n(79925),l=n(76456);function c(e){const{redirectUrl:t=i.De.STORE_BASE_URL}=e,[n]=(0,r.useState)(new a.J(i.De.WEBAPI_BASE_URL).GetAnonymousServiceTransport()),[c,u]=(0,r.useState)(!1);return r.createElement("div",{className:l.LoginContainer},c?r.createElement(o.pT,null):r.createElement(o.wK,{autoFocus:!0,transport:n,platform:2,onComplete:e=>{e==s.TG.k_PrimaryDomainFail?u(!0):window.location.assign(t)},redirectUrl:t}))}}}]);
\ No newline at end of file
因为 它太大了无法显示 source diff 。你可以改为 查看blob
function GetElemSNR( $Elem )
{
var snr = $Elem.data( 'snr' );
if ( typeof snr != 'undefined' )
{
return snr;
}
// look for links with snr parameter
var links = $Elem.is( 'a' ) ? $Elem : $Elem.find( 'a' );
snr = null;
for ( var i = 0; i < links.length; ++i )
{
var link = links[i];
var navinfo = link.href.match( /[\?&]snr=([a-zA-Z0-9\-\_ ]+)/ );
if ( navinfo )
{
snr = navinfo[1];
break;
}
}
$Elem.data( 'snr', snr );
return snr;
}
// given an array of impressions as strings, this will handle joining them all together into a singular string, but enforcing that it doesn't
// go above the cookie size limit which can otherwise cause users to become stuck since the page requests will start failing
function JoinImpressionsUpToLimit( rgImpressions )
{
//cookies generally can go up to 4k bytes, but we can have problems when we start getting that close, so cut it off earlier
var nRemainingLen = 3200;
var result = '';
for ( var i = 0; i < rgImpressions.length; i++ )
{
var impression = String( rgImpressions[ i ] );
var nImpressionLen = encodeURIComponent( impression + '|' ).length;
//did we run out of room in our list?
if ( nRemainingLen < nImpressionLen )
break;
//add the separator if not the first entry
if ( result !== '' )
result += '|';
//add our impression and remove that space from what is available
result += impression;
nRemainingLen -= nImpressionLen;
}
return result;
}
GDynamicStore = {
m_bLoadComplete: false,
s_rgWishlist: {},
s_rgOwnedPackages: {},
s_rgOwnedApps: {},
s_rgMasterSubApps: {},
s_rgAutoGrantApps: {},
s_rgPackagesInCart: {},
s_rgAppsInCart: {},
s_rgRecommendedTags: [],
s_rgIgnoredApps: {},
s_rgIgnoredPackages: {},
s_rgCurators: {},
s_rgCurations: {},
s_rgCreatorsFollowed: {},
s_rgCreatorsIgnored: {},
s_preferences: {},
s_rgExcludedTags: {},
s_rgExcludedDescIDs: {},
s_rgPersonalizedBundleData: {},
s_rgfnOnReadyCallbacks: [],
s_rgDisplayedApps: [],
s_rgDisplayedBundles: [],
s_bUserOnMacOS: false,
s_bUserOnLinux: false,
s_bUserOnWindows: false,
s_rgRecommendedApps: [],
s_ImpressionTracker: false,
s_bAllowAppImpressions: false,
Init: function( accountid, bForceRefresh, strOS, preferences, strCC, optsIn )
{
var opts = $J.extend( { bNoDefaultDescriptors: false }, optsIn || {} );
var rgDesiredOSTypes = strOS ? strOS.split(',') : 'any';
for( var i=0; i < rgDesiredOSTypes.length; i++ )
{
switch( rgDesiredOSTypes[i] )
{
case 'mac': GDynamicStore.s_bUserOnMacOS = true; break;
case 'linux': GDynamicStore.s_bUserOnLinux = true; break;
default:
case 'win': GDynamicStore.s_bUserOnWindows = true; break;
}
}
GDynamicStore.s_preferences = preferences || {};
var fnRunOnLoadCallbacks = function() {
GDynamicStore.m_bLoadComplete = true;
GDynamicStore.InitAppearHandler();
for ( var i = 0; i < GDynamicStore.s_rgfnOnReadyCallbacks.length; i++ )
{
try {
GDynamicStore.s_rgfnOnReadyCallbacks[i]();
}
catch ( e )
{
console.error( e );
}
}
GDynamicStore.s_rgfnOnReadyCallbacks = null;
GDynamicStore.DecorateDynamicItems();
GDynamicStore.PopulateRecommendedTagList();
};
try {
this.RemoveSNRFromURL();
this.RemoveUTMFromURL();
} catch ( e )
{
}
// Create a new monitor to track impressions
this.s_ImpressionTracker = new CAppearMonitor(
function( elElement ){
var fnTrack = function( el )
{
var $Elem = $J(el);
if ( $Elem.data( 'trackedForImpressions' ) )
{
return;
}
$Elem.data( 'trackedForImpressions', true );
// must have appids
var strAppIDs = $Elem.data('dsAppid');
if ( !strAppIDs || strAppIDs.length == 0 )
{
return;
}
var snr = GetElemSNR( $Elem );
if ( !snr )
{
return;
}
GDynamicStore.AddImpressionFromDynamicItem( $Elem );
};
fnTrack( elElement );
// Also track sub-elements, which may exist inside our container. This is useful for nested clusters etc.
$J(elElement).find("*[data-ds-appid]").each(function(i, j){
fnTrack(j);
});
}
);
if ( accountid )
{
if ( bForceRefresh )
GDynamicStore.InvalidateCache();
var url = 'https://store.steampowered.com/dynamicstore/userdata/?id=' + accountid + '&cc=' + strCC;
var unUserdataVersion = WebStorage.GetLocal( 'unUserdataVersion' );
if ( unUserdataVersion )
url += '&v=' + parseInt( unUserdataVersion );
$J.get( url ).done( function( data ) {
var fnEnsureObject = function ( rgMaybeArray ) {
return ( !rgMaybeArray || ( typeof rgMaybeArray.length != 'undefined' && rgMaybeArray.length == 0 ) ) ? {} : rgMaybeArray;
};
var fnConvertToMap = function ( rgData ) {
var out = {};
if ( rgData && rgData.length )
{
for ( var i = 0; i < rgData.length; i++ )
out[ rgData[i] ] = true;
}
return out;
};
GDynamicStore.s_rgWishlist = fnConvertToMap( data.rgWishlist );
GDynamicStore.s_rgOwnedPackages = fnConvertToMap( data.rgOwnedPackages );
GDynamicStore.s_rgOwnedApps = fnConvertToMap( data.rgOwnedApps );
GDynamicStore.s_rgMasterSubApps = fnConvertToMap( data.rgMasterSubApps );
GDynamicStore.s_rgAutoGrantApps = fnConvertToMap( data.rgAutoGrantApps );
GDynamicStore.s_rgPackagesInCart = fnConvertToMap( data.rgPackagesInCart );
GDynamicStore.s_rgAppsInCart = fnConvertToMap( data.rgAppsInCart );
GDynamicStore.s_rgRecommendedTags = data.rgRecommendedTags || [];
GDynamicStore.s_rgIgnoredApps = data.rgIgnoredApps || {}
GDynamicStore.s_rgIgnoredPackages = data.rgIgnoredPackages || {};
GDynamicStore.s_rgCurators = data.rgCurators || {};
GDynamicStore.s_rgCurations = data.rgCurations || {};
GDynamicStore.s_rgCreatorsFollowed = fnConvertToMap( data.rgCreatorsFollowed );
GDynamicStore.s_rgCreatorsIgnored = fnConvertToMap( data.rgCreatorsIgnored );
GDynamicStore.s_bAllowAppImpressions = data.bAllowAppImpressions || false;
if ( data.rgExcludedTags && data.rgExcludedTags.length > 0 )
{
for ( var i = i = 0; i < data.rgExcludedTags.length; ++i )
{
var tag = data.rgExcludedTags[i];
GDynamicStore.s_rgExcludedTags[tag.tagid] = tag.name;
}
}
if ( data.rgExcludedContentDescriptorIDs && data.rgExcludedContentDescriptorIDs.length > 0 &&
!V_GetCookie( 'wants_mature_content') )
{
for ( var i = i = 0; i < data.rgExcludedContentDescriptorIDs.length; ++i )
{
var id = data.rgExcludedContentDescriptorIDs[i];
GDynamicStore.s_rgExcludedDescIDs[id] = id;
}
}
GDynamicStore.s_nRemainingCartDiscount = data.nRemainingCartDiscount ? data.nRemainingCartDiscount : 0;
GDynamicStore.s_nTotalCartDiscount = data.nTotalCartDiscount ? data.nTotalCartDiscount : 0;
GDynamicStore.s_rgRecommendedApps = data.rgRecommendedApps || [];
GDynamicStore.s_nPromotionalDiscount = data.nPromotionalDiscount ? data.nPromotionalDiscount : 0;
GDynamicStore.s_nPromotionalDiscountMinCartAmount = data.nPromotionalDiscountMinCartAmount ? data.nPromotionalDiscountMinCartAmount : 0;
GDynamicStore.s_nPromotionalDiscountAvailableUseCount = data.nPromotionalDiscountAvailableUseCount ? data.nPromotionalDiscountAvailableUseCount : 0;
}).always( function() { $J(fnRunOnLoadCallbacks); } );
}
else
{
if ( !opts.bNoDefaultDescriptors )
GDynamicStore.s_rgExcludedDescIDs[3] = 3;
var url = 'https://store.steampowered.com/dynamicstore/saledata/?cc=' + strCC;
$J.get( url ).done( function( data ) {
GDynamicStore.s_nPromotionalDiscount = data.nPromotionalDiscount ? data.nPromotionalDiscount : 0;
GDynamicStore.s_nPromotionalDiscountMinCartAmount = data.nPromotionalDiscountMinCartAmount ? data.nPromotionalDiscountMinCartAmount : 0;
GDynamicStore.s_nPromotionalDiscountAvailableUseCount = data.nPromotionalDiscountAvailableUseCount ? data.nPromotionalDiscountAvailableUseCount : 0;
GDynamicStore.s_bAllowAppImpressions = data.bAllowAppImpressions || false;
}).always( function() { $J(fnRunOnLoadCallbacks); } );
}
},
RemoveSNRFromURL: function()
{
if ( !window.history || !window.history.replaceState || !window.location.search )
return;
GDynamicStore.RemoveParamFromURL( 'snr' );
GDynamicStore.RemoveParamFromURL( 'ser' );
},
RemoveParamFromURL: function( strParamName )
{
// find snr param
var strParamPrefix = '' + strParamName + '=';
var strSearch = window.location.search;
if ( strSearch.indexOf( '?' ) == 0 )
strSearch = strSearch.slice( 1 );
var rgParams = strSearch.split( '&' );
var iParam = -1;
for ( var i = 0; i < rgParams.length; i++ )
{
var strParam = rgParams[i];
if (strParam.indexOf( strParamPrefix ) == 0)
{
iParam = i;
break;
}
}
if ( iParam < 0 )
return;
var strRemove = '';
if ( rgParams.length == 1 || (rgParams.length == 2 && rgParams[1].length == 0) )
{
// remove the entire search.. just SNR
strRemove = '?' + strSearch;
}
else if ( iParam == 0 )
{
// first param of multiple. Remove snr and trailing &
strRemove = rgParams[iParam] + '&';
}
else
{
// 2nd+ param of multiple. Remove snr and preceeding &
strRemove = '&' + rgParams[iParam];
}
if ( strRemove.length > 0 )
{
var strNewURL = window.location.href.replace( strRemove, '' );
window.history.replaceState( history.state, null, strNewURL );
}
},
RemoveUTMFromURL: function()
{
if ( !window.history || !window.history.replaceState || !window.location.search )
return;
var strSearch = window.location.search;
if ( strSearch.indexOf( '?' ) == 0 )
strSearch = strSearch.slice( 1 );
var rgParams = strSearch.split( '&' );
var rgNewParams = [];
for ( var i = 0; i < rgParams.length; ++i )
{
var strParam = rgParams[i];
if ( strParam.indexOf( 'utm_') == -1 )
{
rgNewParams.push( strParam );
}
}
if ( rgParams.length != rgNewParams.length )
{
var strNewURL = window.location.href.replace( window.location.search, '' );
for ( var i = 0; i < rgNewParams.length; ++i )
{
var strParam = rgNewParams[i];
strNewURL += ( i == 0 ? '?' : '&' ) + strParam;
}
window.history.replaceState( history.state, null, strNewURL );
}
},
// Fixup name portion of URL via history API, if support and if name portion is incorrect
FixupNamePortion: function() {
var rel = $J( "link[rel='canonical']" );
if ( rel.length && window.history ) {
// have rel=canonical URL and access to history API.
// parse out href portion of navigated URL and see if it's OK
var detachedAnchor = document.createElement( 'a' );
detachedAnchor.href = rel.attr( "href" );
if ( window.location.pathname != detachedAnchor.pathname ) {
// URL portion does not match canonical URL; rewrite it, preserving query params and hash
window.history.replaceState( null, null, rel[0].href + window.location.search + window.location.hash );
}
}
},
s_strAppearSelector: '[data-ds-appid], [data-ds-packageid]',
InitAppearHandler: function()
{
$J(GDynamicStore.s_strAppearSelector).each(function(i, elTarget ){
var $Elem = $J(elTarget);
// these are handled manually, so don't add the impression here
if ( $Elem.hasClass( 'cluster_capsule' ) || $Elem.hasClass( 'carousel_cap') )
{
return;
}
GDynamicStore.s_ImpressionTracker.RegisterElement( elTarget )
});
// find our horizontal scrollers and add tracking to them
$J('.store_horizontal_autoslider' ).each(function(i, elTarget ){
GDynamicStore.s_ImpressionTracker.RegisterScrollEvent( elTarget );
});
GDynamicStore.s_ImpressionTracker.CheckVisibility();
},
s_oImpressionsTracked: {},
AddImpressionFromDynamicItem: function( $Elem )
{
if ( !GDynamicStore.s_bAllowAppImpressions )
{
return;
}
if ( $Elem.hasClass( 'app_impression_tracked' ) )
{
return;
}
$Elem.addClass( 'app_impression_tracked' );
var strImpressions = V_GetDecodedCookie( "app_impressions" );
var rgImpressions = strImpressions && strImpressions.length != 0 ? strImpressions.split( "|" ) : [];
// commas not allowed in cookie value
var strAppIDs = $Elem.data('dsAppid');
if ( !strAppIDs )
{
return;
}
var rgAppIds;
if ( typeof strAppIDs == 'string' && ( strAppIDs.indexOf( ',' ) >= 0 || strAppIDs.indexOf( ':' ) >= 0 ))
{
rgAppIds = strAppIDs.split( /[,:]/ );
}
else
{
rgAppIds = [ parseInt( strAppIDs ) ];
}
var snr = GetElemSNR( $Elem );
if ( !snr )
{
return;
}
var rgAppIDsToReport = [];
for ( var i = 0; i < rgAppIds.length; i++ )
{
var nAppID = rgAppIds[i];
var strImpressionData = nAppID + '@' + snr;
if ( !GDynamicStore.s_oImpressionsTracked[ strImpressionData ] )
{
GDynamicStore.s_oImpressionsTracked[ strImpressionData ] = true;
rgAppIDsToReport.push( nAppID );
}
}
if ( !rgAppIDsToReport.length )
return;
rgImpressions.push( rgAppIDsToReport.join( ':' ) + '@' + snr );
V_SetCookie( "app_impressions", JoinImpressionsUpToLimit( rgImpressions ) );
},
AddImpression: function( $Elem, appID, strLink )
{
if ( $Elem.hasClass( 'app_impression_tracked' ) )
{
return;
}
$Elem.addClass( 'app_impression_tracked' );
var navinfo = strLink.match( /[\?&]snr=([^&#]*)(&|$|#)/ );
if ( navinfo )
{
var snr = navinfo[1];
var strImpressions = V_GetCookie( "app_impressions" );
var rgImpressions = strImpressions && strImpressions.length != 0 ? strImpressions.split( "|" ) : [];
var strImpressionData = appID + '@' + snr;
rgImpressions.push( strImpressionData );
V_SetCookie( "app_impressions", JoinImpressionsUpToLimit( rgImpressions ) );
}
},
MarkAppDisplayed: function( rgDisplayList, cItemsToMark )
{
// jquery map takes care of arrays as well as array-ish
GDynamicStore.MarkAppIDsAsDisplayed( $J.map( rgDisplayList, function ( item ) { return item.appid; } ), cItemsToMark );
},
MarkItemsAsDisplayed: function( rgItems, cItemsToMark )
{
for ( var i = 0; i < rgItems.length; i++ )
{
const item = rgItems[i];
if ( item.appid )
{
GDynamicStore.s_rgDisplayedApps.push( item.appid );
// if this appid is a demo, also mark the parent app as displayed
var rgAppData = GStoreItemData.rgAppData[ item.appid ];
if ( rgAppData && rgAppData.demo_for_app )
GDynamicStore.s_rgDisplayedApps.push( rgAppData.demo_for_app );
}
if ( item.bundleid )
{
GDynamicStore.s_rgDisplayedBundles.push( item.bundleid );
}
if ( cItemsToMark !== undefined && --cItemsToMark == 0 )
break;
}
},
MarkAppIDsAsDisplayed: function( rgAppIDs, cItemsToMark )
{
for ( var i = 0; i < rgAppIDs.length; i++ )
{
if ( rgAppIDs[i] )
{
GDynamicStore.s_rgDisplayedApps.push( rgAppIDs[i] );
// if this appid is a demo, also mark the parent app as displayed
var rgAppData = GStoreItemData.rgAppData[ rgAppIDs[i] ];
if ( rgAppData && rgAppData.demo_for_app )
GDynamicStore.s_rgDisplayedApps.push( rgAppData.demo_for_app );
if ( cItemsToMark !== undefined && --cItemsToMark == 0 )
break;
}
}
},
HandleClusterChange: function( cluster ) {
GDynamicStore.s_ImpressionTracker.CheckVisibility();
var $ScrollingContainer = $J( cluster.elScrollArea );
var capsules = $ScrollingContainer.find( '.cluster_capsule' );
GDynamicStore.s_ImpressionTracker.TrackAppearanceIfVisible( capsules[cluster.nCurCap] );
},
HandleCarouselChange: function( targetid, curPos, pageSize ) {
var $ScrollingContainer = $J( "#" + targetid );
var capsules = $ScrollingContainer.find( '.recommendation_carousel_item' );
var idx = (curPos * pageSize);
if ( capsules.length != 0 && idx < capsules.length )
{
GDynamicStore.s_ImpressionTracker.TrackAppearanceIfVisible( capsules[idx] );
}
},
OnReady: function( fnCallback )
{
if ( GDynamicStore.m_bLoadComplete )
fnCallback();
else
GDynamicStore.s_rgfnOnReadyCallbacks.push( fnCallback );
},
DecorateDynamicItems: function( $Selector, bForceRecalculate ) {
if ( !GDynamicStore.m_bLoadComplete )
{
GDynamicStore.OnReady( function() { GDynamicStore.DecorateDynamicItems( $Selector ) } );
return;
}
// locate elements with dynamic store data
var strSelector = '[data-ds-appid], [data-ds-packageid], [data-ds-bundleid]';
// update prices for cart
if ( GDynamicStore.s_nRemainingCartDiscount != 'undefined ')
{
UpdatePricesForAdditionalCartDiscount($Selector, GDynamicStore.s_nRemainingCartDiscount);
}
var bBannerShown = false;
if ( GDynamicStore.s_nTotalCartDiscount != 'undefined ')
{
bBannerShown = UpdateStoreBannerForAdditionalCartDiscount( GDynamicStore.s_nTotalCartDiscount );
}
if ( !bBannerShown && GDynamicStore.s_nPromotionalDiscount != 'undefined' )
{
bBannerShown = UpdateStoreBannerForPromotionalDiscount( GDynamicStore.s_nPromotionalDiscount, GDynamicStore.s_nPromotionalDiscountMinCartAmount, GDynamicStore.s_nPromotionalDiscountAvailableUseCount );
}
var $DynamicElements;
if ( $Selector )
{
if ( $Selector.is( strSelector ) )
{
$DynamicElements = $Selector;
}
else
{
$DynamicElements = $Selector.find( strSelector );
}
}
else
{
$DynamicElements = $J(strSelector);
}
$DynamicElements.each( function() {
var $El = $J(this);
if ( bForceRecalculate )
{
$El.removeClass( 'ds_flagged ds_owned ds_wishlist ds_incart' ).children( '.ds_flag' ).remove();
}
else if ( $El.data('dsInstrumented') )
{
return;
}
$El.data('dsInstrumented', true);
var bOwned = false;
var bWanted = false;
var bInCart = false;
var bIgnored = false;
var unBundleID = $El.data('dsBundleid');
var unPackageID = $El.data('dsPackageid');
var strAppIDs = $El.data('dsAppid');
var eSteamDeckCompatCategory = $El.data('dsSteamDeckCompatCategory');
if ( eSteamDeckCompatCategory !== undefined && !$El.data( 'dsSteamDeckCompatHandled' ) )
{
$El.data('dsSteamDeckCompatHandled', true);
var strClasses = 'ds_steam_deck_compat ';
switch( eSteamDeckCompatCategory )
{
case 3:
strClasses += 'verified';
break;
case 2:
strClasses += 'playable';
break;
case 1:
strClasses += 'unsupported';
break;
case 0:
default:
strClasses += 'unknown';
break;
}
var elSteamDeckCompatCategory = $J( '<div/>', { class: strClasses } );
$El.append( elSteamDeckCompatCategory );
}
if ( unBundleID )
{
var Bundle = GDynamicStore.GetPersonalizedBundleData( unBundleID, $El.data('dsBundleData') );
if ( !Bundle ) // no data available
return;
if ( Bundle.m_cUserItemsInBundle == 0 )
{
bOwned = true;
}
else
{
// pull out all the appids and let the strAppIDs code below handle it
var rgAllAppIDsInBundle = [];
for( var iBundleItem = 0; iBundleItem < Bundle.m_rgBundleItems.length; iBundleItem++ )
{
var BundleItem = Bundle.m_rgBundleItems[iBundleItem];
for ( var iApp = 0; iApp < BundleItem.m_rgIncludedAppIDs.length; iApp++ )
{
rgAllAppIDsInBundle.push( BundleItem.m_rgIncludedAppIDs[iApp] );
}
}
strAppIDs = rgAllAppIDsInBundle.join(',');
}
GDynamicStore.UpdateDynamicBundleElements( Bundle, $El );
}
else if ( unPackageID )
{
if ( GDynamicStore.s_rgPackagesInCart[unPackageID] )
bInCart = true;
else if ( GDynamicStore.s_rgOwnedPackages[unPackageID] )
bOwned = true;
else if ( unPackageID in GDynamicStore.s_rgIgnoredPackages )
bIgnored = true;
}
if ( strAppIDs && typeof strAppIDs == 'string' && strAppIDs.indexOf( ',' ) >= 0 )
{
var rgAppIDs = strAppIDs.split( ',' );
var bValid = false;
var bAllOwned = true, bAllWanted = true, bAllInCart = true;
for ( var i = 0; i < rgAppIDs.length; i++ )
{
var unAppID = parseInt( rgAppIDs[i] );
if ( !unAppID )
continue;
bValid = true;
if ( !GDynamicStore.s_rgOwnedApps[unAppID] )
bAllOwned = false;
if ( !GDynamicStore.s_rgWishlist[unAppID] )
bAllWanted = false;
if ( !GDynamicStore.s_rgAppsInCart[unAppID] )
bAllInCart = false;
}
if ( bValid )
{
if ( bAllInCart )
bInCart = bAllInCart;
else if ( bAllOwned )
bOwned = bAllOwned;
else if ( bAllWanted )
bWanted = bAllWanted;
GDynamicStore.s_ImpressionTracker.RegisterElement( this );
}
}
else if ( parseInt( strAppIDs ) )
{
// simple case of single appid
var unAppID = parseInt( strAppIDs );
if ( GDynamicStore.s_rgAppsInCart[unAppID] )
bInCart = true;
else if ( GDynamicStore.s_rgOwnedApps[unAppID] )
bOwned = true;
else if ( GDynamicStore.s_rgWishlist[unAppID] )
bWanted = true;
else if ( unAppID in GDynamicStore.s_rgIgnoredApps )
bIgnored = true;
GDynamicStore.s_ImpressionTracker.RegisterElement( this );
}
var rgExcludedTagNames = GDynamicStore.GetExcludedTagsOverlap( $El );
var rgExcludedContentDescriptorIDs = GDynamicStore.GetExcludedContentDescriptorOverlap( $El );
var rgExcludedCreatorIDs = GDynamicStore.GetExcludedCreatorOverlap( $El );
if ( !$El.hasClass('ds_no_flags') )
{
// owned and wishlist are mutually exclusive
if ( bOwned )
{
$El.addClass( 'ds_flagged ds_owned' );
$El.append( '<div class="ds_flag ds_owned_flag">在库中&nbsp;&nbsp;</div>');
}
else if ( bWanted )
{
$El.addClass( 'ds_flagged ds_wishlist' );
$El.append( '<div class="ds_flag ds_wishlist_flag">已在愿望单中&nbsp;&nbsp;</div>');
}
else if ( bIgnored )
{
$El.addClass( 'ds_flagged ds_ignored' );
$El.append( '<div class="ds_flag ds_ignored_flag">已忽略&nbsp;&nbsp;</div>');
}
if ( bInCart )
{
$El.addClass( 'ds_flagged ds_incart' );
$El.append( '<div class="ds_flag ds_incart_flag">在购物车中&nbsp;&nbsp;</div>');
}
if ( $El.hasClass( 'ds_excluded_blur' ) )
{
$El.addClass( 'ds_flagged' );
$El.append( '<div class="ds_flag ds_excluded_by_preferences_flag">已按偏好排除&nbsp;&nbsp;</div>' );
}
else if ( rgExcludedContentDescriptorIDs.length != 0 )
{
$El.addClass( 'ds_flagged ds_excluded_by_preferences' );
$El.append( '<div class="ds_flag ds_excluded_by_preferences_flag">已按偏好排除&nbsp;&nbsp;</div>' );
}
else if ( rgExcludedTagNames.length != 0 )
{
$El.addClass( 'ds_flagged ds_excluded_by_preferences' );
$El.append( '<div class="ds_flag ds_excluded_by_preferences_flag">已排除标签:' + rgExcludedTagNames.join(", " ) + '</div>' );
}
else if ( rgExcludedCreatorIDs.length != 0 )
{
$El.addClass( 'ds_flagged ds_excluded_by_preferences' );
$El.append( '<div class="ds_flag ds_excluded_by_preferences_flag">按已忽略的开发者/发行商/系列排除&nbsp;&nbsp;</div>' );
}
if( g_AccountID && unAppID && $El.data('ds-options') !== 0 ) // Only add if we have an appid
{
var $elMenu = $J ( '<div></div>', { 'class': 'ds_options' } ).append($J('<div>'));
$El.append ( $elMenu );
$El.one( 'mouseenter', function() {
$elMenu.v_tooltip ( {
'tooltipClass': 'ds_options_tooltip',
'location': 'bottom left',
'offsetY': -20,
'useClickEvent': true,
'useMouseEnterEvent': false,
func: GDynamicStore.CapsuleSettingsMenu
} );
} );
}
}
});
// make sure that the elements are registered as "appearing" if necessary
GDynamicStore.s_ImpressionTracker.CheckVisibility();
},
CapsuleSettingsMenu: function( elSource )
{
var $El = $J(this);
var $elSource = $J(elSource.parentNode);
$El.empty();
var strAppIDs = $elSource.data('dsAppid');
// AppID specific controls
if( strAppIDs )
{
var rgAppIds = [];
if (strAppIDs && typeof strAppIDs == 'string' && strAppIDs.indexOf(',') >= 0) {
rgAppIds = strAppIDs.split(',');
}
else if (parseInt(strAppIDs)) {
rgAppIds = [parseInt(strAppIDs)];
}
var bIgnored = false;
var bOnWishlist = false;
var bFilteredByContentPreferences = false;
for( var i = 0; i < rgAppIds.length; i++ )
{
bIgnored |= GDynamicStore.BIsAppIgnored( rgAppIds[i] );
bOnWishlist |= GDynamicStore.BIsAppOnWishlist( rgAppIds[i] );
}
if (!bOnWishlist)
{
var fnClick = function ( event )
{
event.preventDefault();
$elSource.addClass( 'ds_flagged ds_wishlist' );
$elSource.append( '<div class="ds_flag ds_wishlist_flag">已在愿望单中&nbsp;&nbsp;</div>');
for( var i=0; i<rgAppIds.length; i++ )
GDynamicStore.ModifyWishlist ( $elSource, rgAppIds[ i ], false, false, function(){
// Remove the flag if we failed.
$elSource.removeClass( 'ds_wishlist ds_flagged' );
$J('.ds_flag.ds_wishlist_flag', $elSource).remove();
} );
$El.trigger('mouseleave');
return false;
};
var strText = "\u6dfb\u52a0\u81f3\u60a8\u7684\u613f\u671b\u5355";
}
else
{
var fnClick = function ( event )
{
event.preventDefault();
$elSource.removeClass( 'ds_wishlist ds_flagged' );
$J('.ds_flag.ds_wishlist_flag', $elSource).remove();
for( var i=0; i<rgAppIds.length; i++ )
GDynamicStore.ModifyWishlist ( $elSource, rgAppIds[ i ], true, false, function(){
// Add the flag back if we failed for some reason
$elSource.addClass( 'ds_flagged ds_wishlist' );
$elSource.append( '<div class="ds_flag ds_wishlist_flag">已在愿望单中&nbsp;&nbsp;</div>');
} );
$El.trigger('mouseleave');
return false;
};
var strText = "\u5df2\u5728\u613f\u671b\u5355\u4e2d";
}
var $elAddToWishlist = $J ( '<div/>' ).click ( fnClick ).text ( strText ).addClass( 'option' );
$El.append($elAddToWishlist);
if (!bIgnored)
{
var fnClick = function ()
{
$elSource.addClass('ds_ignored ds_flagged');
$elSource.append( '<div class="ds_flag ds_ignored_flag">已忽略&nbsp;&nbsp;</div>');
for( var i=0; i<rgAppIds.length; i++ )
GDynamicStore.ModifyIgnoredApp ( $elSource, rgAppIds[ i ], false );
$El.trigger('mouseleave');
return false;
};
var strText = "\u5ffd\u7565";
}
else
{
var fnClick = function ()
{
$elSource.removeClass('ds_flagged ds_ignored');
$J('.ds_flag.ds_ignored_flag', $elSource).remove();
for( var i=0; i<rgAppIds.length; i++ )
GDynamicStore.ModifyIgnoredApp ( $elSource, rgAppIds[ i ], true );
$El.trigger('mouseleave');
return false;
};
var strText = "\u5df2\u5ffd\u7565";
}
var $elNotInterested = $J ( '<div/>' ).click ( fnClick ).text ( strText ).addClass( 'option' );
$El.append($elNotInterested);
var $elPreferences = $J ( '<a/>' ).attr('href', 'https://store.steampowered.com/account/preferences' ).text ( "\u504f\u597d" ).addClass( 'option' );
$El.append($elPreferences);
}
},
ModifyWishlist: function( $elSource, appid, bRemove, fnOnSuccess, fnOnFail )
{
var url = 'https://store.steampowered.com/api/addtowishlist';
GDynamicStore.s_rgWishlist[appid] = !bRemove;
if( bRemove )
url = 'https://store.steampowered.com/api/removefromwishlist';
$J.post( url, {
sessionid: g_sessionID,
appid: appid,
snr: $elSource.data( 'snr' )
}).done( function( data ) {
if( fnOnSuccess )
fnOnSuccess( appid );
GDynamicStore.InvalidateCache();
}).fail( function() {
if( fnOnFail )
fnOnFail( appid );
GDynamicStore.s_rgWishlist[appid] = false;
});
},
ToggleClientsideFilter: function( elControl, strToggleClass, elResults, strFilterClass )
{
$Control = $J(elControl);
$Results = $J(elResults);
$Control.toggleClass( strToggleClass );
if ( $Control.hasClass( strToggleClass ) )
{
$Results.addClass( strFilterClass );
}
else
{
$Results.removeClass( strFilterClass );
}
},
ModifyIgnoredApp: function( $elSource, appid, bRemove, fnOnSuccess, fnOnFail )
{
if ( bRemove )
{
delete GDynamicStore.s_rgIgnoredApps[appid];
}
else
{
GDynamicStore.s_rgIgnoredApps[appid] = 0;
}
$J.post( 'https://store.steampowered.com/recommended/ignorerecommendation/', {
sessionid: g_sessionID,
appid: appid,
remove: bRemove ? 1 : 0,
snr: $elSource.data( 'snr' )
}).done( function() {
if( fnOnSuccess )
fnOnSuccess( appid );
GDynamicStore.InvalidateCache();
}).fail( function( jqXHR ) {
if( fnOnFail )
fnOnFail( appid );
delete GDynamicStore.s_rgIgnoredApps[appid];
});
},
ModifyIgnoredPackage: function( packageid, bRemove, fnOnSuccess, fnOnFail )
{
if ( bRemove )
{
delete GDynamicStore.s_rgIgnoredPackages[packageid];
}
else
{
GDynamicStore.s_rgIgnoredPackages[packageid] = 0;
}
$J.post( 'https://store.steampowered.com/recommended/ignorerecommendation/', {
sessionid: g_sessionID,
subid: packageid,
remove: bRemove
}).done( function() {
if( fnOnSuccess )
fnOnSuccess( packageid );
GDynamicStore.InvalidateCache();
}).fail( function() {
if( fnOnFail )
fnOnFail( packageid );
delete GDynamicStore.s_rgIgnoredPackages[packageid];
});
},
GetPersonalizedBundleData: function( unBundleID, rgPageBundleData )
{
if ( !GDynamicStore.s_rgPersonalizedBundleData[unBundleID] )
{
if ( !GStoreItemData.rgPersonalizedBundleData[ unBundleID ] && rgPageBundleData && rgPageBundleData.m_rgItems )
{
GStoreItemData.rgPersonalizedBundleData[ unBundleID ] = rgPageBundleData;
}
var Bundle = GStoreItemData.rgPersonalizedBundleData[ unBundleID ];
if ( !Bundle )
return null;
var BundleForUser = {
m_nDiscountPct: Bundle.m_nDiscountPct,
m_bMustPurchaseAsSet: Bundle.m_bMustPurchaseAsSet,
m_cTotalItemsInBundle: Bundle.m_rgItems.length,
m_bContainsDiscountedPackage: false,
m_cUserItemsInBundle: 0,
m_nPackageBasePriceInCents: 0,
m_nFinalPriceInCents: 0,
m_nFinalPriceInCentsWithBundleDiscount: 0,
m_rgBundleItems: [],
m_bIsCommercial: Bundle.m_bIsCommercial,
m_bRestrictGifting: Bundle.m_bRestrictGifting
};
for ( var i = 0; i < Bundle.m_rgItems.length; i++ )
{
var BundleItem = Bundle.m_rgItems[i];
if ( !BundleItem.m_nPackageID || GDynamicStore.s_rgOwnedPackages[ BundleItem.m_nPackageID ] )
continue;
if ( BundleItem.m_rgIncludedAppIDs.length )
{
if ( GDynamicStore.BAreAllAppsOwned( BundleItem.m_rgIncludedAppIDs, { bExcludeMasterSub: true } ) )
continue;
}
BundleForUser.m_bContainsDiscountedPackage |= BundleItem.m_bPackageDiscounted;
BundleForUser.m_cUserItemsInBundle++;
BundleForUser.m_nPackageBasePriceInCents += BundleItem.m_nBasePriceInCents;
BundleForUser.m_nFinalPriceInCents += BundleItem.m_nFinalPriceInCents;
BundleForUser.m_nFinalPriceInCentsWithBundleDiscount += BundleItem.m_nFinalPriceWithBundleDiscount;
BundleForUser.m_rgBundleItems.push( BundleItem );
}
// fix the price
BundleForUser.m_nFinalPriceInCentsWithBundleDiscount = GStoreItemData.CalculateCurrencyAppropriatePrice( BundleForUser.m_nFinalPriceInCentsWithBundleDiscount );
GDynamicStore.s_rgPersonalizedBundleData[ unBundleID ] = BundleForUser;
}
return GDynamicStore.s_rgPersonalizedBundleData[ unBundleID ];
},
UpdateDynamicBundleElements: function( Bundle, $El )
{
var $CartBtn = $El.find('.btn_addtocart:not(.btn_packageinfo)' ).children();
var $DiscountBlocks = $El.find('.discount_block');
if ( !Bundle.m_bIsCommercial && ( !Bundle.m_rgBundleItems.length || ( Bundle.m_bMustPurchaseAsSet && Bundle.m_cUserItemsInBundle < Bundle.m_cTotalItemsInBundle ) ) )
{
var strTooltip = '该捆绑包无法在您的帐户上购买,因为您已经拥有所包含的全部物品。';
if ( Bundle.m_bMustPurchaseAsSet && !Bundle.m_bRestrictGifting )
{
strTooltip = '该优惠仅在一次性购买所有的 %s 件物品时可用。您仍能将此捆绑包当作礼物购买,赠与好友。'.replace(/%s/, Bundle.m_cTotalItemsInBundle);
$CartBtn.find('span').text( '作为礼物购买' );
}
else
{
// completely owned "complete the set" bundle
$DiscountBlocks.hide();
$CartBtn.addClass('btn_disabled' ).attr( 'href', 'javascript:void(0)' );
$CartBtn.parent().css( 'background', '#000000' );
}
$CartBtn.data('tooltip-text', strTooltip );
}
else if ( !Bundle.m_bMustPurchaseAsSet )
{
var strFormattedFinalPrice = GStoreItemData.fnFormatCurrency( Bundle.m_nFinalPriceInCentsWithBundleDiscount );
$DiscountBlocks.show();
$DiscountBlocks.find('.discount_original_price' ).text( GStoreItemData.fnFormatCurrency( Bundle.m_nPackageBasePriceInCents ) );
if ( !Bundle.m_bContainsDiscountedPackage )
{
$DiscountBlocks.addClass('no_discount');
$DiscountBlocks.find('.discount_final_price' ).addClass('your_price' ).empty().append($J('<div/>', {'class': 'your_price_label'} ).text('您的价格:'), $J('<div/>' ).text( strFormattedFinalPrice ) );
}
else
{
$DiscountBlocks.removeClass('no_discount');
var nDiscountPct = Math.round( ( Bundle.m_nPackageBasePriceInCents - Bundle.m_nFinalPriceInCentsWithBundleDiscount ) / Bundle.m_nPackageBasePriceInCents * 100 );
nDiscountPct = Math.min( nDiscountPct, 99 );
$DiscountBlocks.find('.discount_pct' ).text( '-' + nDiscountPct + '%' );
$DiscountBlocks.find('.discount_final_price' ).removeClass('your_price').text( strFormattedFinalPrice );
}
if ( Bundle.m_nFinalPriceInCentsWithBundleDiscount == 0 && Bundle.m_cUserItemsInBundle )
{
// Complete the set bundle with only free items remaining
var $AddToAcctBtn = $El.find('.btn_addtoaccount' );
if ( $AddToAcctBtn.length )
{
$CartBtn.hide();
$AddToAcctBtn.show();
}
}
}
var $Description = $El.find('.package_contents');
if ( $Description.length && $El.hasClass('dynamic_bundle_description') && !Bundle.m_bMustPurchaseAsSet )
GDynamicStore.BuildBundleDescription( Bundle, $Description );
if ( $El.is( '.package_totals_area' ) && !Bundle.m_bMustPurchaseAsSet )
{
if ( !Bundle.m_rgBundleItems.length )
{
$El.hide();
}
else
{
$El.show();
if ( Bundle.m_cUserItemsInBundle < Bundle.m_cTotalItemsInBundle )
{
$El.find('.bundle_final_package_price_desc' ).text( '您尚未拥有的 %s 件物品的单价为:'.replace( '%s', Bundle.m_cUserItemsInBundle ) );
}
$El.find('.bundle_final_package_price' ).text( GStoreItemData.fnFormatCurrency( Bundle.m_nFinalPriceInCents ) );
$El.find('.bundle_final_price_with_discount' ).text( GStoreItemData.fnFormatCurrency( Bundle.m_nFinalPriceInCentsWithBundleDiscount ) );
$El.find('.bundle_savings' ).text( GStoreItemData.fnFormatCurrency( Bundle.m_nFinalPriceInCents - Bundle.m_nFinalPriceInCentsWithBundleDiscount ) );
}
}
},
BuildBundleDescription: function( Bundle, $Description )
{
if ( Bundle.m_cUserItemsInBundle == 0 )
{
// already own everything
$Description.html( '<span class="collectionComplete">合集完成!</span>此合集中的 %1$s/%2$s 件物品已存在于您的库中。'.replace( '%1$s', Bundle.m_cTotalItemsInBundle ).replace( '%2$s', Bundle.m_cTotalItemsInBundle ) );
}
else if ( Bundle.m_cUserItemsInBundle < Bundle.m_cTotalItemsInBundle )
{
// own some but not all.
$Description.html( '<div>该捆绑包中的 %1$s/%2$s 件物品已在您的库中。</div>'.replace( '%1$s', Bundle.m_cTotalItemsInBundle - Bundle.m_cUserItemsInBundle ).replace( '%2$s', Bundle.m_cTotalItemsInBundle ) );
$Description.append( '<div>购买该捆绑包即可为您未拥有的 %2$s 件物品省 %1$s%%!</div>'.replace( '%1$s', Bundle.m_nDiscountPct ).replace( '%2$s', Bundle.m_cUserItemsInBundle ).replace( '%%', '%' ) );
// add "complete the set" flag
$Description.parents('.dynamic_bundle_description' ).append( $J('<div/>', {'class': 'ds_flag ds_completetheset'} ).text('完成您的合集!') );
}
else
{
$Description.html( '<div>购买此捆绑包,所有 %2$s 个项目立省 %1$s%%!</div>'.replace( '%1$s', Bundle.m_nDiscountPct ).replace( '%2$s', Bundle.m_cTotalItemsInBundle ).replace( '%%', '%' ) );
}
var rgItemsWithCaps = [];
for ( var iBundleItem = 0; iBundleItem < Bundle.m_rgBundleItems.length; iBundleItem++ )
{
var unPackageID = Bundle.m_rgBundleItems[iBundleItem].m_nPackageID;
var PackageData = GStoreItemData.rgPackageData[ unPackageID];
if ( PackageData && PackageData.tiny_capsule )
{
rgItemsWithCaps.push( unPackageID );
}
}
if ( rgItemsWithCaps.length )
{
// show no more than 9 items
rgItemsWithCaps = rgItemsWithCaps.slice( 0, 9 );
// if there's more than 5 items, we overlap them a little bit on the display
var bNeedToCollapse = rgItemsWithCaps.length > 5;
var $BundleContentsCtn = $J('<div/>', {'class': 'bundle_contents_preview'} );
if ( bNeedToCollapse )
$BundleContentsCtn.addClass( 'collapsed' );
var $BundleContentsPosition = $J('<div/>', {'class': 'bundle_contents_preview_position'} );
for ( var i = 0; i < rgItemsWithCaps.length; i++ )
{
var rgLinkParams = { 'class': 'bundle_contents_preview_item ds_collapse_flag', 'data-panel': '{"focusable":false}' }
var Item = GStoreItemData.GetCapParams( 'bundle_component_preview', null, rgItemsWithCaps[i], null, rgLinkParams );
var $Link = $J('<a/>', rgLinkParams );
var $Img = $J('<img/>', {'src': Item.tiny_capsule, 'class': 'bundle_contents_preview_img' } );
if ( i > 0 && bNeedToCollapse )
{
var flPositionRight = 100 * i / rgItemsWithCaps.length;
$Link.addClass( 'floated' ).css( 'left', flPositionRight + '%' ).css('z-index', (rgItemsWithCaps.length - i));
}
$BundleContentsPosition.append( $Link.append( $Img ) );
GStoreItemData.BindHoverEvents( $Img.parent(), Item.appids.length == 1 ? Item.appids[0] : null, rgItemsWithCaps[i] );
}
$Description.append( $BundleContentsCtn.append($BundleContentsPosition) );
GDynamicStore.DecorateDynamicItems( $BundleContentsCtn );
}
},
PopulateRecommendedTagList: function()
{
var $Element = $J('#foryou_yourtags');
if ( !$Element.length )
return; // no menu
$Element.empty();
$Element.css( 'min-height', '' );
for( var i = 0; i < GDynamicStore.s_rgRecommendedTags.length && i < 4; i++ )
{
var tag = GDynamicStore.s_rgRecommendedTags[i];
var url = 'https://store.steampowered.com/tags/zh-cn/' + encodeURIComponent( tag.name );
var $Link = $J('<a/>', {'class': 'popup_menu_item', 'href': GStoreItemData.AddNavEventParamsToURL( url, 'storemenu_recommendedtags' ) });
$Link.text( tag.name );
$Element.append( $Link );
}
},
InvalidateCache: function()
{
WebStorage.SetLocal( 'unUserdataVersion', parseInt( WebStorage.GetLocal( 'unUserdataVersion' ) || 0 ) + 1 );
},
BIsAppOwned: function( appid, optsIn )
{
var opts = { bExcludeIfAutoGrant: true, bExcludeMasterSub: false };
if ( optsIn === false )
$J.extend( opts, { bExcludeIfAutoGrant: false } );
else if ( optsIn )
$J.extend( opts, optsIn );
if ( GDynamicStore.s_rgOwnedApps[appid] )
{
if ( opts.bExcludeIfAutoGrant && this.BIsAutoGrantedApp( appid ) )
return false;
if ( opts.bExcludeMasterSub && this.BIsMasterSubApp( appid ) )
return false;
return true;
}
return false;
},
BIsAutoGrantedApp: function( appid )
{
return GDynamicStore.s_rgAutoGrantApps[appid] ? true : false;
},
BIsMasterSubApp: function( appid )
{
// presence in s_rgMasterSubApps indicates the user owns the app via a subscription like EA Play
// in some places we allow user to repurchase
return GDynamicStore.s_rgMasterSubApps[appid] ? true : false;
},
BIsSalePageAppID: function( appid )
{
return GStoreItemData.rgAppData[ appid ] && GStoreItemData.rgAppData[ appid ].url;
},
BIsPackageOwned: function( packageid )
{
return GDynamicStore.s_rgOwnedPackages[packageid] ? true : false;
},
BIsAppIgnored: function( appid )
{
return ( appid in GDynamicStore.s_rgIgnoredApps );
},
GetIgnoredAppCount: function( )
{
return Object.keys(GDynamicStore.s_rgIgnoredApps).length;
},
GetExcludedTagsOverlap: function( $e )
{
var rgOverlappingTagNames = [];
var rgTagIDs = $e.data( 'dsTagids' );
if ( rgTagIDs && rgTagIDs.length > 0 )
{
for ( var i = 0; i < rgTagIDs.length; ++i )
{
var tagid = rgTagIDs[i];
if ( GDynamicStore.s_rgExcludedTags[tagid] )
{
rgOverlappingTagNames.push( GDynamicStore.s_rgExcludedTags[tagid] );
}
}
}
return rgOverlappingTagNames;
},
GetExcludedContentDescriptorOverlap: function( $e )
{
var rgOverlappingDescIDs = [];
var rgIDs = $e.data( 'dsDescids' );
if ( rgIDs && rgIDs.length > 0 )
{
for ( var i = 0; i < rgIDs.length; ++i )
{
var id = rgIDs[i];
if ( GDynamicStore.s_rgExcludedDescIDs[id] )
{
rgOverlappingDescIDs.push( id );
}
}
}
return rgOverlappingDescIDs;
},
GetExcludedCreatorOverlap: function( $e )
{
var rgOverlappingDescIDs = [];
var rgIDs = $e.data( 'dsCrtrids' );
if ( rgIDs && rgIDs.length > 0 )
{
for ( var i = 0; i < rgIDs.length; ++i )
{
var id = rgIDs[i];
if ( GDynamicStore.s_rgCreatorsIgnored[id] )
{
rgOverlappingDescIDs.push( id );
}
}
}
return rgOverlappingDescIDs;
},
BIsAppOnWishlist: function( appid )
{
return GDynamicStore.s_rgWishlist[appid] ? true: false;
},
BIsPackageIgnored: function( packageid )
{
return ( packageid in GDynamicStore.s_rgIgnoredPackages );
},
GetCuratorForApp: function( unAppID, bOnlyPositive )
{
var curator = null;
if( GDynamicStore.s_rgCurations[unAppID] )
{
var rgFilteredCuratorIDs = [];
$J.each(GDynamicStore.s_rgCurations[unAppID], function( unCuratorID, unRecommendationState ){
if( !bOnlyPositive || unRecommendationState == 0 )
rgFilteredCuratorIDs.push( unCuratorID );
});
if( rgFilteredCuratorIDs.length )
{
var unCuratorID = rgFilteredCuratorIDs[ Math.floor( Math.random() * rgFilteredCuratorIDs.length ) ];
return {
'recommendation_state': GDynamicStore.s_rgCurations[unAppID][unCuratorID],
'curator': GDynamicStore.s_rgCurators[unCuratorID]
}
}
}
return null;
},
GetCurator: function( clanid )
{
return GDynamicStore.s_rgCurators[ clanid ];
},
GetMatchingCreatorFollowed: function( rgAppCreatorRelationship )
{
if( GDynamicStore.s_rgCreatorsFollowed )
{
for( var clanid in rgAppCreatorRelationship )
{
if( rgAppCreatorRelationship.hasOwnProperty( clanid ) && clanid in GDynamicStore.s_rgCreatorsFollowed )
{
return { 'clanid': clanid, 'relationship': rgAppCreatorRelationship[clanid] }
}
}
}
return null;
},
BAreAllAppsOwned: function( rgAppIds, opts )
{
for ( var i = 0; i < rgAppIds.length; i++ )
{
if ( !GDynamicStore.BIsAppOwned( rgAppIds[i], opts ) )
return false;
}
return true;
},
DisplayBundleSimulator: function( unBundleID )
{
if ( !GDynamicStore.m_bLoadComplete )
{
GDynamicStore.OnReady( function() { GDynamicStore.DisplayBundleSimulator( unBundleID ) } );
return;
}
var Bundle = GStoreItemData.rgPersonalizedBundleData[ unBundleID ];
if ( !Bundle )
{
ShowAlertDialog( '', 'Unknown bundle ID ' + unBundleID );
return;
}
var $Form = $J('<form/>', {'class': 'bundle_simulator_form'});
$Form.append( $J('<h2/>').text('标记物品为“已拥有”') );
$Form.append( $J('<div/>', {'class': 'bundle_simulator_secondary' } ).append(
$J('<a/>', {'href': 'javascript:void(0);'} ).click( function() { $Form.find('input').prop('checked', true ); } ).text( "全选" ),
' - ',
$J('<a/>', {'href': 'javascript:void(0);'} ).click( function() { $Form.find('input').prop('checked', false ); } ).text( "全不选" )
) );
for ( var i =0; i < Bundle.m_rgItems.length; i++ )
{
var BundleItem = Bundle.m_rgItems[i];
var id = 'bundle_check_' + i;
var nPackageID = BundleItem.m_nPackageID;
var rgAppIDs = BundleItem.m_rgIncludedAppIDs;
var $Row = $J('<div/>', {'class': 'bundle_simulator_row'} );
var $Checkbox = $J('<input/>', {type: 'checkbox', id: id } );
if ( GDynamicStore.s_rgOwnedPackages[nPackageID] || ( rgAppIDs.length && GDynamicStore.BAreAllAppsOwned( rgAppIDs ) ) )
$Checkbox.prop('checked', true);
$Checkbox.data( 'BundleItem', BundleItem );
$Row.append( $Checkbox );
var strLabel = GStoreItemData.rgPackageData[nPackageID].name;
if ( rgAppIDs.length == 1 )
strLabel = GStoreItemData.rgAppData[rgAppIDs[0]].name;
$Row.append( $J('<label/>', {'for': id } ).text( strLabel ) );
$Form.append( $Row );
}
var Modal;
Modal = ShowConfirmDialog( '', $Form, '更新显示' ).done( function() {
$Form.find('input').each( function() {
var $Checkbox = $J(this);
var BundleItem = $Checkbox.data('BundleItem');
var nPackageID = BundleItem.m_nPackageID;
var rgAppIDs = BundleItem.m_rgIncludedAppIDs;
if ( $Checkbox.prop('checked') )
{
GDynamicStore.s_rgOwnedPackages[nPackageID] = true;
for ( var i = 0; i < rgAppIDs.length; i++ )
GDynamicStore.s_rgOwnedApps[rgAppIDs[i]] = true;
}
else
{
delete GDynamicStore.s_rgOwnedPackages[nPackageID];
for ( var i = 0; i < rgAppIDs.length; i++ )
delete GDynamicStore.s_rgOwnedApps[rgAppIDs[i]];
}
GDynamicStore.s_rgPersonalizedBundleData = {};
GDynamicStore.DecorateDynamicItems( null, true );
});
Modal.GetContent().remove();
});
Modal.SetRemoveContentOnDismissal( false );
}
};
GStoreItemData = {
rgAppData: {},
rgPackageData: {},
rgBundleData: {},
rgPersonalizedBundleData: {},
rgAccountData: [],
rgNavParams: {},
fnFormatCurrency: function( nValueInCents, bWholeNumbersOnly ) { return v_numberformat( nValueInCents / 100, bWholeNumbersOnly !== undefined ? bWholeNumbersOnly : false ); },
nCurrencyMinPriceIncrement : 1,
AddStoreItemDataSet: function( rgStoreItemData )
{
GStoreItemData.AddStoreItemData( rgStoreItemData.rgApps, rgStoreItemData.rgPackages, rgStoreItemData.rgBundles );
},
/**
* @deprecated - use AddStoreItemDataSet instead
*/
AddStoreItemData: function ( rgApps, rgPackages, rgBundles )
{
if ( rgApps && typeof rgApps.length == 'undefined' )
{
for( var appid in rgApps )
{
if ( !GStoreItemData.rgAppData[appid] )
GStoreItemData.rgAppData[appid] = rgApps[appid];
else
GStoreItemData.MergeStoreItemData( GStoreItemData.rgAppData[appid], rgApps[appid] );
}
}
if ( rgPackages && typeof rgPackages.length == 'undefined' )
{
for( var packageid in rgPackages )
{
if ( !GStoreItemData.rgPackageData[packageid] )
GStoreItemData.rgPackageData[packageid] = rgPackages[packageid];
else
GStoreItemData.MergeStoreItemData( GStoreItemData.rgPackageData[packageid], rgPackages[packageid] );
}
}
if ( rgBundles && typeof rgBundles.length == 'undefined' )
{
for( var bundleid in rgBundles )
{
if ( !GStoreItemData.rgBundleData[bundleid] )
GStoreItemData.rgBundleData[bundleid] = rgBundles[bundleid];
else
GStoreItemData.MergeStoreItemData( GStoreItemData.rgBundleData[bundleid], rgBundles[bundleid] );
}
}
},
GetStoreItemDataForElement: function( $el )
{
var unAppId = $el.data('ds-appid');
var unPackageID = $el.data('ds-packageid');
var unBundleID = $el.data('ds-bundleid' );
if ( unBundleID && GStoreItemData.rgBundleData[unBundleID] )
return { bundleid: unBundleID, item: GStoreItemData.rgBundleData[unBundleID] };
if( unPackageID && unPackageID.toString().indexOf(',') !== -1 )
unPackageID = unPackageID.split(',')[0];
if ( unPackageID && GStoreItemData.rgPackageData[unPackageID] )
return { packageid: unPackageID, item: GStoreItemData.rgPackageData[unPackageID] };
if( unAppId && unAppId.toString().indexOf(',') !== -1 )
unAppId = unAppId.split(',')[0];
if ( unAppId && GStoreItemData.rgAppData[unAppId] )
return { appid: unAppId, item: GStoreItemData.rgAppData[unAppId] };
return { item: undefined };
},
AddStoreAccountData: function( rgAccounts )
{
if ( rgAccounts && rgAccounts.length > 0 )
{
for ( var i = 0; i < rgAccounts.length; i++ )
{
GStoreItemData.rgAccountData.push( rgAccounts[i] );
}
}
},
GetAccountData: function( steamid, accountid, type )
{
// Assume individual account unless otherwise specified.
if( !type )
type = 1;
// Search for an accountid instead
for( var i = 0; i < GStoreItemData.rgAccountData.length; i++ )
{
var account = GStoreItemData.rgAccountData[i];
if( account.accountid == accountid && account.type == type )
return account;
}
},
SetCurrencyFormatter: function( fn )
{
GStoreItemData.fnFormatCurrency = fn;
},
SetCurrencyMinPriceIncrement: function( nMinPriceIncrement )
{
GStoreItemData.nCurrencyMinPriceIncrement = nMinPriceIncrement;
},
CalculateCurrencyAppropriatePrice: function( nPrice )
{
if ( GStoreItemData.nCurrencyMinPriceIncrement > 1 )
{
var nRoundingAmount = GStoreItemData.nCurrencyMinPriceIncrement;
var dAmount = nPrice / nRoundingAmount;
// round "half away from zero" - javascript Math.round rounds -1.5 to -1, which is not desired
var dSign = dAmount < 0 ? -1 : 1;
dAmount = ( dSign * Math.floor( Math.abs( dAmount ) + 0.5 ) ) * nRoundingAmount;
nPrice = dAmount;
}
return nPrice;
},
MergeStoreItemData: function( rgExistingItemData, rgItemData )
{
for( var key in rgItemData )
{
if ( !rgExistingItemData[key] )
rgExistingItemData[key] = rgItemData[key];
}
},
AddNavParams: function( rgNavParams )
{
if ( rgNavParams )
$J.extend( GStoreItemData.rgNavParams, rgNavParams );
},
AddNavEventParamsToURL: function( strURL, strFeatureContext, nDepth, nCuratorClanID )
{
var strClanParam = nCuratorClanID ? 'curator_clanid=' + nCuratorClanID : '';
if ( strFeatureContext )
{
if ( !GStoreItemData.rgNavParams[strFeatureContext] )
{
strFeatureContext = '__page_default';
}
if ( GStoreItemData.rgNavParams[strFeatureContext] )
{
var strNavParam = GStoreItemData.rgNavParams[strFeatureContext];
if ( nDepth )
strNavParam += '_' + parseInt( nDepth );
strURL += ( strURL.indexOf( '?' ) != -1 ? '&' : '?' ) + 'snr=' + strNavParam;
if( strClanParam )
strURL += '&' + strClanParam;
}
}
else if( strClanParam )
{
strURL += ( strURL.indexOf( '?' ) != -1 ? '&' : '?' ) + strClanParam;
}
return strURL;
},
GetCurrentPageNavParams: function()
{
return GStoreItemData.rgNavParams['__page_default'];
},
GetAppURL: function( unAppID, strFeatureContext, nDepth, nCuratorClanID)
{
if ( typeof GStoreItemData.rgAppData[ unAppID ] == 'object' && 'url_name' in GStoreItemData.rgAppData[ unAppID ] ) {
return GStoreItemData.AddNavEventParamsToURL( 'https://store.steampowered.com/app/' + unAppID + '/' + GStoreItemData.rgAppData[ unAppID ].url_name + '/', strFeatureContext, nDepth, nCuratorClanID )
}
return GStoreItemData.AddNavEventParamsToURL( 'https://store.steampowered.com/app/' + unAppID + '/', strFeatureContext, nDepth, nCuratorClanID )
},
GetPackageURL: function( unPackageID, strFeatureContext, nDepth )
{
return GStoreItemData.AddNavEventParamsToURL( 'https://store.steampowered.com/sub/' + unPackageID + '/', strFeatureContext, nDepth )
},
GetBundleURL: function( unBundleID, strFeatureContext, nDepth )
{
return GStoreItemData.AddNavEventParamsToURL( 'https://store.steampowered.com/bundle/' + unBundleID + '/', strFeatureContext, nDepth )
},
GetHoverParams: function ( unAppID, unPackageID, unBundleID )
{
var hoverparams;
if ( unBundleID )
hoverparams = { type: 'bundle', id: unBundleID };
else if ( unPackageID )
hoverparams = { type: 'sub', id: unPackageID };
else if ( unAppID )
hoverparams = { type: 'app', id: unAppID };
else
return null;
hoverparams.v6 = 1;
return hoverparams;
},
BindHoverEvents: function( $Element, unAppID, unPackageID, unBundleID, rgAdditionalParams )
{
$Element.mouseenter( function( event ) {
GameHover( this, event, $J('#global_hover'), $J.extend( GStoreItemData.GetHoverParams( unAppID, unPackageID, unBundleID ), rgAdditionalParams || {}) );
}).mouseleave( function( event ) {
HideGameHover( this, event, $J('#global_hover') );
});
},
BindHoverEventsForItem: function( $Element, oItem, rgAdditionalParams )
{
return GStoreItemData.BindHoverEvents( $Element, oItem.appid, oItem.packageid, oItem.bundleid, rgAdditionalParams );
},
GetCapParamsForItem: function( strFeatureContext, rgItem, params, nDepth )
{
return GStoreItemData.GetCapParams( strFeatureContext, rgItem.appid, rgItem.packageid, rgItem.bundleid, params, nDepth );
},
GetCapParams: function( strFeatureContext, unAppID, unPackageID, unBundleID, params, nDepth )
{
var rgItemData = null;
if( unAppID )
rgItemData = GStoreItemData.rgAppData[ unAppID ];
else if( unPackageID )
rgItemData = GStoreItemData.rgPackageData[ unPackageID ];
else if( unBundleID )
rgItemData = GStoreItemData.rgBundleData[ unBundleID ];
if ( !rgItemData )
return null;
if ( !unAppID && rgItemData['appids'] && rgItemData['appids'].length == 1 )
{
unAppID = rgItemData['appids'][0];
}
if ( rgItemData['steam_deck_compat_category'] !== undefined )
{
params['data-ds-steam-deck-compat-category'] = rgItemData['steam_deck_compat_category'];
}
if ( unAppID )
{
params['data-ds-appid'] = unAppID;
params[ 'href' ] = GStoreItemData.GetAppURL( unAppID, strFeatureContext, nDepth, params['curator_clanid'] );
}
else if ( unPackageID )
{
params['data-ds-packageid'] = unPackageID;
params['href'] = GStoreItemData.GetPackageURL( unPackageID, strFeatureContext, nDepth );
if ( rgItemData['appids'] )
{
params['data-ds-appid'] = rgItemData['appids'].join( ',' );
}
}
else if ( unBundleID )
{
params['data-ds-bundleid'] = unBundleID;
params['href'] = GStoreItemData.GetBundleURL( unBundleID, strFeatureContext, nDepth );
if ( rgItemData['appids'] )
{
params['data-ds-appid'] = rgItemData['appids'].join( ',' );
}
}
if ( rgItemData.tagids && rgItemData.tagids.length != 0 )
{
params['data-ds-tagids'] = '[' + rgItemData['tagids'].join( ',' ) + ']';
}
if ( rgItemData.descids && rgItemData.descids.length != 0 )
{
params['data-ds-descids'] = '[' + rgItemData['descids'].join( ',' ) + ']';
}
// override with item-specific URL
if ( rgItemData.url )
params['href'] = GStoreItemData.AddNavEventParamsToURL( rgItemData.url, strFeatureContext, nDepth, params['curator_clanid'] );
return rgItemData;
},
BuildSupportedPlatformIcon: function( rgItemData )
{
var strHTML = '';
var nPlatforms = 0;
if ( rgItemData.video )
{
strHTML += '<span class="platform_img streamingvideo"></span>';
}
else if ( rgItemData.video360 )
{
strHTML += '<span class="platform_img streaming360video"></span>';
}
else if ( rgItemData.videoseries )
{
strHTML += '<span class="platform_img streamingvideoseries"></span>';
}
else
{
if ( rgItemData.os_windows )
{
strHTML += '<span class="platform_img win"></span>';
nPlatforms++;
}
if ( rgItemData.os_macos )
{
strHTML += '<span class="platform_img mac"></span>';
nPlatforms++;
}
if ( rgItemData.os_linux )
{
strHTML += '<span class="platform_img linux"></span>';
nPlatforms++;
}
}
if ( rgItemData.vr_htcvive || rgItemData.vr_oculusrift || rgItemData.vr_windowsmr )
{
strHTML += '<span class="platform_img hmd_separator"></span>';
if ( rgItemData.vr_htcvive )
{
strHTML += '<span class="platform_img htcvive"></span>';
}
if ( rgItemData.vr_oculusrift )
{
strHTML += '<span class="platform_img oculusrift"></span>';
}
if ( rgItemData.vr_windowsmr )
{
strHTML += '<span class="platform_img windowsmr"></span>';
}
}
return strHTML;
},
// filtering
FilterItemsForDisplay: function( rgItems, Settings, ApplicableSettings, cMaxItemsToDisplay, cMinItemsToDisplay )
{
var rgStrictItems = [], rgGoodItems = [], rgOtherItems = [], rgBadItems = [];
var fnAnnotatePriority = function ( item, priority ) { return item; };
if ( Settings.include_priority )
fnAnnotatePriority = function ( item, priority ) { item.priority = priority; return item; };
if ( !cMaxItemsToDisplay )
cMaxItemsToDisplay = rgItems.length;
for ( var i = 0; i < rgItems.length; i++ )
{
var oItem = rgItems[i];
var unAppID = null, unPackageID = null, unBundleID = null;
if ( oItem instanceof Object )
{
if ( oItem.appid )
unAppID = oItem.appid;
else if ( oItem.packageid )
unPackageID = oItem.packageid;
else if ( oItem.bundleid )
unBundleID = oItem.bundleid;
}
if ( unAppID )
{
if ( GStoreItemData.BAppPassesFilters( unAppID, Settings, ApplicableSettings, true ) )
rgStrictItems.push(oItem);
else if ( GStoreItemData.BAppPassesFilters( unAppID, Settings, ApplicableSettings ) )
rgGoodItems.push(oItem);
else if ( !GDynamicStore.BIsAppIgnored( unAppID ) )
rgOtherItems.push( oItem );
else
rgBadItems.push( oItem );
}
else if ( unPackageID )
{
if ( GStoreItemData.BPackagePassesFilters( unPackageID, Settings, ApplicableSettings, true ) )
rgStrictItems.push( oItem );
else if ( GStoreItemData.BPackagePassesFilters( unPackageID, Settings, ApplicableSettings ) )
rgGoodItems.push( oItem );
else if ( !GDynamicStore.BIsPackageIgnored( unPackageID ) )
rgOtherItems.push( oItem );
else
rgBadItems.push( oItem );
}
else if ( unBundleID )
{
if ( GStoreItemData.BBundlePassesFilters( unBundleID, Settings, ApplicableSettings, true ) )
rgStrictItems.push( oItem );
else if ( GStoreItemData.BBundlePassesFilters( unBundleID, Settings, ApplicableSettings ) )
rgGoodItems.push( oItem );
else
rgOtherItems.push( oItem );
}
if ( rgStrictItems.length >= cMaxItemsToDisplay)
break;
}
if ( Settings.include_priority )
rgStrictItems.forEach( function( item ) { fnAnnotatePriority( item , 1 ); } );
if ( cMinItemsToDisplay )
{
for ( i = 0; rgStrictItems.length < cMinItemsToDisplay && i < rgGoodItems.length; i++ )
{
rgStrictItems.push( fnAnnotatePriority( rgGoodItems[i], 2 ) );
}
if ( Settings.enforce_minimum )
{
for ( i = 0; rgStrictItems.length < cMinItemsToDisplay && i < rgOtherItems.length; i++ )
rgStrictItems.push( fnAnnotatePriority( rgOtherItems[i], 3 ) );
for ( i = 0; rgStrictItems.length < cMinItemsToDisplay && i < rgBadItems.length; i++ )
rgStrictItems.push( fnAnnotatePriority( rgBadItems[i], 4 ) );
}
}
return rgStrictItems;
},
BItemPassesFilters: function( rgItemData, Settings, ApplicableSettings )
{
if ( ApplicableSettings.only_current_platform && Settings.only_current_platform )
{
if ( ( !GDynamicStore.s_bUserOnMacOS || ( GDynamicStore.s_bUserOnMacOS && !rgItemData.os_macos ) ) &&
( !GDynamicStore.s_bUserOnLinux || ( GDynamicStore.s_bUserOnLinux && !rgItemData.os_linux ) ) &&
( !GDynamicStore.s_bUserOnWindows || ( GDynamicStore.s_bUserOnWindows && !rgItemData.os_windows ) &&
( GDynamicStore.s_bUserOnWindows || GDynamicStore.s_bUserOnMacOS || GDynamicStore.s_bUserOnLinux ) )
)
return false;
}
if ( rgItemData.coming_soon && ApplicableSettings.prepurchase && !Settings.prepurchase )
return false;
return true;
},
BAppPassesFilters: function( appid, Settings, ApplicableSettings, bStrict )
{
var rgAppData = GStoreItemData.rgAppData[appid];
if ( !rgAppData )
return false;
// TODO: score based on filter misses and sort by score?
if ( !GStoreItemData.BItemPassesFilters( rgAppData, Settings, ApplicableSettings ) )
return false;
if ( GDynamicStore.BIsAppIgnored( appid ) )
return false;
if ( rgAppData.early_access && ApplicableSettings.early_access && !Settings.early_access )
return false;
if ( rgAppData.software && ApplicableSettings.software && !Settings.software )
return false;
if ( rgAppData.dlc && ( ( ApplicableSettings.dlc && !Settings.dlc ) ||
( ApplicableSettings.dlc_for_you && ( !Settings.dlc_for_you || !rgAppData.dlc_for_app || !GDynamicStore.BIsAppOwned( rgAppData.dlc_for_app, false ) ) ) ) )
return false;
if ( ApplicableSettings.games_already_in_library && !Settings.games_already_in_library && GDynamicStore.BIsAppOwned( appid ) )
return false;
if ( ApplicableSettings.games_not_in_library && !Settings.games_not_in_library && !GDynamicStore.BIsAppOwned( appid ) )
return false;
if ( rgAppData.video && ApplicableSettings.video && !Settings.video )
return false;
// for the strict mode, we check if the app supports any of the preferred languages
if ( bStrict && !rgAppData.localized && ApplicableSettings.localized && Settings.localized )
return false;
// for the non-strict pass, we only check English, even if not part of user preferences.
// we no longer show random language games if the view calls for localized items
if ( !bStrict && !rgAppData.localized_english && ApplicableSettings.localized && Settings.localized )
return false;
if ( bStrict && ApplicableSettings.displayed_elsewhere && !Settings.displayed_elsewhere && GDynamicStore.s_rgDisplayedApps.indexOf( appid ) !== -1 )
return false;
if ( rgAppData.virtual_reality && ApplicableSettings.virtual_reality && !Settings.virtual_reality )
return false;
if ( ApplicableSettings.not_wishlisted && !Settings.not_wishlished && GDynamicStore.BIsAppOnWishlist( appid ) )
return false;
if ( ApplicableSettings.has_discount && Settings.has_discount )
{
if ( !rgAppData.discount_block || rgAppData.discount_block.includes('no_discount') )
{
return false;
}
}
if ( rgAppData.demo_for_app )
{
// skip demos for games that are already owned
if ( ApplicableSettings.games_already_in_library && !Settings.games_already_in_library && GDynamicStore.BIsAppOwned( rgAppData.demo_for_app ) )
return false;
if ( bStrict && ApplicableSettings.displayed_elsewhere && !Settings.displayed_elsewhere && GDynamicStore.s_rgDisplayedApps.indexOf( rgAppData.demo_for_app ) !== -1 )
return false;
}
if ( rgAppData.tagids && rgAppData.tagids.length != 0 )
{
for ( var i = 0; i < rgAppData.tagids.length; ++i )
{
var tagid = rgAppData.tagids[i];
if ( GDynamicStore.s_rgExcludedTags[tagid] )
{
return false;
}
}
}
if ( rgAppData.descids && rgAppData.descids.length != 0 )
{
for ( var i = 0; i < rgAppData.descids.length; ++i )
{
var id = rgAppData.descids[i];
if ( GDynamicStore.s_rgExcludedDescIDs[id] )
{
return false;
}
}
}
// If a non-empty list of explicit tag exclusions is specified, reject apps with one of those tags
if ( ApplicableSettings.explicitly_excluded_tags && Settings.explicitly_excluded_tags.length > 0 && rgAppData.tagids )
{
for ( var i = 0; i < rgAppData.tagids.length; ++i )
{
var tagid = rgAppData.tagids[i];
if ( Settings.explicitly_excluded_tags.includes( tagid ) )
{
return false;
}
}
}
// If a non-empty list of explicit tag inclusions is specified, reject apps which don't have one of those tags
if ( ApplicableSettings.explicitly_included_tags && Settings.explicitly_included_tags.length > 0 )
{
if ( !rgAppData.tagids )
{
return false;
}
for ( var i = 0; i < Settings.explicitly_included_tags.length; ++i )
{
var tagid = Settings.explicitly_included_tags[i];
if ( !rgAppData.tagids.includes( tagid ) )
{
return false;
}
}
}
if ( rgAppData.no_main_cap )
return false;
return true;
},
BPackagePassesFilters: function( packageid, Settings, ApplicableSettings, bStrict )
{
var rgPackageData = GStoreItemData.rgPackageData[ packageid ];
if ( !rgPackageData )
return false;
if ( !GStoreItemData.BItemPassesFilters( rgPackageData, Settings, ApplicableSettings, bStrict ) )
return false;
if ( !GStoreItemData.BAppIDSetPassesFilters( rgPackageData.appids, Settings, ApplicableSettings, bStrict ) )
return false;
if ( ApplicableSettings.games_already_in_library && !Settings.games_already_in_library && GDynamicStore.BIsPackageOwned( packageid ) )
return false;
if ( GDynamicStore.BIsPackageIgnored( packageid ) )
return false;
return true;
},
BBundlePassesFilters: function( bundleid, Settings, ApplicableSettings, bStrict )
{
var rgBundleData = GStoreItemData.rgBundleData[ bundleid ];
if ( !rgBundleData )
return false;
if( !GStoreItemData.BItemPassesFilters( rgBundleData, Settings, ApplicableSettings, bStrict ) )
return false;
if ( !GStoreItemData.BAppIDSetPassesFilters( rgBundleData.appids, Settings, ApplicableSettings, bStrict ) )
return false;
if ( bStrict && ApplicableSettings.displayed_elsewhere && !Settings.displayed_elsewhere && GDynamicStore.s_rgDisplayedBundles.indexOf( bundleid ) !== -1 )
return false;
return true;
},
BAppIDSetPassesFilters: function( appids, Settings, ApplicableSettings, bStrict )
{
// figure out state of apps
var bAnyAppsOwned = false;
for ( var i = 0; i < appids.length; i++ )
{
if ( GDynamicStore.BIsAppIgnored( appids[i] ) )
return false;
if ( GDynamicStore.BIsAppOwned( appids[i] ) )
{
bAnyAppsOwned = true;
}
}
if ( ApplicableSettings.games_already_in_library && !Settings.games_already_in_library )
{
// any app being owned excludes the package; packages don't grant extra copies (usually)
if ( bAnyAppsOwned )
return false;
}
if ( ApplicableSettings.games_not_in_library && !Settings.games_not_in_library )
{
if ( !bAnyAppsOwned )
return false;
}
return true;
},
};
var GDynamicStoreHelpers = {
BuildCapsuleHTML: function( strFeatureContext, unAppID, unPackageID, unBundleID, rgOptions )
{
var rgOptions = $J.extend({
'class': 'store_capsule',
'include_title': false,
'discount_class': 'discount_block_inline',
'capsule_size': 'headerv5',
'html_before_price': '',
'lazy': false,
'curator': false
}, rgOptions ? rgOptions : {} );
var params = { 'class': rgOptions.class };
if( rgOptions.curator )
params['curator_clanid'] = rgOptions.curator
var rgItemData = GStoreItemData.GetCapParams( strFeatureContext, unAppID, unPackageID, unBundleID, params );
if ( !rgItemData )
{
return null;
}
var $CapCtn = $J('<a/>', params );
if ( !rgOptions.no_hover )
GStoreItemData.BindHoverEvents( $CapCtn, unAppID, unPackageID );
var $ImgCtn = $J('<div class="capsule"/>').addClass( rgOptions.capsule_size );
var rgImageProperties = { src: rgItemData[rgOptions.capsule_size] };
if( rgOptions.lazy )
rgImageProperties = { 'data-image-url': rgItemData[rgOptions.capsule_size] }
$ImgCtn.append( $J('<img/>', rgImageProperties ) );
$CapCtn.append( $ImgCtn );
if( rgOptions.include_title )
$CapCtn.append( $J('<div/>', {'class': 'title ellipsis' } ).html( rgItemData.name ) );
if( rgOptions.html_before_price )
$CapCtn.append( rgOptions.html_before_price );
if( rgItemData.has_live_broadcast )
$CapCtn.append( $J( '<div>').addClass( 'broadcast_live_stream_icon' ).append( '直播' ) );
$CapCtn.append( $J('<div/>').html( rgItemData.discount_block ? $J(rgItemData.discount_block).addClass( rgOptions.discount_class ) : '&nbsp;' ) );
return $CapCtn;
},
FillPagedCapsuleCarousel: function( rgCapsules, $elTarget, fnCapsule, strNavContext, nCapsules, rgBaseOptions )
{
var $elCapsuleTarget = $J('.carousel_items', $elTarget);
var $elThumbTarget = $J('.carousel_thumbs', $elTarget);
var bPaginated = !$elCapsuleTarget.hasClass('no_paging');
if ( !bPaginated )
nCapsules = rgCapsules.length;
if( rgCapsules.length < nCapsules )
return;
for( var j=0; j<rgCapsules.length; j+=nCapsules )
{
// Try to avoid half-filling a page
if( j > 0 && j+nCapsules > rgCapsules.length && bPaginated )
break;
var $elPageContainer = $J('<div>');
for( var k=0; k < nCapsules && k + j < rgCapsules.length; k++ )
{
var oItem = rgCapsules[ k + j ];
var rgOptions = $J.extend({}, rgBaseOptions);
if( j > 0 )
rgOptions.lazy = true;
var $CapCtn = fnCapsule( oItem, strNavContext, rgOptions );
if( !$CapCtn )
continue;
// Don't try to do automatic visibilty tracking on non-visible clusters.
if( j > 0 )
$CapCtn.attr('data-manual-tracking', 1);
$elPageContainer.append( $CapCtn );
}
$elCapsuleTarget.append($elPageContainer);
if ( bPaginated )
{
$elThumbTarget.append($J('<div/>'));
}
}
if ( $elCapsuleTarget.children().length > 0 )
{
$elCapsuleTarget.InstrumentLinks();
GDynamicStore.DecorateDynamicItems( $elCapsuleTarget );
$elTarget.show();
}
if ( bPaginated )
CreateFadingCarousel( $elTarget, 0 );
},
AddSNRDepthParamsToCapsuleList: function( $Capsules )
{
var nDepth = 1;
$Capsules.filter('a:visible').each( function() {
ModifyLinkSNR( $J(this), function( snr ) {
var rgParts = snr.split('_');
rgParts[5] = nDepth++;
return rgParts.join('_');
});
});
}
};
function OnDynamicStorePageException(e)
{
}
GDynamicStorePage = {
oSettings: {},
oApplicableSettings: {"main_cluster":{"top_sellers":true,"early_access":true,"games_already_in_library":true,"recommended_for_you":true,"prepurchase":true,"games":"always","software":true,"dlc_for_you":true,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":"always","games_not_in_library":null,"only_current_platform":true,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":true,"hidden":null},"new_on_steam":{"top_sellers":null,"early_access":true,"games_already_in_library":true,"recommended_for_you":null,"prepurchase":null,"games":"always","software":true,"dlc_for_you":null,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":null,"games_not_in_library":null,"only_current_platform":true,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":null,"hidden":null},"recently_updated":{"top_sellers":null,"early_access":true,"games_already_in_library":null,"recommended_for_you":null,"prepurchase":null,"games":"always","software":true,"dlc_for_you":null,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":null,"games_not_in_library":true,"only_current_platform":true,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":null,"hidden":null},"tabs":null,"specials":null,"more_recommendations":null,"friend_recommendations":null,"curators":{"top_sellers":null,"early_access":true,"games_already_in_library":true,"recommended_for_you":null,"prepurchase":null,"games":"always","software":true,"dlc_for_you":null,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":null,"games_not_in_library":null,"only_current_platform":true,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":null,"hidden":null},"home":{"top_sellers":null,"early_access":true,"games_already_in_library":null,"recommended_for_you":null,"prepurchase":true,"games":null,"software":true,"dlc_for_you":null,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":null,"games_not_in_library":null,"only_current_platform":null,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":null,"hidden":null}},
InitUserData: function( rgParams )
{
try {
GDynamicStorePage.oSettings = rgParams.oSettings;
} catch( e ) { OnDynamicStorePageException(e); }
},
ItemIDFromCapsule: function( $capsule )
{
var unPackageId = $capsule.data('ds-packageid');
var unBundleId = $capsule.data('ds-bundleid');
var unAppId = $capsule.data('ds-appid');
// bundles set package and appids, packages set appids, so start from the outside and work down.
if ( unBundleId )
return { bundleid: unBundleId };
else if ( unPackageId)
return { packageid: unPackageId };
else if ( unAppId )
return { appid: unAppId };
return null;
},
BItemValid: function( item, oShownItems, opts )
{
var unAppId = item.appid;
if( unAppId )
{
var rgAppIds = [ unAppId ];
if( unAppId.toString().indexOf(',') !== -1 )
{
rgAppIds = unAppId.toString().split( ',' );
unAppId = rgAppIds[0];
}
for ( var i = 0; i < rgAppIds.length; i++ )
{
if ( oShownItems.rgAppIds.indexOf( rgAppIds[i] ) !== -1 )
{
return false;
}
}
if ( opts && opts.filter_dlc )
{
var rgAppData = GStoreItemData.rgAppData[unAppId];
// Treat DLC as the base app; so we either show the DLC or the base game; but only one (and whichever is in top position).
// If the user owns the base game already, only show the DLC
if ( rgAppData && rgAppData.dlc_for_app )
{
if ( !GDynamicStore.BIsAppOwned( rgAppData.dlc_for_app, false ) )
{
return false;
}
oShownItems.rgAppIds.push( rgAppData.dlc_for_app );
}
}
for ( var i = 0; i < rgAppIds.length; i++ )
oShownItems.rgAppIds.push( rgAppIds[i] );
}
if ( item.packageid )
{
if ( oShownItems.rgPackageIds.indexOf( item.packageid ) !== -1 )
return false;
oShownItems.rgPackageIds.push( item.packageid );
}
if ( item.bundleid )
{
if ( oShownItems.rgBundleIds.indexOf( item.bundleid ) !== -1 )
return false;
oShownItems.rgBundleIds.push( item.bundleid );
}
return true;
},
FilterCapsules: function( nMin, nMax, $elElements, $elContainer, rgFilterParams, bFilterDLC )
{
// by default we want to filter DLC
if ( bFilterDLC === undefined )
bFilterDLC = true;
// Get a list of appids to filter
var rgItems = [];
var oShownItems = { rgAppIds: [], rgPackageIds: [], rgBundleIds: [] };
var rgValidElements = [];
// Remove duplicates or DLC from the list
for( var i = 0; i < $elElements.length; i++ )
{
var $capsule = $J( $elElements[i] );
var item = GDynamicStorePage.ItemIDFromCapsule( $capsule );
if ( !item )
continue;
if ( GDynamicStorePage.BItemValid( item, oShownItems, { filter_dlc: bFilterDLC } ) )
{
rgItems.push( item );
rgValidElements.push( $capsule );
}
else
$capsule.remove();
}
// Filter
var rgFilteredItems = this.FilterItemsForDisplay(
rgItems, 'home', nMin, nMax, rgFilterParams
);
// Now follow filters as long we we can keep 4 items in the capsule
for( var i = 0; i < rgValidElements.length; i++ )
{
var $capsule = rgValidElements[i];
var item = GDynamicStorePage.ItemIDFromCapsule( $capsule );
if ( !item )
continue;
// Test our filtered list
var bVisible = false;
if ( this.GetItemFromList( item, rgFilteredItems ) )
bVisible = true;
if( bVisible )
{
$capsule.removeClass('hidden');
if ( $capsule.parent().length )
{
$capsule.parent().append( $capsule );
}
else
{
$J( $elContainer ).append( $capsule );
}
}
else
$capsule.remove();
}
GDynamicStoreHelpers.AddSNRDepthParamsToCapsuleList( $elElements );
$elElements.parent().trigger('v_contentschanged');
//if( nCapsules < nMin && $elContainer )
// $elContainer.hide();
},
GetItemFromList: function( oItem, rgList )
{
if( rgList )
{
for ( var i = 0; i < rgList.length; i++ )
{
if ( oItem.bundleid && rgList[i].bundleid == oItem.bundleid )
{
return rgList[i];
}
else if ( oItem.packageid && rgList[i].packageid == oItem.packageid )
{
return rgList[i];
}
else if ( oItem.appid && rgList[i].appid == oItem.appid )
{
return rgList[i];
}
}
}
return null;
},
FilterItemsForDisplay: function( rgItems, strSettingsName, cMinItemsToDisplay, cMaxItemsToDisplay, rgAdditionalSettings )
{
var Settings = this.oSettings[strSettingsName] || {};
var ApplicableSettings = this.oApplicableSettings[strSettingsName] || {};
// Allow sections to have additional, section-specific settings. We'll use jQuery to shallow copy the settings
// object so we don't pollute future calls.
if( rgAdditionalSettings )
{
Settings = jQuery.extend({}, Settings, rgAdditionalSettings);
// Ensure our feature is turned on as an applicable setting
for( var strKey in rgAdditionalSettings )
rgAdditionalSettings[strKey] = true;
ApplicableSettings = jQuery.extend({}, ApplicableSettings, rgAdditionalSettings);
}
if ( !cMaxItemsToDisplay )
cMaxItemsToDisplay = cMinItemsToDisplay;
return GStoreItemData.FilterItemsForDisplay( rgItems, Settings, ApplicableSettings, cMaxItemsToDisplay, cMinItemsToDisplay )
},
FilterAndPrioritizeCapsules: function( rgCapsules, strPriorityListKey, strSettingsName, AdditionalSettings, oShownItems, cMinItemsToDisplay )
{
// initialize the shown item list
if ( !oShownItems.rgAppIds )
$J.extend( oShownItems, { rgAppIds: [], rgPackageIds: [], rgBundleIds: [] }, oShownItems );
var rgPriorityList = g_rgAppPriorityLists[strPriorityListKey] || [];
var rgItems = [];
var rgUnidentifiedCaps = [];
for ( var i = 0; i < rgCapsules.length; i++ )
{
var $capsule = $J( rgCapsules[i] );
var itemid = GDynamicStorePage.ItemIDFromCapsule( $capsule );
if ( !itemid || ( itemid.appid && GDynamicStore.BIsSalePageAppID( itemid.appid ) ) )
{
// if there's no item associated, preseve it; it's probably a sale page or event
rgUnidentifiedCaps.push( i );
continue;
}
else if ( !GDynamicStorePage.BItemValid( itemid, oShownItems ) )
{
// duplicate
continue;
}
itemid.capsule = $capsule;
rgItems.push( itemid );
}
rgItems = SortItemListByPriorityList( rgItems, strPriorityListKey );
var rgItems = GDynamicStorePage.FilterItemsForDisplay( rgItems, strSettingsName, cMinItemsToDisplay, rgItems.length, AdditionalSettings );
// splce anything we don't have info about back in the list in the same position it was before.
for ( var i = 0; i < rgUnidentifiedCaps.length; i++ )
{
var $capsule = $J( rgCapsules[ rgUnidentifiedCaps[i] ] );
rgItems.splice( rgUnidentifiedCaps[i], 0, { capsule: $capsule, priority: 1 } );
}
return rgItems;
},
FilterAndPrioritizeItems: function( rgItems, strPriorityListKey, strSettingsName, AdditionalSettings, oShownItems, cMinItemsToDisplay )
{
// initialize the shown item list
if ( !oShownItems.rgAppIds )
$J.extend( oShownItems, { rgAppIds: [], rgPackageIds: [], rgBundleIds: [] }, oShownItems );
// first filter out items that are dupes of ones we've already been asked to list
var rgItemsNoDupes = [];
for( var i = 0; i < rgItems.length; i++ )
{
if ( GDynamicStorePage.BItemValid( rgItems[i], oShownItems ) )
rgItemsNoDupes.push( rgItems[i] );
}
rgItemsNoDupes = SortItemListByPriorityList( rgItemsNoDupes, strPriorityListKey );
return GDynamicStorePage.FilterItemsForDisplay( rgItemsNoDupes, strSettingsName, cMinItemsToDisplay, rgItemsNoDupes.length, AdditionalSettings );
},
};
var g_rgAppPriorityLists = {};
var g_rgAppPriorityListMaps = {};
function InitAppPriorityLists( rgAppPriorityLists )
{
$J.extend( g_rgAppPriorityLists, rgAppPriorityLists );
}
function GetAppPriorityListMap( strPriorityListName )
{
if ( !g_rgAppPriorityListMaps[strPriorityListName] )
{
var rgAppPriorityList = g_rgAppPriorityLists[strPriorityListName] || [];
var rgPositionByApp = {};
for ( var i = 0; i < rgAppPriorityList.length; i++ )
rgPositionByApp[ ItemKey( rgAppPriorityList[i] ) ] = i;
g_rgAppPriorityListMaps[strPriorityListName] = rgPositionByApp;
}
return g_rgAppPriorityListMaps[strPriorityListName];
}
function ItemKey( rgItem )
{
if ( rgItem.appid )
return 'a' + rgItem.appid;
else if ( rgItem.packageid )
return 'p' + rgItem.packageid;
else if ( rgItem.bundleid )
return 'b' + rgItem.bundleid;
return 'unknown';
}
function SortItemListByPriorityList( rgItemList, strPriorityListName )
{
var rgPositionByApp = GetAppPriorityListMap( strPriorityListName );
if ( !rgPositionByApp )
return rgItemList.slice();
/*
javascript sort is stable (except in IE), so this shouldn't be needed
for ( var i = 0; i < rgItemList.length; i++ )
{
var key = ItemKey( rgItemList[i] );
if ( key && rgPositionByApp[key] === undefined )
rgPositionByApp[key] = i + 1000;
}
*/
var rgItemListSorted = rgItemList.slice();
rgItemListSorted.sort( function( a, b ) {
var posA = rgPositionByApp[ ItemKey( a ) ];
var posB = rgPositionByApp[ ItemKey( b ) ];
return ( posA !== undefined ? posA : 1000 ) - ( posB !== undefined ? posB : 1000 );
});
DEBUG_LogItemList( strPriorityListName, rgItemListSorted );
return rgItemListSorted;
}
function DEBUG_LogItemList( strListName, rgItems )
{
}
function ShowHowDoDiscoveryQueuesWorkDialog()
{
$J.get(
'https://store.steampowered.com/explore/howitworks/',
{
l : 'schinese'
},
function( data )
{
ShowAlertDialog( '它是如何运作的?', data );
}
);
}
function GetAvatarURL( strHash, strSize )
{
return "https:\/\/avatars.st.dl.eccdnx.com\/" + strHash + strSize + '.jpg';
}
function GetScreenshotURL( appid, filename, sizeStr )
{
if( sizeStr )
return 'https://media.st.dl.eccdnx.com/steam/' + 'apps/' + appid + '/' + filename.replace('.jpg', sizeStr + '.jpg');
return 'https://media.st.dl.eccdnx.com/steam/' + 'apps/' + appid + '/' + filename;
}
function UpdatePricesForAdditionalCartDiscount( $Selector, nCartDiscount )
{
if ( !nCartDiscount )
return;
var strSelector = '[data-price-final]';
var $DynamicElements = [];
if ( $Selector )
{
if ( $Selector.is( strSelector ) )
$DynamicElements = $Selector;
else
$DynamicElements = $Selector.find( strSelector );
}
else
{
$DynamicElements = $J( strSelector );
}
for ( var i = 0; i < $DynamicElements.length; i++ )
{
$element = $J($DynamicElements[i]);
var nFinalPrice = parseInt($element.attr('data-price-final'));
$element.addClass( 'additional_cart_discount_container' );
if (!nFinalPrice)
continue;
var nToSubtract = Math.min(nFinalPrice, nCartDiscount);
var bNowFree = (nFinalPrice - nToSubtract) <= 0;
var strAdditionalDiscountBlock = '<div class="additional_cart_discount_amount">-' + GStoreItemData.fnFormatCurrency(nToSubtract) + '</div>';
var strFinalPriceBlock = '<div class="additional_cart_discount_final">' + GStoreItemData.fnFormatCurrency(nFinalPrice - nToSubtract) + '</div>';
var $additionalDiscount = $J('<div class="additional_cart_discount' + ( bNowFree ? ' NowFree"' : '"' ) + ' data-test="' + nFinalPrice + '">' + strAdditionalDiscountBlock + strFinalPriceBlock + '</div>');
//var $additionalDiscount = $J('<div class="additional_cart_discount" data-test="' + nFinalPrice + '">' + strAdditionalDiscountBlock + strFinalPriceBlock + '</div>');
var $basePriceStrikeout = $J('<div class="basePriceStrikeout" />');
$element.append($basePriceStrikeout);
$element.append($additionalDiscount);
}
}
function UpdateStoreBannerForAdditionalCartDiscount( nCartDiscount )
{
if ( !nCartDiscount )
return false;
var strTemplate = ' \
<div class="placeHolder_lunarSale2019_giftActiveBar"> \
<div class="lunarSale2019_contentContainer"> \
<div class="lunar_sale_poinks01"> \
<div class="lunar_sale_sparkle sparkle01"> \
<div class="sparkleStar star1"></div> \
<div class="sparkleStar star2"></div> \
<div class="sparkleStar star3"></div> \
</div> \
</div> \
<div class="lunar_sale_title"><img src="https://store.st.dl.eccdnx.com/public/images/promo/lunar2019/lny2019_title_zh-cn.png"/></div> \
<div class="lunar_sale_spacer lunar_leftspacer"></div> \
<div class="lunar_sale_supersavings_label"><div class="highlight">%header%</div><div class="subtitle">%discount%</div></div> \
<div class="lunar_sale_spacer lunar_rightspacer">\
<div class="lunar_sale_poinks02"> \
<div class="lunar_sale_sparkle sparkle02"> \
<div class="sparkleStar star1"></div> \
<div class="sparkleStar star2"></div> \
<div class="sparkleStar star3"></div> \
</div> \
</div> \
</div> \
</div> \
</div> \
';
var strAmount = GStoreItemData.fnFormatCurrency( nCartDiscount );
var strHeader = '激活奖励省钱模式';
var strDiscount = '您的购物车将省 %amount%!'.replace( '%amount%', strAmount );
strTemplate = strTemplate.replace( '%header%', strHeader );
strTemplate = strTemplate.replace( '%discount%', strDiscount );
$Elements = $J( '[data-cart-banner-spot]' );
for ( var i = 0; i < $Elements.length; i++ )
{
$element = $J( $Elements[i] );
$element.replaceWith( $J( strTemplate ) );
}
return true;
}
function UpdateStoreBannerForPromotionalDiscount( nDiscount, nMinCartAmount, nAvailableUseCount )
{
if ( !nDiscount )
return false;
if ( !nAvailableUseCount )
return false;
if ( $J( 'body.events_hub' ).length )
return false;
var strTemplate = ' \
<div id="promo_header_banner" class="placeHolder_summerSale2020_promotionDetailsBar" style="display:none;"> \
<div class="summerSale2020_contentContainer"> \
<div class="summersale2020_supersavings_title">%title%</div> \
<div class="summersale2020_supersavings_label"><div class="highlight">%header%</div><div class="subtitle">%discount%</div></div> \
</div> \
</div> \
</div> \
';
var strAmount = GStoreItemData.fnFormatCurrency( nDiscount, true );
var strMinAmount = GStoreItemData.fnFormatCurrency( nMinCartAmount, true );
var strTitle = '公路旅行特别优惠';
var strHeader = '购物满 %min_amount%,额外再省 %amount%'.replace( '%amount%', strAmount ).replace( '%min_amount%', strMinAmount );
var strDiscount = '折扣于付款时应用';
strTemplate = strTemplate.replace( '%title%', strTitle );
strTemplate = strTemplate.replace( '%header%', strHeader );
strTemplate = strTemplate.replace( '%discount%', strDiscount );
$Elements = $J( '[data-cart-banner-spot]' );
for ( var i = 0; i < $Elements.length; i++ )
{
$element = $J( $Elements[i] );
$element.replaceWith( $J( strTemplate ) );
$J( '#promo_header_banner' ).slideDown( 500 );
}
return true;
}
function GotFlashPopup()
{
var win = window.open( 'https://store.steampowered.com/gotflash','gotflash','width=536,height=546,resize=yes,scrollbars=yes');
win.focus();
}
//
// Page-able tabs
//
var tabStart = { };
var tabMax = { };
var tabTransition = { };
function PageTab( tab, delta, max, params )
{
if ( tabTransition[tab] )
return;
if ( !tabStart[tab] )
tabStart[tab] = 0;
if ( !tabMax[tab] )
tabMax[tab] = 0;
if ( tabStart[tab] + delta >= max )
return;
tabStart[tab] += delta;
tabTransition[tab] = true;
if ( tabStart[tab] > tabMax[tab] )
{
if ( !params )
params = {};
params.tab = tab;
params.start = tabStart[tab];
params.count = delta;
new Ajax.Updater(
'tab_' + tab + '_items',
'https://store.steampowered.com/search/tab',
{ parameters: params, method: 'get', insertion: 'bottom', onComplete: TabCompletionClosure( tab, delta, max ) } );
tabMax[tab] = tabStart[tab];
}
else
{
RollTab( tab, delta );
TabUpdateCounts( tab, delta, max );
}
}
function TabCompletionClosure( tab, delta, max )
{
var tab_closure = tab;
var delta_closure = delta;
var max_closure = max;
return function() {
RollTab( tab_closure, delta_closure );
TabUpdateCounts( tab_closure, delta_closure, max_closure );
};
}
function RollTab( tab, delta )
{
if ( $('tab_' + tab + '_items' ).hasClassName( 'twenty_smallcap_page_items' ) )
{
var xdiff = 950;
if ( delta > 0 )
xdiff = -xdiff;
new Effect.Move( $('tab_' + tab + '_items'), {x: xdiff, afterFinish: TabScrollFinishClosure( tab, delta ) } );
}
else
{
//standard tab
var ydiff = -RowHeightForTab( tab ) * delta;
new Effect.Move( $('tab_' + tab + '_items'), {y: ydiff, afterFinish: TabScrollFinishClosure( tab, delta ) } );
}
var elTabArea = $('tab_' + tab + '_items').up('.tabarea' );
if ( elTabArea )
Effect.ScrollTo( elTabArea, { afterFinish: TabScrollFinishClosure( tab, delta ) } );
}
function TabScrollFinishClosure( tab, delta )
{
var tab_closure = tab;
var delta_closure = delta;
return function() {
tabTransition[tab_closure] = false;
};
}
function TabUpdateCounts( tab, delta, max )
{
if ( $('tab_' + tab + '_count_start') )
{
$('tab_' + tab + '_count_start').update( tabStart[tab] + 1 );
$('tab_' + tab + '_count_end').update( Math.min( tabStart[tab] + Math.abs(delta), max ) );
}
else
{
$('tab_' + tab + '_count').update( (tabStart[tab] + 1) + '-' + Math.min( tabStart[tab] + Math.abs(delta), max ) );
}
if ( tabStart[tab] > 0 )
$('tab_' + tab + '_prev').style.visibility='visible';
else
$('tab_' + tab + '_prev').style.visibility='hidden';
if ( tabStart[tab] + delta >= max )
$('tab_' + tab + '_next').style.visibility='hidden';
else
$('tab_' + tab + '_next').style.visibility='visible';
}
function RowHeightForTab( tab )
{
var tabRow = $('tab_' + tab + '_items' ).down( '.tab_row' );
return tabRow.getHeight();
}
function TabSelect( elem, target )
{
$J('#last_tab').val(target);
var $Elem = $JFromIDOrElement( elem );
$Elem.siblings().removeClass( 'active' );
$Elem.addClass( 'active' );
if( target == "tab_1_content" )
target = GetDefaultTabSelection();
var $Content = $JFromIDOrElement(target);
$Content.siblings().hide();
$Content.show();
// Re-compute impression tracking visibility
if ( typeof GDynamicStore != 'undefined' )
{
GDynamicStore.s_ImpressionTracker.CheckVisibility();
}
}
function TabSelectStealth( target )
{
var tab_content = $(target);
tab_content.siblings().invoke( 'hide' );
tab_content.show();
SetDefaultTabSelection( target );
}
function GetDefaultTabSelection()
{
var value = WebStorage.GetLocal( 'store_newreleases_filter_dlc' );
if( value == null )
return "tab_filtered_dlc_content";
return value;
}
function SetDefaultTabSelection( tab )
{
WebStorage.SetLocal( 'store_newreleases_filter_dlc', tab );
}
function InitTabDefaults()
{
TabSelectStealth( GetDefaultTabSelection() );
}
function ScrollSmallCaps( name, delta, pageSize, totalCount, params )
{
var targetid = 'sm_cap_' + name + '_scroll';
var elem = $( targetid );
if ( elem.effect )
return;
if ( !elem.curPos )
{
elem.curPos = 0;
}
if ( !elem.maxLoaded )
{
elem.maxLoaded = 0;
}
elem.curPos += delta;
if ( elem.curPos > elem.maxLoaded )
{
elem.effect = true;
elem.maxLoaded++;
elem.style.width = ( ( elem.maxLoaded + 2 ) * 614 ) + 'px';
if ( !params )
params = {};
params.name = name;
params.start = elem.curPos * pageSize + pageSize; // we are always loading one page ahead
params.count = pageSize;
new Ajax.Updater(
targetid,
'https://store.steampowered.com/search/smallcapscroll',
{ parameters: params, method: 'get', insertion: 'bottom', onComplete: UpdateSmallCapControl.bind( window, targetid, delta, pageSize, totalCount ) } );
}
else
{
UpdateSmallCapControl( targetid, delta, pageSize, totalCount );
}
}
function ScrollStaticSmallCaps( targetid, delta, pageSize, totalCount )
{
var elem = $( targetid );
if ( elem.effect )
return;
if ( !elem.curPos )
{
elem.curPos = 0;
}
elem.curPos += delta;
UpdateSmallCapControl( targetid, delta, pageSize, totalCount );
}
function ScrollCarouselSmallCaps( targetid, delta, pageSize, totalCount )
{
var elem = $( targetid );
if ( elem.effect )
return;
if ( !elem.curPos )
{
elem.curPos = 0;
}
elem.curPos += delta;
var max = Math.ceil( totalCount / pageSize ) - 1;
var nextLink = targetid + '_next';
var prevLink = targetid + '_prev';
if ( elem.curPos >= max )
$(nextLink).addClassName('disabled');
else
$(nextLink).removeClassName('disabled');
if ( elem.curPos <= 0 )
$(prevLink).addClassName('disabled');
else
$(prevLink).removeClassName('disabled');
var cb = function( targetid ) {
var elem = $( targetid );
elem.effect = false;
if ( typeof GDynamicStore != 'undefined' )
{
GDynamicStore.s_ImpressionTracker.CheckVisibility();
GDynamicStore.HandleCarouselChange( targetid, elem.curPos, pageSize );
}
};
elem.effect = new Effect.Move( elem, {x: -808 * delta, afterFinish: cb.bind( this, targetid ), duration: 0.4 } );
}
function UpdateSmallCapControl( targetid, delta, pageSize, totalCount )
{
var elem = $( targetid );
var max = Math.ceil( totalCount / pageSize ) - 1;
var width = $(elem.parentNode).getWidth();
var nextLink = targetid + '_next';
var prevLink = targetid + '_prev';
if ( elem.curPos >= max )
$(nextLink).hide();
else
$(nextLink).show();
if ( elem.curPos <= 0 )
$(prevLink).hide();
else
$(prevLink).show();
var pageStart = elem.curPos * pageSize + 1;
var pageEnd = pageStart + ( pageSize - 1 );
if ( $( targetid + '_page_start' ) )
$( targetid + '_page_start' ).update( pageStart );
if ( $( targetid + '_page_end' ) )
$( targetid + '_page_end' ).update( Math.min( pageEnd, totalCount ) );
elem.effect = new Effect.Move( elem, {x: -width * delta, afterFinish: function() { elem.effect = false; }, duration: 0.4 } );
}
var g_HoverState = {
target: null,
hiding: false
};
function GetHoverState( $Elem )
{
var oElemState = $Elem.data( 'oHoverState' );
if ( !oElemState )
{
oElemState = {};
$Elem.data( 'oHoverState', oElemState );
}
return oElemState;
}
function GameHover( elem, event, divHover, rgHoverData )
{
if (!event) var event = window.event;
var $Elem = $JFromIDOrElement(elem);
var $Hover = $JFromIDOrElement(divHover);
var bNewHoverSpeed = typeof ( g_bNewHoverSpeed) != 'undefined' && g_bNewHoverSpeed;
var oElemState = GetHoverState( $Elem );
if ( !$Hover.length )
{
$Hover = $J("\t\t<div class=\"hover game_hover\" id=\"global_hover\" style=\"display: none; left: 0; top: 0;\">\r\n\t\t\t<div class=\"game_hover_box hover_box\">\r\n\t\t\t\t<div class=\"content\" id=\"global_hover_content\">\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"hover_arrow_left\"><\/div>\r\n\t\t\t<div class=\"hover_arrow_right\"><\/div>\r\n\t\t<\/div>");
$J(document.body).append( $Hover );
}
if ( g_HoverState.hiding && $Hover.is(':visible') && g_HoverState.target == $Elem[0] )
{
ShowWithFade( $Hover );
}
else if ( ( !$Hover.is(':visible') || g_HoverState.target != $Elem[0] ) && !oElemState.timer )
{
oElemState.bWantsHover = true;
var accountId = ( typeof g_AccountID !== 'undefined' ) && !rgHoverData['public'] ? g_AccountID : 0;
var bPublic = rgHoverData['public'] || accountId == 0;
var strTargetPrefix = '';
var strUrlTarget = '';
if ( rgHoverData['type'] == 'app' )
{
strTargetPrefix = 'hover_app_';
strUrlTarget = 'apphover' + ( bPublic ? 'public' : '' ) + '/' + rgHoverData['id'];
}
else if ( rgHoverData['type'] == 'sub' )
{
strTargetPrefix = 'hover_sub_';
strUrlTarget = 'subhover' + ( bPublic ? 'public' : '' ) + '/' + rgHoverData['id'];
}
else if ( rgHoverData['type'] == 'bundle' )
{
strTargetPrefix = 'hover_bundle_';
strUrlTarget = 'bundle/' + rgHoverData['id'] + '/hover' + ( bPublic ? '_public' : '' ) + '/';
}
else
{
return;
}
if ( typeof GDynamicStore != 'undefined' )
{
strUrlTarget += '?review_score_preference=' + ( !GDynamicStore.s_preferences['review_score_preference'] ? 0 : GDynamicStore.s_preferences['review_score_preference'] );
}
var targetId = strTargetPrefix + rgHoverData['id'];
var $HoverData = $JFromIDOrElement( targetId );
var params = rgHoverData['params'] || {};
var nStartHoverTime = new Date().getTime();
var fnComputeHoverDelay = bNewHoverSpeed ?
function() { return Math.max( 400 - ( new Date().getTime() - nStartHoverTime ), 200 );} :
function() { return 200; };
if ( !$HoverData.length && !oElemState.bAjaxRequestMade )
{
var rgAjaxParams = { u: accountId };
if ( bPublic )
{
// is cc needed?
rgAjaxParams = { /*cc: rgHoverData['cc'],*/ l: 'schinese' };
}
if ( rgHoverData['v6'] )
{
rgAjaxParams['pagev6'] = true;
}
if ( $Elem.data('hoverDisableScreenshots') )
{
rgAjaxParams['disableScreenshots'] = true;
}
window.setTimeout( function() {
if ( oElemState.bWantsHover && !oElemState.bAjaxRequestMade ) {
oElemState.bAjaxRequestMade = true;
$J.get( 'https://store.steampowered.com/' + strUrlTarget, rgAjaxParams ).done( function( html )
{
var $Content = $J(html);
$Content.hide();
$Hover.find( '.content' ).append( $Content );
ShowGameHover( $Elem, $Hover, targetId, params, fnComputeHoverDelay() );
} );
}
}, bNewHoverSpeed ? 50 : 150 );
}
if ( !oElemState.timer )
{
oElemState.timer = window.setTimeout(function () {
oElemState.timer = false;
oElemState.bReadyForHover = true;
ShowGameHover( $Elem, $Hover, targetId, params, fnComputeHoverDelay() );
}, bNewHoverSpeed ? 100 : 300 );
}
}
}
function HideGameHover( elem, event, divHover )
{
var $Elem = $JFromIDOrElement( elem );
var $Hover = $JFromIDOrElement( divHover );
var oElemState = GetHoverState( $Elem );
if ( !$Hover.length )
return;
if (!event) var event = window.event;
var reltarget = $J( (event.relatedTarget) ? event.relatedTarget : event.toElement );
if ( reltarget.length && $J.contains( $Elem[0], reltarget[0] ) )
return;
if ( oElemState.timer )
{
window.clearTimeout( oElemState.timer );
oElemState.timer = false;
}
oElemState.bWantsHover = false;
oElemState.bReadyForHover = false;
HideWithFade( divHover, 200 );
}
function ShowGameHover( elem, divHover, targetContent, params, speed )
{
var $Elem = $JFromIDOrElement( elem );
var $Hover = $JFromIDOrElement( divHover );
var $Target = $JFromIDOrElement( targetContent );
var oElemState = GetHoverState( $Elem );
if ( !$Target.length || !oElemState.bWantsHover || !oElemState.bReadyForHover || !$J.contains( document, $Elem[0] ) )
return;
$Target.siblings().hide();
$Target.show();
g_HoverState.target = $Elem[0];
var $Toparea = $Target.find( '.hover_top_area' );
if ( params && params.top_area_content )
{
$Toparea.html( params.top_area_content );
$Toparea.show();
}
else if ( $Toparea.length )
{
$Toparea.hide();
}
// "show" the hover, but not "visible", letting us do some positioning
$Hover.css( 'visibility', 'hidden' ).show();
var $HoverBox = $Hover.find( '.hover_box' );
var $HoverArrowLeft = $Hover.find( '.hover_arrow_left' );
var $HoverArrowRight = $Hover.find( '.hover_arrow_right' );
var offset = $Elem.offset();
var nWindowScrollTop = $J(window).scrollTop();
var nWindowScrollLeft = $J(window).scrollLeft();
var nViewportWidth = $J(window).width();
var nViewportHeight = $J(window).height();
var nHoverPositionLeft, nHoverPositionTop;
var $HoverArrow = $HoverArrowLeft;
var boxRightViewport = ( offset.left - nWindowScrollLeft ) + $Elem.outerWidth() + $HoverBox.width() + 14;
var nSpaceRight = nViewportWidth - boxRightViewport;
var nSpaceLeft = offset.left - $Hover.width();
if( nSpaceLeft < -10 && nSpaceRight < -10 )
{
//no room at all
$Hover.hide().css('visibility','');
return; //skip showing the hover
}
else if ( nSpaceRight < 14 && nSpaceLeft > nSpaceRight )
{
nHoverPositionLeft = offset.left - $Hover.outerWidth() + 8;
$HoverArrow = $HoverArrowRight;
$HoverArrowLeft.hide();
$HoverArrowRight.show();
}
else
{
nHoverPositionLeft = offset.left + $Elem.outerWidth() - 8;
$HoverArrowLeft.show();
$HoverArrowRight.hide();
}
var nTopAdjustment = -13;
if ( $Elem.height() < 63 )
nTopAdjustment = Math.floor( $Elem.height() ) / 2 - 56;
nHoverPositionTop = offset.top + nTopAdjustment;
$Hover.offset( {top: nHoverPositionTop, left: nHoverPositionLeft} );
var nTargetTopViewport = ( offset.top - nWindowScrollTop ) + nTopAdjustment;
if ( nTargetTopViewport + $HoverBox.height() + 8 > nViewportHeight )
{
var nViewportAdjustment = ( $HoverBox.height() + 8 ) - ( nViewportHeight - nTargetTopViewport );
nViewportAdjustment = Math.min( $HoverBox.height() - 74, nViewportAdjustment );
var nViewportAdjustedHoverTop = offset.top - nViewportAdjustment;
$Hover.css( 'top', nViewportAdjustedHoverTop + 'px' );
$HoverArrow.css( 'top', ( 48 + nHoverPositionTop - nViewportAdjustedHoverTop ) + 'px' );
}
else
{
$HoverArrow.css( 'top', '' );
}
$Hover.hide();
$Hover.css( 'visibility', '' );
ShowWithFade( $Hover, speed );
}
function UpdateWishlistCount( nCount )
{
if ( nCount == 0 )
$JFromIDOrElement('wishlist_link').text( '愿望单' );
else
$JFromIDOrElement('wishlist_link').html( '愿望单 (<span id="wishlist_item_count_value">' + nCount.toString() + '</span>)' );
}
function AddToWishlist( appid, divToHide, divToShowSuccess, divToShowError, navref, divToHide2 )
{
if ( !g_AccountID )
{
ShowAlertDialog( "\u8bf7\u767b\u5f55\u2026", "\u60a8\u5fc5\u987b\u767b\u5f55 Steam \u624d\u80fd\u5c06\u7269\u54c1\u6dfb\u52a0\u81f3\u613f\u671b\u5355\u3002" )
.done( function() {
window.location.href = 'https://store.steampowered.com//login?redir=app/' + appid;
} );
return;
}
var url = 'https://store.steampowered.com/api/addtowishlist';
if ( navref )
{
MakeNavCookie( navref, url );
}
$J.post( url, {sessionid: g_sessionID, appid: appid} )
.done( function( data ) {
$JFromIDOrElement(divToHide).hide();
if ( divToHide2 )
$JFromIDOrElement(divToHide2).hide();
if ( data && data.success ) {
$JFromIDOrElement(divToShowSuccess).show();
if ( data.saleTaskCompleted ) {
NewStickerPackModal( '添加游戏至愿望单' );
}
}
else {
$JFromIDOrElement(divToShowError).show();
}
UpdateWishlistCount( data.wishlistCount );
if ( typeof GDynamicStore != 'undefined' )
GDynamicStore.InvalidateCache();
}).fail( function() {
$JFromIDOrElement(divToShowError).show();
});
}
function RemoveFromWishlist( appid, divToHide, divToShowSuccess, divToShowError, navref, divToHide2 )
{
var url = 'https://store.steampowered.com/api/removefromwishlist';
if ( navref )
{
MakeNavCookie( navref, url );
}
$J.post( url, {sessionid: g_sessionID, appid: appid} )
.done( function( data ) {
$JFromIDOrElement(divToHide).hide();
if ( divToHide2 )
$JFromIDOrElement(divToHide2).hide();
if ( data && data.success ) {
$JFromIDOrElement(divToShowSuccess).show();
}
else {
$JFromIDOrElement(divToShowError).show();
}
UpdateWishlistCount( data.wishlistCount );
if ( typeof GDynamicStore != 'undefined' )
GDynamicStore.InvalidateCache();
}).fail( function() {
$JFromIDOrElement(divToShowError).show();
});
}
function AddToWishlistButton( button, appid, navref )
{
var url = 'https://store.steampowered.com/api/addtowishlist';
if ( navref )
{
MakeNavCookie( navref, url );
}
$J.post( url, {sessionid: g_sessionID, appid: appid} )
.done( function( data ) {
if ( data && data.success )
{
$J(button).addClass('btn_disabled');
$J('span',button).text("\u5df2\u5728\u613f\u671b\u5355\u4e2d");
UpdateWishlistCount( data.wishlistCount );
}
else
{
$J('span',button).text( "\u54ce\u5440\uff0c\u5f88\u62b1\u6b49\uff01" );
}
if ( typeof GDynamicStore != 'undefined' )
GDynamicStore.InvalidateCache();
} );
}
function IgnoreButton( button, appid )
{
$J.post( 'https://store.steampowered.com/recommended/ignorerecommendation/', {
sessionid: g_sessionID,
appid: appid
}).done( function() {
$J(button).addClass('btn_disabled');
GDynamicStore.InvalidateCache();
}).fail( function() {
ShowAlertDialog( '忽略应用', '在保存您的更改时出现问题。请稍后重试。' );
});
}
// unlike wishlists, the "divToHide" is only hidden on success
function RecommendGame( appid, steamworksappid, comment, rated_up, is_public, language, received_compensation, divBtn, onSuccessFunc, divToShowError, navref, bDisableComments )
{
$JFromIDOrElement(divBtn).hide();
var url = 'https://store.steampowered.com/friends/recommendgame';
if ( navref )
MakeNavCookie( navref, url );
var params = {
appid: appid,
steamworksappid: steamworksappid,
comment: comment,
rated_up: rated_up,
is_public: is_public,
language: language,
received_compensation:
received_compensation,
disable_comments: bDisableComments,
sessionid: g_sessionID
};
$J.post( url, params )
.done( function( data ) {
if ( data && data.success )
{
$JFromIDOrElement(divToShowError).hide();
onSuccessFunc();
}
else
{
$JFromIDOrElement(divBtn).show();
var $Error = $JFromIDOrElement(divToShowError);
if ( data && data.strError )
{
if ( !$Error.data( 'strOrigMessage' ) )
$Error.data( 'strOrigMessage', $Error.html() );
$Error.html( data.strError );
}
else if ( $Error.data( 'strOrigMessage' ) )
$Error.html( $Error.data( 'strOrigMessage' ) );
$Error.show();
}
} );
}
//hide a game from being recommended
function HideRecommendation( type, itemid, divBtn, elemContainer )
{
var parameters = { sessionid: g_sessionID };
if ( type == 'app' )
parameters.appid = itemid;
else if ( type == 'sub' )
parameters.subid = itemid;
else
{
// invalid arguments
return false;
}
$(divBtn).hide();
new Ajax.Request( 'https://store.steampowered.com/recommended/ignorerecommendation/', {
method: 'post',
parameters: parameters,
onSuccess: function( transport ) {
if ( transport.responseJSON )
{
if ( $(elemContainer) )
$(elemContainer).update( '<div class="recommendation_ignored">好的,下一次,我们将会给您推荐别的内容</div>' );
}
else
{
$(divBtn).show();
}
}
});
}
var g_OnWebPanelShownHandlers = Array();
function SteamOnWebPanelShown()
{
for ( var i = 0; i < g_OnWebPanelShownHandlers.length; i++ )
{
g_OnWebPanelShownHandlers[i]();
}
}
function RegisterSteamOnWebPanelShownHandler( f )
{
g_OnWebPanelShownHandlers.push( f );
}
var g_OnWebPanelHiddenHandlers = Array();
function SteamOnWebPanelHidden()
{
for( var i = 0; i < g_OnWebPanelHiddenHandlers.length; i++ )
{
g_OnWebPanelHiddenHandlers[i]();
}
}
function RegisterSteamOnWebPanelHiddenHandler( f )
{
g_OnWebPanelHiddenHandlers.push( f );
}
$J( function() {
InstrumentLinks();
// add a jquery extension to handle our SNR stuff (we do this on load because jquery is included after this file)
jQuery.fn.InstrumentLinks = function()
{
if ( this.is('a') )
this.each( function() { InstrumentLink( this ); } );
else
this.find( 'a' ).each( function() { InstrumentLink( this ); } );
return this;
};
} );
function ExtractSNR( href )
{
var match = href.match( /[\?&]snr=([^&]*)/ );
return match && match[1];
}
function ReplaceSNR( href, snr )
{
return href.replace( /([\?&]snr=)[^&]*/, '$1' + snr );
}
/**
*
* @param $Link
* @param fnCallback takes an SNR code, should return the new SNR code
* @constructor
*/
function ModifyLinkSNR( $Link, fnCallback )
{
var href = $Link.attr('href');
var snr = href && ExtractSNR( href );
if ( snr )
{
$Link.attr( 'href', ReplaceSNR( href, fnCallback( snr ) ) );
}
}
function InstrumentLinks()
{
$J('A').each( function() { InstrumentLink( this ); } );
}
function InstrumentLink( link )
{
if ( link.bIsInstrumented )
return;
var bIsInstrumented = false;
// if the anchor uses javascript, then we don't want to monkey with any embedded URL's
if ( !link.href.match( /^javascript/ ) )
{
var navinfo = link.href.match( /[\?&]snr=[^&]*(&|$)/ );
if ( navinfo )
{
bIsInstrumented = true;
$J(link).click( function( event ) { InstrumentedLinkOnClick( event, link ); } );
}
else
{
var outcinfo = link.href.match(/[\?&]outc=([^&]*)(&|$)/);
if ( outcinfo )
{
bIsInstrumented = true;
$J(link).click( function (event) { InstrumentedLinkOnClick(event, link); } );
}
}
if ( bIsInstrumented )
{
link.bIsInstrumented = bIsInstrumented;
}
}
}
function InstrumentedLinkOnClick( event, link )
{
var navinfo = link.href.match( /[\?&]snr=([^&#]*)(&|$|#)/ );
if ( navinfo )
{
}
var exprinfo = link.href.match( /[\?&]outc=([^&#]*)(&|#|$)/ );
if ( exprinfo )
{
replacement = '';
if ( exprinfo[2] == '&' )
replacement = exprinfo[0][0];
else
replacement = navinfo[2];
link.href = link.href.replace( /[\?&]outc=[^&#]*(&|#|$)/, replacement );
MakeOutcomeCookie( exprinfo[1], link.href );
}
return true;
}
function MakeNavCookie( snr, url )
{
var dateExpires = new Date();
dateExpires.setTime( dateExpires.getTime() + 1000 * 60 );
document.cookie = 'snr=' + snr + '|' + encodeURIComponent( url ) +'; expires=' + dateExpires.toGMTString() + ';path=/';
}
function MakeOutcomeCookie( outc, url )
{
var dateExpires = new Date();
dateExpires.setTime( dateExpires.getTime() + 1000 * 60 );
document.cookie = 'outc=' + outc +'; expires=' + dateExpires.toGMTString() + ';path=/';
}
function GetNavCookie()
{
// get back the cookie that MakeNavCookie creates !
var ref = document.cookie.match( /\ssnr=([^;]*);/ );
if ( ref )
return ref[1];
}
var g_iActiveSpotlight = 0;
function AnimateSpotlightTransition( iCurSpotlight, iNextSpotlight )
{
var $elSpotlights = $J('#spotlight_scroll').children();
var $Spotlight = $J( $elSpotlights[iCurSpotlight] );
var $NextSpotlight = $J( $elSpotlights[iNextSpotlight] );
var $Scroll = $JFromIDOrElement('spotlight_scroll');
$Scroll.stop();
var curHeight = $Scroll.height();
$Scroll.css( 'height', curHeight + 'px' );
$Spotlight.css( 'position', 'absolute' );
$NextSpotlight.css( 'position', 'absolute' );
var targetHeight = $NextSpotlight.height();
if ( targetHeight != curHeight )
$Scroll.animate( {height: targetHeight }, 250 );
$Spotlight.stop();
$Spotlight.fadeOut( 250 );
$NextSpotlight.stop();
$NextSpotlight.fadeTo( 200, 1.0 ); //fadeTo rather than fadeIn in case it was already in a fade
}
function NextSpotlight( cMaxSpotlights )
{
if ( g_iActiveSpotlight + 1 >= cMaxSpotlights )
return;
AnimateSpotlightTransition( g_iActiveSpotlight, ++g_iActiveSpotlight );
UpdateSpotlightControls( cMaxSpotlights );
}
function expandTXItem( item )
{
var blurb = $( item ).down( '.tx_record_row_blurb' );
if ( !blurb )
return;
if ( blurb.visible() )
Effect.BlindUp( blurb, { duration: 0.25 } );
else
Effect.BlindDown( blurb, { duration: 0.25 } )
}
function PrevSpotlight( cMaxSpotlights )
{
if ( g_iActiveSpotlight <= 0 )
return;
AnimateSpotlightTransition( g_iActiveSpotlight, --g_iActiveSpotlight );
UpdateSpotlightControls( cMaxSpotlights );
}
function UpdateSpotlightControls( cMaxSpotlights )
{
if ( g_iActiveSpotlight < cMaxSpotlights - 1 )
$JFromIDOrElement('spotlight_scroll_next').removeClass( 'disabled' );
else
$JFromIDOrElement('spotlight_scroll_next').addClass( 'disabled' );
if ( g_iActiveSpotlight > 0 )
$JFromIDOrElement('spotlight_scroll_prev').removeClass( 'disabled' );
else
$JFromIDOrElement('spotlight_scroll_prev').addClass( 'disabled' );
$J('#spotlight_scroll_count_cur').text( g_iActiveSpotlight + 1 );
}
function InitDailyDealTimer( elTimer, nServerEndTime )
{
var nTimeRemaining = nServerEndTime - g_ServerTime;
if ( nTimeRemaining > 0 )
{
var nEndTimeLocal = Math.round( new Date().getTime() / 1000 ) + nTimeRemaining;
new Countdown( elTimer, nEndTimeLocal );
}
}
var CCountdownManager = {
rgCountdowns: [],
nIntervalId: null,
bRefreshOnTimerEnd: false,
tsInit: 0,
bReadyForRefresh: false,
bTriggeredRefresh: false,
registerCountdown: function( countdown )
{
this.rgCountdowns.push( countdown );
if ( !this.nIntervalId )
{
this.nIntervalId = window.setInterval( this.refreshClocks.bind( this ), 1000 );
this.tsInit = Math.round( new Date().getTime() / 1000 );
}
},
refreshClocks: function()
{
for ( var i = 0; i < this.rgCountdowns.length; i++ )
{
var countdown = this.rgCountdowns[i];
if ( countdown.bEnded )
continue;
this.rgCountdowns[i].refreshClock();
if ( countdown.bEnded && this.bRefreshOnTimerEnd )
this.refreshOnUserAction();
}
},
refreshOnUserAction: function()
{
// if a few seconds have passed, we'll refresh
if ( !this.bReadyForRefresh && Math.round( new Date().getTime() / 1000 ) - this.tsInit >= 5 )
{
var _this = this;
$J(document).on( 'focus mousemove', function() { _this.doRefresh(); } );
this.bReadyForRefresh = true;
}
},
doRefresh: function()
{
if ( !this.bTriggeredRefresh )
{
this.bTriggeredRefresh = true;
window.location.reload();
}
}
};
function Countdown( elClock, nEndTimeLocalTime )
{
this.$Clock = $JFromIDOrElement( elClock );
this.nEndTime = nEndTimeLocalTime;
this.bEnded = false;
this.cbkExpired = null;
if ( this.$Clock.length && nEndTimeLocalTime )
{
this.refreshClock();
CCountdownManager.registerCountdown(this);
}
}
Countdown.prototype.setCallback = function( cbkExpired )
{
this.cbkExpired = cbkExpired;
};
Countdown.prototype.refreshClock = function()
{
if ( this.bEnded )
return;
var timeCur = Math.round( new Date().getTime() / 1000 );
var secsRemaining = this.nEndTime - timeCur;
if ( secsRemaining < 0 )
{
this.bEnded = true;
secsRemaining = 0;
}
var remainDays = Math.floor( secsRemaining / 86400 );
var remainHours = Math.floor( ( secsRemaining % 86400 ) / 3600 );
var remainMinutes = Math.floor( ( secsRemaining % 3600 ) / 60 );
var remainSeconds = secsRemaining % 60;
if ( this.cbkExpired && secsRemaining < 1 )
{
this.cbkExpired();
this.cbkExpired = null;
}
this.render( remainDays, remainHours, remainMinutes, remainSeconds );
};
Countdown.prototype.render = function( remainDays, remainHours, remainMinutes, remainSeconds )
{
var str = '';
if ( remainDays < 3 )
{
remainHours += remainDays * 24;
}
else
{
str += remainDays + '';
}
str += (remainHours < 10 ? '0' : '') + remainHours + ':';
str += (remainMinutes < 10 ? '0' : '') + remainMinutes + ':';
str += (remainSeconds < 10 ? '0' : '') + remainSeconds;
this.$Clock.text( str );
};
function GraphicalCountdown( nEndTimeLocalTime, strImagePath, strElPrefix )
{
this.strImagePath = strImagePath;
this.strElPrefix = strElPrefix;
this.strExtension = '.png';
this.rgLastVals = {};
Countdown.apply( this, [ null, nEndtimeLocalTime ] );
}
GraphicalCountdown.prototype = new Countdown;
GraphicalCountdown.prototype.constructor = GraphicalCountdown;
GraphicalCountdown.prototype.render = function( remainDays, remainHours, remainMinutes, remainSeconds )
{
remainHours += remainDays * 24;
this.setImage( 'hours_tens', Math.floor( remainHours / 10 ) );
this.setImage( 'hours_units', remainHours % 10 );
this.setImage( 'minutes_tens', Math.floor( remainMinutes / 10 ) );
this.setImage( 'minutes_units', remainMinutes % 10 );
this.setImage( 'seconds_tens', Math.floor( remainSeconds / 10 ) );
this.setImage( 'seconds_units', remainSeconds % 10 );
};
GraphicalCountdown.prototype.setImage = function( idSuffix, val )
{
if ( this.rgLastVals[idSuffix] != val )
{
$(this.strElPrefix + idSuffix).src = this.strImagePath + val + this.strExtension;
this.rgLastVals[idSuffix] = val;
}
};
// SEARCH.JS
var g_oSuggestParams;
var g_nMobileSearchTermTimer = 0;
// how long we wait after the first keypress after a search or page load
var k_nStartSearchTimeoutMS = 350;
// how long we extend the wait after each keypress. We always time out at 3x the base search timeout ms
var k_nSearchKeypressTimeoutExtensionMS = 125;
// TODO: only reason for having this here is to support the SNR. If the mobile app can identify it, or it's not important to include the SNR then
// we'll have the mobile app navigate to the search page without having to post a message to the webview
function MobileApp_ShowSearchResults( $SNR, $Term )
{
const snr = $SNR ? '&snr=' + $SNR : '';
window.location = 'https://store.steampowered.com/search/?term=' + $Term + snr;
}
function MobileApp_UpdateSearchSuggestions( $Term )
{
var $SuggestionsCtn = $J('#searchterm_options');
var $Suggestions = $J('#search_suggestion_contents');
var msDelayBeforeTimeout = k_nStartSearchTimeoutMS;
if ( g_nMobileSearchTermTimer !== 0 )
{
window.clearTimeout( g_nMobileSearchTermTimer );
g_nMobileSearchTermTimer = 0;
msDelayBeforeTimeout = k_nSearchKeypressTimeoutExtensionMS;
}
g_nMobileSearchTermTimer = window.setTimeout( function() {
g_nMobileSearchTermTimer = 0;
sLastVal = $Term;
SearchTimeout( $Term, v_trim( sLastVal ), $SuggestionsCtn, $Suggestions, true /* bUseResponsivePopupOverlay */ );
}, msDelayBeforeTimeout );
}
function InitializeSearchSuggestionParams( cc, realm, l, rgUserPreferences, strPackageXMLVersion )
{
g_oSuggestParams = $J.extend( {
cc: cc,
realm: realm,
l:l,
v: strPackageXMLVersion
}, rgUserPreferences );
}
// Enable search where the search text input is part of the web page
function EnableSearchSuggestions( elemTerm, navcontext, cc, realm, l, rgUserPreferences, strPackageXMLVersion, elemSuggestionCtn, elemSuggestions )
{
var $Term = $JFromIDOrElement(elemTerm);
var $SuggestionsCtn = elemSuggestionCtn ? $JFromIDOrElement(elemSuggestionCtn) : $J('#searchterm_options');
var $Suggestions = elemSuggestions ? $JFromIDOrElement(elemSuggestions) : $J('#search_suggestion_contents');
$Term.parents('div.searchbox').click( function( event ) {
if ( event.target && event.target.tagName != 'INPUT' )
$J(elemTerm).focus();
});
var sLastVal = $Term.val();
var nTermTimer = 0;
var tsScheduledTimer = 0;
var tsLastSearch = 0;
$Term.on( 'keyup paste', function( event ) {
var sNewVal = $Term.val();
if ( sNewVal != sLastVal )
{
var tsChange = $J.now();
var msDelayBeforeTimeout = k_nStartSearchTimeoutMS;
if ( !tsLastSearch )
tsLastSearch = tsChange;
if ( nTermTimer && tsScheduledTimer - tsChange < k_nSearchKeypressTimeoutExtensionMS && tsChange - tsLastSearch < 3 * k_nStartSearchTimeoutMS )
{
// we have one scheduled within 50ms, just bump it out a little
msDelayBeforeTimeout = k_nSearchKeypressTimeoutExtensionMS;
window.clearTimeout( nTermTimer );
nTermTimer = 0;
}
if ( !nTermTimer )
{
tsScheduledTimer = $J.now() + msDelayBeforeTimeout;
nTermTimer = window.setTimeout( function() {
nTermTimer = 0;
tsLastSearch = 0;
sLastVal = $Term.val();
SearchTimeout( $Term, v_trim( sLastVal ), $SuggestionsCtn, $Suggestions );
}, msDelayBeforeTimeout);
}
}
});
$Term.on( 'keydown', function( event ) { SearchSuggestOnKeyDown( event, $Term, $SuggestionsCtn, $Suggestions ); } );
$Term.on( 'click focus', function( event ) { SearchSuggestClearDefaultSearchText( $Term, $SuggestionsCtn, $Suggestions ); } );
$Term.add( $SuggestionsCtn ).on( 'focusout', function( event ) {
if ( event.relatedTarget && ( $J.contains( $Term[0], event.relatedTarget ) || $Term.is( event.relatedTarget ) || $J.contains( $SuggestionsCtn[0], event.relatedTarget ) ) )
{
return;
}
SearchSuggestSetDefaultSearchText( $Term, $SuggestionsCtn, $Suggestions );
} );
InitializeSearchSuggestionParams( cc, realm, l, rgUserPreferences, strPackageXMLVersion );
}
function SearchTimeout( $Term, value, $SuggestionsCtn, $Suggestions, bUseResponsivePopupOverlay = false )
{
if ( value )
{
var parameters = {term: value, f: 'games' };
$J.extend( parameters, g_oSuggestParams );
$J.get( 'https://store.steampowered.com/search/suggest', parameters).done( function( html ) {
$Suggestions.html( html );
$Suggestions.InstrumentLinks();
$Suggestions.find('.match').attr('data-ds-options', 0);
GDynamicStore.DecorateDynamicItems( $Suggestions );
$Suggestions.find('a.match').each( function () {
var el = this;
$J(el).on( 'mouseover', function( event ) { SearchSuggestOnMouseOver( event, $J(el) ); } );
$J(el).on( 'mouseleave', function( event ) { SearchSuggestOnMouseLeave( event, $J(el) ); } );
$J(el).on( 'mousemove', function( event ) { SearchSuggestOnMouseOver( event, $J(el) ); } );
} );
ShowSuggestionsAsNecessary( false, $SuggestionsCtn, $Suggestions, bUseResponsivePopupOverlay );
} );
}
else
{
$Suggestions.empty();
ShowSuggestionsAsNecessary( false, $SuggestionsCtn, $Suggestions, bUseResponsivePopupOverlay );
}
}
function ShowSuggestionsAsNecessary( bForceHide, $SuggestionsCtn, $Suggestions, bUseResponsivePopupOverlay = false )
{
var bShow = $Suggestions.children().length > 0 && !bForceHide;
if ( bShow )
{
ShowWithFade( $SuggestionsCtn );
}
else
{
HideWithFade( $SuggestionsCtn );
}
if ( bUseResponsivePopupOverlay )
{
UpdateResponsiveSearchOverlay( bShow );
}
}
function UpdateResponsiveSearchOverlay( bShow )
{
var $ResponsiveSearchOverlay = $J( '#responsive_store_search_overlay' );
if ( bShow )
{
$ResponsiveSearchOverlay.show();
document.body.classList.add( 'responsive_store_overlay_visible' );
}
else
{
$ResponsiveSearchOverlay.hide();
document.body.classList.remove( 'responsive_store_overlay_visible' );
}
}
function SearchSuggestOnKeyDown( event, $Term, $SuggestionsCtn, $Suggestions )
{
if ( event.keyCode == 27 /* Event.KEY_ESC */ )
{
ShowSuggestionsAsNecessary( true, $SuggestionsCtn, $Suggestions );
}
else if ( event.keyCode == 13 /* Event.KEY_RETURN */
|| event.keyCode == 38 /* Event.KEY_UP */
|| event.keyCode == 40 /* Event.KEY_DOWN */ )
{
if ( !$SuggestionsCtn.is( ':visible' ) )
return;
var $CurSuggestion = $Suggestions.children('.focus');
var $NewSuggestion = $J();
if ( event.keyCode == 13 /* Event.KEY_RETURN */ )
{
if ( $CurSuggestion.length )
{
window.location = $CurSuggestion.attr( 'href' );
event.preventDefault();
}
}
else
{
if ( !$CurSuggestion.length )
$CurSuggestion = $Suggestions.children('.hover');
if ( event.keyCode == 38 /* Event.KEY_UP */ )
{
if ( $CurSuggestion.length )
$NewSuggestion = $CurSuggestion.prev();
if ( !$NewSuggestion.length )
$NewSuggestion = $Suggestions.children('a.match:last-child');
}
else if ( event.keyCode == 40 /* Event.KEY_DOWN */ )
{
if ( $CurSuggestion.length )
$NewSuggestion = $CurSuggestion.next();
if ( !$NewSuggestion.length )
$NewSuggestion = $Suggestions.children('a.match:first-child');
}
if ( $NewSuggestion.length )
{
$CurSuggestion.removeClass( 'focus' );
$CurSuggestion.removeClass( 'hover' );
$NewSuggestion.addClass('focus');
}
//client webkit will move cursor on up/down
event.preventDefault();
}
}
}
function SearchSuggestOnMouseOver( event, $Suggestion )
{
$Suggestion.siblings().removeClass( 'focus' );
$Suggestion.removeClass( 'focus' );
$Suggestion.siblings().removeClass( 'hover' );
$Suggestion.addClass( 'hover' );
}
function SearchSuggestOnMouseLeave( event, $Suggestion )
{
$Suggestion.removeClass( 'focus' );
$Suggestion.removeClass( 'hover' );
}
function SearchSuggestClearDefaultSearchText( $Term, $SuggestionsCtn, $Suggestions )
{
ShowSuggestionsAsNecessary( false, $SuggestionsCtn, $Suggestions );
SearchSuggestClearFixStyles($Term);
}
function SearchSuggestClearFixStyles($Term)
{
$Term.removeClass( 'default' );
}
function SearchSuggestSetDefaultSearchText( $Term, $SuggestionsCtn, $Suggestions )
{
ShowSuggestionsAsNecessary( true, $SuggestionsCtn, $Suggestions );
SearchSuggestSetFixStyles($Term);
}
function SearchSuggestSetFixStyles($Term)
{
var text = $Term.val();
if (text == '')
{
$Term.addClass( 'default' );
}
}
function SearchSuggestCheckTerm( theform )
{
// no longer need to check for #text_search_the_store, but leaving here
// in case we have other things we want to block in the future.
return true;
}
// HEADER.JS
// We always want to have the timezone cookie set for PHP to use
setTimezoneCookies();
// tags in the menu
function EnsureStoreMenuTagsLoaded( strId )
{
// dynamic store can handle this in v6
if ( typeof GDynamicStore != 'undefined' )
return;
var $Element = $J(strId);
if ( !$Element.data('tags-loaded') )
{
$Element.data('tags-loaded', true );
var url = 'https://store.steampowered.com/tagdata/recommendedtags';
$J.get( url, {ll: 'schinese'} ).done( function( data ) {
var rgYourPopularTags = data || [];
if ( rgYourPopularTags.length > 0 )
{
$Element.empty();
$Element.css( 'min-height', '' );
for( var i = 0; i < rgYourPopularTags.length && i < 5; i++ )
{
var tag = rgYourPopularTags[i];
var $Link = $J('<a/>', {'class': 'popup_menu_item', 'href': 'https://store.steampowered.com/tag/zh-cn/' + encodeURIComponent( tag.name ) });
$Link.text( tag.name );
$Element.append( $Link );
}
}
}).fail( function() {
}).always( function() {
$Element.children( '.popup_menu_subarea').show();
});
}
}
function AddFreeLicense( subid, strDisplayName )
{
if ( window.g_bAddFreeLicenseInFlight )
return;
window.g_bAddFreeLicenseInFlight = true;
var posts = [];
var subids = ( typeof subid == "number" || typeof subid == "string" ? [ subid ] : subid );
for ( var i = 0; i < subids.length; i++ )
{
posts.push( $J.post( 'https://store.steampowered.com/checkout/addfreelicense/' + subids[i], { ajax: true, sessionid: g_sessionID }) );
}
$J.when.apply( $J, posts ).done( function() {
ShowAlertDialog(
strDisplayName,
'%s 已经被添加至您的帐户。它已在您的 Steam 库中可用。'.replace( /%s/, strDisplayName )
).done( function() {
window.location.reload();
});
if ( typeof GDynamicStore != 'undefined' )
GDynamicStore.InvalidateCache();
}).fail( function( jqXHR ) {
var data = V_ParseJSON( jqXHR.responseText );
if ( data && data.purchaseresultdetail == 9 )
ShowAlertDialog( strDisplayName, '该产品已在您的 Steam 库中可用。' );
else if ( data && data.purchaseresultdetail == 24 )
ShowAlertDialog( strDisplayName, '您必须拥有基础产品才能添加此产品至您的 Steam 帐户。' );
else
ShowAlertDialog( strDisplayName, '添加该产品至您的帐户时出现问题。请稍后再试。' );
}).always( function () {
delete window.g_bAddFreeLicenseInFlight;
});
}
function AddFreeBundle( bundleid, strDisplayName )
{
if ( window.g_bAddFreeLicenseInFlight )
return;
window.g_bAddFreeLicenseInFlight = true;
var posts = [];
posts.push( $J.post( 'https://store.steampowered.com/checkout/addfreebundle/' + bundleid, { ajax: true, sessionid: g_sessionID }) );
$J.when.apply( $J, posts ).done( function() {
ShowAlertDialog(
strDisplayName,
'%s 已经被添加至您的帐户。它已在您的 Steam 库中可用。'.replace( /%s/, strDisplayName )
).done( function() {
window.location.reload();
});
if ( typeof GDynamicStore != 'undefined' )
GDynamicStore.InvalidateCache();
}).fail( function( jqXHR ) {
var data = V_ParseJSON( jqXHR.responseText );
if ( data && data.purchaseresultdetail == 9 )
ShowAlertDialog( strDisplayName, '该产品已在您的 Steam 库中可用。' );
else
ShowAlertDialog( strDisplayName, '添加该产品至您的帐户时出现问题。请稍后再试。' );
}).always( function () {
delete window.g_bAddFreeLicenseInFlight;
});
}
function ChangeLanguage( strTargetLanguage, bStayOnPage )
{
var Modal = ShowBlockingWaitDialog( '更改语言', '' );
$J.post( 'https://store.steampowered.com/account/setlanguage/', {language: strTargetLanguage, sessionid: g_sessionID })
.done( function() {
if ( bStayOnPage )
Modal.Dismiss();
else
{
if( typeof g_AccountID != 'undefined' && g_AccountID > 0 )
window.location = 'https://store.steampowered.com/account/languagepreferences/';
else if ( window.location.href.match( /[?&]l=/ ) )
window.location = window.location.href.replace( /([?&])l=[^&]*&?/, '$1' );
else
window.location.reload();
}
}).fail( function() {
Modal.Dismiss();
ShowAlertDialog( '更改语言', '在保存您的更改时出现问题。请稍后重试。' );
});
}
function Logout()
{
var $Form = $J('<form/>', {'action': 'https://store.steampowered.com/logout/', 'method': 'POST' } );
$Form.append( $J('<input/>', {'type': 'hidden', 'name': 'sessionid', 'value': g_sessionID } ) );
$Form.appendTo( 'body' );
$Form.submit();
}
function getBestAvailNavData()
{
var navData = jQuery.data( document, 'x_oldnav' );
if ( navData == undefined )
{
navData = jQuery.data( document, 'x_oldref' );
}
if ( navData === undefined )
{
// try to get what we need from the URL !
var rg = window.location.href.match( /[\?&]snr=([^\?&]*)($|&)/ );
if ( rg )
{
navData = rg[1];
}
}
return navData;
}
// Function to add a package to a cart, assumes form setup on the page
function addToCart( subid, dedupe )
{
try
{
// Find all of the add to cart buttons displayed on the page
var filterAllButtons='a.btn_addtocart_content';
// the filterString can be used to find the element that invoked us, since the subid appears within it
// note that href*= specifies that href contains the string
var filterString = 'a[href*=' + subid + ']';
// within the set of all buttons, get the index of the one that we are dealing with!
// To do that, we find the anchor that invoked us within the larger set of add to cart buttons!
var allButtons = jQuery( filterAllButtons );
// Check for quantity
var idx = ( dedupe !== undefined ) ? dedupe : 0;
var filterStringForm = 'form[name=add_to_cart_'+subid+']';
var $Form = jQuery( filterStringForm );
if ( !$Form.length )
{
$Form= $J('<form/>', { name: 'add_to_cart_' + subid, action: 'https://store.steampowered.com/cart/', method: 'POST', style: 'display: none;' } );
$Form.append( $J('<input/>', { type: 'hidden', name: 'action', value: 'add_to_cart' } ) );
$Form.append( $J('<input/>', { type: 'hidden', name: 'subid', value: subid } ) );
$Form.append( $J('<input/>', { type: 'hidden', name: 'sessionid', value: g_sessionID } ) );
if ( typeof GStoreItemData !== 'undefined' )
$Form.append( $J('<input/>', { type: 'hidden', name: 'snr', value: GStoreItemData.GetCurrentPageNavParams() } ) );
$J(document.body).append( $Form );
}
var quantity = jQuery( '#quantity_update_'+subid+'_'+idx ).val();
if ( quantity !== undefined )
{
jQuery('<input type="hidden">').attr({name: 'quantity', 'value': quantity}).appendTo($Form);
}
// do we have anything to examine?
if ( allButtons.length > 0 )
{
var navData = getBestAvailNavData();
var button;
var buttonOffset = { top : 0, left : 0 };
var buttonIndex = allButtons.index( jQuery( filterString ) );
//
// Subscription pages have ambiguous add to cart buttons - we will try to 'dedupe' it !
//
if ( buttonIndex === -1 )
{
if ( dedupe !== undefined )
{
buttonIndex = dedupe;
}
else
{
// There is a chance this we're mistaken if the .php generation of the page
// didn't generate the addToCart() calls as we expect !
buttonIndex = 0;
}
}
button = allButtons.eq(buttonIndex);
//
// If we are certain we know what button was clicked, then we'll provide info on the form!
//
if ( button != null && button.length === 1 && typeof button.offset == 'function' )
{
buttonOffset = button.offset();
var height = jQuery(window).height();
var width = jQuery(window).width();
//
// We have all the components we want the standard button to submit to the server!
// we will now add input fields to the form we intend to submit.
//
var begintime = jQuery.data(document, 'x_readytime');
var selecttime = 0.0;
if ( begintime !== undefined )
{
selecttime = new Date().getTime() - begintime;
}
if ( $Form.length === 1 )
{
// We include the 'hidden' attribute at this point, because of a believe compatibility issue with Internet Explorer!
jQuery( '<input type="hidden">' ).attr( { name: 'x_selection', 'value' : buttonIndex } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_choices', 'value' : allButtons.length } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_top', 'value' : buttonOffset.top } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_left', 'value' : buttonOffset.left } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_window_height', 'value' : height } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_window_width', 'value' : width } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_select_time', 'value' : selecttime } ).appendTo( $Form );
if ( navData )
{
var pipeSplit = new RegExp( /\|/ );
var resultString = navData.split( pipeSplit )[0];
jQuery( '<input type="hidden">' ).attr( { name: 'x_oldnav', 'value' : resultString } ).appendTo( $Form );
}
}
}
}
}
catch( e )
{
//console.log( e );
}
// Regardless of instrumentation failures, try to submit the form for the user.
try
{
$Form.submit();
}
catch( e )
{
// swallow exceptions !
}
}
// Function to handle quantity box changes per keystroke, largely to show error style if above max quantity
function qtyBoxInputChanged( sIdSuffix, sAddToCartID )
{
let elField = jQuery( "#quantity_update_" + sIdSuffix );
let elCartButton = jQuery( "#" + sAddToCartID );
let elUpdateButton = jQuery( "#btn_quantity_update_" + sIdSuffix );
try
{
let sQty = elField.val();
let sQtyMax = elField.attr( 'max' );
let nQty = parseInt( sQty );
let nQtyMax = parseInt( sQtyMax );
const sDisabledClass = 'btn_disabled';
const sInvalidClass = 'qty_invalid';
if ( nQty !== undefined )
{
if ( ( nQtyMax === undefined || isNaN( nQtyMax ) ) || ( !isNaN( nQty ) && nQty > 0 && nQty <= nQtyMax ) )
{
elField.removeClass( sInvalidClass );
elCartButton.removeClass( sDisabledClass );
elCartButton.prop( 'disabled', false );
elUpdateButton.prop( 'disabled', false );
}
else
{
elField.addClass( sInvalidClass );
elCartButton.addClass( sDisabledClass );
elCartButton.prop( 'disabled', true );
elUpdateButton.prop( 'disabled', true );
}
elUpdateButton.show();
}
}
catch( e )
{
// ignore
}
}
// Function to add a package to a cart, assumes form setup on the page
function updateQtyCart( formName, id )
{
try
{
// Check for quantity
var quantity = jQuery( '#'+id ).val();
if ( quantity !== undefined )
{
var filterStringForm = 'form[name='+formName+']';
var formSelector = jQuery( filterStringForm );
if ( formSelector.length === 1 )
{
jQuery('<input type="hidden">').attr({name: 'quantity', 'value': quantity}).appendTo(formSelector);
}
}
}
catch( e )
{
//console.log( e );
}
// Regardless of instrumentation failures, try to submit the form for the user.
try
{
document.forms[formName].submit();
}
catch( e )
{
// swallow exceptions !
}
}
// Function to add a bundle to a cart, assumes form setup on the page
function addBundleToCart( bundleid, dedupe )
{
try
{
// Find all of the add to cart buttons displayed on the page
var filterAllButtons='a.btn_addtocart_content';
// the filterString can be used to find the element that invoked us, since the subid appears within it
// note that href*= specifies that href contains the string
var filterString = 'a[href*=' + bundleid + ']';
// within the set of all buttons, get the index of the one that we are dealing with!
// To do that, we find the anchor that invoked us within the larger set of add to cart buttons!
var allButtons = jQuery( filterAllButtons );
var filterStringForm = 'form[name=add_bundle_to_cart_'+bundleid+']';
var $Form = jQuery( filterStringForm );
if ( !$Form.length )
{
$Form= $J('<form/>', { name: 'add_bundle_to_cart_' + bundleid, action: 'https://store.steampowered.com/cart/', method: 'POST', style: 'display: none;' } );
$Form.append( $J('<input/>', { type: 'hidden', name: 'action', value: 'add_to_cart' } ) );
$Form.append( $J('<input/>', { type: 'hidden', name: 'bundleid', value: bundleid } ) );
$Form.append( $J('<input/>', { type: 'hidden', name: 'sessionid', value: g_sessionID } ) );
$J(document.body).append( $Form );
}
// Check for quantity
var idx = ( dedupe !== undefined ) ? dedupe : 0;
var quantity = jQuery( '#quantity_update_'+bundleid+'_'+idx ).val();
if ( quantity !== undefined )
{
if ( $Form.length === 1 )
{
jQuery('<input type="hidden">').attr({name: 'quantity', 'value': quantity}).appendTo($Form);
}
}
// do we have anything to examine?
if ( allButtons.length > 0 )
{
var navData = getBestAvailNavData();
var button;
var buttonOffset = { top : 0, left : 0 };
var buttonIndex = allButtons.index( jQuery( filterString ) );
//
// Subscription pages have ambiguous add to cart buttons - we will try to 'dedupe' it !
//
if ( buttonIndex === -1 )
{
if ( dedupe !== undefined )
{
buttonIndex = dedupe;
}
else
{
// There is a chance this we're mistaken if the .php generation of the page
// didn't generate the addToCart() calls as we expect !
buttonIndex = 0;
}
}
button = allButtons.eq(buttonIndex);
//
// If we are certain we know what button was clicked, then we'll provide info on the form!
//
if ( button != null && button.length === 1 && typeof button.offset == 'function' )
{
buttonOffset = button.offset();
var height = jQuery(window).height();
var width = jQuery(window).width();
//
// We have all the components we want the standard button to submit to the server!
// we will now add input fields to the form we intend to submit.
//
var begintime = jQuery.data(document, 'x_readytime');
var selecttime = 0.0;
if ( begintime !== undefined )
{
selecttime = new Date().getTime() - begintime;
}
if ( $Form.length === 1 )
{
// We include the 'hidden' attribute at this point, because of a believe compatibility issue with Internet Explorer!
jQuery( '<input type="hidden">' ).attr( { name: 'x_selection', 'value' : buttonIndex } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_choices', 'value' : allButtons.length } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_top', 'value' : buttonOffset.top } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_left', 'value' : buttonOffset.left } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_window_height', 'value' : height } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_window_width', 'value' : width } ).appendTo( $Form );
jQuery( '<input type="hidden">' ).attr( { name: 'x_select_time', 'value' : selecttime } ).appendTo( $Form );
if ( navData )
{
var pipeSplit = new RegExp( /\|/ );
var resultString = navData.split( pipeSplit )[0];
jQuery( '<input type="hidden">' ).attr( { name: 'x_oldnav', 'value' : resultString } ).appendTo( $Form );
}
}
}
}
}
catch( e )
{
//console.log( e );
}
// Regardless of instrumentation failures, try to submit the form for the user.
try
{
$Form.submit();
}
catch( e )
{
// swallow exceptions !
}
}
function addAllDlcToCart()
{
try
{
// Find all of the add to cart buttons displayed on the page
var filterAllButtons='a.btn_addtocart_content';
// the filterString can be used to find the element that invoked us, since the subid appears within it
// note that href*= specifies that href contains the string
var filterString = 'a[href*=addAllDlcToCart]';
// within the set of all buttons, get the index of the one that we are dealing with!
// To do that, we find the anchor that invoked us within the larger set of add to cart buttons!
var allButtons = jQuery( filterAllButtons );
// do we have anything to examine?
if ( allButtons.length > 0 )
{
var navData = getBestAvailNavData();
var button = null;
var buttonOffset = { top : 0, left : 0 };
var buttonIndex = allButtons.index( jQuery( filterString ) );
if ( buttonIndex !== -1 )
{
button = allButtons.eq(buttonIndex);
}
//
// If we are certain we know what button was clicked, then we'll provide info on the form!
//
if ( button != null && button.length === 1 && typeof button.offset == 'function' )
{
buttonOffset = button.offset();
var height = jQuery(window).height();
var width = jQuery(window).width();
//
// We have all the components we want the standard button to submit to the server!
// we will now add input fields to the form we intend to submit.
//
var filterStringForm = 'form[name=add_all_dlc_to_cart]';
var formSelector = jQuery( filterStringForm );
var begintime = jQuery.data(document, 'x_readytime');
var selecttime = 0.0;
if ( begintime !== undefined )
{
selecttime = new Date().getTime() - begintime;
}
if ( formSelector.length === 1 )
{
// We include the 'hidden' attribute at this point, because of a believe compatibility issue with Internet Explorer!
jQuery( '<input type="hidden">' ).attr( { name: 'x_selection', 'value' : buttonIndex } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_choices', 'value' : allButtons.length } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_top', 'value' : buttonOffset.top } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_left', 'value' : buttonOffset.left } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_window_height', 'value' : height } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_window_width', 'value' : width } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_select_time', 'value' : selecttime } ).appendTo( formSelector );
if ( navData )
{
var pipeSplit = new RegExp( /\|/ );
var resultString = navData.split( pipeSplit )[0];
jQuery( '<input type="hidden">' ).attr( { name: 'x_oldnav', 'value' : resultString } ).appendTo( formSelector );
}
}
}
}
}
catch( e )
{
//console.log( e );
}
try
{
document.forms['add_all_dlc_to_cart'].submit();
}
catch( e )
{
}
}
function removeFromCart( gid )
{
try
{
// Find all of the add to cart buttons displayed on the page
var filterAllButtons='a.remove_link';
// the filterString can be used to find the element that invoked us, since the subid appears within it
// note that href*= specifies that href contains the string
var filterString = 'a[href*=' + gid + ']';
// within the set of all buttons, get the index of the one that we are dealing with!
// To do that, we find the anchor that invoked us within the larger set of add to cart buttons!
var allButtons = jQuery( filterAllButtons );
// do we have anything to examine?
// do we have anything to examine?
if ( allButtons.length > 0 )
{
var navData = getBestAvailNavData();
var buttonIndex = allButtons.index( jQuery( filterString ) );
//
var button = allButtons.filter( jQuery( filterString ) );
var buttonOffset = { top : 0, left : 0 };
if ( button != null && button.length === 1 && typeof button.offset == 'function' )
{
buttonOffset = button.offset();
var height = jQuery(window).height();
var width = jQuery(window).width();
//
// We have all the components we want the standard button to submit to the server!
// we will now add input fields to the form we intend to submit.
//
var filterStringForm = 'form[name=remove_line_item]';
var formSelector = jQuery( filterStringForm );
var begintime = jQuery.data(document, 'x_readytime');
var selecttime = 0.0;
if ( begintime !== undefined )
{
selecttime = new Date().getTime() - begintime;
}
if ( formSelector.length === 1 )
{
// We include the 'hidden' attribute at this point, because of a believe compatibility issue with Internet Explorer!
jQuery( '<input type="hidden">' ).attr( { name: 'x_selection', 'value' : buttonIndex } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_choices', 'value' : allButtons.length } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_top', 'value' : buttonOffset.top } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_left', 'value' : buttonOffset.left } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_window_height', 'value' : height } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_window_width', 'value' : width } ).appendTo( formSelector );
jQuery( '<input type="hidden">' ).attr( { name: 'x_select_time', 'value' : selecttime } ).appendTo( formSelector );
if ( navData )
{
var pipeSplit = new RegExp( /\|/ );
var resultString = navData.split( pipeSplit )[0];
jQuery( '<input type="hidden">' ).attr( { name: 'x_oldnav', 'value' : resultString } ).appendTo( formSelector );
}
}
}
}
}
catch( e )
{
}
try
{
document.getElementById('line_item_to_remove_gid').value = gid;
document.forms['remove_line_item'].submit();
}
catch( e )
{
}
}
function GamePurchaseDropdownSelectOption( dropdownName, subId, inCart )
{
$J('#add_to_cart_' + dropdownName + '_value').val( subId );
$J('#add_to_cart_' + dropdownName + '_selected_text').html( $J('#add_to_cart_' + dropdownName + '_menu_option_' + subId).html() );
$J('#add_to_cart_' + dropdownName + '_select_option').hide();
var $inCart = $J('#add_to_cart_' + dropdownName + '_in_cart_button');
var $addCart = $J('#add_to_cart_' + dropdownName + '_add_button');
if ( inCart )
{
$addCart.hide();
$inCart.show();
if ( typeof GPNavFocusChild !== 'undefined' )
{
GPNavFocusChild( $inCart );
}
}
else
{
$inCart.hide();
$addCart.show();
if ( typeof GPNavFocusChild !== 'undefined' )
{
GPNavFocusChild( $addCart );
}
}
if ( window.UseTabletScreenMode && window.UseTabletScreenMode() )
{
CModal.DismissActiveModal();
}
else
{
HideMenu('game_purchase_dropdown_' + dropdownName + '_region', 'add_to_cart_' + dropdownName + '_menu');
}
}
function GamePurchaseDropdownAddToCart( dropdownName )
{
if ( $J('#add_to_cart_' + dropdownName + '_value').val() == '')
{
ShowGamePurchaseDropdown( 'game_purchase_dropdown_' + dropdownName + '_region', 'add_to_cart_' + dropdownName + '_menu' );
}
else
{
addToCart( dropdownName );
}
}
function ShowGamePurchaseDropdown( elemLink, elemPopup )
{
var $Link = $JFromIDOrElement(elemLink);
var $Popup = $JFromIDOrElement(elemPopup);
var nWidth = $Link.outerWidth();
$Popup.css( 'min-width', nWidth );
if ( window.UseTabletScreenMode && window.UseTabletScreenMode() )
{
var $Content = $Popup.clone();
$Content.attr('data-panel', '{"maintainY":true,"autoFocus":true}' );
$Content.css( 'position', 'relative' );
var $Dialog = ShowDialog( '选择一个购买选项', $Content.show() );
$Dialog.AdjustSizing();
}
else
{
ShowMenu( elemLink, elemPopup, 'right', 'bottom' );
}
}
function AgeGateClear()
{
WebStorage.SetCookie('birthtime', 0, -1);
window.location.reload();
}
function InitHorizontalAutoSliders()
{
$J('.store_horizontal_autoslider' ).each( function() {
var $Scroll = $J(this);
var $Wrapper = $Scroll.wrap( $J('<div/>', {'class': 'store_horizontal_autoslider_ctn' } ) ).parent();
var $SliderCtn = $J('<div/>', {'class': 'slider_ctn store_autoslider'} );
var $SliderLeft = $J('<div/>', {'class': 'slider_left'} ).append($J('<span/>'));
var $SliderRight = $J('<div/>', {'class': 'slider_right'} ).append($J('<span/>'));
var $Slider = $J('<div/>', {'class': 'slider' } );
$SliderCtn.append(
$SliderLeft, $SliderRight,
$J('<div/>', {'class': 'slider_bg' } ),
$Slider.append( $J('<div/>', {'class': 'handle'} ) )
);
$Wrapper.after( $SliderCtn );
var fnFixHeight = function() { $Wrapper.height( $Scroll[0].clientHeight ); };
if ( $Scroll.data( 'usability' ) )
$SliderCtn.attr( 'data-usability', $Scroll.data( 'usability' ) );
$Wrapper.on('v_contentschanged.AutoSlider', function() {
fnFixHeight();
$Wrapper.find('img' ).one('load', fnFixHeight );
} );
$J(window ).on('resize.AutoSlider', fnFixHeight );
$Scroll.attr( 'data-panel', '{"maintainX":true,"bFocusRingRoot":true,"flow-children":"row"}' );
window.setTimeout( function() {
$Wrapper.trigger('v_contentschanged.AutoSlider');
var Slider = new CScrollSlider( $Scroll, $SliderCtn );
var fnGetScrollIncrement = function() {
var $TryChild = $Scroll;
do
{
$TryChild = $TryChild.children().first();
if ( $TryChild.width() && $TryChild.outerWidth() < $Scroll.width() )
{
// optional param determines whether or not we include margin
// As a note, if you use selective margin make sure you're using
// :not(:last-child) as it'll peek the first and you want to ensure
// that element has the expected margin.
return $TryChild.outerWidth( true );
}
} while ( $TryChild.length );
return $Wrapper.width() / 3;
};
$SliderLeft.click( function() {
Slider.SetValue( Slider.GetValue() - fnGetScrollIncrement(), 250 );
});
$SliderRight.click( function() {
Slider.SetValue( Slider.GetValue() + fnGetScrollIncrement(), 250 );
});
}, 1 );
});
$J('.store_horizontal_minislider' ).each( function() {
var $Scroll = $J(this);
var $Wrapper = $Scroll.wrap( $J('<div/>', {'class': 'store_horizontal_minislider_ctn' } ) ).parent();
var $SliderLeft = $J('<div/>', {'class': 'slider_left'} ).append($J('<span/>'));
var $SliderRight = $J('<div/>', {'class': 'slider_right'} ).append($J('<span/>'));
$Wrapper.append( $SliderLeft, $SliderRight );
var fnShowHideButtons = function()
{
var nTallestChild = 0;
$Scroll.children().each( function() {
nTallestChild = Math.max( nTallestChild, $J(this ).outerHeight() );
});
$Wrapper.css('height', nTallestChild );
if ( $Scroll.scrollLeft() <= 1 )
$SliderLeft.hide();
else
$SliderLeft.show();
if ( $Scroll.scrollLeft() + $Scroll.width() < $Scroll[0].scrollWidth - 1 )
$SliderRight.show();
else
$SliderRight.hide();
};
$Scroll.on( 'scroll.AutoSlider v_contentschagned.AutoSlider', fnShowHideButtons );
$J(window ).on('resize.AutoSlider', fnShowHideButtons );
$SliderLeft.click( function() {
$Scroll.animate( {scrollLeft: Math.max( 0, $Scroll.scrollLeft() - $Wrapper.width() ) } );
fnShowHideButtons();
} );
$SliderRight.click( function() {
$Scroll.animate( {scrollLeft: Math.min( $Scroll[0].scrollWidth - $Scroll.width(), $Scroll.scrollLeft() + $Wrapper.width() ) } );
fnShowHideButtons();
} );
window.setTimeout( fnShowHideButtons, 1 );
});
}
function PreloadImages( elElement )
{
$J(elElement).find("*[data-background-image-url]").each(function(i, j){
var $elTarget = $J(j);
$elTarget.css({'background-image': 'url(' + $elTarget.data('background-image-url') + ')' });
});
$J(elElement).find("img[data-image-url]").each(function(i, j){
var $elTarget = $J(j);
$elTarget.attr('src', $elTarget.data('image-url') );
});
}
// Common glue logic for a carousel of some kind
var CGenericCarousel = function( $elContainer, nSpeed, fnOnFocus, fnOnBlur, fnClickThumb, bNoWrap )
{
this.$elContainer = $elContainer;
this.nSpeed = nSpeed;
this.fnOnFocus = fnOnFocus;
this.fnOnBlur = fnOnBlur;
this.fnClickThumb = fnClickThumb;
this.bNoWrap = bNoWrap;
this.nIndex = 0;
this.$elArrowLeft = $J('.arrow.left', this.$elContainer);
this.$elArrowRight = $J('.arrow.right', this.$elContainer);
this.UpdateItems();
PreloadImages( this.$elItems[ this.nIndex ] );
// get ready to preload images when we scroll. Delay this a bit because these are low priority
// and finding them forces layout via a slow jquery :visible call
if ( this.nSpeed )
setTimeout( this.HintNearbyCapsules.bind(this), this.nSpeed * 750 );
this.fnOnFocus( this.nIndex );
var instance = this;
this.fnMouseOver = function(){
clearInterval( instance.timerAdvance );
};
this.fnMouseOut = function(){
if( instance.timerAdvance )
clearInterval( instance.timerAdvance );
if( instance.nSpeed > 0 )
{
instance.timerAdvance = setInterval ( function ()
{
if( !instance.bIsResponsive() )
instance.Advance ();
}, nSpeed * 1000 );
}
};
this.fnMouseOut();
$elContainer.bind('mouseover', function(e) { instance.fnMouseOver(); } );
$elContainer.bind('mouseout', function(e) { instance.fnMouseOut(); } );
// If we get a scroll event, and we're in respondive, hint all remaining images
var $Parent = this.$elItems.parent();
$Parent.bind('scroll', function(e) { if( instance.bIsResponsive() ) PreloadImages( $elContainer ); } );
// add panel attributes, and force an update as this element is already on the page so legacyweb doesn't see this change
$Parent.attr( 'data-panel', '{"bFocusRingRoot":true,"flow-children":"row"}' );
if ( typeof ForceUpdateFocusElements != 'undefined' )
ForceUpdateFocusElements( $Parent );
// Only bind a mouseover thumb event if we have one.
if( fnClickThumb ) {
this.$elThumbs.each(function (i, j)
{
$J(j).bind('click touchstart', (function( index, ele ) {
return function() {
instance.fnClickThumb( index, ele );
};}(i, j) )
);
});
}
// Bind arrows (if we have them)
this.$elArrowLeft.click( function(e){ instance.Advance(-1); e.preventDefault(); return true; });
this.$elArrowRight.click( function(e){ instance.Advance(); e.preventDefault(); return true; });
this.$elContainer.on('v_gamepadpress', function( e, button ) {
if ( button.button == 'BUMPER_BACK' )
{
instance.Advance( -1, true );
e.stopPropagation();
}
else if ( button.button == 'BUMPER_FORWARD' )
{
// -1 means go back 1, any other number means "go to that index".
instance.Advance( undefined, true );
e.stopPropagation();
}
}).on('focusin', function( e ) {
if ( e.target != e.currentTarget )
{
// focus on a child element; make sure it's in view
instance.$elItems.each( function ( index ) {
// $elItems are caps in main cluster, but pages in others
if ( $J.contains( this, e.target ) || this == e.target )
{
if ( !$J(this).hasClass('focus') )
instance.Advance( index );
return false; // stop iterating
}
});
}
instance.fnMouseOver();
}).on('focusout', function() {
instance.fnMouseOut();
});
if( this.$elThumbs.length < 2 )
{
this.$elThumbs.parent().css({'visibility':'hidden'})
}
this.UpdateControls();
};
// Advances the carousel by one. Optionally pass in a specific index to advance to.
CGenericCarousel.prototype.UpdateControls = function( )
{
// Only one item, so hide the arrows since they're not useful.
if( this.nItems == 1 )
{
this.$elArrowLeft.hide();
this.$elArrowRight.hide();
return;
}
// Our carousel does not wrap, so conditionally show/hider arrows
if( this.bNoWrap )
{
if( this.nIndex == 0 )
this.$elArrowLeft.hide();
else
this.$elArrowLeft.show();
if( this.nIndex == this.nItems-1 )
this.$elArrowRight.hide();
else
this.$elArrowRight.show();
}
else
{
this.$elArrowLeft.show();
this.$elArrowRight.show();
return;
}
};
// Start loading images further down the rotation.
CGenericCarousel.prototype.HintNearbyCapsules = function( )
{
PreloadImages( this.$elItems[ this.nIndex ] );
// If we're going to auto-scroll, hint the next item.
if( this.nSpeed > 0 )
{
var nNextIndex = this.GetNextValidIndex();
var elNextElement = this.$elItems[ nNextIndex ];
PreloadImages( elNextElement );
}
};
CGenericCarousel.prototype.UpdateItems = function()
{
this.$elThumbs = $J('.carousel_thumbs', this.$elContainer).children();
this.$elItems = $J('.carousel_items', this.$elContainer).children();
this.nItems = this.$elItems.length;
}
CGenericCarousel.prototype.GetNextValidIndex = function( nNewIndex )
{
if( this.nItems == 0 )
return false;
var nIndex = this.nIndex;
var rgTargets = this.$elItems;
var nSearched = 0;
if( nNewIndex < 0 ) // Allow index of -1 to go backwards.
{
// Skip hidden items
do
{
nIndex = ( nIndex + nNewIndex ) % this.nItems;
// JS's % operator doesn't wrap, so we need to fix it...
if( nIndex < 0 )
nIndex += this.nItems;
}
while( !$J( rgTargets[ nIndex ] ).is( ":visible" ) && nSearched++ < this.nItems );
}
else if( nNewIndex !== undefined )
{
if( nIndex == nNewIndex )
return false
return nNewIndex;
}
else
{
// Skip hidden items
do
{
nIndex = ( nIndex + 1 ) % this.nItems;
}
while( !$J( rgTargets[ nIndex ] ).is( ":visible" ) && nSearched++ < this.nItems );
}
return nIndex;
}
CGenericCarousel.prototype.bIsResponsive = function( )
{
return window.UseSmallScreenMode && window.UseSmallScreenMode();
}
CGenericCarousel.prototype.Advance = function( nNewIndex, bApplyFocus )
{
if( this.bIsResponsive() && !window.UseTabletScreenMode() )
{
return this.ResponsiveAdvance(nNewIndex );
}
if( this.nItems == 0 )
return;
var nNextIndex = this.GetNextValidIndex( nNewIndex );
if( nNextIndex == this.nIndex || nNextIndex === false )
return;
this.fnOnBlur( this.nIndex );
this.fnOnFocus( nNextIndex );
this.nIndex = nNextIndex;
if ( bApplyFocus && typeof GPNavFocusChild !== 'undefined' )
GPNavFocusChild( this.$elItems[this.nIndex] );
this.UpdateControls();
this.HintNearbyCapsules();
}
// Advance function may be (totally is) different in responsive mode
CGenericCarousel.prototype.ResponsiveAdvance = function( nNewIndex )
{
var $elTarget = this.$elItems.parent();
var nMaxScroll = this.$elItems.outerWidth( true ) * this.nItems - $elTarget.innerWidth();
var nCurrentScroll = $elTarget.scrollLeft();
var nScrollRate = this.$elItems.outerWidth( true );
if( nScrollRate > $elTarget.innerWidth() * 1.1 )
nScrollRate = $elTarget.innerWidth();
var nTargetScroll = nCurrentScroll + ( nNewIndex < 0 ? -1 : 1 ) * nScrollRate;
if( nCurrentScroll == nMaxScroll && nCurrentScroll > nCurrentScroll)
nTargetScroll = 0;
if( nTargetScroll > nMaxScroll )
nTargetScroll = nMaxScroll;
if( nTargetScroll < 0 )
nTargetScroll = 0;
// Hintload capsules we're about to show
this.$elItems.each( function(i, j) {
var $el = $J(j);
var rgOffset = $el.offset();
if( rgOffset.left < nScrollRate * 2 )
{
PreloadImages( $el );
}
});
$J( $elTarget ).stop().animate({scrollLeft: nTargetScroll}, 800);
}
// Carousel which adds the 'focus' class to the active element. Can be used for fading carousels
// @todo: This needs to be detangled from CGenericCarousel a bit more to be useful in other applications.....
function CreateFadingCarousel( $elContainer, nSpeed, bNoWrap, fnOnBlur )
{
var fnOnFocus = function( nIndex )
{
this.$elThumbs.removeClass( 'focus' );
this.$elItems.removeClass( 'focus' );
$J( this.$elThumbs[nIndex] ).addClass('focus');
$J( this.$elItems[nIndex] ).addClass('focus');
if ( typeof GDynamicStore != 'undefined' && GDynamicStore.m_bLoadComplete ) {
GDynamicStore.s_ImpressionTracker.TrackAppearanceIfVisible(this.$elItems[nIndex]);
}
}
var fnMouseOverThumb = function( index, element )
{
this.Advance(index);
};
if( !fnOnBlur )
fnOnBlur = function(){};
return new CGenericCarousel( $elContainer, nSpeed, fnOnFocus, fnOnBlur, fnMouseOverThumb, bNoWrap );
}
/**
* Sets up a form to automagically save itself when edited.
* Note: Ajax requests inside fnOnChange should bind their first param to async
* @param elForm
* @param fnOnChange
* @constructor
*/
function RegisterAutoSaveForm( elForm, fnOnChange )
{
// Time to wait before saving a changed element that hasn't explicitly lost focus
var nSaveDelay = 5000; // 5 seconds
// Attempt to save if we're closing the tab in a dirty state
$J( window ).on( 'unload', function(){
$J( elForm ).trigger('saveform');
} );
$J( elForm ).on( 'saveform', function(){
if (!$J.contains(document, elForm))
{
return;
}
$J( 'input[type=text],input[type=radio],input[type=checkbox],select,textarea', elForm ).trigger('save');
});
// Text elements
$J( 'input[type=text],textarea', elForm ).each(function(index, element ){
var curVal = element.value;
var timer = null;
var fnUpdateIfChanged = function( bAsync ){
if( element.value != curVal )
{
curVal = element.value;
fnOnChange ( bAsync );
}
};
$J(element).on('save', fnUpdateIfChanged.bind(null, false ) );
$J(element).on('keyup', function(){
if( timer != null )
clearTimeout( timer );
timer = setTimeout( fnUpdateIfChanged.bind(null, true ), nSaveDelay );
});
});
// input types that use .checked
$J( 'input[type=checkbox],input[type=radio]', elForm ).each(function(index, element ){
var curVal = element.checked;
var timer = null;
var fnUpdateIfChanged = function( bAsync ){
if( element.checked != curVal )
{
curVal = element.checked;
fnOnChange ( bAsync );
}
};
$J(element).on('save', fnUpdateIfChanged.bind(null, false ));
$J(element).on('change', function(){
if( timer != null )
clearTimeout( timer );
timer = setTimeout( fnUpdateIfChanged.bind(null, true ), nSaveDelay );
});
});
// select boxes
$J( 'select', elForm ).each(function(index, element ){
var curVal = element.selectedIndex;
var timer = null;
console.log(element);
var fnUpdateIfChanged = function( bAsync ){
if( element.selectedIndex != curVal )
{
curVal = element.selectedIndex;
fnOnChange ( bAsync );
}
};
$J(element).on('save', fnUpdateIfChanged.bind(null, false ));
$J(element).on('change', function(){
if( timer != null )
clearTimeout( timer );
timer = setTimeout( fnUpdateIfChanged.bind(null, true ), nSaveDelay );
});
});
}
function RecordAJAXPageView( url )
{
if ( typeof ga != "undefined" && ga )
{
var rgURLs = [ 'https://store.steampowered.com/', 'https://store.steampowered.com/' ];
for ( var i = 0; i < rgURLs.length; ++i )
{
var baseURL = rgURLs[i];
var idx = url.indexOf( baseURL );
if ( idx != -1 )
{
url = url.substring( idx + baseURL.length );
}
ga( 'send', 'pageview', url );
return;
}
}
}
function FollowCuratorWithCallback( clanID, bFollow, onComplete )
{
var bHaveUser = ( g_AccountID != 0 );
if ( !bHaveUser )
{
ShowAlertDialog("\u8bf7\u767b\u5f55", "\u60a8\u5fc5\u987b\u767b\u5f55\u624d\u80fd\u5173\u6ce8\u9274\u8d4f\u5bb6");
return;
}
$J.post(
'https://store.steampowered.com/curators/ajaxfollow',
{ 'clanid' : clanID, 'sessionid' : g_sessionID, 'follow' : bFollow ? 1 : 0 },
function( data )
{
onComplete( bFollow );
},
'json'
).fail( function()
{
ShowAlertDialog( '错误', '尝试关注 Steam 鉴赏家时出现问题。可能出现的问题:<ul><li>您不能关注已经忽略的鉴赏家。</li><li>您一次只能关注最多 100 名鉴赏家。</li><li>受限帐户不能关注鉴赏家。</li></ul>' );
}
);
return false;
}
function IgnoreCuratorWithCallback( clanID, bIgnore, onComplete )
{
var bHaveUser = ( g_AccountID != 0 );
if ( !bHaveUser )
{
ShowAlertDialog("\u8bf7\u767b\u5f55", "\u60a8\u5fc5\u987b\u767b\u5f55\u624d\u80fd\u5ffd\u7565 Steam \u9274\u8d4f\u5bb6");
return;
}
$J.post(
'https://store.steampowered.com/curators/ajaxignore',
{ 'clanid' : clanID, 'sessionid' : g_sessionID, 'ignore' : bIgnore ? 1 : 0 },
function( data )
{
onComplete( bIgnore );
GDynamicStore.InvalidateCache();
},
'json'
).fail( function()
{
ShowAlertDialog( '错误', '尝试忽略 Steam 鉴赏家时出现问题。<br>注意:您不能忽略已关注的鉴赏家。' );
}
);
return false;
}
var CUsabilityTracker = function()
{
this.m_strEndpoint = '';
this.m_fnPostCallback = null;
this.m_schUpload = null;
this.m_tsLoaded = performance.now();
this.m_rgScrollToElements = null;
this.m_stats = {
maxScroll: 0,
windowWidth: 0,
windowHeight: 0,
scrolledToSection: 0,
events: []
}
}
CUsabilityTracker.prototype.Init = function( strEndpoint )
{
this.m_strEndpoint = strEndpoint;
this.ResetStats();
var _this = this;
$Window = $J( window );
$Window.unload( function()
{
_this.OnWindowUnload();
});
$Window.on( 'scroll', function()
{
_this.SetScrollPosition();
});
$Window.on( 'click', function( e )
{
_this.HandleWindowClick( e );
});
}
CUsabilityTracker.prototype.ResetStats = function()
{
this.m_stats.windowWidth = window.innerWidth;
this.m_stats.windowHeight = window.innerHeight;
this.m_stats.events = [];
this.m_stats.maxScroll = this.GetScrollPosition();
this.m_stats.scrolledToSection = 0;
}
CUsabilityTracker.prototype.ScheduleUpload = function()
{
this.CancelScheduledUpload();
var _this = this;
this.m_schUpload = setTimeout( function() { _this.PostStats() }, 3 * 60 * 1000 );
}
CUsabilityTracker.prototype.CancelScheduledUpload = function()
{
if ( this.m_schUpload === null )
return;
clearTimeout( this.m_schUpload );
this.m_schUpload = null;
}
CUsabilityTracker.prototype.GetScrollPosition = function()
{
var nCurrent = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
return nCurrent + window.innerHeight;
}
CUsabilityTracker.prototype.SetScrollPosition = function()
{
// ignore scrolling for 1/2 second after page load
if ( (performance.now() - this.m_tsLoaded) < 500.0 )
return;
var nCurrent = this.GetScrollPosition();
// always skip updating and scheduling a new scroll if previous matches, including across page reloads that automatically scroll down
if ( nCurrent <= this.m_stats.maxScroll )
return;
this.m_stats.maxScroll = nCurrent;
this.CheckScrollToElements();
this.ScheduleUpload();
}
CUsabilityTracker.prototype.OnWindowUnload = function ()
{
this.PostStats();
}
CUsabilityTracker.prototype.HandleWindowClick = function( e )
{
var $Target = $J( e.target );
if ( !$Target )
return;
var $Tracked = $Target.closest( '[data-usability]');
if ( !$Tracked || $Tracked.length == 0 )
return;
this.AddEvent( $Tracked.data( 'usability' ) );
}
CUsabilityTracker.prototype.AddEvent = function( eEvent )
{
for ( var i = 0; i < this.m_stats.events.length; i++ )
{
if ( this.m_stats.events[i] == eEvent )
return;
}
this.m_stats.events.push( eEvent );
this.ScheduleUpload();
}
CUsabilityTracker.prototype.IncrementStat = function( strStat, nValue )
{
if ( this.m_stats[strStat] === undefined )
this.m_stats[strStat] = 0;
this.m_stats[strStat] = this.m_stats[strStat] + nValue;
}
CUsabilityTracker.prototype.CheckScrollToElements = function()
{
if ( this.m_rgScrollToElements === null )
this.m_rgScrollToElements = $J( '[data-usability-scroll]' );
var nBottomOfScreen = this.GetScrollPosition();
for ( var i = this.m_rgScrollToElements.length - 1; i >= 0; i-- )
{
var $element = $J( this.m_rgScrollToElements[i] );
if ( !$element.is(':visible') )
continue;
var nOffset = $element.offset().top;
if ( nOffset > nBottomOfScreen )
continue;
this.SetScrollToSection( $element.data( 'usability-scroll' ) );
}
}
CUsabilityTracker.prototype.SetScrollToSection = function( eEvent )
{
if ( this.m_stats.scrolledToSection < eEvent )
{
this.m_stats.scrolledToSection = eEvent;
this.ScheduleUpload();
}
}
CUsabilityTracker.prototype.SetPostCallback = function( fnCallback )
{
this.m_fnPostCallback = fnCallback;
}
CUsabilityTracker.prototype.PostStats = function()
{
// if no scheduled upload, stats aren't dirty
if ( this.m_schUpload === null )
return;
this.CancelScheduledUpload();
this.m_stats.windowWidth = window.innerWidth;
this.m_stats.windowHeight = window.innerHeight;
if ( this.m_fnPostCallback )
this.m_fnPostCallback( this.m_stats );
var strStats = JSON.stringify( this.m_stats );
var strURL = this.m_strEndpoint;
var bSupportsBeacon = typeof navigator.sendBeacon != 'undefined';
if ( bSupportsBeacon )
{
var fdParams = new FormData();
fdParams.append( 'stats', strStats );
navigator.sendBeacon( strURL, fdParams );
}
else
{
$J.ajax(
{
url: strURL,
data: { stats: strStats },
dataType: 'json',
type: 'POST'
})
.done( function( data )
{
});
// if we couldn't send a beacon, we try to busy wait for a bit so the AJAX request has time
// to reach the servers.
var iters = 0;
var start = new Date().getMilliseconds();
while ( iters < 10000000 && ( new Date().getMilliseconds() - start ) < 30 ) { iters++; }
}
this.ResetStats();
}
var g_usabilityTracker = null;
function InitUsabilityTracker( strEndpoint )
{
if ( !g_usabilityTracker )
{
g_usabilityTracker = new CUsabilityTracker();
g_usabilityTracker.Init( strEndpoint );
}
return g_usabilityTracker;
}
function GetUsabilityTracker()
{
return g_usabilityTracker;
}
function ScrollToTopStoreMobileAware()
{
var bUseSmallScreenMode = window.UseSmallScreenMode && window.UseSmallScreenMode();
var bIsMobileClient = $J('html').is('.mobile_client:not(.legacy_mobile)')
if ( bUseSmallScreenMode && !bIsMobileClient )
{
var $Window = $J( window );
$Window.scrollTop( $J( '#store_header' ).height() );
}
else
{
window.scrollTo( 0, 0 );
}
}
"use strict";
function CLoginPromptManager( strBaseURL, rgOptions )
{
// normalize with trailing slash
this.m_strBaseURL = strBaseURL + ( strBaseURL.substr(-1) == '/' ? '' : '/' ) + ( this.m_bIsMobile ? 'mobilelogin' : 'login' ) + '/';
this.m_strSiteBaseURL = strBaseURL; // Actual base url, not the login base url above.
// read options
rgOptions = rgOptions || {};
this.m_bIsMobile = rgOptions.bIsMobile || false;
this.m_strMobileClientType = rgOptions.strMobileClientType || '';
this.m_strMobileClientVersion = rgOptions.strMobileClientVersion || '';
this.m_bIsMobileSteamClient = ( this.m_strMobileClientType ? true : false );
this.m_bMobileClientSupportsPostMessage = rgOptions.bMobileClientSupportsPostMessage || false;
this.m_$LogonForm = $JFromIDOrElement( rgOptions.elLogonForm || document.forms['logon'] );
this.m_fnOnFailure = rgOptions.fnOnFailure || null;
this.m_fnOnSuccess = rgOptions.fnOnSuccess || null;
this.m_strRedirectURL = rgOptions.strRedirectURL || (this.m_bIsMobile ? '' : strBaseURL);
this.m_strSessionID = rgOptions.strSessionID || null;
this.m_strUsernameEntered = null;
this.m_strUsernameCanonical = null;
if ( rgOptions.gidCaptcha )
this.UpdateCaptcha( rgOptions.gidCaptcha );
else
this.RefreshCaptcha(); // check if needed
this.m_bLoginInFlight = false;
this.m_bInEmailAuthProcess = false;
this.m_bInTwoFactorAuthProcess = false;
this.m_TwoFactorModal = null;
this.m_bEmailAuthSuccessful = false;
this.m_bLoginTransferInProgress = false;
this.m_bEmailAuthSuccessfulWantToLeave = false;
this.m_bTwoFactorAuthSuccessful = false;
this.m_bTwoFactorAuthSuccessfulWantToLeave = false;
this.m_sOAuthRedirectURI = 'steammobile://mobileloginsucceeded';
this.m_sAuthCode = "";
this.m_sPhoneNumberLastDigits = "??";
this.m_bTwoFactorReset = false;
// values we collect from the user
this.m_steamidEmailAuth = '';
// record keeping
this.m_iIncorrectLoginFailures = 0; // mobile reveals password after a couple failures
var _this = this;
this.m_$LogonForm.submit( function(e) {
_this.DoLogin();
e.preventDefault();
});
// find buttons and make them clickable
$J('#login_btn_signin' ).children('a, button' ).click( function() { _this.DoLogin(); } );
this.InitModalContent();
// these modals need to be in the body because we refer to elements by name before they are ready
this.m_$ModalAuthCode = this.GetModalContent( 'loginAuthCodeModal' );
this.m_$ModalAuthCode.find('[data-modalstate]' ).each( function() {
$J(this).click( function() { _this.SetEmailAuthModalState( $J(this).data('modalstate') ); } );
});
this.m_$ModalAuthCode.find('form').submit( function(e) {
_this.SetEmailAuthModalState('submit');
e.preventDefault();
});
this.m_EmailAuthModal = null;
this.m_$ModalIPT = this.GetModalContent( 'loginIPTModal' );
this.m_$ModalTwoFactor = this.GetModalContent( 'loginTwoFactorCodeModal' );
this.m_$ModalTwoFactor.find( '[data-modalstate]' ).each( function() {
$J(this).click( function() { _this.SetTwoFactorAuthModalState( $J(this).data('modalstate') ); } );
});
this.m_$ModalTwoFactor.find( 'form' ).submit( function(e) {
// Prevent submit if nothing was entered
if ( $J('#twofactorcode_entry').val() != '' )
{
// Push the left button
var $btnLeft = _this.m_$ModalTwoFactor.find( '.auth_buttonset:visible .auth_button.leftbtn ' );
$btnLeft.trigger( 'click' );
}
e.preventDefault();
});
// register to listen to IOS two factor callback
$J(document).on('SteamMobile_ReceiveAuthCode', function( e, authcode ) {
_this.m_sAuthCode = authcode;
});
$J('#captchaRefreshLink' ).click( $J.proxy( this.RefreshCaptcha, this ) );
// include some additional scripts we may need
if ( typeof BigNumber == 'undefined' )
$J.ajax( { url: 'https://store.st.dl.eccdnx.com/public/shared/javascript/crypto/jsbn.js', type: 'get', dataType: 'script', cache: true } );
if ( typeof RSA == 'undefined' )
$J.ajax( { url: 'https://store.st.dl.eccdnx.com/public/shared/javascript/crypto/rsa.js', type: 'get', dataType: 'script', cache: true } );
}
CLoginPromptManager.prototype.BIsIos = function() { return this.m_strMobileClientType == 'ios'; };
CLoginPromptManager.prototype.BIsAndroid = function() { return this.m_strMobileClientType == 'android'; };
CLoginPromptManager.prototype.BIsWinRT = function() { return this.m_strMobileClientType == 'winrt'; };
CLoginPromptManager.prototype.BIsUserInMobileClientVersionOrNewer = function( nMinMajor, nMinMinor, nMinPatch ) {
if ( (!this.BIsIos() && !this.BIsAndroid() && !this.BIsWinRT() ) || this.m_strMobileClientVersion == '' )
return false;
var version = this.m_strMobileClientVersion.match( /(?:(\d+) )?\(?(\d+)\.(\d+)(?:\.(\d+))?\)?/ );
if ( version && version.length >= 3 )
{
var nMajor = parseInt( version[2] );
var nMinor = parseInt( version[3] );
var nPatch = parseInt( version[4] );
return nMajor > nMinMajor || ( nMajor == nMinMajor && ( nMinor > nMinMinor || ( nMinor == nMinMinor && nPatch >= nMinPatch ) ) );
}
};
CLoginPromptManager.prototype.GetParameters = function( rgParams )
{
var rgDefaultParams = { 'donotcache': new Date().getTime() };
if ( this.m_strSessionID )
rgDefaultParams['sessionid'] = this.m_strSessionID;
return $J.extend( rgDefaultParams, rgParams );
};
CLoginPromptManager.prototype.$LogonFormElement = function( strElementName )
{
var $Form = this.m_$LogonForm;
var elInput = this.m_$LogonForm[0].elements[ strElementName ];
if ( !elInput )
{
var $Input = $J('<input/>', {type: 'hidden', name: strElementName } );
$Form.append( $Input );
return $Input;
}
else
{
return $J( elInput );
}
};
CLoginPromptManager.prototype.HighlightFailure = function( msg )
{
if ( this.m_fnOnFailure )
{
this.m_fnOnFailure( msg );
// always blur on mobile so the error can be seen
if ( this.m_bIsMobile && msg )
$J('input:focus').blur();
}
else
{
var $ErrorElement = $J('#error_display');
if ( msg )
{
$ErrorElement.text( msg );
$ErrorElement.slideDown();
if ( this.m_bIsMobile )
$J('input:focus').blur();
}
else
{
$ErrorElement.hide();
}
}
};
//Refresh the catpcha image
CLoginPromptManager.prototype.RefreshCaptcha = function()
{
var _this = this;
$J.post( this.m_strBaseURL + 'refreshcaptcha/', this.GetParameters( {} ) )
.done( function( data ) {
_this.UpdateCaptcha( data.gid );
});
};
CLoginPromptManager.prototype.UpdateCaptcha = function( gid )
{
if ( gid != -1 )
{
$J('#captcha_entry').show();
var $ImageElement = $J('#captchaImg');
var strURL = this.m_strBaseURL + 'rendercaptcha/?gid=' + gid;
if ( $ImageElement.data( 'noborder' ) )
{
strURL += '&noborder=1';
}
$ImageElement.attr( 'src', strURL );
this.$LogonFormElement('captcha_text').val('');
}
else
{
$J('#captcha_entry' ).hide();
}
this.m_gidCaptcha = gid;
};
CLoginPromptManager.prototype.DoLogin = function()
{
var form = this.m_$LogonForm[0];
var username = form.elements['username'].value;
this.m_strUsernameEntered = username;
username = username.replace( /[^\x00-\x7F]/g, '' ); // remove non-standard-ASCII characters
this.m_strUsernameCanonical = username;
var password = form.elements['password'].value;
password = password.replace( /[^\x00-\x7F]/g, '' ); // remove non-standard-ASCII characters
if ( this.m_bLoginInFlight || password.length == 0 || username.length == 0 )
return;
this.m_bLoginInFlight = true;
$J('#login_btn_signin').hide();
$J('#login_btn_wait').show();
// reset some state
this.HighlightFailure( '' );
var _this = this;
$J.post( this.m_strBaseURL + 'getrsakey/', this.GetParameters( { username: username } ) )
.done( $J.proxy( this.OnRSAKeyResponse, this ) )
.fail( function () {
ShowAlertDialog( '错误', '与 Steam 服务器通信时出现问题。请稍后重试。' );
$J('#login_btn_signin').show();
$J('#login_btn_wait').hide();
_this.m_bLoginInFlight = false;
});
};
// used to get mobile client to execute a steammobile URL
CLoginPromptManager.prototype.RunLocalURL = function(url)
{
var $IFrame = $J('<iframe/>', {src: url} );
$J(document.body).append( $IFrame );
// take it back out immediately
$IFrame.remove();
};
var g_interval = null;
// read results from Android or WinRT clients
CLoginPromptManager.prototype.GetValueFromLocalURL = function( url, callback )
{
window.g_status = null;
window.g_data = null;
this.RunLocalURL( url );
var timeoutTime = Date.now() + 1000 * 5;
if ( g_interval != null )
{
window.clearInterval( g_interval );
g_interval = null;
}
// poll regularly (but gently) for an update.
g_interval = window.setInterval( function() {
var status = window.SGHandler.getResultStatus();
if ( status && status != 'busy' )
{
if ( g_interval )
window.clearInterval( g_interval );
var value = window.SGHandler.getResultValue();
callback( [ status, value ] );
return;
}
if ( Date.now() > timeoutTime )
{
if ( g_interval )
window.clearInterval( g_interval );
callback( ['error', 'timeout'] );
return;
}
}, 100);
};
// this function is invoked by iOS after the steammobile:// url is triggered by GetAuthCode.
// we post an event to the dom to let any login handlers deal with it.
function receiveAuthCode( code )
{
$J(document).trigger( 'SteamMobile_ReceiveAuthCode', [ code ] );
};
CLoginPromptManager.prototype.GetAuthCode = function( results, callback )
{
if ( this.m_bIsMobile )
{
// honor manual entry before anything else
var code = $J('#twofactorcode_entry').val();
if ( code.length > 0 )
{
callback( results, code );
return;
}
if ( this.BIsIos() )
{
this.m_sAuthCode = '';
this.RunLocalURL( "steammobile://twofactorcode?gid=" + results.token_gid );
// this is expected to trigger receiveAuthCode and we'll have this value set by the time it's done
if ( this.m_sAuthCode.length > 0 )
{
callback( results, this.m_sAuthCode );
return;
}
}
else if ( this.BIsAndroid() || this.BIsWinRT() )
{
var result = this.GetValueFromLocalURL('steammobile://twofactorcode?gid=' + results.token_gid, function(result) {
if ( result[0] == 'ok' )
{
callback(results, result[1]);
} else {
// this may be in the modal
callback(results, $J('#twofactorcode_entry').val());
}
});
return;
}
// this may be in the modal
callback(results, $J('#twofactorcode_entry').val());
}
else
{
var authCode = this.m_sAuthCode;
this.m_sAuthCode = '';
callback( results, authCode );
}
};
CLoginPromptManager.prototype.OnRSAKeyResponse = function( results )
{
if ( results.publickey_mod && results.publickey_exp && results.timestamp )
{
this.GetAuthCode( results , $J.proxy(this.OnAuthCodeResponse, this) );
}
else
{
if ( results.message )
{
ShowAlertDialog( '错误', results.message );
}
else
{
ShowAlertDialog( '错误', '与 Steam 服务器通信时出现问题。请稍后重试。' );
}
$J('#login_btn_signin').show();
$J('#login_btn_wait').hide();
this.m_bLoginInFlight = false;
}
};
CLoginPromptManager.prototype.OnAuthCodeResponse = function( results, authCode )
{
var form = this.m_$LogonForm[0];
var pubKey = RSA.getPublicKey(results.publickey_mod, results.publickey_exp);
var username = this.m_strUsernameCanonical;
var password = form.elements['password'].value;
password = password.replace(/[^\x00-\x7F]/g, ''); // remove non-standard-ASCII characters
var encryptedPassword = RSA.encrypt(password, pubKey);
var rgParameters = {
password: encryptedPassword,
username: username,
twofactorcode: authCode,
emailauth: form.elements['emailauth'] ? form.elements['emailauth'].value : '',
loginfriendlyname: form.elements['loginfriendlyname'] ? form.elements['loginfriendlyname'].value : '',
captchagid: this.m_gidCaptcha,
captcha_text: form.elements['captcha_text'] ? form.elements['captcha_text'].value : '',
emailsteamid: this.m_steamidEmailAuth,
rsatimestamp: results.timestamp,
remember_login: ( form.elements['remember_login'] && form.elements['remember_login'].checked ) ? 'true' : 'false'
};
if (this.m_bIsMobile)
rgParameters.oauth_client_id = form.elements['oauth_client_id'].value;
var _this = this;
$J.post(this.m_strBaseURL + 'dologin/', this.GetParameters(rgParameters))
.done($J.proxy(this.OnLoginResponse, this))
.fail(function () {
ShowAlertDialog('错误', '与 Steam 服务器通信时出现问题。请稍后重试。' );
$J('#login_btn_signin').show();
$J('#login_btn_wait').hide();
_this.m_bLoginInFlight = false;
});
};
CLoginPromptManager.prototype.OnLoginResponse = function( results )
{
this.m_bLoginInFlight = false;
var bRetry = true;
if ( results.login_complete )
{
if ( this.m_bIsMobile && results.oauth )
{
if( results.redirect_uri )
{
// Special case dota dev universe work
if ( results.redirect_uri.startsWith( "http://www.dota2.com" ) )
{
}
this.m_sOAuthRedirectURI = results.redirect_uri;
}
this.$LogonFormElement('oauth' ).val( results.oauth );
bRetry = false;
this.LoginComplete();
return;
}
var bRunningTransfer = false;
if ( ( results.transfer_url || results.transfer_urls ) && results.transfer_parameters )
{
bRunningTransfer = true;
var _this = this;
if ( !this.m_bLoginTransferInProgress )
CLoginPromptManager.TransferLogin( results.transfer_urls || [ results.transfer_url ], results.transfer_parameters, function() { _this.OnTransferComplete(); } );
this.m_bLoginTransferInProgress = true;
}
if ( this.m_bInEmailAuthProcess )
{
this.m_bEmailAuthSuccessful = true;
this.SetEmailAuthModalState( 'success' );
}
else if ( this.m_bInTwoFactorAuthProcess )
{
this.m_bTwoFactorAuthSuccessful = true;
this.SetTwoFactorAuthModalState( 'success' );
}
else
{
bRetry = false;
if ( !bRunningTransfer )
this.LoginComplete();
}
}
else
{
// if there was some kind of other error while doing email auth or twofactor, make sure
// the modals don't get stuck
if ( !results.emailauth_needed && this.m_EmailAuthModal )
this.m_EmailAuthModal.Dismiss();
if ( !results.requires_twofactor && this.m_TwoFactorModal )
this.m_TwoFactorModal.Dismiss();
if ( results.requires_twofactor )
{
$J('#captcha_entry').hide();
if ( !this.m_bInTwoFactorAuthProcess )
this.StartTwoFactorAuthProcess();
else
this.SetTwoFactorAuthModalState( 'incorrectcode' );
}
else if ( results.captcha_needed && results.captcha_gid )
{
this.UpdateCaptcha( results.captcha_gid );
this.m_iIncorrectLoginFailures ++;
}
else if ( results.emailauth_needed )
{
if ( results.emaildomain )
$J('#emailauth_entercode_emaildomain').text( results.emaildomain );
if ( results.emailsteamid )
this.m_steamidEmailAuth = results.emailsteamid;
if ( !this.m_bInEmailAuthProcess )
this.StartEmailAuthProcess();
else
this.SetEmailAuthModalState( 'incorrectcode' );
}
else if ( results.denied_ipt )
{
ShowDialog( '英特尔® 身份保护技术', this.m_$ModalIPT.show() ).always( $J.proxy( this.ClearLoginForm, this ) );
}
else if ( results.agreement_session_url )
{
window.location = results.agreement_session_url + '&redir=' + this.m_strBaseURL;
}
else
{
this.m_strUsernameEntered = null;
this.m_strUsernameCanonical = null;
this.m_iIncorrectLoginFailures ++;
}
if ( results.message )
{
this.HighlightFailure( results.message );
if ( this.m_bIsMobile && this.m_iIncorrectLoginFailures > 1 && !results.emailauth_needed && !results.bad_captcha )
{
// 2 failed logins not due to Steamguard or captcha, un-obfuscate the password field
$J( '#passwordclearlabel' ).show();
$J( '#steamPassword' ).val('');
$J( '#steamPassword' ).attr( 'type', 'text' );
$J( '#steamPassword' ).attr( 'autocomplete', 'off' );
}
else if ( results.clear_password_field )
{
$J( '#input_password' ).val('');
$J( '#input_password' ).focus();
}
}
}
if ( bRetry )
{
$J('#login_btn_signin').show();
$J('#login_btn_wait').hide();
}
};
CLoginPromptManager.prototype.ClearLoginForm = function()
{
var rgElements = this.m_$LogonForm[0].elements;
rgElements['username'].value = '';
rgElements['password'].value = '';
if ( rgElements['emailauth'] ) rgElements['emailauth'].value = '';
this.m_steamidEmailAuth = '';
// part of the email auth modal
$J('#authcode').value = '';
if ( this.m_gidCaptcha )
this.RefreshCaptcha();
rgElements['username'].focus();
};
CLoginPromptManager.prototype.StartEmailAuthProcess = function()
{
this.m_bInEmailAuthProcess = true;
this.SetEmailAuthModalState( 'entercode' );
var _this = this;
this.m_EmailAuthModal = ShowDialog( 'Steam 令牌', this.m_$ModalAuthCode.show() )
.always( function() {
$J(document.body).append( _this.m_$ModalAuthCode.hide() );
_this.CancelEmailAuthProcess();
_this.m_EmailAuthModal = null;
} );
this.m_EmailAuthModal.SetDismissOnBackgroundClick( false );
this.m_EmailAuthModal.SetRemoveContentOnDismissal( false );
$J('#authcode_entry').find('input').focus();
};
CLoginPromptManager.prototype.CancelEmailAuthProcess = function()
{
this.m_steamidEmailAuth = '';
if ( this.m_bInEmailAuthProcess )
{
this.m_bInEmailAuthProcess = false;
// if the user closed the auth window on the last step, just redirect them like we normally would
if ( this.m_bEmailAuthSuccessful )
this.LoginComplete();
}
};
CLoginPromptManager.TransferLogin = function( rgURLs, parameters, fnOnComplete )
{
var bOnCompleteFired = false;
var fnFireOnComplete = function( bSuccess )
{
if ( !bOnCompleteFired )
fnOnComplete( bSuccess );
bOnCompleteFired = true;
}
var cResponsesExpected = rgURLs.length;
$J(window).on( 'message', function() {
if ( --cResponsesExpected == 0 )
fnFireOnComplete( true );
});
for ( var i = 0 ; i < rgURLs.length; i++ )
{
var $IFrame = $J('<iframe>', {id: 'transfer_iframe' } ).hide();
$J(document.body).append( $IFrame );
var doc = $IFrame[0].contentWindow.document;
doc.open();
doc.write( '<form method="POST" action="' + rgURLs[i] + '" name="transfer_form">' );
for ( var param in parameters )
{
doc.write( '<input type="hidden" name="' + param + '" value="' + V_EscapeHTML( parameters[param] ) + '">' );
}
doc.write( '</form>' );
doc.write( '<script>window.onload = function(){ document.forms["transfer_form"].submit(); }</script>' );
doc.close();
}
// after 10 seconds, give up on waiting for transfer
window.setTimeout( function() { fnFireOnComplete( false ); }, 10000 );
};
CLoginPromptManager.prototype.OnTransferComplete = function()
{
if ( !this.m_bLoginTransferInProgress )
return;
this.m_bLoginTransferInProgress = false;
if ( !this.m_bInEmailAuthProcess && !this.m_bInTwoFactorAuthProcess )
this.LoginComplete();
else if ( this.m_bEmailAuthSuccessfulWantToLeave || this.m_bTwoFactorAuthSuccessfulWantToLeave)
this.LoginComplete();
};
CLoginPromptManager.prototype.OnEmailAuthSuccessContinue = function()
{
$J('#auth_buttonsets').children().hide();
$J('#auth_buttonset_waiting').show();
if ( this.m_bLoginTransferInProgress )
{
this.m_bEmailAuthSuccessfulWantToLeave = true;
}
else
this.LoginComplete();
};
CLoginPromptManager.prototype.LoginComplete = function()
{
if ( this.m_fnOnSuccess )
{
this.m_fnOnSuccess();
}
else if ( $J('#openidForm').length )
{
$J('#openidForm').submit();
}
else if ( this.m_strRedirectURL != '' )
{
// If this isn't one of our URLs, reject anything that looks like it has a protocol in it.
if ( this.m_strRedirectURL.match ( /^[^\/]*:/i ) )
{
if ( this.m_strRedirectURL.replace( /^http:/, 'https:' ).indexOf( this.m_strSiteBaseURL.replace( /^http:/, 'https:') ) !== 0 )
{
this.m_strRedirectURL = '';
}
}
// browsers treat multiple leading slashes as the end of the protocol specifier
if ( this.m_strRedirectURL.match( /^\/\// ) ) { this.m_strRedirectURL = ''; }
if( this.m_strRedirectURL )
window.location = this.m_strRedirectURL;
else
window.location = this.m_strSiteBaseURL
}
else if ( this.m_bIsMobile )
{
var oauthJSON = document.forms['logon'].elements['oauth'] && document.forms['logon'].elements['oauth'].value;
if ( oauthJSON && ( oauthJSON.length > 0 ) )
{
if ( this.m_bMobileClientSupportsPostMessage )
{
var strHost = window.location.protocol + '//' + window.location.host;
window.postMessage( oauthJSON, strHost );
}
else
{
window.location = this.m_sOAuthRedirectURI + '?' + oauthJSON;
}
}
}
};
CLoginPromptManager.prototype.SubmitAuthCode = function()
{
if ( !v_trim( $J('#authcode').val() ).length )
return;
$J('#auth_details_computer_name').css('color', '85847f' ); //TODO
$J('#auth_buttonsets').children().hide();
$J('#auth_buttonset_waiting').show();
this.$LogonFormElement( 'loginfriendlyname' ).val( $J('#friendlyname').val() );
this.$LogonFormElement( 'emailauth' ).val( $J('#authcode').val() );
this.DoLogin();
};
CLoginPromptManager.prototype.SetEmailAuthModalState = function( step )
{
if ( step == 'submit' )
{
this.SubmitAuthCode();
return;
}
else if ( step == 'complete' )
{
this.OnEmailAuthSuccessContinue();
return;
}
$J('#auth_messages').children().hide();
$J('#auth_message_' + step ).show();
$J('#auth_details_messages').children().hide();
$J('#auth_details_' + step ).show();
$J('#auth_buttonsets').children().hide();
$J('#auth_buttonset_' + step ).show();
$J('#authcode_help_supportlink').hide();
var icon='key';
var bShowAuthcodeEntry = true;
if ( step == 'entercode' )
{
icon = 'mail';
}
else if ( step == 'checkspam' )
{
icon = 'trash';
}
else if ( step == 'success' )
{
icon = 'unlock';
bShowAuthcodeEntry = false;
$J('#success_continue_btn').focus();
this.m_EmailAuthModal.SetDismissOnBackgroundClick( true );
this.m_EmailAuthModal.always( $J.proxy( this.LoginComplete, this ) );
}
else if ( step == 'incorrectcode' )
{
icon = 'lock';
}
else if ( step == 'help' )
{
icon = 'steam';
bShowAuthcodeEntry = false;
$J('#authcode_help_supportlink').show();
}
if ( bShowAuthcodeEntry )
{
var $AuthcodeEntry = $J('#authcode_entry');
if ( !$AuthcodeEntry.is(':visible') )
{
$AuthcodeEntry.show().find('input').focus();
}
$J('#auth_details_computer_name').show();
}
else
{
$J('#authcode_entry').hide();
$J('#auth_details_computer_name').hide();
}
$J('#auth_icon').attr('class', 'auth_icon auth_icon_' + icon );
};
CLoginPromptManager.prototype.StartTwoFactorAuthProcess = function()
{
this.m_bInTwoFactorAuthProcess = true;
this.SetTwoFactorAuthModalState( 'entercode' );
var _this = this;
this.m_TwoFactorModal = ShowDialog( 'Steam 令牌手机验证', this.m_$ModalTwoFactor.show() )
.fail( function() { _this.CancelTwoFactorAuthProcess(); } )
.always( function() {
$J(document.body).append( _this.m_$ModalTwoFactor.hide() );
_this.m_bInTwoFactorAuthProcess = false;
_this.m_TwoFactorModal = null;
} );
this.m_TwoFactorModal.SetMaxWidth( 400 );
this.m_TwoFactorModal.SetDismissOnBackgroundClick( false );
this.m_TwoFactorModal.SetRemoveContentOnDismissal( false );
$J('#twofactorcode_entry').focus();
};
CLoginPromptManager.prototype.CancelTwoFactorAuthProcess = function()
{
this.m_bInTwoFactorAuthProcess = false;
if ( this.m_bTwoFactorAuthSuccessful )
this.LoginComplete();
else
this.ClearLoginForm();
};
CLoginPromptManager.prototype.OnTwoFactorResetOptionsResponse = function( results )
{
if ( results.success && results.options.sms.allowed )
{
this.m_sPhoneNumberLastDigits = results.options.sms.last_digits;
this.SetTwoFactorAuthModalState( 'selfhelp_sms_remove' ); // Or reset if this.m_bTwoFactorReset
}
else if ( results.success )
{
this.SetTwoFactorAuthModalState( 'selfhelp_nosms' );
}
else
{
this.SetTwoFactorAuthModalState( 'selfhelp_failure' );
$J( '#login_twofactorauth_details_selfhelp_failure' ).text( results.message );
}
};
CLoginPromptManager.prototype.OnTwoFactorRecoveryFailure = function()
{
this.SetTwoFactorAuthModalState( 'selfhelp_failure' );
$J( '#login_twofactorauth_details_selfhelp_failure' ).text( '' ); // v0v
};
CLoginPromptManager.prototype.OnStartRemoveTwoFactorResponse = function( results )
{
if ( results.success )
{
this.SetTwoFactorAuthModalState( 'selfhelp_sms_remove_entercode' );
}
else
{
this.SetTwoFactorAuthModalState( 'selfhelp_failure' );
$J( '#login_twofactorauth_details_selfhelp_failure' ).text( results.message );
}
};
CLoginPromptManager.prototype.OnRemoveTwoFactorResponse = function( results )
{
if ( results.success )
{
if ( this.m_bTwoFactorReset )
{
if ( this.m_bIsMobileSteamClient && !this.m_bMobileClientSupportsPostMessage )
this.RunLocalURL( "steammobile://steamguard?op=setsecret&arg1=" + results.replacement_token );
this.SetTwoFactorAuthModalState( 'selfhelp_twofactor_replaced' );
}
else
{
this.SetTwoFactorAuthModalState( 'selfhelp_twofactor_removed' );
}
}
else if ( results.retry )
{
this.SetTwoFactorAuthModalState( 'selfhelp_sms_remove_incorrectcode' );
}
else
{
this.SetTwoFactorAuthModalState( 'selfhelp_failure' );
$J( '#login_twofactorauth_details_selfhelp_failure' ).text( results.message );
}
};
CLoginPromptManager.prototype.OnUseTwoFactorRecoveryCodeResponse = function( results )
{
if ( results.success )
{
this.SetTwoFactorAuthModalState( 'selfhelp_twofactor_removed' );
}
else if ( results.retry )
{
$J( '#login_twofactorauth_details_selfhelp_rcode_incorrectcode' ).text( results.message );
this.SetTwoFactorAuthModalState( 'selfhelp_rcode_incorrectcode' );
}
else if ( results.exhausted )
{
$J( '#login_twofactorauth_details_selfhelp_rcode_incorrectcode_exhausted' ).text( results.message );
this.SetTwoFactorAuthModalState( 'selfhelp_rcode_incorrectcode_exhausted' );
}
else
{
this.SetTwoFactorAuthModalState( 'selfhelp_failure' );
$J( '#login_twofactorauth_details_selfhelp_failure' ).text( results.message );
}
};
CLoginPromptManager.prototype.OnTwoFactorAuthSuccessContinue = function()
{
if ( !this.m_bIsMobile )
{
$J('#login_twofactorauth_buttonsets').children().hide();
$J('#login_twofactorauth_buttonset_waiting').show();
}
if ( this.m_bLoginTransferInProgress )
{
this.m_bTwoFactorAuthSuccessfulWantToLeave = true;
}
else
{
this.LoginComplete();
}
};
CLoginPromptManager.prototype.SetTwoFactorAuthModalState = function( step )
{
if ( step == 'submit' )
{
$J('#login_twofactor_authcode_entry').hide();
this.SubmitTwoFactorCode();
return;
}
else if ( step == 'success' )
{
this.OnTwoFactorAuthSuccessContinue();
return;
}
$J('#login_twofactorauth_messages').children().hide();
$J('#login_twofactorauth_message_' + step ).show();
$J('#login_twofactorauth_details_messages').children().hide();
$J('#login_twofactorauth_details_' + step ).show();
$J('#login_twofactorauth_buttonsets').children().hide();
$J('#login_twofactorauth_buttonset_' + step ).show();
$J('#login_twofactor_authcode_help_supportlink').hide();
var icon = 'key';
if ( step == 'entercode' )
{
icon = 'phone';
$J('#login_twofactor_authcode_entry').show();
$J('#twofactorcode_entry').val('');
$J('#login_twofactorauth_message_entercode_accountname').text( this.m_strUsernameEntered );
$J('#twofactorcode_entry').focus();
}
else if ( step == 'incorrectcode' )
{
icon = 'lock';
$J('#login_twofactor_authcode_entry').show();
$J('#twofactorcode_entry').val('');
$J('#twofactorcode_entry').focus();
}
else if ( step == 'selfhelp' )
{
icon = 'steam';
$J('#login_twofactor_authcode_entry').hide();
if ( !this.m_bIsMobileSteamClient
|| this.BIsAndroid() && !this.BIsUserInMobileClientVersionOrNewer( 2, 0, 32 )
|| this.BIsIos() && !this.BIsUserInMobileClientVersionOrNewer( 2, 0, 0 )
// no version minimum for Windows phones
)
{
$J( '#login_twofactorauth_buttonset_selfhelp div[data-modalstate=selfhelp_sms_reset_start]' ).hide();
}
}
else if ( step == 'selfhelp_sms_remove_start' || step == 'selfhelp_sms_reset_start' )
{
icon = 'steam';
$J('#login_twofactor_authcode_entry').hide();
$J('#login_twofactorauth_messages').children().hide();
$J('#login_twofactorauth_details_messages').children().hide();
$J('#login_twofactorauth_buttonsets').children().hide();
$J('#login_twofactorauth_buttonset_waiting').show();
this.m_bTwoFactorReset = (step == 'selfhelp_sms_reset_start');
$J.post( this.m_strBaseURL + 'getresetoptions/', this.GetParameters( {} ) )
.done( $J.proxy( this.OnTwoFactorResetOptionsResponse, this ) )
.fail( $J.proxy( this.OnTwoFactorRecoveryFailure, this ) );
}
else if ( step == 'selfhelp_sms_remove' )
{
icon = 'steam';
$J('#login_twofactorauth_selfhelp_sms_remove_last_digits').text( this.m_sPhoneNumberLastDigits );
}
else if ( step == 'selfhelp_sms_remove_sendcode' )
{
icon = 'steam';
$J('#login_twofactor_authcode_entry').hide();
$J('#login_twofactorauth_messages').children().hide();
$J('#login_twofactorauth_details_messages').children().hide();
$J('#login_twofactorauth_buttonsets').children().hide();
$J('#login_twofactorauth_buttonset_waiting').show();
$J.post( this.m_strBaseURL + 'startremovetwofactor/', this.GetParameters( {} ) )
.done( $J.proxy( this.OnStartRemoveTwoFactorResponse, this ) )
.fail( $J.proxy( this.OnTwoFactorRecoveryFailure, this ) );
}
else if ( step == 'selfhelp_sms_remove_entercode' )
{
$J('#login_twofactorauth_selfhelp_sms_remove_entercode_last_digits').text( this.m_sPhoneNumberLastDigits );
$J('#login_twofactor_authcode_entry').show();
$J('#twofactorcode_entry').val('');
$J('#twofactorcode_entry').focus();
}
else if ( step == 'selfhelp_sms_remove_checkcode' )
{
$J('#login_twofactor_authcode_entry').hide();
$J('#login_twofactorauth_messages').children().hide();
$J('#login_twofactorauth_details_messages').children().hide();
$J('#login_twofactorauth_buttonsets').children().hide();
$J('#login_twofactorauth_buttonset_waiting').show();
// Immediately skip to incorrect code step without actually checking it if the user forgot to enter a code.
if ( $J('#twofactorcode_entry').val().length == 0 )
{
this.SetTwoFactorAuthModalState( 'selfhelp_sms_remove_incorrectcode' );
}
else
{
var rgParameters = {
smscode: $J( '#twofactorcode_entry' ).val(),
reset: this.m_bTwoFactorReset ? 1 : 0
};
$J.post( this.m_strBaseURL + 'removetwofactor/', this.GetParameters( rgParameters ) )
.done( $J.proxy( this.OnRemoveTwoFactorResponse, this ) )
.fail( $J.proxy( this.OnTwoFactorRecoveryFailure, this ) );
}
}
else if ( step == 'selfhelp_sms_remove_incorrectcode' )
{
icon = 'lock';
$J('#login_twofactor_authcode_entry').show();
$J('#twofactorcode_entry').focus();
}
else if ( step == 'selfhelp_twofactor_removed' )
{
icon = 'unlock';
$J('#twofactorcode_entry').val(''); // Make sure the next login doesn't supply a code
}
else if ( step == 'selfhelp_twofactor_replaced' )
{
icon = 'steam';
$J('#twofactorcode_entry').val('');
}
else if ( step == 'selfhelp_sms_remove_complete' )
{
this.m_TwoFactorModal.Dismiss();
this.m_bInTwoFactorAuthProcess = false;
this.DoLogin();
}
else if ( step == 'selfhelp_nosms' )
{
icon = 'steam';
$J('#login_twofactor_authcode_entry').hide();
}
else if ( step == 'selfhelp_rcode' )
{
$J('#login_twofactor_authcode_entry').show();
$J('#twofactorcode_entry').val('');
$J('#twofactorcode_entry').focus();
}
else if ( step == 'selfhelp_rcode_checkcode' )
{
$J('#login_twofactor_authcode_entry').hide();
$J('#login_twofactorauth_messages').children().hide();
$J('#login_twofactorauth_details_messages').children().hide();
$J('#login_twofactorauth_buttonsets').children().hide();
$J('#login_twofactorauth_buttonset_waiting').show();
// Immediately skip to incorrect code step without actually checking it if the user forgot to enter a code.
if ( $J('#twofactorcode_entry').val().length == 0 )
{
this.SetTwoFactorAuthModalState( 'selfhelp_rcode_incorrectcode' );
}
else
{
var rgParameters = { rcode: $J( '#twofactorcode_entry' ).val() };
$J.post( this.m_strBaseURL + 'userecoverycode/', this.GetParameters( rgParameters ) )
.done( $J.proxy( this.OnUseTwoFactorRecoveryCodeResponse, this ) )
.fail( $J.proxy( this.OnTwoFactorRecoveryFailure, this ) );
}
}
else if ( step == 'selfhelp_rcode_incorrectcode' )
{
icon = 'lock';
$J('#login_twofactor_authcode_entry').show();
$J('#twofactorcode_entry').focus();
}
else if ( step == 'selfhelp_couldnthelp' )
{
icon = 'steam';
$J('#login_twofactor_authcode_entry').hide();
}
else if ( step == 'help' )
{
icon = 'steam';
$J('#login_twofactor_authcode_entry').hide();
$J('#login_twofactor_authcode_help_supportlink').show();
}
else if ( step == 'selfhelp_failure' )
{
icon = 'steam';
}
if ( this.m_bInTwoFactorAuthProcess && this.m_TwoFactorModal )
{
this.m_TwoFactorModal.AdjustSizing();
}
$J('#login_twofactorauth_icon').attr( 'class', 'auth_icon auth_icon_' + icon );
};
CLoginPromptManager.prototype.SubmitTwoFactorCode = function()
{
this.m_sAuthCode = $J('#twofactorcode_entry').val();
$J('#login_twofactorauth_messages').children().hide();
$J('#login_twofactorauth_details_messages').children().hide();
$J('#login_twofactorauth_buttonsets').children().hide();
$J('#login_twofactorauth_buttonset_waiting').show();
this.DoLogin();
};
CLoginPromptManager.sm_$Modals = null; // static
CLoginPromptManager.prototype.InitModalContent = function()
{
var $modals = $J('#loginModals');
if ( $modals.length == 0 )
{
// This does not work on Android 2.3, nor does creating the DOM node and
// setting innerHTML without jQuery. So on the mobile login page, we put
// the modals into the page directly, but not all pages have that.
CLoginPromptManager.sm_$Modals = $J( "<div id=\"loginModals\">\r\n\t<div class=\"login_modal loginAuthCodeModal\" style=\"display: none\">\r\n\t\t<form data-ajax=\"false\">\r\n\t\t\t<div class=\"auth_message_area\">\r\n\t\t\t\t<div id=\"auth_icon\" class=\"auth_icon auth_icon_key\">\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_messages\" id=\"auth_messages\">\r\n\t\t\t\t\t<div class=\"auth_message\" id=\"auth_message_entercode\" style=\"display: none;\">\r\n\t\t\t\t\t\t<div class=\"auth_modal_h1\">\u60a8\u597d\uff01<\/div>\r\n\t\t\t\t\t\t<p>\u6211\u4eec\u53d1\u73b0\u60a8\u6b63\u5728\u4f7f\u7528\u4e00\u53f0\u65b0\u7684\u7535\u8111\u6216\u65b0\u7684\u6d4f\u89c8\u5668\u8fdb\u884c\u767b\u5f55\u3002\u6216\u8005\uff0c\u4e5f\u8bb8\u8fd9\u5df2\u7ecf\u6709\u4e00\u6bb5\u65f6\u95f4...<\/p>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div class=\"auth_message\" id=\"auth_message_checkspam\" style=\"display: none;\">\r\n\t\t\t\t\t\t<div class=\"auth_modal_h1\">\u9519\u8ba4\u4e3a\u5783\u573e\u90ae\u4ef6\uff1f<\/div>\r\n\t\t\t\t\t\t<p>\u60a8\u6709\u68c0\u67e5\u8fc7\u60a8\u7684\u5783\u573e\u90ae\u4ef6\u6587\u4ef6\u5939\u5417\uff1f \u5982\u679c\u60a8\u5728\u6536\u4ef6\u7bb1\u4e2d\u6ca1\u6709\u770b\u5230\u6700\u8fd1\u7684\u4e00\u6761\u6765\u81ea Steam \u5ba2\u670d\u7684\u6d88\u606f\uff0c\u53bb\u90a3\u513f\u770b\u770b\u5427\u3002<\/p>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div class=\"auth_message\" id=\"auth_message_success\" style=\"display: none;\">\r\n\t\t\t\t\t\t<div class=\"auth_modal_h1\">\u6210\u529f\uff01<\/div>\r\n\t\t\t\t\t\t<p>\u73b0\u5728\uff0c\u60a8\u53ef\u4ee5\u5728\u8fd9\u91cc\u8bbf\u95ee\u60a8\u7684 Steam \u5e10\u6237\u3002<\/p>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div class=\"auth_message\" id=\"auth_message_incorrectcode\" style=\"display: none;\">\r\n\t\t\t\t\t\t<div class=\"auth_modal_h1\">\u54ce\u5440\uff01<\/div>\r\n\t\t\t\t\t\t<p>\u62b1\u6b49\uff0c\r\n<br>\u60a8\u6240\u8f93\u5165\u7684\u9a8c\u8bc1\u7801\u4e0d\u6b63\u786e\u2026<\/p>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div class=\"auth_message\" id=\"auth_message_help\" style=\"display: none;\">\r\n\t\t\t\t\t\t<div class=\"auth_modal_h1\">\u8ba9\u6211\u4eec\u6765\u5e2e\u60a8\u5427\uff01<\/div>\r\n\t\t\t\t\t\t<p>\u5f88\u62b1\u6b49\u60a8\u9047\u5230\u95ee\u9898\u3002\u6211\u4eec\u77e5\u9053 Steam \u5e10\u6237\u5bf9\u60a8\u6765\u8bf4\u975e\u5e38\u73cd\u8d35\uff0c\u6211\u4eec\u81f4\u529b\u4e8e\u9632\u6b62\u60a8\u7684\u5e10\u6237\u906d\u5230\u975e\u6cd5\u76d7\u7528\u3002<\/p>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div id=\"auth_details_messages\" class=\"auth_details_messages\">\r\n\t\t\t\t<div class=\"auth_details\" id=\"auth_details_entercode\" style=\"display: none;\">\r\n\t\t\t\t\t\u4f5c\u4e3a\u989d\u5916\u7684\u5e10\u6237\u5b89\u5168\u63aa\u65bd\uff0c\u60a8\u9700\u8981\u8f93\u5165\u6211\u4eec\u521a\u521a\u53d1\u5230\u60a8\u5728 <span id=\"emailauth_entercode_emaildomain\"><\/span> \u7684\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u7684\u7279\u6b8a\u9a8c\u8bc1\u7801\uff0c\u624d\u80fd\u6388\u6743\u6b64\u6d4f\u89c8\u5668\u8fdb\u884c\u767b\u5f55\u3002\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_details\" id=\"auth_details_success\" style=\"display: none;\">\r\n\t\t\t\t\t\u5982\u679c\u8fd9\u662f\u4e00\u53f0\u516c\u7528\u8ba1\u7b97\u673a\uff0c\u8bf7\u52a1\u5fc5\u5728\u60a8\u7ed3\u675f\u4f1a\u8bdd\u65f6\u6ce8\u9500 Steam\u3002\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_details\" id=\"auth_details_help\" style=\"display: none;\">\r\n\t\t\t\t\t\u8bf7\u8054\u7cfb Steam \u5ba2\u670d\u6765\u83b7\u5f97\u6211\u4eec\u5de5\u4f5c\u4eba\u5458\u7684\u5e2e\u52a9\u3002\u89e3\u51b3\u5408\u6cd5\u7528\u6237\u7684\u5e10\u6237\u767b\u5f55\u95ee\u9898\u662f\u6211\u4eec\u7684\u7b2c\u4e00\u8981\u52a1\u3002\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"authcode_entry_area\">\r\n\t\t\t\t<div id=\"authcode_entry\">\r\n\t\t\t\t\t<div class=\"authcode_entry_box\">\r\n\t\t\t\t\t\t<input class=\"authcode_entry_input authcode_placeholder\" id=\"authcode\" type=\"text\" value=\"\"\r\n\t\t\t\t\t\t\t placeholder=\"\u5728\u6b64\u8f93\u5165\u9a8c\u8bc1\u7801\">\r\n\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div id=\"authcode_help_supportlink\">\r\n\t\t\t\t\t<a href=\"https:\/\/help.steampowered.com\/zh-cn\/faqs\/view\/06B0-26E6-2CF8-254C\" data-ajax=\"false\" data-externallink=\"1\">\u8054\u7cfb Steam \u5ba2\u670d\u6765\u83b7\u5f97\u5e10\u6237\u76f8\u5173\u7684\u5e2e\u52a9<\/a>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"modal_buttons\" id=\"auth_buttonsets\">\r\n\t\t\t\t<div class=\"auth_buttonset\" id=\"auth_buttonset_entercode\" style=\"display: none;\">\r\n\t\t\t\t\t<div data-modalstate=\"submit\" class=\"auth_button leftbtn\">\r\n\t\t\t\t\t\t<div class=\"auth_button_h3\">\u63d0\u4ea4<\/div>\r\n\t\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u7684\u7279\u6b8a\u9a8c\u8bc1\u7801<\/div>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div data-modalstate=\"checkspam\" class=\"auth_button\">\r\n\t\t\t\t\t\t<div class=\"auth_button_h3\">\u4ec0\u4e48\u4fe1\u606f\uff1f<\/div>\r\n\t\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u6ca1\u6709\u6536\u5230\u6765\u81ea Steam \u5ba2\u670d\u7684\u4efb\u4f55\u6d88\u606f\u2026<\/div>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div style=\"clear: left;\"><\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_buttonset\" id=\"auth_buttonset_checkspam\" style=\"display: none;\">\r\n\t\t\t\t\t<div data-modalstate=\"submit\" class=\"auth_button leftbtn\">\r\n\t\t\t\t\t\t<div class=\"auth_button_h3\">\u627e\u5230\u5b83\u4e86\uff01<\/div>\r\n\t\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u5df2\u7ecf\u5728\u4e0a\u9762\u8f93\u5165\u4e86\u7279\u6b8a\u9a8c\u8bc1\u7801<\/div>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div data-modalstate=\"help\" class=\"auth_button\">\r\n\t\t\t\t\t\t<div class=\"auth_button_h3\">\u4f9d\u65e7\u4e0d\u5e78\u2026<\/div>\r\n\t\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u6ca1\u6709\u6536\u5230\u6765\u81ea Steam \u5ba2\u670d\u7684\u4efb\u4f55\u6d88\u606f\u2026<\/div>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div style=\"clear: left;\"><\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_buttonset\" id=\"auth_buttonset_success\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_button auth_button_spacer\">\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<a data-modalstate=\"complete\" class=\"auth_button\" id=\"success_continue_btn\" href=\"javascript:void(0);\">\r\n\t\t\t\t\t\t<div class=\"auth_button_h3\">\u8f6c\u5230 Steam \uff01<\/div>\r\n\t\t\t\t\t\t<div class=\"auth_button_h5\">&nbsp;<br>&nbsp;<\/div>\r\n\t\t\t\t\t<\/a>\r\n\t\t\t\t\t<div style=\"clear: left;\"><\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_buttonset\" id=\"auth_buttonset_incorrectcode\" style=\"display: none;\">\r\n\t\t\t\t\t<div data-modalstate=\"submit\" class=\"auth_button leftbtn\">\r\n\t\t\t\t\t\t<div class=\"auth_button_h3\">\u6211\u60f3\u518d\u8bd5\u4e00\u6b21<\/div>\r\n\t\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u5df2\u7ecf\u5728\u4e0a\u9762\u91cd\u65b0\u8f93\u5165\u4e86\u7279\u6b8a\u9a8c\u8bc1\u7801<\/div>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div data-modalstate=\"help\" class=\"auth_button\">\r\n\t\t\t\t\t\t<div class=\"auth_button_h3\">\u8bf7\u6c42\u534f\u52a9<\/div>\r\n\t\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u60f3\u6211\u9700\u8981 Steam \u5ba2\u670d\u7684\u63f4\u52a9\u2026<\/div>\r\n\t\t\t\t\t<\/div>\r\n\t\t\t\t\t<div style=\"clear: left;\"><\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_buttonset\" id=\"auth_buttonset_waiting\" style=\"display: none;\">\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div style=\"\" id=\"auth_details_computer_name\" class=\"auth_details_messages\">\r\n\t\t\t\t\u4e3a\u4e86\u8ba9\u6b64\u6d4f\u89c8\u5668\u5728\u5df2\u542f\u7528 Steam \u4ee4\u724c\u7684\u8bbe\u5907\u5217\u8868\u4e2d\u6613\u4e8e\u8bc6\u522b\uff0c\u7ed9\u5b83\u8d77\u4e00\u4e2a\u597d\u8bb0\u7684\u540d\u79f0\u2014\u2014\u81f3\u5c11 6 \u4e2a\u5b57\u7b26\u957f\u3002\t\t\t\t<div id=\"friendly_name_box\" class=\"friendly_name_box\">\r\n\t\t\t\t\t<input class=\"authcode_entry_input authcode_placeholder\" id=\"friendlyname\" type=\"text\"\r\n\t\t\t\t\t\t placeholder=\"\u5728\u8fd9\u91cc\u8f93\u5165\u4e00\u4e2a\u597d\u8bb0\u7684\u540d\u79f0\">\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div style=\"display: none;\">\r\n\t\t\t\t<input type=\"submit\">\r\n\t\t\t<\/div>\r\n\t\t<\/form>\r\n\t<\/div>\r\n\r\n\t<div class=\"login_modal loginIPTModal\" style=\"display: none\">\r\n\t\t<div class=\"auth_message_area\">\r\n\t\t\t<div class=\"auth_icon ipt_icon\">\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_messages\">\r\n\t\t\t\t<div class=\"auth_message\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u62b1\u6b49<\/div>\r\n\t\t\t\t\t<p>\u6b64\u5e10\u6237\u5728\u6ca1\u6709\u989d\u5916\u6388\u6743\u7684\u60c5\u51b5\u4e0b\u4e0d\u80fd\u4ece\u6b64\u8ba1\u7b97\u673a\u8bbf\u95ee\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t\t<div class=\"auth_details_messages\">\r\n\t\t\t<div class=\"auth_details\">\r\n\t\t\t\t\u8bf7\u8054\u7cfb Steam \u5ba2\u670d\u6765\u83b7\u5f97\u6211\u4eec\u5de5\u4f5c\u4eba\u5458\u7684\u5e2e\u52a9\u3002\u89e3\u51b3\u5408\u6cd5\u7528\u6237\u7684\u5e10\u6237\u767b\u5f55\u95ee\u9898\u662f\u6211\u4eec\u7684\u7b2c\u4e00\u8981\u52a1\u3002\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t\t<div class=\"authcode_entry_area\">\r\n\t\t<\/div>\r\n\t\t<div class=\"modal_buttons\">\r\n\t\t\t<div class=\"auth_buttonset\" >\r\n\t\t\t\t<a href=\"https:\/\/help.steampowered.com\/\" class=\"auth_button leftbtn\" data-ajax=\"false\" data-externallink=\"1\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u4e86\u89e3\u66f4\u591a<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u4e86\u89e3\u66f4\u591a\u6709\u5173 \u82f1\u7279\u5c14&reg; ID \u4fdd\u62a4\u6280\u672f \u7684\u5185\u5bb9<\/div>\r\n\t\t\t\t<\/a>\r\n\t\t\t\t<a href=\"https:\/\/support.steampowered.com\" class=\"auth_button\" data-ajax=\"false\" data-externallink=\"1\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u8bf7\u6c42\u534f\u52a9<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u60f3\u6211\u9700\u8981 Steam \u5ba2\u670d\u7684\u63f4\u52a9\u2026<\/div>\r\n\t\t\t\t<\/a>\r\n\t\t\t\t<div style=\"clear: left;\"><\/div>\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t<\/div>\r\n\r\n\r\n\r\n\t<div class=\"login_modal loginTwoFactorCodeModal\" style=\"display: none;\">\r\n\t\t<form>\r\n\t\t<div class=\"twofactorauth_message_area\">\r\n\t\t\t<div id=\"login_twofactorauth_icon\" class=\"auth_icon auth_icon_key\">\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"twofactorauth_messages\" id=\"login_twofactorauth_messages\">\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_entercode\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\"><span id=\"login_twofactorauth_message_entercode_accountname\"><\/span> \u60a8\u597d\uff01<\/div>\r\n\t\t\t\t\t<p>\u8be5\u5e10\u6237\u76ee\u524d\u6b63\u5728\u4f7f\u7528 Steam \u4ee4\u724c\u624b\u673a\u9a8c\u8bc1\u5668\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_incorrectcode\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u54ce\u5440\uff01<\/div>\r\n\t\t\t\t\t<p>\u62b1\u6b49\uff0c<br>\u60a8\u6240\u8f93\u5165\u7684\u9a8c\u8bc1\u7801\u4e0d\u6b63\u786e\u2026<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u8ba9\u6211\u4eec\u6765\u5e2e\u60a8\u5427\uff01<\/div>\r\n\t\t\t\t\t<p>\u5f88\u62b1\u6b49\u60a8\u9047\u5230\u95ee\u9898\u3002\u6211\u4eec\u77e5\u9053 Steam \u5e10\u6237\u5bf9\u60a8\u6765\u8bf4\u975e\u5e38\u73cd\u8d35\uff0c\u6211\u4eec\u81f4\u529b\u4e8e\u9632\u6b62\u60a8\u7684\u5e10\u6237\u906d\u5230\u975e\u6cd5\u76d7\u7528\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_sms_remove\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u786e\u8ba4\u60a8\u5e10\u6237\u7684\u6240\u6709\u6743<\/div>\r\n\t\t\t\t\t<p>\u6211\u4eec\u4f1a\u53d1\u9001\u4e00\u6761\u5305\u542b\u5e10\u6237\u6062\u590d\u4ee3\u7801\u7684\u77ed\u4fe1\u5230\u5c3e\u53f7\u4e3a <span id=\"login_twofactorauth_selfhelp_sms_remove_last_digits\"><\/span> \u7684\u624b\u673a\u53f7\u7801\u3002\u4e00\u65e6\u60a8\u8f93\u5165\u4ee3\u7801\uff0c\u6211\u4eec\u4f1a\u4ece\u60a8\u7684\u5e10\u6237\u79fb\u9664\u624b\u673a\u9a8c\u8bc1\u5668\uff0c\u60a8\u5c06\u901a\u8fc7\u7535\u5b50\u90ae\u4ef6\u63a5\u6536 Steam \u4ee4\u724c\u9a8c\u8bc1\u7801\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_sms_remove_entercode\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u786e\u8ba4\u60a8\u5e10\u6237\u7684\u6240\u6709\u6743<\/div>\r\n\t\t\t\t\t<p>\u6211\u4eec\u5df2\u5411\u60a8\u5c3e\u53f7\u4e3a <span id=\"login_twofactorauth_selfhelp_sms_remove_entercode_last_digits\"><\/span> \u7684\u624b\u673a\u53f7\u7801\u53d1\u9001\u4e86\u4e00\u6761\u5305\u542b\u786e\u8ba4\u4ee3\u7801\u7684\u77ed\u4fe1\u3002\u5728\u4e0b\u65b9\u8f93\u5165\u4ee3\u7801\u4ee5\u65b9\u4fbf\u6211\u4eec\u4ece\u60a8\u7684\u5e10\u6237\u79fb\u9664\u624b\u673a\u9a8c\u8bc1\u5668\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_sms_remove_incorrectcode\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u54ce\u5440\uff01<\/div>\r\n\t\t\t\t\t<p>\u62b1\u6b49\uff0c<br>\u60a8\u6240\u8f93\u5165\u7684\u9a8c\u8bc1\u7801\u4e0d\u6b63\u786e\u2026<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_twofactor_removed\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u6210\u529f\uff01<\/div>\r\n\t\t\t\t\t<p>\u6211\u4eec\u5df2\u4ece\u60a8\u7684\u5e10\u6237\u79fb\u9664\u624b\u673a\u9a8c\u8bc1\u5668\u3002\u4e0b\u4e00\u6b21\u60a8\u767b\u5f55\u65f6\uff0c\u60a8\u9700\u8981\u8f93\u5165\u53d1\u9001\u5230\u60a8\u7684\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u7684 Steam \u4ee4\u724c\u9a8c\u8bc1\u7801\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_twofactor_replaced\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u6210\u529f\uff01<\/div>\r\n\t\t\t\t\t<p>\u60a8\u53ef\u4ee5\u4f7f\u7528\u8be5\u8bbe\u5907\u4e3a\u60a8\u7684\u5e10\u6237\u83b7\u53d6\u624b\u673a\u9a8c\u8bc1\u7801\u3002\u6b64\u524d\u4e3a\u60a8\u7684\u5e10\u6237\u63d0\u4f9b\u9a8c\u8bc1\u7801\u7684\u4efb\u4f55\u5176\u4ed6\u8bbe\u5907\u5c06\u4e0d\u518d\u62e5\u6709\u64cd\u4f5c\u6743\u9650\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_nosms\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u60a8\u662f\u5426\u62e5\u6709\u6062\u590d\u7801\uff1f<\/div>\r\n\t\t\t\t\t<p>\u60a8\u6ca1\u6709\u4e0e Steam \u5e10\u6237\u76f8\u5173\u8054\u7684\u624b\u673a\u53f7\u7801\uff0c\u56e0\u4e3a\u6211\u4eec\u65e0\u6cd5\u901a\u8fc7\u77ed\u4fe1\u9a8c\u8bc1\u5e10\u6237\u7684\u6240\u6709\u6743\u3002\u60a8\u662f\u5426\u62e5\u6709\u5728\u6dfb\u52a0\u624b\u673a\u9a8c\u8bc1\u5668\u65f6\u8bb0\u4e0b\u7684\u6062\u590d\u7801\uff1f\u6062\u590d\u7801\u4ee5\u5b57\u6bcd\u201cR\u201d\u5f00\u5934\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_rcode\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u8f93\u5165\u60a8\u7684\u6062\u590d\u7801\r\n<\/div>\r\n\t\t\t\t\t<p>\u8bf7\u5728\u4e0b\u65b9\u7684\u5bf9\u8bdd\u6846\u5185\u8f93\u5165\u60a8\u7684\u6062\u590d\u7801\u3002\u6062\u590d\u7801\u4ee5\u5b57\u6bcd\u201cR\u201d\u5f00\u5934\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_rcode_incorrectcode\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u54ce\u5440\uff01<\/div>\r\n\t\t\t\t\t<p>\u62b1\u6b49\uff0c<br>\u60a8\u6240\u8f93\u5165\u7684\u9a8c\u8bc1\u7801\u4e0d\u6b63\u786e\u2026<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_rcode_incorrectcode_exhausted\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u54ce\u5440\uff01<\/div>\r\n\t\t\t\t\t<p>\u62b1\u6b49\uff0c<br>\u60a8\u6240\u8f93\u5165\u7684\u9a8c\u8bc1\u7801\u4e0d\u6b63\u786e\u2026<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_rcode_message\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u54ce\u5440\uff01<\/div>\r\n\t\t\t\t\t<p>\u62b1\u6b49\uff0c<br>\u60a8\u6240\u8f93\u5165\u7684\u9a8c\u8bc1\u7801\u4e0d\u6b63\u786e\u2026<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_couldnthelp\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u8ba9\u6211\u4eec\u6765\u5e2e\u60a8\u5427\uff01<\/div>\r\n\t\t\t\t\t<p>\u5982\u679c\u60a8\u65e0\u6cd5\u4f7f\u7528\u60a8\u7684\u79fb\u52a8\u8bbe\u5907\u3001\u4e0e\u5e10\u6237\u5173\u8054\u7684\u624b\u673a\u53f7\u7801\uff0c\u5e76\u4e14\u6ca1\u6709\u5728\u60a8\u6dfb\u52a0\u624b\u673a\u9a8c\u8bc1\u5668\u65f6\u8bb0\u4e0b\u6062\u590d\u7801\uff0c\u90a3\u4e48\u8bf7\u8054\u7cfb Steam \u5ba2\u670d\u534f\u52a9\u60a8\u6062\u590d\u5e10\u6237\u7684\u8bbf\u95ee\u6743\u9650\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_help\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u8ba9\u6211\u4eec\u6765\u5e2e\u60a8\u5427\uff01<\/div>\r\n\t\t\t\t\t<p>\u5f88\u62b1\u6b49\u60a8\u9047\u5230\u95ee\u9898\u3002\u6211\u4eec\u77e5\u9053 Steam \u5e10\u6237\u5bf9\u60a8\u6765\u8bf4\u975e\u5e38\u73cd\u8d35\uff0c\u6211\u4eec\u81f4\u529b\u4e8e\u9632\u6b62\u60a8\u7684\u5e10\u6237\u906d\u5230\u975e\u6cd5\u76d7\u7528\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"twofactorauth_message\" id=\"login_twofactorauth_message_selfhelp_failure\" style=\"display: none;\">\r\n\t\t\t\t\t<div class=\"auth_modal_h1\">\u62b1\u6b49\uff01<\/div>\r\n\t\t\t\t\t<p>\u5728\u5904\u7406\u60a8\u7684\u8bf7\u6c42\u65f6\u9047\u5230\u9519\u8bef\u3002<\/p>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t\t<div id=\"login_twofactorauth_details_messages\" class=\"twofactorauth_details_messages\">\r\n\t\t\t<div class=\"twofactorauth_details\" id=\"login_twofactorauth_details_entercode\" style=\"display: none;\">\r\n\t\t\t\t\u8f93\u5165\u663e\u793a\u5728 Steam \u624b\u673a\u5e94\u7528\u4e0a\u7684\u6700\u65b0\u9a8c\u8bc1\u7801\uff1a\t\t\t<\/div>\r\n\t\t\t<div class=\"twofactorauth_details\" id=\"login_twofactorauth_details_selfhelp\" style=\"display: none;\">\r\n\t\t\t\t\u5982\u679c\u60a8\u65e0\u6cd5\u4f7f\u7528\u79fb\u52a8\u8bbe\u5907\uff0c\u6216\u662f\u7531\u4e8e\u5378\u8f7d\u4e86 Steam \u5e94\u7528\u800c\u65e0\u6cd5\u63a5\u6536\u4ee3\u7801\uff0c\u90a3\u4e48\u60a8\u53ef\u4ee5\u4ece\u5e10\u6237\u4e2d\u79fb\u9664\u624b\u673a\u9a8c\u8bc1\u5668\u3002\u8fd9\u5c06\u51cf\u5c11\u60a8\u5e10\u6237\u7684\u5b89\u5168\uff0c\u56e0\u6b64\u60a8\u5e94\u8be5\u5728\u8fd9\u4e4b\u540e\u5c06\u624b\u673a\u9a8c\u8bc1\u5668\u6dfb\u52a0\u5230\u65b0\u7684\u79fb\u52a8\u8bbe\u5907\u4e0a\u3002\t\t\t<\/div>\r\n\t\t\t<div class=\"twofactorauth_details\" id=\"login_twofactorauth_details_help\" style=\"display: none;\">\r\n\t\t\t\t\u8bf7\u8054\u7cfb Steam \u5ba2\u670d\u6765\u83b7\u5f97\u6211\u4eec\u5de5\u4f5c\u4eba\u5458\u7684\u5e2e\u52a9\u3002\t\t\t<\/div>\r\n\t\t\t<div class=\"twofactorauth_details\" id=\"login_twofactorauth_details_selfhelp_failure\" style=\"display: none;\">\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"twofactorauth_details\" id=\"login_twofactorauth_details_selfhelp_rcode_incorrectcode\" style=\"display: none;\">\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"twofactorauth_details\" id=\"login_twofactorauth_details_selfhelp_rcode_incorrectcode_exhausted\" style=\"display: none;\">\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t\t<div class=\"twofactorauthcode_entry_area\">\r\n\t\t\t<div id=\"login_twofactor_authcode_entry\">\r\n\t\t\t\t<div class=\"twofactorauthcode_entry_box\">\r\n\t\t\t\t\t<input class=\"twofactorauthcode_entry_input authcode_placeholder\" id=\"twofactorcode_entry\" type=\"text\"\r\n\t\t\t\t\t\t placeholder=\"\u5728\u6b64\u8f93\u5165\u9a8c\u8bc1\u7801\" autocomplete=\"off\">\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div id=\"login_twofactor_authcode_help_supportlink\">\r\n\t\t\t\t<a href=\"https:\/\/help.steampowered.com\/zh-cn\/faqs\/view\/06B0-26E6-2CF8-254C\">\r\n\t\t\t\t\t\u8054\u7cfb Steam \u5ba2\u670d\u6765\u83b7\u5f97\u4f7f\u7528\u5e10\u6237\u7684\u5e2e\u52a9\t\t\t\t<\/a>\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t\t<div class=\"modal_buttons\" id=\"login_twofactorauth_buttonsets\">\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_entercode\" style=\"display: none;\">\r\n\t\t\t\t<div type=\"submit\" class=\"auth_button leftbtn\" data-modalstate=\"submit\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u63d0\u4ea4<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u7684\u9a8c\u8bc1\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u8bf7\u6c42\u534f\u52a9<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u5df2\u65e0\u6cd5\u8bbf\u95ee\u6211\u7684\u624b\u673a\u9a8c\u8bc1\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div style=\"clear: left;\"><\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_incorrectcode\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"submit\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u6211\u60f3\u518d\u8bd5\u4e00\u6b21<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u5df2\u7ecf\u5728\u4e0a\u9762\u91cd\u65b0\u8f93\u5165\u4e86\u6211\u7684\u9a8c\u8bc1\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u8bf7\u6c42\u534f\u52a9<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u60f3\u6211\u9700\u8981 Steam \u5ba2\u670d\u7684\u63f4\u52a9\u2026<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div style=\"clear: left;\"><\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"selfhelp_sms_remove_start\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\" style=\"font-size: 16px;\">\u79fb\u9664\u9a8c\u8bc1\u5668<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u8fd4\u56de\u4ee5\u901a\u8fc7\u7535\u5b50\u90ae\u4ef6\u63a5\u6536\u4ee3\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp_sms_reset_start\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u4f7f\u7528\u8be5\u8bbe\u5907<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u5e76\u5728\u8be5\u5e94\u7528\u4e0a\u83b7\u53d6\u9a8c\u8bc1\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_sms_remove\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"selfhelp_sms_remove_sendcode\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u786e\u5b9a\uff01<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u7ed9\u6211\u53d1\u77ed\u4fe1<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp_nosms\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u6211\u505a\u4e0d\u5230<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u56e0\u4e3a\u6211\u5df2\u65e0\u6cd5\u4f7f\u7528\u8be5\u624b\u673a\u53f7\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_sms_remove_entercode\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"selfhelp_sms_remove_checkcode\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u63d0\u4ea4<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u5df2\u5728\u4e0a\u65b9\u8f93\u5165\u4ee3\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp_nosms\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u8bf7\u6c42\u534f\u52a9<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u6ca1\u6536\u5230\u77ed\u4fe1<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_sms_remove_incorrectcode\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"selfhelp_sms_remove_checkcode\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u63d0\u4ea4<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u5df2\u7ecf\u91cd\u65b0\u8f93\u5165\u4e86\u4ee3\u7801\u3002\u518d\u8bd5\u4e00\u6b21\u3002<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp_nosms\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u8bf7\u6c42\u534f\u52a9<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u6ca1\u6536\u5230\u77ed\u4fe1<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_twofactor_removed\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"selfhelp_sms_remove_complete\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u767b\u5f55<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u79fb\u9664\u624b\u673a\u9a8c\u8bc1\u5668<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_twofactor_replaced\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"selfhelp_sms_remove_complete\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u767b\u5f55<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u5230 Steam \u624b\u673a\u5e94\u7528<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_nosms\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"selfhelp_rcode\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u662f<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u6709\u4ee5\u5b57\u6bcd\u201cR\u201d\u5f00\u5934\u7684\u6062\u590d\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp_couldnthelp\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u5426<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u6ca1\u6709\u4f60\u4eec\u8bf4\u7684\u4ee3\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_rcode\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"selfhelp_rcode_checkcode\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u63d0\u4ea4<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u7684\u6062\u590d\u7801<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp_couldnthelp\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u8bf7\u6c42\u534f\u52a9<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u89c9\u5f97\u81ea\u5df1\u9700\u8981 Steam \u5ba2\u670d\u534f\u52a9\u2026<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_rcode_incorrectcode\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button leftbtn\" data-modalstate=\"selfhelp_rcode_checkcode\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u63d0\u4ea4<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u5df2\u7ecf\u91cd\u65b0\u8f93\u5165\u4e86\u4ee3\u7801\u3002\u518d\u8bd5\u4e00\u6b21\u3002<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp_couldnthelp\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u8bf7\u6c42\u534f\u52a9<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u89c9\u5f97\u81ea\u5df1\u9700\u8981 Steam \u5ba2\u670d\u534f\u52a9\u2026<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_rcode_incorrectcode_exhausted\" style=\"display: none;\">\r\n\t\t\t\t<div class=\"auth_button\" data-modalstate=\"selfhelp_couldnthelp\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u8bf7\u6c42\u534f\u52a9<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u6211\u89c9\u5f97\u81ea\u5df1\u9700\u8981 Steam \u5ba2\u670d\u534f\u52a9\u2026<\/div>\r\n\t\t\t\t<\/div>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_selfhelp_couldnthelp\" style=\"display: none;\">\r\n\t\t\t\t<a class=\"auth_button leftbtn\" href=\"https:\/\/help.steampowered.com\/\">\r\n\t\t\t\t\t<div class=\"auth_button_h3\">\u8054\u7cfb\u6211\u4eec<\/div>\r\n\t\t\t\t\t<div class=\"auth_button_h5\">\u4ee5\u83b7\u5f97\u5e10\u6237\u8bbf\u95ee\u7684\u534f\u52a9<\/div>\r\n\t\t\t\t<\/a>\r\n\t\t\t<\/div>\r\n\t\t\t<div class=\"auth_buttonset\" id=\"login_twofactorauth_buttonset_waiting\" style=\"display: none;\">\r\n\t\t\t<\/div>\r\n\t\t<\/div>\r\n\t\t<div style=\"display: none;\">\r\n\t\t\t<input type=\"submit\">\r\n\t\t<\/div>\r\n\t\t<\/form>\r\n\t<\/div>\r\n<\/div>\r\n" );
$J('body').append( CLoginPromptManager.sm_$Modals );
}
else
{
CLoginPromptManager.sm_$Modals = $modals;
}
};
CLoginPromptManager.prototype.GetModalContent = function( strModalType )
{
var $ModalContent = CLoginPromptManager.sm_$Modals.find( '.login_modal.' + strModalType );
if ( this.m_bIsMobileSteamClient )
{
var manager = this;
$ModalContent.find('a[data-externallink]' ).each( function() {
if ( !manager.m_bMobileClientSupportsPostMessage )
$J(this).attr( 'href', 'steammobile://openexternalurl?url=' + $J(this).attr('href') );
else
$J(this).on('click', function( e ) {
e.preventDefault();
window.postMessage( JSON.stringify( {action: "openexternalurl", url: $J(this).attr('href') } ), window.location );
});
});
}
return $ModalContent;
};
Steam = {
sm_bInitialized: false,
sm_bUserInClient: false,
sm_bUserInGameOverlay: false,
sm_bUserInTenfootBrowser: false,
sm_bUserInMobileChat: false,
sm_bUserInMobileApp: false,
BIsUserInSteamClient: function()
{
if ( !Steam.sm_bInitialized )
Steam.Init();
return Steam.sm_bUserInClient;
},
BIsUserInGameOverlay: function()
{
if ( !Steam.sm_bInitialized )
Steam.Init();
return Steam.sm_bUserInGameOverlay
},
BIsUserInSteamTenfootBrowser: function()
{
if ( !Steam.sm_bInitialized )
Steam.Init();
return Steam.sm_bUserInTenfootBrowser;
},
BIsUserInClientOrOverlay: function()
{
if ( !Steam.sm_bInitialized )
Steam.Init();
return Steam.sm_bUserInClient || Steam.sm_bUserInGameOverlay;
},
BIsUserInSteamMobileChat: function()
{
if ( !Steam.sm_bInitialized )
Steam.Init();
return Steam.sm_bUserInMobileChat;
},
BIsUserInSteamMobileApp: function()
{
if ( !Steam.sm_bInitialized )
Steam.Init();
return Steam.sm_bUserInMobileApp;
},
GetClientPackageVersion: function()
{
if ( !Steam.BIsUserInClientOrOverlay() )
return 0;
if ( typeof navigator != 'undefined' && navigator.userAgent )
{
var matches = navigator.userAgent.match( /Valve Steam [^\/]*\/([0-9]*)/ );
if ( matches && matches.length == 2 )
return matches[1];
}
return 0;
},
Init: function()
{
var fnCheckAgent = function( strUAMatch, strURLParam )
{
if ( window.location.href.match( '[?&]' + strURLParam + '=' ) )
return true;
if ( typeof navigator != 'undefined' && navigator.userAgent && navigator.userAgent.indexOf( strUAMatch ) != -1 )
return true;
return false;
};
Steam.sm_bUserInTenfootBrowser = fnCheckAgent( 'Valve Steam Tenfoot', 'force_tenfoot_client_view' );
Steam.sm_bUserInGameOverlay = fnCheckAgent( 'Valve Steam GameOverlay', 'force_overlay_view' );
Steam.sm_bUserInClient = Steam.sm_bUserInTenfootBrowser || fnCheckAgent( 'Valve Steam Client', 'force_client_view' );
Steam.sm_bUserInMobileChat = fnCheckAgent( 'Valve Steam Mobile Chat', 'force_mobile_chat_view' );
Steam.sm_bUserInMobileApp = fnCheckAgent( 'Valve Steam App', 'force_mobile_app_view' );
Steam.sm_bInitialized = true;
},
LinkInNewWindow: function( $A )
{
if ( Steam.BIsUserInSteamClient() && !Steam.BIsUserInSteamTenfootBrowser() )
$A.attr( 'href', 'steam://openurl_external/' + $A.attr('href') );
else
$A.attr( 'target', '_blank' );
}
};
function OpenFriendChat( steamid, accountid )
{
if ( Steam.BIsUserInClientOrOverlay() )
{
window.location = 'steam://friends/message/' + steamid;
}
else if ( Steam.BIsUserInSteamMobileChat() )
{
window.location = 'steamchatmobile://friend/' + steamid;
}
else if ( Steam.BIsUserInSteamMobileApp() )
{
window.location = 'https://steamcommunity.com/chat/friend/' + steamid;
}
else if ( typeof ClientConnectionAPI !== 'undefined' )
{
ClientConnectionAPI.OpenFriendChatDialog( steamid ).then( function( result ) {
if ( !result.success )
{
PromptContinueToWebChat( result, function() {
OpenFriendChatInWebChat( steamid, accountid );
}, 'steam://friends/message/' + steamid );
}
});
}
else
{
OpenFriendChatInWebChat( steamid, accountid );
}
}
function OpenFriendChatInWebChat( steamid, accountid )
{
LaunchWebChat( { friend: accountid }, {command: 'ShowFriendChatDialog', steamid: steamid} );
}
function OpenGroupChat( steamid )
{
if ( Steam.BIsUserInSteamMobileChat() )
{
window.location = 'steamchatmobile://group/' + steamid;
}
else if ( Steam.BIsUserInSteamMobileApp() )
{
window.location = 'https://steamcommunity.com/chat/group/' + steamid;
}
else if ( !Steam.BIsUserInClientOrOverlay() && typeof ClientConnectionAPI !== 'undefined' )
{
ClientConnectionAPI.OpenFriendChatDialog( steamid ).then( function( result ) {
if ( !result.success )
{
PromptContinueToWebChat( result, function() {
LaunchWebChat( null, {command: 'ShowFriendChatDialog', steamid: steamid} );
}, 'steam://friends/joinchat/' + steamid );
}
});
}
else
{
window.location = 'steam://friends/joinchat/' + steamid;
}
}
function PromptContinueToWebChat( result, fnLaunchWebchat, steamURL )
{
ShowConfirmDialog( '已有 Steam ?', '我们无法在您的设备上检测到 Steam 运行。您是否希望使用网页聊天?',
'使用网页聊天' , null, '获得 Steam' ).done( function( choice ) {
if ( choice == 'OK' )
fnLaunchWebchat();
else
window.location = 'https://store.steampowered.com/about/'
});
}
// proto functions used to accept an id or an element.
// This can be used to migrate them to returning jquery instead of proto-wrapped element
function $JFromIDOrElement( elem )
{
if ( elem instanceof jQuery )
return elem;
else if ( typeof elem == 'string' )
return $J('#' + elem.replace( /\./, '\\.' ) );
else
return $J( elem );
}
/** Show a popup dialog like confirm(), with two buttons. Clicking ok resolves with done(), cancel or closing the window resolves with fail()
*
* @param strTitle Title bar text
* @param strDescription Message text
* @param strOKButton Text to show on OK button (default "OK")
* @param strCancelButton Text to show on Cancel button (default "Cancel")
* @param strSecondaryActionButton Add a secondary ok button (three buttons total). Resolves with done() like OK but passes 'SECONDARY' as argument to handler
* @returns CModal
*/
function ShowConfirmDialog( strTitle, strDescription, strOKButton, strCancelButton, strSecondaryActionButton, rgModalParams )
{
if ( !strOKButton )
strOKButton = '确定';
if ( !strCancelButton )
strCancelButton = '取消';
var deferred = new jQuery.Deferred();
var fnOK = function() { deferred.resolve( 'OK' ); };
var fnSecondary = function() { deferred.resolve( 'SECONDARY' ); };
var fnCancel = function( bWasCancelButton ) { deferred.reject( bWasCancelButton ); };
var rgButtons = [];
var $OKButton = _BuildDialogButton( strOKButton, true );
$OKButton.click( fnOK );
rgButtons.push( $OKButton );
if ( strSecondaryActionButton )
{
var $SecondaryActionButton = _BuildDialogButton( strSecondaryActionButton, false, {strClassName: ' btn_darkblue_white_innerfade btn_medium' } );
$SecondaryActionButton.click( fnSecondary );
rgButtons.push( $SecondaryActionButton );
}
var $CancelButton = _BuildDialogButton( strCancelButton );
$CancelButton.click( function() { fnCancel( true ); } );
rgButtons.push( $CancelButton );
var Modal = _BuildDialog( strTitle, strDescription, rgButtons, fnCancel, rgModalParams );
Modal.Show();
_BindOnEnterKeyPressForDialog( Modal, deferred, fnOK );
deferred.always( function() { Modal.Dismiss(); } );
// attach the deferred's events to the modal
deferred.promise( Modal );
return Modal;
}
/** Show a dialog with a single button, like alert(). Button click or closing the modal resolve deferred with done().
*
* @param strTitle Title bar text
* @param strDescription Message text
* @param strOKButton Text on the OK button ("OK" by default)
* @returns CModal
*/
function ShowAlertDialog( strTitle, strDescription, strOKButton, rgModalParams )
{
if ( !strOKButton )
strOKButton = '确定';
var deferred = new jQuery.Deferred();
var fnOK = function( bWasCancelButton ) { deferred.resolve( bWasCancelButton ); };
var $OKButton = _BuildDialogButton( strOKButton );
$OKButton.click( function() { fnOK( true ); } );
var Modal = _BuildDialog( strTitle, strDescription, [ $OKButton ], fnOK, rgModalParams );
deferred.always( function() { Modal.Dismiss(); } );
Modal.Show();
_BindOnEnterKeyPressForDialog( Modal, deferred, fnOK );
// attach the deferred's events to the modal
deferred.promise( Modal );
return Modal;
}
/** Show a popup dialog. Has no buttons. Closing the dialog resolves deferred with done().
*
* @param strTitle Title bar text
* @param strDescription Message text
* @param rgModalParams See CModal
* @returns CModal
*/
function ShowDialog( strTitle, strDescription, rgModalParams )
{
var deferred = new jQuery.Deferred();
var fnOK = function() { deferred.resolve(); };
var Modal = _BuildDialog( strTitle, strDescription, [], fnOK, rgModalParams );
deferred.always( function() { Modal.Dismiss(); } );
Modal.Show();
// attach the deferred's events to the modal
deferred.promise( Modal );
return Modal;
}
/**
* @returns CModal
*/
function ShowPromptDialog( strTitle, strDescription, strOKButton, strCancelButton, rgModalParams, defaultValue )
{
if ( !strOKButton )
strOKButton = '确定';
if ( !strCancelButton )
strCancelButton = '取消';
var $Body = $J('<form/>');
var $Input = $J('<input/>', {type: 'text', 'class': '' } ).val( defaultValue );
if ( rgModalParams && rgModalParams.inputMaxSize )
{
$Input.attr( 'maxlength', rgModalParams.inputMaxSize );
}
$Body.append( $J('<div/>', {'class': 'newmodal_prompt_description' } ).append( strDescription ) );
$Body.append( $J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth' } ).append( $Input ) );
var deferred = new jQuery.Deferred();
var fnOK = function() { deferred.resolve( $Input.val() ); };
var fnCancel = function() { deferred.reject(); };
$Body.submit( function( event ) { event.preventDefault(); fnOK(); } );
var elButtonLabel = $J( '<span/>' ).text( strOKButton );
var $OKButton = $J('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( elButtonLabel );
$OKButton.click( fnOK );
var $CancelButton = _BuildDialogButton( strCancelButton );
$CancelButton.click( fnCancel );
var Modal = _BuildDialog( strTitle, $Body, [ $OKButton, $CancelButton ], fnCancel );
if( !rgModalParams || !rgModalParams.bNoPromiseDismiss )
deferred.always( function() { Modal.Dismiss(); } );
Modal.Show();
$Input.focus();
// attach the deferred's events to the modal
deferred.promise( Modal );
return Modal;
}
/**
* @returns CModal
*/
function ShowPromptWithTextAreaDialog( strTitle, strInitialText, strOKButton, strCancelButton, textAreaMaxLength, strDescription )
{
if ( !strOKButton )
strOKButton = '确定';
if ( !strCancelButton )
strCancelButton = '取消';
var $Body = $J('<form/>');
var $TextArea = $J('<textarea/>', { 'class': 'newmodal_prompt_textarea' } );
$TextArea.text( strInitialText );
if ( textAreaMaxLength )
{
$TextArea.attr( 'maxlength', textAreaMaxLength );
$TextArea.bind( "keyup change",
function()
{
var str = $J(this).val();
var mx = parseInt($J(this).attr('maxlength'));
if (str.length > mx)
{
$J(this).val(str.substr(0, mx));
return false;
}
}
);
}
if ( strDescription )
{
$Body.append( $J('<div/>', {'class': 'newmodal_prompt_with_textarea newmodal_prompt_description' } ).text( strDescription ) );
}
$Body.append( $J('<div/>', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth' } ).append( $TextArea ) );
var deferred = new jQuery.Deferred();
var fnOK = function() { deferred.resolve( $TextArea.val() ); };
var fnCancel = function() { deferred.reject(); };
$Body.submit( function( event ) { event.preventDefault(); fnOK(); } );
var elButtonLabel = $J( '<span/>' ).text( strOKButton );
var $OKButton = $J('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( elButtonLabel );
$OKButton.click( fnOK );
var $CancelButton = _BuildDialogButton( strCancelButton );
$CancelButton.click( fnCancel );
var Modal = _BuildDialog( strTitle, $Body, [ $OKButton, $CancelButton ], fnCancel );
deferred.always( function() { Modal.Dismiss(); } );
Modal.Show();
$TextArea.focus();
// attach the deferred's events to the modal
deferred.promise( Modal );
return Modal;
}
/**
* @returns CModal
*/
function ShowEditablePrompt( strTitle, obj, onOk, onCancel)
{
strEditButton = '编辑';
strOKButton = '确定';
strCancelButton = '取消';
var $Body = $J('<form/>');
var $TextArea = $J('<textarea/>', {'class': 'newmodal_prompt_textarea', 'id': 'json_window'});
$TextArea[0].readOnly = true;
$TextArea.text( JSON.stringify(obj, null, 2) );
$Body.append( $J('<div/>', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth ' } ).append( $TextArea ) );
$Body.submit( function( event ) { event.preventDefault(); fnOK(); } );
var elButtonLabel = $J( '<span/>' ).text( strOKButton );
var deferredAction = new jQuery.Deferred();
var editButtonLabel = $J( '<span/>' ).text( strEditButton );
var $EditButton = $J('<button/>', {type: 'button', 'class': 'btn_darkred_white_innerfade btn_medium' } ).append( editButtonLabel );
$EditButton.click( function() {
$EditButton[0].disable();
$OKButton[0].enable();
$TextArea[0].readOnly = false;
$TextArea.focus();
})
var $OKButton = $J('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( elButtonLabel );
$OKButton.click( function() { deferredAction.resolve( $TextArea.val() ); } );
$OKButton[0].disabled = true;
fnCancel = function() { deferredAction.reject(); };
var $CancelButton = _BuildDialogButton( strCancelButton );
$CancelButton.click( fnCancel );
var Modal = _BuildDialog( strTitle, $Body, [ $EditButton, $OKButton, $CancelButton ], fnCancel );
deferredAction.always( function() { Modal.Dismiss(); } );
if (onOk)
deferredAction.done(onOk);
if (onCancel)
deferredAction.fail(onCancel);
Modal.Show();
$TextArea.focus();
// attach the deferred's events to the modal
deferredAction.promise( Modal );
return Modal;
}
/**
* @returns CModal
*/
function ShowBlockingWaitDialog( strTitle, strDescription )
{
var deferred = new jQuery.Deferred();
var fnOK = function() { deferred.resolve(); };
var container = $J('<div/>', {'class': 'waiting_dialog_container'} );
var throbber = $J('<div/>', {'class': 'waiting_dialog_throbber'} );
container.append( throbber );
container.append( strDescription );
var Modal = _BuildDialog( strTitle, container, [], fnOK, { bExplicitDismissalOnly: true } );
deferred.always( function() { Modal.Dismiss(); } );
Modal.Show();
// attach the deferred's events to the modal
deferred.promise( Modal );
return Modal;
}
function _BindOnEnterKeyPressForDialog( Modal, deferred, fnOnEnter )
{
var fnOnKeyUp = function( event ) {
if ( Modal.BIsActiveModal() && !event.isDefaultPrevented() && event.which == 13 && ( !event.target || event.target.nodeName != 'TEXTAREA' ) )
fnOnEnter();
};
$J(document).on( 'keyup.SharedConfirmDialog', fnOnKeyUp );
deferred.always( function() { $J(document).off( 'keyup.SharedConfirmDialog' ); } );
}
/**
* @returns CModal
* @private
*/
function _BuildDialog( strTitle, strDescription, rgButtons, fnOnCancel, rgModalParams )
{
var $Dialog = $J('<div/>', {'class': 'newmodal'} );
var $CloseButton = $J('<div/>', {'class': 'newmodal_close', 'data-panel': '{"focusable":true,"clickOnActivate":true}' } );
var $Header = ( $J('<div/>', {'class': 'newmodal_header' }) );
var $TopBar = ( $J('<div/>', {'class': 'modal_top_bar' }) );
if ( strTitle )
$Header.append( $CloseButton ).append( $J('<div/>', {'class': 'title_text' } ).text( strTitle ) );
if ( rgModalParams && rgModalParams.strSubTitle ) {
var $SubTitle = (rgModalParams.strSubTitle);
$Header.append( $J('<div/>', {'class': 'subtitle_text' } ).text( $SubTitle ) )
}
$Header = $J('<div/>', {'class': 'newmodal_header_border'}).append( $Header );
$Dialog.append( $TopBar ).append( $Header );
var $Content = $J('<div/>', {'class': 'newmodal_content' } );
$Content.append( $J('<div/>').append( strDescription ) );
if ( rgButtons.length > 0 )
{
var $Buttons = $J('<div/>', {'class': 'newmodal_buttons', 'data-panel': '{"flow-children":"row"}' } );
$Content.append( $Buttons );
for( var i = 0; i < rgButtons.length; i++ )
{
var $Button = rgButtons[i];
if ( i == 0 )
$Button.attr( 'data-panel', '{"autoFocus":true,"focusable":true,"clickOnActivate":true}' );
else
$Button.attr( 'data-panel', '{"focusable":true,"clickOnActivate":true}' );
$Buttons.append( $Button );
}
}
$Dialog.append( $J('<div/>', {'class': 'newmodal_content_border' } ).append( $Content ) );
if ( rgModalParams && rgModalParams.bExplicitDismissalOnly )
$CloseButton.hide();
var Modal = new CModal( $Dialog, rgModalParams );
if ( fnOnCancel )
{
Modal.OnDismiss( fnOnCancel );
$CloseButton.click( function() { Modal.Dismiss(); } );
}
// on responsive pages, the 'newmodal' element covers the whole viewable area (so that we can control scrolling
// if the modal is very tall). If the modal doesn't cover the whole area, we dismiss on clicks to this background
// area
if ( Modal.m_fnBackgroundClick )
{
$Dialog.click( function(e) { if ( e.target == this ) Modal.m_fnBackgroundClick(); } );
}
Modal.SetRemoveContentOnDismissal( true );
return Modal;
}
function _BuildDialogButton( strText, bActive, rgOptions )
{
if ( !rgOptions )
rgOptions = {};
var strClassName = bActive ? 'btn_green_steamui btn_medium' : 'btn_grey_steamui btn_medium';
if ( rgOptions.strClassName )
strClassName = rgOptions.strClassName;
var elButtonLabel = $J( '<span/>' ).html( strText );
var elButton = $J('<div/>', {'class': strClassName } ).append( elButtonLabel );
return elButton;
}
/** Implemented for Gamepad: show content in a fullscreen, borderless, modal which gets navigation support.
* Used on the app details page to show screenshots in full screen.
* Closing the dialog resolves deferred with done().
*
* @returns CModal
*/
function GPShowFullScreenModal( content )
{
var deferred = new jQuery.Deferred();
var fnOK = function() { deferred.resolve(); };
var Modal = _BuilGPFullScreenModal( content, fnOK );
deferred.always( function() { Modal.Dismiss(); } );
Modal.Show();
// attach the deferred's events to the modal
deferred.promise( Modal );
return Modal;
}
function _BuilGPFullScreenModal( content, fnOnCancel )
{
var $Dialog = $J('<div/>').append( $J('<div/>', {'style': 'display:flex'} ).append( content ) );
var Modal = new CModal( $Dialog );
Modal.SetRemoveContentOnDismissal( true );
if ( fnOnCancel )
{
Modal.OnDismiss( fnOnCancel );
}
return Modal;
}
/* modal params:
bExplicitDismissalOnly - by default, clicking outside of the modal dismisses it. Set this to true to override that behavior
bIgnoreResizeEvents - don't resize the modal when the window resizes
*/
function CModal( $Content, rgParams )
{
rgParams = rgParams || {};
this.m_$Content = $Content;
this.m_bVisible = false;
this.m_bIgnoreResizeEvents = rgParams.bIgnoreResizeEvents;
this.m_fnSizing = null;
this.m_fnBackgroundClick = null;
this.m_fnOnResize = null;
this.m_bDismissOnBackgroundClick = !rgParams.bExplicitDismissalOnly;
this.m_nMaxWidth = rgParams.nMaxWidth || 0;
this.m_nMaxHeight = rgParams.nMaxHeight || 0;
this.m_fnOnDismiss = null;
this.m_fnGPOnCloseModal = null;
this.m_bRemoveContentOnDismissal = false;
this.m_nInitialOffsetTop = $J(window).scrollTop();
this.m_nInitialOffsetLeft = $J(window).scrollLeft();
this.m_$Content.css( 'position', 'fixed' );
this.m_$Content.css( 'z-index', 1000 );
/* default gamepad behavior is B button closes the dialog */
if ( !this.m_$Content.attr('panel' ) )
this.m_$Content.attr( 'data-panel', '{"onCancelButton":"CModal.DismissActiveModal()"}' );
this.m_$StandardContent = null;
this.m_$SizedContent = null;
this.OnContentChanged(); //this will look for StandardContent and SizedContent in the modal body
var _modal = this;
this.m_fnBackgroundClick = function() { if ( _modal.BIsActiveModal() && _modal.m_bDismissOnBackgroundClick ) { _modal.Dismiss(); } };
this.m_fnOnEscapeKeyPress = function( event ) { if ( _modal.BIsActiveModal() && event.which == 27 ) _modal.m_fnBackgroundClick(); };
this.m_fnSizing = function() { _modal.AdjustSizing(); };
/* make sure the content is parented correctly */
$J(document.body).append( this.m_$Content );
}
CModal.prototype.OnDismiss = function( fn )
{
this.m_fnOnDismiss = fn;
};
CModal.prototype.OnResize = function( fn )
{
this.m_fnOnResize = fn;
};
/**
* @returns jQuery
*/
CModal.prototype.GetContent = function ()
{
return this.m_$Content;
};
CModal.prototype.GetBoundOnResizeEvent = function()
{
// in case someone outside needs to tell the modal to resize on certain events (eg images or iframes loading in the modal)
return this.m_fnSizing;
};
CModal.prototype.OnContentChanged = function()
{
// make sure we're holding the right elements
this.m_$StandardContent = this.m_$Content.find( '.newmodal_content' );
if ( !this.m_$StandardContent.length )
this.m_$StandardContent = this.m_$Content;
this.m_$SizedContent = this.m_$Content.find( '.newmodal_sized_content' );
};
CModal.prototype.SetRemoveContentOnDismissal = function ( bRemoveContent )
{
this.m_bRemoveContentOnDismissal = bRemoveContent;
};
CModal.prototype.SetDismissOnBackgroundClick = function ( bDismissOnBackgroundClick )
{
this.m_bDismissOnBackgroundClick = bDismissOnBackgroundClick;
};
CModal.prototype.SetMaxWidth = function ( nMaxWidth )
{
this.m_nMaxWidth = nMaxWidth;
if ( this.m_bVisible )
this.AdjustSizing();
};
CModal.prototype.SetMaxHeight = function ( nMaxHeight )
{
this.m_nMaxHeight = nMaxHeight;
if ( this.m_bVisible )
this.AdjustSizing();
};
CModal.prototype.AdjustSizing = function( duration )
{
if ( !this.m_$Content )
return;
var bResponsiveScreen = window.UseTouchFriendlyMode && UseTouchFriendlyMode();
var bUseTabletScreenMode = window.UseTabletScreenMode && window.UseTabletScreenMode();
var nViewportWidth = document.compatMode === 'BackCompat' ? document.body.clientWidth : $J(window).width();
var nViewportHeight = document.compatMode === 'BackCompat' ? document.body.clientHeight : $J(window).height();
var nMaxWidth = Math.max( nViewportWidth - ( bResponsiveScreen? 24 : 80 ), bResponsiveScreen ? 200 : 500 );
var nMaxHeight = Math.floor( nViewportHeight - 120 );
if ( this.m_nMaxWidth && nMaxWidth > this.m_nMaxWidth )
{
nMaxWidth = this.m_nMaxWidth;
}
if ( this.m_nMaxHeight && nMaxHeight > this.m_nMaxHeight )
{
nMaxHeight = this.m_nMaxHeight;
}
// if the modal has a 'newmodal_sized_content' div, it wants to be the max height, so set it now
// before we compute height ( "- 18" is a fudge for a possible horizontal scrollbar )
this.m_$SizedContent.css( 'min-height', ( nMaxHeight - 18 ) + 'px' );
if ( this.m_fnOnResize )
{
this.m_fnOnResize( nMaxWidth - 40, nMaxHeight );
}
if ( !duration )
{
// set sizes right away so we can calculate a good left and top
this.m_$Content.css( 'max-width', nMaxWidth + 'px' );
if ( !bResponsiveScreen )
{
this.m_$StandardContent.css( 'max-height', nMaxHeight + 'px' );
}
else
{
this.m_$StandardContent.css( 'max-height', '' );
}
}
var nContentWidth = this.m_$Content.width();
var nContentHeight = this.m_$Content.height();
var nLeft = Math.max( Math.floor( ( nViewportWidth - nContentWidth ) / 2 ), 12 );
var nTop = Math.max( Math.floor( ( nViewportHeight - nContentHeight ) / 2 ), 12 );
// only use absolute position on mobile screens
if ( bResponsiveScreen && !bUseTabletScreenMode )
{
nLeft += this.m_nInitialOffsetLeft;
nTop += this.m_nInitialOffsetTop;
this.m_$Content.css( 'position', 'absolute' );
}
else
{
this.m_$Content.css( 'position', 'fixed' );
}
if ( duration )
{
this.m_$Content.animate( { 'max-width': nMaxWidth, left: nLeft, top: nTop }, duration );
this.m_$StandardContent.animate( {'max-height': nMaxHeight }, duration );
}
else
{
this.m_$Content.css( 'left', nLeft );
this.m_$Content.css( 'top', nTop );
}
};
CModal.prototype.Show = function()
{
if ( this.m_bVisible )
return;
CModal.ShowModalBackground();
if ( !this.m_bIgnoreResizeEvents )
{
$J(window).on( 'resize', null, this.m_fnSizing );
}
CModal.s_$Background.on( 'click.CModal', this.m_fnBackgroundClick );
$J(document).on( 'keyup.CModal', this.m_fnOnEscapeKeyPress );
this.AdjustSizing();
// if we're in gamepad, notify gamepad navigation of the modal
if ( typeof GPOnShowingModalWindow === "function" )
this.m_fnGPOnCloseModal = GPOnShowingModalWindow( this.m_$Content.get( 0 ) );
this.m_$Content.show();
// resize as any child image elements load in.
this.m_$Content.find('img').load( this.m_fnSizing );
this.m_bVisible = true;
CModal.PushActiveModal( this );
var _this = this;
if ( typeof GPNavFocusChild !== 'undefined' )
window.setTimeout( function() { GPNavFocusChild( _this.m_$Content ) }, 1 ); };
CModal.prototype.Dismiss = function()
{
if ( !this.m_bVisible )
return;
this.m_bVisible = false;
// tell gamepad navigation we're closing this modal
if ( this.m_fnGPOnCloseModal )
{
this.m_fnGPOnCloseModal();
this.m_fnGPOnCloseModal = null;
}
this.m_$Content.hide();
if ( !this.m_bIgnoreResizeEvents )
{
$J(window).off( 'resize', null, this.m_fnSizing );
}
if ( this.m_fnOnDismiss )
this.m_fnOnDismiss();
if ( this.m_bRemoveContentOnDismissal )
{
this.m_$Content.remove();
this.m_$Content = null;
}
CModal.PopActiveModal( this );
if ( !CModal.s_rgModalStack.length )
{
CModal.s_$Background.off( 'click.CModal', this.m_fnBackgroundClick );
$J(document).off( 'keyup.CModal', this.m_fnOnEscapeKeyPress );
CModal.HideModalBackground();
}
};
CModal.prototype.BIsActiveModal = function()
{
return CModal.s_rgModalStack.length && CModal.s_rgModalStack[ CModal.s_rgModalStack.length - 1 ] == this;
};
/* static */
CModal.ShowModalBackground = function()
{
if ( !CModal.s_$Background )
{
CModal.s_$Background = $J('<div/>', {'class': 'newmodal_background'});
CModal.s_$Background.css( 'opacity', 0 );
$J(document.body).append( CModal.s_$Background );
}
CModal.s_$Background.stop(); // stop running animations
CModal.s_$Background.fadeTo( 200, 0.8 );
};
CModal.HideModalBackground = function()
{
if ( CModal.s_$Background )
{
CModal.s_$Background.stop(); // stop running animations
CModal.s_$Background.fadeOut( 200, 0 );
}
};
CModal.s_rgModalStack = [];
CModal.GetActiveModal = function()
{
if ( CModal.s_rgModalStack.length )
return CModal.s_rgModalStack[CModal.s_rgModalStack.length-1];
else
return null;
};
CModal.DismissActiveModal = function()
{
if ( CModal.s_rgModalStack.length )
CModal.s_rgModalStack[CModal.s_rgModalStack.length-1].Dismiss();
};
CModal.PushActiveModal = function( Modal )
{
for ( var i = 0; i < CModal.s_rgModalStack.length; i++ )
{
// push below background
CModal.s_rgModalStack[i].m_$Content.css( 'z-index', 899 );
}
CModal.s_rgModalStack.push( Modal );
};
CModal.PopActiveModal = function( Modal )
{
for ( var i = 0; i < CModal.s_rgModalStack.length; i++ )
{
if ( CModal.s_rgModalStack[i] == Modal )
{
CModal.s_rgModalStack.splice( i, 1 );
break;
}
}
if ( CModal.s_rgModalStack.length )
CModal.s_rgModalStack[ CModal.s_rgModalStack.length - 1 ].m_$Content.css( 'z-index', 1000 );
};
// this will set the right headers for a cross-domain request to community
function GetDefaultCommunityAJAXParams( path, method )
{
var rgParams = { url: 'https://steamcommunity.com/' + path };
if ( method )
rgParams.type = method;
// if this js file was hosted off the store, add CORS request headers
if ( window.location.href.indexOf( 'https://steamcommunity.com/' ) != 0 )
{
rgParams.crossDomain = true;
rgParams.xhrFields = { withCredentials: true };
}
return rgParams;
}
// spped of the miniprofile fading in and out
var MINIPROFILE_ANIM_SPEED = 150;
// how long the mouse must remain over an element before we'll make an AJAX call
var MINIPROFILE_DELAY_BEFORE_AJAX = 100;
// the delay before we'll show the hover, must be longer than DELAY_BEFORE_AJAX
var MINIPROFILE_DELAY_BEFORE_SHOW = 250;
function CDelayedAJAXData( strURL, msDelayBeforeAJAX )
{
this.m_$Data = null;
this.m_bAJAXFailed = false;
this.m_timerDelayedAJAX = null;
this.m_bAJAXRequestMade = false;
this.m_msDelayBeforeAJAX = msDelayBeforeAJAX;
this.m_strURL = strURL;
this.m_fnOnAJAXComplete = null;
}
CDelayedAJAXData.prototype.GetAJAXParams = function()
{
return GetDefaultCommunityAJAXParams( this.m_strURL, 'GET' );
};
CDelayedAJAXData.prototype.QueueAjaxRequestIfNecessary = function()
{
if ( !this.m_$Data && !this.m_bAJAXRequestMade )
{
var _this = this;
this.m_timerDelayedAJAX = window.setTimeout( function() {
_this.m_timerDelayedAJAX = null;
_this.m_bAJAXRequestMade = true;
var rgAJAXParams = _this.GetAJAXParams();
$J.ajax( rgAJAXParams )
.done( function(data) {
_this.m_$Data = $J(data);
if ( _this.m_fnOnAJAXComplete )
_this.m_fnOnAJAXComplete();
}).fail( function() {
_this.m_bAJAXFailed = true;
});
}, this.m_msDelayBeforeAJAX );
}
};
CDelayedAJAXData.prototype.CancelAJAX = function()
{
if ( this.m_timerDelayedAJAX )
window.clearTimeout( this.m_timerDelayedAJAX );
this.m_fnOnAJAXComplete = null;
};
CDelayedAJAXData.prototype.RunWhenAJAXReady = function( fnOnReady )
{
if ( this.m_$Data )
fnOnReady();
else if ( !this.m_bAJAXFailed )
{
this.m_fnOnAJAXComplete = fnOnReady;
this.QueueAjaxRequestIfNecessary();
}
// if ajax failed we will not call fnOnReady
};
CDelayedAJAXData.prototype.Show = function( $HoverContent )
{
$HoverContent.children().detach();
$HoverContent.append( this.m_$Data );
};
// time the cookie preferences popup waits before appearing
var COOKIE_PREFERENCES_POPUP_DELAY = 4000;
function InitCookiePreferencesPopup()
{
var $popupDialog = $J( '#cookiePrefPopup' );
if ( $popupDialog.length == 0 )
{
$AllowURL = encodeURI( 'https://store.steampowered.com/' + 'account/ajaxsetcookiepreferences');
var $CPopupContent = $J( "<style>\r\n.replyButton {\r\n\twidth: 100%;\r\n text-align: center;\r\n margin-bottom: 4px;\r\n}\r\n\r\n.buttonGroup {\r\n\tflex: 18%;\r\n\tmargin-left: 20px;\r\n\tmargin-right: 0px;\r\n}\r\n.cookieMessage {\r\n\tflex: 85%; \r\n\tmargin: auto;\r\n}\r\n.popupTextTitle {\r\n\tpadding-bottom: 10px;\r\n font-size: 13px;\r\n line-height: 17px;\r\n color: #c6d4df;\r\n}\r\n.popupTextTitle a {\r\n text-decoration: underline;\r\n}\r\n.cookiepreferences_popup {\r\n\tdisplay: none;\r\n\tposition: fixed;\r\n\tbottom: 0;\r\n\twidth: 100%;\r\n\tz-index: 2000;\r\n\tpadding-top: 10px;\r\n}\r\n.cookiepreferences_popup_content {\r\n\tdisplay: flex;\r\n box-sizing: border-box;\r\n\twidth: 90%;\r\n\tmargin: auto;\r\n\tmargin-bottom: 20px;\r\n\tmargin-top: 20px;\r\n\tpadding: 16px 16px 10px 16px;\r\n max-width: 980px;\r\n\tbackground: linear-gradient(90.85deg, #333840 0.58%, #25282E 74.92%);\r\n\tbox-shadow: 0px 0px 10px #000000;\r\n border-left: 2px solid #00CCFF;\r\n}\r\n@media all and (max-width: 910px) {\r\n body.responsive_page .cookiepreferences_popup_content {\r\n flex-direction: column;\r\n }\r\n body.responsive_page .cookiepreferences_popup_content .cookieMessage {\r\n margin-bottom: 20px;\r\n }\r\n body.responsive_page .cookiepreferences_popup_content .buttonGroup {\r\n flex-direction: row;\r\n margin-left: 0px;\r\n }\r\n body.responsive_page .cookiepreferences_popup_content .buttonGroup .replyButton {\r\n width: unset;\r\n }\r\n}\r\n<\/style>\r\n\r\n<div id=\"cookiePrefPopup\" class=\"cookiepreferences_popup\">\r\n\t<div class=\"cookiepreferences_popup_content\">\r\n\t\t<div class=\"cookieMessage\">\r\n\t\t\t<div class=\"popupTextTitle\">\u60a8\u662f\u5426\u4ecb\u610f\u6211\u4eec\u4f7f\u7528\u53ef\u9009\u7684 Cookie\uff0c\u6765\u4e3a\u60a8\u63d0\u4f9b\u4e2a\u6027\u5316\u7684\u5185\u5bb9\u5e76\u5206\u6790\u7f51\u7ad9\u6d41\u91cf\uff0c\u5305\u62ec\u4f7f\u7528 Google Analytics\uff08\u5206\u6790\uff09\uff1f<\/div>\r\n\t\t\t<div class=\"popupTextTitle\">\u6211\u4eec\u4f7f\u7528\u7684 Cookie \u4e0d\u591a\uff0c\u60a8\u53ef\u4ee5\u968f\u65f6\u5728\u6211\u4eec\u7684 <a href=\"https:\/\/store.steampowered.com\/account\/cookiepreferences\">Cookie \u8bbe\u7f6e\u9875\u9762<\/a>\u4e0a\u8fdb\u884c\u67e5\u770b\u548c\u7ba1\u7406\u3002\u5982\u679c\u60a8\u70b9\u51fb\u201c\u5168\u90e8\u63a5\u53d7\u201d\uff0c\u5219\u8868\u793a\u60a8\u540c\u610f\u5728 Steam \u7f51\u7ad9\u4e0a\u4f7f\u7528 Cookie\u3002\u5728\u6211\u4eec\u7684<a href=\"https:\/\/store.steampowered.com\/privacy_agreement\">\u9690\u79c1\u653f\u7b56<\/a>\u4e2d\u4e86\u89e3\u66f4\u591a\u6709\u5173 Cookie \u7684\u4fe1\u606f\u3002<\/div>\r\n\t\t<\/div>\r\n\t\t<div class=\"buttonGroup\">\r\n\t\t\t<div id=\"acceptAllButton\" class=\"btn_blue_steamui btn_medium replyButton\"><span>\u5168\u90e8\u63a5\u53d7<\/span><\/div>\r\n\t\t\t<div id=\"rejectAllButton\" class=\"btn_grey_steamui btn_medium replyButton\"><span>\u5168\u90e8\u62d2\u7edd<\/span><\/div>\r\n\t\t<\/div>\r\n\t<\/div>\r\n<\/div>\r\n" );
var $onAcceptButton = $CPopupContent.find( '#acceptAllButton' );
var $onRejectButton = $CPopupContent.find( '#rejectAllButton' );
if ( $onAcceptButton.length == 0 || $onRejectButton.length == 0 )
{
// template appears broken
console.error("unable to display preferences popup");
return;
}
var fnPostPreference = function ( bAllowChoice )
{
$J.ajax( { type: "POST",
url: $AllowURL,
data: { bAllow: bAllowChoice, sessionid: g_sessionID },
crossDomain: true,
xhrFields: { withCredentials: true }
} ).done( function ( data )
{
if ( data && data.transfer_urls && data.transfer_params )
{
for ( var i = 0; i < data.transfer_urls.length; i++ )
{
$J.ajax( { type: "POST",
url: data.transfer_urls[i],
data: { transfer_params: data.transfer_params },
crossDomain: true,
xhrFields: { withCredentials: true }
} );
}
}
});
$J( '#cookiePrefPopup' ).hide();
}
$onAcceptButton.on('click', '', function() {
fnPostPreference( 1 );
} );
$onRejectButton.on('click', '', function ()
{
fnPostPreference( 0 );
} );
$J('body').append( $CPopupContent );
timerCookiePopup = window.setTimeout( function() {
$J("#cookiePrefPopup").show();
}, COOKIE_PREFERENCES_POPUP_DELAY );
}
}
// The /miniprofile/ is cacheable. We need to specify the origin URL of the request to avoid CORS see cl 5773210
function InitMiniprofileHovers( origin )
{
var $Hover = $J('<div/>', {'class': 'miniprofile_hover'} );
var $HoverContent = $J('<div/>', {'class': 'miniprofile_hover_inner shadow_content'} );
$Hover.append( $J('<div class="shadow_ul"></div><div class="shadow_top"></div><div class="shadow_ur"></div><div class="shadow_left"></div><div class="shadow_right"></div><div class="shadow_bl"></div><div class="shadow_bottom"></div><div class="shadow_br"></div>'), $HoverContent );
$Hover.hide();
$J(document.body).append( $Hover );
var fnDataFactory = function( key ) {
// key is either a number (accountid) or a string of "accountid_appid"
var rgKey = typeof key === "string" ? key.split( /_/ ) : [ key ];
var strURL = 'miniprofile/' + rgKey[0];
if ( rgKey[1] )
{
strURL += '?appid=' + rgKey[1];
if( origin )
{
strURL += '&origin=' + origin;
}
}
else if( origin )
{
strURL += '?origin=' + origin;
}
return new CDelayedAJAXData( strURL, MINIPROFILE_DELAY_BEFORE_AJAX );
};
var fnReadKey = function ( $Target ) {
var appid = $Target.parents('[data-miniprofile-appid]').first().data('miniprofile-appid');
if( appid )
{
return $Target.data('miniprofile') + '_' + appid;
}
else
{
return $Target.data('miniprofile')
}
};
var rgCallbacks = BindAJAXHovers( $Hover, $HoverContent, {
fnDataFactory: fnDataFactory,
fnPositionHover: PositionMiniprofileHover,
fnReadKey: fnReadKey,
strDataName: 'miniprofile',
strURLMatch: 'miniprofile'
} );
}
function _RegisterAJAXHoverHideFunction( fnHide )
{
if ( typeof g_rgfnHideAJAXHover == 'undefined' )
{
g_rgfnHideAJAXHover = [];
$J(window).blur( HideAJAXHovers );
}
g_rgfnHideAJAXHover.push( fnHide );
}
function HideAJAXHovers()
{
if ( typeof g_rgfnHideAJAXHover != 'undefined' )
{
for ( var i = 0; i < g_rgfnHideAJAXHover.length; i++ )
g_rgfnHideAJAXHover[i]();
}
}
function BindAJAXHovers( $Hover, $HoverContent, oParams )
{
var fnDataFactory = oParams.fnDataFactory;
var fnPositionHover = oParams.fnPositionHover || PositionMiniprofileHover;
var strDataName = oParams.strDataName;
var strURLMatch = oParams.strURLMatch;
var fnReadKey = function( $Element ) { return $Element.data( strDataName ); };
if ( oParams.fnReadKey )
fnReadKey = oParams.fnReadKey;
var strSelector = oParams.strSelector || '[data-' + strDataName + ']';
var nDelayBeforeShow = oParams.nDelayBeforeShow || MINIPROFILE_DELAY_BEFORE_SHOW;
// indexed by accountid
var rgHoverData = {};
var HoverTarget = null;
var timerHover = null;
var fnOnHover = function( $Target, key ) {
var bHoverVisible = ( $Hover.css('display') != 'none' );
var HoverData = rgHoverData[key];
if ( !HoverData )
{
HoverData = rgHoverData[key] = fnDataFactory( key );
}
if ( HoverTarget == HoverData && bHoverVisible )
{
//really only want to do this while fading out
$Hover.stop();
fnPositionHover( $Hover, $Target );
$Hover.show(); // PositionMiniprofile toggles visibility
$Hover.fadeTo( MINIPROFILE_ANIM_SPEED, 1.0 );
}
else if ( !timerHover || HoverData != HoverTarget )
{
// this is the new target
if ( HoverTarget && HoverTarget != HoverData )
HoverTarget.CancelAJAX();
HoverTarget = HoverData;
if ( timerHover )
{
window.clearTimeout( timerHover );
timerHover = null;
}
HoverData.QueueAjaxRequestIfNecessary();
timerHover = window.setTimeout( function() {
window.clearTimeout( timerHover );
timerHover = null;
HoverData.RunWhenAJAXReady( function() {
HoverData.Show( $HoverContent );
$Hover.stop();
$Hover.css( 'opacity', '' ); //clean up jquery animation
fnPositionHover( $Hover, $Target );
$Hover.fadeIn( MINIPROFILE_ANIM_SPEED );
var videoElem = $Hover.find( 'video' );
if ( videoElem.length != 0 )
{
var playPromise = videoElem[0].play();
if ( playPromise )
{
playPromise.catch( function( e ) {
} );
}
}
} );
}, nDelayBeforeShow );
}
};
var fnCancelHover = function( key )
{
var bHoverVisible = ( $Hover.css('display') != 'none' );
if ( key )
{
var HoverData = rgHoverData[key];
if ( HoverData )
{
HoverData.CancelAJAX();
}
}
if ( timerHover )
{
window.clearTimeout( timerHover );
timerHover = null;
}
if ( bHoverVisible )
{
$Hover.stop();
$Hover.fadeOut( MINIPROFILE_ANIM_SPEED );
}
};
var strEventNamespace = 'AjaxHover' + strDataName;
$J(document ).on( 'mouseenter.' + strEventNamespace + ' touchend.' + strEventNamespace, strSelector, function(e) {
var $Target = $J(this);
fnOnHover( $Target, fnReadKey( $Target) );
// Prevent click if we're sticky
if ( typeof $Target.data('stickyhover') !== 'undefined' )
{
e.preventDefault();
}
} );
$J(document ).on( 'click.' + strEventNamespace + ' mouseleave.' + strEventNamespace, strSelector, function(e) {
var $Target = $J(this);
// Cancel if we're not sticky
if ( typeof $Target.data('stickyhover') == 'undefined' )
{
fnCancelHover();
}
} );
// Cancel when clicking/touching elsewhere regardless if sticky
$J(document ).on( 'click', null, fnCancelHover );
// register this hover so HideAJAXHovers() can hide it when invoked
_RegisterAJAXHoverHideFunction( fnCancelHover );
return {
fnCancelHover: fnCancelHover
};
}
function PositionMiniprofileHover( $Hover, $Target, oParams )
{
if ( !oParams )
oParams = {};
var bPreferRightSide = oParams.bPreferRightSide || false;
var nPxArrowOverlap = ( oParams.nPxArrowOverlap != undefined ) ? oParams.nPxArrowOverlap : 2;
$Hover.css( 'visibility', 'hidden' );
$Hover.show();
var offset = $Target.offset();
$Hover.css( 'left', offset.left + 'px' );
$Hover.css( 'top', offset.top + 'px');
var $HoverBox = $Hover.children( '.shadow_content' );
if ( !$HoverBox.length )
$HoverBox = $J( $Hover.children()[0] );
var $HoverArrowLeft = $Hover.children( '.hover_arrow.left' );
var $HoverArrowRight = $Hover.children( '.hover_arrow.right' );
var nWindowScrollTop = $J(window).scrollTop();
var nWindowScrollLeft = $J(window).scrollLeft();
var nViewportWidth = $J(window).width();
var nViewportHeight = $J(window).height();
var $HoverArrow = $HoverArrowRight;
var nBoxRightViewport = ( offset.left - nWindowScrollLeft ) + $Target.outerWidth() + $HoverBox.width() + 14;
var nSpaceRight = nViewportWidth - nBoxRightViewport;
var nSpaceLeft = offset.left - $Hover.width();
if ( ( ( nSpaceLeft > 0 || nSpaceLeft > nSpaceRight ) && !bPreferRightSide ) || ( bPreferRightSide && nSpaceRight < 14 && nSpaceLeft > nSpaceRight ) )
{
$Hover.css( 'left', ( offset.left - $Hover.width() + nPxArrowOverlap + 3 ) + 'px' );
$HoverArrowLeft.hide();
$HoverArrowRight.show();
}
else
{
$Hover.css( 'left', ( offset.left + $Target.outerWidth() - nPxArrowOverlap ) + 'px' );
$HoverArrow = $HoverArrowLeft;
$HoverArrowLeft.show();
$HoverArrowRight.hide();
}
var nTopAdjustment = 0;
if ( $Target.height() < 48 )
nTopAdjustment = Math.floor( $Target.height() / 2 ) - 24;
var nDesiredHoverTop = offset.top - 15 + nTopAdjustment;
$Hover.css( 'top', nDesiredHoverTop + 'px' );
// see if the hover is cut off by the bottom of the window, and bump it up if neccessary
var nTargetTopViewport = ( offset.top - nWindowScrollTop ) + nTopAdjustment;
if ( nTargetTopViewport + $HoverBox.height() + 35 > nViewportHeight )
{
var nViewportAdjustment = ( $HoverBox.height() + 35 ) - ( nViewportHeight - nTargetTopViewport );
// if the hover has the "in-game" block at the bottom, we need to have more space at the bottom of the hover
// so that the arrow will appear in the blue part of the hover. This means the game info may be off-screen below.
var bHaveInGameInfo = $HoverBox.find('.miniprofile_ingame').length > 0;
var nHoverBoxBottomMinimum = ( bHaveInGameInfo ? 78 : 24 ); // the minimum amount of space we need below the arrow
nViewportAdjustment = Math.min( $HoverBox.height() - nHoverBoxBottomMinimum, nViewportAdjustment );
var nViewportAdjustedHoverTop = offset.top - nViewportAdjustment;
$Hover.css( 'top', nViewportAdjustedHoverTop + 'px' );
// arrow is normally offset 30pixels. we move it down the same distance we moved the hover up, so it is "fixed" to where it was initially
$HoverArrow.css( 'top', ( 30 + nDesiredHoverTop - nViewportAdjustedHoverTop ) + 'px' );
}
else
{
$HoverArrow.css( 'top', '' );
}
$Hover.hide();
$Hover.css( 'visibility', '' );
}
/* Emoticon hovers */
function CEmoticonDelayedAJAXData( strEmoticonName, msDelayBeforeAJAX )
{
CDelayedAJAXData.apply( this, [ 'economy/emoticonhover/' + strEmoticonName + '/jsonp.js', msDelayBeforeAJAX ]);
this.m_strEmoticonName = strEmoticonName;
}
// subclass CDelayedAJAXData so we can request via JSONP
CEmoticonDelayedAJAXData.prototype = new CDelayedAJAXData;
CEmoticonDelayedAJAXData.prototype.constructor = CEmoticonDelayedAJAXData;
CEmoticonDelayedAJAXData.prototype.GetAJAXParams = function()
{
return {
url: 'https://community.steamstatic.com/' + this.m_strURL,
dataType: 'jsonp',
jsonpCallback: 'OnLoadEmoticon_' + this.m_strEmoticonName, //consistent name for cachability
cache: true,
data: {l: 'schinese' }
}
};
function InitEmoticonHovers()
{
var $Hover = $J('<div/>', {'class': 'emoticon_hover'} );
var $HoverContent = $J('<div/>', {'class': 'emoticon_hover_content'} );
$Hover.append( $HoverContent, $J('<div/>', {'class': 'hover_arrow left emoticon_hover_arrow' } ), $J('<div/>', {'class': 'hover_arrow right emoticon_hover_arrow' } ) );
$Hover.hide();
var fnOneTimeEmoticonSetup = function() {
$J(document.body).append( $Hover );
};
var fnReadKey = function ( $Element ) {
if ( $Element.data('emoticon') )
{
return $Element.data('emoticon');
}
else if ( $Element.attr( 'src' ) )
{
var rgMatches = $Element.attr( 'src' ).match( 'emoticon/(.*)$' );
if ( rgMatches && rgMatches[1] )
{
var strEmoticon = rgMatches[1];
if ( strEmoticon.length > 1 )
{
if ( strEmoticon[0] == ':' )
strEmoticon = strEmoticon.substr( 1 );
if ( strEmoticon[ strEmoticon.length - 1 ] == ':' )
strEmoticon = strEmoticon.substr( 0, strEmoticon.length - 1 );
}
return strEmoticon;
}
}
return null;
};
var fnDataFactory = function( key ) {
if ( fnOneTimeEmoticonSetup )
{
fnOneTimeEmoticonSetup();
fnOneTimeEmoticonSetup = null;
}
return new CEmoticonDelayedAJAXData( key, 50 );
};
var rgCallbacks = BindAJAXHovers( $Hover, $HoverContent, {
fnDataFactory: fnDataFactory,
fnPositionHover: function( $Hover, $Target ) {
PositionMiniprofileHover( $Hover, $Target, {
bPreferRightSide: true,
nPxArrowOverlap: 0
} );
//slide it down a little for emoticon option popup
if ( $Target.hasClass('emoticon_option') )
$Hover.css( 'top', parseInt( $Hover.css('top') ) + 5 );
},
fnReadKey: fnReadKey,
strSelector: 'img.emoticon',
strURLMatch: 'emoticonhover',
nDelayBeforeShow: 50
} );
window.DismissEmoticonHover = rgCallbacks.fnCancelHover;
}
function V_EscapeRegExp( str )
{
return str.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
}
function V_EscapeHTML( str )
{
return String(str).replace( /&/g, '&amp;' ).replace( /["]/g, '&quot;' ).replace( /[']/g, '&#39;' ).replace( /</g, '&lt;').replace( />/g, '&gt;');
}
function v_trim( str )
{
if ( str.trim )
return str.trim();
else
{
return str.replace(/^\s+/, '').replace(/\s+$/, '');
}
}
function V_ParseJSON( str )
{
if ( typeof JSON == 'object' && JSON.parse )
return JSON.parse( str ); // built-in / json2.js
else
str.evalJSON(); // prototype
}
function V_ToJSON( object )
{
if ( typeof JSON == 'object' && JSON.stringify )
return JSON.stringify( object ); // built-in / json2.js
else
Object.toJSON( object ); // prototype
}
function V_IsJSON( str )
{
try
{
if( typeof JSON == 'object' && JSON.parse )
{
var o = JSON.parse(str);
if ( o !== null )
return true;
} else {
return str.isJSON();
}
}
catch (e) { }
return false;
}
function V_GetCookie( strCookieName )
{
var rgMatches = document.cookie.match( '(^|; )' + strCookieName + '=([^;]*)' );
if ( rgMatches && rgMatches[2] )
return rgMatches[2];
else
return null;
}
function V_GetDecodedCookie( strCookieName )
{
var value = V_GetCookie( strCookieName );
return value && decodeURIComponent( value );
}
function V_SetCookie( strCookieName, strValue, expiryInDays, path )
{
if ( !path )
path = '/';
var strDate = '';
if( typeof expiryInDays != 'undefined' && expiryInDays )
{
var dateExpires = new Date();
dateExpires.setTime( dateExpires.getTime() + 1000 * 60 * 60 * 24 * expiryInDays );
strDate = '; expires=' + dateExpires.toGMTString();
}
document.cookie = strCookieName + '=' + strValue + strDate + ';path=' + path;
}
function _GetStorageFromCookie()
{
var oStorage = {};
var strStorageJSON = V_GetCookie( 'storage' );
if ( strStorageJSON )
{
try {
oStorage = V_ParseJSON(decodeURIComponent(strStorageJSON));
}
catch (e) {
oStorage = {};
}
}
return oStorage;
}
function BInsideIFrame()
{
try
{
return window.self !== window.top;
}
catch( e )
{
return true;
}
}
function SetValueLocalStorage( strPreferenceName, value )
{
if ( !BInsideIFrame() && window.localStorage )
{
window.localStorage[strPreferenceName] = value;
}
else
{
var oStorage = _GetStorageFromCookie();
oStorage[strPreferenceName] = value;
V_SetCookie( 'storage', encodeURIComponent( V_ToJSON( oStorage ) ), 365 )
}
}
function UnsetValueLocalStorage( strPreferenceName )
{
if ( !BInsideIFrame() && window.localStorage )
{
delete window.localStorage[strPreferenceName];
}
else
{
var oStorage = _GetStorageFromCookie();
delete oStorage[strPreferenceName];
V_SetCookie( 'storage', encodeURIComponent( V_ToJSON( oStorage ) ), 365 )
}
}
function GetValueLocalStorage( strPreferenceName, defaultValue )
{
if ( !BInsideIFrame() && window.localStorage )
{
return window.localStorage[strPreferenceName] || defaultValue;
}
else
{
var oStorage = _GetStorageFromCookie();
return oStorage[strPreferenceName] || defaultValue;
}
}
function DynamicLink_PlayYouTubeVideoInline( elem, videoid )
{
var el = $J(elem);
var youtubeurl = location.protocol + '//www.youtube.com/embed/' + videoid + '?showinfo=0&autohide=1&fs=1&hd=1&modestbranding=1&rel=0&showsearch=0&wmode=direct&autoplay=1';
var wrapper = $J( '<div/>', { 'class': 'dynamiclink_youtubeviewvideo' } );
var embed_wrapper = $J( '<div/>', { 'class' : 'dynamiclink_youtubeviewvideoembedded', 'frameborder' : '0' } );
var iframeContent = $J( '<iframe/>', { 'frameborder' : '0', src: youtubeurl, 'allowfullscreen': '' } );
if ( el.length )
{
embed_wrapper.append( iframeContent );
wrapper.append( embed_wrapper );
el.after( wrapper );
el.remove();
}
}
function DynamicLink_PlayVimeoVideoInline( elem, videoid )
{
var el = $J(elem);
var videourl = 'https://player.vimeo.com/video/' + videoid + '?badge=0&autopause=0&autoplay=1&player_id=0';
var wrapper = $J( '<div/>', { 'class': 'dynamiclink_youtubeviewvideo' } );
var embed_wrapper = $J( '<div/>', { 'class' : 'dynamiclink_youtubeviewvideoembedded', 'frameborder' : '0' } );
var iframeContent = $J( '<iframe/>', { 'frameborder' : '0', src: videourl, webkitallowfullscreen : '', mozallowfullscreen : '', allowfullscreen : '' } );
if ( el.length )
{
embed_wrapper.append( iframeContent );
wrapper.append( embed_wrapper );
el.after( wrapper );
el.remove();
}
}
function DynamicLink_ShowSketchfabModelInline( elem, details )
{
var url = "https://sketchfab.com/models/" + details.modelid + "/embed?autostart=1";
var el = $J(elem);
var wrapper = $J( '<div/>', { 'class' : 'dynamiclink_sketchfabmodelembedded', 'frameborder' : '0' } );
var iframeContent = $J( '<iframe/>', { 'frameborder' : '0', "mozallowfullscreen" : true, "webkitallowfullscreen" : true, src: url } );
if ( el.length )
{
wrapper.append( iframeContent );
el.after( wrapper );
el.remove();
}
}
function ReplaceDynamicLink( id, strHTML )
{
var el = $J('#'+id);
if ( el.length && strHTML.length > 0 )
{
var newEl = $J( strHTML );
el.after( newEl );
el.remove();
if ( typeof window['HandleNewDynamicLink'] === "function" )
{
HandleNewDynamicLink( newEl );
}
}
}
function ShowBannedDynamicLink( el )
{
el = $J( el );
if ( el.length > 0 )
{
var bannedContentURL = el.data( 'bannedurl' );
var link = $J( "<a>", { href: bannedContentURL, text: bannedContentURL } );
el.html( link );
el.attr( 'onclick', '' );
}
}
function CScrollOffsetWatcher( el, fnCallback )
{
this.m_$Element = $J(el);
this.nOffsetTop = this.m_$Element.offset().top;
this.nBufferHeight = 500;
this.nOffsetTopTrigger = this.nOffsetTop - this.nBufferHeight;
this.fnOnHit = fnCallback;
CScrollOffsetWatcher.RegisterWatcher( this );
}
CScrollOffsetWatcher.prototype.SetBufferHeight = function( nHeight )
{
this.nBufferHeight = nHeight;
this.Recalc();
};
CScrollOffsetWatcher.prototype.Recalc = function()
{
this.nOffsetTop = this.m_$Element.offset().top;
this.nOffsetTopTrigger = this.nOffsetTop - this.nBufferHeight;
};
CScrollOffsetWatcher.sm_rgWatchers = [];
CScrollOffsetWatcher.m_nTimeoutInitialLoad = 0;
CScrollOffsetWatcher.RegisterWatcher = function( Watcher )
{
var bHadWatchers = CScrollOffsetWatcher.sm_rgWatchers.length > 0;
// keep the list sorted by offset trigger
var iInsertionPoint;
for( iInsertionPoint = CScrollOffsetWatcher.sm_rgWatchers.length; iInsertionPoint > 0 ; iInsertionPoint-- )
{
if ( Watcher.nOffsetTopTrigger > CScrollOffsetWatcher.sm_rgWatchers[iInsertionPoint - 1].nOffsetTopTrigger )
break;
}
CScrollOffsetWatcher.sm_rgWatchers.splice( iInsertionPoint, 0, Watcher );
if ( !bHadWatchers )
{
$J(window).on( 'scroll.ScrollOffsetWatcher', CScrollOffsetWatcher.OnScroll );
var nRecalcTimer = 0;
$J(window).on( 'resize.ScrollOffsetWatcher', function() {
if ( nRecalcTimer )
window.clearTimeout( nRecalcTimer );
nRecalcTimer = window.setTimeout( CScrollOffsetWatcher.ForceRecalc, 500 );
} );
}
// use a 1ms timeout to roll these together as much as possible on page load
if ( !CScrollOffsetWatcher.m_nTimeoutInitialLoad )
CScrollOffsetWatcher.m_nTimeoutInitialLoad = window.setTimeout( function() { CScrollOffsetWatcher.OnScroll(); CScrollOffsetWatcher.m_nTimeoutInitialLoad = 0; }, 1 );
};
CScrollOffsetWatcher.ForceRecalc = function()
{
for ( var i = 0; i < CScrollOffsetWatcher.sm_rgWatchers.length; i++ )
{
CScrollOffsetWatcher.sm_rgWatchers[i].Recalc();
}
CScrollOffsetWatcher.OnScroll();
};
CScrollOffsetWatcher.OnScroll = function()
{
var supportPageOffset = window.pageYOffset !== undefined;
var isCSS1Compat = ((document.compatMode || "") === "CSS1Compat");
var nScrollY = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop;
var nOffsetBottom = nScrollY + window.innerHeight;
var cCompletedWatchers = 0;
for( var i = 0; i < CScrollOffsetWatcher.sm_rgWatchers.length; i++ )
{
var Watcher = CScrollOffsetWatcher.sm_rgWatchers[i];
if ( nOffsetBottom > Watcher.nOffsetTopTrigger )
{
// make sure the page hasn't changed and we really need to show content
Watcher.Recalc();
if ( nOffsetBottom > Watcher.nOffsetTopTrigger )
{
Watcher.fnOnHit();
cCompletedWatchers++;
}
}
else
{
break;
}
}
if ( cCompletedWatchers )
CScrollOffsetWatcher.sm_rgWatchers.splice( 0, cCompletedWatchers );
if ( CScrollOffsetWatcher.sm_rgWatchers.length == 0 )
{
$J(window).off( 'scroll.ScrollOffsetWatcher' );
$J(window).off( 'resize.ScrollOffsetWatcher' );
}
};
function LoadImageGroupOnScroll( elTarget, strGroup )
{
var $Element = $J( '#' + elTarget );
if ( $Element.length )
new CScrollOffsetWatcher( $Element, function() { LoadDelayedImages(strGroup); } );
}
function LoadDelayedImages( group )
{
if ( typeof g_rgDelayedLoadImages != 'undefined' && g_rgDelayedLoadImages[group] )
{
var rgURLs = g_rgDelayedLoadImages[group];
for ( var i=0; i < rgURLs.length; i++ )
{
$J('#delayedimage_' + group + '_' + i).attr( 'src', rgURLs[i] );
}
g_rgDelayedLoadImages[group] = false;
}
}
WebStorage = {
GetLocal: function ( key, bSessionOnly )
{
var type = ( bSessionOnly ) ? 'session' : 'local';
if ( !window[type + 'Storage'] )
return WebStorage.GetCookie( key );
var storage = window[type + 'Storage'];
var value;
try {
value = storage.getItem(key);
} catch(err) {
// storage.getItem can throw SecurityError based on user's browser setting.
value = null;
}
try {
value = V_ParseJSON( value );
} catch(err){
return null;
}
if( value == null )
{
// Check if we have the value stored in a cookie instead. If so, move that to LS and remove the cookie
value = V_GetCookie( key );
if( value != null )
{
WebStorage.SetLocal( key, value, bSessionOnly );
WebStorage.ClearCookie( key );
}
}
return value;
},
GetLocalSession: function( key )
{
return WebStorage.GetLocal( key, true );
},
SetLocal: function ( key, value, bSessionOnly )
{
var type = ( bSessionOnly ) ? 'session' : 'local';
if ( !window[type + 'Storage'] )
return WebStorage.SetCookie( key, value, ( bSessionOnly ) ? null : 365 );
value = V_ToJSON( value );
var storage = window[type + 'Storage'];
storage.setItem( key, value );
},
SetLocalSession: function( key, value )
{
WebStorage.SetLocal( key, value, true );
},
GetCookie: function( key )
{
var keyValue = V_GetCookie( key );
if ( keyValue )
keyValue = decodeURIComponent( keyValue );
return V_IsJSON( keyValue ) ? V_ParseJSON( keyValue ) : keyValue;
},
SetCookie: function( key, value, duration )
{
value = encodeURIComponent( V_ToJSON( value ) );
V_SetCookie( key, value, duration );
},
ClearCookie: function( key )
{
WebStorage.SetCookie(key, null, -1 );
},
RemoveLocal : function ( key, bSessionOnly )
{
var type = ( bSessionOnly ) ? 'session' : 'local';
if ( !window[type + 'Storage'] )
return WebStorage.ClearCookie( key );
var storage = window[type + 'Storage'];
storage.removeItem( key );
}
};
// takes an integer
function v_numberformat( n, decimals, strDecimalSymbol, strThousandsSeperator )
{
if ( typeof strDecimalSymbol == 'undefined' && typeof strThousandsSeperator == 'undefined' && typeof Number != 'undefined' && typeof Number.toLocaleString != 'undefined' )
{
var options = {};
if ( typeof decimals != 'undefined' && decimals !== 0 )
{
options.minimumFractionDigits = decimals;
options.maximumFractionDigits = decimals;
}
var num = new Number(n);
try
{
return num.toLocaleString( false, options );
}
catch( e )
{
return num.toString();
}
}
var str = '' + ( n ? n : 0 );
var len = str.indexOf( '.' );
if ( len == -1 )
len = str.length;
var out = '';
for ( var i = 0; i < len; i++ )
{
var c = str.charAt(i);
out += c;
if ( i < len - 1 && (len - i - 1) % 3 == 0 && c != '-' )
out += ( typeof strThousandsSeperator == 'undefined' ? ',' : strThousandsSeperator );
}
if ( ( len < str.length || decimals ) && typeof decimals != 'undefined' && decimals !== 0 )
{
len++;
out += ( typeof strDecimalSymbol == 'undefined' ? ',' : strDecimalSymbol );
for ( var i = 0; i < ( decimals ? decimals : str.length - len ); i++ )
{
if ( len + i < str.length )
out += str.charAt( len + i );
else
out += '0';
}
}
return out;
}
function v_shuffle( rgArray )
{
for ( var i = 0; i < rgArray.length; i++ )
{
var iSwap = Math.floor( Math.random() * ( rgArray.length - i ) + i );
if ( iSwap != i )
{
var temp = rgArray[iSwap];
rgArray[iSwap] = rgArray[i];
rgArray[i] = temp;
}
}
return rgArray;
}
function UpdateFormattedNumber( element, delta )
{
var $Element = $J(element);
$Element.text( v_numberformat( parseInt( $Element.text().replace( /,/, '' ) ) + delta ) );
}
function RateAnnouncement( rateURL, gid, bVoteUp, clanID )
{
if ( bVoteUp && $J('#VoteUpBtn_' + gid).hasClass( "btn_active" ) )
{
return;
}
if ( !bVoteUp && $J('#VoteDownBtn_' + gid).hasClass( "btn_active" ) )
{
return;
}
rateURL = rateURL + gid;
$J.post( rateURL, {
'voteup' : bVoteUp,
'clanid' : clanID,
'sessionid' : g_sessionID
}
).done( function( json ) {
var votesUpCount = $J('#VotesUpCount_' + gid);
if ( votesUpCount )
{
var increment = 0;
if ( bVoteUp )
{
increment = 1;
}
else if ( $J('#VoteUpBtn_' + gid).hasClass( 'btn_active' ) )
{
increment = -1;
}
UpdateFormattedNumber( votesUpCount, increment );
if ( parseInt( votesUpCount.html().replace(/,/g, '') ) == 0 )
{
$J('#VotesUpCountContainer_' + gid).hide();
}
else
{
$J('#VotesUpCountContainer_' + gid).show();
}
}
if ( bVoteUp )
{
$J('#VoteUpBtn_' + gid).addClass( "btn_active" );
$J('#VoteDownBtn_' + gid).removeClass( "btn_active" );
}
else
{
$J('#VoteDownBtn_' + gid).addClass( "btn_active" );
$J('#VoteUpBtn_' + gid).removeClass( "btn_active" );
}
} )
.fail( function( jqxhr ) {
var responseJSON = jqxhr.responseText.evalJSON();
switch ( responseJSON.success )
{
case 21:
ShowAlertDialog( '错误', '您必须登录后才能执行该操作。' );
break;
case 24:
ShowAlertDialog( '错误',
'您的帐户不符合使用该功能的要求。<a class="whiteLink" href="https://help.steampowered.com/zh-cn/wizard/HelpWithLimitedAccount" target="_blank" rel="noreferrer">访问 Steam 客服</a>了解更多信息。'
);
break;
case 15:
ShowAlertDialog( '错误', '您没有执行该操作的权限。' );
break;
default:
ShowAlertDialog( '错误', '在处理您的请求时遇到错误:' + responseJSON.success );
break;
}
} );
return false;
}
function GetResponsiveHeaderFixedOffsetAdjustment()
{
// for responsive pages - we need to adjust for the menu
var $ResponsiveHeader = $J('.responsive_header:visible');
if ( $ResponsiveHeader.length && $ResponsiveHeader.css('position') == 'fixed' )
return $ResponsiveHeader.outerHeight();
else
return 0;
}
/* Scroll to an element if it's not already in view. If it's at the bottom of the viewport, then it will be
scrolled to the top if less than nRequiredPixelsToShow are visible (defaults to the height of the element)
*/
function ScrollToIfNotInView( elem, nRequiredPixelsToShow, nSpacingBefore, nAnimationSpeed )
{
var $Elem = $JFromIDOrElement(elem);
if ( typeof nSpacingBefore == 'undefined' )
nSpacingBefore = 0;
// for responsive pages - we need to adjust for the menu
nSpacingBefore += GetResponsiveHeaderFixedOffsetAdjustment();
var elemTop = $Elem.offset().top;
var nViewportOffsetTop = elemTop - $J(window).scrollTop();
var bNeedToScroll = false;
if ( nViewportOffsetTop < 0 )
{
bNeedToScroll = true;
}
else
{
if ( !nRequiredPixelsToShow )
nRequiredPixelsToShow = $Elem.outerHeight();
var nViewportOffsetBottom = nViewportOffsetTop + nRequiredPixelsToShow;
if ( nViewportOffsetBottom > $J(window).height() )
bNeedToScroll = true;
}
if ( bNeedToScroll )
{
if ( nSpacingBefore )
nViewportOffsetTop -= nSpacingBefore;
if ( typeof nAnimationSpeed != 'undefined' )
$J('html, body' ).animate( {scrollTop: nViewportOffsetTop}, nAnimationSpeed );
else
window.scrollBy( 0, nViewportOffsetTop );
}
}
function CAjaxInfiniteScrollingControls( rgSearchData, url )
{
this.m_strActionURL = null;
this.m_cPageSize = null;
this.m_strElementPrefix = "";
this.m_strClassPrefix = "";
this.m_StrRowsId = "";
this.m_rgStaticParams = null;
this.m_bLoading = false;
this.m_fnPreRequestHandler = null;
this.m_fnResponseHandler = null;
this.m_fnPageChangingHandler = null;
this.m_fnPageChangedHandler = null;
this.m_fnRawScrollHandler = null;
this.m_LoadingDialog = null;
this.m_bRestoringScrollTop = false;
this.m_strActionURL = url;
this.m_strQuery = rgSearchData['query'];
this.m_cTotalCount = rgSearchData['total_count'];
this.m_iCurrentPage = rgSearchData['page'] || 0;
this.m_cPageSize = rgSearchData['pagesize'];
this.m_cMaxPages = Math.ceil( this.m_cTotalCount / this.m_cPageSize );
this.m_iTriggerHeight = rgSearchData['trigger_height'] || 750; // How many px from the bottom should we trigger a page load.
this.m_iCooldownTime = 0; // ms from the epoch when we can next update.
this.m_iCooldownInterval = 200; // Minimum time (in ms) between updates
if ( rgSearchData['prefix'] )
this.m_strElementPrefix = rgSearchData['prefix'];
// Rows element name that's updated when we scroll.
this.m_StrRowsId = this.m_strElementPrefix + 'Rows';
if ( rgSearchData['class_prefix'] )
this.m_strClassPrefix = rgSearchData['class_prefix'];
// We want our handlers to get a copy of our infiniteScroll object when they're called, so we save it
// into a var and build a closure around it.
var thisControl = this;
// By saving the bound scroll-handler to a variable, we can then unload it when our search
// options are upadated.
this.m_fnRawScrollHandler = function() { thisControl.OnScroll() };
$J(document).scroll( this.m_fnRawScrollHandler );
// If we have a _scroll_top element, then set our OnUnload handler.
if ( $J( "#" + this.m_strElementPrefix + '_scroll_top').length )
window.addEventListener('beforeunload', function() { thisControl.OnUnload() } );
// Optional: Dynamic data returned from the last request, to provided to the next request. Lets them
// being stringed together with some memory
this.m_rgDynamicData = null;
this.RestoreScrollTop( true );
}
CAjaxInfiniteScrollingControls.prototype.DoneRestoreScrollTop = function()
{
this.ClearLoadingDialog();
this.m_bRestoringScrollTop = false;
};
CAjaxInfiniteScrollingControls.prototype.ClearLoadingDialog = function()
{
if ( this.m_LoadingDialog )
{
this.m_LoadingDialog.Dismiss();
this.m_LoadingDialog = null;
}
};
// Stops this infinite scroll handler.
CAjaxInfiniteScrollingControls.prototype.Stop = function()
{
// Clear loading notifiers.
this.ClearLoadingDialog();
this.HideThrobber();
// Clear scheduled scroll, if there is one.
if ( this.m_oScheduledScroll )
clearTimeout( this.m_oScheduledScroll );
// Unhook scroll handler.
$J(document).off( "scroll", this.m_fnRawScrollHandler );
// TODO: Do we need to call or clear BeforeUnload?
};
CAjaxInfiniteScrollingControls.prototype.RestoreScrollTop = function( bForce )
{
this.m_bRestoringScrollTop |= bForce;
if ( !this.m_bRestoringScrollTop )
{
return;
}
var scrollTopPrevious = parseInt( $J( "#" + this.m_strElementPrefix + '_scroll_top').val() );
if ( scrollTopPrevious )
{
var viewport = document.viewport.getDimensions(); // Gets the viewport as an object literal
var windowHeight = viewport.height; // Usable window height
var bodyHeight = $(document.body).getHeight();
// done?
if ( scrollTopPrevious < bodyHeight - windowHeight )
{
this.DoneRestoreScrollTop();
window.scrollTo( 0, scrollTopPrevious );
}
else
{
if ( this.m_LoadingDialog == null )
{
this.m_LoadingDialog = ShowBlockingWaitDialog( '请稍候', '正在返回您最后在此页面上浏览的位置…' );
}
window.scrollTo( 0, scrollTopPrevious );
}
}
};
CAjaxInfiniteScrollingControls.prototype.OnUnload = function()
{
var scrollOffset = document.viewport.getScrollOffsets();
var scrollTop = scrollOffset.top;
$J( "#" + this.m_strElementPrefix + '_scroll_top').val( scrollTop );
};
CAjaxInfiniteScrollingControls.prototype.OnScroll = function()
{
if ( this.m_bLoading )
return;
var iNow = new Date().getTime();
// How soon can we scroll?
var iScrollWait = this.m_iCooldownTime - iNow;
// If we haven't reached our cooldown, do nothing.
if ( iScrollWait > 0 )
{
// console.log("InfiniScrolling too fast; engaging throttle");
// Schedule a scroll event for when they would be allowed to scroll. Without this, the
// user needs to jiggle their scrollbar.
if ( this.m_oScheduledScroll == null )
{
this.m_oScheduledScroll = setTimeout( this.m_fnRawScrollHandler, iScrollWait );
this.ShowThrobber(); // Show throbber while waiting to begin load.
}
return;
}
this.m_oScheduledScroll = null;
// The bottom of our screen is equal to how far we've scrolled, plus the height of our window.
var nCurrentScroll = $J(window).scrollTop() + $J(window).height();
var rows = $J('#' + this.m_StrRowsId);
var offset = rows.offset();
// The bottom of our content is the height of our results, plus its offset from the top of the page.
// We want to trigger a load this.m_iTriggerHeight before the user sees that.
var nTriggerPoint = rows.height() + offset.top - this.m_iTriggerHeight;
if ( nCurrentScroll > nTriggerPoint )
{
this.m_iCooldownTime = iNow + this.m_iCooldownInterval;
this.NextPage();
}
};
CAjaxInfiniteScrollingControls.prototype.GetActionURL = function( action )
{
var url = this.m_strActionURL + action + '/';
return url;
};
CAjaxInfiniteScrollingControls.prototype.SetPreRequestHandler = function( fnHandler )
{
this.m_fnPreRequestHandler = fnHandler;
};
CAjaxInfiniteScrollingControls.prototype.SetResponseHandler = function( fnHandler )
{
this.m_fnResponseHandler = fnHandler;
};
CAjaxInfiniteScrollingControls.prototype.SetPageChangingHandler = function ( fnHandler )
{
this.m_fnPageChangingHandler = fnHandler;
};
CAjaxInfiniteScrollingControls.prototype.SetPageChangedHandler = function ( fnHandler )
{
this.m_fnPageChangedHandler = fnHandler;
};
CAjaxInfiniteScrollingControls.prototype.SetStaticParameters = function ( rgParams )
{
this.m_rgStaticParams = rgParams;
};
CAjaxInfiniteScrollingControls.prototype.OnAJAXComplete = function()
{
this.m_bLoading = false;
};
CAjaxInfiniteScrollingControls.prototype.NextPage = function()
{
if ( this.m_iCurrentPage < this.m_cMaxPages - 1 ) {
this.LoadPage( this.m_iCurrentPage + 1 );
}
else
{
// We're reached the end of our results!
this.Stop();
}
};
CAjaxInfiniteScrollingControls.prototype.LoadPage = function( iPage, bForce )
{
if ( typeof( bForce )== 'undefined' || !bForce )
{
if ( this.m_bLoading || iPage >= this.m_cMaxPages || iPage < 0 )
{
return false;
}
else if ( iPage == this.m_iCurrentPage )
{
this.RestoreScrollTop( false );
}
}
var params = {
query: this.m_strQuery,
start: this.m_cPageSize * iPage,
count: this.m_cPageSize,
dynamic_data: this.m_rgDynamicData,
};
if ( this.m_rgStaticParams != null )
{
for ( var sParamName in this.m_rgStaticParams )
{
if ( typeof sParamName != "string" )
continue;
var typeOfParam = typeof this.m_rgStaticParams[sParamName];
if ( typeOfParam != "string" && typeOfParam != "number" && typeOfParam != "boolean" )
continue;
params[sParamName] = this.m_rgStaticParams[sParamName];
}
}
if ( this.m_fnPageChangingHandler != null )
this.m_fnPageChangingHandler( iPage );
if ( this.m_fnPreRequestHandler != null )
this.m_fnPreRequestHandler( params );
this.ShowThrobber();
this.m_bLoading = true;
new Ajax.Request( this.GetActionURL( '' ), {
method: 'get',
parameters: params,
onSuccess: this.OnResponseRenderResults.bind( this ),
onComplete: this.OnAJAXComplete.bind( this )
});
return true;
};
CAjaxInfiniteScrollingControls.prototype.GetThrobber = function() {
// NB: This is using prototype, *not* jQuery, hence the lack of 'J'.
return $(this.m_strElementPrefix + '_loading');
};
CAjaxInfiniteScrollingControls.prototype.HideThrobber = function() {
var throbber = this.GetThrobber();
if (throbber)
throbber.hide();
};
CAjaxInfiniteScrollingControls.prototype.ShowThrobber = function() {
var throbber = this.GetThrobber();
if (throbber)
throbber.show();
};
CAjaxInfiniteScrollingControls.prototype.OnResponseRenderResults = function( transport )
{
if ( transport.responseJSON && transport.responseJSON.success )
{
this.HideThrobber();
if ( typeof RecordAJAXPageView !== "undefined" )
{
RecordAJAXPageView( transport.request.url );
}
var response = transport.responseJSON;
this.m_cTotalCount = response.total_count;
this.m_cMaxPages = Math.ceil( response.total_count / this.m_cPageSize );
this.m_iCurrentPage = Math.floor( response.start / this.m_cPageSize );
if ( this.m_iCurrentPage != 0 && this.m_cTotalCount <= response.start )
{
// this page is no longer valid, flip back a page (deferred so that the AJAX handler exits and reset m_bLoading)
this.DoneRestoreScrollTop();
return;
}
var elResults = $(this.m_StrRowsId);
elResults.insert( response.results_html );
this.m_rgDynamicData = ( response.dynamic_data ) ? response.dynamic_data : null;
if ( this.m_fnResponseHandler != null )
{
this.m_fnResponseHandler( response );
}
if ( this.m_fnPageChangedHandler != null )
this.m_fnPageChangedHandler( this.m_iCurrentPage, elResults );
this.m_bLoading = false;
if ( this.m_iCurrentPage < this.m_cMaxPages - 1 )
{
this.RestoreScrollTop( false );
}
else
{
this.DoneRestoreScrollTop();
}
}
else
{
this.DoneRestoreScrollTop();
}
};
function CAjaxPagingControls( rgSearchData, url )
{
this.m_strActionURL = null;
this.m_cPageSize = null;
this.m_strElementPrefix = "";
this.m_strClassPrefix = "";
this.m_rgStaticParams = null;
this.m_strQuery = null;
this.m_cTotalCount = 0;
this.m_iCurrentPage = 0;
this.m_cMaxPages = 0;
this.m_bLoading = false;
this.m_fnPreRequestHandler = null;
this.m_fnResponseHandler = null;
this.m_fnPageChangingHandler = null;
this.m_fnPageChangedHandler = null;
this.m_strActionURL = url;
this.m_strQuery = rgSearchData['query'];
this.m_cTotalCount = rgSearchData['total_count'];
this.m_iCurrentPage = 0;
this.m_cPageSize = rgSearchData['pagesize'];
this.m_cMaxPages = Math.ceil( this.m_cTotalCount / this.m_cPageSize );
this.m_strDefaultAction = typeof rgSearchData['action'] != 'undefined' ? rgSearchData['action'] : 'render';
this.m_rgAvailableSizes = [];
if ( rgSearchData['prefix'] )
this.m_strElementPrefix = rgSearchData['prefix'];
if ( rgSearchData['class_prefix'] )
this.m_strClassPrefix = rgSearchData['class_prefix'];
$(this.m_strElementPrefix + '_btn_prev').observe( 'click', this.PrevPage.bind( this ) );
$(this.m_strElementPrefix + '_btn_next').observe( 'click', this.NextPage.bind( this ) );
var elPageSizeCtn = $(this.m_strElementPrefix + '_paging_size_ctn');
for( var i = 0; elPageSizeCtn && i < $( elPageSizeCtn ).children.length; ++i )
{
$(this.m_strElementPrefix + '_paging_size_' + i ).observe( 'click', this.OnChangeSize.bind( this ) );
this.m_rgAvailableSizes.push( $( elPageSizeCtn ).children[i].dataset.size );
}
this.UpdatePagingDisplay();
}
CAjaxPagingControls.prototype.GetActionURL = function( action )
{
var url = action ? this.m_strActionURL + action + '/' : this.m_strActionURL;
return url;
};
CAjaxPagingControls.prototype.SetPreRequestHandler = function( fnHandler )
{
this.m_fnPreRequestHandler = fnHandler;
};
CAjaxPagingControls.prototype.SetResponseHandler = function( fnHandler )
{
this.m_fnResponseHandler = fnHandler;
};
CAjaxPagingControls.prototype.SetPageChangingHandler = function ( fnHandler )
{
this.m_fnPageChangingHandler = fnHandler;
};
CAjaxPagingControls.prototype.SetPageChangedHandler = function ( fnHandler )
{
this.m_fnPageChangedHandler = fnHandler;
};
CAjaxPagingControls.prototype.SetStaticParameters = function ( rgParams )
{
this.m_rgStaticParams = rgParams;
};
CAjaxPagingControls.prototype.OnAJAXComplete = function()
{
this.m_bLoading = false;
};
CAjaxPagingControls.prototype.OnChangeSize = function( event )
{
if( event.target && event.target.dataset.size != this.m_cPageSize )
{
this.m_cPageSize = event.target.dataset.size;
this.GoToPage( 0, true );
}
};
CAjaxPagingControls.prototype.NextPage = function()
{
if ( this.m_iCurrentPage < this.m_cMaxPages - 1 )
this.GoToPage( this.m_iCurrentPage + 1 );
};
CAjaxPagingControls.prototype.PrevPage = function()
{
if ( this.m_iCurrentPage > 0 )
this.GoToPage( this.m_iCurrentPage - 1 );
};
CAjaxPagingControls.prototype.GoToPage = function( iPage, bForce )
{
if ( typeof( bForce )== 'undefined' || !bForce )
{
if ( this.m_bLoading || iPage >= this.m_cMaxPages || iPage < 0 || iPage == this.m_iCurrentPage )
return false;
}
var params = {
query: this.m_strQuery,
start: this.m_cPageSize * iPage,
count: this.m_cPageSize
};
if ( this.m_rgStaticParams != null )
{
for ( var sParamName in this.m_rgStaticParams )
{
if ( typeof sParamName != "string" )
continue;
params[sParamName] = this.m_rgStaticParams[sParamName];
}
}
if ( this.m_fnPageChangingHandler != null )
this.m_fnPageChangingHandler( iPage );
if ( this.m_fnPreRequestHandler != null )
this.m_fnPreRequestHandler( params );
this.m_bLoading = true;
new Ajax.Request( this.GetActionURL( this.m_strDefaultAction ), {
method: 'get',
parameters: params,
onSuccess: this.OnResponseRenderResults.bind( this ),
onComplete: this.OnAJAXComplete.bind( this )
});
return true;
};
CAjaxPagingControls.prototype.OnResponseRenderResults = function( transport )
{
if ( transport.responseJSON && transport.responseJSON.success )
{
if ( typeof RecordAJAXPageView !== "undefined" )
{
RecordAJAXPageView( transport.request.url );
}
var response = transport.responseJSON;
this.m_cTotalCount = response.total_count;
this.m_cMaxPages = Math.ceil( response.total_count / this.m_cPageSize );
this.m_iCurrentPage = Math.floor( response.start / this.m_cPageSize );
if ( this.m_iCurrentPage != 0 && this.m_cTotalCount <= response.start )
{
// this page is no longer valid, flip back a page (deferred so that the AJAX handler exits and reset m_bLoading)
this.GoToPage.bind( this, this.m_iCurrentPage - 1 ).defer();
return;
}
var elResults = $(this.m_strElementPrefix + 'Rows');
if( elResults)
elResults.update( response.results_html );
if ( this.m_fnResponseHandler != null )
this.m_fnResponseHandler( response );
this.UpdatePagingDisplay();
ScrollToIfNotInView( $(this.m_strElementPrefix + 'Table'), 40 );
}
};
CAjaxPagingControls.prototype.UpdatePagingDisplay = function()
{
var elemNoResults = $(this.m_strElementPrefix + '_no_results');
if ( this.m_cTotalCount == 0 )
{
$(this.m_strElementPrefix + '_ctn').hide();
if ( elemNoResults )
elemNoResults.show();
}
else
{
$(this.m_strElementPrefix + '_ctn').show();
if ( elemNoResults )
elemNoResults.hide();
$(this.m_strElementPrefix + '_total').update( v_numberformat( this.m_cTotalCount ) );
$(this.m_strElementPrefix + '_start').update( v_numberformat( this.m_iCurrentPage * this.m_cPageSize + 1 ) );
$(this.m_strElementPrefix + '_end').update( Math.min( ( this.m_iCurrentPage + 1 ) * this.m_cPageSize, this.m_cTotalCount ) );
}
if( this.m_rgAvailableSizes && this.m_rgAvailableSizes.length > 0 )
{
if( this.m_cTotalCount <= this.m_rgAvailableSizes[0] )
{
$(this.m_strElementPrefix + '_per_page_ctn').hide();
}
else
{
$(this.m_strElementPrefix + '_per_page_ctn').show();
}
}
if ( this.m_cMaxPages <= 1 )
{
$(this.m_strElementPrefix + '_controls').hide();
}
else
{
$(this.m_strElementPrefix + '_controls').show();
if ( this.m_iCurrentPage > 0 )
$(this.m_strElementPrefix + '_btn_prev').removeClassName('disabled');
else
$(this.m_strElementPrefix + '_btn_prev').addClassName('disabled');
if ( this.m_iCurrentPage < this.m_cMaxPages - 1 )
$(this.m_strElementPrefix + '_btn_next').removeClassName('disabled');
else
$(this.m_strElementPrefix + '_btn_next').addClassName('disabled');
var elPageLinks = $(this.m_strElementPrefix + '_links');
elPageLinks.update('');
// we always show first, last, + 3 page links closest to current page
var cPageLinksAheadBehind = 2;
var firstPageLink = Math.max( this.m_iCurrentPage - cPageLinksAheadBehind, 1 );
var lastPageLink = Math.min( this.m_iCurrentPage + (cPageLinksAheadBehind*2) + ( firstPageLink - this.m_iCurrentPage ), this.m_cMaxPages - 2 );
if ( lastPageLink - this.m_iCurrentPage < cPageLinksAheadBehind )
firstPageLink = Math.max( this.m_iCurrentPage - (cPageLinksAheadBehind*2) + ( lastPageLink - this.m_iCurrentPage ), 1 );
this.AddPageLink( elPageLinks, 0 );
if ( firstPageLink != 1 )
elPageLinks.insert( ' ... ' );
for ( var iPage = firstPageLink; iPage <= lastPageLink; iPage++ )
{
this.AddPageLink( elPageLinks, iPage );
}
if ( lastPageLink != this.m_cMaxPages - 2 )
elPageLinks.insert( ' ... ' );
this.AddPageLink( elPageLinks, this.m_cMaxPages - 1 );
}
if( this.m_rgAvailableSizes.indexOf( this.m_cPageSize ) > -1 )
{
for( var i = 0; i < this.m_rgAvailableSizes.length; ++ i )
{
var elSizeItem = $(this.m_strElementPrefix + '_paging_size_' + i );
if( elSizeItem.dataset.size == this.m_cPageSize )
{
elSizeItem.addClassName( 'size_selected' );
}
else
{
elSizeItem.removeClassName( 'size_selected' );
}
}
}
if ( this.m_fnPageChangedHandler != null )
this.m_fnPageChangedHandler( this.m_iCurrentPage );
};
CAjaxPagingControls.prototype.AddPageLink = function( elPageLinks, iPage )
{
var el = new Element( 'span', {'class': ( this.m_strClassPrefix != "" ? this.m_strClassPrefix : this.m_strElementPrefix ) + '_paging_pagelink' } );
el.update( (iPage + 1) + ' ' );
if ( iPage == this.m_iCurrentPage )
el.addClassName( 'active' );
else
el.observe( 'click', this.GoToPage.bind( this, iPage ) );
elPageLinks.insert( el );
};
function CSlider( $Container, $Grabber, args )
{
this.m_$Container = $Container;
this.m_$Grabber = $Grabber || $Container.find('.handle');
this.m_nMinVal = args.min || 0;
this.m_nMaxVal = args.max || 100;
this.m_nIncrement = args.increment || 1;
this.m_nValue = args.value || 0;
this.m_fnOnChange = args.fnOnChange || function( value, bInDrag ) {};
this.m_$Grabber.css( 'position', 'absolute' );
this.CalcRatios();
this.SetValue( this.m_nValue );
var fnGetPageX = function( event )
{
if ( event.type.indexOf( 'touch' ) == 0 )
{
var TouchEvent = event.originalEvent;
var rgTouches = TouchEvent ? TouchEvent.touches : null;
if ( !rgTouches || rgTouches.length < 1 )
return event.pageX || 0; //probably wrong
return rgTouches[0].pageX || 0;
}
else
{
return event.pageX || 0;
}
};
var _this = this;
this.m_$Container.on( 'mousedown touchstart', function( event ) {
_this.CalcRatios();
if ( !_this.m_$Grabber.is( event.target ) )
{
// jump the grabber to this position and start the drag
var nPosition = fnGetPageX( event ) - _this.m_$Container.offset().left;
// we want the grabber centered under the mosue if possible
nPosition -= Math.floor( _this.m_$Grabber.width() / 2 );
var nNewPosition = Math.min( Math.max( nPosition, 0 ), _this.m_nWidth );
_this.m_$Grabber.css('left', nNewPosition + 'px' );
_this.m_nValue = nNewPosition / _this.m_flRatio;
if ( _this.m_nIncrement > 1 && _this.m_nValue < _this.m_nMaxVal ) {
//_this.m_nValue += _this.m_nIncrement - 1;
_this.m_nValue = parseInt(_this.m_nValue / _this.m_nIncrement) * _this.m_nIncrement;
}
_this.m_fnOnChange( _this.m_nValue, true );
}
var nInitialPosition = parseInt( _this.m_$Grabber.css('left') );
var nStartDragX = fnGetPageX( event );
$J(document).on( 'mousemove.CSlider touchmove.CSlider', function( event ) {
var nDelta = fnGetPageX( event ) - nStartDragX;
var nNewPosition = Math.min( Math.max( nInitialPosition + nDelta, 0 ), _this.m_nWidth );
_this.m_$Grabber.css('left', nNewPosition + 'px' );
_this.m_nValue = nNewPosition / _this.m_flRatio;
if ( _this.m_nIncrement > 1 && _this.m_nValue < _this.m_nMaxVal ) {
//_this.m_nValue += _this.m_nIncrement - 1;
_this.m_nValue = parseInt(_this.m_nValue / _this.m_nIncrement) * _this.m_nIncrement;
}
_this.m_fnOnChange( _this.m_nValue, true );
});
$J(document).on( 'mouseup.CSlider touchend.CSlider', function( event ) {
$J(document).off('.CSlider');
_this.m_fnOnChange( _this.m_nValue, false );
});
event.preventDefault();
});
}
CSlider.prototype.CalcRatios = function()
{
var nGrabberWidth = this.m_$Grabber.width();
this.m_nWidth = this.m_$Container.width() - nGrabberWidth;
this.m_flRatio = this.m_nWidth / ( this.m_nMaxVal - this.m_nMinVal );
};
CSlider.prototype.SetValue = function( nValue, nAnimationSpeed )
{
this.m_nValue = Math.min( Math.max( nValue, this.m_nMinVal ), this.m_nMaxVal );
var nNewPosition = Math.floor( ( this.m_nValue - this.m_nMinVal ) * this.m_flRatio );
this.m_$Grabber.stop();
if ( nAnimationSpeed )
this.m_$Grabber.animate( {left: nNewPosition }, nAnimationSpeed );
else
this.m_$Grabber.css( 'left', nNewPosition + 'px' );
};
CSlider.prototype.GetValue = function()
{
return this.m_nValue;
};
CSlider.prototype.GetMin = function()
{
return this.m_nMinVal;
};
CSlider.prototype.GetMax = function()
{
return this.m_nMaxVal;
};
CSlider.prototype.SetRange = function( nMinVal, nMaxVal, nValue )
{
this.m_nMinVal = nMinVal;
this.m_nMaxVal = nMaxVal;
if ( typeof nValue != 'undefined' )
this.m_nValue = nValue;
this.CalcRatios();
this.SetValue( this.m_nValue );
};
CSlider.prototype.SetIncrement = function( nIncrement )
{
this.m_nIncrement = nIncrement;
};
function CScrollSlider( $Scroll, $Container, $Grabber, args )
{
this.m_$Scroll = $Scroll;
this.m_$SliderCtn = $Container;
var $Slider = $Container.children('.slider');
this.m_Slider = new CSlider( $Slider.length ? $Slider : $Container, $Grabber, { fnOnChange: $J.proxy( this.OnSliderChange, this )} );
this.m_$Scroll.css('overflowX', 'scroll');
// add momentum on iOS
this.m_$Scroll.css('-webkit-overflow-scrolling', 'touch');
var _this = this;
var bDidInitialRecalc = false;
this.m_$Scroll.on( 'scroll.ScrollSlider', function() {
if ( !bDidInitialRecalc )
{
// we don't want to do this all the time, but on some browsers the first call to
// update ranges is too early and values don't compute correctly.
_this.UpdateRanges();
bDidInitialRecalc = true;
}
_this.m_Slider.SetValue( _this.m_$Scroll.scrollLeft() );
});
$J(window).on('resize.ScrollSlider', function() {
_this.UpdateRanges();
} );
this.m_$Scroll.on( 'v_contentschanged', function() {
_this.UpdateRanges();
} );
this.UpdateRanges();
}
CScrollSlider.prototype.SetValue = function( value, nAnimationSpeed ) {
if ( nAnimationSpeed )
{
this.m_$Scroll.stop().animate( {'scrollLeft': value }, nAnimationSpeed );
}
else
{
this.m_$Scroll.stop().scrollLeft( value );
}
};
CScrollSlider.prototype.GetValue = function() {
return this.m_Slider.GetValue();
};
CScrollSlider.prototype.UpdateRanges = function()
{
var nParentWidth = this.m_$Scroll.width();
var nScrollWidth = this.m_$Scroll[0].scrollWidth;
if ( nScrollWidth <= nParentWidth )
{
this.m_$SliderCtn.hide();
}
else
{
this.m_Slider.SetRange( 0, nScrollWidth - nParentWidth, this.m_$Scroll.scrollLeft() );
this.m_$SliderCtn.show();
}
};
CScrollSlider.prototype.OnSliderChange = function( value, bInDrag )
{
this.m_$Scroll.stop().scrollLeft( value );
};
function IsValidEmailAddress( email )
{
var email_regex = /^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i;
return ( email != '' && email_regex.test(email) );
}
(function ($) {
$.deparam = function (params, coerce) {
var obj = {},
coerce_types = { 'true': !0, 'false': !1, 'null': null };
$.each(params.replace(/\+/g, ' ').split('&'), function (j,v) {
var param = v.split('='),
key = decodeURIComponent(param[0]),
val,
cur = obj,
i = 0,
keys = key.split(']['),
keys_last = keys.length - 1;
if (/\[/.test(keys[0]) && /\]$/.test(keys[keys_last])) {
keys[keys_last] = keys[keys_last].replace(/\]$/, '');
keys = keys.shift().split('[').concat(keys);
keys_last = keys.length - 1;
} else {
keys_last = 0;
}
if (param.length === 2) {
val = decodeURIComponent(param[1]);
if (coerce) {
val = val && !isNaN(val) ? +val // number
: val === 'undefined' ? undefined // undefined
: coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
: val; // string
}
if ( keys_last ) {
for (; i <= keys_last; i++) {
key = keys[i] === '' ? cur.length : keys[i];
cur = cur[key] = i < keys_last
? cur[key] || (keys[i+1] && isNaN(keys[i+1]) ? {} : [])
: val;
}
} else {
if ($.isArray(obj[key])) {
obj[key].push( val );
} else if (obj[key] !== undefined) {
obj[key] = [obj[key], val];
} else {
obj[key] = val;
}
}
} else if (key) {
obj[key] = coerce
? undefined
: '';
}
});
return obj;
};
})(jQuery);
/**
* Generic search field that handles:
* 1.) Showing default text if the input field is empty
* 2.) When the input field gets focus, the text field clears
* 3.) Adding CSS class to the input field when it is default text
* 4.) When the user presses return/enter in the field
*
* Call ClearIfDefaultValue() before submitting the form
*/
function SearchFieldWithText( elemID, defaultSearchText, onEnterFunc, defaultTextCSSClass )
{
var elem = $( elemID );
this.elem = elem;
this.defaultSearchText = defaultSearchText;
this.defaultTextCSSClass = defaultTextCSSClass;
this.onEnterFunc = onEnterFunc;
Event.observe( elem, 'click', this.handleClickOrFocus.bind(this));
Event.observe( elem, 'focus', this.handleClickOrFocus.bind(this));
Event.observe( elem, 'blur', this.handleBlur.bind(this));
Event.observe( elem, 'keypress', this.handleKeypress.bind(this));
Event.observe( elem.form, 'submit', this.ClearIfDefaultValue.bind(this));
this.handleBlur();
}
SearchFieldWithText.prototype.handleClickOrFocus = function()
{
if ( this.elem.value == this.defaultSearchText )
{
this.elem.value = '';
if ( this.defaultTextCSSClass )
this.elem.removeClassName( this.defaultTextCSSClass );
}
};
SearchFieldWithText.prototype.handleBlur = function()
{
if ( this.elem.value == '')
{
this.elem.value = this.defaultSearchText;
if ( this.defaultTextCSSClass )
this.elem.addClassName( this.defaultTextCSSClass );
}
};
SearchFieldWithText.prototype.handleKeypress = function()
{
if ( !this.onEnterFunc )
return;
var keyCode = null;
if( event.which )
{
keyCode = event.which;
}
else if( event.keyCode )
{
keyCode = evt.keyCode;
}
if ( 13 == keyCode )
{
this.onEnterFunc();
}
};
SearchFieldWithText.prototype.ClearIfDefaultValue = function()
{
if ( this.elem.value == this.defaultSearchText )
{
this.elem.value = '';
}
};
function CWebAPI( strWebAPIHost, strSecureWebAPIHost, strOAuth2Token )
{
this.m_strHost = strWebAPIHost;
this.m_strSecureWebAPIHost = strSecureWebAPIHost;
this.m_strOAuth2Token = strOAuth2Token;
}
CWebAPI.prototype.BuildURL = function( strInterface, strMethod, bSecure, strVersion )
{
if ( !strVersion )
strVersion = 'v0001';
var strURL = ( bSecure ? this.m_strSecureWebAPIHost : this.m_strHost );
strURL += strInterface + '/' + strMethod + '/' + strVersion + '/';
return strURL;
};
CWebAPI.prototype.ExecJSONP = function( strInterface, strMethod, rgParams, bSecure, strVersion, cTimeoutSecs )
{
rgParams.access_token = this.m_strOAuth2Token;
var rgJQueryParams = {
url: this.BuildURL( strInterface, strMethod, bSecure, strVersion ),
dataType: 'jsonp',
jsonp: 'jsonp', data: rgParams
};
if ( cTimeoutSecs )
rgJQueryParams['timeout'] = cTimeoutSecs * 1000;
return $J.ajax( rgJQueryParams );
/*
// using jsonp plugin instead of built-in jquery jsonp handling. this library supposedly
// works around the firefox "waiting for host..." issue, but it doesn't work.
return $J.jsonp({
url: this.BuildURL( strInterface, strMethod, bSecure, strVersion ),
callbackParameter: 'jsonp', data: rgParams
});
*/
};
// Send a "beacon", which is specifically intended for use in OnUnload events (as outsdanding AJAX/JSONP requests may be cancelled)
// as of sept 2015, only chrome and firefox support this, no iOS or IE support. Check for support before calling.
// Beacons are (per spec) always a POST request, and always include CORS headers. WebAPI respondes properly to CORS for Valve domains.
CWebAPI.prototype.ExecBeacon = function( strInterface, strMethod, rgParams, bSecure, strVersion )
{
rgParams.access_token = this.m_strOAuth2Token;
var fdParams = new FormData();
for ( var key in rgParams )
fdParams.append( key, rgParams[key] );
navigator.sendBeacon( this.BuildURL( strInterface, strMethod, bSecure, strVersion ), fdParams );
};
CWebAPI.prototype.ExecPOST = function( strInterface, strMethod, rgParams, bSecure, strVersion )
{
rgParams.access_token = this.m_strOAuth2Token;
rgParams.format = 'json';
return $J.ajax( {
url: this.BuildURL( strInterface, strMethod, bSecure, strVersion ),
type: 'POST',
data: rgParams
});
};
// register some events to dismiss popup (ie, user clicking elsewhere on the window, escape)
// cleans up event binds afterwards. clicks to children of "elemIgnore" will not dismiss popup
function RegisterPopupDismissal( dismissFunc, elemIgnore )
{
var $Ignore = $JFromIDOrElement( elemIgnore );
// delay registration by one frame so that we don't catch the event that triggered this popup.
window.setTimeout( function() {
$J(document).on('click.RegisterPopupDismissal keydown.RegisterPopupDismissal', function (event) {
if (event.keyCode && event.keyCode != 27 /* KEY_ESC */) {
return;
}
var elem = $J(event.target);
if ( $Ignore.length && $J.contains( $Ignore[0], elem[0] ) )
return;
dismissFunc();
UnregisterPopupDismissal( elemIgnore );
});
// support gamepad B button to dismiss
$Ignore.on( 'vgp_oncancel', function ( event ) {
dismissFunc();
UnregisterPopupDismissal( elemIgnore );
event.stopPropagation();
event.preventDefault();
});
}, 1 );
}
/* Cleanup function for the above RegisterPopupDismissal()
This needs to be accessible to popup owners so they can call this if they're closing the popup */
function UnregisterPopupDismissal( elemIgnore )
{
$J(document).off( '.RegisterPopupDismissal' );
if ( elemIgnore )
{
// unregister gamepad B button press handling
$JFromIDOrElement( elemIgnore ).off( 'vgp_oncancel' );
}
}
function ShowMenu( elemLink, elemPopup, align, valign, bLinkHasBorder )
{
var $Popup = $JFromIDOrElement(elemPopup);
// If we're in tablet screen mode put the menu content in a modal dialog
var bUseTabletScreenMode = window.UseTabletScreenMode && window.UseTabletScreenMode();
if ( bUseTabletScreenMode )
{
// detach this element and when the dialog closes re-attach to the document body
$Popup.detach();
var originalPopupPosition = $Popup.css( 'position' );
$Popup.css( 'position', 'static' ); // clear possible absolute positioning
$Popup.show();
ShowDialog( '', $Popup ).always(
function() {
// save it away again for later
$Popup.hide();
$Popup.css( 'position', originalPopupPosition ); // restore positioning
$J( document.body ).append( $Popup );
}
);
return;
}
var $Link = $JFromIDOrElement(elemLink);
if ( $Link.hasClass('focus') )
{
HideMenu( elemLink, elemPopup );
return;
}
AlignMenu( $Link, $Popup, align, valign, bLinkHasBorder );
ShowWithFade( $Popup );
$Link.addClass('focus');
RegisterPopupDismissal( function() { HideMenu( elemLink, elemPopup ); }, $Popup );
// If we use this control on gamepad (haven't found an example yet we're going to keep) then we'll need to
// add calling: GPOnShowingModalWindow( $Popup[0] ). See shared_responsive_adapter.js for example
}
function HideMenu( elemLink, elemPopup )
{
var $Link = $JFromIDOrElement(elemLink);
var $Popup = $JFromIDOrElement(elemPopup);
$Link.data( 'menu-active', false );
HideWithFade( $Popup );
$Link.removeClass( 'focus' );
UnregisterPopupDismissal( elemPopup );
}
function HideMenuFast( elemLink, elemPopup )
{
var $Link = $JFromIDOrElement(elemLink);
var $Popup = $JFromIDOrElement(elemPopup);
$Popup.hide();
$Link.removeClass( 'focus' );
UnregisterPopupDismissal( elemPopup );
}
function RegisterFlyout( elemLink, elemPopup, align, valign, bLinkHasBorder )
{
var $Link = $JFromIDOrElement( elemLink );
var $Popup = $JFromIDOrElement( elemPopup );
$Link.on( 'mouseenter', function( event ) {
FlyoutMenu( $Link, $Popup, align, valign, bLinkHasBorder );
});
$Link.add( $Popup ).on( 'mouseleave', function( event ) {
HideFlyoutMenu( event, $Link, $Popup );
});
}
function UseSmallScreenMenu()
{
// the new mobile app uses the small screen menu regardless of screen size
return ( window.UseSmallScreenMode && window.UseSmallScreenMode() ) || ( window.UseNewMobileAppMode && window.UseNewMobileAppMode() );
}
function FlyoutMenu( elemLink, elemPopup, align, valign, bLinkHasBorder, elemAlternateAlignTo )
{
var $Link = $JFromIDOrElement(elemLink);
var $Popup = $JFromIDOrElement(elemPopup);
if ( !$Popup.is(':visible') || $Popup.css('opacity') < 1.0 )
{
if ( $Popup.hasClass( 'responsive_slidedown') && UseSmallScreenMenu() )
{
$Popup.stop().slideDown();
}
else
{
// Responsive mode calls slideDown, which applies position: relative to the element and we need to clear it.
// This will only happen if the user interacted with the menu in the responsive mode, then resized
// their window to be large and hovered the elements.
$Popup.css( { position: '' } );
AlignMenu( $Link, $Popup, align, valign, bLinkHasBorder, elemAlternateAlignTo || null );
ShowWithFade( $Popup );
}
$Link.addClass('focus');
}
}
function HideFlyoutMenu( event, elemLink, elemPopup )
{
var $Link = $JFromIDOrElement(elemLink);
var $Popup = $JFromIDOrElement(elemPopup);
if ( !$Link.hasClass('focus') )
return;
if ( event )
{
var reltarget = $J( event.relatedTarget );
if ( !reltarget.length ||
( $Link.length && $J.contains( $Link[0], reltarget[0] ) ) ||
( $Popup.length && $J.contains( $Popup[0], reltarget[0] ) ) ||
$Link.is( reltarget ) )
return;
}
// start hiding in a little bit, have to let the fade in animation start before we can cancel it
if ( $Popup.hasClass( 'responsive_slidedown') && UseSmallScreenMenu() )
$Popup.stop().slideUp();
else
window.setTimeout( function() { HideWithFade( $Popup ); }, 33 );
$Link.removeClass('focus');
$J(document).off('.FlyoutDismiss');
}
function AlignMenu( elemLink, elemPopup, align, valign, bLinkHasBorder, elemAlternateAlignTo )
{
var align = align ? align : 'left';
// alternate align to is used to align the genre menu on the store; it's only used for horizontal alignment,
// trying to align to it vertically messes up responsive view.
var $LinkVertical = $JFromIDOrElement(elemLink);
var $LinkHorizontal = elemAlternateAlignTo ? $JFromIDOrElement(elemAlternateAlignTo) : $LinkVertical;
var $Popup = $JFromIDOrElement(elemPopup);
var offsetLinkVertical = $LinkVertical.offset();
var offsetLinkHorizontal = $LinkHorizontal.offset();
var nWindowScrollTop = $J(window).scrollTop();
var nViewportHeight = $J(window).height();
var nLinkViewportTop = offsetLinkVertical.top - nWindowScrollTop;
// add a little bit of padding so we don't position it flush to an edge if possible
var nPopupHeight = $Popup.height() + 8;
if ( !valign )
{
//if there's not enough room between our spot and the top of the document, we definitely want to drop down
if ( nWindowScrollTop + offsetLinkVertical.top < nPopupHeight )
{
valign = 'bottom';
}
else
{
var nSpaceAbove = nLinkViewportTop;
var nSpaceBelow = nViewportHeight - ( nLinkViewportTop + $LinkVertical.height() );
//otherwise we only want to drop down if we've got enough space below us (measured based on view area)
// or if there's not enough space above to pop in either direction and there's more space below
if ( nSpaceBelow > nPopupHeight || ( nSpaceAbove < nPopupHeight && nSpaceBelow > nSpaceAbove ) )
valign = 'bottom';
else
valign = 'top';
}
}
var borderpx = bLinkHasBorder ? 1 : 0;
var shadowpx = $Popup.hasClass( 'popup_block_new' ) ? 0 : 12;
var offsetLeft = 0;
if ( align == 'left' )
{
//elemPopup.style.left = ( elemLink.positionedOffset()[0] - 12 ) + 'px';
offsetLeft = -shadowpx - borderpx;
}
else if ( align == 'right' )
{
//elemPopup.style.left = ( elemLink.positionedOffset()[0] + elemLink.getWidth() - elemPopup.getWidth() + 13 ) + 'px';
offsetLeft = $LinkHorizontal.outerWidth() - $Popup.outerWidth() + shadowpx + borderpx;
}
else if ( align == 'leftsubmenu' )
{
//elemPopup.style.left = ( elemLink.positionedOffset()[0] - elemPopup.getWidth() + 12 ) + 'px';
offsetLeft = -$Popup.outerWidth() + shadowpx - borderpx;
}
else if ( align == 'rightsubmenu' )
{
//elemPopup.style.left = ( elemLink.positionedOffset()[0] + elemLink.getWidth() - 12 ) + 'px';
offsetLeft = $LinkHorizontal.outerWidth() - shadowpx + 2 * borderpx;
}
var offsetTop = 0;
if ( valign == 'bottom' )
{
//elemPopup.style.top = ( elemLink.positionedOffset()[1] + elemLink.getHeight() - 12 ) + 'px';
offsetTop = $LinkVertical.outerHeight() - shadowpx;
}
else if ( valign == 'top' )
{
//elemPopup.style.top = ( elemLink.positionedOffset()[1] - elemPopup.getHeight() + 12 ) + 'px';
offsetTop = -$Popup.outerHeight() + shadowpx;
}
else if ( valign == 'bottomsubmenu' )
{
//elemPopup.style.top = ( elemLink.positionedOffset()[1] - 12 ) + 'px';
offsetTop = -shadowpx;
}
var bPopupHidden = !$Popup.is(':visible');
if ( bPopupHidden )
{
// IE can't do this with display: none elements
$Popup.css( 'visibility', 'hidden' );
$Popup.show();
}
$Popup.offset( {
top: Math.max( offsetLinkVertical.top + offsetTop, 0 ),
left: Math.max( offsetLinkHorizontal.left + offsetLeft, 0 )
});
if ( bPopupHidden )
{
// restore visibility
$Popup.hide();
$Popup.css( 'visibility', 'visible' );
}
}
function BindAutoFlyoutEvents()
{
var fnShowFlyout = function( $Tab, bIsHover, bTakeFocus )
{
var $Content = $J('#' + $Tab.data('flyout') );
var bResponsiveSlidedownMenu = UseSmallScreenMenu() && $Content.hasClass('responsive_slidedown');
if ( !$Content.length || $Content.data('flyout-event-running') ||
(bIsHover && bResponsiveSlidedownMenu ) )
return;
$Content.data( 'flyout-event-running', true );
window.setTimeout( function() { $Content.data('flyout-event-running', false ); }, 1 );
if ( $Content.is(':visible') )
{
if ( !bIsHover )
HideFlyoutMenu( null, $Tab, $Content );
return;
}
if ( !$Content.data('flyout-events-bound') )
{
$Content.on('mouseleave.Flyout', function( e ) {
if ( UseSmallScreenMenu() && $Content.hasClass('responsive_slidedown') )
return;
if ( $Tab.is( e.relatedTarget ) || $J.contains( $Tab[0], e.relatedTarget ) )
return;
HideFlyoutMenu( null, $Tab, $Content );
});
$Content.add($Tab).on('vgp_oncancel', function( e ) {
HideFlyoutMenu( null, $Tab, $Content );
if ( typeof GPNavFocusChild != 'undefined' )
GPNavFocusChild( $Tab );
});
$Content.on('vgp_onblur', function( e ) {
var node = e.originalEvent.detail.focusedNode;
console.log( node );
if ( !node || !$J.contains( e.currentTarget, node.Element ) )
HideFlyoutMenu( null, $Tab, $Content );
});
$Content.data('flyout-events-bound', true );
}
FlyoutMenu( $Tab, $Content, $Tab.data('flyout-align'), $Tab.data('flyout-valign'), false, $Tab.data('flyout-align-to-element' ) );
if ( bTakeFocus && typeof GPNavFocusChild !== 'undefined' )
{
window.setTimeout( function() {
console.log( 'Request focus in', $Content[0] );
GPNavFocusChild( $Content );
}, 5 );
}
if ( window.UseTouchFriendlyMode && window.UseTouchFriendlyMode() )
{
window.setTimeout( function() {
$J(document).on('click.FlyoutDismiss', function(e) {
if ( $J.contains( $Content[0], e.target ) || $Content.is( e.target ) )
return;
HideFlyoutMenu( null, $Tab, $Content );
e.preventDefault();
});
}, 1 );
}
};
$J(document).on( 'mouseenter.Flyout click.Flyout', '.flyout_tab', function(e) {
var $Tab = $J(this);
var msDelay = $Tab.data('flyout-delay');
var bIsHover = e.type == 'mouseenter';
if ( !bIsHover || !msDelay )
{
fnShowFlyout( $Tab, bIsHover );
}
else if ( !$Tab.data( 'iFlyoutInterval' ) )
{
var fnCleanupFlyoutInterval = function() {
$Tab.removeData( 'iFlyoutInterval' );
$Tab.off( 'mouseleave.FlyoutCancel' );
}
$Tab.on( 'mouseleave.FlyoutCancel', function(e) {
window.clearTimeout( $Tab.data('iFlyoutInterval') );
fnCleanupFlyoutInterval();
});
$Tab.data( 'iFlyoutInterval', window.setTimeout( function() {
fnCleanupFlyoutInterval();
fnShowFlyout( $Tab, bIsHover );
}, msDelay ) );
}
});
$J(document).on('mouseleave.Flyout', '.flyout_tab', function(e) {
var $Tab = $J(this);
var $Content = $J('#' + $Tab.data('flyout') );
var bResponsiveSlidedownMenu = UseSmallScreenMenu() && $Content.hasClass('responsive_slidedown');
if ( !$Content.length || $Content.data('flyout-event-running') || bResponsiveSlidedownMenu ||
$Content.is( e.relatedTarget ) || $J.contains( $Content[0], e.relatedTarget ) )
return;
if ( $Content.is(':visible') )
{
HideFlyoutMenu( null, $Tab, $Content );
return;
}
});
$J(document).on('vgp_onok', '.flyout_tab', function( e ) {
fnShowFlyout( $J(this), false, true );
e.stopPropagation();
});
}
function PollOnUserActionAfterInterval( strNamespace, nPollInterval, fnCallback, nPollImmediateInterval )
{
var bCallbackInvoked = false;
var tsLastUserAction = $J.now();
var fnInvokeCallbackOnNextUserAction;
var strEvents = ['touchstart','mousemove','focus','scroll',''].join( '.' + strNamespace + ' ' );
var fnDoPoll = function() {
if ( !bCallbackInvoked )
fnCallback();
bCallbackInvoked = true;
fnInvokeCallbackOnNextUserAction();
};
fnInvokeCallbackOnNextUserAction = function() {
window.setTimeout( function() {
bCallbackInvoked = false;
if ( nPollImmediateInterval && $J.now() <= tsLastUserAction + nPollImmediateInterval )
{
fnDoPoll();
}
else
{
$J(window).on( strEvents, function() {
$J(window ).off( '.' + strNamespace );
tsLastUserAction = $J.now();
fnDoPoll();
});
}
}, nPollInterval );
};
fnInvokeCallbackOnNextUserAction();
}
var DELAY_BETWEEN_NOTIFICATION_COUNT_POLLS_MS = 30 * 1000;
function EnableNotificationCountPolling()
{
var $NotificationItems = $J('.notification_ctn');
var $NotificationTotalCounts = $J('.notification_count_total_ctn');
if ( $NotificationItems.length || $NotificationTotalCounts.length )
{
PollOnUserActionAfterInterval( 'NotificationCountPoll', DELAY_BETWEEN_NOTIFICATION_COUNT_POLLS_MS, UpdateNotificationCounts );
}
}
function UpdateNotificationCounts()
{
var $NotificationItems = $J('.notification_ctn');
var $NotificationTotalCounts = $J('.notification_count_total_ctn');
if ( $NotificationItems.length || $NotificationTotalCounts.length )
{
$J.ajax( GetDefaultCommunityAJAXParams( 'actions/GetNotificationCounts', 'GET' ) ).done( function( data ) {
var notifications = data && data.notifications;
if ( notifications )
{
$NotificationItems.each( function() {
var $NotificationItem = $J(this);
var nNotificationType = $NotificationItem.data('notification-type');
var cNotifications = notifications[nNotificationType] || 0;
$NotificationItem.find('.notification_count' ).text( v_numberformat( cNotifications ) );
if ( cNotifications > 0 )
$NotificationItem.addClass('active_inbox_item');
else
$NotificationItem.removeClass('active_inbox_item');
var $CountStrings = $NotificationItem.find( '.notification_count_string' );
if ( $CountStrings.length )
{
if ( cNotifications == 1 )
{
$CountStrings.filter('.plural' ).hide();
$CountStrings.filter('.singular' ).show();
}
else
{
$CountStrings.filter('.singular' ).hide();
$CountStrings.filter('.plural' ).show();
}
}
});
if ( !notifications[ 11 ] )
{
$J('.global_header_account_alert').hide();
}
var cTotalNotifications = 0;
for ( var type in notifications )
cTotalNotifications += ( notifications[type] || 0 );
$NotificationTotalCounts.find('.notification_count' ).text( v_numberformat( cTotalNotifications ) );
if ( cTotalNotifications > 0 )
{
$NotificationTotalCounts.removeClass('no_notifications' ).addClass('has_notifications');
}
else
{
$NotificationTotalCounts.removeClass('has_notifications' ).addClass('no_notifications');
}
}
});
}
}
function PostToURLWithSession( url, rgParams )
{
var $Form = $J('<form/>', {'action': url, 'method': 'POST' } );
// site must set this js var or pass to this function
if ( typeof g_sessionID != 'undefined' )
$Form.append( $J('<input/>', {'type': 'hidden', 'name': 'sessionid', 'value': g_sessionID } ) );
if ( rgParams )
{
for ( var name in rgParams )
$Form.append( $J('<input/>', {'type': 'hidden', 'name': name, 'value': rgParams[name] } ) );
}
$Form.appendTo( 'body' );
$Form.submit();
}
function ShowWithFade( elem, speed )
{
var $Elem = $JFromIDOrElement(elem);
$Elem.stop();
$Elem.fadeTo( speed || 200, 1.0 ); //fadeTo rather than fadeIn in case it was already in a fade
}
function HideWithFade( elem, speed )
{
var $Elem = $JFromIDOrElement(elem);
$Elem.stop();
$Elem.fadeOut( speed || 200 );
}
function LaunchWebChat( params, paramsFriendsUI )
{
var winChat = window.open( '', 'SteamWebChat', 'height=790,width=1015,resize=yes,scrollbars=yes' );
if ( !winChat )
{
// popup blocked - this sometimes happens when chat is initiated from the store. just roll with it.
return;
}
var bNewPopup = false;
var bCrossOrigin = false;
try {
bNewPopup = (winChat.location == 'about:blank' );
}
catch ( e )
{
// cross-origin exception on http pages etc
bCrossOrigin = true;
}
if ( bNewPopup )
{
if ( params )
SetValueLocalStorage( 'rgChatStartupParam', V_ToJSON( params ) );
if ( paramsFriendsUI )
{
var fnBoundMessageListener = function( event )
{
if ( event.source == winChat && event.data == "FriendsUIReady" &&
event.origin == 'https://steamcommunity.com')
{
winChat.postMessage( paramsFriendsUI, 'https://steamcommunity.com/' );
window.removeEventListener( 'message', fnBoundMessageListener );
}
};
window.addEventListener( 'message', fnBoundMessageListener );
}
// created a new window, set the url
winChat.location = 'https://steamcommunity.com/chat/';
}
else
{
if ( params && !bCrossOrigin && winChat.OnWebchatLaunchURL )
winChat.OnWebchatLaunchURL( params );
if ( paramsFriendsUI )
winChat.postMessage( paramsFriendsUI, 'https://steamcommunity.com/' );
}
winChat.focus();
}
function ShowSupportAlerts(url)
{
window.open( url, 'SupportAlerts', 'height=700,width=700,resize=yes,scrollbars=yes' );
}
function UnlockFamilyView( strURL )
{
window.location = strURL;
}
function LockFamilyView( bStore )
{
var urlFirst = '';
var urlSecond = '';
if ( bStore )
{
urlFirst = 'https://store.steampowered.com/';
urlSecond = 'https://steamcommunity.com/';
}
else
{
urlFirst = 'https://steamcommunity.com/';
urlSecond = 'https://store.steampowered.com/';
}
ShowConfirmDialog( '返回到家庭监护',
'您确定要返回家庭监护吗?',
'返回到家庭监护'
).done( function() {
ShowBlockingWaitDialog( '返回到家庭监护' );
$J.ajax( {
type: "POST",
url: urlFirst + '/parental/ajaxlock',
crossDomain: true,
xhrFields: { withCredentials: true },
complete: function( jqHXR, textStatus )
{
$J.ajax( {
type: "POST",
url: urlSecond + '/parental/ajaxlock',
crossDomain: true,
xhrFields: { withCredentials: true },
complete: function( jqHXR, textStatus )
{
window.location = urlFirst;
}
} );
}
} );
} );
}
function setTimezoneCookies()
{
var now = new Date();
var expire = new Date();
// One year expiration, this way we don't need to wait at least one page
// load to have accurate timezone info each session, but only each time the user
// comes with cleared cookies
expire.setTime( now.getTime() + 3600000*24*365 );
var tzOffset = now.getTimezoneOffset() * -1 * 60;
var isDST = 0;
var sameSite = '';
if ( window.location.protocol == 'https' )
sameSite = '; Secure; SameSite=None;'
document.cookie = "timezoneOffset=" + tzOffset + "," + isDST + ";expires="+expire.toGMTString() + ";path=/" + sameSite;
}
function FlushStyleChanges( element )
{
$J( element ).css( 'opacity');
}
var k_EScrollbarDirectionVertical = 1;
var k_EScrollbarDirectionHorizontal = 2;
window.VScrollbar = function( eleTarget, eleHandle, direction )
{
this.m_eleHandle = eleHandle;
this.m_eleTarget = eleTarget;
var instance = this;
var propOffset, propSize, propOuterSize, propDimension, propPage, directionInvert, propOffsetEvent;
if( direction == k_EScrollbarDirectionVertical)
{
propOffset = 'top';
propSize = 'height';
propOuterSize = 'outerHeight';
propDimension = 'y';
propPage = 'pageY';
propOffsetEvent = 'offsetY';
} else if( direction == k_EScrollbarDirectionHorizontal )
{
propOffset = 'left';
propSize = 'width';
propOuterSize = 'outerWidth';
propDimension = 'x';
propPage = 'pageX';
propOffsetEvent = 'offsetX';
this.m_eleTarget.first().css({'white-space': 'nowrap'});
}
// Set up some CSS properties we need
this.m_eleHandle.css({position: 'absolute'});
if( this.m_eleHandle.parent().css('position') == 'static' )
this.m_eleHandle.parent().css({position: 'relative'}); // Needs to be relative or absolute, only set it if we didn't do it in CSS
this.m_eleTarget.css({position: 'relative', float: 'left'});
this.m_eleTarget.css(propOffset, '0px');
this.m_eleTarget.addClass('animating');
this.m_eleTarget.parent().addClass('v_scrollbar_target');
this.m_eleTarget.parent().css({position: 'relative', overflow: 'hidden'});
this.m_nDesiredPosition = 0;
this.m_flPercent = 0;
var funcUpdate = function( bDisableTransitions )
{
var bScrolled = true;
if( instance.m_eleTarget[propSize]() - instance.m_eleTarget.parent()[propSize]() <= 0 )
{
instance.m_eleHandle.parent().addClass('disabled');
instance.m_flPercent = 0;
bScrolled = false;
} else
instance.m_eleHandle.parent().removeClass('disabled');
if( instance.m_flPercent < 0 )
instance.m_flPercent = 0;
if( instance.m_flPercent > 1 )
instance.m_flPercent = 1;
var percent = instance.m_flPercent;
if( bDisableTransitions )
instance.DisableTransitions();
instance.m_nDesiredPosition = -percent * ( instance.m_eleTarget[propSize]() - instance.m_eleTarget.parent()[propSize]());
// Update container
instance.m_eleTarget[0].style[propOffset] = instance.m_nDesiredPosition + 'px';
// Update scroll handle
var handleMax = instance.m_eleHandle.parent()[propSize]() - instance.m_eleHandle[propOuterSize]();
instance.m_eleHandle[0].style[propOffset] = ( percent * handleMax ) + 'px';
if( bDisableTransitions )
instance.EnableTransitions();
return bScrolled;
};
var funcMouseMove = function( event ) {
var localDimension = event[propPage] - instance.m_eleHandle.parent().offset()[propOffset];
var localMax = instance.m_eleHandle.parent()[propSize]() - instance.m_eleHandle[propOuterSize]();
var percent = localDimension / localMax;
instance.m_flPercent = percent;
if( funcUpdate(true) )
event.preventDefault();
};
$J(eleTarget).bind('mousewheel DOMMouseScroll',function( event ){
var delta = event.originalEvent.wheelDelta || event.originalEvent.detail * -12;
var localY = instance.m_nDesiredPosition * -1;
var localMax = instance.m_eleTarget[propOuterSize]() - instance.m_eleTarget.parent()[propSize]();
if( localY <= 0 && delta > 0 || localY == localMax && delta < 0 )
return;
localY -= delta;
if( localY < 0 ) localY = 0;
if( localY > localMax ) localY = localMax;
var percent = localY / localMax;
instance.m_flPercent = percent;
if( funcUpdate() )
event.preventDefault();
});
$J(eleHandle.parent()).bind('click',function( event ){
var localY = instance.m_eleTarget.position()[propOffset] * -1;
var localMax = instance.m_eleTarget[propOuterSize]() - instance.m_eleTarget.parent()[propSize]();
var step = instance.m_eleTarget.parent()[propSize]();
if( event[propOffsetEvent] < instance.m_eleHandle.position()[propOffset] )
step *= -1;
localY += step;
if( localY < 0 ) localY = 0;
if( localY > localMax ) localY = localMax;
var percent = localY / localMax;
instance.m_flPercent = percent;
if( funcUpdate() )
event.preventDefault();
});
eleHandle.mousedown(function( event ){
$J(window).bind('mousemove.scroll', funcMouseMove);
event.stopPropagation();
});
eleHandle.click(function( event ){
event.stopPropagation();
});
$J(window).mouseup(function( event ){
$J(window).unbind('mousemove.scroll');
event.stopPropagation();
});
// Resets scroll position to 0 and updates the window. Useful when adding/removing elements or resizing the scroll area
this.Reset = function() {
instance.m_flPercent = 0;
return funcUpdate(true);
};
// Ensures target element is visible. This is taken from position() (NOT OFFSET) so the element must be a direct child of the scroll area
this.EnsureVisible = function( ele ) {
var $ele = $J(ele);
var eleSize = $ele[propOuterSize](true);
var minEdge = $ele.position()[propOffset] - (eleSize * 0.5);
var maxEdge = minEdge + (eleSize * 2);
var viewportMax = instance.m_eleTarget[propOuterSize]() - instance.m_eleTarget.parent()[propSize]();
var viewportSize = instance.m_eleTarget.parent()[propSize]();
var viewportPosition = instance.m_eleTarget.position()[propOffset] * -1;
if( viewportPosition > minEdge )
instance.m_flPercent = minEdge / viewportMax;
else if( ( viewportPosition + viewportSize ) < maxEdge )
instance.m_flPercent = ( maxEdge - viewportSize ) / viewportMax;
return funcUpdate();
};
this.EnableTransitions = function() {
instance.m_eleTarget.addClass('animating');
};
this.DisableTransitions = function() {
instance.m_eleTarget.removeClass('animating');
};
funcUpdate(true);
};
function InitAutoComplete( elInput, fnSearch, fnOnChange )
{
return new CAutoComplete( elInput, fnSearch, fnOnChange );
}
function CAutoComplete( elInput, fnSearch, fnOnChange )
{
this.m_bSuppresseNextKeyUp = false;
this.m_hSearchTimeout = 0;
this.m_strLastSearch = '';
this.m_$Input = $J( elInput );
this.m_fnSearch = fnSearch;
this.m_fnOnChange = fnOnChange;
var strName = this.m_$Input.attr( 'name' );
if ( !strName )
strName = 'unknown';
strName = strName + '_autocomplete';
this.m_$Popup = $J ( '<div class="' + strName + '"></div>' );
this.m_$Popup.css( 'width', this.m_$Input.outerWidth() + 'px' );
this.m_$Input.after( this.m_$Popup );
this.m_$Popup.hide();
var _this = this;
this.m_$Input.on( 'keydown.autocomplete', function( event ) { _this.OnInputKeyDown( event ); } );
this.m_$Input.on( 'keyup.autocomplete', function( event ) { _this.OnInputKeyUp( event ); } );
this.m_$Input.on( 'blur.autocomplete', function( event ) { _this.OnInputBlur( event ); } );
this.m_$Input.on( 'change input paste', function( event ) { _this.OnInputChange( event ); } );
this.m_$Popup.on( 'mousedown.autocomplete', function( event ) { _this.m_$Popup.data( 'mousedown', true ); } );
this.m_$Popup.on( 'mouseup.autocomplete', function( event ) { _this.m_$Popup.data( 'mousedown', false ); } );
}
CAutoComplete.KEY_ENTER = 13;
CAutoComplete.KEY_UP = 38;
CAutoComplete.KEY_DOWN = 40;
CAutoComplete.prototype.OnInputChange = function( event )
{
if ( this.m_fnOnChange )
this.m_fnOnChange( this.m_$Input, null );
if ( this.m_hSearchTimeout == 0 )
{
var _this = this;
var fnDoSearch = function()
{
_this.m_hSearchTimeout = 0;
var strSearch = _this.m_$Input.val();
if ( strSearch == _this.m_strLastSearch )
return;
_this.m_strLastSearch = strSearch;
_this.m_fnSearch( _this.m_$Input, strSearch, function( $Contents ) { _this.SetPopupContents( $Contents ); } );
};
this.m_hSearchTimeout = window.setTimeout( fnDoSearch, 300 );
}
}
CAutoComplete.prototype.SetPopupContents = function( $Contents )
{
if ( !$Contents )
{
this.ClosePopup();
return;
}
this.m_$Popup.html( '' );
this.m_$Popup.append( $Contents );
var _this = this;
this.m_$Popup.children().on( 'click.autocomplete', function( event ) { _this.SelectSuggestion( $J( this ) ); } );
this.m_$Popup.show();
}
CAutoComplete.prototype.OnInputBlur = function( event )
{
if ( !this.m_$Popup.data( 'mousedown' ) )
this.ClosePopup();
}
CAutoComplete.prototype.SelectSuggestion = function( $Suggestion )
{
if ( $Suggestion.length == 0 )
return;
this.m_$Input.val( $Suggestion.data( 'suggestion' ) );
if ( this.m_fnOnChange )
this.m_fnOnChange( this.m_$Input, $Suggestion );
this.ClosePopup();
this.m_$Input.focus();
}
CAutoComplete.prototype.ClosePopup = function()
{
if ( this.m_hSearchTimeout != 0 )
{
window.clearTimeout( this.m_hSearchTimeout );
this.m_hSearchTimeout = 0;
}
this.m_strLastSearch = '';
this.m_$Popup.hide();
}
CAutoComplete.prototype.OnInputKeyDown = function( event )
{
var $CurSuggestion = this.m_$Popup.children('.focus');
var $NewSuggestion = $J();
if ( event.keyCode == CAutoComplete.KEY_ENTER )
{
this.SelectSuggestion( $CurSuggestion );
this.m_bSuppressNextKeyUp = true;
event.preventDefault();
return;
}
if ( event.keyCode == CAutoComplete.KEY_UP || event.keyCode == CAutoComplete.KEY_DOWN )
{
if ( event.keyCode == CAutoComplete.KEY_UP )
{
if ( $CurSuggestion.length )
$NewSuggestion = $CurSuggestion.prev();
if ( !$NewSuggestion.length )
$NewSuggestion = this.m_$Popup.children( 'div:last-child' );
}
else
{
if ( $CurSuggestion.length )
$NewSuggestion = $CurSuggestion.next();
if ( !$NewSuggestion.length )
$NewSuggestion = this.m_$Popup.children( 'div:first-child' );
}
if ( $NewSuggestion.length )
{
$CurSuggestion.removeClass( 'focus' );
$NewSuggestion.addClass( 'focus' );
}
event.preventDefault();
}
}
CAutoComplete.prototype.OnInputKeyUp = function( event )
{
if ( this.m_bSuppressNextKeyUp && event.keyCode == CAutoComplete.KEY_ENTER )
{
this.m_bSuppressNextKeyUp = false;
event.preventDefault();
}
}
function GetCurrentScrollPercentage()
{
var s = $J(window).scrollTop();
var d = $J(document).height();
var c = $J(window).height();
var scrollPercent = (s / (d-c)) * 100;
return scrollPercent;
}
// @elemID id of the element
// @fixedOffsetTop offset from the top when fixed
// @bScrollWithPageIfTooTall if the element is taller than the page, then it will "scroll" with the page if this is true
// @docHeightOffset if bScrollWithPageIfTooTall is set to true, then this is how much the document height is reduced by (recommend this to be 130 for the typical footer)
function FixedElementOnScrollWrapper ( elemID, fixedOffsetTop, bScrollWithPageIfTooTall, docHeightOffset, params )
{
params = $J.extend( {
fixedClass: null
}, params );
this.fixedElement = $( elemID );
this.$FixedElement = $JFromIDOrElement( elemID );
this.fixedOffsetTop = typeof fixedOffsetTop != "undefined" ? fixedOffsetTop : 0;
this.bScrollWithPageIfTooTall = typeof bScrollWithPageIfTooTall != "undefined" ? bScrollWithPageIfTooTall : false;
this.docHeightOffset = typeof docHeightOffset != "undefined" ? docHeightOffset : 0;
this.homePosn = { x: this.fixedElement.cumulativeOffset()[0], y: this.fixedElement.cumulativeOffset()[1] };
this.fixedClass = params.fixedClass;
this.$FixedElementPadding = $J('<div/>', {'class': 'FixedElementOnScrollWrapper_padding', 'id': 'ScrollWrapperPadding_' + elemID } ).hide();
this.fixedElement.insert( { before: this.$FixedElementPadding[0] } );
var _this = this;
$J(window).on( 'scroll.FixedElementOnScrollWrapper hashchange.FixedElementOnScrollWrapper', function() { _this.handleScroll() } );
$J(window).on( 'resize.FixedElementOnScrollWrapper', function() { _this.handleScroll( true /* force recalc */ ) } );
this.handleScroll();
}
FixedElementOnScrollWrapper.prototype.BIsFixed = function()
{
if ( this.fixedClass )
return this.$FixedElement.hasClass( this.fixedClass );
else
return this.$FixedElement.css( 'position' ) == 'fixed';
}
FixedElementOnScrollWrapper.prototype.handleScroll = function( bForceRecalc )
{
this.scrollOffset = document.viewport.getScrollOffsets().top;
var offsetTop = this.fixedOffsetTop + GetResponsiveHeaderFixedOffsetAdjustment();
if ( bForceRecalc && !this.BIsFixed() )
this.homePosn = { x: this.fixedElement.cumulativeOffset()[0], y: this.fixedElement.cumulativeOffset()[1] };
if ( this.scrollOffset > ( this.homePosn.y - offsetTop ) )
{
if ( !this.BIsFixed() || bForceRecalc )
{
if ( this.fixedClass )
this.$FixedElement.addClass( this.fixedClass );
else
this.$FixedElement.css( 'position', 'fixed' );
this.fixedElement.style.top = offsetTop + 'px';
this.fixedElement.style.left = this.homePosn.x;
// jquery show() sets display to block, which prevents css from hiding this element if needed.
this.$FixedElementPadding.css( 'display', '' );
this.$FixedElementPadding.css( 'height', this.fixedElement.getHeight() + 'px' );
}
if ( this.bScrollWithPageIfTooTall )
{
// this forces the element to scroll off the page, but there's enough that isn't on the page, "scroll" this guy percentage-wise
var elemHeight = this.fixedElement.getHeight() + offsetTop;
if ( elemHeight > document.viewport.getHeight() )
{
var currentScrollPercentage = GetCurrentScrollPercentage();
var heightDiff = elemHeight - ( document.viewport.getHeight() - this.docHeightOffset );
offsetTop -= Math.floor( heightDiff * currentScrollPercentage / 100 );
this.fixedElement.style.top = offsetTop + 'px';
}
}
}
else
{
if ( this.BIsFixed() )
{
if ( this.fixedClass )
this.$FixedElement.removeClass( this.fixedClass );
else
this.$FixedElement.css( 'position', '' );
this.$FixedElement.css( 'top', '' ).css( 'left', '' );
this.$FixedElementPadding.css( 'display', 'none' );
}
}
}
// general text suggestion control
// fnSuggestForTerm will be called with two values, the current string to get suggestions for,
// and the callback to invoke with the new values. You should always invoke the callback per call
// to fnSuggestForTerm, but can delay due to ajax if needed.
function CTextInputSuggest( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass )
{
this.Init( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass );
}
CTextInputSuggest.prototype.Init = function( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass )
{
if( !strCssClass )
strCssClass = 'popup_block_new';
this.m_bHaveSuggestions = false;
this.m_$Input = $InputElement;
this.m_fnSuggestForTerm = fnSuggestForTerm;
this.m_fnOnSuggest = fnOnSuggest || function( term ) {};
this.m_strLastVal = '';
this.m_align = 'left';
this.m_valign = 'bottom';
this.m_$Focus = $J();
this.m_strLastFocusVal = null;
this.m_nNextRequestID = 1;
this.m_nRequestIDRecvd = 0;
this.m_$SuggestionsCtn = $J('<div/>', {'class': strCssClass, style: 'display: none;' } );
this.m_$Suggestions = $J('<div/>', {'class': 'popup_body popup_menu' } );
this.m_$SuggestionsCtn.append( this.m_$Suggestions );
this.m_$SuggestionsCtn.hide();
$J(document.body).append( this.m_$SuggestionsCtn );
var zIndex = 200; //normal popup zindex
this.m_$Input.parents().each( function() {
var zIndexParent = $J(this).css('zIndex');
if ( zIndexParent != 'auto' && zIndexParent != 0 )
{
zIndex = zIndexParent;
}
});
this.m_$SuggestionsCtn.css( 'zIndex', zIndex + 20 );
var _this = this;
this.m_$Input.on( 'keyup.CTextInputSuggest click.CTextInputSuggest', function( event ) { _this.OnTextChanged( event ) } );
this.m_$Input.on( 'paste.CTextInputSuggest cut.CTextInputSuggest', function() { window.setTimeout( function() { _this.OnTextChanged() }, 1 ); } );
this.m_$Input.on( 'keydown.CTextInputSuggest', function( event ) { _this.OnKeyDown( event ) } );
this.m_$Input.on( 'focus.CTextInputSuggest', function() { _this.ShowSuggestions() } );
this.m_$Input.on( 'blur.CTextInputSuggest', function() { _this.HideSuggestions() } );
}
CTextInputSuggest.prototype.SetAlignment = function( align, valign )
{
this.m_align = align || 'left';
this.m_valign = valign || 'bottom';
};
CTextInputSuggest.prototype.SetSuggestionsContainerId = function( strSuggestionsCtnId )
{
this.m_$SuggestionsCtn.attr( 'id', strSuggestionsCtnId );
};
CTextInputSuggest.prototype.ShowSuggestions = function()
{
if ( !this.m_$SuggestionsCtn.find(':visible').length && this.m_bHaveSuggestions )
{
AlignMenu( this.m_$Input[0], this.m_$SuggestionsCtn[0], this.m_align, this.m_valign, true );
this.m_$SuggestionsCtn.fadeIn( 'fast' );
}
};
CTextInputSuggest.prototype.HideSuggestions = function()
{
if ( this.m_bHaveSuggestions )
this.m_$SuggestionsCtn.fadeOut( 'fast' );
else
this.m_$SuggestionsCtn.hide();
};
CTextInputSuggest.prototype.OnSuggestionSelected = function( $Suggestion )
{
this.m_$Input.val( $Suggestion.text() );
this.m_bHaveSuggestions = false;
this.m_$Focus = $J();
this.HideSuggestions();
this.m_fnOnSuggest( $Suggestion.text() );
};
CTextInputSuggest.prototype.SetSuggestions = function( rgSuggestions )
{
var strLastFocus = this.m_strLastFocusVal;
this.m_$Suggestions.empty();
this.m_$Focus = $J();
this.m_strLastFocus = null;
if ( rgSuggestions && rgSuggestions.length )
{
var _this = this;
for ( var i = 0; i < rgSuggestions.length; i++ )
{
var $Suggestion = $J('<div/>', {'class': 'suggestion_item popup_menu_item' } );
$Suggestion.text( rgSuggestions[i] );
$Suggestion.click( $J.proxy( this.OnSuggestionSelected, this, $Suggestion ) );
$Suggestion.mouseenter( $J.proxy( this.SetFocus, this, $Suggestion ) );
this.m_$Suggestions.append( $Suggestion );
if ( rgSuggestions[i] == strLastFocus )
this.SetFocus( $Suggestion );
}
this.m_bHaveSuggestions = true;
this.ShowSuggestions();
}
else
{
this.m_bHaveSuggestions = false;
this.HideSuggestions();
}
};
CTextInputSuggest.prototype.OnTextChanged = function( event )
{
if ( event && ( event.which == 13 || event.which == 27 ) )
return;
var value = this.m_$Input.val();
if ( value != this.m_strLastVal )
{
var _this = this;
var nRequestID = this.m_nNextRequestID++;
this.m_fnSuggestForTerm( value, function( rgSuggestions ) {
// we've already recieved a more recent response, this is out-of-order
if ( _this.m_nRequestIDRecvd > nRequestID )
return;
_this.m_nRequestIDRecvd = nRequestID;
_this.SetSuggestions( rgSuggestions );
} );
this.m_strLastVal = value;
}
};
CTextInputSuggest.prototype.OnKeyDown = function( event )
{
if ( event.which == 27 )
{
this.HideSuggestions();
}
else if ( this.m_bHaveSuggestions )
{
var $NewSuggestion = null;
if ( event.which == 13 )
{
if ( this.m_$Focus.length && this.m_bHaveSuggestions )
{
this.OnSuggestionSelected( this.m_$Focus );
event.stopPropagation();
event.preventDefault();
}
}
else if ( event.which == 38 /* up arrow */ )
{
event.preventDefault();
if ( this.m_$Focus.length )
$NewSuggestion = this.m_$Focus.prev();
if ( !$NewSuggestion )
$NewSuggestion = this.m_$Suggestions.children().last();
}
else if ( event.which == 40 /* down arrow */ )
{
event.preventDefault();
if ( this.m_$Focus.length )
$NewSuggestion = this.m_$Focus.next();
if ( !$NewSuggestion )
$NewSuggestion = this.m_$Suggestions.children().first();
}
if ( $NewSuggestion )
this.SetFocus( $NewSuggestion );
}
};
CTextInputSuggest.prototype.SetFocus = function( $Suggestion )
{
this.m_$Focus.removeClass( 'focus' );
this.m_$Focus = $Suggestion;
this.m_$Focus.addClass( 'focus' );
this.m_strLastFocusVal = $Suggestion.text();
};
CTextInputSuggest.prototype.Destroy = function()
{
this.m_$SuggestionsCtn.remove();
this.m_$Input.off( '.CTextInputSuggest' );
};
/**
* Similar to CTextInputSuggest, but uses associative arrays instead of just text; useful for when we may have more than one
* item with the same text name, or when you want to use HTML in the item name instead of just plaintext
*
* Each suggestion should include a 'key', and one of the following:
* text - Suggestion text (escaped, sets textContents)
* html - Raw suggestion html (NOT ESCAPED, sets innerHTML)
*
* @param $InputElement
* @param fnSuggestForTerm
* @param fnOnSuggest
* @constructor
*/
function CIndexedInputSuggest( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass )
{
this.Init( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass );
}
CIndexedInputSuggest.prototype = Object.create(CTextInputSuggest.prototype);;
CIndexedInputSuggest.prototype.OnSuggestionSelected = function( $Suggestion )
{
this.m_$Input.val( $Suggestion.text() );
this.m_bHaveSuggestions = false;
this.m_$Focus = $J();
this.HideSuggestions();
this.m_fnOnSuggest( $Suggestion.data('suggest-key'), $Suggestion.text() );
};
CIndexedInputSuggest.prototype.SetSuggestions = function( rgSuggestions )
{
var strLastFocus = this.m_strLastFocusVal;
this.m_$Suggestions.empty();
this.m_$Focus = $J();
this.m_strLastFocus = null;
if ( rgSuggestions && rgSuggestions.length )
{
var _this = this;
for ( var i = 0; i < rgSuggestions.length; i++ )
{
var $Suggestion = $J('<div/>', {'class': 'suggestion_item popup_menu_item' } );
if( rgSuggestions[i].text )
$Suggestion.text( rgSuggestions[i].text );
else if( rgSuggestions[i].html )
$Suggestion.html( rgSuggestions[i].html );
$Suggestion.data('suggest-key', rgSuggestions[i].key )
$Suggestion.click( $J.proxy( this.OnSuggestionSelected, this, $Suggestion ) );
$Suggestion.mouseenter( $J.proxy( this.SetFocus, this, $Suggestion ) );
this.m_$Suggestions.append( $Suggestion );
if ( rgSuggestions[i] == strLastFocus )
this.SetFocus( $Suggestion );
}
this.m_bHaveSuggestions = true;
this.ShowSuggestions();
}
else
{
this.m_bHaveSuggestions = false;
this.HideSuggestions();
}
};
function InitBBCodeVideos( bAllowAutoPlay )
{
var videos = $J( "video" );
if ( videos.length != 0 )
{
for ( var i = 0; i < videos.length; ++i )
{
var video = videos[i];
var $video = $J( video );
if ( $video.hasClass("bb_video_loop") )
{
video.setAttribute('loop', 'loop');
}
if ( $video.hasClass("bb_video_controls") )
{
video.setAttribute( 'controls', 'controls' );
}
if ( bAllowAutoPlay )
{
$video.unbind('mouseenter mouseleave');
video.preload = $video.hasClass("bb_video_preload") ? 'auto' : 'metadata';
if ( $video.hasClass("bb_video_autoplay") )
{
video.autoplay = 'autoplay';
}
}
else
{
video.preload = 'metadata';
$video.hover( function toggleControls() {
if ( this.hasAttribute("controls") ) {
this.removeAttribute("controls")
} else {
this.setAttribute("controls", "controls")
}
} );
}
}
}
}
/**
* Generic interface for handling ajax-driven sub-pages.
*
* @param elTarget Element to dump content into
* @param strBaseURL Base URL to attach nav params
* @param strInstanceId Unique flag to look for when scanning for state change events. also determines our data selector
* Only needed if you're using more than one CAjaxSubPageController on the same page.
* @param strDefaultLocation Used when no navid is provided
* @constructor
*/
var CAjaxSubPageController = function( elTarget, strBaseURL, strInstanceId, strDefaultLocation )
{
this.elTarget = elTarget;
this.strBaseURL = strBaseURL;
this.strStateID = strInstanceId || 'navid';
this.strDefaultLocation = strDefaultLocation || '';
this.rgOriginalEvent = {'html':this.elTarget.innerHTML,'title':document.title, 'id': this.strStateID};
window.addEventListener('popstate', this.OnWindowPopState.bind(this));
this.InstrumentLinks( document );
var _this = this;
setTimeout( function(){ $J(_this.elTarget).children().trigger('load'); }, 1);
var strLocation = window.location.href.substring(strBaseURL.length);
this.PaintLinks(strLocation || this.strDefaultLocation);
};
/**
* Register click handlers. This also sets the href for browser link preview and fallback for non-click navigation
* events, such as opening in a new tab/window via middle click.
* @param elTarget Element to query from.
* @constructor
*/
CAjaxSubPageController.prototype.InstrumentLinks = function( elTarget )
{
//var rgLinks = elTarget.querySelectorAll('[data-'+this.strStateID+']');
var rgLinks = $J('[data-'+this.strStateID+']', elTarget);
for( var i=0; i<rgLinks.length; i++)
{
rgLinks[i].addEventListener('click', this.Navigate.bind(this, rgLinks[i].dataset[ this.strStateID ], rgLinks[i].dataset[ 'title' ] ) );
rgLinks[i].href = this.strBaseURL + rgLinks[i].dataset[ this.strStateID ];
}
};
/**
* Adds 'active' class to link that was clicked or is currently active.
* @constructor
*/
CAjaxSubPageController.prototype.PaintLinks = function( strLocation )
{
// Figure out which link we clicked and paint it with the correct class
var rgLinks = document.querySelectorAll('[data-'+this.strStateID+']');
for( var i=0; i<rgLinks.length; i++)
{
if( rgLinks[i].dataset[ this.strStateID ] == strLocation || rgLinks[i].dataset[ this.strStateID ] + '/' == strLocation )
rgLinks[i].classList.add('active');
else
rgLinks[i].classList.remove('active');
}
};
/**
* Call to navigate the sub-frame.
*
* @param strLocation Assumed to be strBaseURL + strLocation. Trailing slash should be on strBaseURL already.
* @param strpageTitle Optional: Replace page title with this new value.
* @constructor
*/
CAjaxSubPageController.prototype.Navigate = function( strLocation, strPageTitle, event )
{
var _this = this;
var strURL = this.strBaseURL + strLocation;
this.elTarget.classList.add('loading');
this.PaintLinks( strLocation );
// Trigger the "saveform" event which we may have bound to do things.
$J( 'form', this.elTarget ).trigger('saveform');
$J.ajax({
url: strURL,
dataType: "html",
cache: true, /* Let the browser decide caching rules */
data: { 'ajax': 1 }
}).done(function( result ) {
var elNewContents = $J( result );
$J(_this.elTarget).empty();
$J(_this.elTarget).append( elNewContents );
_this.elTarget.classList.remove('loading');
$J( elNewContents ).trigger('load');
_this.InstrumentLinks( _this.elTarget );
if( strPageTitle )
document.title = strPageTitle;
$J('.tooltip', _this.elTarget).v_tooltip();
window.history.pushState({'html':result,'title':strPageTitle, 'id': _this.strStateID}, '', strURL );
});
if( event )
event.preventDefault();
};
/**
* Internal event handler for the "back" button.
* @param Event event
* @constructor
*/
CAjaxSubPageController.prototype.OnWindowPopState = function( event )
{
// Revert to our original state if there's nothing in the stack.
if(event.state == null)
{
event.state = 5;
}
var state = event.state || this.rgOriginalEvent;
if( state && state.id == this.strStateID)
{
var strLocation = window.location.href.substring(this.strBaseURL.length);
this.PaintLinks( strLocation );
var elNewContents = $J(state.html);
$J(this.elTarget).empty();
$J(this.elTarget).append(elNewContents);
if( state.title )
document.title = state.title;
$J( elNewContents ).trigger('load');
this.InstrumentLinks( elNewContents );
}
};
function SetupAnimateOnHoverImages()
{
var $Images = $J( '[data-animate-on-hover-src]', document );
if ( $Images.length )
{
for ( var i = 0; i < $Images.length; ++i )
{
var img = $J( $Images[i] );
img.data( 'static-src', img.attr( 'src') );
img.hover(
function() {
var thisImage = $J( this );
thisImage.attr( 'src', thisImage.data( 'animate-on-hover-src' ) );
},
function() {
var thisImage = $J( this );
thisImage.attr( 'src', thisImage.data( 'static-src' ) );
}
);
}
}
}
function BindTooltips(selector, rgOptions)
{
// Standard tooltips
var $TextTooltips = $J( '[data-tooltip-text]', selector);
if ( $TextTooltips.length )
$TextTooltips.v_tooltip( { 'tooltipClass': rgOptions.tooltipCSSClass, 'dataName': 'tooltipText', 'defaultType': 'text', 'replaceExisting': false, 'responsiveMode': window.UseSmallScreenMode && window.UseSmallScreenMode() } );
var $HTMLTooltips = $J( '[data-tooltip-html]', selector);
if ( $HTMLTooltips.length )
$HTMLTooltips.v_tooltip( { 'tooltipClass': rgOptions.tooltipCSSClass, 'dataName': 'tooltipHtml', 'defaultType': 'html', 'replaceExisting': false, 'responsiveMode': window.UseSmallScreenMode && window.UseSmallScreenMode() } );
}
function ShowTooltipMenuAsPopup( toolDiv )
{
var $Popup = $J( toolDiv );
// detach this element and when the dialog closes re-attach to the document body
$Popup.detach();
var originalPopupPosition = $Popup.css( 'position' );
$Popup.css( 'position', 'static' ); // clear possible absolute positioning
$Popup.show();
var dialog = ShowDialog( '', $Popup ).always(
function() {
// save it away again for later
$Popup.hide();
$Popup.css( 'position', originalPopupPosition ); // restore positioning
$J( document.body ).append( $Popup );
}
);
return dialog;
}
/**
* Binds standard tooltips for the current site. This function also binds a mutationobserver to bind any future elements without additional work
* @param strCSSClass
* @constructor
*/
var g_TooltipMutationObserver;
var g_bTooltipMutationObserverDisabled = false;
function SetupTooltips( rgOptions )
{
function InnerSetupTooltips() {
BindTooltips(document, rgOptions);
if (g_TooltipMutationObserver || g_bTooltipMutationObserverDisabled)
return;
try
{
var config = {
attributes: true, // not clear if we actually support this, as we only look at addedNodes
childList: true,
subtree: true,
attributeFilter: ["data-tooltip-html", "data-tooltip-text"]
};
var callback = function (mutationsList)
{
for (var i = 0; i < mutationsList.length; i++)
{
var mutation = mutationsList[i];
if (mutation.addedNodes && mutation.addedNodes.length)
BindTooltips(mutation.addedNodes, rgOptions);
}
};
// Create an observer instance linked to the callback function
g_TooltipMutationObserver = new MutationObserver(callback);
// Start observing the target node for configured mutations
g_TooltipMutationObserver.observe(document, config);
}
catch (e)
{
// Swallow exceptions for browsers that don't support mutationobservers
}
}
InnerSetupTooltips();
$J( window ).on( 'Responsive_SmallScreenModeToggled', InnerSetupTooltips );
}
// for perf sensitive pages
function DisableTooltipMutationObserver()
{
g_bTooltipMutationObserverDisabled = true;
if ( g_TooltipMutationObserver )
{
g_TooltipMutationObserver.disconnect();
g_TooltipMutationObserver = null;
}
}
function ViewTitlesWithDescriptors( descid )
{
var waitDialog = ShowBlockingWaitDialog( '示例产品', '正在查找示例产品,请稍候…' );
$J.get( 'https://store.steampowered.com/search/results/',
{
'filter' : 'globaltopsellers',
'ignore_preferences' : 1,
'category1' : '992,994,998', 'descids[]' : descid,
'json' : 1,
}
).done( function( response ) {
waitDialog.Dismiss();
var content = $J( "<div>" );
content.append( $J( "<div>", { class: 'content_descriptors_examples_desc', text: response.desc } ) );
if ( response.items.length != 0 )
{
for ( var i = 0; i < response.items.length && i < 10; ++i )
{
var item = response.items[i];
var elem = $J( "<div>", { class: 'content_descriptors_example_app' } );
elem.append( $J( "<img>", { class: 'app_logo', src: item.logo } ) );
elem.append( $J( "<div>", { class: 'app_name', text: item.name } ) );
content.append( elem );
}
}
else
{
content.append( $J( "<div>", { class: "no_items", text: '未找到产品' } ) );
}
var dialog = ShowAlertDialog( '示例产品', content );
} );
}
// Element appearance montior. Designed to replace jQuery.appear
var CAppearMonitor = function( fnOnAppear, bCheckAnyIntersection ){
this.rgMonitoredElements = [];
this.bRunning = false;
this.unTimerFrequency = 500;
this.bRegisteredWindowEvent = false;
this.fnOnAppear = fnOnAppear;
this.bCheckAnyIntersection = bCheckAnyIntersection || false;
};
CAppearMonitor.prototype.RegisterScrollEvent = function( elTarget )
{
var instance = this;
$J(elTarget).on('scroll', function()
{
if( instance.bRunning || instance.rgMonitoredElements.length == 0 )
return;
instance.bRunning = true;
setTimeout( instance.CheckVisibilityInternal.bind(instance), instance.unTimerFrequency );
});
}
CAppearMonitor.prototype.CheckVisibility = function()
{
// when items are added they will schedule an immediate check. Delay for a bit
if ( this.m_iCheckVisibilityRequestInterval )
{
window.clearTimeout( this.m_iCheckVisibilityRequestInterval );
}
this.m_iCheckVisibilityRequestInterval = setTimeout( this.CheckVisibilityInternal.bind(this), this.unTimerFrequency );
};
CAppearMonitor.prototype.CheckVisibilityInternal = function()
{
// Walk backwards so we can slice while we iterate
for( var i=this.rgMonitoredElements.length-1; i>=0; i--)
{
var rgMonitored = this.rgMonitoredElements[i];
if( this.bIsElementVisible( rgMonitored.element ) )
{
this.rgMonitoredElements.splice(i,1);
this.fnOnAppear( rgMonitored.element );
} else if( !document.body.contains( rgMonitored.element ) )
{
this.rgMonitoredElements.splice(i,1);
}
}
this.bRunning = false;
this.m_iCheckVisibilityRequestInterval = undefined;
};
CAppearMonitor.prototype.bIsElementVisible = function( elElement )
{
if( !elElement )
return;
// Check physical position vs viewport. This is likely the fastest early-out.
var rectElement = elElement.getBoundingClientRect();
if ( this.bCheckAnyIntersection )
{
if ( rectElement.left > window.innerWidth || rectElement.right < 0 )
return false;
if ( rectElement.top > window.innerHeight || rectElement.bottom < 0 )
return false;
}
else
{
if ( !( rectElement.top >= 0 && rectElement.left >= 0
&& rectElement.bottom <= window.innerHeight && rectElement.right <= window.innerWidth ) )
return false;
}
// Ask jQuery to compute visibility, since it knows all the edge cases.
if( !$J(elElement).is(':visible'))
return false;
return true;
};
CAppearMonitor.prototype.RegisterElement = function( elTarget )
{
if( $J(elTarget).data('manual-tracking' ) )
return;
this.rgMonitoredElements.push({
element: elTarget
});
// Register the window scroll event ONLY after we have something to monitor
if( !this.bRegisteredWindowEvent)
this.RegisterScrollEvent( window );
};
CAppearMonitor.prototype.TrackAppearanceIfVisible = function( elTarget )
{
var _this = this;
window.setTimeout( function() {
// Ensure we're actually in the viewport (Carousel may be scrolling out of view and calling this)
// this triggers layout, so we delay this check a little bit to give the page time to settle whatever
// change may currently be in-flight
if( !_this.bIsElementVisible( elTarget ) )
return;
_this.TrackAppearance( elTarget );
}, 10);
};
CAppearMonitor.prototype.TrackAppearance = function( elTarget )
{
// Find our element, splice it out if we were tracking it
for( var i=this.rgMonitoredElements.length-1; i>=0; i--)
{
var rgMonitored = this.rgMonitoredElements[i];
if( rgMonitored.element === elTarget )
{
this.rgMonitoredElements.splice(i,1);
}
}
// Trigger it, even if we weren't actually tracking it
this.fnOnAppear( elTarget );
};
CAppearMonitor.prototype.ClearElements = function()
{
this.rgMonitoredElements = [];
}
// gamepad navigation wrappers, the implementations of these are stored in legacy_web.
// once legacy web initializes, these functions will be replaced by the real thing. In the mean time,
// we will queue any commands and send them when ready.
var g_rgQueuedGamepadCommands = [];
var g_rgOnReadyCallbacks = [];
var g_bGamepadNavReady = false;
function RunWhenGamepadNavReady( callback )
{
if ( g_bGamepadNavReady )
callback();
else
g_rgOnReadyCallbacks.push( callback );
}
window.addEventListener( 'vgp_gamepadnavready', function() {
g_rgQueuedGamepadCommands.forEach( function( command ) { window[command.name].apply( window, command.args ); } );
g_rgQueuedGamepadCommands = [];
g_rgOnReadyCallbacks.forEach( function( fn ) { fn(); } );
g_rgOnReadyCallbacks = [];
});
function InstrumentFocusElements( element )
{
g_rgQueuedGamepadCommands.push( { name: 'InstrumentFocusElements', args: arguments } );
}
function ForceUpdateFocusElements( element )
{
g_rgQueuedGamepadCommands.push( { name: 'ForceUpdateFocusElements', args: arguments } );
}
function GPNavFocusChild( element )
{
g_rgQueuedGamepadCommands.push( { name: 'GPNavFocusChild', args: arguments } );
}
function GPOnShowingModalWindow( element )
{
g_rgQueuedGamepadCommands.push( { name: 'GPOnShowingModalWindow', args: arguments } );
}
function GPShowVirtualKeyboard( bShowKeyboard )
{
g_rgQueuedGamepadCommands.push( { name: 'GPShowVirtualKeyboard', args: arguments } );
}
function GPNavUpdateActionDescriptions( element, actionDescriptions )
{
g_rgQueuedGamepadCommands.push( { name: 'GPNavUpdateActionDescriptions', args: arguments } );
}
var SetGPFocusRestoreTimeout = function(){}; // no op unless InitializeGPFocusRestoreTimeout is called
var nGPFocusRestoreTimeoutID = -1;
function InitializeGPFocusRestoreTimeout( bUseWindowOnload = true )
{
window.history.replaceState( $J.extend( {}, window.history.state, { notify_focus_restore_ready: true } ), "" );
SetGPFocusRestoreTimeout = function( delay = 200 )
{
if ( nGPFocusRestoreTimeoutID == 0 || ( !window.UseTabletScreenMode || !window.UseTabletScreenMode() ) )
return;
if ( nGPFocusRestoreTimeoutID !== -1 )
window.clearTimeout( nGPFocusRestoreTimeoutID );
this.nGPFocusRestoreTimeoutID = window.setTimeout( function(){
nGPFocusRestoreTimeoutID = 0;
dispatchEvent( new Event( 'focus_restore_ready' ) );
}, delay );
}
if ( bUseWindowOnload )
window.addEventListener( "load", function(){ SetGPFocusRestoreTimeout(); } );
}
function HandleOverlayWindowPinnedView( bPinned, bShowPinnedView )
{
$J( 'body' ).toggleClass( 'OverlayWindowPinned', bPinned );
$J( 'body' ).toggleClass( 'OverlayWindowPinnedView', bShowPinnedView );
}
"use strict";
// build our menu on init
jQuery( function($) {
var mqQueryMenuMode = window.matchMedia ? window.matchMedia("(max-width: 910px)") : {matches: false};
var mqMobileMode = window.matchMedia ? window.matchMedia("(max-width: 500px)") : {matches: false};
var $HTML = $J('html');
window.UseTouchFriendlyMode = function() {
return $HTML.hasClass( 'responsive' ) && ( mqQueryMenuMode.matches || $HTML.hasClass('touch') );
};
window.UseSmallScreenMode = function() {
return $HTML.hasClass( 'responsive' ) && mqQueryMenuMode.matches;
};
window.UseMobileScreenMode = function() {
return $HTML.hasClass( 'responsive' ) && mqMobileMode.matches;
};
window.UseTabletScreenMode = function() {
return $HTML.hasClass( 'responsive' ) && $HTML.hasClass( 'tablet' );
};
window.UseNewMobileAppMode = function() {
// the new mobile app can run on screen widths wider than responsive_css_maxwidth
return $HTML.hasClass( 'responsive' ) && $HTML.hasClass( 'rn_mobile_app' );
};
// main menu
var $Menu = $('#responsive_page_menu');
var $Frame = $('.responsive_page_frame');
var $ContentCtn = $('.responsive_page_content' );
var $ContentOverlay = $('.responsive_page_content_overlay');
var fnResetMenuState = function() {
$Frame.removeClass( 'mainmenu_active');
$Frame.removeClass('localmenu_active');
$J(document.body).removeClass( 'overflow_hidden' );
$ContentOverlay.off( 'click.ReponsiveMenuDismiss');
};
$J(document).on('click.OnClickDismissMenu', '.responsive_OnClickDismissMenu', fnResetMenuState );
var strLastExpandedSubmenu = WebStorage.GetLocalSession( 'responsiveMenuLastSubmenu' );
var fnMakeExpandableMenuItem = function( $MenuItem, $Submenu )
{
$MenuItem.append( $J('<div/>', {'class': 'chevron' } ) );
var $SubmenuWrapper = $J('<div/>', {'class': 'menuitem_submenu_wrapper' });
$MenuItem.after( $SubmenuWrapper.append( $Submenu ) );
$Submenu.wrap( $('<div/>', {'class': 'inner_borders' } ) );
// if this was the last used submenu, start with it expanded
if ( strLastExpandedSubmenu && strLastExpandedSubmenu == $Submenu.data('submenuid') )
{
$SubmenuWrapper.css( 'height', $Submenu.height() + 'px' );
$MenuItem.addClass( 'submenu_active' );
$SubmenuWrapper.addClass('active');
}
else
{
$SubmenuWrapper.css( 'height', 0 );
}
$Submenu.show();
$MenuItem.click( function(e) {
e.preventDefault();
if ( $SubmenuWrapper.hasClass('active' ) )
{
$SubmenuWrapper.removeClass('active' ).css('height',0);
$MenuItem.removeClass('submenu_active');
WebStorage.SetLocalSession( 'responsiveMenuLastSubmenu', null );
}
else
{
$SubmenuWrapper.siblings( '.menuitem_submenu_wrapper.active' ).css('height',0 ).removeClass('active');
$MenuItem.siblings('.menuitem').removeClass('submenu_active');
$SubmenuWrapper.css( 'height', $Submenu.height() + 'px' );
$MenuItem.addClass( 'submenu_active' );
$SubmenuWrapper.addClass('active');
WebStorage.SetLocalSession( 'responsiveMenuLastSubmenu', $Submenu.data('submenuid') );
}
});
};
var fnBuildMenuEvents = function( $Menu, strMenuName, fnFirstTimeInitialization )
{
var strActiveClass = strMenuName + '_active';
var fnGPOnClosingModalWindow = null;
var fnDismissMenu = function() {
$ContentCtn.off( 'click.ReponsiveMenuDismiss');
$Frame.removeClass(strActiveClass);
$J(document.body).removeClass('overflow_hidden' );
// tell gamepad navigation we're closing the menu
if ( fnGPOnClosingModalWindow )
{
fnGPOnClosingModalWindow();
fnGPOnClosingModalWindow = null;
}
window.setTimeout( function() {
if ( !$Frame.hasClass('mainmenu_active') && !$Frame.hasClass('localmenu_active') )
{
fnResetMenuState();
}
}, 500 );
};
var bInitialized = false;
var fnActivateMenu = function() {
if ( !bInitialized )
{
fnFirstTimeInitialization && fnFirstTimeInitialization();
bInitialized = true;
}
if ( $Frame.hasClass( strActiveClass ) )
{
fnDismissMenu();
}
else
{
$J(document.body).addClass('overflow_hidden' );
$Menu.removeClass('secondary_active');
$Frame.addClass( strActiveClass );
$ContentOverlay.one( 'click.ResponsiveMenuDismiss', function() {
fnDismissMenu();
});
// tell gamepad navigation to treat this menu as a modal dialog
if ( typeof GPOnShowingModalWindow === "function" )
fnGPOnClosingModalWindow = GPOnShowingModalWindow( $Menu.get( 0 ) );
}
};
return { fnActivateMenu: fnActivateMenu, fnDismissMenu: fnDismissMenu };
};
var fnInitMainMenu = function() {
$('.responsive_page_menu' ).find( '.supernav').each( function() {
var $Element = $(this);
$Element.attr('href','');
var strSubmenuSelector = $Element.data('tooltip-content');
var $Submenu = $Element.parent().find(strSubmenuSelector);
if ( $Submenu.length )
{
fnMakeExpandableMenuItem( $Element, $Submenu );
}
});
var $NotificationItem = $Menu.find( '.notifications_item' );
var $NotificationSubmenu = $Menu.find('.notification_submenu');
if ( $NotificationItem.length && $NotificationSubmenu.length )
{
fnMakeExpandableMenuItem( $NotificationItem, $NotificationSubmenu );
}
Responsive_BuildChangeLanguageOption( $Menu.find( '.change_language_action' ) );
};
var MainMenuEvents = null;
if ( $Menu.length )
{
MainMenuEvents = fnBuildMenuEvents( $Menu, 'mainmenu', fnInitMainMenu );
$('#responsive_menu_logo' ).click( function( e ) {
MainMenuEvents.fnActivateMenu();
} );
}
// local (page-specific) menu
var $LocalMenuContent = $('.responsive_local_menu');
var $LocalMenu = null;
var LocalMenuEvents = null;
if ( $LocalMenuContent.length )
{
var bLocalMenuEnabed = false;
var rgMenuContents = [];
var fnInitLocalMenu = function() {
if ( rgMenuContents.length )
return;
for ( var i = 0; i < $LocalMenuContent.length; i++ )
{
var $LocalMenuElement = $($LocalMenuContent[i] ).wrap( $J('<div/>', {'class': 'responsive_local_menu_placeholder' } ) );
var $LocalMenuWrapper = $($LocalMenuContent[i]).parent();
rgMenuContents.push( {
wrapper: $LocalMenuWrapper,
content: $LocalMenuElement
});
}
};
$LocalMenu = $J('#responsive_page_local_menu');
var $Affordance = $J('.responsive_local_menu_tab');
LocalMenuEvents = fnBuildMenuEvents( $LocalMenu, 'localmenu' );
$Affordance.click( function( e ) {
LocalMenuEvents.fnActivateMenu();
});
g_fnActivateLocalMenu = LocalMenuEvents.fnActivateMenu;
$(window ).on( 'Responsive_SmallScreenModeToggled.ReponsiveLocalMenu', function() {
var bShouldUseResponsiveMenu = UseSmallScreenMode();
if ( bLocalMenuEnabed != bShouldUseResponsiveMenu )
{
if ( bShouldUseResponsiveMenu )
{
$Affordance.addClass( 'active' );
fnInitLocalMenu();
$LocalMenu.find('.localmenu_content' ).append( $LocalMenuContent );
}
else
{
fnResetMenuState();
$Affordance.removeClass('active');
for ( var i = 0; i < rgMenuContents.length; i++ )
{
rgMenuContents[i].wrapper.append( rgMenuContents[i].content );
}
}
bLocalMenuEnabed = bShouldUseResponsiveMenu;
}
} ).trigger( 'Responsive_SmallScreenModeToggled.ReponsiveLocalMenu');
// menu is available, so set the action description for the page content.
GPNavUpdateActionDescriptions( $('#responsive_page_template_content'), { onOptionsActionDescription:'菜单' } );
$('#responsive_page_template_content').on( 'vgp_onoptions', function() { LocalMenuEvents.fnActivateMenu(); } );
}
Responsive_InitMenuSwipes( $, $Menu, $LocalMenu, MainMenuEvents, LocalMenuEvents );
Responsive_InitFixOnScroll( $ );
Responsive_InitTouchDetection( $ );
Responsive_InitTabSelect( $ );
Responsive_InitResponsiveToggleEvents( $ );
Responsive_InitJQPlotHooks( $ );
if ( window.UseTabletScreenMode && window.UseTabletScreenMode() )
Responsive_InitForTablet( $ );
});
function Responsive_InitForTablet( $ )
{
// support using gamepad to change slider position
$( 'input[type=range]' ).on( 'vgp_ondirection', function( event ) {
if ( event.originalEvent.detail.button == 11 || event.originalEvent.detail.button == 12 )
{
if ( event.originalEvent.detail.button == 11 ) // EGamepadButton.DIR_LEFT
this.stepDown();
else if ( event.originalEvent.detail.button == 12 ) // EGamepadButton.DIR_RIGHT
this.stepUp();
$( this ).trigger( 'input' ).trigger( 'change' );
return false; // prevent the message from propagating
}
} );
}
function Responsive_InitMenuSwipes( $, $Menu, $LocalMenu, MainMenuEvents, LocalMenuEvents )
{
if ( !MainMenuEvents && !LocalMenuEvents )
return;
var $Frame = $('.responsive_page_frame');
// set up touch drag events
var bInMainMenuDrag = false;
var bInLocalMenuDrag = false;
var bInDismissMenuDrag = false;
var bLooksLikeSwipe = false;
var nDragMenuWidth = 0;
var nCurDragOffset = 0;
var nTouchStartPageX = 0; // page/CSS coordinates because that's how we measure the menu width
var nTouchStartPageY = 0;
var fnGetSingleTouch = function(e) {
var TouchEvent = e.originalEvent;
var rgTouches = TouchEvent ? TouchEvent.touches : null;
if ( !rgTouches || rgTouches.length != 1 )
return null;
return rgTouches[0];
};
var fnCancelDrag = function()
{
$Frame.removeClass('in_menu_drag');
if ( bLooksLikeSwipe )
{
var DragMenuEvents = bInMainMenuDrag ? MainMenuEvents : LocalMenuEvents;
var $DragMenu = bInMainMenuDrag ? $Menu : $LocalMenu;
$DragMenu.parents('.responsive_page_menu_ctn' ).css( 'left', '' ).css( 'right', '' );
if ( ( !bInDismissMenuDrag && nCurDragOffset < nDragMenuWidth / 2 ) ||
( bInDismissMenuDrag && nCurDragOffset > nDragMenuWidth / 2 ) )
{
DragMenuEvents.fnDismissMenu();
}
}
bInLocalMenuDrag = bInMainMenuDrag = bLooksLikeSwipe = bInDismissMenuDrag = false;
};
$(window ).on('touchstart.ResponsiveMenuActivate', function(e) {
if ( !UseSmallScreenMode() )
return;
var Touch = fnGetSingleTouch(e);
if ( !Touch )
return;
if ( $Frame.hasClass('mainmenu_active') )
{
if ( Touch.clientX > $Menu.width() * 0.9 )
{
bInMainMenuDrag = true;
bInDismissMenuDrag = true;
}
}
else if ( $Frame.hasClass('localmenu_active') )
{
if ( Touch.clientX < ( window.innerWidth - $LocalMenu.width() * 0.9 ) )
{
bInLocalMenuDrag = true;
bInDismissMenuDrag = true;
}
}
else
{
var nClientXAsPct = 100 * Touch.clientX / window.innerWidth;
bInDismissMenuDrag = false;
if ( nClientXAsPct > 93 && LocalMenuEvents )
bInLocalMenuDrag = true;
else if ( nClientXAsPct < 7 && MainMenuEvents )
bInMainMenuDrag = true;
}
if ( bInLocalMenuDrag || bInMainMenuDrag )
{
bLooksLikeSwipe = false;
nTouchStartPageX = Touch.pageX;
nTouchStartPageY = Touch.pageY;
}
});
$(window ).on('touchmove.ResponsiveMenuActive', function(e) {
if ( !bInLocalMenuDrag && !bInMainMenuDrag )
return;
var Touch = fnGetSingleTouch(e);
if ( !Touch )
return;
var nDeltaPageX = Touch.pageX - nTouchStartPageX;
var nDeltaPageY = Touch.pageY - nTouchStartPageY;
if ( ( bInLocalMenuDrag && !bInDismissMenuDrag ) || ( bInMainMenuDrag && bInDismissMenuDrag ) )
nDeltaPageX = -nDeltaPageX;
var DragMenuEvents = bInMainMenuDrag ? MainMenuEvents : LocalMenuEvents;
var $DragMenu = bInMainMenuDrag ? $Menu : $LocalMenu;
if ( !bLooksLikeSwipe && nDeltaPageX > 25 && nDeltaPageX > nDeltaPageY * 2 )
{
// horizontal drag
$Frame.addClass('in_menu_drag');
if ( !bInDismissMenuDrag )
DragMenuEvents.fnActivateMenu();
//$Frame.addClass( bInLocalMenuDrag ? 'localmenu_active' : 'mainmenu_active' );
nDragMenuWidth = $DragMenu.width();
bLooksLikeSwipe = true;
}
else if ( nDeltaPageY > 2 * nDeltaPageX )
{
// looks like scrolling?
fnCancelDrag();
return;
}
if ( bLooksLikeSwipe )
{
nCurDragOffset = Math.max( Math.min( nDeltaPageX, nDragMenuWidth ), 0 );
var nOffset = bInDismissMenuDrag ? -nCurDragOffset : -(nDragMenuWidth - nCurDragOffset);
$DragMenu.parents('.responsive_page_menu_ctn' ).css( bInLocalMenuDrag ? 'right' : 'left', nOffset + 'px' );
}
});
$(window ).on('touchend.ResponsiveMenuActivate touchcancel.ResponsiveMenuActivate', function(e) {
fnCancelDrag();
});
}
function Responsive_InitTouchDetection( $ )
{
var $HTML= $J('html');
if ( !$HTML.hasClass('touch') && $HTML.hasClass('responsive') )
{
$J(window ).one('touchstart', function() {
// user is on a touch device - enable touch-friendly accessors and
// remember for the rest of this session
$HTML.addClass('touch');
V_SetCookie( "strResponsiveViewPrefs", 'touch', 0 );
} );
}
}
function Responsive_InitTabSelect( $ )
{
// handle any tab dropdowns
$J(document).on('change.ResponsiveTabSelect', 'select.responsive_tab_select', function() {
var url = $J(this ).val();
if ( url != window.location )
window.location = url;
});
}
function Responsive_InitFixOnScroll($)
{
var $Ctn = $J('.responsive_fixonscroll_ctn');
var $Elements = $J('.responsive_fixonscroll');
if ( $Elements.length )
{
var nCtnTop = -1;
var nCtnHeight = 0;
$J(window).on('scroll.ResponsiveFixOnScroll resize.ResponsiveFixOnScroll', function() {
var nHeaderOffset = GetResponsiveHeaderFixedOffsetAdjustment();
var nScrollTop = $J(window ).scrollTop() + nHeaderOffset + nCtnHeight;
if ( nHeaderOffset != nCtnTop )
{
nCtnTop = nHeaderOffset;
$Ctn.css( 'top', nCtnTop + 'px' );
}
$Elements.each( function() {
var $Element = $J(this);
if ( !$Element.is(':visible') )
{
if ( $Element.hasClass('in_fixed_ctn') && $Element.data('originalContents') )
{
$Element.append( $Element.data('originalContents') );
$Element.removeClass('in_fixed_ctn');
$Element.css('height', '');
nCtnHeight = $Ctn.height();
}
return;
}
var nElementTop = $Element.offset().top;
if ( nElementTop > nScrollTop )
{
if ( $Element.hasClass('in_fixed_ctn') )
{
$Element.append( $Element.data('originalContents') );
$Element.removeClass('in_fixed_ctn');
$Element.css('height', '');
nCtnHeight = $Ctn.height();
}
}
else
{
if ( !$Element.hasClass('in_fixed_ctn') )
{
$Element.css('height', $Element.height() + 'px' );
$Element.data( 'originalContents', $Element.children() );
$Ctn.append( $Element.children() );
$Element.addClass( 'in_fixed_ctn' );
nCtnHeight = $Ctn.height();
}
}
});
} );
}
}
function Responsive_BuildChangeLanguageOption( $MenuItem )
{
$MenuItem.click( function() {
var $LanguageForm = $J('<form/>', {'class': 'responsive_change_language_form' } );
var $SelectBox = $J('<select/>', {'class': 'responsive_change_language_select' } );
$SelectBox.append(
$J('<option/>', {value: "schinese"} ).text( '简体中文' )
);
$SelectBox.append(
$J('<option/>', {value: "tchinese"} ).text( '繁體中文(繁体中文)' )
);
$SelectBox.append(
$J('<option/>', {value: "japanese"} ).text( '日本語(日语)' )
);
$SelectBox.append(
$J('<option/>', {value: "koreana"} ).text( '한국어(韩语)' )
);
$SelectBox.append(
$J('<option/>', {value: "thai"} ).text( 'ไทย(泰语)' )
);
$SelectBox.append(
$J('<option/>', {value: "bulgarian"} ).text( 'български(保加利亚语)' )
);
$SelectBox.append(
$J('<option/>', {value: "czech"} ).text( 'Čeština(捷克语)' )
);
$SelectBox.append(
$J('<option/>', {value: "danish"} ).text( 'Dansk(丹麦语)' )
);
$SelectBox.append(
$J('<option/>', {value: "german"} ).text( 'Deutsch(德语)' )
);
$SelectBox.append(
$J('<option/>', {value: "english"} ).text( 'English(英语)' )
);
$SelectBox.append(
$J('<option/>', {value: "spanish"} ).text( 'Español-España(西班牙语 - 西班牙)' )
);
$SelectBox.append(
$J('<option/>', {value: "latam"} ).text( 'Español - Latinoamérica(西班牙语 - 拉丁美洲)' )
);
$SelectBox.append(
$J('<option/>', {value: "greek"} ).text( 'Ελληνικά(希腊语)' )
);
$SelectBox.append(
$J('<option/>', {value: "french"} ).text( 'Français(法语)' )
);
$SelectBox.append(
$J('<option/>', {value: "italian"} ).text( 'Italiano(意大利语)' )
);
$SelectBox.append(
$J('<option/>', {value: "hungarian"} ).text( 'Magyar(匈牙利语)' )
);
$SelectBox.append(
$J('<option/>', {value: "dutch"} ).text( 'Nederlands(荷兰语)' )
);
$SelectBox.append(
$J('<option/>', {value: "norwegian"} ).text( 'Norsk(挪威语)' )
);
$SelectBox.append(
$J('<option/>', {value: "polish"} ).text( 'Polski(波兰语)' )
);
$SelectBox.append(
$J('<option/>', {value: "portuguese"} ).text( 'Português(葡萄牙语 - 葡萄牙)' )
);
$SelectBox.append(
$J('<option/>', {value: "brazilian"} ).text( 'Português-Brasil(葡萄牙语 - 巴西)' )
);
$SelectBox.append(
$J('<option/>', {value: "romanian"} ).text( 'Română(罗马尼亚语)' )
);
$SelectBox.append(
$J('<option/>', {value: "russian"} ).text( 'Русский(俄语)' )
);
$SelectBox.append(
$J('<option/>', {value: "finnish"} ).text( 'Suomi(芬兰语)' )
);
$SelectBox.append(
$J('<option/>', {value: "swedish"} ).text( 'Svenska(瑞典语)' )
);
$SelectBox.append(
$J('<option/>', {value: "turkish"} ).text( 'Türkçe(土耳其语)' )
);
$SelectBox.append(
$J('<option/>', {value: "vietnamese"} ).text( 'Tiếng Việt(越南语)' )
);
$SelectBox.append(
$J('<option/>', {value: "ukrainian"} ).text( 'Українська(乌克兰语)' )
);
var Modal = null;
var fnChangeLanguage = function() {
if ( $SelectBox.val() != "schinese" )
ChangeLanguage( $SelectBox.val() );
Modal && Modal.Dismiss();
};
$LanguageForm.submit( function(e) {
e.preventDefault();
fnChangeLanguage();
});
$SelectBox.change( fnChangeLanguage );
$LanguageForm.append( $SelectBox );
$LanguageForm.append( $J('<input/>', {type: 'submit'} ).hide() );
Modal = ShowConfirmDialog( '更改语言', $LanguageForm, '更改语言' ).done( fnChangeLanguage );
$SelectBox.focus();
});
}
function Responsive_RequestDesktopView()
{
// we can dynamically switch, but this doesn't trigger the phone to zoom out and some elements don't resize right
/*
$J('html' ).removeClass('responsive touch' ).addClass('force_desktop');
$J('meta[name=viewport]' ).attr('value', '' );
$J(window ).trigger('resize');
*/
Responsive_UpdateResponsivePrefs( 'desktop', true );
window.location.reload();
}
function Responsive_RequestMobileView()
{
/*
$J('html' ).removeClass('force_desktop' ).addClass('responsive');
$J('meta[name=viewport]' ).attr('value', 'width=device-width,initial-scale=1' );
$J(window ).trigger('resize');
*/
Responsive_UpdateResponsivePrefs( 'desktop', false );
window.location.reload();
}
function Responsive_UpdateResponsivePrefs( strFlag, bEnabled )
{
if ( bEnabled )
{
V_SetCookie( "strResponsiveViewPrefs", strFlag, 365 );
}
else
{
V_SetCookie( "strResponsiveViewPrefs", null, -1 );
}
}
function Responsive_InitResponsiveToggleEvents( $ )
{
// initially undefined, so we will fire the events at at start
var bTouchFriendly, bSmallScreen, bMobileScreen, bTabletScreen;
$(window).on('resize.ResponsiveToggle', function() {
if ( window.UseTouchFriendlyMode() !== bTouchFriendly )
{
bTouchFriendly = window.UseTouchFriendlyMode();
$(window).trigger('Responsive_TouchFriendlyModeToggled');
}
if ( window.UseSmallScreenMode() !== bSmallScreen )
{
bSmallScreen = window.UseSmallScreenMode();
$(window).trigger('Responsive_SmallScreenModeToggled');
}
if ( window.UseMobileScreenMode() !== bMobileScreen )
{
bMobileScreen = window.UseMobileScreenMode();
$(window).trigger('Responsive_MobileScreenModeToggled');
}
if ( window.UseTabletScreenMode() !== bTabletScreen )
{
bTabletScreen = window.UseTabletScreenMode();
$(window).trigger('Responsive_TabletScreenModeToggled');
}
} ).trigger( 'resize.ResponsiveToggle' );
}
/* reparent element when screen width is up to MOBILE_RESPONSIVE_CSS_MAXWIDTH */
function Responsive_ReparentItemsInMobileMode( strItemSelector, $CtnOrFn )
{
return _Responsive_ReparentItems( strItemSelector, $CtnOrFn, function() { return window.UseMobileScreenMode && window.UseMobileScreenMode(); }, 'Responsive_MobileScreenModeToggled' );
}
/* reparent element when we're rendering in the tablet layout (which could be any screen size given docked tablet scenarios) */
function Responsive_ReparentItemsInTabletMode( strItemSelector, $CtnOrFn )
{
return _Responsive_ReparentItems( strItemSelector, $CtnOrFn, function() { return window.UseTabletScreenMode && window.UseTabletScreenMode(); }, 'Responsive_TabletScreenModeToggled' );
}
/* reparent element when screen width is up to RESPONSIVE_CSS_MAXWIDTH, or we're in TabletScreenMode */
function Responsive_ReparentItemsInResponsiveMode( strItemSelector, $CtnOrFn )
{
return _Responsive_ReparentItems( strItemSelector, $CtnOrFn, function() { return ( ( window.UseSmallScreenMode && window.UseSmallScreenMode() ) || ( window.UseTabletScreenMode && window.UseTabletScreenMode() ) ); }, 'Responsive_SmallScreenModeToggled' );
}
function _Responsive_ReparentItems( strItemSelector, $CtnOrFn, fnShouldReparent, bEvent )
{
var fnReparentItems = function() {
var $MoveElements = $J(strItemSelector);
$MoveElements.each( function() {
var $Element = $J(this);
var $OriginalSpot = $Element.data('originalSpot' + bEvent);
if ( fnShouldReparent() )
{
if ( !$OriginalSpot )
{
$OriginalSpot = $J( '<div/>' );
$Element.after( $OriginalSpot );
$Element.data('originalSpot' + bEvent, $OriginalSpot );
}
var $Ctn;
if ( typeof $CtnOrFn === 'function' ) {
$Ctn = $CtnOrFn( $Element );
} else {
$Ctn = $CtnOrFn;
}
$Ctn.append( $Element );
}
else
{
if ( $OriginalSpot )
{
// If we've tracked an original parent, put us back
$OriginalSpot.after( $Element );
$OriginalSpot.remove();
$Element.removeData( 'originalSpot' + bEvent );
}
// Otherwise, we should already be where we want
}
});
};
fnReparentItems();
$J( window ).on( bEvent, fnReparentItems );
}
function Responsive_InitJQPlotHooks( $ )
{
if ( $.jqplot )
{
$.jqplot.postInitHooks.push( function( name, data, options ) {
var jqplot = this;
var bWasInResponsiveMode;
//replotting is very expensive, so we try to delay if the user is actively resizing
var iReplotInterval;
$J(window).resize( function() {
if ( ( UseSmallScreenMode() || bWasInResponsiveMode ) )
{
if ( iReplotInterval )
{
// we will reschedule for 100ms from now
window.clearInterval( iReplotInterval );
}
else
{
//interval hasn't been scheduled yet, must be the first resize.
// set overflow to hidden so the plot doesn't stretch the page while resizing
jqplot.target.css( 'overflow', 'hidden' );
}
iReplotInterval = window.setTimeout( function() {
iReplotInterval = null;
jqplot.replot();
jqplot.target.css( 'overflow', '' );
}, 100 );
bWasInResponsiveMode = UseSmallScreenMode();
}
});
});
}
}
// Enable toggling the local menu via JS
// Added so search can support use of a gamepad button to open the filter window
var g_fnActivateLocalMenu = false;
function Responsive_ToggleLocalMenu()
{
if ( g_fnActivateLocalMenu )
g_fnActivateLocalMenu();
else
console.error("attempted to open a local menu which does not exist");
}
\ No newline at end of file
/* Requires jQuery
*
* This plugin will create div.jsTooltip elements (or configure your own!) in body for every tooltip on the page. Some
* basic CSS is applied automagically, but you'll want to style it on your own from there. This code will be applied to
* every element in your .v_tooltip() selector, so giving it a common selector like '.tooltip' is ideal.
*
* Options:
* - location: Where the tooltip should spawn in relation to it's parent
* - offsetN: How many pixels to add
* - trackMouse: Should we track the mouse cursor instead of the parent?
* - suppressOnClick: Should we hide if a user clicks the target?
* - suppressWhileToggled: Should we ignore events if the target has the 'toggled' class?
* - tooltipClass: css class to apply to tooltip elements
* - fadeSpeed: Time (in milliseconds) to spend fading in/out. Set to 0 to disable.
* - allowHover: Should we keep the tooltip open if we mouse directly on to the tooltip? (Your tooltip will need to spawn inside it's owner's box for this to work)
* - tooltipParent: More generally useless properties for supernav: Lets us specify which element to parent the tooltips to. YOU PROBABLY DON'T NEED THIS.
* - correctForScreenSize: Adjust tooltip position to ensure it doesn't render outside of the viewport
* - sizeCorrectionXPadding: How far we should keep the tooltip from the window edge
* - useClickEvent: Should we use the mouse click event instead of hover?
* - inheritParentMinWidth: Should we set min-width based on our parent's width?
* - parentActiveCSSClass: What CSS class should we add to our parent while we're visible?
* - childActiveCSSClass: What CSS class should we add to the tooltip when active (Mostly useful for triggering CSS transitions
* - funcName: Global name of a function to call on hover (It searches the global window object; not safe inside "use strict"
* - func: Actual JS code to run.
* - responsiveMode: enable when in responsive mode/mobile/tablet, requires tooltip elements to be decorated with data-tooltip-responsive to enable in responsive mode
*/
/* <script> */
(function( $ ){
var methods = {
init : function( options ) {
var settings = $.extend( {
'location' : 'top',
'offsetX' : 0,
'offsetY' : -10,
'trackMouse' : false,
'trackMouseCentered': true,
'suppressOnClick' : true,
'suppressWhileToggled': true,
'tooltipClass' : 'jsTooltip',
'fadeSpeed' : 150,
'allowHover' : true,
'tooltipParent' : 'body',
'correctForScreenSize': true,
'sizeCorrectionXPadding': 15,
'sizeCorrectionYPadding': 10,
'useClickEvent' : false,
'useContextMenuEvent' : false,
'useMouseEnterEvent' : true,
'useMouseLeaveEvent' : true,
'preventDefault' : true,
'stopPropagation' : false,
'inheritParentMinWidth' : false,
'parentActiveCSSClass' : false,
'dataName' : 'tooltipContent',
'funcName' : 'tooltipFunc',
'func' : false,
'disableOnTouchDevice' : false,
'childActiveCSSClass' : false,
'destroyWhenDone': true,
'createOnLoad': false,
'replaceExisting': true, // should we stomp on existing tooltips?
'defaultType': 'html', // default tooltip type if not specified.
'responsiveMode': false,
}, options);
return this.each(function(){
var $target = $(this);
if ( settings.responsiveMode && !this.hasAttribute('data-tooltip-responsive' ) )
return;
if( $target.data('tooltip.settings') && !settings.replaceExisting)
return;
if( settings.useClickEvent )
{
$target.bind('click.tooltip', methods.show);
}
else
{
$target.bind('vgp_onfocus.tooltip', methods.show );
$target.bind('vgp_onblur.tooltip', methods.hide );
}
if( settings.useContextMenuEvent )
{
$target.bind('contextmenu.tooltip', methods.show);
}
if( settings.useMouseEnterEvent )
{
$target.bind('mouseenter.tooltip', methods.show);
}
if( settings.useMouseLeaveEvent )
{
$target.bind('mouseleave.tooltip', methods.hide);
}
if ( settings.disableOnTouchDevice )
{
// if we get a touch event, disable tooltip events on this object. This fires before mouse events, so we
// can prevent the hover from showing.
// (we could preventDefault(), but that will prevent the actual click event that's coming too)
$target.bind('touchstart.tooltip', function(e) { $target.data('inTouchEvent', true); });
$target.bind('mouseup.tooltip', function(e) { $target.data('inTouchEvent', false); });
}
$target.data('tooltip.settings', settings);
if( settings.createOnLoad )
methods.gettooltip( this, settings );
});
},
destroy : function() {
return this.each(function(){
$(window).unbind('.tooltip');
})
},
gettooltip : function( element, settings ) {
var $element= $(element);
var toolDiv = $element.data("tooltip.element");
if ( !toolDiv )
{
toolDiv = $('<div />');
if( settings.suppressOnClick )
{
toolDiv.bind('click.tooltip', jQuery.proxy(methods.hide, element));
}
toolDiv.hide();
toolDiv.addClass(settings.tooltipClass);
if ( $element.data( 'tooltip-class' ) )
{
toolDiv.addClass( $element.data( 'tooltip-class' ) );
}
toolDiv.css({
position: 'absolute',
'z-index': 1500
});
var type = $element.data('tooltip-type');
if( !type )
type = settings.defaultType;
var content = '';
if ( settings.dataAttr )
content = $element.attr( settings.dataAttr );
else if ( settings.dataName )
content = $element.data(settings.dataName);
if( type == 'text')
{
toolDiv.text( content );
}
else if ( type == 'selector' )
{
var toolElement = $( content, $element.parent() );
toolDiv.data( 'originalParent', toolElement.parent() );
toolDiv.append( toolElement.show() );
toolDiv.data( 'preserveContent', true );
}
else
{
toolDiv.html( content );
}
if ( typeof( g_TextFilter ) !== 'undefined' && g_TextFilter != null )
{
g_TextFilter.FilterElement( toolDiv );
}
$( settings.tooltipParent || $element.parent() ).append(toolDiv);
$element.data("tooltip.element", toolDiv);
}
var funcName = settings.funcName && $(element).data( settings.funcName );
if( funcName )
{
if( type == 'text')
toolDiv.text( window[funcName](element) );
else
toolDiv.html( window[funcName](element) );
}
if( settings.func )
settings.func.bind(toolDiv)(element);
return toolDiv;
},
updateposition : function() {
var newPosition = {};
var settings = $(this).data('tooltip.settings');
var toolDiv = methods.gettooltip( this, settings );
var parentPosition = $(this).offset();
if( settings.tooltipParent != 'body' )
parentPosition = $(this).position();
switch( settings.location )
{
case 'top':
newPosition = {
left: parentPosition.left + settings.offsetX,
top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY
};
break;
case 'bottom':
newPosition = {
left: parentPosition.left + settings.offsetX,
top: parentPosition.top + $(this).outerHeight() + settings.offsetY
};
break;
case 'bottom left':
newPosition = {
left: parentPosition.left + settings.offsetX - toolDiv.outerWidth(),
top: parentPosition.top + $(this).outerHeight() + settings.offsetY
};
break;
default:
console.log("Invalid location passed to v_tooltip: %s", settings.location);
}
// Correct for window size
if( settings.correctForScreenSize )
{
var rightEdge = newPosition.left + toolDiv.width();
var windowRightEdge = $(window).width() - settings.sizeCorrectionXPadding + $(window).scrollLeft();
var windowLeftEdge = $(window).scrollLeft() + settings.sizeCorrectionXPadding;
if( rightEdge > windowRightEdge )
newPosition.left = windowRightEdge - toolDiv.width() - settings.sizeCorrectionXPadding;
if( newPosition.left < windowLeftEdge )
newPosition.left = windowLeftEdge;
if ( newPosition.top < 0 )
newPosition.top = parentPosition.top + $(this).height() + settings.sizeCorrectionYPadding - settings.offsetY;
}
toolDiv.css(newPosition);
},
reposition : function(event) {
var newPosition = {};
var settings = $(this).data('tooltip.settings');
var toolDiv = methods.gettooltip( this, settings );
var parentPosition = $(this).offset();
if( settings.tooltipParent != 'body' )
parentPosition = $(this).position();
if( settings.trackMouse )
{
if ( settings.trackMouseCentered )
newPosition.left = event.pageX - toolDiv.outerWidth() / 2;
else
newPosition.left = event.pageX + settings.offsetY;
if ( settings.location == 'top' )
newPosition.top = event.pageY - toolDiv.outerHeight() + settings.offsetY;
else
newPosition.top = event.pageY + settings.offsetY;
} else {
switch( settings.location )
{
case 'top':
newPosition = {
left: parentPosition.left + settings.offsetX,
top: parentPosition.top - toolDiv.outerHeight() + settings.offsetY
};
break;
case 'bottom':
var newLeft = parentPosition.left;
newLeft += settings.offsetX;
newPosition = {
left: newLeft,
top: parentPosition.top + $(this).outerHeight() + settings.offsetY
};
break;
case 'bottom left':
newPosition = {
left: parentPosition.left + settings.offsetX - toolDiv.outerWidth() + $J(this).outerWidth(),
top: parentPosition.top + $(this).outerHeight() + settings.offsetY
};
break;
case 'mouse':
newPosition = {
left: event.pageX + settings.offsetX,
top: event.pageY + settings.offsetY
}
default:
console.log("Invalid location passed to v_tooltip: %s", settings.location);
}
}
// Correct for window size
if( settings.correctForScreenSize )
{
var rightEdge = newPosition.left + toolDiv.width();
var windowRightEdge = $(window).width() - settings.sizeCorrectionXPadding + $(window).scrollLeft();
var windowLeftEdge = $(window).scrollLeft() + settings.sizeCorrectionXPadding;
if( rightEdge > windowRightEdge )
newPosition.left = windowRightEdge - toolDiv.width() - settings.sizeCorrectionXPadding;
if( newPosition.left < windowLeftEdge )
newPosition.left = windowLeftEdge;
if ( newPosition.top < 0 )
newPosition.top = parentPosition.top + $(this).height() + settings.sizeCorrectionYPadding - settings.offsetY;
}
toolDiv.css(newPosition);
},
show : function(event) {
var settings = $(this).data('tooltip.settings') || {};
if ( settings.disableOnTouchDevice && $(this ).data('inTouchEvent') )
{
return;
}
var toolDiv = methods.gettooltip( this, settings );
if( toolDiv.is(':empty') )
return;
if( event.type == "click" && event.currentTarget != this )
return;
if( settings.suppressWhileToggled && $(this).hasClass('toggled') )
return false;
var tipElem = this;
toolDiv.find( 'img' ).on( 'load', function() { jQuery.proxy(methods.updateposition, tipElem)() } );
if( settings.preventDefault )
event.preventDefault();
if( settings.stopPropagation )
event.stopPropagation();
if( settings.parentActiveCSSClass )
$(this).addClass(settings.parentActiveCSSClass);
if( settings.inheritParentMinWidth )
{
var parentWidth = $(this).outerWidth();
var localPadding = toolDiv.outerWidth() - toolDiv.width();
toolDiv.css({'min-width': + (parentWidth - localPadding) + "px"});
}
if( settings.fadeSpeed > 0 )
{
toolDiv.stop(true, true);
toolDiv.fadeTo( settings.fadeSpeed, 1 );
}
else
toolDiv.show();
if( settings.allowHover )
{
if( settings.useClickEvent )
toolDiv.bind('click.tooltip', jQuery.proxy(methods.show, this));
else if( settings.useContextMenuEvent )
toolDiv.bind('contextmenu.tooltip', jQuery.proxy(methods.show, this));
else
toolDiv.bind('mouseenter.tooltip', jQuery.proxy(methods.show, this));
toolDiv.bind('mouseleave.tooltip', jQuery.proxy(methods.hide, this));
}
if( settings.trackMouse )
$(this).bind('mousemove.tooltip', methods.reposition);
else
jQuery.proxy(methods.reposition, this)(event);
if( settings.childActiveCSSClass )
{
toolDiv.css('opacity');
toolDiv.addClass(settings.childActiveCSSClass);
}
toolDiv.css('pointer-events','auto');
$(this).trigger('v_tooltip_shown', [ toolDiv ] );
},
hide : function(event) {
var toolDiv = $(this).data('tooltip.element');
var settings = $(this).data('tooltip.settings') || {};
// the element may not have been created yet - in which case there is nothing to hide
if ( !toolDiv || !toolDiv.length )
return;
if( event && event.type != 'click' )
{
// Moving between the click target and it's children
if( event.relatedTarget && ( this == event.relatedTarget || this.contains(event.relatedTarget) ) )
return;
// Moving to the tooltip or one of it's children
else if( event.relatedTarget && ( event.relatedTarget == toolDiv[0] || toolDiv[0].contains(event.relatedTarget ) ) )
return;
}
if( settings.trackMouse )
$(this).unbind('mousemove.tooltip');
toolDiv.unbind('mouseenter.tooltip');
toolDiv.unbind('mouseleave.tooltip');
if( settings.parentActiveCSSClass )
$(this).removeClass(settings.parentActiveCSSClass);
if( settings.fadeSpeed > 0 && toolDiv.is(':visible') )
{
toolDiv.stop();
toolDiv.fadeTo( settings.fadeSpeed, 0, function() { if( settings.destroyWhenDone ) methods.destroytooltip( toolDiv ) } );
} else if( settings.childActiveCSSClass )
{
toolDiv.css('opacity');
toolDiv.removeClass(settings.childActiveCSSClass);
// Note: This callback isn't reliable, so we may end up junking up the dom with a few spare copies
// Not a huge deal in most cases, but keep in mind if you're using this on pages which may exist for
// long periods of time without reloading.
if( settings.destroyWhenDone )
{
toolDiv.on ( "transitionend", function () {
methods.destroytooltip ( toolDiv )
} );
// Transitionend may not fire under various conditions, so JUST IN CASE....
setTimeout( function(){ methods.destroytooltip ( toolDiv ); }, 1000);
}
}
else
{
if( settings.destroyWhenDone )
methods.destroytooltip( toolDiv );
}
if( settings.destroyWhenDone )
$(this).removeData('tooltip.element');
else
toolDiv.css('pointer-events','none');
$(this).trigger( 'v_tooltip_hidden' );
},
destroytooltip: function( toolDiv )
{
if ( toolDiv )
{
if ( $(toolDiv ).data('preserveContent') )
{
$( toolDiv.data( 'originalParent') ).append( $(toolDiv ).children().hide());
}
$(toolDiv).remove();
}
}
};
$.fn.v_tooltip = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
// ReferenceError: navigator is not defined // ReferenceError: navigator is not defined
global.navigator={ userAgent: 'node.js', }; global.navigator={ userAgent: 'node.js', };
("./crypto/jsbn.js")
import {RSA} from "./crypto/rsa.js" import {RSA} from "./crypto/rsa.js"
// ReferenceError: fetch is not defined
// failed, reason: unable to verify the first certificate
import fetch from 'node-fetch'
function get_publickey_mod(usr)
{
return fetch(`https://api.steampowered.com/IAuthenticationService/GetPasswordRSAPublicKey/v1?origin=https:%2F%2Fstore.steampowered.com&input_json=%7B%22account_name%22:%22${usr}%22%7D`, {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"sec-ch-ua": "\"Microsoft Edge\";v=\"113\", \"Chromium\";v=\"113\", \"Not-A.Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
},
"referrer": "https://store.steampowered.com/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "omit"
}).then(resp => {
return resp.json()
})
.then(j => {
console.log('get_publickey_mod', j)
return j.response
})
// .catch(err => {
// })
}
function myLogin(usr, timestamp, encPwd) {
// let _ = {
// "account_name": usr,
// "encrypted_password": encPwd, // "dviiPhtg5dYHb9Pv4LSdg/5JTvoEdII+Wcwi8KS6DR3NZHBs4oqxej3NY64icVPDZz3nLqJJ1IWCDUsQgqG0KDa3lT6L9Q9BdgX+Rml63rzYa8lBtwS1dzDngfkDqPjVNY+CM8y7NE6SVJFloY5obiCBeIVqC1ldLTl31h01a6yMY5ylWc18mE6zlO3nrqA75f/YiiDgehp/06kEa6N/PTtF/QhvB01iLrGPGJya1JwnPpfvaX/nWTiy0z6XszuNojsw+oIHKml8VPvc1QPNXf1zeWGbfWURIFDTb63YLQ+w9GKJvEoQ2N3CHxTTaBWS+UrUGtJcA2gsBEuJURwHDw==",
// "encryption_timestamp": timestamp,
// "remember_login": true,
// "persistence": 1,
// "website_id": "Store",
// "device_details": {
// "device_friendly_name": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50",
// "platform_type": 2
// },
// "language": 6,
// "qos_level": 2
// }
fetch("https://api.steampowered.com/IAuthenticationService/BeginAuthSessionViaCredentials/v1", {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"content-type": "multipart/form-data; boundary=----WebKitFormBoundaryqdEn9VkmYdIUEwRi",
"sec-ch-ua": "\"Microsoft Edge\";v=\"113\", \"Chromium\";v=\"113\", \"Not-A.Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
},
"referrer": "https://store.steampowered.com/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": `------WebKitFormBoundaryqdEn9VkmYdIUEwRi\r\nContent-Disposition: form-data; name=\"input_json\"\r\n\r\n{\"account_name\":\"${usr}\",\"encrypted_password\":\"${encPwd}\",\"encryption_timestamp\":\"${timestamp}\",\"remember_login\":true,\"persistence\":1,\"website_id\":\"Store\",\"device_details\":{\"device_friendly_name\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50\",\"platform_type\":2},\"language\":6,\"qos_level\":2}\r\n------WebKitFormBoundaryqdEn9VkmYdIUEwRi--\r\n`,
"method": "POST",
"mode": "cors",
"credentials": "omit"
}).then(resp => {
return resp.json()
})
.then(j => {
console.log('myLogin', j)
return j
})
}
function main(usr, pwd, results) { function main(usr, pwd, results) {
pwd = pwd.replace( /[^\x00-\x7F]/g, '' ); // remove non-standard-ASCII characters pwd = pwd.replace( /[^\x00-\x7F]/g, '' ); // remove non-standard-ASCII characters
let m_strUsernameCanonical = usr.replace( /[^\x00-\x7F]/g, '' ); // remove non-standard-ASCII characters usr = usr.replace( /[^\x00-\x7F]/g, '' ); // remove non-standard-ASCII characters
var pubKey = RSA.getPublicKey(results.publickey_mod, results.publickey_exp);
var username = m_strUsernameCanonical;
var encryptedPassword = RSA.encrypt(pwd, pubKey);
var rgParameters = { get_publickey_mod(usr)
password: encryptedPassword, .then(results => {
username: username, // let data =new FormData();
// twofactorcode: authCode, // data.append('file',$("#realFile").files[0]);
// emailauth: form.elements['emailauth'] ? form.elements['emailauth'].value : '', // data.append('name','denzel'),
// loginfriendlyname: form.elements['loginfriendlyname'] ? form.elements['loginfriendlyname'].value : '', // data.append('flag','test')
// captchagid: this.m_gidCaptcha, var pubKey = RSA.getPublicKey(results.publickey_mod, results.publickey_exp);
captcha_text: '', var encryptedPassword = RSA.encrypt(pwd, pubKey);
// emailsteamid: this.m_steamidEmailAuth, myLogin(usr, results.timestamp, encryptedPassword)
rsatimestamp: results.timestamp,
remember_login: 'true' // var rgParameters = {
}; // password: encryptedPassword,
console.log(pubKey, rgParameters) // username: username,
// // twofactorcode: authCode,
// // emailauth: form.elements['emailauth'] ? form.elements['emailauth'].value : '',
// // loginfriendlyname: form.elements['loginfriendlyname'] ? form.elements['loginfriendlyname'].value : '',
// // captchagid: this.m_gidCaptcha,
// captcha_text: '',
// // emailsteamid: this.m_steamidEmailAuth,
// rsatimestamp: results.timestamp,
// remember_login: 'true'
// };
// console.log(pubKey, rgParameters)
})
} }
// ReferenceError: fetch is not defined // let results = {
// failed, reason: unable to verify the first certificate // publickey_mod: '123aee',
import fetch from 'node-fetch' // publickey_exp: '010001',
fetch("https://api.steampowered.com/IAuthenticationService/GetPasswordRSAPublicKey/v1?origin=https:%2F%2Fstore.steampowered.com&input_json=%7B%22account_name%22:%22111%22%7D", { // timestamp: '58350'
"headers": { // }
"accept": "application/json, text/plain, */*", // main('111', '222')
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", main('fgsc177824', '====')
"sec-ch-ua": "\"Microsoft Edge\";v=\"113\", \"Chromium\";v=\"113\", \"Not-A.Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site"
},
"referrer": "https://store.steampowered.com/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
// "credentials": "omit"
}).then(resp => resp.json)
.then(j => console.log(j))
let results = {
publickey_mod: '123aee',
publickey_exp: '010001',
timestamp: '58350'
}
// main('111', '222', results)
console.log("leave") console.log("leave")
/* /*
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册