├── .npmignore ├── docs ├── .nojekyll ├── node_modules │ ├── modalite │ │ ├── .npmignore │ │ ├── dist │ │ │ ├── assets │ │ │ │ ├── close.png │ │ │ │ └── spinner.png │ │ │ ├── modalite.min.js │ │ │ ├── modalite.min.css │ │ │ ├── modalite.min.css.map │ │ │ ├── modalite.scss │ │ │ └── modalite.js │ │ ├── LICENSE │ │ ├── package.json │ │ └── README.md │ └── active-timeout.js │ │ ├── .npmignore │ │ ├── dist │ │ ├── active-timeout.min.js │ │ └── active-timeout.js │ │ ├── LICENSE │ │ ├── README.md │ │ └── package.json ├── style.css ├── script.js ├── donleeve.min.js ├── test │ └── index.html ├── index.html ├── param.html └── donleeve.js ├── package.json ├── LICENSE ├── .gitignore ├── dist ├── donleeve.min.js └── donleeve.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | docs -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | .nojekyll 2 | -------------------------------------------------------------------------------- /docs/node_modules/modalite/.npmignore: -------------------------------------------------------------------------------- 1 | docs -------------------------------------------------------------------------------- /docs/node_modules/active-timeout.js/.npmignore: -------------------------------------------------------------------------------- 1 | demo -------------------------------------------------------------------------------- /docs/node_modules/modalite/dist/assets/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hdodov/donleeve/HEAD/docs/node_modules/modalite/dist/assets/close.png -------------------------------------------------------------------------------- /docs/node_modules/modalite/dist/assets/spinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hdodov/donleeve/HEAD/docs/node_modules/modalite/dist/assets/spinner.png -------------------------------------------------------------------------------- /docs/node_modules/active-timeout.js/dist/active-timeout.min.js: -------------------------------------------------------------------------------- 1 | window.ActiveTimeout=function(){function n(){return o&&document[o.hidden]}function e(n){"undefined"!=typeof requestAnimationFrame?requestAnimationFrame(n):setTimeout(n,1e3/60)}function i(i){var t=0,u=function(){t+=1};d.push(u),function o(c){var r=!1,f=Date.now();t>0?(t-=1,r=!0):r=!!n()||(!c||i(f-c)),!0===r?e(function(){o(f)}):d.splice(d.indexOf(u),1)}()}function t(n){var e=0;i(function(i){return e+=i,n(e,i)})}function u(n,e,i){"number"==typeof e&&(i=e,e=null),t(function(t,u){return"function"==typeof e&&e(i-t,u),!(t>=i)||(n(),!1)})}var o=function(){var n,e;if("hidden"in document)n="hidden",e="visibilitychange";else if("msHidden"in document)n="msHidden",e="msvisibilitychange";else{if(!("webkitHidden"in document))return null;n="webkitHidden",e="webkitvisibilitychange"}return{hidden:n,event:e}}(),d=[];return o&&document.addEventListener(o.event,function(){if(!n())for(var e=d.length-1;e>=0;e--)d[e]()}),{set:u,count:t,pulse:i}}(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "donleeve", 3 | "version": "1.0.1", 4 | "description": "Allows you to perform an action when your website visitor is about to leave (exit intent). Even on mobile.", 5 | "main": "", 6 | "dependencies": { 7 | "active-timeout.js": "^2.0.0" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "Hristiyan Dodov", 14 | "license": "MIT", 15 | "directories": { 16 | "doc": "docs" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/hdodov/donleeve.git" 21 | }, 22 | "keywords": [ 23 | "exit", 24 | "intent", 25 | "conversion", 26 | "rates", 27 | "boost", 28 | "perform", 29 | "action", 30 | "website", 31 | "leave", 32 | "mobile", 33 | "accurate", 34 | 35 | "dom", 36 | "events" 37 | ], 38 | "bugs": { 39 | "url": "https://github.com/hdodov/donleeve/issues" 40 | }, 41 | "homepage": "https://github.com/hdodov/donleeve#readme" 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hristiyan Dodov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/node_modules/modalite/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hristiyan Dodov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/node_modules/active-timeout.js/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hristiyan Dodov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Allow 2 | !docs/node_modules 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | background: #335; 8 | 9 | line-height: 1.5; 10 | font-family: sans-serif; 11 | font-size: 16px; 12 | color: #333; 13 | } 14 | 15 | h1, h2, h3 { 16 | text-align: center; 17 | } 18 | 19 | a, 20 | a:link, 21 | a:visited { 22 | transition: all 0.2s ease-out; 23 | text-decoration: none; 24 | color: #66c; 25 | } 26 | 27 | a:hover { 28 | opacity: 0.6; 29 | } 30 | 31 | .maindiv { 32 | background: #eee; 33 | width: 80%; 34 | max-width: 38rem; 35 | margin: 1rem auto; 36 | padding: 1px 1rem; 37 | border-radius: 5px; 38 | } 39 | 40 | .maindiv h1 { 41 | font-size: 1.4rem; 42 | margin: 1rem 2rem; 43 | } 44 | 45 | .log-window { 46 | width: 100%; 47 | height: 175px; 48 | box-shadow: border-box; 49 | border: 1px solid #ccc; 50 | margin-bottom: 1rem; 51 | overflow-y: auto; 52 | background: #e6e6e6; 53 | } 54 | 55 | .log-window p { 56 | width: 100%; 57 | margin: 0; 58 | padding: 0rem 1rem; 59 | line-height: 35px; 60 | 61 | background: #e6e6e6; 62 | font-family: monospace; 63 | box-sizing: border-box; 64 | } 65 | 66 | .log-window p:nth-child(even) { 67 | background: #ddd; 68 | } 69 | 70 | nav ul { 71 | text-align: center; 72 | padding: 0; 73 | } 74 | 75 | nav ul li { 76 | display: inline-block; 77 | margin: 0.5rem 1rem; 78 | } -------------------------------------------------------------------------------- /docs/node_modules/active-timeout.js/README.md: -------------------------------------------------------------------------------- 1 | # active-timeout.js 2 | Lets you measure time the user has spent **viewing** your page. If he focuses another tab or minimizes the browser, that inactive time will **not** be counted. 3 | 4 | # [Demo](https://hdodov.github.io/active-timeout/) 5 | Can be seen [here](https://hdodov.github.io/active-timeout/) 6 | 7 | # Installation 8 | ``` 9 | npm install active-timeout.js 10 | ``` 11 | or 12 | ``` 13 | git clone https://github.com/hdodov/active-timeout.js 14 | ``` 15 | 16 | # Usage 17 | Make sure to include the library somewhere in your page: 18 | ```html 19 | 20 | ``` 21 | 22 | ## API 23 | ```js 24 | // Pulse at ~60 intervals per second until the predicate function 25 | // returns a falsy value. 26 | ActiveTimeout.pulse(function (tick) { 27 | // `tick` holds the time in milliseconds from the last tick to the 28 | // current one. 29 | console.log(tick); // ~16 30 | return true; 31 | }); 32 | 33 | // Uses `pulse()` and adds its ticks to count time until the predicate 34 | // function returns a falsy value. 35 | ActiveTimeout.count(function (time) { 36 | // `time` holds the *active* time passed up to this point. 37 | return true; 38 | }); 39 | 40 | // Uses `count()` to invoke a callback function after a set amount of 41 | // *active* time has passed. 42 | ActiveTimeout.set(function () { 43 | // Invoked when the timeout ends. 44 | }, 10000); 45 | 46 | // Optionally, `set()` can receive a second function to invoke upon 47 | // each timer tick. 48 | ActiveTimeout.set(function () { 49 | // Invoked when the timeout ends. 50 | }, function (remainder, tick) { 51 | // `remainder` holds the time left until the timeout ends 52 | // `tick` holds the time since the last tick 53 | }, 10000); 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/node_modules/modalite/dist/modalite.min.js: -------------------------------------------------------------------------------- 1 | window.Modalite=function(){var t="modal-visible",e="modal-loading",s="modal-loaded",a="modal-remote-success",n="modal-remote-error",i="modal-remote-loading",d="data-modal-remote",o="data-modal-has-remote",l="data-open-modal",r="data-close-modal",c="data-modal",u=9999;function f(t,e){var s=new XMLHttpRequest;s.addEventListener("load",function(){if(this.status>=200&&this.status<300){t.classList.add(a);t.classList.remove(n)}else{t.classList.add(n)}t.innerHTML=s.responseText});s.addEventListener("error",function(){t.classList.add(n)});s.addEventListener("loadstart",function(){t.classList.add(i)});s.addEventListener("loadend",function(){t.classList.remove(i)});s.open("GET",e);s.send();return s}function m(t,e){t.classList.add(i);t.addEventListener("load",function(){t.classList.add(a);t.classList.remove(i)});t.src=e}function L(t,e,s){var a=t.getAttribute(d);if(a&&a.length>0){e();if(t.tagName==="IFRAME"){m(t,a);t.addEventListener("load",s)}else{request=f(t,a);request.addEventListener("loadend",s)}}}function v(t,e,s){var n=t.getElementsByTagName("*");for(var i=0;i0){t.classList.add(e)}}function E(a){if(typeof a==="string"){a=document.getElementById(a)}if(a){a.classList.add(t);if(a.style){a.style.zIndex=u;u+=1}if(a.getAttribute(o)!==null&&a.classList.contains(e)===false&&a.classList.contains(s)===false){g(a)}}}function b(e){if(typeof e==="string"){e=document.getElementById(e)}if(e){e.classList.remove(t)}}document.addEventListener("click",function(t){var e=t.srcElement||t.target;if(e.getAttribute(c)!=null){b(e)}var s=e.getAttribute(l);if(s&&s.length>0){E(s)}if(e.getAttribute(r)!==null){var a=e.parentNode;while(a&&a!==document.body){if(a.getAttribute(c)!==null){b(a);break}a=a.parentNode}}});return{open:E,close:b}}(); 2 | -------------------------------------------------------------------------------- /docs/script.js: -------------------------------------------------------------------------------- 1 | var timer = document.getElementById("timer"); 2 | var logsMain = document.getElementById("logs-main"); 3 | var logsTriggers = document.getElementById("logs-triggers"); 4 | var purgeLink = document.getElementById("purge-link"); 5 | 6 | function getNow() { 7 | var d = new Date(), 8 | hrs = d.getHours(), 9 | min = d.getMinutes(), 10 | sec = d.getSeconds(); 11 | 12 | if (hrs < 10) { hrs = "0" + hrs; } 13 | if (min < 10) { min = "0" + min; } 14 | if (sec < 10) { sec = "0" + sec; } 15 | 16 | return hrs + ":" + min + ":" + sec; 17 | } 18 | 19 | function log(text, logger) { 20 | logger = logger || logsMain; 21 | logger.innerHTML += "

" + text + "

"; 22 | logger.scrollTop = logger.scrollHeight; 23 | } 24 | 25 | purgeLink.addEventListener("click", function (e) { 26 | e.preventDefault(); 27 | 28 | Donleeve.purgeBlocks(Date.now()); 29 | log("All blocks have been purged!"); 30 | }); 31 | 32 | Donleeve.onTrigger = function (e) { 33 | log("" + getNow() + " " + e.type, logsTriggers); 34 | }; 35 | 36 | Donleeve.onStorageBlock = function (str, time) { 37 | log("Blocked on " + str + " URLs for " + time + " milliseconds."); 38 | }; 39 | 40 | Donleeve.init({ 41 | bindDelay: 2000, 42 | ignoreFlagBlocking: true 43 | }, function (e) { 44 | Modalite.open("the-modal"); 45 | log("Exit intent activated by " + e.type + "!"); 46 | 47 | if (!Donleeve.options.ignoreFlagBlocking) { 48 | log("On this page, an option is set that prevents multiple exit intents to appear on one page load. Refresh the page to see how Storage Blocking works. The block will last " + Math.ceil(Donleeve.options.storageBlockingMinutes * 60) + " seconds."); 49 | } 50 | }); 51 | 52 | ActiveTimeout.set(function () { 53 | log("Events bound!"); 54 | }, function (left) { 55 | if (left < 0) left = 0; 56 | timer.innerHTML = Math.ceil(left / 1000); 57 | }, Donleeve.options.bindDelay); -------------------------------------------------------------------------------- /dist/donleeve.min.js: -------------------------------------------------------------------------------- 1 | window.Donleeve=function(){function n(n){return 60*n*1e3}function e(n){return"number"==typeof n?n-Date.now():0}function o(){var n;try{n=JSON.parse(localStorage[d])}catch(n){console.warn("Couldn't parse blocks JSON.")}return Array.isArray(n)||(t(n=[]),console.warn("Blocks were not an array.")),n}function t(n){for(var o=n.length-1;o>=0;o--)e(n[o][1])<=0&&n.splice(o,1);localStorage[d]=JSON.stringify(n)}function i(){if(f){var e=o(),i=n(s.storageBlockingMinutes);e.push([s.storageBlockingRegex,Date.now()+i]),t(e)}}function r(){if(!f)return!1;var n=!1;return o().forEach(function(o){var t=o[0],i=o[1];if(t===g||null!==new RegExp(t).exec(window.location.href)){var r=e(i);r>0&&(n=!0,"function"==typeof v.onStorageBlock&&v.onStorageBlock(t,r))}}),n}function c(){return!!v.enabled&&(!(!s.ignoreFlagBlocking&&v.acted)&&!(!s.ignoreStorageBlocking&&r()))}function u(n){"function"==typeof v.onAction&&!1!==v.onAction(n)&&(v.acted=!0,i())}function l(n){"function"==typeof v.onTrigger&&v.onTrigger(n),!0===c()&&u(n)}function a(){s.bindEventBlur&&window.addEventListener("blur",l),s.bindEventMouseLeave&&document.documentElement.addEventListener("mouseleave",function(n){(n.y<=0||n.clientY<=0)&&l(n)}),s.bindEventMouseMove&&document.documentElement.addEventListener("mousemove",function(n){n.movementY<0&&(n.y<=0||n.movementY<-n.y)&&l(n)}),"function"==typeof v.onBound&&v.onBound()}var g="*",f="undefined"!=typeof localStorage,d="donleevBlocks",s={bindDelay:3e3,bindEventBlur:!0,bindEventMouseLeave:!0,bindEventMouseMove:!0,storageBlockingRegex:g,storageBlockingMinutes:10,ignoreStorageBlocking:!1,ignoreFlagBlocking:!1},v={options:s,enabled:!0,acted:!1,onBound:null,onTrigger:null,onStorageBlock:null,onAction:null};return v.setOptions=function(n){if("object"==typeof n&&null!==n)for(var e in n)s[e]=n[e]},v.init=function(n,e){"function"==typeof n&&(e=n,n=void 0),v.setOptions(n),v.onAction=e,ActiveTimeout.set(function(){a()},s.bindDelay)},v.purgeBlocks=function(n){f&&localStorage.donleevPurge!==n&&(localStorage.donleevPurge=n,t([]),console.warn("Blocks purged with string:",n))},v}(); 2 | -------------------------------------------------------------------------------- /docs/donleeve.min.js: -------------------------------------------------------------------------------- 1 | window.Donleeve=function(){function n(n){return 60*n*1e3}function e(n){return"number"==typeof n?n-Date.now():0}function o(){var n;try{n=JSON.parse(localStorage[d])}catch(n){console.warn("Couldn't parse blocks JSON.")}return Array.isArray(n)||(t(n=[]),console.warn("Blocks were not an array.")),n}function t(n){for(var o=n.length-1;o>=0;o--)e(n[o][1])<=0&&n.splice(o,1);localStorage[d]=JSON.stringify(n)}function i(){if(f){var e=o(),i=n(s.storageBlockingMinutes);e.push([s.storageBlockingRegex,Date.now()+i]),t(e)}}function r(){if(!f)return!1;var n=!1;return o().forEach(function(o){var t=o[0],i=o[1];if(t===g||null!==new RegExp(t).exec(window.location.href)){var r=e(i);r>0&&(n=!0,"function"==typeof v.onStorageBlock&&v.onStorageBlock(t,r))}}),n}function c(){return!!v.enabled&&(!(!s.ignoreFlagBlocking&&v.acted)&&!(!s.ignoreStorageBlocking&&r()))}function u(n){"function"==typeof v.onAction&&!1!==v.onAction(n)&&(v.acted=!0,i())}function l(n){"function"==typeof v.onTrigger&&v.onTrigger(n),!0===c()&&u(n)}function a(){s.bindEventBlur&&window.addEventListener("blur",l),s.bindEventMouseLeave&&document.documentElement.addEventListener("mouseleave",function(n){(n.y<=0||n.clientY<=0)&&l(n)}),s.bindEventMouseMove&&document.documentElement.addEventListener("mousemove",function(n){n.movementY<0&&(n.y<=0||n.movementY<-n.y)&&l(n)}),"function"==typeof v.onBound&&v.onBound()}var g="*",f="undefined"!=typeof localStorage,d="donleevBlocks",s={bindDelay:3e3,bindEventBlur:!0,bindEventMouseLeave:!0,bindEventMouseMove:!0,storageBlockingRegex:g,storageBlockingMinutes:10,ignoreStorageBlocking:!1,ignoreFlagBlocking:!1},v={options:s,enabled:!0,acted:!1,onBound:null,onTrigger:null,onStorageBlock:null,onAction:null};return v.setOptions=function(n){if("object"==typeof n&&null!==n)for(var e in n)s[e]=n[e]},v.init=function(n,e){"function"==typeof n&&(e=n,n=void 0),v.setOptions(n),v.onAction=e,ActiveTimeout.set(function(){a()},s.bindDelay)},v.purgeBlocks=function(n){f&&localStorage.donleevPurge!==n&&(localStorage.donleevPurge=n,t([]),console.warn("Blocks purged with string:",n))},v}(); 2 | -------------------------------------------------------------------------------- /docs/node_modules/modalite/dist/modalite.min.css: -------------------------------------------------------------------------------- 1 | @keyframes modalite-spinner-animation{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.modal{display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.8);position:fixed;z-index:9999;top:0;left:0;width:100%;height:100%;box-sizing:border-box;padding:3rem;visibility:hidden;opacity:0;transition:visibility 0.3s linear, opacity 0.3s ease-out}.modal.modal-visible{visibility:visible;opacity:1}.modal-container{position:relative;background:#eee;border-radius:5px;width:100%;height:100%;max-width:650px;max-width:40rem;max-height:500px;max-height:30rem;transform:scale(0.9);transition:transform 0.3s ease-out}.modal.modal-visible .modal-container{transform:scale(1)}.modal.modal-remote .modal-container:before{content:"";display:none;background:#eee;position:absolute;left:0;top:0;width:100%;height:100%;border-radius:5px;visibility:visible;opacity:0.9;transition:all 0.3s ease-out}.modal.modal-remote.modal-visible .modal-container:before{display:block}.modal.modal-remote.modal-loaded .modal-container:before{visibility:hidden;opacity:0}.modal.modal-remote .modal-container:after{content:"";background:url("assets/spinner.png");animation:modalite-spinner-animation 0.8s linear infinite;margin-left:-15px;margin-right:-15px;width:30px;height:30px;position:absolute;top:50%;left:50%;visibility:hidden;opacity:0;transition:all 0.3s ease-out}.modal.modal-remote.modal-loading .modal-container:after{visibility:visible;opacity:1}.modal-container .modal-close{position:absolute;top:-16px;right:-16px}.modal-close{background-color:#d12626;background-image:url("assets/close.png");background-repeat:no-repeat;background-position:center center;display:inline-block;width:32px;height:32px;border-radius:32px;transition:all 0.3s ease-out;cursor:pointer}.modal-close:hover{background-color:#ea3c3c}.modal-content{width:100%;height:100%;box-sizing:border-box;padding:2rem;overflow:auto}@media (max-width: 480px){.modal{display:block;padding:1.5rem}.modal-container{max-height:none}.modal-content{padding:1rem}} 2 | /*# sourceMappingURL=modalite.min.css.map */ 3 | -------------------------------------------------------------------------------- /docs/node_modules/modalite/dist/modalite.min.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "AASA,qCAGC,CAFG,EAAK,CAAE,SAAS,CAAE,SAAS,CAC3B,IAAK,CAAE,SAAS,CAAE,cAAc,EAGpC,MAAO,CACH,OAAO,CAAE,IAAI,CACb,WAAW,CAAE,MAAM,CACnB,eAAe,CAAE,MAAM,CAEvB,UAAU,CAAE,eAAkB,CAC9B,QAAQ,CAAE,KAAK,CACX,OAAO,CAAE,IAAI,CACb,GAAG,CAAE,CAAC,CACN,IAAI,CAAE,CAAC,CAEX,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CAEZ,UAAU,CAAE,UAAU,CACtB,OAAO,CAAE,IAAI,CAEb,UAAU,CAAE,MAAM,CAClB,OAAO,CAAE,CAAC,CAEV,UAAU,CACN,6CACqB,CAEzB,oBAAgB,CACZ,UAAU,CAAE,OAAO,CACnB,OAAO,CAAE,CAAC,CAIlB,gBAAiB,CACb,QAAQ,CAAE,QAAQ,CAElB,UAAU,CAAE,IAAI,CAChB,aAAa,CAAE,GAAG,CAElB,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CACZ,SAAS,CAAE,KAAK,CAChB,SAAS,CAAE,KAAK,CAChB,UAAU,CAAE,KAAK,CACjB,UAAU,CAAE,KAAK,CAEjB,SAAS,CAAE,UAAU,CACrB,UAAU,CAAE,uBAAuB,CAEnC,qCAAuB,CACnB,SAAS,CAAE,QAAQ,CAIvB,2CAA6B,CACzB,OAAO,CAAE,EAAE,CACX,OAAO,CAAE,IAAI,CAEb,UAAU,CAAE,IAAI,CAChB,QAAQ,CAAE,QAAQ,CAClB,IAAI,CAAE,CAAC,CACP,GAAG,CAAE,CAAC,CACN,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CACZ,aAAa,CAAE,GAAG,CAClB,UAAU,CAAE,OAAO,CACnB,OAAO,CAAE,GAAG,CACZ,UAAU,CAAE,iBAAiB,CAGjC,yDAA2C,CAMvC,OAAO,CAAE,KAAK,CAId,wDAA0C,CACtC,UAAU,CAAE,MAAM,CAClB,OAAO,CAAE,CAAC,CAIlB,0CAA4B,CACxB,OAAO,CAAE,EAAE,CACX,UAAU,CA9FK,yBAAyB,CA+FxC,SAAS,CAAE,+CAA+C,CAE1D,WAAW,CAAE,KAA8B,CAC3C,YAAY,CAAE,KAA+B,CAC7C,KAAK,CAlGY,IAAI,CAmGrB,MAAM,CAlGY,IAAI,CAoGtB,QAAQ,CAAE,QAAQ,CACd,GAAG,CAAE,GAAG,CACR,IAAI,CAAE,GAAG,CAEb,UAAU,CAAE,MAAM,CAClB,OAAO,CAAE,CAAC,CAEV,UAAU,CAAE,iBAAiB,CAI7B,wDAA0C,CACtC,UAAU,CAAE,OAAO,CACnB,OAAO,CAAE,CAAC,CAGlB,6BAAa,CACT,QAAQ,CAAE,QAAQ,CAClB,GAAG,CAAE,KAA+B,CACpC,KAAK,CAAE,KAA+B,CAI9C,YAAa,CACT,gBAAgB,CAAE,OAAO,CACzB,gBAAgB,CAlIC,uBAAuB,CAmIxC,iBAAiB,CAAE,SAAS,CAC5B,mBAAmB,CAAE,aAAa,CAElC,OAAO,CAAE,YAAY,CACrB,KAAK,CAtIiB,IAAI,CAuI1B,MAAM,CAvIgB,IAAI,CAyI1B,aAAa,CAzIS,IAAI,CA0I1B,UAAU,CAAE,iBAAiB,CAC7B,MAAM,CAAE,OAAO,CAEf,kBAAQ,CACJ,gBAAgB,CAAE,OAAO,CAIjC,cAAe,CACX,KAAK,CAAE,IAAI,CACX,MAAM,CAAE,IAAI,CAEZ,UAAU,CAAE,UAAU,CACtB,OAAO,CAAE,IAAI,CAEb,QAAQ,CAAE,IAAI,CAGlB,yBAAyC,CACrC,MAAO,CACH,OAAO,CAAE,KAAK,CACd,OAAO,CAAE,MAAM,CAGnB,gBAAiB,CACb,UAAU,CAAE,IAAI,CAGpB,cAAe,CACX,OAAO,CAAE,IAAI", 4 | "sources": ["modalite.scss"], 5 | "names": [], 6 | "file": "modalite.min.css" 7 | } 8 | -------------------------------------------------------------------------------- /docs/node_modules/modalite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | "modalite@^1.1.0", 5 | "C:\\Users\\Hristiyan\\Desktop\\focustest" 6 | ] 7 | ], 8 | "_from": "modalite@>=1.1.0 <2.0.0", 9 | "_id": "modalite@1.2.0", 10 | "_inCache": true, 11 | "_installable": true, 12 | "_location": "/modalite", 13 | "_nodeVersion": "6.1.0", 14 | "_npmOperationalInternal": { 15 | "host": "s3://npm-registry-packages", 16 | "tmp": "tmp/modalite-1.2.0.tgz_1501571236685_0.36102276272140443" 17 | }, 18 | "_npmUser": { 19 | "email": "h.dodov@gmail.com", 20 | "name": "hdodov" 21 | }, 22 | "_npmVersion": "3.8.6", 23 | "_phantomChildren": {}, 24 | "_requested": { 25 | "name": "modalite", 26 | "raw": "modalite@^1.1.0", 27 | "rawSpec": "^1.1.0", 28 | "scope": null, 29 | "spec": ">=1.1.0 <2.0.0", 30 | "type": "range" 31 | }, 32 | "_requiredBy": [ 33 | "/" 34 | ], 35 | "_shasum": "90ebbc3ab52543b8d7526d6a76a36b1e82df7968", 36 | "_shrinkwrap": null, 37 | "_spec": "modalite@^1.1.0", 38 | "_where": "C:\\Users\\Hristiyan\\Desktop\\focustest", 39 | "author": { 40 | "name": "Hristiyan Dodov" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/hdodov/modalite/issues" 44 | }, 45 | "dependencies": {}, 46 | "description": "The most simple and flexible modal library, now with support for remote resources and iframes.", 47 | "devDependencies": {}, 48 | "directories": {}, 49 | "dist": { 50 | "shasum": "90ebbc3ab52543b8d7526d6a76a36b1e82df7968", 51 | "tarball": "https://registry.npmjs.org/modalite/-/modalite-1.2.0.tgz" 52 | }, 53 | "gitHead": "f17083ae053ca16dc6c096e9843792db3e63ec39", 54 | "homepage": "https://github.com/hdodov/modalite#readme", 55 | "keywords": [ 56 | "modal", 57 | "dialog", 58 | "box", 59 | "overlay", 60 | "page", 61 | "remote", 62 | "external", 63 | "resource", 64 | "iframe", 65 | "ajax", 66 | "xhr", 67 | "css", 68 | "flexible", 69 | "simple", 70 | "easy", 71 | "quick", 72 | "fast", 73 | "smooth" 74 | ], 75 | "license": "MIT", 76 | "main": "index.js", 77 | "maintainers": [ 78 | { 79 | "email": "h.dodov@gmail.com", 80 | "name": "hdodov" 81 | } 82 | ], 83 | "name": "modalite", 84 | "optionalDependencies": {}, 85 | "readme": "ERROR: No README data found!", 86 | "repository": { 87 | "type": "git", 88 | "url": "git+https://github.com/hdodov/modalite.git" 89 | }, 90 | "scripts": { 91 | "test": "echo \"Error: no test specified\" && exit 1" 92 | }, 93 | "version": "1.2.0" 94 | } 95 | -------------------------------------------------------------------------------- /docs/node_modules/active-timeout.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | "active-timeout.js", 5 | "C:\\Users\\Hristiyan\\Desktop\\donleeve\\docs" 6 | ] 7 | ], 8 | "_from": "active-timeout.js@latest", 9 | "_id": "active-timeout.js@2.0.0", 10 | "_inCache": true, 11 | "_installable": true, 12 | "_location": "/active-timeout.js", 13 | "_nodeVersion": "6.1.0", 14 | "_npmOperationalInternal": { 15 | "host": "s3://npm-registry-packages", 16 | "tmp": "tmp/active-timeout.js-2.0.0.tgz_1501414596837_0.4918554031755775" 17 | }, 18 | "_npmUser": { 19 | "email": "h.dodov@gmail.com", 20 | "name": "hdodov" 21 | }, 22 | "_npmVersion": "3.8.6", 23 | "_phantomChildren": {}, 24 | "_requested": { 25 | "name": "active-timeout.js", 26 | "raw": "active-timeout.js", 27 | "rawSpec": "", 28 | "scope": null, 29 | "spec": "latest", 30 | "type": "tag" 31 | }, 32 | "_requiredBy": [ 33 | "#USER" 34 | ], 35 | "_resolved": "file:active-timeout.js", 36 | "_shasum": "6e758bc44f11490a67e2835c3de0d1f919dacd7c", 37 | "_shrinkwrap": null, 38 | "_spec": "active-timeout.js", 39 | "_where": "C:\\Users\\Hristiyan\\Desktop\\donleeve\\docs", 40 | "author": { 41 | "name": "Hristiyan Dodov" 42 | }, 43 | "bugs": { 44 | "url": "https://github.com/hdodov/active-timeout.js/issues" 45 | }, 46 | "dependencies": {}, 47 | "description": "JavaScript library that lets you measure time the user has spent viewing your page. Inactive time will not be counted.", 48 | "devDependencies": {}, 49 | "directories": {}, 50 | "dist": { 51 | "shasum": "6e758bc44f11490a67e2835c3de0d1f919dacd7c", 52 | "tarball": "https://registry.npmjs.org/active-timeout.js/-/active-timeout.js-2.0.0.tgz" 53 | }, 54 | "gitHead": "7c56922643c425d70252300a4c43edbfc2b03be8", 55 | "homepage": "https://github.com/hdodov/active-timeout.js#readme", 56 | "keywords": [ 57 | "javascript", 58 | "active", 59 | "accurate", 60 | "measure", 61 | "timeout", 62 | "interval", 63 | "analytics", 64 | "visibility", 65 | "settimeout", 66 | "setinterval", 67 | "wait", 68 | "delay", 69 | "viewing", 70 | "view" 71 | ], 72 | "license": "MIT", 73 | "main": "index.js", 74 | "maintainers": [ 75 | { 76 | "email": "h.dodov@gmail.com", 77 | "name": "hdodov" 78 | } 79 | ], 80 | "name": "active-timeout.js", 81 | "optionalDependencies": {}, 82 | "readme": "ERROR: No README data found!", 83 | "repository": { 84 | "type": "git", 85 | "url": "git+https://github.com/hdodov/active-timeout.js.git" 86 | }, 87 | "scripts": { 88 | "test": "echo \"Error: no test specified\" && exit 1" 89 | }, 90 | "version": "2.0.0" 91 | } 92 | -------------------------------------------------------------------------------- /docs/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Donleeve 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Donleeve is a library for exit intent actions!

15 |

On this page, exit intent can occur more than one time, but it has a 1 minute block. Additionally, the global block from the home page still applies.

16 | 17 | 24 |
25 | 26 |
27 |

Waiting for X active seconds to pass to prevent false positives

28 | 29 |

Active means the user must be viewing the page. If he opened your site in a new tab for later reading, the counter will start when he focuses the tab. This functionality is achieved via the active-timeout.js dependency.

30 |
31 | 32 |
33 |

What's happening

34 |
35 | 36 |

Click here to purge all exit intent blocks.

37 |
38 | 39 |
40 |

This fills up with events where the exit intent condition was met

41 |
42 |
43 | 44 | 57 | 58 | 59 | 60 | 61 | 62 | 69 | 70 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Donleeve 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Donleeve is a library for exit intent actions!

15 |

For better user experience, Donleeve uses localStorage to prevent exit intents from popping up too often. You can specify parts of the URL to block with regex if you have different modals on your pages and you want a different block for each. For example, take a look at these links:

16 | 17 | 24 |
25 | 26 |
27 |

Waiting for X active seconds to pass to prevent false positives

28 | 29 |

Active means the user must be viewing the page. If he opened your site in a new tab for later reading, the counter will start when he focuses the tab. This functionality is achieved via the active-timeout.js dependency.

30 |
31 | 32 |
33 |

What's happening

34 |
35 | 36 |

Click here to purge all exit intent blocks.

37 |
38 | 39 |
40 |

This fills up with events where the exit intent condition was met

41 |
42 |
43 | 44 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | -------------------------------------------------------------------------------- /docs/param.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Donleeve 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

Donleeve is a library for exit intent actions!

15 |

Here, the block time will be different based on the parameter in the URL. If it's equal to 3, the block will be 3 seconds, for example. In your website, you can utilize this for A/B testing. Notice, however, that the block on ?p=10 doesn't affect a page with ?p=7, it only affects occurences of ?p=10 in the URL.

16 | 17 | 24 |
25 | 26 |
27 |

Waiting for X active seconds to pass to prevent false positives

28 | 29 |

Active means the user must be viewing the page. If he opened your site in a new tab for later reading, the counter will start when he focuses the tab. This functionality is achieved via the active-timeout.js dependency.

30 |
31 | 32 |
33 |

What's happening

34 |
35 | 36 |

Click here to purge all exit intent blocks.

37 |
38 | 39 |
40 |

This fills up with events where the exit intent condition was met

41 |
42 |
43 | 44 | 57 | 58 | 59 | 60 | 61 | 62 | 75 | 76 | -------------------------------------------------------------------------------- /docs/node_modules/modalite/dist/modalite.scss: -------------------------------------------------------------------------------- 1 | $modalite-breakpoint: 480px; 2 | 3 | $modalite-close-url: url("assets/close.png"); 4 | $modalite-close-diameter: 32px; 5 | 6 | $modalite-spinner-url: url("assets/spinner.png"); 7 | $modalite-spinner-width: 30px; 8 | $modalite-spinner-height: 30px; 9 | 10 | @keyframes modalite-spinner-animation { 11 | 0% { transform: rotate(0); } 12 | 100% { transform: rotate(360deg); } 13 | } 14 | 15 | .modal { 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | 20 | background: rgba(0, 0, 0, 0.8); 21 | position: fixed; 22 | z-index: 9999; 23 | top: 0; 24 | left: 0; 25 | 26 | width: 100%; 27 | height: 100%; 28 | 29 | box-sizing: border-box; 30 | padding: 3rem; 31 | 32 | visibility: hidden; 33 | opacity: 0; 34 | 35 | transition: 36 | visibility 0.3s linear, 37 | opacity 0.3s ease-out; 38 | 39 | &.modal-visible { 40 | visibility: visible; 41 | opacity: 1; 42 | } 43 | } 44 | 45 | .modal-container { 46 | position: relative; 47 | 48 | background: #eee; 49 | border-radius: 5px; 50 | 51 | width: 100%; 52 | height: 100%; 53 | max-width: 650px; 54 | max-width: 40rem; 55 | max-height: 500px; 56 | max-height: 30rem; 57 | 58 | transform: scale(0.9); 59 | transition: transform 0.3s ease-out; 60 | 61 | .modal.modal-visible & { 62 | transform: scale(1); 63 | } 64 | 65 | /* remote modal overlay */ 66 | .modal.modal-remote &:before { 67 | content: ""; 68 | display: none; 69 | 70 | background: #eee; 71 | position: absolute; 72 | left: 0; 73 | top: 0; 74 | width: 100%; 75 | height: 100%; 76 | border-radius: 5px; 77 | visibility: visible; 78 | opacity: 0.9; 79 | transition: all 0.3s ease-out; 80 | } 81 | 82 | .modal.modal-remote.modal-visible &:before { 83 | /** 84 | * By default, the modal overlay is hidden so that it doesn't affect 85 | * mouse input. It only becomes visible when the modal itself is. 86 | */ 87 | 88 | display: block; 89 | } 90 | 91 | /* remote modal overlay active */ 92 | .modal.modal-remote.modal-loaded &:before { 93 | visibility: hidden; 94 | opacity: 0; 95 | } 96 | 97 | /* remote modal spinner */ 98 | .modal.modal-remote &:after { 99 | content: ""; 100 | background: $modalite-spinner-url; 101 | animation: modalite-spinner-animation 0.8s linear infinite; 102 | 103 | margin-left: -($modalite-spinner-width / 2); 104 | margin-right: -($modalite-spinner-height / 2); 105 | width: $modalite-spinner-width; 106 | height: $modalite-spinner-height; 107 | 108 | position: absolute; 109 | top: 50%; 110 | left: 50%; 111 | 112 | visibility: hidden; 113 | opacity: 0; 114 | 115 | transition: all 0.3s ease-out; 116 | } 117 | 118 | /* remote modal spinner active */ 119 | .modal.modal-remote.modal-loading &:after { 120 | visibility: visible; 121 | opacity: 1; 122 | } 123 | 124 | .modal-close { 125 | position: absolute; 126 | top: -($modalite-close-diameter / 2); 127 | right: -($modalite-close-diameter / 2); 128 | } 129 | } 130 | 131 | .modal-close { 132 | background-color: #d12626; 133 | background-image: $modalite-close-url; 134 | background-repeat: no-repeat; 135 | background-position: center center; 136 | 137 | display: inline-block; 138 | width: $modalite-close-diameter; 139 | height: $modalite-close-diameter; 140 | 141 | border-radius: $modalite-close-diameter; 142 | transition: all 0.3s ease-out; 143 | cursor: pointer; 144 | 145 | &:hover { 146 | background-color: #ea3c3c; 147 | } 148 | } 149 | 150 | .modal-content { 151 | width: 100%; 152 | height: 100%; 153 | 154 | box-sizing: border-box; 155 | padding: 2rem; 156 | 157 | overflow: auto; 158 | } 159 | 160 | @media (max-width: $modalite-breakpoint) { 161 | .modal { 162 | display: block; 163 | padding: 1.5rem; 164 | } 165 | 166 | .modal-container { 167 | max-height: none; 168 | } 169 | 170 | .modal-content { 171 | padding: 1rem; 172 | } 173 | } -------------------------------------------------------------------------------- /docs/node_modules/active-timeout.js/dist/active-timeout.js: -------------------------------------------------------------------------------- 1 | window.ActiveTimeout = (function () { 2 | 3 | // Gets the Visibility API properties. If the browser doesn't support 4 | // that, this library would basically be useless, except for the fancy way 5 | // of handling time. 6 | var _visibility = (function () { 7 | var p, e; 8 | 9 | if ("hidden" in document) { 10 | p = "hidden"; 11 | e = "visibilitychange"; 12 | } else if ("msHidden" in document) { 13 | p = "msHidden"; 14 | e = "msvisibilitychange"; 15 | } else if ("webkitHidden" in document) { 16 | p = "webkitHidden"; 17 | e = "webkitvisibilitychange"; 18 | } else { 19 | return null; 20 | } 21 | 22 | return { 23 | hidden: p, 24 | event: e 25 | }; 26 | })(); 27 | 28 | function _isPageHidden() { 29 | return (_visibility && document[_visibility.hidden]); 30 | } 31 | 32 | /** 33 | * Invokes a function after a ~17ms interval. Uses `requestAnimationFrame` 34 | * if supported to ease the load on the browser. 35 | * @param {function} callback Function to execute. 36 | */ 37 | function _delay(callback) { 38 | if (typeof requestAnimationFrame !== "undefined") { 39 | requestAnimationFrame(callback); 40 | } else { 41 | setTimeout(callback, (1000 / 60)); 42 | } 43 | } 44 | 45 | var _listeners = []; 46 | 47 | if (_visibility) { 48 | document.addEventListener(_visibility.event, function () { 49 | if (!_isPageHidden()) { 50 | // The page is not hidden, but a change occured. That means 51 | // is *was* previously hidden, so the timers receive an event 52 | // that tells them to ignore the next tick since it would 53 | // contain the inactive time. 54 | for (var i = _listeners.length - 1; i >= 0; i--) { 55 | _listeners[i](); 56 | } 57 | } 58 | }); 59 | } 60 | 61 | /** 62 | * Measure ~60 intervals per second and ignore ones where the user was 63 | * inactive. Repeat until the predicate returns a falsy value. 64 | * @param {function} predicate Stops pulsing when it returns a falsy value. 65 | */ 66 | function pulse(predicate) { 67 | var ignoreTicks = 0; 68 | var listener = function () { 69 | ignoreTicks += 1; 70 | } 71 | 72 | _listeners.push(listener); 73 | 74 | (function measure(last) { 75 | var proceed = false, now = Date.now(); 76 | 77 | // Whenever the page loses focus, the next tick should be 78 | // ignored, otherwise the inactive time would be added and this 79 | // whole charade would be meaningless. 80 | if (ignoreTicks > 0) { 81 | ignoreTicks -= 1; 82 | proceed = true; 83 | } else { 84 | if (_isPageHidden()) { 85 | // Page is hidden, continue to pulse and wait for focus. 86 | proceed = true; 87 | } else { 88 | if (!last) { 89 | // First iteration of the recursion, continue and wait 90 | // for some time to pass. 91 | proceed = true; 92 | } else { 93 | // Finally, the predicate decides whether to continue. 94 | proceed = predicate(now - last); 95 | } 96 | } 97 | } 98 | 99 | if (proceed === true) { 100 | _delay(function () { 101 | measure(now); 102 | }); 103 | } else { 104 | _listeners.splice(_listeners.indexOf(listener), 1); 105 | } 106 | })(); 107 | } 108 | 109 | /** 110 | * Count *active* passed time until a predicate returns a falsy value. 111 | * @param {function} predicate Stops the counter when it returns a falsy value. 112 | */ 113 | function count(predicate) { 114 | var time = 0; 115 | 116 | pulse(function (tick) { 117 | time += tick; 118 | return predicate(time, tick); 119 | }); 120 | } 121 | 122 | /** 123 | * Invoke a function after the user has spent a set amount of *active* time 124 | * on the page. Optionally provide a callback for each tick. 125 | * @param {function} completeCallback Function to call when the timeout 126 | * completes. 127 | * @param {function} tickCallback Optional function to call upon each 128 | * timer tick. 129 | * @param {number} time How much milliseconds to wait. 130 | */ 131 | function timeout(completeCallback, tickCallback, time) { 132 | if (typeof tickCallback === "number") { 133 | time = tickCallback; 134 | tickCallback = null; 135 | } 136 | 137 | count(function (passed, tick) { 138 | if (typeof tickCallback === "function") { 139 | tickCallback(time - passed, tick); 140 | } 141 | 142 | if (passed >= time) { 143 | completeCallback(); 144 | return false; 145 | } else { 146 | return true; 147 | } 148 | }); 149 | } 150 | 151 | return { 152 | set: timeout, 153 | count: count, 154 | pulse: pulse 155 | }; 156 | })(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Donleeve 2 | Allows you to perform an action when your website visitor is about to leave (exit intent). **Even on mobile.** Uses `localStorage` to prevent the action from occurring too often. 3 | 4 | # [Demo](https://hdodov.github.io/donleeve/) 5 | 6 | Can be seen [here](https://hdodov.github.io/donleeve/). 7 | 8 | # Installation 9 | 10 | ``` 11 | npm install donleeve 12 | ``` 13 | 14 | or 15 | 16 | ``` 17 | git clone https://github.com/hdodov/donleeve 18 | ``` 19 | 20 | # Dependencies 21 | 22 | ## [active-timeout.js](https://github.com/hdodov/active-timeout.js) 23 | 24 | The first several seconds of a user's visit are crucial and it would be bad if you pissed him off with an exit intent modal that was triggered by a false positive. That's why the event listeners who watch the user's behavior are bound after a set delay. 25 | 26 | However, your user might have opened your site in a new tab for later reading. In that case, the bind delay would be useless and would pass while the user was inactive. 27 | 28 | **That's why this library is used.** It allows you to invoke a function after a set amount of *active* time. That is, time that passed *while the user was viewing the page*. 29 | 30 | # Blocking 31 | 32 | For better user experience, there are two types of blocking that can prevent the exit intent action from happening even if the conditions are met. 33 | 34 | ## Flag blocking 35 | 36 | This is a short-term block. When the user sees the exit intent for the first time, the `Donleeve.acted` flag is raised. If `ignoreFlagBlocking` in the options is not enabled, the visitor won't be seeing any exit intents until he refreshes the page. 37 | 38 | ## Storage blocking 39 | 40 | This is a long-term block that utilizes `localStorage`. It's an array of objects with two values inside: 41 | 42 | - Regex string. Controls which pages a block affects. If the regex matched the current URL it will block the current page. If not, this block will be ignored. The default value is `*` and blocks **all** pages. 43 | - UNIX timestamp. Defines a point in the future until which this block is going to be active. 44 | 45 | ## Example 46 | 47 | If an exit intent is triggered on a page with `storageBlockingRegex` set to `\\?para\\=[0-9]` and `storageBlockingMinutes` set to `10`, something like this would be created: 48 | 49 | ```js 50 | ["\\?para\\=[0-9]", 1501680075442] 51 | ``` 52 | 53 | When Donleeve tries to emit the `onAction` event, it would check all listed blocks that have their specified regex match the current URL. If a block matches it and its time is greater than `Date.now()`, the action would be blocked. After 10 minutes have passed since the block's creation, the block will be obsolete. 54 | 55 | # API 56 | 57 | ## Properties 58 | 59 | ```js 60 | Donleeve.options // The configuration object. 61 | Donleeve.enabled // Set to `false` to disable Donleeve. 62 | Donleeve.acted // Whether the exit intent action fired on this page visit. 63 | ``` 64 | 65 | ## Methods 66 | 67 | ```js 68 | /** 69 | * Loops through all the keys of an object and saves them in `_conf`. 70 | * @param {object} options 71 | */ 72 | Donleeve.setOptions(options) 73 | 74 | /** 75 | * Optionally configures the library, gives it an action callback and binds 76 | * the exit intent triggers after the initial bind delay. 77 | * @param {object} config Optional configuration object used to modify 78 | * `_conf`. 79 | * @param {function} callback Function to call when the exit intent must be 80 | * activated. 81 | */ 82 | Donleeve.init(config, callback) 83 | 84 | /** 85 | * This function gives you the ability to remove old storage blocks. For 86 | * example, if you set a block for 2 weeks and later decide to reduce it to 87 | * 5 minutes, any visitor who came while the block was 2 weeks will have to 88 | * wait for it to pass. 89 | * 90 | * With this function, you can pass a string that is saved in the storage. 91 | * If its new value differs from the old one, the blocks are purged. 92 | * 93 | * Back to our example. You release version 1 of your site with the 2 week 94 | * block and pass "v1" to this function. It is saved. Then, when you release 95 | * version 2 with the 5 minute block, you pass "v2" to this function. It 96 | * notices the difference and clears the previous blocks. 97 | * @param {string} purge The purge string. 98 | */ 99 | Donleeve.purgeBlocks(purge) 100 | ``` 101 | 102 | ## Events 103 | 104 | ```js 105 | // Fired after the user tracking events have been attached. 106 | Donleeve.onBound 107 | 108 | // Fired when any event triggered and met the exit intent condition. 109 | // Receives one argument - the DOM event that was triggered. 110 | Donleeve.onTrigger 111 | 112 | // Fired when the action was blocked by localStorage. 113 | // Receives two arguments - the regex string used to check the URL, the time remaining on the block. 114 | Donleeve.onStorageBlock 115 | 116 | // The main action callback. Set via `init()`. 117 | // Receives one argument - the DOM event that triggered the action. 118 | Donleeve.onAction 119 | ``` 120 | 121 | ## Options 122 | 123 | ```js 124 | bindDelay // Time to wait before binding the user tracking events. 125 | bindEventBlur // Whether to use the "onblur" event. 126 | bindEventMouseLeave // Whether to use the "onmouseleave" event. 127 | bindEventMouseMove // Whether to use the "onmousemove" event. 128 | storageBlockingRegex // What blocking regex to apply on this page. Default is "*" and blocks on all URLs. 129 | storageBlockingMinutes // For how many minutes the storage block lasts. 130 | ignoreStorageBlocking // Whether to ignore storage blocks. 131 | ignoreFlagBlocking // Whether to fire the action once per page visit. `Donleeve.acted` is the flag. 132 | ``` 133 | 134 | # Usage 135 | 136 | Start by including [active-timeout.js](https://github.com/hdodov/active-timeout.js) and Donleeve in your page: 137 | 138 | ```html 139 | 140 | 141 | ``` 142 | 143 | After that, initialize Donleeve: 144 | 145 | ```js 146 | Donleeve.init({ 147 | bindDelay: 2000 148 | }, function (e) { 149 | alert("STAY ON MY PAGE, PLEASE."); 150 | }); 151 | ``` 152 | 153 | **Note: If you couldn't do your exit intent action for some reason, you can return `false` in the callback function to make Donleeve act as if nothing happened and add no blocks.** 154 | 155 | # Changelog 156 | 157 | 1.0.0 - Initial release. -------------------------------------------------------------------------------- /docs/node_modules/modalite/README.md: -------------------------------------------------------------------------------- 1 | # Modalite 2 | Is the simplest JavaScript modal library. No jQuery, classes... Just a few HTML attributes. 3 | 4 | # [Demo](https://hdodov.github.io/modalite/) 5 | Can be seen [here](https://hdodov.github.io/modalite/). 6 | 7 | # Installation 8 | 9 | ``` 10 | npm install modalite 11 | ``` 12 | 13 | or 14 | 15 | ``` 16 | git clone https://github.com/hdodov/modalite/ 17 | ``` 18 | 19 | # How to use? 20 | You could use the Modalite modal template and CSS, it takes responsiveness into consideration. If you're not interested in that, you can create your modal from scratch too. 21 | 22 | Whatever you do, start by including the Modalite JavaScript in your page: 23 | ```html 24 | 25 | ``` 26 | 27 | ## Building a modal with the Modalite template 28 | The CSS provided by the library styles the modal so that it's visible only when it has the `modal-visible` class. It also styles the close button, container and content area. For modals with the `modal-remote` class, an overlay and loading spinner are also added. To have all of that, simply include the CSS: 29 | 30 | ```html 31 | 32 | ``` 33 | 34 | Then, to create a modal, use this template: 35 | 36 | ```html 37 | 46 | ``` 47 | 48 | At this point, your modal will be sitting silently and won't be visible. Now, you need to add a trigger: 49 | 50 | ```html 51 |

Click me for a juicy modal!

52 | ``` 53 | 54 | **Note: If you have a remote modal, it won't have the loading spinner and overlay unless you add the `modal-remote` class to the element with the `data-modal` attribute.** 55 | 56 | ## Building a modal with your own CSS 57 | To create a modal, you simply need to: 58 | 59 | 1. Add an element with the `data-modal` attribute and give it an `id`. Style it so that it's hidden. You can use the `visibility` CSS property. It's good for the job, because it's affected by transitions and makes animations a whole lot easier. Add another rule-set for when the element has the `modal-visible` class. This should define its visible behaviour. 60 | 2. Add an element with the `data-open-modal` attribute and set its value to equal the previously created modal's `id`. When clicked, this will add the `modal-visible` class to the targeted modal. 61 | 3. **Optionally**, you can add a close button for the modal. By default, it will be closed when its background is clicked, as this is expected modal behaviour. To add a close functionality, simply add any element **inside** the modal and give it the `data-close-modal` attribute. 62 | 63 | For example, this would be a fully functional modal: 64 | 65 | ```html 66 |
67 |

Close me!

68 |
69 | 70 |

Open the modal!

71 | ``` 72 | 73 | You just need to add some CSS to make the modal is invisible by default and add some styles that show it when it has the `modal-visible` class. 74 | 75 | # Remote modals 76 | They rock! Have a 5MB Privacy Policy? Remote modals are the way. No need to demolish your page loading times with something nobody's going to read. **Ever.** Simply load all of that text when the modal is opened. This way, if your visitor isn't interested in your Privacy Policy, he wouldn't have to wait for it to load in those crucial first seconds of his visit. 77 | 78 | ## How do remote modals work? 79 | When the modal is opened, its contents are searched for elements with the `data-modal-remote` attribute. It should hold the URL to the desired resource. However, DOM operations can be costly, especially for large elements. Suppose you decided to actually splatter that Privacy Policy directly in your modal. It could have thousands of elements... searching for an attribute that might not even exist will be a huge overhead. 80 | 81 | That's why the remote modal must have the `data-modal-has-remote` attribute. Without it, you'll just be having a regular modal. No DOM searches will be performed whatsoever. 82 | 83 | When an element with `data-modal-remote` is found, two things can happen: 84 | 85 | - If the element is **not** an `