Compare commits

...

37 Commits

Author SHA1 Message Date
Samy Pesse 257158321c Bump version to 3.1.0 2016-06-06 19:51:44 +02:00
Samy Pesse ddf0a41e29 Add method editPlugin to config modifiers 2016-06-06 19:42:44 +02:00
Samy Pesse 357e1e8e96 Add unit tests for postRender 2016-06-06 19:24:12 +02:00
Samy Pesse 7a89f9869a Update gitbook-markdown@1.3.0 and gitbook-asciidoc@1.2.0 2016-06-06 18:07:31 +02:00
Samy Pesse 30fbd22efe Add modifier togglePlugin 2016-06-06 17:27:47 +02:00
Samy Pesse 29df73ba72 Add method "toText" to config 2016-06-06 17:16:53 +02:00
Soreine f8676483c1 Default paths are relative to the current book 2016-06-06 16:55:00 +02:00
Johan Preynat c79a6f2fba Use theme-default@1.0.4 2016-06-06 15:46:44 +02:00
Samy Pessé c38e67ea61 Merge pull request #1344 from GitbookIO/fix/readme-in-summary
Search for README file in whole summary before inserting as first article
2016-06-06 12:18:06 +02:00
Johan Preynat 97a5f8805e Use plugin-fontsettings@2.0.0 2016-06-02 10:24:12 +02:00
Johan Preynat 421fef6ebf Search for README file in whole summary before inserting as first article 2016-05-30 15:07:39 +02:00
Johan Preynat 33406c3082 Merge pull request #1336 from GitbookIO/loading/themes
Load plugins before themes
2016-05-28 11:56:34 +02:00
Johan Preynat c3851889b0 Throw an error if generateMock() cannot create the temp directory 2016-05-27 17:15:23 +02:00
Johan Preynat 4a5de2da6b Use sortPlugins() in loadForBook() 2016-05-27 15:06:20 +02:00
Johan Preynat 1afb23cada Add test file for sortPlugins() 2016-05-27 15:06:08 +02:00
Johan Preynat a3b0f3f868 Export plugins sorting method to load plugins before themes 2016-05-27 15:05:55 +02:00
Johan Preynat 0aa32916d6 Add prefix for theme plugins as constant 2016-05-27 15:05:33 +02:00
Samy Pessé ebe845d46c Bump version to 3.0.3 2016-05-27 12:16:56 +02:00
Samy Pessé edee8d2fc5 Update gitbook-theme-default@1.0.3 2016-05-27 12:07:35 +02:00
Samy Pessé 5f3de0876f Add header 'X-Current-Location' to serve and fix handling of directories 2016-05-27 12:01:28 +02:00
Samy Pessé 2ceab04d01 Increase timeout for tests of plugin installation 2016-05-27 11:40:05 +02:00
Samy Pessé de37de44e8 Add tests for installation of all plugins 2016-05-27 11:18:12 +02:00
Samy Pessé ccb87d5a13 Add unit tests for installPlugin 2016-05-27 11:10:39 +02:00
Samy Pessé e2e0ca3f05 Add test for plugin version resolution 2016-05-27 10:54:56 +02:00
Samy Pessé b33a79704b Switch plugin installation and resolveVersion to use PluginDependency instead of Plugin 2016-05-27 09:38:23 +02:00
Samy Pessé 6def174b84 Merge pull request #1334 from ryanswanson/master
Fixed two issues for handling git URLs for plugins
2016-05-27 09:27:11 +02:00
Samy Pessé 346a18b444 Merge pull request #1335 from GitbookIO/fix/1333
Fix #1333: fix invalid URLs for glossary annotations
2016-05-26 22:30:06 +02:00
Samy Pesse b32685c698 Add test for custom glossary file 2016-05-26 22:01:40 +02:00
Samy Pesse 09b3618da8 Annotate glossary terms before resolving links 2016-05-26 21:38:27 +02:00
Samy Pesse 901328f247 Complete basic tests for glossary 2016-05-26 21:37:10 +02:00
Samy Pesse b47ebaa9d9 Use real glossary name when annotating 2016-05-26 21:10:06 +02:00
Ryan Swanson 465e8d6c0a Fixed two issues for handling git URLs for plugins that are published to git repositories rather than on npmjs.org.
Fixed issue in pluginDependency to dereference 'name' and 'version' using Immutable Map.get(...) accessors since config values are now wrapped by Immutable.fromJS(...) in config.js > setValue(...).  Added associated unit test.

Fixed issue in resolveVersion where a plugin may be using a git URL rather than a semver for the version portion of the plugin config definition.  In addition, refactored the resolveVersion function into a new module to allow for unit testing.  Added associated unit test.
2016-05-26 14:30:29 -04:00
Samy Pessé ecd86ec877 Method for parsing glossary when not listed in summary 2016-05-26 17:26:11 +02:00
Samy Pessé 5d7864154c Start test for glossary generation 2016-05-26 17:25:49 +02:00
Samy Pessé 7b915428f7 Fix #1316: fix regression for supporting uppercase filenames in structure 2016-05-26 11:07:13 +02:00
Samy Pesse 0bce3517fd Bump version to 3.0.2 2016-05-25 21:50:38 +02:00
Samy Pesse 089815a8fd Fix #1328: fix crash when anchor links 2016-05-25 21:49:58 +02:00
41 changed files with 922 additions and 367 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.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
+21 -2
View File
@@ -71,11 +71,18 @@ Server.prototype.start = function(dir, port) {
// Redirect to directory's index.html
function redirect() {
var resultURL = urlTransform(req.url, function(parsed) {
parsed.pathname += '/';
return parsed;
});
res.statusCode = 301;
res.setHeader('Location', req.url + '/');
res.end('Redirecting to ' + req.url + '/');
res.setHeader('Location', resultURL);
res.end('Redirecting to ' + resultURL);
}
res.setHeader('X-Current-Location', req.url);
// Send file
send(req, url.parse(req.url).pathname, {
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;
+10
View File
@@ -22,6 +22,16 @@ describe('configSchema', function() {
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: {
+1 -1
View File
@@ -1,4 +1,4 @@
var FILENAME_REGEX = '^[a-z-._\d,\s]+$';
var FILENAME_REGEX = '^[a-zA-Z-._\d,\s]+$';
module.exports = {
'$schema': 'http://json-schema.org/schema#',
+4
View File
@@ -0,0 +1,4 @@
/*
All GitBook themes plugins name start with this prefix once shorted.
*/
module.exports = 'theme-';
+32 -14
View File
@@ -42,21 +42,39 @@ describe('PluginDependency', function() {
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
});
});
});
describe('listToArray', function() {
var list = PluginDependency.listToArray(Immutable.List([
PluginDependency.createFromString('hello@1.0.0'),
PluginDependency.createFromString('noversion'),
PluginDependency.createFromString('-disabled')
]));
describe('listToArray', function() {
it('must create an array from a list of plugin dependencies', function() {
var list = PluginDependency.listToArray(Immutable.List([
PluginDependency.createFromString('hello@1.0.0'),
PluginDependency.createFromString('noversion'),
PluginDependency.createFromString('-disabled')
]));
expect(list).toEqual([
'hello@1.0.0',
'noversion',
'-disabled'
]);
expect(list).toEqual([
'hello@1.0.0',
'noversion',
'-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');
});
});
});
});
+27 -6
View File
@@ -293,26 +293,47 @@ Book.prototype.getDefaultExt = function() {
/**
Infer the default path for a Readme.
@param {Boolean} [absolute=false] False for a path relative to
this book's content root
@return {String}
*/
Book.prototype.getDefaultReadmePath = function() {
return this.getContentRoot()+'README'+this.getDefaultExt();
Book.prototype.getDefaultReadmePath = function(absolute) {
var defaultPath = 'README'+this.getDefaultExt();
if (absolute) {
return path.join(this.getContentRoot(), defaultPath);
} else {
return defaultPath;
}
};
/**
Infer the default path for a Summary.
@param {Boolean} [absolute=false] False for a path relative to
this book's content root
@return {String}
*/
Book.prototype.getDefaultSummaryPath = function() {
return this.getContentRoot()+'SUMMARY'+this.getDefaultExt();
Book.prototype.getDefaultSummaryPath = function(absolute) {
var defaultPath = 'SUMMARY'+this.getDefaultExt();
if (absolute) {
return path.join(this.getContentRoot(), defaultPath);
} else {
return defaultPath;
}
};
/**
Infer the default path for a Glossary.
@param {Boolean} [absolute=false] False for a path relative to
this book's content root
@return {String}
*/
Book.prototype.getDefaultGlossaryPath = function() {
return this.getContentRoot()+'GLOSSARY'+this.getDefaultExt();
Book.prototype.getDefaultGlossaryPath = function(absolute) {
var defaultPath = 'GLOSSARY'+this.getDefaultExt();
if (absolute) {
return path.join(this.getContentRoot(), defaultPath);
} else {
return defaultPath;
}
};
/**
+53 -56
View File
@@ -19,21 +19,27 @@ Config.prototype.getValues = function() {
};
/**
Change the file for the configuration
* Render config as text
* @return {Promise<String>}
*/
Config.prototype.toText = function() {
return JSON.stringify(this.getValues().toJS(), null, 4);
};
@param {File} file
@return {Config}
*/
/**
* Change the file for the configuration
* @param {File} file
* @return {Config}
*/
Config.prototype.setFile = function(file) {
return this.set('file', file);
};
/**
Return a configuration value by its key path
@param {String} key
@return {Mixed}
*/
* Return a configuration value by its key path
* @param {String} key
* @return {Mixed}
*/
Config.prototype.getValue = function(keyPath, def) {
var values = this.getValues();
keyPath = Config.keyToKeyPath(keyPath);
@@ -46,12 +52,11 @@ Config.prototype.getValue = function(keyPath, def) {
};
/**
Update a configuration value
@param {String} key
@param {Mixed} value
@return {Config}
*/
* Update a configuration value
* @param {String} key
* @param {Mixed} value
* @return {Config}
*/
Config.prototype.setValue = function(keyPath, value) {
keyPath = Config.keyToKeyPath(keyPath);
@@ -64,10 +69,9 @@ Config.prototype.setValue = function(keyPath, value) {
};
/**
Return a list of plugin dependencies
@return {List<PluginDependency>}
*/
* Return a list of plugin dependencies
* @return {List<PluginDependency>}
*/
Config.prototype.getPluginDependencies = function() {
var plugins = this.getValue('plugins');
@@ -79,11 +83,10 @@ Config.prototype.getPluginDependencies = function() {
};
/**
Return a plugin dependency by its name
@param {String} name
@return {PluginDependency}
*/
* Return a plugin dependency by its name
* @param {String} name
* @return {PluginDependency}
*/
Config.prototype.getPluginDependency = function(name) {
var plugins = this.getPluginDependencies();
@@ -93,11 +96,10 @@ Config.prototype.getPluginDependency = function(name) {
};
/**
Update the list of plugins dependencies
@param {List<PluginDependency>}
@return {Config}
*/
* Update the list of plugins dependencies
* @param {List<PluginDependency>}
* @return {Config}
*/
Config.prototype.setPluginDependencies = function(deps) {
var plugins = PluginDependency.listToArray(deps);
@@ -106,11 +108,10 @@ Config.prototype.setPluginDependencies = function(deps) {
/**
Update values for an existing configuration
@param {Object} values
@returns {Config}
*/
* Update values for an existing configuration
* @param {Object} values
* @returns {Config}
*/
Config.prototype.updateValues = function(values) {
values = Immutable.fromJS(values);
@@ -118,12 +119,11 @@ Config.prototype.updateValues = function(values) {
};
/**
Update values for an existing configuration
@param {Config} config
@param {Object} values
@returns {Config}
*/
* Update values for an existing configuration
* @param {Config} config
* @param {Object} values
* @returns {Config}
*/
Config.prototype.mergeValues = function(values) {
var currentValues = this.getValues();
values = Immutable.fromJS(values);
@@ -134,12 +134,11 @@ Config.prototype.mergeValues = function(values) {
};
/**
Create a new config for a file
@param {File} file
@param {Object} values
@returns {Config}
*/
* Create a new config for a file
* @param {File} file
* @param {Object} values
* @returns {Config}
*/
Config.create = function(file, values) {
return new Config({
file: file,
@@ -148,11 +147,10 @@ Config.create = function(file, values) {
};
/**
Create a new config
@param {Object} values
@returns {Config}
*/
* Create a new config
* @param {Object} values
* @returns {Config}
*/
Config.createWithValues = function(values) {
return new Config({
values: Immutable.fromJS(values)
@@ -161,11 +159,10 @@ Config.createWithValues = function(values) {
/**
Convert a keyPath to an array of keys
@param {String|Array}
@return {Array}
*/
* Convert a keyPath to an array of keys
* @param {String|Array}
* @return {Array}
*/
Config.keyToKeyPath = function(keyPath) {
if (is.string(keyPath)) keyPath = keyPath.split('.');
return keyPath;
+4 -11
View File
@@ -1,9 +1,10 @@
var Immutable = require('immutable');
var TemplateBlock = require('./templateBlock');
var PREFIX = require('../constants/pluginPrefix');
var DEFAULT_VERSION = '*';
var PluginDependency = require('./pluginDependency');
var Plugin = Immutable.Record({
name: String(),
@@ -53,7 +54,7 @@ Plugin.prototype.getDepth = function() {
@return {String}
*/
Plugin.prototype.getNpmID = function() {
return Plugin.nameToNpmID(this.getName());
return PluginDependency.nameToNpmID(this.getName());
};
/**
@@ -152,14 +153,6 @@ Plugin.createFromDep = function(dep) {
});
};
/**
Return NPM id for a plugin name
@param {String}
@return {String}
*/
Plugin.nameToNpmID = function(s) {
return PREFIX + s;
};
Plugin.nameToNpmID = PluginDependency.nameToNpmID;
module.exports = Plugin;
+64 -29
View File
@@ -1,12 +1,14 @@
var is = require('is');
var semver = require('semver');
var Immutable = require('immutable');
var PREFIX = require('../constants/pluginPrefix');
var DEFAULT_VERSION = '*';
/*
PluginDependency represents the informations about a plugin
stored in config.plugins
*/
* PluginDependency represents the informations about a plugin
* stored in config.plugins
*/
var PluginDependency = Immutable.Record({
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 {Plugin|undefined}
*/
return this.set('enabled', state);
};
/**
* 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) {
if (is.undefined(enabled)) {
enabled = true;
@@ -48,11 +78,10 @@ PluginDependency.create = function(name, version, enabled) {
};
/**
Create a plugin from a string
@param {String}
@return {Plugin|undefined}
*/
* Create a plugin from a string
* @param {String}
* @return {Plugin|undefined}
*/
PluginDependency.createFromString = function(s) {
var parts = s.split('@');
var name = parts[0];
@@ -72,22 +101,20 @@ PluginDependency.createFromString = function(s) {
};
/**
Create a PluginDependency from a string
@param {String}
@return {List<PluginDependency>}
*/
* Create a PluginDependency from a string
* @param {String}
* @return {List<PluginDependency>}
*/
PluginDependency.listFromString = function(s) {
var parts = s.split(',');
return PluginDependency.listFromArray(parts);
};
/**
Create a PluginDependency from an array
@param {Array}
@return {List<PluginDependency>}
*/
* Create a PluginDependency from an array
* @param {Array}
* @return {List<PluginDependency>}
*/
PluginDependency.listFromArray = function(arr) {
return Immutable.List(arr)
.map(function(entry) {
@@ -95,8 +122,8 @@ PluginDependency.listFromArray = function(arr) {
return PluginDependency.createFromString(entry);
} else {
return PluginDependency({
name: entry.name,
version: entry.version
name: entry.get('name'),
version: entry.get('version')
});
}
})
@@ -106,11 +133,10 @@ PluginDependency.listFromArray = function(arr) {
};
/**
Export plugin dependencies as an array
@param {List<PluginDependency>} list
@return {Array<String>}
*/
* Export plugin dependencies as an array
* @param {List<PluginDependency>} list
* @return {Array<String>}
*/
PluginDependency.listToArray = function(list) {
return list
.map(function(dep) {
@@ -130,4 +156,13 @@ PluginDependency.listToArray = function(list) {
.toJS();
};
/**
* Return NPM id for a plugin name
* @param {String}
* @return {String}
*/
PluginDependency.nameToNpmID = function(s) {
return PREFIX + s;
};
module.exports = PluginDependency;
+32 -39
View File
@@ -43,10 +43,9 @@ TemplateBlock.prototype.getBlocks = function() {
/**
Return shortcuts associated with this block or undefined
@return {TemplateShortcut|undefined}
*/
* Return shortcuts associated with this block or undefined
* @return {TemplateShortcut|undefined}
*/
TemplateBlock.prototype.getShortcuts = function() {
var shortcuts = this.get('shortcuts');
if (shortcuts.size === 0) {
@@ -57,19 +56,17 @@ TemplateBlock.prototype.getShortcuts = function() {
};
/**
Return name for the nunjucks extension
@return {String}
*/
* Return name for the nunjucks extension
* @return {String}
*/
TemplateBlock.prototype.getExtensionName = function() {
return 'Block' + this.getName() + 'Extension';
};
/**
Return a nunjucks extension to represents this block
@return {Nunjucks.Extension}
*/
* Return a nunjucks extension to represents this block
* @return {Nunjucks.Extension}
*/
TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
blocksOutput = blocksOutput || {};
@@ -192,11 +189,11 @@ TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
};
/**
Apply a block to a content
@param {Object} inner
@param {Object} context
@return {Promise<String>|String}
*/
* Apply a block to a content
* @param {Object} inner
* @param {Object} context
* @return {Promise<String>|String}
*/
TemplateBlock.prototype.applyBlock = function(inner, context) {
var processFn = this.getProcess();
@@ -215,11 +212,10 @@ TemplateBlock.prototype.applyBlock = function(inner, context) {
};
/**
Normalize result from a block process function
@param {Object|String} result
@return {Object}
*/
* Normalize result from a block process function
* @param {Object|String} result
* @return {Object}
*/
TemplateBlock.prototype.normalizeBlockResult = function(result) {
if (is.string(result)) {
result = { body: result };
@@ -230,12 +226,11 @@ TemplateBlock.prototype.normalizeBlockResult = function(result) {
};
/**
Convert a block result to HTML
@param {Object} result
@param {Object} blocksOutput: stored post processing blocks in this object
@return {String}
*/
* Convert a block result to HTML
* @param {Object} result
* @param {Object} blocksOutput: stored post processing blocks in this object
* @return {String}
*/
TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
var indexedKey;
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
@param {String} blockName
@param {Object} block
@return {TemplateBlock}
*/
* Create a template block from a function or an object
* @param {String} blockName
* @param {Object} block
* @return {TemplateBlock}
*/
TemplateBlock.create = function(blockName, block) {
if (is.fn(block)) {
block = new Immutable.Map({
@@ -275,11 +269,10 @@ TemplateBlock.create = function(blockName, block) {
};
/**
Extract kwargs from an arguments array
@param {Array} args
@return {Object}
*/
* Extract kwargs from an arguments array
* @param {Array} args
* @return {Object}
*/
function extractKwargs(args) {
var last = args[args.length - 1];
return (is.object(last) && last.__keywords)? args.pop() : {};
+10 -12
View File
@@ -17,23 +17,21 @@ TemplateOutput.prototype.getBlocks = function() {
};
/**
Update content of this output
@param {String} content
@return {TemplateContent}
*/
* Update content of this output
* @param {String} content
* @return {TemplateContent}
*/
TemplateOutput.prototype.setContent = function(content) {
return this.set('content', content);
};
/**
Create a TemplateOutput from a text content
and an object containing block definition
@param {String} content
@param {Object} blocks
@return {TemplateOutput}
*/
* Create a TemplateOutput from a text content
* and an object containing block definition
* @param {String} content
* @param {Object} blocks
* @return {TemplateOutput}
*/
TemplateOutput.create = function(content, blocks) {
return new TemplateOutput({
content: content,
@@ -0,0 +1,32 @@
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 testDep = newConfig.getPluginDependency('disabled');
expect(testDep).toBeDefined();
expect(testDep.getVersion()).toEqual('*');
expect(testDep.isEnabled()).toBeTruthy();
});
it('should disable plugin', function() {
var newBook = togglePlugin(book, 'world');
var newConfig = newBook.getConfig();
var testDep = newConfig.getPluginDependency('world');
expect(testDep).toBeDefined();
expect(testDep.getVersion()).toEqual('*');
expect(testDep.isEnabled()).toBeFalsy();
});
});
+7 -7
View File
@@ -1,12 +1,12 @@
var PluginDependency = require('../../models/pluginDependency');
/**
Add a plugin to a book's configuration
@param {Book} book
@param {String} plugin
@param {String} version (optional)
@return {Book}
*/
/**
* Add a plugin to a book's configuration
* @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();
+16
View File
@@ -0,0 +1,16 @@
/**
* Edit ocnfiguration of a plugin
* @param {Book} book
* @param {String} plugin
* @param {Object} pluginConfig
* @return {Book}
*/
function editPlugin(book, pluginName, pluginConfig) {
var config = book.getConfig();
config = config.set('pluginsConfig.'+pluginName, pluginConfig);
return book.setConfig(config);
}
module.exports = editPlugin;
+4 -2
View File
@@ -1,5 +1,7 @@
module.exports = {
addPlugin: require('./addPlugin'),
removePlugin: require('./removePlugin')
addPlugin: require('./addPlugin'),
removePlugin: require('./removePlugin'),
togglePlugin: require('./togglePlugin'),
editPlugin: require('./editPlugin')
};
+5 -6
View File
@@ -1,11 +1,10 @@
/**
Remove a plugin from a book's configuration
@param {Book} book
@param {String} plugin
@return {Book}
*/
* Remove a plugin from a book's configuration
* @param {Book} book
* @param {String} plugin
* @return {Book}
*/
function removePlugin(book, pluginName) {
var config = book.getConfig();
var deps = config.getPluginDependencies();
+25
View File
@@ -0,0 +1,25 @@
/**
* Enable/disable a plugin dependency
* @param {Book} book
* @param {String} plugin
* @param {Boolean} state (optional)
* @return {Book}
*/
function togglePlugin(book, plugin, state) {
var config = book.getConfig();
var deps = config.getPluginDependencies();
deps = deps.map(function(dep) {
if (dep.getName() === plugin) {
return dep.toggle(state);
}
return dep;
});
config = config.setPluginDependencies(deps);
return book.setConfig(config);
}
module.exports = togglePlugin;
+49
View File
@@ -1,3 +1,4 @@
var fs = require('fs');
var generateMock = require('../generateMock');
var WebsiteGenerator = require('../website');
@@ -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() {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
+7 -1
View File
@@ -19,7 +19,13 @@ var generateBook = require('./generateBook');
function generateMock(Generator, files) {
var fs = createMockFS(files);
var book = Book.createForFS(fs);
var dir = tmp.dirSync();
var dir;
try {
dir = tmp.dirSync();
} catch(err) {
throw err;
}
book = book.setLogLevel('disabled');
+12 -7
View File
@@ -4,6 +4,7 @@ var Api = require('../api');
var Plugins = require('../plugins');
var Promise = require('../utils/promise');
var defaultBlocks = require('../constants/defaultBlocks');
var fileToOutput = require('./helper/fileToOutput');
var CODEBLOCK = 'code';
@@ -17,9 +18,13 @@ function getModifiers(output, page) {
var book = output.getBook();
var plugins = output.getPlugins();
var glossary = book.getGlossary();
var entries = glossary.getEntries();
var file = page.getFile();
// Glossary entries
var entries = glossary.getEntries();
var glossaryFile = glossary.getFile();
var glossaryFilename = fileToOutput(output, glossaryFile.getPath());
// Current file path
var currentFilePath = file.getPath();
@@ -34,18 +39,18 @@ function getModifiers(output, page) {
// Normalize IDs on headings
Modifiers.addHeadingId,
// Annotate text with glossary entries
Modifiers.annotateText.bind(null, entries, glossaryFilename),
// Resolve images
Modifiers.resolveImages.bind(null, currentFilePath),
// Resolve links (.md -> .html)
Modifiers.resolveLinks.bind(null,
currentFilePath,
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
Modifiers.highlightCode.bind(null, function(lang, source) {
return Promise(code.applyBlock({
@@ -12,7 +12,7 @@ describe('annotateText', function() {
it('should annotate text', function() {
var $ = cheerio.load('<p>This is a word, and multiple words</p>');
annotateText(entries, $);
annotateText(entries, 'GLOSSARY.md', $);
var links = $('a');
expect(links.length).toBe(2);
@@ -31,14 +31,14 @@ describe('annotateText', function() {
it('should not annotate scripts', function() {
var $ = cheerio.load('<script>This is a word, and multiple words</script>');
annotateText(entries, $);
annotateText(entries, 'GLOSSARY.md', $);
expect($('a').length).toBe(0);
});
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>');
annotateText(entries, $);
annotateText(entries, 'GLOSSARY.md', $);
expect($('a').length).toBe(0);
});
});
+12 -2
View File
@@ -40,9 +40,8 @@ describe('resolveLinks', function() {
});
describe('Anchor', function() {
var TEST = '<p>This is a <a href="test/cool.md#an-anchor"></a></p>';
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, $)
@@ -51,6 +50,17 @@ describe('resolveLinks', function() {
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');
});
});
});
describe('Custom Resolver', function() {
+3 -2
View File
@@ -62,9 +62,10 @@ function replaceText($, el, search, replace, text_only ) {
Annotate text using a list of GlossaryEntry
@param {List<GlossaryEntry>}
@param {String} glossaryFilePath
@param {HTMLDom} $
*/
function annotateText(entries, $) {
function annotateText(entries, glossaryFilePath, $) {
entries.forEach(function(entry) {
var entryId = entry.getID();
var name = entry.getName();
@@ -81,7 +82,7 @@ function annotateText(entries, $) {
) return;
replaceText($, this, searchRegex, function(match) {
return '<a href="/GLOSSARY.md#' + entryId + '" '
return '<a href="/' + glossaryFilePath + '#' + entryId + '" '
+ 'class="glossary-term" title="' + escape(description) + '">'
+ match
+ '</a>';
+9 -7
View File
@@ -30,16 +30,18 @@ function resolveLinks(currentFile, resolveFile, $) {
// Split anchor
var parsed = url.parse(href);
href = parsed.pathname;
href = parsed.pathname || '';
// Calcul absolute path for this
href = LocationUtils.toAbsolute(href, currentDirectory, '.');
if (href) {
// Calcul absolute path for this
href = LocationUtils.toAbsolute(href, currentDirectory, '.');
// Resolve file
href = resolveFile(href);
// Resolve file
href = resolveFile(href);
// Convert back to relative
href = LocationUtils.relative(currentDirectory, href);
// Convert back to relative
href = LocationUtils.relative(currentDirectory, href);
}
// Add back anchor
href = href + (parsed.hash || '');
+41 -10
View File
@@ -5,6 +5,25 @@ var Page = require('../models/page');
var walkSummary = require('./walkSummary');
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
@@ -12,10 +31,11 @@ var parsePage = require('./parsePage');
@return {Promise<OrderedMap<Page>>}
*/
function parsePagesList(book) {
var fs = book.getContentFS();
var summary = book.getSummary();
var glossary = book.getGlossary();
var map = Immutable.OrderedMap();
// Parse pages from summary
return timing.measure(
'parse.listPages',
walkSummary(summary, function(article) {
@@ -26,21 +46,32 @@ function parsePagesList(book) {
// Is the page ignored?
if (book.isContentFileIgnored(filepath)) return;
return fs.statFile(filepath)
.then(function(file) {
var page = Page.createForFile(file);
return parsePage(book, page);
})
return parseFilePage(book, filepath)
.then(function(page) {
map = map.set(filepath, page);
}, function() {
// 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;
});
}
+3 -5
View File
@@ -1,7 +1,6 @@
var parseStructureFile = require('./parseStructureFile');
var Summary = require('../models/summary');
var SummaryModifier = require('../modifiers').Summary;
var location = require('../utils/location');
/**
Parse summary in a book, the summary can only be parsed
@@ -27,11 +26,10 @@ function parseSummary(book) {
summary = Summary.createFromParts(file, result.parts);
}
// Insert readme as first entry
var firstArticle = summary.getFirstArticle();
// Insert readme as first entry if not in SUMMARY.md
var readmeArticle = summary.getByPath(readmeFile.getPath());
if (readmeFile.exists() &&
(!firstArticle || !location.areIdenticalPaths(firstArticle.getRef(), readmeFile.getPath()))) {
if (readmeFile.exists() && !readmeArticle) {
summary = SummaryModifier.unshiftArticle(summary, {
title: 'Introduction',
ref: readmeFile.getPath()
+29
View File
@@ -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);
});
});
+30
View File
@@ -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);
});
});
});
+22
View File
@@ -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');
});
});
});
+70
View File
@@ -0,0 +1,70 @@
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);
});
});
});
+47
View File
@@ -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;
+12 -113
View File
@@ -1,134 +1,32 @@
var npm = require('npm');
var npmi = require('npmi');
var semver = require('semver');
var Immutable = require('immutable');
var pkg = require('../../package.json');
var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
var Promise = require('../utils/promise');
var Plugin = require('../models/plugin');
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');
});
}
var installPlugin = require('./installPlugin');
/**
Install plugin requirements for a book
@param {Book}
@return {Promise}
@return {Promise<Number>}
*/
function installPlugins(book) {
var logger = book.getLogger();
var plugins = listForBook(book);
var config = book.getConfig();
var plugins = config.getPluginDependencies();
// Remove default plugins
// (only if version is same as installed)
plugins = plugins.filterNot(function(plugin) {
return (
DEFAULT_PLUGINS.includes(plugin.getName()) &&
plugin.getVersion() === pkg.dependencies[plugin.getNpmID()]
// Disabled plugin
!plugin.isEnabled() ||
// Or default one installed in GitBook itself
(DEFAULT_PLUGINS.includes(plugin.getName()) &&
plugin.getVersion() === pkg.dependencies[plugin.getNpmID()])
);
});
@@ -141,7 +39,8 @@ function installPlugins(book) {
return Promise.forEach(plugins, function(plugin) {
return installPlugin(book, plugin);
});
})
.thenResolve(plugins.size);
}
module.exports = installPlugins;
+4 -5
View File
@@ -3,6 +3,7 @@ var Promise = require('../utils/promise');
var listForBook = require('./listForBook');
var findForBook = require('./findForBook');
var loadPlugin = require('./loadPlugin');
var sortPlugins = require('./sortPlugins');
/**
@@ -27,11 +28,9 @@ function loadForBook(book) {
);
});
// Sort plugins to match list in book.json
installed = installed.sort(function(a, b){
return requirementsKeys.indexOf(a.getName()) < requirementsKeys.indexOf(b.getName()) ? -1 : 1;
});
return sortPlugins(installed, requirementsKeys);
})
.then(function(installed) {
// Log state
logger.info.ln(installed.size + ' plugins are installed');
if (requirements.size != installed.size) {
+71
View File
@@ -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;
+45
View File
@@ -0,0 +1,45 @@
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;
+51
View File
@@ -0,0 +1,51 @@
var TemplateEngine = require('../../models/templateEngine');
var TemplateBlock = require('../../models/templateBlock');
var renderTemplate = require('../render');
var postRender = require('../postRender');
describe('postRender', function() {
var testPost;
var engine = TemplateEngine.create({
blocks: [
TemplateBlock.create('lower', function(blk) {
return blk.body.toLowerCase();
}),
TemplateBlock.create('prefix', function(blk) {
return {
body: '_' + blk.body + '_',
post: function() {
testPost = true;
}
};
})
]
});
it('should correctly replace block', function() {
return renderTemplate(engine, 'README.md', 'Hello {% lower %}Samy{% endlower %}')
.then(function(output) {
expect(output.getContent()).toMatch(/Hello \{\{\-([\S]+)\-\}\}/);
expect(output.getBlocks().size).toBe(1);
return postRender(engine, output);
})
.then(function(result) {
expect(result).toBe('Hello samy');
});
});
it('should correctly replace blocks', function() {
return renderTemplate(engine, 'README.md', 'Hello {% lower %}Samy{% endlower %}{% prefix %}Pesse{% endprefix %}')
.then(function(output) {
expect(output.getContent()).toMatch(/Hello \{\{\-([\S]+)\-\}\}\{\{\-([\S]+)\-\}\}/);
expect(output.getBlocks().size).toBe(2);
return postRender(engine, output);
})
.then(function(result) {
expect(result).toBe('Hello samy_Pesse_');
expect(testPost).toBe(true);
});
});
});
+14 -14
View File
@@ -2,12 +2,12 @@ var Promise = require('../utils/promise');
/**
Replace position markers of blocks by body after processing
This is done to avoid that markdown/asciidoc processer parse the block content
@param {String} content
@return {Object} {blocks: Set, content: String}
*/
* Replace position markers of blocks by body after processing
* This is done to avoid that markdown/asciidoc processer parse the block content
*
* @param {String} content
* @return {Object} {blocks: Set, content: String}
*/
function replaceBlocks(content, blocks) {
var newContent = content.replace(/\{\{\-\%([\s\S]+?)\%\-\}\}/g, function(match, key) {
var replacedWith = match;
@@ -24,14 +24,14 @@ function replaceBlocks(content, blocks) {
}
/**
Post render a template:
- Execute "post" for blocks
- Replace block content
@param {TemplateEngine} engine
@param {TemplateOutput} content
@return {Promise<String>}
*/
* Post render a template:
* - Execute "post" for blocks
* - Replace block content
*
* @param {TemplateEngine} engine
* @param {TemplateOutput} content
* @return {Promise<String>}
*/
function postRender(engine, output) {
var content = output.getContent();
var blocks = output.getBlocks();
+8 -8
View File
@@ -4,14 +4,14 @@ var TemplateOutput = require('../models/templateOutput');
var replaceShortcuts = require('./replaceShortcuts');
/**
Render a template
@param {TemplateEngine} engine
@param {String} filePath: absolute path for the loader
@param {String} content
@param {Object} context
@return {Promise<TemplateOutput>}
*/
* Render a template
*
* @param {TemplateEngine} engine
* @param {String} filePath: absolute path for the loader
* @param {String} content
* @param {Object} context (optional)
* @return {Promise<TemplateOutput>}
*/
function renderTemplate(engine, filePath, content, context) {
context = context || {};
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "3.0.1",
"version": "3.1.0",
"homepage": "https://www.gitbook.com",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
@@ -21,15 +21,15 @@
"extend": "^3.0.0",
"fresh-require": "1.0.3",
"front-matter": "2.0.8",
"gitbook-asciidoc": "1.1.4",
"gitbook-markdown": "1.2.3",
"gitbook-plugin-fontsettings": "1.0.3",
"gitbook-asciidoc": "1.2.0",
"gitbook-markdown": "1.3.0",
"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-search": "2.2.1",
"gitbook-plugin-sharing": "1.0.2",
"gitbook-plugin-theme-default": "1.0.2",
"gitbook-plugin-theme-default": "1.0.4",
"github-slugid": "1.0.1",
"graceful-fs": "4.1.4",
"i18n-t": "1.0.0",
+13
View File
@@ -2,6 +2,7 @@ var is = require('is');
var path = require('path');
var fs = require('fs');
var expect = require('expect');
var cheerio = require('cheerio');
expect.extend({
/**
@@ -43,6 +44,18 @@ expect.extend({
'expected to be defined'
);
return this;
},
/**
Check that a dom element exists in HTML
@param {String} selector
*/
toHaveDOMElement: function(selector) {
var $ = cheerio.load(this.actual);
var $el = $(selector);
expect.assert($el.length > 0, 'expected HTML to contains %s', selector);
}
});