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