Skip to content

Commit

Permalink
POC Recent searches (#244)
Browse files Browse the repository at this point in the history
A potential idea to close #33 

This obviously needs some polish when it comes to styling, and a few
implementation details. But what do you think of the overall concept? I
tried following the Algolia example when it comes to behaviour (storing
only the searches that were actually clicked).

---------

Co-authored-by: Manuel Kaufmann <[email protected]>
Co-authored-by: Anthony <[email protected]>
  • Loading branch information
3 people authored Mar 7, 2024
1 parent b037918 commit 3d02c7c
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 19 deletions.
20 changes: 10 additions & 10 deletions dist/readthedocs-addons.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/readthedocs-addons.js.map

Large diffs are not rendered by default.

40 changes: 33 additions & 7 deletions src/search.css
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,44 @@ div.hit-block a.hit-block-heading {
align-items: center;
}

div.hit-block a.hit-block-heading i {
div.hit-block .hit-block-heading-container {
display: flex;
align-items: center;
justify-content: space-between;
}

div.hit-block a.hit-block-heading i,
div.hit-block .hit-block-heading-container .close-icon {
font-size: 1.15em;
width: 1em;
padding-right: 10px;
margin-bottom: 15px;
color: #333;
}

div.hit-block a.hit-block-heading i svg {
button.close-icon {
border: none;
margin: 0;
padding: 0;
padding-right: 10px;
margin-bottom: 15px;
overflow: visible;

display: inline-block;
width: 1em;
height: 1em;
vertical-align: middle;

background: transparent;
cursor: pointer;
}

button.close-icon svg {
pointer-events: none;
}

div.hit-block a.hit-block-heading i svg,
div.hit-block .hit-block-heading-container svg {
width: 1em;
}

Expand All @@ -159,7 +188,8 @@ div.hit-block a.hit-block-heading i svg {
box-sizing: border-box;
}

:host > div .results a.hit:hover {
:host > div .results a.hit:hover,
:host > div .results .hit .active {
background-color: rgb(245, 245, 245);
}

Expand Down Expand Up @@ -221,10 +251,6 @@ div.hit-block a.hit-block-heading i svg {
font-weight: bold;
}

:host > div .results .hit .active {
background-color: rgb(245, 245, 245);
}

:host div.content > div.footer {
width: 100%;
display: inline-block;
Expand Down
104 changes: 103 additions & 1 deletion src/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ajv } from "./data-validation";
import { library, icon } from "@fortawesome/fontawesome-svg-core";
import {
faCircleXmark,
faClockRotateLeft,
faMagnifyingGlass,
faCircleNotch,
faBinoculars,
Expand Down Expand Up @@ -61,6 +62,7 @@ export class SearchElement extends LitElement {
library.add(faCircleNotch);
library.add(faBinoculars);
library.add(faBarsStaggered);
library.add(faCircleXmark);

this.config = null;
this.show = false;
Expand All @@ -73,6 +75,8 @@ export class SearchElement extends LitElement {
this.triggerKeycode = 191;
this.triggerSelector = null;
this.triggerEvent = "focusin";
this.recentSearchesLocalStorageKey = "readthedocsSearchRecentSearches";
this.recentSearchesLocalStorageLimit = 20; // Control how many recent searches we store in localStorage
}

loadConfig(config) {
Expand Down Expand Up @@ -136,7 +140,9 @@ export class SearchElement extends LitElement {
/>
</form>
<div class="filters">${this.renderFilters()}</div>
<div class="results">${this.results}</div>
<div class="results">
${this.results || this.renderRecentSearches()}
</div>
<div class="footer">
<ul class="help">
<li><code>Enter</code> to select</li>
Expand Down Expand Up @@ -284,6 +290,7 @@ export class SearchElement extends LitElement {
<a
@click=${this.followResultLink}
@mouseenter=${this.mouseenterResultHit}
@click=${() => this.storeRecentSearch(block, result)}
class="hit"
href="${result.path}#${block.id}"
>
Expand All @@ -295,6 +302,101 @@ export class SearchElement extends LitElement {
`;
}

renderRecentSearches() {
const recentSearches = this.getRecentSearches();
if (!recentSearches || !recentSearches.length) {
return html`<p>No recent searches</p>`;
}
recentSearches.reverse();
const listIcon = icon(faClockRotateLeft, {
title: "Result",
classes: ["header", "icon"],
});

const xmark = icon(faCircleXmark, {
title: "Clear recent search",
classes: ["header", "icon"],
});

return html`
<div class="hit">
<p>Recent:</p>
${recentSearches.map(
({ block, result }) =>
html`<div class="hit-block">
<div class="hit-block-heading-container">
<a class="hit-block-heading" href="${result.path}">
<i>${listIcon.node[0]}</i>
<h2>${result.title} ${this.renderExternalProject(result)}</h2>
</a>
<button
class="close-icon"
@click=${() => this.removeRecentSearch(block, result)}
>
${xmark.node[0]}
</button>
</div>
${html`${this.renderBlockResult(
block,
`recent-search-${block.id}`,
result,
)}`}
</div>`,
)}
</div>
`;
}

getRecentSearches() {
const recentSearchesString = localStorage.getItem(
this.recentSearchesLocalStorageKey,
);
return recentSearchesString ? JSON.parse(recentSearchesString) : [];
}

storeRecentSearch(block, result) {
const recentSearches = this.getRecentSearches().filter((recentSearch) => {
const b = recentSearch.block;
const r = recentSearch.result;
// Remove any duplicates, since this search result will be appended again
return (
r.domain !== result.domain || r.path !== r.path || b.id !== block.id
);
});

recentSearches.push({ block, result });
let recentSearchesLimited = recentSearches;
// If we've stored more results than the limit, let's slice to get rid of the oldest result first
if (recentSearches.length > this.recentSearchesLocalStorageLimit) {
recentSearchesLimited = recentSearches.slice(
recentSearches.length - this.recentSearchesLocalStorageLimit,
);
}

localStorage.setItem(
this.recentSearchesLocalStorageKey,
JSON.stringify(recentSearchesLimited),
);
}

removeRecentSearch(block, result) {
const recentSearches = this.getRecentSearches().filter((recentSearch) => {
const b = recentSearch.block;
const r = recentSearch.result;
// Return everything except this search result
return (
r.domain !== result.domain || r.path !== r.path || b.id !== block.id
);
});

localStorage.setItem(
this.recentSearchesLocalStorageKey,
JSON.stringify(recentSearches),
);
this.requestUpdate();
}

renderExternalProject(result) {
if (result.project.slug !== this.config.projects.current.slug) {
return html`
Expand Down

0 comments on commit 3d02c7c

Please sign in to comment.