1/**
2 * EditSections2 Plugin for DokuWiki / script.js
3 *
4 * Replaces section edit highlighting events
5 *
6 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author  Kazutaka Miyasaka <kazmiya@gmail.com>
8 */
9
10(function() {
11    /**
12     * Lookup table for section edit id (startPos => secedit_id)
13     */
14    var editIdLookup = {};
15
16    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
17
18    // compatibility check
19    if (
20        typeof DEPRECATED === 'function' ||
21        typeof addInitEvent === 'undefined'
22    ) {
23        // for DokuWiki Angua or later
24        jQuery(replaceSectionEditButtonEvents);
25    } else if (typeof JSINFO === 'object') {
26        if (JSINFO.plugin_editsections2) {
27            // for DokuWiki Anteater and Rincewind
28            addInitEvent(replaceSectionEditButtonEvents_Anteater);
29        } else {
30            // for DokuWiki Lemming
31            addInitEvent(replaceSectionEditButtonEvents_Lemming);
32        }
33    }
34
35    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
36
37    /**
38     * Replaces mouseover events on section edit buttons
39     */
40    function replaceSectionEditButtonEvents() {
41        jQuery('form.btn_secedit')
42            .each(function() {
43                addEditIdLookupTable(this);
44            })
45            .unbind('mouseover')
46            .bind('mouseover', function(event) {
47                highlightSections(event);
48            })
49            // FIXME: a huge change has happened... will make a real fix later
50            // https://github.com/splitbrain/dokuwiki/commit/870c8a4b77dd7c2cfdc14045f8604b5bbf34c01e
51            .unbind('mouseout')
52            .bind('mouseout', function(event) {
53                jQuery('.section_highlight').removeClass('section_highlight');
54            });
55    }
56
57    /**
58     * Returns start/end value of the section edit range
59     */
60    function getRangeValue(range, startOrEnd) {
61        var matched,
62            ret;
63
64        if (!range || !(matched = /^(\d+)-(\d*)$/.exec(range.value))) {
65            ret = false;
66        } else if (startOrEnd === 'start') {
67            ret = Number(matched[1]);
68        } else if (matched[2].length) {
69            ret = Number(matched[2]);
70        } else {
71            ret = 'last';
72        }
73
74        return ret;
75    }
76
77    /**
78     * Scans and adds section edit id lookup table
79     */
80    function addEditIdLookupTable(sectionEditForm) {
81        var parent,
82            idMatched,
83            startPos;
84
85        parent = sectionEditForm.parentNode;
86
87        if (
88            parent &&
89            parent.tagName &&
90            parent.tagName.toLowerCase() === 'div' &&
91            parent.className &&
92            (idMatched = /\b(?:editbutton_(\d+))\b/.exec(parent.className)) &&
93            (startPos = getRangeValue(sectionEditForm.range, 'start'))
94        ) {
95            editIdLookup[startPos] = idMatched[1];
96        }
97    }
98
99    /**
100     * Checks if an element is heading
101     */
102    function isHeading(element) {
103        return element.tagName && /^H[1-6]/i.test(element.tagName);
104    }
105
106    /**
107     * Highlights sections in the edit range
108     */
109    function highlightSections(event) {
110        var sectionEditForm,
111            endPos,
112            stopClassRegExp,
113            doNotHighlightHeadings,
114            cursor;
115
116        sectionEditForm = event.target.form;
117
118        if (!sectionEditForm) {
119            return;
120        }
121
122        endPos = getRangeValue(sectionEditForm.range, 'end');
123
124        // set stopClass regexp
125        if (endPos === false) {
126            return;
127        } else if (endPos === 'last') {
128            stopClassRegExp = /\b(?:footnotes)\b/;
129        } else if (editIdLookup[endPos + 1]) {
130            stopClassRegExp = new RegExp(
131                '\\b(?:footnotes|sectionedit' +
132                String(editIdLookup[endPos + 1]).replace(/(\W)/g, '\\$1') +
133                ')\\b'
134            );
135        } else {
136            // edittable plugin etc.
137            return;
138        }
139
140        doNotHighlightHeadings =
141            JSINFO.plugin_editsections2.highlight_target === 'exclude_headings';
142
143        cursor = sectionEditForm.parentNode;
144
145        // highlight until the stopClass appeared
146        while (cursor = cursor.nextSibling) {
147            if (!cursor.className) {
148                continue;
149            }
150
151            if (stopClassRegExp.test(cursor.className)) {
152                break;
153            }
154
155            if (
156                !(doNotHighlightHeadings && isHeading(cursor)) &&
157                !/\b(?:editbutton_section)\b/.test(cursor.className)
158            ) {
159                cursor.className += ' section_highlight';
160            }
161        }
162    }
163
164    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
165
166    /**
167     * Replaces mouseover events on section edit buttons
168     * (for DokuWiki Anteater and Rincewind)
169     */
170    function replaceSectionEditButtonEvents_Anteater() {
171        var i,
172            iMax,
173            parent,
174            events,
175            guid,
176            buttonForms,
177            sectionEditForms = [];
178
179        buttonForms = getElementsByClass('btn_secedit', document, 'form');
180
181        // extract section edit forms
182        for (i = 0, iMax = buttonForms.length; i < iMax; i++) {
183            parent = buttonForms[i].parentNode;
184
185            // parent element must be 'div.editbutton_section'
186            if (
187                parent &&
188                parent.tagName &&
189                parent.tagName.toLowerCase() === 'div' &&
190                parent.className &&
191                /\b(?:editbutton_section)\b/.test(parent.className)
192            ) {
193                sectionEditForms[sectionEditForms.length] = buttonForms[i];
194            }
195        }
196
197        // remove events and collect section info
198        for (i = 0, iMax = sectionEditForms.length; i < iMax; i++) {
199            events = sectionEditForms[i].events;
200
201            // remove all of the previously-set mouseover events from the button
202            if (events && events.mouseover) {
203                for (guid in events.mouseover) {
204                    removeEvent(
205                        sectionEditForms[i], 'mouseover', events.mouseover[guid]
206                    );
207                }
208            }
209
210            addEditIdLookupTable(sectionEditForms[i]);
211        }
212
213        // add new event to highlight sections to be edited
214        for (i = 0, iMax = sectionEditForms.length; i < iMax; i++) {
215            addEvent(
216                sectionEditForms[i],
217                'mouseover',
218                highlightSections
219            );
220        }
221    }
222
223    /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
224
225    /**
226     * Replaces mouseover events on section edit buttons
227     * (for DokuWiki Lemming)
228     */
229    function replaceSectionEditButtonEvents_Lemming() {
230        var i,
231            iMax,
232            events,
233            guid,
234            buttonForms;
235
236        buttonForms = getElementsByClass('btn_secedit', document, 'form');
237
238        for (i = 0, iMax = buttonForms.length; i < iMax; i++) {
239            events = buttonForms[i].events;
240
241            // remove all of the previously-set mouseover events from the button
242            if (events && events.mouseover) {
243                for (guid in events.mouseover) {
244                    removeEvent(
245                        buttonForms[i], 'mouseover', events.mouseover[guid]
246                    );
247                }
248            }
249
250            // add new mouseover event to highlight sections to be edited
251            addEvent(buttonForms[i], 'mouseover', highlightSections_Lemming);
252        }
253    }
254
255    /**
256     * Highlights sections in the edit range
257     * (for DokuWiki Lemming or earlier)
258     */
259    function highlightSections_Lemming(event) {
260        var buttonForm,
261            sectionEditForm,
262            cursor,
263            startPos,
264            endPos;
265
266        buttonForm = event.target.form;
267
268        // get the end position of the section
269        endPos = getRangeValue(buttonForm.lines, 'end');
270
271        if (endPos === false) {
272            return;
273        }
274
275        cursor = buttonForm.parentNode;
276
277        if (!cursor.tagName || cursor.tagName.toLowerCase() !== 'div') {
278            return;
279        }
280
281        // add "section_highlight" class to DIV elements in the edit range
282        while (cursor = cursor.nextSibling) {
283            if (
284                !cursor.tagName ||
285                cursor.tagName.toLowerCase() !== 'div' ||
286                !cursor.className
287            ) {
288                continue;
289            }
290
291            if (
292                /\b(?:secedit)\b/.test(cursor.className) &&
293                endPos !== 'last' &&
294                (sectionEditForm = cursor.getElementsByTagName('form').item(0)) &&
295                (startPos = getRangeValue(sectionEditForm.lines, 'start')) !== false &&
296                endPos < startPos
297            ) {
298                // out of the edit range
299                break;
300            }
301
302            if (/\b(?:level\d)\b/.test(cursor.className)) {
303                cursor.className += ' section_highlight';
304            }
305        }
306    }
307})();
308