mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-28 13:08:57 +00:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33d7e022e6 | |||
| 09782b964b | |||
| 7938ee8ec6 | |||
| 2e8e4f63d7 | |||
| b9fa6c3051 | |||
| 9c071dade5 | |||
| 56e44651fa | |||
| 42fac98992 | |||
| 4f5513add0 | |||
| 90cbfc0596 | |||
| 38ab9fa05a | |||
| 12e20c5fcc | |||
| 6b103f4532 | |||
| d4fa5c18a8 | |||
| 2a116dfdb0 | |||
| 0349f54f3c | |||
| 695bda312c | |||
| 144bd4c97a | |||
| e105983cf8 | |||
| ea3afbed75 | |||
| d563abe3df | |||
| 70fd4ded29 | |||
| 9ce9388dfc | |||
| c575ad83ae | |||
| 936fa1545b | |||
| c9c2dde2b6 | |||
| d9a1d387c7 | |||
| bf29d187c8 | |||
| d86f9b2667 | |||
| aca4313fc3 | |||
| 4cb5bb06fb | |||
| 24988b75ea | |||
| 775e858d05 | |||
| 1e3d871c1e | |||
| 94a4310c1e | |||
| e8ee57be83 | |||
| 50a132cb2c | |||
| 59be32a54f | |||
| 4ae7cdb5d1 | |||
| 990a4efeb5 | |||
| 9c927dcf13 | |||
| 7a08b3f109 | |||
| 94d4234cb8 | |||
| fbc851bc4e | |||
| e92b661a2a | |||
| b9fe60915f | |||
| 1bf7d092f0 | |||
| 826d472511 | |||
| a6b1f4bd87 | |||
| 004f91f02c | |||
| e6c6ec4293 | |||
| a1d3acf065 | |||
| a732660f59 | |||
| e1bb0638f8 | |||
| 788c0c7e7e | |||
| 762fe16a17 | |||
| 15123805d1 | |||
| 23aecb4f2b | |||
| f1d200b81b | |||
| b566711ee1 | |||
| 22da5aac11 | |||
| f7b95102c5 | |||
| ca70c934d6 | |||
| 1cc48fa314 | |||
| b63b5574d3 | |||
| 67106e8698 | |||
| dc2ab51a88 | |||
| cedfa186f2 | |||
| 80f0d1bfed | |||
| 7178ab1f82 | |||
| bf895b707e | |||
| 996170a4ad | |||
| aa66a4f928 | |||
| 23010fa54a | |||
| 0baebc7979 | |||
| 257158321c | |||
| ddf0a41e29 | |||
| 357e1e8e96 | |||
| 7a89f9869a | |||
| 30fbd22efe | |||
| 29df73ba72 | |||
| f8676483c1 | |||
| c79a6f2fba | |||
| c38e67ea61 | |||
| 97a5f8805e | |||
| 421fef6ebf | |||
| 33406c3082 | |||
| c3851889b0 | |||
| 4a5de2da6b | |||
| 1afb23cada | |||
| a3b0f3f868 | |||
| 0aa32916d6 | |||
| ebe845d46c | |||
| edee8d2fc5 | |||
| 5f3de0876f | |||
| 2ceab04d01 | |||
| de37de44e8 | |||
| ccb87d5a13 | |||
| e2e0ca3f05 | |||
| b33a79704b | |||
| 6def174b84 | |||
| 346a18b444 | |||
| b32685c698 | |||
| 09b3618da8 | |||
| 901328f247 | |||
| b47ebaa9d9 | |||
| 465e8d6c0a | |||
| ecd86ec877 | |||
| 5d7864154c | |||
| 7b915428f7 | |||
| 0bce3517fd | |||
| 089815a8fd | |||
| 7e8f1a9632 | |||
| 8469648241 | |||
| e7a44358a5 | |||
| fb930d889f | |||
| 721a75309b | |||
| b856bff91b | |||
| 0744f000f9 |
@@ -0,0 +1,19 @@
|
|||||||
|
Thank you for giving us your feedback! We listed some guidelines to help you:
|
||||||
|
|
||||||
|
If possible, open separate issues for each bug report. Avoid grouping bugs that are unrelated.
|
||||||
|
|
||||||
|
### What is the current behavior?
|
||||||
|
|
||||||
|
If the current behavior is a bug, please provide the steps to reproduce. if possible, provide the GitBook version being used (`gitbook -V`) and the system infromations (OS and version).
|
||||||
|
|
||||||
|
### What is the expected behavior?
|
||||||
|
|
||||||
|
How do you think, or how would you like it should behave.
|
||||||
|
|
||||||
|
#### For GitBook.com
|
||||||
|
|
||||||
|
Provide, if possible, your username and the URL of the concerned book.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
The GitBook Team
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
sudo: false
|
sudo: false
|
||||||
language: node_js
|
language: node_js
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
node_js:
|
node_js:
|
||||||
- "stable"
|
- "stable"
|
||||||
- "4.1"
|
- "4.1"
|
||||||
|
|||||||
+35
-1
@@ -2,7 +2,41 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
## 3.0.0 (pre)
|
## 3.2.0
|
||||||
|
- Switch markdown parser from `kramed` to `markup-it`
|
||||||
|
- Fix support of `|` in tables
|
||||||
|
- Fix access to `output.name` in templating
|
||||||
|
- Fix i18n for website and PDF
|
||||||
|
- Fix minor scrolling issues on website
|
||||||
|
- Improve options of default search indexer (keywords, disabling per pages)
|
||||||
|
- Improve pertinence of search results
|
||||||
|
|
||||||
|
## 3.1.1
|
||||||
|
- Fix order of plugins during loading
|
||||||
|
- Fix error when using math and conrefs
|
||||||
|
- Fix target attribute for external links
|
||||||
|
- Fix serve command
|
||||||
|
|
||||||
|
## 3.1.0
|
||||||
|
- Always load themes after plugins
|
||||||
|
- README/Introduction doesn't require to be the first entry in the summary
|
||||||
|
- Highlight active entry in summary when scrolling a page with anchors
|
||||||
|
- Support empty parts in summary
|
||||||
|
|
||||||
|
## 3.0.3
|
||||||
|
- Fix redirection in `gitbook serve` when accessing a folder without trailing slash
|
||||||
|
- Fix links/annotations for glossary not being correctly resolved
|
||||||
|
- Fix regression for supporting uppercase filenames in structure files
|
||||||
|
- Fix `gitbook install` when using Git URLs for plugins
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
- Fix crash for anchor links
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
- Fix regression in link resolution when contain anchor
|
||||||
|
- Fix `structure.<name>` configuration not supporting filenames with dots
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
- Summary can contain external links and anchors (Fix [#776](https://github.com/GitbookIO/gitbook/issues/776))
|
- Summary can contain external links and anchors (Fix [#776](https://github.com/GitbookIO/gitbook/issues/776))
|
||||||
- Summary can contain differents entitled sections
|
- Summary can contain differents entitled sections
|
||||||
- Glossary is generated as a normal page
|
- Glossary is generated as a normal page
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ module.exports = {
|
|||||||
title: 'GitBook Toolchain Documentation',
|
title: 'GitBook Toolchain Documentation',
|
||||||
|
|
||||||
// Enforce use of GitBook v3
|
// Enforce use of GitBook v3
|
||||||
gitbook: '>=3.0.0-pre.0',
|
gitbook: '3.1.1',
|
||||||
|
|
||||||
// Use the "official" theme
|
// Use the "official" theme
|
||||||
plugins: ['theme-official', 'sitemap'],
|
plugins: ['theme-official@2.1.1', '-sharing', '-fontsettings', 'sitemap'],
|
||||||
|
|
||||||
variables: {
|
variables: {
|
||||||
version: pkg.version
|
version: pkg.version
|
||||||
|
|||||||
+2
-8
@@ -20,18 +20,12 @@ GitBook allows you to customize your book using a flexible configuration. These
|
|||||||
|
|
||||||
Plugins and their configurations are specified in the `book.json`. See [the plugins section](plugins/README.md) for more details.
|
Plugins and their configurations are specified in the `book.json`. See [the plugins section](plugins/README.md) for more details.
|
||||||
|
|
||||||
| Variable | Description |
|
|
||||||
| -------- | ----------- |
|
|
||||||
| `plugins` | List of plugins to load |
|
|
||||||
| `pluginsConfig` |Configuration for plugins |
|
|
||||||
|
|
||||||
### Theme
|
|
||||||
|
|
||||||
Since version 3.0.0, GitBook can use themes. See [the theming section](themes/README.md) for more details.
|
Since version 3.0.0, GitBook can use themes. See [the theming section](themes/README.md) for more details.
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
| -------- | ----------- |
|
| -------- | ----------- |
|
||||||
| `theme` | The theme to use for the book |
|
| `plugins` | List of plugins to load |
|
||||||
|
| `pluginsConfig` |Configuration for plugins |
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -29,12 +29,13 @@ You can replace `/usr/bin` with any directory that is in your $PATH.
|
|||||||
|
|
||||||
### Cover
|
### Cover
|
||||||
|
|
||||||
Covers are used for all the ebook formats. It's an important part of an ebook brandline.
|
Covers are used for all the ebook formats. You can either provide one yourself, or generate one using the [autocover plugin](https://plugins.gitbook.com/plugin/autocover).
|
||||||
|
|
||||||
|
To provide a cover, place a **`cover.jpg`** file at the root directory of your book. Adding a **`cover_small.jpg`** will specify a smaller version of the cover. The cover should be a **JPEG** file.
|
||||||
|
|
||||||
A good cover should respect the following guidelines:
|
A good cover should respect the following guidelines:
|
||||||
|
|
||||||
* Size of 1800x2360 (pixels)
|
* Size of 1800x2360 pixels for `cover.jpg`, 200x262 for `cover_small.jpg`
|
||||||
* No border
|
* No border
|
||||||
* Clearly visible book title
|
* Clearly visible book title
|
||||||
* Any important text should be visible in the small version
|
* Any important text should be visible in the small version
|
||||||
|
|
||||||
|
|||||||
@@ -23,5 +23,7 @@ More than 50,000 books have been published on [GitBook.com](https://www.gitbook.
|
|||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- [DuckDuckHack Documentation](http://docs.duckduckhack.com) by [DuckDuckGo](https://duckduckgo.com/about)
|
- [DuckDuckHack Documentation](http://docs.duckduckhack.com) by [DuckDuckGo](https://duckduckgo.com/about)
|
||||||
|
- [Loomio Handbook](http://loomio.coop/) by [Loomio](https://www.loomio.org/)
|
||||||
|
- [Enspiral Handbook](http://handbook.enspiral.com/) by [Enspiral](http://enspiral.com/)
|
||||||
- This documentation
|
- This documentation
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
### Testing your plugin locally
|
### Testing your plugin locally
|
||||||
|
|
||||||
Testing your plugin on your book before plushing it is possible using [npm link](https://docs.npmjs.com/cli/link).
|
Testing your plugin on your book before publishing it is possible using [npm link](https://docs.npmjs.com/cli/link).
|
||||||
|
|
||||||
In the plugin's folder, run:
|
In the plugin's folder, run:
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ In the plugin's folder, run:
|
|||||||
$ npm link
|
$ npm link
|
||||||
```
|
```
|
||||||
|
|
||||||
The nin your book's folder:
|
Then in your book's folder:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ npm link gitbook-plugin-<plugin's name>
|
$ npm link gitbook-plugin-<plugin's name>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/* Reduce size of logo to allow long versions like 3.0.0-pre.1 */
|
||||||
|
.gb-page-header .container .logo h1 {
|
||||||
|
font-size: 24px ;
|
||||||
|
}
|
||||||
@@ -51,5 +51,5 @@ This is the default content
|
|||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
{% import "./LICENSE" %}
|
{% include "./LICENSE" %}
|
||||||
```
|
```
|
||||||
|
|||||||
Vendored
+16
-1
@@ -17,7 +17,22 @@ A theme is a plugin containing templates and assets. Overriding any individual t
|
|||||||
|
|
||||||
### Extend/Customize theme in a book
|
### Extend/Customize theme in a book
|
||||||
|
|
||||||
Authors can extend the templates of a theme directly from their book's source (without creating an external theme). Templates will be resolved in the `_layouts` folder of the book first, then in
|
Authors can extend the templates of a theme directly from their book's source (without creating an external theme). Templates will be resolved in the `_layouts` folder of the book first, then in the installed plugins/themes.
|
||||||
|
|
||||||
|
### Extend instead of Forking
|
||||||
|
|
||||||
|
When you want to make your theme changes available to multiple books, instead of forking the default theme, you can extend it using the [templating syntax](../templating/README.md):
|
||||||
|
|
||||||
|
```html
|
||||||
|
{% extends template.self %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
{{ super() }}
|
||||||
|
... This will be added to the "body" block
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Take a look at the [API](https://github.com/GitbookIO/theme-api) theme for a more complete example.
|
||||||
|
|
||||||
### Publish a theme
|
### Publish a theme
|
||||||
|
|
||||||
|
|||||||
+11
-4
@@ -6,14 +6,21 @@ module.exports = {
|
|||||||
// Models
|
// Models
|
||||||
Book: require('./models/book'),
|
Book: require('./models/book'),
|
||||||
FS: require('./models/fs'),
|
FS: require('./models/fs'),
|
||||||
|
File: require('./models/file'),
|
||||||
Summary: require('./models/summary'),
|
Summary: require('./models/summary'),
|
||||||
Glossary: require('./models/glossary'),
|
Glossary: require('./models/glossary'),
|
||||||
|
Config: require('./models/config'),
|
||||||
|
Page: require('./models/page'),
|
||||||
|
PluginDependency: require('./models/pluginDependency'),
|
||||||
|
|
||||||
// Modifiers
|
// Modifiers
|
||||||
SummaryModifier: Modifiers.Summary,
|
SummaryModifier: Modifiers.Summary,
|
||||||
ConfigModifier: Modifiers.Config,
|
ConfigModifier: Modifiers.Config,
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
CONFIG_FILES: require('./constants/configFiles.js'),
|
CONFIG_FILES: require('./constants/configFiles.js'),
|
||||||
IGNORE_FILES: require('./constants/ignoreFiles.js')
|
IGNORE_FILES: require('./constants/ignoreFiles.js'),
|
||||||
|
DEFAULT_PLUGINS: require('./constants/defaultPlugins'),
|
||||||
|
EXTENSIONS_MARKDOWN: require('./constants/extsMarkdown'),
|
||||||
|
EXTENSIONS_ASCIIDOC: require('./constants/extsAsciidoc')
|
||||||
};
|
};
|
||||||
|
|||||||
+20
-1
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
var tinylr = require('tiny-lr');
|
var tinylr = require('tiny-lr');
|
||||||
|
var open = require('open');
|
||||||
|
|
||||||
var Parse = require('../parse');
|
var Parse = require('../parse');
|
||||||
var Output = require('../output');
|
var Output = require('../output');
|
||||||
@@ -32,9 +33,11 @@ function generateBook(args, kwargs) {
|
|||||||
var outputFolder = getOutputFolder(args);
|
var outputFolder = getOutputFolder(args);
|
||||||
var book = getBook(args, kwargs);
|
var book = getBook(args, kwargs);
|
||||||
var Generator = Output.getGenerator(kwargs.format);
|
var Generator = Output.getGenerator(kwargs.format);
|
||||||
|
var browser = kwargs['browser'];
|
||||||
|
|
||||||
var hasWatch = kwargs['watch'];
|
var hasWatch = kwargs['watch'];
|
||||||
var hasLiveReloading = kwargs['live'];
|
var hasLiveReloading = kwargs['live'];
|
||||||
|
var hasOpen = kwargs['open'];
|
||||||
|
|
||||||
// Stop server if running
|
// Stop server if running
|
||||||
if (server.isRunning()) console.log('Stopping server');
|
if (server.isRunning()) console.log('Stopping server');
|
||||||
@@ -45,7 +48,9 @@ function generateBook(args, kwargs) {
|
|||||||
.then(function(resultBook) {
|
.then(function(resultBook) {
|
||||||
if (hasLiveReloading) {
|
if (hasLiveReloading) {
|
||||||
// Enable livereload plugin
|
// Enable livereload plugin
|
||||||
resultBook = ConfigModifier.addPlugin(resultBook, 'livereload');
|
var config = resultBook.getConfig();
|
||||||
|
config = ConfigModifier.addPlugin(config, 'livereload');
|
||||||
|
resultBook = resultBook.set('config', config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Output.generate(Generator, resultBook, {
|
return Output.generate(Generator, resultBook, {
|
||||||
@@ -69,6 +74,10 @@ function generateBook(args, kwargs) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasOpen) {
|
||||||
|
open('http://localhost:'+port, browser);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
if (!hasWatch) {
|
if (!hasWatch) {
|
||||||
@@ -110,6 +119,16 @@ module.exports = {
|
|||||||
description: 'Enable live reloading',
|
description: 'Enable live reloading',
|
||||||
defaults: true
|
defaults: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'open',
|
||||||
|
description: 'Enable opening book in browser',
|
||||||
|
defaults: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'browser',
|
||||||
|
description: 'Specify browser for opening book',
|
||||||
|
defaults: ''
|
||||||
|
},
|
||||||
options.log,
|
options.log,
|
||||||
options.format
|
options.format
|
||||||
],
|
],
|
||||||
|
|||||||
+21
-2
@@ -71,11 +71,18 @@ Server.prototype.start = function(dir, port) {
|
|||||||
|
|
||||||
// Redirect to directory's index.html
|
// Redirect to directory's index.html
|
||||||
function redirect() {
|
function redirect() {
|
||||||
|
var resultURL = urlTransform(req.url, function(parsed) {
|
||||||
|
parsed.pathname += '/';
|
||||||
|
return parsed;
|
||||||
|
});
|
||||||
|
|
||||||
res.statusCode = 301;
|
res.statusCode = 301;
|
||||||
res.setHeader('Location', req.url + '/');
|
res.setHeader('Location', resultURL);
|
||||||
res.end('Redirecting to ' + req.url + '/');
|
res.end('Redirecting to ' + resultURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.setHeader('X-Current-Location', req.url);
|
||||||
|
|
||||||
// Send file
|
// Send file
|
||||||
send(req, url.parse(req.url).pathname, {
|
send(req, url.parse(req.url).pathname, {
|
||||||
root: dir
|
root: dir
|
||||||
@@ -106,4 +113,16 @@ Server.prototype.start = function(dir, port) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
urlTransform is a helper function that allows a function to transform
|
||||||
|
a url string in it's parsed form and returns the new url as a string
|
||||||
|
|
||||||
|
@param {String} uri
|
||||||
|
@param {Function} fn
|
||||||
|
@return {String}
|
||||||
|
*/
|
||||||
|
function urlTransform(uri, fn) {
|
||||||
|
return url.format(fn(url.parse(uri)));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = Server;
|
module.exports = Server;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
var jsonschema = require('jsonschema');
|
||||||
|
var schema = require('../configSchema');
|
||||||
|
|
||||||
|
describe('configSchema', function() {
|
||||||
|
|
||||||
|
function validate(cfg) {
|
||||||
|
var v = new jsonschema.Validator();
|
||||||
|
return v.validate(cfg, schema, {
|
||||||
|
propertyName: 'config'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('structure', function() {
|
||||||
|
|
||||||
|
it('should accept dot in filename', function() {
|
||||||
|
var result = validate({
|
||||||
|
structure: {
|
||||||
|
readme: 'book-intro.adoc'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept uppercase in filename', function() {
|
||||||
|
var result = validate({
|
||||||
|
structure: {
|
||||||
|
readme: 'BOOK.adoc'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not accept filepath', function() {
|
||||||
|
var result = validate({
|
||||||
|
structure: {
|
||||||
|
readme: 'folder/myFile.md'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.errors.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
var FILENAME_REGEX = '^[a-zA-Z-._\d,\s]+$';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'$schema': 'http://json-schema.org/schema#',
|
'$schema': 'http://json-schema.org/schema#',
|
||||||
'id': 'https://gitbook.com/schemas/book.json',
|
'id': 'https://gitbook.com/schemas/book.json',
|
||||||
@@ -16,6 +18,10 @@ module.exports = {
|
|||||||
'type': 'string',
|
'type': 'string',
|
||||||
'title': 'ISBN for published book'
|
'title': 'ISBN for published book'
|
||||||
},
|
},
|
||||||
|
'language': {
|
||||||
|
'type': 'string',
|
||||||
|
'title': 'Language of the book'
|
||||||
|
},
|
||||||
'author': {
|
'author': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'title': 'Name of the author'
|
'title': 'Name of the author'
|
||||||
@@ -57,25 +63,25 @@ module.exports = {
|
|||||||
'default': 'LANGS.md',
|
'default': 'LANGS.md',
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'title': 'File to use as languages index',
|
'title': 'File to use as languages index',
|
||||||
'pattern': '^[0-9a-zA-Z ... ]+$'
|
'pattern': FILENAME_REGEX
|
||||||
},
|
},
|
||||||
'readme': {
|
'readme': {
|
||||||
'default': 'README.md',
|
'default': 'README.md',
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'title': 'File to use as preface',
|
'title': 'File to use as preface',
|
||||||
'pattern': '^[0-9a-zA-Z ... ]+$'
|
'pattern': FILENAME_REGEX
|
||||||
},
|
},
|
||||||
'glossary': {
|
'glossary': {
|
||||||
'default': 'GLOSSARY.md',
|
'default': 'GLOSSARY.md',
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'title': 'File to use as glossary index',
|
'title': 'File to use as glossary index',
|
||||||
'pattern': '^[0-9a-zA-Z ... ]+$'
|
'pattern': FILENAME_REGEX
|
||||||
},
|
},
|
||||||
'summary': {
|
'summary': {
|
||||||
'default': 'SUMMARY.md',
|
'default': 'SUMMARY.md',
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'title': 'File to use as table of contents',
|
'title': 'File to use as table of contents',
|
||||||
'pattern': '^[0-9a-zA-Z ... ]+$'
|
'pattern': FILENAME_REGEX
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'additionalProperties': false
|
'additionalProperties': false
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
var Immutable = require('immutable');
|
var Immutable = require('immutable');
|
||||||
|
var PluginDependency = require('../models/pluginDependency');
|
||||||
|
|
||||||
|
var pkg = require('../../package.json');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a PluginDependency from a dependency of gitbook
|
||||||
|
* @param {String} pluginName
|
||||||
|
* @return {PluginDependency}
|
||||||
|
*/
|
||||||
|
function createFromDependency(pluginName) {
|
||||||
|
var npmID = PluginDependency.nameToNpmID(pluginName);
|
||||||
|
var version = pkg.dependencies[npmID];
|
||||||
|
|
||||||
|
return PluginDependency.create(pluginName, version);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
List of default plugins for all books,
|
* List of default plugins for all books,
|
||||||
default plugins should be installed in node dependencies of GitBook
|
* default plugins should be installed in node dependencies of GitBook
|
||||||
*/
|
*/
|
||||||
module.exports = Immutable.List([
|
module.exports = Immutable.List([
|
||||||
'highlight',
|
'highlight',
|
||||||
'search',
|
'search',
|
||||||
@@ -11,4 +26,4 @@ module.exports = Immutable.List([
|
|||||||
'sharing',
|
'sharing',
|
||||||
'fontsettings',
|
'fontsettings',
|
||||||
'theme-default'
|
'theme-default'
|
||||||
]);
|
]).map(createFromDependency);
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = [
|
||||||
|
'.adoc',
|
||||||
|
'.asciidoc'
|
||||||
|
];
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = [
|
||||||
|
'.md',
|
||||||
|
'.markdown',
|
||||||
|
'.mdown'
|
||||||
|
];
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
All GitBook themes plugins name start with this prefix once shorted.
|
||||||
|
*/
|
||||||
|
module.exports = 'theme-';
|
||||||
@@ -3,12 +3,12 @@ var encodePage = require('./encodePage');
|
|||||||
var encodeFile = require('./encodeFile');
|
var encodeFile = require('./encodeFile');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a JSON representation of a book with a specific file
|
* Return a JSON representation of a book with a specific file
|
||||||
|
*
|
||||||
@param {Book} output
|
* @param {Book} output
|
||||||
@param {Page} page
|
* @param {Page} page
|
||||||
@return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
function encodeBookWithPage(book, page) {
|
function encodeBookWithPage(book, page) {
|
||||||
var file = page.getFile();
|
var file = page.getFile();
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
var encodeBook = require('./encodeBook');
|
var encodeBook = require('./encodeBook');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Encode an output to JSON
|
* Encode an output to JSON
|
||||||
|
*
|
||||||
@param {Output}
|
* @param {Output}
|
||||||
@return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
function encodeOutputToJson(output) {
|
function encodeOutputToJson(output) {
|
||||||
var book = output.getBook();
|
var book = output.getBook();
|
||||||
var generator = output.getGenerator();
|
var generator = output.getGenerator();
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
var encodeOutput = require('./encodeOutput');
|
||||||
|
var encodePage = require('./encodePage');
|
||||||
|
var encodeFile = require('./encodeFile');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a JSON representation of a book with a specific file
|
||||||
|
*
|
||||||
|
* @param {Book} output
|
||||||
|
* @param {Page} page
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
function encodeOutputWithPage(output, page) {
|
||||||
|
var file = page.getFile();
|
||||||
|
var book = output.getBook();
|
||||||
|
|
||||||
|
var result = encodeOutput(output);
|
||||||
|
result.page = encodePage(page, book.getSummary());
|
||||||
|
result.file = encodeFile(file);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = encodeOutputWithPage;
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
encodeOutput: require('./encodeOutput'),
|
encodeOutput: require('./encodeOutput'),
|
||||||
encodeBookWithPage: require('./encodeBookWithPage'),
|
encodeBookWithPage: require('./encodeBookWithPage'),
|
||||||
|
encodeOutputWithPage: require('./encodeOutputWithPage'),
|
||||||
encodeBook: require('./encodeBook'),
|
encodeBook: require('./encodeBook'),
|
||||||
encodeFile: require('./encodeFile'),
|
encodeFile: require('./encodeFile'),
|
||||||
encodePage: require('./encodePage'),
|
encodePage: require('./encodePage'),
|
||||||
|
|||||||
@@ -55,6 +55,36 @@ describe('Config', function() {
|
|||||||
expect(world).toBe(2);
|
expect(world).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('toReducedVersion', function() {
|
||||||
|
it('must only return diffs for simple values', function() {
|
||||||
|
var _config = Config.createWithValues({
|
||||||
|
gitbook: '3.0.0'
|
||||||
|
});
|
||||||
|
|
||||||
|
var reducedVersion = _config.toReducedVersion();
|
||||||
|
|
||||||
|
expect(reducedVersion.toJS()).toEqual({
|
||||||
|
gitbook: '3.0.0'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must only return diffs for deep values', function() {
|
||||||
|
var _config = Config.createWithValues({
|
||||||
|
structure: {
|
||||||
|
readme: 'intro.md'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var reducedVersion = _config.toReducedVersion();
|
||||||
|
|
||||||
|
expect(reducedVersion.toJS()).toEqual({
|
||||||
|
structure: {
|
||||||
|
readme: 'intro.md'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
var Immutable = require('immutable');
|
||||||
|
var Page = require('../page');
|
||||||
|
|
||||||
|
describe('Page', function() {
|
||||||
|
|
||||||
|
describe('toText', function() {
|
||||||
|
it('must not prepend frontmatter if no attributes', function() {
|
||||||
|
var page = Page().merge({
|
||||||
|
content: 'Hello World'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(page.toText()).toBe('Hello World');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must prepend frontmatter if attributes', function() {
|
||||||
|
var page = Page().merge({
|
||||||
|
content: 'Hello World',
|
||||||
|
attributes: Immutable.fromJS({
|
||||||
|
hello: 'world'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(page.toText()).toBe('---\nhello: world\n---\n\nHello World');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -42,21 +42,39 @@ describe('PluginDependency', function() {
|
|||||||
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('listToArray', function() {
|
describe('listToArray', function() {
|
||||||
var list = PluginDependency.listToArray(Immutable.List([
|
it('must create an array from a list of plugin dependencies', function() {
|
||||||
PluginDependency.createFromString('hello@1.0.0'),
|
var list = PluginDependency.listToArray(Immutable.List([
|
||||||
PluginDependency.createFromString('noversion'),
|
PluginDependency.createFromString('hello@1.0.0'),
|
||||||
PluginDependency.createFromString('-disabled')
|
PluginDependency.createFromString('noversion'),
|
||||||
]));
|
PluginDependency.createFromString('-disabled')
|
||||||
|
]));
|
||||||
|
|
||||||
expect(list).toEqual([
|
expect(list).toEqual([
|
||||||
'hello@1.0.0',
|
'hello@1.0.0',
|
||||||
'noversion',
|
'noversion',
|
||||||
'-disabled'
|
'-disabled'
|
||||||
]);
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('listFromArray', function() {
|
||||||
|
it('must create an array from a list of plugin dependencies', function() {
|
||||||
|
var arr = Immutable.fromJS([
|
||||||
|
'hello@1.0.0',
|
||||||
|
{
|
||||||
|
'name': 'plugin-ga',
|
||||||
|
'version': 'git+ssh://samy@github.com/GitbookIO/plugin-ga.git'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
var list = PluginDependency.listFromArray(arr);
|
||||||
|
|
||||||
|
expect(list.first().getName()).toBe('hello');
|
||||||
|
expect(list.first().getVersion()).toBe('1.0.0');
|
||||||
|
expect(list.last().getName()).toBe('plugin-ga');
|
||||||
|
expect(list.last().getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
var SummaryArticle = require('../summaryArticle');
|
||||||
|
var File = require('../file');
|
||||||
|
|
||||||
|
describe('SummaryArticle', function() {
|
||||||
|
describe('createChildLevel', function() {
|
||||||
|
it('must create the right level', function() {
|
||||||
|
var article = SummaryArticle.create({}, '1.1');
|
||||||
|
expect(article.createChildLevel()).toBe('1.1.1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must create the right level when has articles', function() {
|
||||||
|
var article = SummaryArticle.create({
|
||||||
|
articles: [
|
||||||
|
{
|
||||||
|
title: 'Test'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, '1.1');
|
||||||
|
expect(article.createChildLevel()).toBe('1.1.2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isFile', function() {
|
||||||
|
it('must return true when exactly the file', function() {
|
||||||
|
var article = SummaryArticle.create({
|
||||||
|
ref: 'hello.md'
|
||||||
|
}, '1.1');
|
||||||
|
var file = File.createWithFilepath('hello.md');
|
||||||
|
|
||||||
|
expect(article.isFile(file)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must return true when path is not normalized', function() {
|
||||||
|
var article = SummaryArticle.create({
|
||||||
|
ref: '/hello.md'
|
||||||
|
}, '1.1');
|
||||||
|
var file = File.createWithFilepath('hello.md');
|
||||||
|
|
||||||
|
expect(article.isFile(file)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must return false when has anchor', function() {
|
||||||
|
var article = SummaryArticle.create({
|
||||||
|
ref: 'hello.md#world'
|
||||||
|
}, '1.1');
|
||||||
|
var file = File.createWithFilepath('hello.md');
|
||||||
|
|
||||||
|
expect(article.isFile(file)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
var SummaryPart = require('../summaryPart');
|
||||||
|
|
||||||
|
describe('SummaryPart', function() {
|
||||||
|
describe('createChildLevel', function() {
|
||||||
|
it('must create the right level', function() {
|
||||||
|
var article = SummaryPart.create({}, '1');
|
||||||
|
expect(article.createChildLevel()).toBe('1.1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must create the right level when has articles', function() {
|
||||||
|
var article = SummaryPart.create({
|
||||||
|
articles: [
|
||||||
|
{
|
||||||
|
title: 'Test'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, '1');
|
||||||
|
expect(article.createChildLevel()).toBe('1.2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
+27
-6
@@ -293,26 +293,47 @@ Book.prototype.getDefaultExt = function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Infer the default path for a Readme.
|
Infer the default path for a Readme.
|
||||||
|
@param {Boolean} [absolute=false] False for a path relative to
|
||||||
|
this book's content root
|
||||||
@return {String}
|
@return {String}
|
||||||
*/
|
*/
|
||||||
Book.prototype.getDefaultReadmePath = function() {
|
Book.prototype.getDefaultReadmePath = function(absolute) {
|
||||||
return this.getContentRoot()+'README'+this.getDefaultExt();
|
var defaultPath = 'README'+this.getDefaultExt();
|
||||||
|
if (absolute) {
|
||||||
|
return path.join(this.getContentRoot(), defaultPath);
|
||||||
|
} else {
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Infer the default path for a Summary.
|
Infer the default path for a Summary.
|
||||||
|
@param {Boolean} [absolute=false] False for a path relative to
|
||||||
|
this book's content root
|
||||||
@return {String}
|
@return {String}
|
||||||
*/
|
*/
|
||||||
Book.prototype.getDefaultSummaryPath = function() {
|
Book.prototype.getDefaultSummaryPath = function(absolute) {
|
||||||
return this.getContentRoot()+'SUMMARY'+this.getDefaultExt();
|
var defaultPath = 'SUMMARY'+this.getDefaultExt();
|
||||||
|
if (absolute) {
|
||||||
|
return path.join(this.getContentRoot(), defaultPath);
|
||||||
|
} else {
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Infer the default path for a Glossary.
|
Infer the default path for a Glossary.
|
||||||
|
@param {Boolean} [absolute=false] False for a path relative to
|
||||||
|
this book's content root
|
||||||
@return {String}
|
@return {String}
|
||||||
*/
|
*/
|
||||||
Book.prototype.getDefaultGlossaryPath = function() {
|
Book.prototype.getDefaultGlossaryPath = function(absolute) {
|
||||||
return this.getContentRoot()+'GLOSSARY'+this.getDefaultExt();
|
var defaultPath = 'GLOSSARY'+this.getDefaultExt();
|
||||||
|
if (absolute) {
|
||||||
|
return path.join(this.getContentRoot(), defaultPath);
|
||||||
|
} else {
|
||||||
|
return defaultPath;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+63
-56
@@ -4,6 +4,7 @@ var Immutable = require('immutable');
|
|||||||
var File = require('./file');
|
var File = require('./file');
|
||||||
var PluginDependency = require('./pluginDependency');
|
var PluginDependency = require('./pluginDependency');
|
||||||
var configDefault = require('../constants/configDefault');
|
var configDefault = require('../constants/configDefault');
|
||||||
|
var reducedObject = require('../utils/reducedObject');
|
||||||
|
|
||||||
var Config = Immutable.Record({
|
var Config = Immutable.Record({
|
||||||
file: File(),
|
file: File(),
|
||||||
@@ -19,21 +20,36 @@ Config.prototype.getValues = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Change the file for the configuration
|
* Return minimum version of configuration,
|
||||||
|
* Basically it returns the current config minus the default one
|
||||||
|
* @return {Map}
|
||||||
|
*/
|
||||||
|
Config.prototype.toReducedVersion = function() {
|
||||||
|
return reducedObject(configDefault, this.getValues());
|
||||||
|
};
|
||||||
|
|
||||||
@param {File} file
|
/**
|
||||||
@return {Config}
|
* Render config as text
|
||||||
*/
|
* @return {Promise<String>}
|
||||||
|
*/
|
||||||
|
Config.prototype.toText = function() {
|
||||||
|
return JSON.stringify(this.toReducedVersion().toJS(), null, 4);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the file for the configuration
|
||||||
|
* @param {File} file
|
||||||
|
* @return {Config}
|
||||||
|
*/
|
||||||
Config.prototype.setFile = function(file) {
|
Config.prototype.setFile = function(file) {
|
||||||
return this.set('file', file);
|
return this.set('file', file);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a configuration value by its key path
|
* Return a configuration value by its key path
|
||||||
|
* @param {String} key
|
||||||
@param {String} key
|
* @return {Mixed}
|
||||||
@return {Mixed}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.getValue = function(keyPath, def) {
|
Config.prototype.getValue = function(keyPath, def) {
|
||||||
var values = this.getValues();
|
var values = this.getValues();
|
||||||
keyPath = Config.keyToKeyPath(keyPath);
|
keyPath = Config.keyToKeyPath(keyPath);
|
||||||
@@ -46,12 +62,11 @@ Config.prototype.getValue = function(keyPath, def) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update a configuration value
|
* Update a configuration value
|
||||||
|
* @param {String} key
|
||||||
@param {String} key
|
* @param {Mixed} value
|
||||||
@param {Mixed} value
|
* @return {Config}
|
||||||
@return {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.setValue = function(keyPath, value) {
|
Config.prototype.setValue = function(keyPath, value) {
|
||||||
keyPath = Config.keyToKeyPath(keyPath);
|
keyPath = Config.keyToKeyPath(keyPath);
|
||||||
|
|
||||||
@@ -64,10 +79,9 @@ Config.prototype.setValue = function(keyPath, value) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a list of plugin dependencies
|
* Return a list of plugin dependencies
|
||||||
|
* @return {List<PluginDependency>}
|
||||||
@return {List<PluginDependency>}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.getPluginDependencies = function() {
|
Config.prototype.getPluginDependencies = function() {
|
||||||
var plugins = this.getValue('plugins');
|
var plugins = this.getValue('plugins');
|
||||||
|
|
||||||
@@ -79,11 +93,10 @@ Config.prototype.getPluginDependencies = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a plugin dependency by its name
|
* Return a plugin dependency by its name
|
||||||
|
* @param {String} name
|
||||||
@param {String} name
|
* @return {PluginDependency}
|
||||||
@return {PluginDependency}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.getPluginDependency = function(name) {
|
Config.prototype.getPluginDependency = function(name) {
|
||||||
var plugins = this.getPluginDependencies();
|
var plugins = this.getPluginDependencies();
|
||||||
|
|
||||||
@@ -93,11 +106,10 @@ Config.prototype.getPluginDependency = function(name) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update the list of plugins dependencies
|
* Update the list of plugins dependencies
|
||||||
|
* @param {List<PluginDependency>}
|
||||||
@param {List<PluginDependency>}
|
* @return {Config}
|
||||||
@return {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.setPluginDependencies = function(deps) {
|
Config.prototype.setPluginDependencies = function(deps) {
|
||||||
var plugins = PluginDependency.listToArray(deps);
|
var plugins = PluginDependency.listToArray(deps);
|
||||||
|
|
||||||
@@ -106,11 +118,10 @@ Config.prototype.setPluginDependencies = function(deps) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update values for an existing configuration
|
* Update values for an existing configuration
|
||||||
|
* @param {Object} values
|
||||||
@param {Object} values
|
* @returns {Config}
|
||||||
@returns {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.updateValues = function(values) {
|
Config.prototype.updateValues = function(values) {
|
||||||
values = Immutable.fromJS(values);
|
values = Immutable.fromJS(values);
|
||||||
|
|
||||||
@@ -118,12 +129,11 @@ Config.prototype.updateValues = function(values) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update values for an existing configuration
|
* Update values for an existing configuration
|
||||||
|
* @param {Config} config
|
||||||
@param {Config} config
|
* @param {Object} values
|
||||||
@param {Object} values
|
* @returns {Config}
|
||||||
@returns {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.prototype.mergeValues = function(values) {
|
Config.prototype.mergeValues = function(values) {
|
||||||
var currentValues = this.getValues();
|
var currentValues = this.getValues();
|
||||||
values = Immutable.fromJS(values);
|
values = Immutable.fromJS(values);
|
||||||
@@ -134,12 +144,11 @@ Config.prototype.mergeValues = function(values) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a new config for a file
|
* Create a new config for a file
|
||||||
|
* @param {File} file
|
||||||
@param {File} file
|
* @param {Object} values
|
||||||
@param {Object} values
|
* @returns {Config}
|
||||||
@returns {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.create = function(file, values) {
|
Config.create = function(file, values) {
|
||||||
return new Config({
|
return new Config({
|
||||||
file: file,
|
file: file,
|
||||||
@@ -148,11 +157,10 @@ Config.create = function(file, values) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a new config
|
* Create a new config
|
||||||
|
* @param {Object} values
|
||||||
@param {Object} values
|
* @returns {Config}
|
||||||
@returns {Config}
|
*/
|
||||||
*/
|
|
||||||
Config.createWithValues = function(values) {
|
Config.createWithValues = function(values) {
|
||||||
return new Config({
|
return new Config({
|
||||||
values: Immutable.fromJS(values)
|
values: Immutable.fromJS(values)
|
||||||
@@ -161,11 +169,10 @@ Config.createWithValues = function(values) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convert a keyPath to an array of keys
|
* Convert a keyPath to an array of keys
|
||||||
|
* @param {String|Array}
|
||||||
@param {String|Array}
|
* @return {Array}
|
||||||
@return {Array}
|
*/
|
||||||
*/
|
|
||||||
Config.keyToKeyPath = function(keyPath) {
|
Config.keyToKeyPath = function(keyPath) {
|
||||||
if (is.string(keyPath)) keyPath = keyPath.split('.');
|
if (is.string(keyPath)) keyPath = keyPath.split('.');
|
||||||
return keyPath;
|
return keyPath;
|
||||||
|
|||||||
+21
-6
@@ -1,4 +1,5 @@
|
|||||||
var Immutable = require('immutable');
|
var Immutable = require('immutable');
|
||||||
|
var yaml = require('js-yaml');
|
||||||
|
|
||||||
var File = require('./file');
|
var File = require('./file');
|
||||||
|
|
||||||
@@ -32,19 +33,33 @@ Page.prototype.getDir = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return path of the page
|
* Return page as text
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
Page.prototype.toText = function() {
|
||||||
|
var attrs = this.getAttributes();
|
||||||
|
var content = this.getContent();
|
||||||
|
|
||||||
@return {String}
|
if (attrs.size === 0) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
var frontMatter = '---\n' + yaml.safeDump(attrs.toJS(), { skipInvalid: true }) + '---\n\n';
|
||||||
|
return (frontMatter + content);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return path of the page
|
||||||
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
Page.prototype.getPath = function() {
|
Page.prototype.getPath = function() {
|
||||||
return this.getFile().getPath();
|
return this.getFile().getPath();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a page for a file
|
* Create a page for a file
|
||||||
|
* @param {File} file
|
||||||
@param {File} file
|
* @return {Page}
|
||||||
@return {Page}
|
|
||||||
*/
|
*/
|
||||||
Page.createForFile = function(file) {
|
Page.createForFile = function(file) {
|
||||||
return new Page({
|
return new Page({
|
||||||
|
|||||||
+53
-49
@@ -1,7 +1,9 @@
|
|||||||
var Immutable = require('immutable');
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
var TemplateBlock = require('./templateBlock');
|
var TemplateBlock = require('./templateBlock');
|
||||||
var PREFIX = require('../constants/pluginPrefix');
|
var PluginDependency = require('./pluginDependency');
|
||||||
|
var THEME_PREFIX = require('../constants/themePrefix');
|
||||||
|
|
||||||
var DEFAULT_VERSION = '*';
|
var DEFAULT_VERSION = '*';
|
||||||
|
|
||||||
var Plugin = Immutable.Record({
|
var Plugin = Immutable.Record({
|
||||||
@@ -16,6 +18,9 @@ var Plugin = Immutable.Record({
|
|||||||
// Depth of this plugin in the dependency tree
|
// Depth of this plugin in the dependency tree
|
||||||
depth: Number(0),
|
depth: Number(0),
|
||||||
|
|
||||||
|
// Parent depending on this plugin
|
||||||
|
parent: String(),
|
||||||
|
|
||||||
// Content of the "package.json"
|
// Content of the "package.json"
|
||||||
package: Immutable.Map(),
|
package: Immutable.Map(),
|
||||||
|
|
||||||
@@ -47,38 +52,48 @@ Plugin.prototype.getDepth = function() {
|
|||||||
return this.get('depth');
|
return this.get('depth');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
Plugin.prototype.getParent = function() {
|
||||||
Return the ID on NPM for this plugin
|
return this.get('parent');
|
||||||
|
|
||||||
@return {String}
|
|
||||||
*/
|
|
||||||
Plugin.prototype.getNpmID = function() {
|
|
||||||
return Plugin.nameToNpmID(this.getName());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Check if a plugin is loaded
|
* Return the ID on NPM for this plugin
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
Plugin.prototype.getNpmID = function() {
|
||||||
|
return PluginDependency.nameToNpmID(this.getName());
|
||||||
|
};
|
||||||
|
|
||||||
@return {Boolean}
|
/**
|
||||||
*/
|
* Check if a plugin is loaded
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
Plugin.prototype.isLoaded = function() {
|
Plugin.prototype.isLoaded = function() {
|
||||||
return Boolean(this.getPackage().size > 0);
|
return Boolean(this.getPackage().size > 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return map of hooks
|
* Check if a plugin is a theme given its name
|
||||||
@return {Map<String:Function>}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
|
Plugin.prototype.isTheme = function() {
|
||||||
|
var name = this.getName();
|
||||||
|
return (name && name.indexOf(THEME_PREFIX) === 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return map of hooks
|
||||||
|
* @return {Map<String:Function>}
|
||||||
|
*/
|
||||||
Plugin.prototype.getHooks = function() {
|
Plugin.prototype.getHooks = function() {
|
||||||
return this.getContent().get('hooks') || Immutable.Map();
|
return this.getContent().get('hooks') || Immutable.Map();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return infos about resources for a specific type
|
* Return infos about resources for a specific type
|
||||||
|
* @param {String} type
|
||||||
@param {String} type
|
* @return {Map<String:Mixed>}
|
||||||
@return {Map<String:Mixed>}
|
*/
|
||||||
*/
|
|
||||||
Plugin.prototype.getResources = function(type) {
|
Plugin.prototype.getResources = function(type) {
|
||||||
if (type != 'website' && type != 'ebook') {
|
if (type != 'website' && type != 'ebook') {
|
||||||
throw new Error('Invalid assets type ' + type);
|
throw new Error('Invalid assets type ' + type);
|
||||||
@@ -91,17 +106,17 @@ Plugin.prototype.getResources = function(type) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return map of filters
|
* Return map of filters
|
||||||
@return {Map<String:Function>}
|
* @return {Map<String:Function>}
|
||||||
*/
|
*/
|
||||||
Plugin.prototype.getFilters = function() {
|
Plugin.prototype.getFilters = function() {
|
||||||
return this.getContent().get('filters');
|
return this.getContent().get('filters');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return map of blocks
|
* Return map of blocks
|
||||||
@return {Map<String:TemplateBlock>}
|
* @return {Map<String:TemplateBlock>}
|
||||||
*/
|
*/
|
||||||
Plugin.prototype.getBlocks = function() {
|
Plugin.prototype.getBlocks = function() {
|
||||||
var blocks = this.getContent().get('blocks');
|
var blocks = this.getContent().get('blocks');
|
||||||
blocks = blocks || Immutable.Map();
|
blocks = blocks || Immutable.Map();
|
||||||
@@ -113,21 +128,19 @@ Plugin.prototype.getBlocks = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a specific hook
|
* Return a specific hook
|
||||||
|
* @param {String} name
|
||||||
@param {String} name
|
* @return {Function|undefined}
|
||||||
@return {Function|undefined}
|
*/
|
||||||
*/
|
|
||||||
Plugin.prototype.getHook = function(name) {
|
Plugin.prototype.getHook = function(name) {
|
||||||
return this.getHooks().get(name);
|
return this.getHooks().get(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a plugin from a string
|
* Create a plugin from a string
|
||||||
|
* @param {String}
|
||||||
@param {String}
|
* @return {Plugin}
|
||||||
@return {Plugin}
|
*/
|
||||||
*/
|
|
||||||
Plugin.createFromString = function(s) {
|
Plugin.createFromString = function(s) {
|
||||||
var parts = s.split('@');
|
var parts = s.split('@');
|
||||||
var name = parts[0];
|
var name = parts[0];
|
||||||
@@ -140,11 +153,10 @@ Plugin.createFromString = function(s) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a plugin from a dependency
|
* Create a plugin from a dependency
|
||||||
|
* @param {PluginDependency}
|
||||||
@param {PluginDependency}
|
* @return {Plugin}
|
||||||
@return {Plugin}
|
*/
|
||||||
*/
|
|
||||||
Plugin.createFromDep = function(dep) {
|
Plugin.createFromDep = function(dep) {
|
||||||
return new Plugin({
|
return new Plugin({
|
||||||
name: dep.getName(),
|
name: dep.getName(),
|
||||||
@@ -152,14 +164,6 @@ Plugin.createFromDep = function(dep) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
Plugin.nameToNpmID = PluginDependency.nameToNpmID;
|
||||||
Return NPM id for a plugin name
|
|
||||||
|
|
||||||
@param {String}
|
|
||||||
@return {String}
|
|
||||||
*/
|
|
||||||
Plugin.nameToNpmID = function(s) {
|
|
||||||
return PREFIX + s;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Plugin;
|
module.exports = Plugin;
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
var is = require('is');
|
var is = require('is');
|
||||||
|
var semver = require('semver');
|
||||||
var Immutable = require('immutable');
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
|
var PREFIX = require('../constants/pluginPrefix');
|
||||||
var DEFAULT_VERSION = '*';
|
var DEFAULT_VERSION = '*';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
PluginDependency represents the informations about a plugin
|
* PluginDependency represents the informations about a plugin
|
||||||
stored in config.plugins
|
* stored in config.plugins
|
||||||
*/
|
*/
|
||||||
var PluginDependency = Immutable.Record({
|
var PluginDependency = Immutable.Record({
|
||||||
name: String(),
|
name: String(),
|
||||||
|
|
||||||
@@ -30,11 +32,39 @@ PluginDependency.prototype.isEnabled = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a plugin with a name and a plugin
|
* Toggle this plugin state
|
||||||
|
* @param {Boolean}
|
||||||
|
* @return {PluginDependency}
|
||||||
|
*/
|
||||||
|
PluginDependency.prototype.toggle = function(state) {
|
||||||
|
if (is.undef(state)) {
|
||||||
|
state = !this.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
@param {String}
|
return this.set('enabled', state);
|
||||||
@return {Plugin|undefined}
|
};
|
||||||
*/
|
|
||||||
|
/**
|
||||||
|
* Return NPM ID for the dependency
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
PluginDependency.prototype.getNpmID = function() {
|
||||||
|
return PluginDependency.nameToNpmID(this.getName());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the plugin using a git dependency
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
PluginDependency.prototype.isGitDependency = function() {
|
||||||
|
return !semver.validRange(this.getVersion());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a plugin with a name and a plugin
|
||||||
|
* @param {String}
|
||||||
|
* @return {Plugin|undefined}
|
||||||
|
*/
|
||||||
PluginDependency.create = function(name, version, enabled) {
|
PluginDependency.create = function(name, version, enabled) {
|
||||||
if (is.undefined(enabled)) {
|
if (is.undefined(enabled)) {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
@@ -48,11 +78,10 @@ PluginDependency.create = function(name, version, enabled) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a plugin from a string
|
* Create a plugin from a string
|
||||||
|
* @param {String}
|
||||||
@param {String}
|
* @return {Plugin|undefined}
|
||||||
@return {Plugin|undefined}
|
*/
|
||||||
*/
|
|
||||||
PluginDependency.createFromString = function(s) {
|
PluginDependency.createFromString = function(s) {
|
||||||
var parts = s.split('@');
|
var parts = s.split('@');
|
||||||
var name = parts[0];
|
var name = parts[0];
|
||||||
@@ -72,22 +101,20 @@ PluginDependency.createFromString = function(s) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a PluginDependency from a string
|
* Create a PluginDependency from a string
|
||||||
|
* @param {String}
|
||||||
@param {String}
|
* @return {List<PluginDependency>}
|
||||||
@return {List<PluginDependency>}
|
*/
|
||||||
*/
|
|
||||||
PluginDependency.listFromString = function(s) {
|
PluginDependency.listFromString = function(s) {
|
||||||
var parts = s.split(',');
|
var parts = s.split(',');
|
||||||
return PluginDependency.listFromArray(parts);
|
return PluginDependency.listFromArray(parts);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a PluginDependency from an array
|
* Create a PluginDependency from an array
|
||||||
|
* @param {Array}
|
||||||
@param {Array}
|
* @return {List<PluginDependency>}
|
||||||
@return {List<PluginDependency>}
|
*/
|
||||||
*/
|
|
||||||
PluginDependency.listFromArray = function(arr) {
|
PluginDependency.listFromArray = function(arr) {
|
||||||
return Immutable.List(arr)
|
return Immutable.List(arr)
|
||||||
.map(function(entry) {
|
.map(function(entry) {
|
||||||
@@ -95,8 +122,8 @@ PluginDependency.listFromArray = function(arr) {
|
|||||||
return PluginDependency.createFromString(entry);
|
return PluginDependency.createFromString(entry);
|
||||||
} else {
|
} else {
|
||||||
return PluginDependency({
|
return PluginDependency({
|
||||||
name: entry.name,
|
name: entry.get('name'),
|
||||||
version: entry.version
|
version: entry.get('version')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -106,11 +133,10 @@ PluginDependency.listFromArray = function(arr) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Export plugin dependencies as an array
|
* Export plugin dependencies as an array
|
||||||
|
* @param {List<PluginDependency>} list
|
||||||
@param {List<PluginDependency>} list
|
* @return {Array<String>}
|
||||||
@return {Array<String>}
|
*/
|
||||||
*/
|
|
||||||
PluginDependency.listToArray = function(list) {
|
PluginDependency.listToArray = function(list) {
|
||||||
return list
|
return list
|
||||||
.map(function(dep) {
|
.map(function(dep) {
|
||||||
@@ -130,4 +156,13 @@ PluginDependency.listToArray = function(list) {
|
|||||||
.toJS();
|
.toJS();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return NPM id for a plugin name
|
||||||
|
* @param {String}
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
PluginDependency.nameToNpmID = function(s) {
|
||||||
|
return PREFIX + s;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = PluginDependency;
|
module.exports = PluginDependency;
|
||||||
|
|||||||
@@ -30,20 +30,21 @@ SummaryArticle.prototype.getArticles = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return how deep the article is.
|
* Return how deep the article is.
|
||||||
The README has a depth of 1
|
* The README has a depth of 1
|
||||||
|
*
|
||||||
@return {Number}
|
* @return {Number}
|
||||||
*/
|
*/
|
||||||
SummaryArticle.prototype.getDepth = function() {
|
SummaryArticle.prototype.getDepth = function() {
|
||||||
return (this.getLevel().split('.').length - 1);
|
return (this.getLevel().split('.').length - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get path (without anchor) to the pointing file
|
* Get path (without anchor) to the pointing file.
|
||||||
|
* It also normalizes the file path.
|
||||||
@return {String}
|
*
|
||||||
*/
|
* @return {String}
|
||||||
|
*/
|
||||||
SummaryArticle.prototype.getPath = function() {
|
SummaryArticle.prototype.getPath = function() {
|
||||||
if (this.isExternal()) {
|
if (this.isExternal()) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -58,24 +59,24 @@ SummaryArticle.prototype.getPath = function() {
|
|||||||
|
|
||||||
var pathname = (parts.length > 1? parts.slice(0, -1).join('#') : ref);
|
var pathname = (parts.length > 1? parts.slice(0, -1).join('#') : ref);
|
||||||
|
|
||||||
// Normalize path to remove ('./', etc)
|
// Normalize path to remove ('./', '/...', etc)
|
||||||
return location.normalize(pathname);
|
return location.flatten(pathname);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return url if article is external
|
* Return url if article is external
|
||||||
|
*
|
||||||
@return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
SummaryArticle.prototype.getUrl = function() {
|
SummaryArticle.prototype.getUrl = function() {
|
||||||
return this.isExternal()? this.getRef() : undefined;
|
return this.isExternal()? this.getRef() : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get anchor for this article (or undefined)
|
* Get anchor for this article (or undefined)
|
||||||
|
*
|
||||||
@return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
SummaryArticle.prototype.getAnchor = function() {
|
SummaryArticle.prototype.getAnchor = function() {
|
||||||
var ref = this.getRef();
|
var ref = this.getRef();
|
||||||
var parts = ref.split('#');
|
var parts = ref.split('#');
|
||||||
@@ -85,29 +86,68 @@ SummaryArticle.prototype.getAnchor = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Is article pointing to a page of an absolute url
|
* Create a new level for a new child article
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
SummaryArticle.prototype.createChildLevel = function() {
|
||||||
|
var level = this.getLevel();
|
||||||
|
var subArticles = this.getArticles();
|
||||||
|
var childLevel = level + '.' + (subArticles.size + 1);
|
||||||
|
|
||||||
@return {Boolean}
|
return childLevel;
|
||||||
*/
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is article pointing to a page of an absolute url
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
SummaryArticle.prototype.isPage = function() {
|
SummaryArticle.prototype.isPage = function() {
|
||||||
return !this.isExternal() && this.getRef();
|
return !this.isExternal() && this.getRef();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Is article pointing to aan absolute url
|
* Check if this article is a file (exatcly)
|
||||||
|
*
|
||||||
|
* @param {File} file
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
SummaryArticle.prototype.isFile = function(file) {
|
||||||
|
return (
|
||||||
|
file.getPath() === this.getPath()
|
||||||
|
&& this.getAnchor() === undefined
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@return {Boolean}
|
/**
|
||||||
*/
|
* Check if this article is the introduction of the book
|
||||||
|
*
|
||||||
|
* @param {Book|Readme} book
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
SummaryArticle.prototype.isReadme = function(book) {
|
||||||
|
var readme = book.getFile? book : book.getReadme();
|
||||||
|
var file = readme.getFile();
|
||||||
|
|
||||||
|
return this.isFile(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is article pointing to aan absolute url
|
||||||
|
*
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
SummaryArticle.prototype.isExternal = function() {
|
SummaryArticle.prototype.isExternal = function() {
|
||||||
return location.isExternal(this.getRef());
|
return location.isExternal(this.getRef());
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a SummaryArticle
|
* Create a SummaryArticle
|
||||||
|
*
|
||||||
@param {Object} def
|
* @param {Object} def
|
||||||
@return {SummaryArticle}
|
* @return {SummaryArticle}
|
||||||
*/
|
*/
|
||||||
SummaryArticle.create = function(def, level) {
|
SummaryArticle.create = function(def, level) {
|
||||||
var articles = (def.articles || []).map(function(article, i) {
|
var articles = (def.articles || []).map(function(article, i) {
|
||||||
if (article instanceof SummaryArticle) {
|
if (article instanceof SummaryArticle) {
|
||||||
@@ -124,14 +164,13 @@ SummaryArticle.create = function(def, level) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Find an article from a base one
|
* Find an article from a base one
|
||||||
|
*
|
||||||
@param {Article|Part} base
|
* @param {Article|Part} base
|
||||||
@param {Function(article)} iter
|
* @param {Function(article)} iter
|
||||||
@return {Article}
|
* @return {Article}
|
||||||
*/
|
*/
|
||||||
SummaryArticle.findArticle = function(base, iter) {
|
SummaryArticle.findArticle = function(base, iter) {
|
||||||
var articles = base.getArticles();
|
var articles = base.getArticles();
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,24 @@ SummaryPart.prototype.getArticles = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a SummaryPart
|
* Create a new level for a new child article
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
SummaryPart.prototype.createChildLevel = function() {
|
||||||
|
var level = this.getLevel();
|
||||||
|
var subArticles = this.getArticles();
|
||||||
|
var childLevel = level + '.' + (subArticles.size + 1);
|
||||||
|
|
||||||
@param {Object} def
|
return childLevel;
|
||||||
@return {SummaryPart}
|
};
|
||||||
*/
|
|
||||||
|
/**
|
||||||
|
* Create a SummaryPart
|
||||||
|
*
|
||||||
|
* @param {Object} def
|
||||||
|
* @return {SummaryPart}
|
||||||
|
*/
|
||||||
SummaryPart.create = function(def, level) {
|
SummaryPart.create = function(def, level) {
|
||||||
var articles = (def.articles || []).map(function(article, i) {
|
var articles = (def.articles || []).map(function(article, i) {
|
||||||
if (article instanceof SummaryArticle) {
|
if (article instanceof SummaryArticle) {
|
||||||
|
|||||||
+32
-39
@@ -43,10 +43,9 @@ TemplateBlock.prototype.getBlocks = function() {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return shortcuts associated with this block or undefined
|
* Return shortcuts associated with this block or undefined
|
||||||
|
* @return {TemplateShortcut|undefined}
|
||||||
@return {TemplateShortcut|undefined}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.getShortcuts = function() {
|
TemplateBlock.prototype.getShortcuts = function() {
|
||||||
var shortcuts = this.get('shortcuts');
|
var shortcuts = this.get('shortcuts');
|
||||||
if (shortcuts.size === 0) {
|
if (shortcuts.size === 0) {
|
||||||
@@ -57,19 +56,17 @@ TemplateBlock.prototype.getShortcuts = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return name for the nunjucks extension
|
* Return name for the nunjucks extension
|
||||||
|
* @return {String}
|
||||||
@return {String}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.getExtensionName = function() {
|
TemplateBlock.prototype.getExtensionName = function() {
|
||||||
return 'Block' + this.getName() + 'Extension';
|
return 'Block' + this.getName() + 'Extension';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a nunjucks extension to represents this block
|
* Return a nunjucks extension to represents this block
|
||||||
|
* @return {Nunjucks.Extension}
|
||||||
@return {Nunjucks.Extension}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
|
TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
|
||||||
blocksOutput = blocksOutput || {};
|
blocksOutput = blocksOutput || {};
|
||||||
|
|
||||||
@@ -192,11 +189,11 @@ TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Apply a block to a content
|
* Apply a block to a content
|
||||||
@param {Object} inner
|
* @param {Object} inner
|
||||||
@param {Object} context
|
* @param {Object} context
|
||||||
@return {Promise<String>|String}
|
* @return {Promise<String>|String}
|
||||||
*/
|
*/
|
||||||
TemplateBlock.prototype.applyBlock = function(inner, context) {
|
TemplateBlock.prototype.applyBlock = function(inner, context) {
|
||||||
var processFn = this.getProcess();
|
var processFn = this.getProcess();
|
||||||
|
|
||||||
@@ -215,11 +212,10 @@ TemplateBlock.prototype.applyBlock = function(inner, context) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Normalize result from a block process function
|
* Normalize result from a block process function
|
||||||
|
* @param {Object|String} result
|
||||||
@param {Object|String} result
|
* @return {Object}
|
||||||
@return {Object}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.normalizeBlockResult = function(result) {
|
TemplateBlock.prototype.normalizeBlockResult = function(result) {
|
||||||
if (is.string(result)) {
|
if (is.string(result)) {
|
||||||
result = { body: result };
|
result = { body: result };
|
||||||
@@ -230,12 +226,11 @@ TemplateBlock.prototype.normalizeBlockResult = function(result) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convert a block result to HTML
|
* Convert a block result to HTML
|
||||||
|
* @param {Object} result
|
||||||
@param {Object} result
|
* @param {Object} blocksOutput: stored post processing blocks in this object
|
||||||
@param {Object} blocksOutput: stored post processing blocks in this object
|
* @return {String}
|
||||||
@return {String}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
|
TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
|
||||||
var indexedKey;
|
var indexedKey;
|
||||||
var toIndex = (!result.parse) || (result.post !== undefined);
|
var toIndex = (!result.parse) || (result.post !== undefined);
|
||||||
@@ -256,12 +251,11 @@ TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a template block from a function or an object
|
* Create a template block from a function or an object
|
||||||
|
* @param {String} blockName
|
||||||
@param {String} blockName
|
* @param {Object} block
|
||||||
@param {Object} block
|
* @return {TemplateBlock}
|
||||||
@return {TemplateBlock}
|
*/
|
||||||
*/
|
|
||||||
TemplateBlock.create = function(blockName, block) {
|
TemplateBlock.create = function(blockName, block) {
|
||||||
if (is.fn(block)) {
|
if (is.fn(block)) {
|
||||||
block = new Immutable.Map({
|
block = new Immutable.Map({
|
||||||
@@ -275,11 +269,10 @@ TemplateBlock.create = function(blockName, block) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Extract kwargs from an arguments array
|
* Extract kwargs from an arguments array
|
||||||
|
* @param {Array} args
|
||||||
@param {Array} args
|
* @return {Object}
|
||||||
@return {Object}
|
*/
|
||||||
*/
|
|
||||||
function extractKwargs(args) {
|
function extractKwargs(args) {
|
||||||
var last = args[args.length - 1];
|
var last = args[args.length - 1];
|
||||||
return (is.object(last) && last.__keywords)? args.pop() : {};
|
return (is.object(last) && last.__keywords)? args.pop() : {};
|
||||||
|
|||||||
@@ -17,23 +17,21 @@ TemplateOutput.prototype.getBlocks = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Update content of this output
|
* Update content of this output
|
||||||
|
* @param {String} content
|
||||||
@param {String} content
|
* @return {TemplateContent}
|
||||||
@return {TemplateContent}
|
*/
|
||||||
*/
|
|
||||||
TemplateOutput.prototype.setContent = function(content) {
|
TemplateOutput.prototype.setContent = function(content) {
|
||||||
return this.set('content', content);
|
return this.set('content', content);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a TemplateOutput from a text content
|
* Create a TemplateOutput from a text content
|
||||||
and an object containing block definition
|
* and an object containing block definition
|
||||||
|
* @param {String} content
|
||||||
@param {String} content
|
* @param {Object} blocks
|
||||||
@param {Object} blocks
|
* @return {TemplateOutput}
|
||||||
@return {TemplateOutput}
|
*/
|
||||||
*/
|
|
||||||
TemplateOutput.create = function(content, blocks) {
|
TemplateOutput.create = function(content, blocks) {
|
||||||
return new TemplateOutput({
|
return new TemplateOutput({
|
||||||
content: content,
|
content: content,
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
var addPlugin = require('../addPlugin');
|
var addPlugin = require('../addPlugin');
|
||||||
var Config = require('../../../models/config');
|
var Config = require('../../../models/config');
|
||||||
var Book = require('../../../models/book');
|
|
||||||
|
|
||||||
describe('addPlugin', function() {
|
describe('addPlugin', function() {
|
||||||
var config = Config.createWithValues({
|
var config = Config.createWithValues({
|
||||||
plugins: ['hello', 'world', '-disabled']
|
plugins: ['hello', 'world', '-disabled']
|
||||||
});
|
});
|
||||||
var book = Book().setConfig(config);
|
|
||||||
|
|
||||||
it('should have correct state of dependencies', function() {
|
it('should have correct state of dependencies', function() {
|
||||||
var disabledDep = config.getPluginDependency('disabled');
|
var disabledDep = config.getPluginDependency('disabled');
|
||||||
@@ -17,8 +15,7 @@ describe('addPlugin', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should add the plugin to the list', function() {
|
it('should add the plugin to the list', function() {
|
||||||
var newBook = addPlugin(book, 'test');
|
var newConfig = addPlugin(config, 'test');
|
||||||
var newConfig = newBook.getConfig();
|
|
||||||
|
|
||||||
var testDep = newConfig.getPluginDependency('test');
|
var testDep = newConfig.getPluginDependency('test');
|
||||||
expect(testDep).toBeDefined();
|
expect(testDep).toBeDefined();
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
var removePlugin = require('../removePlugin');
|
||||||
|
var Config = require('../../../models/config');
|
||||||
|
|
||||||
|
describe('removePlugin', function() {
|
||||||
|
var config = Config.createWithValues({
|
||||||
|
plugins: ['hello', 'world', '-disabled']
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the plugin from the list', function() {
|
||||||
|
var newConfig = removePlugin(config, 'hello');
|
||||||
|
|
||||||
|
var testDep = newConfig.getPluginDependency('hello');
|
||||||
|
expect(testDep).toNotBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the disabled plugin from the list', function() {
|
||||||
|
var newConfig = removePlugin(config, 'disabled');
|
||||||
|
|
||||||
|
var testDep = newConfig.getPluginDependency('disabled');
|
||||||
|
expect(testDep).toNotBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable default plugin', function() {
|
||||||
|
var newConfig = removePlugin(config, 'search');
|
||||||
|
|
||||||
|
var disabledDep = newConfig.getPluginDependency('search');
|
||||||
|
expect(disabledDep).toBeDefined();
|
||||||
|
expect(disabledDep.getVersion()).toEqual('*');
|
||||||
|
expect(disabledDep.isEnabled()).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
var togglePlugin = require('../togglePlugin');
|
||||||
|
var Config = require('../../../models/config');
|
||||||
|
|
||||||
|
describe('togglePlugin', function() {
|
||||||
|
var config = Config.createWithValues({
|
||||||
|
plugins: ['hello', 'world', '-disabled']
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enable plugin', function() {
|
||||||
|
var newConfig = togglePlugin(config, 'disabled');
|
||||||
|
|
||||||
|
var testDep = newConfig.getPluginDependency('disabled');
|
||||||
|
expect(testDep).toBeDefined();
|
||||||
|
expect(testDep.getVersion()).toEqual('*');
|
||||||
|
expect(testDep.isEnabled()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should disable plugin', function() {
|
||||||
|
var newConfig = togglePlugin(config, 'world');
|
||||||
|
|
||||||
|
var testDep = newConfig.getPluginDependency('world');
|
||||||
|
expect(testDep).toBeDefined();
|
||||||
|
expect(testDep.getVersion()).toEqual('*');
|
||||||
|
expect(testDep.isEnabled()).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
var PluginDependency = require('../../models/pluginDependency');
|
var PluginDependency = require('../../models/pluginDependency');
|
||||||
|
var togglePlugin = require('./togglePlugin');
|
||||||
|
var isDefaultPlugin = require('./isDefaultPlugin');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Add a plugin to a book's configuration
|
* Add a plugin to a book's configuration
|
||||||
|
* @param {Config} config
|
||||||
|
* @param {String} pluginName
|
||||||
|
* @param {String} version (optional)
|
||||||
|
* @return {Config}
|
||||||
|
*/
|
||||||
|
function addPlugin(config, pluginName, version) {
|
||||||
|
// For default plugin, we only ensure it is enabled
|
||||||
|
if (isDefaultPlugin(pluginName, version)) {
|
||||||
|
return togglePlugin(config, pluginName, true);
|
||||||
|
}
|
||||||
|
|
||||||
@param {Book} book
|
|
||||||
@param {String} plugin
|
|
||||||
@param {String} version (optional)
|
|
||||||
@return {Book}
|
|
||||||
*/
|
|
||||||
function addPlugin(book, plugin, version) {
|
|
||||||
var config = book.getConfig();
|
|
||||||
var deps = config.getPluginDependencies();
|
var deps = config.getPluginDependencies();
|
||||||
|
var dep = PluginDependency.create(pluginName, version);
|
||||||
var dep = PluginDependency.create(plugin, version);
|
|
||||||
|
|
||||||
deps = deps.push(dep);
|
deps = deps.push(dep);
|
||||||
config = config.setPluginDependencies(deps);
|
return config.setPluginDependencies(deps);
|
||||||
|
|
||||||
return book.setConfig(config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = addPlugin;
|
module.exports = addPlugin;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Edit configuration of a plugin
|
||||||
|
* @param {Config} config
|
||||||
|
* @param {String} plugin
|
||||||
|
* @param {Object} pluginConfig
|
||||||
|
* @return {Config}
|
||||||
|
*/
|
||||||
|
function editPlugin(config, pluginName, pluginConfig) {
|
||||||
|
return config.setValue('pluginsConfig.'+pluginName, pluginConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = editPlugin;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Return the configuration for a plugin
|
||||||
|
* @param {Config} config
|
||||||
|
* @param {String} pluginName
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
function getPluginConfig(config, pluginName) {
|
||||||
|
var pluginsConfig = config.getValues().get('pluginsConfig');
|
||||||
|
if (pluginsConfig === undefined) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
var pluginConf = pluginsConfig.get(pluginName);
|
||||||
|
if (pluginConf === undefined) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
return pluginConf.toJS();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getPluginConfig;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a plugin is listed
|
||||||
|
* @param { {List<PluginDependency}} deps
|
||||||
|
* @param {String} plugin
|
||||||
|
* @param {String} version
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
function hasPlugin(deps, pluginName, version) {
|
||||||
|
return !!deps.find(function(dep) {
|
||||||
|
return dep.getName() === pluginName && (!version || dep.getVersion() === version);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = hasPlugin;
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
addPlugin: require('./addPlugin'),
|
addPlugin: require('./addPlugin'),
|
||||||
removePlugin: require('./removePlugin')
|
removePlugin: require('./removePlugin'),
|
||||||
|
togglePlugin: require('./togglePlugin'),
|
||||||
|
editPlugin: require('./editPlugin'),
|
||||||
|
hasPlugin: require('./hasPlugin'),
|
||||||
|
getPluginConfig: require('./getPluginConfig'),
|
||||||
|
isDefaultPlugin: require('./isDefaultPlugin')
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
var DEFAULT_PLUGINS = require('../../constants/defaultPlugins');
|
||||||
|
var hasPlugin = require('./hasPlugin');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a plugin is a default one
|
||||||
|
* @param {String} plugin
|
||||||
|
* @param {String} version
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
function isDefaultPlugin(pluginName, version) {
|
||||||
|
return hasPlugin(DEFAULT_PLUGINS, pluginName, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = isDefaultPlugin;
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
|
var togglePlugin = require('./togglePlugin');
|
||||||
|
var isDefaultPlugin = require('./isDefaultPlugin');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Remove a plugin from a book's configuration
|
* Remove a plugin from a book's configuration
|
||||||
|
* @param {Config} config
|
||||||
@param {Book} book
|
* @param {String} plugin
|
||||||
@param {String} plugin
|
* @return {Config}
|
||||||
@return {Book}
|
*/
|
||||||
*/
|
function removePlugin(config, pluginName) {
|
||||||
function removePlugin(book, pluginName) {
|
|
||||||
var config = book.getConfig();
|
|
||||||
var deps = config.getPluginDependencies();
|
var deps = config.getPluginDependencies();
|
||||||
|
|
||||||
|
// For default plugin, we have to disable it instead of removing from the list
|
||||||
|
if (isDefaultPlugin(pluginName)) {
|
||||||
|
return togglePlugin(config, pluginName, false);
|
||||||
|
}
|
||||||
|
|
||||||
deps = deps.filter(function(dep) {
|
// Remove the dependency from the list
|
||||||
|
deps = deps.filterNot(function(dep) {
|
||||||
return dep.getName() === pluginName;
|
return dep.getName() === pluginName;
|
||||||
});
|
});
|
||||||
config = config.setPluginDependencies(deps);
|
return config.setPluginDependencies(deps);
|
||||||
|
|
||||||
return book.setConfig(config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = removePlugin;
|
module.exports = removePlugin;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
var PluginDependency = require('../../models/pluginDependency');
|
||||||
|
var hasPlugin = require('./hasPlugin');
|
||||||
|
var isDefaultPlugin = require('./isDefaultPlugin');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable a plugin dependency
|
||||||
|
* @param {Config} config
|
||||||
|
* @param {String} pluginName
|
||||||
|
* @param {Boolean} state (optional)
|
||||||
|
* @return {Config}
|
||||||
|
*/
|
||||||
|
function togglePlugin(config, pluginName, state) {
|
||||||
|
var deps = config.getPluginDependencies();
|
||||||
|
|
||||||
|
// For default plugin, we should ensure it's listed first
|
||||||
|
if (isDefaultPlugin(pluginName) && !hasPlugin(deps, pluginName)) {
|
||||||
|
deps = deps.push(PluginDependency.create(pluginName));
|
||||||
|
}
|
||||||
|
|
||||||
|
deps = deps.map(function(dep) {
|
||||||
|
if (dep.getName() === pluginName) {
|
||||||
|
return dep.toggle(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dep;
|
||||||
|
});
|
||||||
|
|
||||||
|
return config.setPluginDependencies(deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = togglePlugin;
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
var Summary = require('../../../models/summary');
|
||||||
|
var SummaryPart = require('../../../models/summaryPart');
|
||||||
|
|
||||||
|
var File = require('../../../models/file');
|
||||||
|
|
||||||
|
describe('insertPart', function() {
|
||||||
|
var insertPart = require('../insertPart');
|
||||||
|
var summary = Summary.createFromParts(File(), [
|
||||||
|
{
|
||||||
|
articles: [
|
||||||
|
{
|
||||||
|
title: '1.1',
|
||||||
|
path: '1.1'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Part I',
|
||||||
|
articles: [
|
||||||
|
{
|
||||||
|
title: '2.1',
|
||||||
|
path: '2.1',
|
||||||
|
articles: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '2.2',
|
||||||
|
path: '2.2'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('should insert an part at a given level', function() {
|
||||||
|
var part = SummaryPart.create({
|
||||||
|
title: 'Inserted'
|
||||||
|
}, 'meaningless.level');
|
||||||
|
|
||||||
|
var newSummary = insertPart(summary, part, 1);
|
||||||
|
|
||||||
|
var inserted = newSummary.getPart(1);
|
||||||
|
expect(inserted.getTitle()).toBe('Inserted');
|
||||||
|
expect(newSummary.getParts().count()).toBe(3);
|
||||||
|
|
||||||
|
var otherArticle = newSummary.getByLevel('3.1');
|
||||||
|
expect(otherArticle.getTitle()).toBe('2.1');
|
||||||
|
expect(otherArticle.getLevel()).toBe('3.1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert an part in last position', function() {
|
||||||
|
var part = SummaryPart.create({
|
||||||
|
title: 'Inserted'
|
||||||
|
}, 'meaningless.level');
|
||||||
|
|
||||||
|
var newSummary = insertPart(summary, part, 2);
|
||||||
|
|
||||||
|
var inserted = newSummary.getPart(2);
|
||||||
|
expect(inserted.getTitle()).toBe('Inserted');
|
||||||
|
expect(newSummary.getParts().count()).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
|
var Output = require('../../models/output');
|
||||||
|
var Book = require('../../models/book');
|
||||||
|
var parseBook = require('../../parse/parseBook');
|
||||||
|
var createMockFS = require('../../fs/mock');
|
||||||
|
var preparePlugins = require('../preparePlugins');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an output using a generator
|
||||||
|
*
|
||||||
|
* FOR TESTING PURPOSE ONLY
|
||||||
|
*
|
||||||
|
* @param {Generator} generator
|
||||||
|
* @param {Map<String:String|Map>} files
|
||||||
|
* @return {Promise<Output>}
|
||||||
|
*/
|
||||||
|
function createMockOutput(generator, files, options) {
|
||||||
|
var fs = createMockFS(files);
|
||||||
|
var book = Book.createForFS(fs);
|
||||||
|
var state = generator.State? generator.State({}) : Immutable.Map();
|
||||||
|
|
||||||
|
book = book.setLogLevel('disabled');
|
||||||
|
options = generator.Options(options);
|
||||||
|
|
||||||
|
return parseBook(book)
|
||||||
|
.then(function(resultBook) {
|
||||||
|
return new Output({
|
||||||
|
book: resultBook,
|
||||||
|
options: options,
|
||||||
|
state: state,
|
||||||
|
generator: generator.name
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(preparePlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createMockOutput;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
var generateMock = require('../generateMock');
|
var generateMock = require('./generateMock');
|
||||||
var EbookGenerator = require('../ebook');
|
var EbookGenerator = require('../ebook');
|
||||||
|
|
||||||
describe('EbookGenerator', function() {
|
describe('EbookGenerator', function() {
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
var tmp = require('tmp');
|
||||||
|
|
||||||
|
var Book = require('../../models/book');
|
||||||
|
var createMockFS = require('../../fs/mock');
|
||||||
|
var parseBook = require('../../parse/parseBook');
|
||||||
|
var generateBook = require('../generateBook');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a book using a generator
|
||||||
|
* And returns the path to the output dir.
|
||||||
|
*
|
||||||
|
* FOR TESTING PURPOSE ONLY
|
||||||
|
*
|
||||||
|
* @param {Generator}
|
||||||
|
* @param {Map<String:String|Map>} files
|
||||||
|
* @return {Promise<String>}
|
||||||
|
*/
|
||||||
|
function generateMock(Generator, files) {
|
||||||
|
var fs = createMockFS(files);
|
||||||
|
var book = Book.createForFS(fs);
|
||||||
|
var dir;
|
||||||
|
|
||||||
|
try {
|
||||||
|
dir = tmp.dirSync();
|
||||||
|
} catch(err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
book = book.setLogLevel('disabled');
|
||||||
|
|
||||||
|
return parseBook(book)
|
||||||
|
.then(function(resultBook) {
|
||||||
|
return generateBook(Generator, resultBook, {
|
||||||
|
root: dir.name
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.thenResolve(dir.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = generateMock;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
var generateMock = require('../generateMock');
|
var generateMock = require('./generateMock');
|
||||||
var JSONGenerator = require('../json');
|
var JSONGenerator = require('../json');
|
||||||
|
|
||||||
describe('JSONGenerator', function() {
|
describe('JSONGenerator', function() {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
var generateMock = require('../generateMock');
|
var fs = require('fs');
|
||||||
|
var generateMock = require('./generateMock');
|
||||||
var WebsiteGenerator = require('../website');
|
var WebsiteGenerator = require('../website');
|
||||||
|
|
||||||
describe('WebsiteGenerator', function() {
|
describe('WebsiteGenerator', function() {
|
||||||
@@ -12,6 +13,54 @@ describe('WebsiteGenerator', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Glossary', function() {
|
||||||
|
var folder;
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
return generateMock(WebsiteGenerator, {
|
||||||
|
'README.md': 'Hello World',
|
||||||
|
'SUMMARY.md': '* [Deep](folder/page.md)',
|
||||||
|
'folder': {
|
||||||
|
'page.md': 'Hello World'
|
||||||
|
},
|
||||||
|
'GLOSSARY.md': '# Glossary\n\n## Hello\n\nHello World'
|
||||||
|
})
|
||||||
|
.then(function(_folder) {
|
||||||
|
folder = _folder;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate a GLOSSARY.html', function() {
|
||||||
|
expect(folder).toHaveFile('GLOSSARY.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly resolve glossary links in README', function() {
|
||||||
|
var html = fs.readFileSync(folder + '/index.html', 'utf8');
|
||||||
|
expect(html).toHaveDOMElement('.page-inner a[href="GLOSSARY.html#hello"]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly resolve glossary links in directory', function() {
|
||||||
|
var html = fs.readFileSync(folder + '/folder/page.html', 'utf8');
|
||||||
|
expect(html).toHaveDOMElement('.page-inner a[href="../GLOSSARY.html#hello"]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept a custom glossary file', function() {
|
||||||
|
return generateMock(WebsiteGenerator, {
|
||||||
|
'README.md': 'Hello World',
|
||||||
|
'book.json': '{ "structure": { "glossary": "custom.md" } }',
|
||||||
|
'custom.md': '# Glossary\n\n## Hello\n\nHello World'
|
||||||
|
})
|
||||||
|
.then(function(folder) {
|
||||||
|
expect(folder).toHaveFile('custom.html');
|
||||||
|
expect(folder).toNotHaveFile('GLOSSARY.html');
|
||||||
|
|
||||||
|
var html = fs.readFileSync(folder + '/index.html', 'utf8');
|
||||||
|
expect(html).toHaveDOMElement('.page-inner a[href="custom.html#hello"]');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should copy asset files', function() {
|
it('should copy asset files', function() {
|
||||||
return generateMock(WebsiteGenerator, {
|
return generateMock(WebsiteGenerator, {
|
||||||
'README.md': 'Hello World',
|
'README.md': 'Hello World',
|
||||||
|
|||||||
+26
-28
@@ -13,13 +13,12 @@ var generateAssets = require('./generateAssets');
|
|||||||
var generatePages = require('./generatePages');
|
var generatePages = require('./generatePages');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Process an output to generate the book
|
* Process an output to generate the book
|
||||||
|
*
|
||||||
@param {Generator} generator
|
* @param {Generator} generator
|
||||||
@param {Output} output
|
* @param {Output} output
|
||||||
|
* @return {Promise<Output>}
|
||||||
@return {Promise<Output>}
|
*/
|
||||||
*/
|
|
||||||
function processOutput(generator, startOutput) {
|
function processOutput(generator, startOutput) {
|
||||||
return Promise(startOutput)
|
return Promise(startOutput)
|
||||||
.then(preparePlugins)
|
.then(preparePlugins)
|
||||||
@@ -133,27 +132,26 @@ function processOutput(generator, startOutput) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Generate a book using a generator.
|
* Generate a book using a generator.
|
||||||
|
*
|
||||||
The overall process is:
|
* The overall process is:
|
||||||
1. List and load plugins for this book
|
* 1. List and load plugins for this book
|
||||||
2. Call hook "config"
|
* 2. Call hook "config"
|
||||||
3. Call hook "init"
|
* 3. Call hook "init"
|
||||||
4. Initialize generator
|
* 4. Initialize generator
|
||||||
5. List all assets and pages
|
* 5. List all assets and pages
|
||||||
6. Copy all assets to output
|
* 6. Copy all assets to output
|
||||||
7. Generate all pages
|
* 7. Generate all pages
|
||||||
8. Call hook "finish:before"
|
* 8. Call hook "finish:before"
|
||||||
9. Finish generation
|
* 9. Finish generation
|
||||||
10. Call hook "finish"
|
* 10. Call hook "finish"
|
||||||
|
*
|
||||||
|
*
|
||||||
@param {Generator} generator
|
* @param {Generator} generator
|
||||||
@param {Book} book
|
* @param {Book} book
|
||||||
@param {Object} options
|
* @param {Object} options
|
||||||
|
* @return {Promise<Output>}
|
||||||
@return {Promise<Output>}
|
*/
|
||||||
*/
|
|
||||||
function generateBook(generator, book, options) {
|
function generateBook(generator, book, options) {
|
||||||
options = generator.Options(options);
|
options = generator.Options(options);
|
||||||
var state = generator.State? generator.State({}) : Immutable.Map();
|
var state = generator.State? generator.State({}) : Immutable.Map();
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
var tmp = require('tmp');
|
|
||||||
|
|
||||||
var Book = require('../models/book');
|
|
||||||
var createMockFS = require('../fs/mock');
|
|
||||||
var parseBook = require('../parse/parseBook');
|
|
||||||
var generateBook = require('./generateBook');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Generate a book using JSON generator
|
|
||||||
And returns the path to the output dir.
|
|
||||||
|
|
||||||
FOR TESTING PURPOSE ONLY
|
|
||||||
|
|
||||||
@param {Generator}
|
|
||||||
@param {Map<String:String|Map>} files
|
|
||||||
@return {Promise<String>}
|
|
||||||
*/
|
|
||||||
function generateMock(Generator, files) {
|
|
||||||
var fs = createMockFS(files);
|
|
||||||
var book = Book.createForFS(fs);
|
|
||||||
var dir = tmp.dirSync();
|
|
||||||
|
|
||||||
book = book.setLogLevel('disabled');
|
|
||||||
|
|
||||||
return parseBook(book)
|
|
||||||
.then(function(resultBook) {
|
|
||||||
return generateBook(Generator, resultBook, {
|
|
||||||
root: dir.name
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.thenResolve(dir.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = generateMock;
|
|
||||||
@@ -4,19 +4,18 @@ var Promise = require('../utils/promise');
|
|||||||
var error = require('../utils/error');
|
var error = require('../utils/error');
|
||||||
var timing = require('../utils/timing');
|
var timing = require('../utils/timing');
|
||||||
|
|
||||||
var Parse = require('../parse');
|
|
||||||
var Templating = require('../templating');
|
var Templating = require('../templating');
|
||||||
var JSONUtils = require('../json');
|
var JSONUtils = require('../json');
|
||||||
var createTemplateEngine = require('./createTemplateEngine');
|
var createTemplateEngine = require('./createTemplateEngine');
|
||||||
var callPageHook = require('./callPageHook');
|
var callPageHook = require('./callPageHook');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Prepare and generate HTML for a page
|
* Prepare and generate HTML for a page
|
||||||
|
*
|
||||||
@param {Output} output
|
* @param {Output} output
|
||||||
@param {Page} page
|
* @param {Page} page
|
||||||
@return {Promise<Page>}
|
* @return {Promise<Page>}
|
||||||
*/
|
*/
|
||||||
function generatePage(output, page) {
|
function generatePage(output, page) {
|
||||||
var book = output.getBook();
|
var book = output.getBook();
|
||||||
var engine = createTemplateEngine(output);
|
var engine = createTemplateEngine(output);
|
||||||
@@ -28,7 +27,7 @@ function generatePage(output, page) {
|
|||||||
var file = resultPage.getFile();
|
var file = resultPage.getFile();
|
||||||
var filePath = file.getPath();
|
var filePath = file.getPath();
|
||||||
var parser = file.getParser();
|
var parser = file.getParser();
|
||||||
var context = JSONUtils.encodeBookWithPage(book, resultPage);
|
var context = JSONUtils.encodeOutputWithPage(output, resultPage);
|
||||||
|
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
return Promise.reject(error.FileNotParsableError({
|
return Promise.reject(error.FileNotParsableError({
|
||||||
|
|||||||
+17
-12
@@ -4,22 +4,27 @@ var Api = require('../api');
|
|||||||
var Plugins = require('../plugins');
|
var Plugins = require('../plugins');
|
||||||
var Promise = require('../utils/promise');
|
var Promise = require('../utils/promise');
|
||||||
var defaultBlocks = require('../constants/defaultBlocks');
|
var defaultBlocks = require('../constants/defaultBlocks');
|
||||||
|
var fileToOutput = require('./helper/fileToOutput');
|
||||||
|
|
||||||
var CODEBLOCK = 'code';
|
var CODEBLOCK = 'code';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return default modifier to prepare a page for
|
* Return default modifier to prepare a page for
|
||||||
rendering.
|
* rendering.
|
||||||
|
*
|
||||||
@return {Array<Modifier>}
|
* @return {Array<Modifier>}
|
||||||
*/
|
*/
|
||||||
function getModifiers(output, page) {
|
function getModifiers(output, page) {
|
||||||
var book = output.getBook();
|
var book = output.getBook();
|
||||||
var plugins = output.getPlugins();
|
var plugins = output.getPlugins();
|
||||||
var glossary = book.getGlossary();
|
var glossary = book.getGlossary();
|
||||||
var entries = glossary.getEntries();
|
|
||||||
var file = page.getFile();
|
var file = page.getFile();
|
||||||
|
|
||||||
|
// Glossary entries
|
||||||
|
var entries = glossary.getEntries();
|
||||||
|
var glossaryFile = glossary.getFile();
|
||||||
|
var glossaryFilename = fileToOutput(output, glossaryFile.getPath());
|
||||||
|
|
||||||
// Current file path
|
// Current file path
|
||||||
var currentFilePath = file.getPath();
|
var currentFilePath = file.getPath();
|
||||||
|
|
||||||
@@ -34,18 +39,18 @@ function getModifiers(output, page) {
|
|||||||
// Normalize IDs on headings
|
// Normalize IDs on headings
|
||||||
Modifiers.addHeadingId,
|
Modifiers.addHeadingId,
|
||||||
|
|
||||||
|
// Annotate text with glossary entries
|
||||||
|
Modifiers.annotateText.bind(null, entries, glossaryFilename),
|
||||||
|
|
||||||
|
// Resolve images
|
||||||
|
Modifiers.resolveImages.bind(null, currentFilePath),
|
||||||
|
|
||||||
// Resolve links (.md -> .html)
|
// Resolve links (.md -> .html)
|
||||||
Modifiers.resolveLinks.bind(null,
|
Modifiers.resolveLinks.bind(null,
|
||||||
currentFilePath,
|
currentFilePath,
|
||||||
resolveFileToURL.bind(null, output)
|
resolveFileToURL.bind(null, output)
|
||||||
),
|
),
|
||||||
|
|
||||||
// Resolve images
|
|
||||||
Modifiers.resolveImages.bind(null, currentFilePath),
|
|
||||||
|
|
||||||
// Annotate text with glossary entries
|
|
||||||
Modifiers.annotateText.bind(null, entries),
|
|
||||||
|
|
||||||
// Highlight code blocks using "code" block
|
// Highlight code blocks using "code" block
|
||||||
Modifiers.highlightCode.bind(null, function(lang, source) {
|
Modifiers.highlightCode.bind(null, function(lang, source) {
|
||||||
return Promise(code.applyBlock({
|
return Promise(code.applyBlock({
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ var LocationUtils = require('../../utils/location');
|
|||||||
var OUTPUT_EXTENSION = '.html';
|
var OUTPUT_EXTENSION = '.html';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convert a filePath (absolute) to a filename for output
|
* Convert a filePath (absolute) to a filename for output
|
||||||
|
*
|
||||||
@param {Output} output
|
* @param {Output} output
|
||||||
@param {String} filePath
|
* @param {String} filePath
|
||||||
@return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
function fileToOutput(output, filePath) {
|
function fileToOutput(output, filePath) {
|
||||||
var book = output.getBook();
|
var book = output.getBook();
|
||||||
var readme = book.getReadme();
|
var readme = book.getReadme();
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ var LocationUtils = require('../../utils/location');
|
|||||||
var fileToURL = require('./fileToURL');
|
var fileToURL = require('./fileToURL');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Resolve an absolute path (extracted from a link)
|
* Resolve an absolute path (extracted from a link)
|
||||||
|
*
|
||||||
@param {Output} output
|
* @param {Output} output
|
||||||
@param {String} filePath
|
* @param {String} filePath
|
||||||
@return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
function resolveFileToURL(output, filePath) {
|
function resolveFileToURL(output, filePath) {
|
||||||
// Convert /test.png -> test.png
|
// Convert /test.png -> test.png
|
||||||
filePath = LocationUtils.toAbsolute(filePath, '', '');
|
filePath = LocationUtils.toAbsolute(filePath, '', '');
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ var getModifiers = require('../getModifiers');
|
|||||||
var JSON_VERSION = '3';
|
var JSON_VERSION = '3';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Write a page as a json file
|
* Write a page as a json file
|
||||||
|
*
|
||||||
@param {Output} output
|
* @param {Output} output
|
||||||
@param {Page} page
|
* @param {Page} page
|
||||||
*/
|
*/
|
||||||
function onPage(output, page) {
|
function onPage(output, page) {
|
||||||
var file = page.getFile();
|
var file = page.getFile();
|
||||||
var readme = output.getBook().getReadme().getFile();
|
var readme = output.getBook().getReadme().getFile();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe('annotateText', function() {
|
|||||||
it('should annotate text', function() {
|
it('should annotate text', function() {
|
||||||
var $ = cheerio.load('<p>This is a word, and multiple words</p>');
|
var $ = cheerio.load('<p>This is a word, and multiple words</p>');
|
||||||
|
|
||||||
annotateText(entries, $);
|
annotateText(entries, 'GLOSSARY.md', $);
|
||||||
|
|
||||||
var links = $('a');
|
var links = $('a');
|
||||||
expect(links.length).toBe(2);
|
expect(links.length).toBe(2);
|
||||||
@@ -31,14 +31,14 @@ describe('annotateText', function() {
|
|||||||
it('should not annotate scripts', function() {
|
it('should not annotate scripts', function() {
|
||||||
var $ = cheerio.load('<script>This is a word, and multiple words</script>');
|
var $ = cheerio.load('<script>This is a word, and multiple words</script>');
|
||||||
|
|
||||||
annotateText(entries, $);
|
annotateText(entries, 'GLOSSARY.md', $);
|
||||||
expect($('a').length).toBe(0);
|
expect($('a').length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not annotate when has class "no-glossary"', function() {
|
it('should not annotate when has class "no-glossary"', function() {
|
||||||
var $ = cheerio.load('<p class="no-glossary">This is a word, and multiple words</p>');
|
var $ = cheerio.load('<p class="no-glossary">This is a word, and multiple words</p>');
|
||||||
|
|
||||||
annotateText(entries, $);
|
annotateText(entries, 'GLOSSARY.md', $);
|
||||||
expect($('a').length).toBe(0);
|
expect($('a').length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ var resolveLinks = require('../resolveLinks');
|
|||||||
|
|
||||||
describe('resolveLinks', function() {
|
describe('resolveLinks', function() {
|
||||||
function resolveFileBasic(href) {
|
function resolveFileBasic(href) {
|
||||||
return href;
|
return 'fakeDir/' + href;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveFileCustom(href) {
|
function resolveFileCustom(href) {
|
||||||
@@ -24,7 +24,7 @@ describe('resolveLinks', function() {
|
|||||||
return resolveLinks('hello.md', resolveFileBasic, $)
|
return resolveLinks('hello.md', resolveFileBasic, $)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
var link = $('a');
|
var link = $('a');
|
||||||
expect(link.attr('href')).toBe('test/cool.md');
|
expect(link.attr('href')).toBe('fakeDir/test/cool.md');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,7 +34,31 @@ describe('resolveLinks', function() {
|
|||||||
return resolveLinks('afolder/hello.md', resolveFileBasic, $)
|
return resolveLinks('afolder/hello.md', resolveFileBasic, $)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
var link = $('a');
|
var link = $('a');
|
||||||
expect(link.attr('href')).toBe('../test/cool.md');
|
expect(link.attr('href')).toBe('../fakeDir/test/cool.md');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Anchor', function() {
|
||||||
|
it('should prevent anchors in resolution', function() {
|
||||||
|
var TEST = '<p>This is a <a href="test/cool.md#an-anchor"></a></p>';
|
||||||
|
var $ = cheerio.load(TEST);
|
||||||
|
|
||||||
|
return resolveLinks('hello.md', resolveFileCustom, $)
|
||||||
|
.then(function() {
|
||||||
|
var link = $('a');
|
||||||
|
expect(link.attr('href')).toBe('test/cool.html#an-anchor');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore pure anchor links', function() {
|
||||||
|
var TEST = '<p>This is a <a href="#an-anchor"></a></p>';
|
||||||
|
var $ = cheerio.load(TEST);
|
||||||
|
|
||||||
|
return resolveLinks('hello.md', resolveFileCustom, $)
|
||||||
|
.then(function() {
|
||||||
|
var link = $('a');
|
||||||
|
expect(link.attr('href')).toBe('#an-anchor');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -63,6 +87,18 @@ describe('resolveLinks', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('External link', function() {
|
||||||
|
var TEST = '<p>This is a <a href="http://www.github.com">external link</a></p>';
|
||||||
|
|
||||||
|
it('should have target="_blank" attribute', function() {
|
||||||
|
var $ = cheerio.load(TEST);
|
||||||
|
|
||||||
|
return resolveLinks('hello.md', resolveFileBasic, $)
|
||||||
|
.then(function() {
|
||||||
|
var link = $('a');
|
||||||
|
expect(link.attr('target')).toBe('_blank');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,18 +59,18 @@ function replaceText($, el, search, replace, text_only ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Annotate text using a list of GlossaryEntry
|
* Annotate text using a list of GlossaryEntry
|
||||||
|
*
|
||||||
@param {List<GlossaryEntry>}
|
* @param {List<GlossaryEntry>}
|
||||||
@param {HTMLDom} $
|
* @param {String} glossaryFilePath
|
||||||
*/
|
* @param {HTMLDom} $
|
||||||
function annotateText(entries, $) {
|
*/
|
||||||
|
function annotateText(entries, glossaryFilePath, $) {
|
||||||
entries.forEach(function(entry) {
|
entries.forEach(function(entry) {
|
||||||
var entryId = entry.getID();
|
var entryId = entry.getID();
|
||||||
var name = entry.getName();
|
var name = entry.getName();
|
||||||
var description = entry.getDescription();
|
var description = entry.getDescription();
|
||||||
|
var searchRegex = new RegExp( '\\b(' + pregQuote(name.toLowerCase()) + ')\\b' , 'gi' );
|
||||||
var searchRegex = new RegExp( '\\b(' + pregQuote(name.toLowerCase()) + ')\\b' , 'gi' );
|
|
||||||
|
|
||||||
$('*').each(function() {
|
$('*').each(function() {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
@@ -81,7 +81,7 @@ function annotateText(entries, $) {
|
|||||||
) return;
|
) return;
|
||||||
|
|
||||||
replaceText($, this, searchRegex, function(match) {
|
replaceText($, this, searchRegex, function(match) {
|
||||||
return '<a href="/GLOSSARY.md#' + entryId + '" '
|
return '<a href="/' + glossaryFilePath + '#' + entryId + '" '
|
||||||
+ 'class="glossary-term" title="' + escape(description) + '">'
|
+ 'class="glossary-term" title="' + escape(description) + '">'
|
||||||
+ match
|
+ match
|
||||||
+ '</a>';
|
+ '</a>';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
var url = require('url');
|
||||||
|
|
||||||
var LocationUtils = require('../../utils/location');
|
var LocationUtils = require('../../utils/location');
|
||||||
var editHTMLElement = require('./editHTMLElement');
|
var editHTMLElement = require('./editHTMLElement');
|
||||||
@@ -23,18 +24,27 @@ function resolveLinks(currentFile, resolveFile, $) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (LocationUtils.isExternal(href)) {
|
if (LocationUtils.isExternal(href)) {
|
||||||
$a.attr('_target', 'blank');
|
$a.attr('target', '_blank');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcul absolute path for this
|
// Split anchor
|
||||||
href = LocationUtils.toAbsolute(href, currentDirectory, '.');
|
var parsed = url.parse(href);
|
||||||
|
href = parsed.pathname || '';
|
||||||
|
|
||||||
// Resolve file
|
if (href) {
|
||||||
href = resolveFile(href);
|
// Calcul absolute path for this
|
||||||
|
href = LocationUtils.toAbsolute(href, currentDirectory, '.');
|
||||||
|
|
||||||
// Convert back to relative
|
// Resolve file
|
||||||
href = LocationUtils.relative(currentDirectory, href);
|
href = resolveFile(href);
|
||||||
|
|
||||||
|
// Convert back to relative
|
||||||
|
href = LocationUtils.relative(currentDirectory, href);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add back anchor
|
||||||
|
href = href + (parsed.hash || '');
|
||||||
|
|
||||||
$a.attr('href', href);
|
$a.attr('href', href);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ var Plugins = require('../plugins');
|
|||||||
var Promise = require('../utils/promise');
|
var Promise = require('../utils/promise');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Load and setup plugins
|
* Load and setup plugins
|
||||||
|
*
|
||||||
@param {Output}
|
* @param {Output}
|
||||||
@return {Promise<Output>}
|
* @return {Promise<Output>}
|
||||||
*/
|
*/
|
||||||
function preparePlugins(output) {
|
function preparePlugins(output) {
|
||||||
var book = output.getBook();
|
var book = output.getBook();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
var createMockOutput = require('../../__tests__/createMock');
|
||||||
|
var prepareI18n = require('../prepareI18n');
|
||||||
|
var createTemplateEngine = require('../createTemplateEngine');
|
||||||
|
|
||||||
|
var WebsiteGenerator = require('../');
|
||||||
|
|
||||||
|
describe('i18n', function() {
|
||||||
|
it('should correctly use english as default language', function() {
|
||||||
|
return createMockOutput(WebsiteGenerator, {
|
||||||
|
'README.md': 'Hello World'
|
||||||
|
})
|
||||||
|
.then(function(output) {
|
||||||
|
return prepareI18n(output);
|
||||||
|
})
|
||||||
|
.then(function(output) {
|
||||||
|
var engine = createTemplateEngine(output, 'README.md');
|
||||||
|
var t = engine.getFilters().get('t');
|
||||||
|
|
||||||
|
expect(t('SUMMARY_INTRODUCTION')).toEqual('Introduction');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly use language from book.json', function() {
|
||||||
|
return createMockOutput(WebsiteGenerator, {
|
||||||
|
'README.md': 'Hello World',
|
||||||
|
'book.json': JSON.stringify({ language: 'fr' })
|
||||||
|
})
|
||||||
|
.then(function(output) {
|
||||||
|
return prepareI18n(output);
|
||||||
|
})
|
||||||
|
.then(function(output) {
|
||||||
|
var engine = createTemplateEngine(output, 'README.md');
|
||||||
|
var t = engine.getFilters().get('t');
|
||||||
|
|
||||||
|
expect(t('GITBOOK_LINK')).toEqual('Publié avec GitBook');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -18,19 +18,19 @@ var fileToURL = require('../helper/fileToURL');
|
|||||||
var resolveFileToURL = require('../helper/resolveFileToURL');
|
var resolveFileToURL = require('../helper/resolveFileToURL');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Directory for a theme with the templates
|
* Directory for a theme with the templates
|
||||||
*/
|
*/
|
||||||
function templateFolder(dir) {
|
function templateFolder(dir) {
|
||||||
return path.join(dir, templatesFolder);
|
return path.join(dir, templatesFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create templating engine to render themes
|
* Create templating engine to render themes
|
||||||
|
*
|
||||||
@param {Output} output
|
* @param {Output} output
|
||||||
@param {String} currentFile
|
* @param {String} currentFile
|
||||||
@return {TemplateEngine}
|
* @return {TemplateEngine}
|
||||||
*/
|
*/
|
||||||
function createTemplateEngine(output, currentFile) {
|
function createTemplateEngine(output, currentFile) {
|
||||||
var book = output.getBook();
|
var book = output.getBook();
|
||||||
var state = output.getState();
|
var state = output.getState();
|
||||||
@@ -47,18 +47,17 @@ function createTemplateEngine(output, currentFile) {
|
|||||||
var loader = new Templating.ThemesLoader(tplSearchPaths);
|
var loader = new Templating.ThemesLoader(tplSearchPaths);
|
||||||
|
|
||||||
// Get languages
|
// Get languages
|
||||||
var language = config.get('language');
|
var language = config.getValue('language');
|
||||||
|
|
||||||
// Create API context
|
// Create API context
|
||||||
var context = Api.encodeGlobal(output);
|
var context = Api.encodeGlobal(output);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Check if a file exists
|
* Check if a file exists
|
||||||
|
* @param {String} fileName
|
||||||
@param {String} fileName
|
* @return {Boolean}
|
||||||
@return {Boolean}
|
*/
|
||||||
*/
|
|
||||||
function fileExists(fileName) {
|
function fileExists(fileName) {
|
||||||
if (!fileName) {
|
if (!fileName) {
|
||||||
return false;
|
return false;
|
||||||
@@ -69,11 +68,10 @@ function createTemplateEngine(output, currentFile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return an article by its path
|
* Return an article by its path
|
||||||
|
* @param {String} filePath
|
||||||
@param {String} filePath
|
* @return {Object|undefined}
|
||||||
@return {Object|undefined}
|
*/
|
||||||
*/
|
|
||||||
function getArticleByPath(filePath) {
|
function getArticleByPath(filePath) {
|
||||||
var article = summary.getByPath(filePath);
|
var article = summary.getByPath(filePath);
|
||||||
if (!article) return undefined;
|
if (!article) return undefined;
|
||||||
@@ -82,11 +80,10 @@ function createTemplateEngine(output, currentFile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a page by its path
|
* Return a page by its path
|
||||||
|
* @param {String} filePath
|
||||||
@param {String} filePath
|
* @return {Object|undefined}
|
||||||
@return {Object|undefined}
|
*/
|
||||||
*/
|
|
||||||
function getPageByPath(filePath) {
|
function getPageByPath(filePath) {
|
||||||
var page = output.getPage(filePath);
|
var page = output.getPage(filePath);
|
||||||
if (!page) return undefined;
|
if (!page) return undefined;
|
||||||
@@ -94,7 +91,6 @@ function createTemplateEngine(output, currentFile) {
|
|||||||
return JSONUtils.encodePage(page, summary);
|
return JSONUtils.encodePage(page, summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return TemplateEngine.create({
|
return TemplateEngine.create({
|
||||||
loader: loader,
|
loader: loader,
|
||||||
|
|
||||||
@@ -108,17 +104,17 @@ function createTemplateEngine(output, currentFile) {
|
|||||||
|
|
||||||
filters: defaultFilters.merge({
|
filters: defaultFilters.merge({
|
||||||
/**
|
/**
|
||||||
Translate a sentence
|
* Translate a sentence
|
||||||
*/
|
*/
|
||||||
t: function t(s) {
|
t: function t(s) {
|
||||||
return i18n.t(language, s);
|
return i18n.t(language, s);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Resolve an absolute file path into a
|
* Resolve an absolute file path into a
|
||||||
relative path.
|
* relative path.
|
||||||
it also resolve pages
|
* it also resolve pages
|
||||||
*/
|
*/
|
||||||
resolveFile: function(filePath) {
|
resolveFile: function(filePath) {
|
||||||
filePath = resolveFileToURL(output, filePath);
|
filePath = resolveFileToURL(output, filePath);
|
||||||
return LocationUtils.relativeForFile(currentFile, filePath);
|
return LocationUtils.relativeForFile(currentFile, filePath);
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ var createTemplateEngine = require('./createTemplateEngine');
|
|||||||
var fileToOutput = require('../helper/fileToOutput');
|
var fileToOutput = require('../helper/fileToOutput');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Write a page as a json file
|
* Write a page as a json file
|
||||||
|
*
|
||||||
@param {Output} output
|
* @param {Output} output
|
||||||
@param {Page} page
|
* @param {Page} page
|
||||||
*/
|
*/
|
||||||
function onPage(output, page) {
|
function onPage(output, page) {
|
||||||
var options = output.getOptions();
|
var options = output.getOptions();
|
||||||
var prefix = options.get('prefix');
|
var prefix = options.get('prefix');
|
||||||
@@ -40,7 +40,7 @@ function onPage(output, page) {
|
|||||||
return Modifiers.modifyHTML(page, getModifiers(output, page))
|
return Modifiers.modifyHTML(page, getModifiers(output, page))
|
||||||
.then(function(resultPage) {
|
.then(function(resultPage) {
|
||||||
// Generate the context
|
// Generate the context
|
||||||
var context = JSONUtils.encodeBookWithPage(output.getBook(), resultPage);
|
var context = JSONUtils.encodeOutputWithPage(output, resultPage);
|
||||||
context.plugins = {
|
context.plugins = {
|
||||||
resources: Plugins.listResources(plugins, resources).toJS()
|
resources: Plugins.listResources(plugins, resources).toJS()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ var Promise = require('../../utils/promise');
|
|||||||
var listSearchPaths = require('./listSearchPaths');
|
var listSearchPaths = require('./listSearchPaths');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Prepare i18n, load translations from plugins and book
|
* Prepare i18n, load translations from plugins and book
|
||||||
|
*
|
||||||
@param {Output}
|
* @param {Output}
|
||||||
@return {Promise<Output>}
|
* @return {Promise<Output>}
|
||||||
*/
|
*/
|
||||||
function prepareI18n(output) {
|
function prepareI18n(output) {
|
||||||
var state = output.getState();
|
var state = output.getState();
|
||||||
var i18n = state.getI18n();
|
var i18n = state.getI18n();
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
var parsePageFromString = require('../parsePageFromString');
|
||||||
|
var Page = require('../../models/page');
|
||||||
|
|
||||||
|
describe('parsePageFromString', function() {
|
||||||
|
var page = new Page();
|
||||||
|
|
||||||
|
it('should parse YAML frontmatter', function() {
|
||||||
|
var CONTENT = '---\nhello: true\nworld: "cool"\n---\n# Hello World\n';
|
||||||
|
var newPage = parsePageFromString(page, CONTENT);
|
||||||
|
|
||||||
|
expect(newPage.getDir()).toBe('ltr');
|
||||||
|
expect(newPage.getContent()).toBe('# Hello World\n');
|
||||||
|
|
||||||
|
var attrs = newPage.getAttributes();
|
||||||
|
expect(attrs.size).toBe(2);
|
||||||
|
expect(attrs.get('hello')).toBe(true);
|
||||||
|
expect(attrs.get('world')).toBe('cool');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse text direction (english)', function() {
|
||||||
|
var CONTENT = 'Hello World';
|
||||||
|
var newPage = parsePageFromString(page, CONTENT);
|
||||||
|
|
||||||
|
expect(newPage.getDir()).toBe('ltr');
|
||||||
|
expect(newPage.getContent()).toBe('Hello World');
|
||||||
|
expect(newPage.getAttributes().size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse text direction (arab)', function() {
|
||||||
|
var CONTENT = 'مرحبا بالعالم';
|
||||||
|
var newPage = parsePageFromString(page, CONTENT);
|
||||||
|
|
||||||
|
expect(newPage.getDir()).toBe('rtl');
|
||||||
|
expect(newPage.getContent()).toBe('مرحبا بالعالم');
|
||||||
|
expect(newPage.getAttributes().size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
+12
-11
@@ -1,14 +1,15 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parseBook: require('./parseBook'),
|
parseBook: require('./parseBook'),
|
||||||
parseSummary: require('./parseSummary'),
|
parseSummary: require('./parseSummary'),
|
||||||
parseGlossary: require('./parseGlossary'),
|
parseGlossary: require('./parseGlossary'),
|
||||||
parseReadme: require('./parseReadme'),
|
parseReadme: require('./parseReadme'),
|
||||||
parseConfig: require('./parseConfig'),
|
parseConfig: require('./parseConfig'),
|
||||||
parsePagesList: require('./parsePagesList'),
|
parsePagesList: require('./parsePagesList'),
|
||||||
parseIgnore: require('./parseIgnore'),
|
parseIgnore: require('./parseIgnore'),
|
||||||
listAssets: require('./listAssets'),
|
listAssets: require('./listAssets'),
|
||||||
parseLanguages: require('./parseLanguages'),
|
parseLanguages: require('./parseLanguages'),
|
||||||
parsePage: require('./parsePage'),
|
parsePage: require('./parsePage'),
|
||||||
lookupStructureFile: require('./lookupStructureFile')
|
parsePageFromString: require('./parsePageFromString'),
|
||||||
|
lookupStructureFile: require('./lookupStructureFile')
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ var findParsableFile = require('./findParsableFile');
|
|||||||
|
|
||||||
@param {Book} book
|
@param {Book} book
|
||||||
@param {String} type: one of ["glossary", "readme", "summary", "langs"]
|
@param {String} type: one of ["glossary", "readme", "summary", "langs"]
|
||||||
@return {Promise<File>} The path of the file found, relative
|
@return {Promise<File | Undefined>} The path of the file found, relative
|
||||||
to the book content root.
|
to the book content root.
|
||||||
*/
|
*/
|
||||||
function lookupStructureFile(book, type) {
|
function lookupStructureFile(book, type) {
|
||||||
|
|||||||
+8
-16
@@ -1,27 +1,19 @@
|
|||||||
var Immutable = require('immutable');
|
var parsePageFromString = require('./parsePageFromString');
|
||||||
var fm = require('front-matter');
|
|
||||||
var direction = require('direction');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Parse a page, read its content and parse the YAMl header
|
* Parse a page, read its content and parse the YAMl header
|
||||||
|
*
|
||||||
@param {Book} book
|
* @param {Book} book
|
||||||
@param {Page} page
|
* @param {Page} page
|
||||||
@return {Promise<Page>}
|
* @return {Promise<Page>}
|
||||||
*/
|
*/
|
||||||
function parsePage(book, page) {
|
function parsePage(book, page) {
|
||||||
var fs = book.getContentFS();
|
var fs = book.getContentFS();
|
||||||
var file = page.getFile();
|
var file = page.getFile();
|
||||||
|
|
||||||
return fs.readAsString(file.getPath())
|
return fs.readAsString(file.getPath())
|
||||||
.then(function(content) {
|
.then(function(content) {
|
||||||
var parsed = fm(content);
|
return parsePageFromString(page, content);
|
||||||
|
|
||||||
return page.merge({
|
|
||||||
content: parsed.body,
|
|
||||||
attributes: Immutable.fromJS(parsed.attributes),
|
|
||||||
dir: direction(parsed.body)
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
var Immutable = require('immutable');
|
||||||
|
var fm = require('front-matter');
|
||||||
|
var direction = require('direction');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a page, its content and the YAMl header
|
||||||
|
*
|
||||||
|
* @param {Page} page
|
||||||
|
* @return {Page}
|
||||||
|
*/
|
||||||
|
function parsePageFromString(page, content) {
|
||||||
|
var parsed = fm(content);
|
||||||
|
|
||||||
|
return page.merge({
|
||||||
|
content: parsed.body,
|
||||||
|
attributes: Immutable.fromJS(parsed.attributes),
|
||||||
|
dir: direction(parsed.body)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = parsePageFromString;
|
||||||
+41
-10
@@ -5,6 +5,25 @@ var Page = require('../models/page');
|
|||||||
var walkSummary = require('./walkSummary');
|
var walkSummary = require('./walkSummary');
|
||||||
var parsePage = require('./parsePage');
|
var parsePage = require('./parsePage');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Parse a page from a path
|
||||||
|
|
||||||
|
@param {Book} book
|
||||||
|
@param {String} filePath
|
||||||
|
@return {Page}
|
||||||
|
*/
|
||||||
|
function parseFilePage(book, filePath) {
|
||||||
|
var fs = book.getContentFS();
|
||||||
|
|
||||||
|
return fs.statFile(filePath)
|
||||||
|
.then(function(file) {
|
||||||
|
var page = Page.createForFile(file);
|
||||||
|
return parsePage(book, page);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Parse all pages from a book as an OrderedMap
|
Parse all pages from a book as an OrderedMap
|
||||||
|
|
||||||
@@ -12,10 +31,11 @@ var parsePage = require('./parsePage');
|
|||||||
@return {Promise<OrderedMap<Page>>}
|
@return {Promise<OrderedMap<Page>>}
|
||||||
*/
|
*/
|
||||||
function parsePagesList(book) {
|
function parsePagesList(book) {
|
||||||
var fs = book.getContentFS();
|
|
||||||
var summary = book.getSummary();
|
var summary = book.getSummary();
|
||||||
|
var glossary = book.getGlossary();
|
||||||
var map = Immutable.OrderedMap();
|
var map = Immutable.OrderedMap();
|
||||||
|
|
||||||
|
// Parse pages from summary
|
||||||
return timing.measure(
|
return timing.measure(
|
||||||
'parse.listPages',
|
'parse.listPages',
|
||||||
walkSummary(summary, function(article) {
|
walkSummary(summary, function(article) {
|
||||||
@@ -26,21 +46,32 @@ function parsePagesList(book) {
|
|||||||
// Is the page ignored?
|
// Is the page ignored?
|
||||||
if (book.isContentFileIgnored(filepath)) return;
|
if (book.isContentFileIgnored(filepath)) return;
|
||||||
|
|
||||||
return fs.statFile(filepath)
|
return parseFilePage(book, filepath)
|
||||||
.then(function(file) {
|
|
||||||
var page = Page.createForFile(file);
|
|
||||||
return parsePage(book, page);
|
|
||||||
})
|
|
||||||
.then(function(page) {
|
.then(function(page) {
|
||||||
map = map.set(filepath, page);
|
map = map.set(filepath, page);
|
||||||
}, function() {
|
}, function() {
|
||||||
// file doesn't exist
|
// file doesn't exist
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(function() {
|
)
|
||||||
return map;
|
|
||||||
})
|
// Parse glossary
|
||||||
);
|
.then(function() {
|
||||||
|
var file = glossary.getFile();
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseFilePage(book, file.getPath())
|
||||||
|
.then(function(page) {
|
||||||
|
map = map.set(file.getPath(), page);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
.then(function() {
|
||||||
|
return map;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
var parseStructureFile = require('./parseStructureFile');
|
var parseStructureFile = require('./parseStructureFile');
|
||||||
var Summary = require('../models/summary');
|
var Summary = require('../models/summary');
|
||||||
var SummaryModifier = require('../modifiers').Summary;
|
var SummaryModifier = require('../modifiers').Summary;
|
||||||
var location = require('../utils/location');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Parse summary in a book, the summary can only be parsed
|
Parse summary in a book, the summary can only be parsed
|
||||||
@@ -27,11 +26,10 @@ function parseSummary(book) {
|
|||||||
summary = Summary.createFromParts(file, result.parts);
|
summary = Summary.createFromParts(file, result.parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert readme as first entry
|
// Insert readme as first entry if not in SUMMARY.md
|
||||||
var firstArticle = summary.getFirstArticle();
|
var readmeArticle = summary.getByPath(readmeFile.getPath());
|
||||||
|
|
||||||
if (readmeFile.exists() &&
|
if (readmeFile.exists() && !readmeArticle) {
|
||||||
(!firstArticle || !location.areIdenticalPaths(firstArticle.getRef(), readmeFile.getPath()))) {
|
|
||||||
summary = SummaryModifier.unshiftArticle(summary, {
|
summary = SummaryModifier.unshiftArticle(summary, {
|
||||||
title: 'Introduction',
|
title: 'Introduction',
|
||||||
ref: readmeFile.getPath()
|
ref: readmeFile.getPath()
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
var jsonschema = require('jsonschema');
|
var jsonschema = require('jsonschema');
|
||||||
var jsonSchemaDefaults = require('json-schema-defaults');
|
var jsonSchemaDefaults = require('json-schema-defaults');
|
||||||
var mergeDefaults = require('merge-defaults');
|
|
||||||
|
|
||||||
var schema = require('../constants/configSchema');
|
var schema = require('../constants/configSchema');
|
||||||
var error = require('../utils/error');
|
var error = require('../utils/error');
|
||||||
|
var mergeDefaults = require('../utils/mergeDefaults');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Validate a book.json content
|
Validate a book.json content
|
||||||
|
|||||||
+19
-17
@@ -4,20 +4,22 @@ var Immutable = require('immutable');
|
|||||||
var markdownParser = require('gitbook-markdown');
|
var markdownParser = require('gitbook-markdown');
|
||||||
var asciidocParser = require('gitbook-asciidoc');
|
var asciidocParser = require('gitbook-asciidoc');
|
||||||
|
|
||||||
|
var EXTENSIONS_MARKDOWN = require('./constants/extsMarkdown');
|
||||||
|
var EXTENSIONS_ASCIIDOC = require('./constants/extsAsciidoc');
|
||||||
var Parser = require('./models/parser');
|
var Parser = require('./models/parser');
|
||||||
|
|
||||||
// This list is ordered by priority of parsers to use
|
// This list is ordered by priority of parsers to use
|
||||||
var parsers = Immutable.List([
|
var parsers = Immutable.List([
|
||||||
Parser.create('markdown', ['.md', '.markdown', '.mdown'], markdownParser),
|
Parser.create('markdown', EXTENSIONS_MARKDOWN, markdownParser),
|
||||||
Parser.create('asciidoc', ['.adoc', '.asciidoc'], asciidocParser)
|
Parser.create('asciidoc', EXTENSIONS_ASCIIDOC, asciidocParser)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a specific parser by its name
|
* Return a specific parser by its name
|
||||||
|
*
|
||||||
@param {String} name
|
* @param {String} name
|
||||||
@return {Parser|undefined}
|
* @return {Parser|undefined}
|
||||||
*/
|
*/
|
||||||
function getParser(name) {
|
function getParser(name) {
|
||||||
return parsers.find(function(parser) {
|
return parsers.find(function(parser) {
|
||||||
return parser.getName() === name;
|
return parser.getName() === name;
|
||||||
@@ -25,11 +27,11 @@ function getParser(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return a specific parser according to an extension
|
* Return a specific parser according to an extension
|
||||||
|
*
|
||||||
@param {String} ext
|
* @param {String} ext
|
||||||
@return {Parser|undefined}
|
* @return {Parser|undefined}
|
||||||
*/
|
*/
|
||||||
function getParserByExt(ext) {
|
function getParserByExt(ext) {
|
||||||
return parsers.find(function(parser) {
|
return parsers.find(function(parser) {
|
||||||
return parser.matchExtension(ext);
|
return parser.matchExtension(ext);
|
||||||
@@ -37,11 +39,11 @@ function getParserByExt(ext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return parser for a file
|
* Return parser for a file
|
||||||
|
*
|
||||||
@param {String} ext
|
* @param {String} ext
|
||||||
@return {Parser|undefined}
|
* @return {Parser|undefined}
|
||||||
*/
|
*/
|
||||||
function getParserForFile(filename) {
|
function getParserForFile(filename) {
|
||||||
return getParserByExt(path.extname(filename));
|
return getParserByExt(path.extname(filename));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var Book = require('../../models/book');
|
||||||
|
var createNodeFS = require('../../fs/node');
|
||||||
|
var findForBook = require('../findForBook');
|
||||||
|
|
||||||
|
describe('findForBook', function() {
|
||||||
|
var fs = createNodeFS(
|
||||||
|
path.resolve(__dirname, '../../..')
|
||||||
|
);
|
||||||
|
var book = Book.createForFS(fs);
|
||||||
|
|
||||||
|
it('should list default plugins', function() {
|
||||||
|
return findForBook(book)
|
||||||
|
.then(function(plugins) {
|
||||||
|
expect(plugins.has('fontsettings')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var PluginDependency = require('../../models/pluginDependency');
|
||||||
|
var Book = require('../../models/book');
|
||||||
|
var NodeFS = require('../../fs/node');
|
||||||
|
var installPlugin = require('../installPlugin');
|
||||||
|
|
||||||
|
var Parse = require('../../parse');
|
||||||
|
|
||||||
|
describe('installPlugin', function() {
|
||||||
|
var book;
|
||||||
|
|
||||||
|
this.timeout(30000);
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
var fs = NodeFS(path.resolve(__dirname, '../../../'));
|
||||||
|
var baseBook = Book.createForFS(fs);
|
||||||
|
|
||||||
|
return Parse.parseConfig(baseBook)
|
||||||
|
.then(function(_book) {
|
||||||
|
book = _book;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must install a plugin from NPM', function() {
|
||||||
|
var dep = PluginDependency.createFromString('ga');
|
||||||
|
return installPlugin(book, dep);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var Book = require('../../models/book');
|
||||||
|
var NodeFS = require('../../fs/node');
|
||||||
|
var installPlugins = require('../installPlugins');
|
||||||
|
|
||||||
|
var Parse = require('../../parse');
|
||||||
|
|
||||||
|
describe('installPlugins', function() {
|
||||||
|
var book;
|
||||||
|
|
||||||
|
this.timeout(30000);
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
var fs = NodeFS(path.resolve(__dirname, '../../../'));
|
||||||
|
var baseBook = Book.createForFS(fs);
|
||||||
|
|
||||||
|
return Parse.parseConfig(baseBook)
|
||||||
|
.then(function(_book) {
|
||||||
|
book = _book;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must install all plugins from NPM', function() {
|
||||||
|
return installPlugins(book)
|
||||||
|
.then(function(n) {
|
||||||
|
expect(n).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
var PluginDependency = require('../../models/pluginDependency');
|
|
||||||
var listAll = require('../listAll');
|
|
||||||
|
|
||||||
describe('listAll', function() {
|
|
||||||
it('must list default', function() {
|
|
||||||
var deps = PluginDependency.listFromString('ga,great');
|
|
||||||
var plugins = listAll(deps);
|
|
||||||
|
|
||||||
expect(plugins.size).toBe(8);
|
|
||||||
|
|
||||||
expect(plugins.has('ga')).toBe(true);
|
|
||||||
expect(plugins.has('great')).toBe(true);
|
|
||||||
|
|
||||||
expect(plugins.has('search')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must list from array with -', function() {
|
|
||||||
var deps = PluginDependency.listFromString('ga,-great');
|
|
||||||
var plugins = listAll(deps);
|
|
||||||
|
|
||||||
expect(plugins.size).toBe(7);
|
|
||||||
|
|
||||||
expect(plugins.has('ga')).toBe(true);
|
|
||||||
expect(plugins.has('great')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('must remove default plugins using -', function() {
|
|
||||||
var deps = PluginDependency.listFromString('ga,-search');
|
|
||||||
var plugins = listAll(deps);
|
|
||||||
|
|
||||||
expect(plugins.size).toBe(6);
|
|
||||||
|
|
||||||
expect(plugins.has('ga')).toBe(true);
|
|
||||||
expect(plugins.has('search')).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
var PluginDependency = require('../../models/pluginDependency');
|
||||||
|
var listDependencies = require('../listDependencies');
|
||||||
|
var toNames = require('../toNames');
|
||||||
|
|
||||||
|
describe('listDependencies', function() {
|
||||||
|
it('must list default', function() {
|
||||||
|
var deps = PluginDependency.listFromString('ga,great');
|
||||||
|
var plugins = listDependencies(deps);
|
||||||
|
var names = toNames(plugins);
|
||||||
|
|
||||||
|
expect(names).toEqual([
|
||||||
|
'ga', 'great',
|
||||||
|
'highlight', 'search', 'lunr', 'sharing', 'fontsettings',
|
||||||
|
'theme-default' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must list from array with -', function() {
|
||||||
|
var deps = PluginDependency.listFromString('ga,-great');
|
||||||
|
var plugins = listDependencies(deps);
|
||||||
|
var names = toNames(plugins);
|
||||||
|
|
||||||
|
expect(names).toEqual([
|
||||||
|
'ga',
|
||||||
|
'highlight', 'search', 'lunr', 'sharing', 'fontsettings',
|
||||||
|
'theme-default' ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must remove default plugins using -', function() {
|
||||||
|
var deps = PluginDependency.listFromString('ga,-search');
|
||||||
|
var plugins = listDependencies(deps);
|
||||||
|
var names = toNames(plugins);
|
||||||
|
|
||||||
|
expect(names).toEqual([
|
||||||
|
'ga',
|
||||||
|
'highlight', 'lunr', 'sharing', 'fontsettings',
|
||||||
|
'theme-default' ]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
var path = require('path');
|
||||||
|
var locateRootFolder = require('../locateRootFolder');
|
||||||
|
|
||||||
|
describe('locateRootFolder', function() {
|
||||||
|
it('should correctly resolve the node_modules for gitbook', function() {
|
||||||
|
expect(locateRootFolder()).toBe(
|
||||||
|
path.resolve(__dirname, '../../../')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
var PluginDependency = require('../../models/pluginDependency');
|
||||||
|
var resolveVersion = require('../resolveVersion');
|
||||||
|
|
||||||
|
describe('resolveVersion', function() {
|
||||||
|
it('must skip resolving and return non-semver versions', function() {
|
||||||
|
var plugin = PluginDependency.createFromString('ga@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||||
|
|
||||||
|
return resolveVersion(plugin)
|
||||||
|
.then(function(version) {
|
||||||
|
expect(version).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must resolve a normal plugin dependency', function() {
|
||||||
|
var plugin = PluginDependency.createFromString('ga@>0.9.0 < 1.0.1');
|
||||||
|
|
||||||
|
return resolveVersion(plugin)
|
||||||
|
.then(function(version) {
|
||||||
|
expect(version).toBe('1.0.0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
var PluginDependency = require('../../models/pluginDependency');
|
||||||
|
var sortDependencies = require('../sortDependencies');
|
||||||
|
var toNames = require('../toNames');
|
||||||
|
|
||||||
|
describe('sortDependencies', function() {
|
||||||
|
it('must load themes after plugins', function() {
|
||||||
|
var allPlugins = PluginDependency.listFromArray([
|
||||||
|
'hello',
|
||||||
|
'theme-test',
|
||||||
|
'world'
|
||||||
|
]);
|
||||||
|
|
||||||
|
var sorted = sortDependencies(allPlugins);
|
||||||
|
var names = toNames(sorted);
|
||||||
|
|
||||||
|
expect(names).toEqual([
|
||||||
|
'hello',
|
||||||
|
'world',
|
||||||
|
'theme-test'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('must keep order of themes', function() {
|
||||||
|
var allPlugins = PluginDependency.listFromArray([
|
||||||
|
'theme-test',
|
||||||
|
'theme-test1',
|
||||||
|
'hello',
|
||||||
|
'theme-test2',
|
||||||
|
'world'
|
||||||
|
]);
|
||||||
|
var sorted = sortDependencies(allPlugins);
|
||||||
|
var names = toNames(sorted);
|
||||||
|
|
||||||
|
expect(names).toEqual([
|
||||||
|
'hello',
|
||||||
|
'world',
|
||||||
|
'theme-test',
|
||||||
|
'theme-test1',
|
||||||
|
'theme-test2'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
var path = require('path');
|
|
||||||
var Immutable = require('immutable');
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
var Promise = require('../utils/promise');
|
var Promise = require('../utils/promise');
|
||||||
var timing = require('../utils/timing');
|
var timing = require('../utils/timing');
|
||||||
var findInstalled = require('./findInstalled');
|
var findInstalled = require('./findInstalled');
|
||||||
|
var locateRootFolder = require('./locateRootFolder');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
List all plugins installed in a book
|
* List all plugins installed in a book
|
||||||
|
*
|
||||||
@param {Book}
|
* @param {Book}
|
||||||
@return {Promise<OrderedMap<String:Plugin>>}
|
* @return {Promise<OrderedMap<String:Plugin>>}
|
||||||
*/
|
*/
|
||||||
function findForBook(book) {
|
function findForBook(book) {
|
||||||
return timing.measure(
|
return timing.measure(
|
||||||
'plugins.findForBook',
|
'plugins.findForBook',
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
findInstalled(path.resolve(__dirname, '../..')),
|
findInstalled(locateRootFolder()),
|
||||||
findInstalled(book.getRoot())
|
findInstalled(book.getRoot())
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -8,21 +8,21 @@ var Plugin = require('../models/plugin');
|
|||||||
var PREFIX = require('../constants/pluginPrefix');
|
var PREFIX = require('../constants/pluginPrefix');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Validate if a package name is a GitBook plugin
|
* Validate if a package name is a GitBook plugin
|
||||||
|
*
|
||||||
@return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
function validateId(name) {
|
function validateId(name) {
|
||||||
return name && name.indexOf(PREFIX) === 0;
|
return name && name.indexOf(PREFIX) === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
List all packages installed inside a folder
|
* List all packages installed inside a folder
|
||||||
|
*
|
||||||
@param {String} folder
|
* @param {String} folder
|
||||||
@return {OrderedMap<String:Plugin>}
|
* @return {OrderedMap<String:Plugin>}
|
||||||
*/
|
*/
|
||||||
function findInstalled(folder) {
|
function findInstalled(folder) {
|
||||||
var options = {
|
var options = {
|
||||||
dev: false,
|
dev: false,
|
||||||
@@ -31,7 +31,7 @@ function findInstalled(folder) {
|
|||||||
};
|
};
|
||||||
var results = Immutable.OrderedMap();
|
var results = Immutable.OrderedMap();
|
||||||
|
|
||||||
function onPackage(pkg, isRoot) {
|
function onPackage(pkg, parent) {
|
||||||
if (!pkg.name) return;
|
if (!pkg.name) return;
|
||||||
|
|
||||||
var name = pkg.name;
|
var name = pkg.name;
|
||||||
@@ -43,18 +43,19 @@ function findInstalled(folder) {
|
|||||||
var pluginName = name.slice(PREFIX.length);
|
var pluginName = name.slice(PREFIX.length);
|
||||||
|
|
||||||
if (!validateId(name)){
|
if (!validateId(name)){
|
||||||
if (!isRoot) return;
|
if (parent) return;
|
||||||
} else {
|
} else {
|
||||||
results = results.set(pluginName, Plugin({
|
results = results.set(pluginName, Plugin({
|
||||||
name: pluginName,
|
name: pluginName,
|
||||||
version: version,
|
version: version,
|
||||||
path: pkgPath,
|
path: pkgPath,
|
||||||
depth: depth
|
depth: depth,
|
||||||
|
parent: parent
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Immutable.Map(dependencies).forEach(function(dep) {
|
Immutable.Map(dependencies).forEach(function(dep) {
|
||||||
onPackage(dep);
|
onPackage(dep, pluginName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ function findInstalled(folder) {
|
|||||||
var module_folder = path.join(node_modules, module);
|
var module_folder = path.join(node_modules, module);
|
||||||
return Promise.nfcall(readInstalled, module_folder, options)
|
return Promise.nfcall(readInstalled, module_folder, options)
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
onPackage(data, true);
|
onPackage(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
var npmi = require('npmi');
|
||||||
|
|
||||||
|
var Promise = require('../utils/promise');
|
||||||
|
var resolveVersion = require('./resolveVersion');
|
||||||
|
|
||||||
|
/**
|
||||||
|
Install a plugin for a book
|
||||||
|
|
||||||
|
@param {Book}
|
||||||
|
@param {PluginDependency}
|
||||||
|
@return {Promise}
|
||||||
|
*/
|
||||||
|
function installPlugin(book, plugin) {
|
||||||
|
var logger = book.getLogger();
|
||||||
|
|
||||||
|
var installFolder = book.getRoot();
|
||||||
|
var name = plugin.getName();
|
||||||
|
var requirement = plugin.getVersion();
|
||||||
|
|
||||||
|
logger.info.ln('');
|
||||||
|
logger.info.ln('installing plugin "' + name + '"');
|
||||||
|
|
||||||
|
// Find a version to install
|
||||||
|
return resolveVersion(plugin)
|
||||||
|
.then(function(version) {
|
||||||
|
if (!version) {
|
||||||
|
throw new Error('Found no satisfactory version for plugin "' + name + '" with requirement "' + requirement + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info.ln('install plugin "' + name +'" (' + requirement + ') from NPM with version', version);
|
||||||
|
return Promise.nfcall(npmi, {
|
||||||
|
'name': plugin.getNpmID(),
|
||||||
|
'version': version,
|
||||||
|
'path': installFolder,
|
||||||
|
'npmLoad': {
|
||||||
|
'loglevel': 'silent',
|
||||||
|
'loaded': true,
|
||||||
|
'prefix': installFolder
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
logger.info.ok('plugin "' + name + '" installed with success');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = installPlugin;
|
||||||
+16
-115
@@ -1,134 +1,34 @@
|
|||||||
var npm = require('npm');
|
|
||||||
var npmi = require('npmi');
|
var npmi = require('npmi');
|
||||||
var semver = require('semver');
|
|
||||||
var Immutable = require('immutable');
|
|
||||||
|
|
||||||
var pkg = require('../../package.json');
|
|
||||||
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
|
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
|
||||||
var Promise = require('../utils/promise');
|
var Promise = require('../utils/promise');
|
||||||
var Plugin = require('../models/plugin');
|
var installPlugin = require('./installPlugin');
|
||||||
var gitbook = require('../gitbook');
|
|
||||||
var listForBook = require('./listForBook');
|
|
||||||
|
|
||||||
var npmIsReady;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Initialize and prepare NPM
|
|
||||||
|
|
||||||
@return {Promise}
|
|
||||||
*/
|
|
||||||
function initNPM() {
|
|
||||||
if (npmIsReady) return npmIsReady;
|
|
||||||
|
|
||||||
npmIsReady = Promise.nfcall(npm.load, {
|
|
||||||
silent: true,
|
|
||||||
loglevel: 'silent'
|
|
||||||
});
|
|
||||||
|
|
||||||
return npmIsReady;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Resolve a plugin to a version
|
|
||||||
|
|
||||||
@param {Plugin}
|
|
||||||
@return {Promise<String>}
|
|
||||||
*/
|
|
||||||
function resolveVersion(plugin) {
|
|
||||||
var npmId = Plugin.nameToNpmID(plugin.getName());
|
|
||||||
var requiredVersion = plugin.getVersion();
|
|
||||||
|
|
||||||
return initNPM()
|
|
||||||
.then(function() {
|
|
||||||
return Promise.nfcall(npm.commands.view, [npmId + '@' + requiredVersion, 'engines'], true);
|
|
||||||
})
|
|
||||||
.then(function(versions) {
|
|
||||||
versions = Immutable.Map(versions).entrySeq();
|
|
||||||
|
|
||||||
var result = versions
|
|
||||||
.map(function(entry) {
|
|
||||||
return {
|
|
||||||
version: entry[0],
|
|
||||||
gitbook: (entry[1].engines || {}).gitbook
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(function(v) {
|
|
||||||
return v.gitbook && gitbook.satisfies(v.gitbook);
|
|
||||||
})
|
|
||||||
.sort(function(v1, v2) {
|
|
||||||
return semver.lt(v1.version, v2.version)? 1 : -1;
|
|
||||||
})
|
|
||||||
.get(0);
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
} else {
|
|
||||||
return result.version;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Install a plugin for a book
|
|
||||||
|
|
||||||
@param {Book}
|
|
||||||
@param {Plugin}
|
|
||||||
@return {Promise}
|
|
||||||
*/
|
|
||||||
function installPlugin(book, plugin) {
|
|
||||||
var logger = book.getLogger();
|
|
||||||
|
|
||||||
var installFolder = book.getRoot();
|
|
||||||
var name = plugin.getName();
|
|
||||||
var requirement = plugin.getVersion();
|
|
||||||
|
|
||||||
logger.info.ln('');
|
|
||||||
logger.info.ln('installing plugin "' + name + '"');
|
|
||||||
|
|
||||||
// Find a version to install
|
|
||||||
return resolveVersion(plugin)
|
|
||||||
.then(function(version) {
|
|
||||||
if (!version) {
|
|
||||||
throw new Error('Found no satisfactory version for plugin "' + name + '" with requirement "' + requirement + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info.ln('install plugin "' + name +'" (' + requirement + ') from NPM with version', version);
|
|
||||||
return Promise.nfcall(npmi, {
|
|
||||||
'name': plugin.getNpmID(),
|
|
||||||
'version': version,
|
|
||||||
'path': installFolder,
|
|
||||||
'npmLoad': {
|
|
||||||
'loglevel': 'silent',
|
|
||||||
'loaded': true,
|
|
||||||
'prefix': installFolder
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(function() {
|
|
||||||
logger.info.ok('plugin "' + name + '" installed with success');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Install plugin requirements for a book
|
Install plugin requirements for a book
|
||||||
|
|
||||||
@param {Book}
|
@param {Book}
|
||||||
@return {Promise}
|
@return {Promise<Number>}
|
||||||
*/
|
*/
|
||||||
function installPlugins(book) {
|
function installPlugins(book) {
|
||||||
var logger = book.getLogger();
|
var logger = book.getLogger();
|
||||||
var plugins = listForBook(book);
|
var config = book.getConfig();
|
||||||
|
var plugins = config.getPluginDependencies();
|
||||||
|
|
||||||
// Remove default plugins
|
// Remove default plugins
|
||||||
// (only if version is same as installed)
|
// (only if version is same as installed)
|
||||||
plugins = plugins.filterNot(function(plugin) {
|
plugins = plugins.filterNot(function(plugin) {
|
||||||
|
var dependency = DEFAULT_PLUGINS.find(function(dep) {
|
||||||
|
return dep.getName() === plugin.getName();
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
DEFAULT_PLUGINS.includes(plugin.getName()) &&
|
// Disabled plugin
|
||||||
plugin.getVersion() === pkg.dependencies[plugin.getNpmID()]
|
!plugin.isEnabled() ||
|
||||||
|
|
||||||
|
// Or default one installed in GitBook itself
|
||||||
|
(dependency &&
|
||||||
|
plugin.getVersion() === dependency.getVersion())
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,7 +41,8 @@ function installPlugins(book) {
|
|||||||
|
|
||||||
return Promise.forEach(plugins, function(plugin) {
|
return Promise.forEach(plugins, function(plugin) {
|
||||||
return installPlugin(book, plugin);
|
return installPlugin(book, plugin);
|
||||||
});
|
})
|
||||||
|
.thenResolve(plugins.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = installPlugins;
|
module.exports = installPlugins;
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
var Immutable = require('immutable');
|
|
||||||
var Plugin = require('../models/plugin');
|
|
||||||
|
|
||||||
var pkg = require('../../package.json');
|
|
||||||
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
|
|
||||||
|
|
||||||
/**
|
|
||||||
List all plugins for a book
|
|
||||||
|
|
||||||
@param {List<PluginDependency>} deps
|
|
||||||
@return {OrderedMap<Plugin>}
|
|
||||||
*/
|
|
||||||
function listAll(deps) {
|
|
||||||
// Extract list of plugins to disable (starting with -)
|
|
||||||
var toRemove = deps
|
|
||||||
.filter(function(plugin) {
|
|
||||||
return !plugin.isEnabled();
|
|
||||||
})
|
|
||||||
.map(function(plugin) {
|
|
||||||
return plugin.getName();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert to an ordered map of Plugin
|
|
||||||
var plugins = deps
|
|
||||||
.map(function(dep) {
|
|
||||||
var plugin = Plugin.createFromDep(dep);
|
|
||||||
|
|
||||||
return [dep.getName(), plugin];
|
|
||||||
});
|
|
||||||
plugins = Immutable.OrderedMap(plugins);
|
|
||||||
|
|
||||||
// Append default plugins
|
|
||||||
DEFAULT_PLUGINS.forEach(function(pluginName) {
|
|
||||||
if (plugins.has(pluginName)) return;
|
|
||||||
|
|
||||||
plugins = plugins.set(pluginName, new Plugin({
|
|
||||||
name: pluginName,
|
|
||||||
version: pkg.dependencies[Plugin.nameToNpmID(pluginName)]
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove plugins
|
|
||||||
plugins = plugins.filterNot(function(plugin, name) {
|
|
||||||
return toRemove.includes(name);
|
|
||||||
});
|
|
||||||
|
|
||||||
return plugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = listAll;
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
|
||||||
|
var sortDependencies = require('./sortDependencies');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all dependencies for a book, including default plugins.
|
||||||
|
* It returns a concat with default plugins and remove disabled ones.
|
||||||
|
*
|
||||||
|
* @param {List<PluginDependency>} deps
|
||||||
|
* @return {List<PluginDependency>}
|
||||||
|
*/
|
||||||
|
function listDependencies(deps) {
|
||||||
|
// Extract list of plugins to disable (starting with -)
|
||||||
|
var toRemove = deps
|
||||||
|
.filter(function(plugin) {
|
||||||
|
return !plugin.isEnabled();
|
||||||
|
})
|
||||||
|
.map(function(plugin) {
|
||||||
|
return plugin.getName();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Concat with default plugins
|
||||||
|
deps = deps.concat(DEFAULT_PLUGINS);
|
||||||
|
|
||||||
|
// Remove plugins
|
||||||
|
deps = deps.filterNot(function(plugin) {
|
||||||
|
return toRemove.includes(plugin.getName());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort
|
||||||
|
return sortDependencies(deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = listDependencies;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
var listDependencies = require('./listDependencies');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all plugin requirements for a book.
|
||||||
|
* It can be different from the final list of plugins,
|
||||||
|
* since plugins can have their own dependencies
|
||||||
|
*
|
||||||
|
* @param {Book}
|
||||||
|
* @return {List<PluginDependency>}
|
||||||
|
*/
|
||||||
|
function listDepsForBook(book) {
|
||||||
|
var config = book.getConfig();
|
||||||
|
var plugins = config.getPluginDependencies();
|
||||||
|
|
||||||
|
return listDependencies(plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = listDepsForBook;
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
var listAll = require('./listAll');
|
|
||||||
|
|
||||||
/**
|
|
||||||
List all plugin requirements for a book.
|
|
||||||
It can be different from the final list of plugins,
|
|
||||||
since plugins can have their own dependencies
|
|
||||||
|
|
||||||
@param {Book}
|
|
||||||
@return {OrderedMap<Plugin>}
|
|
||||||
*/
|
|
||||||
function listForBook(book) {
|
|
||||||
var config = book.getConfig();
|
|
||||||
var plugins = config.getPluginDependencies();
|
|
||||||
|
|
||||||
return listAll(plugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = listForBook;
|
|
||||||
+47
-31
@@ -1,53 +1,69 @@
|
|||||||
var Promise = require('../utils/promise');
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
var listForBook = require('./listForBook');
|
var Promise = require('../utils/promise');
|
||||||
|
var listDepsForBook = require('./listDepsForBook');
|
||||||
var findForBook = require('./findForBook');
|
var findForBook = require('./findForBook');
|
||||||
var loadPlugin = require('./loadPlugin');
|
var loadPlugin = require('./loadPlugin');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Load a list of plugins in a book
|
* Load all plugins in a book
|
||||||
|
*
|
||||||
@param {Book}
|
* @param {Book}
|
||||||
@return {Promise<Map<String:Plugin>}
|
* @return {Promise<Map<String:Plugin>}
|
||||||
*/
|
*/
|
||||||
function loadForBook(book) {
|
function loadForBook(book) {
|
||||||
var logger = book.getLogger();
|
var logger = book.getLogger();
|
||||||
var requirements = listForBook(book);
|
|
||||||
var requirementsKeys = requirements.keySeq().toList();
|
|
||||||
|
|
||||||
|
// List the dependencies
|
||||||
|
var requirements = listDepsForBook(book);
|
||||||
|
|
||||||
|
// List all plugins installed in the book
|
||||||
return findForBook(book)
|
return findForBook(book)
|
||||||
.then(function(installed) {
|
.then(function(installedMap) {
|
||||||
// Filter out plugins not listed of first level
|
var missing = [];
|
||||||
// (aka pre-installed plugins)
|
var plugins = requirements.reduce(function(result, dep) {
|
||||||
installed = installed.filter(function(plugin) {
|
var name = dep.getName();
|
||||||
return (
|
var installed = installedMap.get(name);
|
||||||
plugin.getDepth() > 0 ||
|
|
||||||
requirements.has(plugin.getName())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort plugins to match list in book.json
|
if (installed) {
|
||||||
installed = installed.sort(function(a, b){
|
var deps = installedMap
|
||||||
return requirementsKeys.indexOf(a.getName()) < requirementsKeys.indexOf(b.getName()) ? -1 : 1;
|
.filter(function(plugin) {
|
||||||
});
|
return plugin.getParent() === name;
|
||||||
|
})
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
result = result.concat(deps);
|
||||||
|
result.push(installed);
|
||||||
|
} else {
|
||||||
|
missing.push(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Convert plugins list to a map
|
||||||
|
plugins = Immutable.List(plugins)
|
||||||
|
.map(function(plugin) {
|
||||||
|
return [
|
||||||
|
plugin.getName(),
|
||||||
|
plugin
|
||||||
|
];
|
||||||
|
});
|
||||||
|
plugins = Immutable.OrderedMap(plugins);
|
||||||
|
|
||||||
// Log state
|
// Log state
|
||||||
logger.info.ln(installed.size + ' plugins are installed');
|
logger.info.ln(installedMap.size + ' plugins are installed');
|
||||||
if (requirements.size != installed.size) {
|
if (requirements.size != installedMap.size) {
|
||||||
logger.info.ln(requirements.size + ' explicitly listed');
|
logger.info.ln(requirements.size + ' explicitly listed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that all plugins are present
|
// Verify that all plugins are present
|
||||||
var notInstalled = requirementsKeys.filter(function(name) {
|
if (missing.length > 0) {
|
||||||
return !installed.has(name);
|
throw new Error('Couldn\'t locate plugins "' + missing.join(', ') + '", Run \'gitbook install\' to install plugins from registry.');
|
||||||
});
|
|
||||||
|
|
||||||
if (notInstalled.size > 0) {
|
|
||||||
throw new Error('Couldn\'t locate plugins "' + notInstalled.join(', ') + '", Run \'gitbook install\' to install plugins from registry.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.map(installed, function(plugin) {
|
return Promise.map(plugins, function(plugin) {
|
||||||
return loadPlugin(book, plugin);
|
return loadPlugin(book, plugin);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
var path = require('path');
|
||||||
|
var resolve = require('resolve');
|
||||||
|
|
||||||
|
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the root folder containing for node_modules
|
||||||
|
* since gitbook can be used as a library and dependency can be flattened.
|
||||||
|
*
|
||||||
|
* @return {String} folderPath
|
||||||
|
*/
|
||||||
|
function locateRootFolder() {
|
||||||
|
var firstDefaultPlugin = DEFAULT_PLUGINS.first();
|
||||||
|
var pluginPath = resolve.sync(firstDefaultPlugin.getNpmID() + '/package.json', {
|
||||||
|
basedir: __dirname
|
||||||
|
});
|
||||||
|
var nodeModules = path.resolve(pluginPath, '../../..');
|
||||||
|
|
||||||
|
return nodeModules;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = locateRootFolder;
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
var npm = require('npm');
|
||||||
|
var semver = require('semver');
|
||||||
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
|
var Promise = require('../utils/promise');
|
||||||
|
var Plugin = require('../models/plugin');
|
||||||
|
var gitbook = require('../gitbook');
|
||||||
|
|
||||||
|
var npmIsReady;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Initialize and prepare NPM
|
||||||
|
|
||||||
|
@return {Promise}
|
||||||
|
*/
|
||||||
|
function initNPM() {
|
||||||
|
if (npmIsReady) return npmIsReady;
|
||||||
|
|
||||||
|
npmIsReady = Promise.nfcall(npm.load, {
|
||||||
|
silent: true,
|
||||||
|
loglevel: 'silent'
|
||||||
|
});
|
||||||
|
|
||||||
|
return npmIsReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Resolve a plugin dependency to a version
|
||||||
|
|
||||||
|
@param {PluginDependency} plugin
|
||||||
|
@return {Promise<String>}
|
||||||
|
*/
|
||||||
|
function resolveVersion(plugin) {
|
||||||
|
var npmId = Plugin.nameToNpmID(plugin.getName());
|
||||||
|
var requiredVersion = plugin.getVersion();
|
||||||
|
|
||||||
|
if (plugin.isGitDependency()) {
|
||||||
|
return Promise.resolve(requiredVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
return initNPM()
|
||||||
|
.then(function() {
|
||||||
|
return Promise.nfcall(npm.commands.view, [npmId + '@' + requiredVersion, 'engines'], true);
|
||||||
|
})
|
||||||
|
.then(function(versions) {
|
||||||
|
versions = Immutable.Map(versions).entrySeq();
|
||||||
|
|
||||||
|
var result = versions
|
||||||
|
.map(function(entry) {
|
||||||
|
return {
|
||||||
|
version: entry[0],
|
||||||
|
gitbook: (entry[1].engines || {}).gitbook
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(function(v) {
|
||||||
|
return v.gitbook && gitbook.satisfies(v.gitbook);
|
||||||
|
})
|
||||||
|
.sort(function(v1, v2) {
|
||||||
|
return semver.lt(v1.version, v2.version)? 1 : -1;
|
||||||
|
})
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
return result.version;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = resolveVersion;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
var Immutable = require('immutable');
|
||||||
|
|
||||||
|
var THEME_PREFIX = require('../constants/themePrefix');
|
||||||
|
|
||||||
|
var TYPE_PLUGIN = 'plugin';
|
||||||
|
var TYPE_THEME = 'theme';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of a plugin given its name
|
||||||
|
* @param {Plugin} plugin
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
function pluginType(plugin) {
|
||||||
|
var name = plugin.getName();
|
||||||
|
return (name && name.indexOf(THEME_PREFIX) === 0) ? TYPE_THEME : TYPE_PLUGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the list of dependencies to match list in book.json
|
||||||
|
* The themes should always be loaded after the plugins
|
||||||
|
*
|
||||||
|
* @param {List<PluginDependency>} deps
|
||||||
|
* @return {List<PluginDependency>}
|
||||||
|
*/
|
||||||
|
function sortDependencies(plugins) {
|
||||||
|
var byTypes = plugins.groupBy(pluginType);
|
||||||
|
|
||||||
|
return byTypes.get(TYPE_PLUGIN, Immutable.List())
|
||||||
|
.concat(byTypes.get(TYPE_THEME, Immutable.List()));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = sortDependencies;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user