1/**
2 * Page behaviours
3 *
4 * This class adds various behaviours to the rendered page
5 */
6dw_page = {
7    /**
8     * initialize page behaviours
9     */
10    init: function(){
11        dw_page.sectionHighlight();
12        dw_page.currentIDHighlight();
13        jQuery('a.fn_top').on('mouseover', dw_page.footnoteDisplay);
14        dw_page.makeToggle('#dw__toc h3','#dw__toc > div');
15    },
16
17    /**
18     * Highlight the section when hovering over the appropriate section edit button
19     *
20     * @author Andreas Gohr <andi@splitbrain.org>
21     */
22    sectionHighlight: function() {
23        jQuery('form.btn_secedit')
24            .on('mouseover', function(){
25                var $tgt = jQuery(this).parent(),
26                    nr = $tgt.attr('class').match(/(\s+|^)editbutton_(\d+)(\s+|$)/)[2],
27                    $highlight = jQuery(),                                             // holder for elements in the section to be highlighted
28                    $highlightWrap = jQuery('<div class="section_highlight"></div>');  // section highlight wrapper
29
30                // Walk the dom tree in reverse to find the sibling which is or contains the section edit marker
31                while($tgt.length > 0 && !($tgt.hasClass('sectionedit' + nr) || $tgt.find('.sectionedit' + nr).length)) {
32                    $tgt = $tgt.prev();
33                    $highlight = $highlight.add($tgt);
34                }
35              // insert the section highlight wrapper before the last element added to $highlight
36              $highlight.filter(':last').before($highlightWrap);
37              // and move the elements to be highlighted inside the section highlight wrapper
38              $highlight.detach().appendTo($highlightWrap);
39            })
40            .on('mouseout', function(){
41                // find the section highlight wrapper...
42                var $highlightWrap = jQuery('.section_highlight');
43                // ...move its children in front of it (as siblings)...
44                $highlightWrap.before($highlightWrap.children().detach());
45                // ...and remove the section highlight wrapper
46                $highlightWrap.detach();
47            });
48    },
49
50
51    /**
52     * Highlight internal link pointing to current page
53     *
54     * @author Henry Pan <dokuwiki@phy25.com>
55     */
56    currentIDHighlight: function(){
57        jQuery('a.wikilink1, a.wikilink2').filter('[data-wiki-id="'+JSINFO.id+'"]').wrap('<span class="curid"></div>');
58    },
59
60    /**
61     * Create/get a insitu popup used by the footnotes
62     *
63     * @param target - the DOM element at which the popup should be aligned at
64     * @param popup_id - the ID of the (new) DOM popup
65     * @return the Popup jQuery object
66     */
67    insituPopup: function(target, popup_id) {
68        // get or create the popup div
69        var $fndiv = jQuery('#' + popup_id);
70
71        // popup doesn't exist, yet -> create it
72        if($fndiv.length === 0){
73            $fndiv = jQuery(document.createElement('div'))
74                .attr('id', popup_id)
75                .addClass('insitu-footnote JSpopup')
76                .attr('aria-hidden', 'true')
77                .on('mouseleave', function () {jQuery(this).hide().attr('aria-hidden', 'true');})
78                .attr('role', 'tooltip');
79            jQuery('.dokuwiki:first').append($fndiv);
80        }
81
82        // position() does not support hidden elements
83        $fndiv.show().position({
84            my: 'left top',
85            at: 'left center',
86            of: target
87        }).hide();
88
89        return $fndiv;
90    },
91
92    /**
93     * Display an insitu footnote popup
94     *
95     * @author Andreas Gohr <andi@splitbrain.org>
96     * @author Chris Smith <chris@jalakai.co.uk>
97     * @author Anika Henke <anika@selfthinker.org>
98     */
99    footnoteDisplay: function () {
100        var $content = jQuery(jQuery(this).attr('href')) // Footnote text anchor
101                      .parent().siblings('.content').clone();
102
103        if (!$content.length) {
104            return;
105        }
106
107        // prefix ids on any elements with "insitu__" to ensure they remain unique
108        jQuery('[id]', $content).each(function(){
109            var id = jQuery(this).attr('id');
110            jQuery(this).attr('id', 'insitu__' + id);
111        });
112
113        var content = $content.html().trim();
114        // now put the content into the wrapper
115        dw_page.insituPopup(this, 'insitu__fn').html(content)
116        .show().attr('aria-hidden', 'false');
117    },
118
119    /**
120     * Makes an element foldable by clicking its handle
121     *
122     * This is used for the TOC toggling, but can be used for other elements
123     * as well. A state indicator is inserted into the handle and can be styled
124     * by CSS.
125     *
126     * To properly reserve space for the expanded element, the sliding animation is
127     * done on the children of the content. To make that look good and to make sure aria
128     * attributes are assigned correctly, it's recommended to make sure that the content
129     * element contains a single child element only.
130     *
131     * @param {selector} handle What should be clicked to toggle
132     * @param {selector} content This element will be toggled
133     * @param {int} state initial state (-1 = open, 1 = closed)
134     */
135    makeToggle: function(handle, content, state){
136        var $handle, $content, $clicky, $child, setClicky;
137        $handle = jQuery(handle);
138        if(!$handle.length) return;
139        $content = jQuery(content);
140        if(!$content.length) return;
141
142        // we animate the children
143        $child = $content.children();
144
145        // class/display toggling
146        setClicky = function(hiding){
147            if(hiding){
148                $clicky.html('<span>+</span>');
149                $handle.addClass('closed');
150                $handle.removeClass('open');
151            }else{
152                $clicky.html('<span>−</span>');
153                $handle.addClass('open');
154                $handle.removeClass('closed');
155            }
156        };
157
158        $handle[0].setState = function(state){
159            var hidden;
160            if(!state) state = 1;
161
162            // Assert that content instantly takes the whole space
163            $content.css('min-height', $content.height()).show();
164
165            // stop any running animation
166            $child.stop(true, true);
167
168            // was a state given or do we toggle?
169            if(state === -1) {
170                hidden = false;
171            } else if(state === 1) {
172                hidden = true;
173            } else {
174                hidden = $child.is(':hidden');
175            }
176
177            // update the state
178            setClicky(!hidden);
179
180            // Start animation and assure that $toc is hidden/visible
181            $child.dw_toggle(hidden, function () {
182                $content.toggle(hidden);
183                $content.attr('aria-expanded', hidden);
184                $content.css('min-height',''); // remove min-height again
185            }, true);
186        };
187
188        // the state indicator
189        $clicky = jQuery(document.createElement('strong'));
190
191        // click function
192        $handle.css('cursor','pointer')
193               .on('click', $handle[0].setState)
194               .prepend($clicky);
195
196        // initial state
197        $handle[0].setState(state);
198    }
199};
200
201jQuery(dw_page.init);
202