1(function($){ 2 3 var popupviewer = function() { 4 }; 5 6 /* singleton */ 7 var instance = null; 8 $.popupviewer = function() { 9 return instance || (instance = new popupviewer()); 10 }; 11 12 // Static functions 13 (function(_){ 14 15 var viewer = null; 16 var content = null; 17 var additionalContent = null; 18 var BASE_URL = DOKU_BASE + 'lib/exe/ajax.php'; 19 var viewerIsFixed = false; 20 var next = null; 21 var previous = null; 22 var internal = {}; 23 24 _.popupImageStack = null; 25 26 internal.log = function(message) { 27 // console.log(message); 28 }; 29 30 _.showViewer = function() { 31 32 if ( viewer == null ) { 33 34 viewer = $('<div id="popupviewer"/>').click(_.hideViewer).appendTo('body'); 35 content = $('<div class="content"/>').click(function(e){e.stopPropagation()}); 36 content.current = $(); 37 38 additionalContent = $('<div class="additionalContent dokuwiki"/>'); 39 viewerIsFixed = viewer.css('position'); 40 41 $('<div class="controls"/>'). 42 append(content). 43 append(additionalContent). 44 append(previous = $('<a class="previous"/>').click({'direction': -1}, _.skipImageInDirection)). 45 append(next = $('<a class="next"/>').click({'direction': 1}, _.skipImageInDirection)). 46 append($('<a class="close"/>').addClass('visible').click(_.hideViewer)). 47 appendTo(viewer); 48 49 $(document).keydown(internal.globalKeyHandler); 50 51 } 52 53 content.empty(); 54 additionalContent.empty(); 55 $('body').css('overflow', 'hidden'); 56 viewer.show(); 57 return _; 58 }; 59 60 _.hideViewer = function(e, finalFunction) { 61 if ( viewer != null ) { 62 $('body').css('overflow', 'auto'); 63 64 additionalContent.animate({ 65 opacity: 0, 66 height: 0 67 }); 68 69 content.animate({ 70 width : 208, 71 height : 13, 72 }).parent('.controls').animate({ 73 top : '50%', 74 left : '50%', 75 'margin-left' : -104 76 }).parent('#popupviewer').animate({ 77 opacity: finalFunction ? 1 : 0 78 }, function(){ 79 viewer.hide(); 80 81 content.empty(); 82 additionalContent.empty(); 83 content.current = null; 84 85 additionalContent.css({ 86 opacity: 1, 87 height: '' 88 }); 89 90 content.css({ 91 width : '', 92 height : '', 93 }).parent('.controls').css({ 94 top : '', 95 left : '', 96 'margin-left' : '' 97 }).parent('#popupviewer').css({ 98 opacity : 1 99 }); 100 101 if ( typeof finalFunction == 'function' ) { 102 finalFunction(e); 103 } 104 }); 105 } 106 107 return _; 108 }; 109 110 internal.globalKeyHandler = function(e) { 111 112 if ( !viewer.is(":visible") ) return; 113 114 switch(e.keyCode) { 115 case 39: // Right 116 e.stopPropagation(); 117 next.click(); 118 break; 119 case 37: // Left 120 e.stopPropagation(); 121 previous.click(); 122 break; 123 case 27: // Escape 124 e.stopPropagation(); 125 _.hideViewer(); 126 break; 127 } 128 }; 129 130 _.presentViewerWithContent = function(e, popupData) { 131 132 popupData = popupData || this.popupData || e.target.popupData; // Either as param or from object 133 134 /* 135 popupData = { 136 isImage: boolean, 137 call: ajax_handler, 138 src: URL, 139 id: alternate_wiki_page, 140 width: width_of_window, 141 height: height_of_window 142 } 143 */ 144 145 if ( !popupData ) { return; } 146 e && e.preventDefault(); 147 148 if ( content && !content.is(':empty') ) { 149 e.target.popupData = popupData; 150 _.hideViewer(e, _.presentViewerWithContent); 151 return _; 152 } 153 154 _.showViewer(); 155 156 content.current = $(this); 157 158 internal.log(popupData); 159 160 if ( popupData.isImage ) { 161 162 // Load image routine 163 internal.log("loading an image"); 164 popupData.call = popupData.call || '_popup_load_image_meta'; 165 var img = new Image(); 166 img.onload = function() { 167 168 var image = $(img); 169 170 var wrapper = $('<div/>').load(BASE_URL, popupData, function() { 171 172 // Force size for the moment 173 content.css({ 174 width: content.width(), 175 height: content.height(), 176 overflow: 'hidden' 177 }); 178 179 content.append(image); 180 content.popupData = jQuery.extend(true, {}, popupData); 181 182 additionalContent.html(wrapper.html()); 183 _.registerCloseHandler(); 184 _.resizePopup(popupData.width, popupData.height, additionalContent.innerHeight(), image, false, popupData.hasNextPrevious); 185 }); 186 }; 187 img.src = popupData.src || this.href; 188 189 } else { 190 191 popupData.call = popupData.call || '_popup_load_file'; 192 popupData.src = popupData.src || BASE_URL; 193 var wrapper = $('<div/>').load(popupData.src, popupData, function(response, status, xhr) { 194 195 var success = function(node) 196 { 197 var popupScriptNode = node; 198 if ( !popupData.do || popupData.do != 'export_xhtmlbody') { 199 popupScriptNode = jQuery('<div/>').append(node.find('popupscript')); 200 node = node.find('div.dokuwiki,body').first(); 201 } 202 203 node.waitForImages({ 204 finished: function() { 205 206 // Force size for the moment 207 content.css({ 208 width: content.width(), 209 height: content.height() 210 }); 211 212 $(this).on( "click", "a", function(){ 213 // Insert base URL, so the link will be correct. hopefully. 214 var base = $('<base href="' + popupData.src + '"/>'); 215 $("head").append(base); 216 }); 217 218 content.html(this); 219 220 // If we want to have all other pages open as well, go with it. 221 if ( popupData.keepOpen ) { 222 _.propagateClickHandler($(this), popupData); 223 } 224 225 // Check for Javascript to execute 226 var scripts = popupScriptNode.find('popupscript'); 227 if ( scripts.length > 0 ) { 228 229 var randomID = Math.ceil(Math.random()*1000000); 230 content.attr('id', randomID); 231 232 scripts.each(function(){ 233 var script = (this.innerHTML || this.innerText); 234 if ( script.length > 0 ) 235 { 236 try{ 237 $.globalEval("try{\n(function(){\n"+script+"\n}).call(jQuery('div#"+randomID+"').get(0));\n}catch(e){console?console.log(e):null}\n//"); 238 } catch (e) { 239 internal.log("Exception!"); 240 internal.log(e); 241 } 242 } 243 }); 244 245 _.addGoogleOutboundTrackingInformation( $("div#"+randomID) ); 246 } 247 248 if ( popupData.postPopupHook && typeof popupData.postPopupHook == 'function' ) { 249 // Post-Hook which as to be a javascript function and my modify the popupData 250 popupData.postPopupHook(this, popupData); 251 } 252 253 _.registerCloseHandler(); 254 // At the very end we will resize the popup to fit the content. 255 _.resizePopup(popupData.width, popupData.height, null, content, true, popupData.hasNextPrevious); 256 257 }, waitForAll: true}); 258 }; 259 260 if ( status == "error") { 261 // Go for an iframe 262 var finished = false; 263 var iframe = null; 264 265 var messageFunction = function(event) { 266 267 finished = true; 268 var data = event.data || event.originalEvent.data; 269 // If this message does not come with what we want, discard it. 270 if ((typeof data).toLowerCase() == "string" || !data.message 271 || data.message != 'frameContent') { 272 alert("Could not load page via popupviewer. The page responded with a wrong message."); 273 return; 274 } 275 276 iframe.remove(); 277 278 // Clear the window Event after we are done! 279 $(window).unbind("message", messageFunction); 280 281 success($(data.body)); 282 }; 283 284 popupData.src = internal.getCurrentLocation(); 285 var iframe = $('<iframe/>').load(function(){ 286 287 var frame = this; 288 if ( frame.contentWindow.postMessage ) { 289 290 // Register the Message Event for PostMessage receival 291 $(window).bind("message", messageFunction); 292 293 // Send a message 294 var message = "getFrameContent"; 295 frame.contentWindow.postMessage(message, "*"); 296 } 297 298 }).hide().attr('src', popupData.src ).appendTo('body'); 299 300 window.setTimeout(function() { 301 if (!finished) { 302 iframe.remove(); 303 alert("Could not load page via popupviewer. The page is not available."); 304 } 305 }, 30000); 306 307 } else { 308 success(wrapper); 309 } 310 311 }); 312 } 313 }; 314 315 /* has to be called via popupscript in page if needed. */ 316 _.propagateClickHandler = function(node, popupData) { 317 node.find('a[href],form[action]'). 318 each(function(){ 319 // Replace all event handler 320 321 var element = $(this); 322 323 var urlpart = element.attr('href') || element.attr('action') || ""; 324 if ( urlpart.match(new RegExp("^#.*?$")) ) { 325 // Scroll to anchor 326 element.click(function(){ 327 content.get(0).scrollTop( urlpart == '#' ? 0 : $(urlpart).offset().top); 328 }); 329 } 330 331 if ( this.getAttribute('data-popupviewer') ) { 332 this.popupData = $.parseJSON(this.getAttribute('data-popupviewer')); 333 this.removeAttribute('data-popupviewer'); 334 } else { 335 this.popupData = jQuery.extend(true, {}, popupData); 336 this.popupData.src = urlpart; 337 this.popupData.do = 'export_xhtmlbody'; 338 339 delete(this.popupData.id); // or it will always load this file. 340 } 341 342 $(this).bind('click', function(e){ 343 e.stopPropagation(); e.preventDefault(); 344 _.hideViewer(e, _.presentViewerWithContent); 345 }); 346 }); 347 }; 348 349 internal.getCurrentLocation = function() { 350 return content.current.attr('href') || content.current.attr('src') || content.current.attr('action'); 351 }; 352 353 internal.optimalSize = function(offsetElement, isPageContent) { 354/* 355 if ( !isPageContent ) { 356 return {width: offsetElement.get(0).width, height: offsetElement.get(0).height}; 357 } 358*/ 359 var prevWidth = content.width(); 360 var prevHeight = content.height(); 361 362 offsetElement.css({width:'auto', height: 'auto'}); 363 364 var width = offsetElement.naturalWidth() || offsetElement.outerWidth(); 365 var height = offsetElement.naturalHeight() || offsetElement.outerHeight(); 366 367 // Reset to previous size so the whole thing will animate from the middle 368 offsetElement.css({width:prevWidth, height: prevHeight}); 369 370 return {width: width, height: height}; 371 }; 372 373 _.resizePopup = function(width, height, additionalHeight, offsetElement, isPageContent, needsNextPrevious) { 374 375 internal.log("Initial Size: " + width + " " + height); 376 internal.log(offsetElement); 377 378 if ( offsetElement && !width && !height) { 379 var optimalSize = internal.optimalSize(offsetElement, isPageContent); 380 width = optimalSize.width; 381 height = optimalSize.height; 382 } 383 384 internal.log("OffsetElement Size: " + width + " " + height); 385 width = parseInt(width) || ($(window).width() * 0.7); 386 height = parseInt(height) || ($(window).height() * 0.8); 387 388 var ratio = width / height; 389 var maxHeight = ( $(window).height() * 0.99 ) - 60; 390 var maxWidth = ( $(window).width() * 0.99 ) - 40; 391 392 additionalHeight = additionalHeight || 0; 393 height += additionalHeight; 394 395 internal.log("After Additional Content Size: " + width + " " + height); 396 397 if ( height > maxHeight ) { 398 height = maxHeight; 399 if ( !isPageContent ) { // If this is an image we will have to fix the size 400 width = (height - additionalHeight) * ratio; 401 } else { 402 width += 20; // For the scroller Bar that will apear; 403 } 404 } 405 406 if ( width > maxWidth ) { 407 width = maxWidth; 408 if ( !isPageContent ) { // If this is an image we will have to fix the size 409 height = width / ratio + additionalHeight; 410 } 411 } 412 413 var xOffset = viewerIsFixed ? 0 : $(document).scrollLeft() || 0; 414 var yOffset = viewerIsFixed ? 0 : $(document).scrollTop() || 0; 415 416 yOffset = Math.max(($(window).height() - height) * 0.5 + yOffset, 5); 417 xOffset += ($(window).width() - width) * 0.5; 418 419 internal.log("Final Size: " + width + " " + height); 420 internal.log("Final Offset: " + xOffset + " " + yOffset); 421 422 if ( !isPageContent || (offsetElement && offsetElement.is('img')) ) { 423 424 offsetElement.animate({ 425 width : width, 426 height : height - additionalHeight 427 }); 428 429 content.css({ 430 width : '', 431 height : '', 432 overflow: '' 433 }); 434 435 } else { 436 content.animate({ 437 width : width, 438 height : isPageContent ? height : 'auto', 439 }); 440 } 441 442 content.parent().animate({ 443 top : yOffset, 444 left : xOffset, 445 'margin-left' : 0 446 }); 447 448 if ( isPageContent ) { 449 content.removeClass('isImage'); 450 } else { 451 content.addClass('isImage'); 452 } 453 454 _.handleNextAndPrevious(!isPageContent || needsNextPrevious); 455 return _; 456 }; 457 458 _.skipImageInDirection = function(e) 459 { 460 e.stopPropagation(); 461 462 if ( !$(this).is(':visible') ) { return; } 463 464 var skipTo = $.inArray(content.current.get(0), _.popupImageStack) + e.data.direction; 465 skipTo = Math.min(_.popupImageStack.length-1, Math.max(skipTo, 0)); 466 467 internal.log("skipping " + (e.data.direction < 0 ? 'previous' : 'next') + ' ' + skipTo ); 468 return _.skipToImage(skipTo, e.data.direction); 469 }; 470 471 _.skipToImage = function(skipTo, inDirection) 472 { 473 if ( !$(_.popupImageStack[skipTo]).is(content.current) ) { 474 _.hideViewer(null, function() { 475 // Deliver extra functionality to clicked item. 476 var nextItem = _.popupImageStack[skipTo]; 477 (nextItem.popupData && nextItem.popupData.click && nextItem.popupData.click(skipTo, inDirection)) || $(nextItem).click(); 478 }); 479 } 480 481 return _; 482 }; 483 484 _.isFirst = function() { 485 return _.popupImageStack.first().is(content.current); 486 }; 487 488 _.isLast = function() { 489 return _.popupImageStack.last().is(content.current); 490 }; 491 492 _.handleNextAndPrevious = function(currentIsImage) { 493 494 if ( currentIsImage && _.popupImageStack && _.popupImageStack.size && _.popupImageStack.length > 1) { 495 496 if ( _.isFirst() ) { 497 previous.addClass('inactive'); 498 } else { 499 previous.removeClass('inactive'); 500 } 501 502 if ( _.isLast() ) { 503 next.addClass('inactive'); 504 } else { 505 next.removeClass('inactive'); 506 } 507 508 next.addClass('visible'); 509 previous.addClass('visible'); 510 } else { 511 next.removeClass('visible'); 512 previous.removeClass('visible'); 513 } 514 515 return _; 516 }; 517 518 _.registerCloseHandler = function () { 519 $('*[data-popupviewerclose]').each(function(){ 520 $(this).click(function(e){ 521 e && e.preventDefault(); 522 _.hideViewer(e); 523 return false; 524 }); 525 if (this.removeAttribute) this.removeAttribute('data-popupviewerclose'); 526 }); 527 }; 528 529 _.init = function(popupImageStack) { 530 531 _.popupImageStack = $(popupImageStack || '*[data-popupviewer]').each(function(){ 532 this.popupData = this.popupData || $.parseJSON(this.getAttribute('data-popupviewer')); 533 if (this.removeAttribute) this.removeAttribute('data-popupviewer'); 534 $(this).unbind('click').click(_.presentViewerWithContent); 535 }).filter(function(){ 536 // Only images allowed in Stack. 537 return this.popupData.isImage || this.popupData.hasNextPrevious; 538 }); 539 540 return _; 541 }; 542 543 _.addGoogleOutboundTrackingInformation = function( $findRoot ) { 544 // track outgoing links, once the document was loaded 545 if (JSINFO.ga && JSINFO.ga.trackOutboundLinks) { 546 // https://support.google.com/analytics/answer/1136920?hl=en 547 $findRoot.find('a.urlextern, a.interwiki').click(function (e) { 548 var url = this.href; 549 ga('send', 'event', 'outbound', 'click', url, { 550 'transport': 'beacon', 551 'hitCallback': function () {} 552 }); 553 554 return true; 555 }); 556 } 557 }; 558 559 })(popupviewer.prototype); 560 561 // Namespace all events. 562 var eventNamespace = 'waitForImages'; 563 564 // CSS properties which contain references to images. 565 $.waitForImages = { 566 hasImageProperties: ['backgroundImage', 'listStyleImage', 'borderImage', 'borderCornerImage', 'cursor'] 567 }; 568 569 // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded. 570 $.expr[':'].uncached = function (obj) { 571 // Ensure we are dealing with an `img` element with a valid `src` attribute. 572 if (!$(obj).is('img[src!=""]')) { 573 return false; 574 } 575 576 // Firefox's `complete` property will always be `true` even if the image has not been downloaded. 577 // Doing it this way works in Firefox. 578 var img = new Image(); 579 img.src = obj.src; 580 return !img.complete; 581 }; 582 583 $.fn.waitForImages = function (finishedCallback, eachCallback, waitForAll) { 584 585 var allImgsLength = 0; 586 var allImgsLoaded = 0; 587 588 // Handle options object. 589 if ($.isPlainObject(arguments[0])) { 590 waitForAll = arguments[0].waitForAll; 591 eachCallback = arguments[0].each; 592 // This must be last as arguments[0] 593 // is aliased with finishedCallback. 594 finishedCallback = arguments[0].finished; 595 } 596 597 // Handle missing callbacks. 598 finishedCallback = finishedCallback || $.noop; 599 eachCallback = eachCallback || $.noop; 600 601 // Convert waitForAll to Boolean 602 waitForAll = !! waitForAll; 603 604 // Ensure callbacks are functions. 605 if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) { 606 throw new TypeError('An invalid callback was supplied.'); 607 } 608 609 return this.each(function () { 610 // Build a list of all imgs, dependent on what images will be considered. 611 var obj = $(this); 612 var allImgs = []; 613 // CSS properties which may contain an image. 614 var hasImgProperties = $.waitForImages.hasImageProperties || []; 615 // To match `url()` references. 616 // Spec: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri 617 var matchUrl = new RegExp("url\(\s*(['\"]?)(.*?)\1\s*\)", "g"); 618 619 if (waitForAll) { 620 621 // Get all elements (including the original), as any one of them could have a background image. 622 obj.find('*').addBack().each(function () { 623 var element = $(this); 624 625 // If an `img` element, add it. But keep iterating in case it has a background image too. 626 if (element.is('img:uncached')) { 627 allImgs.push({ 628 src: element.attr('src'), 629 element: element[0] 630 }); 631 } 632 633 $.each(hasImgProperties, function (i, property) { 634 var propertyValue = element.css(property); 635 var match; 636 637 // If it doesn't contain this property, skip. 638 if (!propertyValue) { 639 return true; 640 } 641 642 // Get all url() of this element. 643 while (match = matchUrl.exec(propertyValue)) { 644 allImgs.push({ 645 src: match[2], 646 element: element[0] 647 }); 648 } 649 }); 650 }); 651 } else { 652 // For images only, the task is simpler. 653 obj.find('img:uncached') 654 .each(function () { 655 allImgs.push({ 656 src: this.src, 657 element: this 658 }); 659 }); 660 } 661 662 allImgsLength = allImgs.length; 663 allImgsLoaded = 0; 664 665 // If no images found, don't bother. 666 if (allImgsLength === 0) { 667 finishedCallback.call(obj[0]); 668 } 669 670 $.each(allImgs, function (i, img) { 671 672 var image = new Image(); 673 674 // Handle the image loading and error with the same callback. 675 $(image).on('load.' + eventNamespace + ' error.' + eventNamespace, function (event) { 676 allImgsLoaded++; 677 678 // If an error occurred with loading the image, set the third argument accordingly. 679 eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load'); 680 681 if (allImgsLoaded == allImgsLength) { 682 finishedCallback.call(obj[0]); 683 return false; 684 } 685 686 }); 687 688 image.src = img.src; 689 }); 690 }); 691 }; 692 693 $(function(){ 694 695 if ( typeof $.fn.naturalWidth != 'undefined' && typeof $.fn.naturalHeight != 'undefined' ) { return; } 696 697 function img(url) { var i = new Image(); i.src = url; return i; } 698 if ('naturalWidth' in (new Image())) { 699 $.fn.naturalWidth = function() { return this[0].naturalWidth; }; 700 $.fn.naturalHeight = function() { return this[0].naturalHeight; }; 701 return; 702 } 703 704 $.fn.naturalWidth = function() { return img(this.src).width; }; 705 $.fn.naturalHeight = function() { return img(this.src).height; }; 706 }); 707 708 $(function(){ 709 $.popupviewer().init(); 710 }); 711 712})(jQuery); 713 714/* Loading the content for locally exported content */ 715(function($){ 716 $(window).bind("message", function(event){ 717 718 var data = event.data || event.originalEvent.data; 719 var source = event.source || event.originalEvent.source; 720 if (data != "getFrameContent") { 721 return; 722 } 723 724 try { 725 source.postMessage({ 726 message : "frameContent", 727 body : jQuery('html').html() 728 }, "*"); 729 } catch (e) { 730 alert("Fatal Exception! Could not load page via popupviewer.\n" + e); 731 } 732 }); 733})(jQuery); 734