diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 13de3985..f91c898a 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -66,6 +66,7 @@ module.exports = { copyright: `Copyright © ${new Date().getFullYear()} Solana Foundation`, }, }, + plugins: [require.resolve('docusaurus-lunr-search')], presets: [ [ "@docusaurus/preset-classic", diff --git a/docs/package-lock.json b/docs/package-lock.json index 46f585a7..1844c5e9 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1743,6 +1743,11 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "@types/parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", + "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" + }, "@types/q": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", @@ -2556,6 +2561,11 @@ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" }, + "bcp-47-match": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz", + "integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -3365,6 +3375,11 @@ "simple-swizzle": "^0.2.2" } }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "colorette": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", @@ -3495,6 +3510,11 @@ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -3864,6 +3884,11 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "css-selector-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz", + "integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g==" + }, "css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -4271,6 +4296,11 @@ } } }, + "direction": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", + "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==" + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -4315,6 +4345,69 @@ "esutils": "^2.0.2" } }, + "docusaurus-lunr-search": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/docusaurus-lunr-search/-/docusaurus-lunr-search-2.1.7.tgz", + "integrity": "sha512-RHrt8li7VIu1ohR+EDM86gSmitTokNFVtc9036wQ9FYZRiHUNoDN+OUPZ65P0nhUiQIAiYkPPxTymDzXTXrmTg==", + "requires": { + "autocomplete.js": "^0.37.0", + "classnames": "^2.2.6", + "gauge": "^3.0.0", + "hast-util-select": "^4.0.0", + "hast-util-to-text": "^2.0.0", + "hogan.js": "^3.0.2", + "lunr": "^2.3.8", + "lunr-languages": "^1.4.0", + "minimatch": "^3.0.4", + "object-assign": "^4.1.1", + "rehype-parse": "^7.0.1", + "to-vfile": "^6.1.0", + "unified": "^9.0.0", + "unist-util-is": "^4.0.2" + }, + "dependencies": { + "autocomplete.js": { + "version": "0.37.1", + "resolved": "https://registry.npmjs.org/autocomplete.js/-/autocomplete.js-0.37.1.tgz", + "integrity": "sha512-PgSe9fHYhZEsm/9jggbjtVsGXJkPLvd+9mC7gZJ662vVL5CRWEtm/mIrrzCx0MrNxHVwxD5d00UOn6NsmL2LUQ==", + "requires": { + "immediate": "^3.2.3" + } + }, + "hast-util-from-parse5": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.0.tgz", + "integrity": "sha512-3ZYnfKenbbkhhNdmOQqgH10vnvPivTdsOJCri+APn0Kty+nRkDHArnaX9Hiaf8H+Ig+vkNptL+SRY/6RwWJk1Q==", + "requires": { + "@types/parse5": "^5.0.0", + "ccount": "^1.0.0", + "hastscript": "^5.0.0", + "property-information": "^5.0.0", + "vfile": "^4.0.0", + "web-namespaces": "^1.0.0" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "rehype-parse": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-7.0.1.tgz", + "integrity": "sha512-fOiR9a9xH+Le19i4fGzIEowAbwG7idy2Jzs4mOrFWBSJ0sNUgy0ev871dwWnbOo371SjgjG4pwzrbgSVrKxecw==", + "requires": { + "hast-util-from-parse5": "^6.0.0", + "parse5": "^6.0.0" + } + }, + "unist-util-is": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" + } + } + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -5662,6 +5755,52 @@ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, + "gauge": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.0.tgz", + "integrity": "sha512-VSxauaaCsLOTerAyzunAYGgK3iaWZvOL1BCvBvf/IhDWrczPAf1tUqn05DOCJOOe4k3vOdX6fHhJIvF2UtCMhw==", + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1 || ^2.0.0", + "strip-ansi": "^3.0.1 || ^4.0.0", + "wide-align": "^1.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + } + } + }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -5900,6 +6039,11 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -6008,6 +6152,16 @@ "xtend": "^4.0.1" } }, + "hast-util-has-property": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-1.0.4.tgz", + "integrity": "sha512-ghHup2voGfgFoHMGnaLHOjbYFACKrRh9KFttdCzMCbFoBMJXiNi2+XTrPP8+q6cDJM/RSqlCfVWrjp1H201rZg==" + }, + "hast-util-is-element": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz", + "integrity": "sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==" + }, "hast-util-parse-selector": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.4.tgz", @@ -6028,6 +6182,27 @@ "zwitch": "^1.0.0" } }, + "hast-util-select": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-4.0.0.tgz", + "integrity": "sha512-FL8rsw0eleGk4Itt3LdSiUvTDZoD1Uz79c/JxVuIjT6uKqOX+gTna0zVu4/lT4Aifw/Z7rdH5wLXtyfp/d+Gdw==", + "requires": { + "bcp-47-match": "^1.0.0", + "comma-separated-tokens": "^1.0.0", + "css-selector-parser": "^1.0.0", + "direction": "^1.0.0", + "hast-util-has-property": "^1.0.0", + "hast-util-is-element": "^1.0.0", + "hast-util-to-string": "^1.0.0", + "hast-util-whitespace": "^1.0.0", + "not": "^0.1.0", + "nth-check": "^1.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0", + "unist-util-visit": "^2.0.0", + "zwitch": "^1.0.0" + } + }, "hast-util-to-parse5": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-5.1.2.tgz", @@ -6040,6 +6215,26 @@ "zwitch": "^1.0.0" } }, + "hast-util-to-string": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-1.0.4.tgz", + "integrity": "sha512-eK0MxRX47AV2eZ+Lyr18DCpQgodvaS3fAQO2+b9Two9F5HEoRPhiUMNzoXArMJfZi2yieFzUBMRl3HNJ3Jus3w==" + }, + "hast-util-to-text": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-2.0.0.tgz", + "integrity": "sha512-idXqFGmKInLKcFMbLvh0fldmV94o+aOdXL/z5H5XhPhUp/5vzycu7i15c8V9kC6W3XgGHg2uuiIcRJlWtESVfQ==", + "requires": { + "hast-util-is-element": "^1.0.0", + "repeat-string": "^1.0.0", + "unist-util-find-after": "^3.0.0" + } + }, + "hast-util-whitespace": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", + "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==" + }, "hastscript": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", @@ -7343,6 +7538,16 @@ "yallist": "^3.0.2" } }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + }, + "lunr-languages": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.4.0.tgz", + "integrity": "sha512-YWfZDExJN/MJEVE/DbM4AuVRLsqeHi+q3wmECMsWjGIOkd5mr9DUNos7fv8f5do9VLRMYXIzFjn+N4+KPI9pQA==" + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -7959,6 +8164,11 @@ "sort-keys": "^1.0.0" } }, + "not": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz", + "integrity": "sha1-yWkcF0bFXc++VMvYvU/wQbwrUZ0=" + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -12022,6 +12232,22 @@ "is-number": "^7.0.0" } }, + "to-vfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-6.1.0.tgz", + "integrity": "sha512-BxX8EkCxOAZe+D/ToHdDsJcVI4HqQfmw0tCkp31zf3dNP/XWIAjU4CmeuSwsSoOzOTqHPOL0KUzyZqJplkD0Qw==", + "requires": { + "is-buffer": "^2.0.0", + "vfile": "^4.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + } + } + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -12238,6 +12464,21 @@ "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==" }, + "unist-util-find-after": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-3.0.0.tgz", + "integrity": "sha512-ojlBqfsBftYXExNu3+hHLfJQ/X1jYY/9vdm4yZWjIbf0VuWF6CRufci1ZyoD/wV2TYMKxXUoNuoqwy+CkgzAiQ==", + "requires": { + "unist-util-is": "^4.0.0" + }, + "dependencies": { + "unist-util-is": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz", + "integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ==" + } + } + }, "unist-util-generated": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.5.tgz", @@ -13572,6 +13813,43 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/docs/package.json b/docs/package.json index 8f21bb2b..9cdb49fe 100644 --- a/docs/package.json +++ b/docs/package.json @@ -18,6 +18,7 @@ "@docusaurus/theme-search-algolia": "^2.0.0-alpha.32", "babel-eslint": "^10.1.0", "clsx": "^1.1.1", + "docusaurus-lunr-search": "^2.1.7", "eslint": "^7.3.1", "eslint-plugin-react": "^7.20.0", "prettier": "^2.0.5", diff --git a/docs/src/theme/SearchBar/algolia.css b/docs/src/theme/SearchBar/algolia.css new file mode 100644 index 00000000..7bc62264 --- /dev/null +++ b/docs/src/theme/SearchBar/algolia.css @@ -0,0 +1,533 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* Bottom border of each suggestion */ +.algolia-docsearch-suggestion { + border-bottom-color: #3a3dd1; +} +/* Main category headers */ +.algolia-docsearch-suggestion--category-header { + background-color: #4b54de; +} +/* Highlighted search terms */ +.algolia-docsearch-suggestion--highlight { + color: #3a33d1; +} +/* Highligted search terms in the main category headers */ +.algolia-docsearch-suggestion--category-header + .algolia-docsearch-suggestion--highlight { + background-color: #4d47d5; +} +/* Currently selected suggestion */ +.aa-cursor .algolia-docsearch-suggestion--content { + color: #272296; +} +.aa-cursor .algolia-docsearch-suggestion { + background: #ebebfb; +} + +/* For bigger screens, when displaying results in two columns */ +@media (min-width: 768px) { + /* Bottom border of each suggestion */ + .algolia-docsearch-suggestion { + border-bottom-color: #7671df; + } + /* Left column, with secondary category header */ + .algolia-docsearch-suggestion--subcategory-column { + border-right-color: #7671df; + color: #4e4726; + } +} + +.searchbox { + display: inline-block; + position: relative; + width: 200px; + height: 32px !important; + white-space: nowrap; + box-sizing: border-box; + visibility: visible !important; +} + +.searchbox .algolia-autocomplete { + display: block; + width: 100%; + height: 100%; +} + +.searchbox__wrapper { + width: 100%; + height: 100%; + z-index: 999; + position: relative; +} + +.searchbox__input { + display: inline-block; + box-sizing: border-box; + -webkit-transition: box-shadow 0.4s ease, background 0.4s ease; + transition: box-shadow 0.4s ease, background 0.4s ease; + border: 0; + border-radius: 16px; + box-shadow: inset 0 0 0 1px #cccccc; + background: #ffffff !important; + padding: 0; + padding-right: 26px; + padding-left: 32px; + width: 100%; + height: 100%; + vertical-align: middle; + white-space: normal; + font-size: 12px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.searchbox__input::-webkit-search-decoration, +.searchbox__input::-webkit-search-cancel-button, +.searchbox__input::-webkit-search-results-button, +.searchbox__input::-webkit-search-results-decoration { + display: none; +} + +.searchbox__input:hover { + box-shadow: inset 0 0 0 1px #b3b3b3; +} + +.searchbox__input:focus, +.searchbox__input:active { + outline: 0; + box-shadow: inset 0 0 0 1px #aaaaaa; + background: #ffffff; +} + +.searchbox__input::-webkit-input-placeholder { + color: #aaaaaa; +} + +.searchbox__input::-moz-placeholder { + color: #aaaaaa; +} + +.searchbox__input:-ms-input-placeholder { + color: #aaaaaa; +} + +.searchbox__input::placeholder { + color: #aaaaaa; +} + +.searchbox__submit { + position: absolute; + top: 0; + margin: 0; + border: 0; + border-radius: 16px 0 0 16px; + background-color: rgba(69, 142, 225, 0); + padding: 0; + width: 32px; + height: 100%; + vertical-align: middle; + text-align: center; + font-size: inherit; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + right: inherit; + left: 0; +} + +.searchbox__submit::before { + display: inline-block; + margin-right: -4px; + height: 100%; + vertical-align: middle; + content: ''; +} + +.searchbox__submit:hover, +.searchbox__submit:active { + cursor: pointer; +} + +.searchbox__submit:focus { + outline: 0; +} + +.searchbox__submit svg { + width: 14px; + height: 14px; + vertical-align: middle; + fill: #6d7e96; +} + +.searchbox__reset { + display: block; + position: absolute; + top: 8px; + right: 8px; + margin: 0; + border: 0; + background: none; + cursor: pointer; + padding: 0; + font-size: inherit; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + fill: rgba(0, 0, 0, 0.5); +} + +.searchbox__reset.hide { + display: none; +} + +.searchbox__reset:focus { + outline: 0; +} + +.searchbox__reset svg { + display: block; + margin: 4px; + width: 8px; + height: 8px; +} + +.searchbox__input:valid ~ .searchbox__reset { + display: block; + -webkit-animation-name: sbx-reset-in; + animation-name: sbx-reset-in; + -webkit-animation-duration: 0.15s; + animation-duration: 0.15s; +} + +@-webkit-keyframes sbx-reset-in { + 0% { + -webkit-transform: translate3d(-20%, 0, 0); + transform: translate3d(-20%, 0, 0); + opacity: 0; + } + 100% { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes sbx-reset-in { + 0% { + -webkit-transform: translate3d(-20%, 0, 0); + transform: translate3d(-20%, 0, 0); + opacity: 0; + } + 100% { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.algolia-autocomplete .ds-dropdown-menu:before { + display: block; + position: absolute; + content: ''; + width: 14px; + height: 14px; + background: #373940; + z-index: 1000; + top: -7px; + border-top: 1px solid #373940; + border-right: 1px solid #373940; + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + border-radius: 2px; +} + +.algolia-autocomplete .ds-dropdown-menu { + box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2), 0 2px 3px 0 rgba(0, 0, 0, 0.1); +} + +@media (min-width: 601px) { + .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu { + right: 0 !important; + left: inherit !important; + } + + .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu:before { + right: 48px; + } + + .algolia-autocomplete .ds-dropdown-menu { + position: relative; + top: -6px; + border-radius: 4px; + margin: 6px 0 0; + padding: 0; + text-align: left; + height: auto; + position: relative; + background: transparent; + border: none; + z-index: 999; + max-width: 600px; + min-width: 500px; + } +} + +@media (max-width: 600px) { + .algolia-autocomplete .ds-dropdown-menu { + z-index: 100; + position: fixed !important; + top: 50px !important; + left: auto !important; + right: 1rem !important; + width: 600px; + max-width: calc(100% - 2rem); + max-height: calc(100% - 5rem); + display: block; + } + + .algolia-autocomplete .ds-dropdown-menu:before { + right: 6rem; + } +} + +.algolia-autocomplete .ds-dropdown-menu .ds-suggestions { + position: relative; + z-index: 1000; +} + +.algolia-autocomplete .ds-dropdown-menu .ds-suggestion { + cursor: pointer; +} + +.algolia-autocomplete .ds-dropdown-menu [class^='ds-dataset-'] { + position: relative; + border-radius: 4px; + overflow: auto; + padding: 0; + background: #ffffff; +} + +.algolia-autocomplete .ds-dropdown-menu * { + box-sizing: border-box; +} + +.algolia-autocomplete .algolia-docsearch-suggestion { + display: block; + position: relative; + padding: 0; + overflow: hidden; + text-decoration: none; +} + +.algolia-autocomplete .ds-cursor .algolia-docsearch-suggestion--wrapper { + background: #f1f1f1; + box-shadow: inset -2px 0 0 #61dafb; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--highlight { + background: #ffe564; + padding: 0.1em 0.05em; +} + +.algolia-autocomplete + .algolia-docsearch-suggestion--category-header + .algolia-docsearch-suggestion--category-header-lvl0 + .algolia-docsearch-suggestion--highlight, +.algolia-autocomplete + .algolia-docsearch-suggestion--category-header + .algolia-docsearch-suggestion--category-header-lvl1 + .algolia-docsearch-suggestion--highlight { + color: inherit; + background: inherit; +} + +.algolia-autocomplete + .algolia-docsearch-suggestion--text + .algolia-docsearch-suggestion--highlight { + padding: 0 0 1px; + background: inherit; + box-shadow: inset 0 -2px 0 0 rgba(69, 142, 225, 0.8); + color: inherit; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--content { + display: block; + float: right; + width: 70%; + position: relative; + padding: 5.33333px 0 5.33333px 10.66667px; + cursor: pointer; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--content:before { + content: ''; + position: absolute; + display: block; + top: 0; + height: 100%; + width: 1px; + background: #ececec; + left: -1px; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--category-header { + position: relative; + display: none; + font-size: 14px; + letter-spacing: 0.08em; + font-weight: 700; + background-color: #373940; + text-transform: uppercase; + color: #fff; + margin: 0; + padding: 5px 8px; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--wrapper { + background-color: #fff; + width: 100%; + float: left; + padding: 8px 0 0 0; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { + float: left; + width: 30%; + display: none; + padding-left: 0; + text-align: right; + position: relative; + padding: 5.33333px 10.66667px; + color: #777; + font-size: 0.9em; + word-wrap: break-word; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column:before { + content: ''; + position: absolute; + display: block; + top: 0; + height: 100%; + width: 1px; + background: #ececec; + right: 0; +} + +.algolia-autocomplete + .algolia-docsearch-suggestion.algolia-docsearch-suggestion__main + .algolia-docsearch-suggestion--category-header, +.algolia-autocomplete + .algolia-docsearch-suggestion.algolia-docsearch-suggestion__secondary { + display: block; +} + +.algolia-autocomplete + .algolia-docsearch-suggestion--subcategory-column + .algolia-docsearch-suggestion--highlight { + background-color: inherit; + color: inherit; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-inline { + display: none; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--title { + margin-bottom: 4px; + color: #02060c; + font-size: 0.9em; + font-weight: bold; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--text { + display: block; + line-height: 1.2em; + font-size: 0.85em; + color: #63676d; + padding-right: 2px; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--no-results { + width: 100%; + padding: 8px 0; + text-align: center; + font-size: 1.2em; + background-color: #373940; + margin-top: -8px; +} + +.algolia-autocomplete + .algolia-docsearch-suggestion--no-results + .algolia-docsearch-suggestion--text { + color: #ffffff; + margin-top: 4px; +} + +.algolia-autocomplete .algolia-docsearch-suggestion--no-results::before { + display: none; +} + +.algolia-autocomplete .algolia-docsearch-suggestion code { + padding: 1px 5px; + font-size: 90%; + border: none; + color: #222222; + background-color: #ebebeb; + border-radius: 3px; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +.algolia-autocomplete + .algolia-docsearch-suggestion + code + .algolia-docsearch-suggestion--highlight { + background: none; +} + +.algolia-autocomplete + .algolia-docsearch-suggestion.algolia-docsearch-suggestion__main + .algolia-docsearch-suggestion--category-header { + color: white; + display: block; +} + +.algolia-autocomplete + .algolia-docsearch-suggestion.algolia-docsearch-suggestion__secondary + .algolia-docsearch-suggestion--subcategory-column { + display: block; +} + +.algolia-autocomplete .algolia-docsearch-footer { + background-color: #fff; + width: 100%; + height: 30px; + z-index: 2000; + float: right; + font-size: 0; + line-height: 0; +} + +.algolia-autocomplete .algolia-docsearch-footer--logo { + background-image: url('data:image/svg+xml;utf8,'); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; + overflow: hidden; + text-indent: -9000px; + width: 110px; + height: 100%; + display: block; + margin-left: auto; + margin-right: 5px; +} diff --git a/docs/src/theme/SearchBar/index.js b/docs/src/theme/SearchBar/index.js new file mode 100644 index 00000000..76c43025 --- /dev/null +++ b/docs/src/theme/SearchBar/index.js @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useRef, useCallback } from "react"; +import classnames from "classnames"; +import { useHistory } from "@docusaurus/router"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +const Search = props => { + const initialized = useRef(false); + const searchBarRef = useRef(null); + const history = useHistory(); + const { siteConfig = {} } = useDocusaurusContext(); + const { baseUrl } = siteConfig; + const initAlgolia = (searchDocs, searchIndex, DocSearch) => { + new DocSearch({ + searchDocs, + searchIndex, + inputSelector: "#search_input_react", + // Override algolia's default selection event, allowing us to do client-side + // navigation and avoiding a full page refresh. + handleSelected: (_input, _event, suggestion) => { + const url = baseUrl + suggestion.url; + // Use an anchor tag to parse the absolute url into a relative url + // Alternatively, we can use new URL(suggestion.url) but its not supported in IE + const a = document.createElement("a"); + a.href = url; + // Algolia use closest parent element id #__docusaurus when a h1 page title does not have an id + // So, we can safely remove it. See https://github.com/facebook/docusaurus/issues/1828 for more details. + + history.push(url); + } + }); + }; + + const getSearchDoc = () => + process.env.NODE_ENV === "production" + ? fetch(`${baseUrl}search-doc.json`).then((content) => content.json()) + : Promise.resolve([]); + + const getLunrIndex = () => + process.env.NODE_ENV === "production" + ? fetch(`${baseUrl}lunr-index.json`).then((content) => content.json()) + : Promise.resolve([]); + + const loadAlgolia = () => { + if (!initialized.current) { + Promise.all([ + getSearchDoc(), + getLunrIndex(), + import("./lib/DocSearch"), + import("./algolia.css") + ]).then(([searchDocs, searchIndex, { default: DocSearch }]) => { + initAlgolia(searchDocs, searchIndex, DocSearch); + }); + initialized.current = true; + } + }; + + const toggleSearchIconClick = useCallback( + e => { + if (!searchBarRef.current.contains(e.target)) { + searchBarRef.current.focus(); + } + + props.handleSearchBarToggle(!props.isSearchBarExpanded); + }, + [props.isSearchBarExpanded] + ); + + return ( +
+ + +
+ ); +}; + +export default Search; diff --git a/docs/src/theme/SearchBar/lib/DocSearch.js b/docs/src/theme/SearchBar/lib/DocSearch.js new file mode 100644 index 00000000..ffdca6a5 --- /dev/null +++ b/docs/src/theme/SearchBar/lib/DocSearch.js @@ -0,0 +1,306 @@ +import Hogan from "hogan.js"; +import LunrSearchAdapter from "./lunar-search"; +import autocomplete from "autocomplete.js"; +import templates from "./templates"; +import utils from "./utils"; +import $ from "autocomplete.js/zepto"; + +/** + * Adds an autocomplete dropdown to an input field + * @function DocSearch + * @param {Object} options.searchDocs Search Documents + * @param {Object} options.searchIndex Lune searchIndexes + * @param {string} options.inputSelector CSS selector that targets the input + * value. + * @param {Object} [options.autocompleteOptions] Options to pass to the underlying autocomplete instance + * @return {Object} + */ +class DocSearch { + constructor({ + searchDocs, + searchIndex, + inputSelector, + debug = false, + queryDataCallback = null, + autocompleteOptions = { + debug: false, + hint: false, + autoselect: true + }, + transformData = false, + queryHook = false, + handleSelected = false, + enhancedSearchInput = false, + layout = "collumns" + }) { + this.input = DocSearch.getInputFromSelector(inputSelector); + this.queryDataCallback = queryDataCallback || null; + const autocompleteOptionsDebug = + autocompleteOptions && autocompleteOptions.debug + ? autocompleteOptions.debug + : false; + // eslint-disable-next-line no-param-reassign + autocompleteOptions.debug = debug || autocompleteOptionsDebug; + this.autocompleteOptions = autocompleteOptions; + this.autocompleteOptions.cssClasses = + this.autocompleteOptions.cssClasses || {}; + this.autocompleteOptions.cssClasses.prefix = + this.autocompleteOptions.cssClasses.prefix || "ds"; + const inputAriaLabel = + this.input && + typeof this.input.attr === "function" && + this.input.attr("aria-label"); + this.autocompleteOptions.ariaLabel = + this.autocompleteOptions.ariaLabel || inputAriaLabel || "search input"; + + this.isSimpleLayout = layout === "simple"; + + this.client = new LunrSearchAdapter(searchDocs, searchIndex); + + if (enhancedSearchInput) { + this.input = DocSearch.injectSearchBox(this.input); + } + this.autocomplete = autocomplete(this.input, autocompleteOptions, [ + { + source: this.getAutocompleteSource(transformData, queryHook), + templates: { + suggestion: DocSearch.getSuggestionTemplate(this.isSimpleLayout), + footer: templates.footer, + empty: DocSearch.getEmptyTemplate() + } + } + ]); + + const customHandleSelected = handleSelected; + this.handleSelected = customHandleSelected || this.handleSelected; + + // We prevent default link clicking if a custom handleSelected is defined + if (customHandleSelected) { + $(".algolia-autocomplete").on("click", ".ds-suggestions a", event => { + event.preventDefault(); + }); + } + + this.autocomplete.on( + "autocomplete:selected", + this.handleSelected.bind(null, this.autocomplete.autocomplete) + ); + + this.autocomplete.on( + "autocomplete:shown", + this.handleShown.bind(null, this.input) + ); + + if (enhancedSearchInput) { + DocSearch.bindSearchBoxEvent(); + } + } + + static injectSearchBox(input) { + input.before(templates.searchBox); + const newInput = input + .prev() + .prev() + .find("input"); + input.remove(); + return newInput; + } + + static bindSearchBoxEvent() { + $('.searchbox [type="reset"]').on("click", function () { + $("input#docsearch").focus(); + $(this).addClass("hide"); + autocomplete.autocomplete.setVal(""); + }); + + $("input#docsearch").on("keyup", () => { + const searchbox = document.querySelector("input#docsearch"); + const reset = document.querySelector('.searchbox [type="reset"]'); + reset.className = "searchbox__reset"; + if (searchbox.value.length === 0) { + reset.className += " hide"; + } + }); + } + + /** + * Returns the matching input from a CSS selector, null if none matches + * @function getInputFromSelector + * @param {string} selector CSS selector that matches the search + * input of the page + * @returns {void} + */ + static getInputFromSelector(selector) { + const input = $(selector).filter("input"); + return input.length ? $(input[0]) : null; + } + + /** + * Returns the `source` method to be passed to autocomplete.js. It will query + * the Algolia index and call the callbacks with the formatted hits. + * @function getAutocompleteSource + * @param {function} transformData An optional function to transform the hits + * @param {function} queryHook An optional function to transform the query + * @returns {function} Method to be passed as the `source` option of + * autocomplete + */ + getAutocompleteSource(transformData, queryHook) { + return (query, callback) => { + if (queryHook) { + // eslint-disable-next-line no-param-reassign + query = queryHook(query) || query; + } + this.client.search(query).then(hits => { + if ( + this.queryDataCallback && + typeof this.queryDataCallback == "function" + ) { + this.queryDataCallback(hits); + } + if (transformData) { + hits = transformData(hits) || hits; + } + callback(DocSearch.formatHits(hits)); + }); + }; + } + + // Given a list of hits returned by the API, will reformat them to be used in + // a Hogan template + static formatHits(receivedHits) { + const clonedHits = utils.deepClone(receivedHits); + const hits = clonedHits.map(hit => { + if (hit._highlightResult) { + // eslint-disable-next-line no-param-reassign + hit._highlightResult = utils.mergeKeyWithParent( + hit._highlightResult, + "hierarchy" + ); + } + return utils.mergeKeyWithParent(hit, "hierarchy"); + }); + + // Group hits by category / subcategory + let groupedHits = utils.groupBy(hits, "lvl0"); + $.each(groupedHits, (level, collection) => { + const groupedHitsByLvl1 = utils.groupBy(collection, "lvl1"); + const flattenedHits = utils.flattenAndFlagFirst( + groupedHitsByLvl1, + "isSubCategoryHeader" + ); + groupedHits[level] = flattenedHits; + }); + groupedHits = utils.flattenAndFlagFirst(groupedHits, "isCategoryHeader"); + + // Translate hits into smaller objects to be send to the template + return groupedHits.map(hit => { + const url = DocSearch.formatURL(hit); + const category = utils.getHighlightedValue(hit, "lvl0"); + const subcategory = utils.getHighlightedValue(hit, "lvl1") || category; + const displayTitle = utils + .compact([ + utils.getHighlightedValue(hit, "lvl2") || subcategory, + utils.getHighlightedValue(hit, "lvl3"), + utils.getHighlightedValue(hit, "lvl4"), + utils.getHighlightedValue(hit, "lvl5"), + utils.getHighlightedValue(hit, "lvl6") + ]) + .join( + '' + ); + const text = utils.getSnippetedValue(hit, "content"); + const isTextOrSubcategoryNonEmpty = + (subcategory && subcategory !== "") || + (displayTitle && displayTitle !== ""); + const isLvl1EmptyOrDuplicate = + !subcategory || subcategory === "" || subcategory === category; + const isLvl2 = + displayTitle && displayTitle !== "" && displayTitle !== subcategory; + const isLvl1 = + !isLvl2 && + (subcategory && subcategory !== "" && subcategory !== category); + const isLvl0 = !isLvl1 && !isLvl2; + + return { + isLvl0, + isLvl1, + isLvl2, + isLvl1EmptyOrDuplicate, + isCategoryHeader: hit.isCategoryHeader, + isSubCategoryHeader: hit.isSubCategoryHeader, + isTextOrSubcategoryNonEmpty, + category, + subcategory, + title: displayTitle, + text, + url + }; + }); + } + + static formatURL(hit) { + const { url, anchor } = hit; + if (url) { + const containsAnchor = url.indexOf("#") !== -1; + if (containsAnchor) return url; + else if (anchor) return `${hit.url}#${hit.anchor}`; + return url; + } else if (anchor) return `#${hit.anchor}`; + /* eslint-disable */ + console.warn("no anchor nor url for : ", JSON.stringify(hit)); + /* eslint-enable */ + return null; + } + + static getEmptyTemplate() { + return args => Hogan.compile(templates.empty).render(args); + } + + static getSuggestionTemplate(isSimpleLayout) { + const stringTemplate = isSimpleLayout + ? templates.suggestionSimple + : templates.suggestion; + const template = Hogan.compile(stringTemplate); + return suggestion => template.render(suggestion); + } + + handleSelected(input, event, suggestion, datasetNumber, context = {}) { + // Do nothing if click on the suggestion, as it's already a , the + // browser will take care of it. This allow Ctrl-Clicking on results and not + // having the main window being redirected as well + if (context.selectionMethod === "click") { + return; + } + + input.setVal(""); + window.location.assign(suggestion.url); + } + + handleShown(input) { + const middleOfInput = input.offset().left + input.width() / 2; + let middleOfWindow = $(document).width() / 2; + + if (isNaN(middleOfWindow)) { + middleOfWindow = 900; + } + + const alignClass = + middleOfInput - middleOfWindow >= 0 + ? "algolia-autocomplete-right" + : "algolia-autocomplete-left"; + const otherAlignClass = + middleOfInput - middleOfWindow < 0 + ? "algolia-autocomplete-right" + : "algolia-autocomplete-left"; + const autocompleteWrapper = $(".algolia-autocomplete"); + if (!autocompleteWrapper.hasClass(alignClass)) { + autocompleteWrapper.addClass(alignClass); + } + + if (autocompleteWrapper.hasClass(otherAlignClass)) { + autocompleteWrapper.removeClass(otherAlignClass); + } + } +} + +export default DocSearch; \ No newline at end of file diff --git a/docs/src/theme/SearchBar/lib/lunar-search.js b/docs/src/theme/SearchBar/lib/lunar-search.js new file mode 100644 index 00000000..159b7950 --- /dev/null +++ b/docs/src/theme/SearchBar/lib/lunar-search.js @@ -0,0 +1,146 @@ +import lunr from "@generated/lunr.client"; +lunr.tokenizer.separator = /[\s\-/]+/; + +class LunrSearchAdapter { + constructor(searchDocs, searchIndex) { + this.searchDocs = searchDocs; + this.lunrIndex = lunr.Index.load(searchIndex); + } + + getLunrResult(input) { + return this.lunrIndex.query(function (query) { + const tokens = lunr.tokenizer(input); + query.term(tokens, { + boost: 10 + }); + query.term(tokens, { + wildcard: lunr.Query.wildcard.TRAILING + }); + }); + } + + getHit(doc, formattedTitle, formattedContent) { + return { + hierarchy: { + lvl0: doc.pageTitle || doc.title, + lvl1: doc.type === 0 ? null : doc.title + }, + url: doc.url, + _snippetResult: formattedContent ? { + content: { + value: formattedContent, + matchLevel: "full" + } + } : null, + _highlightResult: { + hierarchy: { + lvl0: { + value: doc.type === 0 ? formattedTitle || doc.title : doc.pageTitle, + }, + lvl1: + doc.type === 0 + ? null + : { + value: formattedTitle || doc.title + } + } + } + }; + } + getTitleHit(doc, position, length) { + const start = position[0]; + const end = position[0] + length; + let formattedTitle = doc.title.substring(0, start) + '' + doc.title.substring(start, end) + '' + doc.title.substring(end, doc.title.length); + return this.getHit(doc, formattedTitle) + } + + getKeywordHit(doc, position, length) { + const start = position[0]; + const end = position[0] + length; + let formattedTitle = doc.title + '
Keywords: ' + doc.keywords.substring(0, start) + '' + doc.keywords.substring(start, end) + '' + doc.keywords.substring(end, doc.keywords.length) + '' + return this.getHit(doc, formattedTitle) + } + + getContentHit(doc, position) { + const start = position[0]; + const end = position[0] + position[1]; + let previewStart = start; + let previewEnd = end; + let ellipsesBefore = true; + let ellipsesAfter = true; + for (let k = 0; k < 3; k++) { + const nextSpace = doc.content.lastIndexOf(' ', previewStart - 2); + const nextDot = doc.content.lastIndexOf('.', previewStart - 2); + if ((nextDot > 0) && (nextDot > nextSpace)) { + previewStart = nextDot + 1; + ellipsesBefore = false; + break; + } + if (nextSpace < 0) { + previewStart = 0; + ellipsesBefore = false; + break; + } + previewStart = nextSpace + 1; + } + for (let k = 0; k < 10; k++) { + const nextSpace = doc.content.indexOf(' ', previewEnd + 1); + const nextDot = doc.content.indexOf('.', previewEnd + 1); + if ((nextDot > 0) && (nextDot < nextSpace)) { + previewEnd = nextDot; + ellipsesAfter = false; + break; + } + if (nextSpace < 0) { + previewEnd = doc.content.length; + ellipsesAfter = false; + break; + } + previewEnd = nextSpace; + } + let preview = doc.content.substring(previewStart, start); + if (ellipsesBefore) { + preview = '... ' + preview; + } + preview += '' + doc.content.substring(start, end) + ''; + preview += doc.content.substring(end, previewEnd); + if (ellipsesAfter) { + preview += ' ...'; + } + return this.getHit(doc, null, preview); + + } + search(input) { + return new Promise((resolve, rej) => { + const results = this.getLunrResult(input); + const hits = []; + results.length > 5 && (results.length = 5); + this.titleHitsRes = [] + this.contentHitsRes = [] + results.forEach(result => { + const doc = this.searchDocs[result.ref]; + const { metadata } = result.matchData; + for (let i in metadata) { + if (metadata[i].title) { + if (!this.titleHitsRes.includes(result.ref)) { + const position = metadata[i].title.position[0] + hits.push(this.getTitleHit(doc, position, input.length)); + this.titleHitsRes.push(result.ref); + } + } else if (metadata[i].content) { + const position = metadata[i].content.position[0] + hits.push(this.getContentHit(doc, position)) + } else if (metadata[i].keywords) { + const position = metadata[i].keywords.position[0] + hits.push(this.getKeywordHit(doc, position, input.length)); + this.titleHitsRes.push(result.ref); + } + } + }); + hits.length > 5 && (hits.length = 5); + resolve(hits); + }); + } +} + +export default LunrSearchAdapter; diff --git a/docs/src/theme/SearchBar/lib/templates.js b/docs/src/theme/SearchBar/lib/templates.js new file mode 100644 index 00000000..c7ea998f --- /dev/null +++ b/docs/src/theme/SearchBar/lib/templates.js @@ -0,0 +1,114 @@ +const prefix = 'algolia-docsearch'; +const suggestionPrefix = `${prefix}-suggestion`; +const footerPrefix = `${prefix}-footer`; + +/* eslint-disable max-len */ + +const templates = { + suggestion: ` +
+
+ {{{category}}} +
+
+
+ {{{subcategory}}} +
+ {{#isTextOrSubcategoryNonEmpty}} +
+
{{{subcategory}}}
+
{{{title}}}
+ {{#text}}
{{{text}}}
{{/text}} +
+ {{/isTextOrSubcategoryNonEmpty}} +
+
+ `, + suggestionSimple: ` +
+
+ {{^isLvl0}} + {{{category}}} + {{^isLvl1}} + {{^isLvl1EmptyOrDuplicate}} + + {{{subcategory}}} + + {{/isLvl1EmptyOrDuplicate}} + {{/isLvl1}} + {{/isLvl0}} +
+ {{#isLvl2}} + {{{title}}} + {{/isLvl2}} + {{#isLvl1}} + {{{subcategory}}} + {{/isLvl1}} + {{#isLvl0}} + {{{category}}} + {{/isLvl0}} +
+
+
+ {{#text}} +
+
{{{text}}}
+
+ {{/text}} +
+
+ `, + footer: ` +
+
+ `, + empty: ` +
+
+
+
+
+ No results found for query "{{query}}" +
+
+
+
+
+ `, + searchBox: ` + + + + `, +}; + +export default templates; diff --git a/docs/src/theme/SearchBar/lib/utils.js b/docs/src/theme/SearchBar/lib/utils.js new file mode 100644 index 00000000..0807500d --- /dev/null +++ b/docs/src/theme/SearchBar/lib/utils.js @@ -0,0 +1,270 @@ +import $ from "autocomplete.js/zepto"; + +const utils = { + /* + * Move the content of an object key one level higher. + * eg. + * { + * name: 'My name', + * hierarchy: { + * lvl0: 'Foo', + * lvl1: 'Bar' + * } + * } + * Will be converted to + * { + * name: 'My name', + * lvl0: 'Foo', + * lvl1: 'Bar' + * } + * @param {Object} object Main object + * @param {String} property Main object key to move up + * @return {Object} + * @throws Error when key is not an attribute of Object or is not an object itself + */ + mergeKeyWithParent(object, property) { + if (object[property] === undefined) { + return object; + } + if (typeof object[property] !== 'object') { + return object; + } + const newObject = $.extend({}, object, object[property]); + delete newObject[property]; + return newObject; + }, + /* + * Group all objects of a collection by the value of the specified attribute + * If the attribute is a string, use the lowercase form. + * + * eg. + * groupBy([ + * {name: 'Tim', category: 'dev'}, + * {name: 'Vincent', category: 'dev'}, + * {name: 'Ben', category: 'sales'}, + * {name: 'Jeremy', category: 'sales'}, + * {name: 'AlexS', category: 'dev'}, + * {name: 'AlexK', category: 'sales'} + * ], 'category'); + * => + * { + * 'devs': [ + * {name: 'Tim', category: 'dev'}, + * {name: 'Vincent', category: 'dev'}, + * {name: 'AlexS', category: 'dev'} + * ], + * 'sales': [ + * {name: 'Ben', category: 'sales'}, + * {name: 'Jeremy', category: 'sales'}, + * {name: 'AlexK', category: 'sales'} + * ] + * } + * @param {array} collection Array of objects to group + * @param {String} property The attribute on which apply the grouping + * @return {array} + * @throws Error when one of the element does not have the specified property + */ + groupBy(collection, property) { + const newCollection = {}; + $.each(collection, (index, item) => { + if (item[property] === undefined) { + throw new Error(`[groupBy]: Object has no key ${property}`); + } + let key = item[property]; + if (typeof key === 'string') { + key = key.toLowerCase(); + } + // fix #171 the given data type of docsearch hits might be conflict with the properties of the native Object, + // such as the constructor, so we need to do this check. + if (!Object.prototype.hasOwnProperty.call(newCollection, key)) { + newCollection[key] = []; + } + newCollection[key].push(item); + }); + return newCollection; + }, + /* + * Return an array of all the values of the specified object + * eg. + * values({ + * foo: 42, + * bar: true, + * baz: 'yep' + * }) + * => + * [42, true, yep] + * @param {object} object Object to extract values from + * @return {array} + */ + values(object) { + return Object.keys(object).map(key => object[key]); + }, + /* + * Flattens an array + * eg. + * flatten([1, 2, [3, 4], [5, 6]]) + * => + * [1, 2, 3, 4, 5, 6] + * @param {array} array Array to flatten + * @return {array} + */ + flatten(array) { + const results = []; + array.forEach(value => { + if (!Array.isArray(value)) { + results.push(value); + return; + } + value.forEach(subvalue => { + results.push(subvalue); + }); + }); + return results; + }, + /* + * Flatten all values of an object into an array, marking each first element of + * each group with a specific flag + * eg. + * flattenAndFlagFirst({ + * 'devs': [ + * {name: 'Tim', category: 'dev'}, + * {name: 'Vincent', category: 'dev'}, + * {name: 'AlexS', category: 'dev'} + * ], + * 'sales': [ + * {name: 'Ben', category: 'sales'}, + * {name: 'Jeremy', category: 'sales'}, + * {name: 'AlexK', category: 'sales'} + * ] + * , 'isTop'); + * => + * [ + * {name: 'Tim', category: 'dev', isTop: true}, + * {name: 'Vincent', category: 'dev', isTop: false}, + * {name: 'AlexS', category: 'dev', isTop: false}, + * {name: 'Ben', category: 'sales', isTop: true}, + * {name: 'Jeremy', category: 'sales', isTop: false}, + * {name: 'AlexK', category: 'sales', isTop: false} + * ] + * @param {object} object Object to flatten + * @param {string} flag Flag to set to true on first element of each group + * @return {array} + */ + flattenAndFlagFirst(object, flag) { + const values = this.values(object).map(collection => + collection.map((item, index) => { + // eslint-disable-next-line no-param-reassign + item[flag] = index === 0; + return item; + }) + ); + return this.flatten(values); + }, + /* + * Removes all empty strings, null, false and undefined elements array + * eg. + * compact([42, false, null, undefined, '', [], 'foo']); + * => + * [42, [], 'foo'] + * @param {array} array Array to compact + * @return {array} + */ + compact(array) { + const results = []; + array.forEach(value => { + if (!value) { + return; + } + results.push(value); + }); + return results; + }, + /* + * Returns the highlighted value of the specified key in the specified object. + * If no highlighted value is available, will return the key value directly + * eg. + * getHighlightedValue({ + * _highlightResult: { + * text: { + * value: 'foo' + * } + * }, + * text: 'foo' + * }, 'text'); + * => + * 'foo' + * @param {object} object Hit object returned by the Algolia API + * @param {string} property Object key to look for + * @return {string} + **/ + getHighlightedValue(object, property) { + if ( + object._highlightResult && + object._highlightResult.hierarchy_camel && + object._highlightResult.hierarchy_camel[property] && + object._highlightResult.hierarchy_camel[property].matchLevel && + object._highlightResult.hierarchy_camel[property].matchLevel !== 'none' && + object._highlightResult.hierarchy_camel[property].value + ) { + return object._highlightResult.hierarchy_camel[property].value; + } + if ( + object._highlightResult && + object._highlightResult && + object._highlightResult[property] && + object._highlightResult[property].value + ) { + return object._highlightResult[property].value; + } + return object[property]; + }, + /* + * Returns the snippeted value of the specified key in the specified object. + * If no highlighted value is available, will return the key value directly. + * Will add starting and ending ellipsis (…) if we detect that a sentence is + * incomplete + * eg. + * getSnippetedValue({ + * _snippetResult: { + * text: { + * value: 'This is an unfinished sentence' + * } + * }, + * text: 'This is an unfinished sentence' + * }, 'text'); + * => + * 'This is an unfinished sentence…' + * @param {object} object Hit object returned by the Algolia API + * @param {string} property Object key to look for + * @return {string} + **/ + getSnippetedValue(object, property) { + if ( + !object._snippetResult || + !object._snippetResult[property] || + !object._snippetResult[property].value + ) { + return object[property]; + } + let snippet = object._snippetResult[property].value; + + if (snippet[0] !== snippet[0].toUpperCase()) { + snippet = `…${snippet}`; + } + if (['.', '!', '?'].indexOf(snippet[snippet.length - 1]) === -1) { + snippet = `${snippet}…`; + } + return snippet; + }, + /* + * Deep clone an object. + * Note: This will not clone functions and dates + * @param {object} object Object to clone + * @return {object} + */ + deepClone(object) { + return JSON.parse(JSON.stringify(object)); + }, +}; + +export default utils; diff --git a/docs/src/theme/SearchBar/styles.css b/docs/src/theme/SearchBar/styles.css new file mode 100644 index 00000000..047517ec --- /dev/null +++ b/docs/src/theme/SearchBar/styles.css @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.search-icon { + background-image: var(--ifm-navbar-search-input-icon); + height: auto; + width: 24px; + cursor: pointer; + padding: 8px; + line-height: 32px; + background-repeat: no-repeat; + background-position: center; + display: none; +} + +.search-icon-hidden { + visibility: hidden; +} + +@media (max-width: 360px) { + .search-bar { + width: 0 !important; + background: none !important; + padding: 0 !important; + transition: none !important; + } + + .search-bar-expanded { + width: 9rem !important; + } + + .search-icon { + display: inline; + vertical-align: sub; + } +}