├── .eslintrc
├── .gitignore
├── .npmignore
├── CONTRIBUTING.md
├── README.md
├── _assets
└── website
│ ├── Algolia_logo_bg-white.svg
│ └── style.css
├── algolia-ranking.png
├── index.js
├── package.json
└── src
├── components
└── AlgoliaResults.js
├── index.js
└── unescape.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "gitbook/plugin"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Deployed apps should consider commenting this line out:
24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
25 | node_modules
26 |
27 | # Exclude prepublish dependencies
28 | assets/algoliasearch.min.js
29 | # vim swapfile
30 | *.swp
31 |
32 | # Plugin assets
33 | _assets/plugin.js
34 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Publish assets on NPM
2 | !_assets/plugin.js
3 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Building the plugin
2 |
3 | You need to have `npm` installed. Install the dependencies:
4 |
5 | ```
6 | npm install
7 |
8 | ```
9 |
10 | Now, you can build the plugin by running:
11 |
12 | ```
13 | npm run build-js
14 | ```
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Power search using Algolia
2 |
3 | Use Algolia as a back-end to index and search your book's content.
4 |
5 | This plugins requires gitbook `>=4.0.0`.
6 |
7 | ## How to use it?
8 |
9 | ### Create an Algolia account
10 |
11 | 1. Sign up to [Algolia](https://www.algolia.com)
12 | 2. Set up a basic index.
13 | 3. Access your [API keys settings](https://www.algolia.com/api-keys), and keep this page open for the next steps.
14 |
15 | ### Set up the plugin in your book.
16 |
17 | 1. Open Settings > Plugins Store interface in the GitBook Editor.
18 | 2. Install the `algolia` plugin.
19 | 3. Specify the following values in the modal for your book:
20 | * **Algolia Index Name**, which is the name of the index you set up in Algolia web.
21 | * **Algolia Application ID**, which is your unique application ID for API identification.
22 | * **Algolia Search-Only API Key**, which is the unique ID for search queries.
23 | * **Is your Algolia account free**, which you leave as `true` if you have what Algolia call a "Hacker Account".
24 |
25 | Or if you prefer, you can declare the plug-in values in the `book.json` file. Add the plugin and its configuration to your `book.json`:
26 |
27 | ```JSON
28 | {
29 | "plugins": ["algolia"],
30 | "pluginsConfig": {
31 | "algolia": {
32 | "index": "algolia-index-name",
33 | "applicationID": "algolia-application-id",
34 | "publicKey": "algolia-search-only-api-key",
35 | "freeAccount": "true"
36 | }
37 | }
38 | }
39 | ```
40 |
41 | ### Set Algolia Environment Variables
42 |
43 | If you are using the GitBook Editor, you can specify the `ALGOLIA_PRIVATEKEY` in the Settings of your book.
44 |
45 | 1. Login to https://www.gitbook.com
46 | 2. Go to the `Settings` of the book you want to add the environment variable to.
47 | 4. Scroll down to the `Environment Variables` group.
48 | 5. Add a new variable, named `ALGOLIA_PRIVATEKEY`, and put your **Admin API Key** as value.
49 |
50 | If you are using the command line, pass your **Admin API Key** as an environment variable when launching gitbook:
51 |
52 | ```Bash
53 | $ ALGOLIA_PRIVATEKEY="algolia-admin-api-key" gitbook serve
54 | ```
55 |
56 | #### A note about the index
57 |
58 | **WARNING** The plugin will replace the entire index at each build phase. Do not use an existing index, unless you no longer require its contents.
59 |
60 | When setting up the basic index on Algolia, you will be prompted to generate or import the index so Algolia can begin analysing your book.
61 |
62 | You do not need to create the index manually for the plugin to work.
63 |
64 |
65 | ### Adding keywords to a page
66 |
67 | You can specify explicit keywords for any page.
68 |
69 | ```md
70 | ---
71 | search:
72 | keywords: ['keyword1', 'keyword2', 'etc.']
73 |
74 | ---
75 |
76 | # My Page
77 |
78 | This page will rank better if we search for 'keyword1'.
79 | ```
80 |
81 | ### Disabling indexing of a page
82 |
83 | You can disable the indexing of a specific page by adding a YAML header to the page:
84 |
85 | ```md
86 | ---
87 | search: false
88 | ---
89 |
90 | # My Page
91 |
92 | This page is not indexed in by Algolia.
93 | ```
94 |
95 | ### Fine tuning the Algolia search rankings
96 |
97 | After having indexed your book at least once, you can configure your Algolia index from your [dashboard](https://www.algolia.com/explorer). You can go to the _Ranking_ settings and tell Algolia how to search your pages. You can see below an example configuration, that tells Algolia to search, by order of importance, the keywords of your pages, their title, and finally their content (body).
98 |
99 | 
100 |
--------------------------------------------------------------------------------
/_assets/website/Algolia_logo_bg-white.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/_assets/website/style.css:
--------------------------------------------------------------------------------
1 | .Algolia {
2 | opacity: 0.5;
3 | font-size: 10pt;
4 | margin-top: 50px;
5 | text-align: right;
6 | }
7 |
8 | .Algolia a {
9 | color: inherit;
10 | }
11 |
12 | .Algolia-logo {
13 | height: 20pt;
14 | vertical-align: middle;
15 | }
--------------------------------------------------------------------------------
/algolia-ranking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GitbookIO/plugin-algolia/e84a54107045c438fc08279409fc1f80367293e2/algolia-ranking.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const algoliasearch = require('algoliasearch');
2 |
3 | let client = null;
4 | let index = null;
5 |
6 | module.exports = {
7 | hooks: {
8 | init() {
9 | // Don't index when not generating website
10 | if (this.output.name != 'website') return;
11 |
12 | // Check that private key has been provided
13 | if (!process.env.ALGOLIA_PRIVATEKEY) {
14 | this.log.warn.ln('Algolia indexation is disabled:');
15 | this.log.warn.ln('You need to configure the ALGOLIA_PRIVATEKEY environment variable using your Admin API Key.');
16 | return;
17 | }
18 |
19 | // Initialize Algolia client
20 | const config = this.config.get('pluginsConfig.algolia') || this.config.get('algolia');
21 | client = algoliasearch(config.applicationID, process.env.ALGOLIA_PRIVATEKEY);
22 |
23 | // Initialize index with book's title
24 | index = client.initIndex(config.index);
25 |
26 | // Clean index for updates
27 | return index.clearIndex()
28 | .then(function(content) {
29 | return index.waitTask(content.taskID);
30 | });
31 | },
32 |
33 | page(page) {
34 | const searchConfig = page.attributes.search;
35 |
36 | // Don't index when not generating website or if index has not been set
37 | if (this.output.name != 'website' || !index || searchConfig === false) {
38 | return page;
39 | }
40 |
41 | this.log.debug.ln('index page', page.path);
42 | // Transform as text
43 | const text = page.content.replace(/(<([^>]+)>)/ig, '');
44 |
45 | let keywords = [];
46 | if (searchConfig) {
47 | keywords = searchConfig.keywords || [];
48 | }
49 |
50 | // Add to index
51 | return index.addObject({
52 | url: this.output.toURL(page.path),
53 | path: page.path,
54 | title: page.title,
55 | keywords,
56 | body: text,
57 | level: page.level,
58 | depth: page.depth
59 | })
60 | .then(function() {
61 | return page;
62 | });
63 | }
64 | }
65 | };
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gitbook-plugin-algolia",
3 | "description": "Use Algolia as a backend for research",
4 | "main": "index.js",
5 | "version": "2.0.0",
6 | "browser": "./_assets/plugin.js",
7 | "dependencies": {
8 | "gitbook-core": "^4.0.0",
9 | "algoliasearch": "3.18.1"
10 | },
11 | "devDependencies": {
12 | "gitbook-plugin": "^4.0.0",
13 | "eslint": "3.7.1",
14 | "eslint-config-gitbook": "1.4.0"
15 | },
16 | "engines": {
17 | "gitbook": ">=4.0.0-alpha.0"
18 | },
19 | "scripts": {
20 | "build-js": "gitbook-plugin build ./src/index.js ./_assets/plugin.js",
21 | "prepublish": "npm run build-js"
22 | },
23 | "author": "GitBook Team ",
24 | "contributors": [
25 | {
26 | "name": "Johan Preynat",
27 | "email": "johan@gitbook.com"
28 | }
29 | ],
30 | "homepage": "https://github.com/GitbookIO/plugin-algolia",
31 | "repository": {
32 | "type": "git",
33 | "url": "https://github.com/GitbookIO/plugin-algolia.git"
34 | },
35 | "license": "Apache-2.0",
36 | "bugs": {
37 | "url": "https://github.com/GitbookIO/plugin-algolia/issues"
38 | },
39 | "gitbook": {
40 | "properties": {
41 | "index": {
42 | "type": "string",
43 | "title": "Algolia index name",
44 | "required": true
45 | },
46 | "applicationID": {
47 | "type": "string",
48 | "title": "Algolia Application ID",
49 | "required": true
50 | },
51 | "publicKey": {
52 | "type": "string",
53 | "title": "Algolia Search-Only API Key",
54 | "required": true
55 | },
56 | "freeAccount": {
57 | "type": "bool",
58 | "title": "Is your Algolia account free",
59 | "default": true
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/AlgoliaResults.js:
--------------------------------------------------------------------------------
1 | const GitBook = require('gitbook-core');
2 | const { React } = GitBook;
3 |
4 | /**
5 | * Displays the default search results, but adds the Algolia logo.
6 | */
7 | const AlgoliaResults = React.createClass({
8 | propTypes: {
9 | file: GitBook.PropTypes.File.isRequired,
10 | children: React.PropTypes.node
11 | },
12 |
13 | render() {
14 | const { file, children } = this.props;
15 |
16 | return (
17 |
18 |
19 | { children }
20 |
26 |
27 | );
28 | }
29 | });
30 |
31 | function mapStateToProps(state) {
32 | return {
33 | file: state.file
34 | };
35 | }
36 |
37 |
38 | module.exports = GitBook.connect(AlgoliaResults, mapStateToProps);
39 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const GitBook = require('gitbook-core');
2 | const algoliasearch = require('algoliasearch');
3 |
4 | const AlgoliaResults = require('./components/AlgoliaResults');
5 | const unescape = require('./unescape');
6 |
7 | const HITS_PER_PAGE = 15;
8 |
9 | module.exports = GitBook.createPlugin({
10 | activate: (dispatch, getState, { Search, Components }) => {
11 | dispatch(Search.registerHandler('algolia', searchHandler));
12 |
13 | if (isFreeAccount(getState)) {
14 | dispatch(Components.registerComponent(AlgoliaResults, { role: 'search:results' }));
15 | }
16 | },
17 |
18 | reduce: (state, action) => state
19 | });
20 |
21 | /**
22 | * Search against a query
23 | * @param {String} query
24 | * @return {Promise>}
25 | */
26 | function searchHandler(query, dispatch, getState) {
27 | const index = getAlgoliaIndex(getState);
28 |
29 | return new GitBook.Promise((resolve, reject) => {
30 | return index.search(query, { hitsPerPage: HITS_PER_PAGE })
31 | .then(function(res) {
32 | const results = res.hits.map(function(hit) {
33 | return {
34 | title: hit.title,
35 | // hit.body is escaped HTML, we need to unescape it
36 | body: unescape(hit.body),
37 | url: hit.url
38 | };
39 | });
40 |
41 | return resolve(results);
42 | }, reject);
43 | });
44 | }
45 |
46 | /**
47 | * #param {Function} getState
48 | * @return {Object} The plugin config
49 | */
50 | function getConfig(getState) {
51 | return getState().config.getIn(['pluginsConfig', 'algolia']);
52 | }
53 |
54 | /**
55 | * Return the Algolia client. Initialize it if needed.
56 | *#param {Function} getState
57 | */
58 | function getAlgoliaIndex(getState) {
59 | const config = getConfig(getState);
60 | const client = algoliasearch(config.get('applicationID'), config.get('publicKey'));
61 | return client.initIndex(config.get('index'));
62 | }
63 |
64 | /**
65 | * @param getState
66 | * @return {Boolean} true if the configured account is free
67 | */
68 | function isFreeAccount(getState) {
69 | return getConfig(getState).get('freeAccount');
70 | }
71 |
--------------------------------------------------------------------------------
/src/unescape.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Unescape an HTML string. XSS vulnerable!
3 | * @param {String} str
4 | * @return {String}
5 | */
6 | function unescape(str) {
7 | const el = document.createElement('div');
8 | // XSS can happen here... But we trust the results from Algolia,
9 | // and the impact of XSS is limited on books
10 | el.innerHTML = str;
11 | return el.textContent;
12 | }
13 |
14 | module.exports = unescape;
15 |
--------------------------------------------------------------------------------