diff options
author | Marcin Aman <maman@virtuslab.com> | 2020-07-28 16:57:07 +0200 |
---|---|---|
committer | Paweł Marks <Kordyjan@users.noreply.github.com> | 2020-08-03 13:47:51 +0200 |
commit | d0f707af0199a99085dc40693628acbf7e543abe (patch) | |
tree | 8819f1b78fb7a5ff88e824f23cce1632b6dfd8d3 /plugins/base/frontend/src/main | |
parent | 460cdf8bc9c54efd736b10ca58a865f034bf2e84 (diff) | |
download | dokka-d0f707af0199a99085dc40693628acbf7e543abe.tar.gz dokka-d0f707af0199a99085dc40693628acbf7e543abe.tar.bz2 dokka-d0f707af0199a99085dc40693628acbf7e543abe.zip |
Highlight searched phrase
Diffstat (limited to 'plugins/base/frontend/src/main')
4 files changed, 101 insertions, 28 deletions
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<SearchProps> +} + +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<SearchProps> = ({label, page}: SearchProps) => { + const withSignature = page.name.replace(page.searchKey, label) + + return ( + <div className="template-wrapper"> + <span dangerouslySetInnerHTML={ + {__html: withSignature.replace(/\*\*(.*?)\*\*/g, '<span class="phraseHighlight">$1</span>') } + }/> + <span className="template-description">{page.description}</span> + </div> + ) +} + +const highlightMatchedPhrases = (records: OptionWithSearchResult[]): OptionWithHighlightComponent[] => { + // @ts-ignore + return records.map(record => { + return { + ...record, + template: <SearchResultRow label={record.highlight} page={record}/> + } + }) +} + +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<Props> = ({data}: Props) => { const [selected, onSelected] = useState<Option>(data[0]); const onChangeSelected = useCallback( @@ -18,43 +89,37 @@ const WithFuzzySearchFilterComponent: React.FC<Props> = ({data}: Props) => { return ( <div className="search-container"> <div className="search"> - <Select + <DokkaFuzzyFilterComponent selectedLabel="Search" label="Please type page name" - filter={{fuzzy: true}} + filter={true} type={Select.Type.CUSTOM} clear + renderOptimization selected={selected} data={data} popupClassName={"popup-wrapper"} onSelect={onChangeSelected} - customAnchor={({wrapperProps, buttonProps, popup}) => ( - <span {...wrapperProps}> - <button type="button" {...buttonProps}> - <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"> - <path d="M19.64 18.36l-6.24-6.24a7.52 7.52 0 1 0-1.28 1.28l6.24 6.24zM7.5 13.4a5.9 5.9 0 1 1 5.9-5.9 5.91 5.91 0 0 1-5.9 5.9z"/> - </svg> - </button> - {popup} - </span> - )} + customAnchor={({wrapperProps, buttonProps, popup}) => + <DokkaSearchAnchor wrapperProps={wrapperProps} buttonProps={buttonProps} popup={popup}/> + } /> </div> </div> ) } -const templateGenerator = (page:Page) => { - let classGenerator = (page:Page) => { - let classes = "" - if(page.level !== undefined) classes = classes + " indented" - if(page.disabled) classes = classes + " disabled" - return classes - } - return <div className="template-wrapper"> - <span className= {classGenerator(page)}>{page.name}</span> - <span className="template-description">{page.description}</span> - </div> +const DokkaSearchAnchor = ({wrapperProps, buttonProps, popup}) => { + return ( + <span {...wrapperProps}> + <button type="button" {...buttonProps}> + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"> + <path d="M19.64 18.36l-6.24-6.24a7.52 7.52 0 1 0-1.28 1.28l6.24 6.24zM7.5 13.4a5.9 5.9 0 1 1 5.9-5.9 5.91 5.91 0 0 1-5.9 5.9z"/> + </svg> + </button> + {popup} + </span> + ) } export const WithFuzzySearchFilter = () => { @@ -66,7 +131,6 @@ export const WithFuzzySearchFilter = () => { label: page.searchKey, key: i + 1, type: page.kind, - template: templateGenerator(page), rgItemType: List.ListProps.Type.CUSTOM })); } diff --git a/plugins/base/frontend/src/main/components/search/types.ts b/plugins/base/frontend/src/main/components/search/types.ts index 881a16d8..a6736a2d 100644 --- a/plugins/base/frontend/src/main/components/search/types.ts +++ b/plugins/base/frontend/src/main/components/search/types.ts @@ -3,8 +3,6 @@ export type Page = { kind: string; location: string; searchKey: string; - level: number; - index: string; description: string; disabled: boolean; } diff --git a/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts b/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts index 1dc9983c..8ab594e3 100644 --- a/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts +++ b/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts @@ -1,3 +1,10 @@ declare module '@jetbrains/ring-ui' { export const Select: any; + export const List: any; +} + +declare module '@jetbrains/ring-ui/components/global/fuzzy-highlight.js' { + import {Option} from "../../components/search/types"; + + export const fuzzyHighlight: (string, string, boolean) => Option[] } |