diff --git a/.github/workflows/doc-search.yaml b/.github/workflows/doc-search.yaml deleted file mode 100644 index ef60205..0000000 --- a/.github/workflows/doc-search.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Update search index -on: - # For Algolia, we can update search right away in parallel - # with the github-pages build, since it pushes content - # into the index from the local working directory. - push: - branches: - - main - # For MeiliSearch, we crawl the site after it has been - # built and published by the github-pages workflow - workflow_run: - workflows: - - github-pages - types: - - completed - # Or manually - workflow_dispatch: - -jobs: - run: - name: Algolia - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - - uses: ruby/setup-ruby@v1 - # If your site is in a subdirectory - # working-directory: ./ - with: - ruby-version: 2.7 - bundler-cache: true - - - run: bundle exec jekyll algolia - # If your site is in a subdirectory - # working-directory: ./ - env: - ALGOLIA_API_KEY: "${{ secrets.ALGOLIA_API_KEY }}" diff --git a/.meilisearch.json b/.meilisearch.json deleted file mode 100644 index 03c15b7..0000000 --- a/.meilisearch.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "index_uid": "jekyll-theme-amethyst", - "start_urls": ["https://qunitjs.github.io/jekyll-theme-amethyst/"], - "stop_urls": [], - "selectors": { - "default": { - "lvl0": { - "selector": ".sidebar-title-open", - "global": true, - "default_value": "Documentation" - }, - "lvl1": "h1", - "lvl2": "h2", - "lvl3": "h3", - "lvl4": "h4", - "lvl5": "h5", - "content": "p,li,tr,pre" - } - } -} diff --git a/Gemfile b/Gemfile index 49b345c..5d5c480 100644 --- a/Gemfile +++ b/Gemfile @@ -2,5 +2,3 @@ source "https://rubygems.org" ruby RUBY_VERSION gemspec - -gem "jekyll-algolia", "~> 1.7.0", group: :jekyll_plugins diff --git a/_config.yml b/_config.yml index 7deb09b..30e8a6e 100644 --- a/_config.yml +++ b/_config.yml @@ -45,9 +45,11 @@ amethyst: release_base: https://github.com/qunitjs/qunit/releases/tag/ github: qunitjs gitter: qunitjs/qunit - # https://github.com/qunitjs/jekyll-theme-amethyst/blob/main/docs/getting-started.md#enable-algolia-search - algolia: - search_only_api_key: f93ac48385f4f5866ffe9227829b329e + # https://github.com/qunitjs/jekyll-theme-amethyst/blob/main/docs/getting-started.md#enable-typesense + typesense: + origin: https://typesense.jquery.com + collection: amethyst_demo + search_only_api_key: Zh8mMgohXECel9wjPwqT7lekLSG3OCgz # Conversion settings @@ -67,19 +69,6 @@ sass: sourcemap: never -# Backend search settings -# -# Docs: https://github.com/algolia/jekyll-algolia -algolia: - application_id: 2Q66ROFTLQ - index_name: amethyst-demo - # By default only HTML paragraphs are indexed (and headings, albeit through a different mechanism). - # * Include list items (similar to paragraphs). - # * Include tables (index per row for better excerpts). - # * Include
 (typically code examples, omit if it "poisons" results too much).
-  nodes_to_index: "p,li,tr,pre"
-
-
 # Blog archives
 #
 # Docs: https://github.com/jekyll/jekyll-archives/
diff --git a/_includes/search-js.html b/_includes/search-js.html
deleted file mode 100644
index d33e7d7..0000000
--- a/_includes/search-js.html
+++ /dev/null
@@ -1,113 +0,0 @@
-{%- comment -%}
-
-For releases and integrity hashes (SRI), see:
-* https://www.jsdelivr.com/package/npm/algoliasearch
-* https://www.jsdelivr.com/package/npm/autocomplete.js
-
-We use type="module" as a natural way to cut the mustard,
-executing the script only on modern browsers with ES6 support,
-and causing no errors on older browsers.
-
-
-
-
-{%- endcomment -%}
-
diff --git a/_includes/search.html b/_includes/search.html
index e0e9e2c..717a466 100644
--- a/_includes/search.html
+++ b/_includes/search.html
@@ -1,14 +1,6 @@
-
 
-{%- comment -%}
 
-See also search-js.html
-
-{%- endcomment -%}
diff --git a/_layouts/wrapper.html b/_layouts/wrapper.html
index 9aada1e..3b952cc 100644
--- a/_layouts/wrapper.html
+++ b/_layouts/wrapper.html
@@ -15,9 +15,7 @@
                 {{ site.title | escape }}
                 {%- endif -%}
             
-            {%- if site.amethyst.algolia.search_only_api_key -%}
-                {%- include search.html -%}
-            {%- endif -%}
+            {%- include search.html -%}
             
             
- {%- if site.amethyst.algolia.search_only_api_key %} - - {%- endif %}
-{%- if site.amethyst.algolia.search_only_api_key -%} - {%- include search-js.html -%} +{%- comment -%} + +We use type="module" as a natural way to cut the mustard, +executing the script only on modern browsers with ES6 support, +and causing no errors on older browsers. + + + + +{%- endcomment -%} +{%- if site.amethyst.typesense.search_only_api_key -%} + {%- endif -%} diff --git a/_sass/amethyst.scss b/_sass/amethyst.scss index 8d66910..e34087c 100644 --- a/_sass/amethyst.scss +++ b/_sass/amethyst.scss @@ -131,7 +131,9 @@ iframe { .wrapper { max-width: 65rem; margin: 0 auto; - padding: 0 $size-spacing; + @media (min-width: $screen-m) { + padding: 0 $size-spacing; + } } .main { @@ -289,6 +291,8 @@ table { .site-header-wrapper { display: flex; + flex-flow: row wrap; + gap: 0 $size-spacing; justify-content: space-between; position: relative; } @@ -299,7 +303,7 @@ table { align-items: center; font-size: $size-2; font-weight: bold; - padding: $size-1 0; + padding: $size-1 0 $size-1 $size-1; text-decoration: none; transition: color 0.3s; @@ -445,227 +449,34 @@ table { /* Search */ -.site-search { - display: none; - position: absolute; - top: 100%; - left: 0; - width: 100vw; - height: 100%; - z-index: 1; - border-top: 1px solid $color-off-white; - box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23); - - &.opened { - display: block; - } - - @media (min-width: $screen-m) { - display: block; - align-self: center; - position: relative; - flex: 1; - max-width: 20rem; - margin: 0 $size-spacing; - border: none; - box-shadow: none; - } -} - -.algolia-autocomplete { - width: 100%; - height: 100%; -} - -.aa-input-search { - width: 100%; - height: 100%; - padding: 12px 28px 12px 12px; - border: none; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - font-size: $size-1; - - &::-webkit-search-decoration, - &::-webkit-search-cancel-button, - &::-webkit-search-results-button, - &::-webkit-search-results-decoration { - display: none; - } +.tsmb-form { + --tsmb-color-base-background: #{lighten($color-accent, 12%)}; + --tsmb-color-primary30: #{$color-accent}; + --tsmb-color-primary50: #{$color-vibrant}; + --tsmb-color-primary90: #{$color-bright}; + --tsmb-color-base30: var(--tsmb-color-primary90); + --tsmb-color-base50: #{change-color($color-bright, $alpha: 0.6)}; // #a98dc1 + --tsmb-color-base90: #{change-color($color-bright, $alpha: 0.6)}; - @media (min-width: $screen-m) { - border-radius: 3px; - &:not(:focus) { - background: lighten($color-accent, 12%); - color: $color-white; - } - } + width: auto; + flex: 1; + align-self: center; } - -// On wide viewports, when the search field is not focussed, -// the search field has a dark background. Override the default -// user agent styles for placeholders (which are often mid-grey, -// and thus blend into the background too much) to use a bright -// color instead. -// -// When focussed, or when toggling on narrow viewports, the search -// field has a bright background, so don't apply this there! -@media (min-width: $screen-m) { - .site-search:not(:focus-within) .aa-input-search::placeholder { - color: $color-bright; - opacity: 0.6; - } +.tsmb-form:not(:focus-within)::before { + filter: unquote("invert()"); } - -// Only displayed when the optional JS code was supported in the current browser -// and run to completion. Also, don't show on narrow viewports (e.g. mobile) -// where keyboard shortcuts are typically not used. -// Avoid using device type or pointer support, because one can have a large -// "mobile"-like device with external keyboard. -.site-search--bound:not(:focus-within):after { - @media (min-width: $screen-m) { - content: '/'; - display: inline-block; - - font-size: 60%; - line-height: 2.5; - text-align: center; - width: 2.5em; - height: 2.5em; - position: absolute; - top: 50%; - right: 48px; - transform: translateY(-50%); - - border-radius: 3px; - border: 1px solid $color-bright; - color: $color-bright; - opacity: 0.6; - } +.tsmb-form input[type="search"] { + border: none; } - -.aa-input-icon { - width: 16px; - height: 16px; - position: absolute; - top: 50%; - right: 16px; - transform: translateY(-50%); - fill: $color-accent; - - @media (min-width: $screen-m) { - .site-search:not(:focus-within) & { - fill: $color-bright; - } - } -} - -.aa-dropdown-menu { - background: $color-white; - border-top: 1px solid $color-off-white; - width: 100%; - max-height: 60vh; - overflow: auto; - box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); - - @media (min-width: $screen-m) { - border-radius: 3px; - border-top: none; - min-width: 500px; - margin-top: $size-spacing; - } -} - -.aa-footer { - // A sticky element is normally at the start of the content, - // but since this is a footer, the space of the sticky element - // is naturally preserved at the end of the list and thus won't - // overlap the last suggestion. - // If this were a ::before or -header instead, then this would need: - // .aa-header { - // margin-top: -2.4rem; - // } - // - // and - // - // .aa-dropdown-menu::after { - // display: block; - // content: ""; - // height: 2.4rem; - // } - position: sticky; - left: 0; - right: 0; - box-shadow: 0 0 6px rgba(0,0,0,0.19); - - top: calc(100% - 2.4rem); - padding: 0.8rem 1rem; - font-size: $size-sm; - line-height: 1; - - color: $color-darkgrey; - text-align: right; - - .aa-logo { - // Replace alt text with image - line-height: 0; - font-size: 0; - } - .aa-logo::after { - content: ''; - display: inline-block; - vertical-align: top; - // Nominal size is 485x120 - background: url(./logo-algolia.svg); - width: calc((485 / 120) * 0.8rem); - height: 0.8rem; - } -} - -.aa-empty { - padding: $size-spacing; -} - -.aa-suggestion { - padding: $box-spacing; - cursor: pointer; - border-top: 1px solid $color-off-white; - border-left: 2px solid transparent; - - a { - // reset default link style - text-decoration: none; - } - - &:hover, - &.aa-cursor { - background: $color-bright; - border-left: 2px solid $color-vibrant; +@media (max-width: $screen-m) { + .tsmb-form { + display: none; + flex: 100%; + order: 10; + --tsmb-size-input: calc( var(--tsmb-size-base) * 2.0 ); } } -.ais-Highlight { - font-style: normal; - font-weight: bold; -} - -.aa-suggestion_title, -.aa-suggestion_content { - // reset paragraph margin - margin: 0; - // clip title chunks and content match to one line - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.aa-suggestion_title { - color: $color-accent; -} -.aa-suggestion_content { - font-size: $size-sm; - color: $color-darkgrey; -} /* Site header: Mobile toggle controls */ @@ -698,7 +509,7 @@ table { transform: none; } -.opened .icon { +.toggle[aria-expanded="true"] .icon { fill: $color-white; } diff --git a/assets/logo-algolia.svg b/assets/logo-algolia.svg deleted file mode 100644 index 39b77d5..0000000 --- a/assets/logo-algolia.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/styles.scss b/assets/styles.scss index 0ba22e7..06fc4ba 100644 --- a/assets/styles.scss +++ b/assets/styles.scss @@ -1,6 +1,8 @@ --- --- +@use "typesense-minibar.css"; + @import "amethyst-variables"; @import "amethyst"; @import "highlight"; diff --git a/assets/typesense-minibar.css b/assets/typesense-minibar.css new file mode 100644 index 0000000..4354bf5 --- /dev/null +++ b/assets/typesense-minibar.css @@ -0,0 +1,223 @@ +/*! https://github.com/jquery/typesense-minibar 1.0.1 */ +.tsmb-form { + --tsmb-size-edge: 1px; + --tsmb-size-radius: 3px; + --tsmb-size-highlight: 2px; + --tsmb-size-base: 1rem; + --tsmb-size-sm: 0.8rem; + --tsmb-size-half: calc( var(--tsmb-size-sm) * 0.5 ); + --tsmb-size-input: calc( var(--tsmb-size-base) * 1.2 ); + + --tsmb-color-base-background: #fff; + --tsmb-color-base30: #333; + --tsmb-color-base50: #63676d; + --tsmb-color-base90: #cdcdcd; + --tsmb-color-focus-background: #fff; + --tsmb-color-focus30: #333; + --tsmb-color-focus50: #63676d; + --tsmb-color-focus90: #cdcdcd; + + --tsmb-color-primary30: #390f39; + --tsmb-color-primary50: #9c3493; + --tsmb-color-primary90: #fbdbfb; + + position: relative; + width: 20rem; + max-width: 100%; + color: var(--tsmb-color-base30); +} + +.tsmb-form input[type=search] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + color: inherit; + background: var(--tsmb-color-base-background); + padding: var(--tsmb-size-sm) var(--tsmb-size-sm) var(--tsmb-size-sm) calc(var(--tsmb-size-base) + var(--tsmb-size-sm) + var(--tsmb-size-sm)); + border: var(--tsmb-size-edge) solid var(--tsmb-color-base90); + border-radius: var(--tsmb-size-radius); + font-size: var(--tsmb-size-base); + width: 100%; + line-height: var(--tsmb-size-input); +} + +.tsmb-form input[type=search]::placeholder { + color: var(--tsmb-color-base50); + opacity: 1; +} + +.tsmb-form:focus-within { + color: var(--tsmb-color-focus30); +} +.tsmb-form:focus-within input[type=search] { + background: var(--tsmb-color-focus-background); +} +.tsmb-form:focus-within input[type=search]::placeholder { + color: var(--tsmb-color-focus50); +} + +.tsmb-form input[type=search]::-webkit-search-decoration, +.tsmb-form input[type=search]::-webkit-search-cancel-button, +.tsmb-form input[type=search]::-webkit-search-results-button, +.tsmb-form input[type=search]::-webkit-search-results-decoration { + display: none; +} + +/* input icon */ +.tsmb-form::before { + content: ''; + background: url("data:image/svg+xml,") 0 50% / contain no-repeat; + position: absolute; + top: calc(var(--tsmb-size-sm) + var(--tsmb-size-edge)); + left: var(--tsmb-size-base); + width: var(--tsmb-size-base); + height: var(--tsmb-size-input); + opacity: 0.5; +} + +.tsmb-icon-close { + position: absolute; + top: calc(var(--tsmb-size-sm) + var(--tsmb-size-edge)); + right: var(--tsmb-size-base); + width: var(--tsmb-size-base); + height: var(--tsmb-size-base); + + stroke: currentColor; + border: 1px solid currentColor; + border-radius: 50%; + cursor: pointer; +} + +.tsmb-form--open .tsmb-icon-close { + display: block !important; +} + +.tsmb-form--slash:not(.tsmb-form--open):not(:focus-within)::after { + content: '/'; + display: inline-block; + position: absolute; + top: calc(var(--tsmb-size-sm) + var(--tsmb-size-edge) + (var(--tsmb-size-input) / 2) - 1em - var(--tsmb-size-edge)); + right: var(--tsmb-size-base); + width: 2em; + height: 2em; + + font-size: var(--tsmb-size-sm); + line-height: 2; + text-align: center; + + border: var(--tsmb-size-edge) solid var(--tsmb-color-base90); + border-radius: var(--tsmb-size-radius); + color: var(--tsmb-color-base90); +} + +.tsmb-form [role=listbox] { + position: absolute; + z-index: 10; + + background: var(--tsmb-color-focus-background); + color: var(--tsmb-color-focus30); + width: 100%; + max-height: 70vh; + overflow: auto; + border: var(--tsmb-size-edge) solid var(--tsmb-color-focus90); + box-shadow: 0 var(--tsmb-size-sm) 20px rgba(0,0,0,0.12); +} + +.tsmb-suggestion_group { + margin: var(--tsmb-size-sm) var(--tsmb-size-base) 0 var(--tsmb-size-base); + border-bottom: var(--tsmb-size-edge) solid var(--tsmb-color-focus90); +} + +.tsmb-form [role=option] a { + display: block; + padding: var(--tsmb-size-base); + text-decoration: none; + border-left: var(--tsmb-size-highlight) solid transparent; +} +.tsmb-form:not([data-group=true]) [role=option]:not(:first-child) a { + border-top: var(--tsmb-size-edge) solid var(--tsmb-color-focus90); +} +.tsmb-form[data-group=true] [role=option] a { + margin: 0 var(--tsmb-size-base); + padding: var(--tsmb-size-sm); +} + +.tsmb-form [role=option] a:hover, +.tsmb-form [role=option][aria-selected=true] a { + background: var(--tsmb-color-primary90); + border-left-color: var(--tsmb-color-primary50); +} + +.tsmb-form [role=option] mark { + background: none; + color: inherit; + font-style: normal; + font-weight: bold; +} + +.tsmb-suggestion_group, +.tsmb-suggestion_title, +.tsmb-suggestion_content { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.tsmb-suggestion_title { + color: var(--tsmb-color-primary30); +} +.tsmb-form[data-group=true] .tsmb-suggestion_title { + font-weight: bold; + font-size: var(--tsmb-size-sm); +} + +.tsmb-suggestion_content { + font-size: var(--tsmb-size-sm); + color: var(--tsmb-color-focus50); +} + +.tsmb-empty { + padding: var(--tsmb-size-base); +} + +.tsmb-foot { + display: block; + text-align: right; + font-size: var(--tsmb-size-sm); + line-height: 18px; + padding: var(--tsmb-size-half) var(--tsmb-size-sm); + box-shadow: 0 0 10px rgba(0,0,0,0.12); +} +.tsmb-foot:hover { + text-decoration: none; +} +.tsmb-foot::before { + content: 'Search by'; + color: var(--tsmb-color-focus50); +} +.tsmb-foot::after { + content: ' Typesense'; + color: #0300b0; +} + +@media (max-width: 480px) { + .tsmb-form { + width: 100%; + } + + .tsmb-form input[type=search] { + border-radius: 0; + } +} + +@media (min-width: 768px) { + .tsmb-form [role=listbox] { + border-radius: var(--tsmb-size-radius); + min-width: 500px; + margin-top: var(--tsmb-size-half); + } + + .tsmb-form--slash::after { + display: none; + } +} diff --git a/assets/typesense-minibar.js b/assets/typesense-minibar.js new file mode 100644 index 0000000..6a935d0 --- /dev/null +++ b/assets/typesense-minibar.js @@ -0,0 +1,163 @@ +/*! https://github.com/jquery/typesense-minibar 1.0.1 */ +globalThis.tsminibar = function tsminibar (form) { + const { origin, key, collection } = form.dataset; + const group = !!form.dataset.group; + const cache = new Map(); + const state = { query: '', hits: [], cursor: -1, open: false }; + + const input = form.querySelector('input[type=search]'); + const listbox = document.createElement('div'); + listbox.setAttribute('role', 'listbox'); + listbox.hidden = true; + input.after(listbox); + + let preconnect = null; + input.addEventListener('focus', () => { + if (!preconnect) { + preconnect = document.createElement('link'); + preconnect.rel = 'preconnect'; + preconnect.crossOrigin = 'anonymous'; // for fetch mode:cors,credentials:omit + preconnect.href = origin; + document.head.append(preconnect); + } + if (!state.open && state.hits.length) { + state.open = true; + render(); + } + }); + input.addEventListener('click', () => { + if (!state.open && state.hits.length) { + state.open = true; + render(); + } + }); + input.addEventListener('input', async () => { + const query = state.query = input.value; + if (!query) { + state.hits = []; // don't leak old hits on focus + state.cursor = -1; + close(); + return; + } + const hits = await search(query); + if (state.query === query) { // ignore non-current query + state.hits = hits; + state.cursor = -1; + state.open = true; + render(); + } + }); + input.addEventListener('keydown', (e) => { + 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 === 'Enter') { + const url = state.hits[state.cursor]?.url; + if (url) location.href = url; + } + } + }); + form.addEventListener('submit', (e) => { + e.preventDefault(); // disable fallback + }); + form.insertAdjacentHTML('beforeend', ''); + form.querySelector('.tsmb-icon-close').addEventListener('click', close); + connect(); + + function close () { + if (state.open) { + state.cursor = -1; + state.open = false; + render(); + } + } + + function connect () { + document.addEventListener('click', onDocClick); + if (form.dataset.slash !== 'false') { + document.addEventListener('keydown', onDocSlash); + form.classList.add('tsmb-form--slash'); + } + } + + function disconnect () { + document.removeEventListener('click', onDocClick); + document.removeEventListener('keydown', onDocSlash); + } + + function onDocClick (e) { + if (!form.contains(e.target)) close(); + } + + function onDocSlash (e) { + if (e.key === '/' && !/^(INPUT|TEXTAREA)$/.test(document.activeElement?.tagName)) { + input.focus(); + e.preventDefault(); + } + } + + async function search (query) { + let hits = cache.get(query); + if (hits) { + cache.delete(query); + cache.set(query, hits); // LRU + return hits; + } + const resp = await fetch( + `${origin}/collections/${collection}/documents/search?` + new URLSearchParams({ + q: query, + per_page: '5', + query_by: 'hierarchy.lvl0,hierarchy.lvl1,hierarchy.lvl2,hierarchy.lvl3,hierarchy.lvl4,hierarchy.lvl5,content', + include_fields: 'hierarchy.lvl0,hierarchy.lvl1,hierarchy.lvl2,hierarchy.lvl3,hierarchy.lvl4,hierarchy.lvl5,content,url_without_anchor,url,id', + highlight_full_fields: 'hierarchy.lvl0,hierarchy.lvl1,hierarchy.lvl2,hierarchy.lvl3,hierarchy.lvl4,hierarchy.lvl5,content', + group_by: 'url_without_anchor', + group_limit: '1', + sort_by: 'item_priority:desc', + snippet_threshold: '8', + highlight_affix_num_tokens: '12', + 'x-typesense-api-key': key, + }), + { mode: 'cors', credentials: 'omit', method: 'GET' } + ); + const data = await resp.json(); + let lvl0; + hits = data?.grouped_hits?.map(ghit => { + const hit = ghit.hits[0]; + return { + lvl0: group && lvl0 !== hit.document.hierarchy.lvl0 && (lvl0 = hit.document.hierarchy.lvl0), + title: [!group && hit.document.hierarchy.lvl0, hit.document.hierarchy.lvl1, hit.document.hierarchy.lvl2, hit.document.hierarchy.lvl3, hit.document.hierarchy.lvl4, hit.document.hierarchy.lvl5].filter(lvl => !!lvl).join(' › ') || hit.document.hierarchy.lvl0, + url: hit.document.url, + content: hit.highlights[0]?.snippet || hit.document.content || '' + }; + }) || []; + cache.set(query, hits); + if (cache.size > 100) { + cache.delete(cache.keys().next().value); + } + return hits; + } + + function escape (s) { + return s.replace(/['"<>&]/g, c => ({ "'": ''', '"': '"', '<': '<', '>': '>', '&': '&' }[c])); + } + + function render () { + listbox.hidden = !state.open; + form.classList.toggle('tsmb-form--open', state.open); + if (state.open) { + listbox.innerHTML = (state.hits.map((hit, i) => `
${hit.lvl0 ? `
${hit.lvl0}
` : ''}
${hit.title}
${hit.content}
`).join('') || `
No results for '${escape(state.query)}'.
`) + (form.dataset.foot ? '' : ''); + } + } + + function moveCursor (offset) { + state.cursor += offset; + // -1 refers to input field + if (state.cursor >= state.hits.length) state.cursor = -1; + if (state.cursor < -1) state.cursor = state.hits.length - 1; + render(); + } + + return { form, connect, disconnect }; +}; +document.querySelectorAll('.tsmb-form[data-origin]').forEach(form => tsminibar(form)); diff --git a/docs/config.md b/docs/config.md index 27fbe69..fb8e0c5 100644 --- a/docs/config.md +++ b/docs/config.md @@ -52,63 +52,14 @@ amethyst: github: # Gitter.im room (e.g. "qunitjs/qunit") gitter: - # Frontend search powered by Algolia - algolia: - # Key for client-side search queries (32-character hex token) + # Search powered by Typesense + # + # https://github.com/qunitjs/jekyll-theme-amethyst/blob/main/docs/getting-started.md#enable-typesense + typesense: + origin: + collection: search_only_api_key: - # Defaults to `algolia.application_id` (see below) - application_id: - - # Which indexes to use as autocomplete source - # - # Defaults to a single source based on `algolia.index_name`, - # which means the current site only. - # - # If there are multiple relates sites that you want to give - # a unified search experience (e.g. qunitjs.com and api.qunitjs.com) - # then use this option to explicitly specify the sources. - # In that case, be sure to set `base` for the "other" sites - # as the indexes not aware of their full url (indexing happens - # at built time in CI). Include the "current" site index name as - # well, but without any `base` so that its results are linked - # relative, and thus work as expected when testing the site - # locally, for example. - # - # Example: Multiple sources - # - # ``` - # sources: - # - index: qunitjs-api - # base: https://api.qunitjs.com - # - index: qunitjs - # ``` - sources: - - -# Backend search settings -# -# This applies to the 'jekyll algolia' command, which is typically -# run from an after a commit is merged. The settings are documented at: -# https://github.com/algolia/jekyll-algolia -# -# To learn how to set this up, refer to: -# https://github.com/qunitjs/jekyll-theme-amethyst/blob/main/docs/getting-started.md#enable-algolia-search -# -algolia: - application_id: - # Which index_name the 'jekyll algolia' command will create or update. - index_name: - # By default only HTML paragraphs are indexed (and headings, albeit through a different mechanism). - # * Include list items (similar to paragraphs). - # * Include tables (index per row for better excerpts). - # * Include
 (typically code examples, omit if it "poisons" results too much).
-  nodes_to_index: 'p,li,tr,pre'
-  # Pages not to suggest in search, e.g. if they are overviews
-  # that only point to other pages and have no original content.
-  files_to_exclude:
-    # - something-overview.md
-
 
 # Blog archives
 #
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 27deb16..87dc28d 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -47,31 +47,15 @@ Change the top navigation links, or create sub menus, via [sitenav.yml](https://
 
 See [Manage a custom domain with GitHub Pages](https://docs.github.com/en/free-pro-team@latest/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site).
 
-### Enable Algolia search
+### Enable Typesense
 
-1. Create or browse your application in [Algolia](https://www.algolia.com/). (There is a shared "team" for OpenJS Foundation projects. Ask [OpenJS Operations](https://openjsf.org/about/contact/) for your application to be added to the account.)
-2. Go to "API Keys", and take note of the "Search-Only API Key" and "Admin API Key".
-3. Set the following in your `_config.yml` file:
-   - `algolia.application_id`: Application ID.
-   - `amethyst.algolia.search_only_api_key`: Search-Only API Key.
-   - `algolia.index_name`: Unique lowercase name for the current site (e.g. example-com or example-com-foo).
-     This index will be automatically created by GitHub Actions after the next commit.
-4. Create a repository secret on GitHub, named `ALGOLIA_API_KEY`, with the "Admin API Key".
+Optional autocompletion with search suggestions.
 
-   This is used by the [doc-search](https://github.com/qunitjs/jekyll-theme-amethyst/blob/main/.github/workflows/doc-search.yaml) workflow, which updates the search index after each commit.
+1. Follow [Create scraper](https://github.com/jquery/infrastructure-puppet/blob/staging/doc/search.md). Feel free to ask the jQuery Infrastructure Team for help.
+2. Set the following in your `_config.yml` file:
+   - `amethyst.typesense.origin`: 
+   - `amethyst.typesense.collection`: Unique lowercase name for the current site (e.g. example_com or example_foo).
+     This should match the index name used by your scraper.
+   - `amethyst.typesense.search_only_api_key`: Search-only key (copy from another repo, or consult the jQuery Infrastucture credentials vault.)
 
-Done! The presence of these settings will automatically enable display of the search field.
-
-Terminology:
-
-* **application**:
-  A group of one or more search indexes with an associated "team" of users that can administer those indexes. The application is generally named after a top-level project (e.g. "QUnit") with one or more indexes for its sites (e.g. qunitjs-com and qunitjs-com-api).
-
-  Applications need to be created from the Algolia control panel. The Application IDs are considered public information, and look like `ABCDEF0124`.
-
-* **index**:
-  An index holds the crawled content. These do not need to be created ahead of time. The CI builds will create these as-needed.
-
-  As example, qunitjs.com content may be un a `qunit-com` index, and api.qunitjs.com content under `api-qunitjs-com`. Whenever a site is deployed, the index is replaced with the new content. This is why different subdomains or subprojects that have their own repository should have their own index as otherwise content of the "other" sites would be lost.
-
-  Search suggestions may come from multiple indexes at once. See [Amethyst config](./config.md#readme) for how.
+Done! The presence of these settings will automatically enable autocompletion on the search field.