From b005809cbc9d5e96a687d5d0445452fb317981fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Gro=C3=9Fe?= <grosse@cosmocode.de> Date: Mon, 26 Mar 2018 18:21:23 +0200 Subject: [PATCH] feat(search): display search tools as lists on click This functionality is inspired by what other large search engines are doing. --- inc/Ui/Search.php | 319 +++++++++++++++++++++---------- lib/scripts/search.js | 6 + lib/tpl/dokuwiki/css/_search.css | 20 +- 3 files changed, 245 insertions(+), 100 deletions(-) diff --git a/inc/Ui/Search.php b/inc/Ui/Search.php index 5a01b6919..4f477f4d6 100644 --- a/inc/Ui/Search.php +++ b/inc/Ui/Search.php @@ -16,8 +16,8 @@ class Search extends Ui /** * Search constructor. * - * @param array $pageLookupResults - * @param array $fullTextResults + * @param array $pageLookupResults + * @param array $fullTextResults * @param string $highlight */ public function __construct(array $pageLookupResults, array $fullTextResults, $highlight) @@ -90,11 +90,6 @@ class Search extends Ui $searchForm->addHTML('FIXME Your query is too complex. Search assistance is unavailable. See <a href="https://doku.wiki/search">doku.wiki/search</a> for more help.'); $searchForm->addTagClose('span'); } - if ($INPUT->str('sort') === 'mtime') { - $this->searchState->addSearchLinkSort($searchForm, 'sort by hits', ''); - } else { - $this->searchState->addSearchLinkSort($searchForm, 'sort by mtime', 'mtime'); - } $searchForm->addFieldsetClose(); @@ -103,6 +98,59 @@ class Search extends Ui return $searchForm->toHTML(); } + protected function addSortTool(Form $searchForm) + { + global $INPUT, $lang; + + $options = [ + 'hits' => [ + 'label' => $lang['search_sort_by_hits'], + 'sort' => '', + ], + 'mtime' => [ + 'label' => $lang['search_sort_by_mtime'], + 'sort' => 'mtime', + ], + ]; + $activeOption = 'hits'; + + if ($INPUT->str('sort') === 'mtime') { + $activeOption = 'mtime'; + } + + $searchForm->addTagOpen('div')->addClass('search-tool js-search-tool'); + // render current + $currentWrapper = $searchForm->addTagOpen('div')->addClass('search-tool__current js-current'); + if ($activeOption !== 'hits') { + $currentWrapper->addClass('search-tool__current--changed'); + } + $searchForm->addHTML($options[$activeOption]['label']); + $searchForm->addTagClose('div'); + + // render options list + $searchForm->addTagOpen('ul')->addClass('search-tool__options-list js-optionsList'); + + foreach ($options as $key => $option) { + $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item'); + + if ($key === $activeOption) { + $listItem->addClass('search-tool__options-list-item--active'); + $searchForm->addHTML($option['label']); + } else { + $this->searchState->addSearchLinkSort( + $searchForm, + $option['label'], + $option['sort'] + ); + } + $searchForm->addTagClose('li'); + } + $searchForm->addTagClose('ul'); + + $searchForm->addTagClose('div'); + + } + /** * Decide if the given query is simple enough to provide search assistance * @@ -136,7 +184,7 @@ class Search extends Ui /** * Add the elements to be used for search assistance * - * @param Form $searchForm + * @param Form $searchForm */ protected function addSearchAssistanceElements(Form $searchForm) { @@ -152,81 +200,143 @@ class Search extends Ui $this->addFragmentBehaviorLinks($searchForm); $this->addNamespaceSelector($searchForm); $this->addDateSelector($searchForm); + $this->addSortTool($searchForm); $searchForm->addTagClose('div'); } protected function addFragmentBehaviorLinks(Form $searchForm) { - $searchForm->addTagOpen('div')->addClass('search-results-form__subwrapper'); - $searchForm->addHTML('fragment behavior: '); - - $this->searchState->addSearchLinkFragment( - $searchForm, - 'exact match', - array_map(function($term){return trim($term, '*');},$this->parsedQuery['and']) - ); - - $searchForm->addHTML(', '); + global $lang; - $this->searchState->addSearchLinkFragment( - $searchForm, - 'starts with', - array_map(function($term){return trim($term, '*') . '*';},$this->parsedQuery['and']) - ); + $options = [ + 'exact' => [ + 'label' => $lang['search_exact_match'], + 'and' => array_map(function ($term) { + return trim($term, '*'); + }, $this->parsedQuery['and']), + ], + 'starts' => [ + 'label' => $lang['search_starts_with'], + 'and' => array_map(function ($term) { + return trim($term, '*') . '*'; + }, $this->parsedQuery['and']) + ], + 'ends' => [ + 'label' => $lang['search_ends_with'], + 'and' => array_map(function ($term) { + return '*' . trim($term, '*'); + }, $this->parsedQuery['and']) + ], + 'contains' => [ + 'label' => $lang['search_contains'], + 'and' => array_map(function ($term) { + return '*' . trim($term, '*') . '*'; + }, $this->parsedQuery['and']) + ] + ]; + + // detect current + $activeOption = 'exact'; + foreach ($options as $key => $option) { + if ($this->parsedQuery['and'] === $option['and']) { + $activeOption = $key; + } + } - $searchForm->addHTML(', '); + $searchForm->addTagOpen('div')->addClass('search-tool js-search-tool'); + // render current + $currentWrapper = $searchForm->addTagOpen('div')->addClass('search-tool__current js-current'); + if ($activeOption !== 'exact') { + $currentWrapper->addClass('search-tool__current--changed'); + } + $searchForm->addHTML($options[$activeOption]['label']); + $searchForm->addTagClose('div'); - $this->searchState->addSearchLinkFragment( - $searchForm, - 'ends with', - array_map(function($term){return '*' . trim($term, '*');},$this->parsedQuery['and']) - ); + // render options list + $searchForm->addTagOpen('ul')->addClass('search-tool__options-list js-optionsList'); - $searchForm->addHTML(', '); + foreach ($options as $key => $option) { + $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item'); - $this->searchState->addSearchLinkFragment( - $searchForm, - 'contains', - array_map(function($term){return '*' . trim($term, '*') . '*';},$this->parsedQuery['and']) - ); + if ($key === $activeOption) { + $listItem->addClass('search-tool__options-list-item--active'); + $searchForm->addHTML($option['label']); + } else { + $this->searchState->addSearchLinkFragment( + $searchForm, + $option['label'], + $option['and'] + ); + } + $searchForm->addTagClose('li'); + } + $searchForm->addTagClose('ul'); $searchForm->addTagClose('div'); + + // render options list } /** * Add the elements for the namespace selector * - * @param Form $searchForm + * @param Form $searchForm */ protected function addNamespaceSelector(Form $searchForm) { - $baseNS = empty($this->parsedQuery['ns']) ? '' : $this->parsedQuery['ns'][0]; - $searchForm->addTagOpen('div')->addClass('search-results-form__subwrapper'); + global $lang; + $baseNS = empty($this->parsedQuery['ns']) ? '' : $this->parsedQuery['ns'][0]; $extraNS = $this->getAdditionalNamespacesFromResults($baseNS); - if (!empty($extraNS) || $baseNS) { - $searchForm->addTagOpen('div'); - $searchForm->addHTML('limit to namespace: '); - if ($baseNS) { + $searchForm->addTagOpen('div')->addClass('search-tool js-search-tool'); + // render current + $currentWrapper = $searchForm->addTagOpen('div')->addClass('search-tool__current js-current'); + if ($baseNS) { + $currentWrapper->addClass('search-tool__current--changed'); + $searchForm->addHTML('@' . $baseNS); + } else { + $searchForm->addHTML($lang['search_any_ns']); + } + $searchForm->addTagClose('div'); + + // render options list + $searchForm->addTagOpen('ul')->addClass('search-tool__options-list js-optionsList'); + + $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item'); + if ($baseNS) { + $listItem->addClass('search-tool__options-list-item--active'); + $this->searchState->addSeachLinkNS( + $searchForm, + $lang['search_any_ns'], + '' + ); + } else { + $searchForm->addHTML($lang['search_any_ns']); + } + $searchForm->addTagClose('li'); + + foreach ($extraNS as $ns => $count) { + $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item'); + $label = $ns . ($count ? " ($count)" : ''); + + if ($ns === $baseNS) { + $listItem->addClass('search-tool__options-list-item--active'); + $searchForm->addHTML($label); + } else { $this->searchState->addSeachLinkNS( $searchForm, - '(remove limit)', - '' + $label, + $ns ); } - - foreach ($extraNS as $ns => $count) { - $searchForm->addHTML(' '); - $label = $ns . ($count ? " ($count)" : ''); - - $this->searchState->addSeachLinkNS($searchForm, $label, $ns); - } - $searchForm->addTagClose('div'); + $searchForm->addTagClose('li'); } + $searchForm->addTagClose('ul'); $searchForm->addTagClose('div'); + } /** @@ -264,58 +374,69 @@ class Search extends Ui * * @param Form $searchForm */ - protected function addDateSelector(Form $searchForm) { - $searchForm->addTagOpen('div')->addClass('search-results-form__subwrapper'); - $searchForm->addHTML('limit by date: '); - - global $INPUT; - if ($INPUT->has('before') || $INPUT->has('after')) { - $this->searchState->addSearchLinkTime( - $searchForm, - '(remove limit)', - false, - false - ); - - $searchForm->addHTML(', '); + protected function addDateSelector(Form $searchForm) + { + global $INPUT, $lang; + + $options = [ + 'any' => [ + 'before' => false, + 'after' => false, + 'label' => $lang['search_any_time'], + ], + 'week' => [ + 'before' => false, + 'after' => '1 week ago', + 'label' => $lang['search_past_7_days'], + ], + 'month' => [ + 'before' => false, + 'after' => '1 month ago', + 'label' => $lang['search_past_month'], + ], + 'year' => [ + 'before' => false, + 'after' => '1 year ago', + 'label' => $lang['search_past_year'], + ], + ]; + $activeOption = 'any'; + foreach ($options as $key => $option) { + if ($INPUT->str('after') === $option['after']) { + $activeOption = $key; + break; + } } - if ($INPUT->str('after') === '1 week ago') { - $searchForm->addHTML('<span class="active">past 7 days</span>'); - } else { - $this->searchState->addSearchLinkTime( - $searchForm, - 'past 7 days', - '1 week ago', - false - ); + $searchForm->addTagOpen('div')->addClass('search-tool js-search-tool'); + // render current + $currentWrapper = $searchForm->addTagOpen('div')->addClass('search-tool__current js-current'); + if ($INPUT->has('before') || $INPUT->has('after')) { + $currentWrapper->addClass('search-tool__current--changed'); } + $searchForm->addHTML($options[$activeOption]['label']); + $searchForm->addTagClose('div'); - $searchForm->addHTML(', '); - - if ($INPUT->str('after') === '1 month ago') { - $searchForm->addHTML('<span class="active">past month</span>'); - } else { - $this->searchState->addSearchLinkTime( - $searchForm, - 'past month', - '1 month ago', - false - ); - } + // render options list + $searchForm->addTagOpen('ul')->addClass('search-tool__options-list js-optionsList'); - $searchForm->addHTML(', '); + foreach ($options as $key => $option) { + $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item'); - if ($INPUT->str('after') === '1 year ago') { - $searchForm->addHTML('<span class="active">past year</span>'); - } else { - $this->searchState->addSearchLinkTime( - $searchForm, - 'past year', - '1 year ago', - false - ); + if ($key === $activeOption) { + $listItem->addClass('search-tool__options-list-item--active'); + $searchForm->addHTML($option['label']); + } else { + $this->searchState->addSearchLinkTime( + $searchForm, + $option['label'], + $option['after'], + $option['before'] + ); + } + $searchForm->addTagClose('li'); } + $searchForm->addTagClose('ul'); $searchForm->addTagClose('div'); } @@ -416,8 +537,8 @@ class Search extends Ui $resultHeader[] = $cnt . ' ' . $lang['hits']; if ($num < FT_SNIPPET_NUMBER) { // create snippets for the first number of matches only $snippet = '<dd>' . ft_snippet($id, $highlight) . '</dd>'; - $lastMod = '<span class="search_results__lastmod">'. $lang['lastmod'] . ' '; - $lastMod .= '<time datetime="' . date_iso8601($mtime) . '">'. dformat($mtime) . '</time>'; + $lastMod = '<span class="search_results__lastmod">' . $lang['lastmod'] . ' '; + $lastMod .= '<time datetime="' . date_iso8601($mtime) . '">' . dformat($mtime) . '</time>'; $lastMod .= '</span>'; } $num++; diff --git a/lib/scripts/search.js b/lib/scripts/search.js index bfde5490c..9bea9d1d7 100644 --- a/lib/scripts/search.js +++ b/lib/scripts/search.js @@ -19,5 +19,11 @@ jQuery(function () { $toggleAssistanceButton.click(); } + $searchForm.find('.js-search-tool .js-current').on('click', function() { + $searchForm.find('.js-current').show(); + $searchForm.find('.js-optionsList').hide(); + jQuery(this).hide(); + jQuery(this).next('.js-optionsList').show(); + }); }); diff --git a/lib/tpl/dokuwiki/css/_search.css b/lib/tpl/dokuwiki/css/_search.css index 8da2652a2..7e9b6c20a 100644 --- a/lib/tpl/dokuwiki/css/_search.css +++ b/lib/tpl/dokuwiki/css/_search.css @@ -13,7 +13,7 @@ } .search-results-form .search-results-form__fieldset { - width: 80vw; + width: 80%; } .search-results-form__show-assistance-button { @@ -27,6 +27,24 @@ margin-top: -0.3em; } +.search-tool__current { + cursor: pointer; +} + +.search-tool__current--changed { + font-weight: bold; +} + +.search-tool__options-list { + display: none; + + border: 1px solid @ini_border; +} + +.search-tool__options-list-item { + list-style: none; +} + /*____________ matching pagenames ____________*/ -- GitLab