xref: /plugin/tagging/script/search.js (revision 739c5360a53694221b0d57b59b7d9568e4700d3f)
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 {string[]}
119     */
120    function getFiltersFromQuery() {
121        const parts = getQueryElement().val().split(' ');
122        let filters = parts.filter(function (part) {
123            return part.charAt(0) === '#';
124        });
125
126        return filters.map(function (tag) {
127            return tag.replace('#', '');
128        });
129    }
130
131    /**
132     * Called when a tag filter is updated. Manipulates query by adding or removing the selected tag.
133     *
134     * @param {string} tag
135     */
136    function toggleTag(tag) {
137        tag = '#' + tag;
138        const $q = getQueryElement();
139        const q = $q.val();
140        const isFilter = q.indexOf(tag) > -1;
141
142        if (isFilter) {
143            $q.val(q.replace(tag, ''));
144        } else {
145            $q.val(q.trim() + ' ' + tag);
146        }
147    }
148
149    /**
150     * Restore tags in search links
151     *
152     * @param {jQuery} $searchLinks
153     */
154    function addTagsToSearchLinks($searchLinks) {
155        const tags = getFiltersFromQuery();
156        if (!tags) {
157            return;
158        }
159
160        $searchLinks.each(function () {
161            $link = jQuery(this);
162            const qRegex = /q=[^&]+/;
163            const qParam = $link[0]['href'].match(qRegex)[0];
164            $link[0]['href'] = $link[0]['href'].replace(qParam, qParam + encodeURIComponent(' #' + tags.join(' #')));
165        });
166    }
167
168    // tag filter
169    $ul = buildFilter(getTagsFromResults(), getFiltersFromQuery());
170    $inputs = $ul.find('input');
171    $inputs.change(function () {
172        toggleTag(this.value);
173    });
174    $filterContainer.append($ul);
175
176    // tags in other search filters
177    addTagsToSearchLinks(jQuery('.advancedOptions a'));
178
179});
180