diff --git a/.eslintrc.js b/.eslintrc.js
index f52a89f63..81367f4ad 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -24,7 +24,10 @@ module.exports = {
'algolia/func-style-toplevel': 'error',
'no-console': 'off',
+ 'no-continue': 'off',
+ 'no-loop-func': 'off',
'consistent-return': 'off',
+ '@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/explicit-member-accessibility': [
'error',
diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index 0bd9d0482..aeaeae3d7 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -77,23 +77,21 @@ You can find an HTML code snippet in the [Crawler Admin Console](https://crawler
```html
-
+
```
-This code automatically adds a search autocomplete widget on your website on all ` ` tags, using your newly created Algolia index.
+This code automatically creates a new input in the specified `selector` with a ready to use autocomplete, using your newly created Algolia index.
Please refer to the [full documentation](https://github.com/algolia/algoliasearch-netlify/tree/master/frontend) to configure this front-end plugin.
diff --git a/docs/screenshots/frontend/dark-theme.png b/docs/screenshots/frontend/dark-theme.png
new file mode 100644
index 000000000..e0851cfd7
Binary files /dev/null and b/docs/screenshots/frontend/dark-theme.png differ
diff --git a/docs/screenshots/frontend/normal-theme.png b/docs/screenshots/frontend/normal-theme.png
new file mode 100644
index 000000000..1e2fb288c
Binary files /dev/null and b/docs/screenshots/frontend/normal-theme.png differ
diff --git a/frontend/CONTRIBUTING.md b/frontend/CONTRIBUTING.md
new file mode 100644
index 000000000..b6c79db6a
--- /dev/null
+++ b/frontend/CONTRIBUTING.md
@@ -0,0 +1,23 @@
+# Contributing
+
+## Scripts
+
+- `yarn dev`: run dev environment
+- `yarn release`: build & publish the library
+
+## Development
+
+From this folder:
+
+```sh
+yarn dev
+```
+
+Or from the root of the repository:
+
+```sh
+yarn dev:frontend
+```
+
+This runs a `webpack-dev-server` on port 9100.
+Meant to be used in conjunction with the [test website](../public/).
diff --git a/frontend/README.md b/frontend/README.md
index 7baa394b1..54a5069ff 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -2,26 +2,28 @@
`algoliasearch-netlify-frontend` is the front-end bundle we recommend to use with our Netlify plugin.
It's designed to be compatible with the index structure extracted by the [plugin](../plugin).
-It enhances existing search inputs in your website with an autocomplete menu providing search as you type results.
+It **creates a new search input** in your website with an autocomplete menu providing search as you type results.
## Usage
```html
-
-
+
+
```
+
+
+
+
## Available options
Here's the full list of options with their default value.
@@ -33,17 +35,22 @@ algoliasearchNetlify({
apiKey: '', // Search api key (Can be found in https://www.algolia.com/api-keys)
siteId: '', // Netlify Site ID (Can be found in https://crawler.algolia.com/admin/netlify)
branch: '', // Target git branch, either a fixed one (e.g. 'master') or a dynamic one using `process.env.HEAD`. See "Using Multiple branches" in this doc.
+ selector: 'div#search', // Where the autocomplete will be spawned (should not be an input)
// Optional
analytics: true, // Enable search analytics
- autocomplete: {
- hitsPerPage: 5, // Amount of results to display
- inputSelector: 'input[type=search]', // CSS selector of your search input(s)
- },
- color: '#3c4fe0', // Main color
- debug: false, // Debug mode (keeps the autocomplete open)
- silenceWarnings: false, // Disable warnings (e.g. no search input found)
+ hitsPerPage: 5, // Amount of results to display
poweredBy: true, // Controls displaying the logo (mandatory with our FREE plan)
+ placeholder: 'Search...'; // Input placeholder
+ openOnFocus: true; // Open search panel with default search, when focusing input
+
+ // Theme
+ theme: {
+ mark: '#fff', // Color of the matching content
+ background: '#23263b', // Background Color of the input and the panel
+ selected: '#111432', // Background Color of the selected item
+ text: '#d6d6e7' // Color of the title of the items
+ }
});
```
@@ -70,24 +77,26 @@ algoliasearchNetlify({
});
```
-## Scripts
-
-- `yarn dev`: run dev environment
-- `yarn release`: build & publish the library
+## Theme
-## Development
+You can theme the input and the autocomplete by using the `theme` property.
-From this folder:
-
-```sh
-yarn dev
+```js
+// Example of dark theme:
+{
+ theme: {
+ mark: '#fff',
+ background: '#23263b',
+ selected: '#111432',
+ text: '#d6d6e7'
+ }
+}
```
-Or from the root of the repository:
+
-```sh
-yarn dev:frontend
-```
+To go further you should take a look at the [autocomplete.js documentation](https://algolia-autocomplete.netlify.app/), or implement your own search with [InstantSearch.js](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/).
+
+## Development & Release
-This runs a `webpack-dev-server` on port 9100.
-Meant to be used in conjunction with the [test website](../public/).
+See [CONTRIBUTING.md](./CONTRIBUTING.md).
diff --git a/frontend/package.json b/frontend/package.json
index 1c0dc21c8..ee27e06c0 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -18,11 +18,12 @@
"postinstall": "[ -d dist/ ] || npm run build"
},
"devDependencies": {
+ "@algolia/autocomplete-js": "1.0.0-alpha.35",
+ "@algolia/autocomplete-preset-algolia": "1.0.0-alpha.29",
"@algolia/transporter": "4.6.0",
"@babel/core": "7.12.9",
"@babel/preset-env": "7.12.7",
"algoliasearch": "4.6.0",
- "autocomplete.js": "0.38.0",
"babel-loader": "8.2.2",
"clean-webpack-plugin": "3.0.0",
"core-js": "3.8.0",
@@ -37,9 +38,9 @@
"sass-loader": "10.1.0",
"terser-webpack-plugin": "4.2.3",
"ts-loader": "8.0.11",
- "webpack": "4.44.2",
"webpack-cli": "4.2.0",
- "webpack-dev-server": "3.11.0"
+ "webpack-dev-server": "3.11.0",
+ "webpack": "4.44.2"
},
"keywords": [
"algolia",
diff --git a/frontend/src/AlgoliasearchNetlify.ts b/frontend/src/AlgoliasearchNetlify.ts
index dae9bd481..5cb6c3962 100644
--- a/frontend/src/AlgoliasearchNetlify.ts
+++ b/frontend/src/AlgoliasearchNetlify.ts
@@ -1,66 +1,51 @@
import { AutocompleteWrapper } from './AutocompleteWrapper';
-import { Options } from './options';
+import type { Options } from './types';
const defaultOptions: Omit<
Options,
- 'appId' | 'apiKey' | 'indexName' | 'siteId' | 'branch'
+ 'appId' | 'apiKey' | 'selector' | 'siteId' | 'branch'
> = {
analytics: true,
- autocomplete: {
- hitsPerPage: 5,
- inputSelector: 'input[type=search]',
- },
- color: '#3c4fe0',
+ hitsPerPage: 5,
debug: false,
- silenceWarnings: false,
poweredBy: true,
+ placeholder: 'Search...',
+ openOnFocus: false,
};
-class AlgoliasearchNetlify {
- static instances: AlgoliasearchNetlify[];
-
- search: AutocompleteWrapper;
-
- constructor(_options: Options) {
- AlgoliasearchNetlify.instances.push(this);
-
- // Temporary
- const splitIndexName = (
- indexName: string
- ): { siteId: string; branch: string } => {
- const regexp = /^netlify_([0-9a-f-]+)_(.*)_all$/;
- const [, siteId, branch] = indexName.match(regexp)!;
- return { siteId, branch };
- };
-
- // eslint-disable-next-line no-warning-comments
- // TODO: add validation
- const options = {
- ...defaultOptions,
- ..._options,
- ...(_options.indexName && splitIndexName(_options.indexName)), // Temporary
- autocomplete: {
- ...defaultOptions.autocomplete,
- ..._options.autocomplete,
- },
- };
-
- this.search = new AutocompleteWrapper(options);
-
- // Wait for DOM initialization, then render
- const render = this.render.bind(this, options);
- if (['complete', 'interactive'].includes(document.readyState)) {
- render();
- } else {
- document.addEventListener('DOMContentLoaded', render);
- }
+const mandatory: Array = [
+ 'appId',
+ 'apiKey',
+ 'selector',
+ 'siteId',
+ 'branch',
+];
+
+const instances: AutocompleteWrapper[] = [];
+
+function algoliasearchNetlify(_options: Options) {
+ const options = {
+ ...defaultOptions,
+ ..._options,
+ };
+ for (const key of mandatory) {
+ if (options[key]) continue;
+
+ throw new Error(`[algoliasearch-netlify] Missing mandatory key: ${key}`);
}
- render(options: Options) {
- this.search.render(options);
+ const autocomplete = new AutocompleteWrapper(options);
+ instances.push(autocomplete);
+
+ // Wait for DOM initialization, then render
+ const render = () => {
+ autocomplete.render();
+ };
+ if (['complete', 'interactive'].includes(document.readyState)) {
+ render();
+ } else {
+ document.addEventListener('DOMContentLoaded', render);
}
}
-AlgoliasearchNetlify.instances = [];
-
-export { AlgoliasearchNetlify };
+export { algoliasearchNetlify };
diff --git a/frontend/src/AutocompleteWrapper.ts b/frontend/src/AutocompleteWrapper.ts
index 1cf9bc548..cb8eb9f6f 100644
--- a/frontend/src/AutocompleteWrapper.ts
+++ b/frontend/src/AutocompleteWrapper.ts
@@ -1,126 +1,63 @@
-// Small hack to remove verticalAlign on the input
-// Makes IE11 fail though
-import { isMsie } from 'autocomplete.js/src/common/utils';
-if (!isMsie()) {
- const css = require('autocomplete.js/src/autocomplete/css.js');
- delete css.input.verticalAlign;
- delete css.inputWithNoHint.verticalAlign;
-}
-
-import type { SearchClient, SearchIndex } from 'algoliasearch/lite';
-import type { RequestOptions } from '@algolia/transporter';
-import type { Hit } from '@algolia/client-search';
+import type { SearchClient } from 'algoliasearch/lite';
import algoliasearch from 'algoliasearch/lite';
-import autocomplete from 'autocomplete.js';
+import type { Hit } from '@algolia/client-search';
+import {
+ autocomplete,
+ AutocompleteApi,
+ highlightHit,
+ snippetHit,
+ AutocompleteSource,
+} from '@algolia/autocomplete-js';
+import { getAlgoliaHits } from '@algolia/autocomplete-preset-algolia';
-import type { Options } from './options';
+import type { Options, AlgoliaRecord } from './types';
import { templates } from './templates';
-import { addCss } from './addCss';
// @ts-ignore
import { version } from '../package.json';
-import { Data } from './data';
-
-export type SizeModifier = null | 'xs' | 'sm';
-
-type AutocompleteJs = any;
-
-const XS_WIDTH = 400;
-const SM_WIDTH = 600;
class AutocompleteWrapper {
- // All fields are private because they're just here for debugging
- private client: SearchClient;
- private index: SearchIndex;
-
- private $inputs: HTMLInputElement[] = [];
- private autocompletes: AutocompleteJs[] = [];
-
- constructor({ appId, apiKey, siteId, branch }: Options) {
- this.client = this.createClient(appId, apiKey);
- const indexName = this.computeIndexName(siteId, branch);
- this.index = this.client.initIndex(indexName);
+ private options;
+ private indexName;
+ private client;
+ private autocomplete: AutocompleteApi | undefined;
+
+ constructor(options: Options) {
+ this.options = options;
+ this.client = this.createClient();
+ this.indexName = this.computeIndexName();
}
- render({
- analytics,
- autocomplete: { hitsPerPage, inputSelector },
- color,
- debug,
- silenceWarnings,
- poweredBy,
- }: Options) {
- addCss(templates.autocomplete.css(color));
-
- const $inputs = this.getInputs(inputSelector);
-
- if ($inputs.length === 0 && !silenceWarnings) {
- const inputSelectorText = JSON.stringify(inputSelector);
- console.warn(
- [
- `[Algolia] No input matched our default selector ${inputSelectorText}`,
- 'The integration needs a search input to be active on your page. You can either:',
- '- add an input that matches the selector',
- '- or modify `autocomplete.inputSelector` to match your search input.',
- ].join('\n')
+ render() {
+ const $input = document.querySelector(this.options.selector) as HTMLElement;
+ if (!$input) {
+ console.error(
+ `[algoliasearch-netlify] no element ${this.options.selector} found`
);
+ return;
}
- const autocompletes = $inputs.map(($input) => {
- const inputWidth = $input.getBoundingClientRect().width;
-
- const sizeModifier = this.computeSizeModifier(inputWidth);
- const nbSnippetWords = this.computeNbSnippetWords(inputWidth);
-
- const autocompleteParams = {
- hint: false,
- debug,
- templates: this.getDropdownTemplates(poweredBy),
- appendTo: 'body',
- };
-
- const searchParams = {
- analytics,
- hitsPerPage,
- highlightPreTag: '',
- highlightPostTag: ' ',
- attributesToSnippet: [
- `description:${nbSnippetWords}`,
- `content:${nbSnippetWords}`,
- ],
- snippetEllipsisText: '...',
- };
-
- const sources = [
- {
- name: 'hits',
- source: this.createSource(searchParams),
- templates: {
- suggestion: this.createRenderSuggestion(sizeModifier),
- },
- },
- ];
-
- const aa: AutocompleteJs = autocomplete(
- $input,
- autocompleteParams,
- sources
- );
-
- const handleSelected = this.createHandleSelected();
- aa.on('autocomplete:selected', handleSelected);
-
- return aa;
+ const instance = autocomplete({
+ container: $input,
+ autoFocus: false,
+ placeholder: this.options.placeholder,
+ debug: this.options.debug,
+ openOnFocus: this.options.openOnFocus,
+ panelPlacement: 'input-wrapper-width',
+ getSources: () => {
+ return [this.getSources()];
+ },
});
+ this.applyTheme($input.firstElementChild as HTMLElement);
- // Store debug variables
- this.$inputs = $inputs;
- this.autocompletes = autocompletes;
+ this.autocomplete = instance;
}
- private computeIndexName(siteId: string, branch: string): string {
+ private computeIndexName(): string {
+ const { siteId, branch } = this.options;
+
// Keep in sync with crawler code in /netlify/crawl
const cleanBranch = branch
.trim()
@@ -130,79 +67,82 @@ class AutocompleteWrapper {
return `netlify_${siteId}_${cleanBranch}_all`;
}
- private createClient(appId: string, apiKey: string): SearchClient {
- const client = algoliasearch(appId, apiKey);
+ private createClient(): SearchClient {
+ const client = algoliasearch(this.options.appId, this.options.apiKey);
client.addAlgoliaAgent(`Netlify integration ${version}`);
return client;
}
- private getInputs(inputSelector: string): HTMLInputElement[] {
- return Array.from(document.querySelectorAll(inputSelector));
- }
-
- private computeSizeModifier(inputWidth: number): SizeModifier | null {
- if (inputWidth < XS_WIDTH) return 'xs';
- if (inputWidth < SM_WIDTH) return 'sm';
- return null;
- }
-
- private computeNbSnippetWords(inputWidth: number): number {
- if (inputWidth < XS_WIDTH) return 0;
- if (inputWidth < SM_WIDTH) return 3 + Math.floor(inputWidth / 35);
- return Math.floor(inputWidth / 20);
- }
-
- private getDropdownTemplates(poweredBy: boolean): { footer?: string } {
- if (!poweredBy) return {};
- const { hostname } = window.location;
- const algoliaLogoHtml = templates.algolia(hostname);
+ private getSources(): AutocompleteSource {
+ const poweredBy = this.options.poweredBy;
return {
- ...(poweredBy && {
- footer: templates.autocomplete.poweredBy(algoliaLogoHtml),
- }),
- };
- }
-
- private createSource(searchParams: RequestOptions) {
- return (query: string, callback: (hits: Array>) => void) => {
- this.index
- .search('', { ...searchParams, query })
- .then((content) => {
- callback(content.hits);
+ getItems: ({ query }) => {
+ return getAlgoliaHits({
+ searchClient: this.client,
+ queries: [
+ {
+ indexName: this.indexName,
+ query,
+ params: {
+ analytics: this.options.analytics,
+ hitsPerPage: this.options.hitsPerPage,
+ },
+ },
+ ],
});
+ },
+ getItemUrl({ item }) {
+ return item.url;
+ },
+ templates: {
+ header() {
+ return;
+ },
+ item({ item }: { item: Hit }) {
+ return templates.item(
+ item,
+ highlightHit({ hit: item, attribute: 'title' }),
+ getSuggestionSnippet(item)
+ );
+ },
+ footer() {
+ if (poweredBy) {
+ return templates.poweredBy(window.location.host);
+ }
+ },
+ },
};
}
- private createRenderSuggestion(sizeModifier: SizeModifier) {
- return (hit: Hit): string => {
- return templates.autocomplete.suggestion({
- ...hit,
- sizeModifier,
- snippet: this.getSuggestionSnippet(hit),
- });
- };
- }
+ private applyTheme(el: HTMLElement | null) {
+ if (!el || !this.options.theme) {
+ return;
+ }
- private getSuggestionSnippet(hit: Hit): string {
- const description = hit._snippetResult?.description!;
- const content = hit._snippetResult?.content!;
- if (!description || !content) {
- if (description) return description.value;
- if (content) return content.value;
- return '';
+ const theme = this.options.theme;
+ if (theme.mark) {
+ el.style.setProperty('--color-mark', theme.mark);
+ }
+ if (theme.background) {
+ el.style.setProperty('--color-background', theme.background);
+ }
+ if (theme.text) {
+ el.style.setProperty('--color-text', theme.text);
+ }
+ if (theme.selected) {
+ el.style.setProperty('--color-selected', theme.selected);
}
- if (description.matchLevel === 'full') return description.value;
- if (content.matchLevel === 'full') return content.value;
- if (description.matchLevel === 'partial') return description.value;
- if (content.matchLevel === 'partial') return content.value;
- return description.value;
}
+}
- private createHandleSelected() {
- return (_event: any, suggestion: { url: string }) => {
- window.location.href = suggestion.url;
- };
+function getSuggestionSnippet(hit: Hit): string | null {
+ if (hit._snippetResult?.description) {
+ return snippetHit({ hit, attribute: 'description' });
+ }
+ if (hit._snippetResult?.content) {
+ return snippetHit({ hit, attribute: 'content' });
}
+ return hit.description || hit.content;
}
export { AutocompleteWrapper };
diff --git a/frontend/src/addCss.ts b/frontend/src/addCss.ts
deleted file mode 100644
index 31d80372d..000000000
--- a/frontend/src/addCss.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-export function addCss(css: string, $mainStyle: HTMLElement | null = null) {
- const $usedSibling =
- $mainStyle ??
- document.querySelector(
- 'link[rel=stylesheet][href*="algoliasearchNetlify"]'
- ) ??
- document.getElementsByTagName('head')[0].lastChild!;
- const $styleTag = document.createElement('style');
- $styleTag.setAttribute('type', 'text/css');
- $styleTag.appendChild(document.createTextNode(css));
- return $usedSibling.parentNode!.insertBefore(
- $styleTag,
- $usedSibling.nextSibling
- );
-}
diff --git a/frontend/src/data.ts b/frontend/src/data.ts
deleted file mode 100644
index 4130f8897..000000000
--- a/frontend/src/data.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export interface Data {
- url: string;
-
- title: string;
- description: string;
-
- content: string;
-}
diff --git a/frontend/src/escapeHTML.ts b/frontend/src/escapeHTML.ts
deleted file mode 100644
index ef37d3a8d..000000000
--- a/frontend/src/escapeHTML.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// See https://stackoverflow.com/questions/7381974/which-characters-need-to-be-escaped-in-html
-export function escapeHTML(str: string): string {
- return str
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
-}
diff --git a/frontend/src/index.scss b/frontend/src/index.scss
index a05ad91d5..4aa0d0519 100644
--- a/frontend/src/index.scss
+++ b/frontend/src/index.scss
@@ -1,127 +1,197 @@
-/* Search by */
-.aa-powered-by-link {
- display: inline-block;
- width: 64px;
- height: 18px;
- text-indent: 101%;
- overflow: hidden;
- white-space: nowrap;
- background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODUgMTIwIj48ZyBmaWxsPSJub25lIj48cGF0aCBmaWxsPSIjNTQ2OEZGIiBkPSJNMTYuOC0xLjAwMWg4OC40YzguNyAwIDE1LjggNy4wNjUgMTUuOCAxNS44djg4LjQwNWMwIDguNy03LjA2NSAxNS43OTUtMTUuOCAxNS43OTVIMTYuOGMtOC43IDAtMTUuOC03LjA2LTE1LjgtMTUuNzk1VjE0Ljc1OWMwLTguNjk1IDcuMDYtMTUuNzYgMTUuOC0xNS43NiIvPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03My41MDUgMjUuNzg4di00LjExNWE1LjIwOSA1LjIwOSAwIDAwLTUuMjEtNS4yMDVINTYuMTVhNS4yMDkgNS4yMDkgMCAwMC01LjIxIDUuMjA1djQuMjI1YzAgLjQ3LjQzNS44LjkxLjY5YTM3Ljk2NiAzNy45NjYgMCAwMTEwLjU3LTEuNDljMy40NjUgMCA2Ljg5NS40NyAxMC4yMSAxLjM4LjQ0LjExLjg3NS0uMjE1Ljg3NS0uNjlNNDAuMjIgMzEuMTczbC0yLjA3NS0yLjA3NWE1LjIwNiA1LjIwNiAwIDAwLTcuMzY1IDBsLTIuNDggMi40NzVhNS4xODUgNS4xODUgMCAwMDAgNy4zNTVsMi4wNCAyLjA0Yy4zMy4zMjUuODA1LjI1IDEuMDk1LS4wNzVhMzkuODc2IDM5Ljg3NiAwIDAxMy45NzUtNC42NiAzNy42OCAzNy42OCAwIDAxNC43LTRjLjM2NC0uMjIuNC0uNzMuMTEtMS4wNm0yMi4xNjQgMTMuMDY1djE3LjhjMCAuNTEuNTUuODc1IDEuMDIuNjJsMTUuODI1LTguMTljLjM2LS4xOC40Ny0uNjIuMjktLjk4LTMuMjgtNS43NTUtOS4zNy05LjY4NS0xNi40MDUtOS45NC0uMzY1IDAtLjczLjI5LS43My42OW0wIDQyLjg4Yy0xMy4xOTUgMC0yMy45MTUtMTAuNzA1LTIzLjkxNS0yMy44OCAwLTEzLjE3NSAxMC43Mi0yMy44NzUgMjMuOTE1LTIzLjg3NSAxMy4yIDAgMjMuOTE2IDEwLjcgMjMuOTE2IDIzLjg3NXMtMTAuNjggMjMuODgtMjMuOTE2IDIzLjg4bTAtNTcuOGMtMTguNzQgMC0zMy45NCAxNS4xOC0zMy45NCAzMy45MiAwIDE4Ljc0NSAxNS4yIDMzLjg5IDMzLjk0IDMzLjg5czMzLjk0LTE1LjE4IDMzLjk0LTMzLjkyNWMwLTE4Ljc0NS0xNS4xNjUtMzMuODg1LTMzLjk0LTMzLjg4NSIvPjxwYXRoIGZpbGw9IiM1NDY4RkYiIGQ9Ik0yNDAuMjE0IDk0LjE3N2MtMjMuMzY1LjExLTIzLjM2NS0xOC44NTUtMjMuMzY1LTIxLjg3NWwtLjA0LTcxLjA0NSAxNC4yNTQtMi4yNnY3MC42MWMwIDEuNzE1IDAgMTIuNTYgOS4xNSAxMi41OTV2MTEuOTc1aC4wMDF6bS01Ny43OC0xMS42MWM0LjM3NCAwIDcuNjItLjI1NSA5Ljg4LS42OVY2Ny4zOTJhMjkuMTk2IDI5LjE5NiAwIDAwLTMuNDMtLjY5NSAzMy43NDIgMzMuNzQyIDAgMDAtNC45NTYtLjM2NWMtMS41NyAwLTMuMTc1LjExLTQuNzc1LjM2NS0xLjYwNS4yMi0zLjA2NS42NTUtNC4zNCAxLjI3NS0xLjI3NS42Mi0yLjMzNSAxLjQ5NS0zLjEgMi42Mi0uOCAxLjEzLTEuMTY1IDEuNzg1LTEuMTY1IDMuNDk1IDAgMy4zNDUgMS4xNjUgNS4yOCAzLjI4IDYuNTUgMi4xMTUgMS4yNzUgNC45OTUgMS45MyA4LjYwNiAxLjkzem0tMS4yNC01MS42ODVjNC43IDAgOC42NzQuNTg1IDExLjg4NCAxLjc1IDMuMjA2IDEuMTY1IDUuNzk2IDIuOCA3LjY5IDQuODc1IDEuOTM1IDIuMTEgMy4yNDUgNC45MTUgNC4wNDYgNy45Ljg0IDIuOTg1IDEuMjQgNi4yNiAxLjI0IDkuODZ2MzYuNjJjLTIuMTg1LjQ3LTUuNTA2IDEuMDE1LTkuOTUgMS42Ny00LjQ0Ni42NTUtOS40NC45ODUtMTQuOTg2Ljk4NS0zLjY4IDAtNy4wNy0uMzY1LTEwLjA5NS0xLjA1NS0zLjA2NS0uNjktNS42NS0xLjgyLTcuODQtMy4zODUtMi4xNS0xLjU2NS0zLjgyNS0zLjU3LTUuMDY1LTYuMDQtMS4yMDUtMi40OC0xLjgyNS01Ljk3LTEuODI1LTkuNjEgMC0zLjQ5NS42OS01LjcxNSAyLjA0NS04LjEyIDEuMzgtMi40IDMuMjQtNC4zNjUgNS41NzUtNS44OTUgMi4zNy0xLjUzIDUuMDY1LTIuNjIgOC4xNjUtMy4yNzUgMy4xLS42NTUgNi4zNDUtLjk4NSA5LjY5NS0uOTg1IDEuNTcgMCAzLjIxLjExIDQuOTYuMjkgMS43MTUuMTg1IDMuNTc1LjUxNSA1LjU0NS45ODV2LTIuMzNjMC0xLjYzNS0uMTg1LTMuMi0uNTg1LTQuNjU1YTEwLjAxMiAxMC4wMTIgMCAwMC0yLjA0NS0zLjg5NWMtLjk4NS0xLjEzLTIuMjU1LTIuMDA1LTMuODYtMi42Mi0xLjYwNS0uNjItMy42NS0xLjA5NS02LjA5LTEuMDk1LTMuMjggMC02LjI3LjQtOS4wMDUuODc1LTIuNzM1LjQ3LTQuOTk1IDEuMDItNi43MSAxLjYzNWwtMS43MS0xMS42OGMxLjc4NS0uNjIgNC40NDUtMS4yNCA3Ljg3NS0xLjg1NSAzLjQyNS0uNjYgNy4xMS0uOTUgMTEuMDQ1LS45NWguMDAxem0yODEuNTEgNTEuMjg1YzQuMzc1IDAgNy42MTUtLjI1NSA5Ljg3NS0uNjk1di0xNC40OGMtLjgtLjIyLTEuOTMtLjQ3NS0zLjQyNS0uNjk1YTMzLjgxMyAzMy44MTMgMCAwMC00Ljk2LS4zNjVjLTEuNTY1IDAtMy4xNy4xMS00Ljc3NS4zNjUtMS42LjIyLTMuMDYuNjU1LTQuMzM1IDEuMjc1LTEuMjguNjItMi4zMzUgMS40OTUtMy4xIDIuNjItLjgwNSAxLjEzLTEuMTY1IDEuNzg1LTEuMTY1IDMuNDk1IDAgMy4zNDUgMS4xNjUgNS4yOCAzLjI4IDYuNTUgMi4xNSAxLjMxIDQuOTk1IDEuOTMgOC42MDUgMS45M3ptLTEuMjA1LTUxLjY0NWM0LjcgMCA4LjY3NC41OCAxMS44ODQgMS43NDUgMy4yMDUgMS4xNjUgNS43OTUgMi44IDcuNjkgNC44NzUgMS44OTUgMi4wNzUgMy4yNDUgNC45MTUgNC4wNDUgNy45Ljg0IDIuOTg1IDEuMjQgNi4yNiAxLjI0IDkuODY1djM2LjYxNWMtMi4xODUuNDctNS41MDUgMS4wMTUtOS45NSAxLjY3NS00LjQ0NS42NTUtOS40NC45OC0xNC45ODUuOTgtMy42OCAwLTcuMDctLjM2NS0xMC4wOTQtMS4wNTUtMy4wNjUtLjY5LTUuNjUtMS44Mi03Ljg0LTMuMzg1LTIuMTUtMS41NjUtMy44MjUtMy41Ny01LjA2NS02LjA0LTEuMjA1LTIuNDc1LTEuODI1LTUuOTctMS44MjUtOS42MSAwLTMuNDk1LjY5NS01LjcxNSAyLjA0NS04LjEyIDEuMzgtMi40IDMuMjQtNC4zNjUgNS41NzUtNS44OTUgMi4zNy0xLjUyNSA1LjA2NS0yLjYyIDguMTY1LTMuMjc1IDMuMS0uNjU1IDYuMzQ1LS45OCA5LjctLjk4IDEuNTY1IDAgMy4yMDUuMTEgNC45NTUuMjlzMy41NzUuNTEgNS41NC45ODV2LTIuMzNjMC0xLjY0LS4xOC0zLjIwNS0uNTgtNC42NmE5Ljk3NyA5Ljk3NyAwIDAwLTIuMDQ1LTMuODk1Yy0uOTg1LTEuMTMtMi4yNTUtMi4wMDUtMy44Ni0yLjYyLTEuNjA2LS42Mi0zLjY1LTEuMDktNi4wOS0xLjA5LTMuMjggMC02LjI3LjQtOS4wMDUuODctMi43MzUuNDc1LTQuOTk1IDEuMDItNi43MSAxLjY0bC0xLjcxLTExLjY4NWMxLjc4NS0uNjIgNC40NDUtMS4yMzUgNy44NzUtMS44NTUgMy40MjUtLjYyIDcuMTA1LS45NDUgMTEuMDQ1LS45NDV6bS00Mi44LTYuNzdjNC43NzQgMCA4LjY4LTMuODYgOC42OC04LjYzIDAtNC43NjUtMy44NjYtOC42MjUtOC42OC04LjYyNS00LjgxIDAtOC42NzUgMy44Ni04LjY3NSA4LjYyNSAwIDQuNzcgMy45IDguNjMgOC42NzUgOC42M3ptNy4xOCA3MC40MjVoLTE0LjMyNnYtNjEuNDRsMTQuMzI1LTIuMjU1djYzLjY5NWguMDAxem0tMjUuMTE2IDBjLTIzLjM2NS4xMS0yMy4zNjUtMTguODU1LTIzLjM2NS0yMS44NzVsLS4wNC03MS4wNDUgMTQuMjU1LTIuMjZ2NzAuNjFjMCAxLjcxNSAwIDEyLjU2IDkuMTUgMTIuNTk1djExLjk3NXptLTQ2LjMzNS0zMS40NDVjMC02LjE1NS0xLjM1LTExLjI4NS0zLjk3NC0xNC44NS0yLjYyNS0zLjYwNS02LjMwNS01LjM4NS0xMS4wMS01LjM4NS00LjcgMC04LjM4NiAxLjc4LTExLjAwNiA1LjM4NS0yLjYyNSAzLjYtMy45MDQgOC42OTUtMy45MDQgMTQuODUgMCA2LjIyNSAxLjMxNSAxMC40MDUgMy45NCAxNC4wMSAyLjYyNSAzLjY0IDYuMzA1IDUuNDI1IDExLjAxIDUuNDI1IDQuNyAwIDguMzg1LTEuODIgMTEuMDEtNS40MjUgMi42MjQtMy42NCAzLjkzNC03Ljc4NSAzLjkzNC0xNC4wMXptMTQuNTgtLjAzNWMwIDQuODA1LS42OSA4LjQ0LTIuMTE0IDEyLjQxLTEuNDIgMy45NjUtMy40MjUgNy4zNS02LjAxIDEwLjE1NS0yLjU5IDIuOC01LjY5IDQuOTg1LTkuMzM2IDYuNTE1LTMuNjQ0IDEuNTI1LTkuMjYgMi40LTEyLjA2NSAyLjQtMi44MS0uMDM1LTguMzg1LS44MzUtMTEuOTk1LTIuNC0zLjYxLTEuNTY1LTYuNzEtMy43MTUtOS4yOTUtNi41MTUtMi41OS0yLjgwNS00LjU5NC02LjE5LTYuMDU0LTEwLjE1NS0xLjQ1Ni0zLjk3LTIuMTg1LTcuNjA1LTIuMTg1LTEyLjQxcy42NTQtOS40MyAyLjExNC0xMy4zNmMxLjQ2LTMuOTMgMy41LTcuMjggNi4xMjUtMTAuMDggMi42MjUtMi44MDUgNS43Ni00Ljk1NSA5LjMzLTYuNDggMy42MS0xLjUzIDcuNTg1LTIuMjU1IDExLjg4NS0yLjI1NSA0LjMwNSAwIDguMjc1Ljc2IDExLjkyIDIuMjU1IDMuNjUgMS41MjUgNi43ODYgMy42NzUgOS4zMzYgNi40OCAyLjU4NCAyLjggNC41OSA2LjE1IDYuMDUgMTAuMDggMS41MyAzLjkzIDIuMjk1IDguNTU1IDIuMjk1IDEzLjM2aC0uMDAxem0tMTA3LjI4NCAwYzAgNS45NjUgMS4zMSAxMi41OSAzLjkzNSAxNS4zNTUgMi42MjUgMi43NyA2LjAxNCA0LjE1IDEwLjE3NSA0LjE1IDIuMjYgMCA0LjQxLS4zMjUgNi40MTQtLjk0NSAyLjAwNS0uNjIgMy42MDYtMS4zNSA0Ljg4Ni0yLjIydi0zNS4zNGMtMS4wMi0uMjItNS4yODYtMS4wOTUtOS40MS0xLjItNS4xNzUtLjE1LTkuMTEgMS45NjUtMTEuODggNS4zNDUtMi43MzYgMy4zOS00LjEyIDkuMzItNC4xMiAxNC44NTV6bTM5LjYyNSAyOC4wOTVjMCA5LjcyLTIuNDggMTYuODE1LTcuNDc2IDIxLjMzLTQuOTkgNC41MS0xMi42MSA2Ljc3LTIyLjg5IDYuNzctMy43NTUgMC0xMS41NTUtLjczLTE3Ljc5LTIuMTFsMi4yOTUtMTEuMjg1YzUuMjE1IDEuMDkgMTIuMTA1IDEuMzg1IDE1LjcxNSAxLjM4NSA1LjcyIDAgOS44MDUtMS4xNjUgMTIuMjQ1LTMuNDk1IDIuNDQ1LTIuMzMgMy42NDUtNS43ODUgMy42NDUtMTAuMzc1di0yLjMzYy0xLjQyLjY5LTMuMjggMS4zODUtNS41NzUgMi4xMTUtMi4yOTUuNjktNC45NTUgMS4wNTUtNy45NSAxLjA1NS0zLjkzNSAwLTcuNTEtLjYyLTEwLjc1LTEuODYtMy4yNDUtMS4yMzUtNi4wNTUtMy4wNTUtOC4zNS01LjQ2LTIuMjk1LTIuNC00LjEyLTUuNDItNS4zOTUtOS4wMjUtMS4yNzUtMy42MDUtMS45MzUtMTAuMDQ1LTEuOTM1LTE0Ljc3NSAwLTQuNDQuNjk1LTEwLjAxIDIuMDQ2LTEzLjcyNSAxLjM4NC0zLjcxIDMuMzUtNi45MTUgNi4wMTQtOS41NyAyLjYyNi0yLjY1NSA1LjgzNS00LjY5NSA5LjU5LTYuMTkgMy43NTUtMS40OSA4LjE2LTIuNDM1IDEyLjkzNS0yLjQzNSA0LjYzNSAwIDguOS41OCAxMy4wNTUgMS4yNzUgNC4xNTUuNjkgNy42OSAxLjQxNSAxMC41NyAyLjIxNXY1Ni40OWguMDAxeiIvPjwvZz48L3N2Zz4=);
- background-repeat: no-repeat;
- background-size: contain;
- vertical-align: middle;
+$color-bg: #fff;
+$color-muted: #969faf;
+$color-light: #797979;
+$color-text: #23263b;
+$color-mark: rgb(84, 104, 255);
+$color-bg-selected: #f5f5fa;
+
+$font-size-xs: 12px;
+$font-size-s: 14px;
+$font-size-m: 16px;
+
+$size-s: 4px;
+$size-m: 8px;
+$size-l: 16px;
+$size-l: 32px;
+
+.aa-Autocomplete {
+ --color-mark: #{$color-mark};
+ --color-background: #{$color-bg};
+ --color-selected: #{$color-bg-selected};
+ --color-text: #{$color-text};
+}
+// Source (modified) https://github.com/algolia/autocomplete.js/blob/next/examples/js/autocomplete.css
+.aa-Form {
+ position: relative;
+}
+
+.aa-Label {
+ align-items: center;
+ color: #777;
+ cursor: initial;
+ display: flex;
+ height: $size-l;
+ width: $size-l;
+ padding: 0 0.5rem;
+ position: absolute;
+ z-index: 2;
+}
+
+.aa-InputWrapper {
+ background-color: $color-bg;
+ background-color: var(--color-background);
+ max-width: 100%;
+ position: relative;
+ width: 100%;
+ display: flex;
+ border-radius: 3px;
}
-.clearfix {
- clear: both;
+.aa-Input {
+ appearance: none;
+ background: none;
+ border: 1px solid #d6d6e7;
+ border-radius: 3px;
+ box-shadow: rgba(119, 122, 175, 0.3) 0 1px 4px 0 inset;
+ caret-color: #5a5e9a;
+ color: $color-text;
+ color: var(--color-text);
+ height: $size-l;
+ width: 100%;
+ z-index: 2;
+ font-size: $font-size-m;
}
-/***************************/
-/* autocomplete.js */
-/***************************/
+.aa-Input::-webkit-search-decoration,
+.aa-Input::-webkit-search-cancel-button,
+.aa-Input::-webkit-search-results-button,
+.aa-Input::-webkit-search-results-decoration {
+ -webkit-appearance: none;
+}
-.algolia-autocomplete {
- width: 100%;
- line-height: normal;
+.aa-Input {
+ padding: 0 2.25rem;
}
-.aa-input {
- width: 100%;
- outline: none;
+.aa-Input::placeholder {
+ color: #5a5e9a;
}
-.aa-hint {
- width: 100%;
- color: #999;
+.aa-Input:focus {
+ border-color: #3c4fe0;
+ box-shadow: rgba(35, 38, 59, 0.05) 0 1px 0 0;
+ outline: currentcolor none medium;
}
-.aa-dropdown-menu {
- margin-top: 9px;
- border: 1px solid #d9d9d9;
+.aa-ResetButton {
+ background: none;
+ border: 0;
+ cursor: pointer;
+ color: #777;
+ height: $size-l;
+ width: $size-l;
+ position: absolute;
+ right: 0;
+ z-index: 2;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.aa-Panel {
+ background-color: $color-bg;
+ background-color: var(--color-background);
+ border: 1px solid rgba(150, 150, 150, 0.16);
border-radius: 3px;
- background-color: #fff;
- box-shadow: 0 1px 0 0 #ccc, 0 2px 3px 0 #e6e6e6;
- font-size: 14px;
- text-align: left;
+ box-shadow: 0 0 0 1px rgba(35, 38, 59, 0.05),
+ 0 8px 16px -4px rgba(35, 38, 59, 0.25);
+ margin-top: 5px;
+ max-width: 480px;
+ position: absolute;
+ width: 100%;
+ min-width: 400px;
+ font-size: $font-size-m;
+ font-weight: normal;
+ z-index: 100;
+ font-family: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,Helvetica,Arial,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;
}
-.aa-suggestion {
- cursor: pointer;
- padding: 8px 16px;
+.aa-Panel--stalled {
+ filter: grayscale(1);
+ opacity: 0.5;
+ transition: opacity 200ms ease-in;
+}
- & ~ .aa-suggestion {
- border-top: 1px solid #e8e8e8;
- }
+.aa-Panel a {
+ color: inherit;
+ text-decoration: none;
}
-.aa-suggestion.aa-cursor {
- background-color: #f8f8f8;
+.aa-Panel ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
}
-.aa-powered-by {
- text-align: right;
- font-size: 0.8em;
- color: #999;
- padding: 8px 16px 8px 0;
-}
-
-// title
-.aa-hit--title {
- font-size: 1.1em;
- font-weight: bold;
- color: black;
-
- .aa-hit--highlight {
- position: relative;
- z-index: 1;
-
- &::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- opacity: 0.1;
- z-index: -1;
- }
- }
-}
-
-.aa-hit__xs .aa-hit--title,
-.aa-hit__sm .aa-hit--title {
- font-size: 1em;
- line-height: 1.25em;
- display: block;
-}
-
-.aa-hit__xs .aa-hit--title {
- font-weight: normal;
- padding: 8px 0;
+.aa-Item {
+ color: $color-text;
+ color: var(--color-text);
+ cursor: pointer;
+ display: flex;
}
-.aa-suggestion:first-child .aa-hit--title {
- border: none;
+.aa-Item[aria-selected='true'] {
+ background-color: $color-bg-selected;
+ background-color: var(--color-selected);
}
-/** body **/
-.aa-hit--description {
- font-size: 13px;
- color: #797979;
- margin-top: 4px;
- line-height: 1.25em;
+.aa-ItemContent {
+ display: flex;
+ flex-grow: 1;
+ grid-gap: 0.5rem;
+ padding: 0.5rem;
+ text-align: left;
+}
- .aa-hit--highlight {
- color: #666;
- font-weight: bold;
- }
+.aa-ItemSourceIcon {
+ color: rgba(80, 80, 80, 0.32);
}
-.aa-hit__xs .aa-hit--description {
- display: none;
+// ---- More
+.aa-ItemTitle {
+ font-size: $font-size-s;
+ line-height: 18px;
+}
+.aa-ItemDescription {
+ font-size: $font-size-xs;
+ line-height: 16px;
+ color: $color-light;
}
+.aa-ItemContent mark {
+ color: $color-mark;
+ color: var(--color-mark);
+ background-color: transparent;
+}
+
+
-.aa-hit__sm .aa-hit--description {
- font-size: 0.9em;
- line-height: 1.25em;
+/* Search by */
+.aa-powered-by-link {
+ display: inline-block;
+ width: 64px;
+ height: 18px;
+ text-indent: 101%;
+ overflow: hidden;
+ white-space: nowrap;
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODUgMTIwIj48ZyBmaWxsPSJub25lIj48cGF0aCBmaWxsPSIjNTQ2OEZGIiBkPSJNMTYuOC0xLjAwMWg4OC40YzguNyAwIDE1LjggNy4wNjUgMTUuOCAxNS44djg4LjQwNWMwIDguNy03LjA2NSAxNS43OTUtMTUuOCAxNS43OTVIMTYuOGMtOC43IDAtMTUuOC03LjA2LTE1LjgtMTUuNzk1VjE0Ljc1OWMwLTguNjk1IDcuMDYtMTUuNzYgMTUuOC0xNS43NiIvPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik03My41MDUgMjUuNzg4di00LjExNWE1LjIwOSA1LjIwOSAwIDAwLTUuMjEtNS4yMDVINTYuMTVhNS4yMDkgNS4yMDkgMCAwMC01LjIxIDUuMjA1djQuMjI1YzAgLjQ3LjQzNS44LjkxLjY5YTM3Ljk2NiAzNy45NjYgMCAwMTEwLjU3LTEuNDljMy40NjUgMCA2Ljg5NS40NyAxMC4yMSAxLjM4LjQ0LjExLjg3NS0uMjE1Ljg3NS0uNjlNNDAuMjIgMzEuMTczbC0yLjA3NS0yLjA3NWE1LjIwNiA1LjIwNiAwIDAwLTcuMzY1IDBsLTIuNDggMi40NzVhNS4xODUgNS4xODUgMCAwMDAgNy4zNTVsMi4wNCAyLjA0Yy4zMy4zMjUuODA1LjI1IDEuMDk1LS4wNzVhMzkuODc2IDM5Ljg3NiAwIDAxMy45NzUtNC42NiAzNy42OCAzNy42OCAwIDAxNC43LTRjLjM2NC0uMjIuNC0uNzMuMTEtMS4wNm0yMi4xNjQgMTMuMDY1djE3LjhjMCAuNTEuNTUuODc1IDEuMDIuNjJsMTUuODI1LTguMTljLjM2LS4xOC40Ny0uNjIuMjktLjk4LTMuMjgtNS43NTUtOS4zNy05LjY4NS0xNi40MDUtOS45NC0uMzY1IDAtLjczLjI5LS43My42OW0wIDQyLjg4Yy0xMy4xOTUgMC0yMy45MTUtMTAuNzA1LTIzLjkxNS0yMy44OCAwLTEzLjE3NSAxMC43Mi0yMy44NzUgMjMuOTE1LTIzLjg3NSAxMy4yIDAgMjMuOTE2IDEwLjcgMjMuOTE2IDIzLjg3NXMtMTAuNjggMjMuODgtMjMuOTE2IDIzLjg4bTAtNTcuOGMtMTguNzQgMC0zMy45NCAxNS4xOC0zMy45NCAzMy45MiAwIDE4Ljc0NSAxNS4yIDMzLjg5IDMzLjk0IDMzLjg5czMzLjk0LTE1LjE4IDMzLjk0LTMzLjkyNWMwLTE4Ljc0NS0xNS4xNjUtMzMuODg1LTMzLjk0LTMzLjg4NSIvPjxwYXRoIGZpbGw9IiM1NDY4RkYiIGQ9Ik0yNDAuMjE0IDk0LjE3N2MtMjMuMzY1LjExLTIzLjM2NS0xOC44NTUtMjMuMzY1LTIxLjg3NWwtLjA0LTcxLjA0NSAxNC4yNTQtMi4yNnY3MC42MWMwIDEuNzE1IDAgMTIuNTYgOS4xNSAxMi41OTV2MTEuOTc1aC4wMDF6bS01Ny43OC0xMS42MWM0LjM3NCAwIDcuNjItLjI1NSA5Ljg4LS42OVY2Ny4zOTJhMjkuMTk2IDI5LjE5NiAwIDAwLTMuNDMtLjY5NSAzMy43NDIgMzMuNzQyIDAgMDAtNC45NTYtLjM2NWMtMS41NyAwLTMuMTc1LjExLTQuNzc1LjM2NS0xLjYwNS4yMi0zLjA2NS42NTUtNC4zNCAxLjI3NS0xLjI3NS42Mi0yLjMzNSAxLjQ5NS0zLjEgMi42Mi0uOCAxLjEzLTEuMTY1IDEuNzg1LTEuMTY1IDMuNDk1IDAgMy4zNDUgMS4xNjUgNS4yOCAzLjI4IDYuNTUgMi4xMTUgMS4yNzUgNC45OTUgMS45MyA4LjYwNiAxLjkzem0tMS4yNC01MS42ODVjNC43IDAgOC42NzQuNTg1IDExLjg4NCAxLjc1IDMuMjA2IDEuMTY1IDUuNzk2IDIuOCA3LjY5IDQuODc1IDEuOTM1IDIuMTEgMy4yNDUgNC45MTUgNC4wNDYgNy45Ljg0IDIuOTg1IDEuMjQgNi4yNiAxLjI0IDkuODZ2MzYuNjJjLTIuMTg1LjQ3LTUuNTA2IDEuMDE1LTkuOTUgMS42Ny00LjQ0Ni42NTUtOS40NC45ODUtMTQuOTg2Ljk4NS0zLjY4IDAtNy4wNy0uMzY1LTEwLjA5NS0xLjA1NS0zLjA2NS0uNjktNS42NS0xLjgyLTcuODQtMy4zODUtMi4xNS0xLjU2NS0zLjgyNS0zLjU3LTUuMDY1LTYuMDQtMS4yMDUtMi40OC0xLjgyNS01Ljk3LTEuODI1LTkuNjEgMC0zLjQ5NS42OS01LjcxNSAyLjA0NS04LjEyIDEuMzgtMi40IDMuMjQtNC4zNjUgNS41NzUtNS44OTUgMi4zNy0xLjUzIDUuMDY1LTIuNjIgOC4xNjUtMy4yNzUgMy4xLS42NTUgNi4zNDUtLjk4NSA5LjY5NS0uOTg1IDEuNTcgMCAzLjIxLjExIDQuOTYuMjkgMS43MTUuMTg1IDMuNTc1LjUxNSA1LjU0NS45ODV2LTIuMzNjMC0xLjYzNS0uMTg1LTMuMi0uNTg1LTQuNjU1YTEwLjAxMiAxMC4wMTIgMCAwMC0yLjA0NS0zLjg5NWMtLjk4NS0xLjEzLTIuMjU1LTIuMDA1LTMuODYtMi42Mi0xLjYwNS0uNjItMy42NS0xLjA5NS02LjA5LTEuMDk1LTMuMjggMC02LjI3LjQtOS4wMDUuODc1LTIuNzM1LjQ3LTQuOTk1IDEuMDItNi43MSAxLjYzNWwtMS43MS0xMS42OGMxLjc4NS0uNjIgNC40NDUtMS4yNCA3Ljg3NS0xLjg1NSAzLjQyNS0uNjYgNy4xMS0uOTUgMTEuMDQ1LS45NWguMDAxem0yODEuNTEgNTEuMjg1YzQuMzc1IDAgNy42MTUtLjI1NSA5Ljg3NS0uNjk1di0xNC40OGMtLjgtLjIyLTEuOTMtLjQ3NS0zLjQyNS0uNjk1YTMzLjgxMyAzMy44MTMgMCAwMC00Ljk2LS4zNjVjLTEuNTY1IDAtMy4xNy4xMS00Ljc3NS4zNjUtMS42LjIyLTMuMDYuNjU1LTQuMzM1IDEuMjc1LTEuMjguNjItMi4zMzUgMS40OTUtMy4xIDIuNjItLjgwNSAxLjEzLTEuMTY1IDEuNzg1LTEuMTY1IDMuNDk1IDAgMy4zNDUgMS4xNjUgNS4yOCAzLjI4IDYuNTUgMi4xNSAxLjMxIDQuOTk1IDEuOTMgOC42MDUgMS45M3ptLTEuMjA1LTUxLjY0NWM0LjcgMCA4LjY3NC41OCAxMS44ODQgMS43NDUgMy4yMDUgMS4xNjUgNS43OTUgMi44IDcuNjkgNC44NzUgMS44OTUgMi4wNzUgMy4yNDUgNC45MTUgNC4wNDUgNy45Ljg0IDIuOTg1IDEuMjQgNi4yNiAxLjI0IDkuODY1djM2LjYxNWMtMi4xODUuNDctNS41MDUgMS4wMTUtOS45NSAxLjY3NS00LjQ0NS42NTUtOS40NC45OC0xNC45ODUuOTgtMy42OCAwLTcuMDctLjM2NS0xMC4wOTQtMS4wNTUtMy4wNjUtLjY5LTUuNjUtMS44Mi03Ljg0LTMuMzg1LTIuMTUtMS41NjUtMy44MjUtMy41Ny01LjA2NS02LjA0LTEuMjA1LTIuNDc1LTEuODI1LTUuOTctMS44MjUtOS42MSAwLTMuNDk1LjY5NS01LjcxNSAyLjA0NS04LjEyIDEuMzgtMi40IDMuMjQtNC4zNjUgNS41NzUtNS44OTUgMi4zNy0xLjUyNSA1LjA2NS0yLjYyIDguMTY1LTMuMjc1IDMuMS0uNjU1IDYuMzQ1LS45OCA5LjctLjk4IDEuNTY1IDAgMy4yMDUuMTEgNC45NTUuMjlzMy41NzUuNTEgNS41NC45ODV2LTIuMzNjMC0xLjY0LS4xOC0zLjIwNS0uNTgtNC42NmE5Ljk3NyA5Ljk3NyAwIDAwLTIuMDQ1LTMuODk1Yy0uOTg1LTEuMTMtMi4yNTUtMi4wMDUtMy44Ni0yLjYyLTEuNjA2LS42Mi0zLjY1LTEuMDktNi4wOS0xLjA5LTMuMjggMC02LjI3LjQtOS4wMDUuODctMi43MzUuNDc1LTQuOTk1IDEuMDItNi43MSAxLjY0bC0xLjcxLTExLjY4NWMxLjc4NS0uNjIgNC40NDUtMS4yMzUgNy44NzUtMS44NTUgMy40MjUtLjYyIDcuMTA1LS45NDUgMTEuMDQ1LS45NDV6bS00Mi44LTYuNzdjNC43NzQgMCA4LjY4LTMuODYgOC42OC04LjYzIDAtNC43NjUtMy44NjYtOC42MjUtOC42OC04LjYyNS00LjgxIDAtOC42NzUgMy44Ni04LjY3NSA4LjYyNSAwIDQuNzcgMy45IDguNjMgOC42NzUgOC42M3ptNy4xOCA3MC40MjVoLTE0LjMyNnYtNjEuNDRsMTQuMzI1LTIuMjU1djYzLjY5NWguMDAxem0tMjUuMTE2IDBjLTIzLjM2NS4xMS0yMy4zNjUtMTguODU1LTIzLjM2NS0yMS44NzVsLS4wNC03MS4wNDUgMTQuMjU1LTIuMjZ2NzAuNjFjMCAxLjcxNSAwIDEyLjU2IDkuMTUgMTIuNTk1djExLjk3NXptLTQ2LjMzNS0zMS40NDVjMC02LjE1NS0xLjM1LTExLjI4NS0zLjk3NC0xNC44NS0yLjYyNS0zLjYwNS02LjMwNS01LjM4NS0xMS4wMS01LjM4NS00LjcgMC04LjM4NiAxLjc4LTExLjAwNiA1LjM4NS0yLjYyNSAzLjYtMy45MDQgOC42OTUtMy45MDQgMTQuODUgMCA2LjIyNSAxLjMxNSAxMC40MDUgMy45NCAxNC4wMSAyLjYyNSAzLjY0IDYuMzA1IDUuNDI1IDExLjAxIDUuNDI1IDQuNyAwIDguMzg1LTEuODIgMTEuMDEtNS40MjUgMi42MjQtMy42NCAzLjkzNC03Ljc4NSAzLjkzNC0xNC4wMXptMTQuNTgtLjAzNWMwIDQuODA1LS42OSA4LjQ0LTIuMTE0IDEyLjQxLTEuNDIgMy45NjUtMy40MjUgNy4zNS02LjAxIDEwLjE1NS0yLjU5IDIuOC01LjY5IDQuOTg1LTkuMzM2IDYuNTE1LTMuNjQ0IDEuNTI1LTkuMjYgMi40LTEyLjA2NSAyLjQtMi44MS0uMDM1LTguMzg1LS44MzUtMTEuOTk1LTIuNC0zLjYxLTEuNTY1LTYuNzEtMy43MTUtOS4yOTUtNi41MTUtMi41OS0yLjgwNS00LjU5NC02LjE5LTYuMDU0LTEwLjE1NS0xLjQ1Ni0zLjk3LTIuMTg1LTcuNjA1LTIuMTg1LTEyLjQxcy42NTQtOS40MyAyLjExNC0xMy4zNmMxLjQ2LTMuOTMgMy41LTcuMjggNi4xMjUtMTAuMDggMi42MjUtMi44MDUgNS43Ni00Ljk1NSA5LjMzLTYuNDggMy42MS0xLjUzIDcuNTg1LTIuMjU1IDExLjg4NS0yLjI1NSA0LjMwNSAwIDguMjc1Ljc2IDExLjkyIDIuMjU1IDMuNjUgMS41MjUgNi43ODYgMy42NzUgOS4zMzYgNi40OCAyLjU4NCAyLjggNC41OSA2LjE1IDYuMDUgMTAuMDggMS41MyAzLjkzIDIuMjk1IDguNTU1IDIuMjk1IDEzLjM2aC0uMDAxem0tMTA3LjI4NCAwYzAgNS45NjUgMS4zMSAxMi41OSAzLjkzNSAxNS4zNTUgMi42MjUgMi43NyA2LjAxNCA0LjE1IDEwLjE3NSA0LjE1IDIuMjYgMCA0LjQxLS4zMjUgNi40MTQtLjk0NSAyLjAwNS0uNjIgMy42MDYtMS4zNSA0Ljg4Ni0yLjIydi0zNS4zNGMtMS4wMi0uMjItNS4yODYtMS4wOTUtOS40MS0xLjItNS4xNzUtLjE1LTkuMTEgMS45NjUtMTEuODggNS4zNDUtMi43MzYgMy4zOS00LjEyIDkuMzItNC4xMiAxNC44NTV6bTM5LjYyNSAyOC4wOTVjMCA5LjcyLTIuNDggMTYuODE1LTcuNDc2IDIxLjMzLTQuOTkgNC41MS0xMi42MSA2Ljc3LTIyLjg5IDYuNzctMy43NTUgMC0xMS41NTUtLjczLTE3Ljc5LTIuMTFsMi4yOTUtMTEuMjg1YzUuMjE1IDEuMDkgMTIuMTA1IDEuMzg1IDE1LjcxNSAxLjM4NSA1LjcyIDAgOS44MDUtMS4xNjUgMTIuMjQ1LTMuNDk1IDIuNDQ1LTIuMzMgMy42NDUtNS43ODUgMy42NDUtMTAuMzc1di0yLjMzYy0xLjQyLjY5LTMuMjggMS4zODUtNS41NzUgMi4xMTUtMi4yOTUuNjktNC45NTUgMS4wNTUtNy45NSAxLjA1NS0zLjkzNSAwLTcuNTEtLjYyLTEwLjc1LTEuODYtMy4yNDUtMS4yMzUtNi4wNTUtMy4wNTUtOC4zNS01LjQ2LTIuMjk1LTIuNC00LjEyLTUuNDItNS4zOTUtOS4wMjUtMS4yNzUtMy42MDUtMS45MzUtMTAuMDQ1LTEuOTM1LTE0Ljc3NSAwLTQuNDQuNjk1LTEwLjAxIDIuMDQ2LTEzLjcyNSAxLjM4NC0zLjcxIDMuMzUtNi45MTUgNi4wMTQtOS41NyAyLjYyNi0yLjY1NSA1LjgzNS00LjY5NSA5LjU5LTYuMTkgMy43NTUtMS40OSA4LjE2LTIuNDM1IDEyLjkzNS0yLjQzNSA0LjYzNSAwIDguOS41OCAxMy4wNTUgMS4yNzUgNC4xNTUuNjkgNy42OSAxLjQxNSAxMC41NyAyLjIxNXY1Ni40OWguMDAxeiIvPjwvZz48L3N2Zz4=);
+ background-repeat: no-repeat;
+ background-size: contain;
+ vertical-align: middle;
+}
+.aa-powered-by {
+ text-align: right;
+ font-size: $font-size-xs;
+ color: $color-muted;
+ padding: $size-m $size-m $size-s 0;
+ font-weight: normal;
}
diff --git a/frontend/src/index.ts b/frontend/src/index.ts
index 974d9c8a0..798936f4e 100644
--- a/frontend/src/index.ts
+++ b/frontend/src/index.ts
@@ -1,5 +1,4 @@
-import { AlgoliasearchNetlify } from './AlgoliasearchNetlify';
-import { toFactory } from './toFactory';
+import { algoliasearchNetlify } from './AlgoliasearchNetlify';
// eslint-disable-next-line import/no-commonjs
-module.exports = toFactory(AlgoliasearchNetlify);
+module.exports = algoliasearchNetlify;
diff --git a/frontend/src/options.ts b/frontend/src/options.ts
deleted file mode 100644
index 51f790ce6..000000000
--- a/frontend/src/options.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-export interface Options {
- // Mandatory
- appId: string;
- apiKey: string;
-
- // Temporary
- indexName?: string;
- siteId: string;
- branch: string;
-
- // Optional
- analytics: boolean;
- autocomplete: {
- hitsPerPage: number;
- inputSelector: string;
- };
- color: string;
- debug: boolean;
- silenceWarnings: boolean;
- poweredBy: boolean;
-}
diff --git a/frontend/src/templates.ts b/frontend/src/templates.ts
index 01dc78e33..974121b8b 100644
--- a/frontend/src/templates.ts
+++ b/frontend/src/templates.ts
@@ -1,51 +1,41 @@
-import type { Hit } from '@algolia/client-search';
+import { AlgoliaRecord } from './types';
-import { SizeModifier } from './AutocompleteWrapper';
-import { Data } from './data';
-import { escapeHTML } from './escapeHTML';
-
-export interface Templates {
- algolia: (hostname: string) => string;
- autocomplete: {
- css: (color: string) => string;
- poweredBy: (algoliaLogoHtml: string) => string;
- suggestion: (
- hit: Hit & { sizeModifier: SizeModifier; snippet: string }
- ) => string;
- };
-}
-
-export const templates: Templates = {
- algolia: (hostname) => {
- const escapedHostname = escapeHTML(hostname);
+export const templates = {
+ poweredBy: (hostname: string) => {
+ const escapedHostname = encodeURIComponent(hostname);
return `
-
- Algolia
-
+
`;
},
- autocomplete: {
- css: (color) => `
- .aa-hit--highlight {
- color: ${color};
- }
- .aa-hit--title .aa-hit--highlight::before {
- background-color: ${color};
- }
- `,
- poweredBy: (algoliaLogoHtml) => `
-
- Search by ${algoliaLogoHtml}
-
`,
- suggestion: (hit) => `
-
-
${hit._highlightResult!.title.value}
-
${hit.snippet}
-
- `,
+ item: (record: AlgoliaRecord, title: string, description: string | null) => {
+ return `
+
+
+
+
+
+ ${title}
+
+ ${
+ description
+ ? `
${description}
`
+ : ''
+ }
+
+
+
+
+ `;
},
};
diff --git a/frontend/src/toFactory.ts b/frontend/src/toFactory.ts
deleted file mode 100644
index f11ff82e2..000000000
--- a/frontend/src/toFactory.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// Taken from https://github.com/timoxley/to-factory/
-// License: MIT
-
-export function toFactory(Class: any) {
- // eslint-disable-next-line algolia/func-style-toplevel
- const Factory = function (...args: any[]) {
- return new Class(...args);
- };
- // eslint-disable-next-line no-proto
- Factory.__proto__ = Class;
- Factory.prototype = Class.prototype;
- return Factory;
-}
diff --git a/frontend/src/global.d.ts b/frontend/src/types/global.d.ts
similarity index 100%
rename from frontend/src/global.d.ts
rename to frontend/src/types/global.d.ts
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
new file mode 100644
index 000000000..267272ce7
--- /dev/null
+++ b/frontend/src/types/index.ts
@@ -0,0 +1,2 @@
+export * from './options';
+export * from './record';
diff --git a/frontend/src/types/options.ts b/frontend/src/types/options.ts
new file mode 100644
index 000000000..2e5bda0ee
--- /dev/null
+++ b/frontend/src/types/options.ts
@@ -0,0 +1,22 @@
+export interface Options {
+ // Mandatory
+ appId: string;
+ apiKey: string;
+ selector: string;
+ siteId: string;
+ branch: string;
+
+ // Optional
+ analytics?: boolean;
+ hitsPerPage?: number;
+ theme?: {
+ mark?: string;
+ background?: string;
+ selected?: string;
+ text?: string;
+ };
+ debug?: boolean;
+ placeholder?: string;
+ openOnFocus?: boolean;
+ poweredBy?: boolean;
+}
diff --git a/frontend/src/types/record.ts b/frontend/src/types/record.ts
new file mode 100644
index 000000000..f34aa088f
--- /dev/null
+++ b/frontend/src/types/record.ts
@@ -0,0 +1,20 @@
+export type AlgoliaRecord = {
+ objectID: string;
+
+ url: string;
+ origin: string;
+ title: string;
+ content: string;
+
+ lang?: string;
+ description?: string;
+ keywords?: string[];
+ image?: string;
+ authors?: string[];
+ datePublished?: number;
+ dateModified?: number;
+ category?: string;
+
+ urlDepth?: number;
+ position?: number;
+};
diff --git a/public/index.html b/public/index.html
index a408127cd..1d777a867 100644
--- a/public/index.html
+++ b/public/index.html
@@ -33,13 +33,18 @@
}
#search {
+ display: flex;
+ gap: 16px;
+ }
+
+ #searchBig {
width: 60%;
- display: block;
- margin: 16px auto;
- padding: 8px 16px;
- font-size: 1.2em;
- border-radius: 4px;
- border: 1px solid #ccc;
+ height: 32px;
+ }
+
+ #searchSmall {
+ height: 32px;
+ width: 20%;
}
#debug-script {
@@ -103,7 +108,10 @@
Algoliasearch Netlify Test Website
-
+
Script
@@ -146,12 +154,29 @@
Test content