1
2/**
3 * The Tag File Selector Wizard
4 *
5 * This code is used in the toolbar entry of the doxycode plugin for quickly selecting available
6 * tag files from the tag file configuration for a code snippet.
7 *
8 * It opens a floating jQuery dialog that is inspired from the linkwiz floating dialog by Andreas Gohr and Pierre Spring
9 * (see {@link https://github.com/dokuwiki/dokuwiki/blob/master/lib/scripts/linkwiz.js}).
10 * The user can filter all available tag file entries with a search string and select all
11 * tag files to be used via checkboxes for each entry.
12 *
13 * On load the tagselector tries to find a doxygen syntax near the cursor in the editor and loads
14 * the already used tag file names into the dialog. Tag files can quickly be added by clicking on
15 * a tag file entry.
16 *
17 *
18 * @author      Lukas Probsthain <lukas.probsthain@gmail.com>
19 */
20var doxycode_tagselector = {
21
22    /**
23     * The main div that holds the tagselector for rendering
24     * @type {jQuery}
25     */
26    $container: null,
27
28    /**
29     * The search text input for filtering the available tag files
30     * @type {jQuery}
31     */
32    $search: null,
33
34    /**
35     * DIV for rendering the tag name list as a table
36     * @type {DOM/Element}
37     */
38    result: null,
39    timer: null,
40    textArea: null,
41    selected: -1,             // the element in the result that is currently marked with hotkeys
42    /** Selection of the doxycode syntax in the edit textArea @type {selection_class} */
43    doxycodeSelected: null,     // <doxycode ...> syntax that was near the cursor in the edit textArea
44    selection: null,            // the old text selection in the edit textArea
45    tagNames: [],
46
47    /**
48     * Initialize the tag file selector by creating the needed HTML
49     * and attaching the eventhandlers
50     */
51    init: function($editor){
52        // position relative to the text area
53        var pos = $editor.position();
54
55        if(doxycode_tagselector.$container) {
56            // if we already have a container ready do nothing here
57            return;
58        }
59
60        // create HTML Structure
61        doxycode_tagselector.$container = jQuery(document.createElement('div'))
62            .dialog({
63                autoOpen: false,
64                draggable: true,
65                title: LANG.plugins.doxycode.tag_selector_title,
66                resizable: false,
67                buttons: [
68                    {
69                        text: LANG.plugins.doxycode.tag_selector_btn_insert,
70                        click: function() {
71                            doxycode_tagselector.insertTagNames();
72                            doxycode_tagselector.hide();
73                        }
74                    },{
75                        text: LANG.plugins.doxycode.tag_selector_btn_update,
76                        click: function() {
77                            doxycode_tagselector.updateTagNames();
78                        }
79                    }
80                ]
81            })
82            .html(
83                '<div>'+LANG.plugins.doxycode.tag_selector_search+' <input type="text" class="edit" id="doxycode__tagselector_search" autocomplete="off" /></div>'+
84                '<div id="doxycode__tagselector_result"></div>'
85                )
86            .parent()
87            .attr('id','doxycode__tagselector')
88            .css({
89                'position':    'absolute',
90                'top':         (pos.top+20)+'px',
91                'left':        (pos.left+80)+'px'
92                })
93            .hide()
94            .appendTo('.dokuwiki:first');
95
96        doxycode_tagselector.textArea = $editor[0];
97        doxycode_tagselector.result = jQuery('#doxycode__tagselector_result')[0];
98
99        // scrollview correction on arrow up/down gets easier
100        jQuery(doxycode_tagselector.result).css('position', 'relative');
101
102        doxycode_tagselector.$search = jQuery('#doxycode__tagselector_search');
103
104        // attach event handlers
105        jQuery('#doxycode__tagselector .ui-dialog-titlebar-close').on('click', doxycode_tagselector.hide);
106        doxycode_tagselector.$search.keydown(doxycode_tagselector.onEntry);
107        jQuery(doxycode_tagselector.result).on('click', 'a', doxycode_tagselector.onResultClick);
108    },
109
110    /**
111     * handle all keyup events in the search field
112     */
113    onEntry: function(e){
114        if(e.keyCode == 37 || e.keyCode == 39){ //left/right
115            return true; //ignore
116        }
117        if(e.keyCode == 27){ //Escape
118            doxycode_tagselector.hide();
119            e.preventDefault();
120            e.stopPropagation();
121            return false;
122        }
123        if(e.keyCode == 38){ //Up
124            doxycode_tagselector.select(doxycode_tagselector.selected -1);
125            e.preventDefault();
126            e.stopPropagation();
127            return false;
128        }
129        if(e.keyCode == 40){ //Down
130            doxycode_tagselector.select(doxycode_tagselector.selected +1);
131            e.preventDefault();
132            e.stopPropagation();
133            return false;
134        }
135        if(e.keyCode == 32){ //Space
136            // Find the currently selected row based on your selection logic
137            var $selectedRow = doxycode_tagselector.$getResult(doxycode_tagselector.selected);
138
139            // Find the checkbox within that row
140            var $checkbox = $selectedRow.find('td:first-child input[type="checkbox"]');
141
142            // Toggle the checkbox state
143            $checkbox.prop('checked', !$checkbox.prop('checked'));
144
145            e.preventDefault();
146            e.stopPropagation();
147            return false;
148        }
149        if(e.keyCode == 13){ //Enter
150
151            // trigger the insertion of the tagfilelist
152            doxycode_tagselector.insertTagNames();
153
154            // close the tag selector
155            doxycode_tagselector.hide();
156
157            e.preventDefault();
158            e.stopPropagation();
159            return false;
160        }
161        doxycode_tagselector.filterRows();
162    },
163
164    /**
165     * Get the selected tag name list from the result table.
166     *
167     * @returns {Array} List of selected tag names
168     */
169    getTagList: function() {
170        // get the table body
171        var $tbody = jQuery('#doxycode__tagselector_table tbody');
172
173        /**
174         * Array to hold the tag names
175         * @type {Array}
176         */
177        var tagNames = [];
178
179        // Iterate over each row
180        $tbody.find('tr').each(function() {
181            var $row = jQuery(this);
182            var $checkbox = $row.find('td:first-child input[type="checkbox"]');
183
184            // Check if the checkbox is checked
185            if ($checkbox.is(':checked')) {
186                // Get the text from the second column and add it to the tagNames array
187                var name = $row.find('td:nth-child(2)').text();
188                tagNames.push(name);
189            }
190        });
191
192        return tagNames;
193    },
194
195    /**
196     * Get jQuery object of the entry in the result table by index
197     *
198     * @param   {num} int index of the entry in the tbody
199     * @returns {jQuery} Row matching the index
200     */
201    $getResult: function(num) {
202        return jQuery(doxycode_tagselector.$container).find('#doxycode__tagselector_table tbody tr:visible').eq(num);
203    },
204
205    /**
206     * Select the given entry
207     *
208     * @param {num} int index of the entry in the tbody
209     */
210    select: function(num){
211        if(num < 0){
212            doxycode_tagselector.deselect();
213            return;
214        }
215
216        // get the current item
217        var $obj = doxycode_tagselector.$getResult(num);
218        if ($obj.length === 0) {
219            return;
220        }
221
222        // remove class from item
223        doxycode_tagselector.deselect();
224
225        $obj.addClass('selected');
226
227        // make sure the item is viewable in the scroll view
228
229        //getting child position within the parent
230        var childPos = $obj.position().top;
231        //getting difference between the childs top and parents viewable area
232        var yDiff = childPos + $obj.outerHeight() - jQuery(doxycode_tagselector.result).innerHeight();
233
234        if (childPos < 0) {
235            //if childPos is above viewable area (that's why it goes negative)
236            jQuery(doxycode_tagselector.result)[0].scrollTop += childPos;
237        } else if(yDiff > 0) {
238            // if difference between childs top and parents viewable area is
239            // greater than the height of a childDiv
240            jQuery(doxycode_tagselector.result)[0].scrollTop += yDiff;
241        }
242
243        doxycode_tagselector.selected = num;
244    },
245
246    /**
247     * Deselect the entry in the result table
248     */
249    deselect: function(){
250        if(doxycode_tagselector.selected > -1){
251            doxycode_tagselector.$getResult(doxycode_tagselector.selected).removeClass('selected');
252        }
253        doxycode_tagselector.selected = -1;
254    },
255
256    /**
257     * Handle clicks in the result set an dispatch them to resultClick()
258     *
259     * @param {Event} e
260     */
261    onResultClick: function(e){
262        if(!jQuery(this).is('a')) {
263            return;
264        }
265
266        e.stopPropagation();
267        e.preventDefault();
268
269        doxycode_tagselector.resultClick(this);
270        return false;
271    },
272
273    /**
274     * Handles the "click" on a given result anchor
275     *
276     * Enable the clicked tag name and insert new syntax into edit textArea
277     *
278     * @param {DOM/Element} a The link element this event was triggered for.
279     */
280    resultClick: function(a){
281        // enable the checkbox of this item
282        var $row = jQuery(a).closest('tr');
283        var $checkbox = $row.find('td:first-child input[type="checkbox"]');
284        $checkbox.prop('checked', true);
285
286        // trigger the insertion of the tagfilelist
287        doxycode_tagselector.insertTagNames();
288
289        // close the tag selector
290        doxycode_tagselector.hide();
291    },
292
293    /**
294     * Start the timer for filtering the tag file list by the search string.
295     *
296     * If a timer was already running we restart the timer.
297     */
298    filterRows: function() {
299        if(doxycode_tagselector.timer !== null){
300            window.clearTimeout(doxycode_tagselector.timer);
301            doxycode_tagselector.timer = null;
302        }
303
304        doxycode_tagselector.timer = window.setTimeout(doxycode_tagselector.filterRowsExec,350);
305    },
306
307    /**
308     * Filter the tag file list by the search string.
309     */
310    filterRowsExec: function(){
311        // Convert search text to lower case for case-insensitive comparison
312        var searchText = jQuery(doxycode_tagselector.$search).val().toLowerCase();
313
314        var $tbody = jQuery(doxycode_tagselector.result).find('#doxycode__tagselector_table tbody');
315
316        var $selectedRow = null;
317
318        // get currently selected row so we can update the selected index
319        if(doxycode_tagselector.selected >= 0) {
320            $selectedRow = doxycode_tagselector.$getResult(doxycode_tagselector.selected);
321        }
322
323        // get all rows
324        var $rows = $tbody.find('tr');
325
326        // TODO: maybe also match the description?
327
328        // show all matching rows
329        $rows.filter(function() {
330            var name = jQuery(this).find('td').eq(1).text().toLowerCase();
331            return searchText === '' || name.includes(searchText);
332        }).show();
333
334        // hide all not matching rows
335        $rows.not(function() {
336            var name = jQuery(this).find('td').eq(1).text().toLowerCase();
337            return searchText === '' || name.includes(searchText);
338        }).hide();
339
340        if($selectedRow != null) {
341            // update index of the currently selected item inside the list of visible files
342            doxycode_tagselector.selected = $tbody.find('tr:visible').index($selectedRow);
343
344            if(doxycode_tagselector.selected < 0) {
345                // if row can't be selected anymore remove selected class
346                $selectedRow.removeClass('selected');
347            }
348        }
349
350    },
351
352    /**
353     * Executes the AJAX call for loading the tag configuration from the server
354     */
355    updateTagNames: function(){
356        var $res = jQuery(doxycode_tagselector.result);
357
358        // show the loading animation
359        $loading_animation = jQuery('<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />');
360
361        $res.prepend($loading_animation);
362
363        // request the tag file configuration from the server
364        jQuery.post(
365            DOKU_BASE + 'lib/exe/ajax.php',
366            {
367                call: 'plugin_doxycode_get_tag_files'
368            },
369            function(response) {
370                // update the local tag name list
371                doxycode_tagselector.renderTagNameList(response);
372            },
373            'json'
374        ).fail(function(jqXHR, textStatus, errorThrown) {
375            console.error("AJAX error:", textStatus, errorThrown);
376        });
377    },
378
379    /**
380     * Insert or update a table with the current tag file configuration.
381     *
382     * @param {array} response Tag file configuration from the server.
383     */
384    renderTagNameList: function(response) {
385        var $res = jQuery(doxycode_tagselector.result);
386
387        // remove the loading animation
388        $res.find('img').remove();
389
390        var $table = jQuery('#doxycode__tagselector_table'); // Reference to the table
391
392        if ($table.length === 0) {
393            // If the table doesn't exist, create it
394            $table = jQuery('<table id="doxycode__tagselector_table"></table>');
395
396            var $thead = jQuery('<thead></thead>');
397            $table.append($thead);
398
399            var $row = jQuery('<tr></tr>');
400            $thead.append($row);
401
402            $row.append(jQuery('<th></th>'));
403            $row.append(jQuery('<th></th>').text(LANG.plugins.doxycode.tag_selector_name));
404            $row.append(jQuery('<th></th>').text(LANG.plugins.doxycode.tag_selector_description));
405
406            $res.append($table); // Append the table to a container
407        }
408
409        var $tbody;
410        if ($table.find('tbody').length === 0) {
411            // If tbody doesn't exist, create it
412            $tbody = jQuery('<tbody></tbody>');
413            $table.append($tbody);
414        } else {
415            $tbody = $table.find('tbody');
416        }
417
418        var existingRows = $tbody.find('tr').get(); // Get existing rows as an array
419
420        for (const [key, value] of Object.entries(response)) {
421            var $matchingRow = jQuery(existingRows).filter(function() {
422                return jQuery(this).find('td:nth-child(2)').text() === key;
423            });
424
425            if ($matchingRow.length > 0) {
426                // Row exists, update it
427                $matchingRow.find('td:nth-child(3)').text(value['description']);
428
429                // Remove the row from existingRows array as it's already processed
430                existingRows = existingRows.filter(row => row !== $matchingRow[0]);
431            } else {
432                // Row doesn't exist, create and insert it at the correct position
433                var $row = doxycode_tagselector.createRow(key, value);
434                doxycode_tagselector.insertRowInOrder($tbody, $row, Object.keys(response), key);
435            }
436        }
437
438        // Remove any remaining rows that weren't in the response
439        jQuery(existingRows).remove();
440
441        doxycode_tagselector.filterRowsExec();
442
443        // move focus back to search input
444        doxycode_tagselector.$search.focus();
445    },
446
447    /**
448     * Create a row for the table with the tag file names.
449     *
450     * The row consists of:
451     * - a checkbox for selecting the tag name for insertion
452     * - the tag name itself
453     * - a short description from the tag file configuration
454     *
455     * @param {String} key Tag File name
456     * @param {Array.<{description: String}>} value Configuration of the tag file
457     * @returns
458     */
459    createRow: function(key, value) {
460        var $row = jQuery('<tr></tr>').data('name', key);
461        var $checkbox = jQuery('<input>', { type: 'checkbox', value: 0 });
462        if (doxycode_tagselector.tagNames.includes(key)) {
463            $checkbox.prop("checked", true);
464        }
465        $row.append(jQuery('<td></td>').append($checkbox));
466        var $link = jQuery('<a>').text(key);
467        $row.append(jQuery('<td>').append($link));
468        $row.append(jQuery('<td></td>').text(value['description']));
469        return $row;
470    },
471
472    /**
473     * Insert a new row in the result table with the correct position based on the order of a tag name array
474     *
475     * @param {jQuery} $tbody Table body that displays the tag names
476     * @param {jQuery} $newRow Entry row generated with createRow()
477     * @param {Array<String>} keys Tag names that should be displayed in the given order
478     * @param {String} currentKey Tag name of the entry
479     */
480    insertRowInOrder: function($tbody, $newRow, keys, currentKey) {
481        var inserted = false;
482        $tbody.find('tr').each(function() {
483            var rowKey = jQuery(this).find('td:nth-child(2)').text();
484            var index = keys.indexOf(rowKey);
485            if (index > keys.indexOf(currentKey)) {
486                jQuery(this).before($newRow);
487                inserted = true;
488                return false; // break the .each loop
489            }
490        });
491        if (!inserted) {
492            // Append to the end if not inserted in the middle
493            $tbody.append($newRow);
494        }
495    },
496
497    /**
498     * Insert the tagfiles attribute with the list of checked tag names from the tag file selector.
499     *
500     * If a doxycode syntax was detected when the tag file selector was shown, it tries to update
501     * the existing doxycode syntax. If the doxycode syntax didn't include the tagfiles
502     * attribute, it inserts it before the filename (if there was one).
503     *
504     * Otherwise it creates a new doxycode syntax at the position
505     *
506     * @property {selection_class} doxycode_tagselector.doxycodeSelected used for replacing the existing doxycode syntax
507     * @property {selection_class} doxycode_tagselector.selection used for inserting a new doxycode syntax
508     */
509    insertTagNames: function() {
510        var tagFileNames = doxycode_tagselector.getTagList();
511
512        var tagFilesString = 'tagfiles="' + tagFileNames.join(' ') + '"';
513        var tagfilesRegex = /tagfiles=".*?"/;
514
515        if(doxycode_tagselector.doxycodeSelected == null) {
516            // we have to insert a new doxycode syntax into the editor textArea
517            // use the start position from selector
518            var doxycode_string = '<doxycode>\n</doxycode>';
519
520            // insert the string into the editor textArea
521            pasteText(doxycode_tagselector.selection,doxycode_string,{});
522
523            // update doxycode selection, so that it matches the selection of the new text
524            doxycode_tagselector.doxycodeSelected = doxycode_tagselector.selection;
525        }
526
527        // update or insert tagfiles attribute!
528
529        var doxycodeText = doxycode_tagselector.doxycodeSelected.getText();
530        var updatedDoxycode;
531
532        if (tagfilesRegex.test(doxycode_tagselector.doxycodeSelected.getText())) {
533            // Update the tagfiles attribute
534            updatedDoxycode = doxycodeText.replace(tagfilesRegex, tagFilesString);
535        } else {
536            // Add the tagfiles attribute, considering self-closing tags
537            updatedDoxycode = doxycodeText.replace(/<doxycode(.*?)(\/?>)/, function(match, attributes, closingTag) {
538                // Place tagFilesString before the filename and the closing tag
539                var filenameMatch = attributes.match(/ ([^ ]+)(\/?>)$/);
540                var filename = filenameMatch ? filenameMatch[1] : '';
541                var updatedAttributes = filename ? attributes.replace(filename, '').trim() : attributes.trim();
542                return '<doxycode ' + updatedAttributes + ' ' + tagFilesString + (filename ? ' ' + filename : '') + closingTag;
543            });
544        }
545
546        // insert the string into the editor textArea
547        pasteText(doxycode_tagselector.doxycodeSelected,updatedDoxycode,{});
548
549        return;
550
551    },
552    /**
553     * Find the nearest doxycode syntax near the cursor in the edit textArea
554     *
555     * The current selection in the edit textArea might be:
556     *
557     * - at the start or inside of a '<doxycode ...>...<\doxycode>' or '<doxycode ...\>' block:
558     *
559     *   -> just mark from '<' to '>' or '\>'
560     *
561     * - inside the code content
562     *
563     *   -> check if there is a '<doxycode ...>' block that is not closed before the start
564     *
565     *   -> then check if there is a '<\doxycode>' block after the end
566     *
567     * @property {selection_class} doxycode_tagselector.selection used for searching doxycode syntax near the original cursor position
568     * @returns {void}
569     */
570    findNearestDoxycode: function() {
571
572        doxycode_tagselector.doxycodeSelected = new selection_class();
573
574        // extract the text from the edit textArea
575        var text = doxycode_tagselector.textArea.value;
576
577        // Extract the line where the selection starts
578        var selectionStartLine = text.substring(0, doxycode_tagselector.selection.start).lastIndexOf('\n') + 1;
579        var selectionEndLine = text.indexOf('\n', doxycode_tagselector.selection.start);
580        if (selectionEndLine === -1) selectionEndLine = text.length;
581
582        var line = text.substring(selectionStartLine, selectionEndLine);
583
584        // Regex to match <doxycode ...> or <doxycode ...\>
585        var regex = /<doxycode(.*?)>/;
586
587        // detect if cursors starts at doxycode syntax
588        var match = regex.exec(line);
589        if (match) {
590            // copy over the object from the original selection
591            doxycode_tagselector.doxycodeSelected.obj = doxycode_tagselector.selection.obj;
592
593            // Update the selection to cover the entire <doxycode> tag
594            doxycode_tagselector.doxycodeSelected.start = selectionStartLine + match.index;
595            doxycode_tagselector.doxycodeSelected.end = selectionStartLine + match.index + match[0].length;
596            return;
597        }
598
599        // search for doxycode block before the current selection
600
601        // Extract the entire text before and after the selection
602        var textBeforeCursor = text.substring(0, doxycode_tagselector.selection.start);
603        var textAfterCursor = text.substring(doxycode_tagselector.selection.start);
604
605        // Regex to match the opening and closing of doxycode blocks, and self-closing tag
606        var openingTagRegex = /<doxycode(.*?)>/g;
607        var closingTagRegex = /<\/doxycode>|<doxycode.*?\/>/g;
608
609        // Find the last opening tag and first closing tag before the cursor
610        var lastOpeningTagIndex = -1, firstClosingTagIndex = -1;
611        var lastOpeningTagIndexLength = -1;
612        var match;
613
614        // Find the nearest opening tag before the cursor
615        while ((match = openingTagRegex.exec(textBeforeCursor)) !== null) {
616            lastOpeningTagIndex = match.index;
617            lastOpeningTagIndexLength = match[0].length;
618        }
619
620        // Check for closing tags before the cursor
621        while ((match = closingTagRegex.exec(textBeforeCursor)) !== null) {
622            if (match.index >= lastOpeningTagIndex) {
623                // Found a closing tag after or at the last opening tag
624                // ignore opening tag
625                lastOpeningTagIndex = -1;
626            }
627        }
628
629        // Find the nearest closing tag after the last opening tag
630        if (lastOpeningTagIndex !== -1) {
631            while ((match = closingTagRegex.exec(textAfterCursor)) !== null) {
632                firstClosingTagIndex = match.index + doxycode_tagselector.selection.start;
633                if (firstClosingTagIndex > lastOpeningTagIndex) {
634                    break;
635                }
636            }
637        }
638
639        // Determine if the cursor is inside an open doxycode block
640        if (lastOpeningTagIndex !== -1 && (firstClosingTagIndex === -1 || firstClosingTagIndex > doxycode_tagselector.selection.start)) {
641            // Cursor is inside an open doxycode block
642
643            // copy over the object from the original selection
644            doxycode_tagselector.doxycodeSelected.obj = doxycode_tagselector.selection.obj;
645
646            // Update the selection to cover the entire <doxycode> tag
647            doxycode_tagselector.doxycodeSelected.start = lastOpeningTagIndex;
648            doxycode_tagselector.doxycodeSelected.end = lastOpeningTagIndex + lastOpeningTagIndexLength;
649            return;
650        }
651
652        doxycode_tagselector.doxycodeSelected = null;
653
654        return;
655    },
656
657    /**
658     * Extract the tag file list from the selected '<doxycode tagfiles="">' syntax.
659     *
660     * @returns {void}
661     */
662    getTagNamesFromSyntax: function() {
663        // clear the current list of tagNames
664        doxycode_tagselector.tagNames = [];
665
666        if(doxycode_tagselector.doxycodeSelected == null) {
667            return;
668        }
669
670        // extract the doxycode text from the selection
671        var doxycode_syntax = doxycode_tagselector.doxycodeSelected.getText();
672
673        // get the tagfiles from it
674        var regex = /tagfiles="(.*?)"/;
675
676        var match = regex.exec(doxycode_syntax);
677        if (match) {
678            doxycode_tagselector.tagNames = match[1].split(" ");
679            return;
680        }
681    },
682
683    clearResults: function() {
684        // get the table body
685        var $tbody = jQuery('#doxycode__tagselector_table tbody');
686
687        // clear contents
688        $tbody.empty();
689    },
690
691    /**
692     * Show the tag selector
693     */
694    show: function(){
695        // prepare the update from the current selection
696        doxycode_tagselector.selection  = DWgetSelection(doxycode_tagselector.textArea);
697        // we'll scan for the current <doxycode> block containing a 'tagfiles' argument
698        doxycode_tagselector.findNearestDoxycode();
699        doxycode_tagselector.getTagNamesFromSyntax();
700
701        // show the tag selector
702        doxycode_tagselector.$container.show();
703        doxycode_tagselector.$search.focus();
704
705        // get the current list of tagnames from the server
706        doxycode_tagselector.updateTagNames();
707
708        // Move the cursor to the end of the input
709        var temp = doxycode_tagselector.$search.val();
710        doxycode_tagselector.$search.val('');
711        doxycode_tagselector.$search.val(temp);
712    },
713
714    /**
715     * Hide the tag selector
716     */
717    hide: function(){
718        doxycode_tagselector.deselect();
719
720        // clear any results from the last time
721        doxycode_tagselector.clearResults();
722
723        doxycode_tagselector.$container.hide();
724        // put the focus back to the editor
725        doxycode_tagselector.textArea.focus();
726    },
727
728    /**
729     * Toggle the tag selector
730     */
731    toggle: function(){
732        if(doxycode_tagselector.$container.css('display') == 'none'){
733            doxycode_tagselector.show();
734        }else{
735            doxycode_tagselector.hide();
736        }
737    }
738};
739