1/**
2 * The Indexmenu Wizard
3 *
4 * @author Gerrit Uitslag
5 * based on Linkwiz by
6 * @author Andreas Gohr <gohr@cosmocode.de>
7 * @author Pierre Spring <pierre.spring@caillou.ch>
8 * and the concepts of the old indexmenu wizard
9 */
10const indexmenu_wiz = {
11    $wiz:     null,
12    timer:    null,
13    textArea: null,
14
15    defaulttheme: 'default',
16    fields:       {
17        div1: {
18            elems: {
19                jstoggle: {label: 'js'}
20            }
21        },
22        div2: {
23            tlbclass: 'jsitem theme',
24            elems:    {
25                el1: {headerid: 'theme'}
26            }
27        },
28        div3: {
29            elems: {
30                el2:      {headerid: 'navigation'},
31                navbar:   {},
32                context:  {},
33                nocookie: {tlbclass: 'jsitem'},
34                noscroll: {tlbclass: 'jsitem'},
35                notoc:    {tlbclass: 'jsitem'}
36            }
37        },
38        div4: {
39            elems: {
40                el3:   {headerid: 'sort'},
41                tsort: {},
42                dsort: {},
43                msort: {},
44                hsort: {},
45                rsort: {},
46                nsort: {}
47            }
48        },
49        div5: {
50            elems: {
51                el4:  {headerid: 'filter'},
52                nons: {},
53                nopg: {}
54            }
55        },
56        div6: {
57            tlbclass: 'jsitem',
58            elems:    {
59                el5:   {headerid: 'performance'},
60                max:   {tlbclass: 'jsitem', numberinput: ['maxn', 'maxm']},
61                maxjs: {tlbclass: 'jsitem', numberinput: ['maxjsn']},
62                id:    {tlbclass: 'jsitem', numberinput: ['idn']}
63            }
64        }
65    },
66
67    /**
68     * Initialize the indexmenu_wiz by creating the needed HTML
69     * and attaching the eventhandlers
70     */
71    init: function ($editor) {
72        // position relative to the text area
73        const pos = $editor.position();
74
75        // create HTML Structure
76        indexmenu_wiz.$wiz = jQuery(document.createElement('div'))
77            .dialog({
78                autoOpen:  false,
79                draggable: true,
80                title:     LANG.plugins.indexmenu.indexmenuwizard,
81                resizable: false
82            })
83            .html(
84                '<fieldset class="indexmenu_index"><legend>' + LANG.plugins.indexmenu.index + '</legend>' +
85                '<div><label>' + LANG.plugins.indexmenu.namespace + '<input id="namespace" type="text"></label></div>' +
86                '<div><label class="number">' + LANG.plugins.indexmenu.nsdepth + ' #<input id="nsdepth" type="text" value=1></label></div>' +
87                '</fieldset>' +
88
89                '<fieldset class="indexmenu_options"><legend>' + LANG.plugins.indexmenu.options + '</legend>' +
90                '</fieldset>' +
91                '<input type="submit" value="' + LANG.plugins.indexmenu.insert + '" class="button" id="indexmenu__insert">' +
92
93                '<fieldset class="indexmenu_metanumber">' +
94                '<label class="number">' + LANG.plugins.indexmenu.metanum + '<input type="text" id="metanumber"></label>' +
95                '<input type="submit" value="' + LANG.plugins.indexmenu.insertmetanum + '" class="button" id="indexmenu__insertmetanum">' +
96                '</fieldset>'
97            )
98            .parent()
99            .attr('id', 'indexmenu__wiz')
100            .css({
101                'position': 'absolute',
102                'top':      (pos.top + 20) + 'px',
103                'left':     (pos.left + 80) + 'px'
104            })
105            .hide()
106            .appendTo('.dokuwiki:first');
107
108        indexmenu_wiz.textArea = $editor[0];
109        let $opt_fieldset = jQuery('#indexmenu__wiz fieldset.indexmenu_options');
110
111        jQuery.each(indexmenu_wiz.fields, function (i, section) {
112            let div = jQuery('<div>').addClass(section.tlbclass);
113
114            jQuery.each(section.elems, function (elid, props) {
115                if (props.headerid) {
116                    div.append('<strong>' + LANG.plugins.indexmenu[props.headerid] + '</strong><br />');
117                } else {
118                    let label = props.label || elid;
119                    //checkbox
120                    jQuery("<label>")
121                        .addClass(props.tlbclass).addClass(props.numberinput ? ' hasnumber' : '')
122                        .html('<input id="' + elid + '" type="checkbox">' + label)
123                        .attr({title: LANG.plugins.indexmenu[elid]})
124                        .appendTo(div);
125
126                    //number inputs
127                    if (props.numberinput) {
128                        jQuery.each(props.numberinput, function (j, numid) {
129                            jQuery("<label>")
130                                .attr({title: LANG.plugins.indexmenu[elid]})
131                                .addClass("number " + props.tlbclass)
132                                .html('#<input type="text" id="' + numid + '">')
133                                .appendTo(div);
134                        });
135                    }
136                }
137            });
138            $opt_fieldset.append(div);
139        });
140
141        indexmenu_wiz.includeThemes();
142
143        if (JSINFO && JSINFO.namespace) {
144            jQuery('#namespace').val(':' + JSINFO.namespace);
145        }
146
147        // attach event handlers
148
149        //toggle js fields
150        jQuery('#jstoggle')
151            .on('change', function () {
152                jQuery('#indexmenu__wiz .jsitem').toggle(this.checked);
153            }).trigger('change')
154            .parent().css({display: 'inline-block', width: '40px'}); //enlarge clickable area of label
155
156        //interactive number fields
157        jQuery('label.number input').on('keydown keyup', function () {
158            //allow only numbers
159            indexmenu_wiz.filterNumberinput(this);
160            //checked the option if a number in input
161            indexmenu_wiz.autoCheckboxForNumbers(this);
162        });
163
164        jQuery('#indexmenu__insert').on('click', indexmenu_wiz.insertIndexmenu);
165        jQuery('#indexmenu__insertmetanum').on('click', indexmenu_wiz.insertMetaNumber);
166
167        jQuery('#indexmenu__wiz').find('.ui-dialog-titlebar-close').on('click', indexmenu_wiz.hide);
168    },
169
170    /**
171     * Request and include themes in wizard
172     */
173    includeThemes: function () {
174
175        let addButtons = function (data) {
176            jQuery('<div>')
177                .attr('id', 'themebar')
178                .addClass('toolbar')
179                .appendTo('div.theme');
180
181            jQuery.each(data.themes, function (i, theme) {
182                let themeName = theme.split('.');
183
184                let icoUrl = DOKU_BASE + data.themebase + '/' + theme + '/base.' + IndexmenuUtils.determineExtension(theme);
185                let $ico = jQuery('<div>')
186                    .css({background: 'url(' + icoUrl + ') no-repeat center'});
187                jQuery('<button>')
188                    .addClass('themebutton toolbutton')
189                    .attr('id', theme)
190                    .attr('title', themeName[0])
191                    .append($ico)
192                    .on('click', indexmenu_wiz.selectTheme)
193                    .appendTo('div#themebar');
194            });
195
196            //select default theme
197            jQuery('#themebar button#' + indexmenu_wiz.defaulttheme).trigger('click');
198        };
199
200        jQuery.post(
201            DOKU_BASE + 'lib/exe/ajax.php',
202            {call: 'indexmenu', req: 'local'},
203            addButtons,
204            'json'
205        );
206    },
207
208    /**
209     * set class 'selected' to clicked theme, remove from other
210     */
211    selectTheme: function () {
212        jQuery('.themebutton').toggleClass('selected', false);
213        jQuery(this).toggleClass('selected', true);
214    },
215
216    /**
217     * Allow only number, by direct removing other characters from input
218     */
219    filterNumberinput: function (elem) {
220        if (elem.value.match(/\D/)) {
221            elem.value = this.value.replace(/\D/g, '');
222        }
223    },
224
225    /**
226     * When a number larger than zero is inputted, check the checkbox
227     */
228    autoCheckboxForNumbers: function (elem) {
229        let checkboxid = elem.id.substring(0, elem.id.length - 1);
230        let value = elem.value;
231        //exception for second number field of max: only uncheck when first field is also empty
232        if (elem.id === 'maxm' && !(elem.value > 0)) {
233            value = parseInt(jQuery('input#maxn').val());
234        }
235        jQuery('input#' + checkboxid).prop('checked', value > 0);
236    },
237
238    /**
239     * Insert the indexmenu with options to the textarea,
240     * replacing the current selection or at the cursor position.
241     */
242    insertIndexmenu: function () {
243        let options = '';
244        jQuery('fieldset.indexmenu_options input').each(function (i, input) {
245            let $label = jQuery(this).parent();
246
247            if (input.checked && (!$label.hasClass('jsitem') || jQuery('input#jstoggle').is(':checked'))) {
248                if (input.id === 'jstoggle') {
249                    //add js options
250                    options += ' js';
251                    //add theme
252                    let themename = jQuery('#themebar button.selected').attr('id');
253                    if (indexmenu_wiz.defaulttheme !== themename) { //skip default theme
254                        options += '#' + themename;
255                    }
256                } else {
257                    //add option
258                    options += ' ' + input.id;
259                    //add numbers
260                    if ($label.hasClass('hasnumber')) {
261                        jQuery.each(indexmenu_wiz.fields.div6.elems[input.id].numberinput, function (j, numid) {
262                            let num = parseInt(jQuery('input#' + numid).val());
263                            options += num ? '#' + num : '';
264                        });
265                    }
266                }
267            }
268
269        });
270        options = options ? '|' + options.trim() : '';
271
272        let sel, ns, depth, syntax, eo;
273
274        // XXX: Compatibility Fix for 2014-05-05 "Ponder Stibbons", splitbrain/dokuwiki#505
275        if (DWgetSelection) {
276            sel = DWgetSelection(indexmenu_wiz.textArea);
277        } else {
278            sel = getSelection(indexmenu_wiz.textArea);
279        }
280
281
282        ns = jQuery('#namespace').val();
283        depth = parseInt(jQuery('#nsdepth').val());
284        depth = depth ? '#' + depth : '';
285
286        syntax = '{{indexmenu>' + ns + depth + options + '}}';
287        eo = depth.length + options.length + 2;
288
289        pasteText(sel, syntax, {startofs: 12, endofs: eo});
290        indexmenu_wiz.hide();
291    },
292
293    /**
294     * Insert meta number for sorting in textarea
295     * Takes number from input, otherwise tries the selection in textarea
296     */
297    insertMetaNumber: function () {
298        let sel, selnum, syntax, number;
299
300        // XXX: Compatibility Fix for 2014-05-05 "Ponder Stibbons", splitbrain/dokuwiki#505
301        if (DWgetSelection) {
302            sel = DWgetSelection(indexmenu_wiz.textArea);
303        } else {
304            sel = getSelection(indexmenu_wiz.textArea);
305        }
306
307        selnum = parseInt(sel.getText());
308        number = parseInt(jQuery('input#metanumber').val());
309        number = number || selnum || 1;
310        syntax = '{{indexmenu_n>' + number + '}}';
311
312        pasteText(sel, syntax, {startofs: 14, endofs: 2});
313        indexmenu_wiz.hide();
314    },
315
316    /**
317     * Show the indexmenu wizard
318     */
319    show: function () {
320        // XXX: Compatibility Fix for 2014-05-05 "Ponder Stibbons", splitbrain/dokuwiki#505
321        if (DWgetSelection) {
322            indexmenu_wiz.selection = DWgetSelection(indexmenu_wiz.textArea);
323        } else {
324            indexmenu_wiz.selection = getSelection(indexmenu_wiz.textArea);
325        }
326
327        indexmenu_wiz.$wiz.show();
328        jQuery('#namespace').trigger('focus');
329    },
330
331    /**
332     * Hide the indexmenu wizard
333     */
334    hide: function () {
335        indexmenu_wiz.$wiz.hide();
336        indexmenu_wiz.textArea.focus(); //pure js
337    },
338
339    /**
340     * Toggle the indexmenu wizard
341     */
342    toggle: function () {
343        if (indexmenu_wiz.$wiz.css('display') === 'none') {
344            indexmenu_wiz.show();
345        } else {
346            indexmenu_wiz.hide();
347        }
348    }
349};
350