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