1/** 2 * Aurora Heatmap Reporter 3 * 4 * @package aurora-heatmap 5 * @copyright 2019-2022 R3098 <[email protected]> 6 * @version 1.5.4 7 */ 8 9/** 10 * Anonymous function for scope 11 */ 12(function() { 13 "use strict"; 14 15 var html, body; 16 17 /** 18 * Main object 19 */ 20 var self = { 21 readY: 0, 22 readPosition: 1, 23 readTimerCount: 0, 24 maxReadY: 0, 25 disabled: false, 26 ajax_delay: 0, 27 28 /** 29 * Initializer 30 */ 31 init: function() { 32 html = document.documentElement; 33 body = document.body; 34 35 this.args = aurora_heatmap_reporter, 36 this.args.interval = ( parseInt( this.args.interval ) || 10 ) * 1000; 37 this.args.stacks = parseInt( this.args.stacks ) || 10; 38 this.args.reports = this.args.reports.split( ',' ); 39 this.args.debug = ! ! parseInt( this.args.debug ); 40 41 this.args.ajax_delay_time = parseInt( this.args.ajax_delay_time ); 42 if ( ! Number.isInteger( this.args.ajax_delay_time ) ) { 43 this.args.ajax_delay_time = 3000; 44 } 45 this.ajax_delay = new Date().getTime() + this.args.ajax_delay_time; 46 47 const callback_click = self.push_click.bind( self ); 48 const callback_breakaway = self.push_breakaway.bind( self ); 49 50 const md = new MobileDetect( window.navigator.userAgent ); 51 52 this.args.access = md.mobile() ? 'mobile' : md.tablet() ? 'tablet' : 'pc'; 53 54 switch ( this.args.access ) { 55 case 'mobile': 56 this.readPosition = 0.1; // 10% of the window. 57 window.addEventListener( 'pagehide', callback_breakaway ); 58 Array.prototype.forEach.call( 59 document.body.children, 60 function( e ) { 61 e.addEventListener( 'click', callback_click ); 62 } 63 ); 64 break; 65 case 'pc': 66 this.readPosition = 0.5; // Center of the screen. 67 window.addEventListener( 'beforeunload', callback_breakaway ); 68 document.addEventListener( 'click', callback_click ); 69 break; 70 case 'tablet': 71 default: 72 return; 73 } 74 75 /** 76 * Hook Calculate reading area 77 */ 78 window.setInterval( 79 function() { 80 self.calc_attention(); 81 }, 82 1000 83 ); 84 }, 85 86 /** 87 * Get current readY 88 */ 89 getReadY: function() { 90 return Math.floor( this.getScrollTop() + this.getWindowHeight() * this.readPosition ); 91 }, 92 93 /** 94 * Calcurate attention area 95 */ 96 calc_attention: function() { 97 var readY = this.getReadY(); 98 this.maxReadY = Math.max( this.maxReadY, readY ); 99 if ( readY === this.readY ) { 100 this.readTimerCount++; 101 } else { 102 this.readTimerCount = 0; 103 } 104 this.readY = readY; 105 if ( 3 === this.readTimerCount ) { 106 this.push_attention(); 107 } 108 var now = new Date().getTime(); 109 if ( now - this.lastTime > this.args.interval ) { 110 this.push_data( null, true ); 111 } 112 }, 113 114 /** 115 * Get Mouse Cursor Position 116 * 117 * @param event event 118 * @return Object 119 */ 120 getCursorPos: function( event ) { 121 var x, y; 122 if ( ( event.clientX || event.clientY ) && body.scrollLeft ) { 123 return { 124 x: event.clientX + body.scrollLeft, 125 y: event.clientY + body.scrollTop, 126 }; 127 } else if ( ( event.clientX || event.clientY ) && document.compatMode == 'CSS1Compat' && html.scrollLeft ) { 128 return { 129 x: event.clientX + html.scrollLeft, 130 y: event.clientY + html.scrollTop, 131 }; 132 } else if ( event.pageX || event.pageY ) { 133 return { 134 x: event.pageX, 135 y: event.pageY, 136 }; 137 } 138 }, 139 140 /** 141 * Push click data 142 * 143 * @param event event 144 */ 145 push_click: function( event ) { 146 var pos = this.getCursorPos( event ); 147 if ( ! pos ) { 148 return; 149 } 150 pos.event = 'click_' + this.args.access; 151 this.push_data( pos, true ); 152 }, 153 154 /** 155 * Get content end 156 */ 157 getContentEnd: function() { 158 var e = document.getElementsByClassName( 'ahm-content-end-marker' ), y = 0; 159 if ( e && e.length ) { 160 e = e[ e.length - 1 ]; 161 y = this.getPageHeight() - window.pageYOffset - e.getBoundingClientRect().bottom; 162 y = Math.max( 0, y ); 163 } 164 return y; 165 }, 166 167 /** 168 * Push breakaway data 169 * 170 * @param event event 171 */ 172 push_breakaway: function( event ) { 173 var maxReadY = Math.max( this.maxReadY, this.getReadY() ); 174 175 this.push_data( 176 { 177 event: 'breakaway_' + this.args.access, 178 x: this.getContentEnd(), 179 y: maxReadY, 180 }, 181 false 182 ); 183 }, 184 185 /** 186 * Push attention data 187 */ 188 push_attention: function() { 189 this.push_data( 190 { 191 event: 'attention_' + this.args.access, 192 x: this.getContentEnd(), 193 y: this.readY, 194 }, 195 true 196 ); 197 }, 198 199 /** 200 * Get Scroll Top 201 * 202 * @return Number 203 */ 204 getScrollTop: function() { 205 return html.scrollTop || body.scrollTop; 206 }, 207 208 /** 209 * Get Page Width 210 * 211 * @return Number 212 */ 213 getPageWidth: function() { 214 return html.clientWidth || body.clientWidth || 0; 215 }, 216 217 /** 218 * Get Page Height 219 * 220 * @return Number 221 */ 222 getPageHeight: function() { 223 return Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); 224 }, 225 226 /** 227 * Get Window Height 228 * 229 * @return Number 230 */ 231 getWindowHeight: function() { 232 return window.innerHeight || html.clientHeight || null; 233 }, 234 235 /** 236 * Temporary stacking data 237 */ 238 stack: [], 239 240 /** 241 * Last sending time 242 */ 243 lastTime: new Date().getTime(), 244 245 /** 246 * Build preview HTML 247 * 248 * For debug. 249 * 250 * @param Object e 251 * @return String 252 */ 253 build_preview: function( e ) { 254 return '<div><b>event=</b> ' + e.event + ' <b>x=</b> ' + ('x' in e ? e.x : 'null') + ' <b>y=</b> ' + e.y + ' <b>height=</b> ' + e.height + ' <b>width=</b> ' + e.width + '</div>'; 255 }, 256 257 /** 258 * Show preview HTML 259 * 260 * @param String title 261 * @param Array content 262 * @param Object style 263 * @param Number timeout 264 */ 265 show_preview: function( title, content, style, timeout ) { 266 var div = document.createElement( 'div' ); 267 div.setAttribute( 'style', 'color:#000;padding:0.2em;position:fixed;right:0;border:1px solid #000;font-family:monospace;z-index:999999;' ); 268 div.style.background = style.background; 269 div.style.top = style.top; 270 div.innerHTML = '<div style="color:' + style.color + '"><b>' + title + '</b></div>' + content.map( this.build_preview ).join( '' ); 271 body.appendChild( div ); 272 window.setTimeout( 273 function() { 274 body.removeChild( div ); 275 }, 276 timeout 277 ); 278 }, 279 280 /** 281 * Push data 282 * 283 * @param Object data 284 * @param Boolean is_async 285 */ 286 push_data: function( data, is_async ) { 287 if ( this.disabled ) { 288 return; 289 } 290 291 var now = new Date().getTime(), post; 292 293 if ( data && ~this.args.reports.indexOf( data.event ) ) { 294 data.time = now; 295 data.width = this.getPageWidth(); 296 data.height = this.getPageHeight(); 297 this.stack.push( data ); 298 299 // Debug: display stored data for 1 second. 300 if ( this.args.debug ) { 301 this.show_preview( 'Store', [ data ], { color: '#963', background: '#ffc', top: '0' }, 1000 ); 302 } 303 } 304 305 // Wait jQuery. 306 if ( ! 'jQuery' in window ) { 307 return; 308 } 309 310 // Avoid recording such as JavaScript redirects. 311 if ( now <= this.ajax_delay ) { 312 return; 313 } 314 315 // For async, check interval and stacks. 316 if ( is_async && ( now - this.lastTime ) < this.args.interval && this.stack.length < this.args.stacks ) { 317 return; 318 } 319 320 // Stacked no data, do nothing. 321 if ( ! this.stack.length ) { 322 return; 323 } 324 325 [ post, this.stack ] = [ this.stack, [] ]; 326 post.forEach( 327 function( e ) {
328 e.time = Math.floor( ( e.time - now ) / 1000 ); 329 e.x = Math.floor( e.x ); 330 e.y = Math.floor( e.y ); 331 e.width = Math.floor( e.width ); 332 e.height = Math.floor( e.height ); 333 } 334 ); 335 this.lastTime = now; 336 337 // Debug: display sending data for 5 seconds. 338 if ( this.args.debug ) { 339 this.show_preview( 'Send', post, { color: '#369', background: '#cff', top: '4em' }, 5000 ); 340 } 341 342 var res = jQuery.ajax( 343 { 344 type: 'POST', 345 datatype: 'json', 346 url: this.args.ajax_url, 347 cache: false, 348 timeout: 3000, 349 async: is_async, 350 data: { 351 url: document.location.href, 352 title: document.title, 353 data: post, 354 action: this.args.action, 355 }, 356 } 357 ); 358 359 if ( res.readyState ) { 360 return; 361 } 362 363 if ( navigator.sendBeacon ) { 364 var form = new FormData(); 365 form.append( 'action', this.args.action ); 366 form.append( 'url', document.location.href ); 367 form.append( 'title', document.title ); 368 post.forEach( 369 function( e, i ) { 370 for ( var key in e ) { 371 if ( ! e.hasOwnProperty( key ) ) { 372 continue; 373 } 374 form.append( 'data[' + i + '][' + key + ']', e[key] ); 375 } 376 } 377 ); 378 379 if ( navigator.sendBeacon( this.args.ajax_url, form ) ) { 380 return; 381 } 382 } 383 384 self.disabled = true; 385 }, 386 }; 387 388 function init() { 389 var ms = Date.now(); 390 var requirements = [ 'jQuery', 'MobileDetect', 'aurora_heatmap_reporter' ]; 391 392 // Avoid ReferenceError. 393 function check() { 394 var now = Date.now(); 395 396 // Check reqruirements. 397 requirements = requirements.filter( 398 function( e ) { 399 return ! window[ e ]; 400 } 401 ); 402 403 if ( ! requirements.length ) { 404 // Start main initialization. 405 self.init(); 406 } else if ( 15000 < now - ms ) { 407 // Timed out in 15 seconds. 408 requirements.forEach( 409 function( e ) { 410 console.error( e + ' is not defined. Timed out.' ); 411 } 412 ); 413 } else { 414 // Retry. 415 setTimeout( check, 250 ); 416 } 417 } 418 419 check(); 420 } 421 422 if ( ~location.search.indexOf( "aurora-heatmap=" ) ) { 423 return; 424 } else if ( 'loading' !== document.readyState ) { 425 init(); 426 } else { 427 document.addEventListener( 'DOMContentLoaded', init ); 428 } 429})(); 430 431/* vim: set ts=4 sw=4 sts=4 noet: */
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.