Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Krinkle committed Aug 4, 2024
1 parent 3501115 commit 3ff6b6b
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
cp -R $GITHUB_WORKSPACE/{*.*,assets,coverage,demo,test} .
# Modifications
touch .nojekyll
cp assets/gh-pages.html index.html
cp gh-pages.html index.html
# Push
git config user.name "${GITHUB_ACTOR}" && \
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" && \
Expand Down
60 changes: 50 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,64 @@

## Implementation notes

* The `cache` Map lets us instantly display any previous result in the same browser tab. We follow the [LRU strategy](https://en.wikipedia.org/wiki/Least_recently_used).
### Group by url_without_anchor

For example, if you type `a`, `app`, `apple`, `apples`, and backspace to a shorter previous query, we instantly show those previous results. (No time wasted waiting for re-download of the same results. It also saves client bandwidth and server load.) Or, if you think another word might yield better results and replace it with `banana`, and return to `apple` because that had a better one, we respond instantly.
One of the ways in which minibar provides different (and arguably, better) default settings compared to DocSearch.js, is the removal of duplicate results from the same page.

We keep up to 100 past results in memory. After that, we prioritize keeping the most recently shown data, and delete older unused results. We assume that you're most likely to return to what you've seen most recently. (Maybe not within the last 10, but within 100. Even if you do return to the very first after a hundred, you're likely to pass by more recent ones on the way there. All queries have equal cost.) When we store a new result, or when we re-use an old result, we delete it and re-set it, so that it goes to the "bottom" of the map.
- [Demo: typesense-minibar](https://jquery.github.io/typesense-minibar/)
- [Demo: Comparison to DocSearch.js](https://jquery.github.io/typesense-minibar/demo/compare--docsearch-3.html)

When it is time to delete an old result, we take a key from the "top" of the map, which is either the oldest and never used, or the least recently used.
In minibar, entering "ajax" returns these 5 results:

If we only add new results and reuse results as-is ([FIFO strategy](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics))), that may delete very recently used data.
> Low-Level Interface
>
> 1. jQuery.ajax()
> 2. jQuery.ajaxTransport()
>
> Global Event Handlers
>
> 3. ajaxComplete event
> 4. ajaxSuccess event
> 5. ajaxError event
* The styles for `typesense-minibar` as web component, and `.tsmb-form` class name are kept independent (that is, the web component does not auto-add the class name, nor does it otherwise rely on styles for the class name, and vice versa).
In DocSearch, entering "ajax" returns these 10 results (the first 7 are visible without scrolling):

* Documentation
1. jQuery 3.0 Upgrade Guide
2. jQuery 3.0 Upgrade Guide
3. jQuery 3.0 Upgrade Guide
4. jQuery 3.0 Upgrade Guide
5. jQuery 3.0 Upgrade Guide
* Global Event Handlers
6. ajaxComplete event
7. ajaxComplete event
* Global Event Handlers (contd., after scrolling)
8. ajaxSuccess event
9. ajaxSuccess event
10. ajaxError event

### The `cache` Map

This lets us instantly display any previous result in the same browser tab. We follow the [LRU strategy](https://en.wikipedia.org/wiki/Least_recently_used).

For example, if you type `a`, `app`, `apple`, `apples`, and backspace to a shorter previous query, we instantly show those previous results. (No time wasted waiting for re-download of the same results. It also saves client bandwidth and server load.) Or, if you think another word might yield better results and replace it with `banana`, and return to `apple` because that had a better one, we respond instantly.

We keep up to 100 past results in memory. After that, we prioritize keeping the most recently shown data, and delete older unused results. We assume that you're most likely to return to what you've seen most recently. (Maybe not within the last 10, but within 100. Even if you do return to the very first after a hundred, you're likely to pass by more recent ones on the way there. All queries have equal cost.) When we store a new result, or when we re-use an old result, we delete it and re-set it, so that it goes to the "bottom" of the map.

When it is time to delete an old result, we take a key from the "top" of the map, which is either the oldest and never used, or the least recently used.

If we only add new results and reuse results as-is ([FIFO strategy](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics))), that may delete very recently used data.

### Separate CSS selectors for class and web component

The styles for `typesense-minibar` as web component, and `.tsmb-form` class name are kept independent (that is, the web component does not auto-add the class name, nor does it otherwise rely on styles for the class name, and vice versa).

This is done for two reasons:

1. Avoid selector conflict for Style API.
If we were to add `class="tsmb-form"` in the web component, it would mean `typesense-minibar form` and `.tsmb-form` both match. This makes the `typesense-minibar form` selector impsosible to override in CSS for downstream users, because our defaults for `.tsmb-form` (weight 0010) would continue to "win" the cascade, as being a stronger selector than `typesense-minibar form` (weight 0002).
2. Avoid a FOUC.
The element should render identically and without reflows both before and after JavaScript loads. During local development it's easy to miss a FOUC if it fixes itself once JavaScript loads. By not auto-correcting these, the bugs are more obvious and we fix them.
1. Avoid selector conflict for Style API.
If we were to add `class="tsmb-form"` in the web component, it would mean `typesense-minibar form` and `.tsmb-form` both match. This makes the `typesense-minibar form` selector impsosible to override in CSS for downstream users, because our defaults for `.tsmb-form` (weight 0010) would continue to "win" the cascade, as being a stronger selector than `typesense-minibar form` (weight 0002).
2. Avoid a FOUC.
The element should render identically and without reflows both before and after JavaScript loads. During local development it's easy to miss a FOUC if it fixes itself once JavaScript loads. By not auto-correcting these, the bugs are more obvious and we fix them.

## Internal API

Expand Down
19 changes: 0 additions & 19 deletions assets/gh-pages.html

This file was deleted.

4 changes: 2 additions & 2 deletions demo/demo-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

header {
background: #390f39;
color: #fff;
color: #fbdbfb;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);

display: flex;
Expand All @@ -19,6 +19,7 @@ header > * {

.demo-logo {
background-image: url(../assets/logo-text-dark.svg);
color: #fff;
}

header .tsmb-form,
Expand All @@ -27,7 +28,6 @@ header typesense-minibar {
--tsmb-color-base30: var(--tsmb-color-primary90);
--tsmb-color-base50: #c090c0;
--tsmb-color-base90: #c090c0;
--tsmb-size-listbox-width: calc(min(50rem, 80vw));
}
header .tsmb-form,
header typesense-minibar form {
Expand Down
38 changes: 35 additions & 3 deletions demo/demo.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ main,
text-indent: -9999px;
overflow: hidden;
}
.demo-caption {
flex-grow: 1;
}
.demo-head-right .demo-caption {
text-align: right;
}

/* Layout: Push demo-stylecontrol to the bottom. */
body {
Expand All @@ -53,18 +59,44 @@ main {
flex-grow: 1;
}

.demo-custom-form {
.tsmb-form.demo-form-size {
--tsmb-size-input: 2rem;
--tsmb-size-sm: 0.5rem;
--tsmb-size-radius: 0px;
--tsmb-size-listbox-width: calc(min(50rem, 80vw));
width: 30rem;
}

html:has(.demo-form-overlay:focus-within) {
/* disable scrollbar */
overflow: hidden;
}
typesense-minibar.demo-form-overlay:focus-within {
z-index: 1;
position: fixed;
inset: 0px;
background: #f4f4f4;
padding: var(--tsmb-size-sm) 0;
}
typesense-minibar.demo-form-overlay:focus-within form {
width: 30rem;
margin: 0 auto;
}

.demo-right-form {

.demo-head-right typesense-minibar,
.demo-head-right .tsmb-form {
margin-left: auto;
--tsmb-size-listbox-right: 0;
}

.demo-stylecontrol fieldset {
background: #fff;
}
@media (min-height: 600px) {
.demo-stylecontrol {
position: sticky;
bottom: 0;
background: #f4f4f4;
box-shadow: 0 -1px 2px rgba(0,0,0,0.24)
}
}
45 changes: 27 additions & 18 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,51 @@
<input type="hidden" name="sites" value="qunitjs.com">
</form>
</typesense-minibar>
<span class="demo-caption">Themed (Web Component)</span>
</header>
<header>
<span class="demo-logo"></span>
<!-- HTML class, with data-group=true, and right aligned. -->
<form role="search" class="tsmb-form demo-right-form" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</header>
<header>
<span class="demo-logo"></span>
<!-- Web Component class, with data-group=true, and right aligned. -->
<typesense-minibar class="demo-right-form" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true">
<header class="demo-head-right">
<a href="../" class="demo-logo"></a>
<span class="demo-caption">Themed (Web Component, group=true, right aligned)</span>
<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true">
<form role="search" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</typesense-minibar>
</header>
<header class="demo-head-right">
<a href="../" class="demo-logo"></a>
<span class="demo-caption">Themed (HTML class, group=true, right aligned)</span>
<form role="search" class="tsmb-form" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</header>
<main>
<h2>Web Component</h2>
<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="qunitjs_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true">
<h2>Web Component: Default</h2>
<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="qunitjs_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz">
<form role="search" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="qunitjs.com">
</form>
</typesense-minibar>

<h2>HTML class</h2>
<form role="search" class="tsmb-form" data-origin="https://typesense.jquery.com" data-collection="qunitjs_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" action="https://duckduckgo.com">
<h2>HTML class: Default</h2>
<form role="search" class="tsmb-form" data-origin="https://typesense.jquery.com" data-collection="qunitjs_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="qunitjs.com">
</form>

<h2>Style API (customize --tsmb-size-* variables)</h2>
<form role="search" class="tsmb-form demo-custom-form" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" action="https://duckduckgo.com">
<h2>Style API (fullscreen overlay, group=true, Web Component)</h2>
<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" class="demo-form-overlay">
<form role="search" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</typesense-minibar>

<h2>Style API (customized --tsmb-size-* variables)</h2>
<form role="search" class="tsmb-form demo-form-size" data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-foot="true" data-group="true" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
Expand Down
34 changes: 34 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>typesense-minibar</title>
<link rel="icon" href="./assets/logo.svg">
<link rel="stylesheet" href="./demo/demo.css">
<script defer type="module" src="../typesense-minibar.js"></script>
<link rel="stylesheet" href="../typesense-minibar.css">
<body>
<main>
<p align="center"><a href="https://github.com/jquery/typesense-minibar/" title="Browse repository"><img src="./assets/logo-text.svg" height="100" alt="minibar"></a></p>
<p><strong>minibar</strong> is a fast 2kB autocomplete search bar. Alternative to Algolia DocSearch, InstantSearch, autocomplete-js, and typesense-js.</p>

<typesense-minibar data-origin="https://typesense.jquery.com" data-collection="jquery_com" data-key="Zh8mMgohXECel9wjPwqT7lekLSG3OCgz" data-group="true" data-foot="true">
<form role="search" action="https://duckduckgo.com">
<input type="search" name="q" aria-label="Search" placeholder="Search..." autocomplete="off">
<input type="hidden" name="sites" value="jquery.com">
</form>
</typesense-minibar>

<h2>Pages</h2>
<ul>
<li><a href="./demo/">Demos & Comparisons</a></li>
<li><a href="./test/">QUnit test suite</a></li>
<li><a href="./coverage/">Test coverage report</a></li>
</ul>

<h2>External link</h2>
<ul>
<li><a href="https://github.com/jquery/typesense-minibar/">Documentation and view source</a></li>
</ul>
</main>
</body>
6 changes: 4 additions & 2 deletions typesense-minibar.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ typesense-minibar {
--tsmb-color-primary30: #390f39;
--tsmb-color-primary50: #9c3493;
--tsmb-color-primary90: #fbdbfb;

max-width: 100%;
}

.tsmb-form,
Expand Down Expand Up @@ -88,7 +90,7 @@ typesense-minibar form::before {
content: '';
background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='654 -372 1664 1664' width='20' height='20'><path d='M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5 C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5 C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342 c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332 s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225 S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z'/></svg>") 0 50% / contain no-repeat;
position: absolute;
top: calc(var(--tsmb-size-sm) + var(--tsmb-size-edge));
top: calc(var(--tsmb-size-edge) + var(--tsmb-size-sm));
left: var(--tsmb-size-sm);
width: var(--tsmb-size-base);
height: var(--tsmb-size-input);
Expand All @@ -104,7 +106,7 @@ typesense-minibar form::before {
.tsmb-icon-close {
box-sizing: border-box;
position: absolute;
top: calc(50% - var(--tsmb-size-base)/2);
top: calc(var(--tsmb-size-edge) + var(--tsmb-size-sm) + (var(--tsmb-size-input)/2) - (var(--tsmb-size-base)/2));
right: var(--tsmb-size-base);
width: var(--tsmb-size-base);
height: var(--tsmb-size-base);
Expand Down
11 changes: 7 additions & 4 deletions typesense-minibar.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ globalThis.tsminibar = function tsminibar (form, dataset = form.dataset) {
const query = state.query = input.value;
if (!query) {
state.hits = [];
state.cursor = -1;
return close();
}
const hits = await search(query);
Expand All @@ -62,7 +61,10 @@ globalThis.tsminibar = function tsminibar (form, dataset = form.dataset) {
if (!e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
if (e.code === 'ArrowDown') moveCursor(1);
if (e.code === 'ArrowUp') moveCursor(-1);
if (e.code === 'Escape') close();
if (e.code === 'Escape') {
close();
input.blur();
}
if (e.code === 'Enter') {
const url = state.hits[state.cursor]?.url;
if (url) location.href = url;
Expand All @@ -72,11 +74,12 @@ globalThis.tsminibar = function tsminibar (form, dataset = form.dataset) {
form.addEventListener('submit', function (e) {
e.preventDefault();
});
form.insertAdjacentHTML('beforeend', '<svg viewBox="0 0 12 12" width="20" height="20" aria-hidden="true" class="tsmb-icon-close" style="display: none;"><path d="M9 3L3 9M3 3L9 9"/></svg>');
form.insertAdjacentHTML('beforeend', '<svg viewBox="0 0 12 12" width="20" height="20" aria-hidden="true" tabindex="0" class="tsmb-icon-close" style="display: none;"><path d="M9 3L3 9M3 3L9 9"/></svg>');
form.querySelector('.tsmb-icon-close').addEventListener('click', function () {
input.value = '';
state.hits = [];
close()
input.focus();
close();
});
connect();

Expand Down

0 comments on commit 3ff6b6b

Please sign in to comment.