1/*! 2 * jQuery Smart Banner 3 * Copyright (c) 2012 Arnold Daniels <[email protected]> 4 * Based on 'jQuery Smart Web App Banner' by Kurt Zenisek @ kzeni.com 5 */ 6(function(root, factory) { 7 if (typeof define == 'function' && define.amd) { 8 define(['jquery'], factory); 9 } else { 10 factory(root.jQuery); 11 } 12})(this, function($) { 13 var UA = navigator.userAgent; 14 var isEdge = /Edge/i.test(UA); 15 16 var SmartBanner = function(options) { 17 // Get the original margin-top of the HTML element so we can take that into account. 18 this.origHtmlMargin = parseFloat($('html').css('margin-top')); 19 this.options = $.extend({}, $.smartbanner.defaults, options); 20 21 // Check if it's already a standalone web app or running within a webui view of an app (not mobile safari). 22 var standalone = navigator.standalone; 23 24 // Detect banner type (iOS or Android). 25 if (this.options.force) { 26 this.type = this.options.force; 27 } 28 else if (UA.match(/Windows Phone/i) !== null && UA.match(/Edge|Touch/i) !== null) { 29 this.type = 'windows'; 30 } 31 else if (UA.match(/iPhone|iPod/i) !== null || (UA.match(/iPad/) && this.options.iOSUniversalApp)) { 32 if (UA.match(/Safari/i) !== null && 33 (UA.match(/CriOS/i) !== null || 34 UA.match(/FxiOS/i) != null || 35 window.Number(UA.substr(UA.indexOf('OS ') + 3, 3).replace('_', '.')) < 6)) { 36 // Check webview and native smart banner support (iOS 6+). 37 this.type = 'ios'; 38 } 39 } 40 else if (UA.match(/\bSilk\/(.*\bMobile Safari\b)?/) || UA.match(/\bKF\w/) || UA.match('Kindle Fire')) { 41 this.type = 'kindle'; 42 } 43 else if (UA.match(/Android/i) !== null) { 44 this.type = 'android'; 45 } 46 // Don't show banner if device isn't iOS or Android, website is loaded in app or user dismissed banner. 47 if (!this.type || standalone || this.getCookie('sb-closed') || this.getCookie('sb-installed')) { 48 return; 49 } 50 // Calculate scale. 51 this.scale = this.options.scale == 'auto' ? $(window).width() / window.screen.width : this.options.scale; 52 if (this.scale < 1) { 53 this.scale = 1; 54 } 55 // Get info from meta data. 56 var meta = $( 57 this.type == 'android' 58 ? 'meta[name="google-play-app"]' 59 : (this.type == 'ios' 60 ? 'meta[name="apple-itunes-app"]' 61 : (this.type == 'kindle' 62 ? 'meta[name="kindle-fire-app"]' 63 : 'meta[name="msApplication-ID"]' 64 ) 65 ) 66 ); 67 68 if (!meta.length) { 69 return; 70 } 71 // For Windows Store apps, get the PackageFamilyName for protocol launch. 72 if (this.type == 'windows') { 73 if (isEdge) { 74 this.appId = $('meta[name="msApplication-PackageEdgeName"]').attr('content'); 75 } 76 if (!this.appId) { 77 this.appId = $('meta[name="msApplication-PackageFamilyName"]').attr('content'); 78 } 79 } 80 else { 81 // Try to pull the appId out of the meta tag and store the result. 82 var parsedMetaContent = /app-id=([^\s,]+)/.exec(meta.attr('content')); 83 if (parsedMetaContent) { 84 this.appId = parsedMetaContent[1]; 85 } else { 86 return; 87 } 88 } 89 this.title = this.options.title 90 ? this.options.title 91 : (meta.data('title') || $('title').text().replace(/\s*[|\-·].*$/, '')); 92 93 this.author = this.options.author 94 ? this.options.author 95 : (meta.data('author') || ($('meta[name="author"]').length ? $('meta[name="author"]').attr('content') : window.location.hostname)); 96 97 this.iconUrl = meta.data('icon-url'); 98 this.price = meta.data('price'); 99 100 // Set default onInstall callback if not set in options. 101 if (typeof this.options.onInstall == 'function') { 102 this.options.onInstall = this.options.onInstall; 103 } else { 104 this.options.onInstall = function() {}; 105 } 106 // Set default onClose callback if not set in options. 107 if (typeof this.options.onClose == 'function') { 108 this.options.onClose = this.options.onClose; 109 } else { 110 this.options.onClose = function() {}; 111 } 112 // Create banner. 113 this.create(); 114 this.show(); 115 this.listen(); 116 }; 117 118 SmartBanner.prototype = { 119 120 constructor: SmartBanner, 121 122 create: function() { 123 var iconURL; 124 var price = this.price || this.options.price; 125 126 var link = this.options.url || (function() { 127 switch (this.type) { 128 case 'android': 129 return 'market://details?id='; 130 case 'kindle': 131 return 'amzn://apps/android?asin='; 132 case 'windows': 133 return isEdge 134 ? 'ms-windows-store://pdp/?productid=' 135 : 'ms-windows-store:navigate?appid='; 136 } 137 return 'https://itunes.apple.com/' + this.options.appStoreLanguage + '/app/id'; 138 }.call(this) + this.appId); 139 140 var inStore = !price ? '' : (function() { 141 var result = price + ' - '; 142 switch (this.type) { 143 case 'android': 144 return result + this.options.inGooglePlay; 145 case 'kindle': 146 return result + this.options.inAmazonAppStore; 147 case 'windows': 148 return result + this.options.inWindowsStore; 149 } 150 return result + this.options.inAppStore 151 }.call(this)); 152
153 var gloss = this.options.iconGloss == null 154 ? (this.type=='ios') 155 : this.options.iconGloss; 156 157 if (this.type == 'android' && this.options.GooglePlayParams) { 158 link += '&referrer=' + this.options.GooglePlayParams; 159 } 160 var banner = ( 161 '<div id="smartbanner" class="' + this.type + '">' + 162 '<div class="sb-container">' + 163 '<a href="#" class="sb-close">×</a>' + 164 '<span class="sb-icon"></span>' + 165 '<div class="sb-info">' + 166 '<strong>' + this.title + '</strong>' + 167 '<span>' + this.author + '</span>' + 168 '<span>' + inStore + '</span>' + 169 '</div>' + 170 '<a href="' + link + '" class="sb-button">' + 171 '<span>' + this.options.button + '</span>' + 172 '</a>' + 173 '</div>' + 174 '</div>' 175 ); 176 if (this.options.layer) { 177 $(this.options.appendToSelector).append(banner); 178 } else { 179 $(this.options.appendToSelector).prepend(banner); 180 } 181 if (this.options.icon) { 182 iconURL = this.options.icon; 183 } else if(this.iconUrl) { 184 iconURL = this.iconUrl; 185 } else if ($('link[rel="apple-touch-icon-precomposed"]').length > 0) { 186 iconURL = $('link[rel="apple-touch-icon-precomposed"]').attr('href'); 187 if (this.options.iconGloss == null) { 188 gloss = false; 189 } 190 } else if ($('link[rel="apple-touch-icon"]').length > 0) { 191 iconURL = $('link[rel="apple-touch-icon"]').attr('href'); 192 } else if ($('meta[name="msApplication-TileImage"]').length > 0) { 193 iconURL = $('meta[name="msApplication-TileImage"]').attr('content'); 194 } else if ($('meta[name="msapplication-TileImage"]').length > 0) { 195 // Redundant because ms docs show two case usages. 196 iconURL = $('meta[name="msapplication-TileImage"]').attr('content'); 197 } 198 if (iconURL) { 199 $('#smartbanner .sb-icon').css('background-image', 'url(' + iconURL + ')'); 200 if (gloss) { 201 $('#smartbanner .sb-icon').addClass('gloss'); 202 } 203 } else{ 204 $('#smartbanner').addClass('no-icon'); 205 } 206 this.bannerHeight = $('#smartbanner').outerHeight() + 2; 207 208 if (this.scale > 1) { 209 $('#smartbanner') 210 .css('top', parseFloat($('#smartbanner').css('top')) * this.scale) 211 .css('height', parseFloat($('#smartbanner').css('height')) * this.scale) 212 .hide(); 213 $('#smartbanner .sb-container') 214 .css('-webkit-transform', 'scale(' + this.scale + ')') 215 .css('-msie-transform', 'scale(' + this.scale + ')') 216 .css('-moz-transform', 'scale(' + this.scale + ')') 217 .css('width', $(window).width() / this.scale); 218 } 219 $('#smartbanner') 220 .css('position', this.options.layer ? 'absolute' : 'static'); 221 }, 222 223 listen: function() { 224 $('#smartbanner .sb-close').on('click', $.proxy(this.close, this)); 225 $('#smartbanner .sb-button').on('click', $.proxy(this.install, this)); 226 }, 227 228 show: function(callback) { 229 var banner = $('#smartbanner'); 230 banner.stop(); 231 232 if (this.options.layer) { 233 banner 234 .animate({ top: 0, display: 'block' }, this.options.speedIn) 235 .addClass('shown') 236 .show(); 237 $(this.pushSelector) 238 .animate({ 239 paddingTop: this.origHtmlMargin + (this.bannerHeight * this.scale) 240 }, this.options.speedIn, 'swing', callback); 241 } 242 else { 243 if ($.support.transition) { 244 banner.animate({ top: 0 }, this.options.speedIn).addClass('shown'); 245 var transitionCallback = function() { 246 $('html').removeClass('sb-animation'); 247 if (callback) { 248 callback(); 249 } 250 }; 251 $(this.pushSelector) 252 .addClass('sb-animation') 253 .one($.support.transition.end, transitionCallback) 254 .emulateTransitionEnd(this.options.speedIn) 255 .css('margin-top', this.origHtmlMargin + (this.bannerHeight * this.scale)); 256 } 257 else { 258 banner 259 .slideDown(this.options.speedIn) 260 .addClass('shown'); 261 } 262 } 263 }, 264 265 hide: function(callback) { 266 var banner = $('#smartbanner'); 267 banner.stop(); 268 269 if (this.options.layer) { 270 banner.animate({ 271 top: -1 * this.bannerHeight * this.scale, 272 display: 'block' 273 }, this.options.speedIn) 274 .removeClass('shown'); 275 276 $(this.pushSelector) 277 .animate({ 278 paddingTop: this.origHtmlMargin 279 }, this.options.speedIn, 'swing', callback); 280 } 281 else {
282 if ($.support.transition) { 283 if (this.type !== 'android') { 284 banner 285 .css('top', -1 * this.bannerHeight * this.scale) 286 .removeClass('shown'); 287 } 288 else { 289 banner 290 .css({display:'none'}) 291 .removeClass('shown'); 292 } 293 var transitionCallback = function() { 294 $('html').removeClass('sb-animation'); 295 if (callback) { 296 callback(); 297 } 298 }; 299 $(this.pushSelector) 300 .addClass('sb-animation') 301 .one($.support.transition.end, transitionCallback) 302 .emulateTransitionEnd(this.options.speedOut) 303 .css('margin-top', this.origHtmlMargin); 304 } 305 else { 306 banner.slideUp(this.options.speedOut).removeClass('shown'); 307 } 308 } 309 }, 310 311 close: function(e) { 312 e.preventDefault(); 313 this.hide(); 314 this.setCookie('sb-closed', 'true', this.options.daysHidden); 315 this.options.onClose(e); 316 }, 317 318 install: function(e) { 319 if (this.options.hideOnInstall) { 320 this.hide(); 321 } 322 this.setCookie('sb-installed', 'true', this.options.daysReminder); 323 this.options.onInstall(e); 324 }, 325 326 setCookie: function(name, value, exdays) { 327 var exdate = new Date(); 328 exdate.setDate(exdate.getDate() + exdays); 329 value = encodeURI(value) + ((exdays == null) ? '' : '; expires=' + exdate.toUTCString()); 330 document.cookie = name + '=' + value + '; path=/;'; 331 }, 332 333 getCookie: function(name) { 334 var i, x, y, ARRcookies = document.cookie.split(';'); 335 for (i = 0; i < ARRcookies.length; i++) { 336 x = ARRcookies[i].substr(0, ARRcookies[i].indexOf('=')); 337 y = ARRcookies[i].substr(ARRcookies[i].indexOf('=') + 1); 338 x = x.replace(/^\s+|\s+$/g, ''); 339 if (x == name) { 340 return decodeURI(y); 341 } 342 } 343 return null; 344 }, 345 346 // Demo only. 347 switchType: function() { 348 var that = this; 349 350 this.hide(function() { 351 that.type = that.type == 'android' ? 'ios' : 'android'; 352 var meta = $(that.type == 'android' ? 'meta[name="google-play-app"]' : 'meta[name="apple-itunes-app"]').attr('content'); 353 that.appId = /app-id=([^\s,]+)/.exec(meta)[1]; 354 355 $('#smartbanner').detach(); 356 that.create(); 357 that.show(); 358 }); 359 } 360 }; 361 362 $.smartbanner = function(option) { 363 var $window = $(window); 364 var data = $window.data('smartbanner'); 365 var options = typeof option == 'object' && option; 366 if (!data) { 367 $window.data('smartbanner', (data = new SmartBanner(options))); 368 } 369 if (typeof option == 'string') { 370 data[option](); 371 } 372 }; 373 374 // override these globally if you like (they are all optional) 375 $.smartbanner.defaults = { 376 title: null, // What the title of the app should be in the banner (defaults to <title>) 377 author: null, // What the author of the app should be in the banner (defaults to <meta name="author"> or hostname) 378 price: 'FREE', // Price of the app 379 appStoreLanguage: 'us', // Language code for App Store 380 inAppStore: 'On the App Store', // Text of price for iOS 381 inGooglePlay: 'In Google Play', // Text of price for Android 382 inAmazonAppStore: 'In the Amazon Appstore', 383 inWindowsStore: 'In the Windows Store', //Text of price for Windows 384 GooglePlayParams: null, // Aditional parameters for the market 385 icon: null, // The URL of the icon (defaults to <meta name="apple-touch-icon">) 386 iconGloss: null, // Force gloss effect for iOS even for precomposed 387 button: 'VIEW', // Text for the install button 388 url: null, // The URL for the button. Keep null if you want the button to link to the app store. 389 scale: 'auto', // Scale based on viewport size (set to 1 to disable) 390 speedIn: 300, // Show animation speed of the banner 391 speedOut: 400, // Close animation speed of the banner 392 daysHidden: 15, // Duration to hide the banner after being closed (0 = always show banner) 393 daysReminder: 90, // Duration to hide the banner after "VIEW" is clicked *separate from when the close button is clicked* (0 = always show banner) 394 force: null, // Choose 'ios', 'android' or 'windows'. Don't do a browser check, just always show this banner 395 hideOnInstall: true, // Hide the banner after "VIEW" is clicked. 396 layer: false, // Display as overlay layer or slide down the page 397 iOSUniversalApp: true, // If the iOS App is a universal app for both iPad and iPhone, display Smart Banner to iPad users, too. 398 appendToSelector: 'body', //Append the banner to a specific selector 399 pushSelector: 'html' // What element is going to push the site content down; this is where the banner append animation will start. 400 }; 401 402 $.smartbanner.Constructor = SmartBanner; 403 404 // ============================================================ 405 // Bootstrap transition 406 // Copyright 2011-2014 Twitter, Inc. 407 // Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 408 409 function transitionEnd () { 410 var el = document.createElement('smartbanner'); 411 412 var transEndEventNames = { 413 WebkitTransition: 'webkitTransitionEnd', 414 MozTransition: 'transitionend', 415 OTransition: 'oTransitionEnd otransitionend', 416 transition: 'transitionend' 417 }; 418 419 for (var name in transEndEventNames) { 420 if (el.style[name] !== undefined) { 421 return {end: transEndEventNames[name]}; 422 } 423 } 424 // Explicit for ie8. 425 return false;
426 } 427 if ($.support.transition !== undefined) { 428 // Prevent conflict with Twitter Bootstrap. 429 return; 430 } 431 432 // http://blog.alexmaccaw.com/css-transitions 433 $.fn.emulateTransitionEnd = function(duration) { 434 var called = false, $el = this; 435 $(this).one($.support.transition.end, function() { 436 called = true; 437 }); 438 var callback = function() { 439 if (!called) { 440 $($el).trigger($.support.transition.end); 441 } 442 }; 443 setTimeout(callback, duration); 444 return this; 445 }; 446 447 $(function() { 448 $.support.transition = transitionEnd(); 449 }); 450 // ============================================================ 451});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.