Git Product home page Git Product logo

gatsby-plugin-local-search's Introduction

gatsby-plugin-local-search

Gatsby plugin for providing client-side search for data available in Gatsby's GraphQL layer using a variety of engines.

The following engines are supported:

This plugin provides a search index and store using the selected engine. To display search results, pair the index and store with a compatible React hook or component. See Displaying the search results.

Install

npm install --save gatsby-plugin-local-search

How to use

// gatsby-config.js

module.exports = {
  plugins: [
    // You can have multiple instances of this plugin to create indexes with
    // different names or engines. For example, multi-lingual sites could create
    // an index for each language.
    {
      resolve: 'gatsby-plugin-local-search',
      options: {
        // A unique name for the search index. This should be descriptive of
        // what the index contains. This is required.
        name: 'pages',

        // Set the search engine to create the index. This is required.
        // The following engines are supported: flexsearch, lunr
        engine: 'flexsearch',

        // Provide options to the engine. This is optional and only recommended
        // for advanced users.
        //
        // Note: Only the flexsearch engine supports options.
        engineOptions: 'default',

        // GraphQL query used to fetch all data for the search index. This is
        // required.
        query: `
          {
            allMarkdownRemark {
              nodes {
                id
                frontmatter {
                  path
                  title
                }
                rawMarkdownBody
              }
            }
          }
        `,

        // Field used as the reference value for each document.
        // Default: 'id'.
        ref: 'id',

        // List of keys to index. The values of the keys are taken from the
        // normalizer function below.
        // Default: all fields
        index: ['title', 'body'],

        // List of keys to store and make available in your UI. The values of
        // the keys are taken from the normalizer function below.
        // Default: all fields
        store: ['id', 'path', 'title'],

        // Function used to map the result from the GraphQL query. This should
        // return an array of items to index in the form of flat objects
        // containing properties to index. The objects must contain the `ref`
        // field above (default: 'id'). This is required.
        normalizer: ({ data }) =>
          data.allMarkdownRemark.nodes.map((node) => ({
            id: node.id,
            path: node.frontmatter.path,
            title: node.frontmatter.title,
            body: node.rawMarkdownBody,
          })),
      },
    },
  ],
}

How to query

A new node type becomes available named localSearch${name}, where ${name} is the name provided in the options. In the above example, the node would be accessed with localSearchPages.

The search index and store are made available as fields on the node.

  • index: (String) The search index created using the engine selected in the plugin options.
  • store: (JSON) The store used to map a search result's ref key to data.

Note that store is an object but does not require you to explicitly query each field.

{
  localSearchPages {
    index
    store
  }
}

Lazy-loading the index and/or store

The index and store can become large depending on the number of documents and their fields. To reduce your bundle size and delay fetching these pieces of data until needed, you can query a URL for both the index and store like the following.

{
  localSearchPages {
    publicIndexURL
    publicStoreURL
  }
}

Both publicIndexURL and publicStoreURL will return a public URL that can be fetched at run-time. For example, you could call fetch with the URLs to load the data in the background only as the user focuses your interface's search input.

The files contain data identical to querying for index and store directly and will be saved in your site's /public folder. This functionality if very similar to gatsby-source-filesystem's publicURL field.

FlexSearch Options

The engineOptions config allows you to change the FlexSearch Index options. These are detailed extensively in the FlexSearch Documentation, including all the available keys and their valid values.

It accepts a single string matching one of the presets, or an object containing the available keys and their valid settings as listed in the FlexSearch documentation.

These options give you a high degree of customisation, but changes from the defaults can definitely affect performance. The FlexSearch Documentation has extensive details on performance impacts of various settings, as well as their effects.

They generally recommend using the presets as a starting point and appling further custom configuration to get the best out for your situation.

One useful example is turning on 'fuzzy' matching by changing the tokeniser to one of the other options, such as 'forward': engineOptions: { tokenize: "forward" },

Here are the same examples as listed in FlexSearch Useage, and how you would define them with engineOptions:

Create a new index (using only defaults)

engineOptions: '',

Create a new index and choosing one of the presets:

engineOptions: 'performance',

Create a new index with custom options:

engineOptions: {
    charset: "latin:extra",
    tokenize: "reverse",
    resolution: 9
},

Create a new index and extend a preset with custom options:

engineOptions: {
    preset: "memory",
    tokenize: "forward",
    resolution: 5
},

Displaying the search results

This plugin provides a search index and store object but leaves presentation and search functionality up to you.

The following React components/hooks are recommended pairings:

gatsby-plugin-local-search's People

Contributors

angeloashmore avatar asyarb avatar javialon26 avatar khwkang avatar nickbarreto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

gatsby-plugin-local-search's Issues

How to use flexsearch custom matcher together with this plugin?

Hi, any ideas about how to use this Flexsearch function together with this plugin?
Or any other way how to replace special characters with required characters?
FlexSearch.registerMatcher({
'ä': 'a', // replaces all 'ä' to 'a'
'ó': 'o',
'[ûúù]': 'u' // replaces multiple
});
Thank you

Search withing complicated fields

For now i have query mostly title to do the search over all pages but i would like to search in more fields (all fields), as i have many indent fields in relationships, its complicated as there are many types of fields.
The defaulf options in flex seems not working in my query. Any recommendations.

The typeof store is string, causing trouble when using react-use-flexsearch

I use gatsby-plugin-local-search together with react-use-flexsearch. My index and store coming from graphql are both strings. That causes useFlexsearch(query, index, store) not to work, because it uses an array access store[id], which fails when the store is a string instead of an object.

My workaround is useFlexSearch(query, index, JSON.parse(store)), but I think either gatsby-plugin-local-search or react-use-flexsearch should do the conversion instead.

Prioritizing search results / multiple data types

Hello,

let's say I have
sections : [{"section: "Economy", url: '/economy'}, {"section: "Fun", url: '/fun'}]
articles: [{id: 1, "section: "Economy", title, url}, {id: 2, "section: "Economy", title, url}, {id: 3, "section: "Fun", title, url}]

and when I query "economy" I'd like to get something like this:

Sections:
Economy

Articles:
Title (from Economy)
Title 2 (from Economy)

where the query is either in sections section key, or article title.

Would you suggest to use multiple useFlexSearch hooks and multiple gatsby-plugin-local-search instances, or is there any simple way how to connect the data in normalizer as I could query multiple object types and filtering the results somehow later in render function?

Thank you very much for any insight. Even pointing me in any direction would be great :)

engineOptions gets stringified object, not index value

If you try to tokenize value with custom function in engineOptions section:

engineOptions: {
   profile: "speed",
   tokenize: function(str) {
      console.log(typeof(str), str) // <= here you'll get something like 'string' '{"name": "Itemname"}' not just Itemname
       return someTokenizeFunction(str)
   }

Query Multiple nodes in resolver

Loving this plugin. Thanks for your work.

I am querying data from Prismic but I have multiple data nodes from Prismic. I know that I can initialise multiple instances of gatsby-plugin-local-search but that will return multiple data nodes again. What I would like to do it query all of the data types from Prismic in the one instance and map each to the resolver. This is more difficuld as each node has a different shape.

e.g.

query: `
          {
            allPrismicArticle {
              edges {
                node {
                  id
                  slugs
                  data {
                    article_title {
                      text
                    }
                    content {
                      text
                    }
                  }
                }
              }
            }
            allPrismicCaseStudy {
              edges {
                node {
                  id
                  data {
                    title {
                      text
                    }
                  }
                  dataString
                }
              }
            }
          }
        `,

And the normalize function looks like this.

normalizer: ({ data }) =>
  data.allPrismicArticle.edges.map(edge => ({
    id: edge.node.id,
    title: edge.node.data.article_title.text,
    content: edge.node.data.content.text
})),

How do I map both allPrismicCaseStudy & allPrismicArticle?

querying Sharp images

Hello!

I'm wondering if it's possible or even advised to query sharp images on this plugin's config so I can display them as thumbnails on my results.

Using ...GatsbyImageSharpFluid fragment breaks build, and deconstructing this fragment on my query results in a image with weird colors.

Overcoming Prismic's 20 document limit

Hi,

Thanks for this great plugin. I've got a basic implementation working with content sourced from Prismic and it's blazingly fast...

However, because Prismic has a 20 document limit on their API the index is only being populated with the first 20 items. So, I was wondering whether it might be possible to utilise the cursors Prismic returns to fetch all content.

If, as @angeloashmore says, the query option accepted a function we could have some kind of loop that fetches all the documents using cursors (original post here).

Any guidance would be really welcomed.

Many thanks

Using key names in query returns all data as valid

Hello,

first of all, thank's for this awesome plugin...

I use it with useFlexSearch from react-use-flexsearch but I am unable to find out why this happens and how to get rid of it.

Let's say I have data:
[{ first_name: "Alpha" last_name: "Beta" },{ first_name: "Beta" last_name: "Gamma" }]
and config set to engine: "flexsearch", engineOptions: { encode: "icase", tokenize: "strict"}

Everything works fine (with query 'Alpha' || 'Delta' returns 1 result, 'Beta' 2), but query 'first_name' or 'last_name' returns both objects as result. When I use more advanced tokenization, even 'first' returns everything, etc.

Any idea how to get rid of this behavior? It's getting problematic when I have "email" as a key and values like "@email.de" which then shows all users. I am clueless how to approach this.

Thank you very much.

Allow index to be fetched based on fieldName

Having issues for pulling separate indexes for different pages.

Current:

/my-site/page1

query SearchbarIndexQuery {
  localSearchPage1 {
    index
    store
  }

/my-site/another-page

query SearchbarIndexQuery {
  localSearchAnotherPage {
    index
    store
  }

Preferred:

query($indexName: String) {
  localSearchPages(filter: {indexName: {eq: $indexName}}) {
    index
    store
  }

Because gatsby tries to validate pageQueries on compile time the current version requires a dynamic string to be passed in, unless theres a better approach?

Use gatsby plugins options validation

Hey, I propose to setup the Gatsby plugin options validation tool in this plugin.

This will bring few things:

  • Validation of options when gatsby is started, so less options validation in the main code
  • Possibility to generate documentation based on the validation schema
  • Possibility to generate the Typescript types based on the validation schema

See an example in the gatsby wordpress plugin https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-source-wordpress/src/steps/declare-plugin-options-schema.ts

Add `index` option alongside `store`

Add an index option that lives alongside store that, similar to store, allows one to select which fields are indexed.

By default, index is all keys returned by normalizer.

This effectively turns normalizer into a data normalizer where content can be selected. This allows someone to keep data in the store, but not indexed, if such data is only used for presentation.

Working sample repo?

I followed the instructions of this and that repo, for a gatsby site with gatsby-json-tranformer as data source.

I have a query, index, & store; but react-use-flexsearch is returning an array of chars...
Do you have a working sample repo we could give a try?

Clarify a little bit how to use

given your how to use, consider this scenario:
-Search by title but not by body
-When a title is found, pull the body data

The way I've been using it, is searching all data, and that had a severe performance downgrade in the lighthouse test.

Query graphql returning empty result

I can't use a query that contains the metadata of my pages, because for the lib it is empty but when I query in the graphql IDE it returns me all the results, does it have something to do with the order of execution? how to fix this?

my config

module.exports = {
  // A unique name for the search index. This should be descriptive of
  // what the index contains. This is required.
  name: 'pages_metadata',

  // Set the search engine to create the index. This is required.
  // The following engines are supported: flexsearch, lunr
  engine: 'flexsearch',

  // Provide options to the engine. This is optional and only recommended
  // for advanced users.
  //
  // Note: Only the flexsearch engine supports options.
  // engineOptions: 'speed',

  // GraphQL query used to fetch all data for the search index. This is
  // required.
  query: `{
    allPagesIndexable {
      nodes {
        path
        metadata {
          content
          keywords
          title
          type
        }
      }
    }
  }`,
  // Field used as the reference value for each document.
  // Default: 'id'.
  ref: 'path',

  // List of keys to index. The values of the keys are taken from the
  // normalizer function below.
  // Default: all fields
  index: ['title', 'keywords', 'content'],

  // List of keys to store and make available in your UI. The values of
  // the keys are taken from the normalizer function below.
  // Default: all fields
  // store: ['id', 'path', 'title'],

  // Function used to map the result from the GraphQL query. This should
  // return an array of items to index in the form of flat objects
  // containing properties to index. The objects must contain the `ref`
  // field above (default: 'id'). This is required.
  normalizer: ({ data }) => {
    console.log(data.allPagesIndexable) // return [Object: null prototype] { nodes: [] }
    return data.allPagesIndexable.nodes.map(node => ({
      path: node.path,
      ...node.metadata
    }))
  }
}

example of result of my query in IDE:

{
  "data": {
    "allPagesIndexable": {
      "nodes": [
        {
          "path": "/curso/graduacao/contador-global",
          "metadata": {
            "content": null,
            "keywords": null,
            "title": null,
            "type": "Página"
          }
        },
        {
          "path": "/curso/graduacao/contador-global/informacoes-adicionais",
          "metadata": {
            "content": null,
            "keywords": null,
            "title": null,
            "type": "Página"
          }
        },
...
    

I created this separate node because I was having "metadata does not exist in context" problems when the query was executed by this lib
how i create the node:

exports.onCreateNode = ({ node, actions, createNodeId, createContentDigest }) => {
  const { createNode } = actions
  // Transform the new node here and create a new node or
  // create a new node field
  if (node.internal.type === "SitePage" && node.context && node.context.metadata) {
    const page = {
      path: node.path,
      metadata: node.context.metadata
    }
    createNode({
      ...page,
      id: createNodeId(page.path),
      internal: {
        type: `PagesIndexable`,
        contentDigest: createContentDigest(page)
      }
    })
  }
}

Default: all fields

In the gatsby-config example, it says for index and store there's an 'default' option to expose all fields.
However it's not self-explanatory and I tried several options whitout success.

Anybody can explain how I can expose all the properties of normalizer without writing what fields should be in index and store ?

    // List of keys to index. The values of the keys are taken from the
    // normalizer function below.
    // Default: all fields
    index: ['title', 'body'],

    // List of keys to store and make available in your UI. The values of
    // the keys are taken from the normalizer function below.
    // Default: all fields
    store: ['id', 'path', 'title'],

All fields in lunr index

When Lunr engine is used the index option is omitted and all fields from the normalizer are used.

This line always resolves to all fields in the document.

Support language stemmers

Add language support.

To support multiple engines, the best API is probably passing a language string (e.g. “en” or “de”).

Alternatively, allow passing arbitrary Stemmer configs. This would be engine-specific.

Can't query page context fields of pages created in gatsby-node.js

I'm using gatsby-node.js to create a bunch of pages which I would like to search through using your plugin. I'm passing all the needed data into the pagecontext during the creation of the pages.

My query looks like this:

 query: `
      {
        allSitePage {
          edges {
            node {
              path
              context {
                id__normalized
                market_cap_rank
                name
                symbol
              }
            }
          }
        }
      }
        `,

The error says: Cannot query field "context" on type "SitePage".

I'm not really sure if this just isn't supported or whether I am overlooking something here. The only thing that I can see that seems fishy to me is that the "success create pages" message in the console comes after the error, which makes me think that the pages don't exist yet when I'm trying to query them.

Any hints would be much appreciated! :)

Add ability to automatically save index and store to files in /public

The plugin currently saves indices and stores in Gatsby's node system. Retrieving them can be done by querying for them in a page's GraphQL query. If done directly in a page's query, the large indices and stores end up in the site's bundle.

To prevent bloating the site's bundle, the objects can be lazy-loaded by saving to files in the /public folder. The files can then be fetched client-side only when needed, such as the user's first search.

This can currently be done by querying for the objects in gatsby-node.js and saving the files manually using fs. This could be a feature provided by the plugin, however, by adding two new fields and a custom resolver:

query SearchPage {
  localSearchBlogPosts {
    publicIndexURL
    publicStoreURL
  }
}

Which could have a response like:

{
  "data": {
    "localSearchBlogPosts": {
      "publicIndexURL": "/static/6a992d5529f459a44fee58c733255e86.index.txt",
      "publicStoreURL": "/static/6a992d5529f459a44fee58c733255e86.store.json",
    }
  }
}

When someone queries for publicIndexURL or publicStoreURL, the relevant file is written to /public/static/ with an appropriate filename and returned in the query as a string. This removes the need to manage where the file is stored and allows different search engines to save files in their native format.

Note that this is modeled after gatsby-source-filesystem's publicURL field which performs a similar function.

This feature would not replace the direct index and store fields.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.