add-to-homescreen-rails 2.0.11 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 33563cc6a4d40df0a1db3bd9164dbcb510dbd3b1
4
- data.tar.gz: 9b8548bc42c1ed0a38022554f7ded14254698e9e
3
+ metadata.gz: 43848950e30ebbafde25389ad2d854996a5bc41c
4
+ data.tar.gz: c7b0e65e26182693cd08415049890ff67d00860e
5
5
  SHA512:
6
- metadata.gz: b201d11dfbfc19e09aceb307c8dfc4d944ae205ac93a18add684b3b1e528769fd5d73e4ba2d153e2cfa828e9f736f082d46016874a10b2ebce100f4a0b07cb92
7
- data.tar.gz: 83b06828330ace16c2d363410cf67bd1d1d0fd0a218cf302faca997d7f985965696e6ff14fe6548a3a1b8c42b15542568a29e6b6a4b0967f60c9017b0f1ab8fa
6
+ metadata.gz: 653f4252b0dc5a2f012f7ba277d73c2861dbec2d29764d51c9780c3e2eb64bae53e2450b7eebe81107f0bb34578bac1c034f488677af0496efdc2ec977c5deb9
7
+ data.tar.gz: 03321d356ed81b371acfa3df6193b4b44f92131f5395f80cad3cc75e07e45fdb851a3f280c5724dcf1f2c9773c12968da9ec71d28250146c13fb677ef41bc18d
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ *IMPORTANT:* If you are upgrading please note that there are major changes to the way version 3 works. Make sure you read the updated documentation and also update your rails require statement as shown below.
2
+
1
3
  # 'Add to Home screen' for Rails
2
4
 
3
5
  This gem vendors the 'Add to Home screen' assets for Rails 3.1 and greater.
@@ -16,21 +18,19 @@ For the orignal javascript and css file go to his [Github repository](https://gi
16
18
  In your Gemfile, add:
17
19
 
18
20
  ```ruby
19
- group :assets do
20
- gem 'add-to-homescreen-rails'
21
- end
21
+ gem 'add-to-homescreen-rails'
22
22
  ```
23
23
 
24
24
  You can include it by adding the following to your javascript file:
25
25
 
26
26
  ```javascript
27
- //= require add2home
27
+ //= require addtohomescreen
28
28
  ```
29
29
 
30
30
  And to the css file:
31
31
 
32
32
  ```css
33
33
  /*
34
- *= require add2home
34
+ *= require addtohomescreen
35
35
  */
36
36
  ```
@@ -0,0 +1,552 @@
1
+ /* Add to Homescreen v3.0.0 ~ (c) 2014 Matteo Spinelli ~ @license: http://cubiq.org/license */
2
+ (function (window, document) {
3
+ /*
4
+ _ _ _____ _____
5
+ ___ _| |_| |_ _|___| | |___ _____ ___ ___ ___ ___ ___ ___ ___
6
+ | .'| . | . | | | | . | | . | | -_|_ -| _| _| -_| -_| |
7
+ |__,|___|___| |_| |___|__|__|___|_|_|_|___|___|___|_| |___|___|_|_|
8
+ by Matteo Spinelli ~ http://cubiq.org
9
+ */
10
+
11
+ // Check if document is loaded, needed by autostart
12
+ var _DOMReady = false;
13
+ window.addEventListener('load', loaded, false);
14
+ function loaded () {
15
+ window.removeEventListener('load', loaded, false);
16
+ _DOMReady = true;
17
+ }
18
+
19
+ // regex used to detect if app has been added to the homescreen
20
+ var _reSmartURL = /\/ath(\/)?$/;
21
+ var _reQueryString = /([\?&]ath=[^&]*$|&ath=[^&]*(&))/;
22
+
23
+ // singleton
24
+ var _instance;
25
+ function ath (options) {
26
+ _instance = _instance || new ath.Class(options);
27
+
28
+ return _instance;
29
+ }
30
+
31
+ // message in all supported languages
32
+ ath.intl = {
33
+ en_us: {
34
+ message: 'To add this web app to the home screen: tap %icon and then <strong>%action</strong>.',
35
+ action: { ios: 'Add to Home Screen', android: 'Add to homescreen', windows: 'pin to start' }
36
+ },
37
+
38
+ it_it: {
39
+ message: 'Per Aggiungere questa web app alla schermata iniziale: premi %icon e poi <strong>%action</strong>.',
40
+ action: { ios: 'Aggiungi a Home', android: 'Aggiungi alla homescreen', windows: 'pin to start' }
41
+ }
42
+ };
43
+
44
+ // default options
45
+ ath.defaults = {
46
+ appID: 'org.cubiq.addtohome', // local storage name (no need to change)
47
+ fontSize: 15, // base font size, used to properly resize the popup based on viewport scale factor
48
+ debug: false, // override browser checks
49
+ modal: false, // prevent further actions until the message is closed
50
+ mandatory: false, // you can't proceed if you don't add the app to the homescreen
51
+ autostart: true, // show the message automatically
52
+ skipFirstVisit: false, // show only to returning visitors (ie: skip the first time you visit)
53
+ startDelay: 1, // display the message after that many seconds from page load
54
+ lifespan: 15, // life of the message in seconds
55
+ displayPace: 1440, // minutes before the message is shown again (0: display every time, default 24 hours)
56
+ maxDisplayCount: 0, // absolute maximum number of times the message will be shown to the user (0: no limit)
57
+ icon: true, // add touch icon to the message
58
+ message: '', // the message can be customized
59
+ validLocation: [], // list of pages where the message will be shown (array of regexes)
60
+ onInit: null, // executed on instance creation
61
+ onShow: null, // executed when the message is shown
62
+ onRemove: null, // executed when the message is removed
63
+ onAdd: null, // when the application is launched the first time from the homescreen (guesstimate)
64
+ onPrivate: null, // executed if user is in private mode
65
+ detectHomescreen: false // try to detect if the site has been added to the homescreen (false | true | 'hash' | 'queryString' | 'smartURL')
66
+ };
67
+
68
+ // browser info and capability
69
+ var _ua = window.navigator.userAgent;
70
+ var _nav = window.navigator;
71
+ _extend(ath, {
72
+ hasToken: document.location.hash == '#ath' || _reSmartURL.test(document.location.href) || _reQueryString.test(document.location.search),
73
+ isRetina: window.devicePixelRatio && window.devicePixelRatio > 1,
74
+ isIDevice: (/iphone|ipod|ipad/i).test(_ua),
75
+ isMobileChrome: _ua.indexOf('Android') > -1 && (/Chrome\/[.0-9]*/).test(_ua),
76
+ isMobileIE: _ua.indexOf('Windows Phone') > -1,
77
+ language: _nav.language && _nav.language.toLowerCase().replace('-', '_')
78
+ });
79
+
80
+ ath.isMobileSafari = ath.isIDevice && _ua.indexOf('Safari') > -1 && _ua.indexOf('CriOS') < 0;
81
+ ath.OS = ath.isIDevice ? 'ios' : ath.isMobileChrome ? 'android' : ath.isMobileIE ? 'windows' : 'unsupported';
82
+
83
+ ath.OSVersion = _ua.match(/(OS|Android) (\d+[_\.]\d+)/);
84
+ ath.OSVersion = ath.OSVersion && ath.OSVersion[2] ? +ath.OSVersion[2].replace('_', '.') : 0;
85
+
86
+ ath.isStandalone = window.navigator.standalone || ( ath.isMobileChrome && ( screen.height - document.documentElement.clientHeight < 40 ) ); // TODO: check the lame polyfill
87
+ ath.isTablet = (ath.isMobileSafari && _ua.indexOf('iPad') > -1) || (ath.isMobileChrome && _ua.indexOf('Mobile') < 0);
88
+
89
+ ath.isCompatible = (ath.isIDevice && ath.OSVersion >= 6) || ath.isMobileChrome; // TODO: add winphone
90
+
91
+ // falls back to en_us if language is unsupported
92
+ ath.language = ath.language && ath.language in ath.intl ? ath.language : 'en_us';
93
+
94
+ var _defaultSession = {
95
+ lastDisplayTime: 0, // last time we displayed the message
96
+ returningVisitor: false, // is this the first time you visit
97
+ displayCount: 0, // number of times the message has been shown
98
+ optedout: false, // has the user opted out
99
+ added: false // has been actually added to the homescreen
100
+ };
101
+
102
+ ath.removeSession = function (appID) {
103
+ try {
104
+ localStorage.removeItem(appID || ath.defaults.appID);
105
+ } catch (e) {
106
+ // we are most likely in private mode
107
+ }
108
+ };
109
+
110
+ ath.Class = function (options) {
111
+ // merge default options with user config
112
+ this.options = _extend({}, ath.defaults);
113
+ _extend(this.options, options);
114
+
115
+ // normalize some options
116
+ this.options.mandatory = this.options.mandatory && ( 'standalone' in window.navigator || this.options.debug );
117
+ this.options.modal = this.options.modal || this.options.mandatory;
118
+ if ( this.options.mandatory ) {
119
+ this.options.startDelay = -0.5; // make the popup hasty
120
+ }
121
+ this.options.detectHomescreen = this.options.detectHomescreen === true ? 'hash' : this.options.detectHomescreen;
122
+
123
+ // setup the debug environment
124
+ if ( this.options.debug ) {
125
+ ath.isCompatible = true;
126
+ ath.OS = typeof this.options.debug == 'string' ? this.options.debug : ath.OS == 'unsupported' ? 'android' : ath.OS;
127
+ ath.OSVersion = ath.OS == 'ios' ? '7' : '4';
128
+ }
129
+
130
+ // the element the message will be appended to
131
+ this.container = document.documentElement;
132
+
133
+ // load session
134
+ this.session = JSON.parse(localStorage.getItem(this.options.appID));
135
+
136
+ // user most likely came from a direct link containing our token, we don't need it and we remove it
137
+ if ( ath.hasToken && ( !ath.isCompatible || !this.session ) ) {
138
+ ath.hasToken = false;
139
+ _removeToken();
140
+ }
141
+
142
+ // the device is not supported
143
+ if ( !ath.isCompatible ) {
144
+ return;
145
+ }
146
+
147
+ this.session = this.session || _defaultSession;
148
+
149
+ // check if we can use the local storage
150
+ try {
151
+ localStorage.setItem(this.options.appID, JSON.stringify(this.session));
152
+ ath.hasLocalStorage = true;
153
+ } catch (e) {
154
+ // we are most likely in private mode
155
+ ath.hasLocalStorage = false;
156
+
157
+ if ( this.options.onPrivate ) {
158
+ this.options.onPrivate.call(this);
159
+ }
160
+ }
161
+
162
+ // check if this is a valid location
163
+ var isValidLocation = !this.options.validLocation.length;
164
+ for ( var i = this.options.validLocation.length; i--; ) {
165
+ if ( this.options.validLocation[i].test(document.location.href) ) {
166
+ isValidLocation = true;
167
+ break;
168
+ }
169
+ }
170
+
171
+ // check compatibility with old versions of add to homescreen. Opt-out if an old session is found
172
+ if ( localStorage.getItem('addToHome') ) {
173
+ this.optOut();
174
+ }
175
+
176
+ // critical errors:
177
+ // user opted out, already added to the homescreen, not a valid location
178
+ if ( this.session.optedout || this.session.added || !isValidLocation ) {
179
+ return;
180
+ }
181
+
182
+ // check if the app is in stand alone mode
183
+ if ( ath.isStandalone ) {
184
+ // execute the onAdd event if we haven't already
185
+ if ( !this.session.added ) {
186
+ this.session.added = true;
187
+ this.updateSession();
188
+
189
+ if ( this.options.onAdd && ath.hasLocalStorage ) { // double check on localstorage to avoid multiple calls to the custom event
190
+ this.options.onAdd.call(this);
191
+ }
192
+ }
193
+
194
+ return;
195
+ }
196
+
197
+ // (try to) check if the page has been added to the homescreen
198
+ if ( this.options.detectHomescreen ) {
199
+ // the URL has the token, we are likely coming from the homescreen
200
+ if ( ath.hasToken ) {
201
+ _removeToken(); // we don't actually need the token anymore, we remove it to prevent redistribution
202
+
203
+ // this is called the first time the user opens the app from the homescreen
204
+ if ( !this.session.added ) {
205
+ this.session.added = true;
206
+ this.updateSession();
207
+
208
+ if ( this.options.onAdd && ath.hasLocalStorage ) { // double check on localstorage to avoid multiple calls to the custom event
209
+ this.options.onAdd.call(this);
210
+ }
211
+ }
212
+
213
+ return;
214
+ }
215
+
216
+ // URL doesn't have the token, so add it
217
+ if ( this.options.detectHomescreen == 'hash' ) {
218
+ history.replaceState('', window.document.title, document.location.href + '#ath');
219
+ } else if ( this.options.detectHomescreen == 'smartURL' ) {
220
+ history.replaceState('', window.document.title, document.location.href.replace(/(\/)?$/, '/ath$1'));
221
+ } else {
222
+ history.replaceState('', window.document.title, document.location.href + (document.location.search ? '&' : '?' ) + 'ath=');
223
+ }
224
+ }
225
+
226
+ // check if this is a returning visitor
227
+ if ( !this.session.returningVisitor ) {
228
+ this.session.returningVisitor = true;
229
+ this.updateSession();
230
+
231
+ // we do not show the message if this is your first visit
232
+ if ( this.options.skipFirstVisit ) {
233
+ return;
234
+ }
235
+ }
236
+
237
+ // we do no show the message in private mode
238
+ if ( !ath.hasLocalStorage ) {
239
+ return;
240
+ }
241
+
242
+ // all checks passed, ready to display
243
+ this.ready = true;
244
+
245
+ if ( this.options.onInit ) {
246
+ this.options.onInit.call(this);
247
+ }
248
+
249
+ if ( this.options.autostart ) {
250
+ this.show();
251
+ }
252
+ };
253
+
254
+ ath.Class.prototype = {
255
+ // event type to method conversion
256
+ events: {
257
+ load: '_delayedShow',
258
+ error: '_delayedShow',
259
+ orientationchange: 'resize',
260
+ resize: 'resize',
261
+ scroll: 'resize',
262
+ click: 'remove',
263
+ touchmove: '_preventDefault',
264
+ transitionend: '_removeElements',
265
+ webkitTransitionEnd: '_removeElements',
266
+ MSTransitionEnd: '_removeElements'
267
+ },
268
+
269
+ handleEvent: function (e) {
270
+ var type = this.events[e.type];
271
+ if ( type ) {
272
+ this[type](e);
273
+ }
274
+ },
275
+
276
+ show: function (force) {
277
+ // in autostart mode wait for the document to be ready
278
+ if ( this.options.autostart && !_DOMReady ) {
279
+ setTimeout(this.show.bind(this), 50);
280
+ return;
281
+ }
282
+
283
+ // message already on screen
284
+ if ( this.shown ) {
285
+ return;
286
+ }
287
+
288
+ var now = Date.now();
289
+ var lastDisplayTime = this.session.lastDisplayTime;
290
+
291
+ if ( force !== true ) {
292
+ // this is needed if autostart is disabled and you programmatically call the show() method
293
+ if ( !this.ready ) {
294
+ return;
295
+ }
296
+
297
+ // we obey the display pace (prevent the message to popup too often)
298
+ if ( now - lastDisplayTime < this.options.displayPace * 60000 ) {
299
+ return;
300
+ }
301
+
302
+ // obey the maximum number of display count
303
+ if ( this.options.maxDisplayCount && this.session.displayCount >= this.options.maxDisplayCount ) {
304
+ return;
305
+ }
306
+ }
307
+
308
+ this.shown = true;
309
+
310
+ // increment the display count
311
+ this.session.lastDisplayTime = now;
312
+ this.session.displayCount++;
313
+ this.updateSession();
314
+
315
+ // try to get the highest resolution application icon
316
+ if ( !this.applicationIcon ) {
317
+ if ( ath.OS == 'ios' ) {
318
+ this.applicationIcon = document.querySelector('head link[rel^=apple-touch-icon][sizes="152x152"],head link[rel^=apple-touch-icon][sizes="144x144"],head link[rel^=apple-touch-icon][sizes="120x120"],head link[rel^=apple-touch-icon][sizes="114x114"],head link[rel^=apple-touch-icon]');
319
+ } else {
320
+ this.applicationIcon = document.querySelector('head link[rel^="shortcut icon"][sizes="196x196"],head link[rel^=apple-touch-icon]');
321
+ }
322
+ }
323
+
324
+ var message = '';
325
+
326
+ if ( this.options.message in ath.intl ) { // you can force the locale
327
+ message = ath.intl[this.options.message].message.replace('%action', ath.intl[this.options.message].action[ath.OS]);
328
+ } else if ( this.options.message !== '' ) { // or use a custom message
329
+ message = this.options.message;
330
+ } else { // otherwise we use our message
331
+ message = ath.intl[ath.language].message.replace('%action', ath.intl[ath.language].action[ath.OS]);
332
+ }
333
+
334
+ // add the action icon
335
+ message = '<p>' + message.replace('%icon', '<span class="ath-action-icon">icon</span>') + '</p>';
336
+
337
+ // create the message container
338
+ this.viewport = document.createElement('div');
339
+ this.viewport.className = 'ath-viewport';
340
+ if ( this.options.modal ) {
341
+ this.viewport.className += ' ath-modal';
342
+ }
343
+ if ( this.options.mandatory ) {
344
+ this.viewport.className += ' ath-mandatory';
345
+ }
346
+ this.viewport.style.position = 'absolute';
347
+
348
+ // create the actual message element
349
+ this.element = document.createElement('div');
350
+ this.element.className = 'ath-container ath-' + ath.OS + ' ath-' + ath.OS + (ath.OSVersion + '').substr(0,1) + ' ath-' + (ath.isTablet ? 'tablet' : 'phone');
351
+ this.element.style.cssText = '-webkit-transition-property:-webkit-transform,opacity;-webkit-transition-duration:0;-webkit-transform:translate3d(0,0,0);transition-property:transform,opacity;transition-duration:0;transform:translate3d(0,0,0);-webkit-transition-timing-function:ease-out';
352
+ this.element.style.webkitTransform = 'translate3d(0,-' + window.innerHeight + 'px,0)';
353
+ this.element.style.webkitTransitionDuration = '0s';
354
+
355
+ // add the application icon
356
+ if ( this.options.icon && this.applicationIcon ) {
357
+ this.element.className += ' ath-icon';
358
+ this.img = document.createElement('img');
359
+ this.img.className = 'ath-application-icon';
360
+ this.img.addEventListener('load', this, false);
361
+ this.img.addEventListener('error', this, false);
362
+
363
+ this.img.src = this.applicationIcon.href;
364
+ this.element.appendChild(this.img);
365
+ }
366
+
367
+ this.element.innerHTML += message;
368
+
369
+ // we are not ready to show, place the message out of sight
370
+ this.viewport.style.left = '-99999em';
371
+
372
+ // attach all elements to the DOM
373
+ this.viewport.appendChild(this.element);
374
+ this.container.appendChild(this.viewport);
375
+
376
+ // if we don't have to wait for an image to load, show the message right away
377
+ if ( !this.img ) {
378
+ this._delayedShow();
379
+ }
380
+ },
381
+
382
+ _delayedShow: function (e) {
383
+ setTimeout(this._show.bind(this), this.options.startDelay * 1000 + 500);
384
+ },
385
+
386
+ _show: function () {
387
+ var that = this;
388
+
389
+ // update the viewport size and orientation
390
+ this.updateViewport();
391
+
392
+ // reposition/resize the message on orientation change
393
+ window.addEventListener('resize', this, false);
394
+ window.addEventListener('scroll', this, false);
395
+ window.addEventListener('orientationchange', this, false);
396
+
397
+ if ( this.options.modal ) {
398
+ // lock any other interaction
399
+ document.addEventListener('touchmove', this, true);
400
+ }
401
+
402
+ // Enable closing after 1 second
403
+ if ( !this.options.mandatory ) {
404
+ setTimeout(function () {
405
+ that.element.addEventListener('click', that, true);
406
+ }, 1000);
407
+ }
408
+
409
+ // kick the animation
410
+ setTimeout(function () {
411
+ that.element.style.webkitTransform = 'translate3d(0,0,0)';
412
+ that.element.style.webkitTransitionDuration = '1.2s';
413
+ }, 0);
414
+
415
+ // set the destroy timer
416
+ if ( this.options.lifespan ) {
417
+ this.removeTimer = setTimeout(this.remove.bind(this), this.options.lifespan * 1000);
418
+ }
419
+
420
+ // fire the custom onShow event
421
+ if ( this.options.onShow ) {
422
+ this.options.onShow.call(this);
423
+ }
424
+ },
425
+
426
+ remove: function () {
427
+ clearTimeout(this.removeTimer);
428
+
429
+ // clear up the event listeners
430
+ if ( this.img ) {
431
+ this.img.removeEventListener('load', this, false);
432
+ this.img.removeEventListener('error', this, false);
433
+ }
434
+
435
+ window.removeEventListener('resize', this, false);
436
+ window.removeEventListener('scroll', this, false);
437
+ window.removeEventListener('orientationchange', this, false);
438
+ document.removeEventListener('touchmove', this, true);
439
+ this.element.removeEventListener('click', this, true);
440
+
441
+ // remove the message element on transition end
442
+ this.element.addEventListener('transitionend', this, false);
443
+ this.element.addEventListener('webkitTransitionEnd', this, false);
444
+ this.element.addEventListener('MSTransitionEnd', this, false);
445
+
446
+ // start the fade out animation
447
+ this.element.style.webkitTransitionDuration = '0.3s';
448
+ this.element.style.opacity = '0';
449
+ },
450
+
451
+ _removeElements: function () {
452
+ this.element.removeEventListener('transitionend', this, false);
453
+ this.element.removeEventListener('webkitTransitionEnd', this, false);
454
+ this.element.removeEventListener('MSTransitionEnd', this, false);
455
+
456
+ // remove the message from the DOM
457
+ this.container.removeChild(this.viewport);
458
+
459
+ this.shown = false;
460
+
461
+ // fire the custom onRemove event
462
+ if ( this.options.onRemove ) {
463
+ this.options.onRemove.call(this);
464
+ }
465
+ },
466
+
467
+ updateViewport: function () {
468
+ if ( !this.shown ) {
469
+ return;
470
+ }
471
+
472
+ this.viewport.style.width = window.innerWidth + 'px';
473
+ this.viewport.style.height = window.innerHeight + 'px';
474
+ this.viewport.style.left = window.scrollX + 'px';
475
+ this.viewport.style.top = window.scrollY + 'px';
476
+
477
+ var clientWidth = document.documentElement.clientWidth;
478
+
479
+ this.orientation = clientWidth > document.documentElement.clientHeight ? 'landscape' : 'portrait';
480
+
481
+ var screenWidth = ath.OS == 'ios' ? this.orientation == 'portrait' ? screen.width : screen.height : screen.width;
482
+ this.scale = screen.width > clientWidth ? 1 : screenWidth / window.innerWidth;
483
+
484
+ this.element.style.fontSize = this.options.fontSize / this.scale + 'px';
485
+ },
486
+
487
+ resize: function () {
488
+ clearTimeout(this.resizeTimer);
489
+ this.resizeTimer = setTimeout(this.updateViewport.bind(this), 100);
490
+ },
491
+
492
+ updateSession: function () {
493
+ if ( ath.hasLocalStorage === false ) {
494
+ return;
495
+ }
496
+
497
+ localStorage.setItem(this.options.appID, JSON.stringify(this.session));
498
+ },
499
+
500
+ clearSession: function () {
501
+ this.session = _defaultSession;
502
+ this.updateSession();
503
+ },
504
+
505
+ optOut: function () {
506
+ this.session.optedout = true;
507
+ this.updateSession();
508
+ },
509
+
510
+ optIn: function () {
511
+ this.session.optedout = false;
512
+ this.updateSession();
513
+ },
514
+
515
+ clearDisplayCount: function () {
516
+ this.session.displayCount = 0;
517
+ this.updateSession();
518
+ },
519
+
520
+ _preventDefault: function (e) {
521
+ e.preventDefault();
522
+ e.stopPropagation();
523
+ }
524
+ };
525
+
526
+ // utility
527
+ function _extend (target, obj) {
528
+ for ( var i in obj ) {
529
+ target[i] = obj[i];
530
+ }
531
+
532
+ return target;
533
+ }
534
+
535
+ function _removeToken () {
536
+ if ( document.location.hash == '#ath' ) {
537
+ history.replaceState('', window.document.title, document.location.href.split('#')[0]);
538
+ }
539
+
540
+ if ( _reSmartURL.test(document.location.href) ) {
541
+ history.replaceState('', window.document.title, document.location.href.replace(_reSmartURL, '$1'));
542
+ }
543
+
544
+ if ( _reQueryString.test(document.location.search) ) {
545
+ history.replaceState('', window.document.title, document.location.href.replace(_reQueryString, '$2'));
546
+ }
547
+ }
548
+
549
+ // expose to the world
550
+ window.addToHomescreen = ath;
551
+
552
+ })(window, document);