npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

alt-search-docs

v1.0.6

Published

A search application to look through Alt's docs

Downloads

30

Readme

Alt Search

A search application built with React and Flux for searching through Alt's API documentation.

Tech Details

There are a few components to this application: the search index, and the application itself.

The website is indexed with lunr.js. There is a node script here that goes through the documentation and creates a lunr-compatible search index which is then serialized into a json file that sits in the assets directory of alt's website.

The application then uses axios, an HTTP client library, to asynchronously fetch that data and create the lunr database on the client. We're using Alt's async API to load this data:

SearchSource.js

import axios from 'axios'
import SearchActions from '../actions/SearchActions'

export default {
  loadIndex(context) {
    return {
      remote(state, url) {
        return axios.get(url).then(req => req.data)
      },

      success: SearchActions.receivedIndex,
      error: SearchActions.noIndexFound
    }
  }
}

Here loadIndex returns an object which is then used to build your async flow. remote is what is used to fetch the data, and success and error are both actions that are fired according to the response.

SearchStore.loadIndex(this.props.url)

Once the index has been downloaded and parsed it is sent to SearchActions.receivedIndex which is handled by the SearchStore. This store keeps track of the index as well as the documents. The other store, ResultsStore is in charge of storing the current search results as well as building the snippets for them.

@bind(SearchActions.search)
search(text = '') {
  this.searchTerm = text
  this.results = text ? this.doSearch(text) : []
}

doSearch(text) {
  const { index, documents } = SearchStore.getState()

  // Search and return the top 10 documents
  return index.search(text).map((result) => {
    const doc = documents[result.ref]
    doc.snippet = this.getSnippet(doc.tokens)
    doc.selected = false
    return doc
  }).slice(0, 10)
}

The main entry point to the application is through SearchView. We are using AltContainer here to listen to ResultsStore so when data changes our SearchResults view updates with the desired information. The other component is the SearchBox which is an input box that fires actions as you type thus completing the flow:

SearchBox -> SearchActions.search -> ResultsStore -> SearchResults

The SearchBox also listens to keyboard inputs so you can use your up and down arrows to cycle through the search results and make a selection. The selection is stored in the ResultsStore. This keeps all the business logic away in the stores, makes the actions very simple and easy to digest, and keeps the components "dumb" since they're only accepting props and dealing with the presentation of data.

Other

As you can see here we're using decorators to bind actions to the store. This is just a nice clean sugar and is available as a stage 0 transform using babel.

The snippets are the piece of body text that shows under the title of the results. They are calculated by first parsing the markdown of the entire page in question, extracting all the paragraphs out, then finding whether the string you typed is present in any of those paragraphs, the list is then sorted so the paragraph with the highest score is returned. I then take this paragraph and clip it so it doesn't overflow in the UI and present it. We're using marked for markdown parsing, and stringScore for scoring the strings.

getSnippet(tokens) {
  const text = this.searchTerm
  const items = tokens.map((token) => {
    return {
      score: stringScore(token.text, text),
      text: token.text
    }
  }).filter((obj) => {
    return obj.score > 0
  }).sort((a, b) => {
    return a.score < b.score ? 1 : -1
  })

  return items.length ? items[0].text.slice(0, MAX_CHAR_SUMMARY) : ''
}

The most complicated piece of logic is actually the keyboard selection. It is being parsed and sent by the SearchBox and then handled in the ResultsStore.

License

MIT