{"action":"created","issue":{"url":"https://api.github.com/repos/twbs/bootstrap/issues/16901","labels_url":"https://api.github.com/repos/twbs/bootstrap/issues/16901/labels{/name}","comments_url":"https://api.github.com/repos/twbs/bootstrap/issues/16901/comments","events_url":"https://api.github.com/repos/twbs/bootstrap/issues/16901/events","html_url":"https://github.com/twbs/bootstrap/pull/16901","id":97731922,"number":16901,"title":"Add search to the documentation","user":{"login":"pixelastic","id":283419,"avatar_url":"https://avatars.githubusercontent.com/u/283419?v=3","gravatar_id":"","url":"https://api.github.com/users/pixelastic","html_url":"https://github.com/pixelastic","followers_url":"https://api.github.com/users/pixelastic/followers","following_url":"https://api.github.com/users/pixelastic/following{/other_user}","gists_url":"https://api.github.com/users/pixelastic/gists{/gist_id}","starred_url":"https://api.github.com/users/pixelastic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pixelastic/subscriptions","organizations_url":"https://api.github.com/users/pixelastic/orgs","repos_url":"https://api.github.com/users/pixelastic/repos","events_url":"https://api.github.com/users/pixelastic/events{/privacy}","received_events_url":"https://api.github.com/users/pixelastic/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/twbs/bootstrap/labels/docs","name":"docs","color":"0088cc"},{"url":"https://api.github.com/repos/twbs/bootstrap/labels/feature","name":"feature","color":"4bb14b"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":27,"created_at":"2015-07-28T15:14:49Z","updated_at":"2015-08-17T18:17:59Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/twbs/bootstrap/pulls/16901","html_url":"https://github.com/twbs/bootstrap/pull/16901","diff_url":"https://github.com/twbs/bootstrap/pull/16901.diff","patch_url":"https://github.com/twbs/bootstrap/pull/16901.patch"},"body":"Hello Bootstrap team,\r\n\r\nI've always wanted to be able to easily search inside the Bootstrap documentation, so I've added the feature.\r\n\r\n![Desktop search screencast](https://raw.githubusercontent.com/pixelastic/sov/master/pull_requests/bootstrap/desktop.gif)\r\n\r\nYou can see it live and test it here: [https://bootstrap.algolia.com/](https://bootstrap.algolia.com/) (it of course includes smaller [screen](https://raw.githubusercontent.com/pixelastic/sov/master/pull_requests/bootstrap/phone.gif) [versions](https://raw.githubusercontent.com/pixelastic/sov/master/pull_requests/bootstrap/tablet.gif) as well).\r\n\r\nThis pull request uses [Algolia](https://www.algolia.com/) API to index and search the documentation. It includes both the indexing code (using our Jekyll plugin) as well as the front-end code to display the results.\r\n\r\n## Benefits for the end user\r\n\r\n- Search in the whole documentation (CSS, JavaScript, Components), with typo-tolerance\r\n- Fast results (your search index is replicated in 13 regions worldwide, it is fast no matter where the user is)\r\n- Instant access to the documentation at the first keystroke, in a few milliseconds\r\n- Directly scroll to the relevant part of the page\r\n- Responsive integration, available on all viewports\r\n\r\n## How it works\r\n\r\nWe've created a free account for you that hosts the documentation. When a user does a search, it uses the Algolia API to retrieve the most relevant results from the documentation and display them in a dropdown.\r\n\r\nThis uses [Typeahead](https://github.com/twitter/typeahead.js/tree/v0.10.5)  v0.10.15 for the dropdown, [Hogan](https://github.com/twitter/hogan.js) v3.0.2 for the templates and the [Algolia Client](https://github.com/algolia/algoliasearch-client-js) v3.7.5 for the calls to the API. The indexing is done using the [Algolia Jekyll plugin](https://github.com/algolia/algoliasearch-jekyll) v0.4 (more on this later).\r\n\r\n## Testing the pull request locally\r\n\r\nIf you want to test the PR locally, you can do the following:\r\n\r\n```sh\r\n$ git clone -b feature/algolia-search git@github.com:algolia/bootstrap.git\r\n$ cd ./bootstrap\r\n$ bundle install\r\n$ npm install\r\n$ grunt docs\r\n$ jekyll serve\r\n```\r\nNote that we've added a `Gemfile` to avoid any dependency version issue.\r\n\r\n## Updating the index\r\n\r\nThe Jekyll plugins adds a new command to jekyll: `jekyll algolia push`. This will read the generated HTML files from a `jekyll build`, extract each `<p>` paragraph and `<h1> - <h6>` header and index them in Algolia. You'll basically have to re-run this command everytime you push a new version to `gh-pages`.\r\n\r\nThis will need your admin API key as the `ALGOLIA_API_KEY` environment variable. I'll give you full and unlimited access to your dashboard so you can find it there.\r\n\r\n```sh\r\nALGOLIA_API_KEY=[YOUR_ALGOLIA_ADMIN_KEY] jekyll algolia push\r\n```\r\n\r\n## Tweaking\r\n\r\nThe Jekyll plugin default settings are configured for indexing a blog. We had to tweak it a bit for the Bootstrap documentation. The `algolia.settings` key in `_config.yml` are the Algolia settings we think provides the most relevant results. What every option does is explained in [our documentation](https://www.algolia.com/doc#Settings) but I'm also available to give more context if any of them is not clear enough.\r\n\r\n## Records\r\n\r\nHere is an example of what a record looks like:\r\n\r\n```json\r\n{\r\n  \"objectID\": \"components_145\",\r\n  \"type\": \"page\",\r\n  \"url\": \"/components/#navbar-forms\",\r\n  \"title\": \"Components\",\r\n  \"slug\": \"components\",\r\n  \"h2\": \"Forms\",\r\n  \"h1\": \"Navbar\",\r\n  \"tag_name\": \"p\",\r\n  \"raw_html\": \"<p>As a heads up, <code>.navbar-form</code> shares much of its code with <code>.form-inline</code> via mixin. <strong class=\\\"text-danger\\\">Some form controls, like input groups, may require fixed widths to be show up properly within a navbar.</strong></p>\",\r\n  \"text\": \"As a heads up, <code>.navbar-form</code> shares much of its code with <code>.form-inline</code> via mixin. Some form controls, like input groups, may require fixed widths to be show up properly within a navbar.\",\r\n  \"unique_hierarchy\": \"Components > Navbar > Forms\",\r\n  \"css_selector\": \"div:nth-of-type(7) > p:nth-of-type(5)\",\r\n  \"css_selector_parent\": \"#navbar-forms\",\r\n  \"weight\": {\r\n    \"page\": 2,\r\n    \"tag_name\": 0,\r\n    \"number_of_words\": 1\r\n  },\r\n  \"codes\": [\r\n    \"navbar-form\",\r\n    \"form-inline\"\r\n  ]  \r\n}\r\n```\r\n\r\nMost of the keys are self-explanatory. Some of them are used to calculate the relevance of a record, others are used for the display in the dropdown templates.\r\n\r\nEach record contains its `raw_html` version as well as a sanitized `text` version. As we are indexing both `<p>` and `<h1-6>`, the `tag_name` key tells us what kind of node each record is. The `h1`, `h2`, ..., `h6` keys are the parent headings of the records. The `unique_hierarchy` field is a concatenation of those fields, symbolising where the record is in the TOC.\r\n\r\n`css_selector` and `css_selector_parent` are two unique CSS selector that target respectively the record itself, and its closest parent heading. We actually do not use `css_selector` in this PR, because the generated doc adds `<div>` markup (for copy-pasting snippets) that breaks them. Instead, we use the `css_selector_parent` to scroll the page to the closest parent heading.\r\n\r\n`weight` and `codes` are custom attributes that are not part of the plugin, but that we could add using the `custom_hook_each` hook for this specific implementation. `weight` is an attribute used in our custom ranking to break ties between several records. `codes` contains all the class and method found in `<code>` examples of the records, giving us better results when searching for `navbar btn`, for example.\r\n\r\n## About Algolia\r\n\r\nAt Algolia we provide a hosted search API and support community websites like [Hackernews search](https://hn.algolia.com/), [CDNJS](https://cdnjs.com/), [GrowthHackers](https://growthhackers.com/), [Product Hunt](http://www.producthunt.com/) or the [Laravel documentation](http://laravel.com/docs/5.1) with free plans. We only ask for a \"powered by Algolia\". This pull request adds it to the search results.\r\n\r\nIf you want to move forward with this implementation, I'll send you the free and unlimited credentials to your Algolia dashboard as mentionned above.\r\n\r\nI'm available for any questions you may have or any more information you would need."},"comment":{"url":"https://api.github.com/repos/twbs/bootstrap/issues/comments/131914750","html_url":"https://github.com/twbs/bootstrap/pull/16901#issuecomment-131914750","issue_url":"https://api.github.com/repos/twbs/bootstrap/issues/16901","id":131914750,"user":{"login":"maximeprades","id":509269,"avatar_url":"https://avatars.githubusercontent.com/u/509269?v=3","gravatar_id":"","url":"https://api.github.com/users/maximeprades","html_url":"https://github.com/maximeprades","followers_url":"https://api.github.com/users/maximeprades/followers","following_url":"https://api.github.com/users/maximeprades/following{/other_user}","gists_url":"https://api.github.com/users/maximeprades/gists{/gist_id}","starred_url":"https://api.github.com/users/maximeprades/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maximeprades/subscriptions","organizations_url":"https://api.github.com/users/maximeprades/orgs","repos_url":"https://api.github.com/users/maximeprades/repos","events_url":"https://api.github.com/users/maximeprades/events{/privacy}","received_events_url":"https://api.github.com/users/maximeprades/received_events","type":"User","site_admin":false},"created_at":"2015-08-17T18:17:59Z","updated_at":"2015-08-17T18:17:59Z","body":"very cool :+1: cc @pelletier"}}