timc1 / kbar

fast, portable, and extensible cmd+k interface for your site

Home Page:https://kbar.vercel.app

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

More control over the returned matches

ghingis opened this issue · comments

There was already a ticket with something similar: #286

While a permanent element solution with a bit of tinkering could be used as a workaround in our case, our problem is a bit more complex though.

We would like to dynamically extend the action list based on what is in the search.
Similarly, when you want to search for a sender that has a specific sting in it in Gmail, you can write:from:string
Likewise in Discord if you search for users, you can write: user: string

While I can register actions when the search starts with/contains our token, afterward, the Fuse match will not give a result since the query string has that extra part.

So we had an idea, we could extend the kbar config with an optional matcher function, that would run before the fuse.search.

In this matcher function, the devs would get the action implementations and the search string as a param and they could return the matches that they would like to see in the results (like the elements that have the keywords * in them) and a search that can be a string, a Fuse.Expression (so we could use Fuses' logical query operations) or a specific Symbol that skips the original matcher resulting that the returned matches will be the result of the whole search.

With a simple useEffect that reacts to the queryValue change, registering the users as actions and a matcher config like this, you could inject your user list into the actions and navigate to a specific user profile from kbar.

matcher: (actions, search): ReturnType<Matcher> => {
    search = search.trim();

    if ( search === '' ) {
        return { matches: [], search };
    }

    if ( search.toLowerCase().startsWith("user:") ) {
        search = search.replace(/user:/i, "");

        const searchExpression: Fuse.Expression = search.trim() === '' ?
            { keywords: "user" } :
            { $and: [ { $or: [ { name: search }, { subtitle: search } ] }, { keywords: "user" } ] };

        return { matches: [], search: searchExpression };
    }
    return { matches: [], search };
}

Or you can have permanent actions

matcher: (actions, search): ReturnType<Matcher> => {
    const matches = [];

    actions.forEach((action)=> {
        if ( action.keywords === '*' ) {
            matches.push({ action, score: 1 });
        }
    });

    return { matches: [], search };
}

Or you can have the option to give no result for specific search words

import { SKIP_ORIGINAL_MATCHER } from "kbar";
//...
matcher: (actions, search): ReturnType<Matcher> => {
    const matches = [];

    if (search.includes('bad_word')){
        return { matches: [], search: SKIP_ORIGINAL_MATCHER };
    }

   return { matches: [], search }
}

Hey! This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.