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 $(new Image()).attr('src', popupData.src || this.href).load(function(){ 166 167 var image = $(this); 168 169 var wrapper = $('<div/>').load(BASE_URL, popupData, function() { 170 171 // Force size for the moment 172 content.css({ 173 width: content.width(), 174 height: content.height(), 175 overflow: 'hidden' 176 }); 177 178 content.append(image); 179 content.popupData = jQuery.extend(true, {}, popupData); 180 181 additionalContent.html(wrapper.html()); 182 _.registerCloseHandler(); 183 _.resizePopup(popupData.width, popupData.height, additionalContent.innerHeight(), image, false, popupData.hasNextPrevious); 184 }); 185 }); 186 187 } else { 188 189 popupData.call = popupData.call || '_popup_load_file'; 190 popupData.src = popupData.src || BASE_URL; 191 var wrapper = $('<div/>').load(popupData.src, popupData, function(response, status, xhr) { 192 193 var success = function(node) 194 { 195 node.find('div.dokuwiki,body').first().waitForImages({ 196 finished: function() { 197 198 // Force size for the moment 199 content.css({ 200 width: content.width(), 201 height: content.height() 202 }); 203 204 $(this).on( "click", "a", function(){ 205 // Insert base URL, so the link will be correct. hopefully. 206 var base = $('<base href="' + popupData.src + '"/>'); 207 $("head").append(base); 208 }); 209 210 content.html(this); 211 212 // Check for Javascript to execute 213 var script = ""; 214 node.find('popupscript'). 215 each(function() { 216 script += (this.innerHTML || this.innerText); 217 }); 218 219 if ( script.length > 0 ) 220 { 221 var randomID = Math.ceil(Math.random()*1000000); 222 content.attr('id', randomID); 223 224 var newContext = ""; //"jQuery.noConflict(); containerContext = this; ___ = function( selector, context ){return new jQuery.fn.init(selector,context||containerContext);}; ___.fn = ___.prototype = jQuery.fn;jQuery.extend( ___, jQuery );jQuery = ___;\n" 225 226 try{ 227 $.globalEval("try{\n(function(){\n"+newContext+script+"\n}).call(jQuery('div#"+randomID+"').get(0));\n}catch(e){}\n//"); 228 } catch (e) { 229 internal.log("Exception!"); 230 internal.log(e); 231 } 232 } 233 234 if ( popupData.postPopupHook && typeof popupData.postPopupHook == 'function' ) { 235 // Post-Hook which as to be a javascript function and my modify the popupData 236 popupData.postPopupHook(this, popupData); 237 } 238 239 _.registerCloseHandler(); 240 // At the very end we will resize the popup to fit the content. 241 _.resizePopup(popupData.width, popupData.height, null, content, true, popupData.hasNextPrevious); 242 243 }, waitForAll: true}); 244 }; 245 246 247 if ( status == "error") { 248 // Go for an iframe 249 var finished = false; 250 var iframe = null; 251 252 var messageFunction = function(event) { 253 254 finished = true; 255 var data = event.data || event.originalEvent.data; 256 // If this message does not come with what we want, discard it. 257 if ((typeof data).toLowerCase() == "string" || !data.message 258 || data.message != 'frameContent') { 259 alert("Could not load page via popupviewer. The page responded with a wrong message."); 260 return; 261 } 262 263 iframe.remove(); 264 265 // Clear the window Event after we are done! 266 $(window).unbind("message", messageFunction); 267 268 success($(data.body)); 269 }; 270 271 popupData.src = internal.getCurrentLocation(); 272 var iframe = $('<iframe/>').load(function(){ 273 274 var frame = this; 275 if ( frame.contentWindow.postMessage ) { 276 277 // Register the Message Event for PostMessage receival 278 $(window).bind("message", messageFunction); 279 280 // Send a message 281 var message = "getFrameContent"; 282 frame.contentWindow.postMessage(message, "*"); 283 } 284 285 }).hide().attr('src', popupData.src ).appendTo('body'); 286 287 window.setTimeout(function() { 288 if (!finished) { 289 iframe.remove(); 290 alert("Could not load page via popupviewer. The page is not available."); 291 } 292 }, 30000); 293 294 } else { 295 success(wrapper); 296 } 297 298 }); 299 } 300 }; 301 302 /* has to be called via popupscript in page if needed. */ 303 _.propagateClickHandler = function(node) { 304 node.find('a[href],form[action]'). 305 each(function(){ 306 // Replace all event handler 307 308 var element = $(this); 309 310 urlpart = element.attr('href') || element.attr('action') || ""; 311 if ( urlpart.match(new RegExp("^#.*?$")) ) { 312 // Scroll to anchor 313 element.click(function(){ 314 content.get(0).scrollTop( urlpart == '#' ? 0 : $(urlpart).offset().top); 315 }); 316 } 317 318 if ( this.getAttribute('data-popupviewer') ) { 319 this.popupData = $.parseJSON(this.getAttribute('data-popupviewer')); 320 this.removeAttribute('data-popupviewer'); 321 } else { 322 this.popupData = jQuery.extend(true, {}, popupData); 323 this.popupData.src = urlpart; 324 delete(this.popupData.id); // or it will always load this file. 325 } 326 327 $(this).bind('click', function(e){ 328 e.stopPropagation(); e.preventDefault(); 329 _.hideViewer(e, _.presentViewerWithContent); 330 }); 331 }); 332 }; 333 334 internal.getCurrentLocation = function() { 335 return content.current.attr('href') || content.current.attr('src') || content.current.attr('action'); 336 }; 337 338 internal.optimalSize = function(offsetElement, isPageContent) { 339/* 340 if ( !isPageContent ) { 341 return {width: offsetElement.get(0).width, height: offsetElement.get(0).height}; 342 } 343*/ 344 var prevWidth = content.width(); 345 var prevHeight = content.height(); 346 347 offsetElement.css({width:'auto', height: 'auto'}); 348 349 var width = offsetElement.naturalWidth() || offsetElement.width(); 350 var height = offsetElement.naturalHeight() || offsetElement.height(); 351 352 // Reset to previous size so the whole thing will animate from the middle 353 offsetElement.css({width:prevWidth, height: prevHeight}); 354 355 return {width: width, height: height}; 356 }; 357 358 _.resizePopup = function(width, height, additionalHeight, offsetElement, isPageContent, needsNextPrevious) { 359 360 internal.log("Initial Size: " + width + " " + height); 361 internal.log(offsetElement); 362 363 if ( offsetElement && !width && !height) { 364 var optimalSize = internal.optimalSize(offsetElement, isPageContent); 365 width = optimalSize.width; 366 height = optimalSize.height; 367 } 368 369 internal.log("OffsetElement Size: " + width + " " + height); 370 width = parseInt(width) || ($(window).width() * 0.7); 371 height = parseInt(height) || ($(window).height() * 0.8); 372 373 var ratio = width / height; 374 var maxHeight = ( $(window).height() * 0.99 ) - 60; 375 var maxWidth = ( $(window).width() * 0.99 ) - 40; 376 377 additionalHeight = additionalHeight || 0; 378 height += additionalHeight; 379 380 internal.log("After Additional Content Size: " + width + " " + height); 381 382 if ( height > maxHeight ) { 383 height = maxHeight; 384 if ( !isPageContent ) { // If this is an image we will have to fix the size 385 width = (height - additionalHeight) * ratio; 386 } else { 387 width += 20; // For the scroller Bar that will apear; 388 } 389 } 390 391 if ( width > maxWidth ) { 392 width = maxWidth; 393 if ( !isPageContent ) { // If this is an image we will have to fix the size 394 height = width / ratio + additionalHeight; 395 } 396 } 397 398 var xOffset = viewerIsFixed ? 0 : $(document).scrollLeft() || 0; 399 var yOffset = viewerIsFixed ? 0 : $(document).scrollTop() || 0; 400 401 yOffset = Math.max(($(window).height() - height) * 0.5 + yOffset, 5); 402 xOffset += ($(window).width() - width) * 0.5; 403 404 internal.log("Final Size: " + width + " " + height); 405 internal.log("Final Offset: " + xOffset + " " + yOffset); 406 407 if ( !isPageContent || (offsetElement && offsetElement.is('img')) ) { 408 409 offsetElement.animate({ 410 width : width, 411 height : height - additionalHeight 412 }); 413 414 content.css({ 415 width : '', 416 height : '', 417 overflow: '' 418 }); 419 420 } else { 421 content.animate({ 422 width : width, 423 height : isPageContent ? height : 'auto', 424 }); 425 } 426 427 content.parent().animate({ 428 top : yOffset, 429 left : xOffset, 430 'margin-left' : 0 431 }); 432 433 if ( isPageContent ) { 434 content.removeClass('isImage'); 435 } else { 436 content.addClass('isImage'); 437 } 438 439 _.handleNextAndPrevious(!isPageContent || needsNextPrevious); 440 return _; 441 }; 442 443 _.skipImageInDirection = function(e) 444 { 445 e.stopPropagation(); 446 447 if ( !$(this).is(':visible') ) { return; } 448 449 var skipTo = $.inArray(content.current.get(0), _.popupImageStack) + e.data.direction; 450 skipTo = Math.min(_.popupImageStack.length-1, Math.max(skipTo, 0)); 451 452 internal.log("skipping " + (e.data.direction < 0 ? 'previous' : 'next') + ' ' + skipTo ); 453 return _.skipToImage(skipTo, e.data.direction); 454 }; 455 456 _.skipToImage = function(skipTo, inDirection) 457 { 458 if ( !$(_.popupImageStack[skipTo]).is(content.current) ) { 459 _.hideViewer(null, function() { 460 // Deliver extra functionality to clicked item. 461 var nextItem = _.popupImageStack[skipTo]; 462 (nextItem.popupData && nextItem.popupData.click && nextItem.popupData.click(skipTo, inDirection)) || $(nextItem).click(); 463 }); 464 } 465 466 return _; 467 }; 468 469 _.isFirst = function() { 470 return _.popupImageStack.first().is(content.current); 471 }; 472 473 _.isLast = function() { 474 return _.popupImageStack.last().is(content.current); 475 }; 476 477 _.handleNextAndPrevious = function(currentIsImage) { 478 479 if ( currentIsImage && _.popupImageStack && _.popupImageStack.size() > 1) { 480 481 if ( _.isFirst() ) { 482 previous.addClass('inactive'); 483 } else { 484 previous.removeClass('inactive'); 485 } 486 487 if ( _.isLast() ) { 488 next.addClass('inactive'); 489 } else { 490 next.removeClass('inactive'); 491 } 492 493 next.addClass('visible'); 494 previous.addClass('visible'); 495 } else { 496 next.removeClass('visible'); 497 previous.removeClass('visible'); 498 } 499 500 return _; 501 }; 502 503 _.registerCloseHandler = function () { 504 $('*[data-popupviewerclose]').each(function(){ 505 $(this).click(function(e){ 506 e && e.preventDefault(); 507 _.hideViewer(e); 508 return false; 509 }); 510 if (this.removeAttribute) this.removeAttribute('data-popupviewerclose'); 511 }); 512 }; 513 514 _.init = function(popupImageStack) { 515 516 _.popupImageStack = $(popupImageStack || '*[data-popupviewer]').each(function(){ 517 this.popupData = this.popupData || $.parseJSON(this.getAttribute('data-popupviewer')); 518 if (this.removeAttribute) this.removeAttribute('data-popupviewer'); 519 $(this).unbind('click').click(_.presentViewerWithContent); 520 }).filter(function(){ 521 // Only images allowed in Stack. 522 return this.popupData.isImage || this.popupData.hasNextPrevious; 523 }); 524 525 return _; 526 }; 527 528 })(popupviewer.prototype); 529 530 // Namespace all events. 531 var eventNamespace = 'waitForImages'; 532 533 // CSS properties which contain references to images. 534 $.waitForImages = { 535 hasImageProperties: ['backgroundImage', 'listStyleImage', 'borderImage', 'borderCornerImage', 'cursor'] 536 }; 537 538 // Custom selector to find `img` elements that have a valid `src` attribute and have not already loaded. 539 $.expr[':'].uncached = function (obj) { 540 // Ensure we are dealing with an `img` element with a valid `src` attribute. 541 if (!$(obj).is('img[src!=""]')) { 542 return false; 543 } 544 545 // Firefox's `complete` property will always be `true` even if the image has not been downloaded. 546 // Doing it this way works in Firefox. 547 var img = new Image(); 548 img.src = obj.src; 549 return !img.complete; 550 }; 551 552 $.fn.waitForImages = function (finishedCallback, eachCallback, waitForAll) { 553 554 var allImgsLength = 0; 555 var allImgsLoaded = 0; 556 557 // Handle options object. 558 if ($.isPlainObject(arguments[0])) { 559 waitForAll = arguments[0].waitForAll; 560 eachCallback = arguments[0].each; 561 // This must be last as arguments[0] 562 // is aliased with finishedCallback. 563 finishedCallback = arguments[0].finished; 564 } 565 566 // Handle missing callbacks. 567 finishedCallback = finishedCallback || $.noop; 568 eachCallback = eachCallback || $.noop; 569 570 // Convert waitForAll to Boolean 571 waitForAll = !! waitForAll; 572 573 // Ensure callbacks are functions. 574 if (!$.isFunction(finishedCallback) || !$.isFunction(eachCallback)) { 575 throw new TypeError('An invalid callback was supplied.'); 576 } 577 578 return this.each(function () { 579 // Build a list of all imgs, dependent on what images will be considered. 580 var obj = $(this); 581 var allImgs = []; 582 // CSS properties which may contain an image. 583 var hasImgProperties = $.waitForImages.hasImageProperties || []; 584 // To match `url()` references. 585 // Spec: http://www.w3.org/TR/CSS2/syndata.html#value-def-uri 586 var matchUrl = new RegExp("url\(\s*(['\"]?)(.*?)\1\s*\)", "g"); 587 588 if (waitForAll) { 589 590 // Get all elements (including the original), as any one of them could have a background image. 591 obj.find('*').addBack().each(function () { 592 var element = $(this); 593 594 // If an `img` element, add it. But keep iterating in case it has a background image too. 595 if (element.is('img:uncached')) { 596 allImgs.push({ 597 src: element.attr('src'), 598 element: element[0] 599 }); 600 } 601 602 $.each(hasImgProperties, function (i, property) { 603 var propertyValue = element.css(property); 604 var match; 605 606 // If it doesn't contain this property, skip. 607 if (!propertyValue) { 608 return true; 609 } 610 611 // Get all url() of this element. 612 while (match = matchUrl.exec(propertyValue)) { 613 allImgs.push({ 614 src: match[2], 615 element: element[0] 616 }); 617 } 618 }); 619 }); 620 } else { 621 // For images only, the task is simpler. 622 obj.find('img:uncached') 623 .each(function () { 624 allImgs.push({ 625 src: this.src, 626 element: this 627 }); 628 }); 629 } 630 631 allImgsLength = allImgs.length; 632 allImgsLoaded = 0; 633 634 // If no images found, don't bother. 635 if (allImgsLength === 0) { 636 finishedCallback.call(obj[0]); 637 } 638 639 $.each(allImgs, function (i, img) { 640 641 var image = new Image(); 642 643 // Handle the image loading and error with the same callback. 644 $(image).on('load.' + eventNamespace + ' error.' + eventNamespace, function (event) { 645 allImgsLoaded++; 646 647 // If an error occurred with loading the image, set the third argument accordingly. 648 eachCallback.call(img.element, allImgsLoaded, allImgsLength, event.type == 'load'); 649 650 if (allImgsLoaded == allImgsLength) { 651 finishedCallback.call(obj[0]); 652 return false; 653 } 654 655 }); 656 657 image.src = img.src; 658 }); 659 }); 660 }; 661 662 $(function(){ 663 664 if ( typeof $.fn.naturalWidth == 'undefined' && typeof $.fn.naturalHeight == 'undefined' ) { return; } 665 666 function img(url) { var i = new Image(); i.src = url; return i; } 667 if ('naturalWidth' in (new Image())) { 668 $.fn.naturalWidth = function() { return this[0].naturalWidth; }; 669 $.fn.naturalHeight = function() { return this[0].naturalHeight; }; 670 return; 671 } 672 673 $.fn.naturalWidth = function() { return img(this.src).width; }; 674 $.fn.naturalHeight = function() { return img(this.src).height; }; 675 }); 676 677 $(function(){ 678 $.popupviewer().init(); 679 }); 680 681})(jQuery); 682 683 684/* Loading the content for locally exported content */ 685(function($){ 686 $(window).bind("message", function(event){ 687 688 var data = event.data || event.originalEvent.data; 689 var source = event.source || event.originalEvent.source; 690 if (data != "getFrameContent") { 691 return; 692 } 693 694 try { 695 source.postMessage({ 696 message : "frameContent", 697 body : jQuery('html').html() 698 }, "*"); 699 } catch (e) { 700 alert("Fatal Exception! Could not load page via popupviewer.\n" + e); 701 } 702 }); 703})(jQuery); 704