diff options
10 files changed, 378 insertions, 122 deletions
diff --git a/plugins/base/frontend/package-lock.json b/plugins/base/frontend/package-lock.json index 47c54682..4ccf72f2 100644 --- a/plugins/base/frontend/package-lock.json +++ b/plugins/base/frontend/package-lock.json @@ -1553,6 +1553,12 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", + "dev": true + }, "@types/react": { "version": "16.9.36", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.36.tgz", @@ -2254,6 +2260,12 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-react-svg": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/babel-plugin-react-svg/-/babel-plugin-react-svg-3.0.3.tgz", + "integrity": "sha512-Pst1RWjUIiV0Ykv1ODSeceCBsFOP2Y4dusjq7/XkjuzJdvS9CjpkPMUIoO4MLlvp5PiLCeMlsOC7faEUA0gm3Q==", + "dev": true + }, "babel-plugin-transform-define": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-define/-/babel-plugin-transform-define-2.0.0.tgz", @@ -2515,6 +2527,12 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "boolean": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz", @@ -3162,6 +3180,17 @@ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -3718,6 +3747,66 @@ "postcss": "^7.0.5" } }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + }, + "dependencies": { + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + } + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-what": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.3.0.tgz", + "integrity": "sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg==", + "dev": true + }, "cssdb": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", @@ -3728,6 +3817,39 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, + "csso": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", + "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.39" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.39", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", + "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", + "dev": true, + "requires": { + "mdn-data": "2.0.6", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", + "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "csstype": { "version": "2.6.10", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", @@ -7423,12 +7545,24 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, "lodash.pad": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", @@ -7711,6 +7845,12 @@ } } }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8540,6 +8680,15 @@ "gauge": "~1.2.5" } }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -8628,6 +8777,16 @@ "object-keys": "^1.0.11" } }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -8636,6 +8795,18 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -10177,6 +10348,12 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -10335,6 +10512,31 @@ "prop-types": "^15.5.7" } }, + "react-svg-core": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-svg-core/-/react-svg-core-3.0.3.tgz", + "integrity": "sha512-Ws3eM3xCAwcaYeqm4Ajcz3zxBYNI6BeTWWhFR0cpOT+pWuVtozgHYK9xUM0S/ilapZgYMQDe49XgOxpvooFq4w==", + "dev": true, + "requires": { + "@babel/core": "^7.4.5", + "@babel/plugin-syntax-jsx": "^7.2.0", + "@babel/preset-react": "^7.0.0", + "babel-plugin-react-svg": "^3.0.3", + "lodash.clonedeep": "^4.5.0", + "lodash.isplainobject": "^4.0.6", + "svgo": "^1.2.2" + } + }, + "react-svg-loader": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-svg-loader/-/react-svg-loader-3.0.3.tgz", + "integrity": "sha512-V1KnIUtvWVvc4xCig34n+f+/74ylMMugB2FbuAF/yq+QRi+WLi2hUYp9Ze3VylhA1D7ZgRygBh3Ojj8S3TPhJA==", + "dev": true, + "requires": { + "loader-utils": "^1.2.3", + "react-svg-core": "^3.0.3" + } + }, "react-virtualized": { "version": "9.21.2", "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.2.tgz", @@ -11188,6 +11390,12 @@ } } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "scheduler": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", @@ -11915,6 +12123,12 @@ "figgy-pudding": "^3.5.1" } }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, "stackframe": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-0.3.1.tgz", @@ -12526,6 +12740,27 @@ "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", "dev": true }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -13304,6 +13539,12 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -13518,6 +13759,18 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + } + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/plugins/base/frontend/package.json b/plugins/base/frontend/package.json index a1bf42c2..4415dd1c 100644 --- a/plugins/base/frontend/package.json +++ b/plugins/base/frontend/package.json @@ -46,6 +46,7 @@ "@jetbrains/stylelint-config": "^2.0.0", "babel-eslint": "^10.0.3", "eslint": "^6.8.0", + "react-svg-loader": "^3.0.3", "sass": "^1.26.3", "sass-loader": "^8.0.2", "stylelint": "^13.3.2", diff --git a/plugins/base/frontend/src/main/components/search/dokkaFuzzyFilter.tsx b/plugins/base/frontend/src/main/components/search/dokkaFuzzyFilter.tsx new file mode 100644 index 00000000..725fbaee --- /dev/null +++ b/plugins/base/frontend/src/main/components/search/dokkaFuzzyFilter.tsx @@ -0,0 +1,63 @@ +import {Select} from "@jetbrains/ring-ui"; +import {Option, OptionWithHighlightComponent, OptionWithSearchResult, SearchRank} from "./types"; +import fuzzyHighlight from '@jetbrains/ring-ui/components/global/fuzzy-highlight.js' +import React from "react"; +import {SearchResultRow} from "./searchResultRow"; + +const orderRecords = (records: OptionWithSearchResult[], searchPhrase: string): OptionWithSearchResult[] => { + return records.sort((a: OptionWithSearchResult, b: OptionWithSearchResult) => { + //Prefer higher rank + const byRank = b.rank - a.rank + if(byRank !== 0){ + return byRank + } + //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){ + 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 highlightMatchedPhrases = (records: OptionWithSearchResult[]): OptionWithHighlightComponent[] => { + // @ts-ignore + return records.map(record => { + return { + ...record, + template: <SearchResultRow searchResult={record}/> + } + }) +} + +export class DokkaFuzzyFilterComponent extends Select { + getListItems(rawFilterString: string, _: Option[]) { + const matchedRecords = this.props.data + .map((record: Option) => { + const bySearchKey = fuzzyHighlight(rawFilterString.trim(), record.searchKey, false) + if(bySearchKey.matched){ + return { + ...bySearchKey, + ...record, + rank: SearchRank.SearchKeyMatch + } + } + return { + ...fuzzyHighlight(rawFilterString.trim(), record.name, false), + ...record, + rank: SearchRank.NameMatch + } + }) + .filter((record: OptionWithSearchResult) => record.matched) + + return highlightMatchedPhrases(orderRecords(matchedRecords, rawFilterString)) + } +}
\ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx b/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx new file mode 100644 index 00000000..ad0b5f8f --- /dev/null +++ b/plugins/base/frontend/src/main/components/search/dokkaSearchAnchor.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import SearchIcon from 'react-svg-loader!./searchIcon.svg'; + +export const DokkaSearchAnchor = ({wrapperProps, buttonProps, popup}: any) => { + return ( + <span {...wrapperProps}> + <button type="button" {...buttonProps}> + <SearchIcon /> + </button> + {popup} + </span> + ) +}
\ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/search/search.tsx b/plugins/base/frontend/src/main/components/search/search.tsx index b4c5b1cd..c7976edb 100644 --- a/plugins/base/frontend/src/main/components/search/search.tsx +++ b/plugins/base/frontend/src/main/components/search/search.tsx @@ -1,107 +1,10 @@ import React, {useCallback, useState} from 'react'; 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"; - -enum SearchRank { - SearchKeyMatch = 1, - NameMatch = 0 -} -type OptionWithSearchResult = Option & { - matched: boolean, - highlight: string, - rank: SearchRank -} - -type OptionWithHighlightComponent = Option & { - name: React.FC<SearchProps> -} - -type SearchProps = { - searchResult: OptionWithSearchResult, -} - -const orderRecords = (records: OptionWithSearchResult[], searchPhrase: string): OptionWithSearchResult[] => { - return records.sort((a: OptionWithSearchResult, b: OptionWithSearchResult) => { - //Prefer higher rank - const byRank = b.rank - a.rank - if(byRank !== 0){ - return byRank - } - //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){ - 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> = ({searchResult}: SearchProps) => { - const signatureFromSearchResult = (searchResult: OptionWithSearchResult): string => { - if(searchResult.rank == SearchRank.SearchKeyMatch){ - return searchResult.name.replace(searchResult.searchKey, searchResult.highlight) - } - return searchResult.highlight - } - - const renderHighlightMarkersAsHtml = (record: string): string => { - return record.replace(/\*\*(.*?)\*\*/g, '<span class="phraseHighlight">$1</span>') - } - - return ( - <div className="template-wrapper"> - <span dangerouslySetInnerHTML={ - {__html: renderHighlightMarkersAsHtml(signatureFromSearchResult(searchResult)) } - }/> - <span className="template-description">{searchResult.description}</span> - </div> - ) -} - -const highlightMatchedPhrases = (records: OptionWithSearchResult[]): OptionWithHighlightComponent[] => { - // @ts-ignore - return records.map(record => { - return { - ...record, - template: <SearchResultRow searchResult={record}/> - } - }) -} - -class DokkaFuzzyFilterComponent extends Select { - getListItems(rawFilterString: string, _: Option[]) { - const matchedRecords = this.props.data - .map((record: Option) => { - const bySearchKey = fuzzyHighlight(rawFilterString.trim(), record.searchKey, true) - if(bySearchKey.matched){ - return { - ...bySearchKey, - ...record, - rank: SearchRank.SearchKeyMatch - } - } - return { - ...fuzzyHighlight(rawFilterString.trim(), record.name), - ...record, - rank: SearchRank.NameMatch - } - }) - .filter((record: OptionWithSearchResult) => record.matched) - - return highlightMatchedPhrases(orderRecords(matchedRecords, rawFilterString)) - } -} +import {IWindow, Option, Props} from "./types"; +import {DokkaSearchAnchor} from "./dokkaSearchAnchor"; +import {DokkaFuzzyFilterComponent} from "./dokkaFuzzyFilter"; const WithFuzzySearchFilterComponent: React.FC<Props> = ({data}: Props) => { const [selected, onSelected] = useState<Option>(data[0]); @@ -136,19 +39,6 @@ const WithFuzzySearchFilterComponent: React.FC<Props> = ({data}: Props) => { ) } -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 = () => { let data: Option[] = []; const pages = (window as IWindow).pages; diff --git a/plugins/base/frontend/src/main/components/search/searchIcon.svg b/plugins/base/frontend/src/main/components/search/searchIcon.svg new file mode 100644 index 00000000..391b1cab --- /dev/null +++ b/plugins/base/frontend/src/main/components/search/searchIcon.svg @@ -0,0 +1,3 @@ +<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>
\ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/search/searchResultRow.tsx b/plugins/base/frontend/src/main/components/search/searchResultRow.tsx new file mode 100644 index 00000000..9ae19cb9 --- /dev/null +++ b/plugins/base/frontend/src/main/components/search/searchResultRow.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import {OptionWithSearchResult, SearchProps, SearchRank} from "./types"; + +export const SearchResultRow: React.FC<SearchProps> = ({searchResult}: SearchProps) => { + const signatureFromSearchResult = (searchResult: OptionWithSearchResult): string => { + if(searchResult.rank == SearchRank.SearchKeyMatch){ + return searchResult.name.replace(searchResult.searchKey, searchResult.highlight) + } + return searchResult.highlight + } + + const renderHighlightMarkersAsHtml = (record: string): string => { + return record.replace(/\*\*(.*?)\*\*/g, '<span class="phraseHighlight">$1</span>') + } + + return ( + <div className="template-wrapper"> + <span dangerouslySetInnerHTML={ + {__html: renderHighlightMarkersAsHtml(signatureFromSearchResult(searchResult)) } + }/> + <span className="template-description">{searchResult.description}</span> + </div> + ) +}
\ No newline at end of file diff --git a/plugins/base/frontend/src/main/components/search/types.ts b/plugins/base/frontend/src/main/components/search/types.ts index a6736a2d..922935bd 100644 --- a/plugins/base/frontend/src/main/components/search/types.ts +++ b/plugins/base/frontend/src/main/components/search/types.ts @@ -1,3 +1,5 @@ +import React from "react"; + export type Page = { name: string; kind: string; @@ -23,7 +25,20 @@ export type Props = { data: Option[] }; +export enum SearchRank { + SearchKeyMatch = 1, + NameMatch = 0 +} +export type OptionWithSearchResult = Option & { + matched: boolean, + highlight: string, + rank: SearchRank +} + +export type OptionWithHighlightComponent = Option & { + name: React.FC<SearchProps> +} -export type State = { - selected: any +export type SearchProps = { + searchResult: OptionWithSearchResult, } 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 8ab594e3..3d209657 100644 --- a/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts +++ b/plugins/base/frontend/src/main/types/@jetbrains/index.d.ts @@ -2,9 +2,3 @@ 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[] -} diff --git a/plugins/base/frontend/webpack.config.js b/plugins/base/frontend/webpack.config.js index 559f5792..3372b37f 100644 --- a/plugins/base/frontend/webpack.config.js +++ b/plugins/base/frontend/webpack.config.js @@ -16,7 +16,7 @@ const webpackConfig = () => ({ entry: `${componentsPath}/root.tsx`, resolve: { mainFields: ['module', 'browser', 'main'], - extensions: ['.tsx', '.ts', '.js'], + extensions: ['.tsx', '.ts', '.js', '.svg'], alias: { react: resolve('./node_modules/react'), 'react-dom': resolve('./node_modules/react-dom'), |