- {logo && logo.src &&
diff --git a/src/theme/Search/index.js b/src/theme/Search/index.js
new file mode 100644
index 0000000000..ea8ddd2a78
--- /dev/null
+++ b/src/theme/Search/index.js
@@ -0,0 +1,196 @@
+import React, {useEffect} from 'react'
+import RedisSiteSearch from '@abrookins-redis/redis-sitesearch-frontend/dist/redis-sitesearch.esm'
+
+const SEARCH_API_URL = "https://search-service.redislabs.com/search"
+const SEARCH_SITE = "https://developer.redislabs.com"
+const THIRTY_SECONDS = 30000
+const SEARCH_LOGO = '
'
+
+
+function escapeHtml(str) {
+ return str.replace(/[&<>"'\/]/g, function (s) {
+ var entityMap = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': '"',
+ "'": ''',
+ "/": '/'
+ };
+
+ return entityMap[s];
+ });
+}
+
+function setWithExpiry(key, value, ttl) {
+ const now = new Date()
+
+ const item = {
+ value: value,
+ expiry: now.getTime() + ttl,
+ }
+ localStorage.setItem(key, JSON.stringify(item))
+}
+
+
+function getWithExpiry(key) {
+ const itemStr = localStorage.getItem(key)
+ if (!itemStr) {
+ return null
+ }
+ const item = JSON.parse(itemStr)
+ const now = new Date()
+
+ if (now.getTime() > item.expiry) {
+ localStorage.removeItem(key)
+ return null
+ }
+
+ return item.value
+}
+
+
+function Search(props) {
+ var initialized = false;
+
+ useEffect(function onFirstMount() {
+ const searchLogo = document.querySelector('.redisearch-logo'),
+ searchDiv = document.querySelector("#redis-sitesearch")
+ var lastQuery
+
+ initialized = true;
+
+ searchLogo.addEventListener('mousedown', (e) => {
+ e.preventDefault()
+ })
+
+ searchDiv.addEventListener("mousedown", (e) => {
+ })
+
+ new RedisSiteSearch('#redis-sitesearch', {
+ debounceTime: 2,
+ zIndex: 100,
+
+ search: input => {
+ const trimmedInput = input.trim()
+ const url = `${SEARCH_API_URL}?q=${trimmedInput}*&site=${SEARCH_SITE}`
+
+ if (input.length === 0) {
+ return []
+ }
+
+ try { xhr.abort(); } catch(e){}
+
+ // Save the query so we can append it to a selected result URL later.
+ // We use this to track search queries.
+ lastQuery = trimmedInput
+
+ const cachedResults = getWithExpiry(url)
+
+ if (cachedResults) {
+ return cachedResults
+ }
+
+ return new Promise(resolve => {
+ fetch(url)
+ .catch(function(err) {
+ console.error('Error querying search API: ', err)
+ resolve([])
+ })
+ .then(function(response) {
+ if (response === undefined || !response.ok) {
+ console.error('Error querying search API: ', response)
+ resolve([])
+ return
+ }
+ return response.json()
+ })
+ .then(function(data) {
+ if (data === undefined || !data) {
+ return
+ }
+ // Push a fake 'no results' document if there were no results.
+ var results
+ if (!data.results.length) {
+ const safeInput = escapeHtml(trimmedInput)
+ results = [
+ {
+ title: 'No results',
+ section_title: `No results found for '${safeInput}'`,
+ body: '',
+ hierarchy: [''],
+ },
+ ]
+ } else {
+ results = data.results
+ }
+
+ setWithExpiry(url, results, THIRTY_SECONDS)
+ resolve(results)
+ })
+ })
+ },
+
+ renderResult: (result, props) => {
+ let sectionTitle = "",
+ hasSectionTitle = result.section_title !== ""
+
+ if (hasSectionTitle) {
+ sectionTitle = `
+
+ ${result.section_title}
+
+ `
+ }
+
+ return `
+
+
+
+ ${sectionTitle}
+
+ ${result.body}
+
+
+
+ `
+ },
+
+ getResultValue: result => "",
+
+ // Open the selected article in
+ // a new window
+ onSubmit: result => {
+ if (result) {
+ lastQuery = encodeURIComponent(lastQuery)
+ window.open(`${result.url}?s=${lastQuery}`, "_top")
+ }
+ },
+
+ onUpdate: (results, selectedIndex) => {
+ const redisearchLogo = document.querySelector('.redisearch-logo')
+ if (results.length) {
+ redisearchLogo.innerHTML = SEARCH_LOGO
+ } else {
+ redisearchLogo.innerHTML = ""
+ }
+ }
+ })
+ }, [initialized])
+
+ return
+}
+
+export default Search;
diff --git a/static/css/redis-sitesearch-overrides.css b/static/css/redis-sitesearch-overrides.css
new file mode 100644
index 0000000000..29608333d6
--- /dev/null
+++ b/static/css/redis-sitesearch-overrides.css
@@ -0,0 +1,106 @@
+.powered-by-redisearch {
+ background-image: url('/img/redisearch.png');
+ background-size: 173px 29px
+ }
+
+ .redis-sitesearch {
+ max-width: 700px;
+ min-width: 350px
+ }
+
+ .redis-sitesearch-input {
+ padding: 5px 5px 5px 48px
+ }
+
+ redis-sitesearch-result-list-wrapper {
+ width: 400px
+ }
+
+ .redis-sitesearch-inline {
+ margin-bottom: 0
+ }
+
+
+ .redis-sitesearch-inline .search-left {
+ width: 35%;
+ }
+
+ .redis-sitesearch-inline .search-left {
+ width: 35%;
+ }
+
+ .redis-sitesearch-inline .search-right {
+ width: 65%;
+ }
+
+ .redis-sitesearch-inline .search-root-item {
+ padding: 12px 16px 0px;
+ }
+
+ .redis-sitesearch-inline .redis-sitesearch-result {
+ padding: 11px 1em 11px 11px;
+ }
+
+
+
+ .redis-sitesearch .redis-sitesearch-result-list {
+ max-height: 60vh;
+ }
+
+ .redis-sitesearch-inline .redis-sitesearch-result-list {
+ max-height: 70vh;
+ }
+
+ @media screen and (max-width: 376px) {
+ .redis-sitesearch .redis-sitesearch-result-list {
+ max-height: 40vh;
+ }
+
+ .redis-sitesearch-inline .redis-sitesearch-result-list {
+ max-height: 70vh;
+ }
+
+ .search-left {
+ width: 30%;
+ }
+
+ .search-right {
+ width: 70%;
+ }
+
+ .redis-sitesearch-result {
+ padding: 9px 1.5em 9px 9px;
+ }
+
+ .redis-sitesearch {
+ min-width: 300px;
+ }
+
+ }
+
+ @media screen and (max-width: 608px) {
+ .redis-sitesearch {
+ min-width: 100%;
+ margin-right: 15px;
+ margin-left: 15px;
+ }
+ }
+
+
+
+
+ @media screen and (max-height: 750px) {
+ .redis-sitesearch .redis-sitesearch-result-list {
+ max-height: 50vh;
+ }
+
+ .redis-sitesearch-inline .redis-sitesearch-result-list {
+ max-height: 70vh;
+ }
+
+ .redis-sitesearch {
+ margin-top: 10px;
+ min-width: 300px;
+ margin-right: 15px
+ }
+ }
diff --git a/static/css/redis-sitesearch.css b/static/css/redis-sitesearch.css
new file mode 100644
index 0000000000..3ea908c11c
--- /dev/null
+++ b/static/css/redis-sitesearch.css
@@ -0,0 +1 @@
+.redis-sitesearch-input{border:1px solid #eee;border-radius:8px;width:100%;padding:12px 12px 12px 48px;box-sizing:border-box;position:relative;font-size:16px;line-height:1.5;flex:1;background-color:#eee;background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjY2IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGNpcmNsZSBjeD0iMTEiIGN5PSIxMSIgcj0iOCIvPjxwYXRoIGQ9Ik0yMSAyMWwtNC00Ii8+PC9zdmc+");background-repeat:no-repeat;background-position:12px;z-index:100}.redis-sitesearch-input:focus,.redis-sitesearch-input[aria-expanded=true]{border-color:rgba(0,0,0,.12);background-color:#fff;outline:none;box-shadow:0 2px 2px rgba(0,0,0,.16)}[data-position=below] .redis-sitesearch-input[aria-expanded=true]{border-bottom-color:transparent;border-radius:8px 8px 0 0}[data-position=above] .redis-sitesearch-input[aria-expanded=true]{border-top-color:transparent;border-radius:0 0 8px 8px;z-index:2}.redis-sitesearch[data-loading=true]:after{content:"";border:3px solid rgba(0,0,0,.12);border-right-color:rgba(0,0,0,.48);border-radius:100%;width:20px;height:20px;position:absolute;right:12px;top:50%;transform:translateY(-50%);animation:rotate 1s linear infinite}.redis-sitesearch-result-list{margin:0;border:1px solid rgba(0,0,0,.12);padding:0;box-sizing:border-box;max-height:296px;overflow-y:auto;background:#fff;list-style:none;box-shadow:0 2px 2px rgba(0,0,0,.16)}[data-position=below] .redis-sitesearch-result-list{margin-top:-1px;border-top-color:transparent;border-radius:0 0 8px 8px;padding-bottom:8px}[data-position=above] .redis-sitesearch-result-list{margin-bottom:-1px;border-bottom-color:transparent;border-radius:8px 8px 0 0;padding-top:8px}.redis-sitesearch-result{cursor:default;background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjY2NjIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PGNpcmNsZSBjeD0iMTEiIGN5PSIxMSIgcj0iOCIvPjxwYXRoIGQ9Ik0yMSAyMWwtNC00Ii8+PC9zdmc+");background-repeat:no-repeat;background-position:12px;width:100%;padding:11px 2.1em 11px 11px;background:transparent;display:flex;flex-wrap:wrap;justify-content:space-between}.redis-sitesearch-result:hover,.redis-sitesearch-result[aria-selected=true]{background-color:rgba(0,0,0,.06)}@keyframes rotate{0%{transform:translateY(-50%) rotate(0deg)}to{transform:translateY(-50%) rotate(359deg)}}.redis-sitesearch-result-list{display:flex;position:relative;flex-wrap:wrap;justify-content:space-between}.redis-sitesearch-result-list-wrapper .redis-sitesearch-result-list{padding-bottom:60px}.redis-sitesearch-home{max-width:700px;margin:0 auto}.redis-sitesearch-inline{max-width:100%;margin-bottom:1rem}.redis-sitesearch-input-inline{padding:6px 6px 6px 48px}.redis-sitesearch-result-list li:last-of-type{padding-bottom:80px!important}.search-root{font-size:14px;font-weight:700;width:100%}.search-root-item{margin-bottom:0;border:0;cursor:default;padding:16px 16px 8px;border-top:1px solid #eee;width:100%}.search-root-item::first{border:0}.no-section-title{margin-top:0}.search-title{font-size:14px;margin-bottom:10px;color:#5961ff;word-break:break-word;text-align:right;width:100%}.search-left{width:25%;padding-right:5px}.search-right{width:70%;padding-left:15px}.search-section-title{font-size:16px;margin-bottom:8px;width:100%}.search-body{font-size:13px;width:100%}.powered-by-redisearch{background-image:url(../images/redisearch.png);background-repeat:no-repeat;background-position:95% 60%;position:absolute;right:0;width:100%;background-color:#fff;bottom:0;height:60px;border:1px solid rgba(0,0,0,.12);box-shadow:0 2px 2px rgba(0,0,0,.16)}
diff --git a/static/img/redisearch.png b/static/img/redisearch.png
new file mode 100644
index 0000000000..861892d7f0
Binary files /dev/null and b/static/img/redisearch.png differ
diff --git a/yarn.lock b/yarn.lock
index 2c438481f4..2c44ffec9e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,11 @@
# yarn lockfile v1
+"@abrookins-redis/redis-sitesearch-frontend@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@abrookins-redis/redis-sitesearch-frontend/-/redis-sitesearch-frontend-1.0.2.tgz#f4b8f8386c27cb47293987b733b980fca1dc55c7"
+ integrity sha512-aDSUvffA1mbfb93DZT2o9nGLv3D5RgY0eNSSL4FUZFJvSBq3Kx+PWw8/cPNyzzemFXgDMgaxzAKaWofH30F3sA==
+
"@algolia/autocomplete-core@^1.0.0-alpha.35":
version "1.0.0-alpha.37"
resolved "https://registry.npm.taobao.org/@algolia/autocomplete-core/download/@algolia/autocomplete-core-1.0.0-alpha.37.tgz#d41a6561b38761c4967ff7aa3e1d5d0aa3ceb31c"