Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vuepress/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import BlogPosts from './components/BlogPosts.vue';
import JumpToc from './components/JumpToc.vue';
import PrBy from './components/PrBy.vue';
import ReleaseToc from './components/ReleaseToc.vue';
import URLDocSearch from './components/URLDocSearch.vue';

export default defineClientConfig({
enhance({ app }) {
Expand All @@ -18,5 +19,8 @@ export default defineClientConfig({
app.component('JumpToc', JumpToc);
app.component('PrBy', PrBy);
app.component('ReleaseToc', ReleaseToc);

// Override the builtin searchbox
app.component('SearchBox', URLDocSearch);
},
});
103 changes: 103 additions & 0 deletions .vuepress/components/URLDocSearch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<template>
<DocSearch />
</template>

<script setup lang="ts">
import { DocSearch, DocSearchOptions } from '@vuepress/plugin-docsearch/client';
import {
useDebounceFn,
useElementVisibility,
useEventListener,
} from '@vueuse/core';
import { onMounted, ref, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';

const SEARCH_KEY = 'search';
const SEARCH_INPUT_ID = 'docsearch-input';

const router = useRouter();
const route = useRoute();

const inputElement = ref<HTMLInputElement>();
const isNavigating = ref(false);

// Handle initial search query, if one is set
onMounted(async () => {
const query = new URL(window.location.href).searchParams.get(SEARCH_KEY);
if (query) {
(document.querySelector('.DocSearch-Button') as HTMLButtonElement).click();
// Set value in the input element once it appears
performInitialQuery(query);
}
});

function performInitialQuery(query: string) {
const found = document.getElementById(SEARCH_INPUT_ID);
if (found) {
inputElement.value = found as HTMLInputElement;
inputElement.value.value = query;
inputElement.value.dispatchEvent(new Event('input'));
} else {
setTimeout(() => performInitialQuery(query), 50);
}
}

// There's some debounce builtin to docsearch, this mimics that and should
// help prevent browser history from getting filled with partial queries.
const setURLQueryDebounced = useDebounceFn(setURLQuery, 500);

// When the user types a search query, update URL query param accordingly
useEventListener('input', (event) => {
const target = event.target as HTMLInputElement | undefined;
const searchQuery = target?.value;
if (target?.id !== SEARCH_INPUT_ID) {
return;
}
inputElement.value = target;
setURLQueryDebounced(searchQuery);
});

// Clear the URL query param when search input is reset (i.e. "Clear" button).
useEventListener('reset', (event) => {
const target = event.target as HTMLFormElement | undefined;
if (target?.classList.contains('DocSearch-Form')) {
setURLQuery();
}
});

// Clear the URL query param when the search modal is dismissed.
// NOTE: newer versions of @docsearch/js also provide a callback option for this.
const inputIsVisible = useElementVisibility(inputElement);
watch(inputIsVisible, (isVisible, wasVisible) => {
if (wasVisible && !isVisible && !isNavigating.value) {
setURLQuery();
}
});

// When a search result is selected, the modal is dismissed and its route is pushed, without `?search=`.
// Track this to avoid running visibility watch logic to clear the URL in this case.
router.beforeEach((route) => {
if (!route.query[SEARCH_KEY]) {
isNavigating.value = true;
}
});
router.afterEach(() => {
isNavigating.value = false;
});

// Set `?search=` query param; if passed empty string or undefined, clear the param instead.
function setURLQuery(newSearch?: string) {
const { path, query: oldQuery, hash } = route;
const { [SEARCH_KEY]: oldSearch, ...query } = oldQuery;

// Replace the history entry if the only the search query changed, to avoid
// polluting the user's browser history with partial/incomplete search queries.
const replace = oldSearch !== undefined || newSearch === undefined;

if (newSearch) {
query[SEARCH_KEY] = newSearch;
}

router.push({ path, query, hash, replace });
}
</script>
9 changes: 9 additions & 0 deletions .vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ export default defineUserConfig({
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black' },
],
['link', { rel: 'icon', href: '/icon.png' }],
[
'link',
{
rel: 'search',
type: 'application/opensearchdescription+xml',
title: 'Nushell Docs', // NOTE: must match ShortName
href: '/opensearch.xml',
},
],
],
markdown: {
importCode: {
Expand Down
12 changes: 12 additions & 0 deletions .vuepress/public/opensearch.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<OpenSearchDescription
xmlns="http://a9.com/-/spec/opensearch/1.1/"
xmlns:moz="http://www.mozilla.org/2006/browser/search/"
>
<ShortName>Nushell Docs</ShortName>
<Description>Search Nushell documentation</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16" type="image/png">https://www.nushell.sh/icon.png</Image>
<Url type="text/html" template="https://www.nushell.sh/?search={searchTerms}" />
<Url type="application/opensearchdescription+xml" rel="self" template="https://www.nushell.sh/opensearch.xml" />
<!-- TODO: it might be possible to provide suggestions via some Algolia API? -->
</OpenSearchDescription>