PageSourceSearch

https://yp.mo/js/jquery.smartbanner.js

js yp.mo collected 2026-10-02 06:13:42 UTC 17,004 bytes, 469 lines download raw bytes

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

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.