Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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);
},
});
46 changes: 46 additions & 0 deletions .vuepress/components/URLDocSearch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<template>
<DocSearch :options="docsearchOptions" />
</template>

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

const SEARCH_KEY = 'search';
const docsearchOptions: Ref<DocSearchOptions> = ref({});
const router = useRouter();
const route = useRoute();

// Handle initial search query, if one is set
onMounted(() => {
const query = new URL(window.location.href).searchParams.get(SEARCH_KEY);
if (query) {
docsearchOptions.value.initialQuery = query;
(document.querySelector('.DocSearch-Button') as HTMLButtonElement).click();
}
});

// There's some debounce builtin to docsearch, this mimics that and should
// help prevent browser history from getting filled with partial queries.
const inputHandler = useDebounceFn(handleSearchInput, 500);
useEventListener('input', (event) => inputHandler(event as InputEvent));

// Update the URL bar when the search input changes.
function handleSearchInput(event: InputEvent) {
const target = event.target as HTMLInputElement | undefined;
const searchQuery = target?.value;
if (target?.id !== 'docsearch-input' || !searchQuery) {
Copy link
Copy Markdown
Contributor

@hustcer hustcer Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think If the input value is empty it's better to delete the search param here. Returning early when the input is cleared means the existing ?search= parameter is not removed. As a result, after the user clears the search and closes the modal, the URL may still retains the old query.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good point, I think that probably makes sense. I'm not sure how easy it will be to detect the modal being dismissed (since that logic is implemented in a nested child), but it's probably doable with some kind of global event listener at least.

I will try to get to implementing this next week when I should have some time!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, I ended up rewriting a lot of the implementation to cover a few different cases:

  • Modal is dismissed: clear query param
  • Search form is reset ("Clear" button): clear query param
  • Navigate to search result: builtin navigation clears query param automatically

return;
}

// If we had already started a search, replace it instead of appending
const { path, query: routeQuery } = route;
const replace = routeQuery?.hasOwnProperty(SEARCH_KEY);
const query = { ...routeQuery }; // NOTE: must copy the query object
query[SEARCH_KEY] = searchQuery;

router.push({ path, query: { ...query }, 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>