Skip to content

Commit 09be485

Browse files
feat(recent-searches): export storage and search APIs (#473)
1 parent a49a106 commit 09be485

32 files changed

+543
-172
lines changed

.codesandbox/ci.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"/examples/js",
77
"/examples/react-renderer",
88
"/examples/query-suggestions-with-recent-searches",
9-
"/examples/query-suggestions-with-inline-categories"
9+
"/examples/query-suggestions-with-inline-categories",
10+
"/examples/recently-viewed-items"
1011
],
1112
"node": "14"
1213
}

examples/recently-viewed-items/README.md

Whitespace-only changes.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/** @jsx h */
2+
import {
3+
autocomplete,
4+
getAlgoliaHits,
5+
highlightHit,
6+
} from '@algolia/autocomplete-js';
7+
import algoliasearch from 'algoliasearch';
8+
import { h, Fragment } from 'preact';
9+
10+
import '@algolia/autocomplete-theme-classic';
11+
12+
import { createLocalStorageRecentlyViewedItems } from './recentlyViewedItemsPlugin';
13+
import { ProductItem, ProductHit } from './types/ProductHit';
14+
15+
const appId = 'latency';
16+
const apiKey = '6be0576ff61c053d5f9a3225e2a90f76';
17+
const searchClient = algoliasearch(appId, apiKey);
18+
19+
const recentlyViewedItems = createLocalStorageRecentlyViewedItems({
20+
key: 'RECENTLY_VIEWED',
21+
limit: 5,
22+
});
23+
24+
autocomplete({
25+
container: '#autocomplete',
26+
placeholder: 'Search',
27+
openOnFocus: true,
28+
plugins: [recentlyViewedItems],
29+
getSources({ query }) {
30+
if (!query) {
31+
return [];
32+
}
33+
34+
return [
35+
{
36+
sourceId: 'products',
37+
getItems() {
38+
return getAlgoliaHits<ProductItem>({
39+
searchClient,
40+
queries: [
41+
{
42+
indexName: 'instant_search',
43+
query,
44+
params: {
45+
clickAnalytics: true,
46+
attributesToSnippet: ['name:10', 'description:35'],
47+
snippetEllipsisText: '…',
48+
},
49+
},
50+
],
51+
});
52+
},
53+
onSelect({ item }) {
54+
recentlyViewedItems.data.addItem({
55+
id: item.objectID,
56+
label: item.name,
57+
image: item.image,
58+
});
59+
},
60+
templates: {
61+
header() {
62+
return (
63+
<Fragment>
64+
<span className="aa-SourceHeaderTitle">Products</span>
65+
<div className="aa-SourceHeaderLine" />
66+
</Fragment>
67+
);
68+
},
69+
item({ item }) {
70+
return <AutocompleteProductItem hit={item} />;
71+
},
72+
noResults() {
73+
return (
74+
<div className="aa-ItemContent">No products for this query.</div>
75+
);
76+
},
77+
},
78+
},
79+
];
80+
},
81+
});
82+
83+
type ProductItemProps = {
84+
hit: ProductHit;
85+
};
86+
87+
function AutocompleteProductItem({ hit }: ProductItemProps) {
88+
return (
89+
<Fragment>
90+
<div className="aa-ItemIcon aa-ItemIcon--align-top">
91+
<img src={hit.image} alt={hit.name} width="40" height="40" />
92+
</div>
93+
<div className="aa-ItemContent">
94+
<div className="aa-ItemContentTitle">
95+
{highlightHit<ProductHit>({ hit, attribute: 'name' })}
96+
</div>
97+
</div>
98+
<div className="aa-ItemActions">
99+
<button
100+
className="aa-ItemActionButton aa-TouchOnly aa-ActiveOnly"
101+
type="button"
102+
title="Select"
103+
>
104+
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
105+
<path d="M18.984 6.984h2.016v6h-15.188l3.609 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.609 3.609h13.172v-4.031z" />
106+
</svg>
107+
</button>
108+
</div>
109+
</Fragment>
110+
);
111+
}

examples/recently-viewed-items/env.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { h } from 'preact';
2+
3+
// Parcel picks the `source` field of the monorepo packages and thus doesn't
4+
// apply the Babel config to replace our `__DEV__` global expression.
5+
// We therefore need to manually override it in the example app.
6+
// See https://twitter.com/devongovett/status/1134231234605830144
7+
(global as any).__DEV__ = process.env.NODE_ENV !== 'production';
8+
(global as any).__TEST__ = false;
9+
(global as any).h = h;
228 KB
Loading
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<html lang="en">
2+
<head>
3+
<meta charset="UTF-8" />
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
5+
6+
<link rel="shortcut icon" href="favicon.png" type="image/x-icon" />
7+
<link rel="stylesheet" href="style.css" />
8+
9+
<title>Recently Viewed Items Sandbox</title>
10+
</head>
11+
12+
<body>
13+
<div class="container">
14+
<div id="autocomplete"></div>
15+
</div>
16+
17+
<script src="env.ts"></script>
18+
<script src="app.tsx"></script>
19+
</body>
20+
</html>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@algolia/recently-viewed-items-example",
3+
"description": "Autocomplete Recently Viewed Items Sandbox",
4+
"version": "1.0.0-alpha.44",
5+
"private": true,
6+
"license": "MIT",
7+
"main": "index.html",
8+
"scripts": {
9+
"build": "parcel build index.html",
10+
"start": "parcel index.html"
11+
},
12+
"dependencies": {
13+
"@algolia/autocomplete-js": "1.0.0-alpha.44",
14+
"@algolia/autocomplete-plugin-recent-searches": "1.0.0-alpha.44",
15+
"@algolia/autocomplete-theme-classic": "1.0.0-alpha.44",
16+
"@algolia/client-search": "4.8.3",
17+
"algoliasearch": "4.8.3",
18+
"preact": "10.5.7"
19+
},
20+
"devDependencies": {
21+
"parcel-bundler": "1.12.4"
22+
},
23+
"keywords": [
24+
"algolia",
25+
"autocomplete",
26+
"javascript"
27+
]
28+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/** @jsx h */
2+
import { AutocompletePlugin, highlightHit } from '@algolia/autocomplete-js';
3+
import {
4+
createLocalStorageRecentSearchesPlugin,
5+
search,
6+
} from '@algolia/autocomplete-plugin-recent-searches';
7+
import { h, Fragment } from 'preact';
8+
9+
type RecentlyViewedItem = {
10+
id: string;
11+
label: string;
12+
image: string;
13+
_highlightResult: {
14+
label: {
15+
value: string;
16+
};
17+
};
18+
};
19+
20+
type CreateLocalStorageRecentlyViewedItemsParams<TItem> = {
21+
key: string;
22+
limit?: number;
23+
search?(params: any): any[];
24+
};
25+
26+
type RecentlyViewedItemsPluginData<TItem> = {
27+
addItem(item: TItem): void;
28+
removeItem(id: string): void;
29+
getAll(query?: string): any[];
30+
};
31+
32+
export function createLocalStorageRecentlyViewedItems<
33+
TItem extends RecentlyViewedItem
34+
>(
35+
params: CreateLocalStorageRecentlyViewedItemsParams<TItem>
36+
): AutocompletePlugin<TItem, RecentlyViewedItemsPluginData<TItem>> {
37+
const {
38+
onReset,
39+
onSubmit,
40+
subscribe,
41+
...plugin
42+
} = createLocalStorageRecentSearchesPlugin({
43+
...params,
44+
search(params) {
45+
if (params.query) {
46+
return [];
47+
}
48+
49+
return search(params);
50+
},
51+
transformSource({ source, onRemove }) {
52+
const transformedSource = params.transformSource
53+
? params.transformSource({ source, onRemove })
54+
: source;
55+
56+
return {
57+
...transformedSource,
58+
getItemUrl({ item }) {
59+
return item.url;
60+
},
61+
templates: {
62+
...transformedSource.templates,
63+
header({ items }) {
64+
if (items.length === 0) {
65+
return null;
66+
}
67+
68+
return (
69+
<Fragment>
70+
<span className="aa-SourceHeaderTitle">Recently viewed</span>
71+
<div className="aa-SourceHeaderLine" />
72+
</Fragment>
73+
);
74+
},
75+
item({ item, createElement }) {
76+
return (
77+
<a className="aa-ItemLink" href={item.url}>
78+
{item.image ? (
79+
<div className="aa-ItemIcon">
80+
<img src={item.image} alt={item.label} />
81+
</div>
82+
) : (
83+
<div className="aa-ItemIcon aa-ItemIcon--no-border">
84+
<svg
85+
viewBox="0 0 24 24"
86+
width="18"
87+
height="18"
88+
fill="currentColor"
89+
>
90+
<path d="M12.516 6.984v5.25l4.5 2.672-0.75 1.266-5.25-3.188v-6h1.5zM12 20.016q3.281 0 5.648-2.367t2.367-5.648-2.367-5.648-5.648-2.367-5.648 2.367-2.367 5.648 2.367 5.648 5.648 2.367zM12 2.016q4.125 0 7.055 2.93t2.93 7.055-2.93 7.055-7.055 2.93-7.055-2.93-2.93-7.055 2.93-7.055 7.055-2.93z" />
91+
</svg>
92+
</div>
93+
)}
94+
95+
<div className="aa-ItemContent">
96+
<div className="aa-ItemContentTitle">
97+
{highlightHit<RecentlyViewedItem>({
98+
hit: item,
99+
attribute: 'label',
100+
createElement,
101+
})}
102+
</div>
103+
</div>
104+
<div className="aa-ItemActions">
105+
<button
106+
className="aa-ItemActionButton"
107+
title="Remove this search"
108+
onClick={(event) => {
109+
event.stopPropagation();
110+
onRemove(item.id);
111+
}}
112+
>
113+
<svg
114+
viewBox="0 0 24 24"
115+
width="18"
116+
height="18"
117+
fill="currentColor"
118+
>
119+
<path d="M18 7v13c0 0.276-0.111 0.525-0.293 0.707s-0.431 0.293-0.707 0.293h-10c-0.276 0-0.525-0.111-0.707-0.293s-0.293-0.431-0.293-0.707v-13zM17 5v-1c0-0.828-0.337-1.58-0.879-2.121s-1.293-0.879-2.121-0.879h-4c-0.828 0-1.58 0.337-2.121 0.879s-0.879 1.293-0.879 2.121v1h-4c-0.552 0-1 0.448-1 1s0.448 1 1 1h1v13c0 0.828 0.337 1.58 0.879 2.121s1.293 0.879 2.121 0.879h10c0.828 0 1.58-0.337 2.121-0.879s0.879-1.293 0.879-2.121v-13h1c0.552 0 1-0.448 1-1s-0.448-1-1-1zM9 5v-1c0-0.276 0.111-0.525 0.293-0.707s0.431-0.293 0.707-0.293h4c0.276 0 0.525 0.111 0.707 0.293s0.293 0.431 0.293 0.707v1zM9 11v6c0 0.552 0.448 1 1 1s1-0.448 1-1v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1zM13 11v6c0 0.552 0.448 1 1 1s1-0.448 1-1v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1z" />
120+
</svg>
121+
</button>
122+
</div>
123+
</a>
124+
);
125+
},
126+
},
127+
};
128+
},
129+
});
130+
const { getAlgoliaSearchParams, ...data } = plugin.data;
131+
132+
return {
133+
...plugin,
134+
data,
135+
};
136+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
* {
2+
box-sizing: border-box;
3+
}
4+
5+
body {
6+
background-color: rgb(244, 244, 249);
7+
color: rgb(65, 65, 65);
8+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
9+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
10+
sans-serif;
11+
-webkit-font-smoothing: antialiased;
12+
-moz-osx-font-smoothing: grayscale;
13+
padding: 1rem;
14+
}
15+
16+
.container {
17+
margin: 0 auto;
18+
max-width: 640px;
19+
width: 100%;
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Hit } from '@algolia/client-search';
2+
3+
export type ProductItem = {
4+
name: string;
5+
image: string;
6+
description: string;
7+
};
8+
9+
export type ProductHit = Hit<ProductItem> & {
10+
__autocomplete_indexName: string;
11+
__autocomplete_queryID: string;
12+
};

0 commit comments

Comments
 (0)