From d0f707af0199a99085dc40693628acbf7e543abe Mon Sep 17 00:00:00 2001 From: Marcin Aman Date: Tue, 28 Jul 2020 16:57:07 +0200 Subject: Highlight searched phrase --- .../src/main/components/search/search.scss | 4 + .../frontend/src/main/components/search/search.tsx | 116 ++++++++++++++++----- .../frontend/src/main/components/search/types.ts | 2 - .../frontend/src/main/types/@jetbrains/index.d.ts | 7 ++ 4 files changed, 101 insertions(+), 28 deletions(-) (limited to 'plugins/base/frontend/src/main') diff --git a/plugins/base/frontend/src/main/components/search/search.scss b/plugins/base/frontend/src/main/components/search/search.scss index cc5a61ac..1068fe7a 100644 --- a/plugins/base/frontend/src/main/components/search/search.scss +++ b/plugins/base/frontend/src/main/components/search/search.scss @@ -25,6 +25,10 @@ .template-wrapper { display: grid; grid-template-columns: auto auto; + + span.phraseHighlight { + font-weight: bold; + } } .template-name { diff --git a/plugins/base/frontend/src/main/components/search/search.tsx b/plugins/base/frontend/src/main/components/search/search.tsx index c7b36654..14bf3c24 100644 --- a/plugins/base/frontend/src/main/components/search/search.tsx +++ b/plugins/base/frontend/src/main/components/search/search.tsx @@ -1,10 +1,81 @@ import React, {useCallback, useState} from 'react'; -import {Select} from '@jetbrains/ring-ui'; -import {List} from '@jetbrains/ring-ui'; +import {Select, List} from '@jetbrains/ring-ui'; +import fuzzyHighlight from '@jetbrains/ring-ui/components/global/fuzzy-highlight.js' import '@jetbrains/ring-ui/components/input-size/input-size.scss'; import './search.scss'; import {IWindow, Option, Props, Page} from "./types"; +type OptionWithSearchResult = Option & { + matched: boolean, + highlight: string +} + +type OptionWithHighlightComponent = Option & { + name: React.FC +} + +type SearchProps = { + page: Option, + label: string +} + +const orderRecords = (records: OptionWithSearchResult[], searchPhrase: string): OptionWithSearchResult[] => { + return records.sort((a: OptionWithSearchResult, b: OptionWithSearchResult) => { + //Prefer exact matches + const aIncludes = a.name.toLowerCase().includes(searchPhrase.toLowerCase()) ? 1 : 0 + const bIncludes = b.name.toLowerCase().includes(searchPhrase.toLowerCase()) ? 1 : 0 + const byIncludes = bIncludes - aIncludes + if(byIncludes != 0 && aIncludes == 1){ + return byIncludes + } + + //Prefer matches that are closer + const byFirstMatchedPosition = a.highlight.indexOf("**") - b.highlight.indexOf("**") + if(byFirstMatchedPosition == 0) { + return a.name.toLowerCase().localeCompare(b.name.toLowerCase()) + } + return byFirstMatchedPosition + }) +} + +const SearchResultRow: React.FC = ({label, page}: SearchProps) => { + const withSignature = page.name.replace(page.searchKey, label) + + return ( +
+ $1') } + }/> + {page.description} +
+ ) +} + +const highlightMatchedPhrases = (records: OptionWithSearchResult[]): OptionWithHighlightComponent[] => { + // @ts-ignore + return records.map(record => { + return { + ...record, + template: + } + }) +} + +class DokkaFuzzyFilterComponent extends Select { + getListItems(rawFilterString: string, _: Option[]) { + const matchedRecords = this.props.data + .map((record: Option) => { + return { + ...fuzzyHighlight(rawFilterString.trim(), record.searchKey), + ...record + } + }) + .filter((record: OptionWithSearchResult) => record.matched) + + return highlightMatchedPhrases(orderRecords(matchedRecords, rawFilterString)) + } +} + const WithFuzzySearchFilterComponent: React.FC = ({data}: Props) => { const [selected, onSelected] = useState