xref: /plugin/tagging/script/search.js (revision 3a3425672d59d88cab35ebd1d7456590acd9ade7)
1jQuery(function () {
2    /**
3     * Add tag search parameter to all links in the advanced search tools
4     *
5     * This duplicates the solution from the watchcycle plugin, and should also be replaced
6     * with a DokuWiki event, which does not exist yet, but should handle extending search tools.
7     */
8    const $advancedOptions = jQuery('.search-results-form .advancedOptions');
9    if (!$advancedOptions.length) {
10        return;
11    }
12
13    /**
14     * Extracts the value of a given parameter from the URL querystring
15     *
16     * taken via watchcycle from https://stackoverflow.com/a/31412050/3293343
17     * @param param
18     * @returns {*}
19     */
20    function getQueryParam(param) {
21        location.search.substr(1)
22            .split("&")
23            .some(function(item) { // returns first occurence and stops
24                return item.split("=")[0] === param && (param = item.split("=")[1])
25            });
26        return param
27    }
28
29    if (getQueryParam('tagging-logic') === 'and') {
30        $advancedOptions.find('a').each(function (index, element) {
31            const $link = jQuery(element);
32            // do not override parameters in our own links
33            if ($link.attr('href').indexOf('tagging-logic') === -1) {
34                $link.attr('href', $link.attr('href') + '&tagging-logic=and');
35            }
36        });
37    }
38
39
40    /* **************************************************************************
41     * Search filter
42     * ************************************************************************ */
43
44    const $filterContainer = jQuery('#plugin__tagging-tags');
45    const $resultLinks = jQuery('div.search_fullpage_result dt a:not([class])');
46
47    /**
48     * Returns the filter ul
49     *
50     * @param {*} tags
51     * @param {string[]} filters
52     * @returns {jQuery}
53     */
54    function buildFilter(tags, filters) {
55        const lis = [];
56        let i = 0;
57
58        // when tag search has no results, build the filter dropdown anyway but from tags in query
59        if (Object.keys(tags).length === 0 && filters.length > 0) {
60            for (const key of filters) {
61                tags[key] = 0;
62            }
63        }
64
65        for (const tag in tags) {
66            let checked = filters.includes(tag) ? 'checked="checked"' : '';
67
68            lis.push(` <li>
69                <input name="tagging[]" type="checkbox" value="${tag}" id="__tagging-${i}" ${checked}>
70                <label for="__tagging-${i}" title="${tag}">
71                    ${tag} (${tags[tag]})
72                </label>
73            </li>`);
74            i++;
75        }
76
77        $filterContainer.find('div.current').addClass('changed');
78
79        return jQuery('<ul aria-expanded="false">' + lis.join('') + '</ul>');
80    }
81
82    /**
83     * Collects tags from results list
84     *
85     * @returns {*}
86     */
87    function getTagsFromResults() {
88        const tags = [];
89        $resultLinks.toArray().forEach(function(link) {
90            const text = jQuery(link).text();
91            if (text.charAt(0) === '#') {
92                const tag = text.replace('#', '');
93                tags.push(tag);
94            }
95        });
96
97        return tags.sort().reduce(function (allTags, tag) {
98            if (tag in allTags) {
99                allTags[tag]++;
100            }
101            else {
102                allTags[tag] = 1;
103            }
104            return allTags;
105        }, {});
106    }
107
108    /**
109     * Returns query from the main search form, ignoring quicksearch.
110     *
111     * @returns {jQuery}
112     */
113    function getQueryElement() {
114        return jQuery('#dokuwiki__content input[name="q"]');
115    }
116
117    /**
118     * Returns an array of all tags found in search form input
119     *
120     * @returns {string[]}
121     */
122    function getFiltersFromQuery() {
123        const parts = getQueryElement().val().split(' ');
124        let filters = parts.filter(function (part) {
125            return part.charAt(0) === '#';
126        });
127
128        return filters.map(function (tag) {
129            return tag.replace('#', '');
130        });
131    }
132
133    /**
134     * Called when a tag filter is updated. Manipulates query by adding or removing the selected tag.
135     *
136     * @param {string} tag
137     */
138    function toggleTag(tag) {
139        tag = '#' + tag;
140        const $q = getQueryElement();
141        const q = $q.val();
142        const isFilter = q.indexOf(tag) > -1;
143
144        if (isFilter) {
145            $q.val(q.replace(tag, ''));
146        } else {
147            $q.val(q.trim() + ' ' + tag);
148        }
149    }
150
151    /**
152     * Restore tags in search links
153     *
154     * @param {jQuery} $searchLinks
155     */
156    function addTagsToSearchLinks($searchLinks) {
157        const tags = getFiltersFromQuery();
158        if (tags.length === 0) {
159            return;
160        }
161
162        $searchLinks.each(function () {
163            $link = jQuery(this);
164            const qParam = $link[0]['href'].match(/q=[^&]*/)[0];
165            $link[0]['href'] = $link[0]['href'].replace(qParam, qParam + encodeURIComponent(' #' + tags.join(' #')));
166        });
167    }
168
169    // tag filter
170    $ul = buildFilter(getTagsFromResults(), getFiltersFromQuery());
171    $inputs = $ul.find('input');
172    $inputs.change(function () {
173        toggleTag(this.value);
174    });
175    $filterContainer.append($ul);
176
177    // tags in other search filters
178    addTagsToSearchLinks(jQuery('.advancedOptions a'));
179
180});
181