Compare commits

...

65 Commits

Author SHA1 Message Date
Samy Pessé 38ab9fa05a Bump version to 3.2.0 2016-07-11 16:48:13 +02:00
Samy Pessé 12e20c5fcc Update theme-default@1.0.5 2016-07-11 16:47:16 +02:00
Samy Pessé 6b103f4532 Fix #1408: fix i18n in website generator and add tests for it 2016-07-11 16:27:22 +02:00
Soreine d4fa5c18a8 editPlugin and getPluginConfig 2016-07-11 15:18:35 +02:00
Soreine 2a116dfdb0 Fix reducedObject when default property is undefined 2016-07-11 15:18:35 +02:00
Samy Pessé 0349f54f3c Export file model 2016-07-07 16:41:05 +02:00
Samy Pessé 695bda312c Bump version to 3.2.0-pre.1 2016-07-05 17:15:48 +02:00
Samy Pessé 144bd4c97a Update gitbook-markdown@2.0.1 2016-07-05 11:07:51 +02:00
Samy Pessé e105983cf8 Add back property "output" to page/template context 2016-07-04 18:08:44 +02:00
Soreine ea3afbed75 Update gitbook-plugin-lunr@1.2.0 2016-07-01 17:52:15 +02:00
Soreine d563abe3df Reduce size of logo to give room to version number 2016-06-30 15:37:31 +02:00
Soreine 70fd4ded29 Update theme-official for doc 2016-06-30 12:15:50 +02:00
Samy Pessé 9ce9388dfc Bump version to 3.2.0-pre.0 2016-06-30 11:09:18 +02:00
Samy Pessé c575ad83ae Update gitbook-markdown@2.0.0 2016-06-30 10:51:46 +02:00
Samy Pessé 936fa1545b Add tests for locationUtils.flatten 2016-06-29 10:32:49 +02:00
Samy Pessé c9c2dde2b6 Add method "isFile" and "isReadme" in SummaryArticle 2016-06-29 10:31:06 +02:00
Samy Pessé d9a1d387c7 Merge pull request #1397 from mpfeil/master
add open and browser args to serve command
2016-06-23 11:40:05 +02:00
Matthias Pfeil bf29d187c8 disable open by default 2016-06-20 17:24:08 +02:00
Matthias Pfeil d86f9b2667 add open and browser args to serve command 2016-06-17 16:00:53 +02:00
Samy Pesse aca4313fc3 Enable long stack support when process.env.CI is true 2016-06-11 12:38:19 +02:00
Samy Pesse 4cb5bb06fb Bump version to 3.1.1 2016-06-10 22:19:09 +02:00
Samy Pesse 24988b75ea Fix extsAsciidoc.js 2016-06-10 22:14:04 +02:00
Samy Pesse 775e858d05 Expose markdown/asciidoc extensions to gitbook module 2016-06-10 22:13:31 +02:00
Samy Pesse 1e3d871c1e Merge branch 'fix/plugins_loading' 2016-06-10 22:08:16 +02:00
Samy Pesse 94a4310c1e Adapt Plugins.loadForBook to use correct order 2016-06-10 22:04:19 +02:00
Samy Pessé e8ee57be83 Start fixing order of plugins in dependencies 2016-06-10 18:34:24 +02:00
Samy Pessé 50a132cb2c Add method "createChildLevel" to SummaryPart 2016-06-10 18:11:22 +02:00
Samy Pessé 59be32a54f Add method .createChildLevel for SummaryArticle 2016-06-10 18:08:56 +02:00
Soreine 4ae7cdb5d1 Mention autocover plugin in docs 2016-06-10 18:03:40 +02:00
Samy Pessé 990a4efeb5 Update gitbook-markdown and gitbook-asciidoc 2016-06-10 17:53:10 +02:00
Samy Pessé 9c927dcf13 Fix #1374 and #1360: apply transform of conrefsLoader using a string, not a buffer 2016-06-10 17:45:30 +02:00
Samy Pessé 7a08b3f109 Fix #1377: fix listing of plugins when using gitbook as a library 2016-06-10 17:15:04 +02:00
Samy Pessé 94d4234cb8 Merge branch 'fix/plugins_order' 2016-06-10 16:44:13 +02:00
Samy Pessé fbc851bc4e Fix listAll to add default plugins after normal ones 2016-06-10 16:43:32 +02:00
Samy Pessé e92b661a2a Adapt sortPlugins to only sort an ordered map of plugins 2016-06-10 16:37:44 +02:00
Soreine b9fe60915f Add tests for insertPart 2016-06-10 15:40:51 +02:00
Samy Pessé 1bf7d092f0 Merge pull request #1363 from todvora/master
Fixed target attribute for external links
2016-06-10 00:28:50 +02:00
Samy Pesse 826d472511 Fix documentation about themes 2016-06-08 20:06:50 +02:00
Samy Pessé a6b1f4bd87 Compare list of plugins sorted to array of names in tests 2016-06-08 15:08:11 +02:00
Samy Pessé 004f91f02c sortPlugins should not be async 2016-06-08 15:02:34 +02:00
Samy Pessé e6c6ec4293 Improve testing for sortPlugins 2016-06-08 14:57:54 +02:00
Soreine a1d3acf065 Fix serve cli using new ConfigModifier.addPlugin 2016-06-08 10:42:27 +02:00
Soreine a732660f59 docs: cover remove extraneous line 2016-06-08 10:35:12 +02:00
Nicolas Gaborit e1bb0638f8 docs: cover, fix formatting 2016-06-08 10:16:54 +02:00
Soreine 788c0c7e7e Adds cover.jpg info 2016-06-08 10:12:42 +02:00
Tomas Dvorak 762fe16a17 fixed target attribute for external links 2016-06-07 18:34:36 +02:00
Samy Pessé 15123805d1 Fix linting error 2016-06-07 16:24:22 +02:00
Samy Pessé 23aecb4f2b Improve output of page to text with frontmatter 2016-06-07 16:23:12 +02:00
Samy Pessé f1d200b81b Rollback frontmatter parsing to js-yaml to be browserify compatible 2016-06-07 14:32:12 +02:00
Samy Pessé b566711ee1 Add method toText to Page 2016-06-07 12:51:35 +02:00
Samy Pessé 22da5aac11 Switch frontmatter parsing to gray-matter 2016-06-07 12:47:16 +02:00
Samy Pessé f7b95102c5 Expose Page model 2016-06-07 12:36:32 +02:00
Samy Pessé ca70c934d6 Split page parsing into parsePageFromString and parsePage
Add tests for parsePageFromString
2016-06-07 12:31:21 +02:00
Samy Pessé 1cc48fa314 Use Immutable instead of lodash for mergeDefaults 2016-06-07 12:06:29 +02:00
Samy Pessé b63b5574d3 Only merge objects in mergeDefaults 2016-06-07 11:40:54 +02:00
Samy Pessé 67106e8698 Improve plugins config modifier 2016-06-07 11:06:53 +02:00
Samy Pessé dc2ab51a88 Update i18n-t and juice 2016-06-07 10:54:34 +02:00
Samy Pessé cedfa186f2 Remove merge-defaults dependency 2016-06-07 10:48:04 +02:00
Samy Pessé 80f0d1bfed Update gitbook-markdown and gitbook-asciidoc to use lodash@4 2016-06-07 10:36:01 +02:00
Samy Pessé 7178ab1f82 Improve Config.toText to return only the required fields 2016-06-07 10:26:28 +02:00
Samy Pesse bf895b707e Improve addPlugin to not append default plugins to the list 2016-06-07 00:56:24 +02:00
Samy Pesse 996170a4ad ConfigModifier.removePlugin should toggle off if default 2016-06-07 00:48:47 +02:00
Samy Pesse aa66a4f928 Expose DEFAULT_PLUGINS as a list of PluginDependency 2016-06-06 22:04:13 +02:00
Samy Pesse 23010fa54a Adapt tests for config modifiers 2016-06-06 21:22:44 +02:00
Samy Pesse 0baebc7979 Change config modifier to work on a config object 2016-06-06 21:20:00 +02:00
95 changed files with 1527 additions and 784 deletions
+15
View File
@@ -2,6 +2,21 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 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
+2 -2
View File
@@ -6,10 +6,10 @@ module.exports = {
title: 'GitBook Toolchain Documentation',
// Enforce use of GitBook v3
gitbook: '>=3.0.0-pre.0',
gitbook: '3.1.1',
// Use the "official" theme
plugins: ['theme-official', 'sitemap'],
plugins: ['theme-official@2.1.1', '-sharing', '-fontsettings', 'sitemap'],
variables: {
version: pkg.version
+2 -8
View File
@@ -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.
| 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.
| Variable | Description |
| -------- | ----------- |
| `theme` | The theme to use for the book |
| `plugins` | List of plugins to load |
| `pluginsConfig` |Configuration for plugins |
### Structure
+4 -3
View File
@@ -29,12 +29,13 @@ You can replace `/usr/bin` with any directory that is in your $PATH.
### 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:
* Size of 1800x2360 (pixels)
* Size of 1800x2360 pixels for `cover.jpg`, 200x262 for `cover_small.jpg`
* No border
* Clearly visible book title
* Any important text should be visible in the small version
+4
View File
@@ -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 ;
}
+16 -1
View File
@@ -17,7 +17,22 @@ A theme is a plugin containing templates and assets. Overriding any individual t
### 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
+11 -4
View File
@@ -6,14 +6,21 @@ module.exports = {
// Models
Book: require('./models/book'),
FS: require('./models/fs'),
File: require('./models/file'),
Summary: require('./models/summary'),
Glossary: require('./models/glossary'),
Config: require('./models/config'),
Page: require('./models/page'),
PluginDependency: require('./models/pluginDependency'),
// Modifiers
SummaryModifier: Modifiers.Summary,
ConfigModifier: Modifiers.Config,
SummaryModifier: Modifiers.Summary,
ConfigModifier: Modifiers.Config,
// Constants
CONFIG_FILES: require('./constants/configFiles.js'),
IGNORE_FILES: require('./constants/ignoreFiles.js')
CONFIG_FILES: require('./constants/configFiles.js'),
IGNORE_FILES: require('./constants/ignoreFiles.js'),
DEFAULT_PLUGINS: require('./constants/defaultPlugins'),
EXTENSIONS_MARKDOWN: require('./constants/extsMarkdown'),
EXTENSIONS_ASCIIDOC: require('./constants/extsAsciidoc')
};
+20 -1
View File
@@ -1,6 +1,7 @@
/* eslint-disable no-console */
var tinylr = require('tiny-lr');
var open = require('open');
var Parse = require('../parse');
var Output = require('../output');
@@ -32,9 +33,11 @@ function generateBook(args, kwargs) {
var outputFolder = getOutputFolder(args);
var book = getBook(args, kwargs);
var Generator = Output.getGenerator(kwargs.format);
var browser = kwargs['browser'];
var hasWatch = kwargs['watch'];
var hasLiveReloading = kwargs['live'];
var hasOpen = kwargs['open'];
// Stop server if running
if (server.isRunning()) console.log('Stopping server');
@@ -45,7 +48,9 @@ function generateBook(args, kwargs) {
.then(function(resultBook) {
if (hasLiveReloading) {
// 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, {
@@ -69,6 +74,10 @@ function generateBook(args, kwargs) {
}
});
}
if (hasOpen) {
open('http://localhost:'+port, browser);
}
})
.then(function() {
if (!hasWatch) {
@@ -110,6 +119,16 @@ module.exports = {
description: 'Enable live reloading',
defaults: true
},
{
name: 'open',
description: 'Enable opening book in browser',
defaults: false
},
{
name: 'browser',
description: 'Specify browser for opening book',
defaults: ''
},
options.log,
options.format
],
+4
View File
@@ -18,6 +18,10 @@ module.exports = {
'type': 'string',
'title': 'ISBN for published book'
},
'language': {
'type': 'string',
'title': 'Language of the book'
},
'author': {
'type': 'string',
'title': 'Name of the author'
+19 -4
View File
@@ -1,9 +1,24 @@
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,
default plugins should be installed in node dependencies of GitBook
*/
* List of default plugins for all books,
* default plugins should be installed in node dependencies of GitBook
*/
module.exports = Immutable.List([
'highlight',
'search',
@@ -11,4 +26,4 @@ module.exports = Immutable.List([
'sharing',
'fontsettings',
'theme-default'
]);
]).map(createFromDependency);
+4
View File
@@ -0,0 +1,4 @@
module.exports = [
'.adoc',
'.asciidoc'
];
+5
View File
@@ -0,0 +1,5 @@
module.exports = [
'.md',
'.markdown',
'.mdown'
];
+6 -6
View File
@@ -3,12 +3,12 @@ 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}
*/
* Return a JSON representation of a book with a specific file
*
* @param {Book} output
* @param {Page} page
* @return {Object}
*/
function encodeBookWithPage(book, page) {
var file = page.getFile();
+5 -5
View File
@@ -1,11 +1,11 @@
var encodeBook = require('./encodeBook');
/**
Encode an output to JSON
@param {Output}
@return {Object}
*/
* Encode an output to JSON
*
* @param {Output}
* @return {Object}
*/
function encodeOutputToJson(output) {
var book = output.getBook();
var generator = output.getGenerator();
+23
View File
@@ -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;
+1
View File
@@ -2,6 +2,7 @@
module.exports = {
encodeOutput: require('./encodeOutput'),
encodeBookWithPage: require('./encodeBookWithPage'),
encodeOutputWithPage: require('./encodeOutputWithPage'),
encodeBook: require('./encodeBook'),
encodeFile: require('./encodeFile'),
encodePage: require('./encodePage'),
+30
View File
@@ -55,6 +55,36 @@ describe('Config', function() {
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'
}
});
});
});
});
+28
View File
@@ -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');
});
});
});
+53
View File
@@ -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);
});
});
});
+23
View File
@@ -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');
});
});
});
+11 -1
View File
@@ -4,6 +4,7 @@ var Immutable = require('immutable');
var File = require('./file');
var PluginDependency = require('./pluginDependency');
var configDefault = require('../constants/configDefault');
var reducedObject = require('../utils/reducedObject');
var Config = Immutable.Record({
file: File(),
@@ -18,12 +19,21 @@ Config.prototype.getValues = function() {
return this.get('values');
};
/**
* 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());
};
/**
* Render config as text
* @return {Promise<String>}
*/
Config.prototype.toText = function() {
return JSON.stringify(this.getValues().toJS(), null, 4);
return JSON.stringify(this.toReducedVersion().toJS(), null, 4);
};
/**
+21 -6
View File
@@ -1,4 +1,5 @@
var Immutable = require('immutable');
var yaml = require('js-yaml');
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() {
return this.getFile().getPath();
};
/**
Create a page for a file
@param {File} file
@return {Page}
* Create a page for a file
* @param {File} file
* @return {Page}
*/
Page.createForFile = function(file) {
return new Page({
+50 -39
View File
@@ -1,9 +1,10 @@
var Immutable = require('immutable');
var TemplateBlock = require('./templateBlock');
var DEFAULT_VERSION = '*';
var PluginDependency = require('./pluginDependency');
var THEME_PREFIX = require('../constants/themePrefix');
var DEFAULT_VERSION = '*';
var Plugin = Immutable.Record({
name: String(),
@@ -17,6 +18,9 @@ var Plugin = Immutable.Record({
// Depth of this plugin in the dependency tree
depth: Number(0),
// Parent depending on this plugin
parent: String(),
// Content of the "package.json"
package: Immutable.Map(),
@@ -48,38 +52,48 @@ Plugin.prototype.getDepth = function() {
return this.get('depth');
};
/**
Return the ID on NPM for this plugin
Plugin.prototype.getParent = function() {
return this.get('parent');
};
@return {String}
*/
/**
* Return the ID on NPM for this plugin
* @return {String}
*/
Plugin.prototype.getNpmID = function() {
return PluginDependency.nameToNpmID(this.getName());
};
/**
Check if a plugin is loaded
@return {Boolean}
*/
* Check if a plugin is loaded
* @return {Boolean}
*/
Plugin.prototype.isLoaded = function() {
return Boolean(this.getPackage().size > 0);
};
/**
Return map of hooks
@return {Map<String:Function>}
*/
* Check if a plugin is a theme given its name
* @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() {
return this.getContent().get('hooks') || Immutable.Map();
};
/**
Return infos about resources for a specific type
@param {String} type
@return {Map<String:Mixed>}
*/
* Return infos about resources for a specific type
* @param {String} type
* @return {Map<String:Mixed>}
*/
Plugin.prototype.getResources = function(type) {
if (type != 'website' && type != 'ebook') {
throw new Error('Invalid assets type ' + type);
@@ -92,17 +106,17 @@ Plugin.prototype.getResources = function(type) {
};
/**
Return map of filters
@return {Map<String:Function>}
*/
* Return map of filters
* @return {Map<String:Function>}
*/
Plugin.prototype.getFilters = function() {
return this.getContent().get('filters');
};
/**
Return map of blocks
@return {Map<String:TemplateBlock>}
*/
* Return map of blocks
* @return {Map<String:TemplateBlock>}
*/
Plugin.prototype.getBlocks = function() {
var blocks = this.getContent().get('blocks');
blocks = blocks || Immutable.Map();
@@ -114,21 +128,19 @@ Plugin.prototype.getBlocks = function() {
};
/**
Return a specific hook
@param {String} name
@return {Function|undefined}
*/
* Return a specific hook
* @param {String} name
* @return {Function|undefined}
*/
Plugin.prototype.getHook = function(name) {
return this.getHooks().get(name);
};
/**
Create a plugin from a string
@param {String}
@return {Plugin}
*/
* Create a plugin from a string
* @param {String}
* @return {Plugin}
*/
Plugin.createFromString = function(s) {
var parts = s.split('@');
var name = parts[0];
@@ -141,11 +153,10 @@ Plugin.createFromString = function(s) {
};
/**
Create a plugin from a dependency
@param {PluginDependency}
@return {Plugin}
*/
* Create a plugin from a dependency
* @param {PluginDependency}
* @return {Plugin}
*/
Plugin.createFromDep = function(dep) {
return new Plugin({
name: dep.getName(),
+76 -37
View File
@@ -30,20 +30,21 @@ SummaryArticle.prototype.getArticles = function() {
};
/**
Return how deep the article is.
The README has a depth of 1
@return {Number}
*/
* Return how deep the article is.
* The README has a depth of 1
*
* @return {Number}
*/
SummaryArticle.prototype.getDepth = function() {
return (this.getLevel().split('.').length - 1);
};
/**
Get path (without anchor) to the pointing file
@return {String}
*/
* Get path (without anchor) to the pointing file.
* It also normalizes the file path.
*
* @return {String}
*/
SummaryArticle.prototype.getPath = function() {
if (this.isExternal()) {
return undefined;
@@ -58,24 +59,24 @@ SummaryArticle.prototype.getPath = function() {
var pathname = (parts.length > 1? parts.slice(0, -1).join('#') : ref);
// Normalize path to remove ('./', etc)
return location.normalize(pathname);
// Normalize path to remove ('./', '/...', etc)
return location.flatten(pathname);
};
/**
Return url if article is external
@return {String}
*/
* Return url if article is external
*
* @return {String}
*/
SummaryArticle.prototype.getUrl = function() {
return this.isExternal()? this.getRef() : undefined;
};
/**
Get anchor for this article (or undefined)
@return {String}
*/
* Get anchor for this article (or undefined)
*
* @return {String}
*/
SummaryArticle.prototype.getAnchor = function() {
var ref = this.getRef();
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() {
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() {
return location.isExternal(this.getRef());
};
/**
Create a SummaryArticle
@param {Object} def
@return {SummaryArticle}
*/
* Create a SummaryArticle
*
* @param {Object} def
* @return {SummaryArticle}
*/
SummaryArticle.create = function(def, level) {
var articles = (def.articles || []).map(function(article, i) {
if (article instanceof SummaryArticle) {
@@ -124,14 +164,13 @@ SummaryArticle.create = function(def, level) {
});
};
/**
Find an article from a base one
@param {Article|Part} base
@param {Function(article)} iter
@return {Article}
*/
* Find an article from a base one
*
* @param {Article|Part} base
* @param {Function(article)} iter
* @return {Article}
*/
SummaryArticle.findArticle = function(base, iter) {
var articles = base.getArticles();
+17 -4
View File
@@ -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 {SummaryPart}
*/
return childLevel;
};
/**
* Create a SummaryPart
*
* @param {Object} def
* @return {SummaryPart}
*/
SummaryPart.create = function(def, level) {
var articles = (def.articles || []).map(function(article, i) {
if (article instanceof SummaryArticle) {
+1 -4
View File
@@ -1,12 +1,10 @@
var addPlugin = require('../addPlugin');
var Config = require('../../../models/config');
var Book = require('../../../models/book');
describe('addPlugin', function() {
var config = Config.createWithValues({
plugins: ['hello', 'world', '-disabled']
});
var book = Book().setConfig(config);
it('should have correct state of dependencies', function() {
var disabledDep = config.getPluginDependency('disabled');
@@ -17,8 +15,7 @@ describe('addPlugin', function() {
});
it('should add the plugin to the list', function() {
var newBook = addPlugin(book, 'test');
var newConfig = newBook.getConfig();
var newConfig = addPlugin(config, 'test');
var testDep = newConfig.getPluginDependency('test');
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();
});
});
@@ -1,16 +1,13 @@
var togglePlugin = require('../togglePlugin');
var Config = require('../../../models/config');
var Book = require('../../../models/book');
describe('togglePlugin', function() {
var config = Config.createWithValues({
plugins: ['hello', 'world', '-disabled']
});
var book = Book().setConfig(config);
it('should enable plugin', function() {
var newBook = togglePlugin(book, 'disabled');
var newConfig = newBook.getConfig();
var newConfig = togglePlugin(config, 'disabled');
var testDep = newConfig.getPluginDependency('disabled');
expect(testDep).toBeDefined();
@@ -19,8 +16,7 @@ describe('togglePlugin', function() {
});
it('should disable plugin', function() {
var newBook = togglePlugin(book, 'world');
var newConfig = newBook.getConfig();
var newConfig = togglePlugin(config, 'world');
var testDep = newConfig.getPluginDependency('world');
expect(testDep).toBeDefined();
+13 -10
View File
@@ -1,22 +1,25 @@
var PluginDependency = require('../../models/pluginDependency');
var togglePlugin = require('./togglePlugin');
var isDefaultPlugin = require('./isDefaultPlugin');
/**
* Add a plugin to a book's configuration
* @param {Book} book
* @param {String} plugin
* @param {Config} config
* @param {String} pluginName
* @param {String} version (optional)
* @return {Book}
* @return {Config}
*/
function addPlugin(book, plugin, version) {
var config = book.getConfig();
var deps = config.getPluginDependencies();
function addPlugin(config, pluginName, version) {
// For default plugin, we only ensure it is enabled
if (isDefaultPlugin(pluginName, version)) {
return togglePlugin(config, pluginName, true);
}
var dep = PluginDependency.create(plugin, version);
var deps = config.getPluginDependencies();
var dep = PluginDependency.create(pluginName, version);
deps = deps.push(dep);
config = config.setPluginDependencies(deps);
return book.setConfig(config);
return config.setPluginDependencies(deps);
}
module.exports = addPlugin;
+5 -8
View File
@@ -1,16 +1,13 @@
/**
* Edit ocnfiguration of a plugin
* @param {Book} book
* Edit configuration of a plugin
* @param {Config} config
* @param {String} plugin
* @param {Object} pluginConfig
* @return {Book}
* @return {Config}
*/
function editPlugin(book, pluginName, pluginConfig) {
var config = book.getConfig();
config = config.set('pluginsConfig.'+pluginName, pluginConfig);
return book.setConfig(config);
function editPlugin(config, pluginName, pluginConfig) {
return config.setValue('pluginsConfig.'+pluginName, pluginConfig);
}
module.exports = editPlugin;
+20
View File
@@ -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;
+15
View File
@@ -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;
+7 -4
View File
@@ -1,7 +1,10 @@
module.exports = {
addPlugin: require('./addPlugin'),
removePlugin: require('./removePlugin'),
togglePlugin: require('./togglePlugin'),
editPlugin: require('./editPlugin')
addPlugin: require('./addPlugin'),
removePlugin: require('./removePlugin'),
togglePlugin: require('./togglePlugin'),
editPlugin: require('./editPlugin'),
hasPlugin: require('./hasPlugin'),
getPluginConfig: require('./getPluginConfig'),
isDefaultPlugin: require('./isDefaultPlugin')
};
+14
View File
@@ -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;
+12 -8
View File
@@ -1,21 +1,25 @@
var togglePlugin = require('./togglePlugin');
var isDefaultPlugin = require('./isDefaultPlugin');
/**
* Remove a plugin from a book's configuration
* @param {Book} book
* @param {Config} config
* @param {String} plugin
* @return {Book}
* @return {Config}
*/
function removePlugin(book, pluginName) {
var config = book.getConfig();
function removePlugin(config, pluginName) {
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;
});
config = config.setPluginDependencies(deps);
return book.setConfig(config);
return config.setPluginDependencies(deps);
}
module.exports = removePlugin;
+14 -8
View File
@@ -1,25 +1,31 @@
var PluginDependency = require('../../models/pluginDependency');
var hasPlugin = require('./hasPlugin');
var isDefaultPlugin = require('./isDefaultPlugin');
/**
* Enable/disable a plugin dependency
* @param {Book} book
* @param {String} plugin
* @param {Config} config
* @param {String} pluginName
* @param {Boolean} state (optional)
* @return {Book}
* @return {Config}
*/
function togglePlugin(book, plugin, state) {
var config = book.getConfig();
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() === plugin) {
if (dep.getName() === pluginName) {
return dep.toggle(state);
}
return dep;
});
config = config.setPluginDependencies(deps);
return book.setConfig(config);
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);
});
});
+38
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
var generateMock = require('../generateMock');
var generateMock = require('./generateMock');
var EbookGenerator = require('../ebook');
describe('EbookGenerator', function() {
@@ -1,21 +1,20 @@
var tmp = require('tmp');
var Book = require('../models/book');
var createMockFS = require('../fs/mock');
var parseBook = require('../parse/parseBook');
var generateBook = require('./generateBook');
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>}
*/
* 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);
+1 -1
View File
@@ -1,4 +1,4 @@
var generateMock = require('../generateMock');
var generateMock = require('./generateMock');
var JSONGenerator = require('../json');
describe('JSONGenerator', function() {
+1 -1
View File
@@ -1,5 +1,5 @@
var fs = require('fs');
var generateMock = require('../generateMock');
var generateMock = require('./generateMock');
var WebsiteGenerator = require('../website');
describe('WebsiteGenerator', function() {
+26 -28
View File
@@ -13,13 +13,12 @@ var generateAssets = require('./generateAssets');
var generatePages = require('./generatePages');
/**
Process an output to generate the book
@param {Generator} generator
@param {Output} output
@return {Promise<Output>}
*/
* Process an output to generate the book
*
* @param {Generator} generator
* @param {Output} output
* @return {Promise<Output>}
*/
function processOutput(generator, startOutput) {
return Promise(startOutput)
.then(preparePlugins)
@@ -133,27 +132,26 @@ function processOutput(generator, startOutput) {
}
/**
Generate a book using a generator.
The overall process is:
1. List and load plugins for this book
2. Call hook "config"
3. Call hook "init"
4. Initialize generator
5. List all assets and pages
6. Copy all assets to output
7. Generate all pages
8. Call hook "finish:before"
9. Finish generation
10. Call hook "finish"
@param {Generator} generator
@param {Book} book
@param {Object} options
@return {Promise<Output>}
*/
* Generate a book using a generator.
*
* The overall process is:
* 1. List and load plugins for this book
* 2. Call hook "config"
* 3. Call hook "init"
* 4. Initialize generator
* 5. List all assets and pages
* 6. Copy all assets to output
* 7. Generate all pages
* 8. Call hook "finish:before"
* 9. Finish generation
* 10. Call hook "finish"
*
*
* @param {Generator} generator
* @param {Book} book
* @param {Object} options
* @return {Promise<Output>}
*/
function generateBook(generator, book, options) {
options = generator.Options(options);
var state = generator.State? generator.State({}) : Immutable.Map();
+7 -7
View File
@@ -10,12 +10,12 @@ var createTemplateEngine = require('./createTemplateEngine');
var callPageHook = require('./callPageHook');
/**
Prepare and generate HTML for a page
@param {Output} output
@param {Page} page
@return {Promise<Page>}
*/
* Prepare and generate HTML for a page
*
* @param {Output} output
* @param {Page} page
* @return {Promise<Page>}
*/
function generatePage(output, page) {
var book = output.getBook();
var engine = createTemplateEngine(output);
@@ -27,7 +27,7 @@ function generatePage(output, page) {
var file = resultPage.getFile();
var filePath = file.getPath();
var parser = file.getParser();
var context = JSONUtils.encodeBookWithPage(book, resultPage);
var context = JSONUtils.encodeOutputWithPage(output, resultPage);
if (!parser) {
return Promise.reject(error.FileNotParsableError({
+5 -5
View File
@@ -9,11 +9,11 @@ var fileToOutput = require('./helper/fileToOutput');
var CODEBLOCK = 'code';
/**
Return default modifier to prepare a page for
rendering.
@return {Array<Modifier>}
*/
* Return default modifier to prepare a page for
* rendering.
*
* @return {Array<Modifier>}
*/
function getModifiers(output, page) {
var book = output.getBook();
var plugins = output.getPlugins();
+6 -6
View File
@@ -6,12 +6,12 @@ var LocationUtils = require('../../utils/location');
var OUTPUT_EXTENSION = '.html';
/**
Convert a filePath (absolute) to a filename for output
@param {Output} output
@param {String} filePath
@return {String}
*/
* Convert a filePath (absolute) to a filename for output
*
* @param {Output} output
* @param {String} filePath
* @return {String}
*/
function fileToOutput(output, filePath) {
var book = output.getBook();
var readme = book.getReadme();
+6 -6
View File
@@ -3,12 +3,12 @@ var LocationUtils = require('../../utils/location');
var fileToURL = require('./fileToURL');
/**
Resolve an absolute path (extracted from a link)
@param {Output} output
@param {String} filePath
@return {String}
*/
* Resolve an absolute path (extracted from a link)
*
* @param {Output} output
* @param {String} filePath
* @return {String}
*/
function resolveFileToURL(output, filePath) {
// Convert /test.png -> test.png
filePath = LocationUtils.toAbsolute(filePath, '', '');
+5 -5
View File
@@ -7,11 +7,11 @@ var getModifiers = require('../getModifiers');
var JSON_VERSION = '3';
/**
Write a page as a json file
@param {Output} output
@param {Page} page
*/
* Write a page as a json file
*
* @param {Output} output
* @param {Page} page
*/
function onPage(output, page) {
var file = page.getFile();
var readme = output.getBook().getReadme().getFile();
+14 -2
View File
@@ -87,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');
});
});
});
});
+9 -10
View File
@@ -59,19 +59,18 @@ function replaceText($, el, search, replace, text_only ) {
}
/**
Annotate text using a list of GlossaryEntry
@param {List<GlossaryEntry>}
@param {String} glossaryFilePath
@param {HTMLDom} $
*/
* Annotate text using a list of GlossaryEntry
*
* @param {List<GlossaryEntry>}
* @param {String} glossaryFilePath
* @param {HTMLDom} $
*/
function annotateText(entries, glossaryFilePath, $) {
entries.forEach(function(entry) {
var entryId = entry.getID();
var name = entry.getName();
var entryId = entry.getID();
var name = entry.getName();
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() {
var $this = $(this);
+1 -1
View File
@@ -24,7 +24,7 @@ function resolveLinks(currentFile, resolveFile, $) {
}
if (LocationUtils.isExternal(href)) {
$a.attr('_target', 'blank');
$a.attr('target', '_blank');
return;
}
+5 -5
View File
@@ -2,11 +2,11 @@ var Plugins = require('../plugins');
var Promise = require('../utils/promise');
/**
Load and setup plugins
@param {Output}
@return {Promise<Output>}
*/
* Load and setup plugins
*
* @param {Output}
* @return {Promise<Output>}
*/
function preparePlugins(output) {
var book = output.getBook();
+38
View File
@@ -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');
});
});
});
+27 -31
View File
@@ -18,19 +18,19 @@ var fileToURL = require('../helper/fileToURL');
var resolveFileToURL = require('../helper/resolveFileToURL');
/**
Directory for a theme with the templates
*/
* Directory for a theme with the templates
*/
function templateFolder(dir) {
return path.join(dir, templatesFolder);
}
/**
Create templating engine to render themes
@param {Output} output
@param {String} currentFile
@return {TemplateEngine}
*/
* Create templating engine to render themes
*
* @param {Output} output
* @param {String} currentFile
* @return {TemplateEngine}
*/
function createTemplateEngine(output, currentFile) {
var book = output.getBook();
var state = output.getState();
@@ -47,18 +47,17 @@ function createTemplateEngine(output, currentFile) {
var loader = new Templating.ThemesLoader(tplSearchPaths);
// Get languages
var language = config.get('language');
var language = config.getValue('language');
// Create API context
var context = Api.encodeGlobal(output);
/**
Check if a file exists
@param {String} fileName
@return {Boolean}
*/
* Check if a file exists
* @param {String} fileName
* @return {Boolean}
*/
function fileExists(fileName) {
if (!fileName) {
return false;
@@ -69,11 +68,10 @@ function createTemplateEngine(output, currentFile) {
}
/**
Return an article by its path
@param {String} filePath
@return {Object|undefined}
*/
* Return an article by its path
* @param {String} filePath
* @return {Object|undefined}
*/
function getArticleByPath(filePath) {
var article = summary.getByPath(filePath);
if (!article) return undefined;
@@ -82,11 +80,10 @@ function createTemplateEngine(output, currentFile) {
}
/**
Return a page by its path
@param {String} filePath
@return {Object|undefined}
*/
* Return a page by its path
* @param {String} filePath
* @return {Object|undefined}
*/
function getPageByPath(filePath) {
var page = output.getPage(filePath);
if (!page) return undefined;
@@ -94,7 +91,6 @@ function createTemplateEngine(output, currentFile) {
return JSONUtils.encodePage(page, summary);
}
return TemplateEngine.create({
loader: loader,
@@ -108,17 +104,17 @@ function createTemplateEngine(output, currentFile) {
filters: defaultFilters.merge({
/**
Translate a sentence
*/
* Translate a sentence
*/
t: function t(s) {
return i18n.t(language, s);
},
/**
Resolve an absolute file path into a
relative path.
it also resolve pages
*/
* Resolve an absolute file path into a
* relative path.
* it also resolve pages
*/
resolveFile: function(filePath) {
filePath = resolveFileToURL(output, filePath);
return LocationUtils.relativeForFile(currentFile, filePath);
+6 -6
View File
@@ -12,11 +12,11 @@ var createTemplateEngine = require('./createTemplateEngine');
var fileToOutput = require('../helper/fileToOutput');
/**
Write a page as a json file
@param {Output} output
@param {Page} page
*/
* Write a page as a json file
*
* @param {Output} output
* @param {Page} page
*/
function onPage(output, page) {
var options = output.getOptions();
var prefix = options.get('prefix');
@@ -40,7 +40,7 @@ function onPage(output, page) {
return Modifiers.modifyHTML(page, getModifiers(output, page))
.then(function(resultPage) {
// Generate the context
var context = JSONUtils.encodeBookWithPage(output.getBook(), resultPage);
var context = JSONUtils.encodeOutputWithPage(output, resultPage);
context.plugins = {
resources: Plugins.listResources(plugins, resources).toJS()
};
+5 -5
View File
@@ -5,11 +5,11 @@ var Promise = require('../../utils/promise');
var listSearchPaths = require('./listSearchPaths');
/**
Prepare i18n, load translations from plugins and book
@param {Output}
@return {Promise<Output>}
*/
* Prepare i18n, load translations from plugins and book
*
* @param {Output}
* @return {Promise<Output>}
*/
function prepareI18n(output) {
var state = output.getState();
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
View File
@@ -1,14 +1,15 @@
module.exports = {
parseBook: require('./parseBook'),
parseSummary: require('./parseSummary'),
parseGlossary: require('./parseGlossary'),
parseReadme: require('./parseReadme'),
parseConfig: require('./parseConfig'),
parsePagesList: require('./parsePagesList'),
parseIgnore: require('./parseIgnore'),
listAssets: require('./listAssets'),
parseLanguages: require('./parseLanguages'),
parsePage: require('./parsePage'),
lookupStructureFile: require('./lookupStructureFile')
parseBook: require('./parseBook'),
parseSummary: require('./parseSummary'),
parseGlossary: require('./parseGlossary'),
parseReadme: require('./parseReadme'),
parseConfig: require('./parseConfig'),
parsePagesList: require('./parsePagesList'),
parseIgnore: require('./parseIgnore'),
listAssets: require('./listAssets'),
parseLanguages: require('./parseLanguages'),
parsePage: require('./parsePage'),
parsePageFromString: require('./parsePageFromString'),
lookupStructureFile: require('./lookupStructureFile')
};
+1 -1
View File
@@ -6,7 +6,7 @@ var findParsableFile = require('./findParsableFile');
@param {Book} book
@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.
*/
function lookupStructureFile(book, type) {
+8 -16
View File
@@ -1,27 +1,19 @@
var Immutable = require('immutable');
var fm = require('front-matter');
var direction = require('direction');
var parsePageFromString = require('./parsePageFromString');
/**
Parse a page, read its content and parse the YAMl header
@param {Book} book
@param {Page} page
@return {Promise<Page>}
*/
* Parse a page, read its content and parse the YAMl header
*
* @param {Book} book
* @param {Page} page
* @return {Promise<Page>}
*/
function parsePage(book, page) {
var fs = book.getContentFS();
var file = page.getFile();
return fs.readAsString(file.getPath())
.then(function(content) {
var parsed = fm(content);
return page.merge({
content: parsed.body,
attributes: Immutable.fromJS(parsed.attributes),
dir: direction(parsed.body)
});
return parsePageFromString(page, content);
});
}
+22
View File
@@ -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;
+1 -1
View File
@@ -1,9 +1,9 @@
var jsonschema = require('jsonschema');
var jsonSchemaDefaults = require('json-schema-defaults');
var mergeDefaults = require('merge-defaults');
var schema = require('../constants/configSchema');
var error = require('../utils/error');
var mergeDefaults = require('../utils/mergeDefaults');
/**
Validate a book.json content
+19 -17
View File
@@ -4,20 +4,22 @@ var Immutable = require('immutable');
var markdownParser = require('gitbook-markdown');
var asciidocParser = require('gitbook-asciidoc');
var EXTENSIONS_MARKDOWN = require('./constants/extsMarkdown');
var EXTENSIONS_ASCIIDOC = require('./constants/extsAsciidoc');
var Parser = require('./models/parser');
// This list is ordered by priority of parsers to use
var parsers = Immutable.List([
Parser.create('markdown', ['.md', '.markdown', '.mdown'], markdownParser),
Parser.create('asciidoc', ['.adoc', '.asciidoc'], asciidocParser)
Parser.create('markdown', EXTENSIONS_MARKDOWN, markdownParser),
Parser.create('asciidoc', EXTENSIONS_ASCIIDOC, asciidocParser)
]);
/**
Return a specific parser by its name
@param {String} name
@return {Parser|undefined}
*/
* Return a specific parser by its name
*
* @param {String} name
* @return {Parser|undefined}
*/
function getParser(name) {
return parsers.find(function(parser) {
return parser.getName() === name;
@@ -25,11 +27,11 @@ function getParser(name) {
}
/**
Return a specific parser according to an extension
@param {String} ext
@return {Parser|undefined}
*/
* Return a specific parser according to an extension
*
* @param {String} ext
* @return {Parser|undefined}
*/
function getParserByExt(ext) {
return parsers.find(function(parser) {
return parser.matchExtension(ext);
@@ -37,11 +39,11 @@ function getParserByExt(ext) {
}
/**
Return parser for a file
@param {String} ext
@return {Parser|undefined}
*/
* Return parser for a file
*
* @param {String} ext
* @return {Parser|undefined}
*/
function getParserForFile(filename) {
return getParserByExt(path.extname(filename));
}
+19
View File
@@ -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();
});
});
});
-36
View File
@@ -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);
});
});
+38
View File
@@ -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' ]);
});
});
+10
View File
@@ -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, '../../../')
);
});
});
+42
View File
@@ -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'
]);
});
});
-70
View File
@@ -1,70 +0,0 @@
var PluginDependency = require('../../models/pluginDependency');
var sortPlugins = require('../sortPlugins');
var listAll = require('../listAll');
var THEME_PREFIX = require('../../constants/themePrefix');
/**
Check if a plugin is a theme given its name
@return {Boolean}
*/
function isTheme(name) {
return (name && name.indexOf(THEME_PREFIX) === 0);
}
describe('sortPlugins', function() {
it('must load themes after plugins', function() {
var deps = PluginDependency.listFromString('theme-faq'),
allPlugins = listAll(deps);
return sortPlugins(allPlugins, [])
.then(function(sorted) {
var plugins = sorted.slice(0, -2),
themes = sorted.slice(-2);
var pluginsOk = plugins.every(function(plugin) {
return !isTheme(plugin.getName());
});
var themesOk = themes.every(function(theme) {
return isTheme(theme.getName());
});
expect(pluginsOk).toBe(true);
expect(plugins.has('search')).toBe(true);
expect(themesOk).toBe(true);
expect(themes.size).toBe(2);
expect(themes.has('theme-faq')).toBe(true);
expect(themes.has('theme-default')).toBe(true);
});
});
it('must load themes after plugins with a complex dependencies list', function() {
var deps = PluginDependency.listFromString('comment,theme-faq,-search,ga'),
allPlugins = listAll(deps);
return sortPlugins(allPlugins, [])
.then(function(sorted) {
var plugins = sorted.slice(0, -2),
themes = sorted.slice(-2);
var pluginsOk = plugins.every(function(plugin) {
return !isTheme(plugin.getName());
});
var themesOk = themes.every(function(theme) {
return isTheme(theme.getName());
});
expect(pluginsOk).toBe(true);
expect(plugins.has('search')).toBe(false);
expect(themesOk).toBe(true);
expect(themes.size).toBe(2);
expect(themes.has('theme-faq')).toBe(true);
expect(themes.has('theme-default')).toBe(true);
});
});
});
+7 -7
View File
@@ -1,22 +1,22 @@
var path = require('path');
var Immutable = require('immutable');
var Promise = require('../utils/promise');
var timing = require('../utils/timing');
var findInstalled = require('./findInstalled');
var locateRootFolder = require('./locateRootFolder');
/**
List all plugins installed in a book
@param {Book}
@return {Promise<OrderedMap<String:Plugin>>}
*/
* List all plugins installed in a book
*
* @param {Book}
* @return {Promise<OrderedMap<String:Plugin>>}
*/
function findForBook(book) {
return timing.measure(
'plugins.findForBook',
Promise.all([
findInstalled(path.resolve(__dirname, '../..')),
findInstalled(locateRootFolder()),
findInstalled(book.getRoot())
])
+15 -14
View File
@@ -8,21 +8,21 @@ var Plugin = require('../models/plugin');
var PREFIX = require('../constants/pluginPrefix');
/**
Validate if a package name is a GitBook plugin
@return {Boolean}
*/
* Validate if a package name is a GitBook plugin
*
* @return {Boolean}
*/
function validateId(name) {
return name && name.indexOf(PREFIX) === 0;
}
/**
List all packages installed inside a folder
@param {String} folder
@return {OrderedMap<String:Plugin>}
*/
* List all packages installed inside a folder
*
* @param {String} folder
* @return {OrderedMap<String:Plugin>}
*/
function findInstalled(folder) {
var options = {
dev: false,
@@ -31,7 +31,7 @@ function findInstalled(folder) {
};
var results = Immutable.OrderedMap();
function onPackage(pkg, isRoot) {
function onPackage(pkg, parent) {
if (!pkg.name) return;
var name = pkg.name;
@@ -43,18 +43,19 @@ function findInstalled(folder) {
var pluginName = name.slice(PREFIX.length);
if (!validateId(name)){
if (!isRoot) return;
if (parent) return;
} else {
results = results.set(pluginName, Plugin({
name: pluginName,
version: version,
path: pkgPath,
depth: depth
depth: depth,
parent: parent
}));
}
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);
return Promise.nfcall(readInstalled, module_folder, options)
.then(function(data) {
onPackage(data, true);
onPackage(data);
});
});
})
+6 -4
View File
@@ -1,11 +1,9 @@
var npmi = require('npmi');
var pkg = require('../../package.json');
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
var Promise = require('../utils/promise');
var installPlugin = require('./installPlugin');
/**
Install plugin requirements for a book
@@ -20,13 +18,17 @@ function installPlugins(book) {
// Remove default plugins
// (only if version is same as installed)
plugins = plugins.filterNot(function(plugin) {
var dependency = DEFAULT_PLUGINS.find(function(dep) {
return dep.getName() === plugin.getName();
});
return (
// Disabled plugin
!plugin.isEnabled() ||
// Or default one installed in GitBook itself
(DEFAULT_PLUGINS.includes(plugin.getName()) &&
plugin.getVersion() === pkg.dependencies[plugin.getNpmID()])
(dependency &&
plugin.getVersion() === dependency.getVersion())
);
});
-50
View File
@@ -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;
+33
View File
@@ -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;
+18
View File
@@ -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;
-18
View File
@@ -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;
+48 -31
View File
@@ -1,52 +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 loadPlugin = require('./loadPlugin');
var sortPlugins = require('./sortPlugins');
/**
Load a list of plugins in a book
@param {Book}
@return {Promise<Map<String:Plugin>}
*/
* Load all plugins in a book
*
* @param {Book}
* @return {Promise<Map<String:Plugin>}
*/
function loadForBook(book) {
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)
.then(function(installed) {
// Filter out plugins not listed of first level
// (aka pre-installed plugins)
installed = installed.filter(function(plugin) {
return (
plugin.getDepth() > 0 ||
requirements.has(plugin.getName())
);
});
.then(function(installedMap) {
var missing = [];
var plugins = requirements.reduce(function(result, dep) {
var name = dep.getName();
var installed = installedMap.get(name);
if (installed) {
var deps = installedMap
.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);
return sortPlugins(installed, requirementsKeys);
})
.then(function(installed) {
// Log state
logger.info.ln(installed.size + ' plugins are installed');
if (requirements.size != installed.size) {
logger.info.ln(installedMap.size + ' plugins are installed');
if (requirements.size != installedMap.size) {
logger.info.ln(requirements.size + ' explicitly listed');
}
// Verify that all plugins are present
var notInstalled = requirementsKeys.filter(function(name) {
return !installed.has(name);
});
if (notInstalled.size > 0) {
throw new Error('Couldn\'t locate plugins "' + notInstalled.join(', ') + '", Run \'gitbook install\' to install plugins from registry.');
if (missing.length > 0) {
throw new Error('Couldn\'t locate plugins "' + missing.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);
});
});
+22
View File
@@ -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;
+34
View File
@@ -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;
-45
View File
@@ -1,45 +0,0 @@
var Promise = require('../utils/promise');
var THEME_PREFIX = require('../constants/themePrefix');
var LOADING_ORDER = ['plugin', 'theme'];
/**
Returns the type of a plugin given its name
@return {String}
*/
function pluginType(name) {
return (name && name.indexOf(THEME_PREFIX) === 0) ? 'theme' : 'plugin';
}
/**
Sort the list of installed plugins to match list in book.json
The themes should always be loaded after the plugins
@param {<OrderedMap<String:Plugin>>} plugins
@param {<List<String>>} requirementsKeys
@return {Promise<OrderedMap<String:Plugin>>}
*/
function sortPlugins(plugins, requirementsKeys) {
return Promise()
.then(function() {
// Sort plugins to match list in book.json
plugins = plugins.sort(function(a, b) {
// Get order from book.json
var definitionOrder = requirementsKeys.indexOf(a.getName()) < requirementsKeys.indexOf(b.getName());
// Get order from plugins a and b type
var aType = pluginType(a.getName()),
bType = pluginType(b.getName()),
loadingOrder = LOADING_ORDER.indexOf(aType) < LOADING_ORDER.indexOf(bType);
return loadingOrder || definitionOrder ? -1 : 1;
});
return plugins;
});
}
module.exports = sortPlugins;
+16
View File
@@ -0,0 +1,16 @@
/**
* Return list of plugin names. This method is nly used in unit tests.
*
* @param {OrderedMap<String:Plugin} plugins
* @return {Array<String>}
*/
function toNames(plugins) {
return plugins
.map(function(plugin) {
return plugin.getName();
})
.toArray();
}
module.exports = toNames;
+1 -1
View File
@@ -1,10 +1,10 @@
var Immutable = require('immutable');
var jsonschema = require('jsonschema');
var jsonSchemaDefaults = require('json-schema-defaults');
var mergeDefaults = require('merge-defaults');
var Promise = require('../utils/promise');
var error = require('../utils/error');
var mergeDefaults = require('../utils/mergeDefaults');
/**
Validate one plugin for a book and update book's confiration
@@ -75,6 +75,12 @@ describe('ConrefsLoader', function() {
describe('transform', function() {
function transform(filePath, source) {
expect(filePath).toBeA('string');
expect(source).toBeA('string');
expect(filePath).toBe(path.resolve(__dirname, 'include.md'));
expect(source).toBe('Hello World');
return 'test-' + source + '-endtest';
}
var engine = TemplateEngine({
+12 -11
View File
@@ -8,16 +8,15 @@ var PathUtils = require('../utils/path');
/**
Template loader resolving both:
- relative url ("./test.md")
- absolute url ("/test.md")
- git url ("")
@param {String} rootFolder
@param {Function(filePath, source)} transformFn (optional)
@param {Logger} logger (optional)
*/
* Template loader resolving both:
* - relative url ("./test.md")
* - absolute url ("/test.md")
* - git url ("")
*
* @param {String} rootFolder
* @param {Function(filePath, source)} transformFn (optional)
* @param {Logger} logger (optional)
*/
var ConrefsLoader = nunjucks.Loader.extend({
async: true,
@@ -43,6 +42,8 @@ var ConrefsLoader = nunjucks.Loader.extend({
// Read file from absolute path
return fs.readFile(filepath)
.then(function(source) {
source = source.toString('utf8');
if (that.transformFn) {
return that.transformFn(filepath, source);
}
@@ -51,7 +52,7 @@ var ConrefsLoader = nunjucks.Loader.extend({
})
.then(function(source) {
return {
src: source.toString('utf8'),
src: source,
path: filepath
};
});
+7 -7
View File
@@ -2,13 +2,13 @@ var Immutable = require('immutable');
var parsers = require('../parsers');
/**
Return a list of all shortcuts that can apply
to a file for a TemplatEngine
@param {List<TemplateBlock>} engine
@param {String} filePath
@return {List<TemplateShortcut>}
*/
* Return a list of all shortcuts that can apply
* to a file for a TemplatEngine
*
* @param {List<TemplateBlock>} engine
* @param {String} filePath
* @return {List<TemplateShortcut>}
*/
function listShortcuts(blocks, filePath) {
var parser = parsers.getForFile(filePath);
+7 -7
View File
@@ -3,13 +3,13 @@ var error = require('../utils/error');
var render = require('./render');
/**
Render a template
@param {TemplateEngine} engine
@param {String} filePath
@param {Object} context
@return {Promise<TemplateOutput>}
*/
* Render a template
*
* @param {TemplateEngine} engine
* @param {String} filePath
* @param {Object} context
* @return {Promise<TemplateOutput>}
*/
function renderTemplateFile(engine, filePath, context) {
var loader = engine.getLoader();
+13 -13
View File
@@ -1,12 +1,12 @@
var escapeStringRegexp = require('escape-string-regexp');
var listShortcuts = require('./listShortcuts');
/*
Apply a shortcut of block to a template
@param {String} content
@param {Shortcut} shortcut
@return {String}
*/
/**
* Apply a shortcut of block to a template
* @param {String} content
* @param {Shortcut} shortcut
* @return {String}
*/
function applyShortcut(content, shortcut) {
var start = shortcut.getStart();
var end = shortcut.getEnd();
@@ -24,13 +24,13 @@ function applyShortcut(content, shortcut) {
}
/**
Replace shortcuts from blocks in a string
@param {List<TemplateBlock>} engine
@param {String} filePath
@param {String} content
@return {String}
*/
* Replace shortcuts from blocks in a string
*
* @param {List<TemplateBlock>} engine
* @param {String} filePath
* @param {String} content
* @return {String}
*/
function replaceShortcuts(blocks, filePath, content) {
var shortcuts = listShortcuts(blocks, filePath);
return shortcuts.reduce(applyShortcut, content);
+25 -25
View File
@@ -12,11 +12,11 @@ var ThemesLoader = nunjucks.Loader.extend({
.map(path.normalize);
},
/*
Read source of a resolved filepath
@param {String}
@return {Object}
*/
/**
* Read source of a resolved filepath
* @param {String}
* @return {Object}
*/
getSource: function(fullpath) {
if (!fullpath) return null;
@@ -40,19 +40,19 @@ var ThemesLoader = nunjucks.Loader.extend({
};
},
/*
Nunjucks calls "isRelative" to determine when to call "resolve".
We handle absolute paths ourselves in ".resolve" so we always return true
*/
/**
* Nunjucks calls "isRelative" to determine when to call "resolve".
* We handle absolute paths ourselves in ".resolve" so we always return true
*/
isRelative: function() {
return true;
},
/*
Get original search path containing a template
@param {String} filepath
@return {String} searchPath
*/
/**
* Get original search path containing a template
* @param {String} filepath
* @return {String} searchPath
*/
getSearchPath: function(filepath) {
return this.searchPaths
.sortBy(function(s) {
@@ -63,22 +63,22 @@ var ThemesLoader = nunjucks.Loader.extend({
});
},
/*
Get template name from a filepath
@param {String} filepath
@return {String} name
*/
/**
* Get template name from a filepath
* @param {String} filepath
* @return {String} name
*/
getTemplateName: function(filepath) {
var originalSearchPath = this.getSearchPath(filepath);
return originalSearchPath? path.relative(originalSearchPath, filepath) : null;
},
/*
Resolve a template from a current template
@param {String|null} from
@param {String} to
@return {String|null}
*/
/**
* Resolve a template from a current template
* @param {String|null} from
* @param {String} to
* @return {String|null}
*/
resolve: function(from, to) {
var searchPaths = this.searchPaths;
+15
View File
@@ -39,6 +39,21 @@ describe('LocationUtils', function() {
});
});
describe('.flatten', function() {
it('should remove leading slash', function() {
expect(LocationUtils.flatten('/test.md')).toBe('test.md');
expect(LocationUtils.flatten('/hello/cool.md')).toBe('hello/cool.md');
});
it('should remove leading slashes', function() {
expect(LocationUtils.flatten('///test.md')).toBe('test.md');
});
it('should not break paths', function() {
expect(LocationUtils.flatten('hello/cool.md')).toBe('hello/cool.md');
});
});
describe('.toAbsolute', function() {
it('should correctly transform as absolute', function() {
expect(LocationUtils.toAbsolute('http://google.fr')).toBe('http://google.fr');
+51 -35
View File
@@ -40,13 +40,28 @@ function normalize(s) {
}
/**
Convert a relative path to absolute
* Flatten a path, it removes the leading "/"
*
* @param {String} href
* @return {String}
*/
function flatten(href) {
href = normalize(href);
if (href[0] == '/') {
href = normalize(href.slice(1));
}
@param {String} href
@param {String} dir: directory parent of the file currently in rendering process
@param {String} outdir: directory parent from the html output
@return {String}
*/
return href;
}
/**
* Convert a relative path to absolute
*
* @param {String} href
* @param {String} dir: directory parent of the file currently in rendering process
* @param {String} outdir: directory parent from the html output
* @return {String}
*/
function toAbsolute(_href, dir, outdir) {
if (isExternal(_href) || isDataURI(_href)) {
return _href;
@@ -74,50 +89,51 @@ function toAbsolute(_href, dir, outdir) {
}
/**
Convert an absolute path to a relative path for a specific folder (dir)
('test/', 'hello.md') -> '../hello.md'
@param {String} dir: current directory
@param {String} file: absolute path of file
@return {String}
*/
* Convert an absolute path to a relative path for a specific folder (dir)
* ('test/', 'hello.md') -> '../hello.md'
*
* @param {String} dir: current directory
* @param {String} file: absolute path of file
* @return {String}
*/
function relative(dir, file) {
var isDirectory = file.slice(-1) === '/';
return normalize(path.relative(dir, file)) + (isDirectory? '/': '');
}
/**
Convert an absolute path to a relative path for a specific folder (dir)
('test/test.md', 'hello.md') -> '../hello.md'
@param {String} baseFile: current file
@param {String} file: absolute path of file
@return {String}
*/
* Convert an absolute path to a relative path for a specific folder (dir)
* ('test/test.md', 'hello.md') -> '../hello.md'
*
* @param {String} baseFile: current file
* @param {String} file: absolute path of file
* @return {String}
*/
function relativeForFile(baseFile, file) {
return relative(path.dirname(baseFile), file);
}
/**
Compare two paths, return true if they are identical
('README.md', './README.md') -> true
@param {String} p1: first path
@param {String} p2: second path
@return {Boolean}
*/
* Compare two paths, return true if they are identical
* ('README.md', './README.md') -> true
*
* @param {String} p1: first path
* @param {String} p2: second path
* @return {Boolean}
*/
function areIdenticalPaths(p1, p2) {
return normalize(p1) === normalize(p2);
}
module.exports = {
areIdenticalPaths: areIdenticalPaths,
isDataURI: isDataURI,
isExternal: isExternal,
isRelative: isRelative,
isAnchor: isAnchor,
normalize: normalize,
toAbsolute: toAbsolute,
relative: relative,
relativeForFile: relativeForFile
isDataURI: isDataURI,
isExternal: isExternal,
isRelative: isRelative,
isAnchor: isAnchor,
normalize: normalize,
toAbsolute: toAbsolute,
relative: relative,
relativeForFile: relativeForFile,
flatten: flatten
};
+16
View File
@@ -0,0 +1,16 @@
var Immutable = require('immutable');
/**
* Merge
* @param {Object|Map} obj
* @param {Object|Map} src
* @return {Object}
*/
function mergeDefaults(obj, src) {
var objValue = Immutable.fromJS(obj);
var srcValue = Immutable.fromJS(src);
return srcValue.mergeDeep(objValue).toJS();
}
module.exports = mergeDefaults;
+44 -43
View File
@@ -2,34 +2,35 @@ var Q = require('q');
var Immutable = require('immutable');
// Debugging for long stack traces
if (global.__DEV__ || process.env.DEBUG) {
if (process.env.DEBUG || process.env.CI) {
Q.longStackSupport = true;
}
/**
Reduce an array to a promise
@param {Array|List} arr
@param {Function(value, element, index)}
@return {Promise<Mixed>}
*/
* Reduce an array to a promise
*
* @param {Array|List} arr
* @param {Function(value, element, index)}
* @return {Promise<Mixed>}
*/
function reduce(arr, iter, base) {
arr = Immutable.Iterable.isIterable(arr)? arr : Immutable.List(arr);
return arr.reduce(function(prev, elem, key) {
return prev.then(function(val) {
return prev
.then(function(val) {
return iter(val, elem, key);
});
}, Q(base));
}
/**
Iterate over an array using an async iter
@param {Array|List} arr
@param {Function(value, element, index)}
@return {Promise}
*/
* Iterate over an array using an async iter
*
* @param {Array|List} arr
* @param {Function(value, element, index)}
* @return {Promise}
*/
function forEach(arr, iter) {
return reduce(arr, function(val, el, key) {
return iter(el, key);
@@ -37,12 +38,12 @@ function forEach(arr, iter) {
}
/**
Transform an array
@param {Array|List} arr
@param {Function(value, element, index)}
@return {Promise}
*/
* Transform an array
*
* @param {Array|List} arr
* @param {Function(value, element, index)}
* @return {Promise}
*/
function serie(arr, iter, base) {
return reduce(arr, function(before, item, key) {
return Q(iter(item, key))
@@ -54,12 +55,12 @@ function serie(arr, iter, base) {
}
/**
Iter over an array and return first result (not null)
@param {Array|List} arr
@param {Function(element, index)}
@return {Promise<Mixed>}
*/
* Iter over an array and return first result (not null)
*
* @param {Array|List} arr
* @param {Function(element, index)}
* @return {Promise<Mixed>}
*/
function some(arr, iter) {
arr = Immutable.List(arr);
@@ -73,12 +74,12 @@ function some(arr, iter) {
}
/**
Map an array using an async (promised) iterator
@param {Array|List} arr
@param {Function(element, index)}
@return {Promise<List>}
*/
* Map an array using an async (promised) iterator
*
* @param {Array|List} arr
* @param {Function(element, index)}
* @return {Promise<List>}
*/
function mapAsList(arr, iter) {
return reduce(arr, function(prev, entry, i) {
return Q(iter(entry, i))
@@ -90,12 +91,12 @@ function mapAsList(arr, iter) {
}
/**
Map an array or map
@param {Array|List|Map|OrderedMap} arr
@param {Function(element, key)}
@return {Promise<List|Map|OrderedMap>}
*/
* Map an array or map
*
* @param {Array|List|Map|OrderedMap} arr
* @param {Function(element, key)}
* @return {Promise<List|Map|OrderedMap>}
*/
function map(arr, iter) {
if (Immutable.Map.isMap(arr)) {
var type = 'Map';
@@ -122,11 +123,11 @@ function map(arr, iter) {
/**
Wrap a function in a promise
@param {Function} func
@return {Funciton}
*/
* Wrap a function in a promise
*
* @param {Function} func
* @return {Funciton}
*/
function wrap(func) {
return function() {
var args = Array.prototype.slice.call(arguments, 0);
+33
View File
@@ -0,0 +1,33 @@
var Immutable = require('immutable');
/**
* Reduce the difference between a map and its default version
* @param {Map} defaultVersion
* @param {Map} currentVersion
* @return {Map} The properties of currentVersion that differs from defaultVersion
*/
function reducedObject(defaultVersion, currentVersion) {
if(defaultVersion === undefined) {
return currentVersion;
}
return currentVersion.reduce(function(result, value, key) {
var defaultValue = defaultVersion.get(key);
if (Immutable.Map.isMap(value)) {
var diffs = reducedObject(defaultValue, value);
if (diffs.size > 0) {
return result.set(key, diffs);
}
}
if (Immutable.is(defaultValue, value)) {
return result;
}
return result.set(key, value);
}, Immutable.Map());
}
module.exports = reducedObject;
+10 -9
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "3.1.0",
"version": "3.2.0",
"homepage": "https://www.gitbook.com",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
@@ -20,26 +20,26 @@
"escape-string-regexp": "1.0.5",
"extend": "^3.0.0",
"fresh-require": "1.0.3",
"front-matter": "2.0.8",
"gitbook-asciidoc": "1.2.0",
"gitbook-markdown": "1.3.0",
"front-matter": "^2.1.0",
"gitbook-asciidoc": "1.2.2",
"gitbook-markdown": "2.0.1",
"gitbook-plugin-fontsettings": "2.0.0",
"gitbook-plugin-highlight": "2.0.2",
"gitbook-plugin-livereload": "0.0.1",
"gitbook-plugin-lunr": "1.1.0",
"gitbook-plugin-lunr": "1.2.0",
"gitbook-plugin-search": "2.2.1",
"gitbook-plugin-sharing": "1.0.2",
"gitbook-plugin-theme-default": "1.0.4",
"gitbook-plugin-theme-default": "1.0.5",
"github-slugid": "1.0.1",
"graceful-fs": "4.1.4",
"i18n-t": "1.0.0",
"i18n-t": "1.0.1",
"ignore": "3.1.2",
"immutable": "^3.8.1",
"is": "^3.1.0",
"js-yaml": "^3.6.1",
"json-schema-defaults": "0.1.1",
"jsonschema": "1.1.0",
"juice": "1.11.0",
"merge-defaults": "0.2.1",
"juice": "2.0.0",
"mkdirp": "0.5.1",
"moment": "2.13.0",
"npm": "3.9.2",
@@ -48,6 +48,7 @@
"nunjucks-do": "1.0.0",
"object-path": "^0.9.2",
"omit-keys": "^0.1.0",
"open": "0.0.5",
"q": "1.4.1",
"read-installed": "^4.0.3",
"request": "2.72.0",
+19 -9
View File
@@ -6,10 +6,9 @@ var cheerio = require('cheerio');
expect.extend({
/**
Check that a file is created in a directory:
expect('myFolder').toHaveFile('hello.md');
*/
* Check that a file is created in a directory:
* expect('myFolder').toHaveFile('hello.md');
*/
toHaveFile: function(fileName) {
var filePath = path.join(this.actual, fileName);
var exists = fs.existsSync(filePath);
@@ -36,8 +35,8 @@ expect.extend({
},
/**
Check that a value is defined (not null nor undefined)
*/
* Check that a value is defined (not null nor undefined)
*/
toBeDefined: function() {
expect.assert(
!(is.undefined(this.actual) || is.null(this.actual)),
@@ -47,10 +46,21 @@ expect.extend({
},
/**
Check that a dom element exists in HTML
* Check that a value is defined (not null nor undefined)
*/
toNotBeDefined: function() {
expect.assert(
(is.undefined(this.actual) || is.null(this.actual)),
'expected %s to be not defined',
this.actual
);
return this;
},
@param {String} selector
*/
/**
* Check that a dom element exists in HTML
* @param {String} selector
*/
toHaveDOMElement: function(selector) {
var $ = cheerio.load(this.actual);
var $el = $(selector);